mercury-agent 0.5.7 → 0.5.10

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 (34) hide show
  1. package/README.md +29 -3
  2. package/container/Dockerfile +9 -5
  3. package/container/Dockerfile.base +2 -2
  4. package/container/build.sh +7 -38
  5. package/docs/authoring-profiles.md +2 -2
  6. package/docs/container-lifecycle.md +7 -11
  7. package/docs/memory.md +0 -6
  8. package/docs/prd-config-load.md +1 -1
  9. package/examples/extensions/napkin/index.ts +17 -10
  10. package/examples/extensions/napkin/prompts/kb-distillation.md +6 -6
  11. package/examples/extensions/pdf/skill/SKILL.md +545 -178
  12. package/examples/extensions/pdf/skill/forms.md +273 -214
  13. package/examples/extensions/pdf/skill/reference.md +450 -480
  14. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +221 -58
  15. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +90 -5
  16. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +138 -22
  17. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +191 -27
  18. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +149 -104
  19. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +215 -91
  20. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +130 -86
  21. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +204 -87
  22. package/examples/extensions/tradestation/skill/SKILL.md +2 -2
  23. package/package.json +17 -1
  24. package/resources/templates/env.template +0 -3
  25. package/src/adapters/whatsapp-media.ts +5 -2
  26. package/src/agent/container-entry.ts +2 -2
  27. package/src/agent/container-runner.ts +19 -14
  28. package/src/bridges/telegram.ts +5 -2
  29. package/src/chat-shim.ts +26 -17
  30. package/src/core/media.ts +5 -2
  31. package/src/dashboard/tokens.css +1 -1
  32. package/src/extensions/image-builder.ts +2 -2
  33. package/src/main.ts +2 -2
  34. package/examples/extensions/pdf/skill/LICENSE.txt +0 -30
package/README.md CHANGED
@@ -52,21 +52,23 @@ Configure identity and adapters in `.env`:
52
52
  MERCURY_BOT_USERNAME=Mercury
53
53
  MERCURY_TRIGGER_PATTERNS=@Mercury,Mercury
54
54
 
55
- # Enable adapters
55
+ # Enable adapters (pick one or more)
56
56
  MERCURY_ENABLE_WHATSAPP=true
57
57
  MERCURY_ENABLE_DISCORD=true
58
58
  MERCURY_DISCORD_BOT_TOKEN=your-bot-token
59
+ MERCURY_ENABLE_TELEGRAM=true
60
+ MERCURY_TELEGRAM_BOT_TOKEN=your-bot-token
59
61
  ```
60
62
 
61
63
  Start:
62
64
 
63
65
  ```bash
64
66
  mercury run
65
- # or install as a background service:
67
+ # or install as a background service (macOS/Linux only):
66
68
  mercury service install
