@shmulikdav/solix 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -317,6 +317,32 @@ async function probeHealth(port) {
317
317
  };
318
318
  }
319
319
  }
320
+ async function probeWrappers(port) {
321
+ try {
322
+ const res = await fetch(`http://127.0.0.1:${port}/api/wrappers`, {
323
+ signal: AbortSignal.timeout(800)
324
+ });
325
+ if (!res.ok) {
326
+ return {
327
+ ok: true,
328
+ label: "Active solix run wrappers",
329
+ detail: "server too old to report (pre-1.2.1)"
330
+ };
331
+ }
332
+ const records = await res.json();
333
+ return {
334
+ ok: true,
335
+ label: "Active solix run wrappers",
336
+ detail: records.length === 0 ? "none registered" : `${records.length} active`
337
+ };
338
+ } catch {
339
+ return {
340
+ ok: true,
341
+ label: "Active solix run wrappers",
342
+ detail: "unknown \u2014 server unreachable"
343
+ };
344
+ }
345
+ }
320
346
  async function doctor() {
321
347
  const port = Number(process.env.SOLIX_PORT ?? 4242);
322
348
  const checks = [];
@@ -398,6 +424,7 @@ async function doctor() {
398
424
  detail: skillCount > 0 ? `${skillCount} skills in ${SOLIX_SKILLS_DIR}` : "none"
399
425
  });
400
426
  checks.push(await probeHealth(port));
427
+ checks.push(await probeWrappers(port));
401
428
  console.log("\nSolix Diagnostics\n");
402
429
  let allOk = true;
403
430
  for (const c of checks) {
@@ -1155,8 +1182,8 @@ function getDb() {
1155
1182
  }
1156
1183
 
1157
1184
  // ../server/src/http.ts
1158
- import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
1159
- import { dirname as dirname4, extname, join as join10, resolve as resolve3 } from "path";
1185
+ import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
1186
+ import { dirname as dirname4, extname, join as join11, resolve as resolve3 } from "path";
1160
1187
  import { fileURLToPath as fileURLToPath4 } from "url";
1161
1188
  import { spawnSync } from "child_process";
1162
1189
  import { Hono } from "hono";
@@ -1329,6 +1356,13 @@ function setSessionMission(db, sessionId, missionId) {
1329
1356
  ).run(missionId, ts2, sessionId);
1330
1357
  return getSession(db, sessionId);
1331
1358
  }
