@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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.10] — 2026-11-09
11
+
12
+ ### Changed
13
+
14
+ - Keep `connect` and its client/model setup flow, but configure only
15
+ `aisa-router` at `https://mcp.aisa.one/mcp`. Remove the directory-service dependency;
16
+ stop installing per-domain endpoints or an extra docs MCP.
17
+ - Preserve existing client entries for manual cleanup. `mcp setup --all` now
18
+ adds the same single Router entry; `mcp status` skips legacy endpoints.
19
+ - Reject resuming setup runs that still contain retired MCP endpoints.
20
+
10
21
  ## [0.6.9] — 2026-10-21
11
22
 
12
23
  ### Fixed
@@ -544,7 +555,9 @@ supports today; nothing here depends on a backend change.
544
555
  - Config commands (`aisa config get|set|list|reset`) and auth
545
556
  (`aisa login|logout|whoami`).
546
557
 
547
- [Unreleased]: https://github.com/AIsa-team/cli/compare/v0.6.8...HEAD
558
+ [Unreleased]: https://github.com/AIsa-team/cli/compare/v0.6.10...HEAD
559
+ [0.6.10]: https://github.com/AIsa-team/cli/compare/v0.6.9...v0.6.10
560
+ [0.6.9]: https://github.com/AIsa-team/cli/compare/v0.6.8...v0.6.9
548
561
  [0.6.8]: https://github.com/AIsa-team/cli/compare/v0.6.7...v0.6.8
549
562
  [0.6.7]: https://github.com/AIsa-team/cli/compare/v0.6.6...v0.6.7
550
563
  [0.6.6]: https://github.com/AIsa-team/cli/compare/v0.6.5...v0.6.6
package/README.md CHANGED
@@ -289,36 +289,37 @@ To publish a skill, open a pull request against
289
289
  npx @aisa-one/cli connect
290
290
  ```
291
291
 
292
- Opens a small local page (served by this process on `127.0.0.1`, shut down
293
- when finished) where you tick the AIsa MCP servers you want and the coding
294
- agents to install them into. Claude Code is configured through its own
295
- `claude mcp add` (user scope), then signed in through its own OAuth: connect
296
- runs `claude mcp login` per server, your browser opens the AIsa
297
- authorization, and the tokens live in Claude Code's own store where it also
298
- refreshes them — no API key, nothing pasted. Cursor, Claude Desktop and
299
- Windsurf get config entries and run the same OAuth themselves on first use.
300
- The page matches the AIsa Console style, reports authorization progress
301
- live, and a success page with copy-paste try-it-now prompts opens when
302
- everything is connected. No daemon stays behind. `--no-open` prints the URL
303
- instead of launching a browser; `--dry-run` shows what would be written.
304
-
305
- ### Scripted: `mcp setup`
292
+ The browser or terminal wizard connects your coding agent to the single AIsa
293
+ Router MCP at `https://mcp.aisa.one/mcp`, under the name `aisa-router`.
294
+ Client selection, installation, sign-in, model configuration and launch options
295
+ remain available. There is no per-domain server selection.
296
+
297
+ The CLI configures the fixed Router endpoint without requesting a directory
298
+ service. Available tools are discovered by the MCP client from the Router;
299
+ the CLI does not pin a tool count or inventory.
300
+
301
+ ### Scripted setup and status
306
302
 
307
303
  ```bash
308
- aisa mcp setup # configure the default servers for every detected client
309
- aisa mcp setup --all # every live server, not just the defaults
310
- aisa mcp setup --agent cursor # one client only
311
- aisa mcp status # list entries and ping each configured endpoint
304
+ aisa mcp setup --agent cursor # preview the single Router entry
305
+ aisa mcp setup --agent cursor --yes # add it to the detected client
306
+ aisa mcp setup --all --yes # accepted for compatibility; still one MCP
307
+ aisa mcp status # inspect entries and probe the unified endpoint
312
308
  ```
313
309
 
