@ai21/gateway 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +16 -3
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.js +38 -4
  4. package/dist/cli.js.map +1 -1
  5. package/dist/login/lock.d.ts +6 -0
  6. package/dist/login/lock.js +16 -1
  7. package/dist/login/lock.js.map +1 -1
  8. package/dist/login/session.d.ts +12 -0
  9. package/dist/login/session.js +23 -5
  10. package/dist/login/session.js.map +1 -1
  11. package/dist/precheck.d.ts +1 -0
  12. package/dist/precheck.js +1 -1
  13. package/dist/precheck.js.map +1 -1
  14. package/dist/prompt.d.ts +5 -7
  15. package/dist/prompt.js +23 -1
  16. package/dist/prompt.js.map +1 -1
  17. package/dist/report.d.ts +4 -0
  18. package/dist/report.js +59 -17
  19. package/dist/report.js.map +1 -1
  20. package/dist/result.d.ts +2 -0
  21. package/dist/result.js +2 -0
  22. package/dist/result.js.map +1 -1
  23. package/dist/router.d.ts +6 -0
  24. package/dist/router.js +157 -31
  25. package/dist/router.js.map +1 -1
  26. package/dist/status.d.ts +31 -0
  27. package/dist/status.js +107 -0
  28. package/dist/status.js.map +1 -0
  29. package/dist/telemetry/amplitudeHttp.d.ts +28 -0
  30. package/dist/telemetry/amplitudeHttp.js +53 -0
  31. package/dist/telemetry/amplitudeHttp.js.map +1 -0
  32. package/dist/telemetry/events.d.ts +46 -0
  33. package/dist/telemetry/events.js +10 -0
  34. package/dist/telemetry/events.js.map +1 -0
  35. package/dist/telemetry/telemetry.d.ts +27 -0
  36. package/dist/telemetry/telemetry.js +78 -0
  37. package/dist/telemetry/telemetry.js.map +1 -0
  38. package/dist/telemetry/transport.d.ts +10 -0
  39. package/dist/telemetry/transport.js +2 -0
  40. package/dist/telemetry/transport.js.map +1 -0
  41. package/dist/verify.d.ts +2 -0
  42. package/dist/verify.js +2 -1
  43. package/dist/verify.js.map +1 -1
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -15,14 +15,14 @@ sends one request to prove it works. With no key stored it signs you in through
15
15
  browser first and creates an AI21 key for you, all in the one command — pass `--key`
16
16
  only for scripting, since a key in argv stays in your shell history.
17
17
 
18
- The key is checked against the gateway *before* anything is written, so a key the
18
+ The key is checked against the gateway _before_ anything is written, so a key the
19
19
  gateway will not take — or an agent id the key does not carry — stops the command
20
20
  instead of leaving a config that fails every later request. There is no flag to skip
21
21
  that check, and it needs none: only a definite rejection stops the install. If the
22
22
  gateway cannot be reached, or answers something we cannot read, the config is written
23
23
  and a warning says the key went unchecked.
24
24
 
25
- `--no-verify` turns off the *post-write* check — the one that proves end to end that
25
+ `--no-verify` turns off the _post-write_ check — the one that proves end to end that
26
26
  Claude Code reaches the gateway. It does not affect the pre-write key check.
27
27
 
28
28
  The gateway passes your own Claude credential through, so without a Claude login it
@@ -32,6 +32,11 @@ leaves `settings.json` as it was before the run, and exits 9. A timeout or any o
32
32
  verification failure only warns and keeps the config. `--no-verify` skips this too, for
33
33
  machines where Claude Code is signed in later.
34
34
 
35
+ The gateway currently supports only the Claude Code CLI. With no `claude` on PATH and
36
+ no Anthropic key (`--anthropic-key` or `ANTHROPIC_API_KEY`), `install` stops before
37
+ signing in, writes nothing, and exits 10, even under `--no-verify`. With an Anthropic
38
+ key it goes ahead and verifies with a direct request instead.
39
+
35
40
  The dashboard prints this command for you (Add agent, or Reconnect on an existing
36
41
  agent) and offers the key as a separate copy.
37
42
 
@@ -95,6 +100,14 @@ the only one supported so far — an unsupported name is refused rather than
95
100
  defaulted, so a `--client` we do not implement never writes Claude Code's file by
