claude-telegram-bot 0.3.31 β†’ 0.3.34

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/bot.mjs +62 -10
  2. package/package.json +1 -1
package/bot.mjs CHANGED
@@ -150,6 +150,7 @@ const STR = {
150
150
  compactOk: "πŸ—œοΈ Context compacted. The conversation continues with a summary.",
151
151
  compactFail: (m) => `⚠️ Compact failed: ${m}`,
152
152
  compactNoSession: "No active session to compact. Just send a message to start one.",
153
+ autoCompact: "πŸ—œοΈ Auto-compacted context (conversation was getting long).",
153
154
  testFallbackDisabled: "⚠️ Ollama fallback is not enabled. Set `\"ollamaFallback\": true` in config.json.",
154
155
  testFallbackFail: (m) => `⚠️ Ollama test failed: ${m}`,
155
156
  ollamaOn: "πŸŒ™ Ollama mode on. Messages will now go to Ollama. Your Claude session is preserved.",
@@ -292,6 +293,7 @@ const STR = {
292
293
  compactOk: "πŸ—œοΈ μ»¨ν…μŠ€νŠΈλ₯Ό μ••μΆ•ν–ˆμŠ΅λ‹ˆλ‹€. λŒ€ν™”κ°€ μš”μ•½λ³ΈμœΌλ‘œ μ΄μ–΄μ§‘λ‹ˆλ‹€.",
293
294
  compactFail: (m) => `⚠️ compact μ‹€νŒ¨: ${m}`,
294
295
  compactNoSession: "μ••μΆ•ν•  ν™œμ„± μ„Έμ…˜μ΄ μ—†μŠ΅λ‹ˆλ‹€. λ©”μ‹œμ§€λ₯Ό 보내 μ„Έμ…˜μ„ μ‹œμž‘ν•˜μ„Έμš”.",
296
+ autoCompact: "πŸ—œοΈ λŒ€ν™”κ°€ κΈΈμ–΄μ Έ μ»¨ν…μŠ€νŠΈλ₯Ό μžλ™ μ••μΆ•ν–ˆμŠ΅λ‹ˆλ‹€.",
295
297
  contextTooLong: "⚠️ ν”„λ‘¬ν”„νŠΈκ°€ λ„ˆλ¬΄ κΉλ‹ˆλ‹€. `/compact` 둜 μ»¨ν…μŠ€νŠΈλ₯Ό μ••μΆ•ν•˜κ±°λ‚˜ `/new` 둜 μƒˆ μ„Έμ…˜μ„ μ‹œμž‘ν•˜μ„Έμš”.",
296
298
  testFallbackDisabled: "⚠️ Ollama 폴백이 λΉ„ν™œμ„±ν™” μƒνƒœμž…λ‹ˆλ‹€. config.json에 `\"ollamaFallback\": true` λ₯Ό μΆ”κ°€ν•˜μ„Έμš”.",
297
299
  testFallbackFail: (m) => `⚠️ Ollama ν…ŒμŠ€νŠΈ μ‹€νŒ¨: ${m}`,
@@ -570,15 +572,33 @@ function classifyClaudeError(raw, code) {
570
572
  return `Execution error (exit ${code}):\n${raw}`;
571
573
  }
572
574
 
575
+ // ── μ»€μŠ€ν…€ λͺ…λ Ήμ–΄ 슀크립트 μ‹€ν–‰ ──────────────────────────────────────────
576
+ function runCustomCommand(run, args) {
577
+ return new Promise((resolve) => {
578
+ const cmd = args ? `${run} ${args}` : run;
579
+ const child = spawn(cmd, [], {
580
+ shell: true,
581
+ cwd: cfg.projectDir,
582
+ env: { ...process.env, ...(cfg.env || {}) },
583
+ });
584
+ let out = "", err = "";
585
+ child.stdout.on("data", (d) => (out += d));
586
+ child.stderr.on("data", (d) => (err += d));
587
+ child.on("close", (code) => {
588
+ const text = (out + (err ? `\n[stderr]\n${err}` : "")).trim() || "(no output)";
589
+ resolve({ ok: code === 0, text, code });
590
+ });
591
+ child.on("error", (e) => resolve({ ok: false, text: e.message, code: -1 }));
592
+ setTimeout(() => { try { child.kill(); } catch {} resolve({ ok: false, text: "timeout (60s)", code: -1 }); }, 60_000);
593
+ });
594
+ }
595
+
573
596
  // ── Claude μ‹€ν–‰ ───────────────────────────────────────────────────────────
574
597
  function runClaude(prompt, sessionId, opts = {}) {
575
598
  return new Promise((resolve) => {
576
599
  const args = [
577
- "-p",
578
- "--output-format",
579
- "json",
580
- "--permission-mode",
581
- cfg.permissionMode || "acceptEdits",
600
+ "--output-format", "json",
601
+ "--permission-mode", cfg.permissionMode || "acceptEdits",
582
602
  ];
583
603
  const model = state.model || cfg.model; // /model 둜 λ°”κΎΈλ©΄ state.model μš°μ„ 
584
604
  const brevity =
@@ -596,8 +616,9 @@ function runClaude(prompt, sessionId, opts = {}) {
596
616
  if (appendSys) args.push("--append-system-prompt", appendSys);
597
617
  if (model) args.push("--model", model);
598
618
  if (sessionId) args.push("--resume", sessionId);
599
- // ν”„λ‘¬ν”„νŠΈλŠ” λ°˜λ“œμ‹œ 맨 끝에 `--` λ’€λ‘œ β€” `-`둜 μ‹œμž‘ν•΄λ„ μ˜΅μ…˜μœΌλ‘œ μ˜€ν•΄ μ•ˆ 함
600
- args.push("--", prompt);
619
+ // -p와 ν”„λ‘¬ν”„νŠΈλŠ” 맨 끝에 β€” 터미널 ν…ŒμŠ€νŠΈμ—μ„œ κ²€μ¦λœ μˆœμ„œ
620
+ // `--` κ΅¬λΆ„μžλ‘œ `-`둜 μ‹œμž‘ν•˜λŠ” ν”„λ‘¬ν”„νŠΈλ„ μ˜΅μ…˜μœΌλ‘œ μ˜€ν•΄ μ•ˆ 함
621
+ args.push("-p", "--", prompt);
601
622
 
602
623
  const child = spawn(cfg.claudeBin || "claude", args, {
603
624
  cwd: cfg.projectDir,
@@ -625,7 +646,8 @@ function runClaude(prompt, sessionId, opts = {}) {
625
646
  const text = j.is_error ? classifyClaudeError(rawErr, code) : (rawErr || "(empty response)");
626
647
  const resetAt = j.is_error ? parseResetTime(rawErr) : null;
627
648
  const canFallback = j.is_error && isFallbackError(rawErr, code);
628
- resolve({ ok: !j.is_error, text, sessionId: j.session_id, cost: j.total_cost_usd, resetAt, canFallback });
649
+ const cacheTokens = j.usage?.cache_read_input_tokens || 0;
650
+ resolve({ ok: !j.is_error, text, sessionId: j.session_id, cost: j.total_cost_usd, cacheTokens, resetAt, canFallback });
629
651
  } catch {
630
652
  const raw = (err || out || "no output").slice(0, 3500);
631
653
  resolve({ ok: false, text: classifyClaudeError(raw, code), resetAt: parseResetTime(raw), canFallback: isFallbackError(raw, code) });
@@ -1125,6 +1147,22 @@ async function handle(msg) {
1125
1147
  return;
1126
1148
  }
1127
1149
 
1150
+ // μ»€μŠ€ν…€ λͺ…λ Ήμ–΄ (config.commands) β€” Claude와 독립 μ‹€ν–‰
1151
+ if (text.startsWith("/")) {
1152
+ const cmdName = text.slice(1).split(" ")[0];
1153
+ const def = (cfg.commands || {})[cmdName];
1154
+ if (def) {
1155
+ const run = typeof def === "object" ? def.run : null;
1156
+ if (run) {
1157
+ const args = text.length > cmdName.length + 1 ? text.slice(cmdName.length + 2) : "";
1158
+ const res = await runCustomCommand(run, args || undefined);
1159
+ const out = res.text.length > 4000 ? res.text.slice(0, 3990) + "\n…(truncated)" : res.text;
1160
+ await send(chatId, `${res.ok ? "" : "⚠️ "}${out}`);
1161
+ return;
1162
+ }
1163
+ }
1164
+ }
1165
+
1128
1166
  if (busy) {
1129
1167
  msgQueue.push({ msg, receivedAt: Date.now() });
1130
1168
  await send(chatId, t(l, "queued", msgQueue.length));
@@ -1214,6 +1252,15 @@ async function handle(msg) {
1214
1252
  } else {
1215
1253
  const footer = `\n\nβ€” ${secs}s${res.cost ? ` Β· $${res.cost.toFixed(4)}` : ""}`;
1216
1254
  if (!stopping) await send(chatId, res.text + footer);
1255
+ // μžλ™ μ»΄νŒ©μ…˜: cache_read_input_tokens κ°€ μž„κ³„κ°’ 초과 μ‹œ μžλ™ /compact
1256
+ const compactThreshold = cfg.autoCompactThreshold ?? 100000;
1257
+ if (compactThreshold > 0 && res.cacheTokens > compactThreshold && state.sessionId && !stopping) {
1258
+ try {
1259
+ const cr = await runClaude("/compact", state.sessionId);
1260
+ if (cr.sessionId) { state.sessionId = cr.sessionId; saveState(state); }
1261
+ if (cr.ok !== false) await send(chatId, t(l, "autoCompact"));
1262
+ } catch {}
1263
+ }
1217
1264
  }
1218
1265
  } catch (e) {
1219
1266
  if (!stopping) await send(chatId, t(l, "botError", e.message));
@@ -1274,9 +1321,14 @@ async function main() {
1274
1321
  }
1275
1322
  // ν…”λ ˆκ·Έλž¨ λͺ…λ Ήμ–΄ μžλ™μ™„μ„±(/ μž…λ ₯ μ‹œ λœ¨λŠ” 메뉴) 등둝. 직접 νŒŒμ‹±κ³Ό λ³„κ°œλ‘œ ν•œ 번 μ•Œλ €μ€˜μ•Ό 함.
1276
1323
  // κΈ°λ³Έ λͺ©λ‘(BOT_LANG) + ν•œκ΅­μ–΄ λ³€ν˜•(language_code: ko) β†’ ko ν΄λΌμ΄μ–ΈνŠΈλŠ” ν•œκ΅­μ–΄, κ·Έ μ™Έ κΈ°λ³Έ.
1277
- tg("setMyCommands", { commands: COMMANDS[BOT_LANG] || COMMANDS.en }).catch(() => {});
1324
+ // config.commands 에 μ •μ˜λœ μ»€μŠ€ν…€ λͺ…령어도 λͺ©λ‘μ— μΆ”κ°€.
1325
+ const customCmdEntries = Object.entries(cfg.commands || {}).map(([name, def]) => ({
1326
+ command: name,
1327
+ description: (typeof def === "object" ? (def.description || name) : name).slice(0, 256),
1328
+ }));
1329
+ tg("setMyCommands", { commands: [...(COMMANDS[BOT_LANG] || COMMANDS.en), ...customCmdEntries] }).catch(() => {});
1278
1330
  if (!FORCE_LANG) {
1279
- tg("setMyCommands", { commands: COMMANDS.ko, language_code: "ko" }).catch(() => {});
1331
+ tg("setMyCommands", { commands: [...COMMANDS.ko, ...customCmdEntries], language_code: "ko" }).catch(() => {});
1280
1332
  }
1281
1333
 
1282
1334
  // μ‹œμž‘ μ‹œ λ°€λ¦° λ©”μ‹œμ§€ κ±΄λ„ˆλ›°κΈ°
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-telegram-bot",
3
- "version": "0.3.31",
3
+ "version": "0.3.34",
4
4
  "description": "Drive Claude Code from Telegram β€” messages run headless `claude -p` in a project dir and replies come back to the chat. Zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {