@harbinger-ai/harbinger 0.1.0

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 (317) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +406 -0
  3. package/agents/README.md +76 -0
  4. package/agents/_template/CONFIG.yaml +7 -0
  5. package/agents/_template/HEARTBEAT.md +59 -0
  6. package/agents/_template/IDENTITY.md +4 -0
  7. package/agents/_template/SKILLS.md +1 -0
  8. package/agents/_template/SOUL.md +25 -0
  9. package/agents/_template/TOOLS.md +3 -0
  10. package/agents/binary-reverser/CONFIG.yaml +21 -0
  11. package/agents/binary-reverser/HEARTBEAT.md +65 -0
  12. package/agents/binary-reverser/IDENTITY.md +1 -0
  13. package/agents/binary-reverser/SKILLS.md +1 -0
  14. package/agents/binary-reverser/SOUL.md +23 -0
  15. package/agents/binary-reverser/TOOLS.md +99 -0
  16. package/agents/browser-agent/CONFIG.yaml +20 -0
  17. package/agents/browser-agent/HEARTBEAT.md +79 -0
  18. package/agents/browser-agent/IDENTITY.md +5 -0
  19. package/agents/browser-agent/SKILLS.md +86 -0
  20. package/agents/browser-agent/SOUL.md +23 -0
  21. package/agents/browser-agent/TOOLS.md +186 -0
  22. package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
  23. package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
  24. package/agents/cloud-infiltrator/IDENTITY.md +1 -0
  25. package/agents/cloud-infiltrator/SKILLS.md +1 -0
  26. package/agents/cloud-infiltrator/SOUL.md +23 -0
  27. package/agents/cloud-infiltrator/TOOLS.md +68 -0
  28. package/agents/coding-assistant/CONFIG.yaml +22 -0
  29. package/agents/coding-assistant/HEARTBEAT.md +57 -0
  30. package/agents/coding-assistant/IDENTITY.md +5 -0
  31. package/agents/coding-assistant/SKILLS.md +69 -0
  32. package/agents/coding-assistant/SOUL.md +60 -0
  33. package/agents/coding-assistant/TOOLS.md +168 -0
  34. package/agents/learning-agent/CONFIG.yaml +21 -0
  35. package/agents/learning-agent/HEARTBEAT.md +63 -0
  36. package/agents/learning-agent/IDENTITY.md +5 -0
  37. package/agents/learning-agent/SKILLS.md +86 -0
  38. package/agents/learning-agent/SOUL.md +77 -0
  39. package/agents/learning-agent/TOOLS.md +145 -0
  40. package/agents/maintainer/CONFIG.yaml +31 -0
  41. package/agents/maintainer/HEARTBEAT.md +28 -0
  42. package/agents/maintainer/IDENTITY.md +33 -0
  43. package/agents/maintainer/SKILLS.md +24 -0
  44. package/agents/maintainer/SOUL.md +61 -0
  45. package/agents/maintainer/TOOLS.md +29 -0
  46. package/agents/maintainer/lib/engine.js +279 -0
  47. package/agents/maintainer/lib/safe-fixer.js +183 -0
  48. package/agents/morning-brief/CONFIG.yaml +22 -0
  49. package/agents/morning-brief/HEARTBEAT.md +60 -0
  50. package/agents/morning-brief/IDENTITY.md +5 -0
  51. package/agents/morning-brief/SKILLS.md +56 -0
  52. package/agents/morning-brief/SOUL.md +64 -0
  53. package/agents/morning-brief/TOOLS.md +112 -0
  54. package/agents/osint-detective/CONFIG.yaml +24 -0
  55. package/agents/osint-detective/HEARTBEAT.md +66 -0
  56. package/agents/osint-detective/IDENTITY.md +1 -0
  57. package/agents/osint-detective/SKILLS.md +1 -0
  58. package/agents/osint-detective/SOUL.md +23 -0
  59. package/agents/osint-detective/TOOLS.md +81 -0
  60. package/agents/recon-scout/CONFIG.yaml +22 -0
  61. package/agents/recon-scout/HEARTBEAT.md +79 -0
  62. package/agents/recon-scout/IDENTITY.md +1 -0
  63. package/agents/recon-scout/SKILLS.md +1 -0
  64. package/agents/recon-scout/SOUL.md +23 -0
  65. package/agents/recon-scout/TOOLS.md +93 -0
  66. package/agents/report-writer/CONFIG.yaml +21 -0
  67. package/agents/report-writer/HEARTBEAT.md +63 -0
  68. package/agents/report-writer/IDENTITY.md +1 -0
  69. package/agents/report-writer/SKILLS.md +1 -0
  70. package/agents/report-writer/SOUL.md +23 -0
  71. package/agents/report-writer/TOOLS.md +69 -0
  72. package/agents/shared/README.md +13 -0
  73. package/agents/web-hacker/CONFIG.yaml +24 -0
  74. package/agents/web-hacker/HEARTBEAT.md +78 -0
  75. package/agents/web-hacker/IDENTITY.md +1 -0
  76. package/agents/web-hacker/SKILLS.md +1 -0
  77. package/agents/web-hacker/SOUL.md +23 -0
  78. package/agents/web-hacker/TOOLS.md +86 -0
  79. package/api/CLAUDE.md +19 -0
  80. package/api/index.js +274 -0
  81. package/bin/cli.js +620 -0
  82. package/bin/local.sh +31 -0
  83. package/bin/postinstall.js +63 -0
  84. package/config/index.js +24 -0
  85. package/config/instrumentation.js +93 -0
  86. package/drizzle/0000_initial.sql +52 -0
  87. package/drizzle/0001_bounty_and_registry.sql +82 -0
  88. package/drizzle/0002_sync_columns.sql +7 -0
  89. package/drizzle/0003_graceful_bloodscream.sql +86 -0
  90. package/drizzle/meta/0000_snapshot.json +321 -0
  91. package/drizzle/meta/0003_snapshot.json +878 -0
  92. package/drizzle/meta/_journal.json +34 -0
  93. package/drizzle/relations.ts +3 -0
  94. package/drizzle/schema.ts +145 -0
  95. package/lib/actions.js +47 -0
  96. package/lib/agents.js +166 -0
  97. package/lib/ai/agent.js +96 -0
  98. package/lib/ai/autonomous-engine.js +261 -0
  99. package/lib/ai/index.js +359 -0
  100. package/lib/ai/model-router.js +254 -0
  101. package/lib/ai/model.js +73 -0
  102. package/lib/ai/tools.js +84 -0
  103. package/lib/auth/actions.js +28 -0
  104. package/lib/auth/config.js +27 -0
  105. package/lib/auth/edge-config.js +27 -0
  106. package/lib/auth/index.js +27 -0
  107. package/lib/auth/middleware.js +53 -0
  108. package/lib/bounty/actions.js +119 -0
  109. package/lib/bounty/findings.js +64 -0
  110. package/lib/bounty/programs.js +34 -0
  111. package/lib/bounty/sync-targets.js +267 -0
  112. package/lib/bounty/targets.js +33 -0
  113. package/lib/channels/base.js +56 -0
  114. package/lib/channels/index.js +15 -0
  115. package/lib/channels/telegram.js +148 -0
  116. package/lib/chat/actions.js +288 -0
  117. package/lib/chat/api.js +135 -0
  118. package/lib/chat/components/app-sidebar.js +237 -0
  119. package/lib/chat/components/app-sidebar.jsx +289 -0
  120. package/lib/chat/components/chat-header.js +27 -0
  121. package/lib/chat/components/chat-header.jsx +37 -0
  122. package/lib/chat/components/chat-input.js +230 -0
  123. package/lib/chat/components/chat-input.jsx +228 -0
  124. package/lib/chat/components/chat-nav-context.js +11 -0
  125. package/lib/chat/components/chat-nav-context.jsx +11 -0
  126. package/lib/chat/components/chat-page.js +81 -0
  127. package/lib/chat/components/chat-page.jsx +100 -0
  128. package/lib/chat/components/chat.js +150 -0
  129. package/lib/chat/components/chat.jsx +182 -0
  130. package/lib/chat/components/chats-page.js +302 -0
  131. package/lib/chat/components/chats-page.jsx +330 -0
  132. package/lib/chat/components/crons-page.js +172 -0
  133. package/lib/chat/components/crons-page.jsx +244 -0
  134. package/lib/chat/components/enhanced-tool-call.js +103 -0
  135. package/lib/chat/components/enhanced-tool-call.jsx +139 -0
  136. package/lib/chat/components/findings-page.js +175 -0
  137. package/lib/chat/components/findings-page.jsx +214 -0
  138. package/lib/chat/components/greeting.js +22 -0
  139. package/lib/chat/components/greeting.jsx +26 -0
  140. package/lib/chat/components/icons.js +777 -0
  141. package/lib/chat/components/icons.jsx +741 -0
  142. package/lib/chat/components/index.js +26 -0
  143. package/lib/chat/components/mcp-page.js +260 -0
  144. package/lib/chat/components/mcp-page.jsx +355 -0
  145. package/lib/chat/components/message.js +289 -0
  146. package/lib/chat/components/message.jsx +315 -0
  147. package/lib/chat/components/messages.js +66 -0
  148. package/lib/chat/components/messages.jsx +77 -0
  149. package/lib/chat/components/notifications-page.js +56 -0
  150. package/lib/chat/components/notifications-page.jsx +87 -0
  151. package/lib/chat/components/page-layout.js +21 -0
  152. package/lib/chat/components/page-layout.jsx +28 -0
  153. package/lib/chat/components/registry-page.js +222 -0
  154. package/lib/chat/components/registry-page.jsx +255 -0
  155. package/lib/chat/components/settings-layout.js +40 -0
  156. package/lib/chat/components/settings-layout.jsx +54 -0
  157. package/lib/chat/components/settings-secrets-page.js +216 -0
  158. package/lib/chat/components/settings-secrets-page.jsx +264 -0
  159. package/lib/chat/components/sidebar-history-item.js +132 -0
  160. package/lib/chat/components/sidebar-history-item.jsx +113 -0
  161. package/lib/chat/components/sidebar-history.js +115 -0
  162. package/lib/chat/components/sidebar-history.jsx +157 -0
  163. package/lib/chat/components/sidebar-user-nav.js +63 -0
  164. package/lib/chat/components/sidebar-user-nav.jsx +73 -0
  165. package/lib/chat/components/status-bar.js +39 -0
  166. package/lib/chat/components/status-bar.jsx +51 -0
  167. package/lib/chat/components/swarm-page.js +157 -0
  168. package/lib/chat/components/swarm-page.jsx +210 -0
  169. package/lib/chat/components/targets-page.js +376 -0
  170. package/lib/chat/components/targets-page.jsx +389 -0
  171. package/lib/chat/components/tool-call.js +86 -0
  172. package/lib/chat/components/tool-call.jsx +104 -0
  173. package/lib/chat/components/tool-panel.js +107 -0
  174. package/lib/chat/components/tool-panel.jsx +145 -0
  175. package/lib/chat/components/triggers-page.js +153 -0
  176. package/lib/chat/components/triggers-page.jsx +221 -0
  177. package/lib/chat/components/ui/confirm-dialog.js +53 -0
  178. package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
  179. package/lib/chat/components/ui/dropdown-menu.js +98 -0
  180. package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
  181. package/lib/chat/components/ui/rename-dialog.js +74 -0
  182. package/lib/chat/components/ui/rename-dialog.jsx +72 -0
  183. package/lib/chat/components/ui/scroll-area.js +13 -0
  184. package/lib/chat/components/ui/scroll-area.jsx +17 -0
  185. package/lib/chat/components/ui/separator.js +21 -0
  186. package/lib/chat/components/ui/separator.jsx +18 -0
  187. package/lib/chat/components/ui/sheet.js +75 -0
  188. package/lib/chat/components/ui/sheet.jsx +95 -0
  189. package/lib/chat/components/ui/sidebar.js +227 -0
  190. package/lib/chat/components/ui/sidebar.jsx +245 -0
  191. package/lib/chat/components/ui/tooltip.js +56 -0
  192. package/lib/chat/components/ui/tooltip.jsx +66 -0
  193. package/lib/chat/components/upgrade-dialog.js +151 -0
  194. package/lib/chat/components/upgrade-dialog.jsx +170 -0
  195. package/lib/chat/utils.js +11 -0
  196. package/lib/cron.js +246 -0
  197. package/lib/db/api-keys.js +163 -0
  198. package/lib/db/chats.js +145 -0
  199. package/lib/db/index.js +52 -0
  200. package/lib/db/notifications.js +99 -0
  201. package/lib/db/schema.js +145 -0
  202. package/lib/db/update-check.js +96 -0
  203. package/lib/db/users.js +89 -0
  204. package/lib/mcp/actions.js +104 -0
  205. package/lib/mcp/client.js +79 -0
  206. package/lib/mcp/handler.js +57 -0
  207. package/lib/mcp/server.js +165 -0
  208. package/lib/paths.js +46 -0
  209. package/lib/registry/actions.js +164 -0
  210. package/lib/registry/catalog.js +137 -0
  211. package/lib/registry/tools.js +71 -0
  212. package/lib/tools/create-job.js +99 -0
  213. package/lib/tools/github.js +217 -0
  214. package/lib/tools/openai.js +35 -0
  215. package/lib/tools/telegram.js +292 -0
  216. package/lib/triggers.js +118 -0
  217. package/lib/utils/render-md.js +102 -0
  218. package/package.json +103 -0
  219. package/setup/lib/auth.mjs +81 -0
  220. package/setup/lib/env.mjs +21 -0
  221. package/setup/lib/fs-utils.mjs +20 -0
  222. package/setup/lib/github.mjs +149 -0
  223. package/setup/lib/prerequisites.mjs +155 -0
  224. package/setup/lib/prompts.mjs +267 -0
  225. package/setup/lib/providers.mjs +48 -0
  226. package/setup/lib/sync.mjs +125 -0
  227. package/setup/lib/targets.mjs +45 -0
  228. package/setup/lib/telegram-verify.mjs +63 -0
  229. package/setup/lib/telegram.mjs +76 -0
  230. package/setup/setup-telegram.mjs +264 -0
  231. package/setup/setup.mjs +842 -0
  232. package/templates/.dockerignore +5 -0
  233. package/templates/.env.example +63 -0
  234. package/templates/.github/workflows/auto-merge.yml +117 -0
  235. package/templates/.github/workflows/build-image.yml +36 -0
  236. package/templates/.github/workflows/notify-job-failed.yml +64 -0
  237. package/templates/.github/workflows/notify-pr-complete.yml +119 -0
  238. package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
  239. package/templates/.github/workflows/run-job.yml +89 -0
  240. package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
  241. package/templates/.gitignore.template +45 -0
  242. package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
  243. package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
  244. package/templates/CLAUDE.md +29 -0
  245. package/templates/CLAUDE.md.template +307 -0
  246. package/templates/app/api/[...thepopebot]/route.js +1 -0
  247. package/templates/app/api/auth/[...nextauth]/route.js +1 -0
  248. package/templates/app/chat/[chatId]/page.js +8 -0
  249. package/templates/app/chats/page.js +7 -0
  250. package/templates/app/components/ascii-logo.jsx +10 -0
  251. package/templates/app/components/login-form.jsx +92 -0
  252. package/templates/app/components/setup-form.jsx +82 -0
  253. package/templates/app/components/theme-provider.jsx +11 -0
  254. package/templates/app/components/theme-toggle.jsx +38 -0
  255. package/templates/app/components/ui/button.jsx +21 -0
  256. package/templates/app/components/ui/card.jsx +23 -0
  257. package/templates/app/components/ui/input.jsx +10 -0
  258. package/templates/app/components/ui/label.jsx +10 -0
  259. package/templates/app/crons/page.js +5 -0
  260. package/templates/app/findings/page.js +7 -0
  261. package/templates/app/globals.css +90 -0
  262. package/templates/app/layout.js +19 -0
  263. package/templates/app/login/page.js +15 -0
  264. package/templates/app/notifications/page.js +7 -0
  265. package/templates/app/page.js +7 -0
  266. package/templates/app/settings/crons/page.js +5 -0
  267. package/templates/app/settings/layout.js +7 -0
  268. package/templates/app/settings/mcp/page.js +5 -0
  269. package/templates/app/settings/page.js +5 -0
  270. package/templates/app/settings/secrets/page.js +5 -0
  271. package/templates/app/settings/triggers/page.js +5 -0
  272. package/templates/app/stream/chat/route.js +1 -0
  273. package/templates/app/swarm/page.js +7 -0
  274. package/templates/app/targets/page.js +7 -0
  275. package/templates/app/toolbox/page.js +7 -0
  276. package/templates/app/triggers/page.js +5 -0
  277. package/templates/config/AGENT.md +34 -0
  278. package/templates/config/CRONS.json +56 -0
  279. package/templates/config/EVENT_HANDLER.md +224 -0
  280. package/templates/config/HEARTBEAT.md +3 -0
  281. package/templates/config/JOB_SUMMARY.md +130 -0
  282. package/templates/config/MCP_SERVERS.json +1 -0
  283. package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
  284. package/templates/config/SOUL.md +17 -0
  285. package/templates/config/TRIGGERS.json +58 -0
  286. package/templates/docker/event-handler/Dockerfile +20 -0
  287. package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
  288. package/templates/docker/job-claude-code/Dockerfile +34 -0
  289. package/templates/docker/job-claude-code/entrypoint.sh +139 -0
  290. package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
  291. package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
  292. package/templates/docker-compose.yml +63 -0
  293. package/templates/instrumentation.js +6 -0
  294. package/templates/middleware.js +1 -0
  295. package/templates/next.config.mjs +3 -0
  296. package/templates/postcss.config.mjs +5 -0
  297. package/templates/skills/LICENSE +21 -0
  298. package/templates/skills/README.md +119 -0
  299. package/templates/skills/brave-search/SKILL.md +79 -0
  300. package/templates/skills/brave-search/content.js +86 -0
  301. package/templates/skills/brave-search/package-lock.json +621 -0
  302. package/templates/skills/brave-search/package.json +14 -0
  303. package/templates/skills/brave-search/search.js +199 -0
  304. package/templates/skills/browser-tools/SKILL.md +196 -0
  305. package/templates/skills/browser-tools/browser-content.js +103 -0
  306. package/templates/skills/browser-tools/browser-cookies.js +35 -0
  307. package/templates/skills/browser-tools/browser-eval.js +53 -0
  308. package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
  309. package/templates/skills/browser-tools/browser-nav.js +44 -0
  310. package/templates/skills/browser-tools/browser-pick.js +162 -0
  311. package/templates/skills/browser-tools/browser-screenshot.js +34 -0
  312. package/templates/skills/browser-tools/browser-start.js +87 -0
  313. package/templates/skills/browser-tools/package-lock.json +2556 -0
  314. package/templates/skills/browser-tools/package.json +19 -0
  315. package/templates/skills/llm-secrets/SKILL.md +34 -0
  316. package/templates/skills/llm-secrets/llm-secrets.js +33 -0
  317. package/templates/skills/modify-self/SKILL.md +12 -0
