Files
sovereign_browser/plans/cli-flags.md
T

13 KiB

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 184 make_login_data("random", ...)"generate"
src/agent_login.c 425 error string listing methods
src/agent_login.c 440 strcmp(method, "random")"generate"
src/agent_login.c 451 error string listing methods
src/agent_mcp.c 137 login tool description + enum
src/agent_mcp.c 138 login tool JSON schema enum
src/agent_mcp.c 145 switch_identity tool description
src/agent_mcp.c 146 switch_identity tool JSON schema enum
tests/test_agent_login.py any test using method: "random"
README.md any documentation referencing "random"
.roorules the login example uses "method":"random"

The internal C function login_random() in 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() reads only argv[1] as an optional start URL (src/main.c:502). No getopt, no --help.
  • gtk_init(&argc, &argv) 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 and exercised by agent_login() 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 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() 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) 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

# 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_mcp.c, tests/test_agent_login.py, 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() 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:

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 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

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.