Skip to content

feat(cli): redesign command surface into 5 task-oriented groups - #8

Merged
Hureru merged 3 commits into
mainfrom
feat/cli-redesign
Jun 4, 2026
Merged

feat(cli): redesign command surface into 5 task-oriented groups#8
Hureru merged 3 commits into
mainfrom
feat/cli-redesign

Conversation

@Hureru

@Hureru Hureru commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Builds on #7 (now merged to main). This is a clean-break redesign of the CLI command surface — the "command fragmentation" that #7's correctness/security work intentionally left untouched. The diff is exactly one commit on top of current main.

What changes (clean break — no aliases)

  • ① Lifecycle: run / run --background (was daemon), stop, restart (new), status (rewritten), doctor.
  • ② Vault session (network): login now onboards — login + sync + client-side PIN setup = Remembered Device — and hands keys to the running agent; sync; forget.
  • ③ Lock (offline): unlock --method <auto|pin|hello|native> replaces the four bool flags; unlock --password removed (master password lives in login).
  • ④ Keys (offline object ops): keys is a unified offline view (identity + bound hosts + selector + ssh-config Include); keys bind|unbind|ui absorb the former bindings group.
  • ⑤ Integration (local): ssh-config show|write|remove (write is offline; merges --write/install/regenerate); startup enable|disable (was daemon --install/--uninstall) — enable refuses without a Remembered Device.

Removed (no aliases): daemon, unlock --password, the bindings group, ssh-config --write / install / uninstall / regenerate / status subverbs.

Notable internals

  • New ControlAction::Stop; the run_foreground select! loop breaks on it to run the normal cancel-token shutdown. stop/restart drive it.
  • status renders a readable Agent/Device/Lock/Bitwarden/Sync/Keys block with a state-driven Next hint, falling back to local files when the agent is down.
  • cmd_control now exits non-zero on daemon-down / command failure (was always exit 0).
  • Corrected misleading help + error strings; autostart entries now invoke run --background (so auto-start no longer calls the removed daemon).

Deliberately left out

  • build_status_response still composes the legacy jargon human-message, but the new cmd_status renders from the details JSON and never shows it — effectively demoted without touching daemon-side test expectations.
  • llmdoc/architecture/ipc-control-channel.md (historical analysis, disclaimed in README) left as-is; the canonical llmdoc/reference/ipc-control-protocol.md adds stop.

Verification

  • cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace — all clean.
  • Manually verified on Windows: full --help tree + precondition tags; unlock --method enum; keys offline view; status block (incl. agent-down fallback); stop idempotent (exit 0); daemon / unlock --password now error (exit 2).
  • Not yet exercised on real hardware: login PIN onboarding, restart background respawn, startup enable install — recommend a soak before release.

Breaking change

User-facing breaking change to the CLI. Docs (README, llmdoc guide + IPC reference, host-bindings docs) are updated in the same commit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Stop/Restart commands; run supports --background (replacing old daemon flow)
    • startup group to enable/disable auto-start (uses run --background)
    • Consolidated unlock with --method (auto|pin|hello|native); keys and ssh-config reorganized (bind/unbind/ui, show/write/remove)
  • Documentation

    • Updated README, BUILD, guides, and protocol docs with new CLI and startup wording
    • Adjusted release workflow instructions to the new run/login sequence

Clean break (no aliases), layered on fix/comprehensive-remediation to resolve
the command-surface fragmentation that branch left untouched.

Lifecycle:   run / run --background (was `daemon`), stop, restart (NEW),
             status (rewritten), doctor.
Session:     login now onboards (login + sync + client-side PIN setup =
             Remembered Device) and hands keys to the running agent; sync; forget.
Lock:        unlock --method <auto|pin|hello|native> replaces the four bool
             flags; `unlock --password` removed (master password lives in login).
Keys:        `keys` is an offline unified view (identity + bound hosts +
             selector + ssh-config Include); keys bind|unbind|ui absorb the
             former `bindings` group.