96
101
  mistake.
97
102
 
103
+ `install` reports onboarding events to AI21's product analytics (Amplitude), so we can
104
+ see where setup fails: when it started, then whether it completed or at which step it
105
+ failed, with the CLI version, OS, workspace id and a random per-run id. A failure is
106
+ reported as a fixed reason, never the message you saw, so no key, email, path, agent id
107
+ or argument is ever sent. Setup counts as completed only once the written config has
108
+ been verified. Only the production and staging gateways report; any other `--gateway`
109
+ sends nothing. Reporting never delays or fails a command (`src/telemetry/`).
110
+
98
111
  > **Internal only.** The package is published to AI21's Artifact Registry, not
99
112
  > public npm, so a bare `npx` cannot reach it yet — see below.
100
113
 
@@ -116,7 +129,7 @@ last tag. `package.json` stays at `0.0.0`; the git tag is the record.
116
129
  `403` misleads: it reads as though the AI21 gateway rejected you, but it is npm
117
130
  failing to reach the registry — before the CLI has run at all.
118
131
 
119
- The last one catches people out: this project *is* `@ai21/gateway`, so npx
132
+ The last one catches people out: this project _is_ `@ai21/gateway`, so npx
120
133
  resolves the name to the working directory instead of the registry, finds no
121
134
  linked binary, and fails. The published artifact can never be exercised from its
122
135
  own source tree — use a scratch directory.
package/dist/cli.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { type RunResult } from "./result.js";
3
3
  export { EXIT, type RunResult } from "./result.js";
4
- export declare const USAGE = "ai21-gateway \u2014 point a local coding agent at the AI21 Intelligent Gateway\n\nUsage:\n ai21-gateway install --agent-id <id> configure this machine (default command)\n ai21-gateway login sign in and create an AI21 key\n ai21-gateway uninstall revert what this tool wrote\n ai21-gateway --help\n ai21-gateway --version\n\nInstall flags:\n --agent-id <id> agent to attribute requests to (required)\n --key <key> AI21 key. Omit it and the CLI uses the key from your signed-in\n session, signing you in through the browser if you are not\n already. A key passed here is kept in your shell history\n --workspace <id> workspace to create the key in; the default is chosen for you\n --user-id <id> adds the x-ai21-user-id attribution header\n --client <name> which agent to configure; 'claude-code' (default) is the only\n one supported so far\n --gateway <url> override the gateway URL outright\n --user | --repo write to the home directory (default) or repo-local\n --yes overwrite an existing conflicting value\n --print render the resolved configuration instead of writing it\n (the key is always masked)\n --json machine-readable output\n\nLogin flags:\n --workspace <id> workspace to create the key in. Without it one is chosen for\n you and named in the output\n --gateway <url> override the gateway URL outright\n --app <url> the webapp origin serving the sign-in page\n\n'login' opens your browser, creates an AI21 key in your workspace and stores it\nunder ~/.config/ai21-gateway. 'install' does this for you when you have no key\nyet, so it is only needed on its own to sign in ahead of time or to move to a\ndifferent workspace.\n\nUninstall flags:\n --agent-id <id> guard: refuse unless the stored agent matches\n --from-backup restore settings.json.bak verbatim instead of removing\n only this tool's keys \u2014 discards later edits\n --yes proceed with a --from-backup restore that discards changes\n --user | --repo which settings.json to revert (home by default)\n\nBy default 'uninstall' removes only the keys this tool wrote, leaving unrelated\nsettings and any later edits of your own intact.\n\nVerification flags:\n --anthropic-key <key> Anthropic credential for the verifying request; used\n only when 'claude' is not on PATH, never written to disk\n --no-verify write the configuration without verifying it\n\nAfter a successful write the CLI sends one request through the gateway to confirm\nthe configuration works: 'claude -p' when it is on PATH, otherwise a direct\nrequest if an Anthropic credential is available. With neither, verification is\nskipped with a warning \u2014 it never changes the exit code.\n\nRe-running the same command is a no-op. An existing settings.json is backed up\nto settings.json.bak before it is changed, and is left untouched if the CLI\nrefuses.";
4
+ export declare const USAGE = "ai21-gateway \u2014 point a local coding agent at the AI21 Intelligent Gateway\n\nUsage:\n ai21-gateway install --agent-id <id> configure this machine (default command)\n ai21-gateway login sign in and create an AI21 key\n ai21-gateway uninstall revert what this tool wrote\n ai21-gateway --help\n ai21-gateway --version\n\nInstall flags:\n --agent-id <id> agent to attribute requests to (required)\n --key <key> AI21 key. Omit it and the CLI uses the key from your signed-in\n session, signing you in through the browser if you are not\n already. A key passed here is kept in your shell history\n --workspace <id> workspace to create the key in; the default is chosen for you\n --user-id <id> adds the x-ai21-user-id attribution header\n --client <name> which agent to configure; 'claude-code' (default) is the only\n one supported so far\n --gateway <url> override the gateway URL outright\n --user | --repo write to the home directory (default) or repo-local\n --yes overwrite an existing conflicting value\n --print render the resolved configuration instead of writing it\n (the key is always masked)\n --json machine-readable output\n\nLogin flags:\n --workspace <id> workspace to create the key in. Without it one is chosen for\n you and named in the output\n --gateway <url> override the gateway URL outright\n --app <url> the webapp origin serving the sign-in page\n\n'login' opens your browser, creates an AI21 key in your workspace and stores it\nunder ~/.config/ai21-gateway. 'install' does this for you when you have no key\nyet, so it is only needed on its own to sign in ahead of time or to move to a\ndifferent workspace.\n\nUninstall flags:\n --agent-id <id> guard: refuse unless the stored agent matches\n --from-backup restore settings.json.bak verbatim instead of removing\n only this tool's keys \u2014 discards later edits\n --yes proceed with a --from-backup restore that discards changes\n --user | --repo which settings.json to revert (home by default)\n\nBy default 'uninstall' removes only the keys this tool wrote, leaving unrelated\nsettings and any later edits of your own intact.\n\nVerification flags:\n --anthropic-key <key> Anthropic credential for the verifying request; used\n only when 'claude' is not on PATH, never written to disk\n --no-verify write the configuration without verifying it\n\nAfter a successful write the CLI sends one request through the gateway to confirm\nthe configuration works: 'claude -p' when it is on PATH, otherwise a direct\nrequest if an Anthropic credential is available. With neither, 'install' stops\nbefore signing in and writes nothing: only the Claude Code CLI is supported.\n\nRe-running the same command is a no-op. An existing settings.json is backed up\nto settings.json.bak before it is changed, and is left untouched if the CLI\nrefuses.";
5
5
  export declare function readVersion(): string;
