jinzd-ai-cli 0.4.218 → 0.4.220

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/README.md +102 -99
  2. package/README.zh-CN.md +40 -38
  3. package/dist/{batch-7V7OTMUP.js → batch-QBSKTLTO.js} +3 -3
  4. package/dist/{chunk-TZQHYZKT.js → chunk-5ULLIOVC.js} +1 -0
  5. package/dist/{chunk-P4VBLXKS.js → chunk-5WBNXYDJ.js} +2 -2
  6. package/dist/chunk-ALQN7VUJ.js +604 -0
  7. package/dist/{chunk-C2Z42DI5.js → chunk-CAUHLHDR.js} +1 -1
  8. package/dist/{chunk-GX3HSGJX.js → chunk-IZW5LMI6.js} +166 -638
  9. package/dist/chunk-P3LKFA54.js +639 -0
  10. package/dist/{chunk-5CA2TJ5F.js → chunk-P4DYAGUI.js} +1 -1
  11. package/dist/{chunk-L4UREAID.js → chunk-P4PTXCHY.js} +6 -5
  12. package/dist/{chunk-QMXC327F.js → chunk-PUBCJF7E.js} +213 -175
  13. package/dist/{chunk-NTCB7CMT.js → chunk-QZYNRCPX.js} +1 -1
  14. package/dist/{chunk-H2UIHGHH.js → chunk-VHX7KZDY.js} +29 -189
  15. package/dist/{chunk-VGFTM3XT.js → chunk-Y25MSSUP.js} +1 -1
  16. package/dist/{ci-L6GH2WVC.js → ci-5SNXGM3M.js} +6 -6
  17. package/dist/{ci-format-WW7454AY.js → ci-format-57QQTHQE.js} +2 -2
  18. package/dist/{constants-NCTFSHDU.js → constants-ML5KFEOH.js} +1 -1
  19. package/dist/doctor-cli-ZRH3XZ3R.js +17 -0
  20. package/dist/electron-server.js +836 -470
  21. package/dist/{hub-CDL6T7CP.js → hub-IPLMJ6TJ.js} +1 -1
  22. package/dist/index.js +125 -194
  23. package/dist/{pr-D6PEKEGK.js → pr-GJYDAPK4.js} +8 -7
  24. package/dist/{run-tests-NXVVKAK2.js → run-tests-GK5AIAV2.js} +1 -1
  25. package/dist/{run-tests-SWU2XEV7.js → run-tests-KLQAI5CX.js} +2 -2
  26. package/dist/{server-WUT7VYTD.js → server-5FZDALHH.js} +10 -10
  27. package/dist/{server-LHYSS6CK.js → server-RU36PRSH.js} +74 -83
  28. package/dist/{task-orchestrator-C5AA2BI5.js → task-orchestrator-JMHPVNUO.js} +10 -10
  29. package/dist/{usage-6ZUUJBI2.js → usage-IRMRCCKN.js} +3 -3
  30. package/package.json +1 -1
  31. package/dist/chunk-MWKE2TNS.js +0 -129
  32. package/dist/chunk-OUC75QCF.js +0 -166
  33. package/dist/doctor-cli-EWMFBP5Q.js +0 -226
@@ -1,27 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-GX3HSGJX.js";
4
+ } from "./chunk-IZW5LMI6.js";
5
+ import "./chunk-K3CF65QH.js";
5
6
  import "./chunk-T2NL5ZIA.js";
6
7
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-VGFTM3XT.js";
8
+ import "./chunk-Y25MSSUP.js";
9
+ import "./chunk-4BKXL7SM.js";
10
+ import "./chunk-TB4W4Y4T.js";
11
+ import "./chunk-KHYD3WXE.js";
12
+ import "./chunk-SLSWPBK3.js";
8
13
  import {
9
14
  runTool
10
- } from "./chunk-MWKE2TNS.js";
15
+ } from "./chunk-ALQN7VUJ.js";
11
16
  import {
12
17
  getDangerLevel,
13
18
  schemaToJsonSchema
14
19
  } from "./chunk-XPBEJB27.js";
