thepopebot 1.2.76-beta.11 → 1.2.76-beta.12

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/bin/cli.js CHANGED
@@ -86,12 +86,12 @@ function getTemplateFiles(templatesDir) {
86
86
  return files;
87
87
  }
88
88
 
89
- async function init() {
89
+ async function init(options = {}) {
90
90
  let cwd = process.cwd();
91
91
  const packageDir = path.join(__dirname, '..');
92
92
  const templatesDir = path.join(packageDir, 'templates');
93
- const noManaged = args.includes('--no-managed');
94
- const noInstall = args.includes('--no-install');
93
+ const noManaged = options.noManaged ?? args.includes('--no-managed');
94
+ const noInstall = options.noInstall ?? args.includes('--no-install');
95
95
 
96
96
  // Guard: warn if the directory is not empty (unless it's an existing thepopebot project)
97
97
  const entries = fs.readdirSync(cwd);
@@ -646,6 +646,9 @@ const PROTECTED_PATHS = [
646
646
  'package.json',
647
647
  'docker-compose.custom.yml',
648
648
  '.claude/',
649
+ '.codex/',
650
+ '.gemini/',
651
+ '.kimi/',
649
652
  '.pi/',
650
653
  'skills/',
651
654
  'node_modules/',
@@ -739,10 +742,10 @@ async function resetAll() {
739
742
 
740
743
  console.log(`\n Moved ${filesToMove.length} file(s) to .backups/${ts}/`);
741
744
 
742
- // Run init to rebuild from templates
745
+ // Run init to rebuild from templates (call directly to use the same package version)
743
746
  console.log('\n Running init to rebuild project...\n');
744
747
  try {
745
- execSync('npx thepopebot init --no-install', { stdio: 'inherit', cwd });
748
+ await init({ noInstall: true });
746
749
  } catch {
747
750
  console.error('\n Init failed. Your backup is at .backups/' + ts + '/\n');
748
751
  process.exit(1);
@@ -0,0 +1 @@
1
+ ALTER TABLE `code_workspaces` ADD `scope` text;
@@ -0,0 +1,750 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "d1566547-b9cf-4d86-9fcf-416f371ebcad",
5
+ "prevId": "65a1b238-c4cb-4ee1-9eca-f82d07858af2",
6
+ "tables": {
7
+ "chats": {
8
+ "name": "chats",
9
+ "columns": {
10
+ "id": {
11
+ "name": "id",
12
+ "type": "text",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": false
16
+ },
17
+ "user_id": {
18
+ "name": "user_id",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false
23
+ },
24
+ "title": {
25
+ "name": "title",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true,
29
+ "autoincrement": false,
30
+ "default": "'New Chat'"
31
+ },
32
+ "starred": {
33
+ "name": "starred",
34
+ "type": "integer",
35
+ "primaryKey": false,
36
+ "notNull": true,
37
+ "autoincrement": false,
38
+ "default": 0
39
+ },
40
+ "chat_mode": {
41
+ "name": "chat_mode",
42
+ "type": "text",
43
+ "primaryKey": false,
44
+ "notNull": true,
45
+ "autoincrement": false,
46
+ "default": "'agent'"
47
+ },
48
+ "code_workspace_id": {
49
+ "name": "code_workspace_id",
50
+ "type": "text",
51
+ "primaryKey": false,
52
+ "notNull": false,
53
+ "autoincrement": false
54
+ },
55
+ "created_at": {
56
+ "name": "created_at",
57
+ "type": "integer",
58
+ "primaryKey": false,
59
+ "notNull": true,
60
+ "autoincrement": false
61
+ },
62
+ "updated_at": {
63
+ "name": "updated_at",
64
+ "type": "integer",
65
+ "primaryKey": false,
66
+ "notNull": true,
67
+ "autoincrement": false
68
+ }
69
+ },
70
+ "indexes": {},
71
+ "foreignKeys": {},
72
+ "compositePrimaryKeys": {},
73
+ "uniqueConstraints": {},
74
+ "checkConstraints": {}
75
+ },
76
+ "cluster_roles": {
77
+ "name": "cluster_roles",
78
+ "columns": {
79
+ "id": {
80
+ "name": "id",
81
+ "type": "text",
82
+ "primaryKey": true,
83
+ "notNull": true,
84
+ "autoincrement": false
85
+ },
86
+ "cluster_id": {
87
+ "name": "cluster_id",
88
+ "type": "text",
89
+ "primaryKey": false,
90
+ "notNull": true,
91
+ "autoincrement": false
92
+ },
93
+ "role_name": {
94
+ "name": "role_name",
95
+ "type": "text",
96
+ "primaryKey": false,
97
+ "notNull": true,
98
+ "autoincrement": false
99
+ },
100
+ "role": {
101
+ "name": "role",
102
+ "type": "text",
103
+ "primaryKey": false,
104
+ "notNull": true,
105
+ "autoincrement": false,
106
+ "default": "''"
107
+ },
108
+ "prompt": {
109
+ "name": "prompt",
110
+ "type": "text",
111
+ "primaryKey": false,
112
+ "notNull": true,
113
+ "autoincrement": false,
114
+ "default": "'Execute your role.'"
115
+ },
116
+ "trigger_config": {
117
+ "name": "trigger_config",
118
+ "type": "text",
119
+ "primaryKey": false,
120
+ "notNull": false,
121
+ "autoincrement": false
122
+ },
123
+ "max_concurrency": {
124
+ "name": "max_concurrency",
125
+ "type": "integer",
126
+ "primaryKey": false,
127
+ "notNull": true,
128
+ "autoincrement": false,
129
+ "default": 1
130
+ },
131
+ "cleanup_worker_dir": {
132
+ "name": "cleanup_worker_dir",
133
+ "type": "integer",
134
+ "primaryKey": false,
135
+ "notNull": true,
136
+ "autoincrement": false,
137
+ "default": 0
138
+ },
139
+ "plan_mode": {
140
+ "name": "plan_mode",
141
+ "type": "integer",
142
+ "primaryKey": false,
143
+ "notNull": true,
144
+ "autoincrement": false,
145
+ "default": 0
146
+ },
147
+ "folders": {
148
+ "name": "folders",
149
+ "type": "text",
150
+ "primaryKey": false,
151
+ "notNull": false,
152
+ "autoincrement": false
153
+ },
154
+ "sort_order": {
155
+ "name": "sort_order",
156
+ "type": "integer",
157
+ "primaryKey": false,
158
+ "notNull": true,
159
+ "autoincrement": false,
160
+ "default": 0
161
+ },
162
+ "created_at": {
163
+ "name": "created_at",
164
+ "type": "integer",
165
+ "primaryKey": false,
166
+ "notNull": true,
167
+ "autoincrement": false
168
+ },
169
+ "updated_at": {
170
+ "name": "updated_at",
171
+ "type": "integer",
172
+ "primaryKey": false,
173
+ "notNull": true,
174
+ "autoincrement": false
175
+ }
176
+ },
177
+ "indexes": {},
178
+ "foreignKeys": {},
179
+ "compositePrimaryKeys": {},
180
+ "uniqueConstraints": {},
181
+ "checkConstraints": {}
182
+ },
183
+ "clusters": {
184
+ "name": "clusters",
185
+ "columns": {
186
+ "id": {
187
+ "name": "id",
188
+ "type": "text",
189
+ "primaryKey": true,
190
+ "notNull": true,
191
+ "autoincrement": false
192
+ },
193
+ "user_id": {
194
+ "name": "user_id",
195
+ "type": "text",
196
+ "primaryKey": false,
197
+ "notNull": true,
198
+ "autoincrement": false
199
+ },
200
+ "name": {
201
+ "name": "name",
202
+ "type": "text",
203
+ "primaryKey": false,
204
+ "notNull": true,
205
+ "autoincrement": false,
206
+ "default": "'New Cluster'"
207
+ },
208
+ "system_prompt": {
209
+ "name": "system_prompt",
210
+ "type": "text",
211
+ "primaryKey": false,
212
+ "notNull": true,
213
+ "autoincrement": false,
214
+ "default": "''"
215
+ },
216
+ "folders": {
217
+ "name": "folders",
218
+ "type": "text",
219
+ "primaryKey": false,
220
+ "notNull": false,
221
+ "autoincrement": false
222
+ },
223
+ "enabled": {
224
+ "name": "enabled",
225
+ "type": "integer",
226
+ "primaryKey": false,
227
+ "notNull": true,
228
+ "autoincrement": false,
229
+ "default": 0
230
+ },
231
+ "starred": {
232
+ "name": "starred",
233
+ "type": "integer",
234
+ "primaryKey": false,
235
+ "notNull": true,
236
+ "autoincrement": false,
237
+ "default": 0
238
+ },
239
+ "created_at": {
240
+ "name": "created_at",
241
+ "type": "integer",
242
+ "primaryKey": false,
243
+ "notNull": true,
244
+ "autoincrement": false
245
+ },
246
+ "updated_at": {
247
+ "name": "updated_at",
248
+ "type": "integer",
249
+ "primaryKey": false,
250
+ "notNull": true,
251
+ "autoincrement": false
252
+ }
253
+ },
254
+ "indexes": {},
255
+ "foreignKeys": {},
256
+ "compositePrimaryKeys": {},
257
+ "uniqueConstraints": {},
258
+ "checkConstraints": {}
259
+ },
260
+ "code_workspaces": {
261
+ "name": "code_workspaces",
262
+ "columns": {
263
+ "id": {
264
+ "name": "id",
265
+ "type": "text",
266
+ "primaryKey": true,
267
+ "notNull": true,
268
+ "autoincrement": false
269
+ },
270
+ "user_id": {
271
+ "name": "user_id",
272
+ "type": "text",
273
+ "primaryKey": false,
274
+ "notNull": true,
275
+ "autoincrement": false
276
+ },
277
+ "container_name": {
278
+ "name": "container_name",
279
+ "type": "text",
280
+ "primaryKey": false,
281
+ "notNull": false,
282
+ "autoincrement": false
283
+ },
284
+ "repo": {
285
+ "name": "repo",
286
+ "type": "text",
287
+ "primaryKey": false,
288
+ "notNull": false,
289
+ "autoincrement": false
290
+ },
291
+ "branch": {
292
+ "name": "branch",
293
+ "type": "text",
294
+ "primaryKey": false,
295
+ "notNull": false,
296
+ "autoincrement": false
297
+ },
298
+ "feature_branch": {
299
+ "name": "feature_branch",
300
+ "type": "text",
301
+ "primaryKey": false,
302
+ "notNull": false,
303
+ "autoincrement": false
304
+ },
305
+ "title": {
306
+ "name": "title",
307
+ "type": "text",
308
+ "primaryKey": false,
309
+ "notNull": true,
310
+ "autoincrement": false,
311
+ "default": "'Code Workspace'"
312
+ },
313
+ "last_interactive_commit": {
314
+ "name": "last_interactive_commit",
315
+ "type": "text",
316
+ "primaryKey": false,
317
+ "notNull": false,
318
+ "autoincrement": false
319
+ },
320
+ "coding_agent": {
321
+ "name": "coding_agent",
322
+ "type": "text",
323
+ "primaryKey": false,
324
+ "notNull": false,
325
+ "autoincrement": false
326
+ },
327
+ "scope": {
328
+ "name": "scope",
329
+ "type": "text",
330
+ "primaryKey": false,
331
+ "notNull": false,
332
+ "autoincrement": false
333
+ },
334
+ "starred": {
335
+ "name": "starred",
336
+ "type": "integer",
337
+ "primaryKey": false,
338
+ "notNull": true,
339
+ "autoincrement": false,
340
+ "default": 0
341
+ },
342
+ "has_changes": {
343
+ "name": "has_changes",
344
+ "type": "integer",
345
+ "primaryKey": false,
346
+ "notNull": true,
347
+ "autoincrement": false,
348
+ "default": 0
349
+ },
350
+ "created_at": {
351
+ "name": "created_at",
352
+ "type": "integer",
353
+ "primaryKey": false,
354
+ "notNull": true,
355
+ "autoincrement": false
356
+ },
357
+ "updated_at": {
358
+ "name": "updated_at",
359
+ "type": "integer",
360
+ "primaryKey": false,
361
+ "notNull": true,
362
+ "autoincrement": false
363
+ }
364
+ },
365
+ "indexes": {
366
+ "code_workspaces_container_name_unique": {
367
+ "name": "code_workspaces_container_name_unique",
368
+ "columns": [
369
+ "container_name"
370
+ ],
371
+ "isUnique": true
372
+ }
373
+ },
374
+ "foreignKeys": {},
375
+ "compositePrimaryKeys": {},
376
+ "uniqueConstraints": {},
377
+ "checkConstraints": {}
378
+ },
379
+ "messages": {
380
+ "name": "messages",
381
+ "columns": {
382
+ "id": {
383
+ "name": "id",
384
+ "type": "text",
385
+ "primaryKey": true,
386
+ "notNull": true,
387
+ "autoincrement": false
388
+ },
389
+ "chat_id": {
390
+ "name": "chat_id",
391
+ "type": "text",
392
+ "primaryKey": false,
393
+ "notNull": true,
394
+ "autoincrement": false
395
+ },
396
+ "role": {
397
+ "name": "role",
398
+ "type": "text",
399
+ "primaryKey": false,
400
+ "notNull": true,
401
+ "autoincrement": false
402
+ },
403
+ "content": {
404
+ "name": "content",
405
+ "type": "text",
406
+ "primaryKey": false,
407
+ "notNull": true,
408
+ "autoincrement": false
409
+ },
410
+ "created_at": {
411
+ "name": "created_at",
412
+ "type": "integer",
413
+ "primaryKey": false,
414
+ "notNull": true,
415
+ "autoincrement": false
416
+ }
417
+ },
418
+ "indexes": {},
419
+ "foreignKeys": {},
420
+ "compositePrimaryKeys": {},
421
+ "uniqueConstraints": {},
422
+ "checkConstraints": {}
423
+ },
424
+ "notifications": {
425
+ "name": "notifications",
426
+ "columns": {
427
+ "id": {
428
+ "name": "id",
429
+ "type": "text",
430
+ "primaryKey": true,
431
+ "notNull": true,
432
+ "autoincrement": false
433
+ },
434
+ "notification": {
435
+ "name": "notification",
436
+ "type": "text",
437
+ "primaryKey": false,
438
+ "notNull": true,
439
+ "autoincrement": false
440
+ },
441
+ "payload": {
442
+ "name": "payload",
443
+ "type": "text",
444
+ "primaryKey": false,
445
+ "notNull": true,
446
+ "autoincrement": false
447
+ },
448
+ "read": {
449
+ "name": "read",
450
+ "type": "integer",
451
+ "primaryKey": false,
452
+ "notNull": true,
453
+ "autoincrement": false,
454
+ "default": 0
455
+ },
456
+ "created_at": {
457
+ "name": "created_at",
458
+ "type": "integer",
459
+ "primaryKey": false,
460
+ "notNull": true,
461
+ "autoincrement": false
462
+ }
463
+ },
464
+ "indexes": {},
465
+ "foreignKeys": {},
466
+ "compositePrimaryKeys": {},
467
+ "uniqueConstraints": {},
468
+ "checkConstraints": {}
469
+ },
470
+ "settings": {
471
+ "name": "settings",
472
+ "columns": {
473
+ "id": {
474
+ "name": "id",
475
+ "type": "text",
476
+ "primaryKey": true,
477
+ "notNull": true,
478
+ "autoincrement": false
479
+ },
480
+ "type": {
481
+ "name": "type",
482
+ "type": "text",
483
+ "primaryKey": false,
484
+ "notNull": true,
485
+ "autoincrement": false
486
+ },
487
+ "key": {
488
+ "name": "key",
489
+ "type": "text",
490
+ "primaryKey": false,
491
+ "notNull": true,
492
+ "autoincrement": false
493
+ },
494
+ "value": {
495
+ "name": "value",
496
+ "type": "text",
497
+ "primaryKey": false,
498
+ "notNull": true,
499
+ "autoincrement": false
500
+ },
501
+ "created_by": {
502
+ "name": "created_by",
503
+ "type": "text",
504
+ "primaryKey": false,
505
+ "notNull": false,
506
+ "autoincrement": false
507
+ },
508
+ "last_used_at": {
509
+ "name": "last_used_at",
510
+ "type": "integer",
511
+ "primaryKey": false,
512
+ "notNull": false,
513
+ "autoincrement": false
514
+ },
515
+ "created_at": {
516
+ "name": "created_at",
517
+ "type": "integer",
518
+ "primaryKey": false,
519
+ "notNull": true,
520
+ "autoincrement": false
521
+ },
522
+ "updated_at": {
523
+ "name": "updated_at",
524
+ "type": "integer",
525
+ "primaryKey": false,
526
+ "notNull": true,
527
+ "autoincrement": false
528
+ }
529
+ },
530
+ "indexes": {},
531
+ "foreignKeys": {},
532
+ "compositePrimaryKeys": {},
533
+ "uniqueConstraints": {},
534
+ "checkConstraints": {}
535
+ },
536
+ "subscriptions": {
537
+ "name": "subscriptions",
538
+ "columns": {
539
+ "id": {
540
+ "name": "id",
541
+ "type": "text",
542
+ "primaryKey": true,
543
+ "notNull": true,
544
+ "autoincrement": false
545
+ },
546
+ "platform": {
547
+ "name": "platform",
548
+ "type": "text",
549
+ "primaryKey": false,
550
+ "notNull": true,
551
+ "autoincrement": false
552
+ },
553
+ "channel_id": {
554
+ "name": "channel_id",
555
+ "type": "text",
556
+ "primaryKey": false,
557
+ "notNull": true,
558
+ "autoincrement": false
559
+ },
560
+ "created_at": {
561
+ "name": "created_at",
562
+ "type": "integer",
563
+ "primaryKey": false,
564
+ "notNull": true,
565
+ "autoincrement": false
566
+ }
567
+ },
568
+ "indexes": {},
569
+ "foreignKeys": {},
570
+ "compositePrimaryKeys": {},
571
+ "uniqueConstraints": {},
572
+ "checkConstraints": {}
573
+ },
574
+ "user_channels": {
575
+ "name": "user_channels",
576
+ "columns": {
577
+ "id": {
578
+ "name": "id",
579
+ "type": "text",
580
+ "primaryKey": true,
581
+ "notNull": true,
582
+ "autoincrement": false
583
+ },
584
+ "user_id": {
585
+ "name": "user_id",
586
+ "type": "text",
587
+ "primaryKey": false,
588
+ "notNull": true,
589
+ "autoincrement": false
590
+ },
591
+ "channel": {
592
+ "name": "channel",
593
+ "type": "text",
594
+ "primaryKey": false,
595
+ "notNull": true,
596
+ "autoincrement": false
597
+ },
598
+ "channel_chat_id": {
599
+ "name": "channel_chat_id",
600
+ "type": "text",
601
+ "primaryKey": false,
602
+ "notNull": false,
603
+ "autoincrement": false
604
+ },
605
+ "code": {
606
+ "name": "code",
607
+ "type": "text",
608
+ "primaryKey": false,
609
+ "notNull": false,
610
+ "autoincrement": false
611
+ },
612
+ "code_expires_at": {
613
+ "name": "code_expires_at",
614
+ "type": "integer",
615
+ "primaryKey": false,
616
+ "notNull": false,
617
+ "autoincrement": false
618
+ },
619
+ "verified_at": {
620
+ "name": "verified_at",
621
+ "type": "integer",
622
+ "primaryKey": false,
623
+ "notNull": false,
624
+ "autoincrement": false
625
+ },
626
+ "active_thread_id": {
627
+ "name": "active_thread_id",
628
+ "type": "text",
629
+ "primaryKey": false,
630
+ "notNull": false,
631
+ "autoincrement": false
632
+ },
633
+ "created_at": {
634
+ "name": "created_at",
635
+ "type": "integer",
636
+ "primaryKey": false,
637
+ "notNull": true,
638
+ "autoincrement": false
639
+ },
640
+ "updated_at": {
641
+ "name": "updated_at",
642
+ "type": "integer",
643
+ "primaryKey": false,
644
+ "notNull": true,
645
+ "autoincrement": false
646
+ }
647
+ },
648
+ "indexes": {
649
+ "user_channels_user_channel_unique": {
650
+ "name": "user_channels_user_channel_unique",
651
+ "columns": [
652
+ "user_id",
653
+ "channel"
654
+ ],
655
+ "isUnique": true
656
+ },
657
+ "user_channels_channel_chat_id_unique": {
658
+ "name": "user_channels_channel_chat_id_unique",
659
+ "columns": [
660
+ "channel",
661
+ "channel_chat_id"
662
+ ],
663
+ "isUnique": true
664
+ },
665
+ "user_channels_code_lookup": {
666
+ "name": "user_channels_code_lookup",
667
+ "columns": [
668
+ "code"
669
+ ],
670
+ "isUnique": false
671
+ }
672
+ },
673
+ "foreignKeys": {},
674
+ "compositePrimaryKeys": {},
675
+ "uniqueConstraints": {},
676
+ "checkConstraints": {}
677
+ },
678
+ "users": {
679
+ "name": "users",
680
+ "columns": {
681
+ "id": {
682
+ "name": "id",
683
+ "type": "text",
684
+ "primaryKey": true,
685
+ "notNull": true,
686
+ "autoincrement": false
687
+ },
688
+ "email": {
689
+ "name": "email",
690
+ "type": "text",
691
+ "primaryKey": false,
692
+ "notNull": true,
693
+ "autoincrement": false
694
+ },
695
+ "password_hash": {
696
+ "name": "password_hash",
697
+ "type": "text",
698
+ "primaryKey": false,
699
+ "notNull": true,
700
+ "autoincrement": false
701
+ },
702
+ "role": {
703
+ "name": "role",
704
+ "type": "text",
705
+ "primaryKey": false,
706
+ "notNull": true,
707
+ "autoincrement": false,
708
+ "default": "'admin'"
709
+ },
710
+ "created_at": {
711
+ "name": "created_at",
712
+ "type": "integer",
713
+ "primaryKey": false,
714
+ "notNull": true,
715
+ "autoincrement": false
716
+ },
717
+ "updated_at": {
718
+ "name": "updated_at",
719
+ "type": "integer",
720
+ "primaryKey": false,
721
+ "notNull": true,
722
+ "autoincrement": false
723
+ }
724
+ },
725
+ "indexes": {
726
+ "users_email_unique": {
727
+ "name": "users_email_unique",
728
+ "columns": [
729
+ "email"
730
+ ],
731
+ "isUnique": true
732
+ }
733
+ },
734
+ "foreignKeys": {},
735
+ "compositePrimaryKeys": {},
736
+ "uniqueConstraints": {},
737
+ "checkConstraints": {}
738
+ }
739
+ },
740
+ "views": {},
741
+ "enums": {},
742
+ "_meta": {
743
+ "schemas": {},
744
+ "tables": {},
745
+ "columns": {}
746
+ },
747
+ "internal": {
748
+ "indexes": {}
749
+ }
750
+ }
@@ -162,6 +162,13 @@
162
162
  "when": 1776193069845,
163
163
  "tag": "0022_organic_apocalypse",
164
164
  "breakpoints": true
165
+ },
166
+ {
167
+ "idx": 23,
168
+ "version": "6",
169
+ "when": 1776559857722,
170
+ "tag": "0023_needy_ender_wiggin",
171
+ "breakpoints": true
165
172
  }
166
173
  ]
167
174
  }
package/lib/CLAUDE.md CHANGED
@@ -18,10 +18,10 @@ If the task needs to *think*, use `agent`. If it just needs to *do*, use `comman
18
18
 
19
19
  ## Cron Jobs
20
20
 
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`).
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`, `llm_model`, and `scope` fields. `scope` sets the agent's working directory to a subdirectory (e.g., `"scope": "agents/gary-vee"`) — the system prompt and skills resolve from that scope.
22
22
 
23
23
  ## Webhook Triggers
24
24
 
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.
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`/`scope` overrides.
26
26
 
