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
@@ -38,7 +38,7 @@ import {
38
38
  VERSION,
39
39
  buildUserIdentityPrompt,
40
40
  runTestsTool
41
- } from "./chunk-C2Z42DI5.js";
41
+ } from "./chunk-CAUHLHDR.js";
42
42
  import {
43
43
  hasSemanticIndex,
44
44
  semanticSearch
@@ -64,8 +64,8 @@ import {
64
64
  import express from "express";
65
65
  import { createServer } from "http";
66
66
  import { WebSocketServer } from "ws";
67
- import { join as join23, dirname as dirname8, resolve as resolve11, relative as relative4, sep as sep3 } from "path";
68
- import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync14, statSync as statSync10, realpathSync } from "fs";
67
+ import { join as join26, dirname as dirname9, resolve as resolve11, relative as relative4, sep as sep3 } from "path";
68
+ import { existsSync as existsSync31, readFileSync as readFileSync24, readdirSync as readdirSync15, statSync as statSync12, realpathSync } from "fs";
69
69
  import { networkInterfaces } from "os";
70
70
 
71
71
  // src/config/config-manager.ts
@@ -386,6 +386,7 @@ var ENV_KEY_MAP = {
386
386
  zhipu: "AICLI_API_KEY_ZHIPU",
387
387
  kimi: "AICLI_API_KEY_KIMI",
388
388
  minimax: "AICLI_API_KEY_MINIMAX",
389
+ qwen: "AICLI_API_KEY_QWEN",
389
390
  openai: "AICLI_API_KEY_OPENAI",
390
391
  openrouter: "AICLI_API_KEY_OPENROUTER",
391
392
  "google-search": "AICLI_API_KEY_GOOGLESEARCH",
@@ -1297,6 +1298,23 @@ var ClaudeProvider = class extends BaseProvider {
1297
1298
  async listModels() {
1298
1299
  return this.info.models;
1299
1300
  }
1301
+ async refreshModels() {
1302
+ try {
1303
+ const response = await this.client.models.list();
1304
+ const data = response.data ?? [];
1305
+ return data.map((model) => ({
1306
+ id: model.id,
1307
+ displayName: model.id,
1308
+ contextWindow: 2e5,
1309
+ supportsStreaming: true,
1310
+ source: "live",
1311
+ available: true,
1312
+ metadataUnknown: true
1313
+ }));
1314
+ } catch (err) {
1315
+ throw this.wrapError(err);
1316
+ }
1317
+ }
1300
1318
  wrapError(err) {
1301
1319
  if (err instanceof Anthropic.AuthenticationError) {
1302
1320
  return new AuthError("claude");
@@ -3088,6 +3106,22 @@ var OpenAICompatibleProvider = class extends BaseProvider {
3088
3106
  async listModels() {
3089
3107
  return this.info.models;
3090
3108
  }
3109
+ async refreshModels() {
3110
+ try {
3111
+ const response = await this.client.models.list();
3112
+ return response.data.map((model) => ({
3113
+ id: model.id,
3114
+ displayName: model.id,
3115
+ contextWindow: 128e3,
3116
+ supportsStreaming: true,
3117
+ source: "live",
3118
+ available: true,
3119
+ metadataUnknown: true
3120
+ }));
3121
+ } catch (err) {
3122
+ throw this.wrapError(err);
3123
+ }
3124
+ }
3091
3125
  wrapError(err) {
3092
3126
  if (err instanceof OpenAI.AuthenticationError) {
3093
3127
  return new AuthError(this.info.id);
@@ -3278,54 +3312,6 @@ var ZhipuProvider = class extends OpenAICompatibleProvider {
3278
3312
  displayName: "GLM-4.6 (200K, \u4E2D\u6587\u5199\u4F5C\u63A8\u8350)",
3279
3313
  contextWindow: 204800,
3280
3314
  supportsStreaming: true
3281
- },
3282
- {
3283
- id: "glm-4.6v",
3284
- displayName: "GLM-4.6V (Vision + Thinking)",
3285
- contextWindow: 131072,
3286
- supportsStreaming: true,
3287
- supportsThinking: true
3288
- },
3289
- // ── GLM-Z1 推理系列 ──
3290
- {
3291
- id: "glm-z1",
3292
- displayName: "GLM-Z1 (Reasoning Flagship)",
3293
- contextWindow: 131072,
3294
- supportsStreaming: true,
3295
- supportsThinking: true
3296
- },
3297
- {
3298
- id: "glm-z1-air",
3299
- displayName: "GLM-Z1 Air (Lightweight Reasoning)",
3300
- contextWindow: 131072,
3301
- supportsStreaming: true,
3302
- supportsThinking: true
3303
- },
3304
- {
3305
- id: "glm-z1-flash",
3306
- displayName: "GLM-Z1 Flash (Free Reasoning)",
3307
- contextWindow: 128e3,
3308
- supportsStreaming: true,
3309
- supportsThinking: true
3310
- },
3311
- // ── GLM-4 系列(稳定,价格低) ──
3312
- {
3313
- id: "glm-4-plus",
3314
- displayName: "GLM-4 Plus",
3315
- contextWindow: 128e3,
3316
- supportsStreaming: true
3317
- },
3318
- {
3319
- id: "glm-4-air",
3320
- displayName: "GLM-4 Air",
3321
- contextWindow: 128e3,
3322
- supportsStreaming: true
3323
- },
3324
- {
3325
- id: "glm-4-flash",
3326
- displayName: "GLM-4 Flash (Free)",
3327
- contextWindow: 128e3,
3328
- supportsStreaming: true
3329
3315
  }
3330
3316
  ]
3331
3317
  };
@@ -3362,8 +3348,7 @@ var KimiProvider = class _KimiProvider extends OpenAICompatibleProvider {
3362
3348
  "kimi-k2.6",
3363
3349
  "kimi-k2.5",
3364
3350
  "kimi-k2-thinking",
3365
- "kimi-k2-thinking-turbo",
3366
- "kimi-k2-0905-preview"
3351
+ "kimi-k2-thinking-turbo"
3367
3352
  ]);
3368
3353
  /** 对需要固定 temperature=1 的模型自动修正请求。 */
3369
3354
  fixTemp(req) {
@@ -3411,51 +3396,6 @@ var KimiProvider = class _KimiProvider extends OpenAICompatibleProvider {
3411
3396
  displayName: "Kimi K2 Thinking Turbo",
3412
3397
  contextWindow: 262144,
3413
3398
  supportsStreaming: true
3414
- },
3415
- // Kimi K2 Preview 系列
3416
- {
3417
- id: "kimi-k2-0905-preview",
3418
- displayName: "Kimi K2 0905 Preview",
3419
- contextWindow: 262144,
3420
- supportsStreaming: true
3421
- },
3422
- // moonshot-v1 系列(老一代,稳定)
3423
- {
3424
- id: "moonshot-v1-8k",
3425
- displayName: "Moonshot V1 8K",
3426
- contextWindow: 8192,
3427
- supportsStreaming: true
3428
- },
3429
- {
3430
- id: "moonshot-v1-32k",
3431
- displayName: "Moonshot V1 32K",
3432
- contextWindow: 32768,
3433
- supportsStreaming: true
3434
- },
3435
- {
3436
- id: "moonshot-v1-128k",
3437
- displayName: "Moonshot V1 128K",
3438
- contextWindow: 131072,
3439
- supportsStreaming: true
3440
- },
3441
- // moonshot-v1 Vision 系列(支持图片输入)
3442
- {
3443
- id: "moonshot-v1-8k-vision-preview",
3444
- displayName: "Moonshot V1 8K Vision",
3445
- contextWindow: 8192,
3446
- supportsStreaming: true
3447
- },
3448
- {
3449
- id: "moonshot-v1-32k-vision-preview",
3450
- displayName: "Moonshot V1 32K Vision",
3451
- contextWindow: 32768,
3452
- supportsStreaming: true
3453
- },
3454
- {
3455
- id: "moonshot-v1-128k-vision-preview",
3456
- displayName: "Moonshot V1 128K Vision",
3457
- contextWindow: 131072,
3458
- supportsStreaming: true
3459
3399
  }
3460
3400
  ]
3461
3401
  };
@@ -3581,23 +3521,44 @@ var MiniMaxProvider = class extends OpenAICompatibleProvider {
3581
3521
  displayName: "MiniMax M2.5 Highspeed",
3582
3522
  contextWindow: 1e6,
3583
3523
  supportsStreaming: true
3524
+ }
3525
+ ]
3526
+ };
3527
+ };
3528
+
3529
+ // src/providers/qwen.ts
3530
+ var QwenProvider = class extends OpenAICompatibleProvider {
3531
+ defaultBaseUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1";
3532
+ info = {
3533
+ id: "qwen",
3534
+ displayName: "Qwen (Alibaba Cloud)",
3535
+ defaultModel: "qwen-plus",
3536
+ apiKeyEnvVar: "AICLI_API_KEY_QWEN",
3537
+ requiresApiKey: true,
3538
+ baseUrl: this.defaultBaseUrl,
3539
+ models: [
3540
+ {
3541
+ id: "qwen-plus",
3542
+ displayName: "Qwen Plus",
3543
+ contextWindow: 131072,
3544
+ supportsStreaming: true
3584
3545
  },
3585
3546
  {
3586
- id: "MiniMax-M2.1",
3587
- displayName: "MiniMax M2.1",
3588
- contextWindow: 1e6,
3547
+ id: "qwen-max",
3548
+ displayName: "Qwen Max",
3549
+ contextWindow: 131072,
3589
3550
  supportsStreaming: true
3590
3551
  },
3591
3552
  {
3592
- id: "MiniMax-M2.1-highspeed",
3593
- displayName: "MiniMax M2.1 Highspeed",
3594
- contextWindow: 1e6,
3553
+ id: "qwen-turbo",
3554
+ displayName: "Qwen Turbo",
3555
+ contextWindow: 131072,
3595
3556
  supportsStreaming: true
3596
3557
  },
3597
3558
  {
3598
- id: "MiniMax-M2",
3599
- displayName: "MiniMax M2",
3600
- contextWindow: 1e6,
3559
+ id: "qwen-coder-plus",
3560
+ displayName: "Qwen Coder Plus",
3561
+ contextWindow: 131072,
3601
3562
  supportsStreaming: true
3602
3563
  }
3603
3564
  ]
@@ -3610,7 +3571,7 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
3610
3571
  info = {
3611
3572
  id: "openai",
3612
3573
  displayName: "OpenAI",
3613
- defaultModel: "gpt-4o",
3574
+ defaultModel: "gpt-5.4",
3614
3575
  apiKeyEnvVar: "AICLI_API_KEY_OPENAI",
3615
3576
  requiresApiKey: true,
3616
3577
  baseUrl: this.defaultBaseUrl,
@@ -3666,19 +3627,6 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
3666
3627
  contextWindow: 1048576,
3667
3628
  supportsStreaming: true
3668
3629
  },
3669
- // GPT-4o 系列(128K 上下文)
3670
- {
3671
- id: "gpt-4o",
3672
- displayName: "GPT-4o",
3673
- contextWindow: 128e3,
3674
- supportsStreaming: true
3675
- },
3676
- {
3677
- id: "gpt-4o-mini",
3678
- displayName: "GPT-4o Mini",
3679
- contextWindow: 128e3,
3680
- supportsStreaming: true
3681
- },
3682
3630
  // 推理模型(200K 上下文)
3683
3631
  {
3684
3632
  id: "o3",
@@ -3686,12 +3634,6 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
3686
3634
  contextWindow: 2e5,
3687
3635
  supportsStreaming: true
3688
3636
  },
3689
- {
3690
- id: "o3-mini",
3691
- displayName: "o3 Mini",
3692
- contextWindow: 2e5,
3693
- supportsStreaming: true
3694
- },
3695
3637
  {
3696
3638
  id: "o4-mini",
3697
3639
  displayName: "o4 Mini",
@@ -3713,7 +3655,6 @@ var OpenRouterProvider = class extends OpenAICompatibleProvider {
3713
3655
  requiresApiKey: true,
3714
3656
  baseUrl: this.defaultBaseUrl,
3715
3657
  models: [
3716
- // Anthropic Claude 系列
3717
3658
  {
3718
3659
  id: "anthropic/claude-opus-4",
3719
3660
  displayName: "Claude Opus 4",
@@ -3732,26 +3673,12 @@ var OpenRouterProvider = class extends OpenAICompatibleProvider {
3732
3673
  contextWindow: 2e5,
3733
3674
  supportsStreaming: true
3734
3675
  },
3735
- // OpenAI 系列
3736
3676
  {
3737
3677
  id: "openai/gpt-5.4",
3738
3678
  displayName: "GPT-5.4",
3739
3679
  contextWindow: 105e4,
3740
3680
  supportsStreaming: true
3741
3681
  },
3742
- {
3743
- id: "openai/gpt-4o",
3744
- displayName: "GPT-4o",
3745
- contextWindow: 128e3,
3746
- supportsStreaming: true
3747
- },
3748
- {
3749
- id: "openai/gpt-4o-mini",
3750
- displayName: "GPT-4o Mini",
3751
- contextWindow: 128e3,
3752
- supportsStreaming: true
3753
- },
3754
- // Google Gemini 系列
3755
3682
  {
3756
3683
  id: "google/gemini-2.5-pro",
3757
3684
  displayName: "Gemini 2.5 Pro",
@@ -3764,7 +3691,6 @@ var OpenRouterProvider = class extends OpenAICompatibleProvider {
3764
3691
  contextWindow: 1048576,
3765
3692
  supportsStreaming: true
3766
3693
  },
3767
- // Meta Llama 系列
3768
3694
  {
3769
3695
  id: "meta-llama/llama-4-maverick",
3770
3696
  displayName: "Llama 4 Maverick",
@@ -3777,44 +3703,17 @@ var OpenRouterProvider = class extends OpenAICompatibleProvider {
3777
3703
  contextWindow: 512e3,
3778
3704
  supportsStreaming: true
3779
3705
  },
3780
- // DeepSeek 系列
3781
- {
3782
- id: "deepseek/deepseek-chat-v3-0324",
3783
- displayName: "DeepSeek V3 0324",
3784
- contextWindow: 131072,
3785
- supportsStreaming: true
3786
- },
3787
- {
3788
- id: "deepseek/deepseek-r1",
3789
- displayName: "DeepSeek R1",
3790
- contextWindow: 163840,
3791
- supportsStreaming: true
3792
- },
3793
- // Qwen 系列
3794
3706
  {
3795
3707
  id: "qwen/qwen3-coder",
3796
3708
  displayName: "Qwen3 Coder",
3797
3709
  contextWindow: 262144,
3798
3710
  supportsStreaming: true
3799
3711
  },
3800
- {
3801
- id: "qwen/qwen-2.5-72b-instruct",
3802
- displayName: "Qwen 2.5 72B",
3803
- contextWindow: 131072,
3804
- supportsStreaming: true
3805
- },
3806
- // Mistral 系列
3807
3712
  {
3808
3713
  id: "mistralai/mistral-large",
3809
3714
  displayName: "Mistral Large",
3810
3715
  contextWindow: 131072,
3811
3716
  supportsStreaming: true
3812
- },
3813
- {
3814
- id: "mistralai/mistral-small-3.1-24b-instruct",
3815
- displayName: "Mistral Small 3.1 24B",
3816
- contextWindow: 128e3,
3817
- supportsStreaming: true
3818
3717
  }
3819
3718
  ]
3820
3719
  };
@@ -3899,6 +3798,10 @@ var OllamaProvider = class extends OpenAICompatibleProvider {
3899
3798
  await this.tryConnect();
3900
3799
  return this.dynamicModels.length > 0 ? this.dynamicModels : this.info.models;
3901
3800
  }
3801
+ async refreshModels() {
3802
+ await this.tryConnect(false);
3803
+ return this.dynamicModels.length > 0 ? this.dynamicModels : this.info.models;
3804
+ }
3902
3805
  /** Ollama 无需验证 API Key,只检查服务是否可达 */
3903
3806
  async validateApiKey() {
3904
3807
  return this.tryConnect();
@@ -3939,6 +3842,111 @@ var CustomProvider = class extends OpenAICompatibleProvider {
3939
3842
  }
3940
3843
  };
3941
3844
 
3845
+ // src/providers/model-cache.ts
3846
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync } from "fs";
3847
+ import { dirname, join as join2 } from "path";
3848
+ var CACHE_FILE_NAME = "model-cache.json";
3849
+ var LIVE_DEFAULT_CONTEXT = 128e3;
3850
+ function modelCachePath(configDir) {
3851
+ return join2(configDir, CACHE_FILE_NAME);
3852
+ }
3853
+ function loadModelCache(configDir) {
3854
+ const path3 = modelCachePath(configDir);
3855
+ if (!existsSync2(path3)) return { version: 1, providers: {} };
3856
+ try {
3857
+ const parsed = JSON.parse(readFileSync2(path3, "utf-8"));
3858
+ if (parsed.version !== 1 || !parsed.providers || typeof parsed.providers !== "object") {
3859
+ return { version: 1, providers: {} };
3860
+ }
3861
+ return { version: 1, providers: parsed.providers };
3862
+ } catch {
3863
+ return { version: 1, providers: {} };
3864
+ }
3865
+ }
3866
+ function saveModelCache(configDir, cache) {
3867
+ const path3 = modelCachePath(configDir);
3868
+ mkdirSync2(dirname(path3), { recursive: true });
3869
+ atomicWriteFileSync(path3, `${JSON.stringify(cache, null, 2)}
3870
+ `);
3871
+ }
3872
+ function getCachedModels(configDir, providerId) {
3873
+ const entry = loadModelCache(configDir).providers[providerId];
3874
+ if (!entry) return [];
3875
+ return normalizeLiveModels(entry.models, entry.fetchedAt, "cache");
3876
+ }
3877
+ function getModelCacheSummary(configDir) {
3878
+ const cache = loadModelCache(configDir);
3879
+ return Object.entries(cache.providers).map(([providerId, entry]) => ({
3880
+ providerId,
3881
+ count: entry.models.length,
3882
+ fetchedAt: entry.fetchedAt,
3883
+ baseUrl: entry.baseUrl
3884
+ }));
3885
+ }
3886
+ function setCachedModels(configDir, providerId, models, baseUrl) {
3887
+ const cache = loadModelCache(configDir);
3888
+ const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
3889
+ const entry = {
3890
+ fetchedAt,
3891
+ ...baseUrl ? { baseUrl } : {},
3892
+ models: normalizeLiveModels(models, fetchedAt, "live"),
3893
+ lastError: null
3894
+ };
3895
+ cache.providers[providerId] = entry;
3896
+ saveModelCache(configDir, cache);
3897
+ return entry;
3898
+ }
3899
+ function clearModelCache(configDir, providerId) {
3900
+ const path3 = modelCachePath(configDir);
3901
+ if (!providerId) {
3902
+ if (existsSync2(path3)) unlinkSync(path3);
3903
+ return;
3904
+ }
3905
+ const cache = loadModelCache(configDir);
3906
+ delete cache.providers[providerId];
3907
+ saveModelCache(configDir, cache);
3908
+ }
3909
+ function normalizeLiveModels(models, fetchedAt, source = "live") {
3910
+ const seen = /* @__PURE__ */ new Set();
3911
+ const normalized = [];
3912
+ for (const model of models) {
3913
+ if (!model.id || seen.has(model.id)) continue;
3914
+ seen.add(model.id);
3915
+ normalized.push({
3916
+ id: model.id,
3917
+ displayName: model.displayName || model.id,
3918
+ contextWindow: model.contextWindow || LIVE_DEFAULT_CONTEXT,
3919
+ supportsStreaming: model.supportsStreaming ?? true,
3920
+ ...model.supportsThinking !== void 0 ? { supportsThinking: model.supportsThinking } : {},
3921
+ source,
3922
+ available: true,
3923
+ metadataUnknown: model.metadataUnknown ?? !model.contextWindow,
3924
+ ...fetchedAt ? { fetchedAt } : {}
3925
+ });
3926
+ }
3927
+ return normalized;
3928
+ }
3929
+ function mergeModelLists(curatedModels, liveModels) {
3930
+ const liveById = new Map(liveModels.map((model) => [model.id, model]));
3931
+ const merged = curatedModels.map((model) => ({
3932
+ ...model,
3933
+ source: liveById.has(model.id) ? "curated+live" : model.source ?? "curated",
3934
+ available: liveById.has(model.id) ? true : model.available ?? "unknown",
3935
+ fetchedAt: liveById.get(model.id)?.fetchedAt
3936
+ }));
3937
+ const curatedIds = new Set(curatedModels.map((model) => model.id));
3938
+ for (const model of liveModels) {
3939
+ if (curatedIds.has(model.id)) continue;
3940
+ merged.push({
3941
+ ...model,
3942
+ source: model.source ?? "live",
3943
+ available: true,
3944
+ metadataUnknown: model.metadataUnknown ?? true
3945
+ });
3946
+ }
3947
+ return merged;
3948
+ }
3949
+
3942
3950
  // src/providers/registry.ts
3943
3951
  var BUILT_IN_PROVIDERS = [
3944
3952
  ClaudeProvider,
@@ -3947,15 +3955,17 @@ var BUILT_IN_PROVIDERS = [
3947
3955
  ZhipuProvider,
3948
3956
  KimiProvider,
3949
3957
  MiniMaxProvider,
3958
+ QwenProvider,
3950
3959
  OpenAIProvider,
3951
3960
  OpenRouterProvider,
3952
3961
  OllamaProvider
3953
3962
  ];
3954
3963
  var ProviderRegistry = class {
3955
3964
  providers = /* @__PURE__ */ new Map();
3956
- // 记录自定义 provider 的状态(用于 listAll 展示)
3957
3965
  customConfigs = [];
3958
- async initialize(getApiKey, getOptions, customProviderConfigs) {
3966
+ configDir;
3967
+ async initialize(getApiKey, getOptions, customProviderConfigs, configDir) {
3968
+ this.configDir = configDir;
3959
3969
  for (const ProviderClass of BUILT_IN_PROVIDERS) {
3960
3970
  const provider = new ProviderClass();
3961
3971
  const apiKey = getApiKey(provider.info.id);
@@ -3982,11 +3992,6 @@ var ProviderRegistry = class {
3982
3992
  this.providers.set(cfg.id, provider);
3983
3993
  }
3984
3994
  }
3985
- /**
3986
- * Register a provider instance under the given id (overwrites any existing).
3987
- * Primary uses: integration tests injecting MockProvider, and future hot-swap
3988
- * scenarios. The provider is assumed already initialized.
3989
- */
3990
3995
  register(id, provider) {
3991
3996
  this.providers.set(id, provider);
3992
3997
  }
@@ -4000,6 +4005,37 @@ var ProviderRegistry = class {
4000
4005
  has(id) {
4001
4006
  return this.providers.has(id);
4002
4007
  }
4008
+ async listModels(providerId) {
4009
+ const provider = this.get(providerId);
4010
+ const curated = await provider.listModels();
4011
+ if (!this.configDir) return curated;
4012
+ const cached = getCachedModels(this.configDir, providerId);
4013
+ return cached.length > 0 ? mergeModelLists(curated, cached) : curated;
4014
+ }
4015
+ async refreshModels(providerId) {
4016
+ const provider = this.get(providerId);
4017
+ if (!provider.refreshModels) {
4018
+ throw new Error(`Provider ${providerId} does not support model refresh`);
4019
+ }
4020
+ const liveModels = await provider.refreshModels();
4021
+ if (!this.configDir) {
4022
+ const merged2 = mergeModelLists(await provider.listModels(), liveModels);
4023
+ provider.info.models = merged2;
4024
+ return merged2;
4025
+ }
4026
+ setCachedModels(this.configDir, providerId, liveModels, provider.info.baseUrl);
4027
+ const merged = await this.listModels(providerId);
4028
+ provider.info.models = merged;
4029
+ return merged;
4030
+ }
4031
+ getModelCacheStatus() {
4032
+ if (!this.configDir) return [];
4033
+ return getModelCacheSummary(this.configDir);
4034
+ }
4035
+ clearModelCache(providerId) {
4036
+ if (!this.configDir) return;
4037
+ clearModelCache(this.configDir, providerId);
4038
+ }
4003
4039
  listAvailable() {
4004
4040
  return [...this.providers.values()];
4005
4041
  }
@@ -4029,8 +4065,8 @@ var ProviderRegistry = class {
4029
4065
  };
4030
4066
 
4031
4067
  // src/session/session-manager.ts
4032
- import { readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, unlinkSync, openSync, readSync, closeSync } from "fs";
4033
- import { join as join2 } from "path";
4068
+ import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, unlinkSync as unlinkSync2, openSync, readSync, closeSync } from "fs";
4069
+ import { join as join3 } from "path";
4034
4070
  import { v4 as uuidv4 } from "uuid";
4035
4071
 
4036
4072
  // src/core/types.ts
@@ -4609,8 +4645,8 @@ var SessionManager = class {
4609
4645
  }
4610
4646
  async save() {
4611
4647
  if (!this._current) return;
4612
- mkdirSync2(this.historyDir, { recursive: true });
4613
- const filePath = join2(this.historyDir, `${this._current.id}.json`);
4648
+ mkdirSync3(this.historyDir, { recursive: true });
4649
+ const filePath = join3(this.historyDir, `${this._current.id}.json`);
4614
4650
  const raw = this._current.toJSON();
4615
4651
  const opts = this.redactOptionsForSave();
4616
4652
  const { value: payload, hits } = redactJson(raw, opts);
@@ -4618,13 +4654,13 @@ var SessionManager = class {
4618
4654
  atomicWriteFileSync(filePath, JSON.stringify(payload, null, 2));
4619
4655
  }
4620
4656
  loadSession(id) {
4621
- const filePath = join2(this.historyDir, `${id}.json`);
4622
- if (!existsSync2(filePath)) {
4657
+ const filePath = join3(this.historyDir, `${id}.json`);
4658
+ if (!existsSync3(filePath)) {
4623
4659
  throw new Error(`Session ${id} not found`);
4624
4660
  }
4625
4661
  let data;
4626
4662
  try {
4627
- data = JSON.parse(readFileSync2(filePath, "utf-8"));
4663
+ data = JSON.parse(readFileSync3(filePath, "utf-8"));
4628
4664
  } catch (err) {
4629
4665
  throw new Error(`Session ${id} is corrupted: ${err instanceof Error ? err.message : String(err)}`);
4630
4666
  }
@@ -4633,12 +4669,12 @@ var SessionManager = class {
4633
4669
  return session;
4634
4670
  }
4635
4671
  listSessions() {
4636
- if (!existsSync2(this.historyDir)) return [];
4672
+ if (!existsSync3(this.historyDir)) return [];
4637
4673
  const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json"));
4638
4674
  const metas = [];
4639
4675
  for (const file of files) {
4640
4676
  try {
4641
- const meta = this.readSessionMeta(join2(this.historyDir, file));
4677
+ const meta = this.readSessionMeta(join3(this.historyDir, file));
4642
4678
  if (meta) metas.push(meta);
4643
4679
  } catch (err) {
4644
4680
  process.stderr.write(
@@ -4678,7 +4714,7 @@ var SessionManager = class {
4678
4714
  if (messageCount === void 0) {
4679
4715
  messageCount = 0;
4680
4716
  try {
4681
- const full = readFileSync2(filePath, "utf-8");
4717
+ const full = readFileSync3(filePath, "utf-8");
4682
4718
  const matches = full.match(/"role"\s*:/g);
4683
4719
  messageCount = matches ? matches.length : 0;
4684
4720
  } catch {
@@ -4694,7 +4730,7 @@ var SessionManager = class {
4694
4730
  title: title || void 0
4695
4731
  };
4696
4732
  }
4697
- const data = JSON.parse(readFileSync2(filePath, "utf-8"));
4733
+ const data = JSON.parse(readFileSync3(filePath, "utf-8"));
4698
4734
  return {
4699
4735
  id: data.id,
4700
4736
  provider: data.provider,
@@ -4706,10 +4742,10 @@ var SessionManager = class {
4706
4742
  };
4707
4743
  }
4708
4744
  deleteSession(id) {
4709
- const filePath = join2(this.historyDir, `${id}.json`);
4710
- if (!existsSync2(filePath)) return false;
4745
+ const filePath = join3(this.historyDir, `${id}.json`);
4746
+ if (!existsSync3(filePath)) return false;
4711
4747
  try {
4712
- unlinkSync(filePath);
4748
+ unlinkSync2(filePath);
4713
4749
  if (this._current && this._current.id === id) {
4714
4750
  this._current = null;
4715
4751
  }
@@ -4744,14 +4780,14 @@ var SessionManager = class {
4744
4780
  * 每个 session 最多返回 3 条匹配片段,全局最多 maxResults 个 session。
4745
4781
  */
4746
4782
  searchMessages(query, maxResults = 20) {
4747
- if (!existsSync2(this.historyDir)) return [];
4783
+ if (!existsSync3(this.historyDir)) return [];
4748
4784
  const q = query.toLowerCase();
4749
- const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join2(this.historyDir, f));
4785
+ const files = readdirSync(this.historyDir).filter((f) => f.endsWith(".json")).map((f) => join3(this.historyDir, f));
4750
4786
  const results = [];
4751
4787
  for (const filePath of files) {
4752
4788
  if (results.length >= maxResults) break;
4753
4789
  try {
4754
- const data = JSON.parse(readFileSync2(filePath, "utf-8"));
4790
+ const data = JSON.parse(readFileSync3(filePath, "utf-8"));
4755
4791
  const messages = data.messages ?? [];
4756
4792
  const matches = [];
4757
4793
  for (const msg of messages) {
@@ -4798,12 +4834,12 @@ var SessionManager = class {
4798
4834
 
4799
4835
  // src/tools/builtin/bash.ts
4800
4836
  import { spawn } from "child_process";
4801
- import { existsSync as existsSync4, readdirSync as readdirSync2, statSync } from "fs";
4837
+ import { existsSync as existsSync5, readdirSync as readdirSync2, statSync } from "fs";
4802
4838
  import { platform as platform2 } from "os";
4803
4839
  import { resolve } from "path";
4804
4840
 
4805
4841
  // src/tools/undo-stack.ts
4806
- import { readFileSync as readFileSync3, writeFileSync, unlinkSync as unlinkSync2, rmdirSync, existsSync as existsSync3 } from "fs";
4842
+ import { readFileSync as readFileSync4, writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync4 } from "fs";
4807
4843
  var MAX_UNDO_DEPTH = 20;
4808
4844
  var UndoStack = class {
4809
4845
  stack = [];
@@ -4814,9 +4850,9 @@ var UndoStack = class {
4814
4850
  */
4815
4851
  push(filePath, description) {
4816
4852
  let previousContent = null;
4817
- if (existsSync3(filePath)) {
4853
+ if (existsSync4(filePath)) {
4818
4854
  try {
4819
- previousContent = readFileSync3(filePath, "utf-8");
4855
+ previousContent = readFileSync4(filePath, "utf-8");
4820
4856
  } catch {
4821
4857
  return;
4822
4858
  }
@@ -4870,7 +4906,7 @@ var UndoStack = class {
4870
4906
  try {
4871
4907
  if (entry.previousContent === null) {
4872
4908
  if (entry.isDirectory) {
4873
- if (existsSync3(entry.filePath)) {
4909
+ if (existsSync4(entry.filePath)) {
4874
4910
  try {
4875
4911
  rmdirSync(entry.filePath);
4876
4912
  return { entry, result: `Removed newly created directory: ${entry.filePath}` };
@@ -4880,8 +4916,8 @@ var UndoStack = class {
4880
4916
  }
4881
4917
  return { entry, result: `Directory already removed: ${entry.filePath}` };
4882
4918
  } else {
4883
- if (existsSync3(entry.filePath)) {
4884
- unlinkSync2(entry.filePath);
4919
+ if (existsSync4(entry.filePath)) {
4920
+ unlinkSync3(entry.filePath);
4885
4921
  }
4886
4922
  return { entry, result: `Deleted newly created file: ${entry.filePath}` };
4887
4923
  }
@@ -5025,7 +5061,7 @@ Important rules:
5025
5061
  throw new ToolError("bash", blockingHint);
5026
5062
  }
5027
5063
  let currentCwd = getCwd();
5028
- if (!existsSync4(currentCwd)) {
5064
+ if (!existsSync5(currentCwd)) {
5029
5065
  const fallback = process.cwd();
5030
5066
  process.stderr.write(
5031
5067
  `[bash] Previous cwd "${currentCwd}" no longer exists, reset to "${fallback}"
@@ -5037,7 +5073,7 @@ Important rules:
5037
5073
  let effectiveCwd = currentCwd;
5038
5074
  if (cwdArg) {
5039
5075
  const resolved = resolve(currentCwd, cwdArg);
5040
- if (!existsSync4(resolved)) {
5076
+ if (!existsSync5(resolved)) {
5041
5077
  throw new ToolError(
5042
5078
  "bash",
5043
5079
  `cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
@@ -5057,7 +5093,7 @@ Important rules:
5057
5093
  const parsedTargets = parseCreationTargets(command, effectiveCwd);
5058
5094
  const parsedTargetsBefore = /* @__PURE__ */ new Map();
5059
5095
  for (const t of parsedTargets) {
5060
- parsedTargetsBefore.set(t, existsSync4(t));
5096
+ parsedTargetsBefore.set(t, existsSync5(t));
5061
5097
  }
5062
5098
  try {
5063
5099
  const { stdout, stderr, status, signal, timedOut, aborted } = await runShell(
@@ -5285,7 +5321,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
5285
5321
  }
5286
5322
  }
5287
5323
  for (const [target, existedBefore] of parsedTargetsBefore) {
5288
- if (!existedBefore && !tracked.has(target) && existsSync4(target)) {
5324
+ if (!existedBefore && !tracked.has(target) && existsSync5(target)) {
5289
5325
  try {
5290
5326
  const st = statSync(target);
5291
5327
  if (st.isDirectory()) {
@@ -5398,7 +5434,7 @@ function updateCwdFromCommand(command, baseCwd) {
5398
5434
  if (!target || target.startsWith("$") || target === "~") return;
5399
5435
  try {
5400
5436
  const newDir = resolve(baseCwd, target);
5401
- if (existsSync4(newDir)) {
5437
+ if (existsSync5(newDir)) {
5402
5438
  setCwd(newDir);
5403
5439
  }
5404
5440
  } catch {
@@ -5406,9 +5442,9 @@ function updateCwdFromCommand(command, baseCwd) {
5406
5442
  }
5407
5443
 
5408
5444
  // src/tools/builtin/read-file.ts
5409
- import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync2, readdirSync as readdirSync3 } from "fs";
5445
+ import { readFileSync as readFileSync5, existsSync as existsSync6, statSync as statSync2, readdirSync as readdirSync3 } from "fs";
5410
5446
  import { execFileSync } from "child_process";
5411
- import { extname, resolve as resolve2, basename, sep, dirname } from "path";
5447
+ import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
5412
5448
  import { homedir as homedir2 } from "os";
5413
5449
 
5414
5450
  // src/tools/builtin/notebook-utils.ts
@@ -5683,7 +5719,7 @@ var readFileTool = {
5683
5719
  const normalizedPath = resolve2(filePath);
5684
5720
  const refusal = getHardRefusal(normalizedPath);
5685
5721
  if (refusal) return refusal;
5686
- if (!existsSync5(normalizedPath)) {
5722
+ if (!existsSync6(normalizedPath)) {
5687
5723
  const suggestions = findSimilarFiles(filePath);
5688
5724
  if (suggestions.length > 0) {
5689
5725
  throw new ToolError(
@@ -5722,9 +5758,9 @@ Use the bash tool to read in segments, e.g.:
5722
5758
 
5723
5759
  ${pdfText}`;
5724
5760
  }
5725
- const dir = dirname(normalizedPath);
5761
+ const dir = dirname2(normalizedPath);
5726
5762
  const nameNoExt = basename(normalizedPath, ext);
5727
- const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(existsSync5);
5763
+ const textAlts = [".md", ".txt", ".html"].map((e) => resolve2(dir, nameNoExt + e)).filter(existsSync6);
5728
5764
  if (textAlts.length > 0) {
5729
5765
  return `[PDF file: ${filePath}]
5730
5766
  Cannot extract text from this PDF, but found alternative text versions:
@@ -5737,7 +5773,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
5737
5773
  }
5738
5774
  if (ext === ".ipynb") {
5739
5775
  try {
5740
- const raw = readFileSync4(normalizedPath, "utf-8");
5776
+ const raw = readFileSync5(normalizedPath, "utf-8");
5741
5777
  const nb = parseNotebook(raw);
5742
5778
  return renderNotebookAsText(nb, filePath);
5743
5779
  } catch (err) {
@@ -5750,7 +5786,7 @@ ${err.message}`;
5750
5786
  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.`;
5751
5787
  }
5752
5788
  const { readFile: readFile2 } = await import("fs/promises");
5753
- const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync4(normalizedPath);
5789
+ const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync5(normalizedPath);
5754
5790
  if (encoding === "base64") {
5755
5791
  return `[File: ${filePath} | base64]
5756
5792
 
@@ -5788,12 +5824,12 @@ ${content}`;
5788
5824
  };
5789
5825
 
5790
5826
  // src/tools/builtin/write-file.ts
5791
- import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
5792
- import { dirname as dirname3 } from "path";
5827
+ import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
5828
+ import { dirname as dirname4 } from "path";
5793
5829
 
5794
5830
  // src/tools/executor.ts
5795
5831
  import chalk3 from "chalk";
5796
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
5832
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
5797
5833
  import { tmpdir } from "os";
5798
5834
 
5799
5835
  // src/core/readline-internal.ts
@@ -6002,8 +6038,8 @@ function simpleDiff(oldLines, newLines) {
6002
6038
  // src/tools/hooks.ts
6003
6039
  import { execSync } from "child_process";
6004
6040
  import { createHash } from "crypto";
6005
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "fs";
6006
- import { join as join3 } from "path";
6041
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
6042
+ import { join as join4 } from "path";
6007
6043
  var TRUST_FILE = "hooks-trust.json";
6008
6044
  var DEFAULT_TIMEOUT_MS = 5e3;
6009
6045
  function asArray(entry) {
@@ -6012,13 +6048,13 @@ function asArray(entry) {
6012
6048
  return raw.map((item) => typeof item === "string" ? { command: item } : item);
6013
6049
  }
6014
6050
  function hookTrustPath(configDir) {
6015
- return join3(configDir, TRUST_FILE);
6051
+ return join4(configDir, TRUST_FILE);
6016
6052
  }
6017
6053
  function loadHookTrustStore(configDir) {
6018
6054
  const file = hookTrustPath(configDir);
6019
- if (!existsSync6(file)) return { records: [] };
6055
+ if (!existsSync7(file)) return { records: [] };
6020
6056
  try {
6021
- const parsed = JSON.parse(readFileSync5(file, "utf-8"));
6057
+ const parsed = JSON.parse(readFileSync6(file, "utf-8"));
6022
6058
  return { records: Array.isArray(parsed.records) ? parsed.records : [] };
6023
6059
  } catch {
6024
6060
  return { records: [] };
@@ -6544,8 +6580,8 @@ var theme = new Proxy(DARK_THEME, {
6544
6580
  });
6545
6581
 
6546
6582
  // src/diagnostics/tool-stats.ts
6547
- import { existsSync as existsSync7, readFileSync as readFileSync6, mkdirSync as mkdirSync4 } from "fs";
6548
- import { join as join4, dirname as dirname2 } from "path";
6583
+ import { existsSync as existsSync8, readFileSync as readFileSync7, mkdirSync as mkdirSync5 } from "fs";
6584
+ import { join as join5, dirname as dirname3 } from "path";
6549
6585
  import { homedir as homedir3 } from "os";
6550
6586
  var STATS_FILE_NAME = "tool-stats.json";
6551
6587
  var STATS_VERSION = 1;
@@ -6555,16 +6591,16 @@ var dirty = false;
6555
6591
  var pendingWrites = 0;
6556
6592
  var configDirOverride;
6557
6593
  function statsFilePath() {
6558
- const base = configDirOverride ?? join4(homedir3(), CONFIG_DIR_NAME);
6559
- return join4(base, STATS_FILE_NAME);
6594
+ const base = configDirOverride ?? join5(homedir3(), CONFIG_DIR_NAME);
6595
+ return join5(base, STATS_FILE_NAME);
6560
6596
  }
6561
6597
  function load() {
6562
6598
  const path3 = statsFilePath();
6563
- if (!existsSync7(path3)) {
6599
+ if (!existsSync8(path3)) {
6564
6600
  return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
6565
6601
  }
6566
6602
  try {
6567
- const raw = JSON.parse(readFileSync6(path3, "utf-8"));
6603
+ const raw = JSON.parse(readFileSync7(path3, "utf-8"));
6568
6604
  if (raw.version !== STATS_VERSION || !raw.entries) {
6569
6605
  return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
6570
6606
  }
@@ -6622,17 +6658,32 @@ function flush() {
6622
6658
  if (!dirty || state === null) return;
6623
6659
  const path3 = statsFilePath();
6624
6660
  try {
6625
- mkdirSync4(dirname2(path3), { recursive: true });
6661
+ mkdirSync5(dirname3(path3), { recursive: true });
6626
6662
  atomicWriteFileSync(path3, JSON.stringify(state, null, 2));
6627
6663
  dirty = false;
6628
6664
  pendingWrites = 0;
6629
6665
  } catch {
6630
6666
  }
6631
6667
  }
6668
+ function getStatsSnapshot() {
6669
+ const s = ensureLoaded();
6670
+ return Object.values(s.entries).map((e) => ({ ...e }));
6671
+ }
6672
+ function getTopFailingTools(limit = 5) {
6673
+ return getStatsSnapshot().filter((e) => e.failures > 0).sort((a, b) => {
6674
+ const rateA = a.failures / Math.max(1, a.calls);
6675
+ const rateB = b.failures / Math.max(1, b.calls);
6676
+ if (rateB !== rateA) return rateB - rateA;
6677
+ return b.failures - a.failures;
6678
+ }).slice(0, limit);
6679
+ }
6680
+ function getTopUsedTools(limit = 5) {
6681
+ return getStatsSnapshot().sort((a, b) => b.calls - a.calls).slice(0, limit);
6682
+ }
6632
6683
 
6633
6684
  // src/tools/action-classifier.ts
6634
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
6635
- import { isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "path";
6685
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
6686
+ import { isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "path";
6636
6687
  var RECENT_DENIED_LIMIT = 50;
6637
6688
  var recentlyDenied = [];
6638
6689
  function recordRecentlyDeniedAutoAction(call, classification) {
@@ -6673,10 +6724,10 @@ function splitShellWords(command) {
6673
6724
  return out.filter(Boolean);
6674
6725
  }
6675
6726
  function readPackageManifest(root) {
6676
- const p = join5(root, "package.json");
6677
- if (!existsSync8(p)) return null;
6727
+ const p = join6(root, "package.json");
6728
+ if (!existsSync9(p)) return null;
6678
6729
  try {
6679
- return JSON.parse(readFileSync7(p, "utf-8"));
6730
+ return JSON.parse(readFileSync8(p, "utf-8"));
6680
6731
  } catch {
6681
6732
  return null;
6682
6733
  }
@@ -6692,10 +6743,10 @@ function manifestHasDependency(root, name) {
6692
6743
  }
6693
6744
  function lockfileMentions(root, name) {
6694
6745
  for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
6695
- const p = join5(root, file);
6696
- if (!existsSync8(p)) continue;
6746
+ const p = join6(root, file);
6747
+ if (!existsSync9(p)) continue;
6697
6748
  try {
6698
- if (readFileSync7(p, "utf-8").includes(name)) return true;
6749
+ if (readFileSync8(p, "utf-8").includes(name)) return true;
6699
6750
  } catch {
6700
6751
  }
6701
6752
  }
@@ -7259,10 +7310,10 @@ var ToolExecutor = class {
7259
7310
  const filePath = String(call.arguments["path"] ?? "");
7260
7311
  const newContent = String(call.arguments["content"] ?? "");
7261
7312
  if (!filePath) return;
7262
- if (existsSync9(filePath)) {
7313
+ if (existsSync10(filePath)) {
7263
7314
  let oldContent;
7264
7315
  try {
7265
- oldContent = readFileSync8(filePath, "utf-8");
7316
+ oldContent = readFileSync9(filePath, "utf-8");
7266
7317
  } catch {
7267
7318
  return;
7268
7319
  }
@@ -7285,7 +7336,7 @@ var ToolExecutor = class {
7285
7336
  }
7286
7337
  } else if (call.name === "edit_file") {
7287
7338
  const filePath = String(call.arguments["path"] ?? "");
7288
- if (!filePath || !existsSync9(filePath)) return;
7339
+ if (!filePath || !existsSync10(filePath)) return;
7289
7340
  const oldStr = call.arguments["old_str"];
7290
7341
  const newStr = call.arguments["new_str"];
7291
7342
  if (oldStr !== void 0) {
@@ -7312,7 +7363,7 @@ var ToolExecutor = class {
7312
7363
  const to = Number(call.arguments["delete_to_line"] ?? from);
7313
7364
  let fileContent;
7314
7365
  try {
7315
- fileContent = readFileSync8(filePath, "utf-8");
7366
+ fileContent = readFileSync9(filePath, "utf-8");
7316
7367
  } catch {
7317
7368
  return;
7318
7369
  }
@@ -7547,7 +7598,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
7547
7598
  }
7548
7599
  undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
7549
7600
  fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
7550
- mkdirSync5(dirname3(filePath), { recursive: true });
7601
+ mkdirSync6(dirname4(filePath), { recursive: true });
7551
7602
  if (appendMode) {
7552
7603
  appendFileSync(filePath, content, encoding);
7553
7604
  } else {
@@ -7567,7 +7618,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
7567
7618
  };
7568
7619
 
7569
7620
  // src/tools/builtin/edit-file.ts
7570
- import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
7621
+ import { readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
7571
7622
 
7572
7623
  // src/tools/builtin/patch-apply.ts
7573
7624
  function parseUnifiedDiff(patch) {
@@ -7928,7 +7979,7 @@ Note: Path can be absolute or relative to cwd.`,
7928
7979
  const filePath = String(args["path"] ?? "");
7929
7980
  const encoding = args["encoding"] ?? "utf-8";
7930
7981
  if (!filePath) throw new ToolError("edit_file", "path is required");
7931
- if (!existsSync10(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
7982
+ if (!existsSync11(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
7932
7983
  const verdict = classifyWritePath(filePath);
7933
7984
  if (verdict.sensitive && subAgentGuard.active) {
7934
7985
  throw new ToolError(
@@ -7936,7 +7987,7 @@ Note: Path can be absolute or relative to cwd.`,
7936
7987
  `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.`
7937
7988
  );
7938
7989
  }
7939
- const original = readFileSync9(filePath, encoding);
7990
+ const original = readFileSync10(filePath, encoding);
7940
7991
  if (args["patch"] !== void 0) {
7941
7992
  const patchText = String(args["patch"] ?? "");
7942
7993
  const stopOnError = args["stop_on_error"] !== false;
@@ -8094,8 +8145,8 @@ function truncatePreview(str, maxLen = 80) {
8094
8145
  }
8095
8146
 
8096
8147
  // src/tools/builtin/list-dir.ts
8097
- import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync11 } from "fs";
8098
- import { join as join6, basename as basename3 } from "path";
8148
+ import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync12 } from "fs";
8149
+ import { join as join7, basename as basename3 } from "path";
8099
8150
  var listDirTool = {
8100
8151
  definition: {
8101
8152
  name: "list_dir",
@@ -8117,7 +8168,7 @@ var listDirTool = {
8117
8168
  async execute(args) {
8118
8169
  const dirPath = String(args["path"] ?? process.cwd());
8119
8170
  const recursive = Boolean(args["recursive"] ?? false);
8120
- if (!existsSync11(dirPath)) {
8171
+ if (!existsSync12(dirPath)) {
8121
8172
  const targetName = basename3(dirPath).toLowerCase();
8122
8173
  const cwd = process.cwd();
8123
8174
  const suggestions = [];
@@ -8175,11 +8226,11 @@ function listRecursive(basePath, indent, recursive, lines) {
8175
8226
  if (entry.isDirectory()) {
8176
8227
  lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
8177
8228
  if (recursive) {
8178
- listRecursive(join6(basePath, entry.name), indent + " ", true, lines);
8229
+ listRecursive(join7(basePath, entry.name), indent + " ", true, lines);
8179
8230
  }
8180
8231
  } else {
8181
8232
  try {
8182
- const stat = statSync3(join6(basePath, entry.name));
8233
+ const stat = statSync3(join7(basePath, entry.name));
8183
8234
  const size = formatSize(stat.size);
8184
8235
  lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
8185
8236
  } catch {
@@ -8195,9 +8246,9 @@ function formatSize(bytes) {
8195
8246
  }
8196
8247
 
8197
8248
  // src/tools/builtin/grep-files.ts
8198
- import { readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync4, existsSync as existsSync12 } from "fs";
8249
+ import { readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync4, existsSync as existsSync13 } from "fs";
8199
8250
  import { readFile } from "fs/promises";
8200
- import { join as join7, relative } from "path";
8251
+ import { join as join8, relative } from "path";
8201
8252
  var grepFilesTool = {
8202
8253
  definition: {
8203
8254
  name: "grep_files",
@@ -8248,7 +8299,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
8248
8299
  const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
8249
8300
  const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
8250
8301
  if (!pattern) throw new ToolError("grep_files", "pattern is required");
8251
- if (!existsSync12(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
8302
+ if (!existsSync13(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
8252
8303
  const MAX_PATTERN_LENGTH = 1e3;
8253
8304
  if (pattern.length > MAX_PATTERN_LENGTH) {
8254
8305
  throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
@@ -8342,11 +8393,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
8342
8393
  if (paths.length >= maxFiles) return;
8343
8394
  if (entry.isDirectory()) {
8344
8395
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
8345
- walk(join7(dirPath, entry.name));
8396
+ walk(join8(dirPath, entry.name));
8346
8397
  } else if (entry.isFile()) {
8347
8398
  if (isBinary(entry.name)) continue;
8348
8399
  if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
8349
- const fullPath = join7(dirPath, entry.name);
8400
+ const fullPath = join8(dirPath, entry.name);
8350
8401
  try {
8351
8402
  if (statSync4(fullPath).size > 1e6) continue;
8352
8403
  } catch {
@@ -8401,7 +8452,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
8401
8452
  }
8402
8453
  let content;
8403
8454
  try {
8404
- content = readFileSync10(fullPath, "utf-8");
8455
+ content = readFileSync11(fullPath, "utf-8");
8405
8456
  } catch {
8406
8457
  return;
8407
8458
  }
@@ -8432,8 +8483,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
8432
8483
  }
8433
8484
 
8434
8485
  // src/tools/builtin/glob-files.ts
8435
- import { readdirSync as readdirSync6, statSync as statSync5, existsSync as existsSync13 } from "fs";
8436
- import { join as join8, relative as relative2, basename as basename4 } from "path";
8486
+ import { readdirSync as readdirSync6, statSync as statSync5, existsSync as existsSync14 } from "fs";
8487
+ import { join as join9, relative as relative2, basename as basename4 } from "path";
8437
8488
  var globFilesTool = {
8438
8489
  definition: {
8439
8490
  name: "glob_files",
@@ -8466,7 +8517,7 @@ Results sorted by most recent modification time. Automatically skips node_module
8466
8517
  const rootPath = String(args["path"] ?? process.cwd());
8467
8518
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
8468
8519
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
8469
- if (!existsSync13(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
8520
+ if (!existsSync14(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
8470
8521
  const regex = globToRegex(pattern);
8471
8522
  const matches = [];
8472
8523
  collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
@@ -8543,7 +8594,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
8543
8594
  }
8544
8595
  for (const entry of entries) {
8545
8596
  if (results.length >= maxResults) break;
8546
- const fullPath = join8(dirPath, entry.name);
8597
+ const fullPath = join9(dirPath, entry.name);
8547
8598
  if (entry.isDirectory()) {
8548
8599
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
8549
8600
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
@@ -9283,16 +9334,16 @@ ${preamble}`;
9283
9334
  }
9284
9335
 
9285
9336
  // src/tools/builtin/save-last-response.ts
9286
- import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync3, rmdirSync as rmdirSync2 } from "fs";
9287
- import { dirname as dirname4 } from "path";
9337
+ import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2 } from "fs";
9338
+ import { dirname as dirname5 } from "path";
9288
9339
  var lastResponseStore = { content: "" };
9289
9340
  function cleanupRejectedTeeFile(filePath) {
9290
9341
  try {
9291
- unlinkSync3(filePath);
9342
+ unlinkSync4(filePath);
9292
9343
  } catch {
9293
9344
  }
9294
9345
  try {
9295
- rmdirSync2(dirname4(filePath));
9346
+ rmdirSync2(dirname5(filePath));
9296
9347
  } catch {
9297
9348
  }
9298
9349
  }
@@ -9333,7 +9384,7 @@ Any of these triggers means use save_last_response, NOT write_file:
9333
9384
  throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
9334
9385
  }
9335
9386
  undoStack.push(filePath, `save_last_response: ${filePath}`);
9336
- mkdirSync6(dirname4(filePath), { recursive: true });
9387
+ mkdirSync7(dirname5(filePath), { recursive: true });
9337
9388
  atomicWriteFileSync(filePath, content);
9338
9389
  const lines = content.split("\n").length;
9339
9390
  return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
@@ -9341,18 +9392,18 @@ Any of these triggers means use save_last_response, NOT write_file:
9341
9392
  };
9342
9393
 
9343
9394
  // src/tools/builtin/save-memory.ts
9344
- import { join as join11 } from "path";
9395
+ import { join as join12 } from "path";
9345
9396
  import { homedir as homedir5 } from "os";
9346
9397
 
9347
9398
  // src/memory/persistent-memory.ts
9348
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "fs";
9399
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync12 } from "fs";
9349
9400
  import { randomUUID, createHash as createHash2 } from "crypto";
9350
- import { dirname as dirname5, join as join10, resolve as resolve6 } from "path";
9401
+ import { dirname as dirname6, join as join11, resolve as resolve6 } from "path";
9351
9402
 
9352
9403
  // src/tools/git-context.ts
9353
9404
  import { execSync as execSync2 } from "child_process";
9354
- import { existsSync as existsSync14 } from "fs";
9355
- import { join as join9 } from "path";
9405
+ import { existsSync as existsSync15 } from "fs";
9406
+ import { join as join10 } from "path";
9356
9407
  function runGit(cmd, cwd) {
9357
9408
  try {
9358
9409
  return execSync2(`git ${cmd}`, {
@@ -9369,7 +9420,7 @@ function getGitRoot(cwd = process.cwd()) {
9369
9420
  return runGit("rev-parse --show-toplevel", cwd);
9370
9421
  }
9371
9422
  function getGitContext(cwd = process.cwd()) {
9372
- if (!existsSync14(join9(cwd, ".git"))) {
9423
+ if (!existsSync15(join10(cwd, ".git"))) {
9373
9424
  const result = runGit("rev-parse --git-dir", cwd);
9374
9425
  if (!result) return null;
9375
9426
  }
@@ -9441,10 +9492,10 @@ function formatGitContextForPrompt(ctx) {
9441
9492
 
9442
9493
  // src/memory/persistent-memory.ts
9443
9494
  function memoryStorePath(configDir) {
9444
- return join10(configDir, MEMORY_STORE_FILE_NAME);
9495
+ return join11(configDir, MEMORY_STORE_FILE_NAME);
9445
9496
  }
9446
9497
  function memoryMarkdownPath(configDir) {
9447
- return join10(configDir, MEMORY_FILE_NAME);
9498
+ return join11(configDir, MEMORY_FILE_NAME);
9448
9499
  }
9449
9500
  function getProjectKey(cwd = process.cwd()) {
9450
9501
  return resolve6(getGitRoot(cwd) ?? cwd);
@@ -9480,8 +9531,8 @@ function normalizeEntry(value) {
9480
9531
  }
9481
9532
  function legacyEntriesFromMarkdown(configDir) {
9482
9533
  const file = memoryMarkdownPath(configDir);
9483
- if (!existsSync15(file)) return [];
9484
- const content = readFileSync11(file, "utf-8").trim();
9534
+ if (!existsSync16(file)) return [];
9535
+ const content = readFileSync12(file, "utf-8").trim();
9485
9536
  if (!content) return [];
9486
9537
  const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
9487
9538
  const chunks = parts.length > 1 ? parts : [content];
@@ -9497,8 +9548,8 @@ ${chunk}`).digest("hex").slice(0, 12);
9497
9548
  }
9498
9549
  function loadMemoryEntries(configDir) {
9499
9550
  const file = memoryStorePath(configDir);
9500
- if (!existsSync15(file)) return legacyEntriesFromMarkdown(configDir);
9501
- const text = readFileSync11(file, "utf-8").trim();
9551
+ if (!existsSync16(file)) return legacyEntriesFromMarkdown(configDir);
9552
+ const text = readFileSync12(file, "utf-8").trim();
9502
9553
  if (!text) return [];
9503
9554
  const entries = [];
9504
9555
  for (const line of text.split("\n")) {
@@ -9513,7 +9564,7 @@ function loadMemoryEntries(configDir) {
9513
9564
  return entries;
9514
9565
  }
9515
9566
  function saveMemoryEntries(configDir, entries) {
9516
- mkdirSync7(configDir, { recursive: true });
9567
+ mkdirSync8(configDir, { recursive: true });
9517
9568
  const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
9518
9569
  atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
9519
9570
  syncLegacyMarkdown(configDir, entries);
@@ -9588,7 +9639,7 @@ function findUnique(entries, idPrefix) {
9588
9639
  return matches[0];
9589
9640
  }
9590
9641
  function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
9591
- mkdirSync7(dirname5(memoryMarkdownPath(configDir)), { recursive: true });
9642
+ mkdirSync8(dirname6(memoryMarkdownPath(configDir)), { recursive: true });
9592
9643
  const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
9593
9644
  const markdown = active.map((entry) => {
9594
9645
  const date = entry.createdAt.replace("T", " ").slice(0, 19);
@@ -9631,7 +9682,7 @@ ${entry.content}`;
9631
9682
 
9632
9683
  // src/tools/builtin/save-memory.ts
9633
9684
  function getConfigDir() {
9634
- return join11(homedir5(), CONFIG_DIR_NAME);
9685
+ return join12(homedir5(), CONFIG_DIR_NAME);
9635
9686
  }
9636
9687
  function parseScope(value) {
9637
9688
  return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
@@ -9965,13 +10016,13 @@ function formatResults2(query, data, _requested) {
9965
10016
  }
9966
10017
 
9967
10018
  // src/agents/agent-config.ts
9968
- import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
9969
- import { join as join13 } from "path";
10019
+ import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync14 } from "fs";
10020
+ import { join as join14 } from "path";
9970
10021
  import { homedir as homedir6 } from "os";
9971
10022
 
9972
10023
  // src/plugins/plugin-manager.ts
9973
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, cpSync, rmSync, statSync as statSync6 } from "fs";
9974
- import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve7 } from "path";
10024
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync13, cpSync, rmSync, statSync as statSync6 } from "fs";
10025
+ import { basename as basename5, dirname as dirname7, join as join13, resolve as resolve7 } from "path";
9975
10026
  import { createHash as createHash3 } from "crypto";
9976
10027
  import { z as z2 } from "zod";
9977
10028
  var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
@@ -10031,48 +10082,48 @@ var PluginManifestSchema = z2.object({
10031
10082
  permissionHints: z2.array(z2.string()).default([])
10032
10083
  }).strict();
10033
10084
  function pluginRoot(configDir) {
10034
- return join12(configDir, PLUGINS_DIR_NAME);
10085
+ return join13(configDir, PLUGINS_DIR_NAME);
10035
10086
  }
10036
10087
  function pluginManifestPath(pluginDir) {
10037
- return join12(pluginDir, MANIFEST_RELATIVE);
10088
+ return join13(pluginDir, MANIFEST_RELATIVE);
10038
10089
  }
10039
10090
  function statePath(configDir) {
10040
- return join12(pluginRoot(configDir), STATE_FILE);
10091
+ return join13(pluginRoot(configDir), STATE_FILE);
10041
10092
  }
10042
10093
  function loadState(configDir) {
10043
10094
  const file = statePath(configDir);
10044
- if (!existsSync16(file)) return { version: 1, plugins: [] };
10095
+ if (!existsSync17(file)) return { version: 1, plugins: [] };
10045
10096
  try {
10046
- const parsed = JSON.parse(readFileSync12(file, "utf-8"));
10097
+ const parsed = JSON.parse(readFileSync13(file, "utf-8"));
10047
10098
  return { version: 1, plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [] };
10048
10099
  } catch {
10049
10100
  return { version: 1, plugins: [] };
10050
10101
  }
10051
10102
  }
10052
10103
  function saveState(configDir, state2) {
10053
- mkdirSync8(pluginRoot(configDir), { recursive: true });
10104
+ mkdirSync9(pluginRoot(configDir), { recursive: true });
10054
10105
  atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
10055
10106
  }
10056
10107
  function hashPluginManifest(manifestPath) {
10057
- return createHash3("sha256").update(readFileSync12(manifestPath)).digest("hex");
10108
+ return createHash3("sha256").update(readFileSync13(manifestPath)).digest("hex");
10058
10109
  }
10059
10110
  function readPluginManifest(manifestPath) {
10060
- return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
10111
+ return PluginManifestSchema.parse(JSON.parse(readFileSync13(manifestPath, "utf-8")));
10061
10112
  }
10062
10113
  function findPluginSourceDir(inputPath) {
10063
10114
  const abs = resolve7(inputPath);
10064
- const direct = statSync6(abs).isDirectory() ? abs : dirname6(abs);
10065
- if (existsSync16(pluginManifestPath(direct))) return direct;
10066
- const nested = join12(direct, ".aicli-plugin");
10067
- if (existsSync16(join12(nested, "plugin.json"))) return direct;
10115
+ const direct = statSync6(abs).isDirectory() ? abs : dirname7(abs);
10116
+ if (existsSync17(pluginManifestPath(direct))) return direct;
10117
+ const nested = join13(direct, ".aicli-plugin");
10118
+ if (existsSync17(join13(nested, "plugin.json"))) return direct;
10068
10119
  throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
10069
10120
  }
10070
10121
  function installPlugin(configDir, inputPath) {
10071
10122
  const sourceDir = findPluginSourceDir(inputPath);
10072
10123
  const manifest = readPluginManifest(pluginManifestPath(sourceDir));
10073
- const targetDir = join12(pluginRoot(configDir), manifest.name);
10074
- mkdirSync8(pluginRoot(configDir), { recursive: true });
10075
- if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10124
+ const targetDir = join13(pluginRoot(configDir), manifest.name);
10125
+ mkdirSync9(pluginRoot(configDir), { recursive: true });
10126
+ if (existsSync17(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10076
10127
  cpSync(sourceDir, targetDir, { recursive: true });
10077
10128
  const manifestPath = pluginManifestPath(targetDir);
10078
10129
  const hash = hashPluginManifest(manifestPath);
@@ -10130,18 +10181,18 @@ function getInstalledPlugin(configDir, name) {
10130
10181
  }
10131
10182
  function listInstalledPlugins(configDir) {
10132
10183
  const root = pluginRoot(configDir);
10133
- if (!existsSync16(root)) return [];
10184
+ if (!existsSync17(root)) return [];
10134
10185
  const state2 = loadState(configDir);
10135
10186
  const out = [];
10136
10187
  for (const entry of readdirSync7(root)) {
10137
- const dir = join12(root, entry);
10188
+ const dir = join13(root, entry);
10138
10189
  try {
10139
10190
  if (!statSync6(dir).isDirectory()) continue;
10140
10191
  } catch {
10141
10192
  continue;
10142
10193
  }
10143
10194
  const manifestPath = pluginManifestPath(dir);
10144
- if (!existsSync16(manifestPath)) continue;
10195
+ if (!existsSync17(manifestPath)) continue;
10145
10196
  try {
10146
10197
  const manifest = readPluginManifest(manifestPath);
10147
10198
  const hash = hashPluginManifest(manifestPath);
@@ -10181,7 +10232,7 @@ function uniqueDirs(plugin, rels) {
10181
10232
  for (const rel of rels) {
10182
10233
  const abs = resolve7(plugin.dir, rel);
10183
10234
  if (!abs.startsWith(resolve7(plugin.dir))) continue;
10184
- if (existsSync16(abs)) dirs.add(statSync6(abs).isDirectory() ? abs : dirname6(abs));
10235
+ if (existsSync17(abs)) dirs.add(statSync6(abs).isDirectory() ? abs : dirname7(abs));
10185
10236
  }
10186
10237
  return [...dirs];
10187
10238
  }
@@ -10344,13 +10395,13 @@ function parseAgentConfig(raw, source, path3) {
10344
10395
  return cfg;
10345
10396
  }
10346
10397
  function loadAgentDir(dir, source) {
10347
- if (!existsSync17(dir)) return [];
10398
+ if (!existsSync18(dir)) return [];
10348
10399
  const out = [];
10349
10400
  for (const file of readdirSync8(dir)) {
10350
10401
  if (!file.endsWith(".json")) continue;
10351
- const path3 = join13(dir, file);
10402
+ const path3 = join14(dir, file);
10352
10403
  try {
10353
- const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
10404
+ const parsed = JSON.parse(readFileSync14(path3, "utf-8"));
10354
10405
  const cfg = parseAgentConfig(parsed, source, path3);
10355
10406
  if (cfg) out.push(cfg);
10356
10407
  } catch {
@@ -10360,8 +10411,8 @@ function loadAgentDir(dir, source) {
10360
10411
  }
10361
10412
  function getAgentDirs(configDir, cwd = process.cwd()) {
10362
10413
  return {
10363
- user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
10364
- project: join13(cwd, CONFIG_DIR_NAME, "agents")
10414
+ user: join14(configDir ?? join14(homedir6(), CONFIG_DIR_NAME), "agents"),
10415
+ project: join14(cwd, CONFIG_DIR_NAME, "agents")
10365
10416
  };
10366
10417
  }
10367
10418
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -11076,14 +11127,14 @@ var taskStopTool = {
11076
11127
 
11077
11128
  // src/tools/builtin/git-tools.ts
11078
11129
  import { execFileSync as execFileSync2 } from "child_process";
11079
- import { existsSync as existsSync18 } from "fs";
11080
- import { join as join14 } from "path";
11130
+ import { existsSync as existsSync19 } from "fs";
11131
+ import { join as join15 } from "path";
11081
11132
  function assertGitRepo(cwd) {
11082
11133
  let dir = cwd;
11083
11134
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
11084
11135
  while (dir && dir !== root) {
11085
- if (existsSync18(join14(dir, ".git"))) return;
11086
- const parent = join14(dir, "..");
11136
+ if (existsSync19(join15(dir, ".git"))) return;
11137
+ const parent = join15(dir, "..");
11087
11138
  if (parent === dir) break;
11088
11139
  dir = parent;
11089
11140
  }
@@ -11348,7 +11399,7 @@ ${commitOutput.trim()}`;
11348
11399
  };
11349
11400
 
11350
11401
  // src/tools/builtin/notebook-edit.ts
11351
- import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
11402
+ import { readFileSync as readFileSync15, existsSync as existsSync20 } from "fs";
11352
11403
  import { writeFile } from "fs/promises";
11353
11404
  import { resolve as resolve8, extname as extname2 } from "path";
11354
11405
  var notebookEditTool = {
@@ -11403,10 +11454,10 @@ var notebookEditTool = {
11403
11454
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
11404
11455
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
11405
11456
  }
11406
- if (!existsSync19(absPath)) {
11457
+ if (!existsSync20(absPath)) {
11407
11458
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
11408
11459
  }
11409
- const raw = readFileSync14(absPath, "utf-8");
11460
+ const raw = readFileSync15(absPath, "utf-8");
11410
11461
  const nb = parseNotebook(raw);
11411
11462
  const cellIdx0 = cellIndexRaw - 1;
11412
11463
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -11823,8 +11874,8 @@ function estimateToolDefinitionTokens(def) {
11823
11874
 
11824
11875
  // src/tools/registry.ts
11825
11876
  import { pathToFileURL } from "url";
11826
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
11827
- import { join as join15 } from "path";
11877
+ import { existsSync as existsSync21, mkdirSync as mkdirSync10, readdirSync as readdirSync9 } from "fs";
11878
+ import { join as join16 } from "path";
11828
11879
  var ToolRegistry = class {
11829
11880
  tools = /* @__PURE__ */ new Map();
11830
11881
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -11985,9 +12036,9 @@ var ToolRegistry = class {
11985
12036
  * Returns the number of successfully loaded plugins.
11986
12037
  */
11987
12038
  async loadPlugins(pluginsDir, allowPlugins = false) {
11988
- if (!existsSync20(pluginsDir)) {
12039
+ if (!existsSync21(pluginsDir)) {
11989
12040
  try {
11990
- mkdirSync9(pluginsDir, { recursive: true });
12041
+ mkdirSync10(pluginsDir, { recursive: true });
11991
12042
  } catch {
11992
12043
  }
11993
12044
  return 0;
@@ -12011,12 +12062,12 @@ var ToolRegistry = class {
12011
12062
  process.stderr.write(
12012
12063
  `
12013
12064
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
12014
- ` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
12065
+ ` + files.map((f) => ` + ${join16(pluginsDir, f)}`).join("\n") + "\n\n"
12015
12066
  );
12016
12067
  let loaded = 0;
12017
12068
  for (const file of files) {
12018
12069
  try {
12019
- const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
12070
+ const fileUrl = pathToFileURL(join16(pluginsDir, file)).href;
12020
12071
  const mod = await import(fileUrl);
12021
12072
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
12022
12073
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -12562,11 +12613,11 @@ var McpManager = class {
12562
12613
  };
12563
12614
 
12564
12615
  // src/skills/manager.ts
12565
- import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync7 } from "fs";
12566
- import { join as join16 } from "path";
12616
+ import { existsSync as existsSync22, readdirSync as readdirSync10, mkdirSync as mkdirSync11, statSync as statSync7 } from "fs";
12617
+ import { join as join17 } from "path";
12567
12618
 
12568
12619
  // src/skills/types.ts
12569
- import { readFileSync as readFileSync15 } from "fs";
12620
+ import { readFileSync as readFileSync16 } from "fs";
12570
12621
  import { basename as basename6 } from "path";
12571
12622
  function parseSimpleYaml(yaml) {
12572
12623
  const result = {};
@@ -12588,7 +12639,7 @@ function parseYamlArray(value) {
12588
12639
  function parseSkillFile(filePath) {
12589
12640
  let raw;
12590
12641
  try {
12591
- raw = readFileSync15(filePath, "utf-8");
12642
+ raw = readFileSync16(filePath, "utf-8");
12592
12643
  } catch {
12593
12644
  return null;
12594
12645
  }
@@ -12636,10 +12687,10 @@ var SkillManager = class {
12636
12687
  return this.skills.size;
12637
12688
  }
12638
12689
  loadSkillDir(dir, createIfMissing) {
12639
- if (!existsSync21(dir)) {
12690
+ if (!existsSync22(dir)) {
12640
12691
  if (createIfMissing) {
12641
12692
  try {
12642
- mkdirSync10(dir, { recursive: true });
12693
+ mkdirSync11(dir, { recursive: true });
12643
12694
  } catch {
12644
12695
  }
12645
12696
  }
@@ -12653,14 +12704,14 @@ var SkillManager = class {
12653
12704
  }
12654
12705
  for (const entry of entries) {
12655
12706
  let filePath;
12656
- const fullPath = join16(dir, entry);
12707
+ const fullPath = join17(dir, entry);
12657
12708
  if (entry.endsWith(".md")) {
12658
12709
  filePath = fullPath;
12659
12710
  } else {
12660
12711
  try {
12661
12712
  if (statSync7(fullPath).isDirectory()) {
12662
- const skillMd = join16(fullPath, "SKILL.md");
12663
- if (existsSync21(skillMd)) filePath = skillMd;
12713
+ const skillMd = join17(fullPath, "SKILL.md");
12714
+ if (existsSync22(skillMd)) filePath = skillMd;
12664
12715
  else continue;
12665
12716
  } else {
12666
12717
  continue;
@@ -12719,7 +12770,7 @@ var SkillManager = class {
12719
12770
  // src/web/tool-executor-web.ts
12720
12771
  import { randomUUID as randomUUID3 } from "crypto";
12721
12772
  import { tmpdir as tmpdir2 } from "os";
12722
- import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
12773
+ import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
12723
12774
  var ToolExecutorWeb = class _ToolExecutorWeb {
12724
12775
  constructor(registry, ws) {
12725
12776
  this.registry = registry;
@@ -12858,9 +12909,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12858
12909
  if (call.name === "write_file") {
12859
12910
  const filePath = String(call.arguments["path"] ?? "");
12860
12911
  const newContent = String(call.arguments["content"] ?? "");
12861
- if (filePath && existsSync22(filePath)) {
12912
+ if (filePath && existsSync23(filePath)) {
12862
12913
  try {
12863
- const old = readFileSync16(filePath, "utf-8");
12914
+ const old = readFileSync17(filePath, "utf-8");
12864
12915
  return renderDiff(old, newContent, { filePath });
12865
12916
  } catch {
12866
12917
  }
@@ -13163,16 +13214,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
13163
13214
  };
13164
13215
 
13165
13216
  // src/repl/dev-state.ts
13166
- import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync4, mkdirSync as mkdirSync11 } from "fs";
13167
- import { join as join17 } from "path";
13217
+ import { existsSync as existsSync24, readFileSync as readFileSync18, unlinkSync as unlinkSync5, mkdirSync as mkdirSync12 } from "fs";
13218
+ import { join as join18 } from "path";
13168
13219
  import { homedir as homedir7 } from "os";
13169
13220
  function getDevStatePath() {
13170
- return join17(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
13221
+ return join18(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
13171
13222
  }
13172
13223
  function loadDevState() {
13173
13224
  const path3 = getDevStatePath();
13174
- if (!existsSync23(path3)) return null;
13175
- const content = readFileSync17(path3, "utf-8").trim();
13225
+ if (!existsSync24(path3)) return null;
13226
+ const content = readFileSync18(path3, "utf-8").trim();
13176
13227
  return content || null;
13177
13228
  }
13178
13229
 
@@ -13429,8 +13480,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
13429
13480
  }
13430
13481
 
13431
13482
  // src/core/context-files.ts
13432
- import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
13433
- import { join as join18, relative as relative3, resolve as resolve9 } from "path";
13483
+ import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
13484
+ import { join as join19, relative as relative3, resolve as resolve9 } from "path";
13434
13485
  function uniqueNonEmpty(values) {
13435
13486
  const seen = /* @__PURE__ */ new Set();
13436
13487
  const out = [];
@@ -13460,7 +13511,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
13460
13511
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
13461
13512
  const shown = displayPath(filePath, cwd);
13462
13513
  try {
13463
- const raw = readFileSync18(filePath);
13514
+ const raw = readFileSync19(filePath);
13464
13515
  const byteCount = raw.byteLength;
13465
13516
  if (byteCount === 0) {
13466
13517
  return {
@@ -13507,8 +13558,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
13507
13558
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
13508
13559
  const skipped = [];
13509
13560
  for (const candidate of candidates) {
13510
- const filePath = join18(dir, candidate);
13511
- if (!existsSync24(filePath)) continue;
13561
+ const filePath = join19(dir, candidate);
13562
+ if (!existsSync25(filePath)) continue;
13512
13563
  const result = readContextFile(level, filePath, cwd, maxBytes);
13513
13564
  if (result.skipped) skipped.push(result.skipped);
13514
13565
  if (result.layer) return { layer: result.layer, skipped };
@@ -13535,7 +13586,7 @@ function loadContextFiles(options) {
13535
13586
  });
13536
13587
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
13537
13588
  }
13538
- if (!existsSync24(filePath)) {
13589
+ if (!existsSync25(filePath)) {
13539
13590
  skipped.push({
13540
13591
  level: "single",
13541
13592
  filePath,
@@ -13586,8 +13637,8 @@ function loadContextFiles(options) {
13586
13637
  }
13587
13638
 
13588
13639
  // src/web/session-handler.ts
13589
- import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync12, statSync as statSync9, createWriteStream } from "fs";
13590
- import { join as join21, resolve as resolve10, dirname as dirname7 } from "path";
13640
+ import { existsSync as existsSync29, readFileSync as readFileSync22, writeFileSync as writeFileSync3, mkdirSync as mkdirSync14, readdirSync as readdirSync13, statSync as statSync11, createWriteStream } from "fs";
13641
+ import { join as join24, resolve as resolve10, dirname as dirname8 } from "path";
13591
13642
 
13592
13643
  // src/cli/review-prompts.ts
13593
13644
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -13648,8 +13699,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
13648
13699
  }
13649
13700
 
13650
13701
  // src/repl/commands/project-init.ts
13651
- import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
13652
- import { join as join19 } from "path";
13702
+ import { existsSync as existsSync26, readFileSync as readFileSync20, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
13703
+ import { join as join20 } from "path";
13653
13704
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
13654
13705
  "node_modules",
13655
13706
  ".git",
@@ -13693,11 +13744,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13693
13744
  const sorted = filtered.sort((a, b) => {
13694
13745
  let aIsDir = false, bIsDir = false;
13695
13746
  try {
13696
- aIsDir = statSync8(join19(d, a)).isDirectory();
13747
+ aIsDir = statSync8(join20(d, a)).isDirectory();
13697
13748
  } catch {
13698
13749
  }
13699
13750
  try {
13700
- bIsDir = statSync8(join19(d, b)).isDirectory();
13751
+ bIsDir = statSync8(join20(d, b)).isDirectory();
13701
13752
  } catch {
13702
13753
  }
13703
13754
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -13705,7 +13756,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13705
13756
  });
13706
13757
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
13707
13758
  const name = sorted[i];
13708
- const fullPath = join19(d, name);
13759
+ const fullPath = join20(d, name);
13709
13760
  const isLast = i === sorted.length - 1;
13710
13761
  const connector = isLast ? "+-- " : "|-- ";
13711
13762
  let isDir;
@@ -13732,7 +13783,7 @@ function scanProject(cwd) {
13732
13783
  configFiles: [],
13733
13784
  directoryStructure: ""
13734
13785
  };
13735
- const check = (file) => existsSync25(join19(cwd, file));
13786
+ const check = (file) => existsSync26(join20(cwd, file));
13736
13787
  const configCandidates = [
13737
13788
  "package.json",
13738
13789
  "tsconfig.json",
@@ -13759,7 +13810,7 @@ function scanProject(cwd) {
13759
13810
  info.type = "node";
13760
13811
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
13761
13812
  try {
13762
- const pkg = JSON.parse(readFileSync19(join19(cwd, "package.json"), "utf-8"));
13813
+ const pkg = JSON.parse(readFileSync20(join20(cwd, "package.json"), "utf-8"));
13763
13814
  const scripts = pkg.scripts ?? {};
13764
13815
  info.buildCommand = scripts.build ? "npm run build" : void 0;
13765
13816
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -14232,6 +14283,334 @@ var DiscussionOrchestrator = class {
14232
14283
  }
14233
14284
  };
14234
14285
 
14286
+ // src/diagnostics/doctor-report.ts
14287
+ import { existsSync as existsSync28, statSync as statSync10 } from "fs";
14288
+ import { join as join22 } from "path";
14289
+ import { arch as arch2, platform as platform6, release as release2 } from "os";
14290
+
14291
+ // src/diagnostics/crash-log.ts
14292
+ import {
14293
+ existsSync as existsSync27,
14294
+ mkdirSync as mkdirSync13,
14295
+ readdirSync as readdirSync12,
14296
+ readFileSync as readFileSync21,
14297
+ statSync as statSync9,
14298
+ unlinkSync as unlinkSync6,
14299
+ writeFileSync as writeFileSync2
14300
+ } from "fs";
14301
+ import { join as join21 } from "path";
14302
+ import { homedir as homedir8, platform as platform5, release, arch } from "os";
14303
+ var LOGS_DIR_NAME = "logs";
14304
+ var CRASH_LOG_PREFIX = "crash-";
14305
+ function getLogsDir(configDir) {
14306
+ const base = configDir ?? join21(homedir8(), CONFIG_DIR_NAME);
14307
+ return join21(base, LOGS_DIR_NAME);
14308
+ }
14309
+ function truncate(s, n) {
14310
+ return s.length <= n ? s : s.slice(0, n) + "\u2026";
14311
+ }
14312
+ function listRecentCrashes(limit = 10, configDir) {
14313
+ const dir = getLogsDir(configDir);
14314
+ if (!existsSync27(dir)) return [];
14315
+ const files = [];
14316
+ let names;
14317
+ try {
14318
+ names = readdirSync12(dir);
14319
+ } catch {
14320
+ return [];
14321
+ }
14322
+ for (const name of names) {
14323
+ if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
14324
+ const full = join21(dir, name);
14325
+ try {
14326
+ const st = statSync9(full);
14327
+ const head = readFileSync21(full, "utf-8").split("\n").slice(0, 20);
14328
+ const tsLine = head.find((l) => l.startsWith("timestamp:"));
14329
+ const kindLine = head.find((l) => l.startsWith("kind:"));
14330
+ const msgIdx = head.findIndex((l) => l.trim() === "## message");
14331
+ const message = msgIdx >= 0 && head[msgIdx + 1] ? head[msgIdx + 1] : "";
14332
+ files.push({
14333
+ file: full,
14334
+ timestamp: tsLine ? tsLine.replace("timestamp:", "").trim() : st.mtime.toISOString(),
14335
+ kind: kindLine ? kindLine.replace("kind:", "").trim() : "unknown",
14336
+ message: truncate(message, 200),
14337
+ sizeBytes: st.size
14338
+ });
14339
+ } catch {
14340
+ }
14341
+ }
14342
+ files.sort((a, b) => a.timestamp < b.timestamp ? 1 : -1);
14343
+ return files.slice(0, limit);
14344
+ }
14345
+ function getConfigDirUsage(configDir) {
14346
+ const base = configDir ?? join21(homedir8(), CONFIG_DIR_NAME);
14347
+ if (!existsSync27(base)) return { totalBytes: 0, entries: [] };
14348
+ const entries = [];
14349
+ let total = 0;
14350
+ let names;
14351
+ try {
14352
+ names = readdirSync12(base);
14353
+ } catch {
14354
+ return { totalBytes: 0, entries: [] };
14355
+ }
14356
+ for (const name of names) {
14357
+ const full = join21(base, name);
14358
+ const bytes = dirSize(full);
14359
+ entries.push({ name, bytes });
14360
+ total += bytes;
14361
+ }
14362
+ entries.sort((a, b) => b.bytes - a.bytes);
14363
+ return { totalBytes: total, entries };
14364
+ }
14365
+ function dirSize(path3) {
14366
+ try {
14367
+ const st = statSync9(path3);
14368
+ if (st.isFile()) return st.size;
14369
+ if (!st.isDirectory()) return 0;
14370
+ let sum = 0;
14371
+ for (const name of readdirSync12(path3)) {
14372
+ sum += dirSize(join21(path3, name));
14373
+ }
14374
+ return sum;
14375
+ } catch {
14376
+ return 0;
14377
+ }
14378
+ }
14379
+
14380
+ // src/diagnostics/doctor-report.ts
14381
+ function checkFile(label, path3) {
14382
+ const exists = existsSync28(path3);
14383
+ let sizeBytes = null;
14384
+ if (exists) {
14385
+ try {
14386
+ sizeBytes = statSync10(path3).size;
14387
+ } catch {
14388
+ }
14389
+ }
14390
+ return { label, path: path3, exists, sizeBytes };
14391
+ }
14392
+ function buildDoctorReport(options) {
14393
+ const config = options.config;
14394
+ const cwd = options.cwd ?? process.cwd();
14395
+ const configDir = config.getConfigDir();
14396
+ const projectRoot = getGitRoot(cwd) ?? cwd;
14397
+ const contextCfg = config.get("context");
14398
+ const contextSetting = config.get("contextFile");
14399
+ const context = loadContextFiles({
14400
+ cwd,
14401
+ configDir,
14402
+ projectRoot,
14403
+ setting: contextSetting,
14404
+ maxBytes: contextCfg.projectDocMaxBytes,
14405
+ fallbackFilenames: contextCfg.projectDocFallbackFilenames
14406
+ });
14407
+ const hooksConfig = config.get("hooks");
14408
+ const hooks = listHooks(hooksConfig, configDir);
14409
+ const pendingHooks = getPendingHookTrust(hooksConfig, configDir);
14410
+ const plugins = listInstalledPlugins(configDir);
14411
+ const activeAssets = getActivePluginAssets(configDir);
14412
+ const network = config.get("networkPolicy");
14413
+ const mcpServers = config.get("mcpServers");
14414
+ const allStats = getStatsSnapshot();
14415
+ return {
14416
+ version: VERSION,
14417
+ npmCheck: options.npmCheck,
14418
+ node: process.version,
14419
+ platform: `${platform6()} ${release2()} (${arch2()})`,
14420
+ cwd,
14421
+ projectRoot,
14422
+ configDir,
14423
+ providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
14424
+ files: [
14425
+ checkFile("config.json", join22(configDir, "config.json")),
14426
+ checkFile("memory.md", join22(configDir, MEMORY_FILE_NAME)),
14427
+ checkFile("memory.jsonl", join22(configDir, "memory.jsonl")),
14428
+ checkFile("dev-state.md", join22(configDir, DEV_STATE_FILE_NAME)),
14429
+ checkFile("hooks-trust.json", join22(configDir, "hooks-trust.json")),
14430
+ checkFile("plugin-state.json", join22(configDir, "plugins", "plugin-state.json"))
14431
+ ],
14432
+ context: {
14433
+ enabled: contextSetting !== false,
14434
+ setting: contextSetting,
14435
+ candidates: context.candidates,
14436
+ maxBytes: context.maxBytes,
14437
+ layers: context.layers.map((l) => ({ level: l.level, fileName: l.fileName, displayPath: l.displayPath, bytes: l.byteCount, chars: l.charCount, truncated: l.truncated })),
14438
+ skipped: context.skipped.map((s) => ({ level: s.level, fileName: s.fileName, displayPath: s.displayPath, reason: s.reason, message: s.message }))
14439
+ },
14440
+ permissions: {
14441
+ defaultProfile: config.get("defaultPermissionProfile"),
14442
+ allowedProfiles: config.get("allowedPermissionProfiles"),
14443
+ customProfileCount: Object.keys(config.get("permissionProfiles")).length,
14444
+ legacyDefaultAction: config.get("defaultPermission"),
14445
+ ruleCount: config.get("permissionRules").length
14446
+ },
14447
+ networkPolicy: {
14448
+ enabled: network.enabled,
14449
+ defaultAction: network.defaultAction,
14450
+ allowDomains: network.allowDomains.length,
14451
+ denyDomains: network.denyDomains.length,
14452
+ allowPorts: network.allowPorts.length,
14453
+ denyPorts: network.denyPorts.length,
14454
+ allowPrivateNetwork: network.allowPrivateNetwork,
14455
+ toolOverrides: Object.fromEntries(Object.entries(network.tools).filter(([, v]) => v !== void 0))
14456
+ },
14457
+ hooks: {
14458
+ enabled: hooksConfig?.enabled !== false,
14459
+ total: hooks.length,
14460
+ project: hooks.filter((h) => h.source === "project").length,
14461
+ pendingTrust: pendingHooks.length,
14462
+ disabled: hooks.filter((h) => h.disabled).length
14463
+ },
14464
+ plugins: {
14465
+ root: join22(configDir, "plugins"),
14466
+ installed: plugins.length,
14467
+ active: activeAssets.plugins.length,
14468
+ invalid: plugins.filter((p) => !p.valid).length,
14469
+ untrusted: plugins.filter((p) => p.valid && !p.trusted).length,
14470
+ disabled: plugins.filter((p) => p.valid && !p.enabled).length,
14471
+ mcpServers: Object.keys(activeAssets.mcpServers).length,
14472
+ skillDirs: activeAssets.skillDirs.length,
14473
+ commandDirs: activeAssets.commandDirs.length,
14474
+ agentDirs: activeAssets.agentDirs.length,
14475
+ entries: plugins.map((p) => ({ name: p.name, version: p.manifest.version, enabled: p.enabled, trusted: p.trusted, valid: p.valid, error: p.error }))
14476
+ },
14477
+ mcp: {
14478
+ enabled: config.get("mcpEnabled"),
14479
+ configured: Object.keys(mcpServers).length,
14480
+ projectConfigExists: existsSync28(join22(projectRoot, MCP_PROJECT_CONFIG_NAME)),
14481
+ statuses: options.mcpStatuses ?? []
14482
+ },
14483
+ recentCrashes: listRecentCrashes(5, configDir),
14484
+ diskUsage: getConfigDirUsage(configDir),
14485
+ toolStats: {
14486
+ totalCalls: allStats.reduce((a, b) => a + b.calls, 0),
14487
+ totalFailures: allStats.reduce((a, b) => a + b.failures, 0),
14488
+ topUsed: getTopUsedTools(5),
14489
+ topFailing: getTopFailingTools(5)
14490
+ }
14491
+ };
14492
+ }
14493
+
14494
+ // src/diagnostics/doctor-cli.ts
14495
+ function formatBytes(n) {
14496
+ if (n < 1024) return `${n} B`;
14497
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
14498
+ if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
14499
+ return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
14500
+ }
14501
+ function formatDoctorReport(report, ansi = true) {
14502
+ const wrap = (code, s) => ansi ? `${code}${s}\x1B[0m` : s;
14503
+ const B = (s) => wrap("\x1B[1m", s);
14504
+ const D = (s) => wrap("\x1B[2m", s);
14505
+ const G = (s) => wrap("\x1B[32m", s);
14506
+ const Y = (s) => wrap("\x1B[33m", s);
14507
+ const R = (s) => wrap("\x1B[31m", s);
14508
+ const out = [];
14509
+ out.push("");
14510
+ out.push(B("\u{1FA7A} AI-CLI Health Check"));
14511
+ out.push("");
14512
+ const nc = report.npmCheck;
14513
+ let versionLine = ` version: ${report.version}`;
14514
+ if (nc?.status === "up-to-date") versionLine += ` ${D("(latest on npm)")}`;
14515
+ else if (nc?.status === "outdated" && nc.latest) versionLine += ` ${Y(`\u2192 ${nc.latest} available`)} ${D("(npm i -g jinzd-ai-cli@latest)")}`;
14516
+ else if (nc?.status === "skipped") versionLine += ` ${D(`(npm check skipped: ${nc.reason ?? "unknown"})`)}`;
14517
+ out.push(versionLine);
14518
+ out.push(` node: ${report.node}`);
14519
+ out.push(` platform: ${report.platform}`);
14520
+ out.push(` cwd: ${report.cwd}`);
14521
+ out.push(` project: ${report.projectRoot}`);
14522
+ out.push(` config: ${report.configDir}`);
14523
+ out.push("");
14524
+ out.push(B("API Keys:"));
14525
+ for (const p of report.providers) {
14526
+ out.push(` ${p.configured ? G("\u2713") : D("\u25CB")} ${p.id.padEnd(14)} ${p.configured ? G("configured") : D("not configured")}`);
14527
+ }
14528
+ out.push("");
14529
+ out.push(B("Context Files:"));
14530
+ out.push(` status: ${report.context.enabled ? G("enabled") : D("disabled")} \xB7 max ${formatBytes(report.context.maxBytes)}`);
14531
+ if (report.context.layers.length === 0) out.push(` ${D("(no context files loaded)")}`);
14532
+ for (const l of report.context.layers) {
14533
+ out.push(` ${G("\u2713")} ${l.level.padEnd(7)} ${l.displayPath} ${D(`(${formatBytes(l.bytes)}${l.truncated ? ", truncated" : ""})`)}`);
14534
+ }
14535
+ if (report.context.skipped.length > 0) {
14536
+ out.push(` ${D("skipped:")}`);
14537
+ for (const s of report.context.skipped.slice(0, 5)) {
14538
+ out.push(` ${Y("!")} ${s.level}:${s.fileName} ${s.reason}${s.message ? ` \xB7 ${s.message}` : ""}`);
14539
+ }
14540
+ }
14541
+ out.push("");
14542
+ out.push(B("Permissions / Network:"));
14543
+ out.push(` profile: ${report.permissions.defaultProfile} \xB7 allowed ${report.permissions.allowedProfiles.join(", ")} \xB7 rules ${report.permissions.ruleCount} \xB7 custom profiles ${report.permissions.customProfileCount}`);
14544
+ const np = report.networkPolicy;
14545
+ 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"}`);
14546
+ const overrides = Object.entries(np.toolOverrides).map(([k, v]) => `${k}=${v}`).join(", ");
14547
+ if (overrides) out.push(` tool overrides: ${overrides}`);
14548
+ out.push("");
14549
+ out.push(B("Hooks / Plugins:"));
14550
+ 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}`);
14551
+ 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}`);
14552
+ 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)`);
14553
+ if (report.plugins.entries.length > 0) {
14554
+ for (const p of report.plugins.entries.slice(0, 5)) {
14555
+ const state2 = p.valid ? `${p.enabled ? "enabled" : "disabled"} \xB7 ${p.trusted ? "trusted" : "untrusted"}` : `invalid \xB7 ${p.error ?? ""}`;
14556
+ out.push(` ${p.valid ? G("\u2713") : R("\u2717")} ${p.name}@${p.version} ${D(state2)}`);
14557
+ }
14558
+ }
14559
+ out.push("");
14560
+ out.push(B("MCP Servers:"));
14561
+ out.push(` global configured: ${report.mcp.configured} \xB7 project .mcp.json: ${report.mcp.projectConfigExists ? "yes" : "no"} \xB7 mcpEnabled: ${report.mcp.enabled ? "true" : "false"}`);
14562
+ if (report.mcp.statuses.length === 0) out.push(` ${D("(no connected manager status in this mode)")}`);
14563
+ for (const s of report.mcp.statuses) {
14564
+ const state2 = s.connected ? `${G("connected")} \xB7 ${s.serverName} \xB7 ${s.toolCount} tools` : `${R("disconnected")}${s.error ? D(` \xB7 ${s.error}`) : ""}`;
14565
+ out.push(` ${s.connected ? G("\u2713") : R("\u2717")} ${s.serverId.padEnd(16)} ${state2}`);
14566
+ }
14567
+ out.push("");
14568
+ out.push(B("Config Files:"));
14569
+ for (const f of report.files) {
14570
+ const extra = f.exists && f.sizeBytes !== null ? ` ${D(`(${formatBytes(f.sizeBytes)})`)}` : "";
14571
+ out.push(` ${f.exists ? G("\u2713") : D("\u2013")} ${f.label.padEnd(16)} ${f.exists ? f.path : D("(not found)")}${extra}`);
14572
+ }
14573
+ out.push("");
14574
+ out.push(B("Recent Crashes (last 5):"));
14575
+ if (report.recentCrashes.length === 0) out.push(` ${D("(none - clean!)")}`);
14576
+ for (const c of report.recentCrashes) {
14577
+ out.push(` ${R("\u2717")} ${c.timestamp} ${Y(c.kind)}`);
14578
+ out.push(` ${D(c.message)}`);
14579
+ out.push(` ${D(c.file)}`);
14580
+ }
14581
+ out.push("");
14582
+ const ts = report.toolStats;
14583
+ out.push(B("Tool Usage:"));
14584
+ out.push(` total: ${ts.totalCalls} calls \xB7 ${ts.totalFailures} failures (${ts.totalCalls ? (ts.totalFailures * 100 / ts.totalCalls).toFixed(1) : "0.0"}%)`);
14585
+ if (ts.topUsed.length > 0) {
14586
+ out.push(` ${D("top used:")}`);
14587
+ 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`);
14588
+ }
14589
+ if (ts.topFailing.length > 0) {
14590
+ out.push(` ${D("top failing:")}`);
14591
+ 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));
14592
+ }
14593
+ out.push("");
14594
+ const dirAnnotations = {
14595
+ models: "semantic search model -> /index semantic-clear",
14596
+ index: "symbol index -> /index clear",
14597
+ history: "conversation history",
14598
+ logs: "crash logs",
14599
+ "memory-index": "chat memory index -> /memory index-clear",
14600
+ plugins: "installed package plugins"
14601
+ };
14602
+ out.push(B("Disk Usage (~/.aicli):"));
14603
+ out.push(` total: ${formatBytes(report.diskUsage.totalBytes)}`);
14604
+ for (const e of report.diskUsage.entries.slice(0, 10)) {
14605
+ const note = dirAnnotations[e.name];
14606
+ out.push(` ${e.name.padEnd(18)} ${formatBytes(e.bytes)}${note ? ` ${D(`\xB7 ${note}`)}` : ""}`);
14607
+ }
14608
+ out.push("");
14609
+ out.push(G("\u2713 Health check complete"));
14610
+ out.push("");
14611
+ return out.join("\n");
14612
+ }
14613
+
14235
14614
  // src/hub/presets.ts
14236
14615
  var PRESETS = [
14237
14616
  {
@@ -14419,7 +14798,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
14419
14798
  }
14420
14799
 
14421
14800
  // src/hub/persist.ts
14422
- import { join as join20 } from "path";
14801
+ import { join as join23 } from "path";
14423
14802
  function discussionToMessages(state2) {
14424
14803
  const out = [];
14425
14804
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -14457,7 +14836,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
14457
14836
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
14458
14837
  session.titleAiGenerated = true;
14459
14838
  await sm.save();
14460
- return { id: session.id, path: join20(config.getHistoryDir(), `${session.id}.json`) };
14839
+ return { id: session.id, path: join23(config.getHistoryDir(), `${session.id}.json`) };
14461
14840
  }
14462
14841
 
14463
14842
  // src/web/session-handler.ts
@@ -15174,7 +15553,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
15174
15553
  this.send({ type: "response_done", content, usage });
15175
15554
  if (pendingTeeSave && isCleanDocumentBody(content)) {
15176
15555
  try {
15177
- mkdirSync12(dirname7(pendingTeeSave), { recursive: true });
15556
+ mkdirSync14(dirname8(pendingTeeSave), { recursive: true });
15178
15557
  const bodyToSave = stripOuterCodeFence(content);
15179
15558
  atomicWriteFileSync(pendingTeeSave, bodyToSave);
15180
15559
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
@@ -15350,7 +15729,7 @@ ${summaryContent}`,
15350
15729
  let isError = false;
15351
15730
  let summary;
15352
15731
  try {
15353
- mkdirSync12(dirname7(saveToFile), { recursive: true });
15732
+ mkdirSync14(dirname8(saveToFile), { recursive: true });
15354
15733
  fileStream = createWriteStream(saveToFile);
15355
15734
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
15356
15735
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
@@ -15474,13 +15853,39 @@ ${summaryContent}`,
15474
15853
  break;
15475
15854
  }
15476
15855
  case "model": {
15477
- const modelId = args[0];
15478
- if (!modelId) {
15479
- this.send({ type: "error", message: "Usage: /model <id>" });
15856
+ const sub = args[0];
15857
+ if (!sub) {
15858
+ this.send({ type: "error", message: "Usage: /model <id>|refresh [provider]|cache [clear [provider]]" });
15480
15859
  return;
15481
15860
  }
15482
- const provider = this.providers.get(this.currentProvider);
15483
- const found = provider?.info.models.find((m) => m.id === modelId);
15861
+ if (sub === "refresh") {
15862
+ const providerId = args[1] ?? this.currentProvider;
15863
+ try {
15864
+ const models2 = await this.providers.refreshModels(providerId);
15865
+ this.send({ type: "info", message: `Refreshed ${models2.length} model(s) for ${providerId}.` });
15866
+ this.sendStatus();
15867
+ } catch (err) {
15868
+ this.send({ type: "error", message: `Model refresh failed: ${err instanceof Error ? err.message : String(err)}` });
15869
+ }
15870
+ return;
15871
+ }
15872
+ if (sub === "cache") {
15873
+ if ((args[1] ?? "").toLowerCase() === "clear") {
15874
+ const providerId = args[2];
15875
+ this.providers.clearModelCache(providerId);
15876
+ this.send({ type: "info", message: providerId ? `Cleared model cache for ${providerId}.` : "Cleared model cache." });
15877
+ return;
15878
+ }
15879
+ const rows = this.providers.getModelCacheStatus();
15880
+ this.send({
15881
+ type: "info",
15882
+ 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")
15883
+ });
15884
+ return;
15885
+ }
15886
+ const modelId = sub;
15887
+ const models = await this.providers.listModels(this.currentProvider);
15888
+ const found = models.find((m) => m.id === modelId);
15484
15889
  if (!found) {
15485
15890
  this.send({ type: "error", message: `Model "${modelId}" not found` });
15486
15891
  return;
@@ -16039,9 +16444,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16039
16444
  let modifiedFiles = 0;
16040
16445
  const diffLines2 = [];
16041
16446
  for (const [filePath, { earliest }] of fileMap) {
16042
- const currentContent = existsSync26(filePath) ? (() => {
16447
+ const currentContent = existsSync29(filePath) ? (() => {
16043
16448
  try {
16044
- return readFileSync20(filePath, "utf-8");
16449
+ return readFileSync22(filePath, "utf-8");
16045
16450
  } catch {
16046
16451
  return null;
16047
16452
  }
@@ -16557,7 +16962,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16557
16962
  case "test": {
16558
16963
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
16559
16964
  try {
16560
- const { executeTests } = await import("./run-tests-NXVVKAK2.js");
16965
+ const { executeTests } = await import("./run-tests-GK5AIAV2.js");
16561
16966
  const argStr = args.join(" ").trim();
16562
16967
  let testArgs = {};
16563
16968
  if (argStr) {
@@ -16574,9 +16979,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16574
16979
  // ── /init ───────────────────────────────────────────────────────
16575
16980
  case "init": {
16576
16981
  const cwd = process.cwd();
16577
- const targetPath = join21(cwd, "AICLI.md");
16982
+ const targetPath = join24(cwd, "AICLI.md");
16578
16983
  const force = args.includes("--force");
16579
- if (existsSync26(targetPath) && !force) {
16984
+ if (existsSync29(targetPath) && !force) {
16580
16985
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
16581
16986
  Use /init --force to overwrite.` });
16582
16987
  break;
@@ -16586,7 +16991,7 @@ Use /init --force to overwrite.` });
16586
16991
  const projectInfo = scanProject(cwd);
16587
16992
  const prompt = buildInitPrompt(projectInfo, cwd);
16588
16993
  const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
16589
- writeFileSync2(targetPath, content, "utf-8");
16994
+ writeFileSync3(targetPath, content, "utf-8");
16590
16995
  this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
16591
16996
  Use /context reload to load it.` });
16592
16997
  } catch (err) {
@@ -16596,53 +17001,13 @@ Use /context reload to load it.` });
16596
17001
  }
16597
17002
  // ── /doctor ─────────────────────────────────────────────────────
16598
17003
  case "doctor": {
16599
- const lines = ["\u{1FA7A} **AI-CLI Health Check**", ""];
16600
- lines.push("**API Keys:**");
16601
- const providersList = this.providers.listAll();
16602
- for (const p of providersList) {
16603
- const icon = p.configured ? "\u2713" : "\u25CB";
16604
- const status = p.configured ? "configured" : "not configured";
16605
- lines.push(` ${icon} ${p.id.padEnd(14)} ${status}`);
16606
- }
16607
- lines.push("");
16608
- const configDir = this.config.getConfigDir();
16609
- lines.push("**Config Files:**");
16610
- lines.push(` Dir: ${configDir}`);
16611
- const checkFile = (label, filePath) => {
16612
- const exists = existsSync26(filePath);
16613
- let extra = "";
16614
- if (exists) {
16615
- try {
16616
- extra = ` (${statSync9(filePath).size} bytes)`;
16617
- } catch {
16618
- }
16619
- }
16620
- lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
16621
- };
16622
- checkFile("config.json", join21(configDir, "config.json"));
16623
- checkFile("memory.md", join21(configDir, MEMORY_FILE_NAME));
16624
- checkFile("dev-state.md", join21(configDir, "dev-state.md"));
16625
- lines.push("");
16626
- if (this.mcpManager) {
16627
- lines.push("**MCP Servers:**");
16628
- const statuses = this.mcpManager.getStatus();
16629
- if (statuses.length === 0) {
16630
- lines.push(" (no servers configured)");
16631
- } else {
16632
- for (const s of statuses) {
16633
- const state2 = s.connected ? `connected \xB7 ${s.serverName} \xB7 ${s.toolCount} tools` : `disconnected${s.error ? ` \xB7 ${s.error}` : ""}`;
16634
- lines.push(` ${s.connected ? "\u2713" : "\u2717"} ${s.serverId.padEnd(16)} ${state2}`);
16635
- }
16636
- }
16637
- lines.push("");
16638
- }
16639
- lines.push("**Current Session:**");
16640
- lines.push(` Provider: ${this.currentProvider}`);
16641
- lines.push(` Model: ${this.currentModel}`);
16642
- lines.push(` Version: ${VERSION}`);
16643
- lines.push("");
16644
- lines.push("\u2713 Health check complete");
16645
- this.send({ type: "info", message: lines.join("\n") });
17004
+ const report = buildDoctorReport({
17005
+ config: this.config,
17006
+ providers: this.providers,
17007
+ cwd: process.cwd(),
17008
+ mcpStatuses: this.mcpManager?.getStatus() ?? []
17009
+ });
17010
+ this.send({ type: "info", message: formatDoctorReport(report, false) });
16646
17011
  break;
16647
17012
  }
16648
17013
  // ── /about ──────────────────────────────────────────────────────
@@ -16809,11 +17174,11 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
16809
17174
  break;
16810
17175
  }
16811
17176
  const dirPath = resolve10(sub);
16812
- if (!existsSync26(dirPath)) {
17177
+ if (!existsSync29(dirPath)) {
16813
17178
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
16814
17179
  break;
16815
17180
  }
16816
- if (!statSync9(dirPath).isDirectory()) {
17181
+ if (!statSync11(dirPath).isDirectory()) {
16817
17182
  this.send({ type: "error", message: `Not a directory: ${dirPath}` });
16818
17183
  break;
16819
17184
  }
@@ -16825,14 +17190,14 @@ It will be included in AI context for subsequent messages.` });
16825
17190
  // ── /commands ───────────────────────────────────────────────────
16826
17191
  case "commands": {
16827
17192
  const configDir = this.config.getConfigDir();
16828
- const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
16829
- if (!existsSync26(commandsDir)) {
17193
+ const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
17194
+ if (!existsSync29(commandsDir)) {
16830
17195
  this.send({ type: "info", message: `No custom commands directory.
16831
17196
  Create: ${commandsDir}/ with .md files.` });
16832
17197
  break;
16833
17198
  }
16834
17199
  try {
16835
- const files = readdirSync12(commandsDir).filter((f) => f.endsWith(".md"));
17200
+ const files = readdirSync13(commandsDir).filter((f) => f.endsWith(".md"));
16836
17201
  if (files.length === 0) {
16837
17202
  this.send({ type: "info", message: `No custom commands found in ${commandsDir}
16838
17203
  Add .md files to create commands.` });
@@ -16852,7 +17217,7 @@ Add .md files to create commands.` });
16852
17217
  // ── /plugins / /plugin ──────────────────────────────────────────
16853
17218
  case "plugins": {
16854
17219
  const configDir = this.config.getConfigDir();
16855
- const pluginsDir = join21(configDir, PLUGINS_DIR_NAME);
17220
+ const pluginsDir = join24(configDir, PLUGINS_DIR_NAME);
16856
17221
  const pluginTools = this.toolRegistry.listPluginTools();
16857
17222
  const pluginPackages = listInstalledPlugins(configDir);
16858
17223
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
@@ -17134,7 +17499,7 @@ ${entry.content}`;
17134
17499
  const configDir = this.config.getConfigDir();
17135
17500
  try {
17136
17501
  atomicWriteFileSync(memoryStorePath(configDir), "");
17137
- atomicWriteFileSync(join21(configDir, MEMORY_FILE_NAME), "");
17502
+ atomicWriteFileSync(join24(configDir, MEMORY_FILE_NAME), "");
17138
17503
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
17139
17504
  this.sendMemoryEntries();
17140
17505
  } catch (err) {
@@ -17416,8 +17781,8 @@ async function setupProxy(configProxy) {
17416
17781
  }
17417
17782
 
17418
17783
  // src/web/auth.ts
17419
- import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, copyFileSync } from "fs";
17420
- import { join as join22 } from "path";
17784
+ import { existsSync as existsSync30, readFileSync as readFileSync23, writeFileSync as writeFileSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync14, copyFileSync } from "fs";
17785
+ import { join as join25 } from "path";
17421
17786
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
17422
17787
  var USERS_FILE = "users.json";
17423
17788
  var TOKEN_EXPIRY_HOURS = 24;
@@ -17432,7 +17797,7 @@ var AuthManager = class {
17432
17797
  db;
17433
17798
  constructor(baseDir) {
17434
17799
  this.baseDir = baseDir;
17435
- this.usersFile = join22(baseDir, USERS_FILE);
17800
+ this.usersFile = join25(baseDir, USERS_FILE);
17436
17801
  this.db = this.loadOrCreate();
17437
17802
  }
17438
17803
  // ── Public API ─────────────────────────────────────────────────
@@ -17461,9 +17826,9 @@ var AuthManager = class {
17461
17826
  }
17462
17827
  const salt = randomBytes(16).toString("hex");
17463
17828
  const passwordHash = this.hashPassword(password, salt);
17464
- const dataDir = join22(USERS_DIR, username);
17465
- const fullDataDir = join22(this.baseDir, dataDir);
17466
- mkdirSync13(join22(fullDataDir, "history"), { recursive: true });
17829
+ const dataDir = join25(USERS_DIR, username);
17830
+ const fullDataDir = join25(this.baseDir, dataDir);
17831
+ mkdirSync15(join25(fullDataDir, "history"), { recursive: true });
17467
17832
  const user = {
17468
17833
  username,
17469
17834
  passwordHash,
@@ -17578,7 +17943,7 @@ var AuthManager = class {
17578
17943
  getUserDataDir(username) {
17579
17944
  const user = this.db.users.find((u) => u.username === username);
17580
17945
  if (!user) throw new Error(`User not found: ${username}`);
17581
- return join22(this.baseDir, user.dataDir);
17946
+ return join25(this.baseDir, user.dataDir);
17582
17947
  }
17583
17948
  /** List all usernames */
17584
17949
  listUsers() {
@@ -17614,30 +17979,30 @@ var AuthManager = class {
17614
17979
  const err = this.register(username, password);
17615
17980
  if (err) return err;
17616
17981
  const userDir = this.getUserDataDir(username);
17617
- const globalConfig = join22(this.baseDir, "config.json");
17618
- if (existsSync27(globalConfig)) {
17982
+ const globalConfig = join25(this.baseDir, "config.json");
17983
+ if (existsSync30(globalConfig)) {
17619
17984
  try {
17620
- const content = readFileSync21(globalConfig, "utf-8");
17621
- writeFileSync3(join22(userDir, "config.json"), content, "utf-8");
17985
+ const content = readFileSync23(globalConfig, "utf-8");
17986
+ writeFileSync4(join25(userDir, "config.json"), content, "utf-8");
17622
17987
  } catch {
17623
17988
  }
17624
17989
  }
17625
- const globalMemory = join22(this.baseDir, "memory.md");
17626
- if (existsSync27(globalMemory)) {
17990
+ const globalMemory = join25(this.baseDir, "memory.md");
17991
+ if (existsSync30(globalMemory)) {
17627
17992
  try {
17628
- const content = readFileSync21(globalMemory, "utf-8");
17629
- writeFileSync3(join22(userDir, "memory.md"), content, "utf-8");
17993
+ const content = readFileSync23(globalMemory, "utf-8");
17994
+ writeFileSync4(join25(userDir, "memory.md"), content, "utf-8");
17630
17995
  } catch {
17631
17996
  }
17632
17997
  }
17633
- const globalHistory = join22(this.baseDir, "history");
17634
- if (existsSync27(globalHistory)) {
17998
+ const globalHistory = join25(this.baseDir, "history");
17999
+ if (existsSync30(globalHistory)) {
17635
18000
  try {
17636
- const files = readdirSync13(globalHistory).filter((f) => f.endsWith(".json"));
17637
- const userHistory = join22(userDir, "history");
18001
+ const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
18002
+ const userHistory = join25(userDir, "history");
17638
18003
  for (const f of files) {
17639
18004
  try {
17640
- copyFileSync(join22(globalHistory, f), join22(userHistory, f));
18005
+ copyFileSync(join25(globalHistory, f), join25(userHistory, f));
17641
18006
  } catch {
17642
18007
  }
17643
18008
  }
@@ -17648,9 +18013,9 @@ var AuthManager = class {
17648
18013
  }
17649
18014
  // ── Private methods ────────────────────────────────────────────
17650
18015
  loadOrCreate() {
17651
- if (existsSync27(this.usersFile)) {
18016
+ if (existsSync30(this.usersFile)) {
17652
18017
  try {
17653
- return JSON.parse(readFileSync21(this.usersFile, "utf-8"));
18018
+ return JSON.parse(readFileSync23(this.usersFile, "utf-8"));
17654
18019
  } catch {
17655
18020
  }
17656
18021
  }
@@ -17666,7 +18031,7 @@ var AuthManager = class {
17666
18031
  this.saveDB(this.db);
17667
18032
  }
17668
18033
  saveDB(db) {
17669
- mkdirSync13(this.baseDir, { recursive: true });
18034
+ mkdirSync15(this.baseDir, { recursive: true });
17670
18035
  atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
17671
18036
  }
17672
18037
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
@@ -17698,7 +18063,7 @@ function getModuleDir() {
17698
18063
  if (typeof import.meta?.url === "string") {
17699
18064
  const url = new URL(import.meta.url);
17700
18065
  const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
17701
- return dirname8(filePath);
18066
+ return dirname9(filePath);
17702
18067
  }
17703
18068
  } catch {
17704
18069
  }
@@ -17721,7 +18086,8 @@ async function startWebServer(options = {}) {
17721
18086
  timeout: config.get("timeouts")[id],
17722
18087
  proxy: config.get("proxy")
17723
18088
  }),
17724
- config.get("customProviders")
18089
+ config.get("customProviders"),
18090
+ config.getConfigDir()
17725
18091
  );
17726
18092
  if (options._extraProviders) {
17727
18093
  for (const p of options._extraProviders) {
@@ -17769,8 +18135,8 @@ async function startWebServer(options = {}) {
17769
18135
  }
17770
18136
  }
17771
18137
  let skillManager = null;
17772
- const skillsDir = join23(config.getConfigDir(), SKILLS_DIR_NAME);
17773
- if (existsSync28(skillsDir)) {
18138
+ const skillsDir = join26(config.getConfigDir(), SKILLS_DIR_NAME);
18139
+ if (existsSync31(skillsDir)) {
17774
18140
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
17775
18141
  skillManager.loadSkills(pluginAssets.skillDirs);
17776
18142
  const count = skillManager.listSkills().length;
@@ -17841,18 +18207,18 @@ async function startWebServer(options = {}) {
17841
18207
  next();
17842
18208
  };
17843
18209
  const moduleDir = getModuleDir();
17844
- let clientDir = join23(moduleDir, "web", "client");
17845
- if (!existsSync28(clientDir)) {
17846
- clientDir = join23(moduleDir, "client");
18210
+ let clientDir = join26(moduleDir, "web", "client");
18211
+ if (!existsSync31(clientDir)) {
18212
+ clientDir = join26(moduleDir, "client");
17847
18213
  }
17848
- if (!existsSync28(clientDir)) {
17849
- clientDir = join23(moduleDir, "..", "..", "src", "web", "client");
18214
+ if (!existsSync31(clientDir)) {
18215
+ clientDir = join26(moduleDir, "..", "..", "src", "web", "client");
17850
18216
  }
17851
- if (!existsSync28(clientDir)) {
17852
- clientDir = join23(process.cwd(), "src", "web", "client");
18217
+ if (!existsSync31(clientDir)) {
18218
+ clientDir = join26(process.cwd(), "src", "web", "client");
17853
18219
  }
17854
18220
  console.log(` Static files: ${clientDir}`);
17855
- app.use("/vendor", express.static(join23(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
18221
+ app.use("/vendor", express.static(join26(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
17856
18222
  app.use(express.static(clientDir));
17857
18223
  app.get("/api/status", (_req, res) => {
17858
18224
  res.json({
@@ -17919,7 +18285,7 @@ async function startWebServer(options = {}) {
17919
18285
  app.get("/api/files", requireAuth, (req, res) => {
17920
18286
  const cwd = process.cwd();
17921
18287
  const prefix = req.query.prefix || "";
17922
- const targetDir = join23(cwd, prefix);
18288
+ const targetDir = join26(cwd, prefix);
17923
18289
  try {
17924
18290
  const canonicalTarget = realpathSync(resolve11(targetDir));
17925
18291
  const canonicalCwd = realpathSync(resolve11(cwd));
@@ -17933,10 +18299,10 @@ async function startWebServer(options = {}) {
17933
18299
  }
17934
18300
  try {
17935
18301
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
17936
- const entries = readdirSync14(targetDir, { withFileTypes: true });
18302
+ const entries = readdirSync15(targetDir, { withFileTypes: true });
17937
18303
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
17938
18304
  name: e.name,
17939
- path: relative4(cwd, join23(targetDir, e.name)).replace(/\\/g, "/"),
18305
+ path: relative4(cwd, join26(targetDir, e.name)).replace(/\\/g, "/"),
17940
18306
  isDir: e.isDirectory()
17941
18307
  }));
17942
18308
  res.json({ files });
@@ -17972,8 +18338,8 @@ async function startWebServer(options = {}) {
17972
18338
  try {
17973
18339
  const authUser = req._authUser;
17974
18340
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
17975
- const filePath = join23(histDir, `${id}.json`);
17976
- if (!existsSync28(filePath)) {
18341
+ const filePath = join26(histDir, `${id}.json`);
18342
+ if (!existsSync31(filePath)) {
17977
18343
  res.status(404).json({ error: "Session not found" });
17978
18344
  return;
17979
18345
  }
@@ -17988,7 +18354,7 @@ async function startWebServer(options = {}) {
17988
18354
  res.status(404).json({ error: "Session not found" });
17989
18355
  return;
17990
18356
  }
17991
- const data = JSON.parse(readFileSync22(filePath, "utf-8"));
18357
+ const data = JSON.parse(readFileSync24(filePath, "utf-8"));
17992
18358
  res.json({ session: data });
17993
18359
  } catch (err) {
17994
18360
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -18001,7 +18367,7 @@ async function startWebServer(options = {}) {
18001
18367
  return;
18002
18368
  }
18003
18369
  const cwd = process.cwd();
18004
- const fullPath = resolve11(join23(cwd, filePath));
18370
+ const fullPath = resolve11(join26(cwd, filePath));
18005
18371
  try {
18006
18372
  const canonicalFull = realpathSync(fullPath);
18007
18373
  const canonicalCwd = realpathSync(resolve11(cwd));
@@ -18014,12 +18380,12 @@ async function startWebServer(options = {}) {
18014
18380
  return;
18015
18381
  }
18016
18382
  try {
18017
- const stat = statSync10(fullPath);
18383
+ const stat = statSync12(fullPath);
18018
18384
  if (stat.size > 512 * 1024) {
18019
18385
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
18020
18386
  return;
18021
18387
  }
18022
- const content = readFileSync22(fullPath, "utf-8");
18388
+ const content = readFileSync24(fullPath, "utf-8");
18023
18389
  res.json({ content, size: stat.size });
18024
18390
  } catch {
18025
18391
  res.json({ error: "Cannot read file" });
@@ -18274,17 +18640,17 @@ function resolveProjectMcpPath() {
18274
18640
  const cwd = process.cwd();
18275
18641
  const gitRoot = getGitRoot(cwd);
18276
18642
  const projectRoot = gitRoot ?? cwd;
18277
- const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
18278
- return existsSync28(configPath) ? configPath : null;
18643
+ const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
18644
+ return existsSync31(configPath) ? configPath : null;
18279
18645
  }
18280
18646
  function loadProjectMcpConfig() {
18281
18647
  const cwd = process.cwd();
18282
18648
  const gitRoot = getGitRoot(cwd);
18283
18649
  const projectRoot = gitRoot ?? cwd;
18284
- const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
18285
- if (!existsSync28(configPath)) return null;
18650
+ const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
18651
+ if (!existsSync31(configPath)) return null;
18286
18652
  try {
18287
- const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
18653
+ const raw = JSON.parse(readFileSync24(configPath, "utf-8"));
18288
18654
  return raw.mcpServers ?? raw;
18289
18655
  } catch {
18290
18656
  return null;