jinzd-ai-cli 0.4.268 → 0.4.270

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 (70) hide show
  1. package/dist/{batch-2N2YMRX2.js → batch-IMLMUXJL.js} +2 -2
  2. package/dist/{chunk-UT2WAJJG.js → chunk-75SPJKDY.js} +1 -1
  3. package/dist/{chunk-RRQZRSXW.js → chunk-BOMB6XCF.js} +1 -1
  4. package/dist/{chunk-74BK22BM.js → chunk-DIJIYWQE.js} +4 -4
  5. package/dist/{chunk-VGVKJTHF.js → chunk-F26YTEMB.js} +9 -1
  6. package/dist/{chunk-Z7CZ72RP.js → chunk-FXCDD7JS.js} +3 -3
  7. package/dist/{chunk-YLSM7RF6.js → chunk-HOT76ATM.js} +3 -3
  8. package/dist/{chunk-EPVJS4F6.js → chunk-JJVVCWO4.js} +1 -1
  9. package/dist/{chunk-R7GERZDA.js → chunk-JT6UN5C6.js} +26 -5
  10. package/dist/{chunk-2FQGMR34.js → chunk-M3GDYEUH.js} +1 -1
  11. package/dist/{chunk-7US7K3CD.js → chunk-PLFXDRNX.js} +1 -1
  12. package/dist/{chunk-WWLXW4SY.js → chunk-TG4PF7FZ.js} +1 -1
  13. package/dist/{chunk-XDITAGE5.js → chunk-UDUE4DAE.js} +1 -1
  14. package/dist/{chunk-VNCBY4KY.js → chunk-X3JPBJ6C.js} +163 -3
  15. package/dist/{chunk-WNG3YVZX.js → chunk-YHLTSZVH.js} +1 -1
  16. package/dist/{ci-3RWVJSZ3.js → ci-HQVXWJ53.js} +4 -4
  17. package/dist/{ci-format-M6HVCXCG.js → ci-format-762IL4OM.js} +2 -2
  18. package/dist/{constants-G6VN7SJL.js → constants-4TPE3JSV.js} +1 -1
  19. package/dist/{doctor-cli-Y3SVVJBO.js → doctor-cli-3ES6NFVL.js} +4 -4
  20. package/dist/electron-server.js +300 -55
  21. package/dist/{hub-5T5HKOAR.js → hub-QXRWPBJP.js} +1 -1
  22. package/dist/index.js +68 -155
  23. package/dist/{persistent-memory-XVSSR277.js → persistent-memory-F4JPWYZJ.js} +2 -2
  24. package/dist/{persistent-memory-YBWIZWQP.js → persistent-memory-URIJJJ3N.js} +2 -2
  25. package/dist/{pr-NF5IWLH3.js → pr-HVVHZOJ2.js} +4 -4
  26. package/dist/{run-tests-Y44SCLIO.js → run-tests-UZG6SVJ5.js} +2 -2
  27. package/dist/{run-tests-33X3D7PV.js → run-tests-WM7TMO2N.js} +2 -2
  28. package/dist/{server-3LERX42X.js → server-E5OBXT33.js} +5 -5
  29. package/dist/{server-NJ3ZNPQW.js → server-HPRLFLOI.js} +112 -35
  30. package/dist/{task-orchestrator-JN2WFZSF.js → task-orchestrator-HJUUL55Y.js} +5 -5
  31. package/dist/{usage-IFTNXTWC.js → usage-L3DUPHD6.js} +11 -4
  32. package/dist/web/client/actions.js.br +0 -0
  33. package/dist/web/client/actions.js.gz +0 -0
  34. package/dist/web/client/api.js.br +0 -0
  35. package/dist/web/client/api.js.gz +0 -0
  36. package/dist/web/client/app.js +3 -6
  37. package/dist/web/client/app.js.br +0 -0
  38. package/dist/web/client/app.js.gz +0 -0
  39. package/dist/web/client/dom.js.br +0 -0
  40. package/dist/web/client/dom.js.gz +0 -0
  41. package/dist/web/client/icon.svg.br +0 -0
  42. package/dist/web/client/icon.svg.gz +0 -0
  43. package/dist/web/client/index.html.br +0 -0
  44. package/dist/web/client/index.html.gz +0 -0
  45. package/dist/web/client/sidebar-tabs.js.br +0 -0
  46. package/dist/web/client/sidebar-tabs.js.gz +0 -0
  47. package/dist/web/client/state.js.br +0 -0
  48. package/dist/web/client/state.js.gz +0 -0
  49. package/dist/web/client/style.css.br +0 -0
  50. package/dist/web/client/style.css.gz +0 -0
  51. package/dist/web/client/sw.js.br +0 -0
  52. package/dist/web/client/sw.js.gz +0 -0
  53. package/dist/web/client/templates.js.br +0 -0
  54. package/dist/web/client/templates.js.gz +0 -0
  55. package/dist/web/client/util.js +22 -0
  56. package/dist/web/client/util.js.br +0 -0
  57. package/dist/web/client/util.js.gz +0 -0
  58. package/dist/web/client/vendor/daisyui-full.min.css.br +0 -0
  59. package/dist/web/client/vendor/daisyui-full.min.css.gz +0 -0
  60. package/dist/web/client/vendor/github-dark.min.css.br +0 -0
  61. package/dist/web/client/vendor/github-dark.min.css.gz +0 -0
  62. package/dist/web/client/vendor/github.min.css.br +0 -0
  63. package/dist/web/client/vendor/github.min.css.gz +0 -0
  64. package/dist/web/client/vendor/highlight.min.js.br +0 -0
  65. package/dist/web/client/vendor/highlight.min.js.gz +0 -0
  66. package/dist/web/client/vendor/marked.min.js.br +0 -0
  67. package/dist/web/client/vendor/marked.min.js.gz +0 -0
  68. package/dist/web/client/vendor/tailwind.js.br +0 -0
  69. package/dist/web/client/vendor/tailwind.js.gz +0 -0
  70. package/package.json +1 -1