67
69
  ```
68
70
 
69
- > `mercury run` runs in the foreground (good for a first smoke test). For anything long-running, prefer `mercury service install` — it runs in the background and auto-restarts.
71
+ > `mercury run` runs in the foreground — on Windows (WSL2) this is the only option. On macOS/Linux, prefer `mercury service install` for long-running use — it runs in the background and auto-restarts.
70
72
 
71
73
  ### Set up spaces and conversations
72
74
 
@@ -86,6 +88,26 @@ mercury link <id> main # Link a conversation to a space
86
88
 
87
89
  Multiple conversations can point at the same space — they share memory, session, and vault.
88
90
 
91
+ ### Updating Mercury
92
+
93
+ ```bash
94
+ # 1. Stop Mercury
95
+ # macOS/Linux: mercury service uninstall
96
+ # Windows/WSL2: Ctrl+C in the terminal running mercury run
97
+
98
+ # 2. Update the package
99
+ npm update -g mercury-agent
100
+
101
+ # 3. Rebuild the agent container image
102
+ mercury build
103
+
104
+ # 4. Start again
105
+ # macOS/Linux: mercury service install
106
+ # Windows/WSL2: mercury run
107
+ ```
108
+
109
+ `mercury build` rebuilds the base agent image from the installed package source. The derived image (base + your extensions) rebuilds automatically on startup when the base changes.
110
+
89
111
  ---
90
112
 
91
113
  ## How It Works
@@ -445,6 +467,10 @@ This fork is maintained by [Avishai Tsabari](https://github.com/Avishai-Tsabari)
445
467
 
446
468
  MIT — see [LICENSE](LICENSE)
447
469
 
470
+ ### Third-party licenses
471
+
472
+ The WhatsApp adapter depends on [@whiskeysockets/baileys](https://github.com/WhiskeySockets/Baileys), which transitively includes [libsignal](https://github.com/nicktrav/libsignal) (GPL-3.0) for Signal Protocol encryption. This component runs inside an isolated Docker container and is loaded as a runtime dependency, not statically linked into Mercury's codebase. If your use case has strict copyleft concerns, consult legal counsel or omit the WhatsApp adapter.
473
+
448
474
  ---
449
475
 
450
476
  <p align="center">
@@ -60,7 +60,7 @@ RUN echo '{"args":["--no-sandbox"]}' > /home/mercury/.puppeteerrc.json
60
60
  ENV CHROMIUM_FLAGS="--no-sandbox"
61
61
 
62
62
  # Install CLIs
63
- RUN bun add -g @mariozechner/pi-coding-agent@^0.67.2
63
+ RUN bun add -g @earendil-works/pi-coding-agent@~0.79.6
64
64
 
65
65
  WORKDIR /app
66
66
 
@@ -77,7 +77,7 @@ function patch(dir) {
77
77
  const p = path.join(dir, f);
78
78
  try {
79
79
  if (fs.statSync(p).isDirectory()) patch(p);
80
- else if (f === 'google.js' && p.includes('@mariozechner/pi-ai')) {
80
+ else if (f === 'google.js' && p.includes('@earendil-works/pi-ai')) {
81
81
  let c = fs.readFileSync(p, 'utf8');
82
82
  const noThinking = '&& !model.id.startsWith("gemma")';
83
83
  const p1 = 'if (options.thinking?.enabled && model.reasoning) {';
@@ -97,6 +97,13 @@ patch('/home/mercury/.bun');
97
97
  patch('/app/node_modules');
98
98
  EOF
99
99
 
100
+ # Fix ownership of all mercury home dir artifacts before switching user.
101
+ # Placed here — after the last step that writes to /home/mercury (the pi patch),
102
+ # but before the volatile /app source COPYs below — so this expensive `chown -R`
103
+ # (it walks the whole Chromium + .bun tree) lands in a stable cached layer.
104
+ # Editing source files no longer invalidates it or forces the huge layer re-export.
105
+ RUN chown -R mercury:mercury /home/mercury
106
+
100
107
  COPY src/agent/container-entry.ts /app/src/agent/container-entry.ts
101
108
  COPY src/agent/model-capabilities-core.ts /app/src/agent/model-capabilities-core.ts
102
109
  COPY src/agent/pi-failure-class.ts /app/src/agent/pi-failure-class.ts
@@ -119,9 +126,6 @@ RUN while IFS= read -r ext || [ -n "$ext" ]; do \
119
126
  RUN echo '#!/bin/sh\nbun run /app/src/cli/mrctl.ts "$@"' > /usr/local/bin/mrctl && \
120
127
  chmod +x /usr/local/bin/mrctl
121
128
 
122
- # Fix ownership of all mercury home dir artifacts before switching user
123
- RUN chown -R mercury:mercury /home/mercury
124
-
125
129
  USER mercury
126
130
 
127
131
  ENTRYPOINT ["bun", "run", "/app/src/agent/container-entry.ts"]
@@ -42,7 +42,7 @@ RUN echo '{"args":["--no-sandbox"]}' > /home/mercury/.puppeteerrc.json
42
42
  ENV CHROMIUM_FLAGS="--no-sandbox"
43
43
 
44
44
  # Install CLIs
45
- RUN bun add -g @mariozechner/pi-coding-agent@^0.67.2
45
+ RUN bun add -g @earendil-works/pi-coding-agent@~0.79.6
46
46
 
47
47
  WORKDIR /app
48
48
 
@@ -59,7 +59,7 @@ function patch(dir) {
59
59
  const p = path.join(dir, f);
60
60
  try {
61
61
  if (fs.statSync(p).isDirectory()) patch(p);
62
- else if (f === 'google.js' && p.includes('@mariozechner/pi-ai')) {
62
+ else if (f === 'google.js' && p.includes('@earendil-works/pi-ai')) {
63
63
  let c = fs.readFileSync(p, 'utf8');
64
64
  const noThinking = '&& !model.id.startsWith("gemma")';
65
65
  const p1 = 'if (options.thinking?.enabled && model.reasoning) {';
@@ -9,46 +9,15 @@ cd "$PROJECT_ROOT"
9
9
  IMAGE_NAME="mercury-agent"
10
10
 
11
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"
12
+ if [ $# -gt 0 ] && [ "$1" != "latest" ]; then
13
+ echo "Usage: $0 [latest]"
31
14
  echo ""
32
- echo "Default: latest"
15
+ echo "Builds ${IMAGE_NAME}:latest — full devcontainer with Node, Python, Go, git (~2.8GB)."
33
16
  exit 1
34
17
  fi
35
18
 
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
-
19
+ echo "Building ${IMAGE_NAME}:latest (full devcontainer)..."
20
+ docker build -f container/Dockerfile -t "${IMAGE_NAME}:latest" .
21
+ echo "✓ Built ${IMAGE_NAME}:latest"
22
+ echo ""
50
23
  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
@@ -52,7 +52,7 @@ defaults:
52
52
  trigger_patterns: always
53
53
  ```
