@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
@@ -0,0 +1,102 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { PROJECT_ROOT, piSkillsDir } from '../paths.js';
4
+ import { loadAgentDescriptions } from '../agents.js';
5
+
6
+ const INCLUDE_PATTERN = /\{\{([^}]+\.md)\}\}/g;
7
+ const VARIABLE_PATTERN = /\{\{(datetime|skills|agents)\}\}/gi;
8
+
9
+ // Scan skill directories under .pi/skills/ for SKILL.md files and extract
10
+ // description from YAML frontmatter. Returns a bullet list of descriptions.
11
+ function loadSkillDescriptions() {
12
+ try {
13
+ if (!fs.existsSync(piSkillsDir)) {
14
+ return 'No additional abilities configured.';
15
+ }
16
+
17
+ const entries = fs.readdirSync(piSkillsDir, { withFileTypes: true });
18
+ const descriptions = [];
19
+
20
+ for (const entry of entries) {
21
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
22
+
23
+ const skillMdPath = path.join(piSkillsDir, entry.name, 'SKILL.md');
24
+ if (!fs.existsSync(skillMdPath)) continue;
25
+
26
+ const content = fs.readFileSync(skillMdPath, 'utf8');
27
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
28
+ if (!frontmatterMatch) continue;
29
+
30
+ const frontmatter = frontmatterMatch[1];
31
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
32
+ if (descMatch) {
33
+ descriptions.push(`- ${descMatch[1].trim()}`);
34
+ }
35
+ }
36
+
37
+ if (descriptions.length === 0) {
38
+ return 'No additional abilities configured.';
39
+ }
40
+
41
+ return descriptions.join('\n');
42
+ } catch {
43
+ return 'No additional abilities configured.';
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Resolve built-in variables like {{datetime}} and {{skills}}.
49
+ * @param {string} content - Content with possible variable placeholders
50
+ * @returns {string} Content with variables resolved
51
+ */
52
+ function resolveVariables(content) {
53
+ return content.replace(VARIABLE_PATTERN, (match, variable) => {
54
+ switch (variable.toLowerCase()) {
55
+ case 'datetime':
56
+ return new Date().toISOString();
57
+ case 'skills':
58
+ return loadSkillDescriptions();
59
+ case 'agents':
60
+ return loadAgentDescriptions();
61
+ default:
62
+ return match;
63
+ }
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Render a markdown file, resolving {{filepath}} includes recursively
69
+ * and {{datetime}}, {{skills}} built-in variables.
70
+ * Referenced file paths resolve relative to the project root.
71
+ * @param {string} filePath - Absolute path to the markdown file
72
+ * @param {string[]} [chain=[]] - Already-resolved file paths (for circular detection)
73
+ * @returns {string} Rendered markdown content
74
+ */
75
+ function render_md(filePath, chain = []) {
76
+ const resolved = path.resolve(filePath);
77
+
78
+ if (chain.includes(resolved)) {
79
+ const cycle = [...chain, resolved].map((p) => path.relative(PROJECT_ROOT, p)).join(' -> ');
80
+ console.log(`[render_md] Circular include detected: ${cycle}`);
81
+ return '';
82
+ }
83
+
84
+ if (!fs.existsSync(resolved)) {
85
+ return '';
86
+ }
87
+
88
+ const content = fs.readFileSync(resolved, 'utf8');
89
+ const currentChain = [...chain, resolved];
90
+
91
+ const withIncludes = content.replace(INCLUDE_PATTERN, (match, includePath) => {
92
+ const includeResolved = path.resolve(PROJECT_ROOT, includePath.trim());
93
+ if (!fs.existsSync(includeResolved)) {
94
+ return match;
95
+ }
96
+ return render_md(includeResolved, currentChain);
97
+ });
98
+
99
+ return resolveVariables(withIncludes);
100
+ }
101
+
102
+ export { render_md };
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "@harbinger-ai/harbinger",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
+ "bin": {
7
+ "harbinger": "./bin/cli.js",
8
+ "thepopebot": "./bin/cli.js"
9
+ },
10
+ "main": "api/index.js",
11
+ "exports": {
12
+ "./api": "./api/index.js",
13
+ "./config": "./config/index.js",
14
+ "./instrumentation": "./config/instrumentation.js",
15
+ "./auth": "./lib/auth/index.js",
16
+ "./auth/actions": "./lib/auth/actions.js",
17
+ "./chat/api": "./lib/chat/api.js",
18
+ "./db": "./lib/db/index.js",
19
+ "./chat": "./lib/chat/components/index.js",
20
+ "./chat/actions": "./lib/chat/actions.js",
21
+ "./middleware": "./lib/auth/middleware.js",
22
+ "./mcp": "./lib/mcp/server.js",
23
+ "./package.json": "./package.json"
24
+ },
25
+ "files": [
26
+ "agents/",
27
+ "bin/",
28
+ "lib/",
29
+ "api/",
30
+ "config/",
31
+ "setup/",
32
+ "templates/",
33
+ "drizzle/"
34
+ ],
35
+ "scripts": {
36
+ "build": "esbuild lib/chat/components/*.jsx lib/chat/components/**/*.jsx --outdir=lib/chat/components --format=esm --jsx=automatic --outbase=lib/chat/components",
37
+ "prepublishOnly": "npm run build",
38
+ "local": "bash bin/local.sh",
39
+ "postinstall": "node -e \"import('./bin/postinstall.js').catch(()=>{})\"",
40
+ "db:generate": "drizzle-kit generate",
41
+ "db:studio": "drizzle-kit studio",
42
+ "test": "echo \"No tests yet\" && exit 0"
43
+ },
44
+ "keywords": [
45
+ "ai",
46
+ "agent",
47
+ "autonomous",
48
+ "telegram",
49
+ "bot",
50
+ "claude",
51
+ "nextjs"
52
+ ],
53
+ "author": "Stephen Pope",
54
+ "license": "MIT",
55
+ "dependencies": {
56
+ "@ai-sdk/react": "^2.0.0",
57
+ "@grammyjs/parse-mode": "^2.2.0",
58
+ "@langchain/anthropic": "^1.3.17",
59
+ "@langchain/core": "^1.1.24",
60
+ "@langchain/google-genai": "^2.1.18",
61
+ "@langchain/mcp-adapters": "^0.6.0",
62
+ "@langchain/langgraph": "^1.1.4",
63
+ "@langchain/langgraph-checkpoint-sqlite": "^1.0.1",
64
+ "@langchain/openai": "^1.2.7",
65
+ "@modelcontextprotocol/sdk": "^1.27.0",
66
+ "ai": "^5.0.0",
67
+ "bcrypt-ts": "^6.0.0",
68
+ "better-sqlite3": "^12.6.2",
69
+ "chalk": "^5.3.0",
70
+ "class-variance-authority": "^0.7.0",
71
+ "clsx": "^2.0.0",
72
+ "dotenv": "^16.3.1",
73
+ "framer-motion": "^11.0.0",
74
+ "drizzle-orm": "^0.44.0",
75
+ "grammy": "^1.39.3",
76
+ "@clack/prompts": "^0.10.0",
77
+ "lucide-react": "^0.400.0",
78
+ "node-cron": "^3.0.3",
79
+ "open": "^10.0.0",
80
+ "streamdown": "^2.2.0",
81
+ "tailwind-merge": "^3.0.0",
82
+ "uuid": "^9.0.0",
83
+ "zod": "^4.3.6"
84
+ },
85
+ "peerDependencies": {
86
+ "next": ">=15.5.12",
87
+ "next-auth": "^5.0.0-beta.30",
88
+ "react": ">=19.0.0",
89
+ "react-dom": ">=19.0.0"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "next-auth": {
93
+ "optional": false
94
+ }
95
+ },
96
+ "devDependencies": {
97
+ "drizzle-kit": "^0.31.9",
98
+ "esbuild": "^0.27.3"
99
+ },
100
+ "engines": {
101
+ "node": ">=18.0.0"
102
+ }
103
+ }
@@ -0,0 +1,81 @@
1
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ const ROOT_DIR = process.cwd();
5
+
6
+ /**
7
+ * Validate Anthropic API key by making a minimal test call
8
+ */
9
+ export async function validateAnthropicKey(key) {
10
+ try {
11
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ 'x-api-key': key,
16
+ 'anthropic-version': '2023-06-01',
17
+ },
18
+ body: JSON.stringify({
19
+ model: 'claude-3-haiku-20240307',
20
+ max_tokens: 1,
21
+ messages: [{ role: 'user', content: 'Hi' }],
22
+ }),
23
+ });
24
+
25
+ if (response.status === 401) {
26
+ return { valid: false, error: 'Invalid API key' };
27
+ }
28
+ if (response.status === 400) {
29
+ // Bad request but key is valid (e.g., rate limit, model error)
30
+ return { valid: true };
31
+ }
32
+ if (response.ok) {
33
+ return { valid: true };
34
+ }
35
+ return { valid: false, error: `HTTP ${response.status}` };
36
+ } catch (error) {
37
+ return { valid: false, error: error.message };
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Generate .pi/agent/models.json for providers not built into PI.
43
+ * PI resolves the apiKey field as an env var name at runtime ($ENV_VAR).
44
+ */
45
+ export function writeModelsJson(providerName, { baseUrl, apiKey, api, models }) {
46
+ const config = {
47
+ providers: {
48
+ [providerName]: {
49
+ baseUrl,
50
+ apiKey,
51
+ api: api || 'openai-completions',
52
+ models: models.map((m) => ({ id: m })),
53
+ },
54
+ },
55
+ };
56
+ const dir = join(ROOT_DIR, '.pi', 'agent');
57
+ mkdirSync(dir, { recursive: true });
58
+ writeFileSync(join(dir, 'models.json'), JSON.stringify(config, null, 2));
59
+ }
60
+
61
+ /**
62
+ * Update a single variable in an existing .env file
63
+ */
64
+ export function updateEnvVariable(key, value) {
65
+ const envPath = join(ROOT_DIR, '.env');
66
+ if (!existsSync(envPath)) {
67
+ throw new Error('.env file not found. Run npm run setup first.');
68
+ }
69
+
70
+ let content = readFileSync(envPath, 'utf-8');
71
+ const regex = new RegExp(`^${key}=.*$`, 'm');
72
+
73
+ if (regex.test(content)) {
74
+ content = content.replace(regex, `${key}=${value}`);
75
+ } else {
76
+ content = content.trimEnd() + `\n${key}=${value}\n`;
77
+ }
78
+
79
+ writeFileSync(envPath, content);
80
+ return envPath;
81
+ }
@@ -0,0 +1,21 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ /**
5
+ * Parse .env file and return object, or null if no .env exists
6
+ */
7
+ export function loadEnvFile(dir = process.cwd()) {
8
+ const envPath = join(dir, '.env');
9
+ if (!existsSync(envPath)) {
10
+ return null;
11
+ }
12
+ const content = readFileSync(envPath, 'utf-8');
13
+ const env = {};
14
+ for (const line of content.split('\n')) {
15
+ const match = line.match(/^([^#=]+)=(.*)$/);
16
+ if (match) {
17
+ env[match[1].trim()] = match[2].trim();
18
+ }
19
+ }
20
+ return env;
21
+ }
@@ -0,0 +1,20 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Create a directory symlink (Unix) or junction/copy fallback (Windows).
6
+ */
7
+ export function createDirLink(target, linkPath) {
8
+ if (process.platform !== 'win32') {
9
+ fs.symlinkSync(target, linkPath);
10
+ return;
11
+ }
12
+ // Junctions require absolute targets but don't require admin privileges
13
+ const absoluteTarget = path.resolve(path.dirname(linkPath), target);
14
+ try {
15
+ fs.symlinkSync(absoluteTarget, linkPath, 'junction');
16
+ } catch {
17
+ fs.cpSync(absoluteTarget, linkPath, { recursive: true });
18
+ console.log(' (copied — symlinks unavailable on this system)');
19
+ }
20
+ }
@@ -0,0 +1,149 @@
1
+ import { execSync, exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { randomBytes } from 'crypto';
4
+ import { ghEnv } from './prerequisites.mjs';
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ /**
9
+ * Validate GitHub PAT by making a test API call
10
+ */
11
+ export async function validatePAT(token) {
12
+ try {
13
+ const response = await fetch('https://api.github.com/user', {
14
+ headers: {
15
+ Authorization: `token ${token}`,
16
+ Accept: 'application/vnd.github.v3+json',
17
+ },
18
+ });
19
+ if (!response.ok) return { valid: false, error: 'Invalid token' };
20
+ const user = await response.json();
21
+ return { valid: true, user: user.login };
22
+ } catch (error) {
23
+ return { valid: false, error: error.message };
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Check PAT scopes/permissions
29
+ * Works with both classic tokens (x-oauth-scopes header) and fine-grained tokens
30
+ */
31
+ export async function checkPATScopes(token) {
32
+ try {
33
+ const response = await fetch('https://api.github.com/user', {
34
+ headers: {
35
+ Authorization: `token ${token}`,
36
+ Accept: 'application/vnd.github.v3+json',
37
+ },
38
+ });
39
+ const scopes = response.headers.get('x-oauth-scopes') || '';
40
+ const scopeList = scopes.split(',').map((s) => s.trim()).filter(Boolean);
41
+
42
+ // Classic tokens have x-oauth-scopes header
43
+ if (scopeList.length > 0) {
44
+ return {
45
+ hasRepo: scopeList.includes('repo'),
46
+ hasWorkflow: scopeList.includes('workflow'),
47
+ scopes: scopeList,
48
+ isFineGrained: false,
49
+ };
50
+ }
51
+
52
+ // Fine-grained tokens don't have x-oauth-scopes header
53
+ // We can't check permissions directly, so we assume valid if token works
54
+ return {
55
+ hasRepo: true,
56
+ hasWorkflow: true,
57
+ scopes: [],
58
+ isFineGrained: true,
59
+ };
60
+ } catch {
61
+ return { hasRepo: false, hasWorkflow: false, scopes: [], isFineGrained: false };
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Set a GitHub repository secret using gh CLI
67
+ */
68
+ export async function setSecret(owner, repo, name, value) {
69
+ try {
70
+ execSync(
71
+ `gh secret set ${name} --repo ${owner}/${repo}`,
72
+ { input: value, encoding: 'utf-8', env: ghEnv(), stdio: ['pipe', 'pipe', 'pipe'] }
73
+ );
74
+ return { success: true };
75
+ } catch (error) {
76
+ return { success: false, error: error.message };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Set multiple GitHub secrets
82
+ */
83
+ export async function setSecrets(owner, repo, secrets) {
84
+ const results = {};
85
+ for (const [name, value] of Object.entries(secrets)) {
86
+ results[name] = await setSecret(owner, repo, name, value);
87
+ }
88
+ return results;
89
+ }
90
+
91
+ /**
92
+ * List existing secrets
93
+ */
94
+ export async function listSecrets(owner, repo) {
95
+ try {
96
+ const { stdout } = await execAsync(`gh secret list --repo ${owner}/${repo}`, {
97
+ encoding: 'utf-8',
98
+ env: ghEnv(),
99
+ });
100
+ const secrets = stdout
101
+ .trim()
102
+ .split('\n')
103
+ .filter(Boolean)
104
+ .map((line) => line.split('\t')[0]);
105
+ return secrets;
106
+ } catch {
107
+ return [];
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Set a GitHub repository variable using gh CLI
113
+ */
114
+ export async function setVariable(owner, repo, name, value) {
115
+ try {
116
+ execSync(
117
+ `gh variable set ${name} --repo ${owner}/${repo}`,
118
+ { input: value, encoding: 'utf-8', env: ghEnv(), stdio: ['pipe', 'pipe', 'pipe'] }
119
+ );
120
+ return { success: true };
121
+ } catch (error) {
122
+ return { success: false, error: error.message };
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Set multiple GitHub repository variables
128
+ */
129
+ export async function setVariables(owner, repo, variables) {
130
+ const results = {};
131
+ for (const [name, value] of Object.entries(variables)) {
132
+ results[name] = await setVariable(owner, repo, name, value);
133
+ }
134
+ return results;
135
+ }
136
+
137
+ /**
138
+ * Generate a random webhook secret
139
+ */
140
+ export function generateWebhookSecret() {
141
+ return randomBytes(32).toString('hex');
142
+ }
143
+
144
+ /**
145
+ * Get the GitHub PAT creation URL with pre-selected scopes
146
+ */
147
+ export function getPATCreationURL() {
148
+ return 'https://github.com/settings/personal-access-tokens/new';
149
+ }
@@ -0,0 +1,155 @@
1
+ import { execSync, exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+
4
+ const execAsync = promisify(exec);
5
+
6
+ /**
7
+ * Return process.env without GITHUB_TOKEN and GH_TOKEN.
8
+ * The gh CLI auto-uses these env vars, which can shadow interactive login
9
+ * and cause secret/variable operations to fail with the wrong identity.
10
+ */
11
+ export function ghEnv() {
12
+ const env = { ...process.env };
13
+ delete env.GITHUB_TOKEN;
14
+ delete env.GH_TOKEN;
15
+ return env;
16
+ }
17
+
18
+ /**
19
+ * Check if a command exists
20
+ */
21
+ function commandExists(cmd) {
22
+ const checkCmd = process.platform === 'win32' ? 'where' : 'which';
23
+ try {
24
+ execSync(`${checkCmd} ${cmd}`, { stdio: 'ignore' });
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Get Node.js version
33
+ */
34
+ function getNodeVersion() {
35
+ try {
36
+ const version = execSync('node --version', { encoding: 'utf-8' }).trim();
37
+ return version.replace('v', '');
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Check if gh CLI is authenticated
45
+ */
46
+ async function isGhAuthenticated() {
47
+ try {
48
+ await execAsync('gh auth status', { env: ghEnv() });
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get git remote info (owner/repo)
57
+ */
58
+ function getGitRemoteInfo() {
59
+ try {
60
+ const remote = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
61
+ // Handle both HTTPS and SSH formats
62
+ // https://github.com/owner/repo.git
63
+ // git@github.com:owner/repo.git
64
+ const httpsMatch = remote.match(/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
65
+ const sshMatch = remote.match(/github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
66
+ const match = httpsMatch || sshMatch;
67
+ if (match) {
68
+ return { owner: match[1], repo: match[2] };
69
+ }
70
+ return null;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get package manager (pnpm preferred, npm fallback)
78
+ */
79
+ function getPackageManager() {
80
+ if (commandExists('pnpm')) return 'pnpm';
81
+ if (commandExists('npm')) return 'npm';
82
+ return null;
83
+ }
84
+
85
+ /**
86
+ * Check all prerequisites and return status
87
+ */
88
+ export async function checkPrerequisites() {
89
+ const results = {
90
+ node: { installed: false, version: null, ok: false },
91
+ packageManager: { installed: false, name: null },
92
+ gh: { installed: false, authenticated: false },
93
+ ngrok: { installed: false },
94
+ git: { installed: false, remoteInfo: null },
95
+ };
96
+
97
+ // Check Node.js
98
+ const nodeVersion = getNodeVersion();
99
+ if (nodeVersion) {
100
+ results.node.installed = true;
101
+ results.node.version = nodeVersion;
102
+ const [major] = nodeVersion.split('.').map(Number);
103
+ results.node.ok = major >= 18;
104
+ }
105
+
106
+ // Check package manager
107
+ const pm = getPackageManager();
108
+ if (pm) {
109
+ results.packageManager.installed = true;
110
+ results.packageManager.name = pm;
111
+ }
112
+
113
+ // Check gh CLI
114
+ results.gh.installed = commandExists('gh');
115
+ if (results.gh.installed) {
116
+ results.gh.authenticated = await isGhAuthenticated();
117
+ }
118
+
119
+ // Check ngrok
120
+ results.ngrok.installed = commandExists('ngrok');
121
+
122
+ // Check git
123
+ results.git.installed = commandExists('git');
124
+ if (results.git.installed) {
125
+ // Initialize git repo if needed (must happen before remote check)
126
+ try {
127
+ execSync('git rev-parse --git-dir', { stdio: 'ignore' });
128
+ results.git.initialized = true;
129
+ } catch {
130
+ results.git.initialized = false;
131
+ }
132
+ results.git.remoteInfo = getGitRemoteInfo();
133
+ }
134
+
135
+ return results;
136
+ }
137
+
138
+ /**
139
+ * Install a global npm package
140
+ */
141
+ export async function installGlobalPackage(packageName) {
142
+ const pm = getPackageManager();
143
+ const cmd = pm === 'pnpm' ? `pnpm add -g ${packageName}` : `npm install -g ${packageName}`;
144
+ await execAsync(cmd);
145
+ }
146
+
147
+ /**
148
+ * Run gh auth login
149
+ */
150
+ export async function runGhAuth() {
151
+ // This needs to be interactive, so we use execSync
152
+ execSync('gh auth login', { stdio: 'inherit', env: ghEnv() });
153
+ }
154
+
155
+ export { commandExists, getGitRemoteInfo, getPackageManager };