15
- import "./chunk-TZQHYZKT.js";
20
+ import "./chunk-5ULLIOVC.js";
16
21
  import {
17
22
  VERSION
18
- } from "./chunk-NTCB7CMT.js";
23
+ } from "./chunk-QZYNRCPX.js";
19
24
  import "./chunk-HOSJZMQS.js";
20
- import "./chunk-4BKXL7SM.js";
21
- import "./chunk-TB4W4Y4T.js";
22
- import "./chunk-KHYD3WXE.js";
23
- import "./chunk-SLSWPBK3.js";
24
- import "./chunk-K3CF65QH.js";
25
25
  import "./chunk-CKH4KQ4E.js";
26
26
  import "./chunk-IW3Q7AE5.js";
27
27
 
@@ -17,14 +17,13 @@ import {
17
17
  SkillManager,
18
18
  autoTrimSessionIfNeeded,
19
19
  buildInitPrompt,
20
- loadContextFiles,
21
20
  loadDevState,
22
21
  persistToolRound,
23
22
  readGitDiff,
24
23
  scanDirTree,
25
24
  scanProject,
26
25
  setupProxy
27
- } from "./chunk-H2UIHGHH.js";
26
+ } from "./chunk-VHX7KZDY.js";
28
27
  import {
29
28
  buildReviewPrompt,
30
29
  buildSecurityReviewPrompt
@@ -40,49 +39,38 @@ import {
40
39
  clearRecentlyDeniedAutoActions,
41
40
  defaultActionClassifier,
42
41
  deleteMemoryEntry,
43
- describePlugin,
44
42
  estimateTokens,
45
43
  expireMemoryEntry,
46
44
  exportMemoryEntries,
47
45
  formatMemoryForPrompt,
48
46
  formatPermissionProfileWarning,
49
- getActivePluginAssets,
50
- getInstalledPlugin,
51
- getPendingHookTrust,
52
47
  getPreferredAgentName,
53
48
  getRecentlyDeniedAutoActions,
54
49
  googleSearchContext,
55
50
  groupCallsByPhase,
56
- installPlugin,
57
51
  isMemoryExpired,
58
52
  listAgentConfigs,
59
53
  listAgentRuns,
60
- listInstalledPlugins,
61
54
  listMemoryEntries,
62
55
  memoryStorePath,
63
- mergePluginHooks,
64
- pluginRoot,
65
56
  recordRecentlyDeniedAutoAction,
66
57
  renderDiff,
67
58
  requestAgentStop,
68
59
  resolveAgentConfig,
69
- runHook,
70
- runLifecycleHooks,
71
60
  runSafePhases,
72
61
  runWithSessionKey,
73
62
  setContextWindow,
74
63
  setMaxOutputCap,
75
- setPluginEnabled,
76
64
  setPreferredAgentName,
77
65
  spawnAgentContext,
78
66
  truncateOutput,
79
- trustPlugin,
80
67
  undoStack,
81
68
  updateMemoryApproval
82
- } from "./chunk-GX3HSGJX.js";
69
+ } from "./chunk-IZW5LMI6.js";
70
+ import "./chunk-K3CF65QH.js";
83
71
  import "./chunk-T2NL5ZIA.js";
84
72
  import "./chunk-BXP6YZ2P.js";