Integration: ssh-config show|write|remove (write is offline; merges
             --write/install/regenerate); startup enable|disable (was
             `daemon --install/--uninstall`); enable refuses without a
             Remembered Device.

Agent IPC:   add ControlAction::Stop; the run_foreground select! loop breaks on
             it to run the normal cancel-token shutdown. stop/restart drive it.

Legibility:  status renders a readable Agent/Device/Lock/Bitwarden/Sync/Keys
             block with a state-driven Next hint, falling back to local files
             when the agent is down; cmd_control exits non-zero on daemon-down /
             command failure; corrected misleading help + error strings;
             autostart entries now invoke `run --background`.

Docs:        README usage, llmdoc CLI guide + IPC reference (add `stop`), and
             host-bindings docs updated to the new surface.

cargo fmt / clippy -D warnings / test --workspace all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e83ebfd2-84ab-43fc-9a96-ed2b3d0b03dc

📥 Commits

Reviewing files that changed from the base of the PR and between d67e88b and d2fdf3f.

📒 Files selected for processing (2)
  • crates/sshwarden-agent/src/control.rs
  • src/main.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main.rs

📝 Walkthrough

Walkthrough

Refactors the CLI to a run/stop/restart/unlock-method/keys/ssh-config/startup surface, adds ControlAction::Stop and ControlUnreachable for graceful shutdown, rewires runtime/control handling, implements offline keys/ssh-config write, updates onboarding/pin flow, and aligns docs and platform autostart to use run --background.

Changes

CLI Command Restructuring

Layer / File(s) Summary
Documentation and release/build updates
.github/workflows/release.yml, BUILD.md, README.md, docs/host-bindings*.md, llmdoc/guides/how-to-use-cli-commands.md, llmdoc/reference/ipc-control-protocol.md
Update examples and prose throughout docs, build guide, and release notes to use run, run --background, unlock --method, keys ..., ssh-config write/remove, startup enable/disable, and include the new stop command in the IPC reference.
Control types and stop mapping
crates/sshwarden-agent/src/control.rs, llmdoc/reference/ipc-control-protocol.md
Adds pub struct ControlUnreachable { source: std::io::Error }, ControlAction::Stop, and maps "stop"ControlAction::Stop in dispatch.
Control client connection errors
crates/sshwarden-agent/src/control.rs
Windows and non-Windows send_control_command now wrap connection failures in ControlUnreachable via anyhow::Error.
Commands enum and CLI surface
src/main.rs (lines 142–271, 297–426)
Defines new Commands variants: Run { background }, Stop, Restart, Unlock { method: UnlockMethod }, Keys { action: Option<KeysAction> }, SshConfig { action: Option<SshConfigAction> }, Startup { action: StartupAction } and associated enums.
Main dispatch, control helpers, and status
src/main.rs (lines 558–839)
Rewires main dispatch, derives daemon-mode from Run { background }, adds daemon-contact/remembered-device helpers, improves cmd_control error messaging and cmd_status offline fallback with next-step guidance.
Process lifecycle handlers (stop/restart/startup)
src/main.rs (lines ~2990–3140, 6064–6140)
Implements cmd_stop/cmd_restart PID handling and background spawn; updates platform auto-start generation to use run --background and adds cmd_startup_enable wiring.
Run loop stop interception
src/main.rs (lines 3034–3042, 3515–3517)
Intercepts ControlAction::Stop in run_foreground to acknowledge and break for shutdown cleanup; adds defensive Stop arm in handle_control_command.
Login and PIN onboarding
src/main.rs (lines 1456–1557)
cmd_login updated to detect agent presence, show new messaging, and call offer_pin_setup which offers and sends a PIN to the agent via control.
Offline keys and ssh-config write
src/main.rs (lines 2382–2571, 2649–2665, 2696–2757)
Replaces network-backed keys/bindings with offline cmd_keys (list/bind/unbind/ui), adds cmd_sshcfg_write to regenerate managed snippet and ensure Include-line, and removes older regenerate/write/install paths.
Doctor, unlock, and sync messaging
src/main.rs (various)
Updates doctor guidance and user-facing messages to reference unlock --method pin and sshwarden login; updates do_sync missing-auth message to instruct sshwarden login.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A control loop hops with brand new grace,
Stop, restart, and bind in their rightful place,
Keys that unlock offline, a PIN for delight,
The CLI now sparkles, refactored just right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(cli): redesign command surface into 5 task-oriented groups' accurately reflects the main change, which reorganizes the CLI interface into five functional groups (Lifecycle, Vault session, Lock, Keys, Integration).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The 5-group redesign (07d710a) left a few baked-in strings and one
control-flow edge still pointing at the old surface:

