SLOPSHOPPER

accounts

Run different Claude accounts side by side: one config directory per account, bound per project, switched with /account.

newcommandstatusprocess
★ 2v0.1.0no licenseupdated 2026-10-14fagenorn/nix-config/home/common/claude-code/accounts
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · accounts
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /account ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ accounts: account:
README

fagenorn/nix-config

Repo contains configuration for personal machines, mostly running nix-darwin. I have no idea what I'm doing, and the deeper I go the less of a clue I have apparently.

Originally forked from ironicbadger/nix-config; now maintained independently.

Source 1 files
hooks/register.ts 59 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3const COMMAND = 'account'
4const PASSED_ON = new Set(['list', 'status', 'add', 'use'])
5
6export const register: Register = on => {
7  on('session.start', async ($, e, next) => {
8    await $.command.register({
9      name: COMMAND,
10      description: 'Show Claude accounts, or bind this project to one',
11      argumentHint: '[<name> | default | add <name> | status]',
12      immediate: true,
13    })
14    const active = await run($, ['active'])
15    if (active.isOk) $.ui.status(`account: ${active.text}`)
16
17    return next(e)
18  })
19
20  on('command.run', { command: COMMAND }, async ($, e) => {
21    const words = e.args.trim().split(/\s+/).filter(Boolean)
22    const [first = 'list'] = words
23    const argv = PASSED_ON.has(first) ? words.length ? words : ['list'] : ['use', first]
24    const ran = await run($, argv)
25    if (!ran.isOk || argv[0] !== 'use') return { text: ran.text }
26
27    const active = await run($, ['active'])
28    if (!active.isOk || active.text === argv[1]) {
29      if (active.isOk) $.ui.status(`account: ${active.text}`)
30      return { text: ran.text }
31    }
32
33    // Ends this session's claude; the launcher resumes it under the new account.
34    $.ui.status(`account: switching to ${argv[1]}`)
35    const switched = await run($, ['switch', argv[1], await $.session.id()])
36    if (switched.isOk) return { text: `${ran.text}\nSwitching this session to '${argv[1]}'…` }
37
38    $.ui.status(`account: ${active.text} (${argv[1]} after restart)`)
39    return {
40      text: `${ran.text}\nThis session stays on '${active.text}': ${switched.text.replace(/^claude-account: /, '')}`,
41    }
42  })
43}
44
45type Ran = { isOk: boolean; text: string }
46
47async function run($: EngineInterface, argv: string[]): Promise<Ran> {
48  try {
49    const { exitCode, stdout, stderr } = await $.process.run([
50      `${$.plugin.root}/bin/claude-account`,
51      ...argv,
52    ])
53
54    return { isOk: exitCode === 0, text: (exitCode === 0 ? stdout : stderr || stdout).trim() }
55  } catch (error) {
56    return { isOk: false, text: `claude-account did not run: ${String(error)}` }
57  }
58}
59