85
- import "./chunk-VGFTM3XT.js";
73
+ import "./chunk-Y25MSSUP.js";
86
74
  import {
87
75
  SessionManager,
88
76
  getContentText
@@ -91,12 +79,35 @@ import {
91
79
  computeCost,
92
80
  formatCost
93
81
  } from "./chunk-E44DTERW.js";
82
+ import "./chunk-4BKXL7SM.js";
83
+ import "./chunk-TB4W4Y4T.js";
84
+ import "./chunk-KHYD3WXE.js";
85
+ import {
86
+ scanString
87
+ } from "./chunk-SLSWPBK3.js";
88
+ import {
89
+ buildDoctorReport,
90
+ formatDoctorReport,
91
+ loadContextFiles
92
+ } from "./chunk-P3LKFA54.js";
94
93
  import {
95
94
  ProviderRegistry
96
- } from "./chunk-QMXC327F.js";
95
+ } from "./chunk-PUBCJF7E.js";
97
96
  import {
98
- runTool
99
- } from "./chunk-MWKE2TNS.js";
97
+ describePlugin,
98
+ getActivePluginAssets,
99
+ getInstalledPlugin,
100
+ getPendingHookTrust,
101
+ installPlugin,
102
+ listInstalledPlugins,
103
+ mergePluginHooks,
104
+ pluginRoot,
105
+ runHook,
106
+ runLifecycleHooks,
107
+ runTool,
108
+ setPluginEnabled,
109
+ trustPlugin
110
+ } from "./chunk-ALQN7VUJ.js";
100
111
  import {
101
112
  CONTENT_ONLY_STREAM_REMINDER,
102
113
  TEE_FINAL_USER_NUDGE,
@@ -115,8 +126,8 @@ import {
115
126
  } from "./chunk-XPBEJB27.js";
116
127
  import {
117
128
  ConfigManager
118
- } from "./chunk-P4VBLXKS.js";
119
- import "./chunk-TZQHYZKT.js";
129
+ } from "./chunk-5WBNXYDJ.js";
130
+ import "./chunk-5ULLIOVC.js";
120
131
  import {
121
132
  AGENTIC_BEHAVIOR_GUIDELINE,
122
133
  AUTHOR,
@@ -133,19 +144,12 @@ import {
133
144
  SKILLS_DIR_NAME,
134
145
  VERSION,
135
146
  buildUserIdentityPrompt
136
- } from "./chunk-NTCB7CMT.js";
147
+ } from "./chunk-QZYNRCPX.js";
137
148
  import {
138
149
  formatGitContextForPrompt,
139
150
  getGitContext,
140
151
  getGitRoot
141
152
  } from "./chunk-HOSJZMQS.js";
142
- import "./chunk-4BKXL7SM.js";
143
- import "./chunk-TB4W4Y4T.js";
144
- import "./chunk-KHYD3WXE.js";
145
- import {
146
- scanString
147
- } from "./chunk-SLSWPBK3.js";
148
- import "./chunk-K3CF65QH.js";
149
153
  import "./chunk-CKH4KQ4E.js";
150
154
  import {
151
155
  atomicWriteFileSync
@@ -1678,13 +1682,39 @@ ${summaryContent}`,
1678
1682
  break;
1679
1683
  }
1680
1684
  case "model": {
1681
- const modelId = args[0];
1682
- if (!modelId) {
1683
- this.send({ type: "error", message: "Usage: /model <id>" });
1685
+ const sub = args[0];
1686
+ if (!sub) {
1687
+ this.send({ type: "error", message: "Usage: /model <id>|refresh [provider]|cache [clear [provider]]" });
1688
+ return;
1689
+ }
1690
+ if (sub === "refresh") {
1691
+ const providerId = args[1] ?? this.currentProvider;
1692
+ try {
1693
+ const models2 = await this.providers.refreshModels(providerId);
1694
+ this.send({ type: "info", message: `Refreshed ${models2.length} model(s) for ${providerId}.` });
1695
+ this.sendStatus();
1696
+ } catch (err) {
1697
+ this.send({ type: "error", message: `Model refresh failed: ${err instanceof Error ? err.message : String(err)}` });
1698
+ }
1699
+ return;
1700
+ }
1701
+ if (sub === "cache") {
1702
+ if ((args[1] ?? "").toLowerCase() === "clear") {
1703
+ const providerId = args[2];
1704
+ this.providers.clearModelCache(providerId);
1705
+ this.send({ type: "info", message: providerId ? `Cleared model cache for ${providerId}.` : "Cleared model cache." });
1706
+ return;
1707
+ }
1708
+ const rows = this.providers.getModelCacheStatus();
1709
+ this.send({
1710
+ type: "info",
1711
+ message: rows.length === 0 ? "Model cache is empty. Use /model refresh to populate it." : rows.map((row) => `${row.providerId}: ${row.count} model(s), fetched ${row.fetchedAt}`).join("\n")
1712
+ });
1684
1713
  return;
1685
1714
  }
1686
- const provider = this.providers.get(this.currentProvider);
1687
- const found = provider?.info.models.find((m) => m.id === modelId);
1715
+ const modelId = sub;
1716
+ const models = await this.providers.listModels(this.currentProvider);
1717
+ const found = models.find((m) => m.id === modelId);
1688
1718
  if (!found) {
1689
1719
  this.send({ type: "error", message: `Model "${modelId}" not found` });
1690
1720
  return;
@@ -2761,7 +2791,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2761
2791
  case "test": {
2762
2792
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2763
2793
  try {
2764
- const { executeTests } = await import("./run-tests-SWU2XEV7.js");
2794
+ const { executeTests } = await import("./run-tests-KLQAI5CX.js");
2765
2795
  const argStr = args.join(" ").trim();
2766
2796
  let testArgs = {};
2767
2797
  if (argStr) {
@@ -2800,53 +2830,13 @@ Use /context reload to load it.` });
2800
2830
  }