package/bin/cli.js ADDED
@@ -0,0 +1,620 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync, execFileSync } from 'child_process';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { createDirLink } from '../setup/lib/fs-utils.mjs';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const command = process.argv[2];
13
+ const args = process.argv.slice(3);
14
+
15
+ // Files tightly coupled to the package version that are auto-updated by init.
16
+ // These live in the user's project because GitHub/Docker require them at specific paths,
17
+ // but they shouldn't drift from the package version.
18
+ const MANAGED_PATHS = [
19
+ '.github/workflows/',
20
+ 'docker/event-handler/',
21
+ 'docker-compose.yml',
22
+ '.dockerignore',
23
+ 'CLAUDE.md',
24
+ ];
25
+
26
+ function isManaged(relPath) {
27
+ return MANAGED_PATHS.some(p => relPath === p || relPath.startsWith(p));
28
+ }
29
+
30
+ // Files that must never be scaffolded directly (use .template suffix instead).
31
+ const EXCLUDED_FILENAMES = ['CLAUDE.md'];
32
+
33
+ // Files ending in .template are scaffolded with the suffix stripped.
34
+ // e.g. .gitignore.template → .gitignore, CLAUDE.md.template → CLAUDE.md
35
+ function destPath(templateRelPath) {
36
+ if (templateRelPath.endsWith('.template')) {
37
+ return templateRelPath.slice(0, -'.template'.length);
38
+ }
39
+ return templateRelPath;
40
+ }
41
+
42
+ function templatePath(userPath, templatesDir) {
43
+ const withSuffix = userPath + '.template';
44
+ if (fs.existsSync(path.join(templatesDir, withSuffix))) {
45
+ return withSuffix;
46
+ }
47
+ return userPath;
48
+ }
49
+
50
+ function printUsage() {
51
+ console.log(`
52
+ Usage: thepopebot <command>
53
+
54
+ Commands:
55
+ init Scaffold a new thepopebot project
56
+ setup Run interactive setup wizard
57
+ setup-telegram Reconfigure Telegram webhook
58
+ reset-auth Regenerate AUTH_SECRET (invalidates all sessions)
59
+ reset [file] Restore a template file (or list available templates)
60
+ diff [file] Show differences between project files and package templates
61
+ set-agent-secret <KEY> [VALUE] Set a GitHub secret with AGENT_ prefix (also updates .env)
62
+ set-agent-llm-secret <KEY> [VALUE] Set a GitHub secret with AGENT_LLM_ prefix
63
+ set-var <KEY> [VALUE] Set a GitHub repository variable
64
+ `);
65
+ }
66
+
67
+ /**
68
+ * Collect all template files as relative paths.
69
+ */
70
+ function getTemplateFiles(templatesDir) {
71
+ const files = [];
72
+ function walk(dir) {
73
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ const fullPath = path.join(dir, entry.name);
76
+ if (entry.isDirectory()) {
77
+ walk(fullPath);
78
+ } else if (!EXCLUDED_FILENAMES.includes(entry.name)) {
79
+ files.push(path.relative(templatesDir, fullPath));
80
+ }
81
+ }
82
+ }
83
+ walk(templatesDir);
84
+ return files;
85
+ }
86
+
87
+ async function init() {
88
+ let cwd = process.cwd();
89
+ const packageDir = path.join(__dirname, '..');
90
+ const templatesDir = path.join(packageDir, 'templates');
91
+ const noManaged = args.includes('--no-managed');
92
+
93
+ // Guard: warn if the directory is not empty (unless it's an existing thepopebot project)
94
+ const entries = fs.readdirSync(cwd);
95
+ if (entries.length > 0) {
96
+ const pkgPath = path.join(cwd, 'package.json');
97
+ let isExistingProject = false;
98
+ if (fs.existsSync(pkgPath)) {
99
+ try {
100
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
101
+ const deps = pkg.dependencies || {};
102
+ const devDeps = pkg.devDependencies || {};
103
+ if (deps.thepopebot || devDeps.thepopebot) {
104
+ isExistingProject = true;
105
+ }
106
+ } catch {}
107
+ }
108
+
109
+ if (!isExistingProject) {
110
+ console.log('\nThis directory is not empty.');
111
+ const { text, isCancel } = await import('@clack/prompts');
112
+ const dirName = await text({
113
+ message: 'Project directory name:',
114
+ defaultValue: 'my-popebot',
115
+ });
116
+ if (isCancel(dirName)) {
117
+ console.log('\nCancelled.\n');
118
+ process.exit(0);
119
+ }
120
+ const newDir = path.resolve(cwd, dirName);
121
+ fs.mkdirSync(newDir, { recursive: true });
122
+ process.chdir(newDir);
123
+ cwd = newDir;
124
+ console.log(`\nCreated ${dirName}/`);
125
+ }
126
+ }
127
+
128
+ console.log('\nScaffolding thepopebot project...\n');
129
+
130
+ const templateFiles = getTemplateFiles(templatesDir);
131
+ const created = [];
132
+ const skipped = [];
133
+ const changed = [];
134
+ const updated = [];
135
+
136
+ for (const relPath of templateFiles) {
137
+ const src = path.join(templatesDir, relPath);
138
+ const outPath = destPath(relPath);
139
+ const dest = path.join(cwd, outPath);
140
+
141
+ if (!fs.existsSync(dest)) {
142
+ // File doesn't exist — create it
143
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
144
+ fs.copyFileSync(src, dest);
145
+ created.push(outPath);
146
+ console.log(` Created ${outPath}`);
147
+ } else {
148
+ // File exists — check if template has changed
149
+ const srcContent = fs.readFileSync(src);
150
+ const destContent = fs.readFileSync(dest);
151
+ if (srcContent.equals(destContent)) {
152
+ skipped.push(outPath);
153
+ } else if (!noManaged && isManaged(outPath)) {
154
+ // Managed file differs — auto-update to match package
155
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
156
+ fs.copyFileSync(src, dest);
157
+ updated.push(outPath);
158
+ console.log(` Updated ${outPath}`);
159
+ } else {
160
+ changed.push(outPath);
161
+ console.log(` Skipped ${outPath} (already exists)`);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Create package.json if it doesn't exist
167
+ const pkgPath = path.join(cwd, 'package.json');
168
+ if (!fs.existsSync(pkgPath)) {
169
+ const dirName = path.basename(cwd);
170
+ const { version } = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
171
+ const thepopebotDep = version.includes('-') ? version : '^1.0.0';
172
+ const pkg = {
173
+ name: dirName,
174
+ private: true,
175
+ scripts: {
176
+ dev: 'next dev --turbopack',
177
+ build: 'next build',
178
+ start: 'next start',
179
+ setup: 'thepopebot setup',
180
+ 'setup-telegram': 'thepopebot setup-telegram',
181
+ 'reset-auth': 'thepopebot reset-auth',
182
+ },
183
+ dependencies: {
184
+ thepopebot: thepopebotDep,
185
+ next: '^15.5.12',
186
+ 'next-auth': '5.0.0-beta.30',
187
+ 'next-themes': '^0.4.0',
188
+ react: '^19.0.0',
189
+ 'react-dom': '^19.0.0',
190
+ tailwindcss: '^4.0.0',
191
+ '@tailwindcss/postcss': '^4.0.0',
192
+ },
193
+ };
194
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
195
+ console.log(' Created package.json');
196
+ } else {
197
+ console.log(' Skipped package.json (already exists)');
198
+ }
199
+
200
+ // Create .gitkeep files for empty dirs
201
+ const gitkeepDirs = ['cron', 'triggers', 'logs', 'tmp', 'data'];
202
+ for (const dir of gitkeepDirs) {
203
+ const gitkeep = path.join(cwd, dir, '.gitkeep');
204
+ if (!fs.existsSync(gitkeep)) {
205
+ fs.mkdirSync(path.join(cwd, dir), { recursive: true });
206
+ fs.writeFileSync(gitkeep, '');
207
+ }
208
+ }
209
+
210
+ // Create default skill activation symlinks
211
+ const defaultSkills = ['browser-tools', 'llm-secrets', 'modify-self'];
212
+ const activeDir = path.join(cwd, 'skills', 'active');
213
+ fs.mkdirSync(activeDir, { recursive: true });
214
+ for (const skill of defaultSkills) {
215
+ const symlink = path.join(activeDir, skill);
216
+ if (!fs.existsSync(symlink)) {
217
+ createDirLink(`../${skill}`, symlink);
218
+ console.log(` Created skills/active/${skill} → ../${skill}`);
219
+ }
220
+ }
221
+
222
+ // Create .pi/skills → ../skills/active symlink
223
+ const piSkillsLink = path.join(cwd, '.pi', 'skills');
224
+ if (!fs.existsSync(piSkillsLink)) {
225
+ fs.mkdirSync(path.dirname(piSkillsLink), { recursive: true });
226
+ createDirLink('../skills/active', piSkillsLink);
227
+ console.log(' Created .pi/skills → ../skills/active');
228
+ }
229
+
230
+ // Create .claude/skills → ../skills/active symlink
231
+ const claudeSkillsLink = path.join(cwd, '.claude', 'skills');
232
+ if (!fs.existsSync(claudeSkillsLink)) {
233
+ fs.mkdirSync(path.dirname(claudeSkillsLink), { recursive: true });
234
+ createDirLink('../skills/active', claudeSkillsLink);
235
+ console.log(' Created .claude/skills → ../skills/active');
236
+ }
237
+
238
+ // Report updated managed files
239
+ if (updated.length > 0) {
240
+ console.log('\n Updated managed files:');
241
+ for (const file of updated) {
242
+ console.log(` ${file}`);
243
+ }
244
+ }
245
+
246
+ // Report changed templates
247
+ if (changed.length > 0) {
248
+ console.log('\n Updated templates available:');
249
+ console.log(' These files differ from the current package templates.');
250
+ console.log(' This may be from your edits, or from a thepopebot update.\n');
251
+ for (const file of changed) {
252
+ console.log(` ${file}`);
253
+ }
254
+ console.log('\n To view differences: npx thepopebot diff <file>');
255
+ console.log(' To reset to default: npx thepopebot reset <file>');
256
+ }
257
+
258
+ // Run npm install
259
+ console.log('\nInstalling dependencies...\n');
260
+ execSync('npm install', { stdio: 'inherit', cwd });
261
+
262
+ // Create or update .env with auto-generated infrastructure values
263
+ const envPath = path.join(cwd, '.env');
264
+ const { randomBytes } = await import('crypto');
265
+ const thepopebotPkg = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
266
+ const version = thepopebotPkg.version;
267
+
268
+ if (!fs.existsSync(envPath)) {
269
+ // Seed .env for new projects
270
+ const authSecret = randomBytes(32).toString('base64');
271
+ const seedEnv = `# thepopebot Configuration
272
+ # Run "npm run setup" to complete configuration
273
+
274
+ AUTH_SECRET=${authSecret}
275
+ AUTH_TRUST_HOST=true
276
+ THEPOPEBOT_VERSION=${version}
277
+ `;
278
+ fs.writeFileSync(envPath, seedEnv);
279
+ console.log(` Created .env (AUTH_SECRET, THEPOPEBOT_VERSION=${version})`);
280
+ } else {
281
+ // Update THEPOPEBOT_VERSION in existing .env
282
+ try {
283
+ let envContent = fs.readFileSync(envPath, 'utf8');
284
+ if (envContent.match(/^THEPOPEBOT_VERSION=.*/m)) {
285
+ envContent = envContent.replace(/^THEPOPEBOT_VERSION=.*/m, `THEPOPEBOT_VERSION=${version}`);
286
+ } else {
287
+ envContent = envContent.trimEnd() + `\nTHEPOPEBOT_VERSION=${version}\n`;
288
+ }
289
+ fs.writeFileSync(envPath, envContent);
290
+ console.log(` Updated THEPOPEBOT_VERSION to ${version}`);
291
+ } catch {}
292
+ }
293
+
294
+ console.log('\nDone! Run: npm run setup\n');
295
+ }
296
+
297
+ /**
298
+ * List all available template files, or restore a specific one.
299
+ */
300
+ function reset(filePath) {
301
+ const packageDir = path.join(__dirname, '..');
302
+ const templatesDir = path.join(packageDir, 'templates');
303
+ const cwd = process.cwd();
304
+
305
+ if (!filePath) {
306
+ console.log('\nAvailable template files:\n');
307
+ const files = getTemplateFiles(templatesDir);
308
+ for (const file of files) {
309
+ console.log(` ${destPath(file)}`);
310
+ }
311
+ console.log('\nUsage: thepopebot reset <file>');
312
+ console.log('Example: thepopebot reset config/SOUL.md\n');
313
+ return;
314
+ }
315
+
316
+ const tmplPath = templatePath(filePath, templatesDir);
317
+ const src = path.join(templatesDir, tmplPath);
318
+ const dest = path.join(cwd, filePath);
319
+
320
+ if (!fs.existsSync(src)) {
321
+ console.error(`\nTemplate not found: ${filePath}`);
322
+ console.log('Run "thepopebot reset" to see available templates.\n');
323
+ process.exit(1);
324
+ }
325
+
326
+ if (fs.statSync(src).isDirectory()) {
327
+ console.log(`\nRestoring ${filePath}/...\n`);
328
+ copyDirSyncForce(src, dest, tmplPath);
329
+ } else {
330
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
331
+ fs.copyFileSync(src, dest);
332
+ console.log(`\nRestored ${filePath}\n`);
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Show the diff between a user's file and the package template.
338
+ */
339
+ function diff(filePath) {
340
+ const packageDir = path.join(__dirname, '..');
341
+ const templatesDir = path.join(packageDir, 'templates');
342
+ const cwd = process.cwd();
343
+
344
+ if (!filePath) {
345
+ // Show all files that differ
346
+ console.log('\nFiles that differ from package templates:\n');
347
+ const files = getTemplateFiles(templatesDir);
348
+ let anyDiff = false;
349
+ for (const file of files) {
350
+ const src = path.join(templatesDir, file);
351
+ const outPath = destPath(file);
352
+ const dest = path.join(cwd, outPath);
353
+ if (fs.existsSync(dest)) {
354
+ const srcContent = fs.readFileSync(src);
355
+ const destContent = fs.readFileSync(dest);
356
+ if (!srcContent.equals(destContent)) {
357
+ console.log(` ${outPath}`);
358
+ anyDiff = true;
359
+ }
360
+ } else {
361
+ console.log(` ${outPath} (missing)`);
362
+ anyDiff = true;
363
+ }
364
+ }
365
+ if (!anyDiff) {
366
+ console.log(' All files match package templates.');
367
+ }
368
+ console.log('\nUsage: thepopebot diff <file>');
369
+ console.log('Example: thepopebot diff config/SOUL.md\n');
370
+ return;
371
+ }
372
+
373
+ const tmplPath = templatePath(filePath, templatesDir);
374
+ const src = path.join(templatesDir, tmplPath);
375
+ const dest = path.join(cwd, filePath);
376
+
377
+ if (!fs.existsSync(src)) {
378
+ console.error(`\nTemplate not found: ${filePath}`);
379
+ process.exit(1);
380
+ }
381
+
382
+ if (!fs.existsSync(dest)) {
383
+ console.log(`\n${filePath} does not exist in your project.`);
384
+ console.log(`Run "thepopebot reset ${filePath}" to create it.\n`);
385
+ return;
386
+ }
387
+
388
+ try {
389
+ // Use git diff for nice colored output, fall back to plain diff
390
+ execSync(`git diff --no-index -- "${dest}" "${src}"`, { stdio: 'inherit' });
391
+ console.log('\nFiles are identical.\n');
392
+ } catch (e) {
393
+ // git diff exits with 1 when files differ (output already printed)
394
+ console.log(`\n To reset: thepopebot reset ${filePath}\n`);
395
+ }
396
+ }
397
+
398
+ function copyDirSyncForce(src, dest, templateRelBase = '') {
399
+ fs.mkdirSync(dest, { recursive: true });
400
+ const entries = fs.readdirSync(src, { withFileTypes: true });
401
+ for (const entry of entries) {
402
+ if (EXCLUDED_FILENAMES.includes(entry.name)) continue;
403
+ const srcPath = path.join(src, entry.name);
404
+ const templateRel = templateRelBase
405
+ ? path.join(templateRelBase, entry.name)
406
+ : entry.name;
407
+ const outName = path.basename(destPath(templateRel));
408
+ const destFile = path.join(dest, outName);
409
+ if (entry.isDirectory()) {
410
+ copyDirSyncForce(srcPath, destFile, templateRel);
411
+ } else {
412
+ fs.copyFileSync(srcPath, destFile);
413
+ console.log(` Restored ${path.relative(process.cwd(), destFile)}`);
414
+ }
415
+ }
416
+ }
417
+
418
+ function setup() {
419
+ const setupScript = path.join(__dirname, '..', 'setup', 'setup.mjs');
420
+ try {
421
+ execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
422
+ } catch {
423
+ process.exit(1);
424
+ }
425
+ }
426
+
427
+ function setupTelegram() {
428
+ const setupScript = path.join(__dirname, '..', 'setup', 'setup-telegram.mjs');
429
+ try {
430
+ execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
431
+ } catch {
432
+ process.exit(1);
433
+ }
434
+ }
435
+
436
+ async function resetAuth() {
437
+ const { randomBytes } = await import('crypto');
438
+ const { updateEnvVariable } = await import(path.join(__dirname, '..', 'setup', 'lib', 'auth.mjs'));
439
+
440
+ const envPath = path.join(process.cwd(), '.env');
441
+ if (!fs.existsSync(envPath)) {
442
+ console.error('\n No .env file found. Run "npm run setup" first.\n');
443
+ process.exit(1);
444
+ }
445
+
446
+ const newSecret = randomBytes(32).toString('base64');
447
+ updateEnvVariable('AUTH_SECRET', newSecret);
448
+ console.log('\n AUTH_SECRET regenerated.');
449
+ console.log(' All existing sessions have been invalidated.');
450
+ console.log(' Restart your server for the change to take effect.\n');
451
+ }
452
+
453
+ /**
454
+ * Load GH_OWNER and GH_REPO from .env
455
+ */
456
+ function loadRepoInfo() {
457
+ const envPath = path.join(process.cwd(), '.env');
458
+ if (!fs.existsSync(envPath)) {
459
+ console.error('\n No .env file found. Run "npm run setup" first.\n');
460
+ process.exit(1);
461
+ }
462
+ const content = fs.readFileSync(envPath, 'utf-8');
463
+ const env = {};
464
+ for (const line of content.split('\n')) {
465
+ const match = line.match(/^([^#=]+)=(.*)$/);
466
+ if (match) env[match[1].trim()] = match[2].trim();
467
+ }
468
+ if (!env.GH_OWNER || !env.GH_REPO) {
469
+ console.error('\n GH_OWNER and GH_REPO not found in .env. Run "npm run setup" first.\n');
470
+ process.exit(1);
471
+ }
472
+ return { owner: env.GH_OWNER, repo: env.GH_REPO };
473
+ }
474
+
475
+ /**
476
+ * Read all data from a piped stdin stream.
477
+ * Returns null if stdin is a TTY (interactive terminal).
478
+ */
479
+ function readStdin() {
480
+ return new Promise((resolve, reject) => {
481
+ if (process.stdin.isTTY) return resolve(null);
482
+ let data = '';
483
+ process.stdin.setEncoding('utf-8');
484
+ process.stdin.on('data', (chunk) => { data += chunk; });
485
+ process.stdin.on('end', () => resolve(data.trimEnd() || null));
486
+ process.stdin.on('error', reject);
487
+ });
488
+ }
489
+
490
+ /**
491
+ * Prompt for a secret value interactively if not provided as an argument.
492
+ * Supports piped stdin (e.g. echo "val" | thepopebot set-var KEY).
493
+ */
494
+ async function promptForValue(key) {
495
+ const stdin = await readStdin();
496
+ if (stdin) return stdin;
497
+
498
+ if (!process.stdin.isTTY) {
499
+ console.error(`\n No value provided for ${key}. Pipe a value or pass it as an argument.\n`);
500
+ process.exit(1);
501
+ }
502
+
503
+ const { password, isCancel } = await import('@clack/prompts');
504
+ const value = await password({
505
+ message: `Enter value for ${key}:`,
506
+ validate: (input) => {
507
+ if (!input) return 'Value is required';
508
+ },
509
+ });
510
+ if (isCancel(value)) {
511
+ console.log('\nCancelled.\n');
512
+ process.exit(0);
513
+ }
514
+ return value;
515
+ }
516
+
517
+ async function setAgentSecret(key, value) {
518
+ if (!key) {
519
+ console.error('\n Usage: thepopebot set-agent-secret <KEY> [VALUE]\n');
520
+ console.error(' Example: thepopebot set-agent-secret ANTHROPIC_API_KEY\n');
521
+ process.exit(1);
522
+ }
523
+
524
+ if (!value) value = await promptForValue(key);
525
+
526
+ const { owner, repo } = loadRepoInfo();
527
+ const prefixedName = `AGENT_${key}`;
528
+
529
+ const { setSecret } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
530
+ const { updateEnvVariable } = await import(path.join(__dirname, '..', 'setup', 'lib', 'auth.mjs'));
531
+
532
+ const result = await setSecret(owner, repo, prefixedName, value);
533
+ if (result.success) {
534
+ console.log(`\n Set GitHub secret: ${prefixedName}`);
535
+ updateEnvVariable(key, value);
536
+ console.log(` Updated .env: ${key}`);
537
+ console.log('');
538
+ } else {
539
+ console.error(`\n Failed to set ${prefixedName}: ${result.error}\n`);
540
+ process.exit(1);
541
+ }
542
+ }
543
+
544
+ async function setAgentLlmSecret(key, value) {
545
+ if (!key) {
546
+ console.error('\n Usage: thepopebot set-agent-llm-secret <KEY> [VALUE]\n');
547
+ console.error(' Example: thepopebot set-agent-llm-secret BRAVE_API_KEY\n');
548
+ process.exit(1);
549
+ }
550
+
551
+ if (!value) value = await promptForValue(key);
552
+
553
+ const { owner, repo } = loadRepoInfo();
554
+ const prefixedName = `AGENT_LLM_${key}`;
555
+
556
+ const { setSecret } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
557
+
558
+ const result = await setSecret(owner, repo, prefixedName, value);
559
+ if (result.success) {
560
+ console.log(`\n Set GitHub secret: ${prefixedName}\n`);
561
+ } else {
562
+ console.error(`\n Failed to set ${prefixedName}: ${result.error}\n`);
563
+ process.exit(1);
564
+ }
565
+ }
566
+
567
+ async function setVar(key, value) {
568
+ if (!key) {
569
+ console.error('\n Usage: thepopebot set-var <KEY> [VALUE]\n');
570
+ console.error(' Example: thepopebot set-var LLM_MODEL claude-sonnet-4-5-20250929\n');
571
+ process.exit(1);
572
+ }
573
+
574
+ if (!value) value = await promptForValue(key);
575
+
576
+ const { owner, repo } = loadRepoInfo();
577
+
578
+ const { setVariable } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
579
+
580
+ const result = await setVariable(owner, repo, key, value);
581
+ if (result.success) {
582
+ console.log(`\n Set GitHub variable: ${key}\n`);
583
+ } else {
584
+ console.error(`\n Failed to set ${key}: ${result.error}\n`);
585
+ process.exit(1);
586
+ }
587
+ }
588
+
589
+ switch (command) {
590
+ case 'init':
591
+ await init();
592
+ break;
593
+ case 'setup':
594
+ setup();
595
+ break;
596
+ case 'setup-telegram':
597
+ setupTelegram();
598
+ break;
599
+ case 'reset-auth':
600
+ await resetAuth();
601
+ break;
602
+ case 'reset':
603
+ reset(args[0]);
604
+ break;
605
+ case 'diff':
606
+ diff(args[0]);
607
+ break;
608
+ case 'set-agent-secret':
609
+ await setAgentSecret(args[0], args[1]);
610
+ break;
611
+ case 'set-agent-llm-secret':
612
+ await setAgentLlmSecret(args[0], args[1]);
613
+ break;
614
+ case 'set-var':
615
+ await setVar(args[0], args[1]);
616
+ break;
617
+ default:
618
+ printUsage();
619
+ process.exit(command ? 1 : 0);
620
+ }
package/bin/local.sh ADDED
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ PACKAGE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ DEV_DIR="${1:-/tmp/thepopebot.local}"
6
+ ENV_BACKUP="/tmp/env.$(uuidgen)"
7
+
8
+ HAS_ENV=false
9
+ if [ -f "$DEV_DIR/.env" ]; then
10
+ mv "$DEV_DIR/.env" "$ENV_BACKUP"
11
+ HAS_ENV=true
12
+ fi
13
+
14
+ rm -rf "$DEV_DIR"
15
+ mkdir -p "$DEV_DIR"
16
+ cd "$DEV_DIR"
17
+
18
+ node "$PACKAGE_DIR/bin/cli.js" init
19
+
20
+ sed -i '' "s|\"thepopebot\": \".*\"|\"thepopebot\": \"file:$PACKAGE_DIR\"|" package.json
21
+
22
+ rm -rf node_modules package-lock.json
23
+ npm install --install-links
24
+
25
+
26
+ if [ "$HAS_ENV" = true ]; then
27
+ mv "$ENV_BACKUP" .env
28
+ echo "Restored .env from previous build"
29
+ else
30
+ npm run setup
31
+ fi