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.
- package/dist/cli.cjs +95 -27
- package/dist/index.cjs +70 -7
- package/dist/index.mjs +70 -7
- package/dist/types/index.d.ts +2789 -40
- package/package.json +3 -1
- package/dist/types/checkReport.d.ts +0 -154
- package/dist/types/claudeShim.d.ts +0 -114
- package/dist/types/cli/bool.d.ts +0 -8
- package/dist/types/cli/commandDeps.d.ts +0 -39
- package/dist/types/cli/envBool.d.ts +0 -11
- package/dist/types/cli/pairs.d.ts +0 -19
- package/dist/types/cliError.d.ts +0 -27
- package/dist/types/codex/http.d.ts +0 -15
- package/dist/types/config/classify.d.ts +0 -49
- package/dist/types/config/load.d.ts +0 -42
- package/dist/types/config/schema.d.ts +0 -746
- package/dist/types/config/store.d.ts +0 -46
- package/dist/types/configProfilesStore.d.ts +0 -61
- package/dist/types/configure.d.ts +0 -170
- package/dist/types/credential.d.ts +0 -136
- package/dist/types/credentialCache.d.ts +0 -46
- package/dist/types/directoryRulesStore.d.ts +0 -43
- package/dist/types/doctorReport.d.ts +0 -117
- package/dist/types/frontdoor/capability.d.ts +0 -19
- package/dist/types/frontdoor/capture.d.ts +0 -58
- package/dist/types/frontdoor/connect.d.ts +0 -209
- package/dist/types/frontdoor/connectEffects.d.ts +0 -3
- package/dist/types/frontdoor/custody.d.ts +0 -32
- package/dist/types/frontdoor/ensure.d.ts +0 -36
- package/dist/types/frontdoor/headroomHop.d.ts +0 -22
- package/dist/types/frontdoor/pipeline.d.ts +0 -109
- package/dist/types/frontdoor/probe.d.ts +0 -13
- package/dist/types/frontdoor/realFrontDoorPort.d.ts +0 -5
- package/dist/types/frontdoor/route.d.ts +0 -112
- package/dist/types/frontdoor/server.d.ts +0 -44
- package/dist/types/frontdoor/state.d.ts +0 -57
- package/dist/types/frontdoor/supervisor.d.ts +0 -48
- package/dist/types/frontdoor/tap.d.ts +0 -9
- package/dist/types/frontdoor/trust.d.ts +0 -30
- package/dist/types/frontdoor/x509.d.ts +0 -11
- package/dist/types/headroom/ensure.d.ts +0 -31
- package/dist/types/headroom/headers.d.ts +0 -11
- package/dist/types/headroom/settings.d.ts +0 -13
- package/dist/types/headroom/source.d.ts +0 -8
- package/dist/types/headroom/state.d.ts +0 -77
- package/dist/types/headroom/supervisor.d.ts +0 -107
- package/dist/types/identityStore.d.ts +0 -85
- package/dist/types/launchWiring.d.ts +0 -49
- package/dist/types/launcher/argv.d.ts +0 -42
- package/dist/types/launcher/cascade.d.ts +0 -42
- package/dist/types/launcher/cliOverride.d.ts +0 -38
- package/dist/types/launcher/extraFlags.d.ts +0 -6
- package/dist/types/launcher/farm.d.ts +0 -168
- package/dist/types/launcher/flags.d.ts +0 -96
- package/dist/types/launcher/guard.d.ts +0 -57
- package/dist/types/launcher/identity.d.ts +0 -95
- package/dist/types/launcher/lock.d.ts +0 -41
- package/dist/types/launcher/pool.d.ts +0 -44
- package/dist/types/launcher/ports.d.ts +0 -135
- package/dist/types/launcher/spawn.d.ts +0 -20
- package/dist/types/launcher.d.ts +0 -98
- package/dist/types/legacy.d.ts +0 -29
- package/dist/types/pathNorm.d.ts +0 -14
- package/dist/types/paths.d.ts +0 -79
- package/dist/types/poolPickReport.d.ts +0 -44
- package/dist/types/poolStore.d.ts +0 -18
- package/dist/types/providersStore.d.ts +0 -137
- package/dist/types/realCredentialCache.d.ts +0 -11
- package/dist/types/realPorts.d.ts +0 -109
- package/dist/types/resolve/conditions.d.ts +0 -37
- package/dist/types/resolve/decide.d.ts +0 -45
- package/dist/types/resolve/extends.d.ts +0 -41
- package/dist/types/resolve/flatten.d.ts +0 -13
- package/dist/types/resolve/match.d.ts +0 -58
- package/dist/types/resolve/pipeline.d.ts +0 -33
- package/dist/types/resolve/plan.d.ts +0 -57
- package/dist/types/resolve/projects.d.ts +0 -61
- package/dist/types/resolve/reconcile.d.ts +0 -80
- package/dist/types/resolve/types.d.ts +0 -159
- package/dist/types/resolve/walk.d.ts +0 -85
- package/dist/types/resolve/walkPolicy.d.ts +0 -15
- package/dist/types/usage/account.d.ts +0 -20
- package/dist/types/usage/middleware.d.ts +0 -17
- package/dist/types/usage/pick.d.ts +0 -62
- package/dist/types/usage/plan.d.ts +0 -18
- package/dist/types/usage/poolPick.d.ts +0 -31
- package/dist/types/usage/preflight.d.ts +0 -20
- package/dist/types/usage/rateLimit.d.ts +0 -25
- package/dist/types/usage/read.d.ts +0 -70
- package/dist/types/usage/scan.d.ts +0 -24
- package/dist/types/usage/schema.d.ts +0 -318
- package/dist/types/usage/store.d.ts +0 -43
- package/dist/types/versionDiscovery.d.ts +0 -47
package/dist/types/index.d.ts
CHANGED
|
@@ -1,41 +1,2790 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import * as net from 'node:net';
|
|
4
|
+
import { Duplex } from 'node:stream';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
* Every category a `~/.claude` entry can be classified into. `secret` is deliberately part of this list — it is a real classification the resolver acts on — but it is NOT part of `CategoryMapSchema`'s shape, because no configuration layer may ever toggle it. See OVERRIDABLE_CATEGORIES.
|
|
8
|
+
*/
|
|
9
|
+
declare const CATEGORY_NAMES: readonly ["secret", "runtime", "history", "knowledge", "settings"];
|
|
10
|
+
type CategoryName = (typeof CATEGORY_NAMES)[number];
|
|
11
|
+
/** The four categories a configuration layer is allowed to toggle. `secret` is absent by design. */
|
|
12
|
+
declare const OVERRIDABLE_CATEGORIES: readonly ["runtime", "history", "knowledge", "settings"];
|
|
13
|
+
type OverridableCategory = (typeof OVERRIDABLE_CATEGORIES)[number];
|
|
14
|
+
/**
|
|
15
|
+
* The category toggle map's resolved shape — always exactly the four overridable categories, never the `all` pseudo-key `CategoryMapSchema` also accepts on input. Derived directly from `OverridableCategory` rather than `z.infer`red from a schema, since `CategoryMapSchema` itself carries a `.transform()` (whose inferred type follows the transform's *output*, so it can't be used to define its own output type without circularity) and a schema built solely to be `typeof`'d, never actually parsed with, would be dead weight at runtime for no benefit over a plain mapped type.
|
|
16
|
+
*/
|
|
17
|
+
type CategoryMap = Partial<Record<OverridableCategory, boolean>>;
|
|
18
|
+
/**
|
|
19
|
+
* The category toggle map as written by hand: the four overridable categories, plus `all` as shorthand for "every shareable category at once" (expanded by `expandAllCategoryKey` above). `secret` is omitted from the shape entirely, so `{ "categories": { "secret": true } }` is rejected at parse time rather than relying solely on the resolver's runtime floor check. The closed shape is also what lets the published JSON Schema offer real key-name autocomplete, which an open record type cannot.
|
|
20
|
+
*/
|
|
21
|
+
declare const CategoryMapSchema: z.ZodPipe<z.ZodObject<{
|
|
22
|
+
all: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
+
runtime: z.ZodOptional<z.ZodBoolean>;
|
|
24
|
+
history: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
knowledge: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
settings: z.ZodOptional<z.ZodBoolean>;
|
|
27
|
+
}, z.core.$strict>, z.ZodTransform<Partial<Record<"runtime" | "history" | "knowledge" | "settings", boolean>>, {
|
|
28
|
+
all?: boolean | undefined;
|
|
29
|
+
runtime?: boolean | undefined;
|
|
30
|
+
history?: boolean | undefined;
|
|
31
|
+
knowledge?: boolean | undefined;
|
|
32
|
+
settings?: boolean | undefined;
|
|
33
|
+
}>>;
|
|
34
|
+
/**
|
|
35
|
+
* A conditional guard on an entries value or a whole directory rule. Every field present within one `when` object must hold (AND logic). An empty object is vacuously true — `agent-shim check` warns about it, it is never an error.
|
|
36
|
+
*/
|
|
37
|
+
declare const WhenSchema: z.ZodObject<{
|
|
38
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
39
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
40
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
41
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
42
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
43
|
+
}, z.core.$strict>;
|
|
44
|
+
type WhenCondition = z.infer<typeof WhenSchema>;
|
|
45
|
+
/** An entries value: a flat boolean, or a boolean guarded by a `when` condition. */
|
|
46
|
+
declare const EntryValueSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
47
|
+
value: z.ZodBoolean;
|
|
48
|
+
when: z.ZodObject<{
|
|
49
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
50
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
51
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
52
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
53
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
54
|
+
}, z.core.$strict>;
|
|
55
|
+
}, z.core.$strict>]>;
|
|
56
|
+
type EntryValue = z.infer<typeof EntryValueSchema>;
|
|
57
|
+
declare const EntriesSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
58
|
+
value: z.ZodBoolean;
|
|
59
|
+
when: z.ZodObject<{
|
|
60
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
61
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
62
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
63
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
64
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
65
|
+
}, z.core.$strict>;
|
|
66
|
+
}, z.core.$strict>]>>;
|
|
67
|
+
type Entries = z.infer<typeof EntriesSchema>;
|
|
68
|
+
/** Launch flags, resolved through the same cascade as categories and entries. */
|
|
69
|
+
declare const LaunchSchema: z.ZodObject<{
|
|
70
|
+
skipPermissions: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
remoteControl: z.ZodOptional<z.ZodBoolean>;
|
|
72
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
73
|
+
headroom: z.ZodOptional<z.ZodBoolean>;
|
|
74
|
+
trackUsage: z.ZodOptional<z.ZodBoolean>;
|
|
75
|
+
}, z.core.$strict>;
|
|
76
|
+
type LaunchFlags = z.infer<typeof LaunchSchema>;
|
|
77
|
+
/**
|
|
78
|
+
* Where a resolved credential lands in the child's environment. `bearer` exports it as `ANTHROPIC_AUTH_TOKEN` (what relays and aggregators expect, and the default), `apiKey` as `ANTHROPIC_API_KEY` (sent as `x-api-key`, what a regular Anthropic API key needs), and `oauthToken` as `CLAUDE_CODE_OAUTH_TOKEN` (a long-lived subscription token from `claude setup-token`). `oauthToken` authenticates a login, not an endpoint, so only an identity may use it; see `PROVIDER_CREDENTIAL_TARGETS`.
|
|
79
|
+
*/
|
|
80
|
+
declare const CREDENTIAL_TARGETS: readonly ["bearer", "apiKey", "oauthToken"];
|
|
81
|
+
type CredentialTarget = (typeof CREDENTIAL_TARGETS)[number];
|
|
82
|
+
/** The targets a provider's credential may use: every target except `oauthToken`, which only an identity can carry. */
|
|
83
|
+
declare const PROVIDER_CREDENTIAL_TARGETS: readonly ["bearer", "apiKey"];
|
|
84
|
+
/** The environment variable each credential target exports the token as. These are also the variables no provider `env` block may set, since agent-shim sets and clears them itself. */
|
|
85
|
+
declare const CREDENTIAL_TARGET_VARS: {
|
|
86
|
+
readonly bearer: "ANTHROPIC_AUTH_TOKEN";
|
|
87
|
+
readonly apiKey: "ANTHROPIC_API_KEY";
|
|
88
|
+
readonly oauthToken: "CLAUDE_CODE_OAUTH_TOKEN";
|
|
89
|
+
};
|
|
90
|
+
type CredentialTargetVar = (typeof CREDENTIAL_TARGET_VARS)[CredentialTarget];
|
|
91
|
+
/**
|
|
92
|
+
* One place a credential can come from. Each kind is an object with exactly one kind-naming key, so the kinds are told apart by which key is present rather than by a separate tag, and `strictObject` rejects an object naming two.
|
|
93
|
+
*
|
|
94
|
+
* - `env`: the NAME of an environment variable in the launching shell, never its value.
|
|
95
|
+
* - `file`: an absolute or `~`-rooted path to a file holding the token, which must not be readable or writable by group or others (mode 600 or stricter).
|
|
96
|
+
* - `command`: an argv run at launch whose trimmed stdout is the token, so the credential exists only in the child.
|
|
97
|
+
* - `op`: a 1Password secret reference, run as `op read <ref>`. A 1Password service account needs nothing here: `OP_SERVICE_ACCOUNT_TOKEN` in the launching environment is inherited by the command.
|
|
98
|
+
* - `keychain`: a macOS Keychain generic password, run as `security find-generic-password -s <service> [-a <account>] -w`.
|
|
99
|
+
* - `literal`: a fixed value written in the file itself. It is NOT a place for a secret: it exists for local proxies that accept any placeholder token (a codex-translation proxy, say) and so have no real credential to keep out of committed config. Anything with a real credential must use one of the other kinds.
|
|
100
|
+
*/
|
|
101
|
+
declare const CredentialSourceSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
102
|
+
env: z.ZodString;
|
|
103
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
104
|
+
file: z.ZodString;
|
|
105
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
106
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
107
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
108
|
+
command: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
109
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
110
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
111
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
112
|
+
op: z.ZodString;
|
|
113
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
114
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
115
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
116
|
+
keychain: z.ZodObject<{
|
|
117
|
+
service: z.ZodString;
|
|
118
|
+
account: z.ZodOptional<z.ZodString>;
|
|
119
|
+
}, z.core.$strict>;
|
|
120
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
121
|
+
literal: z.ZodString;
|
|
122
|
+
}, z.core.$strict>]>;
|
|
123
|
+
type CredentialSource = z.infer<typeof CredentialSourceSchema>;
|
|
124
|
+
/** Where a cached credential is kept: the macOS login Keychain, or a mode-0600 file under the agent-shim home. */
|
|
125
|
+
declare const CREDENTIAL_CACHE_STORES: readonly ["keychain", "file"];
|
|
126
|
+
type CredentialCacheStore = (typeof CREDENTIAL_CACHE_STORES)[number];
|
|
127
|
+
/**
|
|
128
|
+
* An optional cache on a credential block: the resolved token is kept for `ttl` (a whole number followed by `s`, `m`, `h` or `d`, for example `12h`) so a launch does not re-run a source that needs a person, such as a desktop-unlocked `op`. With no `ttl` the cached token never expires and is replaced only by `credential warm` or removed by `credential forget`. `store` defaults to the Keychain on macOS and a file elsewhere. A block with no `cache` stores nothing and fetches on every launch.
|
|
129
|
+
*/
|
|
130
|
+
declare const CredentialCacheSchema: z.ZodObject<{
|
|
131
|
+
ttl: z.ZodOptional<z.ZodString>;
|
|
132
|
+
store: z.ZodOptional<z.ZodEnum<{
|
|
133
|
+
file: "file";
|
|
134
|
+
keychain: "keychain";
|
|
135
|
+
}>>;
|
|
136
|
+
}, z.core.$strict>;
|
|
137
|
+
type CredentialCache = z.infer<typeof CredentialCacheSchema>;
|
|
138
|
+
/**
|
|
139
|
+
* The credential block providers and identities share: an ordered list of sources, tried in turn until one yields a non-empty token, and the target that token is exported as. `target` defaults to `bearer`.
|
|
140
|
+
*/
|
|
141
|
+
declare const CredentialSchema: z.ZodObject<{
|
|
142
|
+
sources: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
143
|
+
env: z.ZodString;
|
|
144
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
145
|
+
file: z.ZodString;
|
|
146
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
147
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
148
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
149
|
+
command: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
150
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
151
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
152
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
153
|
+
op: z.ZodString;
|
|
154
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
155
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
156
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
157
|
+
keychain: z.ZodObject<{
|
|
158
|
+
service: z.ZodString;
|
|
159
|
+
account: z.ZodOptional<z.ZodString>;
|
|
160
|
+
}, z.core.$strict>;
|
|
161
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
162
|
+
literal: z.ZodString;
|
|
163
|
+
}, z.core.$strict>]>>;
|
|
164
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
165
|
+
bearer: "bearer";
|
|
166
|
+
apiKey: "apiKey";
|
|
167
|
+
oauthToken: "oauthToken";
|
|
168
|
+
}>>;
|
|
169
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
170
|
+
ttl: z.ZodOptional<z.ZodString>;
|
|
171
|
+
store: z.ZodOptional<z.ZodEnum<{
|
|
172
|
+
file: "file";
|
|
173
|
+
keychain: "keychain";
|
|
174
|
+
}>>;
|
|
175
|
+
}, z.core.$strict>>;
|
|
176
|
+
}, z.core.$strict>;
|
|
177
|
+
type Credential = z.infer<typeof CredentialSchema>;
|
|
178
|
+
/** The kinds of provider: `http` is an Anthropic Messages endpoint at a fixed `baseUrl` (the default when `kind` is absent), and `codex` is ChatGPT's Codex backend reached through agent-shim's own supervised translation daemon. */
|
|
179
|
+
declare const PROVIDER_KINDS: readonly ["http", "codex"];
|
|
180
|
+
/**
|
|
181
|
+
* How a codex provider translates Claude Code's requests. Every field is optional and defaults to the shipped mapping (`CODEX_DEFAULT_MODEL`, `CODEX_DEFAULT_TIER_MODELS`, `CODEX_DEFAULT_EFFORT`), so an absent block means the stock behaviour. A requested model that already names a codex model (`gpt-...`) passes through untouched whatever this says.
|
|
182
|
+
*/
|
|
183
|
+
declare const CodexProviderConfigSchema: z.ZodObject<{
|
|
184
|
+
defaultModel: z.ZodOptional<z.ZodString>;
|
|
185
|
+
models: z.ZodOptional<z.ZodObject<{
|
|
186
|
+
fable: z.ZodOptional<z.ZodString>;
|
|
187
|
+
opus: z.ZodOptional<z.ZodString>;
|
|
188
|
+
sonnet: z.ZodOptional<z.ZodString>;
|
|
189
|
+
haiku: z.ZodOptional<z.ZodString>;
|
|
190
|
+
}, z.core.$strict>>;
|
|
191
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
192
|
+
none: "none";
|
|
193
|
+
low: "low";
|
|
194
|
+
medium: "medium";
|
|
195
|
+
high: "high";
|
|
196
|
+
}>>;
|
|
197
|
+
}, z.core.$strict>;
|
|
198
|
+
type CodexProviderConfig = z.infer<typeof CodexProviderConfigSchema>;
|
|
199
|
+
/** Either kind of provider; see `HttpProviderSchema` for the shared fields. */
|
|
200
|
+
declare const ProviderSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
201
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
202
|
+
kind: z.ZodOptional<z.ZodLiteral<"http">>;
|
|
203
|
+
displayName: z.ZodString;
|
|
204
|
+
baseUrl: z.ZodURL;
|
|
205
|
+
credential: z.ZodObject<{
|
|
206
|
+
sources: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
207
|
+
env: z.ZodString;
|
|
208
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
209
|
+
file: z.ZodString;
|
|
210
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
211
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
212
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
213
|
+
command: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
214
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
215
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
216
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
217
|
+
op: z.ZodString;
|
|
218
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
219
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
220
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
221
|
+
keychain: z.ZodObject<{
|
|
222
|
+
service: z.ZodString;
|
|
223
|
+
account: z.ZodOptional<z.ZodString>;
|
|
224
|
+
}, z.core.$strict>;
|
|
225
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
226
|
+
literal: z.ZodString;
|
|
227
|
+
}, z.core.$strict>]>>;
|
|
228
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
229
|
+
ttl: z.ZodOptional<z.ZodString>;
|
|
230
|
+
store: z.ZodOptional<z.ZodEnum<{
|
|
231
|
+
file: "file";
|
|
232
|
+
keychain: "keychain";
|
|
233
|
+
}>>;
|
|
234
|
+
}, z.core.$strict>>;
|
|
235
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
236
|
+
bearer: "bearer";
|
|
237
|
+
apiKey: "apiKey";
|
|
238
|
+
}>>;
|
|
239
|
+
}, z.core.$strict>;
|
|
240
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
241
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
242
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
243
|
+
kind: z.ZodLiteral<"codex">;
|
|
244
|
+
displayName: z.ZodString;
|
|
245
|
+
credential: z.ZodObject<{
|
|
246
|
+
sources: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
247
|
+
env: z.ZodString;
|
|
248
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
249
|
+
file: z.ZodString;
|
|
250
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
251
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
252
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
253
|
+
command: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
254
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
255
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
256
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
257
|
+
op: z.ZodString;
|
|
258
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
259
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
260
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
261
|
+
keychain: z.ZodObject<{
|
|
262
|
+
service: z.ZodString;
|
|
263
|
+
account: z.ZodOptional<z.ZodString>;
|
|
264
|
+
}, z.core.$strict>;
|
|
265
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
266
|
+
literal: z.ZodString;
|
|
267
|
+
}, z.core.$strict>]>>;
|
|
268
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
269
|
+
ttl: z.ZodOptional<z.ZodString>;
|
|
270
|
+
store: z.ZodOptional<z.ZodEnum<{
|
|
271
|
+
file: "file";
|
|
272
|
+
keychain: "keychain";
|
|
273
|
+
}>>;
|
|
274
|
+
}, z.core.$strict>>;
|
|
275
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
276
|
+
bearer: "bearer";
|
|
277
|
+
apiKey: "apiKey";
|
|
278
|
+
}>>;
|
|
279
|
+
}, z.core.$strict>;
|
|
280
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
281
|
+
codex: z.ZodOptional<z.ZodObject<{
|
|
282
|
+
defaultModel: z.ZodOptional<z.ZodString>;
|
|
283
|
+
models: z.ZodOptional<z.ZodObject<{
|
|
284
|
+
fable: z.ZodOptional<z.ZodString>;
|
|
285
|
+
opus: z.ZodOptional<z.ZodString>;
|
|
286
|
+
sonnet: z.ZodOptional<z.ZodString>;
|
|
287
|
+
haiku: z.ZodOptional<z.ZodString>;
|
|
288
|
+
}, z.core.$strict>>;
|
|
289
|
+
effort: z.ZodOptional<z.ZodEnum<{
|
|
290
|
+
none: "none";
|
|
291
|
+
low: "low";
|
|
292
|
+
medium: "medium";
|
|
293
|
+
high: "high";
|
|
294
|
+
}>>;
|
|
295
|
+
}, z.core.$strict>>;
|
|
296
|
+
}, z.core.$strict>], "kind">;
|
|
297
|
+
type Provider = z.infer<typeof ProviderSchema>;
|
|
298
|
+
/**
|
|
299
|
+
* A named, reusable configuration profile at `~/.agent-shim/config-profiles/<name>.json`.
|
|
300
|
+
*
|
|
301
|
+
* `extends` is a flat array of other profiles' *names*, deliberately not a self-referential `z.lazy()` schema: nothing in this shape points back at a profile object, so each file validates in isolation and the extends graph is walked at resolve time. That also means Zod cannot detect a circular `extends` definition — the walker in `src/resolve/extends.ts` carries its own cycle guard.
|
|
302
|
+
*/
|
|
303
|
+
declare const ConfigProfileSchema: z.ZodObject<{
|
|
304
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
305
|
+
description: z.ZodOptional<z.ZodString>;
|
|
306
|
+
extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
307
|
+
categories: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
308
|
+
all: z.ZodOptional<z.ZodBoolean>;
|
|
309
|
+
runtime: z.ZodOptional<z.ZodBoolean>;
|
|
310
|
+
history: z.ZodOptional<z.ZodBoolean>;
|
|
311
|
+
knowledge: z.ZodOptional<z.ZodBoolean>;
|
|
312
|
+
settings: z.ZodOptional<z.ZodBoolean>;
|
|
313
|
+
}, z.core.$strict>, z.ZodTransform<Partial<Record<"runtime" | "history" | "knowledge" | "settings", boolean>>, {
|
|
314
|
+
all?: boolean | undefined;
|
|
315
|
+
runtime?: boolean | undefined;
|
|
316
|
+
history?: boolean | undefined;
|
|
317
|
+
knowledge?: boolean | undefined;
|
|
318
|
+
settings?: boolean | undefined;
|
|
319
|
+
}>>>;
|
|
320
|
+
entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
321
|
+
value: z.ZodBoolean;
|
|
322
|
+
when: z.ZodObject<{
|
|
323
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
324
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
325
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
326
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
327
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
328
|
+
}, z.core.$strict>;
|
|
329
|
+
}, z.core.$strict>]>>>;
|
|
330
|
+
launch: z.ZodOptional<z.ZodObject<{
|
|
331
|
+
skipPermissions: z.ZodOptional<z.ZodBoolean>;
|
|
332
|
+
remoteControl: z.ZodOptional<z.ZodBoolean>;
|
|
333
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
334
|
+
headroom: z.ZodOptional<z.ZodBoolean>;
|
|
335
|
+
trackUsage: z.ZodOptional<z.ZodBoolean>;
|
|
336
|
+
}, z.core.$strict>>;
|
|
337
|
+
}, z.core.$strict>;
|
|
338
|
+
type ConfigProfile = z.infer<typeof ConfigProfileSchema>;
|
|
339
|
+
/** A pool's own name: the identity alphabet minus `@`, since a pool is never an email address. */
|
|
340
|
+
declare const PoolNameSchema: z.ZodString;
|
|
341
|
+
/** A named, explicit set of identities to choose among at launch. Never implicit: a pool lists its members, so an identity that bills a client is only ever picked when someone put it there. */
|
|
342
|
+
declare const PoolSchema: z.ZodObject<{
|
|
343
|
+
identities: z.ZodArray<z.ZodString>;
|
|
344
|
+
}, z.core.$strict>;
|
|
345
|
+
type Pool = z.infer<typeof PoolSchema>;
|
|
346
|
+
/** One directory rule in `~/.agent-shim/directory-rules.json`, scoped to an explicit absolute (or `~`-rooted) path. */
|
|
347
|
+
declare const DirectoryRuleSchema: z.ZodObject<{
|
|
348
|
+
entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
349
|
+
value: z.ZodBoolean;
|
|
350
|
+
when: z.ZodObject<{
|
|
351
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
352
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
353
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
354
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
355
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
356
|
+
}, z.core.$strict>;
|
|
357
|
+
}, z.core.$strict>]>>>;
|
|
358
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
359
|
+
extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
360
|
+
categories: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
361
|
+
all: z.ZodOptional<z.ZodBoolean>;
|
|
362
|
+
runtime: z.ZodOptional<z.ZodBoolean>;
|
|
363
|
+
history: z.ZodOptional<z.ZodBoolean>;
|
|
364
|
+
knowledge: z.ZodOptional<z.ZodBoolean>;
|
|
365
|
+
settings: z.ZodOptional<z.ZodBoolean>;
|
|
366
|
+
}, z.core.$strict>, z.ZodTransform<Partial<Record<"runtime" | "history" | "knowledge" | "settings", boolean>>, {
|
|
367
|
+
all?: boolean | undefined;
|
|
368
|
+
runtime?: boolean | undefined;
|
|
369
|
+
history?: boolean | undefined;
|
|
370
|
+
knowledge?: boolean | undefined;
|
|
371
|
+
settings?: boolean | undefined;
|
|
372
|
+
}>>>;
|
|
373
|
+
launch: z.ZodOptional<z.ZodObject<{
|
|
374
|
+
skipPermissions: z.ZodOptional<z.ZodBoolean>;
|
|
375
|
+
remoteControl: z.ZodOptional<z.ZodBoolean>;
|
|
376
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
377
|
+
headroom: z.ZodOptional<z.ZodBoolean>;
|
|
378
|
+
trackUsage: z.ZodOptional<z.ZodBoolean>;
|
|
379
|
+
}, z.core.$strict>>;
|
|
380
|
+
path: z.ZodString;
|
|
381
|
+
configProfile: z.ZodOptional<z.ZodString>;
|
|
382
|
+
identity: z.ZodOptional<z.ZodString>;
|
|
383
|
+
when: z.ZodOptional<z.ZodObject<{
|
|
384
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
385
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
386
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
387
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
388
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
389
|
+
}, z.core.$strict>>;
|
|
390
|
+
}, z.core.$strict>;
|
|
391
|
+
type DirectoryRule = z.infer<typeof DirectoryRuleSchema>;
|
|
392
|
+
/** The `~/.agent-shim/directory-rules.json` file. */
|
|
393
|
+
declare const DirectoryRulesSchema: z.ZodObject<{
|
|
394
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
395
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
396
|
+
entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
397
|
+
value: z.ZodBoolean;
|
|
398
|
+
when: z.ZodObject<{
|
|
399
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
400
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
401
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
402
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
403
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
404
|
+
}, z.core.$strict>;
|
|
405
|
+
}, z.core.$strict>]>>>;
|
|
406
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
407
|
+
extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
408
|
+
categories: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
409
|
+
all: z.ZodOptional<z.ZodBoolean>;
|
|
410
|
+
runtime: z.ZodOptional<z.ZodBoolean>;
|
|
411
|
+
history: z.ZodOptional<z.ZodBoolean>;
|
|
412
|
+
knowledge: z.ZodOptional<z.ZodBoolean>;
|
|
413
|
+
settings: z.ZodOptional<z.ZodBoolean>;
|
|
414
|
+
}, z.core.$strict>, z.ZodTransform<Partial<Record<"runtime" | "history" | "knowledge" | "settings", boolean>>, {
|
|
415
|
+
all?: boolean | undefined;
|
|
416
|
+
runtime?: boolean | undefined;
|
|
417
|
+
history?: boolean | undefined;
|
|
418
|
+
knowledge?: boolean | undefined;
|
|
419
|
+
settings?: boolean | undefined;
|
|
420
|
+
}>>>;
|
|
421
|
+
launch: z.ZodOptional<z.ZodObject<{
|
|
422
|
+
skipPermissions: z.ZodOptional<z.ZodBoolean>;
|
|
423
|
+
remoteControl: z.ZodOptional<z.ZodBoolean>;
|
|
424
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
425
|
+
headroom: z.ZodOptional<z.ZodBoolean>;
|
|
426
|
+
trackUsage: z.ZodOptional<z.ZodBoolean>;
|
|
427
|
+
}, z.core.$strict>>;
|
|
428
|
+
path: z.ZodString;
|
|
429
|
+
configProfile: z.ZodOptional<z.ZodString>;
|
|
430
|
+
identity: z.ZodOptional<z.ZodString>;
|
|
431
|
+
when: z.ZodOptional<z.ZodObject<{
|
|
432
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
433
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
434
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
435
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
436
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
437
|
+
}, z.core.$strict>>;
|
|
438
|
+
}, z.core.$strict>>;
|
|
439
|
+
}, z.core.$strict>;
|
|
440
|
+
type DirectoryRules = z.infer<typeof DirectoryRulesSchema>;
|
|
441
|
+
/**
|
|
442
|
+
* A committed `.agent-shim.json` (or its gitignored `.agent-shim.local.json` sibling). Structurally a directory rule without the `path` field: its scope is implicit — wherever the file lives, and everything below it — which is exactly what makes it portable across clone locations.
|
|
443
|
+
*/
|
|
444
|
+
declare const PortableConfigSchema: z.ZodObject<{
|
|
445
|
+
entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
446
|
+
value: z.ZodBoolean;
|
|
447
|
+
when: z.ZodObject<{
|
|
448
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
449
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
450
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
451
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
452
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
453
|
+
}, z.core.$strict>;
|
|
454
|
+
}, z.core.$strict>]>>>;
|
|
455
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
456
|
+
extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
457
|
+
categories: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
458
|
+
all: z.ZodOptional<z.ZodBoolean>;
|
|
459
|
+
runtime: z.ZodOptional<z.ZodBoolean>;
|
|
460
|
+
history: z.ZodOptional<z.ZodBoolean>;
|
|
461
|
+
knowledge: z.ZodOptional<z.ZodBoolean>;
|
|
462
|
+
settings: z.ZodOptional<z.ZodBoolean>;
|
|
463
|
+
}, z.core.$strict>, z.ZodTransform<Partial<Record<"runtime" | "history" | "knowledge" | "settings", boolean>>, {
|
|
464
|
+
all?: boolean | undefined;
|
|
465
|
+
runtime?: boolean | undefined;
|
|
466
|
+
history?: boolean | undefined;
|
|
467
|
+
knowledge?: boolean | undefined;
|
|
468
|
+
settings?: boolean | undefined;
|
|
469
|
+
}>>>;
|
|
470
|
+
launch: z.ZodOptional<z.ZodObject<{
|
|
471
|
+
skipPermissions: z.ZodOptional<z.ZodBoolean>;
|
|
472
|
+
remoteControl: z.ZodOptional<z.ZodBoolean>;
|
|
473
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
474
|
+
headroom: z.ZodOptional<z.ZodBoolean>;
|
|
475
|
+
trackUsage: z.ZodOptional<z.ZodBoolean>;
|
|
476
|
+
}, z.core.$strict>>;
|
|
477
|
+
configProfile: z.ZodOptional<z.ZodString>;
|
|
478
|
+
identity: z.ZodOptional<z.ZodString>;
|
|
479
|
+
when: z.ZodOptional<z.ZodObject<{
|
|
480
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
481
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
482
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
483
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
484
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
485
|
+
}, z.core.$strict>>;
|
|
486
|
+
}, z.core.$strict>;
|
|
487
|
+
type PortableConfig = z.infer<typeof PortableConfigSchema>;
|
|
488
|
+
declare const HeadroomGlobalConfigSchema: z.ZodObject<{
|
|
489
|
+
source: z.ZodOptional<z.ZodString>;
|
|
490
|
+
idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
|
|
491
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
492
|
+
cache: "cache";
|
|
493
|
+
token: "token";
|
|
494
|
+
}>>;
|
|
495
|
+
targetRatio: z.ZodOptional<z.ZodNumber>;
|
|
496
|
+
ccr: z.ZodOptional<z.ZodEnum<{
|
|
497
|
+
default: "default";
|
|
498
|
+
none: "none";
|
|
499
|
+
lossless: "lossless";
|
|
500
|
+
}>>;
|
|
501
|
+
rolloutChannel: z.ZodOptional<z.ZodEnum<{
|
|
502
|
+
beta: "beta";
|
|
503
|
+
canary: "canary";
|
|
504
|
+
dev: "dev";
|
|
505
|
+
}>>;
|
|
506
|
+
interceptToolResults: z.ZodOptional<z.ZodBoolean>;
|
|
507
|
+
readMaturation: z.ZodOptional<z.ZodBoolean>;
|
|
508
|
+
}, z.core.$strict>;
|
|
509
|
+
type HeadroomGlobalConfig = z.infer<typeof HeadroomGlobalConfigSchema>;
|
|
510
|
+
/** The user-global `~/.agent-shim/config.json`. */
|
|
511
|
+
declare const GlobalConfigSchema: z.ZodObject<{
|
|
512
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
513
|
+
defaultConfigProfile: z.ZodOptional<z.ZodString>;
|
|
514
|
+
walkUpLimit: z.ZodOptional<z.ZodString>;
|
|
515
|
+
categories: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
516
|
+
all: z.ZodOptional<z.ZodBoolean>;
|
|
517
|
+
runtime: z.ZodOptional<z.ZodBoolean>;
|
|
518
|
+
history: z.ZodOptional<z.ZodBoolean>;
|
|
519
|
+
knowledge: z.ZodOptional<z.ZodBoolean>;
|
|
520
|
+
settings: z.ZodOptional<z.ZodBoolean>;
|
|
521
|
+
}, z.core.$strict>, z.ZodTransform<Partial<Record<"runtime" | "history" | "knowledge" | "settings", boolean>>, {
|
|
522
|
+
all?: boolean | undefined;
|
|
523
|
+
runtime?: boolean | undefined;
|
|
524
|
+
history?: boolean | undefined;
|
|
525
|
+
knowledge?: boolean | undefined;
|
|
526
|
+
settings?: boolean | undefined;
|
|
527
|
+
}>>>;
|
|
528
|
+
entries: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
529
|
+
value: z.ZodBoolean;
|
|
530
|
+
when: z.ZodObject<{
|
|
531
|
+
newerThan: z.ZodOptional<z.ZodString>;
|
|
532
|
+
olderThan: z.ZodOptional<z.ZodString>;
|
|
533
|
+
maxSizeBytes: z.ZodOptional<z.ZodInt>;
|
|
534
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
535
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
536
|
+
}, z.core.$strict>;
|
|
537
|
+
}, z.core.$strict>]>>>;
|
|
538
|
+
launch: z.ZodOptional<z.ZodObject<{
|
|
539
|
+
skipPermissions: z.ZodOptional<z.ZodBoolean>;
|
|
540
|
+
remoteControl: z.ZodOptional<z.ZodBoolean>;
|
|
541
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
542
|
+
headroom: z.ZodOptional<z.ZodBoolean>;
|
|
543
|
+
trackUsage: z.ZodOptional<z.ZodBoolean>;
|
|
544
|
+
}, z.core.$strict>>;
|
|
545
|
+
headroom: z.ZodOptional<z.ZodObject<{
|
|
546
|
+
source: z.ZodOptional<z.ZodString>;
|
|
547
|
+
idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
|
|
548
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
549
|
+
cache: "cache";
|
|
550
|
+
token: "token";
|
|
551
|
+
}>>;
|
|
552
|
+
targetRatio: z.ZodOptional<z.ZodNumber>;
|
|
553
|
+
ccr: z.ZodOptional<z.ZodEnum<{
|
|
554
|
+
default: "default";
|
|
555
|
+
none: "none";
|
|
556
|
+
lossless: "lossless";
|
|
557
|
+
}>>;
|
|
558
|
+
rolloutChannel: z.ZodOptional<z.ZodEnum<{
|
|
559
|
+
beta: "beta";
|
|
560
|
+
canary: "canary";
|
|
561
|
+
dev: "dev";
|
|
562
|
+
}>>;
|
|
563
|
+
interceptToolResults: z.ZodOptional<z.ZodBoolean>;
|
|
564
|
+
readMaturation: z.ZodOptional<z.ZodBoolean>;
|
|
565
|
+
}, z.core.$strict>>;
|
|
566
|
+
frontdoor: z.ZodOptional<z.ZodObject<{
|
|
567
|
+
idleShutdownMinutes: z.ZodOptional<z.ZodNumber>;
|
|
568
|
+
}, z.core.$strict>>;
|
|
569
|
+
pools: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
570
|
+
identities: z.ZodArray<z.ZodString>;
|
|
571
|
+
}, z.core.$strict>>>;
|
|
572
|
+
}, z.core.$strict>;
|
|
573
|
+
type GlobalConfig = z.infer<typeof GlobalConfigSchema>;
|
|
574
|
+
/**
|
|
575
|
+
* An identity's own `identity.json`. `credential`, when present, authenticates the identity from a token instead of (or ahead of) its stored login: the launcher resolves it and exports it as its target (typically `oauthToken`, a `claude setup-token` token) in the child's environment only. Without it the identity uses the login stored in its own directory.
|
|
576
|
+
*/
|
|
577
|
+
declare const IdentitySchema: z.ZodObject<{
|
|
578
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
579
|
+
name: z.ZodString;
|
|
580
|
+
defaultConfigProfile: z.ZodOptional<z.ZodString>;
|
|
581
|
+
allowAmbientCredential: z.ZodDefault<z.ZodBoolean>;
|
|
582
|
+
credential: z.ZodOptional<z.ZodObject<{
|
|
583
|
+
sources: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
584
|
+
env: z.ZodString;
|
|
585
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
586
|
+
file: z.ZodString;
|
|
587
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
588
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
589
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
590
|
+
command: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
591
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
592
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
593
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
594
|
+
op: z.ZodString;
|
|
595
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
596
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
597
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
598
|
+
keychain: z.ZodObject<{
|
|
599
|
+
service: z.ZodString;
|
|
600
|
+
account: z.ZodOptional<z.ZodString>;
|
|
601
|
+
}, z.core.$strict>;
|
|
602
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
603
|
+
literal: z.ZodString;
|
|
604
|
+
}, z.core.$strict>]>>;
|
|
605
|
+
target: z.ZodOptional<z.ZodEnum<{
|
|
606
|
+
bearer: "bearer";
|
|
607
|
+
apiKey: "apiKey";
|
|
608
|
+
oauthToken: "oauthToken";
|
|
609
|
+
}>>;
|
|
610
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
611
|
+
ttl: z.ZodOptional<z.ZodString>;
|
|
612
|
+
store: z.ZodOptional<z.ZodEnum<{
|
|
613
|
+
file: "file";
|
|
614
|
+
keychain: "keychain";
|
|
615
|
+
}>>;
|
|
616
|
+
}, z.core.$strict>>;
|
|
617
|
+
}, z.core.$strict>>;
|
|
618
|
+
}, z.core.$strict>;
|
|
619
|
+
type Identity = z.infer<typeof IdentitySchema>;
|
|
620
|
+
/**
|
|
621
|
+
* The OTHER "categories" concept, and a different shape from CategoryMapSchema entirely: this maps each category name to the list of literal names and globs whose top-level `~/.claude` entries belong to it. CategoryMapSchema says *whether* a category is shared; this says *which entries are in* a category. Do not conflate them.
|
|
622
|
+
*/
|
|
623
|
+
declare const CategoryClassificationSchema: z.ZodObject<{
|
|
624
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
625
|
+
secret: z.ZodArray<z.ZodString>;
|
|
626
|
+
runtime: z.ZodArray<z.ZodString>;
|
|
627
|
+
history: z.ZodArray<z.ZodString>;
|
|
628
|
+
knowledge: z.ZodArray<z.ZodString>;
|
|
629
|
+
settings: z.ZodArray<z.ZodString>;
|
|
630
|
+
}, z.core.$strict>;
|
|
631
|
+
type CategoryClassification = z.infer<typeof CategoryClassificationSchema>;
|
|
632
|
+
/** The gitignored `~/.agent-shim/categories.local.json` overlay: any subset of the classification lists, answering "what category is this new entry?" without editing the shipped default map. */
|
|
633
|
+
declare const CategoryClassificationOverlaySchema: z.ZodObject<{
|
|
634
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
635
|
+
secret: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
636
|
+
runtime: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
637
|
+
history: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
638
|
+
knowledge: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
639
|
+
settings: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
640
|
+
}, z.core.$strict>;
|
|
641
|
+
type CategoryClassificationOverlay = z.infer<typeof CategoryClassificationOverlaySchema>;
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* The resolved set of paths agent-shim reads and writes under its own root.
|
|
645
|
+
*
|
|
646
|
+
* `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.
|
|
647
|
+
*/
|
|
648
|
+
interface LayoutPaths {
|
|
649
|
+
/** The resolved root directory, see `resolveAgentShimHome`. */
|
|
650
|
+
readonly root: string;
|
|
651
|
+
/** Directory holding one subdirectory per identity (symlink farm + local credentials/daemon state). */
|
|
652
|
+
readonly identitiesDir: string;
|
|
653
|
+
/** Directory holding named, reusable configuration profile JSON files. */
|
|
654
|
+
readonly configProfilesDir: string;
|
|
655
|
+
/** Directory holding one `<name>.json` provider definition per API provider. */
|
|
656
|
+
readonly providersDir: string;
|
|
657
|
+
/** Path to the directory-rules.json file describing directory-scoped identity/profile pins. */
|
|
658
|
+
readonly directoryRulesFile: string;
|
|
659
|
+
/** Path to the persisted active-identity file (the identity `agent-shim identity use` selected). */
|
|
660
|
+
readonly activeIdentityFile: string;
|
|
661
|
+
/** Path to the global config.json (user-global override layer, and default profile/walk-limit settings). */
|
|
662
|
+
readonly globalConfigFile: string;
|
|
663
|
+
/** Path to the categories.local.json overlay recording answers to "unclassified entry" prompts. */
|
|
664
|
+
readonly categoriesLocalFile: string;
|
|
665
|
+
/** Path to the claude-shim.json marker recording where `agent-shim shim enable` last placed a `claude`-named copy of this executable, and how. */
|
|
666
|
+
readonly claudeShimFile: string;
|
|
667
|
+
/** Directory holding the headroom daemon's coordination state: state.json, the start lock, and the session registry. */
|
|
668
|
+
readonly headroomDir: string;
|
|
669
|
+
/** Path to the headroom supervisor's state.json: pids, port, version, allowlist hash, last error. */
|
|
670
|
+
readonly headroomStateFile: string;
|
|
671
|
+
/** Path to the exclusive-create marker guarding "who spawns the supervisor" so concurrent launches start at most one. */
|
|
672
|
+
readonly headroomLockFile: string;
|
|
673
|
+
/** Directory holding one `<launcher-pid>.json` session-registry entry per live launch routed through headroom. */
|
|
674
|
+
readonly headroomSessionsDir: string;
|
|
675
|
+
/** Directory holding daemon logs (the headroom proxy's stdout/stderr and the supervisor's own output). */
|
|
676
|
+
readonly logsDir: string;
|
|
677
|
+
/** Path to the headroom daemon's combined log, appended to by both the supervisor and the proxy it owns. */
|
|
678
|
+
readonly headroomLogPath: string;
|
|
679
|
+
/** Directory holding the front-door daemon's coordination state: state.json, the start lock, and the session registry. */
|
|
680
|
+
readonly frontdoorDir: string;
|
|
681
|
+
/** 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). */
|
|
682
|
+
readonly frontdoorCaDir: string;
|
|
683
|
+
/** Path to the front door's CA certificate, generated once and stable across restarts so children keep trusting it. */
|
|
684
|
+
readonly frontdoorCaCertFile: string;
|
|
685
|
+
/** Path to the front door's CA private key, mode 0600: whoever can read it can impersonate the door to every routed child. */
|
|
686
|
+
readonly frontdoorCaKeyFile: string;
|
|
687
|
+
/** 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. */
|
|
688
|
+
readonly frontdoorCaBundlesDir: string;
|
|
689
|
+
/** Path to the front door's state.json: its supervisor pid, its port, and the sticky port it last served on. */
|
|
690
|
+
readonly frontdoorStateFile: string;
|
|
691
|
+
/** Path to the exclusive-create marker guarding "who spawns the front-door supervisor" so concurrent launches start at most one. */
|
|
692
|
+
readonly frontdoorLockFile: string;
|
|
693
|
+
/** Directory holding one `<launcher-pid>.json` session-registry entry per live launch routed through the front door. */
|
|
694
|
+
readonly frontdoorSessionsDir: string;
|
|
695
|
+
/** Path to the front door's log: one line per lifecycle event and routed failure, never a token. */
|
|
696
|
+
readonly frontdoorLogPath: string;
|
|
697
|
+
/** Directory holding the usage store, mode 0700: the request log and the per-identity snapshots. Metadata only, never content or credentials. */
|
|
698
|
+
readonly usageDir: string;
|
|
699
|
+
/** Directory of the append-only usage log: one `<YYYY-MM-DD>.<writer-pid>.jsonl` segment per writing process per UTC day. */
|
|
700
|
+
readonly usageLogDir: string;
|
|
701
|
+
/** Directory of the latest-state snapshots, one `<identity>.json` per identity: the stable file other tools read. */
|
|
702
|
+
readonly usageSnapshotsDir: string;
|
|
703
|
+
/** The last identity picked from a pool for each directory, so a conversation stays on the account whose prompt cache is warm. */
|
|
704
|
+
readonly usagePicksFile: string;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* 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.
|
|
708
|
+
*
|
|
709
|
+
* 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.
|
|
710
|
+
*/
|
|
711
|
+
declare function resolveAgentShimHome(env?: NodeJS.ProcessEnv, home?: string, isDirectory?: (candidate: string) => boolean): string;
|
|
712
|
+
/**
|
|
713
|
+
* Resolves the canonical `~/.claude` directory every farm symlink points back into: `AGENT_SHIM_CLAUDE_HOME` when set to a non-empty string, otherwise `~/.claude`.
|
|
714
|
+
*
|
|
715
|
+
* 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.
|
|
716
|
+
*/
|
|
717
|
+
declare function resolveClaudeHome(): string;
|
|
718
|
+
/** Builds the full LayoutPaths structure from a given root directory. */
|
|
719
|
+
declare function buildLayoutPaths(root: string): LayoutPaths;
|
|
720
|
+
/** Resolves AGENT_SHIM_HOME and builds the full LayoutPaths structure in one call. */
|
|
721
|
+
declare function resolveLayoutPaths(): LayoutPaths;
|
|
722
|
+
|
|
723
|
+
/** Which map a classification pattern came from. A `local` pattern is an answer the user gave to an "unclassified entry" prompt; a `default` pattern is shipped with the tool. */
|
|
724
|
+
type ClassificationSource = "default" | "local";
|
|
725
|
+
/** One compiled classification pattern: which category it assigns, where it came from, and how specific it is. */
|
|
726
|
+
interface ClassificationPattern {
|
|
727
|
+
readonly pattern: string;
|
|
728
|
+
readonly category: CategoryName;
|
|
729
|
+
readonly source: ClassificationSource;
|
|
730
|
+
/** True when the pattern contains no glob metacharacter, so it can only ever match one literal name. */
|
|
731
|
+
readonly isExact: boolean;
|
|
732
|
+
/** Position of the pattern within its own category list, used only as the final deterministic tie-break. */
|
|
733
|
+
readonly ordinal: number;
|
|
734
|
+
readonly matches: (name: string) => boolean;
|
|
735
|
+
}
|
|
736
|
+
/** The outcome of classifying a set of real top-level `~/.claude` entry names. */
|
|
737
|
+
interface ClassifyResult {
|
|
738
|
+
/** Every input name mapped to its category, or to null when nothing in either map recognises it. */
|
|
739
|
+
readonly classification: ReadonlyMap<string, CategoryName | null>;
|
|
740
|
+
/** The input names that nothing recognised, in input order. These are the names `agent-shim configure` prompts about. */
|
|
741
|
+
readonly unclassified: readonly string[];
|
|
742
|
+
/** For each classified name, the pattern that decided it — used by `agent-shim check` to explain a classification. */
|
|
743
|
+
readonly decidedBy: ReadonlyMap<string, ClassificationPattern>;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** A layer's index in the assembled cascade. Strictly ascending in composition order: a higher id was composed later and therefore wins on the comparator's first rank. */
|
|
747
|
+
type LayerId = number;
|
|
748
|
+
/** Where a cascade layer came from. Purely descriptive — precedence is carried by `LayerId`, never by kind. */
|
|
749
|
+
type LayerKind = "global-config" | "config-profile" | "directory-rule" | "portable" | "portable-local" | "cli-override";
|
|
750
|
+
/** One composable layer of the cascade: its own category toggles and entries overrides, plus the entries key order captured at load time. */
|
|
751
|
+
interface Layer {
|
|
752
|
+
readonly id: LayerId;
|
|
753
|
+
readonly kind: LayerKind;
|
|
754
|
+
/** Human-readable origin (a file path, or a profile name) used by `agent-shim check` to explain a decision. */
|
|
755
|
+
readonly source: string;
|
|
756
|
+
readonly categories?: CategoryMap;
|
|
757
|
+
readonly entries?: Entries;
|
|
758
|
+
/** The entries object's own key insertion order, from `src/config/load.ts`. Falls back to `Object.keys(entries)` when absent. */
|
|
759
|
+
readonly entryOrder?: readonly string[];
|
|
760
|
+
readonly launch?: LaunchFlags;
|
|
761
|
+
}
|
|
762
|
+
/** Facts about one entry under `~/.claude`, relative to `~/.claude` itself. */
|
|
763
|
+
interface EntryFact {
|
|
764
|
+
/** Path relative to `~/.claude`, forward-slash separated, never leading or trailing slash. */
|
|
765
|
+
readonly relPath: string;
|
|
766
|
+
readonly isDirectory: boolean;
|
|
767
|
+
/** True when the entry is itself a symlink (including the relative, tree-escaping symlinks that already exist inside a real `~/.claude`). */
|
|
768
|
+
readonly isSymlink: boolean;
|
|
769
|
+
/** The entry's own mtime. */
|
|
770
|
+
readonly mtimeMs: number;
|
|
771
|
+
/** The most recent mtime anywhere in this entry's subtree — its own mtime for a plain file. A directory-scoped `newerThan` must read this, never the directory's own inode mtime, which does not change when a file three levels down is rewritten. */
|
|
772
|
+
readonly latestMtimeMs: number;
|
|
773
|
+
/** The entry's own size. */
|
|
774
|
+
readonly sizeBytes: number;
|
|
775
|
+
/** The recursive total size of this entry's subtree — its own size for a plain file. A directory-scoped `maxSizeBytes` must read this, never the directory's own ~4KB inode size. */
|
|
776
|
+
readonly totalSizeBytes: number;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Every fact the resolver needs, injected rather than read. Nothing in `src/resolve/` ever touches a real filesystem, git repository, clock, or environment: something else builds this structure and hands it over, which is what makes the whole resolver unit-testable with fake mtimes, a fake branch, and a fake environment.
|
|
780
|
+
*/
|
|
781
|
+
interface EntryFacts {
|
|
782
|
+
readonly nowMs: number;
|
|
783
|
+
/** The real home directory, used to expand `~` in `history/projects/` patterns. */
|
|
784
|
+
readonly home: string;
|
|
785
|
+
/** The canonical `~/.claude` directory every relative path in `entries` is relative to. */
|
|
786
|
+
readonly claudeHome: string;
|
|
787
|
+
/** The working directory the cascade was resolved for. */
|
|
788
|
+
readonly cwd: string;
|
|
789
|
+
/** The git branch checked out at `cwd`, or undefined when `cwd` is not in a repository. */
|
|
790
|
+
readonly branch?: string;
|
|
791
|
+
/** True when `cwd`'s repository is in detached-HEAD state, in which case no `branch` condition can match. */
|
|
792
|
+
readonly branchDetached?: boolean;
|
|
793
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
794
|
+
/** Every entry under `~/.claude`, keyed by its path relative to `~/.claude`. Directories appear alongside their descendants. */
|
|
795
|
+
readonly entries: ReadonlyMap<string, EntryFact>;
|
|
796
|
+
}
|
|
797
|
+
/** A single entries override, compiled into the form the resolver actually matches against. */
|
|
798
|
+
interface CompiledRule {
|
|
799
|
+
/** The key exactly as written in its source file, for diagnostics. */
|
|
800
|
+
readonly rawKey: string;
|
|
801
|
+
/** The category prefix the key declared. Cross-checked against the real classification of every path it matches. */
|
|
802
|
+
readonly declaredCategory: CategoryName;
|
|
803
|
+
/** The key with its category prefix stripped and (under `history/projects/`) its path fragment encoded — a pattern over paths relative to `~/.claude`. Two differently-spelled but equivalent keys share one canonical pattern and therefore collapse to one rule. */
|
|
804
|
+
readonly canonicalPattern: string;
|
|
805
|
+
readonly value: boolean;
|
|
806
|
+
readonly when?: WhenCondition;
|
|
807
|
+
readonly layer: LayerId;
|
|
808
|
+
/** The key's position within its own layer's entries object, used only by the comparator's final same-layer tie-break. */
|
|
809
|
+
readonly ordinal: number;
|
|
810
|
+
/** True when the canonical pattern contains no glob metacharacter. */
|
|
811
|
+
readonly isExact: boolean;
|
|
812
|
+
/** The pattern's leading run of literal (non-wildcard) characters. */
|
|
813
|
+
readonly literalPrefix: string;
|
|
814
|
+
/** How many `/`-separated segments the canonical pattern has. */
|
|
815
|
+
readonly segmentCount: number;
|
|
816
|
+
/** Matches the pattern itself and everything beneath it, so a rule on `skills` covers `skills/commit/SKILL.md`. */
|
|
817
|
+
readonly matches: (relPath: string) => boolean;
|
|
818
|
+
}
|
|
819
|
+
/** How a decision was reached, for `agent-shim check`'s "which layer decided this" output. */
|
|
820
|
+
type DecisionVia =
|
|
821
|
+
/** The unconditional pre-cascade floor: the path's real classification is `secret`. */
|
|
822
|
+
"secret-floor"
|
|
823
|
+
/** Nothing in the classification map recognises the path's top-level entry. */
|
|
824
|
+
| "unclassified"
|
|
825
|
+
/** An entries rule matched and its condition (if any) held. */
|
|
826
|
+
| "entry-rule"
|
|
827
|
+
/** No entries rule survived; a layer's category toggle decided it. */
|
|
828
|
+
| "category-override"
|
|
829
|
+
/** No entries rule and no category toggle; the shipped default for the category decided it. */
|
|
830
|
+
| "category-default";
|
|
831
|
+
/** One entry's resolved sharing decision plus the reasoning behind it. */
|
|
832
|
+
interface Decision {
|
|
833
|
+
readonly relPath: string;
|
|
834
|
+
readonly shared: boolean;
|
|
835
|
+
readonly via: DecisionVia;
|
|
836
|
+
readonly category: CategoryName | null;
|
|
837
|
+
/** The winning entries rule, when `via` is "entry-rule". */
|
|
838
|
+
readonly rule?: CompiledRule;
|
|
839
|
+
/** Rules that matched but whose `when` condition failed, in descending specificity — each was eliminated and resolution continued to the next candidate. */
|
|
840
|
+
readonly eliminated?: readonly EliminatedRule[];
|
|
841
|
+
}
|
|
842
|
+
/** A candidate rule that matched the path but was eliminated because its `when` condition did not hold. */
|
|
843
|
+
interface EliminatedRule {
|
|
844
|
+
readonly rule: CompiledRule;
|
|
845
|
+
readonly failed: readonly string[];
|
|
846
|
+
}
|
|
847
|
+
/** Every diagnostic the resolver can raise. Codes are stable strings so `agent-shim check` and tests can assert on them. */
|
|
848
|
+
type DiagnosticCode =
|
|
849
|
+
/** A profile's `extends` graph contains a cycle. */
|
|
850
|
+
"EXTENDS_CYCLE"
|
|
851
|
+
/** An `extends` entry names a profile that does not exist. */
|
|
852
|
+
| "MISSING_PROFILE"
|
|
853
|
+
/** An entries key was written under the `secret/` prefix. Rejected at compile time — `secret` can never be overridden by any layer. */
|
|
854
|
+
| "SECRET_ENTRY_KEY"
|
|
855
|
+
/** An entries key under some other prefix matched a path whose real classification is `secret`. Neutralised at resolve time by the floor check. */
|
|
856
|
+
| "SECRET_PATH_NEUTRALISED"
|
|
857
|
+
/** An entries key's declared category prefix disagrees with the real classification of a path it matched. */
|
|
858
|
+
| "CATEGORY_PREFIX_MISMATCH"
|
|
859
|
+
/** An entries key is not of the form `<category>/<path>`. The schema rejects these at parse time; this fires only for a layer built in code. */
|
|
860
|
+
| "MALFORMED_ENTRY_KEY"
|
|
861
|
+
/** An earlier layer's exact key lost to a later layer's glob. Correct per the comparator, but worth surfacing rather than resolving silently. */
|
|
862
|
+
| "EXACT_ENTRY_OVERRIDDEN_BY_LATER_GLOB"
|
|
863
|
+
/** A `history/projects/` pattern's encoded form could plausibly correspond to more than one real path. */
|
|
864
|
+
| "AMBIGUOUS_PROJECT_ENCODING"
|
|
865
|
+
/** A `history/projects/` key's path fragment is neither home-rooted nor absolute, so it cannot be encoded. */
|
|
866
|
+
| "UNROOTED_PROJECT_PATH"
|
|
867
|
+
/** A `~/.claude` entry nothing in the classification map recognises. */
|
|
868
|
+
| "UNCLASSIFIED_ENTRY"
|
|
869
|
+
/** An empty `when: {}` object, which is vacuously true and therefore has no effect. */
|
|
870
|
+
| "EMPTY_WHEN"
|
|
871
|
+
/** The previous farm's manifest is missing or unreadable, so reconciliation ran in conservative mode. */
|
|
872
|
+
| "FARM_MANIFEST_MISSING"
|
|
873
|
+
/** A file Claude Code wrote into a materialised farm directory differs from the canonical copy of the same path. */
|
|
874
|
+
| "RECONCILE_CONFLICT"
|
|
875
|
+
/** Reconciliation proposed adopting a path whose real classification is `secret`. Refused: the secret floor applies to data moving into `~/.claude` exactly as it applies to data moving out. */
|
|
876
|
+
| "RECONCILE_SECRET_BLOCKED"
|
|
877
|
+
/** A previous launch was interrupted mid-swap and this launch completed or undid the half-finished work. */
|
|
878
|
+
| "FARM_SWAP_RECOVERED"
|
|
879
|
+
/** A superseded farm could not be discarded because it still held data the new farm has its own entry for, so it was left on disk for the user to look at. */
|
|
880
|
+
| "FARM_PREVIOUS_RETAINED";
|
|
881
|
+
/** Severity of a diagnostic. An `error` means a configuration layer asked for something the tool refused to do. */
|
|
882
|
+
type DiagnosticSeverity = "error" | "warning" | "info";
|
|
883
|
+
/** One structured diagnostic. */
|
|
884
|
+
interface Diagnostic {
|
|
885
|
+
readonly code: DiagnosticCode;
|
|
886
|
+
readonly severity: DiagnosticSeverity;
|
|
887
|
+
readonly message: string;
|
|
888
|
+
/** The config key, profile name, or entry path the diagnostic concerns. */
|
|
889
|
+
readonly subject?: string;
|
|
890
|
+
/** The layer that raised it, when the diagnostic is attributable to one. */
|
|
891
|
+
readonly layer?: LayerId;
|
|
892
|
+
}
|
|
893
|
+
/** The flattened result of cascade phase one. */
|
|
894
|
+
interface FlattenedCascade {
|
|
895
|
+
/** The last value each overridable category was set to across every layer. Categories no layer touched are absent. */
|
|
896
|
+
readonly categories: ReadonlyMap<OverridableCategory, boolean>;
|
|
897
|
+
/** One compiled rule per canonical pattern — later layers having already overwritten earlier ones for identical patterns. */
|
|
898
|
+
readonly rules: ReadonlyMap<string, CompiledRule>;
|
|
899
|
+
/** Launch flags, last layer wins per field. */
|
|
900
|
+
readonly launch: LaunchFlags;
|
|
901
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/** Every decision the resolver reaches, plus every diagnostic raised along the way (phase-one diagnostics included, and de-duplicated). */
|
|
905
|
+
interface ResolveAllResult {
|
|
906
|
+
readonly decisions: ReadonlyMap<string, Decision>;
|
|
907
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/** What the farm should contain at one path. */
|
|
911
|
+
type FarmPlanEntry =
|
|
912
|
+
/** A single symlink standing in for this whole path — always absolute, always under `claudeHome`. */
|
|
913
|
+
{
|
|
914
|
+
readonly kind: "link";
|
|
915
|
+
readonly rel: string;
|
|
916
|
+
readonly target: string;
|
|
917
|
+
}
|
|
918
|
+
/** A real local directory the farm builds itself, because the decision beneath it is not uniform or because a conditional rule could touch it. */
|
|
919
|
+
| {
|
|
920
|
+
readonly kind: "materialise";
|
|
921
|
+
readonly rel: string;
|
|
922
|
+
readonly reason: MaterialiseReason;
|
|
923
|
+
}
|
|
924
|
+
/** Nothing at all in the farm for this path. */
|
|
925
|
+
| {
|
|
926
|
+
readonly kind: "omit";
|
|
927
|
+
readonly rel: string;
|
|
928
|
+
};
|
|
929
|
+
/** Why a directory has to be built rather than symlinked. */
|
|
930
|
+
type MaterialiseReason =
|
|
931
|
+
/** Descendants of this directory resolve to different decisions, so a single symlink cannot express the outcome. */
|
|
932
|
+
"split-decision"
|
|
933
|
+
/** A conditional entries rule could match something under this directory, so the decision has to stay live. */
|
|
934
|
+
| "conditional-rule";
|
|
935
|
+
/** The full farm plan for one resolved cascade. */
|
|
936
|
+
interface FarmPlan {
|
|
937
|
+
/** Every planned path, parents before children. */
|
|
938
|
+
readonly entries: readonly FarmPlanEntry[];
|
|
939
|
+
/** Directories the farm builds itself, in plan order. Written into the farm manifest so the next resync can tell them apart from directories Claude Code created. */
|
|
940
|
+
readonly materialised: readonly string[];
|
|
941
|
+
readonly links: readonly {
|
|
942
|
+
readonly rel: string;
|
|
943
|
+
readonly target: string;
|
|
944
|
+
}[];
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Whether the walk that builds entry facts must enter the directory at `dirRel` (relative to `~/.claude`). A directory it does not enter is one entry: nothing recorded below it, because nothing below it can be decided differently from the directory itself.
|
|
949
|
+
*/
|
|
950
|
+
type DescendPolicy = (dirRel: string) => boolean;
|
|
951
|
+
|
|
952
|
+
/** One configuration profile as loaded from disk, with its entries key order captured at load time. */
|
|
953
|
+
interface ProfileSource {
|
|
954
|
+
readonly name: string;
|
|
955
|
+
readonly profile: ConfigProfile;
|
|
956
|
+
readonly entryOrder?: readonly string[];
|
|
957
|
+
/** Where it was loaded from, for diagnostics. Defaults to the profile name. */
|
|
958
|
+
readonly filepath?: string;
|
|
959
|
+
}
|
|
960
|
+
/** Loads a profile by name. Injected, so nothing in the resolver reads a file. Returns undefined when no profile of that name exists. */
|
|
961
|
+
type ProfileLoader = (name: string) => ProfileSource | undefined;
|
|
962
|
+
|
|
963
|
+
/** The up-to-three configuration sources that can apply at one directory level. */
|
|
964
|
+
interface DirectoryLevelSources {
|
|
965
|
+
readonly dir: string;
|
|
966
|
+
/** The committed, team-shared `.agent-shim.json` at this level. */
|
|
967
|
+
readonly portable?: {
|
|
968
|
+
readonly config: PortableConfig;
|
|
969
|
+
readonly entryOrder?: readonly string[];
|
|
970
|
+
readonly filepath: string;
|
|
971
|
+
};
|
|
972
|
+
/** Every rule in this user's own `~/.agent-shim/directory-rules.json` whose path is exactly this level, in file order. */
|
|
973
|
+
readonly rules?: readonly {
|
|
974
|
+
readonly rule: DirectoryRule;
|
|
975
|
+
readonly entryOrder?: readonly string[];
|
|
976
|
+
readonly filepath: string;
|
|
977
|
+
}[];
|
|
978
|
+
/** The gitignored `.agent-shim.local.json` at this level. */
|
|
979
|
+
readonly portableLocal?: {
|
|
980
|
+
readonly config: PortableConfig;
|
|
981
|
+
readonly entryOrder?: readonly string[];
|
|
982
|
+
readonly filepath: string;
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
/** Everything needed to assemble the full ordered layer sequence, all of it already loaded by the caller. */
|
|
986
|
+
interface CascadeInput {
|
|
987
|
+
readonly home: string;
|
|
988
|
+
/** The user-global `~/.agent-shim/config.json`, the least-specific layer above the shipped defaults. */
|
|
989
|
+
readonly globalConfig?: {
|
|
990
|
+
readonly config: GlobalConfig;
|
|
991
|
+
readonly entryOrder?: readonly string[];
|
|
992
|
+
readonly filepath: string;
|
|
993
|
+
};
|
|
994
|
+
/** The configuration profile chosen by the caller's own precedence rules: explicit flag, then the active identity's default, then the global default. */
|
|
995
|
+
readonly baseConfigProfile?: string;
|
|
996
|
+
readonly loadProfile: ProfileLoader;
|
|
997
|
+
/** Directory levels, shallowest-first, as produced by `walkDirectoryAncestors`. */
|
|
998
|
+
readonly levels?: readonly DirectoryLevelSources[];
|
|
999
|
+
/** One-off overrides from the command line or environment, composed last so they beat everything. */
|
|
1000
|
+
readonly cliOverride?: {
|
|
1001
|
+
readonly categories?: Layer["categories"];
|
|
1002
|
+
readonly entries?: Layer["entries"];
|
|
1003
|
+
readonly entryOrder?: readonly string[];
|
|
1004
|
+
readonly launch?: Layer["launch"];
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
/** The assembled cascade: an ordered layer sequence plus what the walk itself decided. */
|
|
1008
|
+
interface AssembledCascade {
|
|
1009
|
+
readonly layers: readonly Layer[];
|
|
1010
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
1011
|
+
/** The deepest directory-level `identity` pin encountered, which the launcher uses as a safety net beneath an explicit `@name`. */
|
|
1012
|
+
readonly identityPin?: string;
|
|
1013
|
+
/** The deepest directory-level `configProfile` selection encountered, which is what `agent-shim configure` treats as the active profile for a path. */
|
|
1014
|
+
readonly directoryConfigProfile?: string;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/** Everything one launch needs resolved, in one call. */
|
|
1018
|
+
interface ResolveDecisionsInput {
|
|
1019
|
+
readonly facts: EntryFacts;
|
|
1020
|
+
readonly cascade: CascadeInput;
|
|
1021
|
+
readonly classification: {
|
|
1022
|
+
readonly defaults: CategoryClassification;
|
|
1023
|
+
readonly overlay?: CategoryClassificationOverlay;
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
/** The fully resolved state of one launch: the layers that composed it, every entry's decision, the farm layout, and everything worth reporting. */
|
|
1027
|
+
interface ResolvedState {
|
|
1028
|
+
readonly assembled: AssembledCascade;
|
|
1029
|
+
readonly flattened: FlattenedCascade;
|
|
1030
|
+
readonly classification: ClassifyResult;
|
|
1031
|
+
readonly decisions: ResolveAllResult["decisions"];
|
|
1032
|
+
readonly farm: FarmPlan;
|
|
1033
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
1034
|
+
}
|
|
1035
|
+
/** The top-level `~/.claude` entry names present in a fact manifest, in sorted order. */
|
|
1036
|
+
declare function topLevelNames(facts: EntryFacts): string[];
|
|
1037
|
+
/**
|
|
1038
|
+
* Runs the whole pipeline for one launch: assemble the ordered layer sequence, flatten it, classify the real `~/.claude` entries, decide every path, and plan the resulting farm.
|
|
1039
|
+
*/
|
|
1040
|
+
declare function resolveDecisions(input: ResolveDecisionsInput): ResolvedState;
|
|
1041
|
+
|
|
1042
|
+
/** The manifest the farm builder writes into each scratch tree before the atomic swap. */
|
|
1043
|
+
interface FarmManifest {
|
|
1044
|
+
readonly version: 1;
|
|
1045
|
+
readonly builtAtMs: number;
|
|
1046
|
+
readonly identity: string;
|
|
1047
|
+
readonly configProfile?: string;
|
|
1048
|
+
readonly cwd: string;
|
|
1049
|
+
readonly claudeHome: string;
|
|
1050
|
+
/** Directories the previous resync built itself. Without this list, a directory the farm materialised and a directory Claude Code created are indistinguishable on disk — both are real, both contain real children. */
|
|
1051
|
+
readonly materialised: readonly string[];
|
|
1052
|
+
readonly links: readonly {
|
|
1053
|
+
readonly rel: string;
|
|
1054
|
+
readonly target: string;
|
|
1055
|
+
}[];
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/** Exit status for every expected failure that is not a usage error: a missing identity, an invalid config file, a refused launch. */
|
|
1059
|
+
declare const EXIT_FAILURE = 1;
|
|
1060
|
+
/** Exit status for a usage error: an unknown command or option, a malformed flag value, or required input missing with no terminal to prompt on. */
|
|
1061
|
+
declare const EXIT_USAGE = 2;
|
|
1062
|
+
/**
|
|
1063
|
+
* Base class for every error this CLI throws to represent an expected, user-facing failure: bad input, a missing identity/profile/rule, a malformed config file. `reportFatalError` prints this class as `agent-shim: <message>` with no stack trace and exits with `exitCode`; anything that does not extend it is an unexpected bug, reported by message alone unless `AGENT_SHIM_DEBUG` asks for the stack.
|
|
1064
|
+
*/
|
|
1065
|
+
declare abstract class CliError extends Error {
|
|
1066
|
+
/** The process exit status this failure maps to. `EXIT_FAILURE` unless a subclass says otherwise. */
|
|
1067
|
+
readonly exitCode: number;
|
|
1068
|
+
}
|
|
1069
|
+
/** A usage error: the command line itself is wrong or incomplete. Exits `EXIT_USAGE`. */
|
|
1070
|
+
declare class UsageError extends CliError {
|
|
1071
|
+
readonly exitCode: number;
|
|
1072
|
+
constructor(message: string);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/** Injected reader for a single config file, so tests never touch a real filesystem. Returns undefined when the file does not exist. */
|
|
1076
|
+
type ConfigFileReader = (filepath: string) => unknown;
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* 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`.
|
|
1080
|
+
*/
|
|
1081
|
+
interface FsPort {
|
|
1082
|
+
/** 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. */
|
|
1083
|
+
readonly readFileUtf8: (filePath: string) => string | undefined;
|
|
1084
|
+
/** 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`. */
|
|
1085
|
+
readonly readConfigFile: ConfigFileReader;
|
|
1086
|
+
/** 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. */
|
|
1087
|
+
readonly readdir: (dir: string) => readonly string[];
|
|
1088
|
+
}
|
|
1089
|
+
/** What one node of a tree is, as reported by an `lstat` that never follows symlinks. */
|
|
1090
|
+
type FarmNodeKind = "dir" | "file" | "symlink";
|
|
1091
|
+
/** The subset of stat information the farm builder and the fact builder actually read. */
|
|
1092
|
+
interface FarmStat {
|
|
1093
|
+
readonly kind: FarmNodeKind;
|
|
1094
|
+
readonly mtimeMs: number;
|
|
1095
|
+
readonly sizeBytes: number;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* 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`.
|
|
1099
|
+
*
|
|
1100
|
+
* `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.
|
|
1101
|
+
*/
|
|
1102
|
+
interface FarmFs {
|
|
1103
|
+
/** Stats one path without following symlinks, or returns undefined when nothing is there. */
|
|
1104
|
+
readonly lstat: (path: string) => FarmStat | undefined;
|
|
1105
|
+
/** Lists one directory's immediate child names, or an empty list when it does not exist. */
|
|
1106
|
+
readonly readdir: (path: string) => readonly string[];
|
|
1107
|
+
/** Creates a directory and any missing parents, succeeding silently when it already exists. */
|
|
1108
|
+
readonly mkdirp: (path: string) => void;
|
|
1109
|
+
/** Creates a symbolic link at `linkPath` pointing at `target`. */
|
|
1110
|
+
readonly symlink: (target: string, linkPath: string) => void;
|
|
1111
|
+
/** 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. */
|
|
1112
|
+
readonly rename: (from: string, to: string) => void;
|
|
1113
|
+
/** Removes a path and everything beneath it, succeeding silently when it does not exist. */
|
|
1114
|
+
readonly removeRecursive: (path: string) => void;
|
|
1115
|
+
/** Copies a file, symlink, or whole directory tree from `from` to `to`. */
|
|
1116
|
+
readonly copyRecursive: (from: string, to: string) => void;
|
|
1117
|
+
/** Reads a file's contents as UTF-8 text, or undefined when it does not exist. */
|
|
1118
|
+
readonly readFileUtf8: (path: string) => string | undefined;
|
|
1119
|
+
/** Writes a file's full contents as UTF-8 text, creating or truncating it. */
|
|
1120
|
+
readonly writeFileUtf8: (path: string, contents: string) => void;
|
|
1121
|
+
/** 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. */
|
|
1122
|
+
readonly mkdirPrivate: (path: string) => void;
|
|
1123
|
+
/** 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. */
|
|
1124
|
+
readonly writeFilePrivate: (path: string, contents: string) => void;
|
|
1125
|
+
/** 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. */
|
|
1126
|
+
readonly appendFilePrivate: (path: string, contents: string) => void;
|
|
1127
|
+
/** 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. */
|
|
1128
|
+
readonly writeFileExclusive: (path: string, contents: string) => boolean;
|
|
1129
|
+
/** 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. */
|
|
1130
|
+
readonly hashFile: (path: string) => string | undefined;
|
|
1131
|
+
}
|
|
1132
|
+
/** 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). */
|
|
1133
|
+
interface RunResult {
|
|
1134
|
+
readonly status: number | null;
|
|
1135
|
+
readonly stdout: string;
|
|
1136
|
+
readonly stderr: string;
|
|
1137
|
+
}
|
|
1138
|
+
/** 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. */
|
|
1139
|
+
interface RunPort {
|
|
1140
|
+
readonly run: (command: string, args: readonly string[]) => RunResult;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* The running process itself: its environment, its logical argv, and the ability to terminate with an exit code.
|
|
1144
|
+
*
|
|
1145
|
+
* `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.
|
|
1146
|
+
*/
|
|
1147
|
+
interface ProcPort {
|
|
1148
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
1149
|
+
readonly argv: readonly string[];
|
|
1150
|
+
readonly exit: (code: number) => never;
|
|
1151
|
+
}
|
|
1152
|
+
/** 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. */
|
|
1153
|
+
interface LogPort {
|
|
1154
|
+
readonly info: (message: string) => void;
|
|
1155
|
+
readonly warn: (message: string) => void;
|
|
1156
|
+
readonly error: (message: string) => void;
|
|
1157
|
+
}
|
|
1158
|
+
/** 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. */
|
|
1159
|
+
interface HeadroomUp {
|
|
1160
|
+
/** 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. */
|
|
1161
|
+
readonly port: number;
|
|
1162
|
+
/** The project identity headroom scopes memory state to: the git repo root of the launch directory, or the directory itself outside a repository. */
|
|
1163
|
+
readonly projectId: string;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* 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).
|
|
1167
|
+
*
|
|
1168
|
+
* `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.
|
|
1169
|
+
*/
|
|
1170
|
+
interface HeadroomPort {
|
|
1171
|
+
readonly ensure: () => HeadroomUp;
|
|
1172
|
+
readonly release: () => void;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* 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.
|
|
1176
|
+
*/
|
|
1177
|
+
interface FrontDoorPort {
|
|
1178
|
+
/** `inheritedExtraCaCerts` is the parent environment's own `NODE_EXTRA_CA_CERTS`, which the returned trust bundle keeps (see `resolveTrustBundle`). */
|
|
1179
|
+
readonly ensure: (inheritedExtraCaCerts: string | undefined) => FrontDoorUp;
|
|
1180
|
+
readonly release: () => void;
|
|
1181
|
+
}
|
|
1182
|
+
/** The running front door one launch routed through, as `FrontDoorPort.ensure` reports it. */
|
|
1183
|
+
interface FrontDoorUp {
|
|
1184
|
+
/** The loopback port the provider listener serves HTTPS on: the base URL every provider session points at. */
|
|
1185
|
+
readonly port: number;
|
|
1186
|
+
/** The loopback port the CONNECT surface listens on: what an OAuth launch points HTTPS_PROXY at. */
|
|
1187
|
+
readonly connectPort: number;
|
|
1188
|
+
/** 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. */
|
|
1189
|
+
readonly trustBundlePath: string;
|
|
1190
|
+
/** Why the trust bundle dropped something the parent environment named, when it did; the launcher logs it. */
|
|
1191
|
+
readonly trustWarning?: string;
|
|
1192
|
+
/** This launch's capability token, injected alongside the session headers and required by the door before it routes anything. */
|
|
1193
|
+
readonly sessionToken: string;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/** The manifest file each farm carries at its own root, recording what that resync built. Named distinctly from anything Claude Code itself writes into a config directory. A farm built under the former name carries `LEGACY_FARM_MANIFEST_FILENAME` instead, which is read as a fallback and never carried into the next farm, whose resync writes this name. */
|
|
1197
|
+
declare const FARM_MANIFEST_FILENAME = ".agent-shim-farm.json";
|
|
1198
|
+
/** Reads and validates the manifest at a farm root, or undefined when it is missing, unparseable, or does not validate. */
|
|
1199
|
+
declare function readFarmManifest(fs: FarmFs, farmRoot: string): FarmManifest | undefined;
|
|
1200
|
+
/** Everything `buildEntryFacts` needs. Everything except the tree itself is injected, so the fact manifest is reproducible from a fake filesystem, a fake clock, and a fake branch. */
|
|
1201
|
+
interface BuildEntryFactsParams {
|
|
1202
|
+
readonly fs: FarmFs;
|
|
1203
|
+
readonly claudeHome: string;
|
|
1204
|
+
readonly home: string;
|
|
1205
|
+
readonly cwd: string;
|
|
1206
|
+
readonly nowMs: number;
|
|
1207
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
1208
|
+
readonly branch?: string;
|
|
1209
|
+
readonly branchDetached?: boolean;
|
|
1210
|
+
/** Which directories the walk enters; see `descendPolicy`. A directory not entered is recorded as one entry with its own size and mtime only, so a caller that needs subtree aggregates passes a policy that enters everything. */
|
|
1211
|
+
readonly descend: DescendPolicy;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Walks the canonical `~/.claude` and builds the `EntryFacts` manifest the pure resolver consumes.
|
|
1215
|
+
*
|
|
1216
|
+
* Two properties matter more than the walk itself. Symlinks are never followed: a directory symlink escaping the tree is one entry, not a subtree, which keeps the walk finite and matches how the farm links such an entry back. And every directory's `latestMtimeMs`/`totalSizeBytes` aggregate over its whole subtree rather than reporting its own inode, because a directory-scoped `newerThan` or `maxSizeBytes` condition asks about the data inside the directory — a directory's own mtime does not move when a file three levels down is rewritten.
|
|
1217
|
+
*/
|
|
1218
|
+
declare function buildEntryFacts(params: BuildEntryFactsParams): EntryFacts;
|
|
1219
|
+
/** Inputs to `carryOver`. */
|
|
1220
|
+
interface CarryOverParams {
|
|
1221
|
+
readonly fs: FarmFs;
|
|
1222
|
+
/** The superseded farm, already renamed aside. */
|
|
1223
|
+
readonly previousRoot: string;
|
|
1224
|
+
/** The new farm, already swapped into place. */
|
|
1225
|
+
readonly farmRoot: string;
|
|
1226
|
+
/**
|
|
1227
|
+
* When given, a colliding name classified as `runtime` is resolved automatically rather than reported — see `CarryOverResult.autoResolved` for why that is always safe. Optional because a caller with no classification loaded (there is none, today) should get the old, fully-manual behaviour rather than a crash; every real call site passes it.
|
|
1228
|
+
*/
|
|
1229
|
+
readonly classification?: {
|
|
1230
|
+
readonly defaults: CategoryClassification;
|
|
1231
|
+
readonly overlay?: CategoryClassificationOverlay;
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
/** What `carryOver` moved, resolved on its own, and could not. */
|
|
1235
|
+
interface CarryOverResult {
|
|
1236
|
+
/** Top-level names moved from the superseded farm into the new one. */
|
|
1237
|
+
readonly carried: readonly string[];
|
|
1238
|
+
/** Top-level names that collided but were resolved automatically because their category is `runtime` — see the doc comment on `carryOver` for why discarding the old copy is always safe here. */
|
|
1239
|
+
readonly autoResolved: readonly string[];
|
|
1240
|
+
/** Top-level names left behind because the new farm has its own entry of that name, and resolving them needs a human — everything that collided but was not classified `runtime`. */
|
|
1241
|
+
readonly collided: readonly string[];
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Moves an identity's own locally-written state out of a superseded farm and into the new one.
|
|
1245
|
+
*
|
|
1246
|
+
* This is what makes a whole-directory swap safe at all. The farm root *is* `CLAUDE_CONFIG_DIR`, so it holds more than symlinks: the identity's own `identity.json`, the credentials file Claude Code writes on Linux and Windows, its `.claude.json`, its daemon and runtime state. None of that is shared, none of it is rebuildable, and a swap that simply replaced the directory would destroy all of it.
|
|
1247
|
+
*
|
|
1248
|
+
* Anything the previous resync built itself is skipped rather than carried: a symlink is a view of the canonical tree with no data of its own, and a directory the manifest records as materialised had its real children adopted into `~/.claude` before the swap ever started. Everything else is real local data and moves across by rename, so a credential file is relocated rather than duplicated — never briefly existing as two copies on disk.
|
|
1249
|
+
*
|
|
1250
|
+
* A name that exists in both is genuinely ambiguous in general — overwriting the new farm's own entry would discard whatever the resync just decided; overwriting the old one would discard data — so by default it is reported rather than resolved here, and the caller keeps the superseded farm on disk in that case.
|
|
1251
|
+
*
|
|
1252
|
+
* One category is not ambiguous, though: `runtime`'s own definition (see `config/categories.default.json`'s category table in the README) is specifically "live per-process or per-machine artifacts" — daemon locks, an MCP auth-needed cache, an update-check result — that make no sense being preserved across a swap at all, let alone fought over. When `classification` is given, a colliding name whose category resolves to `runtime` is discarded from the superseded copy and left exactly as the new farm already has it, with no data ever moved: `keep-new` is not a judgement call for this category, it is what the category already means. This needs only the name's *static* classification, never the resolved shared/not-shared decision for the current directory — a `runtime` entry is disposable whether or not this identity currently chooses to share it, so no cascade resolution is needed to make the call.
|
|
1253
|
+
*
|
|
1254
|
+
* Exported so `launcher/farmResolve.ts` can reuse this exact collision detection (and the same `runtime` auto-resolution) for `agent-shim identity resolve-conflicts`'s interactive pass, rather than a second implementation that could drift from this one.
|
|
1255
|
+
*/
|
|
1256
|
+
declare function carryOver(params: CarryOverParams): CarryOverResult;
|
|
1257
|
+
/** What recovery found and did. */
|
|
1258
|
+
interface RecoveryResult {
|
|
1259
|
+
/** Abandoned scratch trees removed. */
|
|
1260
|
+
readonly removedScratch: readonly string[];
|
|
1261
|
+
/** Set when the farm root was missing and a superseded farm was restored into its place. */
|
|
1262
|
+
readonly restoredFrom?: string;
|
|
1263
|
+
/** Superseded farms whose carry-over was completed and which were then discarded. */
|
|
1264
|
+
readonly completed: readonly string[];
|
|
1265
|
+
/** Top-level names, across every superseded farm processed, resolved automatically because their category is `runtime` — see `carryOver`'s own doc comment for why that needs no human decision. */
|
|
1266
|
+
readonly autoResolved: readonly string[];
|
|
1267
|
+
/** Superseded farms left on disk because they still held colliding data a human still needs to decide. */
|
|
1268
|
+
readonly retained: readonly string[];
|
|
1269
|
+
/** True when recovery changed anything, in which case the farm cannot be assumed to match its own manifest. */
|
|
1270
|
+
readonly recovered: boolean;
|
|
1271
|
+
}
|
|
1272
|
+
/** Inputs to `recoverFarm`: the lock-taking wrapper around `recoverInterruptedSwap`. */
|
|
1273
|
+
interface RecoverFarmParams {
|
|
1274
|
+
readonly fs: FarmFs;
|
|
1275
|
+
readonly identitiesDir: string;
|
|
1276
|
+
readonly identity: string;
|
|
1277
|
+
readonly now: () => number;
|
|
1278
|
+
readonly lock: ResyncFarmParams["lock"];
|
|
1279
|
+
readonly classification?: {
|
|
1280
|
+
readonly defaults: CategoryClassification;
|
|
1281
|
+
readonly overlay?: CategoryClassificationOverlay;
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Runs `recoverInterruptedSwap` under the identity's own lock.
|
|
1286
|
+
*
|
|
1287
|
+
* This exists as its own entry point because recovery has to happen *before* the identity's `identity.json` is read, not merely before the farm is rebuilt: that file lives inside the farm root, so a crash between the swap's two renames leaves it sitting in a superseded directory where nothing will find it. Reading the identity first would silently launch with its `defaultConfigProfile` and `allowAmbientCredential` unset — the first of which decides what the farm is about to share.
|
|
1288
|
+
*
|
|
1289
|
+
* `resyncFarm` calls `recoverInterruptedSwap` directly rather than calling this, since it already holds the lock; taking it a second time from the same process would block against itself.
|
|
1290
|
+
*/
|
|
1291
|
+
declare function recoverFarm(params: RecoverFarmParams): RecoveryResult;
|
|
1292
|
+
/** Inputs to `resyncFarm`. */
|
|
1293
|
+
interface ResyncFarmParams {
|
|
1294
|
+
readonly fs: FarmFs;
|
|
1295
|
+
/** `~/.agent-shim/identities` — the farm root's parent, and where the lock, scratch trees, and superseded farms all live. */
|
|
1296
|
+
readonly identitiesDir: string;
|
|
1297
|
+
readonly identity: string;
|
|
1298
|
+
/** The configuration profile this launch resolved to, recorded in the manifest for `agent-shim check`. */
|
|
1299
|
+
readonly configProfile?: string;
|
|
1300
|
+
/** The canonical `~/.claude` every farm symlink points back into. */
|
|
1301
|
+
readonly claudeHome: string;
|
|
1302
|
+
readonly home: string;
|
|
1303
|
+
readonly cwd: string;
|
|
1304
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
1305
|
+
readonly branch?: string;
|
|
1306
|
+
readonly branchDetached?: boolean;
|
|
1307
|
+
readonly cascade: CascadeInput;
|
|
1308
|
+
readonly classification: {
|
|
1309
|
+
readonly defaults: CategoryClassification;
|
|
1310
|
+
readonly overlay?: CategoryClassificationOverlay;
|
|
1311
|
+
};
|
|
1312
|
+
/** Reads the current time. Called repeatedly while waiting on the lock, then once more to stamp the build. */
|
|
1313
|
+
readonly now: () => number;
|
|
1314
|
+
/** Distinguishes this resync's scratch and superseded directory names from any other process's. `${pid}.${uuid}` in real use; a fixed string in tests. */
|
|
1315
|
+
readonly uniqueSuffix: string;
|
|
1316
|
+
readonly lock: {
|
|
1317
|
+
readonly pid: number;
|
|
1318
|
+
readonly isRunning: (pid: number) => boolean;
|
|
1319
|
+
readonly sleep: (ms: number) => void;
|
|
1320
|
+
readonly staleAfterMs?: number;
|
|
1321
|
+
readonly retryDelayMs?: number;
|
|
1322
|
+
readonly maxAttempts?: number;
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
/** The outcome of one resync. */
|
|
1326
|
+
interface ResyncFarmResult {
|
|
1327
|
+
readonly farmRoot: string;
|
|
1328
|
+
/** True when the resolved decision already matched what the farm's own manifest recorded, so nothing was written. */
|
|
1329
|
+
readonly noOp: boolean;
|
|
1330
|
+
/** The manifest the farm now carries — the newly written one, or the existing one on a no-op. */
|
|
1331
|
+
readonly manifest: FarmManifest;
|
|
1332
|
+
/** The fully resolved state, including the flattened cascade's launch flags, which the launcher goes on to use. */
|
|
1333
|
+
readonly resolved: ResolvedState;
|
|
1334
|
+
readonly adopted: readonly string[];
|
|
1335
|
+
readonly recovery: RecoveryResult;
|
|
1336
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
1337
|
+
readonly retainedPrevious?: string;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Brings one identity's farm into line with the cascade resolved for the current directory, and hands back everything the launcher needs to go on and spawn the real binary.
|
|
1341
|
+
*
|
|
1342
|
+
* The order below is fixed and each step depends on the one before it:
|
|
1343
|
+
*
|
|
1344
|
+
* 1. Take the per-identity lock, so two sessions under one identity in two directories cannot rewrite the same farm toward two different states at once.
|
|
1345
|
+
* 2. Finish or undo any swap a previous launch was killed in the middle of, so everything after this point sees a coherent farm.
|
|
1346
|
+
* 3. Read the previous manifest and, guided by it, list the parts of the still-live farm reconciliation is allowed to look at. This step only reads the farm; nothing mutates it.
|
|
1347
|
+
* 4. Adopt anything Claude Code wrote into a materialised directory back into the canonical `~/.claude`.
|
|
1348
|
+
* 5. **Then** build the fact manifest, from a canonical tree that now includes everything step 4 adopted. Building facts before adoption would make newly adopted data invisible to this resync and only visible on the next launch — the single most important ordering constraint in this module.
|
|
1349
|
+
* 6. Resolve the cascade and plan the farm, both of them pure functions over those facts.
|
|
1350
|
+
* 7. If the plan matches what the farm's manifest already records and step 4 adopted nothing, stop: no filesystem write happens at all. Launching repeatedly from the same directory with no configuration change is the overwhelmingly common case and it must stay cheap.
|
|
1351
|
+
* 8. Otherwise build a scratch tree, write its manifest, and swap it into place.
|
|
1352
|
+
* 9. Release the lock — before the real binary is spawned, never held across it.
|
|
1353
|
+
*/
|
|
1354
|
+
declare function resyncFarm(params: ResyncFarmParams): ResyncFarmResult;
|
|
1355
|
+
/** Turns a recovery result into the one diagnostic worth reporting about it, or nothing when there was nothing to recover. */
|
|
1356
|
+
declare function recoveryDiagnostics(recovery: RecoveryResult, identity: string): Diagnostic[];
|
|
1357
|
+
|
|
1358
|
+
/** The request facts a capture sees, shaped so both a real `IncomingMessage` and a test's plain object satisfy it. */
|
|
1359
|
+
interface CapturedRequestShape {
|
|
1360
|
+
readonly method?: string;
|
|
1361
|
+
readonly url?: string;
|
|
1362
|
+
readonly headers: IncomingHttpHeaders;
|
|
1363
|
+
}
|
|
1364
|
+
/** The callbacks one piped request's forwarding drives, all optional callers except, so a fake or a partial observer fits the same seam. */
|
|
1365
|
+
interface PassthroughObserver {
|
|
1366
|
+
readonly onRequestChunk: (chunk: Buffer) => void;
|
|
1367
|
+
readonly onResponse: (status: number, headers: IncomingHttpHeaders) => void;
|
|
1368
|
+
readonly onResponseChunk: (chunk: Buffer) => void;
|
|
1369
|
+
readonly onEnd: () => void;
|
|
1370
|
+
}
|
|
1371
|
+
/** The direction one tapped byte chunk flowed, named from the session's point of view. */
|
|
1372
|
+
type StreamDirection = "client-to-server" | "server-to-client";
|
|
1373
|
+
/** The byte-level tap for one terminated stream whose protocol the surface does not parse: every chunk both directions, then the end. */
|
|
1374
|
+
interface StreamTap {
|
|
1375
|
+
readonly onChunk: (direction: StreamDirection, chunk: Readonly<Buffer>) => void;
|
|
1376
|
+
readonly onEnd: () => void;
|
|
1377
|
+
}
|
|
1378
|
+
/** The tap the connect surface drives. `undefined` everywhere means "not capturing"; one object covers the whole door process. */
|
|
1379
|
+
interface ConnectCapture {
|
|
1380
|
+
/** Opens the byte-level tap for one host's terminated stream, whose chunks are recorded verbatim (text, or base64 when not valid UTF-8) under the same caps as piped bodies. Optional because a capture can be stream-silent by construction. */
|
|
1381
|
+
readonly tapStream?: (host: string) => StreamTap;
|
|
1382
|
+
/** Records one authenticated CONNECT target: the host and port only, since a blind tunnel's bytes are unreadable by construction. */
|
|
1383
|
+
readonly connect: (target: {
|
|
1384
|
+
readonly host: string;
|
|
1385
|
+
readonly port: number;
|
|
1386
|
+
}, intercepted: boolean) => void;
|
|
1387
|
+
/** Records one non-routed request's head and returns the observer its forwarding drives, so the capture correlates every later chunk with the request it belongs to. */
|
|
1388
|
+
readonly observePassthrough: (request: CapturedRequestShape) => PassthroughObserver;
|
|
1389
|
+
/** Records one HTTP upgrade request arriving on a terminated session. The surface destroys the socket afterwards either way, with or without a capture, matching Node's own no-listener behaviour exactly. */
|
|
1390
|
+
readonly upgrade: (request: CapturedRequestShape) => void;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* The front door's CONNECT surface, so an OAuth session can have agent-shim's routing AND Claude Code's Remote Control at once: Remote Control refuses any `ANTHROPIC_BASE_URL` other than the real API, but happily honours `HTTPS_PROXY`, so OAuth launches route at the proxy layer instead.
|
|
1395
|
+
*
|
|
1396
|
+
* Nothing is tunnelled or intercepted for a client that has not authenticated: every CONNECT request must present a live launch's capability as its proxy credential (`Proxy-Authorization: Basic`, which clients derive from the credential in the `HTTPS_PROXY` URL the launcher sets), or it is answered 407 before any target is dialled, so the port is no open proxy for other local processes. Pending and authenticated connections are bounded by `ConnectLimits`.
|
|
1397
|
+
*
|
|
1398
|
+
* Every authenticated CONNECT target is blind-tunnelled byte for byte EXCEPT the intercept hosts (Claude Code's own API and the claude.ai control plane), whose TLS this surface terminates with leaf certificates signed by a locally generated CA. On the API host's terminated session, the paths the front door routes (`/v1/`) are handed to the same ordered pipeline the provider listener serves (the client's Authorization header passes through untouched), and every other path there, and every path on the control plane's session, is piped by this surface to that session's own real upstream over TLS, so Remote Control's streaming, OAuth refreshes, and unknown endpoints bypass the pipeline entirely. When a `capture` is configured, those piped exchanges and every CONNECT target are recorded through it (see `./capture.ts`); the forwarding itself is identical either way.
|
|
1399
|
+
*/
|
|
1400
|
+
/** The API host: Claude Code's own upstream, the one host whose `/v1/` paths the routed pipeline serves. */
|
|
1401
|
+
declare const CONNECT_INTERCEPT_HOST = "api.anthropic.com";
|
|
1402
|
+
/**
|
|
1403
|
+
* Every CONNECT host whose TLS this surface terminates, each with its own leaf and its own HTTP parser: the API host above, and `platform.claude.com`, the claude.ai control plane where Remote Control's long-lived channel and its handshake live. Terminating the second one is what makes a Remote Control login rejection observable at all: its bytes cross this surface either way, and blind-tunnelling them hides exactly the exchange that failed. Only the API host's `/v1/` paths ever reach the routed pipeline; everything else on either session is piped to that session's own real upstream, so the pipeline's identification and authorisation are never asked to serve another host's traffic.
|
|
1404
|
+
*/
|
|
1405
|
+
declare const CONNECT_INTERCEPT_HOSTS: readonly string[];
|
|
1406
|
+
/** Paths under this prefix are what the routed pipeline serves; everything else on the terminated session goes to the real upstream. */
|
|
1407
|
+
declare const ROUTED_PATH_PREFIX = "/v1/";
|
|
1408
|
+
/**
|
|
1409
|
+
* The surface's connection deadlines and caps. Production passes `CONNECT_LIMITS`; tests pass small values so a deadline or a cap is reached in milliseconds.
|
|
1410
|
+
*
|
|
1411
|
+
* A connection is pending from accept until its CONNECT head has arrived, and authenticated once that head presented a live launch's capability. Pending connections are bounded twice, by `headDeadlineMs` and by `maxPendingHeads` (reaching the cap evicts the oldest pending connection rather than refusing the newest, so a squatter holding open half-sent heads cannot lock live launches out: a real client's head arrives in its first segment and is authenticated long before that many newer connections could push it out). Authenticated connections are bounded by `maxTunnels` and revoked when their launch ends; they have no idle timeout (see `CONNECT_LIMITS`).
|
|
1412
|
+
*/
|
|
1413
|
+
interface ConnectLimits {
|
|
1414
|
+
/** How long a connection may take to deliver its whole CONNECT head before it is answered 408 and closed. */
|
|
1415
|
+
readonly headDeadlineMs: number;
|
|
1416
|
+
/** How many connections may be waiting for their head at once; one more evicts the oldest. */
|
|
1417
|
+
readonly maxPendingHeads: number;
|
|
1418
|
+
/** How many authenticated connections (blind tunnels and terminated sessions together) may be open at once; one more is answered 503 and closed. */
|
|
1419
|
+
readonly maxTunnels: number;
|
|
1420
|
+
/** How often every authenticated connection's capability is re-checked, closing the connections of any launch that has since ended. */
|
|
1421
|
+
readonly revalidateMs: number;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* The production limits, each derived from what the surface's real clients do.
|
|
1425
|
+
*
|
|
1426
|
+
* `headDeadlineMs`: every client checked (Claude Code, curl, Node's own proxy support, Python's urllib) sends its whole CONNECT head, credential included, the moment the TCP connection opens, without waiting for anything from the proxy, so on loopback the head arrives within milliseconds. The deadline only has to outlast a door process stalled by a loaded machine, and ten seconds is three orders of magnitude above any such stall while still ending a slowloris connection quickly.
|
|
1427
|
+
*
|
|
1428
|
+
* `maxPendingHeads`: since a real head arrives in the first segment, the pending set at any instant holds only connections opened in the same instant. The largest real burst is a package manager or similar running inside a session, which opens its parallel downloads through HTTPS_PROXY at once; 64 covers the defaults of npm (`maxsockets` 15) and pnpm (`network-concurrency` 16) several times over, and eviction means even a larger burst only costs a retry, never a lockout.
|
|
1429
|
+
*
|
|
1430
|
+
* `maxTunnels`: each authenticated connection holds two descriptors (the client's and the upstream's). Node raises its soft descriptor limit to the hard limit at start, and 4096 is the smallest hard limit common Linux distributions ship (macOS's is far higher), so 1024 tunnels keep the surface to half of it, leaving the provider and direct listeners room to serve rather than all three failing with EMFILE. It is still many times what concurrent sessions hold: each keeps a handful of tunnels plus whatever bursts its tools make.
|
|
1431
|
+
*
|
|
1432
|
+
* `revalidateMs`: the supervisor prunes dead launches from the registry once per `FRONTDOOR_POLL_MS`, so re-checking more often than that could never find anything newly dead.
|
|
1433
|
+
*
|
|
1434
|
+
* There is deliberately no idle timeout on an authenticated connection: Remote Control and other long-lived streams can sit silent for as long as the far end chooses between events, at an interval the surface cannot know, so any idle timeout would cut legitimate sessions. Authenticated connections are bounded instead by who can open them (only a live launch's process tree holds the capability), by `maxTunnels`, and by revocation: a launch that ends loses every connection it opened at the next revalidation.
|
|
1435
|
+
*/
|
|
1436
|
+
declare const CONNECT_LIMITS: ConnectLimits;
|
|
1437
|
+
/** One parsed CONNECT authority, the host lowercased and the port defaulted. */
|
|
1438
|
+
interface ConnectTarget {
|
|
1439
|
+
readonly host: string;
|
|
1440
|
+
readonly port: number;
|
|
1441
|
+
}
|
|
1442
|
+
/** Parses a CONNECT authority (`host:port`, or a bare `host` meaning 443), or undefined when it names no host or a non-numeric port. */
|
|
1443
|
+
declare function parseConnectTarget(authority: string): ConnectTarget | undefined;
|
|
1444
|
+
/** Whether a CONNECT target's host (port already stripped) is one whose TLS this proxy terminates. */
|
|
1445
|
+
declare function isInterceptedHost(host: string, interceptHosts: readonly string[]): boolean;
|
|
1446
|
+
/** Whether a request path on the terminated session is one the routed pipeline serves. The query string stays part of the path: `/v1/messages?beta=true` is still the pipeline's. */
|
|
1447
|
+
declare function servedByPipeline(path: string | undefined): boolean;
|
|
1448
|
+
/**
|
|
1449
|
+
* Headers a forwarded HTTP message keeps: everything except the hop-by-hop set, which belongs to whichever connection carried it and must not leak onto the next one. The `Connection` header's own value can name further hop-by-hop headers (RFC 9110 section 7.6.1), so those go too. Returns a fresh object; the input is the live parser's map and is never mutated.
|
|
1450
|
+
*/
|
|
1451
|
+
declare function forwardableHeaders(headers: Readonly<IncomingHttpHeaders>): IncomingHttpHeaders;
|
|
1452
|
+
/** A CA certificate and its private key, both as PEM. */
|
|
1453
|
+
interface CaMaterial {
|
|
1454
|
+
readonly certPem: string;
|
|
1455
|
+
readonly keyPem: string;
|
|
1456
|
+
}
|
|
1457
|
+
/** A TLS leaf certificate and its private key, both as PEM. */
|
|
1458
|
+
interface LeafCert {
|
|
1459
|
+
readonly certPem: string;
|
|
1460
|
+
readonly keyPem: string;
|
|
1461
|
+
}
|
|
1462
|
+
/** Generates the local CA: one RSA keypair, self-signed, CA-only by basicConstraints and keyUsage, with a random serial. Built on `node:crypto` alone (see `x509.ts`), no openssl, so the same code works on every platform agent-shim ships to. */
|
|
1463
|
+
declare function generateCa(now: Readonly<Date>): CaMaterial;
|
|
1464
|
+
/**
|
|
1465
|
+
* Mints a TLS leaf signed by the CA: its own RSA keypair, server-auth usage only, the first name as the subject's common name and every name in the SAN. A name that parses as an IP address goes in as an IP SAN, because TLS clients match an IP host only against IP SANs, never against a DNS SAN spelling the same digits.
|
|
1466
|
+
*/
|
|
1467
|
+
declare function mintLeaf(ca: CaMaterial, names: readonly string[], now: Readonly<Date>): LeafCert;
|
|
1468
|
+
/** Mints each host's leaf once from the CA and reuses it, because generating a 2048-bit RSA keypair costs noticeable time and nothing about the certificate changes between sessions. */
|
|
1469
|
+
declare function createLeafCache(ca: CaMaterial, now: () => Date): (host: string) => LeafCert;
|
|
1470
|
+
/**
|
|
1471
|
+
* Persistence for the CA: generated once on the machine's first headroom start and reused forever after, because regenerating it would invalidate every child's `NODE_EXTRA_CA_CERTS` pointing at the old file. The real implementation writes the key with mode 0600; injected fakes keep it in memory.
|
|
1472
|
+
*/
|
|
1473
|
+
interface ConnectCertStore {
|
|
1474
|
+
/** The persisted CA, or undefined when none exists yet (or only half of it does: a cert without its key can sign nothing, so it counts as absent). */
|
|
1475
|
+
readonly loadCa: () => CaMaterial | undefined;
|
|
1476
|
+
/** Persists both halves of the CA, the key unreadable to other users. */
|
|
1477
|
+
readonly writeCa: (ca: CaMaterial) => void;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Loads the persisted CA, generating and persisting one when no usable CA exists. A stored pair that `node:crypto` cannot parse is treated as absent and replaced: a truncated write is the one failure mode a regeneration can actually repair, and every child that trusted the old file needs a new `NODE_EXTRA_CA_CERTS` anyway once its CA stops parsing.
|
|
1481
|
+
*/
|
|
1482
|
+
declare function ensureCa(store: ConnectCertStore, generate: () => CaMaterial): CaMaterial;
|
|
1483
|
+
/** Where one forwarded request goes, and how: the connect surface's own forwards go to the real upstream over TLS, while a local hop over plain HTTP is what tests redirect to. */
|
|
1484
|
+
interface ConnectForwardTarget {
|
|
1485
|
+
readonly host: string;
|
|
1486
|
+
readonly port: number;
|
|
1487
|
+
readonly tls: boolean;
|
|
1488
|
+
/** The SNI name to present; defaults to `host`. Separated so a test can connect to a local fake while presenting the production hostname. */
|
|
1489
|
+
readonly servername?: string;
|
|
1490
|
+
/** Extra trust anchors for the target's certificate, when it is not system-trusted (a test's local fake upstream). */
|
|
1491
|
+
readonly ca?: readonly string[];
|
|
1492
|
+
/** Overrides certificate verification; production leaves it at the Node default (verify against system trust or `ca`). */
|
|
1493
|
+
readonly rejectUnauthorized?: boolean;
|
|
1494
|
+
}
|
|
1495
|
+
/** Handles one parsed HTTP request on a terminated TLS session. */
|
|
1496
|
+
type ConnectRequestHandler = (request: IncomingMessage, response: ServerResponse) => void;
|
|
1497
|
+
/** A bound loopback listener and how to stop it. */
|
|
1498
|
+
interface ConnectListenerHandle {
|
|
1499
|
+
readonly port: number;
|
|
1500
|
+
/** Stops accepting, ends every live connection, and resolves once the port is released. */
|
|
1501
|
+
readonly close: () => Promise<void>;
|
|
1502
|
+
}
|
|
1503
|
+
/** Presents one leaf's server-side TLS on CONNECTed sockets. */
|
|
1504
|
+
interface TlsAcceptor {
|
|
1505
|
+
/** Starts the TLS handshake on a socket that has just received its CONNECT response, carrying `context` to the handshake's completion untouched: whatever the caller knows about this connection (its tunnel's capability) reaches `onSecure` without the acceptor interpreting it. */
|
|
1506
|
+
readonly accept: (socket: net.Socket, context: string) => void;
|
|
1507
|
+
/** Drops every session this acceptor is terminating. */
|
|
1508
|
+
readonly close: () => void;
|
|
1509
|
+
}
|
|
1510
|
+
/** Parses HTTP over terminated sockets and serves each request through the handler, keep-alive included. */
|
|
1511
|
+
interface HttpParserSession {
|
|
1512
|
+
readonly serve: (socket: net.Socket) => void;
|
|
1513
|
+
readonly close: () => void;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Every effect the MITM proxy performs, injected so the routing decisions and lifecycle run against fakes in unit tests (the SupervisorPorts pattern): TCP listening, TLS termination, HTTP parsing, plain TCP tunnels, and request forwarding. The real implementation is `realConnectEffects`; the TLS round-trip test uses it with redirected targets.
|
|
1517
|
+
*/
|
|
1518
|
+
interface ConnectEffects {
|
|
1519
|
+
/** Binds a loopback TCP listener; `preferredPort` is tried first and any free port used when it is taken (bind, do not probe: check-then-bind races). Resolves with the bound port and a close handle. */
|
|
1520
|
+
readonly listenLoopback: (preferredPort: number | undefined, onSocket: (socket: net.Socket) => void) => Promise<ConnectListenerHandle>;
|
|
1521
|
+
/** Builds the TLS terminator for one leaf, handing each successfully handshaked session to `onSecure` together with the context its `accept` call carried. `alpnProtocols`, when given, is advertised during the handshake, which is how a tap host's session can offer HTTP/2 the way its real server does. */
|
|
1522
|
+
readonly createTlsAcceptor: (leaf: LeafCert, onSecure: (secure: net.Socket, context: string) => void, alpnProtocols?: readonly string[]) => TlsAcceptor;
|
|
1523
|
+
/** Opens a TLS connection to a real host, presenting its name as SNI, for a tap session's upstream half. `clientAlpn`, when given, is the protocol the client negotiated with the surface and the only one offered upstream, so the tap never changes the channel's protocol. */
|
|
1524
|
+
readonly connectTlsUpstream: (host: string, port: number, clientAlpn: string | undefined) => Promise<net.Socket>;
|
|
1525
|
+
/** Builds the HTTP parser bound to one request handler. `onUpgrade`, when given, owns every HTTP upgrade request arriving on a terminated session, socket and already-read bytes included; a session without one lets Node destroy the upgrade, its own no-listener behaviour. */
|
|
1526
|
+
readonly createHttpSession: (handler: ConnectRequestHandler, onUpgrade?: (request: IncomingMessage, socket: Duplex, head: Buffer) => void) => HttpParserSession;
|
|
1527
|
+
/** Opens a raw TCP connection for a blind tunnel. */
|
|
1528
|
+
readonly connectTcp: (host: string, port: number) => Promise<net.Socket>;
|
|
1529
|
+
/** Streams one request to `target` and the response back, never buffering a body. When `observer` is given, the forwarding drives it with additive listeners (request and response chunks arrive to both the pipe and the observer) so capturing never changes the forwarding's own flow control. */
|
|
1530
|
+
readonly forwardHttp: (target: ConnectForwardTarget, request: IncomingMessage, response: ServerResponse, observer?: PassthroughObserver) => void;
|
|
1531
|
+
}
|
|
1532
|
+
/** Everything the connect surface needs to route, resolved before it starts. */
|
|
1533
|
+
interface ConnectServerConfig {
|
|
1534
|
+
/** The CONNECT hosts whose TLS get terminated, each with its own leaf, its own HTTP parser and its own upstream. */
|
|
1535
|
+
readonly interceptHosts: readonly string[];
|
|
1536
|
+
/**
|
|
1537
|
+
* The one intercept host whose `/v1/` paths `serveRouted` handles: the API host, whose pipeline identification, authorisation and headroom hop are about Anthropic sessions and no other host's traffic.
|
|
1538
|
+
*/
|
|
1539
|
+
readonly routedHost: string;
|
|
1540
|
+
/**
|
|
1541
|
+
* Serves one routed path (`/v1/...`) from the terminated session: the same ordered pipeline the provider listener hands requests to, so an OAuth session and a provider session run identical identification, middleware and routing.
|
|
1542
|
+
*/
|
|
1543
|
+
readonly serveRouted: ConnectRequestHandler;
|
|
1544
|
+
/** The leaf to terminate each intercept host with, minted and cached per host. */
|
|
1545
|
+
readonly leafFor: (host: string) => LeafCert;
|
|
1546
|
+
/** Where non-routed paths on one host's terminated session are piped: that host's own real upstream over TLS. */
|
|
1547
|
+
readonly upstreamFor: (host: string) => ConnectForwardTarget;
|
|
1548
|
+
/** Whether a capability presented as a CONNECT request's proxy credential belongs to a live launch: the same constant-time check against the same registry the routed pipeline's admission makes, read fresh on every call because launches come and go. */
|
|
1549
|
+
readonly isLiveCapability: (token: string) => boolean;
|
|
1550
|
+
/**
|
|
1551
|
+
* Binds a second listener that serves the intercept hosts' sessions to connections arriving pre-established, with no CONNECT handshake to authenticate: what an operating-system redirect (a pf rule sending the real hosts' address to this port) delivers. Only the API host's session is reachable this way, never a tap host's, because a redirect can name only addresses and the control plane shares the API host's address today; if that ever splits, the transparent listener must not follow it blindly. There is no capability to check on this surface: the connection carried no proxy credential, so every request rides the pipeline anonymously, admitted the way a tunnel's own header-less requests are (by nothing, which is exactly what they present), and the surface exists solely for clients that ignore proxies and would otherwise be invisible to the door entirely.
|
|
1552
|
+
*/
|
|
1553
|
+
readonly transparentPort?: number;
|
|
1554
|
+
/**
|
|
1555
|
+
* The capability the transparent surface's sessions carry: a real, registered token minted by the door for its own pid, so header-less traffic that arrives by redirect is admitted and attributed like any launch's instead of being refused for a credential it cannot possibly present. Absent along with `transparentPort`.
|
|
1556
|
+
*/
|
|
1557
|
+
readonly transparentCapability?: string;
|
|
1558
|
+
/** The intercept hosts that are never HTTP-parsed: terminated, tapped at the byte level, and pumped to their real host. */
|
|
1559
|
+
readonly tapHosts: readonly string[];
|
|
1560
|
+
/** The connection deadlines and caps; production passes `CONNECT_LIMITS`. */
|
|
1561
|
+
readonly limits: ConnectLimits;
|
|
1562
|
+
/** The diagnostic tap, when capturing is enabled; absent means nothing is recorded. */
|
|
1563
|
+
readonly capture?: ConnectCapture;
|
|
1564
|
+
}
|
|
1565
|
+
/** A running connect surface. */
|
|
1566
|
+
interface ConnectServerHandle {
|
|
1567
|
+
readonly port: number;
|
|
1568
|
+
/** The transparent listener's port, when one was requested: what an operating-system redirect should point at. */
|
|
1569
|
+
readonly transparentPort?: number;
|
|
1570
|
+
/** Stops the listener, drops every live tunnel and terminated session, and resolves once the port is released. */
|
|
1571
|
+
readonly close: () => Promise<void>;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Starts the connect surface: binds the listener, mints the intercept host's leaf, and wires the per-connection routing. Resolves once the listener is bound.
|
|
1575
|
+
*
|
|
1576
|
+
* The connection flow: read the CONNECT head within the deadline (never more of the stream than that, so an early ClientHello glued to the head is pushed back with `unshift` and still seen by whatever consumes the socket next), authenticate it, then either blind-tunnel the target or terminate its TLS and parse HTTP on the session. Every network effect flows through `effects`; nothing here touches the network or filesystem itself.
|
|
1577
|
+
*/
|
|
1578
|
+
declare function startConnectServer(config: ConnectServerConfig, effects: ConnectEffects, preferredPort: number | undefined): Promise<ConnectServerHandle>;
|
|
1579
|
+
/** The real `ConnectCertStore` on the filesystem: `ca.pem` world-readable (it is public material), `ca.key` mode 0600 exactly, chmod'ed after the write because a create's mode bits pass through the process umask. */
|
|
1580
|
+
declare function realConnectCertStore(paths: {
|
|
1581
|
+
readonly frontdoorCaDir: string;
|
|
1582
|
+
readonly frontdoorCaCertFile: string;
|
|
1583
|
+
readonly frontdoorCaKeyFile: string;
|
|
1584
|
+
}): ConnectCertStore;
|
|
1585
|
+
|
|
1586
|
+
declare function realConnectEffects(): ConnectEffects;
|
|
1587
|
+
|
|
1588
|
+
/** The filesystem operations headroom coordination needs: the same shape as `FarmFs`, injected so every decision over state, sessions, and the start lock runs against an in-memory fake in tests. */
|
|
1589
|
+
type HeadroomFs = Pick<FarmFs, "mkdirp" | "mkdirPrivate" | "readFileUtf8" | "writeFileUtf8" | "writeFilePrivate" | "writeFileExclusive" | "readdir" | "removeRecursive">;
|
|
1590
|
+
|
|
1591
|
+
/** The outcome of authenticating the listener a front-door state file names. */
|
|
1592
|
+
type ListenerVerdict = {
|
|
1593
|
+
readonly ok: true;
|
|
1594
|
+
} | {
|
|
1595
|
+
readonly ok: false;
|
|
1596
|
+
readonly reason: string;
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
/** Raised when the front door cannot be brought up in time or has recorded a fatal error: a routed launch has no other way to reach its provider, so it must fail rather than start a session that can never answer. */
|
|
1600
|
+
declare class FrontDoorStartError extends CliError {
|
|
1601
|
+
constructor(message: string);
|
|
1602
|
+
}
|
|
1603
|
+
/** Every effect the ensure step performs, injected so it runs against fakes in tests. */
|
|
1604
|
+
interface EnsureFrontDoorPorts {
|
|
1605
|
+
readonly fs: HeadroomFs;
|
|
1606
|
+
/** Zombie-aware liveness (see `realIsProcessRunning`). */
|
|
1607
|
+
readonly isRunning: (pid: number) => boolean;
|
|
1608
|
+
readonly now: () => number;
|
|
1609
|
+
readonly sleep: (ms: number) => void;
|
|
1610
|
+
/** Spawns the detached front-door supervisor, returning its pid. */
|
|
1611
|
+
readonly spawnSupervisor: (paths: LayoutPaths) => number;
|
|
1612
|
+
/** Authenticates the provider listener on `port`: a TLS handshake chaining to agent-shim's CA and a healthy answer, with no capability sent (see `probeFrontDoorSync`). */
|
|
1613
|
+
readonly verifyListener: (port: number) => ListenerVerdict;
|
|
1614
|
+
/** Asks a running front-door supervisor to exit, without waiting for it to: the caller polls `isRunning`. Only ever called for a pid whose recorded listener has just authenticated as agent-shim's own. */
|
|
1615
|
+
readonly stopSupervisor: (pid: number) => void;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Brings the front door up for this launch, or finds it serving, and registers this launcher pid in its session registry: the fact that keeps the door from idling out while this session lives, and the record that holds this launch's capability token. The same lock-and-poll coordination as headroom's and the old codex daemon's ensure: the exclusive-create start lock decides which of several concurrent launches spawns the one supervisor, everyone waits on state.json, and a lock whose holder died is removed and retried. Ready means state names a live supervisor and both listeners' ports, which the supervisor writes only after both have bound (and the provider listener has answered its own health probe).
|
|
1619
|
+
*
|
|
1620
|
+
* A live pid in state proves nothing about who holds the port it names: the door may have crashed and left the port to anyone, or the pid may have been reused by an unrelated process. So before anything is registered or returned, the provider listener is authenticated over TLS against agent-shim's CA. A listener that fails (wrong certificate, nothing answering, a bad answer) is treated as a stale or hostile owner: its pid is distrusted for the rest of this call and a replacement supervisor is spawned, which binds a free port when the sticky one is held and is then verified in turn. If a supervisor this call spawned also fails verification, the launch is refused naming the reason and the daemon log; no capability is written and no address is handed to the child.
|
|
1621
|
+
*/
|
|
1622
|
+
declare function ensureFrontDoor(params: {
|
|
1623
|
+
readonly paths: LayoutPaths;
|
|
1624
|
+
readonly launcherPid: number;
|
|
1625
|
+
readonly ports: EnsureFrontDoorPorts;
|
|
1626
|
+
}): {
|
|
1627
|
+
readonly port: number;
|
|
1628
|
+
readonly connectPort: number;
|
|
1629
|
+
readonly token: string;
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
/** A bound front-door listener in this process, and how to stop it. */
|
|
1633
|
+
interface FrontDoorListenerHandle {
|
|
1634
|
+
readonly port: number;
|
|
1635
|
+
/** Stops accepting, ends every live connection, and resolves once the port is released. */
|
|
1636
|
+
readonly close: () => Promise<void>;
|
|
1637
|
+
}
|
|
1638
|
+
/** Every effect the front-door supervisor performs, injected so its whole lifecycle runs against fakes: no real process, port, clock or listener in a unit test. */
|
|
1639
|
+
interface FrontDoorSupervisorPorts {
|
|
1640
|
+
readonly fs: HeadroomFs;
|
|
1641
|
+
readonly paths: LayoutPaths;
|
|
1642
|
+
readonly ownPid: number;
|
|
1643
|
+
readonly now: () => number;
|
|
1644
|
+
/** An event-loop-yielding wait, for the same reason headroom's is: nothing else would ever run between ticks. */
|
|
1645
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
1646
|
+
/** Zombie-aware liveness, the notion every coordination decision in this project uses. */
|
|
1647
|
+
readonly isRunning: (pid: number) => boolean;
|
|
1648
|
+
/**
|
|
1649
|
+
* Starts the provider listener in this process on `preferredPort` (falling back to any free port when the sticky one is taken, decided at bind time rather than by a racy check-then-bind probe) and resolves once it has bound and answered its own health probe over TLS. This is the HTTPS listener every provider session's base URL points at, serving a loopback leaf signed by agent-shim's CA.
|
|
1650
|
+
*/
|
|
1651
|
+
readonly startProviderListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
1652
|
+
/**
|
|
1653
|
+
* Starts the CONNECT surface in this process on `preferredPort` (same bind-time fallback): the TLS-terminating listener an OAuth session's HTTPS_PROXY points at. Bound before any state names it, so an OAuth launch never finds one listener up without the other.
|
|
1654
|
+
*/
|
|
1655
|
+
readonly startConnectListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
1656
|
+
/**
|
|
1657
|
+
* Starts the direct listener on `preferredPort` (same bind-time fallback): the same routes as the provider listener, minus the headroom hop, over plain HTTP because headroom is what connects to it. Headroom forwards routed traffic back here, which is what keeps the hop from looping, and the port is sticky like the others because headroom's allowlist admits its exact origin. It admits only the hop's own requests and holds no credential until it redeems one from the hop's custody.
|
|
1658
|
+
*/
|
|
1659
|
+
readonly startDirectListener: (preferredPort: number | undefined) => Promise<FrontDoorListenerHandle>;
|
|
1660
|
+
readonly log: (line: string) => void;
|
|
1661
|
+
}
|
|
1662
|
+
/** Options for `runFrontDoorSupervisor`. */
|
|
1663
|
+
interface RunFrontDoorSupervisorOptions {
|
|
1664
|
+
/** Bounds the loop's iterations so a test can observe steady state; such a run returns `FRONTDOOR_SUPERVISOR_STILL_RUNNING`. */
|
|
1665
|
+
readonly tickLimit?: number;
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Runs the front-door supervisor: binds all three listeners (the HTTPS provider listener, the CONNECT surface and the plain-HTTP direct listener, each on its sticky port), records the serving state, and shuts down after `idleShutdownMinutes` with an empty session registry. Returns the exit code: 0 for an idle shutdown, 1 for a fatal start failure.
|
|
1669
|
+
*
|
|
1670
|
+
* The listeners live in this process, so there is no child to keep alive: a crash of this process is a crash of every routed session's door at once, and recovery is the next launch's ensure spawning a replacement on the same sticky ports, which is exactly the failure model the issue asks to settle with tests. Keeping the headroom daemon alive is the headroom supervisor's job, not this one's; the two daemons idle out independently.
|
|
1671
|
+
*/
|
|
1672
|
+
declare function runFrontDoorSupervisor(idleShutdownMinutes: number, ports: FrontDoorSupervisorPorts, options?: RunFrontDoorSupervisorOptions): Promise<number>;
|
|
1673
|
+
|
|
1674
|
+
/**
|
|
1675
|
+
* The latest state for one identity, rewritten atomically after each recorded response: the snapshot file other tools read (`~/.agent-shim/usage/snapshots/<identity>.json`) without shelling out to `agent-shim`.
|
|
1676
|
+
*/
|
|
1677
|
+
declare const UsageSnapshotSchema: z.ZodObject<{
|
|
1678
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
1679
|
+
identity: z.ZodString;
|
|
1680
|
+
updatedAt: z.ZodISODateTime;
|
|
1681
|
+
account: z.ZodOptional<z.ZodObject<{
|
|
1682
|
+
accountUuid: z.ZodOptional<z.ZodString>;
|
|
1683
|
+
emailAddress: z.ZodOptional<z.ZodString>;
|
|
1684
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
1685
|
+
organizationUuid: z.ZodOptional<z.ZodString>;
|
|
1686
|
+
organizationName: z.ZodOptional<z.ZodString>;
|
|
1687
|
+
organizationType: z.ZodOptional<z.ZodString>;
|
|
1688
|
+
organizationRole: z.ZodOptional<z.ZodString>;
|
|
1689
|
+
workspaceRole: z.ZodOptional<z.ZodString>;
|
|
1690
|
+
billingType: z.ZodOptional<z.ZodString>;
|
|
1691
|
+
seatTier: z.ZodOptional<z.ZodString>;
|
|
1692
|
+
organizationRateLimitTier: z.ZodOptional<z.ZodString>;
|
|
1693
|
+
userRateLimitTier: z.ZodOptional<z.ZodString>;
|
|
1694
|
+
hasExtraUsageEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
1695
|
+
subscriptionCreatedAt: z.ZodOptional<z.ZodString>;
|
|
1696
|
+
accountCreatedAt: z.ZodOptional<z.ZodString>;
|
|
1697
|
+
}, z.core.$strict>>;
|
|
1698
|
+
providers: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
1699
|
+
lastRequestAt: z.ZodISODateTime;
|
|
1700
|
+
lastStatus: z.ZodNumber;
|
|
1701
|
+
lastModel: z.ZodOptional<z.ZodString>;
|
|
1702
|
+
rateLimit: z.ZodOptional<z.ZodObject<{
|
|
1703
|
+
observedAt: z.ZodISODateTime;
|
|
1704
|
+
headers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
1705
|
+
unified: z.ZodOptional<z.ZodObject<{
|
|
1706
|
+
status: z.ZodOptional<z.ZodString>;
|
|
1707
|
+
fiveHour: z.ZodOptional<z.ZodObject<{
|
|
1708
|
+
utilization: z.ZodOptional<z.ZodNumber>;
|
|
1709
|
+
resetsAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1710
|
+
status: z.ZodOptional<z.ZodString>;
|
|
1711
|
+
}, z.core.$strict>>;
|
|
1712
|
+
sevenDay: z.ZodOptional<z.ZodObject<{
|
|
1713
|
+
utilization: z.ZodOptional<z.ZodNumber>;
|
|
1714
|
+
resetsAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1715
|
+
status: z.ZodOptional<z.ZodString>;
|
|
1716
|
+
}, z.core.$strict>>;
|
|
1717
|
+
representativeClaim: z.ZodOptional<z.ZodString>;
|
|
1718
|
+
resetAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1719
|
+
overageStatus: z.ZodOptional<z.ZodString>;
|
|
1720
|
+
}, z.core.$strict>>;
|
|
1721
|
+
}, z.core.$strict>>;
|
|
1722
|
+
lastLimit: z.ZodOptional<z.ZodObject<{
|
|
1723
|
+
kind: z.ZodEnum<{
|
|
1724
|
+
"rate-limited": "rate-limited";
|
|
1725
|
+
"quota-exhausted": "quota-exhausted";
|
|
1726
|
+
}>;
|
|
1727
|
+
retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
|
|
1728
|
+
resetAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1729
|
+
window: z.ZodOptional<z.ZodString>;
|
|
1730
|
+
evidence: z.ZodArray<z.ZodString>;
|
|
1731
|
+
observedAt: z.ZodISODateTime;
|
|
1732
|
+
status: z.ZodNumber;
|
|
1733
|
+
}, z.core.$strict>>;
|
|
1734
|
+
quota: z.ZodOptional<z.ZodObject<{
|
|
1735
|
+
observedAt: z.ZodISODateTime;
|
|
1736
|
+
source: z.ZodString;
|
|
1737
|
+
level: z.ZodOptional<z.ZodString>;
|
|
1738
|
+
windows: z.ZodArray<z.ZodObject<{
|
|
1739
|
+
measures: z.ZodString;
|
|
1740
|
+
periodMs: z.ZodOptional<z.ZodNumber>;
|
|
1741
|
+
period: z.ZodOptional<z.ZodString>;
|
|
1742
|
+
utilization: z.ZodNumber;
|
|
1743
|
+
resetsAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1744
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
1745
|
+
used: z.ZodOptional<z.ZodNumber>;
|
|
1746
|
+
remaining: z.ZodOptional<z.ZodNumber>;
|
|
1747
|
+
}, z.core.$strict>>;
|
|
1748
|
+
}, z.core.$strict>>;
|
|
1749
|
+
}, z.core.$strict>>;
|
|
1750
|
+
}, z.core.$strict>;
|
|
1751
|
+
type UsageSnapshot = z.infer<typeof UsageSnapshotSchema>;
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* The read side of the usage store: pure over an injected filesystem, so the CLI, the library surface and tests all read the same way, and nothing here writes.
|
|
1755
|
+
*/
|
|
1756
|
+
/** The filesystem reads the usage store's readers need. */
|
|
1757
|
+
type UsageReadFs = Pick<FarmFs, "readFileUtf8" | "readdir">;
|
|
1758
|
+
/** Raised for a snapshot file that exists but does not hold a snapshot of this schema version. */
|
|
1759
|
+
declare class UsageSnapshotError extends Error {
|
|
1760
|
+
readonly file: string;
|
|
1761
|
+
constructor(file: string, detail: string);
|
|
1762
|
+
}
|
|
1763
|
+
/** The path of an identity's snapshot. Throws for an invalid identity name, which could otherwise name a path outside the snapshots directory. */
|
|
1764
|
+
declare function snapshotPath(snapshotsDir: string, identity: string): string;
|
|
1765
|
+
/** Reads one identity's snapshot: undefined when it has none yet, `UsageSnapshotError` when the file is there but unreadable. */
|
|
1766
|
+
declare function readUsageSnapshot(fs: Pick<FarmFs, "readFileUtf8">, snapshotsDir: string, identity: string): UsageSnapshot | undefined;
|
|
1767
|
+
/** Reads every identity's snapshot, by identity name. */
|
|
1768
|
+
declare function listUsageSnapshots(fs: UsageReadFs, snapshotsDir: string): readonly UsageSnapshot[];
|
|
1769
|
+
|
|
1770
|
+
/** Raised when the daemon cannot be brought up in time, or reports a fatal `lastError`: a launch that asked for headroom must never silently fall back to a direct connection. */
|
|
1771
|
+
declare class HeadroomStartError extends CliError {
|
|
1772
|
+
constructor(message: string);
|
|
1773
|
+
}
|
|
1774
|
+
/** Every effect the ensure step performs, injected so it runs against fakes in tests. */
|
|
1775
|
+
interface EnsureHeadroomPorts {
|
|
1776
|
+
readonly fs: HeadroomFs;
|
|
1777
|
+
/** Zombie-aware liveness: a supervisor or daemon that exited without being reaped still answers signal 0 as alive, but will never serve a request or write state, so it must read as dead here (see `realIsProcessRunning`). */
|
|
1778
|
+
readonly isRunning: (pid: number) => boolean;
|
|
1779
|
+
readonly now: () => number;
|
|
1780
|
+
readonly sleep: (ms: number) => void;
|
|
1781
|
+
/** Spawns the detached supervisor process that owns the headroom daemon, returning its pid. */
|
|
1782
|
+
readonly spawnSupervisor: (paths: LayoutPaths) => number;
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Brings the headroom daemon up for this launch, or finds it already running, and registers this launcher pid in its session registry (the fact that keeps the daemon alive and defers drift restarts until this launch is done).
|
|
1786
|
+
*
|
|
1787
|
+
* The exclusive-create start lock decides which of several concurrent launches spawns the one supervisor: its holder spawns and keeps waiting like everyone else, everyone else waits on state.json, and a lock whose holder has died is removed and retried. Readiness is "state names a live supervisor, a live headroom pid, and a port", which the supervisor only writes after its own `/readyz` probe has passed, so polling state alone never mistakes a bound-but-not-ready port for a usable daemon.
|
|
1788
|
+
*
|
|
1789
|
+
* Throws `HeadroomStartError` (naming the daemon log path and any recorded `lastError`) when the daemon is not up within `HEADROOM_START_TIMEOUT_MS`, or when a live supervisor has recorded a fatal error.
|
|
1790
|
+
*/
|
|
1791
|
+
declare function ensureHeadroom(params: {
|
|
1792
|
+
readonly paths: LayoutPaths;
|
|
1793
|
+
readonly launcherPid: number;
|
|
1794
|
+
readonly ports: EnsureHeadroomPorts;
|
|
1795
|
+
}): {
|
|
1796
|
+
readonly port: number;
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
/** The token-saving settings of the `headroom` config block, without its install and lifecycle fields (`source`, `idleShutdownMinutes`). */
|
|
1800
|
+
type HeadroomSettings = Pick<HeadroomGlobalConfig, "mode" | "targetRatio" | "ccr" | "rolloutChannel" | "interceptToolResults" | "readMaturation">;
|
|
1801
|
+
|
|
1802
|
+
/** Everything the supervisor needs to run the daemon, resolved from the global config and the filesystem before it starts. */
|
|
1803
|
+
interface HeadroomSupervisorConfig {
|
|
1804
|
+
/** The install spec as configured (`headroom.source`), already defaulted. */
|
|
1805
|
+
readonly source: string;
|
|
1806
|
+
/** `headroom.idleShutdownMinutes`, already defaulted. */
|
|
1807
|
+
readonly idleShutdownMinutes: number;
|
|
1808
|
+
/** The token-saving settings (`mode`, `targetRatio`, `ccr`, the rollout channel and its opt-ins), as configured; unset fields leave headroom on its own defaults. */
|
|
1809
|
+
readonly settings: HeadroomSettings;
|
|
1810
|
+
}
|
|
1811
|
+
/** How the supervisor resolves the configured source against its defaults. */
|
|
1812
|
+
declare function resolveSupervisorConfig(configured: Readonly<HeadroomGlobalConfig>): HeadroomSupervisorConfig;
|
|
1813
|
+
/**
|
|
1814
|
+
* Every effect the supervisor performs, injected so the whole lifecycle is testable against fakes: no real process, port, clock, or HTTP call ever happens in a unit test.
|
|
1815
|
+
*/
|
|
1816
|
+
interface SupervisorPorts {
|
|
1817
|
+
readonly fs: HeadroomFs;
|
|
1818
|
+
readonly paths: LayoutPaths;
|
|
1819
|
+
readonly ownPid: number;
|
|
1820
|
+
readonly now: () => number;
|
|
1821
|
+
/**
|
|
1822
|
+
* Waits `ms`, yielding to the event loop while it does. Deliberately NOT a synchronous Atomics.wait-style sleep: the real supervisor learns of its child's death through the ChildProcess exit event, and a blocking sleep would stop the event loop from ever delivering it (the exact failure that left an unreaped zombie answering liveness checks as alive).
|
|
1823
|
+
*/
|
|
1824
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
1825
|
+
/**
|
|
1826
|
+
* Zombie-aware liveness, and for a daemon this supervisor spawned, exit-event-backed: the implementation keeps the spawned ChildProcess handle, consumes its exit event (which is also what reaps it), and reports the pid as not running from that moment. A bare signal-0 check is not enough, because a defunct process still answers it.
|
|
1827
|
+
*/
|
|
1828
|
+
readonly isRunning: (pid: number) => boolean;
|
|
1829
|
+
/** Returns a free loopback port. Async because the only reliable way to reserve one is to bind and release a socket. */
|
|
1830
|
+
readonly freePort: () => Promise<number>;
|
|
1831
|
+
/** Whether nothing is currently listening on one specific loopback port: the check that decides whether the sticky `lastPort` can be reused. The same bind-and-release technique `freePort` uses, applied to a number the caller already cares about. */
|
|
1832
|
+
readonly isPortFree: (port: number) => Promise<boolean>;
|
|
1833
|
+
/**
|
|
1834
|
+
* Starts `headroom proxy` bound to `port` with the given allowlist, returning its pid. Output goes to the daemon log. The implementation must keep the ChildProcess handle and attach an exit listener (detaching the process is fine; unref'ing a child you still need events from is not, since without the listener nothing reaps it and it lingers as a zombie).
|
|
1835
|
+
*/
|
|
1836
|
+
readonly spawnHeadroom: (port: number, allowlist: readonly string[], settings: Readonly<HeadroomSettings>) => number;
|
|
1837
|
+
/** Reads the headroom config block as it stands on disk now. The supervisor calls it every tick, so a changed `source` or setting is noticed while the daemon runs and applied by the same quiet-registry restart an allowlist change gets. */
|
|
1838
|
+
readonly readConfig: () => HeadroomSupervisorConfig;
|
|
1839
|
+
/** Stops a process the supervisor owns, escalating SIGTERM to SIGKILL on a bounded timeout (see `stopSupervisedProcess`). */
|
|
1840
|
+
readonly stopProcess: (pid: number) => void;
|
|
1841
|
+
/** True once `GET /readyz` on the port succeeds. */
|
|
1842
|
+
readonly ready: (port: number) => Promise<boolean>;
|
|
1843
|
+
/** Runs `uv tool install <spec>`, logging output to the daemon log. Returns ok=false with the error when `uv` is absent or the install fails. */
|
|
1844
|
+
readonly install: (spec: string) => {
|
|
1845
|
+
readonly ok: boolean;
|
|
1846
|
+
readonly error?: string;
|
|
1847
|
+
};
|
|
1848
|
+
/** The installed `headroom --version` output, or undefined when the binary is not on PATH. */
|
|
1849
|
+
readonly headroomVersion: () => string | undefined;
|
|
1850
|
+
/** The commit the installed headroom was built from, as the install itself records it (`direct_url.json`), or undefined when it was not installed from a git source. */
|
|
1851
|
+
readonly installedCommit: () => string | undefined;
|
|
1852
|
+
readonly log: (line: string) => void;
|
|
1853
|
+
}
|
|
1854
|
+
/** Options for `runSupervisor`. */
|
|
1855
|
+
interface RunSupervisorOptions {
|
|
1856
|
+
/**
|
|
1857
|
+
* Bounds the supervisor loop's iterations. The real supervisor runs until it exits on its own (idle shutdown or fatal error); a bounded run is how tests observe steady-state behaviour (drift deferral, restart-on-crash, pruning) without an infinite loop, and such a run returns `HEADROOM_SUPERVISOR_STILL_RUNNING` instead of an exit code.
|
|
1858
|
+
*/
|
|
1859
|
+
readonly tickLimit?: number;
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Runs the headroom supervisor loop: install the daemon, start it, keep it alive (for crash or drift), and stop it after the configured idle period with an empty session registry. Returns the process exit code (0 for an idle shutdown, 1 for a fatal setup failure).
|
|
1863
|
+
*
|
|
1864
|
+
* Every effect flows through `ports`, so the whole loop is unit-testable with a fake clock, filesystem, and processes. The loop awaits its sleeps, which in the real implementation run on a timer: between ticks the event loop turns, the spawned child's exit event is delivered (and the child thereby reaped), and the next tick's liveness check reads the truth.
|
|
1865
|
+
*/
|
|
1866
|
+
declare function runSupervisor(initialConfig: HeadroomSupervisorConfig, ports: SupervisorPorts, options?: RunSupervisorOptions): Promise<number>;
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* The environment variables that authenticate Claude Code directly from the process environment, ahead of any stored identity credential. If any of these is set, every identity would silently authenticate as the same key/token/backend while it's set — defeating the entire premise of separate identities. Order here is also lookup order: `detectAmbientCredential` reports the first one found.
|
|
1870
|
+
*/
|
|
1871
|
+
declare const AMBIENT_CREDENTIAL_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN", "CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX", "CLAUDE_CODE_USE_FOUNDRY"];
|
|
1872
|
+
type AmbientCredentialVar = (typeof AMBIENT_CREDENTIAL_VARS)[number];
|
|
1873
|
+
/** Which guarded variable was found set, and to what. */
|
|
1874
|
+
interface AmbientCredentialDetection {
|
|
1875
|
+
readonly variable: AmbientCredentialVar;
|
|
1876
|
+
}
|
|
1877
|
+
/** A credential this launch itself exports to the child: the variable it lands in and its value. */
|
|
1878
|
+
interface InjectedCredential {
|
|
1879
|
+
readonly variable: CredentialTargetVar;
|
|
1880
|
+
readonly token: string;
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Detects whether any of `AMBIENT_CREDENTIAL_VARS` is set to a non-empty value in `env`, returning the first one found in declared order, or undefined when none are set.
|
|
1884
|
+
*
|
|
1885
|
+
* An empty string counts as unset, not set — confirmed load-bearing: one of Joe's real wrapper scripts (`o`, running Claude Code against OpenRouter) does `export ANTHROPIC_API_KEY=""` specifically to *clear* it so `ANTHROPIC_AUTH_TOKEN` takes effect instead, and this must never trip the guard.
|
|
1886
|
+
*
|
|
1887
|
+
* `injected`, when given, is the credential this launch exports for its own identity. Its variable holding exactly that value is not ambient: it is what a agent-shim launch of the same identity left in the environment of the session this one starts from (a `claude @work` run inside a `claude @work` session). The same variable holding any other value still counts.
|
|
1888
|
+
*/
|
|
1889
|
+
declare function detectAmbientCredential(env: Readonly<Record<string, string | undefined>>, injected?: InjectedCredential): AmbientCredentialDetection | undefined;
|
|
1890
|
+
/**
|
|
1891
|
+
* Builds the exact refusal message: which variable was found, why it matters, and the two ways to opt in (a one-off env var, or a persistent per-identity setting). Mirrors the message documented in the project's README. `identityName` is included in the persistent-opt-in command when known; when no identity has been resolved yet (e.g. the `CLAUDE_CONFIG_DIR`-already-set escape hatch, or no identity resolved at all), a generic `<name>` placeholder is used instead, matching the README's own generic wording.
|
|
1892
|
+
*/
|
|
1893
|
+
declare function formatAmbientCredentialGuardMessage(variable: AmbientCredentialVar, identityName?: string): string;
|
|
1894
|
+
/** Inputs to the ambient-credential guard evaluation. */
|
|
1895
|
+
interface EvaluateAmbientCredentialGuardParams {
|
|
1896
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
1897
|
+
/** The active identity's own `allowAmbientCredential` setting from its `identity.json`, or false when no identity is known. */
|
|
1898
|
+
readonly allowAmbientCredential: boolean;
|
|
1899
|
+
/** True when `AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1` is set for this one invocation. */
|
|
1900
|
+
readonly allowAmbientCredentialOverride: boolean;
|
|
1901
|
+
/** The active identity's name, for the message's persistent-opt-in command. Undefined when no identity is known. */
|
|
1902
|
+
readonly identityName?: string;
|
|
1903
|
+
/**
|
|
1904
|
+
* True when this launch routes through an API provider. The guard inspects the PARENT environment, before `buildEnv` runs; a provider launch has its `ANTHROPIC_AUTH_TOKEN` injected and its `ANTHROPIC_API_KEY` cleared by `buildEnv` itself, so an ambient credential in the parent environment never reaches the child and there is nothing left for this guard to protect against. Everything else about the guard (including `IdentitySchema.allowAmbientCredential`) is unchanged by this flag.
|
|
1905
|
+
*/
|
|
1906
|
+
readonly providerSelected?: boolean;
|
|
1907
|
+
/** The launching identity's own credential, when its credential block resolved: see `detectAmbientCredential`. */
|
|
1908
|
+
readonly injectedCredential?: InjectedCredential;
|
|
1909
|
+
}
|
|
1910
|
+
/** The result of one guard evaluation: either launch may proceed, or it must be refused with an explanatory message. */
|
|
1911
|
+
type AmbientCredentialGuardResult = {
|
|
1912
|
+
readonly ok: true;
|
|
1913
|
+
} | {
|
|
1914
|
+
readonly ok: false;
|
|
1915
|
+
readonly variable: AmbientCredentialVar;
|
|
1916
|
+
readonly message: string;
|
|
1917
|
+
};
|
|
1918
|
+
/**
|
|
1919
|
+
* Evaluates the ambient-credential guard: refuses unless no guarded variable is set, or the active identity opted in (`allowAmbientCredential: true`), or this one invocation opted in (`AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1`), or a provider takes over the child's credential (see `providerSelected`).
|
|
1920
|
+
*
|
|
1921
|
+
* This guard is about credential isolation, not identity/config-dir selection — it must still run even when the `CLAUDE_CONFIG_DIR`-already-set escape hatch applies (callers pass `allowAmbientCredential: false` and no `identityName` in that case, since there is no active identity to consult).
|
|
1922
|
+
*/
|
|
1923
|
+
declare function evaluateAmbientCredentialGuard(params: EvaluateAmbientCredentialGuardParams): AmbientCredentialGuardResult;
|
|
1924
|
+
|
|
1925
|
+
/** Raised by any operation that requires an identity to already exist, when it does not. */
|
|
1926
|
+
declare class IdentityNotFoundError extends CliError {
|
|
1927
|
+
readonly name: string;
|
|
1928
|
+
constructor(name: string);
|
|
1929
|
+
}
|
|
1930
|
+
/** Raised by `addIdentity` when an identity with the given name already has an `identity.json`. */
|
|
1931
|
+
declare class IdentityAlreadyExistsError extends CliError {
|
|
1932
|
+
readonly identityName: string;
|
|
1933
|
+
constructor(identityName: string);
|
|
1934
|
+
}
|
|
1935
|
+
/** Raised by `addIdentity` when `name` fails `IdentitySchema`'s own naming rule — it must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs, so an email address names an identity directly while a *leading* `@` stays invalid (it would collide with the `@name` selector syntax's first-`@` split). */
|
|
1936
|
+
declare class InvalidIdentityNameError extends CliError {
|
|
1937
|
+
readonly attemptedName: string;
|
|
1938
|
+
constructor(attemptedName: string);
|
|
1939
|
+
}
|
|
1940
|
+
declare function identityExists(paths: LayoutPaths, name: string): boolean;
|
|
1941
|
+
/** Reads and validates one identity's `identity.json`, or undefined when it does not exist. */
|
|
1942
|
+
declare function readIdentity(paths: LayoutPaths, name: string): Identity | undefined;
|
|
1943
|
+
/**
|
|
1944
|
+
* Creates a new identity: validates `name` against `IdentitySchema`'s own naming rule and writes a fresh `identity.json` with `allowAmbientCredential: false` and no `defaultConfigProfile`.
|
|
1945
|
+
*
|
|
1946
|
+
* Throws `IdentityAlreadyExistsError` if an identity with this name already has an `identity.json` — `add` never silently overwrites an existing identity. Throws `InvalidIdentityNameError` when `name` fails `IdentitySchema`'s naming rule, rather than letting the underlying `ZodError` escape as an unhandled crash.
|
|
1947
|
+
*/
|
|
1948
|
+
declare function addIdentity(paths: LayoutPaths, name: string): Identity;
|
|
1949
|
+
/**
|
|
1950
|
+
* Persists `name` as the active identity, written atomically as plain text (not JSON — this file is read by `decideIdentity` in `src/launcher/identity.ts` via a simple UTF-8 read-and-trim, matching the README's documented `~/.agent-shim/active-identity` file).
|
|
1951
|
+
*
|
|
1952
|
+
* `name` may be a `pool:<name>` selector, which makes launches pick a member of that pool. Throws `IdentityNotFoundError` when no identity with this name exists yet, and `PoolNotFoundError` for a pool that is not defined: selecting either would silently persist a name nothing else can ever load.
|
|
1953
|
+
*/
|
|
1954
|
+
declare function useIdentity(paths: LayoutPaths, name: string): void;
|
|
1955
|
+
/** Reads the persisted active-identity file, or undefined when none is set. */
|
|
1956
|
+
declare function readActiveIdentity(paths: LayoutPaths): string | undefined;
|
|
1957
|
+
/**
|
|
1958
|
+
* Whether a directory name directly under `identitiesDir` names an actual identity, rather than one of agent-shim's own farm directories.
|
|
1959
|
+
*
|
|
1960
|
+
* `IdentitySchema` requires an identity name to start with a letter or digit, so a leading `.` can only be a resync's own bookkeeping: a `.<identity>.scratch.<suffix>` tree still being built, or a `.<identity>.previous.<suffix>` superseded farm retained for `agent-shim identity resolve-conflicts`. Neither is an identity, and neither should be reported as a broken one for lacking an `identity.json` a resync never put there.
|
|
1961
|
+
*/
|
|
1962
|
+
declare function isIdentityDirectoryName(name: string): boolean;
|
|
1963
|
+
/** One identity as reported by `listIdentities`, whose `identity.json` parsed and validated cleanly. */
|
|
1964
|
+
interface IdentityListEntry {
|
|
1965
|
+
readonly name: string;
|
|
1966
|
+
readonly identity: Identity;
|
|
1967
|
+
readonly isActive: boolean;
|
|
1968
|
+
readonly problem?: never;
|
|
1969
|
+
}
|
|
1970
|
+
/** One identity whose `identity.json` is present but unreadable — malformed JSON, or valid JSON this version's `IdentitySchema` rejects. `problem` carries the reason, already flattened onto a single line. */
|
|
1971
|
+
interface UnreadableIdentityListEntry {
|
|
1972
|
+
readonly name: string;
|
|
1973
|
+
readonly identity?: never;
|
|
1974
|
+
readonly isActive: boolean;
|
|
1975
|
+
readonly problem: string;
|
|
1976
|
+
}
|
|
1977
|
+
/** Either shape `listIdentities` can report, discriminated by which of `identity`/`problem` is present rather than by a tag field — the two are never simultaneously satisfiable. */
|
|
1978
|
+
type IdentityListing = IdentityListEntry | UnreadableIdentityListEntry;
|
|
1979
|
+
/**
|
|
1980
|
+
* Lists every identity under `identitiesDir`, marking which one (if any) is currently active.
|
|
1981
|
+
*
|
|
1982
|
+
* One identity whose `identity.json` cannot be read is reported as its own `UnreadableIdentityListEntry` rather than aborting the whole listing. A single bad file blocking `identity list` outright is exactly the failure mode that hides every *other* identity from view at the moment the user most needs to see them — and the file need not even be corrupt to land here, since a name written by a newer agent-shim whose naming rule has since widened is rejected outright by an older binary's own copy of `IdentitySchema`.
|
|
1983
|
+
*/
|
|
1984
|
+
declare function listIdentities(paths: LayoutPaths): readonly IdentityListing[];
|
|
1985
|
+
/**
|
|
1986
|
+
* Sets `identity`'s `defaultConfigProfile` field, or clears it when `profileName` is undefined. Throws `IdentityNotFoundError` when the identity does not exist. Whether the profile exists is the caller's concern: `identity set --default-profile` checks it first.
|
|
1987
|
+
*/
|
|
1988
|
+
declare function setDefaultConfigProfile(paths: LayoutPaths, identityName: string, profileName: string | undefined): Identity;
|
|
1989
|
+
/**
|
|
1990
|
+
* Patches `identity`'s `allowAmbientCredential` field. Throws `IdentityNotFoundError` when the identity does not exist.
|
|
1991
|
+
*/
|
|
1992
|
+
declare function setAllowAmbientCredential(paths: LayoutPaths, identityName: string, allow: boolean): Identity;
|
|
1993
|
+
/** The change `setIdentityCredential` makes: new sources and/or a new target for the credential block, or `false` to remove the block and return the identity to its stored login. */
|
|
1994
|
+
type IdentityCredentialChange = {
|
|
1995
|
+
readonly sources?: readonly CredentialSource[];
|
|
1996
|
+
readonly target?: CredentialTarget;
|
|
1997
|
+
readonly cache?: CredentialCache | false;
|
|
1998
|
+
} | false;
|
|
1999
|
+
/**
|
|
2000
|
+
* Sets, changes or removes `identityName`'s credential block. New `sources` replace the whole ordered list (the order is the meaning); a `target` alone keeps the existing sources, and so needs a credential block to exist already. Throws `IdentityNotFoundError` when the identity does not exist, and `UsageError` when only a target is given for an identity with no credential yet.
|
|
2001
|
+
*/
|
|
2002
|
+
declare function setIdentityCredential(paths: LayoutPaths, identityName: string, change: IdentityCredentialChange): Identity;
|
|
2003
|
+
/**
|
|
2004
|
+
* Deletes identity `name` entirely: its directory under `identitiesDir` (the farm, its `identity.json`, and whatever that farm holds unshared, credentials included), its resync lock, any scratch or superseded farm a resync left behind, and the active-identity selection when it names this identity. Data shared into `~/.claude` stays there, since the farm only ever symlinks to it. Throws `IdentityNotFoundError` when the identity does not exist.
|
|
2005
|
+
*/
|
|
2006
|
+
declare function removeIdentity(paths: LayoutPaths, name: string): void;
|
|
2007
|
+
|
|
2008
|
+
/** Raised by any operation that requires a configuration profile to already exist, when it does not. */
|
|
2009
|
+
declare class ProfileNotFoundError extends CliError {
|
|
2010
|
+
readonly profileName: string;
|
|
2011
|
+
constructor(profileName: string);
|
|
2012
|
+
}
|
|
2013
|
+
/** Raised by `createProfile` (`profile add`) when a profile with the given name already has a file. */
|
|
2014
|
+
declare class ProfileAlreadyExistsError extends CliError {
|
|
2015
|
+
readonly profileName: string;
|
|
2016
|
+
constructor(profileName: string);
|
|
2017
|
+
}
|
|
2018
|
+
/** Raised when a `--category` patch names something other than one of the four overridable categories (e.g. `secret`, or a typo). */
|
|
2019
|
+
declare class InvalidCategoryNameError extends CliError {
|
|
2020
|
+
readonly categoryName: string;
|
|
2021
|
+
constructor(categoryName: string);
|
|
2022
|
+
}
|
|
2023
|
+
/** True when a profile file exists for `name`, regardless of whether it validates. */
|
|
2024
|
+
declare function profileExists(paths: LayoutPaths, name: string): boolean;
|
|
2025
|
+
/** Reads and validates one configuration profile, or undefined when it does not exist. */
|
|
2026
|
+
declare function readProfile(paths: LayoutPaths, name: string): ConfigProfile | undefined;
|
|
2027
|
+
/**
|
|
2028
|
+
* Creates a new configuration profile, empty apart from the optional `extends` list and description. Throws `ProfileAlreadyExistsError` if a profile with this name already has a file, and `ConfigValidationError` when the result fails `ConfigProfileSchema` (e.g. an empty `--extends ""` name), rather than letting the underlying `ZodError` escape as an unhandled crash.
|
|
2029
|
+
*/
|
|
2030
|
+
declare function createProfile(paths: LayoutPaths, name: string, extendsList?: readonly string[], description?: string): ConfigProfile;
|
|
2031
|
+
/** One profile as reported by `listProfiles`. */
|
|
2032
|
+
interface ProfileListEntry {
|
|
2033
|
+
readonly name: string;
|
|
2034
|
+
readonly profile: ConfigProfile;
|
|
2035
|
+
}
|
|
2036
|
+
/** Lists every configuration profile under `configProfilesDir` that has a valid `<name>.json`. */
|
|
2037
|
+
declare function listProfiles(paths: LayoutPaths): readonly ProfileListEntry[];
|
|
2038
|
+
/** Reads the user-global `~/.agent-shim/config.json`, or undefined when it does not exist. */
|
|
2039
|
+
declare function readGlobalConfig(paths: LayoutPaths): GlobalConfig | undefined;
|
|
2040
|
+
/** Sets the user-global default configuration profile in `~/.agent-shim/config.json`, creating the file if it doesn't exist yet. Whether the profile exists is the caller's concern: `profile use` checks it first. */
|
|
2041
|
+
declare function setGlobalDefaultProfile(paths: LayoutPaths, name: string): GlobalConfig;
|
|
2042
|
+
/**
|
|
2043
|
+
* Merges `patch` (from one or more `--category cat=bool` flags) into `profile`'s own `categories` object and writes it back.
|
|
2044
|
+
*
|
|
2045
|
+
* Throws `InvalidCategoryNameError` for any key that isn't one of the four overridable categories — `secret` can never be toggled by any configuration layer, this included.
|
|
2046
|
+
*/
|
|
2047
|
+
declare function setProfileCategories(paths: LayoutPaths, name: string, patch: Readonly<Record<string, boolean>>): ConfigProfile;
|
|
2048
|
+
/**
|
|
2049
|
+
* Merges `patch` (from one or more `--entry "path"=bool` flags) into `profile`'s own `entries` object and writes it back.
|
|
2050
|
+
*
|
|
2051
|
+
* Key validity (the `<category>/<real-relative-path>` prefix requirement) is enforced by `ConfigProfileSchema`'s own `EntriesSchema` at write time — an invalid key surfaces as the usual `ConfigValidationError`.
|
|
2052
|
+
*/
|
|
2053
|
+
declare function setProfileEntries(paths: LayoutPaths, name: string, patch: Readonly<Record<string, boolean>>): ConfigProfile;
|
|
2054
|
+
/**
|
|
2055
|
+
* Merges `patch` into `profile`'s own `launch` object and writes it back. A key present in `patch` with the value `undefined` removes that setting outright (a `--no-launch-provider`, say) rather than overwriting it, and a `launch` object left with no settings is removed too.
|
|
2056
|
+
*/
|
|
2057
|
+
declare function setProfileLaunchFlags(paths: LayoutPaths, name: string, patch: Readonly<LaunchFlags>): ConfigProfile;
|
|
2058
|
+
/** Replaces `profile`'s own `extends` list and/or description; an empty list or an undefined description removes the field. */
|
|
2059
|
+
declare function setProfileMetadata(paths: LayoutPaths, name: string, patch: Readonly<{
|
|
2060
|
+
extends?: readonly string[];
|
|
2061
|
+
description?: string | false;
|
|
2062
|
+
}>): ConfigProfile;
|
|
2063
|
+
/** Deletes a configuration profile's file. Throws `ProfileNotFoundError` when it does not exist. Nothing that references it by name is rewritten; `agent-shim doctor` reports any identity default, directory rule or `extends` left pointing at it. */
|
|
2064
|
+
declare function removeProfile(paths: LayoutPaths, name: string): void;
|
|
2065
|
+
|
|
2066
|
+
/** What a cache stores for one credential: the token, when it was fetched, and the source that produced it (a non-secret descriptor, kept so `check` and the launch log can still say where the token came from). */
|
|
2067
|
+
declare const CachedCredentialSchema: z.ZodObject<{
|
|
2068
|
+
token: z.ZodString;
|
|
2069
|
+
fetchedAt: z.ZodNumber;
|
|
2070
|
+
source: z.ZodUnion<readonly [z.ZodObject<{
|
|
2071
|
+
env: z.ZodString;
|
|
2072
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2073
|
+
file: z.ZodString;
|
|
2074
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2075
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
2076
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
2077
|
+
command: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
2078
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2079
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
2080
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
2081
|
+
op: z.ZodString;
|
|
2082
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2083
|
+
interactive: z.ZodOptional<z.ZodBoolean>;
|
|
2084
|
+
timeoutMs: z.ZodOptional<z.ZodInt>;
|
|
2085
|
+
keychain: z.ZodObject<{
|
|
2086
|
+
service: z.ZodString;
|
|
2087
|
+
account: z.ZodOptional<z.ZodString>;
|
|
2088
|
+
}, z.core.$strict>;
|
|
2089
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
2090
|
+
literal: z.ZodString;
|
|
2091
|
+
}, z.core.$strict>]>;
|
|
2092
|
+
}, z.core.$strict>;
|
|
2093
|
+
type CachedCredential = z.infer<typeof CachedCredentialSchema>;
|
|
2094
|
+
/**
|
|
2095
|
+
* Where cached credentials live, injected so resolution and its tests need neither the Keychain nor a real directory. `owner` names what the credential belongs to (`identity work`, `provider z`) and is the entry's key; `store` picks the backing store. `read` returns undefined for a missing or unreadable entry, since an entry that cannot be used is the same as no entry.
|
|
2096
|
+
*/
|
|
2097
|
+
interface CredentialCachePort {
|
|
2098
|
+
readonly read: (store: CredentialCacheStore, owner: string) => CachedCredential | undefined;
|
|
2099
|
+
readonly write: (store: CredentialCacheStore, owner: string, entry: CachedCredential) => void;
|
|
2100
|
+
readonly remove: (store: CredentialCacheStore, owner: string) => void;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
/** What reading one `file` source found: nothing at that path, or its contents and permission bits. `mode` is undefined on a platform with no POSIX permission bits (Windows), where the looseness check cannot apply. */
|
|
2104
|
+
type SecretFileRead = {
|
|
2105
|
+
readonly found: false;
|
|
2106
|
+
} | {
|
|
2107
|
+
readonly found: true;
|
|
2108
|
+
readonly content: string;
|
|
2109
|
+
readonly mode: number | undefined;
|
|
2110
|
+
};
|
|
2111
|
+
/** The outcome of one credential command. `stderr` is empty for an interactive run, whose stderr goes to the terminal so a person sees any prompt. */
|
|
2112
|
+
interface CredentialCommandResult {
|
|
2113
|
+
readonly status: number | null;
|
|
2114
|
+
readonly stdout: string;
|
|
2115
|
+
readonly stderr: string;
|
|
2116
|
+
readonly timedOut: boolean;
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Everything resolving a credential needs from the outside world, injected so the resolver (and every launcher test) runs without `op`, the Keychain, real secret files or a terminal. `src/realPorts.ts` wires the real one; tests wire a fake.
|
|
2120
|
+
*/
|
|
2121
|
+
interface CredentialPort {
|
|
2122
|
+
/** Reads a `file` source's path (absolute or `~`-rooted). */
|
|
2123
|
+
readonly readSecretFile: (filePath: string) => SecretFileRead;
|
|
2124
|
+
/** Runs a command source's argv directly (no shell) under a timeout, capturing stdout. An interactive run inherits the terminal's stdin and stderr so a person can answer a prompt. */
|
|
2125
|
+
readonly runCommand: (argv: readonly [string, ...string[]], options: {
|
|
2126
|
+
readonly timeoutMs: number;
|
|
2127
|
+
readonly interactive: boolean;
|
|
2128
|
+
}) => CredentialCommandResult;
|
|
2129
|
+
/** Whether a person can answer a prompt now: standard input is a terminal, or there is a desktop session to show a dialog in. */
|
|
2130
|
+
readonly personPresent: () => boolean;
|
|
2131
|
+
/** Where cached credentials live, used only for a credential block that asks for caching. Omit to resolve every launch from the sources. */
|
|
2132
|
+
readonly cache?: CredentialCacheEnv;
|
|
2133
|
+
}
|
|
2134
|
+
/** One source as `check`, `doctor` and every `--json` output report it: its kind plus the same non-secret identifying detail `describeSource` prints. */
|
|
2135
|
+
type CredentialSourceSummary = {
|
|
2136
|
+
readonly kind: "env";
|
|
2137
|
+
readonly variable: string;
|
|
2138
|
+
} | {
|
|
2139
|
+
readonly kind: "file";
|
|
2140
|
+
readonly path: string;
|
|
2141
|
+
} | {
|
|
2142
|
+
readonly kind: "command";
|
|
2143
|
+
readonly program: string;
|
|
2144
|
+
} | {
|
|
2145
|
+
readonly kind: "op";
|
|
2146
|
+
readonly reference: string;
|
|
2147
|
+
} | {
|
|
2148
|
+
readonly kind: "keychain";
|
|
2149
|
+
readonly service: string;
|
|
2150
|
+
readonly account?: string;
|
|
2151
|
+
} | {
|
|
2152
|
+
readonly kind: "literal";
|
|
2153
|
+
};
|
|
2154
|
+
/** A credential block as it is reported: its effective target and each source's summary, in order. */
|
|
2155
|
+
interface CredentialSummary {
|
|
2156
|
+
readonly target: CredentialTarget;
|
|
2157
|
+
readonly sources: readonly CredentialSourceSummary[];
|
|
2158
|
+
/** The block's cache setting when it asks for caching: how long a token is kept (absent for no expiry) and where. */
|
|
2159
|
+
readonly cache?: {
|
|
2160
|
+
readonly ttl?: string;
|
|
2161
|
+
readonly store?: string;
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
/** What caching a credential needs beyond the block's own `cache` setting: the port holding entries, the platform that picks the default store, and the clock the TTL is measured on. */
|
|
2165
|
+
interface CredentialCacheEnv {
|
|
2166
|
+
readonly port: CredentialCachePort;
|
|
2167
|
+
readonly platform: NodeJS.Platform;
|
|
2168
|
+
readonly now: () => number;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
/** Raised by any operation that requires a provider to already exist, when it does not. */
|
|
2172
|
+
declare class ProviderNotFoundError extends CliError {
|
|
2173
|
+
readonly name: string;
|
|
2174
|
+
constructor(name: string);
|
|
2175
|
+
}
|
|
2176
|
+
/** Raised by `addProvider` when a provider with the given name already has a file. */
|
|
2177
|
+
declare class ProviderAlreadyExistsError extends CliError {
|
|
2178
|
+
readonly providerName: string;
|
|
2179
|
+
constructor(providerName: string);
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Raised by `addProvider` when `name` fails the provider naming rule: the same rule `IdentitySchema` applies to identity names (start with a letter or number, then letters, numbers, dots, hyphens, underscores, and at signs). Keeping the two rules identical means a name that works for one works for the other, and neither vocabulary can ever produce a path segment that escapes `providers/` or `identities/`.
|
|
2183
|
+
*/
|
|
2184
|
+
declare class InvalidProviderNameError extends CliError {
|
|
2185
|
+
readonly attemptedName: string;
|
|
2186
|
+
constructor(attemptedName: string);
|
|
2187
|
+
}
|
|
2188
|
+
/** True when a provider file exists for `name`, regardless of whether it validates. */
|
|
2189
|
+
declare function providerExists(paths: LayoutPaths, name: string): boolean;
|
|
2190
|
+
/** What converting one old-format provider file involves: the old fields it uses, and the whole file rewritten in the current format. */
|
|
2191
|
+
interface LegacyProviderConversion {
|
|
2192
|
+
readonly fields: readonly string[];
|
|
2193
|
+
readonly replacement: Record<string, unknown>;
|
|
2194
|
+
}
|
|
2195
|
+
/** Raised when a provider file is still in the format before the credential block, naming the old fields and giving the exact replacement. */
|
|
2196
|
+
declare class LegacyProviderFileError extends CliError {
|
|
2197
|
+
readonly filePath: string;
|
|
2198
|
+
readonly conversion: LegacyProviderConversion;
|
|
2199
|
+
constructor(filePath: string, conversion: LegacyProviderConversion);
|
|
2200
|
+
}
|
|
2201
|
+
/** Reads and validates one provider definition, or undefined when it does not exist. */
|
|
2202
|
+
declare function readProvider(paths: LayoutPaths, name: string): Provider | undefined;
|
|
2203
|
+
/** One provider definition as reported by `listProviders`. */
|
|
2204
|
+
interface ProviderListEntry {
|
|
2205
|
+
readonly name: string;
|
|
2206
|
+
readonly provider: Provider;
|
|
2207
|
+
}
|
|
2208
|
+
/** Lists every provider under `providersDir` that has a valid `<name>.json`. A file that fails validation is skipped here; `provider list` is the place a broken definition gets surfaced, not a launch. */
|
|
2209
|
+
declare function listProviders(paths: LayoutPaths): readonly ProviderListEntry[];
|
|
2210
|
+
/** The credential targets a provider may use. */
|
|
2211
|
+
type ProviderCredentialTarget = (typeof PROVIDER_CREDENTIAL_TARGETS)[number];
|
|
2212
|
+
/** The kinds of provider. */
|
|
2213
|
+
type ProviderKind = (typeof PROVIDER_KINDS)[number];
|
|
2214
|
+
/** Where a provider sends its sessions, for display: an `http` provider's base URL, or the front door for a codex provider (whose address exists only at launch). */
|
|
2215
|
+
declare function describeProviderEndpoint(provider: Provider): string;
|
|
2216
|
+
/** Everything `addProvider` writes into a fresh provider file. `baseUrl` is required for an `http` provider and refused for a codex one; `codex` applies only to a codex provider. */
|
|
2217
|
+
interface AddProviderInput {
|
|
2218
|
+
readonly kind?: ProviderKind;
|
|
2219
|
+
readonly displayName: string;
|
|
2220
|
+
readonly baseUrl?: string;
|
|
2221
|
+
readonly sources: readonly CredentialSource[];
|
|
2222
|
+
readonly target?: ProviderCredentialTarget;
|
|
2223
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
2224
|
+
readonly codex?: CodexProviderConfig;
|
|
2225
|
+
}
|
|
2226
|
+
/** Raised when a provider option does not fit the provider's kind: a base URL for a codex provider, a missing one for an http provider, or codex settings for an http provider. */
|
|
2227
|
+
declare class ProviderKindMismatchError extends UsageError {
|
|
2228
|
+
constructor(message: string);
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Creates a new provider definition. Throws `ProviderAlreadyExistsError` when a provider with this name already has a file, `InvalidProviderNameError` when `name` fails the naming rule, and `ConfigValidationError` when the assembled definition fails `ProviderSchema` (e.g. a `baseUrl` that is not a URL) rather than letting the underlying `ZodError` escape as an unhandled crash.
|
|
2232
|
+
*/
|
|
2233
|
+
declare function addProvider(paths: LayoutPaths, name: string, input: AddProviderInput): Provider;
|
|
2234
|
+
/**
|
|
2235
|
+
* The fields `updateProvider` changes. `sources` replaces the whole ordered source list (a list is set, not patched, since its order is its meaning); `target` changes where the token goes and keeps the sources. `env` entries merge over the existing ones and `unsetEnv` keys are then deleted.
|
|
2236
|
+
*/
|
|
2237
|
+
interface UpdateProviderInput {
|
|
2238
|
+
readonly displayName?: string;
|
|
2239
|
+
readonly baseUrl?: string;
|
|
2240
|
+
readonly sources?: readonly CredentialSource[];
|
|
2241
|
+
readonly target?: ProviderCredentialTarget;
|
|
2242
|
+
/** A new cache block, or false to remove it; undefined leaves the existing one. */
|
|
2243
|
+
readonly cache?: CredentialCache | false;
|
|
2244
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
2245
|
+
readonly unsetEnv?: readonly string[];
|
|
2246
|
+
/** Merged over a codex provider's existing settings: a field given here replaces that field, and `models` entries merge per tier. */
|
|
2247
|
+
readonly codex?: CodexProviderConfig;
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Updates an existing provider definition in place. Throws `ProviderNotFoundError` when it does not exist, and `ConfigValidationError` when the updated definition fails `ProviderSchema`, leaving the file untouched.
|
|
2251
|
+
*/
|
|
2252
|
+
declare function updateProvider(paths: LayoutPaths, name: string, input: UpdateProviderInput): Provider;
|
|
2253
|
+
/** Deletes a provider definition. Throws `ProviderNotFoundError` when it does not exist. */
|
|
2254
|
+
declare function removeProvider(paths: LayoutPaths, name: string): void;
|
|
2255
|
+
|
|
2256
|
+
/** Raised when a command names a pool that is not defined in the global config. */
|
|
2257
|
+
declare class PoolNotFoundError extends CliError {
|
|
2258
|
+
readonly poolName: string;
|
|
2259
|
+
constructor(poolName: string);
|
|
2260
|
+
}
|
|
2261
|
+
/** The pools defined in `~/.agent-shim/config.json`, by name. */
|
|
2262
|
+
declare function readPools(paths: LayoutPaths): Readonly<Record<string, Pool>>;
|
|
2263
|
+
/** The pool named `name`; throws `PoolNotFoundError` when none is defined. */
|
|
2264
|
+
declare function requirePool(paths: LayoutPaths, name: string): Pool;
|
|
2265
|
+
/** Defines a new pool. Throws `InvalidPoolNameError` for a name `PoolNameSchema` rejects and `PoolAlreadyExistsError` for one already defined. */
|
|
2266
|
+
declare function addPool(paths: LayoutPaths, name: string, identities: readonly string[]): Pool;
|
|
2267
|
+
/** Replaces an existing pool's members. Throws `PoolNotFoundError` when it is not defined. */
|
|
2268
|
+
declare function setPool(paths: LayoutPaths, name: string, identities: readonly string[]): Pool;
|
|
2269
|
+
/** Removes a pool. Throws `PoolNotFoundError` when it is not defined. Anything that selected it (an active-identity file, a directory rule) is left for `doctor` to report, as with a removed identity. */
|
|
2270
|
+
declare function removePool(paths: LayoutPaths, name: string): void;
|
|
2271
|
+
|
|
2272
|
+
/** Raised by `removeDirectoryRule`, `updateDirectoryRule` and `rule show` when no rule matches the given path exactly. */
|
|
2273
|
+
declare class DirectoryRuleNotFoundError extends CliError {
|
|
2274
|
+
readonly rulePath: string;
|
|
2275
|
+
constructor(rulePath: string);
|
|
2276
|
+
}
|
|
2277
|
+
/** Raised by `addDirectoryRule` when neither `--config-profile` nor `--identity` is given, and by `updateDirectoryRule` when an update would leave a rule that sets nothing at all: a rule that pins and overrides nothing would do nothing. */
|
|
2278
|
+
declare class DirectoryRuleMissingTargetError extends CliError {
|
|
2279
|
+
constructor();
|
|
2280
|
+
}
|
|
2281
|
+
/** Raised by `addDirectoryRule` when a rule for the exact path already exists: `rule add` creates, `rule set` updates. */
|
|
2282
|
+
declare class DirectoryRuleAlreadyExistsError extends CliError {
|
|
2283
|
+
readonly rulePath: string;
|
|
2284
|
+
constructor(rulePath: string);
|
|
2285
|
+
}
|
|
2286
|
+
/** Reads `~/.agent-shim/directory-rules.json`, or an empty rule set when the file does not exist yet. */
|
|
2287
|
+
declare function readDirectoryRules(paths: LayoutPaths): DirectoryRules;
|
|
2288
|
+
/** Validates and writes the whole `~/.agent-shim/directory-rules.json` file. Exported so `src/configure.ts` can update a single rule's `categories`/`entries` in place without duplicating this validate-then-write step. Throws `ConfigValidationError` when `rules` fails `DirectoryRulesSchema`, rather than letting the underlying `ZodError` escape as an unhandled crash. */
|
|
2289
|
+
declare function writeDirectoryRules(paths: LayoutPaths, rules: DirectoryRules): void;
|
|
2290
|
+
/** Lists every directory rule, in file order. */
|
|
2291
|
+
declare function listDirectoryRules(paths: LayoutPaths): readonly DirectoryRule[];
|
|
2292
|
+
/** Inputs to `addDirectoryRule` beyond the path itself. */
|
|
2293
|
+
interface AddDirectoryRuleOptions {
|
|
2294
|
+
readonly configProfile?: string;
|
|
2295
|
+
readonly identity?: string;
|
|
2296
|
+
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Adds a new directory rule for `rulePath`. Throws `DirectoryRuleAlreadyExistsError` when a rule for that exact path already exists (`updateDirectoryRule` changes one), and `DirectoryRuleMissingTargetError` when neither `configProfile` nor `identity` is given, since a rule that pins neither would do nothing.
|
|
2299
|
+
*/
|
|
2300
|
+
declare function addDirectoryRule(paths: LayoutPaths, rulePath: string, options: AddDirectoryRuleOptions): DirectoryRule;
|
|
2301
|
+
/** The fields `updateDirectoryRule` changes. A value of `false` removes that field from the rule. */
|
|
2302
|
+
interface UpdateDirectoryRuleOptions {
|
|
2303
|
+
readonly configProfile?: string | false;
|
|
2304
|
+
readonly identity?: string | false;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Updates the existing directory rule for `rulePath` in place, keeping its position in the file and every field not named. Throws `DirectoryRuleNotFoundError` when no rule matches that exact path, and `DirectoryRuleMissingTargetError` when the update would leave a rule that does nothing (remove it with `removeDirectoryRule` instead).
|
|
2308
|
+
*/
|
|
2309
|
+
declare function updateDirectoryRule(paths: LayoutPaths, rulePath: string, options: UpdateDirectoryRuleOptions): DirectoryRule;
|
|
2310
|
+
/** Removes the directory rule for `rulePath`. Throws `DirectoryRuleNotFoundError` when no rule matches that exact path. */
|
|
2311
|
+
declare function removeDirectoryRule(paths: LayoutPaths, rulePath: string): void;
|
|
2312
|
+
|
|
2313
|
+
/** Which precedence rule produced an identity decision. */
|
|
2314
|
+
type IdentityDecisionSource =
|
|
2315
|
+
/** `CLAUDE_CONFIG_DIR` was already set: identity resolution is skipped entirely and the real binary uses whatever it already points to. */
|
|
2316
|
+
"config-dir-escape-hatch"
|
|
2317
|
+
/** A leading `@name` argv[0] positional, or its explicit form `--identity <name>`. */
|
|
2318
|
+
| "argv"
|
|
2319
|
+
/** The `AGENT_SHIM_IDENTITY` environment variable. */
|
|
2320
|
+
| "env"
|
|
2321
|
+
/** A directory rule pinning an identity to the current path. */
|
|
2322
|
+
| "directory-pin"
|
|
2323
|
+
/** The persisted `~/.agent-shim/active-identity` file. */
|
|
2324
|
+
| "active-identity-file"
|
|
2325
|
+
/** Nothing resolved an identity at all — a bare launch with no active identity. */
|
|
2326
|
+
| "none";
|
|
2327
|
+
/** Which precedence rule produced a configuration-profile decision. */
|
|
2328
|
+
type ConfigProfileDecisionSource =
|
|
2329
|
+
/** An explicit `--config-profile` CLI flag. */
|
|
2330
|
+
"cli-flag"
|
|
2331
|
+
/** The `AGENT_SHIM_CONFIG_PROFILE` environment variable. */
|
|
2332
|
+
| "env"
|
|
2333
|
+
/** A directory rule's `configProfile` selection for `$PWD`. */
|
|
2334
|
+
| "directory-rule"
|
|
2335
|
+
/** The active identity's own declared `defaultConfigProfile`. */
|
|
2336
|
+
| "identity-default"
|
|
2337
|
+
/** The user-global `~/.agent-shim/config.json` default. */
|
|
2338
|
+
| "global-default"
|
|
2339
|
+
/** Nothing resolved a configuration profile at all. */
|
|
2340
|
+
| "none";
|
|
2341
|
+
|
|
2342
|
+
/** What an account's plan means for picking it: a subscription with a capacity relative to the smallest paid tier, or an account billed by use. */
|
|
2343
|
+
type PlanClass = {
|
|
2344
|
+
readonly kind: "subscription";
|
|
2345
|
+
/** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
|
|
2346
|
+
readonly capacity: number;
|
|
2347
|
+
/** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
|
|
2348
|
+
readonly recognised: boolean;
|
|
2349
|
+
/** The tier string the classification rests on, when the account had one. */
|
|
2350
|
+
readonly tier?: string;
|
|
2351
|
+
} | {
|
|
2352
|
+
readonly kind: "pay-per-use";
|
|
2353
|
+
readonly tier?: string;
|
|
2354
|
+
};
|
|
2355
|
+
|
|
2356
|
+
/** In pick order. `scored` has usable quota data; `unknown` has none (never recorded, or unreadable); `pay-per-use` bills by use instead of drawing on a plan; `ineligible` is refused right now. */
|
|
2357
|
+
type CandidateClass = "scored" | "unknown" | "pay-per-use" | "ineligible";
|
|
2358
|
+
interface Candidate {
|
|
2359
|
+
readonly identity: string;
|
|
2360
|
+
readonly class: CandidateClass;
|
|
2361
|
+
/** Plan-size-weighted remaining quota per hour until its reset: higher is more wasted if left unused. Present for `scored` only. */
|
|
2362
|
+
readonly score?: number;
|
|
2363
|
+
/** False when the five-hour window would run dry, at the observed pace, before it resets. A `scored` candidate that is not feasible ranks below every feasible one. */
|
|
2364
|
+
readonly feasible: boolean;
|
|
2365
|
+
/** When an `ineligible` candidate can be used again. */
|
|
2366
|
+
readonly blockedUntilMs?: number;
|
|
2367
|
+
readonly plan: PlanClass;
|
|
2368
|
+
/** Human-readable facts behind the class and score, in the order they matter. */
|
|
2369
|
+
readonly reasons: readonly string[];
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
/** What `pool pick` reports: the ranking a launch from `directory` would act on right now. */
|
|
2373
|
+
interface PoolPickReport {
|
|
2374
|
+
readonly pool: string;
|
|
2375
|
+
readonly directory: string;
|
|
2376
|
+
/** The member a launch would run as, absent when every member is refused. */
|
|
2377
|
+
readonly pick?: string;
|
|
2378
|
+
readonly candidates: readonly PoolPickCandidateView[];
|
|
2379
|
+
/** Members that name an identity that does not exist; a launch skips them. */
|
|
2380
|
+
readonly missing: readonly string[];
|
|
2381
|
+
/** When nothing can be picked, the soonest any member returns. */
|
|
2382
|
+
readonly earliestReturn?: {
|
|
2383
|
+
readonly identity: string;
|
|
2384
|
+
readonly at: string;
|
|
2385
|
+
};
|
|
2386
|
+
/** Why the last-pick record could not be read, when that is the case. */
|
|
2387
|
+
readonly stickyProblem?: string;
|
|
2388
|
+
}
|
|
2389
|
+
/** One ranked member as `pool pick --json` prints it. */
|
|
2390
|
+
interface PoolPickCandidateView {
|
|
2391
|
+
readonly identity: string;
|
|
2392
|
+
readonly class: Candidate["class"];
|
|
2393
|
+
readonly score?: number;
|
|
2394
|
+
readonly feasible: boolean;
|
|
2395
|
+
readonly blockedUntil?: string;
|
|
2396
|
+
readonly plan: Candidate["plan"];
|
|
2397
|
+
readonly reasons: readonly string[];
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
/**
|
|
2401
|
+
* Forward-only encoding of a real working directory into the single directory name Claude Code gives it under `~/.claude/projects/`.
|
|
2402
|
+
*
|
|
2403
|
+
* The encoding collapses the *entire* non-alphanumeric character class to `-`, not just the path separator: `.`, `_`, ` `, `@`, and `/` all become `-`, while letters and digits pass through with their case preserved. This was confirmed against a real installation's project directories, not assumed from the one `/`-becomes-`-` sample the README quotes.
|
|
2404
|
+
*
|
|
2405
|
+
* The encoding is therefore many-to-one: `~/work/clients/acme`, `~/work/clients-acme`, and `~/work-clients/acme` all produce the identical name. That makes it impossible to invert, so this module never tries: there is no decode function here, and `projects.test.ts` asserts the module's export list to keep it that way. Only the forward direction — real path to encoded name — is ever computed, and `detectEncodingAmbiguity` reports when a pattern's encoded form could plausibly correspond to more than one real path rather than resolving it silently.
|
|
2406
|
+
*/
|
|
2407
|
+
|
|
2408
|
+
/** Why a pattern's encoded form might not identify the real path its author had in mind. */
|
|
2409
|
+
type EncodingAmbiguityReason =
|
|
2410
|
+
/** The literal portion contains a character other than `/` that encodes to `-`, so a sibling path differing only in that character encodes identically. */
|
|
2411
|
+
"lossy-characters"
|
|
2412
|
+
/** Two different patterns in the same configuration encode to the identical form. */
|
|
2413
|
+
| "collides-with-sibling-pattern";
|
|
2414
|
+
/** One reported ambiguity in a `history/projects/` pattern. */
|
|
2415
|
+
interface EncodingAmbiguity {
|
|
2416
|
+
readonly fragment: string;
|
|
2417
|
+
readonly encoded: string;
|
|
2418
|
+
readonly reason: EncodingAmbiguityReason;
|
|
2419
|
+
readonly detail: string;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/** The result of looking up the macOS Keychain service name Claude Code is actually using for one identity's farm. */
|
|
2423
|
+
interface KeychainLookupResult {
|
|
2424
|
+
readonly checked: true;
|
|
2425
|
+
readonly found: boolean;
|
|
2426
|
+
readonly serviceName?: string;
|
|
2427
|
+
readonly note: string;
|
|
2428
|
+
}
|
|
2429
|
+
/** One file's reported settings exposure: names and counts only, never the underlying values. */
|
|
2430
|
+
interface SettingsExposureReport {
|
|
2431
|
+
readonly file: string;
|
|
2432
|
+
readonly envKeyNames: readonly string[];
|
|
2433
|
+
readonly hookEventNames: readonly string[];
|
|
2434
|
+
readonly hookCommandCount: number;
|
|
2435
|
+
}
|
|
2436
|
+
/** The provider the cascade selects for this directory, as the wiring layer loaded it: its definition, or why it could not be used (no such provider, an invalid or old-format file). */
|
|
2437
|
+
type CheckProviderInput = {
|
|
2438
|
+
readonly name: string;
|
|
2439
|
+
readonly definition: Provider;
|
|
2440
|
+
readonly problem?: never;
|
|
2441
|
+
} | {
|
|
2442
|
+
readonly name: string;
|
|
2443
|
+
readonly definition?: never;
|
|
2444
|
+
readonly problem: string;
|
|
2445
|
+
};
|
|
2446
|
+
/**
|
|
2447
|
+
* Which credential a launch here would use, by source kind and target only: a selected provider's credential block always wins; otherwise the identity's own credential block; otherwise the login stored in the identity's directory.
|
|
2448
|
+
*/
|
|
2449
|
+
interface CredentialReport {
|
|
2450
|
+
readonly applies: "provider" | "identity" | "stored-login";
|
|
2451
|
+
readonly provider?: {
|
|
2452
|
+
readonly name: string;
|
|
2453
|
+
readonly credential?: CredentialSummary;
|
|
2454
|
+
readonly problem?: string;
|
|
2455
|
+
};
|
|
2456
|
+
readonly identity?: CredentialSummary;
|
|
2457
|
+
}
|
|
2458
|
+
/** Inputs to `runCheck` — everything already loaded/injected, exactly like the resolver core and the launcher: nothing in this function touches a real filesystem, git repository, clock, or environment itself. */
|
|
2459
|
+
interface RunCheckParams {
|
|
2460
|
+
readonly cwd: string;
|
|
2461
|
+
readonly home: string;
|
|
2462
|
+
readonly claudeHome: string;
|
|
2463
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
2464
|
+
readonly branch?: string;
|
|
2465
|
+
readonly branchDetached?: boolean;
|
|
2466
|
+
readonly nowMs: number;
|
|
2467
|
+
/** Used only to build the fact manifest by reading the canonical `~/.claude` tree — `runCheck` never mutates the farm and never spawns anything. */
|
|
2468
|
+
readonly farmFs: FarmFs;
|
|
2469
|
+
readonly cascade: CascadeInput;
|
|
2470
|
+
readonly classification: {
|
|
2471
|
+
readonly defaults: CategoryClassification;
|
|
2472
|
+
readonly overlay?: CategoryClassificationOverlay;
|
|
2473
|
+
};
|
|
2474
|
+
readonly identityName?: string;
|
|
2475
|
+
readonly identitySource: IdentityDecisionSource;
|
|
2476
|
+
/** The pool the launch selected and how it ranks right now, when it named a pool instead of an identity; `identityName` is then the member it would pick. */
|
|
2477
|
+
readonly poolPick?: PoolPickReport;
|
|
2478
|
+
readonly configProfileName?: string;
|
|
2479
|
+
readonly configProfileSource: ConfigProfileDecisionSource;
|
|
2480
|
+
/** The resolved identity's own `identity.json`, when one was found. */
|
|
2481
|
+
readonly identity?: Identity;
|
|
2482
|
+
/** Raw `settings.json`/`settings.local.json` contents, keyed by filename, undefined when a file does not exist. */
|
|
2483
|
+
readonly settingsFiles: Readonly<Record<string, string | undefined>>;
|
|
2484
|
+
/** Runs `security find-generic-password` for the Keychain diagnostic. Omit to skip that diagnostic entirely (e.g. off macOS, or when no identity/farm root is known). */
|
|
2485
|
+
readonly run?: RunPort;
|
|
2486
|
+
/** The identity's own farm root — `security`'s lookup account. Required alongside `run` for the Keychain diagnostic to run at all. */
|
|
2487
|
+
readonly farmRoot?: string;
|
|
2488
|
+
/** `process.platform` in real use; the Keychain diagnostic only ever runs when this is `"darwin"`. */
|
|
2489
|
+
readonly platform: string;
|
|
2490
|
+
/** The provider a launch here would route through (`launch.provider` in the cascade), when one is selected. */
|
|
2491
|
+
readonly provider?: CheckProviderInput;
|
|
2492
|
+
}
|
|
2493
|
+
/** Everything `agent-shim check` reports about one directory/identity, without touching the farm or spawning anything. */
|
|
2494
|
+
interface CheckReport {
|
|
2495
|
+
readonly identityName?: string;
|
|
2496
|
+
readonly identitySource: IdentityDecisionSource;
|
|
2497
|
+
readonly poolPick?: PoolPickReport;
|
|
2498
|
+
readonly configProfileName?: string;
|
|
2499
|
+
readonly configProfileSource: ConfigProfileDecisionSource;
|
|
2500
|
+
readonly resolved: ResolvedState;
|
|
2501
|
+
readonly decisionLines: readonly string[];
|
|
2502
|
+
readonly projectEncodingAmbiguities: readonly EncodingAmbiguity[];
|
|
2503
|
+
readonly ambientCredential: AmbientCredentialGuardResult;
|
|
2504
|
+
readonly keychain?: KeychainLookupResult;
|
|
2505
|
+
readonly settingsExposure: readonly SettingsExposureReport[];
|
|
2506
|
+
readonly credential: CredentialReport;
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Resolves the full cascade for one directory/identity and reports everything `agent-shim check` documents: every entry's resolved state and which layer/condition decided it, any ambiguous `history/projects/` encoding in scope, and the three always-on diagnostics (ambient-credential exposure, macOS Keychain service name, settings-secrets exposure).
|
|
2510
|
+
*
|
|
2511
|
+
* Deliberately reuses the same cascade machinery a real launch uses — `resolveDecisions` and `buildEntryFacts` — rather than reimplementing any part of resolution. The one thing this function never does that a launch does is touch the farm or spawn anything: it only reads the canonical `~/.claude` tree to build the fact manifest resolution needs, and every other input (cascade, classification, settings file contents, the Keychain lookup) is handed in already loaded.
|
|
2512
|
+
*/
|
|
2513
|
+
declare function runCheck(params: RunCheckParams): CheckReport;
|
|
2514
|
+
/** Renders a full `CheckReport` as plain text lines, in the order `agent-shim check` prints them. */
|
|
2515
|
+
declare function formatCheckReport(report: CheckReport): string[];
|
|
2516
|
+
/**
|
|
2517
|
+
* The `check --json` form of a report: every field the text form prints, as plain data (no `Map`s, no compiled matchers), so a script can read the same verdicts a person would.
|
|
2518
|
+
*/
|
|
2519
|
+
declare function checkReportToJson(report: CheckReport): Record<string, unknown>;
|
|
2520
|
+
/**
|
|
2521
|
+
* Whether a report carries anything `check --strict` fails on: a resolver diagnostic of warning or error severity, an ambiguous `history/projects/` encoding, an ambient credential a launch would refuse, or a selected provider a launch could not use. Informational diagnostics and the settings-exposure advisory (names and counts only, for a person to review) never fail it.
|
|
2522
|
+
*/
|
|
2523
|
+
declare function checkReportHasWarnings(report: CheckReport): boolean;
|
|
2524
|
+
/** What `collectCheckReport` needs from its caller. */
|
|
2525
|
+
interface CollectCheckReportParams {
|
|
2526
|
+
readonly paths: LayoutPaths;
|
|
2527
|
+
/** The directory a launch would run in; resolved against the working directory by the caller. */
|
|
2528
|
+
readonly cwd: string;
|
|
2529
|
+
/** The identity to check, as `--identity` names it; absent means the identity a launch there would resolve. */
|
|
2530
|
+
readonly identity?: string;
|
|
2531
|
+
readonly env: NodeJS.ProcessEnv;
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Resolves what a launch in `params.cwd` would share or hide, and why, against this machine: the edge adapter that reads the real filesystem, clock, git and keychain, resolves the identity, configuration profile and cascade exactly as a launch does, and hands the facts to the pure `runCheck`. Never touches the farm or spawns claude.
|
|
2535
|
+
*/
|
|
2536
|
+
declare function collectCheckReport(params: CollectCheckReportParams): CheckReport;
|
|
2537
|
+
|
|
2538
|
+
declare const ClaudeShimStateSchema: z.ZodObject<{
|
|
2539
|
+
targetPath: z.ZodString;
|
|
2540
|
+
method: z.ZodEnum<{
|
|
2541
|
+
hardlink: "hardlink";
|
|
2542
|
+
copy: "copy";
|
|
2543
|
+
}>;
|
|
2544
|
+
installedAtMs: z.ZodNumber;
|
|
2545
|
+
}, z.core.$strict>;
|
|
2546
|
+
type ClaudeShimState = z.infer<typeof ClaudeShimStateSchema>;
|
|
2547
|
+
type PathShadowStatus = {
|
|
2548
|
+
readonly status: "ok";
|
|
2549
|
+
} | {
|
|
2550
|
+
readonly status: "not-on-path";
|
|
2551
|
+
} | {
|
|
2552
|
+
readonly status: "shadowed";
|
|
2553
|
+
readonly by: string;
|
|
2554
|
+
};
|
|
2555
|
+
|
|
2556
|
+
/** The result of a successful discovery. */
|
|
2557
|
+
interface DiscoveredClaudeBinary {
|
|
2558
|
+
/** Full path to the discovered `claude` executable. */
|
|
2559
|
+
path: string;
|
|
2560
|
+
/** Which strategy found it. */
|
|
2561
|
+
source: "versions-dir" | "path-fallback";
|
|
2562
|
+
/** The version string, when discovered via the versions directory. */
|
|
2563
|
+
version?: string;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
type DoctorSeverity = "pass" | "warn" | "fail";
|
|
2567
|
+
type DoctorSection = "ambient-credential" | "binary-discovery" | "claude-shim" | "path-resolution" | "legacy-name" | "config-profile" | "identity" | "pool" | "provider" | "keychain" | "directory-rules" | "global-config" | "categories-local" | "active-identity" | "headroom";
|
|
2568
|
+
/** One line of `agent-shim doctor`'s report. `subject` names the identity/profile/rule the finding is about, when the section has more than one of those. */
|
|
2569
|
+
interface DoctorFinding {
|
|
2570
|
+
readonly section: DoctorSection;
|
|
2571
|
+
readonly subject?: string;
|
|
2572
|
+
readonly severity: DoctorSeverity;
|
|
2573
|
+
readonly message: string;
|
|
2574
|
+
}
|
|
2575
|
+
/** The full result of `runDoctor`. `ok` is false iff any finding is a `fail` — a `warn` never fails the report on its own. */
|
|
2576
|
+
interface DoctorReport {
|
|
2577
|
+
readonly findings: readonly DoctorFinding[];
|
|
2578
|
+
readonly ok: boolean;
|
|
2579
|
+
}
|
|
2580
|
+
/** One identity's raw `identity.json`, unparsed — `runDoctor` does its own JSON.parse/schema validation so one malformed file never aborts the rest of the report. */
|
|
2581
|
+
interface DoctorIdentityInput {
|
|
2582
|
+
readonly name: string;
|
|
2583
|
+
readonly path: string;
|
|
2584
|
+
readonly raw: string | undefined;
|
|
2585
|
+
/** The identity's own farm root (`identitiesDir/<name>`) — the account name the Keychain lookup uses. */
|
|
2586
|
+
readonly farmRoot: string;
|
|
2587
|
+
}
|
|
2588
|
+
/** One configuration profile's raw `<name>.json`, unparsed. */
|
|
2589
|
+
interface DoctorConfigProfileInput {
|
|
2590
|
+
readonly name: string;
|
|
2591
|
+
readonly path: string;
|
|
2592
|
+
readonly raw: string | undefined;
|
|
2593
|
+
}
|
|
2594
|
+
/** One provider's raw `<name>.json`, unparsed. */
|
|
2595
|
+
interface DoctorProviderInput {
|
|
2596
|
+
readonly name: string;
|
|
2597
|
+
readonly path: string;
|
|
2598
|
+
readonly raw: string | undefined;
|
|
2599
|
+
}
|
|
2600
|
+
/** A single optional top-level file `runDoctor` validates against a schema when present — absent is a legitimate, unconfigured state, not a failure. */
|
|
2601
|
+
interface DoctorFileInput {
|
|
2602
|
+
readonly path: string;
|
|
2603
|
+
readonly raw: string | undefined;
|
|
2604
|
+
}
|
|
2605
|
+
/** The outcome of resolving the real Claude Code binary, pre-resolved by the wiring layer since `discoverClaudeBinary` is not itself a parse-shaped pure operation and already has its own dedicated test coverage. */
|
|
2606
|
+
type DoctorBinaryDiscovery = {
|
|
2607
|
+
readonly ok: true;
|
|
2608
|
+
readonly binary: DiscoveredClaudeBinary;
|
|
2609
|
+
} | {
|
|
2610
|
+
readonly ok: false;
|
|
2611
|
+
readonly message: string;
|
|
2612
|
+
};
|
|
2613
|
+
/**
|
|
2614
|
+
* Where a bare command name resolves for the two names this tool owns.
|
|
2615
|
+
*
|
|
2616
|
+
* `ownExecutablePath` is this process's own PATH-visible location (`realOwnExecutablePath()`); `agentShim` is `findPathShadow`'s verdict for a bare `agent-shim` against the directory that executable lives in. `claude` is only populated when a shim is actually enabled — without one, a `claude` on PATH is Claude Code's own binary, which is not a shadow of anything.
|
|
2617
|
+
*/
|
|
2618
|
+
interface DoctorPathResolution {
|
|
2619
|
+
readonly ownExecutablePath: string;
|
|
2620
|
+
readonly agentShim: PathShadowStatus;
|
|
2621
|
+
readonly claude?: PathShadowStatus;
|
|
2622
|
+
}
|
|
2623
|
+
/** Everything `runDoctor` needs, all of it already loaded/injected — nothing in `runDoctor` itself reads a file, shells out, or touches the farm. */
|
|
2624
|
+
interface RunDoctorParams {
|
|
2625
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
2626
|
+
readonly identities: readonly DoctorIdentityInput[];
|
|
2627
|
+
readonly configProfiles: readonly DoctorConfigProfileInput[];
|
|
2628
|
+
readonly providers: readonly DoctorProviderInput[];
|
|
2629
|
+
readonly directoryRules: DoctorFileInput;
|
|
2630
|
+
readonly globalConfig: DoctorFileInput;
|
|
2631
|
+
readonly categoriesLocal: DoctorFileInput;
|
|
2632
|
+
readonly activeIdentity: DoctorFileInput;
|
|
2633
|
+
readonly binaryDiscovery: DoctorBinaryDiscovery;
|
|
2634
|
+
/** Whether `agent-shim shim enable` has been run, and whether its recorded target still exists on disk — pre-resolved by the wiring layer, since checking a file's existence is real I/O, not a parse-shaped pure operation. */
|
|
2635
|
+
readonly claudeShim: {
|
|
2636
|
+
readonly state: ClaudeShimState | undefined;
|
|
2637
|
+
readonly targetExists: boolean;
|
|
2638
|
+
};
|
|
2639
|
+
/** Which executables a bare `agent-shim` (and, when the shim is enabled, a bare `claude`) would actually run — pre-resolved by the wiring layer, since scanning PATH is real I/O. */
|
|
2640
|
+
readonly pathResolution: DoctorPathResolution;
|
|
2641
|
+
/** The resolved state root (`paths.root`), whose directory name says whether this installation still lives under the former `.claude-use` name. */
|
|
2642
|
+
readonly rootPath: string;
|
|
2643
|
+
/** Runs `security find-generic-password` for the per-identity Keychain check. Omit to skip that check entirely (e.g. off macOS). */
|
|
2644
|
+
readonly run?: RunPort;
|
|
2645
|
+
/** `process.platform` in real use; the Keychain check only ever runs when this is `"darwin"`. */
|
|
2646
|
+
readonly platform: string;
|
|
2647
|
+
/** The headroom daemon's state.json plus a zombie-aware liveness predicate for the pids it names (a defunct daemon holds no port but still answers signal 0). Omit the raw text when the daemon has never run; that is a pass, not a failure. */
|
|
2648
|
+
readonly headroom: {
|
|
2649
|
+
readonly state: DoctorFileInput;
|
|
2650
|
+
readonly isRunning: (pid: number) => boolean;
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
/**
|
|
2654
|
+
* Audits the whole `~/.agent-shim` config graph for internal consistency: every identity, every configuration profile's own `extends` chain, every provider, `directory-rules.json`, `config.json`, `categories.local.json`, `active-identity`, plus real Claude Code binary discoverability and ambient-credential exposure.
|
|
2655
|
+
*
|
|
2656
|
+
* Deliberately identity/directory-agnostic, unlike `runCheck` — there is no single cascade to resolve `doctor` against, so it never touches settings-exposure (which only means anything relative to one resolved cascade).
|
|
2657
|
+
*
|
|
2658
|
+
* Every check aggregates rather than throws: a malformed file becomes one `fail` finding for that file, not an aborted report. This is the one place in the codebase that deliberately breaks the "throw a validation error and let it propagate" convention every other command relies on — `doctor`'s whole purpose is to survive a broken file and keep auditing everything else.
|
|
2659
|
+
*/
|
|
2660
|
+
declare function runDoctor(params: RunDoctorParams): DoctorReport;
|
|
2661
|
+
/** Renders a full `DoctorReport` as plain text lines, one section header at a time, in the order `agent-shim doctor` prints them. */
|
|
2662
|
+
declare function formatDoctorReport(report: DoctorReport): string[];
|
|
2663
|
+
/** What `collectDoctorReport` needs from its caller. */
|
|
2664
|
+
interface CollectDoctorReportParams {
|
|
2665
|
+
readonly paths: LayoutPaths;
|
|
2666
|
+
readonly env: NodeJS.ProcessEnv;
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Audits the whole state root against this machine: the edge adapter that enumerates every identity, configuration profile and provider on disk, reads every top-level config file as raw text (never pre-parsing, so one malformed file cannot abort the report), discovers the real Claude Code binary and resolves where a bare command name runs from, and hands the facts to the pure `runDoctor`.
|
|
2670
|
+
*/
|
|
2671
|
+
declare function collectDoctorReport(params: CollectDoctorReportParams): DoctorReport;
|
|
2672
|
+
|
|
2673
|
+
/** 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. */
|
|
2674
|
+
interface CliOverride {
|
|
2675
|
+
readonly categories?: CategoryMap;
|
|
2676
|
+
readonly entries?: Entries;
|
|
2677
|
+
readonly launch?: {
|
|
2678
|
+
readonly provider?: string;
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
/**
|
|
2683
|
+
* 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.
|
|
2684
|
+
*
|
|
2685
|
+
* 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.
|
|
2686
|
+
*/
|
|
2687
|
+
interface FarmRuntime {
|
|
2688
|
+
readonly fs: FarmFs;
|
|
2689
|
+
/** The canonical `~/.claude` every farm symlink points back into. */
|
|
2690
|
+
readonly claudeHome: string;
|
|
2691
|
+
readonly home: string;
|
|
2692
|
+
readonly cwd: string;
|
|
2693
|
+
/** The git branch at `cwd`, for `when: { branch }` conditions. Undefined when `cwd` is not in a repository. */
|
|
2694
|
+
readonly branch?: string;
|
|
2695
|
+
readonly branchDetached?: boolean;
|
|
2696
|
+
readonly classification: {
|
|
2697
|
+
readonly defaults: CategoryClassification;
|
|
2698
|
+
readonly overlay?: CategoryClassificationOverlay;
|
|
2699
|
+
};
|
|
2700
|
+
/** 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. */
|
|
2701
|
+
readonly loadCascade: (baseConfigProfile: string | undefined, cliOverride: CliOverride | undefined) => CascadeInput;
|
|
2702
|
+
readonly now: () => number;
|
|
2703
|
+
/** Distinguishes this process's scratch and superseded farm directories from any other's. */
|
|
2704
|
+
readonly uniqueSuffix: string;
|
|
2705
|
+
readonly lock: {
|
|
2706
|
+
readonly pid: number;
|
|
2707
|
+
readonly isRunning: (pid: number) => boolean;
|
|
2708
|
+
readonly sleep: (ms: number) => void;
|
|
2709
|
+
readonly staleAfterMs?: number;
|
|
2710
|
+
readonly retryDelayMs?: number;
|
|
2711
|
+
readonly maxAttempts?: number;
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
/** Inputs to `prepareLaunch`: everything a launch needs except the means of spawning the child. */
|
|
2715
|
+
interface PrepareLaunchParams {
|
|
2716
|
+
readonly paths: LayoutPaths;
|
|
2717
|
+
readonly fs: FsPort;
|
|
2718
|
+
readonly proc: ProcPort;
|
|
2719
|
+
readonly log: LogPort;
|
|
2720
|
+
/** 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. */
|
|
2721
|
+
readonly resolveClaudeBinary: () => DiscoveredClaudeBinary;
|
|
2722
|
+
/** 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. */
|
|
2723
|
+
readonly directoryPinnedIdentity?: string;
|
|
2724
|
+
/** A directory rule's `configProfile` selection for `$PWD`. Accepted as an already-resolved value for the same reason. */
|
|
2725
|
+
readonly directoryRuleConfigProfile?: string;
|
|
2726
|
+
/** 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. */
|
|
2727
|
+
readonly cliFlagConfigProfile?: string;
|
|
2728
|
+
/** The user-global `~/.agent-shim/config.json` default configuration profile, when one is configured. */
|
|
2729
|
+
readonly globalDefaultConfigProfile?: string;
|
|
2730
|
+
/** The user-global `pools`, which a `pool:<name>` selector picks a member of. */
|
|
2731
|
+
readonly pools?: Readonly<Record<string, Pool>>;
|
|
2732
|
+
/** Wires the farm resync. Omitted only by a caller that has no farm to manage. */
|
|
2733
|
+
readonly farm?: FarmRuntime;
|
|
2734
|
+
/** 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. */
|
|
2735
|
+
readonly headroom?: HeadroomPort;
|
|
2736
|
+
/** 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. */
|
|
2737
|
+
readonly frontdoor?: FrontDoorPort;
|
|
2738
|
+
/** 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. */
|
|
2739
|
+
readonly credentials?: CredentialPort;
|
|
2740
|
+
/** 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. */
|
|
2741
|
+
readonly allowMissingConfigProfile?: boolean;
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* 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.
|
|
2745
|
+
*/
|
|
2746
|
+
interface LaunchPlan {
|
|
2747
|
+
readonly bin: string;
|
|
2748
|
+
readonly args: readonly string[];
|
|
2749
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
2750
|
+
/** Releases the daemon session registrations this launch holds. Call it when the child has exited; it is safe to call more than once. */
|
|
2751
|
+
readonly release: () => void;
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Prepares one `claude` launch, in order:
|
|
2755
|
+
*
|
|
2756
|
+
* `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.
|
|
2757
|
+
*
|
|
2758
|
+
* 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.
|
|
2759
|
+
*
|
|
2760
|
+
* 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.
|
|
2761
|
+
*/
|
|
2762
|
+
declare function prepareLaunch(params: PrepareLaunchParams): LaunchPlan;
|
|
2763
|
+
|
|
2764
|
+
/** 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. */
|
|
2765
|
+
declare class LaunchRefusedError extends CliError {
|
|
2766
|
+
readonly exitCode: number;
|
|
2767
|
+
constructor(message: string, exitCode: number);
|
|
2768
|
+
}
|
|
2769
|
+
/** What `prepareClaudeLaunch` needs from its caller. */
|
|
2770
|
+
interface PrepareClaudeLaunchOptions {
|
|
2771
|
+
/** The arguments after `agent-shim run`, exactly as the command line takes them: `@name`, launch flags, then the arguments for `claude`. */
|
|
2772
|
+
readonly argv: readonly string[];
|
|
2773
|
+
/** The directory the launch is for. */
|
|
2774
|
+
readonly cwd: string;
|
|
2775
|
+
/** The environment the launch inherits; a variable it sets is what the child sees. */
|
|
2776
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
2777
|
+
/** The state root; defaults to the one the command line resolves. */
|
|
2778
|
+
readonly paths?: LayoutPaths;
|
|
2779
|
+
/** 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. */
|
|
2780
|
+
readonly agentShim?: string;
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* 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.
|
|
2784
|
+
*
|
|
2785
|
+
* 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.
|
|
2786
|
+
*/
|
|
2787
|
+
declare function prepareClaudeLaunch(options: PrepareClaudeLaunchOptions): LaunchPlan;
|
|
2788
|
+
|
|
2789
|
+
export { AMBIENT_CREDENTIAL_VARS, CONNECT_INTERCEPT_HOST, CONNECT_INTERCEPT_HOSTS, CONNECT_LIMITS, CategoryClassificationOverlaySchema, CategoryClassificationSchema, CategoryMapSchema, CliError, ConfigProfileSchema, CredentialCacheSchema, CredentialSchema, CredentialSourceSchema, DirectoryRuleAlreadyExistsError, DirectoryRuleMissingTargetError, DirectoryRuleNotFoundError, DirectoryRuleSchema, DirectoryRulesSchema, EXIT_FAILURE, EXIT_USAGE, EntryValueSchema, FARM_MANIFEST_FILENAME, FrontDoorStartError, GlobalConfigSchema, HeadroomStartError, IdentityAlreadyExistsError, IdentityNotFoundError, IdentitySchema, InvalidCategoryNameError, InvalidIdentityNameError, InvalidProviderNameError, LaunchRefusedError, LegacyProviderFileError, PoolNameSchema, PoolNotFoundError, PoolSchema, PortableConfigSchema, ProfileAlreadyExistsError, ProfileNotFoundError, ProviderAlreadyExistsError, ProviderKindMismatchError, ProviderNotFoundError, ProviderSchema, ROUTED_PATH_PREFIX, UsageError, UsageSnapshotError, UsageSnapshotSchema, WhenSchema, addDirectoryRule, addIdentity, addPool, addProvider, buildEntryFacts, buildLayoutPaths, carryOver, checkReportHasWarnings, checkReportToJson, collectCheckReport, collectDoctorReport, createLeafCache, createProfile, describeProviderEndpoint, detectAmbientCredential, ensureCa, ensureFrontDoor, ensureHeadroom, evaluateAmbientCredentialGuard, formatAmbientCredentialGuardMessage, formatCheckReport, formatDoctorReport, forwardableHeaders, generateCa, identityExists, isIdentityDirectoryName, isInterceptedHost, listDirectoryRules, listIdentities, listProfiles, listProviders, listUsageSnapshots, mintLeaf, parseConnectTarget, prepareClaudeLaunch, prepareLaunch, profileExists, providerExists, readActiveIdentity, readDirectoryRules, readFarmManifest, readGlobalConfig, readIdentity, readPools, readProfile, readProvider, readUsageSnapshot, realConnectCertStore, realConnectEffects, recoverFarm, recoveryDiagnostics, removeDirectoryRule, removeIdentity, removePool, removeProfile, removeProvider, requirePool, resolveAgentShimHome, resolveClaudeHome, resolveDecisions, resolveLayoutPaths, resolveSupervisorConfig, resyncFarm, runCheck, runDoctor, runFrontDoorSupervisor, runSupervisor, servedByPipeline, setAllowAmbientCredential, setDefaultConfigProfile, setGlobalDefaultProfile, setIdentityCredential, setPool, setProfileCategories, setProfileEntries, setProfileLaunchFlags, setProfileMetadata, snapshotPath, startConnectServer, topLevelNames, updateDirectoryRule, updateProvider, useIdentity, writeDirectoryRules };
|
|
2790
|
+
export type { AddDirectoryRuleOptions, AddProviderInput, AmbientCredentialDetection, AmbientCredentialGuardResult, AmbientCredentialVar, BuildEntryFactsParams, CaMaterial, CarryOverParams, CarryOverResult, CategoryMap, CheckReport, CollectCheckReportParams, CollectDoctorReportParams, CompiledRule, ConfigProfile, ConnectCertStore, ConnectEffects, ConnectLimits, ConnectServerConfig, ConnectServerHandle, ConnectTarget, Credential, CredentialSource, Decision, Diagnostic, DirectoryRule, DirectoryRules, DoctorReport, EliminatedRule, EnsureFrontDoorPorts, EnsureHeadroomPorts, EntryFact, EntryFacts, EntryValue, EvaluateAmbientCredentialGuardParams, FlattenedCascade, FrontDoorSupervisorPorts, GlobalConfig, HeadroomSupervisorConfig, Identity, IdentityCredentialChange, IdentityListEntry, IdentityListing, InjectedCredential, LaunchPlan, Layer, LayerId, LayoutPaths, LeafCert, Pool, PortableConfig, PrepareClaudeLaunchOptions, PrepareLaunchParams, ProfileListEntry, Provider, ProviderListEntry, RecoverFarmParams, RecoveryResult, ResolveDecisionsInput, ResolvedState, ResyncFarmParams, ResyncFarmResult, RunCheckParams, RunDoctorParams, RunFrontDoorSupervisorOptions, RunSupervisorOptions, SupervisorPorts, UnreadableIdentityListEntry, UpdateDirectoryRuleOptions, UpdateProviderInput, UsageReadFs, UsageSnapshot, WhenCondition };
|