sakuraai 0.0.6 → 0.0.7
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 +230 -103
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
4
4
|
var __esm = (fn, res) => function __init() {
|
|
5
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
6
|
};
|
|
7
|
-
var __export = (target,
|
|
8
|
-
for (var name in
|
|
9
|
-
__defProp(target, name, { get:
|
|
7
|
+
var __export = (target, all2) => {
|
|
8
|
+
for (var name in all2)
|
|
9
|
+
__defProp(target, name, { get: all2[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
@@ -428,17 +428,24 @@ function sessionCwd(sessionId) {
|
|
|
428
428
|
}
|
|
429
429
|
return void 0;
|
|
430
430
|
}
|
|
431
|
-
function send(sessionId, message, onData) {
|
|
431
|
+
function send(sessionId, message, onData, images) {
|
|
432
432
|
return new Promise((resolve) => {
|
|
433
433
|
const bin = process.env.CLAUDE_BIN || "claude";
|
|
434
434
|
const cwd = sessionCwd(sessionId);
|
|
435
|
+
let prompt = message;
|
|
436
|
+
if (images?.length) {
|
|
437
|
+
const refs = images.map((p) => `[Image: ${p}]`).join("\n");
|
|
438
|
+
prompt = prompt ? `${prompt}
|
|
439
|
+
|
|
440
|
+
${refs}` : refs;
|
|
441
|
+
}
|
|
435
442
|
const child = spawn(
|
|
436
443
|
bin,
|
|
437
444
|
[
|
|
438
445
|
"--resume",
|
|
439
446
|
sessionId,
|
|
440
447
|
"-p",
|
|
441
|
-
|
|
448
|
+
prompt,
|
|
442
449
|
"--output-format",
|
|
443
450
|
"stream-json",
|
|
444
451
|
"--include-partial-messages",
|
|
@@ -609,9 +616,10 @@ function messages2(sessionId) {
|
|
|
609
616
|
const file = findFile2(sessionId);
|
|
610
617
|
return file ? readMessages2(file) : [];
|
|
611
618
|
}
|
|
612
|
-
function send2(sessionId, message, onData) {
|
|
619
|
+
function send2(sessionId, message, onData, images) {
|
|
613
620
|
return new Promise((resolve) => {
|
|
614
|
-
const
|
|
621
|
+
const imageArgs = (images ?? []).flatMap((p) => ["-i", p]);
|
|
622
|
+
const child = spawn2(codexBin(), ["exec", "resume", sessionId, ...imageArgs, message], {
|
|
615
623
|
stdio: ["ignore", "pipe", "pipe"],
|
|
616
624
|
env: process.env
|
|
617
625
|
});
|
|
@@ -755,9 +763,10 @@ async function messages3(sessionId) {
|
|
|
755
763
|
db.close();
|
|
756
764
|
}
|
|
757
765
|
}
|
|
758
|
-
function send3(sessionId, message, onData) {
|
|
766
|
+
function send3(sessionId, message, onData, images) {
|
|
759
767
|
return new Promise((resolve) => {
|
|
760
|
-
const
|
|
768
|
+
const fileArgs = (images ?? []).flatMap((p) => ["-f", p]);
|
|
769
|
+
const child = spawn3(opencodeBin(), ["run", "-s", sessionId, ...fileArgs, message], {
|
|
761
770
|
stdio: ["ignore", "pipe", "pipe"],
|
|
762
771
|
env: process.env
|
|
763
772
|
});
|
|
@@ -789,6 +798,43 @@ var init_opencode = __esm({
|
|
|
789
798
|
}
|
|
790
799
|
});
|
|
791
800
|
|
|
801
|
+
// src/runtime/meta.ts
|
|
802
|
+
import fs6 from "fs";
|
|
803
|
+
import path5 from "path";
|
|
804
|
+
function read() {
|
|
805
|
+
try {
|
|
806
|
+
return JSON.parse(fs6.readFileSync(META_PATH, "utf8"));
|
|
807
|
+
} catch {
|
|
808
|
+
return {};
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function write2(store) {
|
|
812
|
+
fs6.mkdirSync(path5.dirname(META_PATH), { recursive: true });
|
|
813
|
+
fs6.writeFileSync(META_PATH, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
|
|
814
|
+
}
|
|
815
|
+
function all() {
|
|
816
|
+
return read();
|
|
817
|
+
}
|
|
818
|
+
function setTitle(sessionId, title) {
|
|
819
|
+
const store = read();
|
|
820
|
+
store[sessionId] = { ...store[sessionId], title };
|
|
821
|
+
write2(store);
|
|
822
|
+
}
|
|
823
|
+
function setArchived(sessionId, archived) {
|
|
824
|
+
const store = read();
|
|
825
|
+
store[sessionId] = { ...store[sessionId], archived };
|
|
826
|
+
if (!store[sessionId].title && !store[sessionId].archived) delete store[sessionId];
|
|
827
|
+
write2(store);
|
|
828
|
+
}
|
|
829
|
+
var META_PATH;
|
|
830
|
+
var init_meta = __esm({
|
|
831
|
+
"src/runtime/meta.ts"() {
|
|
832
|
+
"use strict";
|
|
833
|
+
init_config();
|
|
834
|
+
META_PATH = path5.join(SAKURA_DIR, "session-meta.json");
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
792
838
|
// src/runtime/sessions.ts
|
|
793
839
|
import { spawn as spawn4 } from "child_process";
|
|
794
840
|
async function list(opts = {}) {
|
|
@@ -798,12 +844,28 @@ async function list(opts = {}) {
|
|
|
798
844
|
if (!opts.agent || opts.agent === "opencode")
|
|
799
845
|
sessions.push(...await listSessions3());
|
|
800
846
|
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
847
|
+
const overrides = all();
|
|
848
|
+
sessions = sessions.map((s) => {
|
|
849
|
+
const o = overrides[s.id];
|
|
850
|
+
return o?.title ? { ...s, title: o.title } : s;
|
|
851
|
+
}).filter((s) => {
|
|
852
|
+
const archived = !!overrides[s.id]?.archived;
|
|
853
|
+
if (opts.archived) return archived;
|
|
854
|
+
if (opts.all) return true;
|
|
855
|
+
return !archived;
|
|
856
|
+
});
|
|
801
857
|
if (opts.limit && opts.limit > 0) sessions = sessions.slice(0, opts.limit);
|
|
802
858
|
return sessions;
|
|
803
859
|
}
|
|
860
|
+
function rename(sessionId, title) {
|
|
861
|
+
setTitle(sessionId, title.trim());
|
|
862
|
+
}
|
|
863
|
+
function setArchived2(sessionId, archived) {
|
|
864
|
+
setArchived(sessionId, archived);
|
|
865
|
+
}
|
|
804
866
|
async function show(sessionId) {
|
|
805
|
-
const
|
|
806
|
-
return
|
|
867
|
+
const all2 = await list();
|
|
868
|
+
return all2.find((s) => s.id === sessionId) ?? null;
|
|
807
869
|
}
|
|
808
870
|
async function detectAgent(sessionId) {
|
|
809
871
|
if (findFile(sessionId)) return "claude";
|
|
@@ -833,11 +895,11 @@ async function history(sessionId, opts = {}, agent) {
|
|
|
833
895
|
if (opts.reverse) msgs = [...msgs].reverse();
|
|
834
896
|
return msgs;
|
|
835
897
|
}
|
|
836
|
-
async function chat(sessionId, prompt, agent, onData) {
|
|
898
|
+
async function chat(sessionId, prompt, agent, onData, images) {
|
|
837
899
|
const a = agent ?? await detectAgent(sessionId);
|
|
838
|
-
if (a === "claude") return send(sessionId, prompt, onData);
|
|
839
|
-
if (a === "codex") return send2(sessionId, prompt, onData);
|
|
840
|
-
if (a === "opencode") return send3(sessionId, prompt, onData);
|
|
900
|
+
if (a === "claude") return send(sessionId, prompt, onData, images);
|
|
901
|
+
if (a === "codex") return send2(sessionId, prompt, onData, images);
|
|
902
|
+
if (a === "opencode") return send3(sessionId, prompt, onData, images);
|
|
841
903
|
return {
|
|
842
904
|
ok: false,
|
|
843
905
|
output: "",
|
|
@@ -880,6 +942,7 @@ var init_sessions = __esm({
|
|
|
880
942
|
init_claude();
|
|
881
943
|
init_codex();
|
|
882
944
|
init_opencode();
|
|
945
|
+
init_meta();
|
|
883
946
|
}
|
|
884
947
|
});
|
|
885
948
|
|
|
@@ -901,8 +964,8 @@ __export(registry_exports, {
|
|
|
901
964
|
updateAgentConfig: () => updateAgentConfig
|
|
902
965
|
});
|
|
903
966
|
import os4 from "os";
|
|
904
|
-
import
|
|
905
|
-
import
|
|
967
|
+
import fs7 from "fs";
|
|
968
|
+
import path6 from "path";
|
|
906
969
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
907
970
|
function listWorkspaces() {
|
|
908
971
|
return loadConfig().workspaces;
|
|
@@ -914,22 +977,22 @@ function detectCli() {
|
|
|
914
977
|
codex: process.env.CODEX_BIN || "codex",
|
|
915
978
|
opencode: process.env.OPENCODE_BIN || "opencode"
|
|
916
979
|
};
|
|
917
|
-
const pathDirs = (process.env.PATH || "").split(
|
|
980
|
+
const pathDirs = (process.env.PATH || "").split(path6.delimiter);
|
|
918
981
|
for (const [name, bin] of Object.entries(candidates)) {
|
|
919
982
|
const onPath = pathDirs.some((dir) => {
|
|
920
983
|
try {
|
|
921
|
-
return
|
|
984
|
+
return fs7.existsSync(path6.join(dir, bin));
|
|
922
985
|
} catch {
|
|
923
986
|
return false;
|
|
924
987
|
}
|
|
925
988
|
});
|
|
926
989
|
const home = os4.homedir();
|
|
927
990
|
const dataDirs = {
|
|
928
|
-
claude:
|
|
929
|
-
codex:
|
|
930
|
-
opencode:
|
|
991
|
+
claude: path6.join(home, ".claude"),
|
|
992
|
+
codex: path6.join(home, ".codex"),
|
|
993
|
+
opencode: path6.join(home, ".local", "share", "opencode")
|
|
931
994
|
};
|
|
932
|
-
if (onPath ||
|
|
995
|
+
if (onPath || fs7.existsSync(dataDirs[name] ?? "")) found.push(name);
|
|
933
996
|
}
|
|
934
997
|
return found;
|
|
935
998
|
}
|
|
@@ -953,15 +1016,15 @@ function listProjects() {
|
|
|
953
1016
|
return loadConfig().projects;
|
|
954
1017
|
}
|
|
955
1018
|
function addProject(dir, name) {
|
|
956
|
-
const abs =
|
|
957
|
-
if (!
|
|
1019
|
+
const abs = path6.resolve(dir);
|
|
1020
|
+
if (!fs7.existsSync(abs) || !fs7.statSync(abs).isDirectory()) {
|
|
958
1021
|
throw new Error(`Not a directory: ${abs}`);
|
|
959
1022
|
}
|
|
960
1023
|
const existing = loadConfig().projects.find((p) => p.path === abs);
|
|
961
1024
|
if (existing) return existing;
|
|
962
1025
|
const project = {
|
|
963
1026
|
id: randomUUID2(),
|
|
964
|
-
name: name ||
|
|
1027
|
+
name: name || path6.basename(abs),
|
|
965
1028
|
path: abs,
|
|
966
1029
|
addedAt: Date.now()
|
|
967
1030
|
};
|
|
@@ -975,7 +1038,7 @@ function deleteProject(idOrNameOrPath) {
|
|
|
975
1038
|
updateConfig((cfg) => {
|
|
976
1039
|
const before = cfg.projects.length;
|
|
977
1040
|
cfg.projects = cfg.projects.filter(
|
|
978
|
-
(p) => p.id !== idOrNameOrPath && p.name !== idOrNameOrPath && p.path !==
|
|
1041
|
+
(p) => p.id !== idOrNameOrPath && p.name !== idOrNameOrPath && p.path !== path6.resolve(idOrNameOrPath)
|
|
979
1042
|
);
|
|
980
1043
|
removed = cfg.projects.length !== before;
|
|
981
1044
|
});
|
|
@@ -983,7 +1046,7 @@ function deleteProject(idOrNameOrPath) {
|
|
|
983
1046
|
}
|
|
984
1047
|
function resolveProject(idOrNameOrPath) {
|
|
985
1048
|
const cfg = loadConfig();
|
|
986
|
-
const abs =
|
|
1049
|
+
const abs = path6.resolve(idOrNameOrPath);
|
|
987
1050
|
return cfg.projects.find(
|
|
988
1051
|
(p) => p.id === idOrNameOrPath || p.name === idOrNameOrPath || p.path === abs
|
|
989
1052
|
) ?? null;
|
|
@@ -1073,24 +1136,24 @@ var init_registry = __esm({
|
|
|
1073
1136
|
});
|
|
1074
1137
|
|
|
1075
1138
|
// src/runtime/fsops.ts
|
|
1076
|
-
import
|
|
1077
|
-
import
|
|
1139
|
+
import fs8 from "fs";
|
|
1140
|
+
import path7 from "path";
|
|
1078
1141
|
import os5 from "os";
|
|
1079
1142
|
function expand(p) {
|
|
1080
1143
|
if (!p || p === "~") return HOME2;
|
|
1081
|
-
if (p.startsWith("~/")) return
|
|
1082
|
-
return
|
|
1144
|
+
if (p.startsWith("~/")) return path7.join(HOME2, p.slice(2));
|
|
1145
|
+
return path7.resolve(p);
|
|
1083
1146
|
}
|
|
1084
1147
|
function list2(dir) {
|
|
1085
1148
|
const abs = expand(dir);
|
|
1086
|
-
const dirents =
|
|
1149
|
+
const dirents = fs8.readdirSync(abs, { withFileTypes: true });
|
|
1087
1150
|
const entries = [];
|
|
1088
1151
|
for (const d of dirents) {
|
|
1089
|
-
const p =
|
|
1152
|
+
const p = path7.join(abs, d.name);
|
|
1090
1153
|
let size = 0;
|
|
1091
1154
|
let mtime = 0;
|
|
1092
1155
|
try {
|
|
1093
|
-
const st =
|
|
1156
|
+
const st = fs8.statSync(p);
|
|
1094
1157
|
size = st.size;
|
|
1095
1158
|
mtime = Math.floor(st.mtimeMs / 1e3);
|
|
1096
1159
|
} catch {
|
|
@@ -1103,7 +1166,7 @@ function list2(dir) {
|
|
|
1103
1166
|
if (a.type !== "dir" && b.type === "dir") return 1;
|
|
1104
1167
|
return a.name.localeCompare(b.name);
|
|
1105
1168
|
});
|
|
1106
|
-
const parent = abs === "/" ? null :
|
|
1169
|
+
const parent = abs === "/" ? null : path7.dirname(abs);
|
|
1107
1170
|
return { path: abs, parent, entries };
|
|
1108
1171
|
}
|
|
1109
1172
|
function looksBinary(buf) {
|
|
@@ -1111,14 +1174,14 @@ function looksBinary(buf) {
|
|
|
1111
1174
|
for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
|
|
1112
1175
|
return false;
|
|
1113
1176
|
}
|
|
1114
|
-
function
|
|
1177
|
+
function read2(file) {
|
|
1115
1178
|
const abs = expand(file);
|
|
1116
|
-
const st =
|
|
1117
|
-
const fd =
|
|
1179
|
+
const st = fs8.statSync(abs);
|
|
1180
|
+
const fd = fs8.openSync(abs, "r");
|
|
1118
1181
|
try {
|
|
1119
1182
|
const len = Math.min(st.size, READ_CAP);
|
|
1120
1183
|
const buf = Buffer.alloc(len);
|
|
1121
|
-
|
|
1184
|
+
fs8.readSync(fd, buf, 0, len, 0);
|
|
1122
1185
|
const binary = looksBinary(buf);
|
|
1123
1186
|
return {
|
|
1124
1187
|
path: abs,
|
|
@@ -1128,18 +1191,18 @@ function read(file) {
|
|
|
1128
1191
|
content: binary ? "" : buf.toString("utf8")
|
|
1129
1192
|
};
|
|
1130
1193
|
} finally {
|
|
1131
|
-
|
|
1194
|
+
fs8.closeSync(fd);
|
|
1132
1195
|
}
|
|
1133
1196
|
}
|
|
1134
|
-
function
|
|
1197
|
+
function write3(file, content) {
|
|
1135
1198
|
const abs = expand(file);
|
|
1136
|
-
|
|
1137
|
-
|
|
1199
|
+
fs8.mkdirSync(path7.dirname(abs), { recursive: true });
|
|
1200
|
+
fs8.writeFileSync(abs, content, "utf8");
|
|
1138
1201
|
return { path: abs, bytes: Buffer.byteLength(content, "utf8") };
|
|
1139
1202
|
}
|
|
1140
1203
|
function mkdir(dir) {
|
|
1141
1204
|
const abs = expand(dir);
|
|
1142
|
-
|
|
1205
|
+
fs8.mkdirSync(abs, { recursive: true });
|
|
1143
1206
|
return { path: abs };
|
|
1144
1207
|
}
|
|
1145
1208
|
var HOME2, READ_CAP;
|
|
@@ -1152,11 +1215,11 @@ var init_fsops = __esm({
|
|
|
1152
1215
|
});
|
|
1153
1216
|
|
|
1154
1217
|
// src/tailscale.ts
|
|
1155
|
-
import
|
|
1218
|
+
import fs9 from "fs";
|
|
1156
1219
|
import { spawn as spawn5 } from "child_process";
|
|
1157
1220
|
function tsBin() {
|
|
1158
1221
|
const macPath = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
|
|
1159
|
-
if (
|
|
1222
|
+
if (fs9.existsSync(macPath)) return macPath;
|
|
1160
1223
|
return process.env.TAILSCALE_BIN || "tailscale";
|
|
1161
1224
|
}
|
|
1162
1225
|
function runTs(args, timeoutMs = 6e4) {
|
|
@@ -1228,7 +1291,7 @@ async function status(port2) {
|
|
|
1228
1291
|
const dnsName = typeof self.DNSName === "string" ? self.DNSName.replace(/\.$/, "") : null;
|
|
1229
1292
|
const online = self.Online === true;
|
|
1230
1293
|
const backendState = res.data?.BackendState ?? "Unknown";
|
|
1231
|
-
const baseUrl = ipv4 ? `http://${ipv4}:${port2}` : null;
|
|
1294
|
+
const baseUrl = ipv4 && backendState === "Running" ? `http://${ipv4}:${port2}` : null;
|
|
1232
1295
|
const sakura = {
|
|
1233
1296
|
online,
|
|
1234
1297
|
backendState,
|
|
@@ -1308,7 +1371,7 @@ var init_hub = __esm({
|
|
|
1308
1371
|
});
|
|
1309
1372
|
|
|
1310
1373
|
// src/daemon/stream.ts
|
|
1311
|
-
async function runTurn(sessionId, agent, prompt) {
|
|
1374
|
+
async function runTurn(sessionId, agent, prompt, images) {
|
|
1312
1375
|
const key = sessionKey(agent, sessionId);
|
|
1313
1376
|
const emit = (e) => hub.broadcast(key, e);
|
|
1314
1377
|
let lastEmitted = "\0";
|
|
@@ -1348,7 +1411,7 @@ async function runTurn(sessionId, agent, prompt) {
|
|
|
1348
1411
|
} catch {
|
|
1349
1412
|
}
|
|
1350
1413
|
}, POLL_MS);
|
|
1351
|
-
const result = await chat(sessionId, prompt, agent, onData);
|
|
1414
|
+
const result = await chat(sessionId, prompt, agent, onData, images);
|
|
1352
1415
|
clearInterval(timer);
|
|
1353
1416
|
emit({ type: "done", sessionId, agent, ok: result.ok, error: result.error });
|
|
1354
1417
|
return { ok: result.ok, error: result.error };
|
|
@@ -1454,13 +1517,15 @@ var init_terminal = __esm({
|
|
|
1454
1517
|
|
|
1455
1518
|
// src/daemon/server.ts
|
|
1456
1519
|
import http from "http";
|
|
1457
|
-
import
|
|
1520
|
+
import fs10 from "fs";
|
|
1521
|
+
import os7 from "os";
|
|
1522
|
+
import path8 from "path";
|
|
1458
1523
|
import { URL } from "url";
|
|
1459
1524
|
import { WebSocketServer } from "ws";
|
|
1460
|
-
function add(method,
|
|
1525
|
+
function add(method, path9, handler) {
|
|
1461
1526
|
const keys = [];
|
|
1462
1527
|
const pattern = new RegExp(
|
|
1463
|
-
"^" +
|
|
1528
|
+
"^" + path9.replace(/:[^/]+/g, (m) => {
|
|
1464
1529
|
keys.push(m.slice(1));
|
|
1465
1530
|
return "([^/]+)";
|
|
1466
1531
|
}) + "/?$"
|
|
@@ -1473,6 +1538,26 @@ function ok(data = null) {
|
|
|
1473
1538
|
function fail(code, stderr, data = null) {
|
|
1474
1539
|
return { ok: false, data, stdout: "", stderr, code };
|
|
1475
1540
|
}
|
|
1541
|
+
function saveImages(sessionId, images) {
|
|
1542
|
+
const dir = path8.join(os7.tmpdir(), "sakura-uploads", sessionId.slice(0, 24));
|
|
1543
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
1544
|
+
const paths = [];
|
|
1545
|
+
images.forEach((img, i) => {
|
|
1546
|
+
const raw = String(img?.data ?? "");
|
|
1547
|
+
const m = raw.match(/^data:([^;]+);base64,(.*)$/s);
|
|
1548
|
+
if (!m) {
|
|
1549
|
+
log.info(`saveImages: image ${i} did not match data-url (len=${raw.length}, prefix="${raw.slice(0, 40)}")`);
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
const ext = EXT_BY_MIME[m[1]] ?? "png";
|
|
1553
|
+
const safe = img.name && /\.[a-z0-9]+$/i.test(img.name) ? img.name.replace(/[^\w.\-]/g, "_") : `image-${Date.now()}-${i + 1}.${ext}`;
|
|
1554
|
+
const file = path8.join(dir, safe);
|
|
1555
|
+
fs10.writeFileSync(file, Buffer.from(m[2], "base64"));
|
|
1556
|
+
log.info(`saveImages: wrote ${file} (${m[2].length} b64 chars)`);
|
|
1557
|
+
paths.push(file);
|
|
1558
|
+
});
|
|
1559
|
+
return paths;
|
|
1560
|
+
}
|
|
1476
1561
|
function kvToObj(env) {
|
|
1477
1562
|
if (!Array.isArray(env)) return void 0;
|
|
1478
1563
|
const out = {};
|
|
@@ -1595,7 +1680,7 @@ function createServer(port2) {
|
|
|
1595
1680
|
function setVersion(v) {
|
|
1596
1681
|
VERSION2 = v;
|
|
1597
1682
|
}
|
|
1598
|
-
var routes, VERSION2;
|
|
1683
|
+
var routes, EXT_BY_MIME, VERSION2;
|
|
1599
1684
|
var init_server = __esm({
|
|
1600
1685
|
"src/daemon/server.ts"() {
|
|
1601
1686
|
"use strict";
|
|
@@ -1611,8 +1696,18 @@ var init_server = __esm({
|
|
|
1611
1696
|
init_stream();
|
|
1612
1697
|
init_terminal();
|
|
1613
1698
|
routes = [];
|
|
1614
|
-
|
|
1615
|
-
|
|
1699
|
+
EXT_BY_MIME = {
|
|
1700
|
+
"image/png": "png",
|
|
1701
|
+
"image/jpeg": "jpg",
|
|
1702
|
+
"image/jpg": "jpg",
|
|
1703
|
+
"image/gif": "gif",
|
|
1704
|
+
"image/webp": "webp",
|
|
1705
|
+
"image/heic": "heic"
|
|
1706
|
+
};
|
|
1707
|
+
add("GET", "/sessions", async ({ url }) => {
|
|
1708
|
+
const archived = url.searchParams.get("archived") === "true";
|
|
1709
|
+
const all2 = url.searchParams.get("all") === "true";
|
|
1710
|
+
const list3 = await list({ archived, all: all2 });
|
|
1616
1711
|
return { sessions: list3 };
|
|
1617
1712
|
});
|
|
1618
1713
|
add("GET", "/sessions/:id/messages", async ({ params, url }) => {
|
|
@@ -1623,9 +1718,28 @@ var init_server = __esm({
|
|
|
1623
1718
|
add("POST", "/sessions/:id/send", async ({ params, body }) => {
|
|
1624
1719
|
const agent = body?.agent || await detectAgent(params.id);
|
|
1625
1720
|
if (!agent) return { ok: false, error: "unknown agent for session" };
|
|
1626
|
-
const
|
|
1721
|
+
const message = String(body?.message ?? "");
|
|
1722
|
+
const incoming = Array.isArray(body?.images) ? body.images : [];
|
|
1723
|
+
log.info(`send: agent=${agent} msgLen=${message.length} bodyKeys=[${Object.keys(body ?? {}).join(",")}] images=${incoming.length}`);
|
|
1724
|
+
const imagePaths = incoming.length ? saveImages(params.id, incoming) : [];
|
|
1725
|
+
if (imagePaths.length) log.info(`send: attached ${imagePaths.length} image(s) to ${agent} prompt`);
|
|
1726
|
+
const r = await runTurn(params.id, agent, message, imagePaths);
|
|
1627
1727
|
return { ok: r.ok, error: r.error };
|
|
1628
1728
|
});
|
|
1729
|
+
add("POST", "/sessions/:id/rename", ({ params, body }) => {
|
|
1730
|
+
const title = String(body?.title ?? "").trim();
|
|
1731
|
+
if (!title) return { ok: false, error: "title is required" };
|
|
1732
|
+
rename(params.id, title);
|
|
1733
|
+
return { ok: true };
|
|
1734
|
+
});
|
|
1735
|
+
add("POST", "/sessions/:id/archive", ({ params }) => {
|
|
1736
|
+
setArchived2(params.id, true);
|
|
1737
|
+
return { ok: true };
|
|
1738
|
+
});
|
|
1739
|
+
add("POST", "/sessions/:id/restore", ({ params }) => {
|
|
1740
|
+
setArchived2(params.id, false);
|
|
1741
|
+
return { ok: true };
|
|
1742
|
+
});
|
|
1629
1743
|
add(
|
|
1630
1744
|
"GET",
|
|
1631
1745
|
"/sakura/health",
|
|
@@ -1648,7 +1762,7 @@ var init_server = __esm({
|
|
|
1648
1762
|
case "logs": {
|
|
1649
1763
|
let logs2 = "";
|
|
1650
1764
|
try {
|
|
1651
|
-
const lines =
|
|
1765
|
+
const lines = fs10.readFileSync(LOG_PATH, "utf8").split("\n");
|
|
1652
1766
|
logs2 = lines.slice(-200).join("\n");
|
|
1653
1767
|
} catch {
|
|
1654
1768
|
}
|
|
@@ -1698,10 +1812,10 @@ var init_server = __esm({
|
|
|
1698
1812
|
return s ? ok(s) : fail("nonzero", "session not found");
|
|
1699
1813
|
});
|
|
1700
1814
|
add("GET", "/sakura/sessions/:id/history", async ({ params, url }) => {
|
|
1701
|
-
const
|
|
1815
|
+
const all2 = url.searchParams.get("all") === "true";
|
|
1702
1816
|
const limit = Number(url.searchParams.get("limit")) || void 0;
|
|
1703
1817
|
const reverse = url.searchParams.get("reverse") === "true";
|
|
1704
|
-
return ok(await history(params.id, { all, limit, reverse }));
|
|
1818
|
+
return ok(await history(params.id, { all: all2, limit, reverse }));
|
|
1705
1819
|
});
|
|
1706
1820
|
add("POST", "/sakura/sessions/:id/chat", async ({ params, body }) => {
|
|
1707
1821
|
const agent = body?.agent || await detectAgent(params.id);
|
|
@@ -1714,22 +1828,27 @@ var init_server = __esm({
|
|
|
1714
1828
|
"/sakura/sessions/:id/cancel",
|
|
1715
1829
|
() => fail("nonzero", "cancel is not supported for local file-backed sessions")
|
|
1716
1830
|
);
|
|
1717
|
-
add(
|
|
1718
|
-
"
|
|
1719
|
-
"
|
|
1720
|
-
(
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1831
|
+
add("POST", "/sakura/sessions/:id/rename", ({ params, body }) => {
|
|
1832
|
+
const title = String(body?.title ?? "").trim();
|
|
1833
|
+
if (!title) return fail("nonzero", "title is required");
|
|
1834
|
+
rename(params.id, title);
|
|
1835
|
+
return ok({ id: params.id, title });
|
|
1836
|
+
});
|
|
1837
|
+
add("POST", "/sakura/sessions/:id/:action", ({ params }) => {
|
|
1838
|
+
switch (params.action) {
|
|
1839
|
+
case "archive":
|
|
1840
|
+
setArchived2(params.id, true);
|
|
1841
|
+
return ok({ id: params.id, archived: true });
|
|
1842
|
+
case "restore":
|
|
1843
|
+
setArchived2(params.id, false);
|
|
1844
|
+
return ok({ id: params.id, archived: false });
|
|
1845
|
+
default:
|
|
1846
|
+
return fail(
|
|
1847
|
+
"nonzero",
|
|
1848
|
+
`${params.action} is disabled for local sessions to avoid deleting agent history`
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1733
1852
|
add("GET", "/sakura/agent-configs", () => ok(listAgentConfigs()));
|
|
1734
1853
|
add("POST", "/sakura/agent-configs", ({ body }) => {
|
|
1735
1854
|
try {
|
|
@@ -1788,7 +1907,7 @@ var init_server = __esm({
|
|
|
1788
1907
|
const p = url.searchParams.get("path");
|
|
1789
1908
|
if (!p) return fail("nonzero", "path required");
|
|
1790
1909
|
try {
|
|
1791
|
-
return ok(
|
|
1910
|
+
return ok(read2(p));
|
|
1792
1911
|
} catch (e) {
|
|
1793
1912
|
return fail("nonzero", e.message);
|
|
1794
1913
|
}
|
|
@@ -1796,7 +1915,7 @@ var init_server = __esm({
|
|
|
1796
1915
|
add("POST", "/sakura/fs/write", ({ body }) => {
|
|
1797
1916
|
if (!body?.path) return fail("nonzero", "path required");
|
|
1798
1917
|
try {
|
|
1799
|
-
return ok(
|
|
1918
|
+
return ok(write3(body.path, String(body.content ?? "")));
|
|
1800
1919
|
} catch (e) {
|
|
1801
1920
|
return fail("nonzero", e.message);
|
|
1802
1921
|
}
|
|
@@ -1820,11 +1939,11 @@ var init_server = __esm({
|
|
|
1820
1939
|
});
|
|
1821
1940
|
|
|
1822
1941
|
// src/daemon/manager.ts
|
|
1823
|
-
import
|
|
1942
|
+
import fs11 from "fs";
|
|
1824
1943
|
import { spawn as spawn7 } from "child_process";
|
|
1825
1944
|
function readDaemonInfo() {
|
|
1826
1945
|
try {
|
|
1827
|
-
return JSON.parse(
|
|
1946
|
+
return JSON.parse(fs11.readFileSync(DAEMON_PATH, "utf8"));
|
|
1828
1947
|
} catch {
|
|
1829
1948
|
return null;
|
|
1830
1949
|
}
|
|
@@ -1841,8 +1960,8 @@ function running() {
|
|
|
1841
1960
|
const info2 = readDaemonInfo();
|
|
1842
1961
|
if (info2 && pidAlive(info2.pid)) return info2;
|
|
1843
1962
|
if (info2) {
|
|
1844
|
-
|
|
1845
|
-
|
|
1963
|
+
fs11.rmSync(DAEMON_PATH, { force: true });
|
|
1964
|
+
fs11.rmSync(PID_PATH, { force: true });
|
|
1846
1965
|
}
|
|
1847
1966
|
return null;
|
|
1848
1967
|
}
|
|
@@ -1865,8 +1984,8 @@ async function runServer(version) {
|
|
|
1865
1984
|
url: `http://${host}:${port2}`,
|
|
1866
1985
|
startedAt: Date.now()
|
|
1867
1986
|
};
|
|
1868
|
-
|
|
1869
|
-
|
|
1987
|
+
fs11.writeFileSync(DAEMON_PATH, JSON.stringify(info2, null, 2));
|
|
1988
|
+
fs11.writeFileSync(PID_PATH, String(process.pid));
|
|
1870
1989
|
const tailnet = await discoverBaseUrl(port2).catch(() => null);
|
|
1871
1990
|
log.info(`sakuraai runtime listening on http://0.0.0.0:${port2}`);
|
|
1872
1991
|
if (tailnet) log.info(`reachable over Tailscale at ${tailnet}`);
|
|
@@ -1874,8 +1993,8 @@ async function runServer(version) {
|
|
|
1874
1993
|
const shutdown = () => {
|
|
1875
1994
|
log.info("shutting down");
|
|
1876
1995
|
server.close();
|
|
1877
|
-
|
|
1878
|
-
|
|
1996
|
+
fs11.rmSync(DAEMON_PATH, { force: true });
|
|
1997
|
+
fs11.rmSync(PID_PATH, { force: true });
|
|
1879
1998
|
process.exit(0);
|
|
1880
1999
|
};
|
|
1881
2000
|
process.on("SIGINT", shutdown);
|
|
@@ -1886,8 +2005,8 @@ function start() {
|
|
|
1886
2005
|
if (existing) return existing;
|
|
1887
2006
|
ensureSakuraDirs();
|
|
1888
2007
|
const entry = process.argv[1] ?? "";
|
|
1889
|
-
const out =
|
|
1890
|
-
const err =
|
|
2008
|
+
const out = fs11.openSync(LOG_PATH, "a");
|
|
2009
|
+
const err = fs11.openSync(LOG_PATH, "a");
|
|
1891
2010
|
const child = spawn7(process.execPath, [entry, "__run-daemon"], {
|
|
1892
2011
|
detached: true,
|
|
1893
2012
|
stdio: ["ignore", out, err],
|
|
@@ -1909,8 +2028,8 @@ function stop() {
|
|
|
1909
2028
|
process.kill(info2.pid, "SIGTERM");
|
|
1910
2029
|
} catch {
|
|
1911
2030
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
2031
|
+
fs11.rmSync(DAEMON_PATH, { force: true });
|
|
2032
|
+
fs11.rmSync(PID_PATH, { force: true });
|
|
1914
2033
|
return true;
|
|
1915
2034
|
}
|
|
1916
2035
|
function restart() {
|
|
@@ -1920,8 +2039,8 @@ function restart() {
|
|
|
1920
2039
|
}
|
|
1921
2040
|
function logs(lines = 50) {
|
|
1922
2041
|
try {
|
|
1923
|
-
const
|
|
1924
|
-
return
|
|
2042
|
+
const all2 = fs11.readFileSync(LOG_PATH, "utf8").split("\n");
|
|
2043
|
+
return all2.slice(-lines).join("\n");
|
|
1925
2044
|
} catch {
|
|
1926
2045
|
return "";
|
|
1927
2046
|
}
|
|
@@ -1937,7 +2056,7 @@ var init_manager = __esm({
|
|
|
1937
2056
|
});
|
|
1938
2057
|
|
|
1939
2058
|
// src/pairing.ts
|
|
1940
|
-
import
|
|
2059
|
+
import os8 from "os";
|
|
1941
2060
|
import qrcode from "qrcode-terminal";
|
|
1942
2061
|
async function buildPairing() {
|
|
1943
2062
|
const token = requireToken();
|
|
@@ -1954,7 +2073,7 @@ function renderQr(deepLink) {
|
|
|
1954
2073
|
});
|
|
1955
2074
|
}
|
|
1956
2075
|
function localIp() {
|
|
1957
|
-
const nets =
|
|
2076
|
+
const nets = os8.networkInterfaces();
|
|
1958
2077
|
for (const name of Object.keys(nets)) {
|
|
1959
2078
|
for (const net of nets[name] ?? []) {
|
|
1960
2079
|
if (net.family === "IPv4" && !net.internal) return net.address;
|
|
@@ -2108,7 +2227,7 @@ function registerRuntime(program, version) {
|
|
|
2108
2227
|
// src/commands/session.ts
|
|
2109
2228
|
init_sessions();
|
|
2110
2229
|
init_output();
|
|
2111
|
-
import
|
|
2230
|
+
import fs12 from "fs";
|
|
2112
2231
|
function resolveSessionId(arg) {
|
|
2113
2232
|
const id = arg || process.env.SAKURA_SESSION_ID;
|
|
2114
2233
|
if (!id) die("Provide a sessionId (positional or via SAKURA_SESSION_ID).");
|
|
@@ -2117,7 +2236,7 @@ function resolveSessionId(arg) {
|
|
|
2117
2236
|
async function resolvePrompt(positional, opts) {
|
|
2118
2237
|
if (positional) return positional;
|
|
2119
2238
|
if (opts.prompt) return opts.prompt;
|
|
2120
|
-
if (opts.promptFile) return
|
|
2239
|
+
if (opts.promptFile) return fs12.readFileSync(opts.promptFile, "utf8");
|
|
2121
2240
|
if (!process.stdin.isTTY) {
|
|
2122
2241
|
const chunks = [];
|
|
2123
2242
|
for await (const c of process.stdin) chunks.push(c);
|
|
@@ -2201,16 +2320,24 @@ function registerSession(program) {
|
|
|
2201
2320
|
resolveSessionId(sessionId);
|
|
2202
2321
|
die("Cancel is not supported for local file-backed sessions.", 2);
|
|
2203
2322
|
});
|
|
2204
|
-
s.command("rename [sessionId] [title]").description("Rename a session").option("--title <title>", "new title").action((sessionId) => {
|
|
2205
|
-
resolveSessionId(sessionId);
|
|
2206
|
-
|
|
2323
|
+
s.command("rename [sessionId] [title]").description("Rename a session (sakura overlay \u2014 agent history is untouched)").option("--title <title>", "new title").action((sessionId, title, opts) => {
|
|
2324
|
+
const id = resolveSessionId(sessionId);
|
|
2325
|
+
const next = String(opts?.title ?? title ?? "").trim();
|
|
2326
|
+
if (!next) die("Provide a new title (positional or --title).");
|
|
2327
|
+
rename(id, next);
|
|
2328
|
+
success(`Renamed session to "${next}".`);
|
|
2207
2329
|
});
|
|
2208
|
-
for (const action of ["archive", "restore"
|
|
2209
|
-
s.command(`${action} [sessionId]`).description(`${action[0].toUpperCase()}${action.slice(1)} a session (
|
|
2210
|
-
resolveSessionId(sessionId);
|
|
2211
|
-
|
|
2330
|
+
for (const action of ["archive", "restore"]) {
|
|
2331
|
+
s.command(`${action} [sessionId]`).description(`${action[0].toUpperCase()}${action.slice(1)} a session (sakura overlay)`).action((sessionId) => {
|
|
2332
|
+
const id = resolveSessionId(sessionId);
|
|
2333
|
+
setArchived2(id, action === "archive");
|
|
2334
|
+
success(`${action === "archive" ? "Archived" : "Restored"} session ${id.slice(0, 12)}.`);
|
|
2212
2335
|
});
|
|
2213
2336
|
}
|
|
2337
|
+
s.command("delete [sessionId]").description("Delete a session (disabled for local sessions)").action((sessionId) => {
|
|
2338
|
+
resolveSessionId(sessionId);
|
|
2339
|
+
die("delete is disabled for local sessions to avoid deleting agent history.", 2);
|
|
2340
|
+
});
|
|
2214
2341
|
}
|
|
2215
2342
|
function collect(value, prev) {
|
|
2216
2343
|
return prev.concat([value]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sakuraai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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",
|