thepopebot 1.2.71 → 1.2.72-beta.1
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 +18 -93
- package/bin/cli.js +4 -3
- package/package.json +1 -1
- package/setup/setup.mjs +5 -5
- package/templates/docker/job/entrypoint.sh +2 -2
package/README.md
CHANGED
|
@@ -206,6 +206,20 @@ If you've made custom changes to managed files (e.g., added extra steps to a Git
|
|
|
206
206
|
npx thepopebot init --no-managed
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
+
#### Template file conventions
|
|
210
|
+
|
|
211
|
+
The `templates/` directory contains files scaffolded into user projects by `thepopebot init`. Two naming conventions handle files that npm or AI tools would otherwise misinterpret:
|
|
212
|
+
|
|
213
|
+
**`.template` suffix** — Files ending in `.template` are scaffolded with the suffix stripped. This is used for files that npm mangles (`.gitignore`) or that AI tools would pick up as real project docs (`CLAUDE.md`).
|
|
214
|
+
|
|
215
|
+
| In `templates/` | Scaffolded as |
|
|
216
|
+
|-----------------|---------------|
|
|
217
|
+
| `.gitignore.template` | `.gitignore` |
|
|
218
|
+
| `CLAUDE.md.template` | `CLAUDE.md` |
|
|
219
|
+
| `api/CLAUDE.md.template` | `api/CLAUDE.md` |
|
|
220
|
+
|
|
221
|
+
**`CLAUDE.md` exclusion** — The scaffolding walker skips any file named `CLAUDE.md` (without the `.template` suffix). This is a safety net so a bare `CLAUDE.md` accidentally added to `templates/` never gets copied into user projects where AI tools would confuse it with real project instructions.
|
|
222
|
+
|
|
209
223
|
---
|
|
210
224
|
|
|
211
225
|
## CLI Commands
|
|
@@ -246,22 +260,6 @@ GitHub secrets use a prefix convention so the workflow can route them correctly:
|
|
|
246
260
|
|
|
247
261
|
---
|
|
248
262
|
|
|
249
|
-
## Template File Conventions
|
|
250
|
-
|
|
251
|
-
The `templates/` directory contains files scaffolded into user projects by `thepopebot init`. Two naming conventions handle files that npm or AI tools would otherwise misinterpret:
|
|
252
|
-
|
|
253
|
-
**`.template` suffix** — Files ending in `.template` are scaffolded with the suffix stripped. This is used for files that npm mangles (`.gitignore`) or that AI tools would pick up as real project docs (`CLAUDE.md`).
|
|
254
|
-
|
|
255
|
-
| In `templates/` | Scaffolded as |
|
|
256
|
-
|-----------------|---------------|
|
|
257
|
-
| `.gitignore.template` | `.gitignore` |
|
|
258
|
-
| `CLAUDE.md.template` | `CLAUDE.md` |
|
|
259
|
-
| `api/CLAUDE.md.template` | `api/CLAUDE.md` |
|
|
260
|
-
|
|
261
|
-
**`CLAUDE.md` exclusion** — The scaffolding walker skips any file named `CLAUDE.md` (without the `.template` suffix). This is a safety net so a bare `CLAUDE.md` accidentally added to `templates/` never gets copied into user projects where AI tools would confuse it with real project instructions.
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
263
|
## Security
|
|
266
264
|
|
|
267
265
|
thepopebot includes API key authentication, webhook secret validation (fail-closed), session encryption, secret filtering in the Docker agent, and auto-merge path restrictions. However, all software carries risk — thepopebot is provided as-is, and you are responsible for securing your own infrastructure. If you're running locally with a tunnel (ngrok, Cloudflare Tunnel, port forwarding), be aware that your dev server endpoints are publicly accessible with no rate limiting and no TLS on the local hop.
|
|
@@ -270,85 +268,11 @@ See [docs/SECURITY.md](docs/SECURITY.md) for full details on what's exposed, the
|
|
|
270
268
|
|
|
271
269
|
---
|
|
272
270
|
|
|
273
|
-
## Running Different Models
|
|
274
|
-
|
|
275
|
-
Every job your agent runs can use a different LLM. Use Claude for one cron, GPT-4o for another, or a local Ollama model for a third — just set `llm_provider` and `llm_model` on the entry.
|
|
276
|
-
|
|
277
|
-
### Per-job overrides
|
|
278
|
-
|
|
279
|
-
Add `llm_provider` and `llm_model` to any agent-type entry in `config/CRONS.json` or any action in `config/TRIGGERS.json`. This overrides the default for just that one job:
|
|
280
|
-
|
|
281
|
-
```json
|
|
282
|
-
{
|
|
283
|
-
"name": "Code review",
|
|
284
|
-
"schedule": "0 9 * * 1",
|
|
285
|
-
"type": "agent",
|
|
286
|
-
"job": "Review open PRs and leave comments",
|
|
287
|
-
"llm_provider": "openai",
|
|
288
|
-
"llm_model": "gpt-4o"
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
The matching API key must already exist as a GitHub secret (see the table below).
|
|
293
|
-
|
|
294
|
-
> **Using `custom` on individual crons:** `llm_provider` and `llm_model` travel with the job, but `OPENAI_BASE_URL`, `RUNS_ON`, and `CUSTOM_API_KEY` are repo-level settings — they must be set as GitHub variables/secrets even if your default provider is something else. See [Using the `custom` provider](#using-the-custom-provider) below.
|
|
295
|
-
|
|
296
|
-
### Providers
|
|
297
|
-
|
|
298
|
-
| Provider | What it is | Example model | GitHub secret needed |
|
|
299
|
-
|----------|------------|---------------|----------------------|
|
|
300
|
-
| `anthropic` | Anthropic (default) | `claude-sonnet-4-20250514` | `AGENT_ANTHROPIC_API_KEY` |
|
|
301
|
-
| `openai` | OpenAI | `gpt-4o` | `AGENT_OPENAI_API_KEY` |
|
|
302
|
-
| `google` | Google Gemini | `gemini-2.5-pro` | `AGENT_GOOGLE_API_KEY` |
|
|
303
|
-
| `custom` | Any OpenAI-compatible API (DeepSeek, Ollama, Together AI, etc.) | `deepseek-chat` | `AGENT_CUSTOM_API_KEY` *(if required — see below)* |
|
|
304
|
-
|
|
305
|
-
### Changing the default for all jobs
|
|
306
|
-
|
|
307
|
-
If you want every job to use the same non-Anthropic model, set `LLM_PROVIDER` and `LLM_MODEL` as GitHub repo variables:
|
|
308
|
-
|
|
309
|
-
```bash
|
|
310
|
-
npx thepopebot set-var LLM_PROVIDER openai
|
|
311
|
-
npx thepopebot set-var LLM_MODEL gpt-4o
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
Per-job overrides in `CRONS.json` and `TRIGGERS.json` still take priority over these defaults.
|
|
315
|
-
|
|
316
|
-
### Using the `custom` provider
|
|
317
|
-
|
|
318
|
-
`custom` means "any server that speaks the OpenAI chat-completions API." This covers two cases: cloud APIs and local models.
|
|
319
|
-
|
|
320
|
-
#### Cloud custom (DeepSeek, Together AI, Fireworks, etc.)
|
|
321
|
-
|
|
322
|
-
Point at the provider's endpoint and add your API key:
|
|
323
|
-
|
|
324
|
-
```bash
|
|
325
|
-
npx thepopebot set-var LLM_PROVIDER custom
|
|
326
|
-
npx thepopebot set-var LLM_MODEL deepseek-chat
|
|
327
|
-
npx thepopebot set-var OPENAI_BASE_URL https://api.deepseek.com/v1
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
Then set the API key as a GitHub secret:
|
|
331
|
-
|
|
332
|
-
```bash
|
|
333
|
-
npx thepopebot set-agent-secret CUSTOM_API_KEY sk-...
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
Cloud custom APIs are reachable from any runner — no other changes needed.
|
|
337
|
-
|
|
338
|
-
#### Local custom (Ollama, LM Studio, vLLM, etc.)
|
|
339
|
-
|
|
340
|
-
For a model running on your own machine you need a [self-hosted runner](https://docs.github.com/en/actions/hosting-your-own-runners) so the job executes on your hardware:
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
npx thepopebot set-var RUNS_ON self-hosted
|
|
344
|
-
npx thepopebot set-var LLM_PROVIDER custom
|
|
345
|
-
npx thepopebot set-var LLM_MODEL qwen3:8b
|
|
346
|
-
npx thepopebot set-var OPENAI_BASE_URL http://host.docker.internal:11434/v1
|
|
347
|
-
```
|
|
271
|
+
## Running Different Models
|
|
348
272
|
|
|
349
|
-
|
|
273
|
+
The Event Handler (chat, Telegram, webhooks) and Jobs (Docker agent) are two independent layers — each can run a different LLM. Use Claude for interactive chat and a cheaper or local model for long-running jobs, mix providers per cron entry, or run everything on a single model.
|
|
350
274
|
|
|
351
|
-
|
|
275
|
+
See [docs/RUNNING_DIFFERENT_MODELS.md](docs/RUNNING_DIFFERENT_MODELS.md) for the full guide: Event Handler config, job defaults, per-job overrides, provider table, and custom provider setup.
|
|
352
276
|
|
|
353
277
|
---
|
|
354
278
|
|
|
@@ -360,6 +284,7 @@ Most local servers don't need an API key. If yours does, set `AGENT_CUSTOM_API_K
|
|
|
360
284
|
| [Configuration](docs/CONFIGURATION.md) | Environment variables, GitHub secrets, repo variables, ngrok, Telegram setup |
|
|
361
285
|
| [Customization](docs/CUSTOMIZATION.md) | Personality, skills, operating system files, using your bot, security details |
|
|
362
286
|
| [Chat Integrations](docs/CHAT_INTEGRATIONS.md) | Web chat, Telegram, adding new channels |
|
|
287
|
+
| [Running Different Models](docs/RUNNING_DIFFERENT_MODELS.md) | Event Handler vs job model config, per-job overrides, providers, custom provider |
|
|
363
288
|
| [Auto-Merge](docs/AUTO_MERGE.md) | Auto-merge controls, ALLOWED_PATHS configuration |
|
|
364
289
|
| [Deployment](docs/DEPLOYMENT.md) | VPS setup, Docker Compose, HTTPS with Let's Encrypt |
|
|
365
290
|
| [How to Use Pi](docs/HOW_TO_USE_PI.md) | Guide to the Pi coding agent |
|
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { execSync } from 'child_process';
|
|
3
|
+
import { execSync, execFileSync } from 'child_process';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
@@ -18,6 +18,7 @@ const args = process.argv.slice(3);
|
|
|
18
18
|
const MANAGED_PATHS = [
|
|
19
19
|
'.github/workflows/',
|
|
20
20
|
'docker/event-handler/',
|
|
21
|
+
'docker/job/',
|
|
21
22
|
'docker-compose.yml',
|
|
22
23
|
'.dockerignore',
|
|
23
24
|
];
|
|
@@ -400,7 +401,7 @@ function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
|
400
401
|
function setup() {
|
|
401
402
|
const setupScript = path.join(__dirname, '..', 'setup', 'setup.mjs');
|
|
402
403
|
try {
|
|
403
|
-
|
|
404
|
+
execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
|
|
404
405
|
} catch {
|
|
405
406
|
process.exit(1);
|
|
406
407
|
}
|
|
@@ -409,7 +410,7 @@ function setup() {
|
|
|
409
410
|
function setupTelegram() {
|
|
410
411
|
const setupScript = path.join(__dirname, '..', 'setup', 'setup-telegram.mjs');
|
|
411
412
|
try {
|
|
412
|
-
|
|
413
|
+
execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
|
|
413
414
|
} catch {
|
|
414
415
|
process.exit(1);
|
|
415
416
|
}
|
package/package.json
CHANGED
package/setup/setup.mjs
CHANGED
|
@@ -237,12 +237,12 @@ async function main() {
|
|
|
237
237
|
|
|
238
238
|
try {
|
|
239
239
|
const url = remoteUrl.replace(/\/$/, '').replace(/\.git$/, '') + '.git';
|
|
240
|
-
execSync(`git remote add origin ${url}`, { stdio: 'ignore' });
|
|
240
|
+
execSync(`git remote add origin "${url}"`, { stdio: 'ignore' });
|
|
241
241
|
remoteAdded = true;
|
|
242
242
|
} catch {
|
|
243
243
|
try {
|
|
244
244
|
const url = remoteUrl.replace(/\/$/, '').replace(/\.git$/, '') + '.git';
|
|
245
|
-
execSync(`git remote set-url origin ${url}`, { stdio: 'ignore' });
|
|
245
|
+
execSync(`git remote set-url origin "${url}"`, { stdio: 'ignore' });
|
|
246
246
|
remoteAdded = true;
|
|
247
247
|
} catch {
|
|
248
248
|
clack.log.error('Failed to set remote. Try again.');
|
|
@@ -355,7 +355,7 @@ async function main() {
|
|
|
355
355
|
let pushed = false;
|
|
356
356
|
while (!pushed) {
|
|
357
357
|
const authedUrl = remote.replace('https://github.com/', `https://x-access-token:${pat}@github.com/`);
|
|
358
|
-
execSync(`git remote set-url origin ${authedUrl}`, { stdio: 'ignore' });
|
|
358
|
+
execSync(`git remote set-url origin "${authedUrl}"`, { stdio: 'ignore' });
|
|
359
359
|
|
|
360
360
|
const pushSpinner = clack.spinner();
|
|
361
361
|
pushSpinner.start('Pushing to GitHub...');
|
|
@@ -368,7 +368,7 @@ async function main() {
|
|
|
368
368
|
pushSpinner.stop('Failed to push');
|
|
369
369
|
const output = (err.stdout || '') + (err.stderr || '');
|
|
370
370
|
if (output) clack.log.error(output.trim());
|
|
371
|
-
execSync(`git remote set-url origin ${remote}`, { stdio: 'ignore' });
|
|
371
|
+
execSync(`git remote set-url origin "${remote}"`, { stdio: 'ignore' });
|
|
372
372
|
clack.log.info('Your PAT may not have write access to this repository.');
|
|
373
373
|
pat = await promptForPAT();
|
|
374
374
|
collected.GH_TOKEN = pat;
|
|
@@ -376,7 +376,7 @@ async function main() {
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
// Reset remote URL back to clean HTTPS (no token embedded)
|
|
379
|
-
execSync(`git remote set-url origin ${remote}`, { stdio: 'ignore' });
|
|
379
|
+
execSync(`git remote set-url origin "${remote}"`, { stdio: 'ignore' });
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
|
|
@@ -12,13 +12,13 @@ echo "Job ID: ${JOB_ID}"
|
|
|
12
12
|
# Export SECRETS (JSON) as flat env vars (GH_TOKEN, ANTHROPIC_API_KEY, etc.)
|
|
13
13
|
# These are filtered from LLM's bash subprocess by env-sanitizer extension
|
|
14
14
|
if [ -n "$SECRETS" ]; then
|
|
15
|
-
eval $(echo "$SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\
|
|
15
|
+
eval $(echo "$SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
|
|
16
16
|
fi
|
|
17
17
|
|
|
18
18
|
# Export LLM_SECRETS (JSON) as flat env vars
|
|
19
19
|
# These are NOT filtered - LLM can access these (browser logins, skill API keys, etc.)
|
|
20
20
|
if [ -n "$LLM_SECRETS" ]; then
|
|
21
|
-
eval $(echo "$LLM_SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\
|
|
21
|
+
eval $(echo "$LLM_SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
|
|
22
22
|
fi
|
|
23
23
|
|
|
24
24
|
# Git setup - derive identity from GitHub token
|