@zltquant/zlt-mcp 0.7.2-beta.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zltquant/zlt-mcp",
3
- "version": "0.7.2-beta.0",
3
+ "version": "0.7.2",
4
4
  "description": "智量通 ZLT Quant MCP server(Windows x64;量化策略创建与本地回测)",
5
5
  "bin": {
6
6
  "zlt-mcp": "./zlt-mcp-proxy.bundle.cjs"
@@ -10609,11 +10609,59 @@ var {
10609
10609
  // src/cli-evidence.ts
10610
10610
  var fs = __toESM(require("node:fs"), 1);
10611
10611
  var path = __toESM(require("node:path"), 1);
10612
+ var READABLE_LINE_LIMIT = 1e3;
10613
+ function readableValue(value) {
10614
+ if (Array.isArray(value)) return value.map(readableValue);
10615
+ if (!value || typeof value !== "object") return value;
10616
+ const source = value;
10617
+ const result = /* @__PURE__ */ Object.create(null);
10618
+ for (const [key, entry] of Object.entries(source)) result[key] = readableValue(entry);
10619
+ if (source.type === "text" && typeof source.text === "string") {
10620
+ try {
10621
+ result.text = {
10622
+ displayNote: "Parsed from content[].text JSON. Exact original text remains in result.json.",
10623
+ parsedJson: readableValue(JSON.parse(source.text))
10624
+ };
10625
+ } catch {
10626
+ result.text = source.text;
10627
+ }
10628
+ }
10629
+ return result;
10630
+ }
10631
+ function takeChunk(value, limit) {
10632
+ let end = Math.min(limit, value.length);
10633
+ if (end < value.length && end > 0) {
10634
+ const code = value.charCodeAt(end - 1);
10635
+ if (code >= 55296 && code <= 56319) end -= 1;
10636
+ }
10637
+ return [value.slice(0, end), value.slice(end)];
10638
+ }
10639
+ function buildReadableEvidence(value) {
10640
+ const header = [
10641
+ "ZLT CLI readable evidence v1 - DISPLAY ONLY; result.json is the authoritative complete saved result.",
10642
+ "WRAPPING: | starts a rendered logical line; + continues the preceding line. Strip the first character and join + lines before interpretation.",
10643
+ "Embedded content[].text that is valid JSON is expanded under parsedJson; non-JSON text is preserved as text."
10644
+ ];
10645
+ const rendered = JSON.stringify(readableValue(value), null, 2).split("\n");
10646
+ const lines = [...header];
10647
+ for (const logical of rendered) {
10648
+ let remaining = logical;
10649
+ let marker = "|";
10650
+ do {
10651
+ const [chunk, rest] = takeChunk(remaining, READABLE_LINE_LIMIT - 1);
10652
+ lines.push(marker + chunk);
10653
+ marker = "+";
10654
+ remaining = rest;
10655
+ } while (remaining.length > 0);
10656
+ }
10657
+ return lines.join("\n") + "\n";
10658
+ }
10612
10659
  function evidenceDirectoryArg(args) {
10613
- if (!["call", "list"].includes(args[0])) return void 0;
10660
+ const authCommand = args[0] === "auth" && ["login", "status", "wait"].includes(args[1]);
10661
+ if (!["call", "list"].includes(args[0]) && !authCommand) return void 0;
10614
10662
  let directory;
10615
- const valueOptions = /* @__PURE__ */ new Set(["--args-json", "--args-file", "--output-json", "--tool", "--timeout-ms", "--max-total-timeout-ms"]);
10616
- for (let i = 1; i < args.length && args[i] !== "--"; i++) {
10663
+ const valueOptions = /* @__PURE__ */ new Set(["--args-json", "--args-file", "--output-json", "--tool", "--timeout-ms", "--max-total-timeout-ms", "--session"]);
10664
+ for (let i = authCommand ? 2 : 1; i < args.length && args[i] !== "--"; i++) {
10617
10665
  const arg = args[i];
10618
10666
  if (valueOptions.has(arg)) {
10619
10667
  i++;
@@ -10629,6 +10677,7 @@ function evidenceDirectoryArg(args) {
10629
10677
  }
10630
10678
  var CliEvidence = class {
10631
10679
  resultFile;
10680
+ readableFile;
10632
10681
  diagnosticsFile;
10633
10682
  executionFile;
10634
10683
  state;
@@ -10640,6 +10689,7 @@ var CliEvidence = class {
10640
10689
  const root2 = path.resolve(directory);
10641
10690
  fs.mkdirSync(root2);
10642
10691
  this.resultFile = path.join(root2, "result.json");
10692
+ this.readableFile = path.join(root2, "result.readable.txt");
10643
10693
  this.diagnosticsFile = path.join(root2, "diagnostics.log");
10644
10694
  this.executionFile = path.join(root2, "execution.json");
10645
10695
  this.state = {
@@ -10653,6 +10703,7 @@ var CliEvidence = class {
10653
10703
  business: "not_reported",
10654
10704
  exitCode: null,
10655
10705
  resultFile: this.resultFile,
10706
+ readableFile: this.readableFile,
10656
10707
  diagnosticsFile: this.diagnosticsFile,
10657
10708
  retry: "inspect_state_before_retry"
10658
10709
  };
@@ -10682,6 +10733,13 @@ var CliEvidence = class {
10682
10733
  checkDiagnostics() {
10683
10734
  if (this.diagnosticFailure) throw new Error(`Saving diagnostics failed: ${this.diagnosticFailure.message}`);
10684
10735
  }
10736
+ writeReadable(value) {
10737
+ fs.writeFileSync(this.readableFile, buildReadableEvidence(value), { encoding: "utf8", flag: "wx" });
10738
+ }
10739
+ writeResult(value) {
10740
+ fs.writeFileSync(this.resultFile, JSON.stringify(value) + "\n", { encoding: "utf8", flag: "wx" });
10741
+ this.writeReadable(value);
10742
+ }
10685
10743
  started(tool) {
10686
10744
  this.checkDiagnostics();
10687
10745
  this.state = { ...this.state, ...tool ? { tool } : {}, stage: "tool_call", toolExecution: "unknown" };
@@ -10730,7 +10788,7 @@ var CliEvidence = class {
10730
10788
  this.failed(stage, this.state.toolExecution, this.state.business);
10731
10789
  }
10732
10790
  receiptFields() {
10733
- return { executionFile: this.executionFile, diagnosticsFile: this.diagnosticsFile };
10791
+ return { executionFile: this.executionFile, diagnosticsFile: this.diagnosticsFile, readableFile: this.readableFile };
10734
10792
  }
10735
10793
  close() {
10736
10794
  if (process.stderr.write === this.teeWrite) process.stderr.write = this.originalWrite;
@@ -10919,6 +10977,7 @@ function buildCliUsage(tool, tools) {
10919
10977
  mutuallyExclusiveWith: "--output-json",
10920
10978
  files: {
10921
10979
  resultJson: "result.json is exclusively reserved before execution. It is parseable only after a complete response is saved; existence alone does not prove a response was received, and disconnects or write failures can leave it empty or partial.",
10980
+ resultReadableTxt: "result.readable.txt is a display-only, line-bounded view that expands JSON inside content[].text without truncating content. result.json remains authoritative.",
10922
10981
  diagnosticsLog: "diagnostics.log captures CLI and child-MCP diagnostics while stderr remains live.",
10923
10982
  executionJson: "execution.json records state, toolExecution, business outcome, and the CLI exitCode."
10924
10983
  },
@@ -29220,6 +29279,7 @@ var path25 = __toESM(require("node:path"), 1);
29220
29279
  var import_node_crypto5 = require("node:crypto");
29221
29280
  var SOURCE = "zlt-mcp-device-login";
29222
29281
  var IGNORED_API_ENV = /* @__PURE__ */ new Set(["ZLTQUANT_API_KEY", "ZLTQUANT_API_BASE"]);
29282
+ var SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
29223
29283
  function authCliEnv(env) {
29224
29284
  return Object.fromEntries(
29225
29285
  Object.entries(env).filter(([key]) => !IGNORED_API_ENV.has(key.toUpperCase()))
@@ -29289,9 +29349,12 @@ function authErrorMessage(error2) {
29289
29349
  return "Authentication operation failed; check local credential configuration and storage permissions. No credential was returned.";
29290
29350
  }
29291
29351
  function sessionPath(id, e = process.env) {
29292
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)) throw Error("Invalid authentication session");
29352
+ if (!SESSION_ID.test(id)) throw Error("Invalid authentication session");
29293
29353
  return path25.join(root(e), id + ".json");
29294
29354
  }
29355
+ function validateAuthSessionId(id) {
29356
+ if (!SESSION_ID.test(id)) throw Error("Invalid authentication session");
29357
+ }
29295
29358
  function drop(id, e = process.env) {
29296
29359
  fs26.rmSync(sessionPath(id, e), { force: true });
29297
29360
  }
@@ -29299,7 +29362,7 @@ function preflight(e) {
29299
29362
  if (e.ZLTQUANT_CREDENTIALS || readCredentials(e))
29300
29363
  throw Error("Authentication is already configured");
29301
29364
  }
29302
- async function finish(s, e, c, json) {
29365
+ async function finish(s, e, c, json, emit = true) {
29303
29366
  const grant = await c.poll(s, {
29304
29367
  maxDurationMs: s.expiresIn * 1e3
29305
29368
  });
@@ -29314,7 +29377,7 @@ async function finish(s, e, c, json) {
29314
29377
  apiKeyMask: a.apiKeyMask,
29315
29378
  apiBase: a.apiBase
29316
29379
  };
29317
- process.stdout.write(json ? JSON.stringify(o) + "\n" : `Signed in (${a.apiKeyMask}).
29380
+ if (emit) process.stdout.write(json ? JSON.stringify(o) + "\n" : `Signed in (${a.apiKeyMask}).
29318
29381
  `);
29319
29382
  return o;
29320
29383
  }
@@ -29332,10 +29395,10 @@ async function runAuthLogin(o = {}) {
29332
29395
  expiresIn: s.expiresIn,
29333
29396
  widget: buildShowWidgetPayload(s)
29334
29397
  };
29335
- process.stdout.write(JSON.stringify(out) + "\n");
29398
+ if (o.emit !== false) process.stdout.write(JSON.stringify(out) + "\n");
29336
29399
  return out;
29337
29400
  }
29338
- process.stdout.write(
29401
+ if (o.emit !== false) process.stdout.write(
29339
29402
  o.json ? JSON.stringify({
29340
29403
  status: "pending",
29341
29404
  verificationUri: s.verificationUri,
@@ -29343,7 +29406,7 @@ async function runAuthLogin(o = {}) {
29343
29406
  }) + "\n" : `Open ${s.verificationUri} and enter code ${s.userCode}.
29344
29407
  `
29345
29408
  );
29346
- return finish(s, e, c, o.json);
29409
+ return finish(s, e, c, o.json, o.emit !== false);
29347
29410
  }
29348
29411
  async function runAuthWait(id, o = {}) {
29349
29412
  const e = authCliEnv(o.env || process.env);
@@ -29357,7 +29420,7 @@ async function runAuthWait(id, o = {}) {
29357
29420
  }
29358
29421
  try {
29359
29422
  const x = load(id, e);
29360
- const result = await finish(x.authorization, e, o.client || client, o.json);
29423
+ const result = await finish(x.authorization, e, o.client || client, o.json, o.emit !== false);
29361
29424
  drop(id, e);
29362
29425
  return result;
29363
29426
  } catch (error2) {
@@ -29401,7 +29464,10 @@ if (process.argv.length <= 2) {
29401
29464
  let evidenceSetupError;
29402
29465
  try {
29403
29466
  const directory = evidenceDirectoryArg(process.argv.slice(2));
29404
- if (directory !== void 0) evidence = new CliEvidence(directory, resolveCliPackageProvenance(), process.argv[2]);
29467
+ if (directory !== void 0) {
29468
+ const command = process.argv[2] === "auth" ? `auth ${process.argv[3] ?? ""}`.trim() : process.argv[2];
29469
+ evidence = new CliEvidence(directory, resolveCliPackageProvenance(), command);
29470
+ }
29405
29471
  } catch (error2) {
29406
29472
  evidenceSetupError = error2;
29407
29473
  }
@@ -29422,34 +29488,127 @@ if (process.argv.length <= 2) {
29422
29488
  });
29423
29489
  program2.name(CLI_NAME).description("MCP proxy for ZLTHost IPC service").version(resolveCliVersion()).configureOutput({ outputError: writeCliArgumentError }).addHelpText("after", "\nDiscovery: list for names; list --tool <name> --json for one complete input schema.\nUse call --help for input, output and diagnostic handling.\nFor npx registry selection, put --registry before the package name; it is not a zlt-mcp option.");
29424
29490
  const auth = program2.command("auth").description("Manage local ZLT Quant authentication");
29425
- auth.command("login").description("Sign in with a device code").option("--json", "Emit a JSON descriptor and status").option("--widget", "Return a login card immediately; render it before running auth wait").addHelpText("after", "\nAgent flow: auth login --widget --json returns a local session ID and widget descriptor.\nInvoke the host show_widget tool with widget.arguments, or show the login URL/code.\nThen run auth wait --session <returned-id> --json. The descriptor alone does not render a widget.\nWithout --widget this command waits in the foreground. Never paste keys or private session files into chat.").action(async (options) => {
29491
+ auth.command("login").description("Sign in with a device code").option("--json", "Emit a JSON descriptor and status").option("--widget", "Return a login card immediately; render it before running auth wait").option("--evidence-dir <path>", "Create a new directory for the display-safe login descriptor and execution status; requires --widget").addHelpText("after", "\nAgent flow: auth login --widget --json returns a local session ID and widget descriptor.\nInvoke the host show_widget tool with widget.arguments, or show the login URL/code.\nThen run auth wait --session <returned-id> --json. The descriptor alone does not render a widget.\nWithout --widget this command waits in the foreground. Never paste keys or private session files into chat.").action(async (options) => {
29492
+ let toolExecution = "not_started";
29493
+ let stage = "arguments";
29494
+ let response;
29426
29495
  try {
29427
- await runAuthLogin(options);
29496
+ if (evidence && !options.widget) throw new Error("--evidence-dir requires --widget for auth login");
29497
+ evidence?.started();
29498
+ toolExecution = evidence ? "unknown" : "not_started";
29499
+ stage = "auth_login";
29500
+ const result = await runAuthLogin({ ...options, emit: !evidence });
29501
+ response = result;
29502
+ if (!evidence) return;
29503
+ toolExecution = "response_received";
29504
+ stage = "output_write";
29505
+ evidence.writeResult(result);
29506
+ stage = "output_emit";
29507
+ evidence.completed("not_reported", 0);
29508
+ process.stdout.write(JSON.stringify({
29509
+ ok: true,
29510
+ command: "auth login",
29511
+ resultFile: evidence.resultFile,
29512
+ cliProvenance: resolveCliPackageProvenance(),
29513
+ ...evidence.receiptFields()
29514
+ }) + "\n");
29428
29515
  } catch (error2) {
29429
- process.stderr.write(`Error: ${authErrorMessage(error2)}
29516
+ if (toolExecution === "response_received" && response !== void 0) {
29517
+ process.stdout.write(JSON.stringify(response) + "\n");
29518
+ }
29519
+ const message = error2 instanceof Error && error2.message === "--evidence-dir requires --widget for auth login" ? error2.message : authErrorMessage(error2);
29520
+ process.stderr.write(`Error: ${message}
29430
29521
  `);
29431
29522
  process.exitCode = 1;
29523
+ try {
29524
+ evidence?.failed(stage, toolExecution);
29525
+ } catch (saveError) {
29526
+ process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29527
+ `);
29528
+ }
29432
29529
  }
29433
29530
  });
29434
- auth.command("status").description("Show masked local authentication status").option("--json", "Emit JSON").action((options) => {
29531
+ auth.command("status").description("Show masked local authentication status").option("--json", "Emit JSON").option("--evidence-dir <path>", "Create a new directory for the masked status and execution state").action((options) => {
29532
+ let toolExecution = "not_started";
29533
+ let stage = "status_query";
29534
+ let response;
29435
29535
  try {
29536
+ evidence?.started();
29537
+ toolExecution = evidence ? "unknown" : "not_started";
29436
29538
  const status = authStatus();
29437
- process.stdout.write(options.json ? `${JSON.stringify(status)}
29539
+ response = status;
29540
+ if (!evidence) {
29541
+ process.stdout.write(options.json ? `${JSON.stringify(status)}
29438
29542
  ` : `${status.configured ? `Configured (${status.apiKeyMask})` : "Not configured"}
29439
29543
  `);
29544
+ return;
29545
+ }
29546
+ toolExecution = "response_received";
29547
+ stage = "output_write";
29548
+ evidence.writeResult(status);
29549
+ stage = "output_emit";
29550
+ evidence.completed("not_reported", 0);
29551
+ process.stdout.write(JSON.stringify({
29552
+ ok: true,
29553
+ command: "auth status",
29554
+ resultFile: evidence.resultFile,
29555
+ cliProvenance: resolveCliPackageProvenance(),
29556
+ ...evidence.receiptFields()
29557
+ }) + "\n");
29440
29558
  } catch (error2) {
29559
+ if (toolExecution === "response_received" && response !== void 0) {
29560
+ process.stdout.write(JSON.stringify(response) + "\n");
29561
+ }
29441
29562
  process.stderr.write(`Error: ${authErrorMessage(error2)}
29442
29563
  `);
29443
29564
  process.exitCode = 1;
29565
+ try {
29566
+ evidence?.failed(stage, toolExecution);
29567
+ } catch (saveError) {
29568
+ process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29569
+ `);
29570
+ }
29444
29571
  }
29445
29572
  });
29446
- auth.command("wait").description("Complete a saved device-code sign in").requiredOption("--session <id>", "Opaque local session ID from auth login --widget").option("--json", "Emit JSON").action(async (options) => {
29573
+ auth.command("wait").description("Complete a saved device-code sign in").requiredOption("--session <id>", "Opaque local session ID from auth login --widget").option("--json", "Emit JSON").option("--evidence-dir <path>", "Create a new directory for the display-safe wait receipt and execution state").action(async (options) => {
29574
+ let toolExecution = "not_started";
29575
+ let stage = "arguments";
29576
+ let business = "not_reported";
29577
+ let response;
29447
29578
  try {
29448
- await runAuthWait(options.session, options);
29579
+ if (evidence) validateAuthSessionId(options.session);
29580
+ evidence?.started();
29581
+ toolExecution = evidence ? "unknown" : "not_started";
29582
+ stage = "auth_wait";
29583
+ const result = await runAuthWait(options.session, { ...options, emit: !evidence });
29584
+ response = result;
29585
+ if (!evidence) return;
29586
+ toolExecution = "response_received";
29587
+ business = "success";
29588
+ stage = "output_write";
29589
+ evidence.writeResult(result);
29590
+ stage = "output_emit";
29591
+ evidence.completed("success", 0);
29592
+ process.stdout.write(JSON.stringify({
29593
+ ok: true,
29594
+ command: "auth wait",
29595
+ resultFile: evidence.resultFile,
29596
+ cliProvenance: resolveCliPackageProvenance(),
29597
+ ...evidence.receiptFields()
29598
+ }) + "\n");
29449
29599
  } catch (error2) {
29600
+ if (toolExecution === "response_received" && response !== void 0) {
29601
+ process.stdout.write(JSON.stringify(response) + "\n");
29602
+ }
29450
29603
  process.stderr.write(`Error: ${authErrorMessage(error2)}
29451
29604
  `);
29452
29605
  process.exitCode = 1;
29606
+ try {
29607
+ evidence?.failed(stage, toolExecution, business);
29608
+ } catch (saveError) {
29609
+ process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29610
+ `);
29611
+ }
29453
29612
  }
29454
29613
  });
29455
29614
  auth.command("logout").description("Remove only CLI-login-owned credentials locally; does not revoke the gateway key").option("--json", "Emit JSON").action((options) => {
@@ -29504,9 +29663,13 @@ if (process.argv.length <= 2) {
29504
29663
  const fd = outputFd;
29505
29664
  outputFd = void 0;
29506
29665
  fs27.closeSync(fd);
29666
+ if (evidence) {
29667
+ stage = "readable_write";
29668
+ evidence.writeReadable(complete);
29669
+ }
29507
29670
  } catch (error2) {
29508
29671
  process.stdout.write(serialized);
29509
- throw new Error(`Tool returned a result, but saving ${resultFile} failed; complete result is on stdout. Do not repeat the tool automatically. ${String(error2)}`);
29672
+ throw new Error(`Tool returned a result, but saving its evidence failed; complete result is on stdout and any saved result.json remains authoritative. Do not repeat the tool automatically. ${String(error2)}`);
29510
29673
  }
29511
29674
  stage = "output_emit";
29512
29675
  evidence?.completed(business, exitCode);
@@ -29563,6 +29726,7 @@ if (process.argv.length <= 2) {
29563
29726
  });
29564
29727
  program2.command("list").description("List MCP tools exposed by the bundled server").option("--json", "Emit the complete tools/list response as JSON").option("--tool <name>", "Select one exact tool name; combine with --json for its complete schema").option("--evidence-dir <path>", "Create a new directory for discovery, diagnostics and execution status").addHelpText("after", "\nReuse discovery within a task. Query one schema with list --tool <name> --json.\nUnknown names fail with exit code 1. Preserve stderr separately from JSON stdout.").action(async (options) => {
29565
29728
  let toolExecution = "not_started";
29729
+ let stage = "discovery";
29566
29730
  try {
29567
29731
  evidence?.started();
29568
29732
  toolExecution = "unknown";
@@ -29577,7 +29741,8 @@ if (process.argv.length <= 2) {
29577
29741
  const complete = withCliProvenance(result);
29578
29742
  const serialized = JSON.stringify(complete) + "\n";
29579
29743
  try {
29580
- fs27.writeFileSync(evidence.resultFile, serialized, { encoding: "utf8", flag: "wx" });
29744
+ stage = "output_write";
29745
+ evidence.writeResult(complete);
29581
29746
  evidence.completed("not_reported", 0);
29582
29747
  } catch (error2) {
29583
29748
  process.stdout.write(serialized);
@@ -29609,7 +29774,7 @@ if (process.argv.length <= 2) {
29609
29774
  `);
29610
29775
  process.exitCode = 1;
29611
29776
  try {
29612
- evidence?.failed("discovery", toolExecution);
29777
+ evidence?.failed(stage, toolExecution);
29613
29778
  } catch (saveError) {
29614
29779
  process.stderr.write(`Error: execution evidence could not be finalized: ${String(saveError)}
29615
29780
  `);