jinzd-ai-cli 0.4.134 → 0.4.136

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-63YPIF2A.js";
4
+ } from "./chunk-2S27S4DO.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-WKITLEBR.js";
6
+ import "./chunk-DPT5HN3D.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
 
9
9
  // src/cli/batch.ts
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-WKITLEBR.js";
11
+ } from "./chunk-DPT5HN3D.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -127,6 +127,18 @@ var ConfigSchema = z.object({
127
127
  // API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
128
128
  // CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
129
129
  googleSearchEngineId: z.string().optional(),
130
+ // google_search 工具的安全开关(v0.4.135+)
131
+ // 默认 disabled。这个工具调用 Google Custom Search API,没有原生 daily-spend cap——
132
+ // 业内有多起 key 被盗后产生数万美元账单的真实案例。现在内置 web_search 工具走 Bing/Google
133
+ // 公开搜索页 scraping,零 key 零账单,是大多数场景下的默认选择。
134
+ // 仍想用 API 版?把 enabled 设为 true,并务必到 GCP Console 把 Custom Search Quota 设硬上限。
135
+ googleSearch: z.object({
136
+ enabled: z.boolean().default(false)
137
+ }).default({ enabled: false }),
138
+ // MCP (Model Context Protocol) 全局开关(v0.4.135+)
139
+ // false 时启动跳过 connectAll(),所有 MCP 服务器都不连,节省冷启动时间 + 工具定义 token。
140
+ // 对应你的 skills(whitelist 不依赖 MCP 工具时)完全不受影响。
141
+ mcpEnabled: z.boolean().default(true),
130
142
  // MCP (Model Context Protocol) 服务器配置
131
143
  // 声明外部 MCP 服务器,启动时自动连接、发现工具并注册
132
144
  // 配置格式兼容 Claude Desktop(command + args + env)
@@ -5,12 +5,12 @@ import {
5
5
  } from "./chunk-3BICTI5M.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-P6CGP7CU.js";
8
+ } from "./chunk-U5YSA4XD.js";
9
9
  import {
10
10
  getDangerLevel,
11
11
  isFileWriteTool,
12
12
  runTool
13
- } from "./chunk-ZOFXDRJT.js";
13
+ } from "./chunk-STUFQVOX.js";
14
14
  import {
15
15
  EnvLoader,
16
16
  NetworkError,
@@ -23,7 +23,7 @@ import {
23
23
  SUBAGENT_ALLOWED_TOOLS,
24
24
  SUBAGENT_DEFAULT_MAX_ROUNDS,
25
25
  SUBAGENT_MAX_ROUNDS_LIMIT
26
- } from "./chunk-WKITLEBR.js";
26
+ } from "./chunk-DPT5HN3D.js";
27
27
  import {
28
28
  fileCheckpoints
29
29
  } from "./chunk-4BKXL7SM.js";
@@ -3386,6 +3386,185 @@ var webFetchTool = {
3386
3386
  }
3387
3387
  };
3388
3388
 
