@sma1lboy/rove 0.9.243 → 0.9.245

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.
Files changed (86) hide show
  1. package/dist/cli/{chunk-7dmqm7e4.js → chunk-1g9vw9az.js} +1 -1
  2. package/dist/cli/chunk-2bamcday.js +2 -0
  3. package/dist/cli/chunk-2mrwdd50.js +10 -0
  4. package/dist/cli/chunk-2pqq8apv.js +2 -0
  5. package/dist/cli/chunk-2zrphhdf.js +2 -0
  6. package/dist/cli/{chunk-wp3ts69s.js → chunk-3pdg2afa.js} +1 -1
  7. package/dist/cli/{chunk-c2m55b72.js → chunk-4wvz9hk7.js} +1 -1
  8. package/dist/cli/chunk-4xns5j33.js +3 -0
  9. package/dist/cli/chunk-55ajzrj6.js +3 -0
  10. package/dist/cli/chunk-5bfern2b.js +2 -0
  11. package/dist/cli/{chunk-z5rdqrqy.js → chunk-6c5hh9zq.js} +1 -1
  12. package/dist/cli/{chunk-estaz0y7.js → chunk-6n360fss.js} +1 -1
  13. package/dist/cli/{chunk-tvk46rfs.js → chunk-78xjh4gg.js} +1 -1
  14. package/dist/cli/chunk-7mq1ascs.js +2 -0
  15. package/dist/cli/chunk-8pna13mr.js +2 -0
  16. package/dist/cli/{chunk-v7y0vz96.js → chunk-9bryj0y4.js} +2 -2
  17. package/dist/cli/{chunk-ekv0jbmk.js → chunk-9kgvdtgs.js} +1 -1
  18. package/dist/cli/chunk-9q1m5qz0.js +2 -0
  19. package/dist/cli/{chunk-x5tdcgnt.js → chunk-9tcfv23g.js} +1 -1
  20. package/dist/cli/{chunk-4wfvajxy.js → chunk-ay9kst6z.js} +1 -1
  21. package/dist/cli/{chunk-e005a00g.js → chunk-b4mynnn7.js} +1 -1
  22. package/dist/cli/chunk-catrr0z3.js +3 -0
  23. package/dist/cli/{chunk-p7x6w8b5.js → chunk-dgt1yq74.js} +1 -1
  24. package/dist/cli/{chunk-pmtwz3nc.js → chunk-e3g79zpp.js} +1 -1
  25. package/dist/cli/{chunk-74wsssjb.js → chunk-e9vye1ev.js} +1 -1
  26. package/dist/cli/{chunk-f0qamp5r.js → chunk-easxds8p.js} +1 -1
  27. package/dist/cli/chunk-ef47c3t0.js +2 -0
  28. package/dist/cli/chunk-es1fh2br.js +9 -0
  29. package/dist/cli/{chunk-zj7fg394.js → chunk-f7mewac2.js} +1 -1
  30. package/dist/cli/chunk-gcxcq22q.js +5 -0
  31. package/dist/cli/{chunk-3b2xqd85.js → chunk-hh50vc40.js} +1 -1
  32. package/dist/cli/{chunk-y765gfe8.js → chunk-j519mpnw.js} +1 -1
  33. package/dist/cli/{chunk-fvtaqqb2.js → chunk-jwex6n4q.js} +1 -1
  34. package/dist/cli/{chunk-1t1hw539.js → chunk-k5py2g4y.js} +1 -1
  35. package/dist/cli/chunk-k67vbszb.js +3 -0
  36. package/dist/cli/{chunk-g4c4ffqp.js → chunk-k9avxd47.js} +2 -2
  37. package/dist/cli/chunk-m92x8ac0.js +2 -0
  38. package/dist/cli/{chunk-2c9tsrmd.js → chunk-mv3t42z7.js} +1 -1
  39. package/dist/cli/{chunk-83gpjtxp.js → chunk-n91640kq.js} +1 -1
  40. package/dist/cli/{chunk-bakn8g9p.js → chunk-nmg8tyvv.js} +1 -1
  41. package/dist/cli/{chunk-6vccp9s3.js → chunk-pzwbhy2n.js} +1 -1
  42. package/dist/cli/{chunk-28bbkgac.js → chunk-q1s44by7.js} +1 -1
  43. package/dist/cli/{chunk-wa7xqf88.js → chunk-qd0vmh6n.js} +1 -1
  44. package/dist/cli/{chunk-9rdmezmh.js → chunk-rn1kwc71.js} +1 -1
  45. package/dist/cli/{chunk-kakzf2er.js → chunk-rwzaq58y.js} +1 -1
  46. package/dist/cli/{chunk-2v9cf20n.js → chunk-s2qesxrk.js} +10 -10
  47. package/dist/cli/{chunk-v472a87w.js → chunk-t5wf730v.js} +3 -3
  48. package/dist/cli/chunk-t7j8g9f6.js +3 -0
  49. package/dist/cli/{chunk-b2hwgdxc.js → chunk-v0p9ec50.js} +1 -1
  50. package/dist/cli/{chunk-hjfb9ejy.js → chunk-v5vchgm4.js} +2 -2
  51. package/dist/cli/{chunk-rfgwj1m7.js → chunk-wpfj0ex4.js} +4 -4
  52. package/dist/cli/{chunk-362shb97.js → chunk-x1d1mq5c.js} +1 -1
  53. package/dist/cli/{chunk-5z8h1ssx.js → chunk-xeeh86mf.js} +1 -1
  54. package/dist/cli/{chunk-xx74c65n.js → chunk-xhcws669.js} +1 -1
  55. package/dist/cli/{chunk-8192pr2q.js → chunk-xnn96zvb.js} +1 -1
  56. package/dist/cli/{chunk-dyb99ygk.js → chunk-xrwcd67f.js} +1 -1
  57. package/dist/cli/{chunk-bkpy1qxy.js → chunk-xvz9gje4.js} +1 -1
  58. package/dist/cli/{chunk-whs6f9hz.js → chunk-xwx45wr5.js} +1 -1
  59. package/dist/cli/chunk-ya4w6nqz.js +3 -0
  60. package/dist/cli/{chunk-2pe059hg.js → chunk-zh6fadgw.js} +1 -1
  61. package/dist/cli/{chunk-sz6ednnh.js → chunk-zpcz6bnm.js} +2 -2
  62. package/dist/cli/index.js +3 -3
  63. package/dist/cli/pty-host-node.mjs +215 -4
  64. package/dist/cli/rove.js +1 -1
  65. package/dist/completions/rove.bash +1 -1
  66. package/dist/completions/rove.fish +1 -1
  67. package/dist/completions/rove.zsh +1 -1
  68. package/dist/skills/rove/SKILL.md +11 -14
  69. package/dist/skills/rove/references/api-flags.md +54 -47
  70. package/package.json +2 -2
  71. package/dist/cli/chunk-0f1kdebe.js +0 -2
  72. package/dist/cli/chunk-0mj358p5.js +0 -3
  73. package/dist/cli/chunk-2andp9gs.js +0 -2
  74. package/dist/cli/chunk-d46ffd1f.js +0 -9
  75. package/dist/cli/chunk-dgs0m8r4.js +0 -3
  76. package/dist/cli/chunk-dkgxz9hf.js +0 -5
  77. package/dist/cli/chunk-eg4yr1he.js +0 -2
  78. package/dist/cli/chunk-gmh4pjfs.js +0 -2
  79. package/dist/cli/chunk-kb6xw0fw.js +0 -2
  80. package/dist/cli/chunk-kbvjnhvx.js +0 -2
  81. package/dist/cli/chunk-m4f5h05c.js +0 -10
  82. package/dist/cli/chunk-m6q44xc0.js +0 -2
  83. package/dist/cli/chunk-qvfdqrbf.js +0 -3
  84. package/dist/cli/chunk-rjq1ryb1.js +0 -2
  85. package/dist/cli/chunk-v39saayz.js +0 -3
  86. package/dist/cli/chunk-zpy3e81m.js +0 -2
