@alphafox/cli 0.1.5 → 0.2.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.
Files changed (50) hide show
  1. package/dist/auth/browser-login.d.ts +30 -0
  2. package/dist/auth/browser-login.js +193 -0
  3. package/dist/auth/loopback-callback.d.ts +32 -0
  4. package/dist/auth/loopback-callback.js +175 -0
  5. package/dist/auth/open-browser.d.ts +15 -0
  6. package/dist/auth/open-browser.js +54 -0
  7. package/dist/auth/refresh.d.ts +27 -2
  8. package/dist/auth/refresh.js +52 -15
  9. package/dist/catalog/allowlist.d.ts +20 -4
  10. package/dist/catalog/allowlist.js +126 -25
  11. package/dist/catalog/command-tree.d.ts +34 -0
  12. package/dist/catalog/command-tree.js +117 -0
  13. package/dist/catalog/compatibility.d.ts +23 -0
  14. package/dist/catalog/compatibility.js +58 -0
  15. package/dist/catalog/generated/registry.json +6137 -0
  16. package/dist/catalog/generated/schemas.json +31036 -0
  17. package/dist/catalog/operations.d.ts +76 -3
  18. package/dist/catalog/operations.js +87 -213
  19. package/dist/commands/run.js +171 -153
  20. package/dist/config/profiles.js +3 -3
  21. package/dist/envelope.d.ts +3 -0
  22. package/dist/envelope.js +43 -3
  23. package/dist/http/client.js +18 -15
  24. package/dist/index.d.ts +12 -5
  25. package/dist/index.js +32 -1
  26. package/dist/keychain/linux-secret-service.d.ts +11 -0
  27. package/dist/keychain/linux-secret-service.js +93 -0
  28. package/dist/keychain/store.d.ts +20 -1
  29. package/dist/keychain/store.js +94 -6
  30. package/dist/keychain/windows-credential.d.ts +13 -0
  31. package/dist/keychain/windows-credential.js +176 -0
  32. package/dist/safety/confirmation.d.ts +10 -3
  33. package/dist/safety/confirmation.js +27 -4
  34. package/dist/version.d.ts +2 -2
  35. package/dist/version.js +3 -2
  36. package/docs/agents/domain.md +51 -0
  37. package/docs/agents/issue-tracker.md +156 -0
  38. package/docs/agents/triage-labels.md +18 -0
  39. package/docs/e2e-staging.md +76 -6
  40. package/docs/release-supply-chain.md +99 -26
  41. package/package.json +4 -2
  42. package/skills/account/SKILL.md +8 -6
  43. package/skills/admin/SKILL.md +9 -4
  44. package/skills/alphafox-shared/SKILL.md +23 -14
  45. package/skills/auth/SKILL.md +21 -6
  46. package/skills/exchange/SKILL.md +10 -3
  47. package/skills/market/SKILL.md +9 -4
  48. package/skills/notification/SKILL.md +8 -3
  49. package/skills/strategy/SKILL.md +15 -9
  50. package/skills/trading/SKILL.md +15 -5
@@ -1,45 +1,14 @@
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");
@@ -94,6 +63,14 @@ function parseGlobalFlags(argv) {
94
63
  return { flags, rest };
95
64
  }
96
65
  async function runCli(argv, env = process.env) {
66
+ if (argv.some((a) => a === "--token" || a.startsWith("--token="))) {
67
+ (0, envelope_1.writeError)({
68
+ type: "usage",
69
+ subtype: "token_argv_forbidden",
70
+ message: "Refusing --token on argv. Store credentials in the OS keychain via alphafox auth login.",
71
+ hint: "Automation tokens are not supported in v1. Do not copy refresh tokens into CI.",
72
+ });
73
+ }
97
74
  const { flags, rest } = parseGlobalFlags(argv);
98
75
  const [cmd, ...args] = rest;
99
76
  if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
@@ -108,13 +85,17 @@ async function runCli(argv, env = process.env) {
108
85
  "alphafox whoami",
109
86
  "alphafox profile list|use <name>",
110
87
  "alphafox schema [operationId]",
88
+ "alphafox catalog",
111
89
  "alphafox api METHOD PATH [--body JSON]",
112
90
  "alphafox <domain> <resource> <action> [flags]",
113
91
  ],
114
- }, { format: flags.format });
92
+ }, { format: flags.format, jq: flags.jq });
115
93
  return 0;
