mercury-agent 0.4.7 → 0.4.9

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 (36) hide show
  1. package/container/Dockerfile.power +1 -1
  2. package/docs/auth/dashboard.md +28 -28
  3. package/docs/container-lifecycle.md +4 -4
  4. package/examples/extensions/voice-synth/index.ts +94 -94
  5. package/examples/extensions/voice-transcribe/scripts/transcribe.py +1 -1
  6. package/package.json +1 -1
  7. package/resources/templates/mercury.example.yaml +1 -1
  8. package/src/adapters/whatsapp.ts +635 -632
  9. package/src/agent/container-runner.ts +3 -1
  10. package/src/agent/model-capabilities.ts +231 -231
  11. package/src/bridges/discord.ts +178 -178
  12. package/src/bridges/slack.ts +179 -179
  13. package/src/bridges/teams.ts +162 -162
  14. package/src/bridges/telegram.ts +579 -579
  15. package/src/cli/mercury.ts +2551 -2536
  16. package/src/cli/whatsapp-auth.ts +263 -260
  17. package/src/config.ts +316 -316
  18. package/src/core/permissions.ts +196 -196
  19. package/src/core/router.ts +191 -191
  20. package/src/core/routes/chat.ts +175 -175
  21. package/src/core/routes/dashboard.ts +2491 -2491
  22. package/src/core/routes/messages.ts +37 -37
  23. package/src/core/routes/mutes.ts +95 -95
  24. package/src/core/routes/roles.ts +135 -135
  25. package/src/core/runtime.ts +1140 -1140
  26. package/src/core/task-scheduler.ts +139 -139
  27. package/src/extensions/catalog.ts +117 -117
  28. package/src/extensions/hooks.ts +161 -161
  29. package/src/extensions/installer.ts +306 -306
  30. package/src/extensions/loader.ts +271 -271
  31. package/src/extensions/permission-guard.ts +52 -52
  32. package/src/server.ts +391 -391
  33. package/src/storage/db.ts +1625 -1625
  34. package/src/storage/pi-auth.ts +95 -95
  35. package/src/tts/azure.ts +52 -52
  36. package/src/tts/synthesize.ts +133 -133
@@ -3,7 +3,7 @@
3
3
  # Extends Base with: python3 + pip + venv, build-essential, jq, ffmpeg, bubblewrap
4
4
  # The patch script, mrctl, and entrypoint are all inherited from Base — not duplicated here.
5
5
  ARG BASE_TAG
6
- FROM ghcr.io/michaelliv/mercury-agent:${BASE_TAG}-base
6
+ FROM ghcr.io/avishai-tsabari/mercury-agent:${BASE_TAG}-base
7
7
 
8
8
  USER root
9
9
  RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -1,28 +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.
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.
@@ -176,8 +176,8 @@ Message received
176
176
  export MERCURY_CONTAINER_TIMEOUT_MS=600000
177
177
 
178
178
  # Use a preset image from GitHub Container Registry
179
- export MERCURY_AGENT_IMAGE=ghcr.io/michaelliv/mercury-agent:latest # Full (default)
180
- export MERCURY_AGENT_IMAGE=ghcr.io/michaelliv/mercury-agent:minimal # Lightweight
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
181
  ```
182
182
 
183
183
  ## Sandboxing (Bubblewrap)
@@ -204,8 +204,8 @@ Mercury publishes two image presets to GitHub Container Registry:
204
204
 
205
205
  | Preset | Size | Contents |
206
206
  |--------|------|----------|
207
- | `ghcr.io/michaelliv/mercury-agent:latest` | ~2.8GB | Full devcontainer: Bun, Node.js, Python, Go, git, build tools |
208
- | `ghcr.io/michaelliv/mercury-agent:minimal` | ~1.9GB | Lightweight runtime: Bun + pi + Chromium deps |
207
+ | `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
209
 
210
210
  Images are published on each release. Version-specific tags are also available (e.g., `:0.2.0`, `:0.2.0-minimal`).
211
211
 
