@alphafox/cli 0.3.3 → 0.3.5

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 (86) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +28 -9
  3. package/dist/auth/loopback-callback.js +3 -3
  4. package/dist/catalog/allowlist.d.ts +1 -1
  5. package/dist/catalog/allowlist.js +1 -1
  6. package/dist/catalog/generated/registry.json +338 -49
  7. package/dist/catalog/generated/schemas.json +1880 -779
  8. package/dist/catalog/omit.d.ts +6 -0
  9. package/dist/catalog/omit.js +11 -0
  10. package/dist/catalog/operations.d.ts +1 -1
  11. package/dist/catalog/operations.js +5 -2
  12. package/dist/commands/run.js +25 -3
  13. package/dist/engine-backtest/load-config.d.ts +4 -0
  14. package/dist/engine-backtest/load-config.js +44 -0
  15. package/dist/engine-backtest/parse-args.d.ts +3 -1
  16. package/dist/engine-backtest/parse-args.js +87 -3
  17. package/dist/engine-backtest/parse-axes.d.ts +3 -0
  18. package/dist/engine-backtest/parse-axes.js +167 -0
  19. package/dist/engine-backtest/persist.d.ts +60 -1
  20. package/dist/engine-backtest/persist.js +196 -1
  21. package/dist/engine-backtest/return-curve.d.ts +27 -0
  22. package/dist/engine-backtest/return-curve.js +80 -0
  23. package/dist/engine-backtest/run-command.d.ts +1 -4
  24. package/dist/engine-backtest/run-command.js +61 -45
  25. package/dist/engine-backtest/sweep-command.d.ts +13 -0
  26. package/dist/engine-backtest/sweep-command.js +749 -0
  27. package/dist/engine-backtest/sweep-kernel/caps.d.ts +26 -0
  28. package/dist/engine-backtest/sweep-kernel/caps.js +48 -0
  29. package/dist/engine-backtest/sweep-kernel/index.d.ts +13 -0
  30. package/dist/engine-backtest/sweep-kernel/index.js +34 -0
  31. package/dist/engine-backtest/sweep-kernel/plan.d.ts +22 -0
  32. package/dist/engine-backtest/sweep-kernel/plan.js +320 -0
  33. package/dist/engine-backtest/sweep-kernel/results.d.ts +72 -0
  34. package/dist/engine-backtest/sweep-kernel/results.js +150 -0
  35. package/dist/engine-backtest/sweep-kernel/session.d.ts +55 -0
  36. package/dist/engine-backtest/sweep-kernel/session.js +56 -0
  37. package/dist/engine-backtest/sweep-kernel/types.d.ts +36 -0
  38. package/dist/engine-backtest/sweep-kernel/types.js +2 -0
  39. package/dist/engine-backtest/types.d.ts +130 -0
  40. package/dist/index.d.ts +3 -0
  41. package/dist/index.js +16 -1
  42. package/dist/install/types.d.ts +8 -1
  43. package/dist/install/types.js +1 -0
  44. package/dist/install/wizard.js +92 -55
  45. package/dist/keychain/windows-credential.d.ts +1 -1
  46. package/dist/keychain/windows-credential.js +1 -1
  47. package/dist/resolve-symbols/catalog.d.ts +3 -0
  48. package/dist/resolve-symbols/catalog.js +50 -0
  49. package/dist/resolve-symbols/errors.d.ts +18 -0
  50. package/dist/resolve-symbols/errors.js +26 -0
  51. package/dist/resolve-symbols/exchanges.d.ts +8 -0
  52. package/dist/resolve-symbols/exchanges.js +53 -0
  53. package/dist/resolve-symbols/match.d.ts +6 -0
  54. package/dist/resolve-symbols/match.js +250 -0
  55. package/dist/resolve-symbols/parse-args.d.ts +5 -0
  56. package/dist/resolve-symbols/parse-args.js +106 -0
  57. package/dist/resolve-symbols/run-command.d.ts +21 -0
  58. package/dist/resolve-symbols/run-command.js +149 -0
  59. package/dist/resolve-symbols/types.d.ts +37 -0
  60. package/dist/resolve-symbols/types.js +2 -0
  61. package/dist/skills/manager.d.ts +96 -0
  62. package/dist/skills/manager.js +445 -0
  63. package/dist/skills/run-command.d.ts +22 -0
  64. package/dist/skills/run-command.js +143 -0
  65. package/dist/skills-manifest.json +141 -0
  66. package/dist/update/run-command.d.ts +41 -0
  67. package/dist/update/run-command.js +204 -0
  68. package/dist/version.d.ts +1 -1
  69. package/dist/version.js +1 -1
  70. package/docs/agents/issue-tracker.md +2 -2
  71. package/docs/agents/triage-labels.md +1 -1
  72. package/docs/alphafox-cli-installation-guide.md +16 -17
  73. package/docs/index.html +11 -11
  74. package/docs/release-supply-chain.md +11 -6
  75. package/package.json +2 -2
  76. package/skills/account/SKILL.md +1 -1
  77. package/skills/admin/SKILL.md +1 -1
  78. package/skills/alphafox/SKILL.md +38 -0
  79. package/skills/alphafox-shared/SKILL.md +20 -4
  80. package/skills/auth/SKILL.md +2 -2
  81. package/skills/engine-backtest/SKILL.md +48 -6
  82. package/skills/exchange/SKILL.md +1 -1
  83. package/skills/market/SKILL.md +27 -4
  84. package/skills/notification/SKILL.md +1 -1
  85. package/skills/strategy/SKILL.md +7 -20
  86. package/skills/trading/SKILL.md +3 -1
