cc-claw 0.20.20 → 0.21.0

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 +837 -522
  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.20" : (() => {
36
+ VERSION = true ? "0.21.0" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -59,6 +59,7 @@ __export(paths_exports, {
59
59
  RUNNERS_PATH: () => RUNNERS_PATH,
60
60
  SESSION_LOGS_PATH: () => SESSION_LOGS_PATH,
61
61
  SKILLS_PATH: () => SKILLS_PATH,
62
+ WHISPER_MODELS_PATH: () => WHISPER_MODELS_PATH,
62
63
  WORKSPACE_PATH: () => WORKSPACE_PATH
63
64
  });
64
65
  import { homedir, userInfo } from "os";
@@ -71,7 +72,7 @@ function resolveRealHome() {
71
72
  }
72
73
  return homedir();
73
74
  }
74
- var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, IDENTITY_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH, SESSION_LOGS_PATH, MEDIA_PATH;
75
+ var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, IDENTITY_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH, SESSION_LOGS_PATH, MEDIA_PATH, WHISPER_MODELS_PATH;
75
76
  var init_paths = __esm({
76
77
  "src/paths.ts"() {
77
78
  "use strict";
@@ -89,6 +90,7 @@ var init_paths = __esm({
89
90
  AGENTS_PATH = join2(CC_CLAW_HOME, "agents");
90
91
  SESSION_LOGS_PATH = join2(LOGS_PATH, "sessions");
91
92
  MEDIA_PATH = join2(CC_CLAW_HOME, "media");
93
+ WHISPER_MODELS_PATH = join2(DATA_PATH, "whisper-models");
92
94
  }
93
95
  });
94
96
 
@@ -1592,6 +1594,14 @@ function initSchema(db3) {
1592
1594
  db3.exec(`ALTER TABLE chat_voice ADD COLUMN voice_id TEXT`);
1593
1595
  } catch {
1594
1596
  }
1597
+ try {
1598
+ db3.exec(`ALTER TABLE chat_voice ADD COLUMN stt_provider TEXT DEFAULT 'groq'`);
1599
+ } catch {
1600
+ }
1601
+ try {
1602
+ db3.exec(`ALTER TABLE chat_voice ADD COLUMN stt_model TEXT DEFAULT 'small.en'`);
1603
+ } catch {
1604
+ }
1595
1605
  db3.exec(`
1596
1606
  CREATE TABLE IF NOT EXISTS backend_limits (
1597
1607
  backend TEXT NOT NULL,
@@ -4307,7 +4317,7 @@ var init_resolve_executable = __esm({
4307
4317
  });
4308
4318
 
4309
4319
  // src/backends/claude.ts
4310
- import { existsSync as existsSync3 } from "fs";
4320
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4311
4321
  import { spawnSync as spawnSync2 } from "child_process";
4312
4322
  import { join as join4 } from "path";
4313
4323
  var ADAPTIVE_MODELS, ClaudeAdapter;
@@ -4433,6 +4443,13 @@ var init_claude = __esm({
4433
4443
  } else if (slot.slotType === "oauth" && slot.configHome) {
4434
4444
  env.HOME = slot.configHome;
4435
4445
  delete env.ANTHROPIC_API_KEY;
4446
+ try {
4447
+ const credsPath = join4(slot.configHome, ".claude", ".credentials.json");
4448
+ const creds = JSON.parse(readFileSync2(credsPath, "utf-8"));
4449
+ const token = creds?.claudeAiOauth?.accessToken;
4450
+ if (token) env.CLAUDE_CODE_OAUTH_TOKEN = token;
4451
+ } catch {
4452
+ }
4436
4453
  }
4437
4454
  return env;
4438
4455
  }
@@ -6645,7 +6662,11 @@ function djb2Hash(str) {
6645
6662
  return hash.toString(36);
6646
6663
  }
6647
6664
  function extractKeyField(input) {
6648
- const key = input.file_path ?? input.path ?? input.file ?? input.command ?? input.cmd ?? input.url ?? input.query ?? input.search_query ?? input.pattern ?? input.content;
6665
+ if (input.pattern !== void 0) {
6666
+ const loc = input.path ?? input.file ?? "";
6667
+ return `${input.pattern}@${loc}`;
6668
+ }
6669
+ const key = input.file_path ?? input.path ?? input.file ?? input.command ?? input.cmd ?? input.url ?? input.query ?? input.search_query ?? input.content;
6649
6670
  if (key !== void 0 && key !== null) {
6650
6671
  return String(key);
6651
6672
  }
@@ -7039,7 +7060,7 @@ import {
7039
7060
  existsSync as existsSync7,
7040
7061
  writeFileSync,
7041
7062
  mkdirSync as mkdirSync3,
7042
- readFileSync as readFileSync2,
7063
+ readFileSync as readFileSync3,
7043
7064
  statSync as statSync3,
7044
7065
  copyFileSync as copyFileSync2,
7045
7066
  unlinkSync as unlinkSync2
@@ -7049,7 +7070,7 @@ function migrateFile(legacyPath, newPath, label2) {
7049
7070
  if (existsSync7(newPath)) return false;
7050
7071
  if (existsSync7(legacyPath)) {
7051
7072
  copyFileSync2(legacyPath, newPath);
7052
- const copied = readFileSync2(newPath, "utf-8");
7073
+ const copied = readFileSync3(newPath, "utf-8");
7053
7074
  if (copied.length > 0) {
7054
7075
  unlinkSync2(legacyPath);
7055
7076
  log(`[bootstrap] Migrated ${label2} from workspace/ to identity/`);
@@ -7132,18 +7153,18 @@ function syncNativeCliFiles() {
7132
7153
  let soul = "";
7133
7154
  let user = "";
7134
7155
  try {
7135
- soul = readFileSync2(SOUL_PATH, "utf-8").trim();
7156
+ soul = readFileSync3(SOUL_PATH, "utf-8").trim();
7136
7157
  } catch {
7137
7158
  try {
7138
- soul = readFileSync2(LEGACY_SOUL_PATH, "utf-8").trim();
7159
+ soul = readFileSync3(LEGACY_SOUL_PATH, "utf-8").trim();
7139
7160
  } catch {
7140
7161
  }
7141
7162
  }
7142
7163
  try {
7143
- user = readFileSync2(USER_PATH, "utf-8").trim();
7164
+ user = readFileSync3(USER_PATH, "utf-8").trim();
7144
7165
  } catch {
7145
7166
  try {
7146
- user = readFileSync2(LEGACY_USER_PATH, "utf-8").trim();
7167
+ user = readFileSync3(LEGACY_USER_PATH, "utf-8").trim();
7147
7168
  } catch {
7148
7169
  }
7149
7170
  }
@@ -7241,7 +7262,7 @@ var init_init = __esm({
7241
7262
  });
7242
7263
 
7243
7264
  // src/bootstrap/loader.ts
7244
- import { readFileSync as readFileSync3, existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
7265
+ import { readFileSync as readFileSync4, existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
7245
7266
  import { join as join8 } from "path";
7246
7267
  function loadContextFiles() {
7247
7268
  if (contextCache && Date.now() - contextCache.timestamp < CONTEXT_CACHE_TTL_MS) {
@@ -7252,7 +7273,7 @@ function loadContextFiles() {
7252
7273
  const entries = readdirSync3(CONTEXT_DIR2).filter((f) => f.endsWith(".md"));
7253
7274
  for (const entry of entries) {
7254
7275
  try {
7255
- const content = readFileSync3(join8(CONTEXT_DIR2, entry), "utf-8").trim();
7276
+ const content = readFileSync4(join8(CONTEXT_DIR2, entry), "utf-8").trim();
7256
7277
  if (content) files.set(entry, content);
7257
7278
  } catch {
7258
7279
  continue;
@@ -8800,8 +8821,7 @@ var init_helpers = __esm({
8800
8821
  { cmd: "/runners", desc: "List CLI runners" }
8801
8822
  ],
8802
8823
  Settings: [
8803
- { cmd: "/voice", desc: "Toggle voice responses" },
8804
- { cmd: "/voice_config", desc: "Configure voice provider" },
8824
+ { cmd: "/voice", desc: "Voice settings (STT transcription + TTS replies)" },
8805
8825
  { cmd: "/cwd", desc: "Set working directory" },
8806
8826
  { cmd: "/tools", desc: "Configure allowed tools" },
8807
8827
  { cmd: "/verbose", desc: "Tool visibility level" },
@@ -8889,7 +8909,7 @@ You have access to cc-claw orchestrator tools via MCP:
8889
8909
  // src/agents/spawn.ts
8890
8910
  import { spawn as spawn2 } from "child_process";
8891
8911
  import { createInterface as createInterface2 } from "readline";
8892
- import { readFileSync as readFileSync4 } from "fs";
8912
+ import { readFileSync as readFileSync5 } from "fs";
8893
8913
  function stripFrontmatter(text) {
8894
8914
  const lines = text.split("\n");
8895
8915
  if (lines.length < 2 || lines[0].trim() !== "---") return text;
@@ -8905,7 +8925,7 @@ function buildAgentPrompt(opts, runnerSkillPath) {
8905
8925
  const parts = [];
8906
8926
  if (runnerSkillPath) {
8907
8927
  try {
8908
- const raw = readFileSync4(runnerSkillPath, "utf-8");
8928
+ const raw = readFileSync5(runnerSkillPath, "utf-8");
8909
8929
  const content = stripFrontmatter(raw).trim();
8910
8930
  if (content) parts.push(content);
8911
8931
  parts.push("");
@@ -9288,7 +9308,7 @@ __export(loader_exports, {
9288
9308
  getTemplate: () => getTemplate,
9289
9309
  listTemplates: () => listTemplates
9290
9310
  });
9291
- import { readdirSync as readdirSync5, readFileSync as readFileSync5 } from "fs";
9311
+ import { readdirSync as readdirSync5, readFileSync as readFileSync6 } from "fs";
9292
9312
  import { join as join10 } from "path";
9293
9313
  function parseFrontmatter(content) {
9294
9314
  const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m);
@@ -9329,7 +9349,7 @@ function scanTemplates() {
9329
9349
  for (const file of files) {
9330
9350
  const filePath = join10(AGENTS_PATH, file);
9331
9351
  try {
9332
- const raw = readFileSync5(filePath, "utf-8");
9352
+ const raw = readFileSync6(filePath, "utf-8");
9333
9353
  const { meta, body } = parseFrontmatter(raw);
9334
9354
  const name = meta.name ?? file.replace(/\.md$/, "");
9335
9355
  const template = {
@@ -11562,7 +11582,7 @@ __export(analyze_exports, {
11562
11582
  });
11563
11583
  import { spawn as spawn4 } from "child_process";
11564
11584
  import { createInterface as createInterface3 } from "readline";
11565
- import { readFileSync as readFileSync6, existsSync as existsSync12, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
11585
+ import { readFileSync as readFileSync7, existsSync as existsSync12, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
11566
11586
  import { join as join12 } from "path";
11567
11587
  import { homedir as homedir4 } from "os";
11568
11588
  function applySignalDecay(confidence, createdAt) {
@@ -11584,7 +11604,7 @@ function discoverReflectionTargets() {
11584
11604
  if (!existsSync12(skillFile)) continue;
11585
11605
  let desc = "skill";
11586
11606
  try {
11587
- const content = readFileSync6(skillFile, "utf-8");
11607
+ const content = readFileSync7(skillFile, "utf-8");
11588
11608
  const descMatch = content.match(/description:\s*["']?([^"'\n]+)/);
11589
11609
  if (descMatch) desc = descMatch[1].trim().slice(0, 80);
11590
11610
  } catch {
@@ -11793,7 +11813,7 @@ function resolveReflectionAdapter(chatId) {
11793
11813
  }
11794
11814
  function readIdentityFile(filename) {
11795
11815
  try {
11796
- return readFileSync6(join12(IDENTITY_PATH, filename), "utf-8");
11816
+ return readFileSync7(join12(IDENTITY_PATH, filename), "utf-8");
11797
11817
  } catch {
11798
11818
  return "";
11799
11819
  }
@@ -11952,7 +11972,7 @@ async function runAnalysisImpl(chatId, opts) {
11952
11972
  try {
11953
11973
  const fullPath = join12(ccClawHome, target.path);
11954
11974
  if (existsSync12(fullPath)) {
11955
- const content = readFileSync6(fullPath, "utf-8");
11975
+ const content = readFileSync7(fullPath, "utf-8");
11956
11976
  if (totalSkillChars + content.length > SKILL_CONTENT_CAP) break;
11957
11977
  skillContents.push({ path: target.path, content });
11958
11978
  totalSkillChars += content.length;
@@ -12332,7 +12352,7 @@ __export(apply_exports, {
12332
12352
  isTargetAllowed: () => isTargetAllowed,
12333
12353
  rollbackInsight: () => rollbackInsight
12334
12354
  });
12335
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync8, unlinkSync as unlinkSync5 } from "fs";
12355
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync8, unlinkSync as unlinkSync5 } from "fs";
12336
12356
  import { join as join13, dirname as dirname3 } from "path";
12337
12357
  function isTargetAllowed(relativePath) {
12338
12358
  if (relativePath.includes("..")) return false;
@@ -12416,7 +12436,7 @@ async function applyInsight(insightId) {
12416
12436
  const absolutePath = join13(CC_CLAW_HOME, insight.targetFile);
12417
12437
  if (insight.proposedAction === "append" && insight.targetFile === "identity/SOUL.md") {
12418
12438
  if (existsSync13(absolutePath)) {
12419
- const currentContent = readFileSync7(absolutePath, "utf-8");
12439
+ const currentContent = readFileSync8(absolutePath, "utf-8");
12420
12440
  const lineCount = currentContent.split("\n").length;
12421
12441
  if (lineCount >= SOUL_LINE_CAP) {
12422
12442
  return {
@@ -12438,7 +12458,7 @@ async function applyInsight(insightId) {
12438
12458
  }
12439
12459
  let original = "";
12440
12460
  if (existsSync13(absolutePath)) {
12441
- original = readFileSync7(absolutePath, "utf-8");
12461
+ original = readFileSync8(absolutePath, "utf-8");
12442
12462
  } else if (insight.proposedAction !== "create") {
12443
12463
  return { success: false, message: `Target file "${insight.targetFile}" does not exist` };
12444
12464
  }
@@ -12578,7 +12598,7 @@ function computeLineDrift(baseline, absolutePath) {
12578
12598
  let current = "";
12579
12599
  try {
12580
12600
  if (existsSync13(absolutePath)) {
12581
- current = readFileSync7(absolutePath, "utf-8");
12601
+ current = readFileSync8(absolutePath, "utf-8");
12582
12602
  }
12583
12603
  } catch {
12584
12604
  return 1;
@@ -12677,12 +12697,12 @@ var init_evolve = __esm({
12677
12697
  const body = JSON.parse(await readBody(req));
12678
12698
  const { setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
12679
12699
  const { existsSync: fileExists, readFileSync: fileRead } = await import("fs");
12680
- const { join: join37 } = await import("path");
12700
+ const { join: join38 } = await import("path");
12681
12701
  const { CC_CLAW_HOME: home } = await Promise.resolve().then(() => (init_paths(), paths_exports));
12682
12702
  const chatId = resolveChatId(body);
12683
12703
  if (!chatId) return jsonResponse(res, { error: "No chatId provided and ALLOWED_CHAT_ID is not set" }, 400);
12684
- const soulPath = join37(home, "identity/SOUL.md");
12685
- const userPath = join37(home, "identity/USER.md");
12704
+ const soulPath = join38(home, "identity/SOUL.md");
12705
+ const userPath = join38(home, "identity/USER.md");
12686
12706
  const soul = fileExists(soulPath) ? fileRead(soulPath, "utf-8") : "";
12687
12707
  const user = fileExists(userPath) ? fileRead(userPath, "utf-8") : "";
12688
12708
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
@@ -13658,6 +13678,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
13658
13678
  let sawToolEvents = false;
13659
13679
  let sawResultEvent = false;
13660
13680
  let toolTurnCount = 0;
13681
+ let loopKillReason;
13661
13682
  const loopDetector = new ToolLoopDetector();
13662
13683
  const t0 = Date.now();
13663
13684
  const elapsed = () => `${((Date.now() - t0) / 1e3).toFixed(1)}s`;
@@ -13694,6 +13715,12 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
13694
13715
  if (contentSilenceTimer) clearTimeout(contentSilenceTimer);
13695
13716
  contentSilenceTimer = setTimeout(() => {
13696
13717
  if (cancelState.cancelled || timedOut) return;
13718
+ if (pendingTools.size > 0) {
13719
+ const tools2 = Array.from(pendingTools.values()).map((t) => typeof t === "string" ? t : t.name).join(", ");
13720
+ log(`[agent] Content silence timer fired but ${pendingTools.size} tool(s) still running (${tools2}) \u2014 resetting`);
13721
+ resetContentSilenceTimer();
13722
+ return;
13723
+ }
13697
13724
  warn(`[agent] Content silence timeout after ${silenceTimeoutMs / 1e3}s for ${adapter.id} \u2014 no content events, killing`);
13698
13725
  timedOut = true;
13699
13726
  timeoutState.contentSilence = true;
@@ -13772,6 +13799,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
13772
13799
  const check = loopDetector.addCall(ev.toolName, ev.toolInput ?? {});
13773
13800
  if (check.isLoop) {
13774
13801
  warn(`[agent] Loop detected for ${adapter.id}: ${check.reason} \u2014 stopping`);
13802
+ loopKillReason = check.reason;
13775
13803
  killProcessGroup(proc, "SIGTERM");
13776
13804
  }
13777
13805
  }
@@ -13896,8 +13924,12 @@ Partial output: ${accumulatedText.slice(-500)}`;
13896
13924
  return;
13897
13925
  }
13898
13926
  if (code && code !== 0 && !cancelState.cancelled && !resultText) {
13899
- const stderr = Buffer.concat(stderrChunks).toString().trim();
13900
- reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
13927
+ if (code === 143 && loopKillReason) {
13928
+ reject(new Error(`Stopped: agent was repeating the same action (${loopKillReason}). Try rephrasing your request.`));
13929
+ } else {
13930
+ const stderr = Buffer.concat(stderrChunks).toString().trim();
13931
+ reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
13932
+ }
13901
13933
  return;
13902
13934
  }
13903
13935
  const cleanedResult = stripThinkingContent(resultText || accumulatedText);
@@ -14448,7 +14480,7 @@ var init_agent = __esm({
14448
14480
  chatLocks = /* @__PURE__ */ new Map();
14449
14481
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
14450
14482
  FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
14451
- CONTENT_SILENCE_TIMEOUT_MS = parseInt(process.env.CONTENT_SILENCE_TIMEOUT_MS ?? "90000", 10);
14483
+ CONTENT_SILENCE_TIMEOUT_MS = parseInt(process.env.CONTENT_SILENCE_TIMEOUT_MS ?? "180000", 10);
14452
14484
  CONTENT_SILENCE_TIMEOUT_ERROR = "CONTENT_SILENCE_TIMEOUT";
14453
14485
  FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
14454
14486
  FREE_SLOTS_EXHAUSTED = "FREE_SLOTS_EXHAUSTED";
@@ -14970,15 +15002,17 @@ var init_telegram_throttle = __esm({
14970
15002
  * Used for cosmetic calls (typing indicators, reactions) that should count toward
14971
15003
  * rate limits but must never queue up or amplify 429 spirals.
14972
15004
  */
14973
- async tryBestEffort(chatId, label2, fn) {
15005
+ async tryBestEffort(chatId, label2, fn, opts) {
14974
15006
  if (this.isPaused()) return void 0;
14975
15007
  if (this.queue.length > 10) return void 0;
14976
- const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
14977
- if (Date.now() - lastChat < PER_CHAT_INTERVAL_MS) return void 0;
14978
- if (Date.now() - this.lastGlobalSend < GLOBAL_INTERVAL_MS) return void 0;
15008
+ if (!opts?.skipRecord) {
15009
+ const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
15010
+ if (Date.now() - lastChat < PER_CHAT_INTERVAL_MS) return void 0;
15011
+ if (Date.now() - this.lastGlobalSend < GLOBAL_INTERVAL_MS) return void 0;
15012
+ }
14979
15013
  try {
14980
15014
  const result = await fn();
14981
- this.recordSend(chatId);
15015
+ if (!opts?.skipRecord) this.recordSend(chatId);
14982
15016
  return result;
14983
15017
  } catch (err) {
14984
15018
  if (is429(err)) {
@@ -15120,11 +15154,11 @@ var init_telegram_throttle = __esm({
15120
15154
  });
15121
15155
 
15122
15156
  // src/health/checks.ts
15123
- import { existsSync as existsSync15, statSync as statSync6, readFileSync as readFileSync8 } from "fs";
15157
+ import { existsSync as existsSync15, statSync as statSync6, readFileSync as readFileSync9 } from "fs";
15124
15158
  import { execFileSync, execSync as execSync3 } from "child_process";
15125
15159
  function getRecentErrors() {
15126
15160
  if (!existsSync15(ERROR_LOG_PATH)) return null;
15127
- const logContent = readFileSync8(ERROR_LOG_PATH, "utf-8");
15161
+ const logContent = readFileSync9(ERROR_LOG_PATH, "utf-8");
15128
15162
  const allLines = logContent.split("\n").filter(Boolean).slice(-500);
15129
15163
  const last24h = Date.now() - 864e5;
15130
15164
  const lines = allLines.filter((line) => {
@@ -15353,7 +15387,7 @@ __export(heartbeat_exports, {
15353
15387
  stopHeartbeatForChat: () => stopHeartbeatForChat,
15354
15388
  updateHeartbeatConfig: () => updateHeartbeatConfig
15355
15389
  });
15356
- import { readFileSync as readFileSync9, existsSync as existsSync16 } from "fs";
15390
+ import { readFileSync as readFileSync10, existsSync as existsSync16 } from "fs";
15357
15391
  import { join as join15 } from "path";
15358
15392
  function findHeartbeatJob() {
15359
15393
  try {
@@ -15487,7 +15521,7 @@ ${watchLines.join("\n")}`);
15487
15521
  }
15488
15522
  if (existsSync16(HEARTBEAT_MD_PATH)) {
15489
15523
  try {
15490
- const custom = readFileSync9(HEARTBEAT_MD_PATH, "utf-8").trim();
15524
+ const custom = readFileSync10(HEARTBEAT_MD_PATH, "utf-8").trim();
15491
15525
  if (custom) {
15492
15526
  sections.push(`[Custom checks from HEARTBEAT.md]
15493
15527
  ${custom}`);
@@ -15556,7 +15590,7 @@ var init_heartbeat2 = __esm({
15556
15590
  });
15557
15591
 
15558
15592
  // src/bootstrap/profile.ts
15559
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync17 } from "fs";
15593
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync17 } from "fs";
15560
15594
  import { join as join16 } from "path";
15561
15595
  function hasActiveProfile(chatId) {
15562
15596
  return activeProfiles.has(chatId);
@@ -15687,7 +15721,7 @@ function extractUserUpdates(text) {
15687
15721
  }
15688
15722
  function appendToUserProfile(key, value) {
15689
15723
  if (!existsSync17(USER_PATH2)) return;
15690
- const content = readFileSync10(USER_PATH2, "utf-8");
15724
+ const content = readFileSync11(USER_PATH2, "utf-8");
15691
15725
  const line = `- **${key}**: ${value}`;
15692
15726
  if (content.includes(line)) return;
15693
15727
  const updated = content.trimEnd() + `
@@ -15796,8 +15830,9 @@ async function classifyWithOllama(text) {
15796
15830
  (a, b) => (a.sizeBytes ?? Infinity) - (b.sizeBytes ?? Infinity)
15797
15831
  );
15798
15832
  const model2 = sorted[0].name;
15833
+ const baseUrl = ollamaStore.getBaseUrl(onlineServer);
15799
15834
  const result = await ollamaClient.chat(
15800
- onlineServer.baseUrl,
15835
+ baseUrl,
15801
15836
  model2,
15802
15837
  [{ role: "user", content: LLM_CLASSIFY_PROMPT + text.slice(0, 500) }],
15803
15838
  { timeoutMs: LLM_CLASSIFY_TIMEOUT_MS, maxTokens: 5, temperature: 0 }
@@ -15822,7 +15857,7 @@ async function classifyWithSummarizerCli(text) {
15822
15857
  const model2 = modelName ?? adapter.summarizerModel;
15823
15858
  const { spawn: spawn8 } = await import("child_process");
15824
15859
  const { resolveExecutable: resolveExecutable4 } = await Promise.resolve().then(() => (init_resolve_executable(), resolve_executable_exports));
15825
- const exe = resolveExecutable4(adapter.id);
15860
+ const exe = resolveExecutable4({ envVar: `${adapter.id.toUpperCase()}_EXECUTABLE`, binaryName: adapter.id, candidates: [] });
15826
15861
  if (!exe) return null;
15827
15862
  return new Promise((resolve) => {
15828
15863
  const timeout = setTimeout(() => {
@@ -17695,7 +17730,9 @@ var init_gate = __esm({
17695
17730
  // src/voice/stt.ts
17696
17731
  import crypto from "crypto";
17697
17732
  import { execFile as execFile2, execFileSync as execFileSync2 } from "child_process";
17698
- import { readFile as readFile2, unlink as unlink2 } from "fs/promises";
17733
+ import { readFile as readFile2, unlink as unlink2, mkdir as mkdir2, writeFile } from "fs/promises";
17734
+ import { existsSync as existsSync19 } from "fs";
17735
+ import { join as join18 } from "path";
17699
17736
  import { promisify as promisify2 } from "util";
17700
17737
  function ensureFfmpeg() {
17701
17738
  if (ffmpegAvailable === true) return;
@@ -17740,11 +17777,120 @@ function setVoiceProvider(chatId, provider, voiceId) {
17740
17777
  ON CONFLICT(chat_id) DO UPDATE SET provider = ?, voice_id = ?, enabled = 1
17741
17778
  `).run(chatId, provider, voiceId, provider, voiceId);
17742
17779
  }
17743
- async function transcribeAudio(audioBuffer, mimeType = "audio/ogg") {
17780
+ function getSttProvider(chatId) {
17781
+ const db3 = getDb();
17782
+ const row = db3.prepare("SELECT stt_provider FROM chat_voice WHERE chat_id = ?").get(chatId);
17783
+ return row?.stt_provider ?? "groq";
17784
+ }
17785
+ function setSttProvider(chatId, provider) {
17786
+ const db3 = getDb();
17787
+ db3.prepare(`
17788
+ INSERT INTO chat_voice (chat_id, enabled, stt_provider) VALUES (?, 0, ?)
17789
+ ON CONFLICT(chat_id) DO UPDATE SET stt_provider = ?
17790
+ `).run(chatId, provider, provider);
17791
+ }
17792
+ function getSttModel(chatId) {
17793
+ const db3 = getDb();
17794
+ const row = db3.prepare("SELECT stt_model FROM chat_voice WHERE chat_id = ?").get(chatId);
17795
+ const model2 = row?.stt_model ?? "small.en";
17796
+ return model2 in LOCAL_WHISPER_MODELS ? model2 : "small.en";
17797
+ }
17798
+ function setSttModel(chatId, model2) {
17799
+ const db3 = getDb();
17800
+ db3.prepare(`
17801
+ INSERT INTO chat_voice (chat_id, enabled, stt_model) VALUES (?, 0, ?)
17802
+ ON CONFLICT(chat_id) DO UPDATE SET stt_model = ?
17803
+ `).run(chatId, model2, model2);
17804
+ }
17805
+ function isWhisperCliAvailable() {
17806
+ if (whisperCliAvailableCache !== null) return whisperCliAvailableCache;
17807
+ try {
17808
+ execFileSync2("whisper-cli", ["--help"], { stdio: "ignore" });
17809
+ whisperCliAvailableCache = true;
17810
+ } catch {
17811
+ try {
17812
+ execFileSync2("whisper", ["--help"], { stdio: "ignore" });
17813
+ whisperCliAvailableCache = true;
17814
+ } catch {
17815
+ whisperCliAvailableCache = false;
17816
+ }
17817
+ }
17818
+ return whisperCliAvailableCache;
17819
+ }
17820
+ function getWhisperBin() {
17821
+ try {
17822
+ execFileSync2("whisper-cli", ["--help"], { stdio: "ignore" });
17823
+ return "whisper-cli";
17824
+ } catch {
17825
+ }
17826
+ try {
17827
+ execFileSync2("whisper", ["--help"], { stdio: "ignore" });
17828
+ return "whisper";
17829
+ } catch {
17830
+ }
17831
+ return null;
17832
+ }
17833
+ function whisperModelPath(model2) {
17834
+ return join18(WHISPER_MODELS_PATH, `ggml-${model2}.bin`);
17835
+ }
17836
+ function isWhisperModelDownloaded(model2) {
17837
+ return existsSync19(whisperModelPath(model2));
17838
+ }
17839
+ async function downloadWhisperModel(model2, onProgress) {
17840
+ await mkdir2(WHISPER_MODELS_PATH, { recursive: true });
17841
+ const dest = whisperModelPath(model2);
17842
+ const url = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model2}.bin`;
17843
+ const info = LOCAL_WHISPER_MODELS[model2];
17844
+ onProgress?.(`\u2B07\uFE0F Downloading Whisper model ${model2} (${info.size})...`);
17845
+ log(`[stt] Downloading model ${model2} from ${url}`);
17846
+ const response = await fetch(url);
17847
+ if (!response.ok) throw new Error(`Failed to download model: ${response.status} ${response.statusText}`);
17848
+ const arrayBuffer = await response.arrayBuffer();
17849
+ await writeFile(dest, Buffer.from(arrayBuffer));
17850
+ log(`[stt] Model ${model2} downloaded to ${dest}`);
17851
+ }
17852
+ async function transcribeWithLocalWhisper(audioBuffer, model2, onProgress) {
17853
+ ensureFfmpeg();
17854
+ const bin = getWhisperBin();
17855
+ if (!bin) throw new Error("whisper-cli not found. Install it: brew install whisper-cpp (macOS) or see https://github.com/ggml-org/whisper.cpp");
17856
+ if (!isWhisperModelDownloaded(model2)) {
17857
+ await downloadWhisperModel(model2, onProgress);
17858
+ }
17859
+ const id = crypto.randomUUID();
17860
+ const tmpOgg = `/tmp/cc-claw-stt-${id}.ogg`;
17861
+ const tmpWav = `/tmp/cc-claw-stt-${id}.wav`;
17862
+ try {
17863
+ await writeFile(tmpOgg, audioBuffer);
17864
+ await execFileAsync2("ffmpeg", ["-y", "-i", tmpOgg, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", tmpWav]);
17865
+ const modelFile = whisperModelPath(model2);
17866
+ const result = await execFileAsync2(bin, ["-m", modelFile, "-f", tmpWav, "-nt", "--output-txt", "-of", `/tmp/cc-claw-stt-${id}`]);
17867
+ const txtFile = `/tmp/cc-claw-stt-${id}.txt`;
17868
+ let transcript = "";
17869
+ if (existsSync19(txtFile)) {
17870
+ transcript = (await readFile2(txtFile, "utf-8")).trim();
17871
+ unlink2(txtFile).catch(() => {
17872
+ });
17873
+ } else {
17874
+ transcript = (result.stdout ?? "").trim();
17875
+ }
17876
+ return transcript;
17877
+ } finally {
17878
+ unlink2(tmpOgg).catch(() => {
17879
+ });
17880
+ unlink2(tmpWav).catch(() => {
17881
+ });
17882
+ }
17883
+ }
17884
+ async function transcribeAudio(audioBuffer, chatId, onProgress) {
17885
+ const provider = chatId ? getSttProvider(chatId) : "groq";
17886
+ if (provider === "local-whisper") {
17887
+ const model2 = chatId ? getSttModel(chatId) : "small.en";
17888
+ return await transcribeWithLocalWhisper(audioBuffer, model2, onProgress);
17889
+ }
17744
17890
  const GROQ_API_KEY = process.env.GROQ_API_KEY;
17745
17891
  if (!GROQ_API_KEY) return null;
17746
17892
  const formData = new FormData();
17747
- formData.append("file", new Blob([new Uint8Array(audioBuffer)], { type: mimeType }), "voice.ogg");
17893
+ formData.append("file", new Blob([new Uint8Array(audioBuffer)], { type: "audio/ogg" }), "voice.ogg");
17748
17894
  formData.append("model", "whisper-large-v3");
17749
17895
  formData.append("response_format", "text");
17750
17896
  const response = await fetch("https://api.groq.com/openai/v1/audio/transcriptions", {
@@ -17845,8 +17991,8 @@ async function mp3ToOgg(mp3Buffer) {
17845
17991
  const id = crypto.randomUUID();
17846
17992
  const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
17847
17993
  const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
17848
- const { writeFile: writeFile6 } = await import("fs/promises");
17849
- await writeFile6(tmpMp3, mp3Buffer);
17994
+ const { writeFile: writeFile7 } = await import("fs/promises");
17995
+ await writeFile7(tmpMp3, mp3Buffer);
17850
17996
  await execFileAsync2("ffmpeg", ["-y", "-i", tmpMp3, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
17851
17997
  const oggBuffer = await readFile2(tmpOgg);
17852
17998
  unlink2(tmpMp3).catch((err) => {
@@ -17873,14 +18019,24 @@ async function macOsTts(text, voice2 = "Samantha") {
17873
18019
  });
17874
18020
  return oggBuffer;
17875
18021
  }
17876
- var execFileAsync2, ffmpegAvailable, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
18022
+ var execFileAsync2, ffmpegAvailable, LOCAL_WHISPER_MODELS, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES, whisperCliAvailableCache;
17877
18023
  var init_stt = __esm({
17878
18024
  "src/voice/stt.ts"() {
17879
18025
  "use strict";
17880
18026
  init_log();
17881
18027
  init_store5();
18028
+ init_paths();
17882
18029
  execFileAsync2 = promisify2(execFile2);
17883
18030
  ffmpegAvailable = null;
18031
+ LOCAL_WHISPER_MODELS = {
18032
+ "tiny.en": { size: "75MB", lang: "English only", speed: "~0.3s", quality: "Basic" },
18033
+ "base.en": { size: "150MB", lang: "English only", speed: "~0.8s", quality: "Good" },
18034
+ "small.en": { size: "500MB", lang: "English only", speed: "~1.5s", quality: "Very good \u2B50" },
18035
+ "small": { size: "500MB", lang: "Multilingual", speed: "~2s", quality: "Very good" },
18036
+ "medium.en": { size: "1.5GB", lang: "English only", speed: "~5s", quality: "Excellent" },
18037
+ "medium": { size: "1.5GB", lang: "Multilingual", speed: "~6s", quality: "Excellent" },
18038
+ "large-v3-turbo": { size: "1.5GB", lang: "Multilingual", speed: "~4s", quality: "Best" }
18039
+ };
17884
18040
  ELEVENLABS_VOICES = {
17885
18041
  "21m00Tcm4TlvDq8ikWAM": { name: "Rachel", gender: "F" },
17886
18042
  "EXAVITQu4vr4xnSDxMaL": { name: "Sarah", gender: "F" },
@@ -17896,13 +18052,14 @@ var init_stt = __esm({
17896
18052
  "Samantha": { name: "Samantha", gender: "F" },
17897
18053
  "Albert": { name: "Albert", gender: "M" }
17898
18054
  };
18055
+ whisperCliAvailableCache = null;
17899
18056
  }
17900
18057
  });
17901
18058
 
17902
18059
  // src/media/image-gen.ts
17903
- import { mkdirSync as mkdirSync9, existsSync as existsSync19, unlink as unlink3, readdir as readdir2, stat as stat2 } from "fs";
17904
- import { writeFile } from "fs/promises";
17905
- import { join as join18 } from "path";
18060
+ import { mkdirSync as mkdirSync9, existsSync as existsSync20, unlink as unlink3, readdir as readdir2, stat as stat2 } from "fs";
18061
+ import { writeFile as writeFile2 } from "fs/promises";
18062
+ import { join as join19 } from "path";
17906
18063
  async function generateImage(prompt) {
17907
18064
  const apiKey = process.env.GEMINI_API_KEY;
17908
18065
  if (!apiKey) {
@@ -17949,14 +18106,14 @@ async function generateImage(prompt) {
17949
18106
  if (!imageData) {
17950
18107
  throw new Error(textResponse ?? "Gemini did not generate an image. The prompt may have been filtered.");
17951
18108
  }
17952
- if (!existsSync19(IMAGE_OUTPUT_DIR)) {
18109
+ if (!existsSync20(IMAGE_OUTPUT_DIR)) {
17953
18110
  mkdirSync9(IMAGE_OUTPUT_DIR, { recursive: true });
17954
18111
  }
17955
18112
  const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
17956
18113
  const filename = `img_${Date.now()}.${ext}`;
17957
- const filePath = join18(IMAGE_OUTPUT_DIR, filename);
18114
+ const filePath = join19(IMAGE_OUTPUT_DIR, filename);
17958
18115
  const buffer = Buffer.from(imageData, "base64");
17959
- await writeFile(filePath, buffer);
18116
+ await writeFile2(filePath, buffer);
17960
18117
  log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
17961
18118
  return { filePath, text: textResponse, mimeType };
17962
18119
  }
@@ -17972,7 +18129,7 @@ function cleanupGeneratedImage(filePath) {
17972
18129
  function pruneImageCache() {
17973
18130
  readdir2(IMAGE_OUTPUT_DIR, (err, files) => {
17974
18131
  if (err || !files) return;
17975
- const imageFiles = files.filter((f) => /\.(png|jpg)$/.test(f)).map((f) => join18(IMAGE_OUTPUT_DIR, f));
18132
+ const imageFiles = files.filter((f) => /\.(png|jpg)$/.test(f)).map((f) => join19(IMAGE_OUTPUT_DIR, f));
17976
18133
  if (imageFiles.length === 0) return;
17977
18134
  const now = Date.now();
17978
18135
  let statsPending = imageFiles.length;
@@ -18004,8 +18161,8 @@ var init_image_gen = __esm({
18004
18161
  MAX_GENERATED_IMAGES = 20;
18005
18162
  IMAGE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
18006
18163
  IMAGE_MODEL = "gemini-3.1-flash-image-preview";
18007
- IMAGE_OUTPUT_DIR = join18(
18008
- process.env.CC_CLAW_HOME ?? join18(process.env.HOME ?? "/tmp", ".cc-claw"),
18164
+ IMAGE_OUTPUT_DIR = join19(
18165
+ process.env.CC_CLAW_HOME ?? join19(process.env.HOME ?? "/tmp", ".cc-claw"),
18009
18166
  "data",
18010
18167
  "images"
18011
18168
  );
@@ -18443,22 +18600,22 @@ var init_video = __esm({
18443
18600
  });
18444
18601
 
18445
18602
  // src/router/media.ts
18446
- import { join as join19 } from "path";
18447
- import { mkdir as mkdir2, writeFile as writeFile2, readdir as readdir3, stat as stat3, unlink as unlink4 } from "fs/promises";
18603
+ import { join as join20 } from "path";
18604
+ import { mkdir as mkdir3, writeFile as writeFile3, readdir as readdir3, stat as stat3, unlink as unlink4 } from "fs/promises";
18448
18605
  function getMediaRetentionMs() {
18449
18606
  const hours = parseInt(process.env.MEDIA_RETENTION_HOURS ?? "24", 10);
18450
18607
  return (isNaN(hours) || hours < 1 ? 24 : hours) * 60 * 60 * 1e3;
18451
18608
  }
18452
18609
  async function saveMedia(buffer, prefix, ext) {
18453
- await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
18610
+ await mkdir3(MEDIA_INCOMING_PATH, { recursive: true });
18454
18611
  const filename = `${prefix}-${Date.now()}.${ext}`;
18455
- const fullPath = join19(MEDIA_INCOMING_PATH, filename);
18456
- await writeFile2(fullPath, buffer);
18612
+ const fullPath = join20(MEDIA_INCOMING_PATH, filename);
18613
+ await writeFile3(fullPath, buffer);
18457
18614
  return fullPath;
18458
18615
  }
18459
18616
  async function cleanupOldMedia() {
18460
18617
  try {
18461
- await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
18618
+ await mkdir3(MEDIA_INCOMING_PATH, { recursive: true });
18462
18619
  const retentionMs = getMediaRetentionMs();
18463
18620
  const retentionHours = Math.round(retentionMs / (60 * 60 * 1e3));
18464
18621
  const files = await readdir3(MEDIA_INCOMING_PATH);
@@ -18466,7 +18623,7 @@ async function cleanupOldMedia() {
18466
18623
  let removed = 0;
18467
18624
  for (const file of files) {
18468
18625
  try {
18469
- const filePath = join19(MEDIA_INCOMING_PATH, file);
18626
+ const filePath = join20(MEDIA_INCOMING_PATH, file);
18470
18627
  const s = await stat3(filePath);
18471
18628
  if (now - s.mtimeMs > retentionMs) {
18472
18629
  await unlink4(filePath);
@@ -18488,9 +18645,11 @@ async function handleVoice(msg, channel) {
18488
18645
  return;
18489
18646
  }
18490
18647
  const audioBuffer = await channel.downloadFile(fileName);
18491
- const transcript = await transcribeAudio(audioBuffer);
18648
+ const transcript = await transcribeAudio(audioBuffer, chatId, async (msg2) => {
18649
+ await channel.sendText(chatId, msg2, { parseMode: "plain" });
18650
+ });
18492
18651
  if (!transcript) {
18493
- await channel.sendText(chatId, "Couldn't transcribe the voice message.", { parseMode: "plain" });
18652
+ await channel.sendText(chatId, "Couldn't transcribe the voice message. Make sure a transcription provider is configured via /voice.", { parseMode: "plain" });
18494
18653
  return;
18495
18654
  }
18496
18655
  const vBackendId = getBackend(chatId) ?? "claude";
@@ -18740,7 +18899,7 @@ var init_media = __esm({
18740
18899
  init_helpers();
18741
18900
  init_response();
18742
18901
  init_live_status();
18743
- MEDIA_INCOMING_PATH = join19(MEDIA_PATH, "incoming");
18902
+ MEDIA_INCOMING_PATH = join20(MEDIA_PATH, "incoming");
18744
18903
  }
18745
18904
  });
18746
18905
 
@@ -19052,9 +19211,9 @@ var install_exports = {};
19052
19211
  __export(install_exports, {
19053
19212
  installSkillFromGitHub: () => installSkillFromGitHub
19054
19213
  });
19055
- import { mkdir as mkdir3, readdir as readdir4, readFile as readFile4, cp } from "fs/promises";
19056
- import { existsSync as existsSync20 } from "fs";
19057
- import { join as join20, basename as basename2 } from "path";
19214
+ import { mkdir as mkdir4, readdir as readdir4, readFile as readFile4, cp } from "fs/promises";
19215
+ import { existsSync as existsSync21 } from "fs";
19216
+ import { join as join21, basename as basename2 } from "path";
19058
19217
  import { execSync as execSync4 } from "child_process";
19059
19218
  async function installSkillFromGitHub(urlOrShorthand) {
19060
19219
  let repoUrl;
@@ -19065,36 +19224,36 @@ async function installSkillFromGitHub(urlOrShorthand) {
19065
19224
  }
19066
19225
  repoUrl = parsed.cloneUrl;
19067
19226
  subPath = parsed.subPath;
19068
- const tmpDir = join20("/tmp", `cc-claw-skill-${Date.now()}`);
19227
+ const tmpDir = join21("/tmp", `cc-claw-skill-${Date.now()}`);
19069
19228
  try {
19070
19229
  log(`[skill-install] Cloning ${repoUrl} to ${tmpDir}`);
19071
19230
  execSync4(`git clone --depth 1 ${repoUrl} ${tmpDir}`, {
19072
19231
  stdio: "pipe",
19073
19232
  timeout: 3e4
19074
19233
  });
19075
- if (!existsSync20(join20(tmpDir, ".git"))) {
19234
+ if (!existsSync21(join21(tmpDir, ".git"))) {
19076
19235
  return { success: false, error: "Git clone failed: no .git directory produced" };
19077
19236
  }
19078
- const searchRoot = subPath ? join20(tmpDir, subPath) : tmpDir;
19237
+ const searchRoot = subPath ? join21(tmpDir, subPath) : tmpDir;
19079
19238
  const skillDir = await findSkillDir(searchRoot);
19080
19239
  if (!skillDir) {
19081
19240
  return { success: false, error: "No SKILL.md found in the repository." };
19082
19241
  }
19083
19242
  const skillFolderName = basename2(skillDir);
19084
- const destDir = join20(SKILLS_PATH, skillFolderName);
19085
- if (existsSync20(destDir)) {
19243
+ const destDir = join21(SKILLS_PATH, skillFolderName);
19244
+ if (existsSync21(destDir)) {
19086
19245
  log(`[skill-install] Overwriting existing skill at ${destDir}`);
19087
19246
  }
19088
- await mkdir3(destDir, { recursive: true });
19247
+ await mkdir4(destDir, { recursive: true });
19089
19248
  await cp(skillDir, destDir, { recursive: true });
19090
19249
  let skillName = skillFolderName;
19091
19250
  try {
19092
- const content = await readFile4(join20(destDir, "SKILL.md"), "utf-8");
19251
+ const content = await readFile4(join21(destDir, "SKILL.md"), "utf-8");
19093
19252
  const nameMatch = content.match(/^name:\s*(.+)$/m);
19094
19253
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
19095
19254
  } catch {
19096
19255
  try {
19097
- const content = await readFile4(join20(destDir, "skill.md"), "utf-8");
19256
+ const content = await readFile4(join21(destDir, "skill.md"), "utf-8");
19098
19257
  const nameMatch = content.match(/^name:\s*(.+)$/m);
19099
19258
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
19100
19259
  } catch {
@@ -19129,15 +19288,15 @@ function parseGitHubUrl(input) {
19129
19288
  async function findSkillDir(root) {
19130
19289
  const candidates = ["SKILL.md", "skill.md"];
19131
19290
  for (const c of candidates) {
19132
- if (existsSync20(join20(root, c))) return root;
19291
+ if (existsSync21(join21(root, c))) return root;
19133
19292
  }
19134
19293
  try {
19135
19294
  const entries = await readdir4(root, { withFileTypes: true });
19136
19295
  for (const entry of entries) {
19137
19296
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19138
19297
  for (const c of candidates) {
19139
- if (existsSync20(join20(root, entry.name, c))) {
19140
- return join20(root, entry.name);
19298
+ if (existsSync21(join21(root, entry.name, c))) {
19299
+ return join21(root, entry.name);
19141
19300
  }
19142
19301
  }
19143
19302
  }
@@ -19149,15 +19308,15 @@ async function findSkillDir(root) {
19149
19308
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19150
19309
  let subEntries;
19151
19310
  try {
19152
- subEntries = await readdir4(join20(root, entry.name), { withFileTypes: true });
19311
+ subEntries = await readdir4(join21(root, entry.name), { withFileTypes: true });
19153
19312
  } catch {
19154
19313
  continue;
19155
19314
  }
19156
19315
  for (const sub of subEntries) {
19157
19316
  if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
19158
19317
  for (const c of candidates) {
19159
- if (existsSync20(join20(root, entry.name, sub.name, c))) {
19160
- return join20(root, entry.name, sub.name);
19318
+ if (existsSync21(join21(root, entry.name, sub.name, c))) {
19319
+ return join21(root, entry.name, sub.name);
19161
19320
  }
19162
19321
  }
19163
19322
  }
@@ -19184,7 +19343,7 @@ __export(discover_exports, {
19184
19343
  import { readdir as readdir5, readFile as readFile5 } from "fs/promises";
19185
19344
  import { createHash } from "crypto";
19186
19345
  import { homedir as homedir5 } from "os";
19187
- import { join as join21 } from "path";
19346
+ import { join as join22 } from "path";
19188
19347
  function invalidateSkillCache() {
19189
19348
  cachedSkills = null;
19190
19349
  cacheTimestamp = 0;
@@ -19202,7 +19361,7 @@ async function discoverAllSkills() {
19202
19361
  const rawSkills = [];
19203
19362
  rawSkills.push(...await scanSkillDir(SKILLS_PATH, "cc-claw"));
19204
19363
  for (const backendId of getAllBackendIds()) {
19205
- const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join21(homedir5(), `.${backendId}`, "skills")];
19364
+ const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join22(homedir5(), `.${backendId}`, "skills")];
19206
19365
  for (const dir of dirs) {
19207
19366
  rawSkills.push(...await scanSkillDir(dir, backendId));
19208
19367
  }
@@ -19230,7 +19389,7 @@ async function scanSkillDir(skillsDir, source) {
19230
19389
  let content;
19231
19390
  let resolvedPath;
19232
19391
  for (const candidate of SKILL_FILE_CANDIDATES) {
19233
- const p = join21(skillsDir, entry.name, candidate);
19392
+ const p = join22(skillsDir, entry.name, candidate);
19234
19393
  try {
19235
19394
  content = await readFile5(p, "utf-8");
19236
19395
  resolvedPath = p;
@@ -19324,15 +19483,15 @@ var init_discover = __esm({
19324
19483
  init_backends();
19325
19484
  SKILL_FILE_CANDIDATES = ["SKILL.md", "skill.md"];
19326
19485
  BACKEND_SKILL_DIRS = {
19327
- claude: [join21(homedir5(), ".claude", "skills")],
19328
- gemini: [join21(homedir5(), ".gemini", "skills")],
19486
+ claude: [join22(homedir5(), ".claude", "skills")],
19487
+ gemini: [join22(homedir5(), ".gemini", "skills")],
19329
19488
  codex: [
19330
- join21(homedir5(), ".agents", "skills"),
19331
- join21(homedir5(), ".codex", "skills")
19489
+ join22(homedir5(), ".agents", "skills"),
19490
+ join22(homedir5(), ".codex", "skills")
19332
19491
  ],
19333
19492
  cursor: [
19334
- join21(homedir5(), ".cursor", "skills"),
19335
- join21(homedir5(), ".cursor", "skills-cursor")
19493
+ join22(homedir5(), ".cursor", "skills"),
19494
+ join22(homedir5(), ".cursor", "skills-cursor")
19336
19495
  ]
19337
19496
  };
19338
19497
  CACHE_TTL_MS2 = 3e5;
@@ -19690,43 +19849,76 @@ async function sendVoiceConfigKeyboard(chatId, channel) {
19690
19849
  await channel.sendText(chatId, "Voice configuration requires an interactive channel (Telegram).", { parseMode: "plain" });
19691
19850
  return;
19692
19851
  }
19693
- const config2 = getVoiceConfig(chatId);
19694
- const currentVoiceName = config2.provider === "elevenlabs" ? ELEVENLABS_VOICES[config2.voiceId ?? ""]?.name ?? "Rachel" : config2.provider === "macos" ? MACOS_VOICES[config2.voiceId ?? ""]?.name ?? "Samantha" : config2.voiceId ?? "eve";
19695
- const providerLabel = config2.provider === "grok" ? "Grok (xAI)" : config2.provider === "macos" ? "macOS" : "ElevenLabs";
19696
- const header2 = `\u{1F3A7} Voice Configuration
19697
- Provider: ${providerLabel}
19698
- Voice: ${currentVoiceName}
19699
- Status: ${config2.enabled ? "ON" : "OFF"}`;
19852
+ const ttsConfig = getVoiceConfig(chatId);
19853
+ const sttProvider = getSttProvider(chatId);
19854
+ const sttModel = getSttModel(chatId);
19855
+ const whisperAvailable = isWhisperCliAvailable();
19700
19856
  const buttons = [];
19857
+ const groqAvailable = !!process.env.GROQ_API_KEY;
19701
19858
  buttons.push([
19702
- { label: `${config2.provider === "elevenlabs" ? "\u2713 " : ""}ElevenLabs`, data: "vcfg:p:elevenlabs" },
19703
- { label: `${config2.provider === "grok" ? "\u2713 " : ""}Grok`, data: "vcfg:p:grok" },
19704
- { label: `${config2.provider === "macos" ? "\u2713 " : ""}macOS`, data: "vcfg:p:macos" }
19859
+ {
19860
+ label: `${sttProvider === "groq" ? "\u2713 " : ""}\u{1F310} Groq${!groqAvailable ? " (no key)" : ""}`,
19861
+ data: "vcfg:stt:groq",
19862
+ ...sttProvider === "groq" ? { style: "primary" } : {}
19863
+ },
19864
+ {
19865
+ label: `${sttProvider === "local-whisper" ? "\u2713 " : ""}\u{1F4BB} Local Whisper${!whisperAvailable ? " (install first)" : ""}`,
19866
+ data: "vcfg:stt:local-whisper",
19867
+ ...sttProvider === "local-whisper" ? { style: "primary" } : {}
19868
+ }
19705
19869
  ]);
19706
- if (config2.provider === "elevenlabs") {
19707
- const entries = Object.entries(ELEVENLABS_VOICES);
19708
- const female = entries.filter(([, v]) => v.gender === "F");
19709
- const male = entries.filter(([, v]) => v.gender === "M");
19710
- buttons.push(female.map(([id, v]) => ({
19711
- label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
19712
- data: `vcfg:v:${id}`
19713
- })));
19714
- buttons.push(male.map(([id, v]) => ({
19715
- label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
19716
- data: `vcfg:v:${id}`
19717
- })));
19718
- } else if (config2.provider === "grok") {
19719
- buttons.push(GROK_VOICES.map((v) => ({
19720
- label: `${config2.voiceId === v ? "\u2713 " : ""}${capitalize(v)}`,
19721
- data: `vcfg:v:${v}`
19722
- })));
19723
- } else {
19724
- const entries = Object.entries(MACOS_VOICES);
19725
- buttons.push(entries.map(([id, v]) => ({
19726
- label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
19727
- data: `vcfg:v:${id}`
19728
- })));
19870
+ if (sttProvider === "local-whisper" || whisperAvailable) {
19871
+ const modelEntries = Object.entries(LOCAL_WHISPER_MODELS);
19872
+ for (let i = 0; i < modelEntries.length; i += 2) {
19873
+ const row = modelEntries.slice(i, i + 2).map(([id, info]) => {
19874
+ const downloaded = isWhisperModelDownloaded(id);
19875
+ const active = sttModel === id;
19876
+ return {
19877
+ label: `${active ? "\u2713 " : ""}${id} ${downloaded ? "\u25CF" : "\u25CB"} ${info.size}`,
19878
+ data: `vcfg:stt-model:${id}`,
19879
+ ...active ? { style: "primary" } : {}
19880
+ };
19881
+ });
19882
+ buttons.push(row);
19883
+ }
19729
19884
  }
19885
+ buttons.push([
19886
+ {
19887
+ label: `${!ttsConfig.enabled ? "\u2713 " : ""}\u{1F507} Replies Off`,
19888
+ data: "voice:off",
19889
+ ...!ttsConfig.enabled ? { style: "danger" } : {}
19890
+ },
19891
+ {
19892
+ label: `${ttsConfig.enabled ? "\u2713 " : ""}\u{1F50A} Replies On`,
19893
+ data: "voice:on",
19894
+ ...ttsConfig.enabled ? { style: "success" } : {}
19895
+ }
19896
+ ]);
19897
+ buttons.push([
19898
+ { label: `${ttsConfig.provider === "elevenlabs" ? "\u2713 " : ""}ElevenLabs`, data: "vcfg:p:elevenlabs", ...ttsConfig.provider === "elevenlabs" ? { style: "primary" } : {} },
19899
+ { label: `${ttsConfig.provider === "grok" ? "\u2713 " : ""}Grok`, data: "vcfg:p:grok", ...ttsConfig.provider === "grok" ? { style: "primary" } : {} },
19900
+ { label: `${ttsConfig.provider === "macos" ? "\u2713 " : ""}macOS`, data: "vcfg:p:macos", ...ttsConfig.provider === "macos" ? { style: "primary" } : {} }
19901
+ ]);
19902
+ if (ttsConfig.enabled) {
19903
+ if (ttsConfig.provider === "elevenlabs") {
19904
+ const entries = Object.entries(ELEVENLABS_VOICES);
19905
+ const female = entries.filter(([, v]) => v.gender === "F");
19906
+ const male = entries.filter(([, v]) => v.gender === "M");
19907
+ buttons.push(female.map(([id, v]) => ({ label: `${ttsConfig.voiceId === id ? "\u2713 " : ""}${v.name}`, data: `vcfg:v:${id}` })));
19908
+ buttons.push(male.map(([id, v]) => ({ label: `${ttsConfig.voiceId === id ? "\u2713 " : ""}${v.name}`, data: `vcfg:v:${id}` })));
19909
+ } else if (ttsConfig.provider === "grok") {
19910
+ buttons.push(GROK_VOICES.map((v) => ({ label: `${ttsConfig.voiceId === v ? "\u2713 " : ""}${capitalize(v)}`, data: `vcfg:v:${v}` })));
19911
+ } else {
19912
+ buttons.push(Object.entries(MACOS_VOICES).map(([id, v]) => ({ label: `${ttsConfig.voiceId === id ? "\u2713 " : ""}${v.name}`, data: `vcfg:v:${id}` })));
19913
+ }
19914
+ }
19915
+ const sttLabel = sttProvider === "groq" ? "Groq (cloud)" : `Local Whisper \xB7 ${sttModel}`;
19916
+ const ttsLabel = ttsConfig.enabled ? `${ttsConfig.provider === "grok" ? "Grok" : ttsConfig.provider === "macos" ? "macOS" : "ElevenLabs"} replies ON` : "Replies OFF";
19917
+ const modelLegend = sttProvider === "local-whisper" || whisperAvailable ? "\n\u25CF = downloaded \u25CB = not yet downloaded" : "";
19918
+ const header2 = `\u{1F399}\uFE0F Voice Settings
19919
+
19920
+ \u{1F3A4} Transcription: ${sttLabel}
19921
+ \u{1F50A} Text-to-Speech: ${ttsLabel}${modelLegend}`;
19730
19922
  await channel.sendKeyboard(chatId, header2, buttons);
19731
19923
  }
19732
19924
  async function sendSkillsPage(chatId, channel, skills2, page, messageId) {
@@ -19823,28 +20015,14 @@ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
19823
20015
  const backendDisplay = config2?.backend ? capitalize(config2.backend) : "Default";
19824
20016
  const modelDisplay = config2?.model ?? "default";
19825
20017
  const thinkingDisplay = config2?.thinking ?? "off";
19826
- const lines = [
19827
- buildSectionHeader("Heartbeat"),
19828
- "CC-Claw periodically wakes up to check on",
19829
- "things and alert you proactively \u2014 even when",
19830
- "you haven't sent a message.",
19831
- "",
19832
- `Status: ${enabled ? "ON" : "OFF"}`,
19833
- `Interval: every ${intervalMin} min`,
19834
- `Active hours: ${activeStart}-${activeEnd}`,
19835
- `Backend: ${backendDisplay} | Model: ${modelDisplay}`
19836
- ];
19837
- if (fallbacks.length > 0) {
19838
- lines.push(`Fallbacks: ${fallbacks.map((f) => `${capitalize(f.backend)}${f.model ? `:${f.model}` : ""}`).join(", ")}`);
19839
- }
19840
- if (config2?.target) {
19841
- lines.push(`Target: ${config2.target}`);
19842
- }
19843
- if (watches.length > 0) {
19844
- lines.push(`Active watches: ${watches.length}`);
19845
- }
19846
- const lastBeat = config2?.lastBeatAt ?? "never";
19847
- lines.push(`Last beat: ${lastBeat}`);
20018
+ const lastBeat = config2?.lastBeatAt ? config2.lastBeatAt.replace("T", " ").slice(0, 16) : "never";
20019
+ const watchNote = watches.length > 0 ? ` \xB7 ${watches.length} watch${watches.length !== 1 ? "es" : ""}` : "";
20020
+ const fallbackNote = fallbacks.length > 0 ? `Fallbacks: ${fallbacks.map((f) => `${capitalize(f.backend)}${f.model ? ` (${shortModelName(f.model)})` : ""}`).join(" \u2192 ")}` : "";
20021
+ const header2 = [
20022
+ `\u{1FAC0} Heartbeat \u2014 ${enabled ? "ON" : "OFF"} \xB7 Every ${intervalMin} min \xB7 ${activeStart}\u2013${activeEnd}${watchNote}`,
20023
+ `Backend: ${backendDisplay} \xB7 Model: ${modelDisplay} \xB7 Last: ${lastBeat}`,
20024
+ fallbackNote
20025
+ ].filter(Boolean).join("\n");
19848
20026
  const buttons = [];
19849
20027
  buttons.push([
19850
20028
  { label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
@@ -19858,63 +20036,62 @@ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
19858
20036
  ...m === intervalMin ? { style: "primary" } : {}
19859
20037
  })));
19860
20038
  const available = getAvailableBackendIds();
19861
- buttons.push([{ label: "\u2699\uFE0F Main Engine", data: "hb:noop" }]);
19862
- const backendRow = available.map((bid) => ({
19863
- label: `${config2?.backend === bid ? "\u2713 " : ""}${capitalize(bid)}`,
19864
- data: `hb:backend:${bid}`,
19865
- ...config2?.backend === bid ? { style: "primary" } : {}
19866
- }));
19867
- if (config2?.backend) {
19868
- backendRow.push({ label: "\u2715", data: "hb:backend:default", style: "danger" });
19869
- }
20039
+ const backendRow = [
20040
+ {
20041
+ label: `${!config2?.backend ? "\u2713 " : ""}Default`,
20042
+ data: "hb:backend:default",
20043
+ ...!config2?.backend ? { style: "primary" } : {}
20044
+ },
20045
+ ...available.map((bid) => ({
20046
+ label: `${config2?.backend === bid ? "\u2713 " : ""}${capitalize(bid)}`,
20047
+ data: `hb:backend:${bid}`,
20048
+ ...config2?.backend === bid ? { style: "primary" } : {}
20049
+ }))
20050
+ ];
19870
20051
  buttons.push(backendRow);
19871
20052
  const targetBackend = config2?.backend ?? getBackend(chatId) ?? "claude";
19872
20053
  try {
19873
20054
  const adapter = getAdapter(targetBackend);
19874
20055
  const models = Object.entries(adapter.availableModels);
19875
20056
  if (models.length > 0) {
19876
- const modelRow = models.slice(0, 4).map(([key]) => ({
19877
- label: `${config2?.model === key ? "\u2713 " : ""}${shortModelName(key)}`,
19878
- data: `hb:model:${key}`,
19879
- ...config2?.model === key ? { style: "primary" } : {}
19880
- }));
19881
- if (config2?.model) {
19882
- modelRow.push({ label: "\u2715", data: "hb:model:default", style: "danger" });
19883
- }
20057
+ const modelRow = [
20058
+ {
20059
+ label: `${!config2?.model ? "\u2713 " : ""}Default`,
20060
+ data: "hb:model:default",
20061
+ ...!config2?.model ? { style: "primary" } : {}
20062
+ },
20063
+ ...models.slice(0, 4).map(([key]) => ({
20064
+ label: `${config2?.model === key ? "\u2713 " : ""}${shortModelName(key)}`,
20065
+ data: `hb:model:${key}`,
20066
+ ...config2?.model === key ? { style: "primary" } : {}
20067
+ }))
20068
+ ];
19884
20069
  buttons.push(modelRow);
19885
20070
  }
19886
20071
  } catch {
19887
20072
  }
19888
20073
  const fbCandidates = available.filter((bid) => !fallbacks.some((f) => f.backend === bid) && bid !== config2?.backend);
19889
- if (fallbacks.length > 0 || fbCandidates.length > 0) {
19890
- buttons.push([{ label: "\u{1F504} Fallback Chain", data: "hb:noop" }]);
19891
- }
19892
20074
  if (fallbacks.length > 0) {
19893
20075
  buttons.push([
19894
- { label: fallbacks.map((f) => {
19895
- const name = capitalize(f.backend);
19896
- return f.model ? `${name} (${shortModelName(f.model)})` : name;
19897
- }).join(" \u2192 "), data: "hb:noop" },
19898
- { label: "Clear All", data: "hb:fb:clear", style: "danger" }
20076
+ { label: `\u{1F504} ${fallbacks.map((f) => capitalize(f.backend)).join(" \u2192 ")}`, data: "hb:noop" },
20077
+ { label: "\u2715 Clear", data: "hb:fb:clear", style: "danger" }
19899
20078
  ]);
19900
20079
  }
19901
20080
  if (fbCandidates.length > 0) {
19902
20081
  buttons.push(fbCandidates.slice(0, 4).map((bid) => ({
19903
- label: `+ ${capitalize(bid)}`,
20082
+ label: `+ ${capitalize(bid)} fallback`,
19904
20083
  data: `hb:fb:add:${bid}`
19905
20084
  })));
19906
20085
  }
19907
- const optionsRow = [
19908
- { label: `\u{1F4AD} Thinking: ${thinkingDisplay}`, data: "hb:thinking" }
19909
- ];
20086
+ const optionsRow = [];
19910
20087
  if (watches.length > 0) {
19911
20088
  optionsRow.push({ label: `\u{1F441} Watches (${watches.length})`, data: "hb:watches" });
19912
20089
  } else {
19913
- optionsRow.push({ label: "+ Add Watch", data: "hb:addwatch" });
20090
+ optionsRow.push({ label: "\u{1F441} Add Watch", data: "hb:addwatch" });
19914
20091
  }
20092
+ optionsRow.push({ label: `\u23F0 Hours: ${activeStart}\u2013${activeEnd}`, data: "hb:noop" });
19915
20093
  buttons.push(optionsRow);
19916
- buttons.push([{ label: `\u23F0 ${activeStart}\u2013${activeEnd} (use /heartbeat hours)`, data: "hb:noop" }]);
19917
- await sendOrEditKeyboard(chatId, channel, messageId, lines.join("\n"), buttons);
20094
+ await sendOrEditKeyboard(chatId, channel, messageId, header2, buttons);
19918
20095
  }
19919
20096
  async function sendForgetPicker(chatId, channel, page, messageId) {
19920
20097
  const memories = listMemories();
@@ -20563,13 +20740,13 @@ async function handleEvolveCallback(chatId, data, channel) {
20563
20740
  const { getReflectionStatus: getReflectionStatus2, setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
20564
20741
  const current = getReflectionStatus2(getDb(), chatId);
20565
20742
  if (current === "frozen") {
20566
- const { readFileSync: readFileSync29, existsSync: existsSync58 } = await import("fs");
20567
- const { join: join37 } = await import("path");
20743
+ const { readFileSync: readFileSync30, existsSync: existsSync59 } = await import("fs");
20744
+ const { join: join38 } = await import("path");
20568
20745
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
20569
- const soulPath = join37(CC_CLAW_HOME3, "identity/SOUL.md");
20570
- const userPath = join37(CC_CLAW_HOME3, "identity/USER.md");
20571
- const soul = existsSync58(soulPath) ? readFileSync29(soulPath, "utf-8") : "";
20572
- const user = existsSync58(userPath) ? readFileSync29(userPath, "utf-8") : "";
20746
+ const soulPath = join38(CC_CLAW_HOME3, "identity/SOUL.md");
20747
+ const userPath = join38(CC_CLAW_HOME3, "identity/USER.md");
20748
+ const soul = existsSync59(soulPath) ? readFileSync30(soulPath, "utf-8") : "";
20749
+ const user = existsSync59(userPath) ? readFileSync30(userPath, "utf-8") : "";
20573
20750
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
20574
20751
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
20575
20752
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -20646,11 +20823,11 @@ var init_evolve2 = __esm({
20646
20823
  });
20647
20824
 
20648
20825
  // src/optimizer/identity-audit.ts
20649
- import { readFileSync as readFileSync11, existsSync as existsSync21, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
20650
- import { join as join22 } from "path";
20826
+ import { readFileSync as readFileSync12, existsSync as existsSync22, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
20827
+ import { join as join23 } from "path";
20651
20828
  function readIdentityFile2(filename) {
20652
20829
  try {
20653
- return readFileSync11(join22(IDENTITY_PATH, filename), "utf-8");
20830
+ return readFileSync12(join23(IDENTITY_PATH, filename), "utf-8");
20654
20831
  } catch {
20655
20832
  return "";
20656
20833
  }
@@ -20665,13 +20842,13 @@ function getMtime(filepath) {
20665
20842
  function findBackupFiles() {
20666
20843
  const backups = [];
20667
20844
  const dirs = [IDENTITY_PATH];
20668
- const contextDir = join22(IDENTITY_PATH, "..", "workspace", "context");
20669
- if (existsSync21(contextDir)) dirs.push(contextDir);
20845
+ const contextDir = join23(IDENTITY_PATH, "..", "workspace", "context");
20846
+ if (existsSync22(contextDir)) dirs.push(contextDir);
20670
20847
  for (const dir of dirs) {
20671
20848
  try {
20672
20849
  for (const entry of readdirSync10(dir)) {
20673
20850
  if (entry.endsWith(".bak") || /\.bak\.\d{4}-\d{2}-\d{2}/.test(entry)) {
20674
- backups.push(join22(dir, entry));
20851
+ backups.push(join23(dir, entry));
20675
20852
  }
20676
20853
  }
20677
20854
  } catch {
@@ -20692,9 +20869,9 @@ function computeIdentityStats(pendingProposals, driftPercent) {
20692
20869
  userChars,
20693
20870
  ccClawChars,
20694
20871
  boilerplateChars,
20695
- soulMtime: getMtime(join22(IDENTITY_PATH, "SOUL.md")),
20696
- userMtime: getMtime(join22(IDENTITY_PATH, "USER.md")),
20697
- ccClawMtime: getMtime(join22(IDENTITY_PATH, "CC-CLAW.md")),
20872
+ soulMtime: getMtime(join23(IDENTITY_PATH, "SOUL.md")),
20873
+ userMtime: getMtime(join23(IDENTITY_PATH, "USER.md")),
20874
+ ccClawMtime: getMtime(join23(IDENTITY_PATH, "CC-CLAW.md")),
20698
20875
  backupFiles: findBackupFiles(),
20699
20876
  estimatedTokens: Math.ceil(ccClawChars / 4),
20700
20877
  pendingEvolveProposals: pendingProposals,
@@ -20803,8 +20980,8 @@ var init_identity_audit = __esm({
20803
20980
  });
20804
20981
 
20805
20982
  // src/optimizer/skill-audit.ts
20806
- import { readFileSync as readFileSync12, existsSync as existsSync22 } from "fs";
20807
- import { join as join23, basename as basename3 } from "path";
20983
+ import { readFileSync as readFileSync13, existsSync as existsSync23 } from "fs";
20984
+ import { join as join24, basename as basename3 } from "path";
20808
20985
  function parseFrontmatter3(content) {
20809
20986
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
20810
20987
  if (!fmMatch) return {};
@@ -20840,10 +21017,10 @@ function detectDependentSkills(content) {
20840
21017
  return Array.from(deps);
20841
21018
  }
20842
21019
  function computeSkillStats(skillPath) {
20843
- const content = readFileSync12(skillPath, "utf-8");
21020
+ const content = readFileSync13(skillPath, "utf-8");
20844
21021
  const lines = content.split("\n");
20845
21022
  return {
20846
- skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join23(skillPath, "..")) : basename3(skillPath, ".md"),
21023
+ skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join24(skillPath, "..")) : basename3(skillPath, ".md"),
20847
21024
  skillPath,
20848
21025
  lineCount: lines.length,
20849
21026
  charCount: content.length,
@@ -20863,13 +21040,13 @@ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
20863
21040
  const results = [];
20864
21041
  for (const name of depNames) {
20865
21042
  const candidates = [
20866
- join23(ccClawSkillsDir, name, "SKILL.md"),
20867
- join23(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
21043
+ join24(ccClawSkillsDir, name, "SKILL.md"),
21044
+ join24(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
20868
21045
  ];
20869
21046
  for (const candidate of candidates) {
20870
- if (existsSync22(candidate)) {
21047
+ if (existsSync23(candidate)) {
20871
21048
  try {
20872
- const content = readFileSync12(candidate, "utf-8");
21049
+ const content = readFileSync13(candidate, "utf-8");
20873
21050
  results.push({
20874
21051
  name,
20875
21052
  content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
@@ -20981,8 +21158,8 @@ __export(analyze_exports2, {
20981
21158
  });
20982
21159
  import { spawn as spawn7 } from "child_process";
20983
21160
  import { createInterface as createInterface7 } from "readline";
20984
- import { readFileSync as readFileSync13, existsSync as existsSync23, readdirSync as readdirSync12 } from "fs";
20985
- import { join as join24 } from "path";
21161
+ import { readFileSync as readFileSync14, existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
21162
+ import { join as join25 } from "path";
20986
21163
  import { homedir as homedir7 } from "os";
20987
21164
  function parseOptimizeOutput(raw, validAreas) {
20988
21165
  if (!raw || raw.includes("NO_FINDINGS")) return [];
@@ -21112,20 +21289,20 @@ function getModelDisplayInfo(chatId) {
21112
21289
  }
21113
21290
  function readIdentityFile3(filename) {
21114
21291
  try {
21115
- return readFileSync13(join24(IDENTITY_PATH, filename), "utf-8");
21292
+ return readFileSync14(join25(IDENTITY_PATH, filename), "utf-8");
21116
21293
  } catch {
21117
21294
  return "";
21118
21295
  }
21119
21296
  }
21120
21297
  function loadContextFiles2() {
21121
- const contextDir = join24(homedir7(), ".cc-claw", "workspace", "context");
21298
+ const contextDir = join25(homedir7(), ".cc-claw", "workspace", "context");
21122
21299
  const results = [];
21123
- if (!existsSync23(contextDir)) return results;
21300
+ if (!existsSync24(contextDir)) return results;
21124
21301
  try {
21125
21302
  for (const entry of readdirSync12(contextDir)) {
21126
21303
  if (!entry.endsWith(".md")) continue;
21127
21304
  try {
21128
- const content = readFileSync13(join24(contextDir, entry), "utf-8");
21305
+ const content = readFileSync14(join25(contextDir, entry), "utf-8");
21129
21306
  results.push({ name: entry, content });
21130
21307
  } catch {
21131
21308
  }
@@ -21176,8 +21353,8 @@ async function runSkillAudit(chatId, skillPath) {
21176
21353
  const stats = computeSkillStats(skillPath);
21177
21354
  log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
21178
21355
  const soulMd = readIdentityFile3("SOUL.md");
21179
- const ccClawSkillsDir = join24(homedir7(), ".cc-claw", "workspace", "skills");
21180
- const skillContent = readFileSync13(skillPath, "utf-8");
21356
+ const ccClawSkillsDir = join25(homedir7(), ".cc-claw", "workspace", "skills");
21357
+ const skillContent = readFileSync14(skillPath, "utf-8");
21181
21358
  const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
21182
21359
  const raw = await spawnAnalysis2(adapter, model2, prompt);
21183
21360
  const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
@@ -21191,16 +21368,16 @@ async function runSkillAudit(chatId, skillPath) {
21191
21368
  };
21192
21369
  }
21193
21370
  function listCcClawSkills() {
21194
- const skillsDir = join24(homedir7(), ".cc-claw", "workspace", "skills");
21371
+ const skillsDir = join25(homedir7(), ".cc-claw", "workspace", "skills");
21195
21372
  const entries = [];
21196
- if (!existsSync23(skillsDir)) return entries;
21373
+ if (!existsSync24(skillsDir)) return entries;
21197
21374
  try {
21198
21375
  for (const dir of readdirSync12(skillsDir)) {
21199
- const skillFile = join24(skillsDir, dir, "SKILL.md");
21200
- if (!existsSync23(skillFile)) continue;
21376
+ const skillFile = join25(skillsDir, dir, "SKILL.md");
21377
+ if (!existsSync24(skillFile)) continue;
21201
21378
  let description = "skill";
21202
21379
  try {
21203
- const content = readFileSync13(skillFile, "utf-8");
21380
+ const content = readFileSync14(skillFile, "utf-8");
21204
21381
  const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
21205
21382
  if (descMatch) description = descMatch[1].trim().slice(0, 60);
21206
21383
  } catch {
@@ -21520,8 +21697,8 @@ var init_ui2 = __esm({
21520
21697
  });
21521
21698
 
21522
21699
  // src/router/optimize.ts
21523
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync7, existsSync as existsSync24, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
21524
- import { join as join25, dirname as dirname4 } from "path";
21700
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, existsSync as existsSync25, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
21701
+ import { join as join26, dirname as dirname4 } from "path";
21525
21702
  import { homedir as homedir8 } from "os";
21526
21703
  async function handleOptimizeCommand(chatId, channel, _args) {
21527
21704
  const { getModelDisplayInfo: getModelDisplayInfo2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
@@ -21702,7 +21879,7 @@ async function runSkillAuditFlow(chatId, channel, skillName) {
21702
21879
  } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
21703
21880
  const modelInfo = getModelDisplayInfo2(chatId);
21704
21881
  if (!modelInfo) return;
21705
- const skillPath = join25(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
21882
+ const skillPath = join26(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
21706
21883
  const progressMsgId = typeof channel.sendTextReturningId === "function" ? await channel.sendTextReturningId(
21707
21884
  chatId,
21708
21885
  buildProgressMessage2(`skill: ${skillName}`, modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
@@ -21791,13 +21968,13 @@ async function applyFinding(chatId, channel, index) {
21791
21968
  await showFinding(chatId, channel, index + 1);
21792
21969
  return;
21793
21970
  }
21794
- if (!existsSync24(targetPath)) {
21971
+ if (!existsSync25(targetPath)) {
21795
21972
  await channel.sendText(chatId, `Target file not found: ${targetPath}`, { parseMode: "plain" });
21796
21973
  session2.skipped.push(index);
21797
21974
  await showFinding(chatId, channel, index + 1);
21798
21975
  return;
21799
21976
  }
21800
- const original = readFileSync14(targetPath, "utf-8");
21977
+ const original = readFileSync15(targetPath, "utf-8");
21801
21978
  const backupPath = targetPath + `.bak.${Date.now()}`;
21802
21979
  writeFileSync7(backupPath, original, "utf-8");
21803
21980
  pruneBackups2(targetPath);
@@ -21873,14 +22050,14 @@ async function finishReview(chatId, channel) {
21873
22050
  activeSessions.delete(chatId);
21874
22051
  }
21875
22052
  function resolveTargetFile(location, auditTarget) {
21876
- const ccClawHome = join25(homedir8(), ".cc-claw");
22053
+ const ccClawHome = join26(homedir8(), ".cc-claw");
21877
22054
  const filePart = location.split(":")[0]?.trim();
21878
22055
  if (!filePart) return null;
21879
- if (filePart === "SOUL.md") return join25(ccClawHome, "identity", "SOUL.md");
21880
- if (filePart === "USER.md") return join25(ccClawHome, "identity", "USER.md");
21881
- if (filePart === "CC-CLAW.md") return join25(ccClawHome, "identity", "CC-CLAW.md");
22056
+ if (filePart === "SOUL.md") return join26(ccClawHome, "identity", "SOUL.md");
22057
+ if (filePart === "USER.md") return join26(ccClawHome, "identity", "USER.md");
22058
+ if (filePart === "CC-CLAW.md") return join26(ccClawHome, "identity", "CC-CLAW.md");
21882
22059
  if (filePart === "SKILL.md" && auditTarget !== "identity") {
21883
- return join25(ccClawHome, "workspace", "skills", auditTarget, "SKILL.md");
22060
+ return join26(ccClawHome, "workspace", "skills", auditTarget, "SKILL.md");
21884
22061
  }
21885
22062
  return null;
21886
22063
  }
@@ -21888,7 +22065,7 @@ function pruneBackups2(absolutePath) {
21888
22065
  const dir = dirname4(absolutePath);
21889
22066
  const baseName = absolutePath.split("/").pop() ?? "";
21890
22067
  try {
21891
- const backups = readdirSync13(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join25(dir, f));
22068
+ const backups = readdirSync13(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join26(dir, f));
21892
22069
  while (backups.length > 3) {
21893
22070
  const oldest = backups.shift();
21894
22071
  try {
@@ -21922,8 +22099,8 @@ __export(auto_create_exports, {
21922
22099
  saveSkill: () => saveSkill,
21923
22100
  storePendingDraft: () => storePendingDraft
21924
22101
  });
21925
- import { join as join26 } from "path";
21926
- import { writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
22102
+ import { join as join27 } from "path";
22103
+ import { writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
21927
22104
  function isSkillWorthy(signals) {
21928
22105
  const { toolUseCount, tokenOutput, elapsedMs, userMessage } = signals;
21929
22106
  if (toolUseCount < 12) return false;
@@ -22069,10 +22246,10 @@ function parseExtractedSkill(llmResponse) {
22069
22246
  return { name, content };
22070
22247
  }
22071
22248
  async function saveSkill(name, content) {
22072
- const dir = join26(SKILLS_PATH, name);
22073
- await mkdir4(dir, { recursive: true });
22074
- const filePath = join26(dir, "SKILL.md");
22075
- await writeFile4(filePath, content, "utf-8");
22249
+ const dir = join27(SKILLS_PATH, name);
22250
+ await mkdir5(dir, { recursive: true });
22251
+ const filePath = join27(dir, "SKILL.md");
22252
+ await writeFile5(filePath, content, "utf-8");
22076
22253
  invalidateSkillCache();
22077
22254
  log(`[auto-skill] Saved skill "${name}" to ${filePath}`);
22078
22255
  return { path: filePath };
@@ -22242,18 +22419,7 @@ async function handleStopCommand(chatId, commandArgs, msg, channel) {
22242
22419
  }
22243
22420
  }
22244
22421
  async function handleVoiceCommand(chatId, commandArgs, msg, channel) {
22245
- const vcEnabled = isVoiceEnabled(chatId);
22246
- if (typeof channel.sendKeyboard === "function") {
22247
- await channel.sendKeyboard(chatId, `\u{1F3A7} Voice responses: ${vcEnabled ? "ON" : "OFF"}`, [
22248
- [
22249
- { label: `${vcEnabled ? "" : "\u2713 "}\u{1F507} Off`, data: "voice:off", ...!vcEnabled ? { style: "danger" } : {} },
22250
- { label: `${vcEnabled ? "\u2713 " : ""}\u{1F50A} On`, data: "voice:on", ...vcEnabled ? { style: "success" } : {} }
22251
- ]
22252
- ]);
22253
- } else {
22254
- const toggled = toggleVoice(chatId);
22255
- await channel.sendText(chatId, toggled ? "Voice responses enabled." : "Voice responses disabled.", { parseMode: "plain" });
22256
- }
22422
+ await sendVoiceConfigKeyboard(chatId, channel);
22257
22423
  }
22258
22424
  async function handleVoiceConfigCommand(chatId, commandArgs, msg, channel) {
22259
22425
  await sendVoiceConfigKeyboard(chatId, channel);
@@ -22452,42 +22618,40 @@ Use /skills to see it.`, { parseMode: "plain" });
22452
22618
  async function handleExtractSkillCommand(chatId, commandArgs, msg, channel) {
22453
22619
  const { getLog: getLog2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
22454
22620
  const sessionMessages = getLog2(chatId);
22621
+ const exchangeCount = Math.floor(sessionMessages.length / 2);
22455
22622
  if (sessionMessages.length < 2) {
22456
22623
  await channel.sendText(chatId, "No session history to extract from. Have a conversation first, then run /extract_skill.", { parseMode: "plain" });
22457
22624
  return;
22458
22625
  }
22459
- await channel.sendText(chatId, `Reviewing session (${Math.floor(sessionMessages.length / 2)} exchanges)...`, { parseMode: "plain" });
22460
- const { buildSessionExtractionPrompt: buildSessionExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, storePendingDraft: storePendingDraft2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
22461
- const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
22462
- const { getMode: getMode3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
22463
- const prompt = buildSessionExtractionPrompt2(sessionMessages);
22464
- const response = await askAgent3(chatId, prompt, { permMode: getMode3(chatId) });
22465
- const extracted = parseExtractedSkill2(response.text);
22466
- if (!extracted) {
22467
- await channel.sendText(chatId, "Could not extract a skill from this session. The session may be too short or unfocused.", { parseMode: "plain" });
22468
- return;
22469
- }
22470
- storePendingDraft2(chatId, {
22471
- name: extracted.name,
22472
- content: extracted.content,
22473
- userMessage: sessionMessages.filter((m) => m.role === "user").map((m) => m.text).join("\n"),
22474
- assistantResponse: sessionMessages.filter((m) => m.role === "assistant").map((m) => m.text).join("\n")
22475
- });
22476
- const preview = extracted.content.length > 3500 ? extracted.content.slice(0, 3500) + "\n\n[...truncated...]" : extracted.content;
22477
22626
  if (typeof channel.sendKeyboard === "function") {
22478
- await channel.sendText(chatId, `Extracted skill: "${extracted.name}"
22479
-
22480
- ${preview}`, { parseMode: "plain" });
22481
22627
  await channel.sendKeyboard(
22482
22628
  chatId,
22483
- `Save this skill?`,
22629
+ `\u{1F9E0} Extract Reusable Skill
22630
+
22631
+ This will analyze your current session (${exchangeCount} exchange${exchangeCount !== 1 ? "s" : ""}) and generate a reusable skill file.
22632
+
22633
+ \u2022 The AI reviews your conversation to identify the workflow
22634
+ \u2022 Generates a structured SKILL.md you can reuse in future tasks
22635
+ \u2022 You'll preview it before anything is saved
22636
+
22637
+ Ready to start?`,
22484
22638
  [[
22485
- { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
22486
- { label: "\u2715 Discard", data: "skill:discard" }
22639
+ { label: "\u26A1 Start Extraction", data: "skill:start-extract", style: "success" },
22640
+ { label: "\u2715 Cancel", data: "skill:discard" }
22487
22641
  ]]
22488
22642
  );
22489
22643
  } else {
22490
- const { saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
22644
+ await channel.sendText(chatId, `Reviewing session (${exchangeCount} exchanges)...`, { parseMode: "plain" });
22645
+ const { buildSessionExtractionPrompt: buildSessionExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
22646
+ const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
22647
+ const { getMode: getMode3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
22648
+ const prompt = buildSessionExtractionPrompt2(sessionMessages);
22649
+ const response = await askAgent3(chatId, prompt, { permMode: getMode3(chatId) });
22650
+ const extracted = parseExtractedSkill2(response.text);
22651
+ if (!extracted) {
22652
+ await channel.sendText(chatId, "Could not extract a skill from this session. The session may be too short or unfocused.", { parseMode: "plain" });
22653
+ return;
22654
+ }
22491
22655
  const result = await saveSkill2(extracted.name, extracted.content);
22492
22656
  await channel.sendText(chatId, `Skill "${extracted.name}" saved to ${result.path}`, { parseMode: "plain" });
22493
22657
  }
@@ -22598,6 +22762,22 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22598
22762
  stopAllSideQuests(chatId);
22599
22763
  const oldSessionId = getSessionId(chatId);
22600
22764
  const exchangeCount = getMessagePairCount(chatId);
22765
+ const needsSummary = exchangeCount > 0;
22766
+ let ackMsgId;
22767
+ if (needsSummary) {
22768
+ ackMsgId = await channel.sendTextReturningId?.(
22769
+ chatId,
22770
+ `\u23F3 Archiving session (${exchangeCount} exchanges)...`,
22771
+ "plain"
22772
+ );
22773
+ if (!ackMsgId) {
22774
+ await channel.sendText(
22775
+ chatId,
22776
+ `\u23F3 Archiving session (${exchangeCount} exchanges)...`,
22777
+ { parseMode: "plain" }
22778
+ );
22779
+ }
22780
+ }
22601
22781
  const summarized = await summarizeSession(chatId);
22602
22782
  clearSession(chatId);
22603
22783
  clearChatPaidSlots(chatId);
@@ -22616,6 +22796,7 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22616
22796
  const text = `\u2705 New session started. Previous session archived${exchangeCount > 0 ? ` (${exchangeCount} exchanges)` : ""}.
22617
22797
 
22618
22798
  \u{1F9E0} ${backendLabel} \xB7 ${modelLabel}`;
22799
+ if (ackMsgId) await channel.editText?.(chatId, ackMsgId, text, "plain");
22619
22800
  const kbMsgId = await channel.sendKeyboard(chatId, text, [
22620
22801
  [
22621
22802
  { label: "Switch Backend", data: "menu:backend", style: "primary" },
@@ -22634,7 +22815,11 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22634
22815
  }
22635
22816
  } else {
22636
22817
  const text = summarized ? "Session summarized and saved. Fresh conversation started!" : "Fresh conversation started. What's on your mind?";
22637
- await channel.sendText(chatId, text, { parseMode: "plain" });
22818
+ if (ackMsgId) {
22819
+ await channel.editText?.(chatId, ackMsgId, text, "plain");
22820
+ } else {
22821
+ await channel.sendText(chatId, text, { parseMode: "plain" });
22822
+ }
22638
22823
  }
22639
22824
  }
22640
22825
  async function handleSummarizeCommand(chatId, commandArgs, msg, channel) {
@@ -24516,6 +24701,44 @@ ${plan.originalMessage}`;
24516
24701
  if (current !== desired) toggleVoice(chatId);
24517
24702
  await channel.sendText(chatId, desired ? "\u{1F50A} Voice responses enabled." : "\u{1F507} Voice responses disabled.", { parseMode: "plain" });
24518
24703
  }
24704
+ } else if (data.startsWith("vcfg:stt-model:")) {
24705
+ const model2 = data.slice(15);
24706
+ if (!(model2 in LOCAL_WHISPER_MODELS)) {
24707
+ await channel.sendText(chatId, "Unknown model.", { parseMode: "plain" });
24708
+ return;
24709
+ }
24710
+ setSttProvider(chatId, "local-whisper");
24711
+ setSttModel(chatId, model2);
24712
+ const info = LOCAL_WHISPER_MODELS[model2];
24713
+ const downloaded = isWhisperModelDownloaded(model2);
24714
+ const notice = downloaded ? `\u2705 Transcription model set to ${model2} (${info.size}, ${info.lang}). Already downloaded.` : `\u2705 Transcription model set to ${model2} (${info.size}, ${info.lang}).
24715
+
24716
+ \u2B07\uFE0F Model will be downloaded on your first voice message (~${info.size}). This is a one-time download.`;
24717
+ await channel.sendText(chatId, notice, { parseMode: "plain" });
24718
+ await sendVoiceConfigKeyboard(chatId, channel);
24719
+ } else if (data.startsWith("vcfg:stt:")) {
24720
+ const provider = data.slice(9);
24721
+ if (provider === "local-whisper") {
24722
+ if (!isWhisperCliAvailable()) {
24723
+ await channel.sendText(
24724
+ chatId,
24725
+ "\u26A0\uFE0F Local Whisper requires `whisper-cli` to be installed.\n\nInstall it:\n```\nbrew install whisper-cpp\n```\n(macOS/Linux with Homebrew)\n\nOr download from: https://github.com/ggml-org/whisper.cpp/releases\n\nOnce installed, select Local Whisper here and pick a model.",
24726
+ { parseMode: "markdown" }
24727
+ );
24728
+ return;
24729
+ }
24730
+ }
24731
+ if (provider === "groq" && !process.env.GROQ_API_KEY) {
24732
+ await channel.sendText(
24733
+ chatId,
24734
+ "\u26A0\uFE0F Groq requires `GROQ_API_KEY` to be set.\n\nGet a free key at https://console.groq.com/keys, then add it:\n```\necho 'GROQ_API_KEY=your-key' >> ~/.cc-claw/.env\ncc-claw service restart\n```",
24735
+ { parseMode: "markdown" }
24736
+ );
24737
+ }
24738
+ setSttProvider(chatId, provider);
24739
+ const label2 = provider === "groq" ? "Groq (cloud)" : "Local Whisper";
24740
+ await channel.sendText(chatId, `\u2705 Transcription provider set to: ${label2}`, { parseMode: "plain" });
24741
+ await sendVoiceConfigKeyboard(chatId, channel);
24519
24742
  } else if (data.startsWith("vcfg:")) {
24520
24743
  const parts = data.slice(5).split(":");
24521
24744
  const action = parts[0];
@@ -25423,15 +25646,16 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
25423
25646
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25424
25647
  } else if (rest.startsWith("backend:")) {
25425
25648
  const bid = rest.slice(8);
25426
- if (bid === "default") {
25427
- updateHeartbeatConfig2({ backend: null, model: null });
25428
- } else {
25429
- updateHeartbeatConfig2({ backend: bid, model: null });
25430
- }
25649
+ const newBackend = bid === "default" ? null : bid;
25650
+ updateHeartbeatConfig2({ backend: newBackend, model: null });
25651
+ updateHeartbeatField(chatId, "backend", newBackend);
25652
+ updateHeartbeatField(chatId, "model", null);
25431
25653
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25432
25654
  } else if (rest.startsWith("model:")) {
25433
25655
  const model2 = rest.slice(6);
25434
- updateHeartbeatConfig2({ model: model2 === "default" ? null : model2 });
25656
+ const newModel = model2 === "default" ? null : model2;
25657
+ updateHeartbeatConfig2({ model: newModel });
25658
+ updateHeartbeatField(chatId, "model", newModel);
25435
25659
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25436
25660
  } else if (rest === "thinking") {
25437
25661
  await channel.sendText(chatId, "Thinking level for heartbeat: use /editjob to configure.", { parseMode: "plain" });
@@ -25551,6 +25775,71 @@ Example: /limits ${bid} daily 500000`, { parseMode: "plain" });
25551
25775
  const page = parseInt(data.slice(12), 10);
25552
25776
  const skills2 = await discoverAllSkills();
25553
25777
  await sendSkillsPage(chatId, channel, skills2, page, messageId);
25778
+ } else if (data === "skill:start-extract") {
25779
+ const { getLog: getLog2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
25780
+ const sessionMessages = getLog2(chatId);
25781
+ if (sessionMessages.length < 2) {
25782
+ if (messageId) await replaceWithText("No session history to extract from.");
25783
+ else await channel.sendText(chatId, "No session history to extract from.", { parseMode: "plain" });
25784
+ return;
25785
+ }
25786
+ const exchangeCount = Math.floor(sessionMessages.length / 2);
25787
+ if (messageId) await replaceWithText(`\u{1F50D} Reviewing session (${exchangeCount} exchanges)...`);
25788
+ else await channel.sendText(chatId, `\u{1F50D} Reviewing session (${exchangeCount} exchanges)...`, { parseMode: "plain" });
25789
+ try {
25790
+ const { buildSessionExtractionPrompt: buildSessionExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, storePendingDraft: storePendingDraft2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
25791
+ const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
25792
+ const { getMode: getMode3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
25793
+ const prompt = buildSessionExtractionPrompt2(sessionMessages);
25794
+ const response = await askAgent3(chatId, prompt, { permMode: getMode3(chatId) });
25795
+ const extracted = parseExtractedSkill2(response.text);
25796
+ if (!extracted) {
25797
+ await channel.sendText(chatId, "Could not extract a skill from this session. The session may be too short or unfocused.", { parseMode: "plain" });
25798
+ return;
25799
+ }
25800
+ storePendingDraft2(chatId, {
25801
+ name: extracted.name,
25802
+ content: extracted.content,
25803
+ userMessage: sessionMessages.filter((m) => m.role === "user").map((m) => m.text).join("\n"),
25804
+ assistantResponse: sessionMessages.filter((m) => m.role === "assistant").map((m) => m.text).join("\n")
25805
+ });
25806
+ const escaped = extracted.content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
25807
+ const header2 = `\u{1F4CB} <b>Skill Preview: "${extracted.name}"</b>
25808
+
25809
+ `;
25810
+ const MAX_PREVIEW = 3500 - header2.length;
25811
+ if (typeof channel.sendKeyboard === "function") {
25812
+ if (escaped.length > MAX_PREVIEW) {
25813
+ await channel.sendText(chatId, `${header2}<pre>${escaped.slice(0, 3500)}</pre>
25814
+
25815
+ \u2026(truncated \u2014 full skill will be saved)`, { parseMode: "html" });
25816
+ await channel.sendKeyboard(
25817
+ chatId,
25818
+ `Save "${extracted.name}" as a reusable skill?`,
25819
+ [[
25820
+ { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
25821
+ { label: "\u2715 Discard", data: "skill:discard" }
25822
+ ]]
25823
+ );
25824
+ } else {
25825
+ await channel.sendKeyboard(
25826
+ chatId,
25827
+ `${header2}<pre>${escaped}</pre>`,
25828
+ [[
25829
+ { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
25830
+ { label: "\u2715 Discard", data: "skill:discard" }
25831
+ ]]
25832
+ );
25833
+ }
25834
+ } else {
25835
+ const { saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
25836
+ const { path } = await saveSkill2(extracted.name, extracted.content);
25837
+ await channel.sendText(chatId, `\u2705 Skill "${extracted.name}" saved.
25838
+ Path: ${path}`, { parseMode: "plain" });
25839
+ }
25840
+ } catch (e) {
25841
+ await channel.sendText(chatId, `Skill extraction failed: ${e.message}`, { parseMode: "plain" });
25842
+ }
25554
25843
  } else if (data === "skill:extract") {
25555
25844
  const { getPendingDraft: getPendingDraft2, clearPendingDraft: clearPendingDraft2, buildSkillExtractionPrompt: buildSkillExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
25556
25845
  const draft = getPendingDraft2(chatId);
@@ -26426,7 +26715,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26426
26715
  })) {
26427
26716
  const planDirective = buildPlanningDirective();
26428
26717
  let typingActive2 = true;
26429
- const typingLoop = async () => {
26718
+ const typingLoop2 = async () => {
26430
26719
  while (typingActive2) {
26431
26720
  try {
26432
26721
  await channel.sendTyping?.(chatId);
@@ -26435,7 +26724,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26435
26724
  await new Promise((r) => setTimeout(r, 4e3));
26436
26725
  }
26437
26726
  };
26438
- typingLoop().catch(() => {
26727
+ typingLoop2().catch(() => {
26439
26728
  });
26440
26729
  try {
26441
26730
  const planResponse = await askAgent(chatId, cleanText || text, {
@@ -26476,23 +26765,18 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26476
26765
  }
26477
26766
  return;
26478
26767
  }
26479
- const verboseForTyping = settings.getVerboseLevel();
26480
- const showThinkingForTyping = settings.getShowThinkingUi();
26481
- const needsLiveStatusForTyping = verboseForTyping !== "off" || showThinkingForTyping;
26482
- let typingActive = !needsLiveStatusForTyping;
26483
- if (typingActive) {
26484
- const typingLoop = async () => {
26485
- while (typingActive) {
26486
- try {
26487
- await channel.sendTyping?.(chatId);
26488
- } catch {
26489
- }
26490
- await new Promise((r) => setTimeout(r, 4e3));
26768
+ let typingActive = true;
26769
+ const typingLoop = async () => {
26770
+ while (typingActive) {
26771
+ try {
26772
+ await channel.sendTyping?.(chatId);
26773
+ } catch {
26491
26774
  }
26492
- };
26493
- typingLoop().catch(() => {
26494
- });
26495
- }
26775
+ await new Promise((r) => setTimeout(r, 4e3));
26776
+ }
26777
+ };
26778
+ typingLoop().catch(() => {
26779
+ });
26496
26780
  try {
26497
26781
  const tMode = settings.getMode();
26498
26782
  const tVerbose = settings.getVerboseLevel();
@@ -26523,6 +26807,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26523
26807
  }
26524
26808
  };
26525
26809
  await liveStatus.init();
26810
+ typingActive = false;
26526
26811
  if (showThinkingUi && adapter.id !== "claude") {
26527
26812
  liveStatus.addInfo(`\u{1F4AD} Thinking display not available for ${adapter.displayName}`);
26528
26813
  }
@@ -26616,7 +26901,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26616
26901
  }
26617
26902
  responseText += `
26618
26903
 
26619
- \u{1F9E0} [${shortModel} | ${capitalize(thinking2)}${slotTag}] \u23F1\uFE0F ${elapsedSec}s`;
26904
+ \u{1F9E0} [${shortModel} | ${capitalize(thinking2)}${slotTag}] \u23F1\uFE0F ${elapsedSec}s \xB7 \u{1F195}/new`;
26620
26905
  }
26621
26906
  if (observedSubagents.size > 0) {
26622
26907
  const names = [...observedSubagents].join(", ");
@@ -27171,7 +27456,19 @@ function resolveJobBackendId(job) {
27171
27456
  })();
27172
27457
  }
27173
27458
  function resolveJobModel(job) {
27174
- if (job.model) return job.model;
27459
+ if (job.model) {
27460
+ if (job.backend) {
27461
+ try {
27462
+ const adapter = getAdapter(job.backend);
27463
+ if (!(job.model in adapter.availableModels)) {
27464
+ warn(`[scheduler] job #${job.id}: model "${job.model}" not valid for backend "${job.backend}" \u2014 using default`);
27465
+ return adapter.defaultModel;
27466
+ }
27467
+ } catch {
27468
+ }
27469
+ }
27470
+ return job.model;
27471
+ }
27175
27472
  const backendId = resolveJobBackendId(job);
27176
27473
  try {
27177
27474
  const adapter = getAdapter(backendId);
@@ -27205,7 +27502,7 @@ var init_cron = __esm({
27205
27502
  });
27206
27503
 
27207
27504
  // src/agents/runners/wrap-backend.ts
27208
- import { join as join27 } from "path";
27505
+ import { join as join28 } from "path";
27209
27506
  function buildMcpCommands(backendId) {
27210
27507
  const exe = backendId === BACKEND.CURSOR ? "agent" : backendId;
27211
27508
  return {
@@ -27299,7 +27596,7 @@ function wrapBackendAdapter(adapter) {
27299
27596
  const configPath = writeMcpConfigFile(server);
27300
27597
  return ["--mcp-config", configPath];
27301
27598
  },
27302
- getSkillPath: () => join27(SKILLS_PATH, `agent-${adapter.id}.md`)
27599
+ getSkillPath: () => join28(SKILLS_PATH, `agent-${adapter.id}.md`)
27303
27600
  };
27304
27601
  }
27305
27602
  var BACKEND_CAPABILITIES;
@@ -27361,18 +27658,18 @@ var init_wrap_backend = __esm({
27361
27658
  });
27362
27659
 
27363
27660
  // src/agents/runners/config-loader.ts
27364
- import { readFileSync as readFileSync15, readdirSync as readdirSync14, existsSync as existsSync25, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
27365
- import { join as join28 } from "path";
27661
+ import { readFileSync as readFileSync16, readdirSync as readdirSync14, existsSync as existsSync26, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
27662
+ import { join as join29 } from "path";
27366
27663
  import { execFileSync as execFileSync3 } from "child_process";
27367
27664
  function resolveExecutable2(config2) {
27368
- if (existsSync25(config2.executable)) return config2.executable;
27665
+ if (existsSync26(config2.executable)) return config2.executable;
27369
27666
  try {
27370
27667
  return execFileSync3("which", [config2.executable], { encoding: "utf-8" }).trim();
27371
27668
  } catch {
27372
27669
  }
27373
27670
  for (const fallback of config2.executableFallbacks ?? []) {
27374
27671
  const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
27375
- if (existsSync25(resolved)) return resolved;
27672
+ if (existsSync26(resolved)) return resolved;
27376
27673
  }
27377
27674
  return config2.executable;
27378
27675
  }
@@ -27498,12 +27795,12 @@ function configToRunner(config2) {
27498
27795
  prepareMcpInjection() {
27499
27796
  return [];
27500
27797
  },
27501
- getSkillPath: () => join28(SKILLS_PATH, `agent-${config2.id}.md`)
27798
+ getSkillPath: () => join29(SKILLS_PATH, `agent-${config2.id}.md`)
27502
27799
  };
27503
27800
  }
27504
27801
  function loadRunnerConfig(filePath) {
27505
27802
  try {
27506
- const content = readFileSync15(filePath, "utf-8");
27803
+ const content = readFileSync16(filePath, "utf-8");
27507
27804
  return JSON.parse(content);
27508
27805
  } catch (err) {
27509
27806
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -27511,14 +27808,14 @@ function loadRunnerConfig(filePath) {
27511
27808
  }
27512
27809
  }
27513
27810
  function loadAllRunnerConfigs() {
27514
- if (!existsSync25(RUNNERS_PATH)) {
27811
+ if (!existsSync26(RUNNERS_PATH)) {
27515
27812
  mkdirSync10(RUNNERS_PATH, { recursive: true });
27516
27813
  return [];
27517
27814
  }
27518
27815
  const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
27519
27816
  const configs = [];
27520
27817
  for (const file of files) {
27521
- const config2 = loadRunnerConfig(join28(RUNNERS_PATH, file));
27818
+ const config2 = loadRunnerConfig(join29(RUNNERS_PATH, file));
27522
27819
  if (config2) configs.push(config2);
27523
27820
  }
27524
27821
  return configs;
@@ -27539,16 +27836,16 @@ function registerConfigRunners() {
27539
27836
  return count;
27540
27837
  }
27541
27838
  function watchRunnerConfigs(onChange) {
27542
- if (!existsSync25(RUNNERS_PATH)) return;
27839
+ if (!existsSync26(RUNNERS_PATH)) return;
27543
27840
  for (const prev of watchedFiles) {
27544
- if (!existsSync25(prev)) {
27841
+ if (!existsSync26(prev)) {
27545
27842
  unwatchFile(prev);
27546
27843
  watchedFiles.delete(prev);
27547
27844
  }
27548
27845
  }
27549
27846
  const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
27550
27847
  for (const file of files) {
27551
- const fullPath = join28(RUNNERS_PATH, file);
27848
+ const fullPath = join29(RUNNERS_PATH, file);
27552
27849
  if (watchedFiles.has(fullPath)) continue;
27553
27850
  watchedFiles.add(fullPath);
27554
27851
  watchFile(fullPath, { interval: 5e3 }, () => {
@@ -28070,20 +28367,6 @@ var init_telegram2 = __esm({
28070
28367
  this.allowedChatIds = new Set(ids);
28071
28368
  this.bot = new Bot(token);
28072
28369
  this.throttle = new TelegramThrottle();
28073
- this.throttle.setResumeNotifier(async (chatId, pausedSec, queuedCount) => {
28074
- if (pausedSec > 60) {
28075
- log(`[telegram] Skipping resume notification (paused ${pausedSec}s \u2014 too long, would risk another 429)`);
28076
- return;
28077
- }
28078
- try {
28079
- await this.bot.api.sendMessage(
28080
- numericChatId(chatId),
28081
- `\u26A0\uFE0F Rate-limited by Telegram for ${pausedSec}s \u2014 delivering ${queuedCount} pending message(s) now.`
28082
- );
28083
- } catch (err) {
28084
- warn("[telegram] Resume notification failed:", err instanceof Error ? err.message : err);
28085
- }
28086
- });
28087
28370
  }
28088
28371
  /** The first ID in ALLOWED_CHAT_ID — used for heartbeat, notifications, etc. */
28089
28372
  getPrimaryChatId() {
@@ -28167,8 +28450,7 @@ var init_telegram2 = __esm({
28167
28450
  { command: "skills", description: "List and invoke skills" },
28168
28451
  { command: "extract_skill", description: "Extract a reusable skill from this session" },
28169
28452
  { command: "skill_install", description: "Install a skill from GitHub" },
28170
- { command: "voice", description: "Toggle voice responses" },
28171
- { command: "voice_config", description: "Configure voice provider and voice" },
28453
+ { command: "voice", description: "Voice settings (STT transcription + TTS replies)" },
28172
28454
  { command: "response_style", description: "Set the AI response style (concise/normal/detailed)" },
28173
28455
  { command: "model_signature", description: "Toggle model+thinking signature on responses" },
28174
28456
  { command: "imagine", description: "Generate an image from a prompt" },
@@ -28596,7 +28878,8 @@ var init_telegram2 = __esm({
28596
28878
  "reaction",
28597
28879
  () => this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
28598
28880
  { type: "emoji", emoji }
28599
- ])
28881
+ ]),
28882
+ { skipRecord: true }
28600
28883
  );
28601
28884
  } catch (err) {
28602
28885
  log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
@@ -28839,19 +29122,19 @@ var init_telegram2 = __esm({
28839
29122
  });
28840
29123
 
28841
29124
  // src/skills/bootstrap.ts
28842
- import { existsSync as existsSync26 } from "fs";
28843
- import { readdir as readdir6, readFile as readFile8, writeFile as writeFile5, copyFile } from "fs/promises";
28844
- import { join as join29, dirname as dirname5 } from "path";
29125
+ import { existsSync as existsSync27 } from "fs";
29126
+ import { readdir as readdir6, readFile as readFile8, writeFile as writeFile6, copyFile } from "fs/promises";
29127
+ import { join as join30, dirname as dirname5 } from "path";
28845
29128
  import { fileURLToPath as fileURLToPath2 } from "url";
28846
29129
  async function copyAgentManifestSkills() {
28847
- if (!existsSync26(PKG_SKILLS)) return;
29130
+ if (!existsSync27(PKG_SKILLS)) return;
28848
29131
  try {
28849
29132
  const entries = await readdir6(PKG_SKILLS, { withFileTypes: true });
28850
29133
  for (const entry of entries) {
28851
29134
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
28852
- const src = join29(PKG_SKILLS, entry.name);
28853
- const dest = join29(SKILLS_PATH, entry.name);
28854
- if (existsSync26(dest)) continue;
29135
+ const src = join30(PKG_SKILLS, entry.name);
29136
+ const dest = join30(SKILLS_PATH, entry.name);
29137
+ if (existsSync27(dest)) continue;
28855
29138
  await copyFile(src, dest);
28856
29139
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
28857
29140
  }
@@ -28861,8 +29144,8 @@ async function copyAgentManifestSkills() {
28861
29144
  }
28862
29145
  async function bootstrapSkills() {
28863
29146
  await copyAgentManifestSkills();
28864
- const usmDir = join29(SKILLS_PATH, USM_DIR_NAME);
28865
- if (existsSync26(usmDir)) return;
29147
+ const usmDir = join30(SKILLS_PATH, USM_DIR_NAME);
29148
+ if (existsSync27(usmDir)) return;
28866
29149
  try {
28867
29150
  const entries = await readdir6(SKILLS_PATH);
28868
29151
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -28885,8 +29168,8 @@ async function bootstrapSkills() {
28885
29168
  }
28886
29169
  }
28887
29170
  async function patchUsmForCcClaw(usmDir) {
28888
- const skillPath = join29(usmDir, "SKILL.md");
28889
- if (!existsSync26(skillPath)) return;
29171
+ const skillPath = join30(usmDir, "SKILL.md");
29172
+ if (!existsSync27(skillPath)) return;
28890
29173
  try {
28891
29174
  let content = await readFile8(skillPath, "utf-8");
28892
29175
  let patched = false;
@@ -28914,7 +29197,7 @@ async function patchUsmForCcClaw(usmDir) {
28914
29197
  }
28915
29198
  }
28916
29199
  if (patched) {
28917
- await writeFile5(skillPath, content, "utf-8");
29200
+ await writeFile6(skillPath, content, "utf-8");
28918
29201
  log("[skills] Patched USM SKILL.md with CC-Claw support");
28919
29202
  }
28920
29203
  } catch (err) {
@@ -28931,8 +29214,8 @@ var init_bootstrap = __esm({
28931
29214
  USM_REPO = "jacob-bd/universal-skills-manager";
28932
29215
  USM_DIR_NAME = "universal-skills-manager";
28933
29216
  CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
28934
- PKG_ROOT = join29(dirname5(fileURLToPath2(import.meta.url)), "..", "..");
28935
- PKG_SKILLS = join29(PKG_ROOT, "skills");
29217
+ PKG_ROOT = join30(dirname5(fileURLToPath2(import.meta.url)), "..", "..");
29218
+ PKG_SKILLS = join30(PKG_ROOT, "skills");
28936
29219
  }
28937
29220
  });
28938
29221
 
@@ -29045,13 +29328,13 @@ __export(ai_skill_exports, {
29045
29328
  generateAiSkill: () => generateAiSkill,
29046
29329
  installAiSkill: () => installAiSkill
29047
29330
  });
29048
- import { existsSync as existsSync27, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
29049
- import { join as join30 } from "path";
29331
+ import { existsSync as existsSync28, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
29332
+ import { join as join31 } from "path";
29050
29333
  import { homedir as homedir9 } from "os";
29051
29334
  function generateAiSkill() {
29052
29335
  const version = VERSION;
29053
29336
  let systemState = "";
29054
- if (existsSync27(DB_PATH)) {
29337
+ if (existsSync28(DB_PATH)) {
29055
29338
  try {
29056
29339
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
29057
29340
  const readDb = openDatabaseReadOnly2();
@@ -29491,8 +29774,8 @@ function installAiSkill() {
29491
29774
  const failed = [];
29492
29775
  for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
29493
29776
  for (const dir of dirs) {
29494
- const skillDir = join30(dir, "cc-claw-cli");
29495
- const skillPath = join30(skillDir, "SKILL.md");
29777
+ const skillDir = join31(dir, "cc-claw-cli");
29778
+ const skillPath = join31(skillDir, "SKILL.md");
29496
29779
  try {
29497
29780
  mkdirSync11(skillDir, { recursive: true });
29498
29781
  writeFileSync8(skillPath, skill, "utf-8");
@@ -29511,11 +29794,11 @@ var init_ai_skill = __esm({
29511
29794
  init_paths();
29512
29795
  init_version();
29513
29796
  BACKEND_SKILL_DIRS2 = {
29514
- "cc-claw": [join30(homedir9(), ".cc-claw", "workspace", "skills")],
29515
- claude: [join30(homedir9(), ".claude", "skills")],
29516
- gemini: [join30(homedir9(), ".gemini", "skills")],
29517
- codex: [join30(homedir9(), ".agents", "skills")],
29518
- cursor: [join30(homedir9(), ".cursor", "skills"), join30(homedir9(), ".cursor", "skills-cursor")]
29797
+ "cc-claw": [join31(homedir9(), ".cc-claw", "workspace", "skills")],
29798
+ claude: [join31(homedir9(), ".claude", "skills")],
29799
+ gemini: [join31(homedir9(), ".gemini", "skills")],
29800
+ codex: [join31(homedir9(), ".agents", "skills")],
29801
+ cursor: [join31(homedir9(), ".cursor", "skills"), join31(homedir9(), ".cursor", "skills-cursor")]
29519
29802
  };
29520
29803
  }
29521
29804
  });
@@ -29525,21 +29808,21 @@ var index_exports = {};
29525
29808
  __export(index_exports, {
29526
29809
  main: () => main
29527
29810
  });
29528
- import { mkdirSync as mkdirSync12, existsSync as existsSync28, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync17 } from "fs";
29529
- import { join as join31 } from "path";
29811
+ import { mkdirSync as mkdirSync12, existsSync as existsSync29, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync18 } from "fs";
29812
+ import { join as join32 } from "path";
29530
29813
  import dotenv from "dotenv";
29531
29814
  function migrateLayout() {
29532
29815
  const moves = [
29533
- [join31(CC_CLAW_HOME, "cc-claw.db"), join31(DATA_PATH, "cc-claw.db")],
29534
- [join31(CC_CLAW_HOME, "cc-claw.db-shm"), join31(DATA_PATH, "cc-claw.db-shm")],
29535
- [join31(CC_CLAW_HOME, "cc-claw.db-wal"), join31(DATA_PATH, "cc-claw.db-wal")],
29536
- [join31(CC_CLAW_HOME, "cc-claw.log"), join31(LOGS_PATH, "cc-claw.log")],
29537
- [join31(CC_CLAW_HOME, "cc-claw.log.1"), join31(LOGS_PATH, "cc-claw.log.1")],
29538
- [join31(CC_CLAW_HOME, "cc-claw.error.log"), join31(LOGS_PATH, "cc-claw.error.log")],
29539
- [join31(CC_CLAW_HOME, "cc-claw.error.log.1"), join31(LOGS_PATH, "cc-claw.error.log.1")]
29816
+ [join32(CC_CLAW_HOME, "cc-claw.db"), join32(DATA_PATH, "cc-claw.db")],
29817
+ [join32(CC_CLAW_HOME, "cc-claw.db-shm"), join32(DATA_PATH, "cc-claw.db-shm")],
29818
+ [join32(CC_CLAW_HOME, "cc-claw.db-wal"), join32(DATA_PATH, "cc-claw.db-wal")],
29819
+ [join32(CC_CLAW_HOME, "cc-claw.log"), join32(LOGS_PATH, "cc-claw.log")],
29820
+ [join32(CC_CLAW_HOME, "cc-claw.log.1"), join32(LOGS_PATH, "cc-claw.log.1")],
29821
+ [join32(CC_CLAW_HOME, "cc-claw.error.log"), join32(LOGS_PATH, "cc-claw.error.log")],
29822
+ [join32(CC_CLAW_HOME, "cc-claw.error.log.1"), join32(LOGS_PATH, "cc-claw.error.log.1")]
29540
29823
  ];
29541
29824
  for (const [from, to] of moves) {
29542
- if (existsSync28(from) && !existsSync28(to)) {
29825
+ if (existsSync29(from) && !existsSync29(to)) {
29543
29826
  try {
29544
29827
  renameSync2(from, to);
29545
29828
  } catch {
@@ -29568,7 +29851,7 @@ async function main() {
29568
29851
  let version = "unknown";
29569
29852
  try {
29570
29853
  const pkgPath = new URL("../package.json", import.meta.url);
29571
- version = JSON.parse(readFileSync17(pkgPath, "utf-8")).version;
29854
+ version = JSON.parse(readFileSync18(pkgPath, "utf-8")).version;
29572
29855
  } catch {
29573
29856
  }
29574
29857
  log(`[cc-claw] Starting v${version}`);
@@ -29716,10 +29999,10 @@ async function main() {
29716
29999
  try {
29717
30000
  const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
29718
30001
  const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync19 } = await import("fs");
29719
- const { join: join37 } = await import("path");
29720
- const skillDir = join37(SKILLS_PATH, "cc-claw-cli");
30002
+ const { join: join38 } = await import("path");
30003
+ const skillDir = join38(SKILLS_PATH, "cc-claw-cli");
29721
30004
  mkdirSync19(skillDir, { recursive: true });
29722
- writeFileSync13(join37(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
30005
+ writeFileSync13(join38(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
29723
30006
  log("[cc-claw] AI skill updated");
29724
30007
  } catch {
29725
30008
  }
@@ -29819,10 +30102,10 @@ var init_index = __esm({
29819
30102
  init_health3();
29820
30103
  init_image_gen();
29821
30104
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SESSION_LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
29822
- if (!existsSync28(dir)) mkdirSync12(dir, { recursive: true });
30105
+ if (!existsSync29(dir)) mkdirSync12(dir, { recursive: true });
29823
30106
  }
29824
30107
  migrateLayout();
29825
- if (existsSync28(ENV_PATH)) {
30108
+ if (existsSync29(ENV_PATH)) {
29826
30109
  dotenv.config({ path: ENV_PATH });
29827
30110
  } else {
29828
30111
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -29843,12 +30126,12 @@ __export(api_client_exports, {
29843
30126
  apiPost: () => apiPost,
29844
30127
  isDaemonRunning: () => isDaemonRunning
29845
30128
  });
29846
- import { readFileSync as readFileSync18, existsSync as existsSync29 } from "fs";
30129
+ import { readFileSync as readFileSync19, existsSync as existsSync30 } from "fs";
29847
30130
  import { request as httpRequest, Agent } from "http";
29848
30131
  function getToken() {
29849
30132
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
29850
30133
  try {
29851
- if (existsSync29(TOKEN_PATH)) return readFileSync18(TOKEN_PATH, "utf-8").trim();
30134
+ if (existsSync30(TOKEN_PATH)) return readFileSync19(TOKEN_PATH, "utf-8").trim();
29852
30135
  } catch {
29853
30136
  }
29854
30137
  return null;
@@ -29947,10 +30230,10 @@ __export(service_exports2, {
29947
30230
  serviceStatus: () => serviceStatus,
29948
30231
  uninstallService: () => uninstallService
29949
30232
  });
29950
- import { existsSync as existsSync30, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
30233
+ import { existsSync as existsSync31, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
29951
30234
  import { execFileSync as execFileSync4, execSync as execSync5 } from "child_process";
29952
30235
  import { homedir as homedir10, platform } from "os";
29953
- import { join as join32, dirname as dirname6 } from "path";
30236
+ import { join as join33, dirname as dirname6 } from "path";
29954
30237
  function xmlEscape(s) {
29955
30238
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
29956
30239
  }
@@ -29959,7 +30242,7 @@ function resolveExecutable3(name) {
29959
30242
  return execFileSync4("which", [name], { encoding: "utf-8" }).trim();
29960
30243
  } catch {
29961
30244
  const fallback = process.argv[1];
29962
- if (fallback && existsSync30(fallback)) return fallback;
30245
+ if (fallback && existsSync31(fallback)) return fallback;
29963
30246
  throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
29964
30247
  }
29965
30248
  }
@@ -29968,14 +30251,14 @@ function getPathDirs() {
29968
30251
  const home = homedir10();
29969
30252
  const dirs = /* @__PURE__ */ new Set([
29970
30253
  nodeBin,
29971
- join32(home, ".local", "bin"),
30254
+ join33(home, ".local", "bin"),
29972
30255
  "/usr/local/bin",
29973
30256
  "/usr/bin",
29974
30257
  "/bin"
29975
30258
  ]);
29976
30259
  try {
29977
30260
  const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
29978
- if (prefix) dirs.add(join32(prefix, "bin"));
30261
+ if (prefix) dirs.add(join33(prefix, "bin"));
29979
30262
  } catch {
29980
30263
  }
29981
30264
  return [...dirs].join(":");
@@ -30034,9 +30317,9 @@ function generatePlist() {
30034
30317
  }
30035
30318
  function installMacOS() {
30036
30319
  const agentsDir = dirname6(PLIST_PATH);
30037
- if (!existsSync30(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
30038
- if (!existsSync30(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30039
- if (existsSync30(PLIST_PATH)) {
30320
+ if (!existsSync31(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
30321
+ if (!existsSync31(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30322
+ if (existsSync31(PLIST_PATH)) {
30040
30323
  try {
30041
30324
  execFileSync4("launchctl", ["unload", PLIST_PATH]);
30042
30325
  } catch {
@@ -30048,7 +30331,7 @@ function installMacOS() {
30048
30331
  console.log(" Service loaded and starting.");
30049
30332
  }
30050
30333
  function uninstallMacOS() {
30051
- if (!existsSync30(PLIST_PATH)) {
30334
+ if (!existsSync31(PLIST_PATH)) {
30052
30335
  console.log(" No service found to uninstall.");
30053
30336
  return;
30054
30337
  }
@@ -30123,8 +30406,8 @@ WantedBy=default.target
30123
30406
  `;
30124
30407
  }
30125
30408
  function installLinux() {
30126
- if (!existsSync30(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
30127
- if (!existsSync30(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30409
+ if (!existsSync31(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
30410
+ if (!existsSync31(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30128
30411
  writeFileSync9(UNIT_PATH, generateUnit());
30129
30412
  console.log(` Installed: ${UNIT_PATH}`);
30130
30413
  execFileSync4("systemctl", ["--user", "daemon-reload"]);
@@ -30133,7 +30416,7 @@ function installLinux() {
30133
30416
  console.log(" Service enabled and started.");
30134
30417
  }
30135
30418
  function uninstallLinux() {
30136
- if (!existsSync30(UNIT_PATH)) {
30419
+ if (!existsSync31(UNIT_PATH)) {
30137
30420
  console.log(" No service found to uninstall.");
30138
30421
  return;
30139
30422
  }
@@ -30158,7 +30441,7 @@ function statusLinux() {
30158
30441
  }
30159
30442
  }
30160
30443
  function installService() {
30161
- if (!existsSync30(join32(CC_CLAW_HOME, ".env"))) {
30444
+ if (!existsSync31(join33(CC_CLAW_HOME, ".env"))) {
30162
30445
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
30163
30446
  console.error(" Run 'cc-claw setup' before installing the service.");
30164
30447
  process.exitCode = 1;
@@ -30187,9 +30470,9 @@ var init_service2 = __esm({
30187
30470
  "use strict";
30188
30471
  init_paths();
30189
30472
  PLIST_LABEL = "com.cc-claw";
30190
- PLIST_PATH = join32(homedir10(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
30191
- SYSTEMD_DIR = join32(homedir10(), ".config", "systemd", "user");
30192
- UNIT_PATH = join32(SYSTEMD_DIR, "cc-claw.service");
30473
+ PLIST_PATH = join33(homedir10(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
30474
+ SYSTEMD_DIR = join33(homedir10(), ".config", "systemd", "user");
30475
+ UNIT_PATH = join33(SYSTEMD_DIR, "cc-claw.service");
30193
30476
  }
30194
30477
  });
30195
30478
 
@@ -30356,13 +30639,13 @@ var init_daemon = __esm({
30356
30639
  });
30357
30640
 
30358
30641
  // src/cli/resolve-chat.ts
30359
- import { readFileSync as readFileSync20 } from "fs";
30642
+ import { readFileSync as readFileSync21 } from "fs";
30360
30643
  function resolveChatId2(globalOpts) {
30361
30644
  const explicit = globalOpts.chat;
30362
30645
  if (explicit) return explicit;
30363
30646
  if (_cachedDefault) return _cachedDefault;
30364
30647
  try {
30365
- const content = readFileSync20(ENV_PATH, "utf-8");
30648
+ const content = readFileSync21(ENV_PATH, "utf-8");
30366
30649
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
30367
30650
  if (match) {
30368
30651
  _cachedDefault = match[1].split(",")[0].trim();
@@ -30386,7 +30669,7 @@ var status_exports = {};
30386
30669
  __export(status_exports, {
30387
30670
  statusCommand: () => statusCommand
30388
30671
  });
30389
- import { existsSync as existsSync31, statSync as statSync10 } from "fs";
30672
+ import { existsSync as existsSync32, statSync as statSync10 } from "fs";
30390
30673
  async function statusCommand(globalOpts, localOpts) {
30391
30674
  try {
30392
30675
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
@@ -30426,7 +30709,7 @@ async function statusCommand(globalOpts, localOpts) {
30426
30709
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
30427
30710
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
30428
30711
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
30429
- const dbStat = existsSync31(DB_PATH) ? statSync10(DB_PATH) : null;
30712
+ const dbStat = existsSync32(DB_PATH) ? statSync10(DB_PATH) : null;
30430
30713
  let daemonRunning = false;
30431
30714
  let daemonInfo = {};
30432
30715
  try {
@@ -30538,13 +30821,13 @@ __export(doctor_exports, {
30538
30821
  doctorCommand: () => doctorCommand,
30539
30822
  doctorErrors: () => doctorErrors
30540
30823
  });
30541
- import { existsSync as existsSync32, accessSync, constants } from "fs";
30824
+ import { existsSync as existsSync33, accessSync, constants } from "fs";
30542
30825
  import { execFileSync as execFileSync5 } from "child_process";
30543
30826
  async function doctorCommand(globalOpts, localOpts) {
30544
30827
  const checks = [];
30545
30828
  const dbChecks = checkDatabase();
30546
30829
  checks.push(...dbChecks);
30547
- if (existsSync32(DB_PATH)) {
30830
+ if (existsSync33(DB_PATH)) {
30548
30831
  try {
30549
30832
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30550
30833
  const readDb = openDatabaseReadOnly2();
@@ -30570,7 +30853,7 @@ async function doctorCommand(globalOpts, localOpts) {
30570
30853
  checks.push({ name: "Database health", status: "error", message: err.message });
30571
30854
  }
30572
30855
  }
30573
- if (existsSync32(ENV_PATH)) {
30856
+ if (existsSync33(ENV_PATH)) {
30574
30857
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
30575
30858
  } else {
30576
30859
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
@@ -30613,7 +30896,7 @@ async function doctorCommand(globalOpts, localOpts) {
30613
30896
  } catch {
30614
30897
  }
30615
30898
  const tokenPath = `${DATA_PATH}/api-token`;
30616
- if (existsSync32(tokenPath)) {
30899
+ if (existsSync33(tokenPath)) {
30617
30900
  try {
30618
30901
  accessSync(tokenPath, constants.R_OK);
30619
30902
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -30679,7 +30962,7 @@ async function doctorCommand(globalOpts, localOpts) {
30679
30962
  const errorChecks = checks.filter(
30680
30963
  (c) => ["Rate limits", "Content silence", "Spawn timeouts", "Other errors"].includes(c.name) && c.status !== "ok"
30681
30964
  );
30682
- if (errorChecks.length > 0 && existsSync32(ERROR_LOG_PATH)) {
30965
+ if (errorChecks.length > 0 && existsSync33(ERROR_LOG_PATH)) {
30683
30966
  try {
30684
30967
  const { writeFileSync: writeFileSync13 } = await import("fs");
30685
30968
  writeFileSync13(ERROR_LOG_PATH, "");
@@ -30808,15 +31091,15 @@ var logs_exports = {};
30808
31091
  __export(logs_exports, {
30809
31092
  logsCommand: () => logsCommand
30810
31093
  });
30811
- import { existsSync as existsSync33, readFileSync as readFileSync22, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
31094
+ import { existsSync as existsSync34, readFileSync as readFileSync23, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
30812
31095
  async function logsCommand(opts) {
30813
31096
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
30814
- if (!existsSync33(logFile)) {
31097
+ if (!existsSync34(logFile)) {
30815
31098
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
30816
31099
  process.exit(1);
30817
31100
  }
30818
31101
  const maxLines = parseInt(opts.lines ?? "100", 10);
30819
- const content = readFileSync22(logFile, "utf-8");
31102
+ const content = readFileSync23(logFile, "utf-8");
30820
31103
  const allLines = content.split("\n");
30821
31104
  const tailLines = allLines.slice(-maxLines);
30822
31105
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -30826,7 +31109,7 @@ async function logsCommand(opts) {
30826
31109
  let lastLength = content.length;
30827
31110
  watchFile2(logFile, { interval: 500 }, () => {
30828
31111
  try {
30829
- const newContent = readFileSync22(logFile, "utf-8");
31112
+ const newContent = readFileSync23(logFile, "utf-8");
30830
31113
  if (newContent.length > lastLength) {
30831
31114
  const newPart = newContent.slice(lastLength);
30832
31115
  process.stdout.write(newPart);
@@ -30858,7 +31141,7 @@ __export(session_logs_exports, {
30858
31141
  sessionLogsList: () => sessionLogsList,
30859
31142
  sessionLogsTail: () => sessionLogsTail
30860
31143
  });
30861
- import { readFileSync as readFileSync23, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
31144
+ import { readFileSync as readFileSync24, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
30862
31145
  async function sessionLogsList(opts) {
30863
31146
  const logs = listSessionLogs();
30864
31147
  if (logs.length === 0) {
@@ -30915,12 +31198,12 @@ async function sessionLogsTail(opts) {
30915
31198
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
30916
31199
  let lastLength = 0;
30917
31200
  try {
30918
- lastLength = readFileSync23(targetPath, "utf-8").length;
31201
+ lastLength = readFileSync24(targetPath, "utf-8").length;
30919
31202
  } catch {
30920
31203
  }
30921
31204
  watchFile3(targetPath, { interval: 500 }, () => {
30922
31205
  try {
30923
- const content = readFileSync23(targetPath, "utf-8");
31206
+ const content = readFileSync24(targetPath, "utf-8");
30924
31207
  if (content.length > lastLength) {
30925
31208
  process.stdout.write(content.slice(lastLength));
30926
31209
  lastLength = content.length;
@@ -30964,11 +31247,11 @@ __export(gemini_exports, {
30964
31247
  geminiReorder: () => geminiReorder,
30965
31248
  geminiRotation: () => geminiRotation
30966
31249
  });
30967
- import { existsSync as existsSync35, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync24, chmodSync } from "fs";
30968
- import { join as join33 } from "path";
31250
+ import { existsSync as existsSync36, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync25, chmodSync } from "fs";
31251
+ import { join as join34 } from "path";
30969
31252
  import { createInterface as createInterface8 } from "readline";
30970
31253
  function requireDb() {
30971
- if (!existsSync35(DB_PATH)) {
31254
+ if (!existsSync36(DB_PATH)) {
30972
31255
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30973
31256
  process.exit(1);
30974
31257
  }
@@ -30993,9 +31276,9 @@ async function resolveSlotId(idOrLabel) {
30993
31276
  function resolveOAuthEmail(configHome) {
30994
31277
  if (!configHome) return null;
30995
31278
  try {
30996
- const accountsPath = join33(configHome, ".gemini", "google_accounts.json");
30997
- if (!existsSync35(accountsPath)) return null;
30998
- const accounts = JSON.parse(readFileSync24(accountsPath, "utf-8"));
31279
+ const accountsPath = join34(configHome, ".gemini", "google_accounts.json");
31280
+ if (!existsSync36(accountsPath)) return null;
31281
+ const accounts = JSON.parse(readFileSync25(accountsPath, "utf-8"));
30999
31282
  return accounts.active || null;
31000
31283
  } catch {
31001
31284
  return null;
@@ -31077,14 +31360,14 @@ async function geminiAddKey(globalOpts, opts) {
31077
31360
  }
31078
31361
  async function geminiAddAccount(globalOpts, opts) {
31079
31362
  await requireWriteDb();
31080
- const slotsDir = join33(CC_CLAW_HOME, "gemini-slots");
31081
- if (!existsSync35(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
31363
+ const slotsDir = join34(CC_CLAW_HOME, "gemini-slots");
31364
+ if (!existsSync36(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
31082
31365
  const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
31083
31366
  const tempId = Date.now();
31084
- const slotDir = join33(slotsDir, `slot-${tempId}`);
31367
+ const slotDir = join34(slotsDir, `slot-${tempId}`);
31085
31368
  mkdirSync14(slotDir, { recursive: true, mode: 448 });
31086
- mkdirSync14(join33(slotDir, ".gemini"), { recursive: true });
31087
- writeFileSync10(join33(slotDir, ".gemini", "settings.json"), JSON.stringify({
31369
+ mkdirSync14(join34(slotDir, ".gemini"), { recursive: true });
31370
+ writeFileSync10(join34(slotDir, ".gemini", "settings.json"), JSON.stringify({
31088
31371
  security: { auth: { selectedType: "oauth-personal" } }
31089
31372
  }, null, 2));
31090
31373
  console.log("");
@@ -31101,8 +31384,8 @@ async function geminiAddAccount(globalOpts, opts) {
31101
31384
  });
31102
31385
  } catch {
31103
31386
  }
31104
- const oauthPath = join33(slotDir, ".gemini", "oauth_creds.json");
31105
- if (!existsSync35(oauthPath)) {
31387
+ const oauthPath = join34(slotDir, ".gemini", "oauth_creds.json");
31388
+ if (!existsSync36(oauthPath)) {
31106
31389
  console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
31107
31390
  console.log(" The slot directory is preserved at: " + slotDir);
31108
31391
  console.log(" Re-run: cc-claw gemini add-account\n");
@@ -31110,7 +31393,7 @@ async function geminiAddAccount(globalOpts, opts) {
31110
31393
  }
31111
31394
  let accountEmail = "unknown";
31112
31395
  try {
31113
- const accounts = JSON.parse(__require("fs").readFileSync(join33(slotDir, ".gemini", "google_accounts.json"), "utf-8"));
31396
+ const accounts = JSON.parse(__require("fs").readFileSync(join34(slotDir, ".gemini", "google_accounts.json"), "utf-8"));
31114
31397
  accountEmail = accounts.active || accountEmail;
31115
31398
  } catch {
31116
31399
  }
@@ -31221,9 +31504,9 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31221
31504
  outputError("NO_CONFIG", `Slot "${idOrLabel}" has no config directory \u2014 cannot re-login.`);
31222
31505
  return;
31223
31506
  }
31224
- const settingsPath = join33(slot.configHome, ".gemini", "settings.json");
31225
- if (!existsSync35(settingsPath)) {
31226
- mkdirSync14(join33(slot.configHome, ".gemini"), { recursive: true });
31507
+ const settingsPath = join34(slot.configHome, ".gemini", "settings.json");
31508
+ if (!existsSync36(settingsPath)) {
31509
+ mkdirSync14(join34(slot.configHome, ".gemini"), { recursive: true });
31227
31510
  writeFileSync10(settingsPath, JSON.stringify({
31228
31511
  security: { auth: { selectedType: "oauth-personal" } }
31229
31512
  }, null, 2));
@@ -31247,8 +31530,8 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31247
31530
  });
31248
31531
  } catch {
31249
31532
  }
31250
- const oauthPath = join33(slot.configHome, ".gemini", "oauth_creds.json");
31251
- if (!existsSync35(oauthPath)) {
31533
+ const oauthPath = join34(slot.configHome, ".gemini", "oauth_creds.json");
31534
+ if (!existsSync36(oauthPath)) {
31252
31535
  console.log(error2("\n Re-login failed \u2014 no OAuth credentials found."));
31253
31536
  console.log(` Try again: cc-claw gemini re-login ${idOrLabel}
31254
31537
  `);
@@ -31258,7 +31541,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31258
31541
  setGeminiSlotEnabled2(slotId, true);
31259
31542
  let accountEmail = slot.label;
31260
31543
  try {
31261
- const accounts = JSON.parse(readFileSync24(join33(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
31544
+ const accounts = JSON.parse(readFileSync25(join34(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
31262
31545
  if (accounts.active) accountEmail = accounts.active;
31263
31546
  } catch {
31264
31547
  }
@@ -31317,11 +31600,11 @@ __export(backend_cmd_factory_exports, {
31317
31600
  makeReorder: () => makeReorder,
31318
31601
  registerBackendSlotCommands: () => registerBackendSlotCommands
31319
31602
  });
31320
- import { existsSync as existsSync36, mkdirSync as mkdirSync15, readFileSync as readFileSync25 } from "fs";
31321
- import { join as join34 } from "path";
31603
+ import { existsSync as existsSync37, mkdirSync as mkdirSync15, readFileSync as readFileSync26 } from "fs";
31604
+ import { join as join35 } from "path";
31322
31605
  import { createInterface as createInterface9 } from "readline";
31323
31606
  function requireDb2() {
31324
- if (!existsSync36(DB_PATH)) {
31607
+ if (!existsSync37(DB_PATH)) {
31325
31608
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31326
31609
  process.exit(1);
31327
31610
  }
@@ -31410,10 +31693,10 @@ function makeAddAccount(backend2, displayName) {
31410
31693
  process.exit(1);
31411
31694
  }
31412
31695
  await requireWriteDb2();
31413
- const slotsDir = join34(CC_CLAW_HOME, config2.slotsSubdir);
31414
- if (!existsSync36(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
31696
+ const slotsDir = join35(CC_CLAW_HOME, config2.slotsSubdir);
31697
+ if (!existsSync37(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
31415
31698
  const tempId = Date.now();
31416
- const slotDir = join34(slotsDir, `slot-${tempId}`);
31699
+ const slotDir = join35(slotsDir, `slot-${tempId}`);
31417
31700
  mkdirSync15(slotDir, { recursive: true, mode: 448 });
31418
31701
  if (config2.preSetup) config2.preSetup(slotDir);
31419
31702
  console.log("");
@@ -31664,22 +31947,22 @@ var init_backend_cmd_factory = __esm({
31664
31947
  envValue: (slotDir) => slotDir,
31665
31948
  envOverrides: { ANTHROPIC_API_KEY: void 0 },
31666
31949
  preSetup: (slotDir) => {
31667
- mkdirSync15(join34(slotDir, ".claude"), { recursive: true });
31950
+ mkdirSync15(join35(slotDir, ".claude"), { recursive: true });
31668
31951
  },
31669
31952
  verifyCredentials: (slotDir) => {
31670
- const claudeJson = join34(slotDir, ".claude.json");
31671
- const claudeJsonNested = join34(slotDir, ".claude", ".claude.json");
31672
- if (existsSync36(claudeJson)) {
31953
+ const claudeJson = join35(slotDir, ".claude.json");
31954
+ const claudeJsonNested = join35(slotDir, ".claude", ".claude.json");
31955
+ if (existsSync37(claudeJson)) {
31673
31956
  try {
31674
- const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
31957
+ const data = JSON.parse(readFileSync26(claudeJson, "utf-8"));
31675
31958
  return Boolean(data.oauthAccount);
31676
31959
  } catch {
31677
31960
  return false;
31678
31961
  }
31679
31962
  }
31680
- if (existsSync36(claudeJsonNested)) {
31963
+ if (existsSync37(claudeJsonNested)) {
31681
31964
  try {
31682
- const data = JSON.parse(readFileSync25(claudeJsonNested, "utf-8"));
31965
+ const data = JSON.parse(readFileSync26(claudeJsonNested, "utf-8"));
31683
31966
  return Boolean(data.oauthAccount);
31684
31967
  } catch {
31685
31968
  return false;
@@ -31700,9 +31983,9 @@ var init_backend_cmd_factory = __esm({
31700
31983
  } catch {
31701
31984
  }
31702
31985
  try {
31703
- const claudeJson = join34(slotDir, ".claude.json");
31704
- if (existsSync36(claudeJson)) {
31705
- const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
31986
+ const claudeJson = join35(slotDir, ".claude.json");
31987
+ if (existsSync37(claudeJson)) {
31988
+ const data = JSON.parse(readFileSync26(claudeJson, "utf-8"));
31706
31989
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
31707
31990
  }
31708
31991
  } catch {
@@ -31717,11 +32000,11 @@ var init_backend_cmd_factory = __esm({
31717
32000
  envValue: (slotDir) => slotDir,
31718
32001
  envOverrides: { OPENAI_API_KEY: void 0 },
31719
32002
  verifyCredentials: (slotDir) => {
31720
- return existsSync36(join34(slotDir, "auth.json"));
32003
+ return existsSync37(join35(slotDir, "auth.json"));
31721
32004
  },
31722
32005
  extractLabel: (slotDir) => {
31723
32006
  try {
31724
- const authData = JSON.parse(readFileSync25(join34(slotDir, "auth.json"), "utf-8"));
32007
+ const authData = JSON.parse(readFileSync26(join35(slotDir, "auth.json"), "utf-8"));
31725
32008
  if (authData.email) return authData.email;
31726
32009
  if (authData.account_name) return authData.account_name;
31727
32010
  if (authData.user?.email) return authData.user.email;
@@ -31745,9 +32028,9 @@ __export(ollama_exports3, {
31745
32028
  ollamaRemove: () => ollamaRemove,
31746
32029
  ollamaTest: () => ollamaTest
31747
32030
  });
31748
- import { existsSync as existsSync37 } from "fs";
32031
+ import { existsSync as existsSync38 } from "fs";
31749
32032
  function requireDb3() {
31750
- if (!existsSync37(DB_PATH)) {
32033
+ if (!existsSync38(DB_PATH)) {
31751
32034
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31752
32035
  process.exit(1);
31753
32036
  }
@@ -32006,12 +32289,12 @@ __export(backend_exports, {
32006
32289
  backendList: () => backendList,
32007
32290
  backendSet: () => backendSet
32008
32291
  });
32009
- import { existsSync as existsSync38 } from "fs";
32292
+ import { existsSync as existsSync39 } from "fs";
32010
32293
  async function backendList(globalOpts) {
32011
32294
  const { getAvailableAdapters: getAvailableAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
32012
32295
  const chatId = resolveChatId2(globalOpts);
32013
32296
  let activeBackend = null;
32014
- if (existsSync38(DB_PATH)) {
32297
+ if (existsSync39(DB_PATH)) {
32015
32298
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32016
32299
  const readDb = openDatabaseReadOnly2();
32017
32300
  try {
@@ -32042,7 +32325,7 @@ async function backendList(globalOpts) {
32042
32325
  }
32043
32326
  async function backendGet(globalOpts) {
32044
32327
  const chatId = resolveChatId2(globalOpts);
32045
- if (!existsSync38(DB_PATH)) {
32328
+ if (!existsSync39(DB_PATH)) {
32046
32329
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32047
32330
  process.exit(1);
32048
32331
  }
@@ -32086,13 +32369,13 @@ __export(model_exports, {
32086
32369
  modelList: () => modelList,
32087
32370
  modelSet: () => modelSet
32088
32371
  });
32089
- import { existsSync as existsSync39 } from "fs";
32372
+ import { existsSync as existsSync40 } from "fs";
32090
32373
  async function modelList(globalOpts) {
32091
32374
  const chatId = resolveChatId2(globalOpts);
32092
32375
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32093
32376
  const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
32094
32377
  let backendId = "claude";
32095
- if (existsSync39(DB_PATH)) {
32378
+ if (existsSync40(DB_PATH)) {
32096
32379
  const readDb = openDatabaseReadOnly2();
32097
32380
  try {
32098
32381
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -32125,7 +32408,7 @@ async function modelList(globalOpts) {
32125
32408
  }
32126
32409
  async function modelGet(globalOpts) {
32127
32410
  const chatId = resolveChatId2(globalOpts);
32128
- if (!existsSync39(DB_PATH)) {
32411
+ if (!existsSync40(DB_PATH)) {
32129
32412
  outputError("DB_NOT_FOUND", "Database not found.");
32130
32413
  process.exit(1);
32131
32414
  }
@@ -32169,9 +32452,9 @@ __export(memory_exports2, {
32169
32452
  memoryList: () => memoryList,
32170
32453
  memorySearch: () => memorySearch
32171
32454
  });
32172
- import { existsSync as existsSync40 } from "fs";
32455
+ import { existsSync as existsSync41 } from "fs";
32173
32456
  async function memoryList(globalOpts) {
32174
- if (!existsSync40(DB_PATH)) {
32457
+ if (!existsSync41(DB_PATH)) {
32175
32458
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32176
32459
  process.exit(1);
32177
32460
  }
@@ -32195,7 +32478,7 @@ async function memoryList(globalOpts) {
32195
32478
  });
32196
32479
  }
32197
32480
  async function memorySearch(globalOpts, query) {
32198
- if (!existsSync40(DB_PATH)) {
32481
+ if (!existsSync41(DB_PATH)) {
32199
32482
  outputError("DB_NOT_FOUND", "Database not found.");
32200
32483
  process.exit(1);
32201
32484
  }
@@ -32217,7 +32500,7 @@ async function memorySearch(globalOpts, query) {
32217
32500
  });
32218
32501
  }
32219
32502
  async function memoryHistory(globalOpts, opts) {
32220
- if (!existsSync40(DB_PATH)) {
32503
+ if (!existsSync41(DB_PATH)) {
32221
32504
  outputError("DB_NOT_FOUND", "Database not found.");
32222
32505
  process.exit(1);
32223
32506
  }
@@ -32265,7 +32548,7 @@ __export(cron_exports2, {
32265
32548
  cronList: () => cronList,
32266
32549
  cronRuns: () => cronRuns
32267
32550
  });
32268
- import { existsSync as existsSync41 } from "fs";
32551
+ import { existsSync as existsSync42 } from "fs";
32269
32552
  function parseFallbacks(raw) {
32270
32553
  return raw.slice(0, 3).map((f) => {
32271
32554
  const [backend2, ...rest] = f.split(":");
@@ -32286,7 +32569,7 @@ function parseAndValidateTimeout(raw) {
32286
32569
  return val;
32287
32570
  }
32288
32571
  async function cronList(globalOpts) {
32289
- if (!existsSync41(DB_PATH)) {
32572
+ if (!existsSync42(DB_PATH)) {
32290
32573
  outputError("DB_NOT_FOUND", "Database not found.");
32291
32574
  process.exit(1);
32292
32575
  }
@@ -32324,7 +32607,7 @@ async function cronList(globalOpts) {
32324
32607
  });
32325
32608
  }
32326
32609
  async function cronHealth(globalOpts) {
32327
- if (!existsSync41(DB_PATH)) {
32610
+ if (!existsSync42(DB_PATH)) {
32328
32611
  outputError("DB_NOT_FOUND", "Database not found.");
32329
32612
  process.exit(1);
32330
32613
  }
@@ -32485,7 +32768,7 @@ async function cronEdit(globalOpts, id, opts) {
32485
32768
  }
32486
32769
  }
32487
32770
  async function cronRuns(globalOpts, jobId, opts) {
32488
- if (!existsSync41(DB_PATH)) {
32771
+ if (!existsSync42(DB_PATH)) {
32489
32772
  outputError("DB_NOT_FOUND", "Database not found.");
32490
32773
  process.exit(1);
32491
32774
  }
@@ -32532,9 +32815,9 @@ __export(agents_exports, {
32532
32815
  runnersList: () => runnersList,
32533
32816
  tasksList: () => tasksList
32534
32817
  });
32535
- import { existsSync as existsSync42 } from "fs";
32818
+ import { existsSync as existsSync43 } from "fs";
32536
32819
  async function agentsList(globalOpts) {
32537
- if (!existsSync42(DB_PATH)) {
32820
+ if (!existsSync43(DB_PATH)) {
32538
32821
  outputError("DB_NOT_FOUND", "Database not found.");
32539
32822
  process.exit(1);
32540
32823
  }
@@ -32565,7 +32848,7 @@ async function agentsList(globalOpts) {
32565
32848
  });
32566
32849
  }
32567
32850
  async function tasksList(globalOpts) {
32568
- if (!existsSync42(DB_PATH)) {
32851
+ if (!existsSync43(DB_PATH)) {
32569
32852
  outputError("DB_NOT_FOUND", "Database not found.");
32570
32853
  process.exit(1);
32571
32854
  }
@@ -32693,10 +32976,10 @@ __export(db_exports, {
32693
32976
  dbPath: () => dbPath,
32694
32977
  dbStats: () => dbStats
32695
32978
  });
32696
- import { existsSync as existsSync43, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
32979
+ import { existsSync as existsSync44, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
32697
32980
  import { dirname as dirname7 } from "path";
32698
32981
  async function dbStats(globalOpts) {
32699
- if (!existsSync43(DB_PATH)) {
32982
+ if (!existsSync44(DB_PATH)) {
32700
32983
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
32701
32984
  process.exit(1);
32702
32985
  }
@@ -32704,7 +32987,7 @@ async function dbStats(globalOpts) {
32704
32987
  const readDb = openDatabaseReadOnly2();
32705
32988
  const mainSize = statSync11(DB_PATH).size;
32706
32989
  const walPath = DB_PATH + "-wal";
32707
- const walSize = existsSync43(walPath) ? statSync11(walPath).size : 0;
32990
+ const walSize = existsSync44(walPath) ? statSync11(walPath).size : 0;
32708
32991
  const tableNames = readDb.prepare(
32709
32992
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
32710
32993
  ).all();
@@ -32738,7 +33021,7 @@ async function dbPath(globalOpts) {
32738
33021
  output({ path: DB_PATH }, (d) => d.path);
32739
33022
  }
32740
33023
  async function dbBackup(globalOpts, destPath) {
32741
- if (!existsSync43(DB_PATH)) {
33024
+ if (!existsSync44(DB_PATH)) {
32742
33025
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
32743
33026
  process.exit(1);
32744
33027
  }
@@ -32747,7 +33030,7 @@ async function dbBackup(globalOpts, destPath) {
32747
33030
  mkdirSync16(dirname7(dest), { recursive: true });
32748
33031
  copyFileSync3(DB_PATH, dest);
32749
33032
  const walPath = DB_PATH + "-wal";
32750
- if (existsSync43(walPath)) copyFileSync3(walPath, dest + "-wal");
33033
+ if (existsSync44(walPath)) copyFileSync3(walPath, dest + "-wal");
32751
33034
  output({ path: dest, sizeBytes: statSync11(dest).size }, (d) => {
32752
33035
  const b = d;
32753
33036
  return `
@@ -32776,9 +33059,9 @@ __export(usage_exports, {
32776
33059
  usageCost: () => usageCost,
32777
33060
  usageTokens: () => usageTokens
32778
33061
  });
32779
- import { existsSync as existsSync44 } from "fs";
33062
+ import { existsSync as existsSync45 } from "fs";
32780
33063
  function ensureDb() {
32781
- if (!existsSync44(DB_PATH)) {
33064
+ if (!existsSync45(DB_PATH)) {
32782
33065
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32783
33066
  process.exit(1);
32784
33067
  }
@@ -32968,9 +33251,9 @@ __export(config_exports2, {
32968
33251
  configList: () => configList,
32969
33252
  configSet: () => configSet
32970
33253
  });
32971
- import { existsSync as existsSync45, readFileSync as readFileSync26 } from "fs";
33254
+ import { existsSync as existsSync46, readFileSync as readFileSync27 } from "fs";
32972
33255
  async function configList(globalOpts) {
32973
- if (!existsSync45(DB_PATH)) {
33256
+ if (!existsSync46(DB_PATH)) {
32974
33257
  outputError("DB_NOT_FOUND", "Database not found.");
32975
33258
  process.exit(1);
32976
33259
  }
@@ -33004,7 +33287,7 @@ async function configGet(globalOpts, key) {
33004
33287
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
33005
33288
  process.exit(1);
33006
33289
  }
33007
- if (!existsSync45(DB_PATH)) {
33290
+ if (!existsSync46(DB_PATH)) {
33008
33291
  outputError("DB_NOT_FOUND", "Database not found.");
33009
33292
  process.exit(1);
33010
33293
  }
@@ -33050,11 +33333,11 @@ async function configSet(globalOpts, key, value) {
33050
33333
  }
33051
33334
  }
33052
33335
  async function configEnv(_globalOpts) {
33053
- if (!existsSync45(ENV_PATH)) {
33336
+ if (!existsSync46(ENV_PATH)) {
33054
33337
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
33055
33338
  process.exit(1);
33056
33339
  }
33057
- const content = readFileSync26(ENV_PATH, "utf-8");
33340
+ const content = readFileSync27(ENV_PATH, "utf-8");
33058
33341
  const entries = {};
33059
33342
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
33060
33343
  for (const line of content.split("\n")) {
@@ -33104,9 +33387,9 @@ __export(session_exports, {
33104
33387
  sessionGet: () => sessionGet,
33105
33388
  sessionNew: () => sessionNew
33106
33389
  });
33107
- import { existsSync as existsSync46 } from "fs";
33390
+ import { existsSync as existsSync47 } from "fs";
33108
33391
  async function sessionGet(globalOpts) {
33109
- if (!existsSync46(DB_PATH)) {
33392
+ if (!existsSync47(DB_PATH)) {
33110
33393
  outputError("DB_NOT_FOUND", "Database not found.");
33111
33394
  process.exit(1);
33112
33395
  }
@@ -33167,9 +33450,9 @@ __export(permissions_exports, {
33167
33450
  verboseGet: () => verboseGet,
33168
33451
  verboseSet: () => verboseSet
33169
33452
  });
33170
- import { existsSync as existsSync47 } from "fs";
33453
+ import { existsSync as existsSync48 } from "fs";
33171
33454
  function ensureDb2() {
33172
- if (!existsSync47(DB_PATH)) {
33455
+ if (!existsSync48(DB_PATH)) {
33173
33456
  outputError("DB_NOT_FOUND", "Database not found.");
33174
33457
  process.exit(1);
33175
33458
  }
@@ -33316,9 +33599,9 @@ __export(cwd_exports, {
33316
33599
  cwdGet: () => cwdGet,
33317
33600
  cwdSet: () => cwdSet
33318
33601
  });
33319
- import { existsSync as existsSync48 } from "fs";
33602
+ import { existsSync as existsSync49 } from "fs";
33320
33603
  async function cwdGet(globalOpts) {
33321
- if (!existsSync48(DB_PATH)) {
33604
+ if (!existsSync49(DB_PATH)) {
33322
33605
  outputError("DB_NOT_FOUND", "Database not found.");
33323
33606
  process.exit(1);
33324
33607
  }
@@ -33380,9 +33663,9 @@ __export(voice_exports, {
33380
33663
  voiceGet: () => voiceGet,
33381
33664
  voiceSet: () => voiceSet
33382
33665
  });
33383
- import { existsSync as existsSync49 } from "fs";
33666
+ import { existsSync as existsSync50 } from "fs";
33384
33667
  async function voiceGet(globalOpts) {
33385
- if (!existsSync49(DB_PATH)) {
33668
+ if (!existsSync50(DB_PATH)) {
33386
33669
  outputError("DB_NOT_FOUND", "Database not found.");
33387
33670
  process.exit(1);
33388
33671
  }
@@ -33431,9 +33714,9 @@ __export(heartbeat_exports2, {
33431
33714
  heartbeatGet: () => heartbeatGet,
33432
33715
  heartbeatSet: () => heartbeatSet
33433
33716
  });
33434
- import { existsSync as existsSync50 } from "fs";
33717
+ import { existsSync as existsSync51 } from "fs";
33435
33718
  async function heartbeatGet(globalOpts) {
33436
- if (!existsSync50(DB_PATH)) {
33719
+ if (!existsSync51(DB_PATH)) {
33437
33720
  outputError("DB_NOT_FOUND", "Database not found.");
33438
33721
  process.exit(1);
33439
33722
  }
@@ -33644,9 +33927,9 @@ __export(summarizer_exports, {
33644
33927
  summarizerGet: () => summarizerGet,
33645
33928
  summarizerSet: () => summarizerSet
33646
33929
  });
33647
- import { existsSync as existsSync51 } from "fs";
33930
+ import { existsSync as existsSync52 } from "fs";
33648
33931
  async function summarizerGet(globalOpts) {
33649
- if (!existsSync51(DB_PATH)) {
33932
+ if (!existsSync52(DB_PATH)) {
33650
33933
  outputError("DB_NOT_FOUND", "Database not found.");
33651
33934
  process.exit(1);
33652
33935
  }
@@ -33690,9 +33973,9 @@ __export(thinking_exports, {
33690
33973
  thinkingGet: () => thinkingGet,
33691
33974
  thinkingSet: () => thinkingSet
33692
33975
  });
33693
- import { existsSync as existsSync52 } from "fs";
33976
+ import { existsSync as existsSync53 } from "fs";
33694
33977
  async function thinkingGet(globalOpts) {
33695
- if (!existsSync52(DB_PATH)) {
33978
+ if (!existsSync53(DB_PATH)) {
33696
33979
  outputError("DB_NOT_FOUND", "Database not found.");
33697
33980
  process.exit(1);
33698
33981
  }
@@ -33736,9 +34019,9 @@ __export(chats_exports, {
33736
34019
  chatsList: () => chatsList,
33737
34020
  chatsRemoveAlias: () => chatsRemoveAlias
33738
34021
  });
33739
- import { existsSync as existsSync53 } from "fs";
34022
+ import { existsSync as existsSync54 } from "fs";
33740
34023
  async function chatsList(_globalOpts) {
33741
- if (!existsSync53(DB_PATH)) {
34024
+ if (!existsSync54(DB_PATH)) {
33742
34025
  outputError("DB_NOT_FOUND", "Database not found.");
33743
34026
  process.exit(1);
33744
34027
  }
@@ -33866,9 +34149,9 @@ var mcps_exports2 = {};
33866
34149
  __export(mcps_exports2, {
33867
34150
  mcpsList: () => mcpsList
33868
34151
  });
33869
- import { existsSync as existsSync54 } from "fs";
34152
+ import { existsSync as existsSync55 } from "fs";
33870
34153
  async function mcpsList(_globalOpts) {
33871
- if (!existsSync54(DB_PATH)) {
34154
+ if (!existsSync55(DB_PATH)) {
33872
34155
  outputError("DB_NOT_FOUND", "Database not found.");
33873
34156
  process.exit(1);
33874
34157
  }
@@ -33905,11 +34188,11 @@ __export(chat_exports2, {
33905
34188
  chatSend: () => chatSend
33906
34189
  });
33907
34190
  import { request as httpRequest2 } from "http";
33908
- import { readFileSync as readFileSync27, existsSync as existsSync55 } from "fs";
34191
+ import { readFileSync as readFileSync28, existsSync as existsSync56 } from "fs";
33909
34192
  function getToken2() {
33910
34193
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
33911
34194
  try {
33912
- if (existsSync55(TOKEN_PATH2)) return readFileSync27(TOKEN_PATH2, "utf-8").trim();
34195
+ if (existsSync56(TOKEN_PATH2)) return readFileSync28(TOKEN_PATH2, "utf-8").trim();
33913
34196
  } catch {
33914
34197
  }
33915
34198
  return null;
@@ -34397,7 +34680,7 @@ __export(completion_exports, {
34397
34680
  completionCommand: () => completionCommand
34398
34681
  });
34399
34682
  import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync17 } from "fs";
34400
- import { join as join35 } from "path";
34683
+ import { join as join36 } from "path";
34401
34684
  import { homedir as homedir11 } from "os";
34402
34685
  async function completionCommand(opts) {
34403
34686
  const shell = opts.shell ?? detectShell();
@@ -34413,10 +34696,10 @@ async function completionCommand(opts) {
34413
34696
  process.exit(1);
34414
34697
  }
34415
34698
  if (opts.install) {
34416
- const dir = join35(homedir11(), ".config", "cc-claw", "completions");
34699
+ const dir = join36(homedir11(), ".config", "cc-claw", "completions");
34417
34700
  mkdirSync17(dir, { recursive: true });
34418
34701
  const filename = shell === "zsh" ? "_cc-claw" : shell === "fish" ? "cc-claw.fish" : "cc-claw.bash";
34419
- const filepath = join35(dir, filename);
34702
+ const filepath = join36(dir, filename);
34420
34703
  writeFileSync11(filepath, script, "utf-8");
34421
34704
  console.log(`\u2713 Completion script written to ${filepath}
34422
34705
  `);
@@ -34589,9 +34872,9 @@ __export(evolve_exports2, {
34589
34872
  evolveStatus: () => evolveStatus,
34590
34873
  evolveUndo: () => evolveUndo
34591
34874
  });
34592
- import { existsSync as existsSync56 } from "fs";
34875
+ import { existsSync as existsSync57 } from "fs";
34593
34876
  function ensureDb3() {
34594
- if (!existsSync56(DB_PATH)) {
34877
+ if (!existsSync57(DB_PATH)) {
34595
34878
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
34596
34879
  process.exit(1);
34597
34880
  }
@@ -35065,10 +35348,10 @@ var init_optimize2 = __esm({
35065
35348
 
35066
35349
  // src/setup.ts
35067
35350
  var setup_exports = {};
35068
- import { existsSync as existsSync57, writeFileSync as writeFileSync12, readFileSync as readFileSync28, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
35351
+ import { existsSync as existsSync58, writeFileSync as writeFileSync12, readFileSync as readFileSync29, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
35069
35352
  import { execFileSync as execFileSync6 } from "child_process";
35070
35353
  import { createInterface as createInterface11 } from "readline";
35071
- import { join as join36 } from "path";
35354
+ import { join as join37 } from "path";
35072
35355
  function divider2() {
35073
35356
  console.log(dim("\u2500".repeat(55)));
35074
35357
  }
@@ -35143,21 +35426,21 @@ async function setup() {
35143
35426
  }
35144
35427
  console.log("");
35145
35428
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
35146
- if (!existsSync57(dir)) mkdirSync18(dir, { recursive: true });
35429
+ if (!existsSync58(dir)) mkdirSync18(dir, { recursive: true });
35147
35430
  }
35148
35431
  const env = {};
35149
- const envSource = existsSync57(ENV_PATH) ? ENV_PATH : existsSync57(".env") ? ".env" : null;
35432
+ const envSource = existsSync58(ENV_PATH) ? ENV_PATH : existsSync58(".env") ? ".env" : null;
35150
35433
  if (envSource) {
35151
35434
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
35152
35435
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
35153
- const existing = readFileSync28(envSource, "utf-8");
35436
+ const existing = readFileSync29(envSource, "utf-8");
35154
35437
  for (const line of existing.split("\n")) {
35155
35438
  const match = line.match(/^([^#=]+)=(.*)$/);
35156
35439
  if (match) env[match[1].trim()] = match[2].trim();
35157
35440
  }
35158
35441
  }
35159
- const cwdDb = join36(process.cwd(), "cc-claw.db");
35160
- if (existsSync57(cwdDb) && !existsSync57(DB_PATH)) {
35442
+ const cwdDb = join37(process.cwd(), "cc-claw.db");
35443
+ if (existsSync58(cwdDb) && !existsSync58(DB_PATH)) {
35161
35444
  const { size } = statSync12(cwdDb);
35162
35445
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
35163
35446
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
@@ -35297,14 +35580,44 @@ async function setup() {
35297
35580
  }
35298
35581
  header(4, TOTAL_STEPS, "Optional Features");
35299
35582
  console.log(" These are optional \u2014 you can enable them later by editing .env\n");
35300
- if (await confirm("Enable voice messages? (requires a free Groq API key)")) {
35583
+ if (await confirm("Enable voice message transcription (speech-to-text)?")) {
35584
+ console.log("");
35585
+ console.log(dim(" Choose your transcription provider:\n"));
35586
+ console.log(" 1. Local Whisper \u2014 free, works offline, runs on your machine");
35587
+ console.log(" Requires: whisper-cli (brew install whisper-cpp on macOS)");
35588
+ console.log(" Models downloaded on first use (~75MB\u20131.5GB depending on quality)");
35589
+ console.log("");
35590
+ console.log(" 2. Groq \u2014 free cloud API, fast, no local install needed");
35591
+ console.log(" Requires: free Groq API key from https://console.groq.com/keys");
35301
35592
  console.log("");
35302
- console.log(dim(" Get a free Groq API key at: https://console.groq.com/keys\n"));
35303
- const groqKey = await requiredInput("Groq API key", env.GROQ_API_KEY);
35304
- env.GROQ_API_KEY = groqKey;
35305
- console.log(green(" Voice transcription (STT) enabled!"));
35593
+ console.log(" 3. Skip");
35594
+ console.log("");
35595
+ const sttChoice = await requiredInput("Enter choice (1/2/3)", "1");
35596
+ if (sttChoice === "1") {
35597
+ console.log("");
35598
+ console.log(dim(" Local Whisper model to use (select quality vs download size):"));
35599
+ console.log(" 1. tiny.en 75MB English only ~0.3s Basic");
35600
+ console.log(" 2. base.en 150MB English only ~0.8s Good");
35601
+ console.log(" 3. small.en 500MB English only ~1.5s Very good \u2B50 Recommended");
35602
+ console.log(" 4. small 500MB Multilingual ~2s Very good");
35603
+ console.log(" 5. medium.en 1.5GB English only ~5s Excellent");
35604
+ console.log(" 6. medium 1.5GB Multilingual ~6s Excellent");
35605
+ console.log("");
35606
+ const modelChoice = await requiredInput("Enter choice (1\u20136)", "3");
35607
+ const modelMap = { "1": "tiny.en", "2": "base.en", "3": "small.en", "4": "small", "5": "medium.en", "6": "medium" };
35608
+ env.STT_PROVIDER = "local-whisper";
35609
+ env.STT_MODEL = modelMap[modelChoice] ?? "small.en";
35610
+ console.log(green(` Local Whisper selected (${env.STT_MODEL}). Model will download on first voice message.`));
35611
+ } else if (sttChoice === "2") {
35612
+ const groqKey = await requiredInput("Groq API key", env.GROQ_API_KEY);
35613
+ env.GROQ_API_KEY = groqKey;
35614
+ env.STT_PROVIDER = "groq";
35615
+ console.log(green(" Groq transcription enabled!"));
35616
+ } else {
35617
+ console.log(dim(" Voice transcription skipped. Configure later via /voice in Telegram."));
35618
+ }
35306
35619
  console.log("");
35307
- console.log(dim(" Choose a voice reply provider:"));
35620
+ console.log(dim(" Choose a voice reply provider (text-to-speech):"));
35308
35621
  console.log(" 1. ElevenLabs (high-quality, requires API key)");
35309
35622
  console.log(" 2. Grok / xAI (high-quality, requires API key)");
35310
35623
  console.log(" 3. macOS (free, uses built-in voices \u2014 Samantha, Albert)");
@@ -35321,10 +35634,10 @@ async function setup() {
35321
35634
  const xaiKey = await requiredInput("xAI API key", env.XAI_API_KEY);
35322
35635
  env.XAI_API_KEY = xaiKey;
35323
35636
  console.log(green(" Voice replies via Grok enabled!"));
35324
- console.log(dim(" Use /voice_config in Telegram to select a voice (Eve, Ara, Rex, Sal, Leo)."));
35637
+ console.log(dim(" Use /voice in Telegram to select a voice (Eve, Ara, Rex, Sal, Leo)."));
35325
35638
  } else if (ttsChoice === "3") {
35326
35639
  console.log(green(" Voice replies via macOS enabled!"));
35327
- console.log(dim(" Use /voice_config in Telegram to select a voice (Samantha or Albert)."));
35640
+ console.log(dim(" Use /voice in Telegram to select a voice (Samantha or Albert)."));
35328
35641
  } else {
35329
35642
  console.log(dim(" Voice replies will use macOS text-to-speech as fallback."));
35330
35643
  }
@@ -35358,14 +35671,14 @@ async function setup() {
35358
35671
  `ANTHROPIC_VERTEX_PROJECT_ID=${env.ANTHROPIC_VERTEX_PROJECT_ID ?? ""}`
35359
35672
  );
35360
35673
  }
35361
- if (env.GROQ_API_KEY) {
35362
- envLines.push("", "# Voice", `GROQ_API_KEY=${env.GROQ_API_KEY}`);
35363
- if (env.ELEVENLABS_API_KEY) {
35364
- envLines.push(`ELEVENLABS_API_KEY=${env.ELEVENLABS_API_KEY}`);
35365
- }
35366
- if (env.XAI_API_KEY) {
35367
- envLines.push(`XAI_API_KEY=${env.XAI_API_KEY}`);
35368
- }
35674
+ const hasVoice = env.GROQ_API_KEY || env.STT_PROVIDER || env.ELEVENLABS_API_KEY || env.XAI_API_KEY;
35675
+ if (hasVoice) {
35676
+ envLines.push("", "# Voice");
35677
+ if (env.STT_PROVIDER) envLines.push(`STT_PROVIDER=${env.STT_PROVIDER}`);
35678
+ if (env.STT_MODEL) envLines.push(`STT_MODEL=${env.STT_MODEL}`);
35679
+ if (env.GROQ_API_KEY) envLines.push(`GROQ_API_KEY=${env.GROQ_API_KEY}`);
35680
+ if (env.ELEVENLABS_API_KEY) envLines.push(`ELEVENLABS_API_KEY=${env.ELEVENLABS_API_KEY}`);
35681
+ if (env.XAI_API_KEY) envLines.push(`XAI_API_KEY=${env.XAI_API_KEY}`);
35369
35682
  }
35370
35683
  if (env.DASHBOARD_ENABLED) {
35371
35684
  envLines.push("", "# Dashboard", `DASHBOARD_ENABLED=${env.DASHBOARD_ENABLED}`);
@@ -35409,7 +35722,9 @@ async function setup() {
35409
35722
  console.log(" " + green("[ok]") + ` Telegram bot: @${env.TELEGRAM_BOT_TOKEN ? "configured" : "missing"}`);
35410
35723
  console.log(" " + green("[ok]") + ` Chat ID: ${env.ALLOWED_CHAT_ID ?? "not set (anyone can message)"}`);
35411
35724
  console.log(" " + green("[ok]") + ` Vertex AI: ${env.ANTHROPIC_VERTEX_PROJECT_ID ?? "not configured"}`);
35412
- console.log(" " + (env.GROQ_API_KEY ? green("[ok]") : dim("[--]")) + " Voice transcription");
35725
+ const sttConfigured = env.STT_PROVIDER === "local-whisper" || !!env.GROQ_API_KEY;
35726
+ const sttLabel = env.STT_PROVIDER === "local-whisper" ? `Local Whisper (${env.STT_MODEL ?? "small.en"})` : env.GROQ_API_KEY ? "Groq" : "not configured";
35727
+ console.log(" " + (sttConfigured ? green("[ok]") : dim("[--]")) + ` Voice transcription: ${sttLabel}`);
35413
35728
  console.log(" " + (env.ELEVENLABS_API_KEY ? green("[ok]") : dim("[--]")) + " Voice replies");
35414
35729
  console.log(" " + (env.DASHBOARD_ENABLED ? green("[ok]") : dim("[--]")) + " Web dashboard");
35415
35730
  console.log(" " + (env.GEMINI_API_KEY ? green("[ok]") : dim("[--]")) + " Video analysis");