mercury-agent 0.4.5

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 (218) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +438 -0
  3. package/container/Dockerfile +127 -0
  4. package/container/Dockerfile.base +109 -0
  5. package/container/Dockerfile.power +17 -0
  6. package/container/agent-package.json +8 -0
  7. package/container/build.sh +54 -0
  8. package/docs/TODOS.md +147 -0
  9. package/docs/auth/dashboard.md +28 -0
  10. package/docs/auth/overview.md +109 -0
  11. package/docs/auth/whatsapp.md +173 -0
  12. package/docs/configuration.md +54 -0
  13. package/docs/container-lifecycle.md +349 -0
  14. package/docs/context-architecture.md +87 -0
  15. package/docs/deployment.md +199 -0
  16. package/docs/extensions.md +375 -0
  17. package/docs/graceful-shutdown.md +62 -0
  18. package/docs/kb-distillation.md +77 -0
  19. package/docs/media/overview.md +140 -0
  20. package/docs/media/whatsapp.md +171 -0
  21. package/docs/memory.md +137 -0
  22. package/docs/permissions.md +217 -0
  23. package/docs/pipeline.md +228 -0
  24. package/docs/prd-chat-memory.md +76 -0
  25. package/docs/prd-config-load.md +82 -0
  26. package/docs/rate-limiting.md +166 -0
  27. package/docs/scheduler.md +288 -0
  28. package/docs/setup-discord.md +100 -0
  29. package/docs/setup-slack.md +119 -0
  30. package/docs/setup-whatsapp.md +94 -0
  31. package/docs/subagents.md +166 -0
  32. package/docs/web-search.md +62 -0
  33. package/examples/extensions/README.md +12 -0
  34. package/examples/extensions/charts/index.ts +13 -0
  35. package/examples/extensions/charts/skill/SKILL.md +98 -0
  36. package/examples/extensions/gws/README.md +52 -0
  37. package/examples/extensions/gws/index.ts +106 -0
  38. package/examples/extensions/gws/skill/SKILL.md +57 -0
  39. package/examples/extensions/gws/skill/references/calendar.md +101 -0
  40. package/examples/extensions/gws/skill/references/docs.md +65 -0
  41. package/examples/extensions/gws/skill/references/drive.md +79 -0
  42. package/examples/extensions/gws/skill/references/gmail.md +85 -0
  43. package/examples/extensions/gws/skill/references/sheets.md +60 -0
  44. package/examples/extensions/napkin/index.ts +821 -0
  45. package/examples/extensions/napkin/prompts/consolidation-monthly.md +73 -0
  46. package/examples/extensions/napkin/prompts/consolidation-weekly.md +67 -0
  47. package/examples/extensions/napkin/prompts/kb-distillation.md +176 -0
  48. package/examples/extensions/napkin/skill/SKILL.md +728 -0
  49. package/examples/extensions/pdf/index.ts +23 -0
  50. package/examples/extensions/pdf/skill/LICENSE.txt +30 -0
  51. package/examples/extensions/pdf/skill/SKILL.md +314 -0
  52. package/examples/extensions/pdf/skill/forms.md +294 -0
  53. package/examples/extensions/pdf/skill/reference.md +612 -0
  54. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -0
  55. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -0
  56. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -0
  57. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -0
  58. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -0
  59. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -0
  60. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -0
  61. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -0
  62. package/examples/extensions/permission-guard/index.ts +65 -0
  63. package/examples/extensions/pinchtab/index.ts +199 -0
  64. package/examples/extensions/pinchtab/lib/session-injector.ts +144 -0
  65. package/examples/extensions/pinchtab/skill/SKILL.md +224 -0
  66. package/examples/extensions/pinchtab/skill/TRUST.md +69 -0
  67. package/examples/extensions/pinchtab/skill/references/api.md +297 -0
  68. package/examples/extensions/pinchtab/skill/references/env.md +45 -0
  69. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -0
  70. package/examples/extensions/tradestation/host/refresh.ts +102 -0
  71. package/examples/extensions/tradestation/index.ts +153 -0
  72. package/examples/extensions/tradestation/skill/SKILL.md +67 -0
  73. package/examples/extensions/tradestation/skill/scripts/ts-cli.ts +111 -0
  74. package/examples/extensions/voice-synth/index.ts +94 -0
  75. package/examples/extensions/voice-synth/skill/SKILL.md +38 -0
  76. package/examples/extensions/voice-transcribe/index.ts +381 -0
  77. package/examples/extensions/voice-transcribe/requirements.txt +8 -0
  78. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -0
  79. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -0
  80. package/examples/extensions/web-search/index.ts +22 -0
  81. package/examples/extensions/web-search/skill/SKILL.md +114 -0
  82. package/examples/extensions/web-search/skill/references/apartments.md +178 -0
  83. package/examples/extensions/web-search/skill/references/car-purchase.md +132 -0
  84. package/examples/extensions/web-search/skill/references/car-rental.md +113 -0
  85. package/examples/extensions/web-search/skill/references/flights.md +133 -0
  86. package/examples/extensions/web-search/skill/references/hotels.md +148 -0
  87. package/examples/extensions/yahoo-mail/cli/bun.lock +66 -0
  88. package/examples/extensions/yahoo-mail/cli/package.json +13 -0
  89. package/examples/extensions/yahoo-mail/cli/ymail.mjs +353 -0
  90. package/examples/extensions/yahoo-mail/index.ts +57 -0
  91. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -0
  92. package/package.json +106 -0
  93. package/resources/agents/explore.md +50 -0
  94. package/resources/agents/worker.md +24 -0
  95. package/resources/builtin-extensions.txt +3 -0
  96. package/resources/connection-env-vars.json +25 -0
  97. package/resources/extensions/.gitkeep +0 -0
  98. package/resources/pi-extensions/subagent/agents.ts +126 -0
  99. package/resources/pi-extensions/subagent/index.ts +964 -0
  100. package/resources/profiles/coding/AGENTS.md +43 -0
  101. package/resources/profiles/coding/mercury-profile.yaml +15 -0
  102. package/resources/profiles/general/AGENTS.md +31 -0
  103. package/resources/profiles/general/mercury-profile.yaml +15 -0
  104. package/resources/profiles/research/AGENTS.md +40 -0
  105. package/resources/profiles/research/mercury-profile.yaml +15 -0
  106. package/resources/skills/config/SKILL.md +25 -0
  107. package/resources/skills/context/SKILL.md +33 -0
  108. package/resources/skills/conversation-recap/SKILL.md +19 -0
  109. package/resources/skills/media/SKILL.md +27 -0
  110. package/resources/skills/mutes/SKILL.md +31 -0
  111. package/resources/skills/permissions/SKILL.md +19 -0
  112. package/resources/skills/preferences/SKILL.md +31 -0
  113. package/resources/skills/recall/SKILL.md +24 -0
  114. package/resources/skills/roles/SKILL.md +18 -0
  115. package/resources/skills/spaces/SKILL.md +18 -0
  116. package/resources/skills/tasks/SKILL.md +45 -0
  117. package/resources/templates/AGENTS.md +157 -0
  118. package/resources/templates/env.template +34 -0
  119. package/resources/templates/mercury.example.yaml +75 -0
  120. package/src/adapters/discord-native.ts +534 -0
  121. package/src/adapters/discord.ts +38 -0
  122. package/src/adapters/setup.ts +89 -0
  123. package/src/adapters/slack.ts +9 -0
  124. package/src/adapters/whatsapp-media.ts +337 -0
  125. package/src/adapters/whatsapp.ts +629 -0
  126. package/src/agent/api-socket.ts +127 -0
  127. package/src/agent/container-entry.ts +967 -0
  128. package/src/agent/container-error.ts +49 -0
  129. package/src/agent/container-runner.ts +1272 -0
  130. package/src/agent/model-capabilities-core.ts +23 -0
  131. package/src/agent/model-capabilities.ts +231 -0
  132. package/src/agent/pi-failure-class.ts +83 -0
  133. package/src/agent/pi-jsonl-parser.ts +306 -0
  134. package/src/agent/preferences-prompt.ts +20 -0
  135. package/src/agent/user-error-messages.ts +78 -0
  136. package/src/bridges/discord.ts +171 -0
  137. package/src/bridges/slack.ts +177 -0
  138. package/src/bridges/teams.ts +160 -0
  139. package/src/bridges/telegram.ts +571 -0
  140. package/src/bridges/whatsapp.ts +290 -0
  141. package/src/chat-shim.ts +259 -0
  142. package/src/cli/mercury.ts +2508 -0
  143. package/src/cli/mrctl-http.ts +27 -0
  144. package/src/cli/mrctl.ts +611 -0
  145. package/src/cli/whatsapp-auth.ts +260 -0
  146. package/src/config-file.ts +397 -0
  147. package/src/config-model-chain.ts +30 -0
  148. package/src/config.ts +316 -0
  149. package/src/core/api-types.ts +58 -0
  150. package/src/core/api.ts +105 -0
  151. package/src/core/commands.ts +76 -0
  152. package/src/core/conversation.ts +47 -0
  153. package/src/core/handler.ts +206 -0
  154. package/src/core/media.ts +200 -0
  155. package/src/core/mute-duration.ts +22 -0
  156. package/src/core/outbox.ts +76 -0
  157. package/src/core/permissions.ts +192 -0
  158. package/src/core/profiles.ts +245 -0
  159. package/src/core/rate-limiter.ts +127 -0
  160. package/src/core/router.ts +191 -0
  161. package/src/core/routes/chat.ts +172 -0
  162. package/src/core/routes/config-builtin.ts +107 -0
  163. package/src/core/routes/config.ts +81 -0
  164. package/src/core/routes/connections.ts +190 -0
  165. package/src/core/routes/console.ts +668 -0
  166. package/src/core/routes/control.ts +46 -0
  167. package/src/core/routes/conversations.ts +66 -0
  168. package/src/core/routes/dashboard.ts +2491 -0
  169. package/src/core/routes/extensions.ts +37 -0
  170. package/src/core/routes/index.ts +14 -0
  171. package/src/core/routes/media.ts +72 -0
  172. package/src/core/routes/messages.ts +37 -0
  173. package/src/core/routes/mutes.ts +89 -0
  174. package/src/core/routes/prefs.ts +95 -0
  175. package/src/core/routes/roles.ts +125 -0
  176. package/src/core/routes/spaces.ts +60 -0
  177. package/src/core/routes/storage.ts +126 -0
  178. package/src/core/routes/tasks.ts +189 -0
  179. package/src/core/routes/tradestation.ts +268 -0
  180. package/src/core/routes/tts.ts +51 -0
  181. package/src/core/runtime.ts +1140 -0
  182. package/src/core/space-queue.ts +103 -0
  183. package/src/core/storage-cleanup.ts +140 -0
  184. package/src/core/storage-guard.ts +24 -0
  185. package/src/core/task-scheduler.ts +132 -0
  186. package/src/core/telegram-format.ts +178 -0
  187. package/src/core/trigger.ts +142 -0
  188. package/src/dashboard/index.html +729 -0
  189. package/src/dashboard/tokens.css +53 -0
  190. package/src/extensions/api.ts +252 -0
  191. package/src/extensions/catalog.ts +117 -0
  192. package/src/extensions/config-registry.ts +83 -0
  193. package/src/extensions/context.ts +36 -0
  194. package/src/extensions/hooks.ts +156 -0
  195. package/src/extensions/image-builder.ts +617 -0
  196. package/src/extensions/installer.ts +306 -0
  197. package/src/extensions/jobs.ts +122 -0
  198. package/src/extensions/loader.ts +271 -0
  199. package/src/extensions/permission-guard.ts +52 -0
  200. package/src/extensions/reserved.ts +28 -0
  201. package/src/extensions/skills.ts +123 -0
  202. package/src/extensions/types.ts +462 -0
  203. package/src/logger.ts +174 -0
  204. package/src/main.ts +586 -0
  205. package/src/server.ts +391 -0
  206. package/src/storage/db.ts +1624 -0
  207. package/src/storage/memory.ts +45 -0
  208. package/src/storage/pi-auth.ts +95 -0
  209. package/src/text/markdown.ts +117 -0
  210. package/src/text/rtl.ts +38 -0
  211. package/src/tradestation/host-api.ts +77 -0
  212. package/src/tradestation/pending-orders.ts +69 -0
  213. package/src/tts/azure.ts +52 -0
  214. package/src/tts/google.ts +128 -0
  215. package/src/tts/index.ts +8 -0
  216. package/src/tts/language.ts +20 -0
  217. package/src/tts/synthesize.ts +133 -0
  218. package/src/types.ts +295 -0
