cc-claw 0.3.10 → 0.3.11

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 +594 -245
  2. package/package.json +1 -1
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.3.10" : (() => {
51
+ VERSION = true ? "0.3.11" : (() => {
52
52
  try {
53
53
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
54
54
  } catch {
@@ -1144,6 +1144,7 @@ function initDatabase() {
1144
1144
  model TEXT,
1145
1145
  thinking TEXT,
1146
1146
  timeout INTEGER,
1147
+ fallbacks TEXT,
1147
1148
  session_type TEXT NOT NULL DEFAULT 'isolated',
1148
1149
  channel TEXT,
1149
1150
  target TEXT,
@@ -1263,6 +1264,10 @@ function initDatabase() {
1263
1264
  db.exec("ALTER TABLE jobs ADD COLUMN timeout INTEGER");
1264
1265
  } catch {
1265
1266
  }
1267
+ try {
1268
+ db.exec("ALTER TABLE jobs ADD COLUMN fallbacks TEXT");
1269
+ } catch {
1270
+ }
1266
1271
  }
1267
1272
  } catch {
1268
1273
  }
@@ -1955,8 +1960,8 @@ function getBackendUsageInWindow(backend2, windowType) {
1955
1960
  function insertJob(params) {
1956
1961
  const result = db.prepare(`
1957
1962
  INSERT INTO jobs (schedule_type, cron, at_time, every_ms, description, chat_id,
1958
- backend, model, thinking, timeout, session_type, channel, target, delivery_mode, timezone)
1959
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1963
+ backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone)
1964
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1960
1965
  `).run(
1961
1966
  params.scheduleType,
1962
1967
  params.cron ?? null,
@@ -1968,6 +1973,7 @@ function insertJob(params) {
1968
1973
  params.model ?? null,
1969
1974
  params.thinking ?? null,
1970
1975
  params.timeout ?? null,
1976
+ params.fallbacks?.length ? JSON.stringify(params.fallbacks) : null,
1971
1977
  params.sessionType ?? "isolated",
1972
1978
  params.channel ?? null,
1973
1979
  params.target ?? null,
@@ -1976,14 +1982,19 @@ function insertJob(params) {
1976
1982
  );
1977
1983
  return getJobById(Number(result.lastInsertRowid));
1978
1984
  }
1985
+ function mapJobRow(row) {
1986
+ if (!row) return void 0;
1987
+ row.fallbacks = row.fallbacks ? JSON.parse(row.fallbacks) : [];
1988
+ return row;
1989
+ }
1979
1990
  function getJobById(id) {
1980
- return db.prepare(`${JOB_SELECT} WHERE id = ?`).get(id);
1991
+ return mapJobRow(db.prepare(`${JOB_SELECT} WHERE id = ?`).get(id));
1981
1992
  }
1982
1993
  function getActiveJobs() {
1983
- return db.prepare(`${JOB_SELECT} WHERE active = 1 AND enabled = 1`).all();
1994
+ return db.prepare(`${JOB_SELECT} WHERE active = 1 AND enabled = 1`).all().map((r) => mapJobRow(r));
1984
1995
  }
1985
1996
  function getAllJobs() {
1986
- return db.prepare(`${JOB_SELECT} WHERE active = 1 ORDER BY id`).all();
1997
+ return db.prepare(`${JOB_SELECT} WHERE active = 1 ORDER BY id`).all().map((r) => mapJobRow(r));
1987
1998
  }
1988
1999
  function updateJobEnabled(id, enabled) {
1989
2000
  const result = db.prepare("UPDATE jobs SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
@@ -2283,7 +2294,7 @@ var init_store4 = __esm({
2283
2294
  ALL_TOOLS = ["Read", "Glob", "Grep", "Bash", "Write", "Edit", "WebFetch", "WebSearch", "Agent", "AskUserQuestion"];
2284
2295
  JOB_SELECT = `
2285
2296
  SELECT id, schedule_type as scheduleType, cron, at_time as atTime, every_ms as everyMs,
2286
- description, chat_id as chatId, backend, model, thinking, timeout,
2297
+ description, chat_id as chatId, backend, model, thinking, timeout, fallbacks,
2287
2298
  session_type as sessionType, channel, target, delivery_mode as deliveryMode,
2288
2299
  timezone, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
2289
2300
  next_run_at as nextRunAt, consecutive_failures as consecutiveFailures
@@ -2982,17 +2993,21 @@ async function injectMemoryContext(userMessage) {
2982
2993
  if (combinedMemories.length === 0 && combinedSessions.length === 0) return null;
2983
2994
  const lines = [];
2984
2995
  for (const m of combinedMemories) {
2985
- lines.push(`- [${m.type}] ${m.trigger}: ${m.content}`);
2996
+ let text = `- [${m.type}] ${m.trigger}: ${m.content}`;
2997
+ if (text.length > MAX_MEMORY_CHARS) text = text.slice(0, MAX_MEMORY_CHARS) + "\u2026";
2998
+ lines.push(text);
2986
2999
  }
2987
3000
  for (const s of combinedSessions) {
2988
3001
  const date = s.created_at.split("T")[0] ?? s.created_at.split(" ")[0];
2989
- lines.push(`- [episodic] ${date} session (${s.message_count} msgs): ${s.summary}${s.topics ? ` Topics: ${s.topics}` : ""}`);
3002
+ let text = `- [episodic] ${date} session (${s.message_count} msgs): ${s.summary}${s.topics ? ` Topics: ${s.topics}` : ""}`;
3003
+ if (text.length > MAX_SESSION_CHARS) text = text.slice(0, MAX_SESSION_CHARS) + "\u2026";
3004
+ lines.push(text);
2990
3005
  }
2991
3006
  return `[Memory context]
2992
3007
  ${lines.join("\n")}
2993
3008
  [End memory context]`;
2994
3009
  }
2995
- var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, VECTOR_TOP_K, FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS;
3010
+ var MEMORY_DECAY_RATE, SESSION_DECAY_RATE, VECTOR_TOP_K, FTS_TOP_K, FINAL_TOP_K_MEMORIES, FINAL_TOP_K_SESSIONS, MAX_MEMORY_CHARS, MAX_SESSION_CHARS;
2996
3011
  var init_inject = __esm({
2997
3012
  "src/memory/inject.ts"() {
2998
3013
  "use strict";
@@ -3004,6 +3019,8 @@ var init_inject = __esm({
3004
3019
  FTS_TOP_K = 20;
3005
3020
  FINAL_TOP_K_MEMORIES = 5;
3006
3021
  FINAL_TOP_K_SESSIONS = 3;
3022
+ MAX_MEMORY_CHARS = 500;
3023
+ MAX_SESSION_CHARS = 800;
3007
3024
  }
3008
3025
  });
3009
3026
 
@@ -3114,6 +3131,7 @@ function syncNativeCliFiles() {
3114
3131
  "## System Capabilities",
3115
3132
  "",
3116
3133
  "- To send a file to the user: write [SEND_FILE:/absolute/path] in your response",
3134
+ "- To generate an image: write [GENERATE_IMAGE:detailed prompt] in your response (requires GEMINI_API_KEY)",
3117
3135
  "- To suggest saving a user preference: write [UPDATE_USER:key=value] in your response",
3118
3136
  "- For heartbeat checks: respond with exactly HEARTBEAT_OK if nothing needs attention",
3119
3137
  "- Your working directory is ~/.cc-claw/workspace/",
@@ -3147,29 +3165,8 @@ var init_init = __esm({
3147
3165
  });
3148
3166
 
3149
3167
  // src/bootstrap/loader.ts
3150
- import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync as statSync2 } from "fs";
3168
+ import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync } from "fs";
3151
3169
  import { join as join4 } from "path";
3152
- function loadFileWithCache(path, cache) {
3153
- if (!existsSync5(path)) return null;
3154
- try {
3155
- const stat = statSync2(path);
3156
- if (stat.mtimeMs !== cache.mtime) {
3157
- cache.content = readFileSync3(path, "utf-8").trim();
3158
- cache.mtime = stat.mtimeMs;
3159
- log(`[bootstrap] Loaded ${path} (${cache.content.length} chars)`);
3160
- syncNativeCliFiles();
3161
- }
3162
- return cache.content;
3163
- } catch {
3164
- return null;
3165
- }
3166
- }
3167
- function loadSoul() {
3168
- return loadFileWithCache(SOUL_PATH2, soulCacheObj);
3169
- }
3170
- function loadUser() {
3171
- return loadFileWithCache(USER_PATH2, userCacheObj);
3172
- }
3173
3170
  function searchContext(userMessage) {
3174
3171
  if (!existsSync5(CONTEXT_DIR2)) return null;
3175
3172
  const msgWords = new Set(
@@ -3201,24 +3198,16 @@ function searchContext(userMessage) {
3201
3198
  } catch {
3202
3199
  }
3203
3200
  if (bestMatch && bestMatch.score >= 2) {
3201
+ if (bestMatch.content.length > MAX_CONTEXT_CHARS) {
3202
+ return bestMatch.content.slice(0, MAX_CONTEXT_CHARS) + "\n\u2026(truncated)";
3203
+ }
3204
3204
  return bestMatch.content;
3205
3205
  }
3206
3206
  return null;
3207
3207
  }
3208
3208
  async function assembleBootstrapPrompt(userMessage, tier = "full", chatId) {
3209
3209
  const sections = [];
3210
- const soul = loadSoul();
3211
- if (soul) {
3212
- sections.push(`[System instructions \u2014 follow these]
3213
- ${soul}`);
3214
- }
3215
- if (tier === "full" || tier === "heartbeat") {
3216
- const user = loadUser();
3217
- if (user) {
3218
- sections.push(`[User profile]
3219
- ${user}`);
3220
- }
3221
- }
3210
+ syncNativeCliFiles();
3222
3211
  if (tier === "full") {
3223
3212
  const ctx = searchContext(userMessage);
3224
3213
  if (ctx) {
@@ -3226,9 +3215,11 @@ ${user}`);
3226
3215
  ${ctx}`);
3227
3216
  }
3228
3217
  }
3229
- const memory2 = await injectMemoryContext(userMessage);
3230
- if (memory2) {
3231
- sections.push(memory2);
3218
+ if (tier !== "slim") {
3219
+ const memory2 = await injectMemoryContext(userMessage);
3220
+ if (memory2) {
3221
+ sections.push(memory2);
3222
+ }
3232
3223
  }
3233
3224
  if (chatId && (tier === "full" || tier === "heartbeat")) {
3234
3225
  const orchestrationContext = buildOrchestrationContext(chatId);
@@ -3285,7 +3276,7 @@ ${boardText}`);
3285
3276
  return null;
3286
3277
  }
3287
3278
  }
3288
- var SOUL_PATH2, USER_PATH2, CONTEXT_DIR2, soulCacheObj, userCacheObj, ACTIVITY_TOKEN_BUDGET, INBOX_TOKEN_BUDGET, WHITEBOARD_TOKEN_BUDGET;
3279
+ var CONTEXT_DIR2, MAX_CONTEXT_CHARS, ACTIVITY_TOKEN_BUDGET, INBOX_TOKEN_BUDGET, WHITEBOARD_TOKEN_BUDGET;
3289
3280
  var init_loader = __esm({
3290
3281
  "src/bootstrap/loader.ts"() {
3291
3282
  "use strict";
@@ -3296,11 +3287,8 @@ var init_loader = __esm({
3296
3287
  init_store3();
3297
3288
  init_store4();
3298
3289
  init_store();
3299
- SOUL_PATH2 = join4(WORKSPACE_PATH, "SOUL.md");
3300
- USER_PATH2 = join4(WORKSPACE_PATH, "USER.md");
3301
3290
  CONTEXT_DIR2 = join4(WORKSPACE_PATH, "context");
3302
- soulCacheObj = { content: null, mtime: 0 };
3303
- userCacheObj = { content: null, mtime: 0 };
3291
+ MAX_CONTEXT_CHARS = 4e3;
3304
3292
  ACTIVITY_TOKEN_BUDGET = 1500;
3305
3293
  INBOX_TOKEN_BUDGET = 2e3;
3306
3294
  WHITEBOARD_TOKEN_BUDGET = 500;
@@ -3452,9 +3440,9 @@ ${transcript}`;
3452
3440
  if (ev.type === "result") {
3453
3441
  resultText = ev.resultText || accumulatedText;
3454
3442
  if (ev.usage) {
3455
- inputTokens += ev.usage.input;
3456
- outputTokens += ev.usage.output;
3457
- cacheReadTokens += ev.usage.cacheRead;
3443
+ inputTokens = ev.usage.input;
3444
+ outputTokens = ev.usage.output;
3445
+ cacheReadTokens = ev.usage.cacheRead;
3458
3446
  }
3459
3447
  if (adapter.shouldKillOnResult()) {
3460
3448
  rl2.close();
@@ -3579,20 +3567,20 @@ var init_registry = __esm({
3579
3567
  });
3580
3568
 
3581
3569
  // src/agents/roles.ts
3582
- function buildRoleInstructions(role, task, persona) {
3570
+ function buildRoleInstructions(role, task, persona, includeTools = true) {
3583
3571
  const rolePrompt = persona ?? ROLE_PRESETS[role] ?? ROLE_PRESETS.worker;
3584
3572
  const capitalizedRole = role.charAt(0).toUpperCase() + role.slice(1);
3585
- return [
3573
+ const parts = [
3586
3574
  `## Your Role: ${capitalizedRole}`,
3587
3575
  "",
3588
3576
  rolePrompt,
3589
- "",
3590
- ORCHESTRATOR_TOOLS,
3591
- "",
3592
- "## Task",
3593
- "",
3594
- task
3595
- ].join("\n");
3577
+ ""
3578
+ ];
3579
+ if (includeTools) {
3580
+ parts.push(ORCHESTRATOR_TOOLS, "");
3581
+ }
3582
+ parts.push("## Task", "", task);
3583
+ return parts.join("\n");
3596
3584
  }
3597
3585
  var ROLE_PRESETS, ORCHESTRATOR_TOOLS;
3598
3586
  var init_roles = __esm({
@@ -3660,7 +3648,8 @@ function buildAgentPrompt(opts, runnerSkillPath) {
3660
3648
  }
3661
3649
  parts.push("");
3662
3650
  }
3663
- parts.push(buildRoleInstructions(opts.role ?? "worker", opts.task, opts.persona));
3651
+ const includeTools = opts.includeOrchestratorTools !== false;
3652
+ parts.push(buildRoleInstructions(opts.role ?? "worker", opts.task, opts.persona, includeTools));
3664
3653
  return parts.join("\n");
3665
3654
  }
3666
3655
  function buildSpawnEnv(runner, isSubAgent = false) {
@@ -4243,7 +4232,8 @@ async function startAgent(agentId, chatId, opts) {
4243
4232
  role: opts.role,
4244
4233
  persona: opts.persona,
4245
4234
  extraArgs: mcpExtraArgs.length ? mcpExtraArgs : void 0,
4246
- isSubAgent: true
4235
+ isSubAgent: true,
4236
+ includeOrchestratorTools: process.env.DASHBOARD_ENABLED === "1"
4247
4237
  }, {
4248
4238
  onText: () => {
4249
4239
  updateAgentOutput(db3, agentId);
@@ -5866,8 +5856,8 @@ function askAgent(chatId, userMessage, opts) {
5866
5856
  return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
5867
5857
  }
5868
5858
  async function askAgentImpl(chatId, userMessage, opts) {
5869
- const { cwd, onStream, model: model2, permMode, onToolAction, bootstrapTier, timeoutMs } = opts ?? {};
5870
- const adapter = getAdapterForChat(chatId);
5859
+ const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs } = opts ?? {};
5860
+ const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(chatId);
5871
5861
  const mode = permMode ?? "yolo";
5872
5862
  const thinkingLevel = getThinkingLevel(chatId);
5873
5863
  const resolvedCwd = cwd ?? WORKSPACE_PATH;
@@ -5934,6 +5924,14 @@ async function askAgentImpl(chatId, userMessage, opts) {
5934
5924
  }
5935
5925
  if (result.resultText) {
5936
5926
  appendToLog(chatId, userMessage, result.resultText);
5927
+ const AUTO_SUMMARIZE_THRESHOLD = 30;
5928
+ const pairCount = getMessagePairCount(chatId);
5929
+ if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
5930
+ log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
5931
+ summarizeSession(chatId).catch((err) => {
5932
+ warn(`[agent] Auto-summarize failed for chat ${chatId}: ${err}`);
5933
+ });
5934
+ }
5937
5935
  }
5938
5936
  return {
5939
5937
  text: result.resultText || `(No response from ${adapter.displayName})`,
@@ -6125,6 +6123,9 @@ var init_delivery = __esm({
6125
6123
  // src/scheduler/retry.ts
6126
6124
  function classifyError(err) {
6127
6125
  const msg = err instanceof Error ? err.message : String(err);
6126
+ for (const pattern of EXHAUSTED_PATTERNS) {
6127
+ if (pattern.test(msg)) return "exhausted";
6128
+ }
6128
6129
  for (const pattern of TRANSIENT_PATTERNS) {
6129
6130
  if (pattern.test(msg)) return "transient";
6130
6131
  }
@@ -6138,10 +6139,21 @@ function getBackoffMs(retryCount) {
6138
6139
  const jitter = Math.floor(base * 0.2 * (Math.random() * 2 - 1));
6139
6140
  return base + jitter;
6140
6141
  }
6141
- var TRANSIENT_PATTERNS, PERMANENT_PATTERNS, BACKOFF_MS, MAX_RETRIES, AUTO_PAUSE_THRESHOLD;
6142
+ var EXHAUSTED_PATTERNS, TRANSIENT_PATTERNS, PERMANENT_PATTERNS, BACKOFF_MS, MAX_RETRIES, AUTO_PAUSE_THRESHOLD;
6142
6143
  var init_retry = __esm({
6143
6144
  "src/scheduler/retry.ts"() {
6144
6145
  "use strict";
6146
+ EXHAUSTED_PATTERNS = [
6147
+ /out of.*usage/i,
6148
+ /\d+-hour limit reached/i,
6149
+ /usage limit reached/i,
6150
+ /hit your.*limit/i,
6151
+ /usage.?limit/i,
6152
+ /exceeded your current quota/i,
6153
+ /RESOURCE.?EXHAUSTED/i,
6154
+ /insufficient.?quota/i,
6155
+ /check your plan and billing/i
6156
+ ];
6145
6157
  TRANSIENT_PATTERNS = [
6146
6158
  /rate.?limit/i,
6147
6159
  /too many requests/i,
@@ -6458,23 +6470,49 @@ async function runWithRetry(job, model2, runId, t0) {
6458
6470
  if (job.thinking && job.thinking !== "auto") {
6459
6471
  setThinkingLevel(chatId, job.thinking);
6460
6472
  }
6461
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
6462
- try {
6463
- const timeoutMs = job.timeout ? job.timeout * 1e3 : void 0;
6464
- const response = await askAgent(chatId, job.description, { model: model2, bootstrapTier: "slim", timeoutMs });
6465
- return response;
6466
- } catch (err) {
6467
- lastError = err;
6468
- const errorClass = classifyError(err);
6469
- if (errorClass === "permanent" || attempt >= MAX_RETRIES) {
6470
- throw err;
6473
+ const timeoutMs = job.timeout ? job.timeout * 1e3 : void 0;
6474
+ const primaryBackend = resolveJobBackendId(job);
6475
+ const chain = [
6476
+ { backend: primaryBackend, model: model2 },
6477
+ ...job.fallbacks ?? []
6478
+ ];
6479
+ for (let chainIdx = 0; chainIdx < chain.length; chainIdx++) {
6480
+ const { backend: currentBackend, model: currentModel } = chain[chainIdx];
6481
+ const isFallback = chainIdx > 0;
6482
+ if (isFallback) {
6483
+ log(`[scheduler] Job #${job.id} falling back to ${currentBackend}:${currentModel} (fallback ${chainIdx}/${job.fallbacks.length})`);
6484
+ }
6485
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
6486
+ try {
6487
+ const response = await askAgent(chatId, job.description, {
6488
+ model: currentModel,
6489
+ backend: currentBackend,
6490
+ bootstrapTier: "slim",
6491
+ timeoutMs
6492
+ });
6493
+ if (isFallback) {
6494
+ response.text = `[Fallback: ran on ${currentBackend}:${currentModel}]
6495
+
6496
+ ${response.text}`;
6497
+ }
6498
+ return response;
6499
+ } catch (err) {
6500
+ lastError = err;
6501
+ const errorClass = classifyError(err);
6502
+ if (errorClass === "exhausted") {
6503
+ log(`[scheduler] Job #${job.id} backend ${currentBackend} exhausted: ${errorMessage(err)}`);
6504
+ break;
6505
+ }
6506
+ if (errorClass === "permanent" || attempt >= MAX_RETRIES) {
6507
+ throw err;
6508
+ }
6509
+ const backoffMs = getBackoffMs(attempt);
6510
+ log(`[scheduler] Job #${job.id} transient error (attempt ${attempt + 1}/${MAX_RETRIES}), retrying in ${backoffMs / 1e3}s: ${errorMessage(err)}`);
6511
+ await new Promise((r) => setTimeout(r, backoffMs));
6471
6512
  }
6472
- const backoffMs = getBackoffMs(attempt);
6473
- log(`[scheduler] Job #${job.id} transient error (attempt ${attempt + 1}/${MAX_RETRIES}), retrying in ${backoffMs / 1e3}s: ${errorMessage(err)}`);
6474
- await new Promise((r) => setTimeout(r, backoffMs));
6475
6513
  }
6476
6514
  }
6477
- throw lastError ?? new Error("Unknown error");
6515
+ throw lastError ?? new Error("All backends exhausted");
6478
6516
  }
6479
6517
  function resolveJobBackendId(job) {
6480
6518
  return job.backend ?? (() => {
@@ -7146,6 +7184,7 @@ var init_telegram2 = __esm({
7146
7184
  // Skills & profile
7147
7185
  { command: "skills", description: "List and invoke skills" },
7148
7186
  { command: "voice", description: "Toggle voice responses" },
7187
+ { command: "imagine", description: "Generate an image from a prompt" },
7149
7188
  { command: "heartbeat", description: "Configure proactive heartbeat" },
7150
7189
  { command: "chats", description: "Manage multi-chat aliases" }
7151
7190
  ]);
@@ -7791,7 +7830,7 @@ async function finalizeProfile(chatId, state, channel) {
7791
7830
  "<!-- Add any additional preferences below this line -->",
7792
7831
  ""
7793
7832
  ].join("\n");
7794
- writeFileSync4(USER_PATH3, content, "utf-8");
7833
+ writeFileSync4(USER_PATH2, content, "utf-8");
7795
7834
  activeProfiles.delete(chatId);
7796
7835
  log(`[profile] User profile saved for chat ${chatId}`);
7797
7836
  await channel.sendText(
@@ -7817,23 +7856,23 @@ function extractUserUpdates(text) {
7817
7856
  return { cleanText, updates };
7818
7857
  }
7819
7858
  function appendToUserProfile(key, value) {
7820
- if (!existsSync11(USER_PATH3)) return;
7821
- const content = readFileSync7(USER_PATH3, "utf-8");
7859
+ if (!existsSync11(USER_PATH2)) return;
7860
+ const content = readFileSync7(USER_PATH2, "utf-8");
7822
7861
  const line = `- **${key}**: ${value}`;
7823
7862
  if (content.includes(line)) return;
7824
7863
  const updated = content.trimEnd() + `
7825
7864
  ${line}
7826
7865
  `;
7827
- writeFileSync4(USER_PATH3, updated, "utf-8");
7866
+ writeFileSync4(USER_PATH2, updated, "utf-8");
7828
7867
  log(`[profile] Appended preference: ${key}=${value}`);
7829
7868
  }
7830
- var USER_PATH3, activeProfiles;
7869
+ var USER_PATH2, activeProfiles;
7831
7870
  var init_profile = __esm({
7832
7871
  "src/bootstrap/profile.ts"() {
7833
7872
  "use strict";
7834
7873
  init_paths();
7835
7874
  init_log();
7836
- USER_PATH3 = join12(WORKSPACE_PATH, "USER.md");
7875
+ USER_PATH2 = join12(WORKSPACE_PATH, "USER.md");
7837
7876
  activeProfiles = /* @__PURE__ */ new Map();
7838
7877
  }
7839
7878
  });
@@ -8069,6 +8108,83 @@ var init_format_time = __esm({
8069
8108
  }
8070
8109
  });
8071
8110
 
8111
+ // src/media/image-gen.ts
8112
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync13 } from "fs";
8113
+ import { join as join14 } from "path";
8114
+ async function generateImage(prompt) {
8115
+ const apiKey = process.env.GEMINI_API_KEY;
8116
+ if (!apiKey) {
8117
+ throw new Error("Image generation requires GEMINI_API_KEY. Configure it in ~/.cc-claw/.env");
8118
+ }
8119
+ log(`[image-gen] Generating image: "${prompt.slice(0, 100)}"`);
8120
+ const response = await fetch(
8121
+ `https://generativelanguage.googleapis.com/v1beta/models/${IMAGE_MODEL}:generateContent?key=${apiKey}`,
8122
+ {
8123
+ method: "POST",
8124
+ headers: { "Content-Type": "application/json" },
8125
+ body: JSON.stringify({
8126
+ contents: [
8127
+ {
8128
+ parts: [{ text: prompt }]
8129
+ }
8130
+ ],
8131
+ generationConfig: {
8132
+ responseModalities: ["TEXT", "IMAGE"]
8133
+ }
8134
+ })
8135
+ }
8136
+ );
8137
+ if (!response.ok) {
8138
+ const errText = await response.text();
8139
+ throw new Error(`Gemini image API error: ${response.status} ${errText.slice(0, 300)}`);
8140
+ }
8141
+ const data = await response.json();
8142
+ const parts = data.candidates?.[0]?.content?.parts;
8143
+ if (!parts || parts.length === 0) {
8144
+ throw new Error("Gemini returned no content for image generation");
8145
+ }
8146
+ let imageData = null;
8147
+ let mimeType = "image/png";
8148
+ let textResponse = null;
8149
+ for (const part of parts) {
8150
+ if (part.inlineData) {
8151
+ imageData = part.inlineData.data;
8152
+ mimeType = part.inlineData.mimeType ?? "image/png";
8153
+ } else if (part.text) {
8154
+ textResponse = part.text;
8155
+ }
8156
+ }
8157
+ if (!imageData) {
8158
+ throw new Error(textResponse ?? "Gemini did not generate an image. The prompt may have been filtered.");
8159
+ }
8160
+ if (!existsSync13(IMAGE_OUTPUT_DIR)) {
8161
+ mkdirSync5(IMAGE_OUTPUT_DIR, { recursive: true });
8162
+ }
8163
+ const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
8164
+ const filename = `img_${Date.now()}.${ext}`;
8165
+ const filePath = join14(IMAGE_OUTPUT_DIR, filename);
8166
+ const buffer = Buffer.from(imageData, "base64");
8167
+ writeFileSync5(filePath, buffer);
8168
+ log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
8169
+ return { filePath, text: textResponse, mimeType };
8170
+ }
8171
+ function isImageGenAvailable() {
8172
+ return !!process.env.GEMINI_API_KEY;
8173
+ }
8174
+ var IMAGE_MODEL, IMAGE_OUTPUT_DIR;
8175
+ var init_image_gen = __esm({
8176
+ "src/media/image-gen.ts"() {
8177
+ "use strict";
8178
+ init_log();
8179
+ IMAGE_MODEL = "gemini-3.1-flash-image-preview";
8180
+ IMAGE_OUTPUT_DIR = join14(
8181
+ process.env.CC_CLAW_HOME ?? join14(process.env.HOME ?? "/tmp", ".cc-claw"),
8182
+ "data",
8183
+ "images"
8184
+ );
8185
+ }
8186
+ });
8187
+
8072
8188
  // src/voice/stt.ts
8073
8189
  import crypto from "crypto";
8074
8190
  import { execFile as execFile2 } from "child_process";
@@ -9137,7 +9253,7 @@ async function handleCommand(msg, channel) {
9137
9253
  case "help":
9138
9254
  await channel.sendText(
9139
9255
  chatId,
9140
- "Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/summarize - Save session to memory (without resetting)\n/summarize all - Summarize all pending sessions (pre-restart)\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/readonly/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/agents - List active sub-agents\n/tasks - Show task board for current orchestration\n/stopagent <id> - Cancel a specific sub-agent\n/stopall - Cancel all sub-agents in this chat\n/runners - List registered CLI runners\n/mcps - List registered MCP servers\n/help - Show this message",
9256
+ "Hey! I'm CC-Claw \u2014 your personal AI assistant on Telegram.\n\nI use AI coding CLIs (Claude, Gemini, Codex) as my brain. Just send me a message to get started.\n\nCommands:\n/backend [name] - Switch AI backend (or /claude /gemini /codex)\n/model - Switch model for active backend\n/summarizer - Configure session summarization model\n/status - Show session, model, backend, and usage\n/cost - Show estimated API cost (use /cost all for all-time)\n/usage - Show usage per backend with limits\n/limits - Configure usage limits per backend\n/newchat - Start a fresh conversation\n/summarize - Save session to memory (without resetting)\n/summarize all - Summarize all pending sessions (pre-restart)\n/cwd <path> - Set working directory\n/cwd - Show current working directory\n/memory - List stored memories\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/imagine <prompt> - Generate an image (or /image)\n/cron <description> - Schedule a task (or /schedule)\n/cron - List scheduled jobs (or /jobs)\n/cron cancel <id> - Cancel a job\n/cron pause <id> - Pause a job\n/cron resume <id> - Resume a job\n/cron run <id> - Trigger a job now\n/cron runs [id] - View run history\n/cron edit <id> - Edit a job\n/cron health - Scheduler health\n/skills - List skills from all backends\n/skill-install <url> - Install a skill from GitHub\n/setup-profile - Set up your user profile\n/chats - List authorized chats and aliases\n/heartbeat - Proactive awareness (on/off/interval/hours)\n/history - List recent session summaries\n/stop - Cancel the current running task\n/tools - Configure which tools the agent can use\n/permissions - Switch permission mode (yolo/safe/readonly/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/agents - List active sub-agents\n/tasks - Show task board for current orchestration\n/stopagent <id> - Cancel a specific sub-agent\n/stopall - Cancel all sub-agents in this chat\n/runners - List registered CLI runners\n/mcps - List registered MCP servers\n/help - Show this message",
9141
9257
  "plain"
9142
9258
  );
9143
9259
  break;
@@ -9620,6 +9736,30 @@ ${lines.join("\n")}`, "plain");
9620
9736
  );
9621
9737
  break;
9622
9738
  }
9739
+ case "imagine":
9740
+ case "image": {
9741
+ if (!commandArgs) {
9742
+ await channel.sendText(chatId, "Usage: /imagine <prompt>\nExample: /imagine a cat astronaut on Mars", "plain");
9743
+ return;
9744
+ }
9745
+ if (!isImageGenAvailable()) {
9746
+ await channel.sendText(chatId, "Image generation requires GEMINI_API_KEY. Configure it in ~/.cc-claw/.env", "plain");
9747
+ return;
9748
+ }
9749
+ await channel.sendText(chatId, "\u{1F3A8} Generating image\u2026", "plain");
9750
+ try {
9751
+ const result = await generateImage(commandArgs);
9752
+ const file = await readFile5(result.filePath);
9753
+ const name = result.filePath.split("/").pop() ?? "image.png";
9754
+ await channel.sendFile(chatId, file, name);
9755
+ if (result.text) {
9756
+ await channel.sendText(chatId, result.text, "plain");
9757
+ }
9758
+ } catch (err) {
9759
+ await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, "plain");
9760
+ }
9761
+ break;
9762
+ }
9623
9763
  case "schedule": {
9624
9764
  if (!commandArgs) {
9625
9765
  await channel.sendText(
@@ -9876,8 +10016,8 @@ Use /skills to see it.`, "plain");
9876
10016
  if (!lim.max_input_tokens) continue;
9877
10017
  const u = getBackendUsageInWindow(lim.backend, lim.window);
9878
10018
  const pct = (u.input_tokens / lim.max_input_tokens * 100).toFixed(0);
9879
- const warn2 = u.input_tokens / lim.max_input_tokens >= lim.warn_pct;
9880
- lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${warn2 ? " \u26A0\uFE0F" : ""}`);
10019
+ const warn3 = u.input_tokens / lim.max_input_tokens >= lim.warn_pct;
10020
+ lines.push(` ${lim.backend} (${lim.window}): ${pct}% of ${(lim.max_input_tokens / 1e3).toFixed(0)}K${warn3 ? " \u26A0\uFE0F" : ""}`);
9881
10021
  }
9882
10022
  }
9883
10023
  await channel.sendText(chatId, lines.join("\n"), "plain");
@@ -10377,7 +10517,16 @@ async function handleText(msg, channel) {
10377
10517
  return;
10378
10518
  }
10379
10519
  if (isChatBusy(chatId)) {
10380
- await channel.sendText(chatId, "I'm currently processing another request. Your message is queued and will be handled next.", "plain");
10520
+ if (typeof channel.sendKeyboard === "function") {
10521
+ pendingInterrupts.set(chatId, { msg, channel });
10522
+ await channel.sendKeyboard(chatId, "\u23F3 Agent is working on a request\u2026", [
10523
+ [
10524
+ { label: "\u{1F4E5} Queue message", data: `interrupt:queue:${chatId}` },
10525
+ { label: "\u26A1 Send now", data: `interrupt:now:${chatId}` }
10526
+ ]
10527
+ ]);
10528
+ return;
10529
+ }
10381
10530
  }
10382
10531
  let typingActive = true;
10383
10532
  const typingLoop = async () => {
@@ -10401,6 +10550,30 @@ async function handleText(msg, channel) {
10401
10550
  } catch (err) {
10402
10551
  error("[router] Error:", err);
10403
10552
  const errMsg = errorMessage(err);
10553
+ const errorClass = classifyError(err);
10554
+ if (errorClass === "exhausted" && typeof channel.sendKeyboard === "function") {
10555
+ const currentBackend = getBackend(chatId) ?? "claude";
10556
+ const { getAllAdapters: getAllAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
10557
+ const otherBackends = getAllAdapters2().filter((a) => a.id !== currentBackend);
10558
+ if (otherBackends.length > 0) {
10559
+ pendingFallbackMessages.set(chatId, { msg, channel });
10560
+ const buttons = otherBackends.map((a) => ({
10561
+ label: `\u{1F504} ${a.displayName}`,
10562
+ data: `fallback:${a.id}:${chatId}`
10563
+ }));
10564
+ buttons.push({ label: "\u274C Wait", data: `fallback:wait:${chatId}` });
10565
+ await channel.sendKeyboard(
10566
+ chatId,
10567
+ `\u26A0\uFE0F ${getAdapter(currentBackend).displayName} is out of usage.
10568
+
10569
+ ${errMsg}
10570
+
10571
+ Switch to another backend?`,
10572
+ [buttons]
10573
+ );
10574
+ return;
10575
+ }
10576
+ }
10404
10577
  const userMsg = diagnoseAgentError(errMsg, chatId);
10405
10578
  await channel.sendText(chatId, userMsg, "plain");
10406
10579
  } finally {
@@ -10505,16 +10678,48 @@ async function processFileSends2(chatId, channel, text) {
10505
10678
  }
10506
10679
  return text.replace(fileSendPattern, "").trim();
10507
10680
  }
10681
+ async function processImageGenerations(chatId, channel, text) {
10682
+ const pattern = /\[GENERATE_IMAGE:(.+?)\]/g;
10683
+ const prompts = [];
10684
+ for (const match of text.matchAll(pattern)) {
10685
+ prompts.push(match[1].trim());
10686
+ }
10687
+ if (prompts.length === 0) return text;
10688
+ if (!isImageGenAvailable()) {
10689
+ log("[router] [GENERATE_IMAGE] marker found but GEMINI_API_KEY not set");
10690
+ return text.replace(pattern, "(Image generation unavailable \u2014 GEMINI_API_KEY not configured)").trim();
10691
+ }
10692
+ for (const prompt of prompts) {
10693
+ try {
10694
+ const result = await generateImage(prompt);
10695
+ const file = await readFile5(result.filePath);
10696
+ const name = result.filePath.split("/").pop() ?? "image.png";
10697
+ await channel.sendFile(chatId, file, name);
10698
+ if (result.text) {
10699
+ await channel.sendText(chatId, result.text, "plain");
10700
+ }
10701
+ } catch (err) {
10702
+ error(`[router] Image generation failed for "${prompt.slice(0, 50)}":`, err);
10703
+ await channel.sendText(chatId, `Image generation failed: ${errorMessage(err)}`, "plain");
10704
+ }
10705
+ }
10706
+ return text.replace(pattern, "").trim();
10707
+ }
10508
10708
  async function processReaction(chatId, channel, text, messageId) {
10509
- const reactPattern = /\[REACT:(.+?)\]/;
10510
- const match = text.match(reactPattern);
10511
- if (!match) return text;
10512
- const emoji = match[1].trim();
10513
- if (messageId && typeof channel.reactToMessage === "function" && ALLOWED_REACTION_EMOJIS.has(emoji)) {
10514
- channel.reactToMessage(chatId, messageId, emoji).catch(() => {
10515
- });
10709
+ const reactPatternGlobal = /\[REACT:(.+?)\]/g;
10710
+ if (!reactPatternGlobal.test(text)) return text;
10711
+ let reacted = false;
10712
+ reactPatternGlobal.lastIndex = 0;
10713
+ let match;
10714
+ while ((match = reactPatternGlobal.exec(text)) !== null) {
10715
+ const emoji = match[1].trim();
10716
+ if (!reacted && messageId && typeof channel.reactToMessage === "function" && ALLOWED_REACTION_EMOJIS.has(emoji)) {
10717
+ channel.reactToMessage(chatId, messageId, emoji).catch(() => {
10718
+ });
10719
+ reacted = true;
10720
+ }
10516
10721
  }
10517
- return text.replace(reactPattern, "").trim();
10722
+ return text.replace(/\[REACT:(.+?)\]/g, "").trim();
10518
10723
  }
10519
10724
  async function sendResponse(chatId, channel, text, messageId) {
10520
10725
  text = await processReaction(chatId, channel, text, messageId);
@@ -10526,7 +10731,8 @@ async function sendResponse(chatId, channel, text, messageId) {
10526
10731
  ]);
10527
10732
  }
10528
10733
  }
10529
- const cleanText = await processFileSends2(chatId, channel, afterUpdates);
10734
+ const afterFiles = await processFileSends2(chatId, channel, afterUpdates);
10735
+ const cleanText = await processImageGenerations(chatId, channel, afterFiles);
10530
10736
  if (!cleanText) return;
10531
10737
  if (isVoiceEnabled(chatId)) {
10532
10738
  try {
@@ -10746,6 +10952,45 @@ ${PERM_MODES[chosen]}`,
10746
10952
  touchBookmark(chatId, alias);
10747
10953
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: `Working directory set to ${bookmark.path}`, detail: { field: "cwd", value: bookmark.path } });
10748
10954
  await sendCwdSessionChoice(chatId, bookmark.path, channel);
10955
+ } else if (data.startsWith("interrupt:")) {
10956
+ const parts = data.split(":");
10957
+ const action = parts[1];
10958
+ const targetChatId = parts.slice(2).join(":");
10959
+ const pending = pendingInterrupts.get(targetChatId);
10960
+ if (action === "now" && pending) {
10961
+ pendingInterrupts.delete(targetChatId);
10962
+ stopAgent(targetChatId);
10963
+ await channel.sendText(chatId, "\u26A1 Stopping current task and processing your message\u2026", "plain");
10964
+ await new Promise((r) => setTimeout(r, 500));
10965
+ await handleMessage(pending.msg, pending.channel);
10966
+ } else if (action === "queue" && pending) {
10967
+ pendingInterrupts.delete(targetChatId);
10968
+ await channel.sendText(chatId, "\u{1F4E5} Message queued \u2014 will process after current task.", "plain");
10969
+ handleMessage(pending.msg, pending.channel).catch(() => {
10970
+ });
10971
+ } else {
10972
+ await channel.sendText(chatId, "Message already processed or expired.", "plain");
10973
+ }
10974
+ } else if (data.startsWith("fallback:")) {
10975
+ const parts = data.split(":");
10976
+ const targetBackend = parts[1];
10977
+ const targetChatId = parts[2];
10978
+ const pendingMsg = pendingFallbackMessages.get(targetChatId);
10979
+ if (targetBackend === "wait") {
10980
+ pendingFallbackMessages.delete(targetChatId);
10981
+ await channel.sendText(chatId, "OK \u2014 you can switch manually with /backend when ready.", "plain");
10982
+ } else if (pendingMsg) {
10983
+ pendingFallbackMessages.delete(targetChatId);
10984
+ const { summarizeSession: summarizeSession2 } = await Promise.resolve().then(() => (init_summarize(), summarize_exports));
10985
+ await summarizeSession2(targetChatId);
10986
+ clearSession(targetChatId);
10987
+ setBackend(targetChatId, targetBackend);
10988
+ const adapter = getAdapter(targetBackend);
10989
+ await channel.sendText(chatId, `Switched to ${adapter.displayName}. Resending your message\u2026`, "plain");
10990
+ await handleMessage(pendingMsg.msg, pendingMsg.channel);
10991
+ } else {
10992
+ await channel.sendText(chatId, "Fallback expired. Use /backend to switch manually.", "plain");
10993
+ }
10749
10994
  } else if (data.startsWith("skills:page:")) {
10750
10995
  const page = parseInt(data.slice(12), 10);
10751
10996
  const skills2 = await discoverAllSkills();
@@ -10957,7 +11202,7 @@ Use /skills <page> to navigate (e.g. /skills 2)` : "";
10957
11202
  const header2 = totalPages > 1 ? `${skills2.length} skills (page ${safePage}/${totalPages}). Select one to invoke:` : `${skills2.length} skills available. Select one to invoke:`;
10958
11203
  await channel.sendKeyboard(chatId, header2, buttons);
10959
11204
  }
10960
- var PERM_MODES, VERBOSE_LEVELS, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
11205
+ var PERM_MODES, VERBOSE_LEVELS, pendingInterrupts, pendingFallbackMessages, CLI_INSTALL_HINTS, BLOCKED_PATH_PATTERNS2, ALLOWED_REACTION_EMOJIS, SKILLS_PER_PAGE;
10961
11206
  var init_router = __esm({
10962
11207
  "src/router.ts"() {
10963
11208
  "use strict";
@@ -10968,6 +11213,8 @@ var init_router = __esm({
10968
11213
  init_log();
10969
11214
  init_format_time();
10970
11215
  init_agent();
11216
+ init_retry();
11217
+ init_image_gen();
10971
11218
  init_stt();
10972
11219
  init_store4();
10973
11220
  init_summarize();
@@ -10996,6 +11243,8 @@ var init_router = __esm({
10996
11243
  normal: "Normal \u2014 summarized actions",
10997
11244
  verbose: "Verbose \u2014 full details"
10998
11245
  };
11246
+ pendingInterrupts = /* @__PURE__ */ new Map();
11247
+ pendingFallbackMessages = /* @__PURE__ */ new Map();
10999
11248
  CLI_INSTALL_HINTS = {
11000
11249
  claude: "Install: npm install -g @anthropic-ai/claude-code",
11001
11250
  gemini: "Install: npm install -g @anthropic-ai/gemini-cli",
@@ -11090,19 +11339,19 @@ var init_router = __esm({
11090
11339
  });
11091
11340
 
11092
11341
  // src/skills/bootstrap.ts
11093
- import { existsSync as existsSync13 } from "fs";
11342
+ import { existsSync as existsSync14 } from "fs";
11094
11343
  import { readdir as readdir3, readFile as readFile6, writeFile as writeFile3, copyFile } from "fs/promises";
11095
- import { join as join14, dirname as dirname3 } from "path";
11344
+ import { join as join15, dirname as dirname3 } from "path";
11096
11345
  import { fileURLToPath as fileURLToPath3 } from "url";
11097
11346
  async function copyAgentManifestSkills() {
11098
- if (!existsSync13(PKG_SKILLS)) return;
11347
+ if (!existsSync14(PKG_SKILLS)) return;
11099
11348
  try {
11100
11349
  const entries = await readdir3(PKG_SKILLS, { withFileTypes: true });
11101
11350
  for (const entry of entries) {
11102
11351
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
11103
- const src = join14(PKG_SKILLS, entry.name);
11104
- const dest = join14(SKILLS_PATH, entry.name);
11105
- if (existsSync13(dest)) continue;
11352
+ const src = join15(PKG_SKILLS, entry.name);
11353
+ const dest = join15(SKILLS_PATH, entry.name);
11354
+ if (existsSync14(dest)) continue;
11106
11355
  await copyFile(src, dest);
11107
11356
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
11108
11357
  }
@@ -11112,8 +11361,8 @@ async function copyAgentManifestSkills() {
11112
11361
  }
11113
11362
  async function bootstrapSkills() {
11114
11363
  await copyAgentManifestSkills();
11115
- const usmDir = join14(SKILLS_PATH, USM_DIR_NAME);
11116
- if (existsSync13(usmDir)) return;
11364
+ const usmDir = join15(SKILLS_PATH, USM_DIR_NAME);
11365
+ if (existsSync14(usmDir)) return;
11117
11366
  try {
11118
11367
  const entries = await readdir3(SKILLS_PATH);
11119
11368
  const dirs = entries.filter((e) => !e.startsWith("."));
@@ -11135,8 +11384,8 @@ async function bootstrapSkills() {
11135
11384
  }
11136
11385
  }
11137
11386
  async function patchUsmForCcClaw(usmDir) {
11138
- const skillPath = join14(usmDir, "SKILL.md");
11139
- if (!existsSync13(skillPath)) return;
11387
+ const skillPath = join15(usmDir, "SKILL.md");
11388
+ if (!existsSync14(skillPath)) return;
11140
11389
  try {
11141
11390
  let content = await readFile6(skillPath, "utf-8");
11142
11391
  let patched = false;
@@ -11181,8 +11430,8 @@ var init_bootstrap = __esm({
11181
11430
  USM_REPO = "jacob-bd/universal-skills-manager";
11182
11431
  USM_DIR_NAME = "universal-skills-manager";
11183
11432
  CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
11184
- PKG_ROOT = join14(dirname3(fileURLToPath3(import.meta.url)), "..", "..");
11185
- PKG_SKILLS = join14(PKG_ROOT, "skills");
11433
+ PKG_ROOT = join15(dirname3(fileURLToPath3(import.meta.url)), "..", "..");
11434
+ PKG_SKILLS = join15(PKG_ROOT, "skills");
11186
11435
  }
11187
11436
  });
11188
11437
 
@@ -11396,13 +11645,13 @@ __export(ai_skill_exports, {
11396
11645
  generateAiSkill: () => generateAiSkill,
11397
11646
  installAiSkill: () => installAiSkill
11398
11647
  });
11399
- import { existsSync as existsSync14, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
11400
- import { join as join15 } from "path";
11648
+ import { existsSync as existsSync15, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
11649
+ import { join as join16 } from "path";
11401
11650
  import { homedir as homedir4 } from "os";
11402
11651
  function generateAiSkill() {
11403
11652
  const version = VERSION;
11404
11653
  let systemState = "";
11405
- if (existsSync14(DB_PATH)) {
11654
+ if (existsSync15(DB_PATH)) {
11406
11655
  try {
11407
11656
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = (init_store4(), __toCommonJS(store_exports3));
11408
11657
  const readDb = openDatabaseReadOnly2();
@@ -11467,11 +11716,10 @@ cc-claw logs --error # Show error log
11467
11716
  \`\`\`bash
11468
11717
  cc-claw backend list --json # Available backends
11469
11718
  cc-claw backend get --json # Active backend
11470
- cc-claw backend set claude # Switch backend
11719
+ cc-claw backend set claude # Switch backend (claude/gemini/codex)
11471
11720
  cc-claw model list --json # Models for active backend
11721
+ cc-claw model get --json # Active model
11472
11722
  cc-claw model set claude-opus-4-6 # Switch model
11473
- cc-claw thinking get --json # Current thinking level
11474
- cc-claw thinking set high # Set thinking level
11475
11723
  \`\`\`
11476
11724
 
11477
11725
  ### Chat
@@ -11491,12 +11739,23 @@ cc-claw memory add "key" "value" # Save a memory (needs daemon)
11491
11739
  cc-claw memory forget "keyword" # Delete memories (needs daemon)
11492
11740
  \`\`\`
11493
11741
 
11742
+ ### Session
11743
+ \`\`\`bash
11744
+ cc-claw session get --json # Current session ID and age
11745
+ cc-claw session new # Clear session (newchat + summarize)
11746
+ \`\`\`
11747
+
11494
11748
  ### Scheduler (cron/schedule are aliases)
11495
11749
  \`\`\`bash
11496
11750
  cc-claw cron list --json # All scheduled jobs
11497
11751
  cc-claw cron health --json # Scheduler health
11498
11752
  cc-claw cron runs --json # Run history
11499
11753
  cc-claw cron create --description "Morning briefing" --cron "0 9 * * *" --backend claude
11754
+ cc-claw cron edit 3 --model gemini-3-flash-preview # Change job model
11755
+ cc-claw cron edit 3 --backend gemini --model gemini-3-flash-preview # Change backend + model
11756
+ cc-claw cron edit 3 --description "New task" --timeout 300 # Edit multiple fields
11757
+ cc-claw cron create --description "Report" --cron "0 9 * * *" --backend claude --model claude-sonnet-4-6 --fallback codex:gpt-5.4 --fallback gemini:gemini-3-flash-preview # With fallback chain
11758
+ cc-claw cron edit 3 --fallback codex:gpt-5.4 --fallback gemini:gemini-3-flash-preview # Set fallbacks
11500
11759
  cc-claw cron cancel 3 # Cancel job #3
11501
11760
  cc-claw cron pause 3 # Pause job
11502
11761
  cc-claw cron resume 3 # Resume job
@@ -11527,7 +11786,14 @@ cc-claw usage cost --json # Session cost estimate
11527
11786
  cc-claw usage cost --all --json # All-time cost by model
11528
11787
  cc-claw usage tokens --json # Backend usage in last 24h
11529
11788
  cc-claw usage limits list --json # Current limits
11530
- cc-claw usage limits set claude daily 500000
11789
+ cc-claw usage limits set claude daily 500000 # Set a limit
11790
+ cc-claw usage limits clear claude daily # Remove a limit
11791
+ \`\`\`
11792
+
11793
+ ### Thinking / Reasoning
11794
+ \`\`\`bash
11795
+ cc-claw thinking get --json # Current thinking level
11796
+ cc-claw thinking set high # Set level (auto/off/low/medium/high/extra_high)
11531
11797
  \`\`\`
11532
11798
 
11533
11799
  ### Permissions & Tools
@@ -11537,10 +11803,71 @@ cc-claw permissions set yolo # Set mode (yolo/safe/readonly/plan)
11537
11803
  cc-claw tools list --json # Tools with enabled/disabled status
11538
11804
  cc-claw tools enable Bash # Enable a tool
11539
11805
  cc-claw tools disable WebFetch # Disable a tool
11806
+ cc-claw tools reset # Reset to defaults (all on)
11540
11807
  cc-claw verbose get --json # Tool visibility level
11541
11808
  cc-claw verbose set verbose # Set level (off/normal/verbose)
11542
11809
  \`\`\`
11543
11810
 
11811
+ ### Working Directory
11812
+ \`\`\`bash
11813
+ cc-claw cwd get --json # Show current working directory
11814
+ cc-claw cwd set /path/to/dir # Set working directory
11815
+ cc-claw cwd clear # Reset to default
11816
+ \`\`\`
11817
+
11818
+ ### Voice
11819
+ \`\`\`bash
11820
+ cc-claw voice get --json # Show voice status
11821
+ cc-claw voice set on # Enable voice responses
11822
+ cc-claw voice set off # Disable voice responses
11823
+ \`\`\`
11824
+
11825
+ ### Image Generation
11826
+ \`\`\`bash
11827
+ # Via Telegram command:
11828
+ /imagine a futuristic city at sunset # Generate image from prompt
11829
+ /image a cat astronaut on Mars # Alias for /imagine
11830
+
11831
+ # Via AI marker in responses:
11832
+ # The AI can write [GENERATE_IMAGE:prompt] in its response to trigger generation
11833
+ # Requires GEMINI_API_KEY in ~/.cc-claw/.env
11834
+ \`\`\`
11835
+
11836
+ ### Heartbeat
11837
+ \`\`\`bash
11838
+ cc-claw heartbeat get --json # Show heartbeat config
11839
+ cc-claw heartbeat set on # Enable proactive awareness
11840
+ cc-claw heartbeat set off # Disable heartbeat
11841
+ cc-claw heartbeat set interval 30m # Set check interval
11842
+ cc-claw heartbeat set hours 9-22 # Set active hours
11843
+ \`\`\`
11844
+
11845
+ ### Summarizer
11846
+ \`\`\`bash
11847
+ cc-claw summarizer get --json # Current summarizer config
11848
+ cc-claw summarizer set auto # Auto (use active backend's summarizer)
11849
+ cc-claw summarizer set off # Disable summarization
11850
+ cc-claw summarizer set claude:claude-haiku-4-5 # Pin specific backend:model
11851
+ \`\`\`
11852
+
11853
+ ### Chat Aliases
11854
+ \`\`\`bash
11855
+ cc-claw chats list --json # Authorized chats and aliases
11856
+ cc-claw chats alias 123456789 "work" # Set alias for a chat
11857
+ cc-claw chats remove-alias "work" # Remove alias
11858
+ \`\`\`
11859
+
11860
+ ### Skills
11861
+ \`\`\`bash
11862
+ cc-claw skills list --json # All skills from all backends
11863
+ cc-claw skills install <github-url> # Install skill from GitHub
11864
+ \`\`\`
11865
+
11866
+ ### MCP Servers
11867
+ \`\`\`bash
11868
+ cc-claw mcps list --json # Registered MCP servers
11869
+ \`\`\`
11870
+
11544
11871
  ### Database
11545
11872
  \`\`\`bash
11546
11873
  cc-claw db stats --json # Row counts, file size, WAL status
@@ -11561,15 +11888,10 @@ cc-claw service uninstall # Remove service
11561
11888
 
11562
11889
  ### Other
11563
11890
  \`\`\`bash
11564
- cc-claw skills list --json # Available skills
11565
- cc-claw mcps list --json # MCP servers
11566
- cc-claw chats list --json # Chat aliases
11567
- cc-claw session get --json # Current session
11568
- cc-claw session new # Clear session
11569
- cc-claw cwd get --json # Working directory
11570
- cc-claw voice get --json # Voice status
11571
- cc-claw heartbeat get --json # Heartbeat config
11572
- cc-claw summarizer get --json # Summarizer config
11891
+ cc-claw setup # Interactive configuration wizard
11892
+ cc-claw tui # Interactive terminal chat
11893
+ cc-claw completion --shell zsh # Generate shell completions (bash/zsh/fish)
11894
+ cc-claw --ai # Generate/install SKILL.md for AI tools
11573
11895
  \`\`\`
11574
11896
 
11575
11897
  ## JSON Output Format
@@ -11625,11 +11947,11 @@ function installAiSkill() {
11625
11947
  const failed = [];
11626
11948
  for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
11627
11949
  for (const dir of dirs) {
11628
- const skillDir = join15(dir, "cc-claw-cli");
11629
- const skillPath = join15(skillDir, "SKILL.md");
11950
+ const skillDir = join16(dir, "cc-claw-cli");
11951
+ const skillPath = join16(skillDir, "SKILL.md");
11630
11952
  try {
11631
- mkdirSync5(skillDir, { recursive: true });
11632
- writeFileSync5(skillPath, skill, "utf-8");
11953
+ mkdirSync6(skillDir, { recursive: true });
11954
+ writeFileSync6(skillPath, skill, "utf-8");
11633
11955
  installed.push(skillPath);
11634
11956
  } catch {
11635
11957
  failed.push(skillPath);
@@ -11645,10 +11967,10 @@ var init_ai_skill = __esm({
11645
11967
  init_paths();
11646
11968
  init_version();
11647
11969
  BACKEND_SKILL_DIRS2 = {
11648
- "cc-claw": [join15(homedir4(), ".cc-claw", "workspace", "skills")],
11649
- claude: [join15(homedir4(), ".claude", "skills")],
11650
- gemini: [join15(homedir4(), ".gemini", "skills")],
11651
- codex: [join15(homedir4(), ".agents", "skills")]
11970
+ "cc-claw": [join16(homedir4(), ".cc-claw", "workspace", "skills")],
11971
+ claude: [join16(homedir4(), ".claude", "skills")],
11972
+ gemini: [join16(homedir4(), ".gemini", "skills")],
11973
+ codex: [join16(homedir4(), ".agents", "skills")]
11652
11974
  };
11653
11975
  }
11654
11976
  });
@@ -11658,21 +11980,21 @@ var index_exports = {};
11658
11980
  __export(index_exports, {
11659
11981
  main: () => main
11660
11982
  });
11661
- import { mkdirSync as mkdirSync6, existsSync as existsSync15, renameSync, statSync as statSync3 } from "fs";
11662
- import { join as join16 } from "path";
11983
+ import { mkdirSync as mkdirSync7, existsSync as existsSync16, renameSync, statSync as statSync2 } from "fs";
11984
+ import { join as join17 } from "path";
11663
11985
  import dotenv from "dotenv";
11664
11986
  function migrateLayout() {
11665
11987
  const moves = [
11666
- [join16(CC_CLAW_HOME, "cc-claw.db"), join16(DATA_PATH, "cc-claw.db")],
11667
- [join16(CC_CLAW_HOME, "cc-claw.db-shm"), join16(DATA_PATH, "cc-claw.db-shm")],
11668
- [join16(CC_CLAW_HOME, "cc-claw.db-wal"), join16(DATA_PATH, "cc-claw.db-wal")],
11669
- [join16(CC_CLAW_HOME, "cc-claw.log"), join16(LOGS_PATH, "cc-claw.log")],
11670
- [join16(CC_CLAW_HOME, "cc-claw.log.1"), join16(LOGS_PATH, "cc-claw.log.1")],
11671
- [join16(CC_CLAW_HOME, "cc-claw.error.log"), join16(LOGS_PATH, "cc-claw.error.log")],
11672
- [join16(CC_CLAW_HOME, "cc-claw.error.log.1"), join16(LOGS_PATH, "cc-claw.error.log.1")]
11988
+ [join17(CC_CLAW_HOME, "cc-claw.db"), join17(DATA_PATH, "cc-claw.db")],
11989
+ [join17(CC_CLAW_HOME, "cc-claw.db-shm"), join17(DATA_PATH, "cc-claw.db-shm")],
11990
+ [join17(CC_CLAW_HOME, "cc-claw.db-wal"), join17(DATA_PATH, "cc-claw.db-wal")],
11991
+ [join17(CC_CLAW_HOME, "cc-claw.log"), join17(LOGS_PATH, "cc-claw.log")],
11992
+ [join17(CC_CLAW_HOME, "cc-claw.log.1"), join17(LOGS_PATH, "cc-claw.log.1")],
11993
+ [join17(CC_CLAW_HOME, "cc-claw.error.log"), join17(LOGS_PATH, "cc-claw.error.log")],
11994
+ [join17(CC_CLAW_HOME, "cc-claw.error.log.1"), join17(LOGS_PATH, "cc-claw.error.log.1")]
11673
11995
  ];
11674
11996
  for (const [from, to] of moves) {
11675
- if (existsSync15(from) && !existsSync15(to)) {
11997
+ if (existsSync16(from) && !existsSync16(to)) {
11676
11998
  try {
11677
11999
  renameSync(from, to);
11678
12000
  } catch {
@@ -11683,7 +12005,7 @@ function migrateLayout() {
11683
12005
  function rotateLogs() {
11684
12006
  for (const file of [LOG_PATH, ERROR_LOG_PATH]) {
11685
12007
  try {
11686
- const { size } = statSync3(file);
12008
+ const { size } = statSync2(file);
11687
12009
  if (size > LOG_MAX_BYTES) {
11688
12010
  const archivePath = `${file}.1`;
11689
12011
  try {
@@ -11767,11 +12089,11 @@ async function main() {
11767
12089
  bootstrapSkills().catch((err) => error("[cc-claw] Skill bootstrap failed:", err));
11768
12090
  try {
11769
12091
  const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
11770
- const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync10 } = await import("fs");
11771
- const { join: join19 } = await import("path");
11772
- const skillDir = join19(SKILLS_PATH, "cc-claw-cli");
11773
- mkdirSync10(skillDir, { recursive: true });
11774
- writeFileSync8(join19(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
12092
+ const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync11 } = await import("fs");
12093
+ const { join: join20 } = await import("path");
12094
+ const skillDir = join20(SKILLS_PATH, "cc-claw-cli");
12095
+ mkdirSync11(skillDir, { recursive: true });
12096
+ writeFileSync9(join20(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
11775
12097
  log("[cc-claw] AI skill updated");
11776
12098
  } catch {
11777
12099
  }
@@ -11831,10 +12153,10 @@ var init_index = __esm({
11831
12153
  init_bootstrap2();
11832
12154
  init_health3();
11833
12155
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
11834
- if (!existsSync15(dir)) mkdirSync6(dir, { recursive: true });
12156
+ if (!existsSync16(dir)) mkdirSync7(dir, { recursive: true });
11835
12157
  }
11836
12158
  migrateLayout();
11837
- if (existsSync15(ENV_PATH)) {
12159
+ if (existsSync16(ENV_PATH)) {
11838
12160
  dotenv.config({ path: ENV_PATH });
11839
12161
  } else {
11840
12162
  console.error(`[cc-claw] Config not found at ${ENV_PATH} \u2014 run 'cc-claw setup' first`);
@@ -11855,10 +12177,10 @@ __export(service_exports, {
11855
12177
  serviceStatus: () => serviceStatus,
11856
12178
  uninstallService: () => uninstallService
11857
12179
  });
11858
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
12180
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
11859
12181
  import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
11860
12182
  import { homedir as homedir5, platform } from "os";
11861
- import { join as join17, dirname as dirname4 } from "path";
12183
+ import { join as join18, dirname as dirname4 } from "path";
11862
12184
  function resolveExecutable2(name) {
11863
12185
  try {
11864
12186
  return execFileSync2("which", [name], { encoding: "utf-8" }).trim();
@@ -11871,14 +12193,14 @@ function getPathDirs() {
11871
12193
  const home = homedir5();
11872
12194
  const dirs = /* @__PURE__ */ new Set([
11873
12195
  nodeBin,
11874
- join17(home, ".local", "bin"),
12196
+ join18(home, ".local", "bin"),
11875
12197
  "/usr/local/bin",
11876
12198
  "/usr/bin",
11877
12199
  "/bin"
11878
12200
  ]);
11879
12201
  try {
11880
12202
  const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
11881
- if (prefix) dirs.add(join17(prefix, "bin"));
12203
+ if (prefix) dirs.add(join18(prefix, "bin"));
11882
12204
  } catch {
11883
12205
  }
11884
12206
  return [...dirs].join(":");
@@ -11933,21 +12255,21 @@ function generatePlist() {
11933
12255
  }
11934
12256
  function installMacOS() {
11935
12257
  const agentsDir = dirname4(PLIST_PATH);
11936
- if (!existsSync16(agentsDir)) mkdirSync7(agentsDir, { recursive: true });
11937
- if (!existsSync16(LOGS_PATH)) mkdirSync7(LOGS_PATH, { recursive: true });
11938
- if (existsSync16(PLIST_PATH)) {
12258
+ if (!existsSync17(agentsDir)) mkdirSync8(agentsDir, { recursive: true });
12259
+ if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
12260
+ if (existsSync17(PLIST_PATH)) {
11939
12261
  try {
11940
12262
  execFileSync2("launchctl", ["unload", PLIST_PATH]);
11941
12263
  } catch {
11942
12264
  }
11943
12265
  }
11944
- writeFileSync6(PLIST_PATH, generatePlist());
12266
+ writeFileSync7(PLIST_PATH, generatePlist());
11945
12267
  console.log(` Installed: ${PLIST_PATH}`);
11946
12268
  execFileSync2("launchctl", ["load", PLIST_PATH]);
11947
12269
  console.log(" Service loaded and starting.");
11948
12270
  }
11949
12271
  function uninstallMacOS() {
11950
- if (!existsSync16(PLIST_PATH)) {
12272
+ if (!existsSync17(PLIST_PATH)) {
11951
12273
  console.log(" No service found to uninstall.");
11952
12274
  return;
11953
12275
  }
@@ -11998,9 +12320,9 @@ WantedBy=default.target
11998
12320
  `;
11999
12321
  }
12000
12322
  function installLinux() {
12001
- if (!existsSync16(SYSTEMD_DIR)) mkdirSync7(SYSTEMD_DIR, { recursive: true });
12002
- if (!existsSync16(LOGS_PATH)) mkdirSync7(LOGS_PATH, { recursive: true });
12003
- writeFileSync6(UNIT_PATH, generateUnit());
12323
+ if (!existsSync17(SYSTEMD_DIR)) mkdirSync8(SYSTEMD_DIR, { recursive: true });
12324
+ if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
12325
+ writeFileSync7(UNIT_PATH, generateUnit());
12004
12326
  console.log(` Installed: ${UNIT_PATH}`);
12005
12327
  execFileSync2("systemctl", ["--user", "daemon-reload"]);
12006
12328
  execFileSync2("systemctl", ["--user", "enable", "cc-claw"]);
@@ -12008,7 +12330,7 @@ function installLinux() {
12008
12330
  console.log(" Service enabled and started.");
12009
12331
  }
12010
12332
  function uninstallLinux() {
12011
- if (!existsSync16(UNIT_PATH)) {
12333
+ if (!existsSync17(UNIT_PATH)) {
12012
12334
  console.log(" No service found to uninstall.");
12013
12335
  return;
12014
12336
  }
@@ -12033,7 +12355,7 @@ function statusLinux() {
12033
12355
  }
12034
12356
  }
12035
12357
  function installService() {
12036
- if (!existsSync16(join17(CC_CLAW_HOME, ".env"))) {
12358
+ if (!existsSync17(join18(CC_CLAW_HOME, ".env"))) {
12037
12359
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
12038
12360
  console.error(" Run 'cc-claw setup' before installing the service.");
12039
12361
  process.exitCode = 1;
@@ -12062,9 +12384,9 @@ var init_service = __esm({
12062
12384
  "use strict";
12063
12385
  init_paths();
12064
12386
  PLIST_LABEL = "com.cc-claw";
12065
- PLIST_PATH = join17(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
12066
- SYSTEMD_DIR = join17(homedir5(), ".config", "systemd", "user");
12067
- UNIT_PATH = join17(SYSTEMD_DIR, "cc-claw.service");
12387
+ PLIST_PATH = join18(homedir5(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
12388
+ SYSTEMD_DIR = join18(homedir5(), ".config", "systemd", "user");
12389
+ UNIT_PATH = join18(SYSTEMD_DIR, "cc-claw.service");
12068
12390
  }
12069
12391
  });
12070
12392
 
@@ -12263,12 +12585,12 @@ __export(api_client_exports, {
12263
12585
  apiPost: () => apiPost,
12264
12586
  isDaemonRunning: () => isDaemonRunning
12265
12587
  });
12266
- import { readFileSync as readFileSync12, existsSync as existsSync17 } from "fs";
12588
+ import { readFileSync as readFileSync12, existsSync as existsSync18 } from "fs";
12267
12589
  import { request as httpRequest } from "http";
12268
12590
  function getToken() {
12269
12591
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
12270
12592
  try {
12271
- if (existsSync17(TOKEN_PATH)) return readFileSync12(TOKEN_PATH, "utf-8").trim();
12593
+ if (existsSync18(TOKEN_PATH)) return readFileSync12(TOKEN_PATH, "utf-8").trim();
12272
12594
  } catch {
12273
12595
  }
12274
12596
  return null;
@@ -12361,7 +12683,7 @@ var status_exports = {};
12361
12683
  __export(status_exports, {
12362
12684
  statusCommand: () => statusCommand
12363
12685
  });
12364
- import { existsSync as existsSync18, statSync as statSync4 } from "fs";
12686
+ import { existsSync as existsSync19, statSync as statSync3 } from "fs";
12365
12687
  async function statusCommand(globalOpts, localOpts) {
12366
12688
  try {
12367
12689
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
@@ -12401,7 +12723,7 @@ async function statusCommand(globalOpts, localOpts) {
12401
12723
  const cwdRow = readDb.prepare("SELECT cwd FROM chat_cwd WHERE chat_id = ?").get(chatId);
12402
12724
  const voiceRow = readDb.prepare("SELECT enabled FROM chat_voice WHERE chat_id = ?").get(chatId);
12403
12725
  const usageRow = readDb.prepare("SELECT * FROM chat_usage WHERE chat_id = ?").get(chatId);
12404
- const dbStat = existsSync18(DB_PATH) ? statSync4(DB_PATH) : null;
12726
+ const dbStat = existsSync19(DB_PATH) ? statSync3(DB_PATH) : null;
12405
12727
  let daemonRunning = false;
12406
12728
  let daemonInfo = {};
12407
12729
  if (localOpts.deep) {
@@ -12492,12 +12814,12 @@ var doctor_exports = {};
12492
12814
  __export(doctor_exports, {
12493
12815
  doctorCommand: () => doctorCommand
12494
12816
  });
12495
- import { existsSync as existsSync19, statSync as statSync5, accessSync, constants } from "fs";
12817
+ import { existsSync as existsSync20, statSync as statSync4, accessSync, constants } from "fs";
12496
12818
  import { execFileSync as execFileSync3 } from "child_process";
12497
12819
  async function doctorCommand(globalOpts, localOpts) {
12498
12820
  const checks = [];
12499
- if (existsSync19(DB_PATH)) {
12500
- const size = statSync5(DB_PATH).size;
12821
+ if (existsSync20(DB_PATH)) {
12822
+ const size = statSync4(DB_PATH).size;
12501
12823
  checks.push({ name: "Database", status: "ok", message: `${DB_PATH} (${(size / 1024).toFixed(0)}KB)` });
12502
12824
  try {
12503
12825
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
@@ -12526,7 +12848,7 @@ async function doctorCommand(globalOpts, localOpts) {
12526
12848
  } else {
12527
12849
  checks.push({ name: "Database", status: "error", message: `Not found at ${DB_PATH}`, fix: "cc-claw setup" });
12528
12850
  }
12529
- if (existsSync19(ENV_PATH)) {
12851
+ if (existsSync20(ENV_PATH)) {
12530
12852
  checks.push({ name: "Environment", status: "ok", message: `.env loaded` });
12531
12853
  } else {
12532
12854
  checks.push({ name: "Environment", status: "error", message: "No .env found", fix: "cc-claw setup" });
@@ -12566,7 +12888,7 @@ async function doctorCommand(globalOpts, localOpts) {
12566
12888
  checks.push({ name: "Daemon", status: "warning", message: "could not probe" });
12567
12889
  }
12568
12890
  const tokenPath = `${DATA_PATH}/api-token`;
12569
- if (existsSync19(tokenPath)) {
12891
+ if (existsSync20(tokenPath)) {
12570
12892
  try {
12571
12893
  accessSync(tokenPath, constants.R_OK);
12572
12894
  checks.push({ name: "API token", status: "ok", message: "token file readable" });
@@ -12591,7 +12913,7 @@ async function doctorCommand(globalOpts, localOpts) {
12591
12913
  }
12592
12914
  } catch {
12593
12915
  }
12594
- if (existsSync19(ERROR_LOG_PATH)) {
12916
+ if (existsSync20(ERROR_LOG_PATH)) {
12595
12917
  try {
12596
12918
  const { readFileSync: readFileSync17 } = await import("fs");
12597
12919
  const logContent = readFileSync17(ERROR_LOG_PATH, "utf-8");
@@ -12710,10 +13032,10 @@ var logs_exports = {};
12710
13032
  __export(logs_exports, {
12711
13033
  logsCommand: () => logsCommand
12712
13034
  });
12713
- import { existsSync as existsSync20, readFileSync as readFileSync13, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
13035
+ import { existsSync as existsSync21, readFileSync as readFileSync13, watchFile as watchFile2, unwatchFile as unwatchFile2 } from "fs";
12714
13036
  async function logsCommand(opts) {
12715
13037
  const logFile = opts.error ? ERROR_LOG_PATH : LOG_PATH;
12716
- if (!existsSync20(logFile)) {
13038
+ if (!existsSync21(logFile)) {
12717
13039
  outputError("LOG_NOT_FOUND", `Log file not found: ${logFile}`);
12718
13040
  process.exit(1);
12719
13041
  }
@@ -12761,12 +13083,12 @@ __export(backend_exports, {
12761
13083
  backendList: () => backendList,
12762
13084
  backendSet: () => backendSet
12763
13085
  });
12764
- import { existsSync as existsSync21 } from "fs";
13086
+ import { existsSync as existsSync22 } from "fs";
12765
13087
  async function backendList(globalOpts) {
12766
13088
  const { getAllAdapters: getAllAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
12767
13089
  const chatId = resolveChatId(globalOpts);
12768
13090
  let activeBackend = null;
12769
- if (existsSync21(DB_PATH)) {
13091
+ if (existsSync22(DB_PATH)) {
12770
13092
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
12771
13093
  const readDb = openDatabaseReadOnly2();
12772
13094
  try {
@@ -12797,7 +13119,7 @@ async function backendList(globalOpts) {
12797
13119
  }
12798
13120
  async function backendGet(globalOpts) {
12799
13121
  const chatId = resolveChatId(globalOpts);
12800
- if (!existsSync21(DB_PATH)) {
13122
+ if (!existsSync22(DB_PATH)) {
12801
13123
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
12802
13124
  process.exit(1);
12803
13125
  }
@@ -12841,13 +13163,13 @@ __export(model_exports, {
12841
13163
  modelList: () => modelList,
12842
13164
  modelSet: () => modelSet
12843
13165
  });
12844
- import { existsSync as existsSync22 } from "fs";
13166
+ import { existsSync as existsSync23 } from "fs";
12845
13167
  async function modelList(globalOpts) {
12846
13168
  const chatId = resolveChatId(globalOpts);
12847
13169
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
12848
13170
  const { getAdapter: getAdapter2, getAllAdapters: getAllAdapters2 } = await Promise.resolve().then(() => (init_backends(), backends_exports));
12849
13171
  let backendId = "claude";
12850
- if (existsSync22(DB_PATH)) {
13172
+ if (existsSync23(DB_PATH)) {
12851
13173
  const readDb = openDatabaseReadOnly2();
12852
13174
  try {
12853
13175
  const row = readDb.prepare("SELECT backend FROM chat_backend WHERE chat_id = ?").get(chatId);
@@ -12880,7 +13202,7 @@ async function modelList(globalOpts) {
12880
13202
  }
12881
13203
  async function modelGet(globalOpts) {
12882
13204
  const chatId = resolveChatId(globalOpts);
12883
- if (!existsSync22(DB_PATH)) {
13205
+ if (!existsSync23(DB_PATH)) {
12884
13206
  outputError("DB_NOT_FOUND", "Database not found.");
12885
13207
  process.exit(1);
12886
13208
  }
@@ -12924,9 +13246,9 @@ __export(memory_exports, {
12924
13246
  memoryList: () => memoryList,
12925
13247
  memorySearch: () => memorySearch
12926
13248
  });
12927
- import { existsSync as existsSync23 } from "fs";
13249
+ import { existsSync as existsSync24 } from "fs";
12928
13250
  async function memoryList(globalOpts) {
12929
- if (!existsSync23(DB_PATH)) {
13251
+ if (!existsSync24(DB_PATH)) {
12930
13252
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
12931
13253
  process.exit(1);
12932
13254
  }
@@ -12950,7 +13272,7 @@ async function memoryList(globalOpts) {
12950
13272
  });
12951
13273
  }
12952
13274
  async function memorySearch(globalOpts, query) {
12953
- if (!existsSync23(DB_PATH)) {
13275
+ if (!existsSync24(DB_PATH)) {
12954
13276
  outputError("DB_NOT_FOUND", "Database not found.");
12955
13277
  process.exit(1);
12956
13278
  }
@@ -12972,7 +13294,7 @@ async function memorySearch(globalOpts, query) {
12972
13294
  });
12973
13295
  }
12974
13296
  async function memoryHistory(globalOpts, opts) {
12975
- if (!existsSync23(DB_PATH)) {
13297
+ if (!existsSync24(DB_PATH)) {
12976
13298
  outputError("DB_NOT_FOUND", "Database not found.");
12977
13299
  process.exit(1);
12978
13300
  }
@@ -13020,7 +13342,17 @@ __export(cron_exports2, {
13020
13342
  cronList: () => cronList,
13021
13343
  cronRuns: () => cronRuns
13022
13344
  });
13023
- import { existsSync as existsSync24 } from "fs";
13345
+ import { existsSync as existsSync25 } from "fs";
13346
+ function parseFallbacks(raw) {
13347
+ return raw.slice(0, 3).map((f) => {
13348
+ const [backend2, ...rest] = f.split(":");
13349
+ const model2 = rest.join(":");
13350
+ if (!backend2 || !model2) {
13351
+ throw new Error(`Invalid fallback format "${f}" \u2014 expected backend:model (e.g. gemini:gemini-3-flash-preview)`);
13352
+ }
13353
+ return { backend: backend2, model: model2 };
13354
+ });
13355
+ }
13024
13356
  function parseAndValidateTimeout(raw) {
13025
13357
  if (!raw) return null;
13026
13358
  const val = parseInt(raw, 10);
@@ -13031,7 +13363,7 @@ function parseAndValidateTimeout(raw) {
13031
13363
  return val;
13032
13364
  }
13033
13365
  async function cronList(globalOpts) {
13034
- if (!existsSync24(DB_PATH)) {
13366
+ if (!existsSync25(DB_PATH)) {
13035
13367
  outputError("DB_NOT_FOUND", "Database not found.");
13036
13368
  process.exit(1);
13037
13369
  }
@@ -13052,15 +13384,24 @@ async function cronList(globalOpts) {
13052
13384
  lines.push(` ${statusDot(status)} #${j.id} [${status}] ${schedule2}${tz}`);
13053
13385
  lines.push(` ${j.description}`);
13054
13386
  if (j.backend) lines.push(` Backend: ${j.backend}${j.model ? ` / ${j.model}` : ""}`);
13387
+ if (j.fallbacks) {
13388
+ try {
13389
+ const fb = JSON.parse(j.fallbacks);
13390
+ if (Array.isArray(fb) && fb.length > 0) {
13391
+ lines.push(` Fallbacks: ${fb.map((f) => `${f.backend}:${f.model}`).join(" \u2192 ")}`);
13392
+ }
13393
+ } catch {
13394
+ }
13395
+ }
13055
13396
  if (j.timeout) lines.push(` Timeout: ${j.timeout}s`);
13056
- if (j.next_run_at) lines.push(` Next: ${muted(j.next_run_at)}`);
13397
+ if (j.next_run_at) lines.push(` Next: ${muted(formatLocalDateTime(j.next_run_at))}`);
13057
13398
  lines.push("");
13058
13399
  }
13059
13400
  return lines.join("\n");
13060
13401
  });
13061
13402
  }
13062
13403
  async function cronHealth(globalOpts) {
13063
- if (!existsSync24(DB_PATH)) {
13404
+ if (!existsSync25(DB_PATH)) {
13064
13405
  outputError("DB_NOT_FOUND", "Database not found.");
13065
13406
  process.exit(1);
13066
13407
  }
@@ -13114,6 +13455,7 @@ async function cronCreate(globalOpts, opts) {
13114
13455
  everyMs = unit.startsWith("h") ? num * 36e5 : unit.startsWith("m") ? num * 6e4 : num * 1e3;
13115
13456
  }
13116
13457
  }
13458
+ const fallbacks = opts.fallback?.length ? parseFallbacks(opts.fallback) : void 0;
13117
13459
  const job = insertJob2({
13118
13460
  scheduleType: schedType,
13119
13461
  cron: opts.cron ?? null,
@@ -13125,6 +13467,7 @@ async function cronCreate(globalOpts, opts) {
13125
13467
  model: opts.model ?? null,
13126
13468
  thinking: opts.thinking ?? null,
13127
13469
  timeout,
13470
+ fallbacks,
13128
13471
  sessionType: opts.sessionType ?? "isolated",
13129
13472
  deliveryMode: opts.delivery ?? "announce",
13130
13473
  channel: opts.channel ?? null,
@@ -13209,6 +13552,11 @@ async function cronEdit(globalOpts, id, opts) {
13209
13552
  updates.push("timezone = ?");
13210
13553
  values.push(opts.timezone);
13211
13554
  }
13555
+ if (opts.fallback?.length) {
13556
+ const fallbacks = parseFallbacks(opts.fallback);
13557
+ updates.push("fallbacks = ?");
13558
+ values.push(JSON.stringify(fallbacks));
13559
+ }
13212
13560
  if (updates.length === 0) {
13213
13561
  outputError("NO_CHANGES", "No fields to update. Specify fields with flags (e.g. --description, --cron).");
13214
13562
  process.exit(1);
@@ -13224,7 +13572,7 @@ async function cronEdit(globalOpts, id, opts) {
13224
13572
  }
13225
13573
  }
13226
13574
  async function cronRuns(globalOpts, jobId, opts) {
13227
- if (!existsSync24(DB_PATH)) {
13575
+ if (!existsSync25(DB_PATH)) {
13228
13576
  outputError("DB_NOT_FOUND", "Database not found.");
13229
13577
  process.exit(1);
13230
13578
  }
@@ -13243,7 +13591,7 @@ async function cronRuns(globalOpts, jobId, opts) {
13243
13591
  const lines = ["", divider("Run History"), ""];
13244
13592
  for (const r of list) {
13245
13593
  const duration = r.duration_ms ? ` (${(r.duration_ms / 1e3).toFixed(1)}s)` : "";
13246
- lines.push(` #${r.job_id} [${r.status}] ${r.started_at}${duration}`);
13594
+ lines.push(` #${r.job_id} [${r.status}] ${formatLocalDateTime(r.started_at)}${duration}`);
13247
13595
  if (r.error) lines.push(` Error: ${r.error.slice(0, 100)}`);
13248
13596
  lines.push("");
13249
13597
  }
@@ -13257,6 +13605,7 @@ var init_cron2 = __esm({
13257
13605
  init_paths();
13258
13606
  init_resolve_chat();
13259
13607
  init_types2();
13608
+ init_format_time();
13260
13609
  }
13261
13610
  });
13262
13611
 
@@ -13270,9 +13619,9 @@ __export(agents_exports, {
13270
13619
  runnersList: () => runnersList,
13271
13620
  tasksList: () => tasksList
13272
13621
  });
13273
- import { existsSync as existsSync25 } from "fs";
13622
+ import { existsSync as existsSync26 } from "fs";
13274
13623
  async function agentsList(globalOpts) {
13275
- if (!existsSync25(DB_PATH)) {
13624
+ if (!existsSync26(DB_PATH)) {
13276
13625
  outputError("DB_NOT_FOUND", "Database not found.");
13277
13626
  process.exit(1);
13278
13627
  }
@@ -13303,7 +13652,7 @@ async function agentsList(globalOpts) {
13303
13652
  });
13304
13653
  }
13305
13654
  async function tasksList(globalOpts) {
13306
- if (!existsSync25(DB_PATH)) {
13655
+ if (!existsSync26(DB_PATH)) {
13307
13656
  outputError("DB_NOT_FOUND", "Database not found.");
13308
13657
  process.exit(1);
13309
13658
  }
@@ -13431,18 +13780,18 @@ __export(db_exports, {
13431
13780
  dbPath: () => dbPath,
13432
13781
  dbStats: () => dbStats
13433
13782
  });
13434
- import { existsSync as existsSync26, statSync as statSync6, copyFileSync, mkdirSync as mkdirSync8 } from "fs";
13783
+ import { existsSync as existsSync27, statSync as statSync5, copyFileSync, mkdirSync as mkdirSync9 } from "fs";
13435
13784
  import { dirname as dirname5 } from "path";
13436
13785
  async function dbStats(globalOpts) {
13437
- if (!existsSync26(DB_PATH)) {
13786
+ if (!existsSync27(DB_PATH)) {
13438
13787
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
13439
13788
  process.exit(1);
13440
13789
  }
13441
13790
  const { openDatabaseReadOnly: openDatabaseReadOnly2 } = await Promise.resolve().then(() => (init_store4(), store_exports3));
13442
13791
  const readDb = openDatabaseReadOnly2();
13443
- const mainSize = statSync6(DB_PATH).size;
13792
+ const mainSize = statSync5(DB_PATH).size;
13444
13793
  const walPath = DB_PATH + "-wal";
13445
- const walSize = existsSync26(walPath) ? statSync6(walPath).size : 0;
13794
+ const walSize = existsSync27(walPath) ? statSync5(walPath).size : 0;
13446
13795
  const tableNames = readDb.prepare(
13447
13796
  "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts%' ORDER BY name"
13448
13797
  ).all();
@@ -13476,17 +13825,17 @@ async function dbPath(globalOpts) {
13476
13825
  output({ path: DB_PATH }, (d) => d.path);
13477
13826
  }
13478
13827
  async function dbBackup(globalOpts, destPath) {
13479
- if (!existsSync26(DB_PATH)) {
13828
+ if (!existsSync27(DB_PATH)) {
13480
13829
  outputError("DB_NOT_FOUND", `Database not found at ${DB_PATH}`);
13481
13830
  process.exit(1);
13482
13831
  }
13483
13832
  const dest = destPath ?? `${DB_PATH}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
13484
13833
  try {
13485
- mkdirSync8(dirname5(dest), { recursive: true });
13834
+ mkdirSync9(dirname5(dest), { recursive: true });
13486
13835
  copyFileSync(DB_PATH, dest);
13487
13836
  const walPath = DB_PATH + "-wal";
13488
- if (existsSync26(walPath)) copyFileSync(walPath, dest + "-wal");
13489
- output({ path: dest, sizeBytes: statSync6(dest).size }, (d) => {
13837
+ if (existsSync27(walPath)) copyFileSync(walPath, dest + "-wal");
13838
+ output({ path: dest, sizeBytes: statSync5(dest).size }, (d) => {
13490
13839
  const b = d;
13491
13840
  return `
13492
13841
  ${success("Backup created:")} ${b.path} (${(b.sizeBytes / 1024).toFixed(0)}KB)
@@ -13514,9 +13863,9 @@ __export(usage_exports, {
13514
13863
  usageCost: () => usageCost,
13515
13864
  usageTokens: () => usageTokens
13516
13865
  });
13517
- import { existsSync as existsSync27 } from "fs";
13866
+ import { existsSync as existsSync28 } from "fs";
13518
13867
  function ensureDb() {
13519
- if (!existsSync27(DB_PATH)) {
13868
+ if (!existsSync28(DB_PATH)) {
13520
13869
  outputError("DB_NOT_FOUND", "Database not found. Run cc-claw setup first.");
13521
13870
  process.exit(1);
13522
13871
  }
@@ -13706,9 +14055,9 @@ __export(config_exports, {
13706
14055
  configList: () => configList,
13707
14056
  configSet: () => configSet
13708
14057
  });
13709
- import { existsSync as existsSync28, readFileSync as readFileSync14 } from "fs";
14058
+ import { existsSync as existsSync29, readFileSync as readFileSync14 } from "fs";
13710
14059
  async function configList(globalOpts) {
13711
- if (!existsSync28(DB_PATH)) {
14060
+ if (!existsSync29(DB_PATH)) {
13712
14061
  outputError("DB_NOT_FOUND", "Database not found.");
13713
14062
  process.exit(1);
13714
14063
  }
@@ -13742,7 +14091,7 @@ async function configGet(globalOpts, key) {
13742
14091
  outputError("INVALID_KEY", `Unknown config key "${key}". Valid keys: ${RUNTIME_KEYS.join(", ")}`);
13743
14092
  process.exit(1);
13744
14093
  }
13745
- if (!existsSync28(DB_PATH)) {
14094
+ if (!existsSync29(DB_PATH)) {
13746
14095
  outputError("DB_NOT_FOUND", "Database not found.");
13747
14096
  process.exit(1);
13748
14097
  }
@@ -13788,7 +14137,7 @@ async function configSet(globalOpts, key, value) {
13788
14137
  }
13789
14138
  }
13790
14139
  async function configEnv(_globalOpts) {
13791
- if (!existsSync28(ENV_PATH)) {
14140
+ if (!existsSync29(ENV_PATH)) {
13792
14141
  outputError("ENV_NOT_FOUND", `No .env file at ${ENV_PATH}. Run cc-claw setup.`);
13793
14142
  process.exit(1);
13794
14143
  }
@@ -13841,9 +14190,9 @@ __export(session_exports, {
13841
14190
  sessionGet: () => sessionGet,
13842
14191
  sessionNew: () => sessionNew
13843
14192
  });
13844
- import { existsSync as existsSync29 } from "fs";
14193
+ import { existsSync as existsSync30 } from "fs";
13845
14194
  async function sessionGet(globalOpts) {
13846
- if (!existsSync29(DB_PATH)) {
14195
+ if (!existsSync30(DB_PATH)) {
13847
14196
  outputError("DB_NOT_FOUND", "Database not found.");
13848
14197
  process.exit(1);
13849
14198
  }
@@ -13904,9 +14253,9 @@ __export(permissions_exports, {
13904
14253
  verboseGet: () => verboseGet,
13905
14254
  verboseSet: () => verboseSet
13906
14255
  });
13907
- import { existsSync as existsSync30 } from "fs";
14256
+ import { existsSync as existsSync31 } from "fs";
13908
14257
  function ensureDb2() {
13909
- if (!existsSync30(DB_PATH)) {
14258
+ if (!existsSync31(DB_PATH)) {
13910
14259
  outputError("DB_NOT_FOUND", "Database not found.");
13911
14260
  process.exit(1);
13912
14261
  }
@@ -14052,9 +14401,9 @@ __export(cwd_exports, {
14052
14401
  cwdGet: () => cwdGet,
14053
14402
  cwdSet: () => cwdSet
14054
14403
  });
14055
- import { existsSync as existsSync31 } from "fs";
14404
+ import { existsSync as existsSync32 } from "fs";
14056
14405
  async function cwdGet(globalOpts) {
14057
- if (!existsSync31(DB_PATH)) {
14406
+ if (!existsSync32(DB_PATH)) {
14058
14407
  outputError("DB_NOT_FOUND", "Database not found.");
14059
14408
  process.exit(1);
14060
14409
  }
@@ -14116,9 +14465,9 @@ __export(voice_exports, {
14116
14465
  voiceGet: () => voiceGet,
14117
14466
  voiceSet: () => voiceSet
14118
14467
  });
14119
- import { existsSync as existsSync32 } from "fs";
14468
+ import { existsSync as existsSync33 } from "fs";
14120
14469
  async function voiceGet(globalOpts) {
14121
- if (!existsSync32(DB_PATH)) {
14470
+ if (!existsSync33(DB_PATH)) {
14122
14471
  outputError("DB_NOT_FOUND", "Database not found.");
14123
14472
  process.exit(1);
14124
14473
  }
@@ -14167,9 +14516,9 @@ __export(heartbeat_exports, {
14167
14516
  heartbeatGet: () => heartbeatGet,
14168
14517
  heartbeatSet: () => heartbeatSet
14169
14518
  });
14170
- import { existsSync as existsSync33 } from "fs";
14519
+ import { existsSync as existsSync34 } from "fs";
14171
14520
  async function heartbeatGet(globalOpts) {
14172
- if (!existsSync33(DB_PATH)) {
14521
+ if (!existsSync34(DB_PATH)) {
14173
14522
  outputError("DB_NOT_FOUND", "Database not found.");
14174
14523
  process.exit(1);
14175
14524
  }
@@ -14279,9 +14628,9 @@ __export(chats_exports, {
14279
14628
  chatsList: () => chatsList,
14280
14629
  chatsRemoveAlias: () => chatsRemoveAlias
14281
14630
  });
14282
- import { existsSync as existsSync34 } from "fs";
14631
+ import { existsSync as existsSync35 } from "fs";
14283
14632
  async function chatsList(_globalOpts) {
14284
- if (!existsSync34(DB_PATH)) {
14633
+ if (!existsSync35(DB_PATH)) {
14285
14634
  outputError("DB_NOT_FOUND", "Database not found.");
14286
14635
  process.exit(1);
14287
14636
  }
@@ -14409,9 +14758,9 @@ var mcps_exports = {};
14409
14758
  __export(mcps_exports, {
14410
14759
  mcpsList: () => mcpsList
14411
14760
  });
14412
- import { existsSync as existsSync35 } from "fs";
14761
+ import { existsSync as existsSync36 } from "fs";
14413
14762
  async function mcpsList(_globalOpts) {
14414
- if (!existsSync35(DB_PATH)) {
14763
+ if (!existsSync36(DB_PATH)) {
14415
14764
  outputError("DB_NOT_FOUND", "Database not found.");
14416
14765
  process.exit(1);
14417
14766
  }
@@ -14448,11 +14797,11 @@ __export(chat_exports, {
14448
14797
  chatSend: () => chatSend
14449
14798
  });
14450
14799
  import { request as httpRequest2 } from "http";
14451
- import { readFileSync as readFileSync15, existsSync as existsSync36 } from "fs";
14800
+ import { readFileSync as readFileSync15, existsSync as existsSync37 } from "fs";
14452
14801
  function getToken2() {
14453
14802
  if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
14454
14803
  try {
14455
- if (existsSync36(TOKEN_PATH2)) return readFileSync15(TOKEN_PATH2, "utf-8").trim();
14804
+ if (existsSync37(TOKEN_PATH2)) return readFileSync15(TOKEN_PATH2, "utf-8").trim();
14456
14805
  } catch {
14457
14806
  }
14458
14807
  return null;
@@ -14878,10 +15227,10 @@ var init_completion = __esm({
14878
15227
 
14879
15228
  // src/setup.ts
14880
15229
  var setup_exports = {};
14881
- import { existsSync as existsSync37, writeFileSync as writeFileSync7, readFileSync as readFileSync16, copyFileSync as copyFileSync2, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
15230
+ import { existsSync as existsSync38, writeFileSync as writeFileSync8, readFileSync as readFileSync16, copyFileSync as copyFileSync2, mkdirSync as mkdirSync10, statSync as statSync6 } from "fs";
14882
15231
  import { execFileSync as execFileSync4 } from "child_process";
14883
15232
  import { createInterface as createInterface5 } from "readline";
14884
- import { join as join18 } from "path";
15233
+ import { join as join19 } from "path";
14885
15234
  function divider2() {
14886
15235
  console.log(dim("\u2500".repeat(55)));
14887
15236
  }
@@ -14955,10 +15304,10 @@ async function setup() {
14955
15304
  }
14956
15305
  console.log("");
14957
15306
  for (const dir of [CC_CLAW_HOME, DATA_PATH, LOGS_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH]) {
14958
- if (!existsSync37(dir)) mkdirSync9(dir, { recursive: true });
15307
+ if (!existsSync38(dir)) mkdirSync10(dir, { recursive: true });
14959
15308
  }
14960
15309
  const env = {};
14961
- const envSource = existsSync37(ENV_PATH) ? ENV_PATH : existsSync37(".env") ? ".env" : null;
15310
+ const envSource = existsSync38(ENV_PATH) ? ENV_PATH : existsSync38(".env") ? ".env" : null;
14962
15311
  if (envSource) {
14963
15312
  console.log(yellow(` Found existing config at ${envSource} \u2014 your values will be preserved`));
14964
15313
  console.log(yellow(" unless you enter new ones. Just press Enter to keep existing values.\n"));
@@ -14968,9 +15317,9 @@ async function setup() {
14968
15317
  if (match) env[match[1].trim()] = match[2].trim();
14969
15318
  }
14970
15319
  }
14971
- const cwdDb = join18(process.cwd(), "cc-claw.db");
14972
- if (existsSync37(cwdDb) && !existsSync37(DB_PATH)) {
14973
- const { size } = statSync7(cwdDb);
15320
+ const cwdDb = join19(process.cwd(), "cc-claw.db");
15321
+ if (existsSync38(cwdDb) && !existsSync38(DB_PATH)) {
15322
+ const { size } = statSync6(cwdDb);
14974
15323
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
14975
15324
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
14976
15325
  if (migrate) {
@@ -15163,7 +15512,7 @@ async function setup() {
15163
15512
  envLines.push("", "# Video Analysis", `GEMINI_API_KEY=${env.GEMINI_API_KEY}`);
15164
15513
  }
15165
15514
  const envContent = envLines.join("\n") + "\n";
15166
- writeFileSync7(ENV_PATH, envContent, { mode: 384 });
15515
+ writeFileSync8(ENV_PATH, envContent, { mode: 384 });
15167
15516
  console.log(green(` Config saved to ${ENV_PATH} (permissions: owner-only)`));
15168
15517
  header(6, TOTAL_STEPS, "Run on Startup (Daemon)");
15169
15518
  console.log(" CC-Claw can run automatically in the background, starting");
@@ -15332,7 +15681,7 @@ function registerCronCommands(cmd) {
15332
15681
  const { cronList: cronList2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
15333
15682
  await cronList2(program.opts());
15334
15683
  });
15335
- cmd.command("create").description("Create a scheduled job").requiredOption("--description <text>", "Job description").option("--prompt <text>", "Agent prompt (defaults to description)").option("--cron <expr>", "Cron expression (e.g. '0 9 * * *')").option("--at <iso8601>", "One-shot time").option("--every <interval>", "Repeat interval (e.g. 30m, 1h)").option("--backend <name>", "Backend for this job").option("--model <name>", "Model for this job").option("--thinking <level>", "Thinking level").option("--timeout <seconds>", "Job timeout in seconds (30-3600)").option("--timezone <tz>", "IANA timezone", "UTC").option("--session-type <type>", "Session type (isolated/main)", "isolated").option("--delivery <mode>", "Delivery mode (announce/webhook/none)", "announce").option("--channel <name>", "Delivery channel").option("--target <id>", "Delivery target").option("--cwd <path>", "Working directory").action(async (opts) => {
15684
+ cmd.command("create").description("Create a scheduled job").requiredOption("--description <text>", "Job description").option("--prompt <text>", "Agent prompt (defaults to description)").option("--cron <expr>", "Cron expression (e.g. '0 9 * * *')").option("--at <iso8601>", "One-shot time").option("--every <interval>", "Repeat interval (e.g. 30m, 1h)").option("--backend <name>", "Backend for this job").option("--model <name>", "Model for this job").option("--thinking <level>", "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>", "IANA timezone", "UTC").option("--session-type <type>", "Session type (isolated/main)", "isolated").option("--delivery <mode>", "Delivery mode (announce/webhook/none)", "announce").option("--channel <name>", "Delivery channel").option("--target <id>", "Delivery target").option("--cwd <path>", "Working directory").action(async (opts) => {
15336
15685
  const { cronCreate: cronCreate2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
15337
15686
  await cronCreate2(program.opts(), opts);
15338
15687
  });
@@ -15352,7 +15701,7 @@ function registerCronCommands(cmd) {
15352
15701
  const { cronAction: cronAction2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
15353
15702
  await cronAction2(program.opts(), "run", id);
15354
15703
  });
15355
- cmd.command("edit <id>").description("Edit a job (same flags as create)").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("--timezone <tz>").action(async (id, opts) => {
15704
+ cmd.command("edit <id>").description("Edit a job (same flags as create)").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>").action(async (id, opts) => {
15356
15705
  const { cronEdit: cronEdit2 } = await Promise.resolve().then(() => (init_cron2(), cron_exports2));
15357
15706
  await cronEdit2(program.opts(), id, opts);
15358
15707
  });