thepopebot 1.2.74-beta.9 → 1.2.74

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 (210) hide show
  1. package/README.md +37 -38
  2. package/api/CLAUDE.md +6 -5
  3. package/api/index.js +93 -25
  4. package/bin/CLAUDE.md +37 -0
  5. package/bin/cli.js +9 -73
  6. package/bin/docker-build.js +94 -32
  7. package/bin/managed-paths.js +6 -0
  8. package/bin/sync.js +170 -76
  9. package/config/CLAUDE.md +19 -9
  10. package/config/instrumentation.js +4 -4
  11. package/drizzle/0016_majestic_metal_master.sql +1 -0
  12. package/drizzle/0017_smart_trauma.sql +1 -0
  13. package/drizzle/0018_aberrant_vertigo.sql +1 -0
  14. package/drizzle/0019_slimy_midnight.sql +1 -0
  15. package/drizzle/0020_natural_fabian_cortez.sql +1 -0
  16. package/drizzle/meta/0016_snapshot.json +616 -0
  17. package/drizzle/meta/0017_snapshot.json +624 -0
  18. package/drizzle/meta/0018_snapshot.json +632 -0
  19. package/drizzle/meta/0019_snapshot.json +640 -0
  20. package/drizzle/meta/0020_snapshot.json +632 -0
  21. package/drizzle/meta/_journal.json +35 -0
  22. package/lib/CLAUDE.md +2 -2
  23. package/lib/actions.js +4 -4
  24. package/lib/ai/CLAUDE.md +40 -17
  25. package/lib/ai/agent.js +33 -64
  26. package/lib/ai/headless-stream.js +55 -93
  27. package/lib/ai/index.js +116 -88
  28. package/lib/ai/line-mappers.js +393 -0
  29. package/lib/ai/model.js +43 -5
  30. package/lib/ai/tools.js +109 -309
  31. package/lib/auth/CLAUDE.md +8 -0
  32. package/lib/auth/components/login-form.js +1 -1
  33. package/lib/auth/components/login-form.jsx +1 -1
  34. package/lib/auth/components/setup-form.js +50 -1
  35. package/lib/auth/components/setup-form.jsx +57 -2
  36. package/lib/auth/middleware.js +1 -1
  37. package/lib/chat/CLAUDE.md +53 -0
  38. package/lib/chat/actions.js +468 -28
  39. package/lib/chat/api.js +340 -6
  40. package/lib/chat/components/CLAUDE.md +145 -0
  41. package/lib/chat/components/app-sidebar.js +11 -13
  42. package/lib/chat/components/app-sidebar.jsx +14 -17
  43. package/lib/chat/components/chat-header.js +15 -6
  44. package/lib/chat/components/chat-header.jsx +27 -8
  45. package/lib/chat/components/chat-input.js +140 -21
  46. package/lib/chat/components/chat-input.jsx +132 -25
  47. package/lib/chat/components/chat-page.js +10 -4
  48. package/lib/chat/components/chat-page.jsx +53 -45
  49. package/lib/chat/components/chat.js +254 -67
  50. package/lib/chat/components/chat.jsx +275 -81
  51. package/lib/chat/components/chats-page.js +32 -5
  52. package/lib/chat/components/chats-page.jsx +45 -6
  53. package/lib/chat/components/code-mode-toggle.js +311 -162
  54. package/lib/chat/components/code-mode-toggle.jsx +349 -198
  55. package/lib/chat/components/containers-page.js +542 -0
  56. package/lib/chat/components/containers-page.jsx +710 -0
  57. package/lib/chat/components/diff-viewer.js +138 -0
  58. package/lib/chat/components/diff-viewer.jsx +158 -0
  59. package/lib/chat/components/icons.js +88 -0
  60. package/lib/chat/components/icons.jsx +82 -0
  61. package/lib/chat/components/index.js +6 -4
  62. package/lib/chat/components/message.js +107 -27
  63. package/lib/chat/components/message.jsx +128 -30
  64. package/lib/chat/components/notifications-page.js +1 -1
  65. package/lib/chat/components/notifications-page.jsx +1 -1
  66. package/lib/chat/components/profile-page.js +1 -1
  67. package/lib/chat/components/profile-page.jsx +1 -1
  68. package/lib/chat/components/runners-page.js +1 -1
  69. package/lib/chat/components/runners-page.jsx +1 -1
  70. package/lib/chat/components/settings-chat-page.js +437 -294
  71. package/lib/chat/components/settings-chat-page.jsx +381 -243
  72. package/lib/chat/components/settings-coding-agents-page.js +767 -0
  73. package/lib/chat/components/settings-coding-agents-page.jsx +940 -0
  74. package/lib/chat/components/settings-general-page.js +189 -29
  75. package/lib/chat/components/settings-general-page.jsx +178 -14
  76. package/lib/chat/components/settings-github-page.js +103 -475
  77. package/lib/chat/components/settings-github-page.jsx +96 -462
  78. package/lib/chat/components/settings-jobs-page.js +527 -0
  79. package/lib/chat/components/settings-jobs-page.jsx +619 -0
  80. package/lib/chat/components/settings-layout.js +5 -6
  81. package/lib/chat/components/settings-layout.jsx +5 -6
  82. package/lib/chat/components/settings-secrets-layout.js +15 -6
  83. package/lib/chat/components/settings-secrets-layout.jsx +16 -6
  84. package/lib/chat/components/settings-secrets-page.js +23 -189
  85. package/lib/chat/components/settings-secrets-page.jsx +20 -195
  86. package/lib/chat/components/settings-shared.js +328 -0
  87. package/lib/chat/components/settings-shared.jsx +339 -0
  88. package/lib/chat/components/settings-users-page.js +22 -58
  89. package/lib/chat/components/settings-users-page.jsx +19 -57
  90. package/lib/chat/components/sidebar-history-item.js +5 -2
  91. package/lib/chat/components/sidebar-history-item.jsx +7 -2
  92. package/lib/chat/components/sidebar-history.js +9 -10
  93. package/lib/chat/components/sidebar-history.jsx +10 -10
  94. package/lib/chat/components/tool-call.js +2 -2
  95. package/lib/chat/components/tool-call.jsx +2 -2
  96. package/lib/chat/components/ui/combobox.js +12 -8
  97. package/lib/chat/components/ui/combobox.jsx +17 -9
  98. package/lib/chat/components/ui/dropdown-menu.js +52 -26
  99. package/lib/chat/components/ui/dropdown-menu.jsx +40 -18
  100. package/lib/chat/components/upgrade-dialog.js +3 -3
  101. package/lib/chat/components/upgrade-dialog.jsx +3 -3
  102. package/lib/chat/components/voice-bars.js +2 -2
  103. package/lib/chat/components/voice-bars.jsx +2 -2
  104. package/lib/cluster/CLAUDE.md +1 -1
  105. package/lib/cluster/actions.js +2 -2
  106. package/lib/cluster/components/cluster-console-page.js +3 -3
  107. package/lib/cluster/components/cluster-console-page.jsx +3 -3
  108. package/lib/cluster/components/cluster-logs-page.js +7 -37
  109. package/lib/cluster/components/cluster-logs-page.jsx +7 -40
  110. package/lib/cluster/components/cluster-page.js +82 -17
  111. package/lib/cluster/components/cluster-page.jsx +77 -16
  112. package/lib/cluster/components/clusters-page.js +13 -1
  113. package/lib/cluster/components/clusters-page.jsx +17 -3
  114. package/lib/cluster/execute.js +6 -3
  115. package/lib/cluster/runtime.js +2 -1
  116. package/lib/cluster/stream.js +22 -34
  117. package/lib/code/CLAUDE.md +13 -1
  118. package/lib/code/actions.js +741 -151
  119. package/lib/code/code-page.js +264 -104
  120. package/lib/code/code-page.jsx +279 -112
  121. package/lib/code/editor-view.js +662 -0
  122. package/lib/code/editor-view.jsx +749 -0
  123. package/lib/code/port-forwards.js +152 -0
  124. package/lib/code/terminal-sessions.js +4 -1
  125. package/lib/code/terminal-view.js +447 -35
  126. package/lib/code/terminal-view.jsx +434 -30
  127. package/lib/config.js +57 -15
  128. package/lib/containers/CLAUDE.md +9 -0
  129. package/lib/containers/logs.js +178 -0
  130. package/lib/containers/stream.js +72 -0
  131. package/lib/cron.js +1 -1
  132. package/lib/db/CLAUDE.md +36 -3
  133. package/lib/db/api-keys.js +38 -12
  134. package/lib/db/chats.js +3 -1
  135. package/lib/db/clusters.js +1 -0
  136. package/lib/db/code-workspaces.js +27 -3
  137. package/lib/db/config.js +136 -11
  138. package/lib/db/index.js +16 -6
  139. package/lib/db/oauth-tokens.js +157 -0
  140. package/lib/db/schema.js +4 -1
  141. package/lib/llm-providers.js +93 -10
  142. package/lib/oauth/helper.js +58 -0
  143. package/lib/oauth/providers.js +35 -0
  144. package/lib/paths.js +7 -9
  145. package/lib/tools/CLAUDE.md +7 -3
  146. package/lib/tools/create-agent-job.js +137 -0
  147. package/lib/tools/docker.js +541 -73
  148. package/lib/tools/github.js +40 -71
  149. package/lib/utils/random-name.js +58 -0
  150. package/lib/utils/render-md.js +2 -13
  151. package/lib/voice/use-voice-input.js +8 -3
  152. package/package.json +12 -1
  153. package/setup/CLAUDE.md +32 -0
  154. package/setup/lib/providers.mjs +11 -0
  155. package/setup/lib/targets.mjs +2 -1
  156. package/setup/setup.mjs +64 -460
  157. package/templates/.env.example +3 -0
  158. package/templates/.github/workflows/CLAUDE.md.template +16 -0
  159. package/templates/.github/workflows/auto-merge.yml +1 -1
  160. package/templates/.github/workflows/notify-pr-complete.yml +8 -8
  161. package/templates/.github/workflows/rebuild-event-handler.yml +9 -5
  162. package/templates/.github/workflows/upgrade-event-handler.yml +1 -1
  163. package/templates/.gitignore.template +9 -10
  164. package/templates/CLAUDE.md +1 -1
  165. package/templates/CLAUDE.md.template +130 -74
  166. package/templates/README.md +75 -0
  167. package/templates/config/CLAUDE.md.template +40 -0
  168. package/templates/config/agent-chat/SYSTEM.md +76 -0
  169. package/templates/config/code-chat/SYSTEM.md +10 -0
  170. package/templates/config/litellm/main.yaml +6 -0
  171. package/templates/cron/.gitkeep +0 -0
  172. package/templates/cron/CLAUDE.md.template +24 -0
  173. package/templates/docker-compose.custom.yml +1 -1
  174. package/templates/docker-compose.litellm.yml +81 -0
  175. package/templates/docker-compose.yml +1 -1
  176. package/templates/docs/CLAUDE.md.template +12 -0
  177. package/templates/docs/CLI.md +59 -0
  178. package/templates/docs/CLUSTERS.md +151 -0
  179. package/templates/docs/CONFIGURATION.md +181 -0
  180. package/templates/docs/CRONS_AND_TRIGGERS.md +132 -0
  181. package/templates/docs/GETTING_STARTED.md +64 -0
  182. package/templates/docs/SECURITY.md +61 -0
  183. package/templates/docs/SKILLS.md +113 -0
  184. package/templates/docs/UPGRADING.md +92 -0
  185. package/templates/logs/.gitkeep +0 -0
  186. package/templates/skills/CLAUDE.md.template +109 -0
  187. package/templates/skills/README.md +0 -2
  188. package/templates/skills/{llm-secrets → get-secret}/SKILL.md +2 -2
  189. package/templates/skills/{llm-secrets/llm-secrets.js → get-secret/get-secret.js} +7 -7
  190. package/templates/triggers/.gitkeep +0 -0
  191. package/templates/triggers/CLAUDE.md.template +41 -0
  192. package/lib/ai/web-search.js +0 -44
  193. package/lib/chat/components/features-context.js +0 -14
  194. package/lib/chat/components/features-context.jsx +0 -13
  195. package/lib/tools/create-job.js +0 -97
  196. package/templates/.github/workflows/notify-job-failed.yml +0 -64
  197. package/templates/.github/workflows/run-job.yml +0 -89
  198. package/templates/config/CODE_PLANNING.md +0 -14
  199. package/templates/config/JOB_PLANNING.md +0 -240
  200. package/templates/config/SKILL_BUILDING_GUIDE.md +0 -96
  201. package/templates/config/WEB_SEARCH_AVAILABLE.md +0 -5
  202. package/templates/config/WEB_SEARCH_UNAVAILABLE.md +0 -3
  203. package/templates/skills/modify-self/SKILL.md +0 -12
  204. /package/lib/{cluster → chat}/components/code-log-view.js +0 -0
  205. /package/lib/{cluster → chat}/components/code-log-view.jsx +0 -0
  206. /package/templates/config/{JOB_AGENT.md → agent-job/AGENT_JOB.md} +0 -0
  207. /package/templates/config/{SOUL.md → agent-job/SOUL.md} +0 -0
  208. /package/templates/config/{JOB_SUMMARY.md → agent-job/SUMMARY.md} +0 -0
  209. /package/templates/config/{CLUSTER_ROLE_PROMPT.md → cluster/ROLE.md} +0 -0
  210. /package/templates/config/{CLUSTER_SYSTEM_PROMPT.md → cluster/SYSTEM.md} +0 -0