@@ -22,21 +22,28 @@ import {
22
22
  buildInitPrompt,
23
23
  buildSessionCostReport,
24
24
  costSourceNote,
25
+ decideModel,
25
26
  estimateConversationTokens,
26
27
  evaluateContextPressure,
28
+ filterMemoryEntries,
27
29
  loadDevState,
30
+ parseMemoryShowFilters,
28
31
  persistToolRound,
29
32
  readGitDiff,
30
33
  scanDirTree,
31
34
  scanProject,
32
- setupProxy
33
- } from "./chunk-VNCBY4KY.js";
35
+ setupProxy,
36
+ stripRoutingTags
37
+ } from "./chunk-X3JPBJ6C.js";
34
38
  import {
39
+ CNY_PER_USD,
35
40
  CostTracker,
36
41
  computeCost,
37
42
  formatCost,
38
- getPricing
39
- } from "./chunk-R7GERZDA.js";
43
+ getCostCurrency,
44
+ getPricing,
45
+ setCostCurrency
46
+ } from "./chunk-JT6UN5C6.js";
40
47
  import {
41
48
  buildReviewPrompt,
42
49
  buildSecurityReviewPrompt
@@ -75,7 +82,7 @@ import {
75
82
  spawnAgentContext,
76
83
  truncateOutput,
77
84
  undoStack
78
- } from "./chunk-74BK22BM.js";
85
+ } from "./chunk-DIJIYWQE.js";
79
86
  import {
80
87
  addMemoryEntry,
81
88
  clearMemoryEntries,
@@ -85,11 +92,12 @@ import {
85
92
  formatMemoryForPrompt,
86
93
  isMemoryExpired,
87
94
  listMemoryEntries,
95
+ listOrphanedProjectMemories,
88
96
  memoryStorePath,
89
97
  parseWritableScope,
90
98
  updateMemoryApproval,
91
99
  updateMemoryEntry
92
- } from "./chunk-WNG3YVZX.js";
100
+ } from "./chunk-YHLTSZVH.js";
93
101
  import "./chunk-GLSR2NR3.js";
94
102
  import "./chunk-UUSRWSSX.js";
