@alphafox/cli 0.2.0 → 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 (72) hide show
  1. package/README.md +42 -4
  2. package/dist/catalog/command-tree.d.ts +1 -0
  3. package/dist/catalog/command-tree.js +5 -3
  4. package/dist/catalog/validate-body.d.ts +22 -0
  5. package/dist/catalog/validate-body.js +98 -0
  6. package/dist/commands/request-body.d.ts +21 -0
  7. package/dist/commands/request-body.js +92 -0
  8. package/dist/commands/run.js +113 -10
  9. package/dist/engine-backtest/errors.d.ts +18 -0
  10. package/dist/engine-backtest/errors.js +26 -0
  11. package/dist/engine-backtest/fetch-runtime.d.ts +38 -0
  12. package/dist/engine-backtest/fetch-runtime.js +170 -0
  13. package/dist/engine-backtest/native-import.d.ts +1 -0
  14. package/dist/engine-backtest/native-import.js +10 -0
  15. package/dist/engine-backtest/parse-args.d.ts +6 -0
  16. package/dist/engine-backtest/parse-args.js +281 -0
  17. package/dist/engine-backtest/persist.d.ts +32 -0
  18. package/dist/engine-backtest/persist.js +94 -0
  19. package/dist/engine-backtest/replay-timeframe.d.ts +16 -0
  20. package/dist/engine-backtest/replay-timeframe.js +48 -0
  21. package/dist/engine-backtest/resolve-packages.d.ts +27 -0
  22. package/dist/engine-backtest/resolve-packages.js +288 -0
  23. package/dist/engine-backtest/run-command.d.ts +38 -0
  24. package/dist/engine-backtest/run-command.js +540 -0
  25. package/dist/engine-backtest/types.d.ts +249 -0
  26. package/dist/engine-backtest/types.js +2 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.js +16 -1
  29. package/dist/install/exec.d.ts +13 -0
  30. package/dist/install/exec.js +73 -0
  31. package/dist/install/package-root.d.ts +4 -0
  32. package/dist/install/package-root.js +59 -0
  33. package/dist/install/types.d.ts +69 -0
  34. package/dist/install/types.js +26 -0
  35. package/dist/install/wizard.d.ts +21 -0
  36. package/dist/install/wizard.js +332 -0
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/docs/.nojekyll +0 -0
  40. package/docs/alphafox-cli-installation-guide.md +105 -0
  41. package/docs/favicon.svg +4 -0
  42. package/docs/index.html +748 -0
  43. package/docs/logo/alphafox-mark.svg +4 -0
  44. package/docs/logo/alphafox-wordmark-black-en.svg +17 -0
  45. package/docs/logo/alphafox-wordmark-white-en.svg +16 -0
  46. package/docs/release-supply-chain.md +10 -2
  47. package/package.json +7 -2
  48. package/skills/account/SKILL.md +2 -2
  49. package/skills/admin/SKILL.md +2 -2
  50. package/skills/alphafox-shared/SKILL.md +22 -1
  51. package/skills/auth/SKILL.md +1 -1
  52. package/skills/engine-backtest/SKILL.md +71 -0
  53. package/skills/exchange/SKILL.md +2 -2
  54. package/skills/market/SKILL.md +1 -1
  55. package/skills/notification/SKILL.md +2 -2
  56. package/skills/strategy/SKILL.md +10 -2
  57. package/skills/trading/SKILL.md +6 -3
  58. package/vendor/backtest-runner/NOTICE.md +1 -0
  59. package/vendor/backtest-runner/README.md +97 -0
  60. package/vendor/backtest-runner/index.d.ts +423 -0
  61. package/vendor/backtest-runner/index.mjs +59 -0
  62. package/vendor/backtest-runner/lib/abortable.mjs +23 -0
  63. package/vendor/backtest-runner/lib/cache.mjs +159 -0
  64. package/vendor/backtest-runner/lib/coverage.mjs +238 -0
  65. package/vendor/backtest-runner/lib/encode.mjs +28 -0
  66. package/vendor/backtest-runner/lib/exchanges.mjs +143 -0
  67. package/vendor/backtest-runner/lib/proxy.mjs +78 -0
  68. package/vendor/backtest-runner/lib/scenario.mjs +66 -0
  69. package/vendor/backtest-runner/lib/series.mjs +370 -0
  70. package/vendor/backtest-runner/lib/tape-loader.mjs +614 -0
  71. package/vendor/backtest-runner/lib/timeframes.mjs +55 -0
  72. package/vendor/backtest-runner/package.json +13 -0
