gigaclaw 1.4.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 (249) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +237 -0
  3. package/api/CLAUDE.md +19 -0
  4. package/api/index.js +265 -0
  5. package/bin/cli.js +823 -0
  6. package/bin/local.sh +85 -0
  7. package/bin/postinstall.js +63 -0
  8. package/config/index.js +26 -0
  9. package/config/instrumentation.js +62 -0
  10. package/drizzle/0000_initial.sql +52 -0
  11. package/drizzle/0001_nostalgic_sersi.sql +11 -0
  12. package/drizzle/0002_black_daimon_hellstrom.sql +19 -0
  13. package/drizzle/0003_rename_code_workspaces.sql +5 -0
  14. package/drizzle/meta/0000_snapshot.json +321 -0
  15. package/drizzle/meta/0001_snapshot.json +390 -0
  16. package/drizzle/meta/0002_snapshot.json +411 -0
  17. package/drizzle/meta/0003_snapshot.json +419 -0
  18. package/drizzle/meta/_journal.json +34 -0
  19. package/lib/actions.js +44 -0
  20. package/lib/ai/agent.js +86 -0
  21. package/lib/ai/index.js +342 -0
  22. package/lib/ai/model.js +180 -0
  23. package/lib/ai/tools.js +269 -0
  24. package/lib/ai/web-search.js +42 -0
  25. package/lib/auth/actions.js +28 -0
  26. package/lib/auth/config.js +27 -0
  27. package/lib/auth/edge-config.js +27 -0
  28. package/lib/auth/index.js +27 -0
  29. package/lib/auth/middleware.js +62 -0
  30. package/lib/channels/base.js +56 -0
  31. package/lib/channels/index.js +15 -0
  32. package/lib/channels/telegram.js +148 -0
  33. package/lib/chat/actions.js +579 -0
  34. package/lib/chat/api.js +140 -0
  35. package/lib/chat/components/app-sidebar.js +213 -0
  36. package/lib/chat/components/app-sidebar.jsx +279 -0
  37. package/lib/chat/components/chat-header.js +192 -0
  38. package/lib/chat/components/chat-header.jsx +223 -0
  39. package/lib/chat/components/chat-input.js +236 -0
  40. package/lib/chat/components/chat-input.jsx +249 -0
  41. package/lib/chat/components/chat-nav-context.js +11 -0
  42. package/lib/chat/components/chat-nav-context.jsx +11 -0
  43. package/lib/chat/components/chat-page.js +99 -0
  44. package/lib/chat/components/chat-page.jsx +121 -0
  45. package/lib/chat/components/chat.js +153 -0
  46. package/lib/chat/components/chat.jsx +199 -0
  47. package/lib/chat/components/chats-page.js +367 -0
  48. package/lib/chat/components/chats-page.jsx +394 -0
  49. package/lib/chat/components/code-mode-toggle.js +132 -0
  50. package/lib/chat/components/code-mode-toggle.jsx +163 -0
  51. package/lib/chat/components/crons-page.js +172 -0
  52. package/lib/chat/components/crons-page.jsx +244 -0
  53. package/lib/chat/components/greeting.js +11 -0
  54. package/lib/chat/components/greeting.jsx +16 -0
  55. package/lib/chat/components/icons.js +805 -0
  56. package/lib/chat/components/icons.jsx +751 -0
  57. package/lib/chat/components/index.js +20 -0
  58. package/lib/chat/components/message.js +363 -0
  59. package/lib/chat/components/message.jsx +422 -0
  60. package/lib/chat/components/messages.js +65 -0
  61. package/lib/chat/components/messages.jsx +74 -0
  62. package/lib/chat/components/notifications-page.js +56 -0
  63. package/lib/chat/components/notifications-page.jsx +87 -0
  64. package/lib/chat/components/page-layout.js +21 -0
  65. package/lib/chat/components/page-layout.jsx +28 -0
  66. package/lib/chat/components/pull-requests-page.js +103 -0
  67. package/lib/chat/components/pull-requests-page.jsx +113 -0
  68. package/lib/chat/components/settings-layout.js +39 -0
  69. package/lib/chat/components/settings-layout.jsx +53 -0
  70. package/lib/chat/components/settings-secrets-page.js +216 -0
  71. package/lib/chat/components/settings-secrets-page.jsx +264 -0
  72. package/lib/chat/components/sidebar-history-item.js +138 -0
  73. package/lib/chat/components/sidebar-history-item.jsx +119 -0
  74. package/lib/chat/components/sidebar-history.js +167 -0
  75. package/lib/chat/components/sidebar-history.jsx +220 -0
  76. package/lib/chat/components/sidebar-user-nav.js +61 -0
  77. package/lib/chat/components/sidebar-user-nav.jsx +77 -0
  78. package/lib/chat/components/swarm-page.js +157 -0
  79. package/lib/chat/components/swarm-page.jsx +210 -0
  80. package/lib/chat/components/tool-call.js +89 -0
  81. package/lib/chat/components/tool-call.jsx +107 -0
  82. package/lib/chat/components/triggers-page.js +153 -0
  83. package/lib/chat/components/triggers-page.jsx +221 -0
  84. package/lib/chat/components/ui/combobox.js +98 -0
  85. package/lib/chat/components/ui/combobox.jsx +114 -0
  86. package/lib/chat/components/ui/confirm-dialog.js +53 -0
  87. package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
  88. package/lib/chat/components/ui/dropdown-menu.js +194 -0
  89. package/lib/chat/components/ui/dropdown-menu.jsx +215 -0
  90. package/lib/chat/components/ui/rename-dialog.js +78 -0
  91. package/lib/chat/components/ui/rename-dialog.jsx +74 -0
  92. package/lib/chat/components/ui/scroll-area.js +13 -0
  93. package/lib/chat/components/ui/scroll-area.jsx +17 -0
  94. package/lib/chat/components/ui/separator.js +21 -0
  95. package/lib/chat/components/ui/separator.jsx +18 -0
  96. package/lib/chat/components/ui/sheet.js +75 -0
  97. package/lib/chat/components/ui/sheet.jsx +95 -0
  98. package/lib/chat/components/ui/sidebar.js +228 -0
  99. package/lib/chat/components/ui/sidebar.jsx +246 -0
  100. package/lib/chat/components/ui/tooltip.js +56 -0
  101. package/lib/chat/components/ui/tooltip.jsx +66 -0
  102. package/lib/chat/components/upgrade-dialog.js +151 -0
  103. package/lib/chat/components/upgrade-dialog.jsx +170 -0
  104. package/lib/chat/utils.js +11 -0
  105. package/lib/code/actions.js +153 -0
  106. package/lib/code/code-page.js +22 -0
  107. package/lib/code/code-page.jsx +25 -0
  108. package/lib/code/index.js +1 -0
  109. package/lib/code/terminal-view.js +201 -0
  110. package/lib/code/terminal-view.jsx +224 -0
  111. package/lib/code/ws-proxy.js +80 -0
  112. package/lib/cron.js +246 -0
  113. package/lib/db/api-keys.js +163 -0
  114. package/lib/db/chats.js +168 -0
  115. package/lib/db/code-workspaces.js +110 -0
  116. package/lib/db/index.js +52 -0
  117. package/lib/db/notifications.js +99 -0
  118. package/lib/db/schema.js +66 -0
  119. package/lib/db/update-check.js +96 -0
  120. package/lib/db/users.js +89 -0
  121. package/lib/paths.js +42 -0
  122. package/lib/tools/create-job.js +97 -0
  123. package/lib/tools/docker.js +146 -0
  124. package/lib/tools/github.js +271 -0
  125. package/lib/tools/openai.js +35 -0
  126. package/lib/tools/telegram.js +292 -0
  127. package/lib/triggers.js +104 -0
  128. package/lib/utils/render-md.js +111 -0
  129. package/package.json +118 -0
  130. package/setup/lib/auth.mjs +81 -0
  131. package/setup/lib/env.mjs +21 -0
  132. package/setup/lib/fs-utils.mjs +20 -0
  133. package/setup/lib/github.mjs +149 -0
  134. package/setup/lib/prerequisites.mjs +155 -0
  135. package/setup/lib/prompts.mjs +267 -0
  136. package/setup/lib/providers.mjs +105 -0
  137. package/setup/lib/sync.mjs +125 -0
  138. package/setup/lib/targets.mjs +45 -0
  139. package/setup/lib/telegram-verify.mjs +63 -0
  140. package/setup/lib/telegram.mjs +76 -0
  141. package/setup/setup-cloud.mjs +833 -0
  142. package/setup/setup-local.mjs +377 -0
  143. package/setup/setup-telegram.mjs +265 -0
  144. package/setup/setup.mjs +87 -0
  145. package/templates/.dockerignore +5 -0
  146. package/templates/.env.example +104 -0
  147. package/templates/.github/workflows/auto-merge.yml +117 -0
  148. package/templates/.github/workflows/notify-job-failed.yml +64 -0
  149. package/templates/.github/workflows/notify-pr-complete.yml +119 -0
  150. package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
  151. package/templates/.github/workflows/run-job.yml +89 -0
  152. package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
  153. package/templates/.gitignore.template +45 -0
  154. package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
  155. package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
  156. package/templates/CLAUDE.md +29 -0
  157. package/templates/CLAUDE.md.template +308 -0
  158. package/templates/app/api/[...gigaclaw]/route.js +1 -0
  159. package/templates/app/api/auth/[...nextauth]/route.js +1 -0
  160. package/templates/app/chat/[chatId]/page.js +9 -0
  161. package/templates/app/chats/page.js +7 -0
  162. package/templates/app/code/[codeWorkspaceId]/page.js +9 -0
  163. package/templates/app/components/ascii-logo.jsx +12 -0
  164. package/templates/app/components/login-form.jsx +92 -0
  165. package/templates/app/components/setup-form.jsx +82 -0
  166. package/templates/app/components/theme-provider.jsx +11 -0
  167. package/templates/app/components/theme-toggle.jsx +38 -0
  168. package/templates/app/components/ui/button.jsx +21 -0
  169. package/templates/app/components/ui/card.jsx +23 -0
  170. package/templates/app/components/ui/input.jsx +10 -0
  171. package/templates/app/components/ui/label.jsx +10 -0
  172. package/templates/app/crons/page.js +5 -0
  173. package/templates/app/globals.css +90 -0
  174. package/templates/app/layout.js +33 -0
  175. package/templates/app/login/page.js +15 -0
  176. package/templates/app/notifications/page.js +7 -0
  177. package/templates/app/page.js +7 -0
  178. package/templates/app/pull-requests/page.js +7 -0
  179. package/templates/app/settings/crons/page.js +5 -0
  180. package/templates/app/settings/layout.js +7 -0
  181. package/templates/app/settings/page.js +5 -0
  182. package/templates/app/settings/secrets/page.js +5 -0
  183. package/templates/app/settings/triggers/page.js +5 -0
  184. package/templates/app/stream/chat/route.js +1 -0
  185. package/templates/app/swarm/page.js +7 -0
  186. package/templates/app/triggers/page.js +5 -0
  187. package/templates/config/CODE_PLANNING.md +14 -0
  188. package/templates/config/CRONS.json +56 -0
  189. package/templates/config/HEARTBEAT.md +3 -0
  190. package/templates/config/JOB_AGENT.md +30 -0
  191. package/templates/config/JOB_PLANNING.md +240 -0
  192. package/templates/config/JOB_SUMMARY.md +130 -0
  193. package/templates/config/SKILL_BUILDING_GUIDE.md +96 -0
  194. package/templates/config/SOUL.md +48 -0
  195. package/templates/config/TRIGGERS.json +58 -0
  196. package/templates/config/WEB_SEARCH_AVAILABLE.md +5 -0
  197. package/templates/config/WEB_SEARCH_UNAVAILABLE.md +3 -0
  198. package/templates/docker/claude-code-job/Dockerfile +34 -0
  199. package/templates/docker/claude-code-job/entrypoint.sh +149 -0
  200. package/templates/docker/claude-code-workspace/.tmux.conf +5 -0
  201. package/templates/docker/claude-code-workspace/Dockerfile +61 -0
  202. package/templates/docker/claude-code-workspace/entrypoint.sh +51 -0
  203. package/templates/docker/event-handler/Dockerfile +20 -0
  204. package/templates/docker/event-handler/ecosystem.config.cjs +7 -0
  205. package/templates/docker/pi-coding-agent-job/Dockerfile +51 -0
  206. package/templates/docker/pi-coding-agent-job/entrypoint.sh +164 -0
  207. package/templates/docker-compose.local.yml +78 -0
  208. package/templates/docker-compose.yml +64 -0
  209. package/templates/instrumentation.js +6 -0
  210. package/templates/middleware.js +23 -0
  211. package/templates/next.config.mjs +3 -0
  212. package/templates/postcss.config.mjs +5 -0
  213. package/templates/public/favicon.ico +0 -0
  214. package/templates/server.js +25 -0
  215. package/templates/skills/LICENSE +21 -0
  216. package/templates/skills/README.md +119 -0
  217. package/templates/skills/brave-search/SKILL.md +79 -0
  218. package/templates/skills/brave-search/content.js +86 -0
  219. package/templates/skills/brave-search/package-lock.json +621 -0
  220. package/templates/skills/brave-search/package.json +14 -0
  221. package/templates/skills/brave-search/search.js +199 -0
  222. package/templates/skills/browser-tools/SKILL.md +196 -0
  223. package/templates/skills/browser-tools/browser-content.js +103 -0
  224. package/templates/skills/browser-tools/browser-cookies.js +35 -0
  225. package/templates/skills/browser-tools/browser-eval.js +53 -0
  226. package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
  227. package/templates/skills/browser-tools/browser-nav.js +44 -0
  228. package/templates/skills/browser-tools/browser-pick.js +162 -0
  229. package/templates/skills/browser-tools/browser-screenshot.js +34 -0
  230. package/templates/skills/browser-tools/browser-start.js +87 -0
  231. package/templates/skills/browser-tools/package-lock.json +2556 -0
  232. package/templates/skills/browser-tools/package.json +19 -0
  233. package/templates/skills/google-docs/SKILL.md +23 -0
  234. package/templates/skills/google-docs/create.sh +69 -0
  235. package/templates/skills/google-drive/SKILL.md +47 -0
  236. package/templates/skills/google-drive/delete.sh +47 -0
  237. package/templates/skills/google-drive/download.sh +50 -0
  238. package/templates/skills/google-drive/list.sh +41 -0
  239. package/templates/skills/google-drive/upload.sh +76 -0
  240. package/templates/skills/kie-ai/SKILL.md +38 -0
  241. package/templates/skills/kie-ai/generate-image.sh +77 -0
  242. package/templates/skills/kie-ai/generate-video.sh +69 -0
  243. package/templates/skills/llm-secrets/SKILL.md +34 -0
  244. package/templates/skills/llm-secrets/llm-secrets.js +33 -0
  245. package/templates/skills/modify-self/SKILL.md +12 -0
  246. package/templates/skills/youtube-transcript/SKILL.md +41 -0
  247. package/templates/skills/youtube-transcript/package-lock.json +24 -0
  248. package/templates/skills/youtube-transcript/package.json +8 -0
  249. package/templates/skills/youtube-transcript/transcript.js +84 -0
