jinzd-ai-cli 0.4.265 → 0.4.267

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 (33) hide show
  1. package/dist/{batch-C7SHXRQ2.js → batch-GY5MAN5F.js} +2 -2
  2. package/dist/{chunk-TJBG4F2R.js → chunk-5AEOIETL.js} +4 -4
  3. package/dist/{chunk-NOS3QV7G.js → chunk-5CU72V6P.js} +3 -3
  4. package/dist/{chunk-IFTUBTDA.js → chunk-6C6FLDGH.js} +1 -1
  5. package/dist/{chunk-M2RKBTVZ.js → chunk-7HECI3U5.js} +1 -1
  6. package/dist/{chunk-NJLPA6S4.js → chunk-ARYCWJN6.js} +1 -1
  7. package/dist/{chunk-X6NWK2YK.js → chunk-EWRZMIQE.js} +1 -1
  8. package/dist/{chunk-YXYOLPDB.js → chunk-FI6IWUXW.js} +1 -1
  9. package/dist/{chunk-CVI3LSGR.js → chunk-JH343BDI.js} +105 -2
  10. package/dist/{chunk-E3JRGJCJ.js → chunk-QN7WD2SM.js} +3 -3
  11. package/dist/{chunk-V4D7YD24.js → chunk-R7GERZDA.js} +145 -4
  12. package/dist/{chunk-LZBD2VGJ.js → chunk-RVNL7MQG.js} +1 -1
  13. package/dist/{chunk-Y7KJPNQI.js → chunk-THHPKZJ3.js} +1 -1
  14. package/dist/{chunk-2MR7EP24.js → chunk-WPN7LDLM.js} +1 -1
  15. package/dist/{chunk-YDTYBUAB.js → chunk-XLLUQEGO.js} +1 -1
  16. package/dist/{ci-TDXLI3OM.js → ci-MUEQSNKU.js} +4 -4
  17. package/dist/{ci-format-AFX2LP4Y.js → ci-format-2ZGGSOFK.js} +2 -2
  18. package/dist/{constants-66TQEDGO.js → constants-SZTJESDF.js} +1 -1
  19. package/dist/{doctor-cli-GMCOGCDR.js → doctor-cli-Z272364P.js} +4 -4
  20. package/dist/electron-server.js +781 -420
  21. package/dist/{hub-2YXETHPY.js → hub-MRLGKMRI.js} +1 -1
  22. package/dist/index.js +51 -93
  23. package/dist/{persistent-memory-WTZO5CQ7.js → persistent-memory-3FTOC7QT.js} +2 -2
  24. package/dist/{persistent-memory-2DKCETB7.js → persistent-memory-HZASMDJQ.js} +2 -2
  25. package/dist/{pr-Y2LDG3R7.js → pr-VLLIALRK.js} +4 -4
  26. package/dist/{run-tests-FIUGDIDL.js → run-tests-6MWE6D5S.js} +2 -2
  27. package/dist/{run-tests-PWTNSUVP.js → run-tests-NTDZ6DDU.js} +2 -2
  28. package/dist/{server-OZ4RFDFH.js → server-IALCWKSU.js} +61 -48
  29. package/dist/{server-ZCG5QD6P.js → server-SP3AEHRQ.js} +5 -5
  30. package/dist/{task-orchestrator-Q2L6JEUX.js → task-orchestrator-Q6WFFI4J.js} +5 -5
  31. package/dist/{usage-SDL5Z4CS.js → usage-DJDLHICZ.js} +4 -6
  32. package/package.json +1 -1
  33. package/dist/chunk-YRD6M5XI.js +0 -149
@@ -34,14 +34,14 @@ import {
34
34
  touchMemoryReferences,
35
35
  updateMemoryApproval,
36
36
  updateMemoryEntry
37
- } from "./chunk-YXYOLPDB.js";
37
+ } from "./chunk-FI6IWUXW.js";
38
38
  import {
39
39
  redactJson,
40
40
  scanString
41
41
  } from "./chunk-YVTASHS5.js";
