sakuraai 0.0.4 → 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 +252 -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,75 @@ 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 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
+
1290
1429
  // src/daemon/server.ts
1291
1430
  import http from "http";
1292
- import fs8 from "fs";
1431
+ import fs9 from "fs";
1293
1432
  import { URL } from "url";
1294
1433
  import { WebSocketServer } from "ws";
1295
- function add(method, path6, handler) {
1434
+ function add(method, path7, handler) {
1296
1435
  const keys = [];
1297
1436
  const pattern = new RegExp(
1298
- "^" + path6.replace(/:[^/]+/g, (m) => {
1437
+ "^" + path7.replace(/:[^/]+/g, (m) => {
1299
1438
  keys.push(m.slice(1));
1300
1439
  return "([^/]+)";
1301
1440
  }) + "/?$"
@@ -1373,15 +1512,28 @@ function createServer(port2) {
1373
1512
  send4(res, 500, fail("nonzero", e.message));
1374
1513
  }
1375
1514
  });
1376
- const wss = new WebSocketServer({ server, path: "/ws" });
1377
- wss.on("connection", (ws, req) => {
1515
+ const wss = new WebSocketServer({ noServer: true });
1516
+ const pty = new WebSocketServer({ noServer: true });
1517
+ const authedToken = (req) => {
1518
+ const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1519
+ return u.searchParams.get("token");
1520
+ };
1521
+ server.on("upgrade", (req, socket, head) => {
1378
1522
  const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
1379
- const token = u.searchParams.get("token");
1523
+ const target = u.pathname === "/ws" ? wss : u.pathname === "/pty" ? pty : null;
1524
+ if (!target) {
1525
+ socket.destroy();
1526
+ return;
1527
+ }
1380
1528
  const expected = getToken();
1381
- if (!expected || token !== expected) {
1382
- 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();
1383
1532
  return;
1384
1533
  }
1534
+ target.handleUpgrade(req, socket, head, (ws) => target.emit("connection", ws, req));
1535
+ });
1536
+ wss.on("connection", (ws) => {
1385
1537
  ws.on("message", (raw) => {
1386
1538
  try {
1387
1539
  const msg = JSON.parse(raw.toString());
@@ -1397,6 +1549,21 @@ function createServer(port2) {
1397
1549
  ws.on("close", () => hub.remove(ws));
1398
1550
  ws.on("error", () => hub.remove(ws));
1399
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
+ });
1400
1567
  return server;
1401
1568
  }
1402
1569
  function setVersion(v) {
@@ -1411,14 +1578,16 @@ var init_server = __esm({
1411
1578
  init_logger();
1412
1579
  init_sessions();
1413
1580
  init_registry();
1581
+ init_fsops();
1414
1582
  init_tailscale();
1415
1583
  init_registry();
1416
1584
  init_hub();
1417
1585
  init_stream();
1586
+ init_terminal();
1418
1587
  routes = [];
1419
1588
  add("GET", "/sessions", async () => {
1420
- const list2 = await list();
1421
- return { sessions: list2 };
1589
+ const list3 = await list();
1590
+ return { sessions: list3 };
1422
1591
  });
1423
1592
  add("GET", "/sessions/:id/messages", async ({ params, url }) => {
1424
1593
  const agent = url.searchParams.get("agent") || void 0;
@@ -1453,7 +1622,7 @@ var init_server = __esm({
1453
1622
  case "logs": {
1454
1623
  let logs2 = "";
1455
1624
  try {
1456
- const lines = fs8.readFileSync(LOG_PATH, "utf8").split("\n");
1625
+ const lines = fs9.readFileSync(LOG_PATH, "utf8").split("\n");
1457
1626
  logs2 = lines.slice(-200).join("\n");
1458
1627
  } catch {
1459
1628
  }
@@ -1582,6 +1751,38 @@ var init_server = __esm({
1582
1751
  return fail("nonzero", e.message);
1583
1752
  }
1584
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
+ });
1585
1786
  add("GET", "/tailscale/health", () => installed());
1586
1787
  add("GET", "/tailscale/status", ({ port: port2 }) => status(port2));
1587
1788
  add("POST", "/tailscale/up", () => up());
@@ -1593,11 +1794,11 @@ var init_server = __esm({
1593
1794
  });
1594
1795
 
1595
1796
  // src/daemon/manager.ts
1596
- import fs9 from "fs";
1597
- import { spawn as spawn6 } from "child_process";
1797
+ import fs10 from "fs";
1798
+ import { spawn as spawn7 } from "child_process";
1598
1799
  function readDaemonInfo() {
1599
1800
  try {
1600
- return JSON.parse(fs9.readFileSync(DAEMON_PATH, "utf8"));
1801
+ return JSON.parse(fs10.readFileSync(DAEMON_PATH, "utf8"));
1601
1802
  } catch {
1602
1803
  return null;
1603
1804
  }
@@ -1614,8 +1815,8 @@ function running() {
1614
1815
  const info2 = readDaemonInfo();
1615
1816
  if (info2 && pidAlive(info2.pid)) return info2;
1616
1817
  if (info2) {
1617
- fs9.rmSync(DAEMON_PATH, { force: true });
1618
- fs9.rmSync(PID_PATH, { force: true });
1818
+ fs10.rmSync(DAEMON_PATH, { force: true });
1819
+ fs10.rmSync(PID_PATH, { force: true });
1619
1820
  }
1620
1821
  return null;
1621
1822
  }
@@ -1638,8 +1839,8 @@ async function runServer(version) {
1638
1839
  url: `http://${host}:${port2}`,
1639
1840
  startedAt: Date.now()
1640
1841
  };
1641
- fs9.writeFileSync(DAEMON_PATH, JSON.stringify(info2, null, 2));
1642
- 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));
1643
1844
  const tailnet = await discoverBaseUrl(port2).catch(() => null);
1644
1845
  log.info(`sakuraai runtime listening on http://0.0.0.0:${port2}`);
1645
1846
  if (tailnet) log.info(`reachable over Tailscale at ${tailnet}`);
@@ -1647,8 +1848,8 @@ async function runServer(version) {
1647
1848
  const shutdown = () => {
1648
1849
  log.info("shutting down");
1649
1850
  server.close();
1650
- fs9.rmSync(DAEMON_PATH, { force: true });
1651
- fs9.rmSync(PID_PATH, { force: true });
1851
+ fs10.rmSync(DAEMON_PATH, { force: true });
1852
+ fs10.rmSync(PID_PATH, { force: true });
1652
1853
  process.exit(0);
1653
1854
  };
1654
1855
  process.on("SIGINT", shutdown);
@@ -1659,9 +1860,9 @@ function start() {
1659
1860
  if (existing) return existing;
1660
1861
  ensureSakuraDirs();
1661
1862
  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"], {
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"], {
1665
1866
  detached: true,
1666
1867
  stdio: ["ignore", out, err],
1667
1868
  env: process.env
@@ -1682,8 +1883,8 @@ function stop() {
1682
1883
  process.kill(info2.pid, "SIGTERM");
1683
1884
  } catch {
1684
1885
  }
1685
- fs9.rmSync(DAEMON_PATH, { force: true });
1686
- fs9.rmSync(PID_PATH, { force: true });
1886
+ fs10.rmSync(DAEMON_PATH, { force: true });
1887
+ fs10.rmSync(PID_PATH, { force: true });
1687
1888
  return true;
1688
1889
  }
1689
1890
  function restart() {
@@ -1693,7 +1894,7 @@ function restart() {
1693
1894
  }
1694
1895
  function logs(lines = 50) {
1695
1896
  try {
1696
- const all = fs9.readFileSync(LOG_PATH, "utf8").split("\n");
1897
+ const all = fs10.readFileSync(LOG_PATH, "utf8").split("\n");
1697
1898
  return all.slice(-lines).join("\n");
1698
1899
  } catch {
1699
1900
  return "";
@@ -1710,7 +1911,7 @@ var init_manager = __esm({
1710
1911
  });
1711
1912
 
1712
1913
  // src/pairing.ts
1713
- import os5 from "os";
1914
+ import os7 from "os";
1714
1915
  import qrcode from "qrcode-terminal";
1715
1916
  async function buildPairing() {
1716
1917
  const token = requireToken();
@@ -1727,7 +1928,7 @@ function renderQr(deepLink) {
1727
1928
  });
1728
1929
  }
1729
1930
  function localIp() {
1730
- const nets = os5.networkInterfaces();
1931
+ const nets = os7.networkInterfaces();
1731
1932
  for (const name of Object.keys(nets)) {
1732
1933
  for (const net of nets[name] ?? []) {
1733
1934
  if (net.family === "IPv4" && !net.internal) return net.address;
@@ -1790,7 +1991,7 @@ var init_pair = __esm({
1790
1991
  import { Command } from "commander";
1791
1992
 
1792
1993
  // src/version.ts
1793
- var VERSION = "0.0.4";
1994
+ var VERSION = "0.0.5";
1794
1995
 
1795
1996
  // src/index.ts
1796
1997
  init_config();
@@ -1881,7 +2082,7 @@ function registerRuntime(program, version) {
1881
2082
  // src/commands/session.ts
1882
2083
  init_sessions();
1883
2084
  init_output();
1884
- import fs10 from "fs";
2085
+ import fs11 from "fs";
1885
2086
  function resolveSessionId(arg) {
1886
2087
  const id = arg || process.env.SAKURA_SESSION_ID;
1887
2088
  if (!id) die("Provide a sessionId (positional or via SAKURA_SESSION_ID).");
@@ -1890,7 +2091,7 @@ function resolveSessionId(arg) {
1890
2091
  async function resolvePrompt(positional, opts) {
1891
2092
  if (positional) return positional;
1892
2093
  if (opts.prompt) return opts.prompt;
1893
- if (opts.promptFile) return fs10.readFileSync(opts.promptFile, "utf8");
2094
+ if (opts.promptFile) return fs11.readFileSync(opts.promptFile, "utf8");
1894
2095
  if (!process.stdin.isTTY) {
1895
2096
  const chunks = [];
1896
2097
  for await (const c of process.stdin) chunks.push(c);
@@ -1906,15 +2107,15 @@ function registerSession(program) {
1906
2107
  if (opts.json) return printJson([]);
1907
2108
  return info("No archived sessions (archival is not tracked for local sessions).");
1908
2109
  }
1909
- const list2 = await list({
2110
+ const list3 = await list({
1910
2111
  agent: opts.agent,
1911
2112
  limit: opts.limit ? Number(opts.limit) : void 0
1912
2113
  });
1913
- if (opts.json) return printJson(list2);
1914
- if (!list2.length) return info("No sessions found.");
2114
+ if (opts.json) return printJson(list3);
2115
+ if (!list3.length) return info("No sessions found.");
1915
2116
  printTable(
1916
2117
  ["ID", "Agent", "Status", "Updated", "Title"],
1917
- 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])
1918
2119
  );
1919
2120
  });
1920
2121
  s.command("show [sessionId]").description("Show session metadata").option("--json", "output as JSON").action(async (sessionId, opts) => {
@@ -2006,9 +2207,9 @@ function envToObj(pairs) {
2006
2207
  function registerRegistry(program) {
2007
2208
  const ws = program.command("workspace").description("Discover accessible workspaces");
2008
2209
  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]));
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]));
2012
2213
  });
2013
2214
  const gh = program.command("github").description("GitHub repositories linked to a workspace");
2014
2215
  gh.command("list").description("List GitHub repositories linked to a workspace").option("--workspace <idOrSlug>", "workspace").option("--json", "output as JSON").action((opts) => {
@@ -2027,31 +2228,31 @@ function registerRegistry(program) {
2027
2228
  }
2028
2229
  });
2029
2230
  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]));
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]));
2034
2235
  });
2035
2236
  proj.command("delete <idOrNameOrPath>").description("Delete a registered local project").action((sel) => {
2036
2237
  deleteProject(sel) ? success("Project removed.") : warn("No matching project.");
2037
2238
  });
2038
2239
  const m = program.command("machine").description("Inspect machines in a workspace");
2039
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) => {
2040
- const list2 = listMachines(!!opts.onlineOnly);
2041
- if (opts.json) return printJson(list2);
2241
+ const list3 = listMachines(!!opts.onlineOnly);
2242
+ if (opts.json) return printJson(list3);
2042
2243
  printTable(
2043
2244
  ["ID", "Name", "Online", "CLI"],
2044
- 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"])
2045
2246
  );
2046
2247
  });
2047
2248
  const ac = program.command("agent-config").description("Manage agent configs");
2048
2249
  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`.");
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`.");
2052
2253
  printTable(
2053
2254
  ["ID", "Name", "Type", "Description"],
2054
- 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 ?? ""])
2055
2256
  );
2056
2257
  });
2057
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.4",
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",