6
6
  /**
7
7
  * Pure entry point: argv in, streams-and-exit-code out. Keeping I/O at the edge
package/dist/cli.js CHANGED
@@ -12,7 +12,12 @@ import { nodeBrowserDeps, openBrowser } from "./login/browser.js";
12
12
  import { selectSecretStore } from "./login/keychain/select.js";
13
13
  import { runCredentialCommand } from "./login/keychain/runner.js";
14
14
  import { startLoopback } from "./login/loopback.js";
15
+ import { createStatusLine, isInteractive } from "./status.js";
16
+ import { waitForEnter } from "./prompt.js";
17
+ import { FLUSH_CAP_MS, nodeTelemetry } from "./telemetry/telemetry.js";
15
18
  export { EXIT } from "./result.js";
19
+ /** 128 + SIGINT: what a shell reports for Ctrl-C, so scripts see the usual code. */
20
+ const EXIT_INTERRUPTED = 130;
16
21
  export const USAGE = `ai21-gateway — point a local coding agent at the AI21 Intelligent Gateway
17
22
 
18
23
  Usage:
@@ -66,8 +71,8 @@ Verification flags:
66
71
 
67
72
  After a successful write the CLI sends one request through the gateway to confirm
68
73
  the configuration works: 'claude -p' when it is on PATH, otherwise a direct
69
- request if an Anthropic credential is available. With neither, verification is
70
- skipped with a warning — it never changes the exit code.
74
+ request if an Anthropic credential is available. With neither, 'install' stops
75
+ before signing in and writes nothing: only the Claude Code CLI is supported.
71
76
 
72
77
  Re-running the same command is a no-op. An existing settings.json is backed up
73
78
  to settings.json.bak before it is changed, and is left untouched if the CLI
@@ -84,6 +89,15 @@ export function readVersion() {
84
89
  }
85
90
  return version;
86
91
  }
92
+ /** A corrupt install still reports, rather than failing a command over analytics. */
93
+ function versionForTelemetry() {
94
+ try {
95
+ return readVersion();
96
+ }
97
+ catch {
98
+ return "unknown";
99
+ }
100
+ }
87
101
  function versionResult() {
88
102
  try {
89
103
  return { code: EXIT.OK, out: `${readVersion()}\n`, err: "" };
@@ -116,12 +130,22 @@ export async function run(argv) {
116
130
  case "error":
117
131
  return { code: EXIT.USAGE, out: "", err: `${outcome.message}\n\n${USAGE}\n` };
118
132
  case "args": {
133
+ // `--json` keeps stderr to plain lines too: a consumer may be capturing both streams.
134
+ const status = createStatusLine(process.stderr, !outcome.args.json && isInteractive(process.stderr, process.env));
119
135
  // One stream for login's progress and credential-file warnings, so neither is missed.
120
- const notify = (message) => void process.stderr.write(`${message}\n`);
136
+ // Routed through the status line so a message never lands in the middle of the spinner.
137
+ const notify = (message) => status.log(message);
121
138
  // Everything the CLI needs from the outside world is gathered here, at the
122
139
  // edge, so every layer below stays pure and testable against a temp
123
140
  // directory and a pair of fake streams.
124
- return dispatch(outcome.args, {
141
+ const telemetry = nodeTelemetry(outcome.args.urls.gateway, versionForTelemetry(), fetch);
142
+ // Ctrl-C would otherwise leave a half-drawn spinner on the prompt line.
143
+ const interrupt = () => {
144
+ status.stop();
145
+ process.exit(EXIT_INTERRUPTED);
146
+ };
147
+ process.once("SIGINT", interrupt);
148
+ const result = await dispatch(outcome.args, {
125
149
  paths: { cwd: process.cwd(), home: homedir() },
126
150
  prompt: { input: process.stdin, output: process.stderr },
127
151
  processEnv: process.env,
@@ -130,6 +154,7 @@ export async function run(argv) {
130
154
  login: {
131
155
  startListener: startLoopback,
132
156
  openBrowser: (url) => openBrowser(url, nodeBrowserDeps()),
157
+ waitForEnter: () => waitForEnter(process.stdin),
133
158
  fetchImpl: fetch,
134
159
  secrets: selectSecretStore({
135
160
  paths: { home: homedir(), warn: notify },
@@ -142,7 +167,16 @@ export async function run(argv) {
142
167
  // stderr, so `--json` on stdout stays a single parseable object even while
143
168
  // login narrates its progress.
144
169
  notify,
170
+ status,
171
+ telemetry,
172
+ }).finally(() => {
173
+ // Cleared before the caller writes the result, and on a throw, so no frame survives.
174
+ status.stop();
175
+ process.off("SIGINT", interrupt);
145
176
  });
177
+ // The caller exits right after this returns, which would cut off a send in flight.
178
+ await telemetry.flush(FLUSH_CAP_MS);
179
+ return result;
146
180
  }
147
181
  }
148
182
  }
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":"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"}
@@ -7,6 +7,12 @@
7
7
  * within microseconds of a crash.
8
8
  */
9
9
  export declare function breakStale(lockPath: string, judgedIno: number): void;
10
+ /**
11
+ * On Windows a lock another process has just deleted lingers, "delete pending", until the
12
+ * last handle on it closes (a waiter's `inspect` is one). Creating it again meanwhile
13
+ * fails with EPERM rather than EEXIST, so there it means "still held, retry".
14
+ */
15
+ export declare function isDeletePending(err: unknown, platform?: NodeJS.Platform): boolean;
10
16
  /**
11
17
  * Runs `change` with the lock held, and releases it however that turns out. The lock's
12
18
  * directory must exist. Throws if the lock is not free within the budget.
@@ -95,6 +95,15 @@ function release(lockPath, ownIno) {
95
95
  throw err;
96
96
  }
97
97
  }
98
+ /**
99
+ * On Windows a lock another process has just deleted lingers, "delete pending", until the
100
+ * last handle on it closes (a waiter's `inspect` is one). Creating it again meanwhile
101
+ * fails with EPERM rather than EEXIST, so there it means "still held, retry".
102
+ */
103
+ export function isDeletePending(err, platform = process.platform) {
104
+ const code = err.code;
105
+ return platform === "win32" && (code === "EPERM" || code === "EACCES");
106
+ }
98
107
  /**
99
108
  * Runs `change` with the lock held, and releases it however that turns out. The lock's
100
109
  * directory must exist. Throws if the lock is not free within the budget.
@@ -103,6 +112,7 @@ export function withLock(lockPath, change, options = {}) {
103
112
  // Overridable so the test for the refusal does not spend the real budget waiting.
104
113
  const deadline = Date.now() + (options.waitBudgetMs ?? WAIT_BUDGET_MS);
105
114
  let fd;
115
+ let deletePending;
106
116
  for (;;) {
107
117
  try {
108
118
  // "wx" is O_CREAT|O_EXCL: the create either wins or fails, with no window between
@@ -111,7 +121,9 @@ export function withLock(lockPath, change, options = {}) {
111
121
  break;
112
122
  }
113
123
  catch (err) {
114
- if (err.code !== "EEXIST")
124
+ if (isDeletePending(err))
125
+ deletePending = err;
126
+ else if (err.code !== "EEXIST")
115
127
  throw err;
116
128
  }
117
129
  const holder = inspect(lockPath);
@@ -120,6 +132,9 @@ export function withLock(lockPath, change, options = {}) {
120
132
  continue;
121
133
  }
122
134
  if (Date.now() >= deadline) {
135
+ // Never contended, only refused: a real permission error, so report it as one.
136
+ if (holder === undefined && deletePending !== undefined)
137
+ throw deletePending;
123
138
  throw new Error(`another ai21-gateway command is writing credentials (${lockPath} held for ${holder?.ageMs ?? "?"}ms). ` +
124
139
  "Wait for it to finish, or delete that file if nothing is running.");
125
140
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/login/lock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/login/lock.ts"],"names":[],"mappings":"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"}
@@ -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":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,OAAO,EACL,SAAS,EACT,YAAY,EACZ,cAAc,EAEd,UAAU,EACV,aAAa,EACb,cAAc,EACd,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAA6D,MAAM,eAAe,CAAC;AA8B5G;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,KAAK,GAAG,uBAAuB,IAAI,GAAG,CAAC;IAC7C,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;IAE3C,OAAO,MAAM,IAAI,SAAS,KAAK,SAAS,IAAI,GAAG,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CACtB,IAAkB,EAClB,WAAmB,EACnB,KAAa,EACb,IAAY,EACZ,gBAAwB;IAExB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAClD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACvC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC9D,gDAAgD;IAChD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IAE9C,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAkB,EAAE,IAAe,EAAE,EAAiB;IACnF,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAEvD;;gCAE4B;IAC5B,EAAE,CAAC,MAAM,CACP,mCAAmC,OAAO,CAAC,IAAI,CAAC,MAAM;QACpD,kFAAkF;QAClF,0DAA0D,CAC7D,CAAC;IACF,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACpC,EAAE,CAAC,MAAM,CAAC,yBAAyB,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC;QAC9E,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;IAC5G,CAAC;IAED,8EAA8E;IAC9E,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;IAE5E,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC;YAClC,aAAa,EAAE,IAAI,CAAC,GAAG;YACvB,SAAS,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,CAAC;SAC9F,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,yCAAyC,EAAE,CAAC;IACpH,CAAC;IAED;yCACqC;IACrC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACrC,EAAE,CAAC,MAAM,CAAC,8CAA8C,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9E,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC;IAEf,OAAO,MAAM,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,MAAM,CAAC,OAAwB;IACtC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7B,KAAK,QAAQ;YACX,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;QACpD,KAAK,UAAU;YACb,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU;gBACxC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE;gBACtD,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;IACvE,CAAC;AACH,CAAC"}
@@ -30,6 +30,7 @@ export type PrecheckOutcome = {
30
30
  } | {
31
31
  readonly kind: "rejected";
32
32
  readonly reason: string;
33
+ readonly status: number;
33
34
  } | {
34
35
  readonly kind: "unknown-agent";
35
36
  readonly known: readonly string[];
package/dist/precheck.js CHANGED
@@ -67,7 +67,7 @@ export async function precheckKey(ctx, deps) {
67
67
  }
68
68
  if (!response.ok) {
69
69
  return REJECTION_STATUSES.has(response.status)
70
- ? { kind: "rejected", reason: "the gateway rejected it" }
70
+ ? { kind: "rejected", reason: "the gateway rejected it", status: response.status }
71
71
  : { kind: "inconclusive", reason: `the gateway answered ${response.status}` };
72
72
  }
73
73
  const ids = agentIdsIn((await response.text?.()) ?? "");
@@ -1 +1 @@
1
- {"version":3,"file":"precheck.js","sourceRoot":"","sources":["../src/precheck.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"precheck.js","sourceRoot":"","sources":["../src/precheck.ts"],"names":[],"mappings":"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"}
package/dist/prompt.d.ts CHANGED
@@ -1,10 +1,3 @@
1
- /**
2
- * The terminal the CLI was run from.
3
- *
4
- * All that is left of the key prompt: pasting a key is error-prone, so the only ways in
5
- * are `--key` and signing in through the browser. What survives is the tty check, which
6
- * decides whether a sign-in can be offered at all.
7
- */
8
1
  import type { Readable, Writable } from "node:stream";
9
2
  export interface PromptIo {
10
3
  readonly input: Readable & {
@@ -15,3 +8,8 @@ export interface PromptIo {
15
8
  /** Overrides the stream's own flag; tests drive a pipe as though it were a tty. */
16
9
  readonly isTTY?: boolean;
17
10
  }
11
+ /**
12
+ * Resolves true on the next Enter, false if the input ends first (Ctrl-D, a closed pipe).
13
+ * The interface is closed either way, so stdin does not keep the process alive.
14
+ */
15
+ export declare function waitForEnter(input: Readable): Promise<boolean>;
package/dist/prompt.js CHANGED
@@ -1,2 +1,24 @@
1
- export {};
1
+ /**
2
+ * The terminal the CLI was run from.
3
+ *
4
+ * All that is left of the key prompt: pasting a key is error-prone, so the only ways in
5
+ * are `--key` and signing in through the browser. What survives is the tty check, which
6
+ * decides whether a sign-in can be offered at all, and the Enter that opens the browser.
7
+ */
8
+ import { createInterface } from "node:readline";
9
+ /**
10
+ * Resolves true on the next Enter, false if the input ends first (Ctrl-D, a closed pipe).
11
+ * The interface is closed either way, so stdin does not keep the process alive.
12
+ */
13
+ export function waitForEnter(input) {
14
+ return new Promise((resolve) => {
15
+ const rl = createInterface({ input });
16
+ let answered = false;
17
+ rl.once("line", () => {
18
+ answered = true;
19
+ rl.close();
20
+ });
21
+ rl.once("close", () => resolve(answered));
22
+ });
23
+ }
2
24
  //# sourceMappingURL=prompt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAUhD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAe;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACtC,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;YACnB,QAAQ,GAAG,IAAI,CAAC;YAChB,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/report.d.ts CHANGED
@@ -80,3 +80,7 @@ export interface NoCredentialInput {
80
80
  * gateway can never answer, so a config left in place only breaks Claude Code.
81
81
  */
82
82
  export declare function reportNoClaudeCredential(input: NoCredentialInput): RunResult;
83
+ /** `login`'s success: who is signed in, and the workspace any key was minted in. */
84
+ export declare function reportSignedIn(email: string, workspace: string, streams?: ColorTargets): RunResult;
85
+ /** Refused before anything else runs: without `claude` there is nothing to configure. */
86
+ export declare function reportNoClaudeCli(json: boolean, streams?: ColorTargets): RunResult;
package/dist/report.js CHANGED
@@ -57,6 +57,17 @@ function painterFor(target) {
57
57
  return PLAIN;
58
58
  return (format, text) => styleText(format, text, { validateStream: false });
59
59
  }
60
+ const MARK = { green: "✔", red: "✖", yellow: "⚠" };
61
+ /**
62
+ * A result line in its tone, led by a symbol so pass and fail read apart at a glance.
63
+ * The symbol rides with colour: plain output stays byte-identical, which is the
64
+ * contract every existing output assertion and log scraper relies on.
65
+ */
66
+ function headline(paint, tone, text) {
67
+ if (paint === PLAIN)
68
+ return text;
69
+ return `${paint(tone, MARK[tone])} ${paint(tone, text)}`;
70
+ }
60
71
  /**
61
72
  * The sentence a refusal exists to deliver: it answers the only question a refusal
62
73
  * raises — what happened to my file? — so it is painted like a `warning:` label
@@ -70,19 +81,20 @@ const NOTHING_WRITTEN = "Nothing was written.";
70
81
  export function nothingWasWritten(streams = PROCESS_STREAMS) {
71
82
  return painterFor(streams.err)("yellow", NOTHING_WRITTEN);
72
83
  }
73
- function revertLine(outcome) {
84
+ function revertLine(outcome, paint) {
85
+ const path = paint("bold", outcome.path);
74
86
  switch (outcome.kind) {
75
87
  case "reverted": {
76
88
  const parts = [
77
89
  outcome.restored.length > 0 ? `restored ${outcome.restored.join(", ")} to previous values` : undefined,
78
90
  outcome.removed.length > 0 ? `removed ${outcome.removed.join(", ")}` : undefined,
79
91
  ].filter((part) => part !== undefined);
80
- return `Reverted ${outcome.path} — ${parts.join("; ")}`;
92
+ return `Reverted ${path} — ${parts.join("; ")}`;
81
93
  }
82
94
  case "restored":
83
- return `Restored ${outcome.path} from ${outcome.backup}`;
95
+ return `Restored ${path} from ${outcome.backup}`;
84
96
  case "not-configured":
85
- return `Nothing to revert — ${outcome.path} does not carry this tool's settings.`;
97
+ return `Nothing to revert — ${path} does not carry this tool's settings.`;
86
98
  case "agent-mismatch":
