@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.
Files changed (104) hide show
  1. package/README.md +42 -4
  2. package/dist/auth/browser-login.d.ts +30 -0
  3. package/dist/auth/browser-login.js +193 -0
  4. package/dist/auth/loopback-callback.d.ts +32 -0
  5. package/dist/auth/loopback-callback.js +175 -0
  6. package/dist/auth/open-browser.d.ts +15 -0
  7. package/dist/auth/open-browser.js +54 -0
  8. package/dist/auth/refresh.d.ts +27 -2
  9. package/dist/auth/refresh.js +52 -15
  10. package/dist/catalog/allowlist.d.ts +20 -4
  11. package/dist/catalog/allowlist.js +126 -25
  12. package/dist/catalog/command-tree.d.ts +35 -0
  13. package/dist/catalog/command-tree.js +119 -0
  14. package/dist/catalog/compatibility.d.ts +23 -0
  15. package/dist/catalog/compatibility.js +58 -0
  16. package/dist/catalog/generated/registry.json +6137 -0
  17. package/dist/catalog/generated/schemas.json +31036 -0
  18. package/dist/catalog/operations.d.ts +76 -3
  19. package/dist/catalog/operations.js +87 -213
  20. package/dist/catalog/validate-body.d.ts +22 -0
  21. package/dist/catalog/validate-body.js +98 -0
  22. package/dist/commands/request-body.d.ts +21 -0
  23. package/dist/commands/request-body.js +92 -0
  24. package/dist/commands/run.js +283 -162
  25. package/dist/config/profiles.js +3 -3
  26. package/dist/engine-backtest/errors.d.ts +18 -0
  27. package/dist/engine-backtest/errors.js +26 -0
  28. package/dist/engine-backtest/fetch-runtime.d.ts +38 -0
  29. package/dist/engine-backtest/fetch-runtime.js +170 -0
  30. package/dist/engine-backtest/native-import.d.ts +1 -0
  31. package/dist/engine-backtest/native-import.js +10 -0
  32. package/dist/engine-backtest/parse-args.d.ts +6 -0
  33. package/dist/engine-backtest/parse-args.js +281 -0
  34. package/dist/engine-backtest/persist.d.ts +32 -0
  35. package/dist/engine-backtest/persist.js +94 -0
  36. package/dist/engine-backtest/replay-timeframe.d.ts +16 -0
  37. package/dist/engine-backtest/replay-timeframe.js +48 -0
  38. package/dist/engine-backtest/resolve-packages.d.ts +27 -0
  39. package/dist/engine-backtest/resolve-packages.js +288 -0
  40. package/dist/engine-backtest/run-command.d.ts +38 -0
  41. package/dist/engine-backtest/run-command.js +540 -0
  42. package/dist/engine-backtest/types.d.ts +249 -0
  43. package/dist/engine-backtest/types.js +2 -0
  44. package/dist/envelope.d.ts +3 -0
  45. package/dist/envelope.js +43 -3
  46. package/dist/http/client.js +18 -15
  47. package/dist/index.d.ts +16 -5
  48. package/dist/index.js +47 -1
  49. package/dist/install/exec.d.ts +13 -0
  50. package/dist/install/exec.js +73 -0
  51. package/dist/install/package-root.d.ts +4 -0
  52. package/dist/install/package-root.js +59 -0
  53. package/dist/install/types.d.ts +69 -0
  54. package/dist/install/types.js +26 -0
  55. package/dist/install/wizard.d.ts +21 -0
  56. package/dist/install/wizard.js +332 -0
  57. package/dist/keychain/linux-secret-service.d.ts +11 -0
  58. package/dist/keychain/linux-secret-service.js +93 -0
  59. package/dist/keychain/store.d.ts +20 -1
  60. package/dist/keychain/store.js +94 -6
  61. package/dist/keychain/windows-credential.d.ts +13 -0
  62. package/dist/keychain/windows-credential.js +176 -0
  63. package/dist/safety/confirmation.d.ts +10 -3
  64. package/dist/safety/confirmation.js +27 -4
  65. package/dist/version.d.ts +2 -2
  66. package/dist/version.js +3 -2
  67. package/docs/.nojekyll +0 -0
  68. package/docs/agents/domain.md +51 -0
  69. package/docs/agents/issue-tracker.md +156 -0
  70. package/docs/agents/triage-labels.md +18 -0
  71. package/docs/alphafox-cli-installation-guide.md +105 -0
  72. package/docs/e2e-staging.md +76 -6
  73. package/docs/favicon.svg +4 -0
  74. package/docs/index.html +748 -0
  75. package/docs/logo/alphafox-mark.svg +4 -0
  76. package/docs/logo/alphafox-wordmark-black-en.svg +17 -0
  77. package/docs/logo/alphafox-wordmark-white-en.svg +16 -0
  78. package/docs/release-supply-chain.md +107 -26
  79. package/package.json +10 -3
  80. package/skills/account/SKILL.md +8 -6
  81. package/skills/admin/SKILL.md +9 -4
  82. package/skills/alphafox-shared/SKILL.md +44 -14
  83. package/skills/auth/SKILL.md +21 -6
  84. package/skills/engine-backtest/SKILL.md +71 -0
  85. package/skills/exchange/SKILL.md +10 -3
  86. package/skills/market/SKILL.md +9 -4
  87. package/skills/notification/SKILL.md +8 -3
  88. package/skills/strategy/SKILL.md +23 -9
  89. package/skills/trading/SKILL.md +18 -5
  90. package/vendor/backtest-runner/NOTICE.md +1 -0
  91. package/vendor/backtest-runner/README.md +97 -0
  92. package/vendor/backtest-runner/index.d.ts +423 -0
  93. package/vendor/backtest-runner/index.mjs +59 -0
  94. package/vendor/backtest-runner/lib/abortable.mjs +23 -0
  95. package/vendor/backtest-runner/lib/cache.mjs +159 -0
  96. package/vendor/backtest-runner/lib/coverage.mjs +238 -0
  97. package/vendor/backtest-runner/lib/encode.mjs +28 -0
  98. package/vendor/backtest-runner/lib/exchanges.mjs +143 -0
  99. package/vendor/backtest-runner/lib/proxy.mjs +78 -0
  100. package/vendor/backtest-runner/lib/scenario.mjs +66 -0
  101. package/vendor/backtest-runner/lib/series.mjs +370 -0
  102. package/vendor/backtest-runner/lib/tape-loader.mjs +614 -0
  103. package/vendor/backtest-runner/lib/timeframes.mjs +55 -0
  104. package/vendor/backtest-runner/package.json +13 -0
