thepopebot 1.2.76-beta.24 → 1.2.76-beta.26

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.
@@ -1,19 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Build all Docker images locally (in parallel).
4
+ * Build all Docker images locally.
5
5
  *
6
6
  * Usage:
7
- * npm run docker:build # build all images in parallel
8
- * npm run docker:build -- --image event-handler # build one image
7
+ * npm run docker:build # build everything
8
+ * npm run docker:build -- --image event-handler # build one (deps built first)
9
9
  *
10
10
  * Reads the version from package.json and tags each image as:
11
11
  * stephengpope/thepopebot:{image}-{version}
12
12
  *
13
- * The coding-agent images use a two-stage build: a shared base image
14
- * (Dockerfile) is built first, then each agent-specific Dockerfile
15
- * extends it. The base is tagged as coding-agent-base-{version} and
16
- * is NOT pushed it's only used locally as a build dependency.
13
+ * Image hierarchy:
14
+ *
15
+ * thepopebot-base ← Ubuntu + Node + locale + Chromium + playwright + user
16
+ * ├── coding-agent-base ← + tmux, ttyd, scripts, entrypoint
17
+ * │ ├── coding-agent-claude-code ← + per-agent CLI
18
+ * │ ├── coding-agent-pi-coding-agent
19
+ * │ └── ... (one per agent)
20
+ * └── event-handler ← + pm2, gosu, Next.js, server.js
21
+ *
22
+ * Build order:
23
+ * 1. thepopebot-base
24
+ * 2. coding-agent-base + event-handler in parallel
25
+ * 3. all coding-agent variants in parallel
26
+ *
27
+ * Base images are tagged both versioned and unversioned (no version) so child
28
+ * Dockerfiles can `FROM thepopebot-base` / `FROM coding-agent-base` without a
29
+ * build-arg for local development.
17
30
  */
18
31
 
19
32
  import { spawn } from 'child_process';