116
94
  }
117
95
  try {
96
+ if (cmd !== "version") {
97
+ assertCatalogCompatible();
98
+ }
118
99
  switch (cmd) {
119
100
  case "version":
120
101
  return cmdVersion(flags);
@@ -154,18 +135,36 @@ async function runCli(argv, env = process.env) {
154
135
  }, { exitCode: status === 401 || status === 403 ? 77 : 1 });
155
136
  }
156
137
  }
138
+ function assertCatalogCompatible() {
139
+ const result = (0, operations_1.checkGeneratedCatalogCompatibility)(version_1.CLI_VERSION);
140
+ if (result.ok)
141
+ return;
142
+ (0, envelope_1.writeError)({
143
+ type: "compatibility",
144
+ subtype: result.code,
145
+ message: result.message,
146
+ hint: "Upgrade or pin @alphafox/cli to the catalog minCliVersion/maxCliVersion range.",
147
+ });
148
+ }
157
149
  function cmdVersion(flags) {
150
+ const compatibility = (0, operations_1.checkGeneratedCatalogCompatibility)(version_1.CLI_VERSION);
158
151
  (0, envelope_1.writeSuccess)({
159
152
  name: version_1.CLI_NAME,
160
153
  package: version_1.CLI_PACKAGE,
161
154
  version: version_1.CLI_VERSION,
162
155
  contractVersion: version_1.CLI_CONTRACT_VERSION,
163
156
  catalogVersion: operations_1.CATALOG_VERSION,
157
+ registryVersion: operations_1.COMPATIBILITY_RANGE.registryVersion,
158
+ minCliVersion: operations_1.COMPATIBILITY_RANGE.minCliVersion,
159
+ maxCliVersion: operations_1.COMPATIBILITY_RANGE.maxCliVersion,
160
+ openapi: operations_1.COMPATIBILITY_RANGE.openapi,
161
+ contractsSha: operations_1.CATALOG_SOURCE.contractsSha,
162
+ compatibility,
164
163
  node: process.version,
165
164
  platform: process.platform,
166
165
  arch: process.arch,
167
- }, { format: flags.format });
168
- return 0;
166
+ }, { format: flags.format, jq: flags.jq });
167
+ return compatibility.ok ? 0 : 1;
169
168
  }
