claude-use 0.3.3 → 0.3.6
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 +13 -13
- package/dist/cli.cjs +413 -404
- package/package.json +17 -7
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ Every top-level entry in `~/.claude` is classified into one of five categories,
|
|
|
126
126
|
|
|
127
127
|
This is a safe-by-default posture: only `knowledge` and `settings` are shared out of the box. A configuration profile can open up `history` (or anything else) wholesale, or share individual items within a closed category.
|
|
128
128
|
|
|
129
|
-
`secret`'s "never, cannot be overridden" is an absolute check `resolve.ts` makes *before* running the two-phase cascade at all — not merely the least-specific layer in that cascade, the way every other category is. This matters because [The cascade](#the-cascade-how-everything-composes)'s general rule is that a specific `entries` override always beats a category default; `secret` is the one deliberate exception, so an explicit `entries: { "secret/.credentials.json": true }` anywhere in any layer is rejected outright, the same as a bare `categories: { secret: true }` would be — path-specificity never gets a chance to apply to this one category.
|
|
129
|
+
`secret`'s "never, cannot be overridden" is an absolute check `resolve/decide.ts` makes *before* running the two-phase cascade at all — not merely the least-specific layer in that cascade, the way every other category is. This matters because [The cascade](#the-cascade-how-everything-composes)'s general rule is that a specific `entries` override always beats a category default; `secret` is the one deliberate exception, so an explicit `entries: { "secret/.credentials.json": true }` anywhere in any layer is rejected outright, the same as a bare `categories: { secret: true }` would be — path-specificity never gets a chance to apply to this one category.
|
|
130
130
|
|
|
131
131
|
**`~/.claude.json` isn't in this table at all, because — unlike `backups/` above — it isn't sourced from `~/.claude` the way everything else here is.** It's a sibling *file* next to the `~/.claude` directory, not an entry inside it: the OAuth session, personal (user/local-scope) MCP server definitions, and per-project trust decisions (which directories you've approved Claude Code to run in, and what it's allowed to do there). It fully relocates to `$CLAUDE_CONFIG_DIR/.claude.json` when set, the same as everything else — confirmed both in Anthropic's own Agent SDK documentation and empirically in this project's own development. Because it's generated fresh by Claude Code itself the moment it first runs under a new `CLAUDE_CONFIG_DIR`, `claude-use` treats it the same way as `secret`: always identity-local, never part of the shared cascade, and — since it isn't even a descendant of `~/.claude` — never something the resolver's directory walk encounters at all, rather than something explicitly excluded by category. `~/.claude/backups/` holds rolling timestamped copies of it (capped at five, auto-rotating) for Claude Code's own config-migration safety; being a genuine descendant of `~/.claude`, it *is* something the resolver walks past, which is exactly why it's listed under `secret` in the table above rather than merely assumed safe.
|
|
132
132
|
|
|
@@ -458,8 +458,8 @@ src/
|
|
|
458
458
|
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
|
|
459
459
|
cli/
|
|
460
460
|
parsers.ts # shared CLI-flag parsing helpers (splitTopLevelCommas, parsePair, repeatable-flag collectors)
|
|
461
|
-
resolve.ts # public facade re-exporting the resolver below — nothing outside src/resolve/ imports its internals directly
|
|
462
461
|
resolve/
|
|
462
|
+
pipeline.ts # resolveDecisions: runs the whole pipeline for one launch, topLevelNames
|
|
463
463
|
types.ts # every resolver type
|
|
464
464
|
match.ts # canonicaliseEntryKey, compileMatcher, compareSpecificity
|
|
465
465
|
projects.ts # forward-only ~/.claude/projects/ path encoder — no decoder exists
|
|
@@ -495,23 +495,23 @@ install.sh # downloads the latest release's binary for the runni
|
|
|
495
495
|
|
|
496
496
|
`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), so it's a closed `z.strictObject({ runtime: z.boolean().optional(), history: z.boolean().optional(), knowledge: z.boolean().optional(), settings: z.boolean().optional() })` — 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), which a record type can't offer.
|
|
497
497
|
|
|
498
|
-
`ConfigProfile.extends` is a flat `z.array(z.string()).optional()` — a list of other profiles' *names*, resolved by `resolve.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.ts` needs its own cycle guard (a visited-set), independent of schema validation.
|
|
498
|
+
`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.
|
|
499
499
|
|
|
500
500
|
The `when` condition object's `env` field is `z.record(z.string().min(1), z.string()).optional()` — zero or more named environment-variable checks, ANDed together within the same `when` object exactly like every other condition, rather than a single fixed `{ name, value }` pair (which would need `when` itself to become an array to check more than one variable, a shape nothing else in this design uses).
|
|
501
501
|
|
|
502
502
|
### Why config file loading uses cosmiconfig's `load()`, never its `search()`
|
|
503
503
|
|
|
504
|
-
Every config file this tool reads — the global config, named configuration profiles, and each `.claude-use.json`/`.claude-use.local.json` found while walking the directory tree — is loaded with [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig)'s `load(filepath)`. Its `search()` method stops at the first config file found while walking upward; this design needs the opposite — every ancestor collected, shallowest-first — so `
|
|
504
|
+
Every config file this tool reads — the global config, named configuration profiles, and each `.claude-use.json`/`.claude-use.local.json` found while walking the directory tree — is loaded with [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig)'s `load(filepath)`. Its `search()` method stops at the first config file found while walking upward; this design needs the opposite — every ancestor collected, shallowest-first — so `launcher/cascade.ts` does its own directory walk and calls `load()` at each level it visits, getting cosmiconfig's format flexibility without fighting its traversal semantics. JSON and YAML work with zero extra setup (`js-yaml` is a bundled dependency); JS config files work via native dynamic `import`/`require`. TS config files are real too, but cosmiconfig lists `typescript` as an *optional peer dependency*, not a bundled one — since every config file this tool actually defines is `.json`, that's moot in practice, but it means `.ts` config support isn't something this codebase gets "for free" the way JSON/YAML/JS are, and shipping the compiled Node SEA binary with no `node_modules` at runtime (per [Install](#install)) means a `.ts` config file would fail to load unless `typescript` were bundled into the SEA blob specifically for that purpose — not planned, since nothing this tool ships needs it.
|
|
505
505
|
|
|
506
506
|
### Why `extends` isn't cosmiconfig's `$import`
|
|
507
507
|
|
|
508
|
-
cosmiconfig also supports an `$import` directive that deep-merges imported files, later imports winning — close to what `extends` needs. (Its default `mergeImportArrays: true` concatenates arrays — imported items first, then local — rather than fully replacing them; only `mergeImportArrays: false` gives array fields the same "later wins" outright-replacement behaviour objects and primitives already get.) It isn't used for two reasons: it resolves imports by relative file path, not by profile name, so a name-to-path resolution step is needed regardless; and it has no awareness of the entries-beat-categories, most-specific-path-wins rule, which has to be bespoke either way. `resolve.ts` implements one flatten function, reused for both the `extends` chain and the outer cascade, rather than splitting the same conceptual merge across two implementations that could drift apart.
|
|
508
|
+
cosmiconfig also supports an `$import` directive that deep-merges imported files, later imports winning — close to what `extends` needs. (Its default `mergeImportArrays: true` concatenates arrays — imported items first, then local — rather than fully replacing them; only `mergeImportArrays: false` gives array fields the same "later wins" outright-replacement behaviour objects and primitives already get.) It isn't used for two reasons: it resolves imports by relative file path, not by profile name, so a name-to-path resolution step is needed regardless; and it has no awareness of the entries-beat-categories, most-specific-path-wins rule, which has to be bespoke either way. `resolve/flatten.ts` implements one flatten function, reused for both the `extends` chain and the outer cascade, rather than splitting the same conceptual merge across two implementations that could drift apart.
|
|
509
509
|
|
|
510
510
|
### Resolver mechanics
|
|
511
511
|
|
|
512
512
|
For each `~/.claude` entry, walk the cascade to a boolean decision. If the decision is uniform for an entire subtree, symlink that directory in one shot. If a deeper path override splits the decision, materialise that directory as a real local directory instead of a symlink and recurse, repeating the check at each level — only directories with an actual split ever get exploded. A conditional entries key is never eligible for the uniform-symlink shortcut, since its decision can only be evaluated per-file.
|
|
513
513
|
|
|
514
|
-
The pure decision logic — `(entryFacts, cascade, path) => Map<path, boolean>` — takes filesystem/git/env facts as an injected parameter (an entry manifest of path, mtime, and size; a resolved git branch; an env snapshot), rather than reading any of that itself. This is what makes it unit-testable with fake mtimes and a fake branch, per [Testing strategy](#testing-strategy), without touching a real filesystem or `git` — "pure" here means decoupled from I/O via dependency injection, not that no I/O happens anywhere in
|
|
514
|
+
The pure decision logic — `(entryFacts, cascade, path) => Map<path, boolean>` — takes filesystem/git/env facts as an injected parameter (an entry manifest of path, mtime, and size; a resolved git branch; an env snapshot), rather than reading any of that itself. This is what makes it unit-testable with fake mtimes and a fake branch, per [Testing strategy](#testing-strategy), without touching a real filesystem or `git` — "pure" here means decoupled from I/O via dependency injection, not that no I/O happens anywhere in the resolver; something still has to walk `~/.claude` and stat its entries to build the manifest this function consumes.
|
|
515
515
|
|
|
516
516
|
**Materialised directories need a write-through reconciliation step, not just a one-way split.** A directory the real Claude Code binary can create new children in at runtime — `history/projects/` chief among them, since Claude Code creates a new project subdirectory there the first time it sees an unfamiliar working directory — is exactly the kind of directory the tool's own conditional and per-project sharing examples recommend materialising. Once materialised, it stops being a live view of `~/.claude/projects/` and becomes a locally-built directory of symlinks (and further materialised subdirectories) frozen at resync time. Anything Claude Code subsequently writes into it — a brand-new project subdirectory, a new session file inside an existing one — lands as a real, untracked child of that materialised directory, not a symlink back to `~/.claude`: invisible to every other identity, and liable to be misread as stale scaffolding and pruned on a later resync.
|
|
517
517
|
|
|
@@ -521,15 +521,15 @@ The reverse direction matters just as much: a directory materialised because of
|
|
|
521
521
|
|
|
522
522
|
### Automated releases (semantic-release)
|
|
523
523
|
|
|
524
|
-
Every push to `main`
|
|
524
|
+
Every push to `main` runs [semantic-release](https://github.com/semantic-release/semantic-release) after `check` passes, via `release.config.ts` (a typed config, matching how this org's other semantic-release repos — e.g. `graphle` — configure it, rather than the untyped `.releaserc.json` form this project started with). It analyses every commit since the last `vX.Y.Z` tag using the `conventionalcommits` preset — the same convention this project's own commitlint config already enforces — and decides whether a release is warranted at all: a `feat:` commit bumps minor, `fix:`/`perf:` bump patch, a `BREAKING CHANGE:` footer bumps major, and anything else (`chore:`, `docs:`, `ci:`, `test:`) triggers no release on its own. If a release is warranted, it creates and pushes the next tag itself — exactly the step that used to be a manual `git tag`/`git push` earlier in this project's life.
|
|
525
525
|
|
|
526
|
-
|
|
526
|
+
`release.config.ts` configures four plugins, in this order: `@semantic-release/commit-analyzer` and `@semantic-release/release-notes-generator` (both `conventionalcommits`-preset, deciding the version and generating notes text), `@semantic-release/changelog` (writes `CHANGELOG.md`), `@semantic-release/npm` with **`npmPublish: false`** (bumps `package.json`'s version field and stages it, but never touches the npm registry), and `@semantic-release/git` (commits `CHANGELOG.md` + `package.json` with a `chore(release): X.Y.Z [skip ci]` message and pushes it to `main`). `@semantic-release/github` is not loaded at all. This split is deliberate: semantic-release owns only the version *decision*, the tag, and the changelog — actual npm publishing (this project's own OIDC trusted-publishing job, below), GitHub Release creation (with this project's own multi-platform asset list and release-notes body, not semantic-release's generic one), and the Homebrew/Scoop tap updates all remain this project's own jobs. The `[skip ci]` marker on the changelog commit stops it from re-triggering `check`/`semantic-release` on `main`.
|
|
527
527
|
|
|
528
|
-
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.
|
|
528
|
+
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.
|
|
529
529
|
|
|
530
530
|
**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.
|
|
531
531
|
|
|
532
|
-
**The
|
|
532
|
+
**The build/publish/verify pipeline below is not a separate tag-triggered workflow run — it's later jobs in this SAME run, gated on `needs: [semantic-release]`.** This project originally tried the opposite: let semantic-release's tag push trigger a second, tag-scoped workflow run, the way `ci.yml` used to be structured before this section was rewritten. That never worked — GitHub Actions never lets a `GITHUB_TOKEN`-authenticated push start a new workflow run, to prevent recursive loops, and (confirmed empirically, since most write-ups only discuss `GITHUB_TOKEN` and imply any other credential is exempt) an SSH deploy key registered on the repository hit the identical restriction. This org's other semantic-release repos (`graphle`, `spot-of-the-day`) never hit this at all, because their own post-release jobs (a Pages redeploy, a Worker deploy) are idempotent and just run unconditionally on every push to `main` — they never need to ask "did a release actually happen." Building five platform binaries, publishing to npm, and updating two external tap repos are not safe to run unconditionally, so this project's `semantic-release` job runs a plain `git describe --exact-match --tags HEAD` right after `npx semantic-release` and exposes the result as job outputs (`published`, `version`, `tag`) — no plugin needed, since it's just a shell check, not a hook into semantic-release's own plugin lifecycle. Every downstream job gates on `needs.semantic-release.outputs.published == 'true'`.
|
|
533
533
|
|
|
534
534
|
### Build (Node SEA)
|
|
535
535
|
|
|
@@ -557,7 +557,7 @@ The published JSON Schemas under `schema/` should self-reference (and, if ever s
|
|
|
557
557
|
|
|
558
558
|
## Testing strategy
|
|
559
559
|
|
|
560
|
-
|
|
560
|
+
the resolver's cascade and materialisation logic is exactly the kind of thing that's easy to get subtly wrong, so it gets thorough unit tests before anything else is built on it:
|
|
561
561
|
|
|
562
562
|
- Each cascade layer overriding the last; path overrides beating their parent category
|
|
563
563
|
- Directory rules folding correctly for nested paths, and correctly composing configuration profiles mid-tree
|
|
@@ -571,7 +571,7 @@ The published JSON Schemas under `schema/` should self-reference (and, if ever s
|
|
|
571
571
|
- 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
|
|
572
572
|
- 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
|
|
573
573
|
|
|
574
|
-
`identityManager.ts`, `configProfiles.ts`, `directoryRules.ts`, and `configure.ts` stay thin adapters over
|
|
574
|
+
`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.
|
|
575
575
|
|
|
576
576
|
`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.
|
|
577
577
|
|
|
@@ -590,7 +590,7 @@ pnpm build # bundle src/cli.ts with esbuild, then node --build-sea= (see B
|
|
|
590
590
|
pnpm schema # regenerate schema/*.schema.json from src/config/schema.ts; CI fails if this drifts from what's committed
|
|
591
591
|
```
|
|
592
592
|
|
|
593
|
-
Every test run gets `CLAUDE_USE_HOME` set to a throwaway directory by `vitest.config.
|
|
593
|
+
Every test run gets `CLAUDE_USE_HOME` set to a throwaway directory by `vitest.config.ts`, and a Vitest setup file (`src/test-setup.ts`) refuses to let any test run at all if that variable is unset or resolves to the real `~/.claude-use` — there is no path by which the test suite can touch a real identity. A farm test that also needs a canonical `~/.claude` to resync against injects its own fake filesystem port rather than touching a real path. Manual, non-test exploration of a locally built binary should follow the same discipline: export `CLAUDE_USE_HOME` (and, if exercising a real farm resync, `CLAUDE_USE_CLAUDE_HOME`) to point at scratch directories, never at your own real identities.
|
|
594
594
|
|
|
595
595
|
Commits are gated by Husky hooks (`pnpm install` wires them up via the `prepare` script): `commit-msg` enforces conventional-commit format, `pre-commit` rejects merge/squash commits on `main` and runs `eslint --fix` on staged files via lint-staged, `pre-push` runs the full test suite. Both `pre-commit` and `pre-push` also reject a commit or push that deletes more than 100 files, as a guard against a sparse-checkout or partial-worktree bug landing a mass deletion.
|
|
596
596
|
|
package/dist/cli.cjs
CHANGED
|
@@ -218432,8 +218432,8 @@ var require_src = __commonJS({
|
|
|
218432
218432
|
});
|
|
218433
218433
|
|
|
218434
218434
|
// src/cli.ts
|
|
218435
|
-
var import_node_os6 = __toESM(require("node:os"));
|
|
218436
|
-
var import_node_path20 = __toESM(require("node:path"));
|
|
218435
|
+
var import_node_os6 = __toESM(require("node:os"), 1);
|
|
218436
|
+
var import_node_path20 = __toESM(require("node:path"), 1);
|
|
218437
218437
|
var import_node_crypto4 = require("node:crypto");
|
|
218438
218438
|
|
|
218439
218439
|
// node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
|
|
@@ -221857,7 +221857,7 @@ var categories_default_default = {
|
|
|
221857
221857
|
// package.json
|
|
221858
221858
|
var package_default = {
|
|
221859
221859
|
name: "claude-use",
|
|
221860
|
-
version: "0.3.
|
|
221860
|
+
version: "0.3.6",
|
|
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>",
|
|
@@ -221873,7 +221873,7 @@ var package_default = {
|
|
|
221873
221873
|
"cli"
|
|
221874
221874
|
],
|
|
221875
221875
|
packageManager: "pnpm@11.6.0",
|
|
221876
|
-
type: "
|
|
221876
|
+
type: "module",
|
|
221877
221877
|
engines: {
|
|
221878
221878
|
node: ">=22.12.0"
|
|
221879
221879
|
},
|
|
@@ -221885,10 +221885,18 @@ var package_default = {
|
|
|
221885
221885
|
],
|
|
221886
221886
|
scripts: {
|
|
221887
221887
|
prepare: "husky",
|
|
221888
|
-
typecheck: "
|
|
221889
|
-
|
|
221890
|
-
|
|
221891
|
-
|
|
221888
|
+
typecheck: "turbo run _typecheck",
|
|
221889
|
+
_typecheck: "tsc --noEmit",
|
|
221890
|
+
lint: "turbo run _lint",
|
|
221891
|
+
_lint: "eslint . --cache --cache-location .eslintcache --max-warnings 0",
|
|
221892
|
+
test: "turbo run _test",
|
|
221893
|
+
_test: "vitest run",
|
|
221894
|
+
"test:coverage": "turbo run _test:coverage",
|
|
221895
|
+
"_test:coverage": "vitest run --coverage",
|
|
221896
|
+
knip: "turbo run _knip",
|
|
221897
|
+
_knip: "knip --cache",
|
|
221898
|
+
build: "turbo run _build",
|
|
221899
|
+
_build: "node scripts/build.mts",
|
|
221892
221900
|
"build:bundle": "node scripts/build.mts --bundle-only",
|
|
221893
221901
|
prepublishOnly: "pnpm build:bundle",
|
|
221894
221902
|
schema: "node scripts/gen-schema.mts"
|
|
@@ -221905,19 +221913,21 @@ var package_default = {
|
|
|
221905
221913
|
"@commitlint/config-conventional": "^21.2.0",
|
|
221906
221914
|
"@eslint/js": "^10.0.1",
|
|
221907
221915
|
"@semantic-release/changelog": "^7.0.0",
|
|
221908
|
-
"@semantic-release/exec": "^7.1.0",
|
|
221909
221916
|
"@semantic-release/git": "^11.0.1",
|
|
221910
221917
|
"@semantic-release/npm": "^13.1.5",
|
|
221911
221918
|
"@types/node": "^26.1.2",
|
|
221912
221919
|
"@types/picomatch": "^4.0.3",
|
|
221920
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
221913
221921
|
"conventional-changelog-conventionalcommits": "^10.2.1",
|
|
221914
221922
|
esbuild: "^0.28.1",
|
|
221915
221923
|
eslint: "^10.8.0",
|
|
221916
221924
|
globals: "^17.8.0",
|
|
221917
221925
|
husky: "^9.1.7",
|
|
221918
221926
|
jiti: "^2.7.0",
|
|
221927
|
+
knip: "^6.31.0",
|
|
221919
221928
|
"lint-staged": "^17.3.0",
|
|
221920
221929
|
"semantic-release": "^25.0.8",
|
|
221930
|
+
turbo: "^2.10.8",
|
|
221921
221931
|
typescript: "^6.0.3",
|
|
221922
221932
|
"typescript-eslint": "^8.65.0",
|
|
221923
221933
|
vitest: "^4.1.10"
|
|
@@ -221925,7 +221935,7 @@ var package_default = {
|
|
|
221925
221935
|
};
|
|
221926
221936
|
|
|
221927
221937
|
// src/config/load.ts
|
|
221928
|
-
var import_cosmiconfig = __toESM(require_dist());
|
|
221938
|
+
var import_cosmiconfig = __toESM(require_dist(), 1);
|
|
221929
221939
|
function createExplorer(moduleName = "claude-use") {
|
|
221930
221940
|
return (0, import_cosmiconfig.cosmiconfigSync)(moduleName, { searchPlaces: [] });
|
|
221931
221941
|
}
|
|
@@ -236609,8 +236619,8 @@ var SHIPPED_CATEGORY_DEFAULTS = Object.freeze({
|
|
|
236609
236619
|
});
|
|
236610
236620
|
|
|
236611
236621
|
// src/config/store.ts
|
|
236612
|
-
var import_node_fs2 = __toESM(require("node:fs"));
|
|
236613
|
-
var import_node_path2 = __toESM(require("node:path"));
|
|
236622
|
+
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
236623
|
+
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
236614
236624
|
var import_node_crypto = require("node:crypto");
|
|
236615
236625
|
function isEnoent(error51) {
|
|
236616
236626
|
return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
|
|
@@ -236690,14 +236700,14 @@ function applyPatch(filePath, schema, patch, options = {}) {
|
|
|
236690
236700
|
}
|
|
236691
236701
|
|
|
236692
236702
|
// src/check.ts
|
|
236693
|
-
var import_node_os3 = __toESM(require("node:os"));
|
|
236694
|
-
var import_node_path13 = __toESM(require("node:path"));
|
|
236703
|
+
var import_node_os3 = __toESM(require("node:os"), 1);
|
|
236704
|
+
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
236695
236705
|
|
|
236696
236706
|
// src/launcher/cascade.ts
|
|
236697
|
-
var
|
|
236707
|
+
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
236698
236708
|
|
|
236699
236709
|
// src/pathNorm.ts
|
|
236700
|
-
var import_node_path3 = __toESM(require("node:path"));
|
|
236710
|
+
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
236701
236711
|
function expandTilde(value, home) {
|
|
236702
236712
|
if (value === "~") {
|
|
236703
236713
|
return home;
|
|
@@ -236718,8 +236728,249 @@ function isAncestorOrSelf(ancestor, descendant) {
|
|
|
236718
236728
|
return descendant.startsWith(withSeparator);
|
|
236719
236729
|
}
|
|
236720
236730
|
|
|
236731
|
+
// src/resolve/walk.ts
|
|
236732
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
236733
|
+
|
|
236734
|
+
// src/resolve/extends.ts
|
|
236735
|
+
function lineariseProfile(rootName, load) {
|
|
236736
|
+
const stack = [];
|
|
236737
|
+
const visited = /* @__PURE__ */ new Set();
|
|
236738
|
+
const order = [];
|
|
236739
|
+
const diagnostics = [];
|
|
236740
|
+
const visit = (name) => {
|
|
236741
|
+
if (stack.includes(name)) {
|
|
236742
|
+
diagnostics.push({
|
|
236743
|
+
code: "EXTENDS_CYCLE",
|
|
236744
|
+
severity: "error",
|
|
236745
|
+
message: `Circular profile extends: ${[...stack, name].join(" -> ")}. The cycle is broken here and "${name}" is not applied twice.`,
|
|
236746
|
+
subject: name
|
|
236747
|
+
});
|
|
236748
|
+
return;
|
|
236749
|
+
}
|
|
236750
|
+
if (visited.has(name)) {
|
|
236751
|
+
return;
|
|
236752
|
+
}
|
|
236753
|
+
const source = load(name);
|
|
236754
|
+
if (source === void 0) {
|
|
236755
|
+
diagnostics.push({
|
|
236756
|
+
code: "MISSING_PROFILE",
|
|
236757
|
+
severity: "error",
|
|
236758
|
+
message: `Configuration profile "${name}" does not exist.`,
|
|
236759
|
+
subject: name
|
|
236760
|
+
});
|
|
236761
|
+
visited.add(name);
|
|
236762
|
+
return;
|
|
236763
|
+
}
|
|
236764
|
+
stack.push(name);
|
|
236765
|
+
for (const dependency of source.profile.extends ?? []) {
|
|
236766
|
+
visit(dependency);
|
|
236767
|
+
}
|
|
236768
|
+
stack.pop();
|
|
236769
|
+
visited.add(name);
|
|
236770
|
+
order.push(name);
|
|
236771
|
+
};
|
|
236772
|
+
visit(rootName);
|
|
236773
|
+
return { order, diagnostics };
|
|
236774
|
+
}
|
|
236775
|
+
function profileLayers(rootName, load, startId) {
|
|
236776
|
+
const { order, diagnostics } = lineariseProfile(rootName, load);
|
|
236777
|
+
const layers = [];
|
|
236778
|
+
let nextId = startId;
|
|
236779
|
+
for (const name of order) {
|
|
236780
|
+
const source = load(name);
|
|
236781
|
+
if (source === void 0) {
|
|
236782
|
+
continue;
|
|
236783
|
+
}
|
|
236784
|
+
layers.push({
|
|
236785
|
+
id: nextId,
|
|
236786
|
+
kind: "config-profile",
|
|
236787
|
+
source: source.filepath ?? name,
|
|
236788
|
+
...source.profile.categories === void 0 ? {} : { categories: source.profile.categories },
|
|
236789
|
+
...source.profile.entries === void 0 ? {} : { entries: source.profile.entries },
|
|
236790
|
+
...source.entryOrder === void 0 ? {} : { entryOrder: source.entryOrder },
|
|
236791
|
+
...source.profile.launch === void 0 ? {} : { launch: source.profile.launch }
|
|
236792
|
+
});
|
|
236793
|
+
nextId += 1;
|
|
236794
|
+
}
|
|
236795
|
+
return { layers, nextId, diagnostics };
|
|
236796
|
+
}
|
|
236797
|
+
|
|
236798
|
+
// src/resolve/walk.ts
|
|
236799
|
+
function walkDirectoryAncestors(cwd, options) {
|
|
236800
|
+
const limit = import_node_path4.default.resolve(options.limit ?? options.home);
|
|
236801
|
+
const isReadable = options.isReadable ?? (() => true);
|
|
236802
|
+
const collected = [];
|
|
236803
|
+
let current = import_node_path4.default.resolve(cwd);
|
|
236804
|
+
for (; ; ) {
|
|
236805
|
+
if (!isReadable(current)) {
|
|
236806
|
+
break;
|
|
236807
|
+
}
|
|
236808
|
+
collected.push(current);
|
|
236809
|
+
if (current === limit) {
|
|
236810
|
+
break;
|
|
236811
|
+
}
|
|
236812
|
+
const parent = import_node_path4.default.dirname(current);
|
|
236813
|
+
if (parent === current) {
|
|
236814
|
+
break;
|
|
236815
|
+
}
|
|
236816
|
+
current = parent;
|
|
236817
|
+
}
|
|
236818
|
+
return collected.reverse();
|
|
236819
|
+
}
|
|
236820
|
+
function assembleCascade(input) {
|
|
236821
|
+
const layers = [];
|
|
236822
|
+
const diagnostics = [];
|
|
236823
|
+
let nextId = 0;
|
|
236824
|
+
let identityPin;
|
|
236825
|
+
let directoryConfigProfile;
|
|
236826
|
+
if (input.globalConfig !== void 0) {
|
|
236827
|
+
const { config: config2, entryOrder, filepath } = input.globalConfig;
|
|
236828
|
+
layers.push({
|
|
236829
|
+
id: nextId,
|
|
236830
|
+
kind: "global-config",
|
|
236831
|
+
source: filepath,
|
|
236832
|
+
...config2.categories === void 0 ? {} : { categories: config2.categories },
|
|
236833
|
+
...config2.entries === void 0 ? {} : { entries: config2.entries },
|
|
236834
|
+
...entryOrder === void 0 ? {} : { entryOrder },
|
|
236835
|
+
...config2.launch === void 0 ? {} : { launch: config2.launch }
|
|
236836
|
+
});
|
|
236837
|
+
nextId += 1;
|
|
236838
|
+
}
|
|
236839
|
+
if (input.baseConfigProfile !== void 0) {
|
|
236840
|
+
const resolved = profileLayers(input.baseConfigProfile, input.loadProfile, nextId);
|
|
236841
|
+
layers.push(...resolved.layers);
|
|
236842
|
+
diagnostics.push(...resolved.diagnostics);
|
|
236843
|
+
nextId = resolved.nextId;
|
|
236844
|
+
}
|
|
236845
|
+
for (const level of input.levels ?? []) {
|
|
236846
|
+
const sources = [];
|
|
236847
|
+
if (level.portable !== void 0) {
|
|
236848
|
+
sources.push({ kind: "portable", config: level.portable.config, ...level.portable.entryOrder === void 0 ? {} : { entryOrder: level.portable.entryOrder }, filepath: level.portable.filepath });
|
|
236849
|
+
}
|
|
236850
|
+
for (const entry of level.rules ?? []) {
|
|
236851
|
+
sources.push({ kind: "directory-rule", config: entry.rule, ...entry.entryOrder === void 0 ? {} : { entryOrder: entry.entryOrder }, filepath: entry.filepath });
|
|
236852
|
+
}
|
|
236853
|
+
if (level.portableLocal !== void 0) {
|
|
236854
|
+
sources.push({ kind: "portable-local", config: level.portableLocal.config, ...level.portableLocal.entryOrder === void 0 ? {} : { entryOrder: level.portableLocal.entryOrder }, filepath: level.portableLocal.filepath });
|
|
236855
|
+
}
|
|
236856
|
+
for (const source of sources) {
|
|
236857
|
+
if (source.config.identity !== void 0) {
|
|
236858
|
+
identityPin = source.config.identity;
|
|
236859
|
+
}
|
|
236860
|
+
if (source.config.configProfile !== void 0) {
|
|
236861
|
+
directoryConfigProfile = source.config.configProfile;
|
|
236862
|
+
const resolved = profileLayers(source.config.configProfile, input.loadProfile, nextId);
|
|
236863
|
+
layers.push(...resolved.layers);
|
|
236864
|
+
diagnostics.push(...resolved.diagnostics);
|
|
236865
|
+
nextId = resolved.nextId;
|
|
236866
|
+
}
|
|
236867
|
+
layers.push({
|
|
236868
|
+
id: nextId,
|
|
236869
|
+
kind: source.kind,
|
|
236870
|
+
source: source.filepath,
|
|
236871
|
+
...source.config.categories === void 0 ? {} : { categories: source.config.categories },
|
|
236872
|
+
...source.config.entries === void 0 ? {} : { entries: source.config.entries },
|
|
236873
|
+
...source.entryOrder === void 0 ? {} : { entryOrder: source.entryOrder },
|
|
236874
|
+
...source.config.launch === void 0 ? {} : { launch: source.config.launch }
|
|
236875
|
+
});
|
|
236876
|
+
nextId += 1;
|
|
236877
|
+
}
|
|
236878
|
+
}
|
|
236879
|
+
if (input.cliOverride !== void 0) {
|
|
236880
|
+
layers.push({
|
|
236881
|
+
id: nextId,
|
|
236882
|
+
kind: "cli-override",
|
|
236883
|
+
source: "command line",
|
|
236884
|
+
...input.cliOverride.categories === void 0 ? {} : { categories: input.cliOverride.categories },
|
|
236885
|
+
...input.cliOverride.entries === void 0 ? {} : { entries: input.cliOverride.entries },
|
|
236886
|
+
...input.cliOverride.entryOrder === void 0 ? {} : { entryOrder: input.cliOverride.entryOrder },
|
|
236887
|
+
...input.cliOverride.launch === void 0 ? {} : { launch: input.cliOverride.launch }
|
|
236888
|
+
});
|
|
236889
|
+
}
|
|
236890
|
+
return {
|
|
236891
|
+
layers,
|
|
236892
|
+
diagnostics,
|
|
236893
|
+
...identityPin === void 0 ? {} : { identityPin },
|
|
236894
|
+
...directoryConfigProfile === void 0 ? {} : { directoryConfigProfile }
|
|
236895
|
+
};
|
|
236896
|
+
}
|
|
236897
|
+
|
|
236898
|
+
// src/launcher/cascade.ts
|
|
236899
|
+
var PORTABLE_CONFIG_FILENAME = ".claude-use.json";
|
|
236900
|
+
var PORTABLE_LOCAL_CONFIG_FILENAME = ".claude-use.local.json";
|
|
236901
|
+
function loadCascadeInput(params) {
|
|
236902
|
+
const globalConfig2 = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
|
|
236903
|
+
const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
|
|
236904
|
+
const loadProfile = (name) => {
|
|
236905
|
+
const filepath = import_node_path5.default.join(params.paths.configProfilesDir, `${name}.json`);
|
|
236906
|
+
const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
|
|
236907
|
+
if (loaded === void 0) {
|
|
236908
|
+
return void 0;
|
|
236909
|
+
}
|
|
236910
|
+
return { name, profile: loaded.config, entryOrder: loaded.entryOrder, filepath: loaded.filepath };
|
|
236911
|
+
};
|
|
236912
|
+
const limit = globalConfig2?.config.walkUpLimit;
|
|
236913
|
+
const dirs = walkDirectoryAncestors(params.cwd, {
|
|
236914
|
+
home: params.home,
|
|
236915
|
+
...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
|
|
236916
|
+
...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
|
|
236917
|
+
});
|
|
236918
|
+
const levels = [];
|
|
236919
|
+
for (const dir of dirs) {
|
|
236920
|
+
const portable = loadConfigFile(import_node_path5.default.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
236921
|
+
const portableLocal = loadConfigFile(import_node_path5.default.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
236922
|
+
const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
|
|
236923
|
+
const entryOrder = directoryRules?.ruleEntryOrders[index];
|
|
236924
|
+
return {
|
|
236925
|
+
rule,
|
|
236926
|
+
...entryOrder === void 0 ? {} : { entryOrder },
|
|
236927
|
+
filepath: params.paths.directoryRulesFile
|
|
236928
|
+
};
|
|
236929
|
+
});
|
|
236930
|
+
if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
|
|
236931
|
+
continue;
|
|
236932
|
+
}
|
|
236933
|
+
levels.push({
|
|
236934
|
+
dir,
|
|
236935
|
+
...portable === void 0 ? {} : { portable: { config: portable.config, entryOrder: portable.entryOrder, filepath: portable.filepath } },
|
|
236936
|
+
...rules.length === 0 ? {} : { rules },
|
|
236937
|
+
...portableLocal === void 0 ? {} : { portableLocal: { config: portableLocal.config, entryOrder: portableLocal.entryOrder, filepath: portableLocal.filepath } }
|
|
236938
|
+
});
|
|
236939
|
+
}
|
|
236940
|
+
const input = {
|
|
236941
|
+
home: params.home,
|
|
236942
|
+
...globalConfig2 === void 0 ? {} : { globalConfig: { config: globalConfig2.config, entryOrder: globalConfig2.entryOrder, filepath: globalConfig2.filepath } },
|
|
236943
|
+
...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
|
|
236944
|
+
loadProfile,
|
|
236945
|
+
levels,
|
|
236946
|
+
...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
|
|
236947
|
+
};
|
|
236948
|
+
return { input, ...globalConfig2 === void 0 ? {} : { globalConfig: globalConfig2.config } };
|
|
236949
|
+
}
|
|
236950
|
+
function readDirectorySelections(loaded) {
|
|
236951
|
+
let identity;
|
|
236952
|
+
let configProfile;
|
|
236953
|
+
for (const level of loaded.input.levels ?? []) {
|
|
236954
|
+
for (const source of [level.portable?.config, ...(level.rules ?? []).map((entry) => entry.rule), level.portableLocal?.config]) {
|
|
236955
|
+
if (source === void 0) {
|
|
236956
|
+
continue;
|
|
236957
|
+
}
|
|
236958
|
+
if (source.identity !== void 0) {
|
|
236959
|
+
identity = source.identity;
|
|
236960
|
+
}
|
|
236961
|
+
if (source.configProfile !== void 0) {
|
|
236962
|
+
configProfile = source.configProfile;
|
|
236963
|
+
}
|
|
236964
|
+
}
|
|
236965
|
+
}
|
|
236966
|
+
return { ...identity === void 0 ? {} : { identity }, ...configProfile === void 0 ? {} : { configProfile } };
|
|
236967
|
+
}
|
|
236968
|
+
|
|
236969
|
+
// src/launcher/farm.ts
|
|
236970
|
+
var import_node_path8 = __toESM(require("node:path"), 1);
|
|
236971
|
+
|
|
236721
236972
|
// src/config/classify.ts
|
|
236722
|
-
var import_picomatch = __toESM(require_picomatch2());
|
|
236973
|
+
var import_picomatch = __toESM(require_picomatch2(), 1);
|
|
236723
236974
|
var GLOB_METACHARACTERS = /[*?[\]{}!()]/;
|
|
236724
236975
|
function isExactPattern(pattern) {
|
|
236725
236976
|
return !GLOB_METACHARACTERS.test(pattern);
|
|
@@ -236793,30 +237044,120 @@ function classifyEntries(names, options) {
|
|
|
236793
237044
|
return { classification, unclassified, decidedBy };
|
|
236794
237045
|
}
|
|
236795
237046
|
|
|
236796
|
-
// src/resolve/
|
|
236797
|
-
|
|
236798
|
-
|
|
236799
|
-
|
|
236800
|
-
|
|
236801
|
-
|
|
236802
|
-
|
|
236803
|
-
|
|
236804
|
-
|
|
236805
|
-
|
|
236806
|
-
|
|
236807
|
-
|
|
236808
|
-
|
|
236809
|
-
|
|
236810
|
-
|
|
236811
|
-
}
|
|
236812
|
-
const
|
|
236813
|
-
|
|
236814
|
-
|
|
236815
|
-
|
|
236816
|
-
|
|
236817
|
-
|
|
236818
|
-
|
|
236819
|
-
|
|
237047
|
+
// src/resolve/reconcile.ts
|
|
237048
|
+
function parentOf(rel) {
|
|
237049
|
+
const index = rel.lastIndexOf("/");
|
|
237050
|
+
return index === -1 ? "" : rel.slice(0, index);
|
|
237051
|
+
}
|
|
237052
|
+
function indexByParent(listing) {
|
|
237053
|
+
const children = /* @__PURE__ */ new Map();
|
|
237054
|
+
for (const entry of listing) {
|
|
237055
|
+
const parent = parentOf(entry.rel);
|
|
237056
|
+
const bucket = children.get(parent);
|
|
237057
|
+
if (bucket === void 0) {
|
|
237058
|
+
children.set(parent, [entry]);
|
|
237059
|
+
} else {
|
|
237060
|
+
bucket.push(entry);
|
|
237061
|
+
}
|
|
237062
|
+
}
|
|
237063
|
+
for (const bucket of children.values()) {
|
|
237064
|
+
bucket.sort((a2, b) => a2.rel < b.rel ? -1 : a2.rel > b.rel ? 1 : 0);
|
|
237065
|
+
}
|
|
237066
|
+
return children;
|
|
237067
|
+
}
|
|
237068
|
+
function planReconciliation(params) {
|
|
237069
|
+
const conservative = params.manifest === void 0;
|
|
237070
|
+
const diagnostics = [];
|
|
237071
|
+
const actions = [];
|
|
237072
|
+
const farmChildren = indexByParent(params.farmListing);
|
|
237073
|
+
const canonicalByRel = new Map(params.canonicalListing.map((entry) => [entry.rel, entry]));
|
|
237074
|
+
const materialised = /* @__PURE__ */ new Set([""]);
|
|
237075
|
+
if (params.manifest !== void 0) {
|
|
237076
|
+
for (const rel of params.manifest.materialised) {
|
|
237077
|
+
materialised.add(rel);
|
|
237078
|
+
}
|
|
237079
|
+
} else {
|
|
237080
|
+
for (const entry of params.farmListing) {
|
|
237081
|
+
if (entry.kind === "dir") {
|
|
237082
|
+
materialised.add(entry.rel);
|
|
237083
|
+
}
|
|
237084
|
+
}
|
|
237085
|
+
diagnostics.push({
|
|
237086
|
+
code: "FARM_MANIFEST_MISSING",
|
|
237087
|
+
severity: "warning",
|
|
237088
|
+
message: "The previous farm's manifest is missing or unreadable, so there is no record of which directories the farm built itself. Every real directory in the farm was treated as materialised and every real child examined for adoption. This over-adopts rather than risking data Claude Code wrote being discarded."
|
|
237089
|
+
});
|
|
237090
|
+
}
|
|
237091
|
+
const visit = (dir) => {
|
|
237092
|
+
for (const child of farmChildren.get(dir) ?? []) {
|
|
237093
|
+
if (child.kind === "symlink") {
|
|
237094
|
+
actions.push({ kind: "skip", rel: child.rel, reason: "is-symlink" });
|
|
237095
|
+
continue;
|
|
237096
|
+
}
|
|
237097
|
+
if (child.kind === "dir" && materialised.has(child.rel)) {
|
|
237098
|
+
actions.push({ kind: "skip", rel: child.rel, reason: "tracked-materialised-dir" });
|
|
237099
|
+
visit(child.rel);
|
|
237100
|
+
continue;
|
|
237101
|
+
}
|
|
237102
|
+
if (child.kind === "dir") {
|
|
237103
|
+
const grandchildren = farmChildren.get(child.rel) ?? [];
|
|
237104
|
+
if (grandchildren.length === 0) {
|
|
237105
|
+
if (canonicalByRel.has(child.rel)) {
|
|
237106
|
+
actions.push({ kind: "skip", rel: child.rel, reason: "identical-content" });
|
|
237107
|
+
} else {
|
|
237108
|
+
actions.push({ kind: "adopt", rel: child.rel, reason: "absent-from-canonical" });
|
|
237109
|
+
}
|
|
237110
|
+
continue;
|
|
237111
|
+
}
|
|
237112
|
+
visit(child.rel);
|
|
237113
|
+
continue;
|
|
237114
|
+
}
|
|
237115
|
+
const canonical = canonicalByRel.get(child.rel);
|
|
237116
|
+
if (canonical === void 0) {
|
|
237117
|
+
actions.push({ kind: "adopt", rel: child.rel, reason: "absent-from-canonical" });
|
|
237118
|
+
continue;
|
|
237119
|
+
}
|
|
237120
|
+
if (canonical.contentHash !== void 0 && canonical.contentHash === child.contentHash) {
|
|
237121
|
+
actions.push({ kind: "skip", rel: child.rel, reason: "identical-content" });
|
|
237122
|
+
continue;
|
|
237123
|
+
}
|
|
237124
|
+
actions.push({ kind: "adopt-conflict", rel: child.rel });
|
|
237125
|
+
diagnostics.push({
|
|
237126
|
+
code: "RECONCILE_CONFLICT",
|
|
237127
|
+
severity: "warning",
|
|
237128
|
+
message: `"${child.rel}" exists both in the farm and in the canonical ~/.claude with different content. The canonical copy is left authoritative and the farm's copy is preserved alongside it rather than either being discarded.`,
|
|
237129
|
+
subject: child.rel
|
|
237130
|
+
});
|
|
237131
|
+
}
|
|
237132
|
+
};
|
|
237133
|
+
visit("");
|
|
237134
|
+
return { actions, diagnostics, conservative };
|
|
237135
|
+
}
|
|
237136
|
+
|
|
237137
|
+
// src/resolve/conditions.ts
|
|
237138
|
+
var import_picomatch2 = __toESM(require_picomatch2(), 1);
|
|
237139
|
+
var MILLISECONDS_PER_UNIT = Object.freeze({
|
|
237140
|
+
ms: 1,
|
|
237141
|
+
s: 1e3,
|
|
237142
|
+
m: 6e4,
|
|
237143
|
+
h: 36e5,
|
|
237144
|
+
d: 864e5,
|
|
237145
|
+
w: 6048e5
|
|
237146
|
+
});
|
|
237147
|
+
var DURATION_PARTS_RE = /^(0|[1-9][0-9]*)(ms|s|m|h|d|w)$/;
|
|
237148
|
+
function parseDuration(value) {
|
|
237149
|
+
const parts = DURATION_PARTS_RE.exec(value);
|
|
237150
|
+
if (parts === null) {
|
|
237151
|
+
throw new Error(`"${value}" is not a valid duration. Expected a count followed by ms, s, m, h, d, or w.`);
|
|
237152
|
+
}
|
|
237153
|
+
const [, count, unit] = parts;
|
|
237154
|
+
const multiplier = MILLISECONDS_PER_UNIT[unit];
|
|
237155
|
+
if (count === void 0 || multiplier === void 0) {
|
|
237156
|
+
throw new Error(`"${value}" is not a valid duration.`);
|
|
237157
|
+
}
|
|
237158
|
+
return Number(count) * multiplier;
|
|
237159
|
+
}
|
|
237160
|
+
function matchBranch(pattern, branch, detached = false) {
|
|
236820
237161
|
if (branch === void 0 || branch === "" || detached) {
|
|
236821
237162
|
return false;
|
|
236822
237163
|
}
|
|
@@ -236871,7 +237212,7 @@ function isVacuousWhen(when) {
|
|
|
236871
237212
|
}
|
|
236872
237213
|
|
|
236873
237214
|
// src/resolve/match.ts
|
|
236874
|
-
var import_picomatch3 = __toESM(require_picomatch2());
|
|
237215
|
+
var import_picomatch3 = __toESM(require_picomatch2(), 1);
|
|
236875
237216
|
|
|
236876
237217
|
// src/resolve/projects.ts
|
|
236877
237218
|
var NON_ALPHANUMERIC = /[^A-Za-z0-9]/g;
|
|
@@ -237360,15 +237701,15 @@ function dedupeDiagnostics(diagnostics) {
|
|
|
237360
237701
|
}
|
|
237361
237702
|
|
|
237362
237703
|
// src/resolve/plan.ts
|
|
237363
|
-
var
|
|
237364
|
-
function
|
|
237704
|
+
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
237705
|
+
function parentOf2(rel) {
|
|
237365
237706
|
const index = rel.lastIndexOf("/");
|
|
237366
237707
|
return index === -1 ? "" : rel.slice(0, index);
|
|
237367
237708
|
}
|
|
237368
237709
|
function buildChildIndex(facts) {
|
|
237369
237710
|
const children = /* @__PURE__ */ new Map();
|
|
237370
237711
|
for (const rel of facts.entries.keys()) {
|
|
237371
|
-
const parent =
|
|
237712
|
+
const parent = parentOf2(rel);
|
|
237372
237713
|
const bucket = children.get(parent);
|
|
237373
237714
|
if (bucket === void 0) {
|
|
237374
237715
|
children.set(parent, [rel]);
|
|
@@ -237410,7 +237751,7 @@ function planFarm(params) {
|
|
|
237410
237751
|
}
|
|
237411
237752
|
return value;
|
|
237412
237753
|
};
|
|
237413
|
-
const linkTarget = (rel) =>
|
|
237754
|
+
const linkTarget = (rel) => import_node_path6.default.join(params.facts.claudeHome, rel);
|
|
237414
237755
|
const visit = (rel) => {
|
|
237415
237756
|
const fact = params.facts.entries.get(rel);
|
|
237416
237757
|
const decision = params.decisions.get(rel);
|
|
@@ -237456,264 +237797,7 @@ function planFarm(params) {
|
|
|
237456
237797
|
return { entries, materialised, links };
|
|
237457
237798
|
}
|
|
237458
237799
|
|
|
237459
|
-
// src/resolve/
|
|
237460
|
-
var import_node_path5 = __toESM(require("node:path"));
|
|
237461
|
-
|
|
237462
|
-
// src/resolve/extends.ts
|
|
237463
|
-
function lineariseProfile(rootName, load) {
|
|
237464
|
-
const stack = [];
|
|
237465
|
-
const visited = /* @__PURE__ */ new Set();
|
|
237466
|
-
const order = [];
|
|
237467
|
-
const diagnostics = [];
|
|
237468
|
-
const visit = (name) => {
|
|
237469
|
-
if (stack.includes(name)) {
|
|
237470
|
-
diagnostics.push({
|
|
237471
|
-
code: "EXTENDS_CYCLE",
|
|
237472
|
-
severity: "error",
|
|
237473
|
-
message: `Circular profile extends: ${[...stack, name].join(" -> ")}. The cycle is broken here and "${name}" is not applied twice.`,
|
|
237474
|
-
subject: name
|
|
237475
|
-
});
|
|
237476
|
-
return;
|
|
237477
|
-
}
|
|
237478
|
-
if (visited.has(name)) {
|
|
237479
|
-
return;
|
|
237480
|
-
}
|
|
237481
|
-
const source = load(name);
|
|
237482
|
-
if (source === void 0) {
|
|
237483
|
-
diagnostics.push({
|
|
237484
|
-
code: "MISSING_PROFILE",
|
|
237485
|
-
severity: "error",
|
|
237486
|
-
message: `Configuration profile "${name}" does not exist.`,
|
|
237487
|
-
subject: name
|
|
237488
|
-
});
|
|
237489
|
-
visited.add(name);
|
|
237490
|
-
return;
|
|
237491
|
-
}
|
|
237492
|
-
stack.push(name);
|
|
237493
|
-
for (const dependency of source.profile.extends ?? []) {
|
|
237494
|
-
visit(dependency);
|
|
237495
|
-
}
|
|
237496
|
-
stack.pop();
|
|
237497
|
-
visited.add(name);
|
|
237498
|
-
order.push(name);
|
|
237499
|
-
};
|
|
237500
|
-
visit(rootName);
|
|
237501
|
-
return { order, diagnostics };
|
|
237502
|
-
}
|
|
237503
|
-
function profileLayers(rootName, load, startId) {
|
|
237504
|
-
const { order, diagnostics } = lineariseProfile(rootName, load);
|
|
237505
|
-
const layers = [];
|
|
237506
|
-
let nextId = startId;
|
|
237507
|
-
for (const name of order) {
|
|
237508
|
-
const source = load(name);
|
|
237509
|
-
if (source === void 0) {
|
|
237510
|
-
continue;
|
|
237511
|
-
}
|
|
237512
|
-
layers.push({
|
|
237513
|
-
id: nextId,
|
|
237514
|
-
kind: "config-profile",
|
|
237515
|
-
source: source.filepath ?? name,
|
|
237516
|
-
...source.profile.categories === void 0 ? {} : { categories: source.profile.categories },
|
|
237517
|
-
...source.profile.entries === void 0 ? {} : { entries: source.profile.entries },
|
|
237518
|
-
...source.entryOrder === void 0 ? {} : { entryOrder: source.entryOrder },
|
|
237519
|
-
...source.profile.launch === void 0 ? {} : { launch: source.profile.launch }
|
|
237520
|
-
});
|
|
237521
|
-
nextId += 1;
|
|
237522
|
-
}
|
|
237523
|
-
return { layers, nextId, diagnostics };
|
|
237524
|
-
}
|
|
237525
|
-
|
|
237526
|
-
// src/resolve/walk.ts
|
|
237527
|
-
function walkDirectoryAncestors(cwd, options) {
|
|
237528
|
-
const limit = import_node_path5.default.resolve(options.limit ?? options.home);
|
|
237529
|
-
const isReadable = options.isReadable ?? (() => true);
|
|
237530
|
-
const collected = [];
|
|
237531
|
-
let current = import_node_path5.default.resolve(cwd);
|
|
237532
|
-
for (; ; ) {
|
|
237533
|
-
if (!isReadable(current)) {
|
|
237534
|
-
break;
|
|
237535
|
-
}
|
|
237536
|
-
collected.push(current);
|
|
237537
|
-
if (current === limit) {
|
|
237538
|
-
break;
|
|
237539
|
-
}
|
|
237540
|
-
const parent = import_node_path5.default.dirname(current);
|
|
237541
|
-
if (parent === current) {
|
|
237542
|
-
break;
|
|
237543
|
-
}
|
|
237544
|
-
current = parent;
|
|
237545
|
-
}
|
|
237546
|
-
return collected.reverse();
|
|
237547
|
-
}
|
|
237548
|
-
function assembleCascade(input) {
|
|
237549
|
-
const layers = [];
|
|
237550
|
-
const diagnostics = [];
|
|
237551
|
-
let nextId = 0;
|
|
237552
|
-
let identityPin;
|
|
237553
|
-
let directoryConfigProfile;
|
|
237554
|
-
if (input.globalConfig !== void 0) {
|
|
237555
|
-
const { config: config2, entryOrder, filepath } = input.globalConfig;
|
|
237556
|
-
layers.push({
|
|
237557
|
-
id: nextId,
|
|
237558
|
-
kind: "global-config",
|
|
237559
|
-
source: filepath,
|
|
237560
|
-
...config2.categories === void 0 ? {} : { categories: config2.categories },
|
|
237561
|
-
...config2.entries === void 0 ? {} : { entries: config2.entries },
|
|
237562
|
-
...entryOrder === void 0 ? {} : { entryOrder },
|
|
237563
|
-
...config2.launch === void 0 ? {} : { launch: config2.launch }
|
|
237564
|
-
});
|
|
237565
|
-
nextId += 1;
|
|
237566
|
-
}
|
|
237567
|
-
if (input.baseConfigProfile !== void 0) {
|
|
237568
|
-
const resolved = profileLayers(input.baseConfigProfile, input.loadProfile, nextId);
|
|
237569
|
-
layers.push(...resolved.layers);
|
|
237570
|
-
diagnostics.push(...resolved.diagnostics);
|
|
237571
|
-
nextId = resolved.nextId;
|
|
237572
|
-
}
|
|
237573
|
-
for (const level of input.levels ?? []) {
|
|
237574
|
-
const sources = [];
|
|
237575
|
-
if (level.portable !== void 0) {
|
|
237576
|
-
sources.push({ kind: "portable", config: level.portable.config, ...level.portable.entryOrder === void 0 ? {} : { entryOrder: level.portable.entryOrder }, filepath: level.portable.filepath });
|
|
237577
|
-
}
|
|
237578
|
-
for (const entry of level.rules ?? []) {
|
|
237579
|
-
sources.push({ kind: "directory-rule", config: entry.rule, ...entry.entryOrder === void 0 ? {} : { entryOrder: entry.entryOrder }, filepath: entry.filepath });
|
|
237580
|
-
}
|
|
237581
|
-
if (level.portableLocal !== void 0) {
|
|
237582
|
-
sources.push({ kind: "portable-local", config: level.portableLocal.config, ...level.portableLocal.entryOrder === void 0 ? {} : { entryOrder: level.portableLocal.entryOrder }, filepath: level.portableLocal.filepath });
|
|
237583
|
-
}
|
|
237584
|
-
for (const source of sources) {
|
|
237585
|
-
if (source.config.identity !== void 0) {
|
|
237586
|
-
identityPin = source.config.identity;
|
|
237587
|
-
}
|
|
237588
|
-
if (source.config.configProfile !== void 0) {
|
|
237589
|
-
directoryConfigProfile = source.config.configProfile;
|
|
237590
|
-
const resolved = profileLayers(source.config.configProfile, input.loadProfile, nextId);
|
|
237591
|
-
layers.push(...resolved.layers);
|
|
237592
|
-
diagnostics.push(...resolved.diagnostics);
|
|
237593
|
-
nextId = resolved.nextId;
|
|
237594
|
-
}
|
|
237595
|
-
layers.push({
|
|
237596
|
-
id: nextId,
|
|
237597
|
-
kind: source.kind,
|
|
237598
|
-
source: source.filepath,
|
|
237599
|
-
...source.config.categories === void 0 ? {} : { categories: source.config.categories },
|
|
237600
|
-
...source.config.entries === void 0 ? {} : { entries: source.config.entries },
|
|
237601
|
-
...source.entryOrder === void 0 ? {} : { entryOrder: source.entryOrder },
|
|
237602
|
-
...source.config.launch === void 0 ? {} : { launch: source.config.launch }
|
|
237603
|
-
});
|
|
237604
|
-
nextId += 1;
|
|
237605
|
-
}
|
|
237606
|
-
}
|
|
237607
|
-
if (input.cliOverride !== void 0) {
|
|
237608
|
-
layers.push({
|
|
237609
|
-
id: nextId,
|
|
237610
|
-
kind: "cli-override",
|
|
237611
|
-
source: "command line",
|
|
237612
|
-
...input.cliOverride.categories === void 0 ? {} : { categories: input.cliOverride.categories },
|
|
237613
|
-
...input.cliOverride.entries === void 0 ? {} : { entries: input.cliOverride.entries },
|
|
237614
|
-
...input.cliOverride.entryOrder === void 0 ? {} : { entryOrder: input.cliOverride.entryOrder },
|
|
237615
|
-
...input.cliOverride.launch === void 0 ? {} : { launch: input.cliOverride.launch }
|
|
237616
|
-
});
|
|
237617
|
-
}
|
|
237618
|
-
return {
|
|
237619
|
-
layers,
|
|
237620
|
-
diagnostics,
|
|
237621
|
-
...identityPin === void 0 ? {} : { identityPin },
|
|
237622
|
-
...directoryConfigProfile === void 0 ? {} : { directoryConfigProfile }
|
|
237623
|
-
};
|
|
237624
|
-
}
|
|
237625
|
-
|
|
237626
|
-
// src/resolve/reconcile.ts
|
|
237627
|
-
function parentOf2(rel) {
|
|
237628
|
-
const index = rel.lastIndexOf("/");
|
|
237629
|
-
return index === -1 ? "" : rel.slice(0, index);
|
|
237630
|
-
}
|
|
237631
|
-
function indexByParent(listing) {
|
|
237632
|
-
const children = /* @__PURE__ */ new Map();
|
|
237633
|
-
for (const entry of listing) {
|
|
237634
|
-
const parent = parentOf2(entry.rel);
|
|
237635
|
-
const bucket = children.get(parent);
|
|
237636
|
-
if (bucket === void 0) {
|
|
237637
|
-
children.set(parent, [entry]);
|
|
237638
|
-
} else {
|
|
237639
|
-
bucket.push(entry);
|
|
237640
|
-
}
|
|
237641
|
-
}
|
|
237642
|
-
for (const bucket of children.values()) {
|
|
237643
|
-
bucket.sort((a2, b) => a2.rel < b.rel ? -1 : a2.rel > b.rel ? 1 : 0);
|
|
237644
|
-
}
|
|
237645
|
-
return children;
|
|
237646
|
-
}
|
|
237647
|
-
function planReconciliation(params) {
|
|
237648
|
-
const conservative = params.manifest === void 0;
|
|
237649
|
-
const diagnostics = [];
|
|
237650
|
-
const actions = [];
|
|
237651
|
-
const farmChildren = indexByParent(params.farmListing);
|
|
237652
|
-
const canonicalByRel = new Map(params.canonicalListing.map((entry) => [entry.rel, entry]));
|
|
237653
|
-
const materialised = /* @__PURE__ */ new Set([""]);
|
|
237654
|
-
if (params.manifest !== void 0) {
|
|
237655
|
-
for (const rel of params.manifest.materialised) {
|
|
237656
|
-
materialised.add(rel);
|
|
237657
|
-
}
|
|
237658
|
-
} else {
|
|
237659
|
-
for (const entry of params.farmListing) {
|
|
237660
|
-
if (entry.kind === "dir") {
|
|
237661
|
-
materialised.add(entry.rel);
|
|
237662
|
-
}
|
|
237663
|
-
}
|
|
237664
|
-
diagnostics.push({
|
|
237665
|
-
code: "FARM_MANIFEST_MISSING",
|
|
237666
|
-
severity: "warning",
|
|
237667
|
-
message: "The previous farm's manifest is missing or unreadable, so there is no record of which directories the farm built itself. Every real directory in the farm was treated as materialised and every real child examined for adoption. This over-adopts rather than risking data Claude Code wrote being discarded."
|
|
237668
|
-
});
|
|
237669
|
-
}
|
|
237670
|
-
const visit = (dir) => {
|
|
237671
|
-
for (const child of farmChildren.get(dir) ?? []) {
|
|
237672
|
-
if (child.kind === "symlink") {
|
|
237673
|
-
actions.push({ kind: "skip", rel: child.rel, reason: "is-symlink" });
|
|
237674
|
-
continue;
|
|
237675
|
-
}
|
|
237676
|
-
if (child.kind === "dir" && materialised.has(child.rel)) {
|
|
237677
|
-
actions.push({ kind: "skip", rel: child.rel, reason: "tracked-materialised-dir" });
|
|
237678
|
-
visit(child.rel);
|
|
237679
|
-
continue;
|
|
237680
|
-
}
|
|
237681
|
-
if (child.kind === "dir") {
|
|
237682
|
-
const grandchildren = farmChildren.get(child.rel) ?? [];
|
|
237683
|
-
if (grandchildren.length === 0) {
|
|
237684
|
-
if (canonicalByRel.has(child.rel)) {
|
|
237685
|
-
actions.push({ kind: "skip", rel: child.rel, reason: "identical-content" });
|
|
237686
|
-
} else {
|
|
237687
|
-
actions.push({ kind: "adopt", rel: child.rel, reason: "absent-from-canonical" });
|
|
237688
|
-
}
|
|
237689
|
-
continue;
|
|
237690
|
-
}
|
|
237691
|
-
visit(child.rel);
|
|
237692
|
-
continue;
|
|
237693
|
-
}
|
|
237694
|
-
const canonical = canonicalByRel.get(child.rel);
|
|
237695
|
-
if (canonical === void 0) {
|
|
237696
|
-
actions.push({ kind: "adopt", rel: child.rel, reason: "absent-from-canonical" });
|
|
237697
|
-
continue;
|
|
237698
|
-
}
|
|
237699
|
-
if (canonical.contentHash !== void 0 && canonical.contentHash === child.contentHash) {
|
|
237700
|
-
actions.push({ kind: "skip", rel: child.rel, reason: "identical-content" });
|
|
237701
|
-
continue;
|
|
237702
|
-
}
|
|
237703
|
-
actions.push({ kind: "adopt-conflict", rel: child.rel });
|
|
237704
|
-
diagnostics.push({
|
|
237705
|
-
code: "RECONCILE_CONFLICT",
|
|
237706
|
-
severity: "warning",
|
|
237707
|
-
message: `"${child.rel}" exists both in the farm and in the canonical ~/.claude with different content. The canonical copy is left authoritative and the farm's copy is preserved alongside it rather than either being discarded.`,
|
|
237708
|
-
subject: child.rel
|
|
237709
|
-
});
|
|
237710
|
-
}
|
|
237711
|
-
};
|
|
237712
|
-
visit("");
|
|
237713
|
-
return { actions, diagnostics, conservative };
|
|
237714
|
-
}
|
|
237715
|
-
|
|
237716
|
-
// src/resolve.ts
|
|
237800
|
+
// src/resolve/pipeline.ts
|
|
237717
237801
|
function topLevelNames(facts) {
|
|
237718
237802
|
const names = /* @__PURE__ */ new Set();
|
|
237719
237803
|
for (const rel of facts.entries.keys()) {
|
|
@@ -237747,83 +237831,9 @@ function resolveDecisions(input) {
|
|
|
237747
237831
|
};
|
|
237748
237832
|
}
|
|
237749
237833
|
|
|
237750
|
-
// src/launcher/cascade.ts
|
|
237751
|
-
var PORTABLE_CONFIG_FILENAME = ".claude-use.json";
|
|
237752
|
-
var PORTABLE_LOCAL_CONFIG_FILENAME = ".claude-use.local.json";
|
|
237753
|
-
function loadCascadeInput(params) {
|
|
237754
|
-
const globalConfig2 = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
|
|
237755
|
-
const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
|
|
237756
|
-
const loadProfile = (name) => {
|
|
237757
|
-
const filepath = import_node_path6.default.join(params.paths.configProfilesDir, `${name}.json`);
|
|
237758
|
-
const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
|
|
237759
|
-
if (loaded === void 0) {
|
|
237760
|
-
return void 0;
|
|
237761
|
-
}
|
|
237762
|
-
return { name, profile: loaded.config, entryOrder: loaded.entryOrder, filepath: loaded.filepath };
|
|
237763
|
-
};
|
|
237764
|
-
const limit = globalConfig2?.config.walkUpLimit;
|
|
237765
|
-
const dirs = walkDirectoryAncestors(params.cwd, {
|
|
237766
|
-
home: params.home,
|
|
237767
|
-
...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
|
|
237768
|
-
...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
|
|
237769
|
-
});
|
|
237770
|
-
const levels = [];
|
|
237771
|
-
for (const dir of dirs) {
|
|
237772
|
-
const portable = loadConfigFile(import_node_path6.default.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
237773
|
-
const portableLocal = loadConfigFile(import_node_path6.default.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
237774
|
-
const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
|
|
237775
|
-
const entryOrder = directoryRules?.ruleEntryOrders[index];
|
|
237776
|
-
return {
|
|
237777
|
-
rule,
|
|
237778
|
-
...entryOrder === void 0 ? {} : { entryOrder },
|
|
237779
|
-
filepath: params.paths.directoryRulesFile
|
|
237780
|
-
};
|
|
237781
|
-
});
|
|
237782
|
-
if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
|
|
237783
|
-
continue;
|
|
237784
|
-
}
|
|
237785
|
-
levels.push({
|
|
237786
|
-
dir,
|
|
237787
|
-
...portable === void 0 ? {} : { portable: { config: portable.config, entryOrder: portable.entryOrder, filepath: portable.filepath } },
|
|
237788
|
-
...rules.length === 0 ? {} : { rules },
|
|
237789
|
-
...portableLocal === void 0 ? {} : { portableLocal: { config: portableLocal.config, entryOrder: portableLocal.entryOrder, filepath: portableLocal.filepath } }
|
|
237790
|
-
});
|
|
237791
|
-
}
|
|
237792
|
-
const input = {
|
|
237793
|
-
home: params.home,
|
|
237794
|
-
...globalConfig2 === void 0 ? {} : { globalConfig: { config: globalConfig2.config, entryOrder: globalConfig2.entryOrder, filepath: globalConfig2.filepath } },
|
|
237795
|
-
...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
|
|
237796
|
-
loadProfile,
|
|
237797
|
-
levels,
|
|
237798
|
-
...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
|
|
237799
|
-
};
|
|
237800
|
-
return { input, ...globalConfig2 === void 0 ? {} : { globalConfig: globalConfig2.config } };
|
|
237801
|
-
}
|
|
237802
|
-
function readDirectorySelections(loaded) {
|
|
237803
|
-
let identity;
|
|
237804
|
-
let configProfile;
|
|
237805
|
-
for (const level of loaded.input.levels ?? []) {
|
|
237806
|
-
for (const source of [level.portable?.config, ...(level.rules ?? []).map((entry) => entry.rule), level.portableLocal?.config]) {
|
|
237807
|
-
if (source === void 0) {
|
|
237808
|
-
continue;
|
|
237809
|
-
}
|
|
237810
|
-
if (source.identity !== void 0) {
|
|
237811
|
-
identity = source.identity;
|
|
237812
|
-
}
|
|
237813
|
-
if (source.configProfile !== void 0) {
|
|
237814
|
-
configProfile = source.configProfile;
|
|
237815
|
-
}
|
|
237816
|
-
}
|
|
237817
|
-
}
|
|
237818
|
-
return { ...identity === void 0 ? {} : { identity }, ...configProfile === void 0 ? {} : { configProfile } };
|
|
237819
|
-
}
|
|
237820
|
-
|
|
237821
|
-
// src/launcher/farm.ts
|
|
237822
|
-
var import_node_path8 = __toESM(require("node:path"));
|
|
237823
|
-
|
|
237824
237834
|
// src/launcher/lock.ts
|
|
237825
237835
|
var import_node_crypto2 = require("node:crypto");
|
|
237826
|
-
var import_node_path7 = __toESM(require("node:path"));
|
|
237836
|
+
var import_node_path7 = __toESM(require("node:path"), 1);
|
|
237827
237837
|
var DEFAULT_STALE_AFTER_MS = 12e4;
|
|
237828
237838
|
var DEFAULT_RETRY_DELAY_MS = 50;
|
|
237829
237839
|
var DEFAULT_MAX_ATTEMPTS = 200;
|
|
@@ -238403,7 +238413,7 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
238403
238413
|
}
|
|
238404
238414
|
|
|
238405
238415
|
// src/launcher/identity.ts
|
|
238406
|
-
var import_node_path9 = __toESM(require("node:path"));
|
|
238416
|
+
var import_node_path9 = __toESM(require("node:path"), 1);
|
|
238407
238417
|
function isNonEmpty(value) {
|
|
238408
238418
|
return value !== void 0 && value !== "";
|
|
238409
238419
|
}
|
|
@@ -238449,8 +238459,8 @@ function loadIdentity(identitiesDir, name, fs9) {
|
|
|
238449
238459
|
}
|
|
238450
238460
|
|
|
238451
238461
|
// src/paths.ts
|
|
238452
|
-
var import_node_os = __toESM(require("node:os"));
|
|
238453
|
-
var import_node_path10 = __toESM(require("node:path"));
|
|
238462
|
+
var import_node_os = __toESM(require("node:os"), 1);
|
|
238463
|
+
var import_node_path10 = __toESM(require("node:path"), 1);
|
|
238454
238464
|
function resolveClaudeUseHome() {
|
|
238455
238465
|
const fromEnv = process.env.CLAUDE_USE_HOME;
|
|
238456
238466
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
@@ -238484,13 +238494,13 @@ function resolveLayoutPaths() {
|
|
|
238484
238494
|
// src/realPorts.ts
|
|
238485
238495
|
var import_node_child_process2 = require("node:child_process");
|
|
238486
238496
|
var import_node_crypto3 = require("node:crypto");
|
|
238487
|
-
var import_node_fs3 = __toESM(require("node:fs"));
|
|
238488
|
-
var import_node_path12 = __toESM(require("node:path"));
|
|
238497
|
+
var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
238498
|
+
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
238489
238499
|
var import_node_sea = require("node:sea");
|
|
238490
238500
|
|
|
238491
238501
|
// src/versionDiscovery.ts
|
|
238492
|
-
var import_node_os2 = __toESM(require("node:os"));
|
|
238493
|
-
var import_node_path11 = __toESM(require("node:path"));
|
|
238502
|
+
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
238503
|
+
var import_node_path11 = __toESM(require("node:path"), 1);
|
|
238494
238504
|
function defaultVersionsDir() {
|
|
238495
238505
|
return import_node_path11.default.join(import_node_os2.default.homedir(), ".local", "share", "claude", "versions");
|
|
238496
238506
|
}
|
|
@@ -239089,9 +239099,9 @@ function registerCheckCommand(program2, paths) {
|
|
|
239089
239099
|
}
|
|
239090
239100
|
|
|
239091
239101
|
// src/configure.ts
|
|
239092
|
-
var import_node_fs6 = __toESM(require("node:fs"));
|
|
239093
|
-
var import_node_os4 = __toESM(require("node:os"));
|
|
239094
|
-
var import_node_path16 = __toESM(require("node:path"));
|
|
239102
|
+
var import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
239103
|
+
var import_node_os4 = __toESM(require("node:os"), 1);
|
|
239104
|
+
var import_node_path16 = __toESM(require("node:path"), 1);
|
|
239095
239105
|
|
|
239096
239106
|
// node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.mjs
|
|
239097
239107
|
var import_node_process2 = require("node:process");
|
|
@@ -240135,8 +240145,8 @@ ${b}
|
|
|
240135
240145
|
var i = `${(0, import_node_util3.styleText)("gray", S_BAR)} `;
|
|
240136
240146
|
|
|
240137
240147
|
// src/identityManager.ts
|
|
240138
|
-
var import_node_fs4 = __toESM(require("node:fs"));
|
|
240139
|
-
var import_node_path14 = __toESM(require("node:path"));
|
|
240148
|
+
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
240149
|
+
var import_node_path14 = __toESM(require("node:path"), 1);
|
|
240140
240150
|
var IdentityNotFoundError = class extends Error {
|
|
240141
240151
|
constructor(name) {
|
|
240142
240152
|
super(`No identity named "${name}" \u2014 run \`claude-use identity add ${name}\` first.`);
|
|
@@ -240255,8 +240265,8 @@ function registerIdentityCommand(program2, paths) {
|
|
|
240255
240265
|
}
|
|
240256
240266
|
|
|
240257
240267
|
// src/configProfiles.ts
|
|
240258
|
-
var import_node_fs5 = __toESM(require("node:fs"));
|
|
240259
|
-
var import_node_path15 = __toESM(require("node:path"));
|
|
240268
|
+
var import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
240269
|
+
var import_node_path15 = __toESM(require("node:path"), 1);
|
|
240260
240270
|
|
|
240261
240271
|
// src/cli/parsers.ts
|
|
240262
240272
|
function splitTopLevelCommas(input) {
|
|
@@ -240791,11 +240801,10 @@ async function runProfileDirectMode(context) {
|
|
|
240791
240801
|
deps.prompts.cancel("Cancelled.");
|
|
240792
240802
|
return;
|
|
240793
240803
|
}
|
|
240794
|
-
const
|
|
240795
|
-
const profile = readProfile(deps.paths, profileName) ?? {};
|
|
240804
|
+
const profile = readProfile(deps.paths, chosen) ?? {};
|
|
240796
240805
|
const current = resolveCategoryState((name) => profile.categories?.[name]);
|
|
240797
240806
|
const selected = await deps.prompts.multiselect({
|
|
240798
|
-
message: `Which categories should configuration profile "${
|
|
240807
|
+
message: `Which categories should configuration profile "${chosen}" share?`,
|
|
240799
240808
|
options: OVERRIDABLE_CATEGORIES.map((name) => ({
|
|
240800
240809
|
value: name,
|
|
240801
240810
|
label: CATEGORY_LABELS[name],
|
|
@@ -240818,8 +240827,8 @@ async function runProfileDirectMode(context) {
|
|
|
240818
240827
|
deps.log.info("No changes.");
|
|
240819
240828
|
return;
|
|
240820
240829
|
}
|
|
240821
|
-
setProfileCategories(deps.paths,
|
|
240822
|
-
deps.log.info(`Updated configuration profile "${
|
|
240830
|
+
setProfileCategories(deps.paths, chosen, patch);
|
|
240831
|
+
deps.log.info(`Updated configuration profile "${chosen}".`);
|
|
240823
240832
|
}
|
|
240824
240833
|
async function runCategoriesMode(context) {
|
|
240825
240834
|
const { deps } = context;
|
|
@@ -240931,8 +240940,8 @@ function registerConfigureCommand(program2, paths) {
|
|
|
240931
240940
|
}
|
|
240932
240941
|
|
|
240933
240942
|
// src/claudeShim.ts
|
|
240934
|
-
var import_node_fs7 = __toESM(require("node:fs"));
|
|
240935
|
-
var import_node_path17 = __toESM(require("node:path"));
|
|
240943
|
+
var import_node_fs7 = __toESM(require("node:fs"), 1);
|
|
240944
|
+
var import_node_path17 = __toESM(require("node:path"), 1);
|
|
240936
240945
|
var ClaudeShimStateSchema = external_exports.strictObject({
|
|
240937
240946
|
targetPath: external_exports.string().min(1),
|
|
240938
240947
|
method: external_exports.enum(["hardlink", "copy"]),
|
|
@@ -241107,8 +241116,8 @@ function registerShimCommand(program2, paths) {
|
|
|
241107
241116
|
}
|
|
241108
241117
|
|
|
241109
241118
|
// src/doctor.ts
|
|
241110
|
-
var import_node_fs8 = __toESM(require("node:fs"));
|
|
241111
|
-
var import_node_path18 = __toESM(require("node:path"));
|
|
241119
|
+
var import_node_fs8 = __toESM(require("node:fs"), 1);
|
|
241120
|
+
var import_node_path18 = __toESM(require("node:path"), 1);
|
|
241112
241121
|
function validateJson(schema, input) {
|
|
241113
241122
|
if (input.raw === void 0) {
|
|
241114
241123
|
return { ok: false, message: `${input.path} is missing.` };
|
|
@@ -241501,7 +241510,7 @@ function buildCliOverride(params) {
|
|
|
241501
241510
|
}
|
|
241502
241511
|
|
|
241503
241512
|
// src/launcher/flags.ts
|
|
241504
|
-
var import_node_path19 = __toESM(require("node:path"));
|
|
241513
|
+
var import_node_path19 = __toESM(require("node:path"), 1);
|
|
241505
241514
|
function isEnvFlagSet(value) {
|
|
241506
241515
|
return value === "1";
|
|
241507
241516
|
}
|
|
@@ -241547,7 +241556,7 @@ function splitExtraFlags(value) {
|
|
|
241547
241556
|
}
|
|
241548
241557
|
|
|
241549
241558
|
// src/launcher/spawn.ts
|
|
241550
|
-
var import_node_os5 = __toESM(require("node:os"));
|
|
241559
|
+
var import_node_os5 = __toESM(require("node:os"), 1);
|
|
241551
241560
|
function exitCodeFor(result) {
|
|
241552
241561
|
if (result.status !== null) {
|
|
241553
241562
|
return result.status;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-use",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
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>",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"cli"
|
|
17
17
|
],
|
|
18
18
|
"packageManager": "pnpm@11.6.0",
|
|
19
|
-
"type": "
|
|
19
|
+
"type": "module",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22.12.0"
|
|
22
22
|
},
|
|
@@ -28,10 +28,18 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"prepare": "husky",
|
|
31
|
-
"typecheck": "
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
31
|
+
"typecheck": "turbo run _typecheck",
|
|
32
|
+
"_typecheck": "tsc --noEmit",
|
|
33
|
+
"lint": "turbo run _lint",
|
|
34
|
+
"_lint": "eslint . --cache --cache-location .eslintcache --max-warnings 0",
|
|
35
|
+
"test": "turbo run _test",
|
|
36
|
+
"_test": "vitest run",
|
|
37
|
+
"test:coverage": "turbo run _test:coverage",
|
|
38
|
+
"_test:coverage": "vitest run --coverage",
|
|
39
|
+
"knip": "turbo run _knip",
|
|
40
|
+
"_knip": "knip --cache",
|
|
41
|
+
"build": "turbo run _build",
|
|
42
|
+
"_build": "node scripts/build.mts",
|
|
35
43
|
"build:bundle": "node scripts/build.mts --bundle-only",
|
|
36
44
|
"prepublishOnly": "pnpm build:bundle",
|
|
37
45
|
"schema": "node scripts/gen-schema.mts"
|
|
@@ -48,19 +56,21 @@
|
|
|
48
56
|
"@commitlint/config-conventional": "^21.2.0",
|
|
49
57
|
"@eslint/js": "^10.0.1",
|
|
50
58
|
"@semantic-release/changelog": "^7.0.0",
|
|
51
|
-
"@semantic-release/exec": "^7.1.0",
|
|
52
59
|
"@semantic-release/git": "^11.0.1",
|
|
53
60
|
"@semantic-release/npm": "^13.1.5",
|
|
54
61
|
"@types/node": "^26.1.2",
|
|
55
62
|
"@types/picomatch": "^4.0.3",
|
|
63
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
56
64
|
"conventional-changelog-conventionalcommits": "^10.2.1",
|
|
57
65
|
"esbuild": "^0.28.1",
|
|
58
66
|
"eslint": "^10.8.0",
|
|
59
67
|
"globals": "^17.8.0",
|
|
60
68
|
"husky": "^9.1.7",
|
|
61
69
|
"jiti": "^2.7.0",
|
|
70
|
+
"knip": "^6.31.0",
|
|
62
71
|
"lint-staged": "^17.3.0",
|
|
63
72
|
"semantic-release": "^25.0.8",
|
|
73
|
+
"turbo": "^2.10.8",
|
|
64
74
|
"typescript": "^6.0.3",
|
|
65
75
|
"typescript-eslint": "^8.65.0",
|
|
66
76
|
"vitest": "^4.1.10"
|