@lesscap/baton-cli 0.3.0 → 0.3.1

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 (2) hide show
  1. package/dist/baton.mjs +200 -68
  2. package/package.json +1 -1
package/dist/baton.mjs CHANGED
@@ -1015,6 +1015,25 @@ var channelClient = (baseUrl) => {
1015
1015
  if (!res.ok) throw new Error(`POST channel message \u2192 ${res.status}: ${await res.text()}`);
1016
1016
  return await res.json();
1017
1017
  },
1018
+ uploadAttachment: async (channelId, token, input) => {
1019
+ const res = await fetch(
1020
+ u(`/channels/${channelId}/attachments?filename=${encodeURIComponent(input.filename)}`),
1021
+ {
1022
+ method: "POST",
1023
+ headers: { "content-type": input.contentType, ...authed(token) },
1024
+ body: input.body
1025
+ }
1026
+ );
1027
+ if (!res.ok) throw new Error(`upload attachment \u2192 ${res.status}: ${await res.text()}`);
1028
+ return await res.json();
1029
+ },
1030
+ downloadAttachment: async (channelId, token, attId) => {
1031
+ const res = await fetch(u(`/channels/${channelId}/attachments/${attId}`), {
1032
+ headers: authed(token)
1033
+ });
1034
+ if (!res.ok) throw new Error(`download attachment \u2192 ${res.status}: ${await res.text()}`);
1035
+ return res;
1036
+ },
1018
1037
  read: async (channelId, token, opts) => {
1019
1038
  const params = new URLSearchParams({ since: String(opts?.since ?? 0) });
1020
1039
  if (opts?.for) params.set("for", opts.for);
@@ -1221,6 +1240,25 @@ var requirementClient = (baseUrl) => {
1221
1240
  };
1222
1241
  };
1223
1242
 
1243
+ // src/client/retry.ts
1244
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
1245
+ var withRetry = async (fn, opts = {}) => {
1246
+ const tries = Math.max(1, opts.tries ?? 4);
1247
+ const baseMs = opts.baseMs ?? 300;
1248
+ let lastErr;
1249
+ for (let i = 0; i < tries; i++) {
1250
+ try {
1251
+ return await fn();
1252
+ } catch (err) {
1253
+ lastErr = err;
1254
+ if (i === tries - 1) break;
1255
+ const backoff = baseMs * 2 ** i;
1256
+ await sleep(backoff + Math.random() * baseMs);
1257
+ }
1258
+ }
1259
+ throw lastErr;
1260
+ };
1261
+
1224
1262
  // src/client/sessions.ts
