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.
Files changed (143) hide show
  1. package/README.md +1 -1
  2. package/api/index.js +72 -165
  3. package/bin/cli.js +36 -6
  4. package/bin/local.sh +31 -0
  5. package/bin/postinstall.js +6 -2
  6. package/config/index.js +2 -11
  7. package/config/instrumentation.js +17 -5
  8. package/lib/actions.js +7 -6
  9. package/lib/ai/agent.js +36 -0
  10. package/lib/ai/index.js +274 -0
  11. package/lib/ai/model.js +67 -0
  12. package/lib/ai/tools.js +49 -0
  13. package/lib/auth/actions.js +28 -0
  14. package/lib/auth/config.js +45 -0
  15. package/lib/auth/index.js +27 -0
  16. package/lib/auth/middleware.js +30 -0
  17. package/lib/channels/base.js +56 -0
  18. package/lib/channels/index.js +15 -0
  19. package/lib/channels/telegram.js +146 -0
  20. package/lib/chat/actions.js +239 -0
  21. package/lib/chat/api.js +103 -0
  22. package/lib/chat/components/app-sidebar.js +161 -0
  23. package/lib/chat/components/app-sidebar.jsx +214 -0
  24. package/lib/chat/components/chat-header.js +9 -0
  25. package/lib/chat/components/chat-header.jsx +14 -0
  26. package/lib/chat/components/chat-input.js +230 -0
  27. package/lib/chat/components/chat-input.jsx +232 -0
  28. package/lib/chat/components/chat-nav-context.js +11 -0
  29. package/lib/chat/components/chat-nav-context.jsx +11 -0
  30. package/lib/chat/components/chat-page.js +70 -0
  31. package/lib/chat/components/chat-page.jsx +89 -0
  32. package/lib/chat/components/chat.js +78 -0
  33. package/lib/chat/components/chat.jsx +91 -0
  34. package/lib/chat/components/chats-page.js +170 -0
  35. package/lib/chat/components/chats-page.jsx +203 -0
  36. package/lib/chat/components/crons-page.js +144 -0
  37. package/lib/chat/components/crons-page.jsx +204 -0
  38. package/lib/chat/components/greeting.js +11 -0
  39. package/lib/chat/components/greeting.jsx +14 -0
  40. package/lib/chat/components/icons.js +518 -0
  41. package/lib/chat/components/icons.jsx +482 -0
  42. package/lib/chat/components/index.js +19 -0
  43. package/lib/chat/components/message.js +66 -0
  44. package/lib/chat/components/message.jsx +92 -0
  45. package/lib/chat/components/messages.js +63 -0
  46. package/lib/chat/components/messages.jsx +72 -0
  47. package/lib/chat/components/notifications-page.js +54 -0
  48. package/lib/chat/components/notifications-page.jsx +83 -0
  49. package/lib/chat/components/page-layout.js +21 -0
  50. package/lib/chat/components/page-layout.jsx +28 -0
  51. package/lib/chat/components/settings-layout.js +37 -0
  52. package/lib/chat/components/settings-layout.jsx +51 -0
  53. package/lib/chat/components/settings-secrets-page.js +216 -0
  54. package/lib/chat/components/settings-secrets-page.jsx +264 -0
  55. package/lib/chat/components/sidebar-history-item.js +54 -0
  56. package/lib/chat/components/sidebar-history-item.jsx +50 -0
  57. package/lib/chat/components/sidebar-history.js +92 -0
  58. package/lib/chat/components/sidebar-history.jsx +132 -0
  59. package/lib/chat/components/sidebar-user-nav.js +59 -0
  60. package/lib/chat/components/sidebar-user-nav.jsx +69 -0
  61. package/lib/chat/components/swarm-page.js +250 -0
  62. package/lib/chat/components/swarm-page.jsx +356 -0
  63. package/lib/chat/components/triggers-page.js +121 -0
  64. package/lib/chat/components/triggers-page.jsx +177 -0
  65. package/lib/chat/components/ui/dropdown-menu.js +98 -0
  66. package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
  67. package/lib/chat/components/ui/scroll-area.js +13 -0
  68. package/lib/chat/components/ui/scroll-area.jsx +17 -0
  69. package/lib/chat/components/ui/separator.js +21 -0
  70. package/lib/chat/components/ui/separator.jsx +18 -0
  71. package/lib/chat/components/ui/sheet.js +75 -0
  72. package/lib/chat/components/ui/sheet.jsx +95 -0
  73. package/lib/chat/components/ui/sidebar.js +227 -0
  74. package/lib/chat/components/ui/sidebar.jsx +245 -0
  75. package/lib/chat/components/ui/tooltip.js +56 -0
  76. package/lib/chat/components/ui/tooltip.jsx +66 -0
  77. package/lib/chat/utils.js +11 -0
  78. package/lib/cron.js +7 -8
  79. package/lib/db/api-keys.js +160 -0
  80. package/lib/db/chats.js +129 -0
  81. package/lib/db/index.js +106 -0
  82. package/lib/db/notifications.js +99 -0
  83. package/lib/db/schema.js +51 -0
  84. package/lib/db/users.js +89 -0
  85. package/lib/paths.js +23 -17
  86. package/lib/tools/create-job.js +3 -3
  87. package/lib/tools/github.js +145 -1
  88. package/lib/tools/openai.js +1 -1
  89. package/lib/tools/telegram.js +4 -3
  90. package/lib/triggers.js +6 -7
  91. package/lib/utils/render-md.js +6 -6
  92. package/package.json +43 -6
  93. package/setup/lib/auth.mjs +22 -9
  94. package/setup/lib/prerequisites.mjs +10 -3
  95. package/setup/lib/telegram-verify.mjs +3 -16
  96. package/setup/setup-telegram.mjs +31 -62
  97. package/setup/setup.mjs +58 -98
  98. package/templates/.dockerignore +5 -0
  99. package/templates/.env.example +18 -2
  100. package/templates/.github/workflows/auto-merge.yml +1 -1
  101. package/templates/.github/workflows/build-image.yml +6 -4
  102. package/templates/.github/workflows/notify-job-failed.yml +2 -2
  103. package/templates/.github/workflows/notify-pr-complete.yml +2 -2
  104. package/templates/.github/workflows/run-job.yml +24 -10
  105. package/templates/CLAUDE.md +5 -3
  106. package/templates/app/api/auth/[...nextauth]/route.js +1 -0
  107. package/templates/app/api/chat/route.js +1 -0
  108. package/templates/app/chat/[chatId]/page.js +8 -0
  109. package/templates/app/chats/page.js +7 -0
  110. package/templates/app/components/ascii-logo.jsx +10 -0
  111. package/templates/app/components/login-form.jsx +81 -0
  112. package/templates/app/components/setup-form.jsx +82 -0
  113. package/templates/app/components/theme-provider.jsx +11 -0
  114. package/templates/app/components/theme-toggle.jsx +38 -0
  115. package/templates/app/components/ui/button.jsx +21 -0
  116. package/templates/app/components/ui/card.jsx +23 -0
  117. package/templates/app/components/ui/input.jsx +10 -0
  118. package/templates/app/components/ui/label.jsx +10 -0
  119. package/templates/app/crons/page.js +7 -0
  120. package/templates/app/globals.css +66 -0
  121. package/templates/app/layout.js +9 -2
  122. package/templates/app/login/page.js +15 -0
  123. package/templates/app/notifications/page.js +7 -0
  124. package/templates/app/page.js +6 -30
  125. package/templates/app/settings/layout.js +7 -0
  126. package/templates/app/settings/page.js +5 -0
  127. package/templates/app/settings/secrets/page.js +5 -0
  128. package/templates/app/swarm/page.js +7 -0
  129. package/templates/app/triggers/page.js +7 -0
  130. package/templates/config/CRONS.json +2 -2
  131. package/templates/config/TRIGGERS.json +2 -2
  132. package/templates/docker/event_handler/Dockerfile +19 -0
  133. package/templates/docker/{entrypoint.sh → job/entrypoint.sh} +4 -4
  134. package/templates/docker/runner/Dockerfile +38 -0
  135. package/templates/docker/runner/entrypoint.sh +41 -0
  136. package/templates/docker-compose.yml +52 -0
  137. package/templates/instrumentation.js +6 -1
  138. package/templates/middleware.js +1 -0
  139. package/templates/postcss.config.mjs +5 -0
  140. package/lib/claude/conversation.js +0 -76
  141. package/lib/claude/index.js +0 -142
  142. package/lib/claude/tools.js +0 -54
  143. /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 repository detected.');
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-only'));
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
- await pressEnter('Fix the issue, then press enter to retry');
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: Event Handler (Anthropic required)
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 3d: Brave Search (optional, default: true since it's free)
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
- PROVIDER: agentProvider,
493
- MODEL: agentModel,
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
- anthropicApiKey: collectedKeys['ANTHROPIC_API_KEY'] || '',
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
- if (response.ok) {
572
- serverSpinner.succeed('Server is running');
573
- serverReachable = true;
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: ngrok
583
- printStep(++currentStep, TOTAL_STEPS, 'Expose Server with ngrok');
554
+ // Step 7: APP_URL
555
+ printStep(++currentStep, TOTAL_STEPS, 'App URL');
584
556
 
585
- console.log(chalk.bold(' Start ngrok in another terminal window:\n'));
586
- console.log(chalk.cyan(' ngrok http 3000\n'));
587
- console.log(chalk.dim(' ngrok will show a "Forwarding" URL like: https://abc123.ngrok.io\n'));
588
- console.log(chalk.yellow(' Note: ') + chalk.dim('ngrok URLs change each time you restart it (unless you have a paid plan).'));
589
- console.log(chalk.dim(' When your URL changes, run: ') + chalk.cyan('npm run setup-telegram') + chalk.dim(' to reconfigure.\n'));
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 ngrokUrl = null;
592
- while (!ngrokUrl) {
593
- const { url: ngrokInput } = await inquirer.prompt([
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: 'Paste your ngrok URL (https://...ngrok...):',
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
- const candidate = ngrokInput.replace(/\/$/, '');
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
- // Set GH_WEBHOOK_URL variable
627
- let webhookUrlSet = false;
628
- while (!webhookUrlSet) {
629
- const urlSpinner = ora('Setting GH_WEBHOOK_URL variable...').start();
630
- const urlResult = await setVariables(owner, repo, { GH_WEBHOOK_URL: ngrokUrl });
631
- if (urlResult.GH_WEBHOOK_URL.success) {
632
- urlSpinner.succeed('GH_WEBHOOK_URL variable set');
633
- webhookUrlSet = true;
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.GH_WEBHOOK_URL.error}`);
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 = `${ngrokUrl}/api/telegram/webhook`;
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(ngrokUrl, apiKey);
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('Webhook URL:')} ${ngrokUrl}`);
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 GH_WEBHOOK_URL');
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 PROVIDER = ${agentProvider}`);
705
- console.log(` \u2022 MODEL = ${agentModel}`);
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/webhook endpoint to create jobs.'));
672
+ console.log(chalk.dim(' Use the /api/create-job endpoint to create jobs.'));
713
673
  }
714
674
 
715
675
  console.log('\n');
@@ -0,0 +1,5 @@
1
+ node_modules
2
+ .env
3
+ data/
4
+ logs/
5
+ .git
@@ -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
- # Authentication key for /api/webhook endpoint (generate random string)
6
- API_KEY=your_random_api_key_here
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=
@@ -7,7 +7,7 @@ on:
7
7
 
8
8
  jobs:
9
9
  auto-merge:
10
- runs-on: ubuntu-latest
10
+ runs-on: ${{ vars.RUNS_ON || 'ubuntu-latest' }}
11
11
  if: startsWith(github.event.pull_request.head.ref, 'job/')
12
12
  permissions:
13
13
  contents: write
@@ -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.IMAGE_URL != '' && startsWith(vars.IMAGE_URL, 'ghcr.io/')
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.IMAGE_URL }}:latest
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.GH_WEBHOOK_URL }}/api/github/webhook" \
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.GH_WEBHOOK_URL }}/api/github/webhook" \
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.IMAGE_URL, 'ghcr.io/')
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
- IMAGE_URL: ${{ vars.IMAGE_URL }}
26
- MODEL: ${{ vars.MODEL }}
27
- PROVIDER: ${{ vars.PROVIDER }}
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 "$IMAGE_URL" ]; then
30
- IMAGE="${IMAGE_URL}"
43
+ if [ -n "$JOB_IMAGE_URL" ]; then
44
+ IMAGE="${JOB_IMAGE_URL}:latest"
31
45
  else
32
- IMAGE="stephengpope/thepopebot:latest"
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 MODEL="${MODEL}" \
41
- -e PROVIDER="${PROVIDER}" \
54
+ -e LLM_MODEL="${LLM_MODEL}" \
55
+ -e LLM_PROVIDER="${LLM_PROVIDER}" \
42
56
  "$IMAGE"
@@ -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
- │ ├── Dockerfile # Agent container (customizable)
27
- └── entrypoint.sh # Container startup script
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/webhook` | POST | Create a new job (requires API_KEY) |
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,7 @@
1
+ import { auth } from 'thepopebot/auth';
2
+ import { ChatsPage } from 'thepopebot/chat';
3
+
4
+ export default async function ChatsRoute() {
5
+ const session = await auth();
6
+ return <ChatsPage session={session} />;
7
+ }
@@ -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
+ }