@aisa-one/cli 0.5.1 → 0.6.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 +41 -1
- package/README.md +33 -8
- package/dist/api.d.ts +2 -2
- package/dist/api.js +7 -6
- package/dist/commands/account.js +3 -3
- package/dist/commands/auth.d.ts +2 -2
- package/dist/commands/auth.js +14 -21
- package/dist/commands/chat.js +2 -2
- package/dist/commands/configCmd.js +2 -0
- package/dist/commands/connect.js +14 -14
- package/dist/commands/flow.js +4 -4
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/models.js +3 -3
- package/dist/commands/oauth-login.d.ts +4 -2
- package/dist/commands/oauth-login.js +29 -31
- package/dist/commands/signin-page.js +5 -6
- package/dist/commands/tools.js +6 -6
- package/dist/config.d.ts +20 -7
- package/dist/config.js +312 -49
- package/dist/constants.d.ts +4 -1
- package/dist/constants.js +4 -1
- package/dist/index.js +6 -4
- package/dist/router.d.ts +3 -3
- package/dist/router.js +6 -6
- package/dist/utils/auth-http.d.ts +3 -0
- package/dist/utils/auth-http.js +18 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] — 2026-09-10
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Browser login stores OAuth access/refresh tokens and the client ID instead
|
|
15
|
+
of minting a permanent API key. Refresh near expiry and retry once after 401.
|
|
16
|
+
- Serialize credential refresh, replacement, and logout across CLI processes.
|
|
17
|
+
Logout and replacement revoke the previous refresh token; preserve pending
|
|
18
|
+
grants for cleanup when login or persistence fails.
|
|
19
|
+
- Keep `AISA_API_KEY` and `login --key` static; migrate `~/.aisa/key` and retain
|
|
20
|
+
legacy credential mirrors. Credentials are stored atomically with mode 0600.
|
|
21
|
+
- Handle async credential errors consistently in logout, whoami, and MCP setup;
|
|
22
|
+
reject incomplete OAuth login responses before replacing existing credentials.
|
|
23
|
+
|
|
24
|
+
### Known limitations
|
|
25
|
+
|
|
26
|
+
- Already-issued access tokens remain valid until expiry after logout.
|
|
27
|
+
- Third-party client configurations receive a fixed access-token snapshot;
|
|
28
|
+
CLI refresh does not update those configurations.
|
|
29
|
+
|
|
30
|
+
## [0.5.2] — 2026-09-10
|
|
31
|
+
|
|
32
|
+
Compatible patch on published `0.5.1`. Expanded `aisa login --help` for Agent
|
|
33
|
+
sign-in handoff, plus a default-off login-help evaluation. No auth runtime,
|
|
34
|
+
credential-precedence, or command-surface changes.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- `aisa login --help` owns accessible-browser vs persistent headless TTY, live
|
|
39
|
+
URL relay, one-time paste into the same living process, reuse of a running
|
|
40
|
+
login, stale-session recovery, no-TTY native MCP OAuth, and connected-only
|
|
41
|
+
after a successful balance check.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Default-off `eval/agent-quickstart/login-handoff/`. Excluded from the npm
|
|
46
|
+
package; does not run in default CI or against production AIsa credentials.
|
|
47
|
+
|
|
10
48
|
## [0.5.1] — 2026-09-10
|
|
11
49
|
|
|
12
50
|
Compatible patch on published `0.5.0`. Browser-login-first onboarding
|
|
@@ -406,7 +444,9 @@ supports today; nothing here depends on a backend change.
|
|
|
406
444
|
- Config commands (`aisa config get|set|list|reset`) and auth
|
|
407
445
|
(`aisa login|logout|whoami`).
|
|
408
446
|
|
|
409
|
-
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.
|
|
447
|
+
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.6.0...HEAD
|
|
448
|
+
[0.6.0]: https://github.com/AIsa-team/cli/compare/v0.5.2...v0.6.0
|
|
449
|
+
[0.5.2]: https://github.com/AIsa-team/cli/compare/v0.5.1...v0.5.2
|
|
410
450
|
[0.5.1]: https://github.com/AIsa-team/cli/compare/v0.5.0...v0.5.1
|
|
411
451
|
[0.5.0]: https://github.com/AIsa-team/cli/compare/v0.3.0...v0.5.0
|
|
412
452
|
[0.4.0]: https://github.com/AIsa-team/cli/compare/v0.3.0...b5c0b04b2a7a2cb9efcb568be5ee5440d7f7d94d
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm install -g @aisa-one/cli
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
# Sign in (browser; stores
|
|
17
|
+
# Sign in (browser; stores OAuth tokens — no key to copy)
|
|
18
18
|
aisa login
|
|
19
19
|
|
|
20
20
|
# Discover published tools (Router; search/schema may be anonymous)
|
|
@@ -29,7 +29,7 @@ aisa api show financial
|
|
|
29
29
|
aisa quote --input '{"calls":[{"call_id":"c1","tool":"get_financial_company_facts","arguments":{"ticker":"AAPL"}}]}' --json
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
`aisa login` opens a browser, signs you in, and stores
|
|
32
|
+
`aisa login` opens a browser, signs you in, and stores OAuth tokens. You do not
|
|
33
33
|
need to create or paste a key from the console. For CI or scripts, set
|
|
34
34
|
`AISA_API_KEY` or run `aisa login --key <key>`. New accounts receive $5 in
|
|
35
35
|
free credits.
|
|
@@ -64,7 +64,7 @@ and `aisa manifest search` / `schema` / `quote` / `call` expose `mcp`, `auth`,
|
|
|
64
64
|
Recommended sequence: discover a tool → `aisa schema` when
|
|
65
65
|
`has_full_schema=false` → `aisa quote` → `aisa call`. Quote and call share
|
|
66
66
|
one request shape. **Enforced:** invalid local input exits 2 and is not sent;
|
|
67
|
-
quote and call refuse to run without
|
|
67
|
+
quote and call refuse to run without an OAuth session or static AIsa API key. **Not
|
|
68
68
|
enforced:** the CLI does not record quotes, approvals, or budget caps and
|
|
69
69
|
does not reject an unquoted call. **Instruction:** do not execute unquoted
|
|
70
70
|
calls; the caller must ensure a matching quote and approval. Quote is a
|
|
@@ -74,7 +74,7 @@ not spending approval. Do not invent tool names or guess required values.
|
|
|
74
74
|
cost is not a limit. If a hard monetary cap is required, do not execute
|
|
75
75
|
calls with no guaranteed maximum. A partial quote is not a full-batch total;
|
|
76
76
|
call only an independently approved successful subset, and do not silently
|
|
77
|
-
retry. Without
|
|
77
|
+
retry. Without an OAuth session or static AIsa API key, do not invent a business result.
|
|
78
78
|
|
|
79
79
|
`--input` is inline JSON (no file required). Documented shell examples use
|
|
80
80
|
POSIX single quotes so apostrophes, Unicode, `$()`, and backticks stay
|
|
@@ -104,9 +104,12 @@ HTTP error; `3` means the Router returned a batch with at least one failed
|
|
|
104
104
|
item.
|
|
105
105
|
|
|
106
106
|
`search` and `schema` may be anonymous. `quote` and `call` require a
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
stored OAuth session or static API key. `aisa login` stores access and refresh
|
|
108
|
+
tokens in `~/.aisa/tokens.json` (0600), along with the OAuth client ID and
|
|
109
|
+
`expiresAt` (Unix milliseconds). Access tokens refresh within 60 seconds of
|
|
110
|
+
expiry; a 401 triggers one refresh and retry. `AISA_API_KEY` takes precedence
|
|
111
|
+
and never refreshes. Legacy `~/.aisa/key` files migrate on first read; conf
|
|
112
|
+
`apiKey` is a write-only compatibility mirror.
|
|
110
113
|
For CI, set `AISA_API_KEY` or use `aisa login --key <key>`. The default Router
|
|
111
114
|
origin is `https://tools.aisa.one` (independent of `baseUrl` /
|
|
112
115
|
`https://api.aisa.one`). Point a test Router at `AISA_ROUTER_BASE_URL` (origin
|
|
@@ -323,7 +326,12 @@ Settings:
|
|
|
323
326
|
independent of `baseUrl`); overridden by `AISA_ROUTER_BASE_URL`
|
|
324
327
|
- `outputFormat` — `text` or `json`
|
|
325
328
|
|
|
326
|
-
`aisa login` stores
|
|
329
|
+
`aisa login` stores OAuth credentials in `~/.aisa/tokens.json`.
|
|
330
|
+
`aisa login --key <key>` stores a static credential without refresh metadata.
|
|
331
|
+
Legacy mirrors contain the current access token; older CLIs cannot refresh it.
|
|
332
|
+
Third-party client configurations written by `aisa connect` also contain a
|
|
333
|
+
snapshot of the credential, not a refresh-capable OAuth session.
|
|
334
|
+
Environment variables:
|
|
327
335
|
`AISA_API_KEY` takes precedence over the stored key.
|
|
328
336
|
`AISA_ROUTER_BASE_URL` is the Router origin/prefix before
|
|
329
337
|
`/v1/tool-router/...` and overrides the default `https://tools.aisa.one`.
|
|
@@ -367,3 +375,20 @@ catalog metadata, not an execution recipe.
|
|
|
367
375
|
## License
|
|
368
376
|
|
|
369
377
|
MIT. See [LICENSE](LICENSE). Copyright (c) 2026 AIsa Team.
|
|
378
|
+
|
|
379
|
+
`aisa logout` revokes the stored OAuth refresh token at Clerk before deleting
|
|
380
|
+
local credentials and compatibility mirrors. If revocation fails, it exits
|
|
381
|
+
with an error and retains the credentials so you can retry. Already-issued
|
|
382
|
+
JWT access tokens remain valid until expiry. Static API keys are only removed
|
|
383
|
+
locally; an `AISA_API_KEY` environment variable must be unset separately.
|
|
384
|
+
|
|
385
|
+
Credential reads and changes use a cross-process file lock with heartbeat and
|
|
386
|
+
stale-lock recovery. Re-running `aisa login` or switching to `--key` revokes
|
|
387
|
+
the previous stored OAuth grant before replacing it. Failed replacements
|
|
388
|
+
retain pending credentials in a private `.pending-tokens.json` recovery file;
|
|
389
|
+
the next login or logout cleans up those grants before completing.
|
|
390
|
+
|
|
391
|
+
If a login cannot acquire the credential lock, the newly issued grant is kept
|
|
392
|
+
in a private `.pending-login-*.json` file for the next login/logout to clean up.
|
|
393
|
+
Refresh persists the new credentials before optional rotation hints and legacy
|
|
394
|
+
mirrors; failure to save the primary token file is reported explicitly.
|
package/dist/api.d.ts
CHANGED
|
@@ -44,8 +44,8 @@ export interface RequestOptions {
|
|
|
44
44
|
/** Overrides the default 30s budget for a slow endpoint. */
|
|
45
45
|
timeoutMs?: number;
|
|
46
46
|
}
|
|
47
|
-
export declare function apiRequest<T = unknown>(
|
|
48
|
-
export declare function apiRequestRaw(
|
|
47
|
+
export declare function apiRequest<T = unknown>(accessToken: string, endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
48
|
+
export declare function apiRequestRaw(accessToken: string, endpoint: string, options?: RequestOptions): Promise<Response>;
|
|
49
49
|
/**
|
|
50
50
|
* Cache namespace for data served by the configured gateway. Catalog, model
|
|
51
51
|
* and coin-list caches must be keyed by the host they came from: a fixed key
|
package/dist/api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { authenticatedFetch } from "./utils/auth-http.js";
|
|
1
2
|
import { BASE_URL } from "./constants.js";
|
|
2
3
|
import { httpFetch, INFO_TIMEOUT_MS } from "./utils/http.js";
|
|
3
4
|
import { getConfig } from "./config.js";
|
|
@@ -41,7 +42,7 @@ export function parseCost(res) {
|
|
|
41
42
|
};
|
|
42
43
|
return Object.values(cost).some((v) => v !== undefined) ? cost : undefined;
|
|
43
44
|
}
|
|
44
|
-
export async function apiRequest(
|
|
45
|
+
export async function apiRequest(accessToken, endpoint, options = {}) {
|
|
45
46
|
const { method = "GET", query, body, headers: extraHeaders, domain } = options;
|
|
46
47
|
const bases = resolveBases();
|
|
47
48
|
const baseUrl = domain ? bases.domain : bases.llm;
|
|
@@ -51,12 +52,12 @@ export async function apiRequest(apiKey, endpoint, options = {}) {
|
|
|
51
52
|
url += `?${params.toString()}`;
|
|
52
53
|
}
|
|
53
54
|
const headers = {
|
|
54
|
-
Authorization: `Bearer ${
|
|
55
|
+
Authorization: `Bearer ${accessToken}`,
|
|
55
56
|
"Content-Type": "application/json",
|
|
56
57
|
"x-aisa-source": "cli",
|
|
57
58
|
...extraHeaders,
|
|
58
59
|
};
|
|
59
|
-
const res = await
|
|
60
|
+
const res = await authenticatedFetch(url, {
|
|
60
61
|
method,
|
|
61
62
|
headers,
|
|
62
63
|
body: body ? JSON.stringify(body) : undefined,
|
|
@@ -78,7 +79,7 @@ export async function apiRequest(apiKey, endpoint, options = {}) {
|
|
|
78
79
|
const data = (await res.json());
|
|
79
80
|
return { success: true, data, cost: parseCost(res) };
|
|
80
81
|
}
|
|
81
|
-
export async function apiRequestRaw(
|
|
82
|
+
export async function apiRequestRaw(accessToken, endpoint, options = {}) {
|
|
82
83
|
const { method = "GET", query, body, headers: extraHeaders, domain } = options;
|
|
83
84
|
const bases = resolveBases();
|
|
84
85
|
const baseUrl = domain ? bases.domain : bases.llm;
|
|
@@ -88,12 +89,12 @@ export async function apiRequestRaw(apiKey, endpoint, options = {}) {
|
|
|
88
89
|
url += `?${params.toString()}`;
|
|
89
90
|
}
|
|
90
91
|
const headers = {
|
|
91
|
-
Authorization: `Bearer ${
|
|
92
|
+
Authorization: `Bearer ${accessToken}`,
|
|
92
93
|
"Content-Type": "application/json",
|
|
93
94
|
"x-aisa-source": "cli",
|
|
94
95
|
...extraHeaders,
|
|
95
96
|
};
|
|
96
|
-
return
|
|
97
|
+
return authenticatedFetch(url, {
|
|
97
98
|
method,
|
|
98
99
|
headers,
|
|
99
100
|
body: body ? JSON.stringify(body) : undefined,
|
package/dist/commands/account.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { run } from "../utils/exec.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import {
|
|
3
|
+
import { requireAccessToken } from "../config.js";
|
|
4
4
|
import { apiRequest } from "../api.js";
|
|
5
5
|
import { formatJson, hint, info, error } from "../utils/display.js";
|
|
6
6
|
import { CONSOLE_BILLING_URL } from "../constants.js";
|
|
@@ -17,7 +17,7 @@ export function formatMicrosUSD(micros) {
|
|
|
17
17
|
return `${negative ? "-" : ""}$${dollars.toString()}.${cents.toString().padStart(2, "0")}`;
|
|
18
18
|
}
|
|
19
19
|
export async function balanceAction(options = {}) {
|
|
20
|
-
const key =
|
|
20
|
+
const key = await requireAccessToken();
|
|
21
21
|
const res = await apiRequest(key, "credits/balance");
|
|
22
22
|
if (!res.success || !res.data) {
|
|
23
23
|
throw new Error(res.error || "Failed to fetch balance");
|
|
@@ -79,7 +79,7 @@ export function topupAction(amount, options = {}) {
|
|
|
79
79
|
hint("Credit lands in your account as soon as the payment completes");
|
|
80
80
|
}
|
|
81
81
|
export async function usageAction(_options) {
|
|
82
|
-
|
|
82
|
+
await requireAccessToken();
|
|
83
83
|
// The gateway does not serve /v1/credits/usage yet — it 404s in production
|
|
84
84
|
// even though /v1/credits/balance on the same route group works.
|
|
85
85
|
console.log(chalk.yellow(" Usage API is not yet available on the gateway."));
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export declare function loginAction(options: {
|
|
|
2
2
|
key?: string;
|
|
3
3
|
browser?: boolean;
|
|
4
4
|
}): Promise<void>;
|
|
5
|
-
export declare function logoutAction(): void
|
|
6
|
-
export declare function whoamiAction(): void
|
|
5
|
+
export declare function logoutAction(): Promise<void>;
|
|
6
|
+
export declare function whoamiAction(): Promise<void>;
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import {
|
|
2
|
+
import { replaceTokens, revokeAndClearTokens, getAccessToken, getKeySource, maskKey, AUTH_SETUP_GUIDANCE } from "../config.js";
|
|
3
3
|
import { success, info } from "../utils/display.js";
|
|
4
4
|
import { CONSOLE_URL, ENV_VAR_NAME } from "../constants.js";
|
|
5
5
|
export async function loginAction(options) {
|
|
6
6
|
const key = options.key || process.env[ENV_VAR_NAME];
|
|
7
7
|
if (key) {
|
|
8
|
-
|
|
8
|
+
await replaceTokens(key);
|
|
9
9
|
success(`Authenticated: ${maskKey(key)}`);
|
|
10
10
|
await proveItWorks();
|
|
11
11
|
return;
|
|
@@ -20,18 +20,7 @@ export async function loginAction(options) {
|
|
|
20
20
|
await oauthLogin(options.browser === false ? { open: false } : {});
|
|
21
21
|
await proveItWorks();
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Use the key we just stored, and show what came back.
|
|
25
|
-
*
|
|
26
|
-
* "Signed in — key stored" says a file was written. It does not say the key
|
|
27
|
-
* works, and those are different claims: a pasted key can be the wrong one, a
|
|
28
|
-
* minted one can belong to an account with no credit. Ending on a balance
|
|
29
|
-
* turns the question "did that work?" into something already answered on
|
|
30
|
-
* screen, which is what someone finishing a sign-in actually wants to know.
|
|
31
|
-
*
|
|
32
|
-
* Failure here is not a failed sign-in — the key is stored either way — so it
|
|
33
|
-
* says what it could not do and points at the command to retry with.
|
|
34
|
-
*/
|
|
23
|
+
/** Show the balance after storing credentials; a balance failure does not undo login. */
|
|
35
24
|
async function proveItWorks() {
|
|
36
25
|
try {
|
|
37
26
|
const { balanceAction } = await import("./account.js");
|
|
@@ -46,18 +35,22 @@ async function proveItWorks() {
|
|
|
46
35
|
// cannot show. Written out so it can be remembered, not just clicked.
|
|
47
36
|
console.log(chalk.gray(` Account, usage and top-ups — ${chalk.cyan(CONSOLE_URL)}`));
|
|
48
37
|
}
|
|
49
|
-
export function logoutAction() {
|
|
50
|
-
|
|
51
|
-
success("Logged out.
|
|
38
|
+
export async function logoutAction() {
|
|
39
|
+
const revoked = await revokeAndClearTokens();
|
|
40
|
+
success(revoked ? "Logged out. OAuth refresh token revoked and stored tokens removed." : "Logged out. Stored tokens removed.");
|
|
41
|
+
if (revoked)
|
|
42
|
+
info("Already-issued access tokens remain valid until they expire.");
|
|
43
|
+
if (process.env[ENV_VAR_NAME])
|
|
44
|
+
info(`${ENV_VAR_NAME} is still set. Unset it to stop using that API key.`);
|
|
52
45
|
}
|
|
53
|
-
export function whoamiAction() {
|
|
54
|
-
const key =
|
|
46
|
+
export async function whoamiAction() {
|
|
47
|
+
const key = await getAccessToken();
|
|
55
48
|
const source = getKeySource();
|
|
56
49
|
if (!key) {
|
|
57
50
|
info("Not authenticated.");
|
|
58
51
|
console.log(chalk.gray(` ${AUTH_SETUP_GUIDANCE}`));
|
|
59
52
|
return;
|
|
60
53
|
}
|
|
61
|
-
console.log(`
|
|
62
|
-
console.log(` Source: ${source === "env" ? `${ENV_VAR_NAME} env var` : "
|
|
54
|
+
console.log(` Token: ${maskKey(key)}`);
|
|
55
|
+
console.log(` Source: ${source === "env" ? `${ENV_VAR_NAME} env var` : "~/.aisa/tokens.json"}`);
|
|
63
56
|
}
|
package/dist/commands/chat.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import ora from "ora";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import {
|
|
3
|
+
import { requireAccessToken, getConfig } from "../config.js";
|
|
4
4
|
import { apiRequest, apiRequestRaw } from "../api.js";
|
|
5
5
|
import { error } from "../utils/display.js";
|
|
6
6
|
import { handleSSEStream } from "../utils/streaming.js";
|
|
7
7
|
export async function chatAction(message, options) {
|
|
8
|
-
const key =
|
|
8
|
+
const key = await requireAccessToken();
|
|
9
9
|
// Read from stdin if no message provided
|
|
10
10
|
let text = message;
|
|
11
11
|
if (!text) {
|
package/dist/commands/connect.js
CHANGED
|
@@ -8,12 +8,12 @@ import chalk from "chalk";
|
|
|
8
8
|
import { error } from "../utils/display.js";
|
|
9
9
|
import { expandHome } from "../utils/file.js";
|
|
10
10
|
import { MCP_CONFIGS, MCP_DEFAULT_SLUGS, AISA_PROVIDER_ID } from "../constants.js";
|
|
11
|
-
import {
|
|
11
|
+
import { getAccessToken, getConfig, setConfig } from "../config.js";
|
|
12
12
|
import { fetchLiveServers, writeClientConfig, buildEntry, stripped } 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";
|
|
16
|
-
import {
|
|
16
|
+
import { signInAndStoreTokens } from "./oauth-login.js";
|
|
17
17
|
import { canOpenBrowser } from "../utils/browser.js";
|
|
18
18
|
import { vscodeDetected, vscodeUserDir, writeVSCodeLLM, writeVSCodeMCP, installVSCodeExtension, launchVSCode, VSCODE_MODELS } from "./vscode.js";
|
|
19
19
|
import { formatMicrosUSD } from "./account.js";
|
|
@@ -62,9 +62,9 @@ export function resolveTemplate(flag) {
|
|
|
62
62
|
* sign in, exit. No daemon, no terminal takeover, no prompt or skill
|
|
63
63
|
* injection into the user's agent. The user stays in their own Claude Code.
|
|
64
64
|
* - 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
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* starts with the same browser approval `aisa login` uses. Every MCP entry
|
|
66
|
+
* is written with the current access token and the model provider gets
|
|
67
|
+
* the same token — zero
|
|
68
68
|
* per-server authorization popups. Only if that sign-in fails do we fall
|
|
69
69
|
* back to each client's own OAuth machinery (`claude mcp login <name>` per
|
|
70
70
|
* server; `codex mcp add` runs its own flow), which still works but costs
|
|
@@ -408,7 +408,7 @@ const SIGNIN_CATCH_TIMEOUT_MS = 10 * 60 * 1000;
|
|
|
408
408
|
const CODEX_KEY_ENV_VAR = "AISA_API_KEY";
|
|
409
409
|
/** Below this the balance step lingers and nudges towards a top-up. */
|
|
410
410
|
const LOW_BALANCE_MICROS = 5_000_000;
|
|
411
|
-
/** The manual fallback when the inline sign-in cannot
|
|
411
|
+
/** The manual fallback when the inline sign-in cannot obtain an access token. */
|
|
412
412
|
const CONSOLE_KEYS_URL = "https://console.aisa.one/api-keys";
|
|
413
413
|
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
414
414
|
/** Mutate one step in place; the page picks it up on its next poll. */
|
|
@@ -450,7 +450,7 @@ function buildPlan(input) {
|
|
|
450
450
|
: "npm install -g @aisa-one/cli — the aisa command for balance, top-up and key rotation",
|
|
451
451
|
});
|
|
452
452
|
// One sign-in, before anything that wants a credential: the browser
|
|
453
|
-
// approval
|
|
453
|
+
// approval obtains the access token, and with it every MCP entry is a
|
|
454
454
|
// bearer and the model provider can be written — no per-server popups.
|
|
455
455
|
// If it fails at run time the per-server OAuth rounds come back as a
|
|
456
456
|
// fallback (added to the plan then, not promised now).
|
|
@@ -466,7 +466,7 @@ function buildPlan(input) {
|
|
|
466
466
|
// they had signed in before.
|
|
467
467
|
detail: input.keyRejected
|
|
468
468
|
? "your stored key is no longer accepted — one browser approval replaces it"
|
|
469
|
-
: "one browser approval — it
|
|
469
|
+
: "one browser approval — it stores your OAuth session",
|
|
470
470
|
});
|
|
471
471
|
}
|
|
472
472
|
const web = input.clients[0] === "claude-ai";
|
|
@@ -666,7 +666,7 @@ async function runPlan(state, input, log) {
|
|
|
666
666
|
ok("signin", "dry run — the browser approval would open here");
|
|
667
667
|
}
|
|
668
668
|
else {
|
|
669
|
-
key = await
|
|
669
|
+
key = await signInAndStoreTokens({ lang: input.lang, catcher: input.catcher });
|
|
670
670
|
// Two facts, in the order they matter to the person who just left
|
|
671
671
|
// the page to go and do this: it worked, and here is what it got
|
|
672
672
|
// them. The old line led with the artifact — "your CLI key is
|
|
@@ -810,7 +810,7 @@ async function runPlan(state, input, log) {
|
|
|
810
810
|
}
|
|
811
811
|
else if (!key) {
|
|
812
812
|
// Only reachable when the sign-in above failed (or was declined): the
|
|
813
|
-
// normal path
|
|
813
|
+
// normal path obtains an access token before this step runs. A provider entry has
|
|
814
814
|
// nowhere to put an OAuth token, so without a key the fallback is the
|
|
815
815
|
// console page that hands them out, with exact instructions.
|
|
816
816
|
setStep(state, "llm", {
|
|
@@ -1151,10 +1151,10 @@ function renderPage(servers, clients, token, keyed, canInstall) {
|
|
|
1151
1151
|
// Named per client so the page can say which model a pick actually gets.
|
|
1152
1152
|
const modelByClient = Object.fromEntries(clients.map((c) => [c.id, defaultModelsFor(c.id).model]));
|
|
1153
1153
|
const authCopy = keyed
|
|
1154
|
-
? `Your
|
|
1154
|
+
? `Your current AIsa credential is written into each entry — <b>no sign-in needed</b>.`
|
|
1155
1155
|
: `<b>One sign-in, nothing to paste.</b> Your browser opens the AIsa approval
|
|
1156
|
-
<b>once</b>;
|
|
1157
|
-
|
|
1156
|
+
<b>once</b>; every server and model below is configured with the current
|
|
1157
|
+
access token. Reconnect when that token expires.`;
|
|
1158
1158
|
const body = `
|
|
1159
1159
|
<div class="eyebrow">Connect</div>
|
|
1160
1160
|
<h1><em>AIsa MCP</em> — powerful real-world reach for your agent</h1>
|
|
@@ -2256,7 +2256,7 @@ export async function connectAction(options) {
|
|
|
2256
2256
|
process.exitCode = 1;
|
|
2257
2257
|
return;
|
|
2258
2258
|
}
|
|
2259
|
-
const key =
|
|
2259
|
+
const key = await getAccessToken();
|
|
2260
2260
|
// A key that exists is not a key that works. One revoked from the console,
|
|
2261
2261
|
// or belonging to a deleted account, sits in ~/.aisa/key looking exactly
|
|
2262
2262
|
// like a good one — and the run then skipped the sign-in, wrote the dead
|
package/dist/commands/flow.js
CHANGED
|
@@ -408,12 +408,12 @@ export const STEP_INSTALL = {
|
|
|
408
408
|
zh: "下面是将要发生的全部事情,按顺序列出。它会自动开始;每一步完成后都会汇报,结果会在最后一步打开。",
|
|
409
409
|
},
|
|
410
410
|
authKeyed: {
|
|
411
|
-
en: "Your
|
|
412
|
-
zh: "
|
|
411
|
+
en: "Your current AIsa credential is written into each entry — <b>no sign-in needed</b>.",
|
|
412
|
+
zh: "你当前的 AIsa 凭证会写进每一条配置 —— <b>无需登录</b>。",
|
|
413
413
|
},
|
|
414
414
|
authFresh: {
|
|
415
|
-
en: "<b>One sign-in, nothing to paste.</b> Your browser opens the AIsa approval once;
|
|
416
|
-
zh: "<b>登录一次,不用粘贴任何东西。</b>浏览器会打开一次 AIsa
|
|
415
|
+
en: "<b>One sign-in, nothing to paste.</b> Your browser opens the AIsa approval once; every server and model is configured with the current access token. Reconnect when that token expires.",
|
|
416
|
+
zh: "<b>登录一次,不用粘贴任何东西。</b>浏览器会打开一次 AIsa 授权页;所有 server 和模型都使用当前 access token 配置。token 过期后请重新连接。",
|
|
417
417
|
},
|
|
418
418
|
};
|
|
419
419
|
// ── step 6: done ────────────────────────────────────────────────────────────
|
package/dist/commands/mcp.js
CHANGED
|
@@ -4,7 +4,7 @@ import { success, error, info, hint } from "../utils/display.js";
|
|
|
4
4
|
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
|
-
import {
|
|
7
|
+
import { getAccessToken } from "../config.js";
|
|
8
8
|
import { detectClients } from "./connect.js";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
export const stripped = (name) => name.replace(/^AIsa\s+/i, "");
|
|
@@ -153,7 +153,7 @@ export async function mcpSetupAction(options) {
|
|
|
153
153
|
? servers
|
|
154
154
|
: servers.filter((s) => MCP_DEFAULT_SLUGS.includes(s.slug));
|
|
155
155
|
const skipped = servers.length - chosen.length;
|
|
156
|
-
const key =
|
|
156
|
+
const key = await getAccessToken();
|
|
157
157
|
if (key) {
|
|
158
158
|
info("Using your configured API key (Bearer). It will be written into each client's config file.");
|
|
159
159
|
}
|
package/dist/commands/models.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ora from "ora";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import {
|
|
3
|
+
import { requireAccessToken } from "../config.js";
|
|
4
4
|
import { apiRequest } from "../api.js";
|
|
5
5
|
import { error, badge } from "../utils/display.js";
|
|
6
6
|
import { writeCache } from "../cache.js";
|
|
@@ -8,7 +8,7 @@ import { cacheScope } from "../api.js";
|
|
|
8
8
|
const modelsCacheKey = () => `models/${cacheScope()}.json`;
|
|
9
9
|
const MODELS_TTL_MS = 24 * 60 * 60 * 1000;
|
|
10
10
|
export async function modelsListAction(options) {
|
|
11
|
-
const key =
|
|
11
|
+
const key = await requireAccessToken();
|
|
12
12
|
const spinner = ora("Fetching models...").start();
|
|
13
13
|
const res = await apiRequest(key, "models");
|
|
14
14
|
if (!res.success || !res.data) {
|
|
@@ -47,7 +47,7 @@ export async function modelsListAction(options) {
|
|
|
47
47
|
console.log(chalk.gray(" Run 'aisa models show <model-id>' for details"));
|
|
48
48
|
}
|
|
49
49
|
export async function modelsShowAction(modelId) {
|
|
50
|
-
const key =
|
|
50
|
+
const key = await requireAccessToken();
|
|
51
51
|
const spinner = ora(`Loading ${modelId}...`).start();
|
|
52
52
|
const res = await apiRequest(key, `models/${modelId}`);
|
|
53
53
|
if (!res.success || !res.data) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Lang } from "./flow.js";
|
|
2
2
|
/**
|
|
3
3
|
* The whole sign-in, as a function other commands can embed: browser (or
|
|
4
|
-
* paste-back) OAuth
|
|
4
|
+
* paste-back) OAuth. Stores tokens and returns the access token; throws on
|
|
5
5
|
* any failure. `aisa login` wraps this with CLI messaging; `aisa connect`
|
|
6
6
|
* runs it as its "Sign in to AIsa" step.
|
|
7
7
|
*/
|
|
@@ -22,11 +22,13 @@ export interface OAuthCatcher {
|
|
|
22
22
|
/** Resolves with the authorization code, or rejects with why it will not. */
|
|
23
23
|
wait(expectedState: string): Promise<string>;
|
|
24
24
|
}
|
|
25
|
-
export declare function
|
|
25
|
+
export declare function signInAndStoreTokens(options?: {
|
|
26
26
|
open?: boolean;
|
|
27
27
|
lang?: Lang;
|
|
28
28
|
catcher?: OAuthCatcher;
|
|
29
29
|
}): Promise<string>;
|
|
30
|
+
/** Commander after-help for `aisa login`. Mechanics only; runtime is unchanged. */
|
|
31
|
+
export declare function loginHelpAfter(): string;
|
|
30
32
|
export declare function oauthLogin(options?: {
|
|
31
33
|
open?: boolean;
|
|
32
34
|
lang?: Lang;
|