jinzd-ai-cli 0.4.237 → 0.4.239

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.
@@ -566,8 +566,8 @@ Important rules:
566
566
  const hint = buildErrorHint(command, combined);
567
567
  throw new ToolError(
568
568
  "bash",
569
- `Exit code ${status}:
570
- ${combined || `exit ${status}`}
569
+ `${formatFailureHeader(status)}
570
+ ${combined || formatFailureFallback(status)}
571
571
 
572
572
  ` + (hint ? `${hint}
573
573
 
@@ -643,8 +643,8 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
643
643
  const hint = buildErrorHint(command, combined);
644
644
  throw new ToolError(
645
645
  "bash",
646
- `Exit code ${execErr.status}:
647
- ${combined || (execErr.message ?? "Unknown error")}
646
+ `${formatFailureHeader(execErr.status)}
647
+ ${combined || execErr.message || formatFailureFallback(execErr.status)}
648
648
 
649
649
  ` + (hint ? `${hint}
650
650
 
@@ -656,6 +656,12 @@ ${combined || (execErr.message ?? "Unknown error")}
656
656
  }
657
657
  }
658
658
  };
659
+ function formatFailureHeader(status) {
660
+ return typeof status === "number" ? `Exit code ${status}:` : "Command failed before exit code could be determined:";
661
+ }
662
+ function formatFailureFallback(status) {
663
+ return typeof status === "number" ? `exit ${status}` : "The shell process ended before reporting a numeric exit code.";
664
+ }
659
665
  function fixWindowsDeleteCommand(command) {
660
666
  return command.replace(
661
667
  /Remove-Item\b([^;\n]*)/gi,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-WI4DN5FN.js";
4
+ } from "./chunk-BTKFZUIJ.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
@@ -509,6 +509,48 @@ function formatBytes(n) {
509
509
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
510
510
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
511
511
  }
512
+ function buildActionSummary(report) {
513
+ const problems = [];
514
+ const steps = [];
515
+ const configuredProviders = report.providers.filter((p) => p.configured).length;
516
+ if (configuredProviders === 0) {
517
+ problems.push("No API keys are configured.");
518
+ steps.push("Run aicli config and add a provider key, or configure Ollama for local use.");
519
+ }
520
+ if (report.npmCheck?.status === "outdated" && report.npmCheck.latest) {
521
+ problems.push(`Installed version ${report.version} is behind npm ${report.npmCheck.latest}.`);
522
+ steps.push("Upgrade with npm i -g jinzd-ai-cli@latest, then run aicli doctor again.");
523
+ }
524
+ if (report.context.skipped.length > 0) {
525
+ const first = report.context.skipped[0];
526
+ problems.push(`Context file skipped: ${first.displayPath} (${first.reason}).`);
527
+ steps.push("Fix or shrink the skipped context file, or adjust the context settings in config.");
528
+ }
529
+ if (report.hooks.pendingTrust > 0) {
530
+ problems.push(`${report.hooks.pendingTrust} project hook(s) are pending trust.`);
531
+ steps.push("Review hooks with /hooks list, then trust only the expected project hooks.");
532
+ }
533
+ if (report.plugins.invalid > 0 || report.plugins.untrusted > 0) {
534
+ problems.push(`Plugin attention needed: ${report.plugins.invalid} invalid, ${report.plugins.untrusted} untrusted.`);
535
+ steps.push("Inspect plugins with /plugin list and /plugin inspect <name>.");
536
+ }
537
+ const disconnected = report.mcp.statuses.filter((s) => !s.connected);
538
+ if (disconnected.length > 0) {
539
+ problems.push(`${disconnected.length} MCP server(s) are disconnected.`);
540
+ steps.push("Run /mcp reconnect; inspect server command/env if it stays disconnected.");
541
+ }
542
+ if (report.recentCrashes.length > 0) {
543
+ problems.push(`${report.recentCrashes.length} recent crash log(s) found.`);
544
+ steps.push("Open the newest crash log path shown below and include it in a bug report if reproducible.");
545
+ }
546
+ const total = report.toolStats.totalCalls;
547
+ const failures = report.toolStats.totalFailures;
548
+ if (total >= 10 && failures / total >= 0.2) {
549
+ problems.push(`Tool failure rate is ${(failures * 100 / total).toFixed(1)}% (${failures}/${total}).`);
550
+ steps.push("Check the Top failing tools section and narrow permissions or command scope.");
551
+ }
552
+ return { problems: problems.slice(0, 3), steps: steps.slice(0, 3) };
553
+ }
512
554
  function formatDoctorReport(report, ansi = true) {
513
555
  const wrap = (code, s) => ansi ? `${code}${s}\x1B[0m` : s;
514
556
  const B = (s) => wrap("\x1B[1m", s);
@@ -523,7 +565,7 @@ function formatDoctorReport(report, ansi = true) {
523
565
  const nc = report.npmCheck;
524
566
  let versionLine = ` version: ${report.version}`;
525
567
  if (nc?.status === "up-to-date") versionLine += ` ${D("(latest on npm)")}`;
526
- else if (nc?.status === "outdated" && nc.latest) versionLine += ` ${Y(`\u2192 ${nc.latest} available`)} ${D("(npm i -g jinzd-ai-cli@latest)")}`;
568
+ else if (nc?.status === "outdated" && nc.latest) versionLine += ` ${Y(`update available: ${nc.latest}`)} ${D("(npm i -g jinzd-ai-cli@latest)")}`;
527
569
  else if (nc?.status === "skipped") versionLine += ` ${D(`(npm check skipped: ${nc.reason ?? "unknown"})`)}`;
528
570
  out.push(versionLine);
529
571
  out.push(` node: ${report.node}`);
@@ -532,6 +574,19 @@ function formatDoctorReport(report, ansi = true) {
532
574
  out.push(` project: ${report.projectRoot}`);
533
575
  out.push(` config: ${report.configDir}`);
534
576
  out.push("");
577
+ const actionSummary = buildActionSummary(report);
578
+ if (actionSummary.problems.length > 0) {
579
+ out.push(B("Problems:"));
580
+ for (const p of actionSummary.problems) out.push(` ${Y("!")} ${p}`);
581
+ out.push("");
582
+ out.push(B("Suggested next steps:"));
583
+ actionSummary.steps.forEach((step, i) => out.push(` ${i + 1}. ${step}`));
584
+ out.push("");
585
+ } else {
586
+ out.push(B("Problems:"));
587
+ out.push(` ${G("OK")} No high-priority issues detected.`);
588
+ out.push("");
589
+ }
535
590
  out.push(B("API Keys:"));
536
591
  for (const p of report.providers) {
537
592
  out.push(` ${p.configured ? G("\u2713") : D("\u25CB")} ${p.id.padEnd(14)} ${p.configured ? G("configured") : D("not configured")}`);
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  formatDoctorReport,
4
4
  runDoctorCli
5
- } from "./chunk-NAPAMAFY.js";
5
+ } from "./chunk-UEFVKTAF.js";
6
6
  import "./chunk-QYQI7ZWK.js";
7
7
  import "./chunk-MSK2BAKT.js";
8
8
  import "./chunk-O3XSFPYH.js";
@@ -5185,8 +5185,8 @@ Important rules:
5185
5185
  const hint = buildErrorHint(command, combined);
5186
5186
  throw new ToolError(
5187
5187
  "bash",
5188
- `Exit code ${status}:
5189
- ${combined || `exit ${status}`}
5188
+ `${formatFailureHeader(status)}
5189
+ ${combined || formatFailureFallback(status)}
5190
5190
 
5191
5191
  ` + (hint ? `${hint}
5192
5192
 
@@ -5262,8 +5262,8 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
5262
5262
  const hint = buildErrorHint(command, combined);
5263
5263
  throw new ToolError(
5264
5264
  "bash",
5265
- `Exit code ${execErr.status}:
5266
- ${combined || (execErr.message ?? "Unknown error")}
5265
+ `${formatFailureHeader(execErr.status)}
5266
+ ${combined || execErr.message || formatFailureFallback(execErr.status)}
5267
5267
 
5268
5268
  ` + (hint ? `${hint}
5269
5269
 
@@ -5275,6 +5275,12 @@ ${combined || (execErr.message ?? "Unknown error")}
5275
5275
  }
5276
5276
  }
5277
5277
  };
5278
+ function formatFailureHeader(status) {
5279
+ return typeof status === "number" ? `Exit code ${status}:` : "Command failed before exit code could be determined:";
5280
+ }
5281
+ function formatFailureFallback(status) {
5282
+ return typeof status === "number" ? `exit ${status}` : "The shell process ended before reporting a numeric exit code.";
5283
+ }
5278
5284
  function fixWindowsDeleteCommand(command) {
5279
5285
  return command.replace(
5280
5286
  /Remove-Item\b([^;\n]*)/gi,
@@ -15081,6 +15087,48 @@ function formatBytes(n) {
15081
15087
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
15082
15088
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
15083
15089
  }
15090
+ function buildActionSummary(report) {
15091
+ const problems = [];
15092
+ const steps = [];
15093
+ const configuredProviders = report.providers.filter((p) => p.configured).length;
15094
+ if (configuredProviders === 0) {
15095
+ problems.push("No API keys are configured.");
15096
+ steps.push("Run aicli config and add a provider key, or configure Ollama for local use.");
15097
+ }
15098
+ if (report.npmCheck?.status === "outdated" && report.npmCheck.latest) {
15099
+ problems.push(`Installed version ${report.version} is behind npm ${report.npmCheck.latest}.`);
15100
+ steps.push("Upgrade with npm i -g jinzd-ai-cli@latest, then run aicli doctor again.");
15101
+ }
15102
+ if (report.context.skipped.length > 0) {
15103
+ const first = report.context.skipped[0];
15104
+ problems.push(`Context file skipped: ${first.displayPath} (${first.reason}).`);
15105
+ steps.push("Fix or shrink the skipped context file, or adjust the context settings in config.");
15106
+ }
15107
+ if (report.hooks.pendingTrust > 0) {
15108
+ problems.push(`${report.hooks.pendingTrust} project hook(s) are pending trust.`);
15109
+ steps.push("Review hooks with /hooks list, then trust only the expected project hooks.");
15110
+ }
15111
+ if (report.plugins.invalid > 0 || report.plugins.untrusted > 0) {
15112
+ problems.push(`Plugin attention needed: ${report.plugins.invalid} invalid, ${report.plugins.untrusted} untrusted.`);
15113
+ steps.push("Inspect plugins with /plugin list and /plugin inspect <name>.");
15114
+ }
15115
+ const disconnected = report.mcp.statuses.filter((s) => !s.connected);
15116
+ if (disconnected.length > 0) {
15117
+ problems.push(`${disconnected.length} MCP server(s) are disconnected.`);
15118
+ steps.push("Run /mcp reconnect; inspect server command/env if it stays disconnected.");
15119
+ }
15120
+ if (report.recentCrashes.length > 0) {
15121
+ problems.push(`${report.recentCrashes.length} recent crash log(s) found.`);
15122
+ steps.push("Open the newest crash log path shown below and include it in a bug report if reproducible.");
15123
+ }
15124
+ const total = report.toolStats.totalCalls;
15125
+ const failures = report.toolStats.totalFailures;
15126
+ if (total >= 10 && failures / total >= 0.2) {
15127
+ problems.push(`Tool failure rate is ${(failures * 100 / total).toFixed(1)}% (${failures}/${total}).`);
15128
+ steps.push("Check the Top failing tools section and narrow permissions or command scope.");
15129
+ }
15130
+ return { problems: problems.slice(0, 3), steps: steps.slice(0, 3) };
15131
+ }
15084
15132
  function formatDoctorReport(report, ansi = true) {
15085
15133
  const wrap = (code, s) => ansi ? `${code}${s}\x1B[0m` : s;
15086
15134
  const B = (s) => wrap("\x1B[1m", s);
@@ -15095,7 +15143,7 @@ function formatDoctorReport(report, ansi = true) {
15095
15143
  const nc = report.npmCheck;
15096
15144
  let versionLine = ` version: ${report.version}`;
15097
15145
  if (nc?.status === "up-to-date") versionLine += ` ${D("(latest on npm)")}`;
15098
- else if (nc?.status === "outdated" && nc.latest) versionLine += ` ${Y(`\u2192 ${nc.latest} available`)} ${D("(npm i -g jinzd-ai-cli@latest)")}`;
15146
+ else if (nc?.status === "outdated" && nc.latest) versionLine += ` ${Y(`update available: ${nc.latest}`)} ${D("(npm i -g jinzd-ai-cli@latest)")}`;
15099
15147
  else if (nc?.status === "skipped") versionLine += ` ${D(`(npm check skipped: ${nc.reason ?? "unknown"})`)}`;
15100
15148
  out.push(versionLine);
15101
15149
  out.push(` node: ${report.node}`);
@@ -15104,6 +15152,19 @@ function formatDoctorReport(report, ansi = true) {
15104
15152
  out.push(` project: ${report.projectRoot}`);
15105
15153
  out.push(` config: ${report.configDir}`);
15106
15154
  out.push("");
15155
+ const actionSummary = buildActionSummary(report);
15156
+ if (actionSummary.problems.length > 0) {
15157
+ out.push(B("Problems:"));
15158
+ for (const p of actionSummary.problems) out.push(` ${Y("!")} ${p}`);
15159
+ out.push("");
15160
+ out.push(B("Suggested next steps:"));
15161
+ actionSummary.steps.forEach((step, i) => out.push(` ${i + 1}. ${step}`));
15162
+ out.push("");
15163
+ } else {
15164
+ out.push(B("Problems:"));
15165
+ out.push(` ${G("OK")} No high-priority issues detected.`);
15166
+ out.push("");
15167
+ }
15107
15168
  out.push(B("API Keys:"));
15108
15169
  for (const p of report.providers) {
15109
15170
  out.push(` ${p.configured ? G("\u2713") : D("\u25CB")} ${p.id.padEnd(14)} ${p.configured ? G("configured") : D("not configured")}`);
@@ -15867,6 +15928,10 @@ async function handleMcp(_args, ctx) {
15867
15928
  ctx.sendToolsList();
15868
15929
  return;
15869
15930
  }
15931
+ if (sub) {
15932
+ ctx.send({ type: "error", message: `The Web UI supports /mcp reconnect and status display only. Run this command in the terminal instead: aicli, then /mcp ${[sub, ..._args.slice(1)].join(" ")}` });
15933
+ return;
15934
+ }
15870
15935
  const statuses = ctx.mcpManager.getStatus();
15871
15936
  if (statuses.length === 0) {
15872
15937
  ctx.send({ type: "info", message: "No MCP servers configured." });
@@ -16668,12 +16733,21 @@ var SessionHandler = class {
16668
16733
  planMode = false;
16669
16734
  runtimeThinking = null;
16670
16735
  sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
16736
+ unknownPricingWarnings = /* @__PURE__ */ new Set();
16671
16737
  /** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
16672
16738
  addWebSessionUsage(u) {
16673
16739
  this.sessionTokenUsage.inputTokens += u.inputTokens;
16674
16740
  this.sessionTokenUsage.outputTokens += u.outputTokens;
16675
16741
  this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
16676
16742
  this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
16743
+ const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
16744
+ if (totalTokens > 0 && !getPricing(this.currentProvider, this.currentModel)) {
16745
+ const key = `${this.currentProvider}/${this.currentModel}`;
16746
+ if (!this.unknownPricingWarnings.has(key)) {
16747
+ this.unknownPricingWarnings.add(key);
16748
+ this.send({ type: "info", message: `Cost notice: pricing is unknown for ${key}. Tokens are tracked, but cost is excluded from totals. Run /cost or aicli usage to review.` });
16749
+ }
16750
+ }
16677
16751
  }
16678
16752
  resetWebSessionUsage() {
16679
16753
  this.sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
@@ -16777,6 +16851,7 @@ var SessionHandler = class {
16777
16851
  models: p.info.models.map((m) => ({ id: m.id, name: m.displayName ?? m.id }))
16778
16852
  }));
16779
16853
  const costUsd = computeCost(this.currentProvider, this.currentModel, this.sessionTokenUsage);
16854
+ const costPricingUnknown = costUsd == null && this.sessionTokenUsage.inputTokens + this.sessionTokenUsage.outputTokens + this.sessionTokenUsage.cacheCreationTokens + this.sessionTokenUsage.cacheReadTokens > 0;
16780
16855
  const sess = this.sessions.current;
16781
16856
  const branches = sess ? sess.listBranches().map((b) => ({
16782
16857
  id: b.id,
@@ -16792,12 +16867,14 @@ var SessionHandler = class {
16792
16867
  model: this.currentModel,
16793
16868
  sessionId: this.sessions.current?.id ?? "",
16794
16869
  sessionTitle: this.sessions.current?.title ?? void 0,
16870
+ cwd: process.cwd(),
16795
16871
  messageCount: this.sessions.current?.messages.length ?? 0,
16796
16872
  planMode: this.planMode,
16797
16873
  thinkingMode: this.runtimeThinking ?? false,
16798
16874
  permissionProfile: this.config.get("defaultPermissionProfile") ?? "legacy",
16799
16875
  tokenUsage: { ...this.sessionTokenUsage },
16800
16876
  costUsd,
16877
+ costPricingUnknown,
16801
16878
  providers: providerList,
16802
16879
  branches,
16803
16880
  activeBranchId: sess?.activeBranchId ?? "main",
@@ -154,7 +154,7 @@ ${content}`);
154
154
  }
155
155
  }
156
156
  async function runTaskMode(config, providers, configManager, topic) {
157
- const { TaskOrchestrator } = await import("./task-orchestrator-D66BJZKU.js");
157
+ const { TaskOrchestrator } = await import("./task-orchestrator-AJRFTQVB.js");
158
158
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
159
159
  let interrupted = false;
160
160
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  scanProject,
15
15
  sessionHasMeaningfulContent,
16
16
  setupProxy
17
- } from "./chunk-KQJ55BTD.js";
17
+ } from "./chunk-PTUOWHOH.js";
18
18
  import {
19
19
  buildReviewPrompt,
20
20
  buildSecurityReviewPrompt
@@ -52,7 +52,7 @@ import {
52
52
  theme,
53
53
  undoStack,
54
54
  updateMemoryApproval
55
- } from "./chunk-WI4DN5FN.js";
55
+ } from "./chunk-BTKFZUIJ.js";
56
56
  import "./chunk-K3CF65QH.js";
57
57
  import "./chunk-UUSRWSSX.js";
58
58
  import "./chunk-CKH4KQ4E.js";
@@ -74,7 +74,7 @@ import {
74
74
  formatDoctorReport,
75
75
  loadContextFiles,
76
76
  writeCrashLog
77
- } from "./chunk-NAPAMAFY.js";
77
+ } from "./chunk-UEFVKTAF.js";
78
78
  import {
79
79
  ProviderRegistry
80
80
  } from "./chunk-QYQI7ZWK.js";
@@ -4289,6 +4289,10 @@ function maskKey(key) {
4289
4289
  if (key.length <= 10) return "****";
4290
4290
  return key.slice(0, 6) + "****" + key.slice(-4);
4291
4291
  }
4292
+ function errorMessage(err) {
4293
+ if (err instanceof Error && err.message) return err.message;
4294
+ return String(err ?? "unknown error");
4295
+ }
4292
4296
  var SetupWizard = class {
4293
4297
  constructor(config) {
4294
4298
  this.config = config;
@@ -4312,7 +4316,17 @@ var SetupWizard = class {
4312
4316
  ${greeting} Starting ai-cli...
4313
4317
  `));
4314
4318
  return true;
4315
- } catch {
4319
+ } catch (err) {
4320
+ const message = errorMessage(err);
4321
+ const canceled = /user force closed|interrupted|cancel|aborted/i.test(message);
4322
+ if (canceled) {
4323
+ console.log(theme.warning("\nSetup canceled. Run aicli config to try again.\n"));
4324
+ } else {
4325
+ console.log(theme.error(`
4326
+ First-run setup failed: ${message}`));
4327
+ console.log(theme.dim(`Config directory: ${this.config.getConfigDir()}`));
4328
+ console.log(theme.dim("Run aicli config to retry, or aicli doctor to inspect provider/config health.\n"));
4329
+ }
4316
4330
  return false;
4317
4331
  }
4318
4332
  }
@@ -4340,6 +4354,7 @@ ${greeting} Starting ai-cli...
4340
4354
  { value: "proxy", name: `Configure proxy (HTTP/HTTPS) ${proxyStatus}` },
4341
4355
  { value: "google", name: `Configure Google Search (API Key + CX) ${googleKeyStatus}` },
4342
4356
  { value: "behavior", name: `Behavior toggles (YOLO, MCP, ...) ${behaviorStatus}` },
4357
+ { value: "preset", name: "Apply task preset (safe review, workspace coding, local-only, team web)" },
4343
4358
  { value: "done", name: "Done" }
4344
4359
  ]
4345
4360
  });
@@ -4351,6 +4366,8 @@ ${greeting} Starting ai-cli...
4351
4366
  await this.setupGoogleSearch();
4352
4367
  } else if (action === "behavior") {
4353
4368
  await this.setupBehavior();
4369
+ } else if (action === "preset") {
4370
+ await this.setupPreset();
4354
4371
  } else if (action === "apikey") {
4355
4372
  const choicesWithStatus = PROVIDERS.map((p) => {
4356
4373
  if (NO_KEY_PROVIDERS.has(p.value)) {
@@ -4379,6 +4396,42 @@ ${greeting} Starting ai-cli...
4379
4396
  }
4380
4397
  }
4381
4398
  }
4399
+ async setupPreset() {
4400
+ const preset = await select({
4401
+ message: "Choose a task-oriented preset:",
4402
+ choices: [
4403
+ { value: "safe-review", name: "Safe review only - read-only tools, network confirm" },
4404
+ { value: "workspace-coding", name: "Workspace coding - write in workspace with confirmations" },
4405
+ { value: "local-ollama", name: "Local-only Ollama - local provider, external network denied" },
4406
+ { value: "team-web", name: "Team Web UI - workspace coding plus auth reminder" }
4407
+ ]
4408
+ });
4409
+ const currentNetwork = this.config.get("networkPolicy");
4410
+ if (preset === "safe-review") {
4411
+ this.config.set("defaultPermissionProfile", "read-only");
4412
+ this.config.set("alwaysYolo", false);
4413
+ this.config.set("networkPolicy", { ...currentNetwork, enabled: true, defaultAction: "confirm", tools: { ...currentNetwork.tools, shell: "confirm", web_fetch: "confirm", web_search: "confirm", google_search: "confirm", mcp: "confirm" } });
4414
+ console.log(theme.success("Preset applied: Safe review only. Writes require denial/confirmation according to the read-only profile."));
4415
+ } else if (preset === "workspace-coding") {
4416
+ this.config.set("defaultPermissionProfile", "workspace-write");
4417
+ this.config.set("alwaysYolo", false);
4418
+ this.config.set("networkPolicy", { ...currentNetwork, enabled: true, defaultAction: "confirm", tools: { ...currentNetwork.tools, shell: "confirm", web_fetch: "confirm", web_search: "confirm", google_search: "confirm", mcp: "confirm" } });
4419
+ console.log(theme.success("Preset applied: Workspace coding. File writes stay inside the workspace and sensitive actions still ask."));
4420
+ } else if (preset === "local-ollama") {
4421
+ this.config.set("defaultProvider", "ollama");
4422
+ this.config.set("defaultPermissionProfile", "workspace-write");
4423
+ this.config.set("alwaysYolo", false);
4424
+ this.config.set("networkPolicy", { ...currentNetwork, enabled: true, defaultAction: "deny", allowPrivateNetwork: true, tools: { ...currentNetwork.tools, shell: "confirm", web_fetch: "deny", web_search: "deny", google_search: "deny", mcp: "confirm" } });
4425
+ console.log(theme.success("Preset applied: Local-only Ollama. External web/search is denied; private network is allowed for local services."));
4426
+ } else {
4427
+ this.config.set("defaultPermissionProfile", "workspace-write");
4428
+ this.config.set("alwaysYolo", false);
4429
+ this.config.set("networkPolicy", { ...currentNetwork, enabled: true, defaultAction: "confirm", tools: { ...currentNetwork.tools, shell: "confirm", web_fetch: "confirm", web_search: "confirm", google_search: "confirm", mcp: "confirm" } });
4430
+ console.log(theme.warning("Preset applied: Team Web UI. Start Web with auth enabled and put it behind your normal TLS/reverse proxy boundary."));
4431
+ }
4432
+ this.config.save();
4433
+ console.log(theme.dim("Review changes with aicli doctor or aicli config.\n"));
4434
+ }
4382
4435
  async setupProvider(providerId) {
4383
4436
  if (NO_KEY_PROVIDERS.has(providerId)) {
4384
4437
  await this.setupOllama();
@@ -4396,25 +4449,18 @@ Managing ${displayName} API Key`);
4396
4449
  }
4397
4450
  const choices = existingKey ? [
4398
4451
  { value: "keep", name: "Keep current key" },
4399
- { value: "show", name: "Show full key" },
4452
+ { value: "fingerprint", name: `Copy masked fingerprint (${maskKey(existingKey)})` },
4400
4453
  { value: "change", name: "Update key" }
4401
4454
  ] : [{ value: "change", name: "Enter key" }, { value: "skip", name: "Skip" }];
4402
4455
  const action = await select({
4403
4456
  message: "Action:",
4404
4457
  choices
4405
4458
  });
4406
- if (action === "show" && existingKey) {
4407
- console.log(theme.warning(`
4408
- Full Key: ${existingKey}
4409
- `));
4410
- const updateAfterShow = await select({
4411
- message: "Would you like to update this key?",
4412
- choices: [
4413
- { value: "keep", name: "Keep current" },
4414
- { value: "change", name: "Update" }
4415
- ]
4416
- });
4417
- if (updateAfterShow !== "change") return;
4459
+ if (action === "fingerprint" && existingKey) {
4460
+ console.log(theme.dim(`
4461
+ Fingerprint: ${maskKey(existingKey)}`));
4462
+ console.log(theme.dim(" Full secrets are not printed. Use Update key to replace it.\n"));
4463
+ return;
4418
4464
  } else if (action === "keep" || action === "skip") {
4419
4465
  return;
4420
4466
  }
@@ -5366,6 +5412,7 @@ var Repl = class {
5366
5412
  contextLoadResult = null;
5367
5413
  /** 本次会话累计 token 用量 */
5368
5414
  sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
5415
+ unknownPricingWarnings = /* @__PURE__ */ new Set();
5369
5416
  /** Fold a single-request TokenUsage (with optional cache fields) into sessionTokenUsage + cost tracker.
5370
5417
  * modelOverride lets the smart router attribute cost to the actually-used model
5371
5418
  * when it differs from the UI-selected currentModel. */
@@ -5374,7 +5421,16 @@ var Repl = class {
5374
5421
  this.sessionTokenUsage.outputTokens += u.outputTokens;
5375
5422
  this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
5376
5423
  this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
5377
- this.costTracker.addCost(this.currentProvider, modelOverride ?? this.currentModel, u);
5424
+ const effectiveModel = modelOverride ?? this.currentModel;
5425
+ const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
5426
+ if (totalTokens > 0 && !getPricing(this.currentProvider, effectiveModel)) {
5427
+ const key = `${this.currentProvider}/${effectiveModel}`;
5428
+ if (!this.unknownPricingWarnings.has(key)) {
5429
+ this.unknownPricingWarnings.add(key);
5430
+ this.renderer.printInfo(`Cost notice: pricing is unknown for ${key}. Tokens are tracked, but cost is excluded from totals. Run aicli usage to review unknown-priced models.`);
5431
+ }
5432
+ }
5433
+ this.costTracker.addCost(this.currentProvider, effectiveModel, u);
5378
5434
  }
5379
5435
  /** 启动时检测到的 Git 分支(无 git 仓库时为 null) */
5380
5436
  gitBranch = null;
@@ -7747,7 +7803,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7747
7803
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7748
7804
  process.exit(1);
7749
7805
  }
7750
- const { startWebServer } = await import("./server-J36CY5EU.js");
7806
+ const { startWebServer } = await import("./server-DWQTKBCQ.js");
7751
7807
  await startWebServer({ port, host: options.host });
7752
7808
  });
7753
7809
  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) => {
@@ -7918,7 +7974,7 @@ program.command("usage").description("Show token + cost usage grouped by provide
7918
7974
  await runUsageCli(options);
7919
7975
  });
7920
7976
  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) => {
7921
- const { runDoctorCli } = await import("./doctor-cli-E5MBY7CZ.js");
7977
+ const { runDoctorCli } = await import("./doctor-cli-Q3XR5DQT.js");
7922
7978
  const argv = process.argv.slice(2);
7923
7979
  await runDoctorCli({
7924
7980
  json: !!options.json || argv.includes("--json"),
@@ -7970,7 +8026,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7970
8026
  }
7971
8027
  });
7972
8028
  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) => {
7973
- const { startMcpServer } = await import("./server-JEVAHG53.js");
8029
+ const { startMcpServer } = await import("./server-77FK46KF.js");
7974
8030
  await startMcpServer({
7975
8031
  allowDestructive: !!options.allowDestructive,
7976
8032
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -8162,7 +8218,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
8162
8218
  config.get("customProviders"),
8163
8219
  config.getConfigDir()
8164
8220
  );
8165
- const { startHub } = await import("./hub-2WPMT242.js");
8221
+ const { startHub } = await import("./hub-5TKAR7Y7.js");
8166
8222
  await startHub(
8167
8223
  {
8168
8224
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-WI4DN5FN.js";
4
+ } from "./chunk-BTKFZUIJ.js";
5
5
  import "./chunk-K3CF65QH.js";
6
6
  import "./chunk-UUSRWSSX.js";
7
7
  import "./chunk-CKH4KQ4E.js";
@@ -23,7 +23,7 @@ import {
23
23
  scanDirTree,
24
24
  scanProject,
25
25
  setupProxy
26
- } from "./chunk-KQJ55BTD.js";
26
+ } from "./chunk-PTUOWHOH.js";
27
27
  import {
28
28
  buildReviewPrompt,
29
29
  buildSecurityReviewPrompt
@@ -67,7 +67,7 @@ import {
67
67
  truncateOutput,
68
68
  undoStack,
69
69
  updateMemoryApproval
70
- } from "./chunk-WI4DN5FN.js";
70
+ } from "./chunk-BTKFZUIJ.js";
71
71
  import "./chunk-K3CF65QH.js";
72
72
  import "./chunk-UUSRWSSX.js";
73
73
  import "./chunk-CKH4KQ4E.js";
@@ -78,13 +78,14 @@ import {
78
78
  } from "./chunk-JMP3LJC2.js";
79
79
  import {
80
80
  computeCost,
81
- formatCost
81
+ formatCost,
82
+ getPricing
82
83
  } from "./chunk-E44DTERW.js";
83
84
  import {
84
85
  buildDoctorReport,
85
86
  formatDoctorReport,
86
87
  loadContextFiles
87
- } from "./chunk-NAPAMAFY.js";
88
+ } from "./chunk-UEFVKTAF.js";
88
89
  import {
89
90
  ProviderRegistry
90
91
  } from "./chunk-QYQI7ZWK.js";
@@ -1431,6 +1432,10 @@ async function handleMcp(_args, ctx) {
1431
1432
  ctx.sendToolsList();
1432
1433
  return;
1433
1434
  }
1435
+ if (sub) {
1436
+ ctx.send({ type: "error", message: `The Web UI supports /mcp reconnect and status display only. Run this command in the terminal instead: aicli, then /mcp ${[sub, ..._args.slice(1)].join(" ")}` });
1437
+ return;
1438
+ }
1434
1439
  const statuses = ctx.mcpManager.getStatus();
1435
1440
  if (statuses.length === 0) {
1436
1441
  ctx.send({ type: "info", message: "No MCP servers configured." });
@@ -2232,12 +2237,21 @@ var SessionHandler = class {
2232
2237
  planMode = false;
2233
2238
  runtimeThinking = null;
2234
2239
  sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
2240
+ unknownPricingWarnings = /* @__PURE__ */ new Set();
2235
2241
  /** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
2236
2242
  addWebSessionUsage(u) {
2237
2243
  this.sessionTokenUsage.inputTokens += u.inputTokens;
2238
2244
  this.sessionTokenUsage.outputTokens += u.outputTokens;
2239
2245
  this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
2240
2246
  this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
2247
+ const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
2248
+ if (totalTokens > 0 && !getPricing(this.currentProvider, this.currentModel)) {
2249
+ const key = `${this.currentProvider}/${this.currentModel}`;
2250
+ if (!this.unknownPricingWarnings.has(key)) {
2251
+ this.unknownPricingWarnings.add(key);
2252
+ this.send({ type: "info", message: `Cost notice: pricing is unknown for ${key}. Tokens are tracked, but cost is excluded from totals. Run /cost or aicli usage to review.` });
2253
+ }
2254
+ }
2241
2255
  }
2242
2256
  resetWebSessionUsage() {
2243
2257
  this.sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
@@ -2341,6 +2355,7 @@ var SessionHandler = class {
2341
2355
  models: p.info.models.map((m) => ({ id: m.id, name: m.displayName ?? m.id }))
2342
2356
  }));
2343
2357
  const costUsd = computeCost(this.currentProvider, this.currentModel, this.sessionTokenUsage);
2358
+ const costPricingUnknown = costUsd == null && this.sessionTokenUsage.inputTokens + this.sessionTokenUsage.outputTokens + this.sessionTokenUsage.cacheCreationTokens + this.sessionTokenUsage.cacheReadTokens > 0;
2344
2359
  const sess = this.sessions.current;
2345
2360
  const branches = sess ? sess.listBranches().map((b) => ({
2346
2361
  id: b.id,
@@ -2356,12 +2371,14 @@ var SessionHandler = class {
2356
2371
  model: this.currentModel,
2357
2372
  sessionId: this.sessions.current?.id ?? "",
2358
2373
  sessionTitle: this.sessions.current?.title ?? void 0,
2374
+ cwd: process.cwd(),
2359
2375
  messageCount: this.sessions.current?.messages.length ?? 0,
2360
2376
  planMode: this.planMode,
2361
2377
  thinkingMode: this.runtimeThinking ?? false,
2362
2378
  permissionProfile: this.config.get("defaultPermissionProfile") ?? "legacy",
2363
2379
  tokenUsage: { ...this.sessionTokenUsage },
2364
2380
  costUsd,
2381
+ costPricingUnknown,
2365
2382
  providers: providerList,
2366
2383
  branches,
2367
2384
  activeBranchId: sess?.activeBranchId ?? "main",
@@ -3,7 +3,7 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-WI4DN5FN.js";
6
+ } from "./chunk-BTKFZUIJ.js";
7
7
  import "./chunk-K3CF65QH.js";
8
8
  import "./chunk-UUSRWSSX.js";
9
9
  import "./chunk-CKH4KQ4E.js";