@shmulikdav/solix 1.4.2 → 1.5.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
@@ -343,6 +343,46 @@ async function probeWrappers(port) {
343
343
  };
344
344
  }
345
345
  }
346
+ async function probeAgentView(port) {
347
+ try {
348
+ const res = await fetch(`http://127.0.0.1:${port}/api/system/preflight`, {
349
+ signal: AbortSignal.timeout(1500)
350
+ });
351
+ if (!res.ok) {
352
+ return {
353
+ ok: true,
354
+ label: "Agent View available",
355
+ detail: "server too old to report"
356
+ };
357
+ }
358
+ const data = await res.json();
359
+ if (!data.claudeAvailable) {
360
+ return {
361
+ ok: false,
362
+ label: "Agent View available",
363
+ detail: "claude not on PATH"
364
+ };
365
+ }
366
+ if (data.agentViewAvailable) {
367
+ return {
368
+ ok: true,
369
+ label: "Agent View available",
370
+ detail: `yes (${data.version ?? "unknown version"})`
371
+ };
372
+ }
373
+ return {
374
+ ok: true,
375
+ label: "Agent View available",
376
+ detail: `no \u2014 need Claude Code 2.1.139+ (have ${data.version ?? "?"})`
377
+ };
378
+ } catch {
379
+ return {
380
+ ok: true,
381
+ label: "Agent View available",
382
+ detail: "unknown \u2014 server unreachable"
383
+ };
384
+ }
385
+ }
346
386
  async function doctor() {
347
387
  const port = Number(process.env.SOLIX_PORT ?? 4242);
348
388
  const checks = [];
@@ -425,6 +465,7 @@ async function doctor() {
425
465
  });
426
466
  checks.push(await probeHealth(port));
427
467
  checks.push(await probeWrappers(port));
468
+ checks.push(await probeAgentView(port));
428
469
  console.log("\nSolix Diagnostics\n");
429
470
  let allOk = true;
