claude-telegram-bot 0.2.2 → 0.2.4
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 -2
- package/README.md +5 -3
- package/bot.mjs +79 -13
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -100,7 +100,7 @@ claude-telegram-bot ~/botconfigs/myproj/config.json
|
|
|
100
100
|
| `env` | (선택) `claude` 프로세스에 넘길 환경 변수 |
|
|
101
101
|
| `schedule` | (선택) 정해진 시각에 프롬프트를 실행하는 cron 작업 — [예약 작업](#예약-작업-cron) 참고 |
|
|
102
102
|
|
|
103
|
-
`state
|
|
103
|
+
`state`와 첨부 파일은 config 파일 옆 **`.claude-bot/` 숨김 폴더**에 저장됩니다(프로젝트 격리). 구버전에서 올리면 첫 시작 때 기존 `state.json`·`attachments/`를 `.claude-bot/`로 **자동 이동**합니다(무손실). 로그는 launchd plist가 가리키는 위치 그대로입니다.
|
|
104
104
|
|
|
105
105
|
## 첫 실행
|
|
106
106
|
|
|
@@ -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
|
|
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
|
|
@@ -192,8 +192,10 @@ Edit `config.json`:
|
|
|
192
192
|
| `env` | (optional) Extra environment variables passed to the `claude` process |
|
|
193
193
|
| `schedule` | (optional) Cron jobs that run a prompt on a timer — see [Scheduled tasks](#scheduled-tasks-cron) |
|
|
194
194
|
|
|
195
|
-
State
|
|
196
|
-
projects stay isolated.
|
|
195
|
+
State and downloaded attachments live in a hidden **`.claude-bot/`** folder next to the config
|
|
196
|
+
file, so projects stay isolated. Upgrading from an older version **auto-moves** an existing
|
|
197
|
+
`state.json` / `attachments/` into `.claude-bot/` on first start (no data loss). Logs stay wherever
|
|
198
|
+
your launchd plist points them.
|
|
197
199
|
|
|
198
200
|
### Usage details
|
|
199
201
|
|
package/bot.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// 자동 판별하고, cfg.lang 을 주면 그 언어로 고정함. 콘솔/CLI 출력은 영어 단일.
|
|
15
15
|
|
|
16
16
|
import { basename, dirname, join } from "node:path";
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
18
18
|
|
|
19
19
|
import dns from "node:dns";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
@@ -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") {
|
|
@@ -65,13 +73,33 @@ Requires: the claude CLI installed and authenticated on the host.`);
|
|
|
65
73
|
// isolated. Falls back to ./config.json for the single-project setup.
|
|
66
74
|
const CONFIG_PATH = process.argv[2] || process.env.BOT_CONFIG || join(HERE, "config.json");
|
|
67
75
|
const DATA_DIR = dirname(CONFIG_PATH);
|
|
68
|
-
// state
|
|
69
|
-
// config
|
|
76
|
+
// 데이터(state·attachments)는 config 폴더 아래 숨김 폴더 .claude-bot/ 에 모은다.
|
|
77
|
+
// state 파일명은 config 이름에서 파생 → 여러 페르소나 config 가 한 .claude-bot/ 를 공유해도 안 섞임
|
|
78
|
+
// (config.json → state.json, 그 외 foo.json → foo.state.json).
|
|
70
79
|
const stateBase = basename(CONFIG_PATH, ".json");
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
80
|
+
const stateFile = stateBase === "config" ? "state.json" : `${stateBase}.state.json`;
|
|
81
|
+
const BOT_DIR = join(DATA_DIR, ".claude-bot");
|
|
82
|
+
const STATE_PATH = join(BOT_DIR, stateFile);
|
|
83
|
+
const ATTACH_DIR = join(BOT_DIR, "attachments");
|
|
84
|
+
const LEGACY_STATE_PATH = join(DATA_DIR, stateFile); // 구버전(루트 직하) 호환
|
|
85
|
+
const LEGACY_ATTACH_DIR = join(DATA_DIR, "attachments");
|
|
86
|
+
|
|
87
|
+
// 구버전에서 올라온 경우, 루트 직하 데이터를 .claude-bot/ 로 1회 이동(무손실, 실패 시 기존 경로 폴백).
|
|
88
|
+
function migrateData() {
|
|
89
|
+
try {
|
|
90
|
+
mkdirSync(BOT_DIR, { recursive: true });
|
|
91
|
+
if (!existsSync(STATE_PATH) && existsSync(LEGACY_STATE_PATH)) {
|
|
92
|
+
renameSync(LEGACY_STATE_PATH, STATE_PATH);
|
|
93
|
+
console.log(`Migrated state → ${STATE_PATH}`);
|
|
94
|
+
}
|
|
95
|
+
if (!existsSync(ATTACH_DIR) && existsSync(LEGACY_ATTACH_DIR)) {
|
|
96
|
+
renameSync(LEGACY_ATTACH_DIR, ATTACH_DIR);
|
|
97
|
+
console.log(`Migrated attachments → ${ATTACH_DIR}`);
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.error("Data migration skipped:", e.message);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
75
103
|
|
|
76
104
|
if (!existsSync(CONFIG_PATH)) {
|
|
77
105
|
console.error(
|
|
@@ -100,6 +128,7 @@ const STR = {
|
|
|
100
128
|
"• /new — reset conversation context (new session)\n" +
|
|
101
129
|
"• /cron — list tasks · /cron add <natural language> to add · /cron rm <id> to remove\n" +
|
|
102
130
|
"• /restart — restart the bot (after a syntax check)\n" +
|
|
131
|
+
"• /status — bot status & version\n" +
|
|
103
132
|
"• /id — show this chat ID\n" +
|
|
104
133
|
`\nWorking dir: ${cfg.projectDir}\nPermission mode: ${cfg.permissionMode}`,
|
|
105
134
|
newSession: "🆕 Started a new conversation (previous context cleared).",
|
|
@@ -131,6 +160,14 @@ const STR = {
|
|
|
131
160
|
extractNoUnderstand: "Couldn't understand the schedule. Try rephrasing.",
|
|
132
161
|
extractBadCron: (cron) => `Couldn't parse cron: ${cron}`,
|
|
133
162
|
extractNoPrompt: "Couldn't find what to run.",
|
|
163
|
+
status: (i) =>
|
|
164
|
+
`🤖 ${i.name}\n` +
|
|
165
|
+
`• Version: ${i.version}\n` +
|
|
166
|
+
`• Model: ${i.model}\n` +
|
|
167
|
+
`• Session: ${i.hasSession ? "active" : "none (fresh)"}\n` +
|
|
168
|
+
`• Scheduled jobs: ${i.jobs}\n` +
|
|
169
|
+
`• Project: ${i.projectDir}\n` +
|
|
170
|
+
`• Permission: ${i.permissionMode}`,
|
|
134
171
|
},
|
|
135
172
|
ko: {
|
|
136
173
|
help: () =>
|
|
@@ -139,6 +176,7 @@ const STR = {
|
|
|
139
176
|
"• /new — 대화 맥락 초기화 (새 세션)\n" +
|
|
140
177
|
"• /cron — 예약 작업 보기 · /cron add <자연어>로 추가 · /cron rm <번호>로 삭제\n" +
|
|
141
178
|
"• /restart — 봇 재시작 (문법 검사 후 안전하게)\n" +
|
|
179
|
+
"• /status — 봇 상태·버전 보기\n" +
|
|
142
180
|
"• /id — 이 채팅 ID 확인\n" +
|
|
143
181
|
`\n작업 폴더: ${cfg.projectDir}\n권한 모드: ${cfg.permissionMode}`,
|
|
144
182
|
newSession: "🆕 새 대화를 시작합니다 (이전 맥락 초기화).",
|
|
@@ -169,6 +207,14 @@ const STR = {
|
|
|
169
207
|
extractNoUnderstand: "일정을 이해하지 못했어요. 다르게 표현해 보세요.",
|
|
170
208
|
extractBadCron: (cron) => `cron 해석 실패: ${cron}`,
|
|
171
209
|
extractNoPrompt: "무엇을 실행할지 찾지 못했어요.",
|
|
210
|
+
status: (i) =>
|
|
211
|
+
`🤖 ${i.name}\n` +
|
|
212
|
+
`• 버전: ${i.version}\n` +
|
|
213
|
+
`• 모델: ${i.model}\n` +
|
|
214
|
+
`• 세션: ${i.hasSession ? "이어가는 중" : "없음 (새 세션)"}\n` +
|
|
215
|
+
`• 예약 작업: ${i.jobs}개\n` +
|
|
216
|
+
`• 작업 폴더: ${i.projectDir}\n` +
|
|
217
|
+
`• 권한 모드: ${i.permissionMode}`,
|
|
172
218
|
},
|
|
173
219
|
};
|
|
174
220
|
const t = (l, key, ...a) => {
|
|
@@ -182,6 +228,7 @@ const COMMANDS = {
|
|
|
182
228
|
{ command: "new", description: "Reset context (new session)" },
|
|
183
229
|
{ command: "cron", description: "List / add / remove scheduled tasks" },
|
|
184
230
|
{ command: "restart", description: "Restart the bot (after syntax check)" },
|
|
231
|
+
{ command: "status", description: "Bot status / version" },
|
|
185
232
|
{ command: "id", description: "Show this chat ID" },
|
|
186
233
|
{ command: "help", description: "Help" },
|
|
187
234
|
],
|
|
@@ -189,6 +236,7 @@ const COMMANDS = {
|
|
|
189
236
|
{ command: "new", description: "대화 맥락 초기화 (새 세션)" },
|
|
190
237
|
{ command: "cron", description: "예약 작업 보기·추가·삭제" },
|
|
191
238
|
{ command: "restart", description: "봇 재시작 (문법 검사 후)" },
|
|
239
|
+
{ command: "status", description: "봇 상태·버전 보기" },
|
|
192
240
|
{ command: "id", description: "이 채팅 ID 확인" },
|
|
193
241
|
{ command: "help", description: "도움말" },
|
|
194
242
|
],
|
|
@@ -196,11 +244,13 @@ const COMMANDS = {
|
|
|
196
244
|
|
|
197
245
|
// ── 상태 (세션 이어가기용) ────────────────────────────────────────────────
|
|
198
246
|
function loadState() {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
247
|
+
// 새 경로(.claude-bot/) 우선, 없으면 구버전 루트 경로로 폴백(이주 실패 시 안전망).
|
|
248
|
+
for (const p of [STATE_PATH, LEGACY_STATE_PATH]) {
|
|
249
|
+
try {
|
|
250
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
251
|
+
} catch {}
|
|
203
252
|
}
|
|
253
|
+
return {};
|
|
204
254
|
}
|
|
205
255
|
function saveState(s) {
|
|
206
256
|
try {
|
|
@@ -209,6 +259,7 @@ function saveState(s) {
|
|
|
209
259
|
console.error("Failed to save state", e);
|
|
210
260
|
}
|
|
211
261
|
}
|
|
262
|
+
migrateData(); // 루트 직하 → .claude-bot/ 1회 이주(있으면) 후 state 로드
|
|
212
263
|
let state = loadState(); // { sessionId?, cron?: [{ id, cron, prompt, label? }], restartNotify? }
|
|
213
264
|
|
|
214
265
|
// ── 텔레그램 헬퍼 ─────────────────────────────────────────────────────────
|
|
@@ -588,7 +639,7 @@ async function downloadAttachment(att) {
|
|
|
588
639
|
const r = await fetch(`https://api.telegram.org/file/bot${cfg.token}/${filePath}`);
|
|
589
640
|
if (!r.ok) throw new Error(`download failed ${r.status}`);
|
|
590
641
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
591
|
-
const dir =
|
|
642
|
+
const dir = ATTACH_DIR;
|
|
592
643
|
mkdirSync(dir, { recursive: true });
|
|
593
644
|
const ext = filePath.includes(".") ? filePath.slice(filePath.lastIndexOf(".")) : "";
|
|
594
645
|
const name = att.name || `tg-${att.fileId.slice(-10)}${ext}`;
|
|
@@ -627,6 +678,21 @@ async function handle(msg) {
|
|
|
627
678
|
await send(chatId, `chatId: ${chatId}`);
|
|
628
679
|
return;
|
|
629
680
|
}
|
|
681
|
+
if (text === "/status") {
|
|
682
|
+
await send(
|
|
683
|
+
chatId,
|
|
684
|
+
t(l, "status", {
|
|
685
|
+
version: VERSION,
|
|
686
|
+
name: cfg.name || "claude-telegram-bot",
|
|
687
|
+
model: cfg.model || (l === "ko" ? "(기본값)" : "(default)"),
|
|
688
|
+
hasSession: Boolean(state.sessionId),
|
|
689
|
+
jobs: schedule.length,
|
|
690
|
+
projectDir: cfg.projectDir,
|
|
691
|
+
permissionMode: cfg.permissionMode || "acceptEdits",
|
|
692
|
+
}),
|
|
693
|
+
);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
630
696
|
if (text === "/cron" || text.startsWith("/cron ")) {
|
|
631
697
|
await handleCron(chatId, text.slice(5).trim(), l);
|
|
632
698
|
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.4",
|
|
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": {
|