package/README.md CHANGED
@@ -2,16 +2,43 @@
2
2
 
3
3
  Alphafox CLI (`alphafox`) — Agent and human entry for the versioned Public Application API on alphafox-web.
4
4
 
5
+ Homepage: https://alphafoxai.github.io/alphafox-cli/
6
+
5
7
  ## Install
6
8
 
9
+ Pick **one** of the following. Both install the CLI **and** register Agent
10
+ Skills. `npm install -g @alphafox/cli` alone does **not** install Skills.
11
+
12
+ ### Method 1 — Manual wizard
13
+
7
14
  ```bash
8
- npm install -g @alphafox/cli
9
- # or
10
- npx alphafox-cli version
15
+ npx @alphafox/cli@latest install
11
16
  ```
12
17
 
18
+ This installs `@alphafox/cli` globally, runs `npx skills add` into detected
19
+ agents (Cursor, Claude Code, Codex, …), and may prompt for
20
+ `alphafox auth login --browser`. **Restart the AI tool** afterwards so Skills
21
+ load.
22
+
23
+ ### Method 2 — Install via Agent
24
+
25
+ Copy and send this to Cursor / Claude Code / Codex / Trae:
26
+
27
+ ```
28
+ 帮我安装 Alphafox CLI:https://github.com/alphafoxai/alphafox-cli/blob/main/docs/alphafox-cli-installation-guide.md
29
+ ```
30
+
31
+ ```
32
+ Help me install Alphafox CLI: https://github.com/alphafoxai/alphafox-cli/blob/main/docs/alphafox-cli-installation-guide.md
33
+ ```
34
+
35
+ The Agent follows that guide (`npm install -g`, `npx skills add`, login,
36
+ `doctor`). **Restart the AI tool** when it finishes.
37
+
13
38
  ## Quick start
14
39
 