@@ -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 api METHOD PATH [--body JSON]",
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
- // still clear local
377
+ remoteRevoke = "failed";
301
378
  }
302
379
  }
303
380
  (0, store_1.deleteTokens)(profile.name, env);
304
- (0, envelope_1.writeSuccess)({ loggedOut: true, profile: profile.name }, { format: flags.format });
305
- return 0;
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 7636 S256)
593
+ // Browser Authorization Code + PKCE with loopback callback (RFC 8252).
507
594
  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");
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
- 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 });
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
- if (!op) {
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
- 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 });
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
- const bodyIdx = args.indexOf("--body");
627
- if (bodyIdx >= 0) {
628
- body = JSON.parse(args[bodyIdx + 1] ?? "{}");
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 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
- }
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: catalogHit?.risk ?? "unknown",
654
- }, { format: flags.format });
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
- // 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
- }
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: `Unknown command "${domain}". Try alphafox schema or alphafox catalog.`,
794
+ message: `Ambiguous command "${resolved.prefix}"`,
795
+ hint: `Matches: ${resolved.candidates.join(", ")}`,
689
796
  });
690
797
  }
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) {
798
+ if (resolved.kind === "missing") {
702
799
  (0, envelope_1.writeError)({
703
800
  type: "not_found",
704
- message: `No catalog operation for ${domain} ${resource} ${action}`,
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
- return await invokeOperation(op.operationId, args.slice(2), flags, env);
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
- params[key] = args[++i] ?? "";
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
- const bodyIdx = args.indexOf("--body");
740
- if (bodyIdx >= 0) {
741
- body = JSON.parse(args[bodyIdx + 1] ?? "{}");
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
- }, { format: flags.format });
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
  }