jinzd-ai-cli 0.4.218 → 0.4.219

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 (30) hide show
  1. package/README.md +1 -0
  2. package/dist/{batch-7V7OTMUP.js → batch-CHAPYRVM.js} +2 -2
  3. package/dist/chunk-3I4RZYYO.js +638 -0
  4. package/dist/{chunk-L4UREAID.js → chunk-4ZN6L6K5.js} +3 -3
  5. package/dist/{chunk-VGFTM3XT.js → chunk-64NUW3WL.js} +1 -1
  6. package/dist/{chunk-C2Z42DI5.js → chunk-EGMORH5W.js} +1 -1
  7. package/dist/{chunk-GX3HSGJX.js → chunk-GEN4O5QH.js} +165 -637
  8. package/dist/chunk-GGKQHPB3.js +604 -0
  9. package/dist/{chunk-P4VBLXKS.js → chunk-KIEZBTVD.js} +1 -1
  10. package/dist/{chunk-H2UIHGHH.js → chunk-QUFYBR6Q.js} +29 -189
  11. package/dist/{chunk-NTCB7CMT.js → chunk-U7KVU25H.js} +1 -1
  12. package/dist/{chunk-5CA2TJ5F.js → chunk-VADHBW7T.js} +1 -1
  13. package/dist/{ci-L6GH2WVC.js → ci-QPP66T53.js} +4 -4
  14. package/dist/{ci-format-WW7454AY.js → ci-format-5S3EEYRK.js} +2 -2
  15. package/dist/{constants-NCTFSHDU.js → constants-FJOLBABC.js} +1 -1
  16. package/dist/doctor-cli-KB2LUDF3.js +17 -0
  17. package/dist/electron-server.js +419 -116
  18. package/dist/{hub-CDL6T7CP.js → hub-DIM7SKKY.js} +1 -1
  19. package/dist/index.js +71 -181
  20. package/dist/{pr-D6PEKEGK.js → pr-YQGH72N6.js} +4 -4
  21. package/dist/{run-tests-SWU2XEV7.js → run-tests-GEZSSNJM.js} +2 -2
  22. package/dist/{run-tests-NXVVKAK2.js → run-tests-O76JIBCW.js} +1 -1
  23. package/dist/{server-LHYSS6CK.js → server-5E2AIXVX.js} +39 -75
  24. package/dist/{server-WUT7VYTD.js → server-Z6O3G2LY.js} +9 -9
  25. package/dist/{task-orchestrator-C5AA2BI5.js → task-orchestrator-5HBW4O64.js} +9 -9
  26. package/dist/{usage-6ZUUJBI2.js → usage-BMK6M5U3.js} +2 -2
  27. package/package.json +1 -1
  28. package/dist/chunk-MWKE2TNS.js +0 -129
  29. package/dist/chunk-OUC75QCF.js +0 -166
  30. package/dist/doctor-cli-EWMFBP5Q.js +0 -226
package/README.md CHANGED
@@ -563,6 +563,7 @@ node scripts/release.mjs patch -m "..." --dry-run
563
563
  - [`docs/USAGE.md`](docs/USAGE.md) — Complete reference manual (commands, tools, config)
564
564
  - [`docs/TUTORIAL.md`](docs/TUTORIAL.md) — Hands-on tutorial, zero to fluent in an hour
565
565
  - [`docs/ADVANCED.md`](docs/ADVANCED.md) — Architecture and internals (for developers)
566
+ - [`docs/MIGRATION.md`](docs/MIGRATION.md) — Migration guide from Claude Code, Codex, Claude Desktop, and Cursor
566
567
  - [`docs/RECIPES.md`](docs/RECIPES.md) — Practical recipes by scenario
567
568
  - [`docs/SECURITY.md`](docs/SECURITY.md) — **Security model, deployment checklist, audit history.** Read before exposing `aicli web` to a network.
568
569
  - [`CHANGELOG.md`](CHANGELOG.md) — Per-version change log
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-P4VBLXKS.js";
4
+ } from "./chunk-KIEZBTVD.js";
5
5
  import "./chunk-TZQHYZKT.js";
6
- import "./chunk-NTCB7CMT.js";
6
+ import "./chunk-U7KVU25H.js";
7
7
  import {
8
8
  atomicWriteFileSync
9
9
  } from "./chunk-IW3Q7AE5.js";
