feat(multi-account): codev account CLI + global-default (Batch 2a + 2e)#123
feat(multi-account): codev account CLI + global-default (Batch 2a + 2e)#123grimmerk wants to merge 5 commits into
Conversation
Add a `codev account` CLI that manages ~/.config/codev/accounts.json and regenerates accounts.sh from ONE shared generator, replacing Batch 1's hand-editing. Run in dev via `yarn account <cmd>`. - src/cli/account-manager.ts: shared registry read/write + accounts.sh generator (the 2b Settings UI will reuse the same generator) - src/cli/codev-account.ts: CLI — list/add/default/remove/regenerate/ show/install/uninstall - src/cli/account-manager.test.ts: Vitest suite for the generator (default -> no env, extra -> CLAUDE_CONFIG_DIR, dispatcher * -> defaultAccount, whoami mapping, bash -n validity) - Replace the placeholder CI with a real test workflow (yarn test) - Bump 1.0.77 + CHANGELOG + design-doc §6.A/§7 Verified: generated accounts.sh is functionally equivalent to the hand-written one (diff) and passes `zsh -n`; 9/9 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
buildResumeCommand now emits `command claude` (bypassing the accounts.sh `claude` dispatcher) so a configured non-anchor global-default can't re-route an anchor-account resume to the wrong account. Behavior is unchanged when the default IS the anchor; a no-op for single-account users (command claude == claude). 2e's shell + CLI side already shipped in 2a (`codev account default` plus the dispatcher `*)` -> defaultAccount). Also documents the 2c <-> Batch 3 coupling (cross-account resume needs shared session data). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
📝 WalkthroughWalkthroughAdds shared account-management logic and a ChangesMulti-account management feature
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Manager as account-manager
participant Registry as accounts.json
participant ShellScript as accounts.sh
participant Zshrc as ~/.zshrc
Manager->>Registry: readRegistry
Manager->>Manager: generateAccountsSh
Manager->>ShellScript: regenerate writes script
Manager->>Zshrc: installShellHook appends source block
Manager->>Zshrc: uninstallShellHook removes source block
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/claude-session-utility.ts`:
- Around line 1081-1093: The resume command builder is interpolating
CLAUDE_CONFIG_DIR without escaping, so paths containing quotes can break the
shell command. Update buildResumeCommand to shell-escape the configDir value
returned by getResumeConfigDirEnv before placing it into the single-quoted
prefix, keeping the rest of the command assembly unchanged.
In `@src/cli/account-manager.ts`:
- Around line 155-210: The generated shell snippet in account-manager’s
formatter block has Prettier violations in the repeated L.push() assembly for
the claude launcher script. Update the code in this section to match Prettier’s
formatting expectations, especially around array-like argument lists and
trailing commas in multiline constructs, so the generated content passes
prettier/prettier checks. Use the L.push sequence that builds the claude,
claude-whoami, and claude-accounts functions as the place to apply the
formatting cleanup.
- Around line 94-102: `readRegistry()` is initializing a new registry with a
hardcoded defaultAccount of 'personal', which prevents `addAccount()` from
selecting the first added account as the default and can leave stale defaults in
place. Update `readRegistry()` so a missing registry starts without any default
account, and ensure `addAccount()` remains responsible for setting the default
only when none exists; use the existing `Registry` shape and `defaultAccount`
handling in `readRegistry` and `addAccount` to locate the change.
- Around line 142-145: The `opts.dir` value is being written into `accounts.sh`
and later sourced, so it must not be interpolated raw into shell content. Update
the account generation flow in `launchPrefix` and the
`claude-whoami`/`claude-accounts` builders to either reject unsafe `--dir`
values up front or escape them with strict shell-safe quoting before persisting.
Make sure the fix covers any path that can contain quotes or shell expansion
characters so the generated wrapper and case entries cannot be broken or
evaluated unexpectedly.
In `@src/cli/codev-account.ts`:
- Around line 73-83: The add command in codev-account.ts is picking the wrong
account label when --dir appears before the label because the label scan
includes the directory value; update the label selection logic in the add case
so it ignores the argument immediately following --dir while still using
getFlag(rest, '--dir') for the directory. Keep the fix local to the add branch
and verify manager.addAccount receives the actual label token (for example, the
work name) instead of the path.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 96ee50bd-1912-4983-be6b-c251cde9bd3f
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (9)
.github/workflows/test_ci.yamlCHANGELOG.mddocs/multi-account-support-design.mdpackage.jsonsrc/claude-session-utility.tssrc/cli/account-manager.test.tssrc/cli/account-manager.tssrc/cli/codev-account.tsvitest.config.ts
There was a problem hiding this comment.
7 issues found across 10 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- add: skip the --dir value when picking the label, so `add --dir /foo work` registers `work` (was misread as `/foo`) - shell safety: assertSafeDir rejects shell-metachar dirs at add time AND at generation, so a crafted --dir / hand-edited registry can't inject code into the sourced accounts.sh - default account: launch via `env -u CLAUDE_CONFIG_DIR claude` so a stray exported CLAUDE_CONFIG_DIR can't hijack it; extra accounts use `env CLAUDE_CONFIG_DIR=... claude` (env also bypasses the dispatcher) - resume: single-quote-escape configDir so an apostrophe in the path can't break buildResumeCommand - registry: drop the hardcoded defaultAccount='personal'; set it only when it doesn't name a real account (no dangling reference) - prettier-format the new cli files; +1 injection-rejection test (10/10) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/cli/account-manager.ts">
<violation number="1" location="src/cli/account-manager.ts:172">
P1: Sourcing generated accounts.sh is still injectable via `configDirEnv` from `accounts.json`. The new guard validates `a.dir` only, so validating `a.configDirEnv` before rendering would close this path.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The dir guard didn't cover configDirEnv, which launchCmd embeds as the launcher's CLAUDE_CONFIG_DIR — a hand-edited registry could pass a clean dir but an injectable configDirEnv. Validate both at generation. (cubic P1 follow-up.) +1 test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/cli/account-manager.test.ts (1)
76-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest doesn't isolate the
dircheck.Both
dirandconfigDirEnvare set to the same unsafe string, so this test can't distinguish whether the thrown error comes from thedirvalidation or theconfigDirEnvvalidation (which is already covered separately at Line 105-119). Use a cleanconfigDirEnvhere to truly isolate thedircheck.♻️ Proposed fix
it('rejects an account dir with shell-unsafe characters', () => { const bad = reg({ defaultAccount: 'evil', accounts: [ { label: 'evil', dir: '/tmp/$(touch pwned)', identityFile: '/tmp/x/.claude.json', - configDirEnv: '/tmp/$(touch pwned)', + configDirEnv: '/tmp/clean', isDefault: false, }, ], }); expect(() => generateAccountsSh(bad)).toThrow(/Unsafe shell characters/); });🤖 Prompt for 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. In `@src/cli/account-manager.test.ts` around lines 76 - 90, The generateAccountsSh test for shell-unsafe paths is not isolating the dir validation because the account fixture in account-manager.test.ts sets both dir and configDirEnv to the same unsafe value. Update the test case around generateAccountsSh to keep dir unsafe while using a clean configDirEnv, so the assertion specifically exercises the dir check and does not overlap with the separate configDirEnv validation already covered elsewhere.
🤖 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.
Nitpick comments:
In `@src/cli/account-manager.test.ts`:
- Around line 76-90: The generateAccountsSh test for shell-unsafe paths is not
isolating the dir validation because the account fixture in
account-manager.test.ts sets both dir and configDirEnv to the same unsafe value.
Update the test case around generateAccountsSh to keep dir unsafe while using a
clean configDirEnv, so the assertion specifically exercises the dir check and
does not overlap with the separate configDirEnv validation already covered
elsewhere.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7458d333-fe41-4a24-97e7-54d99e40ed3f
📒 Files selected for processing (4)
src/claude-session-utility.tssrc/cli/account-manager.test.tssrc/cli/account-manager.tssrc/cli/codev-account.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/claude-session-utility.ts
- src/cli/codev-account.ts
- src/cli/account-manager.ts
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/cli/account-manager.ts">
<violation number="1" location="src/cli/account-manager.ts:177">
P2: A hand-edited accounts.json with a non-string `configDirEnv` can still crash `regenerate` after this validation step. Since this path is explicitly defending against edited registries, it would be safer to reject non-string or empty `configDirEnv` values before calling `expandHome`/`assertSafeDir`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
A hand-edited accounts.json with a non-string or empty configDirEnv slipped past the truthy guard and would crash toShellPath. assertSafeDir now rejects non-string/empty values, and the loop validates every non-null configDirEnv. (cubic P2 follow-up.) +1 test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
Summary
Batch 2a + 2e of multi-account support (follows #122 / Batch 1).
codev accountCLI: manage the account registry + shell integration from the terminal, replacing Batch 1's hand-editing of~/.config/codev/accounts.json+accounts.sh.codev account default <label>makes bareclauderesolve to a chosen account; CodeV resume now bypasses the shell dispatcher so it can't be misrouted.test_ci.yamlwas a placeholder).What's included
src/cli/account-manager.ts— the single shared generator: reads/writesaccounts.json, generatesaccounts.sh(per-account launchers +claude <account>dispatcher +claude-whoami+claude-accounts). The Batch 2b Settings UI will reuse this same module (no second generator).src/cli/codev-account.ts— the CLI entry.src/cli/account-manager.test.ts— Vitest suite (9 tests).vitest.config.ts,.github/workflows/test_ci.yaml(real CI),package.json(yarn account,yarn test).src/claude-session-utility.ts— 2e:buildResumeCommandemitscommand claude.How to use
Run in dev via
yarn account <cmd>(a realcodevbinary on PATH lands in Batch 2b):After add/default/remove: open a new shell or
source ~/.zshrc.How to test
Automated:
yarn test(9/9). CI runs it on PRs tomain.Manual smoke-test (needs
yarn make— 2e is a main-process change):yarn account list,yarn account show.yarn account showoutput ≈ your currentaccounts.sh(functionally equal — verified via diff +zsh -n).yarn account default work(sets bareclaude→ work),source ~/.zshrc.command claudefix guarantees this.yarn account default personalwhen done.Verification done
accounts.shis functionally equivalent to the hand-written Batch 1 file (diff) and passeszsh -n.tsc --noEmit: 0 errors.yarn test: 9/9.command claudeis behavior-unchanged when the default is the anchor, and a no-op for single-account users.Notes
accounts.sh— runyarn account regeneratewhen you want the generated version.🤖 Generated with Claude Code
Summary by cubic
Add the
codev accountCLI and a shared generator for multi-account setup with a configurable global default for bareclaude, plus hardened shell integration and safer resume routing.New Features
src/cli/account-manager.ts) reads/writes~/.config/codev/accounts.jsonand generatesaccounts.sh(to be reused by the Settings UI).src/cli/codev-account.ts):list,add <label> [--dir D],default <label>,remove <label>,regenerate,show,install,uninstall(run in dev withyarn account <cmd>).codev account default <label>sets where bareclauderesolves; dispatcher fallback honors this. Test infra added withvitestand a real CI workflow (yarn test).Bug Fixes
add --dir <path> <label>.dirandconfigDirEnvat add time and duringaccounts.shgeneration.env -u CLAUDE_CONFIG_DIR claudefor the default andenv CLAUDE_CONFIG_DIR=… claudefor others (bypasses the dispatcher and prevents hijack).command claudeto avoid misrouting; dropped hardcodeddefaultAccountfallback and only set it when valid.Written for commit f7d4b4c. Summary will update on new commits.
Summary by CodeRabbit
codev accountcommand to list, add, set default, remove, regenerate, preview, and install/uninstall multi-account setup.