@aisa-one/cli 0.2.4 → 0.5.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 (102) hide show
  1. package/CHANGELOG.md +172 -1
  2. package/LICENSE +21 -0
  3. package/README.md +163 -169
  4. package/dist/api.d.ts +10 -0
  5. package/dist/api.js +14 -3
  6. package/dist/catalog.d.ts +1 -18
  7. package/dist/catalog.js +1 -24
  8. package/dist/cli-error.d.ts +17 -0
  9. package/dist/cli-error.js +25 -0
  10. package/dist/cli-guidance.d.ts +14 -0
  11. package/dist/cli-guidance.js +55 -0
  12. package/dist/commands/account.d.ts +17 -0
  13. package/dist/commands/account.js +45 -1
  14. package/dist/commands/api.d.ts +0 -11
  15. package/dist/commands/api.js +6 -175
  16. package/dist/commands/auth.d.ts +2 -1
  17. package/dist/commands/auth.js +43 -9
  18. package/dist/commands/brand-logos.d.ts +6 -0
  19. package/dist/commands/brand-logos.js +21 -0
  20. package/dist/commands/configCmd.js +8 -3
  21. package/dist/commands/connect-shared.d.ts +136 -0
  22. package/dist/commands/connect-shared.js +143 -0
  23. package/dist/commands/connect-t2.d.ts +18 -0
  24. package/dist/commands/connect-t2.js +1599 -0
  25. package/dist/commands/connect-terminal.d.ts +65 -0
  26. package/dist/commands/connect-terminal.js +822 -0
  27. package/dist/commands/connect.d.ts +31 -0
  28. package/dist/commands/connect.js +3181 -0
  29. package/dist/commands/flow.d.ts +685 -0
  30. package/dist/commands/flow.js +697 -0
  31. package/dist/commands/install.d.ts +91 -0
  32. package/dist/commands/install.js +258 -0
  33. package/dist/commands/llm-config.d.ts +103 -0
  34. package/dist/commands/llm-config.js +452 -0
  35. package/dist/commands/manifest.d.ts +68 -0
  36. package/dist/commands/manifest.js +90 -0
  37. package/dist/commands/mcp.d.ts +79 -2
  38. package/dist/commands/mcp.js +262 -48
  39. package/dist/commands/oauth-login.d.ts +33 -0
  40. package/dist/commands/oauth-login.js +333 -0
  41. package/dist/commands/prompt.d.ts +96 -0
  42. package/dist/commands/prompt.js +397 -0
  43. package/dist/commands/serve-results.d.ts +61 -0
  44. package/dist/commands/serve-results.js +245 -0
  45. package/dist/commands/serve-signin.d.ts +28 -0
  46. package/dist/commands/serve-signin.js +90 -0
  47. package/dist/commands/signin-page.d.ts +49 -0
  48. package/dist/commands/signin-page.js +250 -0
  49. package/dist/commands/skills.js +87 -50
  50. package/dist/commands/tool-help.d.ts +69 -0
  51. package/dist/commands/tool-help.js +189 -0
  52. package/dist/commands/tool-input.d.ts +22 -0
  53. package/dist/commands/tool-input.js +220 -0
  54. package/dist/commands/tools.d.ts +12 -0
  55. package/dist/commands/tools.js +307 -0
  56. package/dist/commands/update.d.ts +11 -0
  57. package/dist/commands/update.js +92 -0
  58. package/dist/commands/vscode.d.ts +65 -0
  59. package/dist/commands/vscode.js +173 -0
  60. package/dist/commands/wrappers.d.ts +23 -0
  61. package/dist/commands/wrappers.js +102 -0
  62. package/dist/completion.js +3 -80
  63. package/dist/config.js +76 -2
  64. package/dist/constants.d.ts +78 -38
  65. package/dist/constants.js +80 -102
  66. package/dist/index.js +155 -357
  67. package/dist/json-text.d.ts +12 -0
  68. package/dist/json-text.js +60 -0
  69. package/dist/router.d.ts +39 -0
  70. package/dist/router.js +59 -0
  71. package/dist/skills-registry.js +12 -3
  72. package/dist/utils/banner.d.ts +9 -0
  73. package/dist/utils/banner.js +41 -0
  74. package/dist/utils/browser.d.ts +19 -0
  75. package/dist/utils/browser.js +31 -0
  76. package/dist/utils/exec.d.ts +43 -0
  77. package/dist/utils/exec.js +61 -0
  78. package/dist/utils/http.d.ts +53 -0
  79. package/dist/utils/http.js +124 -0
  80. package/dist/utils/install-method.d.ts +36 -0
  81. package/dist/utils/install-method.js +48 -0
  82. package/dist/utils/journal.d.ts +57 -0
  83. package/dist/utils/journal.js +188 -0
  84. package/dist/utils/key-check.d.ts +20 -0
  85. package/dist/utils/key-check.js +24 -0
  86. package/dist/utils/update-check.d.ts +23 -0
  87. package/dist/utils/update-check.js +116 -0
  88. package/dist/utils/width.d.ts +32 -0
  89. package/dist/utils/width.js +73 -0
  90. package/package.json +8 -4
  91. package/vscode-extension/README.md +6 -0
  92. package/vscode-extension/aisa-vscode.vsix +0 -0
  93. package/dist/commands/finance.d.ts +0 -17
  94. package/dist/commands/finance.js +0 -342
  95. package/dist/commands/run.d.ts +0 -28
  96. package/dist/commands/run.js +0 -182
  97. package/dist/commands/search.d.ts +0 -9
  98. package/dist/commands/search.js +0 -81
  99. package/dist/commands/twitter.d.ts +0 -138
  100. package/dist/commands/twitter.js +0 -374
  101. package/dist/commands/video.d.ts +0 -49
  102. package/dist/commands/video.js +0 -311
