cc-claw 0.20.21 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +859 -509
  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.1" : (() => {
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 = {
@@ -11159,6 +11179,10 @@ var init_scheduler = __esm({
11159
11179
  updates.push("delivery_mode = ?");
11160
11180
  values.push(body.deliveryMode);
11161
11181
  }
11182
+ if (body.credentialSlotId !== void 0) {
11183
+ updates.push("credential_slot_id = ?");
11184
+ values.push(body.credentialSlotId ?? null);
11185
+ }
11162
11186
  if (updates.length === 0) {
11163
11187
  return jsonResponse(res, { error: "No fields to update" }, 400);
11164
11188
  }
@@ -11562,7 +11586,7 @@ __export(analyze_exports, {
11562
11586
  });
11563
11587
  import { spawn as spawn4 } from "child_process";
11564
11588
  import { createInterface as createInterface3 } from "readline";
11565
- import { readFileSync as readFileSync6, existsSync as existsSync12, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
11589
+ import { readFileSync as readFileSync7, existsSync as existsSync12, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
11566
11590
  import { join as join12 } from "path";
11567
11591
  import { homedir as homedir4 } from "os";
11568
11592
  function applySignalDecay(confidence, createdAt) {
@@ -11584,7 +11608,7 @@ function discoverReflectionTargets() {
11584
11608
  if (!existsSync12(skillFile)) continue;
11585
11609
  let desc = "skill";
11586
11610
  try {
11587
- const content = readFileSync6(skillFile, "utf-8");
11611
+ const content = readFileSync7(skillFile, "utf-8");
11588
11612
  const descMatch = content.match(/description:\s*["']?([^"'\n]+)/);
11589
11613
  if (descMatch) desc = descMatch[1].trim().slice(0, 80);
11590
11614
  } catch {
@@ -11793,7 +11817,7 @@ function resolveReflectionAdapter(chatId) {
11793
11817
  }
11794
11818
  function readIdentityFile(filename) {
11795
11819
  try {
11796
- return readFileSync6(join12(IDENTITY_PATH, filename), "utf-8");
11820
+ return readFileSync7(join12(IDENTITY_PATH, filename), "utf-8");
11797
11821
  } catch {
11798
11822
  return "";
11799
11823
  }
@@ -11952,7 +11976,7 @@ async function runAnalysisImpl(chatId, opts) {
11952
11976
  try {
11953
11977
  const fullPath = join12(ccClawHome, target.path);
11954
11978
  if (existsSync12(fullPath)) {
11955
- const content = readFileSync6(fullPath, "utf-8");
11979
+ const content = readFileSync7(fullPath, "utf-8");
11956
11980
  if (totalSkillChars + content.length > SKILL_CONTENT_CAP) break;
11957
11981
  skillContents.push({ path: target.path, content });
11958
11982
  totalSkillChars += content.length;
@@ -12332,7 +12356,7 @@ __export(apply_exports, {
12332
12356
  isTargetAllowed: () => isTargetAllowed,
12333
12357
  rollbackInsight: () => rollbackInsight
12334
12358
  });
12335
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync8, unlinkSync as unlinkSync5 } from "fs";
12359
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync8, unlinkSync as unlinkSync5 } from "fs";
12336
12360
  import { join as join13, dirname as dirname3 } from "path";
12337
12361
  function isTargetAllowed(relativePath) {
12338
12362
  if (relativePath.includes("..")) return false;
@@ -12416,7 +12440,7 @@ async function applyInsight(insightId) {
12416
12440
  const absolutePath = join13(CC_CLAW_HOME, insight.targetFile);
12417
12441
  if (insight.proposedAction === "append" && insight.targetFile === "identity/SOUL.md") {
12418
12442
  if (existsSync13(absolutePath)) {
12419
- const currentContent = readFileSync7(absolutePath, "utf-8");
12443
+ const currentContent = readFileSync8(absolutePath, "utf-8");
12420
12444
  const lineCount = currentContent.split("\n").length;
12421
12445
  if (lineCount >= SOUL_LINE_CAP) {
12422
12446
  return {
@@ -12438,7 +12462,7 @@ async function applyInsight(insightId) {
12438
12462
  }
12439
12463
  let original = "";
12440
12464
  if (existsSync13(absolutePath)) {
12441
- original = readFileSync7(absolutePath, "utf-8");
12465
+ original = readFileSync8(absolutePath, "utf-8");
12442
12466
  } else if (insight.proposedAction !== "create") {
12443
12467
  return { success: false, message: `Target file "${insight.targetFile}" does not exist` };
12444
12468
  }
@@ -12578,7 +12602,7 @@ function computeLineDrift(baseline, absolutePath) {
12578
12602
  let current = "";
12579
12603
  try {
12580
12604
  if (existsSync13(absolutePath)) {
12581
- current = readFileSync7(absolutePath, "utf-8");
12605
+ current = readFileSync8(absolutePath, "utf-8");
12582
12606
  }
12583
12607
  } catch {
12584
12608
  return 1;
@@ -12677,12 +12701,12 @@ var init_evolve = __esm({
12677
12701
  const body = JSON.parse(await readBody(req));
12678
12702
  const { setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
12679
12703
  const { existsSync: fileExists, readFileSync: fileRead } = await import("fs");
12680
- const { join: join37 } = await import("path");
12704
+ const { join: join38 } = await import("path");
12681
12705
  const { CC_CLAW_HOME: home } = await Promise.resolve().then(() => (init_paths(), paths_exports));
12682
12706
  const chatId = resolveChatId(body);
12683
12707
  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");
12708
+ const soulPath = join38(home, "identity/SOUL.md");
12709
+ const userPath = join38(home, "identity/USER.md");
12686
12710
  const soul = fileExists(soulPath) ? fileRead(soulPath, "utf-8") : "";
12687
12711
  const user = fileExists(userPath) ? fileRead(userPath, "utf-8") : "";
12688
12712
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
@@ -13658,6 +13682,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
13658
13682
  let sawToolEvents = false;
13659
13683
  let sawResultEvent = false;
13660
13684
  let toolTurnCount = 0;
13685
+ let loopKillReason;
13661
13686
  const loopDetector = new ToolLoopDetector();
13662
13687
  const t0 = Date.now();
13663
13688
  const elapsed = () => `${((Date.now() - t0) / 1e3).toFixed(1)}s`;
@@ -13778,6 +13803,7 @@ function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeou
13778
13803
  const check = loopDetector.addCall(ev.toolName, ev.toolInput ?? {});
13779
13804
  if (check.isLoop) {
13780
13805
  warn(`[agent] Loop detected for ${adapter.id}: ${check.reason} \u2014 stopping`);
13806
+ loopKillReason = check.reason;
13781
13807
  killProcessGroup(proc, "SIGTERM");
13782
13808
  }
13783
13809
  }
@@ -13902,8 +13928,12 @@ Partial output: ${accumulatedText.slice(-500)}`;
13902
13928
  return;
13903
13929
  }
13904
13930
  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)}` : ""}`));
13931
+ if (code === 143 && loopKillReason) {
13932
+ reject(new Error(`Stopped: agent was repeating the same action (${loopKillReason}). Try rephrasing your request.`));
13933
+ } else {
13934
+ const stderr = Buffer.concat(stderrChunks).toString().trim();
13935
+ reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
13936
+ }
13907
13937
  return;
13908
13938
  }
13909
13939
  const cleanedResult = stripThinkingContent(resultText || accumulatedText);
@@ -14454,7 +14484,7 @@ var init_agent = __esm({
14454
14484
  chatLocks = /* @__PURE__ */ new Map();
14455
14485
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
14456
14486
  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);
14487
+ CONTENT_SILENCE_TIMEOUT_MS = parseInt(process.env.CONTENT_SILENCE_TIMEOUT_MS ?? "180000", 10);
14458
14488
  CONTENT_SILENCE_TIMEOUT_ERROR = "CONTENT_SILENCE_TIMEOUT";
14459
14489
  FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
14460
14490
  FREE_SLOTS_EXHAUSTED = "FREE_SLOTS_EXHAUSTED";
@@ -14976,15 +15006,17 @@ var init_telegram_throttle = __esm({
14976
15006
  * Used for cosmetic calls (typing indicators, reactions) that should count toward
14977
15007
  * rate limits but must never queue up or amplify 429 spirals.
14978
15008
  */
14979
- async tryBestEffort(chatId, label2, fn) {
15009
+ async tryBestEffort(chatId, label2, fn, opts) {
14980
15010
  if (this.isPaused()) return void 0;
14981
15011
  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;
15012
+ if (!opts?.skipRecord) {
15013
+ const lastChat = this.lastSendPerChat.get(chatId) ?? 0;
15014
+ if (Date.now() - lastChat < PER_CHAT_INTERVAL_MS) return void 0;
15015
+ if (Date.now() - this.lastGlobalSend < GLOBAL_INTERVAL_MS) return void 0;
15016
+ }
14985
15017
  try {
14986
15018
  const result = await fn();
14987
- this.recordSend(chatId);
15019
+ if (!opts?.skipRecord) this.recordSend(chatId);
14988
15020
  return result;
14989
15021
  } catch (err) {
14990
15022
  if (is429(err)) {
@@ -15126,11 +15158,11 @@ var init_telegram_throttle = __esm({
15126
15158
  });
15127
15159
 
15128
15160
  // src/health/checks.ts
15129
- import { existsSync as existsSync15, statSync as statSync6, readFileSync as readFileSync8 } from "fs";
15161
+ import { existsSync as existsSync15, statSync as statSync6, readFileSync as readFileSync9 } from "fs";
15130
15162
  import { execFileSync, execSync as execSync3 } from "child_process";
15131
15163
  function getRecentErrors() {
15132
15164
  if (!existsSync15(ERROR_LOG_PATH)) return null;
15133
- const logContent = readFileSync8(ERROR_LOG_PATH, "utf-8");
15165
+ const logContent = readFileSync9(ERROR_LOG_PATH, "utf-8");
15134
15166
  const allLines = logContent.split("\n").filter(Boolean).slice(-500);
15135
15167
  const last24h = Date.now() - 864e5;
15136
15168
  const lines = allLines.filter((line) => {
@@ -15359,7 +15391,7 @@ __export(heartbeat_exports, {
15359
15391
  stopHeartbeatForChat: () => stopHeartbeatForChat,
15360
15392
  updateHeartbeatConfig: () => updateHeartbeatConfig
15361
15393
  });
15362
- import { readFileSync as readFileSync9, existsSync as existsSync16 } from "fs";
15394
+ import { readFileSync as readFileSync10, existsSync as existsSync16 } from "fs";
15363
15395
  import { join as join15 } from "path";
15364
15396
  function findHeartbeatJob() {
15365
15397
  try {
@@ -15493,7 +15525,7 @@ ${watchLines.join("\n")}`);
15493
15525
  }
15494
15526
  if (existsSync16(HEARTBEAT_MD_PATH)) {
15495
15527
  try {
15496
- const custom = readFileSync9(HEARTBEAT_MD_PATH, "utf-8").trim();
15528
+ const custom = readFileSync10(HEARTBEAT_MD_PATH, "utf-8").trim();
15497
15529
  if (custom) {
15498
15530
  sections.push(`[Custom checks from HEARTBEAT.md]
15499
15531
  ${custom}`);
@@ -15562,7 +15594,7 @@ var init_heartbeat2 = __esm({
15562
15594
  });
15563
15595
 
15564
15596
  // src/bootstrap/profile.ts
15565
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync17 } from "fs";
15597
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync17 } from "fs";
15566
15598
  import { join as join16 } from "path";
15567
15599
  function hasActiveProfile(chatId) {
15568
15600
  return activeProfiles.has(chatId);
@@ -15693,7 +15725,7 @@ function extractUserUpdates(text) {
15693
15725
  }
15694
15726
  function appendToUserProfile(key, value) {
15695
15727
  if (!existsSync17(USER_PATH2)) return;
15696
- const content = readFileSync10(USER_PATH2, "utf-8");
15728
+ const content = readFileSync11(USER_PATH2, "utf-8");
15697
15729
  const line = `- **${key}**: ${value}`;
15698
15730
  if (content.includes(line)) return;
15699
15731
  const updated = content.trimEnd() + `
@@ -15802,8 +15834,9 @@ async function classifyWithOllama(text) {
15802
15834
  (a, b) => (a.sizeBytes ?? Infinity) - (b.sizeBytes ?? Infinity)
15803
15835
  );
15804
15836
  const model2 = sorted[0].name;
15837
+ const baseUrl = ollamaStore.getBaseUrl(onlineServer);
15805
15838
  const result = await ollamaClient.chat(
15806
- onlineServer.baseUrl,
15839
+ baseUrl,
15807
15840
  model2,
15808
15841
  [{ role: "user", content: LLM_CLASSIFY_PROMPT + text.slice(0, 500) }],
15809
15842
  { timeoutMs: LLM_CLASSIFY_TIMEOUT_MS, maxTokens: 5, temperature: 0 }
@@ -15828,7 +15861,7 @@ async function classifyWithSummarizerCli(text) {
15828
15861
  const model2 = modelName ?? adapter.summarizerModel;
15829
15862
  const { spawn: spawn8 } = await import("child_process");
15830
15863
  const { resolveExecutable: resolveExecutable4 } = await Promise.resolve().then(() => (init_resolve_executable(), resolve_executable_exports));
15831
- const exe = resolveExecutable4(adapter.id);
15864
+ const exe = resolveExecutable4({ envVar: `${adapter.id.toUpperCase()}_EXECUTABLE`, binaryName: adapter.id, candidates: [] });
15832
15865
  if (!exe) return null;
15833
15866
  return new Promise((resolve) => {
15834
15867
  const timeout = setTimeout(() => {
@@ -17701,7 +17734,9 @@ var init_gate = __esm({
17701
17734
  // src/voice/stt.ts
17702
17735
  import crypto from "crypto";
17703
17736
  import { execFile as execFile2, execFileSync as execFileSync2 } from "child_process";
17704
- import { readFile as readFile2, unlink as unlink2 } from "fs/promises";
17737
+ import { readFile as readFile2, unlink as unlink2, mkdir as mkdir2, writeFile } from "fs/promises";
17738
+ import { existsSync as existsSync19 } from "fs";
17739
+ import { join as join18 } from "path";
17705
17740
  import { promisify as promisify2 } from "util";
17706
17741
  function ensureFfmpeg() {
17707
17742
  if (ffmpegAvailable === true) return;
@@ -17746,11 +17781,120 @@ function setVoiceProvider(chatId, provider, voiceId) {
17746
17781
  ON CONFLICT(chat_id) DO UPDATE SET provider = ?, voice_id = ?, enabled = 1
17747
17782
  `).run(chatId, provider, voiceId, provider, voiceId);
17748
17783
  }
17749
- async function transcribeAudio(audioBuffer, mimeType = "audio/ogg") {
17784
+ function getSttProvider(chatId) {
17785
+ const db3 = getDb();
17786
+ const row = db3.prepare("SELECT stt_provider FROM chat_voice WHERE chat_id = ?").get(chatId);
17787
+ return row?.stt_provider ?? "groq";
17788
+ }
17789
+ function setSttProvider(chatId, provider) {
17790
+ const db3 = getDb();
17791
+ db3.prepare(`
17792
+ INSERT INTO chat_voice (chat_id, enabled, stt_provider) VALUES (?, 0, ?)
17793
+ ON CONFLICT(chat_id) DO UPDATE SET stt_provider = ?
17794
+ `).run(chatId, provider, provider);
17795
+ }
17796
+ function getSttModel(chatId) {
17797
+ const db3 = getDb();
17798
+ const row = db3.prepare("SELECT stt_model FROM chat_voice WHERE chat_id = ?").get(chatId);
17799
+ const model2 = row?.stt_model ?? "small.en";
17800
+ return model2 in LOCAL_WHISPER_MODELS ? model2 : "small.en";
17801
+ }
17802
+ function setSttModel(chatId, model2) {
17803
+ const db3 = getDb();
17804
+ db3.prepare(`
17805
+ INSERT INTO chat_voice (chat_id, enabled, stt_model) VALUES (?, 0, ?)
17806
+ ON CONFLICT(chat_id) DO UPDATE SET stt_model = ?
17807
+ `).run(chatId, model2, model2);
17808
+ }
17809
+ function isWhisperCliAvailable() {
17810
+ if (whisperCliAvailableCache !== null) return whisperCliAvailableCache;
17811
+ try {
17812
+ execFileSync2("whisper-cli", ["--help"], { stdio: "ignore" });
17813
+ whisperCliAvailableCache = true;
17814
+ } catch {
17815
+ try {
17816
+ execFileSync2("whisper", ["--help"], { stdio: "ignore" });
17817
+ whisperCliAvailableCache = true;
17818
+ } catch {
17819
+ whisperCliAvailableCache = false;
17820
+ }
17821
+ }
17822
+ return whisperCliAvailableCache;
17823
+ }
17824
+ function getWhisperBin() {
17825
+ try {
17826
+ execFileSync2("whisper-cli", ["--help"], { stdio: "ignore" });
17827
+ return "whisper-cli";
17828
+ } catch {
17829
+ }
17830
+ try {
17831
+ execFileSync2("whisper", ["--help"], { stdio: "ignore" });
17832
+ return "whisper";
17833
+ } catch {
17834
+ }
17835
+ return null;
17836
+ }
17837
+ function whisperModelPath(model2) {
17838
+ return join18(WHISPER_MODELS_PATH, `ggml-${model2}.bin`);
17839
+ }
17840
+ function isWhisperModelDownloaded(model2) {
17841
+ return existsSync19(whisperModelPath(model2));
17842
+ }
17843
+ async function downloadWhisperModel(model2, onProgress) {
17844
+ await mkdir2(WHISPER_MODELS_PATH, { recursive: true });
17845
+ const dest = whisperModelPath(model2);
17846
+ const url = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model2}.bin`;
17847
+ const info = LOCAL_WHISPER_MODELS[model2];
17848
+ onProgress?.(`\u2B07\uFE0F Downloading Whisper model ${model2} (${info.size})...`);
17849
+ log(`[stt] Downloading model ${model2} from ${url}`);
17850
+ const response = await fetch(url);
17851
+ if (!response.ok) throw new Error(`Failed to download model: ${response.status} ${response.statusText}`);
17852
+ const arrayBuffer = await response.arrayBuffer();
17853
+ await writeFile(dest, Buffer.from(arrayBuffer));
17854
+ log(`[stt] Model ${model2} downloaded to ${dest}`);
17855
+ }
17856
+ async function transcribeWithLocalWhisper(audioBuffer, model2, onProgress) {
17857
+ ensureFfmpeg();
17858
+ const bin = getWhisperBin();
17859
+ 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");
17860
+ if (!isWhisperModelDownloaded(model2)) {
17861
+ await downloadWhisperModel(model2, onProgress);
17862
+ }
17863
+ const id = crypto.randomUUID();
17864
+ const tmpOgg = `/tmp/cc-claw-stt-${id}.ogg`;
17865
+ const tmpWav = `/tmp/cc-claw-stt-${id}.wav`;
17866
+ try {
17867
+ await writeFile(tmpOgg, audioBuffer);
17868
+ await execFileAsync2("ffmpeg", ["-y", "-i", tmpOgg, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", tmpWav]);
17869
+ const modelFile = whisperModelPath(model2);
17870
+ const result = await execFileAsync2(bin, ["-m", modelFile, "-f", tmpWav, "-nt", "--output-txt", "-of", `/tmp/cc-claw-stt-${id}`]);
17871
+ const txtFile = `/tmp/cc-claw-stt-${id}.txt`;
17872
+ let transcript = "";
17873
+ if (existsSync19(txtFile)) {
17874
+ transcript = (await readFile2(txtFile, "utf-8")).trim();
17875
+ unlink2(txtFile).catch(() => {
17876
+ });
17877
+ } else {
17878
+ transcript = (result.stdout ?? "").trim();
17879
+ }
17880
+ return transcript;
17881
+ } finally {
17882
+ unlink2(tmpOgg).catch(() => {
17883
+ });
17884
+ unlink2(tmpWav).catch(() => {
17885
+ });
17886
+ }
17887
+ }
17888
+ async function transcribeAudio(audioBuffer, chatId, onProgress) {
17889
+ const provider = chatId ? getSttProvider(chatId) : "groq";
17890
+ if (provider === "local-whisper") {
17891
+ const model2 = chatId ? getSttModel(chatId) : "small.en";
17892
+ return await transcribeWithLocalWhisper(audioBuffer, model2, onProgress);
17893
+ }
17750
17894
  const GROQ_API_KEY = process.env.GROQ_API_KEY;
17751
17895
  if (!GROQ_API_KEY) return null;
17752
17896
  const formData = new FormData();
17753
- formData.append("file", new Blob([new Uint8Array(audioBuffer)], { type: mimeType }), "voice.ogg");
17897
+ formData.append("file", new Blob([new Uint8Array(audioBuffer)], { type: "audio/ogg" }), "voice.ogg");
17754
17898
  formData.append("model", "whisper-large-v3");
17755
17899
  formData.append("response_format", "text");
17756
17900
  const response = await fetch("https://api.groq.com/openai/v1/audio/transcriptions", {
@@ -17851,8 +17995,8 @@ async function mp3ToOgg(mp3Buffer) {
17851
17995
  const id = crypto.randomUUID();
17852
17996
  const tmpMp3 = `/tmp/cc-claw-tts-${id}.mp3`;
17853
17997
  const tmpOgg = `/tmp/cc-claw-tts-${id}.ogg`;
17854
- const { writeFile: writeFile6 } = await import("fs/promises");
17855
- await writeFile6(tmpMp3, mp3Buffer);
17998
+ const { writeFile: writeFile7 } = await import("fs/promises");
17999
+ await writeFile7(tmpMp3, mp3Buffer);
17856
18000
  await execFileAsync2("ffmpeg", ["-y", "-i", tmpMp3, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
17857
18001
  const oggBuffer = await readFile2(tmpOgg);
17858
18002
  unlink2(tmpMp3).catch((err) => {
@@ -17879,14 +18023,24 @@ async function macOsTts(text, voice2 = "Samantha") {
17879
18023
  });
17880
18024
  return oggBuffer;
17881
18025
  }
17882
- var execFileAsync2, ffmpegAvailable, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES;
18026
+ var execFileAsync2, ffmpegAvailable, LOCAL_WHISPER_MODELS, ELEVENLABS_VOICES, GROK_VOICES, MACOS_VOICES, whisperCliAvailableCache;
17883
18027
  var init_stt = __esm({
17884
18028
  "src/voice/stt.ts"() {
17885
18029
  "use strict";
17886
18030
  init_log();
17887
18031
  init_store5();
18032
+ init_paths();
17888
18033
  execFileAsync2 = promisify2(execFile2);
17889
18034
  ffmpegAvailable = null;
18035
+ LOCAL_WHISPER_MODELS = {
18036
+ "tiny.en": { size: "75MB", lang: "English only", speed: "~0.3s", quality: "Basic" },
18037
+ "base.en": { size: "150MB", lang: "English only", speed: "~0.8s", quality: "Good" },
18038
+ "small.en": { size: "500MB", lang: "English only", speed: "~1.5s", quality: "Very good \u2B50" },
18039
+ "small": { size: "500MB", lang: "Multilingual", speed: "~2s", quality: "Very good" },
18040
+ "medium.en": { size: "1.5GB", lang: "English only", speed: "~5s", quality: "Excellent" },
18041
+ "medium": { size: "1.5GB", lang: "Multilingual", speed: "~6s", quality: "Excellent" },
18042
+ "large-v3-turbo": { size: "1.5GB", lang: "Multilingual", speed: "~4s", quality: "Best" }
18043
+ };
17890
18044
  ELEVENLABS_VOICES = {
17891
18045
  "21m00Tcm4TlvDq8ikWAM": { name: "Rachel", gender: "F" },
17892
18046
  "EXAVITQu4vr4xnSDxMaL": { name: "Sarah", gender: "F" },
@@ -17902,13 +18056,14 @@ var init_stt = __esm({
17902
18056
  "Samantha": { name: "Samantha", gender: "F" },
17903
18057
  "Albert": { name: "Albert", gender: "M" }
17904
18058
  };
18059
+ whisperCliAvailableCache = null;
17905
18060
  }
17906
18061
  });
17907
18062
 
17908
18063
  // 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";
18064
+ import { mkdirSync as mkdirSync9, existsSync as existsSync20, unlink as unlink3, readdir as readdir2, stat as stat2 } from "fs";
18065
+ import { writeFile as writeFile2 } from "fs/promises";
18066
+ import { join as join19 } from "path";
17912
18067
  async function generateImage(prompt) {
17913
18068
  const apiKey = process.env.GEMINI_API_KEY;
17914
18069
  if (!apiKey) {
@@ -17955,14 +18110,14 @@ async function generateImage(prompt) {
17955
18110
  if (!imageData) {
17956
18111
  throw new Error(textResponse ?? "Gemini did not generate an image. The prompt may have been filtered.");
17957
18112
  }
17958
- if (!existsSync19(IMAGE_OUTPUT_DIR)) {
18113
+ if (!existsSync20(IMAGE_OUTPUT_DIR)) {
17959
18114
  mkdirSync9(IMAGE_OUTPUT_DIR, { recursive: true });
17960
18115
  }
17961
18116
  const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
17962
18117
  const filename = `img_${Date.now()}.${ext}`;
17963
- const filePath = join18(IMAGE_OUTPUT_DIR, filename);
18118
+ const filePath = join19(IMAGE_OUTPUT_DIR, filename);
17964
18119
  const buffer = Buffer.from(imageData, "base64");
17965
- await writeFile(filePath, buffer);
18120
+ await writeFile2(filePath, buffer);
17966
18121
  log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
17967
18122
  return { filePath, text: textResponse, mimeType };
17968
18123
  }
@@ -17978,7 +18133,7 @@ function cleanupGeneratedImage(filePath) {
17978
18133
  function pruneImageCache() {
17979
18134
  readdir2(IMAGE_OUTPUT_DIR, (err, files) => {
17980
18135
  if (err || !files) return;
17981
- const imageFiles = files.filter((f) => /\.(png|jpg)$/.test(f)).map((f) => join18(IMAGE_OUTPUT_DIR, f));
18136
+ const imageFiles = files.filter((f) => /\.(png|jpg)$/.test(f)).map((f) => join19(IMAGE_OUTPUT_DIR, f));
17982
18137
  if (imageFiles.length === 0) return;
17983
18138
  const now = Date.now();
17984
18139
  let statsPending = imageFiles.length;
@@ -18010,8 +18165,8 @@ var init_image_gen = __esm({
18010
18165
  MAX_GENERATED_IMAGES = 20;
18011
18166
  IMAGE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
18012
18167
  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"),
18168
+ IMAGE_OUTPUT_DIR = join19(
18169
+ process.env.CC_CLAW_HOME ?? join19(process.env.HOME ?? "/tmp", ".cc-claw"),
18015
18170
  "data",
18016
18171
  "images"
18017
18172
  );
@@ -18449,22 +18604,22 @@ var init_video = __esm({
18449
18604
  });
18450
18605
 
18451
18606
  // 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";
18607
+ import { join as join20 } from "path";
18608
+ import { mkdir as mkdir3, writeFile as writeFile3, readdir as readdir3, stat as stat3, unlink as unlink4 } from "fs/promises";
18454
18609
  function getMediaRetentionMs() {
18455
18610
  const hours = parseInt(process.env.MEDIA_RETENTION_HOURS ?? "24", 10);
18456
18611
  return (isNaN(hours) || hours < 1 ? 24 : hours) * 60 * 60 * 1e3;
18457
18612
  }
18458
18613
  async function saveMedia(buffer, prefix, ext) {
18459
- await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
18614
+ await mkdir3(MEDIA_INCOMING_PATH, { recursive: true });
18460
18615
  const filename = `${prefix}-${Date.now()}.${ext}`;
18461
- const fullPath = join19(MEDIA_INCOMING_PATH, filename);
18462
- await writeFile2(fullPath, buffer);
18616
+ const fullPath = join20(MEDIA_INCOMING_PATH, filename);
18617
+ await writeFile3(fullPath, buffer);
18463
18618
  return fullPath;
18464
18619
  }
18465
18620
  async function cleanupOldMedia() {
18466
18621
  try {
18467
- await mkdir2(MEDIA_INCOMING_PATH, { recursive: true });
18622
+ await mkdir3(MEDIA_INCOMING_PATH, { recursive: true });
18468
18623
  const retentionMs = getMediaRetentionMs();
18469
18624
  const retentionHours = Math.round(retentionMs / (60 * 60 * 1e3));
18470
18625
  const files = await readdir3(MEDIA_INCOMING_PATH);
@@ -18472,7 +18627,7 @@ async function cleanupOldMedia() {
18472
18627
  let removed = 0;
18473
18628
  for (const file of files) {
18474
18629
  try {
18475
- const filePath = join19(MEDIA_INCOMING_PATH, file);
18630
+ const filePath = join20(MEDIA_INCOMING_PATH, file);
18476
18631
  const s = await stat3(filePath);
18477
18632
  if (now - s.mtimeMs > retentionMs) {
18478
18633
  await unlink4(filePath);
@@ -18494,9 +18649,11 @@ async function handleVoice(msg, channel) {
18494
18649
  return;
18495
18650
  }
18496
18651
  const audioBuffer = await channel.downloadFile(fileName);
18497
- const transcript = await transcribeAudio(audioBuffer);
18652
+ const transcript = await transcribeAudio(audioBuffer, chatId, async (msg2) => {
18653
+ await channel.sendText(chatId, msg2, { parseMode: "plain" });
18654
+ });
18498
18655
  if (!transcript) {
18499
- await channel.sendText(chatId, "Couldn't transcribe the voice message.", { parseMode: "plain" });
18656
+ await channel.sendText(chatId, "Couldn't transcribe the voice message. Make sure a transcription provider is configured via /voice.", { parseMode: "plain" });
18500
18657
  return;
18501
18658
  }
18502
18659
  const vBackendId = getBackend(chatId) ?? "claude";
@@ -18746,7 +18903,7 @@ var init_media = __esm({
18746
18903
  init_helpers();
18747
18904
  init_response();
18748
18905
  init_live_status();
18749
- MEDIA_INCOMING_PATH = join19(MEDIA_PATH, "incoming");
18906
+ MEDIA_INCOMING_PATH = join20(MEDIA_PATH, "incoming");
18750
18907
  }
18751
18908
  });
18752
18909
 
@@ -19058,9 +19215,9 @@ var install_exports = {};
19058
19215
  __export(install_exports, {
19059
19216
  installSkillFromGitHub: () => installSkillFromGitHub
19060
19217
  });
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";
19218
+ import { mkdir as mkdir4, readdir as readdir4, readFile as readFile4, cp } from "fs/promises";
19219
+ import { existsSync as existsSync21 } from "fs";
19220
+ import { join as join21, basename as basename2 } from "path";
19064
19221
  import { execSync as execSync4 } from "child_process";
19065
19222
  async function installSkillFromGitHub(urlOrShorthand) {
19066
19223
  let repoUrl;
@@ -19071,36 +19228,36 @@ async function installSkillFromGitHub(urlOrShorthand) {
19071
19228
  }
19072
19229
  repoUrl = parsed.cloneUrl;
19073
19230
  subPath = parsed.subPath;
19074
- const tmpDir = join20("/tmp", `cc-claw-skill-${Date.now()}`);
19231
+ const tmpDir = join21("/tmp", `cc-claw-skill-${Date.now()}`);
19075
19232
  try {
19076
19233
  log(`[skill-install] Cloning ${repoUrl} to ${tmpDir}`);
19077
19234
  execSync4(`git clone --depth 1 ${repoUrl} ${tmpDir}`, {
19078
19235
  stdio: "pipe",
19079
19236
  timeout: 3e4
19080
19237
  });
19081
- if (!existsSync20(join20(tmpDir, ".git"))) {
19238
+ if (!existsSync21(join21(tmpDir, ".git"))) {
19082
19239
  return { success: false, error: "Git clone failed: no .git directory produced" };
19083
19240
  }
19084
- const searchRoot = subPath ? join20(tmpDir, subPath) : tmpDir;
19241
+ const searchRoot = subPath ? join21(tmpDir, subPath) : tmpDir;
19085
19242
  const skillDir = await findSkillDir(searchRoot);
19086
19243
  if (!skillDir) {
19087
19244
  return { success: false, error: "No SKILL.md found in the repository." };
19088
19245
  }
19089
19246
  const skillFolderName = basename2(skillDir);
19090
- const destDir = join20(SKILLS_PATH, skillFolderName);
19091
- if (existsSync20(destDir)) {
19247
+ const destDir = join21(SKILLS_PATH, skillFolderName);
19248
+ if (existsSync21(destDir)) {
19092
19249
  log(`[skill-install] Overwriting existing skill at ${destDir}`);
19093
19250
  }
19094
- await mkdir3(destDir, { recursive: true });
19251
+ await mkdir4(destDir, { recursive: true });
19095
19252
  await cp(skillDir, destDir, { recursive: true });
19096
19253
  let skillName = skillFolderName;
19097
19254
  try {
19098
- const content = await readFile4(join20(destDir, "SKILL.md"), "utf-8");
19255
+ const content = await readFile4(join21(destDir, "SKILL.md"), "utf-8");
19099
19256
  const nameMatch = content.match(/^name:\s*(.+)$/m);
19100
19257
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
19101
19258
  } catch {
19102
19259
  try {
19103
- const content = await readFile4(join20(destDir, "skill.md"), "utf-8");
19260
+ const content = await readFile4(join21(destDir, "skill.md"), "utf-8");
19104
19261
  const nameMatch = content.match(/^name:\s*(.+)$/m);
19105
19262
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
19106
19263
  } catch {
@@ -19135,15 +19292,15 @@ function parseGitHubUrl(input) {
19135
19292
  async function findSkillDir(root) {
19136
19293
  const candidates = ["SKILL.md", "skill.md"];
19137
19294
  for (const c of candidates) {
19138
- if (existsSync20(join20(root, c))) return root;
19295
+ if (existsSync21(join21(root, c))) return root;
19139
19296
  }
19140
19297
  try {
19141
19298
  const entries = await readdir4(root, { withFileTypes: true });
19142
19299
  for (const entry of entries) {
19143
19300
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19144
19301
  for (const c of candidates) {
19145
- if (existsSync20(join20(root, entry.name, c))) {
19146
- return join20(root, entry.name);
19302
+ if (existsSync21(join21(root, entry.name, c))) {
19303
+ return join21(root, entry.name);
19147
19304
  }
19148
19305
  }
19149
19306
  }
@@ -19155,15 +19312,15 @@ async function findSkillDir(root) {
19155
19312
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
19156
19313
  let subEntries;
19157
19314
  try {
19158
- subEntries = await readdir4(join20(root, entry.name), { withFileTypes: true });
19315
+ subEntries = await readdir4(join21(root, entry.name), { withFileTypes: true });
19159
19316
  } catch {
19160
19317
  continue;
19161
19318
  }
19162
19319
  for (const sub of subEntries) {
19163
19320
  if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
19164
19321
  for (const c of candidates) {
19165
- if (existsSync20(join20(root, entry.name, sub.name, c))) {
19166
- return join20(root, entry.name, sub.name);
19322
+ if (existsSync21(join21(root, entry.name, sub.name, c))) {
19323
+ return join21(root, entry.name, sub.name);
19167
19324
  }
19168
19325
  }
19169
19326
  }
@@ -19190,7 +19347,7 @@ __export(discover_exports, {
19190
19347
  import { readdir as readdir5, readFile as readFile5 } from "fs/promises";
19191
19348
  import { createHash } from "crypto";
19192
19349
  import { homedir as homedir5 } from "os";
19193
- import { join as join21 } from "path";
19350
+ import { join as join22 } from "path";
19194
19351
  function invalidateSkillCache() {
19195
19352
  cachedSkills = null;
19196
19353
  cacheTimestamp = 0;
@@ -19208,7 +19365,7 @@ async function discoverAllSkills() {
19208
19365
  const rawSkills = [];
19209
19366
  rawSkills.push(...await scanSkillDir(SKILLS_PATH, "cc-claw"));
19210
19367
  for (const backendId of getAllBackendIds()) {
19211
- const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join21(homedir5(), `.${backendId}`, "skills")];
19368
+ const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join22(homedir5(), `.${backendId}`, "skills")];
19212
19369
  for (const dir of dirs) {
19213
19370
  rawSkills.push(...await scanSkillDir(dir, backendId));
19214
19371
  }
@@ -19236,7 +19393,7 @@ async function scanSkillDir(skillsDir, source) {
19236
19393
  let content;
19237
19394
  let resolvedPath;
19238
19395
  for (const candidate of SKILL_FILE_CANDIDATES) {
19239
- const p = join21(skillsDir, entry.name, candidate);
19396
+ const p = join22(skillsDir, entry.name, candidate);
19240
19397
  try {
19241
19398
  content = await readFile5(p, "utf-8");
19242
19399
  resolvedPath = p;
@@ -19330,15 +19487,15 @@ var init_discover = __esm({
19330
19487
  init_backends();
19331
19488
  SKILL_FILE_CANDIDATES = ["SKILL.md", "skill.md"];
19332
19489
  BACKEND_SKILL_DIRS = {
19333
- claude: [join21(homedir5(), ".claude", "skills")],
19334
- gemini: [join21(homedir5(), ".gemini", "skills")],
19490
+ claude: [join22(homedir5(), ".claude", "skills")],
19491
+ gemini: [join22(homedir5(), ".gemini", "skills")],
19335
19492
  codex: [
19336
- join21(homedir5(), ".agents", "skills"),
19337
- join21(homedir5(), ".codex", "skills")
19493
+ join22(homedir5(), ".agents", "skills"),
19494
+ join22(homedir5(), ".codex", "skills")
19338
19495
  ],
19339
19496
  cursor: [
19340
- join21(homedir5(), ".cursor", "skills"),
19341
- join21(homedir5(), ".cursor", "skills-cursor")
19497
+ join22(homedir5(), ".cursor", "skills"),
19498
+ join22(homedir5(), ".cursor", "skills-cursor")
19342
19499
  ]
19343
19500
  };
19344
19501
  CACHE_TTL_MS2 = 3e5;
@@ -19696,43 +19853,76 @@ async function sendVoiceConfigKeyboard(chatId, channel) {
19696
19853
  await channel.sendText(chatId, "Voice configuration requires an interactive channel (Telegram).", { parseMode: "plain" });
19697
19854
  return;
19698
19855
  }
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"}`;
19856
+ const ttsConfig = getVoiceConfig(chatId);
19857
+ const sttProvider = getSttProvider(chatId);
19858
+ const sttModel = getSttModel(chatId);
19859
+ const whisperAvailable = isWhisperCliAvailable();
19706
19860
  const buttons = [];
19861
+ const groqAvailable = !!process.env.GROQ_API_KEY;
19707
19862
  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" }
19863
+ {
19864
+ label: `${sttProvider === "groq" ? "\u2713 " : ""}\u{1F310} Groq${!groqAvailable ? " (no key)" : ""}`,
19865
+ data: "vcfg:stt:groq",
19866
+ ...sttProvider === "groq" ? { style: "primary" } : {}
19867
+ },
19868
+ {
19869
+ label: `${sttProvider === "local-whisper" ? "\u2713 " : ""}\u{1F4BB} Local Whisper${!whisperAvailable ? " (install first)" : ""}`,
19870
+ data: "vcfg:stt:local-whisper",
19871
+ ...sttProvider === "local-whisper" ? { style: "primary" } : {}
19872
+ }
19711
19873
  ]);
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
- })));
19874
+ if (sttProvider === "local-whisper" || whisperAvailable) {
19875
+ const modelEntries = Object.entries(LOCAL_WHISPER_MODELS);
19876
+ for (let i = 0; i < modelEntries.length; i += 2) {
19877
+ const row = modelEntries.slice(i, i + 2).map(([id, info]) => {
19878
+ const downloaded = isWhisperModelDownloaded(id);
19879
+ const active = sttModel === id;
19880
+ return {
19881
+ label: `${active ? "\u2713 " : ""}${id} ${downloaded ? "\u25CF" : "\u25CB"} ${info.size}`,
19882
+ data: `vcfg:stt-model:${id}`,
19883
+ ...active ? { style: "primary" } : {}
19884
+ };
19885
+ });
19886
+ buttons.push(row);
19887
+ }
19735
19888
  }
19889
+ buttons.push([
19890
+ {
19891
+ label: `${!ttsConfig.enabled ? "\u2713 " : ""}\u{1F507} Replies Off`,
19892
+ data: "voice:off",
19893
+ ...!ttsConfig.enabled ? { style: "danger" } : {}
19894
+ },
19895
+ {
19896
+ label: `${ttsConfig.enabled ? "\u2713 " : ""}\u{1F50A} Replies On`,
19897
+ data: "voice:on",
19898
+ ...ttsConfig.enabled ? { style: "success" } : {}
19899
+ }
19900
+ ]);
19901
+ buttons.push([
19902
+ { label: `${ttsConfig.provider === "elevenlabs" ? "\u2713 " : ""}ElevenLabs`, data: "vcfg:p:elevenlabs", ...ttsConfig.provider === "elevenlabs" ? { style: "primary" } : {} },
19903
+ { label: `${ttsConfig.provider === "grok" ? "\u2713 " : ""}Grok`, data: "vcfg:p:grok", ...ttsConfig.provider === "grok" ? { style: "primary" } : {} },
19904
+ { label: `${ttsConfig.provider === "macos" ? "\u2713 " : ""}macOS`, data: "vcfg:p:macos", ...ttsConfig.provider === "macos" ? { style: "primary" } : {} }
19905
+ ]);
19906
+ if (ttsConfig.enabled) {
19907
+ if (ttsConfig.provider === "elevenlabs") {
19908
+ const entries = Object.entries(ELEVENLABS_VOICES);
19909
+ const female = entries.filter(([, v]) => v.gender === "F");
19910
+ const male = entries.filter(([, v]) => v.gender === "M");
19911
+ buttons.push(female.map(([id, v]) => ({ label: `${ttsConfig.voiceId === id ? "\u2713 " : ""}${v.name}`, data: `vcfg:v:${id}` })));
19912
+ buttons.push(male.map(([id, v]) => ({ label: `${ttsConfig.voiceId === id ? "\u2713 " : ""}${v.name}`, data: `vcfg:v:${id}` })));
19913
+ } else if (ttsConfig.provider === "grok") {
19914
+ buttons.push(GROK_VOICES.map((v) => ({ label: `${ttsConfig.voiceId === v ? "\u2713 " : ""}${capitalize(v)}`, data: `vcfg:v:${v}` })));
19915
+ } else {
19916
+ buttons.push(Object.entries(MACOS_VOICES).map(([id, v]) => ({ label: `${ttsConfig.voiceId === id ? "\u2713 " : ""}${v.name}`, data: `vcfg:v:${id}` })));
19917
+ }
19918
+ }
19919
+ const sttLabel = sttProvider === "groq" ? "Groq (cloud)" : `Local Whisper \xB7 ${sttModel}`;
19920
+ const ttsLabel = ttsConfig.enabled ? `${ttsConfig.provider === "grok" ? "Grok" : ttsConfig.provider === "macos" ? "macOS" : "ElevenLabs"} replies ON` : "Replies OFF";
19921
+ const modelLegend = sttProvider === "local-whisper" || whisperAvailable ? "\n\u25CF = downloaded \u25CB = not yet downloaded" : "";
19922
+ const header2 = `\u{1F399}\uFE0F Voice Settings
19923
+
19924
+ \u{1F3A4} Transcription: ${sttLabel}
19925
+ \u{1F50A} Text-to-Speech: ${ttsLabel}${modelLegend}`;
19736
19926
  await channel.sendKeyboard(chatId, header2, buttons);
19737
19927
  }
19738
19928
  async function sendSkillsPage(chatId, channel, skills2, page, messageId) {
@@ -19829,28 +20019,14 @@ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
19829
20019
  const backendDisplay = config2?.backend ? capitalize(config2.backend) : "Default";
19830
20020
  const modelDisplay = config2?.model ?? "default";
19831
20021
  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}`);
20022
+ const lastBeat = config2?.lastBeatAt ? config2.lastBeatAt.replace("T", " ").slice(0, 16) : "never";
20023
+ const watchNote = watches.length > 0 ? ` \xB7 ${watches.length} watch${watches.length !== 1 ? "es" : ""}` : "";
20024
+ const fallbackNote = fallbacks.length > 0 ? `Fallbacks: ${fallbacks.map((f) => `${capitalize(f.backend)}${f.model ? ` (${shortModelName(f.model)})` : ""}`).join(" \u2192 ")}` : "";
20025
+ const header2 = [
20026
+ `\u{1FAC0} Heartbeat \u2014 ${enabled ? "ON" : "OFF"} \xB7 Every ${intervalMin} min \xB7 ${activeStart}\u2013${activeEnd}${watchNote}`,
20027
+ `Backend: ${backendDisplay} \xB7 Model: ${modelDisplay} \xB7 Last: ${lastBeat}`,
20028
+ fallbackNote
20029
+ ].filter(Boolean).join("\n");
19854
20030
  const buttons = [];
19855
20031
  buttons.push([
19856
20032
  { label: `${enabled ? "\u2713 " : ""}On`, data: "hb:on", ...enabled ? { style: "success" } : {} },
@@ -19864,63 +20040,62 @@ async function sendHeartbeatKeyboard(chatId, channel, messageId) {
19864
20040
  ...m === intervalMin ? { style: "primary" } : {}
19865
20041
  })));
19866
20042
  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
- }
20043
+ const backendRow = [
20044
+ {
20045
+ label: `${!config2?.backend ? "\u2713 " : ""}Default`,
20046
+ data: "hb:backend:default",
20047
+ ...!config2?.backend ? { style: "primary" } : {}
20048
+ },
20049
+ ...available.map((bid) => ({
20050
+ label: `${config2?.backend === bid ? "\u2713 " : ""}${capitalize(bid)}`,
20051
+ data: `hb:backend:${bid}`,
20052
+ ...config2?.backend === bid ? { style: "primary" } : {}
20053
+ }))
20054
+ ];
19876
20055
  buttons.push(backendRow);
19877
20056
  const targetBackend = config2?.backend ?? getBackend(chatId) ?? "claude";
19878
20057
  try {
19879
20058
  const adapter = getAdapter(targetBackend);
19880
20059
  const models = Object.entries(adapter.availableModels);
19881
20060
  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
- }
20061
+ const modelRow = [
20062
+ {
20063
+ label: `${!config2?.model ? "\u2713 " : ""}Default`,
20064
+ data: "hb:model:default",
20065
+ ...!config2?.model ? { style: "primary" } : {}
20066
+ },
20067
+ ...models.slice(0, 4).map(([key]) => ({
20068
+ label: `${config2?.model === key ? "\u2713 " : ""}${shortModelName(key)}`,
20069
+ data: `hb:model:${key}`,
20070
+ ...config2?.model === key ? { style: "primary" } : {}
20071
+ }))
20072
+ ];
19890
20073
  buttons.push(modelRow);
19891
20074
  }
19892
20075
  } catch {
19893
20076
  }
19894
20077
  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
20078
  if (fallbacks.length > 0) {
19899
20079
  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" }
20080
+ { label: `\u{1F504} ${fallbacks.map((f) => capitalize(f.backend)).join(" \u2192 ")}`, data: "hb:noop" },
20081
+ { label: "\u2715 Clear", data: "hb:fb:clear", style: "danger" }
19905
20082
  ]);
19906
20083
  }
19907
20084
  if (fbCandidates.length > 0) {
19908
20085
  buttons.push(fbCandidates.slice(0, 4).map((bid) => ({
19909
- label: `+ ${capitalize(bid)}`,
20086
+ label: `+ ${capitalize(bid)} fallback`,
19910
20087
  data: `hb:fb:add:${bid}`
19911
20088
  })));
19912
20089
  }
19913
- const optionsRow = [
19914
- { label: `\u{1F4AD} Thinking: ${thinkingDisplay}`, data: "hb:thinking" }
19915
- ];
20090
+ const optionsRow = [];
19916
20091
  if (watches.length > 0) {
19917
20092
  optionsRow.push({ label: `\u{1F441} Watches (${watches.length})`, data: "hb:watches" });
19918
20093
  } else {
19919
- optionsRow.push({ label: "+ Add Watch", data: "hb:addwatch" });
20094
+ optionsRow.push({ label: "\u{1F441} Add Watch", data: "hb:addwatch" });
19920
20095
  }
20096
+ optionsRow.push({ label: `\u23F0 Hours: ${activeStart}\u2013${activeEnd}`, data: "hb:noop" });
19921
20097
  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);
20098
+ await sendOrEditKeyboard(chatId, channel, messageId, header2, buttons);
19924
20099
  }
19925
20100
  async function sendForgetPicker(chatId, channel, page, messageId) {
19926
20101
  const memories = listMemories();
@@ -20569,13 +20744,13 @@ async function handleEvolveCallback(chatId, data, channel) {
20569
20744
  const { getReflectionStatus: getReflectionStatus2, setReflectionStatus: setReflectionStatus2 } = await Promise.resolve().then(() => (init_store4(), store_exports4));
20570
20745
  const current = getReflectionStatus2(getDb(), chatId);
20571
20746
  if (current === "frozen") {
20572
- const { readFileSync: readFileSync29, existsSync: existsSync58 } = await import("fs");
20573
- const { join: join37 } = await import("path");
20747
+ const { readFileSync: readFileSync30, existsSync: existsSync59 } = await import("fs");
20748
+ const { join: join38 } = await import("path");
20574
20749
  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") : "";
20750
+ const soulPath = join38(CC_CLAW_HOME3, "identity/SOUL.md");
20751
+ const userPath = join38(CC_CLAW_HOME3, "identity/USER.md");
20752
+ const soul = existsSync59(soulPath) ? readFileSync30(soulPath, "utf-8") : "";
20753
+ const user = existsSync59(userPath) ? readFileSync30(userPath, "utf-8") : "";
20579
20754
  setReflectionStatus2(getDb(), chatId, "active", soul, user);
20580
20755
  const { logActivity: logActivity2 } = await Promise.resolve().then(() => (init_store3(), store_exports3));
20581
20756
  logActivity2(getDb(), { chatId, source: "telegram", eventType: "reflection_unfrozen", summary: "Reflection enabled" });
@@ -20652,11 +20827,11 @@ var init_evolve2 = __esm({
20652
20827
  });
20653
20828
 
20654
20829
  // 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";
20830
+ import { readFileSync as readFileSync12, existsSync as existsSync22, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
20831
+ import { join as join23 } from "path";
20657
20832
  function readIdentityFile2(filename) {
20658
20833
  try {
20659
- return readFileSync11(join22(IDENTITY_PATH, filename), "utf-8");
20834
+ return readFileSync12(join23(IDENTITY_PATH, filename), "utf-8");
20660
20835
  } catch {
20661
20836
  return "";
20662
20837
  }
@@ -20671,13 +20846,13 @@ function getMtime(filepath) {
20671
20846
  function findBackupFiles() {
20672
20847
  const backups = [];
20673
20848
  const dirs = [IDENTITY_PATH];
20674
- const contextDir = join22(IDENTITY_PATH, "..", "workspace", "context");
20675
- if (existsSync21(contextDir)) dirs.push(contextDir);
20849
+ const contextDir = join23(IDENTITY_PATH, "..", "workspace", "context");
20850
+ if (existsSync22(contextDir)) dirs.push(contextDir);
20676
20851
  for (const dir of dirs) {
20677
20852
  try {
20678
20853
  for (const entry of readdirSync10(dir)) {
20679
20854
  if (entry.endsWith(".bak") || /\.bak\.\d{4}-\d{2}-\d{2}/.test(entry)) {
20680
- backups.push(join22(dir, entry));
20855
+ backups.push(join23(dir, entry));
20681
20856
  }
20682
20857
  }
20683
20858
  } catch {
@@ -20698,9 +20873,9 @@ function computeIdentityStats(pendingProposals, driftPercent) {
20698
20873
  userChars,
20699
20874
  ccClawChars,
20700
20875
  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")),
20876
+ soulMtime: getMtime(join23(IDENTITY_PATH, "SOUL.md")),
20877
+ userMtime: getMtime(join23(IDENTITY_PATH, "USER.md")),
20878
+ ccClawMtime: getMtime(join23(IDENTITY_PATH, "CC-CLAW.md")),
20704
20879
  backupFiles: findBackupFiles(),
20705
20880
  estimatedTokens: Math.ceil(ccClawChars / 4),
20706
20881
  pendingEvolveProposals: pendingProposals,
@@ -20809,8 +20984,8 @@ var init_identity_audit = __esm({
20809
20984
  });
20810
20985
 
20811
20986
  // 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";
20987
+ import { readFileSync as readFileSync13, existsSync as existsSync23 } from "fs";
20988
+ import { join as join24, basename as basename3 } from "path";
20814
20989
  function parseFrontmatter3(content) {
20815
20990
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
20816
20991
  if (!fmMatch) return {};
@@ -20846,10 +21021,10 @@ function detectDependentSkills(content) {
20846
21021
  return Array.from(deps);
20847
21022
  }
20848
21023
  function computeSkillStats(skillPath) {
20849
- const content = readFileSync12(skillPath, "utf-8");
21024
+ const content = readFileSync13(skillPath, "utf-8");
20850
21025
  const lines = content.split("\n");
20851
21026
  return {
20852
- skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join23(skillPath, "..")) : basename3(skillPath, ".md"),
21027
+ skillName: basename3(skillPath, ".md") === "SKILL" ? basename3(join24(skillPath, "..")) : basename3(skillPath, ".md"),
20853
21028
  skillPath,
20854
21029
  lineCount: lines.length,
20855
21030
  charCount: content.length,
@@ -20869,13 +21044,13 @@ function loadDependentSkillContents(depNames, ccClawSkillsDir) {
20869
21044
  const results = [];
20870
21045
  for (const name of depNames) {
20871
21046
  const candidates = [
20872
- join23(ccClawSkillsDir, name, "SKILL.md"),
20873
- join23(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
21047
+ join24(ccClawSkillsDir, name, "SKILL.md"),
21048
+ join24(ccClawSkillsDir, `${name}-skill`, "SKILL.md")
20874
21049
  ];
20875
21050
  for (const candidate of candidates) {
20876
- if (existsSync22(candidate)) {
21051
+ if (existsSync23(candidate)) {
20877
21052
  try {
20878
- const content = readFileSync12(candidate, "utf-8");
21053
+ const content = readFileSync13(candidate, "utf-8");
20879
21054
  results.push({
20880
21055
  name,
20881
21056
  content: content.length > 3e3 ? content.slice(0, 3e3) + "\n[...truncated]" : content
@@ -20987,8 +21162,8 @@ __export(analyze_exports2, {
20987
21162
  });
20988
21163
  import { spawn as spawn7 } from "child_process";
20989
21164
  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";
21165
+ import { readFileSync as readFileSync14, existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
21166
+ import { join as join25 } from "path";
20992
21167
  import { homedir as homedir7 } from "os";
20993
21168
  function parseOptimizeOutput(raw, validAreas) {
20994
21169
  if (!raw || raw.includes("NO_FINDINGS")) return [];
@@ -21118,20 +21293,20 @@ function getModelDisplayInfo(chatId) {
21118
21293
  }
21119
21294
  function readIdentityFile3(filename) {
21120
21295
  try {
21121
- return readFileSync13(join24(IDENTITY_PATH, filename), "utf-8");
21296
+ return readFileSync14(join25(IDENTITY_PATH, filename), "utf-8");
21122
21297
  } catch {
21123
21298
  return "";
21124
21299
  }
21125
21300
  }
21126
21301
  function loadContextFiles2() {
21127
- const contextDir = join24(homedir7(), ".cc-claw", "workspace", "context");
21302
+ const contextDir = join25(homedir7(), ".cc-claw", "workspace", "context");
21128
21303
  const results = [];
21129
- if (!existsSync23(contextDir)) return results;
21304
+ if (!existsSync24(contextDir)) return results;
21130
21305
  try {
21131
21306
  for (const entry of readdirSync12(contextDir)) {
21132
21307
  if (!entry.endsWith(".md")) continue;
21133
21308
  try {
21134
- const content = readFileSync13(join24(contextDir, entry), "utf-8");
21309
+ const content = readFileSync14(join25(contextDir, entry), "utf-8");
21135
21310
  results.push({ name: entry, content });
21136
21311
  } catch {
21137
21312
  }
@@ -21182,8 +21357,8 @@ async function runSkillAudit(chatId, skillPath) {
21182
21357
  const stats = computeSkillStats(skillPath);
21183
21358
  log(`[optimizer] Running skill audit on ${stats.skillName} with ${adapter.id}:${model2}`);
21184
21359
  const soulMd = readIdentityFile3("SOUL.md");
21185
- const ccClawSkillsDir = join24(homedir7(), ".cc-claw", "workspace", "skills");
21186
- const skillContent = readFileSync13(skillPath, "utf-8");
21360
+ const ccClawSkillsDir = join25(homedir7(), ".cc-claw", "workspace", "skills");
21361
+ const skillContent = readFileSync14(skillPath, "utf-8");
21187
21362
  const prompt = buildSkillAuditPrompt(skillContent, stats, soulMd, ccClawSkillsDir);
21188
21363
  const raw = await spawnAnalysis2(adapter, model2, prompt);
21189
21364
  const findings = parseOptimizeOutput(raw, VALID_SKILL_AREAS);
@@ -21197,16 +21372,16 @@ async function runSkillAudit(chatId, skillPath) {
21197
21372
  };
21198
21373
  }
21199
21374
  function listCcClawSkills() {
21200
- const skillsDir = join24(homedir7(), ".cc-claw", "workspace", "skills");
21375
+ const skillsDir = join25(homedir7(), ".cc-claw", "workspace", "skills");
21201
21376
  const entries = [];
21202
- if (!existsSync23(skillsDir)) return entries;
21377
+ if (!existsSync24(skillsDir)) return entries;
21203
21378
  try {
21204
21379
  for (const dir of readdirSync12(skillsDir)) {
21205
- const skillFile = join24(skillsDir, dir, "SKILL.md");
21206
- if (!existsSync23(skillFile)) continue;
21380
+ const skillFile = join25(skillsDir, dir, "SKILL.md");
21381
+ if (!existsSync24(skillFile)) continue;
21207
21382
  let description = "skill";
21208
21383
  try {
21209
- const content = readFileSync13(skillFile, "utf-8");
21384
+ const content = readFileSync14(skillFile, "utf-8");
21210
21385
  const descMatch = content.match(/description:\s*>?\s*\n?\s*(.+)/);
21211
21386
  if (descMatch) description = descMatch[1].trim().slice(0, 60);
21212
21387
  } catch {
@@ -21526,8 +21701,8 @@ var init_ui2 = __esm({
21526
21701
  });
21527
21702
 
21528
21703
  // 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";
21704
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, existsSync as existsSync25, readdirSync as readdirSync13, unlinkSync as unlinkSync7 } from "fs";
21705
+ import { join as join26, dirname as dirname4 } from "path";
21531
21706
  import { homedir as homedir8 } from "os";
21532
21707
  async function handleOptimizeCommand(chatId, channel, _args) {
21533
21708
  const { getModelDisplayInfo: getModelDisplayInfo2 } = await Promise.resolve().then(() => (init_analyze2(), analyze_exports2));
@@ -21708,7 +21883,7 @@ async function runSkillAuditFlow(chatId, channel, skillName) {
21708
21883
  } = await Promise.resolve().then(() => (init_ui2(), ui_exports));
21709
21884
  const modelInfo = getModelDisplayInfo2(chatId);
21710
21885
  if (!modelInfo) return;
21711
- const skillPath = join25(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
21886
+ const skillPath = join26(homedir8(), ".cc-claw", "workspace", "skills", skillName, "SKILL.md");
21712
21887
  const progressMsgId = typeof channel.sendTextReturningId === "function" ? await channel.sendTextReturningId(
21713
21888
  chatId,
21714
21889
  buildProgressMessage2(`skill: ${skillName}`, modelInfo.backend, modelInfo.model, modelInfo.thinkingLevel),
@@ -21797,13 +21972,13 @@ async function applyFinding(chatId, channel, index) {
21797
21972
  await showFinding(chatId, channel, index + 1);
21798
21973
  return;
21799
21974
  }
21800
- if (!existsSync24(targetPath)) {
21975
+ if (!existsSync25(targetPath)) {
21801
21976
  await channel.sendText(chatId, `Target file not found: ${targetPath}`, { parseMode: "plain" });
21802
21977
  session2.skipped.push(index);
21803
21978
  await showFinding(chatId, channel, index + 1);
21804
21979
  return;
21805
21980
  }
21806
- const original = readFileSync14(targetPath, "utf-8");
21981
+ const original = readFileSync15(targetPath, "utf-8");
21807
21982
  const backupPath = targetPath + `.bak.${Date.now()}`;
21808
21983
  writeFileSync7(backupPath, original, "utf-8");
21809
21984
  pruneBackups2(targetPath);
@@ -21879,14 +22054,14 @@ async function finishReview(chatId, channel) {
21879
22054
  activeSessions.delete(chatId);
21880
22055
  }
21881
22056
  function resolveTargetFile(location, auditTarget) {
21882
- const ccClawHome = join25(homedir8(), ".cc-claw");
22057
+ const ccClawHome = join26(homedir8(), ".cc-claw");
21883
22058
  const filePart = location.split(":")[0]?.trim();
21884
22059
  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");
22060
+ if (filePart === "SOUL.md") return join26(ccClawHome, "identity", "SOUL.md");
22061
+ if (filePart === "USER.md") return join26(ccClawHome, "identity", "USER.md");
22062
+ if (filePart === "CC-CLAW.md") return join26(ccClawHome, "identity", "CC-CLAW.md");
21888
22063
  if (filePart === "SKILL.md" && auditTarget !== "identity") {
21889
- return join25(ccClawHome, "workspace", "skills", auditTarget, "SKILL.md");
22064
+ return join26(ccClawHome, "workspace", "skills", auditTarget, "SKILL.md");
21890
22065
  }
21891
22066
  return null;
21892
22067
  }
@@ -21894,7 +22069,7 @@ function pruneBackups2(absolutePath) {
21894
22069
  const dir = dirname4(absolutePath);
21895
22070
  const baseName = absolutePath.split("/").pop() ?? "";
21896
22071
  try {
21897
- const backups = readdirSync13(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join25(dir, f));
22072
+ const backups = readdirSync13(dir).filter((f) => f.startsWith(baseName + ".bak.")).sort().map((f) => join26(dir, f));
21898
22073
  while (backups.length > 3) {
21899
22074
  const oldest = backups.shift();
21900
22075
  try {
@@ -21928,8 +22103,8 @@ __export(auto_create_exports, {
21928
22103
  saveSkill: () => saveSkill,
21929
22104
  storePendingDraft: () => storePendingDraft
21930
22105
  });
21931
- import { join as join26 } from "path";
21932
- import { writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
22106
+ import { join as join27 } from "path";
22107
+ import { writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
21933
22108
  function isSkillWorthy(signals) {
21934
22109
  const { toolUseCount, tokenOutput, elapsedMs, userMessage } = signals;
21935
22110
  if (toolUseCount < 12) return false;
@@ -22075,10 +22250,10 @@ function parseExtractedSkill(llmResponse) {
22075
22250
  return { name, content };
22076
22251
  }
22077
22252
  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");
22253
+ const dir = join27(SKILLS_PATH, name);
22254
+ await mkdir5(dir, { recursive: true });
22255
+ const filePath = join27(dir, "SKILL.md");
22256
+ await writeFile5(filePath, content, "utf-8");
22082
22257
  invalidateSkillCache();
22083
22258
  log(`[auto-skill] Saved skill "${name}" to ${filePath}`);
22084
22259
  return { path: filePath };
@@ -22248,18 +22423,7 @@ async function handleStopCommand(chatId, commandArgs, msg, channel) {
22248
22423
  }
22249
22424
  }
22250
22425
  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
- }
22426
+ await sendVoiceConfigKeyboard(chatId, channel);
22263
22427
  }
22264
22428
  async function handleVoiceConfigCommand(chatId, commandArgs, msg, channel) {
22265
22429
  await sendVoiceConfigKeyboard(chatId, channel);
@@ -22458,42 +22622,40 @@ Use /skills to see it.`, { parseMode: "plain" });
22458
22622
  async function handleExtractSkillCommand(chatId, commandArgs, msg, channel) {
22459
22623
  const { getLog: getLog2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
22460
22624
  const sessionMessages = getLog2(chatId);
22625
+ const exchangeCount = Math.floor(sessionMessages.length / 2);
22461
22626
  if (sessionMessages.length < 2) {
22462
22627
  await channel.sendText(chatId, "No session history to extract from. Have a conversation first, then run /extract_skill.", { parseMode: "plain" });
22463
22628
  return;
22464
22629
  }
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
22630
  if (typeof channel.sendKeyboard === "function") {
22484
- await channel.sendText(chatId, `Extracted skill: "${extracted.name}"
22485
-
22486
- ${preview}`, { parseMode: "plain" });
22487
22631
  await channel.sendKeyboard(
22488
22632
  chatId,
22489
- `Save this skill?`,
22633
+ `\u{1F9E0} Extract Reusable Skill
22634
+
22635
+ This will analyze your current session (${exchangeCount} exchange${exchangeCount !== 1 ? "s" : ""}) and generate a reusable skill file.
22636
+
22637
+ \u2022 The AI reviews your conversation to identify the workflow
22638
+ \u2022 Generates a structured SKILL.md you can reuse in future tasks
22639
+ \u2022 You'll preview it before anything is saved
22640
+
22641
+ Ready to start?`,
22490
22642
  [[
22491
- { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
22492
- { label: "\u2715 Discard", data: "skill:discard" }
22643
+ { label: "\u26A1 Start Extraction", data: "skill:start-extract", style: "success" },
22644
+ { label: "\u2715 Cancel", data: "skill:discard" }
22493
22645
  ]]
22494
22646
  );
22495
22647
  } else {
22496
- const { saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
22648
+ await channel.sendText(chatId, `Reviewing session (${exchangeCount} exchanges)...`, { parseMode: "plain" });
22649
+ const { buildSessionExtractionPrompt: buildSessionExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
22650
+ const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
22651
+ const { getMode: getMode3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
22652
+ const prompt = buildSessionExtractionPrompt2(sessionMessages);
22653
+ const response = await askAgent3(chatId, prompt, { permMode: getMode3(chatId) });
22654
+ const extracted = parseExtractedSkill2(response.text);
22655
+ if (!extracted) {
22656
+ await channel.sendText(chatId, "Could not extract a skill from this session. The session may be too short or unfocused.", { parseMode: "plain" });
22657
+ return;
22658
+ }
22497
22659
  const result = await saveSkill2(extracted.name, extracted.content);
22498
22660
  await channel.sendText(chatId, `Skill "${extracted.name}" saved to ${result.path}`, { parseMode: "plain" });
22499
22661
  }
@@ -22604,6 +22766,22 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22604
22766
  stopAllSideQuests(chatId);
22605
22767
  const oldSessionId = getSessionId(chatId);
22606
22768
  const exchangeCount = getMessagePairCount(chatId);
22769
+ const needsSummary = exchangeCount > 0;
22770
+ let ackMsgId;
22771
+ if (needsSummary) {
22772
+ ackMsgId = await channel.sendTextReturningId?.(
22773
+ chatId,
22774
+ `\u23F3 Archiving session (${exchangeCount} exchanges)...`,
22775
+ "plain"
22776
+ );
22777
+ if (!ackMsgId) {
22778
+ await channel.sendText(
22779
+ chatId,
22780
+ `\u23F3 Archiving session (${exchangeCount} exchanges)...`,
22781
+ { parseMode: "plain" }
22782
+ );
22783
+ }
22784
+ }
22607
22785
  const summarized = await summarizeSession(chatId);
22608
22786
  clearSession(chatId);
22609
22787
  clearChatPaidSlots(chatId);
@@ -22622,6 +22800,7 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22622
22800
  const text = `\u2705 New session started. Previous session archived${exchangeCount > 0 ? ` (${exchangeCount} exchanges)` : ""}.
22623
22801
 
22624
22802
  \u{1F9E0} ${backendLabel} \xB7 ${modelLabel}`;
22803
+ if (ackMsgId) await channel.editText?.(chatId, ackMsgId, text, "plain");
22625
22804
  const kbMsgId = await channel.sendKeyboard(chatId, text, [
22626
22805
  [
22627
22806
  { label: "Switch Backend", data: "menu:backend", style: "primary" },
@@ -22640,7 +22819,11 @@ async function handleNewchatCommand(chatId, commandArgs, msg, channel) {
22640
22819
  }
22641
22820
  } else {
22642
22821
  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" });
22822
+ if (ackMsgId) {
22823
+ await channel.editText?.(chatId, ackMsgId, text, "plain");
22824
+ } else {
22825
+ await channel.sendText(chatId, text, { parseMode: "plain" });
22826
+ }
22644
22827
  }
22645
22828
  }
22646
22829
  async function handleSummarizeCommand(chatId, commandArgs, msg, channel) {
@@ -24522,6 +24705,44 @@ ${plan.originalMessage}`;
24522
24705
  if (current !== desired) toggleVoice(chatId);
24523
24706
  await channel.sendText(chatId, desired ? "\u{1F50A} Voice responses enabled." : "\u{1F507} Voice responses disabled.", { parseMode: "plain" });
24524
24707
  }
24708
+ } else if (data.startsWith("vcfg:stt-model:")) {
24709
+ const model2 = data.slice(15);
24710
+ if (!(model2 in LOCAL_WHISPER_MODELS)) {
24711
+ await channel.sendText(chatId, "Unknown model.", { parseMode: "plain" });
24712
+ return;
24713
+ }
24714
+ setSttProvider(chatId, "local-whisper");
24715
+ setSttModel(chatId, model2);
24716
+ const info = LOCAL_WHISPER_MODELS[model2];
24717
+ const downloaded = isWhisperModelDownloaded(model2);
24718
+ 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}).
24719
+
24720
+ \u2B07\uFE0F Model will be downloaded on your first voice message (~${info.size}). This is a one-time download.`;
24721
+ await channel.sendText(chatId, notice, { parseMode: "plain" });
24722
+ await sendVoiceConfigKeyboard(chatId, channel);
24723
+ } else if (data.startsWith("vcfg:stt:")) {
24724
+ const provider = data.slice(9);
24725
+ if (provider === "local-whisper") {
24726
+ if (!isWhisperCliAvailable()) {
24727
+ await channel.sendText(
24728
+ chatId,
24729
+ "\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.",
24730
+ { parseMode: "markdown" }
24731
+ );
24732
+ return;
24733
+ }
24734
+ }
24735
+ if (provider === "groq" && !process.env.GROQ_API_KEY) {
24736
+ await channel.sendText(
24737
+ chatId,
24738
+ "\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```",
24739
+ { parseMode: "markdown" }
24740
+ );
24741
+ }
24742
+ setSttProvider(chatId, provider);
24743
+ const label2 = provider === "groq" ? "Groq (cloud)" : "Local Whisper";
24744
+ await channel.sendText(chatId, `\u2705 Transcription provider set to: ${label2}`, { parseMode: "plain" });
24745
+ await sendVoiceConfigKeyboard(chatId, channel);
24525
24746
  } else if (data.startsWith("vcfg:")) {
24526
24747
  const parts = data.slice(5).split(":");
24527
24748
  const action = parts[0];
@@ -25429,15 +25650,16 @@ Salience: ${memory2.salience.toFixed(2)} | Created: ${memory2.created_at.slice(0
25429
25650
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25430
25651
  } else if (rest.startsWith("backend:")) {
25431
25652
  const bid = rest.slice(8);
25432
- if (bid === "default") {
25433
- updateHeartbeatConfig2({ backend: null, model: null });
25434
- } else {
25435
- updateHeartbeatConfig2({ backend: bid, model: null });
25436
- }
25653
+ const newBackend = bid === "default" ? null : bid;
25654
+ updateHeartbeatConfig2({ backend: newBackend, model: null });
25655
+ updateHeartbeatField(chatId, "backend", newBackend);
25656
+ updateHeartbeatField(chatId, "model", null);
25437
25657
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25438
25658
  } else if (rest.startsWith("model:")) {
25439
25659
  const model2 = rest.slice(6);
25440
- updateHeartbeatConfig2({ model: model2 === "default" ? null : model2 });
25660
+ const newModel = model2 === "default" ? null : model2;
25661
+ updateHeartbeatConfig2({ model: newModel });
25662
+ updateHeartbeatField(chatId, "model", newModel);
25441
25663
  await sendHeartbeatKeyboard(chatId, channel, messageId);
25442
25664
  } else if (rest === "thinking") {
25443
25665
  await channel.sendText(chatId, "Thinking level for heartbeat: use /editjob to configure.", { parseMode: "plain" });
@@ -25557,6 +25779,71 @@ Example: /limits ${bid} daily 500000`, { parseMode: "plain" });
25557
25779
  const page = parseInt(data.slice(12), 10);
25558
25780
  const skills2 = await discoverAllSkills();
25559
25781
  await sendSkillsPage(chatId, channel, skills2, page, messageId);
25782
+ } else if (data === "skill:start-extract") {
25783
+ const { getLog: getLog2 } = await Promise.resolve().then(() => (init_session_log(), session_log_exports));
25784
+ const sessionMessages = getLog2(chatId);
25785
+ if (sessionMessages.length < 2) {
25786
+ if (messageId) await replaceWithText("No session history to extract from.");
25787
+ else await channel.sendText(chatId, "No session history to extract from.", { parseMode: "plain" });
25788
+ return;
25789
+ }
25790
+ const exchangeCount = Math.floor(sessionMessages.length / 2);
25791
+ if (messageId) await replaceWithText(`\u{1F50D} Reviewing session (${exchangeCount} exchanges)...`);
25792
+ else await channel.sendText(chatId, `\u{1F50D} Reviewing session (${exchangeCount} exchanges)...`, { parseMode: "plain" });
25793
+ try {
25794
+ const { buildSessionExtractionPrompt: buildSessionExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, storePendingDraft: storePendingDraft2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
25795
+ const { askAgent: askAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
25796
+ const { getMode: getMode3 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
25797
+ const prompt = buildSessionExtractionPrompt2(sessionMessages);
25798
+ const response = await askAgent3(chatId, prompt, { permMode: getMode3(chatId) });
25799
+ const extracted = parseExtractedSkill2(response.text);
25800
+ if (!extracted) {
25801
+ await channel.sendText(chatId, "Could not extract a skill from this session. The session may be too short or unfocused.", { parseMode: "plain" });
25802
+ return;
25803
+ }
25804
+ storePendingDraft2(chatId, {
25805
+ name: extracted.name,
25806
+ content: extracted.content,
25807
+ userMessage: sessionMessages.filter((m) => m.role === "user").map((m) => m.text).join("\n"),
25808
+ assistantResponse: sessionMessages.filter((m) => m.role === "assistant").map((m) => m.text).join("\n")
25809
+ });
25810
+ const escaped = extracted.content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
25811
+ const header2 = `\u{1F4CB} <b>Skill Preview: "${extracted.name}"</b>
25812
+
25813
+ `;
25814
+ const MAX_PREVIEW = 3500 - header2.length;
25815
+ if (typeof channel.sendKeyboard === "function") {
25816
+ if (escaped.length > MAX_PREVIEW) {
25817
+ await channel.sendText(chatId, `${header2}<pre>${escaped.slice(0, 3500)}</pre>
25818
+
25819
+ \u2026(truncated \u2014 full skill will be saved)`, { parseMode: "html" });
25820
+ await channel.sendKeyboard(
25821
+ chatId,
25822
+ `Save "${extracted.name}" as a reusable skill?`,
25823
+ [[
25824
+ { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
25825
+ { label: "\u2715 Discard", data: "skill:discard" }
25826
+ ]]
25827
+ );
25828
+ } else {
25829
+ await channel.sendKeyboard(
25830
+ chatId,
25831
+ `${header2}<pre>${escaped}</pre>`,
25832
+ [[
25833
+ { label: "\u2705 Save Skill", data: "skill:confirm-save", style: "success" },
25834
+ { label: "\u2715 Discard", data: "skill:discard" }
25835
+ ]]
25836
+ );
25837
+ }
25838
+ } else {
25839
+ const { saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
25840
+ const { path } = await saveSkill2(extracted.name, extracted.content);
25841
+ await channel.sendText(chatId, `\u2705 Skill "${extracted.name}" saved.
25842
+ Path: ${path}`, { parseMode: "plain" });
25843
+ }
25844
+ } catch (e) {
25845
+ await channel.sendText(chatId, `Skill extraction failed: ${e.message}`, { parseMode: "plain" });
25846
+ }
25560
25847
  } else if (data === "skill:extract") {
25561
25848
  const { getPendingDraft: getPendingDraft2, clearPendingDraft: clearPendingDraft2, buildSkillExtractionPrompt: buildSkillExtractionPrompt2, parseExtractedSkill: parseExtractedSkill2, saveSkill: saveSkill2 } = await Promise.resolve().then(() => (init_auto_create(), auto_create_exports));
25562
25849
  const draft = getPendingDraft2(chatId);
@@ -26432,7 +26719,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26432
26719
  })) {
26433
26720
  const planDirective = buildPlanningDirective();
26434
26721
  let typingActive2 = true;
26435
- const typingLoop = async () => {
26722
+ const typingLoop2 = async () => {
26436
26723
  while (typingActive2) {
26437
26724
  try {
26438
26725
  await channel.sendTyping?.(chatId);
@@ -26441,7 +26728,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26441
26728
  await new Promise((r) => setTimeout(r, 4e3));
26442
26729
  }
26443
26730
  };
26444
- typingLoop().catch(() => {
26731
+ typingLoop2().catch(() => {
26445
26732
  });
26446
26733
  try {
26447
26734
  const planResponse = await askAgent(chatId, cleanText || text, {
@@ -26482,23 +26769,18 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26482
26769
  }
26483
26770
  return;
26484
26771
  }
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));
26772
+ let typingActive = true;
26773
+ const typingLoop = async () => {
26774
+ while (typingActive) {
26775
+ try {
26776
+ await channel.sendTyping?.(chatId);
26777
+ } catch {
26497
26778
  }
26498
- };
26499
- typingLoop().catch(() => {
26500
- });
26501
- }
26779
+ await new Promise((r) => setTimeout(r, 4e3));
26780
+ }
26781
+ };
26782
+ typingLoop().catch(() => {
26783
+ });
26502
26784
  try {
26503
26785
  const tMode = settings.getMode();
26504
26786
  const tVerbose = settings.getVerboseLevel();
@@ -26529,6 +26811,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26529
26811
  }
26530
26812
  };
26531
26813
  await liveStatus.init();
26814
+ typingActive = false;
26532
26815
  if (showThinkingUi && adapter.id !== "claude") {
26533
26816
  liveStatus.addInfo(`\u{1F4AD} Thinking display not available for ${adapter.displayName}`);
26534
26817
  }
@@ -26622,7 +26905,7 @@ Debating: "${question.slice(0, 100)}${question.length > 100 ? "\u2026" : ""}"`,
26622
26905
  }
26623
26906
  responseText += `
26624
26907
 
26625
- \u{1F9E0} [${shortModel} | ${capitalize(thinking2)}${slotTag}] \u23F1\uFE0F ${elapsedSec}s`;
26908
+ \u{1F9E0} [${shortModel} | ${capitalize(thinking2)}${slotTag}] \u23F1\uFE0F ${elapsedSec}s \xB7 \u{1F195}/new`;
26626
26909
  }
26627
26910
  if (observedSubagents.size > 0) {
26628
26911
  const names = [...observedSubagents].join(", ");
@@ -27177,7 +27460,19 @@ function resolveJobBackendId(job) {
27177
27460
  })();
27178
27461
  }
27179
27462
  function resolveJobModel(job) {
27180
- if (job.model) return job.model;
27463
+ if (job.model) {
27464
+ if (job.backend) {
27465
+ try {
27466
+ const adapter = getAdapter(job.backend);
27467
+ if (!(job.model in adapter.availableModels)) {
27468
+ warn(`[scheduler] job #${job.id}: model "${job.model}" not valid for backend "${job.backend}" \u2014 using default`);
27469
+ return adapter.defaultModel;
27470
+ }
27471
+ } catch {
27472
+ }
27473
+ }
27474
+ return job.model;
27475
+ }
27181
27476
  const backendId = resolveJobBackendId(job);
27182
27477
  try {
27183
27478
  const adapter = getAdapter(backendId);
@@ -27211,7 +27506,7 @@ var init_cron = __esm({
27211
27506
  });
27212
27507
 
27213
27508
  // src/agents/runners/wrap-backend.ts
27214
- import { join as join27 } from "path";
27509
+ import { join as join28 } from "path";
27215
27510
  function buildMcpCommands(backendId) {
27216
27511
  const exe = backendId === BACKEND.CURSOR ? "agent" : backendId;
27217
27512
  return {
@@ -27305,7 +27600,7 @@ function wrapBackendAdapter(adapter) {
27305
27600
  const configPath = writeMcpConfigFile(server);
27306
27601
  return ["--mcp-config", configPath];
27307
27602
  },
27308
- getSkillPath: () => join27(SKILLS_PATH, `agent-${adapter.id}.md`)
27603
+ getSkillPath: () => join28(SKILLS_PATH, `agent-${adapter.id}.md`)
27309
27604
  };
27310
27605
  }
27311
27606
  var BACKEND_CAPABILITIES;
@@ -27367,18 +27662,18 @@ var init_wrap_backend = __esm({
27367
27662
  });
27368
27663
 
27369
27664
  // 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";
27665
+ import { readFileSync as readFileSync16, readdirSync as readdirSync14, existsSync as existsSync26, mkdirSync as mkdirSync10, watchFile, unwatchFile } from "fs";
27666
+ import { join as join29 } from "path";
27372
27667
  import { execFileSync as execFileSync3 } from "child_process";
27373
27668
  function resolveExecutable2(config2) {
27374
- if (existsSync25(config2.executable)) return config2.executable;
27669
+ if (existsSync26(config2.executable)) return config2.executable;
27375
27670
  try {
27376
27671
  return execFileSync3("which", [config2.executable], { encoding: "utf-8" }).trim();
27377
27672
  } catch {
27378
27673
  }
27379
27674
  for (const fallback of config2.executableFallbacks ?? []) {
27380
27675
  const resolved = fallback.replace(/^~/, process.env.HOME ?? "");
27381
- if (existsSync25(resolved)) return resolved;
27676
+ if (existsSync26(resolved)) return resolved;
27382
27677
  }
27383
27678
  return config2.executable;
27384
27679
  }
@@ -27504,12 +27799,12 @@ function configToRunner(config2) {
27504
27799
  prepareMcpInjection() {
27505
27800
  return [];
27506
27801
  },
27507
- getSkillPath: () => join28(SKILLS_PATH, `agent-${config2.id}.md`)
27802
+ getSkillPath: () => join29(SKILLS_PATH, `agent-${config2.id}.md`)
27508
27803
  };
27509
27804
  }
27510
27805
  function loadRunnerConfig(filePath) {
27511
27806
  try {
27512
- const content = readFileSync15(filePath, "utf-8");
27807
+ const content = readFileSync16(filePath, "utf-8");
27513
27808
  return JSON.parse(content);
27514
27809
  } catch (err) {
27515
27810
  warn(`[runners] Failed to load config ${filePath}: ${err}`);
@@ -27517,14 +27812,14 @@ function loadRunnerConfig(filePath) {
27517
27812
  }
27518
27813
  }
27519
27814
  function loadAllRunnerConfigs() {
27520
- if (!existsSync25(RUNNERS_PATH)) {
27815
+ if (!existsSync26(RUNNERS_PATH)) {
27521
27816
  mkdirSync10(RUNNERS_PATH, { recursive: true });
27522
27817
  return [];
27523
27818
  }
27524
27819
  const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
27525
27820
  const configs = [];
27526
27821
  for (const file of files) {
27527
- const config2 = loadRunnerConfig(join28(RUNNERS_PATH, file));
27822
+ const config2 = loadRunnerConfig(join29(RUNNERS_PATH, file));
27528
27823
  if (config2) configs.push(config2);
27529
27824
  }
27530
27825
  return configs;
@@ -27545,16 +27840,16 @@ function registerConfigRunners() {
27545
27840
  return count;
27546
27841
  }
27547
27842
  function watchRunnerConfigs(onChange) {
27548
- if (!existsSync25(RUNNERS_PATH)) return;
27843
+ if (!existsSync26(RUNNERS_PATH)) return;
27549
27844
  for (const prev of watchedFiles) {
27550
- if (!existsSync25(prev)) {
27845
+ if (!existsSync26(prev)) {
27551
27846
  unwatchFile(prev);
27552
27847
  watchedFiles.delete(prev);
27553
27848
  }
27554
27849
  }
27555
27850
  const files = readdirSync14(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
27556
27851
  for (const file of files) {
27557
- const fullPath = join28(RUNNERS_PATH, file);
27852
+ const fullPath = join29(RUNNERS_PATH, file);
27558
27853
  if (watchedFiles.has(fullPath)) continue;
27559
27854
  watchedFiles.add(fullPath);
27560
27855
  watchFile(fullPath, { interval: 5e3 }, () => {
@@ -28159,8 +28454,7 @@ var init_telegram2 = __esm({
28159
28454
  { command: "skills", description: "List and invoke skills" },
28160
28455
  { command: "extract_skill", description: "Extract a reusable skill from this session" },
28161
28456
  { 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" },
28457
+ { command: "voice", description: "Voice settings (STT transcription + TTS replies)" },
28164
28458
  { command: "response_style", description: "Set the AI response style (concise/normal/detailed)" },
28165
28459
  { command: "model_signature", description: "Toggle model+thinking signature on responses" },
28166
28460
  { command: "imagine", description: "Generate an image from a prompt" },
@@ -28588,7 +28882,8 @@ var init_telegram2 = __esm({
28588
28882
  "reaction",
28589
28883
  () => this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
28590
28884
  { type: "emoji", emoji }
28591
- ])
28885
+ ]),
28886
+ { skipRecord: true }
28592
28887
  );
28593
28888
  } catch (err) {
28594
28889
  log(`[telegram] reactToMessage failed (chat=${chatId} msg=${messageId}): ${err}`);
@@ -28831,19 +29126,19 @@ var init_telegram2 = __esm({
28831
29126
  });
28832
29127
 
28833
29128
  // 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";
29129
+ import { existsSync as existsSync27 } from "fs";
29130
+ import { readdir as readdir6, readFile as readFile8, writeFile as writeFile6, copyFile } from "fs/promises";
29131
+ import { join as join30, dirname as dirname5 } from "path";
28837
29132
  import { fileURLToPath as fileURLToPath2 } from "url";
28838
29133
  async function copyAgentManifestSkills() {
28839
- if (!existsSync26(PKG_SKILLS)) return;
29134
+ if (!existsSync27(PKG_SKILLS)) return;
28840
29135
  try {
28841
29136
  const entries = await readdir6(PKG_SKILLS, { withFileTypes: true });
28842
29137
  for (const entry of entries) {
28843
29138
  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;
29139
+ const src = join30(PKG_SKILLS, entry.name);
29140
+ const dest = join30(SKILLS_PATH, entry.name);
29141
+ if (existsSync27(dest)) continue;
28847
29142
  await copyFile(src, dest);
28848
29143
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
28849
29144
  }
@@ -28853,8 +29148,8 @@ async function copyAgentManifestSkills() {
28853
29148
  }
28854
29149
  async function bootstrapSkills() {
28855
29150
  await copyAgentManifestSkills();
28856
- const usmDir = join29(SKILLS_PATH, USM_DIR_NAME);
28857
- if (existsSync26(usmDir)) return;
29151
+ const usmDir = join30(SKILLS_PATH, USM_DIR_NAME);
29152
+ if (existsSync27(usmDir)) return;
28858
29153
  try {
28859
29154
  const entries = await readdir6(SKILLS_PATH);
28860
29155
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -28877,8 +29172,8 @@ async function bootstrapSkills() {
28877
29172
  }
28878
29173
  }
28879
29174
  async function patchUsmForCcClaw(usmDir) {
28880
- const skillPath = join29(usmDir, "SKILL.md");
28881
- if (!existsSync26(skillPath)) return;
29175
+ const skillPath = join30(usmDir, "SKILL.md");
29176
+ if (!existsSync27(skillPath)) return;
28882
29177
  try {
28883
29178
  let content = await readFile8(skillPath, "utf-8");
28884
29179
  let patched = false;
@@ -28906,7 +29201,7 @@ async function patchUsmForCcClaw(usmDir) {
28906
29201
  }
28907
29202
  }
28908
29203
  if (patched) {
28909
- await writeFile5(skillPath, content, "utf-8");
29204
+ await writeFile6(skillPath, content, "utf-8");
28910
29205
  log("[skills] Patched USM SKILL.md with CC-Claw support");
28911
29206
  }
28912
29207
  } catch (err) {
@@ -28923,8 +29218,8 @@ var init_bootstrap = __esm({
28923
29218
  USM_REPO = "jacob-bd/universal-skills-manager";
28924
29219
  USM_DIR_NAME = "universal-skills-manager";
28925
29220
  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");
29221
+ PKG_ROOT = join30(dirname5(fileURLToPath2(import.meta.url)), "..", "..");
29222
+ PKG_SKILLS = join30(PKG_ROOT, "skills");
28928
29223
  }
28929
29224
  });
28930
29225
 
@@ -29037,13 +29332,13 @@ __export(ai_skill_exports, {
29037
29332
  generateAiSkill: () => generateAiSkill,
29038
29333
  installAiSkill: () => installAiSkill
29039
29334
  });
29040
- import { existsSync as existsSync27, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
29041
- import { join as join30 } from "path";
29335
+ import { existsSync as existsSync28, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
29336
+ import { join as join31 } from "path";
29042
29337
  import { homedir as homedir9 } from "os";
29043
29338
  function generateAiSkill() {
29044
29339
  const version = VERSION;
29045
29340
  let systemState = "";
29046
- if (existsSync27(DB_PATH)) {
29341
+ if (existsSync28(DB_PATH)) {
29047
29342
  try {
29048
29343
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store5(), __toCommonJS(store_exports5));
29049
29344
  const readDb = openDatabaseReadOnly2();
@@ -29483,8 +29778,8 @@ function installAiSkill() {
29483
29778
  const failed = [];
29484
29779
  for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
29485
29780
  for (const dir of dirs) {
29486
- const skillDir = join30(dir, "cc-claw-cli");
29487
- const skillPath = join30(skillDir, "SKILL.md");
29781
+ const skillDir = join31(dir, "cc-claw-cli");
29782
+ const skillPath = join31(skillDir, "SKILL.md");
29488
29783
  try {
29489
29784
  mkdirSync11(skillDir, { recursive: true });
29490
29785
  writeFileSync8(skillPath, skill, "utf-8");
@@ -29503,11 +29798,11 @@ var init_ai_skill = __esm({
29503
29798
  init_paths();
29504
29799
  init_version();
29505
29800
  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")]
29801
+ "cc-claw": [join31(homedir9(), ".cc-claw", "workspace", "skills")],
29802
+ claude: [join31(homedir9(), ".claude", "skills")],
29803
+ gemini: [join31(homedir9(), ".gemini", "skills")],
29804
+ codex: [join31(homedir9(), ".agents", "skills")],
29805
+ cursor: [join31(homedir9(), ".cursor", "skills"), join31(homedir9(), ".cursor", "skills-cursor")]
29511
29806
  };
29512
29807
  }
29513
29808
  });
@@ -29517,21 +29812,21 @@ var index_exports = {};
29517
29812
  __export(index_exports, {
29518
29813
  main: () => main
29519
29814
  });
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";
29815
+ import { mkdirSync as mkdirSync12, existsSync as existsSync29, renameSync as renameSync2, statSync as statSync9, readFileSync as readFileSync18 } from "fs";
29816
+ import { join as join32 } from "path";
29522
29817
  import dotenv from "dotenv";
29523
29818
  function migrateLayout() {
29524
29819
  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")]
29820
+ [join32(CC_CLAW_HOME, "cc-claw.db"), join32(DATA_PATH, "cc-claw.db")],
29821
+ [join32(CC_CLAW_HOME, "cc-claw.db-shm"), join32(DATA_PATH, "cc-claw.db-shm")],
29822
+ [join32(CC_CLAW_HOME, "cc-claw.db-wal"), join32(DATA_PATH, "cc-claw.db-wal")],
29823
+ [join32(CC_CLAW_HOME, "cc-claw.log"), join32(LOGS_PATH, "cc-claw.log")],
29824
+ [join32(CC_CLAW_HOME, "cc-claw.log.1"), join32(LOGS_PATH, "cc-claw.log.1")],
29825
+ [join32(CC_CLAW_HOME, "cc-claw.error.log"), join32(LOGS_PATH, "cc-claw.error.log")],
29826
+ [join32(CC_CLAW_HOME, "cc-claw.error.log.1"), join32(LOGS_PATH, "cc-claw.error.log.1")]
29532
29827
  ];
29533
29828
  for (const [from, to] of moves) {
29534
- if (existsSync28(from) && !existsSync28(to)) {
29829
+ if (existsSync29(from) && !existsSync29(to)) {
29535
29830
  try {
29536
29831
  renameSync2(from, to);
29537
29832
  } catch {
@@ -29560,7 +29855,7 @@ async function main() {
29560
29855
  let version = "unknown";
29561
29856
  try {
29562
29857
  const pkgPath = new URL("../package.json", import.meta.url);
29563
- version = JSON.parse(readFileSync17(pkgPath, "utf-8")).version;
29858
+ version = JSON.parse(readFileSync18(pkgPath, "utf-8")).version;
29564
29859
  } catch {
29565
29860
  }
29566
29861
  log(`[cc-claw] Starting v${version}`);
@@ -29708,10 +30003,10 @@ async function main() {
29708
30003
  try {
29709
30004
  const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
29710
30005
  const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync19 } = await import("fs");
29711
- const { join: join37 } = await import("path");
29712
- const skillDir = join37(SKILLS_PATH, "cc-claw-cli");
30006
+ const { join: join38 } = await import("path");
30007
+ const skillDir = join38(SKILLS_PATH, "cc-claw-cli");
29713
30008
  mkdirSync19(skillDir, { recursive: true });
29714
- writeFileSync13(join37(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
30009
+ writeFileSync13(join38(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
29715
30010
  log("[cc-claw] AI skill updated");
29716
30011
  } catch {
29717
30012
  }
@@ -29811,10 +30106,10 @@ var init_index = __esm({
29811
30106
  init_health3();
29812
30107
  init_image_gen();
29813
30108
  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 });
30109
+ if (!existsSync29(dir)) mkdirSync12(dir, { recursive: true });
29815
30110
  }
29816
30111
  migrateLayout();
29817
- if (existsSync28(ENV_PATH)) {
30112
+ if (existsSync29(ENV_PATH)) {
29818
30113
  dotenv.config({ path: ENV_PATH });
29819
30114
  } else {
29820
30115
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -29835,12 +30130,12 @@ __export(api_client_exports, {
29835
30130
  apiPost: () => apiPost,
29836
30131
  isDaemonRunning: () => isDaemonRunning
29837
30132
  });
29838
- import { readFileSync as readFileSync18, existsSync as existsSync29 } from "fs";
30133
+ import { readFileSync as readFileSync19, existsSync as existsSync30 } from "fs";
29839
30134
  import { request as httpRequest, Agent } from "http";
29840
30135
  function getToken() {
29841
30136
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
29842
30137
  try {
29843
- if (existsSync29(TOKEN_PATH)) return readFileSync18(TOKEN_PATH, "utf-8").trim();
30138
+ if (existsSync30(TOKEN_PATH)) return readFileSync19(TOKEN_PATH, "utf-8").trim();
29844
30139
  } catch {
29845
30140
  }
29846
30141
  return null;
@@ -29939,10 +30234,10 @@ __export(service_exports2, {
29939
30234
  serviceStatus: () => serviceStatus,
29940
30235
  uninstallService: () => uninstallService
29941
30236
  });
29942
- import { existsSync as existsSync30, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
30237
+ import { existsSync as existsSync31, mkdirSync as mkdirSync13, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8 } from "fs";
29943
30238
  import { execFileSync as execFileSync4, execSync as execSync5 } from "child_process";
29944
30239
  import { homedir as homedir10, platform } from "os";
29945
- import { join as join32, dirname as dirname6 } from "path";
30240
+ import { join as join33, dirname as dirname6 } from "path";
29946
30241
  function xmlEscape(s) {
29947
30242
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
29948
30243
  }
@@ -29951,7 +30246,7 @@ function resolveExecutable3(name) {
29951
30246
  return execFileSync4("which", [name], { encoding: "utf-8" }).trim();
29952
30247
  } catch {
29953
30248
  const fallback = process.argv[1];
29954
- if (fallback && existsSync30(fallback)) return fallback;
30249
+ if (fallback && existsSync31(fallback)) return fallback;
29955
30250
  throw new Error(`Cannot find '${name}' executable. Install globally: npm install -g cc-claw`);
29956
30251
  }
29957
30252
  }
@@ -29960,14 +30255,14 @@ function getPathDirs() {
29960
30255
  const home = homedir10();
29961
30256
  const dirs = /* @__PURE__ */ new Set([
29962
30257
  nodeBin,
29963
- join32(home, ".local", "bin"),
30258
+ join33(home, ".local", "bin"),
29964
30259
  "/usr/local/bin",
29965
30260
  "/usr/bin",
29966
30261
  "/bin"
29967
30262
  ]);
29968
30263
  try {
29969
30264
  const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
29970
- if (prefix) dirs.add(join32(prefix, "bin"));
30265
+ if (prefix) dirs.add(join33(prefix, "bin"));
29971
30266
  } catch {
29972
30267
  }
29973
30268
  return [...dirs].join(":");
@@ -30026,9 +30321,9 @@ function generatePlist() {
30026
30321
  }
30027
30322
  function installMacOS() {
30028
30323
  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)) {
30324
+ if (!existsSync31(agentsDir)) mkdirSync13(agentsDir, { recursive: true });
30325
+ if (!existsSync31(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30326
+ if (existsSync31(PLIST_PATH)) {
30032
30327
  try {
30033
30328
  execFileSync4("launchctl", ["unload", PLIST_PATH]);
30034
30329
  } catch {
@@ -30040,7 +30335,7 @@ function installMacOS() {
30040
30335
  console.log(" Service loaded and starting.");
30041
30336
  }
30042
30337
  function uninstallMacOS() {
30043
- if (!existsSync30(PLIST_PATH)) {
30338
+ if (!existsSync31(PLIST_PATH)) {
30044
30339
  console.log(" No service found to uninstall.");
30045
30340
  return;
30046
30341
  }
@@ -30115,8 +30410,8 @@ WantedBy=default.target
30115
30410
  `;
30116
30411
  }
30117
30412
  function installLinux() {
30118
- if (!existsSync30(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
30119
- if (!existsSync30(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30413
+ if (!existsSync31(SYSTEMD_DIR)) mkdirSync13(SYSTEMD_DIR, { recursive: true });
30414
+ if (!existsSync31(LOGS_PATH)) mkdirSync13(LOGS_PATH, { recursive: true });
30120
30415
  writeFileSync9(UNIT_PATH, generateUnit());
30121
30416
  console.log(` Installed: ${UNIT_PATH}`);
30122
30417
  execFileSync4("systemctl", ["--user", "daemon-reload"]);
@@ -30125,7 +30420,7 @@ function installLinux() {
30125
30420
  console.log(" Service enabled and started.");
30126
30421
  }
30127
30422
  function uninstallLinux() {
30128
- if (!existsSync30(UNIT_PATH)) {
30423
+ if (!existsSync31(UNIT_PATH)) {
30129
30424
  console.log(" No service found to uninstall.");
30130
30425
  return;
30131
30426
  }
@@ -30150,7 +30445,7 @@ function statusLinux() {
30150
30445
  }
30151
30446
  }
30152
30447
  function installService() {
30153
- if (!existsSync30(join32(CC_CLAW_HOME, ".env"))) {
30448
+ if (!existsSync31(join33(CC_CLAW_HOME, ".env"))) {
30154
30449
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
30155
30450
  console.error(" Run 'cc-claw setup' before installing the service.");
30156
30451
  process.exitCode = 1;
@@ -30179,9 +30474,9 @@ var init_service2 = __esm({
30179
30474
  "use strict";
30180
30475
  init_paths();
30181
30476
  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");
30477
+ PLIST_PATH = join33(homedir10(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
30478
+ SYSTEMD_DIR = join33(homedir10(), ".config", "systemd", "user");
30479
+ UNIT_PATH = join33(SYSTEMD_DIR, "cc-claw.service");
30185
30480
  }
30186
30481
  });
30187
30482
 
@@ -30348,13 +30643,13 @@ var init_daemon = __esm({
30348
30643
  });
30349
30644
 
30350
30645
  // src/cli/resolve-chat.ts
30351
- import { readFileSync as readFileSync20 } from "fs";
30646
+ import { readFileSync as readFileSync21 } from "fs";
30352
30647
  function resolveChatId2(globalOpts) {
30353
30648
  const explicit = globalOpts.chat;
30354
30649
  if (explicit) return explicit;
30355
30650
  if (_cachedDefault) return _cachedDefault;
30356
30651
  try {
30357
- const content = readFileSync20(ENV_PATH, "utf-8");
30652
+ const content = readFileSync21(ENV_PATH, "utf-8");
30358
30653
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
30359
30654
  if (match) {
30360
30655
  _cachedDefault = match[1].split(",")[0].trim();
@@ -30378,7 +30673,7 @@ var status_exports = {};
30378
30673
  __export(status_exports, {
30379
30674
  statusCommand: () => statusCommand
30380
30675
  });
30381
- import { existsSync as existsSync31, statSync as statSync10 } from "fs";
30676
+ import { existsSync as existsSync32, statSync as statSync10 } from "fs";
30382
30677
  async function statusCommand(globalOpts, localOpts) {
30383
30678
  try {
30384
30679
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
@@ -30418,7 +30713,7 @@ async function statusCommand(globalOpts, localOpts) {
30418
30713
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
30419
30714
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
30420
30715
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
30421
- const dbStat = existsSync31(DB_PATH) ? statSync10(DB_PATH) : null;
30716
+ const dbStat = existsSync32(DB_PATH) ? statSync10(DB_PATH) : null;
30422
30717
  let daemonRunning = false;
30423
30718
  let daemonInfo = {};
30424
30719
  try {
@@ -30530,13 +30825,13 @@ __export(doctor_exports, {
30530
30825
  doctorCommand: () => doctorCommand,
30531
30826
  doctorErrors: () => doctorErrors
30532
30827
  });
30533
- import { existsSync as existsSync32, accessSync, constants } from "fs";
30828
+ import { existsSync as existsSync33, accessSync, constants } from "fs";
30534
30829
  import { execFileSync as execFileSync5 } from "child_process";
30535
30830
  async function doctorCommand(globalOpts, localOpts) {
30536
30831
  const checks = [];
30537
30832
  const dbChecks = checkDatabase();
30538
30833
  checks.push(...dbChecks);
30539
- if (existsSync32(DB_PATH)) {
30834
+ if (existsSync33(DB_PATH)) {
30540
30835
  try {
30541
30836
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
30542
30837
  const readDb = openDatabaseReadOnly2();
@@ -30562,7 +30857,7 @@ async function doctorCommand(globalOpts, localOpts) {
30562
30857
  checks.push({ name: "Database health", status: "error", message: err.message });
30563
30858
  }
30564
30859
  }
30565
- if (existsSync32(ENV_PATH)) {
30860
+ if (existsSync33(ENV_PATH)) {
30566
30861
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
30567
30862
  } else {
30568
30863
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
@@ -30605,7 +30900,7 @@ async function doctorCommand(globalOpts, localOpts) {
30605
30900
  } catch {
30606
30901
  }
30607
30902
  const tokenPath = `${DATA_PATH}/api-token`;
30608
- if (existsSync32(tokenPath)) {
30903
+ if (existsSync33(tokenPath)) {
30609
30904
  try {
30610
30905
  accessSync(tokenPath, constants.R_OK);
30611
30906
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -30671,7 +30966,7 @@ async function doctorCommand(globalOpts, localOpts) {
30671
30966
  const errorChecks = checks.filter(
30672
30967
  (c) => ["Rate limits", "Content silence", "Spawn timeouts", "Other errors"].includes(c.name) && c.status !== "ok"
30673
30968
  );
30674
- if (errorChecks.length > 0 && existsSync32(ERROR_LOG_PATH)) {
30969
+ if (errorChecks.length > 0 && existsSync33(ERROR_LOG_PATH)) {
30675
30970
  try {
30676
30971
  const { writeFileSync: writeFileSync13 } = await import("fs");
30677
30972
  writeFileSync13(ERROR_LOG_PATH, "");
@@ -30800,15 +31095,15 @@ var logs_exports = {};
30800
31095
  __export(logs_exports, {
30801
31096
  logsCommand: () => logsCommand
30802
31097
  });
30803
- import { existsSync as existsSync33, readFileSync as readFileSync22, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
31098
+ import { existsSync as existsSync34, readFileSync as readFileSync23, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
30804
31099
  async function logsCommand(opts) {
30805
31100
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
30806
- if (!existsSync33(logFile)) {
31101
+ if (!existsSync34(logFile)) {
30807
31102
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
30808
31103
  process.exit(1);
30809
31104
  }
30810
31105
  const maxLines = parseInt(opts.lines ?? "100", 10);
30811
- const content = readFileSync22(logFile, "utf-8");
31106
+ const content = readFileSync23(logFile, "utf-8");
30812
31107
  const allLines = content.split("\n");
30813
31108
  const tailLines = allLines.slice(-maxLines);
30814
31109
  console.log(muted(` \u2500\u2500 ${logFile} (last ${tailLines.length} lines) \u2500\u2500`));
@@ -30818,7 +31113,7 @@ async function logsCommand(opts) {
30818
31113
  let lastLength = content.length;
30819
31114
  watchFile2(logFile, { interval: 500 }, () => {
30820
31115
  try {
30821
- const newContent = readFileSync22(logFile, "utf-8");
31116
+ const newContent = readFileSync23(logFile, "utf-8");
30822
31117
  if (newContent.length > lastLength) {
30823
31118
  const newPart = newContent.slice(lastLength);
30824
31119
  process.stdout.write(newPart);
@@ -30850,7 +31145,7 @@ __export(session_logs_exports, {
30850
31145
  sessionLogsList: () => sessionLogsList,
30851
31146
  sessionLogsTail: () => sessionLogsTail
30852
31147
  });
30853
- import { readFileSync as readFileSync23, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
31148
+ import { readFileSync as readFileSync24, watchFile as watchFile3, unwatchFile as unwatchFile3 } from "fs";
30854
31149
  async function sessionLogsList(opts) {
30855
31150
  const logs = listSessionLogs();
30856
31151
  if (logs.length === 0) {
@@ -30907,12 +31202,12 @@ async function sessionLogsTail(opts) {
30907
31202
  console.log(muted("\n Following... (Ctrl+C to stop)\n"));
30908
31203
  let lastLength = 0;
30909
31204
  try {
30910
- lastLength = readFileSync23(targetPath, "utf-8").length;
31205
+ lastLength = readFileSync24(targetPath, "utf-8").length;
30911
31206
  } catch {
30912
31207
  }
30913
31208
  watchFile3(targetPath, { interval: 500 }, () => {
30914
31209
  try {
30915
- const content = readFileSync23(targetPath, "utf-8");
31210
+ const content = readFileSync24(targetPath, "utf-8");
30916
31211
  if (content.length > lastLength) {
30917
31212
  process.stdout.write(content.slice(lastLength));
30918
31213
  lastLength = content.length;
@@ -30956,11 +31251,11 @@ __export(gemini_exports, {
30956
31251
  geminiReorder: () => geminiReorder,
30957
31252
  geminiRotation: () => geminiRotation
30958
31253
  });
30959
- import { existsSync as existsSync35, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync24, chmodSync } from "fs";
30960
- import { join as join33 } from "path";
31254
+ import { existsSync as existsSync36, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10, readFileSync as readFileSync25, chmodSync } from "fs";
31255
+ import { join as join34 } from "path";
30961
31256
  import { createInterface as createInterface8 } from "readline";
30962
31257
  function requireDb() {
30963
- if (!existsSync35(DB_PATH)) {
31258
+ if (!existsSync36(DB_PATH)) {
30964
31259
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
30965
31260
  process.exit(1);
30966
31261
  }
@@ -30985,9 +31280,9 @@ async function resolveSlotId(idOrLabel) {
30985
31280
  function resolveOAuthEmail(configHome) {
30986
31281
  if (!configHome) return null;
30987
31282
  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"));
31283
+ const accountsPath = join34(configHome, ".gemini", "google_accounts.json");
31284
+ if (!existsSync36(accountsPath)) return null;
31285
+ const accounts = JSON.parse(readFileSync25(accountsPath, "utf-8"));
30991
31286
  return accounts.active || null;
30992
31287
  } catch {
30993
31288
  return null;
@@ -31069,14 +31364,14 @@ async function geminiAddKey(globalOpts, opts) {
31069
31364
  }
31070
31365
  async function geminiAddAccount(globalOpts, opts) {
31071
31366
  await requireWriteDb();
31072
- const slotsDir = join33(CC_CLAW_HOME, "gemini-slots");
31073
- if (!existsSync35(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
31367
+ const slotsDir = join34(CC_CLAW_HOME, "gemini-slots");
31368
+ if (!existsSync36(slotsDir)) mkdirSync14(slotsDir, { recursive: true });
31074
31369
  const { addGeminiSlot: addGeminiSlot2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
31075
31370
  const tempId = Date.now();
31076
- const slotDir = join33(slotsDir, `slot-${tempId}`);
31371
+ const slotDir = join34(slotsDir, `slot-${tempId}`);
31077
31372
  mkdirSync14(slotDir, { recursive: true, mode: 448 });
31078
- mkdirSync14(join33(slotDir, ".gemini"), { recursive: true });
31079
- writeFileSync10(join33(slotDir, ".gemini", "settings.json"), JSON.stringify({
31373
+ mkdirSync14(join34(slotDir, ".gemini"), { recursive: true });
31374
+ writeFileSync10(join34(slotDir, ".gemini", "settings.json"), JSON.stringify({
31080
31375
  security: { auth: { selectedType: "oauth-personal" } }
31081
31376
  }, null, 2));
31082
31377
  console.log("");
@@ -31093,8 +31388,8 @@ async function geminiAddAccount(globalOpts, opts) {
31093
31388
  });
31094
31389
  } catch {
31095
31390
  }
31096
- const oauthPath = join33(slotDir, ".gemini", "oauth_creds.json");
31097
- if (!existsSync35(oauthPath)) {
31391
+ const oauthPath = join34(slotDir, ".gemini", "oauth_creds.json");
31392
+ if (!existsSync36(oauthPath)) {
31098
31393
  console.log(error2("\n No OAuth credentials found. Sign-in may have failed."));
31099
31394
  console.log(" The slot directory is preserved at: " + slotDir);
31100
31395
  console.log(" Re-run: cc-claw gemini add-account\n");
@@ -31102,7 +31397,7 @@ async function geminiAddAccount(globalOpts, opts) {
31102
31397
  }
31103
31398
  let accountEmail = "unknown";
31104
31399
  try {
31105
- const accounts = JSON.parse(__require("fs").readFileSync(join33(slotDir, ".gemini", "google_accounts.json"), "utf-8"));
31400
+ const accounts = JSON.parse(__require("fs").readFileSync(join34(slotDir, ".gemini", "google_accounts.json"), "utf-8"));
31106
31401
  accountEmail = accounts.active || accountEmail;
31107
31402
  } catch {
31108
31403
  }
@@ -31213,9 +31508,9 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31213
31508
  outputError("NO_CONFIG", `Slot "${idOrLabel}" has no config directory \u2014 cannot re-login.`);
31214
31509
  return;
31215
31510
  }
31216
- const settingsPath = join33(slot.configHome, ".gemini", "settings.json");
31217
- if (!existsSync35(settingsPath)) {
31218
- mkdirSync14(join33(slot.configHome, ".gemini"), { recursive: true });
31511
+ const settingsPath = join34(slot.configHome, ".gemini", "settings.json");
31512
+ if (!existsSync36(settingsPath)) {
31513
+ mkdirSync14(join34(slot.configHome, ".gemini"), { recursive: true });
31219
31514
  writeFileSync10(settingsPath, JSON.stringify({
31220
31515
  security: { auth: { selectedType: "oauth-personal" } }
31221
31516
  }, null, 2));
@@ -31239,8 +31534,8 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31239
31534
  });
31240
31535
  } catch {
31241
31536
  }
31242
- const oauthPath = join33(slot.configHome, ".gemini", "oauth_creds.json");
31243
- if (!existsSync35(oauthPath)) {
31537
+ const oauthPath = join34(slot.configHome, ".gemini", "oauth_creds.json");
31538
+ if (!existsSync36(oauthPath)) {
31244
31539
  console.log(error2("\n Re-login failed \u2014 no OAuth credentials found."));
31245
31540
  console.log(` Try again: cc-claw gemini re-login ${idOrLabel}
31246
31541
  `);
@@ -31250,7 +31545,7 @@ async function geminiRelogin(globalOpts, idOrLabel) {
31250
31545
  setGeminiSlotEnabled2(slotId, true);
31251
31546
  let accountEmail = slot.label;
31252
31547
  try {
31253
- const accounts = JSON.parse(readFileSync24(join33(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
31548
+ const accounts = JSON.parse(readFileSync25(join34(slot.configHome, ".gemini", "google_accounts.json"), "utf-8"));
31254
31549
  if (accounts.active) accountEmail = accounts.active;
31255
31550
  } catch {
31256
31551
  }
@@ -31309,11 +31604,11 @@ __export(backend_cmd_factory_exports, {
31309
31604
  makeReorder: () => makeReorder,
31310
31605
  registerBackendSlotCommands: () => registerBackendSlotCommands
31311
31606
  });
31312
- import { existsSync as existsSync36, mkdirSync as mkdirSync15, readFileSync as readFileSync25 } from "fs";
31313
- import { join as join34 } from "path";
31607
+ import { existsSync as existsSync37, mkdirSync as mkdirSync15, readFileSync as readFileSync26 } from "fs";
31608
+ import { join as join35 } from "path";
31314
31609
  import { createInterface as createInterface9 } from "readline";
31315
31610
  function requireDb2() {
31316
- if (!existsSync36(DB_PATH)) {
31611
+ if (!existsSync37(DB_PATH)) {
31317
31612
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31318
31613
  process.exit(1);
31319
31614
  }
@@ -31402,10 +31697,10 @@ function makeAddAccount(backend2, displayName) {
31402
31697
  process.exit(1);
31403
31698
  }
31404
31699
  await requireWriteDb2();
31405
- const slotsDir = join34(CC_CLAW_HOME, config2.slotsSubdir);
31406
- if (!existsSync36(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
31700
+ const slotsDir = join35(CC_CLAW_HOME, config2.slotsSubdir);
31701
+ if (!existsSync37(slotsDir)) mkdirSync15(slotsDir, { recursive: true });
31407
31702
  const tempId = Date.now();
31408
- const slotDir = join34(slotsDir, `slot-${tempId}`);
31703
+ const slotDir = join35(slotsDir, `slot-${tempId}`);
31409
31704
  mkdirSync15(slotDir, { recursive: true, mode: 448 });
31410
31705
  if (config2.preSetup) config2.preSetup(slotDir);
31411
31706
  console.log("");
@@ -31656,22 +31951,22 @@ var init_backend_cmd_factory = __esm({
31656
31951
  envValue: (slotDir) => slotDir,
31657
31952
  envOverrides: { ANTHROPIC_API_KEY: void 0 },
31658
31953
  preSetup: (slotDir) => {
31659
- mkdirSync15(join34(slotDir, ".claude"), { recursive: true });
31954
+ mkdirSync15(join35(slotDir, ".claude"), { recursive: true });
31660
31955
  },
31661
31956
  verifyCredentials: (slotDir) => {
31662
- const claudeJson = join34(slotDir, ".claude.json");
31663
- const claudeJsonNested = join34(slotDir, ".claude", ".claude.json");
31664
- if (existsSync36(claudeJson)) {
31957
+ const claudeJson = join35(slotDir, ".claude.json");
31958
+ const claudeJsonNested = join35(slotDir, ".claude", ".claude.json");
31959
+ if (existsSync37(claudeJson)) {
31665
31960
  try {
31666
- const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
31961
+ const data = JSON.parse(readFileSync26(claudeJson, "utf-8"));
31667
31962
  return Boolean(data.oauthAccount);
31668
31963
  } catch {
31669
31964
  return false;
31670
31965
  }
31671
31966
  }
31672
- if (existsSync36(claudeJsonNested)) {
31967
+ if (existsSync37(claudeJsonNested)) {
31673
31968
  try {
31674
- const data = JSON.parse(readFileSync25(claudeJsonNested, "utf-8"));
31969
+ const data = JSON.parse(readFileSync26(claudeJsonNested, "utf-8"));
31675
31970
  return Boolean(data.oauthAccount);
31676
31971
  } catch {
31677
31972
  return false;
@@ -31692,9 +31987,9 @@ var init_backend_cmd_factory = __esm({
31692
31987
  } catch {
31693
31988
  }
31694
31989
  try {
31695
- const claudeJson = join34(slotDir, ".claude.json");
31696
- if (existsSync36(claudeJson)) {
31697
- const data = JSON.parse(readFileSync25(claudeJson, "utf-8"));
31990
+ const claudeJson = join35(slotDir, ".claude.json");
31991
+ if (existsSync37(claudeJson)) {
31992
+ const data = JSON.parse(readFileSync26(claudeJson, "utf-8"));
31698
31993
  if (data.oauthAccount?.emailAddress) return data.oauthAccount.emailAddress;
31699
31994
  }
31700
31995
  } catch {
@@ -31709,11 +32004,11 @@ var init_backend_cmd_factory = __esm({
31709
32004
  envValue: (slotDir) => slotDir,
31710
32005
  envOverrides: { OPENAI_API_KEY: void 0 },
31711
32006
  verifyCredentials: (slotDir) => {
31712
- return existsSync36(join34(slotDir, "auth.json"));
32007
+ return existsSync37(join35(slotDir, "auth.json"));
31713
32008
  },
31714
32009
  extractLabel: (slotDir) => {
31715
32010
  try {
31716
- const authData = JSON.parse(readFileSync25(join34(slotDir, "auth.json"), "utf-8"));
32011
+ const authData = JSON.parse(readFileSync26(join35(slotDir, "auth.json"), "utf-8"));
31717
32012
  if (authData.email) return authData.email;
31718
32013
  if (authData.account_name) return authData.account_name;
31719
32014
  if (authData.user?.email) return authData.user.email;
@@ -31737,9 +32032,9 @@ __export(ollama_exports3, {
31737
32032
  ollamaRemove: () => ollamaRemove,
31738
32033
  ollamaTest: () => ollamaTest
31739
32034
  });
31740
- import { existsSync as existsSync37 } from "fs";
32035
+ import { existsSync as existsSync38 } from "fs";
31741
32036
  function requireDb3() {
31742
- if (!existsSync37(DB_PATH)) {
32037
+ if (!existsSync38(DB_PATH)) {
31743
32038
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
31744
32039
  process.exit(1);
31745
32040
  }
@@ -31998,12 +32293,12 @@ __export(backend_exports, {
31998
32293
  backendList: () => backendList,
31999
32294
  backendSet: () => backendSet
32000
32295
  });
32001
- import { existsSync as existsSync38 } from "fs";
32296
+ import { existsSync as existsSync39 } from "fs";
32002
32297
  async function backendList(globalOpts) {
32003
32298
  const { getAvailableAdapters: getAvailableAdapters3 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
32004
32299
  const chatId = resolveChatId2(globalOpts);
32005
32300
  let activeBackend = null;
32006
- if (existsSync38(DB_PATH)) {
32301
+ if (existsSync39(DB_PATH)) {
32007
32302
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32008
32303
  const readDb = openDatabaseReadOnly2();
32009
32304
  try {
@@ -32034,7 +32329,7 @@ async function backendList(globalOpts) {
32034
32329
  }
32035
32330
  async function backendGet(globalOpts) {
32036
32331
  const chatId = resolveChatId2(globalOpts);
32037
- if (!existsSync38(DB_PATH)) {
32332
+ if (!existsSync39(DB_PATH)) {
32038
32333
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32039
32334
  process.exit(1);
32040
32335
  }
@@ -32078,13 +32373,13 @@ __export(model_exports, {
32078
32373
  modelList: () => modelList,
32079
32374
  modelSet: () => modelSet
32080
32375
  });
32081
- import { existsSync as existsSync39 } from "fs";
32376
+ import { existsSync as existsSync40 } from "fs";
32082
32377
  async function modelList(globalOpts) {
32083
32378
  const chatId = resolveChatId2(globalOpts);
32084
32379
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32085
32380
  const { getAdapter: getAdapter4, getAllAdapters: getAllAdapters5 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
32086
32381
  let backendId = "claude";
32087
- if (existsSync39(DB_PATH)) {
32382
+ if (existsSync40(DB_PATH)) {
32088
32383
  const readDb = openDatabaseReadOnly2();
32089
32384
  try {
32090
32385
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -32117,7 +32412,7 @@ async function modelList(globalOpts) {
32117
32412
  }
32118
32413
  async function modelGet(globalOpts) {
32119
32414
  const chatId = resolveChatId2(globalOpts);
32120
- if (!existsSync39(DB_PATH)) {
32415
+ if (!existsSync40(DB_PATH)) {
32121
32416
  outputError("DB_NOT_FOUND", "Database not found.");
32122
32417
  process.exit(1);
32123
32418
  }
@@ -32161,9 +32456,9 @@ __export(memory_exports2, {
32161
32456
  memoryList: () => memoryList,
32162
32457
  memorySearch: () => memorySearch
32163
32458
  });
32164
- import { existsSync as existsSync40 } from "fs";
32459
+ import { existsSync as existsSync41 } from "fs";
32165
32460
  async function memoryList(globalOpts) {
32166
- if (!existsSync40(DB_PATH)) {
32461
+ if (!existsSync41(DB_PATH)) {
32167
32462
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32168
32463
  process.exit(1);
32169
32464
  }
@@ -32187,7 +32482,7 @@ async function memoryList(globalOpts) {
32187
32482
  });
32188
32483
  }
32189
32484
  async function memorySearch(globalOpts, query) {
32190
- if (!existsSync40(DB_PATH)) {
32485
+ if (!existsSync41(DB_PATH)) {
32191
32486
  outputError("DB_NOT_FOUND", "Database not found.");
32192
32487
  process.exit(1);
32193
32488
  }
@@ -32209,7 +32504,7 @@ async function memorySearch(globalOpts, query) {
32209
32504
  });
32210
32505
  }
32211
32506
  async function memoryHistory(globalOpts, opts) {
32212
- if (!existsSync40(DB_PATH)) {
32507
+ if (!existsSync41(DB_PATH)) {
32213
32508
  outputError("DB_NOT_FOUND", "Database not found.");
32214
32509
  process.exit(1);
32215
32510
  }
@@ -32257,7 +32552,7 @@ __export(cron_exports2, {
32257
32552
  cronList: () => cronList,
32258
32553
  cronRuns: () => cronRuns
32259
32554
  });
32260
- import { existsSync as existsSync41 } from "fs";
32555
+ import { existsSync as existsSync42 } from "fs";
32261
32556
  function parseFallbacks(raw) {
32262
32557
  return raw.slice(0, 3).map((f) => {
32263
32558
  const [backend2, ...rest] = f.split(":");
@@ -32278,7 +32573,7 @@ function parseAndValidateTimeout(raw) {
32278
32573
  return val;
32279
32574
  }
32280
32575
  async function cronList(globalOpts) {
32281
- if (!existsSync41(DB_PATH)) {
32576
+ if (!existsSync42(DB_PATH)) {
32282
32577
  outputError("DB_NOT_FOUND", "Database not found.");
32283
32578
  process.exit(1);
32284
32579
  }
@@ -32316,7 +32611,7 @@ async function cronList(globalOpts) {
32316
32611
  });
32317
32612
  }
32318
32613
  async function cronHealth(globalOpts) {
32319
- if (!existsSync41(DB_PATH)) {
32614
+ if (!existsSync42(DB_PATH)) {
32320
32615
  outputError("DB_NOT_FOUND", "Database not found.");
32321
32616
  process.exit(1);
32322
32617
  }
@@ -32457,6 +32752,29 @@ async function cronEdit(globalOpts, id, opts) {
32457
32752
  if (opts.fallback?.length) {
32458
32753
  payload.fallbacks = parseFallbacks(opts.fallback);
32459
32754
  }
32755
+ if (opts.account !== void 0) {
32756
+ const accountVal = opts.account;
32757
+ if (accountVal === "auto" || accountVal === "none" || accountVal === "0") {
32758
+ payload.credentialSlotId = null;
32759
+ } else if (/^\d+$/.test(accountVal)) {
32760
+ payload.credentialSlotId = parseInt(accountVal, 10);
32761
+ } else {
32762
+ const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
32763
+ const readDb = openDatabaseReadOnly2();
32764
+ const geminiSlot = readDb.prepare(
32765
+ "SELECT id, label FROM gemini_credentials WHERE label = ? OR CAST(id AS TEXT) = ?"
32766
+ ).get(accountVal, accountVal);
32767
+ const backendSlot = !geminiSlot ? readDb.prepare(
32768
+ "SELECT id, label FROM backend_credentials WHERE (label = ? OR CAST(id AS TEXT) = ?)"
32769
+ ).get(accountVal, accountVal) : void 0;
32770
+ const resolved = geminiSlot ?? backendSlot;
32771
+ if (!resolved) {
32772
+ outputError("ACCOUNT_NOT_FOUND", `No credential slot found with label or ID "${accountVal}". Run cc-claw gemini list or cc-claw claude list to see available slots.`);
32773
+ process.exit(1);
32774
+ }
32775
+ payload.credentialSlotId = resolved.id;
32776
+ }
32777
+ }
32460
32778
  const fieldCount = Object.keys(payload).length - 1;
32461
32779
  if (fieldCount === 0) {
32462
32780
  outputError("NO_CHANGES", "No fields to update. Specify fields with flags (e.g. --description, --cron).");
@@ -32477,7 +32795,7 @@ async function cronEdit(globalOpts, id, opts) {
32477
32795
  }
32478
32796
  }
32479
32797
  async function cronRuns(globalOpts, jobId, opts) {
32480
- if (!existsSync41(DB_PATH)) {
32798
+ if (!existsSync42(DB_PATH)) {
32481
32799
  outputError("DB_NOT_FOUND", "Database not found.");
32482
32800
  process.exit(1);
32483
32801
  }
@@ -32524,9 +32842,9 @@ __export(agents_exports, {
32524
32842
  runnersList: () => runnersList,
32525
32843
  tasksList: () => tasksList
32526
32844
  });
32527
- import { existsSync as existsSync42 } from "fs";
32845
+ import { existsSync as existsSync43 } from "fs";
32528
32846
  async function agentsList(globalOpts) {
32529
- if (!existsSync42(DB_PATH)) {
32847
+ if (!existsSync43(DB_PATH)) {
32530
32848
  outputError("DB_NOT_FOUND", "Database not found.");
32531
32849
  process.exit(1);
32532
32850
  }
@@ -32557,7 +32875,7 @@ async function agentsList(globalOpts) {
32557
32875
  });
32558
32876
  }
32559
32877
  async function tasksList(globalOpts) {
32560
- if (!existsSync42(DB_PATH)) {
32878
+ if (!existsSync43(DB_PATH)) {
32561
32879
  outputError("DB_NOT_FOUND", "Database not found.");
32562
32880
  process.exit(1);
32563
32881
  }
@@ -32685,10 +33003,10 @@ __export(db_exports, {
32685
33003
  dbPath: () => dbPath,
32686
33004
  dbStats: () => dbStats
32687
33005
  });
32688
- import { existsSync as existsSync43, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
33006
+ import { existsSync as existsSync44, statSync as statSync11, copyFileSync as copyFileSync3, mkdirSync as mkdirSync16 } from "fs";
32689
33007
  import { dirname as dirname7 } from "path";
32690
33008
  async function dbStats(globalOpts) {
32691
- if (!existsSync43(DB_PATH)) {
33009
+ if (!existsSync44(DB_PATH)) {
32692
33010
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
32693
33011
  process.exit(1);
32694
33012
  }
@@ -32696,7 +33014,7 @@ async function dbStats(globalOpts) {
32696
33014
  const readDb = openDatabaseReadOnly2();
32697
33015
  const mainSize = statSync11(DB_PATH).size;
32698
33016
  const walPath = DB_PATH + "-wal";
32699
- const walSize = existsSync43(walPath) ? statSync11(walPath).size : 0;
33017
+ const walSize = existsSync44(walPath) ? statSync11(walPath).size : 0;
32700
33018
  const tableNames = readDb.prepare(
32701
33019
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
32702
33020
  ).all();
@@ -32730,7 +33048,7 @@ async function dbPath(globalOpts) {
32730
33048
  output({ path: DB_PATH }, (d) => d.path);
32731
33049
  }
32732
33050
  async function dbBackup(globalOpts, destPath) {
32733
- if (!existsSync43(DB_PATH)) {
33051
+ if (!existsSync44(DB_PATH)) {
32734
33052
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
32735
33053
  process.exit(1);
32736
33054
  }
@@ -32739,7 +33057,7 @@ async function dbBackup(globalOpts, destPath) {
32739
33057
  mkdirSync16(dirname7(dest), { recursive: true });
32740
33058
  copyFileSync3(DB_PATH, dest);
32741
33059
  const walPath = DB_PATH + "-wal";
32742
- if (existsSync43(walPath)) copyFileSync3(walPath, dest + "-wal");
33060
+ if (existsSync44(walPath)) copyFileSync3(walPath, dest + "-wal");
32743
33061
  output({ path: dest, sizeBytes: statSync11(dest).size }, (d) => {
32744
33062
  const b = d;
32745
33063
  return `
@@ -32768,9 +33086,9 @@ __export(usage_exports, {
32768
33086
  usageCost: () => usageCost,
32769
33087
  usageTokens: () => usageTokens
32770
33088
  });
32771
- import { existsSync as existsSync44 } from "fs";
33089
+ import { existsSync as existsSync45 } from "fs";
32772
33090
  function ensureDb() {
32773
- if (!existsSync44(DB_PATH)) {
33091
+ if (!existsSync45(DB_PATH)) {
32774
33092
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
32775
33093
  process.exit(1);
32776
33094
  }
@@ -32960,9 +33278,9 @@ __export(config_exports2, {
32960
33278
  configList: () => configList,
32961
33279
  configSet: () => configSet
32962
33280
  });
32963
- import { existsSync as existsSync45, readFileSync as readFileSync26 } from "fs";
33281
+ import { existsSync as existsSync46, readFileSync as readFileSync27 } from "fs";
32964
33282
  async function configList(globalOpts) {
32965
- if (!existsSync45(DB_PATH)) {
33283
+ if (!existsSync46(DB_PATH)) {
32966
33284
  outputError("DB_NOT_FOUND", "Database not found.");
32967
33285
  process.exit(1);
32968
33286
  }
@@ -32996,7 +33314,7 @@ async function configGet(globalOpts, key) {
32996
33314
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
32997
33315
  process.exit(1);
32998
33316
  }
32999
- if (!existsSync45(DB_PATH)) {
33317
+ if (!existsSync46(DB_PATH)) {
33000
33318
  outputError("DB_NOT_FOUND", "Database not found.");
33001
33319
  process.exit(1);
33002
33320
  }
@@ -33042,11 +33360,11 @@ async function configSet(globalOpts, key, value) {
33042
33360
  }
33043
33361
  }
33044
33362
  async function configEnv(_globalOpts) {
33045
- if (!existsSync45(ENV_PATH)) {
33363
+ if (!existsSync46(ENV_PATH)) {
33046
33364
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
33047
33365
  process.exit(1);
33048
33366
  }
33049
- const content = readFileSync26(ENV_PATH, "utf-8");
33367
+ const content = readFileSync27(ENV_PATH, "utf-8");
33050
33368
  const entries = {};
33051
33369
  const secretPatterns = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIALS/i;
33052
33370
  for (const line of content.split("\n")) {
@@ -33096,9 +33414,9 @@ __export(session_exports, {
33096
33414
  sessionGet: () => sessionGet,
33097
33415
  sessionNew: () => sessionNew
33098
33416
  });
33099
- import { existsSync as existsSync46 } from "fs";
33417
+ import { existsSync as existsSync47 } from "fs";
33100
33418
  async function sessionGet(globalOpts) {
33101
- if (!existsSync46(DB_PATH)) {
33419
+ if (!existsSync47(DB_PATH)) {
33102
33420
  outputError("DB_NOT_FOUND", "Database not found.");
33103
33421
  process.exit(1);
33104
33422
  }
@@ -33159,9 +33477,9 @@ __export(permissions_exports, {
33159
33477
  verboseGet: () => verboseGet,
33160
33478
  verboseSet: () => verboseSet
33161
33479
  });
33162
- import { existsSync as existsSync47 } from "fs";
33480
+ import { existsSync as existsSync48 } from "fs";
33163
33481
  function ensureDb2() {
33164
- if (!existsSync47(DB_PATH)) {
33482
+ if (!existsSync48(DB_PATH)) {
33165
33483
  outputError("DB_NOT_FOUND", "Database not found.");
33166
33484
  process.exit(1);
33167
33485
  }
@@ -33308,9 +33626,9 @@ __export(cwd_exports, {
33308
33626
  cwdGet: () => cwdGet,
33309
33627
  cwdSet: () => cwdSet
33310
33628
  });
33311
- import { existsSync as existsSync48 } from "fs";
33629
+ import { existsSync as existsSync49 } from "fs";
33312
33630
  async function cwdGet(globalOpts) {
33313
- if (!existsSync48(DB_PATH)) {
33631
+ if (!existsSync49(DB_PATH)) {
33314
33632
  outputError("DB_NOT_FOUND", "Database not found.");
33315
33633
  process.exit(1);
33316
33634
  }
@@ -33372,9 +33690,9 @@ __export(voice_exports, {
33372
33690
  voiceGet: () => voiceGet,
33373
33691
  voiceSet: () => voiceSet
33374
33692
  });
33375
- import { existsSync as existsSync49 } from "fs";
33693
+ import { existsSync as existsSync50 } from "fs";
33376
33694
  async function voiceGet(globalOpts) {
33377
- if (!existsSync49(DB_PATH)) {
33695
+ if (!existsSync50(DB_PATH)) {
33378
33696
  outputError("DB_NOT_FOUND", "Database not found.");
33379
33697
  process.exit(1);
33380
33698
  }
@@ -33423,9 +33741,9 @@ __export(heartbeat_exports2, {
33423
33741
  heartbeatGet: () => heartbeatGet,
33424
33742
  heartbeatSet: () => heartbeatSet
33425
33743
  });
33426
- import { existsSync as existsSync50 } from "fs";
33744
+ import { existsSync as existsSync51 } from "fs";
33427
33745
  async function heartbeatGet(globalOpts) {
33428
- if (!existsSync50(DB_PATH)) {
33746
+ if (!existsSync51(DB_PATH)) {
33429
33747
  outputError("DB_NOT_FOUND", "Database not found.");
33430
33748
  process.exit(1);
33431
33749
  }
@@ -33636,9 +33954,9 @@ __export(summarizer_exports, {
33636
33954
  summarizerGet: () => summarizerGet,
33637
33955
  summarizerSet: () => summarizerSet
33638
33956
  });
33639
- import { existsSync as existsSync51 } from "fs";
33957
+ import { existsSync as existsSync52 } from "fs";
33640
33958
  async function summarizerGet(globalOpts) {
33641
- if (!existsSync51(DB_PATH)) {
33959
+ if (!existsSync52(DB_PATH)) {
33642
33960
  outputError("DB_NOT_FOUND", "Database not found.");
33643
33961
  process.exit(1);
33644
33962
  }
@@ -33682,9 +34000,9 @@ __export(thinking_exports, {
33682
34000
  thinkingGet: () => thinkingGet,
33683
34001
  thinkingSet: () => thinkingSet
33684
34002
  });
33685
- import { existsSync as existsSync52 } from "fs";
34003
+ import { existsSync as existsSync53 } from "fs";
33686
34004
  async function thinkingGet(globalOpts) {
33687
- if (!existsSync52(DB_PATH)) {
34005
+ if (!existsSync53(DB_PATH)) {
33688
34006
  outputError("DB_NOT_FOUND", "Database not found.");
33689
34007
  process.exit(1);
33690
34008
  }
@@ -33728,9 +34046,9 @@ __export(chats_exports, {
33728
34046
  chatsList: () => chatsList,
33729
34047
  chatsRemoveAlias: () => chatsRemoveAlias
33730
34048
  });
33731
- import { existsSync as existsSync53 } from "fs";
34049
+ import { existsSync as existsSync54 } from "fs";
33732
34050
  async function chatsList(_globalOpts) {
33733
- if (!existsSync53(DB_PATH)) {
34051
+ if (!existsSync54(DB_PATH)) {
33734
34052
  outputError("DB_NOT_FOUND", "Database not found.");
33735
34053
  process.exit(1);
33736
34054
  }
@@ -33858,9 +34176,9 @@ var mcps_exports2 = {};
33858
34176
  __export(mcps_exports2, {
33859
34177
  mcpsList: () => mcpsList
33860
34178
  });
33861
- import { existsSync as existsSync54 } from "fs";
34179
+ import { existsSync as existsSync55 } from "fs";
33862
34180
  async function mcpsList(_globalOpts) {
33863
- if (!existsSync54(DB_PATH)) {
34181
+ if (!existsSync55(DB_PATH)) {
33864
34182
  outputError("DB_NOT_FOUND", "Database not found.");
33865
34183
  process.exit(1);
33866
34184
  }
@@ -33897,11 +34215,11 @@ __export(chat_exports2, {
33897
34215
  chatSend: () => chatSend
33898
34216
  });
33899
34217
  import { request as httpRequest2 } from "http";
33900
- import { readFileSync as readFileSync27, existsSync as existsSync55 } from "fs";
34218
+ import { readFileSync as readFileSync28, existsSync as existsSync56 } from "fs";
33901
34219
  function getToken2() {
33902
34220
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
33903
34221
  try {
33904
- if (existsSync55(TOKEN_PATH2)) return readFileSync27(TOKEN_PATH2, "utf-8").trim();
34222
+ if (existsSync56(TOKEN_PATH2)) return readFileSync28(TOKEN_PATH2, "utf-8").trim();
33905
34223
  } catch {
33906
34224
  }
33907
34225
  return null;
@@ -34389,7 +34707,7 @@ __export(completion_exports, {
34389
34707
  completionCommand: () => completionCommand
34390
34708
  });
34391
34709
  import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync17 } from "fs";
34392
- import { join as join35 } from "path";
34710
+ import { join as join36 } from "path";
34393
34711
  import { homedir as homedir11 } from "os";
34394
34712
  async function completionCommand(opts) {
34395
34713
  const shell = opts.shell ?? detectShell();
@@ -34405,10 +34723,10 @@ async function completionCommand(opts) {
34405
34723
  process.exit(1);
34406
34724
  }
34407
34725
  if (opts.install) {
34408
- const dir = join35(homedir11(), ".config", "cc-claw", "completions");
34726
+ const dir = join36(homedir11(), ".config", "cc-claw", "completions");
34409
34727
  mkdirSync17(dir, { recursive: true });
34410
34728
  const filename = shell === "zsh" ? "_cc-claw" : shell === "fish" ? "cc-claw.fish" : "cc-claw.bash";
34411
- const filepath = join35(dir, filename);
34729
+ const filepath = join36(dir, filename);
34412
34730
  writeFileSync11(filepath, script, "utf-8");
34413
34731
  console.log(`\u2713 Completion script written to ${filepath}
34414
34732
  `);
@@ -34581,9 +34899,9 @@ __export(evolve_exports2, {
34581
34899
  evolveStatus: () => evolveStatus,
34582
34900
  evolveUndo: () => evolveUndo
34583
34901
  });
34584
- import { existsSync as existsSync56 } from "fs";
34902
+ import { existsSync as existsSync57 } from "fs";
34585
34903
  function ensureDb3() {
34586
- if (!existsSync56(DB_PATH)) {
34904
+ if (!existsSync57(DB_PATH)) {
34587
34905
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
34588
34906
  process.exit(1);
34589
34907
  }
@@ -35057,10 +35375,10 @@ var init_optimize2 = __esm({
35057
35375
 
35058
35376
  // src/setup.ts
35059
35377
  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";
35378
+ import { existsSync as existsSync58, writeFileSync as writeFileSync12, readFileSync as readFileSync29, copyFileSync as copyFileSync4, mkdirSync as mkdirSync18, statSync as statSync12 } from "fs";
35061
35379
  import { execFileSync as execFileSync6 } from "child_process";
35062
35380
  import { createInterface as createInterface11 } from "readline";
35063
- import { join as join36 } from "path";
35381
+ import { join as join37 } from "path";
35064
35382
  function divider2() {
35065
35383
  console.log(dim("\u2500".repeat(55)));
35066
35384
  }
@@ -35135,21 +35453,21 @@ async function setup() {
35135
35453
  }
35136
35454
  console.log("");
35137
35455
  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 });
35456
+ if (!existsSync58(dir)) mkdirSync18(dir, { recursive: true });
35139
35457
  }
35140
35458
  const env = {};
35141
- const envSource = existsSync57(ENV_PATH) ? ENV_PATH : existsSync57(".env") ? ".env" : null;
35459
+ const envSource = existsSync58(ENV_PATH) ? ENV_PATH : existsSync58(".env") ? ".env" : null;
35142
35460
  if (envSource) {
35143
35461
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
35144
35462
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
35145
- const existing = readFileSync28(envSource, "utf-8");
35463
+ const existing = readFileSync29(envSource, "utf-8");
35146
35464
  for (const line of existing.split("\n")) {
35147
35465
  const match = line.match(/^([^#=]+)=(.*)$/);
35148
35466
  if (match) env[match[1].trim()] = match[2].trim();
35149
35467
  }
35150
35468
  }
35151
- const cwdDb = join36(process.cwd(), "cc-claw.db");
35152
- if (existsSync57(cwdDb) && !existsSync57(DB_PATH)) {
35469
+ const cwdDb = join37(process.cwd(), "cc-claw.db");
35470
+ if (existsSync58(cwdDb) && !existsSync58(DB_PATH)) {
35153
35471
  const { size } = statSync12(cwdDb);
35154
35472
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
35155
35473
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
@@ -35289,14 +35607,44 @@ async function setup() {
35289
35607
  }
35290
35608
  header(4, TOTAL_STEPS, "Optional Features");
35291
35609
  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)")) {
35610
+ if (await confirm("Enable voice message transcription (speech-to-text)?")) {
35293
35611
  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!"));
35612
+ console.log(dim(" Choose your transcription provider:\n"));
35613
+ console.log(" 1. Local Whisper \u2014 free, works offline, runs on your machine");
35614
+ console.log(" Requires: whisper-cli (brew install whisper-cpp on macOS)");
35615
+ console.log(" Models downloaded on first use (~75MB\u20131.5GB depending on quality)");
35298
35616
  console.log("");
35299
- console.log(dim(" Choose a voice reply provider:"));
35617
+ console.log(" 2. Groq \u2014 free cloud API, fast, no local install needed");
35618
+ console.log(" Requires: free Groq API key from https://console.groq.com/keys");
35619
+ console.log("");
35620
+ console.log(" 3. Skip");
35621
+ console.log("");
35622
+ const sttChoice = await requiredInput("Enter choice (1/2/3)", "1");
35623
+ if (sttChoice === "1") {
35624
+ console.log("");
35625
+ console.log(dim(" Local Whisper model to use (select quality vs download size):"));
35626
+ console.log(" 1. tiny.en 75MB English only ~0.3s Basic");
35627
+ console.log(" 2. base.en 150MB English only ~0.8s Good");
35628
+ console.log(" 3. small.en 500MB English only ~1.5s Very good \u2B50 Recommended");
35629
+ console.log(" 4. small 500MB Multilingual ~2s Very good");
35630
+ console.log(" 5. medium.en 1.5GB English only ~5s Excellent");
35631
+ console.log(" 6. medium 1.5GB Multilingual ~6s Excellent");
35632
+ console.log("");
35633
+ const modelChoice = await requiredInput("Enter choice (1\u20136)", "3");
35634
+ const modelMap = { "1": "tiny.en", "2": "base.en", "3": "small.en", "4": "small", "5": "medium.en", "6": "medium" };
35635
+ env.STT_PROVIDER = "local-whisper";
35636
+ env.STT_MODEL = modelMap[modelChoice] ?? "small.en";
35637
+ console.log(green(` Local Whisper selected (${env.STT_MODEL}). Model will download on first voice message.`));
35638
+ } else if (sttChoice === "2") {
35639
+ const groqKey = await requiredInput("Groq API key", env.GROQ_API_KEY);
35640
+ env.GROQ_API_KEY = groqKey;
35641
+ env.STT_PROVIDER = "groq";
35642
+ console.log(green(" Groq transcription enabled!"));
35643
+ } else {
35644
+ console.log(dim(" Voice transcription skipped. Configure later via /voice in Telegram."));
35645
+ }
35646
+ console.log("");
35647
+ console.log(dim(" Choose a voice reply provider (text-to-speech):"));
35300
35648
  console.log(" 1. ElevenLabs (high-quality, requires API key)");
35301
35649
  console.log(" 2. Grok / xAI (high-quality, requires API key)");
35302
35650
  console.log(" 3. macOS (free, uses built-in voices \u2014 Samantha, Albert)");
@@ -35313,10 +35661,10 @@ async function setup() {
35313
35661
  const xaiKey = await requiredInput("xAI API key", env.XAI_API_KEY);
35314
35662
  env.XAI_API_KEY = xaiKey;
35315
35663
  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)."));
35664
+ console.log(dim(" Use /voice in Telegram to select a voice (Eve, Ara, Rex, Sal, Leo)."));
35317
35665
  } else if (ttsChoice === "3") {
35318
35666
  console.log(green(" Voice replies via macOS enabled!"));
35319
- console.log(dim(" Use /voice_config in Telegram to select a voice (Samantha or Albert)."));
35667
+ console.log(dim(" Use /voice in Telegram to select a voice (Samantha or Albert)."));
35320
35668
  } else {
35321
35669
  console.log(dim(" Voice replies will use macOS text-to-speech as fallback."));
35322
35670
  }
@@ -35350,14 +35698,14 @@ async function setup() {
35350
35698
  `ANTHROPIC_VERTEX_PROJECT_ID=${env.ANTHROPIC_VERTEX_PROJECT_ID ?? ""}`
35351
35699
  );
35352
35700
  }
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
- }
35701
+ const hasVoice = env.GROQ_API_KEY || env.STT_PROVIDER || env.ELEVENLABS_API_KEY || env.XAI_API_KEY;
35702
+ if (hasVoice) {
35703
+ envLines.push("", "# Voice");
35704
+ if (env.STT_PROVIDER) envLines.push(`STT_PROVIDER=${env.STT_PROVIDER}`);
35705
+ if (env.STT_MODEL) envLines.push(`STT_MODEL=${env.STT_MODEL}`);
35706
+ if (env.GROQ_API_KEY) envLines.push(`GROQ_API_KEY=${env.GROQ_API_KEY}`);
35707
+ if (env.ELEVENLABS_API_KEY) envLines.push(`ELEVENLABS_API_KEY=${env.ELEVENLABS_API_KEY}`);
35708
+ if (env.XAI_API_KEY) envLines.push(`XAI_API_KEY=${env.XAI_API_KEY}`);
35361
35709
  }
35362
35710
  if (env.DASHBOARD_ENABLED) {
35363
35711
  envLines.push("", "# Dashboard", `DASHBOARD_ENABLED=${env.DASHBOARD_ENABLED}`);
@@ -35401,7 +35749,9 @@ async function setup() {
35401
35749
  console.log(" " + green("[ok]") + ` Telegram bot: @${env.TELEGRAM_BOT_TOKEN ? "configured" : "missing"}`);
35402
35750
  console.log(" " + green("[ok]") + ` Chat ID: ${env.ALLOWED_CHAT_ID ?? "not set (anyone can message)"}`);
35403
35751
  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");
35752
+ const sttConfigured = env.STT_PROVIDER === "local-whisper" || !!env.GROQ_API_KEY;
35753
+ const sttLabel = env.STT_PROVIDER === "local-whisper" ? `Local Whisper (${env.STT_MODEL ?? "small.en"})` : env.GROQ_API_KEY ? "Groq" : "not configured";
35754
+ console.log(" " + (sttConfigured ? green("[ok]") : dim("[--]")) + ` Voice transcription: ${sttLabel}`);
35405
35755
  console.log(" " + (env.ELEVENLABS_API_KEY ? green("[ok]") : dim("[--]")) + " Voice replies");
35406
35756
  console.log(" " + (env.DASHBOARD_ENABLED ? green("[ok]") : dim("[--]")) + " Web dashboard");
35407
35757
  console.log(" " + (env.GEMINI_API_KEY ? green("[ok]") : dim("[--]")) + " Video analysis");
@@ -35688,7 +36038,7 @@ function registerCronCommands(cmd) {
35688
36038
  const { cronAction: cronAction2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
35689
36039
  await cronAction2(program.opts(), "run", id);
35690
36040
  });
35691
- cmd.command("edit <id>").description("Edit a job (same flags as create)").option("--title <text>", "Short title for job list").option("--description <text>").option("--cron <expr>").option("--at <iso8601>").option("--every <interval>").option("--backend <name>").option("--model <name>").option("--thinking <level>").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--fallback <backend:model>", "Fallback backend:model (repeatable, max 3)", (val, prev) => [...prev, val], []).option("--timezone <tz>").option("--target <id>", "Delivery target (chat ID, or chatId:topicId for forum topics)").option("--delivery <mode>", "Delivery mode (announce/webhook/none)").action(async (id, opts) => {
36041
+ cmd.command("edit <id>").description("Edit a job (same flags as create)").option("--title <text>", "Short title for job list").option("--description <text>").option("--cron <expr>").option("--at <iso8601>").option("--every <interval>").option("--backend <name>").option("--model <name>").option("--thinking <level>").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--fallback <backend:model>", "Fallback backend:model (repeatable, max 3)", (val, prev) => [...prev, val], []).option("--timezone <tz>").option("--target <id>", "Delivery target (chat ID, or chatId:topicId for forum topics)").option("--delivery <mode>", "Delivery mode (announce/webhook/none)").option("--account <id-or-label>", "Pin a credential slot by ID or label (use 'auto' to clear)").action(async (id, opts) => {
35692
36042
  const { cronEdit: cronEdit2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
35693
36043
  await cronEdit2(program.opts(), id, opts);
35694
36044
  });