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 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, login state, account email, active marker, and linked/isolated status. |
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` and `CC_ACTIVE_PROFILE` in its **own** spawn environment
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 vars are set on the child process only — that's all `claude` needs.
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, login state, email, active, linked
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
- Show every profile under ~/.claude-profiles/, whether it has credentials, the
62
- account email, which one matches CC_ACTIVE_PROFILE, and whether the profile
63
- is linked to ~/.claude-shared.
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
- realEmail: p.email,
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
- const payload = enriched.map((p) => ({
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', 'Active', 'Linked']];
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.active ? color.stdout.green('*') : '',
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, activeProfile = null, extraEnv = {} } = {}) {
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.0",
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
+ }