@aisa-one/cli 0.6.4 → 0.6.5
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 +18 -0
- package/README.md +12 -7
- package/dist/commands/account.d.ts +76 -2
- package/dist/commands/account.js +161 -7
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/index.js +6 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.5] — 2026-09-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Implement `aisa usage` against the live `GET /v1/usage` endpoint: one row per
|
|
15
|
+
day with requests, failures, input and output tokens, and charged USD, plus a
|
|
16
|
+
total. `--days` (1–31, default 7), `--scope key|account`, `--limit N`, and
|
|
17
|
+
`--json`.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Reject `aisa topup <amount>` below the console's $10 minimum before opening
|
|
22
|
+
the browser (exit 1, no URL), and mention the minimum in `topup` output.
|
|
23
|
+
- Send `start_time` and `end_time` as a coherent pair so `--days 31` cannot race
|
|
24
|
+
the gateway clock past its 31-day cap.
|
|
25
|
+
- Label usage buckets with UTC calendar days; the gateway buckets at UTC
|
|
26
|
+
boundaries, so local-timezone labels were off by one day in UTC-negative zones.
|
|
27
|
+
|
|
10
28
|
## [0.6.4] — 2026-09-30
|
|
11
29
|
|
|
12
30
|
### 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
|
@@ -2,7 +2,8 @@ import { run } from "../utils/exec.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,11 +76,18 @@ 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)
|
|
@@ -79,10 +96,147 @@ export function topupAction(amount, options = {}) {
|
|
|
79
96
|
void run(cmd, [url], { timeout: 30_000 }).catch(() => { });
|
|
80
97
|
hint("Credit lands in your account as soon as the payment completes");
|
|
81
98
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
/** Gateway caps GET /v1/usage at 31 days; buckets are fixed at one day. */
|
|
100
|
+
export const USAGE_MAX_WINDOW_DAYS = 31;
|
|
101
|
+
export const USAGE_DEFAULT_DAYS = 7;
|
|
102
|
+
const USAGE_DAY_SECONDS = 86_400;
|
|
103
|
+
const USAGE_WINDOW_ERROR = "The usage window is at most 31 days. Pass --days as an integer from 1 to 31.";
|
|
104
|
+
/**
|
|
105
|
+
* `aisa usage` reads GET /v1/usage.
|
|
106
|
+
*
|
|
107
|
+
* start_time and end_time are both sent as a coherent pair so the window is
|
|
108
|
+
* exactly --days wide even when the gateway's clock runs ahead of ours. The
|
|
109
|
+
* window cannot exceed 31 days.
|
|
110
|
+
* scope=key is this credential; scope=account sums the whole account, which
|
|
111
|
+
* an OAuth token is allowed to request. --limit trims the table to the latest
|
|
112
|
+
* N days. --json prints the gateway body unchanged.
|
|
113
|
+
*/
|
|
114
|
+
export function parseUsageOptions(options, nowSeconds) {
|
|
115
|
+
const days = parseUsageDays(options.days);
|
|
116
|
+
const scope = parseUsageScope(options.scope);
|
|
117
|
+
const limit = parseUsageLimit(options.limit);
|
|
118
|
+
if (!Number.isFinite(nowSeconds)) {
|
|
119
|
+
throw usageError("Usage start_time must be a positive Unix timestamp in seconds.");
|
|
120
|
+
}
|
|
121
|
+
const end = Math.floor(nowSeconds);
|
|
122
|
+
const start = end - days * USAGE_DAY_SECONDS;
|
|
123
|
+
if (start <= 0) {
|
|
124
|
+
throw usageError("Usage start_time must be a positive Unix timestamp in seconds.");
|
|
125
|
+
}
|
|
126
|
+
return limit === undefined
|
|
127
|
+
? { query: { start_time: String(start), end_time: String(end), scope } }
|
|
128
|
+
: { query: { start_time: String(start), end_time: String(end), scope }, limit };
|
|
129
|
+
}
|
|
130
|
+
function parseUsageDays(raw) {
|
|
131
|
+
const text = raw === undefined || raw.trim() === "" ? String(USAGE_DEFAULT_DAYS) : raw.trim();
|
|
132
|
+
if (!/^\d+$/.test(text))
|
|
133
|
+
throw usageError(USAGE_WINDOW_ERROR);
|
|
134
|
+
const days = Number(text);
|
|
135
|
+
if (!Number.isSafeInteger(days) || days < 1 || days > USAGE_MAX_WINDOW_DAYS) {
|
|
136
|
+
throw usageError(USAGE_WINDOW_ERROR);
|
|
137
|
+
}
|
|
138
|
+
return days;
|
|
139
|
+
}
|
|
140
|
+
function parseUsageScope(raw) {
|
|
141
|
+
const text = raw === undefined || raw.trim() === "" ? "key" : raw.trim();
|
|
142
|
+
if (text !== "key" && text !== "account") {
|
|
143
|
+
throw usageError('Pass --scope as "key" or "account".');
|
|
144
|
+
}
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
function parseUsageLimit(raw) {
|
|
148
|
+
if (raw === undefined || raw.trim() === "")
|
|
149
|
+
return undefined;
|
|
150
|
+
if (!/^\d+$/.test(raw.trim()))
|
|
151
|
+
throw usageError("--limit must be a positive integer.");
|
|
152
|
+
const limit = Number(raw.trim());
|
|
153
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
154
|
+
throw usageError("--limit must be a positive integer.");
|
|
155
|
+
}
|
|
156
|
+
return limit;
|
|
157
|
+
}
|
|
158
|
+
/** UTC calendar day (YYYY-MM-DD) for a bucket's start_time. The gateway
|
|
159
|
+
* buckets at UTC day boundaries, so a local-timezone label would show the
|
|
160
|
+
* previous calendar day for every UTC-midnight bucket in UTC-negative zones. */
|
|
161
|
+
export function formatUsageDate(unixSeconds) {
|
|
162
|
+
if (!Number.isFinite(unixSeconds))
|
|
163
|
+
return "";
|
|
164
|
+
const date = new Date(unixSeconds * 1000);
|
|
165
|
+
if (Number.isNaN(date.getTime()))
|
|
166
|
+
return "";
|
|
167
|
+
return date.toISOString().slice(0, 10);
|
|
168
|
+
}
|
|
169
|
+
/** Chronological order, then the latest `limit` buckets. A missing limit keeps
|
|
170
|
+
* the whole window. */
|
|
171
|
+
export function selectRecentBuckets(buckets, limit) {
|
|
172
|
+
const sorted = [...buckets].sort((a, b) => Number(a.start_time) - Number(b.start_time));
|
|
173
|
+
if (limit === undefined)
|
|
174
|
+
return sorted;
|
|
175
|
+
return sorted.slice(-limit);
|
|
176
|
+
}
|
|
177
|
+
export function renderUsageReport(buckets) {
|
|
178
|
+
if (buckets.length === 0)
|
|
179
|
+
return " No usage in this window.";
|
|
180
|
+
const headers = ["Date", "Requests", "Failed", "Input tokens", "Output tokens", "Charged"];
|
|
181
|
+
const rows = buckets.map((bucket) => [
|
|
182
|
+
formatUsageDate(Number(bucket.start_time)),
|
|
183
|
+
formatCount(bucket.requests),
|
|
184
|
+
formatCount(bucket.failed_requests),
|
|
185
|
+
formatCount(bucket.input_tokens),
|
|
186
|
+
formatCount(bucket.output_tokens),
|
|
187
|
+
formatMicrosUSD(asMicros(bucket.charged_micros_usd)),
|
|
188
|
+
]);
|
|
189
|
+
rows.push([
|
|
190
|
+
"Total",
|
|
191
|
+
formatCount(sumCount(buckets, "requests")),
|
|
192
|
+
formatCount(sumCount(buckets, "failed_requests")),
|
|
193
|
+
formatCount(sumCount(buckets, "input_tokens")),
|
|
194
|
+
formatCount(sumCount(buckets, "output_tokens")),
|
|
195
|
+
formatMicrosUSD(sumMicros(buckets)),
|
|
196
|
+
]);
|
|
197
|
+
return table(headers, rows);
|
|
198
|
+
}
|
|
199
|
+
function formatCount(value) {
|
|
200
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
201
|
+
if (!Number.isFinite(n))
|
|
202
|
+
return "0";
|
|
203
|
+
return String(Math.trunc(n));
|
|
204
|
+
}
|
|
205
|
+
function sumCount(buckets, key) {
|
|
206
|
+
return buckets.reduce((sum, bucket) => {
|
|
207
|
+
const n = Number(bucket[key]);
|
|
208
|
+
return sum + (Number.isFinite(n) ? Math.trunc(n) : 0);
|
|
209
|
+
}, 0);
|
|
210
|
+
}
|
|
211
|
+
function asMicros(value) {
|
|
212
|
+
if (typeof value === "bigint")
|
|
213
|
+
return value;
|
|
214
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
215
|
+
return BigInt(Math.trunc(value));
|
|
216
|
+
if (typeof value === "string" && /^-?\d+$/.test(value.trim()))
|
|
217
|
+
return BigInt(value.trim());
|
|
218
|
+
return 0n;
|
|
219
|
+
}
|
|
220
|
+
function sumMicros(buckets) {
|
|
221
|
+
return buckets.reduce((sum, bucket) => sum + asMicros(bucket.charged_micros_usd), 0n);
|
|
222
|
+
}
|
|
223
|
+
export async function usageAction(options = {}) {
|
|
224
|
+
const parsed = parseUsageOptions(options, Math.floor(Date.now() / 1000));
|
|
225
|
+
const token = await requireAccessToken();
|
|
226
|
+
const res = await apiRequest(token, "usage", {
|
|
227
|
+
query: {
|
|
228
|
+
start_time: parsed.query.start_time,
|
|
229
|
+
end_time: parsed.query.end_time,
|
|
230
|
+
scope: parsed.query.scope,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
if (!res.success || !res.data) {
|
|
234
|
+
throw new Error(res.error || "Failed to fetch usage");
|
|
235
|
+
}
|
|
236
|
+
if (options.json) {
|
|
237
|
+
console.log(formatJson(res.data));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const buckets = Array.isArray(res.data.buckets) ? res.data.buckets : [];
|
|
241
|
+
console.log(renderUsageReport(selectRecentBuckets(buckets, parsed.limit)));
|
|
88
242
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.6.
|
|
1
|
+
export declare const VERSION = "0.6.5";
|
|
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.5";
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aisa-one/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
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",
|