95
103
  import "./chunk-CKH4KQ4E.js";
@@ -103,13 +111,13 @@ import {
103
111
  buildDoctorReport,
104
112
  formatDoctorReport,
105
113
  loadContextFiles
106
- } from "./chunk-YLSM7RF6.js";
114
+ } from "./chunk-HOT76ATM.js";
107
115
  import {
108
116
  ProviderRegistry
109
117
  } from "./chunk-AUEX6RNY.js";
110
118
  import {
111
119
  ConfigManager
112
- } from "./chunk-VGVKJTHF.js";
120
+ } from "./chunk-F26YTEMB.js";
113
121
  import {
114
122
  describePlugin,
115
123
  getActivePluginAssets,
@@ -124,7 +132,7 @@ import {
124
132
  runTool,
125
133
  setPluginEnabled,
126
134
  trustPlugin
127
- } from "./chunk-2FQGMR34.js";
135
+ } from "./chunk-M3GDYEUH.js";
128
136
  import {
129
137
  CONTENT_ONLY_STREAM_REMINDER,
130
138
  TEE_FINAL_USER_NUDGE,
@@ -149,7 +157,7 @@ import {
149
157
  getGitRoot
150
158
  } from "./chunk-HOSJZMQS.js";
151
159
  import "./chunk-4BKXL7SM.js";
152
- import "./chunk-WWLXW4SY.js";
160
+ import "./chunk-TG4PF7FZ.js";
153
161
  import {
154
162
  AGENTIC_BEHAVIOR_GUIDELINE,
155
163
  AUTHOR,
@@ -165,7 +173,7 @@ import {
165
173
  SKILLS_DIR_NAME,
166
174
  VERSION,
167
175
  buildUserIdentityPrompt
168
- } from "./chunk-UT2WAJJG.js";
176
+ } from "./chunk-75SPJKDY.js";
169
177
  import "./chunk-6RPGQ5CG.js";
170
178
  import "./chunk-KHYD3WXE.js";
171
179
  import "./chunk-NYNSYLFY.js";
@@ -180,8 +188,8 @@ import {
180
188
  import express from "express";
181
189
  import { createServer } from "http";
182
190
  import { WebSocketServer } from "ws";
183
- import { join as join2, dirname as dirname3, resolve as resolve2, relative, sep } from "path";
184
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, realpathSync } from "fs";
191
+ import { join as join2, dirname as dirname3, resolve as resolve2, relative, sep, extname, resolve as resolvePath } from "path";
192
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, realpathSync, createReadStream } from "fs";
185
193
  import { networkInterfaces } from "os";
186
194
 
187
195
  // src/web/tool-executor-web.ts
@@ -845,7 +853,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
845
853
  Cost: ${formatCost(cost)}` : "";
846
854
  let memoryLine = "";
847
855
  try {
848
- const { countPendingMemories } = await import("./persistent-memory-XVSSR277.js");
856
+ const { countPendingMemories } = await import("./persistent-memory-F4JPWYZJ.js");
849
857
  const pending = countPendingMemories(ctx.config.getConfigDir());
850
858
  if (pending > 0) memoryLine = `
851
859
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -1658,7 +1666,7 @@ async function handleTest(args, ctx) {
1658
1666
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
1659
1667
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
1660
1668
  }
1661
- const runTests = ctx.runTests ?? (await import("./run-tests-33X3D7PV.js")).executeTests;
1669
+ const runTests = ctx.runTests ?? (await import("./run-tests-WM7TMO2N.js")).executeTests;
1662
1670
  const report = await runTests(testArgs);
1663
1671
  ctx.send({ type: "info", message: report });
