claude-use 2.0.4 → 2.2.0

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.
Files changed (3) hide show
  1. package/README.md +12 -4
  2. package/dist/cli.cjs +120 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -363,6 +363,14 @@ In both modes, *where* a toggle is written depends on `$PWD` at invocation time,
363
363
 
364
364
  `claude-use check` (below) shows you which of the three would apply before you commit to a change, if you're unsure.
365
365
 
366
+ #### Which `claude-use` a bare command name resolves to
367
+
368
+ `doctor`'s PATH-resolution check answers a question no other check does: is the `claude-use` your shell runs the same executable as the one producing this report? It scans PATH for the filename a bare `claude-use` would resolve to, using the same `findPathShadow` scan `shim enable` already uses for `claude`, and compares the first hit against the running executable's own PATH-visible location — collapsing the verdict back to a pass when both names turn out to be the same real file reached through a symlink.
369
+
370
+ An earlier PATH entry winning is a **failure**, not a warning, because it invalidates the rest of the report rather than sitting alongside it: every other finding describes the binary that produced it, which in that state is not the binary your commands reach. The failure mode it exists to catch is entirely silent otherwise — a wrapper script or an abandoned install directory from an earlier channel keeps working at whatever version it was frozen at, so nothing looks broken until a config file written by the newer version trips the older one's own validation. That is not hypothetical: a hand-written wrapper from an earlier install channel, sitting ahead of `~/.local/bin` on PATH, kept re-execing a month-old binary whose copy of `IdentitySchema` predated the naming rule widening to allow `@` — so an `identity.json` a current claude-use had written was rejected outright, with nothing anywhere reporting that the running binary was not the installed one.
371
+
372
+ The two softer verdicts are warnings rather than failures. The running executable's own directory not being on PATH at all is legitimate (an absolute-path invocation, or `npx`), and an enabled `claude` shim being shadowed still leaves the launcher reachable as `claude-use run`.
373
+
366
374
  ### Debugging: `claude-use check`
367
375
 
