agent-theatre 1.3.0 → 1.3.2
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/cli.js +51 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +51 -11
- package/dist/index.js.map +1 -1
- package/hooks/agent-theatre-hook.mjs +9 -7
- package/package.json +1 -1
- package/web/assets/browserAll-eGDH56DF.js +2 -0
- package/web/assets/{browserAll-DMCo76hh.js.map → browserAll-eGDH56DF.js.map} +1 -1
- package/web/assets/{index-BI2vZjgg.css → index-CWrWoYA0.css} +1 -1
- package/web/assets/{index-BULZyIph.js → index-HehktIj0.js} +63 -63
- package/web/assets/index-HehktIj0.js.map +1 -0
- package/web/assets/webworkerAll-2M7wVkAJ.js +2 -0
- package/web/assets/{webworkerAll-CrSrPeea.js.map → webworkerAll-2M7wVkAJ.js.map} +1 -1
- package/web/index.html +2 -2
- package/web/assets/browserAll-DMCo76hh.js +0 -2
- package/web/assets/index-BULZyIph.js.map +0 -1
- package/web/assets/webworkerAll-CrSrPeea.js +0 -2
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ declare class Theatre {
|
|
|
28
28
|
private signal;
|
|
29
29
|
/** The most recent Claude Code session that spoke to this stage; what a prompt from the stage resumes. */
|
|
30
30
|
private lastSessionId;
|
|
31
|
+
/** The most recent cwd of a Claude Code session that is NOT this repo (so an empty stage can point the person there). */
|
|
32
|
+
private elsewhere;
|
|
31
33
|
private timer;
|
|
32
34
|
port: number;
|
|
33
35
|
constructor(opts: TheatreOptions);
|
package/dist/index.js
CHANGED
|
@@ -54,7 +54,8 @@ var INSTALL = /\b(npm\s+(i|install|ci)|pnpm\s+(i|install|add)|yarn(\s+add|\s+ins
|
|
|
54
54
|
var SEARCH = /\b(grep|rg|ag|ack|find|fd|locate)\b/;
|
|
55
55
|
var READ = /^\s*(cat|head|tail|less|more|bat|wc|stat|jq)\b/;
|
|
56
56
|
function argv0Of(command) {
|
|
57
|
-
const
|
|
57
|
+
const segments = command.split(/\s*(?:&&|\|\||;|\|)\s*/);
|
|
58
|
+
const first = (segments.find((s) => !/^\s*(\[|test\b|!)/.test(s)) ?? segments[0] ?? "").replace(/^\s*!\s*/, "");
|
|
58
59
|
const toks = first.trim().split(/\s+/).map((t) => t.replace(/^[({]+/, "")).filter(Boolean);
|
|
59
60
|
let i = 0;
|
|
60
61
|
while (i < toks.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(toks[i])) i++;
|
|
@@ -1195,6 +1196,8 @@ var Theatre = class {
|
|
|
1195
1196
|
signal = "ok";
|
|
1196
1197
|
/** The most recent Claude Code session that spoke to this stage; what a prompt from the stage resumes. */
|
|
1197
1198
|
lastSessionId = null;
|
|
1199
|
+
/** The most recent cwd of a Claude Code session that is NOT this repo (so an empty stage can point the person there). */
|
|
1200
|
+
elsewhere = null;
|
|
1198
1201
|
timer = null;
|
|
1199
1202
|
port = 0;
|
|
1200
1203
|
routes() {
|
|
@@ -1207,7 +1210,13 @@ var Theatre = class {
|
|
|
1207
1210
|
return c.body(null, 204);
|
|
1208
1211
|
}
|
|
1209
1212
|
const cwd = typeof payload?.cwd === "string" ? payload.cwd : "";
|
|
1210
|
-
if (cwd && !isWithin(cwd, this.opts.cwd))
|
|
1213
|
+
if (cwd && !isWithin(cwd, this.opts.cwd)) {
|
|
1214
|
+
if (this.elsewhere !== cwd) {
|
|
1215
|
+
this.elsewhere = cwd;
|
|
1216
|
+
this.broadcast({ type: "elsewhere", cwd });
|
|
1217
|
+
}
|
|
1218
|
+
return c.body(null, 204);
|
|
1219
|
+
}
|
|
1211
1220
|
const sid = payload.session_id;
|
|
1212
1221
|
if (typeof sid === "string" && sid) this.lastSessionId = sid;
|
|
1213
1222
|
const { events } = hookPayloadToEvents(payload, { home: homedir2() });
|
|
@@ -1220,7 +1229,7 @@ var Theatre = class {
|
|
|
1220
1229
|
for (const [k, v] of Object.entries(cors)) c.res.headers.set(k, v);
|
|
1221
1230
|
});
|
|
1222
1231
|
a.options("/api/*", (c) => c.body(null, 204, cors));
|
|
1223
|
-
a.get("/api/scene", (c) => c.json(
|
|
1232
|
+
a.get("/api/scene", (c) => c.json(this.scene()));
|
|
1224
1233
|
a.get("/api/sessions", (c) => c.json(listSessions(this.opts.cwd).map((s) => ({ id: s.id, title: s.title, mtime: s.mtime, bytes: s.bytes }))));
|
|
1225
1234
|
a.get("/api/replay/:id", (c) => {
|
|
1226
1235
|
const id = c.req.param("id");
|
|
@@ -1306,7 +1315,26 @@ var Theatre = class {
|
|
|
1306
1315
|
this.port = typeof addr === "object" && addr ? addr.port : port;
|
|
1307
1316
|
const home = this.opts.home ?? join2(homedir2(), ".agent-theatre");
|
|
1308
1317
|
mkdirSync(home, { recursive: true });
|
|
1318
|
+
try {
|
|
1319
|
+
const old = parseInt(readFileSync2(join2(home, "port"), "utf8").trim(), 10);
|
|
1320
|
+
if (old > 0 && old !== this.port && portAlive(old) && !existsSync(join2(home, "ports", String(old)))) {
|
|
1321
|
+
mkdirSync(join2(home, "ports"), { recursive: true });
|
|
1322
|
+
writeFileSync(join2(home, "ports", String(old)), "(started by an older build)");
|
|
1323
|
+
}
|
|
1324
|
+
} catch {
|
|
1325
|
+
}
|
|
1309
1326
|
writeFileSync(join2(home, "port"), String(this.port));
|
|
1327
|
+
mkdirSync(join2(home, "ports"), { recursive: true });
|
|
1328
|
+
for (const f of readdirSync2(join2(home, "ports"))) {
|
|
1329
|
+
const n = parseInt(f, 10);
|
|
1330
|
+
if (n > 0 && n !== this.port && !portAlive(n)) {
|
|
1331
|
+
try {
|
|
1332
|
+
rmSync(join2(home, "ports", f));
|
|
1333
|
+
} catch {
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
writeFileSync(join2(home, "ports", String(this.port)), this.opts.cwd);
|
|
1310
1338
|
const lostAfter = this.opts.lostAfterMs ?? 9e4;
|
|
1311
1339
|
this.timer = setInterval(() => {
|
|
1312
1340
|
const l = this.live;
|
|
@@ -1325,13 +1353,17 @@ var Theatre = class {
|
|
|
1325
1353
|
if (readFileSync2(join2(home, "port"), "utf8").trim() === String(this.port)) rmSync(join2(home, "port"));
|
|
1326
1354
|
} catch {
|
|
1327
1355
|
}
|
|
1356
|
+
try {
|
|
1357
|
+
rmSync(join2(home, "ports", String(this.port)));
|
|
1358
|
+
} catch {
|
|
1359
|
+
}
|
|
1328
1360
|
for (const ws of this.wss?.clients ?? []) ws.close();
|
|
1329
1361
|
this.wss?.close();
|
|
1330
1362
|
await new Promise((res) => this.server ? this.server.close(() => res()) : res());
|
|
1331
1363
|
}
|
|
1332
1364
|
/** The live scene as /api/scene returns it. */
|
|
1333
1365
|
scene() {
|
|
1334
|
-
return { mode: "live", repoId: this.opts.repoId, world: this.live.world, events: this.live.events, beats: this.live.beats, signal: this.signal };
|
|
1366
|
+
return { mode: "live", repoId: this.opts.repoId, cwd: this.opts.cwd, elsewhere: this.elsewhere, world: this.live.world, events: this.live.events, beats: this.live.beats, signal: this.signal };
|
|
1335
1367
|
}
|
|
1336
1368
|
get url() {
|
|
1337
1369
|
return `http://127.0.0.1:${this.port}/`;
|
|
@@ -1368,6 +1400,12 @@ function claudeBinary() {
|
|
|
1368
1400
|
}
|
|
1369
1401
|
return null;
|
|
1370
1402
|
}
|
|
1403
|
+
function portAlive(port) {
|
|
1404
|
+
const r = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], { encoding: "utf8", timeout: 2e3 });
|
|
1405
|
+
if (r.status === 0 && r.stdout.trim()) return true;
|
|
1406
|
+
if (r.error) return true;
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1371
1409
|
|
|
1372
1410
|
// src/scan.ts
|
|
1373
1411
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
@@ -1502,20 +1540,22 @@ function hasOurHooks(existing) {
|
|
|
1502
1540
|
// src/hooks/script.ts
|
|
1503
1541
|
var HOOK_SCRIPT = `#!/usr/bin/env node
|
|
1504
1542
|
// Agent Theatre hook \u2014 forwards Claude Code events to the local theatre. Never blocks the agent.
|
|
1505
|
-
import { readFileSync } from "node:fs";
|
|
1543
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
1506
1544
|
import { homedir } from "node:os";
|
|
1507
1545
|
import { join } from "node:path";
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
if (
|
|
1546
|
+
// Every running stage registers itself in ports/<port>; the event goes to all of them (browser, VS Code, a second repo\u2026).
|
|
1547
|
+
const home = process.env.AGENT_THEATRE_HOME || join(homedir(), ".agent-theatre");
|
|
1548
|
+
const ports = new Set();
|
|
1549
|
+
try { for (const f of readdirSync(join(home, "ports"))) { const n = parseInt(f, 10); if (n > 0) ports.add(n); } } catch { /* no registry yet */ }
|
|
1550
|
+
try { const n = parseInt(readFileSync(join(home, "port"), "utf8").trim(), 10); if (n > 0) ports.add(n); } catch { /* no port file */ }
|
|
1551
|
+
if (!ports.size) process.exit(0);
|
|
1512
1552
|
let body = "";
|
|
1513
1553
|
process.stdin.setEncoding("utf8");
|
|
1514
1554
|
process.stdin.on("data", (c) => { body += c; if (body.length > 2_000_000) body = body.slice(0, 2_000_000); });
|
|
1515
1555
|
process.stdin.on("end", async () => {
|
|
1516
1556
|
try {
|
|
1517
|
-
await fetch(\`http://127.0.0.1:\${port}/hook\`, { method: "POST", headers: { "content-type": "application/json" }, body, signal: AbortSignal.timeout(300) });
|
|
1518
|
-
} catch { /* theatre not running or slow: that is fine */ }
|
|
1557
|
+
await Promise.allSettled([...ports].map((port) => fetch(\`http://127.0.0.1:\${port}/hook\`, { method: "POST", headers: { "content-type": "application/json" }, body, signal: AbortSignal.timeout(300) })));
|
|
1558
|
+
} catch { /* a theatre not running or slow: that is fine */ }
|
|
1519
1559
|
process.exit(0);
|
|
1520
1560
|
});
|
|
1521
1561
|
setTimeout(() => process.exit(0), 600).unref();
|