1664
1672
  } catch (err) {
@@ -1916,7 +1924,7 @@ async function handleMemory(args, ctx) {
1916
1924
  ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
1917
1925
  } else if (sub === "export") {
1918
1926
  if (args[1] === "md") {
1919
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-XVSSR277.js");
1927
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-F4JPWYZJ.js");
1920
1928
  ctx.send({
1921
1929
  type: "export_data",
1922
1930
  format: "md",
@@ -1936,7 +1944,8 @@ async function handleMemory(args, ctx) {
1936
1944
  });
1937
1945
  }
1938
1946
  } else {
1939
- ctx.memoryShow();
1947
+ const isShow = sub === "show" || sub === "view" || sub === "list";
1948
+ ctx.memoryShow(isShow ? args.slice(1) : args);
1940
1949
  }
1941
1950
  }
1942
1951
  async function handleAddDir(args, ctx) {
@@ -2386,17 +2395,18 @@ var SessionHandler = class {
2386
2395
  sessionAccruedCostUsd = 0;
2387
2396
  sessionCostIncomplete = false;
2388
2397
  /** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
2389
- addWebSessionUsage(u) {
2398
+ addWebSessionUsage(u, modelOverride) {
2399
+ const usageModel = modelOverride ?? this.currentModel;
2390
2400
  this.sessionTokenUsage.inputTokens += u.inputTokens;
2391
2401
  this.sessionTokenUsage.outputTokens += u.outputTokens;
2392
2402
  this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
2393
2403
  this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
2394
- const priced = this.costTracker.addCost(this.currentProvider, this.currentModel, u);
2404
+ const priced = this.costTracker.addCost(this.currentProvider, usageModel, u);
2395
2405
  if (priced === null) this.sessionCostIncomplete = true;
2396
2406
  else this.sessionAccruedCostUsd += priced;
2397
2407
  const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
2398
- if (totalTokens > 0 && !getPricing(this.currentProvider, this.currentModel)) {
2399
- const key = `${this.currentProvider}/${this.currentModel}`;
2408
+ if (totalTokens > 0 && !getPricing(this.currentProvider, usageModel)) {
2409
+ const key = `${this.currentProvider}/${usageModel}`;
2400
2410
  if (!this.unknownPricingWarnings.has(key)) {
2401
2411
  this.unknownPricingWarnings.add(key);
2402
2412
  this.send({ type: "info", message: `Cost notice: pricing is unknown for ${key}. Tokens are tracked, but cost is excluded from totals. Run /cost or aicli usage to review.` });
@@ -2532,6 +2542,8 @@ var SessionHandler = class {
2532
2542
  permissionProfile: this.config.get("defaultPermissionProfile") ?? "legacy",
2533
2543
  tokenUsage: { ...this.sessionTokenUsage },
2534
2544
  costUsd,
2545
+ costCurrency: getCostCurrency(),
2546
+ costCnyPerUsd: CNY_PER_USD,
2535
2547
  costPricingUnknown,
2536
2548
  providers: providerList,
2537
2549
  branches,
@@ -2844,6 +2856,16 @@ var SessionHandler = class {
2844
2856
  } else {
2845
2857
  msgContent = content;
2846
2858
  }
2859
+ const routingDecision = this.computeRoutingDecision(typeof msgContent === "string" ? msgContent : content);
2860
+ if (typeof msgContent === "string") {
2861
+ msgContent = stripRoutingTags(msgContent);
2862
+ } else if (msgContent.length > 0 && msgContent[msgContent.length - 1]?.type === "text") {
2863
+ const last = msgContent[msgContent.length - 1];
2864
+ msgContent = [...msgContent.slice(0, -1), { type: "text", text: stripRoutingTags(last.text) }];
2865
+ }
2866
+ if (routingDecision.overridden) {
2867
+ this.send({ type: "info", message: `\u2192 Routed to ${routingDecision.model} (${routingDecision.reason})` });
2868
+ }
2847
2869
  const userMessage = { role: "user", content: msgContent, timestamp: /* @__PURE__ */ new Date() };
2848
2870
  session.addMessage(userMessage);
2849
2871
  const provider = this.providers.get(this.currentProvider);
@@ -2855,11 +2877,13 @@ var SessionHandler = class {
2855
2877
  const hasToolSupport = typeof provider.chatWithTools === "function";
2856
2878
  const { toolDefs, mcpBudgetNote } = hasToolSupport ? this.getFilteredToolDefs() : { toolDefs: [], mcpBudgetNote: null };
2857
2879
  if (hasToolSupport && toolDefs.length > 0) {
2858
- await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote);
2880
+ await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote, routingDecision.model);
2859
2881
  } else {
2860
- await this.handleChatSimple(provider, session.messages);
2882
+ await this.handleChatSimple(provider, session.messages, routingDecision.model);
2861
2883
  }
2862
2884
  await this.checkContextPressure();
2885
+ const budgetWarning = this.costTracker.checkBudget(this.config.get("monthlyBudget"));
2886
+ if (budgetWarning) this.send({ type: "info", message: `\u26A0 ${budgetWarning}` });
2863
2887
  } catch (err) {
2864
2888
  const parts = [];
2865
2889
  let current = err;
@@ -2878,8 +2902,9 @@ var SessionHandler = class {
2878
2902
  this.sendSessionList();
2879
2903
  }
2880
2904
  }
