@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/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
- var PORT2 = process.env.SOLIX_PORT ?? "4242";
62
- var BASE2 = `http://127.0.0.1:${PORT2}`;
63
- async function postEvent(payload) {
64
- await fetch(`${BASE2}/events`, {
65
- method: "POST",
66
- headers: { "Content-Type": "application/json" },
67
- body: JSON.stringify(payload)
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 ensureReachable() {
84
+ async function sleep(ms) {
85
+ return new Promise((r) => setTimeout(r, ms));
86
+ }
87
+ async function isPortFree(port) {
74
88
  try {
75
- const res = await fetch(`${BASE2}/api/health`, {
76
- signal: AbortSignal.timeout(800)
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 pin(advisorId) {
84
- await fetch(`${BASE2}/api/advisors/${encodeURIComponent(advisorId)}/pin`, {
85
- method: "POST"
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 sleep(ms) {
89
- return new Promise((r) => setTimeout(r, ms));
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 demoCmd(opts = {}) {
92
- if (opts.port) process.env.SOLIX_PORT = String(opts.port);
93
- if (!await ensureReachable()) {
94
- console.error(
95
- "[solix] server not reachable \u2014 run `solix start` first, then `solix demo` in another terminal"
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
- process.exitCode = 1;
98
- return;
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
- const cwd = opts.cwd ?? join(homedir(), "demo-project");
101
- console.log(`[solix demo] seeding fake state for ${BASE2}`);
102
- console.log(`[solix demo] using fake cwd: ${cwd}`);
103
- const sessions = [
104
- {
105
- id: "demo-a",
106
- pid: 90001,
107
- cwd,
108
- payload: { session_id: "demo-a", model: "opus" },
109
- prompt: "Refactor the orbital math for stable layout",
110
- tools: [
111
- { tool: "Read", file: "packages/web/src/scene/orbits.ts" },
112
- { tool: "Edit", file: "packages/web/src/scene/orbits.ts" },
113
- { tool: "Bash", cmd: "pnpm --filter @solix/web typecheck" }
114
- ]
115
- },
116
- {
117
- id: "demo-b",
118
- pid: 90002,
119
- cwd,
120
- payload: { session_id: "demo-b", model: "sonnet" },
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
- id: "demo-c",
129
- pid: 90003,
130
- cwd,
131
- payload: { session_id: "demo-c", model: "haiku" },
132
- prompt: "Document the context envelope strategy",
133
- tools: []
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
- for (const s of sessions) {
137
- await postEvent({
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: s.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
- await sleep(150);
146
- for (const s of sessions.slice(0, 2)) {
147
- await postEvent({
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, prompt: s.prompt }
341
+ payload: { session_id: s.id }
153
342
  });
154
343
  }
155
- await sleep(100);
156
- for (const t of sessions[0].tools) {
157
- if (t.tool === "Bash") {
158
- await postEvent({
159
- event: "pre_tool_bash",
160
- pid: sessions[0].pid,
161
- cwd: sessions[0].cwd,
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: sessions[0].id,
165
- command: t.cmd
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
- } else {
169
- await postEvent({
170
- event: "pre_tool_file",
171
- pid: sessions[0].pid,
172
- cwd: sessions[0].cwd,
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: sessions[0].id,
176
- tool_name: t.tool,
177
- tool_input: { file_path: t.file }
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
- await sleep(80);
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
- await postEvent({
184
- event: "pre_tool_task",
185
- pid: sessions[1].pid,
186
- cwd: sessions[1].cwd,
187
- ts: ts(),
188
- payload: { session_id: sessions[1].id }
189
- });
190
- await postEvent({
191
- event: "notification",
192
- pid: sessions[2].pid,
193
- cwd: sessions[2].cwd,
194
- ts: ts(),
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
- await fetch(`${BASE2}/api/sessions/demo-a/context`, {
203
- method: "POST",
204
- headers: { "Content-Type": "application/json" },
205
- body: JSON.stringify({ pct: 62 })
206
- });
207
- await fetch(`${BASE2}/api/sessions/demo-b/context`, {
208
- method: "POST",
209
- headers: { "Content-Type": "application/json" },
210
- body: JSON.stringify({ pct: 87 })
211
- });
212
- await pin("compass");
213
- console.log(`[solix demo] seeded:`);
214
- console.log(` \u2022 3 user planets (opus / sonnet / haiku)`);
215
- console.log(` \u2022 1 active mission with tool-call comets`);
216
- console.log(` \u2022 1 subagent moon`);
217
- console.log(` \u2022 1 planet awaiting permission (red flare)`);
218
- console.log(` \u2022 1 planet at 87% context (orange flare)`);
219
- console.log(` \u2022 Compass pinned (always-on)`);
220
- console.log(`[solix demo] open ${BASE2} to see it.`);
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] in ~3s: Compass will be invoked on demo-a (watch for the toast + Audit tab row).`
555
+ `[solix demo] live ticker running. Press Ctrl+C to stop and tear down.`
223
556
  );
224
- await sleep(3e3);
225
- await fetch(`${BASE2}/api/advisors/compass/invoke`, {
226
- method: "POST",
227
- headers: { "Content-Type": "application/json" },
228
- body: JSON.stringify({
229
- targetSessionId: "demo-a",
230
- prompt: "Review the orbital math refactor before merging"
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 existsSync2, readdirSync, statSync } from "fs";
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 SOLIX_HOME = process.env.SOLIX_HOME ?? join2(homedir2(), ".solix");
246
- var HOOKS_DIR = join2(SOLIX_HOME, "hooks");
247
- var SOLIX_SKILLS_DIR = join2(SOLIX_HOME, "skills");
248
- var SOLIX_TOKEN_FILE = join2(SOLIX_HOME, "token");
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 = dirname(fileURLToPath(import.meta.url));
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 (existsSync(join2(p, "session-start.sh"))) return p;
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 = dirname(fileURLToPath(import.meta.url));
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 (existsSync(join2(p, "manifest.json"))) return p;
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 = dirname(fileURLToPath(import.meta.url));
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 (existsSync(p)) return p;
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: existsSync2(SOLIX_HOME),
731
+ ok: existsSync3(SOLIX_HOME2),
399
732
  label: "Solix home directory",
400
- detail: SOLIX_HOME
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 (!existsSync2(p)) {
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: existsSync2(CLAUDE_SETTINGS),
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: existsSync2(CLAUDE_BACKUP),
762
+ ok: existsSync3(CLAUDE_BACKUP),
430
763
  label: "Backup of settings.json",
431
- detail: existsSync2(CLAUDE_BACKUP) ? CLAUDE_BACKUP : "not yet created"
764
+ detail: existsSync3(CLAUDE_BACKUP) ? CLAUDE_BACKUP : "not yet created"
432
765
  });
433
766
  let advisorCount = 0;
434
- if (existsSync2(CLAUDE_AGENTS_DIR)) {
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 (existsSync2(SOLIX_SKILLS_DIR)) {
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 PORT3 = process.env.SOLIX_PORT ?? "4242";
492
- var BASE3 = `http://127.0.0.1:${PORT3}`;
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(`${BASE3}${path}`, init);
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(`${BASE3}/api/galaxy/publish`, {
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
- `${BASE3}/api/galaxy/registry/${encodeURIComponent(slug)}/install`,
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 = readFileSync(fileOrUrl, "utf8");
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 existsSync3,
598
- mkdirSync,
930
+ existsSync as existsSync4,
931
+ mkdirSync as mkdirSync2,
599
932
  readdirSync as readdirSync2,
600
- readFileSync as readFileSync2,
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 (!existsSync3(CLAUDE_SETTINGS)) return {};
941
+ if (!existsSync4(CLAUDE_SETTINGS)) return {};
609
942
  try {
610
- const txt = readFileSync2(CLAUDE_SETTINGS, "utf8");
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 (existsSync3(SOLIX_TOKEN_FILE)) return;
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
- mkdirSync(HOOKS_DIR, { recursive: true });
1002
+ mkdirSync2(HOOKS_DIR, { recursive: true });
670
1003
  const src = packagedHooksDir();
671
- if (!existsSync3(src)) {
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 (!existsSync3(src)) {
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 (!existsSync3(manifestPath)) return 0;
691
- const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
692
- mkdirSync(CLAUDE_AGENTS_DIR, { recursive: true });
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 (!existsSync3(from)) continue;
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 (!existsSync3(src)) return 0;
706
- mkdirSync(SOLIX_SKILLS_DIR, { recursive: true });
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
- mkdirSync(SOLIX_HOME, { recursive: true });
725
- mkdirSync(CLAUDE_DIR, { recursive: true });
1057
+ mkdirSync2(SOLIX_HOME2, { recursive: true });
1058
+ mkdirSync2(CLAUDE_DIR, { recursive: true });
726
1059
  const existing = readSettings();
727
- if (existsSync3(CLAUDE_SETTINGS) && !existsSync3(CLAUDE_BACKUP)) {
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 && existsSync3(CLAUDE_SETTINGS)) {
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 existsSync4,
758
- readFileSync as readFileSync3,
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") || existsSync4(join5(home, ".zshrc"))) {
1101
+ if (shell.endsWith("zsh") || existsSync5(join5(home, ".zshrc"))) {
769
1102
  return join5(home, ".zshrc");
770
1103
  }
771
- if (shell.endsWith("bash") || existsSync4(join5(home, ".bashrc"))) {
1104
+ if (shell.endsWith("bash") || existsSync5(join5(home, ".bashrc"))) {
772
1105
  return join5(home, ".bashrc");
773
1106
  }
774
- if (existsSync4(join5(home, ".bash_profile"))) {
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 existsSync4(rcPath) ? readFileSync3(rcPath, "utf8") : "";
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 mkdirSync2, unlinkSync } from "fs";
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 PORT4 = process.env.SOLIX_PORT ?? "4242";
836
- var BASE4 = `http://127.0.0.1:${PORT4}`;
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(`${BASE4}/api/wrappers/register`, {
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
- `${BASE4}/api/wrappers/${encodeURIComponent(wrapperId)}/unregister`,
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
- mkdirSync2(sockDir, { recursive: true });
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 ${BASE4}; claude will run normally, but the UI composer won't be active.
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
- unlinkSync(socketPath);
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 PORT5 = process.env.SOLIX_PORT ?? "4242";
976
- var BASE5 = `http://127.0.0.1:${PORT5}`;
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(`${BASE5}${path}`, init);
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 ${BASE5}: ${String(err)}`);
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 PORT6 = process.env.SOLIX_PORT ?? "4242";
1037
- var BASE6 = `http://127.0.0.1:${PORT6}`;
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(`${BASE6}${path}`, {
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 ${BASE6}: ${String(err)}`);
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 PORT7 = process.env.SOLIX_PORT ?? "4242";
1113
- var BASE7 = `http://127.0.0.1:${PORT7}`;
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(`${BASE7}${path}`, {
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 ${BASE7}: ${String(err)}`);
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 goals = await api5("/api/goals");
1133
- if (!goals.length) {
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 goals) {
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 readFileSync8 } from "fs";
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 mkdirSync3 } from "fs";
1208
- var SOLIX_HOME2 = process.env.SOLIX_HOME ?? join7(homedir5(), ".solix");
1209
- var DB_PATH = join7(SOLIX_HOME2, "solix.db");
1210
- var HOOKS_DIR2 = join7(SOLIX_HOME2, "hooks");
1211
- var LOG_PATH = join7(SOLIX_HOME2, "solix.log");
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
- mkdirSync3(SOLIX_HOME2, { recursive: true });
1214
- mkdirSync3(HOOKS_DIR2, { recursive: true });
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 existsSync8, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
1394
- import { dirname as dirname4, extname, join as join11, resolve as resolve3 } from "path";
1395
- import { fileURLToPath as fileURLToPath4 } from "url";
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 existsSync5, readFileSync as readFileSync4 } from "fs";
1978
- import { dirname as dirname2, join as join8, resolve } from "path";
1979
- import { fileURLToPath as fileURLToPath2 } from "url";
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 && existsSync5(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 = dirname2(fileURLToPath2(import.meta.url));
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 (existsSync5(join8(c, "manifest.json"))) return c;
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 (!existsSync5(path)) {
2334
+ if (!existsSync6(path)) {
2002
2335
  return { version: 1, advisors: [] };
2003
2336
  }
2004
- return JSON.parse(readFileSync4(path, "utf8"));
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 (!existsSync5(advisor.agentMdPath)) {
2438
+ if (!existsSync6(advisor.agentMdPath)) {
2106
2439
  return "";
2107
2440
  }
2108
- return readFileSync4(advisor.agentMdPath, "utf8");
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 existsSync6, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
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 (!existsSync6(dir)) return 0;
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
- unlinkSync2(join9(dir, f));
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 (!existsSync6(socketPath)) return false;
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 existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
2399
- import { dirname as dirname3, join as join10, resolve as resolve2 } from "path";
2400
- import { fileURLToPath as fileURLToPath3 } from "url";
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 = dirname3(fileURLToPath3(import.meta.url));
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 (existsSync7(c)) return c;
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 = readFileSync5(manifestPath, "utf8");
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 (!existsSync7(dir)) continue;
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 (!existsSync7(manifestPath)) continue;
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 (!existsSync7(skill.manifestPath)) return "";
2504
- return readFileSync5(skill.manifestPath, "utf8");
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(baseUrl = process.env.SOLIX_REGISTRY_URL ?? "", apiKey = process.env.SOLIX_REGISTRY_KEY) {
2730
- this.baseUrl = 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: "1.0.0",
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 schedule2 = createSchedule(opts.db, {
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(schedule2);
3127
- return c.json(schedule2);
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 goal2 = createGoal(opts.db, {
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(goal2);
3152
- return c.json(goal2);
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 (!existsSync8(filePath) || statSync4(filePath).isDirectory()) {
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 (!existsSync8(filePath)) return c.notFound();
3201
- const data = readFileSync6(filePath);
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 existsSync8(process.env.SOLIX_WEB_DIST) ? process.env.SOLIX_WEB_DIST : null;
3642
+ return existsSync9(process.env.SOLIX_WEB_DIST) ? process.env.SOLIX_WEB_DIST : null;
3260
3643
  }
3261
- const here = dirname4(fileURLToPath4(import.meta.url));
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 (existsSync8(join11(c, "index.html"))) return c;
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 existsSync9, mkdirSync as mkdirSync4 } from "fs";
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
- mkdirSync4(worktreesDir, { recursive: true });
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 = spawn(spawnSpec.file, spawnSpec.args, {
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 (!existsSync9(spawnCwd)) {
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 (!existsSync9(opts.cwd)) {
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 = spawn(spawnSpec.file, spawnSpec.args, {
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 = spawn(spawnSpec.file, spawnSpec.args, {
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(schedule2) {
4439
- this.broadcaster.broadcast({ type: "schedule_upsert", schedule: schedule2 });
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(goal2) {
4445
- this.broadcaster.broadcast({ type: "goal_upsert", goal: goal2 });
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 existsSync10,
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 (!existsSync10(filePath)) {
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 (existsSync10(filePath)) {
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 existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync6, watch as watch2 } from "fs";
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 (!existsSync11(ROSTER_PATH)) return [];
5286
+ if (!existsSync12(ROSTER_PATH)) return [];
4899
5287
  try {
4900
- const raw = readFileSync7(ROSTER_PATH, "utf8");
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 (!existsSync11(JOBS_DIR)) return [];
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 (!existsSync11(p)) return null;
5313
+ if (!existsSync12(p)) return null;
4926
5314
  try {
4927
- return JSON.parse(readFileSync7(p, "utf8"));
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 (!existsSync11(claudeRoot)) return () => {
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 (existsSync11(daemonDir)) {
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 (existsSync11(JOBS_DIR)) {
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 = readFileSync8(tokenPath, "utf8").trim() || null;
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 existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
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 (existsSync12(CLAUDE_BACKUP)) {
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 (!existsSync12(CLAUDE_SETTINGS)) {
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
- readFileSync9(CLAUDE_SETTINGS, "utf8")
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.8.0");
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
- "Seed the running server with fake planets, missions, and a pinned advisor (great for first-run)"
5225
- ).option("-p, --port <port>", "server port", (v) => parseInt(v, 10), 4242).action(async (opts) => {
5226
- await demoCmd({ port: opts.port });
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 (renders in the inner crew ring)").action(async (id) => {
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 planet)").action(async (id) => {
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 (back to on-demand)").action(async (id) => {
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("Manage discovered skills (asteroid belt)");
5245
- skills.command("list", { isDefault: true }).description("List all known skills (Anthropic + Solix pack)").action(async () => {
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("Mark a skill as installed in a project").option("--project <projectId>", "project id (hash of cwd)").action(async (id, opts) => {
5249
- await installSkillCmd(id, opts.project);
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 import shareable galaxy configurations");
5252
- galaxy.command("export <out>").description("Export the current galaxy to a JSON manifest file").option("--name <name>", "galaxy name", "My Galaxy").option("--author <author>", "author name").option("--description <desc>", "short description").action(
5253
- async (out, opts) => {
5254
- await exportGalaxyCmd(out, opts);
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 <fileOrUrl>").description("Import a galaxy manifest from a local file or URL").action(async (fileOrUrl) => {
5258
- await importGalaxyCmd(fileOrUrl);
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 <slug>").description("Publish the current galaxy to the configured registry").option("--name <name>", "galaxy name", "My Galaxy").option("--author <author>", "author name").option("--description <desc>", "short description").action(
5261
- async (slug, opts) => {
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
- var schedule = program.command("schedule").description('Manage recurring "heartbeat" tasks (Sprint M)');
5269
- schedule.command("list", { isDefault: true }).description("List all scheduled tasks").action(async () => {
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
- schedule.command("add <prompt>").description("Schedule a recurring task").option("--cwd <dir>", "working directory (default: current dir)").option("--every <cadence>", "cadence: 30m, 2h, 1d", "1h").option("--name <name>", "short label for the galaxy node").action(
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
- schedule.command("enable <id>").description("Enable a schedule").action(async (id) => {
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
- schedule.command("disable <id>").description("Disable a schedule (keeps it, stops firing)").action(async (id) => {
5676
+ schedules.command("disable <id>").description("Disable a schedule").action(async (id) => {
5281
5677
  await disableScheduleCmd(id);
5282
5678
  });
5283
- schedule.command("remove <id>").description("Delete a schedule").action(async (id) => {
5284
- await removeScheduleCmd(id);
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
- goal.command("add <name>").description("Create a goal").option("--description <desc>", "optional description").option("--color <hex>", "optional hex color for the constellation").action(
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
- goal.command("remove <id>").description("Delete a goal (detaches it from sessions/missions)").action(async (id) => {
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) => {