claude-telegram-bot 0.3.0 → 0.3.2
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 +6 -7
- package/README.md +12 -13
- package/bot.mjs +59 -6
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -68,11 +68,10 @@ OpenClaw처럼 웹 UI까지 갖춘 구성을 써봤다면, 이 프로젝트는
|
|
|
68
68
|
**npx로 바로 실행**
|
|
69
69
|
|
|
70
70
|
```sh
|
|
71
|
-
npx claude-telegram-bot init # 현재 폴더에
|
|
72
|
-
npx claude-telegram-bot init
|
|
71
|
+
npx claude-telegram-bot init # 현재 폴더에 mybot.json 생성
|
|
72
|
+
npx claude-telegram-bot init myapp.json # 파일명 직접 지정도 가능
|
|
73
73
|
# 설정 편집 (token, projectDir 등)
|
|
74
|
-
npx claude-telegram-bot #
|
|
75
|
-
npx claude-telegram-bot mybot.json # 또는 경로를 직접 전달
|
|
74
|
+
npx claude-telegram-bot # mybot.json 으로 실행 (없으면 config.json 폴백)
|
|
76
75
|
```
|
|
77
76
|
|
|
78
77
|
**전역 설치 (상시 가동에 권장)**
|
|
@@ -80,8 +79,8 @@ npx claude-telegram-bot mybot.json # 또는 경로를 직접 전달
|
|
|
80
79
|
```sh
|
|
81
80
|
npm i -g claude-telegram-bot
|
|
82
81
|
|
|
83
|
-
claude-telegram-bot init ~/botconfigs/myproj #
|
|
84
|
-
claude-telegram-bot init ~/botconfigs/myproj/
|
|
82
|
+
claude-telegram-bot init ~/botconfigs/myproj # mybot.json 생성
|
|
83
|
+
claude-telegram-bot init ~/botconfigs/myproj/myapp.json # 또는 파일명 지정
|
|
85
84
|
# 설정 편집
|
|
86
85
|
claude-telegram-bot ~/botconfigs/myproj/mybot.json
|
|
87
86
|
```
|
|
@@ -90,7 +89,7 @@ claude-telegram-bot ~/botconfigs/myproj/mybot.json
|
|
|
90
89
|
|
|
91
90
|
## 설정
|
|
92
91
|
|
|
93
|
-
`
|
|
92
|
+
`mybot.json`(또는 사용 중인 config 파일)의 키는 다음과 같습니다.
|
|
94
93
|
|
|
95
94
|
| 키 | 설명 |
|
|
96
95
|
|---|---|
|
package/README.md
CHANGED
|
@@ -118,11 +118,10 @@ Prerequisites: **Node 18+** and the **`claude` CLI installed and authenticated**
|
|
|
118
118
|
**Option A — npx (no install)**
|
|
119
119
|
|
|
120
120
|
```sh
|
|
121
|
-
npx claude-telegram-bot init # writes ./
|
|
122
|
-
npx claude-telegram-bot init
|
|
121
|
+
npx claude-telegram-bot init # writes ./mybot.json
|
|
122
|
+
npx claude-telegram-bot init myapp.json # or pick your own filename
|
|
123
123
|
# edit the config (token, projectDir, …)
|
|
124
|
-
npx claude-telegram-bot # runs ./config.json
|
|
125
|
-
npx claude-telegram-bot mybot.json # or pass the path directly
|
|
124
|
+
npx claude-telegram-bot # runs ./mybot.json (falls back to config.json)
|
|
126
125
|
```
|
|
127
126
|
|
|
128
127
|
**Option B — global install (recommended for an always-on daemon)**
|
|
@@ -130,8 +129,8 @@ npx claude-telegram-bot mybot.json # or pass the path directly
|
|
|
130
129
|
```sh
|
|
131
130
|
npm i -g claude-telegram-bot
|
|
132
131
|
|
|
133
|
-
claude-telegram-bot init ~/botconfigs/myproj
|
|
134
|
-
claude-telegram-bot init ~/botconfigs/myproj/
|
|
132
|
+
claude-telegram-bot init ~/botconfigs/myproj # writes ~/botconfigs/myproj/mybot.json
|
|
133
|
+
claude-telegram-bot init ~/botconfigs/myproj/myapp.json # or a custom filename
|
|
135
134
|
# edit the config (token, projectDir, …)
|
|
136
135
|
claude-telegram-bot ~/botconfigs/myproj/mybot.json
|
|
137
136
|
```
|
|
@@ -152,7 +151,7 @@ Run several projects/personas by making one config file each and passing its pat
|
|
|
152
151
|
leave `allowedChatId` empty for now.
|
|
153
152
|
|
|
154
153
|
**2) Find your chatId and lock the bot to it** — Start the bot (`claude-telegram-bot …`), send it any
|
|
155
|
-
message in Telegram; it replies with this chat's `chatId`. Put that number into `
|
|
154
|
+
message in Telegram; it replies with this chat's `chatId`. Put that number into `mybot.json`
|
|
156
155
|
`allowedChatId` and restart. Now only you can use it. (See [Security](#security) — this is your only
|
|
157
156
|
auth layer.)
|
|
158
157
|
|
|
@@ -175,18 +174,18 @@ Commands: `/new` (reset context / new session) · `/stop` (stop current task; `-
|
|
|
175
174
|
|
|
176
175
|
**4) Keep it always on (optional)** — see [Always-on with launchd](#always-on-with-launchd-macos).
|
|
177
176
|
|
|
178
|
-
> **From source** (for hacking on the bot): clone the repo, `cp config.example.json
|
|
179
|
-
> then `node bot.mjs [
|
|
177
|
+
> **From source** (for hacking on the bot): clone the repo, `cp config.example.json mybot.json`,
|
|
178
|
+
> then `node bot.mjs [mybot.json]`. Same behavior as the CLI.
|
|
180
179
|
|
|
181
180
|
---
|
|
182
181
|
|
|
183
182
|
## Configuration
|
|
184
183
|
|
|
185
184
|
```sh
|
|
186
|
-
cp config.example.json
|
|
185
|
+
cp config.example.json mybot.json
|
|
187
186
|
```
|
|
188
187
|
|
|
189
|
-
Edit `
|
|
188
|
+
Edit `mybot.json`:
|
|
190
189
|
|
|
191
190
|
| Key | Description |
|
|
192
191
|
|---|---|
|
|
@@ -273,7 +272,7 @@ Config-defined jobs still require a restart to change; only chat-added jobs are
|
|
|
273
272
|
|
|
274
273
|
The code is project-agnostic: make **one config file per project** and run several at once.
|
|
275
274
|
|
|
276
|
-
- Run: `node bot.mjs /absolute/path/to/project.config.json` (no arg → `./config.json`)
|
|
275
|
+
- Run: `node bot.mjs /absolute/path/to/project.config.json` (no arg → `./mybot.json`, fallback `./config.json`)
|
|
277
276
|
- `state.json` and `attachments/` live in the **config file's folder**, so projects don't mix.
|
|
278
277
|
- **Note**: Telegram allows only one poller per token → each project needs its **own BotFather
|
|
279
278
|
token**.
|
|
@@ -299,7 +298,7 @@ One codebase, **a separate config file per role**.
|
|
|
299
298
|
shell-using bot (`bypassPermissions`) to **just one** to avoid concurrent-edit conflicts. For
|
|
300
299
|
read/plan-only, use `plan`.
|
|
301
300
|
- **Session isolation**: the `state` filename is derived from the config name
|
|
302
|
-
(`
|
|
301
|
+
(`mybot.json` → `mybot.state.json`, `dev.config.json` → `dev.config.state.json`), so multiple configs
|
|
303
302
|
in one folder don't share context.
|
|
304
303
|
- **One token per bot**: each bot needs its own BotFather token (`allowedChatId` can be the same).
|
|
305
304
|
|
package/bot.mjs
CHANGED
|
@@ -58,7 +58,7 @@ Requires: the claude CLI installed and authenticated on the host.`);
|
|
|
58
58
|
}
|
|
59
59
|
if (a === "init") {
|
|
60
60
|
const arg = process.argv[3];
|
|
61
|
-
const target = arg?.endsWith(".json") ? resolve(arg) : join(arg || process.cwd(), "
|
|
61
|
+
const target = arg?.endsWith(".json") ? resolve(arg) : join(arg || process.cwd(), "mybot.json");
|
|
62
62
|
if (existsSync(target)) {
|
|
63
63
|
console.error(`Already exists: ${target}`);
|
|
64
64
|
process.exit(1);
|
|
@@ -71,8 +71,9 @@ Requires: the claude CLI installed and authenticated on the host.`);
|
|
|
71
71
|
|
|
72
72
|
// Config path via arg or BOT_CONFIG env so one shared codebase can drive many
|
|
73
73
|
// projects; state + attachments live next to that config, keeping projects
|
|
74
|
-
// isolated.
|
|
75
|
-
const
|
|
74
|
+
// isolated. Defaults to mybot.json, falls back to config.json for existing setups.
|
|
75
|
+
const _defaultCfg = existsSync(join(HERE, "mybot.json")) ? join(HERE, "mybot.json") : join(HERE, "config.json");
|
|
76
|
+
const CONFIG_PATH = process.argv[2] || process.env.BOT_CONFIG || _defaultCfg;
|
|
76
77
|
const DATA_DIR = dirname(CONFIG_PATH);
|
|
77
78
|
// 데이터(state·attachments)는 config 폴더 아래 숨김 폴더 .claude-bot/ 에 모은다.
|
|
78
79
|
// state 파일명은 config 이름에서 파생 → 여러 페르소나 config 가 한 .claude-bot/ 를 공유해도 안 섞임
|
|
@@ -82,6 +83,7 @@ const stateFile = stateBase === "config" ? "state.json" : `${stateBase}.state.js
|
|
|
82
83
|
const BOT_DIR = join(DATA_DIR, ".claude-bot");
|
|
83
84
|
const STATE_PATH = join(BOT_DIR, stateFile);
|
|
84
85
|
const ATTACH_DIR = join(BOT_DIR, "attachments");
|
|
86
|
+
const MEMORY_PATH = join(BOT_DIR, "memory.md"); // /new 로 초기화해도 유지되는 퍼시스턴트 메모리
|
|
85
87
|
const LEGACY_STATE_PATH = join(DATA_DIR, stateFile); // 구버전(루트 직하) 호환
|
|
86
88
|
const LEGACY_ATTACH_DIR = join(DATA_DIR, "attachments");
|
|
87
89
|
|
|
@@ -129,6 +131,8 @@ const STR = {
|
|
|
129
131
|
"• /new — reset conversation context (new session)\n" +
|
|
130
132
|
"• /stop — stop the current task · /stop --reset to also roll back the session\n" +
|
|
131
133
|
"• /cron — list tasks · /cron add <natural language> to add · /cron rm <id> to remove\n" +
|
|
134
|
+
"• /remember <text> — save to persistent memory (survives /new)\n" +
|
|
135
|
+
"• /memory — view memory · /memory clear to wipe\n" +
|
|
132
136
|
"• /restart — restart the bot (after a syntax check)\n" +
|
|
133
137
|
"• /status — bot status & version\n" +
|
|
134
138
|
"• /model — view / switch the model\n" +
|
|
@@ -182,6 +186,12 @@ const STR = {
|
|
|
182
186
|
`/model default — clear the override`,
|
|
183
187
|
modelSet: (m) => `🧠 Model set to: ${m}`,
|
|
184
188
|
modelReset: (def) => `🧠 Model reset to default (${def}).`,
|
|
189
|
+
memoryEmpty: "No memory yet. Use `/remember <text>` to add.",
|
|
190
|
+
memoryShow: (m) => `💾 Memory:\n\`\`\`\n${m}\n\`\`\``,
|
|
191
|
+
memoryCleared: "🧹 Memory cleared.",
|
|
192
|
+
remembered: "💾 Saved to memory.",
|
|
193
|
+
rememberUsage: "Usage: /remember <text to remember>",
|
|
194
|
+
memoryUsage: "Usage: /memory · /memory clear",
|
|
185
195
|
},
|
|
186
196
|
ko: {
|
|
187
197
|
help: () =>
|
|
@@ -190,6 +200,8 @@ const STR = {
|
|
|
190
200
|
"• /new — 대화 맥락 초기화 (새 세션)\n" +
|
|
191
201
|
"• /stop — 진행 중인 작업 중단 · /stop --reset 으로 세션도 되돌리기\n" +
|
|
192
202
|
"• /cron — 예약 작업 보기 · /cron add <자연어>로 추가 · /cron rm <번호>로 삭제\n" +
|
|
203
|
+
"• /remember <내용> — 퍼시스턴트 메모리에 저장 (/new 로 초기화해도 유지)\n" +
|
|
204
|
+
"• /memory — 메모리 보기 · /memory clear 로 삭제\n" +
|
|
193
205
|
"• /restart — 봇 재시작 (문법 검사 후 안전하게)\n" +
|
|
194
206
|
"• /status — 봇 상태·버전 보기\n" +
|
|
195
207
|
"• /model — 모델 보기·전환\n" +
|
|
@@ -242,6 +254,12 @@ const STR = {
|
|
|
242
254
|
`/model default — 오버라이드 해제`,
|
|
243
255
|
modelSet: (m) => `🧠 모델을 ${m} (으)로 설정했습니다.`,
|
|
244
256
|
modelReset: (def) => `🧠 모델을 기본값(${def})으로 되돌렸습니다.`,
|
|
257
|
+
memoryEmpty: "저장된 메모리가 없습니다. `/remember <내용>`으로 추가하세요.",
|
|
258
|
+
memoryShow: (m) => `💾 메모리:\n\`\`\`\n${m}\n\`\`\``,
|
|
259
|
+
memoryCleared: "🧹 메모리를 삭제했습니다.",
|
|
260
|
+
remembered: "💾 메모리에 저장했습니다.",
|
|
261
|
+
rememberUsage: "사용법: /remember <기억할 내용>",
|
|
262
|
+
memoryUsage: "사용법: /memory · /memory clear",
|
|
245
263
|
},
|
|
246
264
|
};
|
|
247
265
|
const t = (l, key, ...a) => {
|
|
@@ -257,6 +275,8 @@ const COMMANDS = {
|
|
|
257
275
|
en: [
|
|
258
276
|
{ command: "new", description: "Reset context (new session)" },
|
|
259
277
|
{ command: "stop", description: "Stop the current task (--reset to roll back session)" },
|
|
278
|
+
{ command: "remember", description: "Save to persistent memory (survives /new)" },
|
|
279
|
+
{ command: "memory", description: "View or clear persistent memory" },
|
|
260
280
|
{ command: "cron", description: "List / add / remove scheduled tasks" },
|
|
261
281
|
{ command: "restart", description: "Restart the bot (after syntax check)" },
|
|
262
282
|
{ command: "status", description: "Bot status / version" },
|
|
@@ -267,6 +287,8 @@ const COMMANDS = {
|
|
|
267
287
|
ko: [
|
|
268
288
|
{ command: "new", description: "대화 맥락 초기화 (새 세션)" },
|
|
269
289
|
{ command: "stop", description: "작업 중단 (--reset 으로 세션 되돌리기)" },
|
|
290
|
+
{ command: "remember", description: "퍼시스턴트 메모리에 저장 (/new 후에도 유지)" },
|
|
291
|
+
{ command: "memory", description: "메모리 보기·삭제" },
|
|
270
292
|
{ command: "cron", description: "예약 작업 보기·추가·삭제" },
|
|
271
293
|
{ command: "restart", description: "봇 재시작 (문법 검사 후)" },
|
|
272
294
|
{ command: "status", description: "봇 상태·버전 보기" },
|
|
@@ -293,6 +315,15 @@ function checkLocalLock() {
|
|
|
293
315
|
}
|
|
294
316
|
}
|
|
295
317
|
|
|
318
|
+
// ── 퍼시스턴트 메모리 ─────────────────────────────────────────────────────
|
|
319
|
+
// /new 로 세션을 초기화해도 유지되는 메모리. runClaude 시 시스템 프롬프트에 주입.
|
|
320
|
+
function loadMemory() {
|
|
321
|
+
try { return readFileSync(MEMORY_PATH, "utf8").trim(); } catch { return ""; }
|
|
322
|
+
}
|
|
323
|
+
function saveMemory(content) {
|
|
324
|
+
writeFileSync(MEMORY_PATH, content);
|
|
325
|
+
}
|
|
326
|
+
|
|
296
327
|
// ── 상태 (세션 이어가기용) ────────────────────────────────────────────────
|
|
297
328
|
function loadState() {
|
|
298
329
|
// 새 경로(.claude-bot/) 우선, 없으면 구버전 루트 경로로 폴백(이주 실패 시 안전망).
|
|
@@ -429,8 +460,11 @@ function runClaude(prompt, sessionId, opts = {}) {
|
|
|
429
460
|
const modelHint = opts.modelHint
|
|
430
461
|
? `Current model: ${model || "claude (default)"}. Model tiers (low→high): haiku → sonnet → opus → fable. If this question seems to require more capability than the current model, append one short line at the very end of your reply: 💡 \`/model sonnet\` (or \`/model opus\`, \`/model fable\`) for a stronger answer. Omit the suggestion for simple questions.`
|
|
431
462
|
: null;
|
|
432
|
-
//
|
|
433
|
-
const
|
|
463
|
+
// opts.injectMemory: 퍼시스턴트 메모리를 시스템 프롬프트에 주입 (/new 로 초기화해도 유지)
|
|
464
|
+
const mem = opts.injectMemory ? loadMemory() : "";
|
|
465
|
+
const memoryBlock = mem ? `## Persistent memory (survives /new)\n${mem}` : null;
|
|
466
|
+
// 페르소나(cfg.persona) + 간결 지침 + 모델 힌트 + 메모리를 함께 주입
|
|
467
|
+
const appendSys = [cfg.persona, brevity, modelHint, memoryBlock].filter(Boolean).join("\n\n");
|
|
434
468
|
if (appendSys) args.push("--append-system-prompt", appendSys);
|
|
435
469
|
if (model) args.push("--model", model);
|
|
436
470
|
if (sessionId) args.push("--resume", sessionId);
|
|
@@ -820,6 +854,25 @@ async function handle(msg) {
|
|
|
820
854
|
await send(chatId, t(l, reset ? "stopReset" : "stopOk"));
|
|
821
855
|
return;
|
|
822
856
|
}
|
|
857
|
+
if (text.startsWith("/remember ")) {
|
|
858
|
+
const content = text.slice(10).trim();
|
|
859
|
+
if (!content) { await send(chatId, t(l, "rememberUsage")); return; }
|
|
860
|
+
const existing = loadMemory();
|
|
861
|
+
saveMemory(existing ? `${existing}\n- ${content}` : `- ${content}`);
|
|
862
|
+
await send(chatId, t(l, "remembered"));
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (text === "/memory" || text.startsWith("/memory ")) {
|
|
866
|
+
const arg = text.slice(7).trim();
|
|
867
|
+
if (arg === "clear") {
|
|
868
|
+
saveMemory("");
|
|
869
|
+
await send(chatId, t(l, "memoryCleared"));
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const mem = loadMemory();
|
|
873
|
+
await send(chatId, mem ? t(l, "memoryShow", mem) : t(l, "memoryEmpty"));
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
823
876
|
|
|
824
877
|
if (busy) {
|
|
825
878
|
msgQueue.push({ msg, receivedAt: Date.now() });
|
|
@@ -854,7 +907,7 @@ async function handle(msg) {
|
|
|
854
907
|
}
|
|
855
908
|
}
|
|
856
909
|
prevSessionId = state.sessionId; // /stop --reset 복원 대상 저장
|
|
857
|
-
const res = await runClaude(prompt, state.sessionId, { modelHint: true, trackChild: true });
|
|
910
|
+
const res = await runClaude(prompt, state.sessionId, { modelHint: true, trackChild: true, injectMemory: true });
|
|
858
911
|
if (res.sessionId) {
|
|
859
912
|
state.sessionId = res.sessionId;
|
|
860
913
|
saveState(state);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-telegram-bot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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": {
|