agent-theatre 1.1.3 → 1.2.0

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/index.d.ts CHANGED
@@ -26,6 +26,8 @@ declare class Theatre {
26
26
  private wss;
27
27
  private live;
28
28
  private signal;
29
+ /** The most recent Claude Code session that spoke to this stage; what a prompt from the stage resumes. */
30
+ private lastSessionId;
29
31
  private timer;
30
32
  port: number;
31
33
  constructor(opts: TheatreOptions);
@@ -38,6 +40,8 @@ declare class Theatre {
38
40
  get url(): string;
39
41
  get name(): string;
40
42
  }
43
+ /** Claude Code 2.x on PATH, or the binary the VS Code extension ships; null if neither. Overridable with AGENT_THEATRE_CLAUDE. */
44
+ declare function claudeBinary(): string | null;
41
45
 
42
46
  /** The repo's file tree as FileNodes. Counts lines only; never keeps contents. Bounded for huge repos. */
43
47
  declare function scanRepo(root: string, opts?: {
@@ -81,4 +85,4 @@ declare function hooksStatus(paths?: InstallPaths): {
81
85
  settings: string;
82
86
  };
83
87
 
84
- export { type InstallPaths, type SessionInfo, Theatre, type TheatreOptions, hooksStatus, installHooks, installPaths, listSessions, repoIdFor, scanRepo, sessionTitle, uninstallHooks };
88
+ export { type InstallPaths, type SessionInfo, Theatre, type TheatreOptions, claudeBinary, hooksStatus, installHooks, installPaths, listSessions, repoIdFor, scanRepo, sessionTitle, uninstallHooks };
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // src/server.ts
2
2
  import { createServer } from "http";
3
- import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync, realpathSync } from "fs";
3
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, readdirSync as readdirSync2, rmSync, writeFileSync, realpathSync } from "fs";
4
4
  import { homedir as homedir2 } from "os";
5
5
  import { join as join2, basename as basename2, resolve, sep } from "path";
6
+ import { spawn, spawnSync } from "child_process";
6
7
  import { Hono } from "hono";
7
8
  import { getRequestListener } from "@hono/node-server";
8
9
  import { WebSocketServer } from "ws";
@@ -1186,6 +1187,8 @@ var Theatre = class {
1186
1187
  wss = null;
1187
1188
  live;
1188
1189
  signal = "ok";
1190
+ /** The most recent Claude Code session that spoke to this stage; what a prompt from the stage resumes. */
1191
+ lastSessionId = null;
1189
1192
  timer = null;
1190
1193
  port = 0;
1191
1194
  routes() {
@@ -1199,6 +1202,8 @@ var Theatre = class {
1199
1202
  }
1200
1203
  const cwd = typeof payload?.cwd === "string" ? payload.cwd : "";
1201
1204
  if (cwd && !isWithin(cwd, this.opts.cwd)) return c.body(null, 204);
1205
+ const sid = payload.session_id;
1206
+ if (typeof sid === "string" && sid) this.lastSessionId = sid;
1202
1207
  const { events } = hookPayloadToEvents(payload, { home: homedir2() });
1203
1208
  if (events.length) this.ingest(events);
1204
1209
  return c.body(null, 204);
@@ -1223,6 +1228,18 @@ var Theatre = class {
1223
1228
  const beats = direct(events, world);
1224
1229
  return c.json({ mode: "replay", repoId: this.opts.repoId, title: s.title, world, events, beats, warnings: warnings.length });
1225
1230
  });
1231
+ a.get("/api/prompt", (c) => c.json({ available: !!claudeBinary() && !!this.lastSessionId, session: this.lastSessionId }));
1232
+ a.post("/api/prompt", async (c) => {
1233
+ const body = await c.req.json().catch(() => ({}));
1234
+ const text = typeof body.text === "string" ? body.text.trim() : "";
1235
+ const bin = claudeBinary();
1236
+ if (!text) return c.json({ ok: false, error: "empty prompt" }, 400);
1237
+ if (!bin) return c.json({ ok: false, error: "Claude Code 2.x not found on PATH" }, 503);
1238
+ if (!this.lastSessionId) return c.json({ ok: false, error: "no session to resume yet" }, 409);
1239
+ const child = spawn(bin, ["-p", "--resume", this.lastSessionId, text], { cwd: this.opts.cwd, stdio: "ignore", detached: true, env: process.env });
1240
+ child.unref();
1241
+ return c.json({ ok: true, session: this.lastSessionId });
1242
+ });
1226
1243
  a.get("/healthz", (c) => c.json({ ok: true, port: this.port, events: this.live.events.length }));
1227
1244
  if (this.opts.webDir) {
1228
1245
  const dir = this.opts.webDir;
@@ -1316,9 +1333,26 @@ function isWithin(dir, root) {
1316
1333
  const d = norm3(dir), r = norm3(root);
1317
1334
  return d === r || d.startsWith(r.endsWith(sep) ? r : r + sep);
1318
1335
  }
1336
+ function claudeBinary() {
1337
+ const cands = [process.env.AGENT_THEATRE_CLAUDE, "claude"].filter((x) => !!x);
1338
+ try {
1339
+ const ext = join2(homedir2(), ".vscode", "extensions");
1340
+ for (const d of (existsSync(ext) ? readdirSync2(ext) : []).filter((n) => n.startsWith("anthropic.claude-code-")).sort().reverse()) cands.push(join2(ext, d, "resources", "native-binary", "claude"));
1341
+ } catch {
1342
+ }
1343
+ for (const c of cands) {
1344
+ try {
1345
+ const r = spawnSync(c, ["--version"], { encoding: "utf8", timeout: 4e3 });
1346
+ const m = /^(\d+)\./.exec(r.stdout.trim());
1347
+ if (r.status === 0 && m && Number(m[1]) >= 2) return c;
1348
+ } catch {
1349
+ }
1350
+ }
1351
+ return null;
1352
+ }
1319
1353
 
1320
1354
  // src/scan.ts
1321
- import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
1355
+ import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
1322
1356
  import { join as join3, relative } from "path";
1323
1357
  function scanRepo(root, opts = {}) {
1324
1358
  const maxFiles = opts.maxFiles ?? 2e4, maxBytes = opts.maxBytesForLoc ?? 512e3;
@@ -1327,7 +1361,7 @@ function scanRepo(root, opts = {}) {
1327
1361
  if (files.length >= maxFiles) return;
1328
1362
  let ents;
1329
1363
  try {
1330
- ents = readdirSync2(dir, { withFileTypes: true });
1364
+ ents = readdirSync3(dir, { withFileTypes: true });
1331
1365
  } catch {
1332
1366
  return;
1333
1367
  }
@@ -1509,6 +1543,7 @@ function hooksStatus(paths = installPaths()) {
1509
1543
  }
1510
1544
  export {
1511
1545
  Theatre,
1546
+ claudeBinary,
1512
1547
  hooksStatus,
1513
1548
  installHooks,
1514
1549
  installPaths,