170
169
  function cmdDoctor(flags, env) {
171
170
  const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
@@ -174,6 +173,7 @@ function cmdDoctor(flags, env) {
174
173
  // Avoid macOS `security` stderr noise when no item exists; file keychain is fine for doctor.
175
174
  const doctorEnv = { ...env, ALPHAFOX_FORCE_FILE_KEYCHAIN: env.ALPHAFOX_FORCE_FILE_KEYCHAIN ?? "1" };
176
175
  const tokens = (0, store_1.loadTokens)(profile.name, doctorEnv);
176
+ const probe = (0, store_1.probeOsKeychain)(env);
177
177
  const checks = [
178
178
  {
179
179
  name: "node",
@@ -202,6 +202,13 @@ function cmdDoctor(flags, env) {
202
202
  ? `token present (fp=${(0, store_1.tokenFingerprint)(tokens.accessToken)})`
203
203
  : "no tokens stored",
204
204
  },
205
+ {
206
+ name: "osKeychain",
207
+ ok: true,
208
+ detail: probe.available
209
+ ? probe.kind
210
+ : `${probe.kind} unavailable; file fallback (0600) if tokens are saved`,
211
+ },
205
212
  {
206
213
  name: "configHasNoTokens",
207
214
  ok: true,
@@ -212,9 +219,14 @@ function cmdDoctor(flags, env) {
212
219
  ok: true,
213
220
  detail: "v1 deferred (interactive login only)",
214
221
  },
222
+ {
223
+ name: "catalogCompatibility",
224
+ ok: (0, operations_1.checkGeneratedCatalogCompatibility)(version_1.CLI_VERSION).ok,
225
+ detail: `${version_1.CLI_VERSION} in [${operations_1.COMPATIBILITY_RANGE.minCliVersion}, ${operations_1.COMPATIBILITY_RANGE.maxCliVersion}] contract ${operations_1.COMPATIBILITY_RANGE.contractVersion}`,
226
+ },
215
227
  ];
216
228
  const ok = checks.every((c) => c.ok);
217
- (0, envelope_1.writeSuccess)({ ok, profile: profile.name, checks }, { format: flags.format });
229
+ (0, envelope_1.writeSuccess)({ ok, profile: profile.name, checks }, { format: flags.format, jq: flags.jq });
218
230
  return ok ? 0 : 1;
219
231
  }
220
232
  async function cmdWhoami(flags, env) {
@@ -236,7 +248,7 @@ async function cmdWhoami(flags, env) {
236
248
  code: extractErrorCode(res.json),
237
249
  }, { requestId: res.requestId, exitCode: res.status === 401 ? 77 : 1 });
238
250
  }
239
- (0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format });
251
+ (0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format, jq: flags.jq });
240
252
  return 0;
241
253
  }
242
254
  async function cmdAuth(args, flags, env) {
@@ -252,7 +264,7 @@ async function cmdAuth(args, flags, env) {
252
264
  authenticated: false,
253
265
  profile: profile.name,
254
266
  verified: false,
255
- }, { format: flags.format });
267
+ }, { format: flags.format, jq: flags.jq });
256
268
  return 0;
257
269
  }
258
270
  let verified = null;
@@ -278,14 +290,15 @@ async function cmdAuth(args, flags, env) {
278
290
  expiresAt: tokens.expiresAt,
279
291
  verified,
280
292
  whoami,
281
- }, { format: flags.format });
293
+ }, { format: flags.format, jq: flags.jq });
282
294
  return 0;
283
295
  }
284
296
  if (sub === "logout") {
285
297
  const tokens = (0, store_1.loadTokens)(profile.name, env);
298
+ let remoteRevoke = "skipped";
286
299
  if (tokens?.refreshToken) {
287
300
  try {
288
- await (0, client_1.apiRequest)({
301
+ const res = await (0, client_1.apiRequest)({
289
302
  method: "POST",
290
303
  path: "/api/auth/oauth/revoke",
291
304
  profile,
@@ -295,14 +308,26 @@ async function cmdAuth(args, flags, env) {
295
308
  },
296
309
  skipAuth: false,
297
310
  }, env);
311
+ remoteRevoke =
312
+ res.status >= 200 && res.status < 300 ? "ok" : "failed";
298
313
  }
299
314
  catch {
300
- // still clear local
315
+ remoteRevoke = "failed";
301
316
  }
302
317
  }
303
318
  (0, store_1.deleteTokens)(profile.name, env);
304
- (0, envelope_1.writeSuccess)({ loggedOut: true, profile: profile.name }, { format: flags.format });
305
- return 0;
319
+ const localCleared = true;
320
+ const fullyLoggedOut = remoteRevoke !== "failed";
321
+ // Never claim a clean remote logout when revoke failed while an RT was present.
322
+ (0, envelope_1.writeSuccess)({
323
+ localCleared,
324
+ remoteRevoke,
325
+ fullyLoggedOut,
326
+ // retained for compatibility; false when remote revoke failed
327
+ loggedOut: fullyLoggedOut,
328
+ profile: profile.name,
329
+ }, { format: flags.format, jq: flags.jq });
330
+ return fullyLoggedOut ? 0 : 1;
306
331
  }
307
332
  if (sub === "login") {
308
333
  return await cmdAuthLogin(args.slice(1), flags, env, profile);
@@ -370,7 +395,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
370
395
  profile: profile.name,
371
396
  accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
372
397
  expiresIn: tokens.expires_in,
373
- }, { format: flags.format, requestId: res.requestId });
398
+ }, { format: flags.format, jq: flags.jq, requestId: res.requestId });
374
399
  return 0;
