thepopebot 1.2.75-beta.8 → 1.2.75
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.md +1 -1
- package/api/CLAUDE.md +1 -1
- package/api/index.js +5 -12
- package/bin/CLAUDE.md +1 -1
- package/bin/cli.js +295 -9
- package/bin/managed-paths.js +0 -7
- package/config/CLAUDE.md +1 -29
- package/lib/CLAUDE.md +3 -3
- package/lib/ai/CLAUDE.md +24 -3
- package/lib/ai/agent.js +8 -5
- package/lib/ai/headless-stream.js +3 -0
- package/lib/ai/index.js +9 -34
- package/lib/ai/line-mappers.js +72 -9
- package/lib/ai/tools.js +12 -16
- package/lib/chat/actions.js +15 -5
- package/lib/chat/api.js +17 -1
- package/lib/chat/components/chat-input.js +1 -0
- package/lib/chat/components/chat-input.jsx +1 -0
- package/lib/chat/components/chat.js +9 -1
- package/lib/chat/components/chat.jsx +15 -2
- package/lib/chat/components/chats-page.js +9 -4
- package/lib/chat/components/chats-page.jsx +7 -7
- package/lib/chat/components/code-mode-toggle.js +9 -13
- package/lib/chat/components/code-mode-toggle.jsx +17 -13
- package/lib/chat/components/crons-page.js +1 -1
- package/lib/chat/components/crons-page.jsx +1 -1
- package/lib/chat/components/message.js +12 -4
- package/lib/chat/components/message.jsx +17 -4
- package/lib/chat/components/settings-chat-page.js +2 -1
- package/lib/chat/components/settings-chat-page.jsx +4 -1
- package/lib/chat/components/sidebar-history-item.js +3 -3
- package/lib/chat/components/sidebar-history-item.jsx +4 -6
- package/lib/chat/components/triggers-page.js +1 -1
- package/lib/chat/components/triggers-page.jsx +1 -1
- package/lib/chat/components/ui/confirm-dialog.js +32 -28
- package/lib/chat/components/ui/confirm-dialog.jsx +6 -4
- package/lib/cluster/actions.js +4 -4
- package/lib/cluster/execute.js +3 -1
- package/lib/code/actions.js +18 -11
- package/lib/code/code-page.js +40 -40
- package/lib/code/code-page.jsx +36 -36
- package/lib/code/terminal-view.js +23 -7
- package/lib/code/terminal-view.jsx +25 -7
- package/lib/cron.js +3 -3
- package/lib/db/index.js +3 -1
- package/lib/paths.js +1 -38
- package/lib/tools/docker.js +3 -10
- package/lib/triggers.js +4 -3
- package/lib/utils/render-md.js +3 -1
- package/package.json +2 -1
- package/templates/.github/workflows/rebuild-event-handler.yml +3 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +1 -1
- package/templates/.gitignore.template +6 -3
- package/templates/.tmp/CLAUDE.md.template +5 -0
- package/templates/CLAUDE.md +3 -2
- package/templates/CLAUDE.md.template +24 -356
- package/templates/agent-job/CLAUDE.md.template +57 -0
- package/templates/agent-job/CRONS.json +16 -0
- package/templates/{config/agent-job → agent-job}/SOUL.md +3 -3
- package/templates/agent-job/SYSTEM.md +60 -0
- package/templates/agents/CLAUDE.md.template +54 -0
- package/templates/data/CLAUDE.md.template +5 -0
- package/templates/docker-compose.custom.yml +3 -5
- package/templates/docker-compose.yml +3 -5
- package/templates/event-handler/CLAUDE.md.template +0 -0
- package/templates/logs/CLAUDE.md.template +5 -0
- package/templates/skills/CLAUDE.md.template +57 -32
- package/templates/skills/active/.gitkeep +0 -0
- package/templates/skills/library/agent-job-secrets/SKILL.md +23 -0
- package/templates/skills/{agent-job-secrets → library/agent-job-secrets}/agent-job-secrets.js +26 -39
- package/templates/.pi/extensions/env-sanitizer/index.ts +0 -48
- package/templates/.pi/extensions/env-sanitizer/package.json +0 -5
- package/templates/README.md +0 -75
- package/templates/config/CLAUDE.md.template +0 -40
- package/templates/config/CRONS.json +0 -56
- package/templates/config/agent-job/AGENT_JOB.md +0 -30
- package/templates/cron/CLAUDE.md.template +0 -24
- package/templates/docs/CLAUDE.md.template +0 -12
- package/templates/docs/CLI.md +0 -59
- package/templates/docs/CLUSTERS.md +0 -151
- package/templates/docs/CONFIGURATION.md +0 -147
- package/templates/docs/CRONS_AND_TRIGGERS.md +0 -132
- package/templates/docs/GETTING_STARTED.md +0 -64
- package/templates/docs/SECURITY.md +0 -61
- package/templates/docs/SKILLS.md +0 -114
- package/templates/docs/UPGRADING.md +0 -92
- package/templates/skills/LICENSE +0 -21
- package/templates/skills/README.md +0 -117
- package/templates/skills/agent-job-secrets/SKILL.md +0 -25
- package/templates/triggers/CLAUDE.md.template +0 -41
- /package/templates/{config → agent-job}/HEARTBEAT.md +0 -0
- /package/templates/{cron → data}/.gitkeep +0 -0
- /package/templates/{logs → data/clusters}/.gitkeep +0 -0
- /package/templates/{triggers → data/db}/.gitkeep +0 -0
- /package/templates/{config/agent-job → event-handler}/SUMMARY.md +0 -0
- /package/templates/{config → event-handler}/TRIGGERS.json +0 -0
- /package/templates/{config → event-handler}/agent-chat/SYSTEM.md +0 -0
- /package/templates/{config/cluster → event-handler/clusters}/ROLE.md +0 -0
- /package/templates/{config/cluster → event-handler/clusters}/SYSTEM.md +0 -0
- /package/templates/{config → event-handler}/code-chat/SYSTEM.md +0 -0
- /package/templates/{config → event-handler}/litellm/main.yaml +0 -0
- /package/templates/skills/{playwright-cli → library/playwright-cli}/SKILL.md +0 -0
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ The wizard walks you through everything:
|
|
|
106
106
|
- **Web Chat**: Visit your APP_URL to chat with your agent, create jobs, upload files
|
|
107
107
|
- **Telegram** (optional): Run `npm run setup-telegram` to connect a Telegram bot
|
|
108
108
|
- **Webhook**: Send a POST to `/api/create-agent-job` with your API key to create jobs programmatically
|
|
109
|
-
- **Cron**: Edit `
|
|
109
|
+
- **Cron**: Edit `agent-job/CRONS.json` to schedule recurring jobs
|
|
110
110
|
|
|
111
111
|
### Chat vs Agent LLM
|
|
112
112
|
|
package/api/CLAUDE.md
CHANGED
|
@@ -26,7 +26,7 @@ Browser-facing data fetching uses **fetch route handlers** colocated with pages
|
|
|
26
26
|
| GET | `/api/ping` | None | Health check |
|
|
27
27
|
| POST | `/api/create-agent-job` | `x-api-key` | Create agent job |
|
|
28
28
|
| GET | `/api/get-agent-job-secret` | `x-api-key` | Get an agent job secret; oauth2 credentials return only the access_token (auto-refreshed) |
|
|
29
|
-
|
|
|
29
|
+
| GET | `/api/agent-job-list-secrets` | `x-api-key` | List agent job secret keys (no values); returns `{secrets: [{key, isSet, updatedAt, secretType}]}` |
|
|
30
30
|
| GET | `/api/agent-jobs/status` | `x-api-key` | Agent job status (query: `?agent_job_id=`) |
|
|
31
31
|
| POST | `/api/telegram/webhook` | Telegram webhook secret | Telegram message handler |
|
|
32
32
|
| POST | `/api/telegram/register` | `x-api-key` | Register bot token + webhook URL |
|
package/api/index.js
CHANGED
|
@@ -166,20 +166,13 @@ async function handleGetAgentSecret(request) {
|
|
|
166
166
|
return Response.json({ value: raw });
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
async function
|
|
169
|
+
async function handleListAgentSecrets(request) {
|
|
170
170
|
const record = verifyApiKey(request.headers.get('x-api-key'));
|
|
171
171
|
if (record.type !== 'agent_job_api_key') {
|
|
172
172
|
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
173
173
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const { key, value } = body;
|
|
177
|
-
if (!key || typeof value !== 'string') {
|
|
178
|
-
return Response.json({ error: 'Missing key or value' }, { status: 400 });
|
|
179
|
-
}
|
|
180
|
-
const { setAgentJobSecret } = await import('../lib/db/config.js');
|
|
181
|
-
setAgentJobSecret(key, value, 'agent');
|
|
182
|
-
return Response.json({ success: true });
|
|
174
|
+
const { listAgentJobSecrets } = await import('../lib/db/config.js');
|
|
175
|
+
return Response.json({ secrets: listAgentJobSecrets() });
|
|
183
176
|
}
|
|
184
177
|
|
|
185
178
|
async function handleTelegramRegister(request) {
|
|
@@ -405,7 +398,6 @@ async function POST(request) {
|
|
|
405
398
|
// Route to handler
|
|
406
399
|
switch (routePath) {
|
|
407
400
|
case '/create-agent-job': return handleCreateAgentJob(request);
|
|
408
|
-
case '/set-agent-job-secret': return handleSetAgentSecret(request);
|
|
409
401
|
case '/telegram/webhook': return handleTelegramWebhook(request);
|
|
410
402
|
case '/telegram/register': return handleTelegramRegister(request);
|
|
411
403
|
case '/github/webhook': return handleGithubWebhook(request);
|
|
@@ -424,7 +416,8 @@ async function GET(request) {
|
|
|
424
416
|
switch (routePath) {
|
|
425
417
|
case '/ping': return Response.json({ message: 'Pong!' });
|
|
426
418
|
case '/agent-jobs/status': return handleAgentJobStatus(request);
|
|
427
|
-
case '/get-agent-job-secret':
|
|
419
|
+
case '/get-agent-job-secret': return handleGetAgentSecret(request);
|
|
420
|
+
case '/agent-job-list-secrets': return handleListAgentSecrets(request);
|
|
428
421
|
case '/oauth/callback': return handleOAuthCallback(request);
|
|
429
422
|
default: return Response.json({ error: 'Not found' }, { status: 404 });
|
|
430
423
|
}
|
package/bin/CLAUDE.md
CHANGED
|
@@ -21,7 +21,7 @@ Entry point: `cli.js` (invoked via `npx thepopebot <command>`).
|
|
|
21
21
|
|
|
22
22
|
`managed-paths.js` defines files auto-synced by `init`. These are overwritten on every init/upgrade — users should not edit them.
|
|
23
23
|
|
|
24
|
-
**Managed paths**: `.github/workflows/`, `docker-compose.yml`, `.dockerignore`, `.gitignore`, `CLAUDE.md`, `
|
|
24
|
+
**Managed paths**: `.github/workflows/`, `docker-compose.yml`, `.dockerignore`, `.gitignore`, `agent-job/CLAUDE.md`, `event-handler/CLAUDE.md`, `skills/CLAUDE.md`.
|
|
25
25
|
|
|
26
26
|
`isManaged(relPath)` — returns true if a path is managed (exact match or directory prefix).
|
|
27
27
|
|
package/bin/cli.js
CHANGED
|
@@ -56,6 +56,8 @@ Commands:
|
|
|
56
56
|
setup-telegram Reconfigure Telegram webhook
|
|
57
57
|
reset-auth Regenerate AUTH_SECRET (invalidates all sessions)
|
|
58
58
|
reset [file] Restore a template file (or list available templates)
|
|
59
|
+
reset-all Nuclear reset — restore entire project to fresh init state
|
|
60
|
+
audit Show project state vs. package templates (modified/missing/unknown)
|
|
59
61
|
diff [file] Show differences between project files and package templates
|
|
60
62
|
sync <path> Sync local package to a test install (build, pack, Docker)
|
|
61
63
|
sync --fast <path> Fast sync — copy source into running container, rebuild .next
|
|
@@ -211,10 +213,13 @@ async function init() {
|
|
|
211
213
|
const tmplPath = templatePath(relPath, templatesDir);
|
|
212
214
|
const templateExists = fs.existsSync(path.join(templatesDir, tmplPath));
|
|
213
215
|
if (!templateExists) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
const bd = getBackupDir();
|
|
217
|
+
const dest = path.join(bd, relPath);
|
|
218
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
219
|
+
fs.renameSync(fullPath, dest);
|
|
220
|
+
backedUp.push(relPath);
|
|
216
221
|
deleted.push(relPath);
|
|
217
|
-
console.log(`
|
|
222
|
+
console.log(` Removed ${relPath} (stale managed file)`);
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
}
|
|
@@ -273,14 +278,14 @@ async function init() {
|
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
// Create default skill activation symlinks
|
|
276
|
-
const defaultSkills = [
|
|
281
|
+
const defaultSkills = [];
|
|
277
282
|
const activeDir = path.join(cwd, 'skills', 'active');
|
|
278
283
|
fs.mkdirSync(activeDir, { recursive: true });
|
|
279
284
|
for (const skill of defaultSkills) {
|
|
280
285
|
const symlink = path.join(activeDir, skill);
|
|
281
286
|
if (!fs.existsSync(symlink)) {
|
|
282
|
-
createDirLink(
|
|
283
|
-
console.log(` Created skills/active/${skill} →
|
|
287
|
+
createDirLink(`../library/${skill}`, symlink);
|
|
288
|
+
console.log(` Created skills/active/${skill} → ../library/${skill}`);
|
|
284
289
|
}
|
|
285
290
|
}
|
|
286
291
|
|
|
@@ -370,6 +375,36 @@ THEPOPEBOT_VERSION=${version}
|
|
|
370
375
|
console.log('\nDone! Run: npm run setup\n');
|
|
371
376
|
}
|
|
372
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Create a timestamped backup directory and return { dir, ts }.
|
|
380
|
+
*/
|
|
381
|
+
function createBackupDir(cwd) {
|
|
382
|
+
const now = new Date();
|
|
383
|
+
const ts = now.getFullYear().toString()
|
|
384
|
+
+ String(now.getMonth() + 1).padStart(2, '0')
|
|
385
|
+
+ String(now.getDate()).padStart(2, '0')
|
|
386
|
+
+ '-'
|
|
387
|
+
+ String(now.getHours()).padStart(2, '0')
|
|
388
|
+
+ String(now.getMinutes()).padStart(2, '0')
|
|
389
|
+
+ String(now.getSeconds()).padStart(2, '0');
|
|
390
|
+
return { dir: path.join(cwd, '.backups', ts), ts };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Move a file or symlink to the backup directory.
|
|
395
|
+
*/
|
|
396
|
+
function backupAndRemove(fullPath, relPath, backupDir) {
|
|
397
|
+
const dest = path.join(backupDir, relPath);
|
|
398
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
399
|
+
if (fs.lstatSync(fullPath).isSymbolicLink()) {
|
|
400
|
+
const target = fs.readlinkSync(fullPath);
|
|
401
|
+
fs.symlinkSync(target, dest);
|
|
402
|
+
fs.unlinkSync(fullPath);
|
|
403
|
+
} else {
|
|
404
|
+
fs.renameSync(fullPath, dest);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
373
408
|
/**
|
|
374
409
|
* List all available template files, or restore a specific one.
|
|
375
410
|
*/
|
|
@@ -385,7 +420,7 @@ function reset(filePath) {
|
|
|
385
420
|
console.log(` ${destPath(file)}`);
|
|
386
421
|
}
|
|
387
422
|
console.log('\nUsage: thepopebot reset <file>');
|
|
388
|
-
console.log('Example: thepopebot reset
|
|
423
|
+
console.log('Example: thepopebot reset agent-job/SOUL.md\n');
|
|
389
424
|
return;
|
|
390
425
|
}
|
|
391
426
|
|
|
@@ -399,13 +434,38 @@ function reset(filePath) {
|
|
|
399
434
|
process.exit(1);
|
|
400
435
|
}
|
|
401
436
|
|
|
437
|
+
// Back up existing file before overwriting
|
|
438
|
+
if (fs.existsSync(dest)) {
|
|
439
|
+
const { dir, ts } = createBackupDir(cwd);
|
|
440
|
+
if (fs.statSync(src).isDirectory()) {
|
|
441
|
+
// Back up all files in the directory
|
|
442
|
+
function walkBackup(d) {
|
|
443
|
+
const items = fs.readdirSync(d, { withFileTypes: true });
|
|
444
|
+
for (const item of items) {
|
|
445
|
+
const full = path.join(d, item.name);
|
|
446
|
+
const rel = path.relative(cwd, full);
|
|
447
|
+
if (item.isDirectory()) {
|
|
448
|
+
walkBackup(full);
|
|
449
|
+
} else {
|
|
450
|
+
backupAndRemove(full, rel, dir);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
walkBackup(dest);
|
|
455
|
+
console.log(`\n Backed up to .backups/${ts}/`);
|
|
456
|
+
} else {
|
|
457
|
+
backupAndRemove(dest, filePath, dir);
|
|
458
|
+
console.log(`\n Backed up to .backups/${ts}/${filePath}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
402
462
|
if (fs.statSync(src).isDirectory()) {
|
|
403
463
|
console.log(`\nRestoring ${filePath}/...\n`);
|
|
404
464
|
copyDirSyncForce(src, dest, tmplPath);
|
|
405
465
|
} else {
|
|
406
466
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
407
467
|
fs.copyFileSync(src, dest);
|
|
408
|
-
console.log(
|
|
468
|
+
console.log(`Restored ${filePath}\n`);
|
|
409
469
|
}
|
|
410
470
|
}
|
|
411
471
|
|
|
@@ -442,7 +502,7 @@ function diff(filePath) {
|
|
|
442
502
|
console.log(' All files match package templates.');
|
|
443
503
|
}
|
|
444
504
|
console.log('\nUsage: thepopebot diff <file>');
|
|
445
|
-
console.log('Example: thepopebot diff
|
|
505
|
+
console.log('Example: thepopebot diff agent-job/SOUL.md\n');
|
|
446
506
|
return;
|
|
447
507
|
}
|
|
448
508
|
|
|
@@ -471,6 +531,98 @@ function diff(filePath) {
|
|
|
471
531
|
}
|
|
472
532
|
}
|
|
473
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Audit project state against package templates.
|
|
536
|
+
* Groups all non-protected files into: matching, modified, missing, unknown.
|
|
537
|
+
*/
|
|
538
|
+
function audit() {
|
|
539
|
+
const packageDir = path.join(__dirname, '..');
|
|
540
|
+
const templatesDir = path.join(packageDir, 'templates');
|
|
541
|
+
const cwd = process.cwd();
|
|
542
|
+
|
|
543
|
+
const templateFiles = getTemplateFiles(templatesDir);
|
|
544
|
+
const matching = [];
|
|
545
|
+
const modified = [];
|
|
546
|
+
const missing = [];
|
|
547
|
+
|
|
548
|
+
// Check every template file against the project
|
|
549
|
+
for (const relPath of templateFiles) {
|
|
550
|
+
const src = path.join(templatesDir, relPath);
|
|
551
|
+
const outPath = destPath(relPath);
|
|
552
|
+
const dest = path.join(cwd, outPath);
|
|
553
|
+
|
|
554
|
+
if (!fs.existsSync(dest)) {
|
|
555
|
+
missing.push(outPath);
|
|
556
|
+
} else {
|
|
557
|
+
const srcContent = fs.readFileSync(src);
|
|
558
|
+
const destContent = fs.readFileSync(dest);
|
|
559
|
+
if (srcContent.equals(destContent)) {
|
|
560
|
+
matching.push(outPath);
|
|
561
|
+
} else {
|
|
562
|
+
modified.push(outPath);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Build a set of known template dest paths for lookup
|
|
568
|
+
const templateDestPaths = new Set(templateFiles.map(f => destPath(f)));
|
|
569
|
+
|
|
570
|
+
// Walk the project for unknown files (not in templates, not protected)
|
|
571
|
+
const unknown = [];
|
|
572
|
+
function walkProject(dir) {
|
|
573
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
574
|
+
for (const item of items) {
|
|
575
|
+
const fullPath = path.join(dir, item.name);
|
|
576
|
+
const relPath = path.relative(cwd, fullPath);
|
|
577
|
+
if (isProtected(relPath)) continue;
|
|
578
|
+
if (item.isDirectory() && !item.isSymbolicLink()) {
|
|
579
|
+
walkProject(fullPath);
|
|
580
|
+
} else if (!templateDestPaths.has(relPath)) {
|
|
581
|
+
unknown.push(relPath);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
walkProject(cwd);
|
|
586
|
+
|
|
587
|
+
// Report
|
|
588
|
+
console.log('\n Project audit\n');
|
|
589
|
+
|
|
590
|
+
if (modified.length > 0) {
|
|
591
|
+
console.log(` Modified (${modified.length}) — template exists, your version differs:`);
|
|
592
|
+
for (const f of modified) {
|
|
593
|
+
console.log(` ${f}`);
|
|
594
|
+
}
|
|
595
|
+
console.log('');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (missing.length > 0) {
|
|
599
|
+
console.log(` Missing (${missing.length}) — template exists, not in your project:`);
|
|
600
|
+
for (const f of missing) {
|
|
601
|
+
console.log(` ${f}`);
|
|
602
|
+
}
|
|
603
|
+
console.log('');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (unknown.length > 0) {
|
|
607
|
+
console.log(` Unknown (${unknown.length}) — in your project, no template (reset-all would remove):`);
|
|
608
|
+
for (const f of unknown) {
|
|
609
|
+
console.log(` ${f}`);
|
|
610
|
+
}
|
|
611
|
+
console.log('');
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
console.log(` ${matching.length} file(s) match package templates.`);
|
|
615
|
+
|
|
616
|
+
if (modified.length > 0 || missing.length > 0) {
|
|
617
|
+
console.log('\n To reset a file: thepopebot reset <file>');
|
|
618
|
+
console.log(' To view a diff: thepopebot diff <file>');
|
|
619
|
+
}
|
|
620
|
+
if (unknown.length > 0 || modified.length > 0 || missing.length > 0) {
|
|
621
|
+
console.log(' To reset everything: thepopebot reset-all');
|
|
622
|
+
}
|
|
623
|
+
console.log('');
|
|
624
|
+
}
|
|
625
|
+
|
|
474
626
|
function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
475
627
|
fs.mkdirSync(dest, { recursive: true });
|
|
476
628
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -491,6 +643,134 @@ function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
|
491
643
|
}
|
|
492
644
|
}
|
|
493
645
|
|
|
646
|
+
// Paths that reset-all must never touch (relative to project root).
|
|
647
|
+
// Entries ending with '/' are directory prefixes.
|
|
648
|
+
const PROTECTED_PATHS = [
|
|
649
|
+
'.env',
|
|
650
|
+
'.env.local',
|
|
651
|
+
'data/',
|
|
652
|
+
'logs/',
|
|
653
|
+
'.git/',
|
|
654
|
+
'.backups/',
|
|
655
|
+
'package-lock.json',
|
|
656
|
+
'package.json',
|
|
657
|
+
'docker-compose.custom.yml',
|
|
658
|
+
'.claude/',
|
|
659
|
+
'.pi/',
|
|
660
|
+
'skills/',
|
|
661
|
+
'node_modules/',
|
|
662
|
+
];
|
|
663
|
+
|
|
664
|
+
function isProtected(relPath) {
|
|
665
|
+
return PROTECTED_PATHS.some(p =>
|
|
666
|
+
p.endsWith('/') ? relPath === p.slice(0, -1) || relPath.startsWith(p) : relPath === p
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function resetAll() {
|
|
671
|
+
const cwd = process.cwd();
|
|
672
|
+
|
|
673
|
+
// Verify this is a thepopebot project
|
|
674
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
675
|
+
if (!fs.existsSync(pkgPath)) {
|
|
676
|
+
console.error('\n Not a thepopebot project (no package.json found).\n');
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
680
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
681
|
+
if (!deps.thepopebot) {
|
|
682
|
+
console.error('\n Not a thepopebot project (thepopebot not in dependencies).\n');
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Dry run — collect all files that would be moved
|
|
687
|
+
const filesToMove = [];
|
|
688
|
+
function collectFiles(dir) {
|
|
689
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
690
|
+
for (const item of items) {
|
|
691
|
+
const fullPath = path.join(dir, item.name);
|
|
692
|
+
const relPath = path.relative(cwd, fullPath);
|
|
693
|
+
if (isProtected(relPath)) continue;
|
|
694
|
+
if (item.isDirectory() && !item.isSymbolicLink()) {
|
|
695
|
+
collectFiles(fullPath);
|
|
696
|
+
} else {
|
|
697
|
+
filesToMove.push(relPath);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
collectFiles(cwd);
|
|
702
|
+
|
|
703
|
+
if (filesToMove.length === 0) {
|
|
704
|
+
console.log('\n Nothing to reset — no non-protected files found.\n');
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const { confirm, isCancel } = await import('@clack/prompts');
|
|
709
|
+
|
|
710
|
+
console.log('\n This will reset your entire project to a fresh thepopebot init state.');
|
|
711
|
+
console.log(` ${filesToMove.length} file(s) will be moved to .backups/:\n`);
|
|
712
|
+
for (const f of filesToMove) {
|
|
713
|
+
console.log(` ${f}`);
|
|
714
|
+
}
|
|
715
|
+
console.log('\n Protected (will NOT be touched):');
|
|
716
|
+
for (const p of PROTECTED_PATHS) {
|
|
717
|
+
console.log(` ${p}`);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const ok = await confirm({ message: '\nAre you sure? This is the nuclear option.' });
|
|
721
|
+
if (isCancel(ok) || !ok) {
|
|
722
|
+
console.log('\n Cancelled.\n');
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Move all files to backup
|
|
727
|
+
const { dir: backupDir, ts } = createBackupDir(cwd);
|
|
728
|
+
|
|
729
|
+
for (const relPath of filesToMove) {
|
|
730
|
+
const fullPath = path.join(cwd, relPath);
|
|
731
|
+
backupAndRemove(fullPath, relPath, backupDir);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Remove empty directories left behind
|
|
735
|
+
function removeEmptyDirs(dir) {
|
|
736
|
+
if (!fs.existsSync(dir)) return;
|
|
737
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
738
|
+
for (const item of items) {
|
|
739
|
+
if (item.isDirectory()) {
|
|
740
|
+
removeEmptyDirs(path.join(dir, item.name));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const relPath = path.relative(cwd, dir);
|
|
744
|
+
if (relPath && !isProtected(relPath) && fs.readdirSync(dir).length === 0) {
|
|
745
|
+
fs.rmdirSync(dir);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
removeEmptyDirs(cwd);
|
|
749
|
+
|
|
750
|
+
console.log(`\n Moved ${filesToMove.length} file(s) to .backups/${ts}/`);
|
|
751
|
+
|
|
752
|
+
// Run init to rebuild from templates
|
|
753
|
+
console.log('\n Running init to rebuild project...\n');
|
|
754
|
+
try {
|
|
755
|
+
execSync('npx thepopebot init --no-install', { stdio: 'inherit', cwd });
|
|
756
|
+
} catch {
|
|
757
|
+
console.error('\n Init failed. Your backup is at .backups/' + ts + '/\n');
|
|
758
|
+
process.exit(1);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Run npm install separately
|
|
762
|
+
console.log('\nInstalling dependencies...\n');
|
|
763
|
+
try {
|
|
764
|
+
execSync('npm install', { stdio: 'inherit', cwd });
|
|
765
|
+
} catch {
|
|
766
|
+
console.error('\n npm install failed. Your backup is at .backups/' + ts + '/\n');
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
console.log('\n Reset complete. Project restored to fresh init state.');
|
|
771
|
+
console.log(` Backup: .backups/${ts}/\n`);
|
|
772
|
+
}
|
|
773
|
+
|
|
494
774
|
function setup() {
|
|
495
775
|
const setupScript = path.join(__dirname, '..', 'setup', 'setup.mjs');
|
|
496
776
|
try {
|
|
@@ -801,6 +1081,12 @@ switch (command) {
|
|
|
801
1081
|
case 'reset':
|
|
802
1082
|
reset(args[0]);
|
|
803
1083
|
break;
|
|
1084
|
+
case 'reset-all':
|
|
1085
|
+
await resetAll();
|
|
1086
|
+
break;
|
|
1087
|
+
case 'audit':
|
|
1088
|
+
audit();
|
|
1089
|
+
break;
|
|
804
1090
|
case 'diff':
|
|
805
1091
|
diff(args[0]);
|
|
806
1092
|
break;
|
package/bin/managed-paths.js
CHANGED
|
@@ -4,16 +4,9 @@
|
|
|
4
4
|
// Paths ending with '/' are directories (all contents are managed).
|
|
5
5
|
export const MANAGED_PATHS = [
|
|
6
6
|
'.github/workflows/',
|
|
7
|
-
|
|
8
7
|
'docker-compose.yml',
|
|
9
8
|
'.dockerignore',
|
|
10
9
|
'.gitignore',
|
|
11
|
-
'CLAUDE.md',
|
|
12
|
-
'config/CLAUDE.md',
|
|
13
|
-
'skills/CLAUDE.md',
|
|
14
|
-
'cron/CLAUDE.md',
|
|
15
|
-
'triggers/CLAUDE.md',
|
|
16
|
-
'docs/',
|
|
17
10
|
];
|
|
18
11
|
|
|
19
12
|
export function isManaged(relPath) {
|
package/config/CLAUDE.md
CHANGED
|
@@ -1,32 +1,4 @@
|
|
|
1
|
-
# config/ —
|
|
2
|
-
|
|
3
|
-
## Directory Structure
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
config/
|
|
7
|
-
├── agent-chat/
|
|
8
|
-
│ └── SYSTEM.md # Agent chat system prompt (supports {{skills}}, {{datetime}})
|
|
9
|
-
├── code-chat/
|
|
10
|
-
│ └── SYSTEM.md # Code workspace system prompt
|
|
11
|
-
├── agent-job/
|
|
12
|
-
│ ├── SOUL.md # Agent personality/identity (used by Docker agent)
|
|
13
|
-
│ ├── AGENT_JOB.md # Agent runtime environment docs (used by Docker agent)
|
|
14
|
-
│ └── SUMMARY.md # Prompt for summarizing completed jobs
|
|
15
|
-
├── cluster/
|
|
16
|
-
│ ├── SYSTEM.md # Cluster worker system prompt
|
|
17
|
-
│ └── ROLE.md # Per-role prompt template for cluster workers
|
|
18
|
-
├── HEARTBEAT.md # Self-monitoring behavior (cron task prompt)
|
|
19
|
-
├── CRONS.json # Scheduled job definitions
|
|
20
|
-
└── TRIGGERS.json # Webhook trigger definitions
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Markdown File Includes
|
|
24
|
-
|
|
25
|
-
Markdown files in `config/` support includes and built-in variables, powered by `lib/utils/render-md.js`.
|
|
26
|
-
|
|
27
|
-
- **File includes**: `{{ filepath.md }}` — resolves relative to project root, recursive with circular detection. Missing files are left as-is.
|
|
28
|
-
- **`{{datetime}}`** — Current ISO timestamp.
|
|
29
|
-
- **`{{skills}}`** — Dynamic bullet list of active skill descriptions from `skills/active/*/SKILL.md` frontmatter. Never hardcode skill names — this is resolved at runtime.
|
|
1
|
+
# config/ — Next.js Config Wrapper
|
|
30
2
|
|
|
31
3
|
## Next.js Config Wrapper (index.js)
|
|
32
4
|
|
package/lib/CLAUDE.md
CHANGED
|
@@ -12,16 +12,16 @@ If the task needs to *think*, use `agent`. If it just needs to *do*, use `comman
|
|
|
12
12
|
|
|
13
13
|
**Agent**: Creates a Docker Agent job via `createAgentJob()`. Pushes an `agent-job/*` branch and launches a local Docker container. The `job` string is the LLM task prompt. Agent backend selected via `agent_backend` in `agent-job.config.json`.
|
|
14
14
|
|
|
15
|
-
**Command**: Runs a shell command on the event handler. Working directory:
|
|
15
|
+
**Command**: Runs a shell command on the event handler. Working directory: project root.
|
|
16
16
|
|
|
17
17
|
**Webhook**: Makes an HTTP request. `GET` skips the body; `POST` (default) sends `{ ...vars }` or `{ ...vars, data: <payload> }`.
|
|
18
18
|
|
|
19
19
|
## Cron Jobs
|
|
20
20
|
|
|
21
|
-
Defined in `
|
|
21
|
+
Defined in `agent-job/CRONS.json`, loaded by `lib/cron.js` at startup via `node-cron`. Each entry has `name`, `schedule` (cron expression), `type` (`agent`/`command`/`webhook`), and the corresponding action fields (`job`, `command`, or `url`/`method`/`headers`/`vars`). Set `enabled: false` to disable. Agent-type entries support optional `llm_provider` and `llm_model` fields to override the default LLM (passed to Docker agent via `agent-job.config.json`).
|
|
22
22
|
|
|
23
23
|
## Webhook Triggers
|
|
24
24
|
|
|
25
|
-
Defined in `
|
|
25
|
+
Defined in `event-handler/TRIGGERS.json`, loaded by `lib/triggers.js`. Each trigger watches an endpoint path (`watch_path`) and fires an array of actions (fire-and-forget, after auth, before route handler). Actions use the same `type`/`job`/`command`/`url` fields as cron jobs, including optional `llm_provider`/`llm_model` overrides.
|
|
26
26
|
|
|
27
27
|
Template tokens in `job` and `command` strings: `{{body}}`, `{{body.field}}`, `{{query}}`, `{{query.field}}`, `{{headers}}`, `{{headers.field}}`.
|
package/lib/ai/CLAUDE.md
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
Two agent singletons, both using `createReactAgent` from `@langchain/langgraph/prebuilt` with `SqliteSaver` for conversation memory:
|
|
6
6
|
|
|
7
7
|
**Agent Chat** — singleton via `getAgentChat()`:
|
|
8
|
-
- System prompt: `
|
|
8
|
+
- System prompt: `event-handler/agent-chat/SYSTEM.md` (rendered fresh each invocation via `render_md()`)
|
|
9
9
|
- Tools: `agent_job`, `coding_agent`
|
|
10
10
|
- Call `resetAgentChats()` to clear both singletons (required if hot-reloading)
|
|
11
11
|
|
|
12
12
|
**Code Chat** — singleton via `getCodeChat()`:
|
|
13
|
-
- System prompt: `
|
|
13
|
+
- System prompt: `event-handler/code-chat/SYSTEM.md` (rendered fresh each invocation)
|
|
14
14
|
- Tools: `coding_agent` (reads repo/branch/workspace from `runtime.configurable`)
|
|
15
15
|
|
|
16
16
|
## Adding a New Tool
|
|
@@ -73,7 +73,28 @@ Three-layer parser for Claude Code agents running in headless Docker containers:
|
|
|
73
73
|
3. **Event mapper** (`mapLine()`) — Converts each line to chat events:
|
|
74
74
|
- `assistant` messages: `text` blocks → `{ type: 'text' }`, `tool_use` blocks → `{ type: 'tool-call' }`
|
|
75
75
|
- `user` messages: `tool_result` blocks → `{ type: 'tool-result' }` (priority: stdout > string content > array)
|
|
76
|
-
- `result` messages: → `{ type: 'text'
|
|
76
|
+
- `result` messages: → `{ type: 'text' }` (final summary from the agent)
|
|
77
77
|
- Non-JSON lines (e.g. `NO_CHANGES`, `AGENT_FAILED`): wrapped as plain text events
|
|
78
78
|
|
|
79
79
|
`parseHeadlessStream(dockerLogStream)` is an async generator consuming `http.IncomingMessage`. `mapLine()` is also reused by `lib/cluster/stream.js` for worker log parsing.
|
|
80
|
+
|
|
81
|
+
### Tool Return Format
|
|
82
|
+
|
|
83
|
+
The `coding_agent` tool (in `tools.js`) returns the **full container session** as a flat JSON array. This becomes the ToolMessage in LangGraph's checkpoint, giving the LLM complete context on the current turn. The array contains:
|
|
84
|
+
|
|
85
|
+
- `{ type: 'meta', codingAgent, backendApi }` — first event, agent identity
|
|
86
|
+
- `{ type: 'text', text }` — agent text output
|
|
87
|
+
- `{ type: 'tool-call', toolCallId, toolName, args }` — agent tool invocations
|
|
88
|
+
- `{ type: 'tool-result', toolCallId, result }` — tool execution results
|
|
89
|
+
- `{ type: 'exit', exitCode }` — last event, container exit status
|
|
90
|
+
|
|
91
|
+
On error before streaming starts: `[{ type: 'error', message }]`.
|
|
92
|
+
|
|
93
|
+
### Adding a New Agent Mapper (line-mappers.js)
|
|
94
|
+
|
|
95
|
+
Each coding agent CLI has its own mapper function (`mapClaudeCodeLine`, `mapPiLine`, `mapGeminiLine`, `mapCodexLine`, `mapOpenCodeLine`, `mapKimiLine`). When adding a new agent:
|
|
96
|
+
|
|
97
|
+
1. Create `mapXxxLine(parsed)` in `line-mappers.js` that returns an array of `{ type, ... }` events
|
|
98
|
+
2. Register it in `headless-stream.js`: add to imports, re-exports, and the `mapperMap` object
|
|
99
|
+
3. Map the agent's JSON output to three event types: `{ type: 'text', text }`, `{ type: 'tool-call', toolCallId, toolName, args }`, `{ type: 'tool-result', toolCallId, result }`
|
|
100
|
+
4. Return `[{ type: 'skip' }]` for noise events (session init, rate limits, etc.) to suppress them without triggering the unknown fallback
|
package/lib/ai/agent.js
CHANGED
|
@@ -3,7 +3,8 @@ import { SystemMessage } from '@langchain/core/messages';
|
|
|
3
3
|
import { createModel } from './model.js';
|
|
4
4
|
import { agentJobTool, agentChatCodingTool, codeChatCodingTool } from './tools.js';
|
|
5
5
|
import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
|
|
6
|
-
import
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { PROJECT_ROOT } from '../paths.js';
|
|
7
8
|
import { render_md } from '../utils/render-md.js';
|
|
8
9
|
|
|
9
10
|
// Singletons on globalThis to survive Next.js webpack chunk duplication.
|
|
@@ -20,13 +21,14 @@ export async function getAgentChat() {
|
|
|
20
21
|
const model = await createModel();
|
|
21
22
|
const tools = [agentJobTool, agentChatCodingTool];
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
+
const dbPath = process.env.DATABASE_PATH || path.join(PROJECT_ROOT, 'data/db/thepopebot.sqlite');
|
|
25
|
+
const checkpointer = SqliteSaver.fromConnString(dbPath);
|
|
24
26
|
|
|
25
27
|
globalThis.__popebotAgentChat = createReactAgent({
|
|
26
28
|
llm: model,
|
|
27
29
|
tools,
|
|
28
30
|
checkpointSaver: checkpointer,
|
|
29
|
-
prompt: (state) => [new SystemMessage(render_md(
|
|
31
|
+
prompt: (state) => [new SystemMessage(render_md(path.join(PROJECT_ROOT, 'event-handler/agent-chat/SYSTEM.md'))), ...state.messages],
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
return globalThis.__popebotAgentChat;
|
|
@@ -41,13 +43,14 @@ export async function getCodeChat() {
|
|
|
41
43
|
const model = await createModel();
|
|
42
44
|
const tools = [codeChatCodingTool];
|
|
43
45
|
|
|
44
|
-
const
|
|
46
|
+
const dbPath = process.env.DATABASE_PATH || path.join(PROJECT_ROOT, 'data/db/thepopebot.sqlite');
|
|
47
|
+
const checkpointer = SqliteSaver.fromConnString(dbPath);
|
|
45
48
|
|
|
46
49
|
globalThis.__popebotCodeChat = createReactAgent({
|
|
47
50
|
llm: model,
|
|
48
51
|
tools,
|
|
49
52
|
checkpointSaver: checkpointer,
|
|
50
|
-
prompt: (state) => [new SystemMessage(render_md(
|
|
53
|
+
prompt: (state) => [new SystemMessage(render_md(path.join(PROJECT_ROOT, 'event-handler/code-chat/SYSTEM.md'))), ...state.messages],
|
|
51
54
|
});
|
|
52
55
|
}
|
|
53
56
|
return globalThis.__popebotCodeChat;
|
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
mapGeminiLine,
|
|
11
11
|
mapCodexLine,
|
|
12
12
|
mapOpenCodeLine,
|
|
13
|
+
mapKimiLine,
|
|
13
14
|
} from './line-mappers.js';
|
|
14
15
|
|
|
15
16
|
import {
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
mapGeminiLine,
|
|
20
21
|
mapCodexLine,
|
|
21
22
|
mapOpenCodeLine,
|
|
23
|
+
mapKimiLine,
|
|
22
24
|
} from './line-mappers.js';
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -41,6 +43,7 @@ export async function* parseHeadlessStream(dockerLogStream, codingAgent = 'claud
|
|
|
41
43
|
'gemini-cli': mapGeminiLine,
|
|
42
44
|
'codex-cli': mapCodexLine,
|
|
43
45
|
'opencode': mapOpenCodeLine,
|
|
46
|
+
'kimi-cli': mapKimiLine,
|
|
44
47
|
};
|
|
45
48
|
const mapper = mapperMap[codingAgent] || mapClaudeCodeLine;
|
|
46
49
|
|