Files
sovereign_browser/plans/cli-flags.md
T

265 lines
13 KiB
Markdown

# CLI Flags Plan — sovereign_browser
## Rename: "random" → "generate"
As part of this work, rename the login method string `"random"` to
`"generate"` everywhere it appears. "random" implies an arbitrary method
choice; "generate" accurately describes what happens — a fresh local key
is generated and used for login.
Affected locations (from `grep`):
| File | Line | Change |
|---|---|---|
| [`src/agent_login.c`](src/agent_login.c:184) | 184 | `make_login_data("random", ...)``"generate"` |
| [`src/agent_login.c`](src/agent_login.c:425) | 425 | error string listing methods |
| [`src/agent_login.c`](src/agent_login.c:440) | 440 | `strcmp(method, "random")``"generate"` |
| [`src/agent_login.c`](src/agent_login.c:451) | 451 | error string listing methods |
| [`src/agent_mcp.c`](src/agent_mcp.c:137) | 137 | `login` tool description + enum |
| [`src/agent_mcp.c`](src/agent_mcp.c:138) | 138 | `login` tool JSON schema enum |
| [`src/agent_mcp.c`](src/agent_mcp.c:145) | 145 | `switch_identity` tool description |
| [`src/agent_mcp.c`](src/agent_mcp.c:146) | 146 | `switch_identity` tool JSON schema enum |
| [`tests/test_agent_login.py`](tests/test_agent_login.py) | — | any test using `method: "random"` |
| [`README.md`](README.md) | — | any documentation referencing "random" |
| `.roorules` | — | the `login` example uses `"method":"random"` |
The internal C function `login_random()` in
[`src/agent_login.c`](src/agent_login.c) can keep its name (internal) or
be renamed to `login_generate()` for consistency — recommend renaming
for clarity.
## Goal
Add command-line flags to `sovereign_browser` so that startup URLs, agent
server settings, session behavior, and — critically — Nostr login can be
controlled without the GTK login dialog. This enables headless/automated
use (agents, CI, scripting) while preserving the existing interactive
flow as the default.
## Current State
- [`main()`](src/main.c:493) reads only `argv[1]` as an optional start URL
([`src/main.c:502`](src/main.c:502)). No `getopt`, no `--help`.
- [`gtk_init(&argc, &argv)`](src/main.c:496) runs first and would consume
GTK's own flags; our parser must run **before** `gtk_init` so we can
strip our flags out of `argv` before GTK sees them (otherwise GTK aborts
on unknown options like `--login-method`).
- Login methods are enumerated in
[`key_store_method_t`](src/key_store.h:19) and exercised by
[`agent_login()`](src/agent_login.h:38) which already accepts a cJSON
params object. The CLI login path can reuse `agent_login()` directly —
it calls `app_set_signer()` and sets the same global state the GTK
dialog would.
- Settings live in [`browser_settings_t`](src/settings.h:25) and are
loaded from `~/.sovereign_browser/settings.conf`. CLI flags should
**override** settings, not replace them.
## Design Principles
1. **Reuse `agent_login()`** for CLI login — it already does everything
the GTK dialog does, returns structured results, and sets global state
via `app_set_signer()`. The CLI parser just builds the cJSON params
object from flags and calls it.
2. **Parse before `gtk_init()`** so GTK doesn't choke on our flags. We
strip recognized flags from `argc/argv` and pass the reduced vector to
`gtk_init()`.
3. **Flags override settings.conf** but do not write to it. A flag is a
one-shot override for this invocation.
4. **Login flags are mutually exclusive at the method level** — specify
one `--login-*` method; method-specific args are validated against it.
5. **`--login-method generate` is the zero-config path** — generates a
fresh key, logs in, and skips the dialog. Ideal for agents/CI.
6. **Backward compatible**`./sovereign_browser https://example.com`
still works as today (positional URL).
7. **Use `getopt_long`** (POSIX, available on Linux, C99-compatible).
No external deps.
## Proposed Flag Set
### Browser / Startup
| Flag | Arg | Description |
|---|---|---|
| `--url <url>` (repeatable) | URL | Open one or more URLs in tabs at startup. Positional URLs (existing behavior) are still accepted and appended after any `--url` flags. |
| `--new-tab-url <url>` | URL | Override `settings.new_tab_url` for this run (used by Ctrl+T / new-tab button). |
| `--no-session-restore` | — | Skip [`session_restore()`](src/session.c) even if `settings.restore_session` is true. |
| `--session-restore` | — | Force session restore even if disabled in settings. |
| `--max-tabs <n>` | int | Override `settings.max_tabs` for this run. |
| `--version`, `-V` | — | Print `SB_VERSION` (from [`src/version.h`](src/version.h)) and exit 0. |
| `--help`, `-h` | — | Print usage and exit 0. |
### Agent Server
| Flag | Arg | Description |
|---|---|---|
| `--port <port>` | int | Override `settings.agent_server_port` (default 17777). |
| `--no-agent` | — | Disable the agent MCP server for this run (overrides `settings.agent_server_enabled`). |
| `--agent` | — | Force-enable the agent server even if disabled in settings. |
| `--agent-origin <origin>` (repeatable) | string | Append to `agent_allowed_origins` for this run. |
### Login (mutually exclusive method flags)
Exactly one of these may be specified. If none is specified, the GTK
login dialog runs as today.
| Flag | Arg | Description |
|---|---|---|
| `--login-method <m>` | `generate\|local\|seed\|readonly\|nip46\|nsigner` | Select login method. Required to use any other `--login-*` flag. `generate` needs no further flags. |
| `--nsec <nsec1...>` | string | (local) nsec bech32 private key. |
| `--privkey <hex>` | string | (local) 64-char hex private key. Alternative to `--nsec`. |
| `--mnemonic <words>` | string | (seed) BIP-39 mnemonic, quoted ("word1 word2 ..."). |
| `--account <n>` | int | (seed) BIP-44 account index, default 0. |
| `--npub <npub1...>` | string | (readonly) npub bech32 public key. |
| `--pubkey <hex>` | string | (readonly) 64-char hex pubkey. Alternative to `--npub`. |
| `--bunker <url>` | URL | (nip46) `bunker://...` remote signer URL. |
| `--nsigner-transport <t>` | `serial\|unix\|tcp\|qrexec` | (nsigner) transport type. |
| `--nsigner-device <path>` | string | (nsigner) device path / socket / host:port / qube. |
| `--nsigner-service <name>` | string | (nsigner) qrexec service name (qrexec transport only). |
| `--nsigner-index <n>` | int | (nsigner) NIP-06 nostr_index, default 0. |
| `--no-save-identity` | — | Do not persist the CLI-provided identity to `~/.sovereign_browser/identity.json` (default: save, matching GTK dialog behavior). |
| `--login-timeout <ms>` | int | Override `settings.agent_login_timeout_ms`. Only meaningful when the GTK dialog is shown (no `--login-method`). |
### Diagnostics
| Flag | Arg | Description |
|---|---|---|
| `--verbose`, `-v` | — | Increase log verbosity (g_print messages). Repeatable for more detail. |
| `--quiet`, `-q` | — | Suppress non-error log output. |
## Usage Examples
```bash
# Existing behavior — still works
./sovereign_browser https://example.com
# Agent / CI: generated key, no dialog, open a page
./sovereign_browser --login-method generate --url https://example.com
# Local key from env, skip dialog, custom agent port
./sovereign_browser --login-method local --nsec "$NSEC" --port 18888
# Read-only (npub), no session restore, two tabs
./sovereign_browser --login-method readonly --npub npub1... \
--no-session-restore --url https://a.com --url https://b.com
# Seed phrase, account 1
./sovereign_browser --login-method seed \
--mnemonic "abandon abandon abandon ... about" --account 1
# NIP-46 remote signer
./sovereign_browser --login-method nip46 --bunker "bunker://..."
# n_signer hardware via qrexec
./sovereign_browser --login-method nsigner --nsigner-transport qrexec \
--nsigner-device nostr_signer --nsigner-service qubes.NsignerRpc
# Disable agent server, just browse
./sovereign_browser --no-agent --url https://example.com
# Version / help
./sovereign_browser --version
./sovereign_browser --help
```
## Implementation Plan
### Files Touched
| File | Change |
|---|---|
| `src/cli.h` (new) | Declares `cli_args_t` struct and `cli_parse()`. |
| `src/cli.c` (new) | Implements `getopt_long` parsing, validation, usage text, and `cli_login()` wrapper that builds cJSON and calls `agent_login()`. |
| `src/main.c` | Call `cli_parse(&argc, &argv)` **before** `gtk_init()`. Apply overrides to a mutable copy of settings. If `--login-method` was given, call `cli_login()` and skip `do_login()`. Replace `start_url` logic with the `--url` / positional list. |
| `Makefile` | Add `src/cli.o` to `OBJS`. |
| `README.md` | Document the flags. |
| `browser.sh` | Optional: accept extra args after `start`/`restart` and forward them to the binary (e.g. `./browser.sh start --login-method generate`). |
### Todo List
1. Rename "random" → "generate" in [`src/agent_login.c`](src/agent_login.c), [`src/agent_mcp.c`](src/agent_mcp.c), [`tests/test_agent_login.py`](tests/test_agent_login.py), [`README.md`](README.md), and `.roorules`. Rename internal `login_random()``login_generate()`.
2. Create `src/cli.h` with the `cli_args_t` struct and API.
3. Implement `src/cli.c`: `getopt_long` table, validation, `print_usage()`, `cli_login()` wrapper.
4. Wire `cli_parse()` into [`main()`](src/main.c:493) before `gtk_init()`; strip recognized flags from `argv`.
5. Apply CLI overrides to a mutable settings snapshot used for this run (port, agent enabled, max tabs, new-tab URL, session restore).
6. Implement `--url` (repeatable) + positional URL collection; pass list to tab manager at startup.
7. Implement `--login-method` path: build cJSON params, call `agent_login()`, check `success`, skip GTK dialog on success, exit 1 on failure.
8. Implement `--version` / `--help` (print and exit 0 before GTK init).
9. Implement `--no-save-identity` (skip `key_store_save()` on CLI login).
10. Update `Makefile` to compile `src/cli.c`.
11. Update `browser.sh` to forward extra args to the binary.
12. Update `README.md` with a CLI flags section.
13. Add a smoke test: `./sovereign_browser --login-method generate --no-agent --version` style checks (can be a shell test under `tests/`).
### Key Implementation Details
**Parse order:** `cli_parse()` must run before `gtk_init()` because GTK
aborts on unknown `--options`. `getopt_long` with `optind` lets us
repack `argv` so GTK only sees positional URLs. Pattern:
```c
cli_args_t args;
if (cli_parse(&argc, &argv, &args) != 0) {
return EXIT_FAILURE; /* --help / --version / parse error */
}
gtk_init(&argc, &argv); /* sees only positional URLs now */
```
**Login reuse:** `cli_login()` builds the exact cJSON shape documented in
[`agent_login.h`](src/agent_login.h:27) and calls `agent_login()`. On
`success: true`, the global state is already set via `app_set_signer()`,
so we set `g_logged_in = TRUE` and skip `do_login()`. On failure, print
the error message to stderr and exit non-zero.
**Settings override:** Introduce a `settings_apply_cli_overrides(const
cli_args_t *)` that mutates the global singleton in memory (not on disk)
after `settings_load()`. The rest of the code reads via
`settings_get()` unchanged.
**Repeatable `--url`:** Collect into a `GPtrArray` in `cli_args_t`.
After session restore fails (or is skipped), open each URL in its own
tab via `tab_manager_new_tab(url)`. If no URLs given, fall back to
`settings.new_tab_url`.
**`--no-save-identity`:** The GTK dialog path calls `key_store_save()`
after successful login. The CLI path should do the same by default so
the identity persists, but `--no-save-identity` skips it — useful for
ephemeral/CI runs that should not write to `~/.sovereign_browser/`.
## Mermaid: Startup Decision Flow
```mermaid
flowchart TD
A[main: cli_parse] --> B{help or version?}
B -- yes --> Z[print and exit 0]
B -- no --> C[gtk_init with stripped argv]
C --> D[settings_load + apply CLI overrides]
D --> E[agent_server_start unless --no-agent]
E --> F{login-method flag set?}
F -- yes --> G[cli_login: build cJSON, call agent_login]
G --> H{success?}
H -- no --> Y[print error, exit 1]
H -- yes --> I[optionally key_store_save unless --no-save-identity]
F -- no --> J[do_login: GTK dialog as today]
I --> K[session_restore unless --no-session-restore]
J --> K
K --> L{restored or URLs provided?}
L -- URLs --> M[open each --url / positional URL in a tab]
L -- none --> N[open settings.new_tab_url]
M --> O[gtk_main]
N --> O
```
## Open Questions
1. Should `--login-method generate` auto-set `--no-save-identity` by default
(since a generated key is usually ephemeral)? Proposal: **no** — keep
explicit, but document that random keys *will* be saved unless
`--no-save-identity` is passed.
2. Should we support reading `--nsec` / `--mnemonic` from a file path
(e.g. `--nsec-file /run/secrets/nsec`) to avoid leaking secrets in
`ps`/shell history? Proposal: defer to a follow-up; out of scope for
this plan but worth noting.
3. `--headless` (no GTK window, agent server only)? WebKitGTK requires a
display; true headless would need a virtual framebuffer or a non-WebKit
path. Out of scope for this plan.