@aisa-one/cli 0.6.9 → 0.6.10

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.
@@ -7,9 +7,9 @@ import { dirname, join } from "node:path";
7
7
  import chalk from "chalk";
8
8
  import { error } from "../utils/display.js";
9
9
  import { expandHome } from "../utils/file.js";
10
- import { MCP_CONFIGS, MCP_DEFAULT_SLUGS, AISA_PROVIDER_ID } from "../constants.js";
10
+ import { MCP_CONFIGS, AISA_PROVIDER_ID } from "../constants.js";
11
11
  import { getAccessToken, getConfig, setConfig } from "../config.js";
12
- import { fetchLiveServers, writeClientConfig, buildEntry, stripped } from "./mcp.js";
12
+ import { getMcpServers, writeClientConfig, buildEntry, stripped, isUnifiedMcpSelection } from "./mcp.js";
13
13
  import { INSTALLERS, installAgent, isInstalled, supported } from "./install.js";
14
14
  import { writeCodexLLM, writeClaudeCodeLLM, writeOpencodeLLM, writeCodexAisaProfile, writeOpencodeAisaBackup, defaultModelsFor, patchCodexMCPAuth, DEFAULT_MODELS, } from "./llm-config.js";
15
15
  import { writeClaudeAisaSettings, installWrappers } from "./wrappers.js";
@@ -257,6 +257,8 @@ export function cursorDeeplink(name, endpoint, key) {
257
257
  return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(name)}&config=${encodeURIComponent(config)}`;
258
258
  }
259
259
  async function applySelection(clientIds, chosen, key, dryRun, state) {
260
+ if (!isUnifiedMcpSelection(chosen))
261
+ throw new Error("Only the unified AIsa Router MCP can be configured");
260
262
  const results = [];
261
263
  for (const id of clientIds) {
262
264
  if (id === "cursor" && state?.deeplinks) {
@@ -377,7 +379,7 @@ async function applySelection(clientIds, chosen, key, dryRun, state) {
377
379
  }
378
380
  else if (MCP_CONFIGS[id]) {
379
381
  if (dryRun) {
380
- results.push({ client: id, ok: true, message: `would write ${chosen.length + 1} entries to ${MCP_CONFIGS[id].path}` });
382
+ results.push({ client: id, ok: true, message: `would write ${chosen.length} entries to ${MCP_CONFIGS[id].path}` });
381
383
  continue;
382
384
  }
383
385
  const r = writeClientConfig(id, chosen, key);
@@ -1131,14 +1133,13 @@ function renderPage(servers, clients, token, keyed, canInstall) {
1131
1133
  .map(([cat, list]) => {
1132
1134
  const rows = list
1133
1135
  .map((s) => {
1134
- const checked = MCP_DEFAULT_SLUGS.includes(s.slug) ? "checked" : "";
1135
1136
  // The full manifest description, CSS-clamped to two lines; "more"
1136
1137
  // expands it and is hidden by the page script when the text already
1137
1138
  // fits — so short cards carry no dead link and long ones stay tidy.
1138
- return `<label class="card ${checked ? "on" : ""}" data-kind="server">
1139
- <input type="checkbox" name="server" value="${s.slug}" ${checked}>
1139
+ return `<label class="card on" data-kind="server">
1140
+ <input type="checkbox" name="server" value="${s.slug}" checked disabled>
1140
1141
  <span class="body"><span class="head"><span class="name">${stripped(s.name)}</span>
1141
- <span class="badge">${s.toolCount} tools</span></span>
1142
+ </span>
1142
1143
  <span class="desc">${s.description}</span>
1143
1144
  <span class="more" data-more>more</span></span></label>`;
1144
1145
  })
