agent-shim 7.13.1 → 7.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/cli.cjs +95 -27
  2. package/dist/index.cjs +70 -7
  3. package/dist/index.mjs +70 -7
  4. package/dist/types/index.d.ts +2789 -40
  5. package/package.json +3 -1
  6. package/dist/types/checkReport.d.ts +0 -154
  7. package/dist/types/claudeShim.d.ts +0 -114
  8. package/dist/types/cli/bool.d.ts +0 -8
  9. package/dist/types/cli/commandDeps.d.ts +0 -39
  10. package/dist/types/cli/envBool.d.ts +0 -11
  11. package/dist/types/cli/pairs.d.ts +0 -19
  12. package/dist/types/cliError.d.ts +0 -27
  13. package/dist/types/codex/http.d.ts +0 -15
  14. package/dist/types/config/classify.d.ts +0 -49
  15. package/dist/types/config/load.d.ts +0 -42
  16. package/dist/types/config/schema.d.ts +0 -746
  17. package/dist/types/config/store.d.ts +0 -46
  18. package/dist/types/configProfilesStore.d.ts +0 -61
  19. package/dist/types/configure.d.ts +0 -170
  20. package/dist/types/credential.d.ts +0 -136
  21. package/dist/types/credentialCache.d.ts +0 -46
  22. package/dist/types/directoryRulesStore.d.ts +0 -43
  23. package/dist/types/doctorReport.d.ts +0 -117
  24. package/dist/types/frontdoor/capability.d.ts +0 -19
  25. package/dist/types/frontdoor/capture.d.ts +0 -58
  26. package/dist/types/frontdoor/connect.d.ts +0 -209
  27. package/dist/types/frontdoor/connectEffects.d.ts +0 -3
  28. package/dist/types/frontdoor/custody.d.ts +0 -32
  29. package/dist/types/frontdoor/ensure.d.ts +0 -36
  30. package/dist/types/frontdoor/headroomHop.d.ts +0 -22
  31. package/dist/types/frontdoor/pipeline.d.ts +0 -109
  32. package/dist/types/frontdoor/probe.d.ts +0 -13
  33. package/dist/types/frontdoor/realFrontDoorPort.d.ts +0 -5
  34. package/dist/types/frontdoor/route.d.ts +0 -112
  35. package/dist/types/frontdoor/server.d.ts +0 -44
  36. package/dist/types/frontdoor/state.d.ts +0 -57
  37. package/dist/types/frontdoor/supervisor.d.ts +0 -48
  38. package/dist/types/frontdoor/tap.d.ts +0 -9
  39. package/dist/types/frontdoor/trust.d.ts +0 -30
  40. package/dist/types/frontdoor/x509.d.ts +0 -11
  41. package/dist/types/headroom/ensure.d.ts +0 -31
  42. package/dist/types/headroom/headers.d.ts +0 -11
  43. package/dist/types/headroom/settings.d.ts +0 -13
  44. package/dist/types/headroom/source.d.ts +0 -8
  45. package/dist/types/headroom/state.d.ts +0 -77
  46. package/dist/types/headroom/supervisor.d.ts +0 -107
  47. package/dist/types/identityStore.d.ts +0 -85
  48. package/dist/types/launchWiring.d.ts +0 -49
  49. package/dist/types/launcher/argv.d.ts +0 -42
  50. package/dist/types/launcher/cascade.d.ts +0 -42
  51. package/dist/types/launcher/cliOverride.d.ts +0 -38
  52. package/dist/types/launcher/extraFlags.d.ts +0 -6
  53. package/dist/types/launcher/farm.d.ts +0 -168
  54. package/dist/types/launcher/flags.d.ts +0 -96
  55. package/dist/types/launcher/guard.d.ts +0 -57
  56. package/dist/types/launcher/identity.d.ts +0 -95
  57. package/dist/types/launcher/lock.d.ts +0 -41
  58. package/dist/types/launcher/pool.d.ts +0 -44
  59. package/dist/types/launcher/ports.d.ts +0 -135
  60. package/dist/types/launcher/spawn.d.ts +0 -20
  61. package/dist/types/launcher.d.ts +0 -98
  62. package/dist/types/legacy.d.ts +0 -29
  63. package/dist/types/pathNorm.d.ts +0 -14
  64. package/dist/types/paths.d.ts +0 -79
  65. package/dist/types/poolPickReport.d.ts +0 -44
  66. package/dist/types/poolStore.d.ts +0 -18
  67. package/dist/types/providersStore.d.ts +0 -137
  68. package/dist/types/realCredentialCache.d.ts +0 -11
  69. package/dist/types/realPorts.d.ts +0 -109
  70. package/dist/types/resolve/conditions.d.ts +0 -37
  71. package/dist/types/resolve/decide.d.ts +0 -45
  72. package/dist/types/resolve/extends.d.ts +0 -41
  73. package/dist/types/resolve/flatten.d.ts +0 -13
  74. package/dist/types/resolve/match.d.ts +0 -58
  75. package/dist/types/resolve/pipeline.d.ts +0 -33
  76. package/dist/types/resolve/plan.d.ts +0 -57
  77. package/dist/types/resolve/projects.d.ts +0 -61
  78. package/dist/types/resolve/reconcile.d.ts +0 -80
  79. package/dist/types/resolve/types.d.ts +0 -159
  80. package/dist/types/resolve/walk.d.ts +0 -85
  81. package/dist/types/resolve/walkPolicy.d.ts +0 -15
  82. package/dist/types/usage/account.d.ts +0 -20
  83. package/dist/types/usage/middleware.d.ts +0 -17
  84. package/dist/types/usage/pick.d.ts +0 -62
  85. package/dist/types/usage/plan.d.ts +0 -18
  86. package/dist/types/usage/poolPick.d.ts +0 -31
  87. package/dist/types/usage/preflight.d.ts +0 -20
  88. package/dist/types/usage/rateLimit.d.ts +0 -25
  89. package/dist/types/usage/read.d.ts +0 -70
  90. package/dist/types/usage/scan.d.ts +0 -24
  91. package/dist/types/usage/schema.d.ts +0 -318
  92. package/dist/types/usage/store.d.ts +0 -43
  93. package/dist/types/versionDiscovery.d.ts +0 -47