314
- `setup` reads the platform's discovery manifest (`aisa.one/.well-known/mcp.json`)
315
- at run time and writes one entry per live server, in the shape each client
316
- executes: a `url` entry for Cursor, an `npx mcp-remote` stdio bridge for
317
- Claude Desktop and Windsurf. With an API key configured the entries carry it
318
- as a Bearer header; without one they carry no credentials and the server's
319
- OAuth flow opens in your browser on first use. A config file that exists but
320
- does not parse is never overwritten. The docs-search MCP is always included
321
- as `aisa-docs`.
310
+ Cursor uses an HTTP URL entry; Claude Desktop uses an `npx mcp-remote` stdio
311
+ bridge. With a configured credential, setup writes the durable API key as a
312
+ Bearer header. Without one, the client handles the MCP OAuth flow on first use.
313
+ The server's OAuth resource metadata stays on `mcp.aisa.one`.
314
+
315
+ Existing domain entries, `aisa-docs`, and unrelated configuration are preserved.
316
+ The CLI no longer adds a docs MCP automatically. After validating the new
317
+ connection, remove old domain entries manually in each client. Status lists
318
+ legacy entries without sending requests to their retired endpoints.
319
+
320
+ DNS cutover requires the Router ingress and TLS certificate to accept
321
+ `mcp.aisa.one`, and its OAuth metadata to describe that host's `/mcp` resource.
322
+ Updating the CLI alone does not migrate existing clients or change DNS.
322
323
 
323
324
  ## Shell Completion
324
325
 
@@ -52,50 +52,7 @@ export const CATEGORY_ICON = {
52
52
  * AIsa tools on its own and no naming is needed at all.
53
53
  */
54
54
  export const EXAMPLES = {
55
- "web-search": [
56
- "Using AIsa, search the web for this week's biggest AI news and summarize the top 3 results with links.",
57
- "Using AIsa, research how teams are adopting MCP servers in production and give me a sourced brief.",
58
- ],
59
- "twitter-api": [
60
- "Using AIsa, fetch the latest tweets from @AnthropicAI and summarize the main themes.",
61
- "Using AIsa, pull @sama's five most recent tweets with their engagement numbers.",
62
- ],
63
- "crypto-market-data": [
64
- "Using AIsa, get Bitcoin's current price and 24h change, then compare it with Ethereum.",
65
- "Using AIsa's crypto data, list today's trending coins with prices and market caps.",
66
- ],
67
- marketpulse: [
68
- "Using AIsa, pull AAPL's latest income statement and summarize the revenue and margin trend.",
69
- "Using AIsa's market data, screen for US stocks with a market cap above $1T and compare their P/E ratios.",
70
- ],
71
- "stock-pulse": [
72
- "Using AIsa, show what X/Twitter is saying about NVDA today, joined with its market data.",
73
- "Using AIsa, find which tickers are trending on X right now and why.",
74
- ],
75
- "prediction-market-data": [
76
- "Using AIsa, list the most active prediction markets right now with their implied probabilities.",
77
- "Using AIsa, compare what Polymarket and Kalshi imply about the next Fed rate decision.",
78
- ],
79
- reddit: [
80
- "Using AIsa, find today's top posts in r/MachineLearning and summarize the discussion.",
81
- "Using AIsa, search Reddit for real-world Claude Code workflows people recommend.",
82
- ],
83
- "youtube-search": [
84
- "Using AIsa, find the three most relevant YouTube videos about MCP servers and list their channels.",
85
- "Using AIsa, find beginner YouTube tutorials for Claude Code and pick the best one to start with.",
86
- ],
87
- instagram: [
88
- "Using AIsa, fetch the Instagram profile and recent posts of @nasa and describe their content strategy.",
89
- "Using AIsa, compare the recent Instagram engagement of @natgeo and @nasa.",
90
- ],
91
- pinterest: [
92
- "Using AIsa, search Pinterest for 'mid-century interior' and summarize the visual trends.",
93
- "Using AIsa, find trending home-office ideas on Pinterest and summarize them with links.",
94
- ],
95
- apollo: [
96
- "Using AIsa, enrich the company anthropic.com — size, industry, and key people.",
97
- "Using AIsa, find five AI infrastructure startups in San Francisco with their CEOs.",
98
- ],
55
+ router: ["Using AIsa, research the latest developments in AI and cite your sources."],
99
56
  };
100
57
  export const FILE_CLIENT_LABELS = {
101
58
  cursor: "Cursor",
@@ -1,10 +1,9 @@
1
- import { MCP_DEFAULT_SLUGS } from "../constants.js";
2
1
  import { INSTALLERS } from "./install.js";
3
2
  import { defaultModelsFor } from "./llm-config.js";
4
3
  import { stripped } from "./mcp.js";
5
4
  import { BRAND_LOGOS } from "./brand-logos.js";
6
- import { STEP_AGENT, AGENT_ORDER, agentRank, AGENT_BADGE, AGENT_SIDE_TITLES, AGENT_NOTES, AGENT_HAVE_NOTES, installOffer, t, fill, LANGS, LANG_LABEL, NAV, STEP_TITLES, STEP_WELCOME, STEP_MODELS, STEP_CAPS, STEP_INSTALL, STEP_DONE, FILE_MODEL_NOTE, FILE_MODEL_FALLBACK, BACKUP_COPY, categoryBlurb, runtimeCopy, NEXT_WORD, SUPERSEDED, } from "./flow.js";
7
- import { RED, RED_CTA, INK, PAPER, I, LOGO, CATEGORY_ICON, EXAMPLES, CLIENT_LOGOS, CODEX_FACE, CLAUDE_BOT, OPENCODE_MARK, } from "./connect-shared.js";
5
+ import { STEP_AGENT, AGENT_ORDER, agentRank, AGENT_BADGE, AGENT_SIDE_TITLES, AGENT_NOTES, AGENT_HAVE_NOTES, installOffer, t, LANGS, LANG_LABEL, NAV, STEP_TITLES, STEP_WELCOME, STEP_MODELS, STEP_CAPS, STEP_INSTALL, STEP_DONE, FILE_MODEL_NOTE, FILE_MODEL_FALLBACK, BACKUP_COPY, runtimeCopy, NEXT_WORD, SUPERSEDED, } from "./flow.js";
6
+ import { RED, RED_CTA, INK, PAPER, I, LOGO, EXAMPLES, CLIENT_LOGOS, CODEX_FACE, CLAUDE_BOT, OPENCODE_MARK, } from "./connect-shared.js";
8
7
  import { AISA_PROVIDER_ID } from "../constants.js";
9
8
  import { VSCODE_MODELS } from "./vscode.js";
10
9
  /**
@@ -80,7 +79,6 @@ lang = "en") {
80
79
  const T = (x) => t(x, lang);
81
80
  /** A per-agent map of Text, flattened to the current language. */
82
81
  const resolved = (m) => Object.fromEntries(Object.entries(m).map(([k, v]) => [k, t(v, lang)]));
83
- const totalTools = servers.reduce((n, s) => n + s.toolCount, 0);
84
82
  const cats = new Map();
85
83
  for (const s of servers) {
86
84
  const c = normCategory(s.category);
@@ -95,7 +93,6 @@ lang = "en") {
95
93
  endpoint: s.endpoint,
96
94
  name: stripped(s.name),
97
95
  category: normCategory(s.category),
98
- toolCount: s.toolCount,
99
96
  description: s.description,
100
97
  }));
101
98
  // Windsurf is not part of this flow (its file path stays in T1). claude.ai
@@ -204,36 +201,22 @@ ${restChips}
204
201
  <button type="button" class="mw-fix" id="modelfix">${T(STEP_MODELS.warn.fix)}</button>
205
202
  </div>`;
206
203
  // ── step 4: capabilities ──
207
- const catTiles = catList
208
- .map(([cat, list]) => {
209
- const tools = list.reduce((n, s) => n + s.toolCount, 0);
210
- return `<button type="button" class="ctile" data-cat="${cat}">
211
- <span class="cico">${CATEGORY_ICON[cat] ?? I.sparkles}</span>
212
- <span class="cname">${cat}</span>
213
- <span class="cmeta">${fill(STEP_CAPS.tileMeta, lang, { n: list.length, noun: list.length > 1 ? T(STEP_CAPS.serversWord) : T(STEP_CAPS.serverWord), tools })}</span>
214
- <span class="cblurb">${T(categoryBlurb(cat, list)).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</span>
215
- <span class="csel" data-csel></span></button>`;
216
- })
217
- .join("");
218
204
  const serverTiles = catList
219
205
  .map(([cat, list]) => list
220
206
  .map((s) => {
221
- const checked = MCP_DEFAULT_SLUGS.includes(s.slug);
222
- return `<label class="stile${checked ? " on" : ""}" data-cat="${cat}">
223
- <input type="checkbox" name="server" value="${s.slug}"${checked ? " checked" : ""}>
207
+ return `<label class="stile on" data-cat="${cat}">
208
+ <input type="checkbox" name="server" value="${s.slug}" checked disabled>
224
209
  <span class="tbody"><span class="thead"><span class="tname">${stripped(s.name)}</span>
225
- <span class="badge">${s.toolCount} tools</span></span>
210
+ </span>
226
211
  <span class="sdesc">${s.description}</span></span></label>`;
227
212
  })
