# 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 ` (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 | 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 ` | 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 ` | 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 ` (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 ` | `generate\|local\|seed\|readonly\|nip46\|nsigner` | Select login method. Required to use any other `--login-*` flag. `generate` needs no further flags. | | `--nsec ` | string | (local) nsec bech32 private key. | | `--privkey ` | string | (local) 64-char hex private key. Alternative to `--nsec`. | | `--mnemonic ` | string | (seed) BIP-39 mnemonic, quoted ("word1 word2 ..."). | | `--account ` | int | (seed) BIP-44 account index, default 0. | | `--npub ` | string | (readonly) npub bech32 public key. | | `--pubkey ` | string | (readonly) 64-char hex pubkey. Alternative to `--npub`. | | `--bunker ` | URL | (nip46) `bunker://...` remote signer URL. | | `--nsigner-transport ` | `serial\|unix\|tcp\|qrexec` | (nsigner) transport type. | | `--nsigner-device ` | string | (nsigner) device path / socket / host:port / qube. | | `--nsigner-service ` | string | (nsigner) qrexec service name (qrexec transport only). | | `--nsigner-index ` | 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 ` | 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.