@@ -1395,6 +1395,170 @@ function clearFrozenSessions(dir = defaultPtyFreezeDir()) {
1395
1395
  } catch {}
1396
1396
  }
1397
1397
 
1398
+ // ../rove-daemon/src/daemon/osc-signals.ts
1399
+ import { StringDecoder as StringDecoder2 } from "node:string_decoder";
1400
+ function createOscSignals() {
1401
+ return { carry: "", decoder: new StringDecoder2("utf8"), records: new Map, contexts: [] };
1402
+ }
1403
+ function freeText(raw, encodedLimit, decodedLimit) {
1404
+ if (raw === undefined)
1405
+ return;
1406
+ if (raw.length > encodedLimit || !/^[A-Za-z0-9+/]*={0,2}$/.test(raw) || raw.replace(/=+$/, "").length % 4 === 1)
1407
+ throw new Error("Invalid base64");
1408
+ const bytes = Buffer.from(raw, "base64");
1409
+ if (bytes.length > decodedLimit)
1410
+ throw new Error("Text exceeds limit");
1411
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1412
+ if (/[\x00-\x1f\x7f-\x9f]/u.test(text))
1413
+ throw new Error("Control character");
1414
+ return text;
1415
+ }
1416
+ function applyProgramStatus(signals, body, now) {
1417
+ if (Buffer.byteLength(body) + 10 > 4096)
1418
+ return;
1419
+ const pairs = Object.create(null);
1420
+ for (const pair of body.split(":")) {
1421
+ const split = pair.indexOf("=");
1422
+ if (split < 1)
1423
+ continue;
1424
+ const key = pair.slice(0, split).trim();
1425
+ const value = pair.slice(split + 1).trim();
1426
+ if (key.length > 16)
1427
+ return;
1428
+ if (/^[a-z]+$/.test(key) && /^[A-Za-z0-9_.,+/=-]*$/.test(value))
1429
+ pairs[key] = value;
1430
+ }
1431
+ const id = pairs.id ?? "";
1432
+ if (pairs.id !== undefined && (id.length > 128 || id.split("/").length > 8 || !/^[A-Za-z0-9_.+-]{1,32}(\/[A-Za-z0-9_.+-]{1,32})*$/.test(id)))
1433
+ return;
1434
+ const state = pairs.state;
1435
+ if (!["idle", "working", "done", "blocked", "error", "clear"].includes(state ?? ""))
1436
+ return;
1437
+ let msg;
1438
+ let title;
1439
+ try {
1440
+ msg = freeText(pairs.msg, 2732, 2048);
1441
+ title = freeText(pairs.title, 256, 192);
1442
+ } catch {
1443
+ return;
1444
+ }
1445
+ if (pairs.app !== undefined && pairs.app.length > 32)
1446
+ return;
1447
+ if (state === "clear") {
1448
+ for (const key of signals.records.keys())
1449
+ if (!id || key === id || key.startsWith(`${id}/`))
1450
+ signals.records.delete(key);
1451
+ return;
1452
+ }
1453
+ if (state !== "idle" && state !== "working" && state !== "done" && state !== "blocked" && state !== "error")
1454
+ return;
1455
+ const kind = pairs.kind === "permission" || pairs.kind === "question" || pairs.kind === "auth" ? pairs.kind : undefined;
1456
+ const progress = pairs.progress !== undefined && /^\d{1,3}$/.test(pairs.progress) && Number(pairs.progress) <= 100 ? Number(pairs.progress) : undefined;
1457
+ signals.records.delete(id);
1458
+ signals.records.set(id, {
1459
+ id,
1460
+ state,
1461
+ at: now,
1462
+ ...msg !== undefined ? { msg } : {},
1463
+ ...title !== undefined ? { title } : {},
1464
+ ...pairs.app && /^[A-Za-z0-9_.+-]{1,32}$/.test(pairs.app) ? { app: pairs.app } : {},
1465
+ ...state === "blocked" && kind ? { kind } : {},
1466
+ ...(state === "working" || state === "blocked") && progress !== undefined ? { progress } : {}
1467
+ });
1468
+ if (signals.records.size > 64) {
1469
+ const first = signals.records.keys().next().value;
1470
+ if (first !== undefined)
1471
+ signals.records.delete(first);
1472
+ }
1473
+ }
1474
+ function applyContext(signals, body) {
1475
+ if (Buffer.byteLength(body) > 4096 || /[\x00-\x1f\x7f]/.test(body))
1476
+ return;
1477
+ const [head = "", ...fields] = body.split(";");
1478
+ const split = head.indexOf("=");
1479
+ const operation = head.slice(0, split);
1480
+ const decodeContextField = (text) => text.replace(/\\x(3b|5c)/g, (_, hex) => hex === "3b" ? ";" : "\\");
1481
+ const id = decodeContextField(head.slice(split + 1));
1482
+ if (operation !== "start" && operation !== "end" || !/^[ -~]{1,64}$/.test(id))
1483
+ return;
1484
+ const index = signals.contexts.findIndex((c) => c.id === id);
1485
+ if (operation === "end") {
1486
+ if (index !== -1)
1487
+ signals.contexts.splice(index);
1488
+ return;
1489
+ }
1490
+ if (index !== -1)
1491
+ signals.contexts.splice(index);
1492
+ if (signals.contexts.length >= 32)
1493
+ return;
1494
+ const metadata = Object.create(null);
1495
+ for (const field of fields) {
1496
+ const i = field.indexOf("=");
1497
+ if (i > 0)
1498
+ metadata[field.slice(0, i)] = decodeContextField(field.slice(i + 1));
1499
+ }
1500
+ signals.contexts.push({ id, metadata });
1501
+ }
1502
+ function clearTransientStatuses(signals) {
1503
+ for (const [id, record] of signals.records) {
1504
+ if (record.state === "working" || record.state === "blocked" || record.state === "idle")
1505
+ signals.records.delete(id);
1506
+ }
1507
+ }
1508
+ function scanOscSignals(signals, bytes, now = Date.now(), onRootChange) {
1509
+ const text = signals.carry + signals.decoder.write(bytes);
1510
+ signals.carry = "";
1511
+ let queries = 0;
1512
+ for (let i = 0;i < text.length; i++) {
1513
+ if (text[i] !== "\x1B")
1514
+ continue;
1515
+ if (i === text.length - 1) {
1516
+ signals.carry = "\x1B";
1517
+ break;
1518
+ }
1519
+ if (text[i + 1] === "c") {
1520
+ const hadRoot = signals.records.has("");
1521
+ signals.records.clear();
1522
+ if (hadRoot)
1523
+ onRootChange?.(null);
1524
+ signals.contexts = [];
1525
+ i++;
1526
+ continue;
1527
+ }
1528
+ if (text[i + 1] !== "]")
1529
+ continue;
1530
+ const begin = i;
1531
+ i += 2;
1532
+ let end = i;
1533
+ while (end < text.length && text[end] !== "\x07" && text[end] !== "\x1B")
1534
+ end++;
1535
+ if (end === text.length || text[end] === "\x1B" && end + 1 === text.length) {
1536
+ if (text.length - begin <= 8192)
1537
+ signals.carry = text.slice(begin);
1538
+ break;
1539
+ }
1540
+ if (text[end] === "\x1B" && text[end + 1] !== "\\") {
1541
+ i = end - 1;
1542
+ continue;
1543
+ }
1544
+ const before = signals.records.get("");
1545
+ const body = text.slice(i, end);
1546
+ if (body === "7501;?")
1547
+ queries++;
1548
+ else if (body.startsWith("7501;"))
1549
+ applyProgramStatus(signals, body.slice(5), now);
1550
+ else if (body.startsWith("3008;"))
1551
+ applyContext(signals, body.slice(5));
1552
+ else if (body === "133;A" || body.startsWith("133;A;"))
1553
+ clearTransientStatuses(signals);
1554
+ const after = signals.records.get("");
1555
+ if (before !== after)
1556
+ onRootChange?.(after ?? null);
1557
+ i = end + (text[end] === "\x1B" ? 1 : 0);
1558
+ }
1559
+ return queries;
1560
+ }
1561
+
1398
1562
  // ../rove-daemon/src/daemon/pty-child-controller.ts
1399
1563
  import { randomUUID as randomUUID6 } from "node:crypto";
1400
1564
 
@@ -1476,7 +1640,7 @@ function embeddedTerminalEnv(base, overrides = {}) {
1476
1640
 
1477
1641
  // ../rove-daemon/src/daemon/pty-host-types.ts
1478
1642
  import { randomUUID as randomUUID5 } from "node:crypto";
1479
- import { StringDecoder as StringDecoder2 } from "node:string_decoder";
1643
+ import { StringDecoder as StringDecoder3 } from "node:string_decoder";
1480
1644
  function freshSessionState(key, spec, argv) {
1481
1645
  return {
1482
1646
  key,
@@ -1492,7 +1656,7 @@ function freshSessionState(key, spec, argv) {
1492
1656
  command: argv,
1493
1657
  title: "",
1494
1658
  titleCarry: "",
1495
- titleDecoder: new StringDecoder2("utf8"),
1659
+ titleDecoder: new StringDecoder3("utf8"),
1496
1660
  colorQueryCarry: "",
1497
1661
  defaultColors: parseTerminalDefaultColors(spec.defaultColors) ?? DEFAULT_TERMINAL_COLORS,
1498
1662
  modes: freshModeTrack(),
@@ -1517,6 +1681,9 @@ function sessionInfo(s) {
1517
1681
  pid: s.proc?.pid ?? null,
1518
1682
  command: s.command,
1519
1683
  title: s.title,
1684
+ programStatusEvent: s.programStatusEvent,
1685
+ programStatus: s.oscSignals?.records.get(""),
1686
+ contexts: s.oscSignals?.contexts,
1520
1687
  totalBytes: s.totalBytes,
1521
1688
  parked: s.parked,
1522
1689
  parkedScreenBytes: s.parkedScreenBytes,
@@ -1672,6 +1839,9 @@ class PtyChildController {
1672
1839
  }
1673
1840
  startChild(session) {
1674
1841
  session.generation = randomUUID6();
1842
+ session.oscSignals = undefined;
1843
+ session.programStatusEvent = undefined;
1844
+ this.reportStatus(session, null);
1675
1845
  try {
1676
1846
  session.proc = (this.deps.driver ?? bunTerminalDriver())({
1677
1847
  argv: [...session.command],
@@ -1708,6 +1878,12 @@ class PtyChildController {
1708
1878
  if (!session.alive)
1709
1879
  return;
1710
1880
  session.alive = false;
1881
+ if (session.oscSignals) {
1882
+ const before = session.oscSignals.records.get("");
1883
+ clearTransientStatuses(session.oscSignals);
1884
+ if (before !== session.oscSignals.records.get(""))
1885
+ this.reportStatus(session, null);
1886
+ }
1711
1887
  const sessionExit = {
1712
1888
  code: exit?.code ?? null,
1713
1889
  signal: exit?.signal ?? null,
@@ -1719,12 +1895,27 @@ class PtyChildController {
1719
1895
  } catch {}
1720
1896
  this.deps.onExit?.(session, sessionExit);
1721
1897
  }
1898
+ reportStatus(session, status) {
1899
+ const event = {
1900
+ key: session.key,
1901
+ generation: session.generation,
1902
+ revision: (session.programStatusEvent?.revision ?? 0) + 1,
1903
+ at: Date.now(),
1904
+ status
1905
+ };
1906
+ session.programStatusEvent = event;
1907
+ this.deps.onProgramStatus?.(event);
1908
+ }
1722
1909
  onData(session, data) {
1723
1910
  const buf = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
1724
1911
  const text = buf.toString("latin1");
1912
+ session.oscSignals ??= createOscSignals();
1913
+ const queries = scanOscSignals(session.oscSignals, buf, Date.now(), (status) => this.reportStatus(session, status));
1725
1914
  const colorQueries = foldDefaultColorQueries(session.colorQueryCarry, text);
1726
1915
  session.colorQueryCarry = colorQueries.carry;
1727
1916
  const replies = colorQueries.slots.map((slot) => formatDefaultColorReply(slot, session.defaultColors));
1917
+ for (let i = 0;i < Math.min(queries, 16); i++)
1918
+ replies.push("\x1B]7501;?\x1B\\");
1728
1919
  const modeReplies = scanTerminalModes(session.modes, text);
1729
1920
  if (session.emulatorSinks.size === 0)
1730
1921
  replies.push(...modeReplies);
@@ -1833,6 +2024,7 @@ class PtyHost {
1833
2024
  this.scrollbackCap = opts.scrollbackCap ?? DEFAULT_SCROLLBACK_CAP;
1834
2025
  this.childController = new PtyChildController({
1835
2026
  driver: opts.driver,
2027
+ onProgramStatus: (event) => this.opts.onProgramStatus?.(event),
1836
2028
  scrollbackCap: this.scrollbackCap,
1837
2029
  onSessionStart: (spare) => {
1838
2030
  if (!spare)
@@ -1983,6 +2175,12 @@ class PtyHost {
1983
2175
  return false;
1984
2176
  this.sessions.delete(from);
1985
2177
  session.key = to;
2178
+ if (session.programStatusEvent) {
2179
+ const previous = session.programStatusEvent;
2180
+ this.opts.onProgramStatus?.({ ...previous, revision: previous.revision + 1, at: Date.now(), status: null });
2181
+ session.programStatusEvent = { ...previous, key: to, revision: previous.revision + 1, at: Date.now() };
2182
+ this.opts.onProgramStatus?.(session.programStatusEvent);
2183
+ }
1986
2184
  this.sessions.set(to, session);
1987
2185
  this.opts.freeze?.drop(from);
1988
2186
  this.maybeFreeze(session, true);
@@ -2093,7 +2291,7 @@ import { dirname as dirname6 } from "node:path";
2093
2291
 
2094
2292
  // ../rove-daemon/src/client/index.ts
2095
2293
  import { connect } from "node:net";
2096
- import { StringDecoder as StringDecoder3 } from "node:string_decoder";
2294
+ import { StringDecoder as StringDecoder4 } from "node:string_decoder";
2097
2295
 
2098
2296
  // ../rove-daemon/src/client/client-log.ts
2099
2297
  import { appendFile, mkdir as mkdir3, rename as rename2, stat } from "node:fs/promises";
@@ -2362,7 +2560,7 @@ class RoveDaemonClient {
2362
2560
  };
2363
2561
  socket.once("connect", onConnect);
2364
2562
  socket.once("error", onError);
2365
- const decoder = new StringDecoder3("utf8");
2563
+ const decoder = new StringDecoder4("utf8");
2366
2564
  this.buffer = "";
2367
2565
  socket.on("data", (chunk) => this.onData(decoder.write(chunk)));
2368
2566
  socket.on("close", () => this.onSocketClose(socket));
@@ -2684,6 +2882,13 @@ function dispatchPtyRequest(req, client, deps) {
2684
2882
  ptys.detach(requireString(payload, "key"), client, payload.parked === true, typeof payload.parkedScreenBytes === "number" ? payload.parkedScreenBytes : 0);
2685
2883
  }
2686
2884
  return {};
2885
+ case "pty.watchStatus":
2886
+ client.watchProgramStatus = true;
2887
+ for (const session of ptys.list()) {
2888
+ if (session.programStatusEvent)
2889
+ deps.writeFrame(client, { type: "event", name: "pty.programStatus", payload: session.programStatusEvent });
2890
+ }
2891
+ return { supported: true };
2687
2892
  case "pty.list":
2688
2893
  return {
2689
2894
  pid: process.pid,
@@ -2804,6 +3009,12 @@ async function startPtyHostServer(options = {}) {
2804
3009
  }, orphanCheckMs);
2805
3010
  orphanTimer.unref?.();
2806
3011
  const ptys = new PtyHost({
3012
+ onProgramStatus: (event) => {
3013
+ for (const client of clients) {
3014
+ if (client.watchProgramStatus)
3015
+ writeFrame(client, { type: "event", name: "pty.programStatus", payload: event });
3016
+ }
3017
+ },
2807
3018
  onSessionStart: cancelIdle,
2808
3019
  onSessionEnd: () => {
2809
3020
  if (ptys.liveCount() === 0)
package/dist/cli/rove.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{join as n}from"node:path";import{createInterface as a}from"node:readline/promises";import{pathToFileURL as ss}from"node:url";import{spawnSync as Q}from"node:child_process";import{constants as d,accessSync as R}from"node:fs";import{homedir as I}from"node:os";import{basename as U,delimiter as o,dirname as _,join as k}from"node:path";import{fileURLToPath as T}from"node:url";var H={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.243",description:"Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{rove:"dist/cli/rove.js"},files:["dist/cli","dist/completions","dist/web-ui","dist/skills","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:mock":"ROVE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts","build:rubric":"bun run scripts/build-tier-rubric.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"ROVE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"ROVE_INCLUDE_SOCKET=1 ROVE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"ROVE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun scripts/render-track.mjs",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0","node-pty":"^1.1.0",react:"^19.2.8","smol-toml":"^1.7.1",ws:"^8.21.3"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/rove-daemon":"0.7.20","@tsconfig/bun":"1.0.11","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1","rove-weixin":"0.0.4",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var q="rove";var i=q;var z="ROVE_BUN",e="ROVE_NO_BUN_BOOTSTRAP",F="ROVE_SKIP_BUN_CHECK",$=H.engines.bun.match(/\d+\.\d+\.\d+/)?.[0]??"0.0.0",L=(s)=>{try{return R(s,d.X_OK),!0}catch{return!1}},B=(s)=>s==="win32"?"bun.exe":"bun",f=(s)=>{let t=Q(s,["--version"],{encoding:"utf8",timeout:5000});if(t.error||t.status!==0)return null;return t.stdout?.split(`
2
+ import{join as n}from"node:path";import{createInterface as a}from"node:readline/promises";import{pathToFileURL as ss}from"node:url";import{spawnSync as Q}from"node:child_process";import{constants as d,accessSync as R}from"node:fs";import{homedir as I}from"node:os";import{basename as U,delimiter as o,dirname as _,join as k}from"node:path";import{fileURLToPath as T}from"node:url";var H={$schema:"https://json.schemastore.org/package.json",name:"@sma1lboy/rove",version:"0.9.245",description:"Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",keywords:["terminal","tui","cli","multiplexer","ai-agents","coding-agent","ai-coding-assistant","agentic-ai","parallel-agents","git-worktree","claude-code","codex","llm","developer-tools"],type:"module",packageManager:"bun@1.3.13",bin:{rove:"dist/cli/rove.js"},files:["dist/cli","dist/completions","dist/web-ui","dist/skills","dist/*.wav","README.md","LICENSE"],publishConfig:{access:"public"},repository:{type:"git",url:"git+https://github.com/Sma1lboy/rove.git"},homepage:"https://rove.run",bugs:{url:"https://github.com/Sma1lboy/rove/issues"},engines:{bun:">=1.3.11"},scripts:{dev:"ROVE_DEV=1 bun --conditions=browser ./src/cli/rove.ts","dev:mock":"ROVE_DEV=1 bun ./src/tui-react/mock/host.tsx","dev:sandbox":"bun run scripts/dev-sandbox.ts run","dev:sandbox:reset":"bun run scripts/dev-sandbox.ts reset",build:"bun run scripts/build.ts","build:rubric":"bun run scripts/build-tier-rubric.ts",compile:"bun run scripts/compile.ts",typecheck:"tsc --noEmit","check-i18n":"bun run scripts/check-i18n.ts",test:"bun run test:fast && bun run test:socket","perf:golden":"bun scripts/perf-golden.ts","pty:soak":"bun scripts/pty-soak.ts","test:fast":"vitest run --passWithNoTests --minWorkers=1 --maxWorkers=8","test:socket":"ROVE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:socket:coverage":"ROVE_INCLUDE_SOCKET=1 ROVE_COVERAGE_DAEMON=1 vitest run test/daemon --coverage --pool forks --minWorkers=1 --maxWorkers=4 --passWithNoTests","test:behavior":"ROVE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --pool forks --minWorkers=1 --maxWorkers=1 --retry=2 --passWithNoTests","test:render":"bun scripts/render-track.mjs",coverage:"vitest run --coverage --passWithNoTests",bench:"vitest bench --run",lint:"biome check .",knip:"knip-bun",postinstall:"bun run scripts/check-preview-deps.ts || true",prepublishOnly:"bun run typecheck && bun run build","plugin-sandbox":"bun run scripts/plugin-sandbox.ts"},"//":"biome.json + bunfig.toml live at the monorepo root since they apply repo-wide; bun.lock also lives at root (workspace-shared).",dependencies:{"@ansi-tools/parser":"^1.0.15","@opentui/core":"0.4.3","@opentui/react":"0.4.3","@xterm/addon-serialize":"^0.14.0","@xterm/addon-unicode11":"^0.9.0","@xterm/headless":"^6.0.0","node-pty":"^1.1.0",react:"^19.2.8","smol-toml":"^1.7.1",ws:"^8.21.3"},devDependencies:{"@biomejs/biome":"1.9.4","@sma1lboy/rove-daemon":"0.7.20","@tsconfig/bun":"1.0.11","@types/bun":"1.3.14","@types/node":"25.6.2","@types/react":"^19.2.0","@vitest/coverage-v8":"2.1.9",knip:"^6.14.2","react-devtools-core":"^7.0.1","rove-weixin":"0.0.6",typescript:"5.8.2",vitest:"2.1.9"},trustedDependencies:[]};var q="rove";var i=q;var z="ROVE_BUN",e="ROVE_NO_BUN_BOOTSTRAP",F="ROVE_SKIP_BUN_CHECK",$=H.engines.bun.match(/\d+\.\d+\.\d+/)?.[0]??"0.0.0",L=(s)=>{try{return R(s,d.X_OK),!0}catch{return!1}},B=(s)=>s==="win32"?"bun.exe":"bun",f=(s)=>{let t=Q(s,["--version"],{encoding:"utf8",timeout:5000});if(t.error||t.status!==0)return null;return t.stdout?.split(`
3
3
  `)[0]?.trim()??""};function J(s){return s.trim().match(/^v?(\d+\.\d+\.\d+)/)?.[1]??null}function X(s,t=$){let c=J(s),b=J(t);if(!c||!b)return!0;let W=c.split(".").map(Number),g=b.split(".").map(Number);for(let x=0;x<3;x++){let y=W[x]??0,r=g[x]??0;if(y!==r)return y>r}return!0}function Y(s=process.env){return s[F]==="1"}function A(s={}){let t=s.env??process.env,c=s.platform??process.platform,b=s.home??t.HOME??t.USERPROFILE??I(),W=B(c),g=[],x=t[z]?.trim();if(x)g.push(x);for(let G of(t.PATH??t.Path??"").split(o))if(G)g.push(k(G,W));let y=t.BUN_INSTALL?.trim();if(y)g.push(k(y,"bin",W));if(b)g.push(k(b,".bun","bin",W));let r=s.launcherDir;if(r)g.push(k(r,"..","..","node_modules","bun","bin",W)),g.push(k(r,"..","..","..","bun","bin",W));return g}function N(s={}){let t=s.isExecutable??L;return A(s).find((c)=>t(c))??null}function Z(s={}){let t=s.isExecutable??L,c=s.bunVersionOf??f;if(Y(s.env??process.env))return{bun:N(s),stale:null,unusable:null};let b=null,W=null;for(let g of A(s)){if(!t(g))continue;let x=c(g);if(x===null){W??=g;continue}if(X(x))return{bun:g,stale:b,unusable:W};b??={path:g,version:J(x)??x}}return{bun:null,stale:b,unusable:W}}function h(s){return _(T(s))}function C(s){return U(T(s)).replace(/\.[cm]?[jt]s$/,"")}function u(s=process.platform){return s==="win32"?["powershell","-NoProfile","-Command","irm bun.sh/install.ps1 | iex"]:["bash","-c","curl -fsSL https://bun.sh/install | bash"]}function D(s=i,t=process.platform){let c=t==="win32"?'powershell -c "irm bun.sh/install.ps1 | iex"':"curl -fsSL https://bun.sh/install | bash";return[`${s}: Rove runs on the Bun runtime, and no Bun was found on this machine.`,"","Install Bun, then run this command again:",` ${c}`," npm install -g bun # any platform, if you already have npm","","Or install Bun and Rove together in one step:"," curl -fsSL https://rove.run/install.sh | sh","",`Already have Bun somewhere unusual? Point Rove at it: ${z}=/path/to/bun`,""].join(`
4
4
  `)}function j(s,t,c=i){return[`${c}: this machine's Bun is too old for Rove — found ${t} at ${s}, need ${$} or newer.`,"","Rove's terminals are built on Bun's PTY API. An older Bun ignores it silently,","so every terminal and engine tab would open empty and stay empty.","","Upgrade Bun, then run this command again:"," bun upgrade # Bun installed itself (~/.bun)"," brew upgrade bun # Homebrew"," npm install -g bun@latest # npm-managed Bun","",`Have a newer Bun elsewhere? Point Rove at it: ${z}=/path/to/bun`,`Run on this one anyway (unsupported, terminals will be blank): ${F}=1`,""].join(`
5
5
  `)}function v(s,t=i){return[`${t}: the Bun at ${s} could not be run — \`${s} --version\` failed or never returned.`,"","Rove skipped it rather than starting through it, which would hang or fail","with a message from that binary instead of this one.","","Reinstall Bun, then run this command again:"," curl -fsSL https://bun.sh/install | bash","",`Have a working Bun elsewhere? Point Rove at it: ${z}=/path/to/bun`,""].join(`
@@ -12,7 +12,7 @@ _rove() {
12
12
  fi
13
13
  if [[ ${COMP_CWORD} -eq 2 ]]; then
14
14
  case "${prev}" in
15
- api) COMPREPLY=( $(compgen -W "schema engine-list list get-task pty-list collect context digest agent-turns inspect read-output watch add send dispatch interrupt note note-list note-delete pane-open pane-close pane-graphics tab-close notify prompt engine-report row-token set-active feedback issue-list issue-create issue-set-status issue-update issue-delete routine-list routine-create routine-update routine-set-enabled routine-delete routine-run-now routine-respond routine-runs workitem-list workitem-start rename set-branch set-command set-effort set-model set-status pin land delete ensure-worktree remove-worktree discover-adoptable adopt" -- "${cur}") ) ;;
15
+ api) COMPREPLY=( $(compgen -W "schema engine-list list get-task pty-list collect context digest agent-turns inspect read-output watch add send dispatch interrupt note note-list note-delete pane-open pane-close pane-graphics tab-close notify prompt engine-report row-token set-active feedback issue-list issue-create issue-update issue-delete routine-list routine-create routine-update routine-delete routine-run-now routine-respond routine-runs workitem-list workitem-start update land delete ensure-worktree remove-worktree discover-adoptable adopt" -- "${cur}") ) ;;
16
16
  daemon) COMPREPLY=( $(compgen -W "status start stop restart" -- "${cur}") ) ;;
17
17
  machine) COMPREPLY=( $(compgen -W "add remove list" -- "${cur}") ) ;;
18
18
  plugin) COMPREPLY=( $(compgen -W "install link list search outdated update enable disable unlink uninstall config-dir log action pane" -- "${cur}") ) ;;
@@ -19,7 +19,7 @@ complete -c rove -f -n __fish_use_subcommand -a skill
19
19
  complete -c rove -f -n __fish_use_subcommand -a plugin
20
20
  complete -c rove -f -n __fish_use_subcommand -a feedback
21
21
  complete -c rove -f -n __fish_use_subcommand -a update
22
- complete -c rove -f -n "__fish_seen_subcommand_from api" -a "schema engine-list list get-task pty-list collect context digest agent-turns inspect read-output watch add send dispatch interrupt note note-list note-delete pane-open pane-close pane-graphics tab-close notify prompt engine-report row-token set-active feedback issue-list issue-create issue-set-status issue-update issue-delete routine-list routine-create routine-update routine-set-enabled routine-delete routine-run-now routine-respond routine-runs workitem-list workitem-start rename set-branch set-command set-effort set-model set-status pin land delete ensure-worktree remove-worktree discover-adoptable adopt"
22
+ complete -c rove -f -n "__fish_seen_subcommand_from api" -a "schema engine-list list get-task pty-list collect context digest agent-turns inspect read-output watch add send dispatch interrupt note note-list note-delete pane-open pane-close pane-graphics tab-close notify prompt engine-report row-token set-active feedback issue-list issue-create issue-update issue-delete routine-list routine-create routine-update routine-delete routine-run-now routine-respond routine-runs workitem-list workitem-start update land delete ensure-worktree remove-worktree discover-adoptable adopt"
23
23
  complete -c rove -f -n "__fish_seen_subcommand_from daemon" -a "status start stop restart"
24
24
  complete -c rove -f -n "__fish_seen_subcommand_from machine" -a "add remove list"
25
25
  complete -c rove -f -n "__fish_seen_subcommand_from plugin" -a "install link list search outdated update enable disable unlink uninstall config-dir log action pane"
@@ -13,7 +13,7 @@ _rove() {
13
13
 
14
14
  verbs=()
15
15
  case "${words[2]}" in
16
- api) verbs=("schema" "engine-list" "list" "get-task" "pty-list" "collect" "context" "digest" "agent-turns" "inspect" "read-output" "watch" "add" "send" "dispatch" "interrupt" "note" "note-list" "note-delete" "pane-open" "pane-close" "pane-graphics" "tab-close" "notify" "prompt" "engine-report" "row-token" "set-active" "feedback" "issue-list" "issue-create" "issue-set-status" "issue-update" "issue-delete" "routine-list" "routine-create" "routine-update" "routine-set-enabled" "routine-delete" "routine-run-now" "routine-respond" "routine-runs" "workitem-list" "workitem-start" "rename" "set-branch" "set-command" "set-effort" "set-model" "set-status" "pin" "land" "delete" "ensure-worktree" "remove-worktree" "discover-adoptable" "adopt") ;;
16
+ api) verbs=("schema" "engine-list" "list" "get-task" "pty-list" "collect" "context" "digest" "agent-turns" "inspect" "read-output" "watch" "add" "send" "dispatch" "interrupt" "note" "note-list" "note-delete" "pane-open" "pane-close" "pane-graphics" "tab-close" "notify" "prompt" "engine-report" "row-token" "set-active" "feedback" "issue-list" "issue-create" "issue-update" "issue-delete" "routine-list" "routine-create" "routine-update" "routine-delete" "routine-run-now" "routine-respond" "routine-runs" "workitem-list" "workitem-start" "update" "land" "delete" "ensure-worktree" "remove-worktree" "discover-adoptable" "adopt") ;;
17
17
  daemon) verbs=("status" "start" "stop" "restart") ;;
18
18
  machine) verbs=("add" "remove" "list") ;;
19
19
  plugin) verbs=("install" "link" "list" "search" "outdated" "update" "enable" "disable" "unlink" "uninstall" "config-dir" "log" "action" "pane") ;;
@@ -3,7 +3,7 @@ name: rove
3
3
  description: Use when controlling Rove tasks, parallel coding attempts, hosted agent sessions, task lifecycle, or the daemon-owned issue tracker from a shell. Also the ONLY channel for messaging another agent session on this machine — `rove api send`, never a peer/MCP side channel.
4
4
  ---
5
5
 
6
- <!-- rove-skill-version: 54 — bump in lockstep with ROVE_SKILL_VERSION (src/lib/skill-install.ts). -->
6
+ <!-- rove-skill-version: 56 — bump in lockstep with ROVE_SKILL_VERSION (src/lib/skill-install.ts). -->
7
7
 
8
8
  # Rove shell control
9
9
 
@@ -278,8 +278,9 @@ get-task --task-id(REQ) list (no flags)
278
278
  collect --group <groupId> | --task-ids <csv> | --repo
279
279
  ```
280
280
 
281
- Four names that have actually been guessed wrong here: `add --vendor` is
282
- `--command`; `read-output --task` is `--task-id`; `dispatch --text` is
281
+ Names that have actually been guessed wrong here: `add --vendor` is
282
+ `--command`; `read-output --task` is `--task-id`; per-field verbs
283
+ (`set-<field>`, `rename`, `pin`) are all `update --<field>`; `dispatch --text` is
283
284
  `--prompt` (`--text` belongs to `note`); `issue-list` has no `--state` at all
284
285
  — filter its JSON yourself.
285
286
 
@@ -348,7 +349,7 @@ rove api send --task-id <id> --tab tab-3 --prompt "<turn>" # exact alive tab
348
349
  rove api send --task-id <id> --tab new --prompt "<turn>" # fresh engine tab
349
350
  # Same worktree, DIFFERENT agent — the API twin of the TUI's ctrl+e pick. The
350
351
  # engine is pinned to that tab (survives restarts, unaffected by a later
351
- # set-command) and the task's own engine is left alone. --tab new only.
352
+ # update --command) and the task's own engine is left alone. --tab new only.
352
353
  rove api send --task-id <id> --tab new --command codex --prompt "<turn>"
353
354
 
354
355
  rove api get-task --task-id <id>
@@ -423,11 +424,7 @@ logs, dashboards), don't scatter panes for work `add` should own.
423
424
 
424
425
  | Verb | Purpose |
425
426
  |---|---|
426
- | `rename --task-id ID --title T` | Rename a task |
427
- | `set-branch --task-id ID --branch B` | Rename its branch |
428
- | `set-command --task-id ID --command CMD` | Change the engine launch command for the next launch |
429
- | `set-status --task-id ID --status S` | Set the lifecycle LABEL (`backlog`/`in_progress`/`in_review`/`done`/`canceled`/`error`). Cosmetic: the row, its Worktree, its branch and its engine all stay. `canceled` does NOT close or clean up anything |
430
- | `pin --task-id ID [--pinned=false]` | Pin/unpin |
427
+ | `update --task-id ID [--title T] [--branch B] [--command CMD] [--model M] [--effort L] [--pinned true\|false] [--status S] [--report-branch B --report-pr N --report-summary TEXT]` | Change any combination of fields in one call: validated first, applied branch → command → model/effort → title → pinned → status; not atomic (a mid-way failure reports `applied`). `--command` takes effect on the next launch; `--tab tab-N` with `--title` renames that tab. `--status` sets the lifecycle LABEL (`backlog`/`in_progress`/`in_review`/`done`/`canceled`/`error`) — cosmetic: the row, its Worktree, its branch and its engine all stay, and `canceled` does NOT close or clean up anything. `--report-*` (need `--status`) record what you delivered: `update --task-id "$ROVE_TASK_ID" --status in_review --report-pr N` |
431
428
  | `set-active --task-id ID` / `--none` | Change shared active task |
432
429
  | `ensure-worktree --task-id ID` | Materialize without starting an engine |
433
430
  | `land --task-id ID [--strategy merge\|squash] [--delete-branch] [--remove-worktree=false]` | Merge the task's branch into the base repo's current branch; the Worktree is removed by default (`--remove-worktree=false` keeps it). The branch always stays; dirty/self/base removals are refused, outcome in the result's `worktree` field |
@@ -438,7 +435,7 @@ logs, dashboards), don't scatter panes for work `add` should own.
438
435
  ### "Close this task" means `delete`
439
436
 
440
437
  `delete` is the ONLY verb that ends a task: it removes the row and its
441
- Worktree, and the git branch survives as the durable record. `set-status
438
+ Worktree, and the git branch survives as the durable record. `update --status
442
439
  canceled` is a label — the row, Worktree, branch and engine all stay, so a
443
440
  "close" done that way changes nothing the user can see. Reach for `delete`
444
441
  whether or not the work merged; an unmerged branch is still on disk
@@ -458,7 +455,7 @@ Issues are daemon-owned, not repo files:
458
455
  ```bash
459
456
  rove api issue-list --repo "$PWD" --pretty
460
457
  rove api issue-create --repo "$PWD" --title "title" --body "context"
461
- rove api issue-set-status --repo "$PWD" --id <n> --status done
458
+ rove api issue-update --repo "$PWD" --id <n> --status done
462
459
  rove api issue-update --repo "$PWD" --id <n> --title "new" --body "body"
463
460
  rove api issue-update --repo "$PWD" --id <n> --task <taskId> # link; `--task none` unlinks
464
461
  ```
@@ -467,7 +464,7 @@ rove api issue-update --repo "$PWD" --id <n> --task <taskId> # link; `--task n
467
464
 
468
465
  The TUI and web render issues as a Backlog / In progress / Done board whose
469
466
  columns derive from the issue's own lifecycle — do NOT move cards with
470
- `issue-set-status doing`:
467
+ `issue-update --status doing`:
471
468
 
472
469
  - **In progress** = the issue has a linked task; `issue-update --task <taskId>`
473
470
  IS the move (typical flow: `issue-create` → `add` a task → link them).
@@ -569,7 +566,7 @@ party who knows what the work turned out to be. Once the shape is clear,
569
566
  rename it to a short descriptive name in this repo's own convention:
570
567
 
571
568
  ```bash
572
- rove api set-branch --task-id "$ROVE_TASK_ID" --branch <descriptive-slug>
569
+ rove api update --task-id "$ROVE_TASK_ID" --branch <descriptive-slug>
573
570
  ```
574
571
 
575
572
  Do this while you work, not at the end — the branch name is what the user
@@ -625,7 +622,7 @@ None of these mean a task is finished:
625
622
  | the worker said "done" | a message; it has no verb to remove itself |
626
623
  | its PR is MERGED | the code landed; the task did not move |
627
624
  | its issue is `status done` | a field in a different store |
628
- | `set-status canceled` | a label — see Lifecycle above |
625
+ | `update --status canceled` | a label — see Lifecycle above |
629
626
 
630
627
  **The only evidence is that `rove api list` no longer shows it.** Check the
631
628
  task, not the paperwork:
@@ -50,7 +50,14 @@ Four flag names that have actually been guessed wrong here, and what they are:
50
50
  | `fan-out` | `add --count N` / `add --agents claude:2,codex:1` |
51
51
  | `task-list` | `list` |
52
52
  | `send-tab` | `send --tab tab-N` |
53
- | `set-vendor` | `set-command` (an engine id from `engine-list`, or a full command line) |
53
+ | `set-vendor` | `update --command` (an engine id from `engine-list`, or a full command line) |
54
+ | `rename` | `update --title` (`update --tab tab-N --title` for a tab) |
55
+ | `set-branch` / `set-command` / `set-model` | `update --branch` / `--command` / `--model` |
56
+ | `set-effort --level` | `update --effort` |
57
+ | `set-status` | `update --status` (+ `--report-*`) |
58
+ | `pin` | `update --pinned true\|false` |
59
+ | `issue-set-status` | `issue-update --status` |
60
+ | `routine-set-enabled` | `routine-update --enabled true\|false` |
54
61
 
55
62
  Seeing one of these in guidance means that guidance predates the rename —
56
63
  `rove api schema` is the tiebreak.
@@ -64,7 +71,7 @@ see both.
64
71
 
65
72
  <!-- generated:begin read -->
66
73
  ```text
67
- list (none)
74
+ list --repo --status <S1,S2> --activity <A1,A2>
68
75
  get-task --task-id(REQ)
69
76
  pty-list (none)
70
77
  collect --task-ids <a,b,c> --group --repo
@@ -72,7 +79,7 @@ context --repo(REQ) --limit(20) --text
72
79
  digest --repo(REQ) --since-days(7)
73
80
  agent-turns --task-id --repo --since-days(7) --limit(200)
74
81
  inspect --task-id
75
- read-output --task-id --tab --source{auto|history|terminal}(auto) --cursor --limit(40)
82
+ read-output --task-id --tab --source{auto|history|terminal}(auto) --cursor --limit
76
83
  watch --task-ids <a,b,c> --group --until(REQ) <STATE[,STATE]> --timeout
77
84
  ```
78
85
  <!-- generated:end -->
@@ -86,10 +93,14 @@ the wider diagnostic — daemon activity, a pty walk, and every tab snapshot.
86
93
  disagree, it is the ground truth for what is actually alive — key, alive, pid,
87
94
  command, OSC title.
88
95
 
89
- `read-output --limit` maxes at 50. `--tab` is terminal-only — it cannot combine
90
- with `--source history`. The cursor is pinned to one source/session/tab; a
91
- moved target returns `SOURCE_CHANGED` rather than silently paging something
92
- else.
96
+ `read-output` opens on the NEWEST page: the last `--limit` messages (history,
97
+ default 40, max 50) or terminal lines (default 40, max 200). Its `cursor` pages
98
+ forward and, once caught up, is the poll point — re-read with it to get only
99
+ what the session wrote since (`watch` tells you when). `olderCursor` pages back
100
+ through history. `--tab tab-N` reads that tab's own conversation when it is an
101
+ engine tab with a pinned session, else its terminal. A cursor is pinned to one
102
+ source/session/tab; a moved target returns `SOURCE_CHANGED` rather than
103
+ silently paging something else.
93
104
 
94
105
  `digest` and `agent-turns` are the measurement reads — `digest` aggregates a
95
106
  repo's recent task + routine activity, `agent-turns` is per-turn telemetry
@@ -164,28 +175,26 @@ clear the shared active task.
164
175
 
165
176
  <!-- generated:begin create,edit,lifecycle -->
166
177
  ```text
167
- add --repo(REQ) --title --branch --base-branch --worktree-name --command --effort
168
- --tier{swift|standard|deep|auto} --model --count --agents <claude:2,codex:1>
169
- --status{backlog|in_progress|in_review|done|canceled|error}(backlog) --pin
170
- --activate(false) --prompt|--prompt-file
171
- rename --task-id(REQ) --title(REQ) --tab
172
- set-branch --task-id(REQ) --branch(REQ)
173
- set-command --task-id(REQ) --command(REQ)
174
- set-effort --task-id(REQ) --level(REQ)
175
- set-model --task-id(REQ) --model(REQ)
176
- set-status --task-id(REQ)
177
- --status{backlog|in_progress|in_review|done|canceled|error}(REQ)
178
- --report-branch --report-pr --report-summary
179
- pin --task-id(REQ) --pinned(true)
180
- land --task-id(REQ) --dry-run --strategy{merge|squash}(merge) --delete-branch
181
- --remove-worktree(true)
182
- delete --task-id --group --force --delete-branch --delete-remote --wait
178
+ add --repo(REQ) --title --branch --base-branch --worktree-name --command --effort
179
+ --tier{swift|standard|deep|auto} --model --count --agents <claude:2,codex:1>
180
+ --status{backlog|in_progress|in_review|done|canceled|error}(backlog) --pin
181
+ --activate(false) --prompt|--prompt-file
182
+ update --task-id(REQ) --title --tab --branch --command --model --effort --pinned
183
+ --status{backlog|in_progress|in_review|done|canceled|error} --report-branch
184
+ --report-pr --report-summary
185
+ land --task-id(REQ) --dry-run --strategy{merge|squash}(merge) --delete-branch
186
+ --remove-worktree(true)
187
+ delete --task-id --group --force --delete-branch --delete-remote --wait
183
188
  ```
184
189
  <!-- generated:end -->
185
190
 
186
- `set-command` takes effect on the NEXT launch only. `set-effort --level` is
187
- engine-owned — the levels a vendor accepts come from its registry entry, so
188
- check `engine-list` rather than guessing.
191
+ `update` takes any mix of its fields in one call. It validates all of them
192
+ before writing anything, then applies branch → command → model/effort →
193
+ title → pinned → status; a refusal mid-way returns `applied` (what already
194
+ landed). `--command`/`--model`/`--effort` take effect on the NEXT launch only.
195
+ Effort levels are engine-owned — the levels a vendor accepts come from its
196
+ registry entry, so check `engine-list` rather than guessing. `--report-*`
197
+ needs `--status`. With `--tab`, only `--title` is allowed.
189
198
 
190
199
  Task `--status` and issue `--status` are DIFFERENT enums — a task is
191
200
  `backlog|in_progress|in_review|done|canceled|error`, an issue is
@@ -214,17 +223,16 @@ adopt --repo(REQ) --worktree(REQ) --branch --command --title
214
223
 
215
224
  <!-- generated:begin issues -->
216
225
  ```text
217
- issue-list --repo(REQ)
218
- issue-create --repo(REQ) --title(REQ) --body
219
- issue-set-status --repo(REQ) --id(REQ) --status{open|doing|hold|done}(REQ)
220
- issue-update --repo(REQ) --id(REQ) --title --body --task
221
- issue-delete --repo(REQ) --id(REQ)
226
+ issue-list --repo(REQ)
227
+ issue-create --repo(REQ) --title(REQ) --body
228
+ issue-update --repo(REQ) --id(REQ) --title --body --task --status{open|doing|hold|done}
229
+ issue-delete --repo(REQ) --id(REQ)
222
230
  ```
223
231
  <!-- generated:end -->
224
232
 
225
233
  `--id` is an int, not the ULID a task uses. `issue-update --task <taskId>` is
226
234
  the kanban "move to In progress"; `--task none` unlinks. Kanban semantics are
227
- in SKILL.md — do not move cards with `issue-set-status doing`.
235
+ in SKILL.md — do not move cards with `issue-update --status doing`.
228
236
 
229
237
  ## workitems (GitHub, through `gh`)
230
238
 
@@ -256,19 +264,18 @@ it fires with no TUI attached.
256
264
 
257
265
  <!-- generated:begin routine -->
258
266
  ```text
259
- routine-list (none)
260
- routine-create --repo(REQ) --name(REQ) --prompt|--prompt-file(REQ) --schedule(REQ)
261
- --vendor{claude|codex|copilot|kimi|pi|omp|bob} --base-branch
262
- --precheck --precheck-timeout(120) --grace(60) --persistent-session
263
- --target-task --target-tab --disabled
264
- routine-update --id(REQ) --name --prompt|--prompt-file --schedule --vendor
265
- --base-branch --precheck --precheck-timeout(120) --grace(60)
266
- --persistent-session --target-task --target-tab
267
- routine-set-enabled --id(REQ) --enabled(REQ)
268
- routine-delete --id(REQ)
269
- routine-run-now --id(REQ)
270
- routine-respond --run(REQ) --text --prompt-file
271
- routine-runs --id(REQ)
267
+ routine-list (none)
268
+ routine-create --repo(REQ) --name(REQ) --prompt|--prompt-file(REQ) --schedule(REQ)
269
+ --vendor{claude|codex|copilot|kimi|pi|omp|bob} --base-branch --precheck
270
+ --precheck-timeout(120) --grace(60) --persistent-session --target-task
271
+ --target-tab --disabled
272
+ routine-update --id(REQ) --name --prompt|--prompt-file --schedule --vendor --base-branch
273
+ --precheck --precheck-timeout(120) --grace(60) --persistent-session
274
+ --target-task --target-tab --enabled
275
+ routine-delete --id(REQ)
276
+ routine-run-now --id(REQ)
277
+ routine-respond --run(REQ) --text --prompt-file
278
+ routine-runs --id(REQ)
272
279
  ```
273
280
  <!-- generated:end -->
274
281
 
@@ -292,8 +299,8 @@ the daemon respawns it in the SAME worktree — files and branch carry over, the
292
299
  conversation does not — and records that run as `revived`, not `dispatched`.
293
300
 
294
301
  A bare `--enabled` means true, so pausing a routine is
295
- `routine-set-enabled --id <id> --enabled=false` (the same `=false` spelling
296
- `pin --pinned=false` uses).
302
+ `routine-update --id <id> --enabled=false` (the same `=false` spelling
303
+ `update --pinned=false` uses).
297
304
 
298
305
  `routine-update --schedule` re-anchors the next run. `--precheck ''` clears it.
299
306
  `routine-run-now` skips the precheck deliberately (asking for it IS the answer)