cc-claw 0.2.4 → 0.2.6

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 (3) hide show
  1. package/README.md +10 -0
  2. package/dist/cli.js +145 -99
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -43,6 +43,16 @@ Send text, voice, photos, documents, or videos. Switch backends with `/claude`,
43
43
  npm install -g cc-claw
44
44
  ```
45
45
 
46
+ ## Upgrade
47
+
48
+ ```bash
49
+ npm install -g cc-claw@latest # Update to latest version
50
+ cc-claw --version # Verify
51
+ cc-claw service restart # Restart to pick up changes
52
+ ```
53
+
54
+ No config changes or migrations needed.
55
+
46
56
  ## Setup
47
57
 
48
58
  ```bash
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ var VERSION;
48
48
  var init_version = __esm({
49
49
  "src/version.ts"() {
50
50
  "use strict";
51
- VERSION = true ? "0.2.4" : (() => {
51
+ VERSION = true ? "0.2.6" : (() => {
52
52
  try {
53
53
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
54
54
  } catch {
@@ -345,7 +345,7 @@ function claimTask(db3, taskId, agentId) {
345
345
  if (blockedBy.length > 0) {
346
346
  const placeholders = blockedBy.map(() => "?").join(",");
347
347
  const incomplete = db3.prepare(
348
- `SELECT COUNT(*) as count FROM agent_tasks WHERE id IN (${placeholders}) AND status NOT IN ('completed')`
348
+ `SELECT COUNT(*) as count FROM agent_tasks WHERE id IN (${placeholders}) AND status NOT IN ('completed', 'failed', 'abandoned')`
349
349
  ).get(...blockedBy);
350
350
  if (incomplete.count > 0) return false;
351
351
  }
@@ -518,19 +518,18 @@ function cleanupActivity(db3) {
518
518
  if (aged.changes > 0) {
519
519
  log(`[activity] Pruned ${aged.changes} events older than ${RETENTION_DAYS} days`);
520
520
  }
521
- const chatIds = db3.prepare(
522
- "SELECT DISTINCT chatId FROM activity_log"
523
- ).all();
524
- for (const { chatId } of chatIds) {
525
- const excess = db3.prepare(`
526
- DELETE FROM activity_log
527
- WHERE chatId = ? AND id NOT IN (
528
- SELECT id FROM activity_log WHERE chatId = ? ORDER BY createdAt DESC LIMIT ?
529
- )
530
- `).run(chatId, chatId, MAX_ROWS_PER_CHAT);
531
- if (excess.changes > 0) {
532
- log(`[activity] Capped chat ${chatId}: pruned ${excess.changes} excess rows`);
533
- }
521
+ const excess = db3.prepare(`
522
+ DELETE FROM activity_log
523
+ WHERE id NOT IN (
524
+ SELECT id FROM (
525
+ SELECT id, ROW_NUMBER() OVER (PARTITION BY chatId ORDER BY createdAt DESC) AS rn
526
+ FROM activity_log
527
+ ) ranked
528
+ WHERE rn <= ?
529
+ )
530
+ `).run(MAX_ROWS_PER_CHAT);
531
+ if (excess.changes > 0) {
532
+ log(`[activity] Capped activity log: pruned ${excess.changes} excess rows`);
534
533
  }
535
534
  }
536
535
  var RETENTION_DAYS, MAX_ROWS_PER_CHAT;
@@ -1361,7 +1360,7 @@ function searchMemories(queryText, limit = 3) {
1361
1360
  for (const mem of ftsResults) {
1362
1361
  db.prepare(`
1363
1362
  UPDATE memories
1364
- SET salience = MIN(salience + 0.1, 5.0),
1363
+ SET salience = MAX(0, MIN(1.0, salience + 0.1)),
1365
1364
  access_count = access_count + 1,
1366
1365
  last_accessed = datetime('now')
1367
1366
  WHERE id = ?
@@ -1640,7 +1639,7 @@ function searchSessionSummaries(queryText, limit = 3) {
1640
1639
  for (const s of results) {
1641
1640
  db.prepare(`
1642
1641
  UPDATE session_summaries
1643
- SET salience = MIN(salience + 0.1, 5.0)
1642
+ SET salience = MAX(0, MIN(1.0, salience + 0.1))
1644
1643
  WHERE id = ?
1645
1644
  `).run(s.id);
1646
1645
  }
@@ -2233,7 +2232,7 @@ var init_claude = __esm({
2233
2232
  }
2234
2233
  try {
2235
2234
  this._resolvedPath = execSync("which claude", { encoding: "utf-8" }).trim();
2236
- } catch {
2235
+ } catch (_err) {
2237
2236
  const candidates = [
2238
2237
  "/opt/homebrew/bin/claude",
2239
2238
  "/usr/local/bin/claude"
@@ -2437,7 +2436,9 @@ var init_gemini = __esm({
2437
2436
  "-p",
2438
2437
  opts.prompt,
2439
2438
  "-o",
2440
- "stream-json"
2439
+ "stream-json",
2440
+ "--allowed-mcp-server-names"
2441
+ // no values = skip all user MCP servers (avoids crash/hang from broken servers)
2441
2442
  ];
2442
2443
  if (opts.sessionId) args.push("--resume", opts.sessionId);
2443
2444
  if (opts.model) args.push("-m", opts.model);
@@ -2712,8 +2713,7 @@ function normalizeFtsScores(items) {
2712
2713
  const ranks = items.map((it) => Math.abs(it._ftsRank ?? 0));
2713
2714
  const maxRank = Math.max(...ranks, 1e-3);
2714
2715
  for (let i = 0; i < items.length; i++) {
2715
- const id = items[i].id;
2716
- scores.set(id, ranks[i] / maxRank);
2716
+ scores.set(items[i].id, ranks[i] / maxRank);
2717
2717
  }
2718
2718
  return scores;
2719
2719
  }
@@ -2929,13 +2929,13 @@ function syncNativeCliFiles() {
2929
2929
  } catch {
2930
2930
  }
2931
2931
  try {
2932
- const soulStat = statSync(SOUL_PATH);
2933
- const userStat = statSync(USER_PATH);
2934
- if (soulStat.mtimeMs === lastSoulMtime && userStat.mtimeMs === lastUserMtime) {
2932
+ const soulMtime = existsSync4(SOUL_PATH) ? statSync(SOUL_PATH).mtimeMs : 0;
2933
+ const userMtime = existsSync4(USER_PATH) ? statSync(USER_PATH).mtimeMs : 0;
2934
+ if (soulMtime === lastSoulMtime && userMtime === lastUserMtime) {
2935
2935
  return;
2936
2936
  }
2937
- lastSoulMtime = soulStat.mtimeMs;
2938
- lastUserMtime = userStat.mtimeMs;
2937
+ lastSoulMtime = soulMtime;
2938
+ lastUserMtime = userMtime;
2939
2939
  } catch {
2940
2940
  }
2941
2941
  const nativeContent = [
@@ -3267,13 +3267,14 @@ ${transcript}`;
3267
3267
  stdio: ["ignore", "pipe", "pipe"],
3268
3268
  ...config2.cwd ? { cwd: config2.cwd } : {}
3269
3269
  });
3270
+ proc.stderr?.resume();
3271
+ const rl2 = createInterface({ input: proc.stdout });
3270
3272
  const timeout = setTimeout(() => {
3271
3273
  warn(`[summarize] Timeout after ${SUMMARIZE_TIMEOUT_MS / 1e3}s for chat ${chatId} \u2014 killing process`);
3274
+ rl2.close();
3272
3275
  proc.kill("SIGTERM");
3273
3276
  setTimeout(() => proc.kill("SIGKILL"), 2e3);
3274
3277
  }, SUMMARIZE_TIMEOUT_MS);
3275
- proc.stderr?.resume();
3276
- const rl2 = createInterface({ input: proc.stdout });
3277
3278
  rl2.on("line", (line) => {
3278
3279
  if (!line.trim()) return;
3279
3280
  let msg;
@@ -3295,9 +3296,9 @@ ${transcript}`;
3295
3296
  if (ev.type === "result") {
3296
3297
  resultText = ev.resultText || accumulatedText;
3297
3298
  if (ev.usage) {
3298
- inputTokens = ev.usage.input;
3299
- outputTokens = ev.usage.output;
3300
- cacheReadTokens = ev.usage.cacheRead;
3299
+ inputTokens += ev.usage.input;
3300
+ outputTokens += ev.usage.output;
3301
+ cacheReadTokens += ev.usage.cacheRead;
3301
3302
  }
3302
3303
  if (adapter.shouldKillOnResult()) {
3303
3304
  rl2.close();
@@ -3482,8 +3483,9 @@ import { spawn as spawn2 } from "child_process";
3482
3483
  import { createInterface as createInterface2 } from "readline";
3483
3484
  import { readFileSync as readFileSync4 } from "fs";
3484
3485
  function stripFrontmatter(text) {
3485
- const match = text.match(/^---\s*\n[\s\S]*?\n---\s*\n?/);
3486
- return match ? text.slice(match[0].length) : text;
3486
+ const match = text.match(/^---\s*\n[\s\S]*?\n---\s*\n?/m);
3487
+ if (!match || match.index !== 0) return text;
3488
+ return text.slice(match[0].length);
3487
3489
  }
3488
3490
  function buildAgentPrompt(opts, runnerSkillPath) {
3489
3491
  const parts = [];
@@ -3782,8 +3784,8 @@ __export(loader_exports, {
3782
3784
  import { readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
3783
3785
  import { join as join6 } from "path";
3784
3786
  function parseFrontmatter(content) {
3785
- const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
3786
- if (!match) return { meta: {}, body: content.trim() };
3787
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m);
3788
+ if (!match || match.index !== 0) return { meta: {}, body: content.trim() };
3787
3789
  const yamlBlock = match[1];
3788
3790
  const body = match[2].trim();
3789
3791
  const meta = {};
@@ -4283,6 +4285,7 @@ function startNextQueued(chatId) {
4283
4285
  maxRuntimeMs: agent.maxRuntimeMs,
4284
4286
  mcps: JSON.parse(agent.mcpsAdded)
4285
4287
  }).catch((err) => {
4288
+ updateAgentStatus(db3, next.id, "failed");
4286
4289
  error(`[orchestrator] Failed to start queued agent ${next.id}:`, err);
4287
4290
  });
4288
4291
  }
@@ -5210,7 +5213,6 @@ var init_server = __esm({
5210
5213
  var agent_exports = {};
5211
5214
  __export(agent_exports, {
5212
5215
  askAgent: () => askAgent,
5213
- askClaude: () => askClaude,
5214
5216
  isAgentActive: () => isAgentActive,
5215
5217
  isChatBusy: () => isChatBusy,
5216
5218
  stopAgent: () => stopAgent
@@ -5310,7 +5312,8 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
5310
5312
  if (onToolAction && ev.toolName) {
5311
5313
  const toolInput = ev.toolInput ?? {};
5312
5314
  if (ev.toolId) pendingTools.set(ev.toolId, { name: ev.toolName, input: toolInput });
5313
- onToolAction(ev.toolName, toolInput, void 0).catch(() => {
5315
+ onToolAction(ev.toolName, toolInput, void 0).catch((err) => {
5316
+ error("[agent] tool action error:", err);
5314
5317
  });
5315
5318
  }
5316
5319
  break;
@@ -5319,10 +5322,12 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
5319
5322
  const pending = ev.toolId ? pendingTools.get(ev.toolId) : void 0;
5320
5323
  if (pending) {
5321
5324
  pendingTools.delete(ev.toolId);
5322
- onToolAction(pending.name, pending.input, ev.toolOutput ?? "").catch(() => {
5325
+ onToolAction(pending.name, pending.input, ev.toolOutput ?? "").catch((err) => {
5326
+ error("[agent] tool action error:", err);
5323
5327
  });
5324
5328
  } else if (ev.toolName) {
5325
- onToolAction(ev.toolName, {}, ev.toolOutput ?? "").catch(() => {
5329
+ onToolAction(ev.toolName, {}, ev.toolOutput ?? "").catch((err) => {
5330
+ error("[agent] tool action error:", err);
5326
5331
  });
5327
5332
  }
5328
5333
  }
@@ -5344,7 +5349,10 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
5344
5349
  cacheRead = ev.usage.cacheRead;
5345
5350
  }
5346
5351
  if (adapter.shouldKillOnResult()) {
5347
- rl2.close();
5352
+ try {
5353
+ rl2.close();
5354
+ } catch {
5355
+ }
5348
5356
  proc.kill("SIGTERM");
5349
5357
  }
5350
5358
  break;
@@ -5370,7 +5378,7 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
5370
5378
  }
5371
5379
  if (code && code !== 0 && !cancelState.cancelled && !resultText) {
5372
5380
  const stderr = Buffer.concat(stderrChunks).toString().trim();
5373
- reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 200)}` : ""}`));
5381
+ reject(new Error(`CLI exited with code ${code}${stderr ? `: ${stderr.slice(0, 500)}` : ""}`));
5374
5382
  return;
5375
5383
  }
5376
5384
  resolve({ resultText, sessionId, input, output: output2, cacheRead, sawToolEvents, sawResultEvent });
@@ -5466,7 +5474,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
5466
5474
  if (!flag) return args;
5467
5475
  return [...args, ...flag, mcpConfigPath];
5468
5476
  }
5469
- var __filename2, __dirname2, activeChats, chatLocks, SPAWN_TIMEOUT_MS, MCP_CONFIG_FLAG, askClaude;
5477
+ var __filename2, __dirname2, activeChats, chatLocks, SPAWN_TIMEOUT_MS, MCP_CONFIG_FLAG;
5470
5478
  var init_agent = __esm({
5471
5479
  "src/agent.ts"() {
5472
5480
  "use strict";
@@ -5487,7 +5495,6 @@ var init_agent = __esm({
5487
5495
  MCP_CONFIG_FLAG = {
5488
5496
  claude: ["--mcp-config"]
5489
5497
  };
5490
- askClaude = askAgent;
5491
5498
  }
5492
5499
  });
5493
5500
 
@@ -5917,6 +5924,11 @@ async function executeJob(job) {
5917
5924
  return;
5918
5925
  }
5919
5926
  runningJobs.add(job.id);
5927
+ const timer = activeTimers.get(job.id);
5928
+ if (timer instanceof Cron) {
5929
+ const nextRun = timer.nextRun();
5930
+ if (nextRun) updateJobNextRun(job.id, nextRun.toISOString());
5931
+ }
5920
5932
  const t0 = Date.now();
5921
5933
  const resolvedModel = resolveJobModel(job);
5922
5934
  const runId = insertJobRun(job.id, resolvedModel);
@@ -5942,11 +5954,6 @@ async function executeJob(job) {
5942
5954
  addUsage(job.chatId, response.usage.input, response.usage.output, response.usage.cacheRead, resolvedModel);
5943
5955
  }
5944
5956
  await deliverJobOutput(job, response.text);
5945
- const timer = activeTimers.get(job.id);
5946
- if (timer instanceof Cron) {
5947
- const nextRun = timer.nextRun();
5948
- if (nextRun) updateJobNextRun(job.id, nextRun.toISOString());
5949
- }
5950
5957
  } catch (err) {
5951
5958
  const durationMs = Date.now() - t0;
5952
5959
  const errorClass = classifyError(err);
@@ -6082,9 +6089,9 @@ function writeEnvWrapper(server) {
6082
6089
  mkdirSync4(dir, { recursive: true, mode: 448 });
6083
6090
  const lines = ["#!/bin/sh"];
6084
6091
  for (const [k, v] of Object.entries(server.env ?? {})) {
6085
- lines.push(`export ${k}="${v.replace(/"/g, '\\"')}"`);
6092
+ lines.push(`export ${k}='${v.replace(/'/g, "'\\''")}'`);
6086
6093
  }
6087
- lines.push(`exec ${server.command} ${(server.args ?? []).map((a) => `"${a}"`).join(" ")}`);
6094
+ lines.push(`exec ${server.command} ${(server.args ?? []).map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ")}`);
6088
6095
  const path = join8(dir, `wrapper-${server.name}.sh`);
6089
6096
  writeFileSync4(path, lines.join("\n") + "\n", { mode: 448 });
6090
6097
  return path;
@@ -6254,12 +6261,7 @@ function templateReplace(args, vars) {
6254
6261
  }
6255
6262
  function configToRunner(config2) {
6256
6263
  const exePath = resolveExecutable(config2);
6257
- let parseLine;
6258
- if (config2.outputParsing.preset) {
6259
- parseLine = buildGenericParser(config2);
6260
- } else {
6261
- parseLine = buildGenericParser(config2);
6262
- }
6264
+ const parseLine = buildGenericParser(config2);
6263
6265
  return {
6264
6266
  id: config2.id,
6265
6267
  displayName: config2.displayName,
@@ -6355,6 +6357,12 @@ function registerConfigRunners() {
6355
6357
  }
6356
6358
  function watchRunnerConfigs(onChange) {
6357
6359
  if (!existsSync9(RUNNERS_PATH)) return;
6360
+ for (const prev of watchedFiles) {
6361
+ if (!existsSync9(prev)) {
6362
+ unwatchFile(prev);
6363
+ watchedFiles.delete(prev);
6364
+ }
6365
+ }
6358
6366
  const files = readdirSync4(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
6359
6367
  for (const file of files) {
6360
6368
  const fullPath = join9(RUNNERS_PATH, file);
@@ -6521,7 +6529,10 @@ function formatForTelegram(markdown) {
6521
6529
  html = html.replace(/^#{1,6}\s+(.+)$/gm, "<b>$1</b>");
6522
6530
  html = html.replace(/^[-*_]{3,}$/gm, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
6523
6531
  html = html.replace(/^[\s]*[-*]\s+/gm, "\u2022 ");
6524
- html = html.replace(/\x00CODE(\d+)\x00/g, (_, idx) => codeBlocks[parseInt(idx, 10)]);
6532
+ html = html.replace(/\x00CODE(\d+)\x00/g, (match, idx) => {
6533
+ const block = codeBlocks[parseInt(idx, 10)];
6534
+ return block !== void 0 ? block : match;
6535
+ });
6525
6536
  return html.trim();
6526
6537
  }
6527
6538
  function escapeOutsideCode(text) {
@@ -6561,6 +6572,10 @@ var init_telegram = __esm({
6561
6572
 
6562
6573
  // src/channels/telegram.ts
6563
6574
  import { Bot, InlineKeyboard, InputFile } from "grammy";
6575
+ function numericChatId(chatId) {
6576
+ const raw = chatId.includes(":") ? chatId.split(":").pop() : chatId;
6577
+ return parseInt(raw);
6578
+ }
6564
6579
  var TelegramChannel;
6565
6580
  var init_telegram2 = __esm({
6566
6581
  "src/channels/telegram.ts"() {
@@ -6710,13 +6725,13 @@ var init_telegram2 = __esm({
6710
6725
  await this.bot.stop();
6711
6726
  }
6712
6727
  async sendTyping(chatId) {
6713
- await this.bot.api.sendChatAction(parseInt(chatId), "typing");
6728
+ await this.bot.api.sendChatAction(numericChatId(chatId), "typing");
6714
6729
  }
6715
6730
  async sendText(chatId, text, parseMode) {
6716
6731
  if (parseMode === "plain") {
6717
6732
  const plainChunks = splitMessage(text);
6718
6733
  for (const chunk of plainChunks) {
6719
- await this.bot.api.sendMessage(parseInt(chatId), chunk);
6734
+ await this.bot.api.sendMessage(numericChatId(chatId), chunk);
6720
6735
  }
6721
6736
  return;
6722
6737
  }
@@ -6724,12 +6739,12 @@ var init_telegram2 = __esm({
6724
6739
  const chunks = splitMessage(formatted);
6725
6740
  for (const chunk of chunks) {
6726
6741
  try {
6727
- await this.bot.api.sendMessage(parseInt(chatId), chunk, {
6742
+ await this.bot.api.sendMessage(numericChatId(chatId), chunk, {
6728
6743
  parse_mode: "HTML"
6729
6744
  });
6730
6745
  } catch {
6731
6746
  await this.bot.api.sendMessage(
6732
- parseInt(chatId),
6747
+ numericChatId(chatId),
6733
6748
  chunk.replace(/<[^>]+>/g, "")
6734
6749
  );
6735
6750
  }
@@ -6737,13 +6752,13 @@ var init_telegram2 = __esm({
6737
6752
  }
6738
6753
  async sendVoice(chatId, audioBuffer, fileName) {
6739
6754
  await this.bot.api.sendVoice(
6740
- parseInt(chatId),
6755
+ numericChatId(chatId),
6741
6756
  new InputFile(audioBuffer, fileName ?? "response.ogg")
6742
6757
  );
6743
6758
  }
6744
6759
  async sendFile(chatId, buffer, fileName) {
6745
6760
  await this.bot.api.sendDocument(
6746
- parseInt(chatId),
6761
+ numericChatId(chatId),
6747
6762
  new InputFile(buffer, fileName)
6748
6763
  );
6749
6764
  }
@@ -6755,7 +6770,7 @@ var init_telegram2 = __esm({
6755
6770
  }
6756
6771
  async sendTextReturningId(chatId, text, parseMode) {
6757
6772
  try {
6758
- const msg = await this.bot.api.sendMessage(parseInt(chatId), text);
6773
+ const msg = await this.bot.api.sendMessage(numericChatId(chatId), text);
6759
6774
  return msg.message_id.toString();
6760
6775
  } catch {
6761
6776
  return void 0;
@@ -6764,14 +6779,14 @@ var init_telegram2 = __esm({
6764
6779
  async editText(chatId, messageId, text, parseMode) {
6765
6780
  const formatted = parseMode === "html" ? text : formatForTelegram(text);
6766
6781
  try {
6767
- await this.bot.api.editMessageText(parseInt(chatId), parseInt(messageId), formatted, {
6782
+ await this.bot.api.editMessageText(numericChatId(chatId), parseInt(messageId), formatted, {
6768
6783
  parse_mode: "HTML"
6769
6784
  });
6770
6785
  return true;
6771
6786
  } catch {
6772
6787
  try {
6773
6788
  await this.bot.api.editMessageText(
6774
- parseInt(chatId),
6789
+ numericChatId(chatId),
6775
6790
  parseInt(messageId),
6776
6791
  formatted.replace(/<[^>]+>/g, "")
6777
6792
  );
@@ -6793,13 +6808,13 @@ var init_telegram2 = __esm({
6793
6808
  }
6794
6809
  keyboard.row();
6795
6810
  }
6796
- await this.bot.api.sendMessage(parseInt(chatId), text, {
6811
+ await this.bot.api.sendMessage(numericChatId(chatId), text, {
6797
6812
  reply_markup: keyboard
6798
6813
  });
6799
6814
  }
6800
6815
  async reactToMessage(chatId, messageId, emoji) {
6801
6816
  try {
6802
- await this.bot.api.setMessageReaction(parseInt(chatId), parseInt(messageId), [
6817
+ await this.bot.api.setMessageReaction(numericChatId(chatId), parseInt(messageId), [
6803
6818
  { type: "emoji", emoji }
6804
6819
  ]);
6805
6820
  } catch {
@@ -7093,6 +7108,9 @@ async function installSkillFromGitHub(urlOrShorthand) {
7093
7108
  stdio: "pipe",
7094
7109
  timeout: 3e4
7095
7110
  });
7111
+ if (!existsSync10(join11(tmpDir, ".git"))) {
7112
+ return { success: false, error: "Git clone failed: no .git directory produced" };
7113
+ }
7096
7114
  const searchRoot = subPath ? join11(tmpDir, subPath) : tmpDir;
7097
7115
  const skillDir = await findSkillDir(searchRoot);
7098
7116
  if (!skillDir) {
@@ -7165,7 +7183,12 @@ async function findSkillDir(root) {
7165
7183
  const entries = await readdir2(root, { withFileTypes: true });
7166
7184
  for (const entry of entries) {
7167
7185
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
7168
- const subEntries = await readdir2(join11(root, entry.name), { withFileTypes: true });
7186
+ let subEntries;
7187
+ try {
7188
+ subEntries = await readdir2(join11(root, entry.name), { withFileTypes: true });
7189
+ } catch {
7190
+ continue;
7191
+ }
7169
7192
  for (const sub of subEntries) {
7170
7193
  if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
7171
7194
  for (const c of candidates) {
@@ -7371,8 +7394,7 @@ function stopAllHeartbeats() {
7371
7394
  }
7372
7395
  function startAllHeartbeats() {
7373
7396
  try {
7374
- const { getDb: getDb2 } = (init_store4(), __toCommonJS(store_exports2));
7375
- const db3 = getDb2();
7397
+ const db3 = getDb();
7376
7398
  const rows = db3.prepare(
7377
7399
  "SELECT chat_id FROM chat_heartbeat WHERE enabled = 1"
7378
7400
  ).all();
@@ -7610,9 +7632,11 @@ async function macOsTts(text) {
7610
7632
  await execFileAsync2("say", ["-o", tmpAiff, sanitized]);
7611
7633
  await execFileAsync2("ffmpeg", ["-y", "-i", tmpAiff, "-c:a", "libopus", "-b:a", "64k", tmpOgg]);
7612
7634
  const oggBuffer = await readFile4(tmpOgg);
7613
- unlink(tmpAiff).catch(() => {
7635
+ unlink(tmpAiff).catch((err) => {
7636
+ error("[tts] cleanup failed:", err);
7614
7637
  });
7615
- unlink(tmpOgg).catch(() => {
7638
+ unlink(tmpOgg).catch((err) => {
7639
+ error("[tts] cleanup failed:", err);
7616
7640
  });
7617
7641
  return oggBuffer;
7618
7642
  }
@@ -7743,8 +7767,7 @@ async function startWizard(chatId, input, channel) {
7743
7767
  step: "schedule",
7744
7768
  task: parsed?.task || input,
7745
7769
  scheduleType: parsed?.scheduleType,
7746
- cron: parsed?.cron,
7747
- everyMs: parsed?.everyMs
7770
+ cron: parsed?.cron
7748
7771
  };
7749
7772
  if (parsed?.cron) {
7750
7773
  pending.step = "timezone";
@@ -10548,10 +10571,18 @@ async function main() {
10548
10571
  bootstrapBuiltinMcps(getDb());
10549
10572
  const SUMMARIZE_TIMEOUT_MS = 3e4;
10550
10573
  try {
10551
- await Promise.race([
10552
- summarizeAllPending(),
10553
- new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), SUMMARIZE_TIMEOUT_MS))
10554
- ]);
10574
+ let timer;
10575
+ const timeoutPromise = new Promise((_, reject) => {
10576
+ timer = setTimeout(() => reject(new Error("timeout")), SUMMARIZE_TIMEOUT_MS);
10577
+ });
10578
+ try {
10579
+ await Promise.race([
10580
+ summarizeAllPending(),
10581
+ timeoutPromise
10582
+ ]);
10583
+ } finally {
10584
+ clearTimeout(timer);
10585
+ }
10555
10586
  } catch {
10556
10587
  log("[cc-claw] Session summarization skipped (timeout or backend unavailable)");
10557
10588
  }
@@ -10782,7 +10813,7 @@ function uninstallMacOS() {
10782
10813
  }
10783
10814
  function statusMacOS() {
10784
10815
  try {
10785
- const out = execSync5("launchctl list | grep cc-claw", { encoding: "utf-8" }).trim();
10816
+ const out = execSync5("launchctl list | grep cc-claw", { shell: "/bin/sh", encoding: "utf-8" }).trim();
10786
10817
  if (out) {
10787
10818
  const parts = out.split(/\s+/);
10788
10819
  const pid = parts[0];
@@ -11025,7 +11056,8 @@ async function restartService() {
11025
11056
  const os = platform2();
11026
11057
  try {
11027
11058
  if (os === "darwin") {
11028
- execSync6("launchctl stop com.cc-claw && launchctl start com.cc-claw");
11059
+ const uid = process.getuid?.() ?? execSync6("id -u", { encoding: "utf-8" }).trim();
11060
+ execSync6(`launchctl kickstart -k gui/${uid}/com.cc-claw`);
11029
11061
  console.log(`
11030
11062
  ${success("Daemon restarted.")}
11031
11063
  `);
@@ -11324,7 +11356,9 @@ async function doctorCommand(globalOpts, localOpts) {
11324
11356
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports2));
11325
11357
  const readDb = openDatabaseReadOnly2();
11326
11358
  const journal = readDb.prepare("PRAGMA journal_mode").get();
11327
- if (journal.journal_mode === "wal") {
11359
+ if (!journal) {
11360
+ checks.push({ name: "WAL mode", status: "warning", message: "could not read journal_mode" });
11361
+ } else if (journal.journal_mode === "wal") {
11328
11362
  checks.push({ name: "WAL mode", status: "ok", message: "enabled" });
11329
11363
  } else {
11330
11364
  checks.push({ name: "WAL mode", status: "warning", message: `${journal.journal_mode} (expected wal)` });
@@ -12128,8 +12162,8 @@ async function agentsSpawn(globalOpts, opts) {
12128
12162
  role: opts.role
12129
12163
  });
12130
12164
  if (res.ok) {
12165
+ const { success: s } = await Promise.resolve().then(() => (init_format(), format_exports));
12131
12166
  output(res.data, (d) => {
12132
- const { success: s } = (init_format(), __toCommonJS(format_exports));
12133
12167
  return `
12134
12168
  ${s(`Agent spawned: ${d.agentId?.slice(0, 8) ?? "?"} (${opts.runner})`)}
12135
12169
  `;
@@ -12393,13 +12427,21 @@ async function usageTokens(globalOpts) {
12393
12427
  const { getAllAdapters: getAllAdapters2, getAllBackendIds: getAllBackendIds2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
12394
12428
  const readDb = openDatabaseReadOnly2();
12395
12429
  const backendIds = getAllBackendIds2();
12430
+ const adapters2 = getAllAdapters2();
12396
12431
  const cutoff24h = new Date(Date.now() - 864e5).toISOString();
12397
12432
  const data = backendIds.map((bid) => {
12398
- const row = readDb.prepare(`
12399
- SELECT COALESCE(SUM(input_tokens),0) as input_tokens, COALESCE(SUM(output_tokens),0) as output_tokens, COUNT(*) as request_count
12400
- FROM usage_log WHERE model IN (SELECT DISTINCT model FROM usage_log) AND created_at > ?
12401
- `).get(cutoff24h);
12402
- const adapter = getAllAdapters2().find((a) => a.id === bid);
12433
+ const adapter = adapters2.find((a) => a.id === bid);
12434
+ const modelIds = adapter ? Object.keys(adapter.pricing) : [];
12435
+ let row;
12436
+ if (modelIds.length > 0) {
12437
+ const placeholders = modelIds.map(() => "?").join(",");
12438
+ row = readDb.prepare(`
12439
+ SELECT COALESCE(SUM(input_tokens),0) as input_tokens, COALESCE(SUM(output_tokens),0) as output_tokens, COUNT(*) as request_count
12440
+ FROM usage_log WHERE model IN (${placeholders}) AND created_at > ?
12441
+ `).get(...modelIds, cutoff24h);
12442
+ } else {
12443
+ row = { input_tokens: 0, output_tokens: 0, request_count: 0 };
12444
+ }
12403
12445
  return { backend: bid, displayName: adapter?.displayName ?? bid, ...row };
12404
12446
  });
12405
12447
  readDb.close();
@@ -12496,6 +12538,7 @@ async function configList(globalOpts) {
12496
12538
  const config2 = {};
12497
12539
  for (const key of RUNTIME_KEYS) {
12498
12540
  const { table, col } = KEY_TABLE_MAP[key];
12541
+ if (!ALLOWED_TABLES.has(table) || !ALLOWED_COLS.has(col)) continue;
12499
12542
  try {
12500
12543
  const row = readDb.prepare(`SELECT ${col} FROM ${table} WHERE chat_id = ?`).get(chatId);
12501
12544
  config2[key] = row ? row[col] : null;
@@ -12527,6 +12570,10 @@ async function configGet(globalOpts, key) {
12527
12570
  const readDb = openDatabaseReadOnly2();
12528
12571
  const chatId = resolveChatId(globalOpts);
12529
12572
  const { table, col } = KEY_TABLE_MAP[key];
12573
+ if (!ALLOWED_TABLES.has(table) || !ALLOWED_COLS.has(col)) {
12574
+ outputError("INVALID_KEY", `Invalid config mapping for "${key}".`);
12575
+ process.exit(1);
12576
+ }
12530
12577
  let value = null;
12531
12578
  try {
12532
12579
  const row = readDb.prepare(`SELECT ${col} FROM ${table} WHERE chat_id = ?`).get(chatId);
@@ -12585,7 +12632,7 @@ async function configEnv(_globalOpts) {
12585
12632
  return lines.join("\n");
12586
12633
  });
12587
12634
  }
12588
- var RUNTIME_KEYS, KEY_TABLE_MAP;
12635
+ var RUNTIME_KEYS, KEY_TABLE_MAP, ALLOWED_TABLES, ALLOWED_COLS;
12589
12636
  var init_config = __esm({
12590
12637
  "src/cli/commands/config.ts"() {
12591
12638
  "use strict";
@@ -12603,6 +12650,8 @@ var init_config = __esm({
12603
12650
  cwd: { table: "chat_cwd", col: "cwd" },
12604
12651
  voice: { table: "chat_voice", col: "enabled" }
12605
12652
  };
12653
+ ALLOWED_TABLES = new Set(Object.values(KEY_TABLE_MAP).map((v) => v.table));
12654
+ ALLOWED_COLS = new Set(Object.values(KEY_TABLE_MAP).map((v) => v.col));
12606
12655
  }
12607
12656
  });
12608
12657
 
@@ -12676,11 +12725,14 @@ __export(permissions_exports, {
12676
12725
  verboseSet: () => verboseSet
12677
12726
  });
12678
12727
  import { existsSync as existsSync30 } from "fs";
12679
- async function permissionsGet(globalOpts) {
12728
+ function ensureDb2() {
12680
12729
  if (!existsSync30(DB_PATH)) {
12681
12730
  outputError("DB_NOT_FOUND", "Database not found.");
12682
12731
  process.exit(1);
12683
12732
  }
12733
+ }
12734
+ async function permissionsGet(globalOpts) {
12735
+ ensureDb2();
12684
12736
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports2));
12685
12737
  const readDb = openDatabaseReadOnly2();
12686
12738
  const chatId = resolveChatId(globalOpts);
@@ -12712,10 +12764,7 @@ async function permissionsSet(globalOpts, mode) {
12712
12764
  }
12713
12765
  }
12714
12766
  async function toolsList(globalOpts) {
12715
- if (!existsSync30(DB_PATH)) {
12716
- outputError("DB_NOT_FOUND", "Database not found.");
12717
- process.exit(1);
12718
- }
12767
+ ensureDb2();
12719
12768
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports2));
12720
12769
  const readDb = openDatabaseReadOnly2();
12721
12770
  const chatId = resolveChatId(globalOpts);
@@ -12776,10 +12825,7 @@ async function toggleTool2(globalOpts, name, enabled) {
12776
12825
  }
12777
12826
  }
12778
12827
  async function verboseGet(globalOpts) {
12779
- if (!existsSync30(DB_PATH)) {
12780
- outputError("DB_NOT_FOUND", "Database not found.");
12781
- process.exit(1);
12782
- }
12828
+ ensureDb2();
12783
12829
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports2));
12784
12830
  const readDb = openDatabaseReadOnly2();
12785
12831
  const chatId = resolveChatId(globalOpts);
@@ -13682,7 +13728,7 @@ async function requiredInput(prompt, existing) {
13682
13728
  }
13683
13729
  async function validateBotToken(token) {
13684
13730
  try {
13685
- const res = await fetch(`https://api.telegram.org/bot${token}/getMe`);
13731
+ const res = await fetch(`https://api.telegram.org/bot${token}/getMe`, { signal: AbortSignal.timeout(1e4) });
13686
13732
  const data = await res.json();
13687
13733
  if (data.ok && data.result?.username) {
13688
13734
  return { valid: true, botName: data.result.username };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "CC-Claw: Personal AI assistant on Telegram — multi-backend (Claude, Gemini, Codex), sub-agent orchestration, MCP management",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",