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 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.json`과 첨부 파일(`attachments/`) config 파일과 같은 폴더에 저장됩니다. 그래서 config만 따로 두면 프로젝트끼리 섞이지 않습니다.
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 (`state.json`) and downloaded `attachments/` are written **next to the config file**, so
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(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") {
@@ -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 파일은 config 이름에서 파생 같은 폴더에 여러 페르소나 config 를 둬도 세션이 안 섞임.
69
- // config.jsonstate.json (단일 호환), 외 foo.json foo.state.json.
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 STATE_PATH = join(
72
- DATA_DIR,
73
- stateBase === "config" ? "state.json" : `${stateBase}.state.json`,
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
- try {
200
- return JSON.parse(readFileSync(STATE_PATH, "utf8"));
201
- } catch {
202
- return {};
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 = join(DATA_DIR, "attachments");
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.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": {