agent-shim 7.12.0 → 7.13.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.
- package/dist/cli.cjs +445 -424
- package/dist/index.cjs +1471 -140
- package/dist/index.mjs +1468 -140
- package/dist/types/cli/pairs.d.ts +19 -0
- package/dist/types/frontdoor/realFrontDoorPort.d.ts +5 -0
- package/dist/types/frontdoor/trust.d.ts +30 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/launchWiring.d.ts +49 -0
- package/dist/types/launcher/argv.d.ts +42 -0
- package/dist/types/launcher/cliOverride.d.ts +38 -0
- package/dist/types/launcher/extraFlags.d.ts +6 -0
- package/dist/types/launcher/spawn.d.ts +20 -0
- package/dist/types/launcher.d.ts +98 -0
- package/dist/types/realCredentialCache.d.ts +11 -0
- package/dist/types/realPorts.d.ts +8 -1
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** One `<key>=<value>` pair, as parsed by `parsePair`. */
|
|
2
|
+
export interface ParsedPair {
|
|
3
|
+
readonly key: string;
|
|
4
|
+
readonly value: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Splits `input` on its *first* `=` into a key and a value. The value may itself contain further `=` characters (they stay part of the value); only the first `=` is treated as the separator.
|
|
8
|
+
*
|
|
9
|
+
* Throws when there is no `=` at all, or when the key half is empty (`=true`, or a leading `=`).
|
|
10
|
+
*/
|
|
11
|
+
export declare function parsePair(input: string): ParsedPair;
|
|
12
|
+
/** Parses `input` as a boolean in the shared vocabulary (`true`/`1`, `false`/`0`); throws on anything else. */
|
|
13
|
+
export declare function parseBool(input: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Parses a comma-separated list of `<key>=<bool>` pairs into a plain object, e.g. `"history=true,knowledge=false"` becomes `{ history: true, knowledge: false }`. Used only for the list-valued environment variables; flags repeat instead.
|
|
16
|
+
*
|
|
17
|
+
* An empty string parses to `{}`. A key repeated within the same list is not an error: the later occurrence wins, matching how a plain object literal with a repeated key behaves.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseBoolPairList(input: string): Record<string, boolean>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FrontDoorPort } from "../launcher/ports";
|
|
2
|
+
import type { LayoutPaths } from "../paths";
|
|
3
|
+
import { type DaemonSpawner } from "../realPorts";
|
|
4
|
+
/** The real `FrontDoorPort` for one launch: `ensure` runs the lock-and-poll coordination, authenticates the listener and registers this launcher; `release` removes its registration. */
|
|
5
|
+
export declare function realFrontDoorPort(paths: LayoutPaths, spawnDaemon: DaemonSpawner): FrontDoorPort;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The file effects resolving a trust bundle needs, injected so the decision runs against fakes. */
|
|
2
|
+
export interface TrustBundleFs {
|
|
3
|
+
/** Reads a file as UTF-8 text; throws when it cannot be read. */
|
|
4
|
+
readonly readFileUtf8: (file: string) => string;
|
|
5
|
+
/** Whether a file exists. */
|
|
6
|
+
readonly exists: (file: string) => boolean;
|
|
7
|
+
/** Creates a directory and any missing parents. */
|
|
8
|
+
readonly mkdirp: (dir: string) => void;
|
|
9
|
+
/** Writes a file atomically (a reader never sees a partial bundle). */
|
|
10
|
+
readonly writeFileAtomic: (file: string, contents: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/** The CA bundle a routed child is pointed at through `NODE_EXTRA_CA_CERTS`, and why it differs from what the parent environment set, when it does. */
|
|
13
|
+
export interface TrustBundle {
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly warning?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Decides the `NODE_EXTRA_CA_CERTS` a routed child gets. Node reads exactly one file from that variable, so a user who already set it (a corporate proxy's CA, say) would lose that trust if the launcher simply pointed it at agent-shim's CA. Instead:
|
|
19
|
+
*
|
|
20
|
+
* - nothing inherited: agent-shim's CA file itself;
|
|
21
|
+
* - an inherited file that already contains agent-shim's CA (a launch from inside a routed session): that file, unchanged;
|
|
22
|
+
* - any other readable inherited file: a combined bundle (the inherited certificates, then agent-shim's CA) written once under `bundlesDir`, named by its content hash so concurrent launches agree on it and a live child's file is never rewritten under it;
|
|
23
|
+
* - an inherited file that cannot be read: agent-shim's CA alone, with a warning naming the file. Node itself would have ignored that file with a warning, so the child loses no trust it would otherwise have had.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveTrustBundle(params: {
|
|
26
|
+
readonly caCertFile: string;
|
|
27
|
+
readonly bundlesDir: string;
|
|
28
|
+
readonly inherited: string | undefined;
|
|
29
|
+
readonly fs: TrustBundleFs;
|
|
30
|
+
}): TrustBundle;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* - `check` and `doctor` as data: `collectCheckReport` and `collectDoctorReport` read this machine and return the report, and the pure `runCheck` and `runDoctor` take the facts as parameters.
|
|
13
13
|
*
|
|
14
|
+
* - launching: `prepareClaudeLaunch` resolves a launch for a directory on this machine and returns the binary, arguments and environment to spawn (performing the farm resync and daemon registration the child depends on), and `prepareLaunch` does the same over injected ports.
|
|
15
|
+
*
|
|
14
16
|
* Everything the CLI alone needs (commander wiring and prompts) is deliberately not exported here.
|
|
15
17
|
*/
|
|
16
18
|
export { resolveDecisions, topLevelNames, type ResolveDecisionsInput, type ResolvedState } from "./resolve/pipeline";
|
|
@@ -35,3 +37,5 @@ export { addPool, PoolNotFoundError, readPools, removePool, requirePool, setPool
|
|
|
35
37
|
export { addDirectoryRule, DirectoryRuleAlreadyExistsError, DirectoryRuleMissingTargetError, DirectoryRuleNotFoundError, listDirectoryRules, readDirectoryRules, removeDirectoryRule, updateDirectoryRule, writeDirectoryRules, type AddDirectoryRuleOptions, type UpdateDirectoryRuleOptions, } from "./directoryRulesStore";
|
|
36
38
|
export { checkReportHasWarnings, checkReportToJson, collectCheckReport, formatCheckReport, runCheck, type CheckReport, type CollectCheckReportParams, type RunCheckParams, } from "./checkReport";
|
|
37
39
|
export { collectDoctorReport, formatDoctorReport, runDoctor, type CollectDoctorReportParams, type DoctorReport, type RunDoctorParams, } from "./doctorReport";
|
|
40
|
+
export { prepareLaunch, type LaunchPlan, type PrepareLaunchParams } from "./launcher";
|
|
41
|
+
export { LaunchRefusedError, prepareClaudeLaunch, type PrepareClaudeLaunchOptions } from "./launchWiring";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CliError } from "./cliError";
|
|
2
|
+
import type { Pool } from "./config/schema";
|
|
3
|
+
import { type FarmRuntime, type LaunchPlan, type PrepareLaunchParams } from "./launcher";
|
|
4
|
+
import type { LogPort, ProcPort } from "./launcher/ports";
|
|
5
|
+
import { type LayoutPaths } from "./paths";
|
|
6
|
+
import { type DaemonSpawner } from "./realPorts";
|
|
7
|
+
/** Builds the farm runtime the launcher's resync step needs for a launch in `cwd`, wired to real filesystem, clock, git, and process facilities, plus the directory-scoped selections the launcher needs before it can resync anything. */
|
|
8
|
+
export declare function buildFarmRuntime(paths: LayoutPaths, cwd: string): {
|
|
9
|
+
runtime: FarmRuntime;
|
|
10
|
+
directoryIdentity?: string;
|
|
11
|
+
directoryConfigProfile?: string;
|
|
12
|
+
globalDefaultConfigProfile?: string;
|
|
13
|
+
pools?: Readonly<Record<string, Pool>>;
|
|
14
|
+
};
|
|
15
|
+
/** Everything `realPrepareLaunchParams` takes beyond the state root: the process facts to launch against, the farm runtime for the directory, and whether a missing configuration profile was knowingly chosen. */
|
|
16
|
+
export interface RealLaunchOptions {
|
|
17
|
+
readonly proc: ProcPort;
|
|
18
|
+
readonly farm: ReturnType<typeof buildFarmRuntime>;
|
|
19
|
+
readonly log: LogPort;
|
|
20
|
+
/** Starts the front door and headroom daemons when a launch needs them: the command line spawns itself, a library host names the agent-shim executable. */
|
|
21
|
+
readonly spawnDaemon: DaemonSpawner;
|
|
22
|
+
readonly allowMissingConfigProfile?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The launcher's inputs wired to this machine: the real filesystem, daemon ports, credential resolver and `claude` binary discovery. */
|
|
25
|
+
export declare function realPrepareLaunchParams(paths: LayoutPaths, options: RealLaunchOptions): PrepareLaunchParams;
|
|
26
|
+
/** Raised by `prepareClaudeLaunch` when the launch is refused: no identity, a missing profile, an ambient credential, an unresolvable credential and the like. `message` is what the launcher logged; `exitCode` is the status the command line would have exited with. */
|
|
27
|
+
export declare class LaunchRefusedError extends CliError {
|
|
28
|
+
readonly exitCode: number;
|
|
29
|
+
constructor(message: string, exitCode: number);
|
|
30
|
+
}
|
|
31
|
+
/** What `prepareClaudeLaunch` needs from its caller. */
|
|
32
|
+
export interface PrepareClaudeLaunchOptions {
|
|
33
|
+
/** The arguments after `agent-shim run`, exactly as the command line takes them: `@name`, launch flags, then the arguments for `claude`. */
|
|
34
|
+
readonly argv: readonly string[];
|
|
35
|
+
/** The directory the launch is for. */
|
|
36
|
+
readonly cwd: string;
|
|
37
|
+
/** The environment the launch inherits; a variable it sets is what the child sees. */
|
|
38
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
39
|
+
/** The state root; defaults to the one the command line resolves. */
|
|
40
|
+
readonly paths?: LayoutPaths;
|
|
41
|
+
/** The path of the agent-shim executable, used to start the front door and headroom daemons when the launch routes through them. Without it such a launch is refused: this process is not agent-shim, and re-running it would run the host program again. */
|
|
42
|
+
readonly agentShim?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolves a launch for `options.cwd` on this machine and returns what to spawn, so another tool can start `claude` as an identity, through a provider, with the same sharing rules the command line applies, without shelling out. It performs the effects the child depends on: the identity's farm is resynced, and the front door and headroom are brought up and this process registered with them. Call `release` on the plan when the child has exited.
|
|
46
|
+
*
|
|
47
|
+
* A refused launch throws `LaunchRefusedError` carrying the launcher's own message. Interactive offers (creating a missing identity or profile on a terminal) belong to the command line and are not made here.
|
|
48
|
+
*/
|
|
49
|
+
export declare function prepareClaudeLaunch(options: PrepareClaudeLaunchOptions): LaunchPlan;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { UsageError } from "../cliError";
|
|
2
|
+
/** The result of parsing the launcher's own argv: the identity selection, any one-off `agent-shim` flags, and everything left to forward. */
|
|
3
|
+
export interface ParsedLauncherArgv {
|
|
4
|
+
/** The identity named by a leading `@name` positional at argv[0] or by `--identity <name>`, when either was present. */
|
|
5
|
+
readonly identity?: string;
|
|
6
|
+
/** An explicit `--config-profile <name>` flag, when one was present. */
|
|
7
|
+
readonly configProfile?: string;
|
|
8
|
+
/** The last `--provider <name>` or `--no-provider` occurrence: a provider name, or `false` when `--no-provider` opted this launch out of any provider the cascade selects. Undefined when neither was given. */
|
|
9
|
+
readonly provider?: string | false;
|
|
10
|
+
/** The last `--headroom`/`--no-headroom` occurrence, when either was given. */
|
|
11
|
+
readonly headroom?: boolean;
|
|
12
|
+
/** The last `--track-usage`/`--no-track-usage` occurrence, when either was given. */
|
|
13
|
+
readonly trackUsage?: boolean;
|
|
14
|
+
/** The last `--wait`/`--no-wait` occurrence, when either was given: with a pool selected and every member refused, sleep until the earliest returns instead of refusing the launch. */
|
|
15
|
+
readonly wait?: boolean;
|
|
16
|
+
/** The last `--skip-permissions`/`--no-skip-permissions` occurrence, when either was given. */
|
|
17
|
+
readonly skipPermissions?: boolean;
|
|
18
|
+
/** The last `--remote-control`/`--no-remote-control` occurrence, when either was given. */
|
|
19
|
+
readonly remoteControl?: boolean;
|
|
20
|
+
/** Every `--category <cat>=<bool>` flag's raw value, in the order given: later values win on key collision when merged. */
|
|
21
|
+
readonly categoryFlags: readonly string[];
|
|
22
|
+
/** Every `--share <path>` flag's value, in the order given. */
|
|
23
|
+
readonly shareFlags: readonly string[];
|
|
24
|
+
/** Every `--hide <path>` flag's value, in the order given. */
|
|
25
|
+
readonly hideFlags: readonly string[];
|
|
26
|
+
/** Everything else, unchanged and in order: every token from a `--` terminator onwards (the `--` included), and any `@`-prefixed token that appears anywhere other than argv[0]. */
|
|
27
|
+
readonly rest: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
/** Raised when a launch names its identity twice, through both a leading `@name` and `--identity`, with different names. */
|
|
30
|
+
export declare class ConflictingIdentityError extends UsageError {
|
|
31
|
+
readonly positional: string;
|
|
32
|
+
readonly flag: string;
|
|
33
|
+
constructor(positional: string, flag: string);
|
|
34
|
+
}
|
|
35
|
+
/** Every flag `parseLauncherArgv` consumes, both forms of each boolean included: what `agent-shim completion` offers after `run`. */
|
|
36
|
+
export declare const LAUNCHER_FLAG_NAMES: readonly string[];
|
|
37
|
+
/**
|
|
38
|
+
* Parses the launcher's own argv for its identity selection and the one-off `agent-shim` launch flags (`--identity`, `--config-profile`, `--provider`/`--no-provider`, `--category`, `--share`, `--hide`, and the `--[no-]headroom`, `--[no-]track-usage`, `--[no-]skip-permissions`, `--[no-]remote-control`, `--[no-]wait` booleans). None of these are real Claude Code flags, so all are consumed here and never forwarded.
|
|
39
|
+
*
|
|
40
|
+
* `name` in `@name` and `--identity <name>` may also be `pool:<pool>`, which the launcher resolves to a member of that pool. The `@name` form is consumed ONLY at argv[0], never mid-argument-list; `--identity <name>` is its explicit form, and naming two different identities through both throws `ConflictingIdentityError`. The flags are recognised only before a `--` terminator: from `--` onwards every token is forwarded verbatim, so `claude mcp add n -- cmd --provider x` keeps `--provider x` for `cmd`. Valued flags accept both `--flag value` and `--flag=value` and take exactly one value per occurrence; `--category`, `--share` and `--hide` repeat to supply several, and every other flag's later occurrence wins. A valued flag with no value after it (the last token, or directly before `--`) is left in place, untouched, since there is nothing to pair it with.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseLauncherArgv(argv: readonly string[]): ParsedLauncherArgv;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type CategoryMap, type Entries } from "../config/schema";
|
|
2
|
+
import { CliError } from "../cliError";
|
|
3
|
+
/** Raised when a `--category`/`AGENT_SHIM_CATEGORY_OVERRIDE` key names something other than one of the four overridable categories. */
|
|
4
|
+
export declare class InvalidCliCategoryError extends CliError {
|
|
5
|
+
readonly categoryName: string;
|
|
6
|
+
constructor(categoryName: string);
|
|
7
|
+
}
|
|
8
|
+
/** Raised when a `--share`/`--hide`/`AGENT_SHIM_ENTRY_OVERRIDE` path is missing its required `<category>/` prefix. */
|
|
9
|
+
export declare class InvalidCliEntryKeyError extends CliError {
|
|
10
|
+
readonly key: string;
|
|
11
|
+
constructor(key: string);
|
|
12
|
+
}
|
|
13
|
+
/** Inputs to `buildCliOverride`: the raw, still-unparsed flag values `parseLauncherArgv` collected (one value per flag occurrence), plus the environment for their `AGENT_SHIM_*_OVERRIDE` alternatives. */
|
|
14
|
+
export interface BuildCliOverrideParams {
|
|
15
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
16
|
+
readonly providerFlag?: string;
|
|
17
|
+
readonly categoryFlags: readonly string[];
|
|
18
|
+
readonly shareFlags: readonly string[];
|
|
19
|
+
readonly hideFlags: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
/** What a launch's one-off command-line/environment overrides resolve to: the `cliOverride` layer `src/resolve/walk.ts`'s `assembleCascade` composes last, so it beats every other layer. */
|
|
22
|
+
export interface CliOverride {
|
|
23
|
+
readonly categories?: CategoryMap;
|
|
24
|
+
readonly entries?: Entries;
|
|
25
|
+
readonly launch?: {
|
|
26
|
+
readonly provider?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds this launch's one-off category/entry overrides from `--category`/`--share`/`--hide` flags and their `AGENT_SHIM_CATEGORY_OVERRIDE`/`AGENT_SHIM_ENTRY_OVERRIDE` environment-variable alternatives.
|
|
31
|
+
*
|
|
32
|
+
* Each flag occurrence carries exactly one value (`--category history=true`, `--share knowledge/skills/commit`); the flags repeat rather than taking comma lists. The environment variables cannot repeat, so each holds a comma-separated list of `<key>=<bool>` pairs instead.
|
|
33
|
+
*
|
|
34
|
+
* The environment variable provides a base and the flags merge on top, later flag winning on key collision: the same "later occurrence wins" convention `agent-shim profile set --category`/`--entry` use, applied here because the flag and the environment variable are documented as equally-weighted alternatives for the same one-off override, not two different precedence tiers.
|
|
35
|
+
*
|
|
36
|
+
* Returns `undefined` when nothing at all was supplied, so a launch with no overrides adds no `cliOverride` layer rather than an empty no-op one. A malformed flag or environment value throws `UsageError`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildCliOverride(params: BuildCliOverrideParams): CliOverride | undefined;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits `$CLAUDE_EXTRA_FLAGS` into multiple argv entries on whitespace — confirmed against Joe's real dotfiles wrapper scripts, not a hypothetical requirement.
|
|
3
|
+
*
|
|
4
|
+
* `"--continue continue"` splits into `["--continue", "continue"]`, matching what wrappers like `cccc`/`mcc`/`occ`/`zcc`/`scc` rely on. Unset or empty input produces zero argv entries — `splitExtraFlags(undefined) === []` and `splitExtraFlags("") === []` — a naive `.split(" ")` would yield `[""]` for the empty case, a real bug this avoids rather than a hypothetical one. No wrapper script surveyed ever embeds a literal space inside one intended argument, so a plain whitespace split (collapsing any run of whitespace, not just a single space) is correct and sufficient; adding quote-aware parsing would only risk changing the meaning of a future value that happens to contain a quote character, for zero real benefit.
|
|
5
|
+
*/
|
|
6
|
+
export declare function splitExtraFlags(value: string | undefined): string[];
|
|
@@ -0,0 +1,20 @@
|
|
|
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;
|
|
@@ -0,0 +1,98 @@
|
|
|
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;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CredentialCacheEnv } from "./credential";
|
|
2
|
+
import type { LayoutPaths } from "./paths";
|
|
3
|
+
import { type CredentialCachePort } from "./credentialCache";
|
|
4
|
+
/** A file name for an owner such as `identity work`: every character outside letters, digits, `.`, `_` and `-` becomes `_`, so no owner can name a path outside the cache directory. */
|
|
5
|
+
export declare function cacheFileNameFor(owner: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* The real `CredentialCachePort`. The file store keeps one JSON file per owner under `<root>/credential-cache`, directory 0700 and file 0600. The Keychain store keeps the entry (base64 of its JSON, which has no spaces or quotes to escape) as a generic password under `KEYCHAIN_CACHE_SERVICE`, written through `security -i` on standard input so the token is never an argument. Both treat a missing or malformed entry as no entry.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createRealCredentialCache(root: string): CredentialCachePort;
|
|
10
|
+
/** The cache environment the real launcher hands the resolver: the real stores under this layout's root, this platform, and the wall clock. */
|
|
11
|
+
export declare function realCredentialCacheEnv(paths: LayoutPaths): CredentialCacheEnv;
|
|
@@ -38,6 +38,10 @@ export declare function detachedDaemonSpawnOptions(logFd: number, env: NodeJS.Pr
|
|
|
38
38
|
* Spawns a detached supervisor: a background copy of this very executable running a hidden internal subcommand, its output appended to `logPath`, unref'd so the launcher never waits on it. `AGENT_SHIM_HOME` is passed explicitly so the supervisor lands on the same root as its spawner even when the launcher was started with the variable set only for itself.
|
|
39
39
|
*/
|
|
40
40
|
export declare function spawnDetachedSupervisor(paths: LayoutPaths, subcommand: string, logPath: string): number;
|
|
41
|
+
/** Starts a background daemon: `paths`, `subcommand` and `logPath` as for `spawnDetachedSupervisor`, which a caller that is not the agent-shim command line process supplies in place of it. */
|
|
42
|
+
export type DaemonSpawner = (paths: LayoutPaths, subcommand: string, logPath: string) => number;
|
|
43
|
+
/** A `DaemonSpawner` that starts daemons through the agent-shim executable at `executable`, for a caller (a library host) whose own process is not it. */
|
|
44
|
+
export declare function spawnDaemonThrough(executable: string): DaemonSpawner;
|
|
41
45
|
/**
|
|
42
46
|
* Whether nothing is currently listening on one specific loopback port: binds it on 127.0.0.1 and releases, so the headroom supervisor can decide whether its sticky `lastPort` is still reusable. The same bind-and-release technique the supervisor's `freePort` uses, applied to a number the caller already cares about rather than asking the OS to pick one.
|
|
43
47
|
*/
|
|
@@ -45,7 +49,10 @@ export declare function realIsPortFree(port: number): Promise<boolean>;
|
|
|
45
49
|
/**
|
|
46
50
|
* The real `HeadroomPort` for one launch: `ensure` runs the lock-and-poll coordination against the real filesystem and process table and derives the per-project identity from the real git repository containing the working directory; `release` removes this launcher's session-registry entry.
|
|
47
51
|
*/
|
|
48
|
-
export declare function realHeadroomPort(paths: LayoutPaths
|
|
52
|
+
export declare function realHeadroomPort(paths: LayoutPaths, options: {
|
|
53
|
+
readonly spawnDaemon: DaemonSpawner;
|
|
54
|
+
readonly cwd: string;
|
|
55
|
+
}): HeadroomPort;
|
|
49
56
|
/** The real `SpawnPort`, backed by `node:child_process`'s `spawnSync` — the Node equivalent of the legacy bash tool's `exec`. */
|
|
50
57
|
export declare const realSpawnPort: SpawnPort;
|
|
51
58
|
/** The real `ProcPort`, reading the actual process environment and logical CLI argv (`process.argv.slice(2)`). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-shim",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.13.0",
|
|
4
4
|
"description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Joseph Mearman <joseph@mearman.co.uk>",
|