@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.
- package/CHANGELOG.md +172 -1
- package/LICENSE +21 -0
- package/README.md +163 -169
- package/dist/api.d.ts +10 -0
- package/dist/api.js +14 -3
- package/dist/catalog.d.ts +1 -18
- package/dist/catalog.js +1 -24
- package/dist/cli-error.d.ts +17 -0
- package/dist/cli-error.js +25 -0
- package/dist/cli-guidance.d.ts +14 -0
- package/dist/cli-guidance.js +55 -0
- package/dist/commands/account.d.ts +17 -0
- package/dist/commands/account.js +45 -1
- package/dist/commands/api.d.ts +0 -11
- package/dist/commands/api.js +6 -175
- package/dist/commands/auth.d.ts +2 -1
- package/dist/commands/auth.js +43 -9
- package/dist/commands/brand-logos.d.ts +6 -0
- package/dist/commands/brand-logos.js +21 -0
- package/dist/commands/configCmd.js +8 -3
- package/dist/commands/connect-shared.d.ts +136 -0
- package/dist/commands/connect-shared.js +143 -0
- package/dist/commands/connect-t2.d.ts +18 -0
- package/dist/commands/connect-t2.js +1599 -0
- package/dist/commands/connect-terminal.d.ts +65 -0
- package/dist/commands/connect-terminal.js +822 -0
- package/dist/commands/connect.d.ts +31 -0
- package/dist/commands/connect.js +3181 -0
- package/dist/commands/flow.d.ts +685 -0
- package/dist/commands/flow.js +697 -0
- package/dist/commands/install.d.ts +91 -0
- package/dist/commands/install.js +258 -0
- package/dist/commands/llm-config.d.ts +103 -0
- package/dist/commands/llm-config.js +452 -0
- package/dist/commands/manifest.d.ts +68 -0
- package/dist/commands/manifest.js +90 -0
- package/dist/commands/mcp.d.ts +79 -2
- package/dist/commands/mcp.js +262 -48
- package/dist/commands/oauth-login.d.ts +33 -0
- package/dist/commands/oauth-login.js +333 -0
- package/dist/commands/prompt.d.ts +96 -0
- package/dist/commands/prompt.js +397 -0
- package/dist/commands/serve-results.d.ts +61 -0
- package/dist/commands/serve-results.js +245 -0
- package/dist/commands/serve-signin.d.ts +28 -0
- package/dist/commands/serve-signin.js +90 -0
- package/dist/commands/signin-page.d.ts +49 -0
- package/dist/commands/signin-page.js +250 -0
- package/dist/commands/skills.js +87 -50
- package/dist/commands/tool-help.d.ts +69 -0
- package/dist/commands/tool-help.js +189 -0
- package/dist/commands/tool-input.d.ts +22 -0
- package/dist/commands/tool-input.js +220 -0
- package/dist/commands/tools.d.ts +12 -0
- package/dist/commands/tools.js +307 -0
- package/dist/commands/update.d.ts +11 -0
- package/dist/commands/update.js +92 -0
- package/dist/commands/vscode.d.ts +65 -0
- package/dist/commands/vscode.js +173 -0
- package/dist/commands/wrappers.d.ts +23 -0
- package/dist/commands/wrappers.js +102 -0
- package/dist/completion.js +3 -80
- package/dist/config.js +76 -2
- package/dist/constants.d.ts +78 -38
- package/dist/constants.js +80 -102
- package/dist/index.js +155 -357
- package/dist/json-text.d.ts +12 -0
- package/dist/json-text.js +60 -0
- package/dist/router.d.ts +39 -0
- package/dist/router.js +59 -0
- package/dist/skills-registry.js +12 -3
- package/dist/utils/banner.d.ts +9 -0
- package/dist/utils/banner.js +41 -0
- package/dist/utils/browser.d.ts +19 -0
- package/dist/utils/browser.js +31 -0
- package/dist/utils/exec.d.ts +43 -0
- package/dist/utils/exec.js +61 -0
- package/dist/utils/http.d.ts +53 -0
- package/dist/utils/http.js +124 -0
- package/dist/utils/install-method.d.ts +36 -0
- package/dist/utils/install-method.js +48 -0
- package/dist/utils/journal.d.ts +57 -0
- package/dist/utils/journal.js +188 -0
- package/dist/utils/key-check.d.ts +20 -0
- package/dist/utils/key-check.js +24 -0
- package/dist/utils/update-check.d.ts +23 -0
- package/dist/utils/update-check.js +116 -0
- package/dist/utils/width.d.ts +32 -0
- package/dist/utils/width.js +73 -0
- package/package.json +8 -4
- package/vscode-extension/README.md +6 -0
- package/vscode-extension/aisa-vscode.vsix +0 -0
- package/dist/commands/finance.d.ts +0 -17
- package/dist/commands/finance.js +0 -342
- package/dist/commands/run.d.ts +0 -28
- package/dist/commands/run.js +0 -182
- package/dist/commands/search.d.ts +0 -9
- package/dist/commands/search.js +0 -81
- package/dist/commands/twitter.d.ts +0 -138
- package/dist/commands/twitter.js +0 -374
- package/dist/commands/video.d.ts +0 -49
- package/dist/commands/video.js +0 -311
package/dist/commands/mcp.d.ts
CHANGED
|
@@ -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
|
-
|
|
4
|
-
|
|
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>;
|
package/dist/commands/mcp.js
CHANGED
|
@@ -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,
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
173
|
+
for (const s of chosen) {
|
|
174
|
+
console.log(` ${chalk.gray(`aisa-${s.slug}`)} ${stripped(s.name)} (${s.toolCount} tools)`);
|
|
30
175
|
}
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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>;
|