1359
+ function clearSessionWrapper(db, sessionId) {
1360
+ const ts2 = now();
1361
+ db.prepare(
1362
+ `UPDATE sessions SET wrapper_socket_path = NULL, updated_at = ? WHERE id = ?`
1363
+ ).run(ts2, sessionId);
1364
+ return getSession(db, sessionId);
1365
+ }
1332
1366
  function setSessionContextUsage(db, sessionId, pct) {
1333
1367
  const clamped = Math.max(0, Math.min(100, pct));
1334
1368
  const ts2 = now();
@@ -1795,13 +1829,40 @@ function readAdvisorAgentMd(advisor) {
1795
1829
 
1796
1830
  // ../server/src/state/wrappers.ts
1797
1831
  import { connect } from "net";
1832
+ import { existsSync as existsSync6, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
1833
+ import { homedir as homedir6 } from "os";
1834
+ import { join as join9 } from "path";
1798
1835
  var wrappers = /* @__PURE__ */ new Map();
1836
+ var wrapperToSession = /* @__PURE__ */ new Map();
1799
1837
  var FRESHNESS_WINDOW_MS = 6e4;
1800
1838
  function registerWrapper(rec) {
1801
1839
  wrappers.set(rec.wrapperId, rec);
1802
1840
  }
1803
1841
  function unregisterWrapper(wrapperId) {
1804
1842
  wrappers.delete(wrapperId);
1843
+ const sessionId = wrapperToSession.get(wrapperId);
1844
+ wrapperToSession.delete(wrapperId);
1845
+ return sessionId;
1846
+ }
1847
+ function bindWrapperToSession(wrapperId, sessionId) {
1848
+ wrapperToSession.set(wrapperId, sessionId);
1849
+ }
1850
+ function listWrappers() {
1851
+ return [...wrappers.values()];
1852
+ }
1853
+ function cleanupOrphanedSockets() {
1854
+ const dir = join9(homedir6(), ".solix", "wrappers");
1855
+ if (!existsSync6(dir)) return 0;
1856
+ let removed = 0;
1857
+ for (const f of readdirSync3(dir)) {
1858
+ if (!f.endsWith(".sock")) continue;
1859
+ try {
1860
+ unlinkSync2(join9(dir, f));
1861
+ removed++;
1862
+ } catch {
1863
+ }
1864
+ }
1865
+ return removed;
1805
1866
  }
1806
1867
  function claimWrapperForCwd(cwd) {
1807
1868
  const now2 = Date.now();
@@ -1817,21 +1878,17 @@ function claimWrapperForCwd(cwd) {
1817
1878
  return best;
1818
1879
  }
1819
1880
  function writeToWrapperSocket(socketPath, text) {
1881
+ if (!existsSync6(socketPath)) return false;
1820
1882
  try {
1821
1883
  const client = connect(socketPath);
1822
- let settled = false;
1823
1884
  client.on("error", () => {
1824
- if (!settled) {
1825
- settled = true;
1826
- try {
1827
- client.destroy();
1828
- } catch {
1829
- }
1885
+ try {
1886
+ client.destroy();
1887
+ } catch {
1830
1888
  }
1831
1889
  });
1832
1890
  client.write(JSON.stringify({ type: "send_prompt", text }) + "\n");
1833
1891
  client.end();
1834
- settled = true;
1835
1892
  return true;
1836
1893
  } catch {
1837
1894
  return false;
@@ -1925,10 +1982,10 @@ function buildContextEnvelope(db, args) {
1925
1982
  }
1926
1983
 
1927
1984
  // ../server/src/state/skills.ts
1928
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
1929
- import { dirname as dirname3, join as join9, resolve as resolve2 } from "path";
1985
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
1986
+ import { dirname as dirname3, join as join10, resolve as resolve2 } from "path";
1930
1987
  import { fileURLToPath as fileURLToPath3 } from "url";
1931
- import { homedir as homedir6 } from "os";
1988
+ import { homedir as homedir7 } from "os";
1932
1989
  function findSolixSkillsDir() {
1933
1990
  const here = dirname3(fileURLToPath3(import.meta.url));
1934
1991
  const candidates = [
@@ -1937,12 +1994,12 @@ function findSolixSkillsDir() {
1937
1994
  resolve2(process.cwd(), "packages", "skills")
1938
1995
  ];
1939
1996
  for (const c of candidates) {
1940
- if (existsSync6(c)) return c;
1997
+ if (existsSync7(c)) return c;
1941
1998
  }
1942
1999
  return candidates[0];
1943
2000
  }
1944
2001
  var SOLIX_SKILLS_DIR2 = findSolixSkillsDir();
1945
- var ANTHROPIC_SKILLS_DIR = join9(homedir6(), ".claude", "skills");
2002
+ var ANTHROPIC_SKILLS_DIR = join10(homedir7(), ".claude", "skills");
1946
2003
  function parseSkillManifest(manifestPath, fallbackId) {
1947
2004
  try {
1948
2005
  const txt = readFileSync5(manifestPath, "utf8");
@@ -1994,9 +2051,9 @@ function discoverSkills(db) {
1994
2051
  { dir: SOLIX_SKILLS_DIR2, source: "solix" }
1995
2052
  ];
1996
2053
  for (const { dir, source } of sources) {
1997
- if (!existsSync6(dir)) continue;
1998
- for (const entry of readdirSync3(dir)) {
1999
- const full = join9(dir, entry);
2054
+ if (!existsSync7(dir)) continue;
2055
+ for (const entry of readdirSync4(dir)) {
2056
+ const full = join10(dir, entry);
2000
2057
  let isDir = false;
2001
2058
  try {
2002
2059
  isDir = statSync3(full).isDirectory();
@@ -2004,8 +2061,8 @@ function discoverSkills(db) {
2004
2061
  continue;
2005
2062
  }
2006
2063
  if (!isDir) continue;
2007
- const manifestPath = join9(full, "SKILL.md");
2008
- if (!existsSync6(manifestPath)) continue;
2064
+ const manifestPath = join10(full, "SKILL.md");
2065
+ if (!existsSync7(manifestPath)) continue;
2009
2066
  const parsed = parseSkillManifest(manifestPath, entry);
2010
2067
  if (!parsed) continue;
2011
2068
  const id = `${source}:${parsed.id}`;
@@ -2030,7 +2087,7 @@ function getSkill(db, id) {
2030
2087
  return row ? rowToSkill(row) : null;
2031
2088
  }
2032
2089
  function readSkillManifest(skill) {
2033
- if (!existsSync6(skill.manifestPath)) return "";
2090
+ if (!existsSync7(skill.manifestPath)) return "";
2034
2091
  return readFileSync5(skill.manifestPath, "utf8");
2035
2092
  }
2036
2093
  function recordSkillInstall(db, skillId, projectId) {
@@ -2561,9 +2618,16 @@ function createHttpApp(opts) {
2561
2618
  return c.json({ ok: true });
2562
2619
  });
2563
2620
  app.post("/api/wrappers/:id/unregister", (c) => {
2564
- unregisterWrapper(c.req.param("id"));
2621
+ const sessionId = unregisterWrapper(c.req.param("id"));
2622
+ if (sessionId) {
2623
+ const cleared = clearSessionWrapper(opts.db, sessionId);
2624
+ if (cleared) {
2625
+ opts.router.broadcastSessionUpsert(cleared);
2626
+ }
2627
+ }
2565
2628
  return c.json({ ok: true });
2566
2629
  });
2630
+ app.get("/api/wrappers", (c) => c.json(listWrappers()));
2567
2631
  let preflightCache = null;
2568
2632
  app.get("/api/system/preflight", (c) => {
2569
2633
  if (preflightCache) return c.json(preflightCache);
@@ -2593,16 +2657,16 @@ function createHttpApp(opts) {
2593
2657
  return c.notFound();
2594
2658
  }
2595
2659
  const safe = url.pathname.replace(/\.\.+/g, ".");
2596
- const candidate = join10(webDist, safe === "/" ? "index.html" : safe);
2660
+ const candidate = join11(webDist, safe === "/" ? "index.html" : safe);
2597
2661
  let filePath = candidate;
2598
2662
  try {
2599
- if (!existsSync7(filePath) || statSync4(filePath).isDirectory()) {
2600
- filePath = join10(webDist, "index.html");
2663
+ if (!existsSync8(filePath) || statSync4(filePath).isDirectory()) {
2664
+ filePath = join11(webDist, "index.html");
2601
2665
  }
2602
2666
  } catch {
2603
- filePath = join10(webDist, "index.html");
2667
+ filePath = join11(webDist, "index.html");
2604
2668
  }
2605
- if (!existsSync7(filePath)) return c.notFound();
2669
+ if (!existsSync8(filePath)) return c.notFound();
2606
2670
  const data = readFileSync6(filePath);
2607
2671
  return new Response(data, {
2608
2672
  headers: { "Content-Type": mimeFor(filePath) }
@@ -2661,7 +2725,7 @@ function createHttpApp(opts) {
2661
2725
  }
2662
2726
  function findWebDist() {
2663
2727
  if (process.env.SOLIX_WEB_DIST) {
2664
- return existsSync7(process.env.SOLIX_WEB_DIST) ? process.env.SOLIX_WEB_DIST : null;
2728
+ return existsSync8(process.env.SOLIX_WEB_DIST) ? process.env.SOLIX_WEB_DIST : null;
2665
2729
  }
2666
2730
  const here = dirname4(fileURLToPath4(import.meta.url));
2667
2731
  const candidates = [
@@ -2674,7 +2738,7 @@ function findWebDist() {
2674
2738
  resolve3(process.cwd(), "packages", "web", "dist")
2675
2739
  ];
2676
2740
  for (const c of candidates) {
2677
- if (existsSync7(join10(c, "index.html"))) return c;
2741
+ if (existsSync8(join11(c, "index.html"))) return c;
2678
2742
  }
2679
2743
  return null;
2680
2744
  }
@@ -2700,9 +2764,9 @@ function mimeFor(filePath) {
2700
2764
 
2701
2765
  // ../server/src/launcher.ts
2702
2766
  import { spawn, spawnSync as spawnSync2 } from "child_process";
2703
- import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2704
- import { homedir as homedir7 } from "os";
2705
- import { basename as basename3, join as join11 } from "path";
2767
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2768
+ import { homedir as homedir8 } from "os";
2769
+ import { basename as basename3, join as join12 } from "path";
2706
2770
  import { nanoid as nanoid5 } from "nanoid";
2707
2771
  function ensureWorktree(opts) {
2708
2772
  const repoRoot = (() => {
@@ -2717,8 +2781,8 @@ function ensureWorktree(opts) {
2717
2781
  })();
2718
2782
  const repoName = basename3(repoRoot);
2719
2783
  const safeBranch = opts.branch.replace(/[^a-zA-Z0-9._-]+/g, "-");
2720
- const worktreesDir = join11(homedir7(), ".solix", "worktrees");
2721
- const path = join11(worktreesDir, `${repoName}-${safeBranch}`);
2784
+ const worktreesDir = join12(homedir8(), ".solix", "worktrees");
2785
+ const path = join12(worktreesDir, `${repoName}-${safeBranch}`);
2722
2786
  const list = spawnSync2("git", ["worktree", "list", "--porcelain"], {
2723
2787
  cwd: repoRoot,
2724
2788
  encoding: "utf8"
@@ -2930,7 +2994,7 @@ var Launcher = class {
2930
2994
  worktreePath
2931
2995
  });
2932
2996
  }
2933
- if (!existsSync8(spawnCwd)) {
2997
+ if (!existsSync9(spawnCwd)) {
2934
2998
  this.broadcaster.broadcast({
2935
2999
  type: "toast",
2936
3000
  level: "error",
@@ -3236,6 +3300,7 @@ var EventRouter = class {
3236
3300
  worktreePath,
3237
3301
  wrapperSocketPath: wrapper?.socketPath
3238
3302
  });
3303
+ if (wrapper) bindWrapperToSession(wrapper.wrapperId, session.id);
3239
3304
  this.broadcaster.broadcast({ type: "session_upsert", session });
3240
3305
  if (!session.parentSessionId) {
3241
3306
  this.transcripts?.startWatching(sessionId, event.cwd);
@@ -3535,10 +3600,13 @@ var EventRouter = class {
3535
3600
  }
3536
3601
  });
3537
3602
  } else {
3603
+ const cleared = clearSessionWrapper(this.db, sessionId);
3604
+ if (cleared)
3605
+ this.broadcaster.broadcast({ type: "session_upsert", session: cleared });
3538
3606
  this.broadcaster.broadcast({
3539
3607
  type: "toast",
3540
3608
  level: "warn",
3541
- message: "Wrapper socket unreachable \u2014 the `solix run` process may have exited."
3609
+ message: `Wrapper for ${session.name ?? session.id.slice(0, 8)} exited \u2014 chat is now read-only. Restart with \`solix run\`.`
3542
3610
  });
3543
3611
  }
3544
3612
  return ok;
@@ -3551,6 +3619,11 @@ var EventRouter = class {
3551
3619
  pendingPermissions() {
3552
3620
  return [...this.permissions.values()];
3553
3621
  }
3622
+ /** Public re-broadcast helper for cases where state mutates outside
3623
+ * the hook flow (e.g. wrapper unregister clearing the socket path). */
3624
+ broadcastSessionUpsert(session) {
3625
+ this.broadcaster.broadcast({ type: "session_upsert", session });
3626
+ }
3554
3627
  broadcastGalaxyImported(manifest) {
3555
3628
  this.broadcaster.broadcast({ type: "galaxy_imported", manifest });
3556
3629
  this.broadcaster.broadcast({
@@ -3677,15 +3750,15 @@ function handleClientMessage(ctx, _ws, msg) {
3677
3750
  // ../server/src/state/transcript.ts
3678
3751
  import {
3679
3752
  closeSync,
3680
- existsSync as existsSync9,
3753
+ existsSync as existsSync10,
3681
3754
  openSync,
3682
3755
  readSync,
3683
3756
  statSync as statSync5,
3684
3757
  watch
3685
3758
  } from "fs";
3686
- import { homedir as homedir8 } from "os";
3687
- import { join as join12 } from "path";
3688
- var TRANSCRIPT_BASE = join12(homedir8(), ".claude", "projects");
3759
+ import { homedir as homedir9 } from "os";
3760
+ import { join as join13 } from "path";
3761
+ var TRANSCRIPT_BASE = join13(homedir9(), ".claude", "projects");
3689
3762
  var CONTEXT_BUDGETS_BY_MODEL = {
3690
3763
  "claude-opus-4-7": 2e5,
3691
3764
  "claude-opus-4-6": 2e5,
@@ -3698,7 +3771,7 @@ function encodeProjectPath(cwd) {
3698
3771
  return cwd.replace(/[/\\]/g, "-");
3699
3772
  }
3700
3773
  function transcriptPathFor(cwd, sessionId) {
3701
- return join12(TRANSCRIPT_BASE, encodeProjectPath(cwd), `${sessionId}.jsonl`);
3774
+ return join13(TRANSCRIPT_BASE, encodeProjectPath(cwd), `${sessionId}.jsonl`);
3702
3775
  }
3703
3776
  var TranscriptWatcherManager = class {
3704
3777
  constructor(db, broadcaster) {
@@ -3718,7 +3791,7 @@ var TranscriptWatcherManager = class {
3718
3791
  startWatching(sessionId, cwd) {
3719
3792
  if (this.records.has(sessionId)) return;
3720
3793
  const filePath = transcriptPathFor(cwd, sessionId);
3721
- if (!existsSync9(filePath)) {
3794
+ if (!existsSync10(filePath)) {
3722
3795
  this.scheduleRetry(sessionId, cwd, 0);
3723
3796
  return;
3724
3797
  }
@@ -3729,7 +3802,7 @@ var TranscriptWatcherManager = class {
3729
3802
  const t = setTimeout(() => {
3730
3803
  this.deferredRetry.delete(sessionId);
3731
3804
  const filePath = transcriptPathFor(cwd, sessionId);
3732
- if (existsSync9(filePath)) {
3805
+ if (existsSync10(filePath)) {
3733
3806
  this.attach(sessionId, filePath);
3734
3807
  } else {
3735
3808
  this.scheduleRetry(sessionId, cwd, attempt + 1);
@@ -3925,6 +3998,13 @@ async function createSolixServer(opts = {}) {
3925
3998
  const db = getDb();
3926
3999
  seedAdvisors(db);
3927
4000
  discoverSkills(db);
4001
+ const cleared = cleanupOrphanedSockets();
4002
+ if (cleared > 0) {
4003
+ console.log(`[solix] cleaned up ${cleared} orphaned wrapper socket(s)`);
4004
+ }
4005
+ db.prepare(
4006
+ `UPDATE sessions SET wrapper_socket_path = NULL WHERE wrapper_socket_path IS NOT NULL`
4007
+ ).run();
3928
4008
  const broadcaster = new Broadcaster();
3929
4009
  const launcher = new Launcher(db, broadcaster);
3930
4010
  const transcripts = new TranscriptWatcherManager(db, broadcaster);
@@ -3991,15 +4071,15 @@ async function start(opts = {}) {
3991
4071
  }
3992
4072
 
3993
4073
  // src/uninstall.ts
3994
- import { copyFileSync as copyFileSync2, existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
4074
+ import { copyFileSync as copyFileSync2, existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
3995
4075
  function uninstall() {
3996
4076
  uninstallShim();
3997
- if (existsSync10(CLAUDE_BACKUP)) {
4077
+ if (existsSync11(CLAUDE_BACKUP)) {
3998
4078
  copyFileSync2(CLAUDE_BACKUP, CLAUDE_SETTINGS);
3999
4079
  console.log(`[solix] restored settings.json from backup`);
4000
4080
  return;
4001
4081
  }
4002
- if (!existsSync10(CLAUDE_SETTINGS)) {
4082
+ if (!existsSync11(CLAUDE_SETTINGS)) {
4003
4083
  console.log("[solix] nothing to uninstall (no settings.json found)");
4004
4084
  return;
4005
4085
  }
@@ -0,0 +1 @@
1
+ import{r as c,a8 as $,j as e}from"./index-BB6Hys2b.js";function H(s,i){const a=new Map(s.advisors.map(t=>[t.role,t])),n=new Map(i.advisors.map(t=>[t.role,t])),h=[...n.keys()].filter(t=>!a.has(t)),v=[...a.keys()].filter(t=>!n.has(t)),j=[...n.keys()].filter(t=>a.has(t)).map(t=>({role:t,from:a.get(t).pinned,to:n.get(t).pinned})).filter(t=>t.from!==t.to),x=new Set(s.skills.map(t=>t.id)),m=new Set(i.skills.map(t=>t.id)),r=[...m].filter(t=>!x.has(t)),b=[...x].filter(t=>!m.has(t)),d=new Set(s.projects.map(t=>t.name)),f=new Set(i.projects.map(t=>t.name)),u=[...f].filter(t=>!d.has(t)),g=[...d].filter(t=>!f.has(t));return{advisors:{added:h.sort(),removed:v.sort(),pinChanged:j.sort((t,S)=>t.role.localeCompare(S.role))},skills:{added:r.sort(),removed:b.sort()},projects:{added:u.sort(),removed:g.sort()}}}function W({open:s,onClose:i}){const[a,n]=c.useState("share"),[h,v]=c.useState("My Galaxy"),[j,x]=c.useState(""),[m,r]=c.useState(""),[b,d]=c.useState(!1),[f,u]=c.useState(null),[g,t]=c.useState(null),S=$(o=>Object.keys(o.sessions).length),I=$(o=>Object.values(o.advisors).filter(y=>y.enabled).length),l=$(o=>Object.keys(o.skills).length);if(!s)return null;const p=async()=>{d(!0),u(null);try{const o=new URLSearchParams({name:h}),y=await fetch(`/api/galaxy/export?${o.toString()}`);if(!y.ok)throw new Error(`HTTP ${y.status}`);const N=await y.json(),C=new Blob([JSON.stringify(N,null,2)],{type:"application/json"}),w=URL.createObjectURL(C),k=document.createElement("a");k.href=w,k.download=`${h.toLowerCase().replace(/\s+/g,"-")}.galaxy.json`,k.click(),URL.revokeObjectURL(w),u("Downloaded.")}catch(o){u(`Export failed: ${String(o)}`)}finally{d(!1)}},U=async o=>{d(!0),u(null);try{const N=await(await fetch("/api/galaxy/import",{method:"POST",headers:{"Content-Type":"application/json"},body:o})).json();N.ok?(u(`Imported: ${N.advisorsEnabled} enabled, ${N.advisorsDisabled} disabled, ${N.projectsHinted} projects.`),x(""),r("")):u(`Import failed: ${N.error??"unknown"}`)}catch(y){u(`Import failed: ${String(y)}`)}finally{d(!1)}},E=async(o,y,N)=>{u(null);let C,w=N;if(w)try{const k=await fetch("/api/galaxy/export?preview=1");if(k.ok){const B=await k.json();C=H(B,w)}}catch{}t({body:o,label:y,diff:C,manifest:w})},O=()=>{let o;try{o=JSON.parse(j)}catch{u("Could not parse JSON.");return}E(j,"pasted manifest",o)},D=()=>{E(JSON.stringify({url:m}),`URL: ${m}`,void 0)},_=()=>{if(!g)return;const o=g.body;t(null),U(o)},J=()=>{t(null)};return e.jsxs("div",{className:"absolute top-0 right-0 h-full w-full sm:w-[480px] bg-solix-panel border-l border-solix-border backdrop-blur-md flex flex-col z-30",children:[e.jsxs("div",{className:"px-4 py-3 border-b border-solix-border flex items-start justify-between",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-xs uppercase tracking-wide text-solix-accent",children:"Galaxy"}),e.jsx("div",{className:"text-lg font-semibold",children:"Share your space"}),e.jsxs("div",{className:"text-xs text-slate-400 mt-0.5",children:[I," advisors · ",l," skills ·"," ",S," sessions"]})]}),e.jsx("button",{onClick:i,className:"text-slate-400 hover:text-slate-100",children:"✕"})]}),e.jsxs("div",{className:"flex border-b border-solix-border text-xs",children:[e.jsx(P,{active:a==="share",onClick:()=>n("share"),children:"Sharing"}),e.jsx(P,{active:a==="versions",onClick:()=>n("versions"),children:"Versions"}),e.jsx(P,{active:a==="audit",onClick:()=>n("audit"),children:"Audit"})]}),a==="audit"?e.jsx(G,{open:s}):a==="versions"?e.jsx(V,{open:s}):e.jsxs("div",{className:"flex-1 overflow-y-auto p-4 space-y-6",children:[e.jsxs("section",{children:[e.jsx("div",{className:"text-xs uppercase tracking-wide text-slate-400 mb-2",children:"Export"}),e.jsx("input",{value:h,onChange:o=>v(o.target.value),placeholder:"Galaxy name",className:"w-full text-sm bg-black/40 border border-solix-border rounded p-2 text-slate-100 placeholder-slate-600 focus:outline-none focus:border-solix-accent"}),e.jsx("button",{onClick:()=>void p(),disabled:b,className:"mt-2 w-full py-2 rounded bg-solix-accent/20 border border-solix-accent text-solix-accent text-sm hover:bg-solix-accent/30 disabled:opacity-50",children:"Download manifest (.galaxy.json)"})]}),e.jsxs("section",{children:[e.jsx("div",{className:"text-xs uppercase tracking-wide text-slate-400 mb-2",children:"Import from URL"}),e.jsx("input",{value:m,onChange:o=>r(o.target.value),placeholder:"https://… or local server URL",className:"w-full text-sm bg-black/40 border border-solix-border rounded p-2 text-slate-100 placeholder-slate-600 focus:outline-none focus:border-solix-accent"}),e.jsx("button",{onClick:D,disabled:b||!m.trim(),className:"mt-2 w-full py-2 rounded bg-cyan-500/15 border border-cyan-400/40 text-cyan-200 text-sm hover:bg-cyan-500/25 disabled:opacity-50",children:"Pull and import"})]}),e.jsxs("section",{children:[e.jsx("div",{className:"text-xs uppercase tracking-wide text-slate-400 mb-2",children:"Import from JSON"}),e.jsx("textarea",{value:j,onChange:o=>x(o.target.value),placeholder:"Paste a galaxy manifest JSON here…",rows:10,className:"w-full text-xs bg-black/40 border border-solix-border rounded p-2 text-slate-100 placeholder-slate-600 focus:outline-none focus:border-solix-accent font-mono resize-none"}),e.jsx("button",{onClick:O,disabled:b||!j.trim(),className:"mt-2 w-full py-2 rounded bg-cyan-500/15 border border-cyan-400/40 text-cyan-200 text-sm hover:bg-cyan-500/25 disabled:opacity-50",children:"Apply manifest"})]}),g&&e.jsx(K,{label:g.label,diff:g.diff,manifest:g.manifest,busy:b,onConfirm:_,onCancel:J}),f&&e.jsx("div",{className:"text-xs text-slate-300 border border-solix-border rounded p-2 bg-black/30",children:f})]}),e.jsx("div",{className:"px-4 py-3 border-t border-solix-border text-xs text-slate-500",children:a==="audit"?"Append-only history. Read-only.":a==="versions"?"Each export snapshots a version. Identical re-exports are deduped.":"Imports never spawn pinned advisors or run shell commands. You're in control."})]})}function P({active:s,onClick:i,children:a}){return e.jsx("button",{onClick:i,className:`flex-1 px-3 py-2 ${s?"text-solix-accent border-b-2 border-solix-accent":"text-slate-400 hover:text-slate-200 border-b-2 border-transparent"}`,children:a})}const F=["permission_approved","permission_denied","advisor_invoked","advisor_pinned","advisor_unpinned","galaxy_imported"];function G({open:s}){const[i,a]=c.useState([]),[n,h]=c.useState("all"),[v,j]=c.useState(!1),[x,m]=c.useState(null);return c.useEffect(()=>{if(!s)return;let r=!1;j(!0),m(null);const b=`/api/audit${n==="all"?"":`?kind=${n}`}`;return fetch(b).then(d=>d.ok?d.json():Promise.reject(new Error(`HTTP ${d.status}`))).then(d=>{r||a(d)}).catch(d=>{r||m(d.message)}).finally(()=>{r||j(!1)}),()=>{r=!0}},[s,n]),e.jsxs("div",{className:"flex-1 overflow-y-auto p-4 space-y-3",children:[e.jsxs("div",{className:"flex items-center gap-1.5 flex-wrap",children:[e.jsx(T,{label:"all",active:n==="all",onClick:()=>h("all")}),F.map(r=>e.jsx(T,{label:A(r),active:n===r,onClick:()=>h(r)},r))]}),v&&e.jsx("div",{className:"text-xs text-slate-500 italic",children:"Loading…"}),x&&e.jsxs("div",{className:"text-xs text-solix-danger italic",children:["Could not load audit events: ",x]}),!v&&i.length===0&&e.jsx("div",{className:"text-xs text-slate-500 italic",children:"No audit events yet. Approve a permission or invoke an advisor and they'll start appearing here."}),e.jsx("ul",{className:"space-y-1.5",children:i.map(r=>e.jsxs("li",{className:"rounded border border-solix-border bg-black/20 p-2",children:[e.jsxs("div",{className:"flex items-center justify-between text-[10px]",children:[e.jsx("span",{className:`uppercase tracking-wide ${M(r.kind)}`,children:A(r.kind)}),e.jsx("span",{className:"text-slate-500 font-mono",children:new Date(r.ts).toLocaleString("en-US",{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1,month:"short",day:"numeric"})})]}),e.jsx("div",{className:"text-[12px] text-slate-100 mt-1 leading-snug",children:r.summary})]},r.id))})]})}function T({label:s,active:i,onClick:a}){return e.jsx("button",{onClick:a,className:`text-[10px] px-2 py-0.5 rounded border ${i?"bg-solix-accent/15 border-solix-accent text-solix-accent":"border-solix-border text-slate-400 hover:text-slate-200"}`,children:s})}function A(s){return s==="all"?"all":s.replace(/_/g," ")}function M(s){return s==="permission_approved"?"text-solix-ok":s==="permission_denied"?"text-solix-danger":s==="galaxy_imported"?"text-cyan-300":s.startsWith("advisor_")?"text-amber-300":"text-slate-300"}function V({open:s}){const[i,a]=c.useState([]),[n,h]=c.useState(!1),[v,j]=c.useState(null),[x,m]=c.useState(null),[r,b]=c.useState(null),[d,f]=c.useState(null),[u,g]=c.useState(!1);c.useEffect(()=>{if(!s)return;let l=!1;return h(!0),fetch("/api/galaxy/versions").then(p=>p.ok?p.json():Promise.reject(new Error(`HTTP ${p.status}`))).then(p=>{l||a(p)}).catch(p=>{l||j(p.message)}).finally(()=>{l||h(!1)}),()=>{l=!0}},[s]),c.useEffect(()=>{if(!x||!r){f(null);return}if(x===r){f(null);return}let l=!1;return g(!0),fetch(`/api/galaxy/diff?from=${x}&to=${r}`).then(p=>p.ok?p.json():Promise.reject(new Error(`HTTP ${p.status}`))).then(p=>{l||f(p)}).catch(()=>{l||f(null)}).finally(()=>{l||g(!1)}),()=>{l=!0}},[x,r]);const t=l=>{x?!r&&l!==x?b(l):(m(l),b(null),f(null)):m(l)},S=()=>{m(null),b(null),f(null)},I=l=>l.id===x?"from":l.id===r?"to":null;return e.jsxs("div",{className:"flex-1 overflow-y-auto p-4 space-y-3",children:[(x||r)&&e.jsxs("div",{className:"flex items-center justify-between text-[11px] text-slate-400",children:[e.jsxs("div",{children:[x&&!r&&"Pick a second version to diff…",x&&r&&u&&"Computing diff…",x&&r&&!u&&d&&e.jsxs(e.Fragment,{children:["v",d.from.ordinal," → v",d.to.ordinal]})]}),e.jsx("button",{onClick:S,className:"text-slate-500 hover:text-slate-100",children:"clear"})]}),d&&e.jsx(R,{diff:d.diff}),n&&e.jsx("div",{className:"text-xs text-slate-500 italic",children:"Loading…"}),v&&e.jsxs("div",{className:"text-xs text-solix-danger italic",children:["Could not load versions: ",v]}),!n&&i.length===0&&e.jsx("div",{className:"text-xs text-slate-500 italic",children:'No versions yet. Hit "Download manifest" on the Sharing tab to create one.'}),e.jsx("ul",{className:"space-y-1.5",children:i.map(l=>{const p=I(l);return e.jsx("li",{children:e.jsxs("button",{onClick:()=>t(l.id),className:`w-full text-left rounded border p-2 ${p==="from"?"border-solix-accent bg-solix-accent/10":p==="to"?"border-cyan-400 bg-cyan-400/10":"border-solix-border bg-black/20 hover:bg-solix-border/30"}`,children:[e.jsxs("div",{className:"flex items-center justify-between text-[10px]",children:[e.jsxs("span",{className:"uppercase tracking-wide text-slate-400",children:["v",l.ordinal," · ",l.name]}),e.jsx("span",{className:"text-slate-500 font-mono",children:new Date(l.ts).toLocaleString("en-US",{hour:"2-digit",minute:"2-digit",month:"short",day:"numeric"})})]}),e.jsxs("div",{className:"text-[11px] text-slate-300 mt-1",children:[l.manifest.advisors.length," advisors ·"," ",l.manifest.skills.length," skills ·"," ",l.manifest.projects.length," projects",p&&e.jsxs("span",{className:"ml-2 text-[9px] uppercase tracking-wider text-slate-400",children:["[",p,"]"]})]})]})},l.id)})})]})}function R({diff:s}){return s.advisors.added.length===0&&s.advisors.removed.length===0&&s.advisors.pinChanged.length===0&&s.skills.added.length===0&&s.skills.removed.length===0&&s.projects.added.length===0&&s.projects.removed.length===0?e.jsx("div",{className:"text-xs text-slate-500 italic border border-solix-border rounded p-2 bg-black/20",children:"No changes between these versions."}):e.jsxs("div",{className:"rounded border border-solix-border bg-black/30 p-2 space-y-2 text-xs",children:[e.jsx(L,{label:"Advisors",added:s.advisors.added,removed:s.advisors.removed}),s.advisors.pinChanged.length>0&&e.jsxs("div",{children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wide text-slate-500",children:"Advisor pin changes"}),e.jsx("ul",{className:"mt-1 space-y-0.5",children:s.advisors.pinChanged.map(a=>e.jsxs("li",{className:"text-slate-200",children:[e.jsx("span",{className:"font-mono",children:a.role}),":"," ",a.from?"pinned":"unpinned"," →"," ",a.to?"pinned":"unpinned"]},a.role))})]}),e.jsx(L,{label:"Skills",added:s.skills.added,removed:s.skills.removed}),e.jsx(L,{label:"Projects",added:s.projects.added,removed:s.projects.removed})]})}function K({label:s,diff:i,manifest:a,busy:n,onConfirm:h,onCancel:v}){return e.jsxs("div",{className:"rounded border border-amber-300/60 bg-amber-500/10 p-3 space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("div",{className:"text-[11px] uppercase tracking-wide text-amber-200",children:"confirm import"}),e.jsx("div",{className:"text-[10px] text-slate-400 font-mono truncate max-w-[55%]",children:s})]}),a&&e.jsxs("div",{className:"text-xs text-slate-200",children:[e.jsx("span",{className:"font-semibold",children:a.name}),a.author&&e.jsxs("span",{className:"text-slate-400",children:[" · by ",a.author]})]}),i?e.jsx(R,{diff:i}):a?e.jsx("div",{className:"text-xs text-slate-400 italic",children:"Could not compute a diff against the current galaxy. Apply will still proceed if you confirm."}):e.jsx("div",{className:"text-xs text-slate-300",children:"Solix will fetch the manifest from this URL and apply it. Diff preview is only available for pasted JSON."}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:h,disabled:n,className:"flex-1 py-1.5 rounded bg-amber-500/20 border border-amber-300 text-amber-100 text-xs hover:bg-amber-500/30 disabled:opacity-50",children:"Apply"}),e.jsx("button",{onClick:v,disabled:n,className:"px-3 py-1.5 rounded border border-solix-border text-slate-300 text-xs hover:text-white disabled:opacity-50",children:"Cancel"})]})]})}function L({label:s,added:i,removed:a}){return i.length===0&&a.length===0?null:e.jsxs("div",{children:[e.jsx("div",{className:"text-[10px] uppercase tracking-wide text-slate-500",children:s}),e.jsxs("ul",{className:"mt-1 space-y-0.5",children:[i.map(n=>e.jsxs("li",{className:"text-solix-ok",children:["+ ",n]},`+${n}`)),a.map(n=>e.jsxs("li",{className:"text-solix-danger",children:["− ",n]},`-${n}`))]})]})}export{W as GalaxyPanel};