Skip to content

feat(multi-account): codev account CLI + global-default (Batch 2a + 2e)#123

Open
grimmerk wants to merge 5 commits into
mainfrom
feat/codev-account-cli
Open

feat(multi-account): codev account CLI + global-default (Batch 2a + 2e)#123
grimmerk wants to merge 5 commits into
mainfrom
feat/codev-account-cli

Conversation

@grimmerk

@grimmerk grimmerk commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Batch 2a + 2e of multi-account support (follows #122 / Batch 1).

  • 2a — codev account CLI: manage the account registry + shell integration from the terminal, replacing Batch 1's hand-editing of ~/.config/codev/accounts.json + accounts.sh.
  • 2e — configurable global-default: codev account default <label> makes bare claude resolve to a chosen account; CodeV resume now bypasses the shell dispatcher so it can't be misrouted.
  • Test infra: introduces Vitest + a generator test suite and a real CI workflow (the repo had none — test_ci.yaml was a placeholder).

What's included

  • src/cli/account-manager.ts — the single shared generator: reads/writes accounts.json, generates accounts.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: buildResumeCommand emits command claude.

How to use

Run in dev via yarn account <cmd> (a real codev binary on PATH lands in Batch 2b):

yarn account list              # list configured accounts + identity
yarn account show              # dry-run: print the generated accounts.sh (writes nothing)
yarn account add work          # register a new account (dir defaults to ~/.claude-work)
yarn account default work      # bare `claude` now resolves to work
yarn account remove work       # unregister (leaves the dir on disk)
yarn account regenerate        # rewrite ~/.config/codev/accounts.sh from the registry
yarn account install|uninstall # add/remove the ~/.zshrc source block

After add/default/remove: open a new shell or source ~/.zshrc.

How to test

Automated: yarn test (9/9). CI runs it on PRs to main.

Manual smoke-test (needs yarn make — 2e is a main-process change):

  1. CLI, read-only/safe: yarn account list, yarn account show.
  2. Generator equivalence: yarn account show output ≈ your current accounts.sh (functionally equal — verified via diff + zsh -n).
  3. 2e resume routing (the important one):
    • yarn account default work (sets bare claude → work), source ~/.zshrc.
    • In CodeV, resume a personal session → must still open under personal (not work). The command claude fix guarantees this.
    • Resume a work session → opens under work.
    • Restore with yarn account default personal when done.

Verification done

  • Generated accounts.sh is functionally equivalent to the hand-written Batch 1 file (diff) and passes zsh -n.
  • tsc --noEmit: 0 errors. yarn test: 9/9.
  • 2e command claude is behavior-unchanged when the default is the anchor, and a no-op for single-account users.

Notes

  • This does not overwrite your live accounts.sh — run yarn account regenerate when you want the generated version.
  • 2c (per-launch account picker) is coupled with Batch 3 (cross-account sharing) — see design doc §6.G.

🤖 Generated with Claude Code


Summary by cubic

Add the codev account CLI and a shared generator for multi-account setup with a configurable global default for bare claude, plus hardened shell integration and safer resume routing.

  • New Features

    • One shared generator (src/cli/account-manager.ts) reads/writes ~/.config/codev/accounts.json and generates accounts.sh (to be reused by the Settings UI).
    • New CLI (src/cli/codev-account.ts): list, add <label> [--dir D], default <label>, remove <label>, regenerate, show, install, uninstall (run in dev with yarn account <cmd>).
    • Global default: codev account default <label> sets where bare claude resolves; dispatcher fallback honors this. Test infra added with vitest and a real CI workflow (yarn test).
  • Bug Fixes

    • Correctly parse labels when using add --dir <path> <label>.
    • Reject shell-unsafe and non-string/empty values in account dir and configDirEnv at add time and during accounts.sh generation.
    • Launch via env -u CLAUDE_CONFIG_DIR claude for the default and env CLAUDE_CONFIG_DIR=… claude for others (bypasses the dispatcher and prevents hijack).
    • Resume escapes single quotes in paths and uses command claude to avoid misrouting; dropped hardcoded defaultAccount fallback and only set it when valid.

Written for commit f7d4b4c. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a new codev account command to list, add, set default, remove, regenerate, preview, and install/uninstall multi-account setup.
    • Introduced automatic generation of account launcher scripts and guided shell integration for switching accounts.
    • Added Vitest test setup and scripts, plus a full CI workflow to run the suite.
  • Bug Fixes
    • Improved resume behavior to ensure sessions use the correct account configuration, with safer command generation for non-default accounts.
  • Documentation
    • Updated the multi-account support design notes for the new batch progress.

grimmerk and others added 2 commits July 6, 2026 14:02
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
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds shared account-management logic and a codev account CLI, updates resume command generation, adds Vitest coverage/config, replaces CI, and refreshes versioning and design docs.

Changes

Multi-account management feature

Layer / File(s) Summary
Registry contract and persistence
src/cli/account-manager.ts
Defines registry/account types, path constants, shell-path helpers, identity parsing, and registry read/write normalization.
Shell script generation and hook management
src/cli/account-manager.ts
Generates accounts.sh, regenerates it to disk, and installs or removes the zsh source block in ~/.zshrc.
Account mutation operations
src/cli/account-manager.ts
Implements add, remove, default selection, and account listing against the registry, with regeneration after changes.
codev-account CLI entrypoint
src/cli/codev-account.ts
Adds command parsing, list output, subcommand dispatch, usage/help text, reload hints, and error handling for codev account.
account-manager Vitest test suite
src/cli/account-manager.test.ts
Adds Vitest coverage for shell generation, shell-safety validation, and default-account resolution.
Resume command routing fix
src/claude-session-utility.ts
Changes resume command generation to call command claude --resume and escape embedded quotes in config paths.
CI workflow, config, and docs
.github/workflows/test_ci.yaml, vitest.config.ts, package.json, CHANGELOG.md, docs/multi-account-support-design.md
Replaces the workflow with a Yarn test job, adds Vitest config, updates version/scripts/dev dependency, and revises changelog/design notes for Batch 2 progress.

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
Loading

Possibly related PRs

  • grimmerk/codev#102: Also changes resume command construction in src/claude-session-utility.ts, with overlapping claude --resume routing behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: the new codev account CLI and global-default multi-account work.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/codev-account-cli

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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between ce29c11 and d77f2f9.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • .github/workflows/test_ci.yaml
  • CHANGELOG.md
  • docs/multi-account-support-design.md
  • package.json
  • src/claude-session-utility.ts
  • src/cli/account-manager.test.ts
  • src/cli/account-manager.ts
  • src/cli/codev-account.ts
  • vitest.config.ts

Comment thread src/claude-session-utility.ts
Comment thread src/cli/account-manager.ts
Comment thread src/cli/account-manager.ts Outdated
Comment thread src/cli/account-manager.ts Outdated
Comment thread src/cli/codev-account.ts

@cubic-dev-ai cubic-dev-ai 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.

7 issues found across 10 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/cli/account-manager.ts Outdated
Comment thread src/cli/account-manager.ts Outdated
Comment thread src/cli/account-manager.ts
Comment thread src/cli/codev-account.ts Outdated
Comment thread src/claude-session-utility.ts
Comment thread src/cli/account-manager.ts Outdated
Comment thread vitest.config.ts
- 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

@cubic-dev-ai cubic-dev-ai 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.

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

Comment thread src/cli/account-manager.ts Outdated
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

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

🧹 Nitpick comments (1)
src/cli/account-manager.test.ts (1)

76-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't isolate the dir check.

Both dir and configDirEnv are set to the same unsafe string, so this test can't distinguish whether the thrown error comes from the dir validation or the configDirEnv validation (which is already covered separately at Line 105-119). Use a clean configDirEnv here to truly isolate the dir check.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d77f2f9 and 3c9b0a4.

📒 Files selected for processing (4)
  • src/claude-session-utility.ts
  • src/cli/account-manager.test.ts
  • src/cli/account-manager.ts
  • src/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

@cubic-dev-ai cubic-dev-ai 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.

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

Comment thread src/cli/account-manager.ts Outdated
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
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