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.
- package/dist/index.js +278 -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,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
|
|
1457
|
+
import fs9 from "fs";
|
|
1293
1458
|
import { URL } from "url";
|
|
1294
1459
|
import { WebSocketServer } from "ws";
|
|
1295
|
-
function add(method,
|
|
1460
|
+
function add(method, path7, handler) {
|
|
1296
1461
|
const keys = [];
|
|
1297
1462
|
const pattern = new RegExp(
|
|
1298
|
-
"^" +
|
|
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({
|
|
1377
|
-
|
|
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
|
-
|
|
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 ||
|
|
1382
|
-
|
|
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
|
|
1421
|
-
return { sessions:
|
|
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 =
|
|
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
|
|
1597
|
-
import { spawn as
|
|
1823
|
+
import fs10 from "fs";
|
|
1824
|
+
import { spawn as spawn7 } from "child_process";
|
|
1598
1825
|
function readDaemonInfo() {
|
|
1599
1826
|
try {
|
|
1600
|
-
return JSON.parse(
|
|
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
|
-
|
|
1618
|
-
|
|
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
|
-
|
|
1642
|
-
|
|
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
|
-
|
|
1651
|
-
|
|
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 =
|
|
1663
|
-
const err =
|
|
1664
|
-
const child =
|
|
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
|
-
|
|
1686
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
1914
|
-
if (!
|
|
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
|
-
|
|
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
|
|
2010
|
-
if (opts.json) return printJson(
|
|
2011
|
-
printTable(["ID", "Name", "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
|
|
2031
|
-
if (opts.json) return printJson(
|
|
2032
|
-
if (!
|
|
2033
|
-
printTable(["ID", "Name", "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
|
|
2041
|
-
if (opts.json) return printJson(
|
|
2267
|
+
const list3 = listMachines(!!opts.onlineOnly);
|
|
2268
|
+
if (opts.json) return printJson(list3);
|
|
2042
2269
|
printTable(
|
|
2043
2270
|
["ID", "Name", "Online", "CLI"],
|
|
2044
|
-
|
|
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
|
|
2050
|
-
if (opts.json) return printJson(
|
|
2051
|
-
if (!
|
|
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
|
-
|
|
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.
|
|
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",
|