package/README.md CHANGED
@@ -22,36 +22,36 @@ Build autonomous AI agents that work for you 24/7, individually or in teams.
22
22
  │ │
23
23
  │ ┌─────────────────┐ ┌─────────────────┐ │
24
24
  │ │ Event Handler │ ──1──► │ GitHub │ │
25
- │ │ (creates job) │ │ (job/* branch) │ │
26
- │ └────────▲────────┘ └────────┬────────┘
27
- │ │
28
- │ │ 2 (triggers run-job.yml)
29
- │ │
30
-
31
- ┌─────────────────┐
32
- Docker Agent │
33
- (Pi/Claude Code)
34
- └────────┬────────┘
35
- │ │
36
- │ │ 3 (creates PR)
37
- │ │
38
-
39
- ┌─────────────────┐
40
- GitHub │
41
- (PR opened) │
42
- └────────┬────────┘
43
- │ │
44
- │ │ 4a (auto-merge.yml)
45
- │ │ 4b (rebuild-event-handler.yml)
46
- │ │
47
- │ 5 (notify-pr-complete.yml /
48
- │ │ notify-job-failed.yml)
49
- └───────────────────────────┘
25
+ │ │ (creates branch)│ │(agent-job/* br) │ │
26
+ │ └────────▲────────┘ └─────────────────┘
27
+ │ │
28
+ │ │ 2 (launches Docker container locally)
29
+ │ │
30
+ │ ▼
31
+ ┌─────────────────┐
32
+ │ Docker Agent │
33
+ (coding agent)
34
+ └────────┬────────┘
35
+ │ │
36
+ │ │ 3 (commits, pushes, creates PR)
37
+ │ │
38
+ │ ▼
39
+ ┌─────────────────┐
40
+ │ GitHub │
41
+ │ (PR opened) │
42
+ └────────┬────────┘
43
+ │ │
44
+ │ │ 4a (auto-merge.yml)
45
+ │ │ 4b (rebuild-event-handler.yml)
46
+ │ │
47
+ │ 5 (notify-pr-complete.yml
48
+ │ │ webhook to event handler)
49
+ └──────────────────────────► Event Handler
50
50
  │ │
51
51
  └──────────────────────────────────────────────────────────────────────┘
52
52
  ```
53
53
 
54
- You interact with your bot via the web chat interface or Telegram (optional). The Event Handler creates a job branch. GitHub Actions spins up a Docker container with the Pi coding agent. The agent does the work, commits the results, and opens a PR. Auto-merge handles the rest. You get a notification when it's done.
54
+ You interact with your bot via the web chat interface or Telegram (optional). The Event Handler creates an agent-job branch and launches a Docker container locally with the coding agent. The agent does the work, commits the results, pushes, and opens a PR. Auto-merge handles the rest. You get a notification when it's done.
55
55
 
56
56
  ---
57
57
 
@@ -94,19 +94,18 @@ npm run setup
94
94
  ```
95
95
 
96
96
  The wizard walks you through everything:
97
- - Checks prerequisites (Node.js, Git, GitHub CLI)
97
+ - Checks prerequisites (Node.js, Git, GitHub CLI, Docker)
98
98
  - Creates a GitHub repository and pushes your initial commit
99
99
  - Creates a GitHub Personal Access Token (scoped to your repo)
100
- - Collects API keys (Anthropic required; OpenAI, Brave optional)
101
- - Sets GitHub repository secrets and variables
102
- - Generates `.env`
103
- - Builds the project and starts Docker for you
100
+ - Configures your public URL and webhook secret
101
+ - Syncs settings to `.env`, database, and GitHub secrets/variables
102
+ - Starts Docker for you
104
103
 
105
104
  **That's it.** Visit your APP_URL when the wizard finishes.
106
105
 
107
106
  - **Web Chat**: Visit your APP_URL to chat with your agent, create jobs, upload files
108
107
  - **Telegram** (optional): Run `npm run setup-telegram` to connect a Telegram bot
109
- - **Webhook**: Send a POST to `/api/create-job` with your API key to create jobs programmatically
108
+ - **Webhook**: Send a POST to `/api/create-agent-job` with your API key to create jobs programmatically
110
109
  - **Cron**: Edit `config/CRONS.json` to schedule recurring jobs
111
110
 
112
111
  ### Chat vs Agent LLM
@@ -135,7 +134,7 @@ claude setup-token
135
134
 
136
135
  Paste the token (starts with `sk-ant-oat01-`) into the setup wizard. Your agent jobs will now run through your subscription. Note that usage counts toward your Claude.ai limits, and you still need an API key for the chat side.
137
136
 
138
- See [Claude Code vs Pi](docs/CLAUDE_CODE_VS_PI.md) for more details on the two agent backends.
137
+ See [Coding Agents](docs/CODING_AGENTS.md) for details on all five agent backends.
139
138
 
140
139
  > **Local installs**: Your server needs to be reachable from the internet for GitHub webhooks and Telegram. On a VPS/cloud server, your APP_URL is just your domain. For local development, use [ngrok](https://ngrok.com) (`ngrok http 80`) or port forwarding to expose your machine.
141
140
  >
@@ -187,9 +186,9 @@ See [Security](docs/SECURITY.md) for full details on what's exposed, the risks,
187
186
 
188
187
  ## Different Models
189
188
 
190
- 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.
189
+ thepopebot supports 9 built-in LLM providers (Anthropic, OpenAI, Google, DeepSeek, MiniMax, Mistral, xAI, Kimi, OpenRouter) plus custom OpenAI-compatible endpoints. The chat layer and coding agents are independent — use Claude for interactive chat and a different model for code tasks, or run everything on a single provider.
191
190
 
192
- See [Different Models](docs/RUNNING_DIFFERENT_MODELS.md) for the full guide: Event Handler config, job defaults, per-job overrides, provider table, and custom provider setup.
191
+ See [Different Models](docs/RUNNING_DIFFERENT_MODELS.md) for the full provider reference, admin UI configuration, per-job overrides, and custom provider setup.
193
192
 
194
193
  ---
195
194
 
@@ -199,13 +198,13 @@ See [Different Models](docs/RUNNING_DIFFERENT_MODELS.md) for the full guide: Eve
199
198
  |----------|-------------|
200
199
  | [Architecture](docs/ARCHITECTURE.md) | Two-layer design, file structure, API endpoints, GitHub Actions, Docker agent |
201
200
  | [CLI Reference](docs/CLI.md) | `init`, managed vs user files, template conventions, all CLI commands |
202
- | [Configuration](docs/CONFIGURATION.md) | Environment variables, GitHub secrets, repo variables, ngrok, Telegram setup |
201
+ | [Configuration](docs/CONFIGURATION.md) | Admin UI, DB-backed config, infrastructure variables, GitHub secrets, Docker Compose |
203
202
  | [Customization](docs/CUSTOMIZATION.md) | Personality, skills, operating system files, using your bot, security details |
204
203
  | [Chat Integrations](docs/CHAT_INTEGRATIONS.md) | Web chat, Telegram, adding new channels |
205
- | [Different Models](docs/RUNNING_DIFFERENT_MODELS.md) | Event Handler vs job model config, per-job overrides, providers, custom provider |
204
+ | [Different Models](docs/RUNNING_DIFFERENT_MODELS.md) | 9 built-in LLM providers, chat vs coding agent config, per-job overrides, custom providers |
206
205
  | [Auto-Merge](docs/AUTO_MERGE.md) | Auto-merge controls, ALLOWED_PATHS configuration |
207
206
  | [Deployment](docs/DEPLOYMENT.md) | VPS setup, Docker Compose, HTTPS with Let's Encrypt |
208
- | [Claude Code vs Pi](docs/CLAUDE_CODE_VS_PI.md) | Comparing the two agent backends (subscription vs API credits) |
207
+ | [Coding Agents](docs/CODING_AGENTS.md) | 5 coding agent backends, OAuth tokens, LiteLLM proxy, per-agent config |
209
208
  | [How to Build Skills](docs/HOW_TO_BUILD_SKILLS.md) | Guide to building and activating agent skills |
210
209
  | [Pre-Release](docs/PRE_RELEASE.md) | Installing beta/alpha builds |
211
210
  | [Code Workspaces](docs/CODE_WORKSPACES.md) | Interactive Docker containers with in-browser terminal |
package/api/CLAUDE.md CHANGED
@@ -10,21 +10,22 @@ Auth flow: `x-api-key` header -> `verifyApiKey()` -> database lookup (hashed, ti
10
10
 
11
11
  ## Do NOT use these routes for browser UI
12
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.
13
+ Browser-facing data fetching uses **fetch route handlers** colocated with pages (`route.js` files in `web/app/`). These check `auth()` session — never use `/api` routes from the browser. Server actions (`'use server'`) are used only for **mutations** (rename, delete, star, config updates) never for data fetching (causes page refresh issues). Handler implementations live in `lib/chat/api.js`; route files are thin re-exports.
14
14
 
15
15
  | Caller | Mechanism | Auth |
16
16
  |--------|-----------|------|
17
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 |
18
+ | Browser UI (data fetching) | Fetch route handler colocated with page | `auth()` session |
19
+ | Browser UI (mutations) | Server action | `requireAuth()` session |
20
+ | Browser UI (streaming) | `/stream/chat`, `/stream/containers`, `/stream/cluster/*/logs` | `auth()` session |
20
21
 
21
22
  ## Routes
22
23
 
23
24
  | Method | Path | Auth | Handler |
24
25
  |--------|------|------|---------|
25
26
  | GET | `/api/ping` | None | Health check |
26
- | POST | `/api/create-job` | `x-api-key` | Create agent job |
27
- | GET | `/api/jobs/status` | `x-api-key` | Job status (query: `?job_id=`) |
27
+ | POST | `/api/create-agent-job` | `x-api-key` | Create agent job |
28
+ | GET | `/api/agent-jobs/status` | `x-api-key` | Agent job status (query: `?agent_job_id=`) |
28
29
  | POST | `/api/telegram/webhook` | Telegram webhook secret | Telegram message handler |
29
30
  | POST | `/api/telegram/register` | `x-api-key` | Register bot token + webhook URL |
30
31
  | POST | `/api/github/webhook` | GitHub webhook secret | GitHub event handler |
package/api/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import { createHash, timingSafeEqual } from 'crypto';
2
- import { createJob } from '../lib/tools/create-job.js';
2
+ import { createAgentJob } from '../lib/tools/create-agent-job.js';
3
3
  import { setWebhook } from '../lib/tools/telegram.js';
4
- import { getJobStatus, fetchJobLog } from '../lib/tools/github.js';
4
+ import { getAgentJobStatus, fetchAgentJobLog } from '../lib/tools/github.js';
5
5
  import { getTelegramAdapter } from '../lib/channels/index.js';
6
- import { chat, summarizeJob } from '../lib/ai/index.js';
6
+ import { chat, summarizeAgentJob } from '../lib/ai/index.js';
7
7
  import { createNotification } from '../lib/db/notifications.js';
8
8
  import { loadTriggers } from '../lib/triggers.js';
9
9
  import { verifyApiKey } from '../lib/db/api-keys.js';
10
10
  import { getConfig } from '../lib/config.js';
11
+ import { parseOAuthState, exchangeCodeForToken } from '../lib/oauth/helper.js';
12
+ import { setAgentJobSecret } from '../lib/db/config.js';
11
13
 
12
14
  // Bot token — resolved from DB/env, can be overridden by /telegram/register
13
15
  let telegramBotToken = null;
@@ -31,7 +33,7 @@ function getFireTriggers() {
31
33
  }
32
34
 
33
35
  // Routes that have their own authentication
34
- const PUBLIC_ROUTES = ['/telegram/webhook', '/github/webhook', '/ping'];
36
+ const PUBLIC_ROUTES = ['/telegram/webhook', '/github/webhook', '/ping', '/oauth/callback'];
35
37
 
36
38
  /**
37
39
  * Timing-safe string comparison.
@@ -71,28 +73,34 @@ function checkAuth(routePath, request) {
71
73
  }
72
74
 
73
75
  /**
74
- * Extract job ID from branch name (e.g., "job/abc123" -> "abc123")
76
+ * Extract agent job ID from branch name (e.g., "agent-job/abc123" -> "abc123")
75
77
  */
76
- function extractJobId(branchName) {
77
- if (!branchName || !branchName.startsWith('job/')) return null;
78
- return branchName.slice(4);
78
+ function extractAgentJobId(branchName) {
79
+ if (!branchName) return null;
80
+ if (branchName.startsWith('agent-job/')) return branchName.slice(10);
81
+ // Backwards compatibility with old job/ prefix
82
+ if (branchName.startsWith('job/')) return branchName.slice(4);
83
+ return null;
79
84
  }
80
85
 
81
86
  // ─────────────────────────────────────────────────────────────────────────────
82
87
  // Route handlers
83
88
  // ─────────────────────────────────────────────────────────────────────────────
84
89
 
85
- async function handleWebhook(request) {
90
+ async function handleCreateAgentJob(request) {
86
91
  const body = await request.json();
87
92
  const { job } = body;
88
93
  if (!job) return Response.json({ error: 'Missing job field' }, { status: 400 });
89
94
 
90
95
  try {
91
- const result = await createJob(job);
96
+ const result = await createAgentJob(job, {
97
+ llmModel: body.llm_model,
98
+ agentBackend: body.agent_backend,
99
+ });
92
100
  return Response.json(result);
93
101
  } catch (err) {
94
102
  console.error(err);
95
- return Response.json({ error: 'Failed to create job' }, { status: 500 });
103
+ return Response.json({ error: 'Failed to create agent job' }, { status: 500 });
96
104
  }
97
105
  }
98
106
 
@@ -168,14 +176,14 @@ async function handleGithubWebhook(request) {
168
176
  }
169
177
 
170
178
  const payload = await request.json();
171
- const jobId = payload.job_id || extractJobId(payload.branch);
172
- if (!jobId) return Response.json({ ok: true, skipped: true, reason: 'not a job' });
179
+ const agentJobId = payload.agent_job_id || payload.job_id || extractAgentJobId(payload.branch);
180
+ if (!agentJobId) return Response.json({ ok: true, skipped: true, reason: 'not an agent job' });
173
181
 
174
182
  try {
175
183
  // Fetch log from repo via API (no longer sent in payload)
176
184
  let log = payload.log || '';
177
185
  if (!log) {
178
- log = await fetchJobLog(jobId, payload.commit_sha);
186
+ log = await fetchAgentJobLog(agentJobId, payload.commit_sha);
179
187
  }
180
188
 
181
189
  const results = {
@@ -189,10 +197,10 @@ async function handleGithubWebhook(request) {
189
197
  commit_message: payload.commit_message || '',
190
198
  };
191
199
 
192
- const message = await summarizeJob(results);
200
+ const message = await summarizeAgentJob(results);
193
201
  await createNotification(message, payload);
194
202
 
195
- console.log(`Notification saved for job ${jobId.slice(0, 8)}`);
203
+ console.log(`Notification saved for agent-job ${agentJobId.slice(0, 8)}`);
196
204
 
197
205
  return Response.json({ ok: true, notified: true });
198
206
  } catch (err) {
@@ -201,18 +209,77 @@ async function handleGithubWebhook(request) {
201
209
  }
202
210
  }
203
211
 
204
- async function handleJobStatus(request) {
212
+ async function handleAgentJobStatus(request) {
205
213
  try {
206
214
  const url = new URL(request.url);
207
- const jobId = url.searchParams.get('job_id');
208
- const result = await getJobStatus(jobId);
215
+ const agentJobId = url.searchParams.get('agent_job_id') || url.searchParams.get('job_id');
216
+ const result = await getAgentJobStatus(agentJobId);
209
217
  return Response.json(result);
210
218
  } catch (err) {
211
- console.error('Failed to get job status:', err);
212
- return Response.json({ error: 'Failed to get job status' }, { status: 500 });
219
+ console.error('Failed to get agent job status:', err);
220
+ return Response.json({ error: 'Failed to get agent job status' }, { status: 500 });
221
+ }
222
+ }
223
+
224
+ async function handleOAuthCallback(request) {
225
+ const url = new URL(request.url);
226
+ const code = url.searchParams.get('code');
227
+ const stateParam = url.searchParams.get('state');
228
+ const error = url.searchParams.get('error');
229
+
230
+ if (error) {
231
+ const desc = url.searchParams.get('error_description') || error;
232
+ return oauthResultPage(false, desc);
233
+ }
234
+
235
+ if (!code || !stateParam) {
236
+ return oauthResultPage(false, 'Missing code or state parameter.');
237
+ }
238
+
239
+ try {
240
+ const state = parseOAuthState(stateParam);
241
+ const redirectUri = `${process.env.AUTH_URL}/api/oauth/callback`;
242
+
243
+ const tokenData = await exchangeCodeForToken({
244
+ code,
245
+ clientId: state.clientId,
246
+ clientSecret: state.clientSecret,
247
+ tokenUrl: state.tokenUrl,
248
+ redirectUri,
249
+ });
250
+
251
+ // Save the full token JSON as the secret value
252
+ const tokenJson = JSON.stringify(tokenData);
253
+ setAgentJobSecret(state.secretName, tokenJson, 'oauth');
254
+
255
+ return oauthResultPage(true, state.secretName);
256
+ } catch (err) {
257
+ console.error('OAuth callback error:', err);
258
+ return oauthResultPage(false, err.message || 'Token exchange failed.');
213
259
  }
214
260
  }
215
261
 
262
+ function oauthResultPage(success, detail) {
263
+ const safe = String(detail).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
264
+ const messagePayload = JSON.stringify({ type: success ? 'oauth-success' : 'oauth-error', detail: safe });
265
+ const fallback = success
266
+ ? `Token saved as <strong>${safe}</strong>. You can close this tab and return to settings.`
267
+ : `Error: ${safe}`;
268
+
269
+ const html = `<!DOCTYPE html><html><head><title>OAuth ${success ? 'Success' : 'Error'}</title></head><body>
270
+ <script>
271
+ if (window.opener) {
272
+ window.opener.postMessage(${messagePayload}, window.location.origin);
273
+ window.close();
274
+ } else {
275
+ document.body.innerHTML = '<p style="font-family:sans-serif;padding:2rem;">${fallback.replace(/'/g, "\\'")}</p>';
276
+ }
277
+ </script>
278
+ <noscript><p style="font-family:sans-serif;padding:2rem;">${fallback}</p></noscript>
279
+ </body></html>`;
280
+ return new Response(html, { status: success ? 200 : 400, headers: { 'Content-Type': 'text/html' } });
281
+ }
282
+
216
283
  // ─────────────────────────────────────────────────────────────────────────────
217
284
  // Next.js Route Handlers (catch-all)
218
285
  // ─────────────────────────────────────────────────────────────────────────────
@@ -247,7 +314,7 @@ async function POST(request) {
247
314
 
248
315
  // Route to handler
249
316
  switch (routePath) {
250
- case '/create-job': return handleWebhook(request);
317
+ case '/create-agent-job': return handleCreateAgentJob(request);
251
318
  case '/telegram/webhook': return handleTelegramWebhook(request);
252
319
  case '/telegram/register': return handleTelegramRegister(request);
253
320
  case '/github/webhook': return handleGithubWebhook(request);
@@ -264,9 +331,10 @@ async function GET(request) {
264
331
  if (authError) return authError;
265
332
 
266
333
  switch (routePath) {
267
- case '/ping': return Response.json({ message: 'Pong!' });
268
- case '/jobs/status': return handleJobStatus(request);
269
- default: return Response.json({ error: 'Not found' }, { status: 404 });
334
+ case '/ping': return Response.json({ message: 'Pong!' });
335
+ case '/agent-jobs/status': return handleAgentJobStatus(request);
336
+ case '/oauth/callback': return handleOAuthCallback(request);
337
+ default: return Response.json({ error: 'Not found' }, { status: 404 });
270
338
  }
271
339
  }
272
340
 
package/bin/CLAUDE.md ADDED
@@ -0,0 +1,37 @@
1
+ # bin/ — CLI Tools
2
+
3
+ Entry point: `cli.js` (invoked via `npx thepopebot <command>`).
4
+
5
+ ## Commands
6
+
7
+ | Command | Purpose |
8
+ |---------|---------|
9
+ | `init [--no-managed] [--no-install]` | Scaffold project from templates, sync managed files, create `.env`, install deps |
10
+ | `setup` | Run interactive setup wizard (see `setup/CLAUDE.md`) |
11
+ | `setup-telegram` | Reconfigure Telegram webhook |
12
+ | `upgrade [@beta\|version]` | Upgrade package, run init, rebuild, commit, push, restart Docker |
13
+ | `reset [file]` | Restore a template file to defaults |
14
+ | `diff [file]` | Show diff between user file and package template |
15
+ | `reset-auth` | Regenerate `AUTH_SECRET` (invalidates all sessions) |
16
+ | `set-var <KEY> [VALUE]` | Set GitHub repository variable |
17
+ | `user:password <email>` | Change user password |
18
+ | `sync <path>` | Dev helper — sync local package to test install |
19
+
20
+ ## Managed Paths System
21
+
22
+ `managed-paths.js` defines files auto-synced by `init`. These are overwritten on every init/upgrade — users should not edit them.
23
+
24
+ **Managed paths**: `.github/workflows/`, `docker-compose.yml`, `.dockerignore`, `.gitignore`, `CLAUDE.md`, `config/CLAUDE.md`, `skills/CLAUDE.md`, `cron/CLAUDE.md`, `triggers/CLAUDE.md`, `docs/CLAUDE.md`.
25
+
26
+ `isManaged(relPath)` — returns true if a path is managed (exact match or directory prefix).
27
+
28
+ ## Template Processing
29
+
30
+ - Templates live in `templates/` with optional `.template` suffix
31
+ - `.template` suffix is stripped when copying to the user project (e.g., `CLAUDE.md.template` → `CLAUDE.md`)
32
+ - Managed files are deleted from user projects if removed from templates
33
+ - Non-managed template files are only created, never overwritten
34
+
35
+ ## docker-build.js
36
+
37
+ Builds the event-handler Docker image locally. Used by `docker-compose.yml` build step. Bakes the npm package, `web/` source, and `.next` build output into the image.
package/bin/cli.js CHANGED
@@ -57,8 +57,6 @@ Commands:
57
57
  reset [file] Restore a template file (or list available templates)
58
58
  diff [file] Show differences between project files and package templates
59
59
  sync <path> Sync local package to a test install (build, pack, Docker)
60
- set-agent-secret <KEY> [VALUE] Set a GitHub secret with AGENT_ prefix (also updates .env)
61
- set-agent-llm-secret <KEY> [VALUE] Set a GitHub secret with AGENT_LLM_ prefix
62
60
  set-var <KEY> [VALUE] Set a GitHub repository variable
63
61
  user:password <email> Change a user's password
64
62
  `);
