thepopebot 1.2.20 → 1.2.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/api/CLAUDE.md +19 -0
- package/api/index.js +1 -1
- package/package.json +1 -1
- package/setup/setup.mjs +15 -79
- package/templates/.github/workflows/redeploy-event-handler.yml +37 -0
- package/templates/api/CLAUDE.md +19 -0
- package/templates/docker/event-handler/Dockerfile +11 -2
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker-compose.yml +14 -6
package/api/CLAUDE.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# /api — External API Routes
|
|
2
|
+
|
|
3
|
+
This directory contains the route handlers for all `/api/*` endpoints. These routes are for **external callers only** — GitHub Actions, Telegram, cURL, third-party webhooks.
|
|
4
|
+
|
|
5
|
+
## Auth
|
|
6
|
+
|
|
7
|
+
All routes (except `/telegram/webhook` and `/github/webhook`, which use their own webhook secrets) require a valid API key passed via the `x-api-key` header. API keys are stored in the SQLite database and managed through the admin UI — they are NOT environment variables.
|
|
8
|
+
|
|
9
|
+
Auth flow: `x-api-key` header -> `verifyApiKey()` -> database lookup (hashed, timing-safe comparison).
|
|
10
|
+
|
|
11
|
+
## Do NOT use these routes for browser UI
|
|
12
|
+
|
|
13
|
+
Browser-facing features must use **Server Actions** (`'use server'` functions) with `requireAuth()` session checks — never `/api` fetch calls. The only exception is chat streaming, which has its own dedicated route at `/stream/chat` with session auth.
|
|
14
|
+
|
|
15
|
+
| Caller | Mechanism | Auth |
|
|
16
|
+
|--------|-----------|------|
|
|
17
|
+
| External (cURL, GitHub Actions, Telegram) | `/api` route | `x-api-key` header |
|
|
18
|
+
| Browser UI (data/mutations) | Server Action | `requireAuth()` session |
|
|
19
|
+
| Browser UI (chat streaming) | `/stream/chat` | `auth()` session |
|
package/api/index.js
CHANGED
|
@@ -30,7 +30,7 @@ function getFireTriggers() {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// Routes that have their own authentication
|
|
33
|
-
const PUBLIC_ROUTES = ['/telegram/webhook', '/github/webhook'];
|
|
33
|
+
const PUBLIC_ROUTES = ['/telegram/webhook', '/github/webhook', '/ping'];
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Timing-safe string comparison.
|
package/package.json
CHANGED
package/setup/setup.mjs
CHANGED
|
@@ -19,8 +19,6 @@ import {
|
|
|
19
19
|
promptForOptionalKey,
|
|
20
20
|
promptForCustomProvider,
|
|
21
21
|
promptForBraveKey,
|
|
22
|
-
promptForTelegramToken,
|
|
23
|
-
generateTelegramWebhookSecret,
|
|
24
22
|
confirm,
|
|
25
23
|
pressEnter,
|
|
26
24
|
maskSecret,
|
|
@@ -41,8 +39,6 @@ import {
|
|
|
41
39
|
encodeLlmSecretsBase64,
|
|
42
40
|
updateEnvVariable,
|
|
43
41
|
} from './lib/auth.mjs';
|
|
44
|
-
import { setTelegramWebhook, validateBotToken, generateVerificationCode } from './lib/telegram.mjs';
|
|
45
|
-
import { runVerificationFlow, verifyRestart } from './lib/telegram-verify.mjs';
|
|
46
42
|
|
|
47
43
|
const logo = `
|
|
48
44
|
_____ _ ____ ____ _
|
|
@@ -81,7 +77,7 @@ function printInfo(message) {
|
|
|
81
77
|
async function main() {
|
|
82
78
|
printHeader();
|
|
83
79
|
|
|
84
|
-
const TOTAL_STEPS =
|
|
80
|
+
const TOTAL_STEPS = 7;
|
|
85
81
|
let currentStep = 0;
|
|
86
82
|
|
|
87
83
|
// Collected values
|
|
@@ -90,8 +86,6 @@ async function main() {
|
|
|
90
86
|
let agentModel = null;
|
|
91
87
|
const collectedKeys = {};
|
|
92
88
|
let braveKey = null;
|
|
93
|
-
let telegramToken = null;
|
|
94
|
-
let telegramWebhookSecret = null;
|
|
95
89
|
let webhookSecret = null;
|
|
96
90
|
let owner = null;
|
|
97
91
|
let repo = null;
|
|
@@ -488,36 +482,21 @@ async function main() {
|
|
|
488
482
|
}
|
|
489
483
|
}
|
|
490
484
|
|
|
491
|
-
//
|
|
492
|
-
|
|
485
|
+
// Chat Interfaces (informational)
|
|
486
|
+
console.log(chalk.dim('\n Your agent includes a web chat interface at your APP_URL.'));
|
|
487
|
+
console.log(chalk.dim(' You can also connect additional chat interfaces:\n'));
|
|
488
|
+
console.log(chalk.dim(' \u2022 Telegram: ') + chalk.cyan('npm run setup-telegram'));
|
|
489
|
+
console.log('');
|
|
493
490
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (telegramToken) {
|
|
497
|
-
const validateSpinner = ora('Validating bot token...').start();
|
|
498
|
-
const validation = await validateBotToken(telegramToken);
|
|
499
|
-
|
|
500
|
-
if (!validation.valid) {
|
|
501
|
-
validateSpinner.fail(`Invalid token: ${validation.error}`);
|
|
502
|
-
telegramToken = null;
|
|
503
|
-
} else {
|
|
504
|
-
validateSpinner.succeed(`Bot: @${validation.botInfo.username}`);
|
|
505
|
-
telegramWebhookSecret = await generateTelegramWebhookSecret();
|
|
506
|
-
}
|
|
507
|
-
} else {
|
|
508
|
-
printInfo('Skipped Telegram setup');
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Write .env file (now at project root, not event_handler/)
|
|
512
|
-
const telegramVerification = telegramToken ? generateVerificationCode() : null;
|
|
491
|
+
// Write .env file
|
|
513
492
|
const providerConfig = agentProvider !== 'custom' ? PROVIDERS[agentProvider] : null;
|
|
514
493
|
const providerEnvKey = providerConfig ? providerConfig.envKey : 'CUSTOM_API_KEY';
|
|
515
494
|
const envPath = writeEnvFile({
|
|
516
495
|
githubToken: pat,
|
|
517
496
|
githubOwner: owner,
|
|
518
497
|
githubRepo: repo,
|
|
519
|
-
telegramBotToken:
|
|
520
|
-
telegramWebhookSecret,
|
|
498
|
+
telegramBotToken: null,
|
|
499
|
+
telegramWebhookSecret: null,
|
|
521
500
|
ghWebhookSecret: webhookSecret,
|
|
522
501
|
llmProvider: agentProvider,
|
|
523
502
|
llmModel: agentModel,
|
|
@@ -525,11 +504,11 @@ async function main() {
|
|
|
525
504
|
providerApiKey: collectedKeys[providerEnvKey] || '',
|
|
526
505
|
openaiApiKey: collectedKeys['OPENAI_API_KEY'] || '',
|
|
527
506
|
telegramChatId: null,
|
|
528
|
-
telegramVerification,
|
|
507
|
+
telegramVerification: null,
|
|
529
508
|
});
|
|
530
509
|
printSuccess(`Created ${envPath}`);
|
|
531
510
|
|
|
532
|
-
// Step
|
|
511
|
+
// Step 5: Start Server
|
|
533
512
|
printStep(++currentStep, TOTAL_STEPS, 'Start Server');
|
|
534
513
|
|
|
535
514
|
console.log(chalk.bold(' Start the dev server in a new terminal window:\n'));
|
|
@@ -552,10 +531,10 @@ async function main() {
|
|
|
552
531
|
}
|
|
553
532
|
}
|
|
554
533
|
|
|
555
|
-
// Step
|
|
534
|
+
// Step 6: APP_URL
|
|
556
535
|
printStep(++currentStep, TOTAL_STEPS, 'App URL');
|
|
557
536
|
|
|
558
|
-
console.log(chalk.dim(' Your app needs a public URL for GitHub webhooks
|
|
537
|
+
console.log(chalk.dim(' Your app needs a public URL for GitHub webhooks.\n'));
|
|
559
538
|
console.log(chalk.dim(' Examples:'));
|
|
560
539
|
console.log(chalk.dim(' \u2022 ngrok: ') + chalk.cyan('https://abc123.ngrok.io'));
|
|
561
540
|
console.log(chalk.dim(' \u2022 VPS: ') + chalk.cyan('https://mybot.example.com'));
|
|
@@ -598,45 +577,6 @@ async function main() {
|
|
|
598
577
|
}
|
|
599
578
|
}
|
|
600
579
|
|
|
601
|
-
// Register Telegram webhook if configured
|
|
602
|
-
if (telegramToken) {
|
|
603
|
-
const webhookUrl = `${appUrl}/api/telegram/webhook`;
|
|
604
|
-
let tgWebhookSet = false;
|
|
605
|
-
while (!tgWebhookSet) {
|
|
606
|
-
const tgSpinner = ora('Registering Telegram webhook...').start();
|
|
607
|
-
const tgResult = await setTelegramWebhook(telegramToken, webhookUrl, telegramWebhookSecret);
|
|
608
|
-
if (tgResult.ok) {
|
|
609
|
-
tgSpinner.succeed(`Telegram webhook registered: ${webhookUrl}`);
|
|
610
|
-
tgWebhookSet = true;
|
|
611
|
-
} else {
|
|
612
|
-
tgSpinner.fail(`Failed: ${tgResult.description}`);
|
|
613
|
-
await pressEnter('Fix the issue, then press enter to retry');
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Chat ID verification
|
|
618
|
-
let chatVerified = false;
|
|
619
|
-
while (!chatVerified) {
|
|
620
|
-
const chatId = await runVerificationFlow(telegramVerification);
|
|
621
|
-
|
|
622
|
-
if (chatId) {
|
|
623
|
-
updateEnvVariable('TELEGRAM_CHAT_ID', chatId);
|
|
624
|
-
printSuccess(`Chat ID saved: ${chatId}`);
|
|
625
|
-
|
|
626
|
-
const verified = await verifyRestart(appUrl);
|
|
627
|
-
if (verified) {
|
|
628
|
-
printSuccess('Telegram bot is configured and working!');
|
|
629
|
-
} else {
|
|
630
|
-
printWarning('Could not verify bot. Check your configuration.');
|
|
631
|
-
}
|
|
632
|
-
chatVerified = true;
|
|
633
|
-
} else {
|
|
634
|
-
printWarning('Chat ID is required \u2014 the bot will not respond without it.');
|
|
635
|
-
await pressEnter('Fix the issue, then press enter to retry');
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
580
|
// Step 7: Summary
|
|
641
581
|
printStep(++currentStep, TOTAL_STEPS, 'Setup Complete!');
|
|
642
582
|
|
|
@@ -651,7 +591,6 @@ async function main() {
|
|
|
651
591
|
console.log(` ${chalk.dim(`${envVar}:`)} ${maskSecret(value)}`);
|
|
652
592
|
}
|
|
653
593
|
if (braveKey) console.log(` ${chalk.dim('Brave Search:')} ${maskSecret(braveKey)}`);
|
|
654
|
-
if (telegramToken) console.log(` ${chalk.dim('Telegram Bot:')} Webhook registered`);
|
|
655
594
|
|
|
656
595
|
console.log(chalk.bold('\n GitHub Secrets Set:\n'));
|
|
657
596
|
console.log(' \u2022 SECRETS');
|
|
@@ -667,11 +606,8 @@ async function main() {
|
|
|
667
606
|
|
|
668
607
|
console.log(chalk.bold.green('\n You\'re all set!\n'));
|
|
669
608
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
} else {
|
|
673
|
-
console.log(chalk.dim(' Use the /api/create-job endpoint to create jobs.'));
|
|
674
|
-
}
|
|
609
|
+
console.log(chalk.dim(' Chat with your agent at ') + chalk.cyan(appUrl));
|
|
610
|
+
console.log(chalk.dim(' To connect Telegram: ') + chalk.cyan('npm run setup-telegram'));
|
|
675
611
|
|
|
676
612
|
console.log('\n');
|
|
677
613
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Deploy Event Handler
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
concurrency:
|
|
8
|
+
group: deploy
|
|
9
|
+
cancel-in-progress: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
deploy:
|
|
13
|
+
runs-on: self-hosted
|
|
14
|
+
steps:
|
|
15
|
+
- name: Deploy event handler
|
|
16
|
+
run: |
|
|
17
|
+
docker exec thepopebot-event-handler bash -c '
|
|
18
|
+
# Authenticate git via GitHub CLI
|
|
19
|
+
echo "${GH_TOKEN}" | gh auth login --with-token
|
|
20
|
+
gh auth setup-git
|
|
21
|
+
|
|
22
|
+
# Check if there are code changes (not just logs)
|
|
23
|
+
git fetch origin main
|
|
24
|
+
CHANGED=$(git diff --name-only HEAD origin/main)
|
|
25
|
+
if ! echo "$CHANGED" | grep -qv "^logs/"; then
|
|
26
|
+
echo "Logs-only change, skipping rebuild"
|
|
27
|
+
git reset --hard origin/main
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Pull, build, reload
|
|
32
|
+
git reset --hard origin/main
|
|
33
|
+
npm install --omit=dev
|
|
34
|
+
npm run build
|
|
35
|
+
echo "Rebuild complete, reloading Next.js..."
|
|
36
|
+
npx pm2 reload all
|
|
37
|
+
'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# /api — External API Routes
|
|
2
|
+
|
|
3
|
+
This directory contains the route handlers for all `/api/*` endpoints. These routes are for **external callers only** — GitHub Actions, Telegram, cURL, third-party webhooks.
|
|
4
|
+
|
|
5
|
+
## Auth
|
|
6
|
+
|
|
7
|
+
All routes (except `/telegram/webhook` and `/github/webhook`, which use their own webhook secrets) require a valid API key passed via the `x-api-key` header. API keys are stored in the SQLite database and managed through the admin UI — they are NOT environment variables.
|
|
8
|
+
|
|
9
|
+
Auth flow: `x-api-key` header -> `verifyApiKey()` -> database lookup (hashed, timing-safe comparison).
|
|
10
|
+
|
|
11
|
+
## Do NOT use these routes for browser UI
|
|
12
|
+
|
|
13
|
+
Browser-facing features must use **Server Actions** (`'use server'` functions) with `requireAuth()` session checks — never `/api` fetch calls. The only exception is chat streaming, which has its own dedicated route at `/stream/chat` with session auth.
|
|
14
|
+
|
|
15
|
+
| Caller | Mechanism | Auth |
|
|
16
|
+
|--------|-----------|------|
|
|
17
|
+
| External (cURL, GitHub Actions, Telegram) | `/api` route | `x-api-key` header |
|
|
18
|
+
| Browser UI (data/mutations) | Server Action | `requireAuth()` session |
|
|
19
|
+
| Browser UI (chat streaming) | `/stream/chat` | `auth()` session |
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
FROM node:22-bookworm-slim
|
|
2
2
|
|
|
3
|
-
RUN apt-get update && apt-get install -y curl python3 make g++ &&
|
|
3
|
+
RUN apt-get update && apt-get install -y curl git python3 make g++ && \
|
|
4
|
+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
5
|
+
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
|
6
|
+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
7
|
+
| tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \
|
|
8
|
+
apt-get update && apt-get install -y gh && \
|
|
9
|
+
rm -rf /var/lib/apt/lists/*
|
|
10
|
+
RUN npm install -g pm2
|
|
4
11
|
|
|
5
12
|
WORKDIR /app
|
|
6
13
|
COPY package.json package-lock.json* ./
|
|
7
14
|
RUN npm install --omit=dev && \
|
|
8
15
|
npm install --no-save thepopebot@$(node -p "require('./package.json').version")
|
|
9
16
|
|
|
17
|
+
COPY ecosystem.config.cjs ./
|
|
18
|
+
|
|
10
19
|
EXPOSE 80
|
|
11
|
-
CMD ["
|
|
20
|
+
CMD ["pm2-runtime", "ecosystem.config.cjs"]
|
|
@@ -21,7 +21,8 @@ services:
|
|
|
21
21
|
- traefik_certs:/letsencrypt
|
|
22
22
|
restart: unless-stopped
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
event-handler:
|
|
25
|
+
container_name: thepopebot-event-handler
|
|
25
26
|
image: ${EVENT_HANDLER_IMAGE_URL:-stephengpope/thepopebot:event-handler-${THEPOPEBOT_VERSION:-latest}}
|
|
26
27
|
volumes:
|
|
27
28
|
- .:/app
|
|
@@ -29,12 +30,19 @@ services:
|
|
|
29
30
|
labels:
|
|
30
31
|
- traefik.enable=true
|
|
31
32
|
# Set APP_HOSTNAME in .env to the domain from APP_URL (e.g., mybot.example.com)
|
|
32
|
-
- traefik.http.routers.
|
|
33
|
-
- traefik.http.routers.
|
|
34
|
-
- traefik.http.services.
|
|
33
|
+
- traefik.http.routers.event-handler.rule=Host(`${APP_HOSTNAME}`)
|
|
34
|
+
- traefik.http.routers.event-handler.entrypoints=web
|
|
35
|
+
- traefik.http.services.event-handler.loadbalancer.server.port=80
|
|
35
36
|
## Uncomment the following lines to enable TLS via Let's Encrypt:
|
|
36
|
-
# - traefik.http.routers.
|
|
37
|
-
# - traefik.http.routers.
|
|
37
|
+
# - traefik.http.routers.event-handler.entrypoints=websecure
|
|
38
|
+
# - traefik.http.routers.event-handler.tls.certresolver=letsencrypt
|
|
39
|
+
stop_grace_period: 120s
|
|
40
|
+
healthcheck:
|
|
41
|
+
test: ["CMD", "curl", "-f", "http://localhost:80/api/ping"]
|
|
42
|
+
interval: 30s
|
|
43
|
+
timeout: 10s
|
|
44
|
+
retries: 3
|
|
45
|
+
start_period: 30s
|
|
38
46
|
restart: unless-stopped
|
|
39
47
|
|
|
40
48
|
runner:
|