2801
2831
  // ── /doctor ─────────────────────────────────────────────────────
2802
2832
  case "doctor": {
2803
- const lines = ["\u{1FA7A} **AI-CLI Health Check**", ""];
2804
- lines.push("**API Keys:**");
2805
- const providersList = this.providers.listAll();
2806
- for (const p of providersList) {
2807
- const icon = p.configured ? "\u2713" : "\u25CB";
2808
- const status = p.configured ? "configured" : "not configured";
2809
- lines.push(` ${icon} ${p.id.padEnd(14)} ${status}`);
2810
- }
2811
- lines.push("");
2812
- const configDir = this.config.getConfigDir();
2813
- lines.push("**Config Files:**");
2814
- lines.push(` Dir: ${configDir}`);
2815
- const checkFile = (label, filePath) => {
2816
- const exists = existsSync2(filePath);
2817
- let extra = "";
2818
- if (exists) {
2819
- try {
2820
- extra = ` (${statSync(filePath).size} bytes)`;
2821
- } catch {
2822
- }
2823
- }
2824
- lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
2825
- };
2826
- checkFile("config.json", join(configDir, "config.json"));
2827
- checkFile("memory.md", join(configDir, MEMORY_FILE_NAME));
2828
- checkFile("dev-state.md", join(configDir, "dev-state.md"));
2829
- lines.push("");
2830
- if (this.mcpManager) {
2831
- lines.push("**MCP Servers:**");
2832
- const statuses = this.mcpManager.getStatus();
2833
- if (statuses.length === 0) {
2834
- lines.push(" (no servers configured)");
2835
- } else {
2836
- for (const s of statuses) {
2837
- const state = s.connected ? `connected \xB7 ${s.serverName} \xB7 ${s.toolCount} tools` : `disconnected${s.error ? ` \xB7 ${s.error}` : ""}`;
2838
- lines.push(` ${s.connected ? "\u2713" : "\u2717"} ${s.serverId.padEnd(16)} ${state}`);
2839
- }
2840
- }
2841
- lines.push("");
2842
- }
2843
- lines.push("**Current Session:**");
2844
- lines.push(` Provider: ${this.currentProvider}`);
2845
- lines.push(` Model: ${this.currentModel}`);
2846
- lines.push(` Version: ${VERSION}`);
2847
- lines.push("");
2848
- lines.push("\u2713 Health check complete");
2849
- this.send({ type: "info", message: lines.join("\n") });
2833
+ const report = buildDoctorReport({
2834
+ config: this.config,
2835
+ providers: this.providers,
2836
+ cwd: process.cwd(),
2837
+ mcpStatuses: this.mcpManager?.getStatus() ?? []
2838
+ });
2839
+ this.send({ type: "info", message: formatDoctorReport(report, false) });
2850
2840
  break;
2851
2841
  }
2852
2842
  // ── /about ──────────────────────────────────────────────────────
@@ -3637,7 +3627,8 @@ async function startWebServer(options = {}) {
3637
3627
  timeout: config.get("timeouts")[id],
3638
3628
  proxy: config.get("proxy")
3639
3629
  }),
3640
- config.get("customProviders")
3630
+ config.get("customProviders"),
3631
+ config.getConfigDir()
3641
3632
  );
