@ai21/gateway 1.2.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.
Files changed (65) hide show
  1. package/README.md +5 -4
  2. package/dist/agents/claude-code/revert.d.ts +11 -0
  3. package/dist/{revert.js → agents/claude-code/revert.js} +2 -1
  4. package/dist/agents/claude-code/revert.js.map +1 -0
  5. package/dist/agents/claude-code/settings.d.ts +33 -0
  6. package/dist/{config.js → agents/claude-code/settings.js} +6 -57
  7. package/dist/agents/claude-code/settings.js.map +1 -0
  8. package/dist/{snippets.d.ts → agents/claude-code/snippets.d.ts} +1 -18
  9. package/dist/{snippets.js → agents/claude-code/snippets.js} +0 -19
  10. package/dist/agents/claude-code/snippets.js.map +1 -0
  11. package/dist/agents/claude-code/status.d.ts +12 -0
  12. package/dist/agents/claude-code/status.js +42 -0
  13. package/dist/agents/claude-code/status.js.map +1 -0
  14. package/dist/{verify.d.ts → agents/claude-code/verify.d.ts} +14 -54
  15. package/dist/{verify.js → agents/claude-code/verify.js} +2 -133
  16. package/dist/agents/claude-code/verify.js.map +1 -0
  17. package/dist/agents/deps.d.ts +3 -0
  18. package/dist/agents/deps.js +10 -0
  19. package/dist/agents/deps.js.map +1 -0
  20. package/dist/agents/types.d.ts +266 -0
  21. package/dist/agents/types.js +3 -0
  22. package/dist/agents/types.js.map +1 -0
  23. package/dist/cli.js +23 -3
  24. package/dist/cli.js.map +1 -1
  25. package/dist/fs/safeFile.d.ts +19 -0
  26. package/dist/fs/safeFile.js +50 -0
  27. package/dist/fs/safeFile.js.map +1 -0
  28. package/dist/{snapshot.d.ts → fs/snapshot.d.ts} +1 -1
  29. package/dist/{snapshot.js → fs/snapshot.js} +1 -1
  30. package/dist/fs/snapshot.js.map +1 -0
  31. package/dist/login/keychain/linux.d.ts +1 -1
  32. package/dist/login/keychain/linux.js +1 -1
  33. package/dist/login/keychain/linux.js.map +1 -1
  34. package/dist/login/session.d.ts +12 -0
  35. package/dist/login/session.js +23 -5
  36. package/dist/login/session.js.map +1 -1
  37. package/dist/process.d.ts +26 -0
  38. package/dist/process.js +94 -0
  39. package/dist/process.js.map +1 -0
  40. package/dist/prompt.d.ts +5 -7
  41. package/dist/prompt.js +23 -1
  42. package/dist/prompt.js.map +1 -1
  43. package/dist/report.d.ts +5 -8
  44. package/dist/report.js +46 -17
  45. package/dist/report.js.map +1 -1
  46. package/dist/router.d.ts +5 -3
  47. package/dist/router.js +73 -25
  48. package/dist/router.js.map +1 -1
  49. package/dist/status.d.ts +31 -0
  50. package/dist/status.js +107 -0
  51. package/dist/status.js.map +1 -0
  52. package/dist/telemetry/amplitudeHttp.js +1 -0
  53. package/dist/telemetry/amplitudeHttp.js.map +1 -1
  54. package/dist/telemetry/events.d.ts +7 -3
  55. package/dist/telemetry/telemetry.d.ts +6 -2
  56. package/dist/telemetry/telemetry.js +19 -8
  57. package/dist/telemetry/telemetry.js.map +1 -1
  58. package/package.json +3 -3
  59. package/dist/config.d.ts +0 -83
  60. package/dist/config.js.map +0 -1
  61. package/dist/revert.d.ts +0 -60
  62. package/dist/revert.js.map +0 -1
  63. package/dist/snapshot.js.map +0 -1
  64. package/dist/snippets.js.map +0 -1
  65. 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,3 @@