- doctor migration hint: `unlock --pin` -> `unlock --method pin`
- generated ~/.ssh/sshwarden_config header: `bindings ...` ->
  `keys bind/unbind ...`
- `doctor --fix` include placeholder now points at `ssh-config write`
  (the command that actually populates the snippet, not the removed
  snippet-printing path)
- BUILD.md dev-run examples: `daemon` -> `run`, `unlock --hello` ->
  `unlock --method hello`

stop: a clean shutdown often tears down the control channel before its
reply reaches the client. cmd_stop no longer exits(2) on that
disconnect; it falls through to the existing 5s PID-file wait loop, so a
successful stop reports "Agent stopped." (exit 0) instead of a false
failure. A genuinely wedged agent still surfaces "still running after
5s" + exit 1.

cargo fmt / clippy -D warnings / test --workspace all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main.rs`:
- Around line 592-595: The current check using is_daemon_running() before
performing stop/restart makes control commands no-ops when the PID file is
missing or stale; instead, call the agent control/IPC path unconditionally
(e.g., attempt the control socket or send_control_command) in the stop/restart
command handling and only fall back to PID-file logic if the IPC attempt returns
an explicit "no daemon" error; update the branches around is_daemon_running()
and the sections that call out_line("No running SSHWarden agent.") so that stop
and restart first try to contact the running daemon (handle connection errors
and timeouts), and only if that contact proves the daemon is unreachable should
you treat it as not running and proceed with the existing fallback behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ae828ae8-989c-47dc-84e7-2c8acfd79cbf

📥 Commits

Reviewing files that changed from the base of the PR and between 30cd0e7 and d67e88b.

📒 Files selected for processing (9)
  • .github/workflows/release.yml
  • BUILD.md
  • README.md
  • crates/sshwarden-agent/src/control.rs
  • docs/host-bindings-followup.md
  • docs/host-bindings.md
  • llmdoc/guides/how-to-use-cli-commands.md
  • llmdoc/reference/ipc-control-protocol.md
  • src/main.rs

Comment thread src/main.rs Outdated
`cmd_stop`/`cmd_restart` were the only control commands gated on the
PID-file heuristic `is_daemon_running()`; every other command contacts
IPC directly. Since the PID file is created/removed independently of the
control pipe/socket, an externally deleted or stale PID file made `stop`
a silent no-op against a live daemon and -- worse -- made `restart` skip
stopping the running daemon and spawn a second agent that fights it for
the SSH endpoint and control pipe.

Contact the daemon over the control channel unconditionally and fall back
to "not running" only when the channel is genuinely unreachable:

- control.rs: add typed `ControlUnreachable` error, returned only on
  connect failure, so callers distinguish "no daemon" from "channel
  dropped mid-reply" (the expected outcome of a clean stop). Its Display
  matches the previous message, so other call sites are unchanged.
- main.rs: add `contact_daemon()` -- sends with a 5s timeout and classifies
  the result as Replied / Unconfirmed / NotRunning. `stop` short-circuits
  only on NotRunning; `restart` stops whenever IPC reaches a daemon,
  preventing the competing-daemon spawn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Hureru

Hureru commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Hureru
Hureru merged commit ceb3975 into main Jun 4, 2026
6 checks passed
@Hureru
Hureru deleted the feat/cli-redesign branch June 4, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant