@aisa-one/cli 0.6.8 → 0.6.9
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 +15 -0
- package/README.md +19 -8
- package/dist/catalog.d.ts +9 -3
- package/dist/catalog.js +27 -12
- package/dist/commands/api.js +7 -3
- package/dist/commands/connect-t2.js +2 -2
- package/dist/commands/connect.js +40 -9
- package/dist/commands/flow.d.ts +4 -0
- package/dist/commands/flow.js +9 -2
- package/dist/commands/mcp.js +15 -2
- package/dist/completion.js +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/utils/client-key.d.ts +1 -0
- package/dist/utils/client-key.js +36 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.9] — 2026-10-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `aisa connect` and `aisa mcp setup` write the long-lived "aisa cli" key
|
|
15
|
+
(minted via `/v1/keys/mint`) into third-party client configs instead of the
|
|
16
|
+
OAuth access token, which expired after 24 hours. Static keys are unchanged.
|
|
17
|
+
Re-run `aisa connect` to replace a config written by 0.6.0–0.6.8.
|
|
18
|
+
- Preserve Runtime's successful-request billing condition and the full reference
|
|
19
|
+
rate in catalog details. Unknown estimates are not free requests.
|
|
20
|
+
- Revalidate catalog references every five minutes, bypass legacy long-lived
|
|
21
|
+
caches, and label stale offline references. Completion uses the same cache.
|
|
22
|
+
- Display newly published MCP categories using live server names when curated
|
|
23
|
+
copy is unavailable.
|
|
24
|
+
|
|
10
25
|
## [0.6.8] — 2026-10-14
|
|
11
26
|
|
|
12
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -164,18 +164,26 @@ aisa api show dataforseo --all # long lists truncate to 40 by default
|
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
A provider **id is not always its URL slug** — `brave-search` serves
|
|
167
|
-
`/apis/v1/brave/...`, and `api show` prints the catalog path.
|
|
168
|
-
|
|
167
|
+
`/apis/v1/brave/...`, and `api show` prints the catalog path. Use the published
|
|
168
|
+
Router schema for methods and request parameters; legacy catalog entries may
|
|
169
|
+
still report an advisory method.
|
|
169
170
|
|
|
170
171
|
Prices with `type: metered` display as `usage-based` (billing depends on usage),
|
|
171
172
|
even when the catalog returns `normal: 0`. Details show `usage-based (cost varies)`.
|
|
172
173
|
Use `aisa quote` for a request-specific estimate; JSON keeps the original pricing metadata.
|
|
174
|
+
When Runtime declares `customer_pricing: fixed_success`, endpoint details say
|
|
175
|
+
`per successful request`. Other fixed catalog amounts remain references until
|
|
176
|
+
their charging conditions are checked. Unavailable estimates do not mean free,
|
|
177
|
+
and displayed rates retain the supplied amount without rounding.
|
|
173
178
|
In `--json` output, metered providers and endpoints also include `pricing_note`
|
|
174
179
|
with quote guidance, including endpoints nested in provider details. The list
|
|
175
180
|
remains an array, and the original `pricing` fields are unchanged.
|
|
176
181
|
|
|
177
|
-
The catalog is cached in `~/.aisa/cache` (override with
|
|
178
|
-
|
|
182
|
+
The catalog is cached for five minutes in `~/.aisa/cache` (override with
|
|
183
|
+
`AISA_CACHE_DIR`). Upgrading bypasses the old six-hour/day-long catalog cache.
|
|
184
|
+
If refresh fails, an explicitly labelled stale reference remains available;
|
|
185
|
+
schema and quote are still required before a call. Pass `--refresh` to bypass
|
|
186
|
+
the cache, or `aisa cache clear`.
|
|
179
187
|
|
|
180
188
|
## LLM Gateway
|
|
181
189
|
|
|
@@ -366,8 +374,11 @@ Settings:
|
|
|
366
374
|
`aisa login` stores OAuth credentials in `~/.aisa/tokens.json`.
|
|
367
375
|
`aisa login --key <key>` stores a static credential without refresh metadata.
|
|
368
376
|
Legacy mirrors contain the current access token; older CLIs cannot refresh it.
|
|
369
|
-
Third-party client configurations written by `aisa connect`
|
|
370
|
-
|
|
377
|
+
Third-party client configurations written by `aisa connect` and
|
|
378
|
+
`aisa mcp setup` get the long-lived "aisa cli" API key, minted from your
|
|
379
|
+
sign-in, never the expiring OAuth access token. Configs written by 0.6.0–0.6.8
|
|
380
|
+
hold an access token that stops working after a day; run `aisa connect` again
|
|
381
|
+
to replace it.
|
|
371
382
|
Environment variables:
|
|
372
383
|
`AISA_API_KEY` takes precedence over the stored key.
|
|
373
384
|
`AISA_ROUTER_BASE_URL` is the Router origin/prefix before
|
|
@@ -398,8 +409,8 @@ configured `baseUrl`. It tolerates a value with either suffix already attached,
|
|
|
398
409
|
because the shipped default has always included `/v1` and is persisted in every
|
|
399
410
|
existing user's config.
|
|
400
411
|
|
|
401
|
-
**The catalog's shape has three traps.** `endpoints[].method`
|
|
402
|
-
|
|
412
|
+
**The catalog's shape has three traps.** Legacy `endpoints[].method` values can
|
|
413
|
+
be advisory. `endpoint_groups[].name` is an operator-entered label
|
|
403
414
|
(`Zero`, `One`, `default`) with no business meaning, which is why `api show`
|
|
404
415
|
flattens by default. And health is tracked per provider, not per endpoint, so
|
|
405
416
|
per-endpoint counts are one value repeated.
|
package/dist/catalog.d.ts
CHANGED
|
@@ -11,8 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export interface CatalogPricing {
|
|
13
13
|
type: string;
|
|
14
|
-
/**
|
|
15
|
-
normal
|
|
14
|
+
/** Runtime reference estimate in dollars; unavailable estimates are not zero prices. */
|
|
15
|
+
normal?: number;
|
|
16
|
+
estimate_unavailable?: boolean;
|
|
17
|
+
/** Runtime's pricing declaration; provider costs do not determine customer charges. */
|
|
18
|
+
details?: {
|
|
19
|
+
customer_pricing?: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
16
22
|
}
|
|
17
23
|
export interface CatalogProvider {
|
|
18
24
|
id: string;
|
|
@@ -69,6 +75,6 @@ export declare function runSlugOf(detail: CatalogDetail): string | undefined;
|
|
|
69
75
|
* Format catalog pricing, keeping metered billing distinct from a free request.
|
|
70
76
|
* Fixed prices span $0.000001 to $0.08, so a fixed
|
|
71
77
|
* number of decimals either rounds the cheap endpoints to $0.00 or pads the
|
|
72
|
-
* rest with noise
|
|
78
|
+
* rest with noise. Preserve the supplied amount without rounding its rate.
|
|
73
79
|
*/
|
|
74
80
|
export declare function formatPrice(pricing?: CatalogPricing): string;
|
package/dist/catalog.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { publicRequest, cacheScope } from "./api.js";
|
|
2
2
|
import { readCache, writeCache, touchCache } from "./cache.js";
|
|
3
|
-
const CATEGORY_TTL_MS =
|
|
4
|
-
const DETAIL_TTL_MS =
|
|
3
|
+
const CATEGORY_TTL_MS = 5 * 60 * 1000;
|
|
4
|
+
const DETAIL_TTL_MS = 5 * 60 * 1000;
|
|
5
5
|
const HEALTH_TTL_MS = 5 * 60 * 1000;
|
|
6
6
|
/** Fetch with disk caching and ETag revalidation; falls back to stale data offline. */
|
|
7
7
|
async function cachedGet(path, cacheKey, ttlMs, refresh) {
|
|
@@ -13,8 +13,10 @@ async function cachedGet(path, cacheKey, ttlMs, refresh) {
|
|
|
13
13
|
res = await publicRequest(path, { etag: refresh ? undefined : cached?.etag });
|
|
14
14
|
}
|
|
15
15
|
catch (err) {
|
|
16
|
-
if (cached)
|
|
16
|
+
if (cached) {
|
|
17
|
+
console.error("Warning: catalog refresh failed; showing a stale cached reference. Use aisa schema and aisa quote before calling.");
|
|
17
18
|
return cached.data;
|
|
19
|
+
}
|
|
18
20
|
throw err;
|
|
19
21
|
}
|
|
20
22
|
if (res.status === 304 && cached) {
|
|
@@ -22,19 +24,21 @@ async function cachedGet(path, cacheKey, ttlMs, refresh) {
|
|
|
22
24
|
return cached.data;
|
|
23
25
|
}
|
|
24
26
|
if (!res.data) {
|
|
25
|
-
if (cached)
|
|
27
|
+
if (cached) {
|
|
28
|
+
console.error("Warning: catalog refresh failed; showing a stale cached reference. Use aisa schema and aisa quote before calling.");
|
|
26
29
|
return cached.data;
|
|
30
|
+
}
|
|
27
31
|
throw new Error(`Catalog request failed: ${path} returned ${res.status}`);
|
|
28
32
|
}
|
|
29
33
|
writeCache(cacheKey, res.data, ttlMs, res.etag);
|
|
30
34
|
return res.data;
|
|
31
35
|
}
|
|
32
36
|
export async function getProviders(options = {}) {
|
|
33
|
-
const data = await cachedGet("info/apis/category", `catalog/${cacheScope()}/category.json`, CATEGORY_TTL_MS, options.refresh);
|
|
37
|
+
const data = await cachedGet("info/apis/category", `catalog-v2/${cacheScope()}/category.json`, CATEGORY_TTL_MS, options.refresh);
|
|
34
38
|
return data.apis || [];
|
|
35
39
|
}
|
|
36
40
|
export async function getProviderDetail(id, options = {}) {
|
|
37
|
-
const data = await cachedGet(`info/apis/${encodeURIComponent(id)}`, `catalog/${cacheScope()}/${id.replace(/[^\w.-]/g, "_")}.json`, DETAIL_TTL_MS, options.refresh);
|
|
41
|
+
const data = await cachedGet(`info/apis/${encodeURIComponent(id)}`, `catalog-v2/${cacheScope()}/${id.replace(/[^\w.-]/g, "_")}.json`, DETAIL_TTL_MS, options.refresh);
|
|
38
42
|
if (!data.api)
|
|
39
43
|
throw new Error(`Unknown API "${id}"`);
|
|
40
44
|
return data.api;
|
|
@@ -65,18 +69,29 @@ export function runSlugOf(detail) {
|
|
|
65
69
|
* Format catalog pricing, keeping metered billing distinct from a free request.
|
|
66
70
|
* Fixed prices span $0.000001 to $0.08, so a fixed
|
|
67
71
|
* number of decimals either rounds the cheap endpoints to $0.00 or pads the
|
|
68
|
-
* rest with noise
|
|
72
|
+
* rest with noise. Preserve the supplied amount without rounding its rate.
|
|
69
73
|
*/
|
|
70
74
|
export function formatPrice(pricing) {
|
|
71
75
|
if (pricing?.type === "metered")
|
|
72
76
|
return "usage-based";
|
|
77
|
+
if (pricing?.estimate_unavailable)
|
|
78
|
+
return "—";
|
|
73
79
|
const usd = pricing?.normal;
|
|
74
|
-
if (usd == null)
|
|
80
|
+
if (usd == null || !Number.isFinite(usd))
|
|
75
81
|
return "—";
|
|
76
82
|
if (usd === 0)
|
|
77
83
|
return "free";
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
const text = usd.toString();
|
|
85
|
+
if (!text.includes("e"))
|
|
86
|
+
return `$${text}`;
|
|
87
|
+
// Expand the supplied numeric text without rounding tiny nonzero rates.
|
|
88
|
+
const [coefficient, exponent] = text.split("e");
|
|
89
|
+
const negative = coefficient.startsWith("-");
|
|
90
|
+
const [whole, fraction = ""] = (negative ? coefficient.slice(1) : coefficient).split(".");
|
|
91
|
+
const digits = whole + fraction;
|
|
92
|
+
const point = whole.length + Number(exponent);
|
|
93
|
+
const expanded = point <= 0 ? `0.${"0".repeat(-point)}${digits}`
|
|
94
|
+
: point >= digits.length ? digits + "0".repeat(point - digits.length)
|
|
95
|
+
: digits.slice(0, point) + "." + digits.slice(point);
|
|
96
|
+
return `$${negative ? "-" : ""}${expanded}`;
|
|
82
97
|
}
|
package/dist/commands/api.js
CHANGED
|
@@ -15,9 +15,13 @@ function pricingDescription(pricing, from = false) {
|
|
|
15
15
|
const price = formatPrice(pricing);
|
|
16
16
|
if (pricing?.type === "metered")
|
|
17
17
|
return `${price} (cost varies)`;
|
|
18
|
-
if (pricing?.normal == null)
|
|
18
|
+
if (pricing?.estimate_unavailable || pricing?.normal == null)
|
|
19
19
|
return price;
|
|
20
|
-
|
|
20
|
+
const reference = `${from && pricing.normal > 0 ? "from " : ""}${price}`;
|
|
21
|
+
if (pricing.details?.customer_pricing === "fixed_success") {
|
|
22
|
+
return `${reference} per successful request`;
|
|
23
|
+
}
|
|
24
|
+
return `${reference} (reference; check schema and quote for charging conditions)`;
|
|
21
25
|
}
|
|
22
26
|
function statusBadge(status) {
|
|
23
27
|
switch (status) {
|
|
@@ -70,7 +74,7 @@ export async function apiListAction(options) {
|
|
|
70
74
|
p.id,
|
|
71
75
|
categoryOf(p.id),
|
|
72
76
|
String(p.endpoint_count),
|
|
73
|
-
p.pricing?.type !== "metered" && p.pricing?.normal != null && p.pricing.normal > 0
|
|
77
|
+
p.pricing?.type !== "metered" && !p.pricing?.estimate_unavailable && p.pricing?.normal != null && p.pricing.normal > 0
|
|
74
78
|
? `from ${formatPrice(p.pricing)}`
|
|
75
79
|
: formatPrice(p.pricing),
|
|
76
80
|
shortDate(p.updated_at),
|
|
@@ -3,7 +3,7 @@ import { INSTALLERS } from "./install.js";
|
|
|
3
3
|
import { defaultModelsFor } from "./llm-config.js";
|
|
4
4
|
import { stripped } from "./mcp.js";
|
|
5
5
|
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,
|
|
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
7
|
import { RED, RED_CTA, INK, PAPER, I, LOGO, CATEGORY_ICON, EXAMPLES, CLIENT_LOGOS, CODEX_FACE, CLAUDE_BOT, OPENCODE_MARK, } from "./connect-shared.js";
|
|
8
8
|
import { AISA_PROVIDER_ID } from "../constants.js";
|
|
9
9
|
import { VSCODE_MODELS } from "./vscode.js";
|
|
@@ -211,7 +211,7 @@ ${restChips}
|
|
|
211
211
|
<span class="cico">${CATEGORY_ICON[cat] ?? I.sparkles}</span>
|
|
212
212
|
<span class="cname">${cat}</span>
|
|
213
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">${
|
|
214
|
+
<span class="cblurb">${T(categoryBlurb(cat, list)).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")}</span>
|
|
215
215
|
<span class="csel" data-csel></span></button>`;
|
|
216
216
|
})
|
|
217
217
|
.join("");
|
package/dist/commands/connect.js
CHANGED
|
@@ -35,6 +35,7 @@ import { handOverResults, closeStaleResults, SUPERSEDE_GRACE_MS } from "./serve-
|
|
|
35
35
|
import { restoreTerminal, handOffStdin } from "./prompt.js";
|
|
36
36
|
import { printBanner } from "../utils/banner.js";
|
|
37
37
|
import { verifyKey } from "../utils/key-check.js";
|
|
38
|
+
import { clientConfigKey } from "../utils/client-key.js";
|
|
38
39
|
/**
|
|
39
40
|
* Page templates. T1 is the original two-page flow (selection + live
|
|
40
41
|
* progress, then a separate success page); T2 is the guided six-step flow.
|
|
@@ -62,12 +63,13 @@ export function resolveTemplate(flag) {
|
|
|
62
63
|
* sign in, exit. No daemon, no terminal takeover, no prompt or skill
|
|
63
64
|
* injection into the user's agent. The user stays in their own Claude Code.
|
|
64
65
|
* - Sign-in is one OAuth round for everything: with no key stored, the run
|
|
65
|
-
* starts with the same browser approval `aisa login` uses
|
|
66
|
-
* is
|
|
67
|
-
* the
|
|
68
|
-
* per-server authorization popups. Only if that
|
|
69
|
-
* back to each client's own OAuth machinery
|
|
70
|
-
* server; `codex mcp add` runs its own
|
|
66
|
+
* starts with the same browser approval `aisa login` uses, and the token it
|
|
67
|
+
* yields is traded for the durable "aisa cli" key (POST /v1/keys/mint).
|
|
68
|
+
* Every MCP entry is written with that key and the model provider gets
|
|
69
|
+
* the same key — zero per-server authorization popups. Only if that
|
|
70
|
+
* sign-in fails do we fall back to each client's own OAuth machinery
|
|
71
|
+
* (`claude mcp login <name>` per server; `codex mcp add` runs its own
|
|
72
|
+
* flow), which still works but costs
|
|
71
73
|
* one browser round per server. A key configured beforehand skips the
|
|
72
74
|
* sign-in entirely.
|
|
73
75
|
* - The page reports the whole journey live (GET /status polling), and a
|
|
@@ -716,6 +718,26 @@ async function runPlan(state, input, log) {
|
|
|
716
718
|
}
|
|
717
719
|
// Reached the far side of the sign-in, so whatever it was is over.
|
|
718
720
|
state.needsSignIn = false;
|
|
721
|
+
// Everything below writes into other programs' configs, which never refresh
|
|
722
|
+
// an OAuth token: trade it for the durable key first, or every entry dies
|
|
723
|
+
// with the token a day from now.
|
|
724
|
+
if (key && !input.dryRun) {
|
|
725
|
+
try {
|
|
726
|
+
key = await clientConfigKey(key);
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
failures++;
|
|
730
|
+
const why = e.message.split("\n")[0].trim();
|
|
731
|
+
setStep(state, "mcp", { state: "fail", detail: `${why} — nothing was written` });
|
|
732
|
+
log.line("fail", "Issue a key for your clients", e.message);
|
|
733
|
+
for (const step of state.steps) {
|
|
734
|
+
if (step.state === "pending") {
|
|
735
|
+
setStep(state, step.id, { state: "skip", detail: "waiting for a key" });
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return failures;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
719
741
|
// ── MCP entries ──
|
|
720
742
|
const willAuthorize = !key && input.clients[0] === "codex";
|
|
721
743
|
setStep(state, "mcp", {
|
|
@@ -1153,8 +1175,8 @@ function renderPage(servers, clients, token, keyed, canInstall) {
|
|
|
1153
1175
|
const authCopy = keyed
|
|
1154
1176
|
? `Your current AIsa credential is written into each entry — <b>no sign-in needed</b>.`
|
|
1155
1177
|
: `<b>One sign-in, nothing to paste.</b> Your browser opens the AIsa approval
|
|
1156
|
-
<b>once</b>;
|
|
1157
|
-
|
|
1178
|
+
<b>once</b>; it issues a long-lived key for this machine, and every server
|
|
1179
|
+
and model below is configured with it — no further popups.`;
|
|
1158
1180
|
const body = `
|
|
1159
1181
|
<div class="eyebrow">Connect</div>
|
|
1160
1182
|
<h1><em>AIsa MCP</em> — powerful real-world reach for your agent</h1>
|
|
@@ -2534,7 +2556,16 @@ export async function connectAction(options) {
|
|
|
2534
2556
|
res.writeHead(403).end();
|
|
2535
2557
|
return;
|
|
2536
2558
|
}
|
|
2537
|
-
|
|
2559
|
+
// Read now, not at startup: the run may have signed in since. And the
|
|
2560
|
+
// durable key, never the OAuth token — it is about to be pasted into
|
|
2561
|
+
// an app that will not refresh it.
|
|
2562
|
+
const current = await getAccessToken();
|
|
2563
|
+
let pasted = null;
|
|
2564
|
+
try {
|
|
2565
|
+
pasted = current ? await clientConfigKey(current) : null;
|
|
2566
|
+
}
|
|
2567
|
+
catch { /* no key to offer; the page says so */ }
|
|
2568
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ key: pasted }));
|
|
2538
2569
|
return;
|
|
2539
2570
|
}
|
|
2540
2571
|
if (req.method === "POST" && url.pathname === "/lang") {
|
package/dist/commands/flow.d.ts
CHANGED
|
@@ -307,6 +307,10 @@ export declare const STEP_CAPS: {
|
|
|
307
307
|
/** Shown for a file-configured client that has no note of its own. */
|
|
308
308
|
export declare const FILE_MODEL_FALLBACK: Text;
|
|
309
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;
|
|
310
314
|
export declare const STEP_INSTALL: {
|
|
311
315
|
h1: {
|
|
312
316
|
en: string;
|
package/dist/commands/flow.js
CHANGED
|
@@ -400,6 +400,13 @@ export const CATEGORY_BLURB = {
|
|
|
400
400
|
zh: "Similarweb 数据 —— 网站流量、互动情况,以及访客从哪来。",
|
|
401
401
|
},
|
|
402
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
|
+
}
|
|
403
410
|
// ── step 5: install ─────────────────────────────────────────────────────────
|
|
404
411
|
export const STEP_INSTALL = {
|
|
405
412
|
h1: { en: "Ready to connect", zh: "准备接线" },
|
|
@@ -412,8 +419,8 @@ export const STEP_INSTALL = {
|
|
|
412
419
|
zh: "你当前的 AIsa 凭证会写进每一条配置 —— <b>无需登录</b>。",
|
|
413
420
|
},
|
|
414
421
|
authFresh: {
|
|
415
|
-
en: "<b>One sign-in, nothing to paste.</b> Your browser opens the AIsa approval once; every server and model is configured with
|
|
416
|
-
zh: "<b>登录一次,不用粘贴任何东西。</b>浏览器会打开一次 AIsa
|
|
422
|
+
en: "<b>One sign-in, nothing to paste.</b> Your browser opens the AIsa approval once; it issues a long-lived key for this machine, and every server and model is configured with it — no further popups.",
|
|
423
|
+
zh: "<b>登录一次,不用粘贴任何东西。</b>浏览器会打开一次 AIsa 授权页;它会为这台机器签发一把长期有效的 key,所有 server 和模型都用它配置 —— 不会再有弹窗。",
|
|
417
424
|
},
|
|
418
425
|
};
|
|
419
426
|
// ── step 6: done ────────────────────────────────────────────────────────────
|
package/dist/commands/mcp.js
CHANGED
|
@@ -5,6 +5,7 @@ import { expandHome, ensureDir } from "../utils/file.js";
|
|
|
5
5
|
import { MCP_CONFIGS, MCP_MANIFEST_URL, MCP_CATALOG_URL, DOCS_MCP_URL, MCP_DEFAULT_SLUGS } from "../constants.js";
|
|
6
6
|
import { httpFetch, MAX_ATTEMPTS } from "../utils/http.js";
|
|
7
7
|
import { getAccessToken } from "../config.js";
|
|
8
|
+
import { clientConfigKey } from "../utils/client-key.js";
|
|
8
9
|
import { detectClients } from "./connect.js";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
export const stripped = (name) => name.replace(/^AIsa\s+/i, "");
|
|
@@ -153,8 +154,8 @@ export async function mcpSetupAction(options) {
|
|
|
153
154
|
? servers
|
|
154
155
|
: servers.filter((s) => MCP_DEFAULT_SLUGS.includes(s.slug));
|
|
155
156
|
const skipped = servers.length - chosen.length;
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
157
|
+
const credential = await getAccessToken();
|
|
158
|
+
if (credential) {
|
|
158
159
|
info("Using your configured API key (Bearer). It will be written into each client's config file.");
|
|
159
160
|
}
|
|
160
161
|
else {
|
|
@@ -176,6 +177,18 @@ export async function mcpSetupAction(options) {
|
|
|
176
177
|
hint("Nothing was written. Re-run with --yes to apply, or use `aisa connect` to choose interactively.");
|
|
177
178
|
return;
|
|
178
179
|
}
|
|
180
|
+
// A client config never refreshes an OAuth token, so it gets the durable
|
|
181
|
+
// key instead — otherwise every entry stops working a day from now.
|
|
182
|
+
let key;
|
|
183
|
+
try {
|
|
184
|
+
key = credential ? await clientConfigKey(credential) : undefined;
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
error(e.message);
|
|
188
|
+
hint("Nothing was written.");
|
|
189
|
+
process.exitCode = 1;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
179
192
|
for (const agent of targets) {
|
|
180
193
|
const config = MCP_CONFIGS[agent];
|
|
181
194
|
const result = writeClientConfig(agent, chosen, key);
|
package/dist/completion.js
CHANGED
|
@@ -7,7 +7,7 @@ export function isShell(value) {
|
|
|
7
7
|
}
|
|
8
8
|
// ── Dynamic candidates, all cache-only ───────────────────────────────────────
|
|
9
9
|
function cachedProviders() {
|
|
10
|
-
return readCache(`catalog/${cacheScope()}/category.json`)?.data.apis || [];
|
|
10
|
+
return readCache(`catalog-v2/${cacheScope()}/category.json`)?.data.apis || [];
|
|
11
11
|
}
|
|
12
12
|
function providerIds() {
|
|
13
13
|
return cachedProviders().map((p) => ({
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.6.
|
|
1
|
+
export declare const VERSION = "0.6.9";
|
|
2
2
|
/** Default for chat when no model is configured or supplied explicitly. */
|
|
3
3
|
export declare const DEFAULT_CHAT_MODEL = "deepseek-v4.1-flash";
|
|
4
4
|
/** Root of the platform. Per-surface bases are derived in api.ts#resolveBases. */
|
package/dist/constants.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const VERSION = "0.6.
|
|
1
|
+
export const VERSION = "0.6.9";
|
|
2
2
|
/** Default for chat when no model is configured or supplied explicitly. */
|
|
3
3
|
export const DEFAULT_CHAT_MODEL = "deepseek-v4.1-flash";
|
|
4
4
|
/** Root of the platform. Per-surface bases are derived in api.ts#resolveBases. */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function clientConfigKey(credential: string): Promise<string>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BASE_URL } from "../constants.js";
|
|
2
|
+
import { httpFetch } from "./http.js";
|
|
3
|
+
/**
|
|
4
|
+
* The credential a third-party client config gets.
|
|
5
|
+
*
|
|
6
|
+
* Since 0.6.0 browser login stores an OAuth session: this CLI refreshes the
|
|
7
|
+
* access token, but a token copied into Claude Code, Codex or Cursor stays
|
|
8
|
+
* the snapshot it was — and expires in 24 hours, taking every MCP entry and
|
|
9
|
+
* model provider with it the next day. Those configs need an API key, so an
|
|
10
|
+
* OAuth access token is traded for the durable "aisa cli" key at
|
|
11
|
+
* /v1/keys/mint. The server returns the same managed key on every call, so
|
|
12
|
+
* nothing piles up; the CLI's own session is left as it is.
|
|
13
|
+
*
|
|
14
|
+
* A static key (`login --key`, AISA_API_KEY) already is one and passes
|
|
15
|
+
* through untouched — the mint endpoint refuses API keys anyway.
|
|
16
|
+
*/
|
|
17
|
+
const MINT_URL = `${BASE_URL}/v1/keys/mint`;
|
|
18
|
+
export async function clientConfigKey(credential) {
|
|
19
|
+
if (credential.startsWith("sk-"))
|
|
20
|
+
return credential;
|
|
21
|
+
const res = await httpFetch(MINT_URL, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { Authorization: `Bearer ${credential}`, "Content-Type": "application/json" },
|
|
24
|
+
// Named explicitly; no body means the same thing to the server.
|
|
25
|
+
body: JSON.stringify({ client: "cli" }),
|
|
26
|
+
timeoutMs: 20_000,
|
|
27
|
+
});
|
|
28
|
+
if (res.status === 404) {
|
|
29
|
+
throw new Error("this deployment cannot issue CLI keys — copy one from https://console.aisa.one/api-keys and run: aisa login --key <key>");
|
|
30
|
+
}
|
|
31
|
+
const minted = (await res.json().catch(() => ({})));
|
|
32
|
+
if (!res.ok || !minted.key) {
|
|
33
|
+
throw new Error(`could not issue a key for your clients: ${minted.error ?? `HTTP ${res.status}`}`);
|
|
34
|
+
}
|
|
35
|
+
return minted.key;
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aisa-one/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "CLI for the AIsa unified AI infrastructure platform - one API key for 80+ LLMs and 900+ endpoints across finance, search, social, and video APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|