375
400
  }
376
401
  if (deviceCode) {
@@ -389,7 +414,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
389
414
  (0, envelope_1.writeSuccess)({
390
415
  status: "authorization_pending",
391
416
  profile: profile.name,
392
- }, { format: flags.format, requestId: res.requestId });
417
+ }, { format: flags.format, jq: flags.jq, requestId: res.requestId });
393
418
  return 0;
394
419
  }
395
420
  if (res.status >= 400) {
@@ -415,7 +440,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
415
440
  profile: profile.name,
416
441
  accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
417
442
  expiresIn: tokens.expires_in,
418
- }, { format: flags.format, requestId: res.requestId });
443
+ }, { format: flags.format, jq: flags.jq, requestId: res.requestId });
419
444
  return 0;
420
445
  }
421
446
  // Start device flow (default headless path)
@@ -448,7 +473,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
448
473
  expires_in: data.expires_in,
449
474
  interval: data.interval,
450
475
  next: "Open verification_uri, approve, then: alphafox auth login --device-code <device_code>",
451
- }, { format: flags.format, requestId: res.requestId });
476
+ }, { format: flags.format, jq: flags.jq, requestId: res.requestId });
452
477
  if (noWait) {
453
478
  return 0;
454
479
  }
@@ -494,7 +519,7 @@ async function cmdAuthLogin(args, flags, env, profile) {
494
519
  authenticated: true,
495
520
  profile: profile.name,
496
521
  accessTokenFingerprint: (0, store_1.tokenFingerprint)(tokens.access_token),
497
- }, { format: flags.format, requestId: poll.requestId });
522
+ }, { format: flags.format, jq: flags.jq, requestId: poll.requestId });
498
523
  return 0;
499
524
  }
500
525
  (0, envelope_1.writeError)({
@@ -503,36 +528,34 @@ async function cmdAuthLogin(args, flags, env, profile) {
503
528
  subtype: "expired_token",
504
529
  });
505
530
  }
506
- // Browser Authorization Code + PKCE (RFC 7636 S256)
531
+ // Browser Authorization Code + PKCE with loopback callback (RFC 8252).
507
532
  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");
533
+ const result = await (0, browser_login_1.runBrowserPkceLogin)({
534
+ profile,
535
+ env,
536
+ timeoutMs: (0, browser_login_1.browserLoginTimeoutMs)(env),
537
+ openBrowser: (0, browser_login_1.resolveOpenBrowser)(env),
538
+ });
539
+ if (result.status !== "authenticated") {
540
+ (0, envelope_1.writeError)({
541
+ type: "auth",
542
+ subtype: result.reason,
543
+ message: result.message,
544
+ details: result.authorizeUrl
545
+ ? { authorizeUrl: result.authorizeUrl }
546
+ : undefined,
547
+ hint: result.authorizeUrl
548
+ ? "Copy authorizeUrl into a browser, or use Device Flow: alphafox auth login --no-wait"
549
+ : undefined,
550
+ });
551
+ }
523
552
  (0, envelope_1.writeSuccess)({
553
+ authenticated: true,
524
554
  flow: "authorization_code_pkce",
525
555
  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 });
556
+ accessTokenFingerprint: result.accessTokenFingerprint,
557
+ expiresIn: result.expiresIn,
558
+ }, { format: flags.format, jq: flags.jq, requestId: result.requestId });
536
559
  return 0;
537
560
  }
