@hienlh/ppm 0.9.51 → 0.9.52
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/cli/commands/bot-cmd.ts +379 -6
- package/src/services/ppmbot/ppmbot-service.ts +48 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.52] - 2026-04-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Full `ppm bot` CLI**: All 13 Telegram commands now have CLI equivalents — `project switch/list/current`, `session new/list/resume/stop`, `memory save/list/forget`, `status`, `version`, `restart`, `help`. AI can invoke any command via Bash tool from natural language (e.g. "chuyển sang project ppm" → `ppm bot project switch ppm`).
|
|
7
|
+
- **Auto-detect chat ID**: `resolveChatId()` auto-detects single approved paired Telegram chat. Falls back to `--chat <id>` when multiple chats exist.
|
|
8
|
+
- **System prompt with natural language mapping**: AI receives full CLI reference + Vietnamese/English intent examples, executes commands directly instead of describing actions.
|
|
9
|
+
|
|
3
10
|
## [0.9.51] - 2026-04-07
|
|
4
11
|
|
|
5
12
|
### Added
|
package/package.json
CHANGED
|
@@ -11,17 +11,44 @@ const C = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Resolve the Telegram chatId for CLI operations.
|
|
15
|
+
* Auto-detects if exactly 1 approved paired chat exists.
|
|
16
|
+
* Otherwise requires --chat flag.
|
|
17
|
+
*/
|
|
18
|
+
export async function resolveChatId(chatOpt?: string): Promise<string> {
|
|
19
|
+
if (chatOpt) return chatOpt;
|
|
20
|
+
|
|
21
|
+
const { getApprovedPairedChats } = await import("../../services/db.service.ts");
|
|
22
|
+
const approved = getApprovedPairedChats();
|
|
23
|
+
|
|
24
|
+
if (approved.length === 0) {
|
|
25
|
+
throw new Error("No paired Telegram chats. Pair a device in PPM Settings first.");
|
|
26
|
+
}
|
|
27
|
+
if (approved.length > 1) {
|
|
28
|
+
const ids = approved.map((c) => ` ${c.telegram_chat_id} (${c.display_name || "unknown"})`).join("\n");
|
|
29
|
+
throw new Error(`Multiple paired chats found. Use --chat <id> to specify:\n${ids}`);
|
|
30
|
+
}
|
|
31
|
+
return approved[0]!.telegram_chat_id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `ppm bot` CLI — allows AI (via Bash tool) to manage PPMBot sessions,
|
|
36
|
+
* projects, memories, and server operations through natural language.
|
|
16
37
|
*
|
|
17
|
-
*
|
|
18
|
-
* ppm bot memory save "User prefers Vietnamese" --category preference
|
|
19
|
-
* ppm bot memory list
|
|
20
|
-
* ppm bot memory forget "Vietnamese"
|
|
38
|
+
* All session/project commands auto-detect the paired Telegram chat.
|
|
21
39
|
*/
|
|
22
40
|
export function registerBotCommands(program: Command): void {
|
|
23
41
|
const bot = program.command("bot").description("PPMBot utilities");
|
|
24
42
|
|
|
43
|
+
registerMemoryCommands(bot);
|
|
44
|
+
registerProjectCommands(bot);
|
|
45
|
+
registerSessionCommands(bot);
|
|
46
|
+
registerMiscCommands(bot);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Memory ──────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function registerMemoryCommands(bot: Command): void {
|
|
25
52
|
const mem = bot.command("memory").description("Manage cross-project memories");
|
|
26
53
|
|
|
27
54
|
mem
|
|
@@ -94,3 +121,349 @@ export function registerBotCommands(program: Command): void {
|
|
|
94
121
|
}
|
|
95
122
|
});
|
|
96
123
|
}
|
|
124
|
+
|
|
125
|
+
// ── Project ─────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
function registerProjectCommands(bot: Command): void {
|
|
128
|
+
const proj = bot.command("project").description("Manage bot project context");
|
|
129
|
+
|
|
130
|
+
proj
|
|
131
|
+
.command("list")
|
|
132
|
+
.description("List available projects")
|
|
133
|
+
.option("--json", "Output as JSON")
|
|
134
|
+
.action(async (opts: { json?: boolean }) => {
|
|
135
|
+
try {
|
|
136
|
+
const { PPMBotSessionManager } = await import("../../services/ppmbot/ppmbot-session.ts");
|
|
137
|
+
const sessions = new PPMBotSessionManager();
|
|
138
|
+
const projects = sessions.getProjectNames();
|
|
139
|
+
|
|
140
|
+
if (opts.json) {
|
|
141
|
+
console.log(JSON.stringify(projects));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (projects.length === 0) {
|
|
146
|
+
console.log(`${C.dim}No projects configured.${C.reset}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Show current project if possible
|
|
151
|
+
let current = "";
|
|
152
|
+
try {
|
|
153
|
+
const chatId = await resolveChatId();
|
|
154
|
+
const active = sessions.getActiveSession(chatId);
|
|
155
|
+
current = active?.projectName ?? "";
|
|
156
|
+
} catch { /* no chat — skip marker */ }
|
|
157
|
+
|
|
158
|
+
for (const name of projects) {
|
|
159
|
+
const marker = name === current ? ` ${C.green}✓${C.reset}` : "";
|
|
160
|
+
console.log(` ${name}${marker}`);
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
proj
|
|
169
|
+
.command("switch <name>")
|
|
170
|
+
.description("Switch to a different project")
|
|
171
|
+
.option("--chat <id>", "Telegram chat ID (auto-detected if single)")
|
|
172
|
+
.action(async (name: string, opts: { chat?: string }) => {
|
|
173
|
+
try {
|
|
174
|
+
const chatId = await resolveChatId(opts.chat);
|
|
175
|
+
const { PPMBotSessionManager } = await import("../../services/ppmbot/ppmbot-session.ts");
|
|
176
|
+
const sessions = new PPMBotSessionManager();
|
|
177
|
+
const session = await sessions.switchProject(chatId, name);
|
|
178
|
+
console.log(`${C.green}✓${C.reset} Switched to ${C.bold}${session.projectName}${C.reset}`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
proj
|
|
186
|
+
.command("current")
|
|
187
|
+
.description("Show current project")
|
|
188
|
+
.option("--chat <id>", "Telegram chat ID (auto-detected if single)")
|
|
189
|
+
.action(async (opts: { chat?: string }) => {
|
|
190
|
+
try {
|
|
191
|
+
const chatId = await resolveChatId(opts.chat);
|
|
192
|
+
const { PPMBotSessionManager } = await import("../../services/ppmbot/ppmbot-session.ts");
|
|
193
|
+
const sessions = new PPMBotSessionManager();
|
|
194
|
+
const active = sessions.getActiveSession(chatId);
|
|
195
|
+
|
|
196
|
+
// Fallback: check DB for active session
|
|
197
|
+
if (!active) {
|
|
198
|
+
const { getActivePPMBotSession } = await import("../../services/db.service.ts");
|
|
199
|
+
const { configService } = await import("../../services/config.service.ts");
|
|
200
|
+
const projects = (configService.get("projects") as any[]) ?? [];
|
|
201
|
+
for (const p of projects) {
|
|
202
|
+
const dbSession = getActivePPMBotSession(chatId, p.name);
|
|
203
|
+
if (dbSession) {
|
|
204
|
+
console.log(dbSession.project_name);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
console.log(`${C.dim}No active project. Use: ppm bot project switch <name>${C.reset}`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
console.log(active.projectName);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Session ─────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
function registerSessionCommands(bot: Command): void {
|
|
222
|
+
const sess = bot.command("session").description("Manage chat sessions");
|
|
223
|
+
|
|
224
|
+
sess
|
|
225
|
+
.command("new")
|
|
226
|
+
.description("Start a fresh session (current project)")
|
|
227
|
+
.option("--chat <id>", "Telegram chat ID (auto-detected if single)")
|
|
228
|
+
.action(async (opts: { chat?: string }) => {
|
|
229
|
+
try {
|
|
230
|
+
const chatId = await resolveChatId(opts.chat);
|
|
231
|
+
const { PPMBotSessionManager } = await import("../../services/ppmbot/ppmbot-session.ts");
|
|
232
|
+
const sessions = new PPMBotSessionManager();
|
|
233
|
+
|
|
234
|
+
// Get current project before closing
|
|
235
|
+
const active = sessions.getActiveSession(chatId);
|
|
236
|
+
const projectName = active?.projectName;
|
|
237
|
+
await sessions.closeSession(chatId);
|
|
238
|
+
const session = await sessions.getOrCreateSession(chatId, projectName ?? undefined);
|
|
239
|
+
console.log(`${C.green}✓${C.reset} New session for ${C.bold}${session.projectName}${C.reset} (${session.sessionId.slice(0, 8)})`);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
sess
|
|
247
|
+
.command("list")
|
|
248
|
+
.description("List recent sessions")
|
|
249
|
+
.option("--chat <id>", "Telegram chat ID (auto-detected if single)")
|
|
250
|
+
.option("-l, --limit <n>", "Max results", "20")
|
|
251
|
+
.option("--json", "Output as JSON")
|
|
252
|
+
.action(async (opts: { chat?: string; limit: string; json?: boolean }) => {
|
|
253
|
+
try {
|
|
254
|
+
const chatId = await resolveChatId(opts.chat);
|
|
255
|
+
const { getRecentPPMBotSessions, getSessionTitles, getPinnedSessionIds } = await import("../../services/db.service.ts");
|
|
256
|
+
const allSessions = getRecentPPMBotSessions(chatId, Number(opts.limit) || 20);
|
|
257
|
+
|
|
258
|
+
if (allSessions.length === 0) {
|
|
259
|
+
console.log(`${C.dim}No sessions found.${C.reset}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const titles = getSessionTitles(allSessions.map((s) => s.session_id));
|
|
264
|
+
const pinnedIds = getPinnedSessionIds();
|
|
265
|
+
|
|
266
|
+
// Sort: pinned first, then by last_message_at desc
|
|
267
|
+
const sorted = [...allSessions].sort((a, b) => {
|
|
268
|
+
const aPin = pinnedIds.has(a.session_id) ? 1 : 0;
|
|
269
|
+
const bPin = pinnedIds.has(b.session_id) ? 1 : 0;
|
|
270
|
+
if (aPin !== bPin) return bPin - aPin;
|
|
271
|
+
return b.last_message_at - a.last_message_at;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (opts.json) {
|
|
275
|
+
const jsonData = sorted.map((s, i) => ({
|
|
276
|
+
index: i + 1,
|
|
277
|
+
sessionId: s.session_id,
|
|
278
|
+
project: s.project_name,
|
|
279
|
+
title: titles[s.session_id]?.replace(/^\[PPM\]\s*/, "") || "",
|
|
280
|
+
pinned: pinnedIds.has(s.session_id),
|
|
281
|
+
active: !!s.is_active,
|
|
282
|
+
lastMessage: new Date(s.last_message_at * 1000).toISOString(),
|
|
283
|
+
}));
|
|
284
|
+
console.log(JSON.stringify(jsonData, null, 2));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const [i, s] of sorted.entries()) {
|
|
289
|
+
const pin = pinnedIds.has(s.session_id) ? "📌 " : " ";
|
|
290
|
+
const activeDot = s.is_active ? ` ${C.green}⬤${C.reset}` : "";
|
|
291
|
+
const rawTitle = titles[s.session_id]?.replace(/^\[PPM\]\s*/, "") || "";
|
|
292
|
+
const title = rawTitle ? rawTitle.slice(0, 50) : `${C.dim}untitled${C.reset}`;
|
|
293
|
+
const sid = s.session_id.slice(0, 8);
|
|
294
|
+
const date = new Date(s.last_message_at * 1000).toLocaleString(undefined, {
|
|
295
|
+
month: "short", day: "numeric", hour: "2-digit", minute: "2-digit",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
console.log(`${pin}${i + 1}. ${title}${activeDot}`);
|
|
299
|
+
console.log(` ${C.dim}${sid} · ${s.project_name} · ${date}${C.reset}`);
|
|
300
|
+
}
|
|
301
|
+
} catch (e) {
|
|
302
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
sess
|
|
308
|
+
.command("resume <target>")
|
|
309
|
+
.description("Resume a session by index number or session ID prefix")
|
|
310
|
+
.option("--chat <id>", "Telegram chat ID (auto-detected if single)")
|
|
311
|
+
.action(async (target: string, opts: { chat?: string }) => {
|
|
312
|
+
try {
|
|
313
|
+
const chatId = await resolveChatId(opts.chat);
|
|
314
|
+
const { PPMBotSessionManager } = await import("../../services/ppmbot/ppmbot-session.ts");
|
|
315
|
+
const sessions = new PPMBotSessionManager();
|
|
316
|
+
|
|
317
|
+
const index = parseInt(target, 10);
|
|
318
|
+
const isIndex = !isNaN(index) && index >= 1 && String(index) === target.trim();
|
|
319
|
+
|
|
320
|
+
const session = isIndex
|
|
321
|
+
? await sessions.resumeSessionById(chatId, index)
|
|
322
|
+
: await sessions.resumeSessionByIdPrefix(chatId, target.trim());
|
|
323
|
+
|
|
324
|
+
if (!session) {
|
|
325
|
+
console.log(`${C.yellow}Session not found: ${target}${C.reset}`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
console.log(`${C.green}✓${C.reset} Resumed session ${C.dim}${session.sessionId.slice(0, 8)}${C.reset} (${C.bold}${session.projectName}${C.reset})`);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
sess
|
|
336
|
+
.command("stop")
|
|
337
|
+
.description("End the current session")
|
|
338
|
+
.option("--chat <id>", "Telegram chat ID (auto-detected if single)")
|
|
339
|
+
.action(async (opts: { chat?: string }) => {
|
|
340
|
+
try {
|
|
341
|
+
const chatId = await resolveChatId(opts.chat);
|
|
342
|
+
const { PPMBotSessionManager } = await import("../../services/ppmbot/ppmbot-session.ts");
|
|
343
|
+
const sessions = new PPMBotSessionManager();
|
|
344
|
+
await sessions.closeSession(chatId);
|
|
345
|
+
console.log(`${C.green}✓${C.reset} Session ended`);
|
|
346
|
+
} catch (e) {
|
|
347
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ── Misc: status, version, restart, help ────────────────────────────
|
|
354
|
+
|
|
355
|
+
function registerMiscCommands(bot: Command): void {
|
|
356
|
+
bot
|
|
357
|
+
.command("status")
|
|
358
|
+
.description("Show current project and session info")
|
|
359
|
+
.option("--chat <id>", "Telegram chat ID (auto-detected if single)")
|
|
360
|
+
.option("--json", "Output as JSON")
|
|
361
|
+
.action(async (opts: { chat?: string; json?: boolean }) => {
|
|
362
|
+
try {
|
|
363
|
+
const chatId = await resolveChatId(opts.chat);
|
|
364
|
+
const { PPMBotSessionManager } = await import("../../services/ppmbot/ppmbot-session.ts");
|
|
365
|
+
const sessions = new PPMBotSessionManager();
|
|
366
|
+
const active = sessions.getActiveSession(chatId);
|
|
367
|
+
|
|
368
|
+
// Fallback: check DB for any active session
|
|
369
|
+
let project = active?.projectName ?? "";
|
|
370
|
+
let provider = active?.providerId ?? "";
|
|
371
|
+
let sessionId = active?.sessionId ?? "";
|
|
372
|
+
|
|
373
|
+
if (!active) {
|
|
374
|
+
const { getRecentPPMBotSessions } = await import("../../services/db.service.ts");
|
|
375
|
+
const recent = getRecentPPMBotSessions(chatId, 1);
|
|
376
|
+
if (recent.length > 0 && recent[0]!.is_active) {
|
|
377
|
+
project = recent[0]!.project_name;
|
|
378
|
+
provider = recent[0]!.provider_id;
|
|
379
|
+
sessionId = recent[0]!.session_id;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (opts.json) {
|
|
384
|
+
console.log(JSON.stringify({ chatId, project, provider, sessionId }));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!project) {
|
|
389
|
+
console.log(`${C.dim}No active session. Use: ppm bot project switch <name>${C.reset}`);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log(`Project: ${C.bold}${project}${C.reset}`);
|
|
394
|
+
console.log(`Provider: ${provider}`);
|
|
395
|
+
console.log(`Session: ${C.dim}${sessionId.slice(0, 12)}…${C.reset}`);
|
|
396
|
+
console.log(`Chat: ${chatId}`);
|
|
397
|
+
} catch (e) {
|
|
398
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
bot
|
|
404
|
+
.command("version")
|
|
405
|
+
.description("Show PPM version")
|
|
406
|
+
.action(async () => {
|
|
407
|
+
try {
|
|
408
|
+
const { VERSION } = await import("../../version.ts");
|
|
409
|
+
console.log(`PPM v${VERSION}`);
|
|
410
|
+
} catch {
|
|
411
|
+
console.log("PPM version unknown");
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
bot
|
|
416
|
+
.command("restart")
|
|
417
|
+
.description("Restart the PPM server")
|
|
418
|
+
.action(async () => {
|
|
419
|
+
try {
|
|
420
|
+
const { join } = await import("node:path");
|
|
421
|
+
const { writeFileSync } = await import("node:fs");
|
|
422
|
+
const { homedir } = await import("node:os");
|
|
423
|
+
const { getApprovedPairedChats } = await import("../../services/db.service.ts");
|
|
424
|
+
|
|
425
|
+
const approvedChats = getApprovedPairedChats();
|
|
426
|
+
const chatIds = approvedChats.map((c) => c.telegram_chat_id);
|
|
427
|
+
|
|
428
|
+
const markerPath = join(homedir(), ".ppm", "restart-notify.json");
|
|
429
|
+
writeFileSync(markerPath, JSON.stringify({ chatIds, ts: Date.now() }));
|
|
430
|
+
|
|
431
|
+
console.log(`${C.green}✓${C.reset} Restart signal sent (exit code 42)`);
|
|
432
|
+
process.exit(42);
|
|
433
|
+
} catch (e) {
|
|
434
|
+
console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
bot
|
|
440
|
+
.command("help")
|
|
441
|
+
.description("Show all bot CLI commands")
|
|
442
|
+
.action(() => {
|
|
443
|
+
console.log(`${C.bold}PPMBot CLI Commands${C.reset}
|
|
444
|
+
|
|
445
|
+
${C.cyan}Project:${C.reset}
|
|
446
|
+
ppm bot project list List available projects
|
|
447
|
+
ppm bot project switch <name> Switch to a project
|
|
448
|
+
ppm bot project current Show current project
|
|
449
|
+
|
|
450
|
+
${C.cyan}Session:${C.reset}
|
|
451
|
+
ppm bot session new Start fresh session
|
|
452
|
+
ppm bot session list List recent sessions
|
|
453
|
+
ppm bot session resume <n|id> Resume a session
|
|
454
|
+
ppm bot session stop End current session
|
|
455
|
+
|
|
456
|
+
${C.cyan}Memory (cross-project):${C.reset}
|
|
457
|
+
ppm bot memory save "<text>" Save a memory (-c category)
|
|
458
|
+
ppm bot memory list List saved memories
|
|
459
|
+
ppm bot memory forget "<topic>" Delete matching memories
|
|
460
|
+
|
|
461
|
+
${C.cyan}Server:${C.reset}
|
|
462
|
+
ppm bot status Current project/session info
|
|
463
|
+
ppm bot version Show PPM version
|
|
464
|
+
ppm bot restart Restart PPM server
|
|
465
|
+
|
|
466
|
+
${C.dim}Session/project commands auto-detect your Telegram chat.
|
|
467
|
+
Use --chat <id> if multiple chats are paired.${C.reset}`);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
@@ -480,6 +480,52 @@ class PPMBotService {
|
|
|
480
480
|
await this.telegram!.sendMessage(Number(chatId), text);
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
+
// ── System Prompt: CLI Tools ─────────────────────────────────────
|
|
484
|
+
|
|
485
|
+
private buildToolsPrompt(chatId: string): string {
|
|
486
|
+
return `\n\n## PPMBot CLI Tools (use via Bash tool)
|
|
487
|
+
Your chat ID is: ${chatId}
|
|
488
|
+
|
|
489
|
+
You can manage the user's session, project, and memories by running CLI commands via the Bash tool.
|
|
490
|
+
The chat ID is auto-detected so you do NOT need --chat flag.
|
|
491
|
+
|
|
492
|
+
### Project
|
|
493
|
+
ppm bot project list — List available projects
|
|
494
|
+
ppm bot project switch <name> — Switch to a project
|
|
495
|
+
ppm bot project current — Show current project
|
|
496
|
+
|
|
497
|
+
### Session
|
|
498
|
+
ppm bot session new — Start fresh session (current project)
|
|
499
|
+
ppm bot session list — List recent sessions
|
|
500
|
+
ppm bot session resume <n|id> — Resume a session by index or ID prefix
|
|
501
|
+
ppm bot session stop — End current session
|
|
502
|
+
|
|
503
|
+
### Memory (cross-project, persists across all projects)
|
|
504
|
+
ppm bot memory save "<content>" --category <category>
|
|
505
|
+
Categories: preference, fact, decision, architecture, issue
|
|
506
|
+
ppm bot memory list — List saved memories
|
|
507
|
+
ppm bot memory forget "<topic>" — Delete matching memories
|
|
508
|
+
|
|
509
|
+
### Server
|
|
510
|
+
ppm bot status — Current project/session info
|
|
511
|
+
ppm bot version — Show PPM version
|
|
512
|
+
ppm bot restart — Restart PPM server
|
|
513
|
+
|
|
514
|
+
### Natural Language Understanding
|
|
515
|
+
When the user says something like:
|
|
516
|
+
- "chuyển sang project X" or "switch to X" → ppm bot project switch X
|
|
517
|
+
- "tạo session mới" or "new session" → ppm bot session new
|
|
518
|
+
- "liệt kê sessions" or "show sessions" → ppm bot session list
|
|
519
|
+
- "quay lại session cũ" or "resume session 2" → ppm bot session resume 2
|
|
520
|
+
- "kết thúc session" or "stop" → ppm bot session stop
|
|
521
|
+
- "đang ở project nào?" or "current project?" → ppm bot project current
|
|
522
|
+
- "nhớ rằng..." or "remember that..." → ppm bot memory save "..." --category preference
|
|
523
|
+
- "quên đi..." or "forget about..." → ppm bot memory forget "..."
|
|
524
|
+
- "restart server" or "khởi động lại" → ppm bot restart
|
|
525
|
+
|
|
526
|
+
Always execute the appropriate CLI command — do NOT just describe what you would do.`;
|
|
527
|
+
}
|
|
528
|
+
|
|
483
529
|
// ── Chat Message Pipeline ───────────────────────────────────────
|
|
484
530
|
|
|
485
531
|
private async handleMessage(chatId: string, text: string): Promise<void> {
|
|
@@ -535,14 +581,8 @@ class PPMBotService {
|
|
|
535
581
|
systemPrompt += memorySection;
|
|
536
582
|
}
|
|
537
583
|
|
|
538
|
-
// Instruct AI to use CLI for
|
|
539
|
-
systemPrompt +=
|
|
540
|
-
When the user asks you to remember something, change how you address them, or save any preference/fact that should persist across projects and sessions, use the Bash tool to run:
|
|
541
|
-
ppm bot memory save "<content>" --category <category>
|
|
542
|
-
Categories: preference, fact, decision, architecture, issue
|
|
543
|
-
To list saved memories: ppm bot memory list
|
|
544
|
-
To forget: ppm bot memory forget "<topic>"
|
|
545
|
-
This saves to a global store that persists across all projects and sessions.`;
|
|
584
|
+
// Instruct AI to use CLI tools for session/project/memory management
|
|
585
|
+
systemPrompt += this.buildToolsPrompt(chatId);
|
|
546
586
|
|
|
547
587
|
// Send message to AI (prepend system prompt + memory context)
|
|
548
588
|
const opts: SendMessageOpts = {
|