claude-telegram-bot 0.2.1 → 0.2.3

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.
package/README.ko.md CHANGED
@@ -110,7 +110,7 @@ claude-telegram-bot ~/botconfigs/myproj/config.json
110
110
  - `테스트 돌려보고 통과하면 커밋하고 push 해줘`
111
111
  - `api.ts 에 에러 핸들링 추가해줘`
112
112
 
113
- 명령어: `/new`(맥락 초기화) · `/cron`(예약 작업 보기·추가·삭제) · `/restart`(문법 검사 후 재시작) · `/id`(채팅 ID 확인) · `/help`(도움말)
113
+ 명령어: `/new`(맥락 초기화) · `/cron`(예약 작업 보기·추가·삭제) · `/restart`(문법 검사 후 재시작) · `/status`(봇 상태·버전) · `/id`(채팅 ID 확인) · `/help`(도움말)
114
114
 
115
115
  > **`/restart`** 는 먼저 `bot.mjs` 에 `node --check` 를 돌려 **문법 오류가 있으면 재시작을 취소**합니다(잘못된 수정이 봇을 크래시 루프에 빠뜨리는 것 방지). 통과하면 프로세스를 종료하고, 다시 띄우는 건 프로세스 관리자에게 맡깁니다. [launchd 설정](#상시-실행-launchd)(`KeepAlive`)이면 바로 동작하고, 관리자 없이 `node bot.mjs` 로만 돌리면 그냥 멈춥니다. 재시작 후 대화 세션은 `state.json` 의 ID로 이어집니다.
116
116
 
@@ -136,6 +136,7 @@ config에 `schedule` 배열을 두면 정해진 시각에 프롬프트를 자동
136
136
  - **`cron`** — 표준 5필드 `분 시 일 월 요일` (예: `0 9 * * 1-5` = 평일 09:00). `*`, 목록(`1,3,5`), 범위(`1-5`), 스텝(`*/15`)을 지원합니다. 요일 `0`과 `7`은 둘 다 일요일. 시각은 **호스트의 로컬 시간대** 기준입니다. 외부 의존성 없이 파서가 `bot.mjs` 안에 들어 있습니다.
137
137
  - **`prompt`**(필수) — Claude에게 보낼 메시지. **`label`**(선택) — 답장 푸터와 `/cron` 목록에 표시되는 짧은 이름.
138
138
  - **새 세션** — 예약 작업은 **독립된 세션**으로 돌아가서 내 대화 맥락을 오염시키지 않습니다(`state.json`은 내 것 그대로). 단일 작업 락을 공유하므로, 발사 시점에 다른 작업이 진행 중이면 그 회차는 **건너뜁니다**(로그 남김).
139
+ - **조용한 작업(조건부 알림)** — Claude의 출력이 **비었거나 정확히 `SKIP`**이면 그 회차는 텔레그램으로 **아무것도 보내지 않습니다**. "조건이 맞을 때만 알리고 평소엔 조용히" 하고 싶을 때, 프롬프트에 *"조건이 아니면 다른 말 없이 `SKIP`만 출력해"* 라고 적으면 됩니다. 자주 도는 작업(예: 5분마다)도 스팸 없이 쓸 수 있습니다.
139
140
 
140
141
  **채팅에서 자연어로 추가하기**
141
142
 
package/README.md CHANGED
@@ -154,7 +154,7 @@ auth layer.)
154
154
  - `run the solver tests and commit + push if they pass`
155
155
  - `add an edge case to solve-2nd-floor-edges.ts`
156
156
 
157
- Commands: `/new` (reset context / new session) · `/cron` (list / add / remove scheduled tasks) · `/restart` (syntax-check & restart the bot) · `/id` (show chat ID) · `/help`.
157
+ Commands: `/new` (reset context / new session) · `/cron` (list / add / remove scheduled tasks) · `/restart` (syntax-check & restart the bot) · `/status` (bot status & version) · `/id` (show chat ID) · `/help`.
158
158
 
159
159
  > **`/restart`** runs `node --check` on `bot.mjs` first and **aborts the restart if it has a syntax