3642
3633
  if (options._extraProviders) {
3643
3634
  for (const p of options._extraProviders) {
@@ -3,27 +3,27 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-GX3HSGJX.js";
6
+ } from "./chunk-IZW5LMI6.js";
7
+ import "./chunk-K3CF65QH.js";
7
8
  import "./chunk-T2NL5ZIA.js";
8
9
  import "./chunk-BXP6YZ2P.js";
9
- import "./chunk-VGFTM3XT.js";
10
+ import "./chunk-Y25MSSUP.js";
11
+ import "./chunk-4BKXL7SM.js";
12
+ import "./chunk-TB4W4Y4T.js";
13
+ import "./chunk-KHYD3WXE.js";
14
+ import "./chunk-SLSWPBK3.js";
10
15
  import {
11
16
  runTool
12
- } from "./chunk-MWKE2TNS.js";
17
+ } from "./chunk-ALQN7VUJ.js";
13
18
  import {
14
19
  getDangerLevel,
15
20
  runLeanAgentLoop
16
21
  } from "./chunk-XPBEJB27.js";
17
- import "./chunk-TZQHYZKT.js";
22
+ import "./chunk-5ULLIOVC.js";
18
23
  import {
19
24
  SUBAGENT_ALLOWED_TOOLS
20
- } from "./chunk-NTCB7CMT.js";
25
+ } from "./chunk-QZYNRCPX.js";
21
26
  import "./chunk-HOSJZMQS.js";
22
- import "./chunk-4BKXL7SM.js";
23
- import "./chunk-TB4W4Y4T.js";
24
- import "./chunk-KHYD3WXE.js";
25
- import "./chunk-SLSWPBK3.js";
26
- import "./chunk-K3CF65QH.js";
27
27
  import "./chunk-CKH4KQ4E.js";
28
28
  import {
29
29
  atomicWriteFileSync
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-E44DTERW.js";
9
9
  import {
10
10
  ConfigManager
11
- } from "./chunk-P4VBLXKS.js";
12
- import "./chunk-TZQHYZKT.js";
13
- import "./chunk-NTCB7CMT.js";
11
+ } from "./chunk-5WBNXYDJ.js";
12
+ import "./chunk-5ULLIOVC.js";
13
+ import "./chunk-QZYNRCPX.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
 
16
16
  // src/cli/usage.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.218",
