cc-claw 0.17.2 → 0.18.1

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.
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var VERSION;
33
33
  var init_version = __esm({
34
34
  "src/version.ts"() {
35
35
  "use strict";
36
- VERSION = true ? "0.17.2" : (() => {
36
+ VERSION = true ? "0.18.1" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -57,16 +57,25 @@ __export(paths_exports, {
57
57
  LOG_PATH: () => LOG_PATH,
58
58
  MEDIA_PATH: () => MEDIA_PATH,
59
59
  RUNNERS_PATH: () => RUNNERS_PATH,
60
+ SESSION_LOGS_PATH: () => SESSION_LOGS_PATH,
60
61
  SKILLS_PATH: () => SKILLS_PATH,
61
62
  WORKSPACE_PATH: () => WORKSPACE_PATH
62
63
  });
63
- import { homedir } from "os";
64
+ import { homedir, userInfo } from "os";
64
65
  import { join as join2 } from "path";
65
- var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, IDENTITY_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH, MEDIA_PATH;
66
+ function resolveRealHome() {
67
+ try {
68
+ const info = userInfo();
69
+ if (info.homedir) return info.homedir;
70
+ } catch {
71
+ }
72
+ return homedir();
73
+ }
74
+ var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, IDENTITY_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH, SESSION_LOGS_PATH, MEDIA_PATH;
66
75
  var init_paths = __esm({
67
76
  "src/paths.ts"() {
68
77
  "use strict";
69
- CC_CLAW_HOME = process.env.CC_CLAW_HOME ?? join2(homedir(), ".cc-claw");
78
+ CC_CLAW_HOME = process.env.CC_CLAW_HOME ?? join2(resolveRealHome(), ".cc-claw");
70
79
  ENV_PATH = join2(CC_CLAW_HOME, ".env");
71
80
  DATA_PATH = join2(CC_CLAW_HOME, "data");
72
81
  DB_PATH = process.env.DB_PATH ?? join2(DATA_PATH, "cc-claw.db");
@@ -78,6 +87,7 @@ var init_paths = __esm({
78
87
  SKILLS_PATH = join2(WORKSPACE_PATH, "skills");
79
88
  RUNNERS_PATH = join2(CC_CLAW_HOME, "runners");
80
89
  AGENTS_PATH = join2(CC_CLAW_HOME, "agents");
90
+ SESSION_LOGS_PATH = join2(LOGS_PATH, "sessions");
81
91
  MEDIA_PATH = join2(CC_CLAW_HOME, "media");
82
92
  }
83
93
  });
@@ -1735,6 +1745,18 @@ function initSchema(db3) {
1735
1745
  mode TEXT NOT NULL DEFAULT 'approved'
1736
1746
  );
1737
1747
  `);
1748
+ db3.exec(`
1749
+ CREATE TABLE IF NOT EXISTS chat_show_thinking_ui (
1750
+ chat_id TEXT PRIMARY KEY,
1751
+ value INTEGER NOT NULL DEFAULT 0
1752
+ );
1753
+ `);
1754
+ db3.exec(`
1755
+ CREATE TABLE IF NOT EXISTS chat_session_log (
1756
+ chat_id TEXT PRIMARY KEY,
1757
+ value INTEGER NOT NULL DEFAULT 0
1758
+ );
1759
+ `);
1738
1760
  db3.exec(`
1739
1761
  CREATE TABLE IF NOT EXISTS backend_credentials (
1740
1762
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -2419,6 +2441,55 @@ var init_sessions = __esm({
2419
2441
  });
2420
2442
 
2421
2443
  // src/memory/chat-settings.ts
2444
+ var chat_settings_exports = {};
2445
+ __export(chat_settings_exports, {
2446
+ ALL_TOOLS: () => ALL_TOOLS,
2447
+ clearAgentMode: () => clearAgentMode,
2448
+ clearCwd: () => clearCwd,
2449
+ clearExecMode: () => clearExecMode,
2450
+ clearModel: () => clearModel,
2451
+ clearSummarizer: () => clearSummarizer,
2452
+ clearThinkingLevel: () => clearThinkingLevel,
2453
+ deleteBookmark: () => deleteBookmark,
2454
+ determineEscalationTarget: () => determineEscalationTarget,
2455
+ findBookmarksByPrefix: () => findBookmarksByPrefix,
2456
+ getAgentMode: () => getAgentMode,
2457
+ getAllBookmarks: () => getAllBookmarks,
2458
+ getBackend: () => getBackend,
2459
+ getBookmark: () => getBookmark,
2460
+ getCwd: () => getCwd,
2461
+ getEnabledTools: () => getEnabledTools,
2462
+ getExecMode: () => getExecMode,
2463
+ getMode: () => getMode,
2464
+ getModel: () => getModel,
2465
+ getPendingEscalation: () => getPendingEscalation,
2466
+ getRecentBookmarks: () => getRecentBookmarks,
2467
+ getSessionLogEnabled: () => getSessionLogEnabled,
2468
+ getShowThinkingUi: () => getShowThinkingUi,
2469
+ getSummarizer: () => getSummarizer,
2470
+ getThinkingLevel: () => getThinkingLevel,
2471
+ getToolsMap: () => getToolsMap,
2472
+ getVerboseLevel: () => getVerboseLevel,
2473
+ removePendingEscalation: () => removePendingEscalation,
2474
+ resetTools: () => resetTools,
2475
+ setAgentMode: () => setAgentMode,
2476
+ setBackend: () => setBackend,
2477
+ setCwd: () => setCwd,
2478
+ setExecMode: () => setExecMode,
2479
+ setMode: () => setMode,
2480
+ setModel: () => setModel,
2481
+ setSessionLogEnabled: () => setSessionLogEnabled,
2482
+ setShowThinkingUi: () => setShowThinkingUi,
2483
+ setSummarizer: () => setSummarizer,
2484
+ setThinkingLevel: () => setThinkingLevel,
2485
+ setVerboseLevel: () => setVerboseLevel,
2486
+ storePendingEscalation: () => storePendingEscalation,
2487
+ toggleSessionLogEnabled: () => toggleSessionLogEnabled,
2488
+ toggleShowThinkingUi: () => toggleShowThinkingUi,
2489
+ toggleTool: () => toggleTool,
2490
+ touchBookmark: () => touchBookmark,
2491
+ upsertBookmark: () => upsertBookmark
2492
+ });
2422
2493
  function getCwd(chatId) {
2423
2494
  const row = getDb().prepare(
2424
2495
  "SELECT cwd FROM chat_cwd WHERE chat_id = ?"
@@ -2517,6 +2588,25 @@ function setVerboseLevel(chatId, level) {
2517
2588
  ON CONFLICT(chat_id) DO UPDATE SET level = ?
2518
2589
  `).run(chatId, level, level);
2519
2590
  }
2591
+ function getShowThinkingUi(chatId) {
2592
+ const row = getDb().prepare(
2593
+ "SELECT value FROM chat_show_thinking_ui WHERE chat_id = ?"
2594
+ ).get(chatId);
2595
+ return (row?.value ?? 0) === 1;
2596
+ }
2597
+ function setShowThinkingUi(chatId, enabled) {
2598
+ getDb().prepare(`
2599
+ INSERT INTO chat_show_thinking_ui (chat_id, value)
2600
+ VALUES (?, ?)
2601
+ ON CONFLICT(chat_id) DO UPDATE SET value = ?
2602
+ `).run(chatId, enabled ? 1 : 0, enabled ? 1 : 0);
2603
+ }
2604
+ function toggleShowThinkingUi(chatId) {
2605
+ const current = getShowThinkingUi(chatId);
2606
+ const next = !current;
2607
+ setShowThinkingUi(chatId, next);
2608
+ return next;
2609
+ }
2520
2610
  function getMode(chatId) {
2521
2611
  const row = getDb().prepare(
2522
2612
  "SELECT mode FROM chat_mode WHERE chat_id = ?"
@@ -2665,6 +2755,25 @@ function setExecMode(chatId, mode) {
2665
2755
  function clearExecMode(chatId) {
2666
2756
  getDb().prepare("DELETE FROM chat_exec_mode WHERE chat_id = ?").run(chatId);
2667
2757
  }
2758
+ function getSessionLogEnabled(chatId) {
2759
+ const row = getDb().prepare(
2760
+ "SELECT value FROM chat_session_log WHERE chat_id = ?"
2761
+ ).get(chatId);
2762
+ return (row?.value ?? 0) === 1;
2763
+ }
2764
+ function setSessionLogEnabled(chatId, enabled) {
2765
+ getDb().prepare(`
2766
+ INSERT INTO chat_session_log (chat_id, value)
2767
+ VALUES (?, ?)
2768
+ ON CONFLICT(chat_id) DO UPDATE SET value = ?
2769
+ `).run(chatId, enabled ? 1 : 0, enabled ? 1 : 0);
2770
+ }
2771
+ function toggleSessionLogEnabled(chatId) {
2772
+ const current = getSessionLogEnabled(chatId);
2773
+ const next = !current;
2774
+ setSessionLogEnabled(chatId, next);
2775
+ return next;
2776
+ }
2668
2777
  var pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS;
2669
2778
  var init_chat_settings = __esm({
2670
2779
  "src/memory/chat-settings.ts"() {
@@ -3529,9 +3638,11 @@ __export(store_exports5, {
3529
3638
  getRecentMessageLog: () => getRecentMessageLog,
3530
3639
  getResponseStyle: () => getResponseStyle,
3531
3640
  getSessionId: () => getSessionId,
3641
+ getSessionLogEnabled: () => getSessionLogEnabled,
3532
3642
  getSessionStartedAt: () => getSessionStartedAt,
3533
3643
  getSessionSummaries: () => getSessionSummaries,
3534
3644
  getSessionSummariesWithoutEmbeddings: () => getSessionSummariesWithoutEmbeddings,
3645
+ getShowThinkingUi: () => getShowThinkingUi,
3535
3646
  getSummarizer: () => getSummarizer,
3536
3647
  getThinkingLevel: () => getThinkingLevel,
3537
3648
  getToolsMap: () => getToolsMap,
@@ -3590,12 +3701,16 @@ __export(store_exports5, {
3590
3701
  setModelSignature: () => setModelSignature,
3591
3702
  setResponseStyle: () => setResponseStyle,
3592
3703
  setSessionId: () => setSessionId,
3704
+ setSessionLogEnabled: () => setSessionLogEnabled,
3593
3705
  setSessionStartedAt: () => setSessionStartedAt,
3706
+ setShowThinkingUi: () => setShowThinkingUi,
3594
3707
  setSummarizer: () => setSummarizer,
3595
3708
  setThinkingLevel: () => setThinkingLevel,
3596
3709
  setVerboseLevel: () => setVerboseLevel,
3597
3710
  storePendingEscalation: () => storePendingEscalation,
3598
3711
  toFts5Query: () => toFts5Query,
3712
+ toggleSessionLogEnabled: () => toggleSessionLogEnabled,
3713
+ toggleShowThinkingUi: () => toggleShowThinkingUi,
3599
3714
  toggleTool: () => toggleTool,
3600
3715
  touchBookmark: () => touchBookmark,
3601
3716
  updateHeartbeatTimestamps: () => updateHeartbeatTimestamps,
@@ -3914,10 +4029,16 @@ var init_claude = __esm({
3914
4029
  });
3915
4030
  }
3916
4031
  const content = message?.content ?? [];
3917
- const textParts = [];
4032
+ let hasTextContent = false;
4033
+ let textContent = "";
3918
4034
  for (const block of content) {
3919
4035
  if (block.type === "text" && block.text) {
3920
- textParts.push(block.text);
4036
+ textContent += block.text;
4037
+ hasTextContent = true;
4038
+ } else if (block.type === "thinking" && block.thinking) {
4039
+ events.push({ type: "thinking", text: block.thinking });
4040
+ } else if (block.type === "redacted_thinking") {
4041
+ events.push({ type: "thinking", text: "[Redacted Thinking]" });
3921
4042
  } else if (block.type === "tool_use") {
3922
4043
  events.push({
3923
4044
  type: "tool_start",
@@ -3927,8 +4048,16 @@ var init_claude = __esm({
3927
4048
  });
3928
4049
  }
3929
4050
  }
3930
- if (textParts.length > 0) {
3931
- const cleaned = stripThinkingContent(textParts.join(""));
4051
+ if (hasTextContent) {
4052
+ const matches = [...textContent.matchAll(/<thinking>([\s\S]*?)<\/thinking>/gi)];
4053
+ for (const match of matches) {
4054
+ events.push({ type: "thinking", text: match[1].trim() });
4055
+ }
4056
+ const matchesThink = [...textContent.matchAll(/<think>([\s\S]*?)<\/think>/gi)];
4057
+ for (const match of matchesThink) {
4058
+ events.push({ type: "thinking", text: match[1].trim() });
4059
+ }
4060
+ const cleaned = stripThinkingContent(textContent);
3932
4061
  if (cleaned) events.push({ type: "text", text: cleaned });
3933
4062
  }
3934
4063
  } else if (line.type === "user") {
@@ -4282,8 +4411,18 @@ var init_gemini = __esm({
4282
4411
  events.push({ type: "init", sessionId: line.session_id });
4283
4412
  } else if (line.type === "message" && line.role === "assistant") {
4284
4413
  if (line.thought === true) {
4414
+ events.push({ type: "thinking", text: String(line.content ?? "") });
4285
4415
  } else if (line.content) {
4286
- const cleaned = stripThinkingContent(line.content);
4416
+ let contentStr = line.content;
4417
+ const matches = [...contentStr.matchAll(/<thinking>([\s\S]*?)<\/thinking>/gi)];
4418
+ for (const match of matches) {
4419
+ events.push({ type: "thinking", text: match[1].trim() });
4420
+ }
4421
+ const matchesThink = [...contentStr.matchAll(/<think>([\s\S]*?)<\/think>/gi)];
4422
+ for (const match of matchesThink) {
4423
+ events.push({ type: "thinking", text: match[1].trim() });
4424
+ }
4425
+ const cleaned = stripThinkingContent(contentStr);
4287
4426
  if (cleaned) {
4288
4427
  events.push({ type: "text", text: cleaned });
4289
4428
  }
@@ -4490,26 +4629,74 @@ var init_codex = __esm({
4490
4629
  events.push({ type: "init", sessionId: line.thread_id });
4491
4630
  } else if (line.type === "item.completed") {
4492
4631
  const item = line.item;
4493
- if (item?.type === "agent_message" && item.text) {
4632
+ if (!item) return events;
4633
+ if (item.type === "agent_message" && item.text) {
4494
4634
  const cleaned = stripThinkingContent(item.text);
4495
4635
  if (cleaned) events.push({ type: "text", text: cleaned });
4496
- } else if (item?.type === "command_execution") {
4636
+ } else if (item.type === "command_execution") {
4497
4637
  events.push({
4498
4638
  type: "tool_end",
4499
4639
  toolName: "Shell",
4500
4640
  toolId: item.id,
4501
4641
  toolOutput: item.aggregated_output ?? ""
4502
4642
  });
4643
+ } else if (item.type === "web_search") {
4644
+ const action = item.action;
4645
+ const query = action?.query || item.query || "";
4646
+ events.push({
4647
+ type: "tool_end",
4648
+ toolName: "Web Search",
4649
+ toolId: item.id,
4650
+ toolOutput: query ? `Searched: ${query}` : "Search completed"
4651
+ });
4652
+ } else if (item.type === "file_search") {
4653
+ events.push({
4654
+ type: "tool_end",
4655
+ toolName: "File Search",
4656
+ toolId: item.id,
4657
+ toolOutput: item.query ?? ""
4658
+ });
4659
+ } else if (item.type === "mcp_tool_call") {
4660
+ events.push({
4661
+ type: "tool_end",
4662
+ toolName: item.name ?? "MCP Tool",
4663
+ toolId: item.id,
4664
+ toolOutput: typeof item.output === "string" ? item.output : ""
4665
+ });
4503
4666
  }
4504
4667
  } else if (line.type === "item.started") {
4505
4668
  const item = line.item;
4506
- if (item?.type === "command_execution") {
4669
+ if (!item) return events;
4670
+ if (item.type === "command_execution") {
4507
4671
  events.push({
4508
4672
  type: "tool_start",
4509
4673
  toolName: "Shell",
4510
4674
  toolId: item.id,
4511
4675
  toolInput: { command: item.command ?? "" }
4512
4676
  });
4677
+ } else if (item.type === "web_search") {
4678
+ const action = item.action;
4679
+ const query = action?.query || item.query || "";
4680
+ events.push({
4681
+ type: "tool_start",
4682
+ toolName: "Web Search",
4683
+ toolId: item.id,
4684
+ toolInput: { query: query || "Searching\u2026" }
4685
+ });
4686
+ } else if (item.type === "file_search") {
4687
+ events.push({
4688
+ type: "tool_start",
4689
+ toolName: "File Search",
4690
+ toolId: item.id,
4691
+ toolInput: { query: item.query ?? "" }
4692
+ });
4693
+ } else if (item.type === "mcp_tool_call") {
4694
+ events.push({
4695
+ type: "tool_start",
4696
+ toolName: item.name ?? "MCP Tool",
4697
+ toolId: item.id,
4698
+ toolInput: item.arguments ?? {}
4699
+ });
4513
4700
  }
4514
4701
  } else if (line.type === "turn.completed") {
4515
4702
  const u = line.usage;
@@ -5292,6 +5479,11 @@ If the user asks *how* to do something with CC-Claw, use this expertise to sugge
5292
5479
  });
5293
5480
 
5294
5481
  // src/bootstrap/init.ts
5482
+ var init_exports = {};
5483
+ __export(init_exports, {
5484
+ bootstrapWorkspaceFiles: () => bootstrapWorkspaceFiles,
5485
+ syncNativeCliFiles: () => syncNativeCliFiles
5486
+ });
5295
5487
  import {
5296
5488
  existsSync as existsSync8,
5297
5489
  writeFileSync,
@@ -8677,15 +8869,37 @@ var init_scheduler = __esm({
8677
8869
  validateAgentIdentity(req, body);
8678
8870
  const { updateJob: updateJob2, getJobById: getJobById3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8679
8871
  const { stopJobTimer: stopJobTimer2, startSingleJob: startSingleJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8680
- const updated = updateJob2(body.jobId, body.updates);
8681
- if (updated) {
8682
- stopJobTimer2(body.jobId);
8683
- const freshJob = getJobById3(body.jobId);
8684
- if (freshJob && freshJob.active && freshJob.enabled) {
8685
- startSingleJob2(freshJob);
8686
- }
8872
+ const existing = getJobById3(body.jobId);
8873
+ if (!existing) {
8874
+ return jsonResponse(res, { success: false, error: `Job #${body.jobId} not found.` }, 404);
8875
+ }
8876
+ const updates = { ...body.updates };
8877
+ if (updates.cron !== void 0) {
8878
+ updates.scheduleType = "cron";
8879
+ updates.atTime = null;
8880
+ updates.everyMs = null;
8881
+ } else if (updates.atTime !== void 0) {
8882
+ updates.scheduleType = "at";
8883
+ updates.cron = null;
8884
+ updates.everyMs = null;
8885
+ } else if (updates.everyMs !== void 0) {
8886
+ updates.scheduleType = "every";
8887
+ updates.cron = null;
8888
+ updates.atTime = null;
8889
+ }
8890
+ const updated = updateJob2(body.jobId, updates);
8891
+ if (!updated) {
8892
+ return jsonResponse(res, {
8893
+ success: false,
8894
+ error: `No recognized fields to update. Valid fields: cron, atTime, everyMs, title, description, backend, model, thinking, timeout, timezone, sessionType, deliveryMode, channel, target.`
8895
+ }, 400);
8687
8896
  }
8688
- jsonResponse(res, { success: updated });
8897
+ stopJobTimer2(body.jobId);
8898
+ const freshJob = getJobById3(body.jobId);
8899
+ if (freshJob && freshJob.active && freshJob.enabled) {
8900
+ startSingleJob2(freshJob);
8901
+ }
8902
+ jsonResponse(res, { success: true });
8689
8903
  } catch (err) {
8690
8904
  jsonResponse(res, { error: errorMessage(err) }, 400);
8691
8905
  }
@@ -10178,11 +10392,11 @@ var init_evolve = __esm({
10178
10392
  const body = JSON.parse(await readBody(req));
10179
10393
  const { setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
10180
10394
  const { existsSync: fileExists, readFileSync: fileRead } = await import("fs");
10181
- const { join: join30 } = await import("path");
10395
+ const { join: join35 } = await import("path");
10182
10396
  const { CC_CLAW_HOME: home } = await Promise.resolve().then(() => (init_paths(), paths_exports));
10183
10397
  const chatId = body.chatId || (process.env.ALLOWED_CHAT_ID ?? "").split(",")[0]?.trim() || "default";
10184
- const soulPath = join30(home, "identity/SOUL.md");
10185
- const userPath = join30(home, "identity/USER.md");
10398
+ const soulPath = join35(home, "identity/SOUL.md");
10399
+ const userPath = join35(home, "identity/USER.md");
10186
10400
  const soul = fileExists(soulPath) ? fileRead(soulPath, "utf-8") : "";
10187
10401
  const user = fileExists(userPath) ? fileRead(userPath, "utf-8") : "";
10188
10402
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
@@ -10762,6 +10976,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
10762
10976
  }, effectiveTimeout);
10763
10977
  let resultText = "";
10764
10978
  let accumulatedText = "";
10979
+ let accumulatedThinking = "";
10765
10980
  let sessionId;
10766
10981
  let input = 0;
10767
10982
  let output2 = 0;
@@ -10828,6 +11043,19 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
10828
11043
  if (opts?.onStream) opts.onStream(ev.text);
10829
11044
  }
10830
11045
  break;
11046
+ case "thinking":
11047
+ if (!gotModelContent) {
11048
+ gotModelContent = true;
11049
+ if (firstResponseTimer) {
11050
+ clearTimeout(firstResponseTimer);
11051
+ firstResponseTimer = void 0;
11052
+ }
11053
+ }
11054
+ if (ev.text) {
11055
+ accumulatedThinking = appendTextChunk(accumulatedThinking, ev.text);
11056
+ if (opts?.onThinking) opts.onThinking(ev.text);
11057
+ }
11058
+ break;
10831
11059
  case "tool_start":
10832
11060
  if (!gotModelContent) {
10833
11061
  gotModelContent = true;
@@ -10968,7 +11196,7 @@ Partial output: ${accumulatedText.slice(-500)}`;
10968
11196
  return;
10969
11197
  }
10970
11198
  const cleanedResult = stripThinkingContent(resultText || accumulatedText);
10971
- resolve({ resultText: cleanedResult, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
11199
+ resolve({ resultText: cleanedResult, thinkingText: accumulatedThinking, sessionId, input, output: output2, cacheRead, contextSize, sawToolEvents, sawResultEvent });
10972
11200
  });
10973
11201
  });
10974
11202
  }
@@ -11128,7 +11356,7 @@ function askAgent(chatId, userMessage, opts) {
11128
11356
  return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
11129
11357
  }
11130
11358
  async function askAgentImpl(chatId, userMessage, opts) {
11131
- const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, onModelDowngrade, agentMode: optsAgentMode, onSubagentActivity, settingsSourceChatId, planningDirective } = opts ?? {};
11359
+ const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns, onSlotRotation, onModelDowngrade, agentMode: optsAgentMode, onSubagentActivity, settingsSourceChatId, planningDirective, onThinking } = opts ?? {};
11132
11360
  const settingsChat = settingsSourceChatId ?? chatId;
11133
11361
  const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(settingsChat);
11134
11362
  const mode = permMode ?? getMode(settingsChat);
@@ -11176,6 +11404,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
11176
11404
  onStream,
11177
11405
  onToolAction,
11178
11406
  onSubagentActivity,
11407
+ onThinking,
11179
11408
  // First-response timeout: only arm for fresh Gemini sessions (cold-start).
11180
11409
  // If Gemini has already responded at least once (existingSessionId is set),
11181
11410
  // the session is established — mid-conversation silence is a legitimate long task,
@@ -11369,6 +11598,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
11369
11598
  }
11370
11599
  return {
11371
11600
  text: result.resultText || `(No response from ${adapter.displayName})`,
11601
+ thinkingText: result.thinkingText,
11372
11602
  sessionId: result.sessionId,
11373
11603
  usage: { input: result.input, output: result.output, cacheRead: result.cacheRead, contextSize: result.contextSize },
11374
11604
  resolvedModel: result.resolvedModel
@@ -11741,9 +11971,15 @@ function buildReviewCompleteMessage(results) {
11741
11971
  Skipped proposals will appear in your next review.`;
11742
11972
  }
11743
11973
  function formatNightlySummary(insights, totalPending) {
11744
- const displayCount = totalPending ?? insights.length;
11745
- const header2 = `Nightly Reflection \u2014 ${displayCount} proposal${displayCount === 1 ? "" : "s"} ready`;
11746
- const list = insights.map((ins, i) => `${i + 1}. [${ins.category}] ${ins.insight}`).join("\n");
11974
+ const newCount = insights.length;
11975
+ const total = totalPending ?? newCount;
11976
+ let header2;
11977
+ if (total > newCount) {
11978
+ header2 = `Nightly Reflection \u2014 ${newCount} new proposal${newCount === 1 ? "" : "s"} (${total} total pending)`;
11979
+ } else {
11980
+ header2 = `Nightly Reflection \u2014 ${newCount} proposal${newCount === 1 ? "" : "s"} ready`;
11981
+ }
11982
+ const list = insights.map((ins, i) => `\u2022 [${ins.category}] ${ins.insight}`).join("\n");
11747
11983
  return `${header2}
11748
11984
 
11749
11985
  ${list}
@@ -12023,6 +12259,7 @@ function classifyIntent(text, chatId) {
12023
12259
  if (trimmed.startsWith(">>")) return "agentic";
12024
12260
  if (trimmed.startsWith("/")) return "agentic";
12025
12261
  const lower = trimmed.toLowerCase();
12262
+ const normalized = trimmed.replace(/^["'\u201C\u201D\u2018\u2019`\s]+|["'\u201C\u201D\u2018\u2019`\s]+$/g, "");
12026
12263
  const sessionId = getSessionId(chatId);
12027
12264
  if (sessionId) {
12028
12265
  const lastTs = getLastMessageTimestamp(chatId);
@@ -12045,18 +12282,32 @@ function classifyIntent(text, chatId) {
12045
12282
  intentCounts.chat++;
12046
12283
  return "chat";
12047
12284
  }
12048
- for (const pattern of AGENTIC_PATTERNS) {
12049
- if (pattern.test(trimmed)) {
12050
- log(`[intent] "${trimmed.slice(0, 30)}..." -> agentic (pattern: ${pattern})`);
12285
+ for (const pattern of STRUCTURAL_PATTERNS) {
12286
+ if (pattern.test(normalized)) {
12287
+ log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (structural: ${pattern})`);
12288
+ intentCounts.agentic++;
12289
+ return "agentic";
12290
+ }
12291
+ }
12292
+ for (const pattern of MUTATION_PATTERNS) {
12293
+ if (pattern.test(normalized)) {
12294
+ log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (mutation: ${pattern})`);
12051
12295
  intentCounts.agentic++;
12052
12296
  return "agentic";
12053
12297
  }
12054
12298
  }
12055
- log(`[intent] "${trimmed.slice(0, 30)}..." -> agentic (default)`);
12299
+ for (const pattern of CHAT_QUESTION_PATTERNS) {
12300
+ if (pattern.test(normalized)) {
12301
+ log(`[intent] "${trimmed.slice(0, 40)}..." -> chat (question: ${pattern})`);
12302
+ intentCounts.chat++;
12303
+ return "chat";
12304
+ }
12305
+ }
12306
+ log(`[intent] "${trimmed.slice(0, 40)}..." -> agentic (default)`);
12056
12307
  intentCounts.agentic++;
12057
12308
  return "agentic";
12058
12309
  }
12059
- var intentCounts, CHAT_EXACT, AGENTIC_PATTERNS;
12310
+ var intentCounts, CHAT_EXACT, MUTATION_PATTERNS, CHAT_QUESTION_PATTERNS, STRUCTURAL_PATTERNS;
12060
12311
  var init_classify = __esm({
12061
12312
  "src/intent/classify.ts"() {
12062
12313
  "use strict";
@@ -12113,27 +12364,44 @@ var init_classify = __esm({
12113
12364
  "alright",
12114
12365
  "sure"
12115
12366
  ]);
12116
- AGENTIC_PATTERNS = [
12117
- /\.\w{1,5}$/,
12118
- // file extensions (.ts, .py, .md)
12119
- /[\/\\][\w.-]+/,
12120
- // file paths
12367
+ MUTATION_PATTERNS = [
12368
+ /\b(fix|create|build|deploy|install|uninstall|delete|remove|update|edit|write|modify|move|rename|push|pull|commit|merge|rebase|refactor|implement|generate|scaffold|configure|enable|disable|migrate|upgrade|downgrade|seed|reset|rollback|apply|patch|publish|release|format|lint|compile|transpile|bundle|obfuscat)\b/i,
12369
+ /\b(add\s+(?:a|an|the|new|\w+\s+to))/i,
12370
+ // "add a function", "add to the file"
12371
+ /\b(change\s+(?:the|a|my|this|that|\w+\s+to))/i,
12372
+ // "change the config to"
12373
+ /\b(make\s+(?:a|an|it|this|the|sure))\b/i,
12374
+ // "make a new file", "make it work"
12375
+ /\b(set\s+(?:up|the|a|my))\b/i
12376
+ // "set up the database"
12377
+ ];
12378
+ CHAT_QUESTION_PATTERNS = [
12379
+ /^(?:what|whats|what's)\s+/i,
12380
+ // "what is", "what version", "what does"
12381
+ /^(?:which|where|when|why|who)\s+/i,
12382
+ // "which file", "where is", "when did"
12383
+ /^(?:how\s+(?:do|does|can|did|many|much|long|often|come))\s+/i,
12384
+ // "how do I", "how many"
12385
+ /^(?:show|tell|list|explain|describe|summarize|display)\s+(?:me\s+)?/i,
12386
+ /^(?:do you|can you|could you|are you|is there|are there)\s+(?:know|have|see|find|show|tell|check|list|explain|describe)\s+/i,
12387
+ /^(?:give\s+me|get\s+me)\s+(?:the|a|my|an|current|latest)\s+/i,
12388
+ /^(?:is|are|was|were|does|did|has|have|had)\s+(?:the|a|my|it|this|that|there)\s+/i
12389
+ ];
12390
+ STRUCTURAL_PATTERNS = [
12391
+ /\.[a-z]{1,5}\b/,
12392
+ // file extensions (.ts, .py, .md, .json)
12393
+ /[/\\][\w.-]+/,
12394
+ // file paths (/src/foo, .\bar)
12121
12395
  /`[^`]+`/,
12122
- // inline code
12396
+ // inline code `like this`
12123
12397
  /```/,
12124
12398
  // code blocks
12125
- /\b(fix|create|build|deploy|run|execute|install|delete|remove|update|edit|write|read|check|test|debug|refactor|implement|add|change|modify|move|copy|rename|push|pull|commit|merge|rebase)\b/i,
12126
- // imperative verbs
12127
- /\b(error|bug|crash|fail|broken|issue|problem|exception|stack ?trace)\b/i,
12128
- // error keywords
12129
- /\b(function|class|const|let|var|import|export|return|async|await)\b/i,
12130
- // code keywords
12131
12399
  /[!]{1,2}\s/,
12132
- // shell prefix
12400
+ // shell prefix (! or !!)
12133
12401
  /^\/\//,
12134
- // backend command prefix
12135
- /\b(file|folder|directory|script|server|database|api|endpoint|config|package|module|component)\b/i
12136
- // tech nouns
12402
+ // backend command prefix (//)
12403
+ /\b(error|bug|crash|fail|broken|issue|problem|exception|stack\s?trace)\b/i,
12404
+ /\b(function|class|const|let|var|import|export|return|async|await)\b/i
12137
12405
  ];
12138
12406
  }
12139
12407
  });
@@ -12833,7 +13101,7 @@ var init_classify2 = __esm({
12833
13101
  // src/execution/gate.ts
12834
13102
  function shouldRequireApproval(input) {
12835
13103
  if (input.execMode !== "approved") return false;
12836
- if (input.intent !== "agentic") return false;
13104
+ if (input.intent === "chat") return false;
12837
13105
  if (input.messageText.startsWith(">>")) return false;
12838
13106
  if (EXEMPT_TIERS.has(input.bootstrapTier)) return false;
12839
13107
  if (input.isSideQuest) return false;
@@ -12841,28 +13109,40 @@ function shouldRequireApproval(input) {
12841
13109
  }
12842
13110
  function buildPlanningDirective() {
12843
13111
  return [
12844
- "## PLANNING MODE \u2014 Read-Only",
13112
+ "## PLANNING MODE \u2014 Describe Only, Do Not Execute",
12845
13113
  "",
12846
- "You are in PLANNING mode. Your goal is to research and present a plan.",
13114
+ "You are in PLANNING MODE. Your job is to research the request and describe",
13115
+ "what you will do. The user will review and approve before you execute anything.",
12847
13116
  "",
12848
13117
  "**ALLOWED:**",
12849
- "- Read files, directories, and code",
12850
- "- Load and read skills",
12851
- "- Search the codebase (grep, glob, find)",
12852
- "- Search the web for documentation or solutions",
12853
- "- Analyze and reason about architecture",
13118
+ "- Read file contents",
13119
+ "- Search/grep the codebase",
13120
+ "- Analyze code and architecture",
13121
+ "- Search the web for documentation",
13122
+ "",
13123
+ "**NOT ALLOWED \u2014 do NOT do any of these, even if they seem harmless:**",
13124
+ "- Run any shell or terminal commands (no node --version, no ls, no cat, no npm, nothing)",
13125
+ "- Create, modify, or delete any files",
13126
+ "- Write or generate code",
13127
+ "- Install or uninstall packages",
13128
+ "- Execute any mutations whatsoever",
13129
+ "",
13130
+ "**IMPORTANT:** Even if you already know the answer (e.g. from training data),",
13131
+ "do NOT run a command to confirm it. Just describe what you WILL do.",
12854
13132
  "",
12855
- "**NOT ALLOWED:**",
12856
- "- Do NOT create, modify, or delete any files",
12857
- "- Do NOT run shell commands that mutate state",
12858
- "- Do NOT write code or make edits",
12859
- "- Do NOT install packages or dependencies",
13133
+ "**OUTPUT FORMAT (strict):**",
13134
+ "- No preamble, no 'Here is my plan:', no closing remarks",
13135
+ "- Start with a bold title: '**Plan: <short description>**'",
13136
+ "- Use bullet points (\u2022) for each step \u2014 max 15 words per bullet",
13137
+ "- Be specific about which files you'll touch and what you'll change",
13138
+ "- Max total output: 3000 characters",
12860
13139
  "",
12861
- "**YOUR TASK:**",
12862
- "Research the request thoroughly, then present a clear, concise plan",
12863
- "of what you will do. The user will review and approve before you execute.",
12864
- "Structure your plan with numbered steps and be specific about which",
12865
- "files you will modify and what changes you will make."
13140
+ "Example:",
13141
+ "**Plan: Add retry logic to API client**",
13142
+ "\u2022 Read `src/api/client.ts` \u2014 identify fetch call sites",
13143
+ "\u2022 Add `retryWithBackoff()` helper (max 3 retries, exponential backoff)",
13144
+ "\u2022 Update `src/sync.ts` and `src/scheduler.ts` to use the wrapper",
13145
+ "\u2022 Add unit test in `src/api/client.test.ts`"
12866
13146
  ].join("\n");
12867
13147
  }
12868
13148
  function storePendingPlan(chatId, plan, originalMessage) {
@@ -13153,18 +13433,18 @@ function formatToolStart(toolName, input, level) {
13153
13433
  return `\u{1F527} ${toolName}${cmd ? `: ${cmd}` : path ? `: ${path}` : query ? `: ${query}` : ""}`;
13154
13434
  }
13155
13435
  }
13156
- const inputStr = JSON.stringify(input, null, 2).slice(0, 600);
13436
+ const inputStr = JSON.stringify(input, null, 2).slice(0, 300);
13157
13437
  return `\u{1F527} ${toolName}:
13158
13438
  \`\`\`json
13159
13439
  ${inputStr}
13160
13440
  \`\`\``;
13161
13441
  }
13162
13442
  function formatToolResult(toolName, result) {
13163
- const trimmed = result.trim().slice(0, 400);
13443
+ const trimmed = result.trim().slice(0, 250);
13164
13444
  if (!trimmed) return "";
13165
13445
  const firstLine = trimmed.split("\n")[0] ?? "";
13166
13446
  const isError = /error|failed|exception/i.test(firstLine);
13167
- return isError ? `\u274C ${firstLine.slice(0, 120)}` : `\u{1F4E4} Result:
13447
+ return isError ? `\u274C ${firstLine.slice(0, 120)}` : `\u{1F4EC} Result:
13168
13448
  \`\`\`
13169
13449
  ${trimmed}
13170
13450
  \`\`\``;
@@ -15826,11 +16106,11 @@ async function sendJobPicker(chatId, channel, action) {
15826
16106
  }
15827
16107
  async function sendCurrentProposal(chatId, channel) {
15828
16108
  const { getReviewSession: getReviewSession2, getInsightById: getInsightById2, deleteReviewSession: deleteReviewSession2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
15829
- const { formatProposalCardWithProgress: formatProposalCardWithProgress2, buildProposalKeyboard: buildProposalKeyboard2, buildReviewCompleteMessage: buildReviewCompleteMessage2 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
16109
+ const { formatProposalCardWithProgress: formatProposalCardWithProgress2, buildProposalKeyboard: buildProposalKeyboard2, buildReviewCompleteMessage: buildReviewCompleteMessage3 } = await Promise.resolve().then(() => (init_propose(), propose_exports));
15830
16110
  const session2 = getReviewSession2(getDb(), chatId);
15831
16111
  if (!session2) return;
15832
16112
  if (session2.currentIndex >= session2.insightIds.length) {
15833
- const summary = buildReviewCompleteMessage2(session2.results);
16113
+ const summary = buildReviewCompleteMessage3(session2.results);
15834
16114
  deleteReviewSession2(getDb(), chatId);
15835
16115
  await channel.sendText(chatId, summary, { parseMode: "plain" });
15836
16116
  return;
@@ -16070,7 +16350,7 @@ async function handleEvolveCallback(chatId, data, channel) {
16070
16350
  if (pending.length === 0) {
16071
16351
  await channel.sendText(chatId, "No pending proposals.", { parseMode: "plain" });
16072
16352
  } else {
16073
- const insightIds = pending.slice(0, 5).map((p) => p.id);
16353
+ const insightIds = pending.map((p) => p.id);
16074
16354
  createReviewSession2(getDb(), chatId, insightIds);
16075
16355
  await channel.sendText(chatId, `${pending.length} proposal(s) ready. Let's review them one by one.`, { parseMode: "plain" });
16076
16356
  await sendCurrentProposal(chatId, channel);
@@ -16231,13 +16511,13 @@ async function handleEvolveCallback(chatId, data, channel) {
16231
16511
  const { getReflectionStatus: getReflectionStatus2, setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
16232
16512
  const current = getReflectionStatus2(getDb(), chatId);
16233
16513
  if (current === "frozen") {
16234
- const { readFileSync: readFileSync22, existsSync: existsSync50 } = await import("fs");
16235
- const { join: join30 } = await import("path");
16514
+ const { readFileSync: readFileSync27, existsSync: existsSync56 } = await import("fs");
16515
+ const { join: join35 } = await import("path");
16236
16516
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
16237
- const soulPath = join30(CC_CLAW_HOME3, "identity/SOUL.md");
16238
- const userPath = join30(CC_CLAW_HOME3, "identity/USER.md");
16239
- const soul = existsSync50(soulPath) ? readFileSync22(soulPath, "utf-8") : "";
16240
- const user = existsSync50(userPath) ? readFileSync22(userPath, "utf-8") : "";
16517
+ const soulPath = join35(CC_CLAW_HOME3, "identity/SOUL.md");
16518
+ const userPath = join35(CC_CLAW_HOME3, "identity/USER.md");
16519
+ const soul = existsSync56(soulPath) ? readFileSync27(soulPath, "utf-8") : "";
16520
+ const user = existsSync56(userPath) ? readFileSync27(userPath, "utf-8") : "";
16241
16521
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
16242
16522
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
16243
16523
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -16313,102 +16593,1279 @@ var init_evolve2 = __esm({
16313
16593
  }
16314
16594
  });
16315
16595
 
16316
- // src/router/commands.ts
16317
- import { readFile as readFile6 } from "fs/promises";
16318
- async function handleCommand(msg, channel) {
16319
- const { chatId, command, commandArgs } = msg;
16320
- if (command?.startsWith("/")) {
16321
- return handleBackendCommand(command, chatId, channel);
16596
+ // src/optimizer/identity-audit.ts
16597
+ import { readFileSync as readFileSync10, existsSync as existsSync19, readdirSync as readdirSync9, statSync as statSync6 } from "fs";
16598
+ import { join as join20 } from "path";
16599
+ function readIdentityFile2(filename) {
16600
+ try {
16601
+ return readFileSync10(join20(IDENTITY_PATH, filename), "utf-8");
16602
+ } catch {
16603
+ return "";
16322
16604
  }
16323
- switch (command) {
16324
- case "start":
16325
- case "help": {
16326
- if (typeof channel.sendKeyboard === "function") {
16327
- await sendHelpCategories(chatId, channel);
16328
- } else {
16329
- await channel.sendText(
16330
- chatId,
16331
- "CC-Claw Commands\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n" + Object.entries(HELP_CATEGORIES).map(
16332
- ([cat, cmds]) => `${cat}:
16333
- ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
16334
- ).join("\n\n"),
16335
- { parseMode: "plain" }
16336
- );
16605
+ }
16606
+ function getMtime(filepath) {
16607
+ try {
16608
+ return statSync6(filepath).mtime.toISOString();
16609
+ } catch {
16610
+ return "unknown";
16611
+ }
16612
+ }
16613
+ function findBackupFiles() {
16614
+ const backups = [];
16615
+ const dirs = [IDENTITY_PATH];
16616
+ const contextDir = join20(IDENTITY_PATH, "..", "workspace", "context");
16617
+ if (existsSync19(contextDir)) dirs.push(contextDir);
16618
+ for (const dir of dirs) {
16619
+ try {
16620
+ for (const entry of readdirSync9(dir)) {
16621
+ if (entry.endsWith(".bak") || /\.bak\.\d{4}-\d{2}-\d{2}/.test(entry)) {
16622
+ backups.push(join20(dir, entry));
16623
+ }
16337
16624
  }
16338
- break;
16625
+ } catch {
16339
16626
  }
16340
- case "menu":
16341
- case "m": {
16342
- if (typeof channel.sendKeyboard === "function") {
16343
- const header2 = `\u{1F43E} ${buildSectionHeader("CC-Claw")}`;
16344
- await channel.sendKeyboard(chatId, header2, [
16345
- [{ label: "New Chat", data: "menu:newchat" }, { label: "Status", data: "menu:status" }],
16346
- [{ label: "Backend", data: "menu:backend" }, { label: "Model", data: "menu:model" }],
16347
- [{ label: "Jobs", data: "menu:jobs" }, { label: "Memory", data: "menu:memory" }],
16348
- [{ label: "History", data: "menu:history" }, { label: "Help", data: "menu:help" }]
16349
- ]);
16350
- } else {
16351
- await channel.sendText(
16352
- chatId,
16353
- "CC-Claw Menu:\n/newchat \xB7 /status \xB7 /backend \xB7 /model\n/jobs \xB7 /memory \xB7 /history \xB7 /help",
16354
- { parseMode: "plain" }
16355
- );
16627
+ }
16628
+ return backups;
16629
+ }
16630
+ function computeIdentityStats(pendingProposals, driftPercent) {
16631
+ const soulContent = readIdentityFile2("SOUL.md");
16632
+ const userContent = readIdentityFile2("USER.md");
16633
+ const ccClawContent = readIdentityFile2("CC-CLAW.md");
16634
+ const soulChars = soulContent.length;
16635
+ const userChars = userContent.length;
16636
+ const ccClawChars = ccClawContent.length;
16637
+ const boilerplateChars = Math.max(0, ccClawChars - soulChars - userChars);
16638
+ return {
16639
+ soulChars,
16640
+ userChars,
16641
+ ccClawChars,
16642
+ boilerplateChars,
16643
+ soulMtime: getMtime(join20(IDENTITY_PATH, "SOUL.md")),
16644
+ userMtime: getMtime(join20(IDENTITY_PATH, "USER.md")),
16645
+ ccClawMtime: getMtime(join20(IDENTITY_PATH, "CC-CLAW.md")),
16646
+ backupFiles: findBackupFiles(),
16647
+ estimatedTokens: Math.ceil(ccClawChars / 4),
16648
+ pendingEvolveProposals: pendingProposals,
16649
+ driftPercent
16650
+ };
16651
+ }
16652
+ function buildTokenReport(stats) {
16653
+ return {
16654
+ soulChars: stats.soulChars,
16655
+ userChars: stats.userChars,
16656
+ boilerplateChars: stats.boilerplateChars,
16657
+ totalChars: stats.ccClawChars,
16658
+ estimatedTokens: stats.estimatedTokens
16659
+ };
16660
+ }
16661
+ function buildIdentityAuditPrompt(soulMd, userMd, ccClawMd, stats, contextFiles) {
16662
+ const sections = [];
16663
+ sections.push(`You are an expert prompt engineer and identity file auditor for an AI assistant platform.
16664
+
16665
+ Your task: Analyze the assistant's identity files against a strict rubric and produce ONLY high-confidence, actionable findings. Every finding must earn its place \u2014 no fluff, no generic observations, no "nice to have" suggestions.
16666
+
16667
+ SCRUTINY RULE: Before including ANY finding, ask yourself: "Does this have a clear, specific impact on AI behavior, prompt compliance, or token waste?" If you cannot articulate the exact impact, DO NOT include it.
16668
+
16669
+ Return at most 8 findings. If everything is clean, return NO_FINDINGS.`);
16670
+ sections.push(`[Pre-computed Stats]
16671
+ SOUL.md: ${stats.soulChars} chars (~${Math.ceil(stats.soulChars / 4)} tokens), modified: ${stats.soulMtime}
16672
+ USER.md: ${stats.userChars} chars (~${Math.ceil(stats.userChars / 4)} tokens), modified: ${stats.userMtime}
16673
+ CC-CLAW.md: ${stats.ccClawChars} chars (~${stats.estimatedTokens} tokens), modified: ${stats.ccClawMtime}
16674
+ Boilerplate overhead: ~${stats.boilerplateChars} chars (~${Math.ceil(stats.boilerplateChars / 4)} tokens)
16675
+ Total identity tokens per message: ~${stats.estimatedTokens}
16676
+ Backup files found: ${stats.backupFiles.length > 0 ? stats.backupFiles.join(", ") : "none"}
16677
+ Pending evolve proposals: ${stats.pendingEvolveProposals}
16678
+ Drift from baseline: ${stats.driftPercent !== null ? `${stats.driftPercent}%` : "not calculated"}`);
16679
+ sections.push(`[Rubric \u2014 Evaluate Each Area]
16680
+
16681
+ 1. STRUCTURAL HEALTH
16682
+ - Are file sizes reasonable? (SOUL.md >5000 chars or USER.md >3000 chars = bloat risk)
16683
+ - Is CC-CLAW.md stale? (modified before SOUL.md or USER.md = needs regeneration)
16684
+ - Orphan backup files accumulating?
16685
+ - Logical section organization within each file?
16686
+
16687
+ 2. CONTENT ROUTING
16688
+ - User profile data (job title, employer, location) misplaced in SOUL.md? \u2192 belongs in USER.md
16689
+ - Personality directives (tone, style rules) misplaced in USER.md? \u2192 belongs in SOUL.md
16690
+ - Secrets, credentials, or API keys in any file?
16691
+ - Temporary content (TODOs, dates, one-off instructions) in identity files?
16692
+ - Content that should live in workspace/context/ files instead?
16693
+
16694
+ 3. TOKEN EFFICIENCY
16695
+ - Content duplicated between SOUL.md and USER.md?
16696
+ - Verbose explanations that could be tightened without losing meaning?
16697
+ - Inline lists (emoji lists, tool lists) that are too long?
16698
+ - Content from context files that duplicates identity content?
16699
+
16700
+ 4. EVOLVE INTEGRATION
16701
+ - Pending proposal count (${stats.pendingEvolveProposals}) \u2014 are stale proposals accumulating?
16702
+ - Drift from baseline (${stats.driftPercent !== null ? `${stats.driftPercent}%` : "N/A"}) \u2014 is it excessive (>50%)?
16703
+
16704
+ 5. QUALITY & CLARITY
16705
+ - Conflicting instructions within the same file (e.g., "be brief" AND "provide detailed explanations")
16706
+ - Vague or ambiguous rules the AI is unlikely to follow consistently
16707
+ - Outdated references (old tool names, deprecated services)
16708
+ - Missing important directives (timezone, OS, communication style preferences)
16709
+
16710
+ 6. IDENTITY COMPLETENESS
16711
+ - Does USER.md have enough info for the assistant to be truly personal?
16712
+ - Missing: timezone, work context, communication preferences, personal interests?
16713
+ - Contradictory user info (e.g., conflicting roles, locations, or personal details)?
16714
+ - What additional info could the user provide to improve personalization?`);
16715
+ sections.push("[Current SOUL.md]");
16716
+ sections.push(soulMd || "(empty)");
16717
+ sections.push("[Current USER.md]");
16718
+ sections.push(userMd || "(empty)");
16719
+ sections.push("[Current CC-CLAW.md (generated)]");
16720
+ sections.push(ccClawMd || "(empty)");
16721
+ if (contextFiles.length > 0) {
16722
+ sections.push("[Context Files]");
16723
+ for (const f of contextFiles) {
16724
+ sections.push(`--- ${f.name} ---`);
16725
+ sections.push(f.content.length > 2e3 ? f.content.slice(0, 2e3) + "\n[...truncated]" : f.content);
16726
+ }
16727
+ }
16728
+ sections.push(`[Output Format]
16729
+ For each finding, output EXACTLY this format. Separate multiple findings with "---" on its own line.
16730
+
16731
+ FINDING: <short title, max 80 chars>
16732
+ AREA: <structural | routing | efficiency | evolve | quality | completeness>
16733
+ SEVERITY: <critical | warning | info>
16734
+ DETAIL: <1-3 sentences explaining the issue AND its specific impact on AI behavior>
16735
+ LOCATION: <file:line or file:section, e.g. "SOUL.md:## Communication" or "USER.md:15">
16736
+ SUGGESTION: <specific actionable fix>
16737
+ DIFF:
16738
+ <proposed changes as diff lines, + for additions, - for removals>
16739
+ ---
16740
+
16741
+ If no findings are warranted after thorough analysis, output: NO_FINDINGS
16742
+
16743
+ CRITICAL: For COMPLETENESS findings where you suggest the user ADD information, leave DIFF empty and explain in SUGGESTION what the user should provide. You cannot generate personal information \u2014 only the user can.`);
16744
+ return sections.join("\n\n");
16745
+ }
16746
+ var init_identity_audit = __esm({
16747
+ "src/optimizer/identity-audit.ts"() {
16748
+ "use strict";
16749
+ init_paths();
16750
+ }
16751
+ });
16752
+
16753
+ // src/optimizer/skill-audit.ts
16754
+ import { readFileSync as readFileSync11, existsSync as existsSync20 } from "fs";
16755
+ import { join as join21, basename as basename2 } from "path";
16756
+ function parseFrontmatter3(content) {
16757
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
16758
+ if (!fmMatch) return {};
16759
+ const fm = fmMatch[1];
16760
+ const result = {};
16761
+ const nameMatch = fm.match(/^name:\s*(.+)/m);
16762
+ if (nameMatch) result.name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
16763
+ const descMatch = fm.match(/^description:\s*>?\s*\n?([\s\S]*?)(?=\n\w|\n---)/m);
16764
+ if (descMatch) {
16765
+ result.description = descMatch[1].trim().replace(/\n\s+/g, " ");
16766
+ } else {
16767
+ const singleDesc = fm.match(/^description:\s*(.+)/m);
16768
+ if (singleDesc) result.description = singleDesc[1].trim().replace(/^["']|["']$/g, "");
16769
+ }
16770
+ const backendsMatch = fm.match(/^compatible_backends:\s*(.+)/m);
16771
+ if (backendsMatch) result.compatibleBackends = backendsMatch[1].trim();
16772
+ const modelMatch = fm.match(/^recommended_model:\s*(.+)/m);
16773
+ if (modelMatch) result.recommendedModel = modelMatch[1].trim();
16774
+ return result;
16775
+ }
16776
+ function detectDependentSkills(content) {
16777
+ const deps = /* @__PURE__ */ new Set();
16778
+ const loadMatches = content.matchAll(/(?:load|activate|use|invoke)\s+(?:the\s+)?[`"]?([a-z0-9_-]+)[`"]?\s+(?:skill)?/gi);
16779
+ for (const m of loadMatches) {
16780
+ const name = m[1].toLowerCase();
16781
+ if (name.length > 2 && !["the", "this", "that", "skill", "tool"].includes(name)) {
16782
+ deps.add(name);
16783
+ }
16784
+ }
16785
+ const btMatches = content.matchAll(/`([a-z][a-z0-9_-]+(?:-skill)?)`/g);
16786
+ for (const m of btMatches) {
16787
+ const name = m[1];
16788
+ if (!name.includes(".") && !name.startsWith("cc-claw") && name.length > 3) {
16789
+ deps.add(name);
16790
+ }
16791
+ }
16792
+ return Array.from(deps);
16793
+ }
16794
+ function computeSkillStats(skillPath) {
16795
+ const content = readFileSync11(skillPath, "utf-8");
16796
+ const lines = content.split("\n");
16797
+ return {
16798
+ skillName: basename2(skillPath, ".md") === "SKILL" ? basename2(join21(skillPath, "..")) : basename2(skillPath, ".md"),
16799
+ skillPath,
16800
+ lineCount: lines.length,
16801
+ charCount: content.length,
16802
+ estimatedTokens: Math.ceil(content.length / 4),
16803
+ frontmatter: parseFrontmatter3(content),
16804
+ dependentSkills: detectDependentSkills(content)
16805
+ };
16806
+ }
16807
+ function buildSkillTokenReport(stats) {
16808
+ return {
16809
+ skillChars: stats.charCount,
16810
+ estimatedTokens: stats.estimatedTokens,
16811
+ lineCount: stats.lineCount
16812
+ };
16813
+ }
16814
+ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
16815
+ const results = [];
16816
+ for (const name of depNames) {
16817
+ const candidates = [
16818
+ join21(ccClawSkillsDir, name, "SKILL.md"),
16819
+ join21(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
16820
+ ];
16821
+ for (const candidate of candidates) {
16822
+ if (existsSync20(candidate)) {
16823
+ try {
16824
+ const content = readFileSync11(candidate, "utf-8");
16825
+ results.push({
16826
+ name,
16827
+ content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
16828
+ });
16829
+ } catch {
16830
+ }
16831
+ break;
16356
16832
  }
16357
- break;
16358
16833
  }
16359
- case "stop": {
16360
- const stopped = stopAgent(chatId);
16361
- stopAllSideQuests(chatId);
16362
- cancelAllAgents(chatId);
16363
- await channel.sendText(
16364
- chatId,
16365
- stopped ? "Stopping current task..." : "Nothing is running.",
16366
- { parseMode: "plain" }
16367
- );
16368
- if (stopped && typeof channel.sendKeyboard === "function") {
16369
- await channel.sendKeyboard(chatId, "", [
16370
- [{ label: "\u{1F195} New Chat", data: "menu:newchat" }]
16371
- ]);
16834
+ }
16835
+ return results;
16836
+ }
16837
+ function buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir) {
16838
+ const sections = [];
16839
+ sections.push(`You are an expert prompt engineer specializing in AI agent skills. Analyze this skill file against a strict rubric derived from Anthropic's skill-creator best practices and prompt engineering research.
16840
+
16841
+ Your task: Produce ONLY high-confidence, actionable findings that will measurably improve how well the AI follows this skill's instructions. Every finding must earn its place.
16842
+
16843
+ SCRUTINY RULE: Before including ANY finding, ask yourself: "Will fixing this change how the AI executes this skill?" If the answer is no or unclear, DO NOT include it. We want zero fluff.
16844
+
16845
+ Return at most 8 findings. If the skill is well-constructed, return NO_FINDINGS.`);
16846
+ sections.push(`[Skill Stats]
16847
+ Name: ${stats.skillName}
16848
+ Path: ${stats.skillPath}
16849
+ Lines: ${stats.lineCount} (cap: 500 \u2014 over 500 = progressive disclosure needed)
16850
+ Chars: ${stats.charCount} (~${stats.estimatedTokens} tokens)
16851
+ Frontmatter name: ${stats.frontmatter.name || "(missing)"}
16852
+ Frontmatter description: ${stats.frontmatter.description ? stats.frontmatter.description.slice(0, 200) : "(missing)"}
16853
+ Compatible backends: ${stats.frontmatter.compatibleBackends || "(not specified)"}
16854
+ Recommended model: ${stats.frontmatter.recommendedModel || "(not specified)"}
16855
+ Referenced skills: ${stats.dependentSkills.length > 0 ? stats.dependentSkills.join(", ") : "none"}`);
16856
+ sections.push(`[Rubric \u2014 Evaluate Each Area]
16857
+
16858
+ 1. FRONTMATTER QUALITY
16859
+ - Is 'name' present and descriptive?
16860
+ - Does 'description' include BOTH what the skill does AND trigger info (when to use it)?
16861
+ - A good description tells the AI exactly when to activate this skill
16862
+ - Should 'compatible_backends' or 'recommended_model' be specified?
16863
+
16864
+ 2. BODY STRUCTURE
16865
+ - Over 500 lines? \u2192 needs progressive disclosure (essentials in body, details in references/)
16866
+ - Is there a "When to Use" section in the body? \u2192 that belongs in the frontmatter description
16867
+ - Are workflow steps clear and sequential?
16868
+ - Are phases/steps numbered for easy reference?
16869
+ - Is there a clear guard clause (preconditions before starting)?
16870
+
16871
+ 3. TOKEN EFFICIENCY
16872
+ - Verbose explanations that could be condensed without losing meaning?
16873
+ - Redundant examples (one clear example > three repetitive ones)?
16874
+ - Reference material that should be in a references/ subdirectory?
16875
+ - Emotional emphasis (bold, caps, exclamation marks) that wastes tokens without improving adherence?
16876
+ - Duplicated instructions (same rule stated multiple times in different sections)?
16877
+
16878
+ 4. CONTRADICTION DETECTION
16879
+ - Instructions that conflict with SOUL.md behavioral rules?
16880
+ (e.g., skill says "be thorough and detailed" but SOUL.md says "be concise")
16881
+ - Tone mismatches between skill and personality?
16882
+ - Conflicting tool usage patterns?
16883
+ - Internal contradictions within the skill itself?
16884
+
16885
+ 5. COMPLIANCE & CLARITY
16886
+ - Negative framing ("DO NOT do X") that should be positive ("do Y instead")?
16887
+ - Vague rules the AI is unlikely to follow consistently?
16888
+ - Emotional escalation ("NEVER EVER", "THIS IS CRITICAL") \u2014 does it add compliance or just tokens?
16889
+ - Instructions that rely on the AI "remembering" without structural enforcement?`);
16890
+ sections.push("[Skill File Content]");
16891
+ sections.push(skillContent);
16892
+ sections.push("[SOUL.md (for contradiction check)]");
16893
+ sections.push(soulMd || "(empty)");
16894
+ if (stats.dependentSkills.length > 0) {
16895
+ const depContents = loadDependentSkillContents(stats.dependentSkills, ccClawSkillsDir);
16896
+ if (depContents.length > 0) {
16897
+ sections.push("[Referenced Skill Contents]");
16898
+ for (const dep of depContents) {
16899
+ sections.push(`--- ${dep.name} ---`);
16900
+ sections.push(dep.content);
16372
16901
  }
16373
- break;
16374
16902
  }
16375
- case "permissions": {
16376
- const currentMode = getMode(chatId);
16377
- if (typeof channel.sendKeyboard === "function") {
16378
- const buttons = Object.entries(PERM_MODES).map(([id, label2]) => [{
16379
- label: `${id === currentMode ? "\u2713 " : ""}${label2}`,
16380
- data: `perms:${id}`,
16381
- ...id === currentMode ? { style: "primary" } : {}
16382
- }]);
16383
- await channel.sendKeyboard(chatId, "Select permission mode:", buttons);
16903
+ }
16904
+ sections.push(`[Output Format]
16905
+ For each finding, output EXACTLY this format. Separate multiple findings with "---" on its own line.
16906
+
16907
+ FINDING: <short title, max 80 chars>
16908
+ AREA: <frontmatter | body | efficiency | contradiction | compliance>
16909
+ SEVERITY: <critical | warning | info>
16910
+ DETAIL: <1-3 sentences explaining the issue AND its specific impact on AI compliance>
16911
+ LOCATION: <SKILL.md:line or SKILL.md:section, e.g. "SKILL.md:## Phase 1" or "SKILL.md:45">
16912
+ SUGGESTION: <specific actionable fix>
16913
+ DIFF:
16914
+ <proposed changes as diff lines, + for additions, - for removals>
16915
+ ---
16916
+
16917
+ If the skill is well-constructed after thorough analysis, output: NO_FINDINGS`);
16918
+ return sections.join("\n\n");
16919
+ }
16920
+ var init_skill_audit = __esm({
16921
+ "src/optimizer/skill-audit.ts"() {
16922
+ "use strict";
16923
+ }
16924
+ });
16925
+
16926
+ // src/optimizer/analyze.ts
16927
+ var analyze_exports2 = {};
16928
+ __export(analyze_exports2, {
16929
+ getModelDisplayInfo: () => getModelDisplayInfo,
16930
+ isWeakModel: () => isWeakModel,
16931
+ listCcClawSkills: () => listCcClawSkills,
16932
+ parseOptimizeOutput: () => parseOptimizeOutput,
16933
+ resolveOptimizeAdapter: () => resolveOptimizeAdapter,
16934
+ runIdentityAudit: () => runIdentityAudit,
16935
+ runSkillAudit: () => runSkillAudit
16936
+ });
16937
+ import { spawn as spawn6 } from "child_process";
16938
+ import { createInterface as createInterface5 } from "readline";
16939
+ import { readFileSync as readFileSync12, existsSync as existsSync21, readdirSync as readdirSync11 } from "fs";
16940
+ import { join as join22 } from "path";
16941
+ import { homedir as homedir7 } from "os";
16942
+ function parseOptimizeOutput(raw, validAreas) {
16943
+ if (!raw || raw.includes("NO_FINDINGS")) return [];
16944
+ if (!raw.includes("FINDING:")) return [];
16945
+ const blocks = raw.split(/\n---\n/).slice(0, MAX_FINDINGS);
16946
+ const results = [];
16947
+ for (const block of blocks) {
16948
+ const findingMatch = block.match(/^FINDING:\s*(.+)/m);
16949
+ const areaMatch = block.match(/^AREA:\s*(.+)/m);
16950
+ const severityMatch = block.match(/^SEVERITY:\s*(.+)/m);
16951
+ const detailMatch = block.match(/^DETAIL:\s*(.+)/m);
16952
+ const locationMatch = block.match(/^LOCATION:\s*(.+)/m);
16953
+ const suggestionMatch = block.match(/^SUGGESTION:\s*(.+)/m);
16954
+ const diffMatch = block.match(/^DIFF:\n([\s\S]*?)$/m);
16955
+ if (!findingMatch || !areaMatch) continue;
16956
+ const area = areaMatch[1].trim().toLowerCase();
16957
+ if (!validAreas.includes(area)) continue;
16958
+ const severity = severityMatch?.[1]?.trim().toLowerCase() ?? "info";
16959
+ if (!VALID_SEVERITIES.includes(severity)) continue;
16960
+ results.push({
16961
+ title: findingMatch[1].trim().slice(0, 80),
16962
+ area,
16963
+ severity,
16964
+ detail: detailMatch?.[1]?.trim() ?? "",
16965
+ location: locationMatch?.[1]?.trim() ?? "",
16966
+ suggestion: suggestionMatch?.[1]?.trim() ?? "",
16967
+ proposedDiff: diffMatch?.[1]?.trim() ?? ""
16968
+ });
16969
+ }
16970
+ return results;
16971
+ }
16972
+ async function spawnAnalysis2(adapter, model2, prompt, timeoutMs = ANALYSIS_TIMEOUT_MS2) {
16973
+ const config2 = adapter.buildSpawnConfig({
16974
+ prompt,
16975
+ model: model2,
16976
+ permMode: "yolo",
16977
+ allowedTools: []
16978
+ });
16979
+ const env = adapter.getEnv();
16980
+ let resultText = "";
16981
+ let accumulatedText = "";
16982
+ await new Promise((resolve) => {
16983
+ const proc = spawn6(config2.executable, config2.args, {
16984
+ env,
16985
+ stdio: ["ignore", "pipe", "pipe"],
16986
+ ...config2.cwd ? { cwd: config2.cwd } : {}
16987
+ });
16988
+ proc.stderr?.resume();
16989
+ const rl2 = createInterface5({ input: proc.stdout });
16990
+ const timeout = setTimeout(() => {
16991
+ warn(`[optimizer] Analysis timeout (${adapter.id}:${model2})`);
16992
+ rl2.close();
16993
+ proc.kill("SIGTERM");
16994
+ setTimeout(() => proc.kill("SIGKILL"), 2e3);
16995
+ }, timeoutMs);
16996
+ rl2.on("line", (line) => {
16997
+ if (!line.trim()) return;
16998
+ let msg;
16999
+ try {
17000
+ msg = JSON.parse(line);
17001
+ } catch {
17002
+ return;
17003
+ }
17004
+ const events = adapter.parseLine(msg);
17005
+ for (const ev of events) {
17006
+ if (ev.type === "text" && ev.text) accumulatedText = appendTextChunk(accumulatedText, ev.text);
17007
+ if (ev.type === "result") {
17008
+ resultText = ev.resultText || accumulatedText;
17009
+ if (adapter.shouldKillOnResult()) {
17010
+ rl2.close();
17011
+ proc.kill("SIGTERM");
17012
+ }
17013
+ }
17014
+ }
17015
+ });
17016
+ proc.on("error", () => {
17017
+ clearTimeout(timeout);
17018
+ resolve();
17019
+ });
17020
+ proc.on("close", () => {
17021
+ clearTimeout(timeout);
17022
+ resolve();
17023
+ });
17024
+ });
17025
+ if (!resultText) resultText = accumulatedText;
17026
+ return resultText;
17027
+ }
17028
+ function resolveOptimizeAdapter(chatId) {
17029
+ try {
17030
+ const adapter = getAdapterForChat(chatId);
17031
+ return { adapter, model: adapter.defaultModel };
17032
+ } catch {
17033
+ try {
17034
+ const adapter = getAdapter("claude");
17035
+ return { adapter, model: adapter.defaultModel };
17036
+ } catch {
17037
+ return null;
17038
+ }
17039
+ }
17040
+ }
17041
+ function isWeakModel(adapter, model2) {
17042
+ const weakPatterns = [
17043
+ "flash",
17044
+ "haiku",
17045
+ "mini",
17046
+ "4o-mini",
17047
+ "gpt-4o-mini"
17048
+ ];
17049
+ const lower = model2.toLowerCase();
17050
+ return weakPatterns.some((p) => lower.includes(p));
17051
+ }
17052
+ function getModelDisplayInfo(chatId) {
17053
+ const resolved = resolveOptimizeAdapter(chatId);
17054
+ if (!resolved) return null;
17055
+ const { getThinkingLevel: getThinkingLevel2 } = (init_store5(), __toCommonJS(store_exports5));
17056
+ const thinkingLevel = getThinkingLevel2(chatId);
17057
+ return {
17058
+ backend: resolved.adapter.displayName,
17059
+ model: resolved.model,
17060
+ thinkingLevel,
17061
+ isWeak: isWeakModel(resolved.adapter, resolved.model)
17062
+ };
17063
+ }
17064
+ function readIdentityFile3(filename) {
17065
+ try {
17066
+ return readFileSync12(join22(IDENTITY_PATH, filename), "utf-8");
17067
+ } catch {
17068
+ return "";
17069
+ }
17070
+ }
17071
+ function loadContextFiles() {
17072
+ const contextDir = join22(homedir7(), ".cc-claw", "workspace", "context");
17073
+ const results = [];
17074
+ if (!existsSync21(contextDir)) return results;
17075
+ try {
17076
+ for (const entry of readdirSync11(contextDir)) {
17077
+ if (!entry.endsWith(".md")) continue;
17078
+ try {
17079
+ const content = readFileSync12(join22(contextDir, entry), "utf-8");
17080
+ results.push({ name: entry, content });
17081
+ } catch {
17082
+ }
17083
+ }
17084
+ } catch {
17085
+ }
17086
+ return results;
17087
+ }
17088
+ async function runIdentityAudit(chatId) {
17089
+ const resolved = resolveOptimizeAdapter(chatId);
17090
+ if (!resolved) throw new Error("No AI backend available for analysis");
17091
+ const { adapter, model: model2 } = resolved;
17092
+ log(`[optimizer] Running identity audit with ${adapter.id}:${model2}`);
17093
+ let pendingProposals = 0;
17094
+ try {
17095
+ const { getPendingInsightCount: getPendingInsightCount2 } = (init_store4(), __toCommonJS(store_exports4));
17096
+ const { getPrimaryChatId: getPrimaryChatId2 } = (init_resolve(), __toCommonJS(resolve_exports));
17097
+ const reflChatId = getPrimaryChatId2() || chatId;
17098
+ pendingProposals = getPendingInsightCount2(getDb(), reflChatId);
17099
+ } catch {
17100
+ }
17101
+ let driftPercent = null;
17102
+ try {
17103
+ const { calculateDrift: calculateDrift2 } = (init_apply(), __toCommonJS(apply_exports));
17104
+ } catch {
17105
+ }
17106
+ const stats = computeIdentityStats(pendingProposals, driftPercent);
17107
+ const soulMd = readIdentityFile3("SOUL.md");
17108
+ const userMd = readIdentityFile3("USER.md");
17109
+ const ccClawMd = readIdentityFile3("CC-CLAW.md");
17110
+ const contextFiles = loadContextFiles();
17111
+ const prompt = buildIdentityAuditPrompt(soulMd, userMd, ccClawMd, stats, contextFiles);
17112
+ const raw = await spawnAnalysis2(adapter, model2, prompt);
17113
+ const findings = parseOptimizeOutput(raw, VALID_IDENTITY_AREAS);
17114
+ log(`[optimizer] Identity audit complete: ${findings.length} findings`);
17115
+ return {
17116
+ findings,
17117
+ tokenReport: buildTokenReport(stats),
17118
+ model: model2,
17119
+ backend: adapter.id,
17120
+ target: "identity"
17121
+ };
17122
+ }
17123
+ async function runSkillAudit(chatId, skillPath) {
17124
+ const resolved = resolveOptimizeAdapter(chatId);
17125
+ if (!resolved) throw new Error("No AI backend available for analysis");
17126
+ const { adapter, model: model2 } = resolved;
17127
+ const stats = computeSkillStats(skillPath);
17128
+ log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
17129
+ const soulMd = readIdentityFile3("SOUL.md");
17130
+ const ccClawSkillsDir = join22(homedir7(), ".cc-claw", "workspace", "skills");
17131
+ const skillContent = readFileSync12(skillPath, "utf-8");
17132
+ const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
17133
+ const raw = await spawnAnalysis2(adapter, model2, prompt);
17134
+ const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
17135
+ log(`[optimizer] Skill audit complete for ${stats.skillName}: ${findings.length} findings`);
17136
+ return {
17137
+ findings,
17138
+ tokenReport: buildSkillTokenReport(stats),
17139
+ model: model2,
17140
+ backend: adapter.id,
17141
+ target: stats.skillName
17142
+ };
17143
+ }
17144
+ function listCcClawSkills() {
17145
+ const skillsDir = join22(homedir7(), ".cc-claw", "workspace", "skills");
17146
+ const entries = [];
17147
+ if (!existsSync21(skillsDir)) return entries;
17148
+ try {
17149
+ for (const dir of readdirSync11(skillsDir)) {
17150
+ const skillFile = join22(skillsDir, dir, "SKILL.md");
17151
+ if (!existsSync21(skillFile)) continue;
17152
+ let description = "skill";
17153
+ try {
17154
+ const content = readFileSync12(skillFile, "utf-8");
17155
+ const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
17156
+ if (descMatch) description = descMatch[1].trim().slice(0, 60);
17157
+ } catch {
17158
+ }
17159
+ entries.push({ name: dir, path: skillFile, description });
17160
+ }
17161
+ } catch {
17162
+ }
17163
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
17164
+ }
17165
+ var ANALYSIS_TIMEOUT_MS2, MAX_FINDINGS, VALID_IDENTITY_AREAS, VALID_SKILL_AREAS, VALID_SEVERITIES;
17166
+ var init_analyze2 = __esm({
17167
+ "src/optimizer/analyze.ts"() {
17168
+ "use strict";
17169
+ init_log();
17170
+ init_text_utils();
17171
+ init_paths();
17172
+ init_backends();
17173
+ init_store5();
17174
+ init_identity_audit();
17175
+ init_skill_audit();
17176
+ ANALYSIS_TIMEOUT_MS2 = 18e4;
17177
+ MAX_FINDINGS = 8;
17178
+ VALID_IDENTITY_AREAS = [
17179
+ "structural",
17180
+ "routing",
17181
+ "efficiency",
17182
+ "evolve",
17183
+ "quality",
17184
+ "completeness"
17185
+ ];
17186
+ VALID_SKILL_AREAS = [
17187
+ "frontmatter",
17188
+ "body",
17189
+ "efficiency",
17190
+ "contradiction",
17191
+ "compliance"
17192
+ ];
17193
+ VALID_SEVERITIES = ["critical", "warning", "info"];
17194
+ }
17195
+ });
17196
+
17197
+ // src/optimizer/ui.ts
17198
+ var ui_exports = {};
17199
+ __export(ui_exports, {
17200
+ buildFindingKeyboard: () => buildFindingKeyboard,
17201
+ buildFindingMessage: () => buildFindingMessage,
17202
+ buildIdentityAuditKeyboard: () => buildIdentityAuditKeyboard,
17203
+ buildIdentityAuditSummary: () => buildIdentityAuditSummary,
17204
+ buildMainMenuKeyboard: () => buildMainMenuKeyboard,
17205
+ buildMainMenuMessage: () => buildMainMenuMessage,
17206
+ buildModelRecommendationKeyboard: () => buildModelRecommendationKeyboard,
17207
+ buildModelRecommendationMessage: () => buildModelRecommendationMessage,
17208
+ buildProgressMessage: () => buildProgressMessage,
17209
+ buildReviewCompleteKeyboard: () => buildReviewCompleteKeyboard,
17210
+ buildReviewCompleteMessage: () => buildReviewCompleteMessage2,
17211
+ buildSkillAuditKeyboard: () => buildSkillAuditKeyboard,
17212
+ buildSkillAuditSummary: () => buildSkillAuditSummary,
17213
+ buildSkillPickerKeyboard: () => buildSkillPickerKeyboard,
17214
+ buildSkillPickerMessage: () => buildSkillPickerMessage
17215
+ });
17216
+ function severityEmoji(severity) {
17217
+ switch (severity) {
17218
+ case "critical":
17219
+ return "\u{1F534}";
17220
+ case "warning":
17221
+ return "\u26A0\uFE0F";
17222
+ case "info":
17223
+ return "\u2139\uFE0F";
17224
+ default:
17225
+ return "\u2022";
17226
+ }
17227
+ }
17228
+ function areaEmoji(area) {
17229
+ switch (area) {
17230
+ case "structural":
17231
+ return "\u{1F4D0}";
17232
+ case "routing":
17233
+ return "\u{1F4C2}";
17234
+ case "efficiency":
17235
+ return "\u{1F4B0}";
17236
+ case "evolve":
17237
+ return "\u{1F504}";
17238
+ case "quality":
17239
+ return "\u2728";
17240
+ case "completeness":
17241
+ return "\u{1F464}";
17242
+ case "frontmatter":
17243
+ return "\u{1F4CB}";
17244
+ case "body":
17245
+ return "\u{1F4DD}";
17246
+ case "contradiction":
17247
+ return "\u26A1";
17248
+ case "compliance":
17249
+ return "\u2705";
17250
+ default:
17251
+ return "\u2022";
17252
+ }
17253
+ }
17254
+ function buildMainMenuMessage(backend2, model2, thinkingLevel) {
17255
+ const thinkText = thinkingLevel !== "off" && thinkingLevel !== "auto" ? ` | Thinking: ${thinkingLevel}` : thinkingLevel === "auto" ? " | Thinking: auto" : "";
17256
+ return [
17257
+ "\u{1F527} CC-Claw Optimizer",
17258
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
17259
+ "",
17260
+ "AI-powered analysis of your identity files and skills.",
17261
+ "Optimize for clarity, efficiency, and compliance.",
17262
+ "",
17263
+ `\u2699\uFE0F Using: ${backend2} ${model2}${thinkText}`
17264
+ ].join("\n");
17265
+ }
17266
+ function buildMainMenuKeyboard() {
17267
+ return [
17268
+ [
17269
+ { label: "\u{1F9E0} Identity Audit", data: "opt:identity" },
17270
+ { label: "\u{1F9E9} Skill Audit", data: "opt:skill-menu" }
17271
+ ],
17272
+ [
17273
+ { label: "\u274C Close", data: "opt:close" }
17274
+ ]
17275
+ ];
17276
+ }
17277
+ function buildModelRecommendationMessage(currentModel) {
17278
+ return [
17279
+ "\u{1F527} CC-Claw Optimizer",
17280
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
17281
+ "",
17282
+ `\u26A0\uFE0F You're using ${currentModel} \u2014 for best audit results,`,
17283
+ "an advanced model is recommended."
17284
+ ].join("\n");
17285
+ }
17286
+ function buildModelRecommendationKeyboard() {
17287
+ return [
17288
+ [
17289
+ { label: "\u26A1 Continue anyway", data: "opt:model-ok" },
17290
+ { label: "\u{1F9E0} Switch model first", data: "opt:model-switch", style: "primary" }
17291
+ ],
17292
+ [
17293
+ { label: "\u274C Cancel", data: "opt:close" }
17294
+ ]
17295
+ ];
17296
+ }
17297
+ function buildProgressMessage(target, backend2, model2, thinkingLevel) {
17298
+ const thinkText = thinkingLevel !== "off" ? ` | Thinking: ${thinkingLevel}` : "";
17299
+ return `\u{1F50D} Analyzing ${target}...
17300
+ Using ${backend2} ${model2}${thinkText}`;
17301
+ }
17302
+ function buildIdentityAuditSummary(result) {
17303
+ const { findings, tokenReport, model: model2, backend: backend2 } = result;
17304
+ const tr = tokenReport;
17305
+ const identityAreas = ["structural", "routing", "efficiency", "evolve", "quality", "completeness"];
17306
+ const lines = [
17307
+ "\u{1F9E0} Identity Audit Complete",
17308
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
17309
+ ""
17310
+ ];
17311
+ for (const area of identityAreas) {
17312
+ const emoji = areaEmoji(area);
17313
+ const label2 = area.charAt(0).toUpperCase() + area.slice(1);
17314
+ const areaFindings = findings.filter((f) => f.area === area);
17315
+ const status = areaFindings.length === 0 ? "\u2705 Clean" : `\u26A0\uFE0F ${areaFindings.length} finding${areaFindings.length > 1 ? "s" : ""}`;
17316
+ lines.push(`${emoji} ${label2.padEnd(14)} ${status}`);
17317
+ }
17318
+ lines.push("");
17319
+ lines.push(`\u{1F4B0} Token footprint: ~${tr.estimatedTokens}/message`);
17320
+ lines.push(` (SOUL: ~${Math.ceil(tr.soulChars / 4)} + USER: ~${Math.ceil(tr.userChars / 4)} + System: ~${Math.ceil(tr.boilerplateChars / 4)})`);
17321
+ if (findings.length > 0) {
17322
+ lines.push("");
17323
+ lines.push(`${findings.length} finding${findings.length > 1 ? "s" : ""} to review`);
17324
+ } else {
17325
+ lines.push("");
17326
+ lines.push("\u2705 All checks passed \u2014 your identity files are clean!");
17327
+ }
17328
+ lines.push("");
17329
+ lines.push(`Analyzed by: ${backend2} ${model2}`);
17330
+ return lines.join("\n");
17331
+ }
17332
+ function buildIdentityAuditKeyboard(findingsCount) {
17333
+ if (findingsCount === 0) {
17334
+ return [[{ label: "\u2705 Done", data: "opt:close" }]];
17335
+ }
17336
+ return [
17337
+ [
17338
+ { label: "\u{1F4CB} Review Findings", data: "opt:review:0", style: "primary" },
17339
+ { label: "\u274C Close", data: "opt:close" }
17340
+ ]
17341
+ ];
17342
+ }
17343
+ function buildSkillAuditSummary(result) {
17344
+ const { findings, tokenReport, model: model2, backend: backend2, target } = result;
17345
+ const tr = tokenReport;
17346
+ const skillAreas = ["frontmatter", "body", "efficiency", "contradiction", "compliance"];
17347
+ const lines = [
17348
+ `\u{1F9E9} Skill Audit: ${target}`,
17349
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
17350
+ ""
17351
+ ];
17352
+ for (const area of skillAreas) {
17353
+ const emoji = areaEmoji(area);
17354
+ const label2 = area.charAt(0).toUpperCase() + area.slice(1);
17355
+ const areaFindings = findings.filter((f) => f.area === area);
17356
+ const status = areaFindings.length === 0 ? "\u2705 Clean" : `\u26A0\uFE0F ${areaFindings.length} finding${areaFindings.length > 1 ? "s" : ""}`;
17357
+ lines.push(`${emoji} ${label2.padEnd(14)} ${status}`);
17358
+ }
17359
+ lines.push("");
17360
+ lines.push(`\u{1F4CA} Skill size: ${tr.lineCount} lines, ~${tr.estimatedTokens} tokens`);
17361
+ if (findings.length > 0) {
17362
+ lines.push("");
17363
+ lines.push(`${findings.length} finding${findings.length > 1 ? "s" : ""} to review`);
17364
+ } else {
17365
+ lines.push("");
17366
+ lines.push("\u2705 All checks passed \u2014 this skill is well-constructed!");
17367
+ }
17368
+ lines.push("");
17369
+ lines.push(`Analyzed by: ${backend2} ${model2}`);
17370
+ return lines.join("\n");
17371
+ }
17372
+ function buildSkillAuditKeyboard(findingsCount) {
17373
+ if (findingsCount === 0) {
17374
+ return [[{ label: "\u2705 Done", data: "opt:close" }]];
17375
+ }
17376
+ return [
17377
+ [
17378
+ { label: "\u{1F4CB} Review Findings", data: "opt:review:0", style: "primary" },
17379
+ { label: "\u274C Close", data: "opt:close" }
17380
+ ]
17381
+ ];
17382
+ }
17383
+ function buildFindingMessage(finding, index, total) {
17384
+ const lines = [
17385
+ `${severityEmoji(finding.severity)} Finding ${index + 1}/${total} \u2014 ${finding.area}`,
17386
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
17387
+ "",
17388
+ finding.title,
17389
+ "",
17390
+ `\u{1F4CD} ${finding.location}`,
17391
+ "",
17392
+ finding.detail
17393
+ ];
17394
+ if (finding.suggestion) {
17395
+ lines.push("");
17396
+ lines.push(`\u{1F4A1} ${finding.suggestion}`);
17397
+ }
17398
+ if (finding.proposedDiff) {
17399
+ lines.push("");
17400
+ lines.push("Proposed change:");
17401
+ lines.push("```");
17402
+ lines.push(finding.proposedDiff);
17403
+ lines.push("```");
17404
+ }
17405
+ return lines.join("\n");
17406
+ }
17407
+ function buildFindingKeyboard(index, total, hasDiff) {
17408
+ if (hasDiff) {
17409
+ return [[
17410
+ { label: "\u2705 Apply Fix", data: `opt:fix:${index}`, style: "success" },
17411
+ { label: "\u23ED\uFE0F Skip", data: `opt:skip:${index}` },
17412
+ { label: "\u{1F6D1} Stop Review", data: "opt:stop", style: "danger" }
17413
+ ]];
17414
+ }
17415
+ return [[
17416
+ { label: "\u23ED\uFE0F Next", data: `opt:skip:${index}` },
17417
+ { label: "\u{1F6D1} Stop Review", data: "opt:stop", style: "danger" }
17418
+ ]];
17419
+ }
17420
+ function buildSkillPickerMessage(page, totalPages) {
17421
+ return [
17422
+ "\u{1F9E9} Select a Skill to Audit",
17423
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
17424
+ "",
17425
+ "Showing CC-Claw skills (~/.cc-claw/workspace/skills/)",
17426
+ ...totalPages > 1 ? [`Page ${page + 1}/${totalPages}`] : []
17427
+ ].join("\n");
17428
+ }
17429
+ function buildSkillPickerKeyboard(skills2, page) {
17430
+ const totalPages = Math.ceil(skills2.length / SKILLS_PER_PAGE2);
17431
+ const start = page * SKILLS_PER_PAGE2;
17432
+ const pageSkills = skills2.slice(start, start + SKILLS_PER_PAGE2);
17433
+ const rows = [];
17434
+ for (let i = 0; i < pageSkills.length; i += 2) {
17435
+ const row = [];
17436
+ row.push({ label: pageSkills[i].name, data: `opt:skill-pick:${pageSkills[i].name}` });
17437
+ if (pageSkills[i + 1]) {
17438
+ row.push({ label: pageSkills[i + 1].name, data: `opt:skill-pick:${pageSkills[i + 1].name}` });
17439
+ }
17440
+ rows.push(row);
17441
+ }
17442
+ const navRow = [];
17443
+ if (page > 0) navRow.push({ label: "\u2190 Prev", data: `opt:skill-page:${page - 1}` });
17444
+ if (page < totalPages - 1) navRow.push({ label: "Next \u2192", data: `opt:skill-page:${page + 1}` });
17445
+ navRow.push({ label: "\u274C Cancel", data: "opt:close" });
17446
+ rows.push(navRow);
17447
+ return rows;
17448
+ }
17449
+ function buildReviewCompleteMessage2(applied, skipped, total) {
17450
+ return [
17451
+ "\u{1F527} Review Complete",
17452
+ "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
17453
+ "",
17454
+ `\u2705 Applied: ${applied}`,
17455
+ `\u23ED\uFE0F Skipped: ${skipped}`,
17456
+ `\u{1F4CA} Total findings: ${total}`
17457
+ ].join("\n");
17458
+ }
17459
+ function buildReviewCompleteKeyboard() {
17460
+ return [[{ label: "\u2705 Done", data: "opt:close" }]];
17461
+ }
17462
+ var SKILLS_PER_PAGE2;
17463
+ var init_ui2 = __esm({
17464
+ "src/optimizer/ui.ts"() {
17465
+ "use strict";
17466
+ SKILLS_PER_PAGE2 = 6;
17467
+ }
17468
+ });
17469
+
17470
+ // src/router/optimize.ts
17471
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync22 } from "fs";
17472
+ import { join as join23 } from "path";
17473
+ import { homedir as homedir8 } from "os";
17474
+ async function handleOptimizeCommand(chatId, channel, _args) {
17475
+ const { getModelDisplayInfo: getModelDisplayInfo2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
17476
+ const {
17477
+ buildMainMenuMessage: buildMainMenuMessage2,
17478
+ buildMainMenuKeyboard: buildMainMenuKeyboard2,
17479
+ buildModelRecommendationMessage: buildModelRecommendationMessage2,
17480
+ buildModelRecommendationKeyboard: buildModelRecommendationKeyboard2
17481
+ } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17482
+ const modelInfo = getModelDisplayInfo2(chatId);
17483
+ if (!modelInfo) {
17484
+ await channel.sendText(chatId, "No AI backend available. Configure one first.", { parseMode: "plain" });
17485
+ return;
17486
+ }
17487
+ if (modelInfo.isWeak) {
17488
+ const msg2 = buildModelRecommendationMessage2(modelInfo.model);
17489
+ if (typeof channel.sendKeyboard === "function") {
17490
+ await channel.sendKeyboard(chatId, msg2, buildModelRecommendationKeyboard2());
17491
+ } else {
17492
+ await channel.sendText(chatId, msg2 + "\n\nSwitch to an advanced model for best results.", { parseMode: "plain" });
17493
+ }
17494
+ return;
17495
+ }
17496
+ const msg = buildMainMenuMessage2(modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel);
17497
+ if (typeof channel.sendKeyboard === "function") {
17498
+ await channel.sendKeyboard(chatId, msg, buildMainMenuKeyboard2());
17499
+ } else {
17500
+ await channel.sendText(chatId, msg, { parseMode: "plain" });
17501
+ }
17502
+ }
17503
+ async function handleOptimizeCallback(chatId, data, channel) {
17504
+ const parts = data.split(":");
17505
+ const action = parts[1];
17506
+ switch (action) {
17507
+ case "identity": {
17508
+ await runIdentityAuditFlow(chatId, channel);
17509
+ break;
17510
+ }
17511
+ case "skill-menu": {
17512
+ await showSkillPicker(chatId, channel, 0);
17513
+ break;
17514
+ }
17515
+ case "skill-page": {
17516
+ const page = parseInt(parts[2], 10) || 0;
17517
+ await showSkillPicker(chatId, channel, page);
17518
+ break;
17519
+ }
17520
+ case "skill-pick": {
17521
+ const skillName = parts.slice(2).join(":");
17522
+ await runSkillAuditFlow(chatId, channel, skillName);
17523
+ break;
17524
+ }
17525
+ case "model-ok": {
17526
+ const { getModelDisplayInfo: getModelDisplayInfo2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
17527
+ const { buildMainMenuMessage: buildMainMenuMessage2, buildMainMenuKeyboard: buildMainMenuKeyboard2 } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17528
+ const modelInfo = getModelDisplayInfo2(chatId);
17529
+ if (!modelInfo) return;
17530
+ const msg = buildMainMenuMessage2(modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel);
17531
+ if (typeof channel.sendKeyboard === "function") {
17532
+ await channel.sendKeyboard(chatId, msg, buildMainMenuKeyboard2());
17533
+ }
17534
+ break;
17535
+ }
17536
+ case "model-switch": {
17537
+ await channel.sendText(chatId, "Use /model or /backend to switch to a more powerful model, then run /optimize again.", { parseMode: "plain" });
17538
+ break;
17539
+ }
17540
+ case "review": {
17541
+ const index = parseInt(parts[2], 10) || 0;
17542
+ await showFinding(chatId, channel, index);
17543
+ break;
17544
+ }
17545
+ case "fix": {
17546
+ const fixIndex = parseInt(parts[2], 10) || 0;
17547
+ await applyFinding(chatId, channel, fixIndex);
17548
+ break;
17549
+ }
17550
+ case "skip": {
17551
+ const skipIndex = parseInt(parts[2], 10) || 0;
17552
+ await skipFinding(chatId, channel, skipIndex);
17553
+ break;
17554
+ }
17555
+ case "stop": {
17556
+ await stopReview(chatId, channel);
17557
+ break;
17558
+ }
17559
+ case "close": {
17560
+ activeSessions.delete(chatId);
17561
+ await channel.sendText(chatId, "Optimizer closed.", { parseMode: "plain" });
17562
+ break;
17563
+ }
17564
+ default:
17565
+ break;
17566
+ }
17567
+ }
17568
+ async function runIdentityAuditFlow(chatId, channel) {
17569
+ const { getModelDisplayInfo: getModelDisplayInfo2, runIdentityAudit: runIdentityAudit2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
17570
+ const {
17571
+ buildProgressMessage: buildProgressMessage2,
17572
+ buildIdentityAuditSummary: buildIdentityAuditSummary2,
17573
+ buildIdentityAuditKeyboard: buildIdentityAuditKeyboard2
17574
+ } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17575
+ const modelInfo = getModelDisplayInfo2(chatId);
17576
+ if (!modelInfo) return;
17577
+ await channel.sendText(
17578
+ chatId,
17579
+ buildProgressMessage2("identity files", modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
17580
+ { parseMode: "plain" }
17581
+ );
17582
+ try {
17583
+ const result = await runIdentityAudit2(chatId);
17584
+ activeSessions.set(chatId, {
17585
+ chatId,
17586
+ result,
17587
+ currentIndex: 0,
17588
+ applied: [],
17589
+ skipped: []
17590
+ });
17591
+ const summary = buildIdentityAuditSummary2(result);
17592
+ if (typeof channel.sendKeyboard === "function") {
17593
+ await channel.sendKeyboard(chatId, summary, buildIdentityAuditKeyboard2(result.findings.length));
17594
+ } else {
17595
+ await channel.sendText(chatId, summary, { parseMode: "plain" });
17596
+ }
17597
+ } catch (e) {
17598
+ await channel.sendText(chatId, `Identity audit failed: ${e}`, { parseMode: "plain" });
17599
+ }
17600
+ }
17601
+ async function showSkillPicker(chatId, channel, page) {
17602
+ const { listCcClawSkills: listCcClawSkills2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
17603
+ const { buildSkillPickerMessage: buildSkillPickerMessage2, buildSkillPickerKeyboard: buildSkillPickerKeyboard2 } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17604
+ const skills2 = listCcClawSkills2();
17605
+ if (skills2.length === 0) {
17606
+ await channel.sendText(chatId, "No CC-Claw skills found in ~/.cc-claw/workspace/skills/", { parseMode: "plain" });
17607
+ return;
17608
+ }
17609
+ const totalPages = Math.ceil(skills2.length / 6);
17610
+ const msg = buildSkillPickerMessage2(page, totalPages);
17611
+ if (typeof channel.sendKeyboard === "function") {
17612
+ await channel.sendKeyboard(chatId, msg, buildSkillPickerKeyboard2(skills2, page));
17613
+ } else {
17614
+ const list = skills2.map((s) => `\u2022 ${s.name} \u2014 ${s.description}`).join("\n");
17615
+ await channel.sendText(chatId, msg + "\n\n" + list, { parseMode: "plain" });
17616
+ }
17617
+ }
17618
+ async function runSkillAuditFlow(chatId, channel, skillName) {
17619
+ const { getModelDisplayInfo: getModelDisplayInfo2, runSkillAudit: runSkillAudit2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
17620
+ const {
17621
+ buildProgressMessage: buildProgressMessage2,
17622
+ buildSkillAuditSummary: buildSkillAuditSummary2,
17623
+ buildSkillAuditKeyboard: buildSkillAuditKeyboard2
17624
+ } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17625
+ const modelInfo = getModelDisplayInfo2(chatId);
17626
+ if (!modelInfo) return;
17627
+ const skillPath = join23(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
17628
+ await channel.sendText(
17629
+ chatId,
17630
+ buildProgressMessage2(`skill: ${skillName}`, modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
17631
+ { parseMode: "plain" }
17632
+ );
17633
+ try {
17634
+ const result = await runSkillAudit2(chatId, skillPath);
17635
+ activeSessions.set(chatId, {
17636
+ chatId,
17637
+ result,
17638
+ currentIndex: 0,
17639
+ applied: [],
17640
+ skipped: []
17641
+ });
17642
+ const summary = buildSkillAuditSummary2(result);
17643
+ if (typeof channel.sendKeyboard === "function") {
17644
+ await channel.sendKeyboard(chatId, summary, buildSkillAuditKeyboard2(result.findings.length));
17645
+ } else {
17646
+ await channel.sendText(chatId, summary, { parseMode: "plain" });
17647
+ }
17648
+ } catch (e) {
17649
+ await channel.sendText(chatId, `Skill audit failed: ${e}`, { parseMode: "plain" });
17650
+ }
17651
+ }
17652
+ async function showFinding(chatId, channel, index) {
17653
+ const session2 = activeSessions.get(chatId);
17654
+ if (!session2) {
17655
+ await channel.sendText(chatId, "No active optimizer session. Run /optimize first.", { parseMode: "plain" });
17656
+ return;
17657
+ }
17658
+ const { findings } = session2.result;
17659
+ if (index >= findings.length) {
17660
+ await finishReview(chatId, channel);
17661
+ return;
17662
+ }
17663
+ session2.currentIndex = index;
17664
+ const finding = findings[index];
17665
+ const { buildFindingMessage: buildFindingMessage2, buildFindingKeyboard: buildFindingKeyboard2 } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17666
+ const msg = buildFindingMessage2(finding, index, findings.length);
17667
+ if (typeof channel.sendKeyboard === "function") {
17668
+ await channel.sendKeyboard(chatId, msg, buildFindingKeyboard2(index, findings.length, !!finding.proposedDiff));
17669
+ } else {
17670
+ await channel.sendText(chatId, msg, { parseMode: "plain" });
17671
+ }
17672
+ }
17673
+ async function applyFinding(chatId, channel, index) {
17674
+ const session2 = activeSessions.get(chatId);
17675
+ if (!session2) return;
17676
+ const finding = session2.result.findings[index];
17677
+ if (!finding || !finding.proposedDiff) {
17678
+ await channel.sendText(chatId, "No diff to apply for this finding.", { parseMode: "plain" });
17679
+ await showFinding(chatId, channel, index + 1);
17680
+ return;
17681
+ }
17682
+ try {
17683
+ const targetPath = resolveTargetFile(finding.location, session2.result.target);
17684
+ if (!targetPath) {
17685
+ await channel.sendText(chatId, `Cannot determine target file from location: ${finding.location}`, { parseMode: "plain" });
17686
+ session2.skipped.push(index);
17687
+ await showFinding(chatId, channel, index + 1);
17688
+ return;
17689
+ }
17690
+ if (!existsSync22(targetPath)) {
17691
+ await channel.sendText(chatId, `Target file not found: ${targetPath}`, { parseMode: "plain" });
17692
+ session2.skipped.push(index);
17693
+ await showFinding(chatId, channel, index + 1);
17694
+ return;
17695
+ }
17696
+ const original = readFileSync13(targetPath, "utf-8");
17697
+ const backupPath = targetPath + `.bak.${Date.now()}`;
17698
+ writeFileSync7(backupPath, original, "utf-8");
17699
+ pruneBackups2(targetPath);
17700
+ const { applyDiff: applyDiff2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
17701
+ const newContent = applyDiff2(original, finding.proposedDiff, "replace");
17702
+ if (newContent === original) {
17703
+ await channel.sendText(chatId, `\u26A0\uFE0F Diff produced no changes. The content may have already been modified.`, { parseMode: "plain" });
17704
+ session2.skipped.push(index);
17705
+ await showFinding(chatId, channel, index + 1);
17706
+ return;
17707
+ }
17708
+ writeFileSync7(targetPath, newContent, "utf-8");
17709
+ session2.applied.push(index);
17710
+ if (targetPath.includes("identity/")) {
17711
+ try {
17712
+ const { syncNativeCliFiles: syncNativeCliFiles2 } = await Promise.resolve().then(() => (init_init(), init_exports));
17713
+ await syncNativeCliFiles2();
17714
+ } catch {
17715
+ }
17716
+ }
17717
+ await channel.sendText(chatId, `\u2705 Applied: ${finding.title}`, { parseMode: "plain" });
17718
+ } catch (e) {
17719
+ const errorMsg = e?.message ?? String(e);
17720
+ await channel.sendText(chatId, `\u274C Failed to apply fix: ${errorMsg}`, { parseMode: "plain" });
17721
+ session2.skipped.push(index);
17722
+ }
17723
+ await showFinding(chatId, channel, index + 1);
17724
+ }
17725
+ async function skipFinding(chatId, channel, index) {
17726
+ const session2 = activeSessions.get(chatId);
17727
+ if (!session2) return;
17728
+ session2.skipped.push(index);
17729
+ await showFinding(chatId, channel, index + 1);
17730
+ }
17731
+ async function stopReview(chatId, channel) {
17732
+ await finishReview(chatId, channel);
17733
+ }
17734
+ async function finishReview(chatId, channel) {
17735
+ const session2 = activeSessions.get(chatId);
17736
+ if (!session2) return;
17737
+ const { buildReviewCompleteMessage: buildReviewCompleteMessage3, buildReviewCompleteKeyboard: buildReviewCompleteKeyboard2 } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
17738
+ const msg = buildReviewCompleteMessage3(
17739
+ session2.applied.length,
17740
+ session2.skipped.length,
17741
+ session2.result.findings.length
17742
+ );
17743
+ if (typeof channel.sendKeyboard === "function") {
17744
+ await channel.sendKeyboard(chatId, msg, buildReviewCompleteKeyboard2());
17745
+ } else {
17746
+ await channel.sendText(chatId, msg, { parseMode: "plain" });
17747
+ }
17748
+ activeSessions.delete(chatId);
17749
+ }
17750
+ function resolveTargetFile(location, auditTarget) {
17751
+ const ccClawHome = join23(homedir8(), ".cc-claw");
17752
+ const filePart = location.split(":")[0]?.trim();
17753
+ if (!filePart) return null;
17754
+ if (filePart === "SOUL.md") return join23(ccClawHome, "identity", "SOUL.md");
17755
+ if (filePart === "USER.md") return join23(ccClawHome, "identity", "USER.md");
17756
+ if (filePart === "CC-CLAW.md") return join23(ccClawHome, "identity", "CC-CLAW.md");
17757
+ if (filePart === "SKILL.md" && auditTarget !== "identity") {
17758
+ return join23(ccClawHome, "workspace", "skills", auditTarget, "SKILL.md");
17759
+ }
17760
+ return null;
17761
+ }
17762
+ function pruneBackups2(absolutePath) {
17763
+ const { readdirSync: readDir, unlinkSync: unlink4 } = __require("fs");
17764
+ const { dirname: dirName } = __require("path");
17765
+ const dir = dirName(absolutePath);
17766
+ const baseName = absolutePath.split("/").pop() ?? "";
17767
+ try {
17768
+ const backups = readDir(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join23(dir, f));
17769
+ while (backups.length > 3) {
17770
+ const oldest = backups.shift();
17771
+ try {
17772
+ unlink4(oldest);
17773
+ } catch {
17774
+ }
17775
+ }
17776
+ } catch {
17777
+ }
17778
+ }
17779
+ var activeSessions;
17780
+ var init_optimize = __esm({
17781
+ "src/router/optimize.ts"() {
17782
+ "use strict";
17783
+ activeSessions = /* @__PURE__ */ new Map();
17784
+ }
17785
+ });
17786
+
17787
+ // src/router/commands.ts
17788
+ import { readFile as readFile6 } from "fs/promises";
17789
+ async function handleCommand(msg, channel) {
17790
+ const { chatId, command, commandArgs } = msg;
17791
+ if (command?.startsWith("/")) {
17792
+ return handleBackendCommand(command, chatId, channel);
17793
+ }
17794
+ switch (command) {
17795
+ case "start":
17796
+ case "help": {
17797
+ if (typeof channel.sendKeyboard === "function") {
17798
+ await sendHelpCategories(chatId, channel);
16384
17799
  } else {
16385
- const lines = ["Permission modes:", ""];
16386
- for (const [id, label2] of Object.entries(PERM_MODES)) {
16387
- lines.push(`${id === currentMode ? "\u2713 " : " "}/permissions ${id} \u2014 ${label2}`);
16388
- }
16389
- await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
17800
+ await channel.sendText(
17801
+ chatId,
17802
+ "CC-Claw Commands\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n" + Object.entries(HELP_CATEGORIES).map(
17803
+ ([cat, cmds]) => `${cat}:
17804
+ ${cmds.map((c) => ` ${c.cmd} \u2014 ${c.desc}`).join("\n")}`
17805
+ ).join("\n\n"),
17806
+ { parseMode: "plain" }
17807
+ );
17808
+ }
17809
+ break;
17810
+ }
17811
+ case "menu":
17812
+ case "m": {
17813
+ if (typeof channel.sendKeyboard === "function") {
17814
+ const header2 = `\u{1F43E} ${buildSectionHeader("CC-Claw")}`;
17815
+ await channel.sendKeyboard(chatId, header2, [
17816
+ [{ label: "New Chat", data: "menu:newchat" }, { label: "Status", data: "menu:status" }],
17817
+ [{ label: "Backend", data: "menu:backend" }, { label: "Model", data: "menu:model" }],
17818
+ [{ label: "Jobs", data: "menu:jobs" }, { label: "Memory", data: "menu:memory" }],
17819
+ [{ label: "History", data: "menu:history" }, { label: "Help", data: "menu:help" }]
17820
+ ]);
17821
+ } else {
17822
+ await channel.sendText(
17823
+ chatId,
17824
+ "CC-Claw Menu:\n/newchat \xB7 /status \xB7 /backend \xB7 /model\n/jobs \xB7 /memory \xB7 /history \xB7 /help",
17825
+ { parseMode: "plain" }
17826
+ );
17827
+ }
17828
+ break;
17829
+ }
17830
+ case "stop": {
17831
+ const stopped = stopAgent(chatId);
17832
+ stopAllSideQuests(chatId);
17833
+ cancelAllAgents(chatId);
17834
+ await channel.sendText(
17835
+ chatId,
17836
+ stopped ? "Stopping current task..." : "Nothing is running.",
17837
+ { parseMode: "plain" }
17838
+ );
17839
+ if (stopped && typeof channel.sendKeyboard === "function") {
17840
+ await channel.sendKeyboard(chatId, "", [
17841
+ [{ label: "\u{1F195} New Chat", data: "menu:newchat" }]
17842
+ ]);
16390
17843
  }
16391
17844
  break;
16392
17845
  }
16393
- case "mode":
16394
- case "execmode": {
17846
+ case "permissions": {
17847
+ const currentMode = getMode(chatId);
16395
17848
  const currentExecMode = getExecMode(chatId);
16396
- const EXEC_MODES = {
16397
- approved: "\u2705 Approved \u2014 AI shows a plan before acting",
16398
- yolo: "\u26A1 YOLO \u2014 AI executes immediately"
16399
- };
16400
17849
  if (typeof channel.sendKeyboard === "function") {
16401
- const buttons = Object.entries(EXEC_MODES).map(([id, label2]) => [{
16402
- label: `${id === currentExecMode ? "\u2713 " : ""}${label2}`,
16403
- data: `execmode:${id}`,
16404
- ...id === currentExecMode ? { style: "primary" } : {}
17850
+ const permButtons = Object.entries(PERM_MODES).map(([id, label2]) => [{
17851
+ label: `${id === currentMode ? "\u2713 " : ""}${label2}`,
17852
+ data: `perms:${id}`,
17853
+ ...id === currentMode ? { style: "primary" } : {}
17854
+ }]);
17855
+ const approvalOn = currentExecMode === "approved";
17856
+ permButtons.push([{
17857
+ label: `${approvalOn ? "\u2713 " : ""}\u{1F512} Approve Before Execute: ${approvalOn ? "ON" : "OFF"}`,
17858
+ data: `execmode:${approvalOn ? "yolo" : "approved"}`,
17859
+ ...approvalOn ? { style: "primary" } : {}
16405
17860
  }]);
16406
- await channel.sendKeyboard(chatId, `Execution mode (current: <b>${currentExecMode}</b>):`, buttons);
17861
+ await channel.sendKeyboard(chatId, "Permission & Execution Settings:", permButtons);
16407
17862
  } else {
16408
- const lines = ["Execution modes:", ""];
16409
- for (const [id, label2] of Object.entries(EXEC_MODES)) {
16410
- lines.push(`${id === currentExecMode ? "\u2713 " : " "}/mode ${id} \u2014 ${label2}`);
17863
+ const lines = ["Permission modes:", ""];
17864
+ for (const [id, label2] of Object.entries(PERM_MODES)) {
17865
+ lines.push(`${id === currentMode ? "\u2713 " : " "}/permissions ${id} \u2014 ${label2}`);
16411
17866
  }
17867
+ lines.push("");
17868
+ lines.push(`Approve Before Execute: ${currentExecMode === "approved" ? "ON" : "OFF"}`);
16412
17869
  await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
16413
17870
  }
16414
17871
  break;
@@ -16971,8 +18428,8 @@ ${lines.join("\n")}`, { parseMode: "plain" });
16971
18428
  if (arg.startsWith("/") || arg.startsWith("~")) {
16972
18429
  const resolvedPath = arg.startsWith("~") ? arg.replace("~", process.env.HOME ?? "") : arg;
16973
18430
  setCwd(chatId, resolvedPath);
16974
- const basename2 = resolvedPath.split("/").filter(Boolean).pop();
16975
- if (basename2) upsertBookmark(chatId, basename2, resolvedPath, false);
18431
+ const basename4 = resolvedPath.split("/").filter(Boolean).pop();
18432
+ if (basename4) upsertBookmark(chatId, basename4, resolvedPath, false);
16976
18433
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${resolvedPath}`, detail: { field: "cwd", value: resolvedPath } });
16977
18434
  await sendCwdSessionChoice(chatId, resolvedPath, channel);
16978
18435
  return;
@@ -17133,24 +18590,66 @@ Use /model to pick a model with \u26A1 thinking support.`, { parseMode: "plain"
17133
18590
  break;
17134
18591
  }
17135
18592
  if (typeof channel.sendKeyboard === "function") {
18593
+ const showThinkingUi = getShowThinkingUi(chatId);
17136
18594
  const buttons = modelInfo.thinkingLevels.map((level) => [{
17137
18595
  label: `${level === currentLevel ? "\u2713 " : ""}${level === "auto" ? "Auto" : capitalize(level)}`,
17138
18596
  data: `thinking:${level}`,
17139
18597
  ...level === currentLevel ? { style: "primary" } : {}
17140
18598
  }]);
18599
+ buttons.push([{
18600
+ label: `${showThinkingUi ? "\u2713 " : ""}\u{1F4AD} Show Thinking`,
18601
+ data: "thinking_show_ui:toggle",
18602
+ ...showThinkingUi ? { style: "primary" } : {}
18603
+ }]);
17141
18604
  await channel.sendKeyboard(
17142
18605
  chatId,
17143
18606
  `\u{1F4AD} Thinking Level \u2014 ${shortModelName(currentModel)}
17144
- Current: ${capitalize(currentLevel)}`,
18607
+ Current: ${capitalize(currentLevel)}
18608
+ Show thinking tokens: ${showThinkingUi ? "On" : "Off"}${adapter.id !== "claude" ? `
18609
+
18610
+ \u26A0\uFE0F ${adapter.displayName} CLI doesn't stream thinking tokens` : ""}`,
17145
18611
  buttons
17146
18612
  );
17147
18613
  } else {
18614
+ const showThinkingUi = getShowThinkingUi(chatId);
17148
18615
  await channel.sendText(chatId, `Thinking: ${capitalize(currentLevel)}
17149
18616
  Levels: ${modelInfo.thinkingLevels.join(", ")}
18617
+ Show thinking tokens: ${showThinkingUi ? "On" : "Off"}
17150
18618
  Set via callback (keyboard required).`, { parseMode: "plain" });
17151
18619
  }
17152
18620
  break;
17153
18621
  }
18622
+ case "debug": {
18623
+ const { getSessionLogEnabled: getSessionLogEnabled2, toggleSessionLogEnabled: toggleSessionLogEnabled2 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
18624
+ const current = getSessionLogEnabled2(chatId);
18625
+ if (commandArgs === "on" || commandArgs === "off") {
18626
+ const { setSessionLogEnabled: setSessionLogEnabled2 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
18627
+ const value = commandArgs === "on";
18628
+ setSessionLogEnabled2(chatId, value);
18629
+ await channel.sendText(chatId, `\u{1F52C} Session debug logging: ${value ? "ON" : "OFF"}
18630
+
18631
+ ${value ? "Full tool inputs/results will be saved to ~/.cc-claw/logs/sessions/\nUse 'cc-claw logs session list' or 'cc-claw logs session tail' to inspect." : "Session logs disabled."}`, { parseMode: "plain" });
18632
+ } else if (typeof channel.sendKeyboard === "function") {
18633
+ await channel.sendKeyboard(
18634
+ chatId,
18635
+ `\u{1F52C} Session Debug Logging
18636
+
18637
+ Records full, untruncated tool inputs and results to disk for debugging.
18638
+ Logs: ~/.cc-claw/logs/sessions/
18639
+ Retention: ${process.env.SESSION_LOG_RETENTION_DAYS ?? "7"} day(s)
18640
+
18641
+ Currently: ${current ? "ON" : "OFF"}`,
18642
+ [[
18643
+ { label: current ? "\u2713 ON" : "ON", data: "debug_log:on", ...current ? { style: "primary" } : {} },
18644
+ { label: !current ? "\u2713 OFF" : "OFF", data: "debug_log:off", ...!current ? { style: "primary" } : {} }
18645
+ ]]
18646
+ );
18647
+ } else {
18648
+ const next = toggleSessionLogEnabled2(chatId);
18649
+ await channel.sendText(chatId, `\u{1F52C} Session debug logging: ${next ? "ON" : "OFF"}`, { parseMode: "plain" });
18650
+ }
18651
+ break;
18652
+ }
17154
18653
  case "imagine":
17155
18654
  case "image": {
17156
18655
  if (!commandArgs) {
@@ -17744,8 +19243,8 @@ ${agentLines.join("\n")}`, buttons);
17744
19243
  lines.push(` \u2705 <b>cc-claw</b> <i>Agent orchestrator (spawn, tasks, inbox)</i>`);
17745
19244
  }
17746
19245
  const { execFile: execFile5 } = await import("child_process");
17747
- const { homedir: homedir10 } = await import("os");
17748
- const discoveryCwd = homedir10();
19246
+ const { homedir: homedir12 } = await import("os");
19247
+ const discoveryCwd = homedir12();
17749
19248
  const runnerResults = await Promise.allSettled(
17750
19249
  getAllRunners().map((runner) => {
17751
19250
  const listCmd = runner.getMcpListCommand();
@@ -17887,6 +19386,10 @@ Message: "${testMsg}"`, { parseMode: "plain" });
17887
19386
  await handleEvolveCommand(chatId, channel);
17888
19387
  break;
17889
19388
  }
19389
+ case "optimize": {
19390
+ await handleOptimizeCommand(chatId, channel, commandArgs);
19391
+ break;
19392
+ }
17890
19393
  default:
17891
19394
  await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, { parseMode: "plain" });
17892
19395
  }
@@ -17925,6 +19428,7 @@ var init_commands = __esm({
17925
19428
  init_ui();
17926
19429
  init_state();
17927
19430
  init_evolve2();
19431
+ init_optimize();
17928
19432
  }
17929
19433
  });
17930
19434
 
@@ -18046,6 +19550,29 @@ Select thinking/effort level:`,
18046
19550
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Thinking level set to ${level}`, detail: { field: "thinking", value: level } });
18047
19551
  const label2 = level === "auto" ? "Auto" : level.replace("_", " ").replace(/\b\w/g, (c) => c.toUpperCase());
18048
19552
  await channel.sendText(chatId, `Thinking level set to: ${label2}`, { parseMode: "plain" });
19553
+ } else if (data === "thinking_show_ui:toggle") {
19554
+ const newState = toggleShowThinkingUi(chatId);
19555
+ logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Show thinking UI ${newState ? "enabled" : "disabled"}`, detail: { field: "show_thinking_ui", value: String(newState) } });
19556
+ let msg;
19557
+ if (newState) {
19558
+ let backendId;
19559
+ try {
19560
+ backendId = getAdapterForChat(chatId).id;
19561
+ } catch {
19562
+ }
19563
+ msg = backendId && backendId !== "claude" ? `\u{1F4AD} Thinking tokens enabled \u2014 but ${backendId.charAt(0).toUpperCase() + backendId.slice(1)} CLI doesn't stream them.` : "\u{1F4AD} Thinking tokens will now stream live in the status message.";
19564
+ } else {
19565
+ msg = "\u{1F4AD} Thinking tokens hidden. Toggle on again via /thinking.";
19566
+ }
19567
+ await channel.sendText(chatId, msg, { parseMode: "plain" });
19568
+ } else if (data.startsWith("debug_log:")) {
19569
+ const value = data.slice(10) === "on";
19570
+ const { setSessionLogEnabled: setSessionLogEnabled2 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
19571
+ setSessionLogEnabled2(chatId, value);
19572
+ logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Session debug log ${value ? "enabled" : "disabled"}`, detail: { field: "session_log", value: String(value) } });
19573
+ await channel.sendText(chatId, `\u{1F52C} Session debug logging: ${value ? "ON" : "OFF"}
19574
+
19575
+ ${value ? "Full tool inputs/results will be saved to ~/.cc-claw/logs/sessions/" : "Session logs disabled."}`, { parseMode: "plain" });
18049
19576
  } else if (data.startsWith("summarizer:")) {
18050
19577
  const rest = data.slice(11);
18051
19578
  if (rest === "auto") {
@@ -18185,9 +19712,8 @@ ${plan.originalMessage}`;
18185
19712
  const mode = data.split(":")[1];
18186
19713
  if (mode === "approved" || mode === "yolo") {
18187
19714
  setExecMode(chatId, mode);
18188
- const desc = mode === "approved" ? "AI will show a plan for approval before acting." : "AI will execute immediately without showing a plan.";
18189
- await channel.sendText(chatId, `Execution mode set to <b>${mode}</b>.
18190
- ${desc}`, { parseMode: "html" });
19715
+ const msg = mode === "approved" ? "\u{1F512} Approve Before Execute: <b>ON</b>\nAI will show a plan for your approval before acting." : "\u26A1 Approve Before Execute: <b>OFF</b>\nAI will execute immediately (YOLO mode).";
19716
+ await channel.sendText(chatId, msg, { parseMode: "html" });
18191
19717
  }
18192
19718
  return;
18193
19719
  } else if (data.startsWith("model_sig:")) {
@@ -18617,6 +20143,9 @@ ${rotationNote}`, { parseMode: "html" });
18617
20143
  } else if (data.startsWith("reflect:")) {
18618
20144
  await handleReflectCallback(chatId, data, channel);
18619
20145
  return;
20146
+ } else if (data.startsWith("opt:")) {
20147
+ await handleOptimizeCallback(chatId, data, channel);
20148
+ return;
18620
20149
  } else if (data.startsWith("summ:")) {
18621
20150
  const action = data.slice(5);
18622
20151
  if (action === "all") {
@@ -18897,67 +20426,465 @@ Example: /limits ${bid} daily 500000`, { parseMode: "plain" });
18897
20426
  skillSource = parts[0];
18898
20427
  skillName = parts.slice(1).join(":");
18899
20428
  } else {
18900
- skillName = parts[0];
18901
- }
18902
- const skills2 = await discoverAllSkills();
18903
- const skill = skillSource ? skills2.find((s) => s.name === skillName && s.source === skillSource) : skills2.find((s) => s.name === skillName);
18904
- if (!skill) {
18905
- await channel.sendText(chatId, `Skill "${skillName}" not found.`, { parseMode: "plain" });
18906
- return;
18907
- }
18908
- const activeBackend = getBackend(chatId) ?? "claude";
18909
- if (skill.compatibleBackends && !skill.compatibleBackends.includes(activeBackend)) {
18910
- await channel.sendText(
18911
- chatId,
18912
- `Note: "${skillName}" lists compatible backends as [${skill.compatibleBackends.join(", ")}], but active backend is ${activeBackend}. Proceeding anyway.`,
18913
- { parseMode: "plain" }
18914
- );
20429
+ skillName = parts[0];
20430
+ }
20431
+ const skills2 = await discoverAllSkills();
20432
+ const skill = skillSource ? skills2.find((s) => s.name === skillName && s.source === skillSource) : skills2.find((s) => s.name === skillName);
20433
+ if (!skill) {
20434
+ await channel.sendText(chatId, `Skill "${skillName}" not found.`, { parseMode: "plain" });
20435
+ return;
20436
+ }
20437
+ const activeBackend = getBackend(chatId) ?? "claude";
20438
+ if (skill.compatibleBackends && !skill.compatibleBackends.includes(activeBackend)) {
20439
+ await channel.sendText(
20440
+ chatId,
20441
+ `Note: "${skillName}" lists compatible backends as [${skill.compatibleBackends.join(", ")}], but active backend is ${activeBackend}. Proceeding anyway.`,
20442
+ { parseMode: "plain" }
20443
+ );
20444
+ }
20445
+ const raw = await readFile7(skill.filePath, "utf-8");
20446
+ const skillContent = stripFrontmatter2(raw);
20447
+ const tags = skill.sources.join(", ");
20448
+ await channel.sendText(chatId, `Loading skill: ${skillName} [${tags}]...`, { parseMode: "plain" });
20449
+ const skillModel = resolveModel(chatId);
20450
+ const sMode = getMode(chatId);
20451
+ const sVerbose = getVerboseLevel(chatId);
20452
+ const sToolCb = sVerbose !== "off" ? makeToolActionCallback(chatId, channel, sVerbose) : void 0;
20453
+ const response = await askAgent(chatId, skillContent, { cwd: getCwd(chatId), model: skillModel, permMode: sMode, onToolAction: sToolCb });
20454
+ if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, skillModel, void 0, response.usage.contextSize);
20455
+ await sendResponse(chatId, channel, response.text);
20456
+ }
20457
+ }
20458
+ var init_callbacks = __esm({
20459
+ "src/router/callbacks.ts"() {
20460
+ "use strict";
20461
+ init_format();
20462
+ init_log();
20463
+ init_agent();
20464
+ init_discover();
20465
+ init_profile();
20466
+ init_profile();
20467
+ init_heartbeat2();
20468
+ init_store5();
20469
+ init_summarize();
20470
+ init_inject();
20471
+ init_session_log();
20472
+ init_backends();
20473
+ init_cron();
20474
+ init_wizard();
20475
+ init_store();
20476
+ init_orchestrator();
20477
+ init_store3();
20478
+ init_guard();
20479
+ init_pagination();
20480
+ init_stt();
20481
+ init_gate();
20482
+ init_helpers();
20483
+ init_response();
20484
+ init_shell();
20485
+ init_ui();
20486
+ init_state();
20487
+ init_sidequest();
20488
+ init_evolve2();
20489
+ init_optimize();
20490
+ init_commands();
20491
+ }
20492
+ });
20493
+
20494
+ // src/router/session-log.ts
20495
+ var session_log_exports2 = {};
20496
+ __export(session_log_exports2, {
20497
+ SessionLogFile: () => SessionLogFile,
20498
+ cleanupSessionLogs: () => cleanupSessionLogs,
20499
+ getRetentionDays: () => getRetentionDays,
20500
+ listSessionLogs: () => listSessionLogs,
20501
+ startSessionLogCleanupTimer: () => startSessionLogCleanupTimer,
20502
+ tailSessionLog: () => tailSessionLog
20503
+ });
20504
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, appendFileSync, readdirSync as readdirSync12, unlinkSync as unlinkSync6, statSync as statSync7, createReadStream } from "fs";
20505
+ import { join as join24, basename as basename3 } from "path";
20506
+ import { createInterface as createInterface6 } from "readline";
20507
+ function getRetentionDays() {
20508
+ const env = process.env.SESSION_LOG_RETENTION_DAYS;
20509
+ if (env) {
20510
+ const n = parseInt(env, 10);
20511
+ if (!isNaN(n) && n > 0) return n;
20512
+ }
20513
+ return DEFAULT_RETENTION_DAYS;
20514
+ }
20515
+ function cleanupSessionLogs(retentionDays) {
20516
+ const days = retentionDays ?? getRetentionDays();
20517
+ if (!existsSync23(SESSION_LOGS_PATH)) return 0;
20518
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
20519
+ let cleaned = 0;
20520
+ try {
20521
+ for (const file of readdirSync12(SESSION_LOGS_PATH)) {
20522
+ if (!file.startsWith("session-") || !file.endsWith(".log")) continue;
20523
+ const filePath = join24(SESSION_LOGS_PATH, file);
20524
+ try {
20525
+ const { mtimeMs } = statSync7(filePath);
20526
+ if (mtimeMs < cutoff) {
20527
+ unlinkSync6(filePath);
20528
+ cleaned++;
20529
+ }
20530
+ } catch {
20531
+ }
20532
+ }
20533
+ } catch (err) {
20534
+ log(`[session-log] Cleanup failed: ${err}`);
20535
+ }
20536
+ if (cleaned > 0) {
20537
+ log(`[session-log] Cleaned ${cleaned} session log(s) older than ${days} day(s)`);
20538
+ }
20539
+ return cleaned;
20540
+ }
20541
+ function startSessionLogCleanupTimer() {
20542
+ const timer = setInterval(() => {
20543
+ cleanupSessionLogs();
20544
+ }, 24 * 60 * 60 * 1e3);
20545
+ timer.unref();
20546
+ return timer;
20547
+ }
20548
+ function listSessionLogs() {
20549
+ if (!existsSync23(SESSION_LOGS_PATH)) return [];
20550
+ const logs = [];
20551
+ for (const file of readdirSync12(SESSION_LOGS_PATH)) {
20552
+ if (!file.startsWith("session-") || !file.endsWith(".log")) continue;
20553
+ const filePath = join24(SESSION_LOGS_PATH, file);
20554
+ try {
20555
+ const stat4 = statSync7(filePath);
20556
+ const match = file.match(/^session-(.+?)-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})\.log$/);
20557
+ logs.push({
20558
+ filename: file,
20559
+ filePath,
20560
+ chatId: match?.[1] ?? "unknown",
20561
+ timestamp: match?.[2]?.replace(/-/g, (m, i) => i > 9 ? ":" : m) ?? "unknown",
20562
+ sizeBytes: stat4.size,
20563
+ modifiedAt: stat4.mtime
20564
+ });
20565
+ } catch {
20566
+ }
20567
+ }
20568
+ logs.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
20569
+ return logs;
20570
+ }
20571
+ async function* tailSessionLog(filePath, lines = 50) {
20572
+ if (!existsSync23(filePath)) {
20573
+ yield `File not found: ${filePath}`;
20574
+ return;
20575
+ }
20576
+ const allLines = [];
20577
+ const rl2 = createInterface6({
20578
+ input: createReadStream(filePath, { encoding: "utf-8" }),
20579
+ crlfDelay: Infinity
20580
+ });
20581
+ for await (const line of rl2) {
20582
+ allLines.push(line);
20583
+ }
20584
+ const start = Math.max(0, allLines.length - lines);
20585
+ for (let i = start; i < allLines.length; i++) {
20586
+ yield allLines[i];
20587
+ }
20588
+ }
20589
+ var DEFAULT_RETENTION_DAYS, SessionLogFile;
20590
+ var init_session_log2 = __esm({
20591
+ "src/router/session-log.ts"() {
20592
+ "use strict";
20593
+ init_paths();
20594
+ init_log();
20595
+ DEFAULT_RETENTION_DAYS = 7;
20596
+ SessionLogFile = class {
20597
+ constructor(chatId, backend2, model2) {
20598
+ this.backend = backend2;
20599
+ this.model = model2;
20600
+ if (!existsSync23(SESSION_LOGS_PATH)) {
20601
+ mkdirSync9(SESSION_LOGS_PATH, { recursive: true });
20602
+ }
20603
+ const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
20604
+ const sanitizedChatId = chatId.replace(/[^a-zA-Z0-9_-]/g, "_");
20605
+ this.filePath = join24(SESSION_LOGS_PATH, `session-${sanitizedChatId}-${ts2}.log`);
20606
+ const header2 = [
20607
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
20608
+ `CC-Claw Agent Session \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
20609
+ `Chat: ${chatId} | Backend: ${backend2} | Model: ${model2}`,
20610
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
20611
+ ""
20612
+ ].join("\n");
20613
+ appendFileSync(this.filePath, header2 + "\n", "utf-8");
20614
+ }
20615
+ filePath;
20616
+ startTime = Date.now();
20617
+ closed = false;
20618
+ /** Log a tool start with full, untruncated input. */
20619
+ logToolStart(toolName, input) {
20620
+ if (this.closed) return;
20621
+ const ts2 = this.timestamp();
20622
+ const inputStr = JSON.stringify(input, null, 2);
20623
+ const entry = `[${ts2}] TOOL_START ${toolName}
20624
+ ${inputStr}
20625
+
20626
+ `;
20627
+ this.append(entry);
20628
+ }
20629
+ /** Log a tool end with full, untruncated result. */
20630
+ logToolEnd(toolName, result) {
20631
+ if (this.closed) return;
20632
+ const ts2 = this.timestamp();
20633
+ const preview = result.length > 5e3 ? `(${result.length} chars)
20634
+ ${result.slice(0, 5e3)}\u2026[truncated]` : result;
20635
+ const entry = `[${ts2}] TOOL_END ${toolName}
20636
+ ${preview}
20637
+
20638
+ `;
20639
+ this.append(entry);
20640
+ }
20641
+ /** Log thinking tokens with full text. */
20642
+ logThinking(text) {
20643
+ if (this.closed) return;
20644
+ const ts2 = this.timestamp();
20645
+ const entry = `[${ts2}] THINKING
20646
+ ${text}
20647
+
20648
+ `;
20649
+ this.append(entry);
20650
+ }
20651
+ /** Log informational entry. */
20652
+ logInfo(text) {
20653
+ if (this.closed) return;
20654
+ const ts2 = this.timestamp();
20655
+ const entry = `[${ts2}] INFO ${text}
20656
+
20657
+ `;
20658
+ this.append(entry);
20659
+ }
20660
+ /** Finalize the session log with elapsed time and token counts. */
20661
+ finalize(elapsedMs, usage2) {
20662
+ if (this.closed) return;
20663
+ this.closed = true;
20664
+ const ts2 = this.timestamp();
20665
+ const elapsed = (elapsedMs / 1e3).toFixed(1);
20666
+ const usageStr = usage2 ? ` | input=${usage2.input} output=${usage2.output}${usage2.cacheRead ? ` cacheRead=${usage2.cacheRead}` : ""}` : "";
20667
+ const entry = `[${ts2}] FINALIZED \u2014 ${elapsed}s elapsed${usageStr}
20668
+ `;
20669
+ this.append(entry);
20670
+ }
20671
+ /** Get the file path for display/reference. */
20672
+ getFilePath() {
20673
+ return this.filePath;
20674
+ }
20675
+ /** Get just the filename (for compact display). */
20676
+ getFilename() {
20677
+ return basename3(this.filePath);
20678
+ }
20679
+ // ── Internal ──────────────────────────────────────────────────────
20680
+ timestamp() {
20681
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false });
20682
+ }
20683
+ append(text) {
20684
+ try {
20685
+ appendFileSync(this.filePath, text, "utf-8");
20686
+ } catch (err) {
20687
+ log(`[session-log] Write failed: ${err}`);
20688
+ }
20689
+ }
20690
+ };
20691
+ }
20692
+ });
20693
+
20694
+ // src/router/live-status.ts
20695
+ var live_status_exports = {};
20696
+ __export(live_status_exports, {
20697
+ LiveStatusMessage: () => LiveStatusMessage,
20698
+ makeLiveStatus: () => makeLiveStatus
20699
+ });
20700
+ function dedupThinking(entries) {
20701
+ const out = [];
20702
+ for (const e of entries) {
20703
+ const last = out[out.length - 1];
20704
+ if (e.kind === "thinking" && last?.kind === "thinking" && last.text === e.text) continue;
20705
+ out.push(e);
20706
+ }
20707
+ return out;
20708
+ }
20709
+ function renderEntry(e) {
20710
+ if (e.kind === "thinking") {
20711
+ const truncated = e.text.length > MAX_THINKING_CHARS ? e.text.slice(0, MAX_THINKING_CHARS) + "\u2026" : e.text;
20712
+ return `\u{1F4AD} ${truncated}`;
20713
+ }
20714
+ return e.text;
20715
+ }
20716
+ function renderEntries(entries, modelLabel, elapsedMs, trimmed) {
20717
+ const elapsedSec = (elapsedMs / 1e3).toFixed(1);
20718
+ const lines = [`\u23F3 ${modelLabel} \xB7 ${elapsedSec}s`];
20719
+ if (trimmed) lines[0] += " (\u2026)";
20720
+ lines.push("");
20721
+ for (const e of entries) lines.push(renderEntry(e));
20722
+ return lines.join("\n");
20723
+ }
20724
+ function renderFinal(entries, modelLabel, elapsedSec, trimmed) {
20725
+ const lines = [`\u2705 ${modelLabel} \xB7 ${elapsedSec}s`];
20726
+ if (trimmed) lines[0] += " (\u2026)";
20727
+ if (entries.length === 0) return lines[0];
20728
+ lines.push("");
20729
+ for (const e of entries) lines.push(renderEntry(e));
20730
+ return lines.join("\n");
20731
+ }
20732
+ function makeLiveStatus(chatId, channel, modelLabel, verboseLevel, showThinking) {
20733
+ const liveStatus = new LiveStatusMessage(chatId, channel, modelLabel, verboseLevel, showThinking);
20734
+ const toolCb = async (toolName, input, result) => {
20735
+ if (result === void 0) {
20736
+ liveStatus.addToolStart(toolName, input);
20737
+ } else {
20738
+ liveStatus.addToolEnd(toolName, result);
18915
20739
  }
18916
- const raw = await readFile7(skill.filePath, "utf-8");
18917
- const skillContent = stripFrontmatter2(raw);
18918
- const tags = skill.sources.join(", ");
18919
- await channel.sendText(chatId, `Loading skill: ${skillName} [${tags}]...`, { parseMode: "plain" });
18920
- const skillModel = resolveModel(chatId);
18921
- const sMode = getMode(chatId);
18922
- const sVerbose = getVerboseLevel(chatId);
18923
- const sToolCb = sVerbose !== "off" ? makeToolActionCallback(chatId, channel, sVerbose) : void 0;
18924
- const response = await askAgent(chatId, skillContent, { cwd: getCwd(chatId), model: skillModel, permMode: sMode, onToolAction: sToolCb });
18925
- if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, skillModel, void 0, response.usage.contextSize);
18926
- await sendResponse(chatId, channel, response.text);
18927
- }
20740
+ };
20741
+ return { liveStatus, toolCb };
18928
20742
  }
18929
- var init_callbacks = __esm({
18930
- "src/router/callbacks.ts"() {
20743
+ var FLUSH_INTERVAL_MS, MAX_THINKING_CHARS, TRIM_THRESHOLD, MAX_ENTRIES, LiveStatusMessage;
20744
+ var init_live_status = __esm({
20745
+ "src/router/live-status.ts"() {
18931
20746
  "use strict";
18932
- init_format();
18933
20747
  init_log();
18934
- init_agent();
18935
- init_discover();
18936
- init_profile();
18937
- init_profile();
18938
- init_heartbeat2();
18939
- init_store5();
18940
- init_summarize();
18941
- init_inject();
18942
- init_session_log();
18943
- init_backends();
18944
- init_cron();
18945
- init_wizard();
18946
- init_store();
18947
- init_orchestrator();
18948
- init_store3();
18949
- init_guard();
18950
- init_pagination();
18951
- init_stt();
18952
- init_gate();
18953
20748
  init_helpers();
18954
- init_response();
18955
- init_shell();
18956
- init_ui();
18957
- init_state();
18958
- init_sidequest();
18959
- init_evolve2();
18960
- init_commands();
20749
+ FLUSH_INTERVAL_MS = 1e3;
20750
+ MAX_THINKING_CHARS = 800;
20751
+ TRIM_THRESHOLD = 3500;
20752
+ MAX_ENTRIES = 200;
20753
+ LiveStatusMessage = class {
20754
+ constructor(chatId, channel, modelLabel, verboseLevel, showThinking = false) {
20755
+ this.chatId = chatId;
20756
+ this.channel = channel;
20757
+ this.modelLabel = modelLabel;
20758
+ this.verboseLevel = verboseLevel;
20759
+ this.showThinking = showThinking;
20760
+ }
20761
+ messageId = null;
20762
+ entries = [];
20763
+ startTime = Date.now();
20764
+ flushTimer = null;
20765
+ lastRendered = "";
20766
+ finalized = false;
20767
+ /** Earliest time the next flush is allowed (set after 429 backoff) */
20768
+ nextFlushAllowedAt = 0;
20769
+ /** Tracks whether entries have been trimmed at least once (for display hint). */
20770
+ hasTrimmed = false;
20771
+ /** Send the initial status message. Must be called before adding entries. */
20772
+ async init() {
20773
+ if (!this.channel.sendTextReturningId) return;
20774
+ try {
20775
+ const initial = `\u23F3 ${this.modelLabel} \xB7 Processing\u2026`;
20776
+ this.messageId = await this.channel.sendTextReturningId(this.chatId, initial, "plain") ?? null;
20777
+ if (this.messageId) {
20778
+ this.flushTimer = setInterval(() => this.flush().catch(() => {
20779
+ }), FLUSH_INTERVAL_MS);
20780
+ }
20781
+ } catch (err) {
20782
+ log(`[live-status] init failed: ${err}`);
20783
+ }
20784
+ }
20785
+ /** Add a thinking token preview (from NdjsonEvent type "thinking"). */
20786
+ addThinking(text) {
20787
+ if (!this.showThinking) return;
20788
+ if (!this.messageId || this.finalized) return;
20789
+ const trimmed = text.trim();
20790
+ if (!trimmed) return;
20791
+ this.entries.push({ kind: "thinking", text: trimmed, ts: Date.now() });
20792
+ this.trimEntries();
20793
+ }
20794
+ /** Add a tool start event. */
20795
+ addToolStart(toolName, input, toolId) {
20796
+ if (!this.messageId || this.finalized) return;
20797
+ const formatted = formatToolStart(toolName, input, this.verboseLevel);
20798
+ this.entries.push({ kind: "tool_start", text: formatted, toolId, ts: Date.now() });
20799
+ this.trimEntries();
20800
+ }
20801
+ /** Add a tool result event. */
20802
+ addToolEnd(toolName, result, toolId) {
20803
+ if (!this.messageId || this.finalized) return;
20804
+ if (this.verboseLevel !== "verbose") return;
20805
+ const formatted = formatToolResult(toolName, result ?? "");
20806
+ if (formatted) {
20807
+ this.entries.push({ kind: "tool_end", text: formatted, toolId, ts: Date.now() });
20808
+ this.trimEntries();
20809
+ }
20810
+ }
20811
+ /** Add an informational entry (e.g. backend capability notes). */
20812
+ addInfo(text) {
20813
+ if (!this.messageId || this.finalized) return;
20814
+ this.entries.push({ kind: "info", text, ts: Date.now() });
20815
+ this.trimEntries();
20816
+ }
20817
+ /**
20818
+ * Finalize the status message: replace the spinner with ✅ and elapsed time.
20819
+ * Stops the flush loop. No-op if no message was created (channel doesn't support editing).
20820
+ */
20821
+ async finalize(elapsedMs) {
20822
+ this.finalized = true;
20823
+ if (this.flushTimer) {
20824
+ clearInterval(this.flushTimer);
20825
+ this.flushTimer = null;
20826
+ }
20827
+ if (!this.messageId || !this.channel.editText) return;
20828
+ const elapsedSec = (elapsedMs / 1e3).toFixed(1);
20829
+ const deduped = dedupThinking(this.entries);
20830
+ const body = renderFinal(deduped, this.modelLabel, elapsedSec, this.hasTrimmed);
20831
+ try {
20832
+ await this.channel.editText(this.chatId, this.messageId, body, "plain");
20833
+ } catch (err) {
20834
+ log(`[live-status] finalize edit failed: ${err}`);
20835
+ }
20836
+ }
20837
+ /** Returns the messageId of the status message (if created). */
20838
+ getMessageId() {
20839
+ return this.messageId;
20840
+ }
20841
+ // ── Internal ──────────────────────────────────────────────────────────
20842
+ async flush() {
20843
+ if (this.finalized || !this.messageId || !this.channel.editText) return;
20844
+ if (Date.now() < this.nextFlushAllowedAt) return;
20845
+ const deduped = dedupThinking(this.entries);
20846
+ const body = renderEntries(deduped, this.modelLabel, Date.now() - this.startTime, this.hasTrimmed);
20847
+ if (body === this.lastRendered) return;
20848
+ this.lastRendered = body;
20849
+ try {
20850
+ await this.channel.editText(this.chatId, this.messageId, body, "plain");
20851
+ } catch (err) {
20852
+ this.handleRateLimit(err);
20853
+ }
20854
+ }
20855
+ /**
20856
+ * Trim entries from the BEGINNING when the rendered body exceeds the threshold.
20857
+ * This is the core of the single-message pattern: always show the most recent
20858
+ * activity, drop the oldest entries that scroll past the limit.
20859
+ */
20860
+ trimEntries() {
20861
+ if (this.entries.length > MAX_ENTRIES) {
20862
+ this.entries = this.entries.slice(this.entries.length - MAX_ENTRIES);
20863
+ this.hasTrimmed = true;
20864
+ }
20865
+ let rendered = renderEntries(dedupThinking(this.entries), this.modelLabel, Date.now() - this.startTime, this.hasTrimmed);
20866
+ while (rendered.length > TRIM_THRESHOLD && this.entries.length > 3) {
20867
+ this.entries.shift();
20868
+ this.hasTrimmed = true;
20869
+ rendered = renderEntries(dedupThinking(this.entries), this.modelLabel, Date.now() - this.startTime, this.hasTrimmed);
20870
+ }
20871
+ }
20872
+ /**
20873
+ * Handle Telegram 429 rate-limit errors by backing off.
20874
+ * Parses the "retry after N" hint and sets a cooldown.
20875
+ */
20876
+ handleRateLimit(err) {
20877
+ const msg = String(err);
20878
+ const match = msg.match(/retry after (\d+)/i);
20879
+ if (match) {
20880
+ const retrySec = parseInt(match[1], 10) || 3;
20881
+ this.nextFlushAllowedAt = Date.now() + retrySec * 1e3;
20882
+ log(`[live-status] 429 rate-limited, backing off ${retrySec}s`);
20883
+ } else {
20884
+ log(`[live-status] edit failed: ${msg}`);
20885
+ }
20886
+ }
20887
+ };
18961
20888
  }
18962
20889
  });
18963
20890
 
@@ -19078,7 +21005,7 @@ async function handleText(msg, channel) {
19078
21005
  let intent = classifyIntent(text, chatId);
19079
21006
  const cleanText = text.startsWith(">>") ? text.slice(2).trim() : text;
19080
21007
  let bootstrapTier = intent === "chat" ? "chat" : void 0;
19081
- let maxTurns = intent === "chat" ? 1 : void 0;
21008
+ let maxTurns = void 0;
19082
21009
  let effectiveAgentMode = msg.agentMode ?? getAgentMode(chatId);
19083
21010
  const observedSubagents = /* @__PURE__ */ new Set();
19084
21011
  if (effectiveAgentMode === "auto" && !text.startsWith(">>")) {
@@ -19245,6 +21172,10 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
19245
21172
  typingActive2 = false;
19246
21173
  if (planResponse.text) {
19247
21174
  let planText = planResponse.text.replace(/\[REACT:.+?\]/g, "").replace(/\[SEND_FILE:.+?\]/g, "").replace(/\[GENERATE_IMAGE:.+?\]/g, "").replace(/\[HISTORY_SEARCH:[^\]]+\]/g, "").trim();
21175
+ const PLAN_DISPLAY_LIMIT = 3500;
21176
+ if (planText.length > PLAN_DISPLAY_LIMIT) {
21177
+ planText = planText.slice(0, PLAN_DISPLAY_LIMIT) + "\n\u2026(truncated \u2014 tap Approve to proceed)";
21178
+ }
19248
21179
  storePendingPlan(chatId, planText, cleanText || text);
19249
21180
  if (typeof channel.sendKeyboard === "function") {
19250
21181
  await channel.sendKeyboard(chatId, `\u{1F50D} ${planText}`, [
@@ -19253,7 +21184,7 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
19253
21184
  { label: "\u274C Reject", data: "exec:reject", style: "danger" }
19254
21185
  ],
19255
21186
  [
19256
- { label: "\u26A1 Skip & YOLO", data: "exec:yolo" }
21187
+ { label: "\u26A1 Approve & Switch to YOLO", data: "exec:yolo" }
19257
21188
  ]
19258
21189
  ]);
19259
21190
  } else {
@@ -19283,7 +21214,48 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
19283
21214
  try {
19284
21215
  const tMode = getMode(chatId);
19285
21216
  const tVerbose = getVerboseLevel(chatId);
19286
- const tToolCb = tVerbose !== "off" ? makeToolActionCallback(chatId, channel, tVerbose) : void 0;
21217
+ const adapter = getAdapterForChat(chatId);
21218
+ const modelLabel = formatModelShort(model2 ?? adapter.defaultModel);
21219
+ const { getShowThinkingUi: getShowThinkingUi3 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
21220
+ const showThinkingUi = getShowThinkingUi3(chatId);
21221
+ const needsLiveStatus = tVerbose !== "off" || showThinkingUi;
21222
+ let liveStatus = null;
21223
+ let tToolCb;
21224
+ const { getSessionLogEnabled: getSessionLogEnabled2 } = await Promise.resolve().then(() => (init_chat_settings(), chat_settings_exports));
21225
+ const { SessionLogFile: SessionLogFile2 } = await Promise.resolve().then(() => (init_session_log2(), session_log_exports2));
21226
+ let sessionLog = null;
21227
+ if (getSessionLogEnabled2(chatId)) {
21228
+ sessionLog = new SessionLogFile2(chatId, adapter.id, model2 ?? adapter.defaultModel);
21229
+ }
21230
+ if (needsLiveStatus) {
21231
+ const { makeLiveStatus: makeLiveStatus2 } = await Promise.resolve().then(() => (init_live_status(), live_status_exports));
21232
+ const effectiveVerbose = tVerbose === "off" ? "normal" : tVerbose;
21233
+ const ls = makeLiveStatus2(chatId, channel, modelLabel, effectiveVerbose, showThinkingUi);
21234
+ liveStatus = ls.liveStatus;
21235
+ const baseCb = tVerbose !== "off" ? ls.toolCb : void 0;
21236
+ tToolCb = async (toolName, input, result) => {
21237
+ if (baseCb) await baseCb(toolName, input, result);
21238
+ if (sessionLog) {
21239
+ if (result === void 0) {
21240
+ sessionLog.logToolStart(toolName, input);
21241
+ } else {
21242
+ sessionLog.logToolEnd(toolName, result);
21243
+ }
21244
+ }
21245
+ };
21246
+ await liveStatus.init();
21247
+ if (showThinkingUi && adapter.id !== "claude") {
21248
+ liveStatus.addInfo(`\u{1F4AD} Thinking display not available for ${adapter.displayName}`);
21249
+ }
21250
+ } else if (sessionLog) {
21251
+ tToolCb = async (toolName, input, result) => {
21252
+ if (result === void 0) {
21253
+ sessionLog.logToolStart(toolName, input);
21254
+ } else {
21255
+ sessionLog.logToolEnd(toolName, result);
21256
+ }
21257
+ };
21258
+ }
19287
21259
  const sigT0 = Date.now();
19288
21260
  const response = await askAgent(chatId, cleanText || text, {
19289
21261
  cwd: getCwd(chatId),
@@ -19293,6 +21265,10 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
19293
21265
  bootstrapTier,
19294
21266
  maxTurns,
19295
21267
  agentMode: effectiveAgentMode,
21268
+ onThinking: liveStatus || sessionLog ? (chunk) => {
21269
+ if (liveStatus) liveStatus.addThinking(chunk);
21270
+ if (sessionLog) sessionLog.logThinking(chunk);
21271
+ } : void 0,
19296
21272
  onSubagentActivity: (backendId2, info) => {
19297
21273
  observedSubagents.add(info.name);
19298
21274
  try {
@@ -19326,26 +21302,32 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
19326
21302
  });
19327
21303
  }
19328
21304
  });
19329
- const elapsedSec = ((Date.now() - sigT0) / 1e3).toFixed(1);
21305
+ const elapsedMs = Date.now() - sigT0;
21306
+ const elapsedSec = (elapsedMs / 1e3).toFixed(1);
21307
+ if (liveStatus && response.thinkingText?.trim()) {
21308
+ liveStatus.addThinking(response.thinkingText.trim());
21309
+ }
21310
+ if (liveStatus) await liveStatus.finalize(elapsedMs);
21311
+ if (sessionLog) sessionLog.finalize(elapsedMs, response.usage);
19330
21312
  if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2, void 0, response.usage.contextSize);
19331
21313
  let responseText = response.text;
19332
21314
  const sigEnabled = getModelSignature(chatId);
19333
21315
  if (sigEnabled === "on" && responseText && !responseText.startsWith("(No response")) {
19334
- const adapter = getAdapterForChat(chatId);
19335
- const modelId = response.resolvedModel ?? model2 ?? adapter.defaultModel;
21316
+ const adapter2 = getAdapterForChat(chatId);
21317
+ const modelId = response.resolvedModel ?? model2 ?? adapter2.defaultModel;
19336
21318
  const thinking2 = getThinkingLevel(chatId) || "auto";
19337
21319
  const shortModel = formatModelShort(modelId);
19338
21320
  let slotTag = "";
19339
- if (adapter.id === "gemini") {
21321
+ if (adapter2.id === "gemini") {
19340
21322
  const slotId = getChatGeminiSlotId(chatId);
19341
21323
  if (slotId) {
19342
21324
  const slot = getGeminiSlots().find((s) => s.id === slotId);
19343
21325
  if (slot) slotTag = ` \xB7 ${slot.label || `slot-${slot.id}`}`;
19344
21326
  }
19345
- } else if (adapter.id === "claude" || adapter.id === "codex") {
19346
- const slotId = getChatBackendSlotId(chatId, adapter.id);
21327
+ } else if (adapter2.id === "claude" || adapter2.id === "codex") {
21328
+ const slotId = getChatBackendSlotId(chatId, adapter2.id);
19347
21329
  if (slotId) {
19348
- const slot = getBackendSlots(adapter.id).find((s) => s.id === slotId);
21330
+ const slot = getBackendSlots(adapter2.id).find((s) => s.id === slotId);
19349
21331
  if (slot) slotTag = ` \xB7 ${slot.label || `slot-${slot.id}`}`;
19350
21332
  }
19351
21333
  }
@@ -19379,10 +21361,10 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
19379
21361
  await sendResponse(chatId, channel, responseText, msg.messageId);
19380
21362
  try {
19381
21363
  const { detectAndLogSignals: detectAndLogSignals2 } = await Promise.resolve().then(() => (init_detect(), detect_exports));
19382
- const adapter = getAdapterForChat(chatId);
21364
+ const adapter2 = getAdapterForChat(chatId);
19383
21365
  detectAndLogSignals2(chatId, cleanText || text, responseText, {
19384
- backendId: adapter.id,
19385
- model: model2 ?? adapter.defaultModel,
21366
+ backendId: adapter2.id,
21367
+ model: model2 ?? adapter2.defaultModel,
19386
21368
  thinkingLevel: getThinkingLevel(chatId) || void 0
19387
21369
  });
19388
21370
  } catch (e) {
@@ -19835,7 +21817,7 @@ var init_cron = __esm({
19835
21817
  });
19836
21818
 
19837
21819
  // src/agents/runners/wrap-backend.ts
19838
- import { join as join20 } from "path";
21820
+ import { join as join25 } from "path";
19839
21821
  function buildMcpCommands(backendId) {
19840
21822
  const exe = backendId === "cursor" ? "agent" : backendId;
19841
21823
  return {
@@ -19929,7 +21911,7 @@ function wrapBackendAdapter(adapter) {
19929
21911
  const configPath = writeMcpConfigFile(server);
19930
21912
  return ["--mcp-config", configPath];
19931
21913
  },
19932
- getSkillPath: () => join20(SKILLS_PATH, `agent-${adapter.id}.md`)
21914
+ getSkillPath: () => join25(SKILLS_PATH, `agent-${adapter.id}.md`)
19933
21915
  };
19934
21916
  }
19935
21917
  var BACKEND_CAPABILITIES;
@@ -19980,18 +21962,18 @@ var init_wrap_backend = __esm({
19980
21962
  });
19981
21963
 
19982
21964
  // src/agents/runners/config-loader.ts
19983
- import { readFileSync as readFileSync10, readdirSync as readdirSync9, existsSync as existsSync19, mkdirSync as mkdirSync9, watchFile, unwatchFile } from "fs";
19984
- import { join as join21 } from "path";
21965
+ import { readFileSync as readFileSync14, readdirSync as readdirSync13, existsSync as existsSync24, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
21966
+ import { join as join26 } from "path";
19985
21967
  import { execFileSync as execFileSync2 } from "child_process";
19986
21968
  function resolveExecutable(config2) {
19987
- if (existsSync19(config2.executable)) return config2.executable;
21969
+ if (existsSync24(config2.executable)) return config2.executable;
19988
21970
  try {
19989
21971
  return execFileSync2("which", [config2.executable], { encoding: "utf-8" }).trim();
19990
21972
  } catch {
19991
21973
  }
19992
21974
  for (const fallback of config2.executableFallbacks ?? []) {
19993
21975
  const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
19994
- if (existsSync19(resolved)) return resolved;
21976
+ if (existsSync24(resolved)) return resolved;
19995
21977
  }
19996
21978
  return config2.executable;
19997
21979
  }
@@ -20117,12 +22099,12 @@ function configToRunner(config2) {
20117
22099
  prepareMcpInjection() {
20118
22100
  return [];
20119
22101
  },
20120
- getSkillPath: () => join21(SKILLS_PATH, `agent-${config2.id}.md`)
22102
+ getSkillPath: () => join26(SKILLS_PATH, `agent-${config2.id}.md`)
20121
22103
  };
20122
22104
  }
20123
22105
  function loadRunnerConfig(filePath) {
20124
22106
  try {
20125
- const content = readFileSync10(filePath, "utf-8");
22107
+ const content = readFileSync14(filePath, "utf-8");
20126
22108
  return JSON.parse(content);
20127
22109
  } catch (err) {
20128
22110
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -20130,14 +22112,14 @@ function loadRunnerConfig(filePath) {
20130
22112
  }
20131
22113
  }
20132
22114
  function loadAllRunnerConfigs() {
20133
- if (!existsSync19(RUNNERS_PATH)) {
20134
- mkdirSync9(RUNNERS_PATH, { recursive: true });
22115
+ if (!existsSync24(RUNNERS_PATH)) {
22116
+ mkdirSync10(RUNNERS_PATH, { recursive: true });
20135
22117
  return [];
20136
22118
  }
20137
- const files = readdirSync9(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
22119
+ const files = readdirSync13(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
20138
22120
  const configs = [];
20139
22121
  for (const file of files) {
20140
- const config2 = loadRunnerConfig(join21(RUNNERS_PATH, file));
22122
+ const config2 = loadRunnerConfig(join26(RUNNERS_PATH, file));
20141
22123
  if (config2) configs.push(config2);
20142
22124
  }
20143
22125
  return configs;
@@ -20158,16 +22140,16 @@ function registerConfigRunners() {
20158
22140
  return count;
20159
22141
  }
20160
22142
  function watchRunnerConfigs(onChange) {
20161
- if (!existsSync19(RUNNERS_PATH)) return;
22143
+ if (!existsSync24(RUNNERS_PATH)) return;
20162
22144
  for (const prev of watchedFiles) {
20163
- if (!existsSync19(prev)) {
22145
+ if (!existsSync24(prev)) {
20164
22146
  unwatchFile(prev);
20165
22147
  watchedFiles.delete(prev);
20166
22148
  }
20167
22149
  }
20168
- const files = readdirSync9(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
22150
+ const files = readdirSync13(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
20169
22151
  for (const file of files) {
20170
- const fullPath = join21(RUNNERS_PATH, file);
22152
+ const fullPath = join26(RUNNERS_PATH, file);
20171
22153
  if (watchedFiles.has(fullPath)) continue;
20172
22154
  watchedFiles.add(fullPath);
20173
22155
  watchFile(fullPath, { interval: 5e3 }, () => {
@@ -20568,6 +22550,7 @@ var init_telegram2 = __esm({
20568
22550
  { command: "mode", description: "Execution gate (approved/yolo)" },
20569
22551
  { command: "tools", description: "Configure which tools the agent can use" },
20570
22552
  { command: "verbose", description: "Tool visibility (off/normal/verbose)" },
22553
+ { command: "debug", description: "Toggle session debug logging (full tool I/O)" },
20571
22554
  { command: "cwd", description: "Set or show working directory" },
20572
22555
  // Memory
20573
22556
  { command: "memory", description: "List stored memories" },
@@ -20608,7 +22591,8 @@ var init_telegram2 = __esm({
20608
22591
  { command: "chats", description: "Manage multi-chat aliases" },
20609
22592
  { command: "intent", description: "Test intent classifier on a message" },
20610
22593
  { command: "evolve", description: "Self-learning & evolution controls" },
20611
- { command: "reflect", description: "Trigger reflection analysis" }
22594
+ { command: "reflect", description: "Trigger reflection analysis" },
22595
+ { command: "optimize", description: "Audit identity files and skills" }
20612
22596
  ]);
20613
22597
  this.bot.on("message", async (ctx) => {
20614
22598
  const chatId = ctx.chat.id.toString();
@@ -21013,19 +22997,19 @@ var init_telegram2 = __esm({
21013
22997
  });
21014
22998
 
21015
22999
  // src/skills/bootstrap.ts
21016
- import { existsSync as existsSync20 } from "fs";
23000
+ import { existsSync as existsSync25 } from "fs";
21017
23001
  import { readdir as readdir6, readFile as readFile8, writeFile as writeFile5, copyFile } from "fs/promises";
21018
- import { join as join22, dirname as dirname4 } from "path";
23002
+ import { join as join27, dirname as dirname5 } from "path";
21019
23003
  import { fileURLToPath as fileURLToPath2 } from "url";
21020
23004
  async function copyAgentManifestSkills() {
21021
- if (!existsSync20(PKG_SKILLS)) return;
23005
+ if (!existsSync25(PKG_SKILLS)) return;
21022
23006
  try {
21023
23007
  const entries = await readdir6(PKG_SKILLS, { withFileTypes: true });
21024
23008
  for (const entry of entries) {
21025
23009
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
21026
- const src = join22(PKG_SKILLS, entry.name);
21027
- const dest = join22(SKILLS_PATH, entry.name);
21028
- if (existsSync20(dest)) continue;
23010
+ const src = join27(PKG_SKILLS, entry.name);
23011
+ const dest = join27(SKILLS_PATH, entry.name);
23012
+ if (existsSync25(dest)) continue;
21029
23013
  await copyFile(src, dest);
21030
23014
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
21031
23015
  }
@@ -21035,8 +23019,8 @@ async function copyAgentManifestSkills() {
21035
23019
  }
21036
23020
  async function bootstrapSkills() {
21037
23021
  await copyAgentManifestSkills();
21038
- const usmDir = join22(SKILLS_PATH, USM_DIR_NAME);
21039
- if (existsSync20(usmDir)) return;
23022
+ const usmDir = join27(SKILLS_PATH, USM_DIR_NAME);
23023
+ if (existsSync25(usmDir)) return;
21040
23024
  try {
21041
23025
  const entries = await readdir6(SKILLS_PATH);
21042
23026
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -21058,8 +23042,8 @@ async function bootstrapSkills() {
21058
23042
  }
21059
23043
  }
21060
23044
  async function patchUsmForCcClaw(usmDir) {
21061
- const skillPath = join22(usmDir, "SKILL.md");
21062
- if (!existsSync20(skillPath)) return;
23045
+ const skillPath = join27(usmDir, "SKILL.md");
23046
+ if (!existsSync25(skillPath)) return;
21063
23047
  try {
21064
23048
  let content = await readFile8(skillPath, "utf-8");
21065
23049
  let patched = false;
@@ -21104,8 +23088,8 @@ var init_bootstrap = __esm({
21104
23088
  USM_REPO = "jacob-bd/universal-skills-manager";
21105
23089
  USM_DIR_NAME = "universal-skills-manager";
21106
23090
  CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
21107
- PKG_ROOT = join22(dirname4(fileURLToPath2(import.meta.url)), "..", "..");
21108
- PKG_SKILLS = join22(PKG_ROOT, "skills");
23091
+ PKG_ROOT = join27(dirname5(fileURLToPath2(import.meta.url)), "..", "..");
23092
+ PKG_SKILLS = join27(PKG_ROOT, "skills");
21109
23093
  }
21110
23094
  });
21111
23095
 
@@ -21327,13 +23311,13 @@ __export(ai_skill_exports, {
21327
23311
  generateAiSkill: () => generateAiSkill,
21328
23312
  installAiSkill: () => installAiSkill
21329
23313
  });
21330
- import { existsSync as existsSync21, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10 } from "fs";
21331
- import { join as join23 } from "path";
21332
- import { homedir as homedir7 } from "os";
23314
+ import { existsSync as existsSync26, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
23315
+ import { join as join28 } from "path";
23316
+ import { homedir as homedir9 } from "os";
21333
23317
  function generateAiSkill() {
21334
23318
  const version = VERSION;
21335
23319
  let systemState = "";
21336
- if (existsSync21(DB_PATH)) {
23320
+ if (existsSync26(DB_PATH)) {
21337
23321
  try {
21338
23322
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
21339
23323
  const readDb = openDatabaseReadOnly2();
@@ -21421,6 +23405,7 @@ Use the CC-Claw CLI when you need to:
21421
23405
  - \`/model_signature\` \u2014 Show/hide model name on responses
21422
23406
  - \`/agents mode\` \u2014 Agent mode (auto/native/claw)
21423
23407
  - \`/voice_config\` \u2014 Voice provider settings
23408
+ - \`/debug\` \u2014 Toggle per-chat session debug logging (full tool I/O to disk)
21424
23409
 
21425
23410
  **Memory:**
21426
23411
  - \`/remember <text>\` \u2014 Save a memory
@@ -21463,6 +23448,16 @@ cc-claw logs --error # Show error log
21463
23448
  cc-claw logs --lines 50 # Show last N lines
21464
23449
  \`\`\`
21465
23450
 
23451
+ ### Session Debug Logs
23452
+ \`\`\`bash
23453
+ cc-claw session-logs list # List all session debug logs
23454
+ cc-claw session-logs tail # Tail latest session log
23455
+ cc-claw session-logs tail -f # Follow latest log (like tail -f)
23456
+ cc-claw session-logs tail --file <name> # Tail a specific log file
23457
+ cc-claw session-logs clean # Remove old session logs
23458
+ cc-claw session-logs clean --days 3 # Remove logs older than 3 days
23459
+ \`\`\`
23460
+
21466
23461
  ### Backend & Model
21467
23462
  \`\`\`bash
21468
23463
  cc-claw backend list --json # Available backends
@@ -21737,11 +23732,11 @@ function installAiSkill() {
21737
23732
  const failed = [];
21738
23733
  for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
21739
23734
  for (const dir of dirs) {
21740
- const skillDir = join23(dir, "cc-claw-cli");
21741
- const skillPath = join23(skillDir, "SKILL.md");
23735
+ const skillDir = join28(dir, "cc-claw-cli");
23736
+ const skillPath = join28(skillDir, "SKILL.md");
21742
23737
  try {
21743
- mkdirSync10(skillDir, { recursive: true });
21744
- writeFileSync7(skillPath, skill, "utf-8");
23738
+ mkdirSync11(skillDir, { recursive: true });
23739
+ writeFileSync8(skillPath, skill, "utf-8");
21745
23740
  installed.push(skillPath);
21746
23741
  } catch {
21747
23742
  failed.push(skillPath);
@@ -21757,11 +23752,11 @@ var init_ai_skill = __esm({
21757
23752
  init_paths();
21758
23753
  init_version();
21759
23754
  BACKEND_SKILL_DIRS2 = {
21760
- "cc-claw": [join23(homedir7(), ".cc-claw", "workspace", "skills")],
21761
- claude: [join23(homedir7(), ".claude", "skills")],
21762
- gemini: [join23(homedir7(), ".gemini", "skills")],
21763
- codex: [join23(homedir7(), ".agents", "skills")],
21764
- cursor: [join23(homedir7(), ".cursor", "skills"), join23(homedir7(), ".cursor", "skills-cursor")]
23755
+ "cc-claw": [join28(homedir9(), ".cc-claw", "workspace", "skills")],
23756
+ claude: [join28(homedir9(), ".claude", "skills")],
23757
+ gemini: [join28(homedir9(), ".gemini", "skills")],
23758
+ codex: [join28(homedir9(), ".agents", "skills")],
23759
+ cursor: [join28(homedir9(), ".cursor", "skills"), join28(homedir9(), ".cursor", "skills-cursor")]
21765
23760
  };
21766
23761
  }
21767
23762
  });
@@ -21771,21 +23766,21 @@ var index_exports = {};
21771
23766
  __export(index_exports, {
21772
23767
  main: () => main
21773
23768
  });
21774
- import { mkdirSync as mkdirSync11, existsSync as existsSync22, renameSync as renameSync2, statSync as statSync6, readFileSync as readFileSync12 } from "fs";
21775
- import { join as join24 } from "path";
23769
+ import { mkdirSync as mkdirSync12, existsSync as existsSync27, renameSync as renameSync2, statSync as statSync8, readFileSync as readFileSync16 } from "fs";
23770
+ import { join as join29 } from "path";
21776
23771
  import dotenv from "dotenv";
21777
23772
  function migrateLayout() {
21778
23773
  const moves = [
21779
- [join24(CC_CLAW_HOME, "cc-claw.db"), join24(DATA_PATH, "cc-claw.db")],
21780
- [join24(CC_CLAW_HOME, "cc-claw.db-shm"), join24(DATA_PATH, "cc-claw.db-shm")],
21781
- [join24(CC_CLAW_HOME, "cc-claw.db-wal"), join24(DATA_PATH, "cc-claw.db-wal")],
21782
- [join24(CC_CLAW_HOME, "cc-claw.log"), join24(LOGS_PATH, "cc-claw.log")],
21783
- [join24(CC_CLAW_HOME, "cc-claw.log.1"), join24(LOGS_PATH, "cc-claw.log.1")],
21784
- [join24(CC_CLAW_HOME, "cc-claw.error.log"), join24(LOGS_PATH, "cc-claw.error.log")],
21785
- [join24(CC_CLAW_HOME, "cc-claw.error.log.1"), join24(LOGS_PATH, "cc-claw.error.log.1")]
23774
+ [join29(CC_CLAW_HOME, "cc-claw.db"), join29(DATA_PATH, "cc-claw.db")],
23775
+ [join29(CC_CLAW_HOME, "cc-claw.db-shm"), join29(DATA_PATH, "cc-claw.db-shm")],
23776
+ [join29(CC_CLAW_HOME, "cc-claw.db-wal"), join29(DATA_PATH, "cc-claw.db-wal")],
23777
+ [join29(CC_CLAW_HOME, "cc-claw.log"), join29(LOGS_PATH, "cc-claw.log")],
23778
+ [join29(CC_CLAW_HOME, "cc-claw.log.1"), join29(LOGS_PATH, "cc-claw.log.1")],
23779
+ [join29(CC_CLAW_HOME, "cc-claw.error.log"), join29(LOGS_PATH, "cc-claw.error.log")],
23780
+ [join29(CC_CLAW_HOME, "cc-claw.error.log.1"), join29(LOGS_PATH, "cc-claw.error.log.1")]
21786
23781
  ];
21787
23782
  for (const [from, to] of moves) {
21788
- if (existsSync22(from) && !existsSync22(to)) {
23783
+ if (existsSync27(from) && !existsSync27(to)) {
21789
23784
  try {
21790
23785
  renameSync2(from, to);
21791
23786
  } catch {
@@ -21796,7 +23791,7 @@ function migrateLayout() {
21796
23791
  function rotateLogs() {
21797
23792
  for (const file of [LOG_PATH, ERROR_LOG_PATH]) {
21798
23793
  try {
21799
- const { size } = statSync6(file);
23794
+ const { size } = statSync8(file);
21800
23795
  if (size > LOG_MAX_BYTES) {
21801
23796
  const archivePath = `${file}.1`;
21802
23797
  try {
@@ -21814,7 +23809,7 @@ async function main() {
21814
23809
  let version = "unknown";
21815
23810
  try {
21816
23811
  const pkgPath = new URL("../package.json", import.meta.url);
21817
- version = JSON.parse(readFileSync12(pkgPath, "utf-8")).version;
23812
+ version = JSON.parse(readFileSync16(pkgPath, "utf-8")).version;
21818
23813
  } catch {
21819
23814
  }
21820
23815
  log(`[cc-claw] Starting v${version}`);
@@ -21930,11 +23925,11 @@ async function main() {
21930
23925
  bootstrapSkills().catch((err) => error("[cc-claw] Skill bootstrap failed:", err));
21931
23926
  try {
21932
23927
  const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
21933
- const { writeFileSync: writeFileSync12, mkdirSync: mkdirSync18 } = await import("fs");
21934
- const { join: join30 } = await import("path");
21935
- const skillDir = join30(SKILLS_PATH, "cc-claw-cli");
21936
- mkdirSync18(skillDir, { recursive: true });
21937
- writeFileSync12(join30(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
23928
+ const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync19 } = await import("fs");
23929
+ const { join: join35 } = await import("path");
23930
+ const skillDir = join35(SKILLS_PATH, "cc-claw-cli");
23931
+ mkdirSync19(skillDir, { recursive: true });
23932
+ writeFileSync13(join35(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
21938
23933
  log("[cc-claw] AI skill updated");
21939
23934
  } catch {
21940
23935
  }
@@ -21954,6 +23949,12 @@ async function main() {
21954
23949
  cleanupOldMedia().catch(() => {
21955
23950
  });
21956
23951
  pruneImageCache();
23952
+ try {
23953
+ const { cleanupSessionLogs: cleanupSessionLogs2, startSessionLogCleanupTimer: startSessionLogCleanupTimer2 } = await Promise.resolve().then(() => (init_session_log2(), session_log_exports2));
23954
+ cleanupSessionLogs2();
23955
+ startSessionLogCleanupTimer2();
23956
+ } catch {
23957
+ }
21957
23958
  log("[cc-claw] Ready!");
21958
23959
  const shutdown = async (signal) => {
21959
23960
  log(`[cc-claw] Received ${signal}, shutting down...`);
@@ -22006,11 +24007,11 @@ var init_index = __esm({
22006
24007
  init_bootstrap2();
22007
24008
  init_health3();
22008
24009
  init_image_gen();
22009
- for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
22010
- if (!existsSync22(dir)) mkdirSync11(dir, { recursive: true });
24010
+ for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SESSION_LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
24011
+ if (!existsSync27(dir)) mkdirSync12(dir, { recursive: true });
22011
24012
  }
22012
24013
  migrateLayout();
22013
- if (existsSync22(ENV_PATH)) {
24014
+ if (existsSync27(ENV_PATH)) {
22014
24015
  dotenv.config({ path: ENV_PATH });
22015
24016
  } else {
22016
24017
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -22031,12 +24032,12 @@ __export(api_client_exports, {
22031
24032
  apiPost: () => apiPost,
22032
24033
  isDaemonRunning: () => isDaemonRunning
22033
24034
  });
22034
- import { readFileSync as readFileSync13, existsSync as existsSync23 } from "fs";
24035
+ import { readFileSync as readFileSync17, existsSync as existsSync28 } from "fs";
22035
24036
  import { request as httpRequest, Agent } from "http";
22036
24037
  function getToken() {
22037
24038
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
22038
24039
  try {
22039
- if (existsSync23(TOKEN_PATH)) return readFileSync13(TOKEN_PATH, "utf-8").trim();
24040
+ if (existsSync28(TOKEN_PATH)) return readFileSync17(TOKEN_PATH, "utf-8").trim();
22040
24041
  } catch {
22041
24042
  }
22042
24043
  return null;
@@ -22135,10 +24136,10 @@ __export(service_exports, {
22135
24136
  serviceStatus: () => serviceStatus,
22136
24137
  uninstallService: () => uninstallService
22137
24138
  });
22138
- import { existsSync as existsSync24, mkdirSync as mkdirSync12, writeFileSync as writeFileSync8, unlinkSync as unlinkSync6 } from "fs";
24139
+ import { existsSync as existsSync29, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync7 } from "fs";
22139
24140
  import { execFileSync as execFileSync3, execSync as execSync6 } from "child_process";
22140
- import { homedir as homedir8, platform } from "os";
22141
- import { join as join25, dirname as dirname5 } from "path";
24141
+ import { homedir as homedir10, platform } from "os";
24142
+ import { join as join30, dirname as dirname6 } from "path";
22142
24143
  function xmlEscape(s) {
22143
24144
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
22144
24145
  }
@@ -22147,23 +24148,23 @@ function resolveExecutable2(name) {
22147
24148
  return execFileSync3("which", [name], { encoding: "utf-8" }).trim();
22148
24149
  } catch {
22149
24150
  const fallback = process.argv[1];
22150
- if (fallback && existsSync24(fallback)) return fallback;
24151
+ if (fallback && existsSync29(fallback)) return fallback;
22151
24152
  throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
22152
24153
  }
22153
24154
  }
22154
24155
  function getPathDirs() {
22155
- const nodeBin = dirname5(process.execPath);
22156
- const home = homedir8();
24156
+ const nodeBin = dirname6(process.execPath);
24157
+ const home = homedir10();
22157
24158
  const dirs = /* @__PURE__ */ new Set([
22158
24159
  nodeBin,
22159
- join25(home, ".local", "bin"),
24160
+ join30(home, ".local", "bin"),
22160
24161
  "/usr/local/bin",
22161
24162
  "/usr/bin",
22162
24163
  "/bin"
22163
24164
  ]);
22164
24165
  try {
22165
24166
  const prefix = execSync6("npm config get prefix", { encoding: "utf-8" }).trim();
22166
- if (prefix) dirs.add(join25(prefix, "bin"));
24167
+ if (prefix) dirs.add(join30(prefix, "bin"));
22167
24168
  } catch {
22168
24169
  }
22169
24170
  return [...dirs].join(":");
@@ -22171,7 +24172,7 @@ function getPathDirs() {
22171
24172
  function generatePlist() {
22172
24173
  const ccClawBin = resolveExecutable2("cc-claw");
22173
24174
  const pathDirs = getPathDirs();
22174
- const home = homedir8();
24175
+ const home = homedir10();
22175
24176
  const safeBin = xmlEscape(ccClawBin);
22176
24177
  const safePaths = xmlEscape(pathDirs);
22177
24178
  const safeHome = xmlEscape(home);
@@ -22221,22 +24222,22 @@ function generatePlist() {
22221
24222
  </plist>`;
22222
24223
  }
22223
24224
  function installMacOS() {
22224
- const agentsDir = dirname5(PLIST_PATH);
22225
- if (!existsSync24(agentsDir)) mkdirSync12(agentsDir, { recursive: true });
22226
- if (!existsSync24(LOGS_PATH)) mkdirSync12(LOGS_PATH, { recursive: true });
22227
- if (existsSync24(PLIST_PATH)) {
24225
+ const agentsDir = dirname6(PLIST_PATH);
24226
+ if (!existsSync29(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
24227
+ if (!existsSync29(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
24228
+ if (existsSync29(PLIST_PATH)) {
22228
24229
  try {
22229
24230
  execFileSync3("launchctl", ["unload", PLIST_PATH]);
22230
24231
  } catch {
22231
24232
  }
22232
24233
  }
22233
- writeFileSync8(PLIST_PATH, generatePlist());
24234
+ writeFileSync9(PLIST_PATH, generatePlist());
22234
24235
  console.log(` Installed: ${PLIST_PATH}`);
22235
24236
  execFileSync3("launchctl", ["load", PLIST_PATH]);
22236
24237
  console.log(" Service loaded and starting.");
22237
24238
  }
22238
24239
  function uninstallMacOS() {
22239
- if (!existsSync24(PLIST_PATH)) {
24240
+ if (!existsSync29(PLIST_PATH)) {
22240
24241
  console.log(" No service found to uninstall.");
22241
24242
  return;
22242
24243
  }
@@ -22244,7 +24245,7 @@ function uninstallMacOS() {
22244
24245
  execFileSync3("launchctl", ["unload", PLIST_PATH]);
22245
24246
  } catch {
22246
24247
  }
22247
- unlinkSync6(PLIST_PATH);
24248
+ unlinkSync7(PLIST_PATH);
22248
24249
  console.log(" Service uninstalled.");
22249
24250
  }
22250
24251
  function formatUptime(seconds) {
@@ -22304,16 +24305,16 @@ Restart=on-failure
22304
24305
  RestartSec=10
22305
24306
  WorkingDirectory=${CC_CLAW_HOME}
22306
24307
  Environment=PATH=${pathDirs}
22307
- Environment=HOME=${homedir8()}
24308
+ Environment=HOME=${homedir10()}
22308
24309
 
22309
24310
  [Install]
22310
24311
  WantedBy=default.target
22311
24312
  `;
22312
24313
  }
22313
24314
  function installLinux() {
22314
- if (!existsSync24(SYSTEMD_DIR)) mkdirSync12(SYSTEMD_DIR, { recursive: true });
22315
- if (!existsSync24(LOGS_PATH)) mkdirSync12(LOGS_PATH, { recursive: true });
22316
- writeFileSync8(UNIT_PATH, generateUnit());
24315
+ if (!existsSync29(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
24316
+ if (!existsSync29(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
24317
+ writeFileSync9(UNIT_PATH, generateUnit());
22317
24318
  console.log(` Installed: ${UNIT_PATH}`);
22318
24319
  execFileSync3("systemctl", ["--user", "daemon-reload"]);
22319
24320
  execFileSync3("systemctl", ["--user", "enable", "cc-claw"]);
@@ -22321,7 +24322,7 @@ function installLinux() {
22321
24322
  console.log(" Service enabled and started.");
22322
24323
  }
22323
24324
  function uninstallLinux() {
22324
- if (!existsSync24(UNIT_PATH)) {
24325
+ if (!existsSync29(UNIT_PATH)) {
22325
24326
  console.log(" No service found to uninstall.");
22326
24327
  return;
22327
24328
  }
@@ -22333,7 +24334,7 @@ function uninstallLinux() {
22333
24334
  execFileSync3("systemctl", ["--user", "disable", "cc-claw"]);
22334
24335
  } catch {
22335
24336
  }
22336
- unlinkSync6(UNIT_PATH);
24337
+ unlinkSync7(UNIT_PATH);
22337
24338
  execFileSync3("systemctl", ["--user", "daemon-reload"]);
22338
24339
  console.log(" Service uninstalled.");
22339
24340
  }
@@ -22346,7 +24347,7 @@ function statusLinux() {
22346
24347
  }
22347
24348
  }
22348
24349
  function installService() {
22349
- if (!existsSync24(join25(CC_CLAW_HOME, ".env"))) {
24350
+ if (!existsSync29(join30(CC_CLAW_HOME, ".env"))) {
22350
24351
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
22351
24352
  console.error(" Run 'cc-claw setup' before installing the service.");
22352
24353
  process.exitCode = 1;
@@ -22375,9 +24376,9 @@ var init_service = __esm({
22375
24376
  "use strict";
22376
24377
  init_paths();
22377
24378
  PLIST_LABEL = "com.cc-claw";
22378
- PLIST_PATH = join25(homedir8(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
22379
- SYSTEMD_DIR = join25(homedir8(), ".config", "systemd", "user");
22380
- UNIT_PATH = join25(SYSTEMD_DIR, "cc-claw.service");
24379
+ PLIST_PATH = join30(homedir10(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
24380
+ SYSTEMD_DIR = join30(homedir10(), ".config", "systemd", "user");
24381
+ UNIT_PATH = join30(SYSTEMD_DIR, "cc-claw.service");
22381
24382
  }
22382
24383
  });
22383
24384
 
@@ -22544,13 +24545,13 @@ var init_daemon = __esm({
22544
24545
  });
22545
24546
 
22546
24547
  // src/cli/resolve-chat.ts
22547
- import { readFileSync as readFileSync15 } from "fs";
24548
+ import { readFileSync as readFileSync19 } from "fs";
22548
24549
  function resolveChatId(globalOpts) {
22549
24550
  const explicit = globalOpts.chat;
22550
24551
  if (explicit) return explicit;
22551
24552
  if (_cachedDefault) return _cachedDefault;
22552
24553
  try {
22553
- const content = readFileSync15(ENV_PATH, "utf-8");
24554
+ const content = readFileSync19(ENV_PATH, "utf-8");
22554
24555
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
22555
24556
  if (match) {
22556
24557
  _cachedDefault = match[1].split(",")[0].trim();
@@ -22574,7 +24575,7 @@ var status_exports = {};
22574
24575
  __export(status_exports, {
22575
24576
  statusCommand: () => statusCommand
22576
24577
  });
22577
- import { existsSync as existsSync25, statSync as statSync7 } from "fs";
24578
+ import { existsSync as existsSync30, statSync as statSync9 } from "fs";
22578
24579
  async function statusCommand(globalOpts, localOpts) {
22579
24580
  try {
22580
24581
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
@@ -22614,7 +24615,7 @@ async function statusCommand(globalOpts, localOpts) {
22614
24615
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
22615
24616
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
22616
24617
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
22617
- const dbStat = existsSync25(DB_PATH) ? statSync7(DB_PATH) : null;
24618
+ const dbStat = existsSync30(DB_PATH) ? statSync9(DB_PATH) : null;
22618
24619
  let daemonRunning = false;
22619
24620
  let daemonInfo = {};
22620
24621
  try {
@@ -22703,12 +24704,12 @@ var doctor_exports = {};
22703
24704
  __export(doctor_exports, {
22704
24705
  doctorCommand: () => doctorCommand
22705
24706
  });
22706
- import { existsSync as existsSync26, statSync as statSync8, accessSync, constants } from "fs";
24707
+ import { existsSync as existsSync31, statSync as statSync10, accessSync, constants } from "fs";
22707
24708
  import { execFileSync as execFileSync4 } from "child_process";
22708
24709
  async function doctorCommand(globalOpts, localOpts) {
22709
24710
  const checks = [];
22710
- if (existsSync26(DB_PATH)) {
22711
- const size = statSync8(DB_PATH).size;
24711
+ if (existsSync31(DB_PATH)) {
24712
+ const size = statSync10(DB_PATH).size;
22712
24713
  checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
22713
24714
  try {
22714
24715
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
@@ -22737,7 +24738,7 @@ async function doctorCommand(globalOpts, localOpts) {
22737
24738
  } else {
22738
24739
  checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
22739
24740
  }
22740
- if (existsSync26(ENV_PATH)) {
24741
+ if (existsSync31(ENV_PATH)) {
22741
24742
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
22742
24743
  } else {
22743
24744
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
@@ -22792,7 +24793,7 @@ async function doctorCommand(globalOpts, localOpts) {
22792
24793
  } catch {
22793
24794
  }
22794
24795
  const tokenPath = `${DATA_PATH}/api-token`;
22795
- if (existsSync26(tokenPath)) {
24796
+ if (existsSync31(tokenPath)) {
22796
24797
  try {
22797
24798
  accessSync(tokenPath, constants.R_OK);
22798
24799
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -22817,10 +24818,10 @@ async function doctorCommand(globalOpts, localOpts) {
22817
24818
  }
22818
24819
  } catch {
22819
24820
  }
22820
- if (existsSync26(ERROR_LOG_PATH)) {
24821
+ if (existsSync31(ERROR_LOG_PATH)) {
22821
24822
  try {
22822
- const { readFileSync: readFileSync22 } = await import("fs");
22823
- const logContent = readFileSync22(ERROR_LOG_PATH, "utf-8");
24823
+ const { readFileSync: readFileSync27 } = await import("fs");
24824
+ const logContent = readFileSync27(ERROR_LOG_PATH, "utf-8");
22824
24825
  const recentLines = logContent.split("\n").filter(Boolean).slice(-100);
22825
24826
  const last24h = Date.now() - 864e5;
22826
24827
  const recentErrors = recentLines.filter((line) => {
@@ -22943,15 +24944,15 @@ var logs_exports = {};
22943
24944
  __export(logs_exports, {
22944
24945
  logsCommand: () => logsCommand
22945
24946
  });
22946
- import { existsSync as existsSync27, readFileSync as readFileSync16, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
24947
+ import { existsSync as existsSync32, readFileSync as readFileSync20, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
22947
24948
  async function logsCommand(opts) {
22948
24949
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
22949
- if (!existsSync27(logFile)) {
24950
+ if (!existsSync32(logFile)) {
22950
24951
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
22951
24952
  process.exit(1);
22952
24953
  }
22953
24954
  const maxLines = parseInt(opts.lines ?? "100", 10);
22954
- const content = readFileSync16(logFile, "utf-8");
24955
+ const content = readFileSync20(logFile, "utf-8");
22955
24956
  const allLines = content.split("\n");
22956
24957
  const tailLines = allLines.slice(-maxLines);
22957
24958
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -22961,7 +24962,7 @@ async function logsCommand(opts) {
22961
24962
  let lastLength = content.length;
22962
24963
  watchFile2(logFile, { interval: 500 }, () => {
22963
24964
  try {
22964
- const newContent = readFileSync16(logFile, "utf-8");
24965
+ const newContent = readFileSync20(logFile, "utf-8");
22965
24966
  if (newContent.length > lastLength) {
22966
24967
  const newPart = newContent.slice(lastLength);
22967
24968
  process.stdout.write(newPart);
@@ -22986,6 +24987,105 @@ var init_logs = __esm({
22986
24987
  }
22987
24988
  });
22988
24989
 
24990
+ // src/cli/commands/session-logs.ts
24991
+ var session_logs_exports = {};
24992
+ __export(session_logs_exports, {
24993
+ sessionLogsClean: () => sessionLogsClean,
24994
+ sessionLogsList: () => sessionLogsList,
24995
+ sessionLogsTail: () => sessionLogsTail
24996
+ });
24997
+ import { readFileSync as readFileSync21, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
24998
+ async function sessionLogsList(opts) {
24999
+ const logs = listSessionLogs();
25000
+ if (logs.length === 0) {
25001
+ console.log(muted(" No session debug logs found."));
25002
+ console.log(muted(` Enable via /debug in Telegram, then logs will appear in ${SESSION_LOGS_PATH}`));
25003
+ return;
25004
+ }
25005
+ if (opts.json) {
25006
+ console.log(JSON.stringify(logs.map((l) => ({
25007
+ filename: l.filename,
25008
+ chatId: l.chatId,
25009
+ timestamp: l.timestamp,
25010
+ sizeBytes: l.sizeBytes,
25011
+ modifiedAt: l.modifiedAt.toISOString()
25012
+ })), null, 2));
25013
+ return;
25014
+ }
25015
+ console.log(muted(` \u2500\u2500 Session Debug Logs (${logs.length}) \u2500\u2500
25016
+ `));
25017
+ console.log(` ${"Filename".padEnd(55)} ${"Size".padStart(8)} Chat ID`);
25018
+ console.log(` ${"\u2500".repeat(55)} ${"\u2500".repeat(8)} ${"\u2500".repeat(15)}`);
25019
+ for (const log5 of logs) {
25020
+ const size = log5.sizeBytes < 1024 ? `${log5.sizeBytes}B` : log5.sizeBytes < 1024 * 1024 ? `${(log5.sizeBytes / 1024).toFixed(1)}K` : `${(log5.sizeBytes / 1024 / 1024).toFixed(1)}M`;
25021
+ console.log(` ${log5.filename.padEnd(55)} ${size.padStart(8)} ${log5.chatId}`);
25022
+ }
25023
+ console.log(muted(`
25024
+ Path: ${SESSION_LOGS_PATH}`));
25025
+ console.log(muted(` Retention: ${getRetentionDays()} day(s) (set SESSION_LOG_RETENTION_DAYS to change)`));
25026
+ }
25027
+ async function sessionLogsTail(opts) {
25028
+ const logs = listSessionLogs();
25029
+ if (logs.length === 0) {
25030
+ outputError("NO_LOGS", "No session debug logs found. Enable via /debug in Telegram.");
25031
+ return;
25032
+ }
25033
+ let targetPath;
25034
+ if (opts.file) {
25035
+ const match = logs.find((l) => l.filename.includes(opts.file));
25036
+ if (!match) {
25037
+ outputError("NOT_FOUND", `No session log matching "${opts.file}". Use 'cc-claw logs session list' to see available logs.`);
25038
+ return;
25039
+ }
25040
+ targetPath = match.filePath;
25041
+ } else {
25042
+ targetPath = logs[0].filePath;
25043
+ }
25044
+ const lineCount = parseInt(opts.lines ?? "50", 10);
25045
+ console.log(muted(` \u2500\u2500 ${targetPath} (last ${lineCount} lines) \u2500\u2500
25046
+ `));
25047
+ for await (const line of tailSessionLog(targetPath, lineCount)) {
25048
+ console.log(line);
25049
+ }
25050
+ if (opts.follow) {
25051
+ console.log(muted("\n Following... (Ctrl+C to stop)\n"));
25052
+ let lastLength = 0;
25053
+ try {
25054
+ lastLength = readFileSync21(targetPath, "utf-8").length;
25055
+ } catch {
25056
+ }
25057
+ watchFile3(targetPath, { interval: 500 }, () => {
25058
+ try {
25059
+ const content = readFileSync21(targetPath, "utf-8");
25060
+ if (content.length > lastLength) {
25061
+ process.stdout.write(content.slice(lastLength));
25062
+ lastLength = content.length;
25063
+ }
25064
+ } catch {
25065
+ }
25066
+ });
25067
+ process.on("SIGINT", () => {
25068
+ unwatchFile3(targetPath);
25069
+ process.exit(0);
25070
+ });
25071
+ await new Promise(() => {
25072
+ });
25073
+ }
25074
+ }
25075
+ async function sessionLogsClean(opts) {
25076
+ const days = opts.days ? parseInt(opts.days, 10) : void 0;
25077
+ const cleaned = cleanupSessionLogs(days);
25078
+ console.log(success(`Cleaned ${cleaned} session log(s) older than ${days ?? getRetentionDays()} day(s).`));
25079
+ }
25080
+ var init_session_logs = __esm({
25081
+ "src/cli/commands/session-logs.ts"() {
25082
+ "use strict";
25083
+ init_session_log2();
25084
+ init_paths();
25085
+ init_format2();
25086
+ }
25087
+ });
25088
+
22989
25089
  // src/cli/commands/gemini.ts
22990
25090
  var gemini_exports = {};
22991
25091
  __export(gemini_exports, {
@@ -22998,11 +25098,11 @@ __export(gemini_exports, {
22998
25098
  geminiReorder: () => geminiReorder,
22999
25099
  geminiRotation: () => geminiRotation
23000
25100
  });
23001
- import { existsSync as existsSync28, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, readFileSync as readFileSync17, chmodSync } from "fs";
23002
- import { join as join26 } from "path";
23003
- import { createInterface as createInterface5 } from "readline";
25101
+ import { existsSync as existsSync34, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync22, chmodSync } from "fs";
25102
+ import { join as join31 } from "path";
25103
+ import { createInterface as createInterface7 } from "readline";
23004
25104
  function requireDb() {
23005
- if (!existsSync28(DB_PATH)) {
25105
+ if (!existsSync34(DB_PATH)) {
23006
25106
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
23007
25107
  process.exit(1);
23008
25108
  }
@@ -23027,9 +25127,9 @@ async function resolveSlotId(idOrLabel) {
23027
25127
  function resolveOAuthEmail(configHome) {
23028
25128
  if (!configHome) return null;
23029
25129
  try {
23030
- const accountsPath = join26(configHome, ".gemini", "google_accounts.json");
23031
- if (!existsSync28(accountsPath)) return null;
23032
- const accounts = JSON.parse(readFileSync17(accountsPath, "utf-8"));
25130
+ const accountsPath = join31(configHome, ".gemini", "google_accounts.json");
25131
+ if (!existsSync34(accountsPath)) return null;
25132
+ const accounts = JSON.parse(readFileSync22(accountsPath, "utf-8"));
23033
25133
  return accounts.active || null;
23034
25134
  } catch {
23035
25135
  return null;
@@ -23073,7 +25173,7 @@ async function geminiList(globalOpts) {
23073
25173
  }
23074
25174
  async function geminiAddKey(globalOpts, opts) {
23075
25175
  await requireWriteDb();
23076
- const rl2 = createInterface5({ input: process.stdin, output: process.stdout });
25176
+ const rl2 = createInterface7({ input: process.stdin, output: process.stdout });
23077
25177
  const ask2 = (q) => new Promise((r) => rl2.question(q, r));
23078
25178
  const key = await ask2("Paste your Gemini API key: ");
23079
25179
  rl2.close();
@@ -23111,14 +25211,14 @@ async function geminiAddKey(globalOpts, opts) {
23111
25211
  }
23112
25212
  async function geminiAddAccount(globalOpts, opts) {
23113
25213
  await requireWriteDb();
23114
- const slotsDir = join26(CC_CLAW_HOME, "gemini-slots");
23115
- if (!existsSync28(slotsDir)) mkdirSync13(slotsDir, { recursive: true });
25214
+ const slotsDir = join31(CC_CLAW_HOME, "gemini-slots");
25215
+ if (!existsSync34(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
23116
25216
  const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
23117
25217
  const tempId = Date.now();
23118
- const slotDir = join26(slotsDir, `slot-${tempId}`);
23119
- mkdirSync13(slotDir, { recursive: true, mode: 448 });
23120
- mkdirSync13(join26(slotDir, ".gemini"), { recursive: true });
23121
- writeFileSync9(join26(slotDir, ".gemini", "settings.json"), JSON.stringify({
25218
+ const slotDir = join31(slotsDir, `slot-${tempId}`);
25219
+ mkdirSync14(slotDir, { recursive: true, mode: 448 });
25220
+ mkdirSync14(join31(slotDir, ".gemini"), { recursive: true });
25221
+ writeFileSync10(join31(slotDir, ".gemini", "settings.json"), JSON.stringify({
23122
25222
  security: { auth: { selectedType: "oauth-personal" } }
23123
25223
  }, null, 2));
23124
25224
  console.log("");
@@ -23135,8 +25235,8 @@ async function geminiAddAccount(globalOpts, opts) {
23135
25235
  });
23136
25236
  } catch {
23137
25237
  }
23138
- const oauthPath = join26(slotDir, ".gemini", "oauth_creds.json");
23139
- if (!existsSync28(oauthPath)) {
25238
+ const oauthPath = join31(slotDir, ".gemini", "oauth_creds.json");
25239
+ if (!existsSync34(oauthPath)) {
23140
25240
  console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
23141
25241
  console.log(" The slot directory is preserved at: " + slotDir);
23142
25242
  console.log(" Re-run: cc-claw gemini add-account\n");
@@ -23144,7 +25244,7 @@ async function geminiAddAccount(globalOpts, opts) {
23144
25244
  }
23145
25245
  let accountEmail = "unknown";
23146
25246
  try {
23147
- const accounts = JSON.parse(__require("fs").readFileSync(join26(slotDir, ".gemini", "google_accounts.json"), "utf-8"));
25247
+ const accounts = JSON.parse(__require("fs").readFileSync(join31(slotDir, ".gemini", "google_accounts.json"), "utf-8"));
23148
25248
  accountEmail = accounts.active || accountEmail;
23149
25249
  } catch {
23150
25250
  }
@@ -23263,11 +25363,11 @@ __export(backend_cmd_factory_exports, {
23263
25363
  makeReorder: () => makeReorder,
23264
25364
  registerBackendSlotCommands: () => registerBackendSlotCommands
23265
25365
  });
23266
- import { existsSync as existsSync29, mkdirSync as mkdirSync14, readFileSync as readFileSync18 } from "fs";
23267
- import { join as join27 } from "path";
23268
- import { createInterface as createInterface6 } from "readline";
25366
+ import { existsSync as existsSync35, mkdirSync as mkdirSync15, readFileSync as readFileSync23 } from "fs";
25367
+ import { join as join32 } from "path";
25368
+ import { createInterface as createInterface8 } from "readline";
23269
25369
  function requireDb2() {
23270
- if (!existsSync29(DB_PATH)) {
25370
+ if (!existsSync35(DB_PATH)) {
23271
25371
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
23272
25372
  process.exit(1);
23273
25373
  }
@@ -23326,7 +25426,7 @@ Add one with: cc-claw ${backend2} add-account or cc-claw ${backend2} add-key`)
23326
25426
  function makeAddKey(backend2, displayName) {
23327
25427
  return async function addKey(_globalOpts, opts) {
23328
25428
  await requireWriteDb2();
23329
- const rl2 = createInterface6({ input: process.stdin, output: process.stdout });
25429
+ const rl2 = createInterface8({ input: process.stdin, output: process.stdout });
23330
25430
  const ask2 = (q) => new Promise((r) => rl2.question(q, r));
23331
25431
  const key = await ask2(`Paste your ${displayName} API key: `);
23332
25432
  rl2.close();
@@ -23356,11 +25456,11 @@ function makeAddAccount(backend2, displayName) {
23356
25456
  process.exit(1);
23357
25457
  }
23358
25458
  await requireWriteDb2();
23359
- const slotsDir = join27(CC_CLAW_HOME, config2.slotsSubdir);
23360
- if (!existsSync29(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
25459
+ const slotsDir = join32(CC_CLAW_HOME, config2.slotsSubdir);
25460
+ if (!existsSync35(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
23361
25461
  const tempId = Date.now();
23362
- const slotDir = join27(slotsDir, `slot-${tempId}`);
23363
- mkdirSync14(slotDir, { recursive: true, mode: 448 });
25462
+ const slotDir = join32(slotsDir, `slot-${tempId}`);
25463
+ mkdirSync15(slotDir, { recursive: true, mode: 448 });
23364
25464
  if (config2.preSetup) config2.preSetup(slotDir);
23365
25465
  console.log("");
23366
25466
  console.log(` Opening ${displayName} CLI for sign-in...`);
@@ -23528,22 +25628,22 @@ var init_backend_cmd_factory = __esm({
23528
25628
  envValue: (slotDir) => slotDir,
23529
25629
  envOverrides: { ANTHROPIC_API_KEY: void 0 },
23530
25630
  preSetup: (slotDir) => {
23531
- mkdirSync14(join27(slotDir, ".claude"), { recursive: true });
25631
+ mkdirSync15(join32(slotDir, ".claude"), { recursive: true });
23532
25632
  },
23533
25633
  verifyCredentials: (slotDir) => {
23534
- const claudeJson = join27(slotDir, ".claude.json");
23535
- const claudeJsonNested = join27(slotDir, ".claude", ".claude.json");
23536
- if (existsSync29(claudeJson)) {
25634
+ const claudeJson = join32(slotDir, ".claude.json");
25635
+ const claudeJsonNested = join32(slotDir, ".claude", ".claude.json");
25636
+ if (existsSync35(claudeJson)) {
23537
25637
  try {
23538
- const data = JSON.parse(readFileSync18(claudeJson, "utf-8"));
25638
+ const data = JSON.parse(readFileSync23(claudeJson, "utf-8"));
23539
25639
  return Boolean(data.oauthAccount);
23540
25640
  } catch {
23541
25641
  return false;
23542
25642
  }
23543
25643
  }
23544
- if (existsSync29(claudeJsonNested)) {
25644
+ if (existsSync35(claudeJsonNested)) {
23545
25645
  try {
23546
- const data = JSON.parse(readFileSync18(claudeJsonNested, "utf-8"));
25646
+ const data = JSON.parse(readFileSync23(claudeJsonNested, "utf-8"));
23547
25647
  return Boolean(data.oauthAccount);
23548
25648
  } catch {
23549
25649
  return false;
@@ -23564,9 +25664,9 @@ var init_backend_cmd_factory = __esm({
23564
25664
  } catch {
23565
25665
  }
23566
25666
  try {
23567
- const claudeJson = join27(slotDir, ".claude.json");
23568
- if (existsSync29(claudeJson)) {
23569
- const data = JSON.parse(readFileSync18(claudeJson, "utf-8"));
25667
+ const claudeJson = join32(slotDir, ".claude.json");
25668
+ if (existsSync35(claudeJson)) {
25669
+ const data = JSON.parse(readFileSync23(claudeJson, "utf-8"));
23570
25670
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
23571
25671
  }
23572
25672
  } catch {
@@ -23581,11 +25681,11 @@ var init_backend_cmd_factory = __esm({
23581
25681
  envValue: (slotDir) => slotDir,
23582
25682
  envOverrides: { OPENAI_API_KEY: void 0 },
23583
25683
  verifyCredentials: (slotDir) => {
23584
- return existsSync29(join27(slotDir, "auth.json"));
25684
+ return existsSync35(join32(slotDir, "auth.json"));
23585
25685
  },
23586
25686
  extractLabel: (slotDir) => {
23587
25687
  try {
23588
- const authData = JSON.parse(readFileSync18(join27(slotDir, "auth.json"), "utf-8"));
25688
+ const authData = JSON.parse(readFileSync23(join32(slotDir, "auth.json"), "utf-8"));
23589
25689
  if (authData.email) return authData.email;
23590
25690
  if (authData.account_name) return authData.account_name;
23591
25691
  if (authData.user?.email) return authData.user.email;
@@ -23605,12 +25705,12 @@ __export(backend_exports, {
23605
25705
  backendList: () => backendList,
23606
25706
  backendSet: () => backendSet
23607
25707
  });
23608
- import { existsSync as existsSync30 } from "fs";
25708
+ import { existsSync as existsSync36 } from "fs";
23609
25709
  async function backendList(globalOpts) {
23610
25710
  const { getAvailableAdapters: getAvailableAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
23611
25711
  const chatId = resolveChatId(globalOpts);
23612
25712
  let activeBackend = null;
23613
- if (existsSync30(DB_PATH)) {
25713
+ if (existsSync36(DB_PATH)) {
23614
25714
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
23615
25715
  const readDb = openDatabaseReadOnly2();
23616
25716
  try {
@@ -23641,7 +25741,7 @@ async function backendList(globalOpts) {
23641
25741
  }
23642
25742
  async function backendGet(globalOpts) {
23643
25743
  const chatId = resolveChatId(globalOpts);
23644
- if (!existsSync30(DB_PATH)) {
25744
+ if (!existsSync36(DB_PATH)) {
23645
25745
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
23646
25746
  process.exit(1);
23647
25747
  }
@@ -23685,13 +25785,13 @@ __export(model_exports, {
23685
25785
  modelList: () => modelList,
23686
25786
  modelSet: () => modelSet
23687
25787
  });
23688
- import { existsSync as existsSync31 } from "fs";
25788
+ import { existsSync as existsSync37 } from "fs";
23689
25789
  async function modelList(globalOpts) {
23690
25790
  const chatId = resolveChatId(globalOpts);
23691
25791
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
23692
25792
  const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
23693
25793
  let backendId = "claude";
23694
- if (existsSync31(DB_PATH)) {
25794
+ if (existsSync37(DB_PATH)) {
23695
25795
  const readDb = openDatabaseReadOnly2();
23696
25796
  try {
23697
25797
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -23724,7 +25824,7 @@ async function modelList(globalOpts) {
23724
25824
  }
23725
25825
  async function modelGet(globalOpts) {
23726
25826
  const chatId = resolveChatId(globalOpts);
23727
- if (!existsSync31(DB_PATH)) {
25827
+ if (!existsSync37(DB_PATH)) {
23728
25828
  outputError("DB_NOT_FOUND", "Database not found.");
23729
25829
  process.exit(1);
23730
25830
  }
@@ -23768,9 +25868,9 @@ __export(memory_exports2, {
23768
25868
  memoryList: () => memoryList,
23769
25869
  memorySearch: () => memorySearch
23770
25870
  });
23771
- import { existsSync as existsSync32 } from "fs";
25871
+ import { existsSync as existsSync38 } from "fs";
23772
25872
  async function memoryList(globalOpts) {
23773
- if (!existsSync32(DB_PATH)) {
25873
+ if (!existsSync38(DB_PATH)) {
23774
25874
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
23775
25875
  process.exit(1);
23776
25876
  }
@@ -23794,7 +25894,7 @@ async function memoryList(globalOpts) {
23794
25894
  });
23795
25895
  }
23796
25896
  async function memorySearch(globalOpts, query) {
23797
- if (!existsSync32(DB_PATH)) {
25897
+ if (!existsSync38(DB_PATH)) {
23798
25898
  outputError("DB_NOT_FOUND", "Database not found.");
23799
25899
  process.exit(1);
23800
25900
  }
@@ -23816,7 +25916,7 @@ async function memorySearch(globalOpts, query) {
23816
25916
  });
23817
25917
  }
23818
25918
  async function memoryHistory(globalOpts, opts) {
23819
- if (!existsSync32(DB_PATH)) {
25919
+ if (!existsSync38(DB_PATH)) {
23820
25920
  outputError("DB_NOT_FOUND", "Database not found.");
23821
25921
  process.exit(1);
23822
25922
  }
@@ -23864,7 +25964,7 @@ __export(cron_exports2, {
23864
25964
  cronList: () => cronList,
23865
25965
  cronRuns: () => cronRuns
23866
25966
  });
23867
- import { existsSync as existsSync33 } from "fs";
25967
+ import { existsSync as existsSync39 } from "fs";
23868
25968
  function parseFallbacks(raw) {
23869
25969
  return raw.slice(0, 3).map((f) => {
23870
25970
  const [backend2, ...rest] = f.split(":");
@@ -23885,7 +25985,7 @@ function parseAndValidateTimeout(raw) {
23885
25985
  return val;
23886
25986
  }
23887
25987
  async function cronList(globalOpts) {
23888
- if (!existsSync33(DB_PATH)) {
25988
+ if (!existsSync39(DB_PATH)) {
23889
25989
  outputError("DB_NOT_FOUND", "Database not found.");
23890
25990
  process.exit(1);
23891
25991
  }
@@ -23923,7 +26023,7 @@ async function cronList(globalOpts) {
23923
26023
  });
23924
26024
  }
23925
26025
  async function cronHealth(globalOpts) {
23926
- if (!existsSync33(DB_PATH)) {
26026
+ if (!existsSync39(DB_PATH)) {
23927
26027
  outputError("DB_NOT_FOUND", "Database not found.");
23928
26028
  process.exit(1);
23929
26029
  }
@@ -24082,7 +26182,7 @@ async function cronEdit(globalOpts, id, opts) {
24082
26182
  }
24083
26183
  }
24084
26184
  async function cronRuns(globalOpts, jobId, opts) {
24085
- if (!existsSync33(DB_PATH)) {
26185
+ if (!existsSync39(DB_PATH)) {
24086
26186
  outputError("DB_NOT_FOUND", "Database not found.");
24087
26187
  process.exit(1);
24088
26188
  }
@@ -24129,9 +26229,9 @@ __export(agents_exports, {
24129
26229
  runnersList: () => runnersList,
24130
26230
  tasksList: () => tasksList
24131
26231
  });
24132
- import { existsSync as existsSync34 } from "fs";
26232
+ import { existsSync as existsSync40 } from "fs";
24133
26233
  async function agentsList(globalOpts) {
24134
- if (!existsSync34(DB_PATH)) {
26234
+ if (!existsSync40(DB_PATH)) {
24135
26235
  outputError("DB_NOT_FOUND", "Database not found.");
24136
26236
  process.exit(1);
24137
26237
  }
@@ -24162,7 +26262,7 @@ async function agentsList(globalOpts) {
24162
26262
  });
24163
26263
  }
24164
26264
  async function tasksList(globalOpts) {
24165
- if (!existsSync34(DB_PATH)) {
26265
+ if (!existsSync40(DB_PATH)) {
24166
26266
  outputError("DB_NOT_FOUND", "Database not found.");
24167
26267
  process.exit(1);
24168
26268
  }
@@ -24290,18 +26390,18 @@ __export(db_exports, {
24290
26390
  dbPath: () => dbPath,
24291
26391
  dbStats: () => dbStats
24292
26392
  });
24293
- import { existsSync as existsSync35, statSync as statSync9, copyFileSync as copyFileSync3, mkdirSync as mkdirSync15 } from "fs";
24294
- import { dirname as dirname6 } from "path";
26393
+ import { existsSync as existsSync41, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
26394
+ import { dirname as dirname7 } from "path";
24295
26395
  async function dbStats(globalOpts) {
24296
- if (!existsSync35(DB_PATH)) {
26396
+ if (!existsSync41(DB_PATH)) {
24297
26397
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
24298
26398
  process.exit(1);
24299
26399
  }
24300
26400
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
24301
26401
  const readDb = openDatabaseReadOnly2();
24302
- const mainSize = statSync9(DB_PATH).size;
26402
+ const mainSize = statSync11(DB_PATH).size;
24303
26403
  const walPath = DB_PATH + "-wal";
24304
- const walSize = existsSync35(walPath) ? statSync9(walPath).size : 0;
26404
+ const walSize = existsSync41(walPath) ? statSync11(walPath).size : 0;
24305
26405
  const tableNames = readDb.prepare(
24306
26406
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
24307
26407
  ).all();
@@ -24335,17 +26435,17 @@ async function dbPath(globalOpts) {
24335
26435
  output({ path: DB_PATH }, (d) => d.path);
24336
26436
  }
24337
26437
  async function dbBackup(globalOpts, destPath) {
24338
- if (!existsSync35(DB_PATH)) {
26438
+ if (!existsSync41(DB_PATH)) {
24339
26439
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
24340
26440
  process.exit(1);
24341
26441
  }
24342
26442
  const dest = destPath ?? `${DB_PATH}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
24343
26443
  try {
24344
- mkdirSync15(dirname6(dest), { recursive: true });
26444
+ mkdirSync16(dirname7(dest), { recursive: true });
24345
26445
  copyFileSync3(DB_PATH, dest);
24346
26446
  const walPath = DB_PATH + "-wal";
24347
- if (existsSync35(walPath)) copyFileSync3(walPath, dest + "-wal");
24348
- output({ path: dest, sizeBytes: statSync9(dest).size }, (d) => {
26447
+ if (existsSync41(walPath)) copyFileSync3(walPath, dest + "-wal");
26448
+ output({ path: dest, sizeBytes: statSync11(dest).size }, (d) => {
24349
26449
  const b = d;
24350
26450
  return `
24351
26451
  ${success("Backup created:")} ${b.path} (${(b.sizeBytes / 1024).toFixed(0)}KB)
@@ -24373,9 +26473,9 @@ __export(usage_exports, {
24373
26473
  usageCost: () => usageCost,
24374
26474
  usageTokens: () => usageTokens
24375
26475
  });
24376
- import { existsSync as existsSync36 } from "fs";
26476
+ import { existsSync as existsSync42 } from "fs";
24377
26477
  function ensureDb() {
24378
- if (!existsSync36(DB_PATH)) {
26478
+ if (!existsSync42(DB_PATH)) {
24379
26479
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
24380
26480
  process.exit(1);
24381
26481
  }
@@ -24565,9 +26665,9 @@ __export(config_exports2, {
24565
26665
  configList: () => configList,
24566
26666
  configSet: () => configSet
24567
26667
  });
24568
- import { existsSync as existsSync37, readFileSync as readFileSync19 } from "fs";
26668
+ import { existsSync as existsSync43, readFileSync as readFileSync24 } from "fs";
24569
26669
  async function configList(globalOpts) {
24570
- if (!existsSync37(DB_PATH)) {
26670
+ if (!existsSync43(DB_PATH)) {
24571
26671
  outputError("DB_NOT_FOUND", "Database not found.");
24572
26672
  process.exit(1);
24573
26673
  }
@@ -24601,7 +26701,7 @@ async function configGet(globalOpts, key) {
24601
26701
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
24602
26702
  process.exit(1);
24603
26703
  }
24604
- if (!existsSync37(DB_PATH)) {
26704
+ if (!existsSync43(DB_PATH)) {
24605
26705
  outputError("DB_NOT_FOUND", "Database not found.");
24606
26706
  process.exit(1);
24607
26707
  }
@@ -24647,11 +26747,11 @@ async function configSet(globalOpts, key, value) {
24647
26747
  }
24648
26748
  }
24649
26749
  async function configEnv(_globalOpts) {
24650
- if (!existsSync37(ENV_PATH)) {
26750
+ if (!existsSync43(ENV_PATH)) {
24651
26751
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
24652
26752
  process.exit(1);
24653
26753
  }
24654
- const content = readFileSync19(ENV_PATH, "utf-8");
26754
+ const content = readFileSync24(ENV_PATH, "utf-8");
24655
26755
  const entries = {};
24656
26756
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
24657
26757
  for (const line of content.split("\n")) {
@@ -24701,9 +26801,9 @@ __export(session_exports, {
24701
26801
  sessionGet: () => sessionGet,
24702
26802
  sessionNew: () => sessionNew
24703
26803
  });
24704
- import { existsSync as existsSync38 } from "fs";
26804
+ import { existsSync as existsSync44 } from "fs";
24705
26805
  async function sessionGet(globalOpts) {
24706
- if (!existsSync38(DB_PATH)) {
26806
+ if (!existsSync44(DB_PATH)) {
24707
26807
  outputError("DB_NOT_FOUND", "Database not found.");
24708
26808
  process.exit(1);
24709
26809
  }
@@ -24764,9 +26864,9 @@ __export(permissions_exports, {
24764
26864
  verboseGet: () => verboseGet,
24765
26865
  verboseSet: () => verboseSet
24766
26866
  });
24767
- import { existsSync as existsSync39 } from "fs";
26867
+ import { existsSync as existsSync45 } from "fs";
24768
26868
  function ensureDb2() {
24769
- if (!existsSync39(DB_PATH)) {
26869
+ if (!existsSync45(DB_PATH)) {
24770
26870
  outputError("DB_NOT_FOUND", "Database not found.");
24771
26871
  process.exit(1);
24772
26872
  }
@@ -24913,9 +27013,9 @@ __export(cwd_exports, {
24913
27013
  cwdGet: () => cwdGet,
24914
27014
  cwdSet: () => cwdSet
24915
27015
  });
24916
- import { existsSync as existsSync40 } from "fs";
27016
+ import { existsSync as existsSync46 } from "fs";
24917
27017
  async function cwdGet(globalOpts) {
24918
- if (!existsSync40(DB_PATH)) {
27018
+ if (!existsSync46(DB_PATH)) {
24919
27019
  outputError("DB_NOT_FOUND", "Database not found.");
24920
27020
  process.exit(1);
24921
27021
  }
@@ -24977,9 +27077,9 @@ __export(voice_exports, {
24977
27077
  voiceGet: () => voiceGet,
24978
27078
  voiceSet: () => voiceSet
24979
27079
  });
24980
- import { existsSync as existsSync41 } from "fs";
27080
+ import { existsSync as existsSync47 } from "fs";
24981
27081
  async function voiceGet(globalOpts) {
24982
- if (!existsSync41(DB_PATH)) {
27082
+ if (!existsSync47(DB_PATH)) {
24983
27083
  outputError("DB_NOT_FOUND", "Database not found.");
24984
27084
  process.exit(1);
24985
27085
  }
@@ -25028,9 +27128,9 @@ __export(heartbeat_exports, {
25028
27128
  heartbeatGet: () => heartbeatGet,
25029
27129
  heartbeatSet: () => heartbeatSet
25030
27130
  });
25031
- import { existsSync as existsSync42 } from "fs";
27131
+ import { existsSync as existsSync48 } from "fs";
25032
27132
  async function heartbeatGet(globalOpts) {
25033
- if (!existsSync42(DB_PATH)) {
27133
+ if (!existsSync48(DB_PATH)) {
25034
27134
  outputError("DB_NOT_FOUND", "Database not found.");
25035
27135
  process.exit(1);
25036
27136
  }
@@ -25139,9 +27239,9 @@ __export(summarizer_exports, {
25139
27239
  summarizerGet: () => summarizerGet,
25140
27240
  summarizerSet: () => summarizerSet
25141
27241
  });
25142
- import { existsSync as existsSync43 } from "fs";
27242
+ import { existsSync as existsSync49 } from "fs";
25143
27243
  async function summarizerGet(globalOpts) {
25144
- if (!existsSync43(DB_PATH)) {
27244
+ if (!existsSync49(DB_PATH)) {
25145
27245
  outputError("DB_NOT_FOUND", "Database not found.");
25146
27246
  process.exit(1);
25147
27247
  }
@@ -25185,9 +27285,9 @@ __export(thinking_exports, {
25185
27285
  thinkingGet: () => thinkingGet,
25186
27286
  thinkingSet: () => thinkingSet
25187
27287
  });
25188
- import { existsSync as existsSync44 } from "fs";
27288
+ import { existsSync as existsSync50 } from "fs";
25189
27289
  async function thinkingGet(globalOpts) {
25190
- if (!existsSync44(DB_PATH)) {
27290
+ if (!existsSync50(DB_PATH)) {
25191
27291
  outputError("DB_NOT_FOUND", "Database not found.");
25192
27292
  process.exit(1);
25193
27293
  }
@@ -25231,9 +27331,9 @@ __export(chats_exports, {
25231
27331
  chatsList: () => chatsList,
25232
27332
  chatsRemoveAlias: () => chatsRemoveAlias
25233
27333
  });
25234
- import { existsSync as existsSync45 } from "fs";
27334
+ import { existsSync as existsSync51 } from "fs";
25235
27335
  async function chatsList(_globalOpts) {
25236
- if (!existsSync45(DB_PATH)) {
27336
+ if (!existsSync51(DB_PATH)) {
25237
27337
  outputError("DB_NOT_FOUND", "Database not found.");
25238
27338
  process.exit(1);
25239
27339
  }
@@ -25361,9 +27461,9 @@ var mcps_exports2 = {};
25361
27461
  __export(mcps_exports2, {
25362
27462
  mcpsList: () => mcpsList
25363
27463
  });
25364
- import { existsSync as existsSync46 } from "fs";
27464
+ import { existsSync as existsSync52 } from "fs";
25365
27465
  async function mcpsList(_globalOpts) {
25366
- if (!existsSync46(DB_PATH)) {
27466
+ if (!existsSync52(DB_PATH)) {
25367
27467
  outputError("DB_NOT_FOUND", "Database not found.");
25368
27468
  process.exit(1);
25369
27469
  }
@@ -25400,11 +27500,11 @@ __export(chat_exports2, {
25400
27500
  chatSend: () => chatSend
25401
27501
  });
25402
27502
  import { request as httpRequest2 } from "http";
25403
- import { readFileSync as readFileSync20, existsSync as existsSync47 } from "fs";
27503
+ import { readFileSync as readFileSync25, existsSync as existsSync53 } from "fs";
25404
27504
  function getToken2() {
25405
27505
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
25406
27506
  try {
25407
- if (existsSync47(TOKEN_PATH2)) return readFileSync20(TOKEN_PATH2, "utf-8").trim();
27507
+ if (existsSync53(TOKEN_PATH2)) return readFileSync25(TOKEN_PATH2, "utf-8").trim();
25408
27508
  } catch {
25409
27509
  }
25410
27510
  return null;
@@ -25541,7 +27641,7 @@ var tui_exports = {};
25541
27641
  __export(tui_exports, {
25542
27642
  tuiCommand: () => tuiCommand
25543
27643
  });
25544
- import { createInterface as createInterface7 } from "readline";
27644
+ import { createInterface as createInterface9 } from "readline";
25545
27645
  import pc2 from "picocolors";
25546
27646
  async function tuiCommand(globalOpts, cmdOpts) {
25547
27647
  const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
@@ -25551,7 +27651,7 @@ async function tuiCommand(globalOpts, cmdOpts) {
25551
27651
  }
25552
27652
  const chatId = resolveChatId(globalOpts);
25553
27653
  const { chatSend: chatSend2 } = await Promise.resolve().then(() => (init_chat2(), chat_exports2));
25554
- const rl2 = createInterface7({
27654
+ const rl2 = createInterface9({
25555
27655
  input: process.stdin,
25556
27656
  output: process.stdout,
25557
27657
  prompt: pc2.cyan("you > "),
@@ -25683,9 +27783,9 @@ var completion_exports = {};
25683
27783
  __export(completion_exports, {
25684
27784
  completionCommand: () => completionCommand
25685
27785
  });
25686
- import { writeFileSync as writeFileSync10, mkdirSync as mkdirSync16 } from "fs";
25687
- import { join as join28 } from "path";
25688
- import { homedir as homedir9 } from "os";
27786
+ import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync17 } from "fs";
27787
+ import { join as join33 } from "path";
27788
+ import { homedir as homedir11 } from "os";
25689
27789
  async function completionCommand(opts) {
25690
27790
  const shell = opts.shell ?? detectShell();
25691
27791
  let script;
@@ -25700,11 +27800,11 @@ async function completionCommand(opts) {
25700
27800
  process.exit(1);
25701
27801
  }
25702
27802
  if (opts.install) {
25703
- const dir = join28(homedir9(), ".config", "cc-claw", "completions");
25704
- mkdirSync16(dir, { recursive: true });
27803
+ const dir = join33(homedir11(), ".config", "cc-claw", "completions");
27804
+ mkdirSync17(dir, { recursive: true });
25705
27805
  const filename = shell === "zsh" ? "_cc-claw" : shell === "fish" ? "cc-claw.fish" : "cc-claw.bash";
25706
- const filepath = join28(dir, filename);
25707
- writeFileSync10(filepath, script, "utf-8");
27806
+ const filepath = join33(dir, filename);
27807
+ writeFileSync11(filepath, script, "utf-8");
25708
27808
  console.log(`\u2713 Completion script written to ${filepath}
25709
27809
  `);
25710
27810
  if (shell === "zsh") {
@@ -25874,9 +27974,9 @@ __export(evolve_exports2, {
25874
27974
  evolveStatus: () => evolveStatus,
25875
27975
  evolveUndo: () => evolveUndo
25876
27976
  });
25877
- import { existsSync as existsSync48 } from "fs";
27977
+ import { existsSync as existsSync54 } from "fs";
25878
27978
  function ensureDb3() {
25879
- if (!existsSync48(DB_PATH)) {
27979
+ if (!existsSync54(DB_PATH)) {
25880
27980
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
25881
27981
  process.exit(1);
25882
27982
  }
@@ -26290,12 +28390,70 @@ var init_evolve3 = __esm({
26290
28390
  }
26291
28391
  });
26292
28392
 
28393
+ // src/cli/commands/optimize.ts
28394
+ var optimize_exports = {};
28395
+ __export(optimize_exports, {
28396
+ optimizeSkills: () => optimizeSkills,
28397
+ optimizeStatus: () => optimizeStatus
28398
+ });
28399
+ async function optimizeStatus() {
28400
+ const { listCcClawSkills: listCcClawSkills2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
28401
+ const skills2 = listCcClawSkills2();
28402
+ output({
28403
+ status: "ok",
28404
+ description: "Run identity and skill audits via Telegram with /optimize",
28405
+ availableSkills: skills2.length,
28406
+ skillNames: skills2.map((s) => s.name)
28407
+ }, () => {
28408
+ const lines = [
28409
+ box("\u{1F527} CC-Claw Optimizer"),
28410
+ "",
28411
+ " Use /optimize in Telegram for the interactive audit experience.",
28412
+ "",
28413
+ divider("CLI subcommands"),
28414
+ kvLine("skills", "List available CC-Claw skills"),
28415
+ "",
28416
+ muted(` \u{1F4CA} ${skills2.length} skill(s) available for audit`),
28417
+ ""
28418
+ ];
28419
+ return lines.join("\n");
28420
+ });
28421
+ }
28422
+ async function optimizeSkills() {
28423
+ const { listCcClawSkills: listCcClawSkills2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
28424
+ const skills2 = listCcClawSkills2();
28425
+ output({
28426
+ status: "ok",
28427
+ skills: skills2.map((s) => ({ name: s.name, path: s.path, description: s.description })),
28428
+ count: skills2.length
28429
+ }, () => {
28430
+ if (skills2.length === 0) {
28431
+ return " No CC-Claw skills found in ~/.cc-claw/workspace/skills/";
28432
+ }
28433
+ const lines = [
28434
+ box("\u{1F9E9} CC-Claw Skills"),
28435
+ "",
28436
+ ...skills2.map((s) => kvLine(s.name, s.description)),
28437
+ "",
28438
+ muted(` ${skills2.length} skill(s) \u2014 use /optimize in Telegram to audit`),
28439
+ ""
28440
+ ];
28441
+ return lines.join("\n");
28442
+ });
28443
+ }
28444
+ var init_optimize2 = __esm({
28445
+ "src/cli/commands/optimize.ts"() {
28446
+ "use strict";
28447
+ init_format2();
28448
+ }
28449
+ });
28450
+
26293
28451
  // src/setup.ts
26294
28452
  var setup_exports = {};
26295
- import { existsSync as existsSync49, writeFileSync as writeFileSync11, readFileSync as readFileSync21, copyFileSync as copyFileSync4, mkdirSync as mkdirSync17, statSync as statSync10 } from "fs";
28453
+ import { existsSync as existsSync55, writeFileSync as writeFileSync12, readFileSync as readFileSync26, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
26296
28454
  import { execFileSync as execFileSync5 } from "child_process";
26297
- import { createInterface as createInterface8 } from "readline";
26298
- import { join as join29 } from "path";
28455
+ import { createInterface as createInterface10 } from "readline";
28456
+ import { join as join34 } from "path";
26299
28457
  function divider2() {
26300
28458
  console.log(dim("\u2500".repeat(55)));
26301
28459
  }
@@ -26370,22 +28528,22 @@ async function setup() {
26370
28528
  }
26371
28529
  console.log("");
26372
28530
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
26373
- if (!existsSync49(dir)) mkdirSync17(dir, { recursive: true });
28531
+ if (!existsSync55(dir)) mkdirSync18(dir, { recursive: true });
26374
28532
  }
26375
28533
  const env = {};
26376
- const envSource = existsSync49(ENV_PATH) ? ENV_PATH : existsSync49(".env") ? ".env" : null;
28534
+ const envSource = existsSync55(ENV_PATH) ? ENV_PATH : existsSync55(".env") ? ".env" : null;
26377
28535
  if (envSource) {
26378
28536
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
26379
28537
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
26380
- const existing = readFileSync21(envSource, "utf-8");
28538
+ const existing = readFileSync26(envSource, "utf-8");
26381
28539
  for (const line of existing.split("\n")) {
26382
28540
  const match = line.match(/^([^#=]+)=(.*)$/);
26383
28541
  if (match) env[match[1].trim()] = match[2].trim();
26384
28542
  }
26385
28543
  }
26386
- const cwdDb = join29(process.cwd(), "cc-claw.db");
26387
- if (existsSync49(cwdDb) && !existsSync49(DB_PATH)) {
26388
- const { size } = statSync10(cwdDb);
28544
+ const cwdDb = join34(process.cwd(), "cc-claw.db");
28545
+ if (existsSync55(cwdDb) && !existsSync55(DB_PATH)) {
28546
+ const { size } = statSync12(cwdDb);
26389
28547
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
26390
28548
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
26391
28549
  if (migrate) {
@@ -26601,7 +28759,7 @@ async function setup() {
26601
28759
  envLines.push("", "# Video Analysis", `GEMINI_API_KEY=${env.GEMINI_API_KEY}`);
26602
28760
  }
26603
28761
  const envContent = envLines.join("\n") + "\n";
26604
- writeFileSync11(ENV_PATH, envContent, { mode: 384 });
28762
+ writeFileSync12(ENV_PATH, envContent, { mode: 384 });
26605
28763
  console.log(green(` Config saved to ${ENV_PATH} (permissions: owner-only)`));
26606
28764
  header(6, TOTAL_STEPS, "Run on Startup (Daemon)");
26607
28765
  console.log(" CC-Claw can run automatically in the background, starting");
@@ -26657,7 +28815,7 @@ var init_setup = __esm({
26657
28815
  "src/setup.ts"() {
26658
28816
  "use strict";
26659
28817
  init_paths();
26660
- rl = createInterface8({ input: process.stdin, output: process.stdout });
28818
+ rl = createInterface10({ input: process.stdin, output: process.stdout });
26661
28819
  ask = (q) => new Promise((resolve) => rl.question(q, resolve));
26662
28820
  bold = (s) => `\x1B[1m${s}\x1B[0m`;
26663
28821
  green = (s) => `\x1B[32m${s}\x1B[0m`;
@@ -26726,6 +28884,19 @@ program.command("logs").description("Tail daemon logs").option("-f, --follow", "
26726
28884
  const { logsCommand: logsCommand2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
26727
28885
  await logsCommand2(opts);
26728
28886
  });
28887
+ var sessionLogs = program.command("session-logs").description("Manage session debug logs (enable via /debug in Telegram)");
28888
+ sessionLogs.command("list").description("List all session debug logs").action(async () => {
28889
+ const { sessionLogsList: sessionLogsList2 } = await Promise.resolve().then(() => (init_session_logs(), session_logs_exports));
28890
+ await sessionLogsList2(program.opts());
28891
+ });
28892
+ sessionLogs.command("tail").description("Tail latest (or specific) session log").option("-f, --follow", "Follow mode (like tail -f)").option("--file <name>", "Specific log file (partial match)").option("--lines <n>", "Number of lines", "50").action(async (opts) => {
28893
+ const { sessionLogsTail: sessionLogsTail2 } = await Promise.resolve().then(() => (init_session_logs(), session_logs_exports));
28894
+ await sessionLogsTail2(opts);
28895
+ });
28896
+ sessionLogs.command("clean").description("Remove old session logs").option("--days <n>", "Remove logs older than N days").action(async (opts) => {
28897
+ const { sessionLogsClean: sessionLogsClean2 } = await Promise.resolve().then(() => (init_session_logs(), session_logs_exports));
28898
+ await sessionLogsClean2(opts);
28899
+ });
26729
28900
  var gemini = program.command("gemini").description("Manage Gemini credential slots for rotation");
26730
28901
  gemini.command("list").description("Show all configured Gemini credential slots").action(async () => {
26731
28902
  const { geminiList: geminiList2 } = await Promise.resolve().then(() => (init_gemini2(), gemini_exports));
@@ -27182,6 +29353,15 @@ evolve.command("settings").description("View or update reflection settings").opt
27182
29353
  const { evolveSettings: evolveSettings2 } = await Promise.resolve().then(() => (init_evolve3(), evolve_exports2));
27183
29354
  await evolveSettings2(program.opts(), opts);
27184
29355
  });
29356
+ var optimize = program.command("optimize").description("Audit identity files and skills");
29357
+ optimize.action(async () => {
29358
+ const { optimizeStatus: optimizeStatus2 } = await Promise.resolve().then(() => (init_optimize2(), optimize_exports));
29359
+ await optimizeStatus2();
29360
+ });
29361
+ optimize.command("skills").description("List available CC-Claw skills").action(async () => {
29362
+ const { optimizeSkills: optimizeSkills2 } = await Promise.resolve().then(() => (init_optimize2(), optimize_exports));
29363
+ await optimizeSkills2();
29364
+ });
27185
29365
  program.command("start", { hidden: true }).description("Run the bot in the foreground (use 'service start' for background daemon)").action(async () => {
27186
29366
  await Promise.resolve().then(() => (init_index(), index_exports));
27187
29367
  });