@shmulikdav/solix 1.1.1 → 1.2.0
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
CHANGED
|
@@ -671,11 +671,231 @@ function install(opts = {}) {
|
|
|
671
671
|
console.log(`[solix] merged hooks into ${CLAUDE_SETTINGS}`);
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
-
// src/
|
|
674
|
+
// src/install-shim.ts
|
|
675
|
+
import {
|
|
676
|
+
appendFileSync,
|
|
677
|
+
existsSync as existsSync4,
|
|
678
|
+
readFileSync as readFileSync3,
|
|
679
|
+
writeFileSync as writeFileSync3
|
|
680
|
+
} from "fs";
|
|
681
|
+
import { homedir as homedir3 } from "os";
|
|
682
|
+
import { basename, join as join5 } from "path";
|
|
683
|
+
var BLOCK_START = "# >>> solix shim >>>";
|
|
684
|
+
var BLOCK_END = "# <<< solix shim <<<";
|
|
685
|
+
function detectShellRcPath() {
|
|
686
|
+
const shell = process.env.SHELL ?? "";
|
|
687
|
+
const home = homedir3();
|
|
688
|
+
if (shell.endsWith("zsh") || existsSync4(join5(home, ".zshrc"))) {
|
|
689
|
+
return join5(home, ".zshrc");
|
|
690
|
+
}
|
|
691
|
+
if (shell.endsWith("bash") || existsSync4(join5(home, ".bashrc"))) {
|
|
692
|
+
return join5(home, ".bashrc");
|
|
693
|
+
}
|
|
694
|
+
if (existsSync4(join5(home, ".bash_profile"))) {
|
|
695
|
+
return join5(home, ".bash_profile");
|
|
696
|
+
}
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
function readRc(rcPath) {
|
|
700
|
+
return existsSync4(rcPath) ? readFileSync3(rcPath, "utf8") : "";
|
|
701
|
+
}
|
|
702
|
+
function blockText() {
|
|
703
|
+
return [
|
|
704
|
+
BLOCK_START,
|
|
705
|
+
"# Aliases `claude` to `solix run` so every claude session is wrapped",
|
|
706
|
+
"# by Solix. The Solix UI composer becomes write-enabled for these.",
|
|
707
|
+
"# Remove with `solix uninstall` (or delete this block manually).",
|
|
708
|
+
"alias claude='solix run'",
|
|
709
|
+
BLOCK_END,
|
|
710
|
+
""
|
|
711
|
+
].join("\n");
|
|
712
|
+
}
|
|
713
|
+
function installShim() {
|
|
714
|
+
const rcPath = detectShellRcPath();
|
|
715
|
+
if (!rcPath) {
|
|
716
|
+
console.error(
|
|
717
|
+
"[solix] couldn't find a shell rc file (.zshrc / .bashrc). Add `alias claude='solix run'` manually to your shell config."
|
|
718
|
+
);
|
|
719
|
+
process.exitCode = 1;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const current = readRc(rcPath);
|
|
723
|
+
if (current.includes(BLOCK_START)) {
|
|
724
|
+
console.log(`[solix] shim already installed in ${rcPath}`);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const prefix = current.endsWith("\n") || current.length === 0 ? "" : "\n";
|
|
728
|
+
appendFileSync(rcPath, prefix + "\n" + blockText());
|
|
729
|
+
console.log(`[solix] shim added to ${basename(rcPath)}.`);
|
|
730
|
+
console.log(
|
|
731
|
+
`[solix] run \`exec $SHELL\` (or open a new terminal) to activate, then \`claude\` will route through \`solix run\`.`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
function uninstallShim() {
|
|
735
|
+
const rcPath = detectShellRcPath();
|
|
736
|
+
if (!rcPath) return false;
|
|
737
|
+
const current = readRc(rcPath);
|
|
738
|
+
if (!current.includes(BLOCK_START)) return false;
|
|
739
|
+
const startIdx = current.indexOf(BLOCK_START);
|
|
740
|
+
const endIdx = current.indexOf(BLOCK_END);
|
|
741
|
+
if (endIdx < 0) return false;
|
|
742
|
+
const before = current.slice(0, startIdx).replace(/\n+$/, "\n");
|
|
743
|
+
const after = current.slice(endIdx + BLOCK_END.length).replace(/^\n+/, "");
|
|
744
|
+
writeFileSync3(rcPath, before + after);
|
|
745
|
+
console.log(`[solix] shim removed from ${basename(rcPath)}.`);
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/run.ts
|
|
750
|
+
import { createServer as createUnixServer } from "net";
|
|
751
|
+
import { mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
752
|
+
import { homedir as homedir4 } from "os";
|
|
753
|
+
import { join as join6 } from "path";
|
|
754
|
+
import { nanoid } from "nanoid";
|
|
675
755
|
var PORT4 = process.env.SOLIX_PORT ?? "4242";
|
|
676
756
|
var BASE4 = `http://127.0.0.1:${PORT4}`;
|
|
757
|
+
async function registerWithServer(payload) {
|
|
758
|
+
try {
|
|
759
|
+
const res = await fetch(`${BASE4}/api/wrappers/register`, {
|
|
760
|
+
method: "POST",
|
|
761
|
+
headers: { "Content-Type": "application/json" },
|
|
762
|
+
body: JSON.stringify(payload),
|
|
763
|
+
signal: AbortSignal.timeout(800)
|
|
764
|
+
});
|
|
765
|
+
return res.ok;
|
|
766
|
+
} catch {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
async function unregisterFromServer(wrapperId) {
|
|
771
|
+
try {
|
|
772
|
+
await fetch(
|
|
773
|
+
`${BASE4}/api/wrappers/${encodeURIComponent(wrapperId)}/unregister`,
|
|
774
|
+
{ method: "POST", signal: AbortSignal.timeout(800) }
|
|
775
|
+
);
|
|
776
|
+
} catch {
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
async function runWrapped(args) {
|
|
780
|
+
let pty;
|
|
781
|
+
try {
|
|
782
|
+
pty = await import("node-pty");
|
|
783
|
+
} catch (err) {
|
|
784
|
+
console.error(
|
|
785
|
+
"[solix run] node-pty failed to load. Bidirectional chat needs a working PTY; on most platforms a clean `pnpm install` (or `npm rebuild`) fixes it."
|
|
786
|
+
);
|
|
787
|
+
console.error(`[solix run] underlying error: ${err.message}`);
|
|
788
|
+
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
const wrapperId = nanoid(10);
|
|
791
|
+
const sockDir = join6(homedir4(), ".solix", "wrappers");
|
|
792
|
+
mkdirSync2(sockDir, { recursive: true });
|
|
793
|
+
const socketPath = join6(sockDir, `${wrapperId}.sock`);
|
|
794
|
+
const cwd = process.cwd();
|
|
795
|
+
const registered = await registerWithServer({ wrapperId, socketPath, cwd });
|
|
796
|
+
const cols = process.stdout.columns ?? 80;
|
|
797
|
+
const rows = process.stdout.rows ?? 24;
|
|
798
|
+
const term = pty.spawn("claude", args, {
|
|
799
|
+
name: process.env.TERM ?? "xterm-256color",
|
|
800
|
+
cols,
|
|
801
|
+
rows,
|
|
802
|
+
cwd,
|
|
803
|
+
env: { ...process.env, SOLIX_WRAPPER_ID: wrapperId }
|
|
804
|
+
});
|
|
805
|
+
const wasRaw = Boolean(process.stdin.isTTY);
|
|
806
|
+
if (wasRaw) process.stdin.setRawMode?.(true);
|
|
807
|
+
process.stdin.resume();
|
|
808
|
+
const onStdin = (chunk) => {
|
|
809
|
+
term.write(chunk.toString("utf8"));
|
|
810
|
+
};
|
|
811
|
+
process.stdin.on("data", onStdin);
|
|
812
|
+
term.onData((d) => {
|
|
813
|
+
process.stdout.write(d);
|
|
814
|
+
});
|
|
815
|
+
const onResize = () => {
|
|
816
|
+
term.resize(process.stdout.columns ?? 80, process.stdout.rows ?? 24);
|
|
817
|
+
};
|
|
818
|
+
process.stdout.on("resize", onResize);
|
|
819
|
+
const sockServer = createUnixServer((conn) => {
|
|
820
|
+
let buf = "";
|
|
821
|
+
conn.setEncoding("utf8");
|
|
822
|
+
conn.on("data", (chunk) => {
|
|
823
|
+
buf += chunk;
|
|
824
|
+
let i = buf.indexOf("\n");
|
|
825
|
+
while (i >= 0) {
|
|
826
|
+
const line = buf.slice(0, i).trim();
|
|
827
|
+
buf = buf.slice(i + 1);
|
|
828
|
+
if (line) {
|
|
829
|
+
try {
|
|
830
|
+
const msg = JSON.parse(line);
|
|
831
|
+
if (msg.type === "send_prompt" && typeof msg.text === "string") {
|
|
832
|
+
term.write(msg.text + "\r");
|
|
833
|
+
}
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
i = buf.indexOf("\n");
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
conn.on("error", () => {
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
sockServer.on("error", (err) => {
|
|
844
|
+
console.error(`[solix run] socket error: ${err.message}`);
|
|
845
|
+
});
|
|
846
|
+
sockServer.listen(socketPath);
|
|
847
|
+
if (registered) {
|
|
848
|
+
process.stderr.write(
|
|
849
|
+
`[solix run] wrapped \u2014 UI prompts to this session will land here. Don't type a prompt while the UI is sending one.
|
|
850
|
+
`
|
|
851
|
+
);
|
|
852
|
+
} else {
|
|
853
|
+
process.stderr.write(
|
|
854
|
+
`[solix run] note: Solix server not reachable at ${BASE4}; claude will run normally, but the UI composer won't be active.
|
|
855
|
+
`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
let cleaned = false;
|
|
859
|
+
const cleanup = async (exitCode = 0) => {
|
|
860
|
+
if (cleaned) return;
|
|
861
|
+
cleaned = true;
|
|
862
|
+
try {
|
|
863
|
+
sockServer.close();
|
|
864
|
+
} catch {
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
unlinkSync(socketPath);
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
if (registered) await unregisterFromServer(wrapperId);
|
|
871
|
+
process.stdin.removeListener("data", onStdin);
|
|
872
|
+
process.stdout.removeListener("resize", onResize);
|
|
873
|
+
if (wasRaw) {
|
|
874
|
+
try {
|
|
875
|
+
process.stdin.setRawMode?.(false);
|
|
876
|
+
} catch {
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
process.exit(exitCode);
|
|
880
|
+
};
|
|
881
|
+
term.onExit(({ exitCode }) => {
|
|
882
|
+
void cleanup(exitCode ?? 0);
|
|
883
|
+
});
|
|
884
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
885
|
+
process.on(sig, () => {
|
|
886
|
+
try {
|
|
887
|
+
term.kill();
|
|
888
|
+
} catch {
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/skills.ts
|
|
895
|
+
var PORT5 = process.env.SOLIX_PORT ?? "4242";
|
|
896
|
+
var BASE5 = `http://127.0.0.1:${PORT5}`;
|
|
677
897
|
async function api3(path, init) {
|
|
678
|
-
const res = await fetch(`${
|
|
898
|
+
const res = await fetch(`${BASE5}${path}`, init);
|
|
679
899
|
if (!res.ok) {
|
|
680
900
|
const text = await res.text().catch(() => "");
|
|
681
901
|
throw new Error(`HTTP ${res.status} on ${path}: ${text}`);
|
|
@@ -699,7 +919,7 @@ async function listSkillsCmd() {
|
|
|
699
919
|
);
|
|
700
920
|
}
|
|
701
921
|
} catch (err) {
|
|
702
|
-
console.error(`[solix] could not reach server at ${
|
|
922
|
+
console.error(`[solix] could not reach server at ${BASE5}: ${String(err)}`);
|
|
703
923
|
process.exitCode = 1;
|
|
704
924
|
}
|
|
705
925
|
}
|
|
@@ -765,16 +985,16 @@ var Broadcaster = class {
|
|
|
765
985
|
import Database from "better-sqlite3";
|
|
766
986
|
|
|
767
987
|
// ../server/src/paths.ts
|
|
768
|
-
import { homedir as
|
|
769
|
-
import { join as
|
|
770
|
-
import { mkdirSync as
|
|
771
|
-
var SOLIX_HOME2 = process.env.SOLIX_HOME ??
|
|
772
|
-
var DB_PATH =
|
|
773
|
-
var HOOKS_DIR2 =
|
|
774
|
-
var LOG_PATH =
|
|
988
|
+
import { homedir as homedir5 } from "os";
|
|
989
|
+
import { join as join7 } from "path";
|
|
990
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
991
|
+
var SOLIX_HOME2 = process.env.SOLIX_HOME ?? join7(homedir5(), ".solix");
|
|
992
|
+
var DB_PATH = join7(SOLIX_HOME2, "solix.db");
|
|
993
|
+
var HOOKS_DIR2 = join7(SOLIX_HOME2, "hooks");
|
|
994
|
+
var LOG_PATH = join7(SOLIX_HOME2, "solix.log");
|
|
775
995
|
function ensureSolixHome() {
|
|
776
|
-
|
|
777
|
-
|
|
996
|
+
mkdirSync3(SOLIX_HOME2, { recursive: true });
|
|
997
|
+
mkdirSync3(HOOKS_DIR2, { recursive: true });
|
|
778
998
|
}
|
|
779
999
|
|
|
780
1000
|
// ../server/src/db.ts
|
|
@@ -927,6 +1147,7 @@ function getDb() {
|
|
|
927
1147
|
ensureColumn(db, "sessions", "kind", "kind TEXT NOT NULL DEFAULT 'user'");
|
|
928
1148
|
ensureColumn(db, "sessions", "advisor_role", "advisor_role TEXT");
|
|
929
1149
|
ensureColumn(db, "sessions", "worktree_path", "worktree_path TEXT");
|
|
1150
|
+
ensureColumn(db, "sessions", "wrapper_socket_path", "wrapper_socket_path TEXT");
|
|
930
1151
|
ensureColumn(db, "advisors", "texture_pack", "texture_pack TEXT");
|
|
931
1152
|
ensureColumn(db, "missions", "error_summary", "error_summary TEXT");
|
|
932
1153
|
_db = db;
|
|
@@ -934,8 +1155,8 @@ function getDb() {
|
|
|
934
1155
|
}
|
|
935
1156
|
|
|
936
1157
|
// ../server/src/http.ts
|
|
937
|
-
import { existsSync as
|
|
938
|
-
import { dirname as dirname4, extname, join as
|
|
1158
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
1159
|
+
import { dirname as dirname4, extname, join as join10, resolve as resolve3 } from "path";
|
|
939
1160
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
940
1161
|
import { spawnSync } from "child_process";
|
|
941
1162
|
import { Hono } from "hono";
|
|
@@ -943,12 +1164,12 @@ import { cors } from "hono/cors";
|
|
|
943
1164
|
|
|
944
1165
|
// ../server/src/util.ts
|
|
945
1166
|
import { createHash } from "crypto";
|
|
946
|
-
import { basename } from "path";
|
|
1167
|
+
import { basename as basename2 } from "path";
|
|
947
1168
|
function hashCwd(cwd) {
|
|
948
1169
|
return createHash("sha1").update(cwd).digest("hex").slice(0, 12);
|
|
949
1170
|
}
|
|
950
1171
|
function projectNameFromCwd(cwd) {
|
|
951
|
-
return
|
|
1172
|
+
return basename2(cwd) || cwd;
|
|
952
1173
|
}
|
|
953
1174
|
function now() {
|
|
954
1175
|
return Date.now();
|
|
@@ -1013,7 +1234,8 @@ function rowToSession(row) {
|
|
|
1013
1234
|
lastCompletedMissionId: row.last_completed_mission_id ?? void 0,
|
|
1014
1235
|
orbitSlot: row.orbit_slot,
|
|
1015
1236
|
name: row.name ?? void 0,
|
|
1016
|
-
worktreePath: row.worktree_path ?? void 0
|
|
1237
|
+
worktreePath: row.worktree_path ?? void 0,
|
|
1238
|
+
wrapperSocketPath: row.wrapper_socket_path ?? void 0
|
|
1017
1239
|
};
|
|
1018
1240
|
}
|
|
1019
1241
|
function nextOrbitSlot(db, projectId) {
|
|
@@ -1048,9 +1270,9 @@ function upsertSession(db, input) {
|
|
|
1048
1270
|
`INSERT INTO sessions (
|
|
1049
1271
|
id, pid, project_id, parent_session_id, origin, model, status,
|
|
1050
1272
|
context_usage_pct, orbit_slot, cwd, name, kind, advisor_role,
|
|
1051
|
-
worktree_path, created_at, updated_at
|
|
1273
|
+
worktree_path, wrapper_socket_path, created_at, updated_at
|
|
1052
1274
|
)
|
|
1053
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?, ?)`
|
|
1275
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?, ?, ?)`
|
|
1054
1276
|
).run(
|
|
1055
1277
|
input.id,
|
|
1056
1278
|
input.pid,
|
|
@@ -1064,6 +1286,7 @@ function upsertSession(db, input) {
|
|
|
1064
1286
|
kind,
|
|
1065
1287
|
input.advisorRole ?? null,
|
|
1066
1288
|
input.worktreePath ?? null,
|
|
1289
|
+
input.wrapperSocketPath ?? null,
|
|
1067
1290
|
ts2,
|
|
1068
1291
|
ts2
|
|
1069
1292
|
);
|
|
@@ -1082,7 +1305,8 @@ function upsertSession(db, input) {
|
|
|
1082
1305
|
parentSessionId: input.parentSessionId,
|
|
1083
1306
|
contextUsagePct: 0,
|
|
1084
1307
|
orbitSlot,
|
|
1085
|
-
worktreePath: input.worktreePath
|
|
1308
|
+
worktreePath: input.worktreePath,
|
|
1309
|
+
wrapperSocketPath: input.wrapperSocketPath
|
|
1086
1310
|
};
|
|
1087
1311
|
}
|
|
1088
1312
|
function setSessionStatus(db, sessionId, status) {
|
|
@@ -1135,7 +1359,7 @@ function listSessionsForProject(db, projectId) {
|
|
|
1135
1359
|
}
|
|
1136
1360
|
|
|
1137
1361
|
// ../server/src/state/missions.ts
|
|
1138
|
-
import { nanoid } from "nanoid";
|
|
1362
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
1139
1363
|
function rowToMission(row) {
|
|
1140
1364
|
let filesTouched = [];
|
|
1141
1365
|
try {
|
|
@@ -1178,7 +1402,7 @@ function shortNameFromPrompt(prompt) {
|
|
|
1178
1402
|
).filter(Boolean).join(" ") || "New Mission";
|
|
1179
1403
|
}
|
|
1180
1404
|
function startMission(db, sessionId, prompt) {
|
|
1181
|
-
const id =
|
|
1405
|
+
const id = nanoid2();
|
|
1182
1406
|
const ts2 = now();
|
|
1183
1407
|
const shortName = shortNameFromPrompt(prompt);
|
|
1184
1408
|
db.prepare(
|
|
@@ -1357,7 +1581,7 @@ function loadTimeline(db, opts = {}) {
|
|
|
1357
1581
|
}
|
|
1358
1582
|
|
|
1359
1583
|
// ../server/src/state/audit.ts
|
|
1360
|
-
import { nanoid as
|
|
1584
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
1361
1585
|
function rowToAuditEvent(row) {
|
|
1362
1586
|
let payload;
|
|
1363
1587
|
if (row.payload_json) {
|
|
@@ -1380,7 +1604,7 @@ function rowToAuditEvent(row) {
|
|
|
1380
1604
|
}
|
|
1381
1605
|
function recordAudit(db, input) {
|
|
1382
1606
|
const event = {
|
|
1383
|
-
id:
|
|
1607
|
+
id: nanoid3(),
|
|
1384
1608
|
ts: Date.now(),
|
|
1385
1609
|
kind: input.kind,
|
|
1386
1610
|
sessionId: input.sessionId,
|
|
@@ -1435,11 +1659,11 @@ function listAudit(db, opts = {}) {
|
|
|
1435
1659
|
}
|
|
1436
1660
|
|
|
1437
1661
|
// ../server/src/state/advisors.ts
|
|
1438
|
-
import { existsSync as
|
|
1439
|
-
import { dirname as dirname2, join as
|
|
1662
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1663
|
+
import { dirname as dirname2, join as join8, resolve } from "path";
|
|
1440
1664
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1441
1665
|
function findAgentsDir() {
|
|
1442
|
-
if (process.env.SOLIX_AGENTS_DIR &&
|
|
1666
|
+
if (process.env.SOLIX_AGENTS_DIR && existsSync5(process.env.SOLIX_AGENTS_DIR)) {
|
|
1443
1667
|
return process.env.SOLIX_AGENTS_DIR;
|
|
1444
1668
|
}
|
|
1445
1669
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
@@ -1452,17 +1676,17 @@ function findAgentsDir() {
|
|
|
1452
1676
|
resolve(process.cwd(), "packages", "agents")
|
|
1453
1677
|
];
|
|
1454
1678
|
for (const c of candidates) {
|
|
1455
|
-
if (
|
|
1679
|
+
if (existsSync5(join8(c, "manifest.json"))) return c;
|
|
1456
1680
|
}
|
|
1457
1681
|
return candidates[0];
|
|
1458
1682
|
}
|
|
1459
1683
|
var AGENTS_DIR = findAgentsDir();
|
|
1460
1684
|
function readManifest() {
|
|
1461
|
-
const path =
|
|
1462
|
-
if (!
|
|
1685
|
+
const path = join8(AGENTS_DIR, "manifest.json");
|
|
1686
|
+
if (!existsSync5(path)) {
|
|
1463
1687
|
return { version: 1, advisors: [] };
|
|
1464
1688
|
}
|
|
1465
|
-
return JSON.parse(
|
|
1689
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
1466
1690
|
}
|
|
1467
1691
|
function rowToAdvisor(row) {
|
|
1468
1692
|
let requiredSkills = [];
|
|
@@ -1505,7 +1729,7 @@ function seedAdvisors(db) {
|
|
|
1505
1729
|
WHERE id = ?`
|
|
1506
1730
|
);
|
|
1507
1731
|
for (const a of manifest.advisors) {
|
|
1508
|
-
const md =
|
|
1732
|
+
const md = join8(AGENTS_DIR, a.agentMd);
|
|
1509
1733
|
insert.run(
|
|
1510
1734
|
a.id,
|
|
1511
1735
|
a.role,
|
|
@@ -1563,10 +1787,55 @@ function setAdvisorPinned(db, id, pinned, sessionId) {
|
|
|
1563
1787
|
return getAdvisor(db, id);
|
|
1564
1788
|
}
|
|
1565
1789
|
function readAdvisorAgentMd(advisor) {
|
|
1566
|
-
if (!
|
|
1790
|
+
if (!existsSync5(advisor.agentMdPath)) {
|
|
1567
1791
|
return "";
|
|
1568
1792
|
}
|
|
1569
|
-
return
|
|
1793
|
+
return readFileSync4(advisor.agentMdPath, "utf8");
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// ../server/src/state/wrappers.ts
|
|
1797
|
+
import { connect } from "net";
|
|
1798
|
+
var wrappers = /* @__PURE__ */ new Map();
|
|
1799
|
+
var FRESHNESS_WINDOW_MS = 6e4;
|
|
1800
|
+
function registerWrapper(rec) {
|
|
1801
|
+
wrappers.set(rec.wrapperId, rec);
|
|
1802
|
+
}
|
|
1803
|
+
function unregisterWrapper(wrapperId) {
|
|
1804
|
+
wrappers.delete(wrapperId);
|
|
1805
|
+
}
|
|
1806
|
+
function claimWrapperForCwd(cwd) {
|
|
1807
|
+
const now2 = Date.now();
|
|
1808
|
+
let best;
|
|
1809
|
+
for (const rec of wrappers.values()) {
|
|
1810
|
+
if (rec.cwd !== cwd) continue;
|
|
1811
|
+
if (now2 - rec.registeredAt > FRESHNESS_WINDOW_MS) continue;
|
|
1812
|
+
if (!best || rec.registeredAt > best.registeredAt) best = rec;
|
|
1813
|
+
}
|
|
1814
|
+
if (best) {
|
|
1815
|
+
wrappers.delete(best.wrapperId);
|
|
1816
|
+
}
|
|
1817
|
+
return best;
|
|
1818
|
+
}
|
|
1819
|
+
function writeToWrapperSocket(socketPath, text) {
|
|
1820
|
+
try {
|
|
1821
|
+
const client = connect(socketPath);
|
|
1822
|
+
let settled = false;
|
|
1823
|
+
client.on("error", () => {
|
|
1824
|
+
if (!settled) {
|
|
1825
|
+
settled = true;
|
|
1826
|
+
try {
|
|
1827
|
+
client.destroy();
|
|
1828
|
+
} catch {
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
});
|
|
1832
|
+
client.write(JSON.stringify({ type: "send_prompt", text }) + "\n");
|
|
1833
|
+
client.end();
|
|
1834
|
+
settled = true;
|
|
1835
|
+
return true;
|
|
1836
|
+
} catch {
|
|
1837
|
+
return false;
|
|
1838
|
+
}
|
|
1570
1839
|
}
|
|
1571
1840
|
|
|
1572
1841
|
// ../server/src/state/context.ts
|
|
@@ -1656,10 +1925,10 @@ function buildContextEnvelope(db, args) {
|
|
|
1656
1925
|
}
|
|
1657
1926
|
|
|
1658
1927
|
// ../server/src/state/skills.ts
|
|
1659
|
-
import { existsSync as
|
|
1660
|
-
import { dirname as dirname3, join as
|
|
1928
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
1929
|
+
import { dirname as dirname3, join as join9, resolve as resolve2 } from "path";
|
|
1661
1930
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1662
|
-
import { homedir as
|
|
1931
|
+
import { homedir as homedir6 } from "os";
|
|
1663
1932
|
function findSolixSkillsDir() {
|
|
1664
1933
|
const here = dirname3(fileURLToPath3(import.meta.url));
|
|
1665
1934
|
const candidates = [
|
|
@@ -1668,15 +1937,15 @@ function findSolixSkillsDir() {
|
|
|
1668
1937
|
resolve2(process.cwd(), "packages", "skills")
|
|
1669
1938
|
];
|
|
1670
1939
|
for (const c of candidates) {
|
|
1671
|
-
if (
|
|
1940
|
+
if (existsSync6(c)) return c;
|
|
1672
1941
|
}
|
|
1673
1942
|
return candidates[0];
|
|
1674
1943
|
}
|
|
1675
1944
|
var SOLIX_SKILLS_DIR2 = findSolixSkillsDir();
|
|
1676
|
-
var ANTHROPIC_SKILLS_DIR =
|
|
1945
|
+
var ANTHROPIC_SKILLS_DIR = join9(homedir6(), ".claude", "skills");
|
|
1677
1946
|
function parseSkillManifest(manifestPath, fallbackId) {
|
|
1678
1947
|
try {
|
|
1679
|
-
const txt =
|
|
1948
|
+
const txt = readFileSync5(manifestPath, "utf8");
|
|
1680
1949
|
const match = txt.match(/^---\n([\s\S]*?)\n---/);
|
|
1681
1950
|
let name = fallbackId;
|
|
1682
1951
|
let description = "";
|
|
@@ -1725,9 +1994,9 @@ function discoverSkills(db) {
|
|
|
1725
1994
|
{ dir: SOLIX_SKILLS_DIR2, source: "solix" }
|
|
1726
1995
|
];
|
|
1727
1996
|
for (const { dir, source } of sources) {
|
|
1728
|
-
if (!
|
|
1997
|
+
if (!existsSync6(dir)) continue;
|
|
1729
1998
|
for (const entry of readdirSync3(dir)) {
|
|
1730
|
-
const full =
|
|
1999
|
+
const full = join9(dir, entry);
|
|
1731
2000
|
let isDir = false;
|
|
1732
2001
|
try {
|
|
1733
2002
|
isDir = statSync3(full).isDirectory();
|
|
@@ -1735,8 +2004,8 @@ function discoverSkills(db) {
|
|
|
1735
2004
|
continue;
|
|
1736
2005
|
}
|
|
1737
2006
|
if (!isDir) continue;
|
|
1738
|
-
const manifestPath =
|
|
1739
|
-
if (!
|
|
2007
|
+
const manifestPath = join9(full, "SKILL.md");
|
|
2008
|
+
if (!existsSync6(manifestPath)) continue;
|
|
1740
2009
|
const parsed = parseSkillManifest(manifestPath, entry);
|
|
1741
2010
|
if (!parsed) continue;
|
|
1742
2011
|
const id = `${source}:${parsed.id}`;
|
|
@@ -1761,8 +2030,8 @@ function getSkill(db, id) {
|
|
|
1761
2030
|
return row ? rowToSkill(row) : null;
|
|
1762
2031
|
}
|
|
1763
2032
|
function readSkillManifest(skill) {
|
|
1764
|
-
if (!
|
|
1765
|
-
return
|
|
2033
|
+
if (!existsSync6(skill.manifestPath)) return "";
|
|
2034
|
+
return readFileSync5(skill.manifestPath, "utf8");
|
|
1766
2035
|
}
|
|
1767
2036
|
function recordSkillInstall(db, skillId, projectId) {
|
|
1768
2037
|
const skill = getSkill(db, skillId);
|
|
@@ -1775,7 +2044,7 @@ function recordSkillInstall(db, skillId, projectId) {
|
|
|
1775
2044
|
}
|
|
1776
2045
|
|
|
1777
2046
|
// ../server/src/state/galaxy.ts
|
|
1778
|
-
import { nanoid as
|
|
2047
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1779
2048
|
function exportManifest(db, opts = {}) {
|
|
1780
2049
|
const advisors2 = listAdvisors(db);
|
|
1781
2050
|
const skills2 = listSkills(db);
|
|
@@ -1823,7 +2092,7 @@ function importManifest(db, manifest, sourceUrl) {
|
|
|
1823
2092
|
db.prepare(
|
|
1824
2093
|
`INSERT INTO galaxy_imports (id, source_url, manifest_json, imported_at)
|
|
1825
2094
|
VALUES (?, ?, ?, ?)`
|
|
1826
|
-
).run(
|
|
2095
|
+
).run(nanoid4(), sourceUrl ?? null, JSON.stringify(manifest), now());
|
|
1827
2096
|
return {
|
|
1828
2097
|
advisorsEnabled: enabled,
|
|
1829
2098
|
advisorsDisabled: disabled,
|
|
@@ -1884,7 +2153,7 @@ function snapshotExport(db, manifest) {
|
|
|
1884
2153
|
return rowToVersion(existing);
|
|
1885
2154
|
}
|
|
1886
2155
|
}
|
|
1887
|
-
const id =
|
|
2156
|
+
const id = nanoid4();
|
|
1888
2157
|
const ts2 = now();
|
|
1889
2158
|
const ordinal = (last?.ordinal ?? 0) + 1;
|
|
1890
2159
|
db.prepare(
|
|
@@ -2278,6 +2547,23 @@ function createHttpApp(opts) {
|
|
|
2278
2547
|
}
|
|
2279
2548
|
});
|
|
2280
2549
|
app.get("/api/galaxy/imports", (c) => c.json(listImportHistory(opts.db)));
|
|
2550
|
+
app.post("/api/wrappers/register", async (c) => {
|
|
2551
|
+
const body = await c.req.json().catch(() => null);
|
|
2552
|
+
if (!body?.wrapperId || !body.socketPath || !body.cwd) {
|
|
2553
|
+
return c.json({ error: "wrapperId, socketPath, cwd required" }, 400);
|
|
2554
|
+
}
|
|
2555
|
+
registerWrapper({
|
|
2556
|
+
wrapperId: body.wrapperId,
|
|
2557
|
+
socketPath: body.socketPath,
|
|
2558
|
+
cwd: body.cwd,
|
|
2559
|
+
registeredAt: Date.now()
|
|
2560
|
+
});
|
|
2561
|
+
return c.json({ ok: true });
|
|
2562
|
+
});
|
|
2563
|
+
app.post("/api/wrappers/:id/unregister", (c) => {
|
|
2564
|
+
unregisterWrapper(c.req.param("id"));
|
|
2565
|
+
return c.json({ ok: true });
|
|
2566
|
+
});
|
|
2281
2567
|
let preflightCache = null;
|
|
2282
2568
|
app.get("/api/system/preflight", (c) => {
|
|
2283
2569
|
if (preflightCache) return c.json(preflightCache);
|
|
@@ -2307,17 +2593,17 @@ function createHttpApp(opts) {
|
|
|
2307
2593
|
return c.notFound();
|
|
2308
2594
|
}
|
|
2309
2595
|
const safe = url.pathname.replace(/\.\.+/g, ".");
|
|
2310
|
-
const candidate =
|
|
2596
|
+
const candidate = join10(webDist, safe === "/" ? "index.html" : safe);
|
|
2311
2597
|
let filePath = candidate;
|
|
2312
2598
|
try {
|
|
2313
|
-
if (!
|
|
2314
|
-
filePath =
|
|
2599
|
+
if (!existsSync7(filePath) || statSync4(filePath).isDirectory()) {
|
|
2600
|
+
filePath = join10(webDist, "index.html");
|
|
2315
2601
|
}
|
|
2316
2602
|
} catch {
|
|
2317
|
-
filePath =
|
|
2603
|
+
filePath = join10(webDist, "index.html");
|
|
2318
2604
|
}
|
|
2319
|
-
if (!
|
|
2320
|
-
const data =
|
|
2605
|
+
if (!existsSync7(filePath)) return c.notFound();
|
|
2606
|
+
const data = readFileSync6(filePath);
|
|
2321
2607
|
return new Response(data, {
|
|
2322
2608
|
headers: { "Content-Type": mimeFor(filePath) }
|
|
2323
2609
|
});
|
|
@@ -2375,7 +2661,7 @@ function createHttpApp(opts) {
|
|
|
2375
2661
|
}
|
|
2376
2662
|
function findWebDist() {
|
|
2377
2663
|
if (process.env.SOLIX_WEB_DIST) {
|
|
2378
|
-
return
|
|
2664
|
+
return existsSync7(process.env.SOLIX_WEB_DIST) ? process.env.SOLIX_WEB_DIST : null;
|
|
2379
2665
|
}
|
|
2380
2666
|
const here = dirname4(fileURLToPath4(import.meta.url));
|
|
2381
2667
|
const candidates = [
|
|
@@ -2388,7 +2674,7 @@ function findWebDist() {
|
|
|
2388
2674
|
resolve3(process.cwd(), "packages", "web", "dist")
|
|
2389
2675
|
];
|
|
2390
2676
|
for (const c of candidates) {
|
|
2391
|
-
if (
|
|
2677
|
+
if (existsSync7(join10(c, "index.html"))) return c;
|
|
2392
2678
|
}
|
|
2393
2679
|
return null;
|
|
2394
2680
|
}
|
|
@@ -2414,10 +2700,10 @@ function mimeFor(filePath) {
|
|
|
2414
2700
|
|
|
2415
2701
|
// ../server/src/launcher.ts
|
|
2416
2702
|
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
2417
|
-
import { existsSync as
|
|
2418
|
-
import { homedir as
|
|
2419
|
-
import { basename as
|
|
2420
|
-
import { nanoid as
|
|
2703
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
2704
|
+
import { homedir as homedir7 } from "os";
|
|
2705
|
+
import { basename as basename3, join as join11 } from "path";
|
|
2706
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
2421
2707
|
function ensureWorktree(opts) {
|
|
2422
2708
|
const repoRoot = (() => {
|
|
2423
2709
|
const r = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -2429,10 +2715,10 @@ function ensureWorktree(opts) {
|
|
|
2429
2715
|
}
|
|
2430
2716
|
return (r.stdout ?? "").trim();
|
|
2431
2717
|
})();
|
|
2432
|
-
const repoName =
|
|
2718
|
+
const repoName = basename3(repoRoot);
|
|
2433
2719
|
const safeBranch = opts.branch.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
2434
|
-
const worktreesDir =
|
|
2435
|
-
const path =
|
|
2720
|
+
const worktreesDir = join11(homedir7(), ".solix", "worktrees");
|
|
2721
|
+
const path = join11(worktreesDir, `${repoName}-${safeBranch}`);
|
|
2436
2722
|
const list = spawnSync2("git", ["worktree", "list", "--porcelain"], {
|
|
2437
2723
|
cwd: repoRoot,
|
|
2438
2724
|
encoding: "utf8"
|
|
@@ -2440,7 +2726,7 @@ function ensureWorktree(opts) {
|
|
|
2440
2726
|
if (list.status === 0 && (list.stdout ?? "").includes(`worktree ${path}`)) {
|
|
2441
2727
|
return { path, created: false };
|
|
2442
2728
|
}
|
|
2443
|
-
|
|
2729
|
+
mkdirSync4(worktreesDir, { recursive: true });
|
|
2444
2730
|
const branchProbe = spawnSync2(
|
|
2445
2731
|
"git",
|
|
2446
2732
|
["rev-parse", "--verify", "--quiet", `refs/heads/${opts.branch}`],
|
|
@@ -2535,7 +2821,7 @@ var Launcher = class {
|
|
|
2535
2821
|
}
|
|
2536
2822
|
pinSynthetic(advisorId, codename, cwd) {
|
|
2537
2823
|
const project = ensureProject(this.db, cwd);
|
|
2538
|
-
const sessionId = `advisor-${advisorId}-${
|
|
2824
|
+
const sessionId = `advisor-${advisorId}-${nanoid5(6)}`;
|
|
2539
2825
|
const fakePid = 1e5 + Math.floor(Math.random() * 1e5);
|
|
2540
2826
|
const session = upsertSession(this.db, {
|
|
2541
2827
|
id: sessionId,
|
|
@@ -2644,7 +2930,7 @@ var Launcher = class {
|
|
|
2644
2930
|
worktreePath
|
|
2645
2931
|
});
|
|
2646
2932
|
}
|
|
2647
|
-
if (!
|
|
2933
|
+
if (!existsSync8(spawnCwd)) {
|
|
2648
2934
|
this.broadcaster.broadcast({
|
|
2649
2935
|
type: "toast",
|
|
2650
2936
|
level: "error",
|
|
@@ -2655,7 +2941,7 @@ var Launcher = class {
|
|
|
2655
2941
|
const args = ["--print"];
|
|
2656
2942
|
if (opts.model) args.push("--model", String(opts.model));
|
|
2657
2943
|
args.push(opts.initialPrompt);
|
|
2658
|
-
const sessionId = `task-${
|
|
2944
|
+
const sessionId = `task-${nanoid5(8)}`;
|
|
2659
2945
|
return this.spawnPrint({
|
|
2660
2946
|
sessionId,
|
|
2661
2947
|
cwd: spawnCwd,
|
|
@@ -2783,7 +3069,7 @@ var Launcher = class {
|
|
|
2783
3069
|
}
|
|
2784
3070
|
launchSynthetic(opts) {
|
|
2785
3071
|
const project = ensureProject(this.db, opts.cwd);
|
|
2786
|
-
const sessionId = `task-${
|
|
3072
|
+
const sessionId = `task-${nanoid5(8)}`;
|
|
2787
3073
|
const fakePid = 2e5 + Math.floor(Math.random() * 1e5);
|
|
2788
3074
|
upsertSession(this.db, {
|
|
2789
3075
|
id: sessionId,
|
|
@@ -2829,12 +3115,12 @@ var Launcher = class {
|
|
|
2829
3115
|
};
|
|
2830
3116
|
|
|
2831
3117
|
// ../server/src/router.ts
|
|
2832
|
-
import { nanoid as
|
|
3118
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
2833
3119
|
|
|
2834
3120
|
// ../server/src/state/toolcalls.ts
|
|
2835
|
-
import { nanoid as
|
|
3121
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
2836
3122
|
function recordToolCall(db, input) {
|
|
2837
|
-
const id =
|
|
3123
|
+
const id = nanoid6();
|
|
2838
3124
|
const ts2 = now();
|
|
2839
3125
|
const status = input.status ?? "running";
|
|
2840
3126
|
db.prepare(
|
|
@@ -2936,6 +3222,7 @@ var EventRouter = class {
|
|
|
2936
3222
|
const sessionId = this.extractSessionId(event);
|
|
2937
3223
|
const advisorRole = this.launcher?.advisorRoleForPid(event.pid);
|
|
2938
3224
|
const worktreePath = this.launcher?.worktreePathForInternalCwd(event.cwd);
|
|
3225
|
+
const wrapper = claimWrapperForCwd(event.cwd);
|
|
2939
3226
|
const session = upsertSession(this.db, {
|
|
2940
3227
|
id: sessionId,
|
|
2941
3228
|
pid: event.pid,
|
|
@@ -2946,7 +3233,8 @@ var EventRouter = class {
|
|
|
2946
3233
|
parentSessionId: this.extractParentSessionId(event),
|
|
2947
3234
|
kind: advisorRole ? "advisor" : "user",
|
|
2948
3235
|
advisorRole,
|
|
2949
|
-
worktreePath
|
|
3236
|
+
worktreePath,
|
|
3237
|
+
wrapperSocketPath: wrapper?.socketPath
|
|
2950
3238
|
});
|
|
2951
3239
|
this.broadcaster.broadcast({ type: "session_upsert", session });
|
|
2952
3240
|
if (!session.parentSessionId) {
|
|
@@ -3013,7 +3301,7 @@ var EventRouter = class {
|
|
|
3013
3301
|
const parentSessionId = this.extractSessionId(event);
|
|
3014
3302
|
const parent = getSession(this.db, parentSessionId);
|
|
3015
3303
|
if (!parent) return;
|
|
3016
|
-
const subId =
|
|
3304
|
+
const subId = nanoid7();
|
|
3017
3305
|
const sub = upsertSession(this.db, {
|
|
3018
3306
|
id: subId,
|
|
3019
3307
|
pid: event.pid,
|
|
@@ -3090,7 +3378,7 @@ var EventRouter = class {
|
|
|
3090
3378
|
const p = event.payload;
|
|
3091
3379
|
const message = typeof p.message === "string" ? p.message : "Permission requested";
|
|
3092
3380
|
const tool = typeof p.tool_name === "string" ? p.tool_name : "unknown";
|
|
3093
|
-
const requestId =
|
|
3381
|
+
const requestId = nanoid7();
|
|
3094
3382
|
this.permissions.set(requestId, {
|
|
3095
3383
|
requestId,
|
|
3096
3384
|
sessionId,
|
|
@@ -3230,6 +3518,31 @@ var EventRouter = class {
|
|
|
3230
3518
|
});
|
|
3231
3519
|
}
|
|
3232
3520
|
sendPromptToSession(sessionId, text) {
|
|
3521
|
+
const session = getSession(this.db, sessionId);
|
|
3522
|
+
if (!session) return false;
|
|
3523
|
+
if (session.wrapperSocketPath) {
|
|
3524
|
+
const ok = writeToWrapperSocket(session.wrapperSocketPath, text);
|
|
3525
|
+
if (ok) {
|
|
3526
|
+
this.broadcaster.broadcast({
|
|
3527
|
+
type: "chat_delta",
|
|
3528
|
+
sessionId,
|
|
3529
|
+
delta: {
|
|
3530
|
+
messageId: `u-${Date.now()}`,
|
|
3531
|
+
role: "user",
|
|
3532
|
+
content: text,
|
|
3533
|
+
ts: Date.now(),
|
|
3534
|
+
done: true
|
|
3535
|
+
}
|
|
3536
|
+
});
|
|
3537
|
+
} else {
|
|
3538
|
+
this.broadcaster.broadcast({
|
|
3539
|
+
type: "toast",
|
|
3540
|
+
level: "warn",
|
|
3541
|
+
message: "Wrapper socket unreachable \u2014 the `solix run` process may have exited."
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
return ok;
|
|
3545
|
+
}
|
|
3233
3546
|
if (!this.launcher) return false;
|
|
3234
3547
|
return this.launcher.sendPromptToInternal(sessionId, text);
|
|
3235
3548
|
}
|
|
@@ -3364,15 +3677,15 @@ function handleClientMessage(ctx, _ws, msg) {
|
|
|
3364
3677
|
// ../server/src/state/transcript.ts
|
|
3365
3678
|
import {
|
|
3366
3679
|
closeSync,
|
|
3367
|
-
existsSync as
|
|
3680
|
+
existsSync as existsSync9,
|
|
3368
3681
|
openSync,
|
|
3369
3682
|
readSync,
|
|
3370
3683
|
statSync as statSync5,
|
|
3371
3684
|
watch
|
|
3372
3685
|
} from "fs";
|
|
3373
|
-
import { homedir as
|
|
3374
|
-
import { join as
|
|
3375
|
-
var TRANSCRIPT_BASE =
|
|
3686
|
+
import { homedir as homedir8 } from "os";
|
|
3687
|
+
import { join as join12 } from "path";
|
|
3688
|
+
var TRANSCRIPT_BASE = join12(homedir8(), ".claude", "projects");
|
|
3376
3689
|
var CONTEXT_BUDGETS_BY_MODEL = {
|
|
3377
3690
|
"claude-opus-4-7": 2e5,
|
|
3378
3691
|
"claude-opus-4-6": 2e5,
|
|
@@ -3385,7 +3698,7 @@ function encodeProjectPath(cwd) {
|
|
|
3385
3698
|
return cwd.replace(/[/\\]/g, "-");
|
|
3386
3699
|
}
|
|
3387
3700
|
function transcriptPathFor(cwd, sessionId) {
|
|
3388
|
-
return
|
|
3701
|
+
return join12(TRANSCRIPT_BASE, encodeProjectPath(cwd), `${sessionId}.jsonl`);
|
|
3389
3702
|
}
|
|
3390
3703
|
var TranscriptWatcherManager = class {
|
|
3391
3704
|
constructor(db, broadcaster) {
|
|
@@ -3405,7 +3718,7 @@ var TranscriptWatcherManager = class {
|
|
|
3405
3718
|
startWatching(sessionId, cwd) {
|
|
3406
3719
|
if (this.records.has(sessionId)) return;
|
|
3407
3720
|
const filePath = transcriptPathFor(cwd, sessionId);
|
|
3408
|
-
if (!
|
|
3721
|
+
if (!existsSync9(filePath)) {
|
|
3409
3722
|
this.scheduleRetry(sessionId, cwd, 0);
|
|
3410
3723
|
return;
|
|
3411
3724
|
}
|
|
@@ -3416,7 +3729,7 @@ var TranscriptWatcherManager = class {
|
|
|
3416
3729
|
const t = setTimeout(() => {
|
|
3417
3730
|
this.deferredRetry.delete(sessionId);
|
|
3418
3731
|
const filePath = transcriptPathFor(cwd, sessionId);
|
|
3419
|
-
if (
|
|
3732
|
+
if (existsSync9(filePath)) {
|
|
3420
3733
|
this.attach(sessionId, filePath);
|
|
3421
3734
|
} else {
|
|
3422
3735
|
this.scheduleRetry(sessionId, cwd, attempt + 1);
|
|
@@ -3678,19 +3991,20 @@ async function start(opts = {}) {
|
|
|
3678
3991
|
}
|
|
3679
3992
|
|
|
3680
3993
|
// src/uninstall.ts
|
|
3681
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
3994
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
3682
3995
|
function uninstall() {
|
|
3683
|
-
|
|
3996
|
+
uninstallShim();
|
|
3997
|
+
if (existsSync10(CLAUDE_BACKUP)) {
|
|
3684
3998
|
copyFileSync2(CLAUDE_BACKUP, CLAUDE_SETTINGS);
|
|
3685
3999
|
console.log(`[solix] restored settings.json from backup`);
|
|
3686
4000
|
return;
|
|
3687
4001
|
}
|
|
3688
|
-
if (!
|
|
4002
|
+
if (!existsSync10(CLAUDE_SETTINGS)) {
|
|
3689
4003
|
console.log("[solix] nothing to uninstall (no settings.json found)");
|
|
3690
4004
|
return;
|
|
3691
4005
|
}
|
|
3692
4006
|
const cur = JSON.parse(
|
|
3693
|
-
|
|
4007
|
+
readFileSync7(CLAUDE_SETTINGS, "utf8")
|
|
3694
4008
|
);
|
|
3695
4009
|
if (cur.hooks) {
|
|
3696
4010
|
for (const [evt, entries] of Object.entries(cur.hooks)) {
|
|
@@ -3700,7 +4014,7 @@ function uninstall() {
|
|
|
3700
4014
|
if (cur.hooks[evt].length === 0) delete cur.hooks[evt];
|
|
3701
4015
|
}
|
|
3702
4016
|
}
|
|
3703
|
-
|
|
4017
|
+
writeFileSync4(CLAUDE_SETTINGS, JSON.stringify(cur, null, 2) + "\n");
|
|
3704
4018
|
console.log(`[solix] removed Solix hooks from ${CLAUDE_SETTINGS}`);
|
|
3705
4019
|
}
|
|
3706
4020
|
|
|
@@ -3717,6 +4031,16 @@ program.command("install").description("Install Solix hooks into ~/.claude/setti
|
|
|
3717
4031
|
program.command("uninstall").description("Restore ~/.claude/settings.json from backup").action(() => {
|
|
3718
4032
|
uninstall();
|
|
3719
4033
|
});
|
|
4034
|
+
program.command("run").description(
|
|
4035
|
+
"Wrap a claude session under a PTY so the Solix UI can send prompts to it. Pass any args you would normally pass to claude."
|
|
4036
|
+
).allowUnknownOption(true).helpOption(false).action(async (_opts, cmd) => {
|
|
4037
|
+
await runWrapped(cmd.args ?? []);
|
|
4038
|
+
});
|
|
4039
|
+
program.command("install-shim").description(
|
|
4040
|
+
"Add `alias claude='solix run'` to your shell rc so every claude session is wrapped automatically."
|
|
4041
|
+
).action(() => {
|
|
4042
|
+
installShim();
|
|
4043
|
+
});
|
|
3720
4044
|
program.command("doctor").description("Run diagnostics").action(async () => {
|
|
3721
4045
|
await doctor();
|
|
3722
4046
|
});
|