3389
+ // src/tools/builtin/web-search.ts
3390
+ var REQUEST_TIMEOUT_MS = 15e3;
3391
+ var MAX_RESULTS = 10;
3392
+ var DEFAULT_RESULTS = 5;
3393
+ var UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";
3394
+ var webSearchTool = {
3395
+ definition: {
3396
+ name: "web_search",
3397
+ description: 'Search the web using Bing or Google by scraping their public result pages. No API key required, no billing risk. Returns structured {title, url, snippet} for each result. Use this for fresh facts, news, or any "what does X say about Y" lookup. Prefer this over google_search (which needs a paid API key).',
3398
+ parameters: {
3399
+ query: {
3400
+ type: "string",
3401
+ description: "The search query string.",
3402
+ required: true
3403
+ },
3404
+ num_results: {
3405
+ type: "number",
3406
+ description: "Number of results to return (1-10, default 5).",
3407
+ required: false
3408
+ },
3409
+ engine: {
3410
+ type: "string",
3411
+ description: "Search engine: 'bing' (default for CN reachability), 'google', or 'auto' (try bing first, fall back to google on empty/error). Default 'auto'.",
3412
+ required: false
3413
+ }
3414
+ },
3415
+ dangerous: false
3416
+ },
3417
+ async execute(args) {
3418
+ const query = String(args["query"] ?? "").trim();
3419
+ if (!query) throw new ToolError("web_search", "query parameter is required");
3420
+ const numResults = Math.min(
3421
+ Math.max(Math.floor(Number(args["num_results"] ?? DEFAULT_RESULTS)), 1),
3422
+ MAX_RESULTS
3423
+ );
3424
+ const engine = String(args["engine"] ?? "auto").toLowerCase();
3425
+ if (!["bing", "google", "auto"].includes(engine)) {
3426
+ throw new ToolError("web_search", `engine must be one of: bing, google, auto (got: ${engine})`);
3427
+ }
3428
+ const order = engine === "auto" ? ["bing", "google"] : engine === "bing" ? ["bing"] : ["google"];
3429
+ const errors = [];
3430
+ for (let i = 0; i < order.length; i++) {
3431
+ const eng = order[i];
3432
+ const isLastEngine = i === order.length - 1;
3433
+ try {
3434
+ const results = eng === "bing" ? await searchBing(query, numResults) : await searchGoogle(query, numResults);
3435
+ if (results.length === 0) {
3436
+ errors.push(`${eng}: 0 results`);
3437
+ continue;
3438
+ }
3439
+ if (eng === "bing" && !isLastEngine && isWeakBingResult(results)) {
3440
+ errors.push(`bing: weak results (likely long-tail fallback), trying ${order[i + 1]}`);
3441
+ continue;
3442
+ }
3443
+ return formatResults(query, eng, results);
3444
+ } catch (err) {
3445
+ errors.push(`${eng}: ${err instanceof Error ? err.message : String(err)}`);
3446
+ }
3447
+ }
3448
+ throw new NetworkError(
3449
+ `web_search failed across all engines.
3450
+ ${errors.map((e) => " - " + e).join("\n")}
3451
+ Tip: a CAPTCHA wall or a result-page redesign can cause this. Try web_fetch with a specific URL, or report at https://github.com/jinzhengdong/ai-cli/issues.`
3452
+ );
3453
+ }
3454
+ };
3455
+ function isWeakBingResult(results) {
3456
+ if (results.length < 3) return false;
3457
+ const hostCounts = /* @__PURE__ */ new Map();
3458
+ for (const r of results) {
3459
+ try {
3460
+ const host = new URL(r.url).hostname.replace(/^www\./, "");
3461
+ hostCounts.set(host, (hostCounts.get(host) ?? 0) + 1);
3462
+ } catch {
3463
+ }
3464
+ }
3465
+ const maxSameHost = Math.max(0, ...hostCounts.values());
3466
+ if (maxSameHost / results.length >= 0.6) return true;
3467
+ const noiseRe = /\b(speedtest\.net|fast\.com|ookla\.com|samh130|91secomic|baike\.baidu\.com\/item\/[^\/]+\d{4,}|baidu\.com\/s\?|encompasshealth|kansas)\b/i;
3468
+ const noiseCount = results.filter((r) => noiseRe.test(r.url)).length;
3469
+ if (noiseCount / results.length >= 0.4) return true;
3470
+ return false;
3471
+ }
3472
+ async function searchBing(query, num) {
3473
+ const url = new URL("https://cn.bing.com/search");
3474
+ url.searchParams.set("q", query);
3475
+ url.searchParams.set("count", String(num));
3476
+ url.searchParams.set("mkt", "zh-CN");
3477
+ const html = await fetchHtml(url.toString());
3478
+ return parseBingResults(html, num);
3479
+ }
3480
+ function parseBingResults(html, maxResults) {
3481
+ if (/CaptchaRedirect|sysmsg/i.test(html) && !/b_algo/.test(html)) {
3482
+ throw new NetworkError('Bing returned a CAPTCHA / interstitial \u2014 try again later or use engine: "google".');
3483
+ }
3484
+ const results = [];
3485
+ const liRegex = /<li[^>]*class="[^"]*\bb_algo\b[^"]*"[^>]*>([\s\S]*?)<\/li>/gi;
3486
+ let match;
3487
+ while ((match = liRegex.exec(html)) !== null && results.length < maxResults) {
3488
+ const block = match[1];
3489
+ const titleMatch = /<h2[^>]*>[\s\S]*?<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<\/h2>/i.exec(block);
3490
+ if (!titleMatch) continue;
3491
+ const url = decodeHtmlEntities(titleMatch[1]);
3492
+ const title = decodeHtmlEntities(stripTags2(titleMatch[2])).trim();
3493
+ const snippetMatch = /<p[^>]*class="[^"]*b_(lineclamp\d*|paractl|algoSlug)[^"]*"[^>]*>([\s\S]*?)<\/p>/i.exec(block) ?? /<div[^>]*class="[^"]*b_caption[^"]*"[^>]*>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/i.exec(block);
3494
+ const snippet = snippetMatch ? decodeHtmlEntities(stripTags2(snippetMatch[snippetMatch.length - 1])).trim() : "";
3495
+ if (!url || !title) continue;
3496
+ results.push({ title, url, snippet });
3497
+ }
3498
+ return results;
3499
+ }
3500
+ async function searchGoogle(query, num) {
3501
+ const url = new URL("https://www.google.com/search");
3502
+ url.searchParams.set("q", query);
3503
+ url.searchParams.set("num", String(num));
3504
+ url.searchParams.set("hl", "en");
3505
+ const html = await fetchHtml(url.toString());
3506
+ return parseGoogleResults(html, num);
3507
+ }
3508
+ function parseGoogleResults(html, maxResults) {
3509
+ if (/CaptchaRedirect|\/sorry\/index/i.test(html)) {
3510
+ throw new NetworkError('Google returned a CAPTCHA wall \u2014 try again later or use engine: "bing".');
3511
+ }
3512
+ const results = [];
3513
+ const linkRegex = /<a[^>]+href="\/url\?q=([^&"]+)[^"]*"[^>]*>[\s\S]*?<h3[^>]*>([\s\S]*?)<\/h3>([\s\S]*?)(?=<a[^>]+href="\/url\?q=|<\/body>)/gi;
3514
+ let match;
3515
+ while ((match = linkRegex.exec(html)) !== null && results.length < maxResults) {
3516
+ const rawUrl = decodeHtmlEntities(decodeURIComponent(match[1]));
3517
+ const title = decodeHtmlEntities(stripTags2(match[2])).trim();
3518
+ const tail = match[3] ?? "";
3519
+ if (!rawUrl.startsWith("http")) continue;
3520
+ if (/google\.com\/(images|maps|search|webhp)/i.test(rawUrl)) continue;
3521
+ const snippetMatch = /<span[^>]*>([\s\S]{40,400}?)<\/span>/i.exec(tail);
3522
+ const snippet = snippetMatch ? decodeHtmlEntities(stripTags2(snippetMatch[1])).trim() : "";
3523
+ results.push({ title, url: rawUrl, snippet });
3524
+ }
3525
+ return results;
3526
+ }
3527
+ async function fetchHtml(url) {
3528
+ const controller2 = new AbortController();
3529
+ const timer = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS);
3530
+ try {
3531
+ const res = await fetch(url, {
3532
+ method: "GET",
3533
+ headers: {
3534
+ "User-Agent": UA,
3535
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
3536
+ "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
3537
+ },
3538
+ signal: controller2.signal
3539
+ });
3540
+ if (!res.ok) {
3541
+ throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
3542
+ }
3543
+ return await res.text();
3544
+ } catch (err) {
3545
+ if (err instanceof Error && err.name === "AbortError") {
3546
+ throw new NetworkError(`Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s.`, void 0, err);
3547
+ }
3548
+ throw err;
3549
+ } finally {
3550
+ clearTimeout(timer);
3551
+ }
3552
+ }
3553
+ function stripTags2(html) {
3554
+ return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ");
3555
+ }
3556
+ function decodeHtmlEntities(s) {
3557
+ return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x2F;/g, "/").replace(/&nbsp;/g, " ");
3558
+ }
3559
+ function formatResults(query, engine, results) {
3560
+ const header = `Search results for "${query}" via ${engine} (${results.length} results):
3561
+ `;
3562
+ const body = results.map((r, i) => `${i + 1}. **${r.title}**
3563
+ URL: ${r.url}
3564
+ ${decodeHtmlEntities(r.snippet) || "(no snippet extracted)"}`).join("\n\n");
3565
+ return header + "\n" + body;
3566
+ }
3567
+
3389
3568
  // src/tools/builtin/save-last-response.ts
