sakuraai 0.0.1 → 0.0.3
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 +168 -31
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
4
|
var __esm = (fn, res) => function __init() {
|
|
11
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
6
|
};
|
|
@@ -425,15 +419,28 @@ function messages(sessionId) {
|
|
|
425
419
|
const file = findFile(sessionId);
|
|
426
420
|
return file ? readMessages(file) : [];
|
|
427
421
|
}
|
|
428
|
-
function
|
|
422
|
+
function sessionCwd(sessionId) {
|
|
423
|
+
const file = findFile(sessionId);
|
|
424
|
+
if (!file) return void 0;
|
|
425
|
+
for (const entry of jsonlEntries(file)) {
|
|
426
|
+
if (typeof entry?.cwd === "string" && entry.cwd) return entry.cwd;
|
|
427
|
+
}
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
function send(sessionId, message, onData) {
|
|
429
431
|
return new Promise((resolve) => {
|
|
430
432
|
const bin = process.env.CLAUDE_BIN || "claude";
|
|
433
|
+
const cwd = sessionCwd(sessionId);
|
|
431
434
|
const child = spawn(bin, ["--resume", sessionId, "-p", message], {
|
|
435
|
+
cwd: cwd && fs3.existsSync(cwd) ? cwd : void 0,
|
|
432
436
|
stdio: ["ignore", "pipe", "pipe"]
|
|
433
437
|
});
|
|
434
438
|
let out = "";
|
|
435
439
|
let err = "";
|
|
436
|
-
child.stdout.on("data", (d) =>
|
|
440
|
+
child.stdout.on("data", (d) => {
|
|
441
|
+
out += d.toString();
|
|
442
|
+
onData?.(d.toString());
|
|
443
|
+
});
|
|
437
444
|
child.stderr.on("data", (d) => err += d.toString());
|
|
438
445
|
child.on(
|
|
439
446
|
"error",
|
|
@@ -560,7 +567,7 @@ function messages2(sessionId) {
|
|
|
560
567
|
const file = findFile2(sessionId);
|
|
561
568
|
return file ? readMessages2(file) : [];
|
|
562
569
|
}
|
|
563
|
-
function send2(sessionId, message) {
|
|
570
|
+
function send2(sessionId, message, onData) {
|
|
564
571
|
return new Promise((resolve) => {
|
|
565
572
|
const child = spawn2(codexBin(), ["exec", "resume", sessionId, message], {
|
|
566
573
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -568,7 +575,10 @@ function send2(sessionId, message) {
|
|
|
568
575
|
});
|
|
569
576
|
let out = "";
|
|
570
577
|
let err = "";
|
|
571
|
-
child.stdout.on("data", (d) =>
|
|
578
|
+
child.stdout.on("data", (d) => {
|
|
579
|
+
out += d.toString();
|
|
580
|
+
onData?.(d.toString());
|
|
581
|
+
});
|
|
572
582
|
child.stderr.on("data", (d) => err += d.toString());
|
|
573
583
|
child.on("error", (e) => resolve({ ok: false, output: "", error: e.message }));
|
|
574
584
|
child.on(
|
|
@@ -701,7 +711,7 @@ async function messages3(sessionId) {
|
|
|
701
711
|
db.close();
|
|
702
712
|
}
|
|
703
713
|
}
|
|
704
|
-
function send3(sessionId, message) {
|
|
714
|
+
function send3(sessionId, message, onData) {
|
|
705
715
|
return new Promise((resolve) => {
|
|
706
716
|
const child = spawn3(opencodeBin(), ["run", "-s", sessionId, message], {
|
|
707
717
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -709,7 +719,10 @@ function send3(sessionId, message) {
|
|
|
709
719
|
});
|
|
710
720
|
let out = "";
|
|
711
721
|
let err = "";
|
|
712
|
-
child.stdout.on("data", (d) =>
|
|
722
|
+
child.stdout.on("data", (d) => {
|
|
723
|
+
out += d.toString();
|
|
724
|
+
onData?.(d.toString());
|
|
725
|
+
});
|
|
713
726
|
child.stderr.on("data", (d) => err += d.toString());
|
|
714
727
|
child.on("error", (e) => resolve({ ok: false, output: "", error: e.message }));
|
|
715
728
|
child.on(
|
|
@@ -776,11 +789,11 @@ async function history(sessionId, opts = {}, agent) {
|
|
|
776
789
|
if (opts.reverse) msgs = [...msgs].reverse();
|
|
777
790
|
return msgs;
|
|
778
791
|
}
|
|
779
|
-
async function chat(sessionId, prompt, agent) {
|
|
792
|
+
async function chat(sessionId, prompt, agent, onData) {
|
|
780
793
|
const a = agent ?? await detectAgent(sessionId);
|
|
781
|
-
if (a === "claude") return send(sessionId, prompt);
|
|
782
|
-
if (a === "codex") return send2(sessionId, prompt);
|
|
783
|
-
if (a === "opencode") return send3(sessionId, prompt);
|
|
794
|
+
if (a === "claude") return send(sessionId, prompt, onData);
|
|
795
|
+
if (a === "codex") return send2(sessionId, prompt, onData);
|
|
796
|
+
if (a === "opencode") return send3(sessionId, prompt, onData);
|
|
784
797
|
return {
|
|
785
798
|
ok: false,
|
|
786
799
|
output: "",
|
|
@@ -1134,10 +1147,107 @@ var init_tailscale = __esm({
|
|
|
1134
1147
|
}
|
|
1135
1148
|
});
|
|
1136
1149
|
|
|
1150
|
+
// src/daemon/hub.ts
|
|
1151
|
+
function sessionKey(agent, sessionId) {
|
|
1152
|
+
return `${agent}:${sessionId}`;
|
|
1153
|
+
}
|
|
1154
|
+
var Hub, hub;
|
|
1155
|
+
var init_hub = __esm({
|
|
1156
|
+
"src/daemon/hub.ts"() {
|
|
1157
|
+
"use strict";
|
|
1158
|
+
Hub = class {
|
|
1159
|
+
clients = /* @__PURE__ */ new Map();
|
|
1160
|
+
subscribe(key, ws) {
|
|
1161
|
+
let set = this.clients.get(key);
|
|
1162
|
+
if (!set) {
|
|
1163
|
+
set = /* @__PURE__ */ new Set();
|
|
1164
|
+
this.clients.set(key, set);
|
|
1165
|
+
}
|
|
1166
|
+
set.add(ws);
|
|
1167
|
+
}
|
|
1168
|
+
remove(ws) {
|
|
1169
|
+
for (const set of this.clients.values()) set.delete(ws);
|
|
1170
|
+
}
|
|
1171
|
+
broadcast(key, event) {
|
|
1172
|
+
const set = this.clients.get(key);
|
|
1173
|
+
if (!set || set.size === 0) return;
|
|
1174
|
+
const payload = JSON.stringify(event);
|
|
1175
|
+
for (const ws of set) {
|
|
1176
|
+
try {
|
|
1177
|
+
ws.send(payload);
|
|
1178
|
+
} catch {
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
hub = new Hub();
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// src/daemon/stream.ts
|
|
1188
|
+
async function runTurn(sessionId, agent, prompt) {
|
|
1189
|
+
const key = sessionKey(agent, sessionId);
|
|
1190
|
+
const emit = (e) => hub.broadcast(key, e);
|
|
1191
|
+
let lastEmitted = "\0";
|
|
1192
|
+
const emitLive = (text) => {
|
|
1193
|
+
if (text === lastEmitted) return;
|
|
1194
|
+
lastEmitted = text;
|
|
1195
|
+
emit({
|
|
1196
|
+
type: "message",
|
|
1197
|
+
sessionId,
|
|
1198
|
+
agent,
|
|
1199
|
+
data: {
|
|
1200
|
+
id: LIVE_ID,
|
|
1201
|
+
role: "assistant",
|
|
1202
|
+
content: text,
|
|
1203
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1204
|
+
streaming: true
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
};
|
|
1208
|
+
emitLive("");
|
|
1209
|
+
const baseIds = /* @__PURE__ */ new Set();
|
|
1210
|
+
try {
|
|
1211
|
+
for (const m of await messages4(sessionId, agent)) baseIds.add(m.id);
|
|
1212
|
+
} catch {
|
|
1213
|
+
}
|
|
1214
|
+
let stdoutAcc = "";
|
|
1215
|
+
const onData = (chunk) => {
|
|
1216
|
+
stdoutAcc += stripAnsi(chunk);
|
|
1217
|
+
const t = stdoutAcc.trim();
|
|
1218
|
+
if (t.length >= lastEmitted.replace("\0", "").length) emitLive(t);
|
|
1219
|
+
};
|
|
1220
|
+
const timer = setInterval(async () => {
|
|
1221
|
+
try {
|
|
1222
|
+
const msgs = await messages4(sessionId, agent);
|
|
1223
|
+
const fresh = msgs.filter((m) => m.role === "assistant" && !baseIds.has(m.id)).map((m) => m.content).join("\n\n").trim();
|
|
1224
|
+
if (fresh && fresh.length >= lastEmitted.length) emitLive(fresh);
|
|
1225
|
+
} catch {
|
|
1226
|
+
}
|
|
1227
|
+
}, POLL_MS);
|
|
1228
|
+
const result = await chat(sessionId, prompt, agent, onData);
|
|
1229
|
+
clearInterval(timer);
|
|
1230
|
+
emit({ type: "done", sessionId, agent, ok: result.ok, error: result.error });
|
|
1231
|
+
return { ok: result.ok, error: result.error };
|
|
1232
|
+
}
|
|
1233
|
+
var ANSI, stripAnsi, LIVE_ID, POLL_MS;
|
|
1234
|
+
var init_stream = __esm({
|
|
1235
|
+
"src/daemon/stream.ts"() {
|
|
1236
|
+
"use strict";
|
|
1237
|
+
init_sessions();
|
|
1238
|
+
init_hub();
|
|
1239
|
+
ANSI = /\[[0-9;]*[a-zA-Z]/g;
|
|
1240
|
+
stripAnsi = (s) => s.replace(ANSI, "");
|
|
1241
|
+
LIVE_ID = "__live__";
|
|
1242
|
+
POLL_MS = 300;
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1137
1246
|
// src/daemon/server.ts
|
|
1138
1247
|
import http from "http";
|
|
1139
1248
|
import fs8 from "fs";
|
|
1140
1249
|
import { URL } from "url";
|
|
1250
|
+
import { WebSocketServer } from "ws";
|
|
1141
1251
|
function add(method, path6, handler) {
|
|
1142
1252
|
const keys = [];
|
|
1143
1253
|
const pattern = new RegExp(
|
|
@@ -1190,7 +1300,7 @@ function send4(res, status2, payload) {
|
|
|
1190
1300
|
res.end(body);
|
|
1191
1301
|
}
|
|
1192
1302
|
function createServer(port2) {
|
|
1193
|
-
|
|
1303
|
+
const server = http.createServer(async (req, res) => {
|
|
1194
1304
|
const method = req.method ?? "GET";
|
|
1195
1305
|
const url = new URL(req.url ?? "/", `http://localhost:${port2}`);
|
|
1196
1306
|
if (method === "OPTIONS") return send4(res, 204, {});
|
|
@@ -1219,6 +1329,31 @@ function createServer(port2) {
|
|
|
1219
1329
|
send4(res, 500, fail("nonzero", e.message));
|
|
1220
1330
|
}
|
|
1221
1331
|
});
|
|
1332
|
+
const wss = new WebSocketServer({ server, path: "/ws" });
|
|
1333
|
+
wss.on("connection", (ws, req) => {
|
|
1334
|
+
const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
|
|
1335
|
+
const token = u.searchParams.get("token");
|
|
1336
|
+
const expected = getToken();
|
|
1337
|
+
if (!expected || token !== expected) {
|
|
1338
|
+
ws.close(1008, "Unauthorized");
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
ws.on("message", (raw) => {
|
|
1342
|
+
try {
|
|
1343
|
+
const msg = JSON.parse(raw.toString());
|
|
1344
|
+
if (msg?.type === "subscribe" && msg.sessionId && msg.agent) {
|
|
1345
|
+
hub.subscribe(sessionKey(msg.agent, msg.sessionId), ws);
|
|
1346
|
+
ws.send(JSON.stringify({ type: "subscribed", sessionId: msg.sessionId, agent: msg.agent }));
|
|
1347
|
+
} else if (msg?.type === "ping") {
|
|
1348
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
1349
|
+
}
|
|
1350
|
+
} catch {
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
ws.on("close", () => hub.remove(ws));
|
|
1354
|
+
ws.on("error", () => hub.remove(ws));
|
|
1355
|
+
});
|
|
1356
|
+
return server;
|
|
1222
1357
|
}
|
|
1223
1358
|
function setVersion(v) {
|
|
1224
1359
|
VERSION2 = v;
|
|
@@ -1234,6 +1369,8 @@ var init_server = __esm({
|
|
|
1234
1369
|
init_registry();
|
|
1235
1370
|
init_tailscale();
|
|
1236
1371
|
init_registry();
|
|
1372
|
+
init_hub();
|
|
1373
|
+
init_stream();
|
|
1237
1374
|
routes = [];
|
|
1238
1375
|
add("GET", "/sessions", async () => {
|
|
1239
1376
|
const list2 = await list();
|
|
@@ -1245,9 +1382,10 @@ var init_server = __esm({
|
|
|
1245
1382
|
return { messages: messages5 };
|
|
1246
1383
|
});
|
|
1247
1384
|
add("POST", "/sessions/:id/send", async ({ params, body }) => {
|
|
1248
|
-
const agent = body?.agent ||
|
|
1249
|
-
|
|
1250
|
-
|
|
1385
|
+
const agent = body?.agent || await detectAgent(params.id);
|
|
1386
|
+
if (!agent) return { ok: false, error: "unknown agent for session" };
|
|
1387
|
+
const r = await runTurn(params.id, agent, String(body?.message ?? ""));
|
|
1388
|
+
return { ok: r.ok, error: r.error };
|
|
1251
1389
|
});
|
|
1252
1390
|
add(
|
|
1253
1391
|
"GET",
|
|
@@ -1327,8 +1465,10 @@ var init_server = __esm({
|
|
|
1327
1465
|
return ok(await history(params.id, { all, limit, reverse }));
|
|
1328
1466
|
});
|
|
1329
1467
|
add("POST", "/sakura/sessions/:id/chat", async ({ params, body }) => {
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1468
|
+
const agent = body?.agent || await detectAgent(params.id);
|
|
1469
|
+
if (!agent) return fail("nonzero", "unknown agent for session");
|
|
1470
|
+
const r = await runTurn(params.id, agent, String(body?.prompt ?? ""));
|
|
1471
|
+
return r.ok ? ok({ ok: true }) : fail("nonzero", r.error ?? "chat failed");
|
|
1332
1472
|
});
|
|
1333
1473
|
add(
|
|
1334
1474
|
"POST",
|
|
@@ -1526,6 +1666,7 @@ var init_manager = __esm({
|
|
|
1526
1666
|
});
|
|
1527
1667
|
|
|
1528
1668
|
// src/pairing.ts
|
|
1669
|
+
import os5 from "os";
|
|
1529
1670
|
import qrcode from "qrcode-terminal";
|
|
1530
1671
|
async function buildPairing() {
|
|
1531
1672
|
const token = requireToken();
|
|
@@ -1542,15 +1683,11 @@ function renderQr(deepLink) {
|
|
|
1542
1683
|
});
|
|
1543
1684
|
}
|
|
1544
1685
|
function localIp() {
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
const nets
|
|
1548
|
-
|
|
1549
|
-
for (const net of nets[name] ?? []) {
|
|
1550
|
-
if (net.family === "IPv4" && !net.internal) return net.address;
|
|
1551
|
-
}
|
|
1686
|
+
const nets = os5.networkInterfaces();
|
|
1687
|
+
for (const name of Object.keys(nets)) {
|
|
1688
|
+
for (const net of nets[name] ?? []) {
|
|
1689
|
+
if (net.family === "IPv4" && !net.internal) return net.address;
|
|
1552
1690
|
}
|
|
1553
|
-
} catch {
|
|
1554
1691
|
}
|
|
1555
1692
|
return "127.0.0.1";
|
|
1556
1693
|
}
|
|
@@ -1609,7 +1746,7 @@ var init_pair = __esm({
|
|
|
1609
1746
|
import { Command } from "commander";
|
|
1610
1747
|
|
|
1611
1748
|
// src/version.ts
|
|
1612
|
-
var VERSION = "0.0.
|
|
1749
|
+
var VERSION = "0.0.3";
|
|
1613
1750
|
|
|
1614
1751
|
// src/index.ts
|
|
1615
1752
|
init_config();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sakuraai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
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",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"cli-table3": "^0.6.5",
|
|
43
43
|
"commander": "^12.1.0",
|
|
44
44
|
"ora": "^8.1.0",
|
|
45
|
-
"qrcode-terminal": "^0.12.0"
|
|
45
|
+
"qrcode-terminal": "^0.12.0",
|
|
46
|
+
"ws": "^8.21.0"
|
|
46
47
|
},
|
|
47
48
|
"optionalDependencies": {
|
|
48
49
|
"better-sqlite3": "^11.3.0"
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
"@types/better-sqlite3": "^7.6.11",
|
|
52
53
|
"@types/node": "^22.7.0",
|
|
53
54
|
"@types/qrcode-terminal": "^0.12.2",
|
|
55
|
+
"@types/ws": "^8.18.1",
|
|
54
56
|
"rimraf": "^6.0.1",
|
|
55
57
|
"tsup": "^8.3.0",
|
|
56
58
|
"tsx": "^4.19.0",
|