sakuraai 0.0.4 → 0.0.6

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 +278 -51
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1072,12 +1072,91 @@ var init_registry = __esm({
1072
1072
  }
1073
1073
  });
1074
1074
 
1075
- // src/tailscale.ts
1075
+ // src/runtime/fsops.ts
1076
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";
1077
1156
  import { spawn as spawn5 } from "child_process";
1078
1157
  function tsBin() {
1079
1158
  const macPath = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
1080
- if (fs7.existsSync(macPath)) return macPath;
1159
+ if (fs8.existsSync(macPath)) return macPath;
1081
1160
  return process.env.TAILSCALE_BIN || "tailscale";
1082
1161
  }
1083
1162
  function runTs(args, timeoutMs = 6e4) {
@@ -1287,15 +1366,101 @@ var init_stream = __esm({
1287
1366
  }
1288
1367
  });
1289
1368
 
1369
+ // src/daemon/terminal.ts
1370
+ import os6 from "os";
1371
+ import { spawn as spawn6 } from "child_process";
1372
+ function cleanOutput(t, chunk) {
1373
+ let s = t.buf + chunk;
1374
+ s = s.replace(OSC, "").replace(CSI, "").replace(ESC2, "");
1375
+ const esc = s.lastIndexOf(ESC);
1376
+ if (esc !== -1) {
1377
+ t.buf = s.slice(esc);
1378
+ s = s.slice(0, esc);
1379
+ } else {
1380
+ t.buf = "";
1381
+ }
1382
+ return s.replace(OTHER_CTRL, "");
1383
+ }
1384
+ function defaultShell() {
1385
+ return process.env.SHELL || (process.platform === "win32" ? "cmd.exe" : "/bin/zsh");
1386
+ }
1387
+ function openTerminal(ws, opts) {
1388
+ const shell = defaultShell();
1389
+ const child = spawn6(shell, ["-i"], {
1390
+ cwd: opts?.cwd && opts.cwd.trim() ? opts.cwd : os6.homedir(),
1391
+ env: {
1392
+ ...process.env,
1393
+ TERM: "dumb",
1394
+ // Quiet common shell-integration noise so output stays clean.
1395
+ WARP_HONOR_PS1: "0",
1396
+ ITERM_SHELL_INTEGRATION_INSTALLED: ""
1397
+ }
1398
+ });
1399
+ const term = { child, buf: "" };
1400
+ terminals.set(ws, term);
1401
+ const sendOut = (raw) => {
1402
+ const data = cleanOutput(term, raw);
1403
+ if (!data) return;
1404
+ try {
1405
+ ws.send(JSON.stringify({ type: "output", data }));
1406
+ } catch {
1407
+ }
1408
+ };
1409
+ child.stdout.on("data", (d) => sendOut(d.toString()));
1410
+ child.stderr.on("data", (d) => sendOut(d.toString()));
1411
+ child.on("exit", (code) => {
1412
+ try {
1413
+ ws.send(JSON.stringify({ type: "exit", code }));
1414
+ } catch {
1415
+ }
1416
+ });
1417
+ child.on("error", (e) => sendOut(`
1418
+ [shell error: ${e.message}]
1419
+ `));
1420
+ try {
1421
+ ws.send(JSON.stringify({ type: "ready", shell, cwd: opts?.cwd || os6.homedir() }));
1422
+ } catch {
1423
+ }
1424
+ }
1425
+ function writeTerminal(ws, input) {
1426
+ const t = terminals.get(ws);
1427
+ if (!t) return;
1428
+ try {
1429
+ t.child.stdin.write(input);
1430
+ } catch {
1431
+ }
1432
+ }
1433
+ function closeTerminal(ws) {
1434
+ const t = terminals.get(ws);
1435
+ if (!t) return;
1436
+ try {
1437
+ t.child.kill("SIGKILL");
1438
+ } catch {
1439
+ }
1440
+ terminals.delete(ws);
1441
+ }
1442
+ var terminals, ESC, OSC, CSI, ESC2, OTHER_CTRL;
1443
+ var init_terminal = __esm({
1444
+ "src/daemon/terminal.ts"() {
1445
+ "use strict";
1446
+ terminals = /* @__PURE__ */ new WeakMap();
1447
+ ESC = String.fromCharCode(27);
1448
+ OSC = new RegExp("\\u001B\\][\\s\\S]*?(?:\\u0007|\\u001B\\\\)", "g");
1449
+ CSI = new RegExp("\\u001B\\[[0-9;?]*[ -/]*[@-~]", "g");
1450
+ ESC2 = new RegExp("\\u001B[@-Z\\\\-_]", "g");
1451
+ OTHER_CTRL = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]", "g");
1452
+ }
1453
+ });
1454
+
1290
1455
  // src/daemon/server.ts