538
561
  (0, envelope_1.writeError)({
@@ -547,7 +570,7 @@ function cmdProfile(args, flags, env) {
547
570
  (0, envelope_1.writeSuccess)({
548
571
  activeProfile: file.activeProfile,
549
572
  profiles: ["production", "staging", "local"].map((name) => (0, profiles_1.resolveProfile)(name, env)),
550
- }, { format: flags.format });
573
+ }, { format: flags.format, jq: flags.jq });
551
574
  return 0;
552
575
  }
553
576
  if (sub === "use") {
@@ -560,7 +583,7 @@ function cmdProfile(args, flags, env) {
560
583
  }
561
584
  const file = (0, profiles_1.loadConfigFile)(env);
562
585
  (0, profiles_1.saveConfigFile)({ ...file, activeProfile: name }, env);
563
- (0, envelope_1.writeSuccess)({ activeProfile: name }, { format: flags.format });
586
+ (0, envelope_1.writeSuccess)({ activeProfile: name }, { format: flags.format, jq: flags.jq });
564
587
  return 0;
565
588
  }
566
589
  (0, envelope_1.writeError)({ type: "usage", message: "Usage: alphafox profile list|use" });
@@ -570,12 +593,17 @@ function cmdSchema(args, flags) {
570
593
  if (!operationId) {
571
594
  (0, envelope_1.writeSuccess)({
572
595
  contractVersion: operations_1.CATALOG_VERSION,
596
+ registryVersion: operations_1.COMPATIBILITY_RANGE.registryVersion,
597
+ openapi: operations_1.COMPATIBILITY_RANGE.openapi,
598
+ minCliVersion: operations_1.COMPATIBILITY_RANGE.minCliVersion,
599
+ maxCliVersion: operations_1.COMPATIBILITY_RANGE.maxCliVersion,
573
600
  operations: operations_1.CATALOG_OPERATIONS.map((o) => o.operationId),
574
- }, { format: flags.format });
601
+ }, { format: flags.format, jq: flags.jq });
575
602
  return 0;
576
603
  }
577
604
  const op = (0, operations_1.findCatalogOperation)(operationId);
578
- if (!op) {
605
+ const schema = (0, operations_1.getOperationSchemaDocument)(operationId);
606
+ if (!op || !schema) {
579
607
  (0, envelope_1.writeError)({
580
608
  type: "not_found",
581
609
  message: `Unknown operationId: ${operationId}`,
@@ -583,26 +611,13 @@ function cmdSchema(args, flags) {
583
611
  });
584
612
  }
585
613
  (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 });
614
+ ...schema,
615
+ examples: (0, command_tree_1.typedCommandExample)(op),
616
+ }, { format: flags.format, jq: flags.jq });
602
617
  return 0;
603
618
  }
604
619
  function cmdCatalog(flags) {
605
- (0, envelope_1.writeSuccess)((0, operations_1.buildCapabilityManifest)(), { format: flags.format });
620
+ (0, envelope_1.writeSuccess)((0, operations_1.buildCapabilityManifest)(), { format: flags.format, jq: flags.jq });
606
621
  return 0;
607
622
  }
608
623
  async function cmdApi(args, flags, env) {
@@ -630,19 +645,18 @@ async function cmdApi(args, flags, env) {
630
645
  const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
631
646
  unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
632
647
  });
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
- }
648
+ // Infer risk from catalog; uncataloged mutations are treated as high-risk.
649
+ const catalogHit = (0, operations_1.findCatalogOperationByRoute)(method, (0, allowlist_1.normalizeApiPath)(path));
650
+ const risk = (0, confirmation_1.inferRawApiRisk)(method, catalogHit?.risk);
651
+ const gate = (0, confirmation_1.assertHighRiskConfirmation)({
652
+ risk,
653
+ yes: flags.yes,
654
+ dryRun: flags.dryRun,
655
+ action: `api ${method} ${path}`,
656
+ });
657
+ if (!gate.allowed && gate.error) {
658
+ process.stderr.write(`${JSON.stringify((0, envelope_1.errorEnvelope)(gate.error))}\n`);
659
+ return 10;
646
660
  }
