@shmulikdav/solix 1.8.0 → 1.9.1
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/README.md +3 -3
- package/dist/index.js +723 -330
- package/dist/skills/advisor-prompt/SKILL.md +39 -0
- package/dist/skills/galaxy-publish/SKILL.md +31 -0
- package/dist/skills/mission-summary/SKILL.md +28 -0
- package/dist/skills/permission-explainer/SKILL.md +39 -0
- package/dist/web/assets/{CrewPanel-BZW5L0xR.js → CrewPanel-CCQyVXEO.js} +1 -1
- package/dist/web/assets/GalaxyPanel-YXuzkrWv.js +1 -0
- package/dist/web/assets/{Scene-D9_dLZUf.js → Scene-NahUVf-g.js} +1 -1
- package/dist/web/assets/{TimelineDrawer-xRdQJcuf.js → TimelineDrawer-CpPhGaYI.js} +1 -1
- package/dist/web/assets/index-BE_H37qZ.css +1 -0
- package/dist/web/assets/{index-Df150VgL.js → index-C8BeteJb.js} +17 -17
- package/dist/web/index.html +22 -2
- package/dist/web/sw.js +1 -1
- package/dist/web/workbox-0bb07689.js +1 -0
- package/package.json +4 -4
- package/dist/web/assets/GalaxyPanel-CCsddWgL.js +0 -1
- package/dist/web/assets/index-V4rr9htx.css +0 -1
- package/dist/web/workbox-9c191d2f.js +0 -1
package/dist/index.js
CHANGED
|
@@ -56,196 +56,527 @@ var pinAdvisorCmd = (id) => postAdvisor(id, "pin");
|
|
|
56
56
|
var unpinAdvisorCmd = (id) => postAdvisor(id, "unpin");
|
|
57
57
|
|
|
58
58
|
// src/demo.ts
|
|
59
|
+
import { spawn } from "child_process";
|
|
60
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from "fs";
|
|
59
61
|
import { homedir } from "os";
|
|
60
62
|
import { join } from "path";
|
|
61
|
-
|
|
62
|
-
var
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
import { fileURLToPath } from "url";
|
|
64
|
+
var SOLIX_HOME = process.env.SOLIX_HOME ?? join(homedir(), ".solix");
|
|
65
|
+
var DEMO_DB_PATH = join(SOLIX_HOME, "demo.db");
|
|
66
|
+
var DEMO_PID_PATH = join(SOLIX_HOME, "demo.pid");
|
|
67
|
+
var TOKEN_PATH = join(SOLIX_HOME, "token");
|
|
68
|
+
var demoToken = "";
|
|
69
|
+
try {
|
|
70
|
+
demoToken = readFileSync(TOKEN_PATH, "utf8").trim();
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
var TICKER_COMET_MS = 2500;
|
|
74
|
+
var TICKER_PROMOTE_MS = 25e3;
|
|
75
|
+
var TICKER_MISSION_MS = 45e3;
|
|
76
|
+
var TICKER_PERMISSION_MS = 75e3;
|
|
77
|
+
var TICKER_ADVISOR_MS = 12e4;
|
|
78
|
+
function baseUrl(port) {
|
|
79
|
+
return `http://127.0.0.1:${port}`;
|
|
69
80
|
}
|
|
70
81
|
function ts() {
|
|
71
82
|
return Date.now();
|
|
72
83
|
}
|
|
73
|
-
async function
|
|
84
|
+
async function sleep(ms) {
|
|
85
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
86
|
+
}
|
|
87
|
+
async function isPortFree(port) {
|
|
74
88
|
try {
|
|
75
|
-
|
|
76
|
-
signal: AbortSignal.timeout(
|
|
89
|
+
await fetch(`${baseUrl(port)}/api/health`, {
|
|
90
|
+
signal: AbortSignal.timeout(300)
|
|
77
91
|
});
|
|
78
|
-
return res.ok;
|
|
79
|
-
} catch {
|
|
80
92
|
return false;
|
|
93
|
+
} catch {
|
|
94
|
+
return true;
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
|
-
async function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
async function waitForServer(port, timeoutMs = 8e3) {
|
|
98
|
+
const start2 = Date.now();
|
|
99
|
+
while (Date.now() - start2 < timeoutMs) {
|
|
100
|
+
if (!await isPortFree(port)) return true;
|
|
101
|
+
await sleep(150);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
87
104
|
}
|
|
88
|
-
async function
|
|
89
|
-
|
|
105
|
+
async function postEvent(base, payload) {
|
|
106
|
+
try {
|
|
107
|
+
await fetch(`${base}/events`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
...demoToken ? { "x-solix-token": demoToken } : {}
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(payload)
|
|
114
|
+
});
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
90
117
|
}
|
|
91
|
-
async function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
async function postJson(base, path, body) {
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch(`${base}${path}`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: body ? { "Content-Type": "application/json" } : {},
|
|
123
|
+
body: body ? JSON.stringify(body) : void 0
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok) return null;
|
|
126
|
+
if (res.headers.get("content-type")?.includes("json")) {
|
|
127
|
+
return await res.json();
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function getJson(base, path) {
|
|
135
|
+
try {
|
|
136
|
+
const res = await fetch(`${base}${path}`);
|
|
137
|
+
if (!res.ok) return null;
|
|
138
|
+
return await res.json();
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function randomChoice(arr) {
|
|
144
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
145
|
+
}
|
|
146
|
+
async function bootSandbox(preferredPort) {
|
|
147
|
+
let port = preferredPort;
|
|
148
|
+
if (!await isPortFree(port)) {
|
|
149
|
+
console.log(
|
|
150
|
+
`[solix demo] port ${port} is in use \u2014 falling back to ${port + 1} for the sandbox.`
|
|
96
151
|
);
|
|
97
|
-
|
|
98
|
-
|
|
152
|
+
port += 1;
|
|
153
|
+
if (!await isPortFree(port)) {
|
|
154
|
+
console.error(
|
|
155
|
+
`[solix demo] both ${preferredPort} and ${port} are in use. Stop one of them or pass --port.`
|
|
156
|
+
);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
99
159
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
prompt: "Wire up the asteroid belt to real skill data",
|
|
122
|
-
tools: [
|
|
123
|
-
{ tool: "Read", file: "packages/server/src/state/skills.ts" },
|
|
124
|
-
{ tool: "Write", file: "packages/web/src/scene/AsteroidBelt.tsx" }
|
|
125
|
-
]
|
|
126
|
-
},
|
|
160
|
+
if (existsSync(DEMO_PID_PATH)) {
|
|
161
|
+
try {
|
|
162
|
+
const pid = parseInt(readFileSync(DEMO_PID_PATH, "utf8").trim(), 10);
|
|
163
|
+
if (pid > 0) {
|
|
164
|
+
try {
|
|
165
|
+
process.kill(pid, 0);
|
|
166
|
+
try {
|
|
167
|
+
process.kill(pid);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
mkdirSync(SOLIX_HOME, { recursive: true });
|
|
177
|
+
const selfScript = fileURLToPath(import.meta.url);
|
|
178
|
+
const child = spawn(
|
|
179
|
+
process.execPath,
|
|
180
|
+
[selfScript, "start", "--port", String(port), "--no-open"],
|
|
127
181
|
{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
182
|
+
env: {
|
|
183
|
+
...process.env,
|
|
184
|
+
SOLIX_DB_PATH: DEMO_DB_PATH
|
|
185
|
+
},
|
|
186
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
187
|
+
detached: false
|
|
134
188
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
189
|
+
);
|
|
190
|
+
if (child.pid) {
|
|
191
|
+
try {
|
|
192
|
+
(await import("fs")).writeFileSync(
|
|
193
|
+
DEMO_PID_PATH,
|
|
194
|
+
String(child.pid)
|
|
195
|
+
);
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
child.on("exit", (code) => {
|
|
200
|
+
if (code != null && code !== 0) {
|
|
201
|
+
console.error(`[solix demo] sandbox server exited with code ${code}`);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
if (!await waitForServer(port)) {
|
|
205
|
+
console.error(`[solix demo] sandbox server failed to start within 8s.`);
|
|
206
|
+
try {
|
|
207
|
+
child.kill();
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return { child, port, base: baseUrl(port) };
|
|
213
|
+
}
|
|
214
|
+
var PROJECTS = [
|
|
215
|
+
"web-app",
|
|
216
|
+
"infrastructure",
|
|
217
|
+
"data-pipeline",
|
|
218
|
+
"mobile-client",
|
|
219
|
+
"design-system",
|
|
220
|
+
"observability",
|
|
221
|
+
"ml-research",
|
|
222
|
+
"docs-site"
|
|
223
|
+
];
|
|
224
|
+
var MODELS = ["opus", "sonnet", "haiku", "default"];
|
|
225
|
+
var PROMPTS = [
|
|
226
|
+
"Refactor the orbital math for stable layout",
|
|
227
|
+
"Wire up the asteroid belt to real skill data",
|
|
228
|
+
"Document the context envelope strategy",
|
|
229
|
+
"Audit the auth flow for token leak risk",
|
|
230
|
+
"Generate a migration plan for the new schema",
|
|
231
|
+
"Triage the failing Playwright spec",
|
|
232
|
+
"Build a flame graph from the last week of traces",
|
|
233
|
+
"Sweep deprecated APIs out of the SDK",
|
|
234
|
+
"Polish the README with three quickstart examples",
|
|
235
|
+
"Draft a runbook for the budget breach scenario"
|
|
236
|
+
];
|
|
237
|
+
var TOOL_FILES = [
|
|
238
|
+
"packages/web/src/scene/Planet.tsx",
|
|
239
|
+
"packages/server/src/router.ts",
|
|
240
|
+
"packages/cli/src/install.ts",
|
|
241
|
+
"packages/shared/src/types.ts",
|
|
242
|
+
"packages/web/src/store/index.ts"
|
|
243
|
+
];
|
|
244
|
+
var TOOL_COMMANDS = [
|
|
245
|
+
"pnpm -r typecheck",
|
|
246
|
+
"pnpm --filter @solix/web build",
|
|
247
|
+
"git status -sb",
|
|
248
|
+
"cargo test --workspace",
|
|
249
|
+
"curl -s http://127.0.0.1:4242/api/health"
|
|
250
|
+
];
|
|
251
|
+
var STATUS_PLAN = [
|
|
252
|
+
...Array(5).fill("active"),
|
|
253
|
+
...Array(3).fill("awaiting_permission"),
|
|
254
|
+
...Array(2).fill("awaiting_input"),
|
|
255
|
+
...Array(1).fill("error"),
|
|
256
|
+
...Array(1).fill("plan_review"),
|
|
257
|
+
...Array(18).fill("idle")
|
|
258
|
+
];
|
|
259
|
+
async function richSeed(base, demoRootCwd) {
|
|
260
|
+
console.log(`[solix demo] seeding ${STATUS_PLAN.length} sessions across ${PROJECTS.length} projects\u2026`);
|
|
261
|
+
const advisors2 = await getJson(base, "/api/advisors") ?? [];
|
|
262
|
+
for (const a of advisors2) {
|
|
263
|
+
await postJson(base, `/api/advisors/${encodeURIComponent(a.id)}/enable`);
|
|
264
|
+
}
|
|
265
|
+
console.log(`[solix demo] enabled ${advisors2.length} advisors`);
|
|
266
|
+
const sessions = [];
|
|
267
|
+
let pid = 9e4;
|
|
268
|
+
for (let i = 0; i < STATUS_PLAN.length; i++) {
|
|
269
|
+
const projectName = PROJECTS[i % PROJECTS.length];
|
|
270
|
+
const cwd = join(demoRootCwd, projectName);
|
|
271
|
+
const status = STATUS_PLAN[i];
|
|
272
|
+
const id = `demo-${String(i).padStart(2, "0")}-${projectName}`;
|
|
273
|
+
const model = MODELS[i % MODELS.length];
|
|
274
|
+
sessions.push({ id, pid, cwd, projectName, status });
|
|
275
|
+
await postEvent(base, {
|
|
138
276
|
event: "session_start",
|
|
277
|
+
pid,
|
|
278
|
+
cwd,
|
|
279
|
+
ts: ts(),
|
|
280
|
+
payload: { session_id: id, model }
|
|
281
|
+
});
|
|
282
|
+
pid += 1;
|
|
283
|
+
}
|
|
284
|
+
await sleep(120);
|
|
285
|
+
for (const s of sessions) {
|
|
286
|
+
const prompt = randomChoice(PROMPTS);
|
|
287
|
+
if (s.status === "active" || s.status === "idle" || s.status === "awaiting_input") {
|
|
288
|
+
await postEvent(base, {
|
|
289
|
+
event: "user_prompt_submit",
|
|
290
|
+
pid: s.pid,
|
|
291
|
+
cwd: s.cwd,
|
|
292
|
+
ts: ts(),
|
|
293
|
+
payload: { session_id: s.id, prompt }
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
if (s.status === "idle") {
|
|
297
|
+
await postEvent(base, {
|
|
298
|
+
event: "stop",
|
|
299
|
+
pid: s.pid,
|
|
300
|
+
cwd: s.cwd,
|
|
301
|
+
ts: ts(),
|
|
302
|
+
payload: { session_id: s.id }
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (s.status === "awaiting_permission") {
|
|
306
|
+
await postEvent(base, {
|
|
307
|
+
event: "notification",
|
|
308
|
+
pid: s.pid,
|
|
309
|
+
cwd: s.cwd,
|
|
310
|
+
ts: ts(),
|
|
311
|
+
payload: {
|
|
312
|
+
session_id: s.id,
|
|
313
|
+
tool_name: "Bash",
|
|
314
|
+
tool_input: { command: "git push origin main" },
|
|
315
|
+
message: "Permission for git push"
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const active = sessions.filter((s) => s.status === "active");
|
|
321
|
+
for (const s of active) {
|
|
322
|
+
await postEvent(base, {
|
|
323
|
+
event: "pre_tool_file",
|
|
139
324
|
pid: s.pid,
|
|
140
325
|
cwd: s.cwd,
|
|
141
326
|
ts: ts(),
|
|
142
|
-
payload:
|
|
327
|
+
payload: {
|
|
328
|
+
session_id: s.id,
|
|
329
|
+
tool_name: "Read",
|
|
330
|
+
tool_input: { file_path: randomChoice(TOOL_FILES) }
|
|
331
|
+
}
|
|
143
332
|
});
|
|
333
|
+
await sleep(40);
|
|
144
334
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
event: "user_prompt_submit",
|
|
335
|
+
for (const s of active.slice(0, 2)) {
|
|
336
|
+
await postEvent(base, {
|
|
337
|
+
event: "pre_tool_task",
|
|
149
338
|
pid: s.pid,
|
|
150
339
|
cwd: s.cwd,
|
|
151
340
|
ts: ts(),
|
|
152
|
-
payload: { session_id: s.id
|
|
341
|
+
payload: { session_id: s.id }
|
|
153
342
|
});
|
|
154
343
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
344
|
+
if (active[0]) {
|
|
345
|
+
await postJson(base, `/api/sessions/${active[0].id}/context`, { pct: 62 });
|
|
346
|
+
}
|
|
347
|
+
if (active[1]) {
|
|
348
|
+
await postJson(base, `/api/sessions/${active[1].id}/context`, { pct: 89 });
|
|
349
|
+
}
|
|
350
|
+
console.log(`[solix demo] seed complete:`);
|
|
351
|
+
console.log(` \u2022 ${sessions.length} sessions across ${PROJECTS.length} projects`);
|
|
352
|
+
console.log(` \u2022 ${advisors2.length} advisors enabled`);
|
|
353
|
+
console.log(` \u2022 status mix: 5 active, 18 idle, 3 awaiting_permission, 2 awaiting_input, 1 error, 1 plan_review`);
|
|
354
|
+
console.log(` \u2022 2 subagent moons, 1 high-context flare`);
|
|
355
|
+
return { sessions, advisorIds: advisors2.map((a) => a.id) };
|
|
356
|
+
}
|
|
357
|
+
function startTicker(base, state) {
|
|
358
|
+
const intervals = [];
|
|
359
|
+
const activeIds = new Set(
|
|
360
|
+
state.sessions.filter((s) => s.status === "active").map((s) => s.id)
|
|
361
|
+
);
|
|
362
|
+
const idleIds = new Set(
|
|
363
|
+
state.sessions.filter((s) => s.status === "idle").map((s) => s.id)
|
|
364
|
+
);
|
|
365
|
+
const byId = new Map(state.sessions.map((s) => [s.id, s]));
|
|
366
|
+
intervals.push(
|
|
367
|
+
setInterval(() => {
|
|
368
|
+
const candidates = [...activeIds];
|
|
369
|
+
if (candidates.length === 0) return;
|
|
370
|
+
const id = randomChoice(candidates);
|
|
371
|
+
const s = byId.get(id);
|
|
372
|
+
if (!s) return;
|
|
373
|
+
const useBash = Math.random() < 0.4;
|
|
374
|
+
void postEvent(base, {
|
|
375
|
+
event: useBash ? "pre_tool_bash" : "pre_tool_file",
|
|
376
|
+
pid: s.pid,
|
|
377
|
+
cwd: s.cwd,
|
|
162
378
|
ts: ts(),
|
|
163
|
-
payload: {
|
|
164
|
-
session_id:
|
|
165
|
-
|
|
379
|
+
payload: useBash ? { session_id: s.id, command: randomChoice(TOOL_COMMANDS) } : {
|
|
380
|
+
session_id: s.id,
|
|
381
|
+
tool_name: Math.random() < 0.5 ? "Read" : "Edit",
|
|
382
|
+
tool_input: { file_path: randomChoice(TOOL_FILES) }
|
|
166
383
|
}
|
|
167
384
|
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
385
|
+
}, TICKER_COMET_MS)
|
|
386
|
+
);
|
|
387
|
+
intervals.push(
|
|
388
|
+
setInterval(() => {
|
|
389
|
+
if (Math.random() < 0.5 && idleIds.size > 0) {
|
|
390
|
+
const id = randomChoice([...idleIds]);
|
|
391
|
+
const s = byId.get(id);
|
|
392
|
+
if (!s) return;
|
|
393
|
+
idleIds.delete(id);
|
|
394
|
+
activeIds.add(id);
|
|
395
|
+
void postEvent(base, {
|
|
396
|
+
event: "user_prompt_submit",
|
|
397
|
+
pid: s.pid,
|
|
398
|
+
cwd: s.cwd,
|
|
399
|
+
ts: ts(),
|
|
400
|
+
payload: { session_id: id, prompt: randomChoice(PROMPTS) }
|
|
401
|
+
});
|
|
402
|
+
} else if (activeIds.size > 1) {
|
|
403
|
+
const id = randomChoice([...activeIds]);
|
|
404
|
+
const s = byId.get(id);
|
|
405
|
+
if (!s) return;
|
|
406
|
+
activeIds.delete(id);
|
|
407
|
+
idleIds.add(id);
|
|
408
|
+
void postEvent(base, {
|
|
409
|
+
event: "stop",
|
|
410
|
+
pid: s.pid,
|
|
411
|
+
cwd: s.cwd,
|
|
412
|
+
ts: ts(),
|
|
413
|
+
payload: { session_id: id }
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}, TICKER_PROMOTE_MS)
|
|
417
|
+
);
|
|
418
|
+
intervals.push(
|
|
419
|
+
setInterval(() => {
|
|
420
|
+
const actives = [...activeIds];
|
|
421
|
+
if (actives.length < 2) return;
|
|
422
|
+
const finisher = byId.get(randomChoice(actives));
|
|
423
|
+
const starter = byId.get(
|
|
424
|
+
randomChoice(actives.filter((id) => id !== finisher.id))
|
|
425
|
+
);
|
|
426
|
+
void postEvent(base, {
|
|
427
|
+
event: "stop",
|
|
428
|
+
pid: finisher.pid,
|
|
429
|
+
cwd: finisher.cwd,
|
|
430
|
+
ts: ts(),
|
|
431
|
+
payload: { session_id: finisher.id }
|
|
432
|
+
});
|
|
433
|
+
void postEvent(base, {
|
|
434
|
+
event: "user_prompt_submit",
|
|
435
|
+
pid: starter.pid,
|
|
436
|
+
cwd: starter.cwd,
|
|
437
|
+
ts: ts(),
|
|
438
|
+
payload: { session_id: starter.id, prompt: randomChoice(PROMPTS) }
|
|
439
|
+
});
|
|
440
|
+
}, TICKER_MISSION_MS)
|
|
441
|
+
);
|
|
442
|
+
intervals.push(
|
|
443
|
+
setInterval(() => {
|
|
444
|
+
const actives = [...activeIds];
|
|
445
|
+
if (actives.length === 0) return;
|
|
446
|
+
const s = byId.get(randomChoice(actives));
|
|
447
|
+
if (!s) return;
|
|
448
|
+
void postEvent(base, {
|
|
449
|
+
event: "notification",
|
|
450
|
+
pid: s.pid,
|
|
451
|
+
cwd: s.cwd,
|
|
173
452
|
ts: ts(),
|
|
174
453
|
payload: {
|
|
175
|
-
session_id:
|
|
176
|
-
tool_name:
|
|
177
|
-
tool_input: {
|
|
454
|
+
session_id: s.id,
|
|
455
|
+
tool_name: "Bash",
|
|
456
|
+
tool_input: { command: "rm -rf node_modules" },
|
|
457
|
+
message: "Permission for destructive shell command"
|
|
178
458
|
}
|
|
179
459
|
});
|
|
460
|
+
}, TICKER_PERMISSION_MS)
|
|
461
|
+
);
|
|
462
|
+
intervals.push(
|
|
463
|
+
setInterval(() => {
|
|
464
|
+
if (state.advisorIds.length === 0 || activeIds.size === 0) return;
|
|
465
|
+
const advisorId = randomChoice(state.advisorIds);
|
|
466
|
+
const targetSessionId = randomChoice([...activeIds]);
|
|
467
|
+
void postJson(
|
|
468
|
+
base,
|
|
469
|
+
`/api/advisors/${encodeURIComponent(advisorId)}/invoke`,
|
|
470
|
+
{
|
|
471
|
+
targetSessionId,
|
|
472
|
+
prompt: "Spot-check this session before the next mission."
|
|
473
|
+
}
|
|
474
|
+
);
|
|
475
|
+
}, TICKER_ADVISOR_MS)
|
|
476
|
+
);
|
|
477
|
+
return () => {
|
|
478
|
+
for (const i of intervals) clearInterval(i);
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function registerTeardown(opts) {
|
|
482
|
+
let torn = false;
|
|
483
|
+
const onSignal = (sig) => {
|
|
484
|
+
if (torn) return;
|
|
485
|
+
torn = true;
|
|
486
|
+
console.log(`
|
|
487
|
+
[solix demo] received ${sig} \u2014 tearing down\u2026`);
|
|
488
|
+
if (opts.stopTicker) opts.stopTicker();
|
|
489
|
+
if (opts.child) {
|
|
490
|
+
try {
|
|
491
|
+
opts.child.kill();
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
180
494
|
}
|
|
181
|
-
|
|
495
|
+
if (!opts.keep) {
|
|
496
|
+
for (const p of [DEMO_DB_PATH, `${DEMO_DB_PATH}-shm`, `${DEMO_DB_PATH}-wal`, DEMO_PID_PATH]) {
|
|
497
|
+
try {
|
|
498
|
+
if (existsSync(p)) unlinkSync(p);
|
|
499
|
+
} catch {
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
console.log(`[solix demo] sandbox cleaned up.`);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(`[solix demo] --keep set; ${DEMO_DB_PATH} preserved.`);
|
|
505
|
+
}
|
|
506
|
+
process.exit(0);
|
|
507
|
+
};
|
|
508
|
+
process.on("SIGINT", onSignal);
|
|
509
|
+
process.on("SIGTERM", onSignal);
|
|
510
|
+
}
|
|
511
|
+
async function tryOpenBrowser(url) {
|
|
512
|
+
const platform = process.platform;
|
|
513
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
514
|
+
try {
|
|
515
|
+
const child = spawn(cmd, [url], { stdio: "ignore", detached: true });
|
|
516
|
+
child.on("error", () => {
|
|
517
|
+
});
|
|
518
|
+
child.unref();
|
|
519
|
+
} catch {
|
|
182
520
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
payload: {
|
|
196
|
-
session_id: sessions[2].id,
|
|
197
|
-
tool_name: "Bash",
|
|
198
|
-
tool_input: { command: "git push origin main" },
|
|
199
|
-
message: "Permission for git push"
|
|
521
|
+
}
|
|
522
|
+
async function demoCmd(opts = {}) {
|
|
523
|
+
const preferredPort = opts.port ?? 4242;
|
|
524
|
+
const demoRootCwd = opts.cwd ?? join(homedir(), "demo-projects");
|
|
525
|
+
let boot = null;
|
|
526
|
+
if (opts.noServer) {
|
|
527
|
+
if (!await waitForServer(preferredPort, 1e3)) {
|
|
528
|
+
console.error(
|
|
529
|
+
`[solix demo] --no-server set but nothing is listening on ${preferredPort}. Start a server first.`
|
|
530
|
+
);
|
|
531
|
+
process.exitCode = 1;
|
|
532
|
+
return;
|
|
200
533
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
await
|
|
213
|
-
console.log(`[solix demo]
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
534
|
+
boot = { child: null, port: preferredPort, base: baseUrl(preferredPort) };
|
|
535
|
+
} else {
|
|
536
|
+
boot = await bootSandbox(preferredPort);
|
|
537
|
+
if (!boot) {
|
|
538
|
+
process.exitCode = 1;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
console.log(
|
|
542
|
+
`[solix demo] server up at ${boot.base} (sandbox DB at ${DEMO_DB_PATH})`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
const seed = await richSeed(boot.base, demoRootCwd);
|
|
546
|
+
console.log(`[solix demo] open ${boot.base} to see the galaxy.`);
|
|
547
|
+
void tryOpenBrowser(boot.base);
|
|
548
|
+
if (opts.noTicker) {
|
|
549
|
+
console.log(
|
|
550
|
+
`[solix demo] --no-ticker set; static snapshot only. Exiting.`
|
|
551
|
+
);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
221
554
|
console.log(
|
|
222
|
-
`[solix demo]
|
|
555
|
+
`[solix demo] live ticker running. Press Ctrl+C to stop and tear down.`
|
|
223
556
|
);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
})
|
|
557
|
+
const stopTicker = startTicker(boot.base, seed);
|
|
558
|
+
registerTeardown({
|
|
559
|
+
child: boot.child,
|
|
560
|
+
stopTicker,
|
|
561
|
+
keep: opts.keep ?? false
|
|
562
|
+
});
|
|
563
|
+
await new Promise(() => {
|
|
232
564
|
});
|
|
233
|
-
console.log(`[solix demo] Compass invoked. Demo complete.`);
|
|
234
565
|
}
|
|
235
566
|
|
|
236
567
|
// src/doctor.ts
|
|
237
|
-
import { existsSync as
|
|
568
|
+
import { existsSync as existsSync3, readdirSync, statSync } from "fs";
|
|
238
569
|
import { join as join3 } from "path";
|
|
239
570
|
|
|
240
571
|
// src/paths.ts
|
|
241
572
|
import { homedir as homedir2 } from "os";
|
|
242
|
-
import { existsSync } from "fs";
|
|
243
|
-
import { join as join2, dirname } from "path";
|
|
244
|
-
import { fileURLToPath } from "url";
|
|
245
|
-
var
|
|
246
|
-
var HOOKS_DIR = join2(
|
|
247
|
-
var SOLIX_SKILLS_DIR = join2(
|
|
248
|
-
var SOLIX_TOKEN_FILE = join2(
|
|
573
|
+
import { existsSync as existsSync2 } from "fs";
|
|
574
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
575
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
576
|
+
var SOLIX_HOME2 = process.env.SOLIX_HOME ?? join2(homedir2(), ".solix");
|
|
577
|
+
var HOOKS_DIR = join2(SOLIX_HOME2, "hooks");
|
|
578
|
+
var SOLIX_SKILLS_DIR = join2(SOLIX_HOME2, "skills");
|
|
579
|
+
var SOLIX_TOKEN_FILE = join2(SOLIX_HOME2, "token");
|
|
249
580
|
var CLAUDE_DIR = join2(homedir2(), ".claude");
|
|
250
581
|
var CLAUDE_SETTINGS = join2(CLAUDE_DIR, "settings.json");
|
|
251
582
|
var CLAUDE_BACKUP = join2(CLAUDE_DIR, "settings.solix.backup.json");
|
|
@@ -263,38 +594,40 @@ var HOOK_NAMES = [
|
|
|
263
594
|
"notification"
|
|
264
595
|
];
|
|
265
596
|
function packagedHooksDir() {
|
|
266
|
-
const here =
|
|
597
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
267
598
|
const candidates = [
|
|
268
599
|
join2(here, "hooks"),
|
|
269
600
|
join2(here, "..", "hooks"),
|
|
270
601
|
join2(here, "..", "..", "hooks")
|
|
271
602
|
];
|
|
272
603
|
for (const p of candidates) {
|
|
273
|
-
if (
|
|
604
|
+
if (existsSync2(join2(p, "session-start.sh"))) return p;
|
|
274
605
|
}
|
|
275
606
|
return candidates[0];
|
|
276
607
|
}
|
|
277
608
|
function packagedAgentsDir() {
|
|
278
|
-
const here =
|
|
609
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
279
610
|
const candidates = [
|
|
611
|
+
join2(here, "agents"),
|
|
280
612
|
join2(here, "..", "..", "agents"),
|
|
281
613
|
join2(here, "..", "..", "..", "agents"),
|
|
282
614
|
join2(here, "..", "..", "..", "..", "packages", "agents")
|
|
283
615
|
];
|
|
284
616
|
for (const p of candidates) {
|
|
285
|
-
if (
|
|
617
|
+
if (existsSync2(join2(p, "manifest.json"))) return p;
|
|
286
618
|
}
|
|
287
619
|
return candidates[0];
|
|
288
620
|
}
|
|
289
621
|
function packagedSkillsDir() {
|
|
290
|
-
const here =
|
|
622
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
291
623
|
const candidates = [
|
|
624
|
+
join2(here, "skills"),
|
|
292
625
|
join2(here, "..", "..", "skills"),
|
|
293
626
|
join2(here, "..", "..", "..", "skills"),
|
|
294
627
|
join2(here, "..", "..", "..", "..", "packages", "skills")
|
|
295
628
|
];
|
|
296
629
|
for (const p of candidates) {
|
|
297
|
-
if (
|
|
630
|
+
if (existsSync2(p)) return p;
|
|
298
631
|
}
|
|
299
632
|
return candidates[0];
|
|
300
633
|
}
|
|
@@ -395,15 +728,15 @@ async function doctor() {
|
|
|
395
728
|
detail: `v${nodeVersion}`
|
|
396
729
|
});
|
|
397
730
|
checks.push({
|
|
398
|
-
ok:
|
|
731
|
+
ok: existsSync3(SOLIX_HOME2),
|
|
399
732
|
label: "Solix home directory",
|
|
400
|
-
detail:
|
|
733
|
+
detail: SOLIX_HOME2
|
|
401
734
|
});
|
|
402
735
|
let allHooksPresent = true;
|
|
403
736
|
const missing = [];
|
|
404
737
|
for (const name of HOOK_NAMES) {
|
|
405
738
|
const p = join3(HOOKS_DIR, `${name}.sh`);
|
|
406
|
-
if (!
|
|
739
|
+
if (!existsSync3(p)) {
|
|
407
740
|
allHooksPresent = false;
|
|
408
741
|
missing.push(name);
|
|
409
742
|
continue;
|
|
@@ -421,17 +754,17 @@ async function doctor() {
|
|
|
421
754
|
detail: allHooksPresent ? `${HOOK_NAMES.length} scripts in ${HOOKS_DIR}` : `missing: ${missing.join(", ")}`
|
|
422
755
|
});
|
|
423
756
|
checks.push({
|
|
424
|
-
ok:
|
|
757
|
+
ok: existsSync3(CLAUDE_SETTINGS),
|
|
425
758
|
label: "Claude settings.json present",
|
|
426
759
|
detail: CLAUDE_SETTINGS
|
|
427
760
|
});
|
|
428
761
|
checks.push({
|
|
429
|
-
ok:
|
|
762
|
+
ok: existsSync3(CLAUDE_BACKUP),
|
|
430
763
|
label: "Backup of settings.json",
|
|
431
|
-
detail:
|
|
764
|
+
detail: existsSync3(CLAUDE_BACKUP) ? CLAUDE_BACKUP : "not yet created"
|
|
432
765
|
});
|
|
433
766
|
let advisorCount = 0;
|
|
434
|
-
if (
|
|
767
|
+
if (existsSync3(CLAUDE_AGENTS_DIR)) {
|
|
435
768
|
try {
|
|
436
769
|
advisorCount = readdirSync(CLAUDE_AGENTS_DIR).filter(
|
|
437
770
|
(f) => f.endsWith(".md")
|
|
@@ -446,7 +779,7 @@ async function doctor() {
|
|
|
446
779
|
detail: advisorCount > 0 ? `${advisorCount} agents in ${CLAUDE_AGENTS_DIR}` : "none yet \u2014 run `solix install`"
|
|
447
780
|
});
|
|
448
781
|
let skillCount = 0;
|
|
449
|
-
if (
|
|
782
|
+
if (existsSync3(SOLIX_SKILLS_DIR)) {
|
|
450
783
|
try {
|
|
451
784
|
skillCount = readdirSync(SOLIX_SKILLS_DIR).filter((entry) => {
|
|
452
785
|
try {
|
|
@@ -487,11 +820,11 @@ async function doctor() {
|
|
|
487
820
|
}
|
|
488
821
|
|
|
489
822
|
// src/galaxy.ts
|
|
490
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
491
|
-
var
|
|
492
|
-
var
|
|
823
|
+
import { readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
824
|
+
var PORT2 = process.env.SOLIX_PORT ?? "4242";
|
|
825
|
+
var BASE2 = `http://127.0.0.1:${PORT2}`;
|
|
493
826
|
async function api2(path, init) {
|
|
494
|
-
const res = await fetch(`${
|
|
827
|
+
const res = await fetch(`${BASE2}${path}`, init);
|
|
495
828
|
if (!res.ok) {
|
|
496
829
|
const text = await res.text().catch(() => "");
|
|
497
830
|
throw new Error(`HTTP ${res.status} on ${path}: ${text}`);
|
|
@@ -518,7 +851,7 @@ async function exportGalaxyCmd(outFile, opts = {}) {
|
|
|
518
851
|
}
|
|
519
852
|
async function publishGalaxyCmd(slug, opts = {}) {
|
|
520
853
|
try {
|
|
521
|
-
const res = await fetch(`${
|
|
854
|
+
const res = await fetch(`${BASE2}/api/galaxy/publish`, {
|
|
522
855
|
method: "POST",
|
|
523
856
|
headers: { "Content-Type": "application/json" },
|
|
524
857
|
body: JSON.stringify({ slug, ...opts })
|
|
@@ -543,7 +876,7 @@ async function publishGalaxyCmd(slug, opts = {}) {
|
|
|
543
876
|
async function installFromRegistryCmd(slug) {
|
|
544
877
|
try {
|
|
545
878
|
const res = await fetch(
|
|
546
|
-
`${
|
|
879
|
+
`${BASE2}/api/galaxy/registry/${encodeURIComponent(slug)}/install`,
|
|
547
880
|
{ method: "POST" }
|
|
548
881
|
);
|
|
549
882
|
const data = await res.json();
|
|
@@ -568,7 +901,7 @@ async function importGalaxyCmd(fileOrUrl) {
|
|
|
568
901
|
if (fileOrUrl.startsWith("http://") || fileOrUrl.startsWith("https://")) {
|
|
569
902
|
body = JSON.stringify({ url: fileOrUrl });
|
|
570
903
|
} else {
|
|
571
|
-
const text =
|
|
904
|
+
const text = readFileSync2(fileOrUrl, "utf8");
|
|
572
905
|
body = text;
|
|
573
906
|
}
|
|
574
907
|
const res = await api2(`/api/galaxy/import`, {
|
|
@@ -594,10 +927,10 @@ async function importGalaxyCmd(fileOrUrl) {
|
|
|
594
927
|
import {
|
|
595
928
|
copyFileSync,
|
|
596
929
|
cpSync,
|
|
597
|
-
existsSync as
|
|
598
|
-
mkdirSync,
|
|
930
|
+
existsSync as existsSync4,
|
|
931
|
+
mkdirSync as mkdirSync2,
|
|
599
932
|
readdirSync as readdirSync2,
|
|
600
|
-
readFileSync as
|
|
933
|
+
readFileSync as readFileSync3,
|
|
601
934
|
statSync as statSync2,
|
|
602
935
|
writeFileSync as writeFileSync2,
|
|
603
936
|
chmodSync
|
|
@@ -605,9 +938,9 @@ import {
|
|
|
605
938
|
import { randomBytes } from "crypto";
|
|
606
939
|
import { join as join4 } from "path";
|
|
607
940
|
function readSettings() {
|
|
608
|
-
if (!
|
|
941
|
+
if (!existsSync4(CLAUDE_SETTINGS)) return {};
|
|
609
942
|
try {
|
|
610
|
-
const txt =
|
|
943
|
+
const txt = readFileSync3(CLAUDE_SETTINGS, "utf8");
|
|
611
944
|
return JSON.parse(txt);
|
|
612
945
|
} catch (err) {
|
|
613
946
|
console.warn(`[solix] could not parse ${CLAUDE_SETTINGS}: ${String(err)}`);
|
|
@@ -657,7 +990,7 @@ function mergeHooks(existing, solix) {
|
|
|
657
990
|
return merged;
|
|
658
991
|
}
|
|
659
992
|
function ensureToken() {
|
|
660
|
-
if (
|
|
993
|
+
if (existsSync4(SOLIX_TOKEN_FILE)) return;
|
|
661
994
|
const token = randomBytes(24).toString("hex");
|
|
662
995
|
writeFileSync2(SOLIX_TOKEN_FILE, token, { mode: 384 });
|
|
663
996
|
try {
|
|
@@ -666,9 +999,9 @@ function ensureToken() {
|
|
|
666
999
|
}
|
|
667
1000
|
}
|
|
668
1001
|
function installHookScripts() {
|
|
669
|
-
|
|
1002
|
+
mkdirSync2(HOOKS_DIR, { recursive: true });
|
|
670
1003
|
const src = packagedHooksDir();
|
|
671
|
-
if (!
|
|
1004
|
+
if (!existsSync4(src)) {
|
|
672
1005
|
throw new Error(
|
|
673
1006
|
`Solix hook scripts not found at ${src}. Did the package build correctly?`
|
|
674
1007
|
);
|
|
@@ -682,19 +1015,19 @@ function installHookScripts() {
|
|
|
682
1015
|
}
|
|
683
1016
|
function installAdvisorAgents() {
|
|
684
1017
|
const src = packagedAgentsDir();
|
|
685
|
-
if (!
|
|
1018
|
+
if (!existsSync4(src)) {
|
|
686
1019
|
console.warn(`[solix] no advisors/ directory at ${src}; skipping`);
|
|
687
1020
|
return 0;
|
|
688
1021
|
}
|
|
689
1022
|
const manifestPath = join4(src, "manifest.json");
|
|
690
|
-
if (!
|
|
691
|
-
const manifest = JSON.parse(
|
|
692
|
-
|
|
1023
|
+
if (!existsSync4(manifestPath)) return 0;
|
|
1024
|
+
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
1025
|
+
mkdirSync2(CLAUDE_AGENTS_DIR, { recursive: true });
|
|
693
1026
|
let copied = 0;
|
|
694
1027
|
for (const a of manifest.advisors) {
|
|
695
1028
|
const from = join4(src, a.agentMd);
|
|
696
1029
|
const to = join4(CLAUDE_AGENTS_DIR, a.agentMd);
|
|
697
|
-
if (!
|
|
1030
|
+
if (!existsSync4(from)) continue;
|
|
698
1031
|
copyFileSync(from, to);
|
|
699
1032
|
copied += 1;
|
|
700
1033
|
}
|
|
@@ -702,8 +1035,8 @@ function installAdvisorAgents() {
|
|
|
702
1035
|
}
|
|
703
1036
|
function installSolixSkills() {
|
|
704
1037
|
const src = packagedSkillsDir();
|
|
705
|
-
if (!
|
|
706
|
-
|
|
1038
|
+
if (!existsSync4(src)) return 0;
|
|
1039
|
+
mkdirSync2(SOLIX_SKILLS_DIR, { recursive: true });
|
|
707
1040
|
let copied = 0;
|
|
708
1041
|
for (const entry of readdirSync2(src)) {
|
|
709
1042
|
const fromDir = join4(src, entry);
|
|
@@ -721,13 +1054,13 @@ function installSolixSkills() {
|
|
|
721
1054
|
return copied;
|
|
722
1055
|
}
|
|
723
1056
|
function install(opts = {}) {
|
|
724
|
-
|
|
725
|
-
|
|
1057
|
+
mkdirSync2(SOLIX_HOME2, { recursive: true });
|
|
1058
|
+
mkdirSync2(CLAUDE_DIR, { recursive: true });
|
|
726
1059
|
const existing = readSettings();
|
|
727
|
-
if (
|
|
1060
|
+
if (existsSync4(CLAUDE_SETTINGS) && !existsSync4(CLAUDE_BACKUP)) {
|
|
728
1061
|
copyFileSync(CLAUDE_SETTINGS, CLAUDE_BACKUP);
|
|
729
1062
|
console.log(`[solix] backed up settings.json -> ${CLAUDE_BACKUP}`);
|
|
730
|
-
} else if (opts.force &&
|
|
1063
|
+
} else if (opts.force && existsSync4(CLAUDE_SETTINGS)) {
|
|
731
1064
|
copyFileSync(CLAUDE_SETTINGS, CLAUDE_BACKUP);
|
|
732
1065
|
}
|
|
733
1066
|
ensureToken();
|
|
@@ -754,8 +1087,8 @@ function install(opts = {}) {
|
|
|
754
1087
|
// src/install-shim.ts
|
|
755
1088
|
import {
|
|
756
1089
|
appendFileSync,
|
|
757
|
-
existsSync as
|
|
758
|
-
readFileSync as
|
|
1090
|
+
existsSync as existsSync5,
|
|
1091
|
+
readFileSync as readFileSync4,
|
|
759
1092
|
writeFileSync as writeFileSync3
|
|
760
1093
|
} from "fs";
|
|
761
1094
|
import { homedir as homedir3 } from "os";
|
|
@@ -765,19 +1098,19 @@ var BLOCK_END = "# <<< solix shim <<<";
|
|
|
765
1098
|
function detectShellRcPath() {
|
|
766
1099
|
const shell = process.env.SHELL ?? "";
|
|
767
1100
|
const home = homedir3();
|
|
768
|
-
if (shell.endsWith("zsh") ||
|
|
1101
|
+
if (shell.endsWith("zsh") || existsSync5(join5(home, ".zshrc"))) {
|
|
769
1102
|
return join5(home, ".zshrc");
|
|
770
1103
|
}
|
|
771
|
-
if (shell.endsWith("bash") ||
|
|
1104
|
+
if (shell.endsWith("bash") || existsSync5(join5(home, ".bashrc"))) {
|
|
772
1105
|
return join5(home, ".bashrc");
|
|
773
1106
|
}
|
|
774
|
-
if (
|
|
1107
|
+
if (existsSync5(join5(home, ".bash_profile"))) {
|
|
775
1108
|
return join5(home, ".bash_profile");
|
|
776
1109
|
}
|
|
777
1110
|
return null;
|
|
778
1111
|
}
|
|
779
1112
|
function readRc(rcPath) {
|
|
780
|
-
return
|
|
1113
|
+
return existsSync5(rcPath) ? readFileSync4(rcPath, "utf8") : "";
|
|
781
1114
|
}
|
|
782
1115
|
function blockText() {
|
|
783
1116
|
return [
|
|
@@ -828,15 +1161,15 @@ function uninstallShim() {
|
|
|
828
1161
|
|
|
829
1162
|
// src/run.ts
|
|
830
1163
|
import { createServer as createUnixServer } from "net";
|
|
831
|
-
import { mkdirSync as
|
|
1164
|
+
import { mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
832
1165
|
import { homedir as homedir4 } from "os";
|
|
833
1166
|
import { join as join6 } from "path";
|
|
834
1167
|
import { nanoid } from "nanoid";
|
|
835
|
-
var
|
|
836
|
-
var
|
|
1168
|
+
var PORT3 = process.env.SOLIX_PORT ?? "4242";
|
|
1169
|
+
var BASE3 = `http://127.0.0.1:${PORT3}`;
|
|
837
1170
|
async function registerWithServer(payload) {
|
|
838
1171
|
try {
|
|
839
|
-
const res = await fetch(`${
|
|
1172
|
+
const res = await fetch(`${BASE3}/api/wrappers/register`, {
|
|
840
1173
|
method: "POST",
|
|
841
1174
|
headers: { "Content-Type": "application/json" },
|
|
842
1175
|
body: JSON.stringify(payload),
|
|
@@ -850,7 +1183,7 @@ async function registerWithServer(payload) {
|
|
|
850
1183
|
async function unregisterFromServer(wrapperId) {
|
|
851
1184
|
try {
|
|
852
1185
|
await fetch(
|
|
853
|
-
`${
|
|
1186
|
+
`${BASE3}/api/wrappers/${encodeURIComponent(wrapperId)}/unregister`,
|
|
854
1187
|
{ method: "POST", signal: AbortSignal.timeout(800) }
|
|
855
1188
|
);
|
|
856
1189
|
} catch {
|
|
@@ -869,7 +1202,7 @@ async function runWrapped(args) {
|
|
|
869
1202
|
}
|
|
870
1203
|
const wrapperId = nanoid(10);
|
|
871
1204
|
const sockDir = join6(homedir4(), ".solix", "wrappers");
|
|
872
|
-
|
|
1205
|
+
mkdirSync3(sockDir, { recursive: true });
|
|
873
1206
|
const socketPath = join6(sockDir, `${wrapperId}.sock`);
|
|
874
1207
|
const cwd = process.cwd();
|
|
875
1208
|
const registered = await registerWithServer({ wrapperId, socketPath, cwd });
|
|
@@ -931,7 +1264,7 @@ async function runWrapped(args) {
|
|
|
931
1264
|
);
|
|
932
1265
|
} else {
|
|
933
1266
|
process.stderr.write(
|
|
934
|
-
`[solix run] note: Solix server not reachable at ${
|
|
1267
|
+
`[solix run] note: Solix server not reachable at ${BASE3}; claude will run normally, but the UI composer won't be active.
|
|
935
1268
|
`
|
|
936
1269
|
);
|
|
937
1270
|
}
|
|
@@ -944,7 +1277,7 @@ async function runWrapped(args) {
|
|
|
944
1277
|
} catch {
|
|
945
1278
|
}
|
|
946
1279
|
try {
|
|
947
|
-
|
|
1280
|
+
unlinkSync2(socketPath);
|
|
948
1281
|
} catch {
|
|
949
1282
|
}
|
|
950
1283
|
if (registered) await unregisterFromServer(wrapperId);
|
|
@@ -972,10 +1305,10 @@ async function runWrapped(args) {
|
|
|
972
1305
|
}
|
|
973
1306
|
|
|
974
1307
|
// src/skills.ts
|
|
975
|
-
var
|
|
976
|
-
var
|
|
1308
|
+
var PORT4 = process.env.SOLIX_PORT ?? "4242";
|
|
1309
|
+
var BASE4 = `http://127.0.0.1:${PORT4}`;
|
|
977
1310
|
async function api3(path, init) {
|
|
978
|
-
const res = await fetch(`${
|
|
1311
|
+
const res = await fetch(`${BASE4}${path}`, init);
|
|
979
1312
|
if (!res.ok) {
|
|
980
1313
|
const text = await res.text().catch(() => "");
|
|
981
1314
|
throw new Error(`HTTP ${res.status} on ${path}: ${text}`);
|
|
@@ -999,7 +1332,7 @@ async function listSkillsCmd() {
|
|
|
999
1332
|
);
|
|
1000
1333
|
}
|
|
1001
1334
|
} catch (err) {
|
|
1002
|
-
console.error(`[solix] could not reach server at ${
|
|
1335
|
+
console.error(`[solix] could not reach server at ${BASE4}: ${String(err)}`);
|
|
1003
1336
|
process.exitCode = 1;
|
|
1004
1337
|
}
|
|
1005
1338
|
}
|
|
@@ -1033,10 +1366,10 @@ async function installSkillCmd(id, projectId) {
|
|
|
1033
1366
|
}
|
|
1034
1367
|
|
|
1035
1368
|
// src/schedule.ts
|
|
1036
|
-
var
|
|
1037
|
-
var
|
|
1369
|
+
var PORT5 = process.env.SOLIX_PORT ?? "4242";
|
|
1370
|
+
var BASE5 = `http://127.0.0.1:${PORT5}`;
|
|
1038
1371
|
async function api4(path, init) {
|
|
1039
|
-
const res = await fetch(`${
|
|
1372
|
+
const res = await fetch(`${BASE5}${path}`, {
|
|
1040
1373
|
...init,
|
|
1041
1374
|
headers: { "content-type": "application/json", ...init?.headers ?? {} }
|
|
1042
1375
|
});
|
|
@@ -1047,7 +1380,7 @@ async function api4(path, init) {
|
|
|
1047
1380
|
return await res.json();
|
|
1048
1381
|
}
|
|
1049
1382
|
function unreachable(err) {
|
|
1050
|
-
console.error(`[solix] could not reach server at ${
|
|
1383
|
+
console.error(`[solix] could not reach server at ${BASE5}: ${String(err)}`);
|
|
1051
1384
|
console.error("[solix] is `solix start` running?");
|
|
1052
1385
|
process.exitCode = 1;
|
|
1053
1386
|
}
|
|
@@ -1109,10 +1442,10 @@ async function removeScheduleCmd(id) {
|
|
|
1109
1442
|
}
|
|
1110
1443
|
|
|
1111
1444
|
// src/goals.ts
|
|
1112
|
-
var
|
|
1113
|
-
var
|
|
1445
|
+
var PORT6 = process.env.SOLIX_PORT ?? "4242";
|
|
1446
|
+
var BASE6 = `http://127.0.0.1:${PORT6}`;
|
|
1114
1447
|
async function api5(path, init) {
|
|
1115
|
-
const res = await fetch(`${
|
|
1448
|
+
const res = await fetch(`${BASE6}${path}`, {
|
|
1116
1449
|
...init,
|
|
1117
1450
|
headers: { "content-type": "application/json", ...init?.headers ?? {} }
|
|
1118
1451
|
});
|
|
@@ -1123,19 +1456,19 @@ async function api5(path, init) {
|
|
|
1123
1456
|
return await res.json();
|
|
1124
1457
|
}
|
|
1125
1458
|
function unreachable2(err) {
|
|
1126
|
-
console.error(`[solix] could not reach server at ${
|
|
1459
|
+
console.error(`[solix] could not reach server at ${BASE6}: ${String(err)}`);
|
|
1127
1460
|
console.error("[solix] is `solix start` running?");
|
|
1128
1461
|
process.exitCode = 1;
|
|
1129
1462
|
}
|
|
1130
1463
|
async function listGoalsCmd() {
|
|
1131
1464
|
try {
|
|
1132
|
-
const
|
|
1133
|
-
if (!
|
|
1465
|
+
const goals2 = await api5("/api/goals");
|
|
1466
|
+
if (!goals2.length) {
|
|
1134
1467
|
console.log('No goals. Add one with `solix goal add "<name>"`.');
|
|
1135
1468
|
return;
|
|
1136
1469
|
}
|
|
1137
1470
|
console.log("id color name");
|
|
1138
|
-
for (const g of
|
|
1471
|
+
for (const g of goals2) {
|
|
1139
1472
|
console.log(` ${g.id.padEnd(8)} ${g.color.padEnd(8)} ${g.name}`);
|
|
1140
1473
|
}
|
|
1141
1474
|
} catch (err) {
|
|
@@ -1168,7 +1501,7 @@ async function removeGoalCmd(id) {
|
|
|
1168
1501
|
|
|
1169
1502
|
// ../server/src/create.ts
|
|
1170
1503
|
import { serve } from "@hono/node-server";
|
|
1171
|
-
import { readFileSync as
|
|
1504
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
1172
1505
|
import { homedir as homedir11 } from "os";
|
|
1173
1506
|
import { join as join15 } from "path";
|
|
1174
1507
|
|
|
@@ -1204,14 +1537,14 @@ import Database from "better-sqlite3";
|
|
|
1204
1537
|
// ../server/src/paths.ts
|
|
1205
1538
|
import { homedir as homedir5 } from "os";
|
|
1206
1539
|
import { join as join7 } from "path";
|
|
1207
|
-
import { mkdirSync as
|
|
1208
|
-
var
|
|
1209
|
-
var DB_PATH = join7(
|
|
1210
|
-
var HOOKS_DIR2 = join7(
|
|
1211
|
-
var LOG_PATH = join7(
|
|
1540
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
1541
|
+
var SOLIX_HOME3 = process.env.SOLIX_HOME ?? join7(homedir5(), ".solix");
|
|
1542
|
+
var DB_PATH = process.env.SOLIX_DB_PATH ?? join7(SOLIX_HOME3, "solix.db");
|
|
1543
|
+
var HOOKS_DIR2 = join7(SOLIX_HOME3, "hooks");
|
|
1544
|
+
var LOG_PATH = join7(SOLIX_HOME3, "solix.log");
|
|
1212
1545
|
function ensureSolixHome() {
|
|
1213
|
-
|
|
1214
|
-
|
|
1546
|
+
mkdirSync4(SOLIX_HOME3, { recursive: true });
|
|
1547
|
+
mkdirSync4(HOOKS_DIR2, { recursive: true });
|
|
1215
1548
|
}
|
|
1216
1549
|
|
|
1217
1550
|
// ../server/src/db.ts
|
|
@@ -1390,9 +1723,9 @@ function getDb() {
|
|
|
1390
1723
|
}
|
|
1391
1724
|
|
|
1392
1725
|
// ../server/src/http.ts
|
|
1393
|
-
import { existsSync as
|
|
1394
|
-
import { dirname as
|
|
1395
|
-
import { fileURLToPath as
|
|
1726
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
1727
|
+
import { dirname as dirname5, extname, join as join11, resolve as resolve3 } from "path";
|
|
1728
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1396
1729
|
import { spawnSync } from "child_process";
|
|
1397
1730
|
import { Hono } from "hono";
|
|
1398
1731
|
import { cors } from "hono/cors";
|
|
@@ -1974,14 +2307,14 @@ function listAudit(db, opts = {}) {
|
|
|
1974
2307
|
}
|
|
1975
2308
|
|
|
1976
2309
|
// ../server/src/state/advisors.ts
|
|
1977
|
-
import { existsSync as
|
|
1978
|
-
import { dirname as
|
|
1979
|
-
import { fileURLToPath as
|
|
2310
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
2311
|
+
import { dirname as dirname3, join as join8, resolve } from "path";
|
|
2312
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1980
2313
|
function findAgentsDir() {
|
|
1981
|
-
if (process.env.SOLIX_AGENTS_DIR &&
|
|
2314
|
+
if (process.env.SOLIX_AGENTS_DIR && existsSync6(process.env.SOLIX_AGENTS_DIR)) {
|
|
1982
2315
|
return process.env.SOLIX_AGENTS_DIR;
|
|
1983
2316
|
}
|
|
1984
|
-
const here =
|
|
2317
|
+
const here = dirname3(fileURLToPath3(import.meta.url));
|
|
1985
2318
|
const candidates = [
|
|
1986
2319
|
// Bundled npm package: agents/ ships next to the bundled JS file.
|
|
1987
2320
|
resolve(here, "agents"),
|
|
@@ -1991,17 +2324,17 @@ function findAgentsDir() {
|
|
|
1991
2324
|
resolve(process.cwd(), "packages", "agents")
|
|
1992
2325
|
];
|
|
1993
2326
|
for (const c of candidates) {
|
|
1994
|
-
if (
|
|
2327
|
+
if (existsSync6(join8(c, "manifest.json"))) return c;
|
|
1995
2328
|
}
|
|
1996
2329
|
return candidates[0];
|
|
1997
2330
|
}
|
|
1998
2331
|
var AGENTS_DIR = findAgentsDir();
|
|
1999
2332
|
function readManifest() {
|
|
2000
2333
|
const path = join8(AGENTS_DIR, "manifest.json");
|
|
2001
|
-
if (!
|
|
2334
|
+
if (!existsSync6(path)) {
|
|
2002
2335
|
return { version: 1, advisors: [] };
|
|
2003
2336
|
}
|
|
2004
|
-
return JSON.parse(
|
|
2337
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
2005
2338
|
}
|
|
2006
2339
|
function rowToAdvisor(row) {
|
|
2007
2340
|
let requiredSkills = [];
|
|
@@ -2102,15 +2435,15 @@ function setAdvisorPinned(db, id, pinned, sessionId) {
|
|
|
2102
2435
|
return getAdvisor(db, id);
|
|
2103
2436
|
}
|
|
2104
2437
|
function readAdvisorAgentMd(advisor) {
|
|
2105
|
-
if (!
|
|
2438
|
+
if (!existsSync6(advisor.agentMdPath)) {
|
|
2106
2439
|
return "";
|
|
2107
2440
|
}
|
|
2108
|
-
return
|
|
2441
|
+
return readFileSync5(advisor.agentMdPath, "utf8");
|
|
2109
2442
|
}
|
|
2110
2443
|
|
|
2111
2444
|
// ../server/src/state/wrappers.ts
|
|
2112
2445
|
import { connect } from "net";
|
|
2113
|
-
import { existsSync as
|
|
2446
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2114
2447
|
import { homedir as homedir6 } from "os";
|
|
2115
2448
|
import { join as join9 } from "path";
|
|
2116
2449
|
var wrappers = /* @__PURE__ */ new Map();
|
|
@@ -2133,12 +2466,12 @@ function listWrappers() {
|
|
|
2133
2466
|
}
|
|
2134
2467
|
function cleanupOrphanedSockets() {
|
|
2135
2468
|
const dir = join9(homedir6(), ".solix", "wrappers");
|
|
2136
|
-
if (!
|
|
2469
|
+
if (!existsSync7(dir)) return 0;
|
|
2137
2470
|
let removed = 0;
|
|
2138
2471
|
for (const f of readdirSync3(dir)) {
|
|
2139
2472
|
if (!f.endsWith(".sock")) continue;
|
|
2140
2473
|
try {
|
|
2141
|
-
|
|
2474
|
+
unlinkSync3(join9(dir, f));
|
|
2142
2475
|
removed++;
|
|
2143
2476
|
} catch {
|
|
2144
2477
|
}
|
|
@@ -2159,7 +2492,7 @@ function claimWrapperForCwd(cwd) {
|
|
|
2159
2492
|
return best;
|
|
2160
2493
|
}
|
|
2161
2494
|
function writeToWrapperSocket(socketPath, text) {
|
|
2162
|
-
if (!
|
|
2495
|
+
if (!existsSync7(socketPath)) return false;
|
|
2163
2496
|
try {
|
|
2164
2497
|
const client = connect(socketPath);
|
|
2165
2498
|
client.on("error", () => {
|
|
@@ -2395,19 +2728,19 @@ function buildContextEnvelope(db, args) {
|
|
|
2395
2728
|
}
|
|
2396
2729
|
|
|
2397
2730
|
// ../server/src/state/skills.ts
|
|
2398
|
-
import { existsSync as
|
|
2399
|
-
import { dirname as
|
|
2400
|
-
import { fileURLToPath as
|
|
2731
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
2732
|
+
import { dirname as dirname4, join as join10, resolve as resolve2 } from "path";
|
|
2733
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2401
2734
|
import { homedir as homedir7 } from "os";
|
|
2402
2735
|
function findSolixSkillsDir() {
|
|
2403
|
-
const here =
|
|
2736
|
+
const here = dirname4(fileURLToPath4(import.meta.url));
|
|
2404
2737
|
const candidates = [
|
|
2405
2738
|
resolve2(here, "..", "..", "..", "skills"),
|
|
2406
2739
|
resolve2(here, "..", "..", "skills"),
|
|
2407
2740
|
resolve2(process.cwd(), "packages", "skills")
|
|
2408
2741
|
];
|
|
2409
2742
|
for (const c of candidates) {
|
|
2410
|
-
if (
|
|
2743
|
+
if (existsSync8(c)) return c;
|
|
2411
2744
|
}
|
|
2412
2745
|
return candidates[0];
|
|
2413
2746
|
}
|
|
@@ -2415,7 +2748,7 @@ var SOLIX_SKILLS_DIR2 = findSolixSkillsDir();
|
|
|
2415
2748
|
var ANTHROPIC_SKILLS_DIR = join10(homedir7(), ".claude", "skills");
|
|
2416
2749
|
function parseSkillManifest(manifestPath, fallbackId) {
|
|
2417
2750
|
try {
|
|
2418
|
-
const txt =
|
|
2751
|
+
const txt = readFileSync6(manifestPath, "utf8");
|
|
2419
2752
|
const match = txt.match(/^---\n([\s\S]*?)\n---/);
|
|
2420
2753
|
let name = fallbackId;
|
|
2421
2754
|
let description = "";
|
|
@@ -2464,7 +2797,7 @@ function discoverSkills(db) {
|
|
|
2464
2797
|
{ dir: SOLIX_SKILLS_DIR2, source: "solix" }
|
|
2465
2798
|
];
|
|
2466
2799
|
for (const { dir, source } of sources) {
|
|
2467
|
-
if (!
|
|
2800
|
+
if (!existsSync8(dir)) continue;
|
|
2468
2801
|
for (const entry of readdirSync4(dir)) {
|
|
2469
2802
|
const full = join10(dir, entry);
|
|
2470
2803
|
let isDir = false;
|
|
@@ -2475,7 +2808,7 @@ function discoverSkills(db) {
|
|
|
2475
2808
|
}
|
|
2476
2809
|
if (!isDir) continue;
|
|
2477
2810
|
const manifestPath = join10(full, "SKILL.md");
|
|
2478
|
-
if (!
|
|
2811
|
+
if (!existsSync8(manifestPath)) continue;
|
|
2479
2812
|
const parsed = parseSkillManifest(manifestPath, entry);
|
|
2480
2813
|
if (!parsed) continue;
|
|
2481
2814
|
const id = `${source}:${parsed.id}`;
|
|
@@ -2500,8 +2833,8 @@ function getSkill(db, id) {
|
|
|
2500
2833
|
return row ? rowToSkill(row) : null;
|
|
2501
2834
|
}
|
|
2502
2835
|
function readSkillManifest(skill) {
|
|
2503
|
-
if (!
|
|
2504
|
-
return
|
|
2836
|
+
if (!existsSync8(skill.manifestPath)) return "";
|
|
2837
|
+
return readFileSync6(skill.manifestPath, "utf8");
|
|
2505
2838
|
}
|
|
2506
2839
|
function recordSkillInstall(db, skillId, projectId) {
|
|
2507
2840
|
const skill = getSkill(db, skillId);
|
|
@@ -2726,8 +3059,8 @@ function totalTokens(usage) {
|
|
|
2726
3059
|
|
|
2727
3060
|
// ../server/src/cloud.ts
|
|
2728
3061
|
var RegistryClient = class {
|
|
2729
|
-
constructor(
|
|
2730
|
-
this.baseUrl =
|
|
3062
|
+
constructor(baseUrl2 = process.env.SOLIX_REGISTRY_URL ?? "", apiKey = process.env.SOLIX_REGISTRY_KEY) {
|
|
3063
|
+
this.baseUrl = baseUrl2;
|
|
2731
3064
|
this.apiKey = apiKey;
|
|
2732
3065
|
}
|
|
2733
3066
|
baseUrl;
|
|
@@ -2797,6 +3130,42 @@ var RegistryClient = class {
|
|
|
2797
3130
|
}
|
|
2798
3131
|
};
|
|
2799
3132
|
|
|
3133
|
+
// ../server/src/origins.ts
|
|
3134
|
+
function isAllowedOrigin(origin) {
|
|
3135
|
+
if (!origin) return true;
|
|
3136
|
+
let hostname;
|
|
3137
|
+
try {
|
|
3138
|
+
hostname = new URL(origin).hostname;
|
|
3139
|
+
} catch {
|
|
3140
|
+
return false;
|
|
3141
|
+
}
|
|
3142
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1" || hostname === "[::1]";
|
|
3143
|
+
}
|
|
3144
|
+
function isSafeFetchUrl(raw) {
|
|
3145
|
+
let u;
|
|
3146
|
+
try {
|
|
3147
|
+
u = new URL(raw);
|
|
3148
|
+
} catch {
|
|
3149
|
+
return false;
|
|
3150
|
+
}
|
|
3151
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
3152
|
+
const h = u.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
3153
|
+
if (h === "localhost" || h === "::1" || h.endsWith(".localhost")) return false;
|
|
3154
|
+
const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
3155
|
+
if (m) {
|
|
3156
|
+
const a = Number(m[1]);
|
|
3157
|
+
const b = Number(m[2]);
|
|
3158
|
+
if (a === 0 || a === 127 || a === 10) return false;
|
|
3159
|
+
if (a === 169 && b === 254) return false;
|
|
3160
|
+
if (a === 172 && b >= 16 && b <= 31) return false;
|
|
3161
|
+
if (a === 192 && b === 168) return false;
|
|
3162
|
+
}
|
|
3163
|
+
if (h.startsWith("fc") || h.startsWith("fd") || h.startsWith("fe80")) {
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
return true;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
2800
3169
|
// ../server/src/http.ts
|
|
2801
3170
|
function isAgentViewVersion(version) {
|
|
2802
3171
|
if (!version) return false;
|
|
@@ -2824,6 +3193,14 @@ function createHttpApp(opts) {
|
|
|
2824
3193
|
]
|
|
2825
3194
|
})
|
|
2826
3195
|
);
|
|
3196
|
+
app.use("*", async (c, next) => {
|
|
3197
|
+
const method = c.req.method;
|
|
3198
|
+
const mutating = method !== "GET" && method !== "HEAD" && method !== "OPTIONS";
|
|
3199
|
+
if (mutating && !isAllowedOrigin(c.req.header("origin"))) {
|
|
3200
|
+
return c.json({ error: "cross-origin request refused" }, 403);
|
|
3201
|
+
}
|
|
3202
|
+
await next();
|
|
3203
|
+
});
|
|
2827
3204
|
if (opts.token) {
|
|
2828
3205
|
const expected = opts.token;
|
|
2829
3206
|
const paths = ["/events", "/events/permission"];
|
|
@@ -2842,7 +3219,7 @@ function createHttpApp(opts) {
|
|
|
2842
3219
|
(c) => c.json({
|
|
2843
3220
|
ok: true,
|
|
2844
3221
|
service: "solix",
|
|
2845
|
-
version:
|
|
3222
|
+
version: opts.version ?? "unknown",
|
|
2846
3223
|
ts: Date.now()
|
|
2847
3224
|
})
|
|
2848
3225
|
);
|
|
@@ -3058,6 +3435,12 @@ function createHttpApp(opts) {
|
|
|
3058
3435
|
let sourceUrl;
|
|
3059
3436
|
if ("url" in body && typeof body.url === "string") {
|
|
3060
3437
|
sourceUrl = body.url;
|
|
3438
|
+
if (!isSafeFetchUrl(body.url)) {
|
|
3439
|
+
return c.json(
|
|
3440
|
+
{ error: "import URL not allowed (must be a public http(s) address)" },
|
|
3441
|
+
400
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3061
3444
|
try {
|
|
3062
3445
|
const res = await fetch(body.url, {
|
|
3063
3446
|
signal: AbortSignal.timeout(5e3)
|
|
@@ -3117,14 +3500,14 @@ function createHttpApp(opts) {
|
|
|
3117
3500
|
if (!body.cwd || !body.prompt || !body.cadence) {
|
|
3118
3501
|
return c.json({ error: "cwd, prompt, cadence required" }, 400);
|
|
3119
3502
|
}
|
|
3120
|
-
const
|
|
3503
|
+
const schedule = createSchedule(opts.db, {
|
|
3121
3504
|
cwd: body.cwd,
|
|
3122
3505
|
prompt: body.prompt,
|
|
3123
3506
|
cadence: body.cadence,
|
|
3124
3507
|
name: body.name
|
|
3125
3508
|
});
|
|
3126
|
-
opts.router.broadcastScheduleUpsert(
|
|
3127
|
-
return c.json(
|
|
3509
|
+
opts.router.broadcastScheduleUpsert(schedule);
|
|
3510
|
+
return c.json(schedule);
|
|
3128
3511
|
});
|
|
3129
3512
|
app.post("/api/schedules/:id/toggle", async (c) => {
|
|
3130
3513
|
const body = await c.req.json().catch(() => ({}));
|
|
@@ -3143,13 +3526,13 @@ function createHttpApp(opts) {
|
|
|
3143
3526
|
app.post("/api/goals", async (c) => {
|
|
3144
3527
|
const body = await c.req.json().catch(() => ({}));
|
|
3145
3528
|
if (!body.name) return c.json({ error: "name required" }, 400);
|
|
3146
|
-
const
|
|
3529
|
+
const goal = createGoal(opts.db, {
|
|
3147
3530
|
name: body.name,
|
|
3148
3531
|
description: body.description,
|
|
3149
3532
|
color: body.color
|
|
3150
3533
|
});
|
|
3151
|
-
opts.router.broadcastGoalUpsert(
|
|
3152
|
-
return c.json(
|
|
3534
|
+
opts.router.broadcastGoalUpsert(goal);
|
|
3535
|
+
return c.json(goal);
|
|
3153
3536
|
});
|
|
3154
3537
|
app.delete("/api/goals/:id", (c) => {
|
|
3155
3538
|
const id = c.req.param("id");
|
|
@@ -3191,14 +3574,14 @@ function createHttpApp(opts) {
|
|
|
3191
3574
|
const candidate = join11(webDist, safe === "/" ? "index.html" : safe);
|
|
3192
3575
|
let filePath = candidate;
|
|
3193
3576
|
try {
|
|
3194
|
-
if (!
|
|
3577
|
+
if (!existsSync9(filePath) || statSync4(filePath).isDirectory()) {
|
|
3195
3578
|
filePath = join11(webDist, "index.html");
|
|
3196
3579
|
}
|
|
3197
3580
|
} catch {
|
|
3198
3581
|
filePath = join11(webDist, "index.html");
|
|
3199
3582
|
}
|
|
3200
|
-
if (!
|
|
3201
|
-
const data =
|
|
3583
|
+
if (!existsSync9(filePath)) return c.notFound();
|
|
3584
|
+
const data = readFileSync7(filePath);
|
|
3202
3585
|
return new Response(data, {
|
|
3203
3586
|
headers: { "Content-Type": mimeFor(filePath) }
|
|
3204
3587
|
});
|
|
@@ -3256,9 +3639,9 @@ function createHttpApp(opts) {
|
|
|
3256
3639
|
}
|
|
3257
3640
|
function findWebDist() {
|
|
3258
3641
|
if (process.env.SOLIX_WEB_DIST) {
|
|
3259
|
-
return
|
|
3642
|
+
return existsSync9(process.env.SOLIX_WEB_DIST) ? process.env.SOLIX_WEB_DIST : null;
|
|
3260
3643
|
}
|
|
3261
|
-
const here =
|
|
3644
|
+
const here = dirname5(fileURLToPath5(import.meta.url));
|
|
3262
3645
|
const candidates = [
|
|
3263
3646
|
// Bundled npm package: web/ ships next to the bundled JS file.
|
|
3264
3647
|
resolve3(here, "web"),
|
|
@@ -3269,7 +3652,7 @@ function findWebDist() {
|
|
|
3269
3652
|
resolve3(process.cwd(), "packages", "web", "dist")
|
|
3270
3653
|
];
|
|
3271
3654
|
for (const c of candidates) {
|
|
3272
|
-
if (
|
|
3655
|
+
if (existsSync9(join11(c, "index.html"))) return c;
|
|
3273
3656
|
}
|
|
3274
3657
|
return null;
|
|
3275
3658
|
}
|
|
@@ -3294,8 +3677,8 @@ function mimeFor(filePath) {
|
|
|
3294
3677
|
}
|
|
3295
3678
|
|
|
3296
3679
|
// ../server/src/launcher.ts
|
|
3297
|
-
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
3298
|
-
import { existsSync as
|
|
3680
|
+
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
3681
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
3299
3682
|
import { homedir as homedir8 } from "os";
|
|
3300
3683
|
import { basename as basename3, join as join12 } from "path";
|
|
3301
3684
|
import { nanoid as nanoid7 } from "nanoid";
|
|
@@ -3321,7 +3704,7 @@ function ensureWorktree(opts) {
|
|
|
3321
3704
|
if (list.status === 0 && (list.stdout ?? "").includes(`worktree ${path}`)) {
|
|
3322
3705
|
return { path, created: false };
|
|
3323
3706
|
}
|
|
3324
|
-
|
|
3707
|
+
mkdirSync5(worktreesDir, { recursive: true });
|
|
3325
3708
|
const branchProbe = spawnSync2(
|
|
3326
3709
|
"git",
|
|
3327
3710
|
["rev-parse", "--verify", "--quiet", `refs/heads/${opts.branch}`],
|
|
@@ -3421,7 +3804,7 @@ var Launcher = class {
|
|
|
3421
3804
|
advisor.id,
|
|
3422
3805
|
"--no-tty"
|
|
3423
3806
|
]);
|
|
3424
|
-
const child =
|
|
3807
|
+
const child = spawn2(spawnSpec.file, spawnSpec.args, {
|
|
3425
3808
|
cwd,
|
|
3426
3809
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3427
3810
|
detached: false,
|
|
@@ -3584,7 +3967,7 @@ var Launcher = class {
|
|
|
3584
3967
|
goalId: opts.goalId
|
|
3585
3968
|
});
|
|
3586
3969
|
}
|
|
3587
|
-
if (!
|
|
3970
|
+
if (!existsSync10(spawnCwd)) {
|
|
3588
3971
|
this.broadcaster.broadcast({
|
|
3589
3972
|
type: "toast",
|
|
3590
3973
|
level: "error",
|
|
@@ -3622,7 +4005,7 @@ var Launcher = class {
|
|
|
3622
4005
|
});
|
|
3623
4006
|
return { ok: true };
|
|
3624
4007
|
}
|
|
3625
|
-
if (!
|
|
4008
|
+
if (!existsSync10(opts.cwd)) {
|
|
3626
4009
|
this.broadcaster.broadcast({
|
|
3627
4010
|
type: "toast",
|
|
3628
4011
|
level: "error",
|
|
@@ -3637,7 +4020,7 @@ var Launcher = class {
|
|
|
3637
4020
|
let child;
|
|
3638
4021
|
try {
|
|
3639
4022
|
const spawnSpec = sandboxWrap("claude", args);
|
|
3640
|
-
child =
|
|
4023
|
+
child = spawn2(spawnSpec.file, spawnSpec.args, {
|
|
3641
4024
|
cwd: opts.cwd,
|
|
3642
4025
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3643
4026
|
detached: false,
|
|
@@ -3743,7 +4126,7 @@ var Launcher = class {
|
|
|
3743
4126
|
let child;
|
|
3744
4127
|
try {
|
|
3745
4128
|
const spawnSpec = sandboxWrap("claude", opts.args);
|
|
3746
|
-
child =
|
|
4129
|
+
child = spawn2(spawnSpec.file, spawnSpec.args, {
|
|
3747
4130
|
cwd: opts.cwd,
|
|
3748
4131
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3749
4132
|
detached: false,
|
|
@@ -4435,14 +4818,14 @@ var EventRouter = class {
|
|
|
4435
4818
|
this.broadcaster.broadcast({ type: "session_upsert", session });
|
|
4436
4819
|
}
|
|
4437
4820
|
// Sprint M — broadcast helpers for schedule/goal CRUD driven by HTTP/CLI.
|
|
4438
|
-
broadcastScheduleUpsert(
|
|
4439
|
-
this.broadcaster.broadcast({ type: "schedule_upsert", schedule
|
|
4821
|
+
broadcastScheduleUpsert(schedule) {
|
|
4822
|
+
this.broadcaster.broadcast({ type: "schedule_upsert", schedule });
|
|
4440
4823
|
}
|
|
4441
4824
|
broadcastScheduleRemove(scheduleId) {
|
|
4442
4825
|
this.broadcaster.broadcast({ type: "schedule_remove", scheduleId });
|
|
4443
4826
|
}
|
|
4444
|
-
broadcastGoalUpsert(
|
|
4445
|
-
this.broadcaster.broadcast({ type: "goal_upsert", goal
|
|
4827
|
+
broadcastGoalUpsert(goal) {
|
|
4828
|
+
this.broadcaster.broadcast({ type: "goal_upsert", goal });
|
|
4446
4829
|
}
|
|
4447
4830
|
broadcastGoalRemove(goalId) {
|
|
4448
4831
|
this.broadcaster.broadcast({ type: "goal_remove", goalId });
|
|
@@ -4489,6 +4872,11 @@ function attachWs(server, ctx) {
|
|
|
4489
4872
|
socket.destroy();
|
|
4490
4873
|
return;
|
|
4491
4874
|
}
|
|
4875
|
+
if (!isAllowedOrigin(req.headers.origin)) {
|
|
4876
|
+
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
4877
|
+
socket.destroy();
|
|
4878
|
+
return;
|
|
4879
|
+
}
|
|
4492
4880
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
4493
4881
|
wss.emit("connection", ws, req);
|
|
4494
4882
|
});
|
|
@@ -4587,7 +4975,7 @@ function handleClientMessage(ctx, _ws, msg) {
|
|
|
4587
4975
|
// ../server/src/state/transcript.ts
|
|
4588
4976
|
import {
|
|
4589
4977
|
closeSync,
|
|
4590
|
-
existsSync as
|
|
4978
|
+
existsSync as existsSync11,
|
|
4591
4979
|
openSync,
|
|
4592
4980
|
readSync,
|
|
4593
4981
|
statSync as statSync5,
|
|
@@ -4631,7 +5019,7 @@ var TranscriptWatcherManager = class {
|
|
|
4631
5019
|
startWatching(sessionId, cwd) {
|
|
4632
5020
|
if (this.records.has(sessionId)) return;
|
|
4633
5021
|
const filePath = transcriptPathFor(cwd, sessionId);
|
|
4634
|
-
if (!
|
|
5022
|
+
if (!existsSync11(filePath)) {
|
|
4635
5023
|
this.scheduleRetry(sessionId, cwd, 0);
|
|
4636
5024
|
return;
|
|
4637
5025
|
}
|
|
@@ -4642,7 +5030,7 @@ var TranscriptWatcherManager = class {
|
|
|
4642
5030
|
const t = setTimeout(() => {
|
|
4643
5031
|
this.deferredRetry.delete(sessionId);
|
|
4644
5032
|
const filePath = transcriptPathFor(cwd, sessionId);
|
|
4645
|
-
if (
|
|
5033
|
+
if (existsSync11(filePath)) {
|
|
4646
5034
|
this.attach(sessionId, filePath);
|
|
4647
5035
|
} else {
|
|
4648
5036
|
this.scheduleRetry(sessionId, cwd, attempt + 1);
|
|
@@ -4865,7 +5253,7 @@ ${text.slice(0, 600)}`);
|
|
|
4865
5253
|
};
|
|
4866
5254
|
|
|
4867
5255
|
// ../server/src/state/agentview.ts
|
|
4868
|
-
import { existsSync as
|
|
5256
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync6, watch as watch2 } from "fs";
|
|
4869
5257
|
import { homedir as homedir10 } from "os";
|
|
4870
5258
|
import { join as join14 } from "path";
|
|
4871
5259
|
var ROSTER_PATH = join14(homedir10(), ".claude", "daemon", "roster.json");
|
|
@@ -4895,9 +5283,9 @@ function mapPrStatus(s) {
|
|
|
4895
5283
|
return void 0;
|
|
4896
5284
|
}
|
|
4897
5285
|
function readRoster() {
|
|
4898
|
-
if (!
|
|
5286
|
+
if (!existsSync12(ROSTER_PATH)) return [];
|
|
4899
5287
|
try {
|
|
4900
|
-
const raw =
|
|
5288
|
+
const raw = readFileSync8(ROSTER_PATH, "utf8");
|
|
4901
5289
|
const parsed = JSON.parse(raw);
|
|
4902
5290
|
if (Array.isArray(parsed)) return parsed;
|
|
4903
5291
|
if (parsed && Array.isArray(parsed.sessions)) return parsed.sessions;
|
|
@@ -4907,7 +5295,7 @@ function readRoster() {
|
|
|
4907
5295
|
}
|
|
4908
5296
|
}
|
|
4909
5297
|
function readJobIds() {
|
|
4910
|
-
if (!
|
|
5298
|
+
if (!existsSync12(JOBS_DIR)) return [];
|
|
4911
5299
|
try {
|
|
4912
5300
|
return readdirSync5(JOBS_DIR).filter((entry) => {
|
|
4913
5301
|
try {
|
|
@@ -4922,9 +5310,9 @@ function readJobIds() {
|
|
|
4922
5310
|
}
|
|
4923
5311
|
function readJobState(jobId) {
|
|
4924
5312
|
const p = join14(JOBS_DIR, jobId, "state.json");
|
|
4925
|
-
if (!
|
|
5313
|
+
if (!existsSync12(p)) return null;
|
|
4926
5314
|
try {
|
|
4927
|
-
return JSON.parse(
|
|
5315
|
+
return JSON.parse(readFileSync8(p, "utf8"));
|
|
4928
5316
|
} catch {
|
|
4929
5317
|
return null;
|
|
4930
5318
|
}
|
|
@@ -5004,7 +5392,7 @@ function debounce(fn, ms) {
|
|
|
5004
5392
|
}
|
|
5005
5393
|
function startAgentViewBridge(opts) {
|
|
5006
5394
|
const claudeRoot = join14(homedir10(), ".claude");
|
|
5007
|
-
if (!
|
|
5395
|
+
if (!existsSync12(claudeRoot)) return () => {
|
|
5008
5396
|
};
|
|
5009
5397
|
const sync = () => {
|
|
5010
5398
|
try {
|
|
@@ -5017,7 +5405,7 @@ function startAgentViewBridge(opts) {
|
|
|
5017
5405
|
sync();
|
|
5018
5406
|
const watchers = [];
|
|
5019
5407
|
const daemonDir = join14(homedir10(), ".claude", "daemon");
|
|
5020
|
-
if (
|
|
5408
|
+
if (existsSync12(daemonDir)) {
|
|
5021
5409
|
try {
|
|
5022
5410
|
watchers.push(watch2(daemonDir, { persistent: false }, debounced));
|
|
5023
5411
|
} catch (err) {
|
|
@@ -5027,7 +5415,7 @@ function startAgentViewBridge(opts) {
|
|
|
5027
5415
|
);
|
|
5028
5416
|
}
|
|
5029
5417
|
}
|
|
5030
|
-
if (
|
|
5418
|
+
if (existsSync12(JOBS_DIR)) {
|
|
5031
5419
|
try {
|
|
5032
5420
|
watchers.push(watch2(JOBS_DIR, { recursive: true, persistent: false }, debounced));
|
|
5033
5421
|
} catch (err) {
|
|
@@ -5078,11 +5466,11 @@ async function createSolixServer(opts = {}) {
|
|
|
5078
5466
|
);
|
|
5079
5467
|
let token = null;
|
|
5080
5468
|
try {
|
|
5081
|
-
token =
|
|
5469
|
+
token = readFileSync9(tokenPath, "utf8").trim() || null;
|
|
5082
5470
|
} catch {
|
|
5083
5471
|
token = null;
|
|
5084
5472
|
}
|
|
5085
|
-
const app = createHttpApp({ db, router, token });
|
|
5473
|
+
const app = createHttpApp({ db, router, token, version: opts.version });
|
|
5086
5474
|
const server = serve({
|
|
5087
5475
|
fetch: app.fetch,
|
|
5088
5476
|
port,
|
|
@@ -5141,7 +5529,7 @@ var BANNER = `
|
|
|
5141
5529
|
async function start(opts = {}) {
|
|
5142
5530
|
const port = opts.port ?? Number(process.env.SOLIX_PORT ?? 4242);
|
|
5143
5531
|
console.log(BANNER);
|
|
5144
|
-
const handle = await createSolixServer({ port });
|
|
5532
|
+
const handle = await createSolixServer({ port, version: "1.9.1" });
|
|
5145
5533
|
const url = `http://${handle.hostname}:${handle.port}`;
|
|
5146
5534
|
console.log(`[solix] server listening on ${url}`);
|
|
5147
5535
|
console.log(`[solix] events -> POST ${url}/events`);
|
|
@@ -5167,20 +5555,20 @@ async function start(opts = {}) {
|
|
|
5167
5555
|
}
|
|
5168
5556
|
|
|
5169
5557
|
// src/uninstall.ts
|
|
5170
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
5558
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
5171
5559
|
function uninstall() {
|
|
5172
5560
|
uninstallShim();
|
|
5173
|
-
if (
|
|
5561
|
+
if (existsSync13(CLAUDE_BACKUP)) {
|
|
5174
5562
|
copyFileSync2(CLAUDE_BACKUP, CLAUDE_SETTINGS);
|
|
5175
5563
|
console.log(`[solix] restored settings.json from backup`);
|
|
5176
5564
|
return;
|
|
5177
5565
|
}
|
|
5178
|
-
if (!
|
|
5566
|
+
if (!existsSync13(CLAUDE_SETTINGS)) {
|
|
5179
5567
|
console.log("[solix] nothing to uninstall (no settings.json found)");
|
|
5180
5568
|
return;
|
|
5181
5569
|
}
|
|
5182
5570
|
const cur = JSON.parse(
|
|
5183
|
-
|
|
5571
|
+
readFileSync10(CLAUDE_SETTINGS, "utf8")
|
|
5184
5572
|
);
|
|
5185
5573
|
if (cur.hooks) {
|
|
5186
5574
|
for (const [evt, entries] of Object.entries(cur.hooks)) {
|
|
@@ -5196,7 +5584,7 @@ function uninstall() {
|
|
|
5196
5584
|
|
|
5197
5585
|
// src/index.ts
|
|
5198
5586
|
var program = new Command();
|
|
5199
|
-
program.name("solix").description("Solix \u2014 a solar-system command center for Claude Code agents").version("1.
|
|
5587
|
+
program.name("solix").description("Solix \u2014 a solar-system command center for Claude Code agents").version("1.9.1");
|
|
5200
5588
|
program.command("start", { isDefault: true }).description("Start the Solix server and open the browser").option("-p, --port <port>", "port to listen on", (v) => parseInt(v, 10), 4242).option("--no-open", "do not open browser automatically").action(async (opts) => {
|
|
5201
5589
|
await start({ port: opts.port, noOpen: !opts.open });
|
|
5202
5590
|
});
|
|
@@ -5221,78 +5609,83 @@ program.command("doctor").description("Run diagnostics").action(async () => {
|
|
|
5221
5609
|
await doctor();
|
|
5222
5610
|
});
|
|
5223
5611
|
program.command("demo").description(
|
|
5224
|
-
"
|
|
5225
|
-
).option("-p, --port <port>", "server port", (v) => parseInt(v, 10), 4242).
|
|
5226
|
-
|
|
5227
|
-
|
|
5612
|
+
"Boot a sandbox server, seed a rich galaxy (8 projects, ~30 sessions, all advisors), and keep firing activity until Ctrl+C. Showcase mode for live demos."
|
|
5613
|
+
).option("-p, --port <port>", "server port (falls back to +1 on conflict)", (v) => parseInt(v, 10), 4242).option("--keep", "preserve ~/.solix/demo.db after teardown (default removes it)").option("--no-server", "skip spawning a sandbox server; seed against the server you already have running").option("--no-ticker", "seed once and exit (static snapshot for screenshots)").action(
|
|
5614
|
+
async (opts) => {
|
|
5615
|
+
await demoCmd({
|
|
5616
|
+
port: opts.port,
|
|
5617
|
+
keep: opts.keep,
|
|
5618
|
+
noServer: !opts.server,
|
|
5619
|
+
noTicker: !opts.ticker
|
|
5620
|
+
});
|
|
5621
|
+
}
|
|
5622
|
+
);
|
|
5228
5623
|
var advisors = program.command("advisors").description("Manage built-in advisor agents (PM, Builder, UX, etc.)");
|
|
5229
5624
|
advisors.command("list", { isDefault: true }).description("List all advisor agents and their state").action(async () => {
|
|
5230
5625
|
await listAdvisorsCmd();
|
|
5231
5626
|
});
|
|
5232
|
-
advisors.command("enable <id>").description("Enable an advisor (
|
|
5627
|
+
advisors.command("enable <id>").description("Enable an advisor (add to the inner ring)").action(async (id) => {
|
|
5233
5628
|
await enableAdvisorCmd(id);
|
|
5234
5629
|
});
|
|
5235
|
-
advisors.command("disable <id>").description("Disable an advisor").action(async (id) => {
|
|
5630
|
+
advisors.command("disable <id>").description("Disable an advisor (remove from the inner ring)").action(async (id) => {
|
|
5236
5631
|
await disableAdvisorCmd(id);
|
|
5237
5632
|
});
|
|
5238
|
-
advisors.command("pin <id>").description("Pin an advisor (always-on
|
|
5633
|
+
advisors.command("pin <id>").description("Pin an advisor (spawn an always-on session)").action(async (id) => {
|
|
5239
5634
|
await pinAdvisorCmd(id);
|
|
5240
5635
|
});
|
|
5241
|
-
advisors.command("unpin <id>").description("Unpin an advisor (
|
|
5636
|
+
advisors.command("unpin <id>").description("Unpin an advisor (kill the always-on session)").action(async (id) => {
|
|
5242
5637
|
await unpinAdvisorCmd(id);
|
|
5243
5638
|
});
|
|
5244
|
-
var skills = program.command("skills").description("
|
|
5245
|
-
skills.command("list", { isDefault: true }).description("List
|
|
5639
|
+
var skills = program.command("skills").description("Browse + install skills from Anthropic, Solix, and your own");
|
|
5640
|
+
skills.command("list", { isDefault: true }).description("List skills detected in this project").action(async () => {
|
|
5246
5641
|
await listSkillsCmd();
|
|
5247
5642
|
});
|
|
5248
|
-
skills.command("install <id>").description("
|
|
5249
|
-
await installSkillCmd(id
|
|
5643
|
+
skills.command("install <id>").description("Install a skill into the current project").action(async (id) => {
|
|
5644
|
+
await installSkillCmd(id);
|
|
5250
5645
|
});
|
|
5251
|
-
var galaxy = program.command("galaxy").description("Export and
|
|
5252
|
-
galaxy.command("export <
|
|
5253
|
-
async (
|
|
5254
|
-
await exportGalaxyCmd(
|
|
5646
|
+
var galaxy = program.command("galaxy").description("Export, import, and publish galaxy presets");
|
|
5647
|
+
galaxy.command("export <name>").description("Snapshot the current crew + skills + projects into a manifest").option("--author <author>", "author tag for the manifest").option("--description <description>", "one-line manifest description").action(
|
|
5648
|
+
async (name, opts) => {
|
|
5649
|
+
await exportGalaxyCmd(name, opts);
|
|
5255
5650
|
}
|
|
5256
5651
|
);
|
|
5257
|
-
galaxy.command("import <
|
|
5258
|
-
await importGalaxyCmd(
|
|
5652
|
+
galaxy.command("import <path>").description("Apply a galaxy manifest (enables advisors, seeds skills + projects)").action(async (path) => {
|
|
5653
|
+
await importGalaxyCmd(path);
|
|
5259
5654
|
});
|
|
5260
|
-
galaxy.command("publish <
|
|
5261
|
-
|
|
5262
|
-
await publishGalaxyCmd(slug, opts);
|
|
5263
|
-
}
|
|
5264
|
-
);
|
|
5265
|
-
galaxy.command("install <slug>").description("Pull and install a galaxy from the configured registry").action(async (slug) => {
|
|
5266
|
-
await installFromRegistryCmd(slug);
|
|
5655
|
+
galaxy.command("publish <path>").description("Publish a manifest to the configured registry (SOLIX_REGISTRY_URL)").action(async (path) => {
|
|
5656
|
+
await publishGalaxyCmd(path);
|
|
5267
5657
|
});
|
|
5268
|
-
|
|
5269
|
-
|
|
5658
|
+
galaxy.command("install <id>").description("Install a galaxy preset from the registry by id").action(async (id) => {
|
|
5659
|
+
await installFromRegistryCmd(id);
|
|
5660
|
+
});
|
|
5661
|
+
var schedules = program.command("schedules").description("Manage recurring scheduled tasks");
|
|
5662
|
+
schedules.command("list", { isDefault: true }).description("List schedules across all projects").action(async () => {
|
|
5270
5663
|
await listSchedulesCmd();
|
|
5271
5664
|
});
|
|
5272
|
-
|
|
5665
|
+
schedules.command("add <prompt>").description("Schedule a recurring task").option("--cwd <dir>", "working directory (default: current dir)").option("--every <cadence>", "cadence, e.g. 30m, 1h, 1d (default: 1h)").option("--name <name>", "short label for the schedule").action(
|
|
5273
5666
|
async (prompt, opts) => {
|
|
5274
5667
|
await addScheduleCmd(prompt, opts);
|
|
5275
5668
|
}
|
|
5276
5669
|
);
|
|
5277
|
-
|
|
5670
|
+
schedules.command("remove <id>").description("Remove a schedule").action(async (id) => {
|
|
5671
|
+
await removeScheduleCmd(id);
|
|
5672
|
+
});
|
|
5673
|
+
schedules.command("enable <id>").description("Enable a schedule").action(async (id) => {
|
|
5278
5674
|
await enableScheduleCmd(id);
|
|
5279
5675
|
});
|
|
5280
|
-
|
|
5676
|
+
schedules.command("disable <id>").description("Disable a schedule").action(async (id) => {
|
|
5281
5677
|
await disableScheduleCmd(id);
|
|
5282
5678
|
});
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
});
|
|
5286
|
-
var goal = program.command("goal").description("Manage goals that missions roll up to (Sprint M)");
|
|
5287
|
-
goal.command("list", { isDefault: true }).description("List all goals").action(async () => {
|
|
5679
|
+
var goals = program.command("goals").description("Manage cross-session goals (constellations)");
|
|
5680
|
+
goals.command("list", { isDefault: true }).description("List all goals").action(async () => {
|
|
5288
5681
|
await listGoalsCmd();
|
|
5289
5682
|
});
|
|
5290
|
-
|
|
5683
|
+
goals.command("add <name>").description("Create a goal").option("--description <desc>", "optional description").option("--color <hex>", "goal color (default sky blue)").action(
|
|
5291
5684
|
async (name, opts) => {
|
|
5292
5685
|
await addGoalCmd(name, opts);
|
|
5293
5686
|
}
|
|
5294
5687
|
);
|
|
5295
|
-
|
|
5688
|
+
goals.command("remove <id>").description("Remove a goal").action(async (id) => {
|
|
5296
5689
|
await removeGoalCmd(id);
|
|
5297
5690
|
});
|
|
5298
5691
|
program.parseAsync(process.argv).catch((err) => {
|