1291
1456
  import http from "http";
1292
- import fs8 from "fs";
1457
+ import fs9 from "fs";
1293
1458
  import { URL } from "url";
1294
1459
  import { WebSocketServer } from "ws";
1295
- function add(method, path6, handler) {
1460
+ function add(method, path7, handler) {
1296
1461
  const keys = [];
1297
1462
  const pattern = new RegExp(
1298
- "^" + path6.replace(/:[^/]+/g, (m) => {
1463
+ "^" + path7.replace(/:[^/]+/g, (m) => {
1299
1464
  keys.push(m.slice(1));
1300
1465
  return "([^/]+)";
1301
1466
  }) + "/?$"
@@ -1373,15 +1538,28 @@ function createServer(port2) {
1373
1538
  send4(res, 500, fail("nonzero", e.message));
1374
1539
  }
1375
1540
  });
1376
- const wss = new WebSocketServer({ server, path: "/ws" });
1377
- wss.on("connection", (ws, req) => {
1541
+ const wss = new WebSocketServer({ noServer: true });
1542
+ const pty = new WebSocketServer({ noServer: true });
1543
+ const authedToken = (req) => {
1378
1544
  const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1379
- const token = u.searchParams.get("token");
1545
+ return u.searchParams.get("token");
1546
+ };
1547
+ server.on("upgrade", (req, socket, head) => {
1548
+ const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1549
+ const target = u.pathname === "/ws" ? wss : u.pathname === "/pty" ? pty : null;
1550
+ if (!target) {
1551
+ socket.destroy();
1552
+ return;
1553
+ }
1380
1554
  const expected = getToken();
1381
- if (!expected || token !== expected) {
1382
- ws.close(1008, "Unauthorized");
1555
+ if (!expected || authedToken(req) !== expected) {
1556
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
1557
+ socket.destroy();
1383
1558
  return;
1384
1559
  }
1560
+ target.handleUpgrade(req, socket, head, (ws) => target.emit("connection", ws, req));
1561
+ });
1562
+ wss.on("connection", (ws) => {
1385
1563
  ws.on("message", (raw) => {
1386
1564
  try {
1387
1565
  const msg = JSON.parse(raw.toString());
@@ -1397,6 +1575,21 @@ function createServer(port2) {
1397
1575
  ws.on("close", () => hub.remove(ws));
1398
1576
  ws.on("error", () => hub.remove(ws));
1399
1577
  });
1578
+ pty.on("connection", (ws, req) => {
1579
+ const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1580
+ openTerminal(ws, { cwd: u.searchParams.get("cwd") ?? void 0 });
1581
+ ws.on("message", (raw) => {
1582
+ try {
1583
+ const msg = JSON.parse(raw.toString());
1584
+ if (msg?.type === "input" && typeof msg.data === "string") {
1585
+ writeTerminal(ws, msg.data);
1586
+ }
1587
+ } catch {
1588
+ }
1589
+ });
1590
+ ws.on("close", () => closeTerminal(ws));
1591
+ ws.on("error", () => closeTerminal(ws));
1592
+ });
1400
1593
  return server;
1401
1594
  }
1402
1595
  function setVersion(v) {
@@ -1411,14 +1604,16 @@ var init_server = __esm({
1411
1604
  init_logger();
1412
1605
  init_sessions();
1413
1606
  init_registry();
1607
+ init_fsops();
1414
1608
  init_tailscale();
1415
1609
  init_registry();
1416
1610
  init_hub();
1417
1611
  init_stream();
1612
+ init_terminal();
1418
1613
  routes = [];
1419
1614
  add("GET", "/sessions", async () => {
1420
- const list2 = await list();
1421
- return { sessions: list2 };
1615
+ const list3 = await list();
1616
+ return { sessions: list3 };
1422
1617
  });
1423
1618
  add("GET", "/sessions/:id/messages", async ({ params, url }) => {
1424
1619
  const agent = url.searchParams.get("agent") || void 0;
@@ -1453,7 +1648,7 @@ var init_server = __esm({
1453
1648
  case "logs": {
1454
1649
  let logs2 = "";
1455
1650
  try {
1456
- const lines = fs8.readFileSync(LOG_PATH, "utf8").split("\n");
1651
+ const lines = fs9.readFileSync(LOG_PATH, "utf8").split("\n");
1457
1652
  logs2 = lines.slice(-200).join("\n");
1458
1653
  } catch {
1459
1654
  }
@@ -1582,6 +1777,38 @@ var init_server = __esm({
1582
1777
  return fail("nonzero", e.message);
1583
1778
  }
1584
1779
  });
1780
+ add("GET", "/sakura/fs/list", ({ url }) => {
1781
+ try {
1782
+ return ok(list2(url.searchParams.get("path") ?? void 0));
1783
+ } catch (e) {
1784
+ return fail("nonzero", e.message);
1785
+ }
1786
+ });
1787
+ add("GET", "/sakura/fs/read", ({ url }) => {
1788
+ const p = url.searchParams.get("path");
1789
+ if (!p) return fail("nonzero", "path required");
1790
+ try {
1791
+ return ok(read(p));
1792
+ } catch (e) {
1793
+ return fail("nonzero", e.message);
1794
+ }
1795
+ });
1796
+ add("POST", "/sakura/fs/write", ({ body }) => {
1797
+ if (!body?.path) return fail("nonzero", "path required");
1798
+ try {
1799
+ return ok(write2(body.path, String(body.content ?? "")));
1800
+ } catch (e) {
1801
+ return fail("nonzero", e.message);
1802
+ }
1803
+ });
1804
+ add("POST", "/sakura/fs/mkdir", ({ body }) => {
1805
+ if (!body?.path) return fail("nonzero", "path required");
1806
+ try {
1807
+ return ok(mkdir(body.path));
1808
+ } catch (e) {
1809
+ return fail("nonzero", e.message);
1810
+ }
1811
+ });
1585
1812
  add("GET", "/tailscale/health", () => installed());
1586
1813
  add("GET", "/tailscale/status", ({ port: port2 }) => status(port2));
1587
1814
  add("POST", "/tailscale/up", () => up());
@@ -1593,11 +1820,11 @@ var init_server = __esm({
1593
1820
  });
1594
1821
 
1595
1822
  // src/daemon/manager.ts
1596
- import fs9 from "fs";
1597
- import { spawn as spawn6 } from "child_process";
1823
+ import fs10 from "fs";
1824
+ import { spawn as spawn7 } from "child_process";
1598
1825
  function readDaemonInfo() {
1599
1826
  try {
1600
- return JSON.parse(fs9.readFileSync(DAEMON_PATH, "utf8"));
1827
+ return JSON.parse(fs10.readFileSync(DAEMON_PATH, "utf8"));
1601
1828
  } catch {
1602
1829
  return null;
1603
1830
  }
@@ -1614,8 +1841,8 @@ function running() {
1614
1841
  const info2 = readDaemonInfo();
1615
1842
  if (info2 && pidAlive(info2.pid)) return info2;
1616
1843
  if (info2) {
1617
- fs9.rmSync(DAEMON_PATH, { force: true });
1618
- fs9.rmSync(PID_PATH, { force: true });
1844
+ fs10.rmSync(DAEMON_PATH, { force: true });
1845
+ fs10.rmSync(PID_PATH, { force: true });
1619
1846
  }
1620
1847
  return null;
1621
1848
  }
@@ -1638,8 +1865,8 @@ async function runServer(version) {
1638
1865
  url: `http://${host}:${port2}`,
1639
1866
  startedAt: Date.now()
1640
1867
  };
1641
- fs9.writeFileSync(DAEMON_PATH, JSON.stringify(info2, null, 2));
1642
- fs9.writeFileSync(PID_PATH, String(process.pid));
1868
+ fs10.writeFileSync(DAEMON_PATH, JSON.stringify(info2, null, 2));
1869
+ fs10.writeFileSync(PID_PATH, String(process.pid));
1643
1870
  const tailnet = await discoverBaseUrl(port2).catch(() => null);
1644
1871
  log.info(`sakuraai runtime listening on http://0.0.0.0:${port2}`);
1645
1872
  if (tailnet) log.info(`reachable over Tailscale at ${tailnet}`);
@@ -1647,8 +1874,8 @@ async function runServer(version) {
1647
1874
  const shutdown = () => {
1648
1875
  log.info("shutting down");
1649
1876
  server.close();
1650
- fs9.rmSync(DAEMON_PATH, { force: true });
1651
- fs9.rmSync(PID_PATH, { force: true });
1877
+ fs10.rmSync(DAEMON_PATH, { force: true });
1878
+ fs10.rmSync(PID_PATH, { force: true });
1652
1879
  process.exit(0);
1653
1880
  };
1654
1881
  process.on("SIGINT", shutdown);
@@ -1659,9 +1886,9 @@ function start() {
1659
1886
  if (existing) return existing;
1660
1887
  ensureSakuraDirs();
1661
1888
  const entry = process.argv[1] ?? "";
1662
- const out = fs9.openSync(LOG_PATH, "a");
1663
- const err = fs9.openSync(LOG_PATH, "a");
1664
- const child = spawn6(process.execPath, [entry, "__run-daemon"], {
1889
+ const out = fs10.openSync(LOG_PATH, "a");
1890
+ const err = fs10.openSync(LOG_PATH, "a");
1891
+ const child = spawn7(process.execPath, [entry, "__run-daemon"], {
1665
1892
  detached: true,
1666
1893
  stdio: ["ignore", out, err],
1667
1894
  env: process.env
@@ -1682,8 +1909,8 @@ function stop() {
1682
1909
  process.kill(info2.pid, "SIGTERM");
1683
1910
  } catch {
1684
1911
  }
1685
- fs9.rmSync(DAEMON_PATH, { force: true });
1686
- fs9.rmSync(PID_PATH, { force: true });
1912
+ fs10.rmSync(DAEMON_PATH, { force: true });
1913
+ fs10.rmSync(PID_PATH, { force: true });
1687
1914
  return true;
1688
1915
  }
1689
1916
  function restart() {
@@ -1693,7 +1920,7 @@ function restart() {
1693
1920
  }
1694
1921
  function logs(lines = 50) {
1695
1922
  try {
1696
- const all = fs9.readFileSync(LOG_PATH, "utf8").split("\n");
1923
+ const all = fs10.readFileSync(LOG_PATH, "utf8").split("\n");
1697
1924
  return all.slice(-lines).join("\n");
1698
1925
  } catch {
1699
1926
  return "";
@@ -1710,7 +1937,7 @@ var init_manager = __esm({
1710
1937
  });
1711
1938
 
1712
1939
  // src/pairing.ts
1713
- import os5 from "os";
1940
+ import os7 from "os";
1714
1941
  import qrcode from "qrcode-terminal";
1715
1942
  async function buildPairing() {
1716
1943
  const token = requireToken();
@@ -1727,7 +1954,7 @@ function renderQr(deepLink) {
1727
1954
  });
1728
1955
  }
1729
1956
  function localIp() {
1730
- const nets = os5.networkInterfaces();
1957
+ const nets = os7.networkInterfaces();
1731
1958
  for (const name of Object.keys(nets)) {
1732
1959
  for (const net of nets[name] ?? []) {
1733
1960
  if (net.family === "IPv4" && !net.internal) return net.address;
@@ -1790,7 +2017,7 @@ var init_pair = __esm({
1790
2017
  import { Command } from "commander";
1791
2018
 
1792
2019
  // src/version.ts
1793
- var VERSION = "0.0.4";
2020
+ var VERSION = "0.0.6";
1794
2021
 
1795
2022
  // src/index.ts
1796
2023
  init_config();
@@ -1881,7 +2108,7 @@ function registerRuntime(program, version) {
1881
2108
  // src/commands/session.ts
1882
2109
  init_sessions();
1883
2110
  init_output();
1884
- import fs10 from "fs";
2111
+ import fs11 from "fs";
1885
2112
  function resolveSessionId(arg) {
1886
2113
  const id = arg || process.env.SAKURA_SESSION_ID;
1887
2114
  if (!id) die("Provide a sessionId (positional or via SAKURA_SESSION_ID).");
@@ -1890,7 +2117,7 @@ function resolveSessionId(arg) {
1890
2117
  async function resolvePrompt(positional, opts) {
1891
2118
  if (positional) return positional;
1892
2119
  if (opts.prompt) return opts.prompt;
1893
- if (opts.promptFile) return fs10.readFileSync(opts.promptFile, "utf8");
2120
+ if (opts.promptFile) return fs11.readFileSync(opts.promptFile, "utf8");
1894
2121
  if (!process.stdin.isTTY) {
1895
2122
  const chunks = [];
1896
2123
  for await (const c of process.stdin) chunks.push(c);
@@ -1906,15 +2133,15 @@ function registerSession(program) {
1906
2133
  if (opts.json) return printJson([]);
1907
2134
  return info("No archived sessions (archival is not tracked for local sessions).");
1908
2135
  }
1909
- const list2 = await list({
2136
+ const list3 = await list({
1910
2137
  agent: opts.agent,
1911
2138
  limit: opts.limit ? Number(opts.limit) : void 0
1912
2139
  });
1913
- if (opts.json) return printJson(list2);
1914
- if (!list2.length) return info("No sessions found.");
2140
+ if (opts.json) return printJson(list3);
2141
+ if (!list3.length) return info("No sessions found.");
1915
2142
  printTable(
1916
2143
  ["ID", "Agent", "Status", "Updated", "Title"],
1917
- list2.map((x) => [x.id.slice(0, 12), x.agent, x.status, x.updatedAt, x.title])
2144
+ list3.map((x) => [x.id.slice(0, 12), x.agent, x.status, x.updatedAt, x.title])
1918
2145
  );
1919
2146
  });
1920
2147
  s.command("show [sessionId]").description("Show session metadata").option("--json", "output as JSON").action(async (sessionId, opts) => {
@@ -2006,9 +2233,9 @@ function envToObj(pairs) {
2006
2233
  function registerRegistry(program) {
2007
2234
  const ws = program.command("workspace").description("Discover accessible workspaces");
2008
2235
  ws.command("list").description("List workspaces you can access").option("--json", "output as JSON").action((opts) => {
2009
- const list2 = listWorkspaces();
2010
- if (opts.json) return printJson(list2);
2011
- printTable(["ID", "Name", "Slug"], list2.map((w) => [w.id, w.name, w.slug]));
2236
+ const list3 = listWorkspaces();
2237
+ if (opts.json) return printJson(list3);
2238
+ printTable(["ID", "Name", "Slug"], list3.map((w) => [w.id, w.name, w.slug]));
2012
2239
  });
2013
2240
  const gh = program.command("github").description("GitHub repositories linked to a workspace");
2014
2241
  gh.command("list").description("List GitHub repositories linked to a workspace").option("--workspace <idOrSlug>", "workspace").option("--json", "output as JSON").action((opts) => {
@@ -2027,31 +2254,31 @@ function registerRegistry(program) {
2027
2254
  }
2028
2255
  });
2029
2256
  proj.command("list").description("List registered local projects").option("--json", "output as JSON").action((opts) => {
2030
- const list2 = listProjects();
2031
- if (opts.json) return printJson(list2);
2032
- if (!list2.length) return info("No projects registered. Add one with `sakuraai project add`.");
2033
- printTable(["ID", "Name", "Path"], list2.map((p) => [p.id.slice(0, 8), p.name, p.path]));
2257
+ const list3 = listProjects();
2258
+ if (opts.json) return printJson(list3);
2259
+ if (!list3.length) return info("No projects registered. Add one with `sakuraai project add`.");
2260
+ printTable(["ID", "Name", "Path"], list3.map((p) => [p.id.slice(0, 8), p.name, p.path]));
2034
2261
  });
2035
2262
  proj.command("delete <idOrNameOrPath>").description("Delete a registered local project").action((sel) => {
2036
2263
  deleteProject(sel) ? success("Project removed.") : warn("No matching project.");
2037
2264
  });
2038
2265
  const m = program.command("machine").description("Inspect machines in a workspace");
2039
2266
  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) => {
2040
- const list2 = listMachines(!!opts.onlineOnly);
2041
- if (opts.json) return printJson(list2);
2267
+ const list3 = listMachines(!!opts.onlineOnly);
2268
+ if (opts.json) return printJson(list3);
2042
2269
  printTable(
2043
2270
  ["ID", "Name", "Online", "CLI"],
2044
- list2.map((x) => [x.id.slice(0, 8), x.name, x.online ? "yes" : "no", x.cli.join(", ") || "\u2014"])
2271
+ list3.map((x) => [x.id.slice(0, 8), x.name, x.online ? "yes" : "no", x.cli.join(", ") || "\u2014"])
2045
2272
  );
2046
2273
  });
2047
2274
  const ac = program.command("agent-config").description("Manage agent configs");
2048
2275
  ac.command("list").description("List configs in a workspace").option("--workspace <idOrSlug>", "workspace").option("--json", "output as JSON").action((opts) => {
2049
- const list2 = listAgentConfigs();
2050
- if (opts.json) return printJson(list2);
2051
- if (!list2.length) return info("No agent configs. Create one with `sakuraai agent-config create`.");
2276
+ const list3 = listAgentConfigs();
2277
+ if (opts.json) return printJson(list3);
2278
+ if (!list3.length) return info("No agent configs. Create one with `sakuraai agent-config create`.");
2052
2279
  printTable(
2053
2280
  ["ID", "Name", "Type", "Description"],
2054
- list2.map((a) => [a.id.slice(0, 8), a.name, a.agentType, a.description ?? ""])
2281
+ list3.map((a) => [a.id.slice(0, 8), a.name, a.agentType, a.description ?? ""])
2055
2282
  );
2056
2283
  });
2057
2284
  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.4",
3
+ "version": "0.0.6",
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",