87
99
  return `${outcome.path} is configured for agent '${outcome.actual}', not '${outcome.expected}'.`;
88
100
  case "drifted":
@@ -129,9 +141,9 @@ export function reportRevert(outcome, json, streams = PROCESS_STREAMS) {
129
141
  const ok = code === EXIT.OK;
130
142
  // Colour tracks the exit code, with one exception: `not-configured` is a state,
131
143
  // not a refusal, and painting "nothing to revert" red reads like a crash.
132
- const format = outcome.kind === "not-configured" ? "yellow" : ok ? "green" : "red";
144
+ const tone = outcome.kind === "not-configured" ? "yellow" : ok ? "green" : "red";
133
145
  const paint = painterFor(ok ? streams.out : streams.err);
134
- const body = `${paint(format, revertLine(outcome))}\n${revertDetail(outcome, paint)}`;
146
+ const body = `${headline(paint, tone, revertLine(outcome, paint))}\n${revertDetail(outcome, paint)}`;
135
147
  return ok ? { code, out: body, err: "" } : { code, out: "", err: body };
136
148
  }
137
149
  /**
@@ -176,14 +188,15 @@ export function collectWarnings(ctx) {
176
188
  return warnings;
177
189
  }
178
190
  /** Human-readable line for a config outcome. Never includes the key. */