3
+ "version": "0.4.220",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,129 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- CONFIG_DIR_NAME
4
- } from "./chunk-NTCB7CMT.js";
5
- import {
6
- atomicWriteFileSync
7
- } from "./chunk-IW3Q7AE5.js";
8
-
9
- // src/diagnostics/tool-stats.ts
10
- import { existsSync, readFileSync, mkdirSync } from "fs";
11
- import { join, dirname } from "path";
12
- import { homedir } from "os";
13
- var STATS_FILE_NAME = "tool-stats.json";
14
- var STATS_VERSION = 1;
15
- var FLUSH_EVERY_N = 50;
16
- var state = null;
17
- var dirty = false;
18
- var pendingWrites = 0;
19
- var configDirOverride;
20
- function statsFilePath() {
21
- const base = configDirOverride ?? join(homedir(), CONFIG_DIR_NAME);
22
- return join(base, STATS_FILE_NAME);
23
- }
24
- function load() {
25
- const path = statsFilePath();
26
- if (!existsSync(path)) {
27
- return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
28
- }
29
- try {
30
- const raw = JSON.parse(readFileSync(path, "utf-8"));
31
- if (raw.version !== STATS_VERSION || !raw.entries) {
32
- return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
33
- }
34
- return raw;
35
- } catch {
36
- return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
37
- }
38
- }
39
- function ensureLoaded() {
40
- if (state === null) state = load();
41
- return state;
42
- }
43
- function recordToolCall(name, durationMs, ok, errorMessage) {
44
- const s = ensureLoaded();
45
- const now = (/* @__PURE__ */ new Date()).toISOString();
46
- let entry = s.entries[name];
47
- if (!entry) {
48
- entry = {
49
- name,
50
- calls: 0,
51
- failures: 0,
52
- totalDurationMs: 0,
53
- firstSeenAt: now,
54
- lastUsedAt: now
55
- };
56
- s.entries[name] = entry;
57
- }
58
- entry.calls += 1;
59
- entry.totalDurationMs += Math.max(0, Math.round(durationMs));
60
- entry.lastUsedAt = now;
61
- if (!ok) {
62
- entry.failures += 1;
63
- entry.lastFailureAt = now;
64
- if (errorMessage) entry.lastFailureMessage = errorMessage.slice(0, 300);
65
- }
66
- dirty = true;
67
- pendingWrites += 1;
68
- if (pendingWrites >= FLUSH_EVERY_N) {
69
- flush();
70
- }
71
- }
72
- async function runTool(tool, args, name) {
73
- const start = Date.now();
74
- try {
75
- const result = await tool.execute(args);
76
- recordToolCall(name, Date.now() - start, true);
77
- return result;
78
- } catch (err) {
79
- const msg = err instanceof Error ? err.message : String(err);
80
- recordToolCall(name, Date.now() - start, false, msg);
81
- throw err;
82
- }
83
- }
84
- function flush() {
85
- if (!dirty || state === null) return;
86
- const path = statsFilePath();
87
- try {
88
- mkdirSync(dirname(path), { recursive: true });
89
- atomicWriteFileSync(path, JSON.stringify(state, null, 2));
90
- dirty = false;
91
- pendingWrites = 0;
92
- } catch {
93
- }
94
- }
95
- function getStatsSnapshot() {
96
- const s = ensureLoaded();
97
- return Object.values(s.entries).map((e) => ({ ...e }));
98
- }
99
- function getTopFailingTools(limit = 5) {
100
- return getStatsSnapshot().filter((e) => e.failures > 0).sort((a, b) => {
101
- const rateA = a.failures / Math.max(1, a.calls);
102
- const rateB = b.failures / Math.max(1, b.calls);
103
- if (rateB !== rateA) return rateB - rateA;
104
- return b.failures - a.failures;
105
- }).slice(0, limit);
106
- }
107
- function getTopUsedTools(limit = 5) {
108
- return getStatsSnapshot().sort((a, b) => b.calls - a.calls).slice(0, limit);
109
- }
110
- function resetStats() {
111
- state = { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
112
- dirty = true;
113
- flush();
114
- }
115
- var exitHookInstalled = false;
116
- function installFlushOnExit() {
117
- if (exitHookInstalled) return;
118
- exitHookInstalled = true;
119
- process.on("exit", () => flush());
120
- }
121
-
122
- export {
123
- runTool,
124
- getStatsSnapshot,
125
- getTopFailingTools,
126
- getTopUsedTools,
127
- resetStats,
128
- installFlushOnExit
129
- };
@@ -1,166 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- CONFIG_DIR_NAME,
4
- VERSION
5
- } from "./chunk-NTCB7CMT.js";
6
-
7
- // src/diagnostics/crash-log.ts
8
- import {
9
- existsSync,
10
- mkdirSync,
11
- readdirSync,
12
- readFileSync,
13
- statSync,
14
- unlinkSync,
15
- writeFileSync
16
- } from "fs";
17
- import { join } from "path";
18
- import { homedir, platform, release, arch } from "os";
19
- var LOGS_DIR_NAME = "logs";
20
- var CRASH_LOG_PREFIX = "crash-";
21
- var MAX_CRASH_FILES = 20;
22
- function getLogsDir(configDir) {
23
- const base = configDir ?? join(homedir(), CONFIG_DIR_NAME);
24
- return join(base, LOGS_DIR_NAME);
25
- }
26
- function ensureLogsDir(configDir) {
27
- const dir = getLogsDir(configDir);
28
- if (!existsSync(dir)) {
29
- mkdirSync(dir, { recursive: true });
30
- }
31
- return dir;
32
- }
33
- function isoTimestampForFilename(d = /* @__PURE__ */ new Date()) {
34
- return d.toISOString().replace(/[:.]/g, "-");
35
- }
36
- function writeCrashLog(err, context, configDir) {
37
- try {
38
- const dir = ensureLogsDir(configDir);
39
- const ts = /* @__PURE__ */ new Date();
40
- const file = join(dir, `${CRASH_LOG_PREFIX}${isoTimestampForFilename(ts)}.log`);
41
- const errMessage = err instanceof Error ? err.message : String(err);
42
- const errStack = err instanceof Error ? err.stack ?? "(no stack)" : "(non-Error thrown)";
43
- const lines = [
44
- `# ai-cli crash log`,
45
- `timestamp: ${ts.toISOString()}`,
46
- `kind: ${context.kind}`,
47
- `version: ${VERSION}`,
48
- `node: ${process.version}`,
49
- `platform: ${platform()} ${release()} (${arch()})`,
50
- `pid: ${process.pid}`,
51
- context.provider ? `provider: ${context.provider}` : "",
52
- context.model ? `model: ${context.model}` : "",
53
- context.lastAction ? `lastAction: ${truncate(context.lastAction, 500)}` : "",
54
- "",
55
- `## message`,
56
- errMessage,
57
- "",
58
- `## stack`,
59
- errStack,
60
- ""
61
- ].filter(Boolean);
62
- writeFileSync(file, lines.join("\n"), "utf-8");
63
- pruneOldCrashes(configDir);
64
- return file;
65
- } catch {
66
- return null;
67
- }
68
- }
69
- function truncate(s, n) {
70
- return s.length <= n ? s : s.slice(0, n) + "\u2026";
71
- }
72
- function pruneOldCrashes(configDir) {
73
- try {
74
- const dir = getLogsDir(configDir);
75
- if (!existsSync(dir)) return;
76
- const entries = readdirSync(dir).filter((f) => f.startsWith(CRASH_LOG_PREFIX) && f.endsWith(".log")).map((f) => {
77
- const full = join(dir, f);
78
- let mtime = 0;
79
- try {
80
- mtime = statSync(full).mtimeMs;
81
- } catch {
82
- }
83
- return { file: full, mtime };
84
- }).sort((a, b) => b.mtime - a.mtime);
85
- for (const e of entries.slice(MAX_CRASH_FILES)) {
86
- try {
87
- unlinkSync(e.file);
88
- } catch {
89
- }
90
- }
91
- } catch {
92
- }
93
- }
94
- function listRecentCrashes(limit = 10, configDir) {
95
- const dir = getLogsDir(configDir);
96
- if (!existsSync(dir)) return [];
97
- const files = [];
98
- let names;
99
- try {
100
- names = readdirSync(dir);
101
- } catch {
102
- return [];
103
- }
104
- for (const name of names) {
105
- if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
106
- const full = join(dir, name);
107
- try {
108
- const st = statSync(full);
109
- const head = readFileSync(full, "utf-8").split("\n").slice(0, 20);
110
- const tsLine = head.find((l) => l.startsWith("timestamp:"));
111
- const kindLine = head.find((l) => l.startsWith("kind:"));
112
- const msgIdx = head.findIndex((l) => l.trim() === "## message");
113
- const message = msgIdx >= 0 && head[msgIdx + 1] ? head[msgIdx + 1] : "";
114
- files.push({
115
- file: full,
116
- timestamp: tsLine ? tsLine.replace("timestamp:", "").trim() : st.mtime.toISOString(),
117
- kind: kindLine ? kindLine.replace("kind:", "").trim() : "unknown",
118
- message: truncate(message, 200),
119
- sizeBytes: st.size
120
- });
121
- } catch {
122
- }
123
- }
124
- files.sort((a, b) => a.timestamp < b.timestamp ? 1 : -1);
125
- return files.slice(0, limit);
126
- }
127
- function getConfigDirUsage(configDir) {
128
- const base = configDir ?? join(homedir(), CONFIG_DIR_NAME);
129
- if (!existsSync(base)) return { totalBytes: 0, entries: [] };
130
- const entries = [];
131
- let total = 0;
132
- let names;
133
- try {
134
- names = readdirSync(base);
135
- } catch {
136
- return { totalBytes: 0, entries: [] };
137
- }
138
- for (const name of names) {
139
- const full = join(base, name);
140
- const bytes = dirSize(full);
141
- entries.push({ name, bytes });
142
- total += bytes;
143
- }
144
- entries.sort((a, b) => b.bytes - a.bytes);
145
- return { totalBytes: total, entries };
146
- }
147
- function dirSize(path) {
148
- try {
149
- const st = statSync(path);
150
- if (st.isFile()) return st.size;
151
- if (!st.isDirectory()) return 0;
152
- let sum = 0;
153
- for (const name of readdirSync(path)) {
154
- sum += dirSize(join(path, name));
155
- }
156
- return sum;
157
- } catch {
158
- return 0;
159
- }
160
- }
161
-
162
- export {
163
- writeCrashLog,
164
- listRecentCrashes,
165
- getConfigDirUsage
166
- };