27
27
  Template tokens in `job` and `command` strings: `{{body}}`, `{{body.field}}`, `{{query}}`, `{{query.field}}`, `{{headers}}`, `{{headers.field}}`.
package/lib/actions.js CHANGED
@@ -1,12 +1,15 @@
1
1
  import { exec } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import { createAgentJob } from './tools/create-agent-job.js';
4
+ import { buildCodingAgentSystemPrompt } from './ai/system-prompt.js';
5
+ import { resolveAgentScope } from './ai/scope.js';
6
+ import { PROJECT_ROOT } from './paths.js';
4
7
 
5
8
  const execAsync = promisify(exec);
6
9
 
7
10
  /**
8
11
  * Execute a single action
9
- * @param {Object} action - { type, job, command, url, method, headers, vars } (type: agent|command|webhook)
12
+ * @param {Object} action - { type, job, command, url, method, headers, vars, scope } (type: agent|command|webhook)
10
13
  * @param {Object} opts - { cwd, data }
11
14
  * @returns {Promise<string>} Result description for logging
12
15
  */
@@ -37,6 +40,11 @@ async function executeAction(action, opts = {}) {
37
40
  const options = {};
38
41
  if (action.llm_model) options.llmModel = action.llm_model;
39
42
  if (action.agent_backend) options.agentBackend = action.agent_backend;
43
+ if (action.scope) {
44
+ options.scope = action.scope;
45
+ const { skillsDir } = resolveAgentScope(PROJECT_ROOT, action.scope);
46
+ options.systemPrompt = buildCodingAgentSystemPrompt('agent', skillsDir, action.scope);
47
+ }
40
48
  const result = await createAgentJob(action.job, options);
41
49
  return `agent-job ${result.agent_job_id} — ${result.title}`;
42
50
  }
