sakuraai 0.0.6 → 0.0.8
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 +317 -104
- 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
|
|
@@ -37,7 +37,8 @@ function defaultConfig() {
|
|
|
37
37
|
workspaces: [{ id: "local", name: "Local", slug: "local" }],
|
|
38
38
|
projects: [],
|
|
39
39
|
agentConfigs: [],
|
|
40
|
-
daemon: { host: DEFAULT_HOST, port: DEFAULT_PORT }
|
|
40
|
+
daemon: { host: DEFAULT_HOST, port: DEFAULT_PORT },
|
|
41
|
+
pushTokens: []
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
function loadConfig() {
|
|
@@ -428,17 +429,24 @@ function sessionCwd(sessionId) {
|
|
|
428
429
|
}
|
|
429
430
|
return void 0;
|
|
430
431
|
}
|
|
431
|
-
function send(sessionId, message, onData) {
|
|
432
|
+
function send(sessionId, message, onData, images) {
|
|
432
433
|
return new Promise((resolve) => {
|
|
433
434
|
const bin = process.env.CLAUDE_BIN || "claude";
|
|
434
435
|
const cwd = sessionCwd(sessionId);
|
|
436
|
+
let prompt = message;
|
|
437
|
+
if (images?.length) {
|
|
438
|
+
const refs = images.map((p) => `[Image: ${p}]`).join("\n");
|
|
439
|
+
prompt = prompt ? `${prompt}
|
|
440
|
+
|
|
441
|
+
${refs}` : refs;
|
|
442
|
+
}
|
|
435
443
|
const child = spawn(
|
|
436
444
|
bin,
|
|
437
445
|
[
|
|
438
446
|
"--resume",
|
|
439
447
|
sessionId,
|
|
440
448
|
"-p",
|
|
441
|
-
|
|
449
|
+
prompt,
|
|
442
450
|
"--output-format",
|
|
443
451
|
"stream-json",
|
|
444
452
|
"--include-partial-messages",
|
|
@@ -609,9 +617,10 @@ function messages2(sessionId) {
|
|
|
609
617
|
const file = findFile2(sessionId);
|
|
610
618
|
return file ? readMessages2(file) : [];
|
|
611
619
|
}
|
|
612
|
-
function send2(sessionId, message, onData) {
|
|
620
|
+
function send2(sessionId, message, onData, images) {
|
|
613
621
|
return new Promise((resolve) => {
|
|
614
|
-
const
|
|
622
|
+
const imageArgs = (images ?? []).flatMap((p) => ["-i", p]);
|
|
623
|
+
const child = spawn2(codexBin(), ["exec", "resume", sessionId, ...imageArgs, message], {
|
|
615
624
|
stdio: ["ignore", "pipe", "pipe"],
|
|
616
625
|
env: process.env
|
|
617
626
|
});
|
|
@@ -755,9 +764,10 @@ async function messages3(sessionId) {
|
|
|
755
764
|
db.close();
|
|
756
765
|
}
|
|
757
766
|
}
|
|
758
|
-
function send3(sessionId, message, onData) {
|
|
767
|
+
function send3(sessionId, message, onData, images) {
|
|
759
768
|
return new Promise((resolve) => {
|
|
760
|
-
const
|
|
769
|
+
const fileArgs = (images ?? []).flatMap((p) => ["-f", p]);
|
|
770
|
+
const child = spawn3(opencodeBin(), ["run", "-s", sessionId, ...fileArgs, message], {
|
|
761
771
|
stdio: ["ignore", "pipe", "pipe"],
|
|
762
772
|
env: process.env
|
|
763
773
|
});
|
|
@@ -789,6 +799,43 @@ var init_opencode = __esm({
|
|
|
789
799
|
}
|
|
790
800
|
});
|
|
791
801
|
|
|
802
|
+
// src/runtime/meta.ts
|
|
803
|
+
import fs6 from "fs";
|
|
804
|
+
import path5 from "path";
|
|
805
|
+
function read() {
|
|
806
|
+
try {
|
|
807
|
+
return JSON.parse(fs6.readFileSync(META_PATH, "utf8"));
|
|
808
|
+
} catch {
|
|
809
|
+
return {};
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function write2(store) {
|
|
813
|
+
fs6.mkdirSync(path5.dirname(META_PATH), { recursive: true });
|
|
814
|
+
fs6.writeFileSync(META_PATH, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
|
|
815
|
+
}
|
|
816
|
+
function all() {
|
|
817
|
+
return read();
|
|
818
|
+
}
|
|
819
|
+
function setTitle(sessionId, title) {
|
|
820
|
+
const store = read();
|
|
821
|
+
store[sessionId] = { ...store[sessionId], title };
|
|
822
|
+
write2(store);
|
|
823
|
+
}
|
|
824
|
+
function setArchived(sessionId, archived) {
|
|
825
|
+
const store = read();
|
|
826
|
+
store[sessionId] = { ...store[sessionId], archived };
|
|
827
|
+
if (!store[sessionId].title && !store[sessionId].archived) delete store[sessionId];
|
|
828
|
+
write2(store);
|
|
829
|
+
}
|
|
830
|
+
var META_PATH;
|
|
831
|
+
var init_meta = __esm({
|
|
832
|
+
"src/runtime/meta.ts"() {
|
|
833
|
+
"use strict";
|
|
834
|
+
init_config();
|
|
835
|
+
META_PATH = path5.join(SAKURA_DIR, "session-meta.json");
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
|
|
792
839
|
// src/runtime/sessions.ts
|
|
793
840
|
import { spawn as spawn4 } from "child_process";
|
|
794
841
|
async function list(opts = {}) {
|
|
@@ -798,12 +845,28 @@ async function list(opts = {}) {
|
|
|
798
845
|
if (!opts.agent || opts.agent === "opencode")
|
|
799
846
|
sessions.push(...await listSessions3());
|
|
800
847
|
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
848
|
+
const overrides = all();
|
|
849
|
+
sessions = sessions.map((s) => {
|
|
850
|
+
const o = overrides[s.id];
|
|
851
|
+
return o?.title ? { ...s, title: o.title } : s;
|
|
852
|
+
}).filter((s) => {
|
|
853
|
+
const archived = !!overrides[s.id]?.archived;
|
|
854
|
+
if (opts.archived) return archived;
|
|
855
|
+
if (opts.all) return true;
|
|
856
|
+
return !archived;
|
|
857
|
+
});
|
|
801
858
|
if (opts.limit && opts.limit > 0) sessions = sessions.slice(0, opts.limit);
|
|
802
859
|
return sessions;
|
|
803
860
|
}
|
|
861
|
+
function rename(sessionId, title) {
|
|
862
|
+
setTitle(sessionId, title.trim());
|
|
863
|
+
}
|
|
864
|
+
function setArchived2(sessionId, archived) {
|
|
865
|
+
setArchived(sessionId, archived);
|
|
866
|
+
}
|
|
804
867
|
async function show(sessionId) {
|
|
805
|
-
const
|
|
806
|
-
return
|
|
868
|
+
const all2 = await list();
|
|
869
|
+
return all2.find((s) => s.id === sessionId) ?? null;
|
|
807
870
|
}
|
|
808
871
|
async function detectAgent(sessionId) {
|
|
809
872
|
if (findFile(sessionId)) return "claude";
|
|
@@ -833,11 +896,11 @@ async function history(sessionId, opts = {}, agent) {
|
|
|
833
896
|
if (opts.reverse) msgs = [...msgs].reverse();
|
|
834
897
|
return msgs;
|
|
835
898
|
}
|
|
836
|
-
async function chat(sessionId, prompt, agent, onData) {
|
|
899
|
+
async function chat(sessionId, prompt, agent, onData, images) {
|
|
837
900
|
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);
|
|
901
|
+
if (a === "claude") return send(sessionId, prompt, onData, images);
|
|
902
|
+
if (a === "codex") return send2(sessionId, prompt, onData, images);
|
|
903
|
+
if (a === "opencode") return send3(sessionId, prompt, onData, images);
|
|
841
904
|
return {
|
|
842
905
|
ok: false,
|
|
843
906
|
output: "",
|
|
@@ -880,6 +943,79 @@ var init_sessions = __esm({
|
|
|
880
943
|
init_claude();
|
|
881
944
|
init_codex();
|
|
882
945
|
init_opencode();
|
|
946
|
+
init_meta();
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
// src/push.ts
|
|
951
|
+
function addPushToken(token) {
|
|
952
|
+
updateConfig((cfg) => {
|
|
953
|
+
if (!cfg.pushTokens) cfg.pushTokens = [];
|
|
954
|
+
if (!cfg.pushTokens.includes(token)) cfg.pushTokens.push(token);
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
async function sendPush(title, body, data = {}) {
|
|
958
|
+
const tokens = loadConfig().pushTokens ?? [];
|
|
959
|
+
if (tokens.length === 0) return;
|
|
960
|
+
const messages5 = tokens.map((to) => ({ to, title, body, sound: "default", data }));
|
|
961
|
+
try {
|
|
962
|
+
const res = await fetch(EXPO_PUSH_URL, {
|
|
963
|
+
method: "POST",
|
|
964
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
965
|
+
body: JSON.stringify(messages5)
|
|
966
|
+
});
|
|
967
|
+
if (!res.ok) console.error(`[push] send failed: HTTP ${res.status}`);
|
|
968
|
+
} catch (e) {
|
|
969
|
+
console.error("[push] send error:", e);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function notifyTurnEnd(agent, sessionId, ok2, error, output) {
|
|
973
|
+
const label = AGENT_LABEL[agent] ?? agent;
|
|
974
|
+
const data = { sessionId, agent };
|
|
975
|
+
if (!ok2) {
|
|
976
|
+
const m2 = pick(ERROR)(label, error);
|
|
977
|
+
void sendPush(m2.title, m2.body, { ...data, kind: "error" });
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (PERMISSION_RE.test(output)) {
|
|
981
|
+
const m2 = pick(PERMISSION)(label);
|
|
982
|
+
void sendPush(m2.title, m2.body, { ...data, kind: "permission" });
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const m = pick(DONE)(label);
|
|
986
|
+
void sendPush(m.title, m.body, { ...data, kind: "done" });
|
|
987
|
+
}
|
|
988
|
+
var EXPO_PUSH_URL, AGENT_LABEL, PERMISSION_RE, pick, DONE, PERMISSION, ERROR;
|
|
989
|
+
var init_push = __esm({
|
|
990
|
+
"src/push.ts"() {
|
|
991
|
+
"use strict";
|
|
992
|
+
init_config();
|
|
993
|
+
EXPO_PUSH_URL = "https://exp.host/--/api/v2/push/send";
|
|
994
|
+
AGENT_LABEL = {
|
|
995
|
+
claude: "Claude Code",
|
|
996
|
+
codex: "Codex",
|
|
997
|
+
opencode: "OpenCode"
|
|
998
|
+
};
|
|
999
|
+
PERMISSION_RE = /\b(need|needs|require[sd]?|asking for|waiting for|grant|allow|approve|approval|permission|authoriz)\b.{0,40}\b(permission|approval|access|to run|to proceed|to continue|your ok|confirm)\b/i;
|
|
1000
|
+
pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
1001
|
+
DONE = [
|
|
1002
|
+
(a) => ({ title: "\u{1F389} Done & dusted", body: `${a} shipped your task. Go peek \u{1F440}` }),
|
|
1003
|
+
(a) => ({ title: "\u2728 Nailed it", body: `${a} just wrapped up \u2014 it's all yours.` }),
|
|
1004
|
+
(a) => ({ title: "\u{1F680} Mission complete", body: `${a} crushed it. Tap to review.` }),
|
|
1005
|
+
(a) => ({ title: "\u{1F3C1} Finished", body: `${a} finished the job \u{1F525}` }),
|
|
1006
|
+
(a) => ({ title: "\u{1F485} Ta-da", body: `${a} is done flexing on your codebase.` })
|
|
1007
|
+
];
|
|
1008
|
+
PERMISSION = [
|
|
1009
|
+
(a) => ({ title: "\u{1F510} Permission, please", body: `${a} needs your go-ahead to continue.` }),
|
|
1010
|
+
(a) => ({ title: "\u{1F64B} Tap to approve", body: `${a} is standing by for your OK.` }),
|
|
1011
|
+
(a) => ({ title: "\u23F8\uFE0F Hold up", body: `${a} paused \u2014 it wants your blessing first.` }),
|
|
1012
|
+
(a) => ({ title: "\u{1F6A6} Waiting on you", body: `${a} can't proceed without a green light.` })
|
|
1013
|
+
];
|
|
1014
|
+
ERROR = [
|
|
1015
|
+
(a, e) => ({ title: "\u{1F4A5} Uh oh", body: e ? `${a}: ${e}` : `${a} hit a snag. Tap to see what happened.` }),
|
|
1016
|
+
(a, e) => ({ title: "\u{1FAE0} Something broke", body: e ? `${a} stumbled: ${e}` : `${a} stopped unexpectedly.` }),
|
|
1017
|
+
(a) => ({ title: "\u26A0\uFE0F Agent tripped", body: `${a} ran into an error. Better check on it.` })
|
|
1018
|
+
];
|
|
883
1019
|
}
|
|
884
1020
|
});
|
|
885
1021
|
|
|
@@ -901,8 +1037,8 @@ __export(registry_exports, {
|
|
|
901
1037
|
updateAgentConfig: () => updateAgentConfig
|
|
902
1038
|
});
|
|
903
1039
|
import os4 from "os";
|
|
904
|
-
import
|
|
905
|
-
import
|
|
1040
|
+
import fs7 from "fs";
|
|
1041
|
+
import path6 from "path";
|
|
906
1042
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
907
1043
|
function listWorkspaces() {
|
|
908
1044
|
return loadConfig().workspaces;
|
|
@@ -914,22 +1050,22 @@ function detectCli() {
|
|
|
914
1050
|
codex: process.env.CODEX_BIN || "codex",
|
|
915
1051
|
opencode: process.env.OPENCODE_BIN || "opencode"
|
|
916
1052
|
};
|
|
917
|
-
const pathDirs = (process.env.PATH || "").split(
|
|
1053
|
+
const pathDirs = (process.env.PATH || "").split(path6.delimiter);
|
|
918
1054
|
for (const [name, bin] of Object.entries(candidates)) {
|
|
919
1055
|
const onPath = pathDirs.some((dir) => {
|
|
920
1056
|
try {
|
|
921
|
-
return
|
|
1057
|
+
return fs7.existsSync(path6.join(dir, bin));
|
|
922
1058
|
} catch {
|
|
923
1059
|
return false;
|
|
924
1060
|
}
|
|
925
1061
|
});
|
|
926
1062
|
const home = os4.homedir();
|
|
927
1063
|
const dataDirs = {
|
|
928
|
-
claude:
|
|
929
|
-
codex:
|
|
930
|
-
opencode:
|
|
1064
|
+
claude: path6.join(home, ".claude"),
|
|
1065
|
+
codex: path6.join(home, ".codex"),
|
|
1066
|
+
opencode: path6.join(home, ".local", "share", "opencode")
|
|
931
1067
|
};
|
|
932
|
-
if (onPath ||
|
|
1068
|
+
if (onPath || fs7.existsSync(dataDirs[name] ?? "")) found.push(name);
|
|
933
1069
|
}
|
|
934
1070
|
return found;
|
|
935
1071
|
}
|
|
@@ -953,15 +1089,15 @@ function listProjects() {
|
|
|
953
1089
|
return loadConfig().projects;
|
|
954
1090
|
}
|
|
955
1091
|
function addProject(dir, name) {
|
|
956
|
-
const abs =
|
|
957
|
-
if (!
|
|
1092
|
+
const abs = path6.resolve(dir);
|
|
1093
|
+
if (!fs7.existsSync(abs) || !fs7.statSync(abs).isDirectory()) {
|
|
958
1094
|
throw new Error(`Not a directory: ${abs}`);
|
|
959
1095
|
}
|
|
960
1096
|
const existing = loadConfig().projects.find((p) => p.path === abs);
|
|
961
1097
|
if (existing) return existing;
|
|
962
1098
|
const project = {
|
|
963
1099
|
id: randomUUID2(),
|
|
964
|
-
name: name ||
|
|
1100
|
+
name: name || path6.basename(abs),
|
|
965
1101
|
path: abs,
|
|
966
1102
|
addedAt: Date.now()
|
|
967
1103
|
};
|
|
@@ -975,7 +1111,7 @@ function deleteProject(idOrNameOrPath) {
|
|
|
975
1111
|
updateConfig((cfg) => {
|
|
976
1112
|
const before = cfg.projects.length;
|
|
977
1113
|
cfg.projects = cfg.projects.filter(
|
|
978
|
-
(p) => p.id !== idOrNameOrPath && p.name !== idOrNameOrPath && p.path !==
|
|
1114
|
+
(p) => p.id !== idOrNameOrPath && p.name !== idOrNameOrPath && p.path !== path6.resolve(idOrNameOrPath)
|
|
979
1115
|
);
|
|
980
1116
|
removed = cfg.projects.length !== before;
|
|
981
1117
|
});
|
|
@@ -983,7 +1119,7 @@ function deleteProject(idOrNameOrPath) {
|
|
|
983
1119
|
}
|
|
984
1120
|
function resolveProject(idOrNameOrPath) {
|
|
985
1121
|
const cfg = loadConfig();
|
|
986
|
-
const abs =
|
|
1122
|
+
const abs = path6.resolve(idOrNameOrPath);
|
|
987
1123
|
return cfg.projects.find(
|
|
988
1124
|
(p) => p.id === idOrNameOrPath || p.name === idOrNameOrPath || p.path === abs
|
|
989
1125
|
) ?? null;
|
|
@@ -1073,24 +1209,24 @@ var init_registry = __esm({
|
|
|
1073
1209
|
});
|
|
1074
1210
|
|
|
1075
1211
|
// src/runtime/fsops.ts
|
|
1076
|
-
import
|
|
1077
|
-
import
|
|
1212
|
+
import fs8 from "fs";
|
|
1213
|
+
import path7 from "path";
|
|
1078
1214
|
import os5 from "os";
|
|
1079
1215
|
function expand(p) {
|
|
1080
1216
|
if (!p || p === "~") return HOME2;
|
|
1081
|
-
if (p.startsWith("~/")) return
|
|
1082
|
-
return
|
|
1217
|
+
if (p.startsWith("~/")) return path7.join(HOME2, p.slice(2));
|
|
1218
|
+
return path7.resolve(p);
|
|
1083
1219
|
}
|
|
1084
1220
|
function list2(dir) {
|
|
1085
1221
|
const abs = expand(dir);
|
|
1086
|
-
const dirents =
|
|
1222
|
+
const dirents = fs8.readdirSync(abs, { withFileTypes: true });
|
|
1087
1223
|
const entries = [];
|
|
1088
1224
|
for (const d of dirents) {
|
|
1089
|
-
const p =
|
|
1225
|
+
const p = path7.join(abs, d.name);
|
|
1090
1226
|
let size = 0;
|
|
1091
1227
|
let mtime = 0;
|
|
1092
1228
|
try {
|
|
1093
|
-
const st =
|
|
1229
|
+
const st = fs8.statSync(p);
|
|
1094
1230
|
size = st.size;
|
|
1095
1231
|
mtime = Math.floor(st.mtimeMs / 1e3);
|
|
1096
1232
|
} catch {
|
|
@@ -1103,7 +1239,7 @@ function list2(dir) {
|
|
|
1103
1239
|
if (a.type !== "dir" && b.type === "dir") return 1;
|
|
1104
1240
|
return a.name.localeCompare(b.name);
|
|
1105
1241
|
});
|
|
1106
|
-
const parent = abs === "/" ? null :
|
|
1242
|
+
const parent = abs === "/" ? null : path7.dirname(abs);
|
|
1107
1243
|
return { path: abs, parent, entries };
|
|
1108
1244
|
}
|
|
1109
1245
|
function looksBinary(buf) {
|
|
@@ -1111,14 +1247,14 @@ function looksBinary(buf) {
|
|
|
1111
1247
|
for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
|
|
1112
1248
|
return false;
|
|
1113
1249
|
}
|
|
1114
|
-
function
|
|
1250
|
+
function read2(file) {
|
|
1115
1251
|
const abs = expand(file);
|
|
1116
|
-
const st =
|
|
1117
|
-
const fd =
|
|
1252
|
+
const st = fs8.statSync(abs);
|
|
1253
|
+
const fd = fs8.openSync(abs, "r");
|
|
1118
1254
|
try {
|
|
1119
1255
|
const len = Math.min(st.size, READ_CAP);
|
|
1120
1256
|
const buf = Buffer.alloc(len);
|
|
1121
|
-
|
|
1257
|
+
fs8.readSync(fd, buf, 0, len, 0);
|
|
1122
1258
|
const binary = looksBinary(buf);
|
|
1123
1259
|
return {
|
|
1124
1260
|
path: abs,
|
|
@@ -1128,18 +1264,18 @@ function read(file) {
|
|
|
1128
1264
|
content: binary ? "" : buf.toString("utf8")
|
|
1129
1265
|
};
|
|
1130
1266
|
} finally {
|
|
1131
|
-
|
|
1267
|
+
fs8.closeSync(fd);
|
|
1132
1268
|
}
|
|
1133
1269
|
}
|
|
1134
|
-
function
|
|
1270
|
+
function write3(file, content) {
|
|
1135
1271
|
const abs = expand(file);
|
|
1136
|
-
|
|
1137
|
-
|
|
1272
|
+
fs8.mkdirSync(path7.dirname(abs), { recursive: true });
|
|
1273
|
+
fs8.writeFileSync(abs, content, "utf8");
|
|
1138
1274
|
return { path: abs, bytes: Buffer.byteLength(content, "utf8") };
|
|
1139
1275
|
}
|
|
1140
1276
|
function mkdir(dir) {
|
|
1141
1277
|
const abs = expand(dir);
|
|
1142
|
-
|
|
1278
|
+
fs8.mkdirSync(abs, { recursive: true });
|
|
1143
1279
|
return { path: abs };
|
|
1144
1280
|
}
|
|
1145
1281
|
var HOME2, READ_CAP;
|
|
@@ -1152,11 +1288,11 @@ var init_fsops = __esm({
|
|
|
1152
1288
|
});
|
|
1153
1289
|
|
|
1154
1290
|
// src/tailscale.ts
|
|
1155
|
-
import
|
|
1291
|
+
import fs9 from "fs";
|
|
1156
1292
|
import { spawn as spawn5 } from "child_process";
|
|
1157
1293
|
function tsBin() {
|
|
1158
1294
|
const macPath = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
|
|
1159
|
-
if (
|
|
1295
|
+
if (fs9.existsSync(macPath)) return macPath;
|
|
1160
1296
|
return process.env.TAILSCALE_BIN || "tailscale";
|
|
1161
1297
|
}
|
|
1162
1298
|
function runTs(args, timeoutMs = 6e4) {
|
|
@@ -1228,7 +1364,7 @@ async function status(port2) {
|
|
|
1228
1364
|
const dnsName = typeof self.DNSName === "string" ? self.DNSName.replace(/\.$/, "") : null;
|
|
1229
1365
|
const online = self.Online === true;
|
|
1230
1366
|
const backendState = res.data?.BackendState ?? "Unknown";
|
|
1231
|
-
const baseUrl = ipv4 ? `http://${ipv4}:${port2}` : null;
|
|
1367
|
+
const baseUrl = ipv4 && backendState === "Running" ? `http://${ipv4}:${port2}` : null;
|
|
1232
1368
|
const sakura = {
|
|
1233
1369
|
online,
|
|
1234
1370
|
backendState,
|
|
@@ -1308,7 +1444,7 @@ var init_hub = __esm({
|
|
|
1308
1444
|
});
|
|
1309
1445
|
|
|
1310
1446
|
// src/daemon/stream.ts
|
|
1311
|
-
async function runTurn(sessionId, agent, prompt) {
|
|
1447
|
+
async function runTurn(sessionId, agent, prompt, images) {
|
|
1312
1448
|
const key = sessionKey(agent, sessionId);
|
|
1313
1449
|
const emit = (e) => hub.broadcast(key, e);
|
|
1314
1450
|
let lastEmitted = "\0";
|
|
@@ -1348,9 +1484,10 @@ async function runTurn(sessionId, agent, prompt) {
|
|
|
1348
1484
|
} catch {
|
|
1349
1485
|
}
|
|
1350
1486
|
}, POLL_MS);
|
|
1351
|
-
const result = await chat(sessionId, prompt, agent, onData);
|
|
1487
|
+
const result = await chat(sessionId, prompt, agent, onData, images);
|
|
1352
1488
|
clearInterval(timer);
|
|
1353
1489
|
emit({ type: "done", sessionId, agent, ok: result.ok, error: result.error });
|
|
1490
|
+
notifyTurnEnd(agent, sessionId, result.ok, result.error, stdoutAcc);
|
|
1354
1491
|
return { ok: result.ok, error: result.error };
|
|
1355
1492
|
}
|
|
1356
1493
|
var ANSI, stripAnsi, LIVE_ID, POLL_MS;
|
|
@@ -1359,6 +1496,7 @@ var init_stream = __esm({
|
|
|
1359
1496
|
"use strict";
|
|
1360
1497
|
init_sessions();
|
|
1361
1498
|
init_hub();
|
|
1499
|
+
init_push();
|
|
1362
1500
|
ANSI = /\[[0-9;]*[a-zA-Z]/g;
|
|
1363
1501
|
stripAnsi = (s) => s.replace(ANSI, "");
|
|
1364
1502
|
LIVE_ID = "__live__";
|
|
@@ -1454,13 +1592,15 @@ var init_terminal = __esm({
|
|
|
1454
1592
|
|
|
1455
1593
|
// src/daemon/server.ts
|
|
1456
1594
|
import http from "http";
|
|
1457
|
-
import
|
|
1595
|
+
import fs10 from "fs";
|
|
1596
|
+
import os7 from "os";
|
|
1597
|
+
import path8 from "path";
|
|
1458
1598
|
import { URL } from "url";
|
|
1459
1599
|
import { WebSocketServer } from "ws";
|
|
1460
|
-
function add(method,
|
|
1600
|
+
function add(method, path9, handler) {
|
|
1461
1601
|
const keys = [];
|
|
1462
1602
|
const pattern = new RegExp(
|
|
1463
|
-
"^" +
|
|
1603
|
+
"^" + path9.replace(/:[^/]+/g, (m) => {
|
|
1464
1604
|
keys.push(m.slice(1));
|
|
1465
1605
|
return "([^/]+)";
|
|
1466
1606
|
}) + "/?$"
|
|
@@ -1473,6 +1613,26 @@ function ok(data = null) {
|
|
|
1473
1613
|
function fail(code, stderr, data = null) {
|
|
1474
1614
|
return { ok: false, data, stdout: "", stderr, code };
|
|
1475
1615
|
}
|
|
1616
|
+
function saveImages(sessionId, images) {
|
|
1617
|
+
const dir = path8.join(os7.tmpdir(), "sakura-uploads", sessionId.slice(0, 24));
|
|
1618
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
1619
|
+
const paths = [];
|
|
1620
|
+
images.forEach((img, i) => {
|
|
1621
|
+
const raw = String(img?.data ?? "");
|
|
1622
|
+
const m = raw.match(/^data:([^;]+);base64,(.*)$/s);
|
|
1623
|
+
if (!m) {
|
|
1624
|
+
log.info(`saveImages: image ${i} did not match data-url (len=${raw.length}, prefix="${raw.slice(0, 40)}")`);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const ext = EXT_BY_MIME[m[1]] ?? "png";
|
|
1628
|
+
const safe = img.name && /\.[a-z0-9]+$/i.test(img.name) ? img.name.replace(/[^\w.\-]/g, "_") : `image-${Date.now()}-${i + 1}.${ext}`;
|
|
1629
|
+
const file = path8.join(dir, safe);
|
|
1630
|
+
fs10.writeFileSync(file, Buffer.from(m[2], "base64"));
|
|
1631
|
+
log.info(`saveImages: wrote ${file} (${m[2].length} b64 chars)`);
|
|
1632
|
+
paths.push(file);
|
|
1633
|
+
});
|
|
1634
|
+
return paths;
|
|
1635
|
+
}
|
|
1476
1636
|
function kvToObj(env) {
|
|
1477
1637
|
if (!Array.isArray(env)) return void 0;
|
|
1478
1638
|
const out = {};
|
|
@@ -1595,7 +1755,7 @@ function createServer(port2) {
|
|
|
1595
1755
|
function setVersion(v) {
|
|
1596
1756
|
VERSION2 = v;
|
|
1597
1757
|
}
|
|
1598
|
-
var routes, VERSION2;
|
|
1758
|
+
var routes, EXT_BY_MIME, VERSION2;
|
|
1599
1759
|
var init_server = __esm({
|
|
1600
1760
|
"src/daemon/server.ts"() {
|
|
1601
1761
|
"use strict";
|
|
@@ -1603,6 +1763,7 @@ var init_server = __esm({
|
|
|
1603
1763
|
init_config();
|
|
1604
1764
|
init_logger();
|
|
1605
1765
|
init_sessions();
|
|
1766
|
+
init_push();
|
|
1606
1767
|
init_registry();
|
|
1607
1768
|
init_fsops();
|
|
1608
1769
|
init_tailscale();
|
|
@@ -1611,8 +1772,18 @@ var init_server = __esm({
|
|
|
1611
1772
|
init_stream();
|
|
1612
1773
|
init_terminal();
|
|
1613
1774
|
routes = [];
|
|
1614
|
-
|
|
1615
|
-
|
|
1775
|
+
EXT_BY_MIME = {
|
|
1776
|
+
"image/png": "png",
|
|
1777
|
+
"image/jpeg": "jpg",
|
|
1778
|
+
"image/jpg": "jpg",
|
|
1779
|
+
"image/gif": "gif",
|
|
1780
|
+
"image/webp": "webp",
|
|
1781
|
+
"image/heic": "heic"
|
|
1782
|
+
};
|
|
1783
|
+
add("GET", "/sessions", async ({ url }) => {
|
|
1784
|
+
const archived = url.searchParams.get("archived") === "true";
|
|
1785
|
+
const all2 = url.searchParams.get("all") === "true";
|
|
1786
|
+
const list3 = await list({ archived, all: all2 });
|
|
1616
1787
|
return { sessions: list3 };
|
|
1617
1788
|
});
|
|
1618
1789
|
add("GET", "/sessions/:id/messages", async ({ params, url }) => {
|
|
@@ -1623,9 +1794,28 @@ var init_server = __esm({
|
|
|
1623
1794
|
add("POST", "/sessions/:id/send", async ({ params, body }) => {
|
|
1624
1795
|
const agent = body?.agent || await detectAgent(params.id);
|
|
1625
1796
|
if (!agent) return { ok: false, error: "unknown agent for session" };
|
|
1626
|
-
const
|
|
1797
|
+
const message = String(body?.message ?? "");
|
|
1798
|
+
const incoming = Array.isArray(body?.images) ? body.images : [];
|
|
1799
|
+
log.info(`send: agent=${agent} msgLen=${message.length} bodyKeys=[${Object.keys(body ?? {}).join(",")}] images=${incoming.length}`);
|
|
1800
|
+
const imagePaths = incoming.length ? saveImages(params.id, incoming) : [];
|
|
1801
|
+
if (imagePaths.length) log.info(`send: attached ${imagePaths.length} image(s) to ${agent} prompt`);
|
|
1802
|
+
const r = await runTurn(params.id, agent, message, imagePaths);
|
|
1627
1803
|
return { ok: r.ok, error: r.error };
|
|
1628
1804
|
});
|
|
1805
|
+
add("POST", "/sessions/:id/rename", ({ params, body }) => {
|
|
1806
|
+
const title = String(body?.title ?? "").trim();
|
|
1807
|
+
if (!title) return { ok: false, error: "title is required" };
|
|
1808
|
+
rename(params.id, title);
|
|
1809
|
+
return { ok: true };
|
|
1810
|
+
});
|
|
1811
|
+
add("POST", "/sessions/:id/archive", ({ params }) => {
|
|
1812
|
+
setArchived2(params.id, true);
|
|
1813
|
+
return { ok: true };
|
|
1814
|
+
});
|
|
1815
|
+
add("POST", "/sessions/:id/restore", ({ params }) => {
|
|
1816
|
+
setArchived2(params.id, false);
|
|
1817
|
+
return { ok: true };
|
|
1818
|
+
});
|
|
1629
1819
|
add(
|
|
1630
1820
|
"GET",
|
|
1631
1821
|
"/sakura/health",
|
|
@@ -1648,7 +1838,7 @@ var init_server = __esm({
|
|
|
1648
1838
|
case "logs": {
|
|
1649
1839
|
let logs2 = "";
|
|
1650
1840
|
try {
|
|
1651
|
-
const lines =
|
|
1841
|
+
const lines = fs10.readFileSync(LOG_PATH, "utf8").split("\n");
|
|
1652
1842
|
logs2 = lines.slice(-200).join("\n");
|
|
1653
1843
|
} catch {
|
|
1654
1844
|
}
|
|
@@ -1681,6 +1871,16 @@ var init_server = __esm({
|
|
|
1681
1871
|
({ url }) => ok(listMachines(url.searchParams.get("onlineOnly") === "true"))
|
|
1682
1872
|
);
|
|
1683
1873
|
add("GET", "/sakura/projects", () => ok(listProjects()));
|
|
1874
|
+
add("POST", "/sakura/push-token", ({ body }) => {
|
|
1875
|
+
const token = String(body?.token ?? "");
|
|
1876
|
+
if (!token) return fail("bad_request", "token required");
|
|
1877
|
+
addPushToken(token);
|
|
1878
|
+
return ok({ registered: true });
|
|
1879
|
+
});
|
|
1880
|
+
add("POST", "/sakura/push-test", async () => {
|
|
1881
|
+
await sendPush("\u{1F338} Sakura says hi", "Push is locked in. Your agents will ping you here.", { kind: "test" });
|
|
1882
|
+
return ok({ sent: true });
|
|
1883
|
+
});
|
|
1684
1884
|
add("GET", "/sakura/sessions", async ({ url }) => {
|
|
1685
1885
|
const limit = Number(url.searchParams.get("limit")) || void 0;
|
|
1686
1886
|
const agent = url.searchParams.get("agent") || void 0;
|
|
@@ -1698,10 +1898,10 @@ var init_server = __esm({
|
|
|
1698
1898
|
return s ? ok(s) : fail("nonzero", "session not found");
|
|
1699
1899
|
});
|
|
1700
1900
|
add("GET", "/sakura/sessions/:id/history", async ({ params, url }) => {
|
|
1701
|
-
const
|
|
1901
|
+
const all2 = url.searchParams.get("all") === "true";
|
|
1702
1902
|
const limit = Number(url.searchParams.get("limit")) || void 0;
|
|
1703
1903
|
const reverse = url.searchParams.get("reverse") === "true";
|
|
1704
|
-
return ok(await history(params.id, { all, limit, reverse }));
|
|
1904
|
+
return ok(await history(params.id, { all: all2, limit, reverse }));
|
|
1705
1905
|
});
|
|
1706
1906
|
add("POST", "/sakura/sessions/:id/chat", async ({ params, body }) => {
|
|
1707
1907
|
const agent = body?.agent || await detectAgent(params.id);
|
|
@@ -1714,22 +1914,27 @@ var init_server = __esm({
|
|
|
1714
1914
|
"/sakura/sessions/:id/cancel",
|
|
1715
1915
|
() => fail("nonzero", "cancel is not supported for local file-backed sessions")
|
|
1716
1916
|
);
|
|
1717
|
-
add(
|
|
1718
|
-
"
|
|
1719
|
-
"
|
|
1720
|
-
(
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1917
|
+
add("POST", "/sakura/sessions/:id/rename", ({ params, body }) => {
|
|
1918
|
+
const title = String(body?.title ?? "").trim();
|
|
1919
|
+
if (!title) return fail("nonzero", "title is required");
|
|
1920
|
+
rename(params.id, title);
|
|
1921
|
+
return ok({ id: params.id, title });
|
|
1922
|
+
});
|
|
1923
|
+
add("POST", "/sakura/sessions/:id/:action", ({ params }) => {
|
|
1924
|
+
switch (params.action) {
|
|
1925
|
+
case "archive":
|
|
1926
|
+
setArchived2(params.id, true);
|
|
1927
|
+
return ok({ id: params.id, archived: true });
|
|
1928
|
+
case "restore":
|
|
1929
|
+
setArchived2(params.id, false);
|
|
1930
|
+
return ok({ id: params.id, archived: false });
|
|
1931
|
+
default:
|
|
1932
|
+
return fail(
|
|
1933
|
+
"nonzero",
|
|
1934
|
+
`${params.action} is disabled for local sessions to avoid deleting agent history`
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1733
1938
|
add("GET", "/sakura/agent-configs", () => ok(listAgentConfigs()));
|
|
1734
1939
|
add("POST", "/sakura/agent-configs", ({ body }) => {
|
|
1735
1940
|
try {
|
|
@@ -1788,7 +1993,7 @@ var init_server = __esm({
|
|
|
1788
1993
|
const p = url.searchParams.get("path");
|
|
1789
1994
|
if (!p) return fail("nonzero", "path required");
|
|
1790
1995
|
try {
|
|
1791
|
-
return ok(
|
|
1996
|
+
return ok(read2(p));
|
|
1792
1997
|
} catch (e) {
|
|
1793
1998
|
return fail("nonzero", e.message);
|
|
1794
1999
|
}
|
|
@@ -1796,7 +2001,7 @@ var init_server = __esm({
|
|
|
1796
2001
|
add("POST", "/sakura/fs/write", ({ body }) => {
|
|
1797
2002
|
if (!body?.path) return fail("nonzero", "path required");
|
|
1798
2003
|
try {
|
|
1799
|
-
return ok(
|
|
2004
|
+
return ok(write3(body.path, String(body.content ?? "")));
|
|
1800
2005
|
} catch (e) {
|
|
1801
2006
|
return fail("nonzero", e.message);
|
|
1802
2007
|
}
|
|
@@ -1820,11 +2025,11 @@ var init_server = __esm({
|
|
|
1820
2025
|
});
|
|
1821
2026
|
|
|
1822
2027
|
// src/daemon/manager.ts
|
|
1823
|
-
import
|
|
2028
|
+
import fs11 from "fs";
|
|
1824
2029
|
import { spawn as spawn7 } from "child_process";
|
|
1825
2030
|
function readDaemonInfo() {
|
|
1826
2031
|
try {
|
|
1827
|
-
return JSON.parse(
|
|
2032
|
+
return JSON.parse(fs11.readFileSync(DAEMON_PATH, "utf8"));
|
|
1828
2033
|
} catch {
|
|
1829
2034
|
return null;
|
|
1830
2035
|
}
|
|
@@ -1841,8 +2046,8 @@ function running() {
|
|
|
1841
2046
|
const info2 = readDaemonInfo();
|
|
1842
2047
|
if (info2 && pidAlive(info2.pid)) return info2;
|
|
1843
2048
|
if (info2) {
|
|
1844
|
-
|
|
1845
|
-
|
|
2049
|
+
fs11.rmSync(DAEMON_PATH, { force: true });
|
|
2050
|
+
fs11.rmSync(PID_PATH, { force: true });
|
|
1846
2051
|
}
|
|
1847
2052
|
return null;
|
|
1848
2053
|
}
|
|
@@ -1865,8 +2070,8 @@ async function runServer(version) {
|
|
|
1865
2070
|
url: `http://${host}:${port2}`,
|
|
1866
2071
|
startedAt: Date.now()
|
|
1867
2072
|
};
|
|
1868
|
-
|
|
1869
|
-
|
|
2073
|
+
fs11.writeFileSync(DAEMON_PATH, JSON.stringify(info2, null, 2));
|
|
2074
|
+
fs11.writeFileSync(PID_PATH, String(process.pid));
|
|
1870
2075
|
const tailnet = await discoverBaseUrl(port2).catch(() => null);
|
|
1871
2076
|
log.info(`sakuraai runtime listening on http://0.0.0.0:${port2}`);
|
|
1872
2077
|
if (tailnet) log.info(`reachable over Tailscale at ${tailnet}`);
|
|
@@ -1874,8 +2079,8 @@ async function runServer(version) {
|
|
|
1874
2079
|
const shutdown = () => {
|
|
1875
2080
|
log.info("shutting down");
|
|
1876
2081
|
server.close();
|
|
1877
|
-
|
|
1878
|
-
|
|
2082
|
+
fs11.rmSync(DAEMON_PATH, { force: true });
|
|
2083
|
+
fs11.rmSync(PID_PATH, { force: true });
|
|
1879
2084
|
process.exit(0);
|
|
1880
2085
|
};
|
|
1881
2086
|
process.on("SIGINT", shutdown);
|
|
@@ -1886,8 +2091,8 @@ function start() {
|
|
|
1886
2091
|
if (existing) return existing;
|
|
1887
2092
|
ensureSakuraDirs();
|
|
1888
2093
|
const entry = process.argv[1] ?? "";
|
|
1889
|
-
const out =
|
|
1890
|
-
const err =
|
|
2094
|
+
const out = fs11.openSync(LOG_PATH, "a");
|
|
2095
|
+
const err = fs11.openSync(LOG_PATH, "a");
|
|
1891
2096
|
const child = spawn7(process.execPath, [entry, "__run-daemon"], {
|
|
1892
2097
|
detached: true,
|
|
1893
2098
|
stdio: ["ignore", out, err],
|
|
@@ -1909,8 +2114,8 @@ function stop() {
|
|
|
1909
2114
|
process.kill(info2.pid, "SIGTERM");
|
|
1910
2115
|
} catch {
|
|
1911
2116
|
}
|
|
1912
|
-
|
|
1913
|
-
|
|
2117
|
+
fs11.rmSync(DAEMON_PATH, { force: true });
|
|
2118
|
+
fs11.rmSync(PID_PATH, { force: true });
|
|
1914
2119
|
return true;
|
|
1915
2120
|
}
|
|
1916
2121
|
function restart() {
|
|
@@ -1920,8 +2125,8 @@ function restart() {
|
|
|
1920
2125
|
}
|
|
1921
2126
|
function logs(lines = 50) {
|
|
1922
2127
|
try {
|
|
1923
|
-
const
|
|
1924
|
-
return
|
|
2128
|
+
const all2 = fs11.readFileSync(LOG_PATH, "utf8").split("\n");
|
|
2129
|
+
return all2.slice(-lines).join("\n");
|
|
1925
2130
|
} catch {
|
|
1926
2131
|
return "";
|
|
1927
2132
|
}
|
|
@@ -1937,7 +2142,7 @@ var init_manager = __esm({
|
|
|
1937
2142
|
});
|
|
1938
2143
|
|
|
1939
2144
|
// src/pairing.ts
|
|
1940
|
-
import
|
|
2145
|
+
import os8 from "os";
|
|
1941
2146
|
import qrcode from "qrcode-terminal";
|
|
1942
2147
|
async function buildPairing() {
|
|
1943
2148
|
const token = requireToken();
|
|
@@ -1954,7 +2159,7 @@ function renderQr(deepLink) {
|
|
|
1954
2159
|
});
|
|
1955
2160
|
}
|
|
1956
2161
|
function localIp() {
|
|
1957
|
-
const nets =
|
|
2162
|
+
const nets = os8.networkInterfaces();
|
|
1958
2163
|
for (const name of Object.keys(nets)) {
|
|
1959
2164
|
for (const net of nets[name] ?? []) {
|
|
1960
2165
|
if (net.family === "IPv4" && !net.internal) return net.address;
|
|
@@ -2108,7 +2313,7 @@ function registerRuntime(program, version) {
|
|
|
2108
2313
|
// src/commands/session.ts
|
|
2109
2314
|
init_sessions();
|
|
2110
2315
|
init_output();
|
|
2111
|
-
import
|
|
2316
|
+
import fs12 from "fs";
|
|
2112
2317
|
function resolveSessionId(arg) {
|
|
2113
2318
|
const id = arg || process.env.SAKURA_SESSION_ID;
|
|
2114
2319
|
if (!id) die("Provide a sessionId (positional or via SAKURA_SESSION_ID).");
|
|
@@ -2117,7 +2322,7 @@ function resolveSessionId(arg) {
|
|
|
2117
2322
|
async function resolvePrompt(positional, opts) {
|
|
2118
2323
|
if (positional) return positional;
|
|
2119
2324
|
if (opts.prompt) return opts.prompt;
|
|
2120
|
-
if (opts.promptFile) return
|
|
2325
|
+
if (opts.promptFile) return fs12.readFileSync(opts.promptFile, "utf8");
|
|
2121
2326
|
if (!process.stdin.isTTY) {
|
|
2122
2327
|
const chunks = [];
|
|
2123
2328
|
for await (const c of process.stdin) chunks.push(c);
|
|
@@ -2201,16 +2406,24 @@ function registerSession(program) {
|
|
|
2201
2406
|
resolveSessionId(sessionId);
|
|
2202
2407
|
die("Cancel is not supported for local file-backed sessions.", 2);
|
|
2203
2408
|
});
|
|
2204
|
-
s.command("rename [sessionId] [title]").description("Rename a session").option("--title <title>", "new title").action((sessionId) => {
|
|
2205
|
-
resolveSessionId(sessionId);
|
|
2206
|
-
|
|
2409
|
+
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) => {
|
|
2410
|
+
const id = resolveSessionId(sessionId);
|
|
2411
|
+
const next = String(opts?.title ?? title ?? "").trim();
|
|
2412
|
+
if (!next) die("Provide a new title (positional or --title).");
|
|
2413
|
+
rename(id, next);
|
|
2414
|
+
success(`Renamed session to "${next}".`);
|
|
2207
2415
|
});
|
|
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
|
-
|
|
2416
|
+
for (const action of ["archive", "restore"]) {
|
|
2417
|
+
s.command(`${action} [sessionId]`).description(`${action[0].toUpperCase()}${action.slice(1)} a session (sakura overlay)`).action((sessionId) => {
|
|
2418
|
+
const id = resolveSessionId(sessionId);
|
|
2419
|
+
setArchived2(id, action === "archive");
|
|
2420
|
+
success(`${action === "archive" ? "Archived" : "Restored"} session ${id.slice(0, 12)}.`);
|
|
2212
2421
|
});
|
|
2213
2422
|
}
|
|
2423
|
+
s.command("delete [sessionId]").description("Delete a session (disabled for local sessions)").action((sessionId) => {
|
|
2424
|
+
resolveSessionId(sessionId);
|
|
2425
|
+
die("delete is disabled for local sessions to avoid deleting agent history.", 2);
|
|
2426
|
+
});
|
|
2214
2427
|
}
|
|
2215
2428
|
function collect(value, prev) {
|
|
2216
2429
|
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.8",
|
|
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",
|