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,149 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Extract job ID from branch name (job/uuid -> uuid), fallback to random UUID
5
+ if [[ "$BRANCH" == job/* ]]; then
6
+ JOB_ID="${BRANCH#job/}"
7
+ else
8
+ JOB_ID=$(cat /proc/sys/kernel/random/uuid)
9
+ fi
10
+ echo "Job ID: ${JOB_ID}"
11
+
12
+ # Export SECRETS (JSON) as flat env vars (GH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, etc.)
13
+ if [ -n "$SECRETS" ]; then
14
+ eval $(echo "$SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
15
+ fi
16
+
17
+ # Export LLM_SECRETS (JSON) as flat env vars
18
+ if [ -n "$LLM_SECRETS" ]; then
19
+ eval $(echo "$LLM_SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
20
+ fi
21
+
22
+ # Unset ANTHROPIC_API_KEY so Claude Code uses the OAuth token.
23
+ # If both are set, Claude Code prioritizes API key (billing to API credits)
24
+ # which defeats the purpose. The API key is for the event handler, not here.
25
+ unset ANTHROPIC_API_KEY
26
+
27
+ # Git setup - derive identity from GitHub token
28
+ gh auth setup-git
29
+ GH_USER_JSON=$(gh api user -q '{name: .name, login: .login, email: .email, id: .id}')
30
+ GH_USER_NAME=$(echo "$GH_USER_JSON" | jq -r '.name // .login')
31
+ GH_USER_EMAIL=$(echo "$GH_USER_JSON" | jq -r '.email // "\(.id)+\(.login)@users.noreply.github.com"')
32
+ git config --global user.name "$GH_USER_NAME"
33
+ git config --global user.email "$GH_USER_EMAIL"
34
+
35
+ # Clone branch
36
+ if [ -n "$REPO_URL" ]; then
37
+ git clone --single-branch --branch "$BRANCH" --depth 1 "$REPO_URL" /job
38
+ else
39
+ echo "No REPO_URL provided"
40
+ fi
41
+
42
+ cd /job
43
+
44
+ # Install npm deps for active skills (native deps need correct Linux arch)
45
+ for skill_dir in /job/skills/active/*/; do
46
+ if [ -f "${skill_dir}package.json" ]; then
47
+ echo "Installing skill deps: $(basename "$skill_dir")"
48
+ (cd "$skill_dir" && npm install --omit=dev --no-package-lock)
49
+ fi
50
+ done
51
+
52
+ # Start Chrome if puppeteer installed it (needed by browser-tools skill)
53
+ CHROME_PID=""
54
+ CHROME_BIN=$(find /home/agent/.cache/puppeteer -name "chrome" -type f 2>/dev/null | head -1)
55
+ if [ -n "$CHROME_BIN" ]; then
56
+ $CHROME_BIN --headless --no-sandbox --disable-gpu --remote-debugging-port=9222 2>/dev/null &
57
+ CHROME_PID=$!
58
+ sleep 2
59
+ fi
60
+
61
+ # Setup logs
62
+ LOG_DIR="/job/logs/${JOB_ID}"
63
+ mkdir -p "${LOG_DIR}"
64
+
65
+ # Build system prompt from config MD files
66
+ SYSTEM_PROMPT_FILE="${LOG_DIR}/system-prompt.md"
67
+ SYSTEM_FILES=("SOUL.md" "JOB_AGENT.md")
68
+ > "$SYSTEM_PROMPT_FILE"
69
+ for i in "${!SYSTEM_FILES[@]}"; do
70
+ cat "/job/config/${SYSTEM_FILES[$i]}" >> "$SYSTEM_PROMPT_FILE"
71
+ if [ "$i" -lt $((${#SYSTEM_FILES[@]} - 1)) ]; then
72
+ echo -e "\n\n" >> "$SYSTEM_PROMPT_FILE"
73
+ fi
74
+ done
75
+
76
+ # Resolve {{datetime}} variable in system prompt
77
+ sed -i "s/{{datetime}}/$(date -u +"%Y-%m-%dT%H:%M:%SZ")/g" "$SYSTEM_PROMPT_FILE"
78
+
79
+ # Read job metadata from job.config.json
80
+ JOB_CONFIG="/job/logs/${JOB_ID}/job.config.json"
81
+ TITLE=$(jq -r '.title // empty' "$JOB_CONFIG")
82
+ JOB_DESCRIPTION=$(jq -r '.job // empty' "$JOB_CONFIG")
83
+
84
+ PROMPT="
85
+
86
+ # Your Job
87
+
88
+ ${JOB_DESCRIPTION}"
89
+
90
+ # Build --model flag if LLM_MODEL is set
91
+ MODEL_FLAG=""
92
+ if [ -n "$LLM_MODEL" ]; then
93
+ MODEL_FLAG="--model $LLM_MODEL"
94
+ fi
95
+
96
+ # Run Claude Code — capture exit code instead of letting set -e kill the script
97
+ # stream-json gives the full conversation trace (thinking, tool calls, results)
98
+ # similar to Pi's .jsonl session logs
99
+ set +e
100
+ claude -p "$PROMPT" \
101
+ $MODEL_FLAG \
102
+ --append-system-prompt-file "$SYSTEM_PROMPT_FILE" \
103
+ --dangerously-skip-permissions \
104
+ --verbose \
105
+ --output-format stream-json \
106
+ > "${LOG_DIR}/claude-session.jsonl" 2>"${LOG_DIR}/claude-stderr.log"
107
+ AGENT_EXIT=$?
108
+
109
+ # Commit based on outcome
110
+ if [ $AGENT_EXIT -ne 0 ]; then
111
+ # Claude Code failed — only commit session logs, not partial code changes
112
+ git reset || true
113
+ git add -f "${LOG_DIR}"
114
+ git commit -m "🤖 Agent Job: ${TITLE} (failed)" || true
115
+ else
116
+ # Claude Code succeeded — commit everything
117
+ git add -A
118
+ git add -f "${LOG_DIR}"
119
+ git commit -m "🤖 Agent Job: ${TITLE}" || true
120
+ fi
121
+
122
+ git push origin
123
+
124
+ # Capture log commit SHA, then remove logs so they don't merge into main
125
+ LOG_SHA=$(git rev-parse HEAD)
126
+ git rm -rf "${LOG_DIR}"
127
+ git commit -m "done." || true
128
+ git push origin
129
+ set -e
130
+
131
+ # Cleanup Chrome
132
+ if [ -n "$CHROME_PID" ]; then
133
+ kill $CHROME_PID 2>/dev/null || true
134
+ fi
135
+
136
+ # Create PR with log permalink (auto-merge handled by GitHub Actions workflow)
137
+ REPO_SLUG=$(gh repo view --json nameWithOwner -q .nameWithOwner)
138
+ LOG_URL="https://github.com/${REPO_SLUG}/tree/${LOG_SHA}/logs/${JOB_ID}"
139
+ gh pr create --title "🤖 Agent Job: ${TITLE}" \
140
+ --body "📋 [View Job Logs](${LOG_URL})"$'\n\n---\n\n'"${JOB_DESCRIPTION}" \
141
+ --base main || true
142
+
143
+ # Re-raise failure so the workflow reports it
144
+ if [ $AGENT_EXIT -ne 0 ]; then
145
+ echo "Claude Code exited with code ${AGENT_EXIT}"
146
+ exit $AGENT_EXIT
147
+ fi
148
+
149
+ echo "Done. Job ID: ${JOB_ID}"
@@ -0,0 +1,5 @@
1
+ set -g default-terminal "xterm-256color"
2
+ set-option -ga terminal-overrides ",xterm-256color:Tc"
3
+ set -g mouse on
4
+ set -g history-limit 50000
5
+ setw -q -g utf8 on
@@ -0,0 +1,61 @@
1
+ FROM ubuntu:24.04
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive
4
+
5
+ # System packages
6
+ RUN apt-get update && apt-get install -y \
7
+ tmux \
8
+ git \
9
+ curl \
10
+ jq \
11
+ build-essential \
12
+ locales \
13
+ fonts-noto-color-emoji \
14
+ fonts-symbola \
15
+ ca-certificates \
16
+ gnupg \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ # Locale
20
+ RUN locale-gen en_US.UTF-8
21
+ ENV LANG=en_US.UTF-8
22
+ ENV LC_ALL=en_US.UTF-8
23
+ ENV LC_CTYPE=en_US.UTF-8
24
+
25
+ # GitHub CLI
26
+ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
27
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
28
+ && apt-get update && apt-get install -y gh \
29
+ && rm -rf /var/lib/apt/lists/*
30
+
31
+ # ttyd (web terminal)
32
+ RUN curl -fsSL -o /usr/local/bin/ttyd https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.x86_64 \
33
+ && chmod +x /usr/local/bin/ttyd
34
+
35
+ # Node.js 22
36
+ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
37
+ && apt-get install -y nodejs \
38
+ && rm -rf /var/lib/apt/lists/*
39
+
40
+ # Claude Code
41
+ RUN npm install -g @anthropic-ai/claude-code
42
+
43
+ # Non-root user
44
+ RUN useradd -m -s /bin/bash claude-code \
45
+ && mkdir -p /home/claude-code/workspace \
46
+ && chown claude-code:claude-code /home/claude-code/workspace
47
+
48
+ COPY .tmux.conf /home/claude-code/.tmux.conf
49
+ RUN chown claude-code:claude-code /home/claude-code/.tmux.conf
50
+
51
+ COPY entrypoint.sh /entrypoint.sh
52
+ RUN chmod +x /entrypoint.sh
53
+
54
+ USER claude-code
55
+ WORKDIR /home/claude-code/workspace
56
+
57
+ ENV REPO=""
58
+ ENV BRANCH=main
59
+ ENV PORT=7681
60
+
61
+ ENTRYPOINT ["/entrypoint.sh"]
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Git setup - derive identity from GitHub token
5
+ gh auth setup-git
6
+ GH_USER_JSON=$(gh api user -q '{name: .name, login: .login, email: .email, id: .id}')
7
+ GH_USER_NAME=$(echo "$GH_USER_JSON" | jq -r '.name // .login')
8
+ GH_USER_EMAIL=$(echo "$GH_USER_JSON" | jq -r '.email // "\(.id)+\(.login)@users.noreply.github.com"')
9
+ git config --global user.name "$GH_USER_NAME"
10
+ git config --global user.email "$GH_USER_EMAIL"
11
+
12
+ # Clone repo (skip if already cloned — enables docker restart)
13
+ if [ -n "$REPO" ] && [ ! -d "/home/claude-code/workspace/.git" ]; then
14
+ git clone --branch "$BRANCH" "https://github.com/$REPO" /home/claude-code/workspace
15
+ fi
16
+
17
+ cd /home/claude-code/workspace
18
+ WORKSPACE_DIR=$(pwd)
19
+
20
+ # Claude Code auth — use OAuth token, not API key
21
+ unset ANTHROPIC_API_KEY
22
+ export CLAUDE_CODE_OAUTH_TOKEN="${CLAUDE_CODE_OAUTH_TOKEN}"
23
+
24
+ # Skip onboarding and trust dialogs
25
+ mkdir -p ~/.claude
26
+ cat > ~/.claude/settings.json << 'EOF'
27
+ {
28
+ "theme": "dark",
29
+ "hasTrustDialogAccepted": true,
30
+ "skipDangerousModePermissionPrompt": true
31
+ }
32
+ EOF
33
+
34
+ cat > ~/.claude.json << ENDJSON
35
+ {
36
+ "hasCompletedOnboarding": true,
37
+ "projects": {
38
+ "${WORKSPACE_DIR}": {
39
+ "allowedTools": [],
40
+ "hasTrustDialogAccepted": true,
41
+ "hasTrustDialogHooksAccepted": true
42
+ }
43
+ }
44
+ }
45
+ ENDJSON
46
+
47
+ # Start Claude Code in a tmux session
48
+ tmux -u new-session -d -s claude 'claude --dangerously-skip-permissions'
49
+
50
+ # Start ttyd in foreground (PID 1) — serves tmux over WebSocket
51
+ exec ttyd --writable -p "${PORT:-7681}" tmux attach -t claude
@@ -0,0 +1,20 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ RUN apt-get update && apt-get install -y curl git python3 make g++ && \
4
+ curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
5
+ | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
6
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
7
+ | tee /etc/apt/sources.list.d/github-cli.list > /dev/null && \
8
+ apt-get update && apt-get install -y gh && \
9
+ rm -rf /var/lib/apt/lists/*
10
+ RUN npm install -g pm2
11
+
12
+ WORKDIR /app
13
+ COPY package.json package-lock.json* ./
14
+ RUN npm install --omit=dev && \
15
+ npm install --no-save gigabot@$(node -p "require('./package.json').version")
16
+
17
+ COPY /templates/docker/event-handler/ecosystem.config.cjs /opt/ecosystem.config.cjs
18
+
19
+ EXPOSE 80
20
+ CMD ["pm2-runtime", "/opt/ecosystem.config.cjs"]
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ apps: [{
3
+ name: 'next',
4
+ script: 'server.js',
5
+ kill_timeout: 120000,
6
+ }]
7
+ };
@@ -0,0 +1,51 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ git \
5
+ jq \
6
+ curl \
7
+ procps \
8
+ # Chrome/Chromium dependencies
9
+ libnss3 \
10
+ libnspr4 \
11
+ libatk1.0-0 \
12
+ libatk-bridge2.0-0 \
13
+ libcups2 \
14
+ libdrm2 \
15
+ libdbus-1-3 \
16
+ libxkbcommon0 \
17
+ libatspi2.0-0 \
18
+ libxcomposite1 \
19
+ libxdamage1 \
20
+ libxfixes3 \
21
+ libxrandr2 \
22
+ libgbm1 \
23
+ libasound2 \
24
+ libpango-1.0-0 \
25
+ libcairo2 \
26
+ && rm -rf /var/lib/apt/lists/*
27
+
28
+ # Install GitHub CLI
29
+ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
30
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
31
+ && apt-get update && apt-get install -y gh \
32
+ && rm -rf /var/lib/apt/lists/*
33
+
34
+ RUN npm install -g @mariozechner/pi-coding-agent
35
+
36
+ # Create a non-root user and give it ownership of /job.
37
+ RUN useradd -m -s /bin/bash agent \
38
+ && mkdir -p /job \
39
+ && chown agent:agent /job
40
+
41
+ # Create Pi config directory (extension loaded from repo at runtime)
42
+ RUN mkdir -p /home/agent/.pi/agent \
43
+ && chown -R agent:agent /home/agent/.pi
44
+
45
+ COPY entrypoint.sh /entrypoint.sh
46
+ RUN chmod +x /entrypoint.sh
47
+
48
+ USER agent
49
+ WORKDIR /job
50
+
51
+ ENTRYPOINT ["/entrypoint.sh"]
@@ -0,0 +1,164 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Extract job ID from branch name (job/uuid -> uuid), fallback to random UUID
5
+ if [[ "$BRANCH" == job/* ]]; then
6
+ JOB_ID="${BRANCH#job/}"
7
+ else
8
+ JOB_ID=$(cat /proc/sys/kernel/random/uuid)
9
+ fi
10
+ echo "Job ID: ${JOB_ID}"
11
+
12
+ # Export SECRETS (JSON) as flat env vars (GH_TOKEN, ANTHROPIC_API_KEY, etc.)
13
+ # These are filtered from LLM's bash subprocess by env-sanitizer extension
14
+ if [ -n "$SECRETS" ]; then
15
+ eval $(echo "$SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
16
+ fi
17
+
18
+ # Export LLM_SECRETS (JSON) as flat env vars
19
+ # These are NOT filtered - LLM can access these (browser logins, skill API keys, etc.)
20
+ if [ -n "$LLM_SECRETS" ]; then
21
+ eval $(echo "$LLM_SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
22
+ fi
23
+
24
+ # Git setup - derive identity from GitHub token
25
+ gh auth setup-git
26
+ GH_USER_JSON=$(gh api user -q '{name: .name, login: .login, email: .email, id: .id}')
27
+ GH_USER_NAME=$(echo "$GH_USER_JSON" | jq -r '.name // .login')
28
+ GH_USER_EMAIL=$(echo "$GH_USER_JSON" | jq -r '.email // "\(.id)+\(.login)@users.noreply.github.com"')
29
+ git config --global user.name "$GH_USER_NAME"
30
+ git config --global user.email "$GH_USER_EMAIL"
31
+
32
+ # Clone branch
33
+ if [ -n "$REPO_URL" ]; then
34
+ git clone --single-branch --branch "$BRANCH" --depth 1 "$REPO_URL" /job
35
+ else
36
+ echo "No REPO_URL provided"
37
+ fi
38
+
39
+ cd /job
40
+
41
+ # Install npm deps for active skills (native deps need correct Linux arch)
42
+ for skill_dir in /job/skills/active/*/; do
43
+ if [ -f "${skill_dir}package.json" ]; then
44
+ echo "Installing skill deps: $(basename "$skill_dir")"
45
+ (cd "$skill_dir" && npm install --omit=dev --no-package-lock)
46
+ fi
47
+ done
48
+
49
+ # Start Chrome if available (installed by browser-tools skill via Puppeteer)
50
+ CHROME_PID=""
51
+ CHROME_BIN=$(find /home/agent/.cache/puppeteer -name "chrome" -type f 2>/dev/null | head -1)
52
+ if [ -n "$CHROME_BIN" ]; then
53
+ $CHROME_BIN --headless --no-sandbox --disable-gpu --remote-debugging-port=9222 2>/dev/null &
54
+ CHROME_PID=$!
55
+ sleep 2
56
+ fi
57
+
58
+ # Setup logs
59
+ LOG_DIR="/job/logs/${JOB_ID}"
60
+ mkdir -p "${LOG_DIR}"
61
+
62
+ # 1. Build system prompt from config MD files
63
+ SYSTEM_FILES=("SOUL.md" "JOB_AGENT.md")
64
+ > /job/.pi/SYSTEM.md
65
+ for i in "${!SYSTEM_FILES[@]}"; do
66
+ cat "/job/config/${SYSTEM_FILES[$i]}" >> /job/.pi/SYSTEM.md
67
+ if [ "$i" -lt $((${#SYSTEM_FILES[@]} - 1)) ]; then
68
+ echo -e "\n\n" >> /job/.pi/SYSTEM.md
69
+ fi
70
+ done
71
+
72
+ # Resolve {{datetime}} variable in SYSTEM.md
73
+ sed -i "s/{{datetime}}/$(date -u +"%Y-%m-%dT%H:%M:%SZ")/g" /job/.pi/SYSTEM.md
74
+
75
+ # Read job metadata from job.config.json
76
+ JOB_CONFIG="/job/logs/${JOB_ID}/job.config.json"
77
+ TITLE=$(jq -r '.title // empty' "$JOB_CONFIG")
78
+ JOB_DESCRIPTION=$(jq -r '.job // empty' "$JOB_CONFIG")
79
+
80
+ PROMPT="
81
+
82
+ # Your Job
83
+
84
+ ${JOB_DESCRIPTION}"
85
+
86
+ LLM_PROVIDER="${LLM_PROVIDER:-anthropic}"
87
+
88
+ MODEL_FLAGS="--provider $LLM_PROVIDER"
89
+ if [ -n "$LLM_MODEL" ]; then
90
+ MODEL_FLAGS="$MODEL_FLAGS --model $LLM_MODEL"
91
+ fi
92
+
93
+ # Generate models.json for custom provider (OpenAI-compatible endpoints like Ollama)
94
+ if [ "$LLM_PROVIDER" = "custom" ] && [ -n "$OPENAI_BASE_URL" ]; then
95
+ # If no API key was provided, set a dummy so Pi doesn't send empty auth
96
+ if [ -z "$CUSTOM_API_KEY" ]; then
97
+ export CUSTOM_API_KEY="not-needed"
98
+ fi
99
+ cat > /home/agent/.pi/agent/models.json <<MODELS
100
+ {
101
+ "providers": {
102
+ "custom": {
103
+ "baseUrl": "$OPENAI_BASE_URL",
104
+ "api": "openai-completions",
105
+ "apiKey": "CUSTOM_API_KEY",
106
+ "models": [{ "id": "$LLM_MODEL" }]
107
+ }
108
+ }
109
+ }
110
+ MODELS
111
+ fi
112
+
113
+ # Copy custom models.json to PI's global config if present in repo (overrides generated)
114
+ if [ -f "/job/.pi/agent/models.json" ]; then
115
+ mkdir -p /home/agent/.pi/agent
116
+ cp /job/.pi/agent/models.json /home/agent/.pi/agent/models.json
117
+ fi
118
+
119
+ # Run Pi — capture exit code instead of letting set -e kill the script
120
+ set +e
121
+ pi $MODEL_FLAGS -p "$PROMPT" --session-dir "${LOG_DIR}"
122
+ PI_EXIT=$?
123
+
124
+ # 2. Commit based on outcome
125
+ if [ $PI_EXIT -ne 0 ]; then
126
+ # Pi failed — only commit session logs, not partial code changes
127
+ git reset || true
128
+ git add -f "${LOG_DIR}"
129
+ git commit -m "🤖 Agent Job: ${TITLE} (failed)" || true
130
+ else
131
+ # Pi succeeded — commit everything
132
+ git add -A
133
+ git add -f "${LOG_DIR}"
134
+ git commit -m "🤖 Agent Job: ${TITLE}" || true
135
+ fi
136
+
137
+ git push origin
138
+
139
+ # Capture log commit SHA, then remove logs so they don't merge into main
140
+ LOG_SHA=$(git rev-parse HEAD)
141
+ git rm -rf "${LOG_DIR}"
142
+ git commit -m "done." || true
143
+ git push origin
144
+ set -e
145
+
146
+ # Create PR with log permalink (auto-merge handled by GitHub Actions workflow)
147
+ REPO_SLUG=$(gh repo view --json nameWithOwner -q .nameWithOwner)
148
+ LOG_URL="https://github.com/${REPO_SLUG}/tree/${LOG_SHA}/logs/${JOB_ID}"
149
+ gh pr create --title "🤖 Agent Job: ${TITLE}" \
150
+ --body "📋 [View Job Logs](${LOG_URL})"$'\n\n---\n\n'"${JOB_DESCRIPTION}" \
151
+ --base main || true
152
+
153
+ # Cleanup
154
+ if [ -n "$CHROME_PID" ]; then
155
+ kill $CHROME_PID 2>/dev/null || true
156
+ fi
157
+
158
+ # Re-raise Pi's failure so the workflow reports it
159
+ if [ $PI_EXIT -ne 0 ]; then
160
+ echo "Pi exited with code ${PI_EXIT}"
161
+ exit $PI_EXIT
162
+ fi
163
+
164
+ echo "Done. Job ID: ${JOB_ID}"
@@ -0,0 +1,78 @@
1
+ # GigaClaw — Local Mode (docker-compose.local.yml)
2
+ # Runs GigaClaw 100% offline using Ollama for LLM inference.
3
+ # No GitHub, no ngrok, no Telegram required.
4
+ #
5
+ # Prerequisites:
6
+ # 1. Install Ollama: https://ollama.com/download
7
+ # 2. Pull a model: ollama pull llama3.1:8b
8
+ # 3. Start Ollama: ollama serve
9
+ #
10
+ # Start GigaClaw:
11
+ # docker compose -f docker-compose.local.yml up -d
12
+ #
13
+ # Access the web interface at: http://localhost:3000
14
+ #
15
+ # ─── NOTE ────────────────────────────────────────────────────────────────────
16
+ # This compose file builds GigaClaw from your local source code using the
17
+ # official Node.js image. No private Docker registry or login is required.
18
+ # First build: ~2-3 minutes. Subsequent starts: instant.
19
+ # ─────────────────────────────────────────────────────────────────────────────
20
+
21
+ services:
22
+ gigaclaw:
23
+ container_name: gigaclaw-local
24
+ # Build from local source — no private registry or docker login required
25
+ build:
26
+ context: .
27
+ dockerfile_inline: |
28
+ FROM node:20-alpine
29
+ WORKDIR /app
30
+ COPY package*.json ./
31
+ RUN npm ci
32
+ COPY . .
33
+ EXPOSE 3000
34
+ ENV NODE_ENV=development
35
+ CMD ["npm", "run", "dev"]
36
+ ports:
37
+ - "3000:3000"
38
+ volumes:
39
+ # Mount source for hot-reload in dev mode
40
+ - .:/app
41
+ - /app/node_modules
42
+ # Persist the SQLite database across container restarts
43
+ - gigaclaw-data:/app/data
44
+ environment:
45
+ # Ollama runs on the host machine — Docker reaches it via host.docker.internal
46
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
47
+ - NODE_ENV=development
48
+ env_file:
49
+ - .env
50
+ extra_hosts:
51
+ # Ensures host.docker.internal resolves on Linux (already works on macOS/Windows)
52
+ - "host.docker.internal:host-gateway"
53
+ stop_grace_period: 30s
54
+ healthcheck:
55
+ test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/ping"]
56
+ interval: 15s
57
+ timeout: 5s
58
+ retries: 3
59
+ start_period: 60s
60
+ restart: unless-stopped
61
+
62
+ # Optional: self-hosted push notifications over LAN (no internet required)
63
+ # Enable with: docker compose -f docker-compose.local.yml --profile notifications up -d
64
+ ntfy:
65
+ image: binwiederhier/ntfy
66
+ container_name: gigaclaw-ntfy
67
+ ports:
68
+ - "8080:80"
69
+ command: serve --base-url http://localhost:8080
70
+ volumes:
71
+ - ntfy-data:/var/cache/ntfy
72
+ profiles:
73
+ - notifications
74
+ restart: unless-stopped
75
+
76
+ volumes:
77
+ gigaclaw-data:
78
+ ntfy-data:
@@ -0,0 +1,64 @@
1
+ services:
2
+ traefik:
3
+ image: traefik:v3
4
+ command:
5
+ - --providers.docker=true
6
+ - --providers.docker.exposedByDefault=false
7
+ - --entrypoints.web.address=:80
8
+ - --entrypoints.websecure.address=:443
9
+ ## Uncomment the following lines to enable TLS via Let's Encrypt
10
+ ## (requires LETSENCRYPT_EMAIL in .env):
11
+ # - --entrypoints.web.http.redirections.entrypoint.to=websecure
12
+ # - --entrypoints.web.http.redirections.entrypoint.scheme=https
13
+ # - --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}
14
+ # - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
15
+ # - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
16
+ ports:
17
+ - "80:80"
18
+ - "443:443"
19
+ volumes:
20
+ - /var/run/docker.sock:/var/run/docker.sock:ro
21
+ - traefik_certs:/letsencrypt
22
+ restart: unless-stopped
23
+
24
+ event-handler:
25
+ container_name: gigaclaw-event-handler
26
+ image: ${EVENT_HANDLER_IMAGE_URL:-gignaati/gigaclaw:event-handler-${GIGACLAW_VERSION:-latest}}
27
+ volumes:
28
+ - .:/app
29
+ - /app/node_modules
30
+ - /var/run/docker.sock:/var/run/docker.sock
31
+ labels:
32
+ - traefik.enable=true
33
+ # Set APP_HOSTNAME in .env to the domain from APP_URL (e.g., mybot.example.com)
34
+ - traefik.http.routers.event-handler.rule=Host(`${APP_HOSTNAME}`)
35
+ - traefik.http.routers.event-handler.entrypoints=web
36
+ - traefik.http.services.event-handler.loadbalancer.server.port=80
37
+ ## Uncomment the following lines to enable TLS via Let's Encrypt:
38
+ # - traefik.http.routers.event-handler.entrypoints=websecure
39
+ # - traefik.http.routers.event-handler.tls.certresolver=letsencrypt
40
+ stop_grace_period: 120s
41
+ healthcheck:
42
+ test: ["CMD", "curl", "-f", "http://localhost:80/api/ping"]
43
+ interval: 10s
44
+ timeout: 3s
45
+ retries: 3
46
+ start_period: 30s
47
+ restart: unless-stopped
48
+
49
+ runner:
50
+ image: myoung34/github-runner:latest
51
+ deploy:
52
+ replicas: ${RUNNER_REPLICAS:-2}
53
+ environment:
54
+ REPO_URL: https://github.com/${GH_OWNER}/${GH_REPO}
55
+ ACCESS_TOKEN: ${GH_TOKEN}
56
+ RUNNER_SCOPE: repo
57
+ LABELS: self-hosted
58
+ volumes:
59
+ - /var/run/docker.sock:/var/run/docker.sock
60
+ - .:/project:ro
61
+ restart: unless-stopped
62
+
63
+ volumes:
64
+ traefik_certs:
@@ -0,0 +1,6 @@
1
+ export async function register() {
2
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
3
+ const { register } = await import('gigaclaw/instrumentation');
4
+ await register();
5
+ }
6
+ }