@@ -1169,7 +1170,6 @@ function renderPage(servers, clients, token, keyed, canInstall) {
1169
1170
  (rest.length
1170
1171
  ? `<div class="chips">${rest.map((c) => `${c.label} <i>· not found</i>`).join(" &nbsp;&nbsp; ")}</div>`
1171
1172
  : "");
1172
- const totalTools = servers.reduce((n, s) => n + s.toolCount, 0);
1173
1173
  // Named per client so the page can say which model a pick actually gets.
1174
1174
  const modelByClient = Object.fromEntries(clients.map((c) => [c.id, defaultModelsFor(c.id).model]));
1175
1175
  const authCopy = keyed
@@ -1180,13 +1180,11 @@ function renderPage(servers, clients, token, keyed, canInstall) {
1180
1180
  const body = `
1181
1181
  <div class="eyebrow">Connect</div>
1182
1182
  <h1><em>AIsa MCP</em> — powerful real-world reach for your agent</h1>
1183
- <p class="lede">One connection puts <b>${totalTools} live tools</b> — web search &amp; research,
1184
- X/Twitter, Reddit, Instagram, stocks, crypto, prediction markets and B2B data — inside the
1185
- coding agent you already use. Pick what you need, press Connect, approve in the browser. Done.</p>
1183
+ <p class="lede">One connection gives your coding agent access to AIsa tools. Press Connect and approve in the browser. Done.</p>
1186
1184
 
1187
1185
  <div class="cols">
1188
1186
  <div class="left">
1189
- <h2><span class="n">1</span>Choose capabilities</h2>
1187
+ <h2><span class="n">1</span>Connect AIsa Router</h2>
1190
1188
  ${serverGroups}
1191
1189
  </div>
1192
1190
 
@@ -1461,7 +1459,7 @@ machine except the OAuth you approve. The process exits when everything is conne
1461
1459
  var servers = picked("server");
1462
1460
  var chosen = document.querySelector('input[name="client"]:checked');
1463
1461
  if (!servers.length || !chosen) {
1464
- result.textContent = "Pick at least one capability and one client."; return;
1462
+ result.textContent = "Choose a client to connect to AIsa Router."; return;
1465
1463
  }
1466
1464
  var clients = [chosen.value];
1467
1465
  var install = chosen.dataset.install === "1" ? [chosen.value] : [];
@@ -1524,9 +1522,6 @@ export function renderDone(chosen, clientIds, steps, allServers, balanceMicros,
1524
1522
  Fix the above, then run <code>npx @aisa-one/cli connect</code> again — it is
1525
1523
  safe to re-run and picks up where things stand.</div></div>`
1526
1524
  : "";
1527
- const toolCount = chosen.reduce((n, s) => n + s.toolCount, 0);
1528
- const remaining = allServers.length - chosen.length;
1529
- const remainingTools = allServers.reduce((n, s) => n + s.toolCount, 0) - toolCount;
1530
1525
  // A run that installed an agent is a much bigger deal than one that only
1531
1526
  // added tools, and the headline must say so: the user walked in with
1532
1527
  // nothing and walked out with a working coding agent on AIsa models.
@@ -1537,12 +1532,12 @@ export function renderDone(chosen, clientIds, steps, allServers, balanceMicros,
1537
1532
  const model = defaultModelsFor(clientIds[0] ?? "");
1538
1533
  const successHeadline = installedAgents.length
1539
1534
  ? `<h1>Congratulations — <em>${installedAgents.join(" & ")}</em> is installed and armed
1540
- with <em>${toolCount} powerful tool${toolCount > 1 ? "s" : ""}</em></h1>
1535
+ with <em>AIsa tools</em></h1>
1541
1536
  <p class="lede"><b>${installedAgents.join(" & ")}</b> is now on this machine, signed in to
1542
1537
  AIsa${llmOk ? `, running on <b>${model.model}</b> through AIsa,` : ","} with
1543
1538
  ${chosen.length} MCP server${chosen.length > 1 ? "s" : ""} wired in — a complete AI setup,
1544
1539
  nothing else to configure.</p>`
1545
- : `<h1>Congratulations — your agent just got <em>${toolCount} powerful new tool${toolCount > 1 ? "s" : ""}</em></h1>
1540
+ : `<h1>Congratulations — your agent just got <em>access to AIsa tools</em></h1>
1546
1541
  <p class="lede">${chosen.length} AIsa MCP server${chosen.length > 1 ? "s are" : " is"} now
1547
1542
  installed and signed in for <b>${clientNames}</b> — nothing else to configure.</p>`;
1548
1543
  // Launchable only for the CLI agents, and only while the local process is
@@ -1618,7 +1613,7 @@ ${successHeadline}`;
1618
1613
  const recap = `
1619
1614
  <h2>Everything you just gained</h2>
1620
1615
  <div class="recap">
1621
- <div class="rsum">${chosen.length} capabilit${chosen.length === 1 ? "y" : "ies"} · ${toolCount} tools · ${clientNames}</div>
1616
+ <div class="rsum">AIsa Router MCP · ${clientNames}</div>
1622
1617
  ${recapRows}
1623
1618
  </div>`;
1624
1619
  // ── the balance card: always shown, always with the top-up button — money
@@ -1696,10 +1691,7 @@ ${headline}
1696
1691
  ${mcpFailed
1697
1692
  ? failBlock + recap + balanceCard
1698
1693
  : `<p class="lede" style="margin-top:.6rem">You are now connected to <b>AIsa</b> — a powerful capability layer for agents: one account for
1699
- <b>Claude, GPT, Gemini, DeepSeek, Kimi, GLM</b> and the live data behind them: <b>Twitter, SimilarWeb, CoinGecko, Apollo, Polymarket</b> and more.${remaining > 0
1700
- ? ` ${remaining} more MCP server${remaining > 1 ? "s" : ""} (${remainingTools} tools) are one
1701
- <code>npx @aisa-one/cli connect</code> away.`
1702
- : ""}
1694
+ <b>Claude, GPT, Gemini, DeepSeek, Kimi, GLM</b> and the live data behind them: <b>Twitter, SimilarWeb, CoinGecko, Apollo, Polymarket</b> and more.
1703
1695
  Explore the platform at <a href="https://aisa.one" target="_blank" rel="noopener noreferrer">aisa.one</a> ·
1704
1696
  usage &amp; billing at <a href="https://console.aisa.one" target="_blank" rel="noopener noreferrer">console.aisa.one</a>.</p>
1705
1697
  ${failBlock}
@@ -2246,14 +2238,17 @@ export async function connectAction(options) {
2246
2238
  }
2247
2239
  let servers;
2248
2240
  try {
2249
- servers = resumed ? resumed.servers : await fetchLiveServers();
2241
+ servers = resumed ? resumed.servers : getMcpServers();
2242
+ if (!isUnifiedMcpSelection(servers))
2243
+ throw new Error("This saved run uses retired MCP endpoints. Start again with aisa connect --force.");
2250
2244
  }
2251
2245
  catch (e) {
2252
- log.line("fail", "Could not read the MCP manifest", e.message);
2253
- log.note("check your network and try again");
2246
+ log.line("fail", "Could not prepare the unified MCP connection", e.message);
2247
+ log.note("Review your clients and models again with: aisa connect --force");
2254
2248
  process.exitCode = 1;
2255
2249
  return;
2256
2250
  }
2251
+ log.note("Existing MCP entries are preserved. Remove old domain entries manually after connecting to aisa-router.");
2257
2252
  const clients = resumed ? resumed.clients : detectClients();
2258
2253
  // Rehearsal aid: in a dry run, pretend the listed clients are present so a
2259
2254
  // page can be reviewed for a client this machine does not have. Never
@@ -2313,7 +2308,7 @@ export async function connectAction(options) {
2313
2308
  // that never opens a browser starts from the same defaults rather than
2314
2309
  // from nothing.
2315
2310
  draft: {
2316
- servers: servers.filter((s) => MCP_DEFAULT_SLUGS.includes(s.slug)).map((s) => s.slug),
2311
+ servers: servers.map((s) => s.slug),
2317
2312
  clients: [],
2318
2313
  install: [],
2319
2314
  llmMode: "backup",
@@ -2715,7 +2710,7 @@ export async function connectAction(options) {
2715
2710
  }
2716
2711
  // Who started this decides whether there is an animation to wait for.
2717
2712
  drivenByTerminal = req.headers["x-connect-source"] === "terminal";
2718
- chosenServers = servers.filter((s) => body.servers?.includes(s.slug));
2713
+ chosenServers = servers;
2719
2714
  chosenClients = body.clients ?? [];
2720
2715
  const wantInstall = new Set(body.install ?? []);
2721
2716
  // Ticking "install" means "and connect it": nobody installs an agent
@@ -2995,7 +2990,7 @@ export async function connectAction(options) {
2995
2990
  // the real problem.
2996
2991
  if (resumed && state.selection) {
2997
2992
  chosenClients = [...state.selection.clients];
2998
- chosenServers = servers.filter((x) => state.selection.servers.includes(x.slug));
2993
+ chosenServers = servers;
2999
2994
  // llmMode is not a local here — the routes read state.llmMode, which
3000
2995
  // travels with the run.
3001
2996
  }
@@ -3006,7 +3001,7 @@ export async function connectAction(options) {
3006
3001
  armRetry({
3007
3002
  install: [...sel.install],
3008
3003
  clients: [...sel.clients],
3009
- servers: servers.filter((x) => sel.servers.includes(x.slug)),
3004
+ servers: servers,
3010
3005
  key: liveKey(),
3011
3006
  catcher: signInCatcher,
3012
3007
  signinShown: signinShownP,
@@ -3126,7 +3121,7 @@ export async function connectAction(options) {
3126
3121
  const missing = clients.filter((c) => !c.detected).map((c) => c.label);
3127
3122
  if (missing.length)
3128
3123
  log.record(`not found: ${missing.join(", ")}`);
3129
- log.line("info", `${servers.length} AIsa MCP servers are live`, "pick what you need");
3124
+ log.line("info", "AIsa Router MCP", "one connection for all published tools");
3130
3125
  // The terminal walks the same six steps from the same definition. It is not
3131
3126
  // a fallback: on a machine with no browser, printing a localhost URL is a
3132
3127
  // dead end, and both surfaces share one draft so either can be used —
@@ -262,55 +262,13 @@ export declare const STEP_CAPS: {
262
262
  en: string;
263
263
  zh: string;
264
264
  };
265
- pickArea: {
266
- en: string;
267
- zh: string;
268
- };
269
- selectAll: {
270
- en: string;
271
- zh: string;
272
- };
273
- /**
274
- * The counts sentence as one string, because punctuation is not shared
275
- * between languages: joining "N", "areas", ", " in code produced
276
- * "2 个领域, 2 个 server, 48 个工具." — Chinese text with ASCII commas and a
277
- * full stop. Whatever varies goes in as a placeholder instead.
278
- */
279
265
  counts: {
280
266
  en: string;
281
267
  zh: string;
282
268
  };
283
- /** Category tile meta: "3 servers · 47 tools". */
284
- tileMeta: {
285
- en: string;
286
- zh: string;
287
- };
288
- /**
289
- * A placeholder both renderers can fill. The page used to carry
290
- * <b id="mModel"></b> here and fill it with script — which left the
291
- * terminal printing "AIsa()" with nothing inside, because stripping tags is
292
- * all it can do with markup meant for the other surface.
293
- */
294
- serversWord: {
295
- en: string;
296
- zh: string;
297
- };
298
- serverWord: {
299
- en: string;
300
- zh: string;
301
- };
302
- toolsWord: {
303
- en: string;
304
- zh: string;
305
- };
306
269
  };
307
270
  /** Shown for a file-configured client that has no note of its own. */
308
271
  export declare const FILE_MODEL_FALLBACK: Text;
309
- export declare const CATEGORY_BLURB: Record<string, Text>;
310
- /** New host categories remain visible without waiting for a client release. */
311
- export declare function categoryBlurb(category: string, servers: Array<{
312
- name: string;
313
- }>): Text;
314
272
  export declare const STEP_INSTALL: {
315
273
  h1: {
316
274
  en: string;
@@ -344,10 +302,6 @@ export declare const STEP_DONE: {
344
302
  };
345
303
  };
346
304
  export declare const RUNTIME_COPY: {
347
- readonly nothingSelected: {
348
- readonly en: "Nothing selected yet — pick at least one server.";
349
- readonly zh: "还没有选择 —— 至少选一个 server。";
350
- };
351
305
  readonly planBackup: {
352
306
  readonly "claude-code": {
353
307
  readonly en: "Install the claude-aisa command";
@@ -136,12 +136,12 @@ export const AGENT_NOTES = {
136
136
  zh: "每个 server 一次点击:最后一步会给出 <b>Add to Cursor</b> 链接。Cursor 会打开、把确切的配置内容给你看,你确认后由它自己写进 MCP 设置。模型保持原样 —— Cursor 用它自己的。",
137
137
  },
138
138
  "claude-desktop": {
139
- en: "Claude Desktop's config file only takes MCP servers over stdio, so each AIsa server is written in as a small <code>mcp-remote</code> bridge carrying your key (plus the free <code>aisa-docs</code> server). The bridges live and die with the app — no background service, no open ports. Models cannot be changed: Claude Desktop runs Anthropic's own. Restart the app and the servers appear under its tools menu.",
140
- zh: "Claude Desktop 的配置文件只接受 stdio 形式的 MCP server,所以每个 AIsa server 都写成一个带你 key 的 <code>mcp-remote</code> 小桥接(外加免费的 <code>aisa-docs</code>)。桥接随 app 启停 —— 没有后台服务,不开端口。模型无法更改:Claude Desktop 跑的是 Anthropic 自家模型。重启 app 后就能在工具菜单里看到这些 server。",
139
+ en: "Claude Desktop's config file only takes MCP servers over stdio, so the AIsa Router is written in as a small <code>mcp-remote</code> bridge carrying your key. The bridge lives and dies with the app — no background service, no open ports. Models cannot be changed: Claude Desktop runs Anthropic's own. Restart the app and the Router appears under its tools menu.",
140
+ zh: "Claude Desktop 的配置文件只接受 stdio 形式的 MCP server,所以 AIsa Router 写成一个带你 key 的 <code>mcp-remote</code> 小桥接。桥接随 app 启停 —— 没有后台服务,不开端口。模型无法更改:Claude Desktop 跑的是 Anthropic 自家模型。重启 app 后就能在工具菜单里看到 AIsa Router。",
141
141
  },
142
142
  "claude-ai": {
143
- en: "Nothing is installed. claude.ai takes remote MCP servers as <b>Connectors</b>: on the last step you get one URL per server with a Copy button, and add each under <b>Settings → Connectors → Add custom connector</b>, then press Connect — claude.ai runs the AIsa sign-in itself. Models cannot be changed: claude.ai runs Anthropic's own.",
144
- zh: "不安装任何东西。claude.ai 以 <b>Connectors</b> 的形式接受远程 MCP server:最后一步会给出每个 server 的 URL 和复制按钮,在 <b>Settings → Connectors → Add custom connector</b> 里逐个添加,然后点 Connect —— claude.ai 会自己跑 AIsa 的登录流程。模型无法更改:claude.ai 跑的是 Anthropic 自家模型。",
143
+ en: "Nothing is installed. claude.ai takes remote MCP servers as <b>Connectors</b>: on the last step you get the Router URL with a Copy button, and add it under <b>Settings → Connectors → Add custom connector</b>, then press Connect — claude.ai runs the AIsa sign-in itself. Models cannot be changed: claude.ai runs Anthropic's own.",
144
+ zh: "不安装任何东西。claude.ai 以 <b>Connectors</b> 的形式接受远程 MCP server:最后一步会给出 Router 的 URL 和复制按钮,在 <b>Settings → Connectors → Add custom connector</b> 里添加,然后点 Connect —— claude.ai 会自己跑 AIsa 的登录流程。模型无法更改:claude.ai 跑的是 Anthropic 自家模型。",
145
145
  },
146
146
  };
147
147
  /**
@@ -176,8 +176,8 @@ export const AGENT_HAVE_NOTES = {
176
176
  zh: "你原有的东西一样都不会被替换。那两个文件里其他的 MCP server 和模型分组保持原样;只有 <code>aisa-*</code> 条目和 AIsa 那个分组是我们写的,要移除就是删掉它们。",
177
177
  },
178
178
  cursor: {
179
- en: "Nothing of yours is replaced. Cursor adds each AIsa server beside your existing MCP entries under <code>aisa-*</code> names, and you approve every one inside Cursor first.",
180
- zh: "你原有的东西一样都不会被替换。Cursor 会把每个 AIsa server 以 <code>aisa-*</code> 命名加在你已有的 MCP 条目旁边,而且每一个都要你在 Cursor 里先确认。",
179
+ en: "Nothing of yours is replaced. Cursor adds the AIsa Router beside your existing MCP entries under the <code>aisa-router</code> name, and you approve it inside Cursor first.",
180
+ zh: "你原有的东西一样都不会被替换。Cursor 会把 AIsa Router 以 <code>aisa-router</code> 命名加在你已有的 MCP 条目旁边,并需要你在 Cursor 里先确认。",
181
181
  },
182
182
  };
183
183
  // ── the rail: every step's name ─────────────────────────────────────────────
@@ -325,88 +325,21 @@ export const BACKUP_COPY = {
325
325
  };
326
326
  // ── step 4: capabilities ────────────────────────────────────────────────────
327
327
  export const STEP_CAPS = {
328
- h1: { en: "What should your agent be able to reach?", zh: "你的 agent 需要够得着什么?" },
328
+ h1: { en: "Connect AIsa tools", zh: "连接 AIsa 工具" },
329
329
  ledeTail: {
330
- en: "Open an area to pick the servers inside it. Everything here is live production data with licensed sources; you can add more later with one more <code>aisa connect</code>.",
331
- zh: "点开一个领域来挑里面的 server。这里全都是有授权来源的生产环境实时数据;之后再跑一次 <code>aisa connect</code> 就能加更多。",
330
+ en: "One Router MCP connects your agent to AIsa tools. Existing MCP entries stay unchanged; remove old domain entries manually.",
331
+ zh: "通过一个 Router MCP 接入 AIsa 工具。已有 MCP 条目保持不变;旧的领域条目请自行删除。",
332
332
  },
333
- pickArea: { en: "Pick an area above", zh: "先在上面选一个领域" },
334
- selectAll: { en: "Select all in this area", zh: "全选这个领域" },
335
- /**
336
- * The counts sentence as one string, because punctuation is not shared
337
- * between languages: joining "N", "areas", ", " in code produced
338
- * "2 个领域, 2 个 server, 48 个工具." — Chinese text with ASCII commas and a
339
- * full stop. Whatever varies goes in as a placeholder instead.
340
- */
341
333
  counts: {
342
- en: "{areas} areas, {servers} servers, {tools} tools.",
343
- zh: "{areas} 个领域、{servers} 个 server、{tools} 个工具。",
344
- },
345
- /** Category tile meta: "3 servers · 47 tools". */
346
- tileMeta: {
347
- en: "{n} {noun} · {tools} tools",
348
- zh: "{n} 个 {noun} · {tools} 个工具",
334
+ en: "One unified AIsa MCP connection.",
335
+ zh: "一个统一的 AIsa MCP 连接。",
349
336
  },
350
- /**
351
- * A placeholder both renderers can fill. The page used to carry
352
- * <b id="mModel"></b> here and fill it with script — which left the
353
- * terminal printing "AIsa()" with nothing inside, because stripping tags is
354
- * all it can do with markup meant for the other surface.
355
- */
356
- serversWord: { en: "servers", zh: "server" },
357
- serverWord: { en: "server", zh: "server" },
358
- toolsWord: { en: "tools", zh: "个工具" },
359
337
  };
360
338
  /** Shown for a file-configured client that has no note of its own. */
361
339
  export const FILE_MODEL_FALLBACK = {
362
340
  en: "This client picks its own models inside the app, so nothing is changed here — only the MCP servers are added.",
363
341
  zh: "这个客户端在它自己的应用里选模型,所以这一步不做改动 —— 只添加 MCP server。",
364
342
  };
365
- export const CATEGORY_BLURB = {
366
- "Search & Research": {
367
- en: "Ranked web results with page text already extracted, plus YouTube.",
368
- zh: "带排序的网页搜索结果,正文已经抽好,另有 YouTube。",
369
- },
370
- Finance: {
371
- en: "US equities, crypto, prediction markets and what X is saying about a ticker.",
372
- zh: "美股、加密货币、预测市场,以及 X 上关于某只票的讨论。",
373
- },
374
- Social: {
375
- en: "Public X/Twitter, Reddit, Instagram and Pinterest — profiles, posts, engagement.",
376
- zh: "公开的 X/Twitter、Reddit、Instagram 和 Pinterest —— 资料、帖子、互动数据。",
377
- },
378
- Sales: {
379
- en: "Apollo B2B data — enrich people and companies, find prospects.",
380
- zh: "Apollo 的 B2B 数据 —— 补全人和公司信息、找潜在客户。",
381
- },
382
- "SEO & Search Data": {
383
- en: "Keywords, rankings, backlinks and on-page checks — and how a brand shows up in AI answers.",
384
- zh: "关键词、排名、外链、页面检查 —— 还有一个品牌在 AI 回答里的露出情况。",
385
- },
386
- Communication: {
387
- en: "An inbox an agent can actually use — its own address, sending and receiving.",
388
- zh: "agent 能真正用起来的邮箱 —— 自己的地址,能收能发。",
389
- },
390
- Creators: {
391
- en: "Find creators by audience, topic and reach, across platforms.",
392
- zh: "按受众、话题和影响力找创作者,跨平台。",
393
- },
394
- Search: {
395
- en: "YouTube search on its own.",
396
- zh: "单独的 YouTube 搜索。",
397
- },
398
- "Market Intelligence": {
399
- en: "Similarweb data — a site's traffic, engagement and where its visitors come from.",
400
- zh: "Similarweb 数据 —— 网站流量、互动情况,以及访客从哪来。",
401
- },
402
- };
403
- /** New host categories remain visible without waiting for a client release. */
404
- export function categoryBlurb(category, servers) {
405
- if (Object.hasOwn(CATEGORY_BLURB, category))
406
- return CATEGORY_BLURB[category];
407
- const names = servers.map((server) => server.name).filter(Boolean).join(", ");
408
- return { en: `Explore ${names || category}.`, zh: `查看 ${names || category} 的接口。` };
409
- }
410
343
  // ── step 5: install ─────────────────────────────────────────────────────────
411
344
  export const STEP_INSTALL = {
412
345
  h1: { en: "Ready to connect", zh: "准备接线" },
@@ -433,10 +366,6 @@ export const STEP_DONE = {
433
366
  // Resolved to the current language and injected as a single object, because
434
367
  // the browser has no access to this file. Same source, one hop later.
435
368
  export const RUNTIME_COPY = {
436
- nothingSelected: {
437
- en: "Nothing selected yet — pick at least one server.",
438
- zh: "还没有选择 —— 至少选一个 server。",
439
- },
440
369
  planBackup: {
441
370
  "claude-code": { en: "Install the claude-aisa command", zh: "安装 claude-aisa 命令" },
442
371
  codex: { en: "Add the aisa profile and codex-aisa", zh: "添加 aisa profile 和 codex-aisa" },
@@ -1,47 +1,19 @@
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
- */
1
+ /** Configure the unified Router MCP. Existing client entries are preserved. */
20
2
  export interface LiveServer {
21
3
  slug: string;
22
4
  name: string;
23
5
  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. */
6
+ toolCount?: number;
7
+ /** Capability description rendered on the connect page. */
27
8
  description: string;
28
9
  category: string;
29
10
  }
30
11
  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[]>;
12
+ /** Router's fixed public MCP contract, independent of the legacy module catalog. */
13
+ export declare function unifiedMcpServer(): LiveServer;
14
+ export declare function isUnifiedMcpSelection(servers: LiveServer[]): boolean;
15
+ /** The unified entry is fixed; setup does not depend on a directory service. */
16
+ export declare function getMcpServers(): LiveServer[];
45
17
  /** One config entry in the shape the client actually executes. */
46
18
  export declare function buildEntry(shape: "url" | "stdio", endpoint: string, key: string | undefined): Record<string, unknown>;
47
19
  export type WriteResult = {
@@ -51,12 +23,7 @@ export type WriteResult = {
51
23
  ok: false;
52
24
  reason: string;
53
25
  };
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
- */
26
+ /** Add the unified entry without deleting old AIsa, docs, or unrelated entries. */
60
27
  export declare function writeClientConfig(agent: string, chosen: LiveServer[], key: string | undefined): WriteResult;
61
28
  export declare function mcpSetupAction(options: {
62
29
  agent?: string;