1
+ /** Stand-in for the AI21 key in a preview, before one has been supplied. */
2
+ export const AI21_KEY_PLACEHOLDER = "<your-ai21-api-key>";
3
+ //# sourceMappingURL=types.js.map
@@ -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,13 +7,18 @@ 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 { isExecutableFile, nodeVerifyDeps } from "./verify.js";
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";
14
15
  import { startLoopback } from "./login/loopback.js";
16
+ import { createStatusLine, isInteractive } from "./status.js";
17
+ import { waitForEnter } from "./prompt.js";
15
18
  import { FLUSH_CAP_MS, nodeTelemetry } from "./telemetry/telemetry.js";
16
19
  export { EXIT } from "./result.js";
20
+ /** 128 + SIGINT: what a shell reports for Ctrl-C, so scripts see the usual code. */
21
+ const EXIT_INTERRUPTED = 130;
17
22
  export const USAGE = `ai21-gateway — point a local coding agent at the AI21 Intelligent Gateway
18
23
 
19
24
  Usage:
@@ -126,21 +131,31 @@ export async function run(argv) {
126
131
  case "error":
127
132
  return { code: EXIT.USAGE, out: "", err: `${outcome.message}\n\n${USAGE}\n` };
128
133
  case "args": {
134
+ // `--json` keeps stderr to plain lines too: a consumer may be capturing both streams.
135
+ const status = createStatusLine(process.stderr, !outcome.args.json && isInteractive(process.stderr, process.env));
129
136
  // One stream for login's progress and credential-file warnings, so neither is missed.
130
- const notify = (message) => void process.stderr.write(`${message}\n`);
137
+ // Routed through the status line so a message never lands in the middle of the spinner.
138
+ const notify = (message) => status.log(message);
131
139
  // Everything the CLI needs from the outside world is gathered here, at the
132
140
  // edge, so every layer below stays pure and testable against a temp
133
141
  // directory and a pair of fake streams.
134
142
  const telemetry = nodeTelemetry(outcome.args.urls.gateway, versionForTelemetry(), fetch);
143
+ // Ctrl-C would otherwise leave a half-drawn spinner on the prompt line.
144
+ const interrupt = () => {
145
+ status.stop();
146
+ process.exit(EXIT_INTERRUPTED);
147
+ };
148
+ process.once("SIGINT", interrupt);
135
149
  const result = await dispatch(outcome.args, {
136
150
  paths: { cwd: process.cwd(), home: homedir() },
137
151
  prompt: { input: process.stdin, output: process.stderr },
138
152
  processEnv: process.env,
139
- verify: nodeVerifyDeps(process.env),
153
+ verify: nodeAgentDeps(process.env),
140
154
  precheck: nodePrecheckDeps(),
141
155
  login: {
142
156
  startListener: startLoopback,
143
157
  openBrowser: (url) => openBrowser(url, nodeBrowserDeps()),
158
+ waitForEnter: () => waitForEnter(process.stdin),
144
159
  fetchImpl: fetch,
145
160
  secrets: selectSecretStore({
146
161
  paths: { home: homedir(), warn: notify },
@@ -153,7 +168,12 @@ export async function run(argv) {
153
168
  // stderr, so `--json` on stdout stays a single parseable object even while
154
169
  // login narrates its progress.
155
170
  notify,
171
+ status,
156
172
  telemetry,
173
+ }).finally(() => {
174
+ // Cleared before the caller writes the result, and on a throw, so no frame survives.
175
+ status.stop();
176
+ process.off("SIGINT", interrupt);
157
177
  });
158
178
  // The caller exits right after this returns, which would cut off a send in flight.
159
179
  await telemetry.flush(FLUSH_CAP_MS);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
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,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EAAE,IAAI,EAAkB,MAAM,aAAa,CAAC;AAEnD,oFAAoF;AACpF,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,MAAM,CAAC,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA0DZ,CAAC;AAEV,+EAA+E;AAC/E,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;IACtE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC;IAC7D,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,qFAAqF;AACrF,SAAS,mBAAmB;IAC1B,IAAI,CAAC;QACH,OAAO,WAAW,EAAE,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;QACtE,0EAA0E;QAC1E,kCAAkC;QAClC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,QAAQ;YACnB,GAAG,EAAE,EAAE;YACP,GAAG,EAAE,6GAA6G;SACnH,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAuB;IAC/C,wEAAwE;IACxE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAE5E,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAEnC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;QACvD,KAAK,SAAS;YACZ,OAAO,aAAa,EAAE,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,OAAO,KAAK,IAAI,EAAE,CAAC;QAChF,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,sFAAsF;YACtF,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAClH,sFAAsF;YACtF,wFAAwF;YACxF,MAAM,MAAM,GAAG,CAAC,OAAe,EAAQ,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAE9D,2EAA2E;YAC3E,oEAAoE;YACpE,wCAAwC;YACxC,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,KAAK,CAAC,CAAC;YAEzF,wEAAwE;YACxE,MAAM,SAAS,GAAG,GAAS,EAAE;gBAC3B,MAAM,CAAC,IAAI,EAAE,CAAC;gBACd,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACjC,CAAC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YAElC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE;gBAC1C,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;gBAC9C,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;gBACxD,UAAU,EAAE,OAAO,CAAC,GAAG;gBACvB,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC;gBAClC,QAAQ,EAAE,gBAAgB,EAAE;gBAC5B,KAAK,EAAE;oBACL,aAAa,EAAE,aAAa;oBAC5B,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,CAAC;oBACzD,YAAY,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC;oBAC/C,SAAS,EAAE,KAAK;oBAChB,OAAO,EAAE,iBAAiB,CAAC;wBACzB,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;wBACxC,GAAG,EAAE,oBAAoB;wBACzB,QAAQ,EAAE,OAAO,CAAC,QAAQ;wBAC1B,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE;wBACjF,SAAS,EAAE,aAAa;qBACzB,CAAC;iBACH;gBACD,2EAA2E;gBAC3E,+BAA+B;gBAC/B,MAAM;gBACN,MAAM;gBACN,SAAS;aACV,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;gBACd,qFAAqF;gBACrF,MAAM,CAAC,IAAI,EAAE,CAAC;gBACd,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;YAEH,mFAAmF;YACnF,MAAM,SAAS,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAEpC,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,4BAA4B,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,MAAM;IACb,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAExC,IAAI,CAAC;QACH,8EAA8E;QAC9E,OAAO,YAAY,CAAC,OAAO,CAAC,KAAK,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,IAAI,MAAM,EAAE,EAAE,CAAC;IACb,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE1C,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE1C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC"}
@@ -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"}
@@ -1,4 +1,4 @@
1
- import { type ConfigPaths } from "./config.js";
1
+ import { type ConfigPaths } from "./safeFile.js";
2
2
  /** One file as it was: its bytes and mode, or the fact that it did not exist. */
3
3
  type FileState = {
4
4
  readonly path: string;
@@ -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 "./config.js";
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 "../../verify.js";
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 "../../verify.js";
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,iBAAiB,CAAC;AAE9D,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"}
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"}
@@ -6,6 +6,11 @@ import type { SecretStore } from "./secretStore.js";
6
6
  export interface LoginDeps {
7
7
  readonly startListener: (options: LoopbackOptions) => Promise<Listener>;
8
8
  readonly openBrowser: (url: string) => boolean;
9
+ /**
10
+ * Holds the flow until the user presses Enter; false if input ended instead. Absent,
11
+ * the browser opens straight away, which is what tests and non-interactive callers get.
12
+ */
13
+ readonly waitForEnter?: () => Promise<boolean>;
9
14
  /** For the token exchange and workspace listing; injected so tests make no calls. */
10
15
  readonly fetchImpl: FetchLike;
11
16
  /** Where the refresh token and the minted key are kept; injected so tests use neither. */
@@ -26,7 +31,14 @@ export type LoginOutcome = {
26
31
  export interface LoginProgress {
27
32
  /** The caller decides which stream this lands on. */
28
33
  readonly notify: (message: string) => void;
34
+ /** Called once the browser has been sent to the sign-in page and the wait begins. */
35
+ readonly waiting?: () => void;
29
36
  }
37
+ /**
38
+ * The code framed so the eye lands on it: it is the whole defence against another
39
+ * process asking to sign in, and in running prose it was easy to skim past.
40
+ */
41
+ export declare function codeBox(code: string): string;
30
42
  /**
31
43
  * The consent screen's URL.
32
44
  *
@@ -6,6 +6,15 @@
6
6
  import { randomInt } from "node:crypto";
7
7
  import { CLIENT_ID, CLIENT_PARAM, CLI_LOGIN_PATH, CODE_PARAM, EXPIRES_PARAM, REDIRECT_PARAM, STATE_PARAM, verificationCode, } from "./contract.js";
8
8
  import { LOGIN_TIMEOUT_MS } from "./loopback.js";
9
+ /**
10
+ * The code framed so the eye lands on it: it is the whole defence against another
11
+ * process asking to sign in, and in running prose it was easy to skim past.
12
+ */
13
+ export function codeBox(code) {
14
+ const inner = ` verification code: ${code} `;
15
+ const rule = "─".repeat([...inner].length);
16
+ return ` ╭${rule}╮\n │${inner}│\n ╰${rule}╯`;
17
+ }
9
18
  /**
10
19
  * The consent screen's URL.
11
20
  *
@@ -25,7 +34,18 @@ export function loginUrl(urls, callbackUrl, state, code, expiresAtSeconds) {
25
34
  }
26
35
  export async function runLogin(urls, deps, io) {
27
36
  const code = verificationCode((max) => randomInt(max));
28
- // Stamped early, so the page gives up a moment before the listener, not after.
37
+ /* The code comes first and the browser only on Enter, so it is read before the browser
38
+ * takes focus. The listener starts after that too: its clock must not run while the
39
+ * user is still reading. */
40
+ io.notify(`Sign in to AI21 to continue.\n\n${codeBox(code)}\n\n` +
41
+ `The sign-in page will show this code. If it shows anything else, choose Deny —\n` +
42
+ `something other than this terminal is asking to sign in.`);
43
+ if (deps.waitForEnter !== undefined) {
44
+ io.notify(`\nPress Enter to open ${new URL(urls.app).host} in your browser…`);
45
+ if (!(await deps.waitForEnter()))
46
+ return { kind: "declined", reason: "it was cancelled in the terminal" };
47
+ }
48
+ // Stamped here, so the page gives up a moment before the listener, not after.
29
49
  const expiresAtSeconds = Math.floor((Date.now() + LOGIN_TIMEOUT_MS) / 1000);
30
50
  let listener;
31
51
  try {
@@ -40,10 +60,8 @@ export async function runLogin(urls, deps, io) {
40
60
  /* Printed as well as opened: `openBrowser` cannot tell us a browser appeared, and over
41
61
  * SSH the URL is the only way in. */
42
62
  deps.openBrowser(listener.signInUrl);
43
- io.notify(`Sign in to continue — your browser should open. If it does not, visit:\n\n ${listener.signInUrl}\n\n` +
44
- ` verification code: ${code}\n\n` +
45
- `The page will show this code. If it shows anything else, choose Deny — something\n` +
46
- `other than this terminal is asking to sign in.\n\nWaiting…`);
63
+ io.notify(`\nIf the browser did not open, visit:\n\n ${listener.signInUrl}`);
64
+ io.waiting?.();
47
65
  return settle(await listener.result);
48
66
  }
49
67
  function settle(outcome) {
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/login/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/login/session.ts"],"names":[],"mappings":"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"}
@@ -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;