@stage-labs/metro 0.1.0-beta.8 → 0.1.0-beta.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.
package/README.md CHANGED
@@ -174,7 +174,7 @@ All commands accept `--json`. `reply` / `send` / `edit` read multi-line `<text>`
174
174
  - `agent-registry.json` — every `(station, agent-id, sessions[])` tuple metro has seen; surfaced under each agent row in `metro stations`
175
175
  - `stations/codex/session-id` — current codex-rc thread id (daemon writes on handshake; CLI processes read for `metro://codex/<agent-id>/<session>`)
176
176
  - `webhooks.json` — registered HTTP receive endpoints (id, label, optional shared secret)
177
- - `tunnel.json` — Cloudflare named-tunnel config (`{name, hostname}`); when present, the daemon spawns `cloudflared tunnel run`
177
+ - `tunnel.json` — Cloudflare named-tunnel config (`{name, hostname}`); when present, the daemon spawns `cloudflared tunnel run`. The token is resolved via `cloudflared tunnel token <name>` and passed through as `TUNNEL_TOKEN`, so the per-tunnel credentials JSON at `~/.cloudflared/<id>.json` is not required (the named-form spawn is the fallback when the token call fails)
178
178
  - `.tail-lock` — dispatcher pid
179
179
  - `metro.sock` — daemon IPC socket
180
180
  - `telegram-offset.json` — last processed update id
package/dist/tunnel.js CHANGED
@@ -1,5 +1,5 @@
1
- /** Cloudflared named-tunnel manager. Spawns `cloudflared tunnel run` from `tunnel.json` for stable webhook URLs. */
2
- import { spawn } from 'node:child_process';
1
+ /** Cloudflared tunnel manager. Prefers token-from-env so a missing local credentials JSON does not block startup. */
2
+ import { spawn, spawnSync } from 'node:child_process';
3
3
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { STATE_DIR } from './paths.js';
@@ -10,11 +10,21 @@ export const loadTunnelConfig = () => existsSync(FILE)
10
10
  ? JSON.parse(readFileSync(FILE, 'utf8'))
11
11
  : null;
12
12
  export function saveTunnelConfig(c) { writeFileSync(FILE, JSON.stringify(c, null, 2)); }
13
+ /** Fetch the tunnel's auth token. Null when CLI is unavailable, not logged in, or no such tunnel. */
14
+ function fetchTunnelToken(name) {
15
+ const r = spawnSync('cloudflared', ['tunnel', 'token', name], { encoding: 'utf8' });
16
+ if (r.status !== 0)
17
+ return null;
18
+ const token = r.stdout.trim();
19
+ return token.length > 0 ? token : null;
20
+ }
13
21
  export class Tunnel {
14
22
  cfg;
15
23
  port;
16
24
  child = null;
17
25
  closed = false;
26
+ token = null;
27
+ tokenResolved = false;
18
28
  constructor(cfg, port) {
19
29
  this.cfg = cfg;
20
30
  this.port = port;
@@ -23,14 +33,21 @@ export class Tunnel {
23
33
  start() {
24
34
  if (this.closed)
25
35
  return;
26
- log.info({ name: this.cfg.name, hostname: this.cfg.hostname, port: this.port }, 'cloudflared tunnel starting');
36
+ if (!this.tokenResolved) {
37
+ this.token = fetchTunnelToken(this.cfg.name);
38
+ this.tokenResolved = true;
39
+ }
40
+ const mode = this.token ? 'token' : 'named';
41
+ log.info({ name: this.cfg.name, hostname: this.cfg.hostname, port: this.port, mode }, 'cloudflared tunnel starting');
27
42
  /** `--no-autoupdate` is a global cloudflared flag — must come before the `tunnel` subcommand. */
28
- this.child = spawn('cloudflared', [
29
- '--no-autoupdate',
30
- 'tunnel', 'run',
31
- '--url', `http://127.0.0.1:${this.port}`,
32
- this.cfg.name,
33
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
43
+ const args = ['--no-autoupdate', 'tunnel', 'run', '--url', `http://127.0.0.1:${this.port}`];
44
+ /** Token form resolves the tunnel from TUNNEL_TOKEN so the trailing name arg must be omitted. */
45
+ if (!this.token)
46
+ args.push(this.cfg.name);
47
+ const env = this.token
48
+ ? { ...process.env, TUNNEL_TOKEN: this.token }
49
+ : process.env;
50
+ this.child = spawn('cloudflared', args, { stdio: ['ignore', 'pipe', 'pipe'], env });
34
51
  this.child.stderr?.on('data', d => log.debug({ cloudflared: d.toString().trim() }, 'cloudflared'));
35
52
  this.child.on('exit', code => {
36
53
  this.child = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage-labs/metro",
3
- "version": "0.1.0-beta.8",
3
+ "version": "0.1.0-beta.9",
4
4
  "description": "Live JSON stream of Telegram + Discord messages for your local Claude Code / Codex session. The agent launches metro; metro emits inbounds on stdout and accepts replies via CLI subcommands.",
5
5
  "license": "MIT",
6
6
  "repository": {