thepopebot 1.1.2 → 1.2.3
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/index.js +72 -165
- package/bin/cli.js +36 -6
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +6 -2
- package/config/index.js +2 -11
- package/config/instrumentation.js +17 -5
- package/lib/actions.js +7 -6
- package/lib/ai/agent.js +36 -0
- package/lib/ai/index.js +274 -0
- package/lib/ai/model.js +67 -0
- package/lib/ai/tools.js +49 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +45 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +30 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +146 -0
- package/lib/chat/actions.js +239 -0
- package/lib/chat/api.js +103 -0
- package/lib/chat/components/app-sidebar.js +161 -0
- package/lib/chat/components/app-sidebar.jsx +214 -0
- package/lib/chat/components/chat-header.js +9 -0
- package/lib/chat/components/chat-header.jsx +14 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +232 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +70 -0
- package/lib/chat/components/chat-page.jsx +89 -0
- package/lib/chat/components/chat.js +78 -0
- package/lib/chat/components/chat.jsx +91 -0
- package/lib/chat/components/chats-page.js +170 -0
- package/lib/chat/components/chats-page.jsx +203 -0
- package/lib/chat/components/crons-page.js +144 -0
- package/lib/chat/components/crons-page.jsx +204 -0
- package/lib/chat/components/greeting.js +11 -0
- package/lib/chat/components/greeting.jsx +14 -0
- package/lib/chat/components/icons.js +518 -0
- package/lib/chat/components/icons.jsx +482 -0
- package/lib/chat/components/index.js +19 -0
- package/lib/chat/components/message.js +66 -0
- package/lib/chat/components/message.jsx +92 -0
- package/lib/chat/components/messages.js +63 -0
- package/lib/chat/components/messages.jsx +72 -0
- package/lib/chat/components/notifications-page.js +54 -0
- package/lib/chat/components/notifications-page.jsx +83 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/settings-layout.js +37 -0
- package/lib/chat/components/settings-layout.jsx +51 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +54 -0
- package/lib/chat/components/sidebar-history-item.jsx +50 -0
- package/lib/chat/components/sidebar-history.js +92 -0
- package/lib/chat/components/sidebar-history.jsx +132 -0
- package/lib/chat/components/sidebar-user-nav.js +59 -0
- package/lib/chat/components/sidebar-user-nav.jsx +69 -0
- package/lib/chat/components/swarm-page.js +250 -0
- package/lib/chat/components/swarm-page.jsx +356 -0
- package/lib/chat/components/triggers-page.js +121 -0
- package/lib/chat/components/triggers-page.jsx +177 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +7 -8
- package/lib/db/api-keys.js +160 -0
- package/lib/db/chats.js +129 -0
- package/lib/db/index.js +106 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +51 -0
- package/lib/db/users.js +89 -0
- package/lib/paths.js +23 -17
- package/lib/tools/create-job.js +3 -3
- package/lib/tools/github.js +145 -1
- package/lib/tools/openai.js +1 -1
- package/lib/tools/telegram.js +4 -3
- package/lib/triggers.js +6 -7
- package/lib/utils/render-md.js +6 -6
- package/package.json +43 -6
- package/setup/lib/auth.mjs +22 -9
- package/setup/lib/prerequisites.mjs +10 -3
- package/setup/lib/telegram-verify.mjs +3 -16
- package/setup/setup-telegram.mjs +31 -62
- package/setup/setup.mjs +58 -98
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +18 -2
- package/templates/.github/workflows/auto-merge.yml +1 -1
- package/templates/.github/workflows/build-image.yml +6 -4
- package/templates/.github/workflows/notify-job-failed.yml +2 -2
- package/templates/.github/workflows/notify-pr-complete.yml +2 -2
- package/templates/.github/workflows/run-job.yml +24 -10
- package/templates/CLAUDE.md +5 -3
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/api/chat/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +81 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +7 -0
- package/templates/app/globals.css +66 -0
- package/templates/app/layout.js +9 -2
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +6 -30
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/triggers/page.js +7 -0
- package/templates/config/CRONS.json +2 -2
- package/templates/config/TRIGGERS.json +2 -2
- package/templates/docker/event_handler/Dockerfile +19 -0
- package/templates/docker/{entrypoint.sh → job/entrypoint.sh} +4 -4
- package/templates/docker/runner/Dockerfile +38 -0
- package/templates/docker/runner/entrypoint.sh +41 -0
- package/templates/docker-compose.yml +52 -0
- package/templates/instrumentation.js +6 -1
- package/templates/middleware.js +1 -0
- package/templates/postcss.config.mjs +5 -0
- package/lib/claude/conversation.js +0 -76
- package/lib/claude/index.js +0 -142
- package/lib/claude/tools.js +0 -54
- /package/templates/docker/{Dockerfile → job/Dockerfile} +0 -0
package/setup/setup.mjs
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
getPATCreationURL,
|
|
36
36
|
} from './lib/github.mjs';
|
|
37
37
|
import {
|
|
38
|
-
validateAnthropicKey,
|
|
39
38
|
writeEnvFile,
|
|
40
39
|
writeModelsJson,
|
|
41
40
|
encodeSecretsBase64,
|
|
@@ -171,22 +170,19 @@ async function main() {
|
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
172
|
|
|
173
|
+
// Initialize git repo if needed
|
|
174
|
+
if (!prereqs.git.initialized) {
|
|
175
|
+
const initSpinner = ora('Initializing git repo...').start();
|
|
176
|
+
execSync('git init', { stdio: 'ignore' });
|
|
177
|
+
initSpinner.succeed('Git repo initialized');
|
|
178
|
+
}
|
|
179
|
+
|
|
174
180
|
if (prereqs.git.remoteInfo) {
|
|
175
181
|
owner = prereqs.git.remoteInfo.owner;
|
|
176
182
|
repo = prereqs.git.remoteInfo.repo;
|
|
177
183
|
printSuccess(`Repository: ${owner}/${repo}`);
|
|
178
184
|
} else {
|
|
179
|
-
printWarning('No GitHub
|
|
180
|
-
|
|
181
|
-
// Initialize git repo if needed
|
|
182
|
-
try {
|
|
183
|
-
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
|
|
184
|
-
printSuccess('Git repo already initialized');
|
|
185
|
-
} catch {
|
|
186
|
-
const initSpinner = ora('Initializing git repo...').start();
|
|
187
|
-
execSync('git init', { stdio: 'ignore' });
|
|
188
|
-
initSpinner.succeed('Git repo initialized');
|
|
189
|
-
}
|
|
185
|
+
printWarning('No GitHub remote detected. We\'ll set one up.');
|
|
190
186
|
|
|
191
187
|
// Stage and commit
|
|
192
188
|
execSync('git add .', { stdio: 'ignore' });
|
|
@@ -288,7 +284,7 @@ async function main() {
|
|
|
288
284
|
|
|
289
285
|
console.log(chalk.dim(` Create a fine-grained PAT scoped to ${chalk.bold(`${owner}/${repo}`)} only:\n`));
|
|
290
286
|
console.log(chalk.dim(' \u2022 Repository access: Only select repositories \u2192 ') + chalk.bold(`${owner}/${repo}`));
|
|
291
|
-
console.log(chalk.dim(' \u2022 Actions: Read
|
|
287
|
+
console.log(chalk.dim(' \u2022 Actions: Read and write'));
|
|
292
288
|
console.log(chalk.dim(' \u2022 Contents: Read and write'));
|
|
293
289
|
console.log(chalk.dim(' \u2022 Metadata: Read-only (required, auto-selected)'));
|
|
294
290
|
console.log(chalk.dim(' \u2022 Pull requests: Read and write'));
|
|
@@ -347,7 +343,8 @@ async function main() {
|
|
|
347
343
|
const output = (err.stdout || '') + (err.stderr || '');
|
|
348
344
|
if (output) printError(output.trim());
|
|
349
345
|
execSync(`git remote set-url origin ${remote}`, { stdio: 'ignore' });
|
|
350
|
-
|
|
346
|
+
printInfo('Your PAT may not have write access to this repository.');
|
|
347
|
+
pat = await promptForPAT();
|
|
351
348
|
continue;
|
|
352
349
|
}
|
|
353
350
|
|
|
@@ -395,31 +392,7 @@ async function main() {
|
|
|
395
392
|
printSuccess(`${providerConfig.name} key added (${maskSecret(agentApiKey)})`);
|
|
396
393
|
}
|
|
397
394
|
|
|
398
|
-
// Step 3b:
|
|
399
|
-
if (collectedKeys['ANTHROPIC_API_KEY']) {
|
|
400
|
-
printSuccess('Using your Anthropic key for the chatbot too.');
|
|
401
|
-
} else {
|
|
402
|
-
console.log('');
|
|
403
|
-
printInfo('Your chatbot requires an Anthropic (Claude) API key.');
|
|
404
|
-
|
|
405
|
-
let anthropicValid = false;
|
|
406
|
-
while (!anthropicValid) {
|
|
407
|
-
const anthropicKey = await promptForApiKey('anthropic');
|
|
408
|
-
|
|
409
|
-
const validateSpinner = ora('Validating Anthropic API key...').start();
|
|
410
|
-
const validation = await validateAnthropicKey(anthropicKey);
|
|
411
|
-
|
|
412
|
-
if (validation.valid) {
|
|
413
|
-
validateSpinner.succeed('Anthropic API key valid');
|
|
414
|
-
collectedKeys['ANTHROPIC_API_KEY'] = anthropicKey;
|
|
415
|
-
anthropicValid = true;
|
|
416
|
-
} else {
|
|
417
|
-
validateSpinner.fail(`Invalid key: ${validation.error}`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Step 3c: Voice Messages (OpenAI optional)
|
|
395
|
+
// Step 3b: Voice Messages (OpenAI optional)
|
|
423
396
|
if (collectedKeys['OPENAI_API_KEY']) {
|
|
424
397
|
printSuccess('Your OpenAI key can also power voice messages.');
|
|
425
398
|
} else {
|
|
@@ -430,7 +403,7 @@ async function main() {
|
|
|
430
403
|
}
|
|
431
404
|
}
|
|
432
405
|
|
|
433
|
-
// Step
|
|
406
|
+
// Step 3c: Brave Search (optional, default: true since it's free)
|
|
434
407
|
braveKey = await promptForBraveKey();
|
|
435
408
|
if (braveKey) {
|
|
436
409
|
printSuccess(`Brave Search key added (${maskSecret(braveKey)})`);
|
|
@@ -489,8 +462,8 @@ async function main() {
|
|
|
489
462
|
const defaultVars = {
|
|
490
463
|
AUTO_MERGE: 'true',
|
|
491
464
|
ALLOWED_PATHS: '/logs',
|
|
492
|
-
|
|
493
|
-
|
|
465
|
+
LLM_PROVIDER: agentProvider,
|
|
466
|
+
LLM_MODEL: agentModel,
|
|
494
467
|
};
|
|
495
468
|
|
|
496
469
|
let allVarsSet = false;
|
|
@@ -535,17 +508,20 @@ async function main() {
|
|
|
535
508
|
}
|
|
536
509
|
|
|
537
510
|
// Write .env file (now at project root, not event_handler/)
|
|
538
|
-
const apiKey = generateWebhookSecret().slice(0, 32); // Random API key for webhook endpoint
|
|
539
511
|
const telegramVerification = telegramToken ? generateVerificationCode() : null;
|
|
512
|
+
const providerConfig = agentProvider !== 'custom' ? PROVIDERS[agentProvider] : null;
|
|
513
|
+
const providerEnvKey = providerConfig ? providerConfig.envKey : 'CUSTOM_API_KEY';
|
|
540
514
|
const envPath = writeEnvFile({
|
|
541
|
-
apiKey,
|
|
542
515
|
githubToken: pat,
|
|
543
516
|
githubOwner: owner,
|
|
544
517
|
githubRepo: repo,
|
|
545
518
|
telegramBotToken: telegramToken,
|
|
546
519
|
telegramWebhookSecret,
|
|
547
520
|
ghWebhookSecret: webhookSecret,
|
|
548
|
-
|
|
521
|
+
llmProvider: agentProvider,
|
|
522
|
+
llmModel: agentModel,
|
|
523
|
+
providerEnvKey,
|
|
524
|
+
providerApiKey: collectedKeys[providerEnvKey] || '',
|
|
549
525
|
openaiApiKey: collectedKeys['OPENAI_API_KEY'] || '',
|
|
550
526
|
telegramChatId: null,
|
|
551
527
|
telegramVerification,
|
|
@@ -565,81 +541,65 @@ async function main() {
|
|
|
565
541
|
try {
|
|
566
542
|
const response = await fetch('http://localhost:3000/api/ping', {
|
|
567
543
|
method: 'GET',
|
|
568
|
-
headers: { 'x-api-key': apiKey },
|
|
569
544
|
signal: AbortSignal.timeout(5000),
|
|
570
545
|
});
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
} else {
|
|
575
|
-
serverSpinner.fail(`Server returned status ${response.status}`);
|
|
576
|
-
}
|
|
546
|
+
// Any HTTP response means the server is running (even 401)
|
|
547
|
+
serverSpinner.succeed('Server is running');
|
|
548
|
+
serverReachable = true;
|
|
577
549
|
} catch {
|
|
578
550
|
serverSpinner.fail('Could not reach server on localhost:3000');
|
|
579
551
|
}
|
|
580
552
|
}
|
|
581
553
|
|
|
582
|
-
// Step 7:
|
|
583
|
-
printStep(++currentStep, TOTAL_STEPS, '
|
|
554
|
+
// Step 7: APP_URL
|
|
555
|
+
printStep(++currentStep, TOTAL_STEPS, 'App URL');
|
|
584
556
|
|
|
585
|
-
console.log(chalk.
|
|
586
|
-
console.log(chalk.
|
|
587
|
-
console.log(chalk.dim('
|
|
588
|
-
console.log(chalk.
|
|
589
|
-
console.log(chalk.dim('
|
|
557
|
+
console.log(chalk.dim(' Your app needs a public URL for GitHub webhooks and Telegram.\n'));
|
|
558
|
+
console.log(chalk.dim(' Examples:'));
|
|
559
|
+
console.log(chalk.dim(' \u2022 ngrok: ') + chalk.cyan('https://abc123.ngrok.io'));
|
|
560
|
+
console.log(chalk.dim(' \u2022 VPS: ') + chalk.cyan('https://mybot.example.com'));
|
|
561
|
+
console.log(chalk.dim(' \u2022 PaaS: ') + chalk.cyan('https://mybot.vercel.app\n'));
|
|
590
562
|
|
|
591
|
-
let
|
|
592
|
-
while (!
|
|
593
|
-
const { url:
|
|
563
|
+
let appUrl = null;
|
|
564
|
+
while (!appUrl) {
|
|
565
|
+
const { url: urlInput } = await inquirer.prompt([
|
|
594
566
|
{
|
|
595
567
|
type: 'input',
|
|
596
568
|
name: 'url',
|
|
597
|
-
message: '
|
|
569
|
+
message: 'Enter your APP_URL (https://...):',
|
|
598
570
|
validate: (input) => {
|
|
599
571
|
if (!input) return 'URL is required';
|
|
600
572
|
if (!input.startsWith('https://')) return 'URL must start with https://';
|
|
601
|
-
if (!input.includes('ngrok')) return 'URL should be an ngrok URL';
|
|
602
573
|
return true;
|
|
603
574
|
},
|
|
604
575
|
},
|
|
605
576
|
]);
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
const ngrokSpinner = ora('Verifying server is reachable through ngrok...').start();
|
|
609
|
-
try {
|
|
610
|
-
const response = await fetch(`${candidate}/api/ping`, {
|
|
611
|
-
method: 'GET',
|
|
612
|
-
headers: { 'x-api-key': apiKey },
|
|
613
|
-
signal: AbortSignal.timeout(10000),
|
|
614
|
-
});
|
|
615
|
-
if (response.ok) {
|
|
616
|
-
ngrokSpinner.succeed('Server is reachable through ngrok');
|
|
617
|
-
ngrokUrl = candidate;
|
|
618
|
-
} else {
|
|
619
|
-
ngrokSpinner.fail(`Server returned status ${response.status}`);
|
|
620
|
-
}
|
|
621
|
-
} catch {
|
|
622
|
-
ngrokSpinner.fail('Could not reach server through ngrok');
|
|
623
|
-
}
|
|
577
|
+
appUrl = urlInput.replace(/\/$/, '');
|
|
624
578
|
}
|
|
625
579
|
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
580
|
+
// Save APP_URL and APP_HOSTNAME to .env
|
|
581
|
+
const appHostname = new URL(appUrl).hostname;
|
|
582
|
+
updateEnvVariable('APP_URL', appUrl);
|
|
583
|
+
updateEnvVariable('APP_HOSTNAME', appHostname);
|
|
584
|
+
printSuccess(`APP_URL saved to .env`);
|
|
585
|
+
|
|
586
|
+
// Set APP_URL variable on GitHub
|
|
587
|
+
let appUrlSet = false;
|
|
588
|
+
while (!appUrlSet) {
|
|
589
|
+
const urlSpinner = ora('Setting APP_URL variable...').start();
|
|
590
|
+
const urlResult = await setVariables(owner, repo, { APP_URL: appUrl });
|
|
591
|
+
if (urlResult.APP_URL.success) {
|
|
592
|
+
urlSpinner.succeed('APP_URL variable set');
|
|
593
|
+
appUrlSet = true;
|
|
634
594
|
} else {
|
|
635
|
-
urlSpinner.fail(`Failed: ${urlResult.
|
|
595
|
+
urlSpinner.fail(`Failed: ${urlResult.APP_URL.error}`);
|
|
636
596
|
await pressEnter('Fix the issue, then press enter to retry');
|
|
637
597
|
}
|
|
638
598
|
}
|
|
639
599
|
|
|
640
600
|
// Register Telegram webhook if configured
|
|
641
601
|
if (telegramToken) {
|
|
642
|
-
const webhookUrl = `${
|
|
602
|
+
const webhookUrl = `${appUrl}/api/telegram/webhook`;
|
|
643
603
|
let tgWebhookSet = false;
|
|
644
604
|
while (!tgWebhookSet) {
|
|
645
605
|
const tgSpinner = ora('Registering Telegram webhook...').start();
|
|
@@ -662,7 +622,7 @@ async function main() {
|
|
|
662
622
|
updateEnvVariable('TELEGRAM_CHAT_ID', chatId);
|
|
663
623
|
printSuccess(`Chat ID saved: ${chatId}`);
|
|
664
624
|
|
|
665
|
-
const verified = await verifyRestart(
|
|
625
|
+
const verified = await verifyRestart(appUrl);
|
|
666
626
|
if (verified) {
|
|
667
627
|
printSuccess('Telegram bot is configured and working!');
|
|
668
628
|
} else {
|
|
@@ -683,7 +643,7 @@ async function main() {
|
|
|
683
643
|
|
|
684
644
|
const providerLabel = agentProvider === 'custom' ? 'Custom / Local' : PROVIDERS[agentProvider].label;
|
|
685
645
|
console.log(` ${chalk.dim('Repository:')} ${owner}/${repo}`);
|
|
686
|
-
console.log(` ${chalk.dim('
|
|
646
|
+
console.log(` ${chalk.dim('App URL:')} ${appUrl}`);
|
|
687
647
|
console.log(` ${chalk.dim('Agent LLM:')} ${providerLabel} (${agentModel})`);
|
|
688
648
|
console.log(` ${chalk.dim('GitHub PAT:')} ${maskSecret(pat)}`);
|
|
689
649
|
for (const [envVar, value] of Object.entries(collectedKeys)) {
|
|
@@ -698,18 +658,18 @@ async function main() {
|
|
|
698
658
|
console.log(' \u2022 GH_WEBHOOK_SECRET');
|
|
699
659
|
|
|
700
660
|
console.log(chalk.bold('\n GitHub Variables Set:\n'));
|
|
701
|
-
console.log(' \u2022
|
|
661
|
+
console.log(' \u2022 APP_URL');
|
|
702
662
|
console.log(' \u2022 AUTO_MERGE = true');
|
|
703
663
|
console.log(' \u2022 ALLOWED_PATHS = /logs');
|
|
704
|
-
console.log(` \u2022
|
|
705
|
-
console.log(` \u2022
|
|
664
|
+
console.log(` \u2022 LLM_PROVIDER = ${agentProvider}`);
|
|
665
|
+
console.log(` \u2022 LLM_MODEL = ${agentModel}`);
|
|
706
666
|
|
|
707
667
|
console.log(chalk.bold.green('\n You\'re all set!\n'));
|
|
708
668
|
|
|
709
669
|
if (telegramToken) {
|
|
710
670
|
console.log(chalk.cyan(' Message your Telegram bot to create your first job!'));
|
|
711
671
|
} else {
|
|
712
|
-
console.log(chalk.dim(' Use the /api/
|
|
672
|
+
console.log(chalk.dim(' Use the /api/create-job endpoint to create jobs.'));
|
|
713
673
|
}
|
|
714
674
|
|
|
715
675
|
console.log('\n');
|
package/templates/.env.example
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
# Copy this file to .env and fill in your values
|
|
3
3
|
# NEVER commit the actual .env file with real secrets!
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
|
|
5
|
+
# Auth.js secret for session encryption (auto-generated by setup wizard)
|
|
6
|
+
# To generate manually: openssl rand -base64 32
|
|
7
|
+
AUTH_SECRET=
|
|
8
|
+
|
|
9
|
+
# Public URL for webhooks, Telegram, and Traefik hostname
|
|
10
|
+
APP_URL=
|
|
11
|
+
|
|
12
|
+
# Hostname extracted from APP_URL (e.g., mybot.example.com) — used by docker-compose/Traefik
|
|
13
|
+
# Auto-set by setup wizard
|
|
14
|
+
APP_HOSTNAME=
|
|
7
15
|
|
|
8
16
|
# GitHub Personal Access Token (needs repo, workflow scopes)
|
|
9
17
|
GH_TOKEN=ghp_your_token_here
|
|
@@ -36,3 +44,11 @@ OPENAI_API_KEY=
|
|
|
36
44
|
# Default Telegram chat ID for scheduled notifications
|
|
37
45
|
# Get this by messaging your bot and checking the webhook logs, or use @userinfobot
|
|
38
46
|
TELEGRAM_CHAT_ID=
|
|
47
|
+
|
|
48
|
+
# Let's Encrypt email for automatic SSL (docker-compose only, optional)
|
|
49
|
+
# If not set, Traefik uses a self-signed certificate
|
|
50
|
+
# LETSENCRYPT_EMAIL=
|
|
51
|
+
|
|
52
|
+
# Custom Docker images (optional, defaults to stephengpope/thepopebot-* images)
|
|
53
|
+
# EVENT_HANDLER_IMAGE_URL=
|
|
54
|
+
# JOB_IMAGE_URL=
|
|
@@ -3,11 +3,13 @@ name: Build and Push Docker Image
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
|
+
paths: ['docker/job/**']
|
|
7
|
+
workflow_dispatch:
|
|
6
8
|
|
|
7
9
|
jobs:
|
|
8
10
|
build:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
if: vars.
|
|
11
|
+
runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }}
|
|
12
|
+
if: vars.JOB_IMAGE_URL != '' && startsWith(vars.JOB_IMAGE_URL, 'ghcr.io/')
|
|
11
13
|
permissions:
|
|
12
14
|
contents: read
|
|
13
15
|
packages: write
|
|
@@ -29,6 +31,6 @@ jobs:
|
|
|
29
31
|
- name: Build and push
|
|
30
32
|
uses: docker/build-push-action@v6
|
|
31
33
|
with:
|
|
32
|
-
context: ./docker
|
|
34
|
+
context: ./docker/job
|
|
33
35
|
push: true
|
|
34
|
-
tags: ${{ vars.
|
|
36
|
+
tags: ${{ vars.JOB_IMAGE_URL }}:latest
|
|
@@ -7,7 +7,7 @@ on:
|
|
|
7
7
|
|
|
8
8
|
jobs:
|
|
9
9
|
notify-failure:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
10
|
+
runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }}
|
|
11
11
|
if: >
|
|
12
12
|
github.event.workflow_run.conclusion != 'success' &&
|
|
13
13
|
startsWith(github.event.workflow_run.head_branch, 'job/')
|
|
@@ -60,7 +60,7 @@ jobs:
|
|
|
60
60
|
merge_result: ""
|
|
61
61
|
}' > /tmp/payload.json
|
|
62
62
|
|
|
63
|
-
curl -s -X POST "${{ vars.
|
|
63
|
+
curl -s -X POST "${{ vars.APP_URL }}/api/github/webhook" \
|
|
64
64
|
-H "Content-Type: application/json" \
|
|
65
65
|
-H "X-GitHub-Webhook-Secret-Token: ${{ secrets.GH_WEBHOOK_SECRET }}" \
|
|
66
66
|
-d @/tmp/payload.json
|
|
@@ -7,7 +7,7 @@ on:
|
|
|
7
7
|
|
|
8
8
|
jobs:
|
|
9
9
|
notify:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
10
|
+
runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }}
|
|
11
11
|
if: startsWith(github.event.workflow_run.head_branch, 'job/')
|
|
12
12
|
permissions:
|
|
13
13
|
contents: read
|
|
@@ -120,7 +120,7 @@ jobs:
|
|
|
120
120
|
}' > /tmp/payload.json
|
|
121
121
|
|
|
122
122
|
# 7. Send to event handler
|
|
123
|
-
curl -s -X POST "${{ vars.
|
|
123
|
+
curl -s -X POST "${{ vars.APP_URL }}/api/github/webhook" \
|
|
124
124
|
-H "Content-Type: application/json" \
|
|
125
125
|
-H "X-GitHub-Webhook-Secret-Token: ${{ secrets.GH_WEBHOOK_SECRET }}" \
|
|
126
126
|
-d @/tmp/payload.json
|
|
@@ -5,15 +5,28 @@ on:
|
|
|
5
5
|
|
|
6
6
|
jobs:
|
|
7
7
|
run-agent:
|
|
8
|
-
runs-on: ubuntu-latest
|
|
8
|
+
runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }}
|
|
9
9
|
if: github.ref_type == 'branch' && startsWith(github.ref_name, 'job/')
|
|
10
10
|
permissions:
|
|
11
11
|
contents: write
|
|
12
12
|
packages: read
|
|
13
13
|
|
|
14
14
|
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
ref: ${{ github.ref_name }}
|
|
19
|
+
sparse-checkout: package-lock.json
|
|
20
|
+
sparse-checkout-cone-mode: false
|
|
21
|
+
|
|
22
|
+
- name: Get thepopebot version
|
|
23
|
+
id: version
|
|
24
|
+
run: |
|
|
25
|
+
VERSION=$(node -p "require('./package-lock.json').packages['node_modules/thepopebot']?.version || 'latest'")
|
|
26
|
+
echo "tag=$VERSION" >> $GITHUB_OUTPUT
|
|
27
|
+
|
|
15
28
|
- name: Login to GHCR
|
|
16
|
-
if: startsWith(vars.
|
|
29
|
+
if: startsWith(vars.JOB_IMAGE_URL, 'ghcr.io/')
|
|
17
30
|
uses: docker/login-action@v3
|
|
18
31
|
with:
|
|
19
32
|
registry: ghcr.io
|
|
@@ -22,14 +35,15 @@ jobs:
|
|
|
22
35
|
|
|
23
36
|
- name: Run thepopebot Agent
|
|
24
37
|
env:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
JOB_IMAGE_URL: ${{ vars.JOB_IMAGE_URL }}
|
|
39
|
+
THEPOPEBOT_VERSION: ${{ steps.version.outputs.tag }}
|
|
40
|
+
LLM_MODEL: ${{ vars.LLM_MODEL }}
|
|
41
|
+
LLM_PROVIDER: ${{ vars.LLM_PROVIDER }}
|
|
28
42
|
run: |
|
|
29
|
-
if [ -n "$
|
|
30
|
-
IMAGE="${
|
|
43
|
+
if [ -n "$JOB_IMAGE_URL" ]; then
|
|
44
|
+
IMAGE="${JOB_IMAGE_URL}:latest"
|
|
31
45
|
else
|
|
32
|
-
IMAGE="stephengpope/thepopebot
|
|
46
|
+
IMAGE="stephengpope/thepopebot:${THEPOPEBOT_VERSION}"
|
|
33
47
|
fi
|
|
34
48
|
echo "Using image: $IMAGE"
|
|
35
49
|
docker run --rm \
|
|
@@ -37,6 +51,6 @@ jobs:
|
|
|
37
51
|
-e BRANCH="${{ github.ref_name }}" \
|
|
38
52
|
-e SECRETS='${{ secrets.SECRETS }}' \
|
|
39
53
|
-e LLM_SECRETS='${{ secrets.LLM_SECRETS }}' \
|
|
40
|
-
-e
|
|
41
|
-
-e
|
|
54
|
+
-e LLM_MODEL="${LLM_MODEL}" \
|
|
55
|
+
-e LLM_PROVIDER="${LLM_PROVIDER}" \
|
|
42
56
|
"$IMAGE"
|
package/templates/CLAUDE.md
CHANGED
|
@@ -23,8 +23,10 @@ This is an autonomous AI agent powered by [thepopebot](https://github.com/stephe
|
|
|
23
23
|
│ ├── page.js # Home page
|
|
24
24
|
│ └── api/[...thepopebot]/ # Framework API routes
|
|
25
25
|
├── docker/
|
|
26
|
-
│ ├──
|
|
27
|
-
│
|
|
26
|
+
│ ├── job/ # Job agent container (customizable)
|
|
27
|
+
│ ├── event_handler/ # Event handler container
|
|
28
|
+
│ └── runner/ # GitHub Actions runner container
|
|
29
|
+
├── docker-compose.yml # Production deployment
|
|
28
30
|
├── cron/ # Working dir for command-type cron actions
|
|
29
31
|
├── triggers/ # Working dir for command-type trigger actions
|
|
30
32
|
├── logs/ # Per-job directories (job.md + session logs)
|
|
@@ -66,7 +68,7 @@ All API routes are under `/api/`:
|
|
|
66
68
|
|
|
67
69
|
| Endpoint | Method | Purpose |
|
|
68
70
|
|----------|--------|---------|
|
|
69
|
-
| `/api/
|
|
71
|
+
| `/api/create-job` | POST | Create a new job (requires API_KEY) |
|
|
70
72
|
| `/api/telegram/webhook` | POST | Telegram bot webhook |
|
|
71
73
|
| `/api/telegram/register` | POST | Register Telegram webhook URL |
|
|
72
74
|
| `/api/github/webhook` | POST | Receives notifications from GitHub Actions |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { GET, POST } from 'thepopebot/auth';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { POST } from 'thepopebot/chat/api';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { auth } from 'thepopebot/auth';
|
|
2
|
+
import { ChatPage } from 'thepopebot/chat';
|
|
3
|
+
|
|
4
|
+
export default async function ChatRoute({ params }) {
|
|
5
|
+
const { chatId } = await params;
|
|
6
|
+
const session = await auth();
|
|
7
|
+
return <ChatPage session={session} needsSetup={false} chatId={chatId} />;
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function AsciiLogo() {
|
|
2
|
+
return (
|
|
3
|
+
<pre className="text-foreground text-[clamp(0.45rem,1.5vw,0.85rem)] leading-snug text-left mb-8 select-none">{` _____ _ ____ ____ _
|
|
4
|
+
|_ _| |__ ___| _ \\ ___ _ __ ___| __ ) ___ | |_
|
|
5
|
+
| | | '_ \\ / _ \\ |_) / _ \\| '_ \\ / _ \\ _ \\ / _ \\| __|
|
|
6
|
+
| | | | | | __/ __/ (_) | |_) | __/ |_) | (_) | |_
|
|
7
|
+
|_| |_| |_|\\___|_| \\___/| .__/ \\___|____/ \\___/ \\__|
|
|
8
|
+
|_|`}</pre>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { signIn } from 'next-auth/react';
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
6
|
+
import { Button } from './ui/button';
|
|
7
|
+
import { Input } from './ui/input';
|
|
8
|
+
import { Label } from './ui/label';
|
|
9
|
+
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from './ui/card';
|
|
10
|
+
|
|
11
|
+
export function LoginForm() {
|
|
12
|
+
const router = useRouter();
|
|
13
|
+
const [email, setEmail] = useState('');
|
|
14
|
+
const [password, setPassword] = useState('');
|
|
15
|
+
const [error, setError] = useState('');
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
|
|
18
|
+
async function handleSubmit(e) {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setError('');
|
|
21
|
+
setLoading(true);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const result = await signIn('credentials', {
|
|
25
|
+
email,
|
|
26
|
+
password,
|
|
27
|
+
redirect: false,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (result?.error) {
|
|
31
|
+
setError('Invalid email or password.');
|
|
32
|
+
} else {
|
|
33
|
+
router.push('/');
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
setError('Something went wrong. Please try again.');
|
|
37
|
+
} finally {
|
|
38
|
+
setLoading(false);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Card className="w-full max-w-sm">
|
|
44
|
+
<CardHeader>
|
|
45
|
+
<CardTitle>Sign In</CardTitle>
|
|
46
|
+
<CardDescription>Log in to your agent dashboard.</CardDescription>
|
|
47
|
+
</CardHeader>
|
|
48
|
+
<CardContent>
|
|
49
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
<Label htmlFor="email">Email</Label>
|
|
52
|
+
<Input
|
|
53
|
+
id="email"
|
|
54
|
+
type="email"
|
|
55
|
+
placeholder="admin@example.com"
|
|
56
|
+
value={email}
|
|
57
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
58
|
+
required
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
<div className="space-y-2">
|
|
62
|
+
<Label htmlFor="password">Password</Label>
|
|
63
|
+
<Input
|
|
64
|
+
id="password"
|
|
65
|
+
type="password"
|
|
66
|
+
value={password}
|
|
67
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
68
|
+
required
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
{error && (
|
|
72
|
+
<p className="text-sm text-destructive">{error}</p>
|
|
73
|
+
)}
|
|
74
|
+
<Button type="submit" className="w-full" disabled={loading}>
|
|
75
|
+
{loading ? 'Signing in...' : 'Sign In'}
|
|
76
|
+
</Button>
|
|
77
|
+
</form>
|
|
78
|
+
</CardContent>
|
|
79
|
+
</Card>
|
|
80
|
+
);
|
|
81
|
+
}
|