@@ -0,0 +1,269 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { tool } from '@langchain/core/tools';
4
+ import { z } from 'zod';
5
+ import { createJob } from '../tools/create-job.js';
6
+ import { getJobStatus } from '../tools/github.js';
7
+ import { claudeMd, skillGuidePath, skillsDir } from '../paths.js';
8
+
9
+ const createJobTool = tool(
10
+ async ({ job_description }) => {
11
+ const result = await createJob(job_description);
12
+ return JSON.stringify({
13
+ success: true,
14
+ job_id: result.job_id,
15
+ branch: result.branch,
16
+ title: result.title,
17
+ });
18
+ },
19
+ {
20
+ name: 'create_job',
21
+ description:
22
+ 'Create an autonomous job that runs a Docker agent in a container. The Docker agent has full filesystem access, web search, browser automation, and other abilities. The job description you provide becomes the Docker agent\'s task prompt. Returns the job ID and branch name.',
23
+ schema: z.object({
24
+ job_description: z
25
+ .string()
26
+ .describe(
27
+ 'Detailed job description including context and requirements. Be specific about what needs to be done.'
28
+ ),
29
+ }),
30
+ }
31
+ );
32
+
33
+ const getJobStatusTool = tool(
34
+ async ({ job_id }) => {
35
+ const result = await getJobStatus(job_id);
36
+ return JSON.stringify(result);
37
+ },
38
+ {
39
+ name: 'get_job_status',
40
+ description:
41
+ 'Check status of running jobs. Returns list of active workflow runs with timing and current step. Use when user asks about job progress, running jobs, or job status.',
42
+ schema: z.object({
43
+ job_id: z
44
+ .string()
45
+ .optional()
46
+ .describe(
47
+ 'Optional: specific job ID to check. If omitted, returns all running jobs.'
48
+ ),
49
+ }),
50
+ }
51
+ );
52
+
53
+ const getSystemTechnicalSpecsTool = tool(
54
+ async () => {
55
+ try {
56
+ return fs.readFileSync(claudeMd, 'utf8');
57
+ } catch {
58
+ return 'No technical documentation found (CLAUDE.md not present in project root).';
59
+ }
60
+ },
61
+ {
62
+ name: 'get_system_technical_specs',
63
+ description:
64
+ 'Read the system architecture and technical documentation (CLAUDE.md). You MUST call this before modifying any config file (CRONS.json, TRIGGERS.json, etc.) or system infrastructure — config entries have advanced fields (per-entry LLM overrides, webhook options, etc.) that are only documented here. Also use this when you need to understand how the system works — event handler, Docker agent, API routes, database, GitHub Actions, deployment, or file structure. NOT for skill creation (use get_skill_building_guide for that).',
65
+ schema: z.object({}),
66
+ }
67
+ );
68
+
69
+ /**
70
+ * Scan skills/ for all skill directories and build an inventory
71
+ * showing which are active vs available (inactive).
72
+ */
73
+ function loadSkillInventory() {
74
+ const activeDir = path.join(skillsDir, 'active');
75
+ const SKIP = new Set(['active', 'LICENSE', 'README.md', '.git', '.github']);
76
+
77
+ try {
78
+ // Get all skill directories
79
+ const allEntries = fs.existsSync(skillsDir)
80
+ ? fs.readdirSync(skillsDir, { withFileTypes: true })
81
+ .filter(e => (e.isDirectory() || e.isSymbolicLink()) && !SKIP.has(e.name))
82
+ : [];
83
+
84
+ // Get active skill names
85
+ const activeNames = new Set();
86
+ if (fs.existsSync(activeDir)) {
87
+ for (const entry of fs.readdirSync(activeDir, { withFileTypes: true })) {
88
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
89
+ activeNames.add(entry.name);
90
+ }
91
+ }
92
+ }
93
+
94
+ // Read frontmatter for each skill
95
+ const skills = [];
96
+ for (const entry of allEntries) {
97
+ const skillMdPath = path.join(skillsDir, entry.name, 'SKILL.md');
98
+ if (!fs.existsSync(skillMdPath)) continue;
99
+
100
+ const content = fs.readFileSync(skillMdPath, 'utf8');
101
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
102
+ if (!frontmatterMatch) continue;
103
+
104
+ const frontmatter = frontmatterMatch[1];
105
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
106
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
107
+
108
+ skills.push({
109
+ dirName: entry.name,
110
+ name: nameMatch ? nameMatch[1].trim() : entry.name,
111
+ description: descMatch ? descMatch[1].trim() : 'No description',
112
+ active: activeNames.has(entry.name),
113
+ });
114
+ }
115
+
116
+ if (skills.length === 0) return '';
117
+
118
+ const active = skills.filter(s => s.active);
119
+ const inactive = skills.filter(s => !s.active);
120
+
121
+ let inventory = '## Current Skills\n\n';
122
+
123
+ if (active.length > 0) {
124
+ inventory += '### Active\n';
125
+ for (const s of active) {
126
+ inventory += `- **${s.dirName}** — ${s.description}\n`;
127
+ }
128
+ inventory += '\n';
129
+ }
130
+
131
+ if (inactive.length > 0) {
132
+ inventory += '### Available (not active)\n';
133
+ for (const s of inactive) {
134
+ inventory += `- **${s.dirName}** — ${s.description}\n`;
135
+ }
136
+ inventory += '\n';
137
+ }
138
+
139
+ inventory += 'Use `get_skill_details` with a skill name to read its full documentation, setup requirements, and usage.\n\n---\n\n';
140
+
141
+ return inventory;
142
+ } catch {
143
+ return '';
144
+ }
145
+ }
146
+
147
+ const getSkillBuildingGuideTool = tool(
148
+ async () => {
149
+ const inventory = loadSkillInventory();
150
+ try {
151
+ const guide = fs.readFileSync(skillGuidePath, 'utf8');
152
+ return inventory + guide;
153
+ } catch {
154
+ return inventory || 'Skill guide not found.';
155
+ }
156
+ },
157
+ {
158
+ name: 'get_skill_building_guide',
159
+ description:
160
+ 'Load the skill building guide and a full inventory of all skills (active and available-but-inactive). You MUST call this before creating or modifying any skill — the guide contains required file structure, naming conventions, SKILL.md frontmatter format, activation steps, and testing procedures that are only documented there. Also use this when you need to check what skills already exist — it shows both active and inactive skills with descriptions. Skills are lightweight bash/Node.js wrappers in `skills/` that extend what agents can do. NOT for understanding the system architecture (use get_system_technical_specs for that).',
161
+ schema: z.object({}),
162
+ }
163
+ );
164
+
165
+ const getSkillDetailsTool = tool(
166
+ async ({ skill_name }) => {
167
+ const skillMdPath = path.join(skillsDir, skill_name, 'SKILL.md');
168
+ try {
169
+ return fs.readFileSync(skillMdPath, 'utf8');
170
+ } catch {
171
+ return `Skill "${skill_name}" not found. Use get_skill_building_guide to see available skills.`;
172
+ }
173
+ },
174
+ {
175
+ name: 'get_skill_details',
176
+ description:
177
+ 'Read the full documentation for a specific skill by name. Returns the complete SKILL.md including setup requirements, usage examples, and credential needs. Use this to understand what a skill does before suggesting it to the user, or to get credential setup details. Works with both active and inactive skills.',
178
+ schema: z.object({
179
+ skill_name: z.string(),
180
+ }),
181
+ }
182
+ );
183
+
184
+ /**
185
+ * Create a start_coding tool bound to a specific workspace context.
186
+ * @param {object} context
187
+ * @param {string} context.repo - GitHub repo (e.g. "owner/repo")
188
+ * @param {string} context.branch - Git branch
189
+ * @param {string} context.workspaceId - Pre-created workspace row ID
190
+ * @returns {import('@langchain/core/tools').StructuredTool}
191
+ */
192
+ function createStartCodingTool({ repo, branch, workspaceId }) {
193
+ return tool(
194
+ async ({ task_description }) => {
195
+ try {
196
+ const { randomUUID } = await import('crypto');
197
+ const containerName = `code-workspace-${randomUUID().slice(0, 8)}`;
198
+
199
+ const { getCodeWorkspaceById, updateContainerName } = await import('../db/code-workspaces.js');
200
+ const workspace = getCodeWorkspaceById(workspaceId);
201
+ const codingAgent = workspace?.codingAgent || 'claude-code';
202
+
203
+ const { createCodeWorkspaceContainer } = await import('../tools/docker.js');
204
+ await createCodeWorkspaceContainer({ containerName, repo, branch, codingAgent });
205
+
206
+ updateContainerName(workspaceId, containerName);
207
+
208
+ return JSON.stringify({
209
+ success: true,
210
+ workspaceId,
211
+ workspaceUrl: `/code/${workspaceId}`,
212
+ });
213
+ } catch (err) {
214
+ console.error('[start_coding] Failed to launch workspace:', err);
215
+ return JSON.stringify({
216
+ success: false,
217
+ error: err.message || 'Failed to launch workspace',
218
+ });
219
+ }
220
+ },
221
+ {
222
+ name: 'start_coding',
223
+ description:
224
+ 'Launch a live code workspace in a Docker container. Only call this when the user explicitly says they are ready to start coding (e.g. "let\'s start coding", "okay let\'s get started", "launch it"). Returns a link to the live workspace.',
225
+ schema: z.object({
226
+ task_description: z
227
+ .string()
228
+ .describe('A summary of what the user wants to build or work on, based on the conversation so far.'),
229
+ }),
230
+ }
231
+ );
232
+ }
233
+
234
+ /**
235
+ * Create a get_repository_details tool bound to a specific repo/branch.
236
+ * Fetches CLAUDE.md and README.md from the repo via GitHub API.
237
+ * @param {object} context
238
+ * @param {string} context.repo - GitHub repo (e.g. "owner/repo")
239
+ * @param {string} context.branch - Git branch
240
+ * @returns {import('@langchain/core/tools').StructuredTool}
241
+ */
242
+ function createGetRepositoryDetailsTool({ repo, branch }) {
243
+ return tool(
244
+ async () => {
245
+ const { githubApi } = await import('../tools/github.js');
246
+ const files = ['CLAUDE.md', 'README.md'];
247
+ const results = {};
248
+
249
+ for (const file of files) {
250
+ try {
251
+ const data = await githubApi(`/repos/${repo}/contents/${file}?ref=${branch}`);
252
+ results[file] = Buffer.from(data.content, 'base64').toString('utf8');
253
+ } catch {
254
+ results[file] = 'Not found';
255
+ }
256
+ }
257
+
258
+ return JSON.stringify(results);
259
+ },
260
+ {
261
+ name: 'get_repository_details',
262
+ description:
263
+ 'Fetch CLAUDE.md and README.md from the selected repository and branch. Call this as your first action when the user sends their first message to understand the project context.',
264
+ schema: z.object({}),
265
+ }
266
+ );
267
+ }
268
+
269
+ export { createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool, getSkillBuildingGuideTool, getSkillDetailsTool, createStartCodingTool, createGetRepositoryDetailsTool };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Web search tool factory for supported LLM providers.
3
+ * Anthropic and OpenAI provide server-side web search — the provider
4
+ * runs the search and the model's synthesized answer streams normally.
5
+ */
6
+
7
+ export function getProvider() {
8
+ return process.env.LLM_PROVIDER || 'anthropic';
9
+ }
10
+
11
+ export function isWebSearchAvailable() {
12
+ if (process.env.WEB_SEARCH === 'false') return false;
13
+ const provider = getProvider();
14
+ return provider === 'anthropic' || provider === 'openai';
15
+ }
16
+
17
+ /**
18
+ * Create a server-side web search tool for the current provider.
19
+ * Returns null if the provider doesn't support it or if the import fails.
20
+ */
21
+ export async function createWebSearchTool() {
22
+ if (!isWebSearchAvailable()) return null;
23
+
24
+ const provider = getProvider();
25
+
26
+ try {
27
+ if (provider === 'anthropic') {
28
+ const { tools } = await import('@langchain/anthropic');
29
+ return tools.webSearch_20250305();
30
+ }
31
+
32
+ if (provider === 'openai') {
33
+ const { tools } = await import('@langchain/openai');
34
+ return tools.webSearch();
35
+ }
36
+
37
+ return null;
38
+ } catch (err) {
39
+ console.warn(`[web-search] Failed to create web search tool for ${provider}:`, err.message);
40
+ return null;
41
+ }
42
+ }
@@ -0,0 +1,28 @@
1
+ 'use server';
2
+
3
+ import { createFirstUser } from '../db/users.js';
4
+
5
+ /**
6
+ * Create the first admin user (setup action).
7
+ * Uses atomic createFirstUser() to prevent race conditions.
8
+ * No session/token is created — the admin must log in through the normal auth flow.
9
+ *
10
+ * @param {string} email
11
+ * @param {string} password
12
+ * @returns {Promise<{ success?: boolean, error?: string }>}
13
+ */
14
+ export async function setupAdmin(email, password) {
15
+ if (!email || !password) {
16
+ return { error: 'Email and password are required.' };
17
+ }
18
+ if (password.length < 8) {
19
+ return { error: 'Password must be at least 8 characters.' };
20
+ }
21
+
22
+ const created = createFirstUser(email, password);
23
+ if (!created) {
24
+ return { error: 'Setup already completed.' };
25
+ }
26
+
27
+ return { success: true };
28
+ }
@@ -0,0 +1,27 @@
1
+ import NextAuth from 'next-auth';
2
+ import Credentials from 'next-auth/providers/credentials';
3
+ import { authConfig } from './edge-config.js';
4
+
5
+ export const { handlers, signIn, signOut, auth } = NextAuth({
6
+ ...authConfig,
7
+ providers: [
8
+ Credentials({
9
+ credentials: {
10
+ email: { label: 'Email', type: 'email' },
11
+ password: { label: 'Password', type: 'password' },
12
+ },
13
+ async authorize(credentials) {
14
+ if (!credentials?.email || !credentials?.password) return null;
15
+
16
+ const { getUserByEmail, verifyPassword } = await import('../db/users.js');
17
+ const user = getUserByEmail(credentials.email);
18
+ if (!user) return null;
19
+
20
+ const valid = await verifyPassword(user, credentials.password);
21
+ if (!valid) return null;
22
+
23
+ return { id: user.id, email: user.email, role: user.role };
24
+ },
25
+ }),
26
+ ],
27
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Edge-safe auth configuration — shared between middleware and server.
3
+ * Contains only JWT/session/callbacks/pages config. No providers, no DB imports.
4
+ * Both instances use the same AUTH_SECRET for JWT signing/verification.
5
+ *
6
+ * Official pattern: https://authjs.dev/guides/edge-compatibility
7
+ */
8
+ export const authConfig = {
9
+ providers: [],
10
+ session: { strategy: 'jwt' },
11
+ pages: { signIn: '/login' },
12
+ callbacks: {
13
+ jwt({ token, user }) {
14
+ if (user) {
15
+ token.role = user.role;
16
+ }
17
+ return token;
18
+ },
19
+ session({ session, token }) {
20
+ if (session.user) {
21
+ session.user.id = token.sub;
22
+ session.user.role = token.role;
23
+ }
24
+ return session;
25
+ },
26
+ },
27
+ };
@@ -0,0 +1,27 @@
1
+ import { handlers, auth } from './config.js';
2
+
3
+ // Re-export Auth.js route handlers (GET + POST for [...nextauth])
4
+ export const { GET, POST } = handlers;
5
+
6
+ // Re-export auth for session checking
7
+ export { auth };
8
+
9
+ /**
10
+ * Get the auth state for the main page (server component).
11
+ * Returns both the session and whether setup is needed, in one call.
12
+ * DB import is dynamic so it doesn't get pulled in at module level.
13
+ *
14
+ * @returns {Promise<{ session: object|null, needsSetup: boolean }>}
15
+ */
16
+ export async function getPageAuthState() {
17
+ const { getUserCount } = await import('../db/users.js');
18
+ const [session, userCount] = await Promise.all([
19
+ auth(),
20
+ Promise.resolve(getUserCount()),
21
+ ]);
22
+
23
+ return {
24
+ session,
25
+ needsSetup: userCount === 0,
26
+ };
27
+ }
@@ -0,0 +1,62 @@
1
+ import NextAuth from 'next-auth';
2
+ import { authConfig } from './edge-config.js';
3
+ import { NextResponse } from 'next/server';
4
+
5
+ const { auth } = NextAuth(authConfig);
6
+
7
+ export const middleware = auth((req) => {
8
+ const { pathname } = req.nextUrl;
9
+
10
+ // API routes use their own centralized auth (checkAuth in api/index.js)
11
+ if (pathname.startsWith('/api')) return;
12
+
13
+ // Static assets from public/ — skip auth for common file extensions
14
+ if (/\.(?:svg|png|jpg|jpeg|gif|webp|ico|css|js|woff2?|ttf|eot|mp4|webm)$/i.test(pathname)) {
15
+ return;
16
+ }
17
+
18
+ // /login is the only unprotected page (login + first-user setup)
19
+ if (pathname === '/login') {
20
+ if (req.auth) return NextResponse.redirect(new URL('/', req.url));
21
+ return;
22
+ }
23
+
24
+ // Everything else requires auth
25
+ if (!req.auth) {
26
+ const response = NextResponse.redirect(new URL('/login', req.url));
27
+
28
+ // Clear stale session cookies that can't be decrypted (e.g. after AUTH_SECRET rotation
29
+ // or container restart). Auth.js clears these internally in route handlers via
30
+ // sessionStore.clean(), but NOT in middleware — so the bad cookie loops forever.
31
+ // Only session-token cookies are cleared; csrf-token and callback-url are left intact.
32
+ const cookieNames = Object.keys(req.cookies.getAll().reduce((acc, c) => { acc[c.name] = true; return acc; }, {}));
33
+ const staleSessionCookies = cookieNames.filter(name =>
34
+ name === 'authjs.session-token' ||
35
+ name === '__Secure-authjs.session-token' ||
36
+ /^authjs\.session-token\.\d+$/.test(name) ||
37
+ /^__Secure-authjs\.session-token\.\d+$/.test(name)
38
+ );
39
+
40
+ if (staleSessionCookies.length > 0) {
41
+ for (const name of staleSessionCookies) {
42
+ response.cookies.set(name, '', { maxAge: 0, path: '/' });
43
+ }
44
+ }
45
+
46
+ return response;
47
+ }
48
+ });
49
+
50
+ // IMPORTANT: The `config` export (matcher) is intentionally NOT defined here.
51
+ //
52
+ // Next.js / Turbopack requires the `config` export to be a static literal object
53
+ // defined DIRECTLY in the project's root middleware.js file. It cannot be
54
+ // re-exported from a module (including this one) because the bundler statically
55
+ // analyses it at compile time and will throw:
56
+ //
57
+ // "The `config` export in Middleware must be a static object literal."
58
+ //
59
+ // The correct pattern lives in the project template at templates/middleware.js:
60
+ //
61
+ // export { middleware } from 'gigaclaw/middleware';
62
+ // export const config = { matcher: ['/((?!_next|favicon.ico).*)'] };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Base channel adapter interface.
3
+ * Every chat channel (Telegram, Slack, web, etc.) implements this contract.
4
+ */
5
+ class ChannelAdapter {
6
+ /**
7
+ * Handle an incoming webhook request from this channel.
8
+ * Returns normalized message data or null if no action needed.
9
+ *
10
+ * @param {Request} request - Incoming HTTP request
11
+ * @returns {Promise<{ threadId: string, text: string, attachments: Array, metadata: object } | null>}
12
+ *
13
+ * Attachments array (may be empty) — only non-text content that the LLM needs to see:
14
+ * { category: "image", mimeType: "image/png", data: Buffer } — send to LLM as vision
15
+ * { category: "document", mimeType: "application/pdf", data: Buffer } — future: extract/attach
16
+ *
17
+ * The adapter downloads authenticated files and normalizes them.
18
+ * Voice/audio messages are fully resolved by the adapter — transcribed to text
19
+ * and included in the `text` field. They are NOT passed as attachments.
20
+ */
21
+ async receive(request) {
22
+ throw new Error('Not implemented');
23
+ }
24
+
25
+ /**
26
+ * Called when message is received — adapter shows acknowledgment.
27
+ * Telegram: thumbs up reaction. Slack: emoji reaction. Web: no-op.
28
+ */
29
+ async acknowledge(metadata) {}
30
+
31
+ /**
32
+ * Called while AI is processing — adapter shows activity.
33
+ * Telegram: typing indicator. Slack: typing indicator. Web: no-op (streaming handles this).
34
+ * Returns a stop function.
35
+ */
36
+ startProcessingIndicator(metadata) {
37
+ return () => {};
38
+ }
39
+
40
+ /**
41
+ * Send a complete (non-streaming) response back to the channel.
42
+ */
43
+ async sendResponse(threadId, text, metadata) {
44
+ throw new Error('Not implemented');
45
+ }
46
+
47
+ /**
48
+ * Whether this channel supports real streaming (e.g., web chat via Vercel AI SDK).
49
+ * If true, the AI layer provides a stream instead of a complete response.
50
+ */
51
+ get supportsStreaming() {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ export { ChannelAdapter };
@@ -0,0 +1,15 @@
1
+ import { TelegramAdapter } from './telegram.js';
2
+
3
+ let _telegramAdapter = null;
4
+
5
+ /**
6
+ * Get the Telegram channel adapter (lazy singleton).
7
+ * @param {string} botToken - Telegram bot token
8
+ * @returns {TelegramAdapter}
9
+ */
10
+ export function getTelegramAdapter(botToken) {
11
+ if (!_telegramAdapter || _telegramAdapter.botToken !== botToken) {
12
+ _telegramAdapter = new TelegramAdapter(botToken);
13
+ }
14
+ return _telegramAdapter;
15
+ }