cchost 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/lib/cli.js +16 -61
- package/lib/spawn.js +1 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,8 +18,7 @@ Requires Node.js 18+ and Claude Code 2.1.140+ already installed. Get Claude Code
|
|
|
18
18
|
| --- | --- |
|
|
19
19
|
| `cc add <profile> [--email=] [--org=] [--name=]` | Create a profile directory. Optional alias flags (EXPERIMENTAL, see below). |
|
|
20
20
|
| `cc use <profile> [...args]` | Launch Claude under that profile. Profile must already exist. Args after the name are forwarded verbatim to `claude`. |
|
|
21
|
-
| `cc list [--original] [--json]` | Show every profile
|
|
22
|
-
| `cc current [--json]` | Print the active profile (`CC_ACTIVE_PROFILE`) or "no profile active". |
|
|
21
|
+
| `cc list [--original] [--json]` | Show every profile: login state, account email, and storage mode (`shared` / `isolated`). |
|
|
23
22
|
| `cc migrate <src> <dest>` | Copy projects from `default`\|`shared` to `shared`\|`<profile>`. Skip-on-collision; credential files never copied. |
|
|
24
23
|
| `cc link <profile> [<profile>...]` | Link a profile's `projects/` and `sessions/` to `~/.claude-shared`. Migrates existing content; renames on collision. |
|
|
25
24
|
| `cc unlink <profile>` | Restore a profile to its own private `projects/` and `sessions/` (copies shared content back). |
|
|
@@ -34,10 +33,18 @@ cc use work # first launch: Claude prompts for login
|
|
|
34
33
|
cc use work --resume # resume last conversation under "work"
|
|
35
34
|
cc use personal -p "summarize" # one-shot prompt under "personal"
|
|
36
35
|
cc list
|
|
37
|
-
cc current
|
|
38
36
|
cc remove old-profile
|
|
39
37
|
```
|
|
40
38
|
|
|
39
|
+
`cc list` output:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Profile LoggedIn Email Storage
|
|
43
|
+
personal true personal@example.com isolated
|
|
44
|
+
scratch false - isolated
|
|
45
|
+
work true work@example.com shared
|
|
46
|
+
```
|
|
47
|
+
|
|
41
48
|
### Migrating existing projects
|
|
42
49
|
|
|
43
50
|
If you've been using Claude Code without profiles, your history lives in `~/.claude/projects/`. Pull it into a profile or the shared pool:
|
|
@@ -57,7 +64,6 @@ If you want each profile to keep separate credentials but share project history
|
|
|
57
64
|
```bash
|
|
58
65
|
cc link work
|
|
59
66
|
cc link personal
|
|
60
|
-
cc list # the Linked column shows "linked" vs "isolated"
|
|
61
67
|
```
|
|
62
68
|
|
|
63
69
|
`work/projects` and `personal/projects` become links to `~/.claude-shared/projects`. Same for `sessions/`. On Windows this uses directory junctions (no admin/Developer Mode required); on macOS/Linux, regular symlinks.
|
|
@@ -80,11 +86,11 @@ If two profiles share history and you launch them against the same project at th
|
|
|
80
86
|
|
|
81
87
|
1. Resolves the profile directory under `~/.claude-profiles/<name>/`
|
|
82
88
|
2. Applies any stored alias to `.claude.json` `oauthAccount` (best-effort)
|
|
83
|
-
3. Sets `CLAUDE_CONFIG_DIR`
|
|
89
|
+
3. Sets `CLAUDE_CONFIG_DIR` in its **own** spawn environment
|
|
84
90
|
4. Spawns `claude` with `stdio: 'inherit'` so the TUI works correctly
|
|
85
91
|
5. Forwards the child's exit code
|
|
86
92
|
|
|
87
|
-
The env
|
|
93
|
+
The env var is set on the child process only — that's all `claude` needs.
|
|
88
94
|
|
|
89
95
|
## Sensitive data
|
|
90
96
|
|
|
@@ -104,9 +110,6 @@ Earlier versions had bugs where Windows read `.claude.json` from `%USERPROFILE%`
|
|
|
104
110
|
**Profile name rejected**
|
|
105
111
|
Names must be free of path separators, whitespace, `..`, leading dots, and reserved DOS names (CON, PRN, COM1…). Stick to letters, digits, dashes, underscores.
|
|
106
112
|
|
|
107
|
-
**`cc current` says no profile is active inside a Claude session**
|
|
108
|
-
That means `CC_ACTIVE_PROFILE` isn't set in your shell — `cc use` sets it on the spawned `claude`, so it's visible to nested `cc current` invocations but not in unrelated shells.
|
|
109
|
-
|
|
110
113
|
**Old shell-script version was leaking keystrokes / leaking `use work` into the prompt**
|
|
111
114
|
That was a known bug in the cmd-based predecessor (`%*` ignored `shift`). This package forwards only the argv after `<profile>` to `claude`; the action and profile name themselves are consumed by `cc`.
|
|
112
115
|
|
package/lib/cli.js
CHANGED
|
@@ -16,8 +16,7 @@ const HELP = `cc - Multi-profile launcher for Claude Code
|
|
|
16
16
|
Usage:
|
|
17
17
|
cc add <profile> [--email=] [--org=] [--name=] Create a profile (EXPERIMENTAL aliases)
|
|
18
18
|
cc use <profile> [...args] Launch Claude under a named profile
|
|
19
|
-
cc list [--original] [--json] Show all profiles
|
|
20
|
-
cc current [--json] Show the active profile (from CC_ACTIVE_PROFILE)
|
|
19
|
+
cc list [--original] [--json] Show all profiles: login state, email, storage (shared/isolated)
|
|
21
20
|
cc migrate <src> <dest> Copy projects from default|shared to shared|<profile>
|
|
22
21
|
cc link <p>... Replace each profile's projects/sessions with links to shared
|
|
23
22
|
cc unlink <profile> Restore a profile's private projects/sessions
|
|
@@ -58,23 +57,17 @@ Examples:
|
|
|
58
57
|
`,
|
|
59
58
|
list: `cc list [--original] [--json]
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
Columns:
|
|
61
|
+
Profile - profile name
|
|
62
|
+
LoggedIn - true if .credentials.json exists
|
|
63
|
+
Email - account email (alias-aware; - if not yet logged in)
|
|
64
|
+
Storage - 'shared' if projects/ or sessions/ is linked to ~/.claude-shared,
|
|
65
|
+
'isolated' otherwise
|
|
64
66
|
|
|
65
67
|
By default, the displayed email reflects any alias set via 'cc add'.
|
|
66
68
|
With --original, shows the real value from .claude.json.
|
|
67
69
|
|
|
68
|
-
With --json, emits an array of
|
|
69
|
-
{name, dir, loggedIn, email, active, linked, shared:{projects, sessions}, alias}.
|
|
70
|
-
`,
|
|
71
|
-
current: `cc current [--json]
|
|
72
|
-
|
|
73
|
-
Print the value of CC_ACTIVE_PROFILE, or "no profile active" if it isn't set.
|
|
74
|
-
CC_ACTIVE_PROFILE is set by 'cc use' on the spawned claude, so nested cc
|
|
75
|
-
invocations see it.
|
|
76
|
-
|
|
77
|
-
With --json, emits {profile, configDir} (both null when nothing is active).
|
|
70
|
+
With --json, emits an array of {name, dir, loggedIn, email, storage, alias}.
|
|
78
71
|
`,
|
|
79
72
|
migrate: `cc migrate <src> <dest>
|
|
80
73
|
|
|
@@ -268,61 +261,32 @@ async function cmdUse(rest) {
|
|
|
268
261
|
return spawnClaude({
|
|
269
262
|
args: passthrough,
|
|
270
263
|
profileDir: dir,
|
|
271
|
-
activeProfile: name,
|
|
272
264
|
});
|
|
273
265
|
}
|
|
274
266
|
|
|
275
|
-
function cmdCurrent(rest) {
|
|
276
|
-
const { flags } = parseFlagsStrict(rest, ['--json']);
|
|
277
|
-
const value = process.env.CC_ACTIVE_PROFILE || '';
|
|
278
|
-
if (flags.has('--json')) {
|
|
279
|
-
const payload = value
|
|
280
|
-
? { profile: value, configDir: process.env.CLAUDE_CONFIG_DIR || profiles.profileDir(value) }
|
|
281
|
-
: { profile: null, configDir: null };
|
|
282
|
-
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
283
|
-
return 0;
|
|
284
|
-
}
|
|
285
|
-
console.log(value ? value : 'no profile active');
|
|
286
|
-
return 0;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
267
|
function cmdList(rest) {
|
|
290
268
|
const { flags } = parseFlagsStrict(rest, ['--json', '--original']);
|
|
291
269
|
const list = profiles.listProfiles();
|
|
292
|
-
const active = process.env.CC_ACTIVE_PROFILE || '';
|
|
293
270
|
const useOriginal = flags.has('--original');
|
|
294
271
|
|
|
295
272
|
const enriched = list.map((p) => {
|
|
296
|
-
const projectsLink = isLinkLike(path.join(p.dir, 'projects'));
|
|
297
|
-
const sessionsLink = isLinkLike(path.join(p.dir, 'sessions'));
|
|
298
|
-
const linked = projectsLink || sessionsLink;
|
|
299
273
|
const alias = p.alias || {};
|
|
300
274
|
const displayEmail = useOriginal ? p.email : (alias.email || p.email);
|
|
275
|
+
const projectsLink = isLinkLike(path.join(p.dir, 'projects'));
|
|
276
|
+
const sessionsLink = isLinkLike(path.join(p.dir, 'sessions'));
|
|
277
|
+
const storage = (projectsLink || sessionsLink) ? 'shared' : 'isolated';
|
|
301
278
|
return {
|
|
302
279
|
name: p.name,
|
|
303
280
|
dir: p.dir,
|
|
304
281
|
loggedIn: p.loggedIn,
|
|
305
282
|
email: displayEmail,
|
|
306
|
-
|
|
307
|
-
active: p.name === active,
|
|
308
|
-
linked,
|
|
309
|
-
shared: { projects: projectsLink, sessions: sessionsLink },
|
|
283
|
+
storage,
|
|
310
284
|
alias,
|
|
311
285
|
};
|
|
312
286
|
});
|
|
313
287
|
|
|
314
288
|
if (flags.has('--json')) {
|
|
315
|
-
|
|
316
|
-
name: p.name,
|
|
317
|
-
dir: p.dir,
|
|
318
|
-
loggedIn: p.loggedIn,
|
|
319
|
-
email: p.email,
|
|
320
|
-
active: p.active,
|
|
321
|
-
linked: p.linked,
|
|
322
|
-
shared: p.shared,
|
|
323
|
-
alias: p.alias,
|
|
324
|
-
}));
|
|
325
|
-
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
289
|
+
process.stdout.write(JSON.stringify(enriched, null, 2) + '\n');
|
|
326
290
|
return 0;
|
|
327
291
|
}
|
|
328
292
|
|
|
@@ -332,14 +296,13 @@ function cmdList(rest) {
|
|
|
332
296
|
return 0;
|
|
333
297
|
}
|
|
334
298
|
|
|
335
|
-
const rows = [['Profile', 'LoggedIn', 'Email', '
|
|
299
|
+
const rows = [['Profile', 'LoggedIn', 'Email', 'Storage']];
|
|
336
300
|
for (const p of enriched) {
|
|
337
301
|
rows.push([
|
|
338
302
|
p.name,
|
|
339
303
|
p.loggedIn ? 'true' : 'false',
|
|
340
|
-
p.email
|
|
341
|
-
p.
|
|
342
|
-
p.linked ? 'linked' : 'isolated',
|
|
304
|
+
p.email ? p.email : '-',
|
|
305
|
+
p.storage,
|
|
343
306
|
]);
|
|
344
307
|
}
|
|
345
308
|
console.log(renderTable(rows));
|
|
@@ -359,9 +322,6 @@ async function cmdRemove(rest) {
|
|
|
359
322
|
console.log('This will permanently delete:');
|
|
360
323
|
console.log(` ${dir}`);
|
|
361
324
|
console.log('including credentials, sessions, projects, plugins, and history.');
|
|
362
|
-
if (name === process.env.CC_ACTIVE_PROFILE) {
|
|
363
|
-
console.log(color.stderr.yellow(`Note: "${name}" is the currently active profile in this shell.`));
|
|
364
|
-
}
|
|
365
325
|
for (const sub of share.SHARED_SUBDIRS) {
|
|
366
326
|
const sub_p = path.join(dir, sub);
|
|
367
327
|
if (isLinkLike(sub_p)) {
|
|
@@ -379,9 +339,6 @@ async function cmdRemove(rest) {
|
|
|
379
339
|
throw new Error(`Failed to fully remove ${dir}: ${err.message}`);
|
|
380
340
|
}
|
|
381
341
|
console.log(`Removed ${dir}.`);
|
|
382
|
-
if (name === process.env.CC_ACTIVE_PROFILE) {
|
|
383
|
-
console.log('Note: the active profile in this shell no longer exists; run `cc use <name>` to switch.');
|
|
384
|
-
}
|
|
385
342
|
return 0;
|
|
386
343
|
}
|
|
387
344
|
|
|
@@ -437,8 +394,6 @@ async function run(argv) {
|
|
|
437
394
|
return await cmdUse(rest);
|
|
438
395
|
case 'list':
|
|
439
396
|
return cmdList(rest);
|
|
440
|
-
case 'current':
|
|
441
|
-
return cmdCurrent(rest);
|
|
442
397
|
case 'migrate':
|
|
443
398
|
return await cmdMigrate(rest);
|
|
444
399
|
case 'link':
|
package/lib/spawn.js
CHANGED
|
@@ -155,7 +155,7 @@ function platformSpawn(bin, args, opts) {
|
|
|
155
155
|
return spawn(bin, args, opts);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
function spawnClaude({ args = [], profileDir = null,
|
|
158
|
+
function spawnClaude({ args = [], profileDir = null, extraEnv = {} } = {}) {
|
|
159
159
|
const bin = findClaude();
|
|
160
160
|
if (!bin) {
|
|
161
161
|
const err = new Error(
|
|
@@ -174,11 +174,6 @@ function spawnClaude({ args = [], profileDir = null, activeProfile = null, extra
|
|
|
174
174
|
} else {
|
|
175
175
|
delete childEnv.CLAUDE_CONFIG_DIR;
|
|
176
176
|
}
|
|
177
|
-
if (activeProfile) {
|
|
178
|
-
childEnv.CC_ACTIVE_PROFILE = activeProfile;
|
|
179
|
-
} else {
|
|
180
|
-
delete childEnv.CC_ACTIVE_PROFILE;
|
|
181
|
-
}
|
|
182
177
|
|
|
183
178
|
return new Promise((resolve) => {
|
|
184
179
|
let child;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cchost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Host multiple isolated Claude Code accounts on one machine via the `cc` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"vitest": "^2.1.0"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|