harlay 0.2.0 → 0.3.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/harlay.mjs CHANGED
@@ -581,14 +581,15 @@ async function write(d, o, dir) {
581
581
  const r = new Redactor();
582
582
  const prompt = launchPrompt(path);
583
583
  const record = buildRecord(d, id, o.previousId, { target: o.target, mode: o.mode, prompt }, r);
584
- await writeFile2(tmp, JSON.stringify(record, null, 2) + "\n");
584
+ const text = JSON.stringify(record, null, 2) + "\n";
585
+ await writeFile2(tmp, text);
585
586
  try {
586
587
  await link(tmp, path);
587
588
  } catch (e) {
588
589
  if (e.code === "EEXIST") continue;
589
590
  throw e;
590
591
  }
591
- return { id, path, prompt, redactions: addCounts(d.redactions ?? {}, r.counts) };
592
+ return { id, path, prompt, redactions: addCounts(d.redactions ?? {}, r.counts), chars: text.length };
592
593
  }
593
594
  } finally {
594
595
  await rm(tmp, { force: true });
@@ -1442,10 +1443,12 @@ function splitShell(cmd, pipes = false) {
1442
1443
  if (c === '"' || c === "'") {
1443
1444
  quote = c;
1444
1445
  cur += c;
1445
- } else if (c === ";" || c === "&" && cmd[i + 1] === "&" || c === "|" && cmd[i + 1] === "|") {
1446
+ } else if (c === "\\" && cmd[i + 1] === "\n") {
1447
+ i++;
1448
+ } else if (c === ";" || c === "\n" || c === "&" && cmd[i + 1] === "&" || c === "|" && cmd[i + 1] === "|") {
1446
1449
  out.push(cur);
1447
1450
  cur = "";
1448
- if (c !== ";") i++;
1451
+ if (c === "&" || c === "|") i++;
1449
1452
  } else if (c === "|" && pipes) {
1450
1453
  out.push(cur);
1451
1454
  cur = "";
@@ -1486,13 +1489,29 @@ function findLastFailure(cmds) {
1486
1489
  }
1487
1490
  var RESULTS_MAX = 12;
1488
1491
  var REFERENCES_MAX = 15;
1492
+ var HEREDOC_RE = /(?<!<)<<(?!<)(-?)\s*(['"]?)([A-Za-z_][\w.-]*)\2/g;
1493
+ function stripHeredocs(cmd) {
1494
+ if (!cmd.includes("<<")) return cmd;
1495
+ const lines2 = cmd.split("\n");
1496
+ const out = [];
1497
+ for (let i = 0; i < lines2.length; i++) {
1498
+ out.push(lines2[i]);
1499
+ for (const m of lines2[i].matchAll(HEREDOC_RE)) {
1500
+ const tabs = m[1] === "-";
1501
+ const end = lines2.findIndex((l, j) => j > i && (tabs ? l.replace(/^\t+/, "") : l) === m[3]);
1502
+ if (end !== -1) i = end;
1503
+ }
1504
+ }
1505
+ return out.join("\n");
1506
+ }
1489
1507
  var REDIRECT_RE = /\s+(?:\d?>>?|&>)\s*(?:&\d|\/dev\/null)/g;
1490
1508
  function checkOf(cmd) {
1491
1509
  let piped = false;
1492
- const parts = splitShell(cmd).filter((s) => !isCd(s) && !isProbeCommand(s)).map((s) => {
1510
+ const parts = splitShell(stripHeredocs(cmd)).filter((s) => !isCd(s) && !isProbeCommand(s)).flatMap((s) => {
1493
1511
  const stages = splitShell(s, true);
1494
1512
  if (stages.length > 1) piped = true;
1495
- return stages[0].replace(REDIRECT_RE, "").trim();
1513
+ const first = stages[0]?.replace(REDIRECT_RE, "").trim();
1514
+ return first ? [first] : [];
1496
1515
  });
1497
1516
  return parts.length ? { key: parts.join(" && "), piped } : null;
1498
1517
  }
@@ -1760,7 +1779,14 @@ async function runHandoff(o) {
1760
1779
  const git2 = await takeGitSnapshot(cwd);
1761
1780
  const previous = await readPreviousHandoff(o.store, git2?.root ?? cwd);
1762
1781
  const data = extractHandoff(session, git2, { now: o.now ?? /* @__PURE__ */ new Date(), reason: o.reason, previous });
1782
+ o.onProgress?.({
1783
+ stage: "extracted",
1784
+ messages: session.timeline.filter((t) => t.kind === "user" || t.kind === "assistant").length,
1785
+ filesChanged: git2 ? git2.files.length : data.filesTouched.length,
1786
+ chars: sessionChars(session)
1787
+ });
1763
1788
  const written = await writeHandoff(data, { target: o.target, mode: o.mode, previousId: data.carriedFrom, store: o.store });
1789
+ o.onProgress?.({ stage: "written", path: written.path, chars: written.chars });
1764
1790
  if (o.launch === false) return { written, plan: null, data };
1765
1791
  let plan;
1766
1792
  try {
@@ -1771,6 +1797,16 @@ async function runHandoff(o) {
1771
1797
  }
1772
1798
  return { written, plan, data };
1773
1799
  }
1800
+ function sessionChars(s) {
1801
+ let n = 0;
1802
+ for (const t of s.timeline) {
1803
+ if (t.kind === "user" || t.kind === "assistant") n += t.text.length;
1804
+ else if (t.kind === "command") n += t.run.cmd.length + t.run.output.length;
1805
+ else if (t.kind === "edit") n += t.edit.path.length + (t.edit.before?.length ?? 0) + (t.edit.after?.length ?? 0);
1806
+ else n += t.path.length;
1807
+ }
1808
+ return n;
1809
+ }
1774
1810
 
1775
1811
  // ../core/src/delivery.ts
1776
1812
  var SLACK_MS = 2e3;
@@ -2001,6 +2037,7 @@ var TAIL_BYTES = 1024 * 1024;
2001
2037
  // ../core/src/usage/claude.ts
2002
2038
  var HOUR = 36e5;
2003
2039
  var SAMPLE_MAX_AGE_MS = 30 * 24 * HOUR;
2040
+ var LIMIT_TRUST_MS = 7 * 24 * HOUR;
2004
2041
  var PACE_MS2 = 30 * 6e4;
2005
2042
 
2006
2043
  // ../core/src/usage/bridge.ts
@@ -2195,7 +2232,7 @@ async function installApp(opts) {
2195
2232
  }
2196
2233
 
2197
2234
  // src/main.ts
2198
- var VERSION = "0.2.0";
2235
+ var VERSION = "0.3.0";
2199
2236
  var HELP = `harlay ${VERSION} \u2014 hand off a stalled AI coding session to another agent
2200
2237
 
2201
2238
  Usage:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harlay",
3
- "version": "0.2.0",
4
- "description": "Hand off a stalled AI coding session (Claude Code, Codex, Cursor) to another agent. CLI and macOS menu bar app.",
3
+ "version": "0.3.0",
4
+ "description": "Move a coding task between AI agents (Claude Code, Codex, Cursor) when one stops. macOS desktop app and CLI.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "bin": {