@@ -11,6 +11,8 @@ exports.resolveLocalSkillsDir = resolveLocalSkillsDir;
11
11
  const node_fs_1 = require("node:fs");
12
12
  const node_path_1 = require("node:path");
13
13
  const version_1 = require("../version");
14
+ const manager_1 = require("../skills/manager");
15
+ const run_command_1 = require("../skills/run-command");
14
16
  const exec_1 = require("./exec");
15
17
  const package_root_1 = require("./package-root");
16
18
  const types_1 = require("./types");
@@ -49,8 +51,7 @@ function parseNpmListVersion(output) {
49
51
  return match?.[1] ?? null;
50
52
  }
51
53
  function skillsListHasAlphafox(output) {
52
- const prefix = types_1.SKILLS_NAME_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
53
- return new RegExp(`(^|\\s)${prefix}[\\w-]+`, "m").test(output);
54
+ return /(^|\s)alphafox(?:-[\w-]+)?(?=\s|$)/m.test(output);
54
55
  }
55
56
  function nextSteps(input) {
56
57
  const steps = ["请重启 AI 工具,以便加载刚安装的 Skills。"];
@@ -64,7 +65,7 @@ function nextSteps(input) {
64
65
  return steps;
65
66
  }
66
67
  async function runInstallWizard(flags, env = process.env, runner = (0, exec_1.createDefaultInstallRunner)(env, defaultSearchDirs())) {
67
- runner.log("正在安装 Alphafox CLI…");
68
+ runner.log("正在安装 AlphaFox CLI…");
68
69
  const installedVer = await readGloballyInstalledVersion(runner);
69
70
  const latestVer = await readLatestVersion(runner);
70
71
  const needsUpgrade = Boolean(installedVer) &&
@@ -181,72 +182,108 @@ async function stepInstallCli(flags, runner, versions) {
181
182
  latestVersion: latestVer ?? undefined,
182
183
  };
183
184
  }
184
- async function resolveSkillsSources(runner) {
185
+ async function resolveSkillsSource(runner) {
185
186
  const override = runner.env.ALPHAFOX_SKILLS_SOURCE?.trim();
186
- if (override)
187
- return [override];
188
- const sources = [];
187
+ if (override) {
188
+ if ((0, package_root_1.packageHasSkills)(override))
189
+ return override;
190
+ throw new types_1.InstallError({
191
+ type: "install",
192
+ subtype: "skills_source_invalid",
193
+ message: "ALPHAFOX_SKILLS_SOURCE must point to a local @alphafox/cli package with bundled Skills.",
194
+ details: override,
195
+ });
196
+ }
189
197
  const npmRoot = await npmRootGlobal(runner);
190
198
  if (npmRoot) {
191
199
  const globalRoot = (0, package_root_1.globalPackageRoot)(npmRoot);
192
200
  if ((0, package_root_1.packageHasSkills)(globalRoot))
193
- sources.push(globalRoot);
201
+ return globalRoot;
194
202
  }
195
203
  const localRoot = (0, package_root_1.findAlphafoxPackageRoot)(runner.packageSearchDirs());
196
- if (localRoot && !sources.includes(localRoot) && (0, package_root_1.packageHasSkills)(localRoot)) {
197
- sources.push(localRoot);
198
- }
199
- sources.push(types_1.SKILLS_GITHUB_SOURCE);
200
- return sources;
201
- }
202
- async function skillsAlreadyInstalled(runner) {
203
- try {
204
- const { stdout, stderr } = await runner.exec("npx", ["-y", "skills", "ls", "-g"], { timeoutMs: SKILLS_TIMEOUT_MS });
205
- return skillsListHasAlphafox(`${stdout}\n${stderr}`);
206
- }
207
- catch {
208
- return false;
204
+ if (localRoot && (0, package_root_1.packageHasSkills)(localRoot)) {
205
+ return localRoot;
209
206
  }
207
+ throw new types_1.InstallError({
208
+ type: "install",
209
+ subtype: "skills_package_missing",
210
+ message: "Could not locate the verified Skills bundle inside @alphafox/cli.",
211
+ hint: `npm install -g ${version_1.CLI_PACKAGE}`,
212
+ });
210
213
  }
211
214
  async function stepInstallSkills(flags, runner) {
212
- const sources = await resolveSkillsSources(runner);
213
- const already = flags.dryRun ? false : await skillsAlreadyInstalled(runner);
214
- if (already) {
215
- runner.log("Skills 已安装,已跳过");
216
- return {
217
- action: "skipped",
218
- source: sources[0],
219
- scope: "global",
220
- alreadyPresent: true,
221
- };
222
- }
223
- if (flags.dryRun) {
224
- runner.log(`将从 ${sources[0] ?? types_1.SKILLS_GITHUB_SOURCE} 安装 Skills`);
215
+ const source = await resolveSkillsSource(runner);
216
+ const manifest = (0, manager_1.loadAndVerifySkillsManifest)(source);
217
+ runner.log(flags.dryRun
218
+ ? `将同步 Skills ${manifest.packageVersion}(${source})`
219
+ : `正在同步 AI Skills ${manifest.packageVersion}…`);
220
+ try {
221
+ const result = await (0, manager_1.syncSkills)({
222
+ manifest,
223
+ packageRoot: source,
224
+ installedRoot: (0, run_command_1.installedSkillsRoot)(runner.env),
225
+ statePath: (0, run_command_1.skillsStatePath)(runner.env),
226
+ dryRun: flags.dryRun,
227
+ force: false,
228
+ }, {
229
+ install: async (names) => {
230
+ await runner.exec("npx", [
231
+ "-y",
232
+ "skills",
233
+ "add",
234
+ source,
235
+ "-y",
236
+ "-g",
237
+ "--skill",
238
+ ...names,
239
+ ], { timeoutMs: SKILLS_TIMEOUT_MS });
240
+ },
241
+ remove: async (names) => {
242
+ await runner.exec("npx", ["-y", "skills", "remove", ...names, "-y", "-g"], { timeoutMs: SKILLS_TIMEOUT_MS });
243
+ },
244
+ });
245
+ const action = result.blocked.length > 0
246
+ ? "blocked"
247
+ : result.action === "planned"
248
+ ? "planned"
249
+ : result.action === "skipped"
250
+ ? "skipped"
251
+ : result.backupDir
252
+ ? "updated"
253
+ : "installed";
254
+ runner.log(action === "skipped"
255
+ ? "Skills 已是当前版本,已跳过"
256
+ : action === "blocked"
257
+ ? `检测到本地修改,未覆盖:${result.blocked.join(", ")}`
258
+ : flags.dryRun
259
+ ? `将同步 ${result.updated.length} 个 Skills`
260
+ : `Skills 已同步(${source})`);
225
261
  return {
226
- action: "planned",
227
- source: sources[0] ?? types_1.SKILLS_GITHUB_SOURCE,
262
+ action,
263
+ source,
228
264
  scope: "global",
265
+ alreadyPresent: result.status.summary.current > 0,
266
+ version: manifest.packageVersion,
267
+ updated: result.updated,
268
+ removed: result.removed,
269
+ blocked: result.blocked,
270
+ backupDir: result.backupDir,
271
+ restartRequired: result.restartRequired,
229
272
  };
230
273
  }
231
- runner.log("正在安装 AI Skills…");
232
- let lastError;
233
- for (const source of sources) {
234
- try {
235
- await runner.exec("npx", ["-y", "skills", "add", source, "-y", "-g"], { timeoutMs: SKILLS_TIMEOUT_MS });
236
- runner.log(`Skills 已安装(${source})`);
237
- return { action: "installed", source, scope: "global" };
238
- }
239
- catch (err) {
240
- lastError = err;
241
- }
274
+ catch (err) {
275
+ if (err instanceof types_1.InstallError)
276
+ throw err;
277
+ throw new types_1.InstallError({
278
+ type: "install",
279
+ subtype: err && typeof err === "object" && "subtype" in err
280
+ ? String(err.subtype)
281
+ : "skills_sync_failed",
282
+ message: "同步 Agent Skills 失败。",
283
+ hint: `alphafox skills sync --format json --no-input`,
284
+ details: err instanceof Error ? err.message : String(err),
285
+ });
242
286
  }
243
- throw new types_1.InstallError({
244
- type: "install",
245
- subtype: "skills_add_failed",
246
- message: "安装 Agent Skills 失败。",
247
- hint: `npx skills add ${types_1.SKILLS_GITHUB_SOURCE} -y -g`,
248
- details: lastError instanceof Error ? lastError.message : String(lastError),
249
- });
250
287
  }
251
288
  async function resolveAlphafoxBin(runner) {
252
289
  const prefix = await npmPrefixGlobal(runner);
@@ -317,7 +354,7 @@ function installHelpData() {
317
354
  "alphafox install --no-auth",
318
355
  "alphafox install --dry-run",
319
356
  ],
320
- description: "全局安装 @alphafox/cli,并通过 npx skills add 把同版本 Agent Skills 写入本机已检测到的 Agent(Cursor、Claude Code、Codex 等)的用户级目录。",
357
+ description: "全局安装 @alphafox/cli,校验包内 Skills manifest,并把同版本 Agent Skills 同步到本机已检测到的 Agent(Cursor、Claude Code、Codex 等)的用户级目录。",
321
358
  agentGuide: types_1.AGENT_INSTALL_GUIDE_BLOB_URL,
322
359
  };
323
360
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /** CRED_MAX_CREDENTIAL_BLOB_SIZE is 5*512 = 2560. */
6
6
  export declare const WINDOWS_CRED_MAX_BYTES = 2560;
7
- export declare const WINDOWS_CRED_PS1 = "# Alphafox CLI \u2014 Windows Credential Manager helper (t101360)\nparam(\n [Parameter(Mandatory = $true)][ValidateSet('write','read','delete')][string]$Action,\n [Parameter(Mandatory = $true)][string]$Target\n)\n$ErrorActionPreference = 'Stop'\nAdd-Type -TypeDefinition @\"\nusing System;\nusing System.Runtime.InteropServices;\nnamespace AlphafoxCred {\n [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]\n public struct CREDENTIAL {\n public uint Flags;\n public uint Type;\n public string TargetName;\n public string Comment;\n public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;\n public uint CredentialBlobSize;\n public IntPtr CredentialBlob;\n public uint Persist;\n public uint AttributeCount;\n public IntPtr Attributes;\n public string TargetAlias;\n public string UserName;\n }\n public static class Native {\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredWrite(ref CREDENTIAL credential, uint flags);\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredRead(string target, uint type, uint flags, out IntPtr credentialPtr);\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredDelete(string target, uint type, uint flags);\n [DllImport(\"advapi32.dll\", SetLastError = true)]\n public static extern void CredFree(IntPtr credential);\n }\n}\n\"@\n$CredTypeGeneric = 1\n$PersistLocalMachine = 2\nswitch ($Action) {\n 'write' {\n $payload = [Console]::In.ReadToEnd()\n $bytes = [Text.Encoding]::UTF8.GetBytes($payload)\n if ($bytes.Length -gt 2560) { throw \"credential blob too large\" }\n $blob = [Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)\n try {\n [Runtime.InteropServices.Marshal]::Copy($bytes, 0, $blob, $bytes.Length)\n $cred = New-Object AlphafoxCred.CREDENTIAL\n $cred.Type = $CredTypeGeneric\n $cred.TargetName = $Target\n $cred.UserName = \"alphafox-cli\"\n $cred.CredentialBlobSize = [uint32]$bytes.Length\n $cred.CredentialBlob = $blob\n $cred.Persist = $PersistLocalMachine\n $ok = [AlphafoxCred.Native]::CredWrite([ref]$cred, 0)\n if (-not $ok) {\n throw \"CredWrite failed Win32=$([Runtime.InteropServices.Marshal]::GetLastWin32Error())\"\n }\n } finally {\n [Runtime.InteropServices.Marshal]::FreeHGlobal($blob)\n }\n }\n 'read' {\n $ptr = [IntPtr]::Zero\n $ok = [AlphafoxCred.Native]::CredRead($Target, $CredTypeGeneric, 0, [ref]$ptr)\n if (-not $ok) { exit 2 }\n try {\n $cred = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [type][AlphafoxCred.CREDENTIAL])\n $size = [int]$cred.CredentialBlobSize\n $bytes = New-Object byte[] $size\n [Runtime.InteropServices.Marshal]::Copy($cred.CredentialBlob, $bytes, 0, $size)\n [Console]::Out.Write([Text.Encoding]::UTF8.GetString($bytes))\n } finally {\n [AlphafoxCred.Native]::CredFree($ptr)\n }\n }\n 'delete' {\n [void][AlphafoxCred.Native]::CredDelete($Target, $CredTypeGeneric, 0)\n }\n}\n";
7
+ export declare const WINDOWS_CRED_PS1 = "# AlphaFox CLI \u2014 Windows Credential Manager helper (t101360)\nparam(\n [Parameter(Mandatory = $true)][ValidateSet('write','read','delete')][string]$Action,\n [Parameter(Mandatory = $true)][string]$Target\n)\n$ErrorActionPreference = 'Stop'\nAdd-Type -TypeDefinition @\"\nusing System;\nusing System.Runtime.InteropServices;\nnamespace AlphafoxCred {\n [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]\n public struct CREDENTIAL {\n public uint Flags;\n public uint Type;\n public string TargetName;\n public string Comment;\n public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;\n public uint CredentialBlobSize;\n public IntPtr CredentialBlob;\n public uint Persist;\n public uint AttributeCount;\n public IntPtr Attributes;\n public string TargetAlias;\n public string UserName;\n }\n public static class Native {\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredWrite(ref CREDENTIAL credential, uint flags);\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredRead(string target, uint type, uint flags, out IntPtr credentialPtr);\n [DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n public static extern bool CredDelete(string target, uint type, uint flags);\n [DllImport(\"advapi32.dll\", SetLastError = true)]\n public static extern void CredFree(IntPtr credential);\n }\n}\n\"@\n$CredTypeGeneric = 1\n$PersistLocalMachine = 2\nswitch ($Action) {\n 'write' {\n $payload = [Console]::In.ReadToEnd()\n $bytes = [Text.Encoding]::UTF8.GetBytes($payload)\n if ($bytes.Length -gt 2560) { throw \"credential blob too large\" }\n $blob = [Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)\n try {\n [Runtime.InteropServices.Marshal]::Copy($bytes, 0, $blob, $bytes.Length)\n $cred = New-Object AlphafoxCred.CREDENTIAL\n $cred.Type = $CredTypeGeneric\n $cred.TargetName = $Target\n $cred.UserName = \"alphafox-cli\"\n $cred.CredentialBlobSize = [uint32]$bytes.Length\n $cred.CredentialBlob = $blob\n $cred.Persist = $PersistLocalMachine\n $ok = [AlphafoxCred.Native]::CredWrite([ref]$cred, 0)\n if (-not $ok) {\n throw \"CredWrite failed Win32=$([Runtime.InteropServices.Marshal]::GetLastWin32Error())\"\n }\n } finally {\n [Runtime.InteropServices.Marshal]::FreeHGlobal($blob)\n }\n }\n 'read' {\n $ptr = [IntPtr]::Zero\n $ok = [AlphafoxCred.Native]::CredRead($Target, $CredTypeGeneric, 0, [ref]$ptr)\n if (-not $ok) { exit 2 }\n try {\n $cred = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [type][AlphafoxCred.CREDENTIAL])\n $size = [int]$cred.CredentialBlobSize\n $bytes = New-Object byte[] $size\n [Runtime.InteropServices.Marshal]::Copy($cred.CredentialBlob, $bytes, 0, $size)\n [Console]::Out.Write([Text.Encoding]::UTF8.GetString($bytes))\n } finally {\n [AlphafoxCred.Native]::CredFree($ptr)\n }\n }\n 'delete' {\n [void][AlphafoxCred.Native]::CredDelete($Target, $CredTypeGeneric, 0)\n }\n}\n";
8
8
  export declare function windowsCredentialTarget(profile: string): string;
9
9
  export declare function windowsPowershellBin(env?: NodeJS.ProcessEnv): string;
10
10
  export declare function windowsCredentialWrite(profile: string, payload: string, env?: NodeJS.ProcessEnv): boolean;
@@ -17,7 +17,7 @@ const node_os_1 = require("node:os");
17
17
  const node_path_1 = require("node:path");
18
18
  /** CRED_MAX_CREDENTIAL_BLOB_SIZE is 5*512 = 2560. */
19
19
  exports.WINDOWS_CRED_MAX_BYTES = 2560;
20
- exports.WINDOWS_CRED_PS1 = `# Alphafox CLI — Windows Credential Manager helper (t101360)
20
+ exports.WINDOWS_CRED_PS1 = `# AlphaFox CLI — Windows Credential Manager helper (t101360)
21
21
  param(
22
22
  [Parameter(Mandatory = $true)][ValidateSet('write','read','delete')][string]$Action,
23
23
  [Parameter(Mandatory = $true)][string]$Target
@@ -0,0 +1,3 @@
1
+ export declare const MARKET_SYMBOLS_PATH = "/api/v1/market/symbols";
2
+ export declare function marketSymbolsPath(exchangeId: string): string;
3
+ export declare function extractCatalogSymbols(json: unknown): string[];
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MARKET_SYMBOLS_PATH = void 0;
4
+ exports.marketSymbolsPath = marketSymbolsPath;
5
+ exports.extractCatalogSymbols = extractCatalogSymbols;
6
+ const errors_1 = require("./errors");
7
+ exports.MARKET_SYMBOLS_PATH = "/api/v1/market/symbols";
8
+ function marketSymbolsPath(exchangeId) {
9
+ const query = new URLSearchParams({ exchange: exchangeId }).toString();
10
+ return `${exports.MARKET_SYMBOLS_PATH}?${query}`;
11
+ }
12
+ function extractCatalogSymbols(json) {
13
+ const root = asRecord(json);
14
+ const payload = asRecord(root?.data) ?? root;
15
+ const symbols = payload?.symbols;
16
+ if (!Array.isArray(symbols)) {
17
+ throw new errors_1.ResolveSymbolsError({
18
+ type: "runtime",
19
+ subtype: "catalog_invalid",
20
+ message: "market.symbols.list response did not include a symbols array",
21
+ details: json,
22
+ });
23
+ }
24
+ const out = [];
25
+ const seen = new Set();
26
+ for (const item of symbols) {
27
+ if (typeof item !== "string")
28
+ continue;
29
+ const trimmed = item.trim();
30
+ if (!trimmed || seen.has(trimmed))
31
+ continue;
32
+ seen.add(trimmed);
33
+ out.push(trimmed);
34
+ }
35
+ if (out.length === 0) {
36
+ throw new errors_1.ResolveSymbolsError({
37
+ type: "runtime",
38
+ subtype: "catalog_empty",
39
+ message: "market.symbols.list returned no contract symbols",
40
+ details: json,
41
+ });
42
+ }
43
+ return out;
44
+ }
45
+ function asRecord(value) {
46
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
47
+ return undefined;
48
+ }
49
+ return value;
50
+ }
@@ -0,0 +1,18 @@
1
+ export declare class ResolveSymbolsError 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 isResolveSymbolsError(value: unknown): value is ResolveSymbolsError;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResolveSymbolsError = void 0;
4
+ exports.isResolveSymbolsError = isResolveSymbolsError;
5
+ class ResolveSymbolsError extends Error {
6
+ type;
7
+ subtype;
8
+ status;
9
+ code;
10
+ hint;
11
+ details;
12
+ constructor(input) {
13
+ super(input.message);
14
+ this.name = "ResolveSymbolsError";
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.ResolveSymbolsError = ResolveSymbolsError;
24
+ function isResolveSymbolsError(value) {
25
+ return value instanceof ResolveSymbolsError;
26
+ }
@@ -0,0 +1,8 @@
1
+ export declare const RESOLVE_SYMBOLS_DEFAULT_EXCHANGE = "binance_perp_usdt";
2
+ export interface ResolveSymbolsExchange {
3
+ readonly id: string;
4
+ readonly label: string;
5
+ readonly aliases: readonly string[];
6
+ }
7
+ export declare const RESOLVE_SYMBOLS_EXCHANGES: readonly ResolveSymbolsExchange[];
8
+ export declare function resolveSymbolsExchangeId(raw: string): ResolveSymbolsExchange;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RESOLVE_SYMBOLS_EXCHANGES = exports.RESOLVE_SYMBOLS_DEFAULT_EXCHANGE = void 0;
4
+ exports.resolveSymbolsExchangeId = resolveSymbolsExchangeId;
5
+ exports.RESOLVE_SYMBOLS_DEFAULT_EXCHANGE = "binance_perp_usdt";
6
+ exports.RESOLVE_SYMBOLS_EXCHANGES = [
7
+ {
8
+ id: "binance_perp_usdt",
9
+ label: "Binance",
10
+ aliases: ["binance", "binanceusdm", "binance_perp_usdt"],
11
+ },
12
+ {
13
+ id: "okx_perp_usdt",
14
+ label: "OKX",
15
+ aliases: ["okx", "okx_perp_usdt"],
16
+ },
17
+ {
18
+ id: "bybit_perp_usdt",
19
+ label: "Bybit",
20
+ aliases: ["bybit", "bybit_perp_usdt"],
21
+ },
22
+ {
23
+ id: "bitget_perp_usdt",
24
+ label: "Bitget",
25
+ aliases: ["bitget", "bitget_perp_usdt"],
26
+ },
27
+ {
28
+ id: "hyperliquid_perp_usdc",
29
+ label: "HyperLiquid",
30
+ aliases: ["hyperliquid", "hyperliquid_perp_usdc"],
31
+ },
32
+ {
33
+ id: "aster_perp_usdt",
34
+ label: "Aster",
35
+ aliases: ["aster", "aster_perp_usdt"],
36
+ },
37
+ ];
38
+ const EXCHANGE_BY_ALIAS = new Map();
39
+ for (const exchange of exports.RESOLVE_SYMBOLS_EXCHANGES) {
40
+ EXCHANGE_BY_ALIAS.set(exchange.id, exchange);
41
+ for (const alias of exchange.aliases) {
42
+ EXCHANGE_BY_ALIAS.set(alias.toLowerCase(), exchange);
43
+ }
44
+ }
45
+ function resolveSymbolsExchangeId(raw) {
46
+ const key = raw.trim().toLowerCase();
47
+ const exchange = EXCHANGE_BY_ALIAS.get(key);
48
+ if (!exchange) {
49
+ const allowed = exports.RESOLVE_SYMBOLS_EXCHANGES.map((item) => item.aliases[0]).join("|");
50
+ throw new Error(`--exchange must be ${allowed} (got ${raw.trim() || "<empty>"})`);
51
+ }
52
+ return exchange;
53
+ }
@@ -0,0 +1,6 @@
1
+ import type { CatalogSymbol, ResolveSymbolsQueryResult } from "./types";
2
+ export declare function normalizeSymbolSearchKey(value: string): string;
3
+ export declare function parseCatalogSymbol(symbol: string): CatalogSymbol | null;
4
+ export declare function indexCatalogSymbols(symbols: readonly string[]): readonly CatalogSymbol[];
5
+ export declare function resolveQueryAgainstCatalog(query: string, catalog: readonly CatalogSymbol[], limit?: number): ResolveSymbolsQueryResult;
6
+ export declare function levenshtein(left: string, right: string): number;
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeSymbolSearchKey = normalizeSymbolSearchKey;
4
+ exports.parseCatalogSymbol = parseCatalogSymbol;
5
+ exports.indexCatalogSymbols = indexCatalogSymbols;
6
+ exports.resolveQueryAgainstCatalog = resolveQueryAgainstCatalog;
7
+ exports.levenshtein = levenshtein;
8
+ const LINEAR_PERP_PATTERN = /^([\p{L}\p{N}]+(?:-[\p{L}\p{N}]+)*)\/(USDT|USDC|USD):\2$/u;
9
+ const QUOTE_ASSETS = ["USDT", "USDC", "BUSD", "USD"];
10
+ const EXACT_REASONS = new Set([
11
+ "exact_canonical",
12
+ "exact_compact",
13
+ "exact_pair",
14
+ "exact_base",
15
+ ]);
16
+ function normalizeSymbolSearchKey(value) {
17
+ return value
18
+ .trim()
19
+ .toUpperCase()
20
+ .replace(/[^\p{L}\p{N}]/gu, "");
21
+ }
22
+ function parseCatalogSymbol(symbol) {
23
+ const trimmed = symbol.trim();
24
+ if (!trimmed)
25
+ return null;
26
+ const upper = trimmed.toUpperCase();
27
+ const linear = LINEAR_PERP_PATTERN.exec(upper);
28
+ if (linear) {
29
+ const base = linear[1];
30
+ const quote = linear[2];
31
+ const canonical = `${base}/${quote}:${quote}`;
32
+ const compact = `${base}${quote}`;
33
+ return {
34
+ symbol: canonical,
35
+ base,
36
+ quote,
37
+ pair: `${base}/${quote}`,
38
+ compact,
39
+ searchKey: normalizeSymbolSearchKey(canonical),
40
+ baseKey: normalizeSymbolSearchKey(base),
41
+ compactKey: normalizeSymbolSearchKey(compact),
42
+ };
43
+ }
44
+ const slash = upper.indexOf("/");
45
+ const colon = upper.lastIndexOf(":");
46
+ if (slash > 0) {
47
+ const base = upper.slice(0, slash);
48
+ const quote = colon > slash ? upper.slice(slash + 1, colon) : upper.slice(slash + 1);
49
+ const compact = `${base}${quote}`;
50
+ return {
51
+ symbol: upper,
52
+ base,
53
+ quote,
54
+ pair: `${base}/${quote}`,
55
+ compact,
56
+ searchKey: normalizeSymbolSearchKey(upper),
57
+ baseKey: normalizeSymbolSearchKey(base),
58
+ compactKey: normalizeSymbolSearchKey(compact),
59
+ };
60
+ }
61
+ return {
62
+ symbol: upper,
63
+ base: upper,
64
+ quote: "",
65
+ pair: upper,
66
+ compact: upper,
67
+ searchKey: normalizeSymbolSearchKey(upper),
68
+ baseKey: normalizeSymbolSearchKey(upper),
69
+ compactKey: normalizeSymbolSearchKey(upper),
70
+ };
71
+ }
72
+ function indexCatalogSymbols(symbols) {
73
+ const seen = new Set();
74
+ const indexed = [];
75
+ for (const raw of symbols) {
76
+ const parsed = parseCatalogSymbol(raw);
77
+ if (!parsed)
78
+ continue;
79
+ if (seen.has(parsed.symbol))
80
+ continue;
81
+ seen.add(parsed.symbol);
82
+ indexed.push(parsed);
83
+ }
84
+ return indexed;
85
+ }
86
+ function resolveQueryAgainstCatalog(query, catalog, limit = 8) {
87
+ const trimmed = query.trim();
88
+ const matches = rankMatches(trimmed, catalog);
89
+ const exact = matches.filter((item) => EXACT_REASONS.has(item.reason));
90
+ const close = matches.filter((item) => !EXACT_REASONS.has(item.reason));
91
+ const capped = (items) => items.slice(0, Math.max(1, limit));
92
+ if (exact.length === 1) {
93
+ return {
94
+ query: trimmed,
95
+ status: "exact",
96
+ resolved: exact[0].symbol,
97
+ needsConfirmation: false,
98
+ matches: exact,
99
+ matchCount: exact.length,
100
+ };
101
+ }
102
+ if (exact.length > 1) {
103
+ return {
104
+ query: trimmed,
105
+ status: "ambiguous",
106
+ resolved: null,
107
+ needsConfirmation: true,
108
+ matches: capped(exact),
109
+ matchCount: exact.length,
110
+ };
111
+ }
112
+ if (close.length === 1) {
113
+ return {
114
+ query: trimmed,
115
+ status: "close",
116
+ resolved: close[0].symbol,
117
+ needsConfirmation: true,
118
+ matches: close,
119
+ matchCount: 1,
120
+ };
121
+ }
122
+ if (close.length > 1) {
123
+ return {
124
+ query: trimmed,
125
+ status: "ambiguous",
126
+ resolved: null,
127
+ needsConfirmation: true,
128
+ matches: capped(close),
129
+ matchCount: close.length,
130
+ };
131
+ }
132
+ return {
133
+ query: trimmed,
134
+ status: "none",
135
+ resolved: null,
136
+ needsConfirmation: false,
137
+ matches: [],
138
+ matchCount: 0,
139
+ };
140
+ }
141
+ function rankMatches(query, catalog) {
142
+ const canonicalQuery = query.trim().toUpperCase();
143
+ const queryKey = normalizeSymbolSearchKey(query);
144
+ if (!canonicalQuery || !queryKey)
145
+ return [];
146
+ if (isQuoteAssetKey(queryKey))
147
+ return [];
148
+ const queryBaseKey = queryBaseSearchKey(canonicalQuery, queryKey);
149
+ const scored = [];
150
+ for (const item of catalog) {
151
+ const ranked = rankCatalogSymbol(item, canonicalQuery, queryKey, queryBaseKey);
152
+ if (ranked)
153
+ scored.push(ranked);
154
+ }
155
+ scored.sort((left, right) => {
156
+ if (right.score !== left.score)
157
+ return right.score - left.score;
158
+ return left.symbol.localeCompare(right.symbol);
159
+ });
160
+ return scored;
161
+ }
162
+ function rankCatalogSymbol(item, canonicalQuery, queryKey, queryBaseKey) {
163
+ if (item.symbol === canonicalQuery) {
164
+ return match(item.symbol, "exact_canonical", 100);
165
+ }
166
+ if (item.pair === canonicalQuery) {
167
+ return match(item.symbol, "exact_pair", 96);
168
+ }
169
+ if (item.compactKey === queryKey || item.searchKey === queryKey) {
170
+ return match(item.symbol, "exact_compact", 98);
171
+ }
172
+ if (item.baseKey === queryKey || item.baseKey === queryBaseKey) {
173
+ return match(item.symbol, "exact_base", 95);
174
+ }
175
+ let best = null;
176
+ const consider = (reason, score) => {
177
+ if (!best || score > best.score) {
178
+ best = match(item.symbol, reason, score);
179
+ }
180
+ };
181
+ if (queryBaseKey.length >= 2 && item.baseKey.startsWith(queryBaseKey)) {
182
+ consider("prefix", clampScore(80 - (item.baseKey.length - queryBaseKey.length)));
183
+ }
184
+ if (queryBaseKey.length >= 3 && queryBaseKey.startsWith(item.baseKey)) {
185
+ consider("prefix", clampScore(74 - (queryBaseKey.length - item.baseKey.length)));
186
+ }
187
+ if (queryBaseKey.length >= 3 && item.baseKey.includes(queryBaseKey)) {
188
+ consider("contains", 55);
189
+ }
190
+ const distance = levenshtein(queryBaseKey, item.baseKey);
191
+ if (isCloseDistance(queryBaseKey, item.baseKey, distance)) {
192
+ consider("close", clampScore(48 - distance * 8));
193
+ }
194
+ return best;
195
+ }
196
+ function queryBaseSearchKey(canonicalQuery, queryKey) {
197
+ const linear = LINEAR_PERP_PATTERN.exec(canonicalQuery);
198
+ if (linear)
199
+ return normalizeSymbolSearchKey(linear[1]);
200
+ const slash = canonicalQuery.indexOf("/");
201
+ if (slash > 0) {
202
+ return normalizeSymbolSearchKey(canonicalQuery.slice(0, slash));
203
+ }
204
+ for (const quote of QUOTE_ASSETS) {
205
+ if (queryKey.length > quote.length && queryKey.endsWith(quote)) {
206
+ return queryKey.slice(0, -quote.length);
207
+ }
208
+ }
209
+ return queryKey;
210
+ }
211
+ function isQuoteAssetKey(queryKey) {
212
+ return QUOTE_ASSETS.includes(queryKey);
213
+ }
214
+ function isCloseDistance(left, right, distance) {
215
+ const shortest = Math.min(left.length, right.length);
216
+ if (shortest < 2 || distance <= 0)
217
+ return false;
218
+ if (shortest <= 4)
219
+ return distance === 1;
220
+ return distance <= 2;
221
+ }
222
+ function match(symbol, reason, score) {
223
+ return { symbol, reason, score };
224
+ }
225
+ function clampScore(score) {
226
+ return Math.max(1, Math.min(94, score));
227
+ }
228
+ function levenshtein(left, right) {
229
+ if (left === right)
230
+ return 0;
231
+ if (left.length === 0)
232
+ return right.length;
233
+ if (right.length === 0)
234
+ return left.length;
235
+ const prev = new Array(right.length + 1);
236
+ const next = new Array(right.length + 1);
237
+ for (let j = 0; j <= right.length; j += 1)
238
+ prev[j] = j;
239
+ for (let i = 1; i <= left.length; i += 1) {
240
+ next[0] = i;
241
+ const leftCh = left.charCodeAt(i - 1);
242
+ for (let j = 1; j <= right.length; j += 1) {
243
+ const cost = leftCh === right.charCodeAt(j - 1) ? 0 : 1;
244
+ next[j] = Math.min((prev[j] ?? 0) + 1, (next[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
245
+ }
246
+ for (let j = 0; j <= right.length; j += 1)
247
+ prev[j] = next[j] ?? 0;
248
+ }
249
+ return prev[right.length] ?? Math.max(left.length, right.length);
250
+ }
@@ -0,0 +1,5 @@
1
+ import type { ResolveSymbolsRunArgs } from "./types";
2
+ export declare const RESOLVE_SYMBOLS_DEFAULT_LIMIT = 8;
3
+ export declare const RESOLVE_SYMBOLS_MAX_LIMIT = 25;
4
+ export declare const RESOLVE_SYMBOLS_USAGE: string[];
5
+ export declare function parseResolveSymbolsArgs(args: readonly string[]): ResolveSymbolsRunArgs;