mercury-agent 0.4.28 → 0.5.1

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 (122) hide show
  1. package/README.md +451 -451
  2. package/container/Dockerfile +127 -127
  3. package/container/Dockerfile.base +109 -109
  4. package/container/Dockerfile.power +17 -17
  5. package/container/agent-package.json +8 -8
  6. package/container/build.sh +54 -54
  7. package/docs/ARCHITECTURE.md +4 -0
  8. package/docs/ROADMAP.md +4 -5
  9. package/docs/{backlog/applicative-profiles.md → archive/applicative-profiles/2026-07-01-applicative-profiles.md} +29 -18
  10. package/docs/{backlog/caller-bound-capability-token.md → archive/applicative-profiles/2026-07-01-caller-bound-capability-token.md} +15 -11
  11. package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
  12. package/docs/auth/dashboard.md +28 -28
  13. package/docs/auth/overview.md +109 -109
  14. package/docs/auth/whatsapp.md +173 -173
  15. package/docs/authoring-profiles.md +174 -0
  16. package/docs/configuration.md +54 -54
  17. package/docs/container-lifecycle.md +349 -349
  18. package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
  19. package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
  20. package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
  21. package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
  22. package/docs/deployment.md +199 -199
  23. package/docs/extensions.md +375 -375
  24. package/docs/graceful-shutdown.md +62 -62
  25. package/docs/kb-distillation.md +77 -77
  26. package/docs/media/overview.md +140 -140
  27. package/docs/media/whatsapp.md +171 -171
  28. package/docs/memory.md +137 -137
  29. package/docs/permissions.md +217 -217
  30. package/docs/pipeline.md +228 -228
  31. package/docs/prd-chat-memory.md +76 -76
  32. package/docs/prd-config-load.md +82 -82
  33. package/docs/rate-limiting.md +229 -229
  34. package/docs/scheduler.md +288 -288
  35. package/docs/setup-discord.md +100 -100
  36. package/docs/setup-slack.md +119 -119
  37. package/docs/setup-whatsapp.md +94 -94
  38. package/docs/subagents.md +166 -166
  39. package/docs/web-search.md +62 -62
  40. package/examples/extensions/README.md +12 -12
  41. package/examples/extensions/charts/index.ts +13 -13
  42. package/examples/extensions/charts/skill/SKILL.md +98 -98
  43. package/examples/extensions/gws/README.md +52 -52
  44. package/examples/extensions/gws/skill/SKILL.md +57 -57
  45. package/examples/extensions/gws/skill/references/calendar.md +101 -101
  46. package/examples/extensions/gws/skill/references/docs.md +65 -65
  47. package/examples/extensions/gws/skill/references/drive.md +79 -79
  48. package/examples/extensions/gws/skill/references/gmail.md +85 -85
  49. package/examples/extensions/gws/skill/references/sheets.md +60 -60
  50. package/examples/extensions/napkin/skill/SKILL.md +728 -728
  51. package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
  52. package/examples/extensions/pdf/skill/SKILL.md +314 -314
  53. package/examples/extensions/pdf/skill/forms.md +294 -294
  54. package/examples/extensions/pdf/skill/reference.md +611 -611
  55. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
  56. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
  57. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
  58. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
  59. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
  60. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
  61. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
  62. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
  63. package/examples/extensions/permission-guard/index.ts +65 -65
  64. package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
  65. package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
  66. package/examples/extensions/pinchtab/skill/references/api.md +297 -297
  67. package/examples/extensions/pinchtab/skill/references/env.md +45 -45
  68. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
  69. package/examples/extensions/tradestation/host/refresh.ts +102 -102
  70. package/examples/extensions/tradestation/index.ts +153 -153
  71. package/examples/extensions/tradestation/skill/SKILL.md +67 -67
  72. package/examples/extensions/voice-synth/index.ts +94 -94
  73. package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
  74. package/examples/extensions/voice-transcribe/requirements.txt +8 -8
  75. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
  76. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
  77. package/examples/extensions/yahoo-mail/cli/package.json +13 -13
  78. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
  79. package/package.json +106 -106
  80. package/resources/agents/explore.md +50 -50
  81. package/resources/agents/worker.md +24 -24
  82. package/resources/connection-env-vars.json +25 -25
  83. package/resources/pi-extensions/subagent/agents.ts +126 -126
  84. package/resources/pi-extensions/subagent/index.ts +964 -964
  85. package/resources/profiles/coding/AGENTS.md +43 -43
  86. package/resources/profiles/coding/mercury-profile.yaml +15 -15
  87. package/resources/profiles/general/AGENTS.md +31 -31
  88. package/resources/profiles/general/mercury-profile.yaml +15 -15
  89. package/resources/profiles/research/AGENTS.md +40 -40
  90. package/resources/profiles/research/mercury-profile.yaml +15 -15
  91. package/resources/skills/config/SKILL.md +25 -25
  92. package/resources/skills/context/SKILL.md +33 -33
  93. package/resources/skills/conversation-recap/SKILL.md +19 -19
  94. package/resources/skills/mutes/SKILL.md +31 -31
  95. package/resources/skills/permissions/SKILL.md +19 -19
  96. package/resources/skills/preferences/SKILL.md +31 -31
  97. package/resources/skills/recall/SKILL.md +24 -24
  98. package/resources/skills/roles/SKILL.md +18 -18
  99. package/resources/skills/spaces/SKILL.md +18 -18
  100. package/resources/skills/tasks/SKILL.md +45 -45
  101. package/resources/templates/AGENTS.md +157 -157
  102. package/resources/templates/env.template +38 -38
  103. package/resources/templates/mercury.example.yaml +15 -0
  104. package/src/agent/container-runner.ts +23 -0
  105. package/src/cli/mercury.ts +38 -6
  106. package/src/cli/mrctl.ts +25 -0
  107. package/src/config-file.ts +3 -0
  108. package/src/config.ts +15 -0
  109. package/src/core/api.ts +23 -3
  110. package/src/core/caller-token.ts +101 -0
  111. package/src/core/permissions.ts +37 -5
  112. package/src/core/profiles.ts +110 -84
  113. package/src/core/routes/capability.ts +70 -0
  114. package/src/core/routes/index.ts +1 -0
  115. package/src/core/runtime.ts +14 -0
  116. package/src/dashboard/index.html +729 -729
  117. package/src/extensions/api.ts +21 -0
  118. package/src/extensions/loader.ts +15 -0
  119. package/src/extensions/types.ts +55 -0
  120. package/src/main.ts +19 -0
  121. package/docs/pending-updates/dm-auto-space.md +0 -15
  122. /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
