@staff0rd/assist 0.191.3 → 0.193.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.3",
9
+ version: "0.193.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",
@@ -920,10 +925,10 @@ function writeSignal(event, data) {
920
925
 
921
926
  // src/commands/backlog/readSignal.ts
922
927
  function readSignal() {
923
- const path50 = getSignalPath();
924
- if (!existsSync5(path50)) return void 0;
928
+ const path52 = getSignalPath();
929
+ if (!existsSync5(path52)) return void 0;
925
930
  try {
926
- return JSON.parse(readFileSync5(path50, "utf-8"));
931
+ return JSON.parse(readFileSync5(path52, "utf-8"));
927
932
  } catch {
928
933
  return void 0;
929
934
  }
@@ -976,10 +981,10 @@ import { stringify as stringifyYaml } from "yaml";
976
981
  // src/shared/loadRawYaml.ts
977
982
  import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
978
983
  import { parse as parseYaml2 } from "yaml";
979
- function loadRawYaml(path50) {
980
- if (!existsSync7(path50)) return {};
984
+ function loadRawYaml(path52) {
985
+ if (!existsSync7(path52)) return {};
981
986
  try {
982
- const content = readFileSync6(path50, "utf-8");
987
+ const content = readFileSync6(path52, "utf-8");
983
988
  return parseYaml2(content) || {};
984
989
  } catch {
985
990
  return {};
@@ -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) {
@@ -4942,9 +4955,9 @@ var __dirname4 = dirname15(__filename2);
4942
4955
  function packageRoot() {
4943
4956
  return __dirname4;
4944
4957
  }
4945
- function readLines(path50) {
4946
- if (!existsSync20(path50)) return [];
4947
- return readFileSync16(path50, "utf-8").split("\n").filter((line) => line.trim() !== "");
4958
+ function readLines(path52) {
4959
+ if (!existsSync20(path52)) return [];
4960
+ return readFileSync16(path52, "utf-8").split("\n").filter((line) => line.trim() !== "");
4948
4961
  }
4949
4962
  var cachedReads;
4950
4963
  var cachedWrites;
@@ -5377,14 +5390,14 @@ function showProgress(p, label2) {
5377
5390
  const pct = Math.round(p.done / p.total * 100);
5378
5391
  process.stderr.write(`\r\x1B[K[${pct}%] Scanning ${label2}...`);
5379
5392
  }
5380
- async function resolveCommand(cli, path50, description, depth, p) {
5381
- showProgress(p, path50.join(" "));
5382
- const subHelp = await runHelp([cli, ...path50]);
5393
+ async function resolveCommand(cli, path52, description, depth, p) {
5394
+ showProgress(p, path52.join(" "));
5395
+ const subHelp = await runHelp([cli, ...path52]);
5383
5396
  if (!subHelp || !hasSubcommands(subHelp)) {
5384
- return [{ path: path50, description }];
5397
+ return [{ path: path52, description }];
5385
5398
  }
5386
- const children = await discoverAt(cli, path50, depth + 1, p);
5387
- return children.length > 0 ? children : [{ path: path50, description }];
5399
+ const children = await discoverAt(cli, path52, depth + 1, p);
5400
+ return children.length > 0 ? children : [{ path: path52, description }];
5388
5401
  }
5389
5402
  async function discoverAt(cli, parentPath, depth, p) {
5390
5403
  if (depth > SAFETY_DEPTH) return [];
@@ -5532,9 +5545,9 @@ function logPath(cli) {
5532
5545
  return join19(homedir4(), ".assist", `cli-discover-${safeName}.log`);
5533
5546
  }
5534
5547
  function readCache(cli) {
5535
- const path50 = logPath(cli);
5536
- if (!existsSync22(path50)) return void 0;
5537
- return readFileSync18(path50, "utf-8");
5548
+ const path52 = logPath(cli);
5549
+ if (!existsSync22(path52)) return void 0;
5550
+ return readFileSync18(path52, "utf-8");
5538
5551
  }
5539
5552
  function writeCache(cli, output) {
5540
5553
  const dir = join19(homedir4(), ".assist");
@@ -6176,8 +6189,8 @@ function stepIntoNested(container, key, nextKey) {
6176
6189
  }
6177
6190
  return ensureObject(container, resolved);
6178
6191
  }
6179
- function setNestedValue(obj, path50, value) {
6180
- const keys = path50.split(".");
6192
+ function setNestedValue(obj, path52, value) {
6193
+ const keys = path52.split(".");
6181
6194
  const result = { ...obj };
6182
6195
  let current = result;
6183
6196
  for (let i = 0; i < keys.length - 1; i++) {
@@ -6257,9 +6270,9 @@ function isTraversable(value) {
6257
6270
  function stepInto(current, key) {
6258
6271
  return isTraversable(current) ? current[key] : void 0;
6259
6272
  }
6260
- function getNestedValue(obj, path50) {
6273
+ function getNestedValue(obj, path52) {
6261
6274
  let current = obj;
6262
- for (const key of path50.split(".")) current = stepInto(current, key);
6275
+ for (const key of path52.split(".")) current = stepInto(current, key);
6263
6276
  return current;
6264
6277
  }
6265
6278
 
@@ -7600,10 +7613,10 @@ function getStorePath(filename) {
7600
7613
  return join26(getStoreDir(), filename);
7601
7614
  }
7602
7615
  function loadJson(filename) {
7603
- const path50 = getStorePath(filename);
7604
- if (existsSync29(path50)) {
7616
+ const path52 = getStorePath(filename);
7617
+ if (existsSync29(path52)) {
7605
7618
  try {
7606
- return JSON.parse(readFileSync25(path50, "utf-8"));
7619
+ return JSON.parse(readFileSync25(path52, "utf-8"));
7607
7620
  } catch {
7608
7621
  return {};
7609
7622
  }
@@ -7996,7 +8009,7 @@ function validateLine(line) {
7996
8009
  process.exit(1);
7997
8010
  }
7998
8011
  }
7999
- function comment2(path50, line, body) {
8012
+ function comment2(path52, line, body) {
8000
8013
  validateBody(body);
8001
8014
  validateLine(line);
8002
8015
  try {
@@ -8016,7 +8029,7 @@ function comment2(path50, line, body) {
8016
8029
  "-f",
8017
8030
  `body=${body}`,
8018
8031
  "-f",
8019
- `path=${path50}`,
8032
+ `path=${path52}`,
8020
8033
  "-F",
8021
8034
  `line=${line}`
8022
8035
  ],
@@ -8025,7 +8038,7 @@ function comment2(path50, line, body) {
8025
8038
  if (result.status !== 0) {
8026
8039
  throw new Error(result.stderr || result.stdout);
8027
8040
  }
8028
- console.log(`Added review comment on ${path50}:${line}`);
8041
+ console.log(`Added review comment on ${path52}:${line}`);
8029
8042
  } finally {
8030
8043
  unlinkSync6(queryFile);
8031
8044
  }
@@ -8524,8 +8537,8 @@ function registerPrs(program2) {
8524
8537
  prsCommand.command("wontfix <comment-id> <reason>").description("Reply with reason and resolve thread").action((commentId, reason) => {
8525
8538
  wontfix(Number.parseInt(commentId, 10), reason);
8526
8539
  });
8527
- prsCommand.command("comment <path> <line> <body>").description("Add a line comment to the pending review").action((path50, line, body) => {
8528
- comment2(path50, Number.parseInt(line, 10), body);
8540
+ prsCommand.command("comment <path> <line> <body>").description("Add a line comment to the pending review").action((path52, line, body) => {
8541
+ comment2(path52, Number.parseInt(line, 10), body);
8529
8542
  });
8530
8543
  }
8531
8544
 
@@ -8777,10 +8790,10 @@ function resolveOpSecret(reference) {
8777
8790
  }
8778
8791
 
8779
8792
  // src/commands/ravendb/ravenFetch.ts
8780
- async function ravenFetch(connection, path50) {
8793
+ async function ravenFetch(connection, path52) {
8781
8794
  const apiKey = resolveOpSecret(connection.apiKeyRef);
8782
8795
  let accessToken = await getAccessToken(apiKey);
8783
- const url = `${connection.url}${path50}`;
8796
+ const url = `${connection.url}${path52}`;
8784
8797
  const headers = {
8785
8798
  Authorization: `Bearer ${accessToken}`,
8786
8799
  "Content-Type": "application/json"
@@ -8870,16 +8883,16 @@ import chalk111 from "chalk";
8870
8883
  // src/commands/ravendb/buildQueryPath.ts
8871
8884
  function buildQueryPath(opts) {
8872
8885
  const db = encodeURIComponent(opts.db);
8873
- let path50;
8886
+ let path52;
8874
8887
  if (opts.collection) {
8875
- path50 = `/databases/${db}/indexes/dynamic/${encodeURIComponent(opts.collection)}?start=${opts.start}&pageSize=${opts.pageSize}&sort=${encodeURIComponent(opts.sort)}`;
8888
+ path52 = `/databases/${db}/indexes/dynamic/${encodeURIComponent(opts.collection)}?start=${opts.start}&pageSize=${opts.pageSize}&sort=${encodeURIComponent(opts.sort)}`;
8876
8889
  } else {
8877
- path50 = `/databases/${db}/queries?start=${opts.start}&pageSize=${opts.pageSize}`;
8890
+ path52 = `/databases/${db}/queries?start=${opts.start}&pageSize=${opts.pageSize}`;
8878
8891
  }
8879
8892
  if (opts.query) {
8880
- path50 += `&query=${encodeURIComponent(opts.query)}`;
8893
+ path52 += `&query=${encodeURIComponent(opts.query)}`;
8881
8894
  }
8882
- return path50;
8895
+ return path52;
8883
8896
  }
8884
8897
 
8885
8898
  // src/commands/ravendb/fetchAllPages.ts
@@ -8888,7 +8901,7 @@ async function fetchAllPages(connection, opts) {
8888
8901
  let start3 = 0;
8889
8902
  while (true) {
8890
8903
  const effectivePageSize = opts.limit !== void 0 ? Math.min(opts.pageSize, opts.limit - allResults.length) : opts.pageSize;
8891
- const path50 = buildQueryPath({
8904
+ const path52 = buildQueryPath({
8892
8905
  db: connection.database,
8893
8906
  collection: opts.collection,
8894
8907
  start: start3,
@@ -8896,7 +8909,7 @@ async function fetchAllPages(connection, opts) {
8896
8909
  sort: opts.sort,
8897
8910
  query: opts.query
8898
8911
  });
8899
- const data = await ravenFetch(connection, path50);
8912
+ const data = await ravenFetch(connection, path52);
8900
8913
  const results = data.Results ?? [];
8901
8914
  const totalResults = data.TotalResults ?? 0;
8902
8915
  if (results.length === 0) break;
@@ -10067,8 +10080,8 @@ function findRootParent(file, importedBy, visited) {
10067
10080
  function clusterFiles(graph) {
10068
10081
  const clusters = /* @__PURE__ */ new Map();
10069
10082
  for (const file of graph.files) {
10070
- const basename7 = path39.basename(file, path39.extname(file));
10071
- if (basename7 === "index") continue;
10083
+ const basename8 = path39.basename(file, path39.extname(file));
10084
+ if (basename8 === "index") continue;
10072
10085
  const importers = graph.importedBy.get(file);
10073
10086
  if (!importers || importers.size !== 1) continue;
10074
10087
  const parent = [...importers][0];
@@ -10518,8 +10531,8 @@ import chalk128 from "chalk";
10518
10531
 
10519
10532
  // src/commands/seq/fetchSeq.ts
10520
10533
  import chalk125 from "chalk";
10521
- async function fetchSeq(conn, path50, params) {
10522
- const url = `${conn.url}${path50}?${params}`;
10534
+ async function fetchSeq(conn, path52, params) {
10535
+ const url = `${conn.url}${path52}?${params}`;
10523
10536
  const response = await fetch(url, {
10524
10537
  headers: {
10525
10538
  Accept: "application/json",
@@ -12123,11 +12136,11 @@ function findLinkIndex() {
12123
12136
  function parseLinkArgs() {
12124
12137
  const idx = findLinkIndex();
12125
12138
  if (idx === -1) return null;
12126
- const path50 = process.argv[idx + 1];
12139
+ const path52 = process.argv[idx + 1];
12127
12140
  const rest = process.argv.slice(idx + 2);
12128
12141
  const { value: prefix2 } = extractOption(rest, "--prefix");
12129
12142
  if (!prefix2) return null;
12130
- return { path: path50, prefix: prefix2 };
12143
+ return { path: path52, prefix: prefix2 };
12131
12144
  }
12132
12145
  function hasDuplicateLink(runList, linkPath) {
12133
12146
  return runList.some(
@@ -12373,6 +12386,422 @@ 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 "resume": {
12472
+ const id = manager.resume(
12473
+ data.sessionId,
12474
+ data.cwd,
12475
+ data.name
12476
+ );
12477
+ ws.send(JSON.stringify({ type: "created", sessionId: id }));
12478
+ break;
12479
+ }
12480
+ case "dismiss":
12481
+ manager.dismissSession(data.sessionId);
12482
+ break;
12483
+ case "history":
12484
+ manager.getHistory().then((history) => {
12485
+ ws.send(JSON.stringify({ type: "history", sessions: history }));
12486
+ });
12487
+ break;
12488
+ }
12489
+ });
12490
+ ws.on("close", () => {
12491
+ manager.removeClient(ws);
12492
+ });
12493
+ }
12494
+
12495
+ // src/commands/sessions/web/spawnClaude.ts
12496
+ import * as pty from "node-pty";
12497
+ function spawnClaude2(opts = {}) {
12498
+ const shell = process.platform === "win32" ? "cmd.exe" : process.env.SHELL ?? "bash";
12499
+ const args = buildArgs(opts);
12500
+ return pty.spawn(shell, args, {
12501
+ name: "xterm-256color",
12502
+ cols: 120,
12503
+ rows: 30,
12504
+ cwd: opts.cwd ?? process.cwd(),
12505
+ env: { ...process.env }
12506
+ });
12507
+ }
12508
+ function buildArgs(opts) {
12509
+ const claudeArgs = opts.resumeSessionId ? ["claude", "--resume", opts.resumeSessionId] : opts.prompt ? ["claude", opts.prompt] : ["claude"];
12510
+ if (process.platform === "win32") {
12511
+ return ["/c", ...claudeArgs];
12512
+ }
12513
+ return ["-c", `exec ${claudeArgs.map(shellEscape).join(" ")}`];
12514
+ }
12515
+ function shellEscape(s) {
12516
+ return `'${s.replace(/'/g, "'\\''")}'`;
12517
+ }
12518
+
12519
+ // src/commands/sessions/web/createSession.ts
12520
+ function createSession(id, prompt) {
12521
+ return {
12522
+ id,
12523
+ name: prompt?.slice(0, 40) || `Session ${id}`,
12524
+ status: "running",
12525
+ startedAt: Date.now(),
12526
+ pty: spawnClaude2({ prompt }),
12527
+ scrollback: "",
12528
+ idleTimer: null,
12529
+ lastResizeAt: 0
12530
+ };
12531
+ }
12532
+ function resumeSession(id, sessionId, cwd, name) {
12533
+ return {
12534
+ id,
12535
+ name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
12536
+ status: "running",
12537
+ startedAt: Date.now(),
12538
+ pty: spawnClaude2({ resumeSessionId: sessionId, cwd }),
12539
+ scrollback: "",
12540
+ idleTimer: null,
12541
+ lastResizeAt: 0
12542
+ };
12543
+ }
12544
+
12545
+ // src/commands/sessions/web/discoverSessions.ts
12546
+ import * as fs24 from "fs";
12547
+ import * as os from "os";
12548
+ import * as path47 from "path";
12549
+
12550
+ // src/commands/sessions/web/parseSessionFile.ts
12551
+ import * as fs23 from "fs";
12552
+ import * as path46 from "path";
12553
+
12554
+ // src/commands/sessions/web/extractSessionMeta.ts
12555
+ function extractSessionMeta(lines) {
12556
+ let sessionId = "";
12557
+ let cwd = "";
12558
+ let timestamp = "";
12559
+ let name = "";
12560
+ for (const line of lines) {
12561
+ const entry = safeParse(line);
12562
+ if (!entry) continue;
12563
+ sessionId ||= typeof entry.sessionId === "string" ? entry.sessionId : "";
12564
+ timestamp ||= typeof entry.timestamp === "string" ? entry.timestamp : "";
12565
+ cwd ||= typeof entry.cwd === "string" ? entry.cwd : "";
12566
+ if (entry.type === "user" && !entry.isMeta) {
12567
+ name = extractName(entry);
12568
+ break;
12569
+ }
12570
+ }
12571
+ return { sessionId, cwd, timestamp, name };
12572
+ }
12573
+ function safeParse(line) {
12574
+ try {
12575
+ return JSON.parse(line);
12576
+ } catch {
12577
+ return null;
12578
+ }
12579
+ }
12580
+ function extractName(entry) {
12581
+ const msg = entry.message;
12582
+ const content = msg?.content;
12583
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
12584
+ return text.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
12585
+ }
12586
+
12587
+ // src/commands/sessions/web/parseSessionFile.ts
12588
+ async function parseSessionFile(filePath) {
12589
+ let handle;
12590
+ try {
12591
+ handle = await fs23.promises.open(filePath, "r");
12592
+ const buf = Buffer.alloc(16384);
12593
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
12594
+ const lines = buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
12595
+ const meta = extractSessionMeta(lines);
12596
+ if (!meta.sessionId) return null;
12597
+ const timestamp = meta.timestamp || (await fs23.promises.stat(filePath)).mtime.toISOString();
12598
+ const project = meta.cwd ? path46.basename(meta.cwd) : dirNameToProject(filePath);
12599
+ return {
12600
+ sessionId: meta.sessionId,
12601
+ name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
12602
+ project,
12603
+ cwd: meta.cwd,
12604
+ timestamp
12605
+ };
12606
+ } catch {
12607
+ return null;
12608
+ } finally {
12609
+ await handle?.close();
12610
+ }
12611
+ }
12612
+ function dirNameToProject(filePath) {
12613
+ const dirName = path46.basename(path46.dirname(filePath));
12614
+ const parts = dirName.split("--");
12615
+ return parts[parts.length - 1].replace(/-/g, "/");
12616
+ }
12617
+
12618
+ // src/commands/sessions/web/discoverSessions.ts
12619
+ async function discoverSessions() {
12620
+ const projectsDir = path47.join(os.homedir(), ".claude", "projects");
12621
+ let projectDirs;
12622
+ try {
12623
+ projectDirs = await fs24.promises.readdir(projectsDir);
12624
+ } catch {
12625
+ return [];
12626
+ }
12627
+ const sessions = [];
12628
+ await Promise.all(
12629
+ projectDirs.map(async (dirName) => {
12630
+ const dirPath = path47.join(projectsDir, dirName);
12631
+ let entries;
12632
+ try {
12633
+ entries = await fs24.promises.readdir(dirPath);
12634
+ } catch {
12635
+ return;
12636
+ }
12637
+ const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
12638
+ await Promise.all(
12639
+ jsonlFiles.map(async (file) => {
12640
+ const filePath = path47.join(dirPath, file);
12641
+ const session = await parseSessionFile(filePath);
12642
+ if (session) sessions.push(session);
12643
+ })
12644
+ );
12645
+ })
12646
+ );
12647
+ sessions.sort(
12648
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
12649
+ );
12650
+ return sessions;
12651
+ }
12652
+
12653
+ // src/commands/sessions/web/scheduleIdle.ts
12654
+ var IDLE_MS = 3e3;
12655
+ function scheduleIdle(session, onIdle) {
12656
+ if (session.idleTimer) clearTimeout(session.idleTimer);
12657
+ if (session.status === "done") return;
12658
+ session.idleTimer = setTimeout(() => {
12659
+ if (session.status === "running") onIdle();
12660
+ }, IDLE_MS);
12661
+ }
12662
+ function clearIdle(session) {
12663
+ if (session.idleTimer) clearTimeout(session.idleTimer);
12664
+ }
12665
+
12666
+ // src/commands/sessions/web/wsBroadcast.ts
12667
+ function wsSend(ws, msg) {
12668
+ if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(msg));
12669
+ }
12670
+ function wsBroadcast(clients, msg) {
12671
+ const json = JSON.stringify(msg);
12672
+ for (const ws of clients) {
12673
+ if (ws.readyState === ws.OPEN) ws.send(json);
12674
+ }
12675
+ }
12676
+
12677
+ // src/commands/sessions/web/wirePtyEvents.ts
12678
+ var MAX_SCROLLBACK = 256 * 1024;
12679
+ var RESIZE_GRACE_MS = 500;
12680
+ function appendScrollback(session, data) {
12681
+ session.scrollback += data;
12682
+ if (session.scrollback.length > MAX_SCROLLBACK) {
12683
+ session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
12684
+ }
12685
+ }
12686
+ function wirePtyEvents(session, clients, onStatusChange) {
12687
+ session.pty.onData((data) => {
12688
+ appendScrollback(session, data);
12689
+ const isRedraw = Date.now() - session.lastResizeAt < RESIZE_GRACE_MS;
12690
+ if (!isRedraw && session.status !== "running")
12691
+ onStatusChange(session, "running");
12692
+ if (!isRedraw)
12693
+ scheduleIdle(session, () => onStatusChange(session, "waiting"));
12694
+ wsBroadcast(clients, { type: "output", sessionId: session.id, data });
12695
+ });
12696
+ session.pty.onExit(() => {
12697
+ clearIdle(session);
12698
+ onStatusChange(session, "done");
12699
+ });
12700
+ }
12701
+
12702
+ // src/commands/sessions/web/SessionManager.ts
12703
+ var SessionManager = class {
12704
+ sessions = /* @__PURE__ */ new Map();
12705
+ clients = /* @__PURE__ */ new Set();
12706
+ nextId = 1;
12707
+ addClient(ws) {
12708
+ this.clients.add(ws);
12709
+ wsSend(ws, { type: "sessions", sessions: this.listSessions() });
12710
+ for (const s of this.sessions.values()) {
12711
+ if (s.scrollback)
12712
+ wsSend(ws, { type: "output", sessionId: s.id, data: s.scrollback });
12713
+ }
12714
+ }
12715
+ removeClient(ws) {
12716
+ this.clients.delete(ws);
12717
+ }
12718
+ spawn(prompt) {
12719
+ const id = String(this.nextId++);
12720
+ const session = createSession(id, prompt);
12721
+ this.wire(session);
12722
+ return id;
12723
+ }
12724
+ resume(sessionId, cwd, name) {
12725
+ const id = String(this.nextId++);
12726
+ const session = resumeSession(id, sessionId, cwd, name);
12727
+ this.wire(session);
12728
+ return id;
12729
+ }
12730
+ wire(session) {
12731
+ this.sessions.set(session.id, session);
12732
+ wirePtyEvents(session, this.clients, (s, status2) => {
12733
+ s.status = status2;
12734
+ this.notify();
12735
+ });
12736
+ scheduleIdle(session, () => {
12737
+ session.status = "waiting";
12738
+ this.notify();
12739
+ });
12740
+ this.notify();
12741
+ }
12742
+ writeToSession(id, data) {
12743
+ const s = this.sessions.get(id);
12744
+ if (s && s.status !== "done") s.pty.write(data);
12745
+ }
12746
+ resizeSession(id, cols, rows) {
12747
+ const s = this.sessions.get(id);
12748
+ if (s && s.status !== "done") {
12749
+ s.lastResizeAt = Date.now();
12750
+ s.pty.resize(cols, rows);
12751
+ }
12752
+ }
12753
+ dismissSession(id) {
12754
+ const s = this.sessions.get(id);
12755
+ if (!s) return;
12756
+ if (s.status !== "done") s.pty.kill();
12757
+ clearIdle(s);
12758
+ this.sessions.delete(id);
12759
+ this.notify();
12760
+ }
12761
+ listSessions() {
12762
+ return [...this.sessions.values()].map(
12763
+ ({ id, name, status: status2, startedAt }) => ({
12764
+ id,
12765
+ name,
12766
+ status: status2,
12767
+ startedAt
12768
+ })
12769
+ );
12770
+ }
12771
+ async getHistory() {
12772
+ return discoverSessions();
12773
+ }
12774
+ notify() {
12775
+ wsBroadcast(this.clients, {
12776
+ type: "sessions",
12777
+ sessions: this.listSessions()
12778
+ });
12779
+ }
12780
+ };
12781
+
12782
+ // src/commands/sessions/web/index.ts
12783
+ async function web3(options2) {
12784
+ const port = Number.parseInt(options2.port, 10);
12785
+ const server = startWebServer("Sessions", port, handleRequest3);
12786
+ const manager = new SessionManager();
12787
+ const wss = new WebSocketServer({ noServer: true });
12788
+ server.on("upgrade", (req, socket, head) => {
12789
+ if (req.url === "/ws") {
12790
+ wss.handleUpgrade(req, socket, head, (ws) => {
12791
+ handleSocket(ws, manager);
12792
+ });
12793
+ } else {
12794
+ socket.destroy();
12795
+ }
12796
+ });
12797
+ }
12798
+
12799
+ // src/commands/sessions/registerSessions.ts
12800
+ function registerSessions(program2) {
12801
+ const cmd = program2.command("sessions").description("Web dashboard for Claude Code sessions").action(() => web3({ port: "3100" }));
12802
+ cmd.command("web").description("Start the sessions web dashboard").option("-p, --port <number>", "Port to listen on", "3100").action(web3);
12803
+ }
12804
+
12376
12805
  // src/commands/statusLine.ts
12377
12806
  import chalk134 from "chalk";
12378
12807
 
@@ -12451,21 +12880,21 @@ async function statusLine() {
12451
12880
  }
12452
12881
 
12453
12882
  // src/commands/sync.ts
12454
- import * as fs25 from "fs";
12455
- import * as os from "os";
12456
- import * as path48 from "path";
12883
+ import * as fs27 from "fs";
12884
+ import * as os2 from "os";
12885
+ import * as path50 from "path";
12457
12886
  import { fileURLToPath as fileURLToPath7 } from "url";
12458
12887
 
12459
12888
  // src/commands/sync/syncClaudeMd.ts
12460
- import * as fs23 from "fs";
12461
- import * as path46 from "path";
12889
+ import * as fs25 from "fs";
12890
+ import * as path48 from "path";
12462
12891
  import chalk135 from "chalk";
12463
12892
  async function syncClaudeMd(claudeDir, targetBase, options2) {
12464
- const source = path46.join(claudeDir, "CLAUDE.md");
12465
- const target = path46.join(targetBase, "CLAUDE.md");
12466
- const sourceContent = fs23.readFileSync(source, "utf-8");
12467
- if (fs23.existsSync(target)) {
12468
- const targetContent = fs23.readFileSync(target, "utf-8");
12893
+ const source = path48.join(claudeDir, "CLAUDE.md");
12894
+ const target = path48.join(targetBase, "CLAUDE.md");
12895
+ const sourceContent = fs25.readFileSync(source, "utf-8");
12896
+ if (fs25.existsSync(target)) {
12897
+ const targetContent = fs25.readFileSync(target, "utf-8");
12469
12898
  if (sourceContent !== targetContent) {
12470
12899
  console.log(
12471
12900
  chalk135.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
@@ -12482,21 +12911,21 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
12482
12911
  }
12483
12912
  }
12484
12913
  }
12485
- fs23.copyFileSync(source, target);
12914
+ fs25.copyFileSync(source, target);
12486
12915
  console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
12487
12916
  }
12488
12917
 
12489
12918
  // src/commands/sync/syncSettings.ts
12490
- import * as fs24 from "fs";
12491
- import * as path47 from "path";
12919
+ import * as fs26 from "fs";
12920
+ import * as path49 from "path";
12492
12921
  import chalk136 from "chalk";
12493
12922
  async function syncSettings(claudeDir, targetBase, options2) {
12494
- const source = path47.join(claudeDir, "settings.json");
12495
- const target = path47.join(targetBase, "settings.json");
12496
- const sourceContent = fs24.readFileSync(source, "utf-8");
12923
+ const source = path49.join(claudeDir, "settings.json");
12924
+ const target = path49.join(targetBase, "settings.json");
12925
+ const sourceContent = fs26.readFileSync(source, "utf-8");
12497
12926
  const mergedContent = JSON.stringify(JSON.parse(sourceContent), null, " ");
12498
- if (fs24.existsSync(target)) {
12499
- const targetContent = fs24.readFileSync(target, "utf-8");
12927
+ if (fs26.existsSync(target)) {
12928
+ const targetContent = fs26.readFileSync(target, "utf-8");
12500
12929
  const normalizedTarget = JSON.stringify(
12501
12930
  JSON.parse(targetContent),
12502
12931
  null,
@@ -12522,29 +12951,29 @@ async function syncSettings(claudeDir, targetBase, options2) {
12522
12951
  }
12523
12952
  }
12524
12953
  }
12525
- fs24.writeFileSync(target, mergedContent);
12954
+ fs26.writeFileSync(target, mergedContent);
12526
12955
  console.log("Copied settings.json to ~/.claude/settings.json");
12527
12956
  }
12528
12957
 
12529
12958
  // src/commands/sync.ts
12530
12959
  var __filename4 = fileURLToPath7(import.meta.url);
12531
- var __dirname7 = path48.dirname(__filename4);
12960
+ var __dirname7 = path50.dirname(__filename4);
12532
12961
  async function sync(options2) {
12533
12962
  const config = loadConfig();
12534
12963
  const yes = options2?.yes ?? config.sync.autoConfirm;
12535
- const claudeDir = path48.join(__dirname7, "..", "claude");
12536
- const targetBase = path48.join(os.homedir(), ".claude");
12964
+ const claudeDir = path50.join(__dirname7, "..", "claude");
12965
+ const targetBase = path50.join(os2.homedir(), ".claude");
12537
12966
  syncCommands(claudeDir, targetBase);
12538
12967
  await syncSettings(claudeDir, targetBase, { yes });
12539
12968
  await syncClaudeMd(claudeDir, targetBase, { yes });
12540
12969
  }
12541
12970
  function syncCommands(claudeDir, targetBase) {
12542
- const sourceDir = path48.join(claudeDir, "commands");
12543
- const targetDir = path48.join(targetBase, "commands");
12544
- fs25.mkdirSync(targetDir, { recursive: true });
12545
- const files = fs25.readdirSync(sourceDir);
12971
+ const sourceDir = path50.join(claudeDir, "commands");
12972
+ const targetDir = path50.join(targetBase, "commands");
12973
+ fs27.mkdirSync(targetDir, { recursive: true });
12974
+ const files = fs27.readdirSync(sourceDir);
12546
12975
  for (const file of files) {
12547
- fs25.copyFileSync(path48.join(sourceDir, file), path48.join(targetDir, file));
12976
+ fs27.copyFileSync(path50.join(sourceDir, file), path50.join(targetDir, file));
12548
12977
  console.log(`Copied ${file} to ${targetDir}`);
12549
12978
  }
12550
12979
  console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
@@ -12552,15 +12981,15 @@ function syncCommands(claudeDir, targetBase) {
12552
12981
 
12553
12982
  // src/commands/update.ts
12554
12983
  import { execSync as execSync42 } from "child_process";
12555
- import * as path49 from "path";
12984
+ import * as path51 from "path";
12556
12985
  function isGlobalNpmInstall(dir) {
12557
12986
  try {
12558
- const resolved = path49.resolve(dir);
12559
- if (resolved.split(path49.sep).includes("node_modules")) {
12987
+ const resolved = path51.resolve(dir);
12988
+ if (resolved.split(path51.sep).includes("node_modules")) {
12560
12989
  return true;
12561
12990
  }
12562
12991
  const globalPrefix = execSync42("npm prefix -g", { stdio: "pipe" }).toString().trim();
12563
- return resolved.toLowerCase().startsWith(path49.resolve(globalPrefix).toLowerCase());
12992
+ return resolved.toLowerCase().startsWith(path51.resolve(globalPrefix).toLowerCase());
12564
12993
  } catch {
12565
12994
  return false;
12566
12995
  }
@@ -12627,6 +13056,7 @@ registerRavendb(program);
12627
13056
  registerSeq(program);
12628
13057
  registerTranscript(program);
12629
13058
  registerVoice(program);
13059
+ registerSessions(program);
12630
13060
  registerDeny(program);
12631
13061
  program.command("next").description("Alias for backlog next -w").action(() => next({ allowEdits: true }));
12632
13062
  program.command("draft").alias("feat").description("Launch Claude in /draft mode, chain into next on /next signal").action(() => launchMode("draft"));