cc-claw 0.20.7 → 0.20.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +983 -467
  2. package/package.json +1 -1
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.20.7" : (() => {
36
+ VERSION = true ? "0.20.8" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -1625,6 +1625,30 @@ function initSchema(db3) {
1625
1625
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
1626
1626
  );
1627
1627
  `);
1628
+ try {
1629
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN backend TEXT");
1630
+ } catch {
1631
+ }
1632
+ try {
1633
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN model TEXT");
1634
+ } catch {
1635
+ }
1636
+ try {
1637
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN fallbacks TEXT");
1638
+ } catch {
1639
+ }
1640
+ try {
1641
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN target TEXT");
1642
+ } catch {
1643
+ }
1644
+ try {
1645
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN channel TEXT NOT NULL DEFAULT 'telegram'");
1646
+ } catch {
1647
+ }
1648
+ try {
1649
+ db3.exec("ALTER TABLE chat_heartbeat ADD COLUMN thinking TEXT");
1650
+ } catch {
1651
+ }
1628
1652
  db3.exec(`
1629
1653
  CREATE TABLE IF NOT EXISTS cwd_bookmarks (
1630
1654
  chatId TEXT NOT NULL,
@@ -3440,36 +3464,89 @@ function getHeartbeatConfig(chatId) {
3440
3464
  return getDb().prepare(
3441
3465
  `SELECT chat_id as chatId, enabled, interval_ms as intervalMs,
3442
3466
  active_start as activeStart, active_end as activeEnd,
3443
- last_beat_at as lastBeatAt, next_beat_at as nextBeatAt
3467
+ last_beat_at as lastBeatAt, next_beat_at as nextBeatAt,
3468
+ backend, model, fallbacks, target, channel, thinking
3444
3469
  FROM chat_heartbeat WHERE chat_id = ?`
3445
3470
  ).get(chatId);
3446
3471
  }
3447
3472
  function setHeartbeatConfig(chatId, config2) {
3448
3473
  getDb().prepare(`
3449
- INSERT INTO chat_heartbeat (chat_id, enabled, interval_ms, active_start, active_end)
3450
- VALUES (?, ?, ?, ?, ?)
3451
- ON CONFLICT(chat_id) DO UPDATE SET
3452
- enabled = COALESCE(?, enabled),
3453
- interval_ms = COALESCE(?, interval_ms),
3454
- active_start = COALESCE(?, active_start),
3455
- active_end = COALESCE(?, active_end)
3456
- `).run(
3457
- chatId,
3458
- config2.enabled !== void 0 ? config2.enabled ? 1 : 0 : 0,
3459
- config2.intervalMs ?? 18e5,
3460
- config2.activeStart ?? "08:00",
3461
- config2.activeEnd ?? "22:00",
3462
- config2.enabled !== void 0 ? config2.enabled ? 1 : 0 : null,
3463
- config2.intervalMs ?? null,
3464
- config2.activeStart ?? null,
3465
- config2.activeEnd ?? null
3466
- );
3474
+ INSERT OR IGNORE INTO chat_heartbeat (chat_id) VALUES (?)
3475
+ `).run(chatId);
3476
+ const updates = [];
3477
+ const values = [];
3478
+ if (config2.enabled !== void 0) {
3479
+ updates.push("enabled = ?");
3480
+ values.push(config2.enabled ? 1 : 0);
3481
+ }
3482
+ if (config2.intervalMs !== void 0) {
3483
+ updates.push("interval_ms = ?");
3484
+ values.push(config2.intervalMs);
3485
+ }
3486
+ if (config2.activeStart !== void 0) {
3487
+ updates.push("active_start = ?");
3488
+ values.push(config2.activeStart);
3489
+ }
3490
+ if (config2.activeEnd !== void 0) {
3491
+ updates.push("active_end = ?");
3492
+ values.push(config2.activeEnd);
3493
+ }
3494
+ if ("backend" in config2) {
3495
+ updates.push("backend = ?");
3496
+ values.push(config2.backend ?? null);
3497
+ }
3498
+ if ("model" in config2) {
3499
+ updates.push("model = ?");
3500
+ values.push(config2.model ?? null);
3501
+ }
3502
+ if ("fallbacks" in config2) {
3503
+ updates.push("fallbacks = ?");
3504
+ values.push(config2.fallbacks ?? null);
3505
+ }
3506
+ if ("target" in config2) {
3507
+ updates.push("target = ?");
3508
+ values.push(config2.target ?? null);
3509
+ }
3510
+ if (config2.channel !== void 0) {
3511
+ updates.push("channel = ?");
3512
+ values.push(config2.channel);
3513
+ }
3514
+ if ("thinking" in config2) {
3515
+ updates.push("thinking = ?");
3516
+ values.push(config2.thinking ?? null);
3517
+ }
3518
+ if (updates.length > 0) {
3519
+ values.push(chatId);
3520
+ getDb().prepare(
3521
+ `UPDATE chat_heartbeat SET ${updates.join(", ")} WHERE chat_id = ?`
3522
+ ).run(...values);
3523
+ }
3524
+ }
3525
+ function updateHeartbeatField(chatId, field, value) {
3526
+ const allowedFields = ["backend", "model", "fallbacks", "target", "channel", "thinking", "enabled", "interval_ms", "active_start", "active_end"];
3527
+ if (!allowedFields.includes(field)) throw new Error(`Invalid heartbeat field: ${field}`);
3528
+ getDb().prepare(`
3529
+ INSERT OR IGNORE INTO chat_heartbeat (chat_id) VALUES (?)
3530
+ `).run(chatId);
3531
+ getDb().prepare(
3532
+ `UPDATE chat_heartbeat SET ${field} = ? WHERE chat_id = ?`
3533
+ ).run(value, chatId);
3467
3534
  }
3468
3535
  function updateHeartbeatTimestamps(chatId, lastBeat, nextBeat) {
3469
3536
  getDb().prepare(
3470
3537
  "UPDATE chat_heartbeat SET last_beat_at = ?, next_beat_at = ? WHERE chat_id = ?"
3471
3538
  ).run(lastBeat, nextBeat, chatId);
3472
3539
  }
3540
+ function parseHeartbeatFallbacks(fallbacksJson) {
3541
+ if (!fallbacksJson) return [];
3542
+ try {
3543
+ const parsed = JSON.parse(fallbacksJson);
3544
+ if (!Array.isArray(parsed)) return [];
3545
+ return parsed.filter((f) => f && typeof f.backend === "string");
3546
+ } catch {
3547
+ return [];
3548
+ }
3549
+ }
3473
3550
  function getActiveWatches(chatId) {
3474
3551
  return getDb().prepare(
3475
3552
  `SELECT id, chat_id as chatId, description, expires_at as expiresAt, created_at as createdAt
@@ -3940,6 +4017,7 @@ __export(store_exports5, {
3940
4017
  markSlotExhausted: () => markSlotExhausted,
3941
4018
  markSlotSuccess: () => markSlotSuccess,
3942
4019
  openDatabaseReadOnly: () => openDatabaseReadOnly,
4020
+ parseHeartbeatFallbacks: () => parseHeartbeatFallbacks,
3943
4021
  pinChatBackendSlot: () => pinChatBackendSlot,
3944
4022
  pinChatGeminiSlot: () => pinChatGeminiSlot,
3945
4023
  pruneJobRuns: () => pruneJobRuns,
@@ -3997,6 +4075,7 @@ __export(store_exports5, {
3997
4075
  toggleShowThinkingUi: () => toggleShowThinkingUi,
3998
4076
  toggleTool: () => toggleTool,
3999
4077
  touchBookmark: () => touchBookmark,
4078
+ updateHeartbeatField: () => updateHeartbeatField,
4000
4079
  updateHeartbeatTimestamps: () => updateHeartbeatTimestamps,
4001
4080
  updateJob: () => updateJob,
4002
4081
  updateJobEnabled: () => updateJobEnabled,
@@ -6120,8 +6199,8 @@ async function healthCheck(serverName) {
6120
6199
  }
6121
6200
  if (needsCatalogRefresh) {
6122
6201
  try {
6123
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
6124
- const adapter = getAdapter4("ollama");
6202
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
6203
+ const adapter = getAdapter5("ollama");
6125
6204
  if ("refreshModelCatalog" in adapter) {
6126
6205
  adapter.refreshModelCatalog();
6127
6206
  }
@@ -6867,21 +6946,33 @@ You are an expert user and operator of the CC-Claw architecture. Because you are
6867
6946
  - **Dry Run & Webhooks**: Jobs aren't just for Telegram messages. Using \`--delivery dry_run\`, you can test jobs internally (output is logged to the database but not spammed to chat). You can also route AI output directly to external APIs using \`--delivery webhook\`.
6868
6947
  - **Auditing**: Every cron execution and its output is permanently logged in the database's \`message_log\` for verifiable auditing.
6869
6948
 
6870
- ## 5. System Controls & Modes
6949
+ ## 5. Heartbeat & Watches (Proactive Monitoring)
6950
+ - **Heartbeat**: A periodic awareness cycle that checks system health and user-defined watches. Configured via \`/heartbeat\`.
6951
+ - **Active Hours**: Heartbeat only fires within configured active hours (default 08:00-22:00).
6952
+ - **Smart Suppression**: When nothing needs attention, the AI responds with \`HEARTBEAT_OK\` and no message is delivered to the user.
6953
+ - **Delivery Parity**: Like cron jobs, heartbeat supports custom backend, model, fallback chain, thinking level, and delivery target.
6954
+ - **Watches**: AI-executable awareness tasks added via \`/watch\`. These are instructions the heartbeat AI will check using its available tools.
6955
+ - \`/watch add Check my email for urgent messages\` \u2014 adds a persistent watch
6956
+ - \`/watch add Monitor Ollama server health for 24h\` \u2014 adds a time-limited watch
6957
+ - \`/watch list\` \u2014 shows all active watches
6958
+ - \`/watch remove <id>\` \u2014 removes a watch
6959
+ - **\u26A0\uFE0F HEARTBEAT MANAGEMENT**: Heartbeat and watch configuration MUST be done exclusively through \`/heartbeat\` and \`/watch\` slash commands. NEVER modify the \`chat_heartbeat\` or \`heartbeat_watches\` database tables directly.
6960
+
6961
+ ## 6. System Controls & Modes
6871
6962
  - **Permission Modes**: Command execution is gated. \`safe\` mode requires user approval for mutations, \`yolo\` auto-runs everything without asking, and \`plan\` is strictly read-only for drafting proposals.
6872
6963
  - **Global Settings**: The user controls your behavior system-wide using commands like \`/response_style\` (concise, normal, detailed) to enforce verbosity constraints, and \`/voice_config\` to select Text-to-Speech voices from premium providers like ElevenLabs, Grok (xAI), or local macOS.
6873
6964
  - **Telegram UX**: Most slash commands use interactive inline keyboards with colored buttons. \`/menu\` (or \`/m\`) is the home screen. \`/usage\` unifies cost, limits, and usage tracking. Inline mode (\`@botname query\`) searches memories from any chat.
6874
6965
 
6875
- ## 6. Integrations & MCP
6966
+ ## 7. Integrations & MCP
6876
6967
  - **Skills**: You can load specific workflows from the \`~/.cc-claw/workspace/skills/\` directory.
6877
6968
  - **Files**: You can send physical files to the user across Telegram by simply writing \`[SEND_FILE:/absolute/path/to/file]\` in your response.
6878
6969
  - **MCP Ecosystem**: You are deeply natively integrated with Model Context Protocol (MCP) servers (like Perplexity, NotebookLM, Context7) granting you immense external reach.
6879
6970
 
6880
- ## 7. CLI Reference
6971
+ ## 8. CLI Reference
6881
6972
  - Run \\\`cc-claw --ai\\\` for a comprehensive command reference covering all CLI and Telegram commands
6882
6973
  - Use \\\`cc-claw --ai --install\\\` to install the skill to all backend skill directories
6883
6974
 
6884
- ## 8. CRITICAL RULES \u2014 Memory & State
6975
+ ## 9. CRITICAL RULES \u2014 Memory & State
6885
6976
  - NEVER use native CLI memory tools (Gemini's save_memory, Claude's memory, etc.) \u2014 they are backend-local
6886
6977
  - NEVER modify the SQLite database directly (no INSERT/UPDATE/DELETE SQL)
6887
6978
  - ALWAYS use CC-Claw slash commands (/remember, /evolve, etc.) or the cc-claw CLI
@@ -9489,8 +9580,8 @@ async function spawnSubAgent(chatId, opts) {
9489
9580
  if (!runner) throw new Error(`Unknown runner: ${opts.runner}`);
9490
9581
  if (opts.model) {
9491
9582
  try {
9492
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9493
- const adapter = getAdapter4(opts.runner);
9583
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9584
+ const adapter = getAdapter5(opts.runner);
9494
9585
  if (adapter.availableModels && !adapter.availableModels[opts.model]) {
9495
9586
  const validModels = Object.keys(adapter.availableModels).join(", ");
9496
9587
  throw new Error(`Unknown model "${opts.model}" for ${opts.runner}. Available: ${validModels}`);
@@ -9871,8 +9962,8 @@ async function handleAgentComplete(agentId, chatId, resultText, usage2, mcpsAdde
9871
9962
  const runner = getRunner(agent.runnerId);
9872
9963
  if (runner) {
9873
9964
  try {
9874
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9875
- const adapter = getAdapter4(agent.runnerId);
9965
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
9966
+ const adapter = getAdapter5(agent.runnerId);
9876
9967
  const model2 = agent.model ?? adapter.defaultModel;
9877
9968
  const pricing = adapter.pricing[model2];
9878
9969
  if (pricing) {
@@ -10621,7 +10712,7 @@ var init_chat = __esm({
10621
10712
  }
10622
10713
  const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
10623
10714
  const { getMode: getMode3, getCwd: getCwd3, getModel: getModel3, addUsage: addUsage3, getBackend: getBackend3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
10624
- const { getAdapterForChat: getAdapterForChat2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
10715
+ const { getAdapterForChat: getAdapterForChat3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
10625
10716
  const chatId = body.chatId;
10626
10717
  const backend2 = body.backend;
10627
10718
  const PERM_LEVEL = { plan: 0, safe: 1, yolo: 2 };
@@ -10631,7 +10722,7 @@ var init_chat = __esm({
10631
10722
  const cwd = body.cwd ?? getCwd3(chatId);
10632
10723
  const model2 = body.model ?? getModel3(chatId) ?? (() => {
10633
10724
  try {
10634
- return getAdapterForChat2(chatId).defaultModel;
10725
+ return getAdapterForChat3(chatId).defaultModel;
10635
10726
  } catch {
10636
10727
  return void 0;
10637
10728
  }
@@ -15654,8 +15745,8 @@ async function handleAdd(chatId, channel, name, host, port) {
15654
15745
  }
15655
15746
  const models = await OllamaService.discoverModels(name);
15656
15747
  try {
15657
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15658
- const adapter = getAdapter4("ollama");
15748
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15749
+ const adapter = getAdapter5("ollama");
15659
15750
  if ("refreshModelCatalog" in adapter) {
15660
15751
  adapter.refreshModelCatalog();
15661
15752
  }
@@ -15708,8 +15799,8 @@ async function sendDiscover(chatId, channel, serverName) {
15708
15799
  const { OllamaService } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
15709
15800
  const models = await OllamaService.discoverModels(serverName);
15710
15801
  try {
15711
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15712
- const adapter = getAdapter4("ollama");
15802
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
15803
+ const adapter = getAdapter5("ollama");
15713
15804
  if ("refreshModelCatalog" in adapter) {
15714
15805
  adapter.refreshModelCatalog();
15715
15806
  }
@@ -17878,8 +17969,197 @@ var init_install = __esm({
17878
17969
  }
17879
17970
  });
17880
17971
 
17972
+ // src/health/checks.ts
17973
+ import { existsSync as existsSync18, statSync as statSync7, readFileSync as readFileSync9 } from "fs";
17974
+ import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
17975
+ function getRecentErrors() {
17976
+ if (!existsSync18(ERROR_LOG_PATH)) return null;
17977
+ const logContent = readFileSync9(ERROR_LOG_PATH, "utf-8");
17978
+ const allLines = logContent.split("\n").filter(Boolean).slice(-500);
17979
+ const last24h = Date.now() - 864e5;
17980
+ const lines = allLines.filter((line) => {
17981
+ const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:+-]+)/);
17982
+ if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
17983
+ return false;
17984
+ });
17985
+ if (lines.length === 0) return null;
17986
+ const classified = { rate429: [], contentSilence: [], spawnTimeout: [], other: [], total: 0 };
17987
+ for (const line of lines) {
17988
+ if (/429|rate.?limit/i.test(line)) classified.rate429.push(line);
17989
+ else if (/content silence/i.test(line)) classified.contentSilence.push(line);
17990
+ else if (/spawn timeout|timeout after \d+s/i.test(line)) classified.spawnTimeout.push(line);
17991
+ else classified.other.push(line);
17992
+ }
17993
+ classified.total = lines.length;
17994
+ return classified;
17995
+ }
17996
+ function checkDatabase() {
17997
+ const checks = [];
17998
+ if (existsSync18(DB_PATH)) {
17999
+ const size = statSync7(DB_PATH).size;
18000
+ checks.push({ name: "Database", status: "ok", message: `${(size / 1024).toFixed(0)}KB` });
18001
+ } else {
18002
+ checks.push({ name: "Database", status: "error", message: "Not found", fix: "cc-claw setup" });
18003
+ }
18004
+ return checks;
18005
+ }
18006
+ function checkDiskSpace() {
18007
+ try {
18008
+ const dfOutput = execSync4("df -k " + DATA_PATH, { encoding: "utf-8" });
18009
+ const lines = dfOutput.trim().split("\n");
18010
+ if (lines.length >= 2) {
18011
+ const parts = lines[1].split(/\s+/);
18012
+ const availKB = parseInt(parts[3], 10);
18013
+ if (availKB < 1e5) {
18014
+ return { name: "Disk space", status: "warning", message: `${(availKB / 1024).toFixed(0)}MB available` };
18015
+ }
18016
+ return { name: "Disk space", status: "ok", message: `${(availKB / 1024 / 1024).toFixed(1)}GB available` };
18017
+ }
18018
+ } catch {
18019
+ }
18020
+ return null;
18021
+ }
18022
+ function checkErrorLog() {
18023
+ const checks = [];
18024
+ const errors = getRecentErrors();
18025
+ if (!errors) {
18026
+ checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
18027
+ return checks;
18028
+ }
18029
+ if (errors.rate429.length > 10) {
18030
+ checks.push({ name: "Rate limits", status: "error", message: `${errors.rate429.length} rate-limit (429) errors in last 24h` });
18031
+ } else if (errors.rate429.length > 0) {
18032
+ checks.push({ name: "Rate limits", status: "warning", message: `${errors.rate429.length} rate-limit errors in last 24h` });
18033
+ }
18034
+ if (errors.contentSilence.length > 0) {
18035
+ checks.push({ name: "Content silence", status: "warning", message: `${errors.contentSilence.length} silence timeout(s) in last 24h` });
18036
+ }
18037
+ if (errors.spawnTimeout.length > 0) {
18038
+ checks.push({ name: "Spawn timeouts", status: "warning", message: `${errors.spawnTimeout.length} backend timeout(s) in last 24h` });
18039
+ }
18040
+ if (errors.other.length > 0) {
18041
+ checks.push({ name: "Other errors", status: "warning", message: `${errors.other.length} error(s) in last 24h` });
18042
+ }
18043
+ if (checks.length === 0) {
18044
+ checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
18045
+ }
18046
+ return checks;
18047
+ }
18048
+ function checkBackendCLIs() {
18049
+ const checks = [];
18050
+ const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
18051
+ let installed = 0;
18052
+ for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
18053
+ try {
18054
+ const path = execFileSync2("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
18055
+ if (path) {
18056
+ checks.push({ name: `${label2} CLI`, status: "ok", message: path });
18057
+ installed++;
18058
+ }
18059
+ } catch {
18060
+ }
18061
+ }
18062
+ if (installed === 0) {
18063
+ checks.push({ name: "Backend CLIs", status: "error", message: "No backend CLIs found" });
18064
+ }
18065
+ return checks;
18066
+ }
18067
+ function checkOllamaServers() {
18068
+ try {
18069
+ const { OllamaStore } = (init_ollama(), __toCommonJS(ollama_exports));
18070
+ const servers = OllamaStore.listServers();
18071
+ if (servers.length > 0) {
18072
+ const online = servers.filter((s) => s.status === "online");
18073
+ const models = OllamaStore.getAvailableModels();
18074
+ if (online.length === servers.length) {
18075
+ return { name: "Ollama", status: "ok", message: `${online.length} server(s) online, ${models.length} model(s)` };
18076
+ } else if (online.length > 0) {
18077
+ return { name: "Ollama", status: "warning", message: `${online.length}/${servers.length} server(s) online` };
18078
+ }
18079
+ return { name: "Ollama", status: "warning", message: `${servers.length} server(s) configured, all offline`, fix: "ollama serve" };
18080
+ }
18081
+ } catch {
18082
+ }
18083
+ return null;
18084
+ }
18085
+ function checkBackendLimitsAll() {
18086
+ const checks = [];
18087
+ try {
18088
+ const { getAllBackendIds: getAllBackendIds3 } = (init_backends(), __toCommonJS(backends_exports));
18089
+ const { checkBackendLimits: checkBackendLimits2 } = (init_store5(), __toCommonJS(store_exports5));
18090
+ for (const backend2 of getAllBackendIds3()) {
18091
+ const limitMsg = checkBackendLimits2(backend2);
18092
+ if (limitMsg) {
18093
+ checks.push({ name: `${backend2} limits`, status: "warning", message: limitMsg });
18094
+ }
18095
+ }
18096
+ } catch {
18097
+ }
18098
+ return checks;
18099
+ }
18100
+ function checkSchedulerHealth() {
18101
+ const checks = [];
18102
+ try {
18103
+ const { getHealthReport: getHealthReport2 } = (init_health2(), __toCommonJS(health_exports2));
18104
+ const report = getHealthReport2();
18105
+ if (report.failingJobs.length > 0) {
18106
+ for (const j of report.failingJobs) {
18107
+ checks.push({
18108
+ name: `Job #${j.id}`,
18109
+ status: report.failingJobs.length > 2 ? "error" : "warning",
18110
+ message: `"${j.description.slice(0, 40)}": ${j.failures} consecutive failures`
18111
+ });
18112
+ }
18113
+ }
18114
+ checks.push({
18115
+ name: "Scheduler",
18116
+ status: report.status === "healthy" ? "ok" : "warning",
18117
+ message: `${report.activeJobs} jobs active, ${report.failingJobs.length} failing`
18118
+ });
18119
+ } catch {
18120
+ }
18121
+ return checks;
18122
+ }
18123
+ function runAllHealthChecks() {
18124
+ const checks = [];
18125
+ checks.push(...checkDatabase());
18126
+ const disk = checkDiskSpace();
18127
+ if (disk) checks.push(disk);
18128
+ checks.push(...checkErrorLog());
18129
+ checks.push(...checkBackendLimitsAll());
18130
+ checks.push(...checkSchedulerHealth());
18131
+ const ollama2 = checkOllamaServers();
18132
+ if (ollama2) checks.push(ollama2);
18133
+ const errors = checks.filter((c) => c.status === "error").length;
18134
+ const warnings = checks.filter((c) => c.status === "warning").length;
18135
+ let statusLine;
18136
+ if (errors === 0 && warnings === 0) {
18137
+ statusLine = "\u2705 All systems OK";
18138
+ } else {
18139
+ const parts = [];
18140
+ if (errors > 0) parts.push(`${errors} error(s)`);
18141
+ if (warnings > 0) parts.push(`${warnings} warning(s)`);
18142
+ statusLine = `\u26A0\uFE0F ${parts.join(", ")}`;
18143
+ }
18144
+ return { checks, errors, warnings, statusLine };
18145
+ }
18146
+ function formatHealthForPrompt(summary) {
18147
+ const lines = [];
18148
+ for (const check of summary.checks) {
18149
+ const icon = check.status === "ok" ? "\u2705" : check.status === "warning" ? "\u26A0\uFE0F" : "\u274C";
18150
+ lines.push(`${icon} ${check.name}: ${check.message}`);
18151
+ }
18152
+ return lines.join("\n");
18153
+ }
18154
+ var init_checks = __esm({
18155
+ "src/health/checks.ts"() {
18156
+ "use strict";
18157
+ init_paths();
18158
+ }
18159
+ });
18160
+
17881
18161
  // src/bootstrap/heartbeat.ts
17882
- import { readFileSync as readFileSync9, existsSync as existsSync18 } from "fs";
18162
+ import { readFileSync as readFileSync10, existsSync as existsSync19 } from "fs";
17883
18163
  import { join as join19 } from "path";
17884
18164
  function initHeartbeat(channelReg) {
17885
18165
  registry2 = channelReg;
@@ -17921,6 +18201,14 @@ function stopAllHeartbeats() {
17921
18201
  function startAllHeartbeats() {
17922
18202
  try {
17923
18203
  const db3 = getDb();
18204
+ const allowedIds = (process.env.ALLOWED_CHAT_ID ?? "").split(",").map((s) => s.trim()).filter(Boolean);
18205
+ for (const chatId of allowedIds) {
18206
+ const existing = getHeartbeatConfig(chatId);
18207
+ if (!existing) {
18208
+ setHeartbeatConfig(chatId, { enabled: true });
18209
+ log(`[heartbeat] Enabled by default for chat ${chatId}`);
18210
+ }
18211
+ }
17924
18212
  const rows = db3.prepare(
17925
18213
  "SELECT chat_id FROM chat_heartbeat WHERE enabled = 1"
17926
18214
  ).all();
@@ -17933,6 +18221,11 @@ function startAllHeartbeats() {
17933
18221
  } catch {
17934
18222
  }
17935
18223
  }
18224
+ async function runHeartbeatNow(chatId) {
18225
+ const config2 = getHeartbeatConfig(chatId);
18226
+ if (!config2) return;
18227
+ await runHeartbeat(chatId, config2);
18228
+ }
17936
18229
  async function runHeartbeat(chatId, config2) {
17937
18230
  if (!isWithinActiveHours(config2.activeStart, config2.activeEnd)) {
17938
18231
  log(`[heartbeat] Skipping for ${chatId}: outside active hours (${config2.activeStart}-${config2.activeEnd})`);
@@ -17940,15 +18233,17 @@ async function runHeartbeat(chatId, config2) {
17940
18233
  }
17941
18234
  cleanExpiredWatches();
17942
18235
  const prompt = assembleHeartbeatPrompt(chatId);
18236
+ const resolvedBackend = resolveBackendId(chatId, config2);
18237
+ const resolvedModel = config2.model ?? void 0;
17943
18238
  try {
17944
- const response = await askAgent(chatId, prompt, { bootstrapTier: "heartbeat" });
18239
+ const response = await askAgent(chatId, prompt, {
18240
+ bootstrapTier: "heartbeat",
18241
+ backend: resolvedBackend,
18242
+ model: resolvedModel,
18243
+ timeoutMs: 12e4
18244
+ });
17945
18245
  if (response.usage) {
17946
- let heartbeatModel;
17947
- try {
17948
- heartbeatModel = getModel(chatId) ?? getAdapterForChat(chatId).defaultModel;
17949
- } catch {
17950
- heartbeatModel = getModel(chatId) ?? "unknown";
17951
- }
18246
+ const heartbeatModel = resolvedModel ?? getModel(chatId) ?? resolvedBackend;
17952
18247
  addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, heartbeatModel, void 0, response.usage.contextSize);
17953
18248
  }
17954
18249
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -17959,14 +18254,62 @@ async function runHeartbeat(chatId, config2) {
17959
18254
  log(`[heartbeat] ${chatId}: nothing to report`);
17960
18255
  return;
17961
18256
  }
17962
- const channel = registry2?.get("telegram");
17963
- if (channel) {
17964
- await channel.sendText(chatId, response.text);
17965
- }
18257
+ await deliverHeartbeatMessage(chatId, config2, response.text);
17966
18258
  } catch (err) {
17967
18259
  error(`[heartbeat] Error for ${chatId}: ${errorMessage(err)}`);
18260
+ const fallbacks = parseHeartbeatFallbacks(config2.fallbacks);
18261
+ if (fallbacks.length > 0) {
18262
+ for (const fb of fallbacks) {
18263
+ try {
18264
+ log(`[heartbeat] Trying fallback: ${fb.backend}${fb.model ? `:${fb.model}` : ""}`);
18265
+ const fbResponse = await askAgent(chatId, prompt, {
18266
+ bootstrapTier: "heartbeat",
18267
+ backend: fb.backend,
18268
+ model: fb.model,
18269
+ timeoutMs: 12e4
18270
+ });
18271
+ const now = (/* @__PURE__ */ new Date()).toISOString();
18272
+ const next = new Date(Date.now() + config2.intervalMs).toISOString();
18273
+ updateHeartbeatTimestamps(chatId, now, next);
18274
+ const trimmed = fbResponse.text.trim();
18275
+ if (trimmed === HEARTBEAT_OK || trimmed.startsWith(HEARTBEAT_OK)) {
18276
+ log(`[heartbeat] ${chatId}: nothing to report (via fallback ${fb.backend})`);
18277
+ return;
18278
+ }
18279
+ await deliverHeartbeatMessage(chatId, config2, fbResponse.text);
18280
+ return;
18281
+ } catch (fbErr) {
18282
+ error(`[heartbeat] Fallback ${fb.backend} failed: ${errorMessage(fbErr)}`);
18283
+ }
18284
+ }
18285
+ }
18286
+ }
18287
+ }
18288
+ async function deliverHeartbeatMessage(chatId, config2, text) {
18289
+ const channelName = config2.channel ?? "telegram";
18290
+ const channel = registry2?.get(channelName);
18291
+ if (!channel) return;
18292
+ const targetChatId = config2.target ?? chatId;
18293
+ if (channelName === "telegram") {
18294
+ const topicMatch = targetChatId.match(/^(.+):topic:(\d+)$/);
18295
+ if (topicMatch) {
18296
+ await channel.sendText(topicMatch[1], text, { threadId: parseInt(topicMatch[2], 10) });
18297
+ } else {
18298
+ await channel.sendText(targetChatId, text);
18299
+ }
18300
+ } else {
18301
+ await channel.sendText(targetChatId, text);
17968
18302
  }
17969
18303
  }
18304
+ function resolveBackendId(chatId, config2) {
18305
+ if (config2.backend) {
18306
+ const available = getAvailableBackendIds();
18307
+ if (available.includes(config2.backend)) {
18308
+ return config2.backend;
18309
+ }
18310
+ }
18311
+ return void 0;
18312
+ }
17970
18313
  function isWithinActiveHours(start, end) {
17971
18314
  try {
17972
18315
  const now = /* @__PURE__ */ new Date();
@@ -17985,55 +18328,25 @@ function isWithinActiveHours(start, end) {
17985
18328
  }
17986
18329
  function assembleHeartbeatPrompt(chatId) {
17987
18330
  const sections = [];
17988
- sections.push("You are running a periodic heartbeat check. Review the following and decide if anything needs the user's attention. If nothing is noteworthy, respond with exactly HEARTBEAT_OK and nothing else.");
17989
- const healthLines = [];
17990
- const report = getHealthReport();
17991
- if (report.failingJobs.length > 0) {
17992
- healthLines.push("Scheduler issues:");
17993
- for (const j of report.failingJobs) {
17994
- healthLines.push(` - Job #${j.id} "${j.description}": ${j.failures} consecutive failures`);
17995
- }
17996
- }
17997
- const recentRuns = getJobRuns(void 0, 20);
17998
- const failedSinceLastBeat = recentRuns.filter((r) => r.status === "failed");
17999
- if (failedSinceLastBeat.length > 0) {
18000
- healthLines.push(`${failedSinceLastBeat.length} job run(s) failed recently.`);
18001
- }
18002
- for (const backend2 of getAllBackendIds()) {
18003
- const limitMsg = checkBackendLimits(backend2);
18004
- if (limitMsg) healthLines.push(limitMsg);
18005
- }
18006
- try {
18007
- const { OllamaService } = (init_ollama(), __toCommonJS(ollama_exports));
18008
- const servers = OllamaService.listServers();
18009
- if (servers.length > 0) {
18010
- const offline = servers.filter((s) => s.status === "offline");
18011
- if (offline.length > 0) {
18012
- healthLines.push(`Ollama: ${offline.length}/${servers.length} server(s) offline: ${offline.map((s) => s.name).join(", ")}`);
18013
- }
18014
- }
18015
- } catch {
18016
- }
18017
- if (healthLines.length > 0) {
18018
- sections.push(`[System health]
18019
- ${healthLines.join("\n")}`);
18020
- } else {
18021
- sections.push("[System health]\nAll systems normal. No failures or limit warnings.");
18022
- }
18331
+ sections.push("You are running a periodic heartbeat check. Review the following system health data and active watches. If nothing needs the user's attention, respond with exactly HEARTBEAT_OK and nothing else. Only alert on genuine issues \u2014 do NOT report that everything is fine.");
18332
+ const healthSummary = runAllHealthChecks();
18333
+ const healthText = formatHealthForPrompt(healthSummary);
18334
+ sections.push(`[System Health Report]
18335
+ ${healthText}`);
18023
18336
  const watches = getActiveWatches(chatId);
18024
18337
  if (watches.length > 0) {
18025
- const watchLines = watches.map((w) => {
18338
+ const watchLines = watches.map((w, i) => {
18026
18339
  const expiry = w.expiresAt ? ` (expires: ${w.expiresAt})` : "";
18027
- return ` - ${w.description}${expiry}`;
18340
+ return `${i + 1}. ${w.description}${expiry}`;
18028
18341
  });
18029
- sections.push(`[Active watches]
18342
+ sections.push(`[Active Watches \u2014 execute these checks using your tools]
18030
18343
  ${watchLines.join("\n")}`);
18031
18344
  }
18032
- if (existsSync18(HEARTBEAT_MD_PATH)) {
18345
+ if (existsSync19(HEARTBEAT_MD_PATH)) {
18033
18346
  try {
18034
- const custom = readFileSync9(HEARTBEAT_MD_PATH, "utf-8").trim();
18347
+ const custom = readFileSync10(HEARTBEAT_MD_PATH, "utf-8").trim();
18035
18348
  if (custom) {
18036
- sections.push(`[Custom checks]
18349
+ sections.push(`[Custom checks from HEARTBEAT.md]
18037
18350
  ${custom}`);
18038
18351
  }
18039
18352
  } catch {
@@ -18054,6 +18367,16 @@ function formatHeartbeatStatus(chatId) {
18054
18367
  `Last beat: ${config2.lastBeatAt ?? "never"}`,
18055
18368
  `Next beat: ${config2.nextBeatAt ?? "N/A"}`
18056
18369
  ];
18370
+ if (config2.backend || config2.model) {
18371
+ lines.push(`Backend: ${config2.backend ?? "default"} | Model: ${config2.model ?? "default"}`);
18372
+ }
18373
+ if (config2.target) {
18374
+ lines.push(`Delivery: ${config2.target}`);
18375
+ }
18376
+ const fallbacks = parseHeartbeatFallbacks(config2.fallbacks);
18377
+ if (fallbacks.length > 0) {
18378
+ lines.push(`Fallbacks: ${fallbacks.map((f) => `${f.backend}${f.model ? `:${f.model}` : ""}`).join(", ")}`);
18379
+ }
18057
18380
  if (watches.length > 0) {
18058
18381
  lines.push("", `Active watches (${watches.length}):`);
18059
18382
  for (const w of watches) {
@@ -18075,7 +18398,7 @@ var init_heartbeat2 = __esm({
18075
18398
  init_agent();
18076
18399
  init_store5();
18077
18400
  init_backends();
18078
- init_health2();
18401
+ init_checks();
18079
18402
  init_log();
18080
18403
  HEARTBEAT_MD_PATH = join19(WORKSPACE_PATH, "HEARTBEAT.md");
18081
18404
  HEARTBEAT_OK = "HEARTBEAT_OK";
@@ -18731,13 +19054,18 @@ async function sendMemoryPage(chatId, channel, page, messageId) {
18731
19054
  buttons.push(footerRow);
18732
19055
  await sendOrEditKeyboard(chatId, channel, messageId, lines.join("\n"), buttons);
18733
19056
  }
18734
- async function sendHeartbeatKeyboard(chatId, channel) {
19057
+ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
18735
19058
  const config2 = getHeartbeatConfig(chatId);
18736
19059
  const enabled = config2?.enabled === 1;
18737
19060
  const intervalMs = config2?.intervalMs ?? 18e5;
18738
19061
  const intervalMin = intervalMs / 6e4;
18739
19062
  const activeStart = config2?.activeStart ?? "08:00";
18740
19063
  const activeEnd = config2?.activeEnd ?? "22:00";
19064
+ const watches = getActiveWatches(chatId);
19065
+ const fallbacks = parseHeartbeatFallbacks(config2?.fallbacks ?? null);
19066
+ const backendDisplay = config2?.backend ? capitalize(config2.backend) : "Default";
19067
+ const modelDisplay = config2?.model ?? "default";
19068
+ const thinkingDisplay = config2?.thinking ?? "off";
18741
19069
  const lines = [
18742
19070
  buildSectionHeader("Heartbeat"),
18743
19071
  "CC-Claw periodically wakes up to check on",
@@ -18746,22 +19074,86 @@ async function sendHeartbeatKeyboard(chatId, channel) {
18746
19074
  "",
18747
19075
  `Status: ${enabled ? "ON" : "OFF"}`,
18748
19076
  `Interval: every ${intervalMin} min`,
18749
- `Active hours: ${activeStart}-${activeEnd}`
19077
+ `Active hours: ${activeStart}-${activeEnd}`,
19078
+ `Backend: ${backendDisplay} | Model: ${modelDisplay}`
19079
+ ];
19080
+ if (fallbacks.length > 0) {
19081
+ lines.push(`Fallbacks: ${fallbacks.map((f) => `${capitalize(f.backend)}${f.model ? `:${f.model}` : ""}`).join(", ")}`);
19082
+ }
19083
+ if (config2?.target) {
19084
+ lines.push(`Target: ${config2.target}`);
19085
+ }
19086
+ if (watches.length > 0) {
19087
+ lines.push(`Active watches: ${watches.length}`);
19088
+ }
19089
+ const lastBeat = config2?.lastBeatAt ?? "never";
19090
+ lines.push(`Last beat: ${lastBeat}`);
19091
+ const buttons = [];
19092
+ const toggleRow = [
19093
+ { label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
19094
+ { label: `${!enabled ? "\u2713 " : ""}Off`, data: "hb:off", ...!enabled ? { style: "danger" } : {} },
19095
+ { label: "\u25B6 Run Now", data: "hb:run", style: "primary" }
18750
19096
  ];
19097
+ buttons.push(toggleRow);
18751
19098
  const presets = [15, 30, 60, 120];
18752
19099
  const intervalRow = presets.map((m) => ({
18753
19100
  label: `${m === intervalMin ? "\u2713 " : ""}${m} min`,
18754
19101
  data: `hb:interval:${m}`,
18755
19102
  ...m === intervalMin ? { style: "primary" } : {}
18756
19103
  }));
18757
- await channel.sendKeyboard(chatId, lines.join("\n"), [
18758
- [
18759
- { label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
18760
- { label: `${!enabled ? "\u2713 " : ""}Off`, data: "hb:off", ...!enabled ? { style: "danger" } : {} }
18761
- ],
18762
- intervalRow,
18763
- [{ label: "Active Hours: /heartbeat hours <start>-<end>", data: "hb:noop" }]
18764
- ]);
19104
+ buttons.push(intervalRow);
19105
+ const available = getAvailableBackendIds();
19106
+ const backendRow = available.map((bid) => ({
19107
+ label: `${config2?.backend === bid ? "\u2713 " : ""}${capitalize(bid)}`,
19108
+ data: `hb:backend:${bid}`,
19109
+ ...config2?.backend === bid ? { style: "primary" } : {}
19110
+ }));
19111
+ if (config2?.backend) {
19112
+ backendRow.push({ label: "Reset", data: "hb:backend:default" });
19113
+ }
19114
+ buttons.push(backendRow);
19115
+ const targetBackend = config2?.backend ?? getBackend(chatId) ?? "claude";
19116
+ try {
19117
+ const adapter = getAdapter(targetBackend);
19118
+ const models = Object.entries(adapter.availableModels);
19119
+ if (models.length > 0) {
19120
+ const modelRow = models.slice(0, 4).map(([key, info]) => ({
19121
+ label: `${config2?.model === key ? "\u2713 " : ""}${info.label}`,
19122
+ data: `hb:model:${key}`,
19123
+ ...config2?.model === key ? { style: "primary" } : {}
19124
+ }));
19125
+ if (config2?.model) {
19126
+ modelRow.push({ label: "Reset", data: "hb:model:default" });
19127
+ }
19128
+ buttons.push(modelRow);
19129
+ }
19130
+ } catch {
19131
+ }
19132
+ if (fallbacks.length > 0) {
19133
+ const fbRow = [
19134
+ { label: `\u{1F504} Fallbacks: ${fallbacks.map((f) => capitalize(f.backend)).join(" \u2192 ")}`, data: "hb:noop" },
19135
+ { label: "Clear", data: "hb:fb:clear", style: "danger" }
19136
+ ];
19137
+ buttons.push(fbRow);
19138
+ }
19139
+ const fbAddRow = available.filter((bid) => !fallbacks.some((f) => f.backend === bid) && bid !== config2?.backend).slice(0, 4).map((bid) => ({
19140
+ label: `+ ${capitalize(bid)}`,
19141
+ data: `hb:fb:add:${bid}`
19142
+ }));
19143
+ if (fbAddRow.length > 0) {
19144
+ buttons.push(fbAddRow);
19145
+ }
19146
+ const configRow = [
19147
+ { label: `\u{1F4AD} Thinking: ${thinkingDisplay}`, data: "hb:thinking" }
19148
+ ];
19149
+ if (watches.length > 0) {
19150
+ configRow.push({ label: `\u{1F441} Watches (${watches.length})`, data: "hb:watches" });
19151
+ } else {
19152
+ configRow.push({ label: "+ Add Watch", data: "hb:addwatch" });
19153
+ }
19154
+ buttons.push(configRow);
19155
+ buttons.push([{ label: `Active Hours: ${activeStart}-${activeEnd} (use /heartbeat hours)`, data: "hb:noop" }]);
19156
+ await sendOrEditKeyboard(chatId, channel, messageId, lines.join("\n"), buttons);
18765
19157
  }
18766
19158
  async function sendForgetPicker(chatId, channel, page, messageId) {
18767
19159
  const memories = listMemories();
@@ -19403,13 +19795,13 @@ async function handleEvolveCallback(chatId, data, channel) {
19403
19795
  const { getReflectionStatus: getReflectionStatus2, setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
19404
19796
  const current = getReflectionStatus2(getDb(), chatId);
19405
19797
  if (current === "frozen") {
19406
- const { readFileSync: readFileSync28, existsSync: existsSync56 } = await import("fs");
19798
+ const { readFileSync: readFileSync29, existsSync: existsSync57 } = await import("fs");
19407
19799
  const { join: join36 } = await import("path");
19408
19800
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
19409
19801
  const soulPath = join36(CC_CLAW_HOME3, "identity/SOUL.md");
19410
19802
  const userPath = join36(CC_CLAW_HOME3, "identity/USER.md");
19411
- const soul = existsSync56(soulPath) ? readFileSync28(soulPath, "utf-8") : "";
19412
- const user = existsSync56(userPath) ? readFileSync28(userPath, "utf-8") : "";
19803
+ const soul = existsSync57(soulPath) ? readFileSync29(soulPath, "utf-8") : "";
19804
+ const user = existsSync57(userPath) ? readFileSync29(userPath, "utf-8") : "";
19413
19805
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
19414
19806
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
19415
19807
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -19486,18 +19878,18 @@ var init_evolve2 = __esm({
19486
19878
  });
19487
19879
 
19488
19880
  // src/optimizer/identity-audit.ts
19489
- import { readFileSync as readFileSync10, existsSync as existsSync19, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
19881
+ import { readFileSync as readFileSync11, existsSync as existsSync20, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
19490
19882
  import { join as join21 } from "path";
19491
19883
  function readIdentityFile2(filename) {
19492
19884
  try {
19493
- return readFileSync10(join21(IDENTITY_PATH, filename), "utf-8");
19885
+ return readFileSync11(join21(IDENTITY_PATH, filename), "utf-8");
19494
19886
  } catch {
19495
19887
  return "";
19496
19888
  }
19497
19889
  }
19498
19890
  function getMtime(filepath) {
19499
19891
  try {
19500
- return statSync7(filepath).mtime.toISOString();
19892
+ return statSync8(filepath).mtime.toISOString();
19501
19893
  } catch {
19502
19894
  return "unknown";
19503
19895
  }
@@ -19506,7 +19898,7 @@ function findBackupFiles() {
19506
19898
  const backups = [];
19507
19899
  const dirs = [IDENTITY_PATH];
19508
19900
  const contextDir = join21(IDENTITY_PATH, "..", "workspace", "context");
19509
- if (existsSync19(contextDir)) dirs.push(contextDir);
19901
+ if (existsSync20(contextDir)) dirs.push(contextDir);
19510
19902
  for (const dir of dirs) {
19511
19903
  try {
19512
19904
  for (const entry of readdirSync10(dir)) {
@@ -19643,7 +20035,7 @@ var init_identity_audit = __esm({
19643
20035
  });
19644
20036
 
19645
20037
  // src/optimizer/skill-audit.ts
19646
- import { readFileSync as readFileSync11, existsSync as existsSync20 } from "fs";
20038
+ import { readFileSync as readFileSync12, existsSync as existsSync21 } from "fs";
19647
20039
  import { join as join22, basename as basename3 } from "path";
19648
20040
  function parseFrontmatter3(content) {
19649
20041
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
@@ -19684,7 +20076,7 @@ function detectDependentSkills(content) {
19684
20076
  return Array.from(deps);
19685
20077
  }
19686
20078
  function computeSkillStats(skillPath) {
19687
- const content = readFileSync11(skillPath, "utf-8");
20079
+ const content = readFileSync12(skillPath, "utf-8");
19688
20080
  const lines = content.split("\n");
19689
20081
  return {
19690
20082
  skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join22(skillPath, "..")) : basename3(skillPath, ".md"),
@@ -19711,9 +20103,9 @@ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
19711
20103
  join22(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
19712
20104
  ];
19713
20105
  for (const candidate of candidates) {
19714
- if (existsSync20(candidate)) {
20106
+ if (existsSync21(candidate)) {
19715
20107
  try {
19716
- const content = readFileSync11(candidate, "utf-8");
20108
+ const content = readFileSync12(candidate, "utf-8");
19717
20109
  results.push({
19718
20110
  name,
19719
20111
  content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
@@ -19828,7 +20220,7 @@ __export(analyze_exports2, {
19828
20220
  });
19829
20221
  import { spawn as spawn7 } from "child_process";
19830
20222
  import { createInterface as createInterface7 } from "readline";
19831
- import { readFileSync as readFileSync12, existsSync as existsSync21, readdirSync as readdirSync12 } from "fs";
20223
+ import { readFileSync as readFileSync13, existsSync as existsSync22, readdirSync as readdirSync12 } from "fs";
19832
20224
  import { join as join23 } from "path";
19833
20225
  import { homedir as homedir7 } from "os";
19834
20226
  function parseOptimizeOutput(raw, validAreas) {
@@ -19959,7 +20351,7 @@ function getModelDisplayInfo(chatId) {
19959
20351
  }
19960
20352
  function readIdentityFile3(filename) {
19961
20353
  try {
19962
- return readFileSync12(join23(IDENTITY_PATH, filename), "utf-8");
20354
+ return readFileSync13(join23(IDENTITY_PATH, filename), "utf-8");
19963
20355
  } catch {
19964
20356
  return "";
19965
20357
  }
@@ -19967,12 +20359,12 @@ function readIdentityFile3(filename) {
19967
20359
  function loadContextFiles2() {
19968
20360
  const contextDir = join23(homedir7(), ".cc-claw", "workspace", "context");
19969
20361
  const results = [];
19970
- if (!existsSync21(contextDir)) return results;
20362
+ if (!existsSync22(contextDir)) return results;
19971
20363
  try {
19972
20364
  for (const entry of readdirSync12(contextDir)) {
19973
20365
  if (!entry.endsWith(".md")) continue;
19974
20366
  try {
19975
- const content = readFileSync12(join23(contextDir, entry), "utf-8");
20367
+ const content = readFileSync13(join23(contextDir, entry), "utf-8");
19976
20368
  results.push({ name: entry, content });
19977
20369
  } catch {
19978
20370
  }
@@ -20024,7 +20416,7 @@ async function runSkillAudit(chatId, skillPath) {
20024
20416
  log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
20025
20417
  const soulMd = readIdentityFile3("SOUL.md");
20026
20418
  const ccClawSkillsDir = join23(homedir7(), ".cc-claw", "workspace", "skills");
20027
- const skillContent = readFileSync12(skillPath, "utf-8");
20419
+ const skillContent = readFileSync13(skillPath, "utf-8");
20028
20420
  const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
20029
20421
  const raw = await spawnAnalysis2(adapter, model2, prompt);
20030
20422
  const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
@@ -20040,14 +20432,14 @@ async function runSkillAudit(chatId, skillPath) {
20040
20432
  function listCcClawSkills() {
20041
20433
  const skillsDir = join23(homedir7(), ".cc-claw", "workspace", "skills");
20042
20434
  const entries = [];
20043
- if (!existsSync21(skillsDir)) return entries;
20435
+ if (!existsSync22(skillsDir)) return entries;
20044
20436
  try {
20045
20437
  for (const dir of readdirSync12(skillsDir)) {
20046
20438
  const skillFile = join23(skillsDir, dir, "SKILL.md");
20047
- if (!existsSync21(skillFile)) continue;
20439
+ if (!existsSync22(skillFile)) continue;
20048
20440
  let description = "skill";
20049
20441
  try {
20050
- const content = readFileSync12(skillFile, "utf-8");
20442
+ const content = readFileSync13(skillFile, "utf-8");
20051
20443
  const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
20052
20444
  if (descMatch) description = descMatch[1].trim().slice(0, 60);
20053
20445
  } catch {
@@ -20367,7 +20759,7 @@ var init_ui2 = __esm({
20367
20759
  });
20368
20760
 
20369
20761
  // src/router/optimize.ts
20370
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7, existsSync as existsSync22, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
20762
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync7, existsSync as existsSync23, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
20371
20763
  import { join as join24, dirname as dirname4 } from "path";
20372
20764
  import { homedir as homedir8 } from "os";
20373
20765
  async function handleOptimizeCommand(chatId, channel, _args) {
@@ -20638,13 +21030,13 @@ async function applyFinding(chatId, channel, index) {
20638
21030
  await showFinding(chatId, channel, index + 1);
20639
21031
  return;
20640
21032
  }
20641
- if (!existsSync22(targetPath)) {
21033
+ if (!existsSync23(targetPath)) {
20642
21034
  await channel.sendText(chatId, `Target file not found: ${targetPath}`, { parseMode: "plain" });
20643
21035
  session2.skipped.push(index);
20644
21036
  await showFinding(chatId, channel, index + 1);
20645
21037
  return;
20646
21038
  }
20647
- const original = readFileSync13(targetPath, "utf-8");
21039
+ const original = readFileSync14(targetPath, "utf-8");
20648
21040
  const backupPath = targetPath + `.bak.${Date.now()}`;
20649
21041
  writeFileSync7(backupPath, original, "utf-8");
20650
21042
  pruneBackups2(targetPath);
@@ -22082,6 +22474,92 @@ async function handleHeartbeatCommand(chatId, commandArgs, msg, channel) {
22082
22474
  await channel.sendText(chatId, formatHeartbeatStatus(chatId), { parseMode: "plain" });
22083
22475
  }
22084
22476
  }
22477
+ async function handleWatchCommand(chatId, commandArgs, msg, channel) {
22478
+ if (!commandArgs) {
22479
+ const watches = getActiveWatches(chatId);
22480
+ if (watches.length === 0) {
22481
+ await channel.sendText(chatId, [
22482
+ "No active watches.",
22483
+ "",
22484
+ "Watches are awareness tasks that the heartbeat",
22485
+ "AI checks periodically using its tools.",
22486
+ "",
22487
+ "Usage:",
22488
+ " /watch add Check my email for urgent messages",
22489
+ " /watch add Monitor Ollama server health",
22490
+ " /watch add Check calendar for upcoming meetings",
22491
+ " /watch list",
22492
+ " /watch remove <id>"
22493
+ ].join("\n"), { parseMode: "plain" });
22494
+ } else {
22495
+ const lines = ["Active watches:", ""];
22496
+ for (const w of watches) {
22497
+ const expiry = w.expiresAt ? ` (until ${formatLocalDateTime(w.expiresAt)})` : "";
22498
+ lines.push(`#${w.id}: ${w.description}${expiry}`);
22499
+ }
22500
+ lines.push("", "Use /watch remove <id> to remove a watch.");
22501
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
22502
+ }
22503
+ return;
22504
+ }
22505
+ const parts = commandArgs.trim().split(/\s+/);
22506
+ const action = parts[0].toLowerCase();
22507
+ const rest = parts.slice(1).join(" ");
22508
+ switch (action) {
22509
+ case "add": {
22510
+ if (!rest) {
22511
+ await channel.sendText(chatId, "Usage: /watch add <description>\n\nExamples:\n /watch add Check my email for urgent messages\n /watch add Monitor if Ollama is running\n /watch add Check calendar for meetings in the next hour", { parseMode: "plain" });
22512
+ return;
22513
+ }
22514
+ let expiresAt;
22515
+ const durationMatch = rest.match(/\s+for\s+(\d+)([hdw])$/i);
22516
+ let description = rest;
22517
+ if (durationMatch) {
22518
+ description = rest.slice(0, durationMatch.index).trim();
22519
+ const amount = parseInt(durationMatch[1], 10);
22520
+ const unit = durationMatch[2].toLowerCase();
22521
+ const ms = unit === "h" ? amount * 36e5 : unit === "d" ? amount * 864e5 : amount * 6048e5;
22522
+ expiresAt = new Date(Date.now() + ms).toISOString();
22523
+ }
22524
+ const id = addHeartbeatWatch(chatId, description, expiresAt);
22525
+ const expiryMsg = expiresAt ? ` (expires: ${formatLocalDateTime(expiresAt)})` : "";
22526
+ await channel.sendText(chatId, `\u2705 Watch #${id} added${expiryMsg}:
22527
+ ${description}`, { parseMode: "plain" });
22528
+ return;
22529
+ }
22530
+ case "remove":
22531
+ case "delete": {
22532
+ const id = parseInt(rest, 10);
22533
+ if (isNaN(id)) {
22534
+ await channel.sendText(chatId, "Usage: /watch remove <id>", { parseMode: "plain" });
22535
+ return;
22536
+ }
22537
+ const removed = removeHeartbeatWatch(id);
22538
+ await channel.sendText(chatId, removed ? `\u2705 Watch #${id} removed.` : `Watch #${id} not found.`, { parseMode: "plain" });
22539
+ return;
22540
+ }
22541
+ case "list": {
22542
+ const watches = getActiveWatches(chatId);
22543
+ if (watches.length === 0) {
22544
+ await channel.sendText(chatId, "No active watches.", { parseMode: "plain" });
22545
+ } else {
22546
+ const lines = ["Active watches:", ""];
22547
+ for (const w of watches) {
22548
+ const expiry = w.expiresAt ? ` (until ${formatLocalDateTime(w.expiresAt)})` : "";
22549
+ lines.push(`#${w.id}: ${w.description}${expiry}`);
22550
+ }
22551
+ await channel.sendText(chatId, lines.join("\n"), { parseMode: "plain" });
22552
+ }
22553
+ return;
22554
+ }
22555
+ default:
22556
+ const watchDesc = commandArgs.trim();
22557
+ const watchId = addHeartbeatWatch(chatId, watchDesc);
22558
+ await channel.sendText(chatId, `\u2705 Watch #${watchId} added:
22559
+ ${watchDesc}`, { parseMode: "plain" });
22560
+ return;
22561
+ }
22562
+ }
22085
22563
  async function handleAgentsCommand(chatId, commandArgs, msg, channel) {
22086
22564
  if (commandArgs?.startsWith("mode")) {
22087
22565
  const modeArg = commandArgs.slice(5).trim().toLowerCase();
@@ -22644,6 +23122,9 @@ async function handleCommand(msg, channel) {
22644
23122
  case "heartbeat":
22645
23123
  await handleHeartbeatCommand(chatId, commandArgs, msg, channel);
22646
23124
  break;
23125
+ case "watch":
23126
+ await handleWatchCommand(chatId, commandArgs, msg, channel);
23127
+ break;
22647
23128
  case "agents":
22648
23129
  await handleAgentsCommand(chatId, commandArgs, msg, channel);
22649
23130
  break;
@@ -23542,8 +24023,8 @@ ${rotationNote}`, { parseMode: "html" });
23542
24023
  if (action === "toggle") {
23543
24024
  const backend2 = parts[2];
23544
24025
  const model2 = parts[3];
23545
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
23546
- const toggleAdapter = getAdapter4(backend2);
24026
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
24027
+ const toggleAdapter = getAdapter5(backend2);
23547
24028
  const label2 = toggleAdapter.availableModels[model2]?.label ?? model2;
23548
24029
  const { toggleParticipant: toggleParticipant2, buildSelectKeyboard: buildSelectKeyboard2, hasPendingCouncil: hasPendingCouncil2 } = await Promise.resolve().then(() => (init_wizard2(), wizard_exports));
23549
24030
  if (!hasPendingCouncil2(chatId)) {
@@ -23793,11 +24274,12 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
23793
24274
  if (rest === "on") {
23794
24275
  setHeartbeatConfig(chatId, { enabled: true });
23795
24276
  startHeartbeatForChat(chatId);
23796
- await sendHeartbeatKeyboard(chatId, channel);
24277
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
23797
24278
  } else if (rest === "off") {
23798
24279
  setHeartbeatConfig(chatId, { enabled: false });
23799
24280
  stopHeartbeatForChat(chatId);
23800
- await sendHeartbeatKeyboard(chatId, channel);
24281
+ await channel.sendText(chatId, "\u26A0\uFE0F Heartbeat disabled. You won't receive proactive health alerts or watch notifications.\nRe-enable with /heartbeat on.", { parseMode: "plain" });
24282
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
23801
24283
  } else if (rest.startsWith("interval:")) {
23802
24284
  const min = parseInt(rest.slice(9), 10);
23803
24285
  if (isNaN(min) || min < 1) return;
@@ -23806,7 +24288,55 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
23806
24288
  stopHeartbeatForChat(chatId);
23807
24289
  const hbConf = getHeartbeatConfig(chatId);
23808
24290
  if (hbConf?.enabled) startHeartbeatForChat(chatId);
23809
- await sendHeartbeatKeyboard(chatId, channel);
24291
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24292
+ } else if (rest.startsWith("backend:")) {
24293
+ const bid = rest.slice(8);
24294
+ if (bid === "default") {
24295
+ updateHeartbeatField(chatId, "backend", null);
24296
+ updateHeartbeatField(chatId, "model", null);
24297
+ } else {
24298
+ updateHeartbeatField(chatId, "backend", bid);
24299
+ updateHeartbeatField(chatId, "model", null);
24300
+ }
24301
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24302
+ } else if (rest.startsWith("model:")) {
24303
+ const model2 = rest.slice(6);
24304
+ if (model2 === "default") {
24305
+ updateHeartbeatField(chatId, "model", null);
24306
+ } else {
24307
+ updateHeartbeatField(chatId, "model", model2);
24308
+ }
24309
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24310
+ } else if (rest === "thinking") {
24311
+ const config2 = getHeartbeatConfig(chatId);
24312
+ const current = config2?.thinking ?? "off";
24313
+ const cycle = ["off", "low", "medium", "high"];
24314
+ const next = cycle[(cycle.indexOf(current) + 1) % cycle.length];
24315
+ updateHeartbeatField(chatId, "thinking", next === "off" ? null : next);
24316
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24317
+ } else if (rest === "run") {
24318
+ await channel.sendText(chatId, "\u23F3 Running heartbeat check...", { parseMode: "plain" });
24319
+ try {
24320
+ await runHeartbeatNow(chatId);
24321
+ await channel.sendText(chatId, "\u2705 Heartbeat check complete.", { parseMode: "plain" });
24322
+ } catch (err) {
24323
+ await channel.sendText(chatId, `\u274C Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, { parseMode: "plain" });
24324
+ }
24325
+ } else if (rest === "watches" || rest === "addwatch") {
24326
+ await channel.sendText(chatId, "Use /watch to manage awareness tasks.\n\nExamples:\n /watch add Check my email for urgent messages\n /watch add Check if Ollama is running\n /watch list\n /watch remove 1", { parseMode: "plain" });
24327
+ } else if (rest.startsWith("fb:add:")) {
24328
+ const bid = rest.slice(7);
24329
+ const config2 = getHeartbeatConfig(chatId);
24330
+ const current = parseHeartbeatFallbacks(config2?.fallbacks ?? null);
24331
+ if (!current.some((f) => f.backend === bid)) {
24332
+ current.push({ backend: bid });
24333
+ updateHeartbeatField(chatId, "fallbacks", JSON.stringify(current));
24334
+ }
24335
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24336
+ } else if (rest === "fb:clear") {
24337
+ updateHeartbeatField(chatId, "fallbacks", null);
24338
+ await sendHeartbeatKeyboard(chatId, channel, messageId);
24339
+ } else if (rest === "noop") {
23810
24340
  }
23811
24341
  return;
23812
24342
  } else if (data.startsWith("newchat:")) {
@@ -25636,18 +26166,18 @@ var init_wrap_backend = __esm({
25636
26166
  });
25637
26167
 
25638
26168
  // src/agents/runners/config-loader.ts
25639
- import { readFileSync as readFileSync14, readdirSync as readdirSync14, existsSync as existsSync23, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
26169
+ import { readFileSync as readFileSync15, readdirSync as readdirSync14, existsSync as existsSync24, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
25640
26170
  import { join as join27 } from "path";
25641
- import { execFileSync as execFileSync2 } from "child_process";
26171
+ import { execFileSync as execFileSync3 } from "child_process";
25642
26172
  function resolveExecutable2(config2) {
25643
- if (existsSync23(config2.executable)) return config2.executable;
26173
+ if (existsSync24(config2.executable)) return config2.executable;
25644
26174
  try {
25645
- return execFileSync2("which", [config2.executable], { encoding: "utf-8" }).trim();
26175
+ return execFileSync3("which", [config2.executable], { encoding: "utf-8" }).trim();
25646
26176
  } catch {
25647
26177
  }
25648
26178
  for (const fallback of config2.executableFallbacks ?? []) {
25649
26179
  const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
25650
- if (existsSync23(resolved)) return resolved;
26180
+ if (existsSync24(resolved)) return resolved;
25651
26181
  }
25652
26182
  return config2.executable;
25653
26183
  }
@@ -25778,7 +26308,7 @@ function configToRunner(config2) {
25778
26308
  }
25779
26309
  function loadRunnerConfig(filePath) {
25780
26310
  try {
25781
- const content = readFileSync14(filePath, "utf-8");
26311
+ const content = readFileSync15(filePath, "utf-8");
25782
26312
  return JSON.parse(content);
25783
26313
  } catch (err) {
25784
26314
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -25786,7 +26316,7 @@ function loadRunnerConfig(filePath) {
25786
26316
  }
25787
26317
  }
25788
26318
  function loadAllRunnerConfigs() {
25789
- if (!existsSync23(RUNNERS_PATH)) {
26319
+ if (!existsSync24(RUNNERS_PATH)) {
25790
26320
  mkdirSync10(RUNNERS_PATH, { recursive: true });
25791
26321
  return [];
25792
26322
  }
@@ -25814,9 +26344,9 @@ function registerConfigRunners() {
25814
26344
  return count;
25815
26345
  }
25816
26346
  function watchRunnerConfigs(onChange) {
25817
- if (!existsSync23(RUNNERS_PATH)) return;
26347
+ if (!existsSync24(RUNNERS_PATH)) return;
25818
26348
  for (const prev of watchedFiles) {
25819
- if (!existsSync23(prev)) {
26349
+ if (!existsSync24(prev)) {
25820
26350
  unwatchFile(prev);
25821
26351
  watchedFiles.delete(prev);
25822
26352
  }
@@ -26348,6 +26878,7 @@ var init_telegram2 = __esm({
26348
26878
  { command: "model_signature", description: "Toggle model+thinking signature on responses" },
26349
26879
  { command: "imagine", description: "Generate an image from a prompt" },
26350
26880
  { command: "heartbeat", description: "Configure proactive heartbeat" },
26881
+ { command: "watch", description: "Manage heartbeat awareness tasks" },
26351
26882
  { command: "chats", description: "Manage multi-chat aliases" },
26352
26883
  { command: "intent", description: "Test intent classifier on a message" },
26353
26884
  { command: "evolve", description: "Self-learning & evolution controls" },
@@ -26911,19 +27442,19 @@ var init_telegram2 = __esm({
26911
27442
  });
26912
27443
 
26913
27444
  // src/skills/bootstrap.ts
26914
- import { existsSync as existsSync24 } from "fs";
27445
+ import { existsSync as existsSync25 } from "fs";
26915
27446
  import { readdir as readdir5, readFile as readFile8, writeFile as writeFile5, copyFile } from "fs/promises";
26916
27447
  import { join as join28, dirname as dirname5 } from "path";
26917
27448
  import { fileURLToPath as fileURLToPath2 } from "url";
26918
27449
  async function copyAgentManifestSkills() {
26919
- if (!existsSync24(PKG_SKILLS)) return;
27450
+ if (!existsSync25(PKG_SKILLS)) return;
26920
27451
  try {
26921
27452
  const entries = await readdir5(PKG_SKILLS, { withFileTypes: true });
26922
27453
  for (const entry of entries) {
26923
27454
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
26924
27455
  const src = join28(PKG_SKILLS, entry.name);
26925
27456
  const dest = join28(SKILLS_PATH, entry.name);
26926
- if (existsSync24(dest)) continue;
27457
+ if (existsSync25(dest)) continue;
26927
27458
  await copyFile(src, dest);
26928
27459
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
26929
27460
  }
@@ -26934,7 +27465,7 @@ async function copyAgentManifestSkills() {
26934
27465
  async function bootstrapSkills() {
26935
27466
  await copyAgentManifestSkills();
26936
27467
  const usmDir = join28(SKILLS_PATH, USM_DIR_NAME);
26937
- if (existsSync24(usmDir)) return;
27468
+ if (existsSync25(usmDir)) return;
26938
27469
  try {
26939
27470
  const entries = await readdir5(SKILLS_PATH);
26940
27471
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -26958,7 +27489,7 @@ async function bootstrapSkills() {
26958
27489
  }
26959
27490
  async function patchUsmForCcClaw(usmDir) {
26960
27491
  const skillPath = join28(usmDir, "SKILL.md");
26961
- if (!existsSync24(skillPath)) return;
27492
+ if (!existsSync25(skillPath)) return;
26962
27493
  try {
26963
27494
  let content = await readFile8(skillPath, "utf-8");
26964
27495
  let patched = false;
@@ -27226,13 +27757,13 @@ __export(ai_skill_exports, {
27226
27757
  generateAiSkill: () => generateAiSkill,
27227
27758
  installAiSkill: () => installAiSkill
27228
27759
  });
27229
- import { existsSync as existsSync25, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
27760
+ import { existsSync as existsSync26, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
27230
27761
  import { join as join29 } from "path";
27231
27762
  import { homedir as homedir9 } from "os";
27232
27763
  function generateAiSkill() {
27233
27764
  const version = VERSION;
27234
27765
  let systemState = "";
27235
- if (existsSync25(DB_PATH)) {
27766
+ if (existsSync26(DB_PATH)) {
27236
27767
  try {
27237
27768
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
27238
27769
  const readDb = openDatabaseReadOnly2();
@@ -27513,7 +28044,7 @@ cc-claw voice set off # Disable voice responses
27513
28044
  # Requires GEMINI_API_KEY in ~/.cc-claw/.env
27514
28045
  \`\`\`
27515
28046
 
27516
- ### Heartbeat
28047
+ ### Heartbeat & Watches (Proactive Monitoring)
27517
28048
  \`\`\`bash
27518
28049
  cc-claw heartbeat get --json # Show heartbeat config
27519
28050
  cc-claw heartbeat set on # Enable proactive awareness
@@ -27522,6 +28053,20 @@ cc-claw heartbeat set interval 30m # Set check interval
27522
28053
  cc-claw heartbeat set hours 9-22 # Set active hours
27523
28054
  \`\`\`
27524
28055
 
28056
+ **Heartbeat** runs a periodic awareness cycle that checks system health and user-defined watches.
28057
+ It supports delivery parity: custom backend, model, fallback chain, thinking level, and delivery target \u2014 all configured via the interactive \`/heartbeat\` keyboard in Telegram.
28058
+ When nothing needs attention, the AI responds with \`HEARTBEAT_OK\` and no message is delivered.
28059
+
28060
+ **Watches** are AI-executable awareness tasks checked during each heartbeat cycle:
28061
+ \`\`\`
28062
+ /watch add Check my email for urgent messages # Persistent watch
28063
+ /watch add Monitor Ollama health for 24h # Time-limited watch
28064
+ /watch list # List active watches
28065
+ /watch remove <id> # Remove a watch
28066
+ \`\`\`
28067
+
28068
+ **\u26A0\uFE0F CRITICAL:** Heartbeat and watch configuration MUST be done via \`/heartbeat\` and \`/watch\` commands. NEVER modify \`chat_heartbeat\` or \`heartbeat_watches\` database tables directly.
28069
+
27525
28070
  ### Summarizer
27526
28071
  \`\`\`bash
27527
28072
  cc-claw summarizer get --json # Current summarizer config
@@ -27692,7 +28237,7 @@ var index_exports = {};
27692
28237
  __export(index_exports, {
27693
28238
  main: () => main
27694
28239
  });
27695
- import { mkdirSync as mkdirSync12, existsSync as existsSync26, renameSync as renameSync2, statSync as statSync8, readFileSync as readFileSync16 } from "fs";
28240
+ import { mkdirSync as mkdirSync12, existsSync as existsSync27, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync17 } from "fs";
27696
28241
  import { join as join30 } from "path";
27697
28242
  import dotenv from "dotenv";
27698
28243
  function migrateLayout() {
@@ -27706,7 +28251,7 @@ function migrateLayout() {
27706
28251
  [join30(CC_CLAW_HOME, "cc-claw.error.log.1"), join30(LOGS_PATH, "cc-claw.error.log.1")]
27707
28252
  ];
27708
28253
  for (const [from, to] of moves) {
27709
- if (existsSync26(from) && !existsSync26(to)) {
28254
+ if (existsSync27(from) && !existsSync27(to)) {
27710
28255
  try {
27711
28256
  renameSync2(from, to);
27712
28257
  } catch {
@@ -27717,7 +28262,7 @@ function migrateLayout() {
27717
28262
  function rotateLogs() {
27718
28263
  for (const file of [LOG_PATH, ERROR_LOG_PATH]) {
27719
28264
  try {
27720
- const { size } = statSync8(file);
28265
+ const { size } = statSync9(file);
27721
28266
  if (size > LOG_MAX_BYTES) {
27722
28267
  const archivePath = `${file}.1`;
27723
28268
  try {
@@ -27735,7 +28280,7 @@ async function main() {
27735
28280
  let version = "unknown";
27736
28281
  try {
27737
28282
  const pkgPath = new URL("../package.json", import.meta.url);
27738
- version = JSON.parse(readFileSync16(pkgPath, "utf-8")).version;
28283
+ version = JSON.parse(readFileSync17(pkgPath, "utf-8")).version;
27739
28284
  } catch {
27740
28285
  }
27741
28286
  log(`[cc-claw] Starting v${version}`);
@@ -27749,11 +28294,11 @@ async function main() {
27749
28294
  } catch {
27750
28295
  }
27751
28296
  try {
27752
- const { getAdapter: getAdapter4, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
28297
+ const { getAdapter: getAdapter5, probeBackendAvailability: probeBackendAvailability2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
27753
28298
  await probeBackendAvailability2();
27754
- const claude = getAdapter4("claude");
28299
+ const claude = getAdapter5("claude");
27755
28300
  if ("getAuthMode" in claude) claude.getAuthMode();
27756
- const cursor = getAdapter4("cursor");
28301
+ const cursor = getAdapter5("cursor");
27757
28302
  if ("probeTier" in cursor) cursor.probeTier();
27758
28303
  } catch {
27759
28304
  }
@@ -27773,8 +28318,8 @@ async function main() {
27773
28318
  }
27774
28319
  if (modelCount > 0) {
27775
28320
  try {
27776
- const { getAdapter: getAdapter4 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
27777
- const adapter = getAdapter4("ollama");
28321
+ const { getAdapter: getAdapter5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
28322
+ const adapter = getAdapter5("ollama");
27778
28323
  if ("refreshModelCatalog" in adapter) {
27779
28324
  adapter.refreshModelCatalog();
27780
28325
  }
@@ -27855,10 +28400,10 @@ async function main() {
27855
28400
  });
27856
28401
  log("[cc-claw] Agent orchestrator initialized");
27857
28402
  try {
27858
- const { execSync: execSync6 } = await import("child_process");
27859
- const codexPath = execSync6("which codex", { encoding: "utf-8", timeout: 5e3 }).trim();
28403
+ const { execSync: execSync7 } = await import("child_process");
28404
+ const codexPath = execSync7("which codex", { encoding: "utf-8", timeout: 5e3 }).trim();
27860
28405
  if (codexPath) {
27861
- const features = execSync6("codex features list", { encoding: "utf-8", timeout: 1e4 });
28406
+ const features = execSync7("codex features list", { encoding: "utf-8", timeout: 1e4 });
27862
28407
  if (/multi_agent\s+\S+\s+false/.test(features)) {
27863
28408
  warn("[cc-claw] Codex multi_agent feature is disabled \u2014 native sub-agent detection will not work. Run: codex features enable multi_agent");
27864
28409
  }
@@ -27986,10 +28531,10 @@ var init_index = __esm({
27986
28531
  init_health3();
27987
28532
  init_image_gen();
27988
28533
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SESSION_LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
27989
- if (!existsSync26(dir)) mkdirSync12(dir, { recursive: true });
28534
+ if (!existsSync27(dir)) mkdirSync12(dir, { recursive: true });
27990
28535
  }
27991
28536
  migrateLayout();
27992
- if (existsSync26(ENV_PATH)) {
28537
+ if (existsSync27(ENV_PATH)) {
27993
28538
  dotenv.config({ path: ENV_PATH });
27994
28539
  } else {
27995
28540
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -28010,12 +28555,12 @@ __export(api_client_exports, {
28010
28555
  apiPost: () => apiPost,
28011
28556
  isDaemonRunning: () => isDaemonRunning
28012
28557
  });
28013
- import { readFileSync as readFileSync17, existsSync as existsSync27 } from "fs";
28558
+ import { readFileSync as readFileSync18, existsSync as existsSync28 } from "fs";
28014
28559
  import { request as httpRequest, Agent } from "http";
28015
28560
  function getToken() {
28016
28561
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
28017
28562
  try {
28018
- if (existsSync27(TOKEN_PATH)) return readFileSync17(TOKEN_PATH, "utf-8").trim();
28563
+ if (existsSync28(TOKEN_PATH)) return readFileSync18(TOKEN_PATH, "utf-8").trim();
28019
28564
  } catch {
28020
28565
  }
28021
28566
  return null;
@@ -28114,8 +28659,8 @@ __export(service_exports2, {
28114
28659
  serviceStatus: () => serviceStatus,
28115
28660
  uninstallService: () => uninstallService
28116
28661
  });
28117
- import { existsSync as existsSync28, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
28118
- import { execFileSync as execFileSync3, execSync as execSync4 } from "child_process";
28662
+ import { existsSync as existsSync29, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
28663
+ import { execFileSync as execFileSync4, execSync as execSync5 } from "child_process";
28119
28664
  import { homedir as homedir10, platform } from "os";
28120
28665
  import { join as join31, dirname as dirname6 } from "path";
28121
28666
  function xmlEscape(s) {
@@ -28123,10 +28668,10 @@ function xmlEscape(s) {
28123
28668
  }
28124
28669
  function resolveExecutable3(name) {
28125
28670
  try {
28126
- return execFileSync3("which", [name], { encoding: "utf-8" }).trim();
28671
+ return execFileSync4("which", [name], { encoding: "utf-8" }).trim();
28127
28672
  } catch {
28128
28673
  const fallback = process.argv[1];
28129
- if (fallback && existsSync28(fallback)) return fallback;
28674
+ if (fallback && existsSync29(fallback)) return fallback;
28130
28675
  throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
28131
28676
  }
28132
28677
  }
@@ -28141,7 +28686,7 @@ function getPathDirs() {
28141
28686
  "/bin"
28142
28687
  ]);
28143
28688
  try {
28144
- const prefix = execSync4("npm config get prefix", { encoding: "utf-8" }).trim();
28689
+ const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
28145
28690
  if (prefix) dirs.add(join31(prefix, "bin"));
28146
28691
  } catch {
28147
28692
  }
@@ -28201,26 +28746,26 @@ function generatePlist() {
28201
28746
  }
28202
28747
  function installMacOS() {
28203
28748
  const agentsDir = dirname6(PLIST_PATH);
28204
- if (!existsSync28(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
28205
- if (!existsSync28(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28206
- if (existsSync28(PLIST_PATH)) {
28749
+ if (!existsSync29(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
28750
+ if (!existsSync29(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28751
+ if (existsSync29(PLIST_PATH)) {
28207
28752
  try {
28208
- execFileSync3("launchctl", ["unload", PLIST_PATH]);
28753
+ execFileSync4("launchctl", ["unload", PLIST_PATH]);
28209
28754
  } catch {
28210
28755
  }
28211
28756
  }
28212
28757
  writeFileSync9(PLIST_PATH, generatePlist());
28213
28758
  console.log(` Installed: ${PLIST_PATH}`);
28214
- execFileSync3("launchctl", ["load", PLIST_PATH]);
28759
+ execFileSync4("launchctl", ["load", PLIST_PATH]);
28215
28760
  console.log(" Service loaded and starting.");
28216
28761
  }
28217
28762
  function uninstallMacOS() {
28218
- if (!existsSync28(PLIST_PATH)) {
28763
+ if (!existsSync29(PLIST_PATH)) {
28219
28764
  console.log(" No service found to uninstall.");
28220
28765
  return;
28221
28766
  }
28222
28767
  try {
28223
- execFileSync3("launchctl", ["unload", PLIST_PATH]);
28768
+ execFileSync4("launchctl", ["unload", PLIST_PATH]);
28224
28769
  } catch {
28225
28770
  }
28226
28771
  unlinkSync8(PLIST_PATH);
@@ -28246,7 +28791,7 @@ async function getUptimeFromDaemon() {
28246
28791
  }
28247
28792
  function statusMacOS() {
28248
28793
  try {
28249
- const out = execFileSync3("launchctl", ["list"], { encoding: "utf-8" });
28794
+ const out = execFileSync4("launchctl", ["list"], { encoding: "utf-8" });
28250
28795
  const line = out.split("\n").find((l) => l.includes("cc-claw"));
28251
28796
  if (line) {
28252
28797
  const parts = line.trim().split(/\s+/);
@@ -28290,42 +28835,42 @@ WantedBy=default.target
28290
28835
  `;
28291
28836
  }
28292
28837
  function installLinux() {
28293
- if (!existsSync28(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
28294
- if (!existsSync28(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28838
+ if (!existsSync29(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
28839
+ if (!existsSync29(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
28295
28840
  writeFileSync9(UNIT_PATH, generateUnit());
28296
28841
  console.log(` Installed: ${UNIT_PATH}`);
28297
- execFileSync3("systemctl", ["--user", "daemon-reload"]);
28298
- execFileSync3("systemctl", ["--user", "enable", "cc-claw"]);
28299
- execFileSync3("systemctl", ["--user", "start", "cc-claw"]);
28842
+ execFileSync4("systemctl", ["--user", "daemon-reload"]);
28843
+ execFileSync4("systemctl", ["--user", "enable", "cc-claw"]);
28844
+ execFileSync4("systemctl", ["--user", "start", "cc-claw"]);
28300
28845
  console.log(" Service enabled and started.");
28301
28846
  }
28302
28847
  function uninstallLinux() {
28303
- if (!existsSync28(UNIT_PATH)) {
28848
+ if (!existsSync29(UNIT_PATH)) {
28304
28849
  console.log(" No service found to uninstall.");
28305
28850
  return;
28306
28851
  }
28307
28852
  try {
28308
- execFileSync3("systemctl", ["--user", "stop", "cc-claw"]);
28853
+ execFileSync4("systemctl", ["--user", "stop", "cc-claw"]);
28309
28854
  } catch {
28310
28855
  }
28311
28856
  try {
28312
- execFileSync3("systemctl", ["--user", "disable", "cc-claw"]);
28857
+ execFileSync4("systemctl", ["--user", "disable", "cc-claw"]);
28313
28858
  } catch {
28314
28859
  }
28315
28860
  unlinkSync8(UNIT_PATH);
28316
- execFileSync3("systemctl", ["--user", "daemon-reload"]);
28861
+ execFileSync4("systemctl", ["--user", "daemon-reload"]);
28317
28862
  console.log(" Service uninstalled.");
28318
28863
  }
28319
28864
  function statusLinux() {
28320
28865
  try {
28321
- const out = execSync4("systemctl --user is-active cc-claw", { encoding: "utf-8" }).trim();
28866
+ const out = execSync5("systemctl --user is-active cc-claw", { encoding: "utf-8" }).trim();
28322
28867
  console.log(` Service is ${out}.`);
28323
28868
  } catch {
28324
28869
  console.log(" Not running or not installed.");
28325
28870
  }
28326
28871
  }
28327
28872
  function installService() {
28328
- if (!existsSync28(join31(CC_CLAW_HOME, ".env"))) {
28873
+ if (!existsSync29(join31(CC_CLAW_HOME, ".env"))) {
28329
28874
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
28330
28875
  console.error(" Run 'cc-claw setup' before installing the service.");
28331
28876
  process.exitCode = 1;
@@ -28466,18 +29011,18 @@ __export(daemon_exports, {
28466
29011
  restartService: () => restartService,
28467
29012
  stopService: () => stopService
28468
29013
  });
28469
- import { execSync as execSync5 } from "child_process";
29014
+ import { execSync as execSync6 } from "child_process";
28470
29015
  import { platform as platform2 } from "os";
28471
29016
  async function stopService() {
28472
29017
  const os2 = platform2();
28473
29018
  try {
28474
29019
  if (os2 === "darwin") {
28475
- execSync5("launchctl stop com.cc-claw");
29020
+ execSync6("launchctl stop com.cc-claw");
28476
29021
  console.log(`
28477
29022
  ${success("Daemon stopped.")}
28478
29023
  `);
28479
29024
  } else if (os2 === "linux") {
28480
- execSync5("systemctl --user stop cc-claw");
29025
+ execSync6("systemctl --user stop cc-claw");
28481
29026
  console.log(`
28482
29027
  ${success("Daemon stopped.")}
28483
29028
  `);
@@ -28495,13 +29040,13 @@ async function restartService() {
28495
29040
  const os2 = platform2();
28496
29041
  try {
28497
29042
  if (os2 === "darwin") {
28498
- const uid = process.getuid?.() ?? execSync5("id -u", { encoding: "utf-8" }).trim();
28499
- execSync5(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
29043
+ const uid = process.getuid?.() ?? execSync6("id -u", { encoding: "utf-8" }).trim();
29044
+ execSync6(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
28500
29045
  console.log(`
28501
29046
  ${success("Daemon restarted.")}
28502
29047
  `);
28503
29048
  } else if (os2 === "linux") {
28504
- execSync5("systemctl --user restart cc-claw");
29049
+ execSync6("systemctl --user restart cc-claw");
28505
29050
  console.log(`
28506
29051
  ${success("Daemon restarted.")}
28507
29052
  `);
@@ -28523,13 +29068,13 @@ var init_daemon = __esm({
28523
29068
  });
28524
29069
 
28525
29070
  // src/cli/resolve-chat.ts
28526
- import { readFileSync as readFileSync19 } from "fs";
29071
+ import { readFileSync as readFileSync20 } from "fs";
28527
29072
  function resolveChatId2(globalOpts) {
28528
29073
  const explicit = globalOpts.chat;
28529
29074
  if (explicit) return explicit;
28530
29075
  if (_cachedDefault) return _cachedDefault;
28531
29076
  try {
28532
- const content = readFileSync19(ENV_PATH, "utf-8");
29077
+ const content = readFileSync20(ENV_PATH, "utf-8");
28533
29078
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
28534
29079
  if (match) {
28535
29080
  _cachedDefault = match[1].split(",")[0].trim();
@@ -28553,13 +29098,13 @@ var status_exports = {};
28553
29098
  __export(status_exports, {
28554
29099
  statusCommand: () => statusCommand
28555
29100
  });
28556
- import { existsSync as existsSync29, statSync as statSync9 } from "fs";
29101
+ import { existsSync as existsSync30, statSync as statSync10 } from "fs";
28557
29102
  async function statusCommand(globalOpts, localOpts) {
28558
29103
  try {
28559
29104
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28560
29105
  const readDb = openDatabaseReadOnly2();
28561
29106
  const chatId = resolveChatId2(globalOpts);
28562
- const { getAdapterForChat: getAdapterForChat2, getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
29107
+ const { getAdapterForChat: getAdapterForChat3, getAdapter: getAdapter5, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
28563
29108
  let backend2 = null;
28564
29109
  let modelName = "not set";
28565
29110
  let contextMax = 2e5;
@@ -28567,7 +29112,7 @@ async function statusCommand(globalOpts, localOpts) {
28567
29112
  const backendRow = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
28568
29113
  if (backendRow) {
28569
29114
  try {
28570
- const a = getAdapter4(backendRow.backend);
29115
+ const a = getAdapter5(backendRow.backend);
28571
29116
  backend2 = { id: a.id, displayName: a.displayName };
28572
29117
  } catch {
28573
29118
  backend2 = { id: backendRow.backend, displayName: backendRow.backend };
@@ -28580,7 +29125,7 @@ async function statusCommand(globalOpts, localOpts) {
28580
29125
  if (modelRow) modelName = modelRow.model;
28581
29126
  else if (backend2) {
28582
29127
  try {
28583
- modelName = getAdapter4(backend2.id).defaultModel;
29128
+ modelName = getAdapter5(backend2.id).defaultModel;
28584
29129
  } catch {
28585
29130
  }
28586
29131
  }
@@ -28593,7 +29138,7 @@ async function statusCommand(globalOpts, localOpts) {
28593
29138
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
28594
29139
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
28595
29140
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
28596
- const dbStat = existsSync29(DB_PATH) ? statSync9(DB_PATH) : null;
29141
+ const dbStat = existsSync30(DB_PATH) ? statSync10(DB_PATH) : null;
28597
29142
  let daemonRunning = false;
28598
29143
  let daemonInfo = {};
28599
29144
  try {
@@ -28705,13 +29250,13 @@ __export(doctor_exports, {
28705
29250
  doctorCommand: () => doctorCommand,
28706
29251
  doctorErrors: () => doctorErrors
28707
29252
  });
28708
- import { existsSync as existsSync30, statSync as statSync10, accessSync, readFileSync as readFileSync20, constants } from "fs";
28709
- import { execFileSync as execFileSync4 } from "child_process";
29253
+ import { existsSync as existsSync31, accessSync, constants } from "fs";
29254
+ import { execFileSync as execFileSync5 } from "child_process";
28710
29255
  async function doctorCommand(globalOpts, localOpts) {
28711
29256
  const checks = [];
28712
- if (existsSync30(DB_PATH)) {
28713
- const size = statSync10(DB_PATH).size;
28714
- checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
29257
+ const dbChecks = checkDatabase();
29258
+ checks.push(...dbChecks);
29259
+ if (existsSync31(DB_PATH)) {
28715
29260
  try {
28716
29261
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
28717
29262
  const readDb = openDatabaseReadOnly2();
@@ -28736,27 +29281,13 @@ async function doctorCommand(globalOpts, localOpts) {
28736
29281
  } catch (err) {
28737
29282
  checks.push({ name: "Database health", status: "error", message: err.message });
28738
29283
  }
28739
- } else {
28740
- checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
28741
29284
  }
28742
- if (existsSync30(ENV_PATH)) {
29285
+ if (existsSync31(ENV_PATH)) {
28743
29286
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
28744
29287
  } else {
28745
29288
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
28746
29289
  }
28747
- const CLI_BINARIES = { claude: "claude", gemini: "gemini", codex: "codex", cursor: "agent" };
28748
- let installedBackends = 0;
28749
- for (const [label2, binary] of Object.entries(CLI_BINARIES)) {
28750
- try {
28751
- const path = execFileSync4("which", [binary], { encoding: "utf-8", timeout: 5e3 }).trim();
28752
- checks.push({ name: `${label2} CLI`, status: "ok", message: path });
28753
- installedBackends++;
28754
- } catch {
28755
- }
28756
- }
28757
- if (installedBackends === 0) {
28758
- checks.push({ name: "Backend CLIs", status: "error", message: "No backend CLIs found. Install at least one (claude, gemini, codex, or cursor agent)." });
28759
- }
29290
+ checks.push(...checkBackendCLIs());
28760
29291
  try {
28761
29292
  const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
28762
29293
  const running = await isDaemonRunning2();
@@ -28787,14 +29318,14 @@ async function doctorCommand(globalOpts, localOpts) {
28787
29318
  checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
28788
29319
  }
28789
29320
  try {
28790
- const latest = execFileSync4("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
29321
+ const latest = execFileSync5("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
28791
29322
  if (latest && latest !== VERSION) {
28792
29323
  checks.push({ name: "Update available", status: "warning", message: `v${latest} available (current: v${VERSION})`, fix: "npm install -g cc-claw@latest" });
28793
29324
  }
28794
29325
  } catch {
28795
29326
  }
28796
29327
  const tokenPath = `${DATA_PATH}/api-token`;
28797
- if (existsSync30(tokenPath)) {
29328
+ if (existsSync31(tokenPath)) {
28798
29329
  try {
28799
29330
  accessSync(tokenPath, constants.R_OK);
28800
29331
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -28804,125 +29335,28 @@ async function doctorCommand(globalOpts, localOpts) {
28804
29335
  } else {
28805
29336
  checks.push({ name: "API token", status: "warning", message: "no token file (daemon not started?)", fix: "cc-claw service start" });
28806
29337
  }
28807
- try {
28808
- const { execSync: execSync6 } = await import("child_process");
28809
- const dfOutput = execSync6("df -k " + DATA_PATH, { encoding: "utf-8" });
28810
- const lines = dfOutput.trim().split("\n");
28811
- if (lines.length >= 2) {
28812
- const parts = lines[1].split(/\s+/);
28813
- const availKB = parseInt(parts[3], 10);
28814
- if (availKB < 1e5) {
28815
- checks.push({ name: "Disk space", status: "warning", message: `${(availKB / 1024).toFixed(0)}MB available` });
28816
- } else {
28817
- checks.push({ name: "Disk space", status: "ok", message: `${(availKB / 1024 / 1024).toFixed(1)}GB available` });
28818
- }
28819
- }
28820
- } catch {
28821
- }
28822
- if (existsSync30(ERROR_LOG_PATH)) {
28823
- try {
28824
- const { readFileSync: readFileSync28 } = await import("fs");
28825
- const logContent = readFileSync28(ERROR_LOG_PATH, "utf-8");
28826
- const recentLines = logContent.split("\n").filter(Boolean).slice(-500);
28827
- const last24h = Date.now() - 864e5;
28828
- const recentErrors = recentLines.filter((line) => {
28829
- const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:]+)/);
28830
- if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
28831
- return false;
28832
- });
28833
- if (recentErrors.length > 0) {
28834
- let rate429 = 0;
28835
- let contentSilence = 0;
28836
- let spawnTimeout = 0;
28837
- const otherLines = [];
28838
- for (const line of recentErrors) {
28839
- if (/429|rate.?limit/i.test(line)) rate429++;
28840
- else if (/content silence/i.test(line)) contentSilence++;
28841
- else if (/spawn timeout|timeout after \d+s/i.test(line)) spawnTimeout++;
28842
- else otherLines.push(line);
28843
- }
28844
- const viewFix = "cc-claw logs --error to view details";
28845
- const clearFix = "cc-claw doctor --fix to clear stale errors";
28846
- if (rate429 > 10) {
28847
- checks.push({ name: "Telegram rate limits", status: "error", message: `${rate429} rate-limit (429) errors in last 24h \u2014 message delivery blocked`, fix: `${viewFix}, ${clearFix}` });
28848
- } else if (rate429 > 0) {
28849
- checks.push({ name: "Telegram rate limits", status: "warning", message: `${rate429} rate-limit (429) errors in last 24h`, fix: `${viewFix}, ${clearFix}` });
28850
- }
28851
- if (contentSilence > 0) {
28852
- checks.push({ name: "Content silence", status: "warning", message: `${contentSilence} agent silence timeout(s) in last 24h \u2014 API went unresponsive`, fix: `${viewFix}, ${clearFix}` });
28853
- }
28854
- if (spawnTimeout > 0) {
28855
- checks.push({ name: "Spawn timeouts", status: "warning", message: `${spawnTimeout} backend timeout(s) in last 24h`, fix: `${viewFix}, ${clearFix}` });
28856
- }
28857
- if (otherLines.length > 0) {
28858
- const summaries = /* @__PURE__ */ new Map();
28859
- for (const line of otherLines) {
28860
- const body = line.replace(/^\[\d{4}-\d{2}-\d{2}\s+[\d:+-]+\]\s*/, "").slice(0, 120);
28861
- const key = body.slice(0, 80);
28862
- summaries.set(key, (summaries.get(key) ?? 0) + 1);
28863
- }
28864
- const topErrors = [...summaries.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([msg, count]) => count > 1 ? `${msg}\u2026 (\xD7${count})` : `${msg}\u2026`).join("\n ");
28865
- checks.push({ name: "Other errors", status: "warning", message: `${otherLines.length} error(s) in last 24h:
28866
- ${topErrors}`, fix: `${viewFix}, ${clearFix}` });
28867
- }
28868
- if (rate429 === 0 && contentSilence === 0 && spawnTimeout === 0 && otherLines.length === 0) {
28869
- checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
28870
- }
28871
- } else {
28872
- checks.push({ name: "Recent errors", status: "ok", message: "none in last 24h" });
28873
- }
28874
- } catch {
28875
- checks.push({ name: "Error log", status: "ok", message: "no errors logged" });
28876
- }
28877
- } else {
28878
- checks.push({ name: "Error log", status: "ok", message: "no error log file" });
28879
- }
28880
- try {
28881
- const { OllamaStore } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
28882
- const servers = OllamaStore.listServers();
28883
- if (servers.length > 0) {
28884
- const online = servers.filter((s) => s.status === "online");
28885
- const models = OllamaStore.getAvailableModels();
28886
- if (online.length === servers.length) {
28887
- checks.push({
28888
- name: "Ollama",
28889
- status: "ok",
28890
- message: `${online.length} server(s) online, ${models.length} model(s)`
28891
- });
28892
- } else if (online.length > 0) {
28893
- checks.push({
28894
- name: "Ollama",
28895
- status: "warning",
28896
- message: `${online.length}/${servers.length} server(s) online, ${models.length} model(s)`
28897
- });
28898
- } else {
28899
- checks.push({
28900
- name: "Ollama",
28901
- status: "warning",
28902
- message: `${servers.length} server(s) configured, all offline`,
28903
- fix: "Check Ollama is running: ollama serve"
28904
- });
28905
- }
28906
- }
28907
- } catch {
28908
- }
29338
+ const disk = checkDiskSpace();
29339
+ if (disk) checks.push(disk);
29340
+ checks.push(...checkErrorLog());
29341
+ const ollama2 = checkOllamaServers();
29342
+ if (ollama2) checks.push(ollama2);
28909
29343
  if (localOpts.fix) {
28910
29344
  const fixable = checks.filter((c) => c.status !== "ok" && c.fix);
28911
29345
  for (const check of fixable) {
28912
29346
  if (check.name === "Daemon" && check.fix?.includes("service start")) {
28913
29347
  try {
28914
- const { execSync: execSync6 } = await import("child_process");
29348
+ const { execSync: execSync7 } = await import("child_process");
28915
29349
  const os2 = (await import("os")).platform();
28916
29350
  if (os2 === "darwin") {
28917
29351
  try {
28918
- execSync6("launchctl start com.cc-claw");
29352
+ execSync7("launchctl start com.cc-claw");
28919
29353
  check.status = "ok";
28920
29354
  check.message = "restarted";
28921
29355
  } catch {
28922
29356
  }
28923
29357
  } else if (os2 === "linux") {
28924
29358
  try {
28925
- execSync6("systemctl --user start cc-claw");
29359
+ execSync7("systemctl --user start cc-claw");
28926
29360
  check.status = "ok";
28927
29361
  check.message = "restarted";
28928
29362
  } catch {
@@ -28955,9 +29389,9 @@ async function doctorCommand(globalOpts, localOpts) {
28955
29389
  }
28956
29390
  }
28957
29391
  const errorChecks = checks.filter(
28958
- (c) => ["Telegram rate limits", "Content silence", "Spawn timeouts", "Other errors"].includes(c.name) && c.status !== "ok"
29392
+ (c) => ["Rate limits", "Content silence", "Spawn timeouts", "Other errors"].includes(c.name) && c.status !== "ok"
28959
29393
  );
28960
- if (errorChecks.length > 0 && existsSync30(ERROR_LOG_PATH)) {
29394
+ if (errorChecks.length > 0 && existsSync31(ERROR_LOG_PATH)) {
28961
29395
  try {
28962
29396
  const { writeFileSync: writeFileSync13 } = await import("fs");
28963
29397
  writeFileSync13(ERROR_LOG_PATH, "");
@@ -28970,9 +29404,9 @@ async function doctorCommand(globalOpts, localOpts) {
28970
29404
  }
28971
29405
  }
28972
29406
  }
28973
- const errors = checks.filter((c) => c.status === "error").length;
28974
- const warnings = checks.filter((c) => c.status === "warning").length;
28975
- const report = { checks, errors, warnings };
29407
+ const errCount = checks.filter((c) => c.status === "error").length;
29408
+ const warnCount = checks.filter((c) => c.status === "warning").length;
29409
+ const report = { checks, errors: errCount, warnings: warnCount };
28976
29410
  output(report, (d) => {
28977
29411
  const r = d;
28978
29412
  const lines = [
@@ -29003,27 +29437,7 @@ async function doctorCommand(globalOpts, localOpts) {
29003
29437
  lines.push("");
29004
29438
  return lines.join("\n");
29005
29439
  });
29006
- process.exit(errors > 0 ? 1 : warnings > 0 ? 2 : 0);
29007
- }
29008
- function getRecentErrors() {
29009
- if (!existsSync30(ERROR_LOG_PATH)) return null;
29010
- const logContent = readFileSync20(ERROR_LOG_PATH, "utf-8");
29011
- const allLines = logContent.split("\n").filter(Boolean).slice(-500);
29012
- const last24h = Date.now() - 864e5;
29013
- const lines = allLines.filter((line) => {
29014
- const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:+-]+)/);
29015
- if (match) return (/* @__PURE__ */ new Date(`${match[1]}T${match[2]}`)).getTime() > last24h;
29016
- return false;
29017
- });
29018
- if (lines.length === 0) return null;
29019
- const classified = { rate429: [], contentSilence: [], spawnTimeout: [], other: [] };
29020
- for (const line of lines) {
29021
- if (/429|rate.?limit/i.test(line)) classified.rate429.push(line);
29022
- else if (/content silence/i.test(line)) classified.contentSilence.push(line);
29023
- else if (/spawn timeout|timeout after \d+s/i.test(line)) classified.spawnTimeout.push(line);
29024
- else classified.other.push(line);
29025
- }
29026
- return { lines, classified };
29440
+ process.exit(errCount > 0 ? 1 : warnCount > 0 ? 2 : 0);
29027
29441
  }
29028
29442
  function stripTimestamp(line) {
29029
29443
  const match = line.match(/^\[(\d{4}-\d{2}-\d{2})\s+([\d:+-]+)\]\s*(.*)/);
@@ -29038,9 +29452,8 @@ async function doctorErrors(globalOpts) {
29038
29452
  `);
29039
29453
  return;
29040
29454
  }
29041
- const { classified } = result;
29042
- const total = classified.rate429.length + classified.contentSilence.length + classified.spawnTimeout.length + classified.other.length;
29043
- output({ total, classified }, () => {
29455
+ const { rate429, contentSilence, spawnTimeout, other, total } = result;
29456
+ output({ total, classified: result }, () => {
29044
29457
  const lines = [
29045
29458
  "",
29046
29459
  box("Recent Errors (last 24h)"),
@@ -29072,10 +29485,10 @@ async function doctorErrors(globalOpts) {
29072
29485
  }
29073
29486
  lines.push("");
29074
29487
  };
29075
- renderCategory("Telegram rate limits", classified.rate429, 5);
29076
- renderCategory("Content silence timeouts", classified.contentSilence, 5);
29077
- renderCategory("Spawn timeouts", classified.spawnTimeout, 5);
29078
- renderCategory("Other errors", classified.other, 10);
29488
+ renderCategory("Telegram rate limits", rate429, 5);
29489
+ renderCategory("Content silence timeouts", contentSilence, 5);
29490
+ renderCategory("Spawn timeouts", spawnTimeout, 5);
29491
+ renderCategory("Other errors", other, 10);
29079
29492
  if (total === 0) {
29080
29493
  lines.push(` ${success("No errors in last 24h.")}`);
29081
29494
  } else {
@@ -29098,6 +29511,7 @@ var init_doctor = __esm({
29098
29511
  init_format2();
29099
29512
  init_paths();
29100
29513
  init_version();
29514
+ init_checks();
29101
29515
  }
29102
29516
  });
29103
29517
 
@@ -29106,15 +29520,15 @@ var logs_exports = {};
29106
29520
  __export(logs_exports, {
29107
29521
  logsCommand: () => logsCommand
29108
29522
  });
29109
- import { existsSync as existsSync31, readFileSync as readFileSync21, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
29523
+ import { existsSync as existsSync32, readFileSync as readFileSync22, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
29110
29524
  async function logsCommand(opts) {
29111
29525
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
29112
- if (!existsSync31(logFile)) {
29526
+ if (!existsSync32(logFile)) {
29113
29527
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
29114
29528
  process.exit(1);
29115
29529
  }
29116
29530
  const maxLines = parseInt(opts.lines ?? "100", 10);
29117
- const content = readFileSync21(logFile, "utf-8");
29531
+ const content = readFileSync22(logFile, "utf-8");
29118
29532
  const allLines = content.split("\n");
29119
29533
  const tailLines = allLines.slice(-maxLines);
29120
29534
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -29124,7 +29538,7 @@ async function logsCommand(opts) {
29124
29538
  let lastLength = content.length;
29125
29539
  watchFile2(logFile, { interval: 500 }, () => {
29126
29540
  try {
29127
- const newContent = readFileSync21(logFile, "utf-8");
29541
+ const newContent = readFileSync22(logFile, "utf-8");
29128
29542
  if (newContent.length > lastLength) {
29129
29543
  const newPart = newContent.slice(lastLength);
29130
29544
  process.stdout.write(newPart);
@@ -29156,7 +29570,7 @@ __export(session_logs_exports, {
29156
29570
  sessionLogsList: () => sessionLogsList,
29157
29571
  sessionLogsTail: () => sessionLogsTail
29158
29572
  });
29159
- import { readFileSync as readFileSync22, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
29573
+ import { readFileSync as readFileSync23, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
29160
29574
  async function sessionLogsList(opts) {
29161
29575
  const logs = listSessionLogs();
29162
29576
  if (logs.length === 0) {
@@ -29213,12 +29627,12 @@ async function sessionLogsTail(opts) {
29213
29627
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
29214
29628
  let lastLength = 0;
29215
29629
  try {
29216
- lastLength = readFileSync22(targetPath, "utf-8").length;
29630
+ lastLength = readFileSync23(targetPath, "utf-8").length;
29217
29631
  } catch {
29218
29632
  }
29219
29633
  watchFile3(targetPath, { interval: 500 }, () => {
29220
29634
  try {
29221
- const content = readFileSync22(targetPath, "utf-8");
29635
+ const content = readFileSync23(targetPath, "utf-8");
29222
29636
  if (content.length > lastLength) {
29223
29637
  process.stdout.write(content.slice(lastLength));
29224
29638
  lastLength = content.length;
@@ -29262,11 +29676,11 @@ __export(gemini_exports, {
29262
29676
  geminiReorder: () => geminiReorder,
29263
29677
  geminiRotation: () => geminiRotation
29264
29678
  });
29265
- import { existsSync as existsSync33, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync23, chmodSync } from "fs";
29679
+ import { existsSync as existsSync34, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync24, chmodSync } from "fs";
29266
29680
  import { join as join32 } from "path";
29267
29681
  import { createInterface as createInterface8 } from "readline";
29268
29682
  function requireDb() {
29269
- if (!existsSync33(DB_PATH)) {
29683
+ if (!existsSync34(DB_PATH)) {
29270
29684
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
29271
29685
  process.exit(1);
29272
29686
  }
@@ -29292,8 +29706,8 @@ function resolveOAuthEmail(configHome) {
29292
29706
  if (!configHome) return null;
29293
29707
  try {
29294
29708
  const accountsPath = join32(configHome, ".gemini", "google_accounts.json");
29295
- if (!existsSync33(accountsPath)) return null;
29296
- const accounts = JSON.parse(readFileSync23(accountsPath, "utf-8"));
29709
+ if (!existsSync34(accountsPath)) return null;
29710
+ const accounts = JSON.parse(readFileSync24(accountsPath, "utf-8"));
29297
29711
  return accounts.active || null;
29298
29712
  } catch {
29299
29713
  return null;
@@ -29376,7 +29790,7 @@ async function geminiAddKey(globalOpts, opts) {
29376
29790
  async function geminiAddAccount(globalOpts, opts) {
29377
29791
  await requireWriteDb();
29378
29792
  const slotsDir = join32(CC_CLAW_HOME, "gemini-slots");
29379
- if (!existsSync33(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
29793
+ if (!existsSync34(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
29380
29794
  const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
29381
29795
  const tempId = Date.now();
29382
29796
  const slotDir = join32(slotsDir, `slot-${tempId}`);
@@ -29390,9 +29804,9 @@ async function geminiAddAccount(globalOpts, opts) {
29390
29804
  console.log(" Sign in with the Google account you want for this slot.");
29391
29805
  console.log(" After sign-in, type /quit to return here.");
29392
29806
  console.log("");
29393
- const { execSync: execSync6 } = await import("child_process");
29807
+ const { execSync: execSync7 } = await import("child_process");
29394
29808
  try {
29395
- execSync6(`GEMINI_CLI_HOME=${slotDir} gemini`, {
29809
+ execSync7(`GEMINI_CLI_HOME=${slotDir} gemini`, {
29396
29810
  stdio: "inherit",
29397
29811
  env: { ...process.env, GEMINI_CLI_HOME: slotDir, GEMINI_API_KEY: void 0, GOOGLE_API_KEY: void 0 },
29398
29812
  cwd: slotDir
@@ -29400,7 +29814,7 @@ async function geminiAddAccount(globalOpts, opts) {
29400
29814
  } catch {
29401
29815
  }
29402
29816
  const oauthPath = join32(slotDir, ".gemini", "oauth_creds.json");
29403
- if (!existsSync33(oauthPath)) {
29817
+ if (!existsSync34(oauthPath)) {
29404
29818
  console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
29405
29819
  console.log(" The slot directory is preserved at: " + slotDir);
29406
29820
  console.log(" Re-run: cc-claw gemini add-account\n");
@@ -29520,7 +29934,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29520
29934
  return;
29521
29935
  }
29522
29936
  const settingsPath = join32(slot.configHome, ".gemini", "settings.json");
29523
- if (!existsSync33(settingsPath)) {
29937
+ if (!existsSync34(settingsPath)) {
29524
29938
  mkdirSync14(join32(slot.configHome, ".gemini"), { recursive: true });
29525
29939
  writeFileSync10(settingsPath, JSON.stringify({
29526
29940
  security: { auth: { selectedType: "oauth-personal" } }
@@ -29531,9 +29945,9 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29531
29945
  console.log(" Sign in with the same Google account when prompted.");
29532
29946
  console.log(" After sign-in, type /quit to return here.");
29533
29947
  console.log("");
29534
- const { execSync: execSync6 } = await import("child_process");
29948
+ const { execSync: execSync7 } = await import("child_process");
29535
29949
  try {
29536
- execSync6(`gemini`, {
29950
+ execSync7(`gemini`, {
29537
29951
  stdio: "inherit",
29538
29952
  env: {
29539
29953
  ...process.env,
@@ -29546,7 +29960,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29546
29960
  } catch {
29547
29961
  }
29548
29962
  const oauthPath = join32(slot.configHome, ".gemini", "oauth_creds.json");
29549
- if (!existsSync33(oauthPath)) {
29963
+ if (!existsSync34(oauthPath)) {
29550
29964
  console.log(error2("\n Re-login failed \u2014 no OAuth credentials found."));
29551
29965
  console.log(` Try again: cc-claw gemini re-login ${idOrLabel}
29552
29966
  `);
@@ -29556,7 +29970,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
29556
29970
  setGeminiSlotEnabled2(slotId, true);
29557
29971
  let accountEmail = slot.label;
29558
29972
  try {
29559
- const accounts = JSON.parse(readFileSync23(join32(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
29973
+ const accounts = JSON.parse(readFileSync24(join32(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
29560
29974
  if (accounts.active) accountEmail = accounts.active;
29561
29975
  } catch {
29562
29976
  }
@@ -29615,11 +30029,11 @@ __export(backend_cmd_factory_exports, {
29615
30029
  makeReorder: () => makeReorder,
29616
30030
  registerBackendSlotCommands: () => registerBackendSlotCommands
29617
30031
  });
29618
- import { existsSync as existsSync34, mkdirSync as mkdirSync15, readFileSync as readFileSync24 } from "fs";
30032
+ import { existsSync as existsSync35, mkdirSync as mkdirSync15, readFileSync as readFileSync25 } from "fs";
29619
30033
  import { join as join33 } from "path";
29620
30034
  import { createInterface as createInterface9 } from "readline";
29621
30035
  function requireDb2() {
29622
- if (!existsSync34(DB_PATH)) {
30036
+ if (!existsSync35(DB_PATH)) {
29623
30037
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
29624
30038
  process.exit(1);
29625
30039
  }
@@ -29709,7 +30123,7 @@ function makeAddAccount(backend2, displayName) {
29709
30123
  }
29710
30124
  await requireWriteDb2();
29711
30125
  const slotsDir = join33(CC_CLAW_HOME, config2.slotsSubdir);
29712
- if (!existsSync34(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
30126
+ if (!existsSync35(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
29713
30127
  const tempId = Date.now();
29714
30128
  const slotDir = join33(slotsDir, `slot-${tempId}`);
29715
30129
  mkdirSync15(slotDir, { recursive: true, mode: 448 });
@@ -29719,14 +30133,14 @@ function makeAddAccount(backend2, displayName) {
29719
30133
  console.log(` Sign in with the account you want for this slot.`);
29720
30134
  console.log(` After sign-in completes, return here.`);
29721
30135
  console.log("");
29722
- const { execSync: execSync6 } = await import("child_process");
30136
+ const { execSync: execSync7 } = await import("child_process");
29723
30137
  const loginEnv = {
29724
30138
  ...process.env,
29725
30139
  [config2.envKey]: config2.envValue(slotDir),
29726
30140
  ...config2.envOverrides
29727
30141
  };
29728
30142
  try {
29729
- execSync6(config2.loginCommand.join(" "), {
30143
+ execSync7(config2.loginCommand.join(" "), {
29730
30144
  stdio: "inherit",
29731
30145
  env: loginEnv,
29732
30146
  cwd: slotDir
@@ -29871,14 +30285,14 @@ function makeRelogin(backend2, displayName) {
29871
30285
  console.log(` Re-authenticating ${displayName} slot "${slot.label || `#${slot.id}`}"...`);
29872
30286
  console.log(` Sign in with the same account when the browser opens.`);
29873
30287
  console.log("");
29874
- const { execSync: execSync6 } = await import("child_process");
30288
+ const { execSync: execSync7 } = await import("child_process");
29875
30289
  const loginEnv = {
29876
30290
  ...process.env,
29877
30291
  [config2.envKey]: config2.envValue(slot.configHome),
29878
30292
  ...config2.envOverrides
29879
30293
  };
29880
30294
  try {
29881
- execSync6(config2.loginCommand.join(" "), {
30295
+ execSync7(config2.loginCommand.join(" "), {
29882
30296
  stdio: "inherit",
29883
30297
  env: loginEnv,
29884
30298
  cwd: slot.configHome
@@ -29967,17 +30381,17 @@ var init_backend_cmd_factory = __esm({
29967
30381
  verifyCredentials: (slotDir) => {
29968
30382
  const claudeJson = join33(slotDir, ".claude.json");
29969
30383
  const claudeJsonNested = join33(slotDir, ".claude", ".claude.json");
29970
- if (existsSync34(claudeJson)) {
30384
+ if (existsSync35(claudeJson)) {
29971
30385
  try {
29972
- const data = JSON.parse(readFileSync24(claudeJson, "utf-8"));
30386
+ const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
29973
30387
  return Boolean(data.oauthAccount);
29974
30388
  } catch {
29975
30389
  return false;
29976
30390
  }
29977
30391
  }
29978
- if (existsSync34(claudeJsonNested)) {
30392
+ if (existsSync35(claudeJsonNested)) {
29979
30393
  try {
29980
- const data = JSON.parse(readFileSync24(claudeJsonNested, "utf-8"));
30394
+ const data = JSON.parse(readFileSync25(claudeJsonNested, "utf-8"));
29981
30395
  return Boolean(data.oauthAccount);
29982
30396
  } catch {
29983
30397
  return false;
@@ -29987,8 +30401,8 @@ var init_backend_cmd_factory = __esm({
29987
30401
  },
29988
30402
  extractLabel: (slotDir) => {
29989
30403
  try {
29990
- const { execSync: execSync6 } = __require("child_process");
29991
- const out = execSync6("claude auth status", {
30404
+ const { execSync: execSync7 } = __require("child_process");
30405
+ const out = execSync7("claude auth status", {
29992
30406
  encoding: "utf-8",
29993
30407
  env: { ...process.env, HOME: slotDir, ANTHROPIC_API_KEY: void 0 },
29994
30408
  timeout: 1e4
@@ -29999,8 +30413,8 @@ var init_backend_cmd_factory = __esm({
29999
30413
  }
30000
30414
  try {
30001
30415
  const claudeJson = join33(slotDir, ".claude.json");
30002
- if (existsSync34(claudeJson)) {
30003
- const data = JSON.parse(readFileSync24(claudeJson, "utf-8"));
30416
+ if (existsSync35(claudeJson)) {
30417
+ const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
30004
30418
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
30005
30419
  }
30006
30420
  } catch {
@@ -30015,11 +30429,11 @@ var init_backend_cmd_factory = __esm({
30015
30429
  envValue: (slotDir) => slotDir,
30016
30430
  envOverrides: { OPENAI_API_KEY: void 0 },
30017
30431
  verifyCredentials: (slotDir) => {
30018
- return existsSync34(join33(slotDir, "auth.json"));
30432
+ return existsSync35(join33(slotDir, "auth.json"));
30019
30433
  },
30020
30434
  extractLabel: (slotDir) => {
30021
30435
  try {
30022
- const authData = JSON.parse(readFileSync24(join33(slotDir, "auth.json"), "utf-8"));
30436
+ const authData = JSON.parse(readFileSync25(join33(slotDir, "auth.json"), "utf-8"));
30023
30437
  if (authData.email) return authData.email;
30024
30438
  if (authData.account_name) return authData.account_name;
30025
30439
  if (authData.user?.email) return authData.user.email;
@@ -30043,9 +30457,9 @@ __export(ollama_exports3, {
30043
30457
  ollamaRemove: () => ollamaRemove,
30044
30458
  ollamaTest: () => ollamaTest
30045
30459
  });
30046
- import { existsSync as existsSync35 } from "fs";
30460
+ import { existsSync as existsSync36 } from "fs";
30047
30461
  function requireDb3() {
30048
- if (!existsSync35(DB_PATH)) {
30462
+ if (!existsSync36(DB_PATH)) {
30049
30463
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30050
30464
  process.exit(1);
30051
30465
  }
@@ -30304,12 +30718,12 @@ __export(backend_exports, {
30304
30718
  backendList: () => backendList,
30305
30719
  backendSet: () => backendSet
30306
30720
  });
30307
- import { existsSync as existsSync36 } from "fs";
30721
+ import { existsSync as existsSync37 } from "fs";
30308
30722
  async function backendList(globalOpts) {
30309
30723
  const { getAvailableAdapters: getAvailableAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
30310
30724
  const chatId = resolveChatId2(globalOpts);
30311
30725
  let activeBackend = null;
30312
- if (existsSync36(DB_PATH)) {
30726
+ if (existsSync37(DB_PATH)) {
30313
30727
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30314
30728
  const readDb = openDatabaseReadOnly2();
30315
30729
  try {
@@ -30340,7 +30754,7 @@ async function backendList(globalOpts) {
30340
30754
  }
30341
30755
  async function backendGet(globalOpts) {
30342
30756
  const chatId = resolveChatId2(globalOpts);
30343
- if (!existsSync36(DB_PATH)) {
30757
+ if (!existsSync37(DB_PATH)) {
30344
30758
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30345
30759
  process.exit(1);
30346
30760
  }
@@ -30384,13 +30798,13 @@ __export(model_exports, {
30384
30798
  modelList: () => modelList,
30385
30799
  modelSet: () => modelSet
30386
30800
  });
30387
- import { existsSync as existsSync37 } from "fs";
30801
+ import { existsSync as existsSync38 } from "fs";
30388
30802
  async function modelList(globalOpts) {
30389
30803
  const chatId = resolveChatId2(globalOpts);
30390
30804
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30391
- const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
30805
+ const { getAdapter: getAdapter5, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
30392
30806
  let backendId = "claude";
30393
- if (existsSync37(DB_PATH)) {
30807
+ if (existsSync38(DB_PATH)) {
30394
30808
  const readDb = openDatabaseReadOnly2();
30395
30809
  try {
30396
30810
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -30400,7 +30814,7 @@ async function modelList(globalOpts) {
30400
30814
  readDb.close();
30401
30815
  }
30402
30816
  try {
30403
- const adapter = getAdapter4(backendId);
30817
+ const adapter = getAdapter5(backendId);
30404
30818
  const models = Object.entries(adapter.availableModels).map(([id, info]) => ({
30405
30819
  id,
30406
30820
  label: info.label,
@@ -30423,7 +30837,7 @@ async function modelList(globalOpts) {
30423
30837
  }
30424
30838
  async function modelGet(globalOpts) {
30425
30839
  const chatId = resolveChatId2(globalOpts);
30426
- if (!existsSync37(DB_PATH)) {
30840
+ if (!existsSync38(DB_PATH)) {
30427
30841
  outputError("DB_NOT_FOUND", "Database not found.");
30428
30842
  process.exit(1);
30429
30843
  }
@@ -30467,9 +30881,9 @@ __export(memory_exports2, {
30467
30881
  memoryList: () => memoryList,
30468
30882
  memorySearch: () => memorySearch
30469
30883
  });
30470
- import { existsSync as existsSync38 } from "fs";
30884
+ import { existsSync as existsSync39 } from "fs";
30471
30885
  async function memoryList(globalOpts) {
30472
- if (!existsSync38(DB_PATH)) {
30886
+ if (!existsSync39(DB_PATH)) {
30473
30887
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30474
30888
  process.exit(1);
30475
30889
  }
@@ -30493,7 +30907,7 @@ async function memoryList(globalOpts) {
30493
30907
  });
30494
30908
  }
30495
30909
  async function memorySearch(globalOpts, query) {
30496
- if (!existsSync38(DB_PATH)) {
30910
+ if (!existsSync39(DB_PATH)) {
30497
30911
  outputError("DB_NOT_FOUND", "Database not found.");
30498
30912
  process.exit(1);
30499
30913
  }
@@ -30515,7 +30929,7 @@ async function memorySearch(globalOpts, query) {
30515
30929
  });
30516
30930
  }
30517
30931
  async function memoryHistory(globalOpts, opts) {
30518
- if (!existsSync38(DB_PATH)) {
30932
+ if (!existsSync39(DB_PATH)) {
30519
30933
  outputError("DB_NOT_FOUND", "Database not found.");
30520
30934
  process.exit(1);
30521
30935
  }
@@ -30563,7 +30977,7 @@ __export(cron_exports2, {
30563
30977
  cronList: () => cronList,
30564
30978
  cronRuns: () => cronRuns
30565
30979
  });
30566
- import { existsSync as existsSync39 } from "fs";
30980
+ import { existsSync as existsSync40 } from "fs";
30567
30981
  function parseFallbacks(raw) {
30568
30982
  return raw.slice(0, 3).map((f) => {
30569
30983
  const [backend2, ...rest] = f.split(":");
@@ -30584,7 +30998,7 @@ function parseAndValidateTimeout(raw) {
30584
30998
  return val;
30585
30999
  }
30586
31000
  async function cronList(globalOpts) {
30587
- if (!existsSync39(DB_PATH)) {
31001
+ if (!existsSync40(DB_PATH)) {
30588
31002
  outputError("DB_NOT_FOUND", "Database not found.");
30589
31003
  process.exit(1);
30590
31004
  }
@@ -30622,7 +31036,7 @@ async function cronList(globalOpts) {
30622
31036
  });
30623
31037
  }
30624
31038
  async function cronHealth(globalOpts) {
30625
- if (!existsSync39(DB_PATH)) {
31039
+ if (!existsSync40(DB_PATH)) {
30626
31040
  outputError("DB_NOT_FOUND", "Database not found.");
30627
31041
  process.exit(1);
30628
31042
  }
@@ -30783,7 +31197,7 @@ async function cronEdit(globalOpts, id, opts) {
30783
31197
  }
30784
31198
  }
30785
31199
  async function cronRuns(globalOpts, jobId, opts) {
30786
- if (!existsSync39(DB_PATH)) {
31200
+ if (!existsSync40(DB_PATH)) {
30787
31201
  outputError("DB_NOT_FOUND", "Database not found.");
30788
31202
  process.exit(1);
30789
31203
  }
@@ -30830,9 +31244,9 @@ __export(agents_exports, {
30830
31244
  runnersList: () => runnersList,
30831
31245
  tasksList: () => tasksList
30832
31246
  });
30833
- import { existsSync as existsSync40 } from "fs";
31247
+ import { existsSync as existsSync41 } from "fs";
30834
31248
  async function agentsList(globalOpts) {
30835
- if (!existsSync40(DB_PATH)) {
31249
+ if (!existsSync41(DB_PATH)) {
30836
31250
  outputError("DB_NOT_FOUND", "Database not found.");
30837
31251
  process.exit(1);
30838
31252
  }
@@ -30863,7 +31277,7 @@ async function agentsList(globalOpts) {
30863
31277
  });
30864
31278
  }
30865
31279
  async function tasksList(globalOpts) {
30866
- if (!existsSync40(DB_PATH)) {
31280
+ if (!existsSync41(DB_PATH)) {
30867
31281
  outputError("DB_NOT_FOUND", "Database not found.");
30868
31282
  process.exit(1);
30869
31283
  }
@@ -30991,10 +31405,10 @@ __export(db_exports, {
30991
31405
  dbPath: () => dbPath,
30992
31406
  dbStats: () => dbStats
30993
31407
  });
30994
- import { existsSync as existsSync41, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
31408
+ import { existsSync as existsSync42, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
30995
31409
  import { dirname as dirname7 } from "path";
30996
31410
  async function dbStats(globalOpts) {
30997
- if (!existsSync41(DB_PATH)) {
31411
+ if (!existsSync42(DB_PATH)) {
30998
31412
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
30999
31413
  process.exit(1);
31000
31414
  }
@@ -31002,7 +31416,7 @@ async function dbStats(globalOpts) {
31002
31416
  const readDb = openDatabaseReadOnly2();
31003
31417
  const mainSize = statSync11(DB_PATH).size;
31004
31418
  const walPath = DB_PATH + "-wal";
31005
- const walSize = existsSync41(walPath) ? statSync11(walPath).size : 0;
31419
+ const walSize = existsSync42(walPath) ? statSync11(walPath).size : 0;
31006
31420
  const tableNames = readDb.prepare(
31007
31421
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
31008
31422
  ).all();
@@ -31036,7 +31450,7 @@ async function dbPath(globalOpts) {
31036
31450
  output({ path: DB_PATH }, (d) => d.path);
31037
31451
  }
31038
31452
  async function dbBackup(globalOpts, destPath) {
31039
- if (!existsSync41(DB_PATH)) {
31453
+ if (!existsSync42(DB_PATH)) {
31040
31454
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
31041
31455
  process.exit(1);
31042
31456
  }
@@ -31045,7 +31459,7 @@ async function dbBackup(globalOpts, destPath) {
31045
31459
  mkdirSync16(dirname7(dest), { recursive: true });
31046
31460
  copyFileSync3(DB_PATH, dest);
31047
31461
  const walPath = DB_PATH + "-wal";
31048
- if (existsSync41(walPath)) copyFileSync3(walPath, dest + "-wal");
31462
+ if (existsSync42(walPath)) copyFileSync3(walPath, dest + "-wal");
31049
31463
  output({ path: dest, sizeBytes: statSync11(dest).size }, (d) => {
31050
31464
  const b = d;
31051
31465
  return `
@@ -31074,9 +31488,9 @@ __export(usage_exports, {
31074
31488
  usageCost: () => usageCost,
31075
31489
  usageTokens: () => usageTokens
31076
31490
  });
31077
- import { existsSync as existsSync42 } from "fs";
31491
+ import { existsSync as existsSync43 } from "fs";
31078
31492
  function ensureDb() {
31079
- if (!existsSync42(DB_PATH)) {
31493
+ if (!existsSync43(DB_PATH)) {
31080
31494
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31081
31495
  process.exit(1);
31082
31496
  }
@@ -31266,9 +31680,9 @@ __export(config_exports2, {
31266
31680
  configList: () => configList,
31267
31681
  configSet: () => configSet
31268
31682
  });
31269
- import { existsSync as existsSync43, readFileSync as readFileSync25 } from "fs";
31683
+ import { existsSync as existsSync44, readFileSync as readFileSync26 } from "fs";
31270
31684
  async function configList(globalOpts) {
31271
- if (!existsSync43(DB_PATH)) {
31685
+ if (!existsSync44(DB_PATH)) {
31272
31686
  outputError("DB_NOT_FOUND", "Database not found.");
31273
31687
  process.exit(1);
31274
31688
  }
@@ -31302,7 +31716,7 @@ async function configGet(globalOpts, key) {
31302
31716
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
31303
31717
  process.exit(1);
31304
31718
  }
31305
- if (!existsSync43(DB_PATH)) {
31719
+ if (!existsSync44(DB_PATH)) {
31306
31720
  outputError("DB_NOT_FOUND", "Database not found.");
31307
31721
  process.exit(1);
31308
31722
  }
@@ -31348,11 +31762,11 @@ async function configSet(globalOpts, key, value) {
31348
31762
  }
31349
31763
  }
31350
31764
  async function configEnv(_globalOpts) {
31351
- if (!existsSync43(ENV_PATH)) {
31765
+ if (!existsSync44(ENV_PATH)) {
31352
31766
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
31353
31767
  process.exit(1);
31354
31768
  }
31355
- const content = readFileSync25(ENV_PATH, "utf-8");
31769
+ const content = readFileSync26(ENV_PATH, "utf-8");
31356
31770
  const entries = {};
31357
31771
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
31358
31772
  for (const line of content.split("\n")) {
@@ -31402,9 +31816,9 @@ __export(session_exports, {
31402
31816
  sessionGet: () => sessionGet,
31403
31817
  sessionNew: () => sessionNew
31404
31818
  });
31405
- import { existsSync as existsSync44 } from "fs";
31819
+ import { existsSync as existsSync45 } from "fs";
31406
31820
  async function sessionGet(globalOpts) {
31407
- if (!existsSync44(DB_PATH)) {
31821
+ if (!existsSync45(DB_PATH)) {
31408
31822
  outputError("DB_NOT_FOUND", "Database not found.");
31409
31823
  process.exit(1);
31410
31824
  }
@@ -31465,9 +31879,9 @@ __export(permissions_exports, {
31465
31879
  verboseGet: () => verboseGet,
31466
31880
  verboseSet: () => verboseSet
31467
31881
  });
31468
- import { existsSync as existsSync45 } from "fs";
31882
+ import { existsSync as existsSync46 } from "fs";
31469
31883
  function ensureDb2() {
31470
- if (!existsSync45(DB_PATH)) {
31884
+ if (!existsSync46(DB_PATH)) {
31471
31885
  outputError("DB_NOT_FOUND", "Database not found.");
31472
31886
  process.exit(1);
31473
31887
  }
@@ -31614,9 +32028,9 @@ __export(cwd_exports, {
31614
32028
  cwdGet: () => cwdGet,
31615
32029
  cwdSet: () => cwdSet
31616
32030
  });
31617
- import { existsSync as existsSync46 } from "fs";
32031
+ import { existsSync as existsSync47 } from "fs";
31618
32032
  async function cwdGet(globalOpts) {
31619
- if (!existsSync46(DB_PATH)) {
32033
+ if (!existsSync47(DB_PATH)) {
31620
32034
  outputError("DB_NOT_FOUND", "Database not found.");
31621
32035
  process.exit(1);
31622
32036
  }
@@ -31678,9 +32092,9 @@ __export(voice_exports, {
31678
32092
  voiceGet: () => voiceGet,
31679
32093
  voiceSet: () => voiceSet
31680
32094
  });
31681
- import { existsSync as existsSync47 } from "fs";
32095
+ import { existsSync as existsSync48 } from "fs";
31682
32096
  async function voiceGet(globalOpts) {
31683
- if (!existsSync47(DB_PATH)) {
32097
+ if (!existsSync48(DB_PATH)) {
31684
32098
  outputError("DB_NOT_FOUND", "Database not found.");
31685
32099
  process.exit(1);
31686
32100
  }
@@ -31729,9 +32143,9 @@ __export(heartbeat_exports, {
31729
32143
  heartbeatGet: () => heartbeatGet,
31730
32144
  heartbeatSet: () => heartbeatSet
31731
32145
  });
31732
- import { existsSync as existsSync48 } from "fs";
32146
+ import { existsSync as existsSync49 } from "fs";
31733
32147
  async function heartbeatGet(globalOpts) {
31734
- if (!existsSync48(DB_PATH)) {
32148
+ if (!existsSync49(DB_PATH)) {
31735
32149
  outputError("DB_NOT_FOUND", "Database not found.");
31736
32150
  process.exit(1);
31737
32151
  }
@@ -31746,8 +32160,25 @@ async function heartbeatGet(globalOpts) {
31746
32160
  intervalMinutes: row.interval_ms / 6e4,
31747
32161
  activeStart: row.active_start,
31748
32162
  activeEnd: row.active_end,
31749
- lastBeatAt: row.last_beat_at
31750
- } : { enabled: false, intervalMs: 18e5, intervalMinutes: 30, activeStart: "08:00", activeEnd: "22:00", lastBeatAt: null };
32163
+ lastBeatAt: row.last_beat_at,
32164
+ backend: row.backend ?? null,
32165
+ model: row.model ?? null,
32166
+ fallbacks: row.fallbacks ?? null,
32167
+ thinking: row.thinking ?? null,
32168
+ target: row.target ?? null
32169
+ } : {
32170
+ enabled: false,
32171
+ intervalMs: 18e5,
32172
+ intervalMinutes: 30,
32173
+ activeStart: "08:00",
32174
+ activeEnd: "22:00",
32175
+ lastBeatAt: null,
32176
+ backend: null,
32177
+ model: null,
32178
+ fallbacks: null,
32179
+ thinking: null,
32180
+ target: null
32181
+ };
31751
32182
  output(data, (d) => {
31752
32183
  const h = d;
31753
32184
  const lines = [
@@ -31757,9 +32188,30 @@ async function heartbeatGet(globalOpts) {
31757
32188
  kvLine("Status", h.enabled ? success("on") : muted("off")),
31758
32189
  kvLine("Interval", `${h.intervalMinutes}m`),
31759
32190
  kvLine("Active hours", `${h.activeStart} - ${h.activeEnd}`),
31760
- kvLine("Last beat", h.lastBeatAt ?? muted("never")),
31761
- ""
32191
+ kvLine("Backend", h.backend ?? muted("default")),
32192
+ kvLine("Model", h.model ?? muted("default")),
32193
+ kvLine("Thinking", h.thinking ?? muted("off"))
31762
32194
  ];
32195
+ if (h.fallbacks) {
32196
+ try {
32197
+ const parsed = JSON.parse(h.fallbacks);
32198
+ const chain = parsed.map((f) => `${f.backend}${f.model ? `:${f.model}` : ""}`).join(" \u2192 ");
32199
+ lines.push(kvLine("Fallbacks", chain));
32200
+ } catch {
32201
+ lines.push(kvLine("Fallbacks", h.fallbacks));
32202
+ }
32203
+ } else {
32204
+ lines.push(kvLine("Fallbacks", muted("none")));
32205
+ }
32206
+ if (h.target) {
32207
+ lines.push(kvLine("Target", h.target));
32208
+ }
32209
+ lines.push(kvLine("Last beat", h.lastBeatAt ?? muted("never")));
32210
+ if (!h.enabled) {
32211
+ lines.push("");
32212
+ lines.push(` ${warning("\u26A0 Heartbeat is disabled \u2014 you won't receive proactive alerts.")}`);
32213
+ }
32214
+ lines.push("");
31763
32215
  return lines.join("\n");
31764
32216
  });
31765
32217
  }
@@ -31781,7 +32233,7 @@ async function heartbeatSet(globalOpts, subcommand, value) {
31781
32233
  body.enabled = false;
31782
32234
  successMsg = "Heartbeat disabled.";
31783
32235
  break;
31784
- case "interval":
32236
+ case "interval": {
31785
32237
  if (!value) {
31786
32238
  outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set interval <value>");
31787
32239
  process.exit(1);
@@ -31797,7 +32249,8 @@ async function heartbeatSet(globalOpts, subcommand, value) {
31797
32249
  body.intervalMs = ms;
31798
32250
  successMsg = `Heartbeat interval: ${ms / 6e4}m`;
31799
32251
  break;
31800
- case "hours":
32252
+ }
32253
+ case "hours": {
31801
32254
  if (!value) {
31802
32255
  outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set hours <start>-<end>");
31803
32256
  process.exit(1);
@@ -31811,8 +32264,71 @@ async function heartbeatSet(globalOpts, subcommand, value) {
31811
32264
  body.activeEnd = `${hourMatch[2].padStart(2, "0")}:00`;
31812
32265
  successMsg = `Active hours: ${body.activeStart} - ${body.activeEnd}`;
31813
32266
  break;
32267
+ }
32268
+ case "backend": {
32269
+ if (!value) {
32270
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set backend <name|default>");
32271
+ process.exit(1);
32272
+ }
32273
+ if (value === "default") {
32274
+ body.backend = null;
32275
+ body.model = null;
32276
+ successMsg = "Heartbeat backend: default (chat-level)";
32277
+ } else {
32278
+ body.backend = value;
32279
+ body.model = null;
32280
+ successMsg = `Heartbeat backend: ${value}`;
32281
+ }
32282
+ break;
32283
+ }
32284
+ case "model": {
32285
+ if (!value) {
32286
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set model <name|default>");
32287
+ process.exit(1);
32288
+ }
32289
+ if (value === "default") {
32290
+ body.model = null;
32291
+ successMsg = "Heartbeat model: default";
32292
+ } else {
32293
+ body.model = value;
32294
+ successMsg = `Heartbeat model: ${value}`;
32295
+ }
32296
+ break;
32297
+ }
32298
+ case "thinking": {
32299
+ if (!value) {
32300
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set thinking <off|low|medium|high>");
32301
+ process.exit(1);
32302
+ }
32303
+ const valid = ["off", "low", "medium", "high"];
32304
+ if (!valid.includes(value)) {
32305
+ outputError("INVALID_THINKING", `Valid values: ${valid.join(", ")}`);
32306
+ process.exit(1);
32307
+ }
32308
+ body.thinking = value === "off" ? null : value;
32309
+ successMsg = `Heartbeat thinking: ${value}`;
32310
+ break;
32311
+ }
32312
+ case "fallbacks": {
32313
+ if (!value) {
32314
+ outputError("MISSING_VALUE", "Usage: cc-claw heartbeat set fallbacks <backend1,backend2|clear>");
32315
+ process.exit(1);
32316
+ }
32317
+ if (value === "clear" || value === "none") {
32318
+ body.fallbacks = null;
32319
+ successMsg = "Heartbeat fallbacks: cleared";
32320
+ } else {
32321
+ const chain = value.split(",").map((s) => {
32322
+ const [backend2, model2] = s.trim().split(":");
32323
+ return model2 ? { backend: backend2, model: model2 } : { backend: backend2 };
32324
+ });
32325
+ body.fallbacks = JSON.stringify(chain);
32326
+ successMsg = `Heartbeat fallbacks: ${chain.map((f) => f.backend).join(" \u2192 ")}`;
32327
+ }
32328
+ break;
32329
+ }
31814
32330
  default:
31815
- outputError("UNKNOWN_SUBCOMMAND", `Unknown: "${subcommand}". Use: on, off, interval, hours`);
32331
+ outputError("UNKNOWN_SUBCOMMAND", `Unknown: "${subcommand}". Use: on, off, interval, hours, backend, model, thinking, fallbacks`);
31816
32332
  process.exit(1);
31817
32333
  }
31818
32334
  const res = await apiPost2("/api/heartbeat/set", body);
@@ -31840,9 +32356,9 @@ __export(summarizer_exports, {
31840
32356
  summarizerGet: () => summarizerGet,
31841
32357
  summarizerSet: () => summarizerSet
31842
32358
  });
31843
- import { existsSync as existsSync49 } from "fs";
32359
+ import { existsSync as existsSync50 } from "fs";
31844
32360
  async function summarizerGet(globalOpts) {
31845
- if (!existsSync49(DB_PATH)) {
32361
+ if (!existsSync50(DB_PATH)) {
31846
32362
  outputError("DB_NOT_FOUND", "Database not found.");
31847
32363
  process.exit(1);
31848
32364
  }
@@ -31886,9 +32402,9 @@ __export(thinking_exports, {
31886
32402
  thinkingGet: () => thinkingGet,
31887
32403
  thinkingSet: () => thinkingSet
31888
32404
  });
31889
- import { existsSync as existsSync50 } from "fs";
32405
+ import { existsSync as existsSync51 } from "fs";
31890
32406
  async function thinkingGet(globalOpts) {
31891
- if (!existsSync50(DB_PATH)) {
32407
+ if (!existsSync51(DB_PATH)) {
31892
32408
  outputError("DB_NOT_FOUND", "Database not found.");
31893
32409
  process.exit(1);
31894
32410
  }
@@ -31932,9 +32448,9 @@ __export(chats_exports, {
31932
32448
  chatsList: () => chatsList,
31933
32449
  chatsRemoveAlias: () => chatsRemoveAlias
31934
32450
  });
31935
- import { existsSync as existsSync51 } from "fs";
32451
+ import { existsSync as existsSync52 } from "fs";
31936
32452
  async function chatsList(_globalOpts) {
31937
- if (!existsSync51(DB_PATH)) {
32453
+ if (!existsSync52(DB_PATH)) {
31938
32454
  outputError("DB_NOT_FOUND", "Database not found.");
31939
32455
  process.exit(1);
31940
32456
  }
@@ -32062,9 +32578,9 @@ var mcps_exports2 = {};
32062
32578
  __export(mcps_exports2, {
32063
32579
  mcpsList: () => mcpsList
32064
32580
  });
32065
- import { existsSync as existsSync52 } from "fs";
32581
+ import { existsSync as existsSync53 } from "fs";
32066
32582
  async function mcpsList(_globalOpts) {
32067
- if (!existsSync52(DB_PATH)) {
32583
+ if (!existsSync53(DB_PATH)) {
32068
32584
  outputError("DB_NOT_FOUND", "Database not found.");
32069
32585
  process.exit(1);
32070
32586
  }
@@ -32101,11 +32617,11 @@ __export(chat_exports2, {
32101
32617
  chatSend: () => chatSend
32102
32618
  });
32103
32619
  import { request as httpRequest2 } from "http";
32104
- import { readFileSync as readFileSync26, existsSync as existsSync53 } from "fs";
32620
+ import { readFileSync as readFileSync27, existsSync as existsSync54 } from "fs";
32105
32621
  function getToken2() {
32106
32622
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
32107
32623
  try {
32108
- if (existsSync53(TOKEN_PATH2)) return readFileSync26(TOKEN_PATH2, "utf-8").trim();
32624
+ if (existsSync54(TOKEN_PATH2)) return readFileSync27(TOKEN_PATH2, "utf-8").trim();
32109
32625
  } catch {
32110
32626
  }
32111
32627
  return null;
@@ -32785,9 +33301,9 @@ __export(evolve_exports2, {
32785
33301
  evolveStatus: () => evolveStatus,
32786
33302
  evolveUndo: () => evolveUndo
32787
33303
  });
32788
- import { existsSync as existsSync54 } from "fs";
33304
+ import { existsSync as existsSync55 } from "fs";
32789
33305
  function ensureDb3() {
32790
- if (!existsSync54(DB_PATH)) {
33306
+ if (!existsSync55(DB_PATH)) {
32791
33307
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32792
33308
  process.exit(1);
32793
33309
  }
@@ -33261,8 +33777,8 @@ var init_optimize2 = __esm({
33261
33777
 
33262
33778
  // src/setup.ts
33263
33779
  var setup_exports = {};
33264
- import { existsSync as existsSync55, writeFileSync as writeFileSync12, readFileSync as readFileSync27, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
33265
- import { execFileSync as execFileSync5 } from "child_process";
33780
+ import { existsSync as existsSync56, writeFileSync as writeFileSync12, readFileSync as readFileSync28, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
33781
+ import { execFileSync as execFileSync6 } from "child_process";
33266
33782
  import { createInterface as createInterface11 } from "readline";
33267
33783
  import { join as join35 } from "path";
33268
33784
  function divider2() {
@@ -33326,7 +33842,7 @@ async function setup() {
33326
33842
  let foundAnyBackend = false;
33327
33843
  for (const bk of backends) {
33328
33844
  try {
33329
- const path = execFileSync5("which", [bk.cmd], { encoding: "utf-8" }).trim();
33845
+ const path = execFileSync6("which", [bk.cmd], { encoding: "utf-8" }).trim();
33330
33846
  console.log(green(` ${bk.name} CLI found: ${path}`));
33331
33847
  foundAnyBackend = true;
33332
33848
  } catch {
@@ -33339,21 +33855,21 @@ async function setup() {
33339
33855
  }
33340
33856
  console.log("");
33341
33857
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
33342
- if (!existsSync55(dir)) mkdirSync18(dir, { recursive: true });
33858
+ if (!existsSync56(dir)) mkdirSync18(dir, { recursive: true });
33343
33859
  }
33344
33860
  const env = {};
33345
- const envSource = existsSync55(ENV_PATH) ? ENV_PATH : existsSync55(".env") ? ".env" : null;
33861
+ const envSource = existsSync56(ENV_PATH) ? ENV_PATH : existsSync56(".env") ? ".env" : null;
33346
33862
  if (envSource) {
33347
33863
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
33348
33864
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
33349
- const existing = readFileSync27(envSource, "utf-8");
33865
+ const existing = readFileSync28(envSource, "utf-8");
33350
33866
  for (const line of existing.split("\n")) {
33351
33867
  const match = line.match(/^([^#=]+)=(.*)$/);
33352
33868
  if (match) env[match[1].trim()] = match[2].trim();
33353
33869
  }
33354
33870
  }
33355
33871
  const cwdDb = join35(process.cwd(), "cc-claw.db");
33356
- if (existsSync55(cwdDb) && !existsSync55(DB_PATH)) {
33872
+ if (existsSync56(cwdDb) && !existsSync56(DB_PATH)) {
33357
33873
  const { size } = statSync12(cwdDb);
33358
33874
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
33359
33875
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
@@ -34073,7 +34589,7 @@ heartbeat.command("get").description("Show heartbeat status and config").action(
34073
34589
  const { heartbeatGet: heartbeatGet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports));
34074
34590
  await heartbeatGet2(program.opts());
34075
34591
  });
34076
- heartbeat.command("set <subcommand> [value]").description("Configure heartbeat (on/off/interval <val>/hours <range>)").action(async (subcommand, value) => {
34592
+ heartbeat.command("set <subcommand> [value]").description("Configure heartbeat (on/off/interval/hours/backend/model/thinking/fallbacks)").action(async (subcommand, value) => {
34077
34593
  const { heartbeatSet: heartbeatSet2 } = await Promise.resolve().then(() => (init_heartbeat3(), heartbeat_exports));
34078
34594
  await heartbeatSet2(program.opts(), subcommand, value);
34079
34595
  });
@@ -34246,8 +34762,8 @@ async function run(argv = process.argv) {
34246
34762
  if (argv.includes("--version") || argv.includes("-V")) {
34247
34763
  console.log(VERSION);
34248
34764
  try {
34249
- const { execFileSync: execFileSync6 } = await import("child_process");
34250
- const latest = execFileSync6("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
34765
+ const { execFileSync: execFileSync7 } = await import("child_process");
34766
+ const latest = execFileSync7("npm", ["view", "cc-claw", "version"], { encoding: "utf-8", timeout: 1e4 }).trim();
34251
34767
  if (latest && latest !== VERSION) {
34252
34768
  console.log(`
34253
34769
  Update available: v${latest} (current: v${VERSION})`);