@staff0rd/assist 0.196.1 → 0.198.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.196.1",
9
+ version: "0.198.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -928,10 +928,10 @@ function writeSignal(event, data) {
928
928
 
929
929
  // src/commands/backlog/readSignal.ts
930
930
  function readSignal() {
931
- const path52 = getSignalPath();
932
- if (!existsSync5(path52)) return void 0;
931
+ const path53 = getSignalPath();
932
+ if (!existsSync5(path53)) return void 0;
933
933
  try {
934
- return JSON.parse(readFileSync5(path52, "utf-8"));
934
+ return JSON.parse(readFileSync5(path53, "utf-8"));
935
935
  } catch {
936
936
  return void 0;
937
937
  }
@@ -1676,10 +1676,10 @@ import { stringify as stringifyYaml } from "yaml";
1676
1676
  // src/shared/loadRawYaml.ts
1677
1677
  import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
1678
1678
  import { parse as parseYaml2 } from "yaml";
1679
- function loadRawYaml(path52) {
1680
- if (!existsSync8(path52)) return {};
1679
+ function loadRawYaml(path53) {
1680
+ if (!existsSync8(path53)) return {};
1681
1681
  try {
1682
- const content = readFileSync7(path52, "utf-8");
1682
+ const content = readFileSync7(path53, "utf-8");
1683
1683
  return parseYaml2(content) || {};
1684
1684
  } catch {
1685
1685
  return {};
@@ -1845,6 +1845,17 @@ function loadConfig() {
1845
1845
  const merged = { ...globalRaw, ...projectRaw };
1846
1846
  return assistConfigSchema.parse(merged);
1847
1847
  }
1848
+ function loadConfigFrom(startDir) {
1849
+ const found = findConfigUp(startDir);
1850
+ const configPath = found ?? join8(startDir, "assist.yml");
1851
+ const globalRaw = loadRawYaml(getGlobalConfigPath());
1852
+ const projectRaw = loadRawYaml(configPath);
1853
+ const merged = { ...globalRaw, ...projectRaw };
1854
+ return {
1855
+ config: assistConfigSchema.parse(merged),
1856
+ configDir: dirname2(configPath)
1857
+ };
1858
+ }
1848
1859
  function loadProjectConfig() {
1849
1860
  return loadRawYaml(getConfigPath());
1850
1861
  }
@@ -2588,9 +2599,9 @@ function getSetupHandlers(hasVite, hasTypescript, hasOpenColor) {
2588
2599
  }
2589
2600
 
2590
2601
  // src/commands/verify/init/index.ts
2591
- async function runSelectedSetups(selected, packageJsonPath, writer, handlers) {
2602
+ async function runSelectedSetups(selected, packageJsonPath, writer, handlers2) {
2592
2603
  for (const choice of selected) {
2593
- await handlers[choice]?.(packageJsonPath, writer);
2604
+ await handlers2[choice]?.(packageJsonPath, writer);
2594
2605
  }
2595
2606
  console.log(chalk33.green(`
2596
2607
  Added ${selected.length} verify script(s):`));
@@ -2621,12 +2632,12 @@ async function init2() {
2621
2632
  const selected = await promptForScripts(getAvailableOptions(setup2));
2622
2633
  if (!selected) return;
2623
2634
  const writer = setup2.hasConfigScripts ? setupVerifyRunEntry : (name, cmd) => setupVerifyScript(packageJsonPath, name, cmd);
2624
- const handlers = getSetupHandlers(
2635
+ const handlers2 = getSetupHandlers(
2625
2636
  setup2.hasVite,
2626
2637
  setup2.hasTypescript,
2627
2638
  setup2.hasOpenColor
2628
2639
  );
2629
- await runSelectedSetups(selected, packageJsonPath, writer, handlers);
2640
+ await runSelectedSetups(selected, packageJsonPath, writer, handlers2);
2630
2641
  }
2631
2642
 
2632
2643
  // src/commands/vscode/init/index.ts
@@ -2746,14 +2757,14 @@ function applySelections(selected, setup2) {
2746
2757
  removeVscodeFromGitignore();
2747
2758
  ensureVscodeFolder();
2748
2759
  const launchType = setup2.hasVite ? "vite" : "tsup";
2749
- const handlers = {
2760
+ const handlers2 = {
2750
2761
  launch: () => createLaunchJson(launchType),
2751
2762
  settings: () => {
2752
2763
  createSettingsJson();
2753
2764
  createExtensionsJson();
2754
2765
  }
2755
2766
  };
2756
- for (const choice of selected) handlers[choice]?.();
2767
+ for (const choice of selected) handlers2[choice]?.();
2757
2768
  }
2758
2769
  async function promptForOptions(options2) {
2759
2770
  console.log(chalk35.bold("Available VS Code configurations to add:\n"));
@@ -5047,9 +5058,9 @@ var __dirname4 = dirname15(__filename2);
5047
5058
  function packageRoot() {
5048
5059
  return __dirname4;
5049
5060
  }
5050
- function readLines(path52) {
5051
- if (!existsSync20(path52)) return [];
5052
- return readFileSync16(path52, "utf-8").split("\n").filter((line) => line.trim() !== "");
5061
+ function readLines(path53) {
5062
+ if (!existsSync20(path53)) return [];
5063
+ return readFileSync16(path53, "utf-8").split("\n").filter((line) => line.trim() !== "");
5053
5064
  }
5054
5065
  var cachedReads;
5055
5066
  var cachedWrites;
@@ -5494,14 +5505,14 @@ function showProgress(p, label2) {
5494
5505
  const pct = Math.round(p.done / p.total * 100);
5495
5506
  process.stderr.write(`\r\x1B[K[${pct}%] Scanning ${label2}...`);
5496
5507
  }
5497
- async function resolveCommand(cli, path52, description, depth, p) {
5498
- showProgress(p, path52.join(" "));
5499
- const subHelp = await runHelp([cli, ...path52]);
5508
+ async function resolveCommand(cli, path53, description, depth, p) {
5509
+ showProgress(p, path53.join(" "));
5510
+ const subHelp = await runHelp([cli, ...path53]);
5500
5511
  if (!subHelp || !hasSubcommands(subHelp)) {
5501
- return [{ path: path52, description }];
5512
+ return [{ path: path53, description }];
5502
5513
  }
5503
- const children = await discoverAt(cli, path52, depth + 1, p);
5504
- return children.length > 0 ? children : [{ path: path52, description }];
5514
+ const children = await discoverAt(cli, path53, depth + 1, p);
5515
+ return children.length > 0 ? children : [{ path: path53, description }];
5505
5516
  }
5506
5517
  async function discoverAt(cli, parentPath, depth, p) {
5507
5518
  if (depth > SAFETY_DEPTH) return [];
@@ -5649,9 +5660,9 @@ function logPath(cli) {
5649
5660
  return join20(homedir5(), ".assist", `cli-discover-${safeName}.log`);
5650
5661
  }
5651
5662
  function readCache(cli) {
5652
- const path52 = logPath(cli);
5653
- if (!existsSync22(path52)) return void 0;
5654
- return readFileSync18(path52, "utf-8");
5663
+ const path53 = logPath(cli);
5664
+ if (!existsSync22(path53)) return void 0;
5665
+ return readFileSync18(path53, "utf-8");
5655
5666
  }
5656
5667
  function writeCache(cli, output) {
5657
5668
  const dir = join20(homedir5(), ".assist");
@@ -6293,8 +6304,8 @@ function stepIntoNested(container, key, nextKey) {
6293
6304
  }
6294
6305
  return ensureObject(container, resolved);
6295
6306
  }
6296
- function setNestedValue(obj, path52, value) {
6297
- const keys = path52.split(".");
6307
+ function setNestedValue(obj, path53, value) {
6308
+ const keys = path53.split(".");
6298
6309
  const result = { ...obj };
6299
6310
  let current = result;
6300
6311
  for (let i = 0; i < keys.length - 1; i++) {
@@ -6374,9 +6385,9 @@ function isTraversable(value) {
6374
6385
  function stepInto(current, key) {
6375
6386
  return isTraversable(current) ? current[key] : void 0;
6376
6387
  }
6377
- function getNestedValue(obj, path52) {
6388
+ function getNestedValue(obj, path53) {
6378
6389
  let current = obj;
6379
- for (const key of path52.split(".")) current = stepInto(current, key);
6390
+ for (const key of path53.split(".")) current = stepInto(current, key);
6380
6391
  return current;
6381
6392
  }
6382
6393
 
@@ -7717,10 +7728,10 @@ function getStorePath(filename) {
7717
7728
  return join27(getStoreDir(), filename);
7718
7729
  }
7719
7730
  function loadJson(filename) {
7720
- const path52 = getStorePath(filename);
7721
- if (existsSync29(path52)) {
7731
+ const path53 = getStorePath(filename);
7732
+ if (existsSync29(path53)) {
7722
7733
  try {
7723
- return JSON.parse(readFileSync25(path52, "utf-8"));
7734
+ return JSON.parse(readFileSync25(path53, "utf-8"));
7724
7735
  } catch {
7725
7736
  return {};
7726
7737
  }
@@ -8167,7 +8178,7 @@ function validateLine(line) {
8167
8178
  process.exit(1);
8168
8179
  }
8169
8180
  }
8170
- function comment2(path52, line, body) {
8181
+ function comment2(path53, line, body) {
8171
8182
  validateBody(body);
8172
8183
  validateLine(line);
8173
8184
  try {
@@ -8187,7 +8198,7 @@ function comment2(path52, line, body) {
8187
8198
  "-f",
8188
8199
  `body=${body}`,
8189
8200
  "-f",
8190
- `path=${path52}`,
8201
+ `path=${path53}`,
8191
8202
  "-F",
8192
8203
  `line=${line}`
8193
8204
  ],
@@ -8196,7 +8207,7 @@ function comment2(path52, line, body) {
8196
8207
  if (result.status !== 0) {
8197
8208
  throw new Error(result.stderr || result.stdout);
8198
8209
  }
8199
- console.log(`Added review comment on ${path52}:${line}`);
8210
+ console.log(`Added review comment on ${path53}:${line}`);
8200
8211
  } finally {
8201
8212
  unlinkSync6(queryFile);
8202
8213
  }
@@ -8695,8 +8706,8 @@ function registerPrs(program2) {
8695
8706
  prsCommand.command("wontfix <comment-id> <reason>").description("Reply with reason and resolve thread").action((commentId, reason) => {
8696
8707
  wontfix(Number.parseInt(commentId, 10), reason);
8697
8708
  });
8698
- prsCommand.command("comment <path> <line> <body>").description("Add a line comment to the pending review").action((path52, line, body) => {
8699
- comment2(path52, Number.parseInt(line, 10), body);
8709
+ prsCommand.command("comment <path> <line> <body>").description("Add a line comment to the pending review").action((path53, line, body) => {
8710
+ comment2(path53, Number.parseInt(line, 10), body);
8700
8711
  });
8701
8712
  }
8702
8713
 
@@ -8948,10 +8959,10 @@ function resolveOpSecret(reference) {
8948
8959
  }
8949
8960
 
8950
8961
  // src/commands/ravendb/ravenFetch.ts
8951
- async function ravenFetch(connection, path52) {
8962
+ async function ravenFetch(connection, path53) {
8952
8963
  const apiKey = resolveOpSecret(connection.apiKeyRef);
8953
8964
  let accessToken = await getAccessToken(apiKey);
8954
- const url = `${connection.url}${path52}`;
8965
+ const url = `${connection.url}${path53}`;
8955
8966
  const headers = {
8956
8967
  Authorization: `Bearer ${accessToken}`,
8957
8968
  "Content-Type": "application/json"
@@ -9041,16 +9052,16 @@ import chalk112 from "chalk";
9041
9052
  // src/commands/ravendb/buildQueryPath.ts
9042
9053
  function buildQueryPath(opts) {
9043
9054
  const db = encodeURIComponent(opts.db);
9044
- let path52;
9055
+ let path53;
9045
9056
  if (opts.collection) {
9046
- path52 = `/databases/${db}/indexes/dynamic/${encodeURIComponent(opts.collection)}?start=${opts.start}&pageSize=${opts.pageSize}&sort=${encodeURIComponent(opts.sort)}`;
9057
+ path53 = `/databases/${db}/indexes/dynamic/${encodeURIComponent(opts.collection)}?start=${opts.start}&pageSize=${opts.pageSize}&sort=${encodeURIComponent(opts.sort)}`;
9047
9058
  } else {
9048
- path52 = `/databases/${db}/queries?start=${opts.start}&pageSize=${opts.pageSize}`;
9059
+ path53 = `/databases/${db}/queries?start=${opts.start}&pageSize=${opts.pageSize}`;
9049
9060
  }
9050
9061
  if (opts.query) {
9051
- path52 += `&query=${encodeURIComponent(opts.query)}`;
9062
+ path53 += `&query=${encodeURIComponent(opts.query)}`;
9052
9063
  }
9053
- return path52;
9064
+ return path53;
9054
9065
  }
9055
9066
 
9056
9067
  // src/commands/ravendb/fetchAllPages.ts
@@ -9059,7 +9070,7 @@ async function fetchAllPages(connection, opts) {
9059
9070
  let start3 = 0;
9060
9071
  while (true) {
9061
9072
  const effectivePageSize = opts.limit !== void 0 ? Math.min(opts.pageSize, opts.limit - allResults.length) : opts.pageSize;
9062
- const path52 = buildQueryPath({
9073
+ const path53 = buildQueryPath({
9063
9074
  db: connection.database,
9064
9075
  collection: opts.collection,
9065
9076
  start: start3,
@@ -9067,7 +9078,7 @@ async function fetchAllPages(connection, opts) {
9067
9078
  sort: opts.sort,
9068
9079
  query: opts.query
9069
9080
  });
9070
- const data = await ravenFetch(connection, path52);
9081
+ const data = await ravenFetch(connection, path53);
9071
9082
  const results = data.Results ?? [];
9072
9083
  const totalResults = data.TotalResults ?? 0;
9073
9084
  if (results.length === 0) break;
@@ -10238,8 +10249,8 @@ function findRootParent(file, importedBy, visited) {
10238
10249
  function clusterFiles(graph) {
10239
10250
  const clusters = /* @__PURE__ */ new Map();
10240
10251
  for (const file of graph.files) {
10241
- const basename9 = path39.basename(file, path39.extname(file));
10242
- if (basename9 === "index") continue;
10252
+ const basename10 = path39.basename(file, path39.extname(file));
10253
+ if (basename10 === "index") continue;
10243
10254
  const importers = graph.importedBy.get(file);
10244
10255
  if (!importers || importers.size !== 1) continue;
10245
10256
  const parent = [...importers][0];
@@ -10689,8 +10700,8 @@ import chalk129 from "chalk";
10689
10700
 
10690
10701
  // src/commands/seq/fetchSeq.ts
10691
10702
  import chalk126 from "chalk";
10692
- async function fetchSeq(conn, path52, params) {
10693
- const url = `${conn.url}${path52}?${params}`;
10703
+ async function fetchSeq(conn, path53, params) {
10704
+ const url = `${conn.url}${path53}?${params}`;
10694
10705
  const response = await fetch(url, {
10695
10706
  headers: {
10696
10707
  Accept: "application/json",
@@ -12340,11 +12351,11 @@ function findLinkIndex() {
12340
12351
  function parseLinkArgs() {
12341
12352
  const idx = findLinkIndex();
12342
12353
  if (idx === -1) return null;
12343
- const path52 = process.argv[idx + 1];
12354
+ const path53 = process.argv[idx + 1];
12344
12355
  const rest = process.argv.slice(idx + 2);
12345
12356
  const { value: prefix2 } = extractOption(rest, "--prefix");
12346
12357
  if (!prefix2) return null;
12347
- return { path: path52, prefix: prefix2 };
12358
+ return { path: path53, prefix: prefix2 };
12348
12359
  }
12349
12360
  function hasDuplicateLink(runList, linkPath) {
12350
12361
  return runList.some(
@@ -12590,11 +12601,319 @@ function screenshot(processName) {
12590
12601
  }
12591
12602
  }
12592
12603
 
12604
+ // src/commands/sessions/summarise/index.ts
12605
+ import * as fs27 from "fs";
12606
+ import chalk134 from "chalk";
12607
+
12608
+ // src/commands/sessions/web/discoverSessions.ts
12609
+ import * as fs24 from "fs";
12610
+ import * as os from "os";
12611
+ import * as path47 from "path";
12612
+
12613
+ // src/commands/sessions/web/parseSessionFile.ts
12614
+ import * as fs23 from "fs";
12615
+ import * as path46 from "path";
12616
+
12617
+ // src/commands/sessions/web/extractSessionMeta.ts
12618
+ function extractSessionMeta(lines) {
12619
+ let sessionId = "";
12620
+ let cwd = "";
12621
+ let timestamp = "";
12622
+ let name = "";
12623
+ for (const line of lines) {
12624
+ const entry = safeParse(line);
12625
+ if (!entry) continue;
12626
+ sessionId ||= typeof entry.sessionId === "string" ? entry.sessionId : "";
12627
+ timestamp ||= typeof entry.timestamp === "string" ? entry.timestamp : "";
12628
+ cwd ||= typeof entry.cwd === "string" ? entry.cwd : "";
12629
+ if (entry.type === "user" && !entry.isMeta) {
12630
+ name = extractName(entry);
12631
+ break;
12632
+ }
12633
+ }
12634
+ return { sessionId, cwd, timestamp, name };
12635
+ }
12636
+ function safeParse(line) {
12637
+ try {
12638
+ return JSON.parse(line);
12639
+ } catch {
12640
+ return null;
12641
+ }
12642
+ }
12643
+ function extractName(entry) {
12644
+ const msg = entry.message;
12645
+ const content = msg?.content;
12646
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
12647
+ return text.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
12648
+ }
12649
+
12650
+ // src/commands/sessions/web/parseSessionFile.ts
12651
+ async function parseSessionFile(filePath) {
12652
+ let handle;
12653
+ try {
12654
+ handle = await fs23.promises.open(filePath, "r");
12655
+ const buf = Buffer.alloc(16384);
12656
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
12657
+ const lines = buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
12658
+ const meta = extractSessionMeta(lines);
12659
+ if (!meta.sessionId) return null;
12660
+ const timestamp = meta.timestamp || (await fs23.promises.stat(filePath)).mtime.toISOString();
12661
+ const project = meta.cwd ? path46.basename(meta.cwd) : dirNameToProject(filePath);
12662
+ return {
12663
+ sessionId: meta.sessionId,
12664
+ name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
12665
+ project,
12666
+ cwd: meta.cwd,
12667
+ timestamp
12668
+ };
12669
+ } catch {
12670
+ return null;
12671
+ } finally {
12672
+ await handle?.close();
12673
+ }
12674
+ }
12675
+ function dirNameToProject(filePath) {
12676
+ const dirName = path46.basename(path46.dirname(filePath));
12677
+ const parts = dirName.split("--");
12678
+ return parts[parts.length - 1].replace(/-/g, "/");
12679
+ }
12680
+
12681
+ // src/commands/sessions/web/discoverSessions.ts
12682
+ async function discoverSessionJsonlPaths() {
12683
+ const projectsDir = path47.join(os.homedir(), ".claude", "projects");
12684
+ let projectDirs;
12685
+ try {
12686
+ projectDirs = await fs24.promises.readdir(projectsDir);
12687
+ } catch {
12688
+ return [];
12689
+ }
12690
+ const paths = [];
12691
+ await Promise.all(
12692
+ projectDirs.map(async (dirName) => {
12693
+ const dirPath = path47.join(projectsDir, dirName);
12694
+ let entries;
12695
+ try {
12696
+ entries = await fs24.promises.readdir(dirPath);
12697
+ } catch {
12698
+ return;
12699
+ }
12700
+ const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
12701
+ for (const file of jsonlFiles) {
12702
+ paths.push(path47.join(dirPath, file));
12703
+ }
12704
+ })
12705
+ );
12706
+ return paths;
12707
+ }
12708
+ async function discoverSessions() {
12709
+ const paths = await discoverSessionJsonlPaths();
12710
+ const sessions = [];
12711
+ await Promise.all(
12712
+ paths.map(async (filePath) => {
12713
+ const session = await parseSessionFile(filePath);
12714
+ if (session) sessions.push(session);
12715
+ })
12716
+ );
12717
+ sessions.sort(
12718
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
12719
+ );
12720
+ return sessions;
12721
+ }
12722
+
12723
+ // src/commands/sessions/summarise/shared.ts
12724
+ import * as fs25 from "fs";
12725
+ function writeSummary(jsonlPath, summary) {
12726
+ fs25.writeFileSync(summaryPathFor(jsonlPath), `${summary.trim()}
12727
+ `, "utf8");
12728
+ }
12729
+ function hasSummary(jsonlPath) {
12730
+ return fs25.existsSync(summaryPathFor(jsonlPath));
12731
+ }
12732
+ function summaryPathFor(jsonlPath) {
12733
+ return jsonlPath.replace(/\.jsonl$/, ".summary");
12734
+ }
12735
+
12736
+ // src/commands/sessions/summarise/summariseSession.ts
12737
+ import { execFileSync as execFileSync2 } from "child_process";
12738
+
12739
+ // src/commands/sessions/summarise/iterateUserMessages.ts
12740
+ import * as fs26 from "fs";
12741
+ function* iterateUserMessages(filePath, maxBytes = 65536) {
12742
+ let content;
12743
+ try {
12744
+ const fd = fs26.openSync(filePath, "r");
12745
+ try {
12746
+ const buf = Buffer.alloc(maxBytes);
12747
+ const bytesRead = fs26.readSync(fd, buf, 0, buf.length, 0);
12748
+ content = buf.toString("utf8", 0, bytesRead);
12749
+ } finally {
12750
+ fs26.closeSync(fd);
12751
+ }
12752
+ } catch {
12753
+ return;
12754
+ }
12755
+ for (const line of content.split("\n")) {
12756
+ if (!line) continue;
12757
+ let entry;
12758
+ try {
12759
+ entry = JSON.parse(line);
12760
+ } catch {
12761
+ continue;
12762
+ }
12763
+ if (entry.type !== "user") continue;
12764
+ const msg = entry.message;
12765
+ const c = msg?.content;
12766
+ if (typeof c === "string") {
12767
+ yield c;
12768
+ } else if (Array.isArray(c)) {
12769
+ const text = c.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
12770
+ if (text) yield text;
12771
+ }
12772
+ }
12773
+ }
12774
+
12775
+ // src/commands/sessions/summarise/extractFirstUserMessage.ts
12776
+ function extractFirstUserMessage(filePath) {
12777
+ for (const text of iterateUserMessages(filePath)) {
12778
+ return truncate2(text);
12779
+ }
12780
+ return void 0;
12781
+ }
12782
+ function truncate2(text, maxChars = 500) {
12783
+ const trimmed = text.trim();
12784
+ if (trimmed.length <= maxChars) return trimmed;
12785
+ return `${trimmed.slice(0, maxChars)}\u2026`;
12786
+ }
12787
+
12788
+ // src/commands/sessions/summarise/scanSessionBacklogRefs.ts
12789
+ function scanSessionBacklogRefs(filePath) {
12790
+ const ids = /* @__PURE__ */ new Set();
12791
+ for (const text of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
12792
+ for (const id of extractBacklogIds(text)) {
12793
+ ids.add(id);
12794
+ }
12795
+ }
12796
+ return [...ids].sort((a, b) => a - b);
12797
+ }
12798
+ function extractBacklogIds(text) {
12799
+ const ids = [];
12800
+ for (const m of text.matchAll(/backlog\s+run\s+(\d+)/gi)) {
12801
+ ids.push(Number.parseInt(m[1], 10));
12802
+ }
12803
+ for (const m of text.matchAll(/backlog\s+(?:item\s+)?#(\d+)/gi)) {
12804
+ ids.push(Number.parseInt(m[1], 10));
12805
+ }
12806
+ for (const m of text.matchAll(/backlog\s+phase-done\s+(\d+)/gi)) {
12807
+ ids.push(Number.parseInt(m[1], 10));
12808
+ }
12809
+ for (const m of text.matchAll(/backlog\s+comment\s+(\d+)/gi)) {
12810
+ ids.push(Number.parseInt(m[1], 10));
12811
+ }
12812
+ for (const m of text.matchAll(/(?:^|[\s(])#(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
12813
+ ids.push(Number.parseInt(m[1], 10));
12814
+ }
12815
+ return ids;
12816
+ }
12817
+
12818
+ // src/commands/sessions/summarise/summariseSession.ts
12819
+ function summariseSession(jsonlPath) {
12820
+ const firstMessage = extractFirstUserMessage(jsonlPath);
12821
+ const backlogIds = scanSessionBacklogRefs(jsonlPath);
12822
+ if (!firstMessage && backlogIds.length === 0) {
12823
+ return void 0;
12824
+ }
12825
+ const prompt = buildPrompt2(firstMessage, backlogIds);
12826
+ try {
12827
+ const output = execFileSync2("claude", ["-p", "--model", "haiku", prompt], {
12828
+ encoding: "utf8",
12829
+ timeout: 3e4,
12830
+ stdio: ["ignore", "pipe", "ignore"]
12831
+ });
12832
+ const summary = output.trim();
12833
+ if (!summary) return void 0;
12834
+ return summary.split("\n")[0].replace(/^["']|["']$/g, "").trim();
12835
+ } catch {
12836
+ return void 0;
12837
+ }
12838
+ }
12839
+ function buildPrompt2(firstMessage, backlogIds) {
12840
+ const parts = [
12841
+ "Summarise this Claude Code session in ONE short sentence (under 100 chars).",
12842
+ "Return ONLY the summary, no quotes or explanation."
12843
+ ];
12844
+ if (backlogIds.length > 0) {
12845
+ const refs = backlogIds.map((id) => `#${id}`).join(", ");
12846
+ parts.push(
12847
+ `The session references backlog item(s) ${refs}. Start the summary with "Backlog ${refs} \u2014" if relevant.`
12848
+ );
12849
+ }
12850
+ if (firstMessage) {
12851
+ parts.push(`First user message:
12852
+ ${firstMessage}`);
12853
+ }
12854
+ return parts.join("\n");
12855
+ }
12856
+
12857
+ // src/commands/sessions/summarise/index.ts
12858
+ async function summarise3(options2) {
12859
+ const files = await discoverSessionJsonlPaths();
12860
+ if (files.length === 0) {
12861
+ console.log(chalk134.yellow("No sessions found."));
12862
+ return;
12863
+ }
12864
+ const toProcess = selectCandidates(files, options2);
12865
+ if (toProcess.length === 0) {
12866
+ console.log(chalk134.green("All sessions already summarised."));
12867
+ return;
12868
+ }
12869
+ console.log(
12870
+ chalk134.cyan(
12871
+ `Summarising ${toProcess.length} session(s) (${files.length} total)\u2026`
12872
+ )
12873
+ );
12874
+ const { succeeded, failed } = processSessions(toProcess);
12875
+ console.log(
12876
+ chalk134.green(`Done: ${succeeded} summarised`) + (failed > 0 ? chalk134.yellow(`, ${failed} skipped`) : "")
12877
+ );
12878
+ }
12879
+ function selectCandidates(files, options2) {
12880
+ const candidates = options2.force ? files : files.filter((f) => !hasSummary(f));
12881
+ candidates.sort((a, b) => {
12882
+ try {
12883
+ return fs27.statSync(b).mtimeMs - fs27.statSync(a).mtimeMs;
12884
+ } catch {
12885
+ return 0;
12886
+ }
12887
+ });
12888
+ const limit = options2.limit ? Number.parseInt(options2.limit, 10) : void 0;
12889
+ return limit && limit > 0 ? candidates.slice(0, limit) : candidates;
12890
+ }
12891
+ function processSessions(files) {
12892
+ let succeeded = 0;
12893
+ let failed = 0;
12894
+ for (let i = 0; i < files.length; i++) {
12895
+ const file = files[i];
12896
+ process.stdout.write(chalk134.dim(` [${i + 1}/${files.length}] `));
12897
+ const summary = summariseSession(file);
12898
+ if (summary) {
12899
+ writeSummary(file, summary);
12900
+ succeeded++;
12901
+ process.stdout.write(`${chalk134.green("\u2713")} ${summary}
12902
+ `);
12903
+ } else {
12904
+ failed++;
12905
+ process.stdout.write(` ${chalk134.yellow("skip")}
12906
+ `);
12907
+ }
12908
+ }
12909
+ return { succeeded, failed };
12910
+ }
12911
+
12593
12912
  // src/commands/sessions/web/index.ts
12594
12913
  import { WebSocketServer } from "ws";
12595
12914
 
12596
12915
  // src/commands/sessions/web/handleRequest.ts
12597
- import { readFileSync as readFileSync34 } from "fs";
12916
+ import { readFileSync as readFileSync35 } from "fs";
12598
12917
  import { createRequire as createRequire2 } from "module";
12599
12918
 
12600
12919
  // src/commands/sessions/web/getHtml.ts
@@ -12629,7 +12948,7 @@ function createCssHandler(packageEntry) {
12629
12948
  return (_req, res) => {
12630
12949
  if (!cache) {
12631
12950
  const resolved = require3.resolve(packageEntry);
12632
- cache = readFileSync34(resolved, "utf-8");
12951
+ cache = readFileSync35(resolved, "utf-8");
12633
12952
  }
12634
12953
  res.writeHead(200, { "Content-Type": "text/css" });
12635
12954
  res.end(cache);
@@ -12646,46 +12965,79 @@ var routes3 = {
12646
12965
  };
12647
12966
  var handleRequest3 = createFallbackHandler(routes3, htmlHandler2);
12648
12967
 
12649
- // src/commands/sessions/web/handleSocket.ts
12650
- function dispatch(ws, manager, data) {
12651
- switch (data.type) {
12652
- case "create": {
12653
- const id = manager.spawn(
12654
- data.prompt,
12655
- data.cwd
12656
- );
12657
- ws.send(JSON.stringify({ type: "created", sessionId: id }));
12658
- break;
12659
- }
12660
- case "input":
12661
- manager.writeToSession(data.sessionId, data.data);
12662
- break;
12663
- case "resize":
12664
- manager.resizeSession(
12665
- data.sessionId,
12666
- data.cols,
12667
- data.rows
12668
- );
12669
- break;
12670
- case "resume": {
12671
- const id = manager.resume(
12672
- data.sessionId,
12673
- data.cwd,
12674
- data.name
12675
- );
12676
- ws.send(JSON.stringify({ type: "created", sessionId: id }));
12677
- break;
12678
- }
12679
- case "dismiss":
12680
- manager.dismissSession(data.sessionId);
12681
- break;
12682
- case "history":
12683
- manager.getHistory().then((history) => {
12684
- ws.send(JSON.stringify({ type: "history", sessions: history }));
12685
- });
12686
- break;
12968
+ // src/commands/sessions/web/handleRunConfigs.ts
12969
+ function handleRunConfigs(ws, cwd) {
12970
+ try {
12971
+ const { config, configDir } = loadConfigFrom(cwd);
12972
+ const configs = resolveRunConfigs(config.run, configDir);
12973
+ ws.send(
12974
+ JSON.stringify({
12975
+ type: "run-configs",
12976
+ configs: configs.map(({ name, params }) => ({ name, params }))
12977
+ })
12978
+ );
12979
+ } catch {
12980
+ ws.send(JSON.stringify({ type: "run-configs", configs: [] }));
12687
12981
  }
12688
12982
  }
12983
+
12984
+ // src/commands/sessions/web/dispatchMessage.ts
12985
+ function sendCreated(ws, id) {
12986
+ ws.send(JSON.stringify({ type: "created", sessionId: id }));
12987
+ }
12988
+ function handleCreate(ws, manager, data) {
12989
+ sendCreated(
12990
+ ws,
12991
+ manager.spawn(
12992
+ data.prompt,
12993
+ data.cwd
12994
+ )
12995
+ );
12996
+ }
12997
+ function handleCreateRun(ws, manager, data) {
12998
+ sendCreated(
12999
+ ws,
13000
+ manager.spawnRun(
13001
+ data.runName,
13002
+ data.runArgs ?? [],
13003
+ data.cwd
13004
+ )
13005
+ );
13006
+ }
13007
+ function handleResume(ws, manager, data) {
13008
+ sendCreated(
13009
+ ws,
13010
+ manager.resume(
13011
+ data.sessionId,
13012
+ data.cwd,
13013
+ data.name
13014
+ )
13015
+ );
13016
+ }
13017
+ function runConfigs(ws, _manager, data) {
13018
+ handleRunConfigs(ws, data.cwd);
13019
+ }
13020
+ function handleHistory(ws, manager) {
13021
+ manager.getHistory().then((history) => {
13022
+ ws.send(JSON.stringify({ type: "history", sessions: history }));
13023
+ });
13024
+ }
13025
+ var handlers = {
13026
+ create: handleCreate,
13027
+ "create-run": handleCreateRun,
13028
+ resume: handleResume,
13029
+ "run-configs": runConfigs,
13030
+ history: handleHistory,
13031
+ input: (_ws, m, d) => m.writeToSession(d.sessionId, d.data),
13032
+ resize: (_ws, m, d) => m.resizeSession(d.sessionId, d.cols, d.rows),
13033
+ retry: (_ws, m, d) => m.retrySession(d.sessionId),
13034
+ dismiss: (_ws, m, d) => m.dismissSession(d.sessionId)
13035
+ };
13036
+ function dispatchMessage(ws, manager, data) {
13037
+ handlers[data.type]?.(ws, manager, data);
13038
+ }
13039
+
13040
+ // src/commands/sessions/web/handleSocket.ts
12689
13041
  function handleSocket(ws, manager) {
12690
13042
  manager.addClient(ws);
12691
13043
  ws.on("message", (msg) => {
@@ -12695,42 +13047,58 @@ function handleSocket(ws, manager) {
12695
13047
  } catch {
12696
13048
  return;
12697
13049
  }
12698
- dispatch(ws, manager, data);
13050
+ dispatchMessage(ws, manager, data);
12699
13051
  });
12700
13052
  ws.on("close", () => {
12701
13053
  manager.removeClient(ws);
12702
13054
  });
12703
13055
  }
12704
13056
 
12705
- // src/commands/sessions/web/spawnClaude.ts
13057
+ // src/commands/sessions/web/repoPrefix.ts
13058
+ import * as path48 from "path";
13059
+ function repoPrefix(cwd) {
13060
+ if (!cwd) return "";
13061
+ return `${path48.basename(cwd)}/`;
13062
+ }
13063
+
13064
+ // src/commands/sessions/web/spawnPty.ts
12706
13065
  import * as pty from "node-pty";
12707
- function spawnClaude2(opts = {}) {
13066
+ function spawnPty(args, cwd) {
12708
13067
  const shell = process.platform === "win32" ? "cmd.exe" : process.env.SHELL ?? "bash";
12709
- const args = buildArgs(opts);
12710
- return pty.spawn(shell, args, {
13068
+ const shellArgs = process.platform === "win32" ? ["/c", ...args] : ["-c", `exec ${args.map(shellEscape).join(" ")}`];
13069
+ return pty.spawn(shell, shellArgs, {
12711
13070
  name: "xterm-256color",
12712
13071
  cols: 120,
12713
13072
  rows: 30,
12714
- cwd: opts.cwd ?? process.cwd(),
13073
+ cwd: cwd ?? process.cwd(),
12715
13074
  env: { ...process.env }
12716
13075
  });
12717
13076
  }
12718
- function buildArgs(opts) {
12719
- const claudeArgs = opts.resumeSessionId ? ["claude", "--resume", opts.resumeSessionId] : opts.prompt ? ["claude", opts.prompt] : ["claude"];
12720
- if (process.platform === "win32") {
12721
- return ["/c", ...claudeArgs];
12722
- }
12723
- return ["-c", `exec ${claudeArgs.map(shellEscape).join(" ")}`];
12724
- }
12725
13077
  function shellEscape(s) {
12726
13078
  return `'${s.replace(/'/g, "'\\''")}'`;
12727
13079
  }
12728
13080
 
13081
+ // src/commands/sessions/web/spawnClaude.ts
13082
+ function spawnClaude2(opts = {}) {
13083
+ return spawnPty(buildArgs(opts), opts.cwd);
13084
+ }
13085
+ function buildArgs(opts) {
13086
+ if (opts.resumeSessionId) return ["claude", "--resume", opts.resumeSessionId];
13087
+ if (opts.prompt) return ["claude", opts.prompt];
13088
+ return ["claude"];
13089
+ }
13090
+
13091
+ // src/commands/sessions/web/spawnRun.ts
13092
+ function spawnRun(opts) {
13093
+ return spawnPty(["assist", "run", opts.name, ...opts.args ?? []], opts.cwd);
13094
+ }
13095
+
12729
13096
  // src/commands/sessions/web/createSession.ts
12730
13097
  function createSession(id, prompt, cwd) {
12731
13098
  return {
12732
13099
  id,
12733
- name: prompt?.slice(0, 40) || `Session ${id}`,
13100
+ name: `${repoPrefix(cwd)}${prompt?.slice(0, 40) || `Session ${id}`}`,
13101
+ commandType: "claude",
12734
13102
  status: "running",
12735
13103
  startedAt: Date.now(),
12736
13104
  pty: spawnClaude2({ prompt, cwd }),
@@ -12739,10 +13107,27 @@ function createSession(id, prompt, cwd) {
12739
13107
  lastResizeAt: 0
12740
13108
  };
12741
13109
  }
13110
+ function createRunSession(id, runName, runArgs, cwd) {
13111
+ return {
13112
+ id,
13113
+ name: `${repoPrefix(cwd)}run: ${runName}`,
13114
+ commandType: "run",
13115
+ status: "running",
13116
+ startedAt: Date.now(),
13117
+ pty: spawnRun({ name: runName, args: runArgs, cwd }),
13118
+ scrollback: "",
13119
+ idleTimer: null,
13120
+ lastResizeAt: 0,
13121
+ runName,
13122
+ runArgs,
13123
+ cwd
13124
+ };
13125
+ }
12742
13126
  function resumeSession(id, sessionId, cwd, name) {
12743
13127
  return {
12744
13128
  id,
12745
- name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
13129
+ name: `${repoPrefix(cwd)}${name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`}`,
13130
+ commandType: "claude",
12746
13131
  status: "running",
12747
13132
  startedAt: Date.now(),
12748
13133
  pty: spawnClaude2({ resumeSessionId: sessionId, cwd }),
@@ -12752,112 +13137,23 @@ function resumeSession(id, sessionId, cwd, name) {
12752
13137
  };
12753
13138
  }
12754
13139
 
12755
- // src/commands/sessions/web/discoverSessions.ts
12756
- import * as fs24 from "fs";
12757
- import * as os from "os";
12758
- import * as path47 from "path";
12759
-
12760
- // src/commands/sessions/web/parseSessionFile.ts
12761
- import * as fs23 from "fs";
12762
- import * as path46 from "path";
12763
-
12764
- // src/commands/sessions/web/extractSessionMeta.ts
12765
- function extractSessionMeta(lines) {
12766
- let sessionId = "";
12767
- let cwd = "";
12768
- let timestamp = "";
12769
- let name = "";
12770
- for (const line of lines) {
12771
- const entry = safeParse(line);
12772
- if (!entry) continue;
12773
- sessionId ||= typeof entry.sessionId === "string" ? entry.sessionId : "";
12774
- timestamp ||= typeof entry.timestamp === "string" ? entry.timestamp : "";
12775
- cwd ||= typeof entry.cwd === "string" ? entry.cwd : "";
12776
- if (entry.type === "user" && !entry.isMeta) {
12777
- name = extractName(entry);
12778
- break;
12779
- }
12780
- }
12781
- return { sessionId, cwd, timestamp, name };
12782
- }
12783
- function safeParse(line) {
12784
- try {
12785
- return JSON.parse(line);
12786
- } catch {
12787
- return null;
12788
- }
12789
- }
12790
- function extractName(entry) {
12791
- const msg = entry.message;
12792
- const content = msg?.content;
12793
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
12794
- return text.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
13140
+ // src/commands/sessions/web/wsBroadcast.ts
13141
+ function wsSend(ws, msg) {
13142
+ if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(msg));
12795
13143
  }
12796
-
12797
- // src/commands/sessions/web/parseSessionFile.ts
12798
- async function parseSessionFile(filePath) {
12799
- let handle;
12800
- try {
12801
- handle = await fs23.promises.open(filePath, "r");
12802
- const buf = Buffer.alloc(16384);
12803
- const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
12804
- const lines = buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
12805
- const meta = extractSessionMeta(lines);
12806
- if (!meta.sessionId) return null;
12807
- const timestamp = meta.timestamp || (await fs23.promises.stat(filePath)).mtime.toISOString();
12808
- const project = meta.cwd ? path46.basename(meta.cwd) : dirNameToProject(filePath);
12809
- return {
12810
- sessionId: meta.sessionId,
12811
- name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
12812
- project,
12813
- cwd: meta.cwd,
12814
- timestamp
12815
- };
12816
- } catch {
12817
- return null;
12818
- } finally {
12819
- await handle?.close();
13144
+ function wsBroadcast(clients, msg) {
13145
+ const json = JSON.stringify(msg);
13146
+ for (const ws of clients) {
13147
+ if (ws.readyState === ws.OPEN) ws.send(json);
12820
13148
  }
12821
13149
  }
12822
- function dirNameToProject(filePath) {
12823
- const dirName = path46.basename(path46.dirname(filePath));
12824
- const parts = dirName.split("--");
12825
- return parts[parts.length - 1].replace(/-/g, "/");
12826
- }
12827
13150
 
12828
- // src/commands/sessions/web/discoverSessions.ts
12829
- async function discoverSessions() {
12830
- const projectsDir = path47.join(os.homedir(), ".claude", "projects");
12831
- let projectDirs;
12832
- try {
12833
- projectDirs = await fs24.promises.readdir(projectsDir);
12834
- } catch {
12835
- return [];
13151
+ // src/commands/sessions/web/replayScrollback.ts
13152
+ function replayScrollback(sessions, ws) {
13153
+ for (const s of sessions.values()) {
13154
+ if (s.scrollback)
13155
+ wsSend(ws, { type: "output", sessionId: s.id, data: s.scrollback });
12836
13156
  }
12837
- const sessions = [];
12838
- await Promise.all(
12839
- projectDirs.map(async (dirName) => {
12840
- const dirPath = path47.join(projectsDir, dirName);
12841
- let entries;
12842
- try {
12843
- entries = await fs24.promises.readdir(dirPath);
12844
- } catch {
12845
- return;
12846
- }
12847
- const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
12848
- await Promise.all(
12849
- jsonlFiles.map(async (file) => {
12850
- const filePath = path47.join(dirPath, file);
12851
- const session = await parseSessionFile(filePath);
12852
- if (session) sessions.push(session);
12853
- })
12854
- );
12855
- })
12856
- );
12857
- sessions.sort(
12858
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
12859
- );
12860
- return sessions;
12861
13157
  }
12862
13158
 
12863
13159
  // src/commands/sessions/web/scheduleIdle.ts
@@ -12873,17 +13169,6 @@ function clearIdle(session) {
12873
13169
  if (session.idleTimer) clearTimeout(session.idleTimer);
12874
13170
  }
12875
13171
 
12876
- // src/commands/sessions/web/wsBroadcast.ts
12877
- function wsSend(ws, msg) {
12878
- if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(msg));
12879
- }
12880
- function wsBroadcast(clients, msg) {
12881
- const json = JSON.stringify(msg);
12882
- for (const ws of clients) {
12883
- if (ws.readyState === ws.OPEN) ws.send(json);
12884
- }
12885
- }
12886
-
12887
13172
  // src/commands/sessions/web/wirePtyEvents.ts
12888
13173
  var MAX_SCROLLBACK = 256 * 1024;
12889
13174
  var RESIZE_GRACE_MS = 500;
@@ -12909,6 +13194,39 @@ function wirePtyEvents(session, clients, onStatusChange) {
12909
13194
  });
12910
13195
  }
12911
13196
 
13197
+ // src/commands/sessions/web/retrySession.ts
13198
+ function retrySession(session, clients, onStatusChange) {
13199
+ if (session.commandType !== "run" || !session.runName) return false;
13200
+ if (session.status !== "done") session.pty.kill();
13201
+ clearIdle(session);
13202
+ session.scrollback = "";
13203
+ session.status = "running";
13204
+ session.startedAt = Date.now();
13205
+ session.pty = spawnRun({
13206
+ name: session.runName,
13207
+ args: session.runArgs,
13208
+ cwd: session.cwd
13209
+ });
13210
+ wsBroadcast(clients, { type: "clear", sessionId: session.id });
13211
+ wirePtyEvents(session, clients, onStatusChange);
13212
+ scheduleIdle(session, () => onStatusChange(session, "waiting"));
13213
+ return true;
13214
+ }
13215
+
13216
+ // src/commands/sessions/web/toSessionInfo.ts
13217
+ function toSessionInfo({
13218
+ id,
13219
+ name,
13220
+ commandType,
13221
+ status: status2,
13222
+ startedAt,
13223
+ runName,
13224
+ runArgs,
13225
+ cwd
13226
+ }) {
13227
+ return { id, name, commandType, status: status2, startedAt, runName, runArgs, cwd };
13228
+ }
13229
+
12912
13230
  // src/commands/sessions/web/writeToSession.ts
12913
13231
  function writeToSession(sessions, id, data) {
12914
13232
  const s = sessions.get(id);
@@ -12947,39 +13265,34 @@ var SessionManager = class {
12947
13265
  cwd: this.repoCwd,
12948
13266
  sessions: this.listSessions()
12949
13267
  });
12950
- this.replayScrollback(ws);
12951
- }
12952
- replayScrollback(ws) {
12953
- for (const s of this.sessions.values()) {
12954
- if (s.scrollback)
12955
- wsSend(ws, { type: "output", sessionId: s.id, data: s.scrollback });
12956
- }
13268
+ replayScrollback(this.sessions, ws);
12957
13269
  }
12958
13270
  removeClient(ws) {
12959
13271
  this.clients.delete(ws);
12960
13272
  }
12961
- spawn(prompt, cwd) {
12962
- const id = String(this.nextId++);
12963
- const session = createSession(id, prompt, cwd);
13273
+ add(session) {
12964
13274
  this.wire(session);
12965
- return id;
13275
+ return session.id;
13276
+ }
13277
+ spawn(prompt, cwd) {
13278
+ return this.add(createSession(String(this.nextId++), prompt, cwd));
13279
+ }
13280
+ spawnRun(runName, runArgs, cwd) {
13281
+ return this.add(
13282
+ createRunSession(String(this.nextId++), runName, runArgs, cwd)
13283
+ );
12966
13284
  }
12967
13285
  resume(sessionId, cwd, name) {
12968
- const id = String(this.nextId++);
12969
- const session = resumeSession(id, sessionId, cwd, name);
12970
- this.wire(session);
12971
- return id;
13286
+ return this.add(resumeSession(String(this.nextId++), sessionId, cwd, name));
12972
13287
  }
13288
+ onStatusChange = (s, status2) => {
13289
+ s.status = status2;
13290
+ this.notify();
13291
+ };
12973
13292
  wire(session) {
12974
13293
  this.sessions.set(session.id, session);
12975
- wirePtyEvents(session, this.clients, (s, status2) => {
12976
- s.status = status2;
12977
- this.notify();
12978
- });
12979
- scheduleIdle(session, () => {
12980
- session.status = "waiting";
12981
- this.notify();
12982
- });
13294
+ wirePtyEvents(session, this.clients, this.onStatusChange);
13295
+ scheduleIdle(session, () => this.onStatusChange(session, "waiting"));
12983
13296
  this.notify();
12984
13297
  }
12985
13298
  writeToSession(id, data) {
@@ -12988,18 +13301,15 @@ var SessionManager = class {
12988
13301
  resizeSession(id, cols, rows) {
12989
13302
  resizeSession(this.sessions, id, cols, rows);
12990
13303
  }
13304
+ retrySession(id) {
13305
+ const s = this.sessions.get(id);
13306
+ if (s && retrySession(s, this.clients, this.onStatusChange)) this.notify();
13307
+ }
12991
13308
  dismissSession(id) {
12992
13309
  if (dismissSession(this.sessions, id)) this.notify();
12993
13310
  }
12994
13311
  listSessions() {
12995
- return [...this.sessions.values()].map(
12996
- ({ id, name, status: status2, startedAt }) => ({
12997
- id,
12998
- name,
12999
- status: status2,
13000
- startedAt
13001
- })
13002
- );
13312
+ return [...this.sessions.values()].map(toSessionInfo);
13003
13313
  }
13004
13314
  async getHistory() {
13005
13315
  return discoverSessions();
@@ -13033,13 +13343,14 @@ async function web3(options2) {
13033
13343
  function registerSessions(program2) {
13034
13344
  const cmd = program2.command("sessions").description("Web dashboard for Claude Code sessions").action(() => web3({ port: "3100" }));
13035
13345
  cmd.command("web").description("Start the sessions web dashboard").option("-p, --port <number>", "Port to listen on", "3100").action(web3);
13346
+ cmd.command("summarise").description("Generate one-line summaries for Claude sessions").option("-f, --force", "Re-generate all summaries, even existing ones").option("-n, --limit <count>", "Maximum number of sessions to summarise").action(summarise3);
13036
13347
  }
13037
13348
 
13038
13349
  // src/commands/statusLine.ts
13039
- import chalk135 from "chalk";
13350
+ import chalk136 from "chalk";
13040
13351
 
13041
13352
  // src/commands/buildLimitsSegment.ts
13042
- import chalk134 from "chalk";
13353
+ import chalk135 from "chalk";
13043
13354
  var FIVE_HOUR_SECONDS = 5 * 3600;
13044
13355
  var SEVEN_DAY_SECONDS = 7 * 86400;
13045
13356
  function formatTimeLeft(resetsAt) {
@@ -13062,10 +13373,10 @@ function projectUsage(pct, resetsAt, windowSeconds) {
13062
13373
  function colorizeRateLimit(pct, resetsAt, windowSeconds) {
13063
13374
  const label2 = `${Math.round(pct)}%`;
13064
13375
  const projected = projectUsage(pct, resetsAt, windowSeconds);
13065
- if (projected == null) return chalk134.green(label2);
13066
- if (projected > 100) return chalk134.red(label2);
13067
- if (projected > 75) return chalk134.yellow(label2);
13068
- return chalk134.green(label2);
13376
+ if (projected == null) return chalk135.green(label2);
13377
+ if (projected > 100) return chalk135.red(label2);
13378
+ if (projected > 75) return chalk135.yellow(label2);
13379
+ return chalk135.green(label2);
13069
13380
  }
13070
13381
  function formatLimit(pct, resetsAt, windowSeconds, fallbackLabel) {
13071
13382
  const timeLabel = resetsAt ? formatTimeLeft(resetsAt) : fallbackLabel;
@@ -13091,14 +13402,14 @@ function buildLimitsSegment(rateLimits) {
13091
13402
  }
13092
13403
 
13093
13404
  // src/commands/statusLine.ts
13094
- chalk135.level = 3;
13405
+ chalk136.level = 3;
13095
13406
  function formatNumber(num) {
13096
13407
  return num.toLocaleString("en-US");
13097
13408
  }
13098
13409
  function colorizePercent(pct) {
13099
13410
  const label2 = `${Math.round(pct)}%`;
13100
- if (pct > 80) return chalk135.red(label2);
13101
- if (pct > 40) return chalk135.yellow(label2);
13411
+ if (pct > 80) return chalk136.red(label2);
13412
+ if (pct > 40) return chalk136.yellow(label2);
13102
13413
  return label2;
13103
13414
  }
13104
13415
  async function statusLine() {
@@ -13113,29 +13424,29 @@ async function statusLine() {
13113
13424
  }
13114
13425
 
13115
13426
  // src/commands/sync.ts
13116
- import * as fs27 from "fs";
13427
+ import * as fs30 from "fs";
13117
13428
  import * as os2 from "os";
13118
- import * as path50 from "path";
13429
+ import * as path51 from "path";
13119
13430
  import { fileURLToPath as fileURLToPath7 } from "url";
13120
13431
 
13121
13432
  // src/commands/sync/syncClaudeMd.ts
13122
- import * as fs25 from "fs";
13123
- import * as path48 from "path";
13124
- import chalk136 from "chalk";
13433
+ import * as fs28 from "fs";
13434
+ import * as path49 from "path";
13435
+ import chalk137 from "chalk";
13125
13436
  async function syncClaudeMd(claudeDir, targetBase, options2) {
13126
- const source = path48.join(claudeDir, "CLAUDE.md");
13127
- const target = path48.join(targetBase, "CLAUDE.md");
13128
- const sourceContent = fs25.readFileSync(source, "utf-8");
13129
- if (fs25.existsSync(target)) {
13130
- const targetContent = fs25.readFileSync(target, "utf-8");
13437
+ const source = path49.join(claudeDir, "CLAUDE.md");
13438
+ const target = path49.join(targetBase, "CLAUDE.md");
13439
+ const sourceContent = fs28.readFileSync(source, "utf-8");
13440
+ if (fs28.existsSync(target)) {
13441
+ const targetContent = fs28.readFileSync(target, "utf-8");
13131
13442
  if (sourceContent !== targetContent) {
13132
13443
  console.log(
13133
- chalk136.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
13444
+ chalk137.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
13134
13445
  );
13135
13446
  console.log();
13136
13447
  printDiff(targetContent, sourceContent);
13137
13448
  const confirm = options2?.yes || await promptConfirm(
13138
- chalk136.red("Overwrite existing CLAUDE.md?"),
13449
+ chalk137.red("Overwrite existing CLAUDE.md?"),
13139
13450
  false
13140
13451
  );
13141
13452
  if (!confirm) {
@@ -13144,21 +13455,21 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
13144
13455
  }
13145
13456
  }
13146
13457
  }
13147
- fs25.copyFileSync(source, target);
13458
+ fs28.copyFileSync(source, target);
13148
13459
  console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
13149
13460
  }
13150
13461
 
13151
13462
  // src/commands/sync/syncSettings.ts
13152
- import * as fs26 from "fs";
13153
- import * as path49 from "path";
13154
- import chalk137 from "chalk";
13463
+ import * as fs29 from "fs";
13464
+ import * as path50 from "path";
13465
+ import chalk138 from "chalk";
13155
13466
  async function syncSettings(claudeDir, targetBase, options2) {
13156
- const source = path49.join(claudeDir, "settings.json");
13157
- const target = path49.join(targetBase, "settings.json");
13158
- const sourceContent = fs26.readFileSync(source, "utf-8");
13467
+ const source = path50.join(claudeDir, "settings.json");
13468
+ const target = path50.join(targetBase, "settings.json");
13469
+ const sourceContent = fs29.readFileSync(source, "utf-8");
13159
13470
  const mergedContent = JSON.stringify(JSON.parse(sourceContent), null, " ");
13160
- if (fs26.existsSync(target)) {
13161
- const targetContent = fs26.readFileSync(target, "utf-8");
13471
+ if (fs29.existsSync(target)) {
13472
+ const targetContent = fs29.readFileSync(target, "utf-8");
13162
13473
  const normalizedTarget = JSON.stringify(
13163
13474
  JSON.parse(targetContent),
13164
13475
  null,
@@ -13167,14 +13478,14 @@ async function syncSettings(claudeDir, targetBase, options2) {
13167
13478
  if (mergedContent !== normalizedTarget) {
13168
13479
  if (!options2?.yes) {
13169
13480
  console.log(
13170
- chalk137.yellow(
13481
+ chalk138.yellow(
13171
13482
  "\n\u26A0\uFE0F Warning: settings.json differs from existing file"
13172
13483
  )
13173
13484
  );
13174
13485
  console.log();
13175
13486
  printDiff(targetContent, mergedContent);
13176
13487
  const confirm = await promptConfirm(
13177
- chalk137.red("Overwrite existing settings.json?"),
13488
+ chalk138.red("Overwrite existing settings.json?"),
13178
13489
  false
13179
13490
  );
13180
13491
  if (!confirm) {
@@ -13184,29 +13495,29 @@ async function syncSettings(claudeDir, targetBase, options2) {
13184
13495
  }
13185
13496
  }
13186
13497
  }
13187
- fs26.writeFileSync(target, mergedContent);
13498
+ fs29.writeFileSync(target, mergedContent);
13188
13499
  console.log("Copied settings.json to ~/.claude/settings.json");
13189
13500
  }
13190
13501
 
13191
13502
  // src/commands/sync.ts
13192
13503
  var __filename4 = fileURLToPath7(import.meta.url);
13193
- var __dirname7 = path50.dirname(__filename4);
13504
+ var __dirname7 = path51.dirname(__filename4);
13194
13505
  async function sync(options2) {
13195
13506
  const config = loadConfig();
13196
13507
  const yes = options2?.yes ?? config.sync.autoConfirm;
13197
- const claudeDir = path50.join(__dirname7, "..", "claude");
13198
- const targetBase = path50.join(os2.homedir(), ".claude");
13508
+ const claudeDir = path51.join(__dirname7, "..", "claude");
13509
+ const targetBase = path51.join(os2.homedir(), ".claude");
13199
13510
  syncCommands(claudeDir, targetBase);
13200
13511
  await syncSettings(claudeDir, targetBase, { yes });
13201
13512
  await syncClaudeMd(claudeDir, targetBase, { yes });
13202
13513
  }
13203
13514
  function syncCommands(claudeDir, targetBase) {
13204
- const sourceDir = path50.join(claudeDir, "commands");
13205
- const targetDir = path50.join(targetBase, "commands");
13206
- fs27.mkdirSync(targetDir, { recursive: true });
13207
- const files = fs27.readdirSync(sourceDir);
13515
+ const sourceDir = path51.join(claudeDir, "commands");
13516
+ const targetDir = path51.join(targetBase, "commands");
13517
+ fs30.mkdirSync(targetDir, { recursive: true });
13518
+ const files = fs30.readdirSync(sourceDir);
13208
13519
  for (const file of files) {
13209
- fs27.copyFileSync(path50.join(sourceDir, file), path50.join(targetDir, file));
13520
+ fs30.copyFileSync(path51.join(sourceDir, file), path51.join(targetDir, file));
13210
13521
  console.log(`Copied ${file} to ${targetDir}`);
13211
13522
  }
13212
13523
  console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
@@ -13214,15 +13525,15 @@ function syncCommands(claudeDir, targetBase) {
13214
13525
 
13215
13526
  // src/commands/update.ts
13216
13527
  import { execSync as execSync42 } from "child_process";
13217
- import * as path51 from "path";
13528
+ import * as path52 from "path";
13218
13529
  function isGlobalNpmInstall(dir) {
13219
13530
  try {
13220
- const resolved = path51.resolve(dir);
13221
- if (resolved.split(path51.sep).includes("node_modules")) {
13531
+ const resolved = path52.resolve(dir);
13532
+ if (resolved.split(path52.sep).includes("node_modules")) {
13222
13533
  return true;
13223
13534
  }
13224
13535
  const globalPrefix = execSync42("npm prefix -g", { stdio: "pipe" }).toString().trim();
13225
- return resolved.toLowerCase().startsWith(path51.resolve(globalPrefix).toLowerCase());
13536
+ return resolved.toLowerCase().startsWith(path52.resolve(globalPrefix).toLowerCase());
13226
13537
  } catch {
13227
13538
  return false;
13228
13539
  }