@astrale-os/cli 1.0.0-beta.26 → 1.0.0-beta.28

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.
Files changed (73) hide show
  1. package/README.md +31 -5
  2. package/dist/astrale.js +1201 -358
  3. package/package.json +3 -2
  4. package/src/commands/__tests__/domain-install-operation.test.ts +23 -0
  5. package/src/commands/__tests__/install-identity-override.test.ts +3 -3
  6. package/src/commands/domain/install.ts +19 -11
  7. package/src/commands/get.ts +1 -1
  8. package/src/commands/identity/register.ts +3 -4
  9. package/src/commands/logs.ts +2 -5
  10. package/src/commands/session/analyze.ts +26 -4
  11. package/src/commands/update.ts +61 -40
  12. package/src/lib/__tests__/skills.test.ts +809 -0
  13. package/src/lib/skills/lock.ts +117 -0
  14. package/src/lib/skills/sync.ts +814 -0
  15. package/src/lib/skills.ts +1 -52
  16. package/src/program/__tests__/program.test.ts +1 -1
  17. package/src/setup/steps/skills.ts +40 -38
  18. package/src/telemetry/__tests__/retention.test.ts +180 -0
  19. package/src/telemetry/__tests__/settings.test.ts +102 -0
  20. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  21. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  22. package/src/telemetry/recorder.ts +6 -3
  23. package/src/telemetry/retention.ts +129 -0
  24. package/src/telemetry/session.ts +18 -12
  25. package/src/telemetry/settings.ts +64 -11
  26. package/src/telemetry/store.ts +87 -9
  27. package/src/telemetry/trigger.ts +16 -28
  28. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  29. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  30. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  31. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  32. package/studio/client/dist/index.html +3 -3
  33. package/studio/server/agent/prompts/anchors.test.ts +17 -4
  34. package/studio/server/agent/prompts/anchors.ts +3 -2
  35. package/studio/server/agent/prompts/system.test.ts +2 -3
  36. package/studio/server/agent/prompts/system.ts +3 -3
  37. package/studio/server/agent/run/preparation.ts +1 -1
  38. package/studio/server/api/context.ts +1 -1
  39. package/studio/server/api/deployment.ts +1 -1
  40. package/studio/server/api/views.ts +2 -2
  41. package/studio/server/api/workspace.ts +1 -1
  42. package/studio/server/cache.test.ts +3 -8
  43. package/studio/server/cache.ts +4 -45
  44. package/studio/server/cli-consumers.test.ts +31 -2
  45. package/studio/server/handoff/copy.ts +1 -1
  46. package/studio/server/introspect/canonical-schema.test.ts +154 -128
  47. package/studio/server/introspect/canonical-schema.ts +183 -302
  48. package/studio/server/introspect/core.ts +14 -21
  49. package/studio/server/introspect/extractor.ts +11 -15
  50. package/studio/server/introspect/overlay-tsmorph.test.ts +1 -5
  51. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  52. package/studio/server/introspect/overlay.ts +3 -24
  53. package/studio/server/introspect/revision.test.ts +24 -28
  54. package/studio/server/introspect/revision.ts +10 -21
  55. package/studio/server/introspect/runtime.test.ts +14 -14
  56. package/studio/server/introspect/runtime.ts +1 -46
  57. package/studio/server/lifecycle.ts +4 -1
  58. package/studio/server/state/documents.test.ts +69 -0
  59. package/studio/server/state/documents.ts +57 -10
  60. package/studio/server/workspace/updates.ts +23 -1
  61. package/studio/shared/contracts/runtime.ts +4 -0
  62. package/studio/shared/contracts/schema.ts +10 -26
  63. package/studio/shared/contracts/surface.test.ts +2 -2
  64. package/studio/shared/contracts/workspace.ts +3 -0
  65. package/studio/shared/schema/identity.ts +0 -1
  66. package/tsconfig.json +1 -1
  67. package/studio/client/dist/assets/index-C0FAnwNw.css +0 -2
  68. package/studio/client/dist/assets/index-OeJK1TjG.js +0 -81
  69. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +0 -1
  70. package/studio/client/dist/assets/schema-studio-DRdfu_cQ.js +0 -8
  71. package/studio/server/introspect/core-extractor.ts +0 -30
  72. package/studio/server/introspect/overlay.test.ts +0 -44
  73. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
