claude-use 1.3.1 → 1.3.2

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 +9 -3
  2. package/dist/cli.cjs +35 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -468,9 +468,9 @@ One compiled binary backs both `claude` and `claude-use` — the entrypoint disp
468
468
  ```
469
469
  src/
470
470
  cli.ts # entrypoint; dispatches on invoked name -> launcher vs identity/profile-manager subcommands
471
+ cliError.ts # CliError — the base class every user-facing error extends, so main()'s top-level catch can print a clean message instead of a stack trace
471
472
  paths.ts # CLAUDE_USE_HOME-aware layout paths — every other module resolves ~/.claude-use/... paths through this, never inline
472
473
  pathNorm.ts # rule-path normalisation/ancestor helpers shared across the resolver and directory rules
473
- exit.ts # exit code constants
474
474
  versionDiscovery.ts # portable "find the real claude binary" logic
475
475
  realPorts.ts # the real filesystem/spawn/proc/clock/git ports wired into runLauncher by cli.ts (tests wire fakes instead)
476
476
  launcher.ts # runLauncher: thin orchestration over launcher/* below
@@ -529,6 +529,12 @@ install.sh # downloads the latest release's binary for the runni
529
529
  # checksum, and installs it as both `claude` and `claude-use` in ~/.local/bin
530
530
  ```
531
531
 