@@ -1,95 +0,0 @@
1
- import { type LoadedFile } from "../config/load";
2
- import { type Identity } from "../config/schema";
3
- import type { FsPort } from "./ports";
4
- /** Which precedence rule produced an identity decision. */
5
- export type IdentityDecisionSource =
6
- /** `CLAUDE_CONFIG_DIR` was already set: identity resolution is skipped entirely and the real binary uses whatever it already points to. */
7
- "config-dir-escape-hatch"
8
- /** A leading `@name` argv[0] positional, or its explicit form `--identity <name>`. */
9
- | "argv"
10
- /** The `AGENT_SHIM_IDENTITY` environment variable. */
11
- | "env"
12
- /** A directory rule pinning an identity to the current path. */
13
- | "directory-pin"
14
- /** The persisted `~/.agent-shim/active-identity` file. */
15
- | "active-identity-file"
16
- /** Nothing resolved an identity at all — a bare launch with no active identity. */
17
- | "none";
18
- /** The pool a `pool:<name>` selector names, or undefined when `selector` names an identity directly. */
19
- export declare function poolNameOf(selector: string): string | undefined;
20
- /** The result of deciding which identity applies to this launch. */
21
- export interface IdentityDecision {
22
- /** The resolved identity name. Absent when the escape hatch applied, when nothing resolved one, or when a pool was selected (the launcher picks a member from `pool`). */
23
- readonly name?: string;
24
- /** The pool selected by a `pool:<name>` selector, when the launch named a pool instead of an identity. */
25
- readonly pool?: string;
26
- readonly source: IdentityDecisionSource;
27
- /** True when `CLAUDE_CONFIG_DIR` was already set and every step below was skipped as a result. */
28
- readonly configDirEscapeHatch: boolean;
29
- }
30
- /** Inputs to `decideIdentity`, in the exact precedence order the README's CLI reference table documents. */
31
- export interface DecideIdentityParams {
32
- readonly env: Readonly<Record<string, string | undefined>>;
33
- /** The identity `parseLauncherArgv` found in a leading `@name` or an `--identity <name>` flag, if either was given. */
34
- readonly argv0Identity?: string;
35
- /** An identity pinned to `$PWD` by a directory rule. Accepted as an already-resolved value here — the rules-loading code that produces it lands in Phase 4/5. */
36
- readonly directoryPinnedIdentity?: string;
37
- /** Reads the persisted active-identity file, returning undefined when it does not exist or is empty. Injected so this stays pure. */
38
- readonly readActiveIdentityFile: () => string | undefined;
39
- }
40
- /**
41
- * Decides which identity applies to this launch, in precedence order:
42
- *
43
- * 1. If `CLAUDE_CONFIG_DIR` is already set, skip identity/cascade resolution entirely: a deliberate carry-forward of the legacy script's own "don't override an explicit launcher" escape hatch.
44
- * 2. A leading `@name` argv[0] positional, or `--identity <name>`.
45
- * 3. The `AGENT_SHIM_IDENTITY` environment variable.
46
- * 4. A directory-pinned identity from a directory rule.
47
- * 5. The persisted `~/.agent-shim/active-identity` file.
48
- *
49
- * Each of steps 2 to 5 may name `pool:<name>` instead of an identity; the decision then carries `pool` and no `name`, and the launcher picks the member.
50
- *
51
- * An empty string counts as unset for `CLAUDE_CONFIG_DIR` and `AGENT_SHIM_IDENTITY`, consistent with how this project treats empty-string environment variables everywhere else (see `src/paths.ts` and the ambient-credential guard).
52
- */
53
- export declare function decideIdentity(params: DecideIdentityParams): IdentityDecision;
54
- /** Which precedence rule produced a configuration-profile decision. */
55
- export type ConfigProfileDecisionSource =
56
- /** An explicit `--config-profile` CLI flag. */
57
- "cli-flag"
58
- /** The `AGENT_SHIM_CONFIG_PROFILE` environment variable. */
59
- | "env"
60
- /** A directory rule's `configProfile` selection for `$PWD`. */
61
- | "directory-rule"
62
- /** The active identity's own declared `defaultConfigProfile`. */
63
- | "identity-default"
64
- /** The user-global `~/.agent-shim/config.json` default. */
65
- | "global-default"
66
- /** Nothing resolved a configuration profile at all. */
67
- | "none";
68
- /** The result of deciding which configuration profile applies to this launch. */
69
- export interface ConfigProfileDecision {
70
- readonly name?: string;
71
- readonly source: ConfigProfileDecisionSource;
72
- }
73
- /** Inputs to `decideConfigProfile`, in the exact precedence order the README's "Configuration profiles" section documents. */
74
- export interface DecideConfigProfileParams {
75
- readonly env: Readonly<Record<string, string | undefined>>;
76
- readonly cliFlagConfigProfile?: string;
77
- readonly directoryRuleConfigProfile?: string;
78
- readonly identityDefaultConfigProfile?: string;
79
- readonly globalDefaultConfigProfile?: string;
80
- }
81
- /**
82
- * Decides which configuration profile applies to this launch, in precedence order:
83
- *
84
- * 1. An explicit `--config-profile` flag or `AGENT_SHIM_CONFIG_PROFILE` environment variable (this run only).
85
- * 2. A directory rule's `configProfile` selection for `$PWD`.
86
- * 3. The active identity's own declared default (`defaultConfigProfile` in its `identity.json`).
87
- * 4. A global default (`~/.agent-shim/config.json`).
88
- */
89
- export declare function decideConfigProfile(params: DecideConfigProfileParams): ConfigProfileDecision;
90
- /**
91
- * Reads and validates one identity's `identity.json` from `<identitiesDir>/<name>/identity.json`, via `IdentitySchema`.
92
- *
93
- * Returns undefined when the file does not exist (e.g. the identity was resolved by name but never actually created — `agent-shim identity add` is Phase 4's job, not this one's). Throws `ConfigValidationError` when the file exists but fails validation, the same as any other config file in this project.
94
- */
95
- export declare function loadIdentity(identitiesDir: string, name: string, fs: FsPort): LoadedFile<Identity> | undefined;
@@ -1,41 +0,0 @@
1
- import { CliError } from "../cliError";
2
- import type { FarmFs } from "./ports";
3
- /** A held identity lock. Releasing is idempotent and never removes a lock some other process has since taken. */
4
- export interface IdentityLock {
5
- readonly path: string;
6
- readonly token: string;
7
- readonly release: () => void;
8
- }
9
- /** Raised when another process holds the identity's lock and did not release it within the retry budget. */
10
- export declare class IdentityLockBusyError extends CliError {
11
- readonly identity: string;
12
- readonly lockPath: string;
13
- readonly holderPid: number | undefined;
14
- constructor(identity: string, lockPath: string, holderPid: number | undefined);
15
- }
16
- /** Everything `acquireIdentityLock` needs, all of it injected so a test never sleeps for real, never reads a real pid table, and never writes outside its own fake filesystem. */
17
- export interface AcquireIdentityLockParams {
18
- readonly identity: string;
19
- /** The directory the lock file lives in — `~/.agent-shim/identities`, alongside (never inside) the farm the lock protects, so a farm swap can rename the farm root out from under itself without disturbing the lock. */
20
- readonly dir: string;
21
- readonly fs: FarmFs;
22
- readonly nowMs: () => number;
23
- readonly pid: number;
24
- /** Answers whether a process is still running, so a lock left behind by a crash is recognised rather than waited out for the full staleness window. Zombie-aware: a holder that exited without being reaped still answers signal 0 as alive, but will never release the lock itself. */
25
- readonly isRunning: (pid: number) => boolean;
26
- /** Blocks for the given number of milliseconds. Synchronous by necessity: the whole launcher is synchronous, right through to `spawnSync`. */
27
- readonly sleep: (ms: number) => void;
28
- readonly staleAfterMs?: number;
29
- readonly retryDelayMs?: number;
30
- readonly maxAttempts?: number;
31
- }
32
- /** The path of one identity's lock file. */
33
- export declare function identityLockPath(dir: string, identity: string): string;
34
- /**
35
- * Takes the per-identity resync lock, held for the whole of a resync and released before the real `claude` binary is spawned.
36
- *
37
- * Two terminals in two different client directories under one identity is exactly the pattern directory rules exist to support, and it is exactly the pattern that races: both launches resolve their own cascade and both want to rewrite the same identity's farm toward two different states. This serialises them.
38
- *
39
- * Mutual exclusion rests on one exclusive-create — a read-then-write pair would leave a window where both processes see no lock. A lock whose holder is no longer running, or whose record is older than the staleness window, or whose contents cannot be parsed at all, is removed and retaken: a machine that crashed mid-resync must not need manual cleanup before `claude` works again. A lock genuinely held by a live process is waited on, and only after the full retry budget does this give up — loudly, with the holder's pid, rather than proceeding unsynchronised.
40
- */
41
- export declare function acquireIdentityLock(params: AcquireIdentityLockParams): IdentityLock;
@@ -1,44 +0,0 @@
1
- import type { Pool } from "../config/schema";
2
- import type { LayoutPaths } from "../paths";
3
- import { type PoolPickFs } from "../usage/poolPick";
4
- import type { FarmFs, FsPort, LogPort } from "./ports";
5
- /** Everything one pool pick needs, injected so it runs against the same fakes as the rest of the launcher. */
6
- export interface ResolvePoolParams {
7
- readonly poolName: string;
8
- /** Which precedence rule selected the pool, for the refusal messages. */
9
- readonly selectedVia: string;
10
- readonly pools: Readonly<Record<string, Pool>> | undefined;
11
- readonly paths: LayoutPaths;
12
- readonly fs: FsPort;
13
- readonly usageFs: PoolPickFs & Pick<FarmFs, "writeFilePrivate" | "mkdirPrivate">;
14
- /** The launch directory: the key of the last-pick record. */
15
- readonly cwd: string;
16
- readonly now: () => number;
17
- readonly sleep: (ms: number) => void;
18
- /** The arguments claude itself will receive. */
19
- readonly passthrough: readonly string[];
20
- /** Sleep until the earliest member returns, instead of refusing, when every member is refused. */
21
- readonly wait: boolean;
22
- readonly log: LogPort;
23
- }
24
- /** The pick, or why none could be made. */
25
- export type PoolResolution = {
26
- readonly ok: true;
27
- readonly identity: string;
28
- /** The pool and the chosen member's leading reasons, for the launch's decision line. */
29
- readonly explanation: string;
30
- } | {
31
- readonly ok: false;
32
- readonly message: string;
33
- };
34
- /** A pool's members split by whether each is an identity that exists: a pool may outlive an identity it names, since removing an identity does not rewrite the pools that list it. */
35
- export declare function splitMembers(pool: Pool, paths: Pick<LayoutPaths, "identitiesDir">, fs: FsPort): {
36
- readonly present: readonly string[];
37
- readonly missing: readonly string[];
38
- };
39
- /**
40
- * Picks the identity a launch of pool `poolName` runs as.
41
- *
42
- * Members without an `identity.json` are skipped with a warning, since a pool may outlive an identity it names. When every remaining member is refused right now the launch is refused, naming the earliest return, unless `wait` is set, which sleeps until then and ranks again. The chosen member is recorded as this directory's last pick so the next launch here keeps its warm prompt cache.
43
- */
44
- export declare function resolvePoolLaunch(params: ResolvePoolParams): PoolResolution;
@@ -1,135 +0,0 @@
1
- import type { ConfigFileReader } from "../config/load";
2
- /**
3
- * Filesystem operations the launcher needs, injected so no test in this project ever touches a real filesystem — consistent with the resolver core's own injected-facts approach in `src/resolve/types.ts`.
4
- */
5
- export interface FsPort {
6
- /** Reads a file's contents as UTF-8 text, or returns undefined when it does not exist. Used for plain-text files like the persisted active-identity file. */
7
- readonly readFileUtf8: (filePath: string) => string | undefined;
8
- /** Reads and parses a config file (JSON/YAML/JS via cosmiconfig in the real implementation), returning undefined when it does not exist. Mirrors `src/config/load.ts`'s own injected reader shape so `loadIdentity` can compose directly with `loadConfigFile`. */
9
- readonly readConfigFile: ConfigFileReader;
10
- /** Lists one directory's immediate child names, or an empty list when it does not exist. Used to name the known providers in an unknown-provider refusal. */
11
- readonly readdir: (dir: string) => readonly string[];
12
- }
13
- /** What one node of a tree is, as reported by an `lstat` that never follows symlinks. */
14
- type FarmNodeKind = "dir" | "file" | "symlink";
15
- /** The subset of stat information the farm builder and the fact builder actually read. */
16
- export interface FarmStat {
17
- readonly kind: FarmNodeKind;
18
- readonly mtimeMs: number;
19
- readonly sizeBytes: number;
20
- }
21
- /**
22
- * Every filesystem operation the farm resync performs, injected so the whole of `src/launcher/farm.ts` — symlink creation, materialisation, adoption copies, and the atomic swap itself — runs against an in-memory fake in tests and never against a real `~/.claude` or `~/.agent-shim`.
23
- *
24
- * `lstat` never follows symlinks: a `~/.claude` that contains a directory symlink escaping the tree (a skills directory linked out to a separate dotfiles repository, say) must be recorded as one symlink entry rather than recursed into, both to keep the fact manifest finite and because the farm links such an entry at `<claudeHome>/<rel>` and lets the OS resolve the remaining hops.
25
- */
26
- export interface FarmFs {
27
- /** Stats one path without following symlinks, or returns undefined when nothing is there. */
28
- readonly lstat: (path: string) => FarmStat | undefined;
29
- /** Lists one directory's immediate child names, or an empty list when it does not exist. */
30
- readonly readdir: (path: string) => readonly string[];
31
- /** Creates a directory and any missing parents, succeeding silently when it already exists. */
32
- readonly mkdirp: (path: string) => void;
33
- /** Creates a symbolic link at `linkPath` pointing at `target`. */
34
- readonly symlink: (target: string, linkPath: string) => void;
35
- /** Renames `from` to `to`. Both are always within one directory tree on one filesystem in this module's own usage, so this is the atomic primitive the farm swap is built on. */
36
- readonly rename: (from: string, to: string) => void;
37
- /** Removes a path and everything beneath it, succeeding silently when it does not exist. */
38
- readonly removeRecursive: (path: string) => void;
39
- /** Copies a file, symlink, or whole directory tree from `from` to `to`. */
40
- readonly copyRecursive: (from: string, to: string) => void;
41
- /** Reads a file's contents as UTF-8 text, or undefined when it does not exist. */
42
- readonly readFileUtf8: (path: string) => string | undefined;
43
- /** Writes a file's full contents as UTF-8 text, creating or truncating it. */
44
- readonly writeFileUtf8: (path: string, contents: string) => void;
45
- /** Creates a directory (and any missing parents) readable only by its owner: mode 0700, enforced on the leaf even when it already existed with a wider mode. For a directory holding capabilities another local account must never read. */
46
- readonly mkdirPrivate: (path: string) => void;
47
- /** Writes a file's full contents as UTF-8 text readable only by its owner (mode 0600), atomically: a temporary sibling is written at that mode and renamed into place, so no reader ever sees a partial file or a wider mode. */
48
- readonly writeFilePrivate: (path: string, contents: string) => void;
49
- /** Appends UTF-8 text to a file readable only by its owner (mode 0600), creating it at that mode when it is missing and narrowing an existing wider mode. The file is opened in append mode, so the text always lands at the end however the file grew; a reader running at the same moment can still see a trailing partial write. It is the primitive an append-only log with one writer per file is built on. */
50
- readonly appendFilePrivate: (path: string, contents: string) => void;
51
- /** Creates a file only if it does not already exist, returning false when it does. This is the exclusive-create the identity lock depends on for its mutual exclusion — a read-then-write pair would race. */
52
- readonly writeFileExclusive: (path: string, contents: string) => boolean;
53
- /** A stable content hash of one file, or undefined when it is missing or is not a regular file. Used only to recognise farm data that has already been adopted into the canonical tree. */
54
- readonly hashFile: (path: string) => string | undefined;
55
- }
56
- /** The outcome of one `spawnSync` call, mirroring Node's own `SpawnSyncReturns` shape narrowly to what the launcher actually reads. */
57
- export interface SpawnResult {
58
- /** The child's exit code, or null when it was terminated by a signal instead of exiting normally. */
59
- readonly status: number | null;
60
- /** The signal that terminated the child, or null when it exited normally. */
61
- readonly signal: NodeJS.Signals | null;
62
- /** Set when the child process could not even be spawned (e.g. the binary does not exist). */
63
- readonly error?: Error;
64
- }
65
- /** Runs the real `claude` binary synchronously, injected so no test ever spawns a real process. The Node equivalent of the legacy bash tool's `exec` is `spawnSync` with `stdio: "inherit"` followed by propagating the child's own exit code — see `src/launcher/spawn.ts`. */
66
- export interface SpawnPort {
67
- readonly spawnSync: (command: string, args: readonly string[], options: {
68
- readonly stdio: "inherit";
69
- readonly env: Readonly<Record<string, string | undefined>>;
70
- }) => SpawnResult;
71
- }
72
- /** The outcome of one synchronous external-command run (distinct from `SpawnPort`, which is specifically for replacing this process with the real `claude` binary — `RunPort` is for auxiliary commands like `git rev-parse` for branch detection, or `security find-generic-password` for the macOS Keychain diagnostic in `check.ts`, whose output this process needs to keep running and read). */
73
- interface RunResult {
74
- readonly status: number | null;
75
- readonly stdout: string;
76
- readonly stderr: string;
77
- }
78
- /** Runs an external command and captures its output, injected so no test ever shells out for real. Not used by this phase's own modules yet — declared here so every later module (branch-conditioned `when` evaluation, `check.ts`'s Keychain lookup) shares one injectable shape rather than each inventing its own. */
79
- export interface RunPort {
80
- readonly run: (command: string, args: readonly string[]) => RunResult;
81
- }
82
- /**
83
- * The running process itself: its environment, its logical argv, and the ability to terminate with an exit code.
84
- *
85
- * `argv` is the *logical* CLI arguments only — whatever the user typed after `claude`/`agent-shim`, already stripped of the Node executable path and the script path (`process.argv.slice(2)` in the real implementation). `exit` mirrors `process.exit`'s own `never` return type: a real process never returns from it, and a test fake enforces the same shape by throwing a marker so control flow unwinds the same way in both cases.
86
- */
87
- export interface ProcPort {
88
- readonly env: Readonly<Record<string, string | undefined>>;
89
- readonly argv: readonly string[];
90
- readonly exit: (code: number) => never;
91
- }
92
- /** Where the launcher reports what it decided and why — refusals, warnings about an identity that could not be loaded, and (per the "strictly-better than the legacy script" goal) which identity/profile ended up selected. */
93
- export interface LogPort {
94
- readonly info: (message: string) => void;
95
- readonly warn: (message: string) => void;
96
- readonly error: (message: string) => void;
97
- }
98
- /** The running headroom daemon one launch routed through, as `HeadroomPort.ensure` reports it. The front door owns the session's routing; this names the daemon the door's headroom hop forwards to, and the project identity headroom scopes memory to. */
99
- export interface HeadroomUp {
100
- /** The loopback port the headroom daemon listens on: what the door's headroom hop reads live and `HEADROOM_PROXY_URL` names for anything else that wants the daemon. */
101
- readonly port: number;
102
- /** The project identity headroom scopes memory state to: the git repo root of the launch directory, or the directory itself outside a repository. */
103
- readonly projectId: string;
104
- }
105
- /**
106
- * Everything the launcher needs from the headroom daemon, injected so its daemon-supervision side effects stay behind a port and the launcher's own logic stays pure over it (`src/realPorts.ts` wires the real one; tests wire fakes).
107
- *
108
- * `ensure` brings the daemon up (spawning the detached supervisor when nothing healthy is running) and registers this launch in its session registry; `release` removes that registration when the spawned `claude` exits.
109
- */
110
- export interface HeadroomPort {
111
- readonly ensure: () => HeadroomUp;
112
- readonly release: () => void;
113
- }
114
- /**
115
- * Everything the launcher needs from the front-door daemon, injected like `HeadroomPort`: `ensure` brings the door up (spawning its detached supervisor when nothing authenticated is serving), registers this launch in its session registry and returns both listeners' loopback ports plus the CA bundle the child must trust; `release` removes that registration when the spawned `claude` exits.
116
- */
117
- export interface FrontDoorPort {
118
- /** `inheritedExtraCaCerts` is the parent environment's own `NODE_EXTRA_CA_CERTS`, which the returned trust bundle keeps (see `resolveTrustBundle`). */
119
- readonly ensure: (inheritedExtraCaCerts: string | undefined) => FrontDoorUp;
120
- readonly release: () => void;
121
- }
122
- /** The running front door one launch routed through, as `FrontDoorPort.ensure` reports it. */
123
- export interface FrontDoorUp {
124
- /** The loopback port the provider listener serves HTTPS on: the base URL every provider session points at. */
125
- readonly port: number;
126
- /** The loopback port the CONNECT surface listens on: what an OAuth launch points HTTPS_PROXY at. */
127
- readonly connectPort: number;
128
- /** The CA bundle every routed child points NODE_EXTRA_CA_CERTS at: agent-shim's CA (which signs both the provider listener's leaf and the CONNECT surface's), plus whatever the parent environment already trusted that way. */
129
- readonly trustBundlePath: string;
130
- /** Why the trust bundle dropped something the parent environment named, when it did; the launcher logs it. */
131
- readonly trustWarning?: string;
132
- /** This launch's capability token, injected alongside the session headers and required by the door before it routes anything. */
133
- readonly sessionToken: string;
134
- }
135
- export {};
@@ -1,20 +0,0 @@
1
- import type { ProcPort, SpawnPort } from "./ports";
2
- /** Inputs to `spawnClaude`. */
3
- export interface SpawnClaudeParams {
4
- /** Full path to the real `claude` binary to run, as discovered by `src/versionDiscovery.ts`. */
5
- readonly bin: string;
6
- readonly args: readonly string[];
7
- readonly env: Readonly<Record<string, string | undefined>>;
8
- readonly spawn: SpawnPort;
9
- readonly proc: ProcPort;
10
- /**
11
- * Runs immediately before the child's exit code propagates. The real `process.exit` never unwinds a `finally` block, so a caller with cleanup to do on the success path (releasing a headroom session registration, say) cannot rely on one; it passes the cleanup here instead.
12
- */
13
- readonly beforeExit?: () => void;
14
- }
15
- /**
16
- * Runs the real `claude` binary and propagates its exit code faithfully, the Node equivalent of the legacy bash tool's `exec` (process replacement): `spawnSync(bin, args, { stdio: "inherit" })` followed by `process.exit(status)`.
17
- *
18
- * Throws when the child could not even be spawned (e.g. `bin` does not exist) rather than silently exiting — a spawn failure is a real problem, not a clean exit code to propagate.
19
- */
20
- export declare function spawnClaude(params: SpawnClaudeParams): never;
@@ -1,98 +0,0 @@
1
- import type { LayoutPaths } from "./paths";
2
- import { type CliOverride } from "./launcher/cliOverride";
3
- import type { FarmFs, FsPort, FrontDoorPort, HeadroomPort, LogPort, ProcPort, SpawnPort } from "./launcher/ports";
4
- import { type CategoryClassification, type CategoryClassificationOverlay, type Pool } from "./config/schema";
5
- import { type CredentialPort } from "./credential";
6
- import type { CascadeInput } from "./resolve/walk";
7
- import type { DiscoveredClaudeBinary } from "./versionDiscovery";
8
- /**
9
- * Everything the farm resync needs that the launcher itself has no way to produce: a real filesystem, a real clock, the working directory, and a way to load the cascade for it.
10
- *
11
- * Supplied by `src/cli.ts` in normal operation. A caller that omits it launches with no farm at all, which is the right behaviour in exactly the cases where there is no agent-shim-managed farm to resync — and is what the launcher's own pre-farm tests exercise.
12
- */
13
- export interface FarmRuntime {
14
- readonly fs: FarmFs;
15
- /** The canonical `~/.claude` every farm symlink points back into. */
16
- readonly claudeHome: string;
17
- readonly home: string;
18
- readonly cwd: string;
19
- /** The git branch at `cwd`, for `when: { branch }` conditions. Undefined when `cwd` is not in a repository. */
20
- readonly branch?: string;
21
- readonly branchDetached?: boolean;
22
- readonly classification: {
23
- readonly defaults: CategoryClassification;
24
- readonly overlay?: CategoryClassificationOverlay;
25
- };
26
- /** Loads and assembles the cascade for `cwd` under the given configuration profile and one-off command-line/environment overrides. Injected so the launcher never reads a config file itself. */
27
- readonly loadCascade: (baseConfigProfile: string | undefined, cliOverride: CliOverride | undefined) => CascadeInput;
28
- readonly now: () => number;
29
- /** Distinguishes this process's scratch and superseded farm directories from any other's. */
30
- readonly uniqueSuffix: string;
31
- readonly lock: {
32
- readonly pid: number;
33
- readonly isRunning: (pid: number) => boolean;
34
- readonly sleep: (ms: number) => void;
35
- readonly staleAfterMs?: number;
36
- readonly retryDelayMs?: number;
37
- readonly maxAttempts?: number;
38
- };
39
- }
40
- /** Inputs to `prepareLaunch`: everything a launch needs except the means of spawning the child. */
41
- export interface PrepareLaunchParams {
42
- readonly paths: LayoutPaths;
43
- readonly fs: FsPort;
44
- readonly proc: ProcPort;
45
- readonly log: LogPort;
46
- /** Discovers the real `claude` binary to spawn. Injected so `runLauncher` never depends on `src/versionDiscovery.ts`'s own filesystem/PATH inputs directly — the caller (`src/cli.ts`) wires the real discovery, tests wire a fake that returns a fixed path. */
47
- readonly resolveClaudeBinary: () => DiscoveredClaudeBinary;
48
- /** An identity pinned to `$PWD` by a directory rule. Accepted as an already-resolved value — the rules-loading code that produces it lands in Phase 4/5. */
49
- readonly directoryPinnedIdentity?: string;
50
- /** A directory rule's `configProfile` selection for `$PWD`. Accepted as an already-resolved value for the same reason. */
51
- readonly directoryRuleConfigProfile?: string;
52
- /** An explicit `--config-profile` value, when the caller wants to force one regardless of argv — normal operation instead relies on `parseLauncherArgv` finding `--config-profile` in `proc.argv` itself, so this is only needed to override that. */
53
- readonly cliFlagConfigProfile?: string;
54
- /** The user-global `~/.agent-shim/config.json` default configuration profile, when one is configured. */
55
- readonly globalDefaultConfigProfile?: string;
56
- /** The user-global `pools`, which a `pool:<name>` selector picks a member of. */
57
- readonly pools?: Readonly<Record<string, Pool>>;
58
- /** Wires the farm resync. Omitted only by a caller that has no farm to manage. */
59
- readonly farm?: FarmRuntime;
60
- /** Wires headroom routing. Omitted by a caller that cannot route through the daemon; a launch that resolves `headroom` on with no port wired is refused loudly rather than silently bypassing it. */
61
- readonly headroom?: HeadroomPort;
62
- /** Wires the front-door daemon. Omitted by a caller that cannot run it; a launch that routes anything (a provider, or headroom) with no port wired is refused rather than started against an address nothing serves. */
63
- readonly frontdoor?: FrontDoorPort;
64
- /** Resolves credential blocks (a provider's, or the launching identity's own). Omitted by a caller that cannot read secret files or run commands; a launch that needs a credential is then refused rather than started without it. */
65
- readonly credentials?: CredentialPort;
66
- /** True when the user was asked on a terminal whether to create the selected configuration profile and chose to launch without it. Without that explicit choice, a selected profile with no file is refused rather than silently skipped. */
67
- readonly allowMissingConfigProfile?: boolean;
68
- }
69
- /** Inputs to `runLauncher`: a launch's inputs plus the port that spawns the child. */
70
- export interface RunLauncherParams extends PrepareLaunchParams {
71
- readonly spawn: SpawnPort;
72
- }
73
- /**
74
- * What one launch resolved to, once every effect its child depends on is in place (the identity's farm resynced, the front door and headroom up and this launch registered with them): the real binary, its arguments and its environment.
75
- */
76
- export interface LaunchPlan {
77
- readonly bin: string;
78
- readonly args: readonly string[];
79
- readonly env: Readonly<Record<string, string | undefined>>;
80
- /** Releases the daemon session registrations this launch holds. Call it when the child has exited; it is safe to call more than once. */
81
- readonly release: () => void;
82
- }
83
- /**
84
- * Prepares one `claude` launch, in order:
85
- *
86
- * `CLAUDE_CONFIG_DIR` escape-hatch check, then the identity/config-profile decision, the provider decision, the identity's own credential, the ambient-credential guard, farm resync, version discovery, flag resolution (and, when headroom resolved on, daemon bring-up), the extra-flags split, and the final argument list and environment, returned as a `LaunchPlan` for the caller to spawn. It refuses through `params.proc.exit`, so a caller that is not a command line process passes a `proc` whose `exit` throws.
87
- *
88
- * A launch that resolves an identity with no `identity.json`, or a configuration profile with no file, is refused with exit 1 naming the missing name and how it was selected: silently proceeding would create a brand-new login for a mistyped `@name`, or launch with a whole cascade layer missing. On a terminal, `src/runClaude.ts` offers to create either before this runs.
89
- *
90
- * The farm resync is skipped when `CLAUDE_CONFIG_DIR` was already set (the escape hatch means the user has named a configuration directory explicitly, and agent-shim manages neither its contents nor its lifetime) and when no identity resolved at all (a bare launch against plain `~/.claude`, matching the legacy tool's own behaviour). In both cases there is no agent-shim-managed farm for a resync to act on.
91
- */
92
- export declare function prepareLaunch(params: PrepareLaunchParams): LaunchPlan;
93
- /**
94
- * Runs one `claude` launch: `prepareLaunch`, then the child under `params.spawn`, whose exit code becomes this process's.
95
- *
96
- * The session registrations are released twice by design: `beforeExit` covers the real success path (where `process.exit` never unwinds a `finally`), and the `finally` below covers a thrown spawn error, where it does.
97
- */
98
- export declare function runLauncher(params: RunLauncherParams): void;
@@ -1,29 +0,0 @@
1
- import type { IncomingHttpHeaders } from "node:http";
2
- /**
3
- * Everything agent-shim still honours from its former name, `claude-use`, so an installation, a committed project config or an exported environment written for the old name keeps working. Nothing here is consulted when the current name's equivalent is present: a current name always wins, and the legacy form is only a fallback that `agent-shim doctor` reports so it can be retired.
4
- */
5
- /** Prefix of every environment variable agent-shim reads. */
6
- export declare const ENV_PREFIX = "AGENT_SHIM_";
7
- /** The former prefix, still accepted wherever `ENV_PREFIX` is. */
8
- export declare const LEGACY_ENV_PREFIX = "CLAUDE_USE_";
9
- /** The state root directory name under the home directory. */
10
- export declare const HOME_DIRNAME = ".agent-shim";
11
- /**
12
- * The former state root directory name. It is adopted in place rather than moved: macOS Claude Code keys each identity's Keychain login on the exact `CLAUDE_CONFIG_DIR` string (`<root>/identities/<name>`), so relocating the root would sign every identity out.
13
- */
14
- export declare const LEGACY_HOME_DIRNAME = ".claude-use";
15
- /** Former committed and per-clone portable config file names. */
16
- export declare const LEGACY_PORTABLE_CONFIG_FILENAME = ".claude-use.json";
17
- export declare const LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME = ".claude-use.local.json";
18
- /** The former farm manifest file name, read when a farm built by an older release has no current one. */
19
- export declare const LEGACY_FARM_MANIFEST_FILENAME = ".claude-use-farm.json";
20
- /** The former executable name, still installed beside the current one and still dispatched to the same program. */
21
- export declare const LEGACY_COMMAND_NAME = "claude-use";
22
- /**
23
- * Copies every `CLAUDE_USE_*` variable in `env` to its `AGENT_SHIM_*` name unless that name is already set, mutating `env`. Returns the legacy names that supplied a value, in sorted order. An empty current value counts as set, so exporting `AGENT_SHIM_X=` still overrides a stale `CLAUDE_USE_X`.
24
- */
25
- export declare function aliasLegacyEnv(env: NodeJS.ProcessEnv): string[];
26
- /**
27
- * Returns `headers` with every `x-claude-use-*` header renamed to its `x-agent-shim-*` name, and no legacy name left in the result, so everything downstream (admission, the identity strip that keeps internal headers from reaching an upstream) sees only the current names. A current-name header that is also present wins and the legacy copy is dropped. Header names arrive lowercased from Node's HTTP parser.
28
- */
29
- export declare function aliasLegacyWireHeaders(headers: Readonly<IncomingHttpHeaders>): IncomingHttpHeaders;
@@ -1,14 +0,0 @@
1
- /**
2
- * Expands a leading `~` against `home`.
3
- *
4
- * Only a leading `~` alone or followed by a separator is expanded — `~other` is a literal name (a directory really called that), not another user's home directory, which this tool has no business resolving.
5
- */
6
- export declare function expandTilde(value: string, home: string): string;
7
- /**
8
- * Normalises a directory rule's `path` into an absolute filesystem path for comparison against a real directory.
9
- *
10
- * Rules are written with `~`-rooted paths far more often than absolute ones, and a rule that fails to match because of a trailing slash or a `..` segment fails silently — the launch just quietly ignores the rule. Normalising both sides identically is what stops that.
11
- */
12
- export declare function normaliseRulePath(rulePath: string, home: string): string;
13
- /** True when `ancestor` is `descendant` itself or one of its parent directories. Both are expected to be already normalised. */
14
- export declare function isAncestorOrSelf(ancestor: string, descendant: string): boolean;
@@ -1,79 +0,0 @@
1
- /**
2
- * The resolved set of paths agent-shim reads and writes under its own root.
3
- *
4
- * `root` is always AGENT_SHIM_HOME when that environment variable is set (used by every test in this project, and by any real installation that wants to relocate its state), falling back to `~/.agent-shim` (or the former `~/.claude-use`, see `resolveAgentShimHome`) only when the variable is unset. Every other field is derived from `root` so there is exactly one place a path can go wrong.
5
- */
6
- export interface LayoutPaths {
7
- /** The resolved root directory, see `resolveAgentShimHome`. */
8
- readonly root: string;
9
- /** Directory holding one subdirectory per identity (symlink farm + local credentials/daemon state). */
10
- readonly identitiesDir: string;
11
- /** Directory holding named, reusable configuration profile JSON files. */
12
- readonly configProfilesDir: string;
13
- /** Directory holding one `<name>.json` provider definition per API provider. */
14
- readonly providersDir: string;
15
- /** Path to the directory-rules.json file describing directory-scoped identity/profile pins. */
16
- readonly directoryRulesFile: string;
17
- /** Path to the persisted active-identity file (the identity `agent-shim identity use` selected). */
18
- readonly activeIdentityFile: string;
19
- /** Path to the global config.json (user-global override layer, and default profile/walk-limit settings). */
20
- readonly globalConfigFile: string;
21
- /** Path to the categories.local.json overlay recording answers to "unclassified entry" prompts. */
22
- readonly categoriesLocalFile: string;
23
- /** Path to the claude-shim.json marker recording where `agent-shim shim enable` last placed a `claude`-named copy of this executable, and how. */
24
- readonly claudeShimFile: string;
25
- /** Directory holding the headroom daemon's coordination state: state.json, the start lock, and the session registry. */
26
- readonly headroomDir: string;
27
- /** Path to the headroom supervisor's state.json: pids, port, version, allowlist hash, last error. */
28
- readonly headroomStateFile: string;
29
- /** Path to the exclusive-create marker guarding "who spawns the supervisor" so concurrent launches start at most one. */
30
- readonly headroomLockFile: string;
31
- /** Directory holding one `<launcher-pid>.json` session-registry entry per live launch routed through headroom. */
32
- readonly headroomSessionsDir: string;
33
- /** Directory holding daemon logs (the headroom proxy's stdout/stderr and the supervisor's own output). */
34
- readonly logsDir: string;
35
- /** Path to the headroom daemon's combined log, appended to by both the supervisor and the proxy it owns. */
36
- readonly headroomLogPath: string;
37
- /** Directory holding the front-door daemon's coordination state: state.json, the start lock, and the session registry. */
38
- readonly frontdoorDir: string;
39
- /** Directory holding the front door's certificate authority, which signs both the provider listener's loopback leaf and the CONNECT surface's intercept leaf: ca.pem (public, handed to children through NODE_EXTRA_CA_CERTS) and ca.key (mode 0600). */
40
- readonly frontdoorCaDir: string;
41
- /** Path to the front door's CA certificate, generated once and stable across restarts so children keep trusting it. */
42
- readonly frontdoorCaCertFile: string;
43
- /** Path to the front door's CA private key, mode 0600: whoever can read it can impersonate the door to every routed child. */
44
- readonly frontdoorCaKeyFile: string;
45
- /** Directory of combined CA bundles: a parent environment's own NODE_EXTRA_CA_CERTS file plus the front door's CA, one file per distinct combination, for children that must keep trusting both. */
46
- readonly frontdoorCaBundlesDir: string;
47
- /** Path to the front door's state.json: its supervisor pid, its port, and the sticky port it last served on. */
48
- readonly frontdoorStateFile: string;
49
- /** Path to the exclusive-create marker guarding "who spawns the front-door supervisor" so concurrent launches start at most one. */
50
- readonly frontdoorLockFile: string;
51
- /** Directory holding one `<launcher-pid>.json` session-registry entry per live launch routed through the front door. */
52
- readonly frontdoorSessionsDir: string;
53
- /** Path to the front door's log: one line per lifecycle event and routed failure, never a token. */
54
- readonly frontdoorLogPath: string;
55
- /** Directory holding the usage store, mode 0700: the request log and the per-identity snapshots. Metadata only, never content or credentials. */
56
- readonly usageDir: string;
57
- /** Directory of the append-only usage log: one `<YYYY-MM-DD>.<writer-pid>.jsonl` segment per writing process per UTC day. */
58
- readonly usageLogDir: string;
59
- /** Directory of the latest-state snapshots, one `<identity>.json` per identity: the stable file other tools read. */
60
- readonly usageSnapshotsDir: string;
61
- /** The last identity picked from a pool for each directory, so a conversation stays on the account whose prompt cache is warm. */
62
- readonly usagePicksFile: string;
63
- }
64
- /**
65
- * Resolves the state root: `AGENT_SHIM_HOME` when set to a non-empty string (an empty string counts as unset, consistent with how this project treats empty-string environment variables elsewhere, see the ambient-credential guard), otherwise `~/.agent-shim`, or the former `~/.claude-use` when only that exists.
66
- *
67
- * The former root is used where it stands, never moved: macOS Claude Code names each identity's Keychain entry after a hash of its exact `CLAUDE_CONFIG_DIR`, so any relocation would sign every identity out. A fresh installation, or one that already has `~/.agent-shim`, uses the current name.
68
- */
69
- export declare function resolveAgentShimHome(env?: NodeJS.ProcessEnv, home?: string, isDirectory?: (candidate: string) => boolean): string;
70
- /**
71
- * Resolves the canonical `~/.claude` directory every farm symlink points back into: `AGENT_SHIM_CLAUDE_HOME` when set to a non-empty string, otherwise `~/.claude`.
72
- *
73
- * The override exists for the same reason `AGENT_SHIM_HOME` does. Exercising a real resync end to end means building and swapping real directories, and doing that against a real, in-use `~/.claude` to find out whether the code is correct is not an acceptable way to find out. Pointing both variables at throwaway directories makes a full end-to-end run safe.
74
- */
75
- export declare function resolveClaudeHome(): string;
76
- /** Builds the full LayoutPaths structure from a given root directory. */
77
- export declare function buildLayoutPaths(root: string): LayoutPaths;
78
- /** Resolves AGENT_SHIM_HOME and builds the full LayoutPaths structure in one call. */
79
- export declare function resolveLayoutPaths(): LayoutPaths;
@@ -1,44 +0,0 @@
1
- import type { Pool } from "./config/schema";
2
- import type { FarmFs, FsPort } from "./launcher/ports";
3
- import type { LayoutPaths } from "./paths";
4
- import type { Candidate } from "./usage/pick";
5
- /** What `pool pick` reports: the ranking a launch from `directory` would act on right now. */
6
- export interface PoolPickReport {
7
- readonly pool: string;
8
- readonly directory: string;
9
- /** The member a launch would run as, absent when every member is refused. */
10
- readonly pick?: string;
11
- readonly candidates: readonly PoolPickCandidateView[];
12
- /** Members that name an identity that does not exist; a launch skips them. */
13
- readonly missing: readonly string[];
14
- /** When nothing can be picked, the soonest any member returns. */
15
- readonly earliestReturn?: {
16
- readonly identity: string;
17
- readonly at: string;
18
- };
19
- /** Why the last-pick record could not be read, when that is the case. */
20
- readonly stickyProblem?: string;
21
- }
22
- /** One ranked member as `pool pick --json` prints it. */
23
- interface PoolPickCandidateView {
24
- readonly identity: string;
25
- readonly class: Candidate["class"];
26
- readonly score?: number;
27
- readonly feasible: boolean;
28
- readonly blockedUntil?: string;
29
- readonly plan: Candidate["plan"];
30
- readonly reasons: readonly string[];
31
- }
32
- /**
33
- * Ranks a pool exactly as a launch from `directory` would right now, without recording a pick. Shares `rankPoolFromStore` with the launcher, so what this prints is what a launch does.
34
- */
35
- export declare function collectPoolPick(params: Readonly<{
36
- paths: LayoutPaths;
37
- fs: FsPort;
38
- usageFs: FarmFs;
39
- poolName: string;
40
- pool: Pool;
41
- directory: string;
42
- nowMs: number;
43
- }>): PoolPickReport;
44
- export {};