package/dist/astrale.js CHANGED
@@ -2578,7 +2578,7 @@ var package_default;
2578
2578
  var init_package = __esm(() => {
2579
2579
  package_default = {
2580
2580
  name: "@astrale-os/cli",
2581
- version: "1.0.0-beta.26",
2581
+ version: "1.0.0-beta.28",
2582
2582
  description: "Astrale CLI — connect to existing Astrale kernels",
2583
2583
  keywords: [
2584
2584
  "astrale",
@@ -2638,6 +2638,7 @@ var init_package = __esm(() => {
2638
2638
  format: "pnpm exec oxfmt --write .",
2639
2639
  "format:check": "pnpm exec oxfmt --check .",
2640
2640
  test: "node scripts/qualification/source-boundary.mjs --target && bun test src studio/client/src studio/server studio/shared && node --test scripts/*.test.mjs",
2641
+ "test:skills-e2e": "node scripts/qualification/skills-update-e2e.mjs",
2641
2642
  "test:studio": "pnpm --dir studio test",
2642
2643
  "test:watch": "bun test --watch src"
2643
2644
  },
@@ -2660,8 +2661,8 @@ var init_package = __esm(() => {
2660
2661
  "@astrale/commitlint-config": "jsr:~2.0.1",
2661
2662
  "@commitlint/cli": "21.2.2",
2662
2663
  "@commitlint/config-conventional": "21.2.2",
2663
- "@types/bun": "^1.1.16",
2664
2664
  "@types/node": "^22.0.0",
2665
+ "bun-types": "1.4.0",
2665
2666
  husky: "~9.1.7",
2666
2667
  "lint-staged": "17.3.0",
2667
2668
  msgpackr: "^2.0.5",
@@ -61295,38 +61296,668 @@ var init_browser = __esm(() => {
61295
61296
  AUTH_EVAL = "fetch('/auth/me',{credentials:'include'})" + ".then(r=>r.json())" + ".then(j=>({authed:!!j.authenticated,email:j.user&&j.user.email}))" + ".catch(()=>({authed:false}))";
61296
61297
  });
61297
61298
 
61298
- // src/lib/skills.ts
61299
- import { existsSync, lstatSync, mkdirSync, readlinkSync, symlinkSync } from "node:fs";
61300
- import { homedir as homedir2 } from "node:os";
61299
+ // src/lib/skills/lock.ts
61300
+ import { randomUUID as randomUUID4 } from "node:crypto";
61301
+ import { lstat, mkdir as mkdir9, readFile as readFile10, readdir as readdir2, rm as rm2, stat as stat2, unlink as unlink4, writeFile as writeFile5 } from "node:fs/promises";
61301
61302
  import { dirname as dirname8, join as join6 } from "node:path";
61302
- async function installSkills() {
61303
- const { code, stdout, stderr } = await run("npx", [
61304
- "skills",
61305
- "add",
61306
- ASTRALE_CLI_SKILL_SOURCE,
61307
- "-g",
61308
- "-y"
61309
- ]);
61310
- if (code !== 0)
61311
- process.stderr.write(stdout + stderr);
61312
- return code === 0;
61303
+ async function withFileLock2(lockPath, fn, opts = {}) {
61304
+ const pollIntervalMs = opts.pollIntervalMs ?? 100;
61305
+ const staleAfterMs = opts.staleAfterMs ?? 30000;
61306
+ const timeoutMs = opts.timeoutMs ?? 60000;
61307
+ const deadline = Date.now() + timeoutMs;
61308
+ const claimsRoot = `${lockPath}.claims`;
61309
+ const token = randomUUID4();
61310
+ const claimDirectory = join6(claimsRoot, token);
61311
+ await mkdir9(dirname8(lockPath), { recursive: true });
61312
+ await mkdir9(claimsRoot, { recursive: true });
61313
+ await mkdir9(claimDirectory);
61314
+ await writeFile5(join6(claimDirectory, "owner.json"), JSON.stringify({ pid: process.pid }), {
61315
+ mode: 384
61316
+ });
61317
+ await writeFile5(join6(claimDirectory, "choosing"), "", { mode: 384 });
61318
+ try {
61319
+ const claims = await liveClaims(claimsRoot, staleAfterMs);
61320
+ const ticket = Math.max(0, ...claims.flatMap((claim) => claim.ticket ?? [])) + 1;
61321
+ await writeFile5(join6(claimDirectory, "ticket"), String(ticket), { mode: 384 });
61322
+ await unlink4(join6(claimDirectory, "choosing"));
61323
+ for (;; ) {
61324
+ const contenders = await liveClaims(claimsRoot, staleAfterMs);
61325
+ const blocked = contenders.some((claim) => claim.directory !== claimDirectory && (claim.choosing || claim.ticket === null || claim.ticket < ticket || claim.ticket === ticket && claim.directory < claimDirectory));
61326
+ if (!blocked)
61327
+ return await fn();
61328
+ if (Date.now() >= deadline)
61329
+ throw new Error("Timed out waiting for another skill update");
61330
+ await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
61331
+ }
61332
+ } finally {
61333
+ await rm2(claimDirectory, { recursive: true, force: true });
61334
+ }
61335
+ }
61336
+ async function liveClaims(root, staleAfterMs) {
61337
+ const directories = (await readdir2(root, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map((entry) => join6(root, entry.name));
61338
+ const claims = await Promise.all(directories.map(async (directory) => {
61339
+ try {
61340
+ const owner4 = JSON.parse(await readFile10(join6(directory, "owner.json"), "utf8"));
61341
+ if (typeof owner4.pid !== "number")
61342
+ return await discardIfStale(directory, staleAfterMs);
61343
+ if (!isPidAlive2(owner4.pid)) {
61344
+ await rm2(directory, { recursive: true, force: true });
61345
+ return null;
61346
+ }
61347
+ const choosing = await lstat(join6(directory, "choosing")).then(() => true).catch(() => false);
61348
+ const ticket = await readFile10(join6(directory, "ticket"), "utf8").then((value2) => {
61349
+ const parsed = Number(value2);
61350
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
61351
+ }).catch(() => null);
61352
+ return { directory, pid: owner4.pid, ticket, choosing };
61353
+ } catch {
61354
+ return await discardIfStale(directory, staleAfterMs);
61355
+ }
61356
+ }));
61357
+ return claims.filter((claim) => claim !== null);
61358
+ }
61359
+ async function discardIfStale(directory, staleAfterMs) {
61360
+ try {
61361
+ if (Date.now() - (await stat2(directory)).mtimeMs > staleAfterMs) {
61362
+ await rm2(directory, { recursive: true, force: true });
61363
+ }
61364
+ } catch {}
61365
+ return null;
61366
+ }
61367
+ function isPidAlive2(pid) {
61368
+ try {
61369
+ process.kill(pid, 0);
61370
+ return true;
61371
+ } catch (error52) {
61372
+ return error52.code === "EPERM";
61373
+ }
61374
+ }
61375
+ var init_lock = () => {};
61376
+
61377
+ // src/lib/skills/sync.ts
61378
+ import { createHash as createHash2 } from "node:crypto";
61379
+ import {
61380
+ cp,
61381
+ lstat as lstat2,
61382
+ mkdir as mkdir10,
61383
+ mkdtemp as mkdtemp2,
61384
+ readFile as readFile11,
61385
+ readdir as readdir3,
61386
+ readlink,
61387
+ rename as rename3,
61388
+ rm as rm3,
61389
+ symlink,
61390
+ writeFile as writeFile6
61391
+ } from "node:fs/promises";
61392
+ import { homedir as homedir2, tmpdir as tmpdir2 } from "node:os";
61393
+ import { dirname as dirname9, join as join7, relative, resolve as resolve3 } from "node:path";
61394
+ function resolvedDependencies(overrides = {}) {
61395
+ const home = overrides.home ?? homedir2();
61396
+ const xdgStateHome = process.env.XDG_STATE_HOME;
61397
+ return {
61398
+ home,
61399
+ lockPath: overrides.lockPath ?? (xdgStateHome ? join7(xdgStateHome, "skills", ".skill-lock.json") : join7(home, ".agents", ".skill-lock.json")),
61400
+ run: overrides.run ?? run,
61401
+ resolveSource: overrides.resolveSource
61402
+ };
61403
+ }
61404
+ function sourceOwned(entry) {
61405
+ return entry.source === ASTRALE_CLI_SKILL_SOURCE || entry.sourceUrl === SOURCE_REPOSITORY_URL || entry.sourceUrl === `https://github.com/${ASTRALE_CLI_SKILL_SOURCE}`;
61406
+ }
61407
+ function folderName(entry) {
61408
+ const match = entry.skillPath?.match(/^skills\/([^/]+)\/SKILL\.md$/u);
61409
+ return match?.[1] ?? null;
61410
+ }
61411
+ async function resolveAstraleSkillSource(execute) {
61412
+ const checkout = await mkdtemp2(join7(tmpdir2(), "astrale-skill-source-"));
61413
+ try {
61414
+ const cloned = await execute("git", [
61415
+ "clone",
61416
+ "--quiet",
61417
+ "--depth",
61418
+ "1",
61419
+ "--filter=blob:none",
61420
+ "--no-checkout",
61421
+ "--single-branch",
61422
+ "--branch",
61423
+ "main",
61424
+ SOURCE_REPOSITORY_URL,
61425
+ checkout
61426
+ ]);
61427
+ if (cloned.code !== 0)
61428
+ throw new Error(cloned.stderr || cloned.stdout || "git clone failed");
61429
+ const revision = await execute("git", ["-C", checkout, "rev-parse", "HEAD"]);
61430
+ if (revision.code !== 0) {
61431
+ throw new Error(revision.stderr || revision.stdout || "git rev-parse failed");
61432
+ }
61433
+ const folders = await execute("git", ["-C", checkout, "ls-tree", "HEAD:skills"]);
61434
+ const files = await execute("git", [
61435
+ "-C",
61436
+ checkout,
61437
+ "ls-tree",
61438
+ "-r",
61439
+ "--name-only",
61440
+ "HEAD:skills"
61441
+ ]);
61442
+ if (folders.code !== 0 || files.code !== 0) {
61443
+ throw new Error(folders.stderr || files.stderr || "git ls-tree failed");
61444
+ }
61445
+ const skillFiles = new Set(files.stdout.split(`
61446
+ `).filter((path4) => /^[^/]+\/SKILL\.md$/u.test(path4)).map((path4) => path4.slice(0, -"/SKILL.md".length)));
61447
+ const skills = folders.stdout.split(`
61448
+ `).flatMap((line) => {
61449
+ const match = line.match(/^040000 tree ([0-9a-f]{40})\t([^/]+)$/u);
61450
+ if (!match || !SAFE_NAME.test(match[2]) || !skillFiles.has(match[2]))
61451
+ return [];
61452
+ return [{ name: match[2], path: `skills/${match[2]}/SKILL.md`, tree: match[1] }];
61453
+ });
61454
+ skills.sort((a, b) => a.name.localeCompare(b.name));
61455
+ if (skills.length === 0)
61456
+ throw new Error("astrale-os/cli publishes no top-level skills");
61457
+ return { ref: "main", revision: revision.stdout.trim(), skills };
61458
+ } finally {
61459
+ await rm3(checkout, { recursive: true, force: true });
61460
+ }
61461
+ }
61462
+ async function readSkillLock(path4) {
61463
+ let raw2;
61464
+ try {
61465
+ raw2 = await readFile11(path4, "utf8");
61466
+ } catch (error52) {
61467
+ if (error52.code === "ENOENT")
61468
+ return { lock: null, raw: null };
61469
+ throw error52;
61470
+ }
61471
+ try {
61472
+ const parsed = JSON.parse(raw2);
61473
+ if (parsed === null || typeof parsed !== "object" || !("skills" in parsed) || parsed.skills === null || typeof parsed.skills !== "object") {
61474
+ return { lock: null, raw: raw2 };
61475
+ }
61476
+ return { lock: parsed, raw: raw2 };
61477
+ } catch {
61478
+ return { lock: null, raw: raw2 };
61479
+ }
61480
+ }
61481
+ async function writeSkillLock(path4, lock) {
61482
+ await mkdir10(dirname9(path4), { recursive: true });
61483
+ const next = `${path4}.next`;
61484
+ await writeFile6(next, `${JSON.stringify(lock, null, 2)}
61485
+ `, { mode: 384 });
61486
+ await rename3(next, path4);
61487
+ }
61488
+ function gitObjectHash(type, body) {
61489
+ return createHash2("sha1").update(Buffer.from(`${type} ${body.length}\x00`)).update(body).digest();
61490
+ }
61491
+ async function computeSkillTreeHash(root) {
61492
+ async function treeHash(directory) {
61493
+ const entries = await Promise.all((await readdir3(directory)).map(async (name) => {
61494
+ const path4 = join7(directory, name);
61495
+ const stat3 = await lstat2(path4);
61496
+ if (stat3.isDirectory()) {
61497
+ return { name, sort: `${name}/`, mode: "40000", hash: await treeHash(path4) };
61498
+ }
61499
+ if (stat3.isSymbolicLink()) {
61500
+ return {
61501
+ name,
61502
+ sort: name,
61503
+ mode: "120000",
61504
+ hash: gitObjectHash("blob", Buffer.from(await readlink(path4)))
61505
+ };
61506
+ }
61507
+ return {
61508
+ name,
61509
+ sort: name,
61510
+ mode: stat3.mode & 73 ? "100755" : "100644",
61511
+ hash: gitObjectHash("blob", await readFile11(path4))
61512
+ };
61513
+ }));
61514
+ entries.sort((a, b) => Buffer.compare(Buffer.from(a.sort), Buffer.from(b.sort)));
61515
+ const body = Buffer.concat(entries.flatMap((entry) => [Buffer.from(`${entry.mode} ${entry.name}\x00`), entry.hash]));
61516
+ return gitObjectHash("tree", body);
61517
+ }
61518
+ return (await treeHash(root)).toString("hex");
61519
+ }
61520
+ async function computeInstallerFolderHash(root) {
61521
+ const files = [];
61522
+ async function collect(directory) {
61523
+ await Promise.all((await readdir3(directory, { withFileTypes: true })).map(async (entry) => {
61524
+ if (entry.name === ".git" || entry.name === "node_modules")
61525
+ return;
61526
+ const path4 = join7(directory, entry.name);
61527
+ if (entry.isDirectory())
61528
+ await collect(path4);
61529
+ else if (entry.isFile()) {
61530
+ files.push({
61531
+ path: relative(root, path4).split("\\").join("/"),
61532
+ content: await readFile11(path4)
61533
+ });
61534
+ }
61535
+ }));
61536
+ }
61537
+ await collect(root);
61538
+ files.sort((a, b) => a.path.localeCompare(b.path));
61539
+ const hash2 = createHash2("sha256");
61540
+ for (const file2 of files)
61541
+ hash2.update(file2.path).update(file2.content);
61542
+ return hash2.digest("hex");
61543
+ }
61544
+ async function inspectAstraleSkills(snapshot2, home, lockPath) {
61545
+ const { lock } = await readSkillLock(lockPath);
61546
+ const managed = Object.entries(lock?.skills ?? {}).filter(([, entry]) => sourceOwned(entry));
61547
+ const expected = new Map(snapshot2.skills.map((skill) => [skill.name, skill]));
61548
+ const expectedPresence = await Promise.all(snapshot2.skills.map(async (skill) => {
61549
+ try {
61550
+ return (await lstat2(join7(home, ".agents", "skills", skill.name))).isDirectory();
61551
+ } catch {
61552
+ return false;
61553
+ }
61554
+ }));
61555
+ if (managed.length === 0 && expectedPresence.every((present2) => !present2)) {
61556
+ return { state: "absent", managedNames: [], managedFolders: [] };
61557
+ }
61558
+ const folders = managed.flatMap(([name, entry]) => {
61559
+ const folder = folderName(entry);
61560
+ return folder ? [{ key: name, folder, entry }] : [];
61561
+ });
61562
+ const uniqueFolders = new Set(folders.map((entry) => entry.folder));
61563
+ let coherent = folders.length === managed.length && uniqueFolders.size === folders.length;
61564
+ const actualHashes = new Map;
61565
+ for (const item of folders) {
61566
+ try {
61567
+ const root = join7(home, ".agents", "skills", item.folder);
61568
+ const actual = await computeSkillTreeHash(root);
61569
+ actualHashes.set(item.folder, actual);
61570
+ const receiptHash = item.entry.skillFolderHash;
61571
+ if (!receiptHash || (receiptHash.length === 40 ? actual !== receiptHash : receiptHash.length === 64 ? await computeInstallerFolderHash(root) !== receiptHash : true)) {
61572
+ coherent = false;
61573
+ }
61574
+ if (item.entry.skillPath !== `skills/${item.folder}/SKILL.md`)
61575
+ coherent = false;
61576
+ } catch {
61577
+ coherent = false;
61578
+ }
61579
+ }
61580
+ for (const [index, skill] of snapshot2.skills.entries()) {
61581
+ if (expectedPresence[index] && !uniqueFolders.has(skill.name))
61582
+ coherent = false;
61583
+ }
61584
+ if (await directoryExists(join7(home, ".claude"))) {
61585
+ for (const skill of snapshot2.skills) {
61586
+ const link = join7(home, ".claude", "skills", skill.name);
61587
+ try {
61588
+ if (!(await lstat2(link)).isSymbolicLink())
61589
+ coherent = false;
61590
+ else if (resolve3(dirname9(link), await readlink(link)) !== join7(home, ".agents", "skills", skill.name)) {
61591
+ coherent = false;
61592
+ }
61593
+ } catch {
61594
+ coherent = false;
61595
+ }
61596
+ }
61597
+ }
61598
+ const exactCurrent = coherent && managed.length === snapshot2.skills.length && folders.every(({ key, folder, entry }) => {
61599
+ const skill = expected.get(folder);
61600
+ return key === folder && skill !== undefined && entry.ref === snapshot2.ref && actualHashes.get(folder) === skill.tree;
61601
+ });
61602
+ return {
61603
+ state: exactCurrent ? "current" : coherent ? "outdated" : "unhealthy",
61604
+ managedNames: managed.map(([name]) => name),
61605
+ managedFolders: [...uniqueFolders]
61606
+ };
61607
+ }
61608
+ async function directoryExists(path4) {
61609
+ try {
61610
+ return (await lstat2(path4)).isDirectory();
61611
+ } catch {
61612
+ return false;
61613
+ }
61614
+ }
61615
+ async function sourceSnapshot(dependencies) {
61616
+ return dependencies.resolveSource ? await dependencies.resolveSource() : await resolveAstraleSkillSource(dependencies.run);
61617
+ }
61618
+ function installerArgs(...args) {
61619
+ return ["--yes", SKILLS_INSTALLER_PACKAGE, ...args];
61620
+ }
61621
+ async function installSnapshot(snapshot2, execute, selectedAgents) {
61622
+ return await execute("npx", installerArgs("add", `${ASTRALE_CLI_SKILL_SOURCE}#${snapshot2.ref}`, "-g", "-y", "--skill", ...snapshot2.skills.map((skill) => skill.name), ...selectedAgents.length > 0 ? ["--agent", ...selectedAgents] : []));
61623
+ }
61624
+ async function selectedAgents(home, lockPath) {
61625
+ const { lock } = await readSkillLock(lockPath);
61626
+ const agents = new Set((lock?.lastSelectedAgents ?? []).filter((agent) => typeof agent === "string" && SAFE_NAME.test(agent)));
61627
+ agents.add("codex");
61628
+ if (await directoryExists(join7(home, ".claude")))
61629
+ agents.add("claude-code");
61630
+ return [...agents];
61631
+ }
61632
+ async function cleanManagedState(names, home, lockPath) {
61633
+ for (const name of names) {
61634
+ await rm3(join7(home, ".agents", "skills", name), { recursive: true, force: true });
61635
+ }
61636
+ const { lock, raw: raw2 } = await readSkillLock(lockPath);
61637
+ if (!lock) {
61638
+ if (raw2 !== null)
61639
+ await rm3(lockPath, { force: true });
61640
+ return;
61641
+ }
61642
+ for (const [name, entry] of Object.entries(lock.skills)) {
61643
+ if (sourceOwned(entry))
61644
+ delete lock.skills[name];
61645
+ }
61646
+ await writeSkillLock(lockPath, lock);
61647
+ }
61648
+ async function removeKnownAgentLinks(names, home) {
61649
+ const roots = await readdir3(home, { withFileTypes: true }).catch(() => []);
61650
+ for (const root of roots) {
61651
+ if (!root.isDirectory() || !root.name.startsWith(".") || root.name === ".agents")
61652
+ continue;
61653
+ for (const name of names) {
61654
+ const link = join7(home, root.name, "skills", name);
61655
+ try {
61656
+ const stat3 = await lstat2(link);
61657
+ if (!stat3.isSymbolicLink())
61658
+ continue;
61659
+ const target2 = await readlink(link);
61660
+ if (resolve3(dirname9(link), target2) === join7(home, ".agents", "skills", name)) {
61661
+ await rm3(link, { force: true });
61662
+ }
61663
+ } catch {}
61664
+ }
61665
+ }
61666
+ }
61667
+ async function pruneObsoleteEntries(snapshot2, home, lockPath, execute, knownRetired) {
61668
+ const expected = new Set(snapshot2.skills.map((skill) => skill.name));
61669
+ const { lock } = await readSkillLock(lockPath);
61670
+ const retiredEntries = Object.entries(lock?.skills ?? {}).filter(([, entry]) => {
61671
+ const folder = folderName(entry);
61672
+ return sourceOwned(entry) && folder !== null && !expected.has(folder);
61673
+ });
61674
+ const retired = [
61675
+ ...new Set([
61676
+ ...knownRetired,
61677
+ ...retiredEntries.flatMap(([, entry]) => folderName(entry) ?? [])
61678
+ ])
61679
+ ];
61680
+ if (retired.length > 0) {
61681
+ const result = await execute("npx", installerArgs("remove", ...retired, "-g", "-y"));
61682
+ if (result.code !== 0)
61683
+ throw new Error(result.stderr || result.stdout || "skill removal failed");
61684
+ }
61685
+ const refreshed = await readSkillLock(lockPath);
61686
+ if (!refreshed.lock)
61687
+ return;
61688
+ let changed = false;
61689
+ for (const [name, entry] of Object.entries(refreshed.lock.skills)) {
61690
+ if (!sourceOwned(entry))
61691
+ continue;
61692
+ const folder = folderName(entry);
61693
+ if (folder === null || name !== folder || !expected.has(folder)) {
61694
+ delete refreshed.lock.skills[name];
61695
+ changed = true;
61696
+ }
61697
+ }
61698
+ if (changed)
61699
+ await writeSkillLock(lockPath, refreshed.lock);
61700
+ await removeKnownAgentLinks(retired, home);
61701
+ for (const name of retired) {
61702
+ await rm3(join7(home, ".agents", "skills", name), { recursive: true, force: true });
61703
+ }
61704
+ }
61705
+ async function captureAgentLinks(home, names) {
61706
+ const links = [];
61707
+ const roots = await readdir3(home, { withFileTypes: true }).catch(() => []);
61708
+ for (const root of roots) {
61709
+ if (!root.isDirectory() || !root.name.startsWith(".") || root.name === ".agents")
61710
+ continue;
61711
+ for (const name of names) {
61712
+ const path4 = join7(home, root.name, "skills", name);
61713
+ try {
61714
+ if ((await lstat2(path4)).isSymbolicLink()) {
61715
+ links.push({ root: root.name, name, target: await readlink(path4) });
61716
+ }
61717
+ } catch {}
61718
+ }
61719
+ }
61720
+ return links;
61721
+ }
61722
+ async function writeBackupManifest(backup) {
61723
+ const next = join7(backup.root, `${BACKUP_MANIFEST}.next`);
61724
+ await writeFile6(next, JSON.stringify(backup), { mode: 384 });
61725
+ await rename3(next, join7(backup.root, BACKUP_MANIFEST));
61313
61726
  }
61727
+ async function captureBackup(home, lockPath, names) {
61728
+ const agentsRoot = join7(home, ".agents");
61729
+ await mkdir10(agentsRoot, { recursive: true });
61730
+ const root = await mkdtemp2(join7(agentsRoot, BACKUP_PREFIX));
61731
+ const copied = [];
61732
+ for (const name of names) {
61733
+ const source2 = join7(agentsRoot, "skills", name);
61734
+ try {
61735
+ await cp(source2, join7(root, name), { recursive: true, dereference: false });
61736
+ copied.push(name);
61737
+ } catch (error52) {
61738
+ if (error52.code !== "ENOENT")
61739
+ throw error52;
61740
+ }
61741
+ }
61742
+ const lock = await readSkillLock(lockPath);
61743
+ const backup = {
61744
+ root,
61745
+ names,
61746
+ copied,
61747
+ links: await captureAgentLinks(home, names),
61748
+ lockRaw: lock.raw,
61749
+ createdAt: new Date().toISOString(),
61750
+ phase: "prepared"
61751
+ };
61752
+ await writeBackupManifest(backup);
61753
+ return backup;
61754
+ }
61755
+ async function extendBackup(backup, home, names) {
61756
+ const additions = names.filter((name) => !backup.names.includes(name));
61757
+ if (additions.length === 0)
61758
+ return;
61759
+ for (const name of additions) {
61760
+ const source2 = join7(home, ".agents", "skills", name);
61761
+ try {
61762
+ await cp(source2, join7(backup.root, name), { recursive: true, dereference: false });
61763
+ backup.copied.push(name);
61764
+ } catch (error52) {
61765
+ if (error52.code !== "ENOENT")
61766
+ throw error52;
61767
+ }
61768
+ }
61769
+ backup.names.push(...additions);
61770
+ backup.links.push(...await captureAgentLinks(home, additions));
61771
+ await writeBackupManifest(backup);
61772
+ }
61773
+ async function restoreAgentLinks(backup, home, onlyForeign = false) {
61774
+ for (const link of backup.links) {
61775
+ const path4 = join7(home, link.root, "skills", link.name);
61776
+ const canonical = join7(home, ".agents", "skills", link.name);
61777
+ if (onlyForeign && resolve3(dirname9(path4), link.target) === canonical)
61778
+ continue;
61779
+ await mkdir10(dirname9(path4), { recursive: true });
61780
+ await rm3(path4, { recursive: true, force: true });
61781
+ await symlink(link.target, path4);
61782
+ }
61783
+ }
61784
+ async function restoreBackup(backup, home, lockPath) {
61785
+ for (const name of backup.names) {
61786
+ await rm3(join7(home, ".agents", "skills", name), { recursive: true, force: true });
61787
+ }
61788
+ await mkdir10(join7(home, ".agents", "skills"), { recursive: true });
61789
+ for (const name of backup.copied) {
61790
+ await cp(join7(backup.root, name), join7(home, ".agents", "skills", name), {
61791
+ recursive: true,
61792
+ dereference: false
61793
+ });
61794
+ }
61795
+ if (backup.lockRaw === null)
61796
+ await rm3(lockPath, { force: true });
61797
+ else {
61798
+ await mkdir10(dirname9(lockPath), { recursive: true });
61799
+ const next = `${lockPath}.restore`;
61800
+ await writeFile6(next, backup.lockRaw, { mode: 384 });
61801
+ await rename3(next, lockPath);
61802
+ }
61803
+ await removeKnownAgentLinks(backup.names, home);
61804
+ await restoreAgentLinks(backup, home);
61805
+ }
61806
+ async function recoverInterruptedBackup(home, lockPath) {
61807
+ const agentsRoot = join7(home, ".agents");
61808
+ const candidates = (await readdir3(agentsRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name.startsWith(BACKUP_PREFIX)).map((entry) => join7(agentsRoot, entry.name));
61809
+ const backups = [];
61810
+ for (const root of candidates) {
61811
+ try {
61812
+ const parsed = JSON.parse(await readFile11(join7(root, BACKUP_MANIFEST), "utf8"));
61813
+ if (Array.isArray(parsed.names) && parsed.names.every((name) => typeof name === "string" && SAFE_NAME.test(name)) && Array.isArray(parsed.copied) && parsed.copied.every((name) => typeof name === "string" && parsed.names.includes(name)) && Array.isArray(parsed.links) && (typeof parsed.lockRaw === "string" || parsed.lockRaw === null) && typeof parsed.createdAt === "string" && (parsed.phase === "prepared" || parsed.phase === "verified") && parsed.links.every((link) => link !== null && typeof link === "object" && typeof link.root === "string" && /^\.[^/]+$/u.test(link.root) && typeof link.name === "string" && parsed.names.includes(link.name) && typeof link.target === "string")) {
61814
+ const backup = { ...parsed, root };
61815
+ if (backup.phase === "verified")
61816
+ await rm3(root, { recursive: true, force: true });
61817
+ else
61818
+ backups.push(backup);
61819
+ } else {
61820
+ await rm3(root, { recursive: true, force: true });
61821
+ }
61822
+ } catch {
61823
+ await rm3(root, { recursive: true, force: true });
61824
+ }
61825
+ }
61826
+ if (backups.length === 0)
61827
+ return;
61828
+ backups.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
61829
+ await restoreBackup(backups[0], home, lockPath);
61830
+ await Promise.all(candidates.map((root) => rm3(root, { recursive: true, force: true })));
61831
+ }
61832
+ async function markBackupVerified(backup) {
61833
+ backup.phase = "verified";
61834
+ await writeBackupManifest(backup);
61835
+ }
61836
+ function skillFailure(error52) {
61837
+ const detail = error52 instanceof Error && error52.message.trim() ? `: ${error52.message}` : "";
61838
+ return new AstraleError("SKILL_UPDATE_FAILED", `Astrale could not install and verify its agent skills${detail}`, `Retry with \`${ASTRALE_SKILL_REPAIR_COMMAND}\`.`, error52 instanceof Error ? { cause: error52 } : undefined);
61839
+ }
61840
+ async function checkAstraleSkills(overrides = {}) {
61841
+ const dependencies = resolvedDependencies(overrides);
61842
+ try {
61843
+ const snapshot2 = await sourceSnapshot(dependencies);
61844
+ const inspection = await inspectAstraleSkills(snapshot2, dependencies.home, dependencies.lockPath);
61845
+ return {
61846
+ status: inspection.state === "current" ? "current" : inspection.state === "unhealthy" ? "repair-needed" : "update-available"
61847
+ };
61848
+ } catch (error52) {
61849
+ return { status: "unavailable", error: error52 instanceof Error ? error52.message : String(error52) };
61850
+ }
61851
+ }
61852
+ async function syncAstraleSkills(overrides = {}) {
61853
+ const dependencies = resolvedDependencies(overrides);
61854
+ let snapshot2;
61855
+ try {
61856
+ snapshot2 = await sourceSnapshot(dependencies);
61857
+ } catch (error52) {
61858
+ throw skillFailure(error52);
61859
+ }
61860
+ const lockFile = join7(process.env.ASTRALE_HOME ?? join7(dependencies.home, ".astrale"), "locks", "skills-update.lock");
61861
+ try {
61862
+ return await withFileLock2(lockFile, async () => {
61863
+ await recoverInterruptedBackup(dependencies.home, dependencies.lockPath);
61864
+ const initial = await inspectAstraleSkills(snapshot2, dependencies.home, dependencies.lockPath);
61865
+ if (initial.state === "current")
61866
+ return { status: "unchanged" };
61867
+ let expectedNames = snapshot2.skills.map((skill) => skill.name);
61868
+ let managedFolders = [...new Set([...expectedNames, ...initial.managedFolders])];
61869
+ let knownRetired = initial.managedFolders.filter((name) => !expectedNames.includes(name));
61870
+ const agents = await selectedAgents(dependencies.home, dependencies.lockPath);
61871
+ const backup = await captureBackup(dependencies.home, dependencies.lockPath, managedFolders);
61872
+ let lastError;
61873
+ try {
61874
+ for (let attempt = 0;attempt < 2; attempt++) {
61875
+ try {
61876
+ if (attempt === 1) {
61877
+ snapshot2 = await sourceSnapshot(dependencies);
61878
+ expectedNames = snapshot2.skills.map((skill) => skill.name);
61879
+ managedFolders = [...new Set([...managedFolders, ...expectedNames])];
61880
+ await extendBackup(backup, dependencies.home, managedFolders);
61881
+ knownRetired = [
61882
+ ...new Set([
61883
+ ...knownRetired,
61884
+ ...managedFolders.filter((name) => !expectedNames.includes(name))
61885
+ ])
61886
+ ];
61887
+ }
61888
+ if (initial.state === "unhealthy" || attempt === 1) {
61889
+ await cleanManagedState(managedFolders, dependencies.home, dependencies.lockPath);
61890
+ }
61891
+ const installed = await installSnapshot(snapshot2, dependencies.run, agents);
61892
+ if (installed.code !== 0) {
61893
+ throw new Error(installed.stderr || installed.stdout || "skill installer failed");
61894
+ }
61895
+ await pruneObsoleteEntries(snapshot2, dependencies.home, dependencies.lockPath, dependencies.run, knownRetired);
61896
+ const verified = await inspectAstraleSkills(snapshot2, dependencies.home, dependencies.lockPath);
61897
+ if (verified.state !== "current") {
61898
+ throw new Error("installed Astrale skills did not pass verification");
61899
+ }
61900
+ const latest = await sourceSnapshot(dependencies);
61901
+ if (latest.revision !== snapshot2.revision) {
61902
+ throw new Error("Astrale skill source changed during installation");
61903
+ }
61904
+ await markBackupVerified(backup);
61905
+ await rm3(backup.root, { recursive: true, force: true });
61906
+ return {
61907
+ status: initial.state === "absent" ? "installed" : initial.state === "outdated" ? "updated" : "repaired"
61908
+ };
61909
+ } catch (error52) {
61910
+ lastError = error52;
61911
+ }
61912
+ }
61913
+ throw lastError;
61914
+ } catch (error52) {
61915
+ if (initial.state === "absent" || initial.state === "unhealthy") {
61916
+ await cleanManagedState(managedFolders, dependencies.home, dependencies.lockPath);
61917
+ await removeKnownAgentLinks(managedFolders, dependencies.home);
61918
+ await restoreAgentLinks(backup, dependencies.home, true);
61919
+ } else {
61920
+ await restoreBackup(backup, dependencies.home, dependencies.lockPath);
61921
+ }
61922
+ await rm3(backup.root, { recursive: true, force: true });
61923
+ throw skillFailure(error52);
61924
+ }
61925
+ });
61926
+ } catch (error52) {
61927
+ if (error52 instanceof AstraleError)
61928
+ throw error52;
61929
+ throw skillFailure(error52);
61930
+ }
61931
+ }
61932
+ var ASTRALE_CLI_SKILL_SOURCE = "astrale-os/cli", ASTRALE_SKILL_REPAIR_COMMAND = "astrale update --yes --no-deps", SKILLS_INSTALLER_PACKAGE = "skills@1.5.23", SKILL_INSTALL_HINT, SOURCE_REPOSITORY_URL = "https://github.com/astrale-os/cli.git", SAFE_NAME, BACKUP_PREFIX = ".astrale-skill-backup-", BACKUP_MANIFEST = ".manifest.json";
61933
+ var init_sync = __esm(() => {
61934
+ init_errors2();
61935
+ init_proc();
61936
+ init_lock();
61937
+ SKILL_INSTALL_HINT = ASTRALE_SKILL_REPAIR_COMMAND;
61938
+ SAFE_NAME = /^[a-z0-9][a-z0-9._-]*$/iu;
61939
+ });
61940
+
61941
+ // src/lib/skills.ts
61942
+ import { existsSync, lstatSync, mkdirSync, readlinkSync, symlinkSync } from "node:fs";
61943
+ import { homedir as homedir3 } from "node:os";
61944
+ import { dirname as dirname10, join as join8 } from "node:path";
61314
61945
  function skillSearchDirs() {
61315
61946
  const dirs = [];
61316
61947
  let cur = process.cwd();
61317
61948
  for (;; ) {
61318
- dirs.push(join6(cur, ".claude", "skills"));
61319
- const parent = dirname8(cur);
61949
+ dirs.push(join8(cur, ".claude", "skills"));
61950
+ const parent = dirname10(cur);
61320
61951
  if (parent === cur)
61321
61952
  break;
61322
61953
  cur = parent;
61323
61954
  }
61324
- dirs.push(join6(homedir2(), ".claude", "skills"));
61955
+ dirs.push(join8(homedir3(), ".claude", "skills"));
61325
61956
  return dirs;
61326
61957
  }
61327
61958
  function detectSkill(name) {
61328
61959
  for (const dir of skillSearchDirs()) {
61329
- const file2 = join6(dir, name, "SKILL.md");
61960
+ const file2 = join8(dir, name, "SKILL.md");
61330
61961
  if (existsSync(file2))
61331
61962
  return { installed: true, location: file2 };
61332
61963
  }
@@ -61345,9 +61976,9 @@ function isSymlink(p) {
61345
61976
  function findAgentsSkillsRoot(fromDir) {
61346
61977
  let cur = fromDir;
61347
61978
  for (;; ) {
61348
- if (existsSync(join6(cur, ".agents", "skills")))
61979
+ if (existsSync(join8(cur, ".agents", "skills")))
61349
61980
  return cur;
61350
- const parent = dirname8(cur);
61981
+ const parent = dirname10(cur);
61351
61982
  if (parent === cur)
61352
61983
  return null;
61353
61984
  cur = parent;
@@ -61357,7 +61988,7 @@ function skillsBridgeStatus(fromDir = process.cwd()) {
61357
61988
  const root = findAgentsSkillsRoot(fromDir);
61358
61989
  if (!root)
61359
61990
  return { kind: "none" };
61360
- const link = join6(root, ".claude", "skills");
61991
+ const link = join8(root, ".claude", "skills");
61361
61992
  if (isSymlink(link)) {
61362
61993
  try {
61363
61994
  if (readlinkSync(link) === BRIDGE_TARGET)
@@ -61373,16 +62004,15 @@ function ensureSkillsBridge(fromDir = process.cwd()) {
61373
62004
  const status = skillsBridgeStatus(fromDir);
61374
62005
  if (status.kind !== "unbridged")
61375
62006
  return status;
61376
- mkdirSync(dirname8(status.link), { recursive: true });
62007
+ mkdirSync(dirname10(status.link), { recursive: true });
61377
62008
  symlinkSync(BRIDGE_TARGET, status.link);
61378
62009
  return { kind: "bridged", root: status.root };
61379
62010
  }
61380
- var ASTRALE_CLI_SKILL = "astrale-cli", ASTRALE_DOMAIN_SKILL = "astrale-domain", AGENT_BROWSER_SKILL = "agent-browser", ASTRALE_CLI_SKILL_SOURCE = "astrale-os/cli", SKILL_INSTALL_HINT, BRIDGE_TARGET;
62011
+ var AGENT_BROWSER_SKILL = "agent-browser", BRIDGE_TARGET;
61381
62012
  var init_skills = __esm(() => {
61382
62013
  init_browser();
61383
- init_proc();
61384
- SKILL_INSTALL_HINT = `npx skills add ${ASTRALE_CLI_SKILL_SOURCE} -g`;
61385
- BRIDGE_TARGET = join6("..", ".agents", "skills");
62014
+ init_sync();
62015
+ BRIDGE_TARGET = join8("..", ".agents", "skills");
61386
62016
  });
61387
62017
 
61388
62018
  // src/setup/steps/agent-browser.ts
@@ -61625,9 +62255,9 @@ var init_auth5 = __esm(() => {
61625
62255
 
61626
62256
  // src/setup/steps/domain.ts
61627
62257
  import { existsSync as existsSync2 } from "node:fs";
61628
- import { join as join7 } from "node:path";
62258
+ import { join as join9 } from "node:path";
61629
62259
  function hasDomainProject() {
61630
- return existsSync2(join7(process.cwd(), "astrale.config.ts"));
62260
+ return existsSync2(join9(process.cwd(), "astrale.config.ts"));
61631
62261
  }
61632
62262
  var FIX4 = "npx create-astrale-domain <name> --instance <slug>", domainStep;
61633
62263
  var init_domain2 = __esm(() => {
@@ -70385,7 +71015,7 @@ function createClasses(definitions, validation) {
70385
71015
  const declarations2 = new Map(definitions.filter((value2) => value2.ref.kind === "class").map((value2) => [Key.of(value2.ref), value2]));
70386
71016
  const resolved = new Map;
70387
71017
  const visiting = new Set;
70388
- const resolve3 = (ref) => {
71018
+ const resolve4 = (ref) => {
70389
71019
  const key = Key.of(ref);
70390
71020
  const cached3 = resolved.get(key);
70391
71021
  if (cached3 !== undefined)
@@ -70396,7 +71026,7 @@ function createClasses(definitions, validation) {
70396
71026
  if (definition === undefined)
70397
71027
  throw new TypeError(`Class ${key} is absent from Domain input.`);
70398
71028
  visiting.add(key);
70399
- const parents = definition.extends.map(resolve3);
71029
+ const parents = definition.extends.map(resolve4);
70400
71030
  if (parents.some((parent) => parent.kind !== definition.kind)) {
70401
71031
  throw new TypeError(`Class ${key} extends a Class of another graph kind.`);
70402
71032
  }
@@ -70510,7 +71140,7 @@ function createClasses(definitions, validation) {
70510
71140
  return value2;
70511
71141
  };
70512
71142
  for (const definition of declarations2.values())
70513
- resolve3(definition.ref);
71143
+ resolve4(definition.ref);
70514
71144
  return resolved;
70515
71145
  }
70516
71146
  function uniquePolicyRefs(values) {
@@ -70981,7 +71611,7 @@ var init_program = __esm(() => {
70981
71611
  });
70982
71612
 
70983
71613
  // node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.13/node_modules/@astrale-os/kernel-dsl/dist/v1/schema/resolution/domain.js
70984
- function resolve3(schema) {
71614
+ function resolve4(schema) {
70985
71615
  const cached3 = resolvedSchemas.get(schema);
70986
71616
  if (cached3 !== undefined)
70987
71617
  return cached3;
@@ -71106,7 +71736,7 @@ var init_schema5 = __esm(() => {
71106
71736
 
71107
71737
  // node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.13/node_modules/@astrale-os/kernel-dsl/dist/v1/schema/compatibility/meaning.js
71108
71738
  function compareDependencyMeaning(dependent, target2) {
71109
- resolve3(dependent);
71739
+ resolve4(dependent);
71110
71740
  const closure2 = knownDomainClosure(dependent) ?? [];
71111
71741
  const source2 = closure2.find((candidate) => candidate.schema.origin === target2.origin)?.schema;
71112
71742
  if (source2 === undefined) {
@@ -71132,7 +71762,7 @@ function compareMemberMeaning(source2, target2, roots) {
71132
71762
  diagnostic2("DM_MEMBER_ROOT_INVALID", "/roots", "Member comparison roots must be one non-empty duplicate-free Key set.")
71133
71763
  ]);
71134
71764
  }
71135
- resolve3(source2);
71765
+ resolve4(source2);
71136
71766
  const closure2 = knownDomainClosure(source2) ?? [];
71137
71767
  const schemas3 = new Map([
71138
71768
  [source2.origin, source2],
@@ -71143,7 +71773,7 @@ function compareMemberMeaning(source2, target2, roots) {
71143
71773
  return compareSourceMeanings(source2, target2, sourceMeanings);
71144
71774
  }
71145
71775
  function compareSourceMeanings(source2, target2, sourceMeanings) {
71146
- resolve3(target2);
71776
+ resolve4(target2);
71147
71777
  const footprint = Object.freeze([...sourceMeanings.keys()].sort(compareUnicode));
71148
71778
  const changes = [];
71149
71779
  const comparisonCoordinates = [];
@@ -73942,12 +74572,12 @@ __export(exports_schema, {
73942
74572
  encode: () => encodeDomainSchema,
73943
74573
  header: () => header,
73944
74574
  load: () => load2,
73945
- resolve: () => resolve4,
74575
+ resolve: () => resolve5,
73946
74576
  revision: () => revisionOfDomainSchema,
73947
74577
  serialize: () => serializeDomainSchema,
73948
74578
  values: () => exports_value_api
73949
74579
  });
73950
- var resolve4;
74580
+ var resolve5;
73951
74581
  var init_schema6 = __esm(() => {
73952
74582
  init_domain7();
73953
74583
  init_accept3();
@@ -73958,7 +74588,7 @@ var init_schema6 = __esm(() => {
73958
74588
  init_compiled();
73959
74589
  init_diagnostic2();
73960
74590
  init_value_api();
73961
- resolve4 = resolve3;
74591
+ resolve5 = resolve4;
73962
74592
  });
73963
74593
 
73964
74594
  // node_modules/.pnpm/@astrale-os+kernel-dsl@0.2.0-beta.13/node_modules/@astrale-os/kernel-dsl/dist/v1/bundle/diagnostic.js
@@ -75709,12 +76339,12 @@ var init_view_resolution = __esm(() => {
75709
76339
  });
75710
76340
 
75711
76341
  // node_modules/.pnpm/@astrale-os+kernel-core@0.9.0-beta.16/node_modules/@astrale-os/kernel-core/dist/schema/kernel/syscalls/descriptors/view.js
75712
- var resolve5;
76342
+ var resolve6;
75713
76343
  var init_view5 = __esm(() => {
75714
76344
  init_kernel();
75715
76345
  init_view();
75716
76346
  init_view_resolution();
75717
- resolve5 = methodSyscall(resolve, K.classes.View.static.methods.resolve);
76347
+ resolve6 = methodSyscall(resolve, K.classes.View.static.methods.resolve);
75718
76348
  });
75719
76349
 
75720
76350
  // node_modules/.pnpm/@astrale-os+kernel-core@0.9.0-beta.16/node_modules/@astrale-os/kernel-core/dist/schema/kernel/syscalls/descriptors/index.js
@@ -78542,14 +79172,14 @@ async function boundedResponse(response2, maximum, signal) {
78542
79172
  signal.removeEventListener("abort", abort);
78543
79173
  reader.releaseLock();
78544
79174
  }
78545
- return join8(chunks, total);
79175
+ return join10(chunks, total);
78546
79176
  }
78547
79177
  function requireExactResponse(response2, expected, label) {
78548
79178
  if (response2.redirected || response2.url !== expected) {
78549
79179
  throw new ClientError(`${label} did not return the exact requested URL.`);
78550
79180
  }
78551
79181
  }
78552
- function join8(parts, length) {
79182
+ function join10(parts, length) {
78553
79183
  if (parts.length === 1)
78554
79184
  return new Uint8Array(parts[0]);
78555
79185
  const output3 = new Uint8Array(length);
@@ -79426,7 +80056,7 @@ function requestUrl(input) {
79426
80056
  return new URL(input.url);
79427
80057
  }
79428
80058
  function fetchWithNode(url3, init, ca) {
79429
- return new Promise((resolve6, reject) => {
80059
+ return new Promise((resolve7, reject) => {
79430
80060
  const request = httpsRequest(url3, {
79431
80061
  method: init?.method ?? "GET",
79432
80062
  headers: headersInitToRecord(init?.headers),
@@ -79443,7 +80073,7 @@ function fetchWithNode(url3, init, ca) {
79443
80073
  headers: responseHeaders(response2.headers)
79444
80074
  });
79445
80075
  Object.defineProperty(result, "url", { value: url3.toString() });
79446
- resolve6(result);
80076
+ resolve7(result);
79447
80077
  });
79448
80078
  });
79449
80079
  request.on("error", reject);
@@ -80705,49 +81335,65 @@ var init_instance3 = __esm(() => {
80705
81335
  });
80706
81336
 
80707
81337
  // src/setup/steps/skills.ts
80708
- var FIX5, bothInstalled = () => detectSkill(ASTRALE_CLI_SKILL).installed && detectSkill(ASTRALE_DOMAIN_SKILL).installed, skillsStep;
81338
+ var FIX5, skillsStep;
80709
81339
  var init_skills2 = __esm(() => {
80710
81340
  init_log();
80711
81341
  init_skills();
80712
81342
  FIX5 = SKILL_INSTALL_HINT;
80713
81343
  skillsStep = {
80714
81344
  id: "skills",
80715
- title: "astrale agent skills (cli + domain)",
81345
+ title: "Astrale agent skills",
80716
81346
  group: "equip",
80717
81347
  async detect() {
80718
- if (bothInstalled()) {
80719
- return { state: "satisfied", summary: "astrale-cli + astrale-domain skills installed" };
81348
+ const result = await checkAstraleSkills();
81349
+ if (result.status === "current") {
81350
+ return { state: "satisfied", summary: "Astrale skills are installed and up to date" };
81351
+ }
81352
+ if (result.status === "unavailable") {
81353
+ return {
81354
+ state: "broken",
81355
+ summary: "Astrale skills could not be verified",
81356
+ detail: result.error,
81357
+ fixHint: FIX5
81358
+ };
80720
81359
  }
80721
- const have = detectSkill(ASTRALE_CLI_SKILL).installed ? "astrale-domain" : "astrale agent skills";
80722
- return { state: "gap", summary: `${have} not installed`, fixHint: FIX5 };
81360
+ return {
81361
+ state: "gap",
81362
+ summary: result.status === "repair-needed" ? "Astrale skills need repair" : "Astrale skills need installation or update",
81363
+ fixHint: FIX5
81364
+ };
80723
81365
  },
80724
81366
  async ensure() {
80725
- if (bothInstalled()) {
80726
- log.success("astrale-cli + astrale-domain skills already installed");
80727
- return "unchanged";
80728
- }
80729
- log.step(`Installing the astrale agent skills — ${FIX5}`);
80730
- if (!await installSkills()) {
80731
- log.warn(`Skill install did not complete — run it later: ${FIX5}`);
81367
+ log.step("Ensuring Astrale agent skills are current and healthy");
81368
+ try {
81369
+ const result = await syncAstraleSkills();
81370
+ if (result.status === "unchanged") {
81371
+ log.success("Astrale skills already up to date");
81372
+ return "unchanged";
81373
+ }
81374
+ if (result.status === "installed")
81375
+ log.success("Astrale skills installed");
81376
+ else if (result.status === "updated")
81377
+ log.success("Astrale skills updated");
81378
+ else if (result.status === "repaired")
81379
+ log.success("Astrale skills repaired and updated");
81380
+ return "fixed";
81381
+ } catch (error52) {
81382
+ log.warn(`Astrale skills could not be installed safely${error52 instanceof Error ? `: ${error52.message}` : ""}`);
81383
+ log.dim(` Retry: ${FIX5}`);
80732
81384
  return "failed";
80733
81385
  }
80734
- if (bothInstalled()) {
80735
- log.success("astrale-cli + astrale-domain skills installed");
80736
- } else {
80737
- log.success("astrale-cli skill installed");
80738
- }
80739
- return "fixed";
80740
81386
  }
80741
81387
  };
80742
81388
  });
80743
81389
 
80744
81390
  // src/setup/steps/skills-bridge.ts
80745
81391
  import { existsSync as existsSync3, readdirSync } from "node:fs";
80746
- import { join as join9 } from "node:path";
81392
+ import { join as join11 } from "node:path";
80747
81393
  function countStaged(root) {
80748
81394
  try {
80749
- const dir = join9(root, ".agents", "skills");
80750
- return readdirSync(dir).filter((e) => existsSync3(join9(dir, e, "SKILL.md"))).length;
81395
+ const dir = join11(root, ".agents", "skills");
81396
+ return readdirSync(dir).filter((e) => existsSync3(join11(dir, e, "SKILL.md"))).length;
80751
81397
  } catch {
80752
81398
  return 0;
80753
81399
  }
@@ -80785,7 +81431,7 @@ var init_skills_bridge = __esm(() => {
80785
81431
  }
80786
81432
  const after = ensureSkillsBridge();
80787
81433
  if (after.kind === "bridged") {
80788
- log.success(`Workspace skills bridged — ${join9(after.root, ".agents/skills")} → .claude/skills`);
81434
+ log.success(`Workspace skills bridged — ${join11(after.root, ".agents/skills")} → .claude/skills`);
80789
81435
  return "fixed";
80790
81436
  }
80791
81437
  log.warn("Could not create the skills bridge — create it manually: ln -s ../.agents/skills .claude/skills");
@@ -81253,18 +81899,18 @@ Examples:
81253
81899
 
81254
81900
  // src/lib/sdk-deps.ts
81255
81901
  import { existsSync as existsSync4 } from "node:fs";
81256
- import { join as join10 } from "node:path";
81902
+ import { join as join12 } from "node:path";
81257
81903
  function inDomainProject(cwd = process.cwd()) {
81258
- return existsSync4(join10(cwd, "astrale.config.ts"));
81904
+ return existsSync4(join12(cwd, "astrale.config.ts"));
81259
81905
  }
81260
81906
  function foreignPackageManager(cwd = process.cwd()) {
81261
- if (existsSync4(join10(cwd, "pnpm-lock.yaml")))
81907
+ if (existsSync4(join12(cwd, "pnpm-lock.yaml")))
81262
81908
  return null;
81263
- if (existsSync4(join10(cwd, "package-lock.json")))
81909
+ if (existsSync4(join12(cwd, "package-lock.json")))
81264
81910
  return "npm";
81265
- if (existsSync4(join10(cwd, "yarn.lock")))
81911
+ if (existsSync4(join12(cwd, "yarn.lock")))
81266
81912
  return "yarn";
81267
- if (existsSync4(join10(cwd, "bun.lockb")) || existsSync4(join10(cwd, "bun.lock")))
81913
+ if (existsSync4(join12(cwd, "bun.lockb")) || existsSync4(join12(cwd, "bun.lock")))
81268
81914
  return "bun";
81269
81915
  return null;
81270
81916
  }
@@ -81317,15 +81963,30 @@ __export(exports_update, {
81317
81963
  fetchNpmTargetVersion: () => fetchNpmTargetVersion
81318
81964
  });
81319
81965
  async function refreshSkills() {
81320
- if (!detectSkill(ASTRALE_CLI_SKILL).installed) {
81321
- log.dim(` Agent skills not installed — get them with: ${SKILL_INSTALL_HINT}`);
81322
- return;
81323
- }
81324
- log.step(`Refreshing the astrale agent skills — ${SKILL_INSTALL_HINT}`);
81325
- if (await installSkills()) {
81326
- log.success("astrale agent skills up to date");
81327
- } else {
81328
- log.warn(`Skill refresh did not complete — run it later: ${SKILL_INSTALL_HINT}`);
81966
+ log.step("Ensuring Astrale agent skills are current and healthy");
81967
+ const result = await syncAstraleSkills();
81968
+ if (result.status === "unchanged")
81969
+ log.success("Astrale skills already up to date");
81970
+ else if (result.status === "installed")
81971
+ log.success("Astrale skills installed");
81972
+ else if (result.status === "updated")
81973
+ log.success("Astrale skills updated");
81974
+ else if (result.status === "repaired")
81975
+ log.success("Astrale skills repaired and updated");
81976
+ return result;
81977
+ }
81978
+ function skillCheckStale(skills) {
81979
+ return skills.status === "update-available" || skills.status === "repair-needed";
81980
+ }
81981
+ function printSkillCheck(skills) {
81982
+ if (skills.status === "current")
81983
+ log.success("Astrale skills are up to date");
81984
+ else if (skills.status === "update-available")
81985
+ log.info("Astrale skills update available");
81986
+ else if (skills.status === "repair-needed")
81987
+ log.warn("Astrale skills need repair");
81988
+ else if (skills.status === "unavailable") {
81989
+ log.warn(`Could not verify Astrale skills${skills.error ? `: ${skills.error}` : ""}`);
81329
81990
  }
81330
81991
  }
81331
81992
  async function refreshSdkDeps(check3, assumeYes = false) {
@@ -81452,7 +82113,7 @@ var init_update2 = __esm(() => {
81452
82113
  default: DEFAULT_UPDATE_CHANNEL
81453
82114
  },
81454
82115
  { flags: "--version <version>", description: "Update to an exact version tag" },
81455
- { flags: "--no-skills", description: "Skip refreshing the astrale agent skills" },
82116
+ { flags: "--no-skills", description: "Skip ensuring the Astrale agent skills" },
81456
82117
  { flags: "--no-deps", description: "Skip checking @astrale-os SDK dependency versions" },
81457
82118
  {
81458
82119
  flags: "--yes",
@@ -81465,23 +82126,22 @@ Behavior:
81465
82126
  Keeps three things current, in order. (1) The CLI binary: updates official
81466
82127
  script installs only — if Astrale was installed by another package manager this
81467
82128
  command refuses so that manager stays in charge; downloads are checksum-verified
81468
- before the binary is replaced. (2) The agent skills: if the astrale skills (cli +
81469
- domain) are already installed, refreshes them to the latest by delegating to
81470
- "npx skills add astrale-os/cli -g" the same installer "astrale setup" uses;
81471
- fresh installs go through "astrale setup". (3) SDK deps: inside a pnpm domain
82129
+ before the binary is replaced. (2) The Astrale agent skills: installs every
82130
+ top-level skill published from astrale-os/cli main, updates healthy older
82131
+ installs, repairs inconsistent installs, and verifies the result before
82132
+ reporting success. (3) SDK deps: inside a pnpm domain
81472
82133
  project, proposes any @astrale-os/* dependency with a newer release and, on
81473
82134
  confirm, runs "pnpm update --latest --lockfile-only" (updates package.json AND
81474
82135
  the lockfile, honoring your registry + supply-chain age policy; run "pnpm
81475
82136
  install" to materialize).
81476
82137
 
81477
82138
  The default release channel is beta; --channel overrides it for one run.
81478
- --check is a dry run (binary + SDK deps; exit 10 if anything is available) and
82139
+ --check is a dry run (binary + skills + SDK deps; exit 10 if anything is available) and
81479
82140
  never writes. With --json it emits a unified staleness report
81480
- ({ stale, cli, sdk }) for tooling non-throwing, skills omitted (they ride
81481
- along with an update). --yes applies all three non-interactively (no prompts) and
82141
+ ({ stale, cli, skills, sdk }) for tooling. --yes applies all three non-interactively and
81482
82142
  is resilient — a binary that can't self-update (package-managed) warns but never
81483
- blocks the skills/deps steps; this is what domain-studio's "Update now" runs.
81484
- --no-skills / --no-deps skip those steps in a real run.
82143
+ blocks the skills/deps steps; a skill failure fails the command rather than
82144
+ claiming a partial success. --no-skills / --no-deps explicitly skip those axes.
81485
82145
 
81486
82146
  Examples:
81487
82147
  $ astrale update
@@ -81496,8 +82156,14 @@ Examples:
81496
82156
  try {
81497
82157
  if (opts.check && isMachine(opts)) {
81498
82158
  const cli = await cliStale(opts);
82159
+ const skills = opts.skills === false ? { status: "skipped" } : await checkAstraleSkills();
81499
82160
  const sdk = await sdkStale();
81500
- const report = { stale: cli.stale || sdk.stale, cli, sdk };
82161
+ const report = {
82162
+ stale: cli.stale || skillCheckStale(skills) || sdk.stale,
82163
+ cli,
82164
+ skills,
82165
+ sdk
82166
+ };
81501
82167
  output(report, opts);
81502
82168
  if (report.stale)
81503
82169
  process.exitCode = 10;
@@ -81536,22 +82202,32 @@ Examples:
81536
82202
  throw error52;
81537
82203
  log.warn(`CLI self-update skipped: ${error52.message}`);
81538
82204
  }
81539
- if (!opts.check && opts.skills !== false)
81540
- await refreshSkills();
82205
+ if (opts.skills !== false) {
82206
+ if (opts.check) {
82207
+ const skills = await checkAstraleSkills();
82208
+ printSkillCheck(skills);
82209
+ if (skillCheckStale(skills))
82210
+ anyAvailable = true;
82211
+ } else {
82212
+ await refreshSkills();
82213
+ }
82214
+ } else if (!opts.check) {
82215
+ log.dim(" Astrale skills skipped (--no-skills)");
82216
+ }
81541
82217
  if (opts.deps !== false && await refreshSdkDeps(opts.check === true, opts.yes === true)) {
81542
82218
  anyAvailable = true;
81543
82219
  }
81544
82220
  if (opts.check && anyAvailable)
81545
82221
  process.exitCode = 10;
81546
82222
  } catch (e) {
81547
- fatal(e);
82223
+ fatal(e, opts);
81548
82224
  }
81549
82225
  }
81550
82226
  };
81551
82227
  });
81552
82228
 
81553
82229
  // src/lib/binary.ts
81554
- import { writeFile as writeFile5 } from "node:fs/promises";
82230
+ import { writeFile as writeFile7 } from "node:fs/promises";
81555
82231
  async function readBinaryBody(body) {
81556
82232
  if (body instanceof Uint8Array)
81557
82233
  return new Uint8Array(body);
@@ -81603,7 +82279,7 @@ function jsonEnvelope(resp, bytes) {
81603
82279
  async function presentBinary(resp, opts, io) {
81604
82280
  const bytes = await readBinaryBody(resp.body);
81605
82281
  if (io?.outFile) {
81606
- await writeFile5(io.outFile, bytes);
82282
+ await writeFile7(io.outFile, bytes);
81607
82283
  process.stderr.write(source_default.dim(` wrote ${humanSize(bytes.length)} (${resp.mediaType}) → ${io.outFile}
81608
82284
  `));
81609
82285
  return;
@@ -81963,7 +82639,7 @@ var init_get = __esm(() => {
81963
82639
  description: "Get one canonical node by Path or ID",
81964
82640
  afterHelpText: `
81965
82641
  Behavior:
81966
- Resolves one exact Kernel V2 Path and prints the canonical Node
82642
+ Resolves one exact Kernel Path and prints the canonical Node
81967
82643
  { id, class, props }. Missing and authorization-masked nodes remain
81968
82644
  intentionally indistinguishable. @self is expanded before dispatch.
81969
82645
 
@@ -82114,7 +82790,7 @@ __export(exports_mutate, {
82114
82790
  default: () => mutate_default,
82115
82791
  mutateCommand: () => mutateCommand
82116
82792
  });
82117
- import { readFile as readFile10 } from "node:fs/promises";
82793
+ import { readFile as readFile12 } from "node:fs/promises";
82118
82794
  async function mutateCommand(opts) {
82119
82795
  let mutation;
82120
82796
  try {
@@ -82147,7 +82823,7 @@ async function readDocument(opts) {
82147
82823
  if (opts.file !== undefined) {
82148
82824
  let raw3;
82149
82825
  try {
82150
- raw3 = await readFile10(opts.file, "utf8");
82826
+ raw3 = await readFile12(opts.file, "utf8");
82151
82827
  } catch (error52) {
82152
82828
  throw new AstraleError("FILE_READ_FAILED", `Cannot read --file ${opts.file}.`, undefined, {
82153
82829
  cause: error52
@@ -82231,7 +82907,7 @@ __export(exports_query, {
82231
82907
  default: () => query_default,
82232
82908
  queryCommand: () => queryCommand
82233
82909
  });
82234
- import { readFile as readFile11 } from "node:fs/promises";
82910
+ import { readFile as readFile13 } from "node:fs/promises";
82235
82911
  async function queryCommand(sources2, opts) {
82236
82912
  let input;
82237
82913
  try {
@@ -82282,7 +82958,7 @@ async function readAst(opts) {
82282
82958
  return;
82283
82959
  let raw2;
82284
82960
  try {
82285
- raw2 = await readFile11(opts.file, "utf8");
82961
+ raw2 = await readFile13(opts.file, "utf8");
82286
82962
  } catch (error52) {
82287
82963
  throw new AstraleError("FILE_READ_FAILED", `Cannot read --file ${opts.file}.`, undefined, {
82288
82964
  cause: error52
@@ -82608,7 +83284,7 @@ async function runOnce(opts) {
82608
83284
  }
82609
83285
  async function followLogs(opts, dependencies = {
82610
83286
  run: runKernelCommand,
82611
- pause: (milliseconds) => new Promise((resolve6) => setTimeout(resolve6, milliseconds))
83287
+ pause: (milliseconds) => new Promise((resolve7) => setTimeout(resolve7, milliseconds))
82612
83288
  }) {
82613
83289
  validateLogsOpts(opts);
82614
83290
  await dependencies.run({
@@ -82776,11 +83452,8 @@ Behavior:
82776
83452
  Calls the public Kernel journal syscall and emits its { records, cursor }
82777
83453
  page. Topic selection is exact or prefix-based; cursors and timestamps are
82778
83454
  opaque strings owned by the journal backend. --follow reuses one Client Session
82779
- and advances only with the returned cursor. Machine follow output is NDJSON:
82780
- one complete admitted record per line; YAML follow is unsupported.
82781
-
82782
- Historical event-glob lowering and the application-specific services-domain
82783
- log buffer are not part of the Kernel V2 journal contract.
83455
+ and advances only with the returned cursor. With --json, follow output is
83456
+ NDJSON with one complete admitted record per line; YAML follow is unsupported.
82784
83457
 
82785
83458
  Examples:
82786
83459
  $ astrale logs -i staging --limit 50
@@ -82814,10 +83487,10 @@ Examples:
82814
83487
  // src/lib/port.ts
82815
83488
  import net from "node:net";
82816
83489
  function portFree(port, host = LOOPBACK) {
82817
- return new Promise((resolve6) => {
83490
+ return new Promise((resolve7) => {
82818
83491
  const srv = net.createServer();
82819
- srv.once("error", () => resolve6(false));
82820
- srv.once("listening", () => srv.close(() => resolve6(true)));
83492
+ srv.once("error", () => resolve7(false));
83493
+ srv.once("listening", () => srv.close(() => resolve7(true)));
82821
83494
  srv.listen({ port, host, exclusive: true });
82822
83495
  });
82823
83496
  }
@@ -82859,10 +83532,10 @@ var init_external_open_origins = __esm(() => {
82859
83532
 
82860
83533
  // src/lib/view/session.ts
82861
83534
  import { closeSync, fchmodSync, openSync } from "node:fs";
82862
- import { chmod as chmod3, mkdir as mkdir9, readdir as readdir2, readFile as readFile12, rm as rm2 } from "node:fs/promises";
82863
- import { join as join11 } from "node:path";
83535
+ import { chmod as chmod3, mkdir as mkdir11, readdir as readdir4, readFile as readFile14, rm as rm4 } from "node:fs/promises";
83536
+ import { join as join13 } from "node:path";
82864
83537
  async function ensureViewDirectory(directory = VIEW_DIR) {
82865
- await mkdir9(directory, { recursive: true, mode: 448 });
83538
+ await mkdir11(directory, { recursive: true, mode: 448 });
82866
83539
  await chmod3(directory, 448);
82867
83540
  }
82868
83541
  async function saveRecord(record12, directory = VIEW_DIR) {
@@ -82888,9 +83561,9 @@ async function openSessionLog(id, directory = VIEW_DIR) {
82888
83561
  }
82889
83562
  async function removeSessionFiles(id, directory = VIEW_DIR) {
82890
83563
  await Promise.all([
82891
- rm2(recordPath(id, directory), { force: true }),
82892
- rm2(configPath(id, directory), { force: true }),
82893
- rm2(logPath(id, directory), { force: true })
83564
+ rm4(recordPath(id, directory), { force: true }),
83565
+ rm4(configPath(id, directory), { force: true }),
83566
+ rm4(logPath(id, directory), { force: true })
82894
83567
  ]);
82895
83568
  }
82896
83569
  function isAlive(pid) {
@@ -82904,7 +83577,7 @@ function isAlive(pid) {
82904
83577
  async function listSessions() {
82905
83578
  let entries;
82906
83579
  try {
82907
- entries = await readdir2(VIEW_DIR);
83580
+ entries = await readdir4(VIEW_DIR);
82908
83581
  } catch {
82909
83582
  return [];
82910
83583
  }
@@ -82914,7 +83587,7 @@ async function listSessions() {
82914
83587
  continue;
82915
83588
  let record12;
82916
83589
  try {
82917
- record12 = JSON.parse(await readFile12(join11(VIEW_DIR, entry2), "utf8"));
83590
+ record12 = JSON.parse(await readFile14(join13(VIEW_DIR, entry2), "utf8"));
82918
83591
  } catch {
82919
83592
  continue;
82920
83593
  }
@@ -82932,7 +83605,7 @@ async function closeSession(record12) {
82932
83605
  } catch {}
82933
83606
  const deadline = Date.now() + CLOSE_GRACE_MS;
82934
83607
  while (isAlive(record12.pid) && Date.now() < deadline) {
82935
- await new Promise((resolve6) => setTimeout(resolve6, 100));
83608
+ await new Promise((resolve7) => setTimeout(resolve7, 100));
82936
83609
  }
82937
83610
  if (isAlive(record12.pid)) {
82938
83611
  try {
@@ -82942,14 +83615,14 @@ async function closeSession(record12) {
82942
83615
  }
82943
83616
  await removeSessionFiles(record12.id);
82944
83617
  }
82945
- var VIEW_DIR, recordPath = (id, directory = VIEW_DIR) => join11(directory, `${id}.json`), logPath = (id, directory = VIEW_DIR) => join11(directory, `${id}.log`), configPath = (id, directory = VIEW_DIR) => join11(directory, `${id}.config.json`), CLOSE_GRACE_MS = 2000;
83618
+ var VIEW_DIR, recordPath = (id, directory = VIEW_DIR) => join13(directory, `${id}.json`), logPath = (id, directory = VIEW_DIR) => join13(directory, `${id}.log`), configPath = (id, directory = VIEW_DIR) => join13(directory, `${id}.config.json`), CLOSE_GRACE_MS = 2000;
82946
83619
  var init_session5 = __esm(() => {
82947
83620
  init_state();
82948
- VIEW_DIR = join11(paths2.home, "view");
83621
+ VIEW_DIR = join13(paths2.home, "view");
82949
83622
  });
82950
83623
 
82951
83624
  // src/lib/view/port-allocation.ts
82952
- import { join as join12 } from "node:path";
83625
+ import { join as join14 } from "node:path";
82953
83626
  function withViewPortAllocationLock(fn, lockPath = VIEW_PORT_LOCK) {
82954
83627
  return withFileLock(lockPath, fn);
82955
83628
  }
@@ -82957,7 +83630,7 @@ var VIEW_PORT_LOCK;
82957
83630
  var init_port_allocation = __esm(() => {
82958
83631
  init_state();
82959
83632
  init_session5();
82960
- VIEW_PORT_LOCK = join12(VIEW_DIR, "ports.lock");
83633
+ VIEW_PORT_LOCK = join14(VIEW_DIR, "ports.lock");
82961
83634
  });
82962
83635
 
82963
83636
  // src/lib/view/resolve.ts
@@ -83027,33 +83700,33 @@ var init_resolve2 = __esm(() => {
83027
83700
  // src/lib/view/assets.ts
83028
83701
  import { existsSync as existsSync5, statSync } from "node:fs";
83029
83702
  import { copyFile as copyFile2 } from "node:fs/promises";
83030
- import { dirname as dirname9, join as join13 } from "node:path";
83703
+ import { dirname as dirname11, join as join15 } from "node:path";
83031
83704
  import { fileURLToPath } from "node:url";
83032
83705
  function viewerDistDir(moduleUrl = import.meta.url, entry2 = process.argv[1] ?? ".", executable = process.execPath) {
83033
83706
  const override = process.env.ASTRALE_VIEWER_DIR;
83034
83707
  if (override)
83035
83708
  return override;
83036
- const moduleDirectory = dirname9(fileURLToPath(moduleUrl));
83037
- const published = join13(moduleDirectory, "..", "viewer", "dist");
83038
- const source2 = join13(moduleDirectory, "..", "..", "..", "viewer", "dist");
83039
- const legacy = join13(dirname9(entry2), "..", "viewer", "dist");
83040
- const standalone = entry2.startsWith("/$bunfs/") ? join13(dirname9(executable), "viewer", "dist") : undefined;
83709
+ const moduleDirectory = dirname11(fileURLToPath(moduleUrl));
83710
+ const published = join15(moduleDirectory, "..", "viewer", "dist");
83711
+ const source2 = join15(moduleDirectory, "..", "..", "..", "viewer", "dist");
83712
+ const legacy = join15(dirname11(entry2), "..", "viewer", "dist");
83713
+ const standalone = entry2.startsWith("/$bunfs/") ? join15(dirname11(executable), "viewer", "dist") : undefined;
83041
83714
  const complete = [standalone, published, source2, legacy].find((candidate2) => candidate2 !== undefined && hasViewerBundle(candidate2));
83042
83715
  if (complete)
83043
83716
  return complete;
83044
- if (hasViewerSource(join13(source2, "..")))
83717
+ if (hasViewerSource(join15(source2, "..")))
83045
83718
  return source2;
83046
83719
  return published;
83047
83720
  }
83048
83721
  async function ensureViewerAssets(moduleUrl = import.meta.url, entry2 = process.argv[1] ?? ".") {
83049
83722
  const dist = viewerDistDir(moduleUrl, entry2);
83050
- const srcDir = join13(dist, "..");
83723
+ const srcDir = join15(dist, "..");
83051
83724
  if (hasViewerBundle(dist) && !viewerSourceIsNewer(srcDir, dist))
83052
83725
  return dist;
83053
83726
  const bun = globalThis.Bun;
83054
83727
  if (bun && hasViewerSource(srcDir)) {
83055
83728
  const result = await bun.build({
83056
- entrypoints: [join13(srcDir, "main.ts")],
83729
+ entrypoints: [join15(srcDir, "main.ts")],
83057
83730
  outdir: dist,
83058
83731
  target: "browser",
83059
83732
  minify: false
@@ -83061,32 +83734,32 @@ async function ensureViewerAssets(moduleUrl = import.meta.url, entry2 = process.
83061
83734
  if (!result.success)
83062
83735
  throw new Error(`viewer build failed: ${result.logs.join(`
83063
83736
  `)}`);
83064
- await copyFile2(join13(srcDir, "index.html"), join13(dist, "index.html"));
83737
+ await copyFile2(join15(srcDir, "index.html"), join15(dist, "index.html"));
83065
83738
  return dist;
83066
83739
  }
83067
83740
  throw new Error(`viewer bundle missing at ${dist} — reinstall the CLI (or run \`bun scripts/build.ts\` in a dev checkout)`);
83068
83741
  }
83069
83742
  function hasViewerBundle(directory) {
83070
- return existsSync5(join13(directory, "main.js")) && existsSync5(join13(directory, "index.html"));
83743
+ return existsSync5(join15(directory, "main.js")) && existsSync5(join15(directory, "index.html"));
83071
83744
  }
83072
83745
  function hasViewerSource(directory) {
83073
- return existsSync5(join13(directory, "main.ts")) && existsSync5(join13(directory, "index.html"));
83746
+ return existsSync5(join15(directory, "main.ts")) && existsSync5(join15(directory, "index.html"));
83074
83747
  }
83075
83748
  function viewerSourceIsNewer(source2, dist) {
83076
83749
  if (!hasViewerSource(source2))
83077
83750
  return false;
83078
83751
  if (!hasViewerBundle(dist))
83079
83752
  return true;
83080
- const newestSource = Math.max(statSync(join13(source2, "main.ts")).mtimeMs, statSync(join13(source2, "index.html")).mtimeMs);
83081
- const oldestOutput = Math.min(statSync(join13(dist, "main.js")).mtimeMs, statSync(join13(dist, "index.html")).mtimeMs);
83753
+ const newestSource = Math.max(statSync(join15(source2, "main.ts")).mtimeMs, statSync(join15(source2, "index.html")).mtimeMs);
83754
+ const oldestOutput = Math.min(statSync(join15(dist, "main.js")).mtimeMs, statSync(join15(dist, "index.html")).mtimeMs);
83082
83755
  return newestSource > oldestOutput;
83083
83756
  }
83084
83757
  var init_assets = () => {};
83085
83758
 
83086
83759
  // src/lib/view/server.ts
83087
- import { readFile as readFile13 } from "node:fs/promises";
83760
+ import { readFile as readFile15 } from "node:fs/promises";
83088
83761
  import { createServer } from "node:http";
83089
- import { join as join14 } from "node:path";
83762
+ import { join as join16 } from "node:path";
83090
83763
  import { Readable } from "node:stream";
83091
83764
  function startViewServer(config2) {
83092
83765
  const { session: session3, proxy } = config2;
@@ -83134,11 +83807,11 @@ function startViewServer(config2) {
83134
83807
  return;
83135
83808
  }
83136
83809
  if (sub === "/" || sub === "/index.html") {
83137
- await serveAsset(res, join14(hostDir, "index.html"), "text/html; charset=utf-8");
83810
+ await serveAsset(res, join16(hostDir, "index.html"), "text/html; charset=utf-8");
83138
83811
  return;
83139
83812
  }
83140
83813
  if (sub === "/main.js") {
83141
- await serveAsset(res, join14(hostDir, "main.js"), "text/javascript; charset=utf-8");
83814
+ await serveAsset(res, join16(hostDir, "main.js"), "text/javascript; charset=utf-8");
83142
83815
  return;
83143
83816
  }
83144
83817
  if (sub === "/config.json" && req.method === "GET") {
@@ -83258,7 +83931,7 @@ function json3(res, code, body) {
83258
83931
  }
83259
83932
  async function serveAsset(res, file2, contentType) {
83260
83933
  try {
83261
- const content = await readFile13(file2);
83934
+ const content = await readFile15(file2);
83262
83935
  res.writeHead(200, { "content-type": contentType, "cache-control": "no-store" });
83263
83936
  res.end(content);
83264
83937
  } catch {
@@ -83350,7 +84023,7 @@ var SETTLE_TIMEOUT_MS = 8000, QUIET_WINDOW_MS = 750, POLL_MS = 250, realClock, d
83350
84023
  var init_snapshot = __esm(() => {
83351
84024
  realClock = {
83352
84025
  now: Date.now,
83353
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms))
84026
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
83354
84027
  };
83355
84028
  defaultPolicy = {
83356
84029
  timeoutMs: SETTLE_TIMEOUT_MS,
@@ -83374,8 +84047,8 @@ __export(exports_view3, {
83374
84047
  });
83375
84048
  import { randomBytes } from "node:crypto";
83376
84049
  import { closeSync as closeSync2, existsSync as existsSync6, statSync as statSync2 } from "node:fs";
83377
- import { readdir as readdir3, readFile as readFile14 } from "node:fs/promises";
83378
- import { dirname as dirname10, join as join15 } from "node:path";
84050
+ import { readdir as readdir5, readFile as readFile16 } from "node:fs/promises";
84051
+ import { dirname as dirname12, join as join17 } from "node:path";
83379
84052
  async function resolveSession(spec, opts) {
83380
84053
  rejectUnrepresentableOverrides(opts);
83381
84054
  const parsed = parseViewSpec(spec);
@@ -83430,7 +84103,7 @@ async function resolveServeRuntime(environment = {}) {
83430
84103
  if (node4 && entry2?.endsWith(".js") && exists(entry2))
83431
84104
  return { file: node4, args: [entry2] };
83432
84105
  if (node4 && entry2?.endsWith(".ts")) {
83433
- const dist = join15(dirname10(entry2), "..", "dist", "astrale.js");
84106
+ const dist = join17(dirname12(entry2), "..", "dist", "astrale.js");
83434
84107
  await ensureDevDist(entry2, dist);
83435
84108
  if (exists(dist))
83436
84109
  return { file: node4, args: [dist] };
@@ -83456,8 +84129,8 @@ async function findOnPath(name) {
83456
84129
  async function ensureDevDist(entry2, dist) {
83457
84130
  if (!await devDistIsStale(entry2, dist))
83458
84131
  return;
83459
- const projectDir = join15(dirname10(entry2), "..");
83460
- const buildScript = join15(projectDir, "scripts", "build.ts");
84132
+ const projectDir = join17(dirname12(entry2), "..");
84133
+ const buildScript = join17(projectDir, "scripts", "build.ts");
83461
84134
  const bun = await findOnPath("bun");
83462
84135
  if (!bun || !existsSync6(buildScript))
83463
84136
  return;
@@ -83473,13 +84146,13 @@ async function ensureDevDist(entry2, dist) {
83473
84146
  async function devDistIsStale(entry2, dist) {
83474
84147
  if (!existsSync6(dist))
83475
84148
  return true;
83476
- const projectDir = join15(dirname10(entry2), "..");
84149
+ const projectDir = join17(dirname12(entry2), "..");
83477
84150
  const builtAt = statSync2(dist).mtimeMs;
83478
- const directories = [join15(projectDir, "src"), join15(projectDir, "bin"), join15(projectDir, "vendor")];
84151
+ const directories = [join17(projectDir, "src"), join17(projectDir, "bin"), join17(projectDir, "vendor")];
83479
84152
  const files = [
83480
- join15(projectDir, "scripts", "build.ts"),
83481
- join15(projectDir, "package.json"),
83482
- join15(projectDir, "pnpm-lock.yaml")
84153
+ join17(projectDir, "scripts", "build.ts"),
84154
+ join17(projectDir, "package.json"),
84155
+ join17(projectDir, "pnpm-lock.yaml")
83483
84156
  ];
83484
84157
  for (const directory of directories) {
83485
84158
  if (existsSync6(directory) && await newerThan(directory, builtAt))
@@ -83488,11 +84161,11 @@ async function devDistIsStale(entry2, dist) {
83488
84161
  return files.some((file2) => existsSync6(file2) && statSync2(file2).mtimeMs > builtAt);
83489
84162
  }
83490
84163
  async function newerThan(dir, mtimeMs) {
83491
- const entries = await readdir3(dir, { withFileTypes: true, recursive: true });
84164
+ const entries = await readdir5(dir, { withFileTypes: true, recursive: true });
83492
84165
  for (const item of entries) {
83493
84166
  if (!item.isFile())
83494
84167
  continue;
83495
- if (statSync2(join15(item.parentPath, item.name)).mtimeMs > mtimeMs)
84168
+ if (statSync2(join17(item.parentPath, item.name)).mtimeMs > mtimeMs)
83496
84169
  return true;
83497
84170
  }
83498
84171
  return false;
@@ -83570,7 +84243,7 @@ async function startSessionLocked(view2, opts, kernelTarget, activeInstance, def
83570
84243
  break;
83571
84244
  await sleep2(POLL_MS2);
83572
84245
  }
83573
- const tail = await readFile14(logPath(id), "utf8").catch(() => "");
84246
+ const tail = await readFile16(logPath(id), "utf8").catch(() => "");
83574
84247
  await closeSession(live);
83575
84248
  throw new Error(`View session server did not come up.${tail ? `
83576
84249
  --- server log ---
@@ -83705,7 +84378,7 @@ async function sessionsCommand(opts) {
83705
84378
  console.log(`${source_default.bold(s.id)} /:${s.view.route.key} target ${s.view.target} ${source_default.dim(s.pageUrl)}`);
83706
84379
  }
83707
84380
  }
83708
- var VIEW_PORT_BASE = 4419, VIEW_PORT_SPAN = 20, IDLE_MS, READY_TIMEOUT_MS = 8000, STATE_TIMEOUT_MS = 25000, POLL_MS2 = 250, VIEW_PROFILE, sleep2 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)), view_default;
84381
+ var VIEW_PORT_BASE = 4419, VIEW_PORT_SPAN = 20, IDLE_MS, READY_TIMEOUT_MS = 8000, STATE_TIMEOUT_MS = 25000, POLL_MS2 = 250, VIEW_PROFILE, sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)), view_default;
83709
84382
  var init_view7 = __esm(() => {
83710
84383
  init_source();
83711
84384
  init_connection2();
@@ -83844,7 +84517,7 @@ Examples:
83844
84517
  if (state2?.state === "failed") {
83845
84518
  await reportOpened(record12, state2, mode, opts);
83846
84519
  if (opts.debug) {
83847
- const tail = await readFile14(logPath(record12.id), "utf8").catch(() => "");
84520
+ const tail = await readFile16(logPath(record12.id), "utf8").catch(() => "");
83848
84521
  if (tail)
83849
84522
  console.error(`--- server log ---
83850
84523
  ${tail.slice(-3000)}`);
@@ -83966,7 +84639,7 @@ function reportConnected(session3, machine, opts) {
83966
84639
  console.log(` agent-browser ${drive} open ${session3.url}`);
83967
84640
  console.log(` agent-browser ${drive} click @e3`);
83968
84641
  }
83969
- var LOGIN_TIMEOUT_MS = 180000, POLL_INTERVAL_MS = 2500, sleep3 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)), browser_default;
84642
+ var LOGIN_TIMEOUT_MS = 180000, POLL_INTERVAL_MS = 2500, sleep3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)), browser_default;
83970
84643
  var init_browser2 = __esm(() => {
83971
84644
  init_source();
83972
84645
  init_browser();
@@ -84083,7 +84756,7 @@ var exports_view_serve = {};
84083
84756
  __export(exports_view_serve, {
84084
84757
  default: () => view_serve_default
84085
84758
  });
84086
- import { readFile as readFile15 } from "node:fs/promises";
84759
+ import { readFile as readFile17 } from "node:fs/promises";
84087
84760
  var view_serve_default;
84088
84761
  var init_view_serve = __esm(() => {
84089
84762
  init_server();
@@ -84095,7 +84768,7 @@ var init_view_serve = __esm(() => {
84095
84768
  action: async (opts) => {
84096
84769
  if (!opts.config)
84097
84770
  throw new Error("--config is required");
84098
- const config2 = JSON.parse(await readFile15(opts.config, "utf8"));
84771
+ const config2 = JSON.parse(await readFile17(opts.config, "utf8"));
84099
84772
  startViewServer(config2);
84100
84773
  console.log(`view session ${config2.session.id} listening on ${config2.session.pageUrl}`);
84101
84774
  await new Promise(() => {});
@@ -84110,7 +84783,7 @@ __export(exports_studio, {
84110
84783
  encodeStudioCliDescriptor: () => encodeStudioCliDescriptor
84111
84784
  });
84112
84785
  import { existsSync as existsSync8, realpathSync } from "node:fs";
84113
- import { dirname as dirname11, join as join16, resolve as resolve6 } from "node:path";
84786
+ import { dirname as dirname13, join as join18, resolve as resolve7 } from "node:path";
84114
84787
  function encodeStudioCliDescriptor(executable = process.execPath, entry2 = process.argv[1]) {
84115
84788
  const args = entry2 && entry2 !== executable && !entry2.startsWith("/$bunfs") && existsSync8(entry2) ? [realpathSync(entry2)] : [];
84116
84789
  return JSON.stringify({ version: 1, executable, args });
@@ -84120,22 +84793,22 @@ function resolveStudioDir() {
84120
84793
  if (process.env.ASTRALE_STUDIO_DIR)
84121
84794
  candidates.push(process.env.ASTRALE_STUDIO_DIR);
84122
84795
  try {
84123
- const entryDir = dirname11(realpathSync(process.argv[1] ?? ""));
84124
- candidates.push(join16(entryDir, "..", "studio"), join16(entryDir, "studio"));
84796
+ const entryDir = dirname13(realpathSync(process.argv[1] ?? ""));
84797
+ candidates.push(join18(entryDir, "..", "studio"), join18(entryDir, "studio"));
84125
84798
  } catch {}
84126
84799
  for (const c of candidates) {
84127
- if (existsSync8(join16(c, "server", "index.ts")))
84128
- return resolve6(c);
84800
+ if (existsSync8(join18(c, "server", "index.ts")))
84801
+ return resolve7(c);
84129
84802
  }
84130
84803
  throw new Error(`Domain Studio assets not found (looked in: ${candidates.join(", ") || "<none>"}). ` + `Reinstall the astrale CLI, or set ASTRALE_STUDIO_DIR to a studio checkout.`);
84131
84804
  }
84132
84805
  function isDevSource(studioDir) {
84133
- return existsSync8(join16(studioDir, "vite.config.ts")) && existsSync8(join16(studioDir, "client", "src"));
84806
+ return existsSync8(join18(studioDir, "vite.config.ts")) && existsSync8(join18(studioDir, "client", "src"));
84134
84807
  }
84135
84808
  function resolveViteBin(studioDir) {
84136
84809
  for (const c of [
84137
- join16(studioDir, "node_modules", ".bin", "vite"),
84138
- join16(studioDir, "..", "..", "node_modules", ".bin", "vite")
84810
+ join18(studioDir, "node_modules", ".bin", "vite"),
84811
+ join18(studioDir, "..", "..", "node_modules", ".bin", "vite")
84139
84812
  ]) {
84140
84813
  if (existsSync8(c))
84141
84814
  return c;
@@ -84256,11 +84929,11 @@ Examples:
84256
84929
  `,
84257
84930
  action: async (pathArg, opts) => {
84258
84931
  try {
84259
- const workspace = resolve6(pathArg ?? process.cwd());
84932
+ const workspace = resolve7(pathArg ?? process.cwd());
84260
84933
  if (!existsSync8(workspace))
84261
84934
  throw new Error(`path not found: ${workspace}`);
84262
84935
  const studioDir = resolveStudioDir();
84263
- if (process.env.ASTRALE_STUDIO_DIR && studioDir === resolve6(process.env.ASTRALE_STUDIO_DIR)) {
84936
+ if (process.env.ASTRALE_STUDIO_DIR && studioDir === resolve7(process.env.ASTRALE_STUDIO_DIR)) {
84264
84937
  log.dim(` using ASTRALE_STUDIO_DIR=${studioDir}`);
84265
84938
  }
84266
84939
  await ensureBun();
@@ -84360,8 +85033,8 @@ Examples:
84360
85033
  }
84361
85034
  }), "server");
84362
85035
  } else {
84363
- const dist = join16(studioDir, "client", "dist");
84364
- if (!existsSync8(join16(dist, "index.html"))) {
85036
+ const dist = join18(studioDir, "client", "dist");
85037
+ if (!existsSync8(join18(dist, "index.html"))) {
84365
85038
  throw new Error(`studio client not built at ${dist} — run: pnpm --filter @astrale-os/studio build`);
84366
85039
  }
84367
85040
  serverChild = supervise(spawnHandle("bun", ["server/index.ts", workspace, "--port", String(studioPort), "--no-open"], {
@@ -84414,10 +85087,10 @@ var init_model7 = __esm(() => {
84414
85087
  });
84415
85088
 
84416
85089
  // src/ui/lock.ts
84417
- import { createHash as createHash2 } from "node:crypto";
84418
- import { readFile as readFile16 } from "node:fs/promises";
85090
+ import { createHash as createHash3 } from "node:crypto";
85091
+ import { readFile as readFile18 } from "node:fs/promises";
84419
85092
  function digest5(value3) {
84420
- return createHash2("sha256").update(value3).digest("hex");
85093
+ return createHash3("sha256").update(value3).digest("hex");
84421
85094
  }
84422
85095
  function parseUiLock(value3) {
84423
85096
  const lock = value3;
@@ -84453,14 +85126,14 @@ function pathIsAbsolute(value3) {
84453
85126
  }
84454
85127
  async function readUiLock(target2) {
84455
85128
  try {
84456
- return parseUiLock(JSON.parse(await readFile16(target2, "utf8")));
85129
+ return parseUiLock(JSON.parse(await readFile18(target2, "utf8")));
84457
85130
  } catch (cause) {
84458
85131
  if (cause instanceof UiError)
84459
85132
  throw cause;
84460
85133
  throw new UiError("UI_CONFIG_MISSING", "Unable to read " + UI_LOCK_FILE + ".", "Run astrale ui init.", { cause });
84461
85134
  }
84462
85135
  }
84463
- var init_lock = __esm(() => {
85136
+ var init_lock2 = __esm(() => {
84464
85137
  init_model7();
84465
85138
  });
84466
85139
 
@@ -86484,14 +87157,14 @@ var require_lib5 = __commonJS(function(exports) {
86484
87157
  });
86485
87158
 
86486
87159
  // src/ui/project.ts
86487
- import { access as access2, lstat, readFile as readFile17, realpath as realpath2 } from "node:fs/promises";
87160
+ import { access as access2, lstat as lstat3, readFile as readFile19, realpath as realpath2 } from "node:fs/promises";
86488
87161
  import path5 from "node:path";
86489
87162
  async function exists(target2) {
86490
87163
  return access2(target2).then(() => true, () => false);
86491
87164
  }
86492
87165
  async function readManifest(target2) {
86493
87166
  try {
86494
- return JSON.parse(await readFile17(target2, "utf8"));
87167
+ return JSON.parse(await readFile19(target2, "utf8"));
86495
87168
  } catch (cause) {
86496
87169
  throw new UiError("UI_PROJECT_UNSUPPORTED", "package.json is not valid JSON.", undefined, {
86497
87170
  cause
@@ -86561,7 +87234,7 @@ async function resolveAlias(project2, candidate2) {
86561
87234
  async function resolveUiRegistryTarget(project2, declaredTarget) {
86562
87235
  if (!declaredTarget.startsWith("components/"))
86563
87236
  return declaredTarget;
86564
- const components = await readFile17(project2.componentsPath, "utf8").then((value3) => JSON.parse(value3)).catch(() => {
87237
+ const components = await readFile19(project2.componentsPath, "utf8").then((value3) => JSON.parse(value3)).catch(() => {
86565
87238
  return;
86566
87239
  });
86567
87240
  const componentsAlias = components?.aliases?.components;
@@ -86573,11 +87246,11 @@ async function resolveUiRegistryTarget(project2, declaredTarget) {
86573
87246
  if (!resolved) {
86574
87247
  throw new UiError("UI_PROJECT_UNSUPPORTED", "components.json alias cannot be resolved through tsconfig.json or jsconfig.json.", "Define a matching compilerOptions.paths entry for " + componentsAlias + ".");
86575
87248
  }
86576
- const relative = path5.relative(project2.root, path5.join(resolved, suffix));
86577
- if (relative === ".." || relative.startsWith(".." + path5.sep) || path5.isAbsolute(relative)) {
87249
+ const relative2 = path5.relative(project2.root, path5.join(resolved, suffix));
87250
+ if (relative2 === ".." || relative2.startsWith(".." + path5.sep) || path5.isAbsolute(relative2)) {
86578
87251
  throw new UiError("UI_PROJECT_UNSUPPORTED", "components.json alias escapes the project.");
86579
87252
  }
86580
- return relative.split(path5.sep).join("/");
87253
+ return relative2.split(path5.sep).join("/");
86581
87254
  }
86582
87255
  function hasReactTailwind(manifest) {
86583
87256
  const dependencies = {
@@ -86597,8 +87270,8 @@ async function assertPhysicalProjectPath(root, target2) {
86597
87270
  existing = parent;
86598
87271
  }
86599
87272
  const physicalTarget = await realpath2(existing);
86600
- const relative = path5.relative(physicalRoot, physicalTarget);
86601
- if (relative === ".." || relative.startsWith(".." + path5.sep) || path5.isAbsolute(relative)) {
87273
+ const relative2 = path5.relative(physicalRoot, physicalTarget);
87274
+ if (relative2 === ".." || relative2.startsWith(".." + path5.sep) || path5.isAbsolute(relative2)) {
86602
87275
  throw new UiError("UI_PROJECT_UNSUPPORTED", "components.json CSS path escapes the project.");
86603
87276
  }
86604
87277
  }
@@ -86607,7 +87280,7 @@ async function discoverUiProject(input = process.cwd()) {
86607
87280
  if (!await exists(root)) {
86608
87281
  throw new UiError("UI_PROJECT_UNSUPPORTED", "Project path does not exist: " + root);
86609
87282
  }
86610
- if (!(await lstat(root)).isDirectory())
87283
+ if (!(await lstat3(root)).isDirectory())
86611
87284
  root = path5.dirname(root);
86612
87285
  while (true) {
86613
87286
  const manifestPath = path5.join(root, "package.json");
@@ -86663,17 +87336,17 @@ async function discoverUiProject(input = process.cwd()) {
86663
87336
  "frontend/src/styles.css"
86664
87337
  ];
86665
87338
  const componentsPath = path5.join(root, "components.json");
86666
- const configuredCss = await readFile17(componentsPath, "utf8").then((value3) => {
87339
+ const configuredCss = await readFile19(componentsPath, "utf8").then((value3) => {
86667
87340
  const components = JSON.parse(value3);
86668
87341
  const css = components.tailwind?.css;
86669
87342
  if (typeof css !== "string" || css.length === 0)
86670
87343
  return;
86671
87344
  const target2 = path5.resolve(root, css);
86672
- const relative = path5.relative(root, target2);
86673
- if (relative === ".." || relative.startsWith(".." + path5.sep) || path5.isAbsolute(relative)) {
87345
+ const relative2 = path5.relative(root, target2);
87346
+ if (relative2 === ".." || relative2.startsWith(".." + path5.sep) || path5.isAbsolute(relative2)) {
86674
87347
  throw new UiError("UI_PROJECT_UNSUPPORTED", "components.json CSS path escapes the project.");
86675
87348
  }
86676
- return { relative: relative.split(path5.sep).join("/"), target: target2 };
87349
+ return { relative: relative2.split(path5.sep).join("/"), target: target2 };
86677
87350
  }).catch((error52) => {
86678
87351
  if (error52 instanceof UiError)
86679
87352
  throw error52;
@@ -86712,11 +87385,11 @@ function assertSupportedUiProject(project2) {
86712
87385
  }
86713
87386
  }
86714
87387
  function projectRelative(project2, target2) {
86715
- const relative = path5.relative(project2.root, target2);
86716
- if (relative === "" || relative === ".." || relative.startsWith(".." + path5.sep) || path5.isAbsolute(relative)) {
87388
+ const relative2 = path5.relative(project2.root, target2);
87389
+ if (relative2 === "" || relative2 === ".." || relative2.startsWith(".." + path5.sep) || path5.isAbsolute(relative2)) {
86717
87390
  throw new UiError("UI_LOCK_INVALID", "Path escapes the project root: " + target2);
86718
87391
  }
86719
- return relative.split(path5.sep).join("/");
87392
+ return relative2.split(path5.sep).join("/");
86720
87393
  }
86721
87394
  var import_tsconfig_paths, MANAGERS;
86722
87395
  var init_project3 = __esm(() => {
@@ -86971,13 +87644,13 @@ var init_runner = __esm(() => {
86971
87644
  });
86972
87645
 
86973
87646
  // src/ui/operations.ts
86974
- import { access as access3, lstat as lstat2, mkdir as mkdir10, readFile as readFile18, realpath as realpath3, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
87647
+ import { access as access3, lstat as lstat4, mkdir as mkdir12, readFile as readFile20, realpath as realpath3, rm as rm5, writeFile as writeFile8 } from "node:fs/promises";
86975
87648
  import path6 from "node:path";
86976
87649
  async function exists2(target2) {
86977
87650
  return access3(target2).then(() => true, () => false);
86978
87651
  }
86979
87652
  async function readOptional(target2) {
86980
- return readFile18(target2, "utf8").catch(() => {
87653
+ return readFile20(target2, "utf8").catch(() => {
86981
87654
  return;
86982
87655
  });
86983
87656
  }
@@ -86993,10 +87666,10 @@ function admitLocalThemeCss(source2, slug) {
86993
87666
  }
86994
87667
  }
86995
87668
  function themeImport(project2, target2) {
86996
- let relative = path6.relative(path6.dirname(project2.cssPath), target2).split(path6.sep).join("/");
86997
- if (!relative.startsWith("."))
86998
- relative = "./" + relative;
86999
- return "@import '" + relative + "';";
87669
+ let relative2 = path6.relative(path6.dirname(project2.cssPath), target2).split(path6.sep).join("/");
87670
+ if (!relative2.startsWith("."))
87671
+ relative2 = "./" + relative2;
87672
+ return "@import '" + relative2 + "';";
87000
87673
  }
87001
87674
  function activateTheme(current, statement) {
87002
87675
  const lines = current.split(/\r?\n/u).filter((line) => !/^@import\s+['"][^'"]*components\/astrale\/theme\/[a-z][a-z0-9-]*\.css['"];?\s*$/u.test(line));
@@ -87067,7 +87740,7 @@ async function appendPnpmWorkspace(project2, workspace) {
87067
87740
  const target2 = pnpmWorkspacePath(project2);
87068
87741
  const source2 = await readOptional(target2);
87069
87742
  if (source2 === undefined) {
87070
- await writeFile6(target2, `packages:
87743
+ await writeFile8(target2, `packages:
87071
87744
  - '` + workspace + `'
87072
87745
  `, "utf8");
87073
87746
  return;
@@ -87086,12 +87759,12 @@ async function appendPnpmWorkspace(project2, workspace) {
87086
87759
  }
87087
87760
  if (!values.includes(workspace))
87088
87761
  packages.add(workspace);
87089
- await writeFile6(target2, String(document), "utf8");
87762
+ await writeFile8(target2, String(document), "utf8");
87090
87763
  }
87091
87764
  async function hasDomainRegistryWorkspace(project2) {
87092
87765
  if (!project2.isAstraleDomain || !await exists2(domainRegistryPackagePath(project2)))
87093
87766
  return false;
87094
- const registryManifest = JSON.parse(await readFile18(domainRegistryPackagePath(project2), "utf8"));
87767
+ const registryManifest = JSON.parse(await readFile20(domainRegistryPackagePath(project2), "utf8"));
87095
87768
  if (registryManifest.private !== true || typeof registryManifest.name !== "string" || registryManifest.name === UI_PACKAGE || registryManifest.name === project2.packageJson.name) {
87096
87769
  return false;
87097
87770
  }
@@ -87211,8 +87884,8 @@ async function initUi(options, dependencies = {}) {
87211
87884
  "@import '" + UI_PACKAGE + "/presets/" + preset + ".css';"
87212
87885
  ];
87213
87886
  const withoutAstrale = currentCss.replace(/^@import\s+['"]@astrale-os\/ui\/(?:theme\.css|presets\/[a-z-]+\.css)['"];?\s*$/gmu, "").trimStart();
87214
- await mkdir10(path6.dirname(project2.cssPath), { recursive: true });
87215
- await writeFile6(project2.cssPath, imports.join(`
87887
+ await mkdir12(path6.dirname(project2.cssPath), { recursive: true });
87888
+ await writeFile8(project2.cssPath, imports.join(`
87216
87889
  `) + `
87217
87890
 
87218
87891
  ` + withoutAstrale, "utf8");
@@ -87269,15 +87942,15 @@ async function initUi(options, dependencies = {}) {
87269
87942
  } catch (error52) {
87270
87943
  for (const [target2, value3] of snapshots) {
87271
87944
  if (value3 !== undefined)
87272
- await writeFile6(target2, value3, "utf8");
87945
+ await writeFile8(target2, value3, "utf8");
87273
87946
  else
87274
- await rm3(target2, { force: true });
87947
+ await rm5(target2, { force: true });
87275
87948
  }
87276
87949
  throw error52;
87277
87950
  }
87278
87951
  }
87279
87952
  async function pinUiDependency(project2, version2, section, runner) {
87280
- const manifest = JSON.parse(await readFile18(project2.packageJsonPath, "utf8"));
87953
+ const manifest = JSON.parse(await readFile20(project2.packageJsonPath, "utf8"));
87281
87954
  const current = manifestDependencies(manifest, section)[UI_PACKAGE];
87282
87955
  const other = section === "dependencies" ? "devDependencies" : "dependencies";
87283
87956
  if (current === version2 && manifestDependencies(manifest, other)[UI_PACKAGE] === undefined)
@@ -87316,7 +87989,7 @@ async function lockedRelease(project2, fetcher) {
87316
87989
  }
87317
87990
  async function addLocalTheme(address, project2, options) {
87318
87991
  const sourcePath = path6.resolve(project2.root, address);
87319
- const sourceInfo = await lstat2(sourcePath).catch(() => {
87992
+ const sourceInfo = await lstat4(sourcePath).catch(() => {
87320
87993
  return;
87321
87994
  });
87322
87995
  if (!sourceInfo?.isFile() || sourceInfo.isSymbolicLink()) {
@@ -87326,7 +87999,7 @@ async function addLocalTheme(address, project2, options) {
87326
87999
  if (!THEME_SLUG.test(slug)) {
87327
88000
  throw new UiError("UI_ITEM_CONFLICT", "Local theme filename must be a kebab-case theme name.", "Rename it to a name such as observatory.css.");
87328
88001
  }
87329
- const source2 = await readFile18(sourcePath, "utf8");
88002
+ const source2 = await readFile20(sourcePath, "utf8");
87330
88003
  admitLocalThemeCss(source2, slug);
87331
88004
  return installThemeCss(slug, source2, digest5(source2), address, project2, options);
87332
88005
  }
@@ -87361,10 +88034,10 @@ async function installThemeCss(slug, source2, sourceDigest, sourceLabel, project
87361
88034
  snapshots.set(mutation, await readOptional(mutation));
87362
88035
  }
87363
88036
  try {
87364
- await mkdir10(path6.dirname(target2), { recursive: true });
87365
- await writeFile6(target2, source2, "utf8");
88037
+ await mkdir12(path6.dirname(target2), { recursive: true });
88038
+ await writeFile8(target2, source2, "utf8");
87366
88039
  const css = await readOptional(project2.cssPath) ?? "";
87367
- await writeFile6(project2.cssPath, activateTheme(css, statement), "utf8");
88040
+ await writeFile8(project2.cssPath, activateTheme(css, statement), "utf8");
87368
88041
  lock.items[canonicalAddress] = {
87369
88042
  address: canonicalAddress,
87370
88043
  sourceDigest,
@@ -87375,9 +88048,9 @@ async function installThemeCss(slug, source2, sourceDigest, sourceLabel, project
87375
88048
  } catch (error52) {
87376
88049
  for (const [mutation, previous] of snapshots) {
87377
88050
  if (previous === undefined)
87378
- await rm3(mutation, { force: true });
88051
+ await rm5(mutation, { force: true });
87379
88052
  else
87380
- await writeFile6(mutation, previous, "utf8");
88053
+ await writeFile8(mutation, previous, "utf8");
87381
88054
  }
87382
88055
  throw error52;
87383
88056
  }
@@ -87464,15 +88137,15 @@ async function addUi(addresses, options, dependencies = {}) {
87464
88137
  if (themeTarget) {
87465
88138
  const target2 = await safeTarget(project2, themeTarget);
87466
88139
  const css = await readOptional(project2.cssPath) ?? "";
87467
- await writeFile6(project2.cssPath, activateTheme(css, themeImport(project2, target2)), "utf8");
88140
+ await writeFile8(project2.cssPath, activateTheme(css, themeImport(project2, target2)), "utf8");
87468
88141
  }
87469
88142
  }
87470
88143
  } catch (error52) {
87471
88144
  for (const [target2, previous] of snapshots) {
87472
88145
  if (previous === undefined)
87473
- await rm3(target2, { force: true });
88146
+ await rm5(target2, { force: true });
87474
88147
  else
87475
- await writeFile6(target2, previous, "utf8");
88148
+ await writeFile8(target2, previous, "utf8");
87476
88149
  }
87477
88150
  throw error52;
87478
88151
  }
@@ -87501,7 +88174,7 @@ async function addUi(addresses, options, dependencies = {}) {
87501
88174
  if (!file2.target)
87502
88175
  continue;
87503
88176
  const target2 = await safeTarget(project2, resolvedTargets.get(file2.target));
87504
- files[projectRelative(project2, target2)] = digest5(await readFile18(target2));
88177
+ files[projectRelative(project2, target2)] = digest5(await readFile20(target2));
87505
88178
  }
87506
88179
  lock.items[item.meta.canonicalAddress] = {
87507
88180
  address: item.meta.canonicalAddress,
@@ -87513,9 +88186,9 @@ async function addUi(addresses, options, dependencies = {}) {
87513
88186
  } catch (error52) {
87514
88187
  for (const [target2, previous] of snapshots) {
87515
88188
  if (previous === undefined)
87516
- await rm3(target2, { force: true });
88189
+ await rm5(target2, { force: true });
87517
88190
  else
87518
- await writeFile6(target2, previous, "utf8");
88191
+ await writeFile8(target2, previous, "utf8");
87519
88192
  }
87520
88193
  throw error52;
87521
88194
  }
@@ -87533,7 +88206,7 @@ async function doctorUi(input) {
87533
88206
  const checks3 = [];
87534
88207
  let lock;
87535
88208
  try {
87536
- lock = parseUiLock(JSON.parse(await readFile18(project2.uiLockPath, "utf8")));
88209
+ lock = parseUiLock(JSON.parse(await readFile20(project2.uiLockPath, "utf8")));
87537
88210
  checks3.push({ check: "lock", ok: true });
87538
88211
  } catch (error52) {
87539
88212
  checks3.push({
@@ -87561,7 +88234,7 @@ async function doctorUi(input) {
87561
88234
  if (lock) {
87562
88235
  for (const item of Object.values(lock.items)) {
87563
88236
  for (const [file2, expected] of Object.entries(item.files)) {
87564
- const actual = await readFile18(path6.join(project2.root, file2)).then(digest5).catch(() => "");
88237
+ const actual = await readFile20(path6.join(project2.root, file2)).then(digest5).catch(() => "");
87565
88238
  checks3.push({ check: "item:" + item.address + ":" + file2, ok: actual === expected });
87566
88239
  }
87567
88240
  }
@@ -87586,7 +88259,7 @@ async function applyPreset(preset, options) {
87586
88259
  throw new UiError("UI_CONFIG_MISSING", "No Astrale preset import was found.");
87587
88260
  }
87588
88261
  if (!options.dryRun) {
87589
- await writeFile6(project2.cssPath, next, "utf8");
88262
+ await writeFile8(project2.cssPath, next, "utf8");
87590
88263
  lock.preset = preset;
87591
88264
  await writeJson(project2.uiLockPath, lock);
87592
88265
  }
@@ -87614,34 +88287,34 @@ async function rejectLocalChanges(project2, lock, items, overwrite) {
87614
88287
  if (!installed)
87615
88288
  continue;
87616
88289
  for (const [file2, expected] of Object.entries(installed.files)) {
87617
- const actual = await readFile18(path6.join(project2.root, file2)).then(digest5).catch(() => "");
88290
+ const actual = await readFile20(path6.join(project2.root, file2)).then(digest5).catch(() => "");
87618
88291
  if (actual !== expected) {
87619
88292
  throw new UiError("UI_LOCAL_CHANGES", "Installed UI file has local changes: " + file2, "Review the file, then repeat add with explicit --overwrite --yes.");
87620
88293
  }
87621
88294
  }
87622
88295
  }
87623
88296
  }
87624
- async function safeTarget(project2, relative) {
87625
- if (path6.isAbsolute(relative) || relative.split(/[\\/]/u).includes("..")) {
87626
- throw new UiError("UI_LOCK_INVALID", "Unsafe registry target: " + relative);
88297
+ async function safeTarget(project2, relative2) {
88298
+ if (path6.isAbsolute(relative2) || relative2.split(/[\\/]/u).includes("..")) {
88299
+ throw new UiError("UI_LOCK_INVALID", "Unsafe registry target: " + relative2);
87627
88300
  }
87628
- const target2 = path6.resolve(project2.root, relative);
88301
+ const target2 = path6.resolve(project2.root, relative2);
87629
88302
  projectRelative(project2, target2);
87630
88303
  const parent = await realpath3(path6.dirname(target2));
87631
88304
  const root = await realpath3(project2.root);
87632
88305
  if (parent !== root && !parent.startsWith(root + path6.sep)) {
87633
- throw new UiError("UI_LOCK_INVALID", "Registry target escapes through a symlink: " + relative);
88306
+ throw new UiError("UI_LOCK_INVALID", "Registry target escapes through a symlink: " + relative2);
87634
88307
  }
87635
- if (await exists2(target2) && (await lstat2(target2)).isSymbolicLink()) {
87636
- throw new UiError("UI_LOCK_INVALID", "Registry target is a symlink: " + relative);
88308
+ if (await exists2(target2) && (await lstat4(target2)).isSymbolicLink()) {
88309
+ throw new UiError("UI_LOCK_INVALID", "Registry target is a symlink: " + relative2);
87637
88310
  }
87638
88311
  return target2;
87639
88312
  }
87640
- async function assertSafePlannedTarget(project2, relative) {
87641
- if (path6.isAbsolute(relative) || relative.split(/[\\/]/u).includes("..")) {
87642
- throw new UiError("UI_LOCK_INVALID", "Unsafe registry target: " + relative);
88313
+ async function assertSafePlannedTarget(project2, relative2) {
88314
+ if (path6.isAbsolute(relative2) || relative2.split(/[\\/]/u).includes("..")) {
88315
+ throw new UiError("UI_LOCK_INVALID", "Unsafe registry target: " + relative2);
87643
88316
  }
87644
- const target2 = path6.resolve(project2.root, relative);
88317
+ const target2 = path6.resolve(project2.root, relative2);
87645
88318
  projectRelative(project2, target2);
87646
88319
  const root = await realpath3(project2.root);
87647
88320
  const physicalTarget = path6.resolve(root, path6.relative(project2.root, target2));
@@ -87650,21 +88323,21 @@ async function assertSafePlannedTarget(project2, relative) {
87650
88323
  current = path6.join(current, segment);
87651
88324
  if (!await exists2(current))
87652
88325
  break;
87653
- if ((await lstat2(current)).isSymbolicLink()) {
87654
- throw new UiError("UI_LOCK_INVALID", "Registry target traverses a symlink: " + relative);
88326
+ if ((await lstat4(current)).isSymbolicLink()) {
88327
+ throw new UiError("UI_LOCK_INVALID", "Registry target traverses a symlink: " + relative2);
87655
88328
  }
87656
88329
  }
87657
88330
  return target2;
87658
88331
  }
87659
88332
  async function writeJson(target2, value3) {
87660
- await mkdir10(path6.dirname(target2), { recursive: true });
87661
- await writeFile6(target2, JSON.stringify(value3, null, 2) + `
88333
+ await mkdir12(path6.dirname(target2), { recursive: true });
88334
+ await writeFile8(target2, JSON.stringify(value3, null, 2) + `
87662
88335
  `, "utf8");
87663
88336
  }
87664
88337
  var LOCAL_THEME_MAX_BYTES = 131072, THEME_SLUG, REQUIRED_THEME_TOKENS;
87665
88338
  var init_operations3 = __esm(() => {
87666
88339
  init_dist();
87667
- init_lock();
88340
+ init_lock2();
87668
88341
  init_model7();
87669
88342
  init_project3();
87670
88343
  init_release();
@@ -89350,7 +90023,7 @@ async function installDirect(target2, opts, dependencies = {}) {
89350
90023
  }
89351
90024
  host = validateInstallUrl(target2);
89352
90025
  operation = opts.operation === undefined ? direct.createOperationId() : direct.acceptOperationId(opts.operation);
89353
- consentedOrigin = await ensureIdentityOverrideConsent(target2, host, opts.allowIdentityOverride ?? false);
90026
+ consentedOrigin = await ensureIdentityOverrideConsent(target2, host, opts.allowIdentityOverride ?? false, isMachine(opts));
89354
90027
  } catch (e) {
89355
90028
  fatal(e, opts);
89356
90029
  }
@@ -89370,7 +90043,9 @@ async function installDirect(target2, opts, dependencies = {}) {
89370
90043
  log.success(`Domain ${installed.status}: ${installed.origin}@${installed.revision}`);
89371
90044
  log.dim(` operation: ${installed.operation}`);
89372
90045
  if (isIdentityOverride(installed.origin, host) && installed.origin !== consentedOrigin) {
89373
- log.warn(`Installed origin "${installed.origin}" differs from the serving host "${host}" ` + `and was not confirmed before install (the worker Publication was unavailable). ` + `Every ${installed.origin}/* call on this instance now routes to ${host}.`);
90046
+ if (!isMachine(fmtOpts)) {
90047
+ log.warn(`Installed origin "${installed.origin}" differs from the serving host "${host}" ` + `and was not confirmed before install (the worker Publication was unavailable). ` + `Every ${installed.origin}/* call on this instance now routes to ${host}.`);
90048
+ }
89374
90049
  }
89375
90050
  }
89376
90051
  });
@@ -89394,16 +90069,20 @@ function validateInstallUrl(value3) {
89394
90069
  function isIdentityOverride(origin2, host) {
89395
90070
  return origin2.toLowerCase() !== host.toLowerCase();
89396
90071
  }
89397
- async function ensureIdentityOverrideConsent(url3, host, allow) {
90072
+ async function ensureIdentityOverrideConsent(url3, host, allow, machine) {
89398
90073
  const origin2 = await probeDeclaredOrigin(url3);
89399
90074
  if (origin2 === undefined) {
89400
- log.warn(`Could not read a declared origin from ` + `${new URL("/.well-known/astrale/domain.json", url3).href} — ` + `skipping the pre-install identity check (the installed origin is verified after install).`);
90075
+ if (!machine) {
90076
+ log.warn(`Could not read a declared origin from ` + `${new URL("/.well-known/astrale/domain.json", url3).href} — ` + `skipping the pre-install identity check (the installed origin is verified after install).`);
90077
+ }
89401
90078
  return;
89402
90079
  }
89403
90080
  if (!isIdentityOverride(origin2, host))
89404
90081
  return;
89405
90082
  if (allow) {
89406
- log.warn(`Identity override consented via --allow-identity-override: ${origin2} ← ${host}`);
90083
+ if (!machine) {
90084
+ log.warn(`Identity override consented via --allow-identity-override: ${origin2} ← ${host}`);
90085
+ }
89407
90086
  return origin2;
89408
90087
  }
89409
90088
  const banner = source_default.red.bold("⚠ DANGER — IDENTITY OVERRIDE") + `
@@ -89813,9 +90492,9 @@ __export(exports_register, {
89813
90492
  formatIdentityRegistration: () => formatIdentityRegistration,
89814
90493
  prepareIdentityProvision: () => prepareIdentityProvision
89815
90494
  });
89816
- import { readFile as readFile19 } from "node:fs/promises";
90495
+ import { readFile as readFile21 } from "node:fs/promises";
89817
90496
  async function readJwk(path8) {
89818
- return JSON.parse(await readFile19(path8, "utf8"));
90497
+ return JSON.parse(await readFile21(path8, "utf8"));
89819
90498
  }
89820
90499
  function formatIdentityRegistration(result, format3, machine) {
89821
90500
  output(result, format3);
@@ -89882,10 +90561,9 @@ Behavior:
89882
90561
  its result and stores the same target-bound registration. The callable owns
89883
90562
  authorization; the CLI never receives installed Domain authority.
89884
90563
 
89885
- Kernel V2 Nodes have opaque identity and no caller-assigned storage path, so
89886
- the historical --path option no longer exists. The CLI binds the key proof
89887
- to the exact provision fingerprint and target Kernel audience, then caches
89888
- the returned (issuer, subject) for subsequent calls.
90564
+ The Kernel assigns each Node ID; callers do not choose a storage path. The
90565
+ CLI binds the key proof to the exact provision fingerprint and target Kernel
90566
+ audience, then caches the returned (issuer, subject) for subsequent calls.
89889
90567
 
89890
90568
  Example:
89891
90569
  $ astrale identity register alice --class /:accounts.example:class.User --props '{"accounts.example:class.User.property.name":"Alice"}' -i staging
@@ -90075,7 +90753,7 @@ __export(exports_sync, {
90075
90753
  default: () => sync_default
90076
90754
  });
90077
90755
  var sync_default;
90078
- var init_sync = __esm(() => {
90756
+ var init_sync2 = __esm(() => {
90079
90757
  init_identity7();
90080
90758
  init_log();
90081
90759
  init_output();
@@ -90196,7 +90874,7 @@ var exports_import = {};
90196
90874
  __export(exports_import, {
90197
90875
  default: () => import_default
90198
90876
  });
90199
- import { readFile as readFile20 } from "node:fs/promises";
90877
+ import { readFile as readFile22 } from "node:fs/promises";
90200
90878
  var import_default;
90201
90879
  var init_import2 = __esm(() => {
90202
90880
  init_identity7();
@@ -90224,7 +90902,7 @@ var init_import2 = __esm(() => {
90224
90902
  ],
90225
90903
  action: async (path8, opts) => {
90226
90904
  try {
90227
- const raw2 = await readFile20(path8, "utf-8");
90905
+ const raw2 = await readFile22(path8, "utf-8");
90228
90906
  const passphrase = isEncryptedIdentityExport(raw2) ? await readPassphrase("Passphrase: ") : undefined;
90229
90907
  const envelope = await decodeIdentityExport(raw2, passphrase);
90230
90908
  const name = opts.name ?? envelope.subject;
@@ -90578,21 +91256,68 @@ var init_status4 = __esm(() => {
90578
91256
 
90579
91257
  // src/telemetry/store.ts
90580
91258
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
90581
- import { join as join17 } from "node:path";
91259
+ import { join as join19 } from "node:path";
90582
91260
  function sessionsRoot() {
90583
- return join17(createPaths().home, "sessions");
91261
+ return join19(createPaths().home, "sessions");
90584
91262
  }
90585
91263
  function sessionDir(id) {
90586
- return join17(sessionsRoot(), id);
91264
+ return join19(sessionsRoot(), id);
90587
91265
  }
90588
91266
  function eventsPath(id) {
90589
- return join17(sessionDir(id), "events.jsonl");
91267
+ return join19(sessionDir(id), "events.jsonl");
90590
91268
  }
90591
91269
  function metaPath(id) {
90592
- return join17(sessionDir(id), "meta.json");
91270
+ return join19(sessionDir(id), "meta.json");
90593
91271
  }
90594
91272
  function markerPath(id) {
90595
- return join17(sessionDir(id), ".analyzed");
91273
+ return join19(sessionDir(id), ".analyzed");
91274
+ }
91275
+ function sessionIds() {
91276
+ try {
91277
+ return readdirSync2(sessionsRoot()).filter((name) => !name.startsWith("."));
91278
+ } catch {
91279
+ return [];
91280
+ }
91281
+ }
91282
+ function scanSessions(now = Date.now()) {
91283
+ return sessionIds().map((id) => {
91284
+ let lastEventAt = null;
91285
+ try {
91286
+ lastEventAt = statSync3(eventsPath(id)).mtime;
91287
+ } catch {}
91288
+ return {
91289
+ id,
91290
+ lastEventAt,
91291
+ closed: lastEventAt !== null && now - lastEventAt.getTime() > IDLE_WINDOW_MS,
91292
+ analyzed: existsSync9(markerPath(id))
91293
+ };
91294
+ }).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
91295
+ }
91296
+ function directoryBytes(dir) {
91297
+ let entries;
91298
+ try {
91299
+ entries = readdirSync2(dir, { withFileTypes: true });
91300
+ } catch {
91301
+ return 0;
91302
+ }
91303
+ let total = 0;
91304
+ for (const entry2 of entries) {
91305
+ const path8 = join19(dir, entry2.name);
91306
+ if (entry2.isDirectory()) {
91307
+ total += directoryBytes(path8);
91308
+ } else if (entry2.isFile()) {
91309
+ try {
91310
+ total += statSync3(path8).size;
91311
+ } catch {}
91312
+ }
91313
+ }
91314
+ return total;
91315
+ }
91316
+ function sessionBytes(id) {
91317
+ return directoryBytes(sessionDir(id));
91318
+ }
91319
+ function readMeta(id) {
91320
+ return readJsonSafe(metaPath(id));
90596
91321
  }
90597
91322
  function readJsonSafe(path8) {
90598
91323
  try {
@@ -90618,13 +91343,7 @@ function inspectSession(id, now = Date.now()) {
90618
91343
  };
90619
91344
  }
90620
91345
  function listSessions2(now = Date.now()) {
90621
- let ids;
90622
- try {
90623
- ids = readdirSync2(sessionsRoot()).filter((n) => !n.startsWith("."));
90624
- } catch {
90625
- return [];
90626
- }
90627
- return ids.map((id) => inspectSession(id, now)).filter((s) => s !== null).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
91346
+ return sessionIds().map((id) => inspectSession(id, now)).filter((s) => s !== null).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
90628
91347
  }
90629
91348
  var IDLE_WINDOW_MS;
90630
91349
  var init_store = __esm(() => {
@@ -90669,13 +91388,13 @@ var init_list5 = __esm(() => {
90669
91388
 
90670
91389
  // src/telemetry/adapters/claude-code.ts
90671
91390
  import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
90672
- import { homedir as homedir3 } from "node:os";
90673
- import { join as join18 } from "node:path";
91391
+ import { homedir as homedir4 } from "node:os";
91392
+ import { join as join20 } from "node:path";
90674
91393
  function mungeCwd(cwd) {
90675
91394
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
90676
91395
  }
90677
- function claudeCodeAdapter(base2 = join18(homedir3(), ".claude")) {
90678
- const projectsDir = join18(base2, "projects");
91396
+ function claudeCodeAdapter(base2 = join20(homedir4(), ".claude")) {
91397
+ const projectsDir = join20(base2, "projects");
90679
91398
  function detect2() {
90680
91399
  try {
90681
91400
  return existsSync10(projectsDir);
@@ -90699,7 +91418,7 @@ function claudeCodeAdapter(base2 = join18(homedir3(), ".claude")) {
90699
91418
  for (const dir of dirs) {
90700
91419
  if (dir !== munged && !dir.startsWith(prefix))
90701
91420
  continue;
90702
- const projectPath = join18(projectsDir, dir);
91421
+ const projectPath = join20(projectsDir, dir);
90703
91422
  let files;
90704
91423
  try {
90705
91424
  files = readdirSync3(projectPath);
@@ -90709,7 +91428,7 @@ function claudeCodeAdapter(base2 = join18(homedir3(), ".claude")) {
90709
91428
  for (const file2 of files) {
90710
91429
  if (!file2.endsWith(".jsonl"))
90711
91430
  continue;
90712
- const transcriptPath = join18(projectPath, file2);
91431
+ const transcriptPath = join20(projectPath, file2);
90713
91432
  try {
90714
91433
  const st = statSync4(transcriptPath);
90715
91434
  const mtimeMs = st.mtime.getTime();
@@ -90744,8 +91463,8 @@ var init_claude_code = __esm(() => {
90744
91463
  // src/telemetry/adapters/codex.ts
90745
91464
  import { existsSync as existsSync11, openSync as openSync2, readdirSync as readdirSync4, readSync, statSync as statSync5 } from "node:fs";
90746
91465
  import { closeSync as closeSync3 } from "node:fs";
90747
- import { homedir as homedir4 } from "node:os";
90748
- import { join as join19 } from "node:path";
91466
+ import { homedir as homedir5 } from "node:os";
91467
+ import { join as join21 } from "node:path";
90749
91468
  function readFirstLine(path8) {
90750
91469
  let fd = null;
90751
91470
  try {
@@ -90785,8 +91504,8 @@ function numericDirs(path8) {
90785
91504
  return [];
90786
91505
  }
90787
91506
  }
90788
- function codexAdapter(base2 = join19(homedir4(), ".codex")) {
90789
- const sessionsDir = join19(base2, "sessions");
91507
+ function codexAdapter(base2 = join21(homedir5(), ".codex")) {
91508
+ const sessionsDir = join21(base2, "sessions");
90790
91509
  function detect2() {
90791
91510
  try {
90792
91511
  return existsSync11(sessionsDir);
@@ -90801,12 +91520,12 @@ function codexAdapter(base2 = join19(homedir4(), ".codex")) {
90801
91520
  const endMs = window2.end.getTime();
90802
91521
  const lowerMs = startMs - DAY_MS;
90803
91522
  for (const yyyy of numericDirs(sessionsDir)) {
90804
- for (const mm of numericDirs(join19(sessionsDir, yyyy))) {
90805
- for (const dd of numericDirs(join19(sessionsDir, yyyy, mm))) {
91523
+ for (const mm of numericDirs(join21(sessionsDir, yyyy))) {
91524
+ for (const dd of numericDirs(join21(sessionsDir, yyyy, mm))) {
90806
91525
  const dayStart = Date.UTC(Number(yyyy), Number(mm) - 1, Number(dd));
90807
91526
  if (dayStart > endMs + DAY_MS || dayStart + DAY_MS <= lowerMs)
90808
91527
  continue;
90809
- scanDay(join19(sessionsDir, yyyy, mm, dd), root, startMs, endMs, sessions);
91528
+ scanDay(join21(sessionsDir, yyyy, mm, dd), root, startMs, endMs, sessions);
90810
91529
  }
90811
91530
  }
90812
91531
  }
@@ -90828,7 +91547,7 @@ function scanDay(dayPath, root, startMs, endMs, out) {
90828
91547
  for (const file2 of files) {
90829
91548
  if (!file2.startsWith("rollout-") || !file2.endsWith(".jsonl"))
90830
91549
  continue;
90831
- const transcriptPath = join19(dayPath, file2);
91550
+ const transcriptPath = join21(dayPath, file2);
90832
91551
  try {
90833
91552
  const st = statSync5(transcriptPath);
90834
91553
  const mtimeMs = st.mtime.getTime();
@@ -90975,7 +91694,7 @@ var init_gate = () => {};
90975
91694
  // src/telemetry/analyze.ts
90976
91695
  import { spawn as spawn3 } from "node:child_process";
90977
91696
  import { appendFileSync, writeFileSync as writeFileSync2 } from "node:fs";
90978
- import { join as join20 } from "node:path";
91697
+ import { join as join22 } from "node:path";
90979
91698
  function writeMarker(id, marker) {
90980
91699
  writeFileSync2(markerPath(id), JSON.stringify(marker, null, 2) + `
90981
91700
  `);
@@ -91071,7 +91790,7 @@ async function analyzeSession(id, opts = {}) {
91071
91790
  const guides = new Map(adapters.map((a) => [a.name, a.readingGuide]));
91072
91791
  const prompt = buildPrompt({ id, root, signals: signals2, guides, file: opts.file ?? false });
91073
91792
  const dir = sessionDir(id);
91074
- writeFileSync2(join20(dir, "analyzer-prompt.md"), prompt);
91793
+ writeFileSync2(join22(dir, "analyzer-prompt.md"), prompt);
91075
91794
  const outcome = await runClaude(prompt, dir, opts);
91076
91795
  const marker = {
91077
91796
  analyzedAt: new Date().toISOString(),
@@ -91079,10 +91798,10 @@ async function analyzeSession(id, opts = {}) {
91079
91798
  note: outcome.note
91080
91799
  };
91081
91800
  writeMarker(id, marker);
91082
- return { ...marker, reportPath: join20(dir, "report.md") };
91801
+ return { ...marker, reportPath: join22(dir, "report.md") };
91083
91802
  }
91084
91803
  function runClaude(prompt, cwd, opts) {
91085
- return new Promise((resolve7) => {
91804
+ return new Promise((resolve8) => {
91086
91805
  const env2 = { ASTRALE_TELEMETRY: "0" };
91087
91806
  for (const [k, v] of Object.entries(process.env)) {
91088
91807
  if (v !== undefined && !k.startsWith("CLAUDE"))
@@ -91095,10 +91814,10 @@ function runClaude(prompt, cwd, opts) {
91095
91814
  try {
91096
91815
  child3 = spawn3("claude", args, { cwd, env: env2, stdio: ["ignore", "pipe", "pipe"] });
91097
91816
  } catch (e) {
91098
- resolve7({ ok: false, note: `claude spawn failed: ${String(e)}` });
91817
+ resolve8({ ok: false, note: `claude spawn failed: ${String(e)}` });
91099
91818
  return;
91100
91819
  }
91101
- child3.on("error", (e) => resolve7({ ok: false, note: `claude not available: ${e.message}` }));
91820
+ child3.on("error", (e) => resolve8({ ok: false, note: `claude not available: ${e.message}` }));
91102
91821
  let out = "";
91103
91822
  let err = "";
91104
91823
  child3.stdout?.setEncoding("utf8");
@@ -91109,22 +91828,22 @@ function runClaude(prompt, cwd, opts) {
91109
91828
  child3.on("close", (code) => {
91110
91829
  clearTimeout(timer);
91111
91830
  try {
91112
- appendFileSync(join20(cwd, "analyzer.log"), out + (err ? `
91831
+ appendFileSync(join22(cwd, "analyzer.log"), out + (err ? `
91113
91832
  --- stderr ---
91114
91833
  ${err}` : ""));
91115
91834
  } catch {}
91116
91835
  try {
91117
91836
  const result = JSON.parse(out);
91118
91837
  if (result.is_error) {
91119
- resolve7({ ok: false, note: `analyzer errored: ${result.result?.slice(0, 200)}` });
91838
+ resolve8({ ok: false, note: `analyzer errored: ${result.result?.slice(0, 200)}` });
91120
91839
  return;
91121
91840
  }
91122
- resolve7({
91841
+ resolve8({
91123
91842
  ok: true,
91124
91843
  note: `${result.num_turns ?? "?"} turns, $${(result.total_cost_usd ?? 0).toFixed(4)}`
91125
91844
  });
91126
91845
  } catch {
91127
- resolve7({ ok: false, note: `claude exit ${code}, unparseable output` });
91846
+ resolve8({ ok: false, note: `claude exit ${code}, unparseable output` });
91128
91847
  }
91129
91848
  });
91130
91849
  });
@@ -91140,28 +91859,111 @@ var init_analyze = __esm(() => {
91140
91859
 
91141
91860
  // src/telemetry/settings.ts
91142
91861
  import { readFileSync as readFileSync5 } from "node:fs";
91862
+ function readConfig2() {
91863
+ try {
91864
+ const parsed = JSON.parse(readFileSync5(createPaths().config, "utf-8"));
91865
+ const telemetry = parsed.telemetry;
91866
+ return typeof telemetry === "object" && telemetry !== null ? telemetry : {};
91867
+ } catch {
91868
+ return {};
91869
+ }
91870
+ }
91143
91871
  function telemetryEnabled() {
91144
91872
  const env2 = process.env.ASTRALE_TELEMETRY;
91145
91873
  if (env2 !== undefined && OFF_VALUES.has(env2.trim().toLowerCase()))
91146
91874
  return false;
91147
- try {
91148
- const parsed = JSON.parse(readFileSync5(createPaths().config, "utf-8"));
91149
- if (parsed.telemetry?.enabled === false)
91150
- return false;
91151
- } catch {}
91152
- return true;
91875
+ return readConfig2().enabled !== false;
91876
+ }
91877
+ function firstPositive(candidates) {
91878
+ for (const candidate2 of candidates) {
91879
+ if (candidate2 === undefined)
91880
+ continue;
91881
+ const n = typeof candidate2 === "string" ? Number(candidate2.trim()) : candidate2;
91882
+ if (Number.isFinite(n) && n > 0)
91883
+ return n;
91884
+ }
91885
+ return null;
91886
+ }
91887
+ function retentionBudget() {
91888
+ const config2 = readConfig2();
91889
+ const days = firstPositive([process.env.ASTRALE_TELEMETRY_MAX_AGE_DAYS, config2.maxAgeDays]) ?? DEFAULT_MAX_AGE_DAYS;
91890
+ const bytes = firstPositive([process.env.ASTRALE_TELEMETRY_MAX_BYTES, config2.maxBytes]) ?? DEFAULT_MAX_BYTES;
91891
+ return { maxAgeMs: days * 24 * 60 * 60 * 1000, maxBytes: bytes };
91153
91892
  }
91154
- var OFF_VALUES;
91893
+ var OFF_VALUES, DEFAULT_MAX_AGE_DAYS = 30, DEFAULT_MAX_BYTES;
91155
91894
  var init_settings = __esm(() => {
91156
91895
  init_state();
91157
91896
  OFF_VALUES = new Set(["0", "false", "off"]);
91897
+ DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
91898
+ });
91899
+
91900
+ // src/telemetry/retention.ts
91901
+ import { rmSync as rmSync2 } from "node:fs";
91902
+ function remove(id, into) {
91903
+ try {
91904
+ rmSync2(sessionDir(id), { recursive: true, force: true });
91905
+ into.push(id);
91906
+ } catch {}
91907
+ }
91908
+ function sweepByAge(sessions, options = {}) {
91909
+ const { maxAgeMs } = options.budget ?? retentionBudget();
91910
+ const cutoff = (options.now ?? Date.now()) - maxAgeMs;
91911
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
91912
+ const protect = options.protect;
91913
+ const removed = [];
91914
+ for (const session3 of sessions) {
91915
+ if (removed.length >= limit)
91916
+ break;
91917
+ if (protect?.has(session3.id))
91918
+ continue;
91919
+ if (session3.lastEventAt !== null && session3.lastEventAt.getTime() < cutoff) {
91920
+ remove(session3.id, removed);
91921
+ }
91922
+ }
91923
+ return { removed };
91924
+ }
91925
+ function sweepToBudget(sessions, options = {}) {
91926
+ const { maxBytes } = options.budget ?? retentionBudget();
91927
+ const protect = options.protect;
91928
+ const removed = [];
91929
+ const sized = sessions.map((session3) => ({ session: session3, bytes: sessionBytes(session3.id) }));
91930
+ let total = sized.reduce((sum, entry2) => sum + entry2.bytes, 0);
91931
+ if (total <= maxBytes)
91932
+ return { removed };
91933
+ const evictable = sized.filter((entry2) => entry2.session.closed && !protect?.has(entry2.session.id)).sort((a, b) => (a.session.lastEventAt?.getTime() ?? 0) - (b.session.lastEventAt?.getTime() ?? 0));
91934
+ for (const analyzedFirst of [true, false]) {
91935
+ for (const entry2 of evictable) {
91936
+ if (total <= maxBytes)
91937
+ return { removed };
91938
+ if (entry2.session.analyzed !== analyzedFirst)
91939
+ continue;
91940
+ const before = removed.length;
91941
+ remove(entry2.session.id, removed);
91942
+ if (removed.length > before)
91943
+ total -= entry2.bytes;
91944
+ }
91945
+ }
91946
+ return { removed };
91947
+ }
91948
+ function sweepStore(options = {}) {
91949
+ const budget = options.budget ?? retentionBudget();
91950
+ const sessions = scanSessions();
91951
+ const byAge = sweepByAge(sessions, { ...options, budget });
91952
+ const gone = new Set(byAge.removed);
91953
+ const bySize = sweepToBudget(sessions.filter((session3) => !gone.has(session3.id)), { ...options, budget });
91954
+ return { removed: [...byAge.removed, ...bySize.removed] };
91955
+ }
91956
+ var MAX_REMOVALS_ON_START = 20;
91957
+ var init_retention = __esm(() => {
91958
+ init_settings();
91959
+ init_store();
91158
91960
  });
91159
91961
 
91160
91962
  // src/telemetry/trigger.ts
91161
- import { existsSync as existsSync12, mkdirSync as mkdirSync2, readFileSync as readFileSync6, rmSync as rmSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
91162
- import { join as join21 } from "node:path";
91963
+ import { existsSync as existsSync12, mkdirSync as mkdirSync2, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
91964
+ import { join as join23 } from "node:path";
91163
91965
  function lockPath() {
91164
- return join21(sessionsRoot(), ".analyzer.lock");
91966
+ return join23(sessionsRoot(), ".analyzer.lock");
91165
91967
  }
91166
91968
  function releaseLock() {
91167
91969
  try {
@@ -91173,12 +91975,12 @@ function restampLock() {
91173
91975
  writeFileSync3(lockPath(), JSON.stringify({ pid: process.pid, at: Date.now() }));
91174
91976
  } catch {}
91175
91977
  }
91176
- var LOCK_STALE_MS, GC_AGE_MS;
91978
+ var LOCK_STALE_MS;
91177
91979
  var init_trigger = __esm(() => {
91980
+ init_retention();
91178
91981
  init_settings();
91179
91982
  init_store();
91180
91983
  LOCK_STALE_MS = 30 * 60 * 1000;
91181
- GC_AGE_MS = 30 * 24 * 60 * 60 * 1000;
91182
91984
  });
91183
91985
 
91184
91986
  // src/commands/session/analyze.ts
@@ -91191,12 +91993,19 @@ var init_analyze2 = __esm(() => {
91191
91993
  init_source();
91192
91994
  init_log();
91193
91995
  init_analyze();
91996
+ init_retention();
91194
91997
  init_store();
91195
91998
  init_trigger();
91196
91999
  analyze_default = {
91197
92000
  name: "analyze",
91198
92001
  description: "Analyze a recorded session for DX frictions (dry-run: writes report.md)",
91199
- arguments: [{ name: "id", description: "Session id (default: most recent closed, unanalyzed)" }],
92002
+ arguments: [
92003
+ {
92004
+ name: "id",
92005
+ description: "Session id (default: most recent closed, unanalyzed)",
92006
+ required: false
92007
+ }
92008
+ ],
91200
92009
  options: [
91201
92010
  { flags: "--file", description: "File cleared findings as issues on the admin tracker" },
91202
92011
  { flags: "--model <model>", description: "Model for the analyzer pass" },
@@ -91206,10 +92015,10 @@ var init_analyze2 = __esm(() => {
91206
92015
  action: async (id, opts) => {
91207
92016
  if (opts.auto)
91208
92017
  restampLock();
92018
+ let target2 = id;
91209
92019
  try {
91210
- let target2 = id;
91211
92020
  if (!target2) {
91212
- const candidate2 = listSessions2().find((s) => s.closed && (opts.force || !s.analyzed));
92021
+ const candidate2 = scanSessions().find((s) => s.closed && (opts.force || !s.analyzed));
91213
92022
  if (!candidate2) {
91214
92023
  if (!opts.auto)
91215
92024
  log.dim("Nothing to analyze — no closed, unanalyzed session.");
@@ -91234,6 +92043,12 @@ var init_analyze2 = __esm(() => {
91234
92043
  } finally {
91235
92044
  if (opts.auto)
91236
92045
  releaseLock();
92046
+ try {
92047
+ const swept = sweepStore({ protect: new Set(target2 === undefined ? [] : [target2]) });
92048
+ if (!opts.auto && swept.removed.length > 0) {
92049
+ log.dim(`retention: removed ${swept.removed.length} session(s)`);
92050
+ }
92051
+ } catch {}
91237
92052
  }
91238
92053
  }
91239
92054
  };
@@ -91244,7 +92059,7 @@ var exports_add2 = {};
91244
92059
  __export(exports_add2, {
91245
92060
  default: () => add_default2
91246
92061
  });
91247
- import { readFile as readFile21 } from "node:fs/promises";
92062
+ import { readFile as readFile23 } from "node:fs/promises";
91248
92063
  function isString(value3) {
91249
92064
  return typeof value3 === "string";
91250
92065
  }
@@ -91306,7 +92121,7 @@ Security:
91306
92121
  if (!opts.issuer && !opts.metadata && !opts.workosAuthkit) {
91307
92122
  throw new Error("Either --issuer, --metadata, or --workos-authkit is required");
91308
92123
  }
91309
- let metadata = opts.workosAuthkit ? workosAuthKitMetadata(opts.workosApiHostname, opts.clientId) : opts.metadata ? OidcMetadataSchema.parse(JSON.parse(await readFile21(opts.metadata, "utf-8"))) : await fetchOidcMetadata(opts.issuer);
92124
+ let metadata = opts.workosAuthkit ? workosAuthKitMetadata(opts.workosApiHostname, opts.clientId) : opts.metadata ? OidcMetadataSchema.parse(JSON.parse(await readFile23(opts.metadata, "utf-8"))) : await fetchOidcMetadata(opts.issuer);
91310
92125
  if (opts.issuer) {
91311
92126
  validateUrl(opts.issuer);
91312
92127
  if (normalizeIssuer2(metadata.issuer) !== normalizeIssuer2(opts.issuer)) {
@@ -91872,7 +92687,7 @@ async function buildProgram() {
91872
92687
  (await Promise.resolve().then(() => (init_use4(), exports_use4))).default,
91873
92688
  (await Promise.resolve().then(() => (init_whoami3(), exports_whoami))).default,
91874
92689
  (await Promise.resolve().then(() => (init_delete2(), exports_delete2))).default,
91875
- (await Promise.resolve().then(() => (init_sync(), exports_sync))).default,
92690
+ (await Promise.resolve().then(() => (init_sync2(), exports_sync))).default,
91876
92691
  (await Promise.resolve().then(() => (init_unsync(), exports_unsync))).default,
91877
92692
  (await Promise.resolve().then(() => (init_export2(), exports_export))).default,
91878
92693
  (await Promise.resolve().then(() => (init_import2(), exports_import))).default
@@ -91996,16 +92811,16 @@ function redactArgv(argv) {
91996
92811
 
91997
92812
  // src/telemetry/session.ts
91998
92813
  init_store();
91999
- import { createHash as createHash3 } from "node:crypto";
92814
+ import { createHash as createHash4 } from "node:crypto";
92000
92815
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
92001
- import { dirname as dirname12, join as join22 } from "node:path";
92816
+ import { dirname as dirname14, join as join24 } from "node:path";
92002
92817
  var MAX_ID_LEN = 64;
92003
92818
  function findGitRoot(start) {
92004
92819
  let dir = start;
92005
92820
  for (;; ) {
92006
- if (existsSync13(join22(dir, ".git")))
92821
+ if (existsSync13(join24(dir, ".git")))
92007
92822
  return dir;
92008
- const parent = dirname12(dir);
92823
+ const parent = dirname14(dir);
92009
92824
  if (parent === dir)
92010
92825
  return null;
92011
92826
  dir = parent;
@@ -92018,19 +92833,21 @@ function stamp(d = new Date) {
92018
92833
  const p = (n, w = 2) => String(n).padStart(w, "0");
92019
92834
  return `${p(d.getFullYear(), 4)}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}`;
92020
92835
  }
92021
- function findOpenAmbient(root) {
92022
- for (const s of listSessions2()) {
92023
- if (s.meta?.root === root && s.meta.explicit === false && !s.closed && s.analyzed === null) {
92836
+ function findOpenAmbient(root, sessions) {
92837
+ for (const s of sessions) {
92838
+ if (s.closed || s.analyzed)
92839
+ continue;
92840
+ const meta3 = readMeta(s.id);
92841
+ if (meta3?.root === root && meta3.explicit === false)
92024
92842
  return s.id;
92025
- }
92026
92843
  }
92027
92844
  return null;
92028
92845
  }
92029
92846
  function mintAmbientId(root) {
92030
- const hash8 = createHash3("sha256").update(root).digest("hex").slice(0, 8);
92847
+ const hash8 = createHash4("sha256").update(root).digest("hex").slice(0, 8);
92031
92848
  return `amb-${hash8}-${stamp()}`;
92032
92849
  }
92033
- function resolveSession2(cwd) {
92850
+ function resolveSession2(cwd, sessions) {
92034
92851
  const root = findGitRoot(cwd) ?? cwd;
92035
92852
  const pinned = process.env.ASTRALE_SESSION;
92036
92853
  if (pinned) {
@@ -92038,10 +92855,11 @@ function resolveSession2(cwd) {
92038
92855
  if (id.length > 0)
92039
92856
  return { id, root, explicit: true };
92040
92857
  }
92041
- return { id: findOpenAmbient(root) ?? mintAmbientId(root), root, explicit: false };
92858
+ const scan2 = sessions ?? scanSessions();
92859
+ return { id: findOpenAmbient(root, scan2) ?? mintAmbientId(root), root, explicit: false };
92042
92860
  }
92043
- function ensureSession(cwd) {
92044
- const resolved = resolveSession2(cwd);
92861
+ function ensureSession(cwd, sessions) {
92862
+ const resolved = resolveSession2(cwd, sessions);
92045
92863
  const dir = sessionDir(resolved.id);
92046
92864
  if (!existsSync13(dir)) {
92047
92865
  try {
@@ -92075,7 +92893,7 @@ var HELP_VERSION = new Set([
92075
92893
  function isHelpOrVersion(args) {
92076
92894
  return args.every((a) => HELP_VERSION.has(a));
92077
92895
  }
92078
- function beginInvocation(argv) {
92896
+ function beginInvocation(argv, sessions) {
92079
92897
  try {
92080
92898
  const args = argv.slice(2);
92081
92899
  if (!telemetryEnabled() || isHelpOrVersion(args))
@@ -92083,7 +92901,7 @@ function beginInvocation(argv) {
92083
92901
  const startMs = Date.now();
92084
92902
  const ts = new Date(startMs).toISOString();
92085
92903
  const cwd = process.cwd();
92086
- const { id, root } = ensureSession(cwd);
92904
+ const { id, root } = ensureSession(cwd, sessions);
92087
92905
  return (exitCode, errorName) => {
92088
92906
  try {
92089
92907
  const event = {
@@ -92107,37 +92925,61 @@ function beginInvocation(argv) {
92107
92925
  }
92108
92926
  }
92109
92927
 
92928
+ // src/telemetry/store.ts
92929
+ init_state();
92930
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync6 } from "node:fs";
92931
+ import { join as join25 } from "node:path";
92932
+ var IDLE_WINDOW_MS2 = 30 * 60 * 1000;
92933
+ function sessionsRoot2() {
92934
+ return join25(createPaths().home, "sessions");
92935
+ }
92936
+ function sessionDir2(id) {
92937
+ return join25(sessionsRoot2(), id);
92938
+ }
92939
+ function eventsPath2(id) {
92940
+ return join25(sessionDir2(id), "events.jsonl");
92941
+ }
92942
+ function markerPath2(id) {
92943
+ return join25(sessionDir2(id), ".analyzed");
92944
+ }
92945
+ function sessionIds2() {
92946
+ try {
92947
+ return readdirSync5(sessionsRoot2()).filter((name) => !name.startsWith("."));
92948
+ } catch {
92949
+ return [];
92950
+ }
92951
+ }
92952
+ function scanSessions2(now = Date.now()) {
92953
+ return sessionIds2().map((id) => {
92954
+ let lastEventAt = null;
92955
+ try {
92956
+ lastEventAt = statSync6(eventsPath2(id)).mtime;
92957
+ } catch {}
92958
+ return {
92959
+ id,
92960
+ lastEventAt,
92961
+ closed: lastEventAt !== null && now - lastEventAt.getTime() > IDLE_WINDOW_MS2,
92962
+ analyzed: existsSync14(markerPath2(id))
92963
+ };
92964
+ }).sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0));
92965
+ }
92966
+
92110
92967
  // src/telemetry/trigger.ts
92968
+ init_retention();
92111
92969
  init_settings();
92112
92970
  init_store();
92113
92971
  import { spawn as spawn4 } from "node:child_process";
92114
- import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync7, rmSync as rmSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
92115
- import { join as join23 } from "node:path";
92972
+ import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
92973
+ import { join as join26 } from "node:path";
92116
92974
  var LOCK_STALE_MS2 = 30 * 60 * 1000;
92117
- var GC_AGE_MS2 = 30 * 24 * 60 * 60 * 1000;
92118
- var GC_MAX_PER_RUN = 20;
92119
- function gcOldSessions(sessions) {
92120
- try {
92121
- const cutoff = Date.now() - GC_AGE_MS2;
92122
- let removed = 0;
92123
- for (const s of sessions) {
92124
- if (removed >= GC_MAX_PER_RUN)
92125
- break;
92126
- if (s.analyzed !== null && s.lastEventAt !== null && s.lastEventAt.getTime() < cutoff) {
92127
- rmSync3(sessionDir(s.id), { recursive: true, force: true });
92128
- removed++;
92129
- }
92130
- }
92131
- } catch {}
92132
- }
92133
92975
  function lockPath2() {
92134
- return join23(sessionsRoot(), ".analyzer.lock");
92976
+ return join26(sessionsRoot(), ".analyzer.lock");
92135
92977
  }
92136
92978
  function claimLock() {
92137
92979
  const path8 = lockPath2();
92138
92980
  try {
92139
- if (existsSync14(path8)) {
92140
- const lock = JSON.parse(readFileSync7(path8, "utf-8"));
92981
+ if (existsSync15(path8)) {
92982
+ const lock = JSON.parse(readFileSync8(path8, "utf-8"));
92141
92983
  const stale2 = Date.now() - lock.at > LOCK_STALE_MS2;
92142
92984
  let alive = false;
92143
92985
  try {
@@ -92155,17 +92997,17 @@ function claimLock() {
92155
92997
  return false;
92156
92998
  }
92157
92999
  }
92158
- function maybeTriggerAnalysis(argv) {
93000
+ function maybeTriggerAnalysis(argv, scan2) {
92159
93001
  try {
92160
- if (!telemetryEnabled())
92161
- return;
92162
93002
  if (process.env.ASTRALE_TELEMETRY_NO_TRIGGER === "1")
92163
93003
  return;
93004
+ const sessions = scan2 ?? scanSessions();
93005
+ const swept = new Set(sweepByAge(sessions, { limit: MAX_REMOVALS_ON_START }).removed);
93006
+ if (!telemetryEnabled())
93007
+ return;
92164
93008
  if (argv[2] === "session")
92165
93009
  return;
92166
- const sessions = listSessions2();
92167
- gcOldSessions(sessions);
92168
- const target2 = sessions.find((s) => s.closed && s.analyzed === null && s.lastEventAt !== null);
93010
+ const target2 = sessions.find((s) => s.closed && !s.analyzed && s.lastEventAt !== null && !swept.has(s.id));
92169
93011
  if (!target2)
92170
93012
  return;
92171
93013
  if (!claimLock())
@@ -92188,11 +93030,12 @@ function overrideExits(command) {
92188
93030
  for (const child3 of command.commands)
92189
93031
  overrideExits(child3);
92190
93032
  }
92191
- var finalize2 = process.argv[2] === "session" ? undefined : beginInvocation(process.argv);
93033
+ var sessions = scanSessions2();
93034
+ var finalize2 = process.argv[2] === "session" ? undefined : beginInvocation(process.argv, sessions);
92192
93035
  var errorName;
92193
93036
  if (finalize2)
92194
93037
  process.on("exit", (code) => finalize2(code ?? 0, errorName));
92195
- maybeTriggerAnalysis(process.argv);
93038
+ maybeTriggerAnalysis(process.argv, sessions);
92196
93039
  var argv = normalizeRootVersionArgv(process.argv);
92197
93040
  configureInvocation(argv);
92198
93041
  var program4 = await buildProgram();