@aisa-one/cli 0.6.4 → 0.6.6
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 +29 -0
- package/README.md +12 -7
- package/dist/commands/account.d.ts +76 -2
- package/dist/commands/account.js +163 -10
- package/dist/commands/connect.js +1 -5
- package/dist/commands/oauth-login.js +1 -6
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/index.js +6 -4
- package/dist/utils/browser.d.ts +26 -0
- package/dist/utils/browser.js +34 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.6] — 2026-09-30
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Open the browser on Windows. `start` is built into cmd.exe, so running it
|
|
15
|
+
directly failed with ENOENT and the error was swallowed: `aisa login`,
|
|
16
|
+
`aisa connect` and `aisa topup` never opened a page, and sign-in waited
|
|
17
|
+
for an approval that could not arrive. The browser now opens through
|
|
18
|
+
cmd.exe, with `&` and other special characters escaped so the whole URL
|
|
19
|
+
gets through.
|
|
20
|
+
|
|
21
|
+
## [0.6.5] — 2026-09-30
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Implement `aisa usage` against the live `GET /v1/usage` endpoint: one row per
|
|
26
|
+
day with requests, failures, input and output tokens, and charged USD, plus a
|
|
27
|
+
total. `--days` (1–31, default 7), `--scope key|account`, `--limit N`, and
|
|
28
|
+
`--json`.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Reject `aisa topup <amount>` below the console's $10 minimum before opening
|
|
33
|
+
the browser (exit 1, no URL), and mention the minimum in `topup` output.
|
|
34
|
+
- Send `start_time` and `end_time` as a coherent pair so `--days 31` cannot race
|
|
35
|
+
the gateway clock past its 31-day cap.
|
|
36
|
+
- Label usage buckets with UTC calendar days; the gateway buckets at UTC
|
|
37
|
+
boundaries, so local-timezone labels were off by one day in UTC-negative zones.
|
|
38
|
+
|
|
10
39
|
## [0.6.4] — 2026-09-30
|
|
11
40
|
|
|
12
41
|
### Fixed
|
package/README.md
CHANGED
|
@@ -200,18 +200,23 @@ Streaming is on by default; pass `--no-stream` to disable it.
|
|
|
200
200
|
```bash
|
|
201
201
|
aisa balance # wallet and API key credit balance
|
|
202
202
|
aisa balance --json
|
|
203
|
+
aisa usage # last 7 days for this API key
|
|
204
|
+
aisa usage --days 31 --scope account
|
|
205
|
+
aisa usage --limit 3 # table shows only the latest 3 days
|
|
206
|
+
aisa usage --json
|
|
203
207
|
aisa topup # open the console billing page to add credit
|
|
204
|
-
aisa topup 20 # same, deep-linked to $20
|
|
208
|
+
aisa topup 20 # same, deep-linked to $20 (minimum $10)
|
|
205
209
|
```
|
|
206
210
|
|
|
211
|
+
`aisa usage` calls `GET /v1/usage` and prints one row per day — requests,
|
|
212
|
+
failed requests, input and output tokens, and charged USD — plus a total.
|
|
213
|
+
`--days` accepts 1–31 and defaults to 7. `--scope key` (the default) is the
|
|
214
|
+
current credential; `--scope account` sums the whole account. `--json` prints
|
|
215
|
+
the gateway response. `--limit` shortens the table only.
|
|
216
|
+
|
|
207
217
|
Payment always finishes in the browser: card details belong to Stripe's hosted
|
|
208
218
|
page, not to us, and a bank's 3-D Secure step needs one. `topup` opens the
|
|
209
|
-
right page; `--no-open` prints the URL instead.
|
|
210
|
-
|
|
211
|
-
`aisa usage` is not available yet — the gateway does not serve
|
|
212
|
-
`GET /v1/credits/usage` (it 404s, while `/v1/credits/balance` on the same route
|
|
213
|
-
group works). Use the [console](https://console.aisa.one/logs) for usage history
|
|
214
|
-
in the meantime.
|
|
219
|
+
right page; `--no-open` prints the URL instead. The minimum top-up is $10.
|
|
215
220
|
|
|
216
221
|
## Skills
|
|
217
222
|
|
|
@@ -2,6 +2,15 @@ export declare function formatMicrosUSD(micros: number | string | bigint): strin
|
|
|
2
2
|
export declare function balanceAction(options?: {
|
|
3
3
|
json?: boolean;
|
|
4
4
|
}): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Minimum top-up in US dollars.
|
|
7
|
+
*
|
|
8
|
+
* Source of truth: the console constant BILLING_MIN_TOPUP_MICROS_USD
|
|
9
|
+
* (production is $10). `/api/billing/config` needs a console session, which
|
|
10
|
+
* this CLI's OAuth token cannot obtain, so the floor is fixed here instead
|
|
11
|
+
* of being read at runtime.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MIN_TOPUP_USD = 10;
|
|
5
14
|
/**
|
|
6
15
|
* `aisa topup [amount]` — open the console's billing page to add credit.
|
|
7
16
|
*
|
|
@@ -19,7 +28,72 @@ export declare function balanceAction(options?: {
|
|
|
19
28
|
export declare function topupAction(amount: string | undefined, options?: {
|
|
20
29
|
open?: boolean;
|
|
21
30
|
}): void;
|
|
22
|
-
|
|
31
|
+
/** Gateway caps GET /v1/usage at 31 days; buckets are fixed at one day. */
|
|
32
|
+
export declare const USAGE_MAX_WINDOW_DAYS = 31;
|
|
33
|
+
export declare const USAGE_DEFAULT_DAYS = 7;
|
|
34
|
+
export interface UsageMetrics {
|
|
35
|
+
requests: number;
|
|
36
|
+
failed_requests: number;
|
|
37
|
+
input_tokens: number;
|
|
38
|
+
output_tokens: number;
|
|
39
|
+
cache_read_tokens: number;
|
|
40
|
+
cache_write_tokens: number;
|
|
41
|
+
usage_value_micros_usd: number;
|
|
42
|
+
charged_micros_usd: number;
|
|
43
|
+
}
|
|
44
|
+
export interface UsageBucket extends UsageMetrics {
|
|
45
|
+
start_time: number;
|
|
46
|
+
end_time: number;
|
|
47
|
+
}
|
|
48
|
+
export interface UsageResponse {
|
|
49
|
+
scope: string;
|
|
50
|
+
currency: string;
|
|
51
|
+
start_time: number;
|
|
52
|
+
end_time: number;
|
|
53
|
+
bucket_width: string;
|
|
54
|
+
totals: UsageMetrics;
|
|
55
|
+
buckets: UsageBucket[];
|
|
56
|
+
}
|
|
57
|
+
export interface UsageQuery {
|
|
58
|
+
start_time: string;
|
|
59
|
+
/** Sent alongside start_time so the window is exactly --days wide even if
|
|
60
|
+
* the gateway's clock runs ahead of ours; a server-filled end_time could
|
|
61
|
+
* push a 31-day request past the gateway's own cap. */
|
|
62
|
+
end_time: string;
|
|
63
|
+
scope: "key" | "account";
|
|
64
|
+
}
|
|
65
|
+
export interface ParsedUsageOptions {
|
|
66
|
+
query: UsageQuery;
|
|
67
|
+
/** Omitted means the table shows every daily bucket in the window. */
|
|
68
|
+
limit?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface UsageActionOptions {
|
|
71
|
+
days?: string;
|
|
72
|
+
scope?: string;
|
|
23
73
|
limit?: string;
|
|
74
|
+
json?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* `aisa usage` reads GET /v1/usage.
|
|
78
|
+
*
|
|
79
|
+
* start_time and end_time are both sent as a coherent pair so the window is
|
|
80
|
+
* exactly --days wide even when the gateway's clock runs ahead of ours. The
|
|
81
|
+
* window cannot exceed 31 days.
|
|
82
|
+
* scope=key is this credential; scope=account sums the whole account, which
|
|
83
|
+
* an OAuth token is allowed to request. --limit trims the table to the latest
|
|
84
|
+
* N days. --json prints the gateway body unchanged.
|
|
85
|
+
*/
|
|
86
|
+
export declare function parseUsageOptions(options: {
|
|
24
87
|
days?: string;
|
|
25
|
-
|
|
88
|
+
scope?: string;
|
|
89
|
+
limit?: string;
|
|
90
|
+
}, nowSeconds: number): ParsedUsageOptions;
|
|
91
|
+
/** UTC calendar day (YYYY-MM-DD) for a bucket's start_time. The gateway
|
|
92
|
+
* buckets at UTC day boundaries, so a local-timezone label would show the
|
|
93
|
+
* previous calendar day for every UTC-midnight bucket in UTC-negative zones. */
|
|
94
|
+
export declare function formatUsageDate(unixSeconds: number): string;
|
|
95
|
+
/** Chronological order, then the latest `limit` buckets. A missing limit keeps
|
|
96
|
+
* the whole window. */
|
|
97
|
+
export declare function selectRecentBuckets(buckets: readonly UsageBucket[], limit?: number): UsageBucket[];
|
|
98
|
+
export declare function renderUsageReport(buckets: readonly UsageBucket[]): string;
|
|
99
|
+
export declare function usageAction(options?: UsageActionOptions): Promise<void>;
|
package/dist/commands/account.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { openBrowser } from "../utils/browser.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { requireAccessToken } from "../config.js";
|
|
4
4
|
import { apiRequest } from "../api.js";
|
|
5
|
-
import {
|
|
5
|
+
import { usageError } from "../cli-error.js";
|
|
6
|
+
import { formatJson, hint, info, error, table } from "../utils/display.js";
|
|
6
7
|
import { CONSOLE_BILLING_URL } from "../constants.js";
|
|
7
8
|
export function formatMicrosUSD(micros) {
|
|
8
9
|
const value = typeof micros === "bigint" ? micros : BigInt(micros);
|
|
@@ -42,6 +43,15 @@ export async function balanceAction(options = {}) {
|
|
|
42
43
|
hint("Out of credit — run 'aisa topup' to add some");
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Minimum top-up in US dollars.
|
|
48
|
+
*
|
|
49
|
+
* Source of truth: the console constant BILLING_MIN_TOPUP_MICROS_USD
|
|
50
|
+
* (production is $10). `/api/billing/config` needs a console session, which
|
|
51
|
+
* this CLI's OAuth token cannot obtain, so the floor is fixed here instead
|
|
52
|
+
* of being read at runtime.
|
|
53
|
+
*/
|
|
54
|
+
export const MIN_TOPUP_USD = 10;
|
|
45
55
|
/**
|
|
46
56
|
* `aisa topup [amount]` — open the console's billing page to add credit.
|
|
47
57
|
*
|
|
@@ -66,23 +76,166 @@ export function topupAction(amount, options = {}) {
|
|
|
66
76
|
process.exitCode = 1;
|
|
67
77
|
return;
|
|
68
78
|
}
|
|
79
|
+
if (value < MIN_TOPUP_USD) {
|
|
80
|
+
error(`Minimum top-up is $${MIN_TOPUP_USD} (got ${amount}).`);
|
|
81
|
+
hint(`The minimum top-up is $${MIN_TOPUP_USD}, e.g. 'aisa topup ${MIN_TOPUP_USD}'.`);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
69
85
|
url = `${CONSOLE_BILLING_URL}?amount=${value}`;
|
|
70
86
|
info(`Opening the billing page to add ${formatMicrosUSD(BigInt(Math.round(value * 1_000_000)))}`);
|
|
71
87
|
}
|
|
72
88
|
else {
|
|
73
89
|
info("Opening the billing page — choose an amount there");
|
|
90
|
+
hint(`The minimum top-up is $${MIN_TOPUP_USD}`);
|
|
74
91
|
}
|
|
75
92
|
console.log(` ${chalk.cyan(url)}`);
|
|
76
93
|
if (options.open === false)
|
|
77
94
|
return;
|
|
78
|
-
|
|
79
|
-
void run(cmd, [url], { timeout: 30_000 }).catch(() => { });
|
|
95
|
+
openBrowser(url);
|
|
80
96
|
hint("Credit lands in your account as soon as the payment completes");
|
|
81
97
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
/** Gateway caps GET /v1/usage at 31 days; buckets are fixed at one day. */
|
|
99
|
+
export const USAGE_MAX_WINDOW_DAYS = 31;
|
|
100
|
+
export const USAGE_DEFAULT_DAYS = 7;
|
|
101
|
+
const USAGE_DAY_SECONDS = 86_400;
|
|
102
|
+
const USAGE_WINDOW_ERROR = "The usage window is at most 31 days. Pass --days as an integer from 1 to 31.";
|
|
103
|
+
/**
|
|
104
|
+
* `aisa usage` reads GET /v1/usage.
|
|
105
|
+
*
|
|
106
|
+
* start_time and end_time are both sent as a coherent pair so the window is
|
|
107
|
+
* exactly --days wide even when the gateway's clock runs ahead of ours. The
|
|
108
|
+
* window cannot exceed 31 days.
|
|
109
|
+
* scope=key is this credential; scope=account sums the whole account, which
|
|
110
|
+
* an OAuth token is allowed to request. --limit trims the table to the latest
|
|
111
|
+
* N days. --json prints the gateway body unchanged.
|
|
112
|
+
*/
|
|
113
|
+
export function parseUsageOptions(options, nowSeconds) {
|
|
114
|
+
const days = parseUsageDays(options.days);
|
|
115
|
+
const scope = parseUsageScope(options.scope);
|
|
116
|
+
const limit = parseUsageLimit(options.limit);
|
|
117
|
+
if (!Number.isFinite(nowSeconds)) {
|
|
118
|
+
throw usageError("Usage start_time must be a positive Unix timestamp in seconds.");
|
|
119
|
+
}
|
|
120
|
+
const end = Math.floor(nowSeconds);
|
|
121
|
+
const start = end - days * USAGE_DAY_SECONDS;
|
|
122
|
+
if (start <= 0) {
|
|
123
|
+
throw usageError("Usage start_time must be a positive Unix timestamp in seconds.");
|
|
124
|
+
}
|
|
125
|
+
return limit === undefined
|
|
126
|
+
? { query: { start_time: String(start), end_time: String(end), scope } }
|
|
127
|
+
: { query: { start_time: String(start), end_time: String(end), scope }, limit };
|
|
128
|
+
}
|
|
129
|
+
function parseUsageDays(raw) {
|
|
130
|
+
const text = raw === undefined || raw.trim() === "" ? String(USAGE_DEFAULT_DAYS) : raw.trim();
|
|
131
|
+
if (!/^\d+$/.test(text))
|
|
132
|
+
throw usageError(USAGE_WINDOW_ERROR);
|
|
133
|
+
const days = Number(text);
|
|
134
|
+
if (!Number.isSafeInteger(days) || days < 1 || days > USAGE_MAX_WINDOW_DAYS) {
|
|
135
|
+
throw usageError(USAGE_WINDOW_ERROR);
|
|
136
|
+
}
|
|
137
|
+
return days;
|
|
138
|
+
}
|
|
139
|
+
function parseUsageScope(raw) {
|
|
140
|
+
const text = raw === undefined || raw.trim() === "" ? "key" : raw.trim();
|
|
141
|
+
if (text !== "key" && text !== "account") {
|
|
142
|
+
throw usageError('Pass --scope as "key" or "account".');
|
|
143
|
+
}
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
function parseUsageLimit(raw) {
|
|
147
|
+
if (raw === undefined || raw.trim() === "")
|
|
148
|
+
return undefined;
|
|
149
|
+
if (!/^\d+$/.test(raw.trim()))
|
|
150
|
+
throw usageError("--limit must be a positive integer.");
|
|
151
|
+
const limit = Number(raw.trim());
|
|
152
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
153
|
+
throw usageError("--limit must be a positive integer.");
|
|
154
|
+
}
|
|
155
|
+
return limit;
|
|
156
|
+
}
|
|
157
|
+
/** UTC calendar day (YYYY-MM-DD) for a bucket's start_time. The gateway
|
|
158
|
+
* buckets at UTC day boundaries, so a local-timezone label would show the
|
|
159
|
+
* previous calendar day for every UTC-midnight bucket in UTC-negative zones. */
|
|
160
|
+
export function formatUsageDate(unixSeconds) {
|
|
161
|
+
if (!Number.isFinite(unixSeconds))
|
|
162
|
+
return "";
|
|
163
|
+
const date = new Date(unixSeconds * 1000);
|
|
164
|
+
if (Number.isNaN(date.getTime()))
|
|
165
|
+
return "";
|
|
166
|
+
return date.toISOString().slice(0, 10);
|
|
167
|
+
}
|
|
168
|
+
/** Chronological order, then the latest `limit` buckets. A missing limit keeps
|
|
169
|
+
* the whole window. */
|
|
170
|
+
export function selectRecentBuckets(buckets, limit) {
|
|
171
|
+
const sorted = [...buckets].sort((a, b) => Number(a.start_time) - Number(b.start_time));
|
|
172
|
+
if (limit === undefined)
|
|
173
|
+
return sorted;
|
|
174
|
+
return sorted.slice(-limit);
|
|
175
|
+
}
|
|
176
|
+
export function renderUsageReport(buckets) {
|
|
177
|
+
if (buckets.length === 0)
|
|
178
|
+
return " No usage in this window.";
|
|
179
|
+
const headers = ["Date", "Requests", "Failed", "Input tokens", "Output tokens", "Charged"];
|
|
180
|
+
const rows = buckets.map((bucket) => [
|
|
181
|
+
formatUsageDate(Number(bucket.start_time)),
|
|
182
|
+
formatCount(bucket.requests),
|
|
183
|
+
formatCount(bucket.failed_requests),
|
|
184
|
+
formatCount(bucket.input_tokens),
|
|
185
|
+
formatCount(bucket.output_tokens),
|
|
186
|
+
formatMicrosUSD(asMicros(bucket.charged_micros_usd)),
|
|
187
|
+
]);
|
|
188
|
+
rows.push([
|
|
189
|
+
"Total",
|
|
190
|
+
formatCount(sumCount(buckets, "requests")),
|
|
191
|
+
formatCount(sumCount(buckets, "failed_requests")),
|
|
192
|
+
formatCount(sumCount(buckets, "input_tokens")),
|
|
193
|
+
formatCount(sumCount(buckets, "output_tokens")),
|
|
194
|
+
formatMicrosUSD(sumMicros(buckets)),
|
|
195
|
+
]);
|
|
196
|
+
return table(headers, rows);
|
|
197
|
+
}
|
|
198
|
+
function formatCount(value) {
|
|
199
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
200
|
+
if (!Number.isFinite(n))
|
|
201
|
+
return "0";
|
|
202
|
+
return String(Math.trunc(n));
|
|
203
|
+
}
|
|
204
|
+
function sumCount(buckets, key) {
|
|
205
|
+
return buckets.reduce((sum, bucket) => {
|
|
206
|
+
const n = Number(bucket[key]);
|
|
207
|
+
return sum + (Number.isFinite(n) ? Math.trunc(n) : 0);
|
|
208
|
+
}, 0);
|
|
209
|
+
}
|
|
210
|
+
function asMicros(value) {
|
|
211
|
+
if (typeof value === "bigint")
|
|
212
|
+
return value;
|
|
213
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
214
|
+
return BigInt(Math.trunc(value));
|
|
215
|
+
if (typeof value === "string" && /^-?\d+$/.test(value.trim()))
|
|
216
|
+
return BigInt(value.trim());
|
|
217
|
+
return 0n;
|
|
218
|
+
}
|
|
219
|
+
function sumMicros(buckets) {
|
|
220
|
+
return buckets.reduce((sum, bucket) => sum + asMicros(bucket.charged_micros_usd), 0n);
|
|
221
|
+
}
|
|
222
|
+
export async function usageAction(options = {}) {
|
|
223
|
+
const parsed = parseUsageOptions(options, Math.floor(Date.now() / 1000));
|
|
224
|
+
const token = await requireAccessToken();
|
|
225
|
+
const res = await apiRequest(token, "usage", {
|
|
226
|
+
query: {
|
|
227
|
+
start_time: parsed.query.start_time,
|
|
228
|
+
end_time: parsed.query.end_time,
|
|
229
|
+
scope: parsed.query.scope,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
if (!res.success || !res.data) {
|
|
233
|
+
throw new Error(res.error || "Failed to fetch usage");
|
|
234
|
+
}
|
|
235
|
+
if (options.json) {
|
|
236
|
+
console.log(formatJson(res.data));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const buckets = Array.isArray(res.data.buckets) ? res.data.buckets : [];
|
|
240
|
+
console.log(renderUsageReport(selectRecentBuckets(buckets, parsed.limit)));
|
|
88
241
|
}
|
package/dist/commands/connect.js
CHANGED
|
@@ -14,7 +14,7 @@ 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
16
|
import { signInAndStoreTokens } from "./oauth-login.js";
|
|
17
|
-
import { canOpenBrowser } from "../utils/browser.js";
|
|
17
|
+
import { canOpenBrowser, openBrowser } 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";
|
|
20
20
|
import { apiRequest } from "../api.js";
|
|
@@ -1812,10 +1812,6 @@ function launchCursor(dir) {
|
|
|
1812
1812
|
function withBrowserEarly() {
|
|
1813
1813
|
return canOpenBrowser();
|
|
1814
1814
|
}
|
|
1815
|
-
function openBrowser(url) {
|
|
1816
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1817
|
-
void run(cmd, [url], { timeout: 30_000 }).catch(() => { });
|
|
1818
|
-
}
|
|
1819
1815
|
/**
|
|
1820
1816
|
* Where a live run leaves its address, so a second `aisa connect` can offer
|
|
1821
1817
|
* the first one's page instead of starting a rival server. Ephemeral by
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { run } from "../utils/exec.js";
|
|
3
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
3
|
import { createServer } from "node:http";
|
|
5
4
|
import { createInterface } from "node:readline/promises";
|
|
@@ -7,7 +6,7 @@ import { error, hint, info, success } from "../utils/display.js";
|
|
|
7
6
|
import { replaceTokens, tokenExpiresAt } from "../config.js";
|
|
8
7
|
import { maskKey } from "../config.js";
|
|
9
8
|
import { httpFetch } from "../utils/http.js";
|
|
10
|
-
import { canOpenBrowser } from "../utils/browser.js";
|
|
9
|
+
import { canOpenBrowser, openBrowser } from "../utils/browser.js";
|
|
11
10
|
import { SIGNIN, t } from "./flow.js";
|
|
12
11
|
import { renderSignInPage } from "./signin-page.js";
|
|
13
12
|
import { handOverSignInPage, SIGNIN_PAGE_TTL_MS } from "./serve-signin.js";
|
|
@@ -220,10 +219,6 @@ async function waitForPaste(expectedState, lang) {
|
|
|
220
219
|
/* c8 ignore next */
|
|
221
220
|
throw new Error(t(SIGNIN.badPaste, lang));
|
|
222
221
|
}
|
|
223
|
-
function openBrowser(url) {
|
|
224
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
225
|
-
void run(cmd, [url], { timeout: 30_000 }).catch(() => { });
|
|
226
|
-
}
|
|
227
222
|
export async function signInAndStoreTokens(options = {}) {
|
|
228
223
|
const lang = options.lang ?? "en";
|
|
229
224
|
// Unset means "work it out": a server has no browser to open and waiting
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.6.
|
|
1
|
+
export declare const VERSION = "0.6.6";
|
|
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.6";
|
|
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. */
|
package/dist/index.js
CHANGED
|
@@ -90,7 +90,7 @@ program
|
|
|
90
90
|
// ── Account ──
|
|
91
91
|
program
|
|
92
92
|
.command("topup [amount]")
|
|
93
|
-
.description("Add credit — opens the console billing page (
|
|
93
|
+
.description("Add credit — opens the console billing page (optional USD amount, minimum $10)")
|
|
94
94
|
.option("--no-open", "Print the URL instead of opening the browser")
|
|
95
95
|
.action(topupAction);
|
|
96
96
|
program
|
|
@@ -100,9 +100,11 @@ program
|
|
|
100
100
|
.action(wrap(balanceAction));
|
|
101
101
|
program
|
|
102
102
|
.command("usage")
|
|
103
|
-
.description("Show
|
|
104
|
-
.option("--
|
|
105
|
-
.option("--
|
|
103
|
+
.description("Show daily API usage")
|
|
104
|
+
.option("--days <n>", "Lookback window in days (1-31)", "7")
|
|
105
|
+
.option("--scope <scope>", "key or account", "key")
|
|
106
|
+
.option("--limit <n>", "Show only the most recent N daily buckets")
|
|
107
|
+
.option("--json", "Output the raw JSON response")
|
|
106
108
|
.action(wrap(usageAction));
|
|
107
109
|
// ── Tool Router (same operations as MCP) ──
|
|
108
110
|
program
|
package/dist/utils/browser.d.ts
CHANGED
|
@@ -17,3 +17,29 @@
|
|
|
17
17
|
* for a click nobody will ever make.
|
|
18
18
|
*/
|
|
19
19
|
export declare function canOpenBrowser(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The command that hands a URL to the default browser on this platform.
|
|
22
|
+
*
|
|
23
|
+
* Windows is the one that needs care. Its opener, `start`, is not a program
|
|
24
|
+
* but a command built into cmd.exe, so running it directly fails with
|
|
25
|
+
* ENOENT, and the failure used to be swallowed: the browser never opened,
|
|
26
|
+
* and `aisa login` sat waiting for an approval that could not arrive.
|
|
27
|
+
* Going through cmd.exe fixes that, with two details:
|
|
28
|
+
*
|
|
29
|
+
* · `start` reads its first quoted argument as a window title, so an empty
|
|
30
|
+
* title goes first; otherwise a quoted URL would become the title.
|
|
31
|
+
* · cmd.exe reads `&` in a query string as "run the next command", which
|
|
32
|
+
* would cut the URL at its second parameter. Its special characters are
|
|
33
|
+
* escaped with `^` so the whole URL reaches the browser.
|
|
34
|
+
*/
|
|
35
|
+
export declare function browserCommand(url: string, platform?: NodeJS.Platform): {
|
|
36
|
+
file: string;
|
|
37
|
+
args: string[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Open a URL in the default browser, without waiting and without failing.
|
|
41
|
+
*
|
|
42
|
+
* Every caller prints the URL as well, so a browser that does not open
|
|
43
|
+
* costs the user a click, not the command.
|
|
44
|
+
*/
|
|
45
|
+
export declare function openBrowser(url: string): void;
|
package/dist/utils/browser.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { run } from "./exec.js";
|
|
1
2
|
/**
|
|
2
3
|
* Can this machine put a page in front of the person using it?
|
|
3
4
|
*
|
|
@@ -29,3 +30,36 @@ export function canOpenBrowser() {
|
|
|
29
30
|
return true;
|
|
30
31
|
return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The command that hands a URL to the default browser on this platform.
|
|
35
|
+
*
|
|
36
|
+
* Windows is the one that needs care. Its opener, `start`, is not a program
|
|
37
|
+
* but a command built into cmd.exe, so running it directly fails with
|
|
38
|
+
* ENOENT, and the failure used to be swallowed: the browser never opened,
|
|
39
|
+
* and `aisa login` sat waiting for an approval that could not arrive.
|
|
40
|
+
* Going through cmd.exe fixes that, with two details:
|
|
41
|
+
*
|
|
42
|
+
* · `start` reads its first quoted argument as a window title, so an empty
|
|
43
|
+
* title goes first; otherwise a quoted URL would become the title.
|
|
44
|
+
* · cmd.exe reads `&` in a query string as "run the next command", which
|
|
45
|
+
* would cut the URL at its second parameter. Its special characters are
|
|
46
|
+
* escaped with `^` so the whole URL reaches the browser.
|
|
47
|
+
*/
|
|
48
|
+
export function browserCommand(url, platform = process.platform) {
|
|
49
|
+
if (platform === "darwin")
|
|
50
|
+
return { file: "open", args: [url] };
|
|
51
|
+
if (platform === "win32") {
|
|
52
|
+
return { file: "cmd", args: ["/c", "start", "", url.replace(/[\^&|<>()]/g, "^$&")] };
|
|
53
|
+
}
|
|
54
|
+
return { file: "xdg-open", args: [url] };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Open a URL in the default browser, without waiting and without failing.
|
|
58
|
+
*
|
|
59
|
+
* Every caller prints the URL as well, so a browser that does not open
|
|
60
|
+
* costs the user a click, not the command.
|
|
61
|
+
*/
|
|
62
|
+
export function openBrowser(url) {
|
|
63
|
+
const { file, args } = browserCommand(url);
|
|
64
|
+
void run(file, args, { timeout: 30_000 }).catch(() => { });
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aisa-one/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
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",
|