cc-claw 0.20.5 → 0.20.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +262 -51
  2. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var VERSION;
33
33
  var init_version = __esm({
34
34
  "src/version.ts"() {
35
35
  "use strict";
36
- VERSION = true ? "0.20.5" : (() => {
36
+ VERSION = true ? "0.20.6" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -26367,8 +26367,12 @@ var init_telegram2 = __esm({
26367
26367
  if (!authorized) return;
26368
26368
  const msg = this.buildIncomingMessage(ctx);
26369
26369
  if (!msg) {
26370
- this.sendText(chatId, "I can handle text, voice, photos, documents, and videos. This message type isn't supported yet.", { parseMode: "plain" }).catch(() => {
26371
- });
26370
+ const m = ctx.message;
26371
+ const hasUserContent = m && (m.sticker || m.animation || m.contact || m.location || m.poll || m.dice || m.video_note || m.audio);
26372
+ if (hasUserContent) {
26373
+ this.sendText(chatId, "I can handle text, voice, photos, documents, and videos. This message type isn't supported yet.", { parseMode: "plain" }).catch(() => {
26374
+ });
26375
+ }
26372
26376
  return;
26373
26377
  }
26374
26378
  log(`[telegram] Processing ${msg.type}: "${msg.text?.slice(0, 50) || msg.command || "(media)"}"`);
@@ -28054,7 +28058,7 @@ async function apiGet(path) {
28054
28058
  req.end();
28055
28059
  });
28056
28060
  }
28057
- async function apiPost(path, body) {
28061
+ async function apiPost(path, body, opts) {
28058
28062
  const token = getToken();
28059
28063
  const payload = JSON.stringify(body);
28060
28064
  return new Promise((resolve, reject) => {
@@ -28066,7 +28070,7 @@ async function apiPost(path, body) {
28066
28070
  "Content-Length": Buffer.byteLength(payload).toString(),
28067
28071
  ...token ? { "Authorization": `Bearer ${token}` } : {}
28068
28072
  },
28069
- timeout: 3e4,
28073
+ timeout: opts?.timeout ?? 3e4,
28070
28074
  agent: keepAliveAgent
28071
28075
  }, (res) => {
28072
28076
  const chunks = [];
@@ -32234,37 +32238,151 @@ var init_chat2 = __esm({
32234
32238
  }
32235
32239
  });
32236
32240
 
32241
+ // src/cli/theme.ts
32242
+ import pc2 from "picocolors";
32243
+ function setTheme(mode) {
32244
+ currentTheme = mode;
32245
+ }
32246
+ function getCurrentThemeMode() {
32247
+ return currentTheme;
32248
+ }
32249
+ function detectTerminalBackground() {
32250
+ const colorfgbg = process.env.COLORFGBG;
32251
+ if (colorfgbg) {
32252
+ const parts = colorfgbg.split(";");
32253
+ const bg = parseInt(parts[parts.length - 1], 10);
32254
+ if (!isNaN(bg) && bg >= 7) return "light";
32255
+ return "dark";
32256
+ }
32257
+ if (process.env.TERM_PROGRAM === "Apple_Terminal") return "light";
32258
+ return "dark";
32259
+ }
32260
+ function getTheme() {
32261
+ const mode = currentTheme === "auto" ? detectTerminalBackground() : currentTheme;
32262
+ if (mode === "light") {
32263
+ return {
32264
+ prompt: pc2.blue,
32265
+ border: pc2.black,
32266
+ success: (s) => pc2.green(pc2.bold(s)),
32267
+ text: pc2.black,
32268
+ dim: pc2.gray,
32269
+ error: (s) => pc2.red(pc2.bold(s)),
32270
+ warn: pc2.yellow
32271
+ };
32272
+ }
32273
+ return {
32274
+ prompt: pc2.cyan,
32275
+ border: pc2.gray,
32276
+ success: (s) => pc2.green(pc2.bold(s)),
32277
+ text: pc2.white,
32278
+ dim: pc2.dim,
32279
+ error: (s) => pc2.red(pc2.bold(s)),
32280
+ warn: pc2.yellow
32281
+ };
32282
+ }
32283
+ var currentTheme;
32284
+ var init_theme = __esm({
32285
+ "src/cli/theme.ts"() {
32286
+ "use strict";
32287
+ currentTheme = "auto";
32288
+ }
32289
+ });
32290
+
32291
+ // src/cli/tui-completer.ts
32292
+ function tuiCompleter(line) {
32293
+ for (const [cmd, args] of Object.entries(COMMAND_ARGS)) {
32294
+ const prefix = cmd + " ";
32295
+ if (line.startsWith(prefix)) {
32296
+ const partial = line.slice(prefix.length).toLowerCase();
32297
+ const hits = args.filter((a) => a.toLowerCase().startsWith(partial));
32298
+ const completions = hits.map((a) => `${cmd} ${a}`);
32299
+ return [completions.length ? completions : args.map((a) => `${cmd} ${a}`), line];
32300
+ }
32301
+ }
32302
+ if (line.startsWith("/")) {
32303
+ const hits = BASE_COMMANDS.filter((c) => c.startsWith(line));
32304
+ return [hits.length ? hits : [], line];
32305
+ }
32306
+ return [[], line];
32307
+ }
32308
+ var BASE_COMMANDS, COMMAND_ARGS;
32309
+ var init_tui_completer = __esm({
32310
+ "src/cli/tui-completer.ts"() {
32311
+ "use strict";
32312
+ BASE_COMMANDS = [
32313
+ "/quit",
32314
+ "/exit",
32315
+ "/new",
32316
+ "/status",
32317
+ "/stop",
32318
+ "/backend",
32319
+ "/model",
32320
+ "/theme",
32321
+ "/help",
32322
+ "/cost",
32323
+ "/usage",
32324
+ "/memory",
32325
+ "/permissions",
32326
+ "/thinking",
32327
+ "/cwd",
32328
+ "/debug",
32329
+ "/evolve"
32330
+ ];
32331
+ COMMAND_ARGS = {
32332
+ "/backend": ["Claude", "Gemini", "Codex", "Cursor"],
32333
+ "/theme": ["dark", "light", "auto"],
32334
+ "/permissions": ["yolo", "safe", "readonly", "plan"],
32335
+ "/thinking": ["off", "low", "medium", "high"]
32336
+ };
32337
+ }
32338
+ });
32339
+
32237
32340
  // src/cli/commands/tui.ts
32238
32341
  var tui_exports = {};
32239
32342
  __export(tui_exports, {
32240
32343
  tuiCommand: () => tuiCommand
32241
32344
  });
32242
32345
  import { createInterface as createInterface10 } from "readline";
32243
- import pc2 from "picocolors";
32346
+ import pc3 from "picocolors";
32347
+ import { marked as marked2 } from "marked";
32348
+ import TerminalRenderer from "marked-terminal";
32244
32349
  async function tuiCommand(globalOpts, cmdOpts) {
32245
- const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
32350
+ const { isDaemonRunning: isDaemonRunning2, apiPost: apiPost2, apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
32246
32351
  if (!await isDaemonRunning2()) {
32247
32352
  outputError("DAEMON_OFFLINE", "CC-Claw daemon is not running.\n\n Start it with: cc-claw service start");
32248
32353
  process.exit(1);
32249
32354
  }
32250
32355
  const chatId = resolveChatId2(globalOpts);
32251
- const { chatSend: chatSend2 } = await Promise.resolve().then(() => (init_chat2(), chat_exports2));
32356
+ let theme = getTheme();
32252
32357
  const rl2 = createInterface10({
32253
32358
  input: process.stdin,
32254
32359
  output: process.stdout,
32255
- prompt: pc2.cyan("you > "),
32360
+ completer: tuiCompleter,
32256
32361
  terminal: true
32257
32362
  });
32363
+ function updatePrompt() {
32364
+ rl2.setPrompt(theme.prompt("you > "));
32365
+ }
32366
+ console.log("");
32367
+ console.log(theme.text(pc3.bold(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")));
32368
+ console.log(theme.text(pc3.bold(" \u2551 CC-Claw Terminal Chat \u2551")));
32369
+ console.log(theme.text(pc3.bold(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")));
32370
+ try {
32371
+ const health = await apiGet2("/api/health");
32372
+ if (health.ok) {
32373
+ const parts = [];
32374
+ if (health.data.backend) parts.push(`Backend: ${pc3.bold(health.data.backend)}`);
32375
+ if (health.data.model) parts.push(`Model: ${pc3.bold(health.data.model)}`);
32376
+ parts.push(`Uptime: ${Math.floor(health.data.uptime / 60)}m`);
32377
+ console.log(theme.dim(` ${parts.join(" \u2022 ")}`));
32378
+ }
32379
+ } catch {
32380
+ }
32381
+ console.log(theme.dim(` Chat: ${chatId} \u2022 Theme: ${getCurrentThemeMode()}`));
32258
32382
  console.log("");
32259
- console.log(pc2.bold(" CC-Claw Terminal Chat"));
32260
- console.log(pc2.dim(" Type a message and press Enter. Commands:"));
32261
- console.log(pc2.dim(" /quit or /exit \u2014 Exit the TUI"));
32262
- console.log(pc2.dim(" /new \u2014 Start a new session"));
32263
- console.log(pc2.dim(" /status \u2014 Show current status"));
32264
- console.log(pc2.dim(" /stop \u2014 Cancel running task"));
32265
- console.log(pc2.dim(" /backend <name> \u2014 Switch backend"));
32266
- console.log(pc2.dim(" /model <name> \u2014 Switch model"));
32383
+ console.log(theme.dim(" Commands: /help for list \u2022 Tab to autocomplete \u2022 Ctrl+C to cancel"));
32267
32384
  console.log("");
32385
+ updatePrompt();
32268
32386
  rl2.prompt();
32269
32387
  rl2.on("line", async (line) => {
32270
32388
  const input = line.trim();
@@ -32273,30 +32391,47 @@ async function tuiCommand(globalOpts, cmdOpts) {
32273
32391
  return;
32274
32392
  }
32275
32393
  if (input === "/quit" || input === "/exit") {
32276
- console.log(pc2.dim("\n Goodbye.\n"));
32394
+ console.log(theme.dim("\n Goodbye.\n"));
32277
32395
  rl2.close();
32278
32396
  process.exit(0);
32279
32397
  }
32398
+ if (input === "/help") {
32399
+ console.log("");
32400
+ console.log(theme.text(pc3.bold(" Available Commands:")));
32401
+ console.log(theme.dim(" /quit, /exit \u2014 Exit the TUI"));
32402
+ console.log(theme.dim(" /new \u2014 Start a new session"));
32403
+ console.log(theme.dim(" /status \u2014 Show daemon status"));
32404
+ console.log(theme.dim(" /stop \u2014 Cancel running task"));
32405
+ console.log(theme.dim(" /backend <name> \u2014 Switch backend (Claude/Gemini/Codex/Cursor)"));
32406
+ console.log(theme.dim(" /model <name> \u2014 Switch model"));
32407
+ console.log(theme.dim(" /theme <mode> \u2014 Switch theme (dark/light/auto)"));
32408
+ console.log("");
32409
+ console.log(theme.dim(" Other slash commands (forwarded to daemon):"));
32410
+ console.log(theme.dim(" /cost, /usage, /memory, /permissions, /thinking, /cwd,"));
32411
+ console.log(theme.dim(" /debug, /evolve, /cron, /agents, and more..."));
32412
+ console.log("");
32413
+ rl2.prompt();
32414
+ return;
32415
+ }
32280
32416
  if (input === "/new") {
32281
32417
  try {
32282
32418
  await apiPost2("/api/session/new", { chatId });
32283
- console.log(pc2.green(" Session cleared.\n"));
32419
+ console.log(theme.success(" \u2713 Session cleared.\n"));
32284
32420
  } catch {
32285
- console.log(pc2.red(" Failed to clear session.\n"));
32421
+ console.log(theme.error(" \u2717 Failed to clear session.\n"));
32286
32422
  }
32287
32423
  rl2.prompt();
32288
32424
  return;
32289
32425
  }
32290
32426
  if (input === "/status") {
32291
32427
  try {
32292
- const { apiGet: apiGet2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
32293
32428
  const res = await apiGet2("/api/health");
32294
32429
  if (res.ok) {
32295
- console.log(pc2.green(` Daemon: running (uptime ${Math.floor(res.data.uptime / 60)}m)
32430
+ console.log(theme.success(` \u2713 Daemon: running (uptime ${Math.floor(res.data.uptime / 60)}m)
32296
32431
  `));
32297
32432
  }
32298
32433
  } catch {
32299
- console.log(pc2.red(" Could not reach daemon.\n"));
32434
+ console.log(theme.error(" \u2717 Could not reach daemon.\n"));
32300
32435
  }
32301
32436
  rl2.prompt();
32302
32437
  return;
@@ -32304,21 +32439,26 @@ async function tuiCommand(globalOpts, cmdOpts) {
32304
32439
  if (input === "/stop") {
32305
32440
  try {
32306
32441
  await apiPost2("/api/chat/stop", { chatId });
32307
- console.log(pc2.yellow(" Stopping current task...\n"));
32442
+ console.log(theme.warn(" \u23F9 Stopping current task...\n"));
32308
32443
  } catch {
32309
- console.log(pc2.dim(" Nothing running.\n"));
32444
+ console.log(theme.dim(" Nothing running.\n"));
32310
32445
  }
32311
32446
  rl2.prompt();
32312
32447
  return;
32313
32448
  }
32314
32449
  if (input.startsWith("/backend ")) {
32315
32450
  const name = input.slice(9).trim();
32451
+ if (!name) {
32452
+ console.log(theme.error(" Usage: /backend <name>\n"));
32453
+ rl2.prompt();
32454
+ return;
32455
+ }
32316
32456
  try {
32317
32457
  await apiPost2("/api/backend/set", { chatId, backend: name });
32318
- console.log(pc2.green(` Backend switched to ${name}.
32458
+ console.log(theme.success(` \u2713 Backend switched to ${name}.
32319
32459
  `));
32320
32460
  } catch {
32321
- console.log(pc2.red(` Failed to switch to ${name}.
32461
+ console.log(theme.error(` \u2717 Failed to switch to ${name}.
32322
32462
  `));
32323
32463
  }
32324
32464
  rl2.prompt();
@@ -32326,53 +32466,124 @@ async function tuiCommand(globalOpts, cmdOpts) {
32326
32466
  }
32327
32467
  if (input.startsWith("/model ")) {
32328
32468
  const name = input.slice(7).trim();
32469
+ if (!name) {
32470
+ console.log(theme.error(" Usage: /model <name>\n"));
32471
+ rl2.prompt();
32472
+ return;
32473
+ }
32329
32474
  try {
32330
32475
  await apiPost2("/api/model/set", { chatId, model: name });
32331
- console.log(pc2.green(` Model switched to ${name}.
32476
+ console.log(theme.success(` \u2713 Model switched to ${name}.
32332
32477
  `));
32333
32478
  } catch {
32334
- console.log(pc2.red(` Failed to switch to ${name}.
32479
+ console.log(theme.error(` \u2717 Failed to switch to ${name}.
32335
32480
  `));
32336
32481
  }
32337
32482
  rl2.prompt();
32338
32483
  return;
32339
32484
  }
32340
- console.log(pc2.dim(" Thinking...\n"));
32341
- try {
32342
- const { apiPost: post } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
32343
- const response = await post("/api/chat", {
32344
- chatId,
32345
- message: input,
32346
- stream: false,
32347
- model: cmdOpts.model,
32348
- cwd: cmdOpts.cwd
32349
- });
32350
- if (response.ok) {
32351
- const data = response.data;
32352
- console.log(pc2.bold("cc-claw > ") + (data.text ?? "No response."));
32353
- if (data.usage) {
32354
- console.log(pc2.dim(` [${data.usage.input?.toLocaleString()} in / ${data.usage.output?.toLocaleString()} out tokens]`));
32355
- }
32356
- console.log("");
32357
- } else {
32358
- console.log(pc2.red(` Error: ${JSON.stringify(response.data)}
32485
+ if (input.startsWith("/theme ")) {
32486
+ const name = input.slice(7).trim();
32487
+ if (["dark", "light", "auto"].includes(name)) {
32488
+ setTheme(name);
32489
+ theme = getTheme();
32490
+ updatePrompt();
32491
+ console.log(theme.success(` \u2713 Theme switched to ${name}.
32359
32492
  `));
32493
+ } else {
32494
+ console.log(theme.error(" Invalid theme. Use dark, light, or auto.\n"));
32360
32495
  }
32361
- } catch (err) {
32362
- console.log(pc2.red(` Error: ${err.message}
32496
+ rl2.prompt();
32497
+ return;
32498
+ }
32499
+ const cmdMatch = input.match(/^\/(\S+)/);
32500
+ if (cmdMatch && !LOCAL_COMMANDS.has("/" + cmdMatch[1])) {
32501
+ console.log(theme.dim(` \u24D8 /${cmdMatch[1]} \u2014 forwarding to AI (interactive menus not yet supported in TUI)
32363
32502
  `));
32503
+ await sendToDaemon(rl2, theme, chatId, input, cmdOpts, apiPost2);
32504
+ return;
32364
32505
  }
32365
- rl2.prompt();
32506
+ await sendToDaemon(rl2, theme, chatId, input, cmdOpts, apiPost2);
32366
32507
  });
32367
32508
  rl2.on("close", () => process.exit(0));
32368
32509
  await new Promise(() => {
32369
32510
  });
32370
32511
  }
32512
+ async function sendToDaemon(rl2, theme, chatId, input, cmdOpts, apiPost2) {
32513
+ console.log(theme.dim(" Thinking...\n"));
32514
+ rl2.pause();
32515
+ let cancelled = false;
32516
+ const sigintHandler = () => {
32517
+ cancelled = true;
32518
+ apiPost2("/api/chat/stop", { chatId }).catch(() => {
32519
+ });
32520
+ console.log(theme.warn("\n \u23F9 Cancelled.\n"));
32521
+ rl2.resume();
32522
+ rl2.prompt();
32523
+ };
32524
+ const existingListeners = process.listeners("SIGINT");
32525
+ process.removeAllListeners("SIGINT");
32526
+ process.once("SIGINT", sigintHandler);
32527
+ try {
32528
+ const response = await apiPost2("/api/chat", {
32529
+ chatId,
32530
+ message: input,
32531
+ stream: false,
32532
+ model: cmdOpts.model,
32533
+ cwd: cmdOpts.cwd
32534
+ }, { timeout: 6e5 });
32535
+ if (cancelled) return;
32536
+ if (response.ok) {
32537
+ const data = response.data;
32538
+ const textToFormat = data.text ?? "No response.";
32539
+ const markdownOutput = marked2.parse(textToFormat);
32540
+ console.log(theme.text(pc3.bold("cc-claw > ")));
32541
+ console.log(markdownOutput.trimEnd());
32542
+ if (data.usage) {
32543
+ console.log(theme.dim(`
32544
+ [${data.usage.input?.toLocaleString()} in / ${data.usage.output?.toLocaleString()} out tokens]`));
32545
+ }
32546
+ console.log("");
32547
+ } else {
32548
+ console.log(theme.error(` \u2717 Error: ${JSON.stringify(response.data)}
32549
+ `));
32550
+ }
32551
+ } catch (err) {
32552
+ if (cancelled) return;
32553
+ const msg = err.message;
32554
+ if (msg.includes("timed out")) {
32555
+ console.log(theme.error(" \u2717 Request timed out (10 minutes). The agent may still be running.\n"));
32556
+ } else {
32557
+ console.log(theme.error(` \u2717 Error: ${msg}
32558
+ `));
32559
+ }
32560
+ } finally {
32561
+ process.removeListener("SIGINT", sigintHandler);
32562
+ for (const listener of existingListeners) {
32563
+ process.on("SIGINT", listener);
32564
+ }
32565
+ if (!cancelled) {
32566
+ rl2.resume();
32567
+ rl2.prompt();
32568
+ }
32569
+ }
32570
+ }
32571
+ var LOCAL_COMMANDS;
32371
32572
  var init_tui = __esm({
32372
32573
  "src/cli/commands/tui.ts"() {
32373
32574
  "use strict";
32374
32575
  init_format2();
32375
32576
  init_resolve_chat();
32577
+ init_theme();
32578
+ init_tui_completer();
32579
+ marked2.setOptions({
32580
+ renderer: new TerminalRenderer({
32581
+ showSectionPrefix: false,
32582
+ reflowText: true,
32583
+ width: Math.min(process.stdout.columns || 100, 120)
32584
+ })
32585
+ });
32586
+ LOCAL_COMMANDS = /* @__PURE__ */ new Set(["/quit", "/exit", "/new", "/status", "/stop", "/backend", "/model", "/theme", "/help"]);
32376
32587
  }
32377
32588
  });
32378
32589
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.20.5",
3
+ "version": "0.20.6",
4
4
  "description": "CC-Claw: Personal AI assistant on Telegram — multi-backend (Claude, Gemini, Codex, Cursor), sub-agent orchestration, MCP management",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -45,12 +45,14 @@
45
45
  "dotenv": "^16.4.7",
46
46
  "grammy": "^1.35.0",
47
47
  "marked": "^9.1.6",
48
+ "marked-terminal": "^7.3.0",
48
49
  "picocolors": "^1.1.1",
49
50
  "zod": "^4.3.6"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@types/better-sqlite3": "^7.6.13",
53
54
  "@types/bun": "^1.3.10",
55
+ "@types/marked-terminal": "^6.1.1",
54
56
  "@types/node": "^25.3.5",
55
57
  "@vitest/coverage-v8": "^4.0.18",
56
58
  "tsup": "^8.5.1",