package/lib/ai/index.js CHANGED
@@ -143,6 +143,7 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
143
143
  const workspace = createCodeWorkspace(options.userId || 'unknown', {
144
144
  repo: repo,
145
145
  branch: branch,
146
+ scope: options.scope || null,
146
147
  });
147
148
  workspaceId = workspace.id;
148
149
  const { generateRandomName } = await import('../utils/random-name.js');
@@ -215,6 +216,7 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
215
216
  const systemPrompt = buildCodingAgentSystemPrompt(
216
217
  chatMode,
217
218
  chatMode === 'agent' ? skillsDir : null,
219
+ chatMode === 'agent' ? scope : null,
218
220
  );
219
221
 
220
222
  // 5. Stream from SDK adapter
@@ -31,16 +31,20 @@ function encodeCwd(absolutePath) {
31
31
  * Ensure the interactive container can find SDK-created sessions on the shared volume.
32
32
  *
33
33
  * The SDK stores sessions at $HOME/.claude/projects/<encoded-cwd>/. The interactive
34
- * container uses cwd=/home/coding-agent/workspace, but the SDK adapter uses
35
- * cwd=/app/data/workspaces/workspace-XXX/workspace — different encoded paths.
34
+ * container uses cwd=/home/coding-agent/workspace[/scope], but the SDK adapter uses
35
+ * cwd=/app/data/workspaces/workspace-XXX/workspace[/scope] — different encoded paths.
36
36
  *
37
37
  * Creates a symlink so the interactive container's encoded path resolves to the
38
38
  * SDK adapter's encoded path, both on the same volume.
39
+ *
40
+ * @param {string} wsBaseDir - Volume root (e.g., /app/data/workspaces/workspace-XXX)
41
+ * @param {string} sdkCwd - SDK's working directory (scoped, e.g., .../workspace/agents/gary-vee)
42
+ * @param {string} interactiveCwd - Interactive container's equivalent cwd (e.g., /home/coding-agent/workspace/agents/gary-vee)
39
43
  */
40
- function ensureSessionSymlink(wsBaseDir, workspaceDir) {
44
+ function ensureSessionSymlink(wsBaseDir, sdkCwd, interactiveCwd) {
41
45
  const projectsDir = path.join(wsBaseDir, '.claude', 'projects');
42
- const sdkEncoded = encodeCwd(workspaceDir);
43
- const interactiveEncoded = encodeCwd('/home/coding-agent/workspace');
46
+ const sdkEncoded = encodeCwd(sdkCwd);
47
+ const interactiveEncoded = encodeCwd(interactiveCwd);
44
48
 
45
49
  // Both point to the same dir — no symlink needed
46
50
  if (sdkEncoded === interactiveEncoded) return;
@@ -65,10 +69,17 @@ function ensureSessionSymlink(wsBaseDir, workspaceDir) {
65
69
  }
66
70
 
67
71
  export async function* claudeCodeStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments, workspaceId, chatMode }) {
68
- // Point HOME at the workspace volume so the SDK stores session data on the
69
- // shared volume (not the EH container's ephemeral filesystem).
70
- const wsBaseDir = path.dirname(workspaceDir);
71
- ensureSessionSymlink(wsBaseDir, workspaceDir);
72
+ // Point HOME at the workspace volume root (always one level above 'workspace/').
73
+ // workspaceDir may be scoped (e.g., .../workspace/agents/gary-vee), so we can't
74
+ // use path.dirname — derive from workspaceId instead.
75
+ const { workspaceDir: getWsDir } = await import('../../tools/docker.js');
76
+ const wsBaseDir = getWsDir(workspaceId);
77
+
78
+ // Build the interactive container's equivalent cwd for session symlink
79
+ const repoRoot = path.join(wsBaseDir, 'workspace');
80
+ const scope = workspaceDir.startsWith(repoRoot) ? workspaceDir.slice(repoRoot.length) : '';
81
+ const interactiveCwd = '/home/coding-agent/workspace' + scope;
82
+ ensureSessionSymlink(wsBaseDir, workspaceDir, interactiveCwd);
72
83
 
73
84
  // Build a local env object with auth credentials from the settings DB.
74
85
  // Passed via the SDK's `env` option — no process.env mutation needed.
@@ -1,4 +1,5 @@
1
1
  import path from 'path';
2
+ import { existsSync } from 'fs';
2
3
  import { PROJECT_ROOT } from '../paths.js';
3
4
  import { render_md } from '../utils/render-md.js';
4
5
 
@@ -6,14 +7,28 @@ import { render_md } from '../utils/render-md.js';
6
7
  * Build the system prompt for a coding agent.
7
8
  * @param {'agent'|'code'} mode - Chat mode
8
9
  * @param {string|null} [skillsDir] - Skills directory for {{skills}} resolution.
9
- * When provided, {{skills}} resolves from this directory (e.g., workspace scope's skills/).
10
- * When null, falls back to PROJECT_ROOT/skills.
10
+ * @param {string|null} [scope] - Agent scope (e.g., 'agents/gary-vee'). When set,
11
+ * looks for SYSTEM.md in the scoped directory first, falls back to agent-job/SYSTEM.md.
11
12
  * @returns {string|null} Rendered system prompt, or null if not configured
12
13
  */
13
- export function buildCodingAgentSystemPrompt(mode, skillsDir) {
14
- const file = mode === 'agent'
15
- ? path.join(PROJECT_ROOT, 'agent-job/SYSTEM.md')
16
- : path.join(PROJECT_ROOT, 'coding-workspace/SYSTEM.md');
14
+ export function buildCodingAgentSystemPrompt(mode, skillsDir, scope) {
15
+ let file;
16
+
17
+ if (mode === 'agent') {
18
+ // Check scoped SYSTEM.md first, fall back to agent-job/SYSTEM.md
19
+ if (scope) {
20
+ const scopedFile = path.join(PROJECT_ROOT, scope, 'SYSTEM.md');
21
+ if (existsSync(scopedFile)) {
22
+ file = scopedFile;
23
+ }
24
+ }
25
+ if (!file) {
26
+ file = path.join(PROJECT_ROOT, 'agent-job/SYSTEM.md');
27
+ }
28
+ } else {
29
+ file = path.join(PROJECT_ROOT, 'coding-workspace/SYSTEM.md');
30
+ }
31
+
17
32
  const rendered = render_md(file, { skillsDir });
18
33
  return rendered?.trim() || null;
19
34
  }
package/lib/ai/tools.js CHANGED
@@ -8,8 +8,15 @@ import { buildCodingAgentSystemPrompt } from './system-prompt.js';
8
8
  import { resolveAgentScope } from './scope.js';
9
9
 
10
10
  const agentJobTool = tool(
11
- async ({ prompt }) => {
12
- const result = await createAgentJob(prompt);
11
+ async ({ prompt }, runtime) => {
12
+ const scope = runtime?.configurable?.scope || null;
13
+
14
+ // Pre-render system prompt with scoped skills (same as all other entry points)
15
+ const { PROJECT_ROOT } = await import('../paths.js');
16
+ const { skillsDir } = resolveAgentScope(PROJECT_ROOT, scope);
17
+ const systemPrompt = buildCodingAgentSystemPrompt('agent', skillsDir, scope);
18
+
19
+ const result = await createAgentJob(prompt, { scope, systemPrompt });
13
20
  return JSON.stringify({
14
21
  success: true,
15
22
  agent_job_id: result.agent_job_id,
@@ -72,7 +79,7 @@ const agentChatCodingTool = tool(
72
79
  workspaceId,
73
80
  taskPrompt: prompt,
74
81
  mode,
75
- systemPrompt: buildCodingAgentSystemPrompt('agent', skillsDir),
82
+ systemPrompt: buildCodingAgentSystemPrompt('agent', skillsDir, scope),
76
83
  injectSecrets: true,
77
84
  scope: scope || undefined,
78
85
  });
@@ -16,6 +16,8 @@ import {
16
16
  touchChatUpdatedAt,
17
17
  } from '../db/chats.js';
18
18
  import { buildCodingAgentSystemPrompt } from '../ai/system-prompt.js';
19
+ import { resolveAgentScope } from '../ai/scope.js';
20
+ import { workspaceDir as getWorkspaceDir } from '../tools/docker.js';
19
21
  import {
20
22
  addSession,
21
23
  getSession as getTermSession,
@@ -138,8 +140,15 @@ export async function ensureCodeWorkspaceContainer(id) {
138
140
 
139
141
  // Inject agent job secrets when the linked chat is in agent mode
140
142
  const chat = getChatByWorkspaceId(id);
141
- const injectSecrets = chat?.chatMode === 'agent';
142
- const systemPrompt = buildCodingAgentSystemPrompt(chat?.chatMode === 'agent' ? 'agent' : 'code');
143
+ const isAgent = chat?.chatMode === 'agent';
144
+ const injectSecrets = isAgent;
145
+
146
+ // Resolve scope for system prompt skills
147
+ const wsBaseDir = getWorkspaceDir(id);
148
+ const repoDir = (await import('path')).join(wsBaseDir, 'workspace');
149
+ const wsScope = workspace.scope || null;
150
+ const { skillsDir } = resolveAgentScope(repoDir, wsScope);
151
+ const systemPrompt = buildCodingAgentSystemPrompt(isAgent ? 'agent' : 'code', isAgent ? skillsDir : null, isAgent ? wsScope : null);
143
152
 
144
153
  try {
145
154
  const { inspectContainer, startContainer, removeContainer, runInteractiveContainer } =
@@ -158,6 +167,7 @@ export async function ensureCodeWorkspaceContainer(id) {
158
167
  workspaceId: id,
159
168
  injectSecrets,
160
169
  systemPrompt,
170
+ scope: workspace.scope || undefined,
161
171
  });
162
172
  return { status: 'created' };
163
173
  }
@@ -188,6 +198,7 @@ export async function ensureCodeWorkspaceContainer(id) {
188
198
  workspaceId: id,
189
199
  injectSecrets,
190
200
  systemPrompt,
201
+ scope: workspace.scope || undefined,
191
202
  });
192
203
  return { status: 'created' };
193
204
  } catch (err) {
@@ -214,8 +225,15 @@ export async function startInteractiveMode(id, agentOverride) {
214
225
 
215
226
  // Inject agent job secrets when the linked chat is in agent mode
216
227
  const chat = getChatByWorkspaceId(id);
217
- const injectSecrets = chat?.chatMode === 'agent';
218
- const systemPrompt = buildCodingAgentSystemPrompt(chat?.chatMode === 'agent' ? 'agent' : 'code');
228
+ const isAgent = chat?.chatMode === 'agent';
229
+ const injectSecrets = isAgent;
230
+
231
+ // Resolve scope for system prompt skills
232
+ const wsBase = getWorkspaceDir(id);
233
+ const repoPath = (await import('path')).join(wsBase, 'workspace');
234
+ const wsScope2 = workspace.scope || null;
235
+ const { skillsDir: scopedSkillsDir } = resolveAgentScope(repoPath, wsScope2);
236
+ const systemPrompt = buildCodingAgentSystemPrompt(isAgent ? 'agent' : 'code', isAgent ? scopedSkillsDir : null, isAgent ? wsScope2 : null);
219
237
 
220
238
  try {
221
239
  const { getConfig } = await import('../config.js');
@@ -234,6 +252,7 @@ export async function startInteractiveMode(id, agentOverride) {
234
252
  workspaceId: id,
235
253
  injectSecrets,
236
254
  systemPrompt,
255
+ scope: workspace.scope || undefined,
237
256
  });
238
257
 
239
258
  // Persist both the container name and the agent so recovery can use the same image
@@ -14,7 +14,7 @@ import { codeWorkspaces } from './schema.js';
14
14
  * @param {string} [options.id] - Optional ID (UUID). Generated if not provided.
15
15
  * @returns {object} The created workspace
16
16
  */
17
- export function createCodeWorkspace(userId, { containerName = null, repo = null, branch = null, title = 'Code Workspace', id = null } = {}) {
17
+ export function createCodeWorkspace(userId, { containerName = null, repo = null, branch = null, scope = null, title = 'Code Workspace', id = null } = {}) {
18
18
  const db = getDb();
19
19
  const now = Date.now();
20
20
  const workspace = {
@@ -23,6 +23,7 @@ export function createCodeWorkspace(userId, { containerName = null, repo = null,
23
23
  containerName,
24
24
  repo,
25
25
  branch,
26
+ scope,
26
27
  title,
27
28
  createdAt: now,
28
29
  updatedAt: now,
package/lib/db/schema.js CHANGED
@@ -53,6 +53,7 @@ export const codeWorkspaces = sqliteTable('code_workspaces', {
53
53
  title: text('title').notNull().default('Code Workspace'),
54
54
  lastInteractiveCommit: text('last_interactive_commit'),
55
55
  codingAgent: text('coding_agent'),
56
+ scope: text('scope'),
56
57
  starred: integer('starred').notNull().default(0),
57
58
  hasChanges: integer('has_changes').notNull().default(0),
58
59
  createdAt: integer('created_at').notNull(),
@@ -51,6 +51,8 @@ async function createAgentJob(agentJobDescription, options = {}) {
51
51
  const config = { title, job: agentJobDescription };
52
52
  if (options.llmModel) config.llm_model = options.llmModel;
53
53
  if (options.agentBackend) config.agent_backend = options.agentBackend;
54
+ if (options.scope) config.scope = options.scope;
55
+ if (options.systemPrompt) config.system_prompt = options.systemPrompt;
54
56
 
55
57
  const treeEntries = [
56
58
  {
@@ -99,6 +101,7 @@ async function createAgentJob(agentJobDescription, options = {}) {
99
101
  description: agentJobDescription,
100
102
  codingAgent: options.agentBackend,
101
103
  llmModel: options.llmModel,
104
+ scope: options.scope,
102
105
  }).catch(err => {
103
106
  console.error(`[agent-job] Failed to launch container for ${agentJobId}:`, err.message);
104
107
  });
@@ -880,7 +880,7 @@ async function removeVolume(name) {
880
880
  * @param {string} [options.llmModel] - Model override
881
881
  * @returns {Promise<{containerId: string, containerName: string, volumeName: string}>}
882
882
  */
883
- async function runAgentJobContainer({ agentJobId, repo, branch, title, description, codingAgent, llmModel }) {
883
+ async function runAgentJobContainer({ agentJobId, repo, branch, title, description, codingAgent, llmModel, scope }) {
884
884
  const agent = codingAgent || getConfig('CODING_AGENT') || 'claude-code';
885
885
  const version = process.env.THEPOPEBOT_VERSION;
886
886
  const image = `stephengpope/thepopebot:coding-agent-${agent}-${version}`;
@@ -904,6 +904,9 @@ async function runAgentJobContainer({ agentJobId, repo, branch, title, descripti
904
904
  if (llmModel) {
905
905
  env.push(`LLM_MODEL=${llmModel}`);
906
906
  }
907
+ if (scope) {
908
+ env.push(`SCOPE=${scope}`);
909
+ }
907
910
 
908
911
  // Auth env vars based on agent type
909
912
  const { env: authEnv, backendApi } = buildAgentAuthEnv(agent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.76-beta.11",
3
+ "version": "1.2.76-beta.12",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {
@@ -31,6 +31,29 @@ Some files are auto-synced by `npx thepopebot init` and will be overwritten on u
31
31
  - `event-handler/CLAUDE.md`
32
32
  - `skills/CLAUDE.md`
33
33
 
34
+ ## Agent Scoping
35
+
36
+ Agents can be scoped to subdirectories within the repository. When a chat is launched with a scope (e.g., `agents/gary-vee`), the coding agent runs with that directory as its working directory.
37
+
38
+ ### Directory Structure
39
+
40
+ ```
41
+ agents/
42
+ gary-vee/
43
+ CLAUDE.md ← agent-specific context (optional)
44
+ SYSTEM.md ← agent-specific system prompt (optional)
45
+ skills/ ← agent-specific skills (optional, overrides root skills/)
46
+ agent-job-secrets → ../../../skills/agent-job-secrets (symlink)
47
+ custom-skill/
48
+ ```
49
+
50
+ ### How Scoping Works
51
+
52
+ - **Working directory** — The agent's cwd is set to the scoped directory. It still has access to the full repo.
53
+ - **Skills** — If the scoped directory has a `skills/` folder, those skills are used. If not, the root `skills/` folder is used as a fallback. Sub-agent skills can symlink back to root skills they need.
54
+ - **CLAUDE.md** — The coding agent automatically picks up `.claude/` and `CLAUDE.md` files relative to its working directory.
55
+ - **Default scope** — When no scope is selected, the agent runs from the repository root with root-level skills.
56
+
34
57
  ## Agents
35
58
 
36
59
  (No agents configured yet.)
@@ -35,9 +35,16 @@ Everything in the workspace is automatically committed and pushed when your job
35
35
  - `docker-compose.yml`, `.dockerignore`, `.gitignore` — Managed infrastructure files
36
36
  - `.env` — Environment secrets
37
37
 
38
+ ## Agent Scoping
39
+
40
+ Agents can be scoped to subdirectories under `agents/`. When scoped, the agent's working directory is set to that subdirectory (e.g., `agents/gary-vee/`). The full repo is still accessible.
41
+
42
+ - **Skills fallback** — If the scoped directory has a `skills/` folder, those are used. Otherwise, the root `skills/` folder applies. Sub-agents can symlink individual skills from root: `skills/agent-job-secrets → ../../../skills/agent-job-secrets`.
43
+ - **No skills folder needed** — If you don't create a `skills/` directory in the agent scope, it inherits all root skills automatically.
44
+
38
45
  ## Self-Modification
39
46
 
40
- **Add an agent** — Create `agents/<name>/` with a `jobs/` subfolder, add a cron entry in `agent-job/CRONS.json` pointing to the prompt file, update `agents/CLAUDE.md` to document it, update root `CLAUDE.md` to reflect the new agent.
47
+ **Add an agent** — Create `agents/<name>/` with an optional `CLAUDE.md`, `SYSTEM.md`, and `skills/` directory. Add a cron entry in `agent-job/CRONS.json` if it runs on a schedule. Update `agents/CLAUDE.md` and root `CLAUDE.md`.
41
48
 
42
49
  **Remove an agent** — Delete the `agents/<name>/` folder, remove its cron entries, update `agents/CLAUDE.md` and root `CLAUDE.md`.
43
50