@@ -1,173 +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
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
@@ -0,0 +1,174 @@
1
+ # Authoring Applicative Profiles
2
+
3
+ The contract for building a **profile** — a package of deterministic business
4
+ logic that wraps raw capabilities (Calendar, email, …) and scopes what members
5
+ can do. Mercury (this repo) provides the schema, loader, permission scoping, and
6
+ the host-side capability broker; **profiles live in a separate repo** and depend
7
+ only on this contract.
8
+
9
+ > Layers: **Mercury** = platform (messaging, spaces, permissions, tokens,
10
+ > broker). **Capability extensions** = raw API access (e.g. `gws`), admin-only.
11
+ > **Profiles** = deterministic domain logic wrapping capabilities, member-facing.
12
+
13
+ ## 1. Profile manifest — `mercury-profile.yaml`
14
+
15
+ ```yaml
16
+ name: barber-appointments # lowercase-kebab; required
17
+ description: Appointment booking for a barber shop
18
+ version: 0.1.0
19
+
20
+ agents_md: ./AGENTS.md # optional; copied to the global AGENTS.md
21
+
22
+ # Raw capability extensions this profile requires to be installed. Validated at
23
+ # apply time — activation fails loudly if any are missing.
24
+ capabilities:
25
+ - gws
26
+
27
+ # Extensions this profile ships (copied into .mercury/extensions).
28
+ extensions:
29
+ - name: barber
30
+ source: ./extensions/barber
31
+
32
+ # EXHAUSTIVE member permission set while active. When present it REPLACES the
33
+ # default member permissions — nothing is merged in — so raw capabilities stay
34
+ # admin-only unless listed here. Omit entirely to keep built-in defaults.
35
+ member_permissions:
36
+ - prompt
37
+ - prefs.get
38
+ - barber # the profile's own capability, NOT gws
39
+
40
+ # Optional env the profile needs (host-side credentials for capabilities).
41
+ env:
42
+ - key: MERCURY_BARBER_BUSINESS_HOURS
43
+ description: "Business hours"
44
+ default: "09:00-18:00"
45
+
46
+ # Project-wide agent persona, injected into every container's system prompt.
47
+ system_prompt: |
48
+ You are a barber shop assistant. Help each customer book, view, and cancel
49
+ ONLY their own appointments. Never reveal other customers' schedules.
50
+
51
+ defaults:
52
+ trigger_patterns: always
53
+ ```
54
+
55
+ Apply it with `mercury profile apply <name|path|git-url>`. That validates
56
+ capabilities, copies extensions/AGENTS.md, and persists activation to
57
+ `.mercury/active-profile.json`, which Mercury loads at startup.
58
+
59
+ ## 2. The capability broker (where the business logic runs)
60
+
61
+ A profile's privileged work runs **on the host**, so credentials never enter the
62
+ customer-controlled agent container. The profile's extension registers a
63
+ handler; the agent invokes it through a thin CLI.
64
+
65
+ ### Register a handler (in the extension's `index.ts`)
66
+
67
+ ```ts
68
+ import type { ExtensionSetupFn } from "mercury-agent"; // types from this repo
69
+
70
+ const setup: ExtensionSetupFn = (mercury) => {
71
+ // Make the capability gate-able. Members get it via member_permissions.
72
+ mercury.permission({ defaultRoles: [] });
73
+
74
+ // Host-side credentials for the wrapped capability (never sent to the container).
75
+ mercury.env({ from: "MERCURY_BARBER_GOOGLE_CREDENTIALS" });
76
+
77
+ mercury.capability("barber", async (req, ctx) => {
78
+ // req.callerId is the TOKEN-DERIVED, unspoofable caller — safe for ownership.
79
+ switch (req.action) {
80
+ case "availability":
81
+ return { data: await listOpenSlots(req.body, ctx) };
82
+ case "book":
83
+ return { data: await book(req.callerId, req.body, ctx) };
84
+ case "cancel":
85
+ return { data: await cancelOwn(req.callerId, req.body, ctx) };
86
+ case "my-appointments":
87
+ return { data: await listOwn(req.callerId, ctx) };
88
+ default:
89
+ return { status: 400, data: { error: `unknown action: ${req.action}` } };
90
+ }
91
+ });
92
+ };
93
+
94
+ export default setup;
95
+ ```
96
+
97
+ ### The contract types (exported from this repo)
98
+
99
+ ```ts
100
+ type CapabilityHandler = (
101
+ req: CapabilityRequest,
102
+ ctx: MercuryExtensionContext,
103
+ ) => Promise<CapabilityResult>;
104
+
105
+ interface CapabilityRequest {
106
+ name: string; // capability name
107
+ action: string; // sub-action (book, cancel, …)
108
+ callerId: string; // token-derived, TRUSTWORTHY — use for ownership checks
109
+ spaceId: string;
110
+ body: unknown; // parsed JSON request body (or null)
111
+ }
112
+
113
+ interface CapabilityResult {
114
+ status?: number; // default 200
115
+ data: unknown; // JSON-serializable response
116
+ }
117
+ ```
118
+
119
+ `ctx` (`MercuryExtensionContext`) gives you `db`, `config`, `log`, and
120
+ `hasCallerPermission(spaceId, callerId, permission)`. Store per-profile state via
121
+ `mercury.store` (namespaced KV) or your own tables through `ctx.db`.
122
+
123
+ ### How the agent invokes it
124
+
125
+ Inside the container the agent runs:
126
+
127
+ ```
128
+ mrctl capability barber book '{"slot":"2026-07-02T15:00"}'
129
+ ```
130
+
131
+ → `POST /api/capability/barber/book`. Document these commands for the agent in
132
+ the extension's `SKILL.md` or the profile's `AGENTS.md`.
133
+
134
+ ## 3. Permission & security model (non-negotiable)
135
+
136
+ - **`member_permissions` is exhaustive.** List every permission a member may
137
+ hold, including the capability name (e.g. `barber`). Anything not listed —
138
+ including raw capabilities like `gws` — is unavailable to members.
139
+ - **Authorization = permission named after the capability.** The broker route
140
+ requires the caller to hold the `<name>` permission; the same grant gates both
141
+ the `mrctl capability <name> …` CLI and the route. Keep capability name ==
142
+ permission name == the entry in `member_permissions`.
143
+ - **Credentials stay on the host.** Put capability credentials in host env
144
+ (`mercury.env`) / `mercury.store` and use them only inside the handler. Never
145
+ expose a raw capability CLI (e.g. `gws`) to members — its secret would be
146
+ readable inside the container.
147
+ - **Enforce ownership with `req.callerId`.** It is token-derived and cannot be
148
+ spoofed by the container. Never trust an id passed in `req.body`.
149
+ - **`callerId === "system"`** for scheduled/system runs — handle it explicitly
150
+ (e.g. skip per-customer ownership).
151
+
152
+ ## 4. Suggested profile repo layout
153
+
154
+ ```
155
+ <profile-repo>/
156
+ barber/ # one folder per profile
157
+ mercury-profile.yaml
158
+ AGENTS.md
159
+ extensions/
160
+ barber/
161
+ index.ts # registers permission + capability
162
+ package.json
163
+ ```
164
+
165
+ Depend on this repo (`mercury-agent`) for the types
166
+ (`ExtensionSetupFn`, `CapabilityRequest`, `CapabilityResult`,
167
+ `MercuryExtensionContext`). See `src/extensions/types.ts` for the full surface.
168
+
169
+ ## 5. Local test loop
170
+
171
+ 1. `mercury profile apply ./barber`
172
+ 2. `mercury service install` (builds the derived image with the extension CLI)
173
+ 3. DM the bot as a non-admin number; confirm you can `book`/`cancel` only your
174
+ own appointments and cannot reach `gws`/Gmail.
@@ -1,54 +1,54 @@
1
- # Configuration
2
-
3
- Mercury reads settings from **environment variables** (`MERCURY_*`) and, optionally, a project **`mercury.yaml`** file in the current working directory.
4
-
5
- **Product / design spec:** [prd-config-load.md](prd-config-load.md) (config load: YAML + env precedence, security, non-goals).
6
-
7
- ## Precedence
8
-
9
- 1. If a `MERCURY_*` variable is **set** in the environment (the key exists, including empty string), its value wins.
10
- 2. Otherwise, if **`mercury.yaml`** (or **`mercury.yml`**) exists in `cwd`, values from that file apply.
11
- 3. Otherwise, built-in defaults from `config.ts` apply.
12
-
13
- Set **`MERCURY_CONFIG_FILE`** to an explicit path to load a different file. Set it to **`""`** (empty) or **`none`** to **disable** loading any file (useful for tests or when you want env/defaults only).
14
-
15
- Relative paths in `MERCURY_CONFIG_FILE` are resolved against `cwd`.
16
-
17
- ## Secrets (never use `mercury.yaml` for these)
18
-
19
- These must be supplied via environment variables only; they are **not** read from YAML:
20
-
21
- - `MERCURY_API_SECRET`
22
- - `MERCURY_CHAT_API_KEY`
23
- - `MERCURY_DISCORD_GATEWAY_SECRET`
24
-
25
- Platform tokens, provider API keys, and extension keys (e.g. `MERCURY_TELEGRAM_BOT_TOKEN`, `MERCURY_BRAVE_API_KEY`) are also **env-only** today—they are not part of the YAML schema.
26
-
27
- ## YAML layout
28
-
29
- See [`resources/templates/mercury.example.yaml`](../resources/templates/mercury.example.yaml) for a commented template. Supported sections include `server`, `model`, `ingress`, `runtime`, `trigger`, `context`, `conditional_context`, `compaction`, `agent`, `discord`, `telegram`, `media`, and `permissions`.
30
-
31
- The `context:` block seeds default conversation-context behavior into the `main` space on first boot:
32
-
33
- ```yaml
34
- context:
35
- mode: context # clear | context (default: context)
36
- window_size: 10 # 1-50 (default: 10). Sliding-window turns when mode=context.
37
- reply_chain_depth: 10 # 1-50 (default: 10). Reply chain depth when mode=clear.
38
- ```
39
-
40
- Per-space overrides via `mrctl config set context.<key> <value>` always win over YAML defaults; YAML re-reads on restart do not overwrite an existing space row.
41
-
42
- You may also set a top-level **`model_chain`** array as an alias for `model.chain`.
43
-
44
- ## Model chain
45
-
46
- In YAML, use a list of `{ provider, model }` objects under `model.chain` (max 4 legs). The same rules apply as for `MERCURY_MODEL_CHAIN` JSON.
47
-
48
- Optional **`model.capabilities`** may be a mapping; it is applied like `MERCURY_MODEL_CAPABILITIES` JSON.
49
-
50
- ### Removed: `provider: cursor`
51
-
52
- The **Cursor Agent CLI** integration has been removed. All model legs use **pi** with standard providers (`anthropic`, `openai`, `google`, `mistral`, `groq`, `openrouter`, etc.).
53
-
54
- If your chain still has `provider: cursor`, the agent run **fails fast** with an error that points here. Switch to the **native provider** for the model you want (for example `anthropic` for Claude, `openai` for GPT) and set the matching **`MERCURY_*_API_KEY`**.
1
+ # Configuration
2
+
3
+ Mercury reads settings from **environment variables** (`MERCURY_*`) and, optionally, a project **`mercury.yaml`** file in the current working directory.
4
+
5
+ **Product / design spec:** [prd-config-load.md](prd-config-load.md) (config load: YAML + env precedence, security, non-goals).
6
+
7
+ ## Precedence
8
+
9
+ 1. If a `MERCURY_*` variable is **set** in the environment (the key exists, including empty string), its value wins.
10
+ 2. Otherwise, if **`mercury.yaml`** (or **`mercury.yml`**) exists in `cwd`, values from that file apply.
11
+ 3. Otherwise, built-in defaults from `config.ts` apply.
12
+
13
+ Set **`MERCURY_CONFIG_FILE`** to an explicit path to load a different file. Set it to **`""`** (empty) or **`none`** to **disable** loading any file (useful for tests or when you want env/defaults only).
14
+
15
+ Relative paths in `MERCURY_CONFIG_FILE` are resolved against `cwd`.
16
+
17
+ ## Secrets (never use `mercury.yaml` for these)
18
+
19
+ These must be supplied via environment variables only; they are **not** read from YAML:
20
+
21
+ - `MERCURY_API_SECRET`
22
+ - `MERCURY_CHAT_API_KEY`
23
+ - `MERCURY_DISCORD_GATEWAY_SECRET`
24
+
25
+ Platform tokens, provider API keys, and extension keys (e.g. `MERCURY_TELEGRAM_BOT_TOKEN`, `MERCURY_BRAVE_API_KEY`) are also **env-only** today—they are not part of the YAML schema.
26
+
27
+ ## YAML layout
28
+
29
+ See [`resources/templates/mercury.example.yaml`](../resources/templates/mercury.example.yaml) for a commented template. Supported sections include `server`, `model`, `ingress`, `runtime`, `trigger`, `context`, `conditional_context`, `compaction`, `agent`, `discord`, `telegram`, `media`, and `permissions`.
30
+
31
+ The `context:` block seeds default conversation-context behavior into the `main` space on first boot:
32
+
33
+ ```yaml
34
+ context:
35
+ mode: context # clear | context (default: context)
36
+ window_size: 10 # 1-50 (default: 10). Sliding-window turns when mode=context.
37
+ reply_chain_depth: 10 # 1-50 (default: 10). Reply chain depth when mode=clear.
38
+ ```
39
+
40
+ Per-space overrides via `mrctl config set context.<key> <value>` always win over YAML defaults; YAML re-reads on restart do not overwrite an existing space row.
41
+
42
+ You may also set a top-level **`model_chain`** array as an alias for `model.chain`.
43
+
44
+ ## Model chain
45
+
46
+ In YAML, use a list of `{ provider, model }` objects under `model.chain` (max 4 legs). The same rules apply as for `MERCURY_MODEL_CHAIN` JSON.
47
+
48
+ Optional **`model.capabilities`** may be a mapping; it is applied like `MERCURY_MODEL_CAPABILITIES` JSON.
49
+
50
+ ### Removed: `provider: cursor`
51
+
52
+ The **Cursor Agent CLI** integration has been removed. All model legs use **pi** with standard providers (`anthropic`, `openai`, `google`, `mistral`, `groq`, `openrouter`, etc.).
53
+
54
+ If your chain still has `provider: cursor`, the agent run **fails fast** with an error that points here. Switch to the **native provider** for the model you want (for example `anthropic` for Claude, `openai` for GPT) and set the matching **`MERCURY_*_API_KEY`**.