@@ -0,0 +1,638 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ProviderRegistry
4
+ } from "./chunk-QMXC327F.js";
5
+ import {
6
+ getActivePluginAssets,
7
+ getPendingHookTrust,
8
+ getStatsSnapshot,
9
+ getTopFailingTools,
10
+ getTopUsedTools,
11
+ listHooks,
12
+ listInstalledPlugins,
13
+ resetStats
14
+ } from "./chunk-GGKQHPB3.js";
15
+ import {
16
+ ConfigManager
17
+ } from "./chunk-KIEZBTVD.js";
18
+ import {
19
+ CONFIG_DIR_NAME,
20
+ CONTEXT_FILE_CANDIDATES,
21
+ CONTEXT_FILE_MAX_BYTES,
22
+ DEV_STATE_FILE_NAME,
23
+ MCP_PROJECT_CONFIG_NAME,
24
+ MEMORY_FILE_NAME,
25
+ VERSION
26
+ } from "./chunk-U7KVU25H.js";
27
+ import {
28
+ getGitRoot
29
+ } from "./chunk-HOSJZMQS.js";
30
+
31
+ // src/diagnostics/doctor-report.ts
32
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
33
+ import { join as join3 } from "path";
34
+ import { arch as arch2, platform as platform2, release as release2 } from "os";
35
+
36
+ // src/core/context-files.ts
37
+ import { existsSync, readFileSync } from "fs";
38
+ import { join, relative, resolve } from "path";
39
+ function uniqueNonEmpty(values) {
40
+ const seen = /* @__PURE__ */ new Set();
41
+ const out = [];
42
+ for (const value of values) {
43
+ const trimmed = value.trim();
44
+ if (!trimmed || seen.has(trimmed)) continue;
45
+ seen.add(trimmed);
46
+ out.push(trimmed);
47
+ }
48
+ return out;
49
+ }
50
+ function buildContextFileCandidates(fallbackFilenames = []) {
51
+ return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
52
+ }
53
+ function displayPath(filePath, cwd) {
54
+ const rel = relative(cwd, filePath);
55
+ if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
56
+ return rel;
57
+ }
58
+ return filePath;
59
+ }
60
+ function isInsideOrEqual(parent, child) {
61
+ const rel = relative(resolve(parent), resolve(child));
62
+ return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
63
+ }
64
+ function readContextFile(level, filePath, cwd, maxBytes) {
65
+ const name = filePath.split(/[\\/]/).pop() ?? filePath;
66
+ const shown = displayPath(filePath, cwd);
67
+ try {
68
+ const raw = readFileSync(filePath);
69
+ const byteCount = raw.byteLength;
70
+ if (byteCount === 0) {
71
+ return {
72
+ layer: null,
73
+ skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
74
+ };
75
+ }
76
+ const truncated = byteCount > maxBytes;
77
+ const bytes = truncated ? raw.subarray(0, maxBytes) : raw;
78
+ const content = bytes.toString("utf-8").trim();
79
+ if (!content) {
80
+ return {
81
+ layer: null,
82
+ skipped: { level, filePath, displayPath: shown, fileName: name, reason: "empty" }
83
+ };
84
+ }
85
+ return {
86
+ layer: {
87
+ level: level === "single" ? "project" : level,
88
+ filePath,
89
+ displayPath: shown,
90
+ fileName: name,
91
+ content,
92
+ charCount: content.length,
93
+ byteCount,
94
+ truncated
95
+ },
96
+ skipped: truncated ? { level, filePath, displayPath: shown, fileName: name, reason: "too-large", message: `truncated to ${maxBytes} bytes` } : null
97
+ };
98
+ } catch (err) {
99
+ return {
100
+ layer: null,
101
+ skipped: {
102
+ level,
103
+ filePath,
104
+ displayPath: shown,
105
+ fileName: name,
106
+ reason: "read-error",
107
+ message: err instanceof Error ? err.message : String(err)
108
+ }
109
+ };
110
+ }
111
+ }
112
+ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
113
+ const skipped = [];
114
+ for (const candidate of candidates) {
115
+ const filePath = join(dir, candidate);
116
+ if (!existsSync(filePath)) continue;
117
+ const result = readContextFile(level, filePath, cwd, maxBytes);
118
+ if (result.skipped) skipped.push(result.skipped);
119
+ if (result.layer) return { layer: result.layer, skipped };
120
+ }
121
+ return { layer: null, skipped };
122
+ }
123
+ function loadContextFiles(options) {
124
+ const cwd = resolve(options.cwd);
125
+ const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
126
+ const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
127
+ const skipped = [];
128
+ if (options.setting === false) {
129
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
130
+ }
131
+ if (options.setting && options.setting !== "auto") {
132
+ const filePath = resolve(cwd, String(options.setting));
133
+ if (!isInsideOrEqual(cwd, filePath)) {
134
+ skipped.push({
135
+ level: "single",
136
+ filePath,
137
+ displayPath: filePath,
138
+ fileName: String(options.setting),
139
+ reason: "outside-cwd"
140
+ });
141
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
142
+ }
143
+ if (!existsSync(filePath)) {
144
+ skipped.push({
145
+ level: "single",
146
+ filePath,
147
+ displayPath: displayPath(filePath, cwd),
148
+ fileName: String(options.setting),
149
+ reason: "missing"
150
+ });
151
+ return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
152
+ }
153
+ const result = readContextFile("single", filePath, cwd, maxBytes);
154
+ if (result.skipped) skipped.push(result.skipped);
155
+ const layers2 = result.layer ? [result.layer] : [];
156
+ const mergedContent2 = layers2.map((l) => l.content).join("\n\n---\n\n");
157
+ return {
158
+ layers: layers2,
159
+ mergedContent: mergedContent2,
160
+ skipped,
161
+ totalChars: layers2.reduce((sum, l) => sum + l.charCount, 0),
162
+ totalBytes: layers2.reduce((sum, l) => sum + l.byteCount, 0),
163
+ maxBytes,
164
+ candidates
165
+ };
166
+ }
167
+ const layers = [];
168
+ for (const [level, dir] of [
169
+ ["global", options.configDir],
170
+ ["project", options.projectRoot]
171
+ ]) {
172
+ const result = findFirstContextFile(level, dir, cwd, candidates, maxBytes);
173
+ skipped.push(...result.skipped);
174
+ if (result.layer) layers.push(result.layer);
175
+ }
176
+ if (resolve(options.cwd) !== resolve(options.projectRoot)) {
177
+ const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
178
+ skipped.push(...result.skipped);
179
+ if (result.layer) layers.push(result.layer);
180
+ }
181
+ const mergedContent = layers.map((l) => l.content).join("\n\n---\n\n");
182
+ return {
183
+ layers,
184
+ mergedContent,
185
+ skipped,
186
+ totalChars: layers.reduce((sum, l) => sum + l.charCount, 0),
187
+ totalBytes: layers.reduce((sum, l) => sum + l.byteCount, 0),
188
+ maxBytes,
189
+ candidates
190
+ };
191
+ }
192
+
193
+ // src/diagnostics/crash-log.ts
194
+ import {
195
+ existsSync as existsSync2,
196
+ mkdirSync,
197
+ readdirSync,
198
+ readFileSync as readFileSync2,
199
+ statSync,
200
+ unlinkSync,
201
+ writeFileSync
202
+ } from "fs";
203
+ import { join as join2 } from "path";
204
+ import { homedir, platform, release, arch } from "os";
205
+ var LOGS_DIR_NAME = "logs";
206
+ var CRASH_LOG_PREFIX = "crash-";
207
+ var MAX_CRASH_FILES = 20;
208
+ function getLogsDir(configDir) {
209
+ const base = configDir ?? join2(homedir(), CONFIG_DIR_NAME);
210
+ return join2(base, LOGS_DIR_NAME);
211
+ }
212
+ function ensureLogsDir(configDir) {
213
+ const dir = getLogsDir(configDir);
214
+ if (!existsSync2(dir)) {
215
+ mkdirSync(dir, { recursive: true });
216
+ }
217
+ return dir;
218
+ }
219
+ function isoTimestampForFilename(d = /* @__PURE__ */ new Date()) {
220
+ return d.toISOString().replace(/[:.]/g, "-");
221
+ }
222
+ function writeCrashLog(err, context, configDir) {
223
+ try {
224
+ const dir = ensureLogsDir(configDir);
225
+ const ts = /* @__PURE__ */ new Date();
226
+ const file = join2(dir, `${CRASH_LOG_PREFIX}${isoTimestampForFilename(ts)}.log`);
227
+ const errMessage = err instanceof Error ? err.message : String(err);
228
+ const errStack = err instanceof Error ? err.stack ?? "(no stack)" : "(non-Error thrown)";
229
+ const lines = [
230
+ `# ai-cli crash log`,
231
+ `timestamp: ${ts.toISOString()}`,
232
+ `kind: ${context.kind}`,
233
+ `version: ${VERSION}`,
234
+ `node: ${process.version}`,
235
+ `platform: ${platform()} ${release()} (${arch()})`,
236
+ `pid: ${process.pid}`,
237
+ context.provider ? `provider: ${context.provider}` : "",
238
+ context.model ? `model: ${context.model}` : "",
239
+ context.lastAction ? `lastAction: ${truncate(context.lastAction, 500)}` : "",
240
+ "",
241
+ `## message`,
242
+ errMessage,
243
+ "",
244
+ `## stack`,
245
+ errStack,
246
+ ""
247
+ ].filter(Boolean);
248
+ writeFileSync(file, lines.join("\n"), "utf-8");
249
+ pruneOldCrashes(configDir);
250
+ return file;
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+ function truncate(s, n) {
256
+ return s.length <= n ? s : s.slice(0, n) + "\u2026";
257
+ }
258
+ function pruneOldCrashes(configDir) {
259
+ try {
260
+ const dir = getLogsDir(configDir);
261
+ if (!existsSync2(dir)) return;
262
+ const entries = readdirSync(dir).filter((f) => f.startsWith(CRASH_LOG_PREFIX) && f.endsWith(".log")).map((f) => {
263
+ const full = join2(dir, f);
264
+ let mtime = 0;
265
+ try {
266
+ mtime = statSync(full).mtimeMs;
267
+ } catch {
268
+ }
269
+ return { file: full, mtime };
270
+ }).sort((a, b) => b.mtime - a.mtime);
271
+ for (const e of entries.slice(MAX_CRASH_FILES)) {
272
+ try {
273
+ unlinkSync(e.file);
274
+ } catch {
275
+ }
276
+ }
277
+ } catch {
278
+ }
279
+ }
280
+ function listRecentCrashes(limit = 10, configDir) {
281
+ const dir = getLogsDir(configDir);
282
+ if (!existsSync2(dir)) return [];
283
+ const files = [];
284
+ let names;
285
+ try {
286
+ names = readdirSync(dir);
287
+ } catch {
288
+ return [];
289
+ }
290
+ for (const name of names) {
291
+ if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
292
+ const full = join2(dir, name);
293
+ try {
294
+ const st = statSync(full);
295
+ const head = readFileSync2(full, "utf-8").split("\n").slice(0, 20);
296
+ const tsLine = head.find((l) => l.startsWith("timestamp:"));
297
+ const kindLine = head.find((l) => l.startsWith("kind:"));
298
+ const msgIdx = head.findIndex((l) => l.trim() === "## message");
299
+ const message = msgIdx >= 0 && head[msgIdx + 1] ? head[msgIdx + 1] : "";
300
+ files.push({
301
+ file: full,
302
+ timestamp: tsLine ? tsLine.replace("timestamp:", "").trim() : st.mtime.toISOString(),
303
+ kind: kindLine ? kindLine.replace("kind:", "").trim() : "unknown",
304
+ message: truncate(message, 200),
305
+ sizeBytes: st.size
306
+ });
307
+ } catch {
308
+ }
309
+ }
310
+ files.sort((a, b) => a.timestamp < b.timestamp ? 1 : -1);
311
+ return files.slice(0, limit);
312
+ }
313
+ function getConfigDirUsage(configDir) {
314
+ const base = configDir ?? join2(homedir(), CONFIG_DIR_NAME);
315
+ if (!existsSync2(base)) return { totalBytes: 0, entries: [] };
316
+ const entries = [];
317
+ let total = 0;
318
+ let names;
319
+ try {
320
+ names = readdirSync(base);
321
+ } catch {
322
+ return { totalBytes: 0, entries: [] };
323
+ }
324
+ for (const name of names) {
325
+ const full = join2(base, name);
326
+ const bytes = dirSize(full);
327
+ entries.push({ name, bytes });
328
+ total += bytes;
329
+ }
330
+ entries.sort((a, b) => b.bytes - a.bytes);
331
+ return { totalBytes: total, entries };
332
+ }
333
+ function dirSize(path) {
334
+ try {
335
+ const st = statSync(path);
336
+ if (st.isFile()) return st.size;
337
+ if (!st.isDirectory()) return 0;
338
+ let sum = 0;
339
+ for (const name of readdirSync(path)) {
340
+ sum += dirSize(join2(path, name));
341
+ }
342
+ return sum;
343
+ } catch {
344
+ return 0;
345
+ }
346
+ }
347
+
348
+ // src/diagnostics/doctor-report.ts
349
+ function checkFile(label, path) {
350
+ const exists = existsSync3(path);
351
+ let sizeBytes = null;
352
+ if (exists) {
353
+ try {
354
+ sizeBytes = statSync2(path).size;
355
+ } catch {
356
+ }
357
+ }
358
+ return { label, path, exists, sizeBytes };
359
+ }
360
+ function buildDoctorReport(options) {
361
+ const config = options.config;
362
+ const cwd = options.cwd ?? process.cwd();
363
+ const configDir = config.getConfigDir();
364
+ const projectRoot = getGitRoot(cwd) ?? cwd;
365
+ const contextCfg = config.get("context");
366
+ const contextSetting = config.get("contextFile");
367
+ const context = loadContextFiles({
368
+ cwd,
369
+ configDir,
370
+ projectRoot,
371
+ setting: contextSetting,
372
+ maxBytes: contextCfg.projectDocMaxBytes,
373
+ fallbackFilenames: contextCfg.projectDocFallbackFilenames
374
+ });
375
+ const hooksConfig = config.get("hooks");
376
+ const hooks = listHooks(hooksConfig, configDir);
377
+ const pendingHooks = getPendingHookTrust(hooksConfig, configDir);
378
+ const plugins = listInstalledPlugins(configDir);
379
+ const activeAssets = getActivePluginAssets(configDir);
380
+ const network = config.get("networkPolicy");
381
+ const mcpServers = config.get("mcpServers");
382
+ const allStats = getStatsSnapshot();
383
+ return {
384
+ version: VERSION,
385
+ npmCheck: options.npmCheck,
386
+ node: process.version,
387
+ platform: `${platform2()} ${release2()} (${arch2()})`,
388
+ cwd,
389
+ projectRoot,
390
+ configDir,
391
+ providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
392
+ files: [
393
+ checkFile("config.json", join3(configDir, "config.json")),
394
+ checkFile("memory.md", join3(configDir, MEMORY_FILE_NAME)),
395
+ checkFile("memory.jsonl", join3(configDir, "memory.jsonl")),
396
+ checkFile("dev-state.md", join3(configDir, DEV_STATE_FILE_NAME)),
397
+ checkFile("hooks-trust.json", join3(configDir, "hooks-trust.json")),
398
+ checkFile("plugin-state.json", join3(configDir, "plugins", "plugin-state.json"))
399
+ ],
400
+ context: {
401
+ enabled: contextSetting !== false,
402
+ setting: contextSetting,
403
+ candidates: context.candidates,
404
+ maxBytes: context.maxBytes,
405
+ layers: context.layers.map((l) => ({ level: l.level, fileName: l.fileName, displayPath: l.displayPath, bytes: l.byteCount, chars: l.charCount, truncated: l.truncated })),
406
+ skipped: context.skipped.map((s) => ({ level: s.level, fileName: s.fileName, displayPath: s.displayPath, reason: s.reason, message: s.message }))
407
+ },
408
+ permissions: {
409
+ defaultProfile: config.get("defaultPermissionProfile"),
410
+ allowedProfiles: config.get("allowedPermissionProfiles"),
411
+ customProfileCount: Object.keys(config.get("permissionProfiles")).length,
412
+ legacyDefaultAction: config.get("defaultPermission"),
413
+ ruleCount: config.get("permissionRules").length
414
+ },
415
+ networkPolicy: {
416
+ enabled: network.enabled,
417
+ defaultAction: network.defaultAction,
418
+ allowDomains: network.allowDomains.length,
419
+ denyDomains: network.denyDomains.length,
420
+ allowPorts: network.allowPorts.length,
421
+ denyPorts: network.denyPorts.length,
422
+ allowPrivateNetwork: network.allowPrivateNetwork,
423
+ toolOverrides: Object.fromEntries(Object.entries(network.tools).filter(([, v]) => v !== void 0))
424
+ },
425
+ hooks: {
426
+ enabled: hooksConfig?.enabled !== false,
427
+ total: hooks.length,
428
+ project: hooks.filter((h) => h.source === "project").length,
429
+ pendingTrust: pendingHooks.length,
430
+ disabled: hooks.filter((h) => h.disabled).length
431
+ },
432
+ plugins: {
433
+ root: join3(configDir, "plugins"),
434
+ installed: plugins.length,
435
+ active: activeAssets.plugins.length,
436
+ invalid: plugins.filter((p) => !p.valid).length,
437
+ untrusted: plugins.filter((p) => p.valid && !p.trusted).length,
438
+ disabled: plugins.filter((p) => p.valid && !p.enabled).length,
439
+ mcpServers: Object.keys(activeAssets.mcpServers).length,
440
+ skillDirs: activeAssets.skillDirs.length,
441
+ commandDirs: activeAssets.commandDirs.length,
442
+ agentDirs: activeAssets.agentDirs.length,
443
+ entries: plugins.map((p) => ({ name: p.name, version: p.manifest.version, enabled: p.enabled, trusted: p.trusted, valid: p.valid, error: p.error }))
444
+ },
445
+ mcp: {
446
+ enabled: config.get("mcpEnabled"),
447
+ configured: Object.keys(mcpServers).length,
448
+ projectConfigExists: existsSync3(join3(projectRoot, MCP_PROJECT_CONFIG_NAME)),
449
+ statuses: options.mcpStatuses ?? []
450
+ },
451
+ recentCrashes: listRecentCrashes(5, configDir),
452
+ diskUsage: getConfigDirUsage(configDir),
453
+ toolStats: {
454
+ totalCalls: allStats.reduce((a, b) => a + b.calls, 0),
455
+ totalFailures: allStats.reduce((a, b) => a + b.failures, 0),
456
+ topUsed: getTopUsedTools(5),
457
+ topFailing: getTopFailingTools(5)
458
+ }
459
+ };
460
+ }
461
+
462
+ // src/diagnostics/doctor-cli.ts
463
+ function compareVersion(a, b) {
464
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
465
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
466
+ const len = Math.max(pa.length, pb.length);
467
+ for (let i = 0; i < len; i++) {
468
+ const da = pa[i] ?? 0;
469
+ const db = pb[i] ?? 0;
470
+ if (da !== db) return da - db;
471
+ }
472
+ return 0;
473
+ }
474
+ async function checkNpmLatest() {
475
+ const controller = new AbortController();
476
+ const timer = setTimeout(() => controller.abort(), 3e3);
477
+ try {
478
+ const res = await fetch("https://registry.npmjs.org/jinzd-ai-cli/latest", {
479
+ signal: controller.signal,
480
+ headers: { accept: "application/json" }
481
+ });
482
+ if (!res.ok) return { latest: null, status: "skipped", reason: `HTTP ${res.status}` };
483
+ const data = await res.json();
484
+ const latest = typeof data.version === "string" ? data.version : null;
485
+ if (!latest) return { latest: null, status: "skipped", reason: "no version in response" };
486
+ return { latest, status: compareVersion(latest, VERSION) > 0 ? "outdated" : "up-to-date" };
487
+ } catch (err) {
488
+ const reason = err?.name === "AbortError" ? "timeout (3s)" : err?.message ?? "network error";
489
+ return { latest: null, status: "skipped", reason };
490
+ } finally {
491
+ clearTimeout(timer);
492
+ }
493
+ }
494
+ async function collect() {
495
+ const config = new ConfigManager();
496
+ const registry = new ProviderRegistry();
497
+ await registry.initialize(
498
+ (id) => config.getApiKey(id),
499
+ (id) => ({ baseUrl: config.get("customBaseUrls")[id], timeout: config.get("timeouts")[id] }),
500
+ config.get("customProviders")
501
+ );
502
+ return buildDoctorReport({ config, providers: registry, npmCheck: await checkNpmLatest() });
503
+ }
504
+ function formatBytes(n) {
505
+ if (n < 1024) return `${n} B`;
506
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
507
+ if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
508
+ return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
509
+ }
510
+ function formatDoctorReport(report, ansi = true) {
511
+ const wrap = (code, s) => ansi ? `${code}${s}\x1B[0m` : s;
512
+ const B = (s) => wrap("\x1B[1m", s);
513
+ const D = (s) => wrap("\x1B[2m", s);
514
+ const G = (s) => wrap("\x1B[32m", s);
515
+ const Y = (s) => wrap("\x1B[33m", s);
516
+ const R = (s) => wrap("\x1B[31m", s);
517
+ const out = [];
518
+ out.push("");
519
+ out.push(B("\u{1FA7A} AI-CLI Health Check"));
520
+ out.push("");
521
+ const nc = report.npmCheck;
522
+ let versionLine = ` version: ${report.version}`;
523
+ if (nc?.status === "up-to-date") versionLine += ` ${D("(latest on npm)")}`;
524
+ else if (nc?.status === "outdated" && nc.latest) versionLine += ` ${Y(`\u2192 ${nc.latest} available`)} ${D("(npm i -g jinzd-ai-cli@latest)")}`;
525
+ else if (nc?.status === "skipped") versionLine += ` ${D(`(npm check skipped: ${nc.reason ?? "unknown"})`)}`;
526
+ out.push(versionLine);
527
+ out.push(` node: ${report.node}`);
528
+ out.push(` platform: ${report.platform}`);
529
+ out.push(` cwd: ${report.cwd}`);
530
+ out.push(` project: ${report.projectRoot}`);
531
+ out.push(` config: ${report.configDir}`);
532
+ out.push("");
533
+ out.push(B("API Keys:"));
534
+ for (const p of report.providers) {
535
+ out.push(` ${p.configured ? G("\u2713") : D("\u25CB")} ${p.id.padEnd(14)} ${p.configured ? G("configured") : D("not configured")}`);
536
+ }
537
+ out.push("");
538
+ out.push(B("Context Files:"));
539
+ out.push(` status: ${report.context.enabled ? G("enabled") : D("disabled")} \xB7 max ${formatBytes(report.context.maxBytes)}`);
540
+ if (report.context.layers.length === 0) out.push(` ${D("(no context files loaded)")}`);
541
+ for (const l of report.context.layers) {
542
+ out.push(` ${G("\u2713")} ${l.level.padEnd(7)} ${l.displayPath} ${D(`(${formatBytes(l.bytes)}${l.truncated ? ", truncated" : ""})`)}`);
543
+ }
544
+ if (report.context.skipped.length > 0) {
545
+ out.push(` ${D("skipped:")}`);
546
+ for (const s of report.context.skipped.slice(0, 5)) {
547
+ out.push(` ${Y("!")} ${s.level}:${s.fileName} ${s.reason}${s.message ? ` \xB7 ${s.message}` : ""}`);
548
+ }
549
+ }
550
+ out.push("");
551
+ out.push(B("Permissions / Network:"));
552
+ out.push(` profile: ${report.permissions.defaultProfile} \xB7 allowed ${report.permissions.allowedProfiles.join(", ")} \xB7 rules ${report.permissions.ruleCount} \xB7 custom profiles ${report.permissions.customProfileCount}`);
553
+ const np = report.networkPolicy;
554
+ out.push(` networkPolicy: ${np.enabled ? G("enabled") : D("disabled")} \xB7 default ${np.defaultAction} \xB7 allow ${np.allowDomains} domain(s)/${np.allowPorts} port(s) \xB7 deny ${np.denyDomains} domain(s)/${np.denyPorts} port(s) \xB7 private ${np.allowPrivateNetwork ? "allow" : "block/confirm"}`);
555
+ const overrides = Object.entries(np.toolOverrides).map(([k, v]) => `${k}=${v}`).join(", ");
556
+ if (overrides) out.push(` tool overrides: ${overrides}`);
557
+ out.push("");
558
+ out.push(B("Hooks / Plugins:"));
559
+ out.push(` hooks: ${report.hooks.enabled ? G("enabled") : D("disabled")} \xB7 total ${report.hooks.total} \xB7 project ${report.hooks.project} \xB7 pending trust ${report.hooks.pendingTrust} \xB7 disabled ${report.hooks.disabled}`);
560
+ out.push(` plugins: installed ${report.plugins.installed} \xB7 active ${report.plugins.active} \xB7 disabled ${report.plugins.disabled} \xB7 untrusted ${report.plugins.untrusted} \xB7 invalid ${report.plugins.invalid}`);
561
+ out.push(` plugin assets: ${report.plugins.skillDirs} skill dir(s), ${report.plugins.commandDirs} command dir(s), ${report.plugins.agentDirs} agent dir(s), ${report.plugins.mcpServers} MCP server(s)`);
562
+ if (report.plugins.entries.length > 0) {
563
+ for (const p of report.plugins.entries.slice(0, 5)) {
564
+ const state = p.valid ? `${p.enabled ? "enabled" : "disabled"} \xB7 ${p.trusted ? "trusted" : "untrusted"}` : `invalid \xB7 ${p.error ?? ""}`;
565
+ out.push(` ${p.valid ? G("\u2713") : R("\u2717")} ${p.name}@${p.version} ${D(state)}`);
566
+ }
567
+ }
568
+ out.push("");
569
+ out.push(B("MCP Servers:"));
570
+ out.push(` global configured: ${report.mcp.configured} \xB7 project .mcp.json: ${report.mcp.projectConfigExists ? "yes" : "no"} \xB7 mcpEnabled: ${report.mcp.enabled ? "true" : "false"}`);
571
+ if (report.mcp.statuses.length === 0) out.push(` ${D("(no connected manager status in this mode)")}`);
572
+ for (const s of report.mcp.statuses) {
573
+ const state = s.connected ? `${G("connected")} \xB7 ${s.serverName} \xB7 ${s.toolCount} tools` : `${R("disconnected")}${s.error ? D(` \xB7 ${s.error}`) : ""}`;
574
+ out.push(` ${s.connected ? G("\u2713") : R("\u2717")} ${s.serverId.padEnd(16)} ${state}`);
575
+ }
576
+ out.push("");
577
+ out.push(B("Config Files:"));
578
+ for (const f of report.files) {
579
+ const extra = f.exists && f.sizeBytes !== null ? ` ${D(`(${formatBytes(f.sizeBytes)})`)}` : "";
580
+ out.push(` ${f.exists ? G("\u2713") : D("\u2013")} ${f.label.padEnd(16)} ${f.exists ? f.path : D("(not found)")}${extra}`);
581
+ }
582
+ out.push("");
583
+ out.push(B("Recent Crashes (last 5):"));
584
+ if (report.recentCrashes.length === 0) out.push(` ${D("(none - clean!)")}`);
585
+ for (const c of report.recentCrashes) {
586
+ out.push(` ${R("\u2717")} ${c.timestamp} ${Y(c.kind)}`);
587
+ out.push(` ${D(c.message)}`);
588
+ out.push(` ${D(c.file)}`);
589
+ }
590
+ out.push("");
591
+ const ts = report.toolStats;
592
+ out.push(B("Tool Usage:"));
593
+ out.push(` total: ${ts.totalCalls} calls \xB7 ${ts.totalFailures} failures (${ts.totalCalls ? (ts.totalFailures * 100 / ts.totalCalls).toFixed(1) : "0.0"}%)`);
594
+ if (ts.topUsed.length > 0) {
595
+ out.push(` ${D("top used:")}`);
596
+ for (const t of ts.topUsed) out.push(` ${t.name.padEnd(20)} ${String(t.calls).padStart(6)} calls \xB7 avg ${t.calls ? (t.totalDurationMs / t.calls).toFixed(0) : "0"}ms`);
597
+ }
598
+ if (ts.topFailing.length > 0) {
599
+ out.push(` ${D("top failing:")}`);
600
+ for (const t of ts.topFailing) out.push(` ${R(t.name.padEnd(20))} ${t.failures}/${t.calls} (${(t.failures * 100 / Math.max(1, t.calls)).toFixed(1)}%) ${D(t.lastFailureMessage ?? "")}`.slice(0, 200));
601
+ }
602
+ out.push("");
603
+ const dirAnnotations = {
604
+ models: "semantic search model -> /index semantic-clear",
605
+ index: "symbol index -> /index clear",
606
+ history: "conversation history",
607
+ logs: "crash logs",
608
+ "memory-index": "chat memory index -> /memory index-clear",
609
+ plugins: "installed package plugins"
610
+ };
611
+ out.push(B("Disk Usage (~/.aicli):"));
612
+ out.push(` total: ${formatBytes(report.diskUsage.totalBytes)}`);
613
+ for (const e of report.diskUsage.entries.slice(0, 10)) {
614
+ const note = dirAnnotations[e.name];
615
+ out.push(` ${e.name.padEnd(18)} ${formatBytes(e.bytes)}${note ? ` ${D(`\xB7 ${note}`)}` : ""}`);
616
+ }
617
+ out.push("");
618
+ out.push(G("\u2713 Health check complete"));
619
+ out.push("");
620
+ return out.join("\n");
621
+ }
622
+ async function runDoctorCli(options = {}) {
623
+ if (options.resetStats) {
624
+ resetStats();
625
+ process.stdout.write("Tool stats reset.\n");
626
+ return;
627
+ }
628
+ const report = await collect();
629
+ process.stdout.write(options.json ? JSON.stringify(report, null, 2) + "\n" : formatDoctorReport(report, true));
630
+ }
631
+
632
+ export {
633
+ writeCrashLog,
634
+ loadContextFiles,
635
+ buildDoctorReport,
636
+ formatDoctorReport,
637
+ runDoctorCli
638
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  evaluateCiGate,
4
4
  normalizeCiThresholds
5
- } from "./chunk-5CA2TJ5F.js";
5
+ } from "./chunk-VADHBW7T.js";
6
6
  import {
7
7
  buildReviewPrompt,
8
8
  buildSecurityReviewPrompt,
@@ -13,10 +13,10 @@ import {
13
13
  } from "./chunk-QMXC327F.js";
14
14
  import {
15
15
  ConfigManager
16
- } from "./chunk-P4VBLXKS.js";
16
+ } from "./chunk-KIEZBTVD.js";
17
17
  import {
18
18
  VERSION
19
- } from "./chunk-NTCB7CMT.js";
19
+ } from "./chunk-U7KVU25H.js";
20
20
 
21
21
  // src/cli/ci.ts
22
22
  import { execFileSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-NTCB7CMT.js";
4
+ } from "./chunk-U7KVU25H.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";