2881
- async handleChatSimple(provider, messages) {
2905
+ async handleChatSimple(provider, messages, modelOverride) {
2882
2906
  const session = this.sessions.current;
2907
+ const effectiveModel = modelOverride ?? this.currentModel;
2883
2908
  const modelParams = this.getModelParams();
2884
2909
  const ac = new AbortController();
2885
2910
  this.abortController = ac;
@@ -2887,7 +2912,7 @@ var SessionHandler = class {
2887
2912
  const { stable: simplStable, volatile: simplVolatile } = this.buildSystemPrompt();
2888
2913
  const stream = provider.chatStream({
2889
2914
  messages,
2890
- model: this.currentModel,
2915
+ model: effectiveModel,
2891
2916
  systemPrompt: simplStable,
2892
2917
  systemPromptVolatile: simplVolatile,
2893
2918
  stream: true,
@@ -2906,7 +2931,7 @@ var SessionHandler = class {
2906
2931
  if (chunk.done) {
2907
2932
  this.send({ type: "response_done", content: fullContent, usage: chunk.usage });
2908
2933
  if (chunk.usage) {
2909
- this.addWebSessionUsage(chunk.usage);
2934
+ this.addWebSessionUsage(chunk.usage, effectiveModel);
2910
2935
  session.addTokenUsage(chunk.usage);
2911
2936
  }
2912
2937
  if (chunk.reasoningContent) simpleReasoning = chunk.reasoningContent;
@@ -2931,8 +2956,9 @@ var SessionHandler = class {
2931
2956
  this.abortController = null;
2932
2957
  }
2933
2958
  }
2934
- async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
2959
+ async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote, modelOverride) {
2935
2960
  const session = this.sessions.current;
2961
+ const effectiveModel = modelOverride ?? this.currentModel;
2936
2962
  const apiMessages = [...messages];
2937
2963
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
2938
2964
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
@@ -2962,7 +2988,7 @@ ${mcpBudgetNote}` : "");
2962
2988
  callModel: async (_round, extraMessages) => {
2963
2989
  const chatRequest = {
2964
2990
  messages: apiMessages,
2965
- model: this.currentModel,
2991
+ model: effectiveModel,
2966
2992
  systemPrompt,
2967
2993
  systemPromptVolatile,
2968
2994
  stream: false,
@@ -3007,7 +3033,7 @@ Details: ${errMsg.split("\n")[0]}
3007
3033
  const summaryResult = await provider.chatWithTools(
3008
3034
  {
3009
3035
  messages: apiMessages,
3010
- model: this.currentModel,
3036
+ model: effectiveModel,
3011
3037
  systemPrompt,
3012
3038
  systemPromptVolatile,
3013
3039
  stream: false,
@@ -3023,7 +3049,7 @@ Details: ${errMsg.split("\n")[0]}
3023
3049
  executeTools: async (toolCalls) => {
3024
3050
  googleSearchContext.configManager = this.config;
3025
3051
  spawnAgentContext.provider = provider;
3026
- spawnAgentContext.model = this.currentModel;
3052
+ spawnAgentContext.model = effectiveModel;
3027
3053
  spawnAgentContext.systemPrompt = systemPromptVolatile ? `${systemPrompt}
3028
3054
 
3029
3055
  ---
@@ -3397,6 +3423,24 @@ ${summaryContent}`,
3397
3423
  * 到达上下文压力阈值时自动压缩(与 REPL 同一判据,见 core/context-pressure.ts)。
3398
3424
  * 上下文窗口未知时不动——不基于猜测改用户的对话历史。
3399
3425
  */
3426
+ /**
3427
+ * 智能模型路由决策(审计附录 D-5)。候选限定在当前 provider 注册的模型内——
3428
+ * 跨 provider 换模型会破坏 API key 与对话连续性。
3429
+ */
3430
+ computeRoutingDecision(userInput) {
3431
+ let availableModels = [];
3432
+ try {
3433
+ availableModels = this.providers.get(this.currentProvider).info.models.map((m) => m.id);
3434
+ } catch {
3435
+ availableModels = [];
3436
+ }
3437
+ return decideModel({
3438
+ userInput,
3439
+ currentModel: this.currentModel,
3440
+ routingConfig: this.config.get("routing"),
3441
+ availableModels
3442
+ });
3443
+ }
3400
3444
  async checkContextPressure() {
3401
3445
  const contextWindow = this.getContextWindowSize();
3402
3446
  const session = this.sessions.current;
@@ -3559,18 +3603,27 @@ ${summaryContent}`,
3559
3603
  });
3560
3604
  }
3561
3605
  }
3562
- memoryShow() {
3606
+ memoryShow(args = []) {
3563
3607
  const configDir = this.config.getConfigDir();
3564
- const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
3565
- const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
3608
+ const filters = parseMemoryShowFilters(args);
3609
+ if (filters.invalidStale) {
3610
+ this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
3611
+ return;
3612
+ }
3613
+ const source = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
3614
+ const { entries, applied } = filterMemoryEntries(source, filters);
3615
+ const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
3616
+ const content = entries.length === 0 ? applied.length > 0 ? `(no entries match [${applied.join(", ")}])` : "(empty \u2014 no persistent memory entries yet)" : `Governed persistent memory${filterNote}
3617
+
3618
+ ` + entries.map((entry) => {
3566
3619
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
3567
- const source = [
3620
+ const source2 = [
3568
3621
  entry.source ?? "unknown",
3569
3622
  entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
3570
3623
  entry.sourceProject ? `project ${entry.sourceProject}` : ""
3571
3624
  ].filter(Boolean).join(" \xB7 ");
3572
3625
  return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
3573
- Source: ${source}
3626
+ Source: ${source2}
3574
3627
  ${entry.content}`;
3575
3628
  }).join("\n\n");
3576
3629
  this.send({
@@ -3973,6 +4026,7 @@ async function startWebServer(options = {}) {
3973
4026
  console.log(` Skills: ${count} available`);
3974
4027
  }
3975
4028
  }
4029
+ setCostCurrency(config.get("ui").currency);
3976
4030
  const shared = {
3977
4031
  config,
3978
4032
  costTracker: new CostTracker(config.getConfigDir()),
@@ -4052,6 +4106,29 @@ async function startWebServer(options = {}) {
4052
4106
  clientDir = join2(process.cwd(), "src", "web", "client");
4053
4107
  }
4054
4108
  console.log(` Static files: ${clientDir}`);
4109
+ const PRECOMPRESSED_TYPES = {
4110
+ ".js": "text/javascript; charset=utf-8",
4111
+ ".css": "text/css; charset=utf-8",
4112
+ ".html": "text/html; charset=utf-8",
4113
+ ".json": "application/json; charset=utf-8",
4114
+ ".svg": "image/svg+xml",
4115
+ ".map": "application/json; charset=utf-8"
4116
+ };
4117
+ app.get(/.*/, (req, res, next) => {
4118
+ const ext = extname(req.path).toLowerCase();
4119
+ const contentType = PRECOMPRESSED_TYPES[ext];
4120
+ if (!contentType) return next();
4121
+ const accept = String(req.headers["accept-encoding"] ?? "");
4122
+ const encoding = /\bbr\b/.test(accept) ? "br" : /\bgzip\b/.test(accept) ? "gzip" : null;
4123
+ if (!encoding) return next();
4124
+ const target = resolvePath(clientDir, `.${req.path}`) + (encoding === "br" ? ".br" : ".gz");
4125
+ if (!target.startsWith(clientDir) || !existsSync3(target)) return next();
4126
+ res.setHeader("Content-Type", contentType);
4127
+ res.setHeader("Content-Encoding", encoding);
4128
+ res.setHeader("Vary", "Accept-Encoding");
4129
+ if (req.path.startsWith("/vendor/")) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
4130
+ createReadStream(target).pipe(res);
4131
+ });
4055
4132
  app.use("/vendor", express.static(join2(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
4056
4133
  app.use(express.static(clientDir));
4057
4134
  app.get("/api/status", (_req, res) => {
@@ -3,8 +3,8 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-74BK22BM.js";
7
- import "./chunk-WNG3YVZX.js";
6
+ } from "./chunk-DIJIYWQE.js";
7
+ import "./chunk-YHLTSZVH.js";
8
8
  import "./chunk-GLSR2NR3.js";
9
9
  import "./chunk-UUSRWSSX.js";
10
10
  import "./chunk-CKH4KQ4E.js";
@@ -12,7 +12,7 @@ import "./chunk-BXP6YZ2P.js";
12
12
  import "./chunk-5C76XDDF.js";
13
13
  import {
14
14
  runTool
15
- } from "./chunk-2FQGMR34.js";
15
+ } from "./chunk-M3GDYEUH.js";
16
16
  import {
17
17
  getDangerLevel,
18
18
  runLeanAgentLoop
@@ -20,10 +20,10 @@ import {
20
20
  import "./chunk-TU3L3PW7.js";
21
21
  import "./chunk-HOSJZMQS.js";
22
22
  import "./chunk-4BKXL7SM.js";
23
- import "./chunk-WWLXW4SY.js";
23
+ import "./chunk-TG4PF7FZ.js";
24
24
  import {
25
25
  SUBAGENT_ALLOWED_TOOLS
26
- } from "./chunk-UT2WAJJG.js";
26
+ } from "./chunk-75SPJKDY.js";
27
27
  import "./chunk-6RPGQ5CG.js";
28
28
  import "./chunk-KHYD3WXE.js";
29
29
  import "./chunk-NYNSYLFY.js";
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CostTracker,
4
+ currencyNote,
4
5
  formatCost,
5
- getPricing
6
- } from "./chunk-R7GERZDA.js";
6
+ getPricing,
7
+ setCostCurrency
8
+ } from "./chunk-JT6UN5C6.js";
7
9
  import {
8
10
  ConfigManager
9
- } from "./chunk-VGVKJTHF.js";
11
+ } from "./chunk-F26YTEMB.js";
10
12
  import "./chunk-TU3L3PW7.js";
11
- import "./chunk-UT2WAJJG.js";
13
+ import "./chunk-75SPJKDY.js";
12
14
  import "./chunk-IW3Q7AE5.js";
13
15
 
14
16
  // src/cli/usage.ts
@@ -23,6 +25,7 @@ function num(n) {
23
25
  async function runUsageCli(options = {}) {
24
26
  const chalk = (await import("chalk")).default;
25
27
  const config = new ConfigManager();
28
+ setCostCurrency(config.get("ui").currency);
26
29
  const tracker = new CostTracker(config.getConfigDir());
27
30
  const sinceDays = options.days ? Math.max(1, parseInt(options.days, 10) || 0) : void 0;
28
31
  const month = options.month?.trim();
@@ -80,6 +83,10 @@ async function runUsageCli(options = {}) {
80
83
  if (unknownCount > 0) {
81
84
  console.log(chalk.yellow(` \u2014 = no pricing data for ${unknownCount} model(s); their tokens are tracked but cost is excluded from the total.`));
82
85
  }
86
+ const rateNote = currencyNote();
87
+ if (rateNote) {
88
+ console.log(chalk.dim(` ${rateNote}`));
89
+ }
83
90
  console.log();
84
91
  }
85
92
  function label(r) {
Binary file
Binary file
Binary file
Binary file
@@ -7,7 +7,7 @@
7
7
  * global lexical environment and are read/reassigned here as free variables.
8
8
  */
9
9
 
10
- import { escapeHtml, formatToolArgs, formatDuration, sanitizeHtml } from './util.js';
10
+ import { escapeHtml, formatToolArgs, formatDuration, sanitizeHtml, formatCostDisplay } from './util.js';
11
11
  import { fetchSessionReplay, fetchStatus, logoutAuth, registerFirstUser, submitAuth } from './api.js';
12
12
 
13
13
  // ── State ──────────────────────────────────────────────────────────
@@ -523,8 +523,7 @@ function handleStatus(msg) {
523
523
  let line = `📊 in: ${u.inputTokens.toLocaleString()} out: ${u.outputTokens.toLocaleString()}`;
524
524
  if (cacheRead > 0) line += ` cache: ${cacheRead.toLocaleString()}`;
525
525
  if (msg.costUsd != null) {
526
- const cost = msg.costUsd;
527
- const costStr = cost === 0 ? '$0' : cost < 0.01 ? `$${cost.toFixed(4)}` : cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`;
526
+ const costStr = formatCostDisplay(msg.costUsd, msg.costCurrency, msg.costCnyPerUsd);
528
527
  line += ` 💰 ${costStr}`;
529
528
  }
530
529
  else if (msg.costPricingUnknown) {
@@ -559,9 +558,7 @@ function renderDashboard() {
559
558
  const pending = [...pendingApprovals.values()];
560
559
  const tokens = s.tokenUsage || {};
561
560
  const totalTokens = (tokens.inputTokens || 0) + (tokens.outputTokens || 0) + (tokens.cacheCreationTokens || 0) + (tokens.cacheReadTokens || 0);
562
- const cost = typeof s.costUsd === 'number'
563
- ? (s.costUsd === 0 ? '$0' : s.costUsd < 0.01 ? `$${s.costUsd.toFixed(4)}` : s.costUsd < 1 ? `$${s.costUsd.toFixed(3)}` : `$${s.costUsd.toFixed(2)}`)
564
- : 'unknown';
561
+ const cost = formatCostDisplay(s.costUsd, s.costCurrency, s.costCnyPerUsd);
565
562
  const recentTests = lastToolResults.filter(r => r.toolName === 'run_tests').slice(-3).reverse();
566
563
  const recentToolErrors = lastToolResults.filter(r => r.isError).slice(-3).reverse();
567
564
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -85,3 +85,25 @@ export function sanitizeHtml(html) {
85
85
 
86
86
  return safe;
87
87
  }
88
+
89
+ /**
90
+ * 成本显示(2026-08-14 审计 P3-02)。
91
+ *
92
+ * 客户端此前有两处各自写死 `$` 的格式化(状态栏与 Dashboard),与服务端的
93
+ * `formatCost` 是第三、第四份实现。币种由 status 消息带下来,这里只负责排版;
94
+ * 人民币按服务端给的记账换算率折算——**不是实时汇率**,调用方需在附近标注。
95
+ *
96
+ * @param {number} usd 美元金额(存储与传输恒为美元)
97
+ * @param {'usd'|'cny'} [currency]
98
+ * @param {number} [cnyPerUsd] 记账换算率,缺省时退回美元显示
99
+ */
100
+ export function formatCostDisplay(usd, currency, cnyPerUsd) {
101
+ if (typeof usd !== 'number' || !isFinite(usd)) return 'unknown';
102
+ const useCny = currency === 'cny' && typeof cnyPerUsd === 'number' && cnyPerUsd > 0;
103
+ const amount = useCny ? usd * cnyPerUsd : usd;
104
+ const sym = useCny ? '¥' : '$';
105
+ if (amount === 0) return `${sym}0`;
106
+ if (amount < 0.01) return `${sym}${amount.toFixed(4)}`;
107
+ if (amount < 1) return `${sym}${amount.toFixed(3)}`;
108
+ return `${sym}${amount.toFixed(2)}`;
109
+ }
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.268",
3
+ "version": "0.4.270",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",