532
+ ### Error reporting: `CliError` vs. everything else
533
+
534
+ Every custom error this project throws to represent an expected, user-facing failure — a missing identity/profile/rule, a malformed config file, an invalid `--category`/`--share`/`--hide` flag — extends `CliError` (`src/cliError.ts`), an otherwise-empty abstract subclass of `Error`. `main()` in `src/cli.ts` wraps its whole body in one top-level `try`/`catch`: a `CliError` prints as `error.message` alone, with no stack trace, and exits `1`; anything else — a genuine, unanticipated bug — is rethrown and crashes with its full stack trace, which is more useful for diagnosing it than swallowing it would be. Before this existed, an error like `IdentityNotFoundError` thrown from the `@name` shortcut or from inside a Commander action (`identity use`, `profile create`, etc.) crashed with a raw Node.js stack trace instead of the one-line message its own constructor already built — the class carried the right text, nothing at the top ever caught it. `main()` calls `buildClaudeUseProgram().parseAsync(process.argv)`, not `.parse()`, specifically so an `async` action's rejection (e.g. `identity resolve <name>`, which awaits an interactive prompt) reaches this same catch too, rather than surfacing as an unhandled promise rejection Commander's synchronous `.parse()` never awaits.
535
+
536
+ `cliError.test.ts` asserts every one of these error classes actually extends `CliError` — the one regression `tsc`/`eslint` can never catch on their own, since a class silently reverting to `extends Error`, or a new one added without extending `CliError` at all, is still perfectly valid TypeScript.
537
+
532
538
  `schema.ts` models `categories` and `entries` differently despite their identical JSON-object appearance in every example above, because they have opposite key cardinality: `categories` only ever touches the four overridable names in the [category table](#category-based-sharing) plus the `all` shorthand, so it's a closed `z.strictObject({ all: z.boolean().optional(), runtime: z.boolean().optional(), history: z.boolean().optional(), knowledge: z.boolean().optional(), settings: z.boolean().optional() })` piped through a `.transform()` that expands `all` into the four real categories and drops it from the result — deliberately omitting `secret` from the shape entirely, so an attempted `secret` key is rejected at parse time rather than relying only on the runtime check described above — while `entries` is genuinely open-ended (any literal or glob path, each required to carry its `<category>/` prefix per the [Category-based sharing](#category-based-sharing) section above) and stays a `z.record(z.string().regex(ENTRY_KEY_RE), EntryValueSchema)`. The closed shape for `categories` also gives editors real key-name autocomplete from the published JSON Schema (the `schema/` directory above) — generated with Zod's `io: "input"` option specifically because a schema with a `.transform()` can't be represented in JSON Schema at all under the default `"output"` mode, and `"input"` is what a hand-written config actually needs describing anyway, which a record type couldn't offer either way.
533
539
 
534
540
  `ConfigProfile.extends` is a flat `z.array(z.string()).optional()` — a list of other profiles' *names*, resolved by `resolve/extends.ts` loading each named file and walking the resulting graph at runtime. It's correctly **not** a self-referential Zod schema (no `z.lazy()` needed): nothing in `ConfigProfile`'s own shape points back at `ConfigProfile`. Because each profile file validates in isolation, though, Zod has no way to catch a circular `extends` definition (`a` extends `b` extends `a`) — the walker in `resolve/extends.ts` needs its own cycle guard (a visited-set), independent of schema validation.
@@ -563,7 +569,7 @@ Every push to `main` runs [semantic-release](https://github.com/semantic-release
563
569
 
564
570
  One accepted quirk worth knowing rather than being surprised by: semantic-release creates the release tag pointing at the commit that already existed (the actual code change being released) *before* running `@semantic-release/git`'s commit step — so the changelog/version-bump commit lands on `main` **after** the tag, not folded into it. Every downstream job below checks out `ref: main` (not the commit that triggered the run) specifically to pick up this post-release state, matching how this org's other semantic-release repos handle the identical quirk.
565
571
 
566
- **The six platform build jobs (`build-arm64` through `build-windows-arm64`) also run on every pull request, not only a published release.** Each builds the real SEA binary for its own platform and then directly executes it (`--version`, `--help`) as a genuine smoke test — this is what would have caught, before merge rather than after a real release, an actual regression this project shipped once: a Turbo cache key that didn't distinguish `runner.arch`, letting one architecture's compiled binary silently serve for another (see `.github/actions/setup-and-run/action.yml`'s own comment). On a PR, each build job checks out the PR's own head commit instead of `ref: main`, since there's no post-release state to pick up yet. Publishing itself — npm, GitHub Packages, the GitHub Release, and the Homebrew/Scoop tap updates — stays gated on `needs.semantic-release.outputs.published == 'true'` alone and never runs on a PR, since those all have real, one-way side effects.
572
+ **The six platform build jobs (`build-arm64` through `build-windows-arm64`) also run on every pull request, not only a published release.** Each has its own dedicated `smoke-test-*` job — a separate job, not a step inside the build job, so a smoke-test failure shows as its own line in the PR checks list, distinct from "did it build" — that downloads the just-built artifact and directly executes it (`--version`, `--help`) on a runner of the same OS+architecture, since a binary built for one architecture cannot run on another. This is what would have caught, before merge rather than after a real release, an actual regression this project shipped once: a Turbo cache key that didn't distinguish `runner.arch`, letting one architecture's compiled binary silently serve for another (see `.github/actions/setup-and-run/action.yml`'s own comment). `release` depends on all five verified `smoke-test-*` jobs succeeding, not just their matching `build-*` jobs, so a smoke-test failure blocks the release the same way a build failure already did — `smoke-test-macos-x64-best-effort` is deliberately excluded from that gate, matching `build-x64-best-effort`'s own `continue-on-error: true`. On a PR, each build job checks out the PR's own head commit instead of `ref: main`, since there's no post-release state to pick up yet. Publishing itself — npm, GitHub Packages, the GitHub Release, and the Homebrew/Scoop tap updates — stays gated on `needs.semantic-release.outputs.published == 'true'` alone and never runs on a PR, since those all have real, one-way side effects.
567
573
 
568
574
  **Branch/tag protection had to be disabled for this to work.** `main`'s branch-protection ruleset previously required every push go through a reviewed PR, and a separate ruleset blocked tag creation/deletion outright — both only bypassable by the Admin repository role. semantic-release's own git operations run as the workflow's default `GITHUB_TOKEN`, which doesn't hold that role, so both rulesets were disabled (not deleted — the rule definitions are preserved and can be re-enabled with a single API call or via the repo's Rules settings page) rather than routing around them with a separate bypass credential.
569
575
 
@@ -575,7 +581,7 @@ One accepted quirk worth knowing rather than being surprised by: semantic-releas
575
581
 
576
582
  One gotcha worth knowing before reaching for this: **Homebrew's macOS Node build has the single-executable-application feature compiled out.** Running the build against a Homebrew-installed Node fails partway through with "Single executable application is disabled" — `scripts/build.mts` detects this specific error and rewrites it into an explanation naming the cause, rather than leaving a contributor to debug an opaque native error. Use a Node binary from a distribution that ships SEA support instead — the official nodejs.org build, or a version manager installing upstream builds (mise, nvm, volta, fnm) — ahead of Homebrew's on `PATH`.
577
583
 
578
- macOS SEA support is tested and verified upstream on **arm64 only** — x64 is explicitly unsupported and skipped in Node core's own test suite. CI builds and publishes the arm64 binary as the verified release artefact; it also attempts an x64 build as a clearly-labelled best-effort convenience (allowed to fail without blocking the release, and published as `claude-use-macos-x64-unverified` when it succeeds), never presented as a supported target. This isn't just a theoretical caveat: v0.2.7 confirmed `claude-use --version` itself segfaults on real x64 macOS hardware, a crash the build job's own smoke test had silently swallowed on every prior release via `|| true` until a dedicated install-and-run verify job (also best-effort, non-blocking) finally exercised the binary for real.
584
+ macOS SEA support is tested and verified upstream on **arm64 only** — x64 is explicitly unsupported and skipped in Node core's own test suite. CI builds and publishes the arm64 binary as the verified release artefact; it also attempts an x64 build as a clearly-labelled best-effort convenience (allowed to fail without blocking the release, and published as `claude-use-macos-x64-unverified` when it succeeds), never presented as a supported target. This isn't just a theoretical caveat: v0.2.7 confirmed `claude-use --version` itself segfaults on real x64 macOS hardware, a crash `smoke-test-macos-x64-best-effort` has silently swallowed via `|| true` until a dedicated install-and-run verify job (also best-effort, non-blocking) finally exercised the binary for real.
579
585
 
580
586
  **Root cause, confirmed rather than assumed.** Reproduced locally under Rosetta with 100% fidelity to CI: `lldb` shows `EXC_BAD_ACCESS` inside `__cxx_global_var_init`, invoked by `dyld` while running C++ static initializers — before `main()` ever executes, with `rdi` (the faulting access) holding the literal value `0x2`. A trivial one-line `console.log(...)` script built through the exact same `--build-sea` + ad-hoc-codesign steps crashes identically, which rules out anything in claude-use's own bundle, build script, or code — this is `--build-sea` itself misbehaving on x64 macOS. This is a known, tracked, and deliberately unfixed upstream limitation: [nodejs/node#62893](https://github.com/nodejs/node/issues/62893) reproduces the identical crash and was closed as documentation-only ([nodejs/node#63181](https://github.com/nodejs/node/pull/63181)), with a Node core maintainer stating SEA on x64 macOS "is not supported and skipped in the tests... until someone volunteers to implement support for it." The deeper investigation thread ([nodejs/node#59553](https://github.com/nodejs/node/issues/59553)) floats an unconfirmed theory — that `postject`'s LIEF-based Mach-O binary injection corrupts the executable such that `dyld` misidentifies a segment as an oversized (>4GB) thread-local-storage region — but that thread closed stale, unfixed, with the same maintainer concluding it's "unlikely that anyone would invest time in fixing it for macOS" given x64 macOS's Tier 2 deprioritisation upstream. There is no available workaround (no alternate `codesign` invocation, `sea-config.json` option, or Node flag) — the fault is inside `--build-sea`'s own binary-injection step, before any code this project controls runs at all.
581
587
 
package/dist/cli.cjs CHANGED
@@ -221857,7 +221857,7 @@ var categories_default_default = {
221857
221857
  // package.json
221858
221858
  var package_default = {
221859
221859
  name: "claude-use",
221860
- version: "1.3.1",
221860
+ version: "1.3.2",
221861
221861
  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.",
221862
221862
  license: "Apache-2.0",
221863
221863
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -221936,6 +221936,12 @@ var package_default = {
221936
221936
 
221937
221937
  // src/config/load.ts
221938
221938
  var import_cosmiconfig = __toESM(require_dist(), 1);
221939
+
221940
+ // src/cliError.ts
221941
+ var CliError = class extends Error {
221942
+ };
221943
+
221944
+ // src/config/load.ts
221939
221945
  function createExplorer(moduleName = "claude-use") {
221940
221946
  return (0, import_cosmiconfig.cosmiconfigSync)(moduleName, { searchPlaces: [] });
221941
221947
  }
@@ -221962,7 +221968,7 @@ function captureRuleEntryOrders(value) {
221962
221968
  }
221963
221969
  return rules.map((rule) => captureEntryOrder(rule));
221964
221970
  }
221965
- var ConfigValidationError = class extends Error {
221971
+ var ConfigValidationError = class extends CliError {
221966
221972
  constructor(filepath, issues) {
221967
221973
  const detail = issues.map((issue2) => ` ${issue2.path.join(".") || "(root)"}: ${issue2.message}`).join("\n");
221968
221974
  super(`Invalid configuration in ${filepath}:
@@ -237253,7 +237259,7 @@ function splitOnWildcards(pattern) {
237253
237259
  }
237254
237260
  return fragments;
237255
237261
  }
237256
- var UnrootedProjectPathError = class extends Error {
237262
+ var UnrootedProjectPathError = class extends CliError {
237257
237263
  constructor(fragment) {
237258
237264
  super(
237259
237265
  `"${fragment}" is not a rooted path. Everything written after the "history/projects/" prefix is a real absolute working directory (optionally globbed), so it must start with "/" or "~/" \u2014 a bare relative path has nothing to encode against.`
@@ -237352,7 +237358,7 @@ function normaliseRelative(fragment) {
237352
237358
  return segments.join("/");
237353
237359
  }
237354
237360
  var PROJECTS_PREFIX = "projects/";
237355
- var EntryKeyError = class extends Error {
237361
+ var EntryKeyError = class extends CliError {
237356
237362
  constructor(key, message, reason) {
237357
237363
  super(message);
237358
237364
  this.key = key;
@@ -237846,7 +237852,7 @@ var import_node_path7 = __toESM(require("node:path"), 1);
237846
237852
  var DEFAULT_STALE_AFTER_MS = 12e4;
237847
237853
  var DEFAULT_RETRY_DELAY_MS = 50;
237848
237854
  var DEFAULT_MAX_ATTEMPTS = 200;
237849
- var IdentityLockBusyError = class extends Error {
237855
+ var IdentityLockBusyError = class extends CliError {
237850
237856
  constructor(identity, lockPath, holderPid) {
237851
237857
  super(
237852
237858
  `Another claude-use resync is already running for identity "${identity}"` + (holderPid === void 0 ? "" : ` (pid ${holderPid})`) + `. Its lock at ${lockPath} was still held after the full retry budget; nothing was changed.`
@@ -240196,7 +240202,7 @@ async function resolveFarmConflicts(params) {
240196
240202
  }
240197
240203
 
240198
240204
  // src/identityManager.ts
240199
- var IdentityNotFoundError = class extends Error {
240205
+ var IdentityNotFoundError = class extends CliError {
240200
240206
  constructor(name) {
240201
240207
  super(`No identity named "${name}" \u2014 run \`claude-use identity add ${name}\` first.`);
240202
240208
  this.name = name;
@@ -240204,7 +240210,7 @@ var IdentityNotFoundError = class extends Error {
240204
240210
  }
240205
240211
  name;
240206
240212
  };
240207
- var IdentityAlreadyExistsError = class extends Error {
240213
+ var IdentityAlreadyExistsError = class extends CliError {
240208
240214
  constructor(identityName) {
240209
240215
  super(`An identity named "${identityName}" already exists.`);
240210
240216
  this.identityName = identityName;
@@ -240399,7 +240405,7 @@ function collectBoolPairs(value, previous = {}) {
240399
240405
  }
240400
240406
 
240401
240407
  // src/configProfiles.ts
240402
- var ProfileNotFoundError = class extends Error {
240408
+ var ProfileNotFoundError = class extends CliError {
240403
240409
  constructor(profileName) {
240404
240410
  super(`No configuration profile named "${profileName}" \u2014 run \`claude-use profile create ${profileName}\` first.`);
240405
240411
  this.profileName = profileName;
@@ -240407,7 +240413,7 @@ var ProfileNotFoundError = class extends Error {
240407
240413
  }
240408
240414
  profileName;
240409
240415
  };
240410
- var ProfileAlreadyExistsError = class extends Error {
240416
+ var ProfileAlreadyExistsError = class extends CliError {
240411
240417
  constructor(profileName) {
240412
240418
  super(`A configuration profile named "${profileName}" already exists.`);
240413
240419
  this.profileName = profileName;
@@ -240415,7 +240421,7 @@ var ProfileAlreadyExistsError = class extends Error {
240415
240421
  }
240416
240422
  profileName;
240417
240423
  };
240418
- var InvalidCategoryNameError = class extends Error {
240424
+ var InvalidCategoryNameError = class extends CliError {
240419
240425
  constructor(categoryName) {
240420
240426
  super(`"${categoryName}" is not a category a configuration profile may toggle (runtime, history, knowledge, settings).`);
240421
240427
  this.categoryName = categoryName;
@@ -240559,7 +240565,7 @@ function registerProfileCommand(program2, paths) {
240559
240565
  }
240560
240566
 
240561
240567
  // src/directoryRules.ts
240562
- var DirectoryRuleNotFoundError = class extends Error {
240568
+ var DirectoryRuleNotFoundError = class extends CliError {
240563
240569
  constructor(rulePath) {
240564
240570
  super(`No directory rule found for path "${rulePath}".`);
240565
240571
  this.rulePath = rulePath;
@@ -241038,7 +241044,7 @@ var ClaudeShimStateSchema = external_exports.strictObject({
241038
241044
  method: external_exports.enum(["hardlink", "copy"]),
241039
241045
  installedAtMs: external_exports.number()
241040
241046
  });
241041
- var ForeignClaudeEntryError = class extends Error {
241047
+ var ForeignClaudeEntryError = class extends CliError {
241042
241048
  constructor(targetPath, action) {
241043
241049
  super(
241044
241050
  `${targetPath} already exists and does not look like something claude-use created. Refusing to ${action === "enable" ? "overwrite" : "remove"} it automatically \u2014 inspect it yourself, or pass --force if you're sure.`
@@ -241050,7 +241056,7 @@ var ForeignClaudeEntryError = class extends Error {
241050
241056
  targetPath;
241051
241057
  action;
241052
241058
  };
241053
- var UnsupportedShimSourceError = class extends Error {
241059
+ var UnsupportedShimSourceError = class extends CliError {
241054
241060
  constructor(contentSourcePath) {
241055
241061
  super(
241056
241062
  `Cannot create a working \`claude\` command from ${contentSourcePath} on Windows: an npm-installed claude-use running under Node.js has no .exe to hardlink/copy, and Windows has no shebang-based dispatch the way POSIX does. Install claude-use via Scoop instead (see README), which ships a real claude-use.exe this command can link from.`
@@ -241533,7 +241539,7 @@ function parseLauncherArgv(argv) {
241533
241539
  }
241534
241540
 
241535
241541
  // src/launcher/cliOverride.ts
241536
- var InvalidCliCategoryError = class extends Error {
241542
+ var InvalidCliCategoryError = class extends CliError {
241537
241543
  constructor(categoryName) {
241538
241544
  super(`"${categoryName}" is not a category this launch may toggle (runtime, history, knowledge, settings).`);
241539
241545
  this.categoryName = categoryName;
@@ -241541,7 +241547,7 @@ var InvalidCliCategoryError = class extends Error {
241541
241547
  }
241542
241548
  categoryName;
241543
241549
  };
241544
- var InvalidCliEntryKeyError = class extends Error {
241550
+ var InvalidCliEntryKeyError = class extends CliError {
241545
241551
  constructor(key) {
241546
241552
  super(`"${key}" is not a valid entries key \u2014 it must start with "<category>/", e.g. "knowledge/skills/commit".`);
241547
241553
  this.key = key;
@@ -241873,15 +241879,25 @@ function runClaude(argvOverride) {
241873
241879
  ...farm.globalDefaultConfigProfile === void 0 ? {} : { globalDefaultConfigProfile: farm.globalDefaultConfigProfile }
241874
241880
  });
241875
241881
  }
241876
- function main() {
241882
+ async function main() {
241877
241883
  const invokedName = import_node_path21.default.basename(process.argv[1] ?? "claude-use");
241878
241884
  if (isInvokedAsClaude(invokedName)) {
241879
241885
  runClaude();
241880
- } else if (!tryRunAtIdentityShortcut(resolveLayoutPaths(), process.argv.slice(2))) {
241881
- buildClaudeUseProgram().parse(process.argv);
241886
+ return;
241882
241887
  }
241888
+ if (tryRunAtIdentityShortcut(resolveLayoutPaths(), process.argv.slice(2))) {
241889
+ return;
241890
+ }
241891
+ await buildClaudeUseProgram().parseAsync(process.argv);
241883
241892
  }
241884
- main();
241893
+ main().catch((error51) => {
241894
+ if (error51 instanceof CliError) {
241895
+ console.error(error51.message);
241896
+ process.exitCode = 1;
241897
+ return;
241898
+ }
241899
+ throw error51;
241900
+ });
241885
241901
  /*! Bundled license information:
241886
241902
 
241887
241903
  typescript/lib/typescript.js:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
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>",