42
42
  import {
43
43
  runTestsTool
44
- } from "./chunk-NJLPA6S4.js";
44
+ } from "./chunk-ARYCWJN6.js";
45
45
  import {
46
46
  AGENTIC_BEHAVIOR_GUIDELINE,
47
47
  APP_NAME,
@@ -73,7 +73,7 @@ import {
73
73
  SUBAGENT_MAX_ROUNDS_LIMIT,
74
74
  VERSION,
75
75
  buildUserIdentityPrompt
76
- } from "./chunk-M2RKBTVZ.js";
76
+ } from "./chunk-7HECI3U5.js";
77
77
  import {
78
78
  hasSemanticIndex,
79
79
  semanticSearch
@@ -91,8 +91,8 @@ import {
91
91
  import express from "express";
92
92
  import { createServer } from "http";
93
93
  import { WebSocketServer } from "ws";
94
- import { join as join25, dirname as dirname10, resolve as resolve10, relative as relative6, sep as sep4 } from "path";
95
- import { existsSync as existsSync30, readFileSync as readFileSync24, readdirSync as readdirSync15, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
94
+ import { join as join26, dirname as dirname10, resolve as resolve10, relative as relative6, sep as sep4 } from "path";
95
+ import { existsSync as existsSync31, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
96
96
  import { networkInterfaces } from "os";
97
97
 
98
98
  // src/config/config-manager.ts
@@ -709,6 +709,403 @@ ${err}`
709
709
  }
710
710
  };
711
711
 
712
+ // src/core/cost-tracker.ts
713
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
714
+ import { join as join2 } from "path";
715
+
716
+ // src/core/pricing.ts
717
+ var DEEPSEEK_CNY_PER_USD = 6.9;
718
+ var DEEPSEEK_TIME_PRICING_EFFECTIVE_AT = /* @__PURE__ */ new Date("2026-08-16T16:00:00.000Z");
719
+ function deepSeekCnyPricing(cacheRead, input, output) {
720
+ return {
721
+ cacheRead: cacheRead / DEEPSEEK_CNY_PER_USD,
722
+ input: input / DEEPSEEK_CNY_PER_USD,
723
+ output: output / DEEPSEEK_CNY_PER_USD
724
+ };
725
+ }
726
+ function getDeepSeekV4Pricing(model, at) {
727
+ const isPro = model.startsWith("deepseek-v4-pro");
728
+ const isFlash = model.startsWith("deepseek-v4-flash");
729
+ if (!isPro && !isFlash) return null;
730
+ if (at < DEEPSEEK_TIME_PRICING_EFFECTIVE_AT) {
731
+ return isPro ? deepSeekCnyPricing(0.025, 3, 6) : deepSeekCnyPricing(0.02, 1, 2);
732
+ }
733
+ const beijingHour = (at.getUTCHours() + 8) % 24;
734
+ const isPeak = beijingHour >= 9 && beijingHour < 12 || beijingHour >= 14 && beijingHour < 18;
735
+ if (isPro) {
736
+ return isPeak ? deepSeekCnyPricing(0.3, 9, 27) : deepSeekCnyPricing(0.15, 4.5, 13.5);
737
+ }
738
+ return isPeak ? deepSeekCnyPricing(0.1, 3, 9) : deepSeekCnyPricing(0.05, 1.5, 4.5);
739
+ }
740
+ var PRICING_TABLE = {
741
+ // ── Anthropic Claude ──────────────────────────────────────────
742
+ "claude-opus-4-8": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
743
+ "claude-opus-4-7": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
744
+ "claude-opus-4-6": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
745
+ "claude-opus-4-5": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
746
+ "claude-sonnet-4-6": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
747
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
748
+ "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
749
+ "claude-haiku-4-5": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
750
+ // Legacy Claude 3.x families (prefix fallback handles minor date suffixes)
751
+ "claude-3-5-sonnet": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
752
+ "claude-3-5-haiku": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },
753
+ "claude-3-opus": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
754
+ // ── OpenAI ────────────────────────────────────────────────────
755
+ // GAP (v0.4.184): the GPT-5 family (gpt-5, gpt-5-mini/nano, gpt-5.4, gpt-5.4-pro)
756
+ // and o4-mini are in the provider's model list but have NO verified pricing yet —
757
+ // left unpriced on purpose so `aicli usage` shows "—" (unknown) rather than a
758
+ // deceptive $0.00. Add real rates when confirmed from the official pricing page.
759
+ "gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
760
+ "gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
761
+ "gpt-4-turbo": { input: 10, output: 30 },
762
+ "gpt-4": { input: 30, output: 60 },
763
+ "gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 },
764
+ "gpt-4.1-mini": { input: 0.4, output: 1.6, cacheRead: 0.1 },
765
+ "gpt-4.1-nano": { input: 0.1, output: 0.4, cacheRead: 0.025 },
766
+ "o1": { input: 15, output: 60, cacheRead: 7.5 },
767
+ "o1-mini": { input: 3, output: 12, cacheRead: 1.5 },
768
+ "o3": { input: 10, output: 40, cacheRead: 2.5 },
769
+ "o3-mini": { input: 1.1, output: 4.4, cacheRead: 0.55 },
770
+ // ── Google Gemini ─────────────────────────────────────────────
771
+ "gemini-2.5-pro": { input: 1.25, output: 10 },
772
+ "gemini-2.5-flash": { input: 0.3, output: 2.5 },
773
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
774
+ "gemini-1.5-pro": { input: 1.25, output: 5 },
775
+ "gemini-1.5-flash": { input: 0.075, output: 0.3 },
776
+ // ── DeepSeek ──────────────────────────────────────────────────
777
+ // V4 family (2026-04-23+):1M context, Thinking / Non-Thinking dual-mode, 384K max output
778
+ // 费率 2026-08-01 对 api-docs.deepseek.com/quick_start/pricing 官方 USD 表逐项核对。
779
+ // 修正前 pro 三项整齐高 4×(官方降过价未跟)、flash cacheRead 高 10×(像是按 input 的
780
+ // 20% 拍脑袋而非抄文档)——`aicli usage` 因此系统性高报 DeepSeek 成本。
781
+ // 待办:官方已公告峰谷定价(北京时间 9-12 点、14-18 点为平时 2 倍),本表仍是单一费率,
782
+ // 届时高峰时段会低报;等该策略实际生效后再决定是否引入时段维度。
783
+ // Legacy aliases:retires 2026-07-24 UTC 15:59,官方 route 到 V4 Flash。
784
+ // 这几条只服务历史 session 的成本回算,保留当时的费率口径,不随现价改动。
785
+ "deepseek-chat": { input: 0.14, output: 0.28, cacheRead: 0.028 },
786
+ "deepseek-reasoner": { input: 0.14, output: 0.28, cacheRead: 0.028 },
787
+ "deepseek-v3": { input: 0.27, output: 1.1, cacheRead: 0.07 },
788
+ // ── Moonshot Kimi ─────────────────────────────────────────────
789
+ "moonshot-v1-8k": { input: 0.17, output: 0.17 },
790
+ "moonshot-v1-32k": { input: 0.33, output: 0.33 },
791
+ "moonshot-v1-128k": { input: 0.83, output: 0.83 },
792
+ "kimi-k2": { input: 0.6, output: 2.5 },
793
+ "kimi-latest": { input: 0.6, output: 2.5 },
794
+ // Kimi K3(2026-07 旗舰,1M 上下文)官方定价 platform.kimi.ai/pricing/chat-k3:
795
+ // 输入 cache-miss $3.00 / cache-hit $0.30,输出 $15.00(/MTok)。
796
+ "kimi-k3": { input: 3, output: 15, cacheRead: 0.3 },
797
+ // ── Zhipu GLM ─────────────────────────────────────────────────
798
+ "glm-4-plus": { input: 0.7, output: 0.7 },
799
+ "glm-4": { input: 0.14, output: 0.14 },
800
+ "glm-4-flash": { input: 0, output: 0 },
801
+ "glm-4.5": { input: 0.29, output: 1.14 },
802
+ "glm-4.6": { input: 0.6, output: 2.2 },
803
+ "glm-4.6v": { input: 0.6, output: 2.2 },
804
+ "glm-5": { input: 0.85, output: 2.85 },
805
+ // GLM-5.2(2026-06)官方直连 API 定价 $1.40 / $4.40。
806
+ "glm-5.2": { input: 1.4, output: 4.4 },
807
+ // GLM-5V-Turbo(2026-04 视觉编程旗舰)官方直连 API 定价 $1.20 / $4.00。
808
+ "glm-5v-turbo": { input: 1.2, output: 4 },
809
+ "glm-5.1": { input: 0.95, output: 3.15 },
810
+ "glm-5.1-reasoning": { input: 1.4, output: 4.4 },
811
+ "glm-5.1-air": { input: 0.4, output: 1.2 },
812
+ "glm-z1": { input: 0.5, output: 1.5 },
813
+ "glm-z1-air": { input: 0.2, output: 0.6 },
814
+ "glm-z1-flash": { input: 0, output: 0 }
815
+ // ── MiniMax (海螺) ────────────────────────────────────────────
816
+ // GAP (v0.4.184): MiniMax-M3 / M2.x are unpriced — no verified rate anchor yet.
817
+ // Unpriced → `aicli usage` shows "—" (unknown), not a false $0.00. Add when confirmed.
818
+ // ── OpenRouter (pass-through — actual cost depends on underlying model) ──
819
+ // Left empty by design: model IDs are "vendor/model" (e.g. anthropic/claude-sonnet-4)
820
+ // and the real price is set by the underlying model + OpenRouter markup, not statically
821
+ // resolvable here. Shows "—" (unknown) in `aicli usage`.
822
+ // ── Ollama (local, zero cost) ─────────────────────────────────
823
+ // Handled via provider check below (FREE_PROVIDERS → genuine $0.00, not "unknown").
824
+ };
825
+ var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama"]);
826
+ function getPricing(provider, model, at = /* @__PURE__ */ new Date()) {
827
+ if (FREE_PROVIDERS.has(provider.toLowerCase())) {
828
+ return { input: 0, output: 0 };
829
+ }
830
+ const key = model.toLowerCase();
831
+ if (provider.toLowerCase() === "deepseek") {
832
+ const dynamicPricing = getDeepSeekV4Pricing(key, at);
833
+ if (dynamicPricing) return dynamicPricing;
834
+ }
835
+ if (PRICING_TABLE[key]) return PRICING_TABLE[key];
836
+ const keys = Object.keys(PRICING_TABLE).sort((a, b) => b.length - a.length);
837
+ for (const k of keys) {
838
+ if (key.startsWith(k)) return PRICING_TABLE[k];
839
+ }
840
+ return null;
841
+ }
842
+ function computeCost(provider, model, usage, at = /* @__PURE__ */ new Date()) {
843
+ const p = getPricing(provider, model, at);
844
+ if (!p) return null;
845
+ const input = usage.inputTokens * p.input;
846
+ const output = usage.outputTokens * p.output;
847
+ const cacheWrite = (usage.cacheCreationTokens ?? 0) * (p.cacheWrite ?? p.input);
848
+ const cacheRead = (usage.cacheReadTokens ?? 0) * (p.cacheRead ?? p.input);
849
+ return (input + output + cacheWrite + cacheRead) / 1e6;
850
+ }
851
+ function formatCost(amount) {
852
+ if (amount === 0) return "$0.0000";
853
+ if (amount < 0.01) return `$${amount.toFixed(4)}`;
854
+ if (amount < 1) return `$${amount.toFixed(3)}`;
855
+ return `$${amount.toFixed(2)}`;
856
+ }
857
+
858
+ // src/core/cost-tracker.ts
859
+ var CostTracker = class _CostTracker {
860
+ filePath;
861
+ records = [];
862
+ dirty = false;
863
+ /** P2-PERF-04: debounce timer to batch rapid addCost() calls into a single save. */
864
+ _saveTimer;
865
+ constructor(configDir) {
866
+ this.filePath = join2(configDir, "cost-history.json");
867
+ this.load();
868
+ }
869
+ /** P2-PERF-04: debounce interval (ms) — coalesces multiple rapid addCost calls. */
870
+ static SAVE_DEBOUNCE_MS = 2e3;
871
+ load() {
872
+ try {
873
+ if (existsSync2(this.filePath)) {
874
+ const data = JSON.parse(readFileSync2(this.filePath, "utf-8"));
875
+ if (data.version === 1 && Array.isArray(data.records)) {
876
+ this.records = [...data.records].sort((a, b) => a.date.localeCompare(b.date));
877
+ }
878
+ }
879
+ } catch {
880
+ this.records = [];
881
+ }
882
+ }
883
+ /**
884
+ * Save to disk (atomic write).
885
+ *
886
+ * H2: Snapshot records before writing and only clear the dirty flag after
887
+ * the write succeeds. If writeFileSync/renameSync throws, dirty remains
888
+ * true so the next save() retries — prevents silent data loss on transient
889
+ * disk errors. writeFileSync is sync in Node.js so there's no interleaving
890
+ * risk within a single save() call; the snapshot mainly protects against
891
+ * future refactors to async I/O.
892
+ */
893
+ save() {
894
+ if (!this.dirty) return;
895
+ if (this._saveTimer) {
896
+ clearTimeout(this._saveTimer);
897
+ this._saveTimer = void 0;
898
+ }
899
+ const snapshot = {
900
+ version: 1,
901
+ records: [...this.records]
902
+ // shallow copy — records are plain data
903
+ };
904
+ try {
905
+ atomicWriteFileSync(this.filePath, JSON.stringify(snapshot, null, 2));
906
+ this.dirty = false;
907
+ } catch (err) {
908
+ console.error("[cost-tracker] Failed to persist cost history:", err);
909
+ }
910
+ }
911
+ /**
912
+ * Record cost from a completed session/interaction.
913
+ *
914
+ * 返回本次记账的美元金额(定价未知时返回 null)。调用方据此累计**按调用时刻**
915
+ * 算出的会话成本——2026-08-14 审计 P3-01:DeepSeek V4 自 8/17 起峰谷计价,
916
+ * 展示层若拿聚合用量在「查看时刻」现算,闲时跑的会话在峰时看会被按峰价显示,
917
+ * 跨时段会话更是整段按一个费率算。记账时刻的值才是真实花费。
918
+ */
919
+ addCost(provider, model, usage) {
920
+ const priced = computeCost(provider, model, usage);
921
+ const cost = priced ?? 0;
922
+ const inTok = usage.inputTokens || 0;
923
+ const outTok = usage.outputTokens || 0;
924
+ if (cost === 0 && inTok === 0 && outTok === 0) return priced;
925
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
926
+ let record = this.records.find((r) => r.date === today);
927
+ if (!record) {
928
+ record = { date: today, cost: 0, sessions: 0, inputTokens: 0, outputTokens: 0 };
929
+ this.records.push(record);
930
+ }
931
+ record.cost += cost;
932
+ record.sessions += 1;
933
+ record.inputTokens += inTok;
934
+ record.outputTokens += outTok;
935
+ const key = `${provider}/${model}`;
936
+ record.byModel ??= {};
937
+ const m = record.byModel[key] ??= { cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
938
+ m.cost += cost;
939
+ m.inputTokens += inTok;
940
+ m.outputTokens += outTok;
941
+ m.cacheReadTokens += usage.cacheReadTokens ?? 0;
942
+ m.calls += 1;
943
+ this.dirty = true;
944
+ if (!this._saveTimer) {
945
+ this._saveTimer = setTimeout(() => {
946
+ this._saveTimer = void 0;
947
+ this.save();
948
+ }, _CostTracker.SAVE_DEBOUNCE_MS);
949
+ if (this._saveTimer && typeof this._saveTimer === "object" && "unref" in this._saveTimer) {
950
+ this._saveTimer.unref();
951
+ }
952
+ }
953
+ if (this.records.length > 90) {
954
+ this.records = this.records.slice(-90);
955
+ }
956
+ return priced;
957
+ }
958
+ /** Get total cost for a given month ("2026-04"). */
959
+ getMonthlyCost(yearMonth) {
960
+ const prefix = yearMonth ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
961
+ return this.records.filter((r) => r.date.startsWith(prefix)).reduce((sum, r) => sum + r.cost, 0);
962
+ }
963
+ /** Get today's cost. */
964
+ getTodayCost() {
965
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
966
+ return this.records.find((r) => r.date === today)?.cost ?? 0;
967
+ }
968
+ /** Get total cost for last N days. */
969
+ getRecentCost(days) {
970
+ const cutoff = /* @__PURE__ */ new Date();
971
+ cutoff.setDate(cutoff.getDate() - days);
972
+ const cutoffStr = cutoff.toISOString().slice(0, 10);
973
+ return this.records.filter((r) => r.date >= cutoffStr).reduce((sum, r) => sum + r.cost, 0);
974
+ }
975
+ /** Get all records for display. */
976
+ getRecords() {
977
+ return [...this.records];
978
+ }
979
+ /**
980
+ * Aggregate usage across a date window, grouped by provider/model (v0.4.184+).
981
+ *
982
+ * Window selection (precedence): `month` ("2026-06") > `sinceDays` (N days back,
983
+ * inclusive of today) > all tracked history. Legacy records without a byModel
984
+ * breakdown fold into a single "(unattributed)" row so totals still reconcile
985
+ * with `getMonthlyCost()` etc.
986
+ *
987
+ * @returns rows sorted by cost desc then total tokens desc, plus grand totals
988
+ * and the actual covered date range.
989
+ */
990
+ getUsageBreakdown(opts) {
991
+ let filtered;
992
+ if (opts?.month) {
993
+ filtered = this.records.filter((r) => r.date.startsWith(opts.month));
994
+ } else if (opts?.sinceDays && opts.sinceDays > 0) {
995
+ const cutoff = /* @__PURE__ */ new Date();
996
+ cutoff.setDate(cutoff.getDate() - (opts.sinceDays - 1));
997
+ const cutoffStr = cutoff.toISOString().slice(0, 10);
998
+ filtered = this.records.filter((r) => r.date >= cutoffStr);
999
+ } else {
1000
+ filtered = [...this.records];
1001
+ }
1002
+ const acc = /* @__PURE__ */ new Map();
1003
+ const bump = (key, provider, model, v) => {
1004
+ const row = acc.get(key) ?? { key, provider, model, cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 };
1005
+ row.cost += v.cost;
1006
+ row.inputTokens += v.inputTokens;
1007
+ row.outputTokens += v.outputTokens;
1008
+ row.cacheReadTokens += v.cacheReadTokens;
1009
+ row.calls += v.calls;
1010
+ acc.set(key, row);
1011
+ };
1012
+ for (const rec of filtered) {
1013
+ if (rec.byModel && Object.keys(rec.byModel).length > 0) {
1014
+ for (const [key, m] of Object.entries(rec.byModel)) {
1015
+ const slash = key.indexOf("/");
1016
+ const provider = slash >= 0 ? key.slice(0, slash) : key;
1017
+ const model = slash >= 0 ? key.slice(slash + 1) : "";
1018
+ bump(key, provider, model, m);
1019
+ }
1020
+ } else {
1021
+ bump("(unattributed)", "(unattributed)", "", {
1022
+ cost: rec.cost,
1023
+ inputTokens: rec.inputTokens,
1024
+ outputTokens: rec.outputTokens,
1025
+ cacheReadTokens: 0,
1026
+ calls: rec.sessions
1027
+ });
1028
+ }
1029
+ }
1030
+ const rows = [...acc.values()].sort(
1031
+ (a, b) => b.cost - a.cost || b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens)
1032
+ );
1033
+ const totals = rows.reduce(
1034
+ (t, r) => {
1035
+ t.cost += r.cost;
1036
+ t.inputTokens += r.inputTokens;
1037
+ t.outputTokens += r.outputTokens;
1038
+ t.cacheReadTokens += r.cacheReadTokens;
1039
+ t.calls += r.calls;
1040
+ return t;
1041
+ },
1042
+ { cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, calls: 0 }
1043
+ );
1044
+ return {
1045
+ rows,
1046
+ totals,
1047
+ fromDate: filtered.length > 0 ? filtered[0].date : null,
1048
+ toDate: filtered.length > 0 ? filtered[filtered.length - 1].date : null,
1049
+ dayCount: filtered.length
1050
+ };
1051
+ }
1052
+ /**
1053
+ * Check if monthly cost exceeds budget, return warning message or null.
1054
+ */
1055
+ checkBudget(monthlyBudget) {
1056
+ if (!monthlyBudget || monthlyBudget <= 0) return null;
1057
+ const monthlyCost = this.getMonthlyCost();
1058
+ const ratio = monthlyCost / monthlyBudget;
1059
+ if (ratio >= 1) {
1060
+ return `\u{1F6A8} Monthly budget exceeded: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
1061
+ }
1062
+ if (ratio >= 0.8) {
1063
+ return `\u26A0 Monthly budget warning: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
1064
+ }
1065
+ return null;
1066
+ }
1067
+ /**
1068
+ * Format a cost summary for display.
1069
+ */
1070
+ formatSummary(monthlyBudget) {
1071
+ const today = this.getTodayCost();
1072
+ const monthly = this.getMonthlyCost();
1073
+ const yearMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
1074
+ const lines = [
1075
+ `Today: ${formatCost(today)}`,
1076
+ `This month: ${formatCost(monthly)} (${yearMonth})`
1077
+ ];
1078
+ if (monthlyBudget && monthlyBudget > 0) {
1079
+ const ratio = monthly / monthlyBudget;
1080
+ const bar = "\u2588".repeat(Math.min(20, Math.round(ratio * 20))) + "\u2591".repeat(Math.max(0, 20 - Math.round(ratio * 20)));
1081
+ lines.push(`Budget: ${formatCost(monthlyBudget)} [${bar}] ${Math.round(ratio * 100)}%`);
1082
+ }
1083
+ const last7 = [];
1084
+ for (let i = 6; i >= 0; i--) {
1085
+ const d = /* @__PURE__ */ new Date();
1086
+ d.setDate(d.getDate() - i);
1087
+ const dateStr = d.toISOString().slice(0, 10);
1088
+ const record = this.records.find((r) => r.date === dateStr);
1089
+ const dayLabel = dateStr.slice(5);
1090
+ last7.push(`${dayLabel}: ${record ? formatCost(record.cost) : "$0.00"}`);
1091
+ }
1092
+ lines.push(`Last 7 days: ${last7.join(" ")}`);
1093
+ return lines.join("\n");
1094
+ }
1095
+ /**
1096
+ * P2-PERF-04: flush pending cost records to disk immediately.
1097
+ * Call this before process exit or before reading cost data for display
1098
+ * to ensure the in-memory buffer is reflected on disk.
1099
+ */
1100
+ flush() {
1101
+ if (this._saveTimer) {
1102
+ clearTimeout(this._saveTimer);
1103
+ this._saveTimer = void 0;
1104
+ }
1105
+ this.save();
1106
+ }
1107
+ };
1108
+
712
1109
  // src/providers/claude.ts
713
1110
  import Anthropic from "@anthropic-ai/sdk";
714
1111
 
@@ -3905,18 +4302,18 @@ var CustomProvider = class extends OpenAICompatibleProvider {
3905
4302
  };
3906
4303
 
3907
4304
  // src/providers/model-cache.ts
3908
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync } from "fs";
3909
- import { dirname, join as join2 } from "path";
4305
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
4306
+ import { dirname, join as join3 } from "path";
3910
4307
  var CACHE_FILE_NAME = "model-cache.json";
3911
4308
  var LIVE_DEFAULT_CONTEXT = 128e3;
3912
4309
  function modelCachePath(configDir) {
3913
- return join2(configDir, CACHE_FILE_NAME);
4310
+ return join3(configDir, CACHE_FILE_NAME);
3914
4311
  }
3915
4312
  function loadModelCache(configDir) {
3916
4313
  const path3 = modelCachePath(configDir);
3917
- if (!existsSync2(path3)) return { version: 1, providers: {} };
4314
+ if (!existsSync3(path3)) return { version: 1, providers: {} };
3918
4315
  try {
3919
- const parsed = JSON.parse(readFileSync2(path3, "utf-8"));
4316
+ const parsed = JSON.parse(readFileSync3(path3, "utf-8"));
3920
4317
  if (parsed.version !== 1 || !parsed.providers || typeof parsed.providers !== "object") {
3921
4318
  return { version: 1, providers: {} };
3922
4319
  }
@@ -3961,7 +4358,7 @@ function setCachedModels(configDir, providerId, models, baseUrl) {
3961
4358
  function clearModelCache(configDir, providerId) {
3962
4359
  const path3 = modelCachePath(configDir);
3963
4360
  if (!providerId) {
3964
- if (existsSync2(path3)) unlinkSync(path3);
4361
+ if (existsSync3(path3)) unlinkSync(path3);
3965
4362
  return;
3966
4363
  }
3967
4364
  const cache = loadModelCache(configDir);
@@ -4152,8 +4549,8 @@ var ProviderRegistry = class {
4152
4549
  };
4153
4550
 
4154
4551
  // src/session/session-manager.ts
4155
- import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, statSync } from "fs";
4156
- import { join as join3 } from "path";
4552
+ import { readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, statSync } from "fs";
4553
+ import { join as join4 } from "path";
4157
4554
  import { v4 as uuidv4 } from "uuid";
4158
4555
 
4159
4556
  // src/core/types.ts
@@ -4738,7 +5135,7 @@ var SessionManager = class {
4738
5135
  if (!this._current) return;
4739
5136
  if (!force && !this._current.dirty) return;
4740
5137
  mkdirSync3(this.historyDir, { recursive: true });
4741
- const filePath = join3(this.historyDir, `${this._current.id}.json`);
5138
+ const filePath = join4(this.historyDir, `${this._current.id}.json`);
4742
5139
  const raw = this._current.toJSON();
4743
5140
  const opts = this.redactOptionsForSave();
4744
5141
  const { value: payload, hits } = redactJson(raw, opts);
@@ -4759,13 +5156,13 @@ var SessionManager = class {
4759
5156
  }
4760
5157
  }
4761
5158
  loadSession(id) {
4762
- const filePath = join3(this.historyDir, `${id}.json`);
4763
- if (!existsSync3(filePath)) {
5159
+ const filePath = join4(this.historyDir, `${id}.json`);
5160
+ if (!existsSync4(filePath)) {
4764
5161
  throw new Error(`Session ${id} not found`);
4765
5162
  }
4766
5163
  let data;
4767
5164
  try {
4768
- data = JSON.parse(readFileSync3(filePath, "utf-8"));
5165
+ data = JSON.parse(readFileSync4(filePath, "utf-8"));
4769
5166
  } catch (err) {
4770
5167
  throw new Error(`Session ${id} is corrupted: ${err instanceof Error ? err.message : String(err)}`);
4771
5168
  }
@@ -4774,7 +5171,7 @@ var SessionManager = class {
4774
5171
  return session;
4775
5172
  }
4776
5173
  listSessions(opts) {
4777
- if (!existsSync3(this.historyDir)) return [];
5174
+ if (!existsSync4(this.historyDir)) return [];
4778
5175
  try {
4779
5176
  return listFromIndex(this.historyDir, opts);
4780
5177
  } catch {
@@ -4783,7 +5180,7 @@ var SessionManager = class {
4783
5180
  const metas = [];
4784
5181
  for (const file of files) {
4785
5182
  try {
4786
- const filePath = join3(this.historyDir, file);
5183
+ const filePath = join4(this.historyDir, file);
4787
5184
  const meta = readSessionMeta(filePath);
4788
5185
  if (!meta) continue;
4789
5186
  if (opts?.includeFileSize) {
@@ -4803,8 +5200,8 @@ var SessionManager = class {
4803
5200
  return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
4804
5201
  }
4805
5202
  deleteSession(id) {
4806
- const filePath = join3(this.historyDir, `${id}.json`);
4807
- if (!existsSync3(filePath)) return false;
5203
+ const filePath = join4(this.historyDir, `${id}.json`);
5204
+ if (!existsSync4(filePath)) return false;
4808
5205
  try {
4809
5206
  unlinkSync2(filePath);
4810
5207
  if (this._current && this._current.id === id) {
@@ -4845,20 +5242,20 @@ var SessionManager = class {
4845
5242
  * 每个 session 最多返回 3 条匹配片段,全局最多 maxResults 个 session。
4846
5243
  */
4847
5244
  searchMessages(query, maxResults = 20) {
4848
- if (!existsSync3(this.historyDir)) return [];
5245
+ if (!existsSync4(this.historyDir)) return [];
4849
5246
  const q = query.toLowerCase();
4850
5247
  const candidateIds = searchCandidates(this.historyDir, q);
4851
5248
  let filesToSearch;
4852
5249
  if (candidateIds !== null) {
4853
- filesToSearch = candidateIds.map((id) => join3(this.historyDir, `${id}.json`)).filter((fp) => existsSync3(fp));
5250
+ filesToSearch = candidateIds.map((id) => join4(this.historyDir, `${id}.json`)).filter((fp) => existsSync4(fp));
4854
5251
  } else {
4855
- filesToSearch = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join3(this.historyDir, f));
5252
+ filesToSearch = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join4(this.historyDir, f));
4856
5253
  }
4857
5254
  const results = [];
4858
5255
  for (const filePath of filesToSearch) {
4859
5256
  if (results.length >= maxResults) break;
4860
5257
  try {
4861
- const data = JSON.parse(readFileSync3(filePath, "utf-8"));
5258
+ const data = JSON.parse(readFileSync4(filePath, "utf-8"));
4862
5259
  const messages = data.messages ?? [];
4863
5260
  const matches = [];
4864
5261
  for (const msg of messages) {
@@ -4905,12 +5302,12 @@ var SessionManager = class {
4905
5302
 
4906
5303
  // src/tools/builtin/bash.ts
4907
5304
  import { spawn as spawn3 } from "child_process";
4908
- import { existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
5305
+ import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
4909
5306
  import { platform as platform3 } from "os";
4910
5307
  import { resolve } from "path";
4911
5308
 
4912
5309
  // src/tools/undo-stack.ts
4913
- import { readFileSync as readFileSync4, writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync4 } from "fs";
5310
+ import { readFileSync as readFileSync5, writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
4914
5311
  var MAX_UNDO_DEPTH = 20;
4915
5312
  var UndoStack = class {
4916
5313
  stack = [];
@@ -4921,9 +5318,9 @@ var UndoStack = class {
4921
5318
  */
4922
5319
  push(filePath, description) {
4923
5320
  let previousContent = null;
4924
- if (existsSync4(filePath)) {
5321
+ if (existsSync5(filePath)) {
4925
5322
  try {
4926
- previousContent = readFileSync4(filePath, "utf-8");
5323
+ previousContent = readFileSync5(filePath, "utf-8");
4927
5324
  } catch {
4928
5325
  return;
4929
5326
  }
@@ -4977,7 +5374,7 @@ var UndoStack = class {
4977
5374
  try {
4978
5375
  if (entry.previousContent === null) {
4979
5376
  if (entry.isDirectory) {
4980
- if (existsSync4(entry.filePath)) {
5377
+ if (existsSync5(entry.filePath)) {
4981
5378
  try {
4982
5379
  rmdirSync(entry.filePath);
4983
5380
  return { entry, result: `Removed newly created directory: ${entry.filePath}` };
@@ -4987,7 +5384,7 @@ var UndoStack = class {
4987
5384
  }
4988
5385
  return { entry, result: `Directory already removed: ${entry.filePath}` };
4989
5386
  } else {
4990
- if (existsSync4(entry.filePath)) {
5387
+ if (existsSync5(entry.filePath)) {
4991
5388
  unlinkSync3(entry.filePath);
4992
5389
  }
4993
5390
  return { entry, result: `Deleted newly created file: ${entry.filePath}` };
@@ -5378,7 +5775,7 @@ Important rules:
5378
5775
  throw new ToolError("bash", blockingHint);
5379
5776
  }
5380
5777
  let currentCwd = getCwd();
5381
- if (!existsSync5(currentCwd)) {
5778
+ if (!existsSync6(currentCwd)) {
5382
5779
  const fallback = process.cwd();
5383
5780
  process.stderr.write(
5384
5781
  `[bash] Previous cwd "${currentCwd}" no longer exists, reset to "${fallback}"
@@ -5390,7 +5787,7 @@ Important rules:
5390
5787
  let effectiveCwd = currentCwd;
5391
5788
  if (cwdArg) {
5392
5789
  const resolved = resolve(currentCwd, cwdArg);
5393
- if (!existsSync5(resolved)) {
5790
+ if (!existsSync6(resolved)) {
5394
5791
  throw new ToolError(
5395
5792
  "bash",
5396
5793
  `cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
@@ -5410,7 +5807,7 @@ Important rules:
5410
5807
  const parsedTargets = parseCreationTargets(command, effectiveCwd);
5411
5808
  const parsedTargetsBefore = /* @__PURE__ */ new Map();
5412
5809
  for (const t of parsedTargets) {
5413
- parsedTargetsBefore.set(t, existsSync5(t));
5810
+ parsedTargetsBefore.set(t, existsSync6(t));
5414
5811
  }
5415
5812
  let poolResult = null;
5416
5813
  try {
@@ -5707,7 +6104,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
5707
6104
  }
5708
6105
  }
5709
6106
  for (const [target, existedBefore] of parsedTargetsBefore) {
5710
- if (!existedBefore && !tracked.has(target) && existsSync5(target)) {
6107
+ if (!existedBefore && !tracked.has(target) && existsSync6(target)) {
5711
6108
  try {
5712
6109
  const st = statSync2(target);
5713
6110
  if (st.isDirectory()) {
@@ -5794,7 +6191,7 @@ function updateCwdFromCommand(command, baseCwd) {
5794
6191
  if (!target || target.startsWith("$") || target === "~") return;
5795
6192
  try {
5796
6193
  const newDir = resolve(baseCwd, target);
5797
- if (existsSync5(newDir)) {
6194
+ if (existsSync6(newDir)) {
5798
6195
  setCwd(newDir);
5799
6196
  }
5800
6197
  } catch {
@@ -5802,7 +6199,7 @@ function updateCwdFromCommand(command, baseCwd) {
5802
6199
  }
5803
6200
 
5804
6201
  // src/tools/builtin/read-file.ts
5805
- import { readFileSync as readFileSync5, existsSync as existsSync6, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
6202
+ import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
5806
6203
  import { execFileSync } from "child_process";
5807
6204
  import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
5808
6205
  import { homedir as homedir2 } from "os";
@@ -6079,7 +6476,7 @@ var readFileTool = {
6079
6476
  const normalizedPath = resolve2(filePath);
6080
6477
  const refusal = getHardRefusal(normalizedPath);
6081
6478
  if (refusal) return refusal;
6082
- if (!existsSync6(normalizedPath)) {
6479
+ if (!existsSync7(normalizedPath)) {
6083
6480
  const suggestions = findSimilarFiles(filePath);
6084
6481
  if (suggestions.length > 0) {
6085
6482
  throw new ToolError(
@@ -6130,7 +6527,7 @@ ${pdfText}`;
6130
6527
  }
6131
6528
  const dir = dirname2(normalizedPath);
6132
6529
  const nameNoExt = basename(normalizedPath, ext);
6133
- const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(existsSync6);
6530
+ const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(existsSync7);
6134
6531
  if (textAlts.length > 0) {
6135
6532
  return `[PDF file: ${filePath}]
6136
6533
  Cannot extract text from this PDF, but found alternative text versions:
@@ -6143,7 +6540,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
6143
6540
  }
6144
6541
  if (ext === ".ipynb") {
6145
6542
  try {
6146
- const raw = readFileSync5(normalizedPath, "utf-8");
6543
+ const raw = readFileSync6(normalizedPath, "utf-8");
6147
6544
  const nb = parseNotebook(raw);
6148
6545
  return renderNotebookAsText(nb, filePath);
6149
6546
  } catch (err) {
@@ -6156,7 +6553,7 @@ ${err.message}`;
6156
6553
  This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
6157
6554
  }
6158
6555
  const { readFile: readFile2 } = await import("fs/promises");
6159
- const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync5(normalizedPath);
6556
+ const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync6(normalizedPath);
6160
6557
  if (encoding === "base64") {
6161
6558
  return `[File: ${filePath} | base64]
6162
6559
 
@@ -6194,12 +6591,12 @@ ${content}`;
6194
6591
  };
6195
6592
 
6196
6593
  // src/tools/builtin/write-file.ts
6197
- import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
6594
+ import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
6198
6595
  import { dirname as dirname5 } from "path";
6199
6596
 
6200
6597
  // src/tools/executor.ts
6201
6598
  import chalk3 from "chalk";
6202
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
6599
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
6203
6600
  import { tmpdir } from "os";
6204
6601
 
6205
6602
  // src/core/readline-internal.ts
@@ -6439,8 +6836,8 @@ function simpleDiff(oldLines, newLines) {
6439
6836
  // src/tools/hooks.ts
6440
6837
  import { execSync } from "child_process";
6441
6838
  import { createHash } from "crypto";
6442
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
6443
- import { join as join4 } from "path";
6839
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7 } from "fs";
6840
+ import { join as join5 } from "path";
6444
6841
  var TRUST_FILE = "hooks-trust.json";
6445
6842
  var DEFAULT_TIMEOUT_MS = 5e3;
6446
6843
  function asArray(entry) {
@@ -6449,13 +6846,13 @@ function asArray(entry) {
6449
6846
  return raw.map((item) => typeof item === "string" ? { command: item } : item);
6450
6847
  }
6451
6848
  function hookTrustPath(configDir) {
6452
- return join4(configDir, TRUST_FILE);
6849
+ return join5(configDir, TRUST_FILE);
6453
6850
  }
6454
6851
  function loadHookTrustStore(configDir) {
6455
6852
  const file = hookTrustPath(configDir);
6456
- if (!existsSync7(file)) return { records: [] };
6853
+ if (!existsSync8(file)) return { records: [] };
6457
6854
  try {
6458
- const parsed = JSON.parse(readFileSync6(file, "utf-8"));
6855
+ const parsed = JSON.parse(readFileSync7(file, "utf-8"));
6459
6856
  return { records: Array.isArray(parsed.records) ? parsed.records : [] };
6460
6857
  } catch {
6461
6858
  return { records: [] };
@@ -6599,7 +6996,7 @@ function runHook(template, vars) {
6599
6996
  }
6600
6997
 
6601
6998
  // src/tools/permissions.ts
6602
- import { existsSync as existsSync8, realpathSync } from "fs";
6999
+ import { existsSync as existsSync9, realpathSync } from "fs";
6603
7000
  import { basename as basename2, dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
6604
7001
  function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
6605
7002
  for (const rule of rules) {
@@ -6648,7 +7045,7 @@ function normalizePathForPermission(value) {
6648
7045
  function canonicalizeWithNearestParent(value) {
6649
7046
  const missing = [];
6650
7047
  let cursor = resolve3(value);
6651
- while (!existsSync8(cursor)) {
7048
+ while (!existsSync9(cursor)) {
6652
7049
  const parent = dirname3(cursor);
6653
7050
  if (parent === cursor) return null;
6654
7051
  missing.unshift(basename2(cursor));
@@ -7013,8 +7410,8 @@ var theme = new Proxy(DARK_THEME, {
7013
7410
  });
7014
7411
 
7015
7412
  // src/diagnostics/tool-stats.ts
7016
- import { existsSync as existsSync9, readFileSync as readFileSync7, mkdirSync as mkdirSync5 } from "fs";
7017
- import { join as join5, dirname as dirname4 } from "path";
7413
+ import { existsSync as existsSync10, readFileSync as readFileSync8, mkdirSync as mkdirSync5 } from "fs";
7414
+ import { join as join6, dirname as dirname4 } from "path";
7018
7415
  import { homedir as homedir3 } from "os";
7019
7416
  var STATS_FILE_NAME = "tool-stats.json";
7020
7417
  var STATS_VERSION = 1;
@@ -7024,16 +7421,16 @@ var dirty = false;
7024
7421
  var pendingWrites = 0;
7025
7422
  var configDirOverride;
7026
7423
  function statsFilePath() {
7027
- const base = configDirOverride ?? join5(homedir3(), CONFIG_DIR_NAME);
7028
- return join5(base, STATS_FILE_NAME);
7424
+ const base = configDirOverride ?? join6(homedir3(), CONFIG_DIR_NAME);
7425
+ return join6(base, STATS_FILE_NAME);
7029
7426
  }
7030
7427
  function load() {
7031
7428
  const path3 = statsFilePath();
7032
- if (!existsSync9(path3)) {
7429
+ if (!existsSync10(path3)) {
7033
7430
  return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
7034
7431
  }
7035
7432
  try {
7036
- const raw = JSON.parse(readFileSync7(path3, "utf-8"));
7433
+ const raw = JSON.parse(readFileSync8(path3, "utf-8"));
7037
7434
  if (raw.version !== STATS_VERSION || !raw.entries) {
7038
7435
  return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
7039
7436
  }
@@ -7115,8 +7512,8 @@ function getTopUsedTools(limit = 5) {
7115
7512
  }
7116
7513
 
7117
7514
  // src/tools/action-classifier.ts
7118
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
7119
- import { isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "path";
7515
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
7516
+ import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
7120
7517
  var RECENT_DENIED_LIMIT = 50;
7121
7518
  var recentlyDenied = [];
7122
7519
  function recordRecentlyDeniedAutoAction(call, classification) {
@@ -7157,10 +7554,10 @@ function splitShellWords(command) {
7157
7554
  return out.filter(Boolean);
7158
7555
  }
7159
7556
  function readPackageManifest(root) {
7160
- const p = join6(root, "package.json");
7161
- if (!existsSync10(p)) return null;
7557
+ const p = join7(root, "package.json");
7558
+ if (!existsSync11(p)) return null;
7162
7559
  try {
7163
- return JSON.parse(readFileSync8(p, "utf-8"));
7560
+ return JSON.parse(readFileSync9(p, "utf-8"));
7164
7561
  } catch {
7165
7562
  return null;
7166
7563
  }
@@ -7176,10 +7573,10 @@ function manifestHasDependency(root, name) {
7176
7573
  }
7177
7574
  function lockfileMentions(root, name) {
7178
7575
  for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
7179
- const p = join6(root, file);
7180
- if (!existsSync10(p)) continue;
7576
+ const p = join7(root, file);
7577
+ if (!existsSync11(p)) continue;
7181
7578
  try {
7182
- if (readFileSync8(p, "utf-8").includes(name)) return true;
7579
+ if (readFileSync9(p, "utf-8").includes(name)) return true;
7183
7580
  } catch {
7184
7581
  }
7185
7582
  }
@@ -7753,10 +8150,10 @@ var ToolExecutor = class {
7753
8150
  if (call.name === "write_file") {
7754
8151
  const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
7755
8152
  if (!filePath) return;
7756
- if (existsSync11(filePath)) {
8153
+ if (existsSync12(filePath)) {
7757
8154
  let oldContent;
7758
8155
  try {
7759
- oldContent = readFileSync9(filePath, "utf-8");
8156
+ oldContent = readFileSync10(filePath, "utf-8");
7760
8157
  } catch {
7761
8158
  return;
7762
8159
  }
@@ -7780,7 +8177,7 @@ var ToolExecutor = class {
7780
8177
  }
7781
8178
  } else if (call.name === "edit_file") {
7782
8179
  const filePath = String(call.arguments["path"] ?? "");
7783
- if (!filePath || !existsSync11(filePath)) return;
8180
+ if (!filePath || !existsSync12(filePath)) return;
7784
8181
  const oldStr = call.arguments["old_str"];
7785
8182
  const newStr = call.arguments["new_str"];
7786
8183
  if (oldStr !== void 0) {
@@ -7807,7 +8204,7 @@ var ToolExecutor = class {
7807
8204
  const to = Number(call.arguments["delete_to_line"] ?? from);
7808
8205
  let fileContent;
7809
8206
  try {
7810
- fileContent = readFileSync9(filePath, "utf-8");
8207
+ fileContent = readFileSync10(filePath, "utf-8");
7811
8208
  } catch {
7812
8209
  return;
7813
8210
  }
@@ -7993,7 +8390,7 @@ var subAgentGuard = {
7993
8390
 
7994
8391
  // src/tools/builtin/write-file.ts
7995
8392
  function countFileLines(filePath) {
7996
- return readFileSync10(filePath, "utf-8").split("\n").length;
8393
+ return readFileSync11(filePath, "utf-8").split("\n").length;
7997
8394
  }
7998
8395
  var writeFileTool = {
7999
8396
  definition: {
@@ -8076,7 +8473,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
8076
8473
  };
8077
8474
 
8078
8475
  // src/tools/builtin/edit-file.ts
8079
- import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
8476
+ import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
8080
8477
 
8081
8478
  // src/tools/builtin/patch-apply.ts
8082
8479
  function parseUnifiedDiff(patch) {
@@ -8444,7 +8841,7 @@ Note: Path can be absolute or relative to cwd.`,
8444
8841
  const filePath = String(args["path"] ?? args["file"] ?? "");
8445
8842
  const encoding = args["encoding"] ?? "utf-8";
8446
8843
  if (!filePath) throw new ToolError("edit_file", "path is required");
8447
- if (!existsSync12(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
8844
+ if (!existsSync13(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
8448
8845
  const verdict = classifyWritePath(filePath);
8449
8846
  if (verdict.sensitive && subAgentGuard.active) {
8450
8847
  throw new ToolError(
@@ -8452,7 +8849,7 @@ Note: Path can be absolute or relative to cwd.`,
8452
8849
  `Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
8453
8850
  );
8454
8851
  }
8455
- const original = readFileSync11(filePath, encoding);
8852
+ const original = readFileSync12(filePath, encoding);
8456
8853
  if (args["patch"] !== void 0) {
8457
8854
  const patchText = String(args["patch"] ?? "");
8458
8855
  const stopOnError = args["stop_on_error"] !== false;
@@ -8623,8 +9020,8 @@ function truncatePreview(str, maxLen = 80) {
8623
9020
  }
8624
9021
 
8625
9022
  // src/tools/builtin/list-dir.ts
8626
- import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync13 } from "fs";
8627
- import { join as join7, basename as basename4 } from "path";
9023
+ import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync14 } from "fs";
9024
+ import { join as join8, basename as basename4 } from "path";
8628
9025
  var listDirTool = {
8629
9026
  definition: {
8630
9027
  name: "list_dir",
@@ -8646,7 +9043,7 @@ var listDirTool = {
8646
9043
  async execute(args) {
8647
9044
  const dirPath = String(args["path"] ?? process.cwd());
8648
9045
  const recursive = Boolean(args["recursive"] ?? false);
8649
- if (!existsSync13(dirPath)) {
9046
+ if (!existsSync14(dirPath)) {
8650
9047
  const targetName = basename4(dirPath).toLowerCase();
8651
9048
  const cwd = process.cwd();
8652
9049
  const suggestions = [];
@@ -8704,11 +9101,11 @@ function listRecursive(basePath, indent, recursive, lines) {
8704
9101
  if (entry.isDirectory()) {
8705
9102
  lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
8706
9103
  if (recursive) {
8707
- listRecursive(join7(basePath, entry.name), indent + " ", true, lines);
9104
+ listRecursive(join8(basePath, entry.name), indent + " ", true, lines);
8708
9105
  }
8709
9106
  } else {
8710
9107
  try {
8711
- const stat = statSync5(join7(basePath, entry.name));
9108
+ const stat = statSync5(join8(basePath, entry.name));
8712
9109
  const size = formatSize(stat.size);
8713
9110
  lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
8714
9111
  } catch {
@@ -8724,9 +9121,9 @@ function formatSize(bytes) {
8724
9121
  }
8725
9122
 
8726
9123
  // src/tools/builtin/grep-files.ts
8727
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync6, existsSync as existsSync14 } from "fs";
9124
+ import { readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync6, existsSync as existsSync15 } from "fs";
8728
9125
  import { readFile } from "fs/promises";
8729
- import { join as join8, relative } from "path";
9126
+ import { join as join9, relative } from "path";
8730
9127
  var grepFilesTool = {
8731
9128
  definition: {
8732
9129
  name: "grep_files",
@@ -8777,7 +9174,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
8777
9174
  const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
8778
9175
  const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
8779
9176
  if (!pattern) throw new ToolError("grep_files", "pattern is required");
8780
- if (!existsSync14(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
9177
+ if (!existsSync15(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
8781
9178
  const MAX_PATTERN_LENGTH = 1e3;
8782
9179
  if (pattern.length > MAX_PATTERN_LENGTH) {
8783
9180
  throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
@@ -8871,11 +9268,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
8871
9268
  if (paths.length >= maxFiles) return;
8872
9269
  if (entry.isDirectory()) {
8873
9270
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
8874
- walk(join8(dirPath, entry.name));
9271
+ walk(join9(dirPath, entry.name));
8875
9272
  } else if (entry.isFile()) {
8876
9273
  if (isBinary(entry.name)) continue;
8877
9274
  if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
8878
- const fullPath = join8(dirPath, entry.name);
9275
+ const fullPath = join9(dirPath, entry.name);
8879
9276
  try {
8880
9277
  if (statSync6(fullPath).size > 1e6) continue;
8881
9278
  } catch {
@@ -8930,7 +9327,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
8930
9327
  }
8931
9328
  let content;
8932
9329
  try {
8933
- content = readFileSync12(fullPath, "utf-8");
9330
+ content = readFileSync13(fullPath, "utf-8");
8934
9331
  } catch {
8935
9332
  return;
8936
9333
  }
@@ -8961,8 +9358,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
8961
9358
  }
8962
9359
 
8963
9360
  // src/tools/builtin/glob-files.ts
8964
- import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync15 } from "fs";
8965
- import { join as join9, relative as relative2, basename as basename5 } from "path";
9361
+ import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync16 } from "fs";
9362
+ import { join as join10, relative as relative2, basename as basename5 } from "path";
8966
9363
  var globFilesTool = {
8967
9364
  definition: {
8968
9365
  name: "glob_files",
@@ -8995,7 +9392,7 @@ Results sorted by most recent modification time. Automatically skips node_module
8995
9392
  const rootPath = String(args["path"] ?? process.cwd());
8996
9393
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
8997
9394
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
8998
- if (!existsSync15(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
9395
+ if (!existsSync16(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
8999
9396
  const regex = globToRegex(pattern);
9000
9397
  const matches = [];
9001
9398
  collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
@@ -9072,7 +9469,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
9072
9469
  }
9073
9470
  for (const entry of entries) {
9074
9471
  if (results.length >= maxResults) break;
9075
- const fullPath = join9(dirPath, entry.name);
9472
+ const fullPath = join10(dirPath, entry.name);
9076
9473
  if (entry.isDirectory()) {
9077
9474
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
9078
9475
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
@@ -9914,10 +10311,10 @@ Any of these triggers means use save_last_response, NOT write_file:
9914
10311
  };
9915
10312
 
9916
10313
  // src/tools/builtin/save-memory.ts
9917
- import { join as join10 } from "path";
10314
+ import { join as join11 } from "path";
9918
10315
  import { homedir as homedir5 } from "os";
9919
10316
  function getConfigDir() {
9920
- return join10(homedir5(), CONFIG_DIR_NAME);
10317
+ return join11(homedir5(), CONFIG_DIR_NAME);
9921
10318
  }
9922
10319
  function parseSensitivity(value) {
9923
10320
  return value === "low" || value === "medium" || value === "high" ? value : void 0;
@@ -10266,13 +10663,13 @@ function formatResults2(query, data, _requested) {
10266
10663
  }
10267
10664
 
10268
10665
  // src/agents/agent-config.ts
10269
- import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync14 } from "fs";
10270
- import { join as join12 } from "path";
10666
+ import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync15 } from "fs";
10667
+ import { join as join13 } from "path";
10271
10668
  import { homedir as homedir6 } from "os";
10272
10669
 
10273
10670
  // src/plugins/plugin-manager.ts
10274
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
10275
- import { basename as basename6, dirname as dirname7, join as join11, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
10671
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync14, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
10672
+ import { basename as basename6, dirname as dirname7, join as join12, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
10276
10673
  import { createHash as createHash2 } from "crypto";
10277
10674
  import { z as z2 } from "zod";
10278
10675
  var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
@@ -10332,19 +10729,19 @@ var PluginManifestSchema = z2.object({
10332
10729
  permissionHints: z2.array(z2.string()).default([])
10333
10730
  }).strict();
10334
10731
  function pluginRoot(configDir) {
10335
- return join11(configDir, PLUGINS_DIR_NAME);
10732
+ return join12(configDir, PLUGINS_DIR_NAME);
10336
10733
  }
10337
10734
  function pluginManifestPath(pluginDir) {
10338
- return join11(pluginDir, MANIFEST_RELATIVE);
10735
+ return join12(pluginDir, MANIFEST_RELATIVE);
10339
10736
  }
10340
10737
  function statePath(configDir) {
10341
- return join11(pluginRoot(configDir), STATE_FILE);
10738
+ return join12(pluginRoot(configDir), STATE_FILE);
10342
10739
  }
10343
10740
  function loadState(configDir) {
10344
10741
  const file = statePath(configDir);
10345
- if (!existsSync16(file)) return { version: 2, plugins: [] };
10742
+ if (!existsSync17(file)) return { version: 2, plugins: [] };
10346
10743
  try {
10347
- const parsed = JSON.parse(readFileSync13(file, "utf-8"));
10744
+ const parsed = JSON.parse(readFileSync14(file, "utf-8"));
10348
10745
  const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
10349
10746
  return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
10350
10747
  } catch {
@@ -10402,7 +10799,7 @@ function extractCommandPaths(command, pluginDir) {
10402
10799
  try {
10403
10800
  const abs = resolve6(resolvedDir, ref);
10404
10801
  if (!abs.startsWith(resolvedDir + sep3)) continue;
10405
- if (existsSync16(abs)) {
10802
+ if (existsSync17(abs)) {
10406
10803
  const real = realpathSync2(abs);
10407
10804
  if (!real.startsWith(resolvedDir + sep3)) continue;
10408
10805
  valid.push(relative3(resolvedDir, real));
@@ -10434,8 +10831,8 @@ function hashPluginContent(pluginDir, manifest) {
10434
10831
  }
10435
10832
  const sorted = [...assetRels].sort();
10436
10833
  const hasher = createHash2("sha256");
10437
- const manifestPath = join11(resolvedDir, MANIFEST_RELATIVE);
10438
- hasher.update(normalizeForHash(readFileSync13(manifestPath)));
10834
+ const manifestPath = join12(resolvedDir, MANIFEST_RELATIVE);
10835
+ hasher.update(normalizeForHash(readFileSync14(manifestPath)));
10439
10836
  for (const rel of sorted) {
10440
10837
  const abs = resolve6(resolvedDir, rel);
10441
10838
  if (!abs.startsWith(resolvedDir + sep3)) {
@@ -10455,7 +10852,7 @@ function hashPluginContent(pluginDir, manifest) {
10455
10852
  if (!realPath.startsWith(resolvedDir + sep3)) {
10456
10853
  throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
10457
10854
  }
10458
- hasher.update(normalizeForHash(readFileSync13(abs)));
10855
+ hasher.update(normalizeForHash(readFileSync14(abs)));
10459
10856
  }
10460
10857
  return hasher.digest("hex");
10461
10858
  }
@@ -10463,22 +10860,22 @@ function effectiveHash(entry) {
10463
10860
  return entry.contentHash ?? entry.hash ?? "";
10464
10861
  }
10465
10862
  function readPluginManifest(manifestPath) {
10466
- return PluginManifestSchema.parse(JSON.parse(readFileSync13(manifestPath, "utf-8")));
10863
+ return PluginManifestSchema.parse(JSON.parse(readFileSync14(manifestPath, "utf-8")));
10467
10864
  }
10468
10865
  function findPluginSourceDir(inputPath) {
10469
10866
  const abs = resolve6(inputPath);
10470
10867
  const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
10471
- if (existsSync16(pluginManifestPath(direct))) return direct;
10472
- const nested = join11(direct, ".aicli-plugin");
10473
- if (existsSync16(join11(nested, "plugin.json"))) return direct;
10868
+ if (existsSync17(pluginManifestPath(direct))) return direct;
10869
+ const nested = join12(direct, ".aicli-plugin");
10870
+ if (existsSync17(join12(nested, "plugin.json"))) return direct;
10474
10871
  throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
10475
10872
  }
10476
10873
  function installPlugin(configDir, inputPath) {
10477
10874
  const sourceDir = findPluginSourceDir(inputPath);
10478
10875
  const manifest = readPluginManifest(pluginManifestPath(sourceDir));
10479
- const targetDir = join11(pluginRoot(configDir), manifest.name);
10876
+ const targetDir = join12(pluginRoot(configDir), manifest.name);
10480
10877
  mkdirSync8(pluginRoot(configDir), { recursive: true });
10481
- if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10878
+ if (existsSync17(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10482
10879
  cpSync(sourceDir, targetDir, { recursive: true });
10483
10880
  const manifestPath = pluginManifestPath(targetDir);
10484
10881
  const contentHash = hashPluginContent(targetDir, manifest);
@@ -10536,18 +10933,18 @@ function getInstalledPlugin(configDir, name) {
10536
10933
  }
10537
10934
  function listInstalledPlugins(configDir) {
10538
10935
  const root = pluginRoot(configDir);
10539
- if (!existsSync16(root)) return [];
10936
+ if (!existsSync17(root)) return [];
10540
10937
  const state2 = loadState(configDir);
10541
10938
  const out = [];
10542
10939
  for (const entry of readdirSync7(root)) {
10543
- const dir = join11(root, entry);
10940
+ const dir = join12(root, entry);
10544
10941
  try {
10545
10942
  if (!statSync8(dir).isDirectory()) continue;
10546
10943
  } catch {
10547
10944
  continue;
10548
10945
  }
10549
10946
  const manifestPath = pluginManifestPath(dir);
10550
- if (!existsSync16(manifestPath)) continue;
10947
+ if (!existsSync17(manifestPath)) continue;
10551
10948
  try {
10552
10949
  const manifest = readPluginManifest(manifestPath);
10553
10950
  const contentHash = hashPluginContent(dir, manifest);
@@ -10587,7 +10984,7 @@ function uniqueDirs(plugin, rels) {
10587
10984
  for (const rel of rels) {
10588
10985
  const abs = resolve6(plugin.dir, rel);
10589
10986
  if (!abs.startsWith(resolve6(plugin.dir))) continue;
10590
- if (existsSync16(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
10987
+ if (existsSync17(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
10591
10988
  }
10592
10989
  return [...dirs];
10593
10990
  }
@@ -10750,13 +11147,13 @@ function parseAgentConfig(raw, source, path3) {
10750
11147
  return cfg;
10751
11148
  }
10752
11149
  function loadAgentDir(dir, source) {
10753
- if (!existsSync17(dir)) return [];
11150
+ if (!existsSync18(dir)) return [];
10754
11151
  const out = [];
10755
11152
  for (const file of readdirSync8(dir)) {
10756
11153
  if (!file.endsWith(".json")) continue;
10757
- const path3 = join12(dir, file);
11154
+ const path3 = join13(dir, file);
10758
11155
  try {
10759
- const parsed = JSON.parse(readFileSync14(path3, "utf-8"));
11156
+ const parsed = JSON.parse(readFileSync15(path3, "utf-8"));
10760
11157
  const cfg = parseAgentConfig(parsed, source, path3);
10761
11158
  if (cfg) out.push(cfg);
10762
11159
  } catch {
@@ -10766,8 +11163,8 @@ function loadAgentDir(dir, source) {
10766
11163
  }
10767
11164
  function getAgentDirs(configDir, cwd = process.cwd()) {
10768
11165
  return {
10769
- user: join12(configDir ?? join12(homedir6(), CONFIG_DIR_NAME), "agents"),
10770
- project: join12(cwd, CONFIG_DIR_NAME, "agents")
11166
+ user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
11167
+ project: join13(cwd, CONFIG_DIR_NAME, "agents")
10771
11168
  };
10772
11169
  }
10773
11170
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -11485,14 +11882,14 @@ var taskStopTool = {
11485
11882
 
11486
11883
  // src/tools/builtin/git-tools.ts
11487
11884
  import { execFileSync as execFileSync2 } from "child_process";
11488
- import { existsSync as existsSync18 } from "fs";
11489
- import { join as join13 } from "path";
11885
+ import { existsSync as existsSync19 } from "fs";
11886
+ import { join as join14 } from "path";
11490
11887
  function assertGitRepo(cwd) {
11491
11888
  let dir = cwd;
11492
11889
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
11493
11890
  while (dir && dir !== root) {
11494
- if (existsSync18(join13(dir, ".git"))) return;
11495
- const parent = join13(dir, "..");
11891
+ if (existsSync19(join14(dir, ".git"))) return;
11892
+ const parent = join14(dir, "..");
11496
11893
  if (parent === dir) break;
11497
11894
  dir = parent;
11498
11895
  }
@@ -11757,7 +12154,7 @@ ${commitOutput.trim()}`;
11757
12154
  };
11758
12155
 
11759
12156
  // src/tools/builtin/notebook-edit.ts
11760
- import { readFileSync as readFileSync15, existsSync as existsSync19 } from "fs";
12157
+ import { readFileSync as readFileSync16, existsSync as existsSync20 } from "fs";
11761
12158
  import { writeFile } from "fs/promises";
11762
12159
  import { resolve as resolve7, extname as extname2 } from "path";
11763
12160
  var notebookEditTool = {
@@ -11812,10 +12209,10 @@ var notebookEditTool = {
11812
12209
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
11813
12210
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
11814
12211
  }
11815
- if (!existsSync19(absPath)) {
12212
+ if (!existsSync20(absPath)) {
11816
12213
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
11817
12214
  }
11818
- const raw = readFileSync15(absPath, "utf-8");
12215
+ const raw = readFileSync16(absPath, "utf-8");
11819
12216
  const nb = parseNotebook(raw);
11820
12217
  const cellIdx0 = cellIndexRaw - 1;
11821
12218
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -12141,7 +12538,7 @@ ${lines.join("\n")}`;
12141
12538
  };
12142
12539
 
12143
12540
  // src/tools/builtin/recall-memory.ts
12144
- import { join as join14 } from "path";
12541
+ import { join as join15 } from "path";
12145
12542
  import { homedir as homedir7 } from "os";
12146
12543
  function formatHit(h, i) {
12147
12544
  const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
@@ -12192,7 +12589,7 @@ var recallMemoryTool = {
12192
12589
  const excludeCurrent = Boolean(args["excludeCurrentSession"]);
12193
12590
  const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
12194
12591
  const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
12195
- const configDir = join14(homedir7(), CONFIG_DIR_NAME);
12592
+ const configDir = join15(homedir7(), CONFIG_DIR_NAME);
12196
12593
  let persistentSection = "";
12197
12594
  try {
12198
12595
  const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
@@ -12246,8 +12643,8 @@ function estimateToolDefinitionTokens(def) {
12246
12643
 
12247
12644
  // src/tools/registry.ts
12248
12645
  import { pathToFileURL } from "url";
12249
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
12250
- import { join as join15 } from "path";
12646
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
12647
+ import { join as join16 } from "path";
12251
12648
  var ToolRegistry = class {
12252
12649
  tools = /* @__PURE__ */ new Map();
12253
12650
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -12408,7 +12805,7 @@ var ToolRegistry = class {
12408
12805
  * Returns the number of successfully loaded plugins.
12409
12806
  */
12410
12807
  async loadPlugins(pluginsDir, allowPlugins = false) {
12411
- if (!existsSync20(pluginsDir)) {
12808
+ if (!existsSync21(pluginsDir)) {
12412
12809
  try {
12413
12810
  mkdirSync9(pluginsDir, { recursive: true });
12414
12811
  } catch {
@@ -12434,12 +12831,12 @@ var ToolRegistry = class {
12434
12831
  process.stderr.write(
12435
12832
  `
12436
12833
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
12437
- ` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
12834
+ ` + files.map((f) => ` + ${join16(pluginsDir, f)}`).join("\n") + "\n\n"
12438
12835
  );
12439
12836
  let loaded = 0;
12440
12837
  for (const file of files) {
12441
12838
  try {
12442
- const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
12839
+ const fileUrl = pathToFileURL(join16(pluginsDir, file)).href;
12443
12840
  const mod = await import(fileUrl);
12444
12841
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
12445
12842
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -12984,11 +13381,11 @@ var McpManager = class {
12984
13381
  };
12985
13382
 
12986
13383
  // src/skills/manager.ts
12987
- import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
12988
- import { join as join16 } from "path";
13384
+ import { existsSync as existsSync22, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
13385
+ import { join as join17 } from "path";
12989
13386
 
12990
13387
  // src/skills/types.ts
12991
- import { readFileSync as readFileSync16 } from "fs";
13388
+ import { readFileSync as readFileSync17 } from "fs";
12992
13389
  import { basename as basename7 } from "path";
12993
13390
  function parseSimpleYaml(yaml) {
12994
13391
  const result = {};
@@ -13010,7 +13407,7 @@ function parseYamlArray(value) {
13010
13407
  function parseSkillFile(filePath) {
13011
13408
  let raw;
13012
13409
  try {
13013
- raw = readFileSync16(filePath, "utf-8");
13410
+ raw = readFileSync17(filePath, "utf-8");
13014
13411
  } catch {
13015
13412
  return null;
13016
13413
  }
@@ -13058,7 +13455,7 @@ var SkillManager = class {
13058
13455
  return this.skills.size;
13059
13456
  }
13060
13457
  loadSkillDir(dir, createIfMissing) {
13061
- if (!existsSync21(dir)) {
13458
+ if (!existsSync22(dir)) {
13062
13459
  if (createIfMissing) {
13063
13460
  try {
13064
13461
  mkdirSync10(dir, { recursive: true });
@@ -13075,14 +13472,14 @@ var SkillManager = class {
13075
13472
  }
13076
13473
  for (const entry of entries) {
13077
13474
  let filePath;
13078
- const fullPath = join16(dir, entry);
13475
+ const fullPath = join17(dir, entry);
13079
13476
  if (entry.endsWith(".md")) {
13080
13477
  filePath = fullPath;
13081
13478
  } else {
13082
13479
  try {
13083
13480
  if (statSync9(fullPath).isDirectory()) {
13084
- const skillMd = join16(fullPath, "SKILL.md");
13085
- if (existsSync21(skillMd)) filePath = skillMd;
13481
+ const skillMd = join17(fullPath, "SKILL.md");
13482
+ if (existsSync22(skillMd)) filePath = skillMd;
13086
13483
  else continue;
13087
13484
  } else {
13088
13485
  continue;
@@ -13141,7 +13538,7 @@ var SkillManager = class {
13141
13538
  // src/web/tool-executor-web.ts
13142
13539
  import { randomUUID as randomUUID2 } from "crypto";
13143
13540
  import { tmpdir as tmpdir2 } from "os";
13144
- import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
13541
+ import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
13145
13542
  var ToolExecutorWeb = class _ToolExecutorWeb {
13146
13543
  constructor(registry, ws) {
13147
13544
  this.registry = registry;
@@ -13281,9 +13678,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13281
13678
  getDiffPreview(call) {
13282
13679
  if (call.name === "write_file") {
13283
13680
  const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
13284
- if (filePath && existsSync22(filePath)) {
13681
+ if (filePath && existsSync23(filePath)) {
13285
13682
  try {
13286
- const old = readFileSync17(filePath, "utf-8");
13683
+ const old = readFileSync18(filePath, "utf-8");
13287
13684
  return renderDiff(old, append ? old + chunk : chunk, { filePath });
13288
13685
  } catch {
13289
13686
  }
@@ -13596,16 +13993,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13596
13993
  };
13597
13994
 
13598
13995
  // src/repl/dev-state.ts
13599
- import { existsSync as existsSync23, readFileSync as readFileSync18, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
13600
- import { join as join17 } from "path";
13996
+ import { existsSync as existsSync24, readFileSync as readFileSync19, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
13997
+ import { join as join18 } from "path";
13601
13998
  import { homedir as homedir8 } from "os";
13602
13999
  function getDevStatePath() {
13603
- return join17(homedir8(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
14000
+ return join18(homedir8(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
13604
14001
  }
13605
14002
  function loadDevState() {
13606
14003
  const path3 = getDevStatePath();
13607
- if (!existsSync23(path3)) return null;
13608
- const content = readFileSync18(path3, "utf-8").trim();
14004
+ if (!existsSync24(path3)) return null;
14005
+ const content = readFileSync19(path3, "utf-8").trim();
13609
14006
  return content || null;
13610
14007
  }
13611
14008
 
@@ -13666,148 +14063,6 @@ function loadMemoryContent(configDir, maxChars) {
13666
14063
  return formatMemoryForPrompt(configDir, process.cwd(), maxChars);
13667
14064
  }
13668
14065
 
13669
- // src/core/pricing.ts
13670
- var DEEPSEEK_CNY_PER_USD = 6.9;
13671
- var DEEPSEEK_TIME_PRICING_EFFECTIVE_AT = /* @__PURE__ */ new Date("2026-08-16T16:00:00.000Z");
13672
- function deepSeekCnyPricing(cacheRead, input, output) {
13673
- return {
13674
- cacheRead: cacheRead / DEEPSEEK_CNY_PER_USD,
13675
- input: input / DEEPSEEK_CNY_PER_USD,
13676
- output: output / DEEPSEEK_CNY_PER_USD
13677
- };
13678
- }
13679
- function getDeepSeekV4Pricing(model, at) {
13680
- const isPro = model.startsWith("deepseek-v4-pro");
13681
- const isFlash = model.startsWith("deepseek-v4-flash");
13682
- if (!isPro && !isFlash) return null;
13683
- if (at < DEEPSEEK_TIME_PRICING_EFFECTIVE_AT) {
13684
- return isPro ? deepSeekCnyPricing(0.025, 3, 6) : deepSeekCnyPricing(0.02, 1, 2);
13685
- }
13686
- const beijingHour = (at.getUTCHours() + 8) % 24;
13687
- const isPeak = beijingHour >= 9 && beijingHour < 12 || beijingHour >= 14 && beijingHour < 18;
13688
- if (isPro) {
13689
- return isPeak ? deepSeekCnyPricing(0.3, 9, 27) : deepSeekCnyPricing(0.15, 4.5, 13.5);
13690
- }
13691
- return isPeak ? deepSeekCnyPricing(0.1, 3, 9) : deepSeekCnyPricing(0.05, 1.5, 4.5);
13692
- }
13693
- var PRICING_TABLE = {
13694
- // ── Anthropic Claude ──────────────────────────────────────────
13695
- "claude-opus-4-8": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
13696
- "claude-opus-4-7": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
13697
- "claude-opus-4-6": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
13698
- "claude-opus-4-5": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
13699
- "claude-sonnet-4-6": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
13700
- "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
13701
- "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
13702
- "claude-haiku-4-5": { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
13703
- // Legacy Claude 3.x families (prefix fallback handles minor date suffixes)
13704
- "claude-3-5-sonnet": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
13705
- "claude-3-5-haiku": { input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },
13706
- "claude-3-opus": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
13707
- // ── OpenAI ────────────────────────────────────────────────────
13708
- // GAP (v0.4.184): the GPT-5 family (gpt-5, gpt-5-mini/nano, gpt-5.4, gpt-5.4-pro)
13709
- // and o4-mini are in the provider's model list but have NO verified pricing yet —
13710
- // left unpriced on purpose so `aicli usage` shows "—" (unknown) rather than a
13711
- // deceptive $0.00. Add real rates when confirmed from the official pricing page.
13712
- "gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25 },
13713
- "gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075 },
13714
- "gpt-4-turbo": { input: 10, output: 30 },
13715
- "gpt-4": { input: 30, output: 60 },
13716
- "gpt-4.1": { input: 2, output: 8, cacheRead: 0.5 },
13717
- "gpt-4.1-mini": { input: 0.4, output: 1.6, cacheRead: 0.1 },
13718
- "gpt-4.1-nano": { input: 0.1, output: 0.4, cacheRead: 0.025 },
13719
- "o1": { input: 15, output: 60, cacheRead: 7.5 },
13720
- "o1-mini": { input: 3, output: 12, cacheRead: 1.5 },
13721
- "o3": { input: 10, output: 40, cacheRead: 2.5 },
13722
- "o3-mini": { input: 1.1, output: 4.4, cacheRead: 0.55 },
13723
- // ── Google Gemini ─────────────────────────────────────────────
13724
- "gemini-2.5-pro": { input: 1.25, output: 10 },
13725
- "gemini-2.5-flash": { input: 0.3, output: 2.5 },
13726
- "gemini-2.0-flash": { input: 0.1, output: 0.4 },
13727
- "gemini-1.5-pro": { input: 1.25, output: 5 },
13728
- "gemini-1.5-flash": { input: 0.075, output: 0.3 },
13729
- // ── DeepSeek ──────────────────────────────────────────────────
13730
- // V4 family (2026-04-23+):1M context, Thinking / Non-Thinking dual-mode, 384K max output
13731
- // 费率 2026-08-01 对 api-docs.deepseek.com/quick_start/pricing 官方 USD 表逐项核对。
13732
- // 修正前 pro 三项整齐高 4×(官方降过价未跟)、flash cacheRead 高 10×(像是按 input 的
13733
- // 20% 拍脑袋而非抄文档)——`aicli usage` 因此系统性高报 DeepSeek 成本。
13734
- // 待办:官方已公告峰谷定价(北京时间 9-12 点、14-18 点为平时 2 倍),本表仍是单一费率,
13735
- // 届时高峰时段会低报;等该策略实际生效后再决定是否引入时段维度。
13736
- // Legacy aliases:retires 2026-07-24 UTC 15:59,官方 route 到 V4 Flash。
13737
- // 这几条只服务历史 session 的成本回算,保留当时的费率口径,不随现价改动。
13738
- "deepseek-chat": { input: 0.14, output: 0.28, cacheRead: 0.028 },
13739
- "deepseek-reasoner": { input: 0.14, output: 0.28, cacheRead: 0.028 },
13740
- "deepseek-v3": { input: 0.27, output: 1.1, cacheRead: 0.07 },
13741
- // ── Moonshot Kimi ─────────────────────────────────────────────
13742
- "moonshot-v1-8k": { input: 0.17, output: 0.17 },
13743
- "moonshot-v1-32k": { input: 0.33, output: 0.33 },
13744
- "moonshot-v1-128k": { input: 0.83, output: 0.83 },
13745
- "kimi-k2": { input: 0.6, output: 2.5 },
13746
- "kimi-latest": { input: 0.6, output: 2.5 },
13747
- // Kimi K3(2026-07 旗舰,1M 上下文)官方定价 platform.kimi.ai/pricing/chat-k3:
13748
- // 输入 cache-miss $3.00 / cache-hit $0.30,输出 $15.00(/MTok)。
13749
- "kimi-k3": { input: 3, output: 15, cacheRead: 0.3 },
13750
- // ── Zhipu GLM ─────────────────────────────────────────────────
13751
- "glm-4-plus": { input: 0.7, output: 0.7 },
13752
- "glm-4": { input: 0.14, output: 0.14 },
13753
- "glm-4-flash": { input: 0, output: 0 },
13754
- "glm-4.5": { input: 0.29, output: 1.14 },
13755
- "glm-4.6": { input: 0.6, output: 2.2 },
13756
- "glm-4.6v": { input: 0.6, output: 2.2 },
13757
- "glm-5": { input: 0.85, output: 2.85 },
13758
- // GLM-5.2(2026-06)官方直连 API 定价 $1.40 / $4.40。
13759
- "glm-5.2": { input: 1.4, output: 4.4 },
13760
- // GLM-5V-Turbo(2026-04 视觉编程旗舰)官方直连 API 定价 $1.20 / $4.00。
13761
- "glm-5v-turbo": { input: 1.2, output: 4 },
13762
- "glm-5.1": { input: 0.95, output: 3.15 },
13763
- "glm-5.1-reasoning": { input: 1.4, output: 4.4 },
13764
- "glm-5.1-air": { input: 0.4, output: 1.2 },
13765
- "glm-z1": { input: 0.5, output: 1.5 },
13766
- "glm-z1-air": { input: 0.2, output: 0.6 },
13767
- "glm-z1-flash": { input: 0, output: 0 }
13768
- // ── MiniMax (海螺) ────────────────────────────────────────────
13769
- // GAP (v0.4.184): MiniMax-M3 / M2.x are unpriced — no verified rate anchor yet.
13770
- // Unpriced → `aicli usage` shows "—" (unknown), not a false $0.00. Add when confirmed.
13771
- // ── OpenRouter (pass-through — actual cost depends on underlying model) ──
13772
- // Left empty by design: model IDs are "vendor/model" (e.g. anthropic/claude-sonnet-4)
13773
- // and the real price is set by the underlying model + OpenRouter markup, not statically
13774
- // resolvable here. Shows "—" (unknown) in `aicli usage`.
13775
- // ── Ollama (local, zero cost) ─────────────────────────────────
13776
- // Handled via provider check below (FREE_PROVIDERS → genuine $0.00, not "unknown").
13777
- };
13778
- var FREE_PROVIDERS = /* @__PURE__ */ new Set(["ollama"]);
13779
- function getPricing(provider, model, at = /* @__PURE__ */ new Date()) {
13780
- if (FREE_PROVIDERS.has(provider.toLowerCase())) {
13781
- return { input: 0, output: 0 };
13782
- }
13783
- const key = model.toLowerCase();
13784
- if (provider.toLowerCase() === "deepseek") {
13785
- const dynamicPricing = getDeepSeekV4Pricing(key, at);
13786
- if (dynamicPricing) return dynamicPricing;
13787
- }
13788
- if (PRICING_TABLE[key]) return PRICING_TABLE[key];
13789
- const keys = Object.keys(PRICING_TABLE).sort((a, b) => b.length - a.length);
13790
- for (const k of keys) {
13791
- if (key.startsWith(k)) return PRICING_TABLE[k];
13792
- }
13793
- return null;
13794
- }
13795
- function computeCost(provider, model, usage, at = /* @__PURE__ */ new Date()) {
13796
- const p = getPricing(provider, model, at);
13797
- if (!p) return null;
13798
- const input = usage.inputTokens * p.input;
13799
- const output = usage.outputTokens * p.output;
13800
- const cacheWrite = (usage.cacheCreationTokens ?? 0) * (p.cacheWrite ?? p.input);
13801
- const cacheRead = (usage.cacheReadTokens ?? 0) * (p.cacheRead ?? p.input);
13802
- return (input + output + cacheWrite + cacheRead) / 1e6;
13803
- }
13804
- function formatCost(amount) {
13805
- if (amount === 0) return "$0.0000";
13806
- if (amount < 0.01) return `$${amount.toFixed(4)}`;
13807
- if (amount < 1) return `$${amount.toFixed(3)}`;
13808
- return `$${amount.toFixed(2)}`;
13809
- }
13810
-
13811
14066
  // src/core/chat-params.ts
13812
14067
  function thinkingParamsFor(allModelParams, model) {
13813
14068
  const params = allModelParams?.[model];
@@ -13907,8 +14162,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
13907
14162
  }
13908
14163
 
13909
14164
  // src/core/context-files.ts
13910
- import { existsSync as existsSync24, readFileSync as readFileSync19 } from "fs";
13911
- import { join as join18, relative as relative4, resolve as resolve8 } from "path";
14165
+ import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
14166
+ import { join as join19, relative as relative4, resolve as resolve8 } from "path";
13912
14167
  function uniqueNonEmpty(values) {
13913
14168
  const seen = /* @__PURE__ */ new Set();
13914
14169
  const out = [];
@@ -13938,7 +14193,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
13938
14193
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
13939
14194
  const shown = displayPath(filePath, cwd);
13940
14195
  try {
13941
- const raw = readFileSync19(filePath);
14196
+ const raw = readFileSync20(filePath);
13942
14197
  const byteCount = raw.byteLength;
13943
14198
  if (byteCount === 0) {
13944
14199
  return {
@@ -13985,8 +14240,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
13985
14240
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
13986
14241
  const skipped = [];
13987
14242
  for (const candidate of candidates) {
13988
- const filePath = join18(dir, candidate);
13989
- if (!existsSync24(filePath)) continue;
14243
+ const filePath = join19(dir, candidate);
14244
+ if (!existsSync25(filePath)) continue;
13990
14245
  const result = readContextFile(level, filePath, cwd, maxBytes);
13991
14246
  if (result.skipped) skipped.push(result.skipped);
13992
14247
  if (result.layer) return { layer: result.layer, skipped };
@@ -14013,7 +14268,7 @@ function loadContextFiles(options) {
14013
14268
  });
14014
14269
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
14015
14270
  }
14016
- if (!existsSync24(filePath)) {
14271
+ if (!existsSync25(filePath)) {
14017
14272
  skipped.push({
14018
14273
  level: "single",
14019
14274
  filePath,
@@ -14068,8 +14323,8 @@ import { mkdirSync as mkdirSync14, createWriteStream as createWriteStream2 } fro
14068
14323
  import { dirname as dirname9 } from "path";
14069
14324
 
14070
14325
  // src/repl/commands/project-init.ts
14071
- import { existsSync as existsSync25, readFileSync as readFileSync20, readdirSync as readdirSync11, statSync as statSync10 } from "fs";
14072
- import { join as join19 } from "path";
14326
+ import { existsSync as existsSync26, readFileSync as readFileSync21, readdirSync as readdirSync11, statSync as statSync10 } from "fs";
14327
+ import { join as join20 } from "path";
14073
14328
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
14074
14329
  "node_modules",
14075
14330
  ".git",
@@ -14113,11 +14368,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
14113
14368
  const sorted = filtered.sort((a, b) => {
14114
14369
  let aIsDir = false, bIsDir = false;
14115
14370
  try {
14116
- aIsDir = statSync10(join19(d, a)).isDirectory();
14371
+ aIsDir = statSync10(join20(d, a)).isDirectory();
14117
14372
  } catch {
14118
14373
  }
14119
14374
  try {
14120
- bIsDir = statSync10(join19(d, b)).isDirectory();
14375
+ bIsDir = statSync10(join20(d, b)).isDirectory();
14121
14376
  } catch {
14122
14377
  }
14123
14378
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -14125,7 +14380,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
14125
14380
  });
14126
14381
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
14127
14382
  const name = sorted[i];
14128
- const fullPath = join19(d, name);
14383
+ const fullPath = join20(d, name);
14129
14384
  const isLast = i === sorted.length - 1;
14130
14385
  const connector = isLast ? "+-- " : "|-- ";
14131
14386
  let isDir;
@@ -14152,7 +14407,7 @@ function scanProject(cwd) {
14152
14407
  configFiles: [],
14153
14408
  directoryStructure: ""
14154
14409
  };
14155
- const check = (file) => existsSync25(join19(cwd, file));
14410
+ const check = (file) => existsSync26(join20(cwd, file));
14156
14411
  const configCandidates = [
14157
14412
  "package.json",
14158
14413
  "tsconfig.json",
@@ -14179,7 +14434,7 @@ function scanProject(cwd) {
14179
14434
  info.type = "node";
14180
14435
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
14181
14436
  try {
14182
- const pkg = JSON.parse(readFileSync20(join19(cwd, "package.json"), "utf-8"));
14437
+ const pkg = JSON.parse(readFileSync21(join20(cwd, "package.json"), "utf-8"));
14183
14438
  const scripts = pkg.scripts ?? {};
14184
14439
  info.buildCommand = scripts.build ? "npm run build" : void 0;
14185
14440
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -14840,7 +15095,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
14840
15095
  }
14841
15096
 
14842
15097
  // src/hub/persist.ts
14843
- import { join as join20 } from "path";
15098
+ import { join as join21 } from "path";
14844
15099
  function discussionToMessages(state2) {
14845
15100
  const out = [];
14846
15101
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -14878,7 +15133,69 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
14878
15133
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
14879
15134
  session.titleAiGenerated = true;
14880
15135
  await sm.save();
14881
- return { id: session.id, path: join20(config.getHistoryDir(), `${session.id}.json`) };
15136
+ return { id: session.id, path: join21(config.getHistoryDir(), `${session.id}.json`) };
15137
+ }
15138
+
15139
+ // src/core/think-command.ts
15140
+ var EFFORTS = /* @__PURE__ */ new Set(["low", "high", "max"]);
15141
+ function resolveEffective(state2) {
15142
+ if (state2.runtimeThinking !== null) return { effective: state2.runtimeThinking, origin: "runtime override" };
15143
+ if (state2.configuredThinking !== void 0) return { effective: state2.configuredThinking, origin: "config" };
15144
+ return { effective: false, origin: "default" };
15145
+ }
15146
+ function effortLine(state2, effort) {
15147
+ if (!state2.supportsEffort) return `Reasoning effort: n/a for ${state2.provider}`;
15148
+ const resolved = effort ?? state2.runtimeEffort ?? state2.configuredEffort;
15149
+ return `Reasoning effort: ${resolved ?? "high (provider default)"}`;
15150
+ }
15151
+ function applyThinkCommand(sub, state2) {
15152
+ const key = (sub ?? "toggle").toLowerCase();
15153
+ const current = resolveEffective(state2);
15154
+ const stay = (summary, details = []) => ({
15155
+ runtimeThinking: state2.runtimeThinking,
15156
+ runtimeEffort: state2.runtimeEffort,
15157
+ effective: current.effective,
15158
+ origin: current.origin,
15159
+ summary,
15160
+ details,
15161
+ changed: false
15162
+ });
15163
+ const enable = (effort) => ({
15164
+ runtimeThinking: true,
15165
+ runtimeEffort: effort ?? state2.runtimeEffort,
15166
+ effective: true,
15167
+ origin: "runtime override",
15168
+ summary: effort && state2.supportsEffort ? `Extended Thinking: ON (effort: ${effort})` : "Extended Thinking: ON",
15169
+ details: effort && !state2.supportsEffort ? [`Note: ${state2.provider} has no reasoning-effort levels \u2014 the level is ignored.`] : [],
15170
+ changed: true
15171
+ });
15172
+ const disable = () => ({
15173
+ runtimeThinking: false,
15174
+ // 刻意保留档位:重新开启时不该丢掉用户选过的档
15175
+ runtimeEffort: state2.runtimeEffort,
15176
+ effective: false,
15177
+ origin: "runtime override",
15178
+ summary: "Extended Thinking: OFF",
15179
+ details: [],
15180
+ changed: true
15181
+ });
15182
+ if (key === "status") {
15183
+ const details = current.effective ? [effortLine(state2, null)] : [
15184
+ "Enable with /think on | low | high | max, or set",
15185
+ "modelParams.<model>.thinking in ~/.aicli/config.json"
15186
+ ];
15187
+ return stay(
15188
+ `Extended Thinking: ${current.effective ? "ON" : "OFF"} (${current.origin})`,
15189
+ details
15190
+ );
15191
+ }
15192
+ if (key === "on") return enable(null);
15193
+ if (key === "off") return disable();
15194
+ if (EFFORTS.has(key)) return enable(key);
15195
+ if (key === "toggle") {
15196
+ return current.effective ? disable() : enable(null);
15197
+ }
15198
+ return stay(`Unknown /think option "${sub}" \u2014 expected on | off | low | high | max | status`);
14882
15199
  }
14883
15200
 
14884
15201
  // src/web/commands/provider-commands.ts
@@ -14943,30 +15260,24 @@ async function handleModel(args, ctx) {
14943
15260
  ctx.sendStatus();
14944
15261
  }
14945
15262
  async function handleThink(args, ctx) {
14946
- const sub = args[0]?.toLowerCase() ?? "toggle";
14947
- if (sub === "status") {
14948
- const configured = ctx.config.get("modelParams")[ctx.currentModel]?.thinking;
14949
- const effective = ctx.runtimeThinking ?? configured ?? false;
14950
- const origin = ctx.runtimeThinking !== null ? "runtime" : configured !== void 0 ? "config" : "default";
14951
- const supportsEffort = ctx.providers.get(ctx.currentProvider)?.supportsReasoningEffort ?? false;
14952
- const effortPart = !effective ? "" : supportsEffort ? `; effort: ${ctx.runtimeThinkingEffort ?? ctx.config.get("modelParams")[ctx.currentModel]?.thinkingEffort ?? "high (provider default)"}` : `; effort: n/a for ${ctx.currentProvider}`;
14953
- ctx.send({
14954
- type: "info",
14955
- message: `Thinking mode: ${effective ? "ON" : "OFF"} (${origin})${effortPart}`
14956
- });
14957
- return;
15263
+ const modelParams = ctx.config.get("modelParams")[ctx.currentModel];
15264
+ const outcome = applyThinkCommand(args[0], {
15265
+ runtimeThinking: ctx.runtimeThinking,
15266
+ runtimeEffort: ctx.runtimeThinkingEffort,
15267
+ configuredThinking: modelParams?.thinking,
15268
+ configuredEffort: modelParams?.thinkingEffort,
15269
+ supportsEffort: ctx.providers.get(ctx.currentProvider)?.supportsReasoningEffort ?? false,
15270
+ provider: ctx.currentProvider
15271
+ });
15272
+ if (outcome.changed) {
15273
+ ctx.runtimeThinking = outcome.runtimeThinking;
15274
+ ctx.runtimeThinkingEffort = outcome.runtimeEffort;
14958
15275
  }
14959
- if (sub === "on") ctx.runtimeThinking = true;
14960
- else if (sub === "off") ctx.runtimeThinking = false;
14961
- else if (sub === "low" || sub === "high" || sub === "max") {
14962
- ctx.runtimeThinking = true;
14963
- ctx.runtimeThinkingEffort = sub;
14964
- } else ctx.runtimeThinking = ctx.runtimeThinking !== true;
14965
15276
  ctx.send({
14966
15277
  type: "info",
14967
- message: `Thinking mode: ${ctx.runtimeThinking ? "ON" : "OFF"}${ctx.runtimeThinking ? ` (${ctx.runtimeThinkingEffort ?? "high"})` : ""}`
15278
+ message: [outcome.summary, ...outcome.details].join(" \u2014 ")
14968
15279
  });
14969
- ctx.sendStatus();
15280
+ if (outcome.changed) ctx.sendStatus();
14970
15281
  }
14971
15282
  async function handlePlan(args, ctx) {
14972
15283
  const sub = args[0];
@@ -14983,9 +15294,44 @@ async function handlePlan(args, ctx) {
14983
15294
  ctx.sendStatus();
14984
15295
  }
14985
15296
 
15297
+ // src/core/session-cost.ts
15298
+ function buildSessionCostReport(input) {
15299
+ const { usage } = input;
15300
+ const totalTokens = usage.inputTokens + usage.outputTokens + usage.cacheCreationTokens + usage.cacheReadTokens;
15301
+ if (totalTokens === 0) {
15302
+ return { hasUsage: false, totalTokens: 0, cacheHitPct: null, cost: null, costSource: "unknown", savings: null };
15303
+ }
15304
+ const liveCost = computeCost(input.provider, input.model, usage);
15305
+ const useAccrual = input.accruedUsd > 0;
15306
+ const cost = useAccrual ? input.accruedUsd : liveCost;
15307
+ const costSource = useAccrual ? input.accrualIncomplete ? "accrued-partial" : "accrued" : liveCost != null ? "estimated" : "unknown";
15308
+ let savings = null;
15309
+ if (liveCost != null && usage.cacheReadTokens > 0) {
15310
+ const withoutCache = computeCost(input.provider, input.model, {
15311
+ inputTokens: usage.inputTokens + usage.cacheReadTokens,
15312
+ outputTokens: usage.outputTokens,
15313
+ cacheCreationTokens: usage.cacheCreationTokens,
15314
+ cacheReadTokens: 0
15315
+ });
15316
+ if (withoutCache != null) savings = withoutCache - liveCost;
15317
+ }
15318
+ const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
15319
+ return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
15320
+ }
15321
+ function costSourceNote(source) {
15322
+ switch (source) {
15323
+ case "accrued-partial":
15324
+ return "(partial \u2014 some turns had unknown pricing)";
15325
+ case "estimated":
15326
+ return "(estimated at current rates)";
15327
+ default:
15328
+ return null;
15329
+ }
15330
+ }
15331
+
14986
15332
  // src/web/commands/_shared.ts
14987
- import { existsSync as existsSync28, readFileSync as readFileSync22, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync13, createWriteStream } from "fs";
14988
- import { join as join23, resolve as resolve9, dirname as dirname8 } from "path";
15333
+ import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync13, createWriteStream } from "fs";
15334
+ import { join as join24, resolve as resolve9, dirname as dirname8 } from "path";
14989
15335
 
14990
15336
  // src/cli/review-prompts.ts
14991
15337
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -15057,34 +15403,34 @@ function readGitDiff(options = {}) {
15057
15403
  }
15058
15404
 
15059
15405
  // src/diagnostics/doctor-report.ts
15060
- import { existsSync as existsSync27, statSync as statSync12 } from "fs";
15061
- import { join as join22 } from "path";
15406
+ import { existsSync as existsSync28, statSync as statSync12 } from "fs";
15407
+ import { join as join23 } from "path";
15062
15408
  import { arch as arch2, platform as platform7, release as release2 } from "os";
15063
15409
 
15064
15410
  // src/diagnostics/crash-log.ts
15065
15411
  import {
15066
- existsSync as existsSync26,
15412
+ existsSync as existsSync27,
15067
15413
  mkdirSync as mkdirSync12,
15068
15414
  readdirSync as readdirSync12,
15069
- readFileSync as readFileSync21,
15415
+ readFileSync as readFileSync22,
15070
15416
  statSync as statSync11,
15071
15417
  unlinkSync as unlinkSync6,
15072
15418
  writeFileSync as writeFileSync2
15073
15419
  } from "fs";
15074
- import { join as join21 } from "path";
15420
+ import { join as join22 } from "path";
15075
15421
  import { homedir as homedir9, platform as platform6, release, arch } from "os";
15076
15422
  var LOGS_DIR_NAME = "logs";
15077
15423
  var CRASH_LOG_PREFIX = "crash-";
15078
15424
  function getLogsDir(configDir) {
15079
- const base = configDir ?? join21(homedir9(), CONFIG_DIR_NAME);
15080
- return join21(base, LOGS_DIR_NAME);
15425
+ const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
15426
+ return join22(base, LOGS_DIR_NAME);
15081
15427
  }
15082
15428
  function truncate(s, n) {
15083
15429
  return s.length <= n ? s : s.slice(0, n) + "\u2026";
15084
15430
  }
15085
15431
  function listRecentCrashes(limit = 10, configDir) {
15086
15432
  const dir = getLogsDir(configDir);
15087
- if (!existsSync26(dir)) return [];
15433
+ if (!existsSync27(dir)) return [];
15088
15434
  const files = [];
15089
15435
  let names;
15090
15436
  try {
@@ -15094,10 +15440,10 @@ function listRecentCrashes(limit = 10, configDir) {
15094
15440
  }
15095
15441
  for (const name of names) {
15096
15442
  if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
15097
- const full = join21(dir, name);
15443
+ const full = join22(dir, name);
15098
15444
  try {
15099
15445
  const st = statSync11(full);
15100
- const head = readFileSync21(full, "utf-8").split("\n").slice(0, 20);
15446
+ const head = readFileSync22(full, "utf-8").split("\n").slice(0, 20);
15101
15447
  const tsLine = head.find((l) => l.startsWith("timestamp:"));
15102
15448
  const kindLine = head.find((l) => l.startsWith("kind:"));
15103
15449
  const msgIdx = head.findIndex((l) => l.trim() === "## message");
@@ -15116,8 +15462,8 @@ function listRecentCrashes(limit = 10, configDir) {
15116
15462
  return files.slice(0, limit);
15117
15463
  }
15118
15464
  function getConfigDirUsage(configDir) {
15119
- const base = configDir ?? join21(homedir9(), CONFIG_DIR_NAME);
15120
- if (!existsSync26(base)) return { totalBytes: 0, entries: [] };
15465
+ const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
15466
+ if (!existsSync27(base)) return { totalBytes: 0, entries: [] };
15121
15467
  const entries = [];
15122
15468
  let total = 0;
15123
15469
  let names;
@@ -15127,7 +15473,7 @@ function getConfigDirUsage(configDir) {
15127
15473
  return { totalBytes: 0, entries: [] };
15128
15474
  }
15129
15475
  for (const name of names) {
15130
- const full = join21(base, name);
15476
+ const full = join22(base, name);
15131
15477
  const bytes = dirSize(full);
15132
15478
  entries.push({ name, bytes });
15133
15479
  total += bytes;
@@ -15142,7 +15488,7 @@ function dirSize(path3) {
15142
15488
  if (!st.isDirectory()) return 0;
15143
15489
  let sum = 0;
15144
15490
  for (const name of readdirSync12(path3)) {
15145
- sum += dirSize(join21(path3, name));
15491
+ sum += dirSize(join22(path3, name));
15146
15492
  }
15147
15493
  return sum;
15148
15494
  } catch {
@@ -15152,7 +15498,7 @@ function dirSize(path3) {
15152
15498
 
15153
15499
  // src/diagnostics/doctor-report.ts
15154
15500
  function checkFile(label, path3) {
15155
- const exists = existsSync27(path3);
15501
+ const exists = existsSync28(path3);
15156
15502
  let sizeBytes = null;
15157
15503
  if (exists) {
15158
15504
  try {
@@ -15195,12 +15541,12 @@ function buildDoctorReport(options) {
15195
15541
  configDir,
15196
15542
  providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
15197
15543
  files: [
15198
- checkFile("config.json", join22(configDir, "config.json")),
15199
- checkFile("memory.md", join22(configDir, MEMORY_FILE_NAME)),
15200
- checkFile("memory.jsonl", join22(configDir, "memory.jsonl")),
15201
- checkFile("dev-state.md", join22(configDir, DEV_STATE_FILE_NAME)),
15202
- checkFile("hooks-trust.json", join22(configDir, "hooks-trust.json")),
15203
- checkFile("plugin-state.json", join22(configDir, "plugins", "plugin-state.json"))
15544
+ checkFile("config.json", join23(configDir, "config.json")),
15545
+ checkFile("memory.md", join23(configDir, MEMORY_FILE_NAME)),
15546
+ checkFile("memory.jsonl", join23(configDir, "memory.jsonl")),
15547
+ checkFile("dev-state.md", join23(configDir, DEV_STATE_FILE_NAME)),
15548
+ checkFile("hooks-trust.json", join23(configDir, "hooks-trust.json")),
15549
+ checkFile("plugin-state.json", join23(configDir, "plugins", "plugin-state.json"))
15204
15550
  ],
15205
15551
  context: {
15206
15552
  enabled: contextSetting !== false,
@@ -15235,7 +15581,7 @@ function buildDoctorReport(options) {
15235
15581
  disabled: hooks.filter((h) => h.disabled).length
15236
15582
  },
15237
15583
  plugins: {
15238
- root: join22(configDir, "plugins"),
15584
+ root: join23(configDir, "plugins"),
15239
15585
  installed: plugins.length,
15240
15586
  active: activeAssets.plugins.length,
15241
15587
  invalid: plugins.filter((p) => !p.valid).length,
@@ -15250,7 +15596,7 @@ function buildDoctorReport(options) {
15250
15596
  mcp: {
15251
15597
  enabled: config.get("mcpEnabled"),
15252
15598
  configured: Object.keys(mcpServers).length,
15253
- projectConfigExists: existsSync27(join22(projectRoot, MCP_PROJECT_CONFIG_NAME)),
15599
+ projectConfigExists: existsSync28(join23(projectRoot, MCP_PROJECT_CONFIG_NAME)),
15254
15600
  statuses: options.mcpStatuses ?? []
15255
15601
  },
15256
15602
  recentCrashes: listRecentCrashes(5, configDir),
@@ -15478,7 +15824,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
15478
15824
  Cost: ${formatCost(cost)}` : "";
15479
15825
  let memoryLine = "";
15480
15826
  try {
15481
- const { countPendingMemories } = await import("./persistent-memory-WTZO5CQ7.js");
15827
+ const { countPendingMemories } = await import("./persistent-memory-3FTOC7QT.js");
15482
15828
  const pending = countPendingMemories(ctx.config.getConfigDir());
15483
15829
  if (pending > 0) memoryLine = `
15484
15830
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -15686,18 +16032,16 @@ async function handleExport(args, ctx) {
15686
16032
  }
15687
16033
  async function handleCost(_args, ctx) {
15688
16034
  const u = ctx.sessionTokenUsage;
15689
- const total = u.inputTokens + u.outputTokens + u.cacheCreationTokens + u.cacheReadTokens;
15690
- const cost = computeCost(ctx.currentProvider, ctx.currentModel, u);
15691
- let savings = null;
15692
- if (cost != null && u.cacheReadTokens > 0) {
15693
- const withoutCache = computeCost(ctx.currentProvider, ctx.currentModel, {
15694
- inputTokens: u.inputTokens + u.cacheReadTokens,
15695
- outputTokens: u.outputTokens,
15696
- cacheCreationTokens: u.cacheCreationTokens,
15697
- cacheReadTokens: 0
15698
- });
15699
- if (withoutCache != null) savings = withoutCache - cost;
15700
- }
16035
+ const report = buildSessionCostReport({
16036
+ provider: ctx.currentProvider,
16037
+ model: ctx.currentModel,
16038
+ usage: u,
16039
+ accruedUsd: ctx.sessionAccruedCostUsd,
16040
+ accrualIncomplete: ctx.sessionCostIncomplete
16041
+ });
16042
+ const total = report.totalTokens;
16043
+ const cost = report.cost;
16044
+ const savings = report.savings;
15701
16045
  const lines = [
15702
16046
  "\u{1F4B0} Session Cost & Token Usage",
15703
16047
  ` Provider : ${ctx.currentProvider}`,
@@ -15706,13 +16050,13 @@ async function handleCost(_args, ctx) {
15706
16050
  ` Output : ${u.outputTokens.toLocaleString()}`
15707
16051
  ];
15708
16052
  if (u.cacheCreationTokens > 0) lines.push(` Cache write : ${u.cacheCreationTokens.toLocaleString()}`);
15709
- if (u.cacheReadTokens > 0) {
15710
- const pct = Math.round(u.cacheReadTokens / (u.cacheReadTokens + u.inputTokens) * 100);
15711
- lines.push(` Cache read : ${u.cacheReadTokens.toLocaleString()} (${pct}% hit rate)`);
16053
+ if (report.cacheHitPct != null) {
16054
+ lines.push(` Cache read : ${u.cacheReadTokens.toLocaleString()} (${report.cacheHitPct}% hit rate)`);
15712
16055
  }
15713
16056
  lines.push(` Total tokens : ${total.toLocaleString()}`);
15714
16057
  if (cost != null) {
15715
- lines.push(` Cost : ${formatCost(cost)}`);
16058
+ const note = costSourceNote(report.costSource);
16059
+ lines.push(` Cost : ${formatCost(cost)}${note ? ` ${note}` : ""}`);
15716
16060
  if (savings != null && savings > 0) {
15717
16061
  lines.push(` Cache savings : -${formatCost(savings)} (vs no cache)`);
15718
16062
  }
@@ -16062,7 +16406,7 @@ async function handlePlugin(args, ctx) {
16062
16406
  lines.push("");
16063
16407
  }
16064
16408
  if (pluginTools.length > 0) {
16065
- const legacyDir = join23(configDir, PLUGINS_DIR_NAME);
16409
+ const legacyDir = join24(configDir, PLUGINS_DIR_NAME);
16066
16410
  lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
16067
16411
  lines.push(`Dir: ${legacyDir}`);
16068
16412
  for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
@@ -16186,8 +16530,8 @@ ${activated.meta.description || ""}` });
16186
16530
  }
16187
16531
  async function handleCommands(_args, ctx) {
16188
16532
  const configDir = ctx.config.getConfigDir();
16189
- const commandsDir = join23(configDir, CUSTOM_COMMANDS_DIR_NAME);
16190
- if (!existsSync28(commandsDir)) {
16533
+ const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
16534
+ if (!existsSync29(commandsDir)) {
16191
16535
  ctx.send({ type: "info", message: `No custom commands directory.
16192
16536
  Create: ${commandsDir}/ with .md files.` });
16193
16537
  return;
@@ -16293,7 +16637,7 @@ async function handleTest(args, ctx) {
16293
16637
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
16294
16638
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
16295
16639
  }
16296
- const runTests = ctx.runTests ?? (await import("./run-tests-FIUGDIDL.js")).executeTests;
16640
+ const runTests = ctx.runTests ?? (await import("./run-tests-6MWE6D5S.js")).executeTests;
16297
16641
  const report = await runTests(testArgs);
16298
16642
  ctx.send({ type: "info", message: report });
16299
16643
  } catch (err) {
@@ -16317,9 +16661,9 @@ async function handleDiff(args, ctx) {
16317
16661
  let modifiedFiles = 0;
16318
16662
  const diffLines2 = [];
16319
16663
  for (const [filePath, { earliest }] of fileMap) {
16320
- const currentContent = existsSync28(filePath) ? (() => {
16664
+ const currentContent = existsSync29(filePath) ? (() => {
16321
16665
  try {
16322
- return readFileSync22(filePath, "utf-8");
16666
+ return readFileSync23(filePath, "utf-8");
16323
16667
  } catch {
16324
16668
  return null;
16325
16669
  }
@@ -16354,9 +16698,9 @@ async function handleDiff(args, ctx) {
16354
16698
  }
16355
16699
  async function handleInit(args, ctx) {
16356
16700
  const cwd = process.cwd();
16357
- const targetPath = join23(cwd, "AICLI.md");
16701
+ const targetPath = join24(cwd, "AICLI.md");
16358
16702
  const force = args.includes("--force");
16359
- if (existsSync28(targetPath) && !force) {
16703
+ if (existsSync29(targetPath) && !force) {
16360
16704
  ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
16361
16705
  Use /init --force to overwrite.` });
16362
16706
  return;
@@ -16551,7 +16895,7 @@ async function handleMemory(args, ctx) {
16551
16895
  ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
16552
16896
  } else if (sub === "export") {
16553
16897
  if (args[1] === "md") {
16554
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-WTZO5CQ7.js");
16898
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-3FTOC7QT.js");
16555
16899
  ctx.send({
16556
16900
  type: "export_data",
16557
16901
  format: "md",
@@ -16589,7 +16933,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
16589
16933
  return;
16590
16934
  }
16591
16935
  const dirPath = resolve9(sub);
16592
- if (!existsSync28(dirPath)) {
16936
+ if (!existsSync29(dirPath)) {
16593
16937
  ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
16594
16938
  return;
16595
16939
  }
@@ -16972,6 +17316,7 @@ var webCommandHandlers = {
16972
17316
  var SessionHandler = class {
16973
17317
  ws;
16974
17318
  config;
17319
+ costTracker;
16975
17320
  providers;
16976
17321
  sessions;
16977
17322
  toolRegistry;
@@ -16985,12 +17330,22 @@ var SessionHandler = class {
16985
17330
  runtimeThinkingEffort = null;
16986
17331
  sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
16987
17332
  unknownPricingWarnings = /* @__PURE__ */ new Set();
17333
+ /**
17334
+ * 本会话按**调用时刻**费率累计的花费(美元)。
17335
+ * 审计 P2-02 第二批:Web 端此前根本不给 CostTracker 记账,aicli usage 与
17336
+ * monthlyBudget 完全看不到 Web UI 的开销;顺带补上 P3-01 的记账口径。
17337
+ */
17338
+ sessionAccruedCostUsd = 0;
17339
+ sessionCostIncomplete = false;
16988
17340
  /** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
16989
17341
  addWebSessionUsage(u) {
16990
17342
  this.sessionTokenUsage.inputTokens += u.inputTokens;
16991
17343
  this.sessionTokenUsage.outputTokens += u.outputTokens;
16992
17344
  this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
16993
17345
  this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
17346
+ const priced = this.costTracker.addCost(this.currentProvider, this.currentModel, u);
17347
+ if (priced === null) this.sessionCostIncomplete = true;
17348
+ else this.sessionAccruedCostUsd += priced;
16994
17349
  const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
16995
17350
  if (totalTokens > 0 && !getPricing(this.currentProvider, this.currentModel)) {
16996
17351
  const key = `${this.currentProvider}/${this.currentModel}`;
@@ -17002,6 +17357,8 @@ var SessionHandler = class {
17002
17357
  }
17003
17358
  resetWebSessionUsage() {
17004
17359
  this.sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
17360
+ this.sessionAccruedCostUsd = 0;
17361
+ this.sessionCostIncomplete = false;
17005
17362
  }
17006
17363
  abortController = null;
17007
17364
  userInterjection = null;
@@ -17035,6 +17392,7 @@ var SessionHandler = class {
17035
17392
  constructor(ws, shared) {
17036
17393
  this.ws = ws;
17037
17394
  this.config = shared.config;
17395
+ this.costTracker = shared.costTracker;
17038
17396
  this.providers = shared.providers;
17039
17397
  this.sessions = shared.sessions;
17040
17398
  this.toolRegistry = shared.toolRegistry;
@@ -18471,8 +18829,8 @@ async function setupProxy(configProxy) {
18471
18829
  }
18472
18830
 
18473
18831
  // src/web/auth.ts
18474
- import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync14, copyFileSync } from "fs";
18475
- import { isAbsolute as isAbsolute3, join as join24, relative as relative5 } from "path";
18832
+ import { existsSync as existsSync30, readFileSync as readFileSync24, writeFileSync as writeFileSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync14, copyFileSync } from "fs";
18833
+ import { isAbsolute as isAbsolute3, join as join25, relative as relative5 } from "path";
18476
18834
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
18477
18835
  var USERS_FILE = "users.json";
18478
18836
  var TOKEN_EXPIRY_HOURS = 24;
@@ -18487,7 +18845,7 @@ var AuthManager = class {
18487
18845
  db;
18488
18846
  constructor(baseDir) {
18489
18847
  this.baseDir = baseDir;
18490
- this.usersFile = join24(baseDir, USERS_FILE);
18848
+ this.usersFile = join25(baseDir, USERS_FILE);
18491
18849
  this.db = this.loadOrCreate();
18492
18850
  }
18493
18851
  // ── Public API ─────────────────────────────────────────────────
@@ -18516,9 +18874,9 @@ var AuthManager = class {
18516
18874
  }
18517
18875
  const salt = randomBytes(16).toString("hex");
18518
18876
  const passwordHash = this.hashPassword(password, salt);
18519
- const dataDir = join24(USERS_DIR, username);
18520
- const fullDataDir = join24(this.baseDir, dataDir);
18521
- mkdirSync15(join24(fullDataDir, "history"), { recursive: true });
18877
+ const dataDir = join25(USERS_DIR, username);
18878
+ const fullDataDir = join25(this.baseDir, dataDir);
18879
+ mkdirSync15(join25(fullDataDir, "history"), { recursive: true });
18522
18880
  const user = {
18523
18881
  username,
18524
18882
  passwordHash,
@@ -18633,7 +18991,7 @@ var AuthManager = class {
18633
18991
  getUserDataDir(username) {
18634
18992
  const user = this.db.users.find((u) => u.username === username);
18635
18993
  if (!user) throw new Error(`User not found: ${username}`);
18636
- return join24(this.baseDir, user.dataDir);
18994
+ return join25(this.baseDir, user.dataDir);
18637
18995
  }
18638
18996
  /** List all usernames */
18639
18997
  listUsers() {
@@ -18669,30 +19027,30 @@ var AuthManager = class {
18669
19027
  const err = this.register(username, password);
18670
19028
  if (err) return err;
18671
19029
  const userDir = this.getUserDataDir(username);
18672
- const globalConfig = join24(this.baseDir, "config.json");
18673
- if (existsSync29(globalConfig)) {
19030
+ const globalConfig = join25(this.baseDir, "config.json");
19031
+ if (existsSync30(globalConfig)) {
18674
19032
  try {
18675
- const content = readFileSync23(globalConfig, "utf-8");
18676
- writeFileSync4(join24(userDir, "config.json"), content, "utf-8");
19033
+ const content = readFileSync24(globalConfig, "utf-8");
19034
+ writeFileSync4(join25(userDir, "config.json"), content, "utf-8");
18677
19035
  } catch {
18678
19036
  }
18679
19037
  }
18680
- const globalMemory = join24(this.baseDir, "memory.md");
18681
- if (existsSync29(globalMemory)) {
19038
+ const globalMemory = join25(this.baseDir, "memory.md");
19039
+ if (existsSync30(globalMemory)) {
18682
19040
  try {
18683
- const content = readFileSync23(globalMemory, "utf-8");
18684
- writeFileSync4(join24(userDir, "memory.md"), content, "utf-8");
19041
+ const content = readFileSync24(globalMemory, "utf-8");
19042
+ writeFileSync4(join25(userDir, "memory.md"), content, "utf-8");
18685
19043
  } catch {
18686
19044
  }
18687
19045
  }
18688
- const globalHistory = join24(this.baseDir, "history");
18689
- if (existsSync29(globalHistory)) {
19046
+ const globalHistory = join25(this.baseDir, "history");
19047
+ if (existsSync30(globalHistory)) {
18690
19048
  try {
18691
19049
  const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
18692
- const userHistory = join24(userDir, "history");
19050
+ const userHistory = join25(userDir, "history");
18693
19051
  for (const f of files) {
18694
19052
  try {
18695
- copyFileSync(join24(globalHistory, f), join24(userHistory, f));
19053
+ copyFileSync(join25(globalHistory, f), join25(userHistory, f));
18696
19054
  } catch {
18697
19055
  }
18698
19056
  }
@@ -18703,9 +19061,9 @@ var AuthManager = class {
18703
19061
  }
18704
19062
  // ── Private methods ────────────────────────────────────────────
18705
19063
  loadOrCreate() {
18706
- if (existsSync29(this.usersFile)) {
19064
+ if (existsSync30(this.usersFile)) {
18707
19065
  try {
18708
- const parsed = JSON.parse(readFileSync23(this.usersFile, "utf-8"));
19066
+ const parsed = JSON.parse(readFileSync24(this.usersFile, "utf-8"));
18709
19067
  if (!this.isValidUsersDB(parsed)) {
18710
19068
  throw new Error("database structure is invalid");
18711
19069
  }
@@ -18739,7 +19097,7 @@ var AuthManager = class {
18739
19097
  if (typeof record.username !== "string" || !/^[a-z0-9_-]{2,32}$/.test(record.username) || usernames.has(record.username) || typeof record.passwordHash !== "string" || record.passwordHash.length === 0 || typeof record.salt !== "string" || record.salt.length === 0 || typeof record.createdAt !== "string" || typeof record.dataDir !== "string" || record.dataDir.length === 0 || isAbsolute3(record.dataDir)) {
18740
19098
  return false;
18741
19099
  }
18742
- const dataDirFromBase = relative5(this.baseDir, join24(this.baseDir, record.dataDir));
19100
+ const dataDirFromBase = relative5(this.baseDir, join25(this.baseDir, record.dataDir));
18743
19101
  if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
18744
19102
  if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
18745
19103
  if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
@@ -18855,8 +19213,8 @@ async function startWebServer(options = {}) {
18855
19213
  }
18856
19214
  }
18857
19215
  let skillManager = null;
18858
- const skillsDir = join25(config.getConfigDir(), SKILLS_DIR_NAME);
18859
- if (existsSync30(skillsDir)) {
19216
+ const skillsDir = join26(config.getConfigDir(), SKILLS_DIR_NAME);
19217
+ if (existsSync31(skillsDir)) {
18860
19218
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
18861
19219
  skillManager.loadSkills(pluginAssets.skillDirs);
18862
19220
  const count = skillManager.listSkills().length;
@@ -18866,6 +19224,7 @@ async function startWebServer(options = {}) {
18866
19224
  }
18867
19225
  const shared = {
18868
19226
  config,
19227
+ costTracker: new CostTracker(config.getConfigDir()),
18869
19228
  providers,
18870
19229
  sessions,
18871
19230
  toolRegistry,
@@ -18931,18 +19290,18 @@ async function startWebServer(options = {}) {
18931
19290
  next();
18932
19291
  };
18933
19292
  const moduleDir = getModuleDir();
18934
- let clientDir = join25(moduleDir, "web", "client");
18935
- if (!existsSync30(clientDir)) {
18936
- clientDir = join25(moduleDir, "client");
19293
+ let clientDir = join26(moduleDir, "web", "client");
19294
+ if (!existsSync31(clientDir)) {
19295
+ clientDir = join26(moduleDir, "client");
18937
19296
  }
18938
- if (!existsSync30(clientDir)) {
18939
- clientDir = join25(moduleDir, "..", "..", "src", "web", "client");
19297
+ if (!existsSync31(clientDir)) {
19298
+ clientDir = join26(moduleDir, "..", "..", "src", "web", "client");
18940
19299
  }
18941
- if (!existsSync30(clientDir)) {
18942
- clientDir = join25(process.cwd(), "src", "web", "client");
19300
+ if (!existsSync31(clientDir)) {
19301
+ clientDir = join26(process.cwd(), "src", "web", "client");
18943
19302
  }
18944
19303
  console.log(` Static files: ${clientDir}`);
18945
- app.use("/vendor", express.static(join25(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
19304
+ app.use("/vendor", express.static(join26(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
18946
19305
  app.use(express.static(clientDir));
18947
19306
  app.get("/api/status", (_req, res) => {
18948
19307
  res.json({
@@ -19009,7 +19368,7 @@ async function startWebServer(options = {}) {
19009
19368
  app.get("/api/files", requireAuth, (req, res) => {
19010
19369
  const cwd = process.cwd();
19011
19370
  const prefix = req.query.prefix || "";
19012
- const targetDir = join25(cwd, prefix);
19371
+ const targetDir = join26(cwd, prefix);
19013
19372
  try {
19014
19373
  const canonicalTarget = realpathSync3(resolve10(targetDir));
19015
19374
  const canonicalCwd = realpathSync3(resolve10(cwd));
@@ -19026,7 +19385,7 @@ async function startWebServer(options = {}) {
19026
19385
  const entries = readdirSync15(targetDir, { withFileTypes: true });
19027
19386
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
19028
19387
  name: e.name,
19029
- path: relative6(cwd, join25(targetDir, e.name)).replace(/\\/g, "/"),
19388
+ path: relative6(cwd, join26(targetDir, e.name)).replace(/\\/g, "/"),
19030
19389
  isDir: e.isDirectory()
19031
19390
  }));
19032
19391
  res.json({ files });
@@ -19066,8 +19425,8 @@ async function startWebServer(options = {}) {
19066
19425
  try {
19067
19426
  const authUser = req._authUser;
19068
19427
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
19069
- const filePath = join25(histDir, `${id}.json`);
19070
- if (!existsSync30(filePath)) {
19428
+ const filePath = join26(histDir, `${id}.json`);
19429
+ if (!existsSync31(filePath)) {
19071
19430
  res.status(404).json({ error: "Session not found" });
19072
19431
  return;
19073
19432
  }
@@ -19082,7 +19441,7 @@ async function startWebServer(options = {}) {
19082
19441
  res.status(404).json({ error: "Session not found" });
19083
19442
  return;
19084
19443
  }
19085
- const data = JSON.parse(readFileSync24(filePath, "utf-8"));
19444
+ const data = JSON.parse(readFileSync25(filePath, "utf-8"));
19086
19445
  res.json({ session: data });
19087
19446
  } catch (err) {
19088
19447
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -19095,7 +19454,7 @@ async function startWebServer(options = {}) {
19095
19454
  return;
19096
19455
  }
19097
19456
  const cwd = process.cwd();
19098
- const fullPath = resolve10(join25(cwd, filePath));
19457
+ const fullPath = resolve10(join26(cwd, filePath));
19099
19458
  try {
19100
19459
  const canonicalFull = realpathSync3(fullPath);
19101
19460
  const canonicalCwd = realpathSync3(resolve10(cwd));
@@ -19113,7 +19472,7 @@ async function startWebServer(options = {}) {
19113
19472
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
19114
19473
  return;
19115
19474
  }
19116
- const content = readFileSync24(fullPath, "utf-8");
19475
+ const content = readFileSync25(fullPath, "utf-8");
19117
19476
  res.json({ content, size: stat.size });
19118
19477
  } catch {
19119
19478
  res.json({ error: "Cannot read file" });
@@ -19132,6 +19491,8 @@ async function startWebServer(options = {}) {
19132
19491
  const userSessions = new SessionManager(userConfig);
19133
19492
  const userShared = {
19134
19493
  config: userConfig,
19494
+ // 每用户各自的 config 目录 → 各自的 cost-history.json
19495
+ costTracker: new CostTracker(userConfig.getConfigDir()),
19135
19496
  providers,
19136
19497
  // Shared — providers re-read API keys per call
19137
19498
  sessions: userSessions,
@@ -19368,17 +19729,17 @@ function resolveProjectMcpPath() {
19368
19729
  const cwd = process.cwd();
19369
19730
  const gitRoot = getGitRoot(cwd);
19370
19731
  const projectRoot = gitRoot ?? cwd;
19371
- const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
19372
- return existsSync30(configPath) ? configPath : null;
19732
+ const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
19733
+ return existsSync31(configPath) ? configPath : null;
19373
19734
  }
19374
19735
  function loadProjectMcpConfig() {
19375
19736
  const cwd = process.cwd();
19376
19737
  const gitRoot = getGitRoot(cwd);
19377
19738
  const projectRoot = gitRoot ?? cwd;
19378
- const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
19379
- if (!existsSync30(configPath)) return null;
19739
+ const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
19740
+ if (!existsSync31(configPath)) return null;
19380
19741
  try {
19381
- const raw = JSON.parse(readFileSync24(configPath, "utf-8"));
19742
+ const raw = JSON.parse(readFileSync25(configPath, "utf-8"));
19382
19743
  return raw.mcpServers ?? raw;
19383
19744
  } catch {
19384
19745
  return null;