@stage-labs/metro 0.1.0-beta.33 → 0.1.0-beta.34
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/dist/control.js +17 -2
- package/dist/serve.js +12 -8
- package/package.json +1 -1
- package/runtime/runtime.json +1 -1
package/dist/control.js
CHANGED
|
@@ -3,6 +3,12 @@ import { existsSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, wri
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
const STATE = () => join(homedir(), '.metro');
|
|
6
|
+
export function serveStateDir() {
|
|
7
|
+
const xdg = process.env.XDG_CACHE_HOME?.trim();
|
|
8
|
+
const base = xdg === undefined || xdg === '' ? join(homedir(), '.cache') : xdg;
|
|
9
|
+
return join(base, 'metro', 'serve');
|
|
10
|
+
}
|
|
11
|
+
const serveLockPath = () => join(serveStateDir(), '.tail-lock');
|
|
6
12
|
const pidPath = (agentId) => join(STATE(), `run-${agentId}.pid`);
|
|
7
13
|
export const logPath = (agentId) => join(STATE(), 'logs', `${agentId}.log`);
|
|
8
14
|
function ensureStateDirs() {
|
|
@@ -83,10 +89,10 @@ function commandOf(pid) {
|
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
const looksLikeMetro = (command) => command.includes('server.ts') || command.includes('metro');
|
|
86
|
-
|
|
92
|
+
function holderOf(lockFile) {
|
|
87
93
|
let pid;
|
|
88
94
|
try {
|
|
89
|
-
pid = Number(readFileSync(
|
|
95
|
+
pid = Number(readFileSync(lockFile, 'utf8').trim());
|
|
90
96
|
}
|
|
91
97
|
catch {
|
|
92
98
|
return null;
|
|
@@ -95,6 +101,8 @@ export function lockedBy() {
|
|
|
95
101
|
return null;
|
|
96
102
|
return looksLikeMetro(commandOf(pid)) ? pid : null;
|
|
97
103
|
}
|
|
104
|
+
export const lockedBy = () => holderOf(lockPath());
|
|
105
|
+
export const serveLockedBy = () => holderOf(serveLockPath());
|
|
98
106
|
function trackedAgents(agentId) {
|
|
99
107
|
const tracked = new Set();
|
|
100
108
|
if (agentId !== undefined)
|
|
@@ -125,10 +133,17 @@ export async function stopAll(agentId, waitMs = 10_000) {
|
|
|
125
133
|
}
|
|
126
134
|
const holder = lockedBy();
|
|
127
135
|
if (holder !== null && !seen.has(holder)) {
|
|
136
|
+
seen.add(holder);
|
|
128
137
|
await stopPid(holder, waitMs);
|
|
129
138
|
rmSync(lockPath(), { force: true });
|
|
130
139
|
stopped.push({ pid: holder, via: 'the machine lock' });
|
|
131
140
|
}
|
|
141
|
+
const served = serveLockedBy();
|
|
142
|
+
if (served !== null && !seen.has(served)) {
|
|
143
|
+
await stopPid(served, waitMs);
|
|
144
|
+
rmSync(serveLockPath(), { force: true });
|
|
145
|
+
stopped.push({ pid: served, via: 'metro serve' });
|
|
146
|
+
}
|
|
132
147
|
return stopped;
|
|
133
148
|
}
|
|
134
149
|
export function tail(agentId, follow) {
|
package/dist/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
2
|
import { join } from 'node:path';
|
|
3
|
+
import { serveLockedBy, serveStateDir } from './control.js';
|
|
4
4
|
import { findBun, localPort, runtimeDir, SERVER_ENTRY, spawnPlan, } from './runtime.js';
|
|
5
5
|
const SCRUBBED = new Set(['METRO_RUN_TOKEN', 'METRO_AGENT', 'DATABASE_URL']);
|
|
6
6
|
const PORT_FLAG = /^--port=(.*)$/;
|
|
@@ -38,13 +38,13 @@ export function findCloudflared() {
|
|
|
38
38
|
const found = spawnSync('cloudflared', ['--version'], { stdio: 'ignore' });
|
|
39
39
|
if (found.error !== undefined || found.status !== 0)
|
|
40
40
|
throw new Error('metro serve --tunnel needs cloudflared, which is not on PATH.\n' +
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
'Debian/Ubuntu (it is not in the distro repos; this adds Cloudflare\'s signed one):\n' +
|
|
42
|
+
' sudo mkdir -p --mode=0755 /usr/share/keyrings\n' +
|
|
43
|
+
' curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null\n' +
|
|
44
|
+
" echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list\n" +
|
|
45
|
+
' sudo apt-get update && sudo apt-get install cloudflared\n' +
|
|
46
|
+
'macOS: brew install cloudflared\n' +
|
|
47
|
+
'Others: https://pkg.cloudflare.com/');
|
|
48
48
|
}
|
|
49
49
|
export function servePlan(opts) {
|
|
50
50
|
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !SCRUBBED.has(key)));
|
|
@@ -65,6 +65,10 @@ export function servePlan(opts) {
|
|
|
65
65
|
}
|
|
66
66
|
export function serve(argv) {
|
|
67
67
|
const { port, tunnel } = parseServeArgs(argv);
|
|
68
|
+
const running = serveLockedBy();
|
|
69
|
+
if (running !== null)
|
|
70
|
+
throw new Error(`a metro serve daemon is already running on this machine (pid ${String(running)}). ` +
|
|
71
|
+
'Stop it first: metro stop');
|
|
68
72
|
if (tunnel)
|
|
69
73
|
findCloudflared();
|
|
70
74
|
const plan = servePlan({ dir: runtimeDir(), port, tunnel });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stage-labs/metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.34",
|
|
4
4
|
"description": "The metro command line. Sign in once per machine, then hand your MCP connector list to Claude Code without the credentials touching disk, argv or shell history.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/runtime/runtime.json
CHANGED