@@ -0,0 +1,109 @@
1
+ # syntax=docker/dockerfile:1
2
+ # Mercury Agent Container — Base tier
3
+ # Base: Microsoft Playwright image (Ubuntu 24.04 noble + Node 22 + Chromium + X11/GL deps preinstalled).
4
+ # Adds: Bun, poppler-utils, pandoc, imagemagick, pi-coding-agent, mercury (UID 1000) user.
5
+ FROM oven/bun:1 AS bun-source
6
+ FROM mcr.microsoft.com/playwright:v1.55.0-noble
7
+
8
+ ENV DEBIAN_FRONTEND=noninteractive
9
+
10
+ # Document-processing tools. Wrapped in a retry loop — archive.ubuntu.com is periodically flaky
11
+ # and a single apt miss shouldn't fail the whole image build.
12
+ RUN set -eux; \
13
+ for i in 1 2 3 4 5; do \
14
+ apt-get update && apt-get install -y --no-install-recommends \
15
+ poppler-utils pandoc imagemagick wget \
16
+ && break \
17
+ || { echo "apt attempt $i/5 failed, sleeping 15s" >&2; apt-get clean; sleep 15; }; \
18
+ done; \
19
+ rm -rf /var/lib/apt/lists/*
20
+
21
+ # Rename the built-in ubuntu user (UID 1000) to mercury.
22
+ # Playwright's noble base inherits the ubuntu user at UID/GID 1000; useradd -u 1000
23
+ # would fail. (pwuser is at UID 1001 — unused by us.)
24
+ RUN usermod -l mercury -d /home/mercury -m ubuntu && groupmod -n mercury ubuntu
25
+
26
+ # Copy Bun from official image
27
+ COPY --from=bun-source /usr/local/bin/bun /usr/local/bin/bun
28
+ COPY --from=bun-source /usr/local/bin/bunx /usr/local/bin/bunx
29
+
30
+ ENV HOME="/home/mercury"
31
+ ENV BUN_INSTALL="/home/mercury/.bun"
32
+ ENV PATH="$BUN_INSTALL/bin:/usr/local/bin:$PATH"
33
+
34
+ # Chromium lives in /ms-playwright/chromium-*/chrome-linux/chrome in the Playwright image.
35
+ # Symlink to /usr/local/bin/chromium so puppeteer-using npm packages find it.
36
+ ENV PUPPETEER_SKIP_DOWNLOAD=true
37
+ RUN ln -sf "$(find /ms-playwright -name chrome -type f -executable | head -1)" /usr/local/bin/chromium
38
+ ENV PUPPETEER_EXECUTABLE_PATH=/usr/local/bin/chromium
39
+
40
+ # --no-sandbox: Chromium can't use its user namespace sandbox inside a container
41
+ RUN echo '{"args":["--no-sandbox"]}' > /home/mercury/.puppeteerrc.json
42
+ ENV CHROMIUM_FLAGS="--no-sandbox"
43
+
44
+ # Install CLIs
45
+ RUN bun add -g @mariozechner/pi-coding-agent@^0.67.2
46
+
47
+ WORKDIR /app
48
+
49
+ COPY container/agent-package.json /app/package.json
50
+ RUN bun install --production
51
+
52
+ # Patch pi: skip thinkingConfig for Gemma models (Google API rejects thinkingBudget for them,
53
+ # but pi marks gemma-4 as reasoning:true and sends thinkingBudget:0 to disable it)
54
+ RUN node <<'EOF'
55
+ const fs = require('fs'), path = require('path');
56
+ function patch(dir) {
57
+ try {
58
+ for (const f of fs.readdirSync(dir)) {
59
+ const p = path.join(dir, f);
60
+ try {
61
+ if (fs.statSync(p).isDirectory()) patch(p);
62
+ else if (f === 'google.js' && p.includes('@mariozechner/pi-ai')) {
63
+ let c = fs.readFileSync(p, 'utf8');
64
+ const noThinking = '&& !model.id.startsWith("gemma")';
65
+ const p1 = 'if (options.thinking?.enabled && model.reasoning) {';
66
+ const p2 = 'else if (model.reasoning && options.thinking && !options.thinking.enabled) {';
67
+ if (!c.includes(noThinking)) {
68
+ c = c.replace(p1, 'if (options.thinking?.enabled && model.reasoning ' + noThinking + ') {');
69
+ c = c.replace(p2, 'else if (model.reasoning && options.thinking && !options.thinking.enabled ' + noThinking + ') {');
70
+ fs.writeFileSync(p, c);
71
+ console.log('Patched:', p);
72
+ }
73
+ }
74
+ } catch(e) {}
75
+ }
76
+ } catch(e) {}
77
+ }
78
+ patch('/home/mercury/.bun');
79
+ patch('/app/node_modules');
80
+ EOF
81
+
82
+ COPY src/agent/container-entry.ts /app/src/agent/container-entry.ts
83
+ COPY src/agent/model-capabilities-core.ts /app/src/agent/model-capabilities-core.ts
84
+ COPY src/agent/pi-failure-class.ts /app/src/agent/pi-failure-class.ts
85
+ COPY src/agent/pi-jsonl-parser.ts /app/src/agent/pi-jsonl-parser.ts
86
+ COPY src/agent/preferences-prompt.ts /app/src/agent/preferences-prompt.ts
87
+ COPY src/cli/mrctl.ts /app/src/cli/mrctl.ts
88
+ COPY src/cli/mrctl-http.ts /app/src/cli/mrctl-http.ts
89
+ COPY src/extensions/reserved.ts /app/src/extensions/reserved.ts
90
+ COPY src/extensions/permission-guard.ts /app/src/extensions/permission-guard.ts
91
+ COPY src/types.ts /app/src/types.ts
92
+ COPY resources/ /app/resources/
93
+ COPY examples/extensions/ /tmp/examples-extensions/
94
+ RUN while IFS= read -r ext || [ -n "$ext" ]; do \
95
+ ext=$(echo "$ext" | xargs); \
96
+ [ -z "$ext" ] && continue; \
97
+ cp -r "/tmp/examples-extensions/$ext" "/app/resources/extensions/$ext"; \
98
+ done < /app/resources/builtin-extensions.txt && \
99
+ rm -rf /tmp/examples-extensions/
100
+
101
+ RUN echo '#!/bin/sh\nbun run /app/src/cli/mrctl.ts "$@"' > /usr/local/bin/mrctl && \
102
+ chmod +x /usr/local/bin/mrctl
103
+
104
+ # Fix ownership of all mercury home dir artifacts before switching user
105
+ RUN chown -R mercury:mercury /home/mercury
106
+
107
+ USER mercury
108
+
109
+ ENTRYPOINT ["bun", "run", "/app/src/agent/container-entry.ts"]
@@ -0,0 +1,17 @@
1
+ # syntax=docker/dockerfile:1
2
+ # Mercury Agent Container — Power tier
3
+ # Extends Base with: python3 + pip + venv, build-essential, jq, ffmpeg, bubblewrap
4
+ # The patch script, mrctl, and entrypoint are all inherited from Base — not duplicated here.
5
+ ARG BASE_TAG
6
+ FROM ghcr.io/michaelliv/mercury-agent:${BASE_TAG}-base
7
+
8
+ USER root
9
+ RUN apt-get update && apt-get install -y --no-install-recommends \
10
+ python3 python3-pip python3-venv build-essential \
11
+ jq \
12
+ ffmpeg \
13
+ # Sandboxing: bubblewrap for defense-in-depth in runc mode.
14
+ # Not used when CONTAINER_RUNTIME=runsc (gVisor handles isolation at the syscall boundary).
15
+ bubblewrap \
16
+ && rm -rf /var/lib/apt/lists/*
17
+ USER mercury
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "mercury-agent-app",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "@mariozechner/pi-coding-agent": "^0.67.2"
7
+ }
8
+ }
@@ -0,0 +1,54 @@
1
+ #!/bin/bash
2
+ # Build the Mercury agent container images
3
+ set -e
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
7
+ cd "$PROJECT_ROOT"
8
+
9
+ IMAGE_NAME="mercury-agent"
10
+
11
+ # Parse arguments
12
+ BUILD_ALL=false
13
+ BUILD_LATEST=false
14
+ BUILD_MINIMAL=false
15
+
16
+ if [ $# -eq 0 ]; then
17
+ BUILD_LATEST=true
18
+ elif [ "$1" = "all" ]; then
19
+ BUILD_ALL=true
20
+ elif [ "$1" = "latest" ]; then
21
+ BUILD_LATEST=true
22
+ elif [ "$1" = "minimal" ]; then
23
+ BUILD_MINIMAL=true
24
+ else
25
+ echo "Usage: $0 [all|latest|minimal]"
26
+ echo ""
27
+ echo "Presets:"
28
+ echo " latest Full devcontainer with Node, Python, Go, git (~2.8GB)"
29
+ echo " minimal Bun + pi + browser only (~1.9GB)"
30
+ echo " all Build both presets"
31
+ echo ""
32
+ echo "Default: latest"
33
+ exit 1
34
+ fi
35
+
36
+ if [ "$BUILD_ALL" = true ] || [ "$BUILD_LATEST" = true ]; then
37
+ echo "Building ${IMAGE_NAME}:latest (full devcontainer)..."
38
+ docker build -f container/Dockerfile -t "${IMAGE_NAME}:latest" .
39
+ echo "✓ Built ${IMAGE_NAME}:latest"
40
+ echo ""
41
+ fi
42
+
43
+ if [ "$BUILD_ALL" = true ] || [ "$BUILD_MINIMAL" = true ]; then
44
+ echo "Building ${IMAGE_NAME}:minimal (bun-only)..."
45
+ docker build -f container/Dockerfile.minimal -t "${IMAGE_NAME}:minimal" .
46
+ echo "✓ Built ${IMAGE_NAME}:minimal"
47
+ echo ""
48
+ fi
49
+
50
+ echo "Build complete!"
51
+ if [ "$BUILD_ALL" = true ]; then
52
+ echo " ${IMAGE_NAME}:latest - Full devcontainer (~2.8GB)"
53
+ echo " ${IMAGE_NAME}:minimal - Bun + pi + browser (~1.9GB)"
54
+ fi
package/docs/TODOS.md ADDED
@@ -0,0 +1,147 @@
1
+ # Mercury — Remaining TODOs
2
+
3
+ This document tracks gaps identified in the comprehensive code review that are not yet addressed. Items are ordered by priority (security first, then reliability, then polish).
4
+
5
+ ---
6
+
7
+ ## Security
8
+
9
+ ### TODO-1: Protect dashboard root routes
10
+
11
+ **Status:** Open
12
+ **Priority:** Medium
13
+ **Files:** `src/server.ts`
14
+
15
+ **Issue:** `GET /` and `GET /dashboard` serve the dashboard HTML without authentication. Only `/dashboard/*` (htmx partials, API, SSE) is protected. Users can load the HTML shell without a token.
16
+
17
+ **Fix:** Apply the same auth middleware to `/` and `/dashboard`, or redirect unauthenticated requests to a login page. When `MERCURY_API_SECRET` is set, require Bearer token or `mercury_token` cookie for these routes.
18
+
19
+ ---
20
+
21
+ ### TODO-2: Harden permission guard
22
+
23
+ **Status:** Open
24
+ **Priority:** Medium
25
+ **Files:** `src/extensions/permission-guard.ts`
26
+
27
+ **Issue:** The permission guard blocks CLI names via regex on bash commands. It can be bypassed by:
28
+ - `env napkin search "query"`
29
+ - `` `which napkin` search "query" ``
30
+ - `python3 -c "import subprocess; subprocess.run(['napkin', ...])"`
31
+ - Path-based execution: `/root/.bun/bin/napkin`
32
+
33
+ **Fix options:**
34
+ 1. Run denied CLIs through an allowlist/blocklist at the pi tool-call layer (if the pi API supports it)
35
+ 2. Use a wrapper script that validates the command before execution
36
+ 3. Document that this is defense-in-depth only; rely on extension trust model
37
+ 4. Consider seccomp or additional bwrap restrictions to limit subprocess execution
38
+
39
+ **Note:** Bubblewrap limits filesystem access but does not prevent the agent from invoking allowed binaries. The permission guard remains the primary control for which CLIs run.
40
+
41
+ ---
42
+
43
+ ### TODO-3: Sanitize extension install commands
44
+
45
+ **Status:** Open
46
+ **Priority:** Low
47
+ **Files:** `src/extensions/image-builder.ts`
48
+
49
+ **Issue:** Extension `install` commands are interpolated directly into Dockerfile `RUN` statements. A malicious extension could inject shell commands that exfiltrate secrets or modify the image.
50
+
51
+ **Fix options:**
52
+ 1. Validate install commands against an allowlist (e.g. `bun add -g X`, `npm install -g X`)
53
+ 2. Run install commands in a sandboxed build step
54
+ 3. Document that extensions are trusted by design (operator installs them); recommend auditing before `mercury add`
55
+
56
+ ---
57
+
58
+ ## Reliability
59
+
60
+ ### TODO-4: Database migration system
61
+
62
+ **Status:** Open
63
+ **Priority:** Medium
64
+ **Files:** `src/storage/db.ts`, new `src/storage/migrations/`
65
+
66
+ **Issue:** Schema evolution uses `CREATE TABLE IF NOT EXISTS` only. Adding columns, changing types, or renaming tables has no migration path.
67
+
68
+ **Fix:** Implement a simple migration system:
69
+ 1. Create `migrations/` directory with sequential SQL files (`001_initial.sql`, `002_add_mutes.sql`, …)
70
+ 2. Add `schema_migrations` table to track applied migrations
71
+ 3. On startup, run any migrations not yet applied
72
+ 4. Document migration authoring in `docs/`
73
+
74
+ ---
75
+
76
+ ### TODO-5: Outbox scan race condition
77
+
78
+ **Status:** Open
79
+ **Priority:** Low
80
+ **Files:** `src/core/outbox.ts`, `src/agent/container-runner.ts`
81
+
82
+ **Issue:** Outbox scanning uses mtime comparison against `startTime`. Files written at exactly `startTime` or during clock skew could be missed or incorrectly included.
83
+
84
+ **Fix:** Use a more robust approach (e.g. track written files explicitly, or use a small time buffer). Document the current behavior and edge cases.
85
+
86
+ ---
87
+
88
+ ## Operations
89
+
90
+ ### TODO-6: Run containers as non-root
91
+
92
+ **Status:** Open
93
+ **Priority:** Low
94
+ **Files:** `container/Dockerfile`, `container/Dockerfile.minimal`, `src/agent/container-runner.ts`
95
+
96
+ **Issue:** Containers run as root. Chromium uses `--no-sandbox` because it runs as root. This increases blast radius if the container is compromised.
97
+
98
+ **Fix:**
99
+ 1. Create a non-root user in the Dockerfile
100
+ 2. Run the entrypoint as that user (`USER mercury` or similar)
101
+ 3. Ensure Chromium can run without `--no-sandbox` (may require `--user-data-dir` and other flags, or use a different approach)
102
+ 4. Update `container-runner.ts` if `docker run` needs `--user` override
103
+
104
+ **Note:** Bubblewrap runs as the same user as the parent process. Moving the container to non-root would also make bwrap run as non-root, reducing privilege further.
105
+
106
+ ---
107
+
108
+ ### TODO-7: CORS configuration
109
+
110
+ **Status:** Open
111
+ **Priority:** Low
112
+ **Files:** `src/server.ts`
113
+
114
+ **Issue:** No CORS headers are set. When Mercury is behind a reverse proxy or accessed from a browser on another origin, cross-origin requests may fail or behave unexpectedly.
115
+
116
+ **Fix:** Add configurable CORS middleware (e.g. `MERCURY_CORS_ORIGINS`). Default to restrictive (same-origin or empty) for security.
117
+
118
+ ---
119
+
120
+ ## In-Memory State (Deferred)
121
+
122
+ ### TODO-8: Crash-safe rate limiter and queue
123
+
124
+ **Status:** Deferred
125
+ **Priority:** Low
126
+ **Files:** `src/core/rate-limiter.ts`, `src/core/space-queue.ts`
127
+
128
+ **Issue:** Rate limiter and queue are in-memory. On crash, rate limit windows reset (allowing burst abuse) and queued work is lost.
129
+
130
+ **Fix:** Persist rate limit state and queue to SQLite or Redis. Adds complexity; may be acceptable for single-node deployments. Revisit if multi-instance or high-availability is required.
131
+
132
+ ---
133
+
134
+ ## Bubblewrap — What It Mitigates
135
+
136
+ Bubblewrap adds **defense-in-depth** inside the container:
137
+
138
+ | Risk | Mitigated by bubblewrap? |
139
+ |------|--------------------------|
140
+ | **Cross-space data access** | Partially — with per-space mount, `/spaces` only has one space. Bwrap further restricts the pi process to a minimal mount set. |
141
+ | **Arbitrary filesystem access** | Yes — pi only sees `/usr`, `/app`, `/etc`, `/docs`, `/spaces`, `/root`, `/proc`, `/dev`, `/tmp`. Cannot access other paths. |
142
+ | **Process isolation** | Yes — `--unshare-pid`, `--new-session`, `--die-with-parent` limit process visibility and lifecycle. |
143
+ | **Permission guard bypass** | No — bwrap limits *what* the agent can access, not *which commands* it runs. A bypassed guard could still invoke allowed binaries. |
144
+ | **Extension install injection** | No — that happens at image build time, before bwrap runs. |
145
+ | **Container non-root** | No — bwrap runs as the same user (root) as the container. Moving to non-root would improve both. |
146
+
147
+ **Summary:** Bubblewrap reduces blast radius if the agent is compromised. It does not replace the permission guard, API auth, or other controls. It is a valuable additional layer.
@@ -0,0 +1,28 @@
1
+ # Dashboard Authentication
2
+
3
+ The Mercury dashboard (`/dashboard/*`) is protected by `MERCURY_API_SECRET`. All htmx partial requests and the SSE events stream require a valid `mercury_token` session cookie.
4
+
5
+ ## How it works
6
+
7
+ ### Login route
8
+
9
+ `GET /dashboard/login?token=<secret>` — the entry point for browser sessions.
10
+
11
+ - Validates `token` against `MERCURY_API_SECRET` using `timingSafeEqual`
12
+ - On success: sets `mercury_token` HttpOnly cookie and redirects to `/dashboard`
13
+ - On failure: returns `401 Invalid or missing token`
14
+ - Not protected by the dashboard auth middleware (it's the login endpoint itself)
15
+
16
+ ## Auth flow
17
+
18
+ ```
19
+ User navigates to https://<host>:<port>/dashboard/login?token=<MERCURY_API_SECRET>
20
+ → agent sets mercury_token HttpOnly cookie
21
+ → 302 /dashboard
22
+ → dashboard loads, htmx requests include cookie ✅
23
+ ```
24
+
25
+ ## Key constraints
26
+
27
+ - `MERCURY_API_SECRET` should be set to a long random string. `mercury setup` auto-generates one.
28
+ - The cookie is HttpOnly — not accessible from JavaScript.
@@ -0,0 +1,109 @@
1
+ # Authentication
2
+
3
+ Mercury needs credentials for two things:
4
+
5
+ 1. **AI model provider** — to call the LLM (Anthropic, OpenAI, etc.)
6
+ 2. **Chat platforms** — to connect to WhatsApp, Discord, Slack, Teams
7
+
8
+ ## Model Provider Auth
9
+
10
+ ### OAuth Login (Recommended)
11
+
12
+ Mercury reuses [pi](https://github.com/badlogic/pi)'s OAuth providers. No API key needed — just login:
13
+
14
+ ```bash
15
+ mercury auth login # Interactive provider picker
16
+ mercury auth login anthropic # Or specify directly
17
+ ```
18
+
19
+ Supported providers:
20
+
21
+ | Provider | ID | What it gives you |
22
+ |----------|----|-------------------|
23
+ | Anthropic (Claude Pro/Max) | `anthropic` | Claude access via your subscription |
24
+ | GitHub Copilot | `github-copilot` | Models via Copilot subscription |
25
+ | Google Gemini CLI | `google-gemini-cli` | Gemini via Google Cloud |
26
+ | Antigravity | `antigravity` | Gemini 3, Claude, GPT-OSS via Google Cloud |
27
+ | ChatGPT Plus/Pro | `openai-codex` | OpenAI models via ChatGPT subscription |
28
+
29
+ Credentials are saved to `.mercury/global/auth.json` and auto-refreshed when expired.
30
+
31
+ ### API Key
32
+
33
+ Alternatively, set an API key in `.env`:
34
+
35
+ ```bash
36
+ MERCURY_ANTHROPIC_API_KEY=sk-ant-...
37
+ # or
38
+ MERCURY_ANTHROPIC_OAUTH_TOKEN=sk-ant-oat01-...
39
+ ```
40
+
41
+ ### Resolution Order
42
+
43
+ Mercury resolves credentials in this order:
44
+
45
+ 1. OAuth credentials from `auth.json` (via `mercury auth login`)
46
+ 2. `MERCURY_ANTHROPIC_OAUTH_TOKEN` from `.env`
47
+ 3. `MERCURY_ANTHROPIC_API_KEY` from `.env`
48
+
49
+ > **OAuth always takes precedence over API keys.** If a provider has both an OAuth connection (in `auth.json` or `MERCURY_*_OAUTH_TOKEN`) and an API key (`MERCURY_*_API_KEY`), the OAuth token is used and the API key is ignored. This applies both to the CLI and to agents provisioned via the cloud console.
50
+
51
+ ### Managing Credentials
52
+
53
+ ```bash
54
+ mercury auth status # Show what's configured
55
+ mercury auth logout anthropic # Remove saved credentials
56
+ mercury auth logout # List what's logged in
57
+ ```
58
+
59
+ ## Chat Platform Auth
60
+
61
+ ### WhatsApp
62
+
63
+ ```bash
64
+ mercury auth whatsapp # QR code (recommended)
65
+ mercury auth whatsapp --pairing-code --phone 1234 # Pairing code (headless)
66
+ ```
67
+
68
+ See [whatsapp.md](whatsapp.md) for details.
69
+
70
+ ### Discord
71
+
72
+ Set in `.env`:
73
+
74
+ ```bash
75
+ MERCURY_ENABLE_DISCORD=true
76
+ MERCURY_DISCORD_BOT_TOKEN=your-bot-token
77
+ ```
78
+
79
+ ### Slack
80
+
81
+ Set in `.env`:
82
+
83
+ ```bash
84
+ MERCURY_ENABLE_SLACK=true
85
+ MERCURY_SLACK_BOT_TOKEN=xoxb-...
86
+ MERCURY_SLACK_SIGNING_SECRET=...
87
+ ```
88
+
89
+ ### Teams
90
+
91
+ Set in `.env`:
92
+
93
+ ```bash
94
+ MERCURY_ENABLE_TEAMS=true
95
+ MERCURY_TEAMS_APP_ID=...
96
+ MERCURY_TEAMS_APP_PASSWORD=...
97
+ MERCURY_TEAMS_APP_TENANT_ID=...
98
+ ```
99
+
100
+ ## Dashboard Auth
101
+
102
+ The web dashboard is protected by `MERCURY_API_SECRET`. Browser sessions are established via a login handshake managed by the cloud console — see [dashboard.md](dashboard.md).
103
+
104
+ ## Security
105
+
106
+ - `auth.json` has `0600` permissions (owner read/write only)
107
+ - WhatsApp credentials in `.mercury/whatsapp-auth/` are sensitive — treat like passwords
108
+ - All `MERCURY_*` env vars are passed into containers with the prefix stripped
109
+ - Never commit `.env` or `auth.json` to version control
@@ -0,0 +1,173 @@
1
+ # WhatsApp Authentication
2
+
3
+ Mercury connects to WhatsApp using the [Baileys](https://github.com/WhiskeySockets/Baileys) library, which implements the WhatsApp Web protocol. This means you need to link your WhatsApp account just like you would link WhatsApp Web.
4
+
5
+ ## Initial Setup
6
+
7
+ ### QR Code Mode (Recommended)
8
+
9
+ The simplest way to authenticate:
10
+
11
+ ```bash
12
+ mercury auth whatsapp
13
+ ```
14
+
15
+ This will:
16
+ 1. Display a QR code in your terminal
17
+ 2. Wait for you to scan it with WhatsApp
18
+ 3. Save credentials to `.mercury/whatsapp-auth/`
19
+ 4. Exit when authentication is complete
20
+
21
+ **To scan:**
22
+ 1. Open WhatsApp on your phone
23
+ 2. Tap **Settings → Linked Devices → Link a Device**
24
+ 3. Point your camera at the QR code
25
+
26
+ ### Pairing Code Mode
27
+
28
+ If you can't scan QR codes (e.g., running on a remote server), use pairing code mode:
29
+
30
+ ```bash
31
+ mercury auth whatsapp --pairing-code --phone 14155551234
32
+ ```
33
+
34
+ Replace `14155551234` with your phone number (country code + number, no `+` or spaces).
35
+
36
+ This will:
37
+ 1. Request a pairing code from WhatsApp
38
+ 2. Display an 8-character code
39
+ 3. Wait for you to enter it on your phone
40
+
41
+ **To pair:**
42
+ 1. Open WhatsApp on your phone
43
+ 2. Tap **Settings → Linked Devices → Link a Device**
44
+ 3. Tap **"Link with phone number instead"**
45
+ 4. Enter the code shown in your terminal
46
+
47
+ ## Session Lifecycle
48
+
49
+ ### Session Duration
50
+
51
+ WhatsApp linked device sessions typically last **~14-20 days** before requiring re-authentication. This is controlled by WhatsApp, not Mercury.
52
+
53
+ Signs your session has expired:
54
+ - WhatsApp stops receiving messages
55
+ - Logs show `connection closed` with `loggedOut` reason
56
+ - Status file shows `failed:logged_out`
57
+
58
+ ### Re-authentication
59
+
60
+ If your session expires:
61
+
62
+ 1. **Stop Mercury** (if running):
63
+ ```bash
64
+ # Ctrl+C in the terminal running mercury, or
65
+ pkill -f "bun.*chat-sdk"
66
+ ```
67
+
68
+ 2. **Delete old credentials**:
69
+ ```bash
70
+ rm -rf .mercury/whatsapp-auth/
71
+ ```
72
+
73
+ 3. **Re-authenticate**:
74
+ ```bash
75
+ mercury auth whatsapp
76
+ ```
77
+
78
+ 4. **Restart Mercury**:
79
+ ```bash
80
+ mercury run
81
+ ```
82
+
83
+ ## Status Files
84
+
85
+ The auth script writes status files for external monitoring (useful for headless deployments):
86
+
87
+ | File | Description |
88
+ |------|-------------|
89
+ | `.mercury/whatsapp-status.txt` | Current status (`authenticated`, `waiting_qr`, `pairing_code:XXXX`, `failed:reason`) |
90
+ | `.mercury/whatsapp-qr.txt` | Raw QR data for external rendering (deleted after successful auth) |
91
+
92
+ ### Status Values
93
+
94
+ - `authenticated` — Successfully connected
95
+ - `already_authenticated` — Existing valid session found
96
+ - `waiting_qr` — Waiting for QR code scan
97
+ - `pairing_code:XXXXXXXX` — Waiting for pairing code entry
98
+ - `failed:logged_out` — Session was logged out by WhatsApp
99
+ - `failed:qr_timeout` — QR code expired before scan
100
+ - `failed:515` — Stream error (usually recovers automatically)
101
+ - `failed:unknown` — Other connection failure
102
+
103
+ ## Auth Status API Endpoint
104
+
105
+ When Mercury is running, you can check auth status via the API:
106
+
107
+ ```bash
108
+ curl http://localhost:8787/auth/whatsapp
109
+ ```
110
+
111
+ **Responses:**
112
+
113
+ ```json
114
+ // Authenticated and connected
115
+ { "status": "authenticated" }
116
+
117
+ // Waiting for QR scan (includes raw QR data)
118
+ { "status": "waiting", "qr": "2@AbCdEf123..." }
119
+
120
+ // Disconnected or not yet connected
121
+ { "status": "disconnected" }
122
+ ```
123
+
124
+ This endpoint requires no authentication, making it suitable for headless monitoring dashboards.
125
+
126
+ ## Troubleshooting
127
+
128
+ ### "Already authenticated"
129
+
130
+ If you see this but WhatsApp isn't working:
131
+ ```bash
132
+ rm -rf .mercury/whatsapp-auth/
133
+ mercury auth whatsapp
134
+ ```
135
+
136
+ ### QR code times out too quickly
137
+
138
+ WhatsApp QR codes expire after ~20 seconds. If you're having trouble:
139
+ 1. Have your phone ready before running the command
140
+ 2. Use `--pairing-code` mode instead
141
+
142
+ ### "Stream error (515)"
143
+
144
+ This is usually transient and the auth script will automatically reconnect. If it persists:
145
+ ```bash
146
+ rm -rf .mercury/whatsapp-auth/
147
+ mercury auth whatsapp
148
+ ```
149
+
150
+ ### WhatsApp shows "Linked Device" but Mercury doesn't receive messages
151
+
152
+ 1. Check that `MERCURY_ENABLE_WHATSAPP=true` is set in `.env`
153
+ 2. Check logs for connection errors
154
+ 3. Try re-authenticating
155
+
156
+ ### Messages from before startup appear
157
+
158
+ Mercury ignores messages that were sent before it connected to prevent processing old backlog. This is intentional.
159
+
160
+ ## Environment Variables
161
+
162
+ | Variable | Default | Description |
163
+ |----------|---------|-------------|
164
+ | `MERCURY_ENABLE_WHATSAPP` | `false` | Enable WhatsApp adapter |
165
+ | `MERCURY_WHATSAPP_AUTH_DIR` | `.mercury/whatsapp-auth` | Directory for auth credentials |
166
+ | `MERCURY_DATA_DIR` | `.mercury` | Base data directory (auth dir is relative to this) |
167
+
168
+ ## Security Notes
169
+
170
+ - Auth credentials in `.mercury/whatsapp-auth/` are sensitive — treat them like passwords
171
+ - Anyone with access to these files can impersonate your WhatsApp account
172
+ - The `/auth/whatsapp` endpoint is unauthenticated — only expose your API port to trusted networks
173
+ - Consider using a dedicated WhatsApp number for your bot