@alphafox/cli 0.1.5 → 0.3.2
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/README.md +42 -4
- package/dist/auth/browser-login.d.ts +30 -0
- package/dist/auth/browser-login.js +193 -0
- package/dist/auth/loopback-callback.d.ts +32 -0
- package/dist/auth/loopback-callback.js +175 -0
- package/dist/auth/open-browser.d.ts +15 -0
- package/dist/auth/open-browser.js +54 -0
- package/dist/auth/refresh.d.ts +27 -2
- package/dist/auth/refresh.js +52 -15
- package/dist/catalog/allowlist.d.ts +20 -4
- package/dist/catalog/allowlist.js +126 -25
- package/dist/catalog/command-tree.d.ts +35 -0
- package/dist/catalog/command-tree.js +119 -0
- package/dist/catalog/compatibility.d.ts +23 -0
- package/dist/catalog/compatibility.js +58 -0
- package/dist/catalog/generated/registry.json +6137 -0
- package/dist/catalog/generated/schemas.json +31036 -0
- package/dist/catalog/operations.d.ts +76 -3
- package/dist/catalog/operations.js +87 -213
- package/dist/catalog/validate-body.d.ts +22 -0
- package/dist/catalog/validate-body.js +98 -0
- package/dist/commands/request-body.d.ts +21 -0
- package/dist/commands/request-body.js +92 -0
- package/dist/commands/run.js +283 -162
- package/dist/config/profiles.js +3 -3
- package/dist/engine-backtest/errors.d.ts +18 -0
- package/dist/engine-backtest/errors.js +26 -0
- package/dist/engine-backtest/fetch-runtime.d.ts +38 -0
- package/dist/engine-backtest/fetch-runtime.js +170 -0
- package/dist/engine-backtest/native-import.d.ts +1 -0
- package/dist/engine-backtest/native-import.js +10 -0
- package/dist/engine-backtest/parse-args.d.ts +6 -0
- package/dist/engine-backtest/parse-args.js +281 -0
- package/dist/engine-backtest/persist.d.ts +32 -0
- package/dist/engine-backtest/persist.js +94 -0
- package/dist/engine-backtest/replay-timeframe.d.ts +16 -0
- package/dist/engine-backtest/replay-timeframe.js +48 -0
- package/dist/engine-backtest/resolve-packages.d.ts +27 -0
- package/dist/engine-backtest/resolve-packages.js +288 -0
- package/dist/engine-backtest/run-command.d.ts +38 -0
- package/dist/engine-backtest/run-command.js +540 -0
- package/dist/engine-backtest/types.d.ts +249 -0
- package/dist/engine-backtest/types.js +2 -0
- package/dist/envelope.d.ts +3 -0
- package/dist/envelope.js +43 -3
- package/dist/http/client.js +18 -15
- package/dist/index.d.ts +16 -5
- package/dist/index.js +47 -1
- package/dist/install/exec.d.ts +13 -0
- package/dist/install/exec.js +73 -0
- package/dist/install/package-root.d.ts +4 -0
- package/dist/install/package-root.js +59 -0
- package/dist/install/types.d.ts +69 -0
- package/dist/install/types.js +26 -0
- package/dist/install/wizard.d.ts +21 -0
- package/dist/install/wizard.js +332 -0
- package/dist/keychain/linux-secret-service.d.ts +11 -0
- package/dist/keychain/linux-secret-service.js +93 -0
- package/dist/keychain/store.d.ts +20 -1
- package/dist/keychain/store.js +94 -6
- package/dist/keychain/windows-credential.d.ts +13 -0
- package/dist/keychain/windows-credential.js +176 -0
- package/dist/safety/confirmation.d.ts +10 -3
- package/dist/safety/confirmation.js +27 -4
- package/dist/version.d.ts +2 -2
- package/dist/version.js +3 -2
- package/docs/.nojekyll +0 -0
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +156 -0
- package/docs/agents/triage-labels.md +18 -0
- package/docs/alphafox-cli-installation-guide.md +105 -0
- package/docs/e2e-staging.md +76 -6
- package/docs/favicon.svg +4 -0
- package/docs/index.html +748 -0
- package/docs/logo/alphafox-mark.svg +4 -0
- package/docs/logo/alphafox-wordmark-black-en.svg +17 -0
- package/docs/logo/alphafox-wordmark-white-en.svg +16 -0
- package/docs/release-supply-chain.md +107 -26
- package/package.json +10 -3
- package/skills/account/SKILL.md +8 -6
- package/skills/admin/SKILL.md +9 -4
- package/skills/alphafox-shared/SKILL.md +44 -14
- package/skills/auth/SKILL.md +21 -6
- package/skills/engine-backtest/SKILL.md +71 -0
- package/skills/exchange/SKILL.md +10 -3
- package/skills/market/SKILL.md +9 -4
- package/skills/notification/SKILL.md +8 -3
- package/skills/strategy/SKILL.md +23 -9
- package/skills/trading/SKILL.md +18 -5
- package/vendor/backtest-runner/NOTICE.md +1 -0
- package/vendor/backtest-runner/README.md +97 -0
- package/vendor/backtest-runner/index.d.ts +423 -0
- package/vendor/backtest-runner/index.mjs +59 -0
- package/vendor/backtest-runner/lib/abortable.mjs +23 -0
- package/vendor/backtest-runner/lib/cache.mjs +159 -0
- package/vendor/backtest-runner/lib/coverage.mjs +238 -0
- package/vendor/backtest-runner/lib/encode.mjs +28 -0
- package/vendor/backtest-runner/lib/exchanges.mjs +143 -0
- package/vendor/backtest-runner/lib/proxy.mjs +78 -0
- package/vendor/backtest-runner/lib/scenario.mjs +66 -0
- package/vendor/backtest-runner/lib/series.mjs +370 -0
- package/vendor/backtest-runner/lib/tape-loader.mjs +614 -0
- package/vendor/backtest-runner/lib/timeframes.mjs +55 -0
- package/vendor/backtest-runner/package.json +13 -0
package/dist/commands/run.js
CHANGED
|
@@ -1,49 +1,23 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.parseGlobalFlags = parseGlobalFlags;
|
|
37
4
|
exports.runCli = runCli;
|
|
38
5
|
const node_crypto_1 = require("node:crypto");
|
|
39
6
|
const operations_1 = require("../catalog/operations");
|
|
7
|
+
const command_tree_1 = require("../catalog/command-tree");
|
|
40
8
|
const allowlist_1 = require("../catalog/allowlist");
|
|
41
9
|
const profiles_1 = require("../config/profiles");
|
|
42
10
|
const envelope_1 = require("../envelope");
|
|
11
|
+
const browser_login_1 = require("../auth/browser-login");
|
|
43
12
|
const client_1 = require("../http/client");
|
|
44
13
|
const store_1 = require("../keychain/store");
|
|
45
14
|
const confirmation_1 = require("../safety/confirmation");
|
|
46
15
|
const version_1 = require("../version");
|
|
16
|
+
const run_command_1 = require("../engine-backtest/run-command");
|
|
17
|
+
const validate_body_1 = require("../catalog/validate-body");
|
|
18
|
+
const types_1 = require("../install/types");
|
|
19
|
+
const wizard_1 = require("../install/wizard");
|
|
20
|
+
const request_body_1 = require("./request-body");
|
|
47
21
|
function parseGlobalFlags(argv) {
|
|
48
22
|
const flags = {
|
|
49
23
|
format: "json",
|
|
@@ -94,6 +68,14 @@ function parseGlobalFlags(argv) {
|
|
|
94
68
|
return { flags, rest };
|
|
95
69
|
}
|
|
96
70
|
async function runCli(argv, env = process.env) {
|
|
71
|
+
if (argv.some((a) => a === "--token" || a.startsWith("--token="))) {
|
|
72
|
+
(0, envelope_1.writeError)({
|
|
73
|
+
type: "usage",
|
|
74
|
+
subtype: "token_argv_forbidden",
|
|
75
|
+
message: "Refusing --token on argv. Store credentials in the OS keychain via alphafox auth login.",
|
|
76
|
+
hint: "Automation tokens are not supported in v1. Do not copy refresh tokens into CI.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
97
79
|
const { flags, rest } = parseGlobalFlags(argv);
|
|
98
80
|
const [cmd, ...args] = rest;
|
|
99
81
|
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
@@ -102,22 +84,30 @@ async function runCli(argv, env = process.env) {
|
|
|
102
84
|
usage: [
|
|
103
85
|
"alphafox version",
|
|
104
86
|
"alphafox doctor",
|
|
87
|
+
"alphafox install [--no-auth|--dry-run]",
|
|
105
88
|
"alphafox auth login [--no-wait|--device-code CODE|--browser]",
|
|
106
89
|
"alphafox auth status [--verify]",
|
|
107
90
|
"alphafox auth logout",
|
|
108
91
|
"alphafox whoami",
|
|
109
92
|
"alphafox profile list|use <name>",
|
|
110
93
|
"alphafox schema [operationId]",
|
|
111
|
-
"alphafox
|
|
94
|
+
"alphafox catalog",
|
|
95
|
+
"alphafox api METHOD PATH [--body JSON|--config @file]",
|
|
96
|
+
"alphafox engine-backtest run --experiment <uuid> --definition <id> --config @file --exchange <id> --range FROM..TO --initial-equity N",
|
|
112
97
|
"alphafox <domain> <resource> <action> [flags]",
|
|
113
98
|
],
|
|
114
|
-
}, { format: flags.format });
|
|
99
|
+
}, { format: flags.format, jq: flags.jq });
|
|
115
100
|
return 0;
|
|
116
101
|
}
|
|
117
102
|
try {
|
|
103
|
+
if (cmd !== "version" && cmd !== "install") {
|
|
104
|
+
assertCatalogCompatible();
|
|
105
|
+
}
|
|
118
106
|
switch (cmd) {
|
|
119
107
|
case "version":
|
|
120
108
|
return cmdVersion(flags);
|
|
109
|
+
case "install":
|
|
110
|
+
return await cmdInstall(args, flags, env);
|
|
121
111
|
case "doctor":
|
|
122
112
|
return cmdDoctor(flags, env);
|
|
123
113
|
case "whoami":
|
|
@@ -132,6 +122,19 @@ async function runCli(argv, env = process.env) {
|
|
|
132
122
|
return await cmdApi(args, flags, env);
|
|
133
123
|
case "catalog":
|
|
134
124
|
return cmdCatalog(flags);
|
|
125
|
+
case "engine-backtest": {
|
|
126
|
+
const sub = args[0];
|
|
127
|
+
if (!sub ||
|
|
128
|
+
sub === "run" ||
|
|
129
|
+
sub === "help" ||
|
|
130
|
+
sub === "--help" ||
|
|
131
|
+
sub === "-h") {
|
|
132
|
+
return await (0, run_command_1.cmdEngineBacktest)(args, flags, env);
|
|
133
|
+
}
|
|
134
|
+
// Hyphen built-in only owns `run`. Underscore/hyphen catalog CRUD
|
|
135
|
+
// (engine_backtest.experiments.*) still goes through the typed tree.
|
|
136
|
+
return await cmdTyped(cmd, args, flags, env);
|
|
137
|
+
}
|
|
135
138
|
default:
|
|
136
139
|
return await cmdTyped(cmd, args, flags, env);
|
|
137
140
|
}
|
|
@@ -154,18 +157,76 @@ async function runCli(argv, env = process.env) {
|
|
|
154
157
|
}, { exitCode: status === 401 || status === 403 ? 77 : 1 });
|
|
155
158
|
}
|
|
156
159
|
}
|
|
160
|
+
function assertCatalogCompatible() {
|
|
161
|
+
const result = (0, operations_1.checkGeneratedCatalogCompatibility)(version_1.CLI_VERSION);
|
|
162
|
+
if (result.ok)
|
|
163
|
+
return;
|
|
164
|
+
(0, envelope_1.writeError)({
|
|
165
|
+
type: "compatibility",
|
|
166
|
+
subtype: result.code,
|
|
167
|
+
message: result.message,
|
|
168
|
+
hint: "Upgrade or pin @alphafox/cli to the catalog minCliVersion/maxCliVersion range.",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async function cmdInstall(args, flags, env) {
|
|
172
|
+
const parsed = (0, wizard_1.parseInstallArgs)(args);
|
|
173
|
+
if (parsed.unknown.length > 0) {
|
|
174
|
+
(0, envelope_1.writeError)({
|
|
175
|
+
type: "usage",
|
|
176
|
+
subtype: "unknown_install_flag",
|
|
177
|
+
message: `Unknown install flag: ${parsed.unknown.join(" ")}`,
|
|
178
|
+
hint: "Usage: alphafox install [--no-auth] [--dry-run]",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (parsed.help) {
|
|
182
|
+
(0, envelope_1.writeSuccess)((0, wizard_1.installHelpData)(), { format: flags.format, jq: flags.jq });
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const data = await (0, wizard_1.runInstallWizard)({
|
|
187
|
+
format: flags.format,
|
|
188
|
+
yes: flags.yes,
|
|
189
|
+
dryRun: flags.dryRun,
|
|
190
|
+
noInput: flags.noInput,
|
|
191
|
+
jq: flags.jq,
|
|
192
|
+
noAuth: parsed.noAuth,
|
|
193
|
+
help: false,
|
|
194
|
+
}, env);
|
|
195
|
+
(0, envelope_1.writeSuccess)(data, { format: flags.format, jq: flags.jq });
|
|
196
|
+
return data.auth.action === "failed" ? 1 : 0;
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
if ((0, types_1.isInstallError)(err)) {
|
|
200
|
+
(0, envelope_1.writeError)({
|
|
201
|
+
type: err.type,
|
|
202
|
+
subtype: err.subtype,
|
|
203
|
+
message: err.message,
|
|
204
|
+
hint: err.hint,
|
|
205
|
+
details: err.details,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
157
211
|
function cmdVersion(flags) {
|
|
212
|
+
const compatibility = (0, operations_1.checkGeneratedCatalogCompatibility)(version_1.CLI_VERSION);
|
|
158
213
|
(0, envelope_1.writeSuccess)({
|
|
159
214
|
name: version_1.CLI_NAME,
|
|
160
215
|
package: version_1.CLI_PACKAGE,
|
|
161
216
|
version: version_1.CLI_VERSION,
|
|
162
217
|
contractVersion: version_1.CLI_CONTRACT_VERSION,
|
|
163
218
|
catalogVersion: operations_1.CATALOG_VERSION,
|
|
219
|
+
registryVersion: operations_1.COMPATIBILITY_RANGE.registryVersion,
|
|
220
|
+
minCliVersion: operations_1.COMPATIBILITY_RANGE.minCliVersion,
|
|
221
|
+
maxCliVersion: operations_1.COMPATIBILITY_RANGE.maxCliVersion,
|
|
222
|
+
openapi: operations_1.COMPATIBILITY_RANGE.openapi,
|
|
223
|
+
contractsSha: operations_1.CATALOG_SOURCE.contractsSha,
|
|
224
|
+
compatibility,
|
|
164
225
|
node: process.version,
|
|
165
226
|
platform: process.platform,
|
|
166
227
|
arch: process.arch,
|
|
167
|
-
}, { format: flags.format });
|
|
168
|
-
return 0;
|
|
228
|
+
}, { format: flags.format, jq: flags.jq });
|
|
229
|
+
return compatibility.ok ? 0 : 1;
|
|
169
230
|
}
|
|
170
231
|
function cmdDoctor(flags, env) {
|
|
171
232
|
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
@@ -174,6 +235,7 @@ function cmdDoctor(flags, env) {
|
|
|
174
235
|
// Avoid macOS `security` stderr noise when no item exists; file keychain is fine for doctor.
|
|
175
236
|
const doctorEnv = { ...env, ALPHAFOX_FORCE_FILE_KEYCHAIN: env.ALPHAFOX_FORCE_FILE_KEYCHAIN ?? "1" };
|
|
176
237
|
const tokens = (0, store_1.loadTokens)(profile.name, doctorEnv);
|
|
238
|
+
const probe = (0, store_1.probeOsKeychain)(env);
|
|
177
239
|
const checks = [
|
|
178
240
|
{
|
|
179
241
|
name: "node",
|
|
@@ -202,6 +264,13 @@ function cmdDoctor(flags, env) {
|
|
|
202
264
|
? `token present (fp=${(0, store_1.tokenFingerprint)(tokens.accessToken)})`
|
|
203
265
|
: "no tokens stored",
|
|
204
266
|
},
|
|
267
|
+
{
|
|
268
|
+
name: "osKeychain",
|
|
269
|
+
ok: true,
|
|
270
|
+
detail: probe.available
|
|
271
|
+
? probe.kind
|
|
272
|
+
: `${probe.kind} unavailable; file fallback (0600) if tokens are saved`,
|
|
273
|
+
},
|
|
205
274
|
{
|
|
206
275
|
name: "configHasNoTokens",
|
|
207
276
|
ok: true,
|
|
@@ -212,9 +281,14 @@ function cmdDoctor(flags, env) {
|
|
|
212
281
|
ok: true,
|
|
213
282
|
detail: "v1 deferred (interactive login only)",
|
|
214
283
|
},
|
|
284
|
+
{
|
|
285
|
+
name: "catalogCompatibility",
|
|
286
|
+
ok: (0, operations_1.checkGeneratedCatalogCompatibility)(version_1.CLI_VERSION).ok,
|
|
287
|
+
detail: `${version_1.CLI_VERSION} in [${operations_1.COMPATIBILITY_RANGE.minCliVersion}, ${operations_1.COMPATIBILITY_RANGE.maxCliVersion}] contract ${operations_1.COMPATIBILITY_RANGE.contractVersion}`,
|
|
288
|
+
},
|
|
215
289
|
];
|
|
216
290
|
const ok = checks.every((c) => c.ok);
|
|
217
|
-
(0, envelope_1.writeSuccess)({ ok, profile: profile.name, checks }, { format: flags.format });
|
|
291
|
+
(0, envelope_1.writeSuccess)({ ok, profile: profile.name, checks }, { format: flags.format, jq: flags.jq });
|
|
218
292
|
return ok ? 0 : 1;
|
|
219
293
|
}
|
|
220
294
|
async function cmdWhoami(flags, env) {
|
|
@@ -236,7 +310,7 @@ async function cmdWhoami(flags, env) {
|
|
|
236
310
|
code: extractErrorCode(res.json),
|
|
237
311
|
}, { requestId: res.requestId, exitCode: res.status === 401 ? 77 : 1 });
|
|
238
312
|
}
|
|
239
|
-
(0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format });
|
|
313
|
+
(0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format, jq: flags.jq });
|
|
240
314
|
return 0;
|
|
241
315
|
}
|
|
242
316
|
async function cmdAuth(args, flags, env) {
|
|
@@ -252,7 +326,7 @@ async function cmdAuth(args, flags, env) {
|
|
|
252
326
|
authenticated: false,
|
|
253
327
|
profile: profile.name,
|
|
254
328
|
verified: false,
|
|
255
|
-
}, { format: flags.format });
|
|
329
|
+
}, { format: flags.format, jq: flags.jq });
|
|
256
330
|
return 0;
|
|
257
331
|
}
|
|
258
332
|
let verified = null;
|
|
@@ -278,14 +352,15 @@ async function cmdAuth(args, flags, env) {
|
|
|
278
352
|
expiresAt: tokens.expiresAt,
|
|
279
353
|
verified,
|
|
280
354
|
whoami,
|
|
281
|
-
}, { format: flags.format });
|
|
355
|
+
}, { format: flags.format, jq: flags.jq });
|
|
282
356
|
return 0;
|
|
283
357
|
}
|
|
284
358
|
if (sub === "logout") {
|
|
285
359
|
const tokens = (0, store_1.loadTokens)(profile.name, env);
|
|
360
|
+
let remoteRevoke = "skipped";
|
|
286
361
|
if (tokens?.refreshToken) {
|
|
287
362
|
try {
|
|
288
|
-
await (0, client_1.apiRequest)({
|
|
363
|
+
const res = await (0, client_1.apiRequest)({
|
|
289
364
|
method: "POST",
|
|
290
365
|
path: "/api/auth/oauth/revoke",
|
|
291
366
|
profile,
|
|
@@ -295,14 +370,26 @@ async function cmdAuth(args, flags, env) {
|
|
|
295
370
|
},
|
|
296
371
|
skipAuth: false,
|
|
297
372
|
}, env);
|
|
373
|
+
remoteRevoke =
|
|
374
|
+
res.status >= 200 && res.status < 300 ? "ok" : "failed";
|
|
298
375
|
}
|
|
299
376
|
catch {
|
|
300
|
-
|
|
377
|
+
remoteRevoke = "failed";
|
|
301
378
|
}
|
|
302
379
|
}
|
|
303
380
|
(0, store_1.deleteTokens)(profile.name, env);
|
|
304
|
-
|
|
305
|
-
|
|
381
|
+
const localCleared = true;
|
|
382
|
+
const fullyLoggedOut = remoteRevoke !== "failed";
|
|
383
|
+
// Never claim a clean remote logout when revoke failed while an RT was present.
|
|
384
|
+
(0, envelope_1.writeSuccess)({
|
|
385
|
+
localCleared,
|
|
386
|
+
remoteRevoke,
|
|
387
|
+
fullyLoggedOut,
|
|
388
|
+
// retained for compatibility; false when remote revoke failed
|
|
389
|
+
loggedOut: fullyLoggedOut,
|
|
390
|
+
profile: profile.name,
|
|
391
|
+
}, { format: flags.format, jq: flags.jq });
|
|
392
|
+
return fullyLoggedOut ? 0 : 1;
|
|
306
393
|
}
|
|
307
394
|
if (sub === "login") {
|
|
308
395
|
return await cmdAuthLogin(args.slice(1), flags, env, profile);
|
|
@@ -370,7 +457,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
|
|
|
370
457
|
profile: profile.name,
|
|
371
458
|
accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
|
|
372
459
|
expiresIn: tokens.expires_in,
|
|
373
|
-
}, { format: flags.format, requestId: res.requestId });
|
|
460
|
+
}, { format: flags.format, jq: flags.jq, requestId: res.requestId });
|
|
374
461
|
return 0;
|
|
375
462
|
}
|
|
376
463
|
if (deviceCode) {
|
|
@@ -389,7 +476,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
|
|
|
389
476
|
(0, envelope_1.writeSuccess)({
|
|
390
477
|
status: "authorization_pending",
|
|
391
478
|
profile: profile.name,
|
|
392
|
-
}, { format: flags.format, requestId: res.requestId });
|
|
479
|
+
}, { format: flags.format, jq: flags.jq, requestId: res.requestId });
|
|
393
480
|
return 0;
|
|
394
481
|
}
|
|
395
482
|
if (res.status >= 400) {
|
|
@@ -415,7 +502,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
|
|
|
415
502
|
profile: profile.name,
|
|
416
503
|
accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
|
|
417
504
|
expiresIn: tokens.expires_in,
|
|
418
|
-
}, { format: flags.format, requestId: res.requestId });
|
|
505
|
+
}, { format: flags.format, jq: flags.jq, requestId: res.requestId });
|
|
419
506
|
return 0;
|
|
420
507
|
}
|
|
421
508
|
// Start device flow (default headless path)
|
|
@@ -448,7 +535,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
|
|
|
448
535
|
expires_in: data.expires_in,
|
|
449
536
|
interval: data.interval,
|
|
450
537
|
next: "Open verification_uri, approve, then: alphafox auth login --device-code <device_code>",
|
|
451
|
-
}, { format: flags.format, requestId: res.requestId });
|
|
538
|
+
}, { format: flags.format, jq: flags.jq, requestId: res.requestId });
|
|
452
539
|
if (noWait) {
|
|
453
540
|
return 0;
|
|
454
541
|
}
|
|
@@ -494,7 +581,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
|
|
|
494
581
|
authenticated: true,
|
|
495
582
|
profile: profile.name,
|
|
496
583
|
accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
|
|
497
|
-
}, { format: flags.format, requestId: poll.requestId });
|
|
584
|
+
}, { format: flags.format, jq: flags.jq, requestId: poll.requestId });
|
|
498
585
|
return 0;
|
|
499
586
|
}
|
|
500
587
|
(0, envelope_1.writeError)({
|
|
@@ -503,36 +590,34 @@ async function cmdAuthLogin(args, flags, env, profile) {
|
|
|
503
590
|
subtype: "expired_token",
|
|
504
591
|
});
|
|
505
592
|
}
|
|
506
|
-
// Browser Authorization Code + PKCE (RFC
|
|
593
|
+
// Browser Authorization Code + PKCE with loopback callback (RFC 8252).
|
|
507
594
|
if (browser || args.includes("--pkce")) {
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
595
|
+
const result = await (0, browser_login_1.runBrowserPkceLogin)({
|
|
596
|
+
profile,
|
|
597
|
+
env,
|
|
598
|
+
timeoutMs: (0, browser_login_1.browserLoginTimeoutMs)(env),
|
|
599
|
+
openBrowser: (0, browser_login_1.resolveOpenBrowser)(env),
|
|
600
|
+
});
|
|
601
|
+
if (result.status !== "authenticated") {
|
|
602
|
+
(0, envelope_1.writeError)({
|
|
603
|
+
type: "auth",
|
|
604
|
+
subtype: result.reason,
|
|
605
|
+
message: result.message,
|
|
606
|
+
details: result.authorizeUrl
|
|
607
|
+
? { authorizeUrl: result.authorizeUrl }
|
|
608
|
+
: undefined,
|
|
609
|
+
hint: result.authorizeUrl
|
|
610
|
+
? "Copy authorizeUrl into a browser, or use Device Flow: alphafox auth login --no-wait"
|
|
611
|
+
: undefined,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
523
614
|
(0, envelope_1.writeSuccess)({
|
|
615
|
+
authenticated: true,
|
|
524
616
|
flow: "authorization_code_pkce",
|
|
525
617
|
profile: profile.name,
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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 });
|
|
618
|
+
accessTokenFingerprint: result.accessTokenFingerprint,
|
|
619
|
+
expiresIn: result.expiresIn,
|
|
620
|
+
}, { format: flags.format, jq: flags.jq, requestId: result.requestId });
|
|
536
621
|
return 0;
|
|
537
622
|
}
|
|
538
623
|
(0, envelope_1.writeError)({
|
|
@@ -547,7 +632,7 @@ function cmdProfile(args, flags, env) {
|
|
|
547
632
|
(0, envelope_1.writeSuccess)({
|
|
548
633
|
activeProfile: file.activeProfile,
|
|
549
634
|
profiles: ["production", "staging", "local"].map((name) => (0, profiles_1.resolveProfile)(name, env)),
|
|
550
|
-
}, { format: flags.format });
|
|
635
|
+
}, { format: flags.format, jq: flags.jq });
|
|
551
636
|
return 0;
|
|
552
637
|
}
|
|
553
638
|
if (sub === "use") {
|
|
@@ -560,7 +645,7 @@ function cmdProfile(args, flags, env) {
|
|
|
560
645
|
}
|
|
561
646
|
const file = (0, profiles_1.loadConfigFile)(env);
|
|
562
647
|
(0, profiles_1.saveConfigFile)({ ...file, activeProfile: name }, env);
|
|
563
|
-
(0, envelope_1.writeSuccess)({ activeProfile: name }, { format: flags.format });
|
|
648
|
+
(0, envelope_1.writeSuccess)({ activeProfile: name }, { format: flags.format, jq: flags.jq });
|
|
564
649
|
return 0;
|
|
565
650
|
}
|
|
566
651
|
(0, envelope_1.writeError)({ type: "usage", message: "Usage: alphafox profile list|use" });
|
|
@@ -570,12 +655,17 @@ function cmdSchema(args, flags) {
|
|
|
570
655
|
if (!operationId) {
|
|
571
656
|
(0, envelope_1.writeSuccess)({
|
|
572
657
|
contractVersion: operations_1.CATALOG_VERSION,
|
|
658
|
+
registryVersion: operations_1.COMPATIBILITY_RANGE.registryVersion,
|
|
659
|
+
openapi: operations_1.COMPATIBILITY_RANGE.openapi,
|
|
660
|
+
minCliVersion: operations_1.COMPATIBILITY_RANGE.minCliVersion,
|
|
661
|
+
maxCliVersion: operations_1.COMPATIBILITY_RANGE.maxCliVersion,
|
|
573
662
|
operations: operations_1.CATALOG_OPERATIONS.map((o) => o.operationId),
|
|
574
|
-
}, { format: flags.format });
|
|
663
|
+
}, { format: flags.format, jq: flags.jq });
|
|
575
664
|
return 0;
|
|
576
665
|
}
|
|
577
666
|
const op = (0, operations_1.findCatalogOperation)(operationId);
|
|
578
|
-
|
|
667
|
+
const schema = (0, operations_1.getOperationSchemaDocument)(operationId);
|
|
668
|
+
if (!op || !schema) {
|
|
579
669
|
(0, envelope_1.writeError)({
|
|
580
670
|
type: "not_found",
|
|
581
671
|
message: `Unknown operationId: ${operationId}`,
|
|
@@ -583,26 +673,13 @@ function cmdSchema(args, flags) {
|
|
|
583
673
|
});
|
|
584
674
|
}
|
|
585
675
|
(0, envelope_1.writeSuccess)({
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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 });
|
|
676
|
+
...schema,
|
|
677
|
+
examples: (0, command_tree_1.typedCommandExample)(op),
|
|
678
|
+
}, { format: flags.format, jq: flags.jq });
|
|
602
679
|
return 0;
|
|
603
680
|
}
|
|
604
681
|
function cmdCatalog(flags) {
|
|
605
|
-
(0, envelope_1.writeSuccess)((0, operations_1.buildCapabilityManifest)(), { format: flags.format });
|
|
682
|
+
(0, envelope_1.writeSuccess)((0, operations_1.buildCapabilityManifest)(), { format: flags.format, jq: flags.jq });
|
|
606
683
|
return 0;
|
|
607
684
|
}
|
|
608
685
|
async function cmdApi(args, flags, env) {
|
|
@@ -611,7 +688,7 @@ async function cmdApi(args, flags, env) {
|
|
|
611
688
|
if (!method || !path) {
|
|
612
689
|
(0, envelope_1.writeError)({
|
|
613
690
|
type: "usage",
|
|
614
|
-
message: "Usage: alphafox api METHOD PATH [--body JSON]",
|
|
691
|
+
message: "Usage: alphafox api METHOD PATH [--body JSON|--config @file]",
|
|
615
692
|
});
|
|
616
693
|
}
|
|
617
694
|
if ((0, allowlist_1.isInternalDisallowedPath)(path) || !(0, allowlist_1.isFacadeAllowlistedPath)(path)) {
|
|
@@ -623,26 +700,44 @@ async function cmdApi(args, flags, env) {
|
|
|
623
700
|
}, { exitCode: 77 });
|
|
624
701
|
}
|
|
625
702
|
let body;
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
703
|
+
try {
|
|
704
|
+
body = (0, request_body_1.parseRequestBodyFlags)(args).body;
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
if ((0, request_body_1.isRequestBodyError)(err)) {
|
|
708
|
+
(0, envelope_1.writeError)({
|
|
709
|
+
type: err.type,
|
|
710
|
+
subtype: err.subtype,
|
|
711
|
+
message: err.message,
|
|
712
|
+
hint: err.hint,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
throw err;
|
|
629
716
|
}
|
|
630
717
|
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
631
718
|
unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
|
|
632
719
|
});
|
|
633
|
-
// Infer risk from catalog
|
|
634
|
-
const catalogHit = operations_1.
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
720
|
+
// Infer risk from catalog; uncataloged mutations are treated as high-risk.
|
|
721
|
+
const catalogHit = (0, operations_1.findCatalogOperationByRoute)(method, (0, allowlist_1.normalizeApiPath)(path));
|
|
722
|
+
const validated = (0, validate_body_1.validateCatalogWriteBody)({
|
|
723
|
+
method,
|
|
724
|
+
operationId: catalogHit?.operationId,
|
|
725
|
+
body,
|
|
726
|
+
});
|
|
727
|
+
if (!validated.ok) {
|
|
728
|
+
(0, envelope_1.writeError)(validated.error);
|
|
729
|
+
}
|
|
730
|
+
body = validated.body;
|
|
731
|
+
const risk = (0, confirmation_1.inferRawApiRisk)(method, catalogHit?.risk);
|
|
732
|
+
const gate = (0, confirmation_1.assertHighRiskConfirmation)({
|
|
733
|
+
risk,
|
|
734
|
+
yes: flags.yes,
|
|
735
|
+
dryRun: flags.dryRun,
|
|
736
|
+
action: `api ${method} ${path}`,
|
|
737
|
+
});
|
|
738
|
+
if (!gate.allowed && gate.error) {
|
|
739
|
+
process.stderr.write(`${JSON.stringify((0, envelope_1.errorEnvelope)(gate.error))}\n`);
|
|
740
|
+
return 10;
|
|
646
741
|
}
|
|
647
742
|
if (flags.dryRun) {
|
|
648
743
|
(0, envelope_1.writeSuccess)({
|
|
@@ -650,8 +745,10 @@ async function cmdApi(args, flags, env) {
|
|
|
650
745
|
method,
|
|
651
746
|
path,
|
|
652
747
|
profile: profile.name,
|
|
653
|
-
risk
|
|
654
|
-
|
|
748
|
+
risk,
|
|
749
|
+
operationId: catalogHit?.operationId,
|
|
750
|
+
body,
|
|
751
|
+
}, { format: flags.format, jq: flags.jq });
|
|
655
752
|
return 0;
|
|
656
753
|
}
|
|
657
754
|
const res = await (0, client_1.apiRequest)({
|
|
@@ -669,44 +766,47 @@ async function cmdApi(args, flags, env) {
|
|
|
669
766
|
code: extractErrorCode(res.json),
|
|
670
767
|
}, { requestId: res.requestId, exitCode: res.status === 401 ? 77 : 1 });
|
|
671
768
|
}
|
|
672
|
-
(0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format });
|
|
769
|
+
(0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format, jq: flags.jq });
|
|
673
770
|
return 0;
|
|
674
771
|
}
|
|
675
772
|
async function cmdTyped(domain, args, flags, env) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
773
|
+
const resolved = (0, command_tree_1.resolveTypedCommand)([domain, ...args]);
|
|
774
|
+
if (resolved.kind === "help") {
|
|
775
|
+
(0, envelope_1.writeSuccess)({
|
|
776
|
+
prefix: resolved.prefix,
|
|
777
|
+
operations: resolved.operations.map((op) => ({
|
|
778
|
+
operationId: op.operationId,
|
|
779
|
+
method: op.method,
|
|
780
|
+
path: op.path,
|
|
781
|
+
risk: op.risk,
|
|
782
|
+
scopes: op.scopes,
|
|
783
|
+
stream: Boolean(op.stream),
|
|
784
|
+
file: Boolean(op.file),
|
|
785
|
+
pagination: Boolean(op.pagination),
|
|
786
|
+
examples: (0, command_tree_1.typedCommandExample)(op),
|
|
787
|
+
})),
|
|
788
|
+
}, { format: flags.format, jq: flags.jq });
|
|
789
|
+
return 0;
|
|
790
|
+
}
|
|
791
|
+
if (resolved.kind === "ambiguous") {
|
|
686
792
|
(0, envelope_1.writeError)({
|
|
687
793
|
type: "usage",
|
|
688
|
-
message: `
|
|
794
|
+
message: `Ambiguous command "${resolved.prefix}"`,
|
|
795
|
+
hint: `Matches: ${resolved.candidates.join(", ")}`,
|
|
689
796
|
});
|
|
690
797
|
}
|
|
691
|
-
|
|
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) {
|
|
798
|
+
if (resolved.kind === "missing") {
|
|
702
799
|
(0, envelope_1.writeError)({
|
|
703
800
|
type: "not_found",
|
|
704
|
-
message: `No catalog operation for ${
|
|
801
|
+
message: `No catalog operation for ${resolved.prefix || domain}`,
|
|
705
802
|
status: 404,
|
|
706
803
|
hint: "Run alphafox schema for operationIds",
|
|
707
804
|
});
|
|
708
805
|
}
|
|
709
|
-
|
|
806
|
+
if (resolved.help) {
|
|
807
|
+
return cmdSchema([resolved.operation.operationId], flags);
|
|
808
|
+
}
|
|
809
|
+
return await invokeOperation(resolved.operation.operationId, resolved.flagArgs, flags, env);
|
|
710
810
|
}
|
|
711
811
|
async function invokeOperation(operationId, args, flags, env) {
|
|
712
812
|
const op = (0, operations_1.findCatalogOperation)(operationId);
|
|
@@ -728,19 +828,52 @@ async function invokeOperation(operationId, args, flags, env) {
|
|
|
728
828
|
return 10;
|
|
729
829
|
}
|
|
730
830
|
const params = {};
|
|
831
|
+
const extra = {};
|
|
832
|
+
const pathParamNames = new Set((0, operations_1.getOperationSchemaDocument)(operationId)?.request.pathParamNames ??
|
|
833
|
+
(0, operations_1.extractPathParamNames)(op.path));
|
|
731
834
|
for (let i = 0; i < args.length; i += 1) {
|
|
732
835
|
const a = args[i];
|
|
733
|
-
if (a.startsWith("--") && a !== "--body") {
|
|
836
|
+
if (a.startsWith("--") && a !== "--body" && a !== "--config") {
|
|
734
837
|
const key = a.slice(2);
|
|
735
|
-
|
|
838
|
+
const value = args[++i] ?? "";
|
|
839
|
+
if (pathParamNames.has(key)) {
|
|
840
|
+
params[key] = value;
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
extra[key] = value;
|
|
844
|
+
}
|
|
736
845
|
}
|
|
737
846
|
}
|
|
738
847
|
let body;
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
848
|
+
try {
|
|
849
|
+
body = (0, request_body_1.parseRequestBodyFlags)(args).body;
|
|
850
|
+
}
|
|
851
|
+
catch (err) {
|
|
852
|
+
if ((0, request_body_1.isRequestBodyError)(err)) {
|
|
853
|
+
(0, envelope_1.writeError)({
|
|
854
|
+
type: err.type,
|
|
855
|
+
subtype: err.subtype,
|
|
856
|
+
message: err.message,
|
|
857
|
+
hint: err.hint,
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
throw err;
|
|
861
|
+
}
|
|
862
|
+
const validated = (0, validate_body_1.validateCatalogWriteBody)({
|
|
863
|
+
method: op.method,
|
|
864
|
+
operationId,
|
|
865
|
+
body,
|
|
866
|
+
});
|
|
867
|
+
if (!validated.ok) {
|
|
868
|
+
(0, envelope_1.writeError)(validated.error);
|
|
869
|
+
}
|
|
870
|
+
body = validated.body;
|
|
871
|
+
let path = (0, operations_1.resolveOperationPath)(op.path, params);
|
|
872
|
+
if ((op.method === "GET" || op.method === "HEAD") &&
|
|
873
|
+
Object.keys(extra).length > 0) {
|
|
874
|
+
const query = new URLSearchParams(extra).toString();
|
|
875
|
+
path = `${path}?${query}`;
|
|
742
876
|
}
|
|
743
|
-
const path = (0, operations_1.resolveOperationPath)(op.path, params);
|
|
744
877
|
const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
|
|
745
878
|
unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
|
|
746
879
|
});
|
|
@@ -752,7 +885,8 @@ async function invokeOperation(operationId, args, flags, env) {
|
|
|
752
885
|
path,
|
|
753
886
|
risk: op.risk,
|
|
754
887
|
profile: profile.name,
|
|
755
|
-
|
|
888
|
+
body: op.method === "GET" || op.method === "HEAD" ? undefined : body,
|
|
889
|
+
}, { format: flags.format, jq: flags.jq });
|
|
756
890
|
return 0;
|
|
757
891
|
}
|
|
758
892
|
const res = await (0, client_1.apiRequest)({
|
|
@@ -772,7 +906,7 @@ async function invokeOperation(operationId, args, flags, env) {
|
|
|
772
906
|
}
|
|
773
907
|
(0, envelope_1.writeSuccess)(res.json, {
|
|
774
908
|
requestId: res.requestId,
|
|
775
|
-
format: flags.format,
|
|
909
|
+
format: flags.format, jq: flags.jq,
|
|
776
910
|
meta: { operationId },
|
|
777
911
|
});
|
|
778
912
|
return 0;
|
|
@@ -835,19 +969,6 @@ function extractErrorCode(json) {
|
|
|
835
969
|
}
|
|
836
970
|
return undefined;
|
|
837
971
|
}
|
|
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
972
|
function sleep(ms) {
|
|
852
973
|
return new Promise((r) => setTimeout(r, ms));
|
|
853
974
|
}
|