@@ -1,4 +1,81 @@
1
+ /**
2
+ * `aisa mcp setup` — write real, per-client MCP entries from the live manifest.
3
+ *
4
+ * Three rules, each one a lesson from the version this replaces:
5
+ *
6
+ * 1. The server list comes from the platform's discovery manifest at setup
7
+ * time, never from this file. Servers ship without a CLI release.
8
+ * 2. Nothing is written unless the manifest was actually fetched. The old
9
+ * command wrote first and verified never, which is how a dead hostname
10
+ * sat in users' configs across releases.
11
+ * 3. Each client gets the shape it executes: `{url}` for clients that dial
12
+ * HTTP themselves, an `npx mcp-remote` stdio bridge for the ones that only
13
+ * spawn processes. One shape for all three was the old bug.
14
+ *
15
+ * Auth is a choice the user already made: with an API key configured, entries
16
+ * carry it as a Bearer header (zero-interaction, but the key lands in each
17
+ * client's config file); without one, entries carry no credentials and the
18
+ * server's 401 challenge drives the client through the Clerk OAuth flow.
19
+ */
20
+ export interface LiveServer {
21
+ slug: string;
22
+ name: string;
23
+ endpoint: string;
24
+ toolCount: number;
25
+ /** One-paragraph capability description from the manifest — the single
26
+ * source of truth for what a server does; rendered on the connect page. */
27
+ description: string;
28
+ category: string;
29
+ }
30
+ export declare const stripped: (name: string) => string;
31
+ /**
32
+ * Which servers exist, asked of the host that runs them.
33
+ *
34
+ * The index is preferred and the manifest is the fallback, in that order for
35
+ * one reason: the manifest is a snapshot committed by hand, and on 2026-08-25
36
+ * it was three servers and 72 tools behind what the host was actually serving.
37
+ * Anything derived from a running process cannot drift that way.
38
+ *
39
+ * The fallback is not defensive padding — the index is newer than some
40
+ * deployments, and a self-hosted gateway configured through `baseUrl` may
41
+ * never grow one. A user in either case should still get a working setup from
42
+ * a slightly stale list rather than an error.
43
+ */
44
+ export declare function fetchLiveServers(): Promise<LiveServer[]>;
45
+ /** One config entry in the shape the client actually executes. */
46
+ export declare function buildEntry(shape: "url" | "stdio", endpoint: string, key: string | undefined): Record<string, unknown>;
47
+ export type WriteResult = {
48
+ ok: true;
49
+ written: number;
50
+ } | {
51
+ ok: false;
52
+ reason: string;
53
+ };
54
+ /**
55
+ * Write the chosen servers (plus the docs server) into one client's config
56
+ * file. Shared by `aisa mcp setup` and `aisa connect` so the two entry points
57
+ * cannot drift in how they treat existing files: unparseable JSON is refused,
58
+ * never replaced, and only the known-dead legacy entry is cleaned up.
59
+ */
60
+ export declare function writeClientConfig(agent: string, chosen: LiveServer[], key: string | undefined): WriteResult;
1
61
  export declare function mcpSetupAction(options: {
2
62
  agent?: string;
3
- }): void;
4
- export declare function mcpStatusAction(): void;
63
+ all?: boolean;
64
+ yes?: boolean;
65
+ }): Promise<void>;
66
+ /**
67
+ * `aisa mcp status` — what is configured, and does it actually answer.
68
+ *
69
+ * The old status only checked that a config file contained an "aisa" key,
70
+ * which is exactly how a dead URL looked healthy. Reachability means a real
71
+ * HTTP exchange with each configured endpoint.
72
+ */
73
+ export declare function mcpStatusAction(): Promise<void>;
74
+ /**
75
+ * One initialize POST, no credentials. What each answer means:
76
+ * 200 -> alive and open (the docs MCP)
77
+ * 401 -> alive, auth required — the expected answer from mcp.aisa.one
78
+ * anything else -> alive but odd; say so
79
+ * network error -> the dead-hostname class this command exists to catch
80
+ */
81
+ export declare function pingEndpoint(endpoint: string): Promise<string>;
@@ -2,66 +2,280 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import chalk from "chalk";
3
3
  import { success, error, info, hint } from "../utils/display.js";
