@stage-labs/metro 0.1.0-beta.7 → 0.1.0-beta.8
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.
- package/README.md +83 -27
- package/dist/cli/index.js +19 -1
- package/dist/cli/webhook.js +83 -0
- package/dist/codex-rc.js +14 -5
- package/dist/dispatcher.js +30 -3
- package/dist/history.js +18 -3
- package/dist/paths.js +3 -3
- package/dist/registry.js +48 -0
- package/dist/stations/claude.js +45 -0
- package/dist/stations/codex.js +68 -0
- package/dist/stations/index.js +68 -8
- package/dist/stations/webhook.js +100 -0
- package/dist/tunnel.js +49 -0
- package/dist/webhooks.js +40 -0
- package/docs/agents.md +47 -16
- package/docs/uri-scheme.md +68 -14
- package/package.json +1 -1
- package/skills/metro/SKILL.md +24 -16
package/skills/metro/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: metro
|
|
3
|
-
description: Run the metro Telegram/Discord bridge in this session — launch `metro` in the background, watch its stdout for inbound JSON events, and act on each. Use when the user asks to start/run/launch metro, when you see JSON lines on stdout shaped `{"kind":"inbound","station":...,"line":"metro://...","messageId":...,"text":...}`, or when handling a chat reply/edit/react/send/download/fetch/notify.
|
|
3
|
+
description: Run the metro Telegram/Discord/webhook bridge in this session — launch `metro` in the background, watch its stdout for inbound JSON events, and act on each. Use when the user asks to start/run/launch metro, when you see JSON lines on stdout shaped `{"kind":"inbound","station":...,"line":"metro://...","messageId":...,"text":...}`, or when handling a chat/webhook reply/edit/react/send/download/fetch/notify.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Metro — running the Telegram
|
|
6
|
+
# Metro — running the Telegram / Discord / webhook bridge
|
|
7
7
|
|
|
8
|
-
Metro is a CLI bridge between this agent session and Telegram
|
|
8
|
+
Metro is a CLI bridge between this agent session and external sources: Telegram, Discord, and HTTP webhooks from third parties (GitHub, Intercom, Fireflies, …). You launch `metro` once when the user asks, then act on each inbound JSON line via `metro <subcommand>`.
|
|
9
9
|
|
|
10
10
|
## Starting the bridge
|
|
11
11
|
|
|
@@ -44,8 +44,8 @@ Every line on stdout is one **history entry** — the same record appended to `h
|
|
|
44
44
|
- `kind` — `"inbound"`, `"notification"`, `"outbound"`, `"edit"`, or `"react"`
|
|
45
45
|
- `id` (`msg_…`) — universal message ID minted by metro
|
|
46
46
|
- `ts` — ISO timestamp
|
|
47
|
-
- `station` — `"discord"`, `"telegram"`, `"claude"`, `"codex"`
|
|
48
|
-
- `line` — conversation URI; `lineName?` is the channel/topic display name
|
|
47
|
+
- `station` — `"discord"`, `"telegram"`, `"claude"`, `"codex"`, `"webhook"`
|
|
48
|
+
- `line` — conversation URI; `lineName?` is the channel/topic display name (for webhooks: the label you gave it)
|
|
49
49
|
- `from` / `fromName?` — sender participant URI + optional display name
|
|
50
50
|
- `to` — recipient participant URI (agent for inbound, line for notification, original sender for replies/reacts)
|
|
51
51
|
- `text` — universal display projection. Includes `[image]`/`[file: …]`/`[voice]`/`[audio]` tags inline.
|
|
@@ -53,11 +53,11 @@ Every line on stdout is one **history entry** — the same record appended to `h
|
|
|
53
53
|
- `payload?` — raw platform-native message object. Set on inbound only. Shape varies per `station`.
|
|
54
54
|
|
|
55
55
|
```json
|
|
56
|
-
{"kind":"inbound","id":"msg_aB3xY7zP","ts":"2026-05-14T12:00:00Z","station":"telegram","line":"metro://telegram/-100…/247","lineName":"infra","from":"metro://telegram/user/12345","fromName":"@alice","to":"metro://claude/
|
|
56
|
+
{"kind":"inbound","id":"msg_aB3xY7zP","ts":"2026-05-14T12:00:00Z","station":"telegram","line":"metro://telegram/-100…/247","lineName":"infra","from":"metro://telegram/user/12345","fromName":"@alice","to":"metro://claude/user/9bfc7af0-…","messageId":"4567","text":"hi [image]","payload":{"message_id":4567,"chat":{"id":-100,"type":"supergroup","is_forum":true},"from":{"id":12345,"username":"alice"},"text":"hi","photo":[{"file_id":"…"}],"reply_to_message":{"message_id":4500,"text":"earlier","from":{"id":99,"username":"bob"}}}}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
```json
|
|
60
|
-
{"kind":"notification","id":"msg_pQ4r5sT0","ts":"…","station":"claude","line":"metro://claude/
|
|
60
|
+
{"kind":"notification","id":"msg_pQ4r5sT0","ts":"…","station":"claude","line":"metro://claude/9bfc7af0-…/50b00d11-…","from":"metro://codex/user/8119ecb1-…","to":"metro://claude/9bfc7af0-…/50b00d11-…","text":"deploy green"}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
### `payload` by station
|
|
@@ -66,6 +66,7 @@ Every line on stdout is one **history entry** — the same record appended to `h
|
|
|
66
66
|
|
|
67
67
|
- **`discord`** — discord.js `Message.toJSON()`: camelCase fields (`channelId`, `guildId`, `content`, `author`, `mentions: { users[], roles[], everyone }`, `attachments[]`, `reference`, …). Collections come back as **arrays of IDs**. `referencedMessage` is added inline on replies (auto-fetched).
|
|
68
68
|
- **`telegram`** — raw Bot API `Message` (snake_case): `{ message_id, chat, from, text, caption, entities[], photo[], document, voice, audio, reply_to_message, … }`. `reply_to_message` is inline on replies.
|
|
69
|
+
- **`webhook`** — `{ headers, body }`. The provider lives in `headers['x-github-event']`, `headers['x-intercom-topic']`, etc. Full event payload is `body` (parsed JSON when possible). `text` is a short summary; always narrow on `body` for real routing.
|
|
69
70
|
|
|
70
71
|
Use `payload` for anything the envelope doesn't surface — mentions, reply chains, embeds, entities.
|
|
71
72
|
|
|
@@ -75,15 +76,18 @@ Derive from `payload`. Bot id per station is cached in `$METRO_STATE_DIR/bot-ids
|
|
|
75
76
|
|
|
76
77
|
- **discord** — DM when `payload.guildId == null`; otherwise pinged when `payload.mentions.users.includes(<bot-id>)`.
|
|
77
78
|
- **telegram** — DM when `payload.chat.type === 'private'`; otherwise pinged when any entity in `payload.entities` (or `caption_entities`) is `{type:"mention"}` matching `@<bot-username>` or `{type:"text_mention", user:{id:<bot-id>}}`.
|
|
79
|
+
- **webhook** — every POST is "for you" by design (it's an endpoint you registered). Route on `payload.headers['x-github-event']` / `x-intercom-topic` etc. to decide which provider event you're handling.
|
|
78
80
|
|
|
79
|
-
Default: only reply on DM or ping; otherwise stay silent or `metro react` to ack.
|
|
81
|
+
Default for chat: only reply on DM or ping; otherwise stay silent or `metro react` to ack. Webhooks have no "ack" mechanism — just consume the event.
|
|
80
82
|
|
|
81
83
|
Both `from` and `to` are **participant URIs** (the conversation context lives in `line`):
|
|
82
84
|
- `metro://<station>/user/<id>` — a person on a chat platform
|
|
83
|
-
- `metro://claude
|
|
85
|
+
- `metro://claude/user/<orgId>` — a Claude Code agent (orgId = stable Anthropic-account UUID, same across devices for the same account)
|
|
86
|
+
- `metro://codex/user/<accountId>` — a Codex agent (accountId = stable ChatGPT-account UUID, same across devices)
|
|
87
|
+
- `metro://webhook/<endpoint-id>` — a webhook endpoint (line + `from` are the same — no HTTP-side user identity)
|
|
84
88
|
- `metro://<station>/<channelId>` — a channel (used as `to` for fresh sends to a group, where no single recipient)
|
|
85
89
|
|
|
86
|
-
When **you** send via `metro send`/`reply`/`edit`/`react`, metro auto-stamps `from = metro://claude/
|
|
90
|
+
When **you** send via `metro send`/`reply`/`edit`/`react`, metro auto-stamps `from = metro://claude/user/<orgId>` (when `$CLAUDECODE` is set; resolved from `claude auth status --json`) or `metro://codex/user/<accountId>` (from `$METRO_CODEX_RC` / `$CODEX_HOME`; resolved from `$CODEX_HOME/auth.json`). Switching accounts via `claude auth login` / `codex login` flips the id on the next event (within ~5 s for the daemon). Override with `--from=<uri>` or `$METRO_FROM`. When replying/reacting, `to` is automatically the original sender (looked up via the universal id).
|
|
87
91
|
|
|
88
92
|
The `id` is the **canonical handle** for that message across all stations — store it if you want to refer back to it later.
|
|
89
93
|
|
|
@@ -111,7 +115,10 @@ All take positional args (no `--to=`/`--text=` flags). Append `--json` to any fo
|
|
|
111
115
|
| Reaction (empty emoji clears) | `metro react <line> <messageId> <emoji>` |
|
|
112
116
|
| Download `[image]` attachments → paths | `metro download <line> <messageId> [--out=<dir>]` |
|
|
113
117
|
| Recent channel history (Discord only) | `metro fetch <line> [--limit=20]` |
|
|
114
|
-
| Ping another agent (cross-agent line) | `metro send metro://claude/<
|
|
118
|
+
| Ping another agent (cross-agent line) | `metro send metro://claude/<agent-id>/<session-id> <text> [--from=<line>]` |
|
|
119
|
+
| Register webhook endpoint | `metro webhook add <label> [--secret=<hmac-secret>]` |
|
|
120
|
+
| List / remove webhook endpoints | `metro webhook list` · `metro webhook remove <id>` |
|
|
121
|
+
| Configure Cloudflare named tunnel | `metro tunnel setup <tunnel-name> <hostname>` |
|
|
115
122
|
|
|
116
123
|
`reply` / `send` / `edit` accept multi-line text via stdin (heredoc).
|
|
117
124
|
|
|
@@ -152,8 +159,9 @@ Limits / quirks:
|
|
|
152
159
|
|------------|-------------------------------------------|--------------------------------------|
|
|
153
160
|
| `discord` | `metro://discord/<channel-id>` | `metro://discord/1234567890` |
|
|
154
161
|
| `telegram` | `metro://telegram/<chat-id>[/<topic-id>]` | `metro://telegram/-1001234567890/42` |
|
|
155
|
-
| `claude` | `metro://claude/<
|
|
156
|
-
| `codex` | `metro://codex/<
|
|
162
|
+
| `claude` | `metro://claude/<agent-id>/<session-id>` | `metro://claude/9bfc7af0-…/50b00d11-…` |
|
|
163
|
+
| `codex` | `metro://codex/<agent-id>/<session-id>` | `metro://codex/8119ecb1-…/01997d4b-…` |
|
|
164
|
+
| `webhook` | `metro://webhook/<endpoint-id>` | `metro://webhook/fwaCgTKJuLAjS2K0` |
|
|
157
165
|
|
|
158
166
|
The `messageId` is **not** part of the URI — it's a separate positional arg for `reply` / `edit` / `react` / `download`.
|
|
159
167
|
|
|
@@ -174,8 +182,8 @@ When an event's `text` contains `[image]`:
|
|
|
174
182
|
Both agents can post to each other's "agent line":
|
|
175
183
|
|
|
176
184
|
```bash
|
|
177
|
-
metro send metro://claude/
|
|
178
|
-
metro send metro://codex/
|
|
185
|
+
metro send metro://claude/9bfc7af0-…/50b00d11-… "build green, ready to ship"
|
|
186
|
+
metro send metro://codex/8119ecb1-…/01997d4b-… "build green" --from=metro://claude/user/9bfc7af0-… # override sender
|
|
179
187
|
```
|
|
180
188
|
|
|
181
189
|
The daemon re-emits the post on its stdout stream (and pushes via codex-rc if configured), so the peer agent sees a `{"kind":"notification",...}` event. Requires the metro daemon to be running on the machine — agent-line sends error with `metro daemon is not running` otherwise.
|
|
@@ -187,7 +195,7 @@ The daemon re-emits the post on its stdout stream (and pushes via codex-rc if co
|
|
|
187
195
|
- `metro history` — universal message log (every inbound + outbound + notification across all stations). Newest first. Filters:
|
|
188
196
|
- `--limit=N` (default 50)
|
|
189
197
|
- `--line=<metro://…>` — only this conversation
|
|
190
|
-
- `--station=<discord|telegram|claude|codex>`
|
|
198
|
+
- `--station=<discord|telegram|claude|codex|webhook>`
|
|
191
199
|
- `--kind=<inbound|outbound|edit|react|notification>`
|
|
192
200
|
- `--from=<sender>`
|
|
193
201
|
- `--text=<substring>`
|