228
213
  .join(""))
229
214
  .join("");
230
215
  const caps = `
231
216
  <h1>${T(STEP_CAPS.h1).replace("reach", "<em>reach</em>")}</h1>
232
- <p class="lede">${fill(STEP_CAPS.counts, lang, { areas: catList.length, servers: servers.length, tools: totalTools })} ${T(STEP_CAPS.ledeTail)}</p>
233
- <div class="cgrid">${catTiles}</div>
217
+ <p class="lede">${T(STEP_CAPS.counts)} ${T(STEP_CAPS.ledeTail)}</p>
234
218
  <div class="spanel" id="spanel">
235
- <div class="sphead"><h2 id="spTitle">${T(STEP_CAPS.pickArea)}</h2><button type="button" class="link" id="spAll">${T(STEP_CAPS.selectAll)}</button></div>
236
- <div class="sgrid" id="sgrid">${serverTiles}</div>
219
+ <div class="sgrid" id="sgrid">${serverTiles}</div>
237
220
  </div>
238
221
  <div class="tally" id="tally"></div>`;
239
222
  // ── step 5: install ──
@@ -757,42 +740,11 @@ ${restChips}
757
740
  });
758
741
 
759
742
  // ── step 4 ──
760
- var activeCat = null;
761
743
  function syncCaps() {
762
- var picked = pickedServers();
763
- $$(".ctile").forEach(function (t) {
764
- var cat = t.dataset.cat;
765
- var n = SERVERS.filter(function (s) { return s.category === cat && picked.indexOf(s.slug) !== -1; }).length;
766
- var sel = t.querySelector("[data-csel]");
767
- sel.textContent = n ? n + " selected" : "";
768
- t.classList.toggle("has", n > 0);
769
- t.classList.toggle("active", cat === activeCat);
770
- });
771
- $$(".stile").forEach(function (t) {
772
- t.classList.toggle("on", t.querySelector("input").checked);
773
- t.style.display = t.dataset.cat === activeCat ? "" : "none";
774
- });
775
- $("#spanel").classList.toggle("open", Boolean(activeCat));
776
- $("#spTitle").textContent = activeCat ? activeCat : "Pick an area above";
777
- var tools = picked.reduce(function (n, s) { return n + (BY_SLUG[s] ? BY_SLUG[s].toolCount : 0); }, 0);
778
- $("#tally").innerHTML = picked.length
779
- ? "<b>" + picked.length + " server" + (picked.length > 1 ? "s" : "") + "</b> · " + tools + " tools selected"
780
- : COPY.nothingSelected;
744
+ $$('input[name="server"]').forEach(function (i) { i.checked = true; i.disabled = true; });
745
+ $("#spanel").classList.add("open");
746
+ $("#tally").textContent = SERVERS[0].endpoint;
781
747
  }
782
- $$(".ctile").forEach(function (t) { t.addEventListener("click", function () {
783
- activeCat = activeCat === t.dataset.cat ? null : t.dataset.cat; syncCaps();
784
- if (activeCat) $("#spanel").scrollIntoView({ block: "nearest", behavior: "smooth" });
785
- }); });
786
- $$('input[name="server"]').forEach(function (c) { c.addEventListener("change", function () { syncCaps(); publishDraft(current); }); });
787
- $("#spAll").addEventListener("click", function () {
788
- if (!activeCat) return;
789
- var boxes = $$('.stile[data-cat="' + activeCat + '"] input');
790
- var all = boxes.every(function (b) { return b.checked; });
791
- boxes.forEach(function (b) { b.checked = !all; });
792
- syncCaps();
793
- });
794
- // Open the area holding the default selection so the page never starts blank.
795
- (function () { var first = $('input[name="server"]:checked'); if (first) activeCat = first.closest(".stile").dataset.cat; })();
796
748
 
797
749
  // ── next button ──
