thepopebot 1.2.75-beta.2 → 1.2.75-beta.21
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 +329 -14
- package/bin/docker-build.js +5 -0
- package/bin/managed-paths.js +0 -7
- package/bin/sync.js +84 -0
- package/config/CLAUDE.md +1 -29
- package/config/instrumentation.js +1 -1
- package/lib/CLAUDE.md +3 -3
- package/lib/ai/CLAUDE.md +24 -3
- package/lib/ai/agent.js +8 -5
- package/lib/ai/async-channel.js +51 -0
- package/lib/ai/headless-stream.js +3 -0
- package/lib/ai/index.js +149 -173
- package/lib/ai/line-mappers.js +72 -9
- package/lib/ai/tools.js +40 -28
- package/lib/chat/actions.js +34 -6
- package/lib/chat/api.js +17 -1
- package/lib/chat/components/chat-header.js +4 -0
- package/lib/chat/components/chat-header.jsx +4 -0
- 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 +3 -3
- package/lib/chat/components/chats-page.jsx +4 -6
- 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/settings-coding-agents-page.js +139 -1
- package/lib/chat/components/settings-coding-agents-page.jsx +160 -0
- package/lib/chat/components/settings-jobs-page.js +13 -2
- package/lib/chat/components/settings-jobs-page.jsx +15 -1
- package/lib/chat/components/settings-secrets-layout.js +1 -1
- package/lib/chat/components/settings-secrets-layout.jsx +1 -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/cluster/actions.js +4 -4
- package/lib/cluster/execute.js +3 -1
- package/lib/code/actions.js +34 -11
- package/lib/code/code-page.js +40 -40
- package/lib/code/code-page.jsx +36 -36
- package/lib/code/port-forwards.js +17 -3
- package/lib/code/terminal-view.js +16 -0
- package/lib/code/terminal-view.jsx +18 -0
- package/lib/config.js +4 -0
- package/lib/cron.js +3 -3
- package/lib/db/api-keys.js +22 -61
- package/lib/db/config.js +23 -0
- package/lib/db/index.js +3 -1
- package/lib/maintenance.js +34 -11
- package/lib/paths.js +1 -38
- package/lib/tools/create-agent-job.js +0 -4
- package/lib/tools/docker.js +23 -16
- package/lib/triggers.js +4 -3
- package/lib/utils/render-md.js +3 -1
- package/package.json +2 -1
- package/setup/setup-ssl.mjs +414 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +3 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +1 -1
- package/templates/.gitignore.template +7 -3
- package/templates/.tmp/CLAUDE.md.template +5 -0
- package/templates/CLAUDE.md +3 -2
- package/templates/CLAUDE.md.template +24 -357
- 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 +41 -62
- package/templates/docker-compose.yml +14 -21
- 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/library/agent-job-secrets/agent-job-secrets.js +62 -0
- 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/docker-compose.litellm.yml +0 -82
- 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 -181
- 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 -113
- 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/skills/agent-job-secrets/agent-job-secrets.js +0 -66
- package/templates/traefik-dynamic.yml.example +0 -7
- 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
|
@@ -52,11 +52,15 @@ Commands:
|
|
|
52
52
|
init Scaffold a new thepopebot project
|
|
53
53
|
upgrade|update [@beta|version] Upgrade thepopebot (install, init, build, commit, push)
|
|
54
54
|
setup Run interactive setup wizard
|
|
55
|
+
setup-ssl Configure SSL with Let's Encrypt wildcard cert
|
|
55
56
|
setup-telegram Reconfigure Telegram webhook
|
|
56
57
|
reset-auth Regenerate AUTH_SECRET (invalidates all sessions)
|
|
57
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)
|
|
58
61
|
diff [file] Show differences between project files and package templates
|
|
59
62
|
sync <path> Sync local package to a test install (build, pack, Docker)
|
|
63
|
+
sync --fast <path> Fast sync — copy source into running container, rebuild .next
|
|
60
64
|
set-var <KEY> [VALUE] Set a GitHub repository variable
|
|
61
65
|
user:password <email> Change a user's password
|
|
62
66
|
`);
|
|
@@ -209,10 +213,13 @@ async function init() {
|
|
|
209
213
|
const tmplPath = templatePath(relPath, templatesDir);
|
|
210
214
|
const templateExists = fs.existsSync(path.join(templatesDir, tmplPath));
|
|
211
215
|
if (!templateExists) {
|
|
212
|
-
|
|
213
|
-
|
|
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);
|
|
214
221
|
deleted.push(relPath);
|
|
215
|
-
console.log(`
|
|
222
|
+
console.log(` Removed ${relPath} (stale managed file)`);
|
|
216
223
|
}
|
|
217
224
|
}
|
|
218
225
|
}
|
|
@@ -246,6 +253,7 @@ async function init() {
|
|
|
246
253
|
const pkg = {
|
|
247
254
|
name: dirName,
|
|
248
255
|
private: true,
|
|
256
|
+
type: 'module',
|
|
249
257
|
scripts: {
|
|
250
258
|
setup: 'thepopebot setup',
|
|
251
259
|
'setup-telegram': 'thepopebot setup-telegram',
|
|
@@ -258,18 +266,26 @@ async function init() {
|
|
|
258
266
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
259
267
|
console.log(' Created package.json');
|
|
260
268
|
} else {
|
|
261
|
-
|
|
269
|
+
// Ensure "type": "module" is set for ESM support
|
|
270
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
271
|
+
if (!pkg.type) {
|
|
272
|
+
pkg.type = 'module';
|
|
273
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
274
|
+
console.log(' Added "type": "module" to package.json');
|
|
275
|
+
} else {
|
|
276
|
+
console.log(' Skipped package.json (already exists)');
|
|
277
|
+
}
|
|
262
278
|
}
|
|
263
279
|
|
|
264
280
|
// Create default skill activation symlinks
|
|
265
|
-
const defaultSkills = [
|
|
281
|
+
const defaultSkills = [];
|
|
266
282
|
const activeDir = path.join(cwd, 'skills', 'active');
|
|
267
283
|
fs.mkdirSync(activeDir, { recursive: true });
|
|
268
284
|
for (const skill of defaultSkills) {
|
|
269
285
|
const symlink = path.join(activeDir, skill);
|
|
270
286
|
if (!fs.existsSync(symlink)) {
|
|
271
|
-
createDirLink(
|
|
272
|
-
console.log(` Created skills/active/${skill} →
|
|
287
|
+
createDirLink(`../library/${skill}`, symlink);
|
|
288
|
+
console.log(` Created skills/active/${skill} → ../library/${skill}`);
|
|
273
289
|
}
|
|
274
290
|
}
|
|
275
291
|
|
|
@@ -337,8 +353,7 @@ AUTH_TRUST_HOST=true
|
|
|
337
353
|
DATABASE_PATH=data/db/thepopebot.sqlite
|
|
338
354
|
THEPOPEBOT_VERSION=${version}
|
|
339
355
|
|
|
340
|
-
#
|
|
341
|
-
# Edit docker-compose.custom.yml with your changes, then uncomment:
|
|
356
|
+
# To enable SSL with Let's Encrypt, run: npx thepopebot setup-ssl
|
|
342
357
|
# COMPOSE_FILE=docker-compose.custom.yml
|
|
343
358
|
`;
|
|
344
359
|
fs.writeFileSync(envPath, seedEnv);
|
|
@@ -360,6 +375,36 @@ THEPOPEBOT_VERSION=${version}
|
|
|
360
375
|
console.log('\nDone! Run: npm run setup\n');
|
|
361
376
|
}
|
|
362
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
|
+
|
|
363
408
|
/**
|
|
364
409
|
* List all available template files, or restore a specific one.
|
|
365
410
|
*/
|
|
@@ -375,7 +420,7 @@ function reset(filePath) {
|
|
|
375
420
|
console.log(` ${destPath(file)}`);
|
|
376
421
|
}
|
|
377
422
|
console.log('\nUsage: thepopebot reset <file>');
|
|
378
|
-
console.log('Example: thepopebot reset
|
|
423
|
+
console.log('Example: thepopebot reset agent-job/SOUL.md\n');
|
|
379
424
|
return;
|
|
380
425
|
}
|
|
381
426
|
|
|
@@ -389,13 +434,38 @@ function reset(filePath) {
|
|
|
389
434
|
process.exit(1);
|
|
390
435
|
}
|
|
391
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
|
+
|
|
392
462
|
if (fs.statSync(src).isDirectory()) {
|
|
393
463
|
console.log(`\nRestoring ${filePath}/...\n`);
|
|
394
464
|
copyDirSyncForce(src, dest, tmplPath);
|
|
395
465
|
} else {
|
|
396
466
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
397
467
|
fs.copyFileSync(src, dest);
|
|
398
|
-
console.log(
|
|
468
|
+
console.log(`Restored ${filePath}\n`);
|
|
399
469
|
}
|
|
400
470
|
}
|
|
401
471
|
|
|
@@ -432,7 +502,7 @@ function diff(filePath) {
|
|
|
432
502
|
console.log(' All files match package templates.');
|
|
433
503
|
}
|
|
434
504
|
console.log('\nUsage: thepopebot diff <file>');
|
|
435
|
-
console.log('Example: thepopebot diff
|
|
505
|
+
console.log('Example: thepopebot diff agent-job/SOUL.md\n');
|
|
436
506
|
return;
|
|
437
507
|
}
|
|
438
508
|
|
|
@@ -461,6 +531,98 @@ function diff(filePath) {
|
|
|
461
531
|
}
|
|
462
532
|
}
|
|
463
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
|
+
|
|
464
626
|
function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
465
627
|
fs.mkdirSync(dest, { recursive: true });
|
|
466
628
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -481,6 +643,134 @@ function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
|
481
643
|
}
|
|
482
644
|
}
|
|
483
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
|
+
|
|
484
774
|
function setup() {
|
|
485
775
|
const setupScript = path.join(__dirname, '..', 'setup', 'setup.mjs');
|
|
486
776
|
try {
|
|
@@ -490,6 +780,15 @@ function setup() {
|
|
|
490
780
|
}
|
|
491
781
|
}
|
|
492
782
|
|
|
783
|
+
function setupSsl() {
|
|
784
|
+
const setupScript = path.join(__dirname, '..', 'setup', 'setup-ssl.mjs');
|
|
785
|
+
try {
|
|
786
|
+
execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
|
|
787
|
+
} catch {
|
|
788
|
+
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
493
792
|
function setupTelegram() {
|
|
494
793
|
const setupScript = path.join(__dirname, '..', 'setup', 'setup-telegram.mjs');
|
|
495
794
|
try {
|
|
@@ -770,6 +1069,9 @@ switch (command) {
|
|
|
770
1069
|
case 'setup':
|
|
771
1070
|
setup();
|
|
772
1071
|
break;
|
|
1072
|
+
case 'setup-ssl':
|
|
1073
|
+
setupSsl();
|
|
1074
|
+
break;
|
|
773
1075
|
case 'setup-telegram':
|
|
774
1076
|
setupTelegram();
|
|
775
1077
|
break;
|
|
@@ -779,6 +1081,12 @@ switch (command) {
|
|
|
779
1081
|
case 'reset':
|
|
780
1082
|
reset(args[0]);
|
|
781
1083
|
break;
|
|
1084
|
+
case 'reset-all':
|
|
1085
|
+
await resetAll();
|
|
1086
|
+
break;
|
|
1087
|
+
case 'audit':
|
|
1088
|
+
audit();
|
|
1089
|
+
break;
|
|
782
1090
|
case 'diff':
|
|
783
1091
|
diff(args[0]);
|
|
784
1092
|
break;
|
|
@@ -787,8 +1095,15 @@ switch (command) {
|
|
|
787
1095
|
await upgrade();
|
|
788
1096
|
break;
|
|
789
1097
|
case 'sync': {
|
|
790
|
-
const
|
|
791
|
-
|
|
1098
|
+
const fast = args.includes('--fast');
|
|
1099
|
+
const syncArgs = args.filter(a => a !== '--fast');
|
|
1100
|
+
if (fast) {
|
|
1101
|
+
const { syncFast } = await import('./sync.js');
|
|
1102
|
+
await syncFast(syncArgs[0]);
|
|
1103
|
+
} else {
|
|
1104
|
+
const { sync } = await import('./sync.js');
|
|
1105
|
+
await sync(syncArgs[0]);
|
|
1106
|
+
}
|
|
792
1107
|
break;
|
|
793
1108
|
}
|
|
794
1109
|
case 'set-var':
|
package/bin/docker-build.js
CHANGED
|
@@ -62,6 +62,11 @@ const CODING_AGENTS = [
|
|
|
62
62
|
context: 'docker/coding-agent',
|
|
63
63
|
dockerfile: 'docker/coding-agent/Dockerfile.opencode',
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
name: 'coding-agent-kimi-cli',
|
|
67
|
+
context: 'docker/coding-agent',
|
|
68
|
+
dockerfile: 'docker/coding-agent/Dockerfile.kimi-cli',
|
|
69
|
+
},
|
|
65
70
|
];
|
|
66
71
|
|
|
67
72
|
// Non-coding-agent images (independent, built in parallel)
|
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/bin/sync.js
CHANGED
|
@@ -306,6 +306,90 @@ function buildDockerImage(projectPath) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Fast sync — skip Docker image rebuild entirely.
|
|
311
|
+
*
|
|
312
|
+
* 1. Build package JSX (npm run build)
|
|
313
|
+
* 2. mirrorTemplates() — scaffold using init's managed-path logic
|
|
314
|
+
* 3. docker cp package source (lib/, api/, config/, package.json) into
|
|
315
|
+
* the running container's /app/node_modules/thepopebot/
|
|
316
|
+
* 4. docker cp web/app/ + web/postcss.config.mjs into container
|
|
317
|
+
* 5. docker exec next build inside the container (tailwindcss already there)
|
|
318
|
+
* 6. Clean up copied source from container
|
|
319
|
+
* 7. docker exec pm2 restart all
|
|
320
|
+
*/
|
|
321
|
+
export async function syncFast(projectPath) {
|
|
322
|
+
if (!projectPath) {
|
|
323
|
+
console.error('\n Usage: thepopebot sync --fast <path-to-project>\n');
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
projectPath = path.resolve(projectPath);
|
|
328
|
+
|
|
329
|
+
if (!fs.existsSync(path.join(projectPath, 'package.json'))) {
|
|
330
|
+
console.error(`\n Not a project directory (no package.json): ${projectPath}\n`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 1. Build JSX
|
|
335
|
+
console.log('\n Building package...');
|
|
336
|
+
execSync('npm run build', { stdio: 'inherit', cwd: PACKAGE_DIR });
|
|
337
|
+
|
|
338
|
+
// 2. Mirror templates
|
|
339
|
+
console.log('\n Mirroring templates...');
|
|
340
|
+
mirrorTemplates(projectPath);
|
|
341
|
+
|
|
342
|
+
// 3. Get running container ID
|
|
343
|
+
const container = execSync('docker compose ps -q event-handler', {
|
|
344
|
+
encoding: 'utf8',
|
|
345
|
+
cwd: projectPath,
|
|
346
|
+
}).trim();
|
|
347
|
+
|
|
348
|
+
if (!container) {
|
|
349
|
+
console.error('\n event-handler container is not running. Use full sync instead.\n');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 4. Copy package source into container's node_modules/thepopebot/
|
|
354
|
+
const PKG_DEST = '/app/node_modules/thepopebot';
|
|
355
|
+
const PACKAGE_DIRS = ['lib', 'api', 'config'];
|
|
356
|
+
|
|
357
|
+
console.log('\n Copying package source into container...');
|
|
358
|
+
for (const dir of PACKAGE_DIRS) {
|
|
359
|
+
execSync(`docker exec ${container} rm -rf ${PKG_DEST}/${dir}`, { stdio: 'inherit' });
|
|
360
|
+
execSync(`docker cp ${path.join(PACKAGE_DIR, dir)} ${container}:${PKG_DEST}/${dir}`, { stdio: 'inherit' });
|
|
361
|
+
}
|
|
362
|
+
// Also copy package.json for exports resolution
|
|
363
|
+
execSync(`docker cp ${path.join(PACKAGE_DIR, 'package.json')} ${container}:${PKG_DEST}/package.json`, { stdio: 'inherit' });
|
|
364
|
+
|
|
365
|
+
// 5. Copy web/app/ source into container for next build
|
|
366
|
+
const webDir = path.join(PACKAGE_DIR, 'web');
|
|
367
|
+
console.log('\n Copying web source into container...');
|
|
368
|
+
execSync(`docker cp ${path.join(webDir, 'app')} ${container}:/app/app`, { stdio: 'inherit' });
|
|
369
|
+
execSync(`docker cp ${path.join(webDir, 'postcss.config.mjs')} ${container}:/app/postcss.config.mjs`, { stdio: 'inherit' });
|
|
370
|
+
execSync(`docker cp ${path.join(webDir, 'next.config.mjs')} ${container}:/app/next.config.mjs`, { stdio: 'inherit' });
|
|
371
|
+
|
|
372
|
+
// 6. Run next build inside the container
|
|
373
|
+
// Hide data/logs dirs so webpack's FileSystemInfo doesn't crawl them (causes OOM/RangeError
|
|
374
|
+
// when workspaces contain thousands of files). Restored immediately after build.
|
|
375
|
+
console.log('\n Building Next.js inside container...');
|
|
376
|
+
execSync(`docker exec ${container} sh -c 'mv /app/data /app/.data-build-tmp 2>/dev/null; mv /app/logs /app/.logs-build-tmp 2>/dev/null; true'`, { stdio: 'inherit' });
|
|
377
|
+
try {
|
|
378
|
+
execSync(`docker exec ${container} ./node_modules/.bin/next build`, { stdio: 'inherit' });
|
|
379
|
+
} finally {
|
|
380
|
+
execSync(`docker exec ${container} sh -c 'mv /app/.data-build-tmp /app/data 2>/dev/null; mv /app/.logs-build-tmp /app/logs 2>/dev/null; true'`, { stdio: 'inherit' });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 7. Clean up web source from container (not needed at runtime)
|
|
384
|
+
execSync(`docker exec ${container} rm -rf /app/app`, { stdio: 'inherit' });
|
|
385
|
+
|
|
386
|
+
// 8. Restart PM2
|
|
387
|
+
console.log('\n Restarting server...');
|
|
388
|
+
execSync(`docker exec ${container} pm2 restart all`, { stdio: 'inherit' });
|
|
389
|
+
|
|
390
|
+
console.log('\n Fast synced!\n');
|
|
391
|
+
}
|
|
392
|
+
|
|
309
393
|
export async function sync(projectPath) {
|
|
310
394
|
if (!projectPath) {
|
|
311
395
|
console.error('\n Usage: thepopebot sync <path-to-project>\n');
|