@alphafox/cli 0.1.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/LICENSE +21 -0
- package/README.md +53 -0
- package/bin/alphafox.js +2 -0
- package/dist/auth/pkce.d.ts +13 -0
- package/dist/auth/pkce.js +27 -0
- package/dist/catalog/allowlist.d.ts +9 -0
- package/dist/catalog/allowlist.js +69 -0
- package/dist/catalog/operations.d.ts +34 -0
- package/dist/catalog/operations.js +247 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +18 -0
- package/dist/commands/run.d.ts +14 -0
- package/dist/commands/run.js +855 -0
- package/dist/config/profiles.d.ts +29 -0
- package/dist/config/profiles.js +134 -0
- package/dist/envelope.d.ts +37 -0
- package/dist/envelope.js +78 -0
- package/dist/http/client.d.ts +19 -0
- package/dist/http/client.js +110 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +36 -0
- package/dist/keychain/store.d.ts +19 -0
- package/dist/keychain/store.js +149 -0
- package/dist/safety/confirmation.d.ts +22 -0
- package/dist/safety/confirmation.js +29 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +7 -0
- package/docs/e2e-staging.md +24 -0
- package/docs/release-supply-chain.md +49 -0
- package/package.json +60 -0
- package/skills/account/SKILL.md +21 -0
- package/skills/admin/SKILL.md +19 -0
- package/skills/alphafox-shared/SKILL.md +59 -0
- package/skills/auth/SKILL.md +35 -0
- package/skills/exchange/SKILL.md +17 -0
- package/skills/market/SKILL.md +14 -0
- package/skills/notification/SKILL.md +16 -0
- package/skills/strategy/SKILL.md +37 -0
- package/skills/trading/SKILL.md +28 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseGlobalFlags = parseGlobalFlags;
|
|
37
|
+
exports.runCli = runCli;
|
|
38
|
+
const node_crypto_1 = require("node:crypto");
|
|
39
|
+
const operations_1 = require("../catalog/operations");
|
|
40
|
+
const allowlist_1 = require("../catalog/allowlist");
|
|
41
|
+
const profiles_1 = require("../config/profiles");
|
|
42
|
+
const envelope_1 = require("../envelope");
|
|
43
|
+
const client_1 = require("../http/client");
|
|
44
|
+
const store_1 = require("../keychain/store");
|
|
45
|
+
const confirmation_1 = require("../safety/confirmation");
|
|
46
|
+
const version_1 = require("../version");
|
|
47
|
+
function parseGlobalFlags(argv) {
|
|
48
|
+
const flags = {
|
|
49
|
+
format: "json",
|
|
50
|
+
yes: false,
|
|
51
|
+
dryRun: false,
|
|
52
|
+
noInput: false,
|
|
53
|
+
};
|
|
54
|
+
const rest = [];
|
|
55
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
56
|
+
const a = argv[i];
|
|
57
|
+
if (a === "--profile" || a === "-p") {
|
|
58
|
+
flags.profile = argv[++i];
|
|
59
|
+
}
|
|
60
|
+
else if (a.startsWith("--profile=")) {
|
|
61
|
+
flags.profile = a.slice("--profile=".length);
|
|
62
|
+
}
|
|
63
|
+
else if (a === "--format") {
|
|
64
|
+
flags.format = argv[++i];
|
|
65
|
+
}
|
|
66
|
+
else if (a.startsWith("--format=")) {
|
|
67
|
+
flags.format = a.slice("--format=".length);
|
|
68
|
+
}
|
|
69
|
+
else if (a === "--yes" || a === "-y") {
|
|
70
|
+
flags.yes = true;
|
|
71
|
+
}
|
|
72
|
+
else if (a === "--dry-run") {
|
|
73
|
+
flags.dryRun = true;
|
|
74
|
+
}
|
|
75
|
+
else if (a === "--no-input") {
|
|
76
|
+
flags.noInput = true;
|
|
77
|
+
}
|
|
78
|
+
else if (a === "--unsafe-custom-endpoint") {
|
|
79
|
+
flags.unsafeCustomEndpoint = argv[++i];
|
|
80
|
+
}
|
|
81
|
+
else if (a.startsWith("--unsafe-custom-endpoint=")) {
|
|
82
|
+
flags.unsafeCustomEndpoint = a.slice("--unsafe-custom-endpoint=".length);
|
|
83
|
+
}
|
|
84
|
+
else if (a === "--jq") {
|
|
85
|
+
flags.jq = argv[++i];
|
|
86
|
+
}
|
|
87
|
+
else if (a.startsWith("--jq=")) {
|
|
88
|
+
flags.jq = a.slice("--jq=".length);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
rest.push(a);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { flags, rest };
|
|
95
|
+
}
|
|
96
|
+
async function runCli(argv, env = process.env) {
|
|
97
|
+
const { flags, rest } = parseGlobalFlags(argv);
|
|
98
|
+
const [cmd, ...args] = rest;
|
|
99
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
100
|
+
(0, envelope_1.writeSuccess)({
|
|
101
|
+
name: version_1.CLI_NAME,
|
|
102
|
+
usage: [
|
|
103
|
+
"alphafox version",
|
|
104
|
+
"alphafox doctor",
|
|
105
|
+
"alphafox auth login [--no-wait|--device-code CODE|--browser]",
|
|
106
|
+
"alphafox auth status [--verify]",
|
|
107
|
+
"alphafox auth logout",
|
|
108
|
+
"alphafox whoami",
|
|
109
|
+
"alphafox profile list|use <name>",
|
|
110
|
+
"alphafox schema [operationId]",
|
|
111
|
+
"alphafox api METHOD PATH [--body JSON]",
|
|
112
|
+
"alphafox <domain> <resource> <action> [flags]",
|
|
113
|
+
],
|
|
114
|
+
}, { format: flags.format });
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
switch (cmd) {
|
|
119
|
+
case "version":
|
|
120
|
+
return cmdVersion(flags);
|
|
121
|
+
case "doctor":
|
|
122
|
+
return cmdDoctor(flags, env);
|
|
123
|
+
case "whoami":
|
|
124
|
+
return await cmdWhoami(flags, env);
|
|
125
|
+
case "auth":
|
|
126
|
+
return await cmdAuth(args, flags, env);
|
|
127
|
+
case "profile":
|
|
128
|
+
return cmdProfile(args, flags, env);
|
|
129
|
+
case "schema":
|
|
130
|
+
return cmdSchema(args, flags);
|
|
131
|
+
case "api":
|
|
132
|
+
return await cmdApi(args, flags, env);
|
|
133
|
+
case "catalog":
|
|
134
|
+
return cmdCatalog(flags);
|
|
135
|
+
default:
|
|
136
|
+
return await cmdTyped(cmd, args, flags, env);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
+
const status = err && typeof err === "object" && "status" in err
|
|
142
|
+
? Number(err.status)
|
|
143
|
+
: undefined;
|
|
144
|
+
const type = err && typeof err === "object" && "type" in err
|
|
145
|
+
? String(err.type)
|
|
146
|
+
: "runtime";
|
|
147
|
+
(0, envelope_1.writeError)({
|
|
148
|
+
type,
|
|
149
|
+
message,
|
|
150
|
+
status,
|
|
151
|
+
subtype: err && typeof err === "object" && "subtype" in err
|
|
152
|
+
? String(err.subtype)
|
|
153
|
+
: undefined,
|
|
154
|
+
}, { exitCode: status === 401 || status === 403 ? 77 : 1 });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function cmdVersion(flags) {
|
|
158
|
+
(0, envelope_1.writeSuccess)({
|
|
159
|
+
name: version_1.CLI_NAME,
|
|
160
|
+
package: version_1.CLI_PACKAGE,
|
|
161
|
+
version: version_1.CLI_VERSION,
|
|
162
|
+
contractVersion: version_1.CLI_CONTRACT_VERSION,
|
|
163
|
+
catalogVersion: operations_1.CATALOG_VERSION,
|
|
164
|
+
node: process.version,
|
|
165
|
+
platform: process.platform,
|
|
166
|
+
arch: process.arch,
|
|
167
|
+
}, { format: flags.format });
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
function cmdDoctor(flags, env) {
|
|
171
|
+
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
172
|
+
unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
|
|
173
|
+
});
|
|
174
|
+
// Avoid macOS `security` stderr noise when no item exists; file keychain is fine for doctor.
|
|
175
|
+
const doctorEnv = { ...env, ALPHAFOX_FORCE_FILE_KEYCHAIN: env.ALPHAFOX_FORCE_FILE_KEYCHAIN ?? "1" };
|
|
176
|
+
const tokens = (0, store_1.loadTokens)(profile.name, doctorEnv);
|
|
177
|
+
const checks = [
|
|
178
|
+
{
|
|
179
|
+
name: "node",
|
|
180
|
+
ok: Number(process.versions.node.split(".")[0]) >= 20,
|
|
181
|
+
detail: process.version,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "profile",
|
|
185
|
+
ok: true,
|
|
186
|
+
detail: profile.name,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "apiBaseUrl",
|
|
190
|
+
ok: Boolean(profile.apiBaseUrl),
|
|
191
|
+
detail: profile.apiBaseUrl,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "issuer",
|
|
195
|
+
ok: Boolean(profile.issuer),
|
|
196
|
+
detail: profile.issuer,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "keychain",
|
|
200
|
+
ok: true,
|
|
201
|
+
detail: tokens
|
|
202
|
+
? `token present (fp=${(0, store_1.tokenFingerprint)(tokens.accessToken)})`
|
|
203
|
+
: "no tokens stored",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "configHasNoTokens",
|
|
207
|
+
ok: true,
|
|
208
|
+
detail: "enforced",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "automation",
|
|
212
|
+
ok: true,
|
|
213
|
+
detail: "v1 deferred (interactive login only)",
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
const ok = checks.every((c) => c.ok);
|
|
217
|
+
(0, envelope_1.writeSuccess)({ ok, profile: profile.name, checks }, { format: flags.format });
|
|
218
|
+
return ok ? 0 : 1;
|
|
219
|
+
}
|
|
220
|
+
async function cmdWhoami(flags, env) {
|
|
221
|
+
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
222
|
+
unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
|
|
223
|
+
});
|
|
224
|
+
const requestId = (0, envelope_1.newRequestId)();
|
|
225
|
+
const res = await (0, client_1.apiRequest)({
|
|
226
|
+
method: "GET",
|
|
227
|
+
path: "/api/v1/me",
|
|
228
|
+
profile,
|
|
229
|
+
requestId,
|
|
230
|
+
}, env);
|
|
231
|
+
if (res.status >= 400) {
|
|
232
|
+
(0, envelope_1.writeError)({
|
|
233
|
+
type: "http",
|
|
234
|
+
status: res.status,
|
|
235
|
+
message: extractErrorMessage(res.json, res.bodyText),
|
|
236
|
+
code: extractErrorCode(res.json),
|
|
237
|
+
}, { requestId: res.requestId, exitCode: res.status === 401 ? 77 : 1 });
|
|
238
|
+
}
|
|
239
|
+
(0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format });
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
async function cmdAuth(args, flags, env) {
|
|
243
|
+
const sub = args[0];
|
|
244
|
+
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
245
|
+
unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
|
|
246
|
+
});
|
|
247
|
+
if (sub === "status") {
|
|
248
|
+
const verify = args.includes("--verify");
|
|
249
|
+
const tokens = (0, store_1.loadTokens)(profile.name, env);
|
|
250
|
+
if (!tokens) {
|
|
251
|
+
(0, envelope_1.writeSuccess)({
|
|
252
|
+
authenticated: false,
|
|
253
|
+
profile: profile.name,
|
|
254
|
+
verified: false,
|
|
255
|
+
}, { format: flags.format });
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
let verified = null;
|
|
259
|
+
let whoami = null;
|
|
260
|
+
if (verify) {
|
|
261
|
+
const res = await (0, client_1.apiRequest)({
|
|
262
|
+
method: "GET",
|
|
263
|
+
path: "/api/v1/me",
|
|
264
|
+
profile,
|
|
265
|
+
}, env);
|
|
266
|
+
verified = res.status >= 200 && res.status < 300;
|
|
267
|
+
whoami = verified ? res.json : { status: res.status, body: res.json };
|
|
268
|
+
}
|
|
269
|
+
(0, envelope_1.writeSuccess)({
|
|
270
|
+
authenticated: true,
|
|
271
|
+
profile: profile.name,
|
|
272
|
+
environment: tokens.environment,
|
|
273
|
+
issuer: tokens.issuer,
|
|
274
|
+
audience: tokens.audience,
|
|
275
|
+
clientId: tokens.clientId,
|
|
276
|
+
scopes: tokens.scopes,
|
|
277
|
+
accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.accessToken),
|
|
278
|
+
expiresAt: tokens.expiresAt,
|
|
279
|
+
verified,
|
|
280
|
+
whoami,
|
|
281
|
+
}, { format: flags.format });
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
if (sub === "logout") {
|
|
285
|
+
const tokens = (0, store_1.loadTokens)(profile.name, env);
|
|
286
|
+
if (tokens?.refreshToken) {
|
|
287
|
+
try {
|
|
288
|
+
await (0, client_1.apiRequest)({
|
|
289
|
+
method: "POST",
|
|
290
|
+
path: "/api/auth/oauth/revoke",
|
|
291
|
+
profile,
|
|
292
|
+
body: {
|
|
293
|
+
token: tokens.refreshToken,
|
|
294
|
+
token_type_hint: "refresh_token",
|
|
295
|
+
},
|
|
296
|
+
skipAuth: false,
|
|
297
|
+
}, env);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// still clear local
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
(0, store_1.deleteTokens)(profile.name, env);
|
|
304
|
+
(0, envelope_1.writeSuccess)({ loggedOut: true, profile: profile.name }, { format: flags.format });
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
if (sub === "login") {
|
|
308
|
+
return await cmdAuthLogin(args.slice(1), flags, env, profile);
|
|
309
|
+
}
|
|
310
|
+
(0, envelope_1.writeError)({
|
|
311
|
+
type: "usage",
|
|
312
|
+
message: "Usage: alphafox auth login|status|logout",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async function cmdAuthLogin(args, flags, env, profile) {
|
|
316
|
+
const noWait = args.includes("--no-wait");
|
|
317
|
+
const deviceCodeIdx = args.indexOf("--device-code");
|
|
318
|
+
const deviceCode = deviceCodeIdx >= 0 ? args[deviceCodeIdx + 1] : undefined;
|
|
319
|
+
const browser = args.includes("--browser");
|
|
320
|
+
const codeIdx = args.indexOf("--code");
|
|
321
|
+
const authCode = codeIdx >= 0 ? args[codeIdx + 1] : undefined;
|
|
322
|
+
const verifierIdx = args.indexOf("--code-verifier");
|
|
323
|
+
const codeVerifierArg = verifierIdx >= 0 ? args[verifierIdx + 1] : undefined;
|
|
324
|
+
const redirectIdx = args.indexOf("--redirect-uri");
|
|
325
|
+
const redirectUriArg = redirectIdx >= 0
|
|
326
|
+
? args[redirectIdx + 1]
|
|
327
|
+
: "http://127.0.0.1:8742/callback";
|
|
328
|
+
// Complete Authorization Code + PKCE exchange
|
|
329
|
+
if (authCode) {
|
|
330
|
+
if (!codeVerifierArg) {
|
|
331
|
+
(0, envelope_1.writeError)({
|
|
332
|
+
type: "usage",
|
|
333
|
+
message: "PKCE exchange requires --code-verifier (from the earlier --browser start output).",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const res = await (0, client_1.apiRequest)({
|
|
337
|
+
method: "POST",
|
|
338
|
+
path: "/api/auth/oauth/token",
|
|
339
|
+
profile,
|
|
340
|
+
skipAuth: true,
|
|
341
|
+
body: {
|
|
342
|
+
grant_type: "authorization_code",
|
|
343
|
+
code: authCode,
|
|
344
|
+
redirect_uri: redirectUriArg,
|
|
345
|
+
client_id: profile.clientId,
|
|
346
|
+
code_verifier: codeVerifierArg,
|
|
347
|
+
},
|
|
348
|
+
}, env);
|
|
349
|
+
if (res.status >= 400) {
|
|
350
|
+
(0, envelope_1.writeError)({
|
|
351
|
+
type: "auth",
|
|
352
|
+
status: res.status,
|
|
353
|
+
message: extractErrorMessage(res.json, res.bodyText),
|
|
354
|
+
}, { requestId: res.requestId });
|
|
355
|
+
}
|
|
356
|
+
const tokens = extractTokenPair(res.json);
|
|
357
|
+
(0, store_1.saveTokens)(profile.name, {
|
|
358
|
+
accessToken: tokens.access_token,
|
|
359
|
+
refreshToken: tokens.refresh_token,
|
|
360
|
+
expiresAt: Date.now() + (tokens.expires_in ?? 600) * 1000,
|
|
361
|
+
environment: profile.name,
|
|
362
|
+
issuer: profile.issuer,
|
|
363
|
+
audience: profile.audience,
|
|
364
|
+
clientId: profile.clientId,
|
|
365
|
+
scopes: (tokens.scope ?? "openid profile").split(/\s+/),
|
|
366
|
+
}, env);
|
|
367
|
+
(0, envelope_1.writeSuccess)({
|
|
368
|
+
authenticated: true,
|
|
369
|
+
flow: "authorization_code_pkce",
|
|
370
|
+
profile: profile.name,
|
|
371
|
+
accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
|
|
372
|
+
expiresIn: tokens.expires_in,
|
|
373
|
+
}, { format: flags.format, requestId: res.requestId });
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
if (deviceCode) {
|
|
377
|
+
const res = await (0, client_1.apiRequest)({
|
|
378
|
+
method: "POST",
|
|
379
|
+
path: "/api/auth/oauth/device/token",
|
|
380
|
+
profile,
|
|
381
|
+
skipAuth: true,
|
|
382
|
+
body: {
|
|
383
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
384
|
+
device_code: deviceCode,
|
|
385
|
+
client_id: profile.clientId,
|
|
386
|
+
},
|
|
387
|
+
}, env);
|
|
388
|
+
if (res.status === 400 && isPending(res.json)) {
|
|
389
|
+
(0, envelope_1.writeSuccess)({
|
|
390
|
+
status: "authorization_pending",
|
|
391
|
+
profile: profile.name,
|
|
392
|
+
}, { format: flags.format, requestId: res.requestId });
|
|
393
|
+
return 0;
|
|
394
|
+
}
|
|
395
|
+
if (res.status >= 400) {
|
|
396
|
+
(0, envelope_1.writeError)({
|
|
397
|
+
type: "auth",
|
|
398
|
+
status: res.status,
|
|
399
|
+
message: extractErrorMessage(res.json, res.bodyText),
|
|
400
|
+
}, { requestId: res.requestId });
|
|
401
|
+
}
|
|
402
|
+
const tokens = extractTokenPair(res.json);
|
|
403
|
+
(0, store_1.saveTokens)(profile.name, {
|
|
404
|
+
accessToken: tokens.access_token,
|
|
405
|
+
refreshToken: tokens.refresh_token,
|
|
406
|
+
expiresAt: Date.now() + (tokens.expires_in ?? 600) * 1000,
|
|
407
|
+
environment: profile.name,
|
|
408
|
+
issuer: profile.issuer,
|
|
409
|
+
audience: profile.audience,
|
|
410
|
+
clientId: profile.clientId,
|
|
411
|
+
scopes: (tokens.scope ?? "openid profile").split(/\s+/),
|
|
412
|
+
}, env);
|
|
413
|
+
(0, envelope_1.writeSuccess)({
|
|
414
|
+
authenticated: true,
|
|
415
|
+
profile: profile.name,
|
|
416
|
+
accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
|
|
417
|
+
expiresIn: tokens.expires_in,
|
|
418
|
+
}, { format: flags.format, requestId: res.requestId });
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
// Start device flow (default headless path)
|
|
422
|
+
if (!browser) {
|
|
423
|
+
const res = await (0, client_1.apiRequest)({
|
|
424
|
+
method: "POST",
|
|
425
|
+
path: "/api/auth/oauth/device/code",
|
|
426
|
+
profile,
|
|
427
|
+
skipAuth: true,
|
|
428
|
+
body: {
|
|
429
|
+
client_id: profile.clientId,
|
|
430
|
+
scope: "openid profile offline_access",
|
|
431
|
+
},
|
|
432
|
+
}, env);
|
|
433
|
+
if (res.status >= 400) {
|
|
434
|
+
(0, envelope_1.writeError)({
|
|
435
|
+
type: "auth",
|
|
436
|
+
status: res.status,
|
|
437
|
+
message: extractErrorMessage(res.json, res.bodyText),
|
|
438
|
+
}, { requestId: res.requestId });
|
|
439
|
+
}
|
|
440
|
+
const data = res.json;
|
|
441
|
+
(0, envelope_1.writeSuccess)({
|
|
442
|
+
flow: "device_authorization",
|
|
443
|
+
profile: profile.name,
|
|
444
|
+
device_code: data.device_code,
|
|
445
|
+
user_code: data.user_code,
|
|
446
|
+
verification_uri: data.verification_uri,
|
|
447
|
+
verification_uri_complete: data.verification_uri_complete,
|
|
448
|
+
expires_in: data.expires_in,
|
|
449
|
+
interval: data.interval,
|
|
450
|
+
next: "Open verification_uri, approve, then: alphafox auth login --device-code <device_code>",
|
|
451
|
+
}, { format: flags.format, requestId: res.requestId });
|
|
452
|
+
if (noWait) {
|
|
453
|
+
return 0;
|
|
454
|
+
}
|
|
455
|
+
// poll
|
|
456
|
+
const code = String(data.device_code);
|
|
457
|
+
const interval = Number(data.interval ?? 5) * 1000;
|
|
458
|
+
const deadline = Date.now() + Number(data.expires_in ?? 600) * 1000;
|
|
459
|
+
while (Date.now() < deadline) {
|
|
460
|
+
await sleep(interval);
|
|
461
|
+
const poll = await (0, client_1.apiRequest)({
|
|
462
|
+
method: "POST",
|
|
463
|
+
path: "/api/auth/oauth/device/token",
|
|
464
|
+
profile,
|
|
465
|
+
skipAuth: true,
|
|
466
|
+
body: {
|
|
467
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
468
|
+
device_code: code,
|
|
469
|
+
client_id: profile.clientId,
|
|
470
|
+
},
|
|
471
|
+
}, env);
|
|
472
|
+
if (isPending(poll.json)) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (poll.status >= 400) {
|
|
476
|
+
(0, envelope_1.writeError)({
|
|
477
|
+
type: "auth",
|
|
478
|
+
status: poll.status,
|
|
479
|
+
message: extractErrorMessage(poll.json, poll.bodyText),
|
|
480
|
+
}, { requestId: poll.requestId });
|
|
481
|
+
}
|
|
482
|
+
const tokens = extractTokenPair(poll.json);
|
|
483
|
+
(0, store_1.saveTokens)(profile.name, {
|
|
484
|
+
accessToken: tokens.access_token,
|
|
485
|
+
refreshToken: tokens.refresh_token,
|
|
486
|
+
expiresAt: Date.now() + (tokens.expires_in ?? 600) * 1000,
|
|
487
|
+
environment: profile.name,
|
|
488
|
+
issuer: profile.issuer,
|
|
489
|
+
audience: profile.audience,
|
|
490
|
+
clientId: profile.clientId,
|
|
491
|
+
scopes: (tokens.scope ?? "openid profile").split(/\s+/),
|
|
492
|
+
}, env);
|
|
493
|
+
(0, envelope_1.writeSuccess)({
|
|
494
|
+
authenticated: true,
|
|
495
|
+
profile: profile.name,
|
|
496
|
+
accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
|
|
497
|
+
}, { format: flags.format, requestId: poll.requestId });
|
|
498
|
+
return 0;
|
|
499
|
+
}
|
|
500
|
+
(0, envelope_1.writeError)({
|
|
501
|
+
type: "auth",
|
|
502
|
+
message: "Device authorization timed out",
|
|
503
|
+
subtype: "expired_token",
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
// Browser Authorization Code + PKCE (RFC 7636 S256)
|
|
507
|
+
if (browser || args.includes("--pkce")) {
|
|
508
|
+
const { createHash, randomBytes } = await Promise.resolve().then(() => __importStar(require("node:crypto")));
|
|
509
|
+
const state = (0, node_crypto_1.randomUUID)();
|
|
510
|
+
const codeVerifier = randomBytes(32).toString("base64url");
|
|
511
|
+
const codeChallenge = createHash("sha256")
|
|
512
|
+
.update(codeVerifier, "utf8")
|
|
513
|
+
.digest("base64url");
|
|
514
|
+
const redirectUri = redirectUriArg;
|
|
515
|
+
const authorize = new URL(`${profile.issuer}/oauth/authorize`);
|
|
516
|
+
authorize.searchParams.set("response_type", "code");
|
|
517
|
+
authorize.searchParams.set("client_id", profile.clientId);
|
|
518
|
+
authorize.searchParams.set("redirect_uri", redirectUri);
|
|
519
|
+
authorize.searchParams.set("code_challenge", codeChallenge);
|
|
520
|
+
authorize.searchParams.set("code_challenge_method", "S256");
|
|
521
|
+
authorize.searchParams.set("state", state);
|
|
522
|
+
authorize.searchParams.set("scope", "openid profile offline_access");
|
|
523
|
+
(0, envelope_1.writeSuccess)({
|
|
524
|
+
flow: "authorization_code_pkce",
|
|
525
|
+
profile: profile.name,
|
|
526
|
+
authorizeUrl: authorize.toString(),
|
|
527
|
+
redirect_uri: redirectUri,
|
|
528
|
+
state,
|
|
529
|
+
code_challenge: codeChallenge,
|
|
530
|
+
code_challenge_method: "S256",
|
|
531
|
+
// Required for the follow-up exchange; store securely (not in config).
|
|
532
|
+
code_verifier: codeVerifier,
|
|
533
|
+
next: `After browser redirect: alphafox auth login --code <code> --code-verifier ${codeVerifier} --redirect-uri ${redirectUri}`,
|
|
534
|
+
note: "Sign in at authorizeUrl if prompted. Prefer Device Flow for headless agents.",
|
|
535
|
+
}, { format: flags.format });
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
(0, envelope_1.writeError)({
|
|
539
|
+
type: "usage",
|
|
540
|
+
message: "Usage: alphafox auth login [--no-wait|--device-code CODE|--browser|--code CODE --code-verifier V]",
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
function cmdProfile(args, flags, env) {
|
|
544
|
+
const sub = args[0] ?? "list";
|
|
545
|
+
if (sub === "list") {
|
|
546
|
+
const file = (0, profiles_1.loadConfigFile)(env);
|
|
547
|
+
(0, envelope_1.writeSuccess)({
|
|
548
|
+
activeProfile: file.activeProfile,
|
|
549
|
+
profiles: ["production", "staging", "local"].map((name) => (0, profiles_1.resolveProfile)(name, env)),
|
|
550
|
+
}, { format: flags.format });
|
|
551
|
+
return 0;
|
|
552
|
+
}
|
|
553
|
+
if (sub === "use") {
|
|
554
|
+
const name = args[1];
|
|
555
|
+
if (!name || !["production", "staging", "local"].includes(name)) {
|
|
556
|
+
(0, envelope_1.writeError)({
|
|
557
|
+
type: "usage",
|
|
558
|
+
message: "Usage: alphafox profile use production|staging|local",
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
const file = (0, profiles_1.loadConfigFile)(env);
|
|
562
|
+
(0, profiles_1.saveConfigFile)({ ...file, activeProfile: name }, env);
|
|
563
|
+
(0, envelope_1.writeSuccess)({ activeProfile: name }, { format: flags.format });
|
|
564
|
+
return 0;
|
|
565
|
+
}
|
|
566
|
+
(0, envelope_1.writeError)({ type: "usage", message: "Usage: alphafox profile list|use" });
|
|
567
|
+
}
|
|
568
|
+
function cmdSchema(args, flags) {
|
|
569
|
+
const operationId = args[0];
|
|
570
|
+
if (!operationId) {
|
|
571
|
+
(0, envelope_1.writeSuccess)({
|
|
572
|
+
contractVersion: operations_1.CATALOG_VERSION,
|
|
573
|
+
operations: operations_1.CATALOG_OPERATIONS.map((o) => o.operationId),
|
|
574
|
+
}, { format: flags.format });
|
|
575
|
+
return 0;
|
|
576
|
+
}
|
|
577
|
+
const op = (0, operations_1.findCatalogOperation)(operationId);
|
|
578
|
+
if (!op) {
|
|
579
|
+
(0, envelope_1.writeError)({
|
|
580
|
+
type: "not_found",
|
|
581
|
+
message: `Unknown operationId: ${operationId}`,
|
|
582
|
+
status: 404,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
(0, envelope_1.writeSuccess)({
|
|
586
|
+
operationId: op.operationId,
|
|
587
|
+
method: op.method,
|
|
588
|
+
path: op.path,
|
|
589
|
+
role: op.role,
|
|
590
|
+
risk: op.risk,
|
|
591
|
+
scopes: op.scopes,
|
|
592
|
+
stream: op.stream ?? false,
|
|
593
|
+
mvp: op.mvp ?? false,
|
|
594
|
+
description: op.description,
|
|
595
|
+
input: {
|
|
596
|
+
type: "object",
|
|
597
|
+
note: "See OpenAPI at GET /api/v1/openapi.json and alphafox-contracts Zod schemas",
|
|
598
|
+
},
|
|
599
|
+
output: { type: "object" },
|
|
600
|
+
errors: ["401", "403", "404", "409", "422", "429", "5xx"],
|
|
601
|
+
}, { format: flags.format });
|
|
602
|
+
return 0;
|
|
603
|
+
}
|
|
604
|
+
function cmdCatalog(flags) {
|
|
605
|
+
(0, envelope_1.writeSuccess)((0, operations_1.buildCapabilityManifest)(), { format: flags.format });
|
|
606
|
+
return 0;
|
|
607
|
+
}
|
|
608
|
+
async function cmdApi(args, flags, env) {
|
|
609
|
+
const method = args[0]?.toUpperCase();
|
|
610
|
+
const path = args[1];
|
|
611
|
+
if (!method || !path) {
|
|
612
|
+
(0, envelope_1.writeError)({
|
|
613
|
+
type: "usage",
|
|
614
|
+
message: "Usage: alphafox api METHOD PATH [--body JSON]",
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
if ((0, allowlist_1.isInternalDisallowedPath)(path) || !(0, allowlist_1.isFacadeAllowlistedPath)(path)) {
|
|
618
|
+
(0, envelope_1.writeError)({
|
|
619
|
+
type: "authorization",
|
|
620
|
+
subtype: "facade_only",
|
|
621
|
+
message: `Raw api only allows Public API facade paths under /api/v1. Rejected: ${path}`,
|
|
622
|
+
status: 403,
|
|
623
|
+
}, { exitCode: 77 });
|
|
624
|
+
}
|
|
625
|
+
let body;
|
|
626
|
+
const bodyIdx = args.indexOf("--body");
|
|
627
|
+
if (bodyIdx >= 0) {
|
|
628
|
+
body = JSON.parse(args[bodyIdx + 1] ?? "{}");
|
|
629
|
+
}
|
|
630
|
+
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
631
|
+
unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
|
|
632
|
+
});
|
|
633
|
+
// Infer risk from catalog if possible
|
|
634
|
+
const catalogHit = operations_1.CATALOG_OPERATIONS.find((o) => o.method === method && pathMatches(o.path, path));
|
|
635
|
+
if (catalogHit) {
|
|
636
|
+
const gate = (0, confirmation_1.assertHighRiskConfirmation)({
|
|
637
|
+
risk: catalogHit.risk,
|
|
638
|
+
yes: flags.yes,
|
|
639
|
+
dryRun: flags.dryRun,
|
|
640
|
+
action: `api ${method} ${path}`,
|
|
641
|
+
});
|
|
642
|
+
if (!gate.allowed && gate.error) {
|
|
643
|
+
process.stderr.write(`${JSON.stringify((0, envelope_1.errorEnvelope)(gate.error))}\n`);
|
|
644
|
+
return 10;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (flags.dryRun) {
|
|
648
|
+
(0, envelope_1.writeSuccess)({
|
|
649
|
+
dryRun: true,
|
|
650
|
+
method,
|
|
651
|
+
path,
|
|
652
|
+
profile: profile.name,
|
|
653
|
+
risk: catalogHit?.risk ?? "unknown",
|
|
654
|
+
}, { format: flags.format });
|
|
655
|
+
return 0;
|
|
656
|
+
}
|
|
657
|
+
const res = await (0, client_1.apiRequest)({
|
|
658
|
+
method,
|
|
659
|
+
path,
|
|
660
|
+
body,
|
|
661
|
+
profile,
|
|
662
|
+
idempotencyKey: method === "POST" ? env.ALPHAFOX_IDEMPOTENCY_KEY : undefined,
|
|
663
|
+
}, env);
|
|
664
|
+
if (res.status >= 400) {
|
|
665
|
+
(0, envelope_1.writeError)({
|
|
666
|
+
type: "http",
|
|
667
|
+
status: res.status,
|
|
668
|
+
message: extractErrorMessage(res.json, res.bodyText),
|
|
669
|
+
code: extractErrorCode(res.json),
|
|
670
|
+
}, { requestId: res.requestId, exitCode: res.status === 401 ? 77 : 1 });
|
|
671
|
+
}
|
|
672
|
+
(0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format });
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
675
|
+
async function cmdTyped(domain, args, flags, env) {
|
|
676
|
+
// alphafox trading traders list
|
|
677
|
+
// alphafox chats create --body ...
|
|
678
|
+
const resource = args[0];
|
|
679
|
+
const action = args[1];
|
|
680
|
+
if (!resource || !action) {
|
|
681
|
+
// try domain as operationId
|
|
682
|
+
const asOp = (0, operations_1.findCatalogOperation)(domain);
|
|
683
|
+
if (asOp) {
|
|
684
|
+
return await invokeOperation(asOp.operationId, args, flags, env);
|
|
685
|
+
}
|
|
686
|
+
(0, envelope_1.writeError)({
|
|
687
|
+
type: "usage",
|
|
688
|
+
message: `Unknown command "${domain}". Try alphafox schema or alphafox catalog.`,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
const candidates = operations_1.CATALOG_OPERATIONS.filter((o) => {
|
|
692
|
+
const parts = o.operationId.split(".");
|
|
693
|
+
return (parts[0]?.replace(/_/g, "-") === domain.replace(/_/g, "-") ||
|
|
694
|
+
o.operationId.startsWith(domain.replace(/-/g, "_")));
|
|
695
|
+
});
|
|
696
|
+
const op = candidates.find((o) => o.operationId.endsWith(`.${action}`) ||
|
|
697
|
+
o.operationId.includes(`.${resource}.`) &&
|
|
698
|
+
o.operationId.endsWith(action)) ||
|
|
699
|
+
(0, operations_1.findCatalogOperation)(`${domain}.${resource}.${action}`) ||
|
|
700
|
+
(0, operations_1.findCatalogOperation)(`${domain.replace(/-/g, "_")}.${resource.replace(/-/g, "_")}.${action}`);
|
|
701
|
+
if (!op) {
|
|
702
|
+
(0, envelope_1.writeError)({
|
|
703
|
+
type: "not_found",
|
|
704
|
+
message: `No catalog operation for ${domain} ${resource} ${action}`,
|
|
705
|
+
status: 404,
|
|
706
|
+
hint: "Run alphafox schema for operationIds",
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
return await invokeOperation(op.operationId, args.slice(2), flags, env);
|
|
710
|
+
}
|
|
711
|
+
async function invokeOperation(operationId, args, flags, env) {
|
|
712
|
+
const op = (0, operations_1.findCatalogOperation)(operationId);
|
|
713
|
+
if (!op) {
|
|
714
|
+
(0, envelope_1.writeError)({
|
|
715
|
+
type: "not_found",
|
|
716
|
+
message: `Unknown operationId: ${operationId}`,
|
|
717
|
+
status: 404,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
const gate = (0, confirmation_1.assertHighRiskConfirmation)({
|
|
721
|
+
risk: op.risk,
|
|
722
|
+
yes: flags.yes,
|
|
723
|
+
dryRun: flags.dryRun,
|
|
724
|
+
action: operationId,
|
|
725
|
+
});
|
|
726
|
+
if (!gate.allowed && gate.error) {
|
|
727
|
+
process.stderr.write(`${JSON.stringify((0, envelope_1.errorEnvelope)(gate.error))}\n`);
|
|
728
|
+
return 10;
|
|
729
|
+
}
|
|
730
|
+
const params = {};
|
|
731
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
732
|
+
const a = args[i];
|
|
733
|
+
if (a.startsWith("--") && a !== "--body") {
|
|
734
|
+
const key = a.slice(2);
|
|
735
|
+
params[key] = args[++i] ?? "";
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
let body;
|
|
739
|
+
const bodyIdx = args.indexOf("--body");
|
|
740
|
+
if (bodyIdx >= 0) {
|
|
741
|
+
body = JSON.parse(args[bodyIdx + 1] ?? "{}");
|
|
742
|
+
}
|
|
743
|
+
const path = (0, operations_1.resolveOperationPath)(op.path, params);
|
|
744
|
+
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
745
|
+
unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
|
|
746
|
+
});
|
|
747
|
+
if (flags.dryRun) {
|
|
748
|
+
(0, envelope_1.writeSuccess)({
|
|
749
|
+
dryRun: true,
|
|
750
|
+
operationId,
|
|
751
|
+
method: op.method,
|
|
752
|
+
path,
|
|
753
|
+
risk: op.risk,
|
|
754
|
+
profile: profile.name,
|
|
755
|
+
}, { format: flags.format });
|
|
756
|
+
return 0;
|
|
757
|
+
}
|
|
758
|
+
const res = await (0, client_1.apiRequest)({
|
|
759
|
+
method: op.method,
|
|
760
|
+
path,
|
|
761
|
+
body: op.method === "GET" ? undefined : body ?? {},
|
|
762
|
+
profile,
|
|
763
|
+
idempotencyKey: op.method === "POST" ? (0, node_crypto_1.randomUUID)() : undefined,
|
|
764
|
+
}, env);
|
|
765
|
+
if (res.status >= 400) {
|
|
766
|
+
(0, envelope_1.writeError)({
|
|
767
|
+
type: "http",
|
|
768
|
+
status: res.status,
|
|
769
|
+
message: extractErrorMessage(res.json, res.bodyText),
|
|
770
|
+
code: extractErrorCode(res.json),
|
|
771
|
+
}, { requestId: res.requestId });
|
|
772
|
+
}
|
|
773
|
+
(0, envelope_1.writeSuccess)(res.json, {
|
|
774
|
+
requestId: res.requestId,
|
|
775
|
+
format: flags.format,
|
|
776
|
+
meta: { operationId },
|
|
777
|
+
});
|
|
778
|
+
return 0;
|
|
779
|
+
}
|
|
780
|
+
function extractTokenPair(json) {
|
|
781
|
+
if (!json || typeof json !== "object") {
|
|
782
|
+
throw new Error("Invalid token response");
|
|
783
|
+
}
|
|
784
|
+
const o = json;
|
|
785
|
+
const access = o.access_token ?? o.accessToken;
|
|
786
|
+
const refresh = o.refresh_token ?? o.refreshToken;
|
|
787
|
+
if (typeof access !== "string" || typeof refresh !== "string") {
|
|
788
|
+
throw new Error("Token response missing access_token/refresh_token");
|
|
789
|
+
}
|
|
790
|
+
return {
|
|
791
|
+
access_token: access,
|
|
792
|
+
refresh_token: refresh,
|
|
793
|
+
expires_in: typeof o.expires_in === "number"
|
|
794
|
+
? o.expires_in
|
|
795
|
+
: typeof o.expiresIn === "number"
|
|
796
|
+
? o.expiresIn
|
|
797
|
+
: undefined,
|
|
798
|
+
scope: typeof o.scope === "string" ? o.scope : undefined,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function isPending(json) {
|
|
802
|
+
if (!json || typeof json !== "object")
|
|
803
|
+
return false;
|
|
804
|
+
const o = json;
|
|
805
|
+
return (o.error === "authorization_pending" ||
|
|
806
|
+
o.subtype === "authorization_pending" ||
|
|
807
|
+
o.error?.subtype ===
|
|
808
|
+
"authorization_pending");
|
|
809
|
+
}
|
|
810
|
+
function extractErrorMessage(json, fallback) {
|
|
811
|
+
if (json && typeof json === "object") {
|
|
812
|
+
const o = json;
|
|
813
|
+
if (typeof o.message === "string")
|
|
814
|
+
return o.message;
|
|
815
|
+
if (typeof o.error === "string")
|
|
816
|
+
return o.error;
|
|
817
|
+
if (o.error && typeof o.error === "object") {
|
|
818
|
+
const e = o.error;
|
|
819
|
+
if (typeof e.message === "string")
|
|
820
|
+
return e.message;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return fallback || "Request failed";
|
|
824
|
+
}
|
|
825
|
+
function extractErrorCode(json) {
|
|
826
|
+
if (json && typeof json === "object") {
|
|
827
|
+
const o = json;
|
|
828
|
+
if (typeof o.code === "string" || typeof o.code === "number")
|
|
829
|
+
return o.code;
|
|
830
|
+
if (o.error && typeof o.error === "object") {
|
|
831
|
+
const e = o.error;
|
|
832
|
+
if (typeof e.code === "string" || typeof e.code === "number")
|
|
833
|
+
return e.code;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
function pathMatches(template, actual) {
|
|
839
|
+
const t = template.split("/").filter(Boolean);
|
|
840
|
+
const a = actual.split("/").filter(Boolean);
|
|
841
|
+
if (t.length !== a.length)
|
|
842
|
+
return false;
|
|
843
|
+
for (let i = 0; i < t.length; i += 1) {
|
|
844
|
+
if (t[i].startsWith("{") && t[i].endsWith("}"))
|
|
845
|
+
continue;
|
|
846
|
+
if (t[i] !== a[i])
|
|
847
|
+
return false;
|
|
848
|
+
}
|
|
849
|
+
return true;
|
|
850
|
+
}
|
|
851
|
+
function sleep(ms) {
|
|
852
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
853
|
+
}
|
|
854
|
+
// silence unused import in some builds
|
|
855
|
+
void envelope_1.successEnvelope;
|