cc-claw 0.4.10 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +643 -254
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -23,7 +23,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
23
23
  // src/paths.ts
24
24
  import { homedir } from "os";
25
25
  import { join } from "path";
26
- var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH;
26
+ var CC_CLAW_HOME, ENV_PATH, DATA_PATH, DB_PATH, LOGS_PATH, LOG_PATH, ERROR_LOG_PATH, IDENTITY_PATH, WORKSPACE_PATH, SKILLS_PATH, RUNNERS_PATH, AGENTS_PATH;
27
27
  var init_paths = __esm({
28
28
  "src/paths.ts"() {
29
29
  "use strict";
@@ -34,6 +34,7 @@ var init_paths = __esm({
34
34
  LOGS_PATH = join(CC_CLAW_HOME, "logs");
35
35
  LOG_PATH = join(LOGS_PATH, "cc-claw.log");
36
36
  ERROR_LOG_PATH = join(LOGS_PATH, "cc-claw.error.log");
37
+ IDENTITY_PATH = join(CC_CLAW_HOME, "identity");
37
38
  WORKSPACE_PATH = join(CC_CLAW_HOME, "workspace");
38
39
  SKILLS_PATH = join(WORKSPACE_PATH, "skills");
39
40
  RUNNERS_PATH = join(CC_CLAW_HOME, "runners");
@@ -48,7 +49,7 @@ var VERSION;
48
49
  var init_version = __esm({
49
50
  "src/version.ts"() {
50
51
  "use strict";
51
- VERSION = true ? "0.4.10" : (() => {
52
+ VERSION = true ? "0.5.1" : (() => {
52
53
  try {
53
54
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
54
55
  } catch {
@@ -964,6 +965,7 @@ __export(store_exports3, {
964
965
  getMemoriesWithoutEmbeddings: () => getMemoriesWithoutEmbeddings,
965
966
  getMode: () => getMode,
966
967
  getModel: () => getModel,
968
+ getModelSignature: () => getModelSignature,
967
969
  getPendingEscalation: () => getPendingEscalation,
968
970
  getRecentBookmarks: () => getRecentBookmarks,
969
971
  getRecentMemories: () => getRecentMemories,
@@ -1010,6 +1012,7 @@ __export(store_exports3, {
1010
1012
  setHeartbeatConfig: () => setHeartbeatConfig,
1011
1013
  setMode: () => setMode,
1012
1014
  setModel: () => setModel,
1015
+ setModelSignature: () => setModelSignature,
1013
1016
  setResponseStyle: () => setResponseStyle,
1014
1017
  setSessionId: () => setSessionId,
1015
1018
  setSessionStartedAt: () => setSessionStartedAt,
@@ -1486,6 +1489,12 @@ function initDatabase() {
1486
1489
  style TEXT NOT NULL DEFAULT 'normal'
1487
1490
  );
1488
1491
  `);
1492
+ db.exec(`
1493
+ CREATE TABLE IF NOT EXISTS chat_model_signature (
1494
+ chat_id TEXT PRIMARY KEY,
1495
+ value TEXT NOT NULL DEFAULT 'off'
1496
+ );
1497
+ `);
1489
1498
  }
1490
1499
  function getDb() {
1491
1500
  return db;
@@ -1629,6 +1638,17 @@ function setResponseStyle(chatId, style) {
1629
1638
  ON CONFLICT(chat_id) DO UPDATE SET style = excluded.style
1630
1639
  `).run(chatId, style);
1631
1640
  }
1641
+ function getModelSignature(chatId) {
1642
+ const row = db.prepare("SELECT value FROM chat_model_signature WHERE chat_id = ?").get(chatId);
1643
+ return row?.value ?? "off";
1644
+ }
1645
+ function setModelSignature(chatId, value) {
1646
+ db.prepare(`
1647
+ INSERT INTO chat_model_signature (chat_id, value)
1648
+ VALUES (?, ?)
1649
+ ON CONFLICT(chat_id) DO UPDATE SET value = excluded.value
1650
+ `).run(chatId, value);
1651
+ }
1632
1652
  function getSessionId(chatId) {
1633
1653
  const row = db.prepare(
1634
1654
  "SELECT session_id FROM sessions WHERE chat_id = ?"
@@ -2533,11 +2553,13 @@ var init_env = __esm({
2533
2553
  // src/backends/claude.ts
2534
2554
  import { existsSync } from "fs";
2535
2555
  import { execSync } from "child_process";
2556
+ import { join as join3 } from "path";
2536
2557
  var ADAPTIVE_MODELS, ClaudeAdapter;
2537
2558
  var init_claude = __esm({
2538
2559
  "src/backends/claude.ts"() {
2539
2560
  "use strict";
2540
2561
  init_env();
2562
+ init_paths();
2541
2563
  ADAPTIVE_MODELS = /* @__PURE__ */ new Set(["claude-opus-4-6", "claude-sonnet-4-6"]);
2542
2564
  ClaudeAdapter = class {
2543
2565
  id = "claude";
@@ -2624,7 +2646,7 @@ var init_claude = __esm({
2624
2646
  "stream-json",
2625
2647
  "--verbose",
2626
2648
  "--max-turns",
2627
- "25"
2649
+ String(opts.maxTurns ?? 25)
2628
2650
  ];
2629
2651
  if (opts.sessionId) args.push("--resume", opts.sessionId);
2630
2652
  if (opts.model) args.push("--model", opts.model);
@@ -2647,6 +2669,10 @@ var init_claude = __esm({
2647
2669
  args.push("--allow-dangerously-skip-permissions");
2648
2670
  break;
2649
2671
  }
2672
+ const identityFile = join3(IDENTITY_PATH, "CC-CLAW.md");
2673
+ if (existsSync(identityFile)) {
2674
+ args.push("--append-system-prompt-file", identityFile);
2675
+ }
2650
2676
  return { executable: this.getExecutablePath(), args, cwd: opts.cwd };
2651
2677
  }
2652
2678
  parseLine(raw) {
@@ -2723,11 +2749,13 @@ var init_claude = __esm({
2723
2749
  // src/backends/gemini.ts
2724
2750
  import { existsSync as existsSync2 } from "fs";
2725
2751
  import { execSync as execSync2 } from "child_process";
2752
+ import { join as join4 } from "path";
2726
2753
  var GeminiAdapter;
2727
2754
  var init_gemini = __esm({
2728
2755
  "src/backends/gemini.ts"() {
2729
2756
  "use strict";
2730
2757
  init_env();
2758
+ init_paths();
2731
2759
  GeminiAdapter = class {
2732
2760
  id = "gemini";
2733
2761
  displayName = "Gemini";
@@ -2774,7 +2802,12 @@ var init_gemini = __esm({
2774
2802
  return this._resolvedPath;
2775
2803
  }
2776
2804
  getEnv(thinkingOverrides) {
2777
- return buildBaseEnv(thinkingOverrides);
2805
+ const env = buildBaseEnv(thinkingOverrides);
2806
+ const identityFile = join4(IDENTITY_PATH, "CC-CLAW.md");
2807
+ if (existsSync2(identityFile)) {
2808
+ env.GEMINI_SYSTEM_MD = identityFile;
2809
+ }
2810
+ return env;
2778
2811
  }
2779
2812
  applyThinkingConfig(level, _model) {
2780
2813
  if (level === "auto" || level === "high") return {};
@@ -3261,7 +3294,7 @@ You are a proactive personal AI assistant running inside a Telegram bot powered
3261
3294
  ## Identity
3262
3295
  - You serve one user through Telegram. Be helpful, concise, and proactive.
3263
3296
  - You can switch between multiple AI backends (Claude, Gemini, Codex) \u2014 the user controls which one is active.
3264
- - Your name, personality, and behavioral rules are defined in this file. The user can customize everything by editing ~/.cc-claw/workspace/SOUL.md.
3297
+ - Your name, personality, and behavioral rules are defined in this file. The user can customize everything by editing ~/.cc-claw/identity/SOUL.md.
3265
3298
 
3266
3299
  ## Capabilities
3267
3300
  - **Coding**: Read, write, and edit files. Run shell commands. Full agentic coding.
@@ -3278,6 +3311,15 @@ You are a proactive personal AI assistant running inside a Telegram bot powered
3278
3311
  - When you learn something about the user's preferences, include \`[UPDATE_USER:key=value]\` in your response to suggest saving it.
3279
3312
  - For the heartbeat: if nothing needs attention, respond with exactly \`HEARTBEAT_OK\` and nothing else.
3280
3313
  - Always use the user's timezone when discussing times and dates.
3314
+
3315
+ ## Identity File Maintenance
3316
+ - Keep SOUL.md under 100 lines. Every line costs tokens on every message.
3317
+ - Before adding content to SOUL.md or USER.md, consider if it belongs in:
3318
+ - A context file (context/*.md) for topic-specific knowledge
3319
+ - A skill (skills/) for reusable instructions
3320
+ - A memory (via /remember) for facts
3321
+ - Never duplicate information already in memories or context files.
3322
+ - When updating identity files, remove outdated content first.
3281
3323
  `;
3282
3324
  DEFAULT_USER = `# User Profile
3283
3325
 
@@ -3298,7 +3340,7 @@ You are an expert user and operator of the CC-Claw architecture. Because you are
3298
3340
  - **Database**: All state is stored locally in SQLite (\`~/.cc-claw/data/cc-claw.db\`), ensuring total privacy and fast local access for memory, jobs, and orchestration.
3299
3341
 
3300
3342
  ## 2. Identity & Context (The "Brain")
3301
- - **SOUL & USER**: Your personality is defined in \`~/.cc-claw/workspace/SOUL.md\`, and the user's profile is in \`USER.md\`. These are the single source of truth for your behavior.
3343
+ - **SOUL & USER**: Your personality is defined in \`~/.cc-claw/identity/SOUL.md\`, and the user's profile is in \`~/.cc-claw/identity/USER.md\`. These are the single source of truth for your behavior.
3302
3344
  - **On-Demand Context**: The \`context/\` directory holds files like this one. They are injected into your prompt only when triggered by relevant semantic keywords, keeping your context window lean.
3303
3345
  - **Proactive Memory**: You can autonomously save user facts and preferences to the database by writing \`[UPDATE_USER:key=value]\` in your replies.
3304
3346
 
@@ -3326,9 +3368,49 @@ If the user asks *how* to do something with CC-Claw, use this expertise to sugge
3326
3368
  });
3327
3369
 
3328
3370
  // src/bootstrap/init.ts
3329
- import { existsSync as existsSync4, writeFileSync, mkdirSync, readFileSync as readFileSync2, statSync } from "fs";
3330
- import { join as join3 } from "path";
3371
+ import {
3372
+ existsSync as existsSync4,
3373
+ writeFileSync,
3374
+ mkdirSync,
3375
+ readFileSync as readFileSync2,
3376
+ statSync,
3377
+ copyFileSync,
3378
+ unlinkSync
3379
+ } from "fs";
3380
+ import { join as join5 } from "path";
3381
+ function migrateFile(legacyPath, newPath, label2) {
3382
+ if (existsSync4(newPath)) return false;
3383
+ if (existsSync4(legacyPath)) {
3384
+ copyFileSync(legacyPath, newPath);
3385
+ const copied = readFileSync2(newPath, "utf-8");
3386
+ if (copied.length > 0) {
3387
+ unlinkSync(legacyPath);
3388
+ log(`[bootstrap] Migrated ${label2} from workspace/ to identity/`);
3389
+ return true;
3390
+ }
3391
+ log(`[bootstrap] WARNING: Migration verification failed for ${label2}, keeping original`);
3392
+ return false;
3393
+ }
3394
+ return false;
3395
+ }
3331
3396
  function bootstrapWorkspaceFiles() {
3397
+ if (!existsSync4(IDENTITY_PATH)) {
3398
+ mkdirSync(IDENTITY_PATH, { recursive: true });
3399
+ log("[bootstrap] Created identity/ directory");
3400
+ }
3401
+ if (!existsSync4(WORKSPACE_PATH)) {
3402
+ mkdirSync(WORKSPACE_PATH, { recursive: true });
3403
+ }
3404
+ migrateFile(LEGACY_SOUL_PATH, SOUL_PATH, "SOUL.md");
3405
+ migrateFile(LEGACY_USER_PATH, USER_PATH, "USER.md");
3406
+ if (existsSync4(LEGACY_CLAUDE_MD)) {
3407
+ unlinkSync(LEGACY_CLAUDE_MD);
3408
+ log("[bootstrap] Removed legacy workspace/CLAUDE.md (replaced by identity/CC-CLAW.md)");
3409
+ }
3410
+ if (existsSync4(LEGACY_GEMINI_MD)) {
3411
+ unlinkSync(LEGACY_GEMINI_MD);
3412
+ log("[bootstrap] Removed legacy workspace/GEMINI.md (replaced by identity/CC-CLAW.md)");
3413
+ }
3332
3414
  if (!existsSync4(SOUL_PATH)) {
3333
3415
  writeFileSync(SOUL_PATH, DEFAULT_SOUL, "utf-8");
3334
3416
  log("[bootstrap] Created default SOUL.md");
@@ -3347,11 +3429,32 @@ function bootstrapWorkspaceFiles() {
3347
3429
  mkdirSync(CONTEXT_DIR, { recursive: true });
3348
3430
  log("[bootstrap] Created context/ directory");
3349
3431
  }
3350
- const expertisePath = join3(CONTEXT_DIR, "cc-claw-expertise.md");
3432
+ const expertisePath = join5(CONTEXT_DIR, "cc-claw-expertise.md");
3351
3433
  if (!existsSync4(expertisePath)) {
3352
3434
  writeFileSync(expertisePath, DEFAULT_EXPERTISE, "utf-8");
3353
3435
  log("[bootstrap] Created default context/cc-claw-expertise.md");
3354
3436
  }
3437
+ const IGNORE_CONTENT = [
3438
+ "# CC-Claw workspace: only essential files are scanned by coding CLIs.",
3439
+ "# Agents can still access any file via absolute path when asked.",
3440
+ "*",
3441
+ "!.gitignore",
3442
+ "!.geminiignore",
3443
+ "!AGENTS.md",
3444
+ "!context/",
3445
+ "!context/**",
3446
+ ""
3447
+ ].join("\n");
3448
+ const gitignorePath = join5(WORKSPACE_PATH, ".gitignore");
3449
+ if (!existsSync4(gitignorePath)) {
3450
+ writeFileSync(gitignorePath, IGNORE_CONTENT, "utf-8");
3451
+ log("[bootstrap] Created .gitignore (workspace allowlist)");
3452
+ }
3453
+ const geminiignorePath = join5(WORKSPACE_PATH, ".geminiignore");
3454
+ if (!existsSync4(geminiignorePath)) {
3455
+ writeFileSync(geminiignorePath, IGNORE_CONTENT, "utf-8");
3456
+ log("[bootstrap] Created .geminiignore (workspace allowlist)");
3457
+ }
3355
3458
  syncNativeCliFiles();
3356
3459
  }
3357
3460
  function syncNativeCliFiles() {
@@ -3360,15 +3463,23 @@ function syncNativeCliFiles() {
3360
3463
  try {
3361
3464
  soul = readFileSync2(SOUL_PATH, "utf-8").trim();
3362
3465
  } catch {
3466
+ try {
3467
+ soul = readFileSync2(LEGACY_SOUL_PATH, "utf-8").trim();
3468
+ } catch {
3469
+ }
3363
3470
  }
3364
3471
  try {
3365
3472
  user = readFileSync2(USER_PATH, "utf-8").trim();
3366
3473
  } catch {
3474
+ try {
3475
+ user = readFileSync2(LEGACY_USER_PATH, "utf-8").trim();
3476
+ } catch {
3477
+ }
3367
3478
  }
3368
3479
  try {
3369
3480
  const soulMtime = existsSync4(SOUL_PATH) ? statSync(SOUL_PATH).mtimeMs : 0;
3370
3481
  const userMtime = existsSync4(USER_PATH) ? statSync(USER_PATH).mtimeMs : 0;
3371
- if (soulMtime === lastSoulMtime && userMtime === lastUserMtime) {
3482
+ if (soulMtime > 0 && soulMtime === lastSoulMtime && userMtime === lastUserMtime) {
3372
3483
  return;
3373
3484
  }
3374
3485
  lastSoulMtime = soulMtime;
@@ -3402,27 +3513,26 @@ function syncNativeCliFiles() {
3402
3513
  "- Skills are in ~/.cc-claw/workspace/skills/",
3403
3514
  ""
3404
3515
  ].join("\n");
3405
- for (const name of NATIVE_CLI_FILES) {
3406
- const filePath = join3(WORKSPACE_PATH, name);
3407
- let content = nativeContent;
3408
- if (name === "GEMINI.md") {
3409
- content = content.replace(/@(\w+)/g, "$1");
3410
- }
3411
- writeFileSync(filePath, content, "utf-8");
3412
- }
3413
- log(`[bootstrap] Synced SOUL.md + USER.md \u2192 ${NATIVE_CLI_FILES.join(", ")}`);
3516
+ const ccClawPath = join5(IDENTITY_PATH, "CC-CLAW.md");
3517
+ writeFileSync(ccClawPath, nativeContent, "utf-8");
3518
+ const agentsPath = join5(WORKSPACE_PATH, "AGENTS.md");
3519
+ writeFileSync(agentsPath, nativeContent, "utf-8");
3520
+ log("[bootstrap] Synced SOUL.md + USER.md \u2192 identity/CC-CLAW.md, workspace/AGENTS.md");
3414
3521
  }
3415
- var SOUL_PATH, USER_PATH, CONTEXT_DIR, NATIVE_CLI_FILES, lastSoulMtime, lastUserMtime;
3522
+ var SOUL_PATH, USER_PATH, CONTEXT_DIR, LEGACY_SOUL_PATH, LEGACY_USER_PATH, LEGACY_CLAUDE_MD, LEGACY_GEMINI_MD, lastSoulMtime, lastUserMtime;
3416
3523
  var init_init = __esm({
3417
3524
  "src/bootstrap/init.ts"() {
3418
3525
  "use strict";
3419
3526
  init_paths();
3420
3527
  init_defaults();
3421
3528
  init_log();
3422
- SOUL_PATH = join3(WORKSPACE_PATH, "SOUL.md");
3423
- USER_PATH = join3(WORKSPACE_PATH, "USER.md");
3424
- CONTEXT_DIR = join3(WORKSPACE_PATH, "context");
3425
- NATIVE_CLI_FILES = ["CLAUDE.md", "GEMINI.md", "AGENTS.md"];
3529
+ SOUL_PATH = join5(IDENTITY_PATH, "SOUL.md");
3530
+ USER_PATH = join5(IDENTITY_PATH, "USER.md");
3531
+ CONTEXT_DIR = join5(WORKSPACE_PATH, "context");
3532
+ LEGACY_SOUL_PATH = join5(WORKSPACE_PATH, "SOUL.md");
3533
+ LEGACY_USER_PATH = join5(WORKSPACE_PATH, "USER.md");
3534
+ LEGACY_CLAUDE_MD = join5(WORKSPACE_PATH, "CLAUDE.md");
3535
+ LEGACY_GEMINI_MD = join5(WORKSPACE_PATH, "GEMINI.md");
3426
3536
  lastSoulMtime = 0;
3427
3537
  lastUserMtime = 0;
3428
3538
  }
@@ -3430,7 +3540,7 @@ var init_init = __esm({
3430
3540
 
3431
3541
  // src/bootstrap/loader.ts
3432
3542
  import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync } from "fs";
3433
- import { join as join4 } from "path";
3543
+ import { join as join6 } from "path";
3434
3544
  function searchContext(userMessage) {
3435
3545
  if (!existsSync5(CONTEXT_DIR2)) return null;
3436
3546
  const msgWords = new Set(
@@ -3441,7 +3551,7 @@ function searchContext(userMessage) {
3441
3551
  try {
3442
3552
  const files = readdirSync(CONTEXT_DIR2).filter((f) => f.endsWith(".md"));
3443
3553
  for (const file of files) {
3444
- const filePath = join4(CONTEXT_DIR2, file);
3554
+ const filePath = join6(CONTEXT_DIR2, file);
3445
3555
  try {
3446
3556
  const content = readFileSync3(filePath, "utf-8").trim();
3447
3557
  if (!content) continue;
@@ -3471,7 +3581,10 @@ function searchContext(userMessage) {
3471
3581
  }
3472
3582
  async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permMode, responseStyle) {
3473
3583
  const sections = [];
3474
- syncNativeCliFiles();
3584
+ if (Date.now() - lastSyncMs >= 5e3) {
3585
+ syncNativeCliFiles();
3586
+ lastSyncMs = Date.now();
3587
+ }
3475
3588
  if (permMode && permMode !== "yolo") {
3476
3589
  sections.push(buildPermissionNotice(permMode));
3477
3590
  }
@@ -3489,7 +3602,7 @@ async function assembleBootstrapPrompt(userMessage, tier = "full", chatId, permM
3489
3602
  ${ctx}`);
3490
3603
  }
3491
3604
  }
3492
- if (chatId && tier !== "slim") {
3605
+ if (chatId && tier !== "slim" && tier !== "chat") {
3493
3606
  const bridge = consumeContextBridge(chatId);
3494
3607
  if (bridge) {
3495
3608
  sections.push(bridge);
@@ -3562,7 +3675,7 @@ ${boardText}`);
3562
3675
  return null;
3563
3676
  }
3564
3677
  }
3565
- var CONTEXT_DIR2, MAX_CONTEXT_CHARS, ACTIVITY_TOKEN_BUDGET, INBOX_TOKEN_BUDGET, WHITEBOARD_TOKEN_BUDGET;
3678
+ var lastSyncMs, CONTEXT_DIR2, MAX_CONTEXT_CHARS, ACTIVITY_TOKEN_BUDGET, INBOX_TOKEN_BUDGET, WHITEBOARD_TOKEN_BUDGET;
3566
3679
  var init_loader = __esm({
3567
3680
  "src/bootstrap/loader.ts"() {
3568
3681
  "use strict";
@@ -3573,7 +3686,8 @@ var init_loader = __esm({
3573
3686
  init_store3();
3574
3687
  init_store4();
3575
3688
  init_store();
3576
- CONTEXT_DIR2 = join4(WORKSPACE_PATH, "context");
3689
+ lastSyncMs = 0;
3690
+ CONTEXT_DIR2 = join6(WORKSPACE_PATH, "context");
3577
3691
  MAX_CONTEXT_CHARS = 4e3;
3578
3692
  ACTIVITY_TOKEN_BUDGET = 1500;
3579
3693
  INBOX_TOKEN_BUDGET = 2e3;
@@ -3615,6 +3729,16 @@ function clearLog(chatId) {
3615
3729
  markMessageLogSummarized(chatId);
3616
3730
  cache.delete(chatId);
3617
3731
  }
3732
+ function getLastMessageTimestamp(chatId) {
3733
+ const cached = cache.get(chatId);
3734
+ if (cached && cached.length > 0) {
3735
+ return cached[cached.length - 1].timestamp;
3736
+ }
3737
+ const rows = getUnsummarizedLog(chatId);
3738
+ if (rows.length === 0) return null;
3739
+ const last = rows[rows.length - 1];
3740
+ return (/* @__PURE__ */ new Date(last.created_at + (last.created_at.includes("Z") ? "" : "Z"))).getTime();
3741
+ }
3618
3742
  function getLoggedChatIds() {
3619
3743
  return getUnsummarizedChatIds();
3620
3744
  }
@@ -3807,7 +3931,15 @@ async function summarizeSession(chatId) {
3807
3931
  return summarizeWithFallbackChain(chatId);
3808
3932
  }
3809
3933
  async function summarizeAllPending() {
3810
- const chatIds = getLoggedChatIds();
3934
+ const allIds = getLoggedChatIds();
3935
+ const chatIds = allIds.filter((id) => !id.includes("-test-"));
3936
+ if (allIds.length > chatIds.length) {
3937
+ const skipped = allIds.length - chatIds.length;
3938
+ log(`[summarize] Skipping ${skipped} test session(s)`);
3939
+ for (const testId of allIds.filter((id) => id.includes("-test-"))) {
3940
+ clearLog(testId);
3941
+ }
3942
+ }
3811
3943
  if (chatIds.length === 0) return;
3812
3944
  log(`[summarize] Summarizing ${chatIds.length} pending session(s)...`);
3813
3945
  for (const chatId of chatIds) {
@@ -4202,13 +4334,13 @@ var init_propagate = __esm({
4202
4334
  });
4203
4335
 
4204
4336
  // src/agents/mcp-config.ts
4205
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync as readdirSync2, unlinkSync } from "fs";
4206
- import { join as join5, dirname } from "path";
4337
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
4338
+ import { join as join7, dirname } from "path";
4207
4339
  import { fileURLToPath } from "url";
4208
4340
  function generateOrchestratorMcpConfig(opts) {
4209
4341
  const port = opts.port ?? process.env.DASHBOARD_PORT ?? "3141";
4210
- const distPath = join5(__dirname, "agents", "mcp-server.js");
4211
- const tsxPath = join5(__dirname, "..", "src", "agents", "mcp-server.ts");
4342
+ const distPath = join7(__dirname, "agents", "mcp-server.js");
4343
+ const tsxPath = join7(__dirname, "..", "src", "agents", "mcp-server.ts");
4212
4344
  const useTs = !existsSync6(distPath);
4213
4345
  return {
4214
4346
  name: opts.agentId === "main" ? "cc-claw" : `cc-claw-${opts.agentId.slice(0, 8)}`,
@@ -4235,15 +4367,15 @@ function writeMcpConfigFile(config2) {
4235
4367
  }
4236
4368
  };
4237
4369
  const safeName = config2.name.replace(/[^a-zA-Z0-9-]/g, "_");
4238
- const configPath = join5(MCP_CONFIG_DIR, `mcp-${safeName}.json`);
4370
+ const configPath = join7(MCP_CONFIG_DIR, `mcp-${safeName}.json`);
4239
4371
  writeFileSync2(configPath, JSON.stringify(jsonConfig, null, 2), { mode: 384 });
4240
4372
  return configPath;
4241
4373
  }
4242
4374
  function deleteMcpConfigFile(mcpName) {
4243
4375
  const safeName = mcpName.replace(/[^a-zA-Z0-9-]/g, "_");
4244
- const configPath = join5(MCP_CONFIG_DIR, `mcp-${safeName}.json`);
4376
+ const configPath = join7(MCP_CONFIG_DIR, `mcp-${safeName}.json`);
4245
4377
  try {
4246
- unlinkSync(configPath);
4378
+ unlinkSync2(configPath);
4247
4379
  } catch {
4248
4380
  }
4249
4381
  }
@@ -4254,7 +4386,7 @@ function cleanupOrphanedMcpConfigs() {
4254
4386
  for (const file of files) {
4255
4387
  if (file.startsWith("mcp-cc-claw-") && file.endsWith(".json")) {
4256
4388
  try {
4257
- unlinkSync(join5(MCP_CONFIG_DIR, file));
4389
+ unlinkSync2(join7(MCP_CONFIG_DIR, file));
4258
4390
  } catch {
4259
4391
  }
4260
4392
  }
@@ -4269,7 +4401,7 @@ var init_mcp_config = __esm({
4269
4401
  init_paths();
4270
4402
  __filename = fileURLToPath(import.meta.url);
4271
4403
  __dirname = dirname(__filename);
4272
- MCP_CONFIG_DIR = join5(CC_CLAW_HOME, "mcp-configs");
4404
+ MCP_CONFIG_DIR = join7(CC_CLAW_HOME, "mcp-configs");
4273
4405
  }
4274
4406
  });
4275
4407
 
@@ -4280,7 +4412,7 @@ __export(loader_exports, {
4280
4412
  listTemplates: () => listTemplates
4281
4413
  });
4282
4414
  import { readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
4283
- import { join as join6 } from "path";
4415
+ import { join as join8 } from "path";
4284
4416
  function parseFrontmatter(content) {
4285
4417
  const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m);
4286
4418
  if (!match || match.index !== 0) return { meta: {}, body: content.trim() };
@@ -4318,7 +4450,7 @@ function scanTemplates() {
4318
4450
  return;
4319
4451
  }
4320
4452
  for (const file of files) {
4321
- const filePath = join6(AGENTS_PATH, file);
4453
+ const filePath = join8(AGENTS_PATH, file);
4322
4454
  try {
4323
4455
  const raw = readFileSync5(filePath, "utf-8");
4324
4456
  const { meta, body } = parseFrontmatter(raw);
@@ -6092,7 +6224,7 @@ function stopAgent(chatId) {
6092
6224
  }
6093
6225
  return true;
6094
6226
  }
6095
- function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs) {
6227
+ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs, maxTurns) {
6096
6228
  const effectiveTimeout = timeoutMs ?? SPAWN_TIMEOUT_MS;
6097
6229
  return new Promise((resolve, reject) => {
6098
6230
  const thinkingConfig = thinkingLevel && thinkingLevel !== "auto" ? adapter.applyThinkingConfig(thinkingLevel, model2) : void 0;
@@ -6123,6 +6255,7 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
6123
6255
  let cacheRead = 0;
6124
6256
  let sawToolEvents = false;
6125
6257
  let sawResultEvent = false;
6258
+ let toolTurnCount = 0;
6126
6259
  const t0 = Date.now();
6127
6260
  const elapsed = () => `${((Date.now() - t0) / 1e3).toFixed(1)}s`;
6128
6261
  const pendingTools = /* @__PURE__ */ new Map();
@@ -6164,6 +6297,13 @@ function spawnQuery(adapter, config2, model2, cancelState, onStream, onToolActio
6164
6297
  error("[agent] tool action error:", err);
6165
6298
  });
6166
6299
  }
6300
+ if (maxTurns && adapter.id !== "claude") {
6301
+ toolTurnCount++;
6302
+ if (toolTurnCount >= maxTurns) {
6303
+ log(`[agent] Turn limit ${maxTurns} reached for ${adapter.id} -- stopping`);
6304
+ killProcessGroup(proc, "SIGTERM");
6305
+ }
6306
+ }
6167
6307
  break;
6168
6308
  case "tool_end":
6169
6309
  if (onToolAction) {
@@ -6246,7 +6386,7 @@ function askAgent(chatId, userMessage, opts) {
6246
6386
  return withChatLock(chatId, () => askAgentImpl(chatId, userMessage, opts));
6247
6387
  }
6248
6388
  async function askAgentImpl(chatId, userMessage, opts) {
6249
- const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs } = opts ?? {};
6389
+ const { cwd, onStream, model: model2, backend: backend2, permMode, onToolAction, bootstrapTier, timeoutMs, maxTurns } = opts ?? {};
6250
6390
  const adapter = backend2 ? getAdapter(backend2) : getAdapterForChat(chatId);
6251
6391
  const mode = permMode ?? getMode(chatId);
6252
6392
  const responseStyle = getResponseStyle(chatId);
@@ -6263,7 +6403,8 @@ async function askAgentImpl(chatId, userMessage, opts) {
6263
6403
  permMode: mode,
6264
6404
  allowedTools,
6265
6405
  cwd: resolvedCwd,
6266
- thinkingLevel
6406
+ thinkingLevel,
6407
+ maxTurns
6267
6408
  });
6268
6409
  if (mcpConfigPath) {
6269
6410
  baseConfig.args = injectMcpConfig(adapter.id, baseConfig.args, mcpConfigPath);
@@ -6276,7 +6417,8 @@ async function askAgentImpl(chatId, userMessage, opts) {
6276
6417
  permMode: mode,
6277
6418
  allowedTools,
6278
6419
  cwd: resolvedCwd,
6279
- thinkingLevel
6420
+ thinkingLevel,
6421
+ maxTurns
6280
6422
  });
6281
6423
  if (mcpConfigPath) {
6282
6424
  cfg.args = injectMcpConfig(adapter.id, cfg.args, mcpConfigPath);
@@ -6288,13 +6430,13 @@ async function askAgentImpl(chatId, userMessage, opts) {
6288
6430
  const resolvedModel = model2 ?? adapter.defaultModel;
6289
6431
  let result = { resultText: "", sessionId: void 0, input: 0, output: 0, cacheRead: 0, sawToolEvents: false, sawResultEvent: false };
6290
6432
  try {
6291
- result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs);
6433
+ result = await spawnQuery(adapter, configWithSession, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs, maxTurns);
6292
6434
  const wasEmptyResponse = !result.resultText && !result.sawToolEvents && !result.sawResultEvent;
6293
6435
  if (wasEmptyResponse && !cancelState.cancelled && existingSessionId) {
6294
6436
  warn(`[agent] No result with session ${existingSessionId} for chat ${chatId} \u2014 retrying fresh`);
6295
6437
  await summarizeSession(chatId);
6296
6438
  clearSession(chatId);
6297
- result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs);
6439
+ result = await spawnQuery(adapter, baseConfig, resolvedModel, cancelState, onStream, onToolAction, thinkingLevel, timeoutMs, maxTurns);
6298
6440
  }
6299
6441
  } finally {
6300
6442
  activeChats.delete(chatId);
@@ -6316,7 +6458,7 @@ async function askAgentImpl(chatId, userMessage, opts) {
6316
6458
  if (result.resultText) {
6317
6459
  appendToLog(chatId, userMessage, result.resultText, adapter.id, model2 ?? null, result.sessionId ?? null);
6318
6460
  const AUTO_SUMMARIZE_THRESHOLD = 30;
6319
- const pairCount = getMessagePairCount(chatId);
6461
+ const pairCount = tier !== "chat" ? getMessagePairCount(chatId) : 0;
6320
6462
  if (pairCount >= AUTO_SUMMARIZE_THRESHOLD) {
6321
6463
  log(`[agent] Auto-summarizing chat ${chatId} after ${pairCount} turns`);
6322
6464
  summarizeWithFallbackChain(chatId).then((saved) => {
@@ -6969,7 +7111,7 @@ var init_cron = __esm({
6969
7111
  });
6970
7112
 
6971
7113
  // src/agents/runners/wrap-backend.ts
6972
- import { join as join7 } from "path";
7114
+ import { join as join9 } from "path";
6973
7115
  function buildMcpCommands(backendId) {
6974
7116
  const exe = backendId;
6975
7117
  return {
@@ -7060,7 +7202,7 @@ function wrapBackendAdapter(adapter) {
7060
7202
  const configPath = writeMcpConfigFile(server);
7061
7203
  return ["--mcp-config", configPath];
7062
7204
  },
7063
- getSkillPath: () => join7(SKILLS_PATH, `agent-${adapter.id}.md`)
7205
+ getSkillPath: () => join9(SKILLS_PATH, `agent-${adapter.id}.md`)
7064
7206
  };
7065
7207
  }
7066
7208
  var BACKEND_CAPABILITIES;
@@ -7103,7 +7245,7 @@ var init_wrap_backend = __esm({
7103
7245
 
7104
7246
  // src/agents/runners/config-loader.ts
7105
7247
  import { readFileSync as readFileSync6, readdirSync as readdirSync4, existsSync as existsSync9, mkdirSync as mkdirSync4, watchFile, unwatchFile } from "fs";
7106
- import { join as join8 } from "path";
7248
+ import { join as join10 } from "path";
7107
7249
  import { execFileSync } from "child_process";
7108
7250
  function resolveExecutable(config2) {
7109
7251
  if (existsSync9(config2.executable)) return config2.executable;
@@ -7239,7 +7381,7 @@ function configToRunner(config2) {
7239
7381
  prepareMcpInjection() {
7240
7382
  return [];
7241
7383
  },
7242
- getSkillPath: () => join8(SKILLS_PATH, `agent-${config2.id}.md`)
7384
+ getSkillPath: () => join10(SKILLS_PATH, `agent-${config2.id}.md`)
7243
7385
  };
7244
7386
  }
7245
7387
  function loadRunnerConfig(filePath) {
@@ -7259,7 +7401,7 @@ function loadAllRunnerConfigs() {
7259
7401
  const files = readdirSync4(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
7260
7402
  const configs = [];
7261
7403
  for (const file of files) {
7262
- const config2 = loadRunnerConfig(join8(RUNNERS_PATH, file));
7404
+ const config2 = loadRunnerConfig(join10(RUNNERS_PATH, file));
7263
7405
  if (config2) configs.push(config2);
7264
7406
  }
7265
7407
  return configs;
@@ -7289,7 +7431,7 @@ function watchRunnerConfigs(onChange) {
7289
7431
  }
7290
7432
  const files = readdirSync4(RUNNERS_PATH).filter((f) => f.endsWith(".json"));
7291
7433
  for (const file of files) {
7292
- const fullPath = join8(RUNNERS_PATH, file);
7434
+ const fullPath = join10(RUNNERS_PATH, file);
7293
7435
  if (watchedFiles.has(fullPath)) continue;
7294
7436
  watchedFiles.add(fullPath);
7295
7437
  watchFile(fullPath, { interval: 5e3 }, () => {
@@ -7600,9 +7742,11 @@ var init_telegram2 = __esm({
7600
7742
  { command: "voice", description: "Toggle voice responses" },
7601
7743
  { command: "voice_config", description: "Configure voice provider and voice" },
7602
7744
  { command: "response_style", description: "Set the AI response style (concise/normal/detailed)" },
7745
+ { command: "model_signature", description: "Toggle model+thinking signature on responses" },
7603
7746
  { command: "imagine", description: "Generate an image from a prompt" },
7604
7747
  { command: "heartbeat", description: "Configure proactive heartbeat" },
7605
- { command: "chats", description: "Manage multi-chat aliases" }
7748
+ { command: "chats", description: "Manage multi-chat aliases" },
7749
+ { command: "intent", description: "Test intent classifier on a message" }
7606
7750
  ]);
7607
7751
  this.bot.on("message", (ctx) => {
7608
7752
  const chatId = ctx.chat.id.toString();
@@ -7876,22 +8020,44 @@ var init_telegram2 = __esm({
7876
8020
  var discover_exports = {};
7877
8021
  __export(discover_exports, {
7878
8022
  discoverAllSkills: () => discoverAllSkills,
8023
+ invalidateSkillCache: () => invalidateSkillCache,
7879
8024
  stripFrontmatter: () => stripFrontmatter2
7880
8025
  });
7881
8026
  import { readdir, readFile as readFile2 } from "fs/promises";
7882
8027
  import { createHash } from "crypto";
7883
8028
  import { homedir as homedir4 } from "os";
7884
- import { join as join9 } from "path";
8029
+ import { join as join11 } from "path";
8030
+ function invalidateSkillCache() {
8031
+ cachedSkills = null;
8032
+ cacheTimestamp = 0;
8033
+ }
7885
8034
  async function discoverAllSkills() {
7886
- const rawSkills = [];
7887
- rawSkills.push(...await scanSkillDir(SKILLS_PATH, "cc-claw"));
7888
- for (const backendId of getAllBackendIds()) {
7889
- const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join9(homedir4(), `.${backendId}`, "skills")];
7890
- for (const dir of dirs) {
7891
- rawSkills.push(...await scanSkillDir(dir, backendId));
7892
- }
8035
+ const now = Date.now();
8036
+ if (cachedSkills !== null && now - cacheTimestamp < CACHE_TTL_MS2) {
8037
+ return cachedSkills;
8038
+ }
8039
+ if (pendingScan !== null) {
8040
+ return pendingScan;
7893
8041
  }
7894
- return mergeAndDeduplicate(rawSkills);
8042
+ pendingScan = (async () => {
8043
+ try {
8044
+ const rawSkills = [];
8045
+ rawSkills.push(...await scanSkillDir(SKILLS_PATH, "cc-claw"));
8046
+ for (const backendId of getAllBackendIds()) {
8047
+ const dirs = BACKEND_SKILL_DIRS[backendId] ?? [join11(homedir4(), `.${backendId}`, "skills")];
8048
+ for (const dir of dirs) {
8049
+ rawSkills.push(...await scanSkillDir(dir, backendId));
8050
+ }
8051
+ }
8052
+ const result = mergeAndDeduplicate(rawSkills);
8053
+ cachedSkills = result;
8054
+ cacheTimestamp = Date.now();
8055
+ return result;
8056
+ } finally {
8057
+ pendingScan = null;
8058
+ }
8059
+ })();
8060
+ return pendingScan;
7895
8061
  }
7896
8062
  async function scanSkillDir(skillsDir, source) {
7897
8063
  const results = [];
@@ -7906,7 +8072,7 @@ async function scanSkillDir(skillsDir, source) {
7906
8072
  let content;
7907
8073
  let resolvedPath;
7908
8074
  for (const candidate of SKILL_FILE_CANDIDATES) {
7909
- const p = join9(skillsDir, entry.name, candidate);
8075
+ const p = join11(skillsDir, entry.name, candidate);
7910
8076
  try {
7911
8077
  content = await readFile2(p, "utf-8");
7912
8078
  resolvedPath = p;
@@ -8001,7 +8167,7 @@ function parseFrontmatter2(content, fallbackName) {
8001
8167
  function stripFrontmatter2(content) {
8002
8168
  return content.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "").trim();
8003
8169
  }
8004
- var SKILL_FILE_CANDIDATES, BACKEND_SKILL_DIRS;
8170
+ var SKILL_FILE_CANDIDATES, BACKEND_SKILL_DIRS, CACHE_TTL_MS2, cachedSkills, cacheTimestamp, pendingScan;
8005
8171
  var init_discover = __esm({
8006
8172
  "src/skills/discover.ts"() {
8007
8173
  "use strict";
@@ -8009,13 +8175,17 @@ var init_discover = __esm({
8009
8175
  init_backends();
8010
8176
  SKILL_FILE_CANDIDATES = ["SKILL.md", "skill.md"];
8011
8177
  BACKEND_SKILL_DIRS = {
8012
- claude: [join9(homedir4(), ".claude", "skills")],
8013
- gemini: [join9(homedir4(), ".gemini", "skills")],
8178
+ claude: [join11(homedir4(), ".claude", "skills")],
8179
+ gemini: [join11(homedir4(), ".gemini", "skills")],
8014
8180
  codex: [
8015
- join9(homedir4(), ".agents", "skills"),
8016
- join9(homedir4(), ".codex", "skills")
8181
+ join11(homedir4(), ".agents", "skills"),
8182
+ join11(homedir4(), ".codex", "skills")
8017
8183
  ]
8018
8184
  };
8185
+ CACHE_TTL_MS2 = 3e5;
8186
+ cachedSkills = null;
8187
+ cacheTimestamp = 0;
8188
+ pendingScan = null;
8019
8189
  }
8020
8190
  });
8021
8191
 
@@ -8026,7 +8196,7 @@ __export(install_exports, {
8026
8196
  });
8027
8197
  import { mkdir, readdir as readdir2, readFile as readFile3, cp } from "fs/promises";
8028
8198
  import { existsSync as existsSync10 } from "fs";
8029
- import { join as join10, basename } from "path";
8199
+ import { join as join12, basename } from "path";
8030
8200
  import { execSync as execSync4 } from "child_process";
8031
8201
  async function installSkillFromGitHub(urlOrShorthand) {
8032
8202
  let repoUrl;
@@ -8037,23 +8207,23 @@ async function installSkillFromGitHub(urlOrShorthand) {
8037
8207
  }
8038
8208
  repoUrl = parsed.cloneUrl;
8039
8209
  subPath = parsed.subPath;
8040
- const tmpDir = join10("/tmp", `cc-claw-skill-${Date.now()}`);
8210
+ const tmpDir = join12("/tmp", `cc-claw-skill-${Date.now()}`);
8041
8211
  try {
8042
8212
  log(`[skill-install] Cloning ${repoUrl} to ${tmpDir}`);
8043
8213
  execSync4(`git clone --depth 1 ${repoUrl} ${tmpDir}`, {
8044
8214
  stdio: "pipe",
8045
8215
  timeout: 3e4
8046
8216
  });
8047
- if (!existsSync10(join10(tmpDir, ".git"))) {
8217
+ if (!existsSync10(join12(tmpDir, ".git"))) {
8048
8218
  return { success: false, error: "Git clone failed: no .git directory produced" };
8049
8219
  }
8050
- const searchRoot = subPath ? join10(tmpDir, subPath) : tmpDir;
8220
+ const searchRoot = subPath ? join12(tmpDir, subPath) : tmpDir;
8051
8221
  const skillDir = await findSkillDir(searchRoot);
8052
8222
  if (!skillDir) {
8053
8223
  return { success: false, error: "No SKILL.md found in the repository." };
8054
8224
  }
8055
8225
  const skillFolderName = basename(skillDir);
8056
- const destDir = join10(SKILLS_PATH, skillFolderName);
8226
+ const destDir = join12(SKILLS_PATH, skillFolderName);
8057
8227
  if (existsSync10(destDir)) {
8058
8228
  log(`[skill-install] Overwriting existing skill at ${destDir}`);
8059
8229
  }
@@ -8061,12 +8231,12 @@ async function installSkillFromGitHub(urlOrShorthand) {
8061
8231
  await cp(skillDir, destDir, { recursive: true });
8062
8232
  let skillName = skillFolderName;
8063
8233
  try {
8064
- const content = await readFile3(join10(destDir, "SKILL.md"), "utf-8");
8234
+ const content = await readFile3(join12(destDir, "SKILL.md"), "utf-8");
8065
8235
  const nameMatch = content.match(/^name:\s*(.+)$/m);
8066
8236
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
8067
8237
  } catch {
8068
8238
  try {
8069
- const content = await readFile3(join10(destDir, "skill.md"), "utf-8");
8239
+ const content = await readFile3(join12(destDir, "skill.md"), "utf-8");
8070
8240
  const nameMatch = content.match(/^name:\s*(.+)$/m);
8071
8241
  if (nameMatch) skillName = nameMatch[1].trim().replace(/^["']|["']$/g, "");
8072
8242
  } catch {
@@ -8101,15 +8271,15 @@ function parseGitHubUrl(input) {
8101
8271
  async function findSkillDir(root) {
8102
8272
  const candidates = ["SKILL.md", "skill.md"];
8103
8273
  for (const c of candidates) {
8104
- if (existsSync10(join10(root, c))) return root;
8274
+ if (existsSync10(join12(root, c))) return root;
8105
8275
  }
8106
8276
  try {
8107
8277
  const entries = await readdir2(root, { withFileTypes: true });
8108
8278
  for (const entry of entries) {
8109
8279
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8110
8280
  for (const c of candidates) {
8111
- if (existsSync10(join10(root, entry.name, c))) {
8112
- return join10(root, entry.name);
8281
+ if (existsSync10(join12(root, entry.name, c))) {
8282
+ return join12(root, entry.name);
8113
8283
  }
8114
8284
  }
8115
8285
  }
@@ -8121,15 +8291,15 @@ async function findSkillDir(root) {
8121
8291
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8122
8292
  let subEntries;
8123
8293
  try {
8124
- subEntries = await readdir2(join10(root, entry.name), { withFileTypes: true });
8294
+ subEntries = await readdir2(join12(root, entry.name), { withFileTypes: true });
8125
8295
  } catch {
8126
8296
  continue;
8127
8297
  }
8128
8298
  for (const sub of subEntries) {
8129
8299
  if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
8130
8300
  for (const c of candidates) {
8131
- if (existsSync10(join10(root, entry.name, sub.name, c))) {
8132
- return join10(root, entry.name, sub.name);
8301
+ if (existsSync10(join12(root, entry.name, sub.name, c))) {
8302
+ return join12(root, entry.name, sub.name);
8133
8303
  }
8134
8304
  }
8135
8305
  }
@@ -8148,7 +8318,7 @@ var init_install = __esm({
8148
8318
 
8149
8319
  // src/bootstrap/profile.ts
8150
8320
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync11 } from "fs";
8151
- import { join as join11 } from "path";
8321
+ import { join as join13 } from "path";
8152
8322
  function hasActiveProfile(chatId) {
8153
8323
  return activeProfiles.has(chatId);
8154
8324
  }
@@ -8263,7 +8433,7 @@ Timezone: ${state.timezone}
8263
8433
  Style: ${state.style}
8264
8434
  Use: ${state.useCase}
8265
8435
 
8266
- You can edit ~/.cc-claw/workspace/USER.md anytime or run /setup-profile again.`,
8436
+ You can edit ~/.cc-claw/identity/USER.md anytime or run /setup-profile again.`,
8267
8437
  "plain"
8268
8438
  );
8269
8439
  }
@@ -8293,14 +8463,14 @@ var init_profile = __esm({
8293
8463
  "use strict";
8294
8464
  init_paths();
8295
8465
  init_log();
8296
- USER_PATH2 = join11(WORKSPACE_PATH, "USER.md");
8466
+ USER_PATH2 = join13(IDENTITY_PATH, "USER.md");
8297
8467
  activeProfiles = /* @__PURE__ */ new Map();
8298
8468
  }
8299
8469
  });
8300
8470
 
8301
8471
  // src/bootstrap/heartbeat.ts
8302
8472
  import { readFileSync as readFileSync8, existsSync as existsSync12 } from "fs";
8303
- import { join as join12 } from "path";
8473
+ import { join as join14 } from "path";
8304
8474
  function initHeartbeat(channelReg) {
8305
8475
  registry2 = channelReg;
8306
8476
  }
@@ -8486,7 +8656,7 @@ var init_heartbeat = __esm({
8486
8656
  init_backends();
8487
8657
  init_health2();
8488
8658
  init_log();
8489
- HEARTBEAT_MD_PATH = join12(WORKSPACE_PATH, "HEARTBEAT.md");
8659
+ HEARTBEAT_MD_PATH = join14(WORKSPACE_PATH, "HEARTBEAT.md");
8490
8660
  HEARTBEAT_OK = "HEARTBEAT_OK";
8491
8661
  registry2 = null;
8492
8662
  activeTimers2 = /* @__PURE__ */ new Map();
@@ -8531,10 +8701,127 @@ var init_format_time = __esm({
8531
8701
  }
8532
8702
  });
8533
8703
 
8704
+ // src/intent/classify.ts
8705
+ function classifyIntent(text, chatId) {
8706
+ const trimmed = text.trim();
8707
+ if (trimmed.startsWith(">>")) return "agentic";
8708
+ if (trimmed.startsWith("/")) return "agentic";
8709
+ const lower = trimmed.toLowerCase();
8710
+ for (const pattern of AGENTIC_PATTERNS) {
8711
+ if (pattern.test(trimmed)) {
8712
+ log(`[intent] "${trimmed.slice(0, 30)}..." -> agentic (pattern: ${pattern})`);
8713
+ return "agentic";
8714
+ }
8715
+ }
8716
+ const sessionId = getSessionId(chatId);
8717
+ if (sessionId) {
8718
+ const lastTs = getLastMessageTimestamp(chatId);
8719
+ if (lastTs) {
8720
+ const elapsed = Date.now() - lastTs;
8721
+ if (elapsed < 12e4) {
8722
+ if (trimmed.length < 30) {
8723
+ log(`[intent] "${trimmed.slice(0, 30)}" -> agentic (active session, ${(elapsed / 1e3).toFixed(0)}s ago)`);
8724
+ return "agentic";
8725
+ }
8726
+ }
8727
+ }
8728
+ }
8729
+ if (CHAT_EXACT.has(lower)) {
8730
+ log(`[intent] "${lower}" -> chat (exact match)`);
8731
+ return "chat";
8732
+ }
8733
+ if (trimmed.length <= 4 && /^[\p{Emoji}\s]+$/u.test(trimmed)) {
8734
+ log(`[intent] "${trimmed}" -> chat (emoji-only)`);
8735
+ return "chat";
8736
+ }
8737
+ log(`[intent] "${trimmed.slice(0, 30)}..." -> agentic (default)`);
8738
+ return "agentic";
8739
+ }
8740
+ var CHAT_EXACT, AGENTIC_PATTERNS;
8741
+ var init_classify = __esm({
8742
+ "src/intent/classify.ts"() {
8743
+ "use strict";
8744
+ init_store4();
8745
+ init_session_log();
8746
+ init_log();
8747
+ CHAT_EXACT = /* @__PURE__ */ new Set([
8748
+ "hey",
8749
+ "hi",
8750
+ "hello",
8751
+ "yo",
8752
+ "sup",
8753
+ "howdy",
8754
+ "hiya",
8755
+ "thanks",
8756
+ "thank you",
8757
+ "thx",
8758
+ "ty",
8759
+ "thank u",
8760
+ "ok",
8761
+ "okay",
8762
+ "k",
8763
+ "kk",
8764
+ "cool",
8765
+ "nice",
8766
+ "great",
8767
+ "awesome",
8768
+ "perfect",
8769
+ "good morning",
8770
+ "good night",
8771
+ "good evening",
8772
+ "gm",
8773
+ "gn",
8774
+ "bye",
8775
+ "goodbye",
8776
+ "later",
8777
+ "see ya",
8778
+ "cya",
8779
+ "lol",
8780
+ "lmao",
8781
+ "haha",
8782
+ "heh",
8783
+ "np",
8784
+ "no problem",
8785
+ "no worries",
8786
+ "nw",
8787
+ "got it",
8788
+ "understood",
8789
+ "roger",
8790
+ "copy",
8791
+ "good",
8792
+ "fine",
8793
+ "alright",
8794
+ "sure"
8795
+ ]);
8796
+ AGENTIC_PATTERNS = [
8797
+ /\.\w{1,5}$/,
8798
+ // file extensions (.ts, .py, .md)
8799
+ /[\/\\][\w.-]+/,
8800
+ // file paths
8801
+ /`[^`]+`/,
8802
+ // inline code
8803
+ /```/,
8804
+ // code blocks
8805
+ /\b(fix|create|build|deploy|run|execute|install|delete|remove|update|edit|write|read|check|test|debug|refactor|implement|add|change|modify|move|copy|rename|push|pull|commit|merge|rebase)\b/i,
8806
+ // imperative verbs
8807
+ /\b(error|bug|crash|fail|broken|issue|problem|exception|stack ?trace)\b/i,
8808
+ // error keywords
8809
+ /\b(function|class|const|let|var|import|export|return|async|await)\b/i,
8810
+ // code keywords
8811
+ /[!]{1,2}\s/,
8812
+ // shell prefix
8813
+ /^\/\//,
8814
+ // backend command prefix
8815
+ /\b(file|folder|directory|script|server|database|api|endpoint|config|package|module|component)\b/i
8816
+ // tech nouns
8817
+ ];
8818
+ }
8819
+ });
8820
+
8534
8821
  // src/media/image-gen.ts
8535
8822
  import { mkdirSync as mkdirSync5, existsSync as existsSync13 } from "fs";
8536
8823
  import { writeFile as writeFile2 } from "fs/promises";
8537
- import { join as join13 } from "path";
8824
+ import { join as join15 } from "path";
8538
8825
  async function generateImage(prompt) {
8539
8826
  const apiKey = process.env.GEMINI_API_KEY;
8540
8827
  if (!apiKey) {
@@ -8586,7 +8873,7 @@ async function generateImage(prompt) {
8586
8873
  }
8587
8874
  const ext = mimeType.includes("jpeg") || mimeType.includes("jpg") ? "jpg" : "png";
8588
8875
  const filename = `img_${Date.now()}.${ext}`;
8589
- const filePath = join13(IMAGE_OUTPUT_DIR, filename);
8876
+ const filePath = join15(IMAGE_OUTPUT_DIR, filename);
8590
8877
  const buffer = Buffer.from(imageData, "base64");
8591
8878
  await writeFile2(filePath, buffer);
8592
8879
  log(`[image-gen] Saved ${buffer.length} bytes to ${filePath}`);
@@ -8601,8 +8888,8 @@ var init_image_gen = __esm({
8601
8888
  "use strict";
8602
8889
  init_log();
8603
8890
  IMAGE_MODEL = "gemini-3.1-flash-image-preview";
8604
- IMAGE_OUTPUT_DIR = join13(
8605
- process.env.CC_CLAW_HOME ?? join13(process.env.HOME ?? "/tmp", ".cc-claw"),
8891
+ IMAGE_OUTPUT_DIR = join15(
8892
+ process.env.CC_CLAW_HOME ?? join15(process.env.HOME ?? "/tmp", ".cc-claw"),
8606
8893
  "data",
8607
8894
  "images"
8608
8895
  );
@@ -9689,6 +9976,25 @@ function shortModelName(modelId) {
9689
9976
  }
9690
9977
  return modelId;
9691
9978
  }
9979
+ function formatModelShort(modelId) {
9980
+ const MAP = {
9981
+ "claude-opus-4-6": "Opus 4.6",
9982
+ "claude-sonnet-4-6": "Sonnet 4.6",
9983
+ "claude-haiku-4-5": "Haiku 4.5",
9984
+ "gemini-3.1-pro-preview": "Gemini Pro 3.1",
9985
+ "gemini-3-flash-preview": "Gemini 3 Flash",
9986
+ "gpt-5.4": "GPT-5.4",
9987
+ "gpt-5.3-codex": "GPT-5.3 Codex",
9988
+ "gpt-5.2-codex": "GPT-5.2 Codex",
9989
+ "gpt-5.1-codex-max": "GPT-5.1 Codex Max",
9990
+ "gpt-5.4-mini": "GPT-5.4-mini"
9991
+ };
9992
+ return MAP[modelId] ?? modelId;
9993
+ }
9994
+ function capitalize(s) {
9995
+ if (s === "extra_high") return "xHigh";
9996
+ return s.charAt(0).toUpperCase() + s.slice(1);
9997
+ }
9692
9998
  function resolveModel(chatId) {
9693
9999
  const explicit = getModel(chatId);
9694
10000
  if (explicit) return explicit;
@@ -9767,7 +10073,7 @@ async function handleCommand(msg, channel) {
9767
10073
  case "help":
9768
10074
  await channel.sendText(
9769
10075
  chatId,
9770
- "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/remember <text> - Save a memory\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/voice_config - Configure voice provider and voice\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/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",
10076
+ "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/remember <text> - Save a memory\n/forget <keyword> - Remove a memory\n/voice - Toggle voice responses\n/voice_config - Configure voice provider and voice\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/plan)\n/verbose - Tool visibility (off/normal/verbose)\n/model_signature - Toggle model+thinking signature on responses\n/intent <msg> - Test intent classifier (chat vs agentic)\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",
9771
10077
  "plain"
9772
10078
  );
9773
10079
  break;
@@ -10294,6 +10600,23 @@ ${lines.join("\n")}`, "plain");
10294
10600
  }
10295
10601
  break;
10296
10602
  }
10603
+ case "model_signature": {
10604
+ const currentSig = getModelSignature(chatId);
10605
+ if (typeof channel.sendKeyboard === "function") {
10606
+ await channel.sendKeyboard(chatId, `Model Signature: ${currentSig === "on" ? "ON" : "OFF"}
10607
+ Appends model + thinking level to each response.`, [
10608
+ [
10609
+ { label: currentSig === "on" ? "\u2713 On" : "On", data: "model_sig:on" },
10610
+ { label: currentSig !== "on" ? "\u2713 Off" : "Off", data: "model_sig:off" }
10611
+ ]
10612
+ ]);
10613
+ } else {
10614
+ const newSig = currentSig === "on" ? "off" : "on";
10615
+ setModelSignature(chatId, newSig);
10616
+ await channel.sendText(chatId, newSig === "on" ? "Model signature enabled." : "Model signature disabled.", "plain");
10617
+ }
10618
+ break;
10619
+ }
10297
10620
  case "imagine":
10298
10621
  case "image": {
10299
10622
  if (!commandArgs) {
@@ -10933,6 +11256,13 @@ Use /skills to see it.`, "plain");
10933
11256
  }
10934
11257
  break;
10935
11258
  }
11259
+ case "intent": {
11260
+ const testMsg = commandArgs?.trim() || "hey";
11261
+ const result = classifyIntent(testMsg, chatId);
11262
+ await channel.sendText(chatId, `Intent: ${result}
11263
+ Message: "${testMsg}"`, "plain");
11264
+ break;
11265
+ }
10936
11266
  default:
10937
11267
  await channel.sendText(chatId, `Unknown command: /${command}. Type /help for available commands.`, "plain");
10938
11268
  }
@@ -11113,6 +11443,10 @@ async function handleText(msg, channel) {
11113
11443
  await channel.sendText(chatId, limitMsg, "plain");
11114
11444
  return;
11115
11445
  }
11446
+ const intent = classifyIntent(text, chatId);
11447
+ const cleanText = text.startsWith(">>") ? text.slice(2).trim() : text;
11448
+ const bootstrapTier = intent === "chat" ? "chat" : void 0;
11449
+ const maxTurns = intent === "chat" ? 1 : void 0;
11116
11450
  if (isChatBusy(chatId) && !bypassBusyCheck.delete(chatId)) {
11117
11451
  if (typeof channel.sendKeyboard === "function") {
11118
11452
  pendingInterrupts.set(chatId, { msg, channel });
@@ -11141,20 +11475,33 @@ async function handleText(msg, channel) {
11141
11475
  const tMode = getMode(chatId);
11142
11476
  const tVerbose = getVerboseLevel(chatId);
11143
11477
  const tToolCb = tVerbose !== "off" ? makeToolActionCallback(chatId, channel, tVerbose) : void 0;
11144
- const response = await askAgent(chatId, text, {
11478
+ const response = await askAgent(chatId, cleanText || text, {
11145
11479
  cwd: getCwd(chatId),
11146
11480
  model: model2,
11147
11481
  permMode: tMode,
11148
11482
  onToolAction: tToolCb,
11483
+ bootstrapTier,
11484
+ maxTurns,
11149
11485
  onCompaction: (cid) => {
11150
11486
  channel.sendText(cid, "\u{1F4BE} Context saved to memory.").catch(() => {
11151
11487
  });
11152
11488
  }
11153
11489
  });
11154
11490
  if (response.usage) addUsage(chatId, response.usage.input, response.usage.output, response.usage.cacheRead, model2);
11155
- if (response.text.includes("[NEED_PERMISSION]") && tMode !== "yolo") {
11156
- const cleanText = response.text.replace(/\[NEED_PERMISSION\]/g, "").trim();
11157
- await sendResponse(chatId, channel, cleanText, msg.messageId);
11491
+ let responseText = response.text;
11492
+ const sigEnabled = getModelSignature(chatId);
11493
+ if (sigEnabled === "on" && responseText && !responseText.startsWith("(No response")) {
11494
+ const adapter = getAdapterForChat(chatId);
11495
+ const modelId = model2 ?? adapter.defaultModel;
11496
+ const thinking2 = getThinkingLevel(chatId) || "auto";
11497
+ const shortModel = formatModelShort(modelId);
11498
+ responseText += `
11499
+
11500
+ \u{1F9E0} [${shortModel} | ${capitalize(thinking2)}]`;
11501
+ }
11502
+ if (responseText.includes("[NEED_PERMISSION]") && tMode !== "yolo") {
11503
+ const cleanText2 = responseText.replace(/\[NEED_PERMISSION\]/g, "").trim();
11504
+ await sendResponse(chatId, channel, cleanText2, msg.messageId);
11158
11505
  const targetMode = determineEscalationTarget(chatId, tMode);
11159
11506
  storePendingEscalation(chatId, text);
11160
11507
  if (typeof channel.sendKeyboard === "function") {
@@ -11164,7 +11511,7 @@ async function handleText(msg, channel) {
11164
11511
  }
11165
11512
  return;
11166
11513
  }
11167
- await sendResponse(chatId, channel, response.text, msg.messageId);
11514
+ await sendResponse(chatId, channel, responseText, msg.messageId);
11168
11515
  } catch (err) {
11169
11516
  error("[router] Error:", err);
11170
11517
  const errMsg = errorMessage(err);
@@ -11745,6 +12092,14 @@ ${PERM_MODES[chosen]}`,
11745
12092
  }
11746
12093
  await channel.sendText(chatId, `Response style set to: ${selectedStyle}`, "plain");
11747
12094
  }
12095
+ } else if (data.startsWith("model_sig:")) {
12096
+ const value = data.slice(10);
12097
+ setModelSignature(chatId, value);
12098
+ await channel.sendText(
12099
+ chatId,
12100
+ value === "on" ? "\u{1F9E0} Model signature enabled. Each response will show the active model and thinking level." : "Model signature disabled.",
12101
+ "plain"
12102
+ );
11748
12103
  } else if (data.startsWith("vcfg:")) {
11749
12104
  const parts = data.slice(5).split(":");
11750
12105
  const action = parts[0];
@@ -11999,6 +12354,7 @@ var init_router = __esm({
11999
12354
  init_format_time();
12000
12355
  init_agent();
12001
12356
  init_retry();
12357
+ init_classify();
12002
12358
  init_image_gen();
12003
12359
  init_stt();
12004
12360
  init_store4();
@@ -12128,7 +12484,7 @@ var init_router = __esm({
12128
12484
  // src/skills/bootstrap.ts
12129
12485
  import { existsSync as existsSync14 } from "fs";
12130
12486
  import { readdir as readdir3, readFile as readFile6, writeFile as writeFile4, copyFile } from "fs/promises";
12131
- import { join as join14, dirname as dirname2 } from "path";
12487
+ import { join as join16, dirname as dirname2 } from "path";
12132
12488
  import { fileURLToPath as fileURLToPath2 } from "url";
12133
12489
  async function copyAgentManifestSkills() {
12134
12490
  if (!existsSync14(PKG_SKILLS)) return;
@@ -12136,8 +12492,8 @@ async function copyAgentManifestSkills() {
12136
12492
  const entries = await readdir3(PKG_SKILLS, { withFileTypes: true });
12137
12493
  for (const entry of entries) {
12138
12494
  if (!entry.isFile() || !entry.name.startsWith("agent-") || !entry.name.endsWith(".md")) continue;
12139
- const src = join14(PKG_SKILLS, entry.name);
12140
- const dest = join14(SKILLS_PATH, entry.name);
12495
+ const src = join16(PKG_SKILLS, entry.name);
12496
+ const dest = join16(SKILLS_PATH, entry.name);
12141
12497
  if (existsSync14(dest)) continue;
12142
12498
  await copyFile(src, dest);
12143
12499
  log(`[skills] Bootstrapped ${entry.name} to ${SKILLS_PATH}`);
@@ -12148,7 +12504,7 @@ async function copyAgentManifestSkills() {
12148
12504
  }
12149
12505
  async function bootstrapSkills() {
12150
12506
  await copyAgentManifestSkills();
12151
- const usmDir = join14(SKILLS_PATH, USM_DIR_NAME);
12507
+ const usmDir = join16(SKILLS_PATH, USM_DIR_NAME);
12152
12508
  if (existsSync14(usmDir)) return;
12153
12509
  try {
12154
12510
  const entries = await readdir3(SKILLS_PATH);
@@ -12171,7 +12527,7 @@ async function bootstrapSkills() {
12171
12527
  }
12172
12528
  }
12173
12529
  async function patchUsmForCcClaw(usmDir) {
12174
- const skillPath = join14(usmDir, "SKILL.md");
12530
+ const skillPath = join16(usmDir, "SKILL.md");
12175
12531
  if (!existsSync14(skillPath)) return;
12176
12532
  try {
12177
12533
  let content = await readFile6(skillPath, "utf-8");
@@ -12217,8 +12573,8 @@ var init_bootstrap = __esm({
12217
12573
  USM_REPO = "jacob-bd/universal-skills-manager";
12218
12574
  USM_DIR_NAME = "universal-skills-manager";
12219
12575
  CC_CLAW_ECOSYSTEM_PATCH = `| **CC-Claw** | \`~/.cc-claw/workspace/skills/\` | N/A (daemon, no project scope) |`;
12220
- PKG_ROOT = join14(dirname2(fileURLToPath2(import.meta.url)), "..", "..");
12221
- PKG_SKILLS = join14(PKG_ROOT, "skills");
12576
+ PKG_ROOT = join16(dirname2(fileURLToPath2(import.meta.url)), "..", "..");
12577
+ PKG_SKILLS = join16(PKG_ROOT, "skills");
12222
12578
  }
12223
12579
  });
12224
12580
 
@@ -12433,7 +12789,7 @@ __export(ai_skill_exports, {
12433
12789
  installAiSkill: () => installAiSkill
12434
12790
  });
12435
12791
  import { existsSync as existsSync15, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
12436
- import { join as join15 } from "path";
12792
+ import { join as join17 } from "path";
12437
12793
  import { homedir as homedir5 } from "os";
12438
12794
  function generateAiSkill() {
12439
12795
  const version = VERSION;
@@ -12734,8 +13090,8 @@ function installAiSkill() {
12734
13090
  const failed = [];
12735
13091
  for (const [backend2, dirs] of Object.entries(BACKEND_SKILL_DIRS2)) {
12736
13092
  for (const dir of dirs) {
12737
- const skillDir = join15(dir, "cc-claw-cli");
12738
- const skillPath = join15(skillDir, "SKILL.md");
13093
+ const skillDir = join17(dir, "cc-claw-cli");
13094
+ const skillPath = join17(skillDir, "SKILL.md");
12739
13095
  try {
12740
13096
  mkdirSync6(skillDir, { recursive: true });
12741
13097
  writeFileSync5(skillPath, skill, "utf-8");
@@ -12754,10 +13110,10 @@ var init_ai_skill = __esm({
12754
13110
  init_paths();
12755
13111
  init_version();
12756
13112
  BACKEND_SKILL_DIRS2 = {
12757
- "cc-claw": [join15(homedir5(), ".cc-claw", "workspace", "skills")],
12758
- claude: [join15(homedir5(), ".claude", "skills")],
12759
- gemini: [join15(homedir5(), ".gemini", "skills")],
12760
- codex: [join15(homedir5(), ".agents", "skills")]
13113
+ "cc-claw": [join17(homedir5(), ".cc-claw", "workspace", "skills")],
13114
+ claude: [join17(homedir5(), ".claude", "skills")],
13115
+ gemini: [join17(homedir5(), ".gemini", "skills")],
13116
+ codex: [join17(homedir5(), ".agents", "skills")]
12761
13117
  };
12762
13118
  }
12763
13119
  });
@@ -12768,17 +13124,17 @@ __export(index_exports, {
12768
13124
  main: () => main
12769
13125
  });
12770
13126
  import { mkdirSync as mkdirSync7, existsSync as existsSync16, renameSync, statSync as statSync2, readFileSync as readFileSync10 } from "fs";
12771
- import { join as join16 } from "path";
13127
+ import { join as join18 } from "path";
12772
13128
  import dotenv from "dotenv";
12773
13129
  function migrateLayout() {
12774
13130
  const moves = [
12775
- [join16(CC_CLAW_HOME, "cc-claw.db"), join16(DATA_PATH, "cc-claw.db")],
12776
- [join16(CC_CLAW_HOME, "cc-claw.db-shm"), join16(DATA_PATH, "cc-claw.db-shm")],
12777
- [join16(CC_CLAW_HOME, "cc-claw.db-wal"), join16(DATA_PATH, "cc-claw.db-wal")],
12778
- [join16(CC_CLAW_HOME, "cc-claw.log"), join16(LOGS_PATH, "cc-claw.log")],
12779
- [join16(CC_CLAW_HOME, "cc-claw.log.1"), join16(LOGS_PATH, "cc-claw.log.1")],
12780
- [join16(CC_CLAW_HOME, "cc-claw.error.log"), join16(LOGS_PATH, "cc-claw.error.log")],
12781
- [join16(CC_CLAW_HOME, "cc-claw.error.log.1"), join16(LOGS_PATH, "cc-claw.error.log.1")]
13131
+ [join18(CC_CLAW_HOME, "cc-claw.db"), join18(DATA_PATH, "cc-claw.db")],
13132
+ [join18(CC_CLAW_HOME, "cc-claw.db-shm"), join18(DATA_PATH, "cc-claw.db-shm")],
13133
+ [join18(CC_CLAW_HOME, "cc-claw.db-wal"), join18(DATA_PATH, "cc-claw.db-wal")],
13134
+ [join18(CC_CLAW_HOME, "cc-claw.log"), join18(LOGS_PATH, "cc-claw.log")],
13135
+ [join18(CC_CLAW_HOME, "cc-claw.log.1"), join18(LOGS_PATH, "cc-claw.log.1")],
13136
+ [join18(CC_CLAW_HOME, "cc-claw.error.log"), join18(LOGS_PATH, "cc-claw.error.log")],
13137
+ [join18(CC_CLAW_HOME, "cc-claw.error.log.1"), join18(LOGS_PATH, "cc-claw.error.log.1")]
12782
13138
  ];
12783
13139
  for (const [from, to] of moves) {
12784
13140
  if (existsSync16(from) && !existsSync16(to)) {
@@ -12884,10 +13240,10 @@ async function main() {
12884
13240
  try {
12885
13241
  const { generateAiSkill: generateAiSkill2 } = await Promise.resolve().then(() => (init_ai_skill(), ai_skill_exports));
12886
13242
  const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync11 } = await import("fs");
12887
- const { join: join19 } = await import("path");
12888
- const skillDir = join19(SKILLS_PATH, "cc-claw-cli");
13243
+ const { join: join21 } = await import("path");
13244
+ const skillDir = join21(SKILLS_PATH, "cc-claw-cli");
12889
13245
  mkdirSync11(skillDir, { recursive: true });
12890
- writeFileSync8(join19(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
13246
+ writeFileSync8(join21(skillDir, "SKILL.md"), generateAiSkill2(), "utf-8");
12891
13247
  log("[cc-claw] AI skill updated");
12892
13248
  } catch {
12893
13249
  }
@@ -12970,6 +13326,106 @@ var init_index = __esm({
12970
13326
  }
12971
13327
  });
12972
13328
 
13329
+ // src/cli/api-client.ts
13330
+ var api_client_exports = {};
13331
+ __export(api_client_exports, {
13332
+ apiGet: () => apiGet,
13333
+ apiPost: () => apiPost,
13334
+ isDaemonRunning: () => isDaemonRunning
13335
+ });
13336
+ import { readFileSync as readFileSync11, existsSync as existsSync17 } from "fs";
13337
+ import { request as httpRequest } from "http";
13338
+ function getToken() {
13339
+ if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
13340
+ try {
13341
+ if (existsSync17(TOKEN_PATH)) return readFileSync11(TOKEN_PATH, "utf-8").trim();
13342
+ } catch {
13343
+ }
13344
+ return null;
13345
+ }
13346
+ async function isDaemonRunning() {
13347
+ try {
13348
+ const res = await apiGet("/api/health");
13349
+ return res.ok;
13350
+ } catch {
13351
+ return false;
13352
+ }
13353
+ }
13354
+ async function apiGet(path) {
13355
+ const token = getToken();
13356
+ return new Promise((resolve, reject) => {
13357
+ const url = new URL(path, BASE_URL);
13358
+ const req = httpRequest(url, {
13359
+ method: "GET",
13360
+ headers: token ? { "Authorization": `Bearer ${token}` } : {},
13361
+ timeout: 3e3
13362
+ }, (res) => {
13363
+ const chunks = [];
13364
+ res.on("data", (c) => chunks.push(c));
13365
+ res.on("end", () => {
13366
+ const body = Buffer.concat(chunks).toString();
13367
+ try {
13368
+ const data = JSON.parse(body);
13369
+ resolve({ ok: res.statusCode === 200, status: res.statusCode ?? 0, data });
13370
+ } catch {
13371
+ resolve({ ok: false, status: res.statusCode ?? 0, data: body });
13372
+ }
13373
+ });
13374
+ });
13375
+ req.on("error", reject);
13376
+ req.on("timeout", () => {
13377
+ req.destroy();
13378
+ reject(new Error("Request timed out"));
13379
+ });
13380
+ req.end();
13381
+ });
13382
+ }
13383
+ async function apiPost(path, body) {
13384
+ const token = getToken();
13385
+ const payload = JSON.stringify(body);
13386
+ return new Promise((resolve, reject) => {
13387
+ const url = new URL(path, BASE_URL);
13388
+ const req = httpRequest(url, {
13389
+ method: "POST",
13390
+ headers: {
13391
+ "Content-Type": "application/json",
13392
+ "Content-Length": Buffer.byteLength(payload).toString(),
13393
+ ...token ? { "Authorization": `Bearer ${token}` } : {}
13394
+ },
13395
+ timeout: 3e4
13396
+ }, (res) => {
13397
+ const chunks = [];
13398
+ res.on("data", (c) => chunks.push(c));
13399
+ res.on("end", () => {
13400
+ const responseBody = Buffer.concat(chunks).toString();
13401
+ try {
13402
+ const data = JSON.parse(responseBody);
13403
+ resolve({ ok: res.statusCode === 200, status: res.statusCode ?? 0, data });
13404
+ } catch {
13405
+ resolve({ ok: false, status: res.statusCode ?? 0, data: responseBody });
13406
+ }
13407
+ });
13408
+ });
13409
+ req.on("error", reject);
13410
+ req.on("timeout", () => {
13411
+ req.destroy();
13412
+ reject(new Error("Request timed out"));
13413
+ });
13414
+ req.write(payload);
13415
+ req.end();
13416
+ });
13417
+ }
13418
+ var TOKEN_PATH, DEFAULT_PORT, BASE_URL;
13419
+ var init_api_client = __esm({
13420
+ "src/cli/api-client.ts"() {
13421
+ "use strict";
13422
+ init_paths();
13423
+ TOKEN_PATH = `${DATA_PATH}/api-token`;
13424
+ DEFAULT_PORT = parseInt(process.env.DASHBOARD_PORT ?? "3141", 10);
13425
+ BASE_URL = `http://127.0.0.1:${DEFAULT_PORT}`;
13426
+ }
13427
+ });
13428
+
12973
13429
  // src/service.ts
12974
13430
  var service_exports = {};
12975
13431
  __export(service_exports, {
@@ -12977,10 +13433,10 @@ __export(service_exports, {
12977
13433
  serviceStatus: () => serviceStatus,
12978
13434
  uninstallService: () => uninstallService
12979
13435
  });
12980
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync2 } from "fs";
13436
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3 } from "fs";
12981
13437
  import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
12982
13438
  import { homedir as homedir6, platform } from "os";
12983
- import { join as join17, dirname as dirname3 } from "path";
13439
+ import { join as join19, dirname as dirname3 } from "path";
12984
13440
  function resolveExecutable2(name) {
12985
13441
  try {
12986
13442
  return execFileSync2("which", [name], { encoding: "utf-8" }).trim();
@@ -12993,14 +13449,14 @@ function getPathDirs() {
12993
13449
  const home = homedir6();
12994
13450
  const dirs = /* @__PURE__ */ new Set([
12995
13451
  nodeBin,
12996
- join17(home, ".local", "bin"),
13452
+ join19(home, ".local", "bin"),
12997
13453
  "/usr/local/bin",
12998
13454
  "/usr/bin",
12999
13455
  "/bin"
13000
13456
  ]);
13001
13457
  try {
13002
13458
  const prefix = execSync5("npm config get prefix", { encoding: "utf-8" }).trim();
13003
- if (prefix) dirs.add(join17(prefix, "bin"));
13459
+ if (prefix) dirs.add(join19(prefix, "bin"));
13004
13460
  } catch {
13005
13461
  }
13006
13462
  return [...dirs].join(":");
@@ -13055,9 +13511,9 @@ function generatePlist() {
13055
13511
  }
13056
13512
  function installMacOS() {
13057
13513
  const agentsDir = dirname3(PLIST_PATH);
13058
- if (!existsSync17(agentsDir)) mkdirSync8(agentsDir, { recursive: true });
13059
- if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
13060
- if (existsSync17(PLIST_PATH)) {
13514
+ if (!existsSync18(agentsDir)) mkdirSync8(agentsDir, { recursive: true });
13515
+ if (!existsSync18(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
13516
+ if (existsSync18(PLIST_PATH)) {
13061
13517
  try {
13062
13518
  execFileSync2("launchctl", ["unload", PLIST_PATH]);
13063
13519
  } catch {
@@ -13069,7 +13525,7 @@ function installMacOS() {
13069
13525
  console.log(" Service loaded and starting.");
13070
13526
  }
13071
13527
  function uninstallMacOS() {
13072
- if (!existsSync17(PLIST_PATH)) {
13528
+ if (!existsSync18(PLIST_PATH)) {
13073
13529
  console.log(" No service found to uninstall.");
13074
13530
  return;
13075
13531
  }
@@ -13077,9 +13533,27 @@ function uninstallMacOS() {
13077
13533
  execFileSync2("launchctl", ["unload", PLIST_PATH]);
13078
13534
  } catch {
13079
13535
  }
13080
- unlinkSync2(PLIST_PATH);
13536
+ unlinkSync3(PLIST_PATH);
13081
13537
  console.log(" Service uninstalled.");
13082
13538
  }
13539
+ function formatUptime(seconds) {
13540
+ seconds = Math.floor(seconds);
13541
+ if (seconds < 60) return `${seconds}s`;
13542
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
13543
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m`;
13544
+ return `${Math.floor(seconds / 86400)}d ${Math.floor(seconds % 86400 / 3600)}h`;
13545
+ }
13546
+ async function getUptimeFromDaemon() {
13547
+ try {
13548
+ const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
13549
+ if (!await isDaemonRunning2()) return null;
13550
+ const res = await apiGet2("/api/health");
13551
+ const sec = res.data?.uptime;
13552
+ return sec ? formatUptime(sec) : null;
13553
+ } catch {
13554
+ return null;
13555
+ }
13556
+ }
13083
13557
  function statusMacOS() {
13084
13558
  try {
13085
13559
  const out = execSync5("launchctl list | grep cc-claw", { shell: "/bin/sh", encoding: "utf-8" }).trim();
@@ -13088,7 +13562,12 @@ function statusMacOS() {
13088
13562
  const pid = parts[0];
13089
13563
  const exitCode = parts[1];
13090
13564
  if (pid !== "-") {
13091
- console.log(` Running (PID ${pid})`);
13565
+ getUptimeFromDaemon().then((uptime) => {
13566
+ const uptimeStr = uptime ? `, uptime ${uptime}` : "";
13567
+ console.log(` Running (PID ${pid}${uptimeStr})`);
13568
+ }).catch(() => {
13569
+ console.log(` Running (PID ${pid})`);
13570
+ });
13092
13571
  } else {
13093
13572
  console.log(` Not running (last exit code: ${exitCode})`);
13094
13573
  }
@@ -13120,8 +13599,8 @@ WantedBy=default.target
13120
13599
  `;
13121
13600
  }
13122
13601
  function installLinux() {
13123
- if (!existsSync17(SYSTEMD_DIR)) mkdirSync8(SYSTEMD_DIR, { recursive: true });
13124
- if (!existsSync17(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
13602
+ if (!existsSync18(SYSTEMD_DIR)) mkdirSync8(SYSTEMD_DIR, { recursive: true });
13603
+ if (!existsSync18(LOGS_PATH)) mkdirSync8(LOGS_PATH, { recursive: true });
13125
13604
  writeFileSync6(UNIT_PATH, generateUnit());
13126
13605
  console.log(` Installed: ${UNIT_PATH}`);
13127
13606
  execFileSync2("systemctl", ["--user", "daemon-reload"]);
@@ -13130,7 +13609,7 @@ function installLinux() {
13130
13609
  console.log(" Service enabled and started.");
13131
13610
  }
13132
13611
  function uninstallLinux() {
13133
- if (!existsSync17(UNIT_PATH)) {
13612
+ if (!existsSync18(UNIT_PATH)) {
13134
13613
  console.log(" No service found to uninstall.");
13135
13614
  return;
13136
13615
  }
@@ -13142,7 +13621,7 @@ function uninstallLinux() {
13142
13621
  execFileSync2("systemctl", ["--user", "disable", "cc-claw"]);
13143
13622
  } catch {
13144
13623
  }
13145
- unlinkSync2(UNIT_PATH);
13624
+ unlinkSync3(UNIT_PATH);
13146
13625
  execFileSync2("systemctl", ["--user", "daemon-reload"]);
13147
13626
  console.log(" Service uninstalled.");
13148
13627
  }
@@ -13155,7 +13634,7 @@ function statusLinux() {
13155
13634
  }
13156
13635
  }
13157
13636
  function installService() {
13158
- if (!existsSync17(join17(CC_CLAW_HOME, ".env"))) {
13637
+ if (!existsSync18(join19(CC_CLAW_HOME, ".env"))) {
13159
13638
  console.error(` Config not found at ${CC_CLAW_HOME}/.env`);
13160
13639
  console.error(" Run 'cc-claw setup' before installing the service.");
13161
13640
  process.exitCode = 1;
@@ -13184,9 +13663,9 @@ var init_service = __esm({
13184
13663
  "use strict";
13185
13664
  init_paths();
13186
13665
  PLIST_LABEL = "com.cc-claw";
13187
- PLIST_PATH = join17(homedir6(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
13188
- SYSTEMD_DIR = join17(homedir6(), ".config", "systemd", "user");
13189
- UNIT_PATH = join17(SYSTEMD_DIR, "cc-claw.service");
13666
+ PLIST_PATH = join19(homedir6(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
13667
+ SYSTEMD_DIR = join19(homedir6(), ".config", "systemd", "user");
13668
+ UNIT_PATH = join19(SYSTEMD_DIR, "cc-claw.service");
13190
13669
  }
13191
13670
  });
13192
13671
 
@@ -13353,13 +13832,13 @@ var init_daemon = __esm({
13353
13832
  });
13354
13833
 
13355
13834
  // src/cli/resolve-chat.ts
13356
- import { readFileSync as readFileSync12 } from "fs";
13835
+ import { readFileSync as readFileSync13 } from "fs";
13357
13836
  function resolveChatId(globalOpts) {
13358
13837
  const explicit = globalOpts.chat;
13359
13838
  if (explicit) return explicit;
13360
13839
  if (_cachedDefault) return _cachedDefault;
13361
13840
  try {
13362
- const content = readFileSync12(ENV_PATH, "utf-8");
13841
+ const content = readFileSync13(ENV_PATH, "utf-8");
13363
13842
  const match = content.match(/^ALLOWED_CHAT_ID=(.+)$/m);
13364
13843
  if (match) {
13365
13844
  _cachedDefault = match[1].split(",")[0].trim();
@@ -13378,106 +13857,6 @@ var init_resolve_chat = __esm({
13378
13857
  }
13379
13858
  });
13380
13859
 
13381
- // src/cli/api-client.ts
13382
- var api_client_exports = {};
13383
- __export(api_client_exports, {
13384
- apiGet: () => apiGet,
13385
- apiPost: () => apiPost,
13386
- isDaemonRunning: () => isDaemonRunning
13387
- });
13388
- import { readFileSync as readFileSync13, existsSync as existsSync18 } from "fs";
13389
- import { request as httpRequest } from "http";
13390
- function getToken() {
13391
- if (process.env.CC_CLAW_API_TOKEN) return process.env.CC_CLAW_API_TOKEN;
13392
- try {
13393
- if (existsSync18(TOKEN_PATH)) return readFileSync13(TOKEN_PATH, "utf-8").trim();
13394
- } catch {
13395
- }
13396
- return null;
13397
- }
13398
- async function isDaemonRunning() {
13399
- try {
13400
- const res = await apiGet("/api/health");
13401
- return res.ok;
13402
- } catch {
13403
- return false;
13404
- }
13405
- }
13406
- async function apiGet(path) {
13407
- const token = getToken();
13408
- return new Promise((resolve, reject) => {
13409
- const url = new URL(path, BASE_URL);
13410
- const req = httpRequest(url, {
13411
- method: "GET",
13412
- headers: token ? { "Authorization": `Bearer ${token}` } : {},
13413
- timeout: 3e3
13414
- }, (res) => {
13415
- const chunks = [];
13416
- res.on("data", (c) => chunks.push(c));
13417
- res.on("end", () => {
13418
- const body = Buffer.concat(chunks).toString();
13419
- try {
13420
- const data = JSON.parse(body);
13421
- resolve({ ok: res.statusCode === 200, status: res.statusCode ?? 0, data });
13422
- } catch {
13423
- resolve({ ok: false, status: res.statusCode ?? 0, data: body });
13424
- }
13425
- });
13426
- });
13427
- req.on("error", reject);
13428
- req.on("timeout", () => {
13429
- req.destroy();
13430
- reject(new Error("Request timed out"));
13431
- });
13432
- req.end();
13433
- });
13434
- }
13435
- async function apiPost(path, body) {
13436
- const token = getToken();
13437
- const payload = JSON.stringify(body);
13438
- return new Promise((resolve, reject) => {
13439
- const url = new URL(path, BASE_URL);
13440
- const req = httpRequest(url, {
13441
- method: "POST",
13442
- headers: {
13443
- "Content-Type": "application/json",
13444
- "Content-Length": Buffer.byteLength(payload).toString(),
13445
- ...token ? { "Authorization": `Bearer ${token}` } : {}
13446
- },
13447
- timeout: 3e4
13448
- }, (res) => {
13449
- const chunks = [];
13450
- res.on("data", (c) => chunks.push(c));
13451
- res.on("end", () => {
13452
- const responseBody = Buffer.concat(chunks).toString();
13453
- try {
13454
- const data = JSON.parse(responseBody);
13455
- resolve({ ok: res.statusCode === 200, status: res.statusCode ?? 0, data });
13456
- } catch {
13457
- resolve({ ok: false, status: res.statusCode ?? 0, data: responseBody });
13458
- }
13459
- });
13460
- });
13461
- req.on("error", reject);
13462
- req.on("timeout", () => {
13463
- req.destroy();
13464
- reject(new Error("Request timed out"));
13465
- });
13466
- req.write(payload);
13467
- req.end();
13468
- });
13469
- }
13470
- var TOKEN_PATH, DEFAULT_PORT, BASE_URL;
13471
- var init_api_client = __esm({
13472
- "src/cli/api-client.ts"() {
13473
- "use strict";
13474
- init_paths();
13475
- TOKEN_PATH = `${DATA_PATH}/api-token`;
13476
- DEFAULT_PORT = parseInt(process.env.DASHBOARD_PORT ?? "3141", 10);
13477
- BASE_URL = `http://127.0.0.1:${DEFAULT_PORT}`;
13478
- }
13479
- });
13480
-
13481
13860
  // src/cli/commands/status.ts
13482
13861
  var status_exports = {};
13483
13862
  __export(status_exports, {
@@ -13575,7 +13954,7 @@ async function statusCommand(globalOpts, localOpts) {
13575
13954
  kvLine("Voice", s.voice ? success("on") : muted("off"))
13576
13955
  ];
13577
13956
  if (localOpts.deep) {
13578
- lines.push(kvLine("Daemon", s.daemon.running ? success(`running${s.daemon.uptime_seconds ? ` (uptime ${formatUptime(s.daemon.uptime_seconds)})` : ""}`) : error2("offline")));
13957
+ lines.push(kvLine("Daemon", s.daemon.running ? success(`running${s.daemon.uptime_seconds ? ` (uptime ${formatUptime2(s.daemon.uptime_seconds)})` : ""}`) : error2("offline")));
13579
13958
  }
13580
13959
  lines.push(
13581
13960
  "",
@@ -13593,7 +13972,7 @@ async function statusCommand(globalOpts, localOpts) {
13593
13972
  process.exit(1);
13594
13973
  }
13595
13974
  }
13596
- function formatUptime(seconds) {
13975
+ function formatUptime2(seconds) {
13597
13976
  if (seconds < 60) return `${seconds}s`;
13598
13977
  if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
13599
13978
  if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m`;
@@ -13665,10 +14044,12 @@ async function doctorCommand(globalOpts, localOpts) {
13665
14044
  const { isDaemonRunning: isDaemonRunning2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
13666
14045
  const running = await isDaemonRunning2();
13667
14046
  if (running) {
13668
- checks.push({ name: "Daemon", status: "ok", message: "running" });
13669
14047
  try {
13670
14048
  const health = await apiGet2("/api/health");
13671
14049
  const daemonVersion = health.data?.version;
14050
+ const uptimeSec = health.data?.uptime;
14051
+ const uptimeStr = uptimeSec ? ` (uptime ${formatUptime3(uptimeSec)})` : "";
14052
+ checks.push({ name: "Daemon", status: "ok", message: `running${uptimeStr}` });
13672
14053
  if (daemonVersion && daemonVersion !== VERSION) {
13673
14054
  checks.push({
13674
14055
  name: "Version",
@@ -13680,6 +14061,7 @@ async function doctorCommand(globalOpts, localOpts) {
13680
14061
  checks.push({ name: "Version", status: "ok", message: `v${VERSION}` });
13681
14062
  }
13682
14063
  } catch {
14064
+ checks.push({ name: "Daemon", status: "ok", message: "running" });
13683
14065
  }
13684
14066
  } else {
13685
14067
  checks.push({ name: "Daemon", status: "warning", message: "not running", fix: "cc-claw service start" });
@@ -13818,6 +14200,13 @@ async function doctorCommand(globalOpts, localOpts) {
13818
14200
  });
13819
14201
  process.exit(errors > 0 ? 1 : warnings > 0 ? 2 : 0);
13820
14202
  }
14203
+ function formatUptime3(seconds) {
14204
+ seconds = Math.floor(seconds);
14205
+ if (seconds < 60) return `${seconds}s`;
14206
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
14207
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor(seconds % 3600 / 60)}m`;
14208
+ return `${Math.floor(seconds / 86400)}d ${Math.floor(seconds % 86400 / 3600)}h`;
14209
+ }
13821
14210
  var init_doctor = __esm({
13822
14211
  "src/cli/commands/doctor.ts"() {
13823
14212
  "use strict";
@@ -14588,7 +14977,7 @@ __export(db_exports, {
14588
14977
  dbPath: () => dbPath,
14589
14978
  dbStats: () => dbStats
14590
14979
  });
14591
- import { existsSync as existsSync27, statSync as statSync5, copyFileSync, mkdirSync as mkdirSync9 } from "fs";
14980
+ import { existsSync as existsSync27, statSync as statSync5, copyFileSync as copyFileSync2, mkdirSync as mkdirSync9 } from "fs";
14592
14981
  import { dirname as dirname4 } from "path";
14593
14982
  async function dbStats(globalOpts) {
14594
14983
  if (!existsSync27(DB_PATH)) {
@@ -14640,9 +15029,9 @@ async function dbBackup(globalOpts, destPath) {
14640
15029
  const dest = destPath ?? `${DB_PATH}.backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
14641
15030
  try {
14642
15031
  mkdirSync9(dirname4(dest), { recursive: true });
14643
- copyFileSync(DB_PATH, dest);
15032
+ copyFileSync2(DB_PATH, dest);
14644
15033
  const walPath = DB_PATH + "-wal";
14645
- if (existsSync27(walPath)) copyFileSync(walPath, dest + "-wal");
15034
+ if (existsSync27(walPath)) copyFileSync2(walPath, dest + "-wal");
14646
15035
  output({ path: dest, sizeBytes: statSync5(dest).size }, (d) => {
14647
15036
  const b = d;
14648
15037
  return `
@@ -16037,10 +16426,10 @@ var init_completion = __esm({
16037
16426
 
16038
16427
  // src/setup.ts
16039
16428
  var setup_exports = {};
16040
- import { existsSync as existsSync38, writeFileSync as writeFileSync7, readFileSync as readFileSync17, copyFileSync as copyFileSync2, mkdirSync as mkdirSync10, statSync as statSync6 } from "fs";
16429
+ import { existsSync as existsSync38, writeFileSync as writeFileSync7, readFileSync as readFileSync17, copyFileSync as copyFileSync3, mkdirSync as mkdirSync10, statSync as statSync6 } from "fs";
16041
16430
  import { execFileSync as execFileSync4 } from "child_process";
16042
16431
  import { createInterface as createInterface5 } from "readline";
16043
- import { join as join18 } from "path";
16432
+ import { join as join20 } from "path";
16044
16433
  function divider2() {
16045
16434
  console.log(dim("\u2500".repeat(55)));
16046
16435
  }
@@ -16127,13 +16516,13 @@ async function setup() {
16127
16516
  if (match) env[match[1].trim()] = match[2].trim();
16128
16517
  }
16129
16518
  }
16130
- const cwdDb = join18(process.cwd(), "cc-claw.db");
16519
+ const cwdDb = join20(process.cwd(), "cc-claw.db");
16131
16520
  if (existsSync38(cwdDb) && !existsSync38(DB_PATH)) {
16132
16521
  const { size } = statSync6(cwdDb);
16133
16522
  console.log(yellow(` Found existing database at ${cwdDb} (${(size / 1024).toFixed(0)}KB)`));
16134
16523
  const migrate = await confirm("Copy database to ~/.cc-claw/? (preserves memories & history)", true);
16135
16524
  if (migrate) {
16136
- copyFileSync2(cwdDb, DB_PATH);
16525
+ copyFileSync3(cwdDb, DB_PATH);
16137
16526
  console.log(green(` Database copied to ${DB_PATH}`));
16138
16527
  }
16139
16528
  console.log("");