798
750
  nextBtn.addEventListener("click", function () {
@@ -984,7 +936,6 @@ ${restChips}
984
936
  locked = true;
985
937
  paintRail();
986
938
  $$('.pane[data-pane="2"] input, .pane[data-pane="3"] input, .pane[data-pane="4"] input').forEach(function (i) { i.disabled = true; });
987
- $$(".ctile, #spAll").forEach(function (b) { b.disabled = false; });
988
939
  document.body.classList.add("locked");
989
940
  }
990
941
  function start() {
@@ -1029,7 +980,6 @@ ${restChips}
1029
980
  var sel = s.selection, steps = s.steps || [];
1030
981
  var id = sel.clients[0], name = LABEL[id] || id;
1031
982
  var chosen = sel.servers.map(function (x) { return BY_SLUG[x]; }).filter(Boolean);
1032
- var tools = chosen.reduce(function (n, x) { return n + x.toolCount; }, 0);
1033
983
  var failed = steps.filter(function (x) { return x.state === "fail"; });
1034
984
  var mcpFailed = failed.some(function (x) { return x.id === "mcp"; });
1035
985
  var installed = steps.filter(function (x) { return x.id.indexOf("install:") === 0 && x.id !== "install:aisa-cli" && x.state === "ok"; })
@@ -1041,8 +991,8 @@ ${restChips}
1041
991
  var head = mcpFailed
1042
992
  ? "<div class='eyebrow'>Almost there</div><h1>Your agent is <em>not connected yet</em></h1><p class='lede'>The MCP entries could not be added to <b>" + name + "</b> — details below.</p>"
1043
993
  : (installed.length
1044
- ? "<h1><em>Congratulations!</em> " + installed.join(" & ") + " is installed and armed with " + tools + " powerful tools<span class='h1check'>" + ICON_CHECK + "</span></h1><p class='lede'><b>" + installed.join(" & ") + "</b> is on this machine, signed in to AIsa" + (llmOk ? ", running on <b>" + MODEL_FOR[id] + "</b> through AIsa," : ",") + " with " + chosen.length + " MCP server" + (chosen.length > 1 ? "s" : "") + " wired in — a complete setup, nothing else to configure.</p>"
1045
- : "<h1><em>Congratulations!</em> Your agent just got " + tools + " powerful new tool" + (tools > 1 ? "s" : "") + ".<span class='h1check'>" + ICON_CHECK + "</span></h1><p class='lede'>" + (id === "claude-ai"
994
+ ? "<h1><em>Congratulations!</em> " + installed.join(" & ") + " is installed and armed with AIsa tools<span class='h1check'>" + ICON_CHECK + "</span></h1><p class='lede'><b>" + installed.join(" & ") + "</b> is on this machine, signed in to AIsa" + (llmOk ? ", running on <b>" + MODEL_FOR[id] + "</b> through AIsa," : ",") + " with " + chosen.length + " MCP server" + (chosen.length > 1 ? "s" : "") + " wired in — a complete setup, nothing else to configure.</p>"
995
+ : "<h1><em>Congratulations!</em> Your agent just got access to AIsa tools.<span class='h1check'>" + ICON_CHECK + "</span></h1><p class='lede'>" + (id === "claude-ai"
1046
996
  ? chosen.length + " connector URL" + (chosen.length > 1 ? "s are" : " is") + " ready for <b>Claude.ai</b> — add them below, it takes about a minute."
1047
997
  : id === "cursor" && s.deeplinks && s.deeplinks.length
1048
998
  ? chosen.length + " install link" + (chosen.length > 1 ? "s are" : " is") + " ready for <b>Cursor</b> — one click each, below."
@@ -1052,7 +1002,7 @@ ${restChips}
1052
1002
  failed.map(function (x) { return "<li><b>" + x.label + "</b> — " + (x.detail || "failed") + "</li>"; }).join("") +
1053
1003
  "</ul>Fix the above, then run <code>npx @aisa-one/cli connect</code> again — it is safe to re-run.</div></div>" : "";
1054
1004
  var ICON = { ok: "✓", fail: "✕", skip: "–", pending: "·", running: "·" };
1055
- var recap = "<h2>Everything you just gained</h2><div class='recap'><div class='rsum'>" + chosen.length + " capabilit" + (chosen.length === 1 ? "y" : "ies") + " · " + tools + " tools · " + name + "</div>" +
1005
+ var recap = "<h2>Everything you just gained</h2><div class='recap'><div class='rsum'>AIsa Router MCP · " + name + "</div>" +
1056
1006
  // The balance has its own card right below, so its row stays out of the recap.
1057
1007
  steps.filter(function (x) { return x.id !== "balance"; }).map(function (x) { return "<div class='rrow " + x.state + "'><span class='rl'>" + x.label + "</span><span class='rd' title=\\"" + (x.detail || "").replace(/"/g, "&quot;") + "\\">" + (x.detail || "") + "</span><span class='ri'>" + ICON[x.state] + "</span></div>"; }).join("") + "</div>";
1058
1008
  var bal = s.balanceMicros, low = bal !== null && bal !== undefined && bal < 5e6;
@@ -1065,11 +1015,11 @@ ${restChips}
1065
1015
  var fileNote = "";
1066
1016
  if (id === "claude-desktop") {
1067
1017
  fileNote = "<div class='vsccard'><div class='vschead'><span class='blogo lg'>" + CLAUDE_DT_LOGO + "</span><div><b>Claude Desktop is ready</b>" +
1068
- "<div class='fine' style='margin:0'>" + chosen.length + " MCP server" + (chosen.length > 1 ? "s" : "") + " (plus aisa-docs) are in its config — one restart loads them.</div></div>" +
1018
+ "<div class='fine' style='margin:0'>" + chosen.length + " MCP server" + (chosen.length > 1 ? "s" : "") + " are in its config — one restart loads them.</div></div>" +
1069
1019
  "<div class='termside'><button type='button' class='cta sm' id='launch'>Restart Claude Desktop →</button><span class='fine' id='launchnote'></span></div></div>" +
1070
1020
  "<div class='vscgrid'>" +
1071
- "<div class='vscitem'><span class='srv'>Tools</span>After the restart, open the <b>tools menu</b> (🔧) in a chat — the <code>aisa-…</code> servers are there. Each runs as a small bridge that lives and dies with the app; no background service.</div>" +
1072
- "<div class='vscitem'><span class='srv'>Across devices</span>Prefer one setup on every device? Add the same server URLs as <b>custom Connectors</b> under Settings → Connectors — they sync with claude.ai through your account.</div>" +
1021
+ "<div class='vscitem'><span class='srv'>Tools</span>After the restart, open the <b>tools menu</b> (🔧) in a chat — the <code>aisa-router</code> server is there. It runs as a small bridge that lives and dies with the app; no background service.</div>" +
1022
+ "<div class='vscitem'><span class='srv'>Across devices</span>Prefer one setup on every device? Add the same server URL as <b>custom Connectors</b> under Settings → Connectors — they sync with claude.ai through your account.</div>" +
1073
1023
  "</div></div>";
1074
1024
  }
1075
1025
  if (id === "vscode") {
@@ -1081,7 +1031,7 @@ ${restChips}
1081
1031
  "<div class='termside'><button type='button' class='cta sm' id='launch'>Open VS Code →</button><span class='fine' id='launchnote'></span></div></div>" +
1082
1032
  "<div class='vscgrid'>" +
1083
1033
  (modelsOn ? "<div class='vscitem'><span class='srv'>Models</span>Open <b>Chat</b> (<code>Ctrl+Cmd+I</code>), click the model name at the bottom-right of the input — the <b>AIsa</b> group lists Claude, GPT, DeepSeek, Kimi, GLM and Qwen. The AIsa extension already stored your key.</div>" : "") +
1084
- "<div class='vscitem'><span class='srv'>Tools</span>Switch Chat to <b>Agent</b> mode and tick the <code>aisa-…</code> servers in the tools picker (🔧). They are listed under <b>Extensions → MCP Servers</b>; if one is missing, run <b>Reload Window</b> once.</div>" +
1034
+ "<div class='vscitem'><span class='srv'>Tools</span>Switch Chat to <b>Agent</b> mode and tick the <code>aisa-router</code> server in the tools picker (🔧). It is listed under <b>Extensions → MCP Servers</b>; if one is missing, run <b>Reload Window</b> once.</div>" +
1085
1035
  "</div></div>";
1086
1036
  if (sel.llmMode === "backup" && needsPaste) {
1087
1037
  fileNote += "<h2>One paste in VS Code — your key</h2><div class='webcard'>" +
@@ -1163,7 +1113,7 @@ ${restChips}
1163
1113
  $$('input[name="server"]').forEach(function (i) { i.checked = sel.servers.indexOf(i.value) !== -1; });
1164
1114
  var fresh = sel.install.length > 0;
1165
1115
  var r = $('input[name="' + (fresh ? "lmodeFresh" : "lmodeDet") + '"][value="' + sel.llmMode + '"]'); if (r) r.checked = true;
1166
- var first = $('input[name="server"]:checked'); if (first) activeCat = first.closest(".stile").dataset.cat;
1116
+
1167
1117
  syncAgent(); syncModels(); syncCaps(); lockSelections();
1168
1118
  phase = s.phase; serverSteps = s.steps; lastStatus = s;
1169
1119
  var settled = phase === "done" || phase === "failed";
@@ -1465,21 +1415,6 @@ function shellT2(title, body) {
1465
1415
  .mw-fix { margin-top: .7rem; background: var(--red-cta); color: #fff; border: 0; border-radius: 6px;
1466
1416
  padding: .5rem 1.2rem; font: inherit; font-weight: 700; cursor: pointer; }
1467
1417
  @keyframes mwshake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } }
1468
- /* capabilities: the area grid, then the servers of the open area */
1469
- .cgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: .9rem; margin: 1.6rem 0 1rem; }
1470
- .ctile { position: relative; display: flex; flex-direction: column; gap: .25rem; align-items: flex-start; text-align: left;
1471
- background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 1.2rem 1.2rem 1.1rem;
1472
- font: inherit; color: var(--ink); cursor: pointer; min-height: 168px; transition: border-color .15s, transform .15s; }
1473
- .ctile:hover { border-color: color-mix(in srgb, var(--red) 45%, var(--line)); transform: translateY(-1px); }
1474
- .ctile.active { border-color: var(--red); box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); }
1475
- .ctile.has { border-left: 3px solid var(--ok); }
1476
- .cico { width: 40px; height: 40px; border-radius: 10px; background: var(--tint); color: var(--red);
1477
- display: flex; align-items: center; justify-content: center; margin-bottom: .4rem; }
1478
- .cico svg { width: 20px; height: 20px; }
1479
- .cname { font-weight: 800; font-size: 1.05rem; }
1480
- .cmeta { font-size: .78rem; color: var(--muted); }
1481
- .cblurb { font-size: .86rem; color: var(--muted); margin-top: .3rem; }
1482
- .csel { position: absolute; top: .8rem; right: .9rem; font-size: .74rem; font-weight: 700; color: var(--ok); }
1483
1418
  .spanel { display: none; border: 1px solid var(--line); border-radius: 12px; background: color-mix(in srgb, var(--card) 60%, var(--paper));
1484
1419
  padding: 1rem 1.2rem 1.2rem; }
1485
1420
  .spanel.open { display: block; animation: fade .2s ease; }
@@ -1,6 +1,6 @@
1
1
  import * as readline from "node:readline/promises";
2
2
  import chalk from "chalk";
3
- import { AGENT_BADGE, AGENT_NOTES, BACKUP_COPY, CONFIRM, FILE_MODEL_FALLBACK, FILE_MODEL_NOTE, STEP_AGENT, STEP_CAPS, STEP_MODELS, STEP_TITLES, STEP_WELCOME, agentRank, fill, t, } from "./flow.js";
3
+ import { AGENT_BADGE, AGENT_NOTES, BACKUP_COPY, CONFIRM, FILE_MODEL_FALLBACK, FILE_MODEL_NOTE, STEP_AGENT, STEP_CAPS, STEP_MODELS, STEP_TITLES, STEP_WELCOME, agentRank, t, } from "./flow.js";
4
4
  import { INSTALLERS } from "./install.js";
5
5
  import { defaultModelsFor } from "./llm-config.js";
6
6
  import { httpFetch } from "../utils/http.js";
@@ -612,104 +612,10 @@ export async function runTerminalFlow(o) {
612
612
  // ── step 4: capabilities ──
613
613
  if (from <= 4) {
614
614
  console.log(header(4, o.lang));
615
- const totalTools = o.servers.reduce((n, s) => n + s.toolCount, 0);
616
- const cats = [...new Set(o.servers.map((s) => s.category))];
617
- say(fill(STEP_CAPS.counts, o.lang, {
618
- areas: cats.length, servers: o.servers.length, tools: totalTools,
619
- }));
620
- console.log(dim("│"));
621
- // Pull once more: the capability list is long, and a page open beside
622
- // this one is the likelier place to have ticked through it.
623
- const fresh = await pull(o);
624
- if (fresh?.draft?.servers?.length)
625
- draft.servers = fresh.draft.servers;
626
- if (fresh)
627
- rev = fresh.rev;
628
- const chosen = new Set(draft.servers);
629
- if (interactive()) {
630
- // Sorted by area, with the area named once where it changes.
631
- //
632
- // Twenty-five servers over eight areas — and five of those areas hold
633
- // exactly one server, so a heading per area produced "heading, row,
634
- // heading, row", which is a hat on every line rather than a grouping.
635
- // The name on the first row of each run says the same thing and costs
636
- // no rows: what follows inherits it from the blank space beside it.
637
- const ordered = [...o.servers].sort((a, b) => a.category.localeCompare(b.category) || a.slug.localeCompare(b.slug));
638
- const rowFor = (slug) => ordered.findIndex((s) => s.slug === slug);
639
- const initial = [...chosen].map(rowFor).filter((i) => i >= 0);
640
- const a3 = await pickOrWatch(o, rev, 4, ordered.map((srv) => ({
641
- label: srv.slug,
642
- meta: `${srv.toolCount} ${t(STEP_CAPS.toolsWord, o.lang)}`,
643
- tag: srv.category,
644
- // Straight from the host, the same sentence the page shows. A
645
- // hand-written table here would be a second source of truth for
646
- // something that ships with the server.
647
- detail: srv.description,
648
- })), true, initial, o.lang === "zh"
649
- ? "↑↓ 移动 · 空格勾选 · a 全选/全不选 · 回车确认 · esc 返回上一步"
650
- : "↑↓ move · space to tick · a for all · enter to confirm · esc to go back", 3, {
651
- read: (d) => d.servers === undefined ? undefined : d.servers.map(rowFor).filter((i) => i >= 0),
652
- write: async (indexes) => {
653
- const next = indexes.map((i) => ordered[i]?.slug).filter((x) => Boolean(x));
654
- ({ rev } = await push(o, rev, { draft: { servers: next } }));
655
- return rev;
656
- },
657
- });
658
- // In a checklist the way back cannot be another row — a row is a
659
- // thing you tick — so Escape is the gesture, and the hint says so.
660
- if (a3.by === "back")
661
- throw new Rewind(a3.to, a3.from === "here");
662
- if (a3.by === "page") {
663
- chosen.clear();
664
- for (const slug of a3.draft.servers ?? [])
665
- chosen.add(slug);
666
- const s2 = await pull(o);
667
- if (s2)
668
- rev = s2.rev;
669
- console.log("\n" + dim("│ ") + chalk.magenta(o.lang === "zh" ? "↩ 已在页面中选择" : "↩ chosen in the page"));
670
- }
671
- else {
672
- chosen.clear();
673
- for (const i of a3.picked ?? []) {
674
- const srv = ordered[i];
675
- if (srv)
676
- chosen.add(srv.slug);
677
- }
678
- }
679
- }
680
- else {
681
- o.servers.forEach((s, i) => {
682
- const mark = chosen.has(s.slug) ? chalk.green("[x]") : dim("[ ]");
683
- console.log(`${dim("│")} ${mark} ${bold(String(i + 1).padStart(2))}) ${s.slug.padEnd(24)} ${dim(String(s.toolCount) + " " + t(STEP_CAPS.toolsWord, o.lang))}`);
684
- });
685
- console.log(dim("│"));
686
- say(o.lang === "zh"
687
- ? "输入编号切换选中(空格分隔),直接回车确认。"
688
- : "Type numbers to toggle (space-separated), or press enter to confirm.");
689
- for (;;) {
690
- const answer = await askLine(bold.cyan("│ > "));
691
- if (answer === "")
692
- break;
693
- for (const tok of answer.split(/[\s,]+/)) {
694
- const n = Number(tok);
695
- if (!Number.isInteger(n) || n < 1 || n > o.servers.length)
696
- continue;
697
- const slug = o.servers[n - 1].slug;
698
- if (chosen.has(slug))
699
- chosen.delete(slug);
700
- else
701
- chosen.add(slug);
702
- }
703
- console.log(dim("│ ") + chalk.green(`${chosen.size} selected: `) + dim([...chosen].join(", ")));
704
- ({ rev } = await push(o, rev, { draft: { servers: [...chosen] } }));
705
- }
706
- }
707
- draft.servers = [...chosen];
708
- if (draft.servers.length === 0) {
709
- console.log(dim("└─ ") + chalk.yellow(o.lang === "zh" ? "至少选一个" : "Pick at least one"));
710
- return undefined;
711
- }
712
- console.log(dim("└─ ") + chalk.green(`${draft.servers.length} ✓`));
615
+ say(t(STEP_CAPS.ledeTail, o.lang));
616
+ draft.servers = o.servers.map((s) => s.slug);
617
+ for (const server of o.servers)
618
+ console.log(dim("│ ") + server.name + " — " + server.endpoint);
713
619
  // Publish the choice but NOT the step: arriving at step 5 is what makes
714
620
  // the page start the run by itself, and the user has not confirmed yet.
715
621
  // Announcing the step here applied everything without being asked.