sakuraai 0.0.3 → 0.0.5

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/index.js +316 -71
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -379,16 +379,16 @@ function findFile(sessionId) {
379
379
  }
380
380
  function readMessages(file) {
381
381
  const msgs = [];
382
- let startSecs = null;
382
+ let lastUserSecs = null;
383
383
  for (const entry of jsonlEntries(file)) {
384
384
  if (entry?.isSidechain === true) continue;
385
385
  const tsStr = entry?.timestamp ?? "";
386
386
  const tsSecs = parseIsoUnix(tsStr);
387
- if (startSecs == null) startSecs = tsSecs;
388
387
  if (entry?.type === "user") {
389
388
  if (entry?.userType !== "external") continue;
390
389
  const text = extractTextFromContent(entry?.message?.content);
391
390
  if (text) {
391
+ lastUserSecs = tsSecs;
392
392
  msgs.push({
393
393
  id: entry?.uuid ?? "",
394
394
  role: "user",
@@ -400,15 +400,16 @@ function readMessages(file) {
400
400
  } else if (entry?.type === "assistant") {
401
401
  const { text, hasTools } = extractAssistantText(entry?.message?.content);
402
402
  if (!text) continue;
403
- const inputTokens = entry?.message?.usage?.input_tokens;
404
- const elapsed = startSecs != null && tsSecs != null ? Math.max(0, tsSecs - startSecs) : void 0;
403
+ const u = entry?.message?.usage;
404
+ const total = (u?.input_tokens ?? 0) + (u?.cache_read_input_tokens ?? 0) + (u?.cache_creation_input_tokens ?? 0);
405
+ const elapsed = lastUserSecs != null && tsSecs != null ? Math.max(0, tsSecs - lastUserSecs) : void 0;
405
406
  msgs.push({
406
407
  id: entry?.uuid ?? "",
407
408
  role: "assistant",
408
409
  content: text,
409
410
  timestamp: tsStr,
410
411
  hasToolUse: hasTools,
411
- inputTokens: typeof inputTokens === "number" ? inputTokens : void 0,
412
+ inputTokens: total > 0 ? total : void 0,
412
413
  elapsedSecs: elapsed
413
414
  });
414
415
  }
@@ -431,31 +432,62 @@ function send(sessionId, message, onData) {
431
432
  return new Promise((resolve) => {
432
433
  const bin = process.env.CLAUDE_BIN || "claude";
433
434
  const cwd = sessionCwd(sessionId);
434
- const child = spawn(bin, ["--resume", sessionId, "-p", message], {
435
- cwd: cwd && fs3.existsSync(cwd) ? cwd : void 0,
436
- stdio: ["ignore", "pipe", "pipe"]
437
- });
438
- let out = "";
435
+ const child = spawn(
436
+ bin,
437
+ [
438
+ "--resume",
439
+ sessionId,
440
+ "-p",
441
+ message,
442
+ "--output-format",
443
+ "stream-json",
444
+ "--include-partial-messages",
445
+ "--verbose"
446
+ ],
447
+ {
448
+ cwd: cwd && fs3.existsSync(cwd) ? cwd : void 0,
449
+ stdio: ["ignore", "pipe", "pipe"]
450
+ }
451
+ );
452
+ let buf = "";
453
+ let finalText = "";
439
454
  let err = "";
440
455
  child.stdout.on("data", (d) => {
441
- out += d.toString();
442
- onData?.(d.toString());
456
+ buf += d.toString();
457
+ let nl;
458
+ while ((nl = buf.indexOf("\n")) >= 0) {
459
+ const line = buf.slice(0, nl);
460
+ buf = buf.slice(nl + 1);
461
+ if (!line.trim()) continue;
462
+ try {
463
+ const delta = textDelta(JSON.parse(line));
464
+ if (delta) {
465
+ finalText += delta;
466
+ onData?.(delta);
467
+ }
468
+ } catch {
469
+ }
470
+ }
443
471
  });
444
472
  child.stderr.on("data", (d) => err += d.toString());
445
- child.on(
446
- "error",
447
- (e) => resolve({ ok: false, output: "", error: e.message })
448
- );
473
+ child.on("error", (e) => resolve({ ok: false, output: "", error: e.message }));
449
474
  child.on(
450
475
  "close",
451
476
  (code) => resolve({
452
477
  ok: code === 0,
453
- output: out.trim(),
478
+ output: finalText.trim(),
454
479
  error: code === 0 ? void 0 : err.trim() || `exited ${code}`
455
480
  })
456
481
  );
457
482
  });
458
483
  }
484
+ function textDelta(ev) {
485
+ const obj = ev?.type === "stream_event" ? ev.event : ev;
486
+ if (obj?.type === "content_block_delta" && obj?.delta?.type === "text_delta") {
487
+ return obj.delta.text ?? null;
488
+ }
489
+ return null;
490
+ }
459
491
  var PROJECTS_DIR;
460
492
  var init_claude = __esm({
461
493
  "src/runtime/claude.ts"() {
@@ -540,6 +572,7 @@ function readMessages2(file) {
540
572
  return [];
541
573
  }
542
574
  const msgs = [];
575
+ let lastUserSecs = null;
543
576
  for (const line of content.split("\n")) {
544
577
  if (!line.trim()) continue;
545
578
  let entry;
@@ -559,7 +592,16 @@ function readMessages2(file) {
559
592
  const text = part?.text ?? "";
560
593
  if (!text) continue;
561
594
  const ts = entry?.timestamp ?? "";
562
- msgs.push({ id: ts, role, content: text, timestamp: ts, hasToolUse: false });
595
+ const tsSecs = parseIsoUnix(ts);
596
+ if (role === "user") lastUserSecs = tsSecs;
597
+ msgs.push({
598
+ id: ts,
599
+ role,
600
+ content: text,
601
+ timestamp: ts,
602
+ hasToolUse: false,
603
+ elapsedSecs: role === "assistant" && lastUserSecs != null && tsSecs != null ? Math.max(0, tsSecs - lastUserSecs) : void 0
604
+ });
563
605
  }
564
606
  return msgs;
565
607
  }
@@ -683,8 +725,8 @@ async function messages3(sessionId) {
683
725
  } catch {
684
726
  }
685
727
  }
686
- const startMs = msgRows[0]?.time_created ?? 0;
687
728
  const out = [];
729
+ let lastUserMs = null;
688
730
  for (const m of msgRows) {
689
731
  let v;
690
732
  try {
@@ -697,13 +739,15 @@ async function messages3(sessionId) {
697
739
  const text = (partsMap.get(m.id) ?? []).join("");
698
740
  if (!text) continue;
699
741
  const mtime = Math.floor(m.time_created / 1e3);
742
+ if (role === "user") lastUserMs = m.time_created;
700
743
  out.push({
701
744
  id: m.id,
702
745
  role,
703
746
  content: text,
704
747
  timestamp: chronoIso(mtime),
705
748
  hasToolUse: false,
706
- elapsedSecs: role === "assistant" ? Math.floor((m.time_created - startMs) / 1e3) : void 0
749
+ // Per-turn duration: assistant time the user msg that started it.
750
+ elapsedSecs: role === "assistant" && lastUserMs != null ? Math.max(0, Math.floor((m.time_created - lastUserMs) / 1e3)) : void 0
707
751
  });
708
752
  }
709
753
  return out;
@@ -1028,12 +1072,91 @@ var init_registry = __esm({
1028
1072
  }
1029
1073
  });
1030
1074
 
1031
- // src/tailscale.ts
1075
+ // src/runtime/fsops.ts
1032
1076
  import fs7 from "fs";
1077
+ import path6 from "path";
1078
+ import os5 from "os";
1079
+ function expand(p) {
1080
+ if (!p || p === "~") return HOME2;
1081
+ if (p.startsWith("~/")) return path6.join(HOME2, p.slice(2));
1082
+ return path6.resolve(p);
1083
+ }
1084
+ function list2(dir) {
1085
+ const abs = expand(dir);
1086
+ const dirents = fs7.readdirSync(abs, { withFileTypes: true });
1087
+ const entries = [];
1088
+ for (const d of dirents) {
1089
+ const p = path6.join(abs, d.name);
1090
+ let size = 0;
1091
+ let mtime = 0;
1092
+ try {
1093
+ const st = fs7.statSync(p);
1094
+ size = st.size;
1095
+ mtime = Math.floor(st.mtimeMs / 1e3);
1096
+ } catch {
1097
+ }
1098
+ const type = d.isDirectory() ? "dir" : d.isSymbolicLink() ? "symlink" : d.isFile() ? "file" : "other";
1099
+ entries.push({ name: d.name, path: p, type, size, mtime });
1100
+ }
1101
+ entries.sort((a, b) => {
1102
+ if (a.type === "dir" && b.type !== "dir") return -1;
1103
+ if (a.type !== "dir" && b.type === "dir") return 1;
1104
+ return a.name.localeCompare(b.name);
1105
+ });
1106
+ const parent = abs === "/" ? null : path6.dirname(abs);
1107
+ return { path: abs, parent, entries };
1108
+ }
1109
+ function looksBinary(buf) {
1110
+ const n = Math.min(buf.length, 4096);
1111
+ for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
1112
+ return false;
1113
+ }
1114
+ function read(file) {
1115
+ const abs = expand(file);
1116
+ const st = fs7.statSync(abs);
1117
+ const fd = fs7.openSync(abs, "r");
1118
+ try {
1119
+ const len = Math.min(st.size, READ_CAP);
1120
+ const buf = Buffer.alloc(len);
1121
+ fs7.readSync(fd, buf, 0, len, 0);
1122
+ const binary = looksBinary(buf);
1123
+ return {
1124
+ path: abs,
1125
+ size: st.size,
1126
+ truncated: st.size > READ_CAP,
1127
+ binary,
1128
+ content: binary ? "" : buf.toString("utf8")
1129
+ };
1130
+ } finally {
1131
+ fs7.closeSync(fd);
1132
+ }
1133
+ }
1134
+ function write2(file, content) {
1135
+ const abs = expand(file);
1136
+ fs7.mkdirSync(path6.dirname(abs), { recursive: true });
1137
+ fs7.writeFileSync(abs, content, "utf8");
1138
+ return { path: abs, bytes: Buffer.byteLength(content, "utf8") };
1139
+ }
1140
+ function mkdir(dir) {
1141
+ const abs = expand(dir);
1142
+ fs7.mkdirSync(abs, { recursive: true });
1143
+ return { path: abs };
1144
+ }
1145
+ var HOME2, READ_CAP;
1146
+ var init_fsops = __esm({
1147
+ "src/runtime/fsops.ts"() {
1148
+ "use strict";
1149
+ HOME2 = os5.homedir();
1150
+ READ_CAP = 512 * 1024;
1151
+ }
1152
+ });
1153
+
1154
+ // src/tailscale.ts
1155
+ import fs8 from "fs";
1033
1156
  import { spawn as spawn5 } from "child_process";
1034
1157
  function tsBin() {
1035
1158
  const macPath = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
1036
- if (fs7.existsSync(macPath)) return macPath;
1159
+ if (fs8.existsSync(macPath)) return macPath;
1037
1160
  return process.env.TAILSCALE_BIN || "tailscale";
1038
1161
  }
1039
1162
  function runTs(args, timeoutMs = 6e4) {
@@ -1243,15 +1366,75 @@ var init_stream = __esm({
1243
1366
  }
1244
1367
  });
1245
1368
 
1369
+ // src/daemon/terminal.ts
1370
+ import os6 from "os";
1371
+ import { spawn as spawn6 } from "child_process";
1372
+ function defaultShell() {
1373
+ return process.env.SHELL || (process.platform === "win32" ? "cmd.exe" : "/bin/zsh");
1374
+ }
1375
+ function openTerminal(ws, opts) {
1376
+ const shell = defaultShell();
1377
+ const child = spawn6(shell, ["-i"], {
1378
+ cwd: opts?.cwd && opts.cwd.trim() ? opts.cwd : os6.homedir(),
1379
+ env: { ...process.env, TERM: "dumb", PS1: "$ " }
1380
+ });
1381
+ const sendOut = (data) => {
1382
+ try {
1383
+ ws.send(JSON.stringify({ type: "output", data }));
1384
+ } catch {
1385
+ }
1386
+ };
1387
+ child.stdout.on("data", (d) => sendOut(d.toString()));
1388
+ child.stderr.on("data", (d) => sendOut(d.toString()));
1389
+ child.on("exit", (code) => {
1390
+ try {
1391
+ ws.send(JSON.stringify({ type: "exit", code }));
1392
+ } catch {
1393
+ }
1394
+ });
1395
+ child.on("error", (e) => sendOut(`
1396
+ [shell error: ${e.message}]
1397
+ `));
1398
+ terminals.set(ws, { child });
1399
+ try {
1400
+ ws.send(JSON.stringify({ type: "ready", shell, cwd: opts?.cwd || os6.homedir() }));
1401
+ } catch {
1402
+ }
1403
+ }
1404
+ function writeTerminal(ws, input) {
1405
+ const t = terminals.get(ws);
1406
+ if (!t) return;
1407
+ try {
1408
+ t.child.stdin.write(input);
1409
+ } catch {
1410
+ }
1411
+ }
1412
+ function closeTerminal(ws) {
1413
+ const t = terminals.get(ws);
1414
+ if (!t) return;
1415
+ try {
1416
+ t.child.kill("SIGKILL");
1417
+ } catch {
1418
+ }
1419
+ terminals.delete(ws);
1420
+ }
1421
+ var terminals;
1422
+ var init_terminal = __esm({
1423
+ "src/daemon/terminal.ts"() {
1424
+ "use strict";
1425
+ terminals = /* @__PURE__ */ new WeakMap();
1426
+ }
1427
+ });
1428
+
1246
1429
  // src/daemon/server.ts
1247
1430
  import http from "http";
1248
- import fs8 from "fs";
1431
+ import fs9 from "fs";
1249
1432
  import { URL } from "url";
1250
1433
  import { WebSocketServer } from "ws";
1251
- function add(method, path6, handler) {
1434
+ function add(method, path7, handler) {
1252
1435
  const keys = [];
1253
1436
  const pattern = new RegExp(
1254
- "^" + path6.replace(/:[^/]+/g, (m) => {
1437
+ "^" + path7.replace(/:[^/]+/g, (m) => {
1255
1438
  keys.push(m.slice(1));
1256
1439
  return "([^/]+)";
1257
1440
  }) + "/?$"
@@ -1329,15 +1512,28 @@ function createServer(port2) {
1329
1512
  send4(res, 500, fail("nonzero", e.message));
1330
1513
  }
1331
1514
  });
1332
- const wss = new WebSocketServer({ server, path: "/ws" });
1333
- wss.on("connection", (ws, req) => {
1515
+ const wss = new WebSocketServer({ noServer: true });
1516
+ const pty = new WebSocketServer({ noServer: true });
1517
+ const authedToken = (req) => {
1334
1518
  const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1335
- const token = u.searchParams.get("token");
1519
+ return u.searchParams.get("token");
1520
+ };
1521
+ server.on("upgrade", (req, socket, head) => {
1522
+ const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1523
+ const target = u.pathname === "/ws" ? wss : u.pathname === "/pty" ? pty : null;
1524
+ if (!target) {
1525
+ socket.destroy();
1526
+ return;
1527
+ }
1336
1528
  const expected = getToken();
1337
- if (!expected || token !== expected) {
1338
- ws.close(1008, "Unauthorized");
1529
+ if (!expected || authedToken(req) !== expected) {
1530
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
1531
+ socket.destroy();
1339
1532
  return;
1340
1533
  }
1534
+ target.handleUpgrade(req, socket, head, (ws) => target.emit("connection", ws, req));
1535
+ });
1536
+ wss.on("connection", (ws) => {
1341
1537
  ws.on("message", (raw) => {
1342
1538
  try {
1343
1539
  const msg = JSON.parse(raw.toString());
@@ -1353,6 +1549,21 @@ function createServer(port2) {
1353
1549
  ws.on("close", () => hub.remove(ws));
1354
1550
  ws.on("error", () => hub.remove(ws));
1355
1551
  });
1552
+ pty.on("connection", (ws, req) => {
1553
+ const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1554
+ openTerminal(ws, { cwd: u.searchParams.get("cwd") ?? void 0 });
1555
+ ws.on("message", (raw) => {
1556
+ try {
1557
+ const msg = JSON.parse(raw.toString());
1558
+ if (msg?.type === "input" && typeof msg.data === "string") {
1559
+ writeTerminal(ws, msg.data);
1560
+ }
1561
+ } catch {
1562
+ }
1563
+ });
1564
+ ws.on("close", () => closeTerminal(ws));
1565
+ ws.on("error", () => closeTerminal(ws));
1566
+ });
1356
1567
  return server;
1357
1568
  }
1358
1569
  function setVersion(v) {
@@ -1367,14 +1578,16 @@ var init_server = __esm({
1367
1578
  init_logger();
1368
1579
  init_sessions();
1369
1580
  init_registry();
1581
+ init_fsops();
1370
1582
  init_tailscale();
1371
1583
  init_registry();
1372
1584
  init_hub();
1373
1585
  init_stream();
1586
+ init_terminal();
1374
1587
  routes = [];
1375
1588
  add("GET", "/sessions", async () => {
1376
- const list2 = await list();
1377
- return { sessions: list2 };
1589
+ const list3 = await list();
1590
+ return { sessions: list3 };
1378
1591
  });
1379
1592
  add("GET", "/sessions/:id/messages", async ({ params, url }) => {
1380
1593
  const agent = url.searchParams.get("agent") || void 0;
@@ -1409,7 +1622,7 @@ var init_server = __esm({
1409
1622
  case "logs": {
1410
1623
  let logs2 = "";
1411
1624
  try {
1412
- const lines = fs8.readFileSync(LOG_PATH, "utf8").split("\n");
1625
+ const lines = fs9.readFileSync(LOG_PATH, "utf8").split("\n");
1413
1626
  logs2 = lines.slice(-200).join("\n");
1414
1627
  } catch {
1415
1628
  }
@@ -1538,6 +1751,38 @@ var init_server = __esm({
1538
1751
  return fail("nonzero", e.message);
1539
1752
  }
1540
1753
  });
1754
+ add("GET", "/sakura/fs/list", ({ url }) => {
1755
+ try {
1756
+ return ok(list2(url.searchParams.get("path") ?? void 0));
1757
+ } catch (e) {
1758
+ return fail("nonzero", e.message);
1759
+ }
1760
+ });
1761
+ add("GET", "/sakura/fs/read", ({ url }) => {
1762
+ const p = url.searchParams.get("path");
1763
+ if (!p) return fail("nonzero", "path required");
1764
+ try {
1765
+ return ok(read(p));
1766
+ } catch (e) {
1767
+ return fail("nonzero", e.message);
1768
+ }
1769
+ });
1770
+ add("POST", "/sakura/fs/write", ({ body }) => {
1771
+ if (!body?.path) return fail("nonzero", "path required");
1772
+ try {
1773
+ return ok(write2(body.path, String(body.content ?? "")));
1774
+ } catch (e) {
1775
+ return fail("nonzero", e.message);
1776
+ }
1777
+ });
1778
+ add("POST", "/sakura/fs/mkdir", ({ body }) => {
1779
+ if (!body?.path) return fail("nonzero", "path required");
1780
+ try {
1781
+ return ok(mkdir(body.path));
1782
+ } catch (e) {
1783
+ return fail("nonzero", e.message);
1784
+ }
1785
+ });
1541
1786
  add("GET", "/tailscale/health", () => installed());
1542
1787
  add("GET", "/tailscale/status", ({ port: port2 }) => status(port2));
1543
1788
  add("POST", "/tailscale/up", () => up());
@@ -1549,11 +1794,11 @@ var init_server = __esm({
1549
1794
  });
1550
1795
 
1551
1796
  // src/daemon/manager.ts
1552
- import fs9 from "fs";
1553
- import { spawn as spawn6 } from "child_process";
1797
+ import fs10 from "fs";
1798
+ import { spawn as spawn7 } from "child_process";
1554
1799
  function readDaemonInfo() {
1555
1800
  try {
1556
- return JSON.parse(fs9.readFileSync(DAEMON_PATH, "utf8"));
1801
+ return JSON.parse(fs10.readFileSync(DAEMON_PATH, "utf8"));
1557
1802
  } catch {
1558
1803
  return null;
1559
1804
  }
@@ -1570,8 +1815,8 @@ function running() {
1570
1815
  const info2 = readDaemonInfo();
1571
1816
  if (info2 && pidAlive(info2.pid)) return info2;
1572
1817
  if (info2) {
1573
- fs9.rmSync(DAEMON_PATH, { force: true });
1574
- fs9.rmSync(PID_PATH, { force: true });
1818
+ fs10.rmSync(DAEMON_PATH, { force: true });
1819
+ fs10.rmSync(PID_PATH, { force: true });
1575
1820
  }
1576
1821
  return null;
1577
1822
  }
@@ -1594,8 +1839,8 @@ async function runServer(version) {
1594
1839
  url: `http://${host}:${port2}`,
1595
1840
  startedAt: Date.now()
1596
1841
  };
1597
- fs9.writeFileSync(DAEMON_PATH, JSON.stringify(info2, null, 2));
1598
- fs9.writeFileSync(PID_PATH, String(process.pid));
1842
+ fs10.writeFileSync(DAEMON_PATH, JSON.stringify(info2, null, 2));
1843
+ fs10.writeFileSync(PID_PATH, String(process.pid));
1599
1844
  const tailnet = await discoverBaseUrl(port2).catch(() => null);
1600
1845
  log.info(`sakuraai runtime listening on http://0.0.0.0:${port2}`);
1601
1846
  if (tailnet) log.info(`reachable over Tailscale at ${tailnet}`);
@@ -1603,8 +1848,8 @@ async function runServer(version) {
1603
1848
  const shutdown = () => {
1604
1849
  log.info("shutting down");
1605
1850
  server.close();
1606
- fs9.rmSync(DAEMON_PATH, { force: true });
1607
- fs9.rmSync(PID_PATH, { force: true });
1851
+ fs10.rmSync(DAEMON_PATH, { force: true });
1852
+ fs10.rmSync(PID_PATH, { force: true });
1608
1853
  process.exit(0);
1609
1854
  };
1610
1855
  process.on("SIGINT", shutdown);
@@ -1615,9 +1860,9 @@ function start() {
1615
1860
  if (existing) return existing;
1616
1861
  ensureSakuraDirs();
1617
1862
  const entry = process.argv[1] ?? "";
1618
- const out = fs9.openSync(LOG_PATH, "a");
1619
- const err = fs9.openSync(LOG_PATH, "a");
1620
- const child = spawn6(process.execPath, [entry, "__run-daemon"], {
1863
+ const out = fs10.openSync(LOG_PATH, "a");
1864
+ const err = fs10.openSync(LOG_PATH, "a");
1865
+ const child = spawn7(process.execPath, [entry, "__run-daemon"], {
1621
1866
  detached: true,
1622
1867
  stdio: ["ignore", out, err],
1623
1868
  env: process.env
@@ -1638,8 +1883,8 @@ function stop() {
1638
1883
  process.kill(info2.pid, "SIGTERM");
1639
1884
  } catch {
1640
1885
  }
1641
- fs9.rmSync(DAEMON_PATH, { force: true });
1642
- fs9.rmSync(PID_PATH, { force: true });
1886
+ fs10.rmSync(DAEMON_PATH, { force: true });
1887
+ fs10.rmSync(PID_PATH, { force: true });
1643
1888
  return true;
1644
1889
  }
1645
1890
  function restart() {
@@ -1649,7 +1894,7 @@ function restart() {
1649
1894
  }
1650
1895
  function logs(lines = 50) {
1651
1896
  try {
1652
- const all = fs9.readFileSync(LOG_PATH, "utf8").split("\n");
1897
+ const all = fs10.readFileSync(LOG_PATH, "utf8").split("\n");
1653
1898
  return all.slice(-lines).join("\n");
1654
1899
  } catch {
1655
1900
  return "";
@@ -1666,7 +1911,7 @@ var init_manager = __esm({
1666
1911
  });
1667
1912
 
1668
1913
  // src/pairing.ts
1669
- import os5 from "os";
1914
+ import os7 from "os";
1670
1915
  import qrcode from "qrcode-terminal";
1671
1916
  async function buildPairing() {
1672
1917
  const token = requireToken();
@@ -1683,7 +1928,7 @@ function renderQr(deepLink) {
1683
1928
  });
1684
1929
  }
1685
1930
  function localIp() {
1686
- const nets = os5.networkInterfaces();
1931
+ const nets = os7.networkInterfaces();
1687
1932
  for (const name of Object.keys(nets)) {
1688
1933
  for (const net of nets[name] ?? []) {
1689
1934
  if (net.family === "IPv4" && !net.internal) return net.address;
@@ -1746,7 +1991,7 @@ var init_pair = __esm({
1746
1991
  import { Command } from "commander";
1747
1992
 
1748
1993
  // src/version.ts
1749
- var VERSION = "0.0.3";
1994
+ var VERSION = "0.0.5";
1750
1995
 
1751
1996
  // src/index.ts
1752
1997
  init_config();
@@ -1837,7 +2082,7 @@ function registerRuntime(program, version) {
1837
2082
  // src/commands/session.ts
1838
2083
  init_sessions();
1839
2084
  init_output();
1840
- import fs10 from "fs";
2085
+ import fs11 from "fs";
1841
2086
  function resolveSessionId(arg) {
1842
2087
  const id = arg || process.env.SAKURA_SESSION_ID;
1843
2088
  if (!id) die("Provide a sessionId (positional or via SAKURA_SESSION_ID).");
@@ -1846,7 +2091,7 @@ function resolveSessionId(arg) {
1846
2091
  async function resolvePrompt(positional, opts) {
1847
2092
  if (positional) return positional;
1848
2093
  if (opts.prompt) return opts.prompt;
1849
- if (opts.promptFile) return fs10.readFileSync(opts.promptFile, "utf8");
2094
+ if (opts.promptFile) return fs11.readFileSync(opts.promptFile, "utf8");
1850
2095
  if (!process.stdin.isTTY) {
1851
2096
  const chunks = [];
1852
2097
  for await (const c of process.stdin) chunks.push(c);
@@ -1862,15 +2107,15 @@ function registerSession(program) {
1862
2107
  if (opts.json) return printJson([]);
1863
2108
  return info("No archived sessions (archival is not tracked for local sessions).");
1864
2109
  }
1865
- const list2 = await list({
2110
+ const list3 = await list({
1866
2111
  agent: opts.agent,
1867
2112
  limit: opts.limit ? Number(opts.limit) : void 0
1868
2113
  });
1869
- if (opts.json) return printJson(list2);
1870
- if (!list2.length) return info("No sessions found.");
2114
+ if (opts.json) return printJson(list3);
2115
+ if (!list3.length) return info("No sessions found.");
1871
2116
  printTable(
1872
2117
  ["ID", "Agent", "Status", "Updated", "Title"],
1873
- list2.map((x) => [x.id.slice(0, 12), x.agent, x.status, x.updatedAt, x.title])
2118
+ list3.map((x) => [x.id.slice(0, 12), x.agent, x.status, x.updatedAt, x.title])
1874
2119
  );
1875
2120
  });
1876
2121
  s.command("show [sessionId]").description("Show session metadata").option("--json", "output as JSON").action(async (sessionId, opts) => {
@@ -1962,9 +2207,9 @@ function envToObj(pairs) {
1962
2207
  function registerRegistry(program) {
1963
2208
  const ws = program.command("workspace").description("Discover accessible workspaces");
1964
2209
  ws.command("list").description("List workspaces you can access").option("--json", "output as JSON").action((opts) => {
1965
- const list2 = listWorkspaces();
1966
- if (opts.json) return printJson(list2);
1967
- printTable(["ID", "Name", "Slug"], list2.map((w) => [w.id, w.name, w.slug]));
2210
+ const list3 = listWorkspaces();
2211
+ if (opts.json) return printJson(list3);
2212
+ printTable(["ID", "Name", "Slug"], list3.map((w) => [w.id, w.name, w.slug]));
1968
2213
  });
1969
2214
  const gh = program.command("github").description("GitHub repositories linked to a workspace");
1970
2215
  gh.command("list").description("List GitHub repositories linked to a workspace").option("--workspace <idOrSlug>", "workspace").option("--json", "output as JSON").action((opts) => {
@@ -1983,31 +2228,31 @@ function registerRegistry(program) {
1983
2228
  }
1984
2229
  });
1985
2230
  proj.command("list").description("List registered local projects").option("--json", "output as JSON").action((opts) => {
1986
- const list2 = listProjects();
1987
- if (opts.json) return printJson(list2);
1988
- if (!list2.length) return info("No projects registered. Add one with `sakuraai project add`.");
1989
- printTable(["ID", "Name", "Path"], list2.map((p) => [p.id.slice(0, 8), p.name, p.path]));
2231
+ const list3 = listProjects();
2232
+ if (opts.json) return printJson(list3);
2233
+ if (!list3.length) return info("No projects registered. Add one with `sakuraai project add`.");
2234
+ printTable(["ID", "Name", "Path"], list3.map((p) => [p.id.slice(0, 8), p.name, p.path]));
1990
2235
  });
1991
2236
  proj.command("delete <idOrNameOrPath>").description("Delete a registered local project").action((sel) => {
1992
2237
  deleteProject(sel) ? success("Project removed.") : warn("No matching project.");
1993
2238
  });
1994
2239
  const m = program.command("machine").description("Inspect machines in a workspace");
1995
2240
  m.command("list").description("List machines in a workspace").option("--workspace <idOrSlug>", "workspace").option("--online-only", "only machines with a recent heartbeat").option("--include-acp-capabilities", "include capability cache in JSON").option("--json", "output as JSON").action((opts) => {
1996
- const list2 = listMachines(!!opts.onlineOnly);
1997
- if (opts.json) return printJson(list2);
2241
+ const list3 = listMachines(!!opts.onlineOnly);
2242
+ if (opts.json) return printJson(list3);
1998
2243
  printTable(
1999
2244
  ["ID", "Name", "Online", "CLI"],
2000
- list2.map((x) => [x.id.slice(0, 8), x.name, x.online ? "yes" : "no", x.cli.join(", ") || "\u2014"])
2245
+ list3.map((x) => [x.id.slice(0, 8), x.name, x.online ? "yes" : "no", x.cli.join(", ") || "\u2014"])
2001
2246
  );
2002
2247
  });
2003
2248
  const ac = program.command("agent-config").description("Manage agent configs");
2004
2249
  ac.command("list").description("List configs in a workspace").option("--workspace <idOrSlug>", "workspace").option("--json", "output as JSON").action((opts) => {
2005
- const list2 = listAgentConfigs();
2006
- if (opts.json) return printJson(list2);
2007
- if (!list2.length) return info("No agent configs. Create one with `sakuraai agent-config create`.");
2250
+ const list3 = listAgentConfigs();
2251
+ if (opts.json) return printJson(list3);
2252
+ if (!list3.length) return info("No agent configs. Create one with `sakuraai agent-config create`.");
2008
2253
  printTable(
2009
2254
  ["ID", "Name", "Type", "Description"],
2010
- list2.map((a) => [a.id.slice(0, 8), a.name, a.agentType, a.description ?? ""])
2255
+ list3.map((a) => [a.id.slice(0, 8), a.name, a.agentType, a.description ?? ""])
2011
2256
  );
2012
2257
  });
2013
2258
  ac.command("show <idOrName>").description("Show one config").option("--json", "output as JSON").action((sel, opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sakuraai",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Sakura Agent CLI + local runtime for managing AI coding sessions (Claude Code, Codex, OpenCode) and reaching them privately from the Sakura mobile app over Tailscale",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",