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 +2 -1
- package/README.md +5 -1
- package/bot.mjs +53 -1
- package/package.json +1 -1
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(
|
|
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.
|
|
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": {
|