54
54
 
55
- Apply it with `mercury profile apply <name|path|git-url>`. That validates
55
+ Apply it with `mercury setup --profile <name|path>`. That validates
56
56
  capabilities, copies extensions/AGENTS.md, and persists activation to
57
57
  `.mercury/active-profile.json`, which Mercury loads at startup.
58
58
 
@@ -168,7 +168,7 @@ Depend on this repo (`mercury-agent`) for the types
168
168
 
169
169
  ## 5. Local test loop
170
170
 
171
- 1. `mercury profile apply ./barber`
171
+ 1. `mercury setup --profile ./barber`
172
172
  2. `mercury service install` (builds the derived image with the extension CLI)
173
173
  3. DM the bot as a non-admin number; confirm you can `book`/`cancel` only your
174
174
  own appointments and cannot reach `gws`/Gmail.
@@ -175,9 +175,8 @@ Message received
175
175
  # Set container timeout to 10 minutes
176
176
  export MERCURY_CONTAINER_TIMEOUT_MS=600000
177
177
 
178
- # Use a preset image from GitHub Container Registry
178
+ # Use the preset image from GitHub Container Registry
179
179
  export MERCURY_AGENT_IMAGE=ghcr.io/avishai-tsabari/mercury-agent:latest # Full (default)
180
- export MERCURY_AGENT_IMAGE=ghcr.io/avishai-tsabari/mercury-agent:minimal # Lightweight
181
180
  ```
182
181
 
183
182
  ## Sandboxing (Bubblewrap)
@@ -198,27 +197,24 @@ If you see `bwrap: Creating new namespace failed: Operation not permitted`, try
198
197
 
199
198
  Custom images must install `bubblewrap` for sandboxing to work.
200
199
 
201
- ## Agent Image Presets
200
+ ## Agent Image Preset
202
201
 
203
- Mercury publishes two image presets to GitHub Container Registry:
202
+ Mercury publishes an image preset to GitHub Container Registry:
204
203
 
205
204
  | Preset | Size | Contents |
206
205
  |--------|------|----------|
207
206
  | `ghcr.io/avishai-tsabari/mercury-agent:latest` | ~2.8GB | Full devcontainer: Bun, Node.js, Python, Go, git, build tools |
208
- | `ghcr.io/avishai-tsabari/mercury-agent:minimal` | ~1.9GB | Lightweight runtime: Bun + pi + Chromium deps |
209
207
 
210
- Images are published on each release. Version-specific tags are also available (e.g., `:0.2.0`, `:0.2.0-minimal`).
208
+ Images are published on each release. Version-specific tags are also available (e.g., `:0.2.0`).
211
209
 
212
210
  ### Building Locally
213
211
 
214
- To build images locally instead of pulling from the registry:
212
+ To build the image locally instead of pulling from the registry:
215
213
  ```bash