4
4
  import { expandHome, ensureDir } from "../utils/file.js";
5
- import { MCP_CONFIGS, MCP_URL } from "../constants.js";
5
+ import { MCP_CONFIGS, MCP_MANIFEST_URL, MCP_CATALOG_URL, DOCS_MCP_URL, MCP_DEFAULT_SLUGS } from "../constants.js";
6
+ import { httpFetch, MAX_ATTEMPTS } from "../utils/http.js";
7
+ import { getApiKey } from "../config.js";
8
+ import { detectClients } from "./connect.js";
6
9
  import { join } from "node:path";
7
- export function mcpSetupAction(options) {
8
- const targets = options.agent && options.agent !== "all"
9
- ? [options.agent]
10
- : Object.keys(MCP_CONFIGS);
11
- let configured = 0;
12
- for (const agent of targets) {
13
- const config = MCP_CONFIGS[agent];
14
- if (!config) {
10
+ export const stripped = (name) => name.replace(/^AIsa\s+/i, "");
11
+ function toLive(servers) {
12
+ return servers
13
+ .filter((s) => s.status === "live" && s.slug && s.transport?.endpoint)
14
+ .map((s) => ({
15
+ slug: s.slug,
16
+ name: s.name ?? s.slug,
17
+ endpoint: s.transport.endpoint,
18
+ toolCount: s.toolCount ?? s.tools?.length ?? 0,
19
+ description: s.description ?? "",
20
+ category: s.category ?? "Other",
21
+ }));
22
+ }
23
+ async function readSource(url, attempts) {
24
+ const res = await httpFetch(url, { idempotent: true, maxAttempts: attempts, timeoutMs: 15_000 });
25
+ if (!res.ok)
26
+ throw new Error(`${url} answered HTTP ${res.status}`);
27
+ const doc = (await res.json());
28
+ const live = toLive(doc.servers ?? []);
29
+ if (live.length === 0)
30
+ throw new Error(`${url} lists no live servers`);
31
+ return live;
32
+ }
33
+ /**
34
+ * Which servers exist, asked of the host that runs them.
35
+ *
36
+ * The index is preferred and the manifest is the fallback, in that order for
37
+ * one reason: the manifest is a snapshot committed by hand, and on 2026-08-25
38
+ * it was three servers and 72 tools behind what the host was actually serving.
39
+ * Anything derived from a running process cannot drift that way.
40
+ *
41
+ * The fallback is not defensive padding — the index is newer than some
42
+ * deployments, and a self-hosted gateway configured through `baseUrl` may
43
+ * never grow one. A user in either case should still get a working setup from
44
+ * a slightly stale list rather than an error.
45
+ */
46
+ export async function fetchLiveServers() {
47
+ try {
48
+ // One attempt only: there is somewhere else to go, so spending a retry
49
+ // budget here just delays the fallback. The manifest is the last resort
50
+ // and gets the full three.
51
+ return await readSource(MCP_CATALOG_URL, 1);
52
+ }
53
+ catch {
54
+ return await readSource(MCP_MANIFEST_URL, MAX_ATTEMPTS);
55
+ }
56
+ }
57
+ /** One config entry in the shape the client actually executes. */
58
+ export function buildEntry(shape, endpoint, key) {
59
+ if (shape === "url") {
60
+ return key ? { url: endpoint, headers: { Authorization: `Bearer ${key}` } } : { url: endpoint };
61
+ }
62
+ // The exact invocation verified against production on 2026-08-18. The
63
+ // header value splits on the first colon, so no space after "Authorization:".
64
+ // Without a key, mcp-remote answers the server's 401 challenge by running
65
+ // the OAuth flow itself and caching tokens under ~/.mcp-auth.
66
+ const args = ["-y", "mcp-remote", endpoint];
67
+ if (key)
68
+ args.push("--header", `Authorization:Bearer ${key}`);
69
+ args.push("--transport", "http-only");
70
+ return { command: "npx", args };
71
+ }
72
+ /** The dead entry every pre-v0.3 setup wrote. Remove it on sight. */
73
+ const DEAD_URL = "https://docs.aisa.one/mcp";
74
+ /**
75
+ * Write the chosen servers (plus the docs server) into one client's config
76
+ * file. Shared by `aisa mcp setup` and `aisa connect` so the two entry points
77
+ * cannot drift in how they treat existing files: unparseable JSON is refused,
78
+ * never replaced, and only the known-dead legacy entry is cleaned up.
79
+ */
80
+ export function writeClientConfig(agent, chosen, key) {
81
+ const config = MCP_CONFIGS[agent];
82
+ if (!config)
83
+ return { ok: false, reason: `unknown client "${agent}"` };
84
+ const filePath = expandHome(config.path);
85
+ let existing = {};
86
+ if (existsSync(filePath)) {
87
+ try {
88
+ existing = JSON.parse(readFileSync(filePath, "utf-8"));
89
+ }
90
+ catch {
91
+ // A file we cannot parse is a file we do not own. Refuse instead of
92
+ // wiping a user's hand-edited config.
93
+ return { ok: false, reason: `${config.path} exists but is not valid JSON` };
94
+ }
95
+ }
96
+ else {
97
+ ensureDir(join(filePath, ".."));
98
+ }
99
+ const entries = existing[config.key] || {};
100
+ // Clean up the dead entry earlier releases wrote, and only that one: an
101
+ // "aisa" entry the user pointed somewhere else on purpose stays.
102
+ const stale = entries["aisa"];
103
+ if (stale?.url === DEAD_URL)
104
+ delete entries["aisa"];
105
+ for (const s of chosen) {
106
+ entries[`aisa-${s.slug}`] = buildEntry(config.shape, s.endpoint, key);
107
+ }
108
+ // The docs-search MCP is tiny, unauthenticated, and answers "how do I call
109
+ // this" questions — always included.
110
+ entries["aisa-docs"] = buildEntry(config.shape, DOCS_MCP_URL, undefined);
111
+ existing[config.key] = entries;
112
+ writeFileSync(filePath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
113
+ return { ok: true, written: chosen.length + 1 };
114
+ }
115
+ export async function mcpSetupAction(options) {
116
+ const requested = options.agent && options.agent !== "all" ? [options.agent] : Object.keys(MCP_CONFIGS);
117
+ for (const agent of requested) {
118
+ if (!MCP_CONFIGS[agent]) {
15
119
  error(`Unknown agent: ${agent}. Valid: ${Object.keys(MCP_CONFIGS).join(", ")}, all`);
16
120
  return;
17
121
  }
18
- const filePath = expandHome(config.path);
19
- let existing = {};
20
- if (existsSync(filePath)) {
21
- try {
22
- existing = JSON.parse(readFileSync(filePath, "utf-8"));
23
- }
24
- catch {
25
- existing = {};
26
- }
122
+ }
123
+ // Write only where the client actually lives. Until 2026-08-25 `--agent all`
124
+ // (the default) wrote to every entry in MCP_CONFIGS whether or not it was
125
+ // installed, which on a machine without Windsurf created ~/.codeium/ and
126
+ // made the next `aisa connect` offer a card for an editor that was never
127
+ // there — this command's output became that command's input.
128
+ const installed = new Set(detectClients().filter((c) => c.detected).map((c) => c.id));
129
+ const targets = requested.filter((a) => installed.has(a));
130
+ const absent = requested.filter((a) => !installed.has(a));
131
+ for (const a of absent) {
132
+ console.log(` ${chalk.gray("○")} ${a}: not installed — skipped`);
133
+ }
134
+ if (targets.length === 0) {
135
+ error("None of the requested clients are installed on this machine.");
136
+ hint("`aisa connect` detects what is here and walks you through it.");
137
+ process.exitCode = 1;
138
+ return;
139
+ }
140
+ // Fetch before touching any file: a setup that cannot see the manifest has
141
+ // nothing true to write.
142
+ let servers;
143
+ try {
144
+ servers = await fetchLiveServers();
145
+ }
146
+ catch (e) {
147
+ error(`Could not read the MCP manifest at ${MCP_MANIFEST_URL}: ${e.message}`);
148
+ hint("Nothing was written. Check your network and try again.");
149
+ process.exitCode = 1;
150
+ return;
151
+ }
152
+ const chosen = options.all
153
+ ? servers
154
+ : servers.filter((s) => MCP_DEFAULT_SLUGS.includes(s.slug));
155
+ const skipped = servers.length - chosen.length;
156
+ const key = getApiKey();
157
+ if (key) {
158
+ info("Using your configured API key (Bearer). It will be written into each client's config file.");
159
+ }
160
+ else {
161
+ info("No API key configured — entries are written without credentials, and the");
162
+ info("server's OAuth flow will open in your browser on first use.");
163
+ }
164
+ // Nothing is written without saying so first. This command used to change
165
+ // three config files the moment it was typed, with no preview and no way to
166
+ // ask what it would do — the opposite of `connect`, which shows the plan and
167
+ // waits. --yes is how a script opts back in to writing straight away.
168
+ if (!options.yes) {
169
+ info("This would write:");
170
+ for (const agent of targets) {
171
+ console.log(` ${chalk.cyan(MCP_CONFIGS[agent].path)} (${agent}, ${MCP_CONFIGS[agent].shape})`);
27
172
  }
28
- else {
29
- ensureDir(join(filePath, ".."));
173
+ for (const s of chosen) {
174
+ console.log(` ${chalk.gray(`aisa-${s.slug}`)} ${stripped(s.name)} (${s.toolCount} tools)`);
30
175
  }
31
- const servers = existing[config.key] || {};
32
- servers["aisa"] = { url: MCP_URL };
33
- existing[config.key] = servers;
34
- writeFileSync(filePath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
35
- console.log(` ${chalk.green("✓")} ${agent}: ${config.path}`);
36
- configured++;
176
+ hint("Nothing was written. Re-run with --yes to apply, or use `aisa connect` to choose interactively.");
177
+ return;
37
178
  }
38
- if (configured > 0) {
39
- success(`MCP server configured for ${configured} agent(s)`);
40
- hint("Restart your agent/editor to activate");
179
+ for (const agent of targets) {
180
+ const config = MCP_CONFIGS[agent];
181
+ const result = writeClientConfig(agent, chosen, key);
182
+ if (!result.ok) {
183
+ error(`${agent}: ${result.reason} — refusing to overwrite it.`);
184
+ hint("Fix or remove the file, then re-run setup.");
185
+ continue;
186
+ }
187
+ console.log(` ${chalk.green("✓")} ${agent}: ${result.written} servers (${config.shape}) → ${config.path}`);
188
+ }
189
+ success(`Configured ${chosen.length} live servers plus docs search for ${targets.length} client(s)`);
190
+ for (const s of chosen) {
191
+ console.log(` ${chalk.cyan(`aisa-${s.slug}`)} ${stripped(s.name)} (${s.toolCount} tools)`);
41
192
  }
193
+ if (skipped > 0)
194
+ hint(`${skipped} more live servers available — run with --all to configure every one`);
195
+ hint("Restart your agent/editor to activate");
42
196
  }
43
- export function mcpStatusAction() {
44
- info(`MCP Server: ${MCP_URL}`);
197
+ /**
198
+ * `aisa mcp status` — what is configured, and does it actually answer.
199
+ *
200
+ * The old status only checked that a config file contained an "aisa" key,
201
+ * which is exactly how a dead URL looked healthy. Reachability means a real
202
+ * HTTP exchange with each configured endpoint.
203
+ */
204
+ export async function mcpStatusAction() {
205
+ const endpoints = new Map(); // endpoint -> [client/entry]
45
206
  for (const [agent, config] of Object.entries(MCP_CONFIGS)) {
46
207
  const filePath = expandHome(config.path);
47
- if (existsSync(filePath)) {
48
- try {
49
- const data = JSON.parse(readFileSync(filePath, "utf-8"));
50
- const servers = data[config.key];
51
- if (servers?.["aisa"]) {
52
- console.log(` ${chalk.green("✓")} ${agent}: configured`);
53
- }
54
- else {
55
- console.log(` ${chalk.gray("○")} ${agent}: not configured`);
56
- }
57
- }
58
- catch {
59
- console.log(` ${chalk.gray("○")} ${agent}: config unreadable`);
60
- }
61
- }
62
- else {
208
+ if (!existsSync(filePath)) {
63
209
  console.log(` ${chalk.gray("○")} ${agent}: not installed`);
210
+ continue;
64
211
  }
212
+ let entries = {};
213
+ try {
214
+ const data = JSON.parse(readFileSync(filePath, "utf-8"));
215
+ entries = data[config.key] || {};
216
+ }
217
+ catch {
218
+ console.log(` ${chalk.gray("○")} ${agent}: config unreadable`);
219
+ continue;
220
+ }
221
+ const ours = Object.entries(entries).filter(([name]) => name.startsWith("aisa"));
222
+ if (ours.length === 0) {
223
+ console.log(` ${chalk.gray("○")} ${agent}: not configured`);
224
+ continue;
225
+ }
226
+ console.log(` ${chalk.green("✓")} ${agent}: ${ours.length} entries`);
227
+ for (const [name, entry] of ours) {
228
+ const e = entry;
229
+ const endpoint = e.url ?? e.args?.find((a) => a.startsWith("http"));
230
+ if (endpoint) {
231
+ const users = endpoints.get(endpoint) ?? [];
232
+ users.push(`${agent}/${name}`);
233
+ endpoints.set(endpoint, users);
234
+ }
235
+ }
236
+ }
237
+ if (endpoints.size === 0) {
238
+ hint("Run 'aisa mcp setup' to configure");
239
+ return;
240
+ }
241
+ console.log("");
242
+ info("Checking each configured endpoint:");
243
+ for (const [endpoint, users] of endpoints) {
244
+ console.log(` ${endpoint} ${chalk.gray(`(${users.length} entr${users.length > 1 ? "ies" : "y"})`)}`);
245
+ console.log(` ${await pingEndpoint(endpoint)}`);
246
+ }
247
+ }
248
+ /**
249
+ * One initialize POST, no credentials. What each answer means:
250
+ * 200 -> alive and open (the docs MCP)
251
+ * 401 -> alive, auth required — the expected answer from mcp.aisa.one
252
+ * anything else -> alive but odd; say so
253
+ * network error -> the dead-hostname class this command exists to catch
254
+ */
255
+ export async function pingEndpoint(endpoint) {
256
+ try {
257
+ const res = await fetch(endpoint, {
258
+ method: "POST",
259
+ headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
260
+ body: JSON.stringify({
261
+ jsonrpc: "2.0",
262
+ id: 1,
263
+ method: "initialize",
264
+ params: {
265
+ protocolVersion: "2025-06-18",
266
+ capabilities: {},
267
+ clientInfo: { name: "aisa-cli", version: "status" },
268
+ },
269
+ }),
270
+ signal: AbortSignal.timeout(8_000),
271
+ });
272
+ if (res.status === 200)
273
+ return `${chalk.green("✓")} reachable (open)`;
274
+ if (res.status === 401)
275
+ return `${chalk.green("✓")} reachable (auth required — normal)`;
276
+ return `${chalk.yellow("⚠")} reachable but answered HTTP ${res.status}`;
277
+ }
278
+ catch (e) {
279
+ return `${chalk.red("✗")} unreachable: ${e.message}`;
65
280
  }
66
- hint("Run 'aisa mcp setup' to configure");
67
281
  }
@@ -0,0 +1,33 @@
1
+ import { type Lang } from "./flow.js";
2
+ /**
3
+ * The whole sign-in, as a function other commands can embed: browser (or
4
+ * paste-back) OAuth, then the mint. Stores the key and returns it; throws on
5
+ * any failure. `aisa login` wraps this with CLI messaging; `aisa connect`
6
+ * runs it as its "Sign in to AIsa" step.
7
+ */
8
+ /**
9
+ * Somewhere for the browser to land.
10
+ *
11
+ * `aisa login` on its own takes a random loopback port and serves one page on
12
+ * it. Inside `connect` that is the wrong shape: the sign-in opens in a second
13
+ * tab, and a random port has nothing to do with the run — the tab finishes on
14
+ * a page that knows nothing about the setup in progress and leaves the user
15
+ * to find their way back to the first tab themselves. Handing the run's own
16
+ * server in means the approval comes home to the address the setup already
17
+ * lives at, and the tab can return the reader to it.
18
+ */
19
+ export interface OAuthCatcher {
20
+ /** Registered with the authorization server before the browser opens. */
21
+ redirectUri: string;
22
+ /** Resolves with the authorization code, or rejects with why it will not. */
23
+ wait(expectedState: string): Promise<string>;
24
+ }
25
+ export declare function mintCliKey(options?: {
26
+ open?: boolean;
27
+ lang?: Lang;
28
+ catcher?: OAuthCatcher;
29
+ }): Promise<string>;
30
+ export declare function oauthLogin(options?: {
31
+ open?: boolean;
32
+ lang?: Lang;
33
+ }): Promise<void>;