647
661
  if (flags.dryRun) {
648
662
  (0, envelope_1.writeSuccess)({
@@ -650,8 +664,8 @@ async function cmdApi(args, flags, env) {
650
664
  method,
651
665
  path,
652
666
  profile: profile.name,
653
- risk: catalogHit?.risk ?? "unknown",
654
- }, { format: flags.format });
667
+ risk,
668
+ }, { format: flags.format, jq: flags.jq });
655
669
  return 0;
656
670
  }
657
671
  const res = await (0, client_1.apiRequest)({
@@ -669,44 +683,47 @@ async function cmdApi(args, flags, env) {
669
683
  code: extractErrorCode(res.json),
670
684
  }, { requestId: res.requestId, exitCode: res.status === 401 ? 77 : 1 });
671
685
  }
672
- (0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format });
686
+ (0, envelope_1.writeSuccess)(res.json, { requestId: res.requestId, format: flags.format, jq: flags.jq });
673
687
  return 0;
674
688
  }
675
689
  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
- }
690
+ const resolved = (0, command_tree_1.resolveTypedCommand)([domain, ...args]);
691
+ if (resolved.kind === "help") {
692
+ (0, envelope_1.writeSuccess)({
693
+ prefix: resolved.prefix,
694
+ operations: resolved.operations.map((op) => ({
695
+ operationId: op.operationId,
696
+ method: op.method,
697
+ path: op.path,
698
+ risk: op.risk,
699
+ scopes: op.scopes,
700
+ stream: Boolean(op.stream),
701
+ file: Boolean(op.file),
702
+ pagination: Boolean(op.pagination),
703
+ examples: (0, command_tree_1.typedCommandExample)(op),
704
+ })),
705
+ }, { format: flags.format, jq: flags.jq });
706
+ return 0;
707
+ }
708
+ if (resolved.kind === "ambiguous") {
686
709
  (0, envelope_1.writeError)({
687
710
  type: "usage",
688
- message: `Unknown command "${domain}". Try alphafox schema or alphafox catalog.`,
711
+ message: `Ambiguous command "${resolved.prefix}"`,
712
+ hint: `Matches: ${resolved.candidates.join(", ")}`,
689
713
  });
690
714
  }
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) {
715
+ if (resolved.kind === "missing") {
702
716
  (0, envelope_1.writeError)({
703
717
  type: "not_found",
704
- message: `No catalog operation for ${domain} ${resource} ${action}`,
718
+ message: `No catalog operation for ${resolved.prefix || domain}`,
705
719
  status: 404,
706
720
  hint: "Run alphafox schema for operationIds",
707
721
  });
708
722
  }
709
- return await invokeOperation(op.operationId, args.slice(2), flags, env);
723
+ if (resolved.help) {
724
+ return cmdSchema([resolved.operation.operationId], flags);
725
+ }
726
+ return await invokeOperation(resolved.operation.operationId, resolved.flagArgs, flags, env);
710
727
  }
711
728
  async function invokeOperation(operationId, args, flags, env) {
712
729
  const op = (0, operations_1.findCatalogOperation)(operationId);
@@ -728,11 +745,20 @@ async function invokeOperation(operationId, args, flags, env) {
728
745
  return 10;
729
746
  }
730
747
  const params = {};
748
+ const extra = {};
749
+ const pathParamNames = new Set((0, operations_1.getOperationSchemaDocument)(operationId)?.request.pathParamNames ??
750
+ (0, operations_1.extractPathParamNames)(op.path));
731
751
  for (let i = 0; i < args.length; i += 1) {
732
752
  const a = args[i];
733
753
  if (a.startsWith("--") && a !== "--body") {
734
754
  const key = a.slice(2);
735
- params[key] = args[++i] ?? "";
755
+ const value = args[++i] ?? "";
756
+ if (pathParamNames.has(key)) {
757
+ params[key] = value;
758
+ }
759
+ else {
760
+ extra[key] = value;
761
+ }
736
762
  }
737
763
  }
738
764
  let body;
@@ -740,7 +766,12 @@ async function invokeOperation(operationId, args, flags, env) {
740
766
  if (bodyIdx >= 0) {
741
767
  body = JSON.parse(args[bodyIdx + 1] ?? "{}");
742
768
  }
743
- const path = (0, operations_1.resolveOperationPath)(op.path, params);
769
+ let path = (0, operations_1.resolveOperationPath)(op.path, params);
770
+ if ((op.method === "GET" || op.method === "HEAD") &&
771
+ Object.keys(extra).length > 0) {
772
+ const query = new URLSearchParams(extra).toString();
773
+ path = `${path}?${query}`;
774
+ }
744
775
  const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
745
776
  unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
746
777
  });