216
- ./container/build.sh all # Both presets
217
- ./container/build.sh latest # Full image only (default)
218
- ./container/build.sh minimal # Lightweight image only
214
+ ./container/build.sh # Full image (default)
219
215
  ```
220
216
 
221
- Then use `mercury-agent:latest` or `mercury-agent:minimal` (without the ghcr.io prefix).
217
+ Then use `mercury-agent:latest` (without the ghcr.io prefix).
222
218
 
223
219
  ## Custom Agent Images
224
220
 
package/docs/memory.md CHANGED
@@ -107,12 +107,6 @@ _2026-02-25:_ Changed venue to [[Cafe Nimrod]].
107
107
  - **Body** — Accumulated context (append semantics, timestamped)
108
108
  - **Wikilinks** — Connections to other entities
109
109
 
110
- ## Conditional Context
111
-
112
- Mercury can skip loading the full session for standalone prompts (e.g. "what's 2+2?"), reducing token usage. After the run, the prompt and reply are merged back into the session so history stays complete.
113
-
114
- See [conditional-context.md](conditional-context.md) for details and configuration.
115
-
116
110
  ## Persistence
117
111
 
118
112
  Memory persists because the agent writes to disk during conversation. When a session compacts or restarts, the vault files remain — the agent reads them fresh on next interaction.
@@ -71,7 +71,7 @@ For each mapped setting, `mergeRawMercuryConfig` applies:
71
71
  | YAML parse, Zod file schema, flatten, merge | `src/config-file.ts` |
72
72
  | Shared model-leg validation | `src/config-model-chain.ts` |
73
73
  | `loadConfig`, Zod app schema | `src/config.ts` |
74
- | Tests | `tests/config.test.ts` (+ guards in `router.test.ts`, `session-context-estimate.test.ts`) |
74
+ | Tests | `tests/config.test.ts` (+ guards in `router.test.ts`) |
75
75
  | Template | `resources/templates/mercury.example.yaml` |
76
76
  | Operator guide | `docs/configuration.md` |
77
77
 
@@ -8,8 +8,9 @@ import {
8
8
  unlinkSync,
9
9
  writeFileSync,
10
10
  } from "node:fs";
11
+ import { createRequire } from "node:module";
11
12
  import { homedir, tmpdir } from "node:os";
12
- import { delimiter, join } from "node:path";
13
+ import { delimiter, dirname, join } from "node:path";
13
14
 
14
15
  const KNOWLEDGE_DIR = "knowledge";
15
16
  const VAULT_DIRS = ["people", "projects", "references", "daily", "episodes", "weekly", "monthly", "templates"];
@@ -135,25 +136,31 @@ function exportMessages(
135
136
  }
136
137
 
137
138
  /**
138
- * Build a child env whose PATH also contains the standard global-bin
139
- * directories where the `pi` CLI is installed. The distill job runs in the
140
- * Mercury HOST process, whichunlike the cloud Docker image, where `pi` is
141
- * always on PATH may have been launched with an incomplete PATH (e.g.
142
- * `bun <abs>/mercury.ts run` from an IDE or a scheduled task resolves
143
- * `.bun/bin` but not the npm global bin). A bare `spawn("pi")` then fails with
144
- * ENOENT before pi ever starts. Appending the well-known global-bin dirs makes
145
- * resolution robust on local installs and is a no-op in the cloud.
139
+ * Build a child env whose PATH also contains directories where the `pi` CLI
140
+ * may live. A global npm install only links the top-level package's bins
141
+ * (mercury, mercury-ctl)dependency bins like `pi` stay in node_modules/.bin.
142
+ * We resolve that .bin dir from the package graph first, then fall back to
143
+ * well-known global-bin dirs (~/.bun/bin, /usr/local/bin).
146
144
  */
147
145
  function envWithPiOnPath(): NodeJS.ProcessEnv {
148
146
  const isWindows = process.platform === "win32";
149
147
  const base = process.env.PATH ?? process.env.Path ?? "";
150
148
  const home = homedir();
149
+
150
+ let piNodeModulesBin: string | undefined;
151
+ try {
152
+ const req = createRequire(import.meta.url);
153
+ const pkgJson = req.resolve("@earendil-works/pi-coding-agent/package.json");
154
+ piNodeModulesBin = join(dirname(dirname(dirname(pkgJson))), ".bin");
155
+ } catch {}
156
+
151
157
  const candidates = [
158
+ piNodeModulesBin,
152
159
  join(home, ".bun", "bin"),
153
160
  isWindows
154
161
  ? join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "npm")
155
162
  : "/usr/local/bin",
156
- ];
163
+ ].filter(Boolean) as string[];
157
164
  // PATH entries are case-insensitive on Windows, case-sensitive on POSIX.
158
165
  const normalize = (p: string) => (isWindows ? p.toLowerCase() : p);
159
166
  const existing = new Set(base.split(delimiter).map(normalize).filter(Boolean));
@@ -45,20 +45,20 @@ Every entity file follows this shape. The **current** value of a fact lives in B
45
45
 
46
46
  ```markdown
47
47
  ---
48
- name: Ronny Cohen
48
+ name: Alex Carter
49
49
  type: person
50
50
  updated: 2026-05-10
51
- summary: Biotech investor in the tagula group; bullish on SLS.
51
+ summary: Biotech investor in the research group; bullish on ACME.
52
52
  # Structured "current value" fields for facts that are known to change:
53
- sls_price_target: "$15"
53
+ acme_price_target: "$15"
54
54
  ---
55
55
 
56
- # Ronny Cohen
56
+ # Alex Carter
57
57
 
58
- Member of the [[tagula-space]] investing group.
58
+ Member of the [[research-space]] investing group.
59
59
 
60
60
  ## Current View
61
- - SLS price target: **$15** (as of 2026-05-10)
61
+ - ACME price target: **$15** (as of 2026-05-10)
62
62
  - Thesis: bullish, waiting for the Phase 3 readout
63
63
 
64
64
  ## History