@@ -89,6 +87,7 @@ async function init() {
89
87
  const packageDir = path.join(__dirname, '..');
90
88
  const templatesDir = path.join(packageDir, 'templates');
91
89
  const noManaged = args.includes('--no-managed');
90
+ const noInstall = args.includes('--no-install');
92
91
 
93
92
  // Guard: warn if the directory is not empty (unless it's an existing thepopebot project)
94
93
  const entries = fs.readdirSync(cwd);
@@ -262,18 +261,8 @@ async function init() {
262
261
  console.log(' Skipped package.json (already exists)');
263
262
  }
264
263
 
265
- // Create .gitkeep files for empty dirs
266
- const gitkeepDirs = ['cron', 'triggers', 'logs', 'tmp', 'data', 'data/clusters'];
267
- for (const dir of gitkeepDirs) {
268
- const gitkeep = path.join(cwd, dir, '.gitkeep');
269
- if (!fs.existsSync(gitkeep)) {
270
- fs.mkdirSync(path.join(cwd, dir), { recursive: true });
271
- fs.writeFileSync(gitkeep, '');
272
- }
273
- }
274
-
275
264
  // Create default skill activation symlinks
276
- const defaultSkills = ['browser-tools', 'llm-secrets', 'modify-self'];
265
+ const defaultSkills = ['get-secret'];
277
266
  const activeDir = path.join(cwd, 'skills', 'active');
278
267
  fs.mkdirSync(activeDir, { recursive: true });
279
268
  for (const skill of defaultSkills) {
@@ -325,9 +314,11 @@ async function init() {
325
314
  console.log(' To reset to default: npx thepopebot reset <file>');
326
315
  }
327
316
 
328
- // Run npm install
329
- console.log('\nInstalling dependencies...\n');
330
- execSync('npm install', { stdio: 'inherit', cwd });
317
+ // Run npm install to ensure lock file reflects installed dependencies
318
+ if (!noInstall) {
319
+ console.log('\nInstalling dependencies...\n');
320
+ execSync('npm install', { stdio: 'inherit', cwd });
321
+ }
331
322
 
332
323
  // Create or update .env with auto-generated infrastructure values
333
324
  const envPath = path.join(cwd, '.env');
@@ -343,6 +334,7 @@ async function init() {
343
334
 
344
335
  AUTH_SECRET=${authSecret}
345
336
  AUTH_TRUST_HOST=true
337
+ DATABASE_PATH=data/db/thepopebot.sqlite
346
338
  THEPOPEBOT_VERSION=${version}
347
339
 
348
340
  # Uncomment to use a custom docker-compose file that won't be overwritten by upgrades.
@@ -641,7 +633,7 @@ async function upgrade() {
641
633
  const running = execSync('docker compose ps --status running -q', { encoding: 'utf8', cwd }).trim();
642
634
  if (running) {
643
635
  console.log(' Pulling new image and restarting Docker containers...\n');
644
- execSync('docker compose pull event-handler && docker compose up -d', { stdio: 'inherit', cwd });
636
+ execSync('docker compose pull event-handler && docker compose up -d --force-recreate event-handler', { stdio: 'inherit', cwd });
645
637
  }
646
638
  } catch {
647
639
  // Docker not available or not running — skip
@@ -717,56 +709,6 @@ async function promptForValue(key) {
717
709
  return value;
718
710
  }
719
711
 
720
- async function setAgentSecret(key, value) {
721
- if (!key) {
722
- console.error('\n Usage: thepopebot set-agent-secret <KEY> [VALUE]\n');
723
- console.error(' Example: thepopebot set-agent-secret ANTHROPIC_API_KEY\n');
724
- process.exit(1);
725
- }
726
-
727
- if (!value) value = await promptForValue(key);
728
-
729
- const { owner, repo } = loadRepoInfo();
730
- const prefixedName = `AGENT_${key}`;
731
-
732
- const { setSecret } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
733
- const { updateEnvVariable } = await import(path.join(__dirname, '..', 'setup', 'lib', 'auth.mjs'));
734
-
735
- const result = await setSecret(owner, repo, prefixedName, value);
736
- if (result.success) {
737
- console.log(`\n Set GitHub secret: ${prefixedName}`);
738
- updateEnvVariable(key, value);
739
- console.log(` Updated .env: ${key}`);
740
- console.log('');
741
- } else {
742
- console.error(`\n Failed to set ${prefixedName}: ${result.error}\n`);
743
- process.exit(1);
744
- }
745
- }
746
-
747
- async function setAgentLlmSecret(key, value) {
748
- if (!key) {
749
- console.error('\n Usage: thepopebot set-agent-llm-secret <KEY> [VALUE]\n');
750
- console.error(' Example: thepopebot set-agent-llm-secret BRAVE_API_KEY\n');
751
- process.exit(1);
752
- }
753
-
754
- if (!value) value = await promptForValue(key);
755
-
756
- const { owner, repo } = loadRepoInfo();
757
- const prefixedName = `AGENT_LLM_${key}`;
758
-
759
- const { setSecret } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
760
-
761
- const result = await setSecret(owner, repo, prefixedName, value);
762
- if (result.success) {
763
- console.log(`\n Set GitHub secret: ${prefixedName}\n`);
764
- } else {
765
- console.error(`\n Failed to set ${prefixedName}: ${result.error}\n`);
766
- process.exit(1);
767
- }
768
- }
769
-
770
712
  async function setVar(key, value) {
771
713
  if (!key) {
772
714
  console.error('\n Usage: thepopebot set-var <KEY> [VALUE]\n');
@@ -849,12 +791,6 @@ switch (command) {
849
791
  await sync(args[0]);
850
792
  break;
851
793
  }
852
- case 'set-agent-secret':
853
- await setAgentSecret(args[0], args[1]);
854
- break;
855
- case 'set-agent-llm-secret':
856
- await setAgentLlmSecret(args[0], args[1]);
857
- break;
858
794
  case 'set-var':
859
795
  await setVar(args[0], args[1]);
860
796
  break;