Skip to content

feat(cli): add llmux user commands (create/list/delete/reset-passphrase)#81

Merged
steve-krisjanovs merged 1 commit into
mainfrom
feat/user-cli-commands
Jul 1, 2026
Merged

feat(cli): add llmux user commands (create/list/delete/reset-passphrase)#81
steve-krisjanovs merged 1 commit into
mainfrom
feat/user-cli-commands

Conversation

@steve-krisjanovs

Copy link
Copy Markdown
Contributor

Summary

Found during the multi-pass sweep: the sign-in page's footer has told locked-out operators to run sudo llmux user reset-passphrase <username> since v2 auth shipped — that command never existed. There was no CLI recovery path at all for a locked-out sole admin (the web /api/admin/users endpoints require already being signed in as an admin).

Adds four verbs, mirroring the existing token CLI's pattern (direct filesystem access to the v2 store, no caller-identity concept — same trust model as token create/list/revoke):

  • user create <username> [--name N] [--admin] [--passphrase P]
  • user list [--json]
  • user delete <username> [--yes] — refuses to delete the last admin; revokes the user's tokens
  • user reset-passphrase <username> [--passphrase P] — admin-style override, revokes existing tokens

Also fixed the login footer's sudo — it's actively wrong, not just unnecessary: the standard deployment stores v2 data under the daemon operator's own $XDG_DATA_HOME, and sudo would resolve HOME to root's, silently pointing at the wrong store.

Test plan

  • npm run typecheck / npm run build clean
  • Live against the running daemon: create/list/duplicate-username/bad-username/short-passphrase all produce clean errors or output
  • Minted a token, confirmed 200; reset-passphrase → confirmed the old token now 401s
  • delete without --yes on non-TTY refuses cleanly; delete --yes removes + reports revoked-token count; delete of nonexistent user errors cleanly
  • --help renders the new verb block correctly
  • "refuses to delete the last admin" — verified by code inspection (direct copy of the already-shipped handleAdminDeleteUser logic), not live-repro'd — doing so safely would require reducing this machine's two real admin accounts to one

🤖 Generated with Claude Code

https://claude.ai/code/session_01Au8T9RPCb6wbgiEecQrBfZ

…ssphrase

The sign-in page's footer has told locked-out operators to run `sudo
llmux user reset-passphrase <username>` since v2 auth shipped. That
command never existed — `llmux user` returned "unknown command". There
was no CLI recovery path at all for a locked-out sole admin; the
equivalent web endpoints (/api/admin/users) require already being signed
in as an admin, which is exactly the thing you don't have if you're
locked out.

Added four verbs, mirroring the existing `token` CLI's pattern exactly
(daemon/handlers.ts: direct filesystem access to the v2 store — no
caller-identity concept, since shell access to the box IS the trust
boundary, same reasoning as token create/list/revoke):

- `user create <username> [--name N] [--admin] [--passphrase P]` —
  prompts for passphrase + confirmation if not given via flag/env
- `user list [--json]`
- `user delete <username> [--yes]` — refuses to delete the last admin
  (mirrors handleAdminDeleteUser's exact rule); revokes the user's
  tokens on delete
- `user reset-passphrase <username> [--passphrase P]` — admin-style
  override, no old passphrase needed; revokes the user's existing
  tokens (matches handleAdminResetPassphrase's behavior)

Also fixed the login page's footer note: it said `sudo llmux user
reset-passphrase` — sudo is actively wrong here, not just unnecessary.
The standard (user-mode) deployment stores v2 data under the daemon
operator's own $XDG_DATA_HOME; running via sudo would resolve HOME to
root's and silently operate on the wrong (nonexistent) store. Removed
sudo, added a note not to use it.

## Verification

typecheck + build clean. Live against the running daemon:
- create/list/duplicate-username/bad-username/short-passphrase all
  produce clean, correct errors or output
- minted a token for a test user, confirmed 200; reset-passphrase ->
  confirmed the old token now 401s (revocation works)
- delete without --yes on a non-TTY correctly refuses; delete --yes
  removes the user and reports revoked-token count; delete of a
  nonexistent user errors cleanly
- root --help renders the new verb block correctly

The "refuses to delete the last admin" guard is a direct copy of the
already-shipped, proven web logic (v2/auth/handlers.ts
handleAdminDeleteUser) — not independently live-tested here, since doing
so safely would require reducing this machine's real admin accounts
(stevekrisjanovs, orchtest) to one, which isn't warranted for a test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Au8T9RPCb6wbgiEecQrBfZ
@steve-krisjanovs steve-krisjanovs merged commit bdc1e60 into main Jul 1, 2026
1 check passed
@steve-krisjanovs steve-krisjanovs deleted the feat/user-cli-commands branch July 1, 2026 15:37
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