3390
3569
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
3391
3570
  import { dirname as dirname3 } from "path";
@@ -3633,14 +3812,14 @@ function renderTodoList(todos) {
3633
3812
 
3634
3813
  // src/tools/builtin/google-search.ts
3635
3814
  var GOOGLE_SEARCH_API = "https://www.googleapis.com/customsearch/v1";
3636
- var REQUEST_TIMEOUT_MS = 15e3;
3637
- var MAX_RESULTS = 10;
3638
- var DEFAULT_RESULTS = 5;
3815
+ var REQUEST_TIMEOUT_MS2 = 15e3;
3816
+ var MAX_RESULTS2 = 10;
3817
+ var DEFAULT_RESULTS2 = 5;
3639
3818
  var googleSearchContext = {};
3640
3819
  var googleSearchTool = {
3641
3820
  definition: {
3642
3821
  name: "google_search",
3643
- description: "Search the web using Google Custom Search API. Returns titles, URLs, and descriptions of search results. Use this to look up current information, verify facts, find documentation, or research topics that require up-to-date web data.",
3822
+ description: "\u26A0\uFE0F NOT RECOMMENDED \u2014 uses Google Custom Search API which requires a paid API key. API keys have leaked in the wild causing tens of thousands of USD in unauthorized charges. PREFER `web_search` (keyless Bing/Google scraping, zero billing risk). This tool is disabled by default; user must set config.googleSearch.enabled=true to use it.",
3644
3823
  parameters: {
3645
3824
  query: {
3646
3825
  type: "string",
@@ -3658,9 +3837,16 @@ var googleSearchTool = {
3658
3837
  async execute(args) {
3659
3838
  const query = String(args["query"] ?? "").trim();
3660
3839
  if (!query) throw new ToolError("google_search", "query parameter is required");
3840
+ const enabledFlag = googleSearchContext.configManager?.get("googleSearch")?.enabled ?? false;
3841
+ if (!enabledFlag) {
3842
+ throw new ToolError(
3843
+ "google_search",
3844
+ '\u{1F6AB} google_search is disabled by default (v0.4.135+).\n\nWHY: Google Custom Search has no native daily-spend cap. Keys leak (search GitHub for "AIza" \u2014 thousands of public exposures), bots scrape them within hours, and the GCP bill lands days later. Multiple engineers have lost $10k+ USD this way.\n\nRECOMMENDED: use `web_search` instead \u2014 same use case, scrapes Bing/Google public pages, zero API key, zero billing risk.\n\nIF YOU INSIST: edit ~/.aicli/config.json and set `googleSearch.enabled = true`. ALSO go to GCP Console \u2192 Custom Search API \u2192 Quotas and set a hard daily request cap (e.g. 100/day) \u2014 that is the only real damage cap.'
3845
+ );
3846
+ }
3661
3847
  const numResults = Math.min(
3662
- Math.max(Math.floor(Number(args["num_results"] ?? DEFAULT_RESULTS)), 1),
3663
- MAX_RESULTS
3848
+ Math.max(Math.floor(Number(args["num_results"] ?? DEFAULT_RESULTS2)), 1),
3849
+ MAX_RESULTS2
3664
3850
  );
3665
3851
  const { apiKey, cx } = resolveConfig();
3666
3852
  const url = new URL(GOOGLE_SEARCH_API);
@@ -3669,7 +3855,7 @@ var googleSearchTool = {
3669
3855
  url.searchParams.set("q", query);
3670
3856
  url.searchParams.set("num", String(numResults));
3671
3857
  const controller2 = new AbortController();
3672
- const timeout = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS);
3858
+ const timeout = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS2);
3673
3859
  try {
3674
3860
  const response = await fetch(url.toString(), {
3675
3861
  method: "GET",
@@ -3697,10 +3883,10 @@ ${errorBody.slice(0, 500)}`,
3697
3883
  );
3698
3884
  }
3699
3885
  const data = await response.json();
3700
- return formatResults(query, data, numResults);
3886
+ return formatResults2(query, data, numResults);
3701
3887
  } catch (err) {
3702
3888
  if (err instanceof Error && err.name === "AbortError") {
3703
- throw new NetworkError(`Google Search request timed out (${REQUEST_TIMEOUT_MS / 1e3}s). Please check your network or proxy configuration.`, void 0, err);
3889
+ throw new NetworkError(`Google Search request timed out (${REQUEST_TIMEOUT_MS2 / 1e3}s). Please check your network or proxy configuration.`, void 0, err);
3704
3890
  }
3705
3891
  throw err;
3706
3892
  } finally {
@@ -3732,7 +3918,7 @@ function resolveConfig() {
3732
3918
  }
3733
3919
  return { apiKey, cx };
3734
3920
  }
3735
- function formatResults(query, data, _requested) {
3921
+ function formatResults2(query, data, _requested) {
3736
3922
  const items = data.items ?? [];
3737
3923
  if (items.length === 0) {
3738
3924
  const info2 = data.searchInformation;
@@ -5015,6 +5201,7 @@ var ToolRegistry = class {
5015
5201
  this.register(globFilesTool);
5016
5202
  this.register(runInteractiveTool);
5017
5203
  this.register(webFetchTool);
5204
+ this.register(webSearchTool);
5018
5205
  this.register(saveLastResponseTool);
5019
5206
  this.register(saveMemoryTool);
5020
5207
  this.register(askUserTool);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.134";
4
+ var VERSION = "0.4.136";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.134";
9
+ var VERSION = "0.4.136";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  CONFIG_DIR_NAME,
4
4
  VERSION
5
- } from "./chunk-WKITLEBR.js";
5
+ } from "./chunk-DPT5HN3D.js";
6
6
 
7
7
  // src/diagnostics/crash-log.ts
8
8
  import {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR_NAME
4
- } from "./chunk-WKITLEBR.js";
4
+ } from "./chunk-DPT5HN3D.js";
5
5
 
6
6
  // src/diagnostics/tool-stats.ts
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-WKITLEBR.js";
4
+ } from "./chunk-DPT5HN3D.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  schemaToJsonSchema
4
- } from "./chunk-ZOFXDRJT.js";
4
+ } from "./chunk-STUFQVOX.js";
5
5
  import {
6
6
  AuthError,
7
7
  ProviderError,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-JER6BUQL.js";
4
+ } from "./chunk-6N5IWWPQ.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
@@ -11,7 +11,7 @@ import {
11
11
  MCP_PROTOCOL_VERSION,
12
12
  MCP_TOOL_PREFIX,
13
13
  VERSION
14
- } from "./chunk-WKITLEBR.js";
14
+ } from "./chunk-DPT5HN3D.js";
15
15
  import {
16
16
  redactJson
17
17
  } from "./chunk-7ZJN4KLV.js";
@@ -36,7 +36,7 @@ import {
36
36
  TEST_TIMEOUT,
37
37
  VERSION,
38
38
  buildUserIdentityPrompt
39
- } from "./chunk-WKITLEBR.js";
39
+ } from "./chunk-DPT5HN3D.js";
40
40
  import "./chunk-PDX44BCA.js";
41
41
  export {
42
42
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -2,25 +2,25 @@
2
2
  import {
3
3
  getConfigDirUsage,
4
4
  listRecentCrashes
5
- } from "./chunk-CQZMVPOW.js";
5
+ } from "./chunk-RZFAK5XL.js";
6
6
  import {
7
7
  ProviderRegistry
8
- } from "./chunk-BQL6IYCI.js";
8
+ } from "./chunk-UGKFU3I5.js";
9
9
  import {
10
10
  ConfigManager
11
- } from "./chunk-63YPIF2A.js";
11
+ } from "./chunk-2S27S4DO.js";
12
12
  import {
13
13
  getStatsSnapshot,
14
14
  getTopFailingTools,
15
15
  getTopUsedTools,
16
16
  resetStats
17
- } from "./chunk-ZOFXDRJT.js";
17
+ } from "./chunk-STUFQVOX.js";
18
18
  import "./chunk-2ZD3YTVM.js";
19
19
  import {
20
20
  DEV_STATE_FILE_NAME,
21
21
  MEMORY_FILE_NAME,
22
22
  VERSION
23
- } from "./chunk-WKITLEBR.js";
23
+ } from "./chunk-DPT5HN3D.js";
24
24
  import "./chunk-PDX44BCA.js";
25
25
 
26
26
  // src/diagnostics/doctor-cli.ts
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-4SSCJXAA.js";
39
+ } from "./chunk-QCFTOP3R.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -178,6 +178,18 @@ var ConfigSchema = z.object({
178
178
  // API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
179
179
  // CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
180
180
  googleSearchEngineId: z.string().optional(),
181
+ // google_search 工具的安全开关(v0.4.135+)
182
+ // 默认 disabled。这个工具调用 Google Custom Search API,没有原生 daily-spend cap——
183
+ // 业内有多起 key 被盗后产生数万美元账单的真实案例。现在内置 web_search 工具走 Bing/Google
184
+ // 公开搜索页 scraping,零 key 零账单,是大多数场景下的默认选择。
185
+ // 仍想用 API 版?把 enabled 设为 true,并务必到 GCP Console 把 Custom Search Quota 设硬上限。
186
+ googleSearch: z.object({
187
+ enabled: z.boolean().default(false)
188
+ }).default({ enabled: false }),
189
+ // MCP (Model Context Protocol) 全局开关(v0.4.135+)
190
+ // false 时启动跳过 connectAll(),所有 MCP 服务器都不连,节省冷启动时间 + 工具定义 token。
191
+ // 对应你的 skills(whitelist 不依赖 MCP 工具时)完全不受影响。
192
+ mcpEnabled: z.boolean().default(true),
181
193
  // MCP (Model Context Protocol) 服务器配置
182
194
  // 声明外部 MCP 服务器,启动时自动连接、发现工具并注册
183
195
  // 配置格式兼容 Claude Desktop(command + args + env)
@@ -7070,6 +7082,185 @@ var webFetchTool = {
7070
7082
  }
7071
7083
  };
7072
7084
 
7085
+ // src/tools/builtin/web-search.ts
7086
+ var REQUEST_TIMEOUT_MS = 15e3;
7087
+ var MAX_RESULTS = 10;
7088
+ var DEFAULT_RESULTS = 5;
7089
+ var UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";
7090
+ var webSearchTool = {
7091
+ definition: {
7092
+ name: "web_search",
7093
+ description: 'Search the web using Bing or Google by scraping their public result pages. No API key required, no billing risk. Returns structured {title, url, snippet} for each result. Use this for fresh facts, news, or any "what does X say about Y" lookup. Prefer this over google_search (which needs a paid API key).',
7094
+ parameters: {
7095
+ query: {
7096
+ type: "string",
7097
+ description: "The search query string.",
7098
+ required: true
7099
+ },
7100
+ num_results: {
7101
+ type: "number",
7102
+ description: "Number of results to return (1-10, default 5).",
7103
+ required: false
7104
+ },
7105
+ engine: {
7106
+ type: "string",
7107
+ description: "Search engine: 'bing' (default for CN reachability), 'google', or 'auto' (try bing first, fall back to google on empty/error). Default 'auto'.",
7108
+ required: false
7109
+ }
7110
+ },
7111
+ dangerous: false
7112
+ },
7113
+ async execute(args) {
7114
+ const query = String(args["query"] ?? "").trim();
7115
+ if (!query) throw new ToolError("web_search", "query parameter is required");
7116
+ const numResults = Math.min(
7117
+ Math.max(Math.floor(Number(args["num_results"] ?? DEFAULT_RESULTS)), 1),
7118
+ MAX_RESULTS
7119
+ );
7120
+ const engine = String(args["engine"] ?? "auto").toLowerCase();
7121
+ if (!["bing", "google", "auto"].includes(engine)) {
7122
+ throw new ToolError("web_search", `engine must be one of: bing, google, auto (got: ${engine})`);
7123
+ }
7124
+ const order = engine === "auto" ? ["bing", "google"] : engine === "bing" ? ["bing"] : ["google"];
7125
+ const errors = [];
7126
+ for (let i = 0; i < order.length; i++) {
7127
+ const eng = order[i];
7128
+ const isLastEngine = i === order.length - 1;
7129
+ try {
7130
+ const results = eng === "bing" ? await searchBing(query, numResults) : await searchGoogle(query, numResults);
7131
+ if (results.length === 0) {
7132
+ errors.push(`${eng}: 0 results`);
7133
+ continue;
7134
+ }
7135
+ if (eng === "bing" && !isLastEngine && isWeakBingResult(results)) {
7136
+ errors.push(`bing: weak results (likely long-tail fallback), trying ${order[i + 1]}`);
7137
+ continue;
7138
+ }
7139
+ return formatResults(query, eng, results);
7140
+ } catch (err) {
7141
+ errors.push(`${eng}: ${err instanceof Error ? err.message : String(err)}`);
7142
+ }
7143
+ }
7144
+ throw new NetworkError(
7145
+ `web_search failed across all engines.
7146
+ ${errors.map((e) => " - " + e).join("\n")}
7147
+ Tip: a CAPTCHA wall or a result-page redesign can cause this. Try web_fetch with a specific URL, or report at https://github.com/jinzhengdong/ai-cli/issues.`
7148
+ );
7149
+ }
7150
+ };
7151
+ function isWeakBingResult(results) {
7152
+ if (results.length < 3) return false;
7153
+ const hostCounts = /* @__PURE__ */ new Map();
7154
+ for (const r of results) {
7155
+ try {
7156
+ const host = new URL(r.url).hostname.replace(/^www\./, "");
7157
+ hostCounts.set(host, (hostCounts.get(host) ?? 0) + 1);
7158
+ } catch {
7159
+ }
7160
+ }
7161
+ const maxSameHost = Math.max(0, ...hostCounts.values());
7162
+ if (maxSameHost / results.length >= 0.6) return true;
7163
+ const noiseRe = /\b(speedtest\.net|fast\.com|ookla\.com|samh130|91secomic|baike\.baidu\.com\/item\/[^\/]+\d{4,}|baidu\.com\/s\?|encompasshealth|kansas)\b/i;
7164
+ const noiseCount = results.filter((r) => noiseRe.test(r.url)).length;
7165
+ if (noiseCount / results.length >= 0.4) return true;
7166
+ return false;
7167
+ }
7168
+ async function searchBing(query, num) {
7169
+ const url = new URL("https://cn.bing.com/search");
7170
+ url.searchParams.set("q", query);
7171
+ url.searchParams.set("count", String(num));
7172
+ url.searchParams.set("mkt", "zh-CN");
7173
+ const html = await fetchHtml(url.toString());
7174
+ return parseBingResults(html, num);
7175
+ }
7176
+ function parseBingResults(html, maxResults) {
7177
+ if (/CaptchaRedirect|sysmsg/i.test(html) && !/b_algo/.test(html)) {
7178
+ throw new NetworkError('Bing returned a CAPTCHA / interstitial \u2014 try again later or use engine: "google".');
7179
+ }
7180
+ const results = [];
7181
+ const liRegex = /<li[^>]*class="[^"]*\bb_algo\b[^"]*"[^>]*>([\s\S]*?)<\/li>/gi;
7182
+ let match;
7183
+ while ((match = liRegex.exec(html)) !== null && results.length < maxResults) {
7184
+ const block = match[1];
7185
+ const titleMatch = /<h2[^>]*>[\s\S]*?<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<\/h2>/i.exec(block);
7186
+ if (!titleMatch) continue;
7187
+ const url = decodeHtmlEntities(titleMatch[1]);
7188
+ const title = decodeHtmlEntities(stripTags2(titleMatch[2])).trim();
7189
+ const snippetMatch = /<p[^>]*class="[^"]*b_(lineclamp\d*|paractl|algoSlug)[^"]*"[^>]*>([\s\S]*?)<\/p>/i.exec(block) ?? /<div[^>]*class="[^"]*b_caption[^"]*"[^>]*>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/i.exec(block);
7190
+ const snippet = snippetMatch ? decodeHtmlEntities(stripTags2(snippetMatch[snippetMatch.length - 1])).trim() : "";
7191
+ if (!url || !title) continue;
7192
+ results.push({ title, url, snippet });
7193
+ }
7194
+ return results;
7195
+ }
7196
+ async function searchGoogle(query, num) {
7197
+ const url = new URL("https://www.google.com/search");
7198
+ url.searchParams.set("q", query);
7199
+ url.searchParams.set("num", String(num));
7200
+ url.searchParams.set("hl", "en");
7201
+ const html = await fetchHtml(url.toString());
7202
+ return parseGoogleResults(html, num);
7203
+ }
7204
+ function parseGoogleResults(html, maxResults) {
7205
+ if (/CaptchaRedirect|\/sorry\/index/i.test(html)) {
7206
+ throw new NetworkError('Google returned a CAPTCHA wall \u2014 try again later or use engine: "bing".');
7207
+ }
7208
+ const results = [];
7209
+ const linkRegex = /<a[^>]+href="\/url\?q=([^&"]+)[^"]*"[^>]*>[\s\S]*?<h3[^>]*>([\s\S]*?)<\/h3>([\s\S]*?)(?=<a[^>]+href="\/url\?q=|<\/body>)/gi;
7210
+ let match;
7211
+ while ((match = linkRegex.exec(html)) !== null && results.length < maxResults) {
7212
+ const rawUrl = decodeHtmlEntities(decodeURIComponent(match[1]));
7213
+ const title = decodeHtmlEntities(stripTags2(match[2])).trim();
7214
+ const tail = match[3] ?? "";
7215
+ if (!rawUrl.startsWith("http")) continue;
7216
+ if (/google\.com\/(images|maps|search|webhp)/i.test(rawUrl)) continue;
7217
+ const snippetMatch = /<span[^>]*>([\s\S]{40,400}?)<\/span>/i.exec(tail);
7218
+ const snippet = snippetMatch ? decodeHtmlEntities(stripTags2(snippetMatch[1])).trim() : "";
7219
+ results.push({ title, url: rawUrl, snippet });
7220
+ }
7221
+ return results;
7222
+ }
7223
+ async function fetchHtml(url) {
7224
+ const controller2 = new AbortController();
7225
+ const timer = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS);
7226
+ try {
7227
+ const res = await fetch(url, {
7228
+ method: "GET",
7229
+ headers: {
7230
+ "User-Agent": UA,
7231
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
7232
+ "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
7233
+ },
7234
+ signal: controller2.signal
7235
+ });
7236
+ if (!res.ok) {
7237
+ throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
7238
+ }
7239
+ return await res.text();
7240
+ } catch (err) {
7241
+ if (err instanceof Error && err.name === "AbortError") {
7242
+ throw new NetworkError(`Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s.`, void 0, err);
7243
+ }
7244
+ throw err;
7245
+ } finally {
7246
+ clearTimeout(timer);
7247
+ }
7248
+ }
7249
+ function stripTags2(html) {
7250
+ return html.replace(/<[^>]+>/g, "").replace(/\s+/g, " ");
7251
+ }
7252
+ function decodeHtmlEntities(s) {
7253
+ return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&#x2F;/g, "/").replace(/&nbsp;/g, " ");
7254
+ }
7255
+ function formatResults(query, engine, results) {
7256
+ const header = `Search results for "${query}" via ${engine} (${results.length} results):
7257
+ `;
7258
+ const body = results.map((r, i) => `${i + 1}. **${r.title}**
7259
+ URL: ${r.url}
7260
+ ${decodeHtmlEntities(r.snippet) || "(no snippet extracted)"}`).join("\n\n");
7261
+ return header + "\n" + body;
7262
+ }
7263
+
7073
7264
  // src/tools/builtin/save-last-response.ts
7074
7265
  import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
7075
7266
  import { dirname as dirname4 } from "path";
@@ -7317,14 +7508,14 @@ function renderTodoList(todos) {
7317
7508
 
7318
7509
  // src/tools/builtin/google-search.ts
7319
7510
  var GOOGLE_SEARCH_API = "https://www.googleapis.com/customsearch/v1";
7320
- var REQUEST_TIMEOUT_MS = 15e3;
7321
- var MAX_RESULTS = 10;
7322
- var DEFAULT_RESULTS = 5;
7511
+ var REQUEST_TIMEOUT_MS2 = 15e3;
7512
+ var MAX_RESULTS2 = 10;
7513
+ var DEFAULT_RESULTS2 = 5;
7323
7514
  var googleSearchContext = {};
7324
7515
  var googleSearchTool = {
7325
7516
  definition: {
7326
7517
  name: "google_search",
7327
- description: "Search the web using Google Custom Search API. Returns titles, URLs, and descriptions of search results. Use this to look up current information, verify facts, find documentation, or research topics that require up-to-date web data.",
7518
+ description: "\u26A0\uFE0F NOT RECOMMENDED \u2014 uses Google Custom Search API which requires a paid API key. API keys have leaked in the wild causing tens of thousands of USD in unauthorized charges. PREFER `web_search` (keyless Bing/Google scraping, zero billing risk). This tool is disabled by default; user must set config.googleSearch.enabled=true to use it.",
7328
7519
  parameters: {
7329
7520
  query: {
7330
7521
  type: "string",
@@ -7342,9 +7533,16 @@ var googleSearchTool = {
7342
7533
  async execute(args) {
7343
7534
  const query = String(args["query"] ?? "").trim();
7344
7535
  if (!query) throw new ToolError("google_search", "query parameter is required");
7536
+ const enabledFlag = googleSearchContext.configManager?.get("googleSearch")?.enabled ?? false;
7537
+ if (!enabledFlag) {
7538
+ throw new ToolError(
7539
+ "google_search",
7540
+ '\u{1F6AB} google_search is disabled by default (v0.4.135+).\n\nWHY: Google Custom Search has no native daily-spend cap. Keys leak (search GitHub for "AIza" \u2014 thousands of public exposures), bots scrape them within hours, and the GCP bill lands days later. Multiple engineers have lost $10k+ USD this way.\n\nRECOMMENDED: use `web_search` instead \u2014 same use case, scrapes Bing/Google public pages, zero API key, zero billing risk.\n\nIF YOU INSIST: edit ~/.aicli/config.json and set `googleSearch.enabled = true`. ALSO go to GCP Console \u2192 Custom Search API \u2192 Quotas and set a hard daily request cap (e.g. 100/day) \u2014 that is the only real damage cap.'
7541
+ );
7542
+ }
7345
7543
  const numResults = Math.min(
7346
- Math.max(Math.floor(Number(args["num_results"] ?? DEFAULT_RESULTS)), 1),
7347
- MAX_RESULTS
7544
+ Math.max(Math.floor(Number(args["num_results"] ?? DEFAULT_RESULTS2)), 1),
7545
+ MAX_RESULTS2
7348
7546
  );
7349
7547
  const { apiKey, cx } = resolveConfig();
7350
7548
  const url = new URL(GOOGLE_SEARCH_API);
@@ -7353,7 +7551,7 @@ var googleSearchTool = {
7353
7551
  url.searchParams.set("q", query);
7354
7552
  url.searchParams.set("num", String(numResults));
7355
7553
  const controller2 = new AbortController();
7356
- const timeout = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS);
7554
+ const timeout = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS2);
7357
7555
  try {
7358
7556
  const response = await fetch(url.toString(), {
7359
7557
  method: "GET",
@@ -7381,10 +7579,10 @@ ${errorBody.slice(0, 500)}`,
7381
7579
  );
7382
7580
  }
7383
7581
  const data = await response.json();
7384
- return formatResults(query, data, numResults);
7582
+ return formatResults2(query, data, numResults);
7385
7583
  } catch (err) {
7386
7584
  if (err instanceof Error && err.name === "AbortError") {
7387
- throw new NetworkError(`Google Search request timed out (${REQUEST_TIMEOUT_MS / 1e3}s). Please check your network or proxy configuration.`, void 0, err);
7585
+ throw new NetworkError(`Google Search request timed out (${REQUEST_TIMEOUT_MS2 / 1e3}s). Please check your network or proxy configuration.`, void 0, err);
7388
7586
  }
7389
7587
  throw err;
7390
7588
  } finally {
@@ -7416,7 +7614,7 @@ function resolveConfig() {
7416
7614
  }
7417
7615
  return { apiKey, cx };
7418
7616
  }
7419
- function formatResults(query, data, _requested) {
7617
+ function formatResults2(query, data, _requested) {
7420
7618
  const items = data.items ?? [];
7421
7619
  if (items.length === 0) {
7422
7620
  const info2 = data.searchInformation;
@@ -8699,6 +8897,7 @@ var ToolRegistry = class {
8699
8897
  this.register(globFilesTool);
8700
8898
  this.register(runInteractiveTool);
8701
8899
  this.register(webFetchTool);
8900
+ this.register(webSearchTool);
8702
8901
  this.register(saveLastResponseTool);
8703
8902
  this.register(saveMemoryTool);
8704
8903
  this.register(askUserTool);
@@ -12219,7 +12418,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
12219
12418
  case "test": {
12220
12419
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
12221
12420
  try {
12222
- const { executeTests } = await import("./run-tests-CL7X7D4R.js");
12421
+ const { executeTests } = await import("./run-tests-AUBA7PAV.js");
12223
12422
  const argStr = args.join(" ").trim();
12224
12423
  let testArgs = {};
12225
12424
  if (argStr) {
@@ -386,7 +386,7 @@ ${content}`);
386
386
  }
387
387
  }
388
388
  async function runTaskMode(config, providers, configManager, topic) {
389
- const { TaskOrchestrator } = await import("./task-orchestrator-HL2GAAGA.js");
389
+ const { TaskOrchestrator } = await import("./task-orchestrator-PHHK3KWD.js");
390
390
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
391
391
  let interrupted = false;
392
392
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -16,12 +16,12 @@ import {
16
16
  saveDevState,
17
17
  sessionHasMeaningfulContent,
18
18
  setupProxy
19
- } from "./chunk-2QPEE3AY.js";
19
+ } from "./chunk-UGNMM6TM.js";
20
20
  import {
21
21
  getConfigDirUsage,
22
22
  listRecentCrashes,
23
23
  writeCrashLog
24
- } from "./chunk-CQZMVPOW.js";
24
+ } from "./chunk-RZFAK5XL.js";
25
25
  import {
26
26
  CONTENT_ONLY_STREAM_REMINDER,
27
27
  HALLUCINATION_CORRECTION_MESSAGE,
@@ -39,10 +39,10 @@ import {
39
39
  looksLikeDocumentBody,
40
40
  stripPseudoToolCalls,
41
41
  stripToolCallReminder
42
- } from "./chunk-BQL6IYCI.js";
42
+ } from "./chunk-UGKFU3I5.js";
43
43
  import {
44
44
  ConfigManager
45
- } from "./chunk-63YPIF2A.js";
45
+ } from "./chunk-2S27S4DO.js";
46
46
  import {
47
47
  ToolExecutor,
48
48
  ToolRegistry,
@@ -61,16 +61,16 @@ import {
61
61
  spawnAgentContext,
62
62
  theme,
63
63
  undoStack
64
- } from "./chunk-JER6BUQL.js";
64
+ } from "./chunk-6N5IWWPQ.js";
65
65
  import "./chunk-3BICTI5M.js";
66
66
  import "./chunk-2DXY7UGF.js";
67
- import "./chunk-P6CGP7CU.js";
67
+ import "./chunk-U5YSA4XD.js";
68
68
  import {
69
69
  getStatsSnapshot,
70
70
  getTopFailingTools,
71
71
  getTopUsedTools,
72
72
  installFlushOnExit
73
- } from "./chunk-ZOFXDRJT.js";
73
+ } from "./chunk-STUFQVOX.js";
74
74
  import "./chunk-2ZD3YTVM.js";
75
75
  import {
76
76
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -93,7 +93,7 @@ import {
93
93
  SKILLS_DIR_NAME,
94
94
  VERSION,
95
95
  buildUserIdentityPrompt
96
- } from "./chunk-WKITLEBR.js";
96
+ } from "./chunk-DPT5HN3D.js";
97
97
  import {
98
98
  formatGitContextForPrompt,
99
99
  getGitContext,
@@ -1812,7 +1812,7 @@ No tools match "${filter}".
1812
1812
  const { join: join6 } = await import("path");
1813
1813
  const { existsSync: existsSync6 } = await import("fs");
1814
1814
  const { getGitRoot: getGitRoot2 } = await import("./git-context-7KIP4X2V.js");
1815
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-7VLZQ2ZS.js");
1815
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-T5DUKPIU.js");
1816
1816
  const { approveProject, hashMcpFile } = await import("./project-trust-IFM7FXEV.js");
1817
1817
  const cwd = process.cwd();
1818
1818
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2873,7 +2873,7 @@ ${hint}` : "")
2873
2873
  usage: "/test [command|filter]",
2874
2874
  async execute(args, ctx) {
2875
2875
  try {
2876
- const { executeTests } = await import("./run-tests-I6RJ35P6.js");
2876
+ const { executeTests } = await import("./run-tests-V3HUOGWE.js");
2877
2877
  const argStr = args.join(" ").trim();
2878
2878
  let testArgs = {};
2879
2879
  if (argStr) {
@@ -5429,8 +5429,12 @@ Session '${this.resumeSessionId}' not found.
5429
5429
  } catch {
5430
5430
  }
5431
5431
  })();
5432
- const globalMcpServers = this.config.get("mcpServers") ?? {};
5433
- const projectMcpResult = this.loadProjectMcpConfig();
5432
+ const mcpEnabled = this.config.get("mcpEnabled") !== false;
5433
+ const globalMcpServers = mcpEnabled ? this.config.get("mcpServers") ?? {} : {};
5434
+ const projectMcpResult = mcpEnabled ? this.loadProjectMcpConfig() : null;
5435
+ if (!mcpEnabled) {
5436
+ process.stdout.write(theme.dim(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)\n"));
5437
+ }
5434
5438
  let projectMcpServers = {};
5435
5439
  if (projectMcpResult) {
5436
5440
  const { checkTrust } = await import("./project-trust-IFM7FXEV.js");
@@ -7323,7 +7327,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7323
7327
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7324
7328
  process.exit(1);
7325
7329
  }
7326
- const { startWebServer } = await import("./server-HIJQRAOG.js");
7330
+ const { startWebServer } = await import("./server-BSCDYGH7.js");
7327
7331
  await startWebServer({ port, host: options.host });
7328
7332
  });
7329
7333
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7490,12 +7494,12 @@ program.command("sessions").description("List recent conversation sessions").opt
7490
7494
  console.log(footer + "\n");
7491
7495
  });
7492
7496
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7493
- const { runDoctorCli } = await import("./doctor-cli-FCJ4W3ZH.js");
7497
+ const { runDoctorCli } = await import("./doctor-cli-WGUJO6LJ.js");
7494
7498
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7495
7499
  });
7496
7500
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7497
7501
  try {
7498
- const batch = await import("./batch-ZCBWXMNR.js");
7502
+ const batch = await import("./batch-PLWYBV75.js");
7499
7503
  switch (action) {
7500
7504
  case "submit":
7501
7505
  if (!arg) {
@@ -7538,7 +7542,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7538
7542
  }
7539
7543
  });
7540
7544
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7541
- const { startMcpServer } = await import("./server-432AF6JW.js");
7545
+ const { startMcpServer } = await import("./server-STXQEXTU.js");
7542
7546
  await startMcpServer({
7543
7547
  allowDestructive: !!options.allowDestructive,
7544
7548
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7665,7 +7669,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7665
7669
  }),
7666
7670
  config.get("customProviders")
7667
7671
  );
7668
- const { startHub } = await import("./hub-6LOXFNGG.js");
7672
+ const { startHub } = await import("./hub-YJFLXIG4.js");
7669
7673
  await startHub(
7670
7674
  {
7671
7675
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-4SSCJXAA.js";
4
+ } from "./chunk-QCFTOP3R.js";
5
5
  import "./chunk-3RG5ZIWI.js";
6
6
  export {
7
7
  executeTests,
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-P6CGP7CU.js";
6
- import "./chunk-WKITLEBR.js";
5
+ } from "./chunk-U5YSA4XD.js";
6
+ import "./chunk-DPT5HN3D.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
  export {
9
9
  executeTests,
@@ -14,7 +14,7 @@ import {
14
14
  loadDevState,
15
15
  persistToolRound,
16
16
  setupProxy
17
- } from "./chunk-2QPEE3AY.js";
17
+ } from "./chunk-UGNMM6TM.js";
18
18
  import {
19
19
  CONTENT_ONLY_STREAM_REMINDER,
20
20
  HALLUCINATION_CORRECTION_MESSAGE,
@@ -28,10 +28,10 @@ import {
28
28
  looksLikeDocumentBody,
29
29
  stripPseudoToolCalls,
30
30
  stripToolCallReminder
31
- } from "./chunk-BQL6IYCI.js";
31
+ } from "./chunk-UGKFU3I5.js";
32
32
  import {
33
33
  ConfigManager
34
- } from "./chunk-63YPIF2A.js";
34
+ } from "./chunk-2S27S4DO.js";
35
35
  import {
36
36
  ToolExecutor,
37
37
  ToolRegistry,
@@ -49,14 +49,14 @@ import {
49
49
  spawnAgentContext,
50
50
  truncateOutput,
51
51
  undoStack
52
- } from "./chunk-JER6BUQL.js";
52
+ } from "./chunk-6N5IWWPQ.js";
53
53
  import "./chunk-3BICTI5M.js";
54
54
  import "./chunk-2DXY7UGF.js";
55
- import "./chunk-P6CGP7CU.js";
55
+ import "./chunk-U5YSA4XD.js";
56
56
  import {
57
57
  getDangerLevel,
58
58
  runTool
59
- } from "./chunk-ZOFXDRJT.js";
59
+ } from "./chunk-STUFQVOX.js";
60
60
  import "./chunk-2ZD3YTVM.js";
61
61
  import {
62
62
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -76,7 +76,7 @@ import {
76
76
  SKILLS_DIR_NAME,
77
77
  VERSION,
78
78
  buildUserIdentityPrompt
79
- } from "./chunk-WKITLEBR.js";
79
+ } from "./chunk-DPT5HN3D.js";
80
80
  import {
81
81
  formatGitContextForPrompt,
82
82
  getGitContext,
@@ -2460,7 +2460,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2460
2460
  case "test": {
2461
2461
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2462
2462
  try {
2463
- const { executeTests } = await import("./run-tests-I6RJ35P6.js");
2463
+ const { executeTests } = await import("./run-tests-V3HUOGWE.js");
2464
2464
  const argStr = args.join(" ").trim();
2465
2465
  let testArgs = {};
2466
2466
  if (argStr) {
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-JER6BUQL.js";
4
+ } from "./chunk-6N5IWWPQ.js";
5
5
  import "./chunk-3BICTI5M.js";
6
6
  import "./chunk-2DXY7UGF.js";
7
- import "./chunk-P6CGP7CU.js";
7
+ import "./chunk-U5YSA4XD.js";
8
8
  import {
9
9
  getDangerLevel,
10
10
  runTool,
11
11
  schemaToJsonSchema
12
- } from "./chunk-ZOFXDRJT.js";
12
+ } from "./chunk-STUFQVOX.js";
13
13
  import "./chunk-2ZD3YTVM.js";
14
14
  import {
15
15
  VERSION
16
- } from "./chunk-WKITLEBR.js";
16
+ } from "./chunk-DPT5HN3D.js";
17
17
  import "./chunk-4BKXL7SM.js";
18
18
  import "./chunk-7ZJN4KLV.js";
19
19
  import "./chunk-KHYD3WXE.js";
@@ -3,18 +3,18 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-JER6BUQL.js";
6
+ } from "./chunk-6N5IWWPQ.js";
7
7
  import "./chunk-3BICTI5M.js";
8
8
  import "./chunk-2DXY7UGF.js";
9
- import "./chunk-P6CGP7CU.js";
9
+ import "./chunk-U5YSA4XD.js";
10
10
  import {
11
11
  getDangerLevel,
12
12
  runTool
13
- } from "./chunk-ZOFXDRJT.js";
13
+ } from "./chunk-STUFQVOX.js";
14
14
  import "./chunk-2ZD3YTVM.js";
15
15
  import {
16
16
  SUBAGENT_ALLOWED_TOOLS
17
- } from "./chunk-WKITLEBR.js";
17
+ } from "./chunk-DPT5HN3D.js";
18
18
  import "./chunk-4BKXL7SM.js";
19
19
  import "./chunk-7ZJN4KLV.js";
20
20
  import "./chunk-KHYD3WXE.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.134",
3
+ "version": "0.4.136",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",