179
- function humanLine(outcome) {
191
+ function humanLine(outcome, paint) {
192
+ const path = paint("bold", outcome.path);
180
193
  switch (outcome.kind) {
181
194
  case "created":
182
- return `Wrote ${outcome.path}`;
195
+ return `Wrote ${path}`;
183
196
  case "updated":
184
- return `Updated ${outcome.path} (${outcome.changed.join(", ")})\nBacked up the previous file to ${outcome.backup}`;
197
+ return `Updated ${path} (${outcome.changed.join(", ")})`;
185
198
  case "unchanged":
186
- return `Already configured — ${outcome.path} is up to date.`;
199
+ return `Already configured — ${path} is up to date.`;
187
200
  case "conflict":
188
201
  return `${outcome.path} already sets values this command would change.`;
189
202
  case "malformed":
@@ -228,10 +241,16 @@ function precheckWarning(outcome) {
228
241
  };
229
242
  }
230
243
  /** The stdout line for a successful verification; nothing for any other outcome. */
231
- function verificationLine(outcome) {
244
+ function verificationLine(outcome, paint) {
232
245
  if (outcome?.kind !== "passed")
233
246
  return "";
234
- return `Verified through the gateway with ${VERIFIED_VIA[outcome.via]}.\n`;
247
+ return `${headline(paint, "green", `Verified through the gateway with ${VERIFIED_VIA[outcome.via]}.`)}\n`;
248
+ }
249
+ /** An aside under the headline, dimmed so the headline stays the thing read first. */
250
+ function backupLine(outcome, paint) {
251
+ if (outcome.kind !== "updated")
252
+ return "";
253
+ return `${paint("dim", `Backed up the previous file to ${outcome.backup}`)}\n`;
235
254
  }
236
255
  /**
237
256
  * One JSON object with what happened, where, why it exited that way, and any
@@ -290,9 +309,10 @@ export function report(input) {
290
309
  const paintWarning = painterFor(streams.err);
291
310
  // Only the label is coloured: the message is prose, and a paragraph of yellow is
292
311
  // harder to read than the plain one it replaced.
293
- const warningText = warnings.map((w) => `${paintWarning("yellow", "warning:")} ${w.message}\n`).join("");
294
- const line = paintBody(ok ? "green" : "red", humanLine(input.outcome));
295
- const body = `${line}\n${verificationLine(input.verification)}${input.detail ?? ""}`;
312
+ const warningText = warnings.map((w) => `${headline(paintWarning, "yellow", "warning:")} ${w.message}\n`).join("");
313
+ const line = headline(paintBody, ok ? "green" : "red", humanLine(input.outcome, paintBody));
314
+ const body = `${line}\n${backupLine(input.outcome, paintBody)}` +
315
+ `${verificationLine(input.verification, paintBody)}${input.detail ?? ""}`;
296
316
  return ok ? { code, out: body, err: warningText } : { code, out: "", err: `${body}${warningText}` };
297
317
  }
298
318
  /** Stable `--json` reasons, spelled for a funnel rather than for a person. */
@@ -340,11 +360,33 @@ export function reportNoClaudeCredential(input) {
340
360
  return { code, out: `${JSON.stringify(payload, null, 2)}\n`, err: "" };
341
361
  }
342
362
  const streams = input.streams ?? PROCESS_STREAMS;
343
- const headline = painterFor(streams.err)("red", NO_CREDENTIAL_HEADLINE[input.reason]);
363
+ const title = headline(painterFor(streams.err), "red", NO_CREDENTIAL_HEADLINE[input.reason]);
344
364
  return {
345
365
  code,
346
366
  out: "",
347
- err: `${headline} ${NO_CREDENTIAL_NEXT_STEP[input.reason]}\n${noCredentialStateLine(input, streams)}\n`,
367
+ err: `${title} ${NO_CREDENTIAL_NEXT_STEP[input.reason]}\n${noCredentialStateLine(input, streams)}\n`,
368
+ };
369
+ }
370
+ /** `login`'s success: who is signed in, and the workspace any key was minted in. */
371
+ export function reportSignedIn(email, workspace, streams = PROCESS_STREAMS) {
372
+ const paint = painterFor(streams.out);
373
+ return {
374
+ code: EXIT.OK,
375
+ out: `${headline(paint, "green", `Signed in as ${paint("bold", email)}.`)}\n workspace: ${paint("bold", workspace)}\n`,
376
+ err: "",
348
377
  };
349
378
  }
379
+ /** Stable `--json` reason; kept apart from `no_claude_subscription` so a funnel can split them. */
380
+ const NO_CLAUDE_CLI_REASON = "claude_code_cli_not_installed";
381
+ const NO_CLAUDE_CLI_MESSAGE = "The Claude Code CLI was not found. The AI21 gateway currently supports only the Claude Code CLI; install it to use the gateway, then re-run this command.";
382
+ /** Refused before anything else runs: without `claude` there is nothing to configure. */
383
+ export function reportNoClaudeCli(json, streams = PROCESS_STREAMS) {
384
+ const code = EXIT.NO_CLAUDE_CLI;
385
+ if (json) {
386
+ const payload = { ok: false, outcome: "no-claude-cli", exitCode: code, reason: NO_CLAUDE_CLI_REASON, warnings: [] };
387
+ return { code, out: `${JSON.stringify(payload, null, 2)}\n`, err: "" };
388
+ }
389
+ const headline = painterFor(streams.err)("red", NO_CLAUDE_CLI_MESSAGE);
390
+ return { code, out: "", err: `${headline}\n${nothingWasWritten(streams)}\n` };
391
+ }
350
392
  //# sourceMappingURL=report.js.map