@staff0rd/assist 0.191.2 → 0.192.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.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.191.2",
9
+ version: "0.192.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -49,10 +49,12 @@ var package_default = {
49
49
  "is-wsl": "^3.1.0",
50
50
  minimatch: "^10.1.1",
51
51
  "node-notifier": "^10.0.1",
52
+ "node-pty": "^1.1.0",
52
53
  semver: "^7.7.3",
53
54
  "shell-quote": "^1.8.3",
54
55
  "ts-morph": "^27.0.2",
55
56
  typescript: "^5.9.3",
57
+ ws: "^8.20.0",
56
58
  yaml: "^2.8.2",
57
59
  zod: "^4.3.6"
58
60
  },
@@ -70,7 +72,10 @@ var package_default = {
70
72
  "@types/react-dom": "^19.2.3",
71
73
  "@types/semver": "^7.7.1",
72
74
  "@types/shell-quote": "^1.7.5",
75
+ "@types/ws": "^8.18.1",
73
76
  "@vitest/coverage-v8": "^4.1.2",
77
+ "@xterm/addon-fit": "^0.11.0",
78
+ "@xterm/xterm": "^6.0.0",
74
79
  esbuild: "^0.27.3",
75
80
  jotai: "^2.18.0",
76
81
  jscpd: "^4.0.5",
@@ -1175,7 +1180,7 @@ function getTranscriptConfig() {
1175
1180
  // src/commands/backlog/spawnClaude.ts
1176
1181
  function spawnClaude(prompt, options2 = {}) {
1177
1182
  const config = loadConfig();
1178
- const finalPrompt = config.caveman ? `/caveman
1183
+ const finalPrompt = config.caveman ? `/caveman:caveman
1179
1184
 
1180
1185
  ${prompt}` : prompt;
1181
1186
  const args = [finalPrompt];
@@ -1573,20 +1578,20 @@ function createBundleHandler(importMetaUrl, bundlePath) {
1573
1578
  res.end(cache);
1574
1579
  };
1575
1580
  }
1576
- function createHtmlHandler(getHtml3) {
1581
+ function createHtmlHandler(getHtml4) {
1577
1582
  return (_req, res) => {
1578
1583
  res.writeHead(200, { "Content-Type": "text/html" });
1579
- res.end(getHtml3());
1584
+ res.end(getHtml4());
1580
1585
  };
1581
1586
  }
1582
1587
  function parseRoute(req, port) {
1583
1588
  const url = new URL(req.url ?? "/", `http://localhost:${port}`);
1584
1589
  return { method: req.method ?? "GET", pathname: url.pathname };
1585
1590
  }
1586
- function createRouteHandler(routes3) {
1591
+ function createRouteHandler(routes4) {
1587
1592
  return async (req, res, port) => {
1588
1593
  const { method, pathname } = parseRoute(req, port);
1589
- const handler = routes3[`${method} ${pathname}`];
1594
+ const handler = routes4[`${method} ${pathname}`];
1590
1595
  if (handler) {
1591
1596
  await handler(req, res);
1592
1597
  return;
@@ -1605,6 +1610,25 @@ function startWebServer(label2, port, handler) {
1605
1610
  console.log(chalk16.dim("Press Ctrl+C to stop"));
1606
1611
  exec(`open ${url}`);
1607
1612
  });
1613
+ return server;
1614
+ }
1615
+
1616
+ // src/shared/createFallbackHandler.ts
1617
+ function createFallbackHandler(routes4, htmlHandler3, extra) {
1618
+ const baseHandler = createRouteHandler(routes4);
1619
+ return async (req, res, port) => {
1620
+ const { method, pathname } = parseRoute(req, port);
1621
+ if (extra && await extra(req, res, pathname)) return;
1622
+ if (routes4[`${method} ${pathname}`]) {
1623
+ await baseHandler(req, res, port);
1624
+ return;
1625
+ }
1626
+ if (method === "GET" && !pathname.startsWith("/api/")) {
1627
+ await htmlHandler3(req, res);
1628
+ return;
1629
+ }
1630
+ await baseHandler(req, res, port);
1631
+ };
1608
1632
  }
1609
1633
 
1610
1634
  // src/commands/backlog/web/getHtml.ts
@@ -1711,8 +1735,9 @@ async function patchItemStatus(req, res, id) {
1711
1735
  }
1712
1736
 
1713
1737
  // src/commands/backlog/web/handleRequest.ts
1738
+ var htmlHandler = createHtmlHandler(getHtml);
1714
1739
  var routes = {
1715
- "GET /": createHtmlHandler(getHtml),
1740
+ "GET /": htmlHandler,
1716
1741
  "GET /bundle.js": createBundleHandler(
1717
1742
  import.meta.url,
1718
1743
  "commands/backlog/web/bundle.js"
@@ -1726,8 +1751,6 @@ var itemRoutes = {
1726
1751
  PATCH: (req, res, id) => patchItemStatus(req, res, id),
1727
1752
  DELETE: (_req, res, id) => deleteItem2(res, id)
1728
1753
  };
1729
- var serveHtml = createHtmlHandler(getHtml);
1730
- var baseHandler = createRouteHandler(routes);
1731
1754
  async function handleItemRoute(req, res, pathname) {
1732
1755
  const match = pathname.match(/^\/api\/items\/(\d+)$/);
1733
1756
  if (!match) return false;
@@ -1736,21 +1759,11 @@ async function handleItemRoute(req, res, pathname) {
1736
1759
  await handler(req, res, Number.parseInt(match[1], 10));
1737
1760
  return true;
1738
1761
  }
1739
- async function handleRequest(req, res, port) {
1740
- const url = new URL(req.url ?? "/", `http://localhost:${port}`);
1741
- const method = req.method ?? "GET";
1742
- const pathname = url.pathname;
1743
- if (await handleItemRoute(req, res, pathname)) return;
1744
- if (routes[`${method} ${pathname}`]) {
1745
- await baseHandler(req, res, port);
1746
- return;
1747
- }
1748
- if (method === "GET" && !pathname.startsWith("/api/")) {
1749
- await serveHtml(req, res);
1750
- return;
1751
- }
1752
- await baseHandler(req, res, port);
1753
- }
1762
+ var handleRequest = createFallbackHandler(
1763
+ routes,
1764
+ htmlHandler,
1765
+ handleItemRoute
1766
+ );
1754
1767
 
1755
1768
  // src/commands/backlog/web/index.ts
1756
1769
  async function web(options2) {
@@ -12373,6 +12386,272 @@ function screenshot(processName) {
12373
12386
  }
12374
12387
  }
12375
12388
 
12389
+ // src/commands/sessions/web/index.ts
12390
+ import { WebSocketServer } from "ws";
12391
+
12392
+ // src/commands/sessions/web/handleRequest.ts
12393
+ import { readFileSync as readFileSync34 } from "fs";
12394
+ import { createRequire as createRequire2 } from "module";
12395
+
12396
+ // src/commands/sessions/web/getHtml.ts
12397
+ function getHtml3() {
12398
+ return `<!DOCTYPE html>
12399
+ <html lang="en">
12400
+ <head>
12401
+ <meta charset="UTF-8">
12402
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
12403
+ <title>Sessions</title>
12404
+ <link rel="stylesheet" href="/xterm.css">
12405
+ <style>
12406
+ * { margin: 0; padding: 0; box-sizing: border-box; }
12407
+ html, body, #app { width: 100%; height: 100%; overflow: hidden; background: #1e1e1e; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
12408
+ ::-webkit-scrollbar { width: 6px; }
12409
+ ::-webkit-scrollbar-track { background: transparent; }
12410
+ ::-webkit-scrollbar-thumb { background: #555; border-radius: 3px; }
12411
+ ::-webkit-scrollbar-thumb:hover { background: #777; }
12412
+ </style>
12413
+ </head>
12414
+ <body>
12415
+ <div id="app"></div>
12416
+ <script src="/bundle.js"></script>
12417
+ </body>
12418
+ </html>`;
12419
+ }
12420
+
12421
+ // src/commands/sessions/web/handleRequest.ts
12422
+ var require3 = createRequire2(import.meta.url);
12423
+ function createCssHandler(packageEntry) {
12424
+ let cache;
12425
+ return (_req, res) => {
12426
+ if (!cache) {
12427
+ const resolved = require3.resolve(packageEntry);
12428
+ cache = readFileSync34(resolved, "utf-8");
12429
+ }
12430
+ res.writeHead(200, { "Content-Type": "text/css" });
12431
+ res.end(cache);
12432
+ };
12433
+ }
12434
+ var htmlHandler2 = createHtmlHandler(getHtml3);
12435
+ var routes3 = {
12436
+ "GET /": htmlHandler2,
12437
+ "GET /bundle.js": createBundleHandler(
12438
+ import.meta.url,
12439
+ "commands/sessions/web/bundle.js"
12440
+ ),
12441
+ "GET /xterm.css": createCssHandler("@xterm/xterm/css/xterm.css")
12442
+ };
12443
+ var handleRequest3 = createFallbackHandler(routes3, htmlHandler2);
12444
+
12445
+ // src/commands/sessions/web/handleSocket.ts
12446
+ function handleSocket(ws, manager) {
12447
+ manager.addClient(ws);
12448
+ ws.on("message", (msg) => {
12449
+ let data;
12450
+ try {
12451
+ data = JSON.parse(msg.toString());
12452
+ } catch {
12453
+ return;
12454
+ }
12455
+ switch (data.type) {
12456
+ case "create": {
12457
+ const id = manager.spawn(data.prompt);
12458
+ ws.send(JSON.stringify({ type: "created", sessionId: id }));
12459
+ break;
12460
+ }
12461
+ case "input":
12462
+ manager.writeToSession(data.sessionId, data.data);
12463
+ break;
12464
+ case "resize":
12465
+ manager.resizeSession(
12466
+ data.sessionId,
12467
+ data.cols,
12468
+ data.rows
12469
+ );
12470
+ break;
12471
+ case "dismiss":
12472
+ manager.dismissSession(data.sessionId);
12473
+ break;
12474
+ }
12475
+ });
12476
+ ws.on("close", () => {
12477
+ manager.removeClient(ws);
12478
+ });
12479
+ }
12480
+
12481
+ // src/commands/sessions/web/spawnClaude.ts
12482
+ import * as pty from "node-pty";
12483
+ function spawnClaude2(prompt) {
12484
+ const shell = process.platform === "win32" ? "cmd.exe" : process.env.SHELL ?? "bash";
12485
+ const args = buildArgs(prompt);
12486
+ return pty.spawn(shell, args, {
12487
+ name: "xterm-256color",
12488
+ cols: 120,
12489
+ rows: 30,
12490
+ cwd: process.cwd(),
12491
+ env: { ...process.env }
12492
+ });
12493
+ }
12494
+ function buildArgs(prompt) {
12495
+ if (process.platform === "win32") {
12496
+ return prompt ? ["/c", "claude", prompt] : ["/c", "claude"];
12497
+ }
12498
+ return prompt ? ["-c", 'exec claude "$0"', prompt] : ["-c", "claude"];
12499
+ }
12500
+
12501
+ // src/commands/sessions/web/createSession.ts
12502
+ function createSession(id, prompt) {
12503
+ return {
12504
+ id,
12505
+ name: prompt?.slice(0, 40) || `Session ${id}`,
12506
+ status: "running",
12507
+ startedAt: Date.now(),
12508
+ pty: spawnClaude2(prompt),
12509
+ scrollback: "",
12510
+ idleTimer: null,
12511
+ lastResizeAt: 0
12512
+ };
12513
+ }
12514
+
12515
+ // src/commands/sessions/web/scheduleIdle.ts
12516
+ var IDLE_MS = 3e3;
12517
+ function scheduleIdle(session, onIdle) {
12518
+ if (session.idleTimer) clearTimeout(session.idleTimer);
12519
+ if (session.status === "done") return;
12520
+ session.idleTimer = setTimeout(() => {
12521
+ if (session.status === "running") onIdle();
12522
+ }, IDLE_MS);
12523
+ }
12524
+ function clearIdle(session) {
12525
+ if (session.idleTimer) clearTimeout(session.idleTimer);
12526
+ }
12527
+
12528
+ // src/commands/sessions/web/wsBroadcast.ts
12529
+ function wsSend(ws, msg) {
12530
+ if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(msg));
12531
+ }
12532
+ function wsBroadcast(clients, msg) {
12533
+ const json = JSON.stringify(msg);
12534
+ for (const ws of clients) {
12535
+ if (ws.readyState === ws.OPEN) ws.send(json);
12536
+ }
12537
+ }
12538
+
12539
+ // src/commands/sessions/web/wirePtyEvents.ts
12540
+ var MAX_SCROLLBACK = 256 * 1024;
12541
+ var RESIZE_GRACE_MS = 500;
12542
+ function appendScrollback(session, data) {
12543
+ session.scrollback += data;
12544
+ if (session.scrollback.length > MAX_SCROLLBACK) {
12545
+ session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
12546
+ }
12547
+ }
12548
+ function wirePtyEvents(session, clients, onStatusChange) {
12549
+ session.pty.onData((data) => {
12550
+ appendScrollback(session, data);
12551
+ const isRedraw = Date.now() - session.lastResizeAt < RESIZE_GRACE_MS;
12552
+ if (!isRedraw && session.status !== "running")
12553
+ onStatusChange(session, "running");
12554
+ if (!isRedraw)
12555
+ scheduleIdle(session, () => onStatusChange(session, "waiting"));
12556
+ wsBroadcast(clients, { type: "output", sessionId: session.id, data });
12557
+ });
12558
+ session.pty.onExit(() => {
12559
+ clearIdle(session);
12560
+ onStatusChange(session, "done");
12561
+ });
12562
+ }
12563
+
12564
+ // src/commands/sessions/web/SessionManager.ts
12565
+ var SessionManager = class {
12566
+ sessions = /* @__PURE__ */ new Map();
12567
+ clients = /* @__PURE__ */ new Set();
12568
+ nextId = 1;
12569
+ addClient(ws) {
12570
+ this.clients.add(ws);
12571
+ wsSend(ws, { type: "sessions", sessions: this.listSessions() });
12572
+ for (const s of this.sessions.values()) {
12573
+ if (s.scrollback)
12574
+ wsSend(ws, { type: "output", sessionId: s.id, data: s.scrollback });
12575
+ }
12576
+ }
12577
+ removeClient(ws) {
12578
+ this.clients.delete(ws);
12579
+ }
12580
+ spawn(prompt) {
12581
+ const id = String(this.nextId++);
12582
+ const session = createSession(id, prompt);
12583
+ this.sessions.set(id, session);
12584
+ wirePtyEvents(session, this.clients, (s, status2) => {
12585
+ s.status = status2;
12586
+ this.notify();
12587
+ });
12588
+ scheduleIdle(session, () => {
12589
+ session.status = "waiting";
12590
+ this.notify();
12591
+ });
12592
+ this.notify();
12593
+ return id;
12594
+ }
12595
+ writeToSession(id, data) {
12596
+ const s = this.sessions.get(id);
12597
+ if (s && s.status !== "done") s.pty.write(data);
12598
+ }
12599
+ resizeSession(id, cols, rows) {
12600
+ const s = this.sessions.get(id);
12601
+ if (s && s.status !== "done") {
12602
+ s.lastResizeAt = Date.now();
12603
+ s.pty.resize(cols, rows);
12604
+ }
12605
+ }
12606
+ dismissSession(id) {
12607
+ const s = this.sessions.get(id);
12608
+ if (!s) return;
12609
+ if (s.status !== "done") s.pty.kill();
12610
+ clearIdle(s);
12611
+ this.sessions.delete(id);
12612
+ this.notify();
12613
+ }
12614
+ listSessions() {
12615
+ return [...this.sessions.values()].map(
12616
+ ({ id, name, status: status2, startedAt }) => ({
12617
+ id,
12618
+ name,
12619
+ status: status2,
12620
+ startedAt
12621
+ })
12622
+ );
12623
+ }
12624
+ notify() {
12625
+ wsBroadcast(this.clients, {
12626
+ type: "sessions",
12627
+ sessions: this.listSessions()
12628
+ });
12629
+ }
12630
+ };
12631
+
12632
+ // src/commands/sessions/web/index.ts
12633
+ async function web3(options2) {
12634
+ const port = Number.parseInt(options2.port, 10);
12635
+ const server = startWebServer("Sessions", port, handleRequest3);
12636
+ const manager = new SessionManager();
12637
+ const wss = new WebSocketServer({ noServer: true });
12638
+ server.on("upgrade", (req, socket, head) => {
12639
+ if (req.url === "/ws") {
12640
+ wss.handleUpgrade(req, socket, head, (ws) => {
12641
+ handleSocket(ws, manager);
12642
+ });
12643
+ } else {
12644
+ socket.destroy();
12645
+ }
12646
+ });
12647
+ }
12648
+
12649
+ // src/commands/sessions/registerSessions.ts
12650
+ function registerSessions(program2) {
12651
+ const cmd = program2.command("sessions").description("Web dashboard for Claude Code sessions").action(() => web3({ port: "3100" }));
12652
+ cmd.command("web").description("Start the sessions web dashboard").option("-p, --port <number>", "Port to listen on", "3100").action(web3);
12653
+ }
12654
+
12376
12655
  // src/commands/statusLine.ts
12377
12656
  import chalk134 from "chalk";
12378
12657
 
@@ -12627,6 +12906,7 @@ registerRavendb(program);
12627
12906
  registerSeq(program);
12628
12907
  registerTranscript(program);
12629
12908
  registerVoice(program);
12909
+ registerSessions(program);
12630
12910
  registerDeny(program);
12631
12911
  program.command("next").description("Alias for backlog next -w").action(() => next({ allowEdits: true }));
12632
12912
  program.command("draft").alias("feat").description("Launch Claude in /draft mode, chain into next on /next signal").action(() => launchMode("draft"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.191.2",
3
+ "version": "0.192.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -43,10 +43,12 @@
43
43
  "is-wsl": "^3.1.0",
44
44
  "minimatch": "^10.1.1",
45
45
  "node-notifier": "^10.0.1",
46
+ "node-pty": "^1.1.0",
46
47
  "semver": "^7.7.3",
47
48
  "shell-quote": "^1.8.3",
48
49
  "ts-morph": "^27.0.2",
49
50
  "typescript": "^5.9.3",
51
+ "ws": "^8.20.0",
50
52
  "yaml": "^2.8.2",
51
53
  "zod": "^4.3.6"
52
54
  },
@@ -64,7 +66,10 @@
64
66
  "@types/react-dom": "^19.2.3",
65
67
  "@types/semver": "^7.7.1",
66
68
  "@types/shell-quote": "^1.7.5",
69
+ "@types/ws": "^8.18.1",
67
70
  "@vitest/coverage-v8": "^4.1.2",
71
+ "@xterm/addon-fit": "^0.11.0",
72
+ "@xterm/xterm": "^6.0.0",
68
73
  "esbuild": "^0.27.3",
69
74
  "jotai": "^2.18.0",
70
75
  "jscpd": "^4.0.5",