@@ -1,94 +1,94 @@
1
- import { mkdirSync, statSync, writeFileSync } from "node:fs";
2
- import path from "node:path";
3
- import type {
4
- MercuryExtensionAPI,
5
- MercuryExtensionContext,
6
- } from "mercury-agent/extensions/types";
7
- import type { MercuryTtsConfig } from "mercury-agent/tts";
8
- import { synthesizeSpeech } from "mercury-agent/tts";
9
- import type { EgressFile } from "mercury-agent/types";
10
-
11
- const EXT = "voice-synth";
12
-
13
- /** Prefer `voice-synth.mode`; fall back to legacy `voice-synth.auto`. */
14
- function readVoiceSynthMode(
15
- ctx: MercuryExtensionContext,
16
- spaceId: string,
17
- ): "on_demand" | "auto" {
18
- const mode = ctx.db.getSpaceConfig(spaceId, `${EXT}.mode`);
19
- if (mode === "auto" || mode === "on_demand") return mode;
20
- const legacy = ctx.db.getSpaceConfig(spaceId, `${EXT}.auto`);
21
- return legacy === "true" ? "auto" : "on_demand";
22
- }
23
-
24
- function toTtsConfig(ctx: MercuryExtensionContext): MercuryTtsConfig {
25
- const c = ctx.config;
26
- return {
27
- ttsProvider: c.ttsProvider,
28
- azureSpeechKey: c.azureSpeechKey,
29
- azureSpeechRegion: c.azureSpeechRegion,
30
- googleApplicationCredentials: c.googleApplicationCredentials,
31
- ttsMaxChars: c.ttsMaxChars,
32
- };
33
- }
34
-
35
- export default function setup(mercury: MercuryExtensionAPI) {
36
- mercury.config("mode", {
37
- description:
38
- "on_demand: TTS only when the agent runs `mrctl tts synthesize` (default). auto: attach a TTS MP3 to every assistant reply.",
39
- default: "on_demand",
40
- validate: (v) => v === "on_demand" || v === "auto",
41
- });
42
-
43
- mercury.config("auto", {
44
- description:
45
- "Legacy: prefer voice-synth.mode. Used only when mode is unset; true equals mode=auto.",
46
- default: "false",
47
- validate: (v) => v === "true" || v === "false",
48
- });
49
-
50
- mercury.skill("./skill");
51
-
52
- mercury.on("after_container", async (event, ctx) => {
53
- if (readVoiceSynthMode(ctx, event.spaceId) !== "auto") return undefined;
54
- if (!event.reply?.trim()) return undefined;
55
- if (event.error) return undefined;
56
-
57
- if (
58
- !ctx.hasCallerPermission(event.spaceId, event.callerId, "tts.synthesize")
59
- ) {
60
- ctx.log.warn("voice-synth.auto skipped: caller lacks tts.synthesize", {
61
- extension: EXT,
62
- spaceId: event.spaceId,
63
- });
64
- return undefined;
65
- }
66
-
67
- const outDir = path.join(event.workspace, "outbox");
68
- mkdirSync(outDir, { recursive: true });
69
- const filename = `tts-${Date.now()}.mp3`;
70
- const absPath = path.join(outDir, filename);
71
-
72
- try {
73
- const { buffer } = await synthesizeSpeech(toTtsConfig(ctx), {
74
- text: event.reply,
75
- language: "auto",
76
- });
77
- writeFileSync(absPath, buffer);
78
- const st = statSync(absPath);
79
- const file: EgressFile = {
80
- path: absPath,
81
- filename,
82
- mimeType: "audio/mpeg",
83
- sizeBytes: st.size,
84
- };
85
- return { files: [file] };
86
- } catch (e) {
87
- ctx.log.error("voice-synth TTS failed", {
88
- extension: EXT,
89
- error: e instanceof Error ? e.message : String(e),
90
- });
91
- return undefined;
92
- }
93
- });
94
- }
1
+ import { mkdirSync, statSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import type {
4
+ MercuryExtensionAPI,
5
+ MercuryExtensionContext,
6
+ } from "mercury-agent/extensions/types";
7
+ import type { MercuryTtsConfig } from "mercury-agent/tts";
8
+ import { synthesizeSpeech } from "mercury-agent/tts";
9
+ import type { EgressFile } from "mercury-agent/types";
10
+
11
+ const EXT = "voice-synth";
12
+
13
+ /** Prefer `voice-synth.mode`; fall back to legacy `voice-synth.auto`. */
14
+ function readVoiceSynthMode(
15
+ ctx: MercuryExtensionContext,
16
+ spaceId: string,
17
+ ): "on_demand" | "auto" {
18
+ const mode = ctx.db.getSpaceConfig(spaceId, `${EXT}.mode`);
19
+ if (mode === "auto" || mode === "on_demand") return mode;
20
+ const legacy = ctx.db.getSpaceConfig(spaceId, `${EXT}.auto`);
21
+ return legacy === "true" ? "auto" : "on_demand";
22
+ }
23
+
24
+ function toTtsConfig(ctx: MercuryExtensionContext): MercuryTtsConfig {
25
+ const c = ctx.config;
26
+ return {
27
+ ttsProvider: c.ttsProvider,
28
+ azureSpeechKey: c.azureSpeechKey,
29
+ azureSpeechRegion: c.azureSpeechRegion,
30
+ googleApplicationCredentials: c.googleApplicationCredentials,
31
+ ttsMaxChars: c.ttsMaxChars,
32
+ };
33
+ }
34
+
35
+ export default function setup(mercury: MercuryExtensionAPI) {
36
+ mercury.config("mode", {
37
+ description:
38
+ "on_demand: TTS only when the agent runs `mrctl tts synthesize` (default). auto: attach a TTS MP3 to every assistant reply.",
39
+ default: "on_demand",
40
+ validate: (v) => v === "on_demand" || v === "auto",
41
+ });
42
+
43
+ mercury.config("auto", {
44
+ description:
45
+ "Legacy: prefer voice-synth.mode. Used only when mode is unset; true equals mode=auto.",
46
+ default: "false",
47
+ validate: (v) => v === "true" || v === "false",
48
+ });
49
+
50
+ mercury.skill("./skill");
51
+
52
+ mercury.on("after_container", async (event, ctx) => {
53
+ if (readVoiceSynthMode(ctx, event.spaceId) !== "auto") return undefined;
54
+ if (!event.reply?.trim()) return undefined;
55
+ if (event.error) return undefined;
56
+
57
+ if (
58
+ !ctx.hasCallerPermission(event.spaceId, event.callerId, "tts.synthesize")
59
+ ) {
60
+ ctx.log.warn("voice-synth.auto skipped: caller lacks tts.synthesize", {
61
+ extension: EXT,
62
+ spaceId: event.spaceId,
63
+ });
64
+ return undefined;
65
+ }
66
+
67
+ const outDir = path.join(event.workspace, "outbox");
68
+ mkdirSync(outDir, { recursive: true });
69
+ const filename = `tts-${Date.now()}.mp3`;
70
+ const absPath = path.join(outDir, filename);
71
+
72
+ try {
73
+ const { buffer } = await synthesizeSpeech(toTtsConfig(ctx), {
74
+ text: event.reply,
75
+ language: "auto",
76
+ });
77
+ writeFileSync(absPath, buffer);
78
+ const st = statSync(absPath);
79
+ const file: EgressFile = {
80
+ path: absPath,
81
+ filename,
82
+ mimeType: "audio/mpeg",
83
+ sizeBytes: st.size,
84
+ };
85
+ return { files: [file] };
86
+ } catch (e) {
87
+ ctx.log.error("voice-synth TTS failed", {
88
+ extension: EXT,
89
+ error: e instanceof Error ? e.message : String(e),
90
+ });
91
+ return undefined;
92
+ }
93
+ });
94
+ }
@@ -117,7 +117,7 @@ def _run_transformers(audio_path: str, model_id: str) -> str:
117
117
  model=model_id,
118
118
  device=device,
119
119
  )
120
- result = pipe(audio_path)
120
+ result = pipe(audio_path, return_timestamps=True)
121
121
  if isinstance(result, dict):
122
122
  return (result.get("text") or "").strip()
123
123
  return str(result).strip()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mercury-agent",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Personal AI assistant for chat platforms (WhatsApp, Slack, Discord, Telegram)",
5
5
  "license": "MIT",
6
6
  "author": "Avishai Tsabari",
@@ -57,7 +57,7 @@
57
57
  # # auto_compact_threshold: 50
58
58
 
59
59
  # agent:
60
- # image: ghcr.io/michaelliv/mercury-agent:latest
60
+ # image: ghcr.io/avishai-tsabari/mercury-agent:latest
61
61
  # container_timeout_ms: 300000
62
62
  # container_bwrap_docker_compat: false
63
63