@@ -752,7 +783,7 @@ async function invokeOperation(operationId, args, flags, env) {
752
783
  path,
753
784
  risk: op.risk,
754
785
  profile: profile.name,
755
- }, { format: flags.format });
786
+ }, { format: flags.format, jq: flags.jq });
756
787
  return 0;
757
788
  }
758
789
  const res = await (0, client_1.apiRequest)({
@@ -772,7 +803,7 @@ async function invokeOperation(operationId, args, flags, env) {
772
803
  }
773
804
  (0, envelope_1.writeSuccess)(res.json, {
774
805
  requestId: res.requestId,
775
- format: flags.format,
806
+ format: flags.format, jq: flags.jq,
776
807
  meta: { operationId },
777
808
  });
778
809
  return 0;
@@ -835,19 +866,6 @@ function extractErrorCode(json) {
835
866
  }
836
867
  return undefined;
837
868
  }
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
869
  function sleep(ms) {
852
870
  return new Promise((r) => setTimeout(r, ms));
853
871
  }
@@ -16,7 +16,7 @@ const DEFAULTS = {
16
16
  issuer: "https://alphafox.app/api/auth",
17
17
  audience: "https://alphafox.app/api/v1",
18
18
  clientId: "alphafox-cli-prod",
19
- contractVersion: "2026-08-11",
19
+ contractVersion: "2026-08-13",
20
20
  },
21
21
  staging: {
22
22
  name: "staging",
@@ -24,7 +24,7 @@ const DEFAULTS = {
24
24
  issuer: "https://staging.alphafox.app/api/auth",
25
25
  audience: "https://staging.alphafox.app/api/v1",
26
26
  clientId: "alphafox-cli-staging",
27
- contractVersion: "2026-08-11",
27
+ contractVersion: "2026-08-13",
28
28
  },
29
29
  local: {
30
30
  name: "local",
@@ -33,7 +33,7 @@ const DEFAULTS = {
33
33
  audience: "http://127.0.0.1:3000/api/v1",
34
34
  clientId: "alphafox-cli-local",
35
35
  localOrigin: "http://127.0.0.1:3000",
36
- contractVersion: "2026-08-11",
36
+ contractVersion: "2026-08-13",
37
37
  },
38
38
  };
39
39
  function defaultConfigDir(env = process.env) {
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from "node:child_process";
1
2
  export type ExitCode = 0 | 1 | 2 | 3 | 4 | 10 | 64 | 65 | 66 | 69 | 70 | 75 | 77 | 78;
2
3
  export interface SuccessEnvelope<T = unknown> {
3
4
  readonly ok: true;
@@ -28,7 +29,9 @@ export declare function writeSuccess(data: unknown, options?: {
28
29
  readonly meta?: Record<string, unknown>;
29
30
  readonly requestId?: string;
30
31
  readonly format?: "json" | "jsonl" | "text";
32
+ readonly jq?: string;
31
33
  }): void;
34
+ export declare function applyJqFilter(value: unknown, filter: string, spawn?: typeof spawnSync): string;
32
35
  export declare function writeError(error: ErrorBody, options?: {
33
36
  readonly requestId?: string;
34
37
  readonly exitCode?: number;