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.
- package/dist/index.js +252 -51
- 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/
|
|
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 (
|
|
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
|
|
1431
|
+
import fs9 from "fs";
|
|
1293
1432
|
import { URL } from "url";
|
|
1294
1433
|
import { WebSocketServer } from "ws";
|
|
1295
|
-
function add(method,
|
|
1434
|
+
function add(method, path7, handler) {
|
|
1296
1435
|
const keys = [];
|
|
1297
1436
|
const pattern = new RegExp(
|
|
1298
|
-
"^" +
|
|
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({
|
|
1377
|
-
|
|
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
|
|
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 ||
|
|
1382
|
-
|
|
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
|
|
1421
|
-
return { sessions:
|
|
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 =
|
|
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
|
|
1597
|
-
import { spawn as
|
|
1797
|
+
import fs10 from "fs";
|
|
1798
|
+
import { spawn as spawn7 } from "child_process";
|
|
1598
1799
|
function readDaemonInfo() {
|
|
1599
1800
|
try {
|
|
1600
|
-
return JSON.parse(
|
|
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
|
-
|
|
1618
|
-
|
|
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
|
-
|
|
1642
|
-
|
|
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
|
-
|
|
1651
|
-
|
|
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 =
|
|
1663
|
-
const err =
|
|
1664
|
-
const child =
|
|
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
|
-
|
|
1686
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
1914
|
-
if (!
|
|
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
|
-
|
|
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
|
|
2010
|
-
if (opts.json) return printJson(
|
|
2011
|
-
printTable(["ID", "Name", "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
|
|
2031
|
-
if (opts.json) return printJson(
|
|
2032
|
-
if (!
|
|
2033
|
-
printTable(["ID", "Name", "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
|
|
2041
|
-
if (opts.json) return printJson(
|
|
2241
|
+
const list3 = listMachines(!!opts.onlineOnly);
|
|
2242
|
+
if (opts.json) return printJson(list3);
|
|
2042
2243
|
printTable(
|
|
2043
2244
|
["ID", "Name", "Online", "CLI"],
|
|
2044
|
-
|
|
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
|
|
2050
|
-
if (opts.json) return printJson(
|
|
2051
|
-
if (!
|
|
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
|
-
|
|
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.
|
|
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",
|