160
160
  > error** (so a bad edit can't crash-loop the bot), then exits — relying on a process supervisor
@@ -231,6 +231,10 @@ checks, reminders. Each entry runs the prompt and sends the result to `allowedCh
231
231
  - **Fresh session**: scheduled jobs run in their **own session** so they never pollute your
232
232
  interactive conversation context (`state.json` stays yours). They share the single-task lock,
233
233
  so a job is **skipped** (logged) if a task is already running when it fires.
234
+ - **Silent jobs (conditional alerts)**: if Claude's output is **empty or exactly `SKIP`**, that run
235
+ sends **nothing** to Telegram. To get "alert only when it matters, stay quiet otherwise," tell the
236
+ prompt to *output just `SKIP` when the condition isn't met*. This lets even frequent jobs (e.g.
237
+ every 5 minutes) run without spamming the chat.
234
238
 
235
239
  **Add jobs from the chat — in plain language:**
236
240
 
package/bot.mjs CHANGED
@@ -28,6 +28,14 @@ if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
28
28
 
29
29
  const SELF = fileURLToPath(import.meta.url); // /restart 자기 문법검사용
30
30
  const HERE = dirname(SELF);
31
+ // package.json 버전을 1회만 읽어 캐시 (--version, /status 에서 사용).
32
+ const VERSION = (() => {
33
+ try {
34
+ return JSON.parse(readFileSync(join(HERE, "package.json"), "utf8")).version;
35
+ } catch {
36
+ return "?";
37
+ }
38
+ })();
31
39
 
32
40
  // ── CLI (help / version / init) ───────────────────────────────────────────
33
41
  {
@@ -45,7 +53,7 @@ Requires: the claude CLI installed and authenticated on the host.`);
45
53
  process.exit(0);
46
54
  }
47
55
  if (a === "-v" || a === "--version") {
48
- console.log(JSON.parse(readFileSync(join(HERE, "package.json"), "utf8")).version);
56
+ console.log(VERSION);
49
57
  process.exit(0);
50
58
  }