1225
1263
  var sessionsClient = (baseUrl) => {
1226
1264
  const u = (p) => `${baseUrl}${p}`;
@@ -1343,15 +1381,24 @@ var clientFromBase = (baseUrl) => ({
1343
1381
  sessions: sessionsClient(baseUrl),
1344
1382
  workers: workersClient(baseUrl)
1345
1383
  });
1384
+ var BOUNDARY_EVENTS = /* @__PURE__ */ new Set([
1385
+ "turn_start",
1386
+ "turn_complete",
1387
+ "turn_error"
1388
+ ]);
1389
+ var emitRetries = () => Number(process.env.BATON_EMIT_RETRIES) || 4;
1346
1390
  var createWorkerClient = (baseUrl, workerToken, sessionId) => {
1347
1391
  const u = (p) => `${baseUrl}${p}`;
1348
1392
  const auth = { authorization: `Bearer ${workerToken}` };
1349
1393
  return {
1350
- emitEvent: (type, payload) => request(u(`/sessions/${sessionId}/events`), {
1351
- method: "POST",
1352
- body: { type, payload },
1353
- headers: auth
1354
- }),
1394
+ emitEvent: (type, payload) => {
1395
+ const post = () => request(u(`/sessions/${sessionId}/events`), {
1396
+ method: "POST",
1397
+ body: { type, payload },
1398
+ headers: auth
1399
+ });
1400
+ return BOUNDARY_EVENTS.has(type) ? withRetry(post, { tries: emitRetries() }) : post();
1401
+ },
1355
1402
  setActive: (active) => request(u(`/sessions/${sessionId}/status`), {
1356
1403
  method: "POST",
1357
1404
  body: { active },
@@ -1422,16 +1469,23 @@ var readStdin = async () => {
1422
1469
  return Buffer.concat(chunks).toString("utf8");
1423
1470
  };
1424
1471
  var renderRoster = (members) => members.length ? members.map((m) => ` ${m.name} (${m.kind})`).join("\n") : " (nobody online)";
1425
- var inviteBlock = (url, channelId, token) => [
1426
- "\u2500\u2500 Share this to invite anyone into the room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1427
- "You're invited to a baton channel. Three steps to get going:",
1428
- ` 1) see the room: curl -sS -H "authorization: Bearer ${token}" "${url}/channels/${channelId}"`,
1429
- ` 2) read protocol: curl -sS "${url}/channels/help"`,
1430
- " 3) follow it to join / listen / send (pick your own NAME).",
1431
- "",
1432
- `connection: url=${url} channel=${channelId} token=${token}`,
1433
- "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
1434
- ].join("\n");
1472
+ var webLink = (url, channelId, token) => {
1473
+ const origin = url.replace(/\/api\/?$/, "");
1474
+ return origin === url ? null : `${origin}/channel/${channelId}#token=${token}`;
1475
+ };
1476
+ var inviteBlock = (url, channelId, token) => {
1477
+ const web = webLink(url, channelId, token);
1478
+ return [
1479
+ "\u2500\u2500 Share this to invite anyone into the room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1480
+ ...web ? [`Humans \u2014 open in a browser: ${web}`, ""] : [],
1481
+ "Agents \u2014 two curl steps:",
1482
+ ` 1) see the room: curl -sS -H "authorization: Bearer ${token}" "${url}/channels/${channelId}"`,
1483
+ ` 2) read protocol: curl -sS "${url}/channels/help" (then join / listen / send, pick your own NAME)`,
1484
+ "",
1485
+ `connection: url=${url} channel=${channelId} token=${token}`,
1486
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
1487
+ ].join("\n");
1488
+ };
1435
1489
 
1436
1490
  // src/commands/channel/create.ts
1437
1491
  var createCommand = defineCommand({
@@ -1454,6 +1508,26 @@ var createCommand = defineCommand({
1454
1508
  }
1455
1509
  });
1456
1510
 
1511
+ // src/commands/channel/download.ts
1512
+ import { writeFileSync as writeFileSync2 } from "node:fs";
1513
+ var downloadCommand = defineCommand({
1514
+ meta: { name: "download", description: "download a channel attachment to a file" },
1515
+ args: {
1516
+ channel: { type: "positional", required: true, description: "channel id" },
1517
+ att: { type: "positional", required: true, description: "attachment id (from a message ref)" },
1518
+ token: { type: "string", required: true, description: "channel token" },
1519
+ out: { type: "string", alias: "o", description: "output file path (default: ./<attId>)" },
1520
+ ...common
1521
+ },
1522
+ run: async ({ args }) => {
1523
+ const url = resolveBaseUrl(args.url);
1524
+ const res = await channelClient(url).downloadAttachment(args.channel, args.token, args.att);
1525
+ const out = args.out ?? args.att;
1526
+ writeFileSync2(out, Buffer.from(await res.arrayBuffer()));
1527
+ console.log(`saved \u2192 ${out}`);
1528
+ }
1529
+ });
1530
+
1457
1531
  // src/commands/channel/info.ts
1458
1532
  var aboutCommand = defineCommand({
1459
1533
  meta: { name: "about", description: "show a channel's manifest (description + online roster)" },
@@ -1520,7 +1594,7 @@ var updateCommand = defineCommand({
1520
1594
  });
1521
1595
 
1522
1596
  // src/commands/channel/listen.ts
1523
- import { mkdirSync, writeFileSync as writeFileSync2 } from "node:fs";
1597
+ import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
1524
1598
  import { tmpdir } from "node:os";
1525
1599
  import { join as join2 } from "node:path";
1526
1600
 
@@ -1552,6 +1626,12 @@ var unstartedUserMessages = (events) => {
1552
1626
  const started = startedMessageIds(events);
1553
1627
  return events.filter((e) => e.type === "user_message" && !started.has(e.id));
1554
1628
  };
1629
+ var opensTurn = (e) => e.type === "user_message" || e.type === "turn_start";
1630
+ var closesTurn = (e) => e.type === "turn_complete" || e.type === "turn_error";
1631
+ var isAgentWorking = (events) => {
1632
+ const last = events.findLast((e) => opensTurn(e) || closesTurn(e));
1633
+ return last ? opensTurn(last) : false;
1634
+ };
1555
1635
 
1556
1636
  // src/commands/channel/listen.ts
1557
1637
  var PREVIEW = 280;
@@ -1569,7 +1649,7 @@ var runListen = async (o) => {
1569
1649
  const surface = (m) => {
1570
1650
  const text = typeof m.text === "string" ? m.text : "";
1571
1651
  const file = join2(spillDir, `${m.seq}.txt`);
1572
- writeFileSync2(file, text);
1652
+ writeFileSync3(file, text);
1573
1653
  const truncated = text.length > PREVIEW;
1574
1654
  emit({
1575
1655
  type: "channel.message",
@@ -1582,6 +1662,13 @@ var runListen = async (o) => {
1582
1662
  chars: text.length,
1583
1663
  truncated,
1584
1664
  preview: truncated ? `${text.slice(0, PREVIEW)} \u2026` : text,
1665
+ // BASE-prefixed urls so the subscribing agent can fetch directly.
1666
+ attachments: m.attachments?.length ? m.attachments.map((a) => ({
1667
+ filename: a.filename,
1668
+ contentType: a.contentType,
1669
+ size: a.size,
1670
+ url: `${o.url}${a.url}`
1671
+ })) : void 0,
1585
1672
  file
1586
1673
  });
1587
1674
  };
@@ -1663,6 +1750,12 @@ ${renderRoster(members)}`);
1663
1750
  });
1664
1751
 
1665
1752
  // src/commands/channel/read.ts
1753
+ var fmtMessage = (m, base) => {
1754
+ const head = `${m.seq} ${m.from} ${m.text}`;
1755
+ const atts = (m.attachments ?? []).map((a) => `
1756
+ \u{1F4CE} ${a.filename} \u2192 ${base}${a.url}`).join("");
1757
+ return head + atts;
1758
+ };
1666
1759
  var readCommand = defineCommand({
1667
1760
  meta: { name: "read", description: "poll channel messages once (curl-style, since a cursor)" },
1668
1761
  args: {
@@ -1681,12 +1774,51 @@ var readCommand = defineCommand({
1681
1774
  });
1682
1775
  const out = args.from ? msgs.filter((m) => m.from !== args.from) : msgs;
1683
1776
  if (args.json) console.log(toJson(out));
1684
- else console.log(out.length ? out.map((m) => `${m.seq} ${m.from} ${m.text}`).join("\n") : "(none)");
1777
+ else console.log(out.length ? out.map((m) => fmtMessage(m, url)).join("\n") : "(none)");
1685
1778
  }
1686
1779
  });
1687
1780
 
1688
1781
  // src/commands/channel/send.ts
1689
- import { readFileSync as readFileSync2 } from "node:fs";
1782
+ import { openAsBlob as openAsBlob2, readFileSync as readFileSync2 } from "node:fs";
1783
+ import { basename as basename2 } from "node:path";
1784
+
1785
+ // src/mime.ts
1786
+ import { extname } from "node:path";
1787
+ var BY_EXT = {
1788
+ ".png": "image/png",
1789
+ ".jpg": "image/jpeg",
1790
+ ".jpeg": "image/jpeg",
1791
+ ".gif": "image/gif",
1792
+ ".webp": "image/webp",
1793
+ ".svg": "image/svg+xml",
1794
+ ".pdf": "application/pdf",
1795
+ ".txt": "text/plain",
1796
+ ".md": "text/markdown",
1797
+ ".json": "application/json",
1798
+ ".csv": "text/csv"
1799
+ };
1800
+ var contentTypeForPath = (path) => BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
1801
+
1802
+ // src/commands/attach.ts
1803
+ import { openAsBlob } from "node:fs";
1804
+ import { basename } from "node:path";
1805
+ var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
1806
+ var uploadAttachments = async (client, sessionId, paths) => {
1807
+ const out = [];
1808
+ for (const path of paths) {
1809
+ const body = await openAsBlob(path, { type: contentTypeForPath(path) });
1810
+ out.push(
1811
+ await client.sessions.uploadAttachment(sessionId, {
1812
+ filename: basename(path),
1813
+ contentType: contentTypeForPath(path),
1814
+ body
1815
+ })
1816
+ );
1817
+ }
1818
+ return out;
1819
+ };
1820
+
1821
+ // src/commands/channel/send.ts
1690
1822
  var sendCommand = defineCommand({
1691
1823
  meta: { name: "send", description: "post one message to a channel" },
1692
1824
  args: {
@@ -1696,19 +1828,32 @@ var sendCommand = defineCommand({
1696
1828
  from: { type: "string", description: "your participant name (default: peer)" },
1697
1829
  to: { type: "string", description: "direct it at these names (comma-separated); omit = broadcast" },
1698
1830
  file: { type: "string", description: "read message body from this file (for large content)" },
1831
+ attach: { type: "string", description: "attach file(s) (comma-separated paths); uploaded + carried on the message" },
1699
1832
  ...common
1700
1833
  },
1701
1834
  run: async ({ args }) => {
1702
1835
  const url = resolveBaseUrl(args.url);
1836
+ const client = channelClient(url);
1703
1837
  let text = args.text;
1704
1838
  if (text === void 0 && args.file) text = readFileSync2(args.file, "utf8");
1705
1839
  if (text === void 0 && !process.stdin.isTTY) text = await readStdin();
1706
- if (text === void 0 || text === "")
1707
- throw new Error("nothing to send: provide text, --file <path>, or pipe stdin");
1708
- const msg = await channelClient(url).send(args.channel, args.token, {
1840
+ const attachments = [];
1841
+ for (const path of attachPaths(args.attach)) {
1842
+ attachments.push(
1843
+ await client.uploadAttachment(args.channel, args.token, {
1844
+ filename: basename2(path),
1845
+ contentType: contentTypeForPath(path),
1846
+ body: await openAsBlob2(path, { type: contentTypeForPath(path) })
1847
+ })
1848
+ );
1849
+ }
1850
+ if (!text && attachments.length === 0)
1851
+ throw new Error("nothing to send: provide text, --file <path>, --attach <file>, or pipe stdin");
1852
+ const msg = await client.send(args.channel, args.token, {
1709
1853
  from: args.from ?? "peer",
1710
- text,
1711
- to: splitCsv(args.to)
1854
+ text: text ?? "",
1855
+ to: splitCsv(args.to),
1856
+ attachments: attachments.length ? attachments : void 0
1712
1857
  });
1713
1858
  if (args.json) console.log(toJson(msg));
1714
1859
  else console.log(`sent (seq ${msg.seq})`);
@@ -1719,7 +1864,7 @@ var sendCommand = defineCommand({
1719
1864
  var channel = defineCommand({
1720
1865
  meta: {
1721
1866
  name: "channel",
1722
- description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / close"
1867
+ description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / download / close"
1723
1868
  },
1724
1869
  subCommands: {
1725
1870
  create: createCommand,
@@ -1731,6 +1876,7 @@ var channel = defineCommand({
1731
1876
  send: sendCommand,
1732
1877
  read: readCommand,
1733
1878
  listen: listenCommand,
1879
+ download: downloadCommand,
1734
1880
  close: closeCommand
1735
1881
  }
1736
1882
  });
@@ -1874,7 +2020,7 @@ var project = defineCommand({
1874
2020
  });
1875
2021
 
1876
2022
  // src/commands/relay.ts
1877
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
2023
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
1878
2024
  import { tmpdir as tmpdir2 } from "node:os";
1879
2025
  import { join as join3 } from "node:path";
1880
2026
 
@@ -2009,7 +2155,7 @@ var listenCommand2 = defineCommand({
2009
2155
  const surface = (m) => {
2010
2156
  const text = typeof m.text === "string" ? m.text : "";
2011
2157
  const file = join3(spillDir, `${m.seq}.txt`);
2012
- writeFileSync3(file, text);
2158
+ writeFileSync4(file, text);
2013
2159
  const truncated = text.length > PREVIEW2;
2014
2160
  emit({
2015
2161
  type: "relay.message",
@@ -2506,6 +2652,7 @@ var toTurnResult = (message) => {
2506
2652
 
2507
2653
  // src/session/runner/turn.ts
2508
2654
  var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
2655
+ var heartbeatMs = () => Number(process.env.BATON_TURN_HEARTBEAT_MS) || 3e4;
2509
2656
  var TIMEOUT = /* @__PURE__ */ Symbol("turn-timeout");
2510
2657
  var ABORTED = /* @__PURE__ */ Symbol("turn-aborted");
2511
2658
  var finalizeTurn = async (result, worker2, log) => {
@@ -2558,6 +2705,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
2558
2705
  const abort = new AbortController();
2559
2706
  const ceiling = turnTimeoutMs();
2560
2707
  let timer;
2708
+ let heartbeat;
2561
2709
  const timeout = new Promise((resolve2) => {
2562
2710
  timer = setTimeout(() => resolve2(TIMEOUT), ceiling);
2563
2711
  });
@@ -2566,6 +2714,9 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
2566
2714
  else externalSignal?.addEventListener("abort", () => resolve2(ABORTED), { once: true });
2567
2715
  });
2568
2716
  try {
2717
+ heartbeat = setInterval(() => {
2718
+ void worker2.emitEvent("turn_heartbeat", {}).catch((e) => log(`[heartbeat] ${String(e)}`));
2719
+ }, heartbeatMs());
2569
2720
  const messages = startQuery(config, text, resuming, queryFn, abort, log, {
2570
2721
  envOverlay,
2571
2722
  planMode,
@@ -2600,6 +2751,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
2600
2751
  return -1;
2601
2752
  } finally {
2602
2753
  clearTimeout(timer);
2754
+ clearInterval(heartbeat);
2603
2755
  }
2604
2756
  };
2605
2757
 
@@ -2680,10 +2832,17 @@ var runDaemon = async (config, deps, signal) => {
2680
2832
  busy = false;
2681
2833
  }
2682
2834
  };
2835
+ const closeOrphanedTurn = async (reason) => {
2836
+ const events = await deps.worker.listEvents();
2837
+ if (!isAgentWorking(events)) return;
2838
+ await deps.worker.emitEvent("turn_error", { message: reason, synthetic: true });
2839
+ log(`closed orphaned open turn (${reason})`);
2840
+ };
2683
2841
  const reconcile = async () => {
2684
2842
  reconciling = true;
2685
2843
  try {
2686
- const pending = unstartedUserMessages(await deps.worker.listEvents());
2844
+ const events = await deps.worker.listEvents();
2845
+ const pending = unstartedUserMessages(events);
2687
2846
  let added = 0;
2688
2847
  for (const ev of pending) {
2689
2848
  if (state.seen.has(ev.sequence)) continue;
@@ -2695,6 +2854,12 @@ var runDaemon = async (config, deps, signal) => {
2695
2854
  log(`reconciled ${added} queued message(s) from transcript`);
2696
2855
  lastActivity = Date.now();
2697
2856
  void drain();
2857
+ } else if (pendingQueue.length === 0 && !busy && !currentTurn && isAgentWorking(events)) {
2858
+ await deps.worker.emitEvent("turn_error", {
2859
+ message: "turn abandoned \u2014 runner restarted",
2860
+ synthetic: true
2861
+ });
2862
+ log("reconcile: closed orphaned open turn from a prior child");
2698
2863
  }
2699
2864
  } catch (e) {
2700
2865
  log(`reconcile failed: ${String(e)}`);
@@ -2719,6 +2884,11 @@ var runDaemon = async (config, deps, signal) => {
2719
2884
  if (currentTurn) {
2720
2885
  log("interrupt \u2014 aborting current turn");
2721
2886
  currentTurn.abort();
2887
+ } else {
2888
+ log("interrupt \u2014 no live turn; closing any orphaned open turn");
2889
+ void closeOrphanedTurn("interrupted by user").catch(
2890
+ (e) => log(`orphan close failed: ${String(e)}`)
2891
+ );
2722
2892
  }
2723
2893
  },
2724
2894
  // Report active only now that we're subscribed — before this, a message
@@ -2811,44 +2981,6 @@ var sessionRunCommand = defineCommand({
2811
2981
  }
2812
2982
  });
2813
2983
 
2814
- // src/commands/attach.ts
2815
- import { openAsBlob } from "node:fs";
2816
- import { basename } from "node:path";
2817
-
2818
- // src/mime.ts
2819
- import { extname } from "node:path";
2820
- var BY_EXT = {
2821
- ".png": "image/png",
2822
- ".jpg": "image/jpeg",
2823
- ".jpeg": "image/jpeg",
2824
- ".gif": "image/gif",
2825
- ".webp": "image/webp",
2826
- ".svg": "image/svg+xml",
2827
- ".pdf": "application/pdf",
2828
- ".txt": "text/plain",
2829
- ".md": "text/markdown",
2830
- ".json": "application/json",
2831
- ".csv": "text/csv"
2832
- };
2833
- var contentTypeForPath = (path) => BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
2834
-
2835
- // src/commands/attach.ts
2836
- var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
2837
- var uploadAttachments = async (client, sessionId, paths) => {
2838
- const out = [];
2839
- for (const path of paths) {
2840
- const body = await openAsBlob(path, { type: contentTypeForPath(path) });
2841
- out.push(
2842
- await client.sessions.uploadAttachment(sessionId, {
2843
- filename: basename(path),
2844
- contentType: contentTypeForPath(path),
2845
- body
2846
- })
2847
- );
2848
- }
2849
- return out;
2850
- };
2851
-
2852
2984
  // src/commands/session/send.ts
2853
2985
  var sessionSendCommand = defineCommand({
2854
2986
  meta: { name: "send", description: "post a chat message into a session (no streaming reply)" },
@@ -3421,7 +3553,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
3421
3553
 
3422
3554
  // src/worker/machine-id.ts
3423
3555
  import { randomUUID as randomUUID2 } from "node:crypto";
3424
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
3556
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
3425
3557
  import { homedir as homedir3 } from "node:os";
3426
3558
  import { dirname as dirname3, join as join8 } from "node:path";
3427
3559
  var machineIdPath = (env = process.env) => join8(env.XDG_DATA_HOME ?? join8(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
@@ -3432,7 +3564,7 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
3432
3564
  }
3433
3565
  const id = randomUUID2();
3434
3566
  mkdirSync4(dirname3(path), { recursive: true });
3435
- writeFileSync4(path, `${id}
3567
+ writeFileSync5(path, `${id}
3436
3568
  `, "utf8");
3437
3569
  return id;
3438
3570
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lesscap/baton-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",