430
471
  for (const c of checks) {
@@ -1175,6 +1216,10 @@ function getDb() {
1175
1216
  ensureColumn(db, "sessions", "advisor_role", "advisor_role TEXT");
1176
1217
  ensureColumn(db, "sessions", "worktree_path", "worktree_path TEXT");
1177
1218
  ensureColumn(db, "sessions", "wrapper_socket_path", "wrapper_socket_path TEXT");
1219
+ ensureColumn(db, "sessions", "agent_view_id", "agent_view_id TEXT");
1220
+ ensureColumn(db, "sessions", "agent_view_summary", "agent_view_summary TEXT");
1221
+ ensureColumn(db, "sessions", "pr_url", "pr_url TEXT");
1222
+ ensureColumn(db, "sessions", "pr_check_status", "pr_check_status TEXT");
1178
1223
  ensureColumn(db, "advisors", "texture_pack", "texture_pack TEXT");
1179
1224
  ensureColumn(db, "missions", "error_summary", "error_summary TEXT");
1180
1225
  _db = db;
@@ -1262,7 +1307,11 @@ function rowToSession(row) {
1262
1307
  orbitSlot: row.orbit_slot,
1263
1308
  name: row.name ?? void 0,
1264
1309
  worktreePath: row.worktree_path ?? void 0,
1265
- wrapperSocketPath: row.wrapper_socket_path ?? void 0
1310
+ wrapperSocketPath: row.wrapper_socket_path ?? void 0,
1311
+ agentViewId: row.agent_view_id ?? void 0,
1312
+ agentViewSummary: row.agent_view_summary ?? void 0,
1313
+ prUrl: row.pr_url ?? void 0,
1314
+ prCheckStatus: row.pr_check_status ?? void 0
1266
1315
  };
1267
1316
  }
1268
1317
  function nextOrbitSlot(db, projectId) {
@@ -1297,9 +1346,11 @@ function upsertSession(db, input) {
1297
1346
  `INSERT INTO sessions (
1298
1347
  id, pid, project_id, parent_session_id, origin, model, status,
1299
1348
  context_usage_pct, orbit_slot, cwd, name, kind, advisor_role,
1300
- worktree_path, wrapper_socket_path, created_at, updated_at
1349
+ worktree_path, wrapper_socket_path,
1350
+ agent_view_id, agent_view_summary, pr_url, pr_check_status,
1351
+ created_at, updated_at
1301
1352
  )
1302
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?, ?, ?)`
1353
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
1303
1354
  ).run(
1304
1355
  input.id,
1305
1356
  input.pid,
@@ -1314,6 +1365,10 @@ function upsertSession(db, input) {
1314
1365
  input.advisorRole ?? null,
1315
1366
  input.worktreePath ?? null,
1316
1367
  input.wrapperSocketPath ?? null,
1368
+ input.agentViewId ?? null,
1369
+ input.agentViewSummary ?? null,
1370
+ input.prUrl ?? null,
1371
+ input.prCheckStatus ?? null,
1317
1372
  ts2,
1318
1373
  ts2
1319
1374
  );
@@ -1333,9 +1388,39 @@ function upsertSession(db, input) {
1333
1388
  contextUsagePct: 0,
1334
1389
  orbitSlot,
1335
1390
  worktreePath: input.worktreePath,
1336
- wrapperSocketPath: input.wrapperSocketPath
1391
+ wrapperSocketPath: input.wrapperSocketPath,
1392
+ agentViewId: input.agentViewId,
1393
+ agentViewSummary: input.agentViewSummary,
1394
+ prUrl: input.prUrl,
1395
+ prCheckStatus: input.prCheckStatus
1337
1396
  };
1338
1397
  }
1398
+ function setAgentViewFields(db, sessionId, fields) {
1399
+ const ts2 = now();
1400
+ const updates = ["updated_at = ?"];
1401
+ const values = [ts2];
1402
+ if (fields.status !== void 0) {
1403
+ updates.push("status = ?");
1404
+ values.push(fields.status);
1405
+ }
1406
+ if (fields.agentViewSummary !== void 0) {
1407
+ updates.push("agent_view_summary = ?");
1408
+ values.push(fields.agentViewSummary);
1409
+ }
1410
+ if (fields.prUrl !== void 0) {
1411
+ updates.push("pr_url = ?");
1412
+ values.push(fields.prUrl);
1413
+ }
1414
+ if (fields.prCheckStatus !== void 0) {
1415
+ updates.push("pr_check_status = ?");
1416
+ values.push(fields.prCheckStatus);
1417
+ }
1418
+ values.push(sessionId);
1419
+ db.prepare(
1420
+ `UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`
1421
+ ).run(...values);
1422
+ return getSession(db, sessionId);
1423
+ }
1339
1424
  function setSessionStatus(db, sessionId, status) {
1340
1425
  const ts2 = now();
1341
1426
  if (status === "terminated") {
@@ -2362,6 +2447,19 @@ var RegistryClient = class {
2362
2447
  };
2363
2448
 
2364
2449
  // ../server/src/http.ts
2450
+ function isAgentViewVersion(version) {
2451
+ if (!version) return false;
2452
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
2453
+ if (!match) return false;
2454
+ const major = Number(match[1]);
2455
+ const minor = Number(match[2]);
2456
+ const patch = Number(match[3]);
2457
+ if (major > 2) return true;
2458
+ if (major < 2) return false;
2459
+ if (minor > 1) return true;
2460
+ if (minor < 1) return false;
2461
+ return patch >= 139;
2462
+ }
2365
2463
  function createHttpApp(opts) {
2366
2464
  const app = new Hono();
2367
2465
  app.use("*", cors());
@@ -2637,15 +2735,17 @@ function createHttpApp(opts) {
2637
2735
  encoding: "utf8"
2638
2736
  });
2639
2737
  if (res.status === 0) {
2738
+ const version = (res.stdout ?? "").trim() || void 0;
2640
2739
  preflightCache = {
2641
2740
  claudeAvailable: true,
2642
- version: (res.stdout ?? "").trim() || void 0
2741
+ version,
2742
+ agentViewAvailable: isAgentViewVersion(version)
2643
2743
  };
2644
2744
  } else {
2645
- preflightCache = { claudeAvailable: false };
2745
+ preflightCache = { claudeAvailable: false, agentViewAvailable: false };
2646
2746
  }
2647
2747
  } catch {
2648
- preflightCache = { claudeAvailable: false };
2748
+ preflightCache = { claudeAvailable: false, agentViewAvailable: false };
2649
2749
  }
2650
2750
  return c.json(preflightCache);
2651
2751
  });
@@ -2961,6 +3061,14 @@ var Launcher = class {
2961
3061
  */
2962
3062
  launch(opts) {
2963
3063
  if (!opts.initialPrompt.trim()) return { ok: false };
3064
+ if (opts.useAgentView) {
3065
+ return this.dispatchAgentView({
3066
+ cwd: opts.cwd,
3067
+ model: opts.model,
3068
+ initialPrompt: opts.initialPrompt,
3069
+ agentName: opts.agentName
3070
+ });
3071
+ }
2964
3072
  let spawnCwd = opts.cwd;
2965
3073
  let worktreePath;
2966
3074
  if (opts.worktreeBranch?.trim()) {
@@ -3014,6 +3122,62 @@ var Launcher = class {
3014
3122
  worktreePath
3015
3123
  });
3016
3124
  }
3125
+ /**
3126
+ * Sprint L: dispatch via Anthropic's Agent View daemon. Runs
3127
+ * `claude --bg "<prompt>"` so the session is hosted by the
3128
+ * supervisor and picked up by Solix's filesystem watcher within ~1s.
3129
+ * Returns the short id parsed from claude's output line:
3130
+ * backgrounded · 7c5dcf5d
3131
+ */
3132
+ dispatchAgentView(opts) {
3133
+ if (FAKE_CLAUDE) {
3134
+ this.broadcaster.broadcast({
3135
+ type: "toast",
3136
+ level: "info",
3137
+ message: "(SOLIX_FAKE_CLAUDE=1) Agent View dispatch skipped"
3138
+ });
3139
+ return { ok: true };
3140
+ }
3141
+ if (!existsSync9(opts.cwd)) {
3142
+ this.broadcaster.broadcast({
3143
+ type: "toast",
3144
+ level: "error",
3145
+ message: `Agent View dispatch failed: cwd does not exist (${opts.cwd})`
3146
+ });
3147
+ return { ok: false };
3148
+ }
3149
+ const args = [];
3150
+ if (opts.agentName) args.push("--agent", opts.agentName);
3151
+ if (opts.model) args.push("--model", String(opts.model));
3152
+ args.push("--bg", opts.initialPrompt);
3153
+ let child;
3154
+ try {
3155
+ child = spawn("claude", args, {
3156
+ cwd: opts.cwd,
3157
+ stdio: ["ignore", "pipe", "pipe"],
3158
+ detached: false
3159
+ });
3160
+ } catch (err) {
3161
+ this.broadcaster.broadcast({
3162
+ type: "toast",
3163
+ level: "error",
3164
+ message: `claude --bg spawn failed: ${err.message}`
3165
+ });
3166
+ return { ok: false };
3167
+ }
3168
+ let stdout = "";
3169
+ child.stdout?.setEncoding("utf8").on("data", (c) => stdout += c);
3170
+ child.on("exit", () => {
3171
+ const match = stdout.match(/backgrounded[^a-z0-9]+([a-f0-9]{6,16})/i);
3172
+ const shortId = match?.[1];
3173
+ this.broadcaster.broadcast({
3174
+ type: "toast",
3175
+ level: "info",
3176
+ message: shortId ? `Dispatched to Agent View \xB7 ${shortId}` : "Dispatched to Agent View"
3177
+ });
3178
+ });
3179
+ return { ok: true };
3180
+ }
3017
3181
  sendPromptToInternal(sessionId, text) {
3018
3182
  if (!text.trim()) return false;
3019
3183
  const session = this.db.prepare(
@@ -3579,7 +3743,9 @@ var EventRouter = class {
3579
3743
  model: opts.model,
3580
3744
  initialPrompt: opts.initialPrompt,
3581
3745
  worktreeBranch: opts.worktreeBranch,
3582
- worktreeBaseRef: opts.worktreeBaseRef
3746
+ worktreeBaseRef: opts.worktreeBaseRef,
3747
+ useAgentView: opts.useAgentView,
3748
+ agentName: opts.agentName
3583
3749
  });
3584
3750
  }
3585
3751
  sendPromptToSession(sessionId, text) {
@@ -3726,7 +3892,9 @@ function handleClientMessage(ctx, _ws, msg) {
3726
3892
  model: msg.model,
3727
3893
  initialPrompt: msg.initialPrompt,
3728
3894
  worktreeBranch: msg.worktreeBranch,
3729
- worktreeBaseRef: msg.worktreeBaseRef
3895
+ worktreeBaseRef: msg.worktreeBaseRef,
3896
+ useAgentView: msg.useAgentView,
3897
+ agentName: msg.agentName
3730
3898
  });
3731
3899
  break;
3732
3900
  case "invoke_advisor":
@@ -3991,6 +4159,196 @@ ${text.slice(0, 600)}`);
3991
4159
  }
3992
4160
  };
3993
4161
 
4162
+ // ../server/src/state/agentview.ts
4163
+ import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync6, watch as watch2 } from "fs";
4164
+ import { homedir as homedir10 } from "os";
4165
+ import { join as join14 } from "path";
4166
+ var ROSTER_PATH = join14(homedir10(), ".claude", "daemon", "roster.json");
4167
+ var JOBS_DIR = join14(homedir10(), ".claude", "jobs");
4168
+ function mapStatus(state) {
4169
+ switch (state) {
4170
+ case "working":
4171
+ return "active";
4172
+ case "needs_input":
4173
+ return "awaiting_input";
4174
+ case "idle":
4175
+ return "idle";
4176
+ case "completed":
4177
+ return "terminated";
4178
+ case "failed":
4179
+ return "error";
4180
+ case "stopped":
4181
+ return "terminated";
4182
+ default:
4183
+ return "idle";
4184
+ }
4185
+ }
4186
+ function mapPrStatus(s) {
4187
+ if (!s) return void 0;
4188
+ if (s === "pending" || s === "success" || s === "failure" || s === "neutral")
4189
+ return s;
4190
+ return void 0;
4191
+ }
4192
+ function readRoster() {
4193
+ if (!existsSync11(ROSTER_PATH)) return [];
4194
+ try {
4195
+ const raw = readFileSync7(ROSTER_PATH, "utf8");
4196
+ const parsed = JSON.parse(raw);
4197
+ if (Array.isArray(parsed)) return parsed;
4198
+ if (parsed && Array.isArray(parsed.sessions)) return parsed.sessions;
4199
+ return [];
4200
+ } catch {
4201
+ return [];
4202
+ }
4203
+ }
4204
+ function readJobIds() {
4205
+ if (!existsSync11(JOBS_DIR)) return [];
4206
+ try {
4207
+ return readdirSync5(JOBS_DIR).filter((entry) => {
4208
+ try {
4209
+ return statSync6(join14(JOBS_DIR, entry)).isDirectory();
4210
+ } catch {
4211
+ return false;
4212
+ }
4213
+ });
4214
+ } catch {
4215
+ return [];
4216
+ }
4217
+ }
4218
+ function readJobState(jobId) {
4219
+ const p = join14(JOBS_DIR, jobId, "state.json");
4220
+ if (!existsSync11(p)) return null;
4221
+ try {
4222
+ return JSON.parse(readFileSync7(p, "utf8"));
4223
+ } catch {
4224
+ return null;
4225
+ }
4226
+ }
4227
+ function syncFromDisk({ db, broadcaster }) {
4228
+ const roster = readRoster();
4229
+ const jobIds = readJobIds();
4230
+ const liveIds = /* @__PURE__ */ new Set();
4231
+ for (const e of roster) if (e.id) liveIds.add(e.id);
4232
+ for (const id of jobIds) liveIds.add(id);
4233
+ for (const agentViewId of liveIds) {
4234
+ const state = readJobState(agentViewId);
4235
+ if (!state) continue;
4236
+ const cwd = state.cwd ?? "";
4237
+ if (!cwd) continue;
4238
+ const solixId = `av-${agentViewId}`;
4239
+ const existing = getSession(db, solixId);
4240
+ const status = mapStatus(state.state);
4241
+ const summary = state.summary ?? null;
4242
+ const prUrl = state.pr_url ?? null;
4243
+ const prCheckStatus = mapPrStatus(state.pr_check_status) ?? null;
4244
+ if (!existing) {
4245
+ const project = ensureProject(db, cwd);
4246
+ const session = upsertSession(db, {
4247
+ id: solixId,
4248
+ pid: 0,
4249
+ // we don't know the pid; supervisor owns it
4250
+ projectId: project.id,
4251
+ cwd,
4252
+ origin: "agentview",
4253
+ model: state.model ?? "default",
4254
+ kind: "user",
4255
+ worktreePath: state.worktree_path ?? void 0,
4256
+ agentViewId,
4257
+ agentViewSummary: summary ?? void 0,
4258
+ prUrl: prUrl ?? void 0,
4259
+ prCheckStatus: prCheckStatus ?? void 0
4260
+ });
4261
+ const updated2 = setAgentViewFields(db, solixId, { status });
4262
+ broadcaster.broadcast({
4263
+ type: "session_upsert",
4264
+ session: updated2 ?? session
4265
+ });
4266
+ continue;
4267
+ }
4268
+ const changed = existing.status !== status || (existing.agentViewSummary ?? null) !== summary || (existing.prUrl ?? null) !== prUrl || (existing.prCheckStatus ?? null) !== prCheckStatus;
4269
+ if (!changed) continue;
4270
+ const updated = setAgentViewFields(db, solixId, {
4271
+ status,
4272
+ agentViewSummary: summary,
4273
+ prUrl,
4274
+ prCheckStatus
4275
+ });
4276
+ if (updated) broadcaster.broadcast({ type: "session_upsert", session: updated });
4277
+ }
4278
+ const rows = db.prepare(
4279
+ `SELECT id, agent_view_id FROM sessions
4280
+ WHERE origin = 'agentview' AND status != 'terminated'`
4281
+ ).all();
4282
+ for (const r of rows) {
4283
+ if (r.agent_view_id && !liveIds.has(r.agent_view_id)) {
4284
+ const updated = setAgentViewFields(db, r.id, { status: "terminated" });
4285
+ if (updated)
4286
+ broadcaster.broadcast({ type: "session_upsert", session: updated });
4287
+ }
4288
+ }
4289
+ }
4290
+ function debounce(fn, ms) {
4291
+ let h = null;
4292
+ return () => {
4293
+ if (h) clearTimeout(h);
4294
+ h = setTimeout(() => {
4295
+ h = null;
4296
+ fn();
4297
+ }, ms);
4298
+ };
4299
+ }
4300
+ function startAgentViewBridge(opts) {
4301
+ const claudeRoot = join14(homedir10(), ".claude");
4302
+ if (!existsSync11(claudeRoot)) return () => {
4303
+ };
4304
+ const sync = () => {
4305
+ try {
4306
+ syncFromDisk(opts);
4307
+ } catch (err) {
4308
+ console.warn("[agentview] sync failed:", err.message);
4309
+ }
4310
+ };
4311
+ const debounced = debounce(sync, 50);
4312
+ sync();
4313
+ const watchers = [];
4314
+ const daemonDir = join14(homedir10(), ".claude", "daemon");
4315
+ if (existsSync11(daemonDir)) {
4316
+ try {
4317
+ watchers.push(watch2(daemonDir, { persistent: false }, debounced));
4318
+ } catch (err) {
4319
+ console.warn(
4320
+ "[agentview] could not watch daemon dir:",
4321
+ err.message
4322
+ );
4323
+ }
4324
+ }
4325
+ if (existsSync11(JOBS_DIR)) {
4326
+ try {
4327
+ watchers.push(watch2(JOBS_DIR, { recursive: true, persistent: false }, debounced));
4328
+ } catch (err) {
4329
+ console.warn("[agentview] recursive watch unsupported; falling back to poll");
4330
+ const poll = setInterval(sync, 3e3);
4331
+ return () => {
4332
+ clearInterval(poll);
4333
+ for (const w of watchers) {
4334
+ try {
4335
+ w.close();
4336
+ } catch {
4337
+ }
4338
+ }
4339
+ };
4340
+ }
4341
+ }
4342
+ return () => {
4343
+ for (const w of watchers) {
4344
+ try {
4345
+ w.close();
4346
+ } catch {
4347
+ }
4348
+ }
4349
+ };
4350
+ }
4351
+
3994
4352
  // ../server/src/create.ts
3995
4353
  async function createSolixServer(opts = {}) {
3996
4354
  const port = opts.port ?? 4242;
@@ -4020,10 +4378,12 @@ async function createSolixServer(opts = {}) {
4020
4378
  router,
4021
4379
  broadcaster
4022
4380
  });
4381
+ const stopAgentViewBridge = startAgentViewBridge({ db, broadcaster });
4023
4382
  return {
4024
4383
  port,
4025
4384
  hostname,
4026
4385
  close: () => new Promise((resolve4) => {
4386
+ stopAgentViewBridge();
4027
4387
  transcripts.shutdownAll();
4028
4388
  launcher.shutdownAll();
4029
4389
  server.close(() => resolve4());
@@ -4071,20 +4431,20 @@ async function start(opts = {}) {
4071
4431
  }
4072
4432
 
4073
4433
  // src/uninstall.ts
4074
- import { copyFileSync as copyFileSync2, existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
4434
+ import { copyFileSync as copyFileSync2, existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
4075
4435
  function uninstall() {
4076
4436
  uninstallShim();
4077
- if (existsSync11(CLAUDE_BACKUP)) {
4437
+ if (existsSync12(CLAUDE_BACKUP)) {
4078
4438
  copyFileSync2(CLAUDE_BACKUP, CLAUDE_SETTINGS);
4079
4439
  console.log(`[solix] restored settings.json from backup`);
4080
4440
  return;
4081
4441
  }
4082
- if (!existsSync11(CLAUDE_SETTINGS)) {
4442
+ if (!existsSync12(CLAUDE_SETTINGS)) {
4083
4443
  console.log("[solix] nothing to uninstall (no settings.json found)");
4084
4444
  return;
4085
4445
  }
4086
4446
  const cur = JSON.parse(
4087
- readFileSync7(CLAUDE_SETTINGS, "utf8")
4447
+ readFileSync8(CLAUDE_SETTINGS, "utf8")
4088
4448
  );
4089
4449
  if (cur.hooks) {
4090
4450
  for (const [evt, entries] of Object.entries(cur.hooks)) {
@@ -4100,7 +4460,7 @@ function uninstall() {
4100
4460
 
4101
4461
  // src/index.ts
4102
4462
  var program = new Command();
4103
- program.name("solix").description("Solix \u2014 a solar-system command center for Claude Code agents").version("1.4.2");
4463
+ program.name("solix").description("Solix \u2014 a solar-system command center for Claude Code agents").version("1.5.0");
4104
4464
  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) => {
4105
4465
  await start({ port: opts.port, noOpen: !opts.open });
4106
4466
  });
@@ -1 +1 @@
1
- import{r as c,a9 as $,j as e}from"./index-BUrmJuUb.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};
1
+ import{r as c,a9 as $,j as e}from"./index-2P2elsQX.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};