40
+ After Method 1 or 2:
41
+
15
42
  ```bash
16
43
  alphafox version
17
44
  alphafox doctor
@@ -28,14 +55,25 @@ alphafox api GET /api/v1/me
28
55
  - Profiles: `production` (default), `staging`, `local` — isolated issuer/audience/client (ADR 0003).
29
56
  - High-risk writes require `--yes`. Automation tokens are **deferred** (ADR 0004).
30
57
  - Raw `api` only hits allowlisted `/api/v1/*` facade paths.
58
+ - Writes validate `--body` / `--config @file` against the catalog schema
59
+ before HTTP. Agents must `alphafox schema <operationId>` first and must
60
+ not invent fields. Large objects use `--config @file`, not argv.
31
61
 
32
62
  ## Skills
33
63
 
34
- Co-versioned Agent Skills live under `skills/`. They route intent to public `operationId`s only.
64
+ Co-versioned Agent Skills live under `skills/`. API-oriented skills route public
65
+ `operationId`s; an explicit local-execution skill may call its co-versioned
66
+ built-in command.
67
+
68
+ `alphafox install` (and the [Agent install guide](docs/alphafox-cli-installation-guide.md))
69
+ run `npx skills add` so those files land in Agent skill directories
70
+ (`.cursor/skills`, `.claude/skills`, `~/.agents/skills`, …).
35
71
 
36
72
  ## Docs
37
73
 
74
+ - [Homepage](https://alphafoxai.github.io/alphafox-cli/)
38
75
  - [Release / supply chain](docs/release-supply-chain.md)
76
+ - [Agent install guide](docs/alphafox-cli-installation-guide.md)
39
77
  - [Staging E2E checklist](docs/e2e-staging.md)
40
78
  - Related ADRs and parity matrix live in the alphafox-web repository
41
79
 
@@ -31,4 +31,5 @@ export declare function resolveTypedCommand(tokens: readonly string[]): TypedRes
31
31
  export declare function typedCommandExample(op: CatalogOperation): {
32
32
  readonly typed: string;
33
33
  readonly api: string;
34
+ readonly config?: string;
34
35
  };
@@ -104,14 +104,16 @@ function typedCommandExample(op) {
104
104
  const schemaNames = op.path.match(/\{([a-zA-Z0-9_]+)\}/g)?.map((s) => s.slice(1, -1)) ?? [];
105
105
  const pathFlags = schemaNames.map((name) => `--${name} <${name}>`).join(" ");
106
106
  const riskFlag = op.risk === "high-risk-write" || op.risk === "unknown" ? " --yes" : "";
107
- const bodyFlag = op.method !== "GET" && op.method !== "HEAD" && op.method !== "DELETE"
108
- ? " --body '{}'"
109
- : "";
107
+ const writesBody = op.method !== "GET" && op.method !== "HEAD" && op.method !== "DELETE";
108
+ const bodyFlag = writesBody ? " --body '{}'" : "";
110
109
  const typedFlags = [pathFlags, bodyFlag.trim(), riskFlag.trim()]
111
110
  .filter(Boolean)
112
111
  .join(" ");
113
112
  return {
114
113
  typed: `alphafox ${segments.join(" ")}${typedFlags ? ` ${typedFlags}` : ""}`,
115
114
  api: `alphafox api ${op.method} ${op.path}${bodyFlag}${riskFlag}`,
115
+ config: writesBody
116
+ ? `alphafox ${segments.join(" ")} --config @./${op.operationId}.json${riskFlag}`
117
+ : undefined,
116
118
  };
117
119
  }
@@ -0,0 +1,22 @@
1
+ import { type ErrorObject } from "ajv/dist/2020";
2
+ import type { ErrorBody } from "../envelope";
3
+ export declare function isWriteMethod(method: string): boolean;
4
+ export declare function isEmptyObject(value: unknown): boolean;
5
+ export type BodyValidationResult = {
6
+ readonly ok: true;
7
+ readonly body: unknown;
8
+ } | {
9
+ readonly ok: false;
10
+ readonly error: ErrorBody;
11
+ };
12
+ export declare function validateCatalogWriteBody(input: {
13
+ readonly method: string;
14
+ readonly operationId?: string;
15
+ readonly body: unknown;
16
+ }): BodyValidationResult;
17
+ export declare function formatAjvErrors(errors: readonly ErrorObject[]): readonly {
18
+ readonly instancePath: string;
19
+ readonly keyword: string;
20
+ readonly message: string;
21
+ readonly params: Record<string, unknown>;
22
+ }[];
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isWriteMethod = isWriteMethod;
7
+ exports.isEmptyObject = isEmptyObject;
8
+ exports.validateCatalogWriteBody = validateCatalogWriteBody;
9
+ exports.formatAjvErrors = formatAjvErrors;
10
+ const _2020_1 = __importDefault(require("ajv/dist/2020"));
11
+ const operations_1 = require("./operations");
12
+ const ajv = new _2020_1.default({
13
+ allErrors: true,
14
+ strict: false,
15
+ validateSchema: false,
16
+ });
17
+ const compiled = new Map();
18
+ function isWriteMethod(method) {
19
+ const verb = method.toUpperCase();
20
+ return verb !== "GET" && verb !== "HEAD";
21
+ }
22
+ function isEmptyObject(value) {
23
+ return (value !== null &&
24
+ typeof value === "object" &&
25
+ !Array.isArray(value) &&
26
+ Object.keys(value).length === 0);
27
+ }
28
+ function validateCatalogWriteBody(input) {
29
+ if (!isWriteMethod(input.method)) {
30
+ return { ok: true, body: input.body };
31
+ }
32
+ const payload = input.body === undefined ? {} : input.body;
33
+ const operationId = input.operationId;
34
+ if (!operationId) {
35
+ if (payload === undefined || isEmptyObject(payload)) {
36
+ return { ok: true, body: payload };
37
+ }
38
+ return {
39
+ ok: false,
40
+ error: {
41
+ type: "usage",
42
+ subtype: "body_schema_missing",
43
+ message: "Uncataloged write cannot send a non-empty body. Find the operationId first.",
44
+ hint: "Run alphafox catalog / alphafox schema <operationId>, then resend only documented fields.",
45
+ },
46
+ };
47
+ }
48
+ const doc = (0, operations_1.getOperationSchemaDocument)(operationId);
49
+ const schema = doc?.request.body ?? null;
50
+ if (!schema) {
51
+ if (payload === undefined || isEmptyObject(payload)) {
52
+ return { ok: true, body: payload };
53
+ }
54
+ return {
55
+ ok: false,
56
+ error: {
57
+ type: "usage",
58
+ subtype: "body_schema_missing",
59
+ message: `Operation ${operationId} has no catalog request body; do not invent fields.`,
60
+ hint: `Run alphafox schema ${operationId}. Omit --body/--config or send {}.`,
61
+ },
62
+ };
63
+ }
64
+ const validate = compileBodySchema(operationId, schema);
65
+ const ok = validate(payload);
66
+ if (ok) {
67
+ return { ok: true, body: payload };
68
+ }
69
+ return {
70
+ ok: false,
71
+ error: {
72
+ type: "usage",
73
+ subtype: "body_schema",
74
+ message: `Request body does not match catalog schema for ${operationId}`,
75
+ hint: `Run alphafox schema ${operationId} and resend only documented fields via --body or --config @file.`,
76
+ details: {
77
+ operationId,
78
+ errors: formatAjvErrors(validate.errors ?? []),
79
+ },
80
+ },
81
+ };
82
+ }
83
+ function compileBodySchema(operationId, schema) {
84
+ const cached = compiled.get(operationId);
85
+ if (cached)
86
+ return cached;
87
+ const validate = ajv.compile(schema);
88
+ compiled.set(operationId, validate);
89
+ return validate;
90
+ }
91
+ function formatAjvErrors(errors) {
92
+ return errors.map((err) => ({
93
+ instancePath: err.instancePath,
94
+ keyword: err.keyword,
95
+ message: err.message ?? "invalid",
96
+ params: (err.params ?? {}),
97
+ }));
98
+ }
@@ -0,0 +1,21 @@
1
+ export declare class RequestBodyError extends Error {
2
+ readonly type = "usage";
3
+ readonly subtype: string;
4
+ readonly hint?: string;
5
+ constructor(input: {
6
+ readonly message: string;
7
+ readonly subtype: string;
8
+ readonly hint?: string;
9
+ });
10
+ }
11
+ export declare function isRequestBodyError(value: unknown): value is RequestBodyError;
12
+ export interface LoadJsonOptions {
13
+ readonly cwd?: string;
14
+ readonly readFile?: (path: string) => string;
15
+ }
16
+ export declare function loadJsonArg(raw: string, options?: LoadJsonOptions): unknown;
17
+ export type RequestBodySource = "body" | "config" | "none";
18
+ export declare function parseRequestBodyFlags(args: readonly string[], options?: LoadJsonOptions): {
19
+ readonly body: unknown;
20
+ readonly source: RequestBodySource;
21
+ };
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RequestBodyError = void 0;
4
+ exports.isRequestBodyError = isRequestBodyError;
5
+ exports.loadJsonArg = loadJsonArg;
6
+ exports.parseRequestBodyFlags = parseRequestBodyFlags;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ class RequestBodyError extends Error {
10
+ type = "usage";
11
+ subtype;
12
+ hint;
13
+ constructor(input) {
14
+ super(input.message);
15
+ this.name = "RequestBodyError";
16
+ this.subtype = input.subtype;
17
+ this.hint = input.hint;
18
+ }
19
+ }
20
+ exports.RequestBodyError = RequestBodyError;
21
+ function isRequestBodyError(value) {
22
+ return value instanceof RequestBodyError;
23
+ }
24
+ function loadJsonArg(raw, options = {}) {
25
+ const cwd = options.cwd ?? process.cwd();
26
+ const read = options.readFile ?? ((p) => (0, node_fs_1.readFileSync)(p, "utf8"));
27
+ if (raw.startsWith("@")) {
28
+ const rel = raw.slice(1);
29
+ if (!rel) {
30
+ throw new RequestBodyError({
31
+ subtype: "missing_config",
32
+ message: "@path is empty",
33
+ hint: "Use --config @./payload.json",
34
+ });
35
+ }
36
+ const abs = (0, node_path_1.isAbsolute)(rel) ? rel : (0, node_path_1.resolve)(cwd, rel);
37
+ try {
38
+ return JSON.parse(read(abs));
39
+ }
40
+ catch (err) {
41
+ throw new RequestBodyError({
42
+ subtype: "invalid_config",
43
+ message: `Cannot read JSON file ${abs}: ${err instanceof Error ? err.message : String(err)}`,
44
+ hint: "Pass a JSON object file via --config @path",
45
+ });
46
+ }
47
+ }
48
+ try {
49
+ return JSON.parse(raw);
50
+ }
51
+ catch (err) {
52
+ throw new RequestBodyError({
53
+ subtype: "invalid_body",
54
+ message: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
55
+ hint: "Small payloads: --body '{...}'. Large objects: --config @./payload.json",
56
+ });
57
+ }
58
+ }
59
+ function parseRequestBodyFlags(args, options = {}) {
60
+ const bodyIdx = args.indexOf("--body");
61
+ const configIdx = args.indexOf("--config");
62
+ if (bodyIdx >= 0 && configIdx >= 0) {
63
+ throw new RequestBodyError({
64
+ subtype: "body_and_config",
65
+ message: "Use either --body or --config, not both",
66
+ hint: "Prefer --config @file for nested / large objects",
67
+ });
68
+ }
69
+ if (configIdx >= 0) {
70
+ const raw = args[configIdx + 1];
71
+ if (!raw || raw.startsWith("--")) {
72
+ throw new RequestBodyError({
73
+ subtype: "missing_config",
74
+ message: "--config requires @path or inline JSON",
75
+ hint: "alphafox <command> --config @./payload.json",
76
+ });
77
+ }
78
+ return { body: loadJsonArg(raw, options), source: "config" };
79
+ }
80
+ if (bodyIdx >= 0) {
81
+ const raw = args[bodyIdx + 1];
82
+ if (!raw || raw.startsWith("--")) {
83
+ throw new RequestBodyError({
84
+ subtype: "missing_body",
85
+ message: "--body requires JSON or @path",
86
+ hint: "Small payloads: --body '{...}'. Large objects: --config @./payload.json",
87
+ });
88
+ }
89
+ return { body: loadJsonArg(raw, options), source: "body" };
90
+ }
91
+ return { body: undefined, source: "none" };
92
+ }
@@ -13,6 +13,11 @@ const client_1 = require("../http/client");
13
13
  const store_1 = require("../keychain/store");
14
14
  const confirmation_1 = require("../safety/confirmation");
15
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");
16
21
  function parseGlobalFlags(argv) {
17
22
  const flags = {
18
23
  format: "json",
@@ -79,6 +84,7 @@ async function runCli(argv, env = process.env) {
79
84
  usage: [
80
85
  "alphafox version",
81
86
  "alphafox doctor",
87
+ "alphafox install [--no-auth|--dry-run]",
82
88
  "alphafox auth login [--no-wait|--device-code CODE|--browser]",
83
89
  "alphafox auth status [--verify]",
84
90
  "alphafox auth logout",
@@ -86,19 +92,22 @@ async function runCli(argv, env = process.env) {
86
92
  "alphafox profile list|use <name>",
87
93
  "alphafox schema [operationId]",
88
94
  "alphafox catalog",
89
- "alphafox api METHOD PATH [--body JSON]",
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",
90
97
  "alphafox <domain> <resource> <action> [flags]",
91
98
  ],
92
99
  }, { format: flags.format, jq: flags.jq });
93
100
  return 0;
94
101
  }
95
102
  try {
96
- if (cmd !== "version") {
103
+ if (cmd !== "version" && cmd !== "install") {
97
104
  assertCatalogCompatible();
98
105
  }
99
106
  switch (cmd) {
100
107
  case "version":
101
108
  return cmdVersion(flags);
109
+ case "install":
110
+ return await cmdInstall(args, flags, env);
102
111
  case "doctor":
103
112
  return cmdDoctor(flags, env);
104
113
  case "whoami":
@@ -113,6 +122,19 @@ async function runCli(argv, env = process.env) {
113
122
  return await cmdApi(args, flags, env);
114
123
  case "catalog":
115
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
+ }
116
138
  default:
117
139
  return await cmdTyped(cmd, args, flags, env);
118
140
  }
@@ -146,6 +168,46 @@ function assertCatalogCompatible() {
146
168
  hint: "Upgrade or pin @alphafox/cli to the catalog minCliVersion/maxCliVersion range.",
147
169
  });
148
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
+ }
149
211
  function cmdVersion(flags) {
150
212
  const compatibility = (0, operations_1.checkGeneratedCatalogCompatibility)(version_1.CLI_VERSION);
151
213
  (0, envelope_1.writeSuccess)({
@@ -626,7 +688,7 @@ async function cmdApi(args, flags, env) {
626
688
  if (!method || !path) {
627
689
  (0, envelope_1.writeError)({
628
690
  type: "usage",
629
- message: "Usage: alphafox api METHOD PATH [--body JSON]",
691
+ message: "Usage: alphafox api METHOD PATH [--body JSON|--config @file]",
630
692
  });
631
693
  }
632
694
  if ((0, allowlist_1.isInternalDisallowedPath)(path) || !(0, allowlist_1.isFacadeAllowlistedPath)(path)) {
@@ -638,15 +700,34 @@ async function cmdApi(args, flags, env) {
638
700
  }, { exitCode: 77 });
639
701
  }
640
702
  let body;
641
- const bodyIdx = args.indexOf("--body");
642
- if (bodyIdx >= 0) {
643
- 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;
644
716
  }
645
717
  const profile = (0, profiles_1.resolveProfile)(flags.profile, env, {
646
718
  unsafeCustomEndpoint: flags.unsafeCustomEndpoint,
647
719
  });
648
720
  // Infer risk from catalog; uncataloged mutations are treated as high-risk.
649
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;
650
731
  const risk = (0, confirmation_1.inferRawApiRisk)(method, catalogHit?.risk);
651
732
  const gate = (0, confirmation_1.assertHighRiskConfirmation)({
652
733
  risk,
@@ -665,6 +746,8 @@ async function cmdApi(args, flags, env) {
665
746
  path,
666
747
  profile: profile.name,
667
748
  risk,
749
+ operationId: catalogHit?.operationId,
750
+ body,
668
751
  }, { format: flags.format, jq: flags.jq });
669
752
  return 0;
670
753
  }
@@ -750,7 +833,7 @@ async function invokeOperation(operationId, args, flags, env) {
750
833
  (0, operations_1.extractPathParamNames)(op.path));
751
834
  for (let i = 0; i < args.length; i += 1) {
752
835
  const a = args[i];
753
- if (a.startsWith("--") && a !== "--body") {
836
+ if (a.startsWith("--") && a !== "--body" && a !== "--config") {
754
837
  const key = a.slice(2);
755
838
  const value = args[++i] ?? "";
756
839
  if (pathParamNames.has(key)) {
@@ -762,10 +845,29 @@ async function invokeOperation(operationId, args, flags, env) {
762
845
  }
763
846
  }
764
847
  let body;
765
- const bodyIdx = args.indexOf("--body");
766
- if (bodyIdx >= 0) {
767
- 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);
768
869
  }
870
+ body = validated.body;
769
871
  let path = (0, operations_1.resolveOperationPath)(op.path, params);
770
872
  if ((op.method === "GET" || op.method === "HEAD") &&
771
873
  Object.keys(extra).length > 0) {
@@ -783,6 +885,7 @@ async function invokeOperation(operationId, args, flags, env) {
783
885
  path,
784
886
  risk: op.risk,
785
887
  profile: profile.name,
888
+ body: op.method === "GET" || op.method === "HEAD" ? undefined : body,
786
889
  }, { format: flags.format, jq: flags.jq });
787
890
  return 0;
788
891
  }
@@ -0,0 +1,18 @@
1
+ export declare class EngineBacktestError extends Error {
2
+ readonly type: string;
3
+ readonly subtype?: string;
4
+ readonly status?: number;
5
+ readonly code?: string | number;
6
+ readonly hint?: string;
7
+ readonly details?: unknown;
8
+ constructor(input: {
9
+ readonly message: string;
10
+ readonly type?: string;
11
+ readonly subtype?: string;
12
+ readonly status?: number;
13
+ readonly code?: string | number;
14
+ readonly hint?: string;
15
+ readonly details?: unknown;
16
+ });
17
+ }
18
+ export declare function isEngineBacktestError(value: unknown): value is EngineBacktestError;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EngineBacktestError = void 0;
4
+ exports.isEngineBacktestError = isEngineBacktestError;
5
+ class EngineBacktestError extends Error {
6
+ type;
7
+ subtype;
8
+ status;
9
+ code;
10
+ hint;
11
+ details;
12
+ constructor(input) {
13
+ super(input.message);
14
+ this.name = "EngineBacktestError";
15
+ this.type = input.type ?? "runtime";
16
+ this.subtype = input.subtype;
17
+ this.status = input.status;
18
+ this.code = input.code;
19
+ this.hint = input.hint;
20
+ this.details = input.details;
21
+ }
22
+ }
23
+ exports.EngineBacktestError = EngineBacktestError;
24
+ function isEngineBacktestError(value) {
25
+ return value instanceof EngineBacktestError;
26
+ }
@@ -0,0 +1,38 @@
1
+ export declare const BACKTEST_RUNTIME_PROTOCOL = 1;
2
+ export declare const DEFAULT_BACKTEST_WASM_MANIFEST_URL = "https://zwggllrrna54e2d6.public.blob.vercel-storage.com/engine-backtest/latest.json";
3
+ export declare const BLOB_RUNTIME_FILES: {
4
+ readonly wasm: "tradingfox-backtest.wasm";
5
+ readonly wasmExec: "wasm_exec.js";
6
+ readonly worker: "worker.mjs";
7
+ readonly client: "index.mjs";
8
+ readonly node: "node.mjs";
9
+ readonly nodeWorker: "worker-node.mjs";
10
+ readonly nodeWorkerPath: "worker-node-path.mjs";
11
+ };
12
+ export type BlobRuntimeFileKey = keyof typeof BLOB_RUNTIME_FILES;
13
+ export interface EngineBacktestBlobManifest {
14
+ readonly version: string;
15
+ readonly hash: string;
16
+ readonly protocol: number;
17
+ readonly engineSha?: string;
18
+ readonly packageVersion?: string;
19
+ readonly wasm: string;
20
+ readonly wasmExec: string;
21
+ readonly worker: string;
22
+ readonly client: string;
23
+ readonly node: string;
24
+ readonly nodeWorker: string;
25
+ readonly nodeWorkerPath: string;
26
+ }
27
+ export interface FetchRuntimeHooks {
28
+ readonly fetch?: typeof fetch;
29
+ readonly cacheDir?: string;
30
+ }
31
+ export declare function resolveBacktestWasmManifestUrl(env?: NodeJS.ProcessEnv): string;
32
+ export declare function resolveRuntimeCacheDir(hash: string, env?: NodeJS.ProcessEnv): string;
33
+ export declare function parseEngineBacktestBlobManifest(value: unknown): EngineBacktestBlobManifest;
34
+ export declare function ensureBlobRuntime(env?: NodeJS.ProcessEnv, hooks?: FetchRuntimeHooks): Promise<{
35
+ readonly manifest: EngineBacktestBlobManifest;
36
+ readonly directory: string;
37
+ readonly nodeEntry: string;
38
+ }>;