sakuraai 0.0.1 → 0.0.2
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 +158 -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,7 +419,7 @@ function messages(sessionId) {
|
|
|
425
419
|
const file = findFile(sessionId);
|
|
426
420
|
return file ? readMessages(file) : [];
|
|
427
421
|
}
|
|
428
|
-
function send(sessionId, message) {
|
|
422
|
+
function send(sessionId, message, onData) {
|
|
429
423
|
return new Promise((resolve) => {
|
|
430
424
|
const bin = process.env.CLAUDE_BIN || "claude";
|
|
431
425
|
const child = spawn(bin, ["--resume", sessionId, "-p", message], {
|
|
@@ -433,7 +427,10 @@ function send(sessionId, message) {
|
|
|
433
427
|
});
|
|
434
428
|
let out = "";
|
|
435
429
|
let err = "";
|
|
436
|
-
child.stdout.on("data", (d) =>
|
|
430
|
+
child.stdout.on("data", (d) => {
|
|
431
|
+
out += d.toString();
|
|
432
|
+
onData?.(d.toString());
|
|
433
|
+
});
|
|
437
434
|
child.stderr.on("data", (d) => err += d.toString());
|
|
438
435
|
child.on(
|
|
439
436
|
"error",
|
|
@@ -560,7 +557,7 @@ function messages2(sessionId) {
|
|
|
560
557
|
const file = findFile2(sessionId);
|
|
561
558
|
return file ? readMessages2(file) : [];
|
|
562
559
|
}
|
|
563
|
-
function send2(sessionId, message) {
|
|
560
|
+
function send2(sessionId, message, onData) {
|
|
564
561
|
return new Promise((resolve) => {
|
|
565
562
|
const child = spawn2(codexBin(), ["exec", "resume", sessionId, message], {
|
|
566
563
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -568,7 +565,10 @@ function send2(sessionId, message) {
|
|
|
568
565
|
});
|
|
569
566
|
let out = "";
|
|
570
567
|
let err = "";
|
|
571
|
-
child.stdout.on("data", (d) =>
|
|
568
|
+
child.stdout.on("data", (d) => {
|
|
569
|
+
out += d.toString();
|
|
570
|
+
onData?.(d.toString());
|
|
571
|
+
});
|
|
572
572
|
child.stderr.on("data", (d) => err += d.toString());
|
|
573
573
|
child.on("error", (e) => resolve({ ok: false, output: "", error: e.message }));
|
|
574
574
|
child.on(
|
|
@@ -701,7 +701,7 @@ async function messages3(sessionId) {
|
|
|
701
701
|
db.close();
|
|
702
702
|
}
|
|
703
703
|
}
|
|
704
|
-
function send3(sessionId, message) {
|
|
704
|
+
function send3(sessionId, message, onData) {
|
|
705
705
|
return new Promise((resolve) => {
|
|
706
706
|
const child = spawn3(opencodeBin(), ["run", "-s", sessionId, message], {
|
|
707
707
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -709,7 +709,10 @@ function send3(sessionId, message) {
|
|
|
709
709
|
});
|
|
710
710
|
let out = "";
|
|
711
711
|
let err = "";
|
|
712
|
-
child.stdout.on("data", (d) =>
|
|
712
|
+
child.stdout.on("data", (d) => {
|
|
713
|
+
out += d.toString();
|
|
714
|
+
onData?.(d.toString());
|
|
715
|
+
});
|
|
713
716
|
child.stderr.on("data", (d) => err += d.toString());
|
|
714
717
|
child.on("error", (e) => resolve({ ok: false, output: "", error: e.message }));
|
|
715
718
|
child.on(
|
|
@@ -776,11 +779,11 @@ async function history(sessionId, opts = {}, agent) {
|
|
|
776
779
|
if (opts.reverse) msgs = [...msgs].reverse();
|
|
777
780
|
return msgs;
|
|
778
781
|
}
|
|
779
|
-
async function chat(sessionId, prompt, agent) {
|
|
782
|
+
async function chat(sessionId, prompt, agent, onData) {
|
|
780
783
|
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);
|
|
784
|
+
if (a === "claude") return send(sessionId, prompt, onData);
|
|
785
|
+
if (a === "codex") return send2(sessionId, prompt, onData);
|
|
786
|
+
if (a === "opencode") return send3(sessionId, prompt, onData);
|
|
784
787
|
return {
|
|
785
788
|
ok: false,
|
|
786
789
|
output: "",
|
|
@@ -1134,10 +1137,107 @@ var init_tailscale = __esm({
|
|
|
1134
1137
|
}
|
|
1135
1138
|
});
|
|
1136
1139
|
|
|
1140
|
+
// src/daemon/hub.ts
|
|
1141
|
+
function sessionKey(agent, sessionId) {
|
|
1142
|
+
return `${agent}:${sessionId}`;
|
|
1143
|
+
}
|
|
1144
|
+
var Hub, hub;
|
|
1145
|
+
var init_hub = __esm({
|
|
1146
|
+
"src/daemon/hub.ts"() {
|
|
1147
|
+
"use strict";
|
|
1148
|
+
Hub = class {
|
|
1149
|
+
clients = /* @__PURE__ */ new Map();
|
|
1150
|
+
subscribe(key, ws) {
|
|
1151
|
+
let set = this.clients.get(key);
|
|
1152
|
+
if (!set) {
|
|
1153
|
+
set = /* @__PURE__ */ new Set();
|
|
1154
|
+
this.clients.set(key, set);
|
|
1155
|
+
}
|
|
1156
|
+
set.add(ws);
|
|
1157
|
+
}
|
|
1158
|
+
remove(ws) {
|
|
1159
|
+
for (const set of this.clients.values()) set.delete(ws);
|
|
1160
|
+
}
|
|
1161
|
+
broadcast(key, event) {
|
|
1162
|
+
const set = this.clients.get(key);
|
|
1163
|
+
if (!set || set.size === 0) return;
|
|
1164
|
+
const payload = JSON.stringify(event);
|
|
1165
|
+
for (const ws of set) {
|
|
1166
|
+
try {
|
|
1167
|
+
ws.send(payload);
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
hub = new Hub();
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
// src/daemon/stream.ts
|
|
1178
|
+
async function runTurn(sessionId, agent, prompt) {
|
|
1179
|
+
const key = sessionKey(agent, sessionId);
|
|
1180
|
+
const emit = (e) => hub.broadcast(key, e);
|
|
1181
|
+
let lastEmitted = "\0";
|
|
1182
|
+
const emitLive = (text) => {
|
|
1183
|
+
if (text === lastEmitted) return;
|
|
1184
|
+
lastEmitted = text;
|
|
1185
|
+
emit({
|
|
1186
|
+
type: "message",
|
|
1187
|
+
sessionId,
|
|
1188
|
+
agent,
|
|
1189
|
+
data: {
|
|
1190
|
+
id: LIVE_ID,
|
|
1191
|
+
role: "assistant",
|
|
1192
|
+
content: text,
|
|
1193
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1194
|
+
streaming: true
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
};
|
|
1198
|
+
emitLive("");
|
|
1199
|
+
const baseIds = /* @__PURE__ */ new Set();
|
|
1200
|
+
try {
|
|
1201
|
+
for (const m of await messages4(sessionId, agent)) baseIds.add(m.id);
|
|
1202
|
+
} catch {
|
|
1203
|
+
}
|
|
1204
|
+
let stdoutAcc = "";
|
|
1205
|
+
const onData = (chunk) => {
|
|
1206
|
+
stdoutAcc += stripAnsi(chunk);
|
|
1207
|
+
const t = stdoutAcc.trim();
|
|
1208
|
+
if (t.length >= lastEmitted.replace("\0", "").length) emitLive(t);
|
|
1209
|
+
};
|
|
1210
|
+
const timer = setInterval(async () => {
|
|
1211
|
+
try {
|
|
1212
|
+
const msgs = await messages4(sessionId, agent);
|
|
1213
|
+
const fresh = msgs.filter((m) => m.role === "assistant" && !baseIds.has(m.id)).map((m) => m.content).join("\n\n").trim();
|
|
1214
|
+
if (fresh && fresh.length >= lastEmitted.length) emitLive(fresh);
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
}, POLL_MS);
|
|
1218
|
+
const result = await chat(sessionId, prompt, agent, onData);
|
|
1219
|
+
clearInterval(timer);
|
|
1220
|
+
emit({ type: "done", sessionId, agent, ok: result.ok, error: result.error });
|
|
1221
|
+
return { ok: result.ok, error: result.error };
|
|
1222
|
+
}
|
|
1223
|
+
var ANSI, stripAnsi, LIVE_ID, POLL_MS;
|
|
1224
|
+
var init_stream = __esm({
|
|
1225
|
+
"src/daemon/stream.ts"() {
|
|
1226
|
+
"use strict";
|
|
1227
|
+
init_sessions();
|
|
1228
|
+
init_hub();
|
|
1229
|
+
ANSI = /\[[0-9;]*[a-zA-Z]/g;
|
|
1230
|
+
stripAnsi = (s) => s.replace(ANSI, "");
|
|
1231
|
+
LIVE_ID = "__live__";
|
|
1232
|
+
POLL_MS = 300;
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1137
1236
|
// src/daemon/server.ts
|
|
1138
1237
|
import http from "http";
|
|
1139
1238
|
import fs8 from "fs";
|
|
1140
1239
|
import { URL } from "url";
|
|
1240
|
+
import { WebSocketServer } from "ws";
|
|
1141
1241
|
function add(method, path6, handler) {
|
|
1142
1242
|
const keys = [];
|
|
1143
1243
|
const pattern = new RegExp(
|
|
@@ -1190,7 +1290,7 @@ function send4(res, status2, payload) {
|
|
|
1190
1290
|
res.end(body);
|
|
1191
1291
|
}
|
|
1192
1292
|
function createServer(port2) {
|
|
1193
|
-
|
|
1293
|
+
const server = http.createServer(async (req, res) => {
|
|
1194
1294
|
const method = req.method ?? "GET";
|
|
1195
1295
|
const url = new URL(req.url ?? "/", `http://localhost:${port2}`);
|
|
1196
1296
|
if (method === "OPTIONS") return send4(res, 204, {});
|
|
@@ -1219,6 +1319,31 @@ function createServer(port2) {
|
|
|
1219
1319
|
send4(res, 500, fail("nonzero", e.message));
|
|
1220
1320
|
}
|
|
1221
1321
|
});
|
|
1322
|
+
const wss = new WebSocketServer({ server, path: "/ws" });
|
|
1323
|
+
wss.on("connection", (ws, req) => {
|
|
1324
|
+
const u = new URL(req.url ?? "/", `http://localhost:${port2}`);
|
|
1325
|
+
const token = u.searchParams.get("token");
|
|
1326
|
+
const expected = getToken();
|
|
1327
|
+
if (!expected || token !== expected) {
|
|
1328
|
+
ws.close(1008, "Unauthorized");
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
ws.on("message", (raw) => {
|
|
1332
|
+
try {
|
|
1333
|
+
const msg = JSON.parse(raw.toString());
|
|
1334
|
+
if (msg?.type === "subscribe" && msg.sessionId && msg.agent) {
|
|
1335
|
+
hub.subscribe(sessionKey(msg.agent, msg.sessionId), ws);
|
|
1336
|
+
ws.send(JSON.stringify({ type: "subscribed", sessionId: msg.sessionId, agent: msg.agent }));
|
|
1337
|
+
} else if (msg?.type === "ping") {
|
|
1338
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
1339
|
+
}
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
ws.on("close", () => hub.remove(ws));
|
|
1344
|
+
ws.on("error", () => hub.remove(ws));
|
|
1345
|
+
});
|
|
1346
|
+
return server;
|
|
1222
1347
|
}
|
|
1223
1348
|
function setVersion(v) {
|
|
1224
1349
|
VERSION2 = v;
|
|
@@ -1234,6 +1359,8 @@ var init_server = __esm({
|
|
|
1234
1359
|
init_registry();
|
|
1235
1360
|
init_tailscale();
|
|
1236
1361
|
init_registry();
|
|
1362
|
+
init_hub();
|
|
1363
|
+
init_stream();
|
|
1237
1364
|
routes = [];
|
|
1238
1365
|
add("GET", "/sessions", async () => {
|
|
1239
1366
|
const list2 = await list();
|
|
@@ -1245,9 +1372,10 @@ var init_server = __esm({
|
|
|
1245
1372
|
return { messages: messages5 };
|
|
1246
1373
|
});
|
|
1247
1374
|
add("POST", "/sessions/:id/send", async ({ params, body }) => {
|
|
1248
|
-
const agent = body?.agent ||
|
|
1249
|
-
|
|
1250
|
-
|
|
1375
|
+
const agent = body?.agent || await detectAgent(params.id);
|
|
1376
|
+
if (!agent) return { ok: false, error: "unknown agent for session" };
|
|
1377
|
+
const r = await runTurn(params.id, agent, String(body?.message ?? ""));
|
|
1378
|
+
return { ok: r.ok, error: r.error };
|
|
1251
1379
|
});
|
|
1252
1380
|
add(
|
|
1253
1381
|
"GET",
|
|
@@ -1327,8 +1455,10 @@ var init_server = __esm({
|
|
|
1327
1455
|
return ok(await history(params.id, { all, limit, reverse }));
|
|
1328
1456
|
});
|
|
1329
1457
|
add("POST", "/sakura/sessions/:id/chat", async ({ params, body }) => {
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1458
|
+
const agent = body?.agent || await detectAgent(params.id);
|
|
1459
|
+
if (!agent) return fail("nonzero", "unknown agent for session");
|
|
1460
|
+
const r = await runTurn(params.id, agent, String(body?.prompt ?? ""));
|
|
1461
|
+
return r.ok ? ok({ ok: true }) : fail("nonzero", r.error ?? "chat failed");
|
|
1332
1462
|
});
|
|
1333
1463
|
add(
|
|
1334
1464
|
"POST",
|
|
@@ -1526,6 +1656,7 @@ var init_manager = __esm({
|
|
|
1526
1656
|
});
|
|
1527
1657
|
|
|
1528
1658
|
// src/pairing.ts
|
|
1659
|
+
import os5 from "os";
|
|
1529
1660
|
import qrcode from "qrcode-terminal";
|
|
1530
1661
|
async function buildPairing() {
|
|
1531
1662
|
const token = requireToken();
|
|
@@ -1542,15 +1673,11 @@ function renderQr(deepLink) {
|
|
|
1542
1673
|
});
|
|
1543
1674
|
}
|
|
1544
1675
|
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
|
-
}
|
|
1676
|
+
const nets = os5.networkInterfaces();
|
|
1677
|
+
for (const name of Object.keys(nets)) {
|
|
1678
|
+
for (const net of nets[name] ?? []) {
|
|
1679
|
+
if (net.family === "IPv4" && !net.internal) return net.address;
|
|
1552
1680
|
}
|
|
1553
|
-
} catch {
|
|
1554
1681
|
}
|
|
1555
1682
|
return "127.0.0.1";
|
|
1556
1683
|
}
|
|
@@ -1609,7 +1736,7 @@ var init_pair = __esm({
|
|
|
1609
1736
|
import { Command } from "commander";
|
|
1610
1737
|
|
|
1611
1738
|
// src/version.ts
|
|
1612
|
-
var VERSION = "0.0.
|
|
1739
|
+
var VERSION = "0.0.2";
|
|
1613
1740
|
|
|
1614
1741
|
// src/index.ts
|
|
1615
1742
|
init_config();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sakuraai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
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",
|