cc-claw 0.20.21 → 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 +831 -508
  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.21" : (() => {
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`;
@@ -13778,6 +13799,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
13778
13799
  const check = loopDetector.addCall(ev.toolName, ev.toolInput ?? {});
13779
13800
  if (check.isLoop) {
13780
13801
  warn(`[agent] Loop detected for ${adapter.id}: ${check.reason} \u2014 stopping`);
13802
+ loopKillReason = check.reason;
13781
13803
  killProcessGroup(proc, "SIGTERM");
13782
13804
  }
13783
13805
  }
@@ -13902,8 +13924,12 @@ Partial output: ${accumulatedText.slice(-500)}`;
13902
13924
  return;
13903
13925
  }
13904
13926
  if (code && code !== 0 && !cancelState.cancelled && !resultText) {
13905
- const stderr = Buffer.concat(stderrChunks).toString().trim();
13906
- 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
+ }
13907
13933
  return;
13908
13934
  }
13909
13935
  const cleanedResult = stripThinkingContent(resultText || accumulatedText);
@@ -14454,7 +14480,7 @@ var init_agent = __esm({
14454
14480
  chatLocks = /* @__PURE__ */ new Map();
14455
14481
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
14456
14482
  FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
14457
- 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);
14458
14484
  CONTENT_SILENCE_TIMEOUT_ERROR = "CONTENT_SILENCE_TIMEOUT";
14459
14485
  FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
14460
14486
  FREE_SLOTS_EXHAUSTED = "FREE_SLOTS_EXHAUSTED";
@@ -14976,15 +15002,17 @@ var init_telegram_throttle = __esm({
14976
15002
  * Used for cosmetic calls (typing indicators, reactions) that should count toward
14977
15003
  * rate limits but must never queue up or amplify 429 spirals.
14978
15004
  */
14979
- async tryBestEffort(chatId, label2, fn) {
15005
+ async tryBestEffort(chatId, label2, fn, opts) {
14980
15006
  if (this.isPaused()) return void 0;
14981
15007
  if (this.queue.length > 10) return void 0;
14982
- const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
14983
- if (Date.now() - lastChat < PER_CHAT_INTERVAL_MS) return void 0;
14984
- 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
+ }
14985
15013
  try {
14986
15014
  const result = await fn();
14987
- this.recordSend(chatId);
15015
+ if (!opts?.skipRecord) this.recordSend(chatId);
14988
15016
  return result;
14989
15017
  } catch (err) {
14990
15018
  if (is429(err)) {
@@ -15126,11 +15154,11 @@ var init_telegram_throttle = __esm({
15126
15154
  });
15127
15155
 
15128
15156
  // src/health/checks.ts
15129
- 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";
15130
15158
  import { execFileSync, execSync as execSync3 } from "child_process";
15131
15159
  function getRecentErrors() {
15132
15160
  if (!existsSync15(ERROR_LOG_PATH)) return null;
15133
- const logContent = readFileSync8(ERROR_LOG_PATH, "utf-8");
15161
+ const logContent = readFileSync9(ERROR_LOG_PATH, "utf-8");
15134
15162
  const allLines = logContent.split("\n").filter(Boolean).slice(-500);
15135
15163
  const last24h = Date.now() - 864e5;
15136
15164
  const lines = allLines.filter((line) => {
@@ -15359,7 +15387,7 @@ __export(heartbeat_exports, {
15359
15387
  stopHeartbeatForChat: () => stopHeartbeatForChat,
15360
15388
  updateHeartbeatConfig: () => updateHeartbeatConfig
15361
15389
  });
15362
- import { readFileSync as readFileSync9, existsSync as existsSync16 } from "fs";
15390
+ import { readFileSync as readFileSync10, existsSync as existsSync16 } from "fs";
15363
15391
  import { join as join15 } from "path";
15364
15392
  function findHeartbeatJob() {
15365
15393
  try {
@@ -15493,7 +15521,7 @@ ${watchLines.join("\n")}`);
15493
15521
  }
15494
15522
  if (existsSync16(HEARTBEAT_MD_PATH)) {
15495
15523
  try {
15496
- const custom = readFileSync9(HEARTBEAT_MD_PATH, "utf-8").trim();
15524
+ const custom = readFileSync10(HEARTBEAT_MD_PATH, "utf-8").trim();
15497
15525
  if (custom) {
15498
15526
  sections.push(`[Custom checks from HEARTBEAT.md]
15499
15527
  ${custom}`);
@@ -15562,7 +15590,7 @@ var init_heartbeat2 = __esm({
15562
15590
  });
15563
15591
 
15564
15592
  // src/bootstrap/profile.ts
15565
- 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";
15566
15594
  import { join as join16 } from "path";
15567
15595
  function hasActiveProfile(chatId) {
15568
15596
  return activeProfiles.has(chatId);
@@ -15693,7 +15721,7 @@ function extractUserUpdates(text) {
15693
15721
  }
15694
15722
  function appendToUserProfile(key, value) {
15695
15723
  if (!existsSync17(USER_PATH2)) return;
15696
- const content = readFileSync10(USER_PATH2, "utf-8");
15724
+ const content = readFileSync11(USER_PATH2, "utf-8");
15697
15725
  const line = `- **${key}**: ${value}`;
15698
15726
  if (content.includes(line)) return;
15699
15727
  const updated = content.trimEnd() + `
@@ -15802,8 +15830,9 @@ async function classifyWithOllama(text) {
15802
15830
  (a, b) => (a.sizeBytes ?? Infinity) - (b.sizeBytes ?? Infinity)
15803
15831
  );
15804
15832
  const model2 = sorted[0].name;
15833
+ const baseUrl = ollamaStore.getBaseUrl(onlineServer);
15805
15834
  const result = await ollamaClient.chat(
15806
- onlineServer.baseUrl,
15835
+ baseUrl,
15807
15836
  model2,
15808
15837
  [{ role: "user", content: LLM_CLASSIFY_PROMPT + text.slice(0, 500) }],
15809
15838
  { timeoutMs: LLM_CLASSIFY_TIMEOUT_MS, maxTokens: 5, temperature: 0 }
@@ -15828,7 +15857,7 @@ async function classifyWithSummarizerCli(text) {
15828
15857
  const model2 = modelName ?? adapter.summarizerModel;
15829
15858
  const { spawn: spawn8 } = await import("child_process");
15830
15859
  const { resolveExecutable: resolveExecutable4 } = await Promise.resolve().then(() => (init_resolve_executable(), resolve_executable_exports));
15831
- const exe = resolveExecutable4(adapter.id);
15860
+ const exe = resolveExecutable4({ envVar: `${adapter.id.toUpperCase()}_EXECUTABLE`, binaryName: adapter.id, candidates: [] });
15832
15861
  if (!exe) return null;
15833
15862
  return new Promise((resolve) => {
15834
15863
  const timeout = setTimeout(() => {
@@ -17701,7 +17730,9 @@ var init_gate = __esm({
17701
17730
  // src/voice/stt.ts
17702
17731
  import crypto from "crypto";
17703
17732
  import { execFile as execFile2, execFileSync as execFileSync2 } from "child_process";
17704
- 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";
17705
17736
  import { promisify as promisify2 } from "util";
17706
17737
  function ensureFfmpeg() {
17707
17738
  if (ffmpegAvailable === true) return;
@@ -17746,11 +17777,120 @@ function setVoiceProvider(chatId, provider, voiceId) {
17746
17777
  ON CONFLICT(chat_id) DO UPDATE SET provider = ?, voice_id = ?, enabled = 1
17747
17778
  `).run(chatId, provider, voiceId, provider, voiceId);
17748
17779
  }
17749
- 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
+ }
17750
17890
  const GROQ_API_KEY = process.env.GROQ_API_KEY;
17751
17891
  if (!GROQ_API_KEY) return null;
17752
17892
  const formData = new FormData();
17753
- 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");
17754
17894
  formData.append("model", "whisper-large-v3");
17755
17895
  formData.append("response_format", "text");
17756
17896
  const response = await fetch("https://api.groq.com/openai/v1/audio/transcriptions", {
@@ -17851,8 +17991,8 @@ async function mp3ToOgg(mp3Buffer) {
17851
17991
  const id = crypto.randomUUID();
17852
17992
  const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
17853
17993
  const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
17854
- const { writeFile: writeFile6 } = await import("fs/promises");
17855
- await writeFile6(tmpMp3, mp3Buffer);
17994
+ const { writeFile: writeFile7 } = await import("fs/promises");
17995
+ await writeFile7(tmpMp3, mp3Buffer);
17856
17996
  await execFileAsync2("ffmpeg", ["-y", "-i", tmpMp3, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
17857
17997
  const oggBuffer = await readFile2(tmpOgg);
17858
17998
  unlink2(tmpMp3).catch((err) => {
@@ -17879,14 +18019,24 @@ async function macOsTts(text, voice2 = "Samantha") {
17879
18019
  });
17880
18020
  return oggBuffer;
17881
18021
  }
17882
- var execFileAsync2, ffmpegAvailable, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
18022
+ var execFileAsync2, ffmpegAvailable, LOCAL_WHISPER_MODELS, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES, whisperCliAvailableCache;
17883
18023
  var init_stt = __esm({
17884
18024
  "src/voice/stt.ts"() {
17885
18025
  "use strict";
17886
18026
  init_log();
17887
18027
  init_store5();
18028
+ init_paths();
17888
18029
  execFileAsync2 = promisify2(execFile2);
17889
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
+ };
17890
18040
  ELEVENLABS_VOICES = {
17891
18041
  "21m00Tcm4TlvDq8ikWAM": { name: "Rachel", gender: "F" },
17892
18042
  "EXAVITQu4vr4xnSDxMaL": { name: "Sarah", gender: "F" },
@@ -17902,13 +18052,14 @@ var init_stt = __esm({
17902
18052
  "Samantha": { name: "Samantha", gender: "F" },
17903
18053
  "Albert": { name: "Albert", gender: "M" }
17904
18054
  };
18055
+ whisperCliAvailableCache = null;
17905
18056
  }
17906
18057
  });
17907
18058
 
17908
18059
  // src/media/image-gen.ts
17909
- import { mkdirSync as mkdirSync9, existsSync as existsSync19, unlink as unlink3, readdir as readdir2, stat as stat2 } from "fs";
17910
- import { writeFile } from "fs/promises";
17911
- 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";
17912
18063
  async function generateImage(prompt) {
17913
18064
  const apiKey = process.env.GEMINI_API_KEY;
17914
18065
  if (!apiKey) {
@@ -17955,14 +18106,14 @@ async function generateImage(prompt) {
17955
18106
  if (!imageData) {
17956
18107
  throw new Error(textResponse ?? "Gemini did not generate an image. The prompt may have been filtered.");
17957
18108
  }
17958
- if (!existsSync19(IMAGE_OUTPUT_DIR)) {
18109
+ if (!existsSync20(IMAGE_OUTPUT_DIR)) {
17959
18110
  mkdirSync9(IMAGE_OUTPUT_DIR, { recursive: true });
17960
18111
  }
17961
18112
  const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
17962
18113
  const filename = `img_${Date.now()}.${ext}`;
17963
- const filePath = join18(IMAGE_OUTPUT_DIR, filename);
18114
+ const filePath = join19(IMAGE_OUTPUT_DIR, filename);
17964
18115
  const buffer = Buffer.from(imageData, "base64");
17965
- await writeFile(filePath, buffer);
18116
+ await writeFile2(filePath, buffer);
17966
18117
  log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
17967
18118
  return { filePath, text: textResponse, mimeType };
17968
18119
  }
@@ -17978,7 +18129,7 @@ function cleanupGeneratedImage(filePath) {
17978
18129
  function pruneImageCache() {
17979
18130
  readdir2(IMAGE_OUTPUT_DIR, (err, files) => {
17980
18131
  if (err || !files) return;
17981
- 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));
17982
18133
  if (imageFiles.length === 0) return;
17983
18134
  const now = Date.now();
17984
18135
  let statsPending = imageFiles.length;
@@ -18010,8 +18161,8 @@ var init_image_gen = __esm({
18010
18161
  MAX_GENERATED_IMAGES = 20;
18011
18162
  IMAGE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
18012
18163
  IMAGE_MODEL = "gemini-3.1-flash-image-preview";
18013
- IMAGE_OUTPUT_DIR = join18(
18014
- 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"),
18015
18166
  "data",
18016
18167
  "images"
18017
18168
  );
@@ -18449,22 +18600,22 @@ var init_video = __esm({
18449
18600
  });
18450
18601
 
18451
18602
  // src/router/media.ts
18452
- import { join as join19 } from "path";
18453
- 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";
18454
18605
  function getMediaRetentionMs() {
18455
18606
  const hours = parseInt(process.env.MEDIA_RETENTION_HOURS ?? "24", 10);
18456
18607
  return (isNaN(hours) || hours < 1 ? 24 : hours) * 60 * 60 * 1e3;
18457
18608
  }
18458
18609
  async function saveMedia(buffer, prefix, ext) {
18459
- await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
18610
+ await mkdir3(MEDIA_INCOMING_PATH, { recursive: true });
18460
18611
  const filename = `${prefix}-${Date.now()}.${ext}`;
18461
- const fullPath = join19(MEDIA_INCOMING_PATH, filename);
18462
- await writeFile2(fullPath, buffer);
18612
+ const fullPath = join20(MEDIA_INCOMING_PATH, filename);
18613
+ await writeFile3(fullPath, buffer);
18463
18614
  return fullPath;
18464
18615
  }
18465
18616
  async function cleanupOldMedia() {
18466
18617
  try {
18467
- await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
18618
+ await mkdir3(MEDIA_INCOMING_PATH, { recursive: true });
18468
18619
  const retentionMs = getMediaRetentionMs();
18469
18620
  const retentionHours = Math.round(retentionMs / (60 * 60 * 1e3));
18470
18621
  const files = await readdir3(MEDIA_INCOMING_PATH);
@@ -18472,7 +18623,7 @@ async function cleanupOldMedia() {
18472
18623
  let removed = 0;
18473
18624
  for (const file of files) {
18474
18625
  try {
18475
- const filePath = join19(MEDIA_INCOMING_PATH, file);
18626
+ const filePath = join20(MEDIA_INCOMING_PATH, file);
18476
18627
  const s = await stat3(filePath);
18477
18628
  if (now - s.mtimeMs > retentionMs) {
18478
18629
  await unlink4(filePath);
@@ -18494,9 +18645,11 @@ async function handleVoice(msg, channel) {
18494
18645
  return;
18495
18646
  }
18496
18647
  const audioBuffer = await channel.downloadFile(fileName);
18497
- const transcript = await transcribeAudio(audioBuffer);
18648
+ const transcript = await transcribeAudio(audioBuffer, chatId, async (msg2) => {
18649
+ await channel.sendText(chatId, msg2, { parseMode: "plain" });
18650
+ });
18498
18651
  if (!transcript) {
18499
- 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" });
18500
18653
  return;
18501
18654
  }
18502
18655
  const vBackendId = getBackend(chatId) ?? "claude";
@@ -18746,7 +18899,7 @@ var init_media = __esm({
18746
18899
  init_helpers();
18747
18900
  init_response();
18748
18901
  init_live_status();
18749
- MEDIA_INCOMING_PATH = join19(MEDIA_PATH, "incoming");
18902
+ MEDIA_INCOMING_PATH = join20(MEDIA_PATH, "incoming");
18750
18903
  }
18751
18904
  });
18752
18905
 
@@ -19058,9 +19211,9 @@ var install_exports = {};
19058
19211
  __export(install_exports, {
19059
19212
  installSkillFromGitHub: () => installSkillFromGitHub
19060
19213
  });
19061
- import { mkdir as mkdir3, readdir as readdir4, readFile as readFile4, cp } from "fs/promises";
19062
- import { existsSync as existsSync20 } from "fs";
19063
- 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";
19064
19217
  import { execSync as execSync4 } from "child_process";
19065
19218
  async function installSkillFromGitHub(urlOrShorthand) {
19066
19219
  let repoUrl;
@@ -19071,36 +19224,36 @@ async function installSkillFromGitHub(urlOrShorthand) {
19071
19224
  }
19072
19225
  repoUrl = parsed.cloneUrl;
19073
19226
  subPath = parsed.subPath;
19074
- const tmpDir = join20("/tmp", `cc-claw-skill-${Date.now()}`);
19227
+ const tmpDir = join21("/tmp", `cc-claw-skill-${Date.now()}`);
19075
19228
  try {
19076
19229
  log(`[skill-install] Cloning ${repoUrl} to ${tmpDir}`);
19077
19230
  execSync4(`git clone --depth 1 ${repoUrl} ${tmpDir}`, {
19078
19231
  stdio: "pipe",
19079
19232
  timeout: 3e4
19080
19233
  });
19081
- if (!existsSync20(join20(tmpDir, ".git"))) {
19234
+ if (!existsSync21(join21(tmpDir, ".git"))) {
19082
19235
  return { success: false, error: "Git clone failed: no .git directory produced" };
19083
19236
  }
19084
- const searchRoot = subPath ? join20(tmpDir, subPath) : tmpDir;
19237
+ const searchRoot = subPath ? join21(tmpDir, subPath) : tmpDir;
19085
19238
  const skillDir = await findSkillDir(searchRoot);
19086
19239
  if (!skillDir) {
19087
19240
  return { success: false, error: "No SKILL.md found in the repository." };
19088
19241
  }
19089
19242
  const skillFolderName = basename2(skillDir);
19090
- const destDir = join20(SKILLS_PATH, skillFolderName);
19091
- if (existsSync20(destDir)) {
19243
+ const destDir = join21(SKILLS_PATH, skillFolderName);
19244
+ if (existsSync21(destDir)) {
19092
19245
  log(`[skill-install] Overwriting existing skill at ${destDir}`);
19093
19246
  }
19094
- await mkdir3(destDir, { recursive: true });
19247
+ await mkdir4(destDir, { recursive: true });
19095
19248
  await cp(skillDir, destDir, { recursive: true });
19096
19249
  let skillName = skillFolderName;
19097
19250
  try {
19098
- const content = await readFile4(join20(destDir, "SKILL.md"), "utf-8");
19251
+ const content = await readFile4(join21(destDir, "SKILL.md"), "utf-8");
19099
19252
  const nameMatch = content.match(/^name:\s*(.+)$/m);
19100
19253
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
19101
19254
  } catch {
19102
19255
  try {
19103
- const content = await readFile4(join20(destDir, "skill.md"), "utf-8");
19256
+ const content = await readFile4(join21(destDir, "skill.md"), "utf-8");
19104
19257
  const nameMatch = content.match(/^name:\s*(.+)$/m);
19105
19258
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
19106
19259
  } catch {
@@ -19135,15 +19288,15 @@ function parseGitHubUrl(input) {
19135
19288
  async function findSkillDir(root) {
19136
19289
  const candidates = ["SKILL.md", "skill.md"];
19137
19290
  for (const c of candidates) {
19138
- if (existsSync20(join20(root, c))) return root;
19291
+ if (existsSync21(join21(root, c))) return root;
19139
19292
  }
19140
19293
  try {
19141
19294
  const entries = await readdir4(root, { withFileTypes: true });
19142
19295
  for (const entry of entries) {
19143
19296
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19144
19297
  for (const c of candidates) {
19145
- if (existsSync20(join20(root, entry.name, c))) {
19146
- return join20(root, entry.name);
19298
+ if (existsSync21(join21(root, entry.name, c))) {
19299
+ return join21(root, entry.name);
19147
19300
  }
19148
19301
  }
19149
19302
  }
@@ -19155,15 +19308,15 @@ async function findSkillDir(root) {
19155
19308
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19156
19309
  let subEntries;
19157
19310
  try {
19158
- subEntries = await readdir4(join20(root, entry.name), { withFileTypes: true });
19311
+ subEntries = await readdir4(join21(root, entry.name), { withFileTypes: true });
19159
19312
  } catch {
19160
19313
  continue;
19161
19314
  }
19162
19315
  for (const sub of subEntries) {
19163
19316
  if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
19164
19317
  for (const c of candidates) {
19165
- if (existsSync20(join20(root, entry.name, sub.name, c))) {
19166
- 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);
19167
19320
  }
19168
19321
  }
19169
19322
  }
@@ -19190,7 +19343,7 @@ __export(discover_exports, {
19190
19343
  import { readdir as readdir5, readFile as readFile5 } from "fs/promises";
19191
19344
  import { createHash } from "crypto";
19192
19345
  import { homedir as homedir5 } from "os";
19193
- import { join as join21 } from "path";
19346
+ import { join as join22 } from "path";
19194
19347
  function invalidateSkillCache() {
19195
19348
  cachedSkills = null;
19196
19349
  cacheTimestamp = 0;
@@ -19208,7 +19361,7 @@ async function discoverAllSkills() {
19208
19361
  const rawSkills = [];
19209
19362
  rawSkills.push(...await scanSkillDir(SKILLS_PATH, "cc-claw"));
19210
19363
  for (const backendId of getAllBackendIds()) {
19211
- const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join21(homedir5(), `.${backendId}`, "skills")];
19364
+ const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join22(homedir5(), `.${backendId}`, "skills")];
19212
19365
  for (const dir of dirs) {
19213
19366
  rawSkills.push(...await scanSkillDir(dir, backendId));
19214
19367
  }
@@ -19236,7 +19389,7 @@ async function scanSkillDir(skillsDir, source) {
19236
19389
  let content;
19237
19390
  let resolvedPath;
19238
19391
  for (const candidate of SKILL_FILE_CANDIDATES) {
19239
- const p = join21(skillsDir, entry.name, candidate);
19392
+ const p = join22(skillsDir, entry.name, candidate);
19240
19393
  try {
19241
19394
  content = await readFile5(p, "utf-8");
19242
19395
  resolvedPath = p;
@@ -19330,15 +19483,15 @@ var init_discover = __esm({
19330
19483
  init_backends();
19331
19484
  SKILL_FILE_CANDIDATES = ["SKILL.md", "skill.md"];
19332
19485
  BACKEND_SKILL_DIRS = {
19333
- claude: [join21(homedir5(), ".claude", "skills")],
19334
- gemini: [join21(homedir5(), ".gemini", "skills")],
19486
+ claude: [join22(homedir5(), ".claude", "skills")],
19487
+ gemini: [join22(homedir5(), ".gemini", "skills")],
19335
19488
  codex: [
19336
- join21(homedir5(), ".agents", "skills"),
19337
- join21(homedir5(), ".codex", "skills")
19489
+ join22(homedir5(), ".agents", "skills"),
19490
+ join22(homedir5(), ".codex", "skills")
19338
19491
  ],
19339
19492
  cursor: [
19340
- join21(homedir5(), ".cursor", "skills"),
19341
- join21(homedir5(), ".cursor", "skills-cursor")
19493
+ join22(homedir5(), ".cursor", "skills"),
19494
+ join22(homedir5(), ".cursor", "skills-cursor")
19342
19495
  ]
19343
19496
  };
19344
19497
  CACHE_TTL_MS2 = 3e5;
@@ -19696,43 +19849,76 @@ async function sendVoiceConfigKeyboard(chatId, channel) {
19696
19849
  await channel.sendText(chatId, "Voice configuration requires an interactive channel (Telegram).", { parseMode: "plain" });
19697
19850
  return;
19698
19851
  }
19699
- const config2 = getVoiceConfig(chatId);
19700
- const currentVoiceName = config2.provider === "elevenlabs" ? ELEVENLABS_VOICES[config2.voiceId ?? ""]?.name ?? "Rachel" : config2.provider === "macos" ? MACOS_VOICES[config2.voiceId ?? ""]?.name ?? "Samantha" : config2.voiceId ?? "eve";
19701
- const providerLabel = config2.provider === "grok" ? "Grok (xAI)" : config2.provider === "macos" ? "macOS" : "ElevenLabs";
19702
- const header2 = `\u{1F3A7} Voice Configuration
19703
- Provider: ${providerLabel}
19704
- Voice: ${currentVoiceName}
19705
- Status: ${config2.enabled ? "ON" : "OFF"}`;
19852
+ const ttsConfig = getVoiceConfig(chatId);
19853
+ const sttProvider = getSttProvider(chatId);
19854
+ const sttModel = getSttModel(chatId);
19855
+ const whisperAvailable = isWhisperCliAvailable();
19706
19856
  const buttons = [];
19857
+ const groqAvailable = !!process.env.GROQ_API_KEY;
19707
19858
  buttons.push([
19708
- { label: `${config2.provider === "elevenlabs" ? "\u2713 " : ""}ElevenLabs`, data: "vcfg:p:elevenlabs" },
19709
- { label: `${config2.provider === "grok" ? "\u2713 " : ""}Grok`, data: "vcfg:p:grok" },
19710
- { 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
+ }
19711
19869
  ]);
19712
- if (config2.provider === "elevenlabs") {
19713
- const entries = Object.entries(ELEVENLABS_VOICES);
19714
- const female = entries.filter(([, v]) => v.gender === "F");
19715
- const male = entries.filter(([, v]) => v.gender === "M");
19716
- buttons.push(female.map(([id, v]) => ({
19717
- label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
19718
- data: `vcfg:v:${id}`
19719
- })));
19720
- buttons.push(male.map(([id, v]) => ({
19721
- label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
19722
- data: `vcfg:v:${id}`
19723
- })));
19724
- } else if (config2.provider === "grok") {
19725
- buttons.push(GROK_VOICES.map((v) => ({
19726
- label: `${config2.voiceId === v ? "\u2713 " : ""}${capitalize(v)}`,
19727
- data: `vcfg:v:${v}`
19728
- })));
19729
- } else {
19730
- const entries = Object.entries(MACOS_VOICES);
19731
- buttons.push(entries.map(([id, v]) => ({
19732
- label: `${config2.voiceId === id ? "\u2713 " : ""}${v.name}`,
19733
- data: `vcfg:v:${id}`
19734
- })));
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
+ }
19735
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}`;
19736
19922
  await channel.sendKeyboard(chatId, header2, buttons);
19737
19923
  }
19738
19924
  async function sendSkillsPage(chatId, channel, skills2, page, messageId) {
@@ -19829,28 +20015,14 @@ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
19829
20015
  const backendDisplay = config2?.backend ? capitalize(config2.backend) : "Default";
19830
20016
  const modelDisplay = config2?.model ?? "default";
19831
20017
  const thinkingDisplay = config2?.thinking ?? "off";
19832
- const lines = [
19833
- buildSectionHeader("Heartbeat"),
19834
- "CC-Claw periodically wakes up to check on",
19835
- "things and alert you proactively \u2014 even when",
19836
- "you haven't sent a message.",
19837
- "",
19838
- `Status: ${enabled ? "ON" : "OFF"}`,
19839
- `Interval: every ${intervalMin} min`,
19840
- `Active hours: ${activeStart}-${activeEnd}`,
19841
- `Backend: ${backendDisplay} | Model: ${modelDisplay}`
19842
- ];
19843
- if (fallbacks.length > 0) {
19844
- lines.push(`Fallbacks: ${fallbacks.map((f) => `${capitalize(f.backend)}${f.model ? `:${f.model}` : ""}`).join(", ")}`);
19845
- }
19846
- if (config2?.target) {
19847
- lines.push(`Target: ${config2.target}`);
19848
- }
19849
- if (watches.length > 0) {
19850
- lines.push(`Active watches: ${watches.length}`);
19851
- }
19852
- const lastBeat = config2?.lastBeatAt ?? "never";
19853
- 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");
19854
20026
  const buttons = [];
19855
20027
  buttons.push([
19856
20028
  { label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
@@ -19864,63 +20036,62 @@ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
19864
20036
  ...m === intervalMin ? { style: "primary" } : {}
19865
20037
  })));
19866
20038
  const available = getAvailableBackendIds();
19867
- buttons.push([{ label: "\u2699\uFE0F Main Engine", data: "hb:noop" }]);
19868
- const backendRow = available.map((bid) => ({
19869
- label: `${config2?.backend === bid ? "\u2713 " : ""}${capitalize(bid)}`,
19870
- data: `hb:backend:${bid}`,
19871
- ...config2?.backend === bid ? { style: "primary" } : {}
19872
- }));
19873
- if (config2?.backend) {
19874
- backendRow.push({ label: "\u2715", data: "hb:backend:default", style: "danger" });
19875
- }
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
+ ];
19876
20051
  buttons.push(backendRow);
19877
20052
  const targetBackend = config2?.backend ?? getBackend(chatId) ?? "claude";
19878
20053
  try {
19879
20054
  const adapter = getAdapter(targetBackend);
19880
20055
  const models = Object.entries(adapter.availableModels);
19881
20056
  if (models.length > 0) {
19882
- const modelRow = models.slice(0, 4).map(([key]) => ({
19883
- label: `${config2?.model === key ? "\u2713 " : ""}${shortModelName(key)}`,
19884
- data: `hb:model:${key}`,
19885
- ...config2?.model === key ? { style: "primary" } : {}
19886
- }));
19887
- if (config2?.model) {
19888
- modelRow.push({ label: "\u2715", data: "hb:model:default", style: "danger" });
19889
- }
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
+ ];
19890
20069
  buttons.push(modelRow);
19891
20070
  }
19892
20071
  } catch {
19893
20072
  }
19894
20073
  const fbCandidates = available.filter((bid) => !fallbacks.some((f) => f.backend === bid) && bid !== config2?.backend);
19895
- if (fallbacks.length > 0 || fbCandidates.length > 0) {
19896
- buttons.push([{ label: "\u{1F504} Fallback Chain", data: "hb:noop" }]);
19897
- }
19898
20074
  if (fallbacks.length > 0) {
19899
20075
  buttons.push([
19900
- { label: fallbacks.map((f) => {
19901
- const name = capitalize(f.backend);
19902
- return f.model ? `${name} (${shortModelName(f.model)})` : name;
19903
- }).join(" \u2192 "), data: "hb:noop" },
19904
- { 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" }
19905
20078
  ]);
19906
20079
  }
19907
20080
  if (fbCandidates.length > 0) {
19908
20081
  buttons.push(fbCandidates.slice(0, 4).map((bid) => ({
19909
- label: `+ ${capitalize(bid)}`,
20082
+ label: `+ ${capitalize(bid)} fallback`,
19910
20083
  data: `hb:fb:add:${bid}`
19911
20084
  })));
19912
20085
  }
19913
- const optionsRow = [
19914
- { label: `\u{1F4AD} Thinking: ${thinkingDisplay}`, data: "hb:thinking" }
19915
- ];
20086
+ const optionsRow = [];
19916
20087
  if (watches.length > 0) {
19917
20088
  optionsRow.push({ label: `\u{1F441} Watches (${watches.length})`, data: "hb:watches" });
19918
20089
  } else {
19919
- optionsRow.push({ label: "+ Add Watch", data: "hb:addwatch" });
20090
+ optionsRow.push({ label: "\u{1F441} Add Watch", data: "hb:addwatch" });
19920
20091
  }
20092
+ optionsRow.push({ label: `\u23F0 Hours: ${activeStart}\u2013${activeEnd}`, data: "hb:noop" });
19921
20093
  buttons.push(optionsRow);
19922
- buttons.push([{ label: `\u23F0 ${activeStart}\u2013${activeEnd} (use /heartbeat hours)`, data: "hb:noop" }]);
19923
- await sendOrEditKeyboard(chatId, channel, messageId, lines.join("\n"), buttons);
20094
+ await sendOrEditKeyboard(chatId, channel, messageId, header2, buttons);
19924
20095
  }
19925
20096
  async function sendForgetPicker(chatId, channel, page, messageId) {
19926
20097
  const memories = listMemories();
@@ -20569,13 +20740,13 @@ async function handleEvolveCallback(chatId, data, channel) {
20569
20740
  const { getReflectionStatus: getReflectionStatus2, setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
20570
20741
  const current = getReflectionStatus2(getDb(), chatId);
20571
20742
  if (current === "frozen") {
20572
- const { readFileSync: readFileSync29, existsSync: existsSync58 } = await import("fs");
20573
- const { join: join37 } = await import("path");
20743
+ const { readFileSync: readFileSync30, existsSync: existsSync59 } = await import("fs");
20744
+ const { join: join38 } = await import("path");
20574
20745
  const { CC_CLAW_HOME: CC_CLAW_HOME3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
20575
- const soulPath = join37(CC_CLAW_HOME3, "identity/SOUL.md");
20576
- const userPath = join37(CC_CLAW_HOME3, "identity/USER.md");
20577
- const soul = existsSync58(soulPath) ? readFileSync29(soulPath, "utf-8") : "";
20578
- 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") : "";
20579
20750
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
20580
20751
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
20581
20752
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -20652,11 +20823,11 @@ var init_evolve2 = __esm({
20652
20823
  });
20653
20824
 
20654
20825
  // src/optimizer/identity-audit.ts
20655
- import { readFileSync as readFileSync11, existsSync as existsSync21, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
20656
- 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";
20657
20828
  function readIdentityFile2(filename) {
20658
20829
  try {
20659
- return readFileSync11(join22(IDENTITY_PATH, filename), "utf-8");
20830
+ return readFileSync12(join23(IDENTITY_PATH, filename), "utf-8");
20660
20831
  } catch {
20661
20832
  return "";
20662
20833
  }
@@ -20671,13 +20842,13 @@ function getMtime(filepath) {
20671
20842
  function findBackupFiles() {
20672
20843
  const backups = [];
20673
20844
  const dirs = [IDENTITY_PATH];
20674
- const contextDir = join22(IDENTITY_PATH, "..", "workspace", "context");
20675
- if (existsSync21(contextDir)) dirs.push(contextDir);
20845
+ const contextDir = join23(IDENTITY_PATH, "..", "workspace", "context");
20846
+ if (existsSync22(contextDir)) dirs.push(contextDir);
20676
20847
  for (const dir of dirs) {
20677
20848
  try {
20678
20849
  for (const entry of readdirSync10(dir)) {
20679
20850
  if (entry.endsWith(".bak") || /\.bak\.\d{4}-\d{2}-\d{2}/.test(entry)) {
20680
- backups.push(join22(dir, entry));
20851
+ backups.push(join23(dir, entry));
20681
20852
  }
20682
20853
  }
20683
20854
  } catch {
@@ -20698,9 +20869,9 @@ function computeIdentityStats(pendingProposals, driftPercent) {
20698
20869
  userChars,
20699
20870
  ccClawChars,
20700
20871
  boilerplateChars,
20701
- soulMtime: getMtime(join22(IDENTITY_PATH, "SOUL.md")),
20702
- userMtime: getMtime(join22(IDENTITY_PATH, "USER.md")),
20703
- 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")),
20704
20875
  backupFiles: findBackupFiles(),
20705
20876
  estimatedTokens: Math.ceil(ccClawChars / 4),
20706
20877
  pendingEvolveProposals: pendingProposals,
@@ -20809,8 +20980,8 @@ var init_identity_audit = __esm({
20809
20980
  });
20810
20981
 
20811
20982
  // src/optimizer/skill-audit.ts
20812
- import { readFileSync as readFileSync12, existsSync as existsSync22 } from "fs";
20813
- 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";
20814
20985
  function parseFrontmatter3(content) {
20815
20986
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
20816
20987
  if (!fmMatch) return {};
@@ -20846,10 +21017,10 @@ function detectDependentSkills(content) {
20846
21017
  return Array.from(deps);
20847
21018
  }
20848
21019
  function computeSkillStats(skillPath) {
20849
- const content = readFileSync12(skillPath, "utf-8");
21020
+ const content = readFileSync13(skillPath, "utf-8");
20850
21021
  const lines = content.split("\n");
20851
21022
  return {
20852
- skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join23(skillPath, "..")) : basename3(skillPath, ".md"),
21023
+ skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join24(skillPath, "..")) : basename3(skillPath, ".md"),
20853
21024
  skillPath,
20854
21025
  lineCount: lines.length,
20855
21026
  charCount: content.length,
@@ -20869,13 +21040,13 @@ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
20869
21040
  const results = [];
20870
21041
  for (const name of depNames) {
20871
21042
  const candidates = [
20872
- join23(ccClawSkillsDir, name, "SKILL.md"),
20873
- join23(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
21043
+ join24(ccClawSkillsDir, name, "SKILL.md"),
21044
+ join24(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
20874
21045
  ];
20875
21046
  for (const candidate of candidates) {
20876
- if (existsSync22(candidate)) {
21047
+ if (existsSync23(candidate)) {
20877
21048
  try {
20878
- const content = readFileSync12(candidate, "utf-8");
21049
+ const content = readFileSync13(candidate, "utf-8");
20879
21050
  results.push({
20880
21051
  name,
20881
21052
  content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
@@ -20987,8 +21158,8 @@ __export(analyze_exports2, {
20987
21158
  });
20988
21159
  import { spawn as spawn7 } from "child_process";
20989
21160
  import { createInterface as createInterface7 } from "readline";
20990
- import { readFileSync as readFileSync13, existsSync as existsSync23, readdirSync as readdirSync12 } from "fs";
20991
- 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";
20992
21163
  import { homedir as homedir7 } from "os";
20993
21164
  function parseOptimizeOutput(raw, validAreas) {
20994
21165
  if (!raw || raw.includes("NO_FINDINGS")) return [];
@@ -21118,20 +21289,20 @@ function getModelDisplayInfo(chatId) {
21118
21289
  }
21119
21290
  function readIdentityFile3(filename) {
21120
21291
  try {
21121
- return readFileSync13(join24(IDENTITY_PATH, filename), "utf-8");
21292
+ return readFileSync14(join25(IDENTITY_PATH, filename), "utf-8");
21122
21293
  } catch {
21123
21294
  return "";
21124
21295
  }
21125
21296
  }
21126
21297
  function loadContextFiles2() {
21127
- const contextDir = join24(homedir7(), ".cc-claw", "workspace", "context");
21298
+ const contextDir = join25(homedir7(), ".cc-claw", "workspace", "context");
21128
21299
  const results = [];
21129
- if (!existsSync23(contextDir)) return results;
21300
+ if (!existsSync24(contextDir)) return results;
21130
21301
  try {
21131
21302
  for (const entry of readdirSync12(contextDir)) {
21132
21303
  if (!entry.endsWith(".md")) continue;
21133
21304
  try {
21134
- const content = readFileSync13(join24(contextDir, entry), "utf-8");
21305
+ const content = readFileSync14(join25(contextDir, entry), "utf-8");
21135
21306
  results.push({ name: entry, content });
21136
21307
  } catch {
21137
21308
  }
@@ -21182,8 +21353,8 @@ async function runSkillAudit(chatId, skillPath) {
21182
21353
  const stats = computeSkillStats(skillPath);
21183
21354
  log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
21184
21355
  const soulMd = readIdentityFile3("SOUL.md");
21185
- const ccClawSkillsDir = join24(homedir7(), ".cc-claw", "workspace", "skills");
21186
- const skillContent = readFileSync13(skillPath, "utf-8");
21356
+ const ccClawSkillsDir = join25(homedir7(), ".cc-claw", "workspace", "skills");
21357
+ const skillContent = readFileSync14(skillPath, "utf-8");
21187
21358
  const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
21188
21359
  const raw = await spawnAnalysis2(adapter, model2, prompt);
21189
21360
  const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
@@ -21197,16 +21368,16 @@ async function runSkillAudit(chatId, skillPath) {
21197
21368
  };
21198
21369
  }
21199
21370
  function listCcClawSkills() {
21200
- const skillsDir = join24(homedir7(), ".cc-claw", "workspace", "skills");
21371
+ const skillsDir = join25(homedir7(), ".cc-claw", "workspace", "skills");
21201
21372
  const entries = [];
21202
- if (!existsSync23(skillsDir)) return entries;
21373
+ if (!existsSync24(skillsDir)) return entries;
21203
21374
  try {
21204
21375
  for (const dir of readdirSync12(skillsDir)) {
21205
- const skillFile = join24(skillsDir, dir, "SKILL.md");
21206
- if (!existsSync23(skillFile)) continue;
21376
+ const skillFile = join25(skillsDir, dir, "SKILL.md");
21377
+ if (!existsSync24(skillFile)) continue;
21207
21378
  let description = "skill";
21208
21379
  try {
21209
- const content = readFileSync13(skillFile, "utf-8");
21380
+ const content = readFileSync14(skillFile, "utf-8");
21210
21381
  const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
21211
21382
  if (descMatch) description = descMatch[1].trim().slice(0, 60);
21212
21383
  } catch {
@@ -21526,8 +21697,8 @@ var init_ui2 = __esm({
21526
21697
  });
21527
21698
 
21528
21699
  // src/router/optimize.ts
21529
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync7, existsSync as existsSync24, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
21530
- 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";
21531
21702
  import { homedir as homedir8 } from "os";
21532
21703
  async function handleOptimizeCommand(chatId, channel, _args) {
21533
21704
  const { getModelDisplayInfo: getModelDisplayInfo2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
@@ -21708,7 +21879,7 @@ async function runSkillAuditFlow(chatId, channel, skillName) {
21708
21879
  } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
21709
21880
  const modelInfo = getModelDisplayInfo2(chatId);
21710
21881
  if (!modelInfo) return;
21711
- const skillPath = join25(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
21882
+ const skillPath = join26(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
21712
21883
  const progressMsgId = typeof channel.sendTextReturningId === "function" ? await channel.sendTextReturningId(
21713
21884
  chatId,
21714
21885
  buildProgressMessage2(`skill: ${skillName}`, modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
@@ -21797,13 +21968,13 @@ async function applyFinding(chatId, channel, index) {
21797
21968
  await showFinding(chatId, channel, index + 1);
21798
21969
  return;
21799
21970
  }
21800
- if (!existsSync24(targetPath)) {
21971
+ if (!existsSync25(targetPath)) {
21801
21972
  await channel.sendText(chatId, `Target file not found: ${targetPath}`, { parseMode: "plain" });
21802
21973
  session2.skipped.push(index);
21803
21974
  await showFinding(chatId, channel, index + 1);
21804
21975
  return;
21805
21976
  }
21806
- const original = readFileSync14(targetPath, "utf-8");
21977
+ const original = readFileSync15(targetPath, "utf-8");
21807
21978
  const backupPath = targetPath + `.bak.${Date.now()}`;
21808
21979
  writeFileSync7(backupPath, original, "utf-8");
21809
21980
  pruneBackups2(targetPath);
@@ -21879,14 +22050,14 @@ async function finishReview(chatId, channel) {
21879
22050
  activeSessions.delete(chatId);
21880
22051
  }
21881
22052
  function resolveTargetFile(location, auditTarget) {
21882
- const ccClawHome = join25(homedir8(), ".cc-claw");
22053
+ const ccClawHome = join26(homedir8(), ".cc-claw");
21883
22054
  const filePart = location.split(":")[0]?.trim();
21884
22055
  if (!filePart) return null;
21885
- if (filePart === "SOUL.md") return join25(ccClawHome, "identity", "SOUL.md");
21886
- if (filePart === "USER.md") return join25(ccClawHome, "identity", "USER.md");
21887
- 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");
21888
22059
  if (filePart === "SKILL.md" && auditTarget !== "identity") {
21889
- return join25(ccClawHome, "workspace", "skills", auditTarget, "SKILL.md");
22060
+ return join26(ccClawHome, "workspace", "skills", auditTarget, "SKILL.md");
21890
22061
  }
21891
22062
  return null;
21892
22063
  }
@@ -21894,7 +22065,7 @@ function pruneBackups2(absolutePath) {
21894
22065
  const dir = dirname4(absolutePath);
21895
22066
  const baseName = absolutePath.split("/").pop() ?? "";
21896
22067
  try {
21897
- 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));
21898
22069
  while (backups.length > 3) {
21899
22070
  const oldest = backups.shift();
21900
22071
  try {
@@ -21928,8 +22099,8 @@ __export(auto_create_exports, {
21928
22099
  saveSkill: () => saveSkill,
21929
22100
  storePendingDraft: () => storePendingDraft
21930
22101
  });
21931
- import { join as join26 } from "path";
21932
- 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";
21933
22104
  function isSkillWorthy(signals) {
21934
22105
  const { toolUseCount, tokenOutput, elapsedMs, userMessage } = signals;
21935
22106
  if (toolUseCount < 12) return false;
@@ -22075,10 +22246,10 @@ function parseExtractedSkill(llmResponse) {
22075
22246
  return { name, content };
22076
22247
  }
22077
22248
  async function saveSkill(name, content) {
22078
- const dir = join26(SKILLS_PATH, name);
22079
- await mkdir4(dir, { recursive: true });
22080
- const filePath = join26(dir, "SKILL.md");
22081
- 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");
22082
22253
  invalidateSkillCache();
22083
22254
  log(`[auto-skill] Saved skill "${name}" to ${filePath}`);
22084
22255
  return { path: filePath };
@@ -22248,18 +22419,7 @@ async function handleStopCommand(chatId, commandArgs, msg, channel) {
22248
22419
  }
22249
22420
  }
22250
22421
  async function handleVoiceCommand(chatId, commandArgs, msg, channel) {
22251
- const vcEnabled = isVoiceEnabled(chatId);
22252
- if (typeof channel.sendKeyboard === "function") {
22253
- await channel.sendKeyboard(chatId, `\u{1F3A7} Voice responses: ${vcEnabled ? "ON" : "OFF"}`, [
22254
- [
22255
- { label: `${vcEnabled ? "" : "\u2713 "}\u{1F507} Off`, data: "voice:off", ...!vcEnabled ? { style: "danger" } : {} },
22256
- { label: `${vcEnabled ? "\u2713 " : ""}\u{1F50A} On`, data: "voice:on", ...vcEnabled ? { style: "success" } : {} }
22257
- ]
22258
- ]);
22259
- } else {
22260
- const toggled = toggleVoice(chatId);
22261
- await channel.sendText(chatId, toggled ? "Voice responses enabled." : "Voice responses disabled.", { parseMode: "plain" });
22262
- }
22422
+ await sendVoiceConfigKeyboard(chatId, channel);
22263
22423
  }
22264
22424
  async function handleVoiceConfigCommand(chatId, commandArgs, msg, channel) {
22265
22425
  await sendVoiceConfigKeyboard(chatId, channel);
@@ -22458,42 +22618,40 @@ Use /skills to see it.`, { parseMode: "plain" });
22458
22618
  async function handleExtractSkillCommand(chatId, commandArgs, msg, channel) {
22459
22619
  const { getLog: getLog2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
22460
22620
  const sessionMessages = getLog2(chatId);
22621
+ const exchangeCount = Math.floor(sessionMessages.length / 2);
22461
22622
  if (sessionMessages.length < 2) {
22462
22623
  await channel.sendText(chatId, "No session history to extract from. Have a conversation first, then run /extract_skill.", { parseMode: "plain" });
22463
22624
  return;
22464
22625
  }
22465
- await channel.sendText(chatId, `Reviewing session (${Math.floor(sessionMessages.length / 2)} exchanges)...`, { parseMode: "plain" });
22466
- const { buildSessionExtractionPrompt: buildSessionExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, storePendingDraft: storePendingDraft2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
22467
- const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
22468
- const { getMode: getMode3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
22469
- const prompt = buildSessionExtractionPrompt2(sessionMessages);
22470
- const response = await askAgent3(chatId, prompt, { permMode: getMode3(chatId) });
22471
- const extracted = parseExtractedSkill2(response.text);
22472
- if (!extracted) {
22473
- await channel.sendText(chatId, "Could not extract a skill from this session. The session may be too short or unfocused.", { parseMode: "plain" });
22474
- return;
22475
- }
22476
- storePendingDraft2(chatId, {
22477
- name: extracted.name,
22478
- content: extracted.content,
22479
- userMessage: sessionMessages.filter((m) => m.role === "user").map((m) => m.text).join("\n"),
22480
- assistantResponse: sessionMessages.filter((m) => m.role === "assistant").map((m) => m.text).join("\n")
22481
- });
22482
- const preview = extracted.content.length > 3500 ? extracted.content.slice(0, 3500) + "\n\n[...truncated...]" : extracted.content;
22483
22626
  if (typeof channel.sendKeyboard === "function") {
22484
- await channel.sendText(chatId, `Extracted skill: "${extracted.name}"
22485
-
22486
- ${preview}`, { parseMode: "plain" });
22487
22627
  await channel.sendKeyboard(
22488
22628
  chatId,
22489
- `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?`,
22490
22638
  [[
22491
- { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
22492
- { label: "\u2715 Discard", data: "skill:discard" }
22639
+ { label: "\u26A1 Start Extraction", data: "skill:start-extract", style: "success" },
22640
+ { label: "\u2715 Cancel", data: "skill:discard" }
22493
22641
  ]]
22494
22642
  );
22495
22643
  } else {
22496
- 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
+ }
22497
22655
  const result = await saveSkill2(extracted.name, extracted.content);
22498
22656
  await channel.sendText(chatId, `Skill "${extracted.name}" saved to ${result.path}`, { parseMode: "plain" });
22499
22657
  }
@@ -22604,6 +22762,22 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22604
22762
  stopAllSideQuests(chatId);
22605
22763
  const oldSessionId = getSessionId(chatId);
22606
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
+ }
22607
22781
  const summarized = await summarizeSession(chatId);
22608
22782
  clearSession(chatId);
22609
22783
  clearChatPaidSlots(chatId);
@@ -22622,6 +22796,7 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22622
22796
  const text = `\u2705 New session started. Previous session archived${exchangeCount > 0 ? ` (${exchangeCount} exchanges)` : ""}.
22623
22797
 
22624
22798
  \u{1F9E0} ${backendLabel} \xB7 ${modelLabel}`;
22799
+ if (ackMsgId) await channel.editText?.(chatId, ackMsgId, text, "plain");
22625
22800
  const kbMsgId = await channel.sendKeyboard(chatId, text, [
22626
22801
  [
22627
22802
  { label: "Switch Backend", data: "menu:backend", style: "primary" },
@@ -22640,7 +22815,11 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22640
22815
  }
22641
22816
  } else {
22642
22817
  const text = summarized ? "Session summarized and saved. Fresh conversation started!" : "Fresh conversation started. What's on your mind?";
22643
- 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
+ }
22644
22823
  }
22645
22824
  }
22646
22825
  async function handleSummarizeCommand(chatId, commandArgs, msg, channel) {
@@ -24522,6 +24701,44 @@ ${plan.originalMessage}`;
24522
24701
  if (current !== desired) toggleVoice(chatId);
24523
24702
  await channel.sendText(chatId, desired ? "\u{1F50A} Voice responses enabled." : "\u{1F507} Voice responses disabled.", { parseMode: "plain" });
24524
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);
24525
24742
  } else if (data.startsWith("vcfg:")) {
24526
24743
  const parts = data.slice(5).split(":");
24527
24744
  const action = parts[0];
@@ -25429,15 +25646,16 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
25429
25646
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25430
25647
  } else if (rest.startsWith("backend:")) {
25431
25648
  const bid = rest.slice(8);
25432
- if (bid === "default") {
25433
- updateHeartbeatConfig2({ backend: null, model: null });
25434
- } else {
25435
- updateHeartbeatConfig2({ backend: bid, model: null });
25436
- }
25649
+ const newBackend = bid === "default" ? null : bid;
25650
+ updateHeartbeatConfig2({ backend: newBackend, model: null });
25651
+ updateHeartbeatField(chatId, "backend", newBackend);
25652
+ updateHeartbeatField(chatId, "model", null);
25437
25653
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25438
25654
  } else if (rest.startsWith("model:")) {
25439
25655
  const model2 = rest.slice(6);
25440
- updateHeartbeatConfig2({ model: model2 === "default" ? null : model2 });
25656
+ const newModel = model2 === "default" ? null : model2;
25657
+ updateHeartbeatConfig2({ model: newModel });
25658
+ updateHeartbeatField(chatId, "model", newModel);
25441
25659
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25442
25660
  } else if (rest === "thinking") {
25443
25661
  await channel.sendText(chatId, "Thinking level for heartbeat: use /editjob to configure.", { parseMode: "plain" });
@@ -25557,6 +25775,71 @@ Example: /limits ${bid} daily 500000`, { parseMode: "plain" });
25557
25775
  const page = parseInt(data.slice(12), 10);
25558
25776
  const skills2 = await discoverAllSkills();
25559
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
+ }
25560
25843
  } else if (data === "skill:extract") {
25561
25844
  const { getPendingDraft: getPendingDraft2, clearPendingDraft: clearPendingDraft2, buildSkillExtractionPrompt: buildSkillExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
25562
25845
  const draft = getPendingDraft2(chatId);
@@ -26432,7 +26715,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26432
26715
  })) {
26433
26716
  const planDirective = buildPlanningDirective();
26434
26717
  let typingActive2 = true;
26435
- const typingLoop = async () => {
26718
+ const typingLoop2 = async () => {
26436
26719
  while (typingActive2) {
26437
26720
  try {
26438
26721
  await channel.sendTyping?.(chatId);
@@ -26441,7 +26724,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26441
26724
  await new Promise((r) => setTimeout(r, 4e3));
26442
26725
  }
26443
26726
  };
26444
- typingLoop().catch(() => {
26727
+ typingLoop2().catch(() => {
26445
26728
  });
26446
26729
  try {
26447
26730
  const planResponse = await askAgent(chatId, cleanText || text, {
@@ -26482,23 +26765,18 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26482
26765
  }
26483
26766
  return;
26484
26767
  }
26485
- const verboseForTyping = settings.getVerboseLevel();
26486
- const showThinkingForTyping = settings.getShowThinkingUi();
26487
- const needsLiveStatusForTyping = verboseForTyping !== "off" || showThinkingForTyping;
26488
- let typingActive = !needsLiveStatusForTyping;
26489
- if (typingActive) {
26490
- const typingLoop = async () => {
26491
- while (typingActive) {
26492
- try {
26493
- await channel.sendTyping?.(chatId);
26494
- } catch {
26495
- }
26496
- 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 {
26497
26774
  }
26498
- };
26499
- typingLoop().catch(() => {
26500
- });
26501
- }
26775
+ await new Promise((r) => setTimeout(r, 4e3));
26776
+ }
26777
+ };
26778
+ typingLoop().catch(() => {
26779
+ });
26502
26780
  try {
26503
26781
  const tMode = settings.getMode();
26504
26782
  const tVerbose = settings.getVerboseLevel();
@@ -26529,6 +26807,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26529
26807
  }
26530
26808
  };
26531
26809
  await liveStatus.init();
26810
+ typingActive = false;
26532
26811
  if (showThinkingUi && adapter.id !== "claude") {
26533
26812
  liveStatus.addInfo(`\u{1F4AD} Thinking display not available for ${adapter.displayName}`);
26534
26813
  }
@@ -26622,7 +26901,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26622
26901
  }
26623
26902
  responseText += `
26624
26903
 
26625
- \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`;
26626
26905
  }
26627
26906
  if (observedSubagents.size > 0) {
26628
26907
  const names = [...observedSubagents].join(", ");
@@ -27177,7 +27456,19 @@ function resolveJobBackendId(job) {
27177
27456
  })();
27178
27457
  }
27179
27458
  function resolveJobModel(job) {
27180
- 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
+ }
27181
27472
  const backendId = resolveJobBackendId(job);
27182
27473
  try {
27183
27474
  const adapter = getAdapter(backendId);
@@ -27211,7 +27502,7 @@ var init_cron = __esm({
27211
27502
  });
27212
27503
 
27213
27504
  // src/agents/runners/wrap-backend.ts
27214
- import { join as join27 } from "path";
27505
+ import { join as join28 } from "path";
27215
27506
  function buildMcpCommands(backendId) {
27216
27507
  const exe = backendId === BACKEND.CURSOR ? "agent" : backendId;
27217
27508
  return {
@@ -27305,7 +27596,7 @@ function wrapBackendAdapter(adapter) {
27305
27596
  const configPath = writeMcpConfigFile(server);
27306
27597
  return ["--mcp-config", configPath];
27307
27598
  },
27308
- getSkillPath: () => join27(SKILLS_PATH, `agent-${adapter.id}.md`)
27599
+ getSkillPath: () => join28(SKILLS_PATH, `agent-${adapter.id}.md`)
27309
27600
  };
27310
27601
  }
27311
27602
  var BACKEND_CAPABILITIES;
@@ -27367,18 +27658,18 @@ var init_wrap_backend = __esm({
27367
27658
  });
27368
27659
 
27369
27660
  // src/agents/runners/config-loader.ts
27370
- import { readFileSync as readFileSync15, readdirSync as readdirSync14, existsSync as existsSync25, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
27371
- 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";
27372
27663
  import { execFileSync as execFileSync3 } from "child_process";
27373
27664
  function resolveExecutable2(config2) {
27374
- if (existsSync25(config2.executable)) return config2.executable;
27665
+ if (existsSync26(config2.executable)) return config2.executable;
27375
27666
  try {
27376
27667
  return execFileSync3("which", [config2.executable], { encoding: "utf-8" }).trim();
27377
27668
  } catch {
27378
27669
  }
27379
27670
  for (const fallback of config2.executableFallbacks ?? []) {
27380
27671
  const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
27381
- if (existsSync25(resolved)) return resolved;
27672
+ if (existsSync26(resolved)) return resolved;
27382
27673
  }
27383
27674
  return config2.executable;
27384
27675
  }
@@ -27504,12 +27795,12 @@ function configToRunner(config2) {
27504
27795
  prepareMcpInjection() {
27505
27796
  return [];
27506
27797
  },
27507
- getSkillPath: () => join28(SKILLS_PATH, `agent-${config2.id}.md`)
27798
+ getSkillPath: () => join29(SKILLS_PATH, `agent-${config2.id}.md`)
27508
27799
  };
27509
27800
  }
27510
27801
  function loadRunnerConfig(filePath) {
27511
27802
  try {
27512
- const content = readFileSync15(filePath, "utf-8");
27803
+ const content = readFileSync16(filePath, "utf-8");
27513
27804
  return JSON.parse(content);
27514
27805
  } catch (err) {
27515
27806
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -27517,14 +27808,14 @@ function loadRunnerConfig(filePath) {
27517
27808
  }
27518
27809
  }
27519
27810
  function loadAllRunnerConfigs() {
27520
- if (!existsSync25(RUNNERS_PATH)) {
27811
+ if (!existsSync26(RUNNERS_PATH)) {
27521
27812
  mkdirSync10(RUNNERS_PATH, { recursive: true });
27522
27813
  return [];
27523
27814
  }
27524
27815
  const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
27525
27816
  const configs = [];
27526
27817
  for (const file of files) {
27527
- const config2 = loadRunnerConfig(join28(RUNNERS_PATH, file));
27818
+ const config2 = loadRunnerConfig(join29(RUNNERS_PATH, file));
27528
27819
  if (config2) configs.push(config2);
27529
27820
  }
27530
27821
  return configs;
@@ -27545,16 +27836,16 @@ function registerConfigRunners() {
27545
27836
  return count;
27546
27837
  }
27547
27838
  function watchRunnerConfigs(onChange) {
27548
- if (!existsSync25(RUNNERS_PATH)) return;
27839
+ if (!existsSync26(RUNNERS_PATH)) return;
27549
27840
  for (const prev of watchedFiles) {
27550
- if (!existsSync25(prev)) {
27841
+ if (!existsSync26(prev)) {
27551
27842
  unwatchFile(prev);
27552
27843
  watchedFiles.delete(prev);
27553
27844
  }
27554
27845
  }
27555
27846
  const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
27556
27847
  for (const file of files) {
27557
- const fullPath = join28(RUNNERS_PATH, file);
27848
+ const fullPath = join29(RUNNERS_PATH, file);
27558
27849
  if (watchedFiles.has(fullPath)) continue;
27559
27850
  watchedFiles.add(fullPath);
27560
27851
  watchFile(fullPath, { interval: 5e3 }, () => {
@@ -28159,8 +28450,7 @@ var init_telegram2 = __esm({
28159
28450
  { command: "skills", description: "List and invoke skills" },
28160
28451
  { command: "extract_skill", description: "Extract a reusable skill from this session" },
28161
28452
  { command: "skill_install", description: "Install a skill from GitHub" },
28162
- { command: "voice", description: "Toggle voice responses" },
28163
- { command: "voice_config", description: "Configure voice provider and voice" },
28453
+ { command: "voice", description: "Voice settings (STT transcription + TTS replies)" },
28164
28454
  { command: "response_style", description: "Set the AI response style (concise/normal/detailed)" },
28165
28455
  { command: "model_signature", description: "Toggle model+thinking signature on responses" },
28166
28456
  { command: "imagine", description: "Generate an image from a prompt" },
@@ -28588,7 +28878,8 @@ var init_telegram2 = __esm({
28588
28878
  "reaction",
28589
28879
  () => this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
28590
28880
  { type: "emoji", emoji }
28591
- ])
28881
+ ]),
28882
+ { skipRecord: true }
28592
28883
  );
28593
28884
  } catch (err) {
28594
28885
  log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
@@ -28831,19 +29122,19 @@ var init_telegram2 = __esm({
28831
29122
  });
28832
29123
 
28833
29124
  // src/skills/bootstrap.ts
28834
- import { existsSync as existsSync26 } from "fs";
28835
- import { readdir as readdir6, readFile as readFile8, writeFile as writeFile5, copyFile } from "fs/promises";
28836
- 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";
28837
29128
  import { fileURLToPath as fileURLToPath2 } from "url";
28838
29129
  async function copyAgentManifestSkills() {
28839
- if (!existsSync26(PKG_SKILLS)) return;
29130
+ if (!existsSync27(PKG_SKILLS)) return;
28840
29131
  try {
28841
29132
  const entries = await readdir6(PKG_SKILLS, { withFileTypes: true });
28842
29133
  for (const entry of entries) {
28843
29134
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
28844
- const src = join29(PKG_SKILLS, entry.name);
28845
- const dest = join29(SKILLS_PATH, entry.name);
28846
- 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;
28847
29138
  await copyFile(src, dest);
28848
29139
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
28849
29140
  }
@@ -28853,8 +29144,8 @@ async function copyAgentManifestSkills() {
28853
29144
  }
28854
29145
  async function bootstrapSkills() {
28855
29146
  await copyAgentManifestSkills();
28856
- const usmDir = join29(SKILLS_PATH, USM_DIR_NAME);
28857
- if (existsSync26(usmDir)) return;
29147
+ const usmDir = join30(SKILLS_PATH, USM_DIR_NAME);
29148
+ if (existsSync27(usmDir)) return;
28858
29149
  try {
28859
29150
  const entries = await readdir6(SKILLS_PATH);
28860
29151
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -28877,8 +29168,8 @@ async function bootstrapSkills() {
28877
29168
  }
28878
29169
  }
28879
29170
  async function patchUsmForCcClaw(usmDir) {
28880
- const skillPath = join29(usmDir, "SKILL.md");
28881
- if (!existsSync26(skillPath)) return;
29171
+ const skillPath = join30(usmDir, "SKILL.md");
29172
+ if (!existsSync27(skillPath)) return;
28882
29173
  try {
28883
29174
  let content = await readFile8(skillPath, "utf-8");
28884
29175
  let patched = false;
@@ -28906,7 +29197,7 @@ async function patchUsmForCcClaw(usmDir) {
28906
29197
  }
28907
29198
  }
28908
29199
  if (patched) {
28909
- await writeFile5(skillPath, content, "utf-8");
29200
+ await writeFile6(skillPath, content, "utf-8");
28910
29201
  log("[skills] Patched USM SKILL.md with CC-Claw support");
28911
29202
  }
28912
29203
  } catch (err) {
@@ -28923,8 +29214,8 @@ var init_bootstrap = __esm({
28923
29214
  USM_REPO = "jacob-bd/universal-skills-manager";
28924
29215
  USM_DIR_NAME = "universal-skills-manager";
28925
29216
  CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
28926
- PKG_ROOT = join29(dirname5(fileURLToPath2(import.meta.url)), "..", "..");
28927
- PKG_SKILLS = join29(PKG_ROOT, "skills");
29217
+ PKG_ROOT = join30(dirname5(fileURLToPath2(import.meta.url)), "..", "..");
29218
+ PKG_SKILLS = join30(PKG_ROOT, "skills");
28928
29219
  }
28929
29220
  });
28930
29221
 
@@ -29037,13 +29328,13 @@ __export(ai_skill_exports, {
29037
29328
  generateAiSkill: () => generateAiSkill,
29038
29329
  installAiSkill: () => installAiSkill
29039
29330
  });
29040
- import { existsSync as existsSync27, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
29041
- 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";
29042
29333
  import { homedir as homedir9 } from "os";
29043
29334
  function generateAiSkill() {
29044
29335
  const version = VERSION;
29045
29336
  let systemState = "";
29046
- if (existsSync27(DB_PATH)) {
29337
+ if (existsSync28(DB_PATH)) {
29047
29338
  try {
29048
29339
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
29049
29340
  const readDb = openDatabaseReadOnly2();
@@ -29483,8 +29774,8 @@ function installAiSkill() {
29483
29774
  const failed = [];
29484
29775
  for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
29485
29776
  for (const dir of dirs) {
29486
- const skillDir = join30(dir, "cc-claw-cli");
29487
- const skillPath = join30(skillDir, "SKILL.md");
29777
+ const skillDir = join31(dir, "cc-claw-cli");
29778
+ const skillPath = join31(skillDir, "SKILL.md");
29488
29779
  try {
29489
29780
  mkdirSync11(skillDir, { recursive: true });
29490
29781
  writeFileSync8(skillPath, skill, "utf-8");
@@ -29503,11 +29794,11 @@ var init_ai_skill = __esm({
29503
29794
  init_paths();
29504
29795
  init_version();
29505
29796
  BACKEND_SKILL_DIRS2 = {
29506
- "cc-claw": [join30(homedir9(), ".cc-claw", "workspace", "skills")],
29507
- claude: [join30(homedir9(), ".claude", "skills")],
29508
- gemini: [join30(homedir9(), ".gemini", "skills")],
29509
- codex: [join30(homedir9(), ".agents", "skills")],
29510
- 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")]
29511
29802
  };
29512
29803
  }
29513
29804
  });
@@ -29517,21 +29808,21 @@ var index_exports = {};
29517
29808
  __export(index_exports, {
29518
29809
  main: () => main
29519
29810
  });
29520
- import { mkdirSync as mkdirSync12, existsSync as existsSync28, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync17 } from "fs";
29521
- 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";
29522
29813
  import dotenv from "dotenv";
29523
29814
  function migrateLayout() {
29524
29815
  const moves = [
29525
- [join31(CC_CLAW_HOME, "cc-claw.db"), join31(DATA_PATH, "cc-claw.db")],
29526
- [join31(CC_CLAW_HOME, "cc-claw.db-shm"), join31(DATA_PATH, "cc-claw.db-shm")],
29527
- [join31(CC_CLAW_HOME, "cc-claw.db-wal"), join31(DATA_PATH, "cc-claw.db-wal")],
29528
- [join31(CC_CLAW_HOME, "cc-claw.log"), join31(LOGS_PATH, "cc-claw.log")],
29529
- [join31(CC_CLAW_HOME, "cc-claw.log.1"), join31(LOGS_PATH, "cc-claw.log.1")],
29530
- [join31(CC_CLAW_HOME, "cc-claw.error.log"), join31(LOGS_PATH, "cc-claw.error.log")],
29531
- [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")]
29532
29823
  ];
29533
29824
  for (const [from, to] of moves) {
29534
- if (existsSync28(from) && !existsSync28(to)) {
29825
+ if (existsSync29(from) && !existsSync29(to)) {
29535
29826
  try {
29536
29827
  renameSync2(from, to);
29537
29828
  } catch {
@@ -29560,7 +29851,7 @@ async function main() {
29560
29851
  let version = "unknown";
29561
29852
  try {
29562
29853
  const pkgPath = new URL("../package.json", import.meta.url);
29563
- version = JSON.parse(readFileSync17(pkgPath, "utf-8")).version;
29854
+ version = JSON.parse(readFileSync18(pkgPath, "utf-8")).version;
29564
29855
  } catch {
29565
29856
  }
29566
29857
  log(`[cc-claw] Starting v${version}`);
@@ -29708,10 +29999,10 @@ async function main() {
29708
29999
  try {
29709
30000
  const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
29710
30001
  const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync19 } = await import("fs");
29711
- const { join: join37 } = await import("path");
29712
- const skillDir = join37(SKILLS_PATH, "cc-claw-cli");
30002
+ const { join: join38 } = await import("path");
30003
+ const skillDir = join38(SKILLS_PATH, "cc-claw-cli");
29713
30004
  mkdirSync19(skillDir, { recursive: true });
29714
- writeFileSync13(join37(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
30005
+ writeFileSync13(join38(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
29715
30006
  log("[cc-claw] AI skill updated");
29716
30007
  } catch {
29717
30008
  }
@@ -29811,10 +30102,10 @@ var init_index = __esm({
29811
30102
  init_health3();
29812
30103
  init_image_gen();
29813
30104
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SESSION_LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
29814
- if (!existsSync28(dir)) mkdirSync12(dir, { recursive: true });
30105
+ if (!existsSync29(dir)) mkdirSync12(dir, { recursive: true });
29815
30106
  }
29816
30107
  migrateLayout();
29817
- if (existsSync28(ENV_PATH)) {
30108
+ if (existsSync29(ENV_PATH)) {
29818
30109
  dotenv.config({ path: ENV_PATH });
29819
30110
  } else {
29820
30111
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -29835,12 +30126,12 @@ __export(api_client_exports, {
29835
30126
  apiPost: () => apiPost,
29836
30127
  isDaemonRunning: () => isDaemonRunning
29837
30128
  });
29838
- import { readFileSync as readFileSync18, existsSync as existsSync29 } from "fs";
30129
+ import { readFileSync as readFileSync19, existsSync as existsSync30 } from "fs";
29839
30130
  import { request as httpRequest, Agent } from "http";
29840
30131
  function getToken() {
29841
30132
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
29842
30133
  try {
29843
- if (existsSync29(TOKEN_PATH)) return readFileSync18(TOKEN_PATH, "utf-8").trim();
30134
+ if (existsSync30(TOKEN_PATH)) return readFileSync19(TOKEN_PATH, "utf-8").trim();
29844
30135
  } catch {
29845
30136
  }
29846
30137
  return null;
@@ -29939,10 +30230,10 @@ __export(service_exports2, {
29939
30230
  serviceStatus: () => serviceStatus,
29940
30231
  uninstallService: () => uninstallService
29941
30232
  });
29942
- 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";
29943
30234
  import { execFileSync as execFileSync4, execSync as execSync5 } from "child_process";
29944
30235
  import { homedir as homedir10, platform } from "os";
29945
- import { join as join32, dirname as dirname6 } from "path";
30236
+ import { join as join33, dirname as dirname6 } from "path";
29946
30237
  function xmlEscape(s) {
29947
30238
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
29948
30239
  }
@@ -29951,7 +30242,7 @@ function resolveExecutable3(name) {
29951
30242
  return execFileSync4("which", [name], { encoding: "utf-8" }).trim();
29952
30243
  } catch {
29953
30244
  const fallback = process.argv[1];
29954
- if (fallback && existsSync30(fallback)) return fallback;
30245
+ if (fallback && existsSync31(fallback)) return fallback;
29955
30246
  throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
29956
30247
  }
29957
30248
  }
@@ -29960,14 +30251,14 @@ function getPathDirs() {
29960
30251
  const home = homedir10();
29961
30252
  const dirs = /* @__PURE__ */ new Set([
29962
30253
  nodeBin,
29963
- join32(home, ".local", "bin"),
30254
+ join33(home, ".local", "bin"),
29964
30255
  "/usr/local/bin",
29965
30256
  "/usr/bin",
29966
30257
  "/bin"
29967
30258
  ]);
29968
30259
  try {
29969
30260
  const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
29970
- if (prefix) dirs.add(join32(prefix, "bin"));
30261
+ if (prefix) dirs.add(join33(prefix, "bin"));
29971
30262
  } catch {
29972
30263
  }
29973
30264
  return [...dirs].join(":");
@@ -30026,9 +30317,9 @@ function generatePlist() {
30026
30317
  }
30027
30318
  function installMacOS() {
30028
30319
  const agentsDir = dirname6(PLIST_PATH);
30029
- if (!existsSync30(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
30030
- if (!existsSync30(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30031
- 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)) {
30032
30323
  try {
30033
30324
  execFileSync4("launchctl", ["unload", PLIST_PATH]);
30034
30325
  } catch {
@@ -30040,7 +30331,7 @@ function installMacOS() {
30040
30331
  console.log(" Service loaded and starting.");
30041
30332
  }
30042
30333
  function uninstallMacOS() {
30043
- if (!existsSync30(PLIST_PATH)) {
30334
+ if (!existsSync31(PLIST_PATH)) {
30044
30335
  console.log(" No service found to uninstall.");
30045
30336
  return;
30046
30337
  }
@@ -30115,8 +30406,8 @@ WantedBy=default.target
30115
30406
  `;
30116
30407
  }
30117
30408
  function installLinux() {
30118
- if (!existsSync30(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
30119
- 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 });
30120
30411
  writeFileSync9(UNIT_PATH, generateUnit());
30121
30412
  console.log(` Installed: ${UNIT_PATH}`);
30122
30413
  execFileSync4("systemctl", ["--user", "daemon-reload"]);
@@ -30125,7 +30416,7 @@ function installLinux() {
30125
30416
  console.log(" Service enabled and started.");
30126
30417
  }
30127
30418
  function uninstallLinux() {
30128
- if (!existsSync30(UNIT_PATH)) {
30419
+ if (!existsSync31(UNIT_PATH)) {
30129
30420
  console.log(" No service found to uninstall.");
30130
30421
  return;
30131
30422
  }
@@ -30150,7 +30441,7 @@ function statusLinux() {
30150
30441
  }
30151
30442
  }
30152
30443
  function installService() {
30153
- if (!existsSync30(join32(CC_CLAW_HOME, ".env"))) {
30444
+ if (!existsSync31(join33(CC_CLAW_HOME, ".env"))) {
30154
30445
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
30155
30446
  console.error(" Run 'cc-claw setup' before installing the service.");
30156
30447
  process.exitCode = 1;
@@ -30179,9 +30470,9 @@ var init_service2 = __esm({
30179
30470
  "use strict";
30180
30471
  init_paths();
30181
30472
  PLIST_LABEL = "com.cc-claw";
30182
- PLIST_PATH = join32(homedir10(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
30183
- SYSTEMD_DIR = join32(homedir10(), ".config", "systemd", "user");
30184
- 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");
30185
30476
  }
30186
30477
  });
30187
30478
 
@@ -30348,13 +30639,13 @@ var init_daemon = __esm({
30348
30639
  });
30349
30640
 
30350
30641
  // src/cli/resolve-chat.ts
30351
- import { readFileSync as readFileSync20 } from "fs";
30642
+ import { readFileSync as readFileSync21 } from "fs";
30352
30643
  function resolveChatId2(globalOpts) {
30353
30644
  const explicit = globalOpts.chat;
30354
30645
  if (explicit) return explicit;
30355
30646
  if (_cachedDefault) return _cachedDefault;
30356
30647
  try {
30357
- const content = readFileSync20(ENV_PATH, "utf-8");
30648
+ const content = readFileSync21(ENV_PATH, "utf-8");
30358
30649
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
30359
30650
  if (match) {
30360
30651
  _cachedDefault = match[1].split(",")[0].trim();
@@ -30378,7 +30669,7 @@ var status_exports = {};
30378
30669
  __export(status_exports, {
30379
30670
  statusCommand: () => statusCommand
30380
30671
  });
30381
- import { existsSync as existsSync31, statSync as statSync10 } from "fs";
30672
+ import { existsSync as existsSync32, statSync as statSync10 } from "fs";
30382
30673
  async function statusCommand(globalOpts, localOpts) {
30383
30674
  try {
30384
30675
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
@@ -30418,7 +30709,7 @@ async function statusCommand(globalOpts, localOpts) {
30418
30709
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
30419
30710
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
30420
30711
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
30421
- const dbStat = existsSync31(DB_PATH) ? statSync10(DB_PATH) : null;
30712
+ const dbStat = existsSync32(DB_PATH) ? statSync10(DB_PATH) : null;
30422
30713
  let daemonRunning = false;
30423
30714
  let daemonInfo = {};
30424
30715
  try {
@@ -30530,13 +30821,13 @@ __export(doctor_exports, {
30530
30821
  doctorCommand: () => doctorCommand,
30531
30822
  doctorErrors: () => doctorErrors
30532
30823
  });
30533
- import { existsSync as existsSync32, accessSync, constants } from "fs";
30824
+ import { existsSync as existsSync33, accessSync, constants } from "fs";
30534
30825
  import { execFileSync as execFileSync5 } from "child_process";
30535
30826
  async function doctorCommand(globalOpts, localOpts) {
30536
30827
  const checks = [];
30537
30828
  const dbChecks = checkDatabase();
30538
30829
  checks.push(...dbChecks);
30539
- if (existsSync32(DB_PATH)) {
30830
+ if (existsSync33(DB_PATH)) {
30540
30831
  try {
30541
30832
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30542
30833
  const readDb = openDatabaseReadOnly2();
@@ -30562,7 +30853,7 @@ async function doctorCommand(globalOpts, localOpts) {
30562
30853
  checks.push({ name: "Database health", status: "error", message: err.message });
30563
30854
  }
30564
30855
  }
30565
- if (existsSync32(ENV_PATH)) {
30856
+ if (existsSync33(ENV_PATH)) {
30566
30857
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
30567
30858
  } else {
30568
30859
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
@@ -30605,7 +30896,7 @@ async function doctorCommand(globalOpts, localOpts) {
30605
30896
  } catch {
30606
30897
  }
30607
30898
  const tokenPath = `${DATA_PATH}/api-token`;
30608
- if (existsSync32(tokenPath)) {
30899
+ if (existsSync33(tokenPath)) {
30609
30900
  try {
30610
30901
  accessSync(tokenPath, constants.R_OK);
30611
30902
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -30671,7 +30962,7 @@ async function doctorCommand(globalOpts, localOpts) {
30671
30962
  const errorChecks = checks.filter(
30672
30963
  (c) => ["Rate limits", "Content silence", "Spawn timeouts", "Other errors"].includes(c.name) && c.status !== "ok"
30673
30964
  );
30674
- if (errorChecks.length > 0 && existsSync32(ERROR_LOG_PATH)) {
30965
+ if (errorChecks.length > 0 && existsSync33(ERROR_LOG_PATH)) {
30675
30966
  try {
30676
30967
  const { writeFileSync: writeFileSync13 } = await import("fs");
30677
30968
  writeFileSync13(ERROR_LOG_PATH, "");
@@ -30800,15 +31091,15 @@ var logs_exports = {};
30800
31091
  __export(logs_exports, {
30801
31092
  logsCommand: () => logsCommand
30802
31093
  });
30803
- 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";
30804
31095
  async function logsCommand(opts) {
30805
31096
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
30806
- if (!existsSync33(logFile)) {
31097
+ if (!existsSync34(logFile)) {
30807
31098
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
30808
31099
  process.exit(1);
30809
31100
  }
30810
31101
  const maxLines = parseInt(opts.lines ?? "100", 10);
30811
- const content = readFileSync22(logFile, "utf-8");
31102
+ const content = readFileSync23(logFile, "utf-8");
30812
31103
  const allLines = content.split("\n");
30813
31104
  const tailLines = allLines.slice(-maxLines);
30814
31105
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -30818,7 +31109,7 @@ async function logsCommand(opts) {
30818
31109
  let lastLength = content.length;
30819
31110
  watchFile2(logFile, { interval: 500 }, () => {
30820
31111
  try {
30821
- const newContent = readFileSync22(logFile, "utf-8");
31112
+ const newContent = readFileSync23(logFile, "utf-8");
30822
31113
  if (newContent.length > lastLength) {
30823
31114
  const newPart = newContent.slice(lastLength);
30824
31115
  process.stdout.write(newPart);
@@ -30850,7 +31141,7 @@ __export(session_logs_exports, {
30850
31141
  sessionLogsList: () => sessionLogsList,
30851
31142
  sessionLogsTail: () => sessionLogsTail
30852
31143
  });
30853
- 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";
30854
31145
  async function sessionLogsList(opts) {
30855
31146
  const logs = listSessionLogs();
30856
31147
  if (logs.length === 0) {
@@ -30907,12 +31198,12 @@ async function sessionLogsTail(opts) {
30907
31198
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
30908
31199
  let lastLength = 0;
30909
31200
  try {
30910
- lastLength = readFileSync23(targetPath, "utf-8").length;
31201
+ lastLength = readFileSync24(targetPath, "utf-8").length;
30911
31202
  } catch {
30912
31203
  }
30913
31204
  watchFile3(targetPath, { interval: 500 }, () => {
30914
31205
  try {
30915
- const content = readFileSync23(targetPath, "utf-8");
31206
+ const content = readFileSync24(targetPath, "utf-8");
30916
31207
  if (content.length > lastLength) {
30917
31208
  process.stdout.write(content.slice(lastLength));
30918
31209
  lastLength = content.length;
@@ -30956,11 +31247,11 @@ __export(gemini_exports, {
30956
31247
  geminiReorder: () => geminiReorder,
30957
31248
  geminiRotation: () => geminiRotation
30958
31249
  });
30959
- import { existsSync as existsSync35, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync24, chmodSync } from "fs";
30960
- 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";
30961
31252
  import { createInterface as createInterface8 } from "readline";
30962
31253
  function requireDb() {
30963
- if (!existsSync35(DB_PATH)) {
31254
+ if (!existsSync36(DB_PATH)) {
30964
31255
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30965
31256
  process.exit(1);
30966
31257
  }
@@ -30985,9 +31276,9 @@ async function resolveSlotId(idOrLabel) {
30985
31276
  function resolveOAuthEmail(configHome) {
30986
31277
  if (!configHome) return null;
30987
31278
  try {
30988
- const accountsPath = join33(configHome, ".gemini", "google_accounts.json");
30989
- if (!existsSync35(accountsPath)) return null;
30990
- 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"));
30991
31282
  return accounts.active || null;
30992
31283
  } catch {
30993
31284
  return null;
@@ -31069,14 +31360,14 @@ async function geminiAddKey(globalOpts, opts) {
31069
31360
  }
31070
31361
  async function geminiAddAccount(globalOpts, opts) {
31071
31362
  await requireWriteDb();
31072
- const slotsDir = join33(CC_CLAW_HOME, "gemini-slots");
31073
- if (!existsSync35(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
31363
+ const slotsDir = join34(CC_CLAW_HOME, "gemini-slots");
31364
+ if (!existsSync36(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
31074
31365
  const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
31075
31366
  const tempId = Date.now();
31076
- const slotDir = join33(slotsDir, `slot-${tempId}`);
31367
+ const slotDir = join34(slotsDir, `slot-${tempId}`);
31077
31368
  mkdirSync14(slotDir, { recursive: true, mode: 448 });
31078
- mkdirSync14(join33(slotDir, ".gemini"), { recursive: true });
31079
- writeFileSync10(join33(slotDir, ".gemini", "settings.json"), JSON.stringify({
31369
+ mkdirSync14(join34(slotDir, ".gemini"), { recursive: true });
31370
+ writeFileSync10(join34(slotDir, ".gemini", "settings.json"), JSON.stringify({
31080
31371
  security: { auth: { selectedType: "oauth-personal" } }
31081
31372
  }, null, 2));
31082
31373
  console.log("");
@@ -31093,8 +31384,8 @@ async function geminiAddAccount(globalOpts, opts) {
31093
31384
  });
31094
31385
  } catch {
31095
31386
  }
31096
- const oauthPath = join33(slotDir, ".gemini", "oauth_creds.json");
31097
- if (!existsSync35(oauthPath)) {
31387
+ const oauthPath = join34(slotDir, ".gemini", "oauth_creds.json");
31388
+ if (!existsSync36(oauthPath)) {
31098
31389
  console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
31099
31390
  console.log(" The slot directory is preserved at: " + slotDir);
31100
31391
  console.log(" Re-run: cc-claw gemini add-account\n");
@@ -31102,7 +31393,7 @@ async function geminiAddAccount(globalOpts, opts) {
31102
31393
  }
31103
31394
  let accountEmail = "unknown";
31104
31395
  try {
31105
- 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"));
31106
31397
  accountEmail = accounts.active || accountEmail;
31107
31398
  } catch {
31108
31399
  }
@@ -31213,9 +31504,9 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31213
31504
  outputError("NO_CONFIG", `Slot "${idOrLabel}" has no config directory \u2014 cannot re-login.`);
31214
31505
  return;
31215
31506
  }
31216
- const settingsPath = join33(slot.configHome, ".gemini", "settings.json");
31217
- if (!existsSync35(settingsPath)) {
31218
- 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 });
31219
31510
  writeFileSync10(settingsPath, JSON.stringify({
31220
31511
  security: { auth: { selectedType: "oauth-personal" } }
31221
31512
  }, null, 2));
@@ -31239,8 +31530,8 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31239
31530
  });
31240
31531
  } catch {
31241
31532
  }
31242
- const oauthPath = join33(slot.configHome, ".gemini", "oauth_creds.json");
31243
- if (!existsSync35(oauthPath)) {
31533
+ const oauthPath = join34(slot.configHome, ".gemini", "oauth_creds.json");
31534
+ if (!existsSync36(oauthPath)) {
31244
31535
  console.log(error2("\n Re-login failed \u2014 no OAuth credentials found."));
31245
31536
  console.log(` Try again: cc-claw gemini re-login ${idOrLabel}
31246
31537
  `);
@@ -31250,7 +31541,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31250
31541
  setGeminiSlotEnabled2(slotId, true);
31251
31542
  let accountEmail = slot.label;
31252
31543
  try {
31253
- 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"));
31254
31545
  if (accounts.active) accountEmail = accounts.active;
31255
31546
  } catch {
31256
31547
  }
@@ -31309,11 +31600,11 @@ __export(backend_cmd_factory_exports, {
31309
31600
  makeReorder: () => makeReorder,
31310
31601
  registerBackendSlotCommands: () => registerBackendSlotCommands
31311
31602
  });
31312
- import { existsSync as existsSync36, mkdirSync as mkdirSync15, readFileSync as readFileSync25 } from "fs";
31313
- 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";
31314
31605
  import { createInterface as createInterface9 } from "readline";
31315
31606
  function requireDb2() {
31316
- if (!existsSync36(DB_PATH)) {
31607
+ if (!existsSync37(DB_PATH)) {
31317
31608
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31318
31609
  process.exit(1);
31319
31610
  }
@@ -31402,10 +31693,10 @@ function makeAddAccount(backend2, displayName) {
31402
31693
  process.exit(1);
31403
31694
  }
31404
31695
  await requireWriteDb2();
31405
- const slotsDir = join34(CC_CLAW_HOME, config2.slotsSubdir);
31406
- if (!existsSync36(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
31696
+ const slotsDir = join35(CC_CLAW_HOME, config2.slotsSubdir);
31697
+ if (!existsSync37(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
31407
31698
  const tempId = Date.now();
31408
- const slotDir = join34(slotsDir, `slot-${tempId}`);
31699
+ const slotDir = join35(slotsDir, `slot-${tempId}`);
31409
31700
  mkdirSync15(slotDir, { recursive: true, mode: 448 });
31410
31701
  if (config2.preSetup) config2.preSetup(slotDir);
31411
31702
  console.log("");
@@ -31656,22 +31947,22 @@ var init_backend_cmd_factory = __esm({
31656
31947
  envValue: (slotDir) => slotDir,
31657
31948
  envOverrides: { ANTHROPIC_API_KEY: void 0 },
31658
31949
  preSetup: (slotDir) => {
31659
- mkdirSync15(join34(slotDir, ".claude"), { recursive: true });
31950
+ mkdirSync15(join35(slotDir, ".claude"), { recursive: true });
31660
31951
  },
31661
31952
  verifyCredentials: (slotDir) => {
31662
- const claudeJson = join34(slotDir, ".claude.json");
31663
- const claudeJsonNested = join34(slotDir, ".claude", ".claude.json");
31664
- if (existsSync36(claudeJson)) {
31953
+ const claudeJson = join35(slotDir, ".claude.json");
31954
+ const claudeJsonNested = join35(slotDir, ".claude", ".claude.json");
31955
+ if (existsSync37(claudeJson)) {
31665
31956
  try {
31666
- const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
31957
+ const data = JSON.parse(readFileSync26(claudeJson, "utf-8"));
31667
31958
  return Boolean(data.oauthAccount);
31668
31959
  } catch {
31669
31960
  return false;
31670
31961
  }
31671
31962
  }
31672
- if (existsSync36(claudeJsonNested)) {
31963
+ if (existsSync37(claudeJsonNested)) {
31673
31964
  try {
31674
- const data = JSON.parse(readFileSync25(claudeJsonNested, "utf-8"));
31965
+ const data = JSON.parse(readFileSync26(claudeJsonNested, "utf-8"));
31675
31966
  return Boolean(data.oauthAccount);
31676
31967
  } catch {
31677
31968
  return false;
@@ -31692,9 +31983,9 @@ var init_backend_cmd_factory = __esm({
31692
31983
  } catch {
31693
31984
  }
31694
31985
  try {
31695
- const claudeJson = join34(slotDir, ".claude.json");
31696
- if (existsSync36(claudeJson)) {
31697
- 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"));
31698
31989
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
31699
31990
  }
31700
31991
  } catch {
@@ -31709,11 +32000,11 @@ var init_backend_cmd_factory = __esm({
31709
32000
  envValue: (slotDir) => slotDir,
31710
32001
  envOverrides: { OPENAI_API_KEY: void 0 },
31711
32002
  verifyCredentials: (slotDir) => {
31712
- return existsSync36(join34(slotDir, "auth.json"));
32003
+ return existsSync37(join35(slotDir, "auth.json"));
31713
32004
  },
31714
32005
  extractLabel: (slotDir) => {
31715
32006
  try {
31716
- const authData = JSON.parse(readFileSync25(join34(slotDir, "auth.json"), "utf-8"));
32007
+ const authData = JSON.parse(readFileSync26(join35(slotDir, "auth.json"), "utf-8"));
31717
32008
  if (authData.email) return authData.email;
31718
32009
  if (authData.account_name) return authData.account_name;
31719
32010
  if (authData.user?.email) return authData.user.email;
@@ -31737,9 +32028,9 @@ __export(ollama_exports3, {
31737
32028
  ollamaRemove: () => ollamaRemove,
31738
32029
  ollamaTest: () => ollamaTest
31739
32030
  });
31740
- import { existsSync as existsSync37 } from "fs";
32031
+ import { existsSync as existsSync38 } from "fs";
31741
32032
  function requireDb3() {
31742
- if (!existsSync37(DB_PATH)) {
32033
+ if (!existsSync38(DB_PATH)) {
31743
32034
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31744
32035
  process.exit(1);
31745
32036
  }
@@ -31998,12 +32289,12 @@ __export(backend_exports, {
31998
32289
  backendList: () => backendList,
31999
32290
  backendSet: () => backendSet
32000
32291
  });
32001
- import { existsSync as existsSync38 } from "fs";
32292
+ import { existsSync as existsSync39 } from "fs";
32002
32293
  async function backendList(globalOpts) {
32003
32294
  const { getAvailableAdapters: getAvailableAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
32004
32295
  const chatId = resolveChatId2(globalOpts);
32005
32296
  let activeBackend = null;
32006
- if (existsSync38(DB_PATH)) {
32297
+ if (existsSync39(DB_PATH)) {
32007
32298
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32008
32299
  const readDb = openDatabaseReadOnly2();
32009
32300
  try {
@@ -32034,7 +32325,7 @@ async function backendList(globalOpts) {
32034
32325
  }
32035
32326
  async function backendGet(globalOpts) {
32036
32327
  const chatId = resolveChatId2(globalOpts);
32037
- if (!existsSync38(DB_PATH)) {
32328
+ if (!existsSync39(DB_PATH)) {
32038
32329
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32039
32330
  process.exit(1);
32040
32331
  }
@@ -32078,13 +32369,13 @@ __export(model_exports, {
32078
32369
  modelList: () => modelList,
32079
32370
  modelSet: () => modelSet
32080
32371
  });
32081
- import { existsSync as existsSync39 } from "fs";
32372
+ import { existsSync as existsSync40 } from "fs";
32082
32373
  async function modelList(globalOpts) {
32083
32374
  const chatId = resolveChatId2(globalOpts);
32084
32375
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32085
32376
  const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
32086
32377
  let backendId = "claude";
32087
- if (existsSync39(DB_PATH)) {
32378
+ if (existsSync40(DB_PATH)) {
32088
32379
  const readDb = openDatabaseReadOnly2();
32089
32380
  try {
32090
32381
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -32117,7 +32408,7 @@ async function modelList(globalOpts) {
32117
32408
  }
32118
32409
  async function modelGet(globalOpts) {
32119
32410
  const chatId = resolveChatId2(globalOpts);
32120
- if (!existsSync39(DB_PATH)) {
32411
+ if (!existsSync40(DB_PATH)) {
32121
32412
  outputError("DB_NOT_FOUND", "Database not found.");
32122
32413
  process.exit(1);
32123
32414
  }
@@ -32161,9 +32452,9 @@ __export(memory_exports2, {
32161
32452
  memoryList: () => memoryList,
32162
32453
  memorySearch: () => memorySearch
32163
32454
  });
32164
- import { existsSync as existsSync40 } from "fs";
32455
+ import { existsSync as existsSync41 } from "fs";
32165
32456
  async function memoryList(globalOpts) {
32166
- if (!existsSync40(DB_PATH)) {
32457
+ if (!existsSync41(DB_PATH)) {
32167
32458
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32168
32459
  process.exit(1);
32169
32460
  }
@@ -32187,7 +32478,7 @@ async function memoryList(globalOpts) {
32187
32478
  });
32188
32479
  }
32189
32480
  async function memorySearch(globalOpts, query) {
32190
- if (!existsSync40(DB_PATH)) {
32481
+ if (!existsSync41(DB_PATH)) {
32191
32482
  outputError("DB_NOT_FOUND", "Database not found.");
32192
32483
  process.exit(1);
32193
32484
  }
@@ -32209,7 +32500,7 @@ async function memorySearch(globalOpts, query) {
32209
32500
  });
32210
32501
  }
32211
32502
  async function memoryHistory(globalOpts, opts) {
32212
- if (!existsSync40(DB_PATH)) {
32503
+ if (!existsSync41(DB_PATH)) {
32213
32504
  outputError("DB_NOT_FOUND", "Database not found.");
32214
32505
  process.exit(1);
32215
32506
  }
@@ -32257,7 +32548,7 @@ __export(cron_exports2, {
32257
32548
  cronList: () => cronList,
32258
32549
  cronRuns: () => cronRuns
32259
32550
  });
32260
- import { existsSync as existsSync41 } from "fs";
32551
+ import { existsSync as existsSync42 } from "fs";
32261
32552
  function parseFallbacks(raw) {
32262
32553
  return raw.slice(0, 3).map((f) => {
32263
32554
  const [backend2, ...rest] = f.split(":");
@@ -32278,7 +32569,7 @@ function parseAndValidateTimeout(raw) {
32278
32569
  return val;
32279
32570
  }
32280
32571
  async function cronList(globalOpts) {
32281
- if (!existsSync41(DB_PATH)) {
32572
+ if (!existsSync42(DB_PATH)) {
32282
32573
  outputError("DB_NOT_FOUND", "Database not found.");
32283
32574
  process.exit(1);
32284
32575
  }
@@ -32316,7 +32607,7 @@ async function cronList(globalOpts) {
32316
32607
  });
32317
32608
  }
32318
32609
  async function cronHealth(globalOpts) {
32319
- if (!existsSync41(DB_PATH)) {
32610
+ if (!existsSync42(DB_PATH)) {
32320
32611
  outputError("DB_NOT_FOUND", "Database not found.");
32321
32612
  process.exit(1);
32322
32613
  }
@@ -32477,7 +32768,7 @@ async function cronEdit(globalOpts, id, opts) {
32477
32768
  }
32478
32769
  }
32479
32770
  async function cronRuns(globalOpts, jobId, opts) {
32480
- if (!existsSync41(DB_PATH)) {
32771
+ if (!existsSync42(DB_PATH)) {
32481
32772
  outputError("DB_NOT_FOUND", "Database not found.");
32482
32773
  process.exit(1);
32483
32774
  }
@@ -32524,9 +32815,9 @@ __export(agents_exports, {
32524
32815
  runnersList: () => runnersList,
32525
32816
  tasksList: () => tasksList
32526
32817
  });
32527
- import { existsSync as existsSync42 } from "fs";
32818
+ import { existsSync as existsSync43 } from "fs";
32528
32819
  async function agentsList(globalOpts) {
32529
- if (!existsSync42(DB_PATH)) {
32820
+ if (!existsSync43(DB_PATH)) {
32530
32821
  outputError("DB_NOT_FOUND", "Database not found.");
32531
32822
  process.exit(1);
32532
32823
  }
@@ -32557,7 +32848,7 @@ async function agentsList(globalOpts) {
32557
32848
  });
32558
32849
  }
32559
32850
  async function tasksList(globalOpts) {
32560
- if (!existsSync42(DB_PATH)) {
32851
+ if (!existsSync43(DB_PATH)) {
32561
32852
  outputError("DB_NOT_FOUND", "Database not found.");
32562
32853
  process.exit(1);
32563
32854
  }
@@ -32685,10 +32976,10 @@ __export(db_exports, {
32685
32976
  dbPath: () => dbPath,
32686
32977
  dbStats: () => dbStats
32687
32978
  });
32688
- 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";
32689
32980
  import { dirname as dirname7 } from "path";
32690
32981
  async function dbStats(globalOpts) {
32691
- if (!existsSync43(DB_PATH)) {
32982
+ if (!existsSync44(DB_PATH)) {
32692
32983
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
32693
32984
  process.exit(1);
32694
32985
  }
@@ -32696,7 +32987,7 @@ async function dbStats(globalOpts) {
32696
32987
  const readDb = openDatabaseReadOnly2();
32697
32988
  const mainSize = statSync11(DB_PATH).size;
32698
32989
  const walPath = DB_PATH + "-wal";
32699
- const walSize = existsSync43(walPath) ? statSync11(walPath).size : 0;
32990
+ const walSize = existsSync44(walPath) ? statSync11(walPath).size : 0;
32700
32991
  const tableNames = readDb.prepare(
32701
32992
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
32702
32993
  ).all();
@@ -32730,7 +33021,7 @@ async function dbPath(globalOpts) {
32730
33021
  output({ path: DB_PATH }, (d) => d.path);
32731
33022
  }
32732
33023
  async function dbBackup(globalOpts, destPath) {
32733
- if (!existsSync43(DB_PATH)) {
33024
+ if (!existsSync44(DB_PATH)) {
32734
33025
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
32735
33026
  process.exit(1);
32736
33027
  }
@@ -32739,7 +33030,7 @@ async function dbBackup(globalOpts, destPath) {
32739
33030
  mkdirSync16(dirname7(dest), { recursive: true });
32740
33031
  copyFileSync3(DB_PATH, dest);
32741
33032
  const walPath = DB_PATH + "-wal";
32742
- if (existsSync43(walPath)) copyFileSync3(walPath, dest + "-wal");
33033
+ if (existsSync44(walPath)) copyFileSync3(walPath, dest + "-wal");
32743
33034
  output({ path: dest, sizeBytes: statSync11(dest).size }, (d) => {
32744
33035
  const b = d;
32745
33036
  return `
@@ -32768,9 +33059,9 @@ __export(usage_exports, {
32768
33059
  usageCost: () => usageCost,
32769
33060
  usageTokens: () => usageTokens
32770
33061
  });
32771
- import { existsSync as existsSync44 } from "fs";
33062
+ import { existsSync as existsSync45 } from "fs";
32772
33063
  function ensureDb() {
32773
- if (!existsSync44(DB_PATH)) {
33064
+ if (!existsSync45(DB_PATH)) {
32774
33065
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32775
33066
  process.exit(1);
32776
33067
  }
@@ -32960,9 +33251,9 @@ __export(config_exports2, {
32960
33251
  configList: () => configList,
32961
33252
  configSet: () => configSet
32962
33253
  });
32963
- import { existsSync as existsSync45, readFileSync as readFileSync26 } from "fs";
33254
+ import { existsSync as existsSync46, readFileSync as readFileSync27 } from "fs";
32964
33255
  async function configList(globalOpts) {
32965
- if (!existsSync45(DB_PATH)) {
33256
+ if (!existsSync46(DB_PATH)) {
32966
33257
  outputError("DB_NOT_FOUND", "Database not found.");
32967
33258
  process.exit(1);
32968
33259
  }
@@ -32996,7 +33287,7 @@ async function configGet(globalOpts, key) {
32996
33287
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
32997
33288
  process.exit(1);
32998
33289
  }
32999
- if (!existsSync45(DB_PATH)) {
33290
+ if (!existsSync46(DB_PATH)) {
33000
33291
  outputError("DB_NOT_FOUND", "Database not found.");
33001
33292
  process.exit(1);
33002
33293
  }
@@ -33042,11 +33333,11 @@ async function configSet(globalOpts, key, value) {
33042
33333
  }
33043
33334
  }
33044
33335
  async function configEnv(_globalOpts) {
33045
- if (!existsSync45(ENV_PATH)) {
33336
+ if (!existsSync46(ENV_PATH)) {
33046
33337
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
33047
33338
  process.exit(1);
33048
33339
  }
33049
- const content = readFileSync26(ENV_PATH, "utf-8");
33340
+ const content = readFileSync27(ENV_PATH, "utf-8");
33050
33341
  const entries = {};
33051
33342
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
33052
33343
  for (const line of content.split("\n")) {
@@ -33096,9 +33387,9 @@ __export(session_exports, {
33096
33387
  sessionGet: () => sessionGet,
33097
33388
  sessionNew: () => sessionNew
33098
33389
  });
33099
- import { existsSync as existsSync46 } from "fs";
33390
+ import { existsSync as existsSync47 } from "fs";
33100
33391
  async function sessionGet(globalOpts) {
33101
- if (!existsSync46(DB_PATH)) {
33392
+ if (!existsSync47(DB_PATH)) {
33102
33393
  outputError("DB_NOT_FOUND", "Database not found.");
33103
33394
  process.exit(1);
33104
33395
  }
@@ -33159,9 +33450,9 @@ __export(permissions_exports, {
33159
33450
  verboseGet: () => verboseGet,
33160
33451
  verboseSet: () => verboseSet
33161
33452
  });
33162
- import { existsSync as existsSync47 } from "fs";
33453
+ import { existsSync as existsSync48 } from "fs";
33163
33454
  function ensureDb2() {
33164
- if (!existsSync47(DB_PATH)) {
33455
+ if (!existsSync48(DB_PATH)) {
33165
33456
  outputError("DB_NOT_FOUND", "Database not found.");
33166
33457
  process.exit(1);
33167
33458
  }
@@ -33308,9 +33599,9 @@ __export(cwd_exports, {
33308
33599
  cwdGet: () => cwdGet,
33309
33600
  cwdSet: () => cwdSet
33310
33601
  });
33311
- import { existsSync as existsSync48 } from "fs";
33602
+ import { existsSync as existsSync49 } from "fs";
33312
33603
  async function cwdGet(globalOpts) {
33313
- if (!existsSync48(DB_PATH)) {
33604
+ if (!existsSync49(DB_PATH)) {
33314
33605
  outputError("DB_NOT_FOUND", "Database not found.");
33315
33606
  process.exit(1);
33316
33607
  }
@@ -33372,9 +33663,9 @@ __export(voice_exports, {
33372
33663
  voiceGet: () => voiceGet,
33373
33664
  voiceSet: () => voiceSet
33374
33665
  });
33375
- import { existsSync as existsSync49 } from "fs";
33666
+ import { existsSync as existsSync50 } from "fs";
33376
33667
  async function voiceGet(globalOpts) {
33377
- if (!existsSync49(DB_PATH)) {
33668
+ if (!existsSync50(DB_PATH)) {
33378
33669
  outputError("DB_NOT_FOUND", "Database not found.");
33379
33670
  process.exit(1);
33380
33671
  }
@@ -33423,9 +33714,9 @@ __export(heartbeat_exports2, {
33423
33714
  heartbeatGet: () => heartbeatGet,
33424
33715
  heartbeatSet: () => heartbeatSet
33425
33716
  });
33426
- import { existsSync as existsSync50 } from "fs";
33717
+ import { existsSync as existsSync51 } from "fs";
33427
33718
  async function heartbeatGet(globalOpts) {
33428
- if (!existsSync50(DB_PATH)) {
33719
+ if (!existsSync51(DB_PATH)) {
33429
33720
  outputError("DB_NOT_FOUND", "Database not found.");
33430
33721
  process.exit(1);
33431
33722
  }
@@ -33636,9 +33927,9 @@ __export(summarizer_exports, {
33636
33927
  summarizerGet: () => summarizerGet,
33637
33928
  summarizerSet: () => summarizerSet
33638
33929
  });
33639
- import { existsSync as existsSync51 } from "fs";
33930
+ import { existsSync as existsSync52 } from "fs";
33640
33931
  async function summarizerGet(globalOpts) {
33641
- if (!existsSync51(DB_PATH)) {
33932
+ if (!existsSync52(DB_PATH)) {
33642
33933
  outputError("DB_NOT_FOUND", "Database not found.");
33643
33934
  process.exit(1);
33644
33935
  }
@@ -33682,9 +33973,9 @@ __export(thinking_exports, {
33682
33973
  thinkingGet: () => thinkingGet,
33683
33974
  thinkingSet: () => thinkingSet
33684
33975
  });
33685
- import { existsSync as existsSync52 } from "fs";
33976
+ import { existsSync as existsSync53 } from "fs";
33686
33977
  async function thinkingGet(globalOpts) {
33687
- if (!existsSync52(DB_PATH)) {
33978
+ if (!existsSync53(DB_PATH)) {
33688
33979
  outputError("DB_NOT_FOUND", "Database not found.");
33689
33980
  process.exit(1);
33690
33981
  }
@@ -33728,9 +34019,9 @@ __export(chats_exports, {
33728
34019
  chatsList: () => chatsList,
33729
34020
  chatsRemoveAlias: () => chatsRemoveAlias
33730
34021
  });
33731
- import { existsSync as existsSync53 } from "fs";
34022
+ import { existsSync as existsSync54 } from "fs";
33732
34023
  async function chatsList(_globalOpts) {
33733
- if (!existsSync53(DB_PATH)) {
34024
+ if (!existsSync54(DB_PATH)) {
33734
34025
  outputError("DB_NOT_FOUND", "Database not found.");
33735
34026
  process.exit(1);
33736
34027
  }
@@ -33858,9 +34149,9 @@ var mcps_exports2 = {};
33858
34149
  __export(mcps_exports2, {
33859
34150
  mcpsList: () => mcpsList
33860
34151
  });
33861
- import { existsSync as existsSync54 } from "fs";
34152
+ import { existsSync as existsSync55 } from "fs";
33862
34153
  async function mcpsList(_globalOpts) {
33863
- if (!existsSync54(DB_PATH)) {
34154
+ if (!existsSync55(DB_PATH)) {
33864
34155
  outputError("DB_NOT_FOUND", "Database not found.");
33865
34156
  process.exit(1);
33866
34157
  }
@@ -33897,11 +34188,11 @@ __export(chat_exports2, {
33897
34188
  chatSend: () => chatSend
33898
34189
  });
33899
34190
  import { request as httpRequest2 } from "http";
33900
- import { readFileSync as readFileSync27, existsSync as existsSync55 } from "fs";
34191
+ import { readFileSync as readFileSync28, existsSync as existsSync56 } from "fs";
33901
34192
  function getToken2() {
33902
34193
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
33903
34194
  try {
33904
- if (existsSync55(TOKEN_PATH2)) return readFileSync27(TOKEN_PATH2, "utf-8").trim();
34195
+ if (existsSync56(TOKEN_PATH2)) return readFileSync28(TOKEN_PATH2, "utf-8").trim();
33905
34196
  } catch {
33906
34197
  }
33907
34198
  return null;
@@ -34389,7 +34680,7 @@ __export(completion_exports, {
34389
34680
  completionCommand: () => completionCommand
34390
34681
  });
34391
34682
  import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync17 } from "fs";
34392
- import { join as join35 } from "path";
34683
+ import { join as join36 } from "path";
34393
34684
  import { homedir as homedir11 } from "os";
34394
34685
  async function completionCommand(opts) {
34395
34686
  const shell = opts.shell ?? detectShell();
@@ -34405,10 +34696,10 @@ async function completionCommand(opts) {
34405
34696
  process.exit(1);
34406
34697
  }
34407
34698
  if (opts.install) {
34408
- const dir = join35(homedir11(), ".config", "cc-claw", "completions");
34699
+ const dir = join36(homedir11(), ".config", "cc-claw", "completions");
34409
34700
  mkdirSync17(dir, { recursive: true });
34410
34701
  const filename = shell === "zsh" ? "_cc-claw" : shell === "fish" ? "cc-claw.fish" : "cc-claw.bash";
34411
- const filepath = join35(dir, filename);
34702
+ const filepath = join36(dir, filename);
34412
34703
  writeFileSync11(filepath, script, "utf-8");
34413
34704
  console.log(`\u2713 Completion script written to ${filepath}
34414
34705
  `);
@@ -34581,9 +34872,9 @@ __export(evolve_exports2, {
34581
34872
  evolveStatus: () => evolveStatus,
34582
34873
  evolveUndo: () => evolveUndo
34583
34874
  });
34584
- import { existsSync as existsSync56 } from "fs";
34875
+ import { existsSync as existsSync57 } from "fs";
34585
34876
  function ensureDb3() {
34586
- if (!existsSync56(DB_PATH)) {
34877
+ if (!existsSync57(DB_PATH)) {
34587
34878
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
34588
34879
  process.exit(1);
34589
34880
  }
@@ -35057,10 +35348,10 @@ var init_optimize2 = __esm({
35057
35348
 
35058
35349
  // src/setup.ts
35059
35350
  var setup_exports = {};
35060
- 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";
35061
35352
  import { execFileSync as execFileSync6 } from "child_process";
35062
35353
  import { createInterface as createInterface11 } from "readline";
35063
- import { join as join36 } from "path";
35354
+ import { join as join37 } from "path";
35064
35355
  function divider2() {
35065
35356
  console.log(dim("\u2500".repeat(55)));
35066
35357
  }
@@ -35135,21 +35426,21 @@ async function setup() {
35135
35426
  }
35136
35427
  console.log("");
35137
35428
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
35138
- if (!existsSync57(dir)) mkdirSync18(dir, { recursive: true });
35429
+ if (!existsSync58(dir)) mkdirSync18(dir, { recursive: true });
35139
35430
  }
35140
35431
  const env = {};
35141
- const envSource = existsSync57(ENV_PATH) ? ENV_PATH : existsSync57(".env") ? ".env" : null;
35432
+ const envSource = existsSync58(ENV_PATH) ? ENV_PATH : existsSync58(".env") ? ".env" : null;
35142
35433
  if (envSource) {
35143
35434
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
35144
35435
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
35145
- const existing = readFileSync28(envSource, "utf-8");
35436
+ const existing = readFileSync29(envSource, "utf-8");
35146
35437
  for (const line of existing.split("\n")) {
35147
35438
  const match = line.match(/^([^#=]+)=(.*)$/);
35148
35439
  if (match) env[match[1].trim()] = match[2].trim();
35149
35440
  }
35150
35441
  }
35151
- const cwdDb = join36(process.cwd(), "cc-claw.db");
35152
- if (existsSync57(cwdDb) && !existsSync57(DB_PATH)) {
35442
+ const cwdDb = join37(process.cwd(), "cc-claw.db");
35443
+ if (existsSync58(cwdDb) && !existsSync58(DB_PATH)) {
35153
35444
  const { size } = statSync12(cwdDb);
35154
35445
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
35155
35446
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
@@ -35289,14 +35580,44 @@ async function setup() {
35289
35580
  }
35290
35581
  header(4, TOTAL_STEPS, "Optional Features");
35291
35582
  console.log(" These are optional \u2014 you can enable them later by editing .env\n");
35292
- if (await confirm("Enable voice messages? (requires a free Groq API key)")) {
35583
+ if (await confirm("Enable voice message transcription (speech-to-text)?")) {
35293
35584
  console.log("");
35294
- console.log(dim(" Get a free Groq API key at: https://console.groq.com/keys\n"));
35295
- const groqKey = await requiredInput("Groq API key", env.GROQ_API_KEY);
35296
- env.GROQ_API_KEY = groqKey;
35297
- console.log(green(" Voice transcription (STT) enabled!"));
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)");
35298
35589
  console.log("");
35299
- console.log(dim(" Choose a voice reply provider:"));
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");
35592
+ console.log("");
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
+ }
35619
+ console.log("");
35620
+ console.log(dim(" Choose a voice reply provider (text-to-speech):"));
35300
35621
  console.log(" 1. ElevenLabs (high-quality, requires API key)");
35301
35622
  console.log(" 2. Grok / xAI (high-quality, requires API key)");
35302
35623
  console.log(" 3. macOS (free, uses built-in voices \u2014 Samantha, Albert)");
@@ -35313,10 +35634,10 @@ async function setup() {
35313
35634
  const xaiKey = await requiredInput("xAI API key", env.XAI_API_KEY);
35314
35635
  env.XAI_API_KEY = xaiKey;
35315
35636
  console.log(green(" Voice replies via Grok enabled!"));
35316
- 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)."));
35317
35638
  } else if (ttsChoice === "3") {
35318
35639
  console.log(green(" Voice replies via macOS enabled!"));
35319
- 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)."));
35320
35641
  } else {
35321
35642
  console.log(dim(" Voice replies will use macOS text-to-speech as fallback."));
35322
35643
  }
@@ -35350,14 +35671,14 @@ async function setup() {
35350
35671
  `ANTHROPIC_VERTEX_PROJECT_ID=${env.ANTHROPIC_VERTEX_PROJECT_ID ?? ""}`
35351
35672
  );
35352
35673
  }
35353
- if (env.GROQ_API_KEY) {
35354
- envLines.push("", "# Voice", `GROQ_API_KEY=${env.GROQ_API_KEY}`);
35355
- if (env.ELEVENLABS_API_KEY) {
35356
- envLines.push(`ELEVENLABS_API_KEY=${env.ELEVENLABS_API_KEY}`);
35357
- }
35358
- if (env.XAI_API_KEY) {
35359
- envLines.push(`XAI_API_KEY=${env.XAI_API_KEY}`);
35360
- }
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}`);
35361
35682
  }
35362
35683
  if (env.DASHBOARD_ENABLED) {
35363
35684
  envLines.push("", "# Dashboard", `DASHBOARD_ENABLED=${env.DASHBOARD_ENABLED}`);
@@ -35401,7 +35722,9 @@ async function setup() {
35401
35722
  console.log(" " + green("[ok]") + ` Telegram bot: @${env.TELEGRAM_BOT_TOKEN ? "configured" : "missing"}`);
35402
35723
  console.log(" " + green("[ok]") + ` Chat ID: ${env.ALLOWED_CHAT_ID ?? "not set (anyone can message)"}`);
35403
35724
  console.log(" " + green("[ok]") + ` Vertex AI: ${env.ANTHROPIC_VERTEX_PROJECT_ID ?? "not configured"}`);
35404
- 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}`);
35405
35728
  console.log(" " + (env.ELEVENLABS_API_KEY ? green("[ok]") : dim("[--]")) + " Voice replies");
35406
35729
  console.log(" " + (env.DASHBOARD_ENABLED ? green("[ok]") : dim("[--]")) + " Web dashboard");
35407
35730
  console.log(" " + (env.GEMINI_API_KEY ? green("[ok]") : dim("[--]")) + " Video analysis");