368
376
  `claude-use check [path] [--identity <name>]` resolves the full cascade for the given path (default `$PWD`) and identity (default the active one), and prints the result — every entry's resolved state, which layer decided it, and which condition (if any) was evaluated and how — without touching the farm or spawning `claude` at all. This is the primary way to answer "why is X shared/hidden here" without launching a session to find out. For any `history/projects/` glob override in scope, it also flags whenever the pattern's encoded form could plausibly match more than one real path (see [Pattern matching](#pattern-matching-against-claudeprojects)), rather than resolving that ambiguity silently.
@@ -375,7 +383,7 @@ It also runs three checks that don't depend on `path` at all, every time, so a r
375
383
 
376
384
  ### Debugging: `claude-use doctor`
377
385
 
378
- Where `claude-use check` resolves one directory+identity's cascade in detail, `claude-use doctor` audits the whole `~/.claude-use` config graph at once — identity/directory-agnostic, no arguments needed. It validates every identity's `identity.json`, every configuration profile's own `extends` chain (catching a missing profile name or a circular `extends` before a launch would), `directory-rules.json`, `config.json`, `categories.local.json`, and `active-identity`, each against its own Zod schema and cross-referenced against each other (an identity's `defaultConfigProfile`, a directory rule's `identity`/`configProfile`, actually pointing at something real) — plus whether a real Claude Code binary is discoverable at all, whether the `claude` command shim is enabled and its recorded location still exists, and the same ambient-credential check `check` runs. One malformed file is reported as its own failure rather than aborting the rest of the audit, and the command exits non-zero if anything failed — useful as a scriptable "is everything still consistent" gate, not just an interactive debugging aid.
386
+ Where `claude-use check` resolves one directory+identity's cascade in detail, `claude-use doctor` audits the whole `~/.claude-use` config graph at once — identity/directory-agnostic, no arguments needed. It validates every identity's `identity.json`, every configuration profile's own `extends` chain (catching a missing profile name or a circular `extends` before a launch would), `directory-rules.json`, `config.json`, `categories.local.json`, and `active-identity`, each against its own Zod schema and cross-referenced against each other (an identity's `defaultConfigProfile`, a directory rule's `identity`/`configProfile`, actually pointing at something real) — plus whether a real Claude Code binary is discoverable at all, whether the `claude` command shim is enabled and its recorded location still exists, **which `claude-use` a bare command name actually resolves to** (below), and the same ambient-credential check `check` runs. One malformed file is reported as its own failure rather than aborting the rest of the audit, and the command exits non-zero if anything failed — useful as a scriptable "is everything still consistent" gate, not just an interactive debugging aid.
379
387
 
380
388
  ## Examples
381
389
 
@@ -490,7 +498,7 @@ src/
490
498
  directoryRules.ts # `claude-use rules` subcommands
491
499
  configure.ts # `claude-use configure` interactive picker (@clack/prompts)
492
500
  check.ts # `claude-use check` dry-run inspector — cascade resolution, ambient-credential/Keychain/settings-secrets diagnostics — no farm writes, no spawn
493
- doctor.ts # `claude-use doctor` whole-tree audit — every identity/profile/extends-chain/directory-rules/config.json/categories.local.json/active-identity, aggregating rather than throwing on a broken file
501
+ doctor.ts # `claude-use doctor` whole-tree audit — every identity/profile/extends-chain/directory-rules/config.json/categories.local.json/active-identity, plus which `claude-use` PATH actually resolves to, aggregating rather than throwing on a broken file
494
502
  claudeShim.ts # `claude-use shim enable`/`disable` — the one explicit action that creates/removes a `claude`-named hardlink of the running executable; records claude-shim.json
495
503
  cli/
496
504
  parsers.ts # shared CLI-flag parsing helpers (splitTopLevelCommas, parsePair, repeatable-flag collectors)
@@ -617,11 +625,11 @@ the resolver's cascade and materialisation logic is exactly the kind of thing th
617
625
  - Conditional entries with injectable/fake mtimes, a fake resolved branch, and a fake env snapshot (never real filesystem/git/environment state, so tests aren't time-dependent, git-dependent, or slow) — a `newerThan` condition including a fresh file and excluding a stale one under the same glob, a `branch` condition applying only on a matching branch, an `env` condition applying only when the right variable is set, and a conditionally-matched subtree always being materialised rather than symlinked
618
626
  - A materialised directory reconciling any real (non-symlink) children written since the last resync back into `~/.claude` before re-deciding, and collapsing back into a plain symlink once its split condition no longer holds
619
627
 
620
- `identityManager.ts`, `configProfiles.ts`, `directoryRules.ts`, and `configure.ts` stay thin adapters over the resolver, so most of their correctness rides on the resolver's own test coverage above. `launcher.ts` carries three separately-testable responsibilities of its own that aren't covered by the resolver's purity, and need their own coverage: translating a resolved `Map<path, boolean>` into real filesystem side effects (creating/removing symlinks, materialising/collapsing directories, diffing against the farm's prior state, the per-identity lock and atomic-swap behaviour from [Directory rules](#directory-rules)) against a fake/in-memory filesystem; invoking the real `claude` binary via an injected `spawn` function (argv/env construction, exit-code propagation), never a real subprocess in a unit test; and the ambient-credential guard — given a fake `process.env`, refusing to proceed when any of the six named variables is set and the active identity's `allowAmbientCredential` is unset/false, proceeding when it's true, and proceeding when `CLAUDE_USE_ALLOW_AMBIENT_CREDENTIAL=1` is set for that one call regardless of the identity's own setting.
628
+ `identityManager.ts`, `configProfiles.ts`, `directoryRules.ts`, and `configure.ts` stay thin adapters over the resolver, so most of their correctness rides on the resolver's own test coverage above. The one exception is `listIdentities`, whose own tests cover a deliberate departure from the "throw a validation error and let it propagate" convention: an `identity.json` that is present but unreadable — malformed JSON, or valid JSON this version's `IdentitySchema` rejects — is reported as that one identity's own unreadable entry, so a single bad file never hides every *other* identity from `claude-use identity list` at the moment they most need to be visible. Only those two content-shaped failures are absorbed; a permission error still propagates. A wholly *absent* `identity.json` remains a silent skip rather than a problem, and both it and `doctor`'s own enumeration filter out directories whose name starts with `.`, since `IdentitySchema` requires an identity name to start with a letter or digit and a resync's own `.<identity>.scratch.<suffix>`/`.<identity>.previous.<suffix>` directories are therefore never identities to report on. `launcher.ts` carries three separately-testable responsibilities of its own that aren't covered by the resolver's purity, and need their own coverage: translating a resolved `Map<path, boolean>` into real filesystem side effects (creating/removing symlinks, materialising/collapsing directories, diffing against the farm's prior state, the per-identity lock and atomic-swap behaviour from [Directory rules](#directory-rules)) against a fake/in-memory filesystem; invoking the real `claude` binary via an injected `spawn` function (argv/env construction, exit-code propagation), never a real subprocess in a unit test; and the ambient-credential guard — given a fake `process.env`, refusing to proceed when any of the six named variables is set and the active identity's `allowAmbientCredential` is unset/false, proceeding when it's true, and proceeding when `CLAUDE_USE_ALLOW_AMBIENT_CREDENTIAL=1` is set for that one call regardless of the identity's own setting.
621
629
 
622
630
  `check.ts`'s three always-on diagnostics get their own tests too, independent of path/cascade resolution: the ambient-credential check against a fake `process.env` (same fixture as `launcher.ts`'s guard, since they share the same detection logic); the settings-secrets advisory against a fake settings.json with populated `env`/`hooks` fields, confirming it reports counts and key names only, never values; and — since Keychain access is real OS state, not something to fake — a manual/integration-only note that the Keychain-name lookup is exercised against a real `security` call in CI on macOS runners, not unit-tested with a mock.
623
631
 
624
- `doctor.ts` deliberately breaks the "throw a validation error and let it propagate" convention every other command file follows, since aggregating every check into one report — rather than aborting on the first broken file — is the whole point of the command. Its own tests cover this directly: every input (identities, configuration profiles, `directory-rules.json`, `config.json`, `categories.local.json`, `active-identity`) fed simultaneously malformed at once, asserting `runDoctor` still returns a full report with one `fail` finding per broken input rather than throwing, plus a genuine `extends` cycle correctly failing and a genuine diamond correctly not being mistaken for one. Its wiring layer sets `process.exitCode` rather than throwing or calling `process.exit()` when the report contains any failure — this is new to the codebase and not unit-tested, matching `registerCheckCommand`'s own I/O wiring being untested for the same reason.
632
+ `doctor.ts` deliberately breaks the "throw a validation error and let it propagate" convention every other command file follows, since aggregating every check into one report — rather than aborting on the first broken file — is the whole point of the command. Its own tests cover this directly: every input (identities, configuration profiles, `directory-rules.json`, `config.json`, `categories.local.json`, `active-identity`) fed simultaneously malformed at once, asserting `runDoctor` still returns a full report with one `fail` finding per broken input rather than throwing, plus a genuine `extends` cycle correctly failing and a genuine diamond correctly not being mistaken for one. The PATH-resolution section gets its own coverage for each of its three verdicts at each of the two names it reports on (a shadowed `claude-use` failing and naming both paths, a not-on-PATH executable warning instead, and an enabled-but-shadowed `claude` shim warning rather than failing), alongside `refinePathShadow`'s own unit tests for the symlink case a directory comparison alone would misreport as a shadow of itself. Its wiring layer sets `process.exitCode` rather than throwing or calling `process.exit()` when the report contains any failure — this is new to the codebase and not unit-tested, matching `registerCheckCommand`'s own I/O wiring being untested for the same reason.
625
633
 
626
634
  `claudeShim.test.ts` follows `identityManager.test.ts`'s real-temp-directory convention (a fake "own executable" file standing in for the running `claude-use` binary), rather than `doctor.ts`'s pure-function style, since `enableClaudeShim`/`disableClaudeShim` are themselves real filesystem operations, not something to keep separate from a thin wiring layer. Coverage includes the version-drift case that motivates persisting `claude-shim.json` at all (the source file overwritten in place between two `shim enable` runs, proving the marker — not the inode — is what lets the second run refresh cleanly instead of refusing), a foreign file at the target being refused without `--force` and accepted with it, and the cross-device (`EXDEV`) copy-fallback path via a small injectable `LinkFs` seam (mirroring `config/store.ts`'s own `StoreFs`/`nodeStoreFs` pattern), since a real cross-filesystem rig isn't practical in CI. One test also reproduces Homebrew's actual layout (a symlink into a separate "Cellar" directory) to confirm the shim lands next to the symlink users invoke, not buried in the directory its realpath resolves to.
627
635
 
package/dist/cli.cjs CHANGED
@@ -221874,7 +221874,7 @@ var categories_default_default = {
221874
221874
  // package.json
221875
221875
  var package_default = {
221876
221876
  name: "claude-use",
221877
- version: "2.0.4",
221877
+ version: "2.2.0",
221878
221878
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
221879
221879
  license: "Apache-2.0",
221880
221880
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -241780,7 +241780,7 @@ var GlobalConfigSchema = external_exports.strictObject({
241780
241780
  });
241781
241781
  var IdentitySchema = external_exports.strictObject({
241782
241782
  $schema: external_exports.string().optional(),
241783
- name: external_exports.string().min(1).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/),
241783
+ name: external_exports.string().min(1).regex(/^[A-Za-z0-9][A-Za-z0-9._@-]*$/),
241784
241784
  defaultConfigProfile: external_exports.string().min(1).optional(),
241785
241785
  allowAmbientCredential: external_exports.boolean().default(false)
241786
241786
  });
@@ -245706,7 +245706,7 @@ var IdentityAlreadyExistsError = class extends CliError {
245706
245706
  var InvalidIdentityNameError = class extends CliError {
245707
245707
  constructor(attemptedName) {
245708
245708
  super(
245709
- `"${attemptedName}" is not a valid identity name \u2014 identity names must start with a letter or number and may only contain letters, numbers, dots, hyphens, and underscores.`
245709
+ `"${attemptedName}" is not a valid identity name \u2014 identity names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
245710
245710
  );
245711
245711
  this.attemptedName = attemptedName;
245712
245712
  this.name = "InvalidIdentityNameError";
@@ -245804,18 +245804,33 @@ function readActiveIdentity(paths) {
245804
245804
  const raw = import_node_fs5.default.readFileSync(paths.activeIdentityFile, "utf8").trim();
245805
245805
  return raw === "" ? void 0 : raw;
245806
245806
  }
245807
+ function isIdentityDirectoryName(name) {
245808
+ return !name.startsWith(".");
245809
+ }
245810
+ function readIdentityForListing(paths, name) {
245811
+ try {
245812
+ return readIdentity(paths, name);
245813
+ } catch (error62) {
245814
+ if (error62 instanceof ConfigValidationError || error62 instanceof SyntaxError) {
245815
+ return { problem: error62.message.replace(/\s*\n\s*/g, " ") };
245816
+ }
245817
+ throw error62;
245818
+ }
245819
+ }
245807
245820
  function listIdentities(paths) {
245808
245821
  if (!import_node_fs5.default.existsSync(paths.identitiesDir)) {
245809
245822
  return [];
245810
245823
  }
245811
245824
  const active = readActiveIdentity(paths);
245812
- const names = import_node_fs5.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
245825
+ const names = import_node_fs5.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort();
245813
245826
  const result = [];
245814
245827
  for (const name of names) {
245815
- const identity = readIdentity(paths, name);
245816
- if (identity !== void 0) {
245817
- result.push({ name, identity, isActive: name === active });
245828
+ const read = readIdentityForListing(paths, name);
245829
+ if (read === void 0) {
245830
+ continue;
245818
245831
  }
245832
+ const isActive = name === active;
245833
+ result.push("problem" in read ? { name, isActive, problem: read.problem } : { name, identity: read, isActive });
245819
245834
  }
245820
245835
  return result;
245821
245836
  }
@@ -245888,10 +245903,17 @@ function registerIdentityCommand(program2, paths) {
245888
245903
  }
245889
245904
  for (const entry of entries) {
245890
245905
  const marker = entry.isActive ? "* " : " ";
245906
+ if (entry.problem !== void 0) {
245907
+ console.log(`${marker}${entry.name} [unreadable: ${entry.problem}]`);
245908
+ continue;
245909
+ }
245891
245910
  const defaultProfile = entry.identity.defaultConfigProfile !== void 0 ? ` (default profile: ${entry.identity.defaultConfigProfile})` : "";
245892
245911
  const ambient = entry.identity.allowAmbientCredential ? " [allows ambient credential]" : "";
245893
245912
  console.log(`${marker}${entry.name}${defaultProfile}${ambient}`);
245894
245913
  }
245914
+ if (entries.some((entry) => entry.problem !== void 0)) {
245915
+ console.log("\nRun `claude-use doctor` for the full detail on every unreadable entry.");
245916
+ }
245895
245917
  });
245896
245918
  identity.command("set-default-profile <identity> <profile>").description("Set an identity's default configuration profile.").action(async (identityName, profileName) => {
245897
245919
  if (readProfile(paths, profileName) === void 0) {
@@ -246277,13 +246299,13 @@ var CATEGORY_LABELS = {
246277
246299
  knowledge: "knowledge",
246278
246300
  settings: "settings"
246279
246301
  };
246280
- var PROFILE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
246302
+ var PROFILE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
246281
246303
  function validateProfileName(value, existingNames) {
246282
246304
  if (value.length === 0) {
246283
246305
  return "A name is required.";
246284
246306
  }
246285
246307
  if (!PROFILE_NAME_RE.test(value)) {
246286
- return "Names must start with a letter or digit, and contain only letters, digits, dots, dashes, and underscores.";
246308
+ return "Names must start with a letter or digit, and contain only letters, digits, dots, dashes, underscores, and at signs.";
246287
246309
  }
246288
246310
  if (existingNames?.includes(value)) {
246289
246311
  return `A configuration profile named "${value}" already exists.`;
@@ -246526,7 +246548,10 @@ var UnsupportedShimSourceError = class extends CliError {
246526
246548
  contentSourcePath;
246527
246549
  };
246528
246550
  function claudeTargetFilename(ownExecutablePath) {
246529
- return ownExecutablePath.toLowerCase().endsWith(".exe") ? "claude.exe" : "claude";
246551
+ return commandFilename(ownExecutablePath, "claude");
246552
+ }
246553
+ function commandFilename(ownExecutablePath, commandName) {
246554
+ return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
246530
246555
  }
246531
246556
  function isInvokedAsClaude(invokedName) {
246532
246557
  return invokedName === "claude" || invokedName.toLowerCase() === "claude.exe";
@@ -246674,6 +246699,12 @@ function registerShimCommand(program2, paths) {
246674
246699
  // src/doctor.ts
246675
246700
  var import_node_fs8 = __toESM(require("node:fs"), 1);
246676
246701
  var import_node_path19 = __toESM(require("node:path"), 1);
246702
+ function refinePathShadow(status, ownExecutablePath, realpath) {
246703
+ if (status.status !== "shadowed") {
246704
+ return status;
246705
+ }
246706
+ return realpath(status.by) === realpath(ownExecutablePath) ? { status: "ok" } : status;
246707
+ }
246677
246708
  function validateJson(schema, input2) {
246678
246709
  if (input2.raw === void 0) {
246679
246710
  return { ok: false, message: `${input2.path} is missing.` };
@@ -246690,6 +246721,49 @@ function validateJson(schema, input2) {
246690
246721
  }
246691
246722
  return { ok: true, data: result.data };
246692
246723
  }
246724
+ function pushPathResolution(push, resolution) {
246725
+ const ownDir = import_node_path19.default.dirname(resolution.ownExecutablePath);
246726
+ switch (resolution.claudeUse.status) {
246727
+ case "ok":
246728
+ push("path-resolution", "pass", `\`claude-use\` on PATH resolves to this running executable, ${resolution.ownExecutablePath}.`, "claude-use");
246729
+ break;
246730
+ case "not-on-path":
246731
+ push(
246732
+ "path-resolution",
246733
+ "warn",
246734
+ `${ownDir} is not on PATH, so a bare \`claude-use\` does not reach ${resolution.ownExecutablePath}. Add it to PATH, or keep invoking this executable by its full path.`,
246735
+ "claude-use"
246736
+ );
246737
+ break;
246738
+ case "shadowed":
246739
+ push(
246740
+ "path-resolution",
246741
+ "fail",
246742
+ `\`claude-use\` on PATH resolves to ${resolution.claudeUse.by}, not this running executable, ${resolution.ownExecutablePath}. Every command you type runs that one instead, at whatever version it happens to be \u2014 including the checks in this report, which describe this executable. Remove ${resolution.claudeUse.by}, repoint it at ${resolution.ownExecutablePath}, or put ${ownDir} ahead of it on PATH.`,
246743
+ "claude-use"
246744
+ );
246745
+ break;
246746
+ }
246747
+ if (resolution.claude === void 0) {
246748
+ return;
246749
+ }
246750
+ switch (resolution.claude.status) {
246751
+ case "ok":
246752
+ push("path-resolution", "pass", "`claude` on PATH resolves to the enabled shim.", "claude");
246753
+ break;
246754
+ case "not-on-path":
246755
+ push("path-resolution", "warn", "The enabled `claude` shim's directory is not on PATH \u2014 add it, or use `claude-use run` instead.", "claude");
246756
+ break;
246757
+ case "shadowed":
246758
+ push(
246759
+ "path-resolution",
246760
+ "warn",
246761
+ `\`claude\` on PATH resolves to ${resolution.claude.by}, not the enabled shim. Put the shim's directory ahead of it on PATH, or run \`claude-use shim disable\` if you meant to launch that one directly.`,
246762
+ "claude"
246763
+ );
246764
+ break;
246765
+ }
246766
+ }
246693
246767
  function runDoctor(params) {
246694
246768
  const findings = [];
246695
246769
  const push = (section, severity, message, subject) => {
@@ -246723,6 +246797,7 @@ function runDoctor(params) {
246723
246797
  `\`claude\` is enabled at ${params.claudeShim.state.targetPath} (${params.claudeShim.state.method}). If you've upgraded claude-use since, re-run \`claude-use shim enable\` to refresh it.`
246724
246798
  );
246725
246799
  }
246800
+ pushPathResolution(push, params.pathResolution);
246726
246801
  const profileSources = /* @__PURE__ */ new Map();
246727
246802
  for (const entry of params.configProfiles) {
246728
246803
  const validated = validateJson(ConfigProfileSchema, entry);
@@ -246843,6 +246918,7 @@ var SECTION_TITLES = {
246843
246918
  "ambient-credential": "Ambient-credential exposure",
246844
246919
  "binary-discovery": "Claude Code binary discovery",
246845
246920
  "claude-shim": "`claude` command shim",
246921
+ "path-resolution": "PATH resolution",
246846
246922
  "config-profile": "Configuration profiles",
246847
246923
  identity: "Identities",
246848
246924
  keychain: "macOS Keychain",
@@ -246855,6 +246931,7 @@ var SECTION_ORDER = [
246855
246931
  "ambient-credential",
246856
246932
  "binary-discovery",
246857
246933
  "claude-shim",
246934
+ "path-resolution",
246858
246935
  "config-profile",
246859
246936
  "identity",
246860
246937
  "keychain",
@@ -246890,11 +246967,18 @@ function formatDoctorReport(report) {
246890
246967
  lines.push("", report.ok ? "All checks passed." : `${failCount} check(s) failed.`);
246891
246968
  return lines;
246892
246969
  }
246970
+ function realpathOrSelf(target) {
246971
+ try {
246972
+ return import_node_fs8.default.realpathSync(target);
246973
+ } catch {
246974
+ return target;
246975
+ }
246976
+ }
246893
246977
  function registerDoctorCommand(program2, paths) {
246894
246978
  program2.command("doctor").description(
246895
246979
  "Audit the whole ~/.claude-use config graph -- every identity, every configuration profile's extends chain, directory-rules.json, config.json, categories.local.json, active-identity, and real Claude Code binary discoverability. Identity/directory-agnostic, unlike `check`."
246896
246980
  ).action(() => {
246897
- const identityNames = import_node_fs8.default.existsSync(paths.identitiesDir) ? import_node_fs8.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort() : [];
246981
+ const identityNames = import_node_fs8.default.existsSync(paths.identitiesDir) ? import_node_fs8.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort() : [];
246898
246982
  const identities = identityNames.map((name) => {
246899
246983
  const farmRoot = import_node_path19.default.join(paths.identitiesDir, name);
246900
246984
  const identityPath = import_node_path19.default.join(farmRoot, "identity.json");
@@ -246914,6 +246998,17 @@ function registerDoctorCommand(program2, paths) {
246914
246998
  binaryDiscovery = { ok: false, message: error62 instanceof Error ? error62.message : String(error62) };
246915
246999
  }
246916
247000
  const shimState = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
247001
+ const pathDirs = (process.env.PATH ?? "").split(import_node_path19.default.delimiter).filter((dir) => dir !== "");
247002
+ const claudeShimShadow = shimState === void 0 ? void 0 : refinePathShadow(
247003
+ findPathShadow({
247004
+ pathDirs,
247005
+ targetDir: import_node_path19.default.dirname(shimState.targetPath),
247006
+ targetFilename: import_node_path19.default.basename(shimState.targetPath),
247007
+ findExecutableInDir
247008
+ }),
247009
+ shimState.targetPath,
247010
+ realpathOrSelf
247011
+ );
246917
247012
  const report = runDoctor({
246918
247013
  env: process.env,
246919
247014
  identities,
@@ -246924,6 +247019,20 @@ function registerDoctorCommand(program2, paths) {
246924
247019
  activeIdentity: { path: paths.activeIdentityFile, raw: realFsPort.readFileUtf8(paths.activeIdentityFile) },
246925
247020
  binaryDiscovery,
246926
247021
  claudeShim: { state: shimState, targetExists: shimState !== void 0 && import_node_fs8.default.existsSync(shimState.targetPath) },
247022
+ pathResolution: {
247023
+ ownExecutablePath,
247024
+ claudeUse: refinePathShadow(
247025
+ findPathShadow({
247026
+ pathDirs,
247027
+ targetDir: import_node_path19.default.dirname(ownExecutablePath),
247028
+ targetFilename: commandFilename(ownExecutablePath, "claude-use"),
247029
+ findExecutableInDir
247030
+ }),
247031
+ ownExecutablePath,
247032
+ realpathOrSelf
247033
+ ),
247034
+ ...claudeShimShadow === void 0 ? {} : { claude: claudeShimShadow }
247035
+ },
246927
247036
  run: realRunPort,
246928
247037
  platform: process.platform
246929
247038
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "2.0.4",
3
+ "version": "2.2.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",