@@ -28,14 +41,21 @@ const pkg = JSON.parse(readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
28
41
  const VERSION = pkg.version;
29
42
  const REPO = 'stephengpope/thepopebot';
30
43
 
31
- // Base image built first — all coding-agent images depend on it
32
- const BASE_IMAGE = {
44
+ // Built first — everything depends on this.
45
+ const THEPOPEBOT_BASE = {
46
+ name: 'thepopebot-base',
47
+ context: 'docker/base',
48
+ dockerfile: 'docker/base/Dockerfile',
49
+ };
50
+
51
+ // Built second — depends on thepopebot-base.
52
+ const CODING_AGENT_BASE = {
33
53
  name: 'coding-agent-base',
34
54
  context: 'docker/coding-agent',
35
55
  dockerfile: 'docker/coding-agent/Dockerfile',
36
56
  };
37
57
 
38
- // Agent-specific images (extend the base)
58
+ // Built third depend on coding-agent-base.
39
59
  const CODING_AGENTS = [
40
60
  {
41
61
  name: 'coding-agent-claude-code',
@@ -69,16 +89,14 @@ const CODING_AGENTS = [
69
89
  },
70
90
  ];
71
91
 
72
- // Non-coding-agent images (independent, built in parallel)
73
- const OTHER_IMAGES = [
74
- {
75
- name: 'event-handler',
76
- context: '.',
77
- dockerfile: 'docker/event-handler/Dockerfile',
78
- },
79
- ];
92
+ // Built second depends on thepopebot-base. Built in parallel with coding-agent-base.
93
+ const EVENT_HANDLER = {
94
+ name: 'event-handler',
95
+ context: '.',
96
+ dockerfile: 'docker/event-handler/Dockerfile',
97
+ };
80
98
 
81
- const ALL_IMAGES = [BASE_IMAGE, ...CODING_AGENTS, ...OTHER_IMAGES];
99
+ const ALL_IMAGES = [THEPOPEBOT_BASE, CODING_AGENT_BASE, ...CODING_AGENTS, EVENT_HANDLER];
82
100
 
83
101
  // Parse --image flag
84
102
  const filterArg = process.argv.find((_, i, a) => a[i - 1] === '--image');
@@ -101,11 +119,14 @@ function buildImage(img) {
101
119
  console.log(` ${label} building — ${tag}`);
102
120
 
103
121
  return new Promise((resolve, reject) => {
104
- // Tag base image as both versioned and unversioned (agent Dockerfiles use FROM coding-agent-base)
105
122
  const args = ['build', '-t', tag, '-f', dockerfile];
106
- if (img.name === 'coding-agent-base') {
107
- args.push('-t', 'coding-agent-base');
123
+
124
+ // Base images get an unversioned tag too so child Dockerfiles can
125
+ // `FROM thepopebot-base` / `FROM coding-agent-base` without a build-arg.
126
+ if (img.name === 'thepopebot-base' || img.name === 'coding-agent-base') {
127
+ args.push('-t', img.name);
108
128
  }
129
+
109
130
  args.push(context);
110
131
 
111
132
  const proc = spawn(
@@ -172,50 +193,56 @@ function buildImage(img) {
172
193
  });
173
194
  }
174
195
 
175
- // Build logic: base first, then agents + others in parallel
176
196
  async function run() {
177
197
  if (filterArg) {
178
- // Single image build
179
- if (filterArg === BASE_IMAGE.name) {
198
+ // Single image build — build dependency chain too.
199
+ if (filterArg === THEPOPEBOT_BASE.name) {
180
200
  console.log(`Building 1 image — version ${VERSION}\n`);
181
- await buildImage(BASE_IMAGE);
182
- } else {
183
- const isCodingAgent = CODING_AGENTS.some(img => img.name === filterArg);
184
- if (isCodingAgent) {
185
- // Need base first
186
- console.log(`Building base + 1 agent image — version ${VERSION}\n`);
187
- await buildImage(BASE_IMAGE);
188
- const agent = CODING_AGENTS.find(img => img.name === filterArg);
189
- await buildImage(agent);
190
- } else {
191
- console.log(`Building 1 image — version ${VERSION}\n`);
192
- const img = OTHER_IMAGES.find(img => img.name === filterArg);
193
- await buildImage(img);
194
- }
201
+ await buildImage(THEPOPEBOT_BASE);
202
+ } else if (filterArg === CODING_AGENT_BASE.name) {
203
+ console.log(`Building 2 images version ${VERSION}\n`);
204
+ await buildImage(THEPOPEBOT_BASE);
205
+ await buildImage(CODING_AGENT_BASE);
206
+ } else if (filterArg === EVENT_HANDLER.name) {
207
+ console.log(`Building 2 images — version ${VERSION}\n`);
208
+ await buildImage(THEPOPEBOT_BASE);
209
+ await buildImage(EVENT_HANDLER);
210
+ } else if (CODING_AGENTS.some(img => img.name === filterArg)) {
211
+ console.log(`Building 3 images — version ${VERSION}\n`);
212
+ await buildImage(THEPOPEBOT_BASE);
213
+ await buildImage(CODING_AGENT_BASE);
214
+ const agent = CODING_AGENTS.find(img => img.name === filterArg);
215
+ await buildImage(agent);
195
216
  }
196
- console.log('\n1/1 images built successfully.');
217
+ console.log('\ndone.');
197
218
  return;
198
219
  }
199
220
 
200
- // Full build: base first, then everything else in parallel
221
+ // Full build: thepopebot-base (coding-agent-base + event-handler in parallel) → variants
201
222
  const totalCount = ALL_IMAGES.length;
202
- console.log(`Building ${totalCount} images (base first, then parallel) — version ${VERSION}\n`);
223
+ console.log(`Building ${totalCount} images — version ${VERSION}\n`);
203
224
 
204
- // Step 1: Build base
205
- await buildImage(BASE_IMAGE);
225
+ // Step 1: thepopebot-base
226
+ await buildImage(THEPOPEBOT_BASE);
206
227
 
207
- // Step 2: Build all agents + other images in parallel
208
- const parallel = [...CODING_AGENTS, ...OTHER_IMAGES];
209
- const results = await Promise.allSettled(parallel.map(buildImage));
228
+ // Step 2: coding-agent-base and event-handler in parallel (both extend thepopebot-base)
229
+ const tier2 = await Promise.allSettled([CODING_AGENT_BASE, EVENT_HANDLER].map(buildImage));
230
+ const tier2Failed = tier2.filter(r => r.status === 'rejected');
231
+ if (tier2Failed.length > 0) {
232
+ console.error(`Tier 2 failed: ${tier2Failed.map(r => r.reason.message).join(', ')}`);
233
+ process.exit(1);
234
+ }
210
235
 
211
- const failed = results.filter((r) => r.status === 'rejected');
212
- const succeeded = results.filter((r) => r.status === 'fulfilled');
236
+ // Step 3: all coding-agent variants in parallel
237
+ const tier3 = await Promise.allSettled(CODING_AGENTS.map(buildImage));
238
+ const tier3Failed = tier3.filter(r => r.status === 'rejected');
239
+ const tier3Succeeded = tier3.filter(r => r.status === 'fulfilled');
213
240
 
214
- // +1 for the base image
215
- console.log(`\n${succeeded.length + 1}/${totalCount} images built successfully.`);
241
+ const succeededCount = 1 + 2 + tier3Succeeded.length;
242
+ console.log(`\n${succeededCount}/${totalCount} images built successfully.`);
216
243
 
217
- if (failed.length > 0) {
218
- console.error(`${failed.length} failed: ${failed.map((r) => r.reason.message).join(', ')}`);
244
+ if (tier3Failed.length > 0) {
245
+ console.error(`${tier3Failed.length} failed: ${tier3Failed.map(r => r.reason.message).join(', ')}`);
219
246
  process.exit(1);
220
247
  }
221
248
  }
package/bin/sync.js CHANGED
@@ -248,6 +248,19 @@ function mirrorTemplates(projectPath) {
248
248
  }
249
249
  }
250
250
 
251
+ /**
252
+ * Build thepopebot-base locally so the event-handler Dockerfile's
253
+ * `FROM ${BASE_IMAGE}` (default: thepopebot-base) resolves. Cached layers
254
+ * make this near-instant when nothing in docker/base changed.
255
+ */
256
+ function buildBaseImage() {
257
+ console.log('\n Building thepopebot-base image...');
258
+ const baseContext = path.join(PACKAGE_DIR, 'docker', 'base');
259
+ execSync(`docker build -t thepopebot-base -f ${path.join(baseContext, 'Dockerfile')} ${baseContext}`, {
260
+ stdio: 'inherit',
261
+ });
262
+ }
263
+
251
264
  function buildDockerImage(projectPath) {
252
265
  console.log('\n Building Docker event handler image...');
253
266
 
@@ -431,7 +444,11 @@ export async function sync(projectPath) {
431
444
  console.log('\n Installing package on host...');
432
445
  execSync(`npm install --no-save ${tarballDest}`, { stdio: 'inherit', cwd: projectPath });
433
446
 
434
- // 5. Build Docker image with patched Dockerfile (includes Next.js build)
447
+ // 5. Build thepopebot-base (event-handler Dockerfile FROMs it).
448
+ // Cached layers — fast unless docker/base/Dockerfile changed.
449
+ buildBaseImage();
450
+
451
+ // 6. Build event-handler image with patched Dockerfile (includes Next.js build)
435
452
  buildDockerImage(projectPath);
436
453
 
437
454
  // 6. Restart container with new image
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.76-beta.24",
3
+ "version": "1.2.76-beta.26",
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": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agent-job-tools
3
- description: Use when the user wants to run an agent job in the background (also "background job", "spawn a job", "kick off a job") — creates and monitors background agent jobs that run in their own container. Also provides access to agent-job secrets (list keys, get values; OAuth credentials are auto-refreshed).
3
+ description: Use when you need to access agent secrets, API keys, or create and manage background jobs. Supports listing api keys, OAuth credentials (auto-refreshed). Also handles requests to "create a background job," "spawn a job," or "kick off an agent job."
4
4
  ---
5
5
 
6
6
  ## Usage
@@ -1,23 +0,0 @@
1
- ---
2
- name: agent-job-secrets
3
- description: List and retrieve agent secrets. Plain secrets are also available as env vars. OAuth credentials are auto-refreshed on every get call.
4
- ---
5
-
6
- ## Usage
7
-
8
- ```bash
9
- # List available secret keys (fetches current list from server)
10
- node skills/agent-job-secrets/agent-job-secrets.js
11
-
12
- # Get a secret value (OAuth credentials are auto-refreshed)
13
- node skills/agent-job-secrets/agent-job-secrets.js get MY_CREDENTIALS
14
- ```
15
-
16
- ## Notes
17
-
18
- - `AGENT_JOB_TOKEN` and `APP_URL` are injected automatically — no setup required
19
- - Plain (non-OAuth) secrets are also available directly as env vars (e.g. `echo $MY_KEY`)
20
- - OAuth credentials must be fetched via `get` — they are not available as env vars
21
- - `get` on an OAuth credential refreshes it server-side and returns a fresh access token
22
- - If a fetched credential stops working (expired token, 401 error), call `get` again to obtain a fresh one
23
- - `list` always fetches from the server, so it reflects secrets added after the container started
@@ -1,62 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const [cmd, key] = process.argv.slice(2);
4
-
5
- const apiKey = process.env.AGENT_JOB_TOKEN;
6
- const appUrl = process.env.APP_URL;
7
-
8
- // Default to list
9
- if (!cmd || cmd === 'list') {
10
- if (!apiKey || !appUrl) {
11
- console.log('No agent secrets available (missing AGENT_JOB_TOKEN or APP_URL).');
12
- process.exit(0);
13
- }
14
- const url = `${appUrl}/api/agent-job-list-secrets`;
15
- const res = await fetch(url, {
16
- headers: { 'x-api-key': apiKey },
17
- });
18
- if (!res.ok) {
19
- const body = await res.text();
20
- console.error(`GET ${url} → ${res.status} ${body}`);
21
- process.exit(1);
22
- }
23
- const json = await res.json();
24
- const secrets = json.secrets;
25
- if (!secrets || secrets.length === 0) {
26
- console.log('No agent secrets configured.');
27
- } else {
28
- console.log('Available secrets:');
29
- secrets.forEach(s => {
30
- const hint = s.secretType === 'oauth2' ? ' (OAuth — use get to fetch access token)'
31
- : s.secretType === 'oauth_token' ? ' (OAuth token — use get to fetch)'
32
- : '';
33
- console.log(` - ${s.key}${hint}`);
34
- });
35
- console.log('\nUse: agent-job-secrets get KEY_NAME');
36
- console.log('If a fetched value stops working, call get again for a fresh one.');
37
- }
38
- process.exit(0);
39
- }
40
-
41
- if (!apiKey) { console.error('AGENT_JOB_TOKEN not available'); process.exit(1); }
42
- if (!appUrl) { console.error('APP_URL not available'); process.exit(1); }
43
-
44
- if (cmd === 'get') {
45
- if (!key) { console.error('Usage: agent-job-secrets get KEY_NAME'); process.exit(1); }
46
- const url = `${appUrl}/api/get-agent-job-secret?key=${encodeURIComponent(key)}`;
47
- const res = await fetch(url, {
48
- headers: { 'x-api-key': apiKey },
49
- });
50
- if (!res.ok) {
51
- const body = await res.text();
52
- console.error(`GET ${url} → ${res.status} ${body}`);
53
- process.exit(1);
54
- }
55
- const json = await res.json();
56
- console.log(json.value);
57
- process.exit(0);
58
- }
59
-
60
- console.error(`Unknown command: ${cmd}`);
61
- console.error('Available commands: list, get');
62
- process.exit(1);