51
59
  if (a === "init") {
@@ -100,6 +108,7 @@ const STR = {
100
108
  "• /new — reset conversation context (new session)\n" +
101
109
  "• /cron — list tasks · /cron add <natural language> to add · /cron rm <id> to remove\n" +
102
110
  "• /restart — restart the bot (after a syntax check)\n" +
111
+ "• /status — bot status & version\n" +
103
112
  "• /id — show this chat ID\n" +
104
113
  `\nWorking dir: ${cfg.projectDir}\nPermission mode: ${cfg.permissionMode}`,
105
114
  newSession: "🆕 Started a new conversation (previous context cleared).",
@@ -131,6 +140,14 @@ const STR = {
131
140
  extractNoUnderstand: "Couldn't understand the schedule. Try rephrasing.",
132
141
  extractBadCron: (cron) => `Couldn't parse cron: ${cron}`,
133
142
  extractNoPrompt: "Couldn't find what to run.",
143
+ status: (i) =>
144
+ `🤖 ${i.name}\n` +
145
+ `• Version: ${i.version}\n` +
146
+ `• Model: ${i.model}\n` +
147
+ `• Session: ${i.hasSession ? "active" : "none (fresh)"}\n` +
148
+ `• Scheduled jobs: ${i.jobs}\n` +
149
+ `• Project: ${i.projectDir}\n` +
150
+ `• Permission: ${i.permissionMode}`,
134
151
  },
135
152
  ko: {
136
153
  help: () =>
@@ -139,6 +156,7 @@ const STR = {
139
156
  "• /new — 대화 맥락 초기화 (새 세션)\n" +
140
157
  "• /cron — 예약 작업 보기 · /cron add <자연어>로 추가 · /cron rm <번호>로 삭제\n" +
141
158
  "• /restart — 봇 재시작 (문법 검사 후 안전하게)\n" +
159
+ "• /status — 봇 상태·버전 보기\n" +
142
160
  "• /id — 이 채팅 ID 확인\n" +
143
161
  `\n작업 폴더: ${cfg.projectDir}\n권한 모드: ${cfg.permissionMode}`,
144
162
  newSession: "🆕 새 대화를 시작합니다 (이전 맥락 초기화).",
@@ -169,6 +187,14 @@ const STR = {
169
187
  extractNoUnderstand: "일정을 이해하지 못했어요. 다르게 표현해 보세요.",
170
188
  extractBadCron: (cron) => `cron 해석 실패: ${cron}`,
171
189
  extractNoPrompt: "무엇을 실행할지 찾지 못했어요.",
190
+ status: (i) =>
191
+ `🤖 ${i.name}\n` +
192
+ `• 버전: ${i.version}\n` +
193
+ `• 모델: ${i.model}\n` +
194
+ `• 세션: ${i.hasSession ? "이어가는 중" : "없음 (새 세션)"}\n` +
195
+ `• 예약 작업: ${i.jobs}개\n` +
196
+ `• 작업 폴더: ${i.projectDir}\n` +
197
+ `• 권한 모드: ${i.permissionMode}`,
172
198
  },
173
199
  };
174
200
  const t = (l, key, ...a) => {
@@ -182,6 +208,7 @@ const COMMANDS = {
182
208
  { command: "new", description: "Reset context (new session)" },
183
209
  { command: "cron", description: "List / add / remove scheduled tasks" },
184
210
  { command: "restart", description: "Restart the bot (after syntax check)" },
211
+ { command: "status", description: "Bot status / version" },
185
212
  { command: "id", description: "Show this chat ID" },
186
213
  { command: "help", description: "Help" },
187
214
  ],
@@ -189,6 +216,7 @@ const COMMANDS = {
189
216
  { command: "new", description: "대화 맥락 초기화 (새 세션)" },
190
217
  { command: "cron", description: "예약 작업 보기·추가·삭제" },
191
218
  { command: "restart", description: "봇 재시작 (문법 검사 후)" },
219
+ { command: "status", description: "봇 상태·버전 보기" },
192
220
  { command: "id", description: "이 채팅 ID 확인" },
193
221
  { command: "help", description: "도움말" },
194
222
  ],
@@ -435,6 +463,15 @@ async function runScheduled(job) {
435
463
  const started = Date.now();
436
464
  try {
437
465
  const res = await runClaude(job.prompt, undefined); // 새 세션 (state 미저장)
466
+ // 조용한 예약 작업: 출력이 비었거나 정확히 "SKIP"이면 전송하지 않는다.
467
+ // (예: "조건 충족 시에만 알리고, 아니면 SKIP만 출력해" 식의 조건부 알림용)
468
+ if (res.ok) {
469
+ const body = (res.text || "").trim();
470
+ if (!body || /^skip$/i.test(body)) {
471
+ console.log(`Scheduled job suppressed (empty/SKIP): ${job.label || job.cron}`);
472
+ return;
473
+ }
474
+ }
438
475
  const secs = Math.round((Date.now() - started) / 1000);
439
476
  const label = job.label || job.cron;
440
477
  const footer = res.ok
@@ -618,6 +655,21 @@ async function handle(msg) {
618
655
  await send(chatId, `chatId: ${chatId}`);
619
656
  return;
620
657
  }
658
+ if (text === "/status") {
659
+ await send(
660
+ chatId,
661
+ t(l, "status", {
662
+ version: VERSION,
663
+ name: cfg.name || "claude-telegram-bot",
664
+ model: cfg.model || (l === "ko" ? "(기본값)" : "(default)"),
665
+ hasSession: Boolean(state.sessionId),
666
+ jobs: schedule.length,
667
+ projectDir: cfg.projectDir,
668
+ permissionMode: cfg.permissionMode || "acceptEdits",
669
+ }),
670
+ );
671
+ return;
672
+ }
621
673
  if (text === "/cron" || text.startsWith("/cron ")) {
622
674
  await handleCron(chatId, text.slice(5).trim(), l);
623
675
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-telegram-bot",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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": {