@ai21/gateway 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/agents/claude-code/revert.d.ts +11 -0
- package/dist/{revert.js → agents/claude-code/revert.js} +2 -1
- package/dist/agents/claude-code/revert.js.map +1 -0
- package/dist/agents/claude-code/settings.d.ts +33 -0
- package/dist/{config.js → agents/claude-code/settings.js} +6 -57
- package/dist/agents/claude-code/settings.js.map +1 -0
- package/dist/{snippets.d.ts → agents/claude-code/snippets.d.ts} +1 -18
- package/dist/{snippets.js → agents/claude-code/snippets.js} +0 -19
- package/dist/agents/claude-code/snippets.js.map +1 -0
- package/dist/agents/claude-code/status.d.ts +12 -0
- package/dist/agents/claude-code/status.js +42 -0
- package/dist/agents/claude-code/status.js.map +1 -0
- package/dist/{verify.d.ts → agents/claude-code/verify.d.ts} +14 -54
- package/dist/{verify.js → agents/claude-code/verify.js} +2 -133
- package/dist/agents/claude-code/verify.js.map +1 -0
- package/dist/agents/deps.d.ts +3 -0
- package/dist/agents/deps.js +10 -0
- package/dist/agents/deps.js.map +1 -0
- package/dist/agents/types.d.ts +266 -0
- package/dist/agents/types.js +3 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/fs/safeFile.d.ts +19 -0
- package/dist/fs/safeFile.js +50 -0
- package/dist/fs/safeFile.js.map +1 -0
- package/dist/{snapshot.d.ts → fs/snapshot.d.ts} +1 -1
- package/dist/{snapshot.js → fs/snapshot.js} +1 -1
- package/dist/fs/snapshot.js.map +1 -0
- package/dist/login/keychain/linux.d.ts +1 -1
- package/dist/login/keychain/linux.js +1 -1
- package/dist/login/keychain/linux.js.map +1 -1
- package/dist/process.d.ts +26 -0
- package/dist/process.js +94 -0
- package/dist/process.js.map +1 -0
- package/dist/report.d.ts +3 -8
- package/dist/report.js.map +1 -1
- package/dist/router.d.ts +2 -3
- package/dist/router.js +26 -16
- package/dist/router.js.map +1 -1
- package/dist/telemetry/amplitudeHttp.js +1 -0
- package/dist/telemetry/amplitudeHttp.js.map +1 -1
- package/dist/telemetry/events.d.ts +7 -3
- package/dist/telemetry/telemetry.d.ts +6 -2
- package/dist/telemetry/telemetry.js +19 -8
- package/dist/telemetry/telemetry.js.map +1 -1
- package/package.json +3 -3
- package/dist/config.d.ts +0 -83
- package/dist/config.js.map +0 -1
- package/dist/revert.d.ts +0 -60
- package/dist/revert.js.map +0 -1
- package/dist/snapshot.js.map +0 -1
- package/dist/snippets.js.map +0 -1
- package/dist/verify.js.map +0 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract every coding agent implements. The router owns the flow that is the same
|
|
3
|
+
* for all of them — resolve the key, check it, gate on a subscription, snapshot, write,
|
|
4
|
+
* verify, undo when there is no subscription, report — and asks the agent for everything
|
|
5
|
+
* that differs: where its config lives, what goes in it, how to take it out again, how
|
|
6
|
+
* to tell whether its user is signed in, and how to prove the result works.
|
|
7
|
+
*
|
|
8
|
+
* Outcomes are agent-neutral (paths and key names are data), so `report.ts` renders any
|
|
9
|
+
* agent's result and reads only the agent's `text` for the words that name it.
|
|
10
|
+
*/
|
|
11
|
+
import type { Client, ResolvedUrls, Target } from "../args.js";
|
|
12
|
+
import type { ConfigPaths } from "../fs/safeFile.js";
|
|
13
|
+
import type { PathLookup, RunCommand } from "../process.js";
|
|
14
|
+
/** What every agent's config is rendered from. */
|
|
15
|
+
export interface SnippetInputs {
|
|
16
|
+
readonly gatewayUrl: string;
|
|
17
|
+
readonly agentId: string;
|
|
18
|
+
readonly ai21Key: string;
|
|
19
|
+
/** Signed-in user id for per-user attribution. Absent on a normal CLI run. */
|
|
20
|
+
readonly userId?: string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Marks the traffic as configured by this tool, e.g. `ai21-gateway-cli/0.4.0`.
|
|
23
|
+
*
|
|
24
|
+
* Set on what the CLI writes, and deliberately absent from the snippet builders the
|
|
25
|
+
* contract tests pin against the webapp: a config someone pasted by hand did not
|
|
26
|
+
* come from here and must not claim to have. Lets the gateway tell the two apart
|
|
27
|
+
* without the CLI reporting anything itself.
|
|
28
|
+
*/
|
|
29
|
+
readonly client?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Stand-in for the AI21 key in a preview, before one has been supplied. */
|
|
32
|
+
export declare const AI21_KEY_PLACEHOLDER = "<your-ai21-api-key>";
|
|
33
|
+
/** Injected rather than read from the process, so tests can use a temp directory. */
|
|
34
|
+
export interface PathContext {
|
|
35
|
+
readonly cwd: string;
|
|
36
|
+
readonly home: string;
|
|
37
|
+
}
|
|
38
|
+
/** What an agent may read to locate its config — some honour an env override. */
|
|
39
|
+
export interface AgentEnv extends PathContext {
|
|
40
|
+
readonly processEnv: Record<string, string | undefined>;
|
|
41
|
+
}
|
|
42
|
+
export interface AgentPaths extends ConfigPaths {
|
|
43
|
+
/**
|
|
44
|
+
* Where to run the agent's own CLI so that the file we wrote is the one it reads.
|
|
45
|
+
* Home for a user-level config, since a project-local one would outrank it.
|
|
46
|
+
*/
|
|
47
|
+
readonly verifyCwd: string;
|
|
48
|
+
}
|
|
49
|
+
export interface Conflict {
|
|
50
|
+
readonly key: string;
|
|
51
|
+
readonly current: string;
|
|
52
|
+
readonly desired: string;
|
|
53
|
+
}
|
|
54
|
+
export type ConfigOutcome = {
|
|
55
|
+
readonly kind: "created";
|
|
56
|
+
readonly path: string;
|
|
57
|
+
} | {
|
|
58
|
+
readonly kind: "updated";
|
|
59
|
+
readonly path: string;
|
|
60
|
+
readonly backup: string;
|
|
61
|
+
readonly changed: readonly string[];
|
|
62
|
+
} | {
|
|
63
|
+
readonly kind: "unchanged";
|
|
64
|
+
readonly path: string;
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: "conflict";
|
|
67
|
+
readonly path: string;
|
|
68
|
+
readonly conflicts: readonly Conflict[];
|
|
69
|
+
} | {
|
|
70
|
+
readonly kind: "malformed";
|
|
71
|
+
readonly path: string;
|
|
72
|
+
readonly reason: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly kind: "failed";
|
|
75
|
+
readonly path: string;
|
|
76
|
+
readonly reason: string;
|
|
77
|
+
};
|
|
78
|
+
export type RevertOutcome = {
|
|
79
|
+
readonly kind: "reverted";
|
|
80
|
+
readonly path: string;
|
|
81
|
+
/** Keys we added, so reverting means deleting them. */
|
|
82
|
+
readonly removed: readonly string[];
|
|
83
|
+
/** Keys that held a value before we wrote, now put back. */
|
|
84
|
+
readonly restored: readonly string[];
|
|
85
|
+
/** A `.bak` was there but could not be parsed, so prior values were lost. */
|
|
86
|
+
readonly backupUnreadable: boolean;
|
|
87
|
+
} | {
|
|
88
|
+
readonly kind: "restored";
|
|
89
|
+
readonly path: string;
|
|
90
|
+
readonly backup: string;
|
|
91
|
+
}
|
|
92
|
+
/** None of the managed keys were there — not ours to revert. */
|
|
93
|
+
| {
|
|
94
|
+
readonly kind: "not-configured";
|
|
95
|
+
readonly path: string;
|
|
96
|
+
} | {
|
|
97
|
+
readonly kind: "agent-mismatch";
|
|
98
|
+
readonly path: string;
|
|
99
|
+
readonly expected: string;
|
|
100
|
+
readonly actual: string;
|
|
101
|
+
}
|
|
102
|
+
/** `--from-backup` would discard edits made since our write. */
|
|
103
|
+
| {
|
|
104
|
+
readonly kind: "drifted";
|
|
105
|
+
readonly path: string;
|
|
106
|
+
readonly backup: string;
|
|
107
|
+
} | {
|
|
108
|
+
readonly kind: "no-backup";
|
|
109
|
+
readonly path: string;
|
|
110
|
+
readonly backup: string;
|
|
111
|
+
} | {
|
|
112
|
+
readonly kind: "malformed";
|
|
113
|
+
readonly path: string;
|
|
114
|
+
readonly reason: string;
|
|
115
|
+
} | {
|
|
116
|
+
readonly kind: "failed";
|
|
117
|
+
readonly path: string;
|
|
118
|
+
readonly reason: string;
|
|
119
|
+
};
|
|
120
|
+
export interface RevertOptions {
|
|
121
|
+
/** Restore the `.bak` verbatim instead of removing our keys surgically. */
|
|
122
|
+
readonly fromBackup: boolean;
|
|
123
|
+
/** Proceed with a restore that discards drift. */
|
|
124
|
+
readonly force: boolean;
|
|
125
|
+
/** When set, the stored `x-agent-id` must equal this or nothing is written. */
|
|
126
|
+
readonly agentIdGuard?: string | undefined;
|
|
127
|
+
}
|
|
128
|
+
/** Whether the agent's own login can carry requests through the gateway. */
|
|
129
|
+
export type SubscriptionStatus = {
|
|
130
|
+
readonly kind: "present";
|
|
131
|
+
}
|
|
132
|
+
/** Definitely unusable; `reason` keys into `AgentText.noSubscription`. */
|
|
133
|
+
| {
|
|
134
|
+
readonly kind: "missing";
|
|
135
|
+
readonly reason: string;
|
|
136
|
+
}
|
|
137
|
+
/** Could not tell — never blocks an install. */
|
|
138
|
+
| {
|
|
139
|
+
readonly kind: "inconclusive";
|
|
140
|
+
};
|
|
141
|
+
/** Whether the agent can run on this machine at all, before anything else does. */
|
|
142
|
+
export type InstallStatus = {
|
|
143
|
+
readonly kind: "present";
|
|
144
|
+
} | {
|
|
145
|
+
readonly kind: "missing";
|
|
146
|
+
};
|
|
147
|
+
/** What `checkInstalled` may consult: an upstream key can stand in for a missing binary. */
|
|
148
|
+
export interface InstallContext {
|
|
149
|
+
readonly upstreamKey?: string | undefined;
|
|
150
|
+
readonly processEnv: Record<string, string | undefined>;
|
|
151
|
+
}
|
|
152
|
+
export interface VerifyContext {
|
|
153
|
+
readonly urls: ResolvedUrls;
|
|
154
|
+
readonly agentId: string;
|
|
155
|
+
readonly ai21Key: string;
|
|
156
|
+
readonly userId?: string | undefined;
|
|
157
|
+
/** The agent's own upstream credential for a direct check, when it has a flag for one. */
|
|
158
|
+
readonly upstreamKey?: string | undefined;
|
|
159
|
+
readonly processEnv: Record<string, string | undefined>;
|
|
160
|
+
/** `AgentPaths.verifyCwd`. */
|
|
161
|
+
readonly cwd: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* How verification ended. Only `no-subscription` changes the exit code. `via` names the
|
|
165
|
+
* tier that ran and `reason` the missing credential; both key into the agent's `text`.
|
|
166
|
+
*/
|
|
167
|
+
export type VerifyOutcome = {
|
|
168
|
+
readonly kind: "disabled";
|
|
169
|
+
} | {
|
|
170
|
+
readonly kind: "skipped";
|
|
171
|
+
readonly hint: string;
|
|
172
|
+
} | {
|
|
173
|
+
readonly kind: "passed";
|
|
174
|
+
readonly via: string;
|
|
175
|
+
} | {
|
|
176
|
+
readonly kind: "failed";
|
|
177
|
+
readonly via: string;
|
|
178
|
+
readonly reason: string;
|
|
179
|
+
} | {
|
|
180
|
+
readonly kind: "no-subscription";
|
|
181
|
+
readonly via: string;
|
|
182
|
+
readonly reason: string;
|
|
183
|
+
};
|
|
184
|
+
/** Just enough of `fetch` to send one request. `text` is optional; absent means inconclusive. */
|
|
185
|
+
export type FetchLike = (url: string, init: {
|
|
186
|
+
method: string;
|
|
187
|
+
headers: Record<string, string>;
|
|
188
|
+
body: string;
|
|
189
|
+
signal: AbortSignal;
|
|
190
|
+
}) => Promise<{
|
|
191
|
+
ok: boolean;
|
|
192
|
+
status: number;
|
|
193
|
+
text?: () => Promise<string>;
|
|
194
|
+
}>;
|
|
195
|
+
/** The outside world an agent touches while checking a login or verifying. */
|
|
196
|
+
export interface AgentDeps {
|
|
197
|
+
readonly lookup: PathLookup;
|
|
198
|
+
readonly runCommand: RunCommand;
|
|
199
|
+
readonly fetchImpl: FetchLike;
|
|
200
|
+
}
|
|
201
|
+
export interface Warning {
|
|
202
|
+
/** Stable identifier, so `--json` consumers match on this and not on prose. */
|
|
203
|
+
readonly code: string;
|
|
204
|
+
readonly message: string;
|
|
205
|
+
}
|
|
206
|
+
/** Flags that feed only a verification tier some agents have. */
|
|
207
|
+
export type VerifyFlag = "anthropic-key";
|
|
208
|
+
/** What the no-subscription refusal says for one `SubscriptionStatus`/`VerifyOutcome` reason. */
|
|
209
|
+
export interface NoSubscriptionText {
|
|
210
|
+
readonly headline: string;
|
|
211
|
+
readonly nextStep: string;
|
|
212
|
+
/** Stable `--json` reason, spelled for a funnel rather than for a person. */
|
|
213
|
+
readonly jsonReason: string;
|
|
214
|
+
}
|
|
215
|
+
/** What the not-installed refusal says. `jsonOutcome` and `jsonReason` are a contract: never renamed. */
|
|
216
|
+
export interface NotInstalledText {
|
|
217
|
+
readonly message: string;
|
|
218
|
+
readonly jsonOutcome: string;
|
|
219
|
+
readonly jsonReason: string;
|
|
220
|
+
}
|
|
221
|
+
/** Every user-facing phrase that names the agent or its tooling. */
|
|
222
|
+
export interface AgentText {
|
|
223
|
+
/** The `--json` `outcome` of the no-subscription refusal. A contract: never renamed. */
|
|
224
|
+
readonly noSubscriptionOutcome: string;
|
|
225
|
+
readonly noSubscription: Readonly<Record<string, NoSubscriptionText>>;
|
|
226
|
+
/** How each verification tier reads once it has passed, keyed by `VerifyOutcome.via`. */
|
|
227
|
+
readonly verifiedVia: Readonly<Record<string, string>>;
|
|
228
|
+
/** Why verification was skipped, before the agent's manual hint. */
|
|
229
|
+
readonly skippedBecause: string;
|
|
230
|
+
readonly notInstalled: NotInstalledText;
|
|
231
|
+
/** The status line while `checkSubscription` runs: "Checking your Claude login…". */
|
|
232
|
+
readonly checkingLogin: string;
|
|
233
|
+
/** The done line once `checkSubscription` found a usable login: "Claude login found". */
|
|
234
|
+
readonly loginFound: string;
|
|
235
|
+
}
|
|
236
|
+
export interface CodingAgent {
|
|
237
|
+
readonly id: Client;
|
|
238
|
+
/** "Claude Code", as a sentence would name it. */
|
|
239
|
+
readonly displayName: string;
|
|
240
|
+
/** The config file's name, for help and messages: "settings.json". */
|
|
241
|
+
readonly configLabel: string;
|
|
242
|
+
/** Which of `--user` / `--repo` this agent can be configured at. */
|
|
243
|
+
readonly targets: readonly Target[];
|
|
244
|
+
readonly verifyFlags: readonly VerifyFlag[];
|
|
245
|
+
/** Checked first by install, even under --no-verify: `missing` stops it before sign-in or any write. */
|
|
246
|
+
checkInstalled(ctx: InstallContext, deps: AgentDeps): InstallStatus;
|
|
247
|
+
resolvePaths(target: Target, env: AgentEnv): AgentPaths;
|
|
248
|
+
/** What `--print` shows: the file as text, and the fields `--json` adds beside target and urls. */
|
|
249
|
+
preview(inputs: SnippetInputs): {
|
|
250
|
+
readonly text: string;
|
|
251
|
+
readonly json: Readonly<Record<string, unknown>>;
|
|
252
|
+
};
|
|
253
|
+
/** Merge our config in. Never throws; nothing is written on `conflict` or `malformed`. */
|
|
254
|
+
write(paths: AgentPaths, inputs: SnippetInputs, options: {
|
|
255
|
+
readonly force: boolean;
|
|
256
|
+
}): ConfigOutcome;
|
|
257
|
+
revert(paths: AgentPaths, options: RevertOptions): RevertOutcome;
|
|
258
|
+
/** Blanks the AI21 key in a config value, so conflict output never prints it. */
|
|
259
|
+
redact(value: string): string;
|
|
260
|
+
/** Agent-specific things that make a correct config look broken later. */
|
|
261
|
+
warnings(paths: AgentPaths, processEnv: Record<string, string | undefined>): Warning[];
|
|
262
|
+
checkSubscription(paths: AgentPaths, deps: AgentDeps): Promise<SubscriptionStatus>;
|
|
263
|
+
/** Exactly one real request through the gateway, or none. */
|
|
264
|
+
verify(ctx: VerifyContext, deps: AgentDeps): Promise<VerifyOutcome>;
|
|
265
|
+
readonly text: AgentText;
|
|
266
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAgCA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,8 @@ import { parseCliArgs } from "./args.js";
|
|
|
7
7
|
import { EXIT } from "./result.js";
|
|
8
8
|
import { dispatch } from "./router.js";
|
|
9
9
|
import { nodePrecheckDeps } from "./precheck.js";
|
|
10
|
-
import {
|
|
10
|
+
import { nodeAgentDeps } from "./agents/deps.js";
|
|
11
|
+
import { isExecutableFile } from "./process.js";
|
|
11
12
|
import { nodeBrowserDeps, openBrowser } from "./login/browser.js";
|
|
12
13
|
import { selectSecretStore } from "./login/keychain/select.js";
|
|
13
14
|
import { runCredentialCommand } from "./login/keychain/runner.js";
|
|
@@ -149,7 +150,7 @@ export async function run(argv) {
|
|
|
149
150
|
paths: { cwd: process.cwd(), home: homedir() },
|
|
150
151
|
prompt: { input: process.stdin, output: process.stderr },
|
|
151
152
|
processEnv: process.env,
|
|
152
|
-
verify:
|
|
153
|
+
verify: nodeAgentDeps(process.env),
|
|
153
154
|
precheck: nodePrecheckDeps(),
|
|
154
155
|
login: {
|
|
155
156
|
startListener: startLoopback,
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,IAAI,EAAkB,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A config file and the backup kept beside it. */
|
|
2
|
+
export interface ConfigPaths {
|
|
3
|
+
readonly settings: string;
|
|
4
|
+
readonly backup: string;
|
|
5
|
+
}
|
|
6
|
+
/** A symlinked config is backed up beside its target, not beside the link. */
|
|
7
|
+
export declare function backupPathFor(paths: ConfigPaths, realPath: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Turns an fs error into something safe to print. The code is translated rather
|
|
10
|
+
* than forwarded so no stack frame, errno struct or internal path escapes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function describeFsError(err: unknown): string;
|
|
13
|
+
/**
|
|
14
|
+
* Write via a sibling temp file and rename. The rename is atomic within a
|
|
15
|
+
* filesystem, so a reader either sees the old file or the new one — never a
|
|
16
|
+
* half-written config. The temp file is removed on any failure so a crashed run
|
|
17
|
+
* leaves no litter next to the real file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function atomicWrite(target: string, contents: string | Buffer): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing a file someone else owns: atomically, owner-only, with errors that are safe
|
|
3
|
+
* to print. Shared by every coding agent's config writer, revert and snapshot.
|
|
4
|
+
*/
|
|
5
|
+
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
/** A symlinked config is backed up beside its target, not beside the link. */
|
|
7
|
+
export function backupPathFor(paths, realPath) {
|
|
8
|
+
return realPath === paths.settings ? paths.backup : `${realPath}.bak`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Turns an fs error into something safe to print. The code is translated rather
|
|
12
|
+
* than forwarded so no stack frame, errno struct or internal path escapes.
|
|
13
|
+
*/
|
|
14
|
+
export function describeFsError(err) {
|
|
15
|
+
const code = err?.code;
|
|
16
|
+
switch (code) {
|
|
17
|
+
case "EACCES":
|
|
18
|
+
case "EPERM":
|
|
19
|
+
return "permission denied";
|
|
20
|
+
case "ENOSPC":
|
|
21
|
+
return "no space left on the device";
|
|
22
|
+
case "EROFS":
|
|
23
|
+
return "the filesystem is read-only";
|
|
24
|
+
case "EISDIR":
|
|
25
|
+
return "it is a directory, not a file";
|
|
26
|
+
default:
|
|
27
|
+
return "the write could not be completed";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Write via a sibling temp file and rename. The rename is atomic within a
|
|
32
|
+
* filesystem, so a reader either sees the old file or the new one — never a
|
|
33
|
+
* half-written config. The temp file is removed on any failure so a crashed run
|
|
34
|
+
* leaves no litter next to the real file.
|
|
35
|
+
*/
|
|
36
|
+
export function atomicWrite(target, contents) {
|
|
37
|
+
// Same directory, so the rename cannot cross a filesystem boundary. The pid
|
|
38
|
+
// keeps two concurrent runs from fighting over one temp name.
|
|
39
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
40
|
+
try {
|
|
41
|
+
// 0600: this file carries an API key.
|
|
42
|
+
writeFileSync(tmp, contents, { mode: 0o600 });
|
|
43
|
+
renameSync(tmp, target);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
rmSync(tmp, { force: true });
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=safeFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeFile.js","sourceRoot":"","sources":["../../src/fs/safeFile.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAQ5D,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,KAAkB,EAAE,QAAgB;IAChE,OAAO,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,MAAM,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,MAAM,IAAI,GAAI,GAAyC,EAAE,IAAI,CAAC;IAC9D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ,CAAC;QACd,KAAK,OAAO;YACV,OAAO,mBAAmB,CAAC;QAC7B,KAAK,QAAQ;YACX,OAAO,6BAA6B,CAAC;QACvC,KAAK,OAAO;YACV,OAAO,6BAA6B,CAAC;QACvC,KAAK,QAAQ;YACX,OAAO,+BAA+B,CAAC;QACzC;YACE,OAAO,kCAAkC,CAAC;IAC9C,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,QAAyB;IACnE,4EAA4E;IAC5E,8DAA8D;IAC9D,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IAC3C,IAAI,CAAC;QACH,sCAAsC;QACtC,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7B,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { chmodSync, existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
8
8
|
import { dirname, resolve } from "node:path";
|
|
9
|
-
import { atomicWrite, backupPathFor, describeFsError } from "./
|
|
9
|
+
import { atomicWrite, backupPathFor, describeFsError } from "./safeFile.js";
|
|
10
10
|
function capture(path) {
|
|
11
11
|
if (!existsSync(path))
|
|
12
12
|
return { path, present: false };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/fs/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACvH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAoB,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAe9F,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAEvD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC;AAClG,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,QAAgB;IACnC,IAAI,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;IAExD,IAAI,SAAS,CAAC,QAAQ,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,IAAI,EAAE,CAAC;QAC9E,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEjC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;AACvG,CAAC;AAED,SAAS,GAAG,CAAC,KAAgB;IAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IAED,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxC,wEAAwE;IACxE,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,aAAa,CAAC,QAAwB;IACpD,IAAI,CAAC;QACH,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS;YAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1D,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* The secret goes in on stdin and comes out on stdout unchanged, so unlike macOS it needs
|
|
10
10
|
* no encoding.
|
|
11
11
|
*/
|
|
12
|
-
import { type PathLookup } from "../../
|
|
12
|
+
import { type PathLookup } from "../../process.js";
|
|
13
13
|
import type { SecretStore } from "../secretStore.js";
|
|
14
14
|
import { type CommandRunner } from "./runner.js";
|
|
15
15
|
export declare const SECRET_TOOL = "secret-tool";
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* The secret goes in on stdin and comes out on stdout unchanged, so unlike macOS it needs
|
|
10
10
|
* no encoding.
|
|
11
11
|
*/
|
|
12
|
-
import { findOnPath } from "../../
|
|
12
|
+
import { findOnPath } from "../../process.js";
|
|
13
13
|
import { KEYCHAIN_SERVICE, KEYCHAIN_TIMEOUT_MS, accountFor, failureReason } from "./runner.js";
|
|
14
14
|
export const SECRET_TOOL = "secret-tool";
|
|
15
15
|
/** How the entry is labelled in Seahorse or KWalletManager, for a human deleting it. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linux.js","sourceRoot":"","sources":["../../../src/login/keychain/linux.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAmB,MAAM,
|
|
1
|
+
{"version":3,"file":"linux.js","sourceRoot":"","sources":["../../../src/login/keychain/linux.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAmB,MAAM,kBAAkB,CAAC;AAE/D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,EAAE,aAAa,EAAsB,MAAM,aAAa,CAAC;AAEnH,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAEzC,wFAAwF;AACxF,MAAM,KAAK,GAAG,kBAAkB,CAAC;AAWjC,gDAAgD;AAChD,MAAM,UAAU,KAAK,CAAC,SAA6B;IACjD,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/D,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,IAAuB;IACpD,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IAElF,OAAO,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAuB,EAAE,UAAkB;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,gBAAgB,CAAC;IACjD,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,IAAgB,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IACrH,MAAM,GAAG,GAAG,CAAC,IAAuB,EAAE,KAAc,EAAE,EAAE,CACtD,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAExE,MAAM,IAAI,GAAG,KAAK,EAAE,OAAe,EAAE,IAAgB,EAA+B,EAAE;QACpF,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAEpE,qFAAqF;QACrF,IAAI,OAAO,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAEnF,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE;YAAE,OAAO,SAAS,CAAC;QAElE,uFAAuF;QACvF,OAAO,OAAO,CAAC,MAAM,CAAC;IACxB,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,UAAU;QAEhB,IAAI;QAEJ,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK;YAC9B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YAE5F,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9G,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI;YACxB,uFAAuF;YACvF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC3C,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;YAEnD,OAAO,QAAQ,KAAK,SAAS,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface CommandResult {
|
|
2
|
+
/** `null` when the process was killed or could not be started at all. */
|
|
3
|
+
readonly code: number | null;
|
|
4
|
+
readonly timedOut: boolean;
|
|
5
|
+
/** Combined stdout+stderr, used only to explain a failure. */
|
|
6
|
+
readonly output: string;
|
|
7
|
+
}
|
|
8
|
+
/** Runs one executable with a hard time budget; never rejects. */
|
|
9
|
+
export type RunCommand = (executable: string, args: readonly string[], opts: {
|
|
10
|
+
readonly cwd: string;
|
|
11
|
+
readonly timeoutMs: number;
|
|
12
|
+
}) => Promise<CommandResult>;
|
|
13
|
+
export interface PathLookup {
|
|
14
|
+
readonly platform: string;
|
|
15
|
+
readonly processEnv: Record<string, string | undefined>;
|
|
16
|
+
/** Injected so the tier-1 tests never depend on what is installed. */
|
|
17
|
+
readonly isExecutableFile: (candidate: string) => boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hand-rolled rather than shelling out to `which`/`where`, which is slower and
|
|
21
|
+
* absent from stripped Windows images. `node:path`'s `join` and `delimiter` follow
|
|
22
|
+
* the *host* platform, so they cannot resolve a Windows PATH under test on macOS.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findOnPath(command: string, lookup: PathLookup): string | undefined;
|
|
25
|
+
export declare const runCommand: RunCommand;
|
|
26
|
+
export declare function isExecutableFile(candidate: string): boolean;
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding and running local executables with a hard time budget. Shared by the coding
|
|
3
|
+
* agents (their own CLIs) and the Linux keychain (`secret-tool`).
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
7
|
+
/** npm installs the Claude Code CLI as `claude.cmd`, so this must not be empty. */
|
|
8
|
+
const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
9
|
+
/**
|
|
10
|
+
* Hand-rolled rather than shelling out to `which`/`where`, which is slower and
|
|
11
|
+
* absent from stripped Windows images. `node:path`'s `join` and `delimiter` follow
|
|
12
|
+
* the *host* platform, so they cannot resolve a Windows PATH under test on macOS.
|
|
13
|
+
*/
|
|
14
|
+
export function findOnPath(command, lookup) {
|
|
15
|
+
const windows = lookup.platform === "win32";
|
|
16
|
+
const separator = windows ? ";" : ":";
|
|
17
|
+
const raw = lookup.processEnv.PATH ?? (windows ? lookup.processEnv.Path : undefined) ?? "";
|
|
18
|
+
const extensions = windows ? (lookup.processEnv.PATHEXT ?? DEFAULT_PATHEXT).split(";") : [""];
|
|
19
|
+
for (const dir of raw.split(separator)) {
|
|
20
|
+
if (dir === "")
|
|
21
|
+
continue;
|
|
22
|
+
for (const extension of extensions) {
|
|
23
|
+
const candidate = `${dir.replace(/[\\/]+$/, "")}${windows ? "\\" : "/"}${command}${extension}`;
|
|
24
|
+
if (lookup.isExecutableFile(candidate))
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Cap on captured subprocess output — only the first line is ever quoted. */
|
|
31
|
+
const MAX_CAPTURED_OUTPUT = 4_096;
|
|
32
|
+
export const runCommand = (executable, args, opts) => {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
// A .cmd cannot be spawned without a shell since the CVE-2024-27980 fix. Safe
|
|
35
|
+
// here because every argument is a literal. Killing the shell may orphan the
|
|
36
|
+
// real `claude`, which exits on its own shortly after.
|
|
37
|
+
const shell = /\.(cmd|bat)$/i.test(executable);
|
|
38
|
+
const child = spawn(shell ? `"${executable}"` : executable, [...args], {
|
|
39
|
+
cwd: opts.cwd,
|
|
40
|
+
shell,
|
|
41
|
+
// stdin closed: `claude` must never sit waiting on input we will not send.
|
|
42
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
43
|
+
});
|
|
44
|
+
let output = "";
|
|
45
|
+
const capture = (chunk) => {
|
|
46
|
+
if (output.length < MAX_CAPTURED_OUTPUT)
|
|
47
|
+
output += chunk.toString("utf8");
|
|
48
|
+
};
|
|
49
|
+
child.stdout?.on("data", capture);
|
|
50
|
+
child.stderr?.on("data", capture);
|
|
51
|
+
let done = false;
|
|
52
|
+
const settle = (result) => {
|
|
53
|
+
if (done)
|
|
54
|
+
return;
|
|
55
|
+
done = true;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
resolve(result);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Our own timer, not spawn's `timeout`: Windows reports a killed process as an
|
|
61
|
+
* ordinary non-zero exit, so an inferred timeout would be wrong there.
|
|
62
|
+
*
|
|
63
|
+
* It resolves the promise itself rather than killing and waiting for `close`.
|
|
64
|
+
* `close` fires when the *stdio* closes, and anything `claude` spawned inherits
|
|
65
|
+
* those pipes — an MCP server or a hook outliving its parent holds them open, so
|
|
66
|
+
* the kill lands and `close` never comes. That hung the CLI indefinitely after a
|
|
67
|
+
* write it had already completed. The budget is now a guarantee, not a hope.
|
|
68
|
+
*/
|
|
69
|
+
let timedOut = false;
|
|
70
|
+
const timer = setTimeout(() => {
|
|
71
|
+
timedOut = true;
|
|
72
|
+
child.kill("SIGKILL");
|
|
73
|
+
settle({ code: null, timedOut, output: output.trim() });
|
|
74
|
+
}, opts.timeoutMs);
|
|
75
|
+
// A spawn error may never emit `close`, so both paths settle. Only the errno is
|
|
76
|
+
// kept — the message carries an absolute path.
|
|
77
|
+
child.once("error", (err) => settle({ code: null, timedOut, output: err.code ?? "" }));
|
|
78
|
+
child.once("close", (code) => settle({ code, timedOut, output: output.trim() }));
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
export function isExecutableFile(candidate) {
|
|
82
|
+
try {
|
|
83
|
+
if (!statSync(candidate).isFile())
|
|
84
|
+
return false;
|
|
85
|
+
// The execute bit is meaningless on Windows, where the extension decides.
|
|
86
|
+
if (process.platform !== "win32")
|
|
87
|
+
accessSync(candidate, constants.X_OK);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../src/process.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAwB1D,mFAAmF;AACnF,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAE9C;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,MAAkB;IAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC5C,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACtC,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC3F,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAE9F,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,IAAI,GAAG,KAAK,EAAE;YAAE,SAAS;QAEzB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,OAAO,GAAG,SAAS,EAAE,CAAC;YAC/F,IAAI,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC;gBAAE,OAAO,SAAS,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAElC,MAAM,CAAC,MAAM,UAAU,GAAe,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;IAC/D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,8EAA8E;QAC9E,6EAA6E;QAC7E,uDAAuD;QACvD,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;YACrE,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK;YACL,2EAA2E;YAC3E,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,CAAC,KAAa,EAAQ,EAAE;YACtC,IAAI,MAAM,CAAC,MAAM,GAAG,mBAAmB;gBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC5E,CAAC,CAAC;QAEF,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAElC,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,CAAC,MAAqB,EAAQ,EAAE;YAC7C,IAAI,IAAI;gBAAE,OAAO;YAEjB,IAAI,GAAG,IAAI,CAAC;YACZ,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC;QAEF;;;;;;;;;WASG;QACH,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1D,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAEnB,gFAAgF;QAChF,+CAA+C;QAC/C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAG,GAA6B,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAClH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,IAAI,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE;YAAE,OAAO,KAAK,CAAC;QAEhD,0EAA0E;QAC1E,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAAE,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAExE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/report.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { ResolvedUrls } from "./args.js";
|
|
2
|
-
import type { ConfigOutcome } from "./
|
|
2
|
+
import type { ConfigOutcome, RevertOutcome, Warning } from "./agents/types.js";
|
|
3
3
|
import type { PrecheckOutcome } from "./precheck.js";
|
|
4
|
-
import type {
|
|
5
|
-
import type { MissingCredential, VerifyOutcome } from "./verify.js";
|
|
4
|
+
import type { MissingCredential, VerifyOutcome } from "./agents/claude-code/verify.js";
|
|
6
5
|
import { type RunResult } from "./result.js";
|
|
7
6
|
/**
|
|
8
7
|
* Outcome name → exit code. This is the map `--json` consumers branch on, so the
|
|
@@ -27,11 +26,7 @@ export interface ColorTargets {
|
|
|
27
26
|
/** `NOTHING_WRITTEN`, painted for stderr — the stream every refusal that says it lands on. */
|
|
28
27
|
export declare function nothingWasWritten(streams?: ColorTargets): string;
|
|
29
28
|
export declare function reportRevert(outcome: RevertOutcome, json: boolean, streams?: ColorTargets): RunResult;
|
|
30
|
-
export
|
|
31
|
-
/** Stable identifier, so `--json` consumers match on this and not on prose. */
|
|
32
|
-
readonly code: "anthropic-api-key-set" | "shadowed-by-local-config" | "non-production-gateway" | "insecure-gateway" | "verification-skipped" | "verification-failed" | "key-check-inconclusive";
|
|
33
|
-
readonly message: string;
|
|
34
|
-
}
|
|
29
|
+
export type { Warning } from "./agents/types.js";
|
|
35
30
|
export interface WarningContext {
|
|
36
31
|
readonly urls: ResolvedUrls;
|
|
37
32
|
/** The process environment, injected so tests need not mutate the real one. */
|