austere-axi 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -492,7 +492,7 @@ var require_ci_info = __commonJS({
492
492
  }
493
493
  });
494
494
 
495
- // src/cli/artifact.js
495
+ // src/cli/artifact.ts
496
496
  function readArtifact(html) {
497
497
  const text = String(html);
498
498
  const m = PROGRAM_RE.exec(text);
@@ -544,28 +544,30 @@ function rewriteRuntime(html, replacement) {
544
544
  return rep + "\n" + text;
545
545
  }
546
546
  function escapeHtml(s) {
547
- return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
547
+ return String(s).replace(/[&<>"']/g, (c) => ENTITIES[c]);
548
548
  }
549
549
  function decodeEntities(s) {
550
550
  return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
551
551
  }
552
- var CDN_RUNTIME_URL, PROGRAM_RE, RUNTIME_RE, TITLE_RE;
552
+ var CDN_RUNTIME_URL, PROGRAM_RE, RUNTIME_RE, TITLE_RE, ENTITIES;
553
553
  var init_artifact = __esm({
554
- "src/cli/artifact.js"() {
554
+ "src/cli/artifact.ts"() {
555
+ "use strict";
555
556
  CDN_RUNTIME_URL = "https://cdn.jsdelivr.net/npm/austere-axi@0/dist/austere.js";
556
557
  PROGRAM_RE = /(<script\b[^>]*\btype\s*=\s*["']text\/openui(?:-lang)?["'][^>]*>)([\s\S]*?)(<\/script\s*>)/i;
557
558
  RUNTIME_RE = /<script\b[^>]*\bsrc\s*=\s*["']([^"']*\/austere(?:\.min)?\.js(?:\?[^"']*)?|austere(?:\.min)?\.js)["'][^>]*>\s*<\/script\s*>/i;
558
559
  TITLE_RE = /<title[^>]*>([\s\S]*?)<\/title\s*>/i;
560
+ ENTITIES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
559
561
  }
560
562
  });
561
563
 
562
- // src/cli/paths.js
564
+ // src/cli/paths.ts
563
565
  import fs from "node:fs";
564
566
  import os from "node:os";
565
567
  import path from "node:path";
566
568
  import { fileURLToPath } from "node:url";
567
569
  function packKitPath() {
568
- return BUNDLED ? path.join(HERE, "pack-kit.mjs") : path.resolve(HERE, "../pack-kit/index.js");
570
+ return BUNDLED ? path.join(HERE, "pack-kit.mjs") : path.resolve(HERE, "../pack-kit/index.ts");
569
571
  }
570
572
  function assetPath(name) {
571
573
  return path.join(ASSET_DIR, name);
@@ -611,17 +613,18 @@ function versionGt(a, b) {
611
613
  }
612
614
  var HERE, BUNDLED, ASSET_DIR, VERSION, CLI_ENTRY, securedDir;
613
615
  var init_paths = __esm({
614
- "src/cli/paths.js"() {
616
+ "src/cli/paths.ts"() {
617
+ "use strict";
615
618
  HERE = path.dirname(fileURLToPath(import.meta.url));
616
619
  BUNDLED = true;
617
620
  ASSET_DIR = BUNDLED ? HERE : path.resolve(HERE, "../../dist");
618
- VERSION = true ? "0.5.2" : JSON.parse(fs.readFileSync(path.resolve(HERE, "../../package.json"), "utf8")).version;
619
- CLI_ENTRY = BUNDLED ? fileURLToPath(import.meta.url) : path.resolve(HERE, "main.js");
621
+ VERSION = true ? "0.7.0" : packageVersion();
622
+ CLI_ENTRY = BUNDLED ? fileURLToPath(import.meta.url) : path.resolve(HERE, "main.ts");
620
623
  securedDir = null;
621
624
  }
622
625
  });
623
626
 
624
- // src/shared/packs-meta.js
627
+ // src/shared/packs-meta.ts
625
628
  function splitSpec(spec) {
626
629
  const at2 = spec.lastIndexOf("@");
627
630
  if (at2 <= 0) return { name: spec, version: "" };
@@ -650,18 +653,16 @@ function isValidVersion(version2) {
650
653
  }
651
654
  var PACKS_META_RE, NAME_RE, VERSION_RE;
652
655
  var init_packs_meta = __esm({
653
- "src/shared/packs-meta.js"() {
656
+ "src/shared/packs-meta.ts"() {
657
+ "use strict";
654
658
  PACKS_META_RE = /<meta\s+name=["']austere-packs["']\s+content=["']([^"']*)["']\s*\/?>/i;
655
659
  NAME_RE = /^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
656
660
  VERSION_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
657
661
  }
658
662
  });
659
663
 
660
- // src/shared/catalog.js
661
- function searchCatalog(entries, query, { notesOf = (
662
- /** @type {(entry: any) => string[]} */
663
- (() => [])
664
- ), limit = 8 } = {}) {
664
+ // src/shared/catalog.ts
665
+ function searchCatalog(entries, query, { notesOf = () => [], limit = 8 } = {}) {
665
666
  const q2 = String(query || "").trim().toLowerCase();
666
667
  const terms = words(q2);
667
668
  if (!terms.length) return [];
@@ -688,13 +689,14 @@ function searchCatalog(entries, query, { notesOf = (
688
689
  }
689
690
  var CATALOG_FORMAT, words;
690
691
  var init_catalog = __esm({
691
- "src/shared/catalog.js"() {
692
+ "src/shared/catalog.ts"() {
693
+ "use strict";
692
694
  CATALOG_FORMAT = 1;
693
695
  words = (text) => String(text || "").toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1);
694
696
  }
695
697
  });
696
698
 
697
- // src/cli/packs.js
699
+ // src/cli/packs.ts
698
700
  import fs2 from "node:fs";
699
701
  import path2 from "node:path";
700
702
  function isFirstParty(name) {
@@ -754,7 +756,7 @@ function installedPacks() {
754
756
  return Object.entries(lock.packs).map(([name, e]) => {
755
757
  const dir = path2.join(packsDir(), packDirName(name, e.version));
756
758
  return { name, ...e, dir, catalog: readJson(path2.join(dir, "catalog.json"), null) };
757
- }).filter((p) => p.catalog).sort((a, b) => a.name.localeCompare(b.name));
759
+ }).filter((p) => !!p.catalog).sort((a, b) => a.name.localeCompare(b.name));
758
760
  }
759
761
  function findInstalled(name) {
760
762
  return installedPacks().find((p) => p.name === name) || null;
@@ -852,7 +854,8 @@ function mergePackSchemas(schema, catalogs) {
852
854
  }
853
855
  var PACK_API, TRUSTED_SCOPE, core, bundled;
854
856
  var init_packs = __esm({
855
- "src/cli/packs.js"() {
857
+ "src/cli/packs.ts"() {
858
+ "use strict";
856
859
  init_paths();
857
860
  init_packs_meta();
858
861
  init_catalog();
@@ -863,10 +866,15 @@ var init_packs = __esm({
863
866
  }
864
867
  });
865
868
 
866
- // src/cli/invites.js
869
+ // src/cli/invites.ts
867
870
  import crypto from "node:crypto";
868
871
  import fs4 from "node:fs";
869
872
  import path3 from "node:path";
873
+ function isInvite(i) {
874
+ if (!i || typeof i !== "object") return false;
875
+ const o = i;
876
+ return typeof o.token === "string" && o.token.length >= 22 && Boolean(o.name) && Boolean(o.sessionId);
877
+ }
870
878
  function safeEqual(a, b) {
871
879
  const x = crypto.createHash("sha256").update(String(a)).digest();
872
880
  const y = crypto.createHash("sha256").update(String(b)).digest();
@@ -923,18 +931,21 @@ function ownerKey(dir) {
923
931
  }
924
932
  var COOKIE, RESERVED, InviteStore, OWNER_COOKIE, LoginCodes, hashCode;
925
933
  var init_invites = __esm({
926
- "src/cli/invites.js"() {
934
+ "src/cli/invites.ts"() {
935
+ "use strict";
927
936
  COOKIE = "austere_rv";
928
937
  RESERVED = /* @__PURE__ */ new Set(["owner", "you", "agent", "me"]);
929
938
  InviteStore = class {
930
- /** @param {string} dir state dir */
939
+ file;
940
+ invites;
941
+ /** @param dir state dir */
931
942
  constructor(dir) {
932
943
  this.file = path3.join(dir, "invites.json");
933
944
  this.invites = [];
934
945
  try {
935
946
  const raw = JSON.parse(fs4.readFileSync(this.file, "utf8"));
936
947
  if (Array.isArray(raw.invites)) {
937
- this.invites = raw.invites.filter((i) => i && typeof i.token === "string" && i.token.length >= 22 && i.name && i.sessionId);
948
+ this.invites = raw.invites.filter(isInvite);
938
949
  }
939
950
  } catch {
940
951
  }
@@ -944,7 +955,6 @@ var init_invites = __esm({
944
955
  fs4.writeFileSync(tmp, JSON.stringify({ invites: this.invites }, null, 2) + "\n", { mode: 384 });
945
956
  fs4.renameSync(tmp, this.file);
946
957
  }
947
- /** @returns {Invite} */
948
958
  add({ name, sessionId: sessionId2, file: file3, auto = false }) {
949
959
  const clean = cleanName(name);
950
960
  if (this.invites.some((i) => i.sessionId === sessionId2 && i.name.toLowerCase() === clean.toLowerCase())) {
@@ -979,7 +989,10 @@ var init_invites = __esm({
979
989
  };
980
990
  OWNER_COOKIE = "austere_owner";
981
991
  LoginCodes = class {
982
- /** @param {number} ttlMs @param {() => number} [now] */
992
+ ttlMs;
993
+ now;
994
+ /** sha256(code) -> expiry */
995
+ codes;
983
996
  constructor(ttlMs, now = Date.now) {
984
997
  this.ttlMs = ttlMs;
985
998
  this.now = now;
@@ -1005,7 +1018,7 @@ var init_invites = __esm({
1005
1018
  }
1006
1019
  });
1007
1020
 
1008
- // src/cli/client.js
1021
+ // src/cli/client.ts
1009
1022
  import { spawn } from "node:child_process";
1010
1023
  import crypto2 from "node:crypto";
1011
1024
  import fs5 from "node:fs";
@@ -1091,7 +1104,8 @@ async function ensureServer() {
1091
1104
  throw new Error(`port ${port2} is served by an austere-axi with another state dir (${h.stateDir}); set AUSTERE_AXI_PORT`);
1092
1105
  }
1093
1106
  if (h && (h.legacy || versionGt(VERSION, h.version))) {
1094
- const keep = (h.listen || []).filter((x) => x !== "127.0.0.1" && (!h.tailnet || x !== h.tailnet.ip));
1107
+ const { tailnet } = h;
1108
+ const keep = (h.listen || []).filter((x) => x !== "127.0.0.1" && (!tailnet || x !== tailnet.ip));
1095
1109
  const persist = !!h.persist;
1096
1110
  await shutdownServer(h, port2);
1097
1111
  for (let i = 0; i < 40 && await health(port2); i++) await sleep(100);
@@ -1163,7 +1177,8 @@ async function openBrowser(url2) {
1163
1177
  }
1164
1178
  var sleep, START_TIMEOUT_MS;
1165
1179
  var init_client = __esm({
1166
- "src/cli/client.js"() {
1180
+ "src/cli/client.ts"() {
1181
+ "use strict";
1167
1182
  init_paths();
1168
1183
  init_invites();
1169
1184
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -1171,7 +1186,7 @@ var init_client = __esm({
1171
1186
  }
1172
1187
  });
1173
1188
 
1174
- // src/cli/hub.js
1189
+ // src/cli/hub.ts
1175
1190
  import fs6 from "node:fs";
1176
1191
  import path5 from "node:path";
1177
1192
  function hubConfigFile() {
@@ -1194,7 +1209,7 @@ async function loadSqlite() {
1194
1209
  if (DatabaseSync) return DatabaseSync;
1195
1210
  const emit2 = process.emitWarning;
1196
1211
  process.emitWarning = function(warning, ...rest) {
1197
- if (/sqlite/i.test(String(warning && warning.message ? warning.message : warning))) return;
1212
+ if (/sqlite/i.test(String(typeof warning !== "string" && warning && warning.message ? warning.message : warning))) return;
1198
1213
  return emit2.call(process, warning, ...rest);
1199
1214
  };
1200
1215
  try {
@@ -1252,7 +1267,8 @@ async function registerWithHub(file3, title, id) {
1252
1267
  }
1253
1268
  var DatabaseSync, SCHEMA, NOTES, Hub;
1254
1269
  var init_hub = __esm({
1255
- "src/cli/hub.js"() {
1270
+ "src/cli/hub.ts"() {
1271
+ "use strict";
1256
1272
  init_paths();
1257
1273
  DatabaseSync = null;
1258
1274
  SCHEMA = `
@@ -1308,10 +1324,12 @@ CREATE TABLE IF NOT EXISTS notes (
1308
1324
  CREATE INDEX IF NOT EXISTS notes_artifact ON notes(artifact_id, at);
1309
1325
  `;
1310
1326
  Hub = class _Hub {
1327
+ db;
1328
+ q;
1311
1329
  static async open(file3 = hubDbFile()) {
1312
1330
  return new _Hub(await openDatabase(file3));
1313
1331
  }
1314
- /** @param {any} db an open hub.db (openDatabase) */
1332
+ /** @param db an open hub.db (openDatabase) */
1315
1333
  constructor(db) {
1316
1334
  this.db = db;
1317
1335
  this.q = {
@@ -1350,13 +1368,15 @@ CREATE INDEX IF NOT EXISTS notes_artifact ON notes(artifact_id, at);
1350
1368
  WHERE (? = '' OR a.title LIKE ? ESCAPE '\\' OR a.file LIKE ? ESCAPE '\\' OR a.project LIKE ? ESCAPE '\\')
1351
1369
  AND (? = '' OR a.project = ?)
1352
1370
  GROUP BY a.id ORDER BY a.updated_at DESC LIMIT ?`
1353
- ).all(q2, like, like, like, project, project, limit).map((r) => ({ ...r, open: r.open || 0, received: r.received || 0, addressed: r.addressed || 0, held: r.held || 0 }));
1371
+ ).all(q2, like, like, like, project, project, limit).map((row) => {
1372
+ const r = row;
1373
+ return { ...r, open: r.open || 0, received: r.received || 0, addressed: r.addressed || 0, held: r.held || 0 };
1374
+ });
1354
1375
  }
1355
1376
  /**
1356
1377
  * Notes (annotations and messages) across all pages, newest first.
1357
1378
  * `q` matches note text, target and page title. `status`: "unanswered" (held, open and received), "answered", or "" for all.
1358
1379
  * `author`: a reviewer's name, "owner" for the owner's own notes, or "" for everyone. Discarded notes are left out.
1359
- * @param {{ q?: string, status?: string, project?: string, author?: string, limit?: number }} [o]
1360
1380
  */
1361
1381
  notes({ q: q2 = "", status = "", project = "", author = "", limit = 200 } = {}) {
1362
1382
  const anyAuthor = author === "" ? "all" : "one";
@@ -1388,7 +1408,7 @@ CREATE INDEX IF NOT EXISTS notes_artifact ON notes(artifact_id, at);
1388
1408
  }
1389
1409
  });
1390
1410
 
1391
- // src/cli/lock.js
1411
+ // src/cli/lock.ts
1392
1412
  import fs7 from "node:fs";
1393
1413
  import path6 from "node:path";
1394
1414
  async function lockStateDir(dir, { port: port2 = null, wait = 0 } = {}) {
@@ -1445,8 +1465,8 @@ async function lockHolder(text, dir) {
1445
1465
  }
1446
1466
  if (typeof lock === "number") lock = { pid: lock };
1447
1467
  const pid = Number(lock?.pid);
1448
- if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) return null;
1449
- const port2 = Number.isInteger(lock.port) && lock.port > 0 ? lock.port : void 0;
1468
+ if (!lock || !Number.isInteger(pid) || pid <= 0 || pid === process.pid) return null;
1469
+ const port2 = typeof lock.port === "number" && Number.isInteger(lock.port) && lock.port > 0 ? lock.port : void 0;
1450
1470
  if (port2 && await serves(port2, pid, dir)) return { pid, port: port2, serving: true };
1451
1471
  if (!processAlive(pid)) return null;
1452
1472
  if (lock.at == null) return { pid, port: port2, serving: false };
@@ -1466,14 +1486,15 @@ function processAlive(pid) {
1466
1486
  }
1467
1487
  var STARTING_MS, sleep2;
1468
1488
  var init_lock = __esm({
1469
- "src/cli/lock.js"() {
1489
+ "src/cli/lock.ts"() {
1490
+ "use strict";
1470
1491
  init_client();
1471
1492
  STARTING_MS = 6e4;
1472
1493
  sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
1473
1494
  }
1474
1495
  });
1475
1496
 
1476
- // src/cli/sessions.js
1497
+ // src/cli/sessions.ts
1477
1498
  import crypto3 from "node:crypto";
1478
1499
  import fs8 from "node:fs";
1479
1500
  import path7 from "node:path";
@@ -1486,7 +1507,7 @@ function newId(prefix) {
1486
1507
  function noteStatus(e, ackedSeq) {
1487
1508
  if (e.discarded) return "discarded";
1488
1509
  if (e.held) return "held";
1489
- if (e.seq > ackedSeq) return "open";
1510
+ if (Number(e.seq) > ackedSeq) return "open";
1490
1511
  if (e.doneAt) return "addressed";
1491
1512
  return "received";
1492
1513
  }
@@ -1523,7 +1544,6 @@ function hydrate(row) {
1523
1544
  endedAt: row.ended_at || null,
1524
1545
  closedAt: row.closed_at || null,
1525
1546
  ackedSeq: Number(row.acked_seq || 0),
1526
- /** @type {Record<string, any>[]} */
1527
1547
  transcript: [],
1528
1548
  // runtime-only
1529
1549
  deliveredSeq: Number(row.acked_seq || 0),
@@ -1534,18 +1554,22 @@ function hydrate(row) {
1534
1554
  }
1535
1555
  var counter, deliverable, SessionStore;
1536
1556
  var init_sessions = __esm({
1537
- "src/cli/sessions.js"() {
1557
+ "src/cli/sessions.ts"() {
1558
+ "use strict";
1538
1559
  init_hub();
1539
1560
  counter = 0;
1540
1561
  deliverable = (e) => e.role === "user" && e.seq != null && !e.held && !e.discarded;
1541
1562
  SessionStore = class _SessionStore {
1563
+ db;
1564
+ sessions;
1565
+ q;
1542
1566
  /** Open the store in a state dir: hub.db, after importing any session files from before it. */
1543
1567
  static async open(dir) {
1544
1568
  return new _SessionStore(await openDatabase(path7.join(dir, "hub.db")), dir);
1545
1569
  }
1546
1570
  /**
1547
- * @param {any} db hub.db, opened with openDatabase
1548
- * @param {string} dir the state dir
1571
+ * @param db hub.db, opened with openDatabase
1572
+ * @param dir the state dir
1549
1573
  */
1550
1574
  constructor(db, dir) {
1551
1575
  this.db = db;
@@ -1684,7 +1708,7 @@ var init_sessions = __esm({
1684
1708
  }
1685
1709
  /** Held entries, oldest first. */
1686
1710
  heldEntries(s) {
1687
- return s.transcript.filter((e) => e.role === "user" && e.held);
1711
+ return s.transcript.filter((e) => e.role === "user" && Boolean(e.held));
1688
1712
  }
1689
1713
  /**
1690
1714
  * The owner sends the held notes named by `ids` on to the agent. A released note gets
@@ -1740,7 +1764,7 @@ var init_sessions = __esm({
1740
1764
  const now = Date.now();
1741
1765
  this.updateNotes(s, entries.filter((e) => !e.seenAt).map((e) => [e, { seenAt: now }]));
1742
1766
  s.lastDeliveredAt = now;
1743
- s.deliveredSeq = Math.max(s.deliveredSeq, ...entries.map((e) => e.seq));
1767
+ s.deliveredSeq = Math.max(s.deliveredSeq, ...entries.map((e) => Number(e.seq)));
1744
1768
  }
1745
1769
  /**
1746
1770
  * The agent acknowledges every note up to `cursor`. A cursor past the last note in the
@@ -1775,10 +1799,9 @@ var init_sessions = __esm({
1775
1799
  * Feedback no agent has acted on (`feedback <file>`): notes not yet acknowledged,
1776
1800
  * plus acknowledged ones with no agent reply since; `all` adds the addressed ones.
1777
1801
  * Unless `peek`, reading acknowledges them, so a later poll does not deliver them again.
1778
- * @returns {{ entries: { id: string, at: number, author?: string, items: any[], status: string }[], fresh: any[] }}
1779
1802
  */
1780
1803
  feedback(s, { all = false, peek = false } = {}) {
1781
- const picked = s.transcript.filter((e) => deliverable(e) && (e.items || []).length && (all || e.seq > s.ackedSeq || !e.doneAt));
1804
+ const picked = s.transcript.filter((e) => deliverable(e) && (e.items || []).length > 0 && (all || e.seq > s.ackedSeq || !e.doneAt));
1782
1805
  const entries = picked.map((e) => ({
1783
1806
  id: e.id,
1784
1807
  at: e.at,
@@ -1845,10 +1868,10 @@ var init_sessions = __esm({
1845
1868
  }
1846
1869
  importSession(d) {
1847
1870
  const now = Date.now();
1848
- const known = !!this.q.hasNotes.get(d.id);
1871
+ const known2 = !!this.q.hasNotes.get(d.id);
1849
1872
  this.q.ensure.run(d.id, d.file, projectOf(d.file), d.title || path7.basename(d.file), d.createdAt || now, d.updatedAt || now);
1850
1873
  const s = hydrate(this.q.session.get(d.id));
1851
- if (!known) {
1874
+ if (!known2) {
1852
1875
  Object.assign(s, {
1853
1876
  title: d.title || s.title,
1854
1877
  updatedAt: Math.max(s.updatedAt, d.updatedAt || 0),
@@ -1859,10 +1882,10 @@ var init_sessions = __esm({
1859
1882
  }
1860
1883
  const transcript = Array.isArray(d.transcript) ? d.transcript : [];
1861
1884
  const pending = new Set(Array.isArray(d.pending) ? d.pending : []);
1862
- const users = transcript.filter((e) => e && e.role === "user" && e.id && !this.q.note.get(e.id));
1885
+ const users = transcript.filter((e) => Boolean(e && e.role === "user" && e.id && !this.q.note.get(e.id)));
1863
1886
  const open2 = (e) => !e.held && !e.discarded;
1864
1887
  const delivered = users.filter((e) => open2(e) && !pending.has(e.id));
1865
- const waiting = [...pending].map((id) => users.find((e) => e.id === id)).filter((e) => e && open2(e));
1888
+ const waiting = [...pending].map((id) => users.find((e) => e.id === id)).filter((e) => Boolean(e && open2(e)));
1866
1889
  const rest = users.filter((e) => !open2(e));
1867
1890
  const save = (e, seq) => {
1868
1891
  const entry = { ...e, items: Array.isArray(e.items) ? e.items : [], seq };
@@ -1871,7 +1894,7 @@ var init_sessions = __esm({
1871
1894
  };
1872
1895
  const saved = [];
1873
1896
  for (const e of delivered) saved.push(save(e, this.nextSeq()));
1874
- if (delivered.length) s.ackedSeq = Math.max(s.ackedSeq, saved[saved.length - 1].seq);
1897
+ if (delivered.length) s.ackedSeq = Math.max(s.ackedSeq, Number(saved[saved.length - 1].seq));
1875
1898
  for (const e of waiting) saved.push(save(e, this.nextSeq()));
1876
1899
  for (const e of rest) saved.push(save(e, null));
1877
1900
  for (const e of saved) this.indexNote(s, e);
@@ -1884,7 +1907,7 @@ var init_sessions = __esm({
1884
1907
  }
1885
1908
  });
1886
1909
 
1887
- // src/cli/export.js
1910
+ // src/cli/export.ts
1888
1911
  import fs9 from "node:fs";
1889
1912
  import path8 from "node:path";
1890
1913
  function inlineScript(code) {
@@ -1911,14 +1934,15 @@ function exportHtml(html) {
1911
1934
  return { html: out, notices };
1912
1935
  }
1913
1936
  var init_export = __esm({
1914
- "src/cli/export.js"() {
1937
+ "src/cli/export.ts"() {
1938
+ "use strict";
1915
1939
  init_artifact();
1916
1940
  init_paths();
1917
1941
  init_packs();
1918
1942
  }
1919
1943
  });
1920
1944
 
1921
- // src/cli/install.js
1945
+ // src/cli/install.ts
1922
1946
  var install_exports = {};
1923
1947
  __export(install_exports, {
1924
1948
  TRUSTED_SCOPE: () => TRUSTED_SCOPE,
@@ -1929,8 +1953,8 @@ __export(install_exports, {
1929
1953
  removePack: () => removePack,
1930
1954
  untar: () => untar
1931
1955
  });
1932
- import fs10 from "node:fs";
1933
- import path9 from "node:path";
1956
+ import fs11 from "node:fs";
1957
+ import path10 from "node:path";
1934
1958
  import zlib from "node:zlib";
1935
1959
  import crypto4 from "node:crypto";
1936
1960
  function registryUrl() {
@@ -2011,7 +2035,8 @@ async function fetchBuffer(url2, accept) {
2011
2035
  let res;
2012
2036
  try {
2013
2037
  res = await fetch(url2, { headers: accept ? { accept } : {}, redirect: "follow" });
2014
- } catch (err) {
2038
+ } catch (caught) {
2039
+ const err = caught;
2015
2040
  const e = Object.assign(new Error(`could not reach ${url2} (${err.cause?.code || err.message})`), { network: true });
2016
2041
  throw e;
2017
2042
  }
@@ -2033,7 +2058,7 @@ async function fromRegistry(spec) {
2033
2058
  try {
2034
2059
  meta3 = JSON.parse((await fetchBuffer(metaUrl, "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8")).toString("utf8"));
2035
2060
  } catch (err) {
2036
- if (err.status === 404) throw new Error(`no package ${name} on ${registryUrl()}`);
2061
+ if (err instanceof Error && "status" in err && err.status === 404) throw new Error(`no package ${name} on ${registryUrl()}`);
2037
2062
  throw err;
2038
2063
  }
2039
2064
  const version2 = pickVersion(meta3, range);
@@ -2056,7 +2081,7 @@ function readTarball(tgz, label) {
2056
2081
  try {
2057
2082
  files = untar(zlib.gunzipSync(tgz));
2058
2083
  } catch (err) {
2059
- throw new Error(`${label}: not a gzipped tarball (${err.message})`);
2084
+ throw new Error(`${label}: not a gzipped tarball (${err instanceof Error ? err.message : void 0})`);
2060
2085
  }
2061
2086
  const packJs = files.get("dist/pack.js");
2062
2087
  const catalog = files.get("dist/catalog.json");
@@ -2066,15 +2091,15 @@ function readTarball(tgz, label) {
2066
2091
  return { packJs, catalog, packageJson: files.get("package.json") || null };
2067
2092
  }
2068
2093
  function fromTarball(file3) {
2069
- const abs = path9.resolve(file3.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2070
- if (!fs10.existsSync(abs)) throw new Error(`no such file: ${abs}`);
2071
- return { ...readTarball(fs10.readFileSync(abs), path9.basename(abs)), source: "tarball", resolved: abs };
2094
+ const abs = path10.resolve(file3.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2095
+ if (!fs11.existsSync(abs)) throw new Error(`no such file: ${abs}`);
2096
+ return { ...readTarball(fs11.readFileSync(abs), path10.basename(abs)), source: "tarball", resolved: abs };
2072
2097
  }
2073
2098
  function fromFolder(dir) {
2074
- const abs = path9.resolve(dir.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2075
- if (!fs10.existsSync(abs)) throw new Error(`no such folder: ${abs}`);
2076
- if (fs10.statSync(abs).isFile()) return fromTarball(abs);
2077
- const read = (rel) => fs10.existsSync(path9.join(abs, rel)) ? fs10.readFileSync(path9.join(abs, rel)) : null;
2099
+ const abs = path10.resolve(dir.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2100
+ if (!fs11.existsSync(abs)) throw new Error(`no such folder: ${abs}`);
2101
+ if (fs11.statSync(abs).isFile()) return fromTarball(abs);
2102
+ const read = (rel) => fs11.existsSync(path10.join(abs, rel)) ? fs11.readFileSync(path10.join(abs, rel)) : null;
2078
2103
  const packJs = read("dist/pack.js");
2079
2104
  const catalog = read("dist/catalog.json");
2080
2105
  if (!packJs || !catalog) throw new Error(`${abs} has no dist/pack.js and dist/catalog.json: run \`austere-axi components build ${dir}\` first`);
@@ -2114,10 +2139,10 @@ async function addPack(spec, { trust = false } = {}) {
2114
2139
  }
2115
2140
  const got = local ? fromFolder(spec) : await fromRegistry(spec);
2116
2141
  if (got.source === "tarball" && !trust) {
2117
- const peek = parseCatalog(got.catalog, path9.basename(got.resolved));
2142
+ const peek = parseCatalog(got.catalog, path10.basename(got.resolved));
2118
2143
  throw Object.assign(
2119
2144
  new Error(
2120
- `${path9.basename(got.resolved)} (${peek.pack}) comes from a file, not the registry: its code would run inside your review pages. Rerun with --trust once you have looked at it.`
2145
+ `${path10.basename(got.resolved)} (${peek.pack}) comes from a file, not the registry: its code would run inside your review pages. Rerun with --trust once you have looked at it.`
2121
2146
  ),
2122
2147
  { code: "untrusted" }
2123
2148
  );
@@ -2142,14 +2167,14 @@ async function addPack(spec, { trust = false } = {}) {
2142
2167
  }
2143
2168
  const lock = readLock();
2144
2169
  const previous = lock.packs[cat.pack];
2145
- const dir = path9.join(packsDir(), packDirName(cat.pack, cat.version));
2146
- fs10.rmSync(dir, { recursive: true, force: true });
2147
- fs10.mkdirSync(dir, { recursive: true });
2148
- fs10.writeFileSync(path9.join(dir, "pack.js"), got.packJs);
2149
- fs10.writeFileSync(path9.join(dir, "catalog.json"), got.catalog);
2150
- if (got.packageJson) fs10.writeFileSync(path9.join(dir, "package.json"), got.packageJson);
2170
+ const dir = path10.join(packsDir(), packDirName(cat.pack, cat.version));
2171
+ fs11.rmSync(dir, { recursive: true, force: true });
2172
+ fs11.mkdirSync(dir, { recursive: true });
2173
+ fs11.writeFileSync(path10.join(dir, "pack.js"), got.packJs);
2174
+ fs11.writeFileSync(path10.join(dir, "catalog.json"), got.catalog);
2175
+ if (got.packageJson) fs11.writeFileSync(path10.join(dir, "package.json"), got.packageJson);
2151
2176
  if (previous && previous.version !== cat.version) {
2152
- fs10.rmSync(path9.join(packsDir(), packDirName(cat.pack, previous.version)), { recursive: true, force: true });
2177
+ fs11.rmSync(path10.join(packsDir(), packDirName(cat.pack, previous.version)), { recursive: true, force: true });
2153
2178
  }
2154
2179
  lock.packs[cat.pack] = {
2155
2180
  version: cat.version,
@@ -2169,12 +2194,13 @@ function removePack(name) {
2169
2194
  if (!entry) return null;
2170
2195
  delete lock.packs[name];
2171
2196
  writeLock(lock);
2172
- fs10.rmSync(path9.join(packsDir(), packDirName(name, entry.version)), { recursive: true, force: true });
2197
+ fs11.rmSync(path10.join(packsDir(), packDirName(name, entry.version)), { recursive: true, force: true });
2173
2198
  return { name, version: entry.version };
2174
2199
  }
2175
2200
  var MAX_TARBALL, MAX_PACK_JS;
2176
2201
  var init_install = __esm({
2177
- "src/cli/install.js"() {
2202
+ "src/cli/install.ts"() {
2203
+ "use strict";
2178
2204
  init_packs();
2179
2205
  init_packs_meta();
2180
2206
  init_catalog();
@@ -2183,14 +2209,14 @@ var init_install = __esm({
2183
2209
  }
2184
2210
  });
2185
2211
 
2186
- // src/cli/pack-dev.js
2212
+ // src/cli/pack-dev.ts
2187
2213
  var pack_dev_exports = {};
2188
2214
  __export(pack_dev_exports, {
2189
2215
  buildPackDir: () => buildPackDir,
2190
2216
  newPack: () => newPack
2191
2217
  });
2192
- import fs11 from "node:fs";
2193
- import path10 from "node:path";
2218
+ import fs12 from "node:fs";
2219
+ import path11 from "node:path";
2194
2220
  import { createRequire } from "node:module";
2195
2221
  import { pathToFileURL } from "node:url";
2196
2222
  function packageJson(name) {
@@ -2227,9 +2253,9 @@ Publish with \`npm publish\` after \`npm run build\`. Others install it with
2227
2253
  }
2228
2254
  function newPack(dir, { name } = {}) {
2229
2255
  if (!dir) throw new Error("new needs a folder: austere-axi components new austere-pack-milestones");
2230
- const abs = path10.resolve(dir);
2231
- if (fs11.existsSync(abs) && fs11.readdirSync(abs).length) throw new Error(`${abs} is not empty`);
2232
- const pkgName = name || path10.basename(abs).toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
2256
+ const abs = path11.resolve(dir);
2257
+ if (fs12.existsSync(abs) && fs12.readdirSync(abs).length) throw new Error(`${abs} is not empty`);
2258
+ const pkgName = name || path11.basename(abs).toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
2233
2259
  if (!isValidPackName(pkgName)) throw new Error(`${JSON.stringify(pkgName)} is not an npm package name; pass --name`);
2234
2260
  const files = {
2235
2261
  "package.json": JSON.stringify(packageJson(pkgName), null, 2) + "\n",
@@ -2238,24 +2264,25 @@ function newPack(dir, { name } = {}) {
2238
2264
  ...TEMPLATE
2239
2265
  };
2240
2266
  for (const [rel, text] of Object.entries(files)) {
2241
- fs11.mkdirSync(path10.dirname(path10.join(abs, rel)), { recursive: true });
2242
- fs11.writeFileSync(path10.join(abs, rel), text);
2267
+ fs12.mkdirSync(path11.dirname(path11.join(abs, rel)), { recursive: true });
2268
+ fs12.writeFileSync(path11.join(abs, rel), text);
2243
2269
  }
2244
2270
  return { dir: abs, name: pkgName, files: Object.keys(files) };
2245
2271
  }
2246
2272
  async function findEsbuild(dir) {
2247
- for (const base of [path10.join(dir, "package.json"), path10.join(process.cwd(), "package.json"), import.meta.url]) {
2273
+ for (const base of [path11.join(dir, "package.json"), path11.join(process.cwd(), "package.json"), import.meta.url]) {
2248
2274
  try {
2249
2275
  const resolved = createRequire(base).resolve("esbuild");
2250
- return await import(pathToFileURL(resolved).href);
2276
+ const esbuild = await import(pathToFileURL(resolved).href);
2277
+ return esbuild;
2251
2278
  } catch {
2252
2279
  }
2253
2280
  }
2254
2281
  return null;
2255
2282
  }
2256
2283
  async function buildPackDir(dir = ".") {
2257
- const abs = path10.resolve(dir);
2258
- if (!fs11.existsSync(path10.join(abs, "package.json"))) throw new Error(`${abs} has no package.json (\`austere-axi components new <dir>\` makes a pack)`);
2284
+ const abs = path11.resolve(dir);
2285
+ if (!fs12.existsSync(path11.join(abs, "package.json"))) throw new Error(`${abs} has no package.json (\`austere-axi components new <dir>\` makes a pack)`);
2259
2286
  const esbuild = await findEsbuild(abs);
2260
2287
  if (!esbuild) throw new Error(`components build needs esbuild: run \`npm install -D esbuild\` in ${abs}`);
2261
2288
  process.env.OPENUI_TELEMETRY_DISABLED ||= "1";
@@ -2276,7 +2303,8 @@ async function buildPackDir(dir = ".") {
2276
2303
  }
2277
2304
  var TEMPLATE;
2278
2305
  var init_pack_dev = __esm({
2279
- "src/cli/pack-dev.js"() {
2306
+ "src/cli/pack-dev.ts"() {
2307
+ "use strict";
2280
2308
  init_paths();
2281
2309
  init_packs();
2282
2310
  init_packs_meta();
@@ -2376,9 +2404,9 @@ export default austereComponent({
2376
2404
  }
2377
2405
  });
2378
2406
 
2379
- // src/cli/net.js
2407
+ // src/cli/net.ts
2380
2408
  import { execFile } from "node:child_process";
2381
- import fs12 from "node:fs";
2409
+ import fs13 from "node:fs";
2382
2410
  function run(bin, args) {
2383
2411
  return new Promise((resolve) => {
2384
2412
  execFile(bin, args, { timeout: 2500, windowsHide: true }, (err, stdout) => resolve(err ? null : stdout));
@@ -2389,7 +2417,7 @@ async function detectTailscale({ fresh = false } = {}) {
2389
2417
  if (!fresh && Date.now() - cache.at < 3e4) return cache.value;
2390
2418
  let value = null;
2391
2419
  for (const bin of CANDIDATES) {
2392
- if (bin.startsWith("/") && !fs12.existsSync(bin)) continue;
2420
+ if (bin.startsWith("/") && !fs13.existsSync(bin)) continue;
2393
2421
  const out = await run(bin, ["status", "--json"]);
2394
2422
  if (!out) continue;
2395
2423
  try {
@@ -2436,7 +2464,8 @@ function isLoopback(addr) {
2436
2464
  }
2437
2465
  var CANDIDATES, cache;
2438
2466
  var init_net = __esm({
2439
- "src/cli/net.js"() {
2467
+ "src/cli/net.ts"() {
2468
+ "use strict";
2440
2469
  CANDIDATES = [
2441
2470
  "tailscale",
2442
2471
  "/usr/bin/tailscale",
@@ -2448,13 +2477,14 @@ var init_net = __esm({
2448
2477
  }
2449
2478
  });
2450
2479
 
2451
- // src/cli/items.js
2480
+ // src/cli/items.ts
2452
2481
  function defined(o) {
2453
- return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
2482
+ const out = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
2483
+ return out;
2454
2484
  }
2455
2485
  function detailOf(d) {
2456
- if (!d || typeof d !== "object") return void 0;
2457
- const out = defined({ part: str2(d.part), row: str2(d.row), column: str2(d.column), rowIndex: Number.isInteger(d.rowIndex) ? d.rowIndex : void 0 });
2486
+ if (!isRecord(d)) return void 0;
2487
+ const out = defined({ part: str2(d.part), row: str2(d.row), column: str2(d.column), rowIndex: typeof d.rowIndex === "number" && Number.isInteger(d.rowIndex) ? d.rowIndex : void 0 });
2458
2488
  return Object.keys(out).length ? out : void 0;
2459
2489
  }
2460
2490
  function reviewerItem(x) {
@@ -2470,20 +2500,22 @@ function reviewerItem(x) {
2470
2500
  detail: detailOf(x.detail)
2471
2501
  });
2472
2502
  }
2473
- var INVISIBLE, str2, ID, COMPONENT, match;
2503
+ var INVISIBLE, str2, ID, COMPONENT, match, isRecord;
2474
2504
  var init_items = __esm({
2475
- "src/cli/items.js"() {
2505
+ "src/cli/items.ts"() {
2506
+ "use strict";
2476
2507
  INVISIBLE = /[\u{E0000}-\u{E007F}\u200B\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu;
2477
2508
  str2 = (v) => typeof v === "string" ? v.replace(INVISIBLE, "") : void 0;
2478
2509
  ID = /^[A-Za-z_$][\w$]{0,99}$/;
2479
2510
  COMPONENT = /^[A-Za-z_][\w.-]{0,99}$/;
2480
2511
  match = (re2, v) => typeof v === "string" && re2.test(v) ? v : void 0;
2512
+ isRecord = (v) => !!v && typeof v === "object";
2481
2513
  }
2482
2514
  });
2483
2515
 
2484
- // src/cli/hub-page.js
2485
- import os2 from "node:os";
2486
- import path11 from "node:path";
2516
+ // src/cli/hub-page.ts
2517
+ import os3 from "node:os";
2518
+ import path12 from "node:path";
2487
2519
  function day(ms) {
2488
2520
  const d = new Date(ms);
2489
2521
  const today = /* @__PURE__ */ new Date();
@@ -2497,8 +2529,8 @@ function time3(ms) {
2497
2529
  return new Date(ms).toLocaleTimeString("en", { hour: "2-digit", minute: "2-digit", hour12: false });
2498
2530
  }
2499
2531
  function tilde(p) {
2500
- const home = os2.homedir();
2501
- return p === home || p.startsWith(home + path11.sep) ? "~" + p.slice(home.length) : p;
2532
+ const home = os3.homedir();
2533
+ return p === home || p.startsWith(home + path12.sep) ? "~" + p.slice(home.length) : p;
2502
2534
  }
2503
2535
  function counts(a) {
2504
2536
  const waiting = a.open + a.received;
@@ -2519,26 +2551,28 @@ function state(a) {
2519
2551
  function hubPage(rows, { version: version2, q: q2, project, stats }) {
2520
2552
  const byProject = /* @__PURE__ */ new Map();
2521
2553
  for (const r of rows) {
2522
- if (!byProject.has(r.project)) byProject.set(r.project, []);
2523
- byProject.get(r.project).push(r);
2554
+ let list = byProject.get(r.project);
2555
+ if (!list) byProject.set(r.project, list = []);
2556
+ list.push(r);
2524
2557
  }
2525
2558
  const sections = [...byProject.entries()].map(([proj, list]) => {
2526
2559
  const days = /* @__PURE__ */ new Map();
2527
2560
  for (const r of list) {
2528
2561
  const k = day(r.updated_at);
2529
- if (!days.has(k)) days.set(k, []);
2530
- days.get(k).push(r);
2562
+ let items = days.get(k);
2563
+ if (!items) days.set(k, items = []);
2564
+ items.push(r);
2531
2565
  }
2532
2566
  const body = [...days.entries()].map(
2533
2567
  ([d, items]) => `<h3>${escapeHtml(d)}</h3><ul>${items.map(
2534
2568
  (a) => `<li><a href="/hub/open/${a.id}" title="${escapeHtml(a.file)}">
2535
- <strong>${escapeHtml(a.title || path11.basename(a.file))}</strong>
2536
- <span class="meta">${escapeHtml(path11.relative(proj, a.file) || path11.basename(a.file))} \xB7 ${time3(a.updated_at)} \xB7 ${state(a)}${counts(a) ? ` \xB7 ${counts(a)}` : ""}</span>
2569
+ <strong>${escapeHtml(a.title || path12.basename(a.file))}</strong>
2570
+ <span class="meta">${escapeHtml(path12.relative(proj, a.file) || path12.basename(a.file))} \xB7 ${time3(a.updated_at)} \xB7 ${state(a)}${counts(a) ? ` \xB7 ${counts(a)}` : ""}</span>
2537
2571
  </a></li>`
2538
2572
  ).join("")}</ul>`
2539
2573
  ).join("");
2540
2574
  const unanswered = list.reduce((n, a) => n + a.open + a.received, 0);
2541
- return `<section><h2><a href="/?project=${encodeURIComponent(proj)}">${escapeHtml(path11.basename(proj) || proj)}</a> <small title="${escapeHtml(proj)}">${escapeHtml(
2575
+ return `<section><h2><a href="/?project=${encodeURIComponent(proj)}">${escapeHtml(path12.basename(proj) || proj)}</a> <small title="${escapeHtml(proj)}">${escapeHtml(
2542
2576
  shortPath(proj)
2543
2577
  )}</small>${unanswered ? ` <b class="open">${unanswered} unanswered</b>` : ""}</h2>${body}</section>`;
2544
2578
  }).join("");
@@ -2561,7 +2595,7 @@ function notesPage(rows, { version: version2, q: q2, project, status, author = "
2561
2595
  const when = `${day(n.at)} ${time3(n.at)}`;
2562
2596
  return `<li class="n"><a href="/hub/open/${escapeHtml(n.artifact_id)}" title="${escapeHtml(n.file)}">
2563
2597
  <p class="note">${escapeHtml(n.text || "(no text)")}</p>
2564
- <span class="note-t">${n.author ? `<b class="by">${escapeHtml(n.author)}</b> \xB7 ` : ""}${n.target ? `on ${escapeHtml(n.target)} \xB7 ` : ""}<strong>${escapeHtml(n.title || path11.basename(n.file))}</strong> \xB7 ${escapeHtml(path11.basename(n.project) || n.project)} \xB7 ${escapeHtml(when)} \xB7 <span class="st ${waiting ? "waiting" : "done"}">${STATUS_LABEL[n.status] || escapeHtml(n.status)}</span></span>
2598
+ <span class="note-t">${n.author ? `<b class="by">${escapeHtml(n.author)}</b> \xB7 ` : ""}${n.target ? `on ${escapeHtml(n.target)} \xB7 ` : ""}<strong>${escapeHtml(n.title || path12.basename(n.file))}</strong> \xB7 ${escapeHtml(path12.basename(n.project) || n.project)} \xB7 ${escapeHtml(when)} \xB7 <span class="st ${waiting ? "waiting" : "done"}">${STATUS_LABEL[n.status] || escapeHtml(n.status)}</span></span>
2565
2599
  </a></li>`;
2566
2600
  }).join("");
2567
2601
  const opt = (v, label) => `<option value="${v}"${status === v ? " selected" : ""}>${label}</option>`;
@@ -2580,7 +2614,8 @@ ${filter}${items ? `<ul>${items}</ul>` : `<p class="empty">${empty}</p>`}
2580
2614
  }
2581
2615
  var STYLE, STATUS_LABEL;
2582
2616
  var init_hub_page = __esm({
2583
- "src/cli/hub-page.js"() {
2617
+ "src/cli/hub-page.ts"() {
2618
+ "use strict";
2584
2619
  init_artifact();
2585
2620
  STYLE = `body{margin:0;background:#0b0b0d;color:#e8e8ec;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif}
2586
2621
  main{max-width:860px;margin:0 auto;padding:32px 16px 64px}
@@ -2609,25 +2644,30 @@ b.by{color:#e8e8ec}
2609
2644
  }
2610
2645
  });
2611
2646
 
2612
- // src/cli/server.js
2647
+ // src/cli/server.ts
2613
2648
  var server_exports = {};
2614
2649
  __export(server_exports, {
2615
2650
  persistEnv: () => persistEnv,
2616
2651
  runServer: () => runServer
2617
2652
  });
2618
- import fs13 from "node:fs";
2619
- import http2 from "node:http";
2620
- import path12 from "node:path";
2653
+ import fs14 from "node:fs";
2654
+ import http3 from "node:http";
2655
+ import path13 from "node:path";
2621
2656
  import crypto5 from "node:crypto";
2622
2657
  function persistEnv() {
2623
2658
  return /^(1|true|yes)$/i.test(process.env.AUSTERE_AXI_PERSIST || "");
2624
2659
  }
2625
- async function runServer({ port: port2, verbose = false, alsoListen = [], persist = false }) {
2660
+ async function runServer({
2661
+ port: port2,
2662
+ verbose = false,
2663
+ alsoListen = [],
2664
+ persist = false
2665
+ }) {
2626
2666
  const log = (...a) => {
2627
2667
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${a.join(" ")}`;
2628
2668
  if (verbose) process.stderr.write(line + "\n");
2629
2669
  try {
2630
- fs13.appendFileSync(path12.join(stateDir(), "server.log"), line + "\n", { mode: 384 });
2670
+ fs14.appendFileSync(path13.join(stateDir(), "server.log"), line + "\n", { mode: 384 });
2631
2671
  } catch {
2632
2672
  }
2633
2673
  };
@@ -2652,6 +2692,7 @@ async function runServer({ port: port2, verbose = false, alsoListen = [], persis
2652
2692
  const graceTimers = /* @__PURE__ */ new Map();
2653
2693
  const byeAt = /* @__PURE__ */ new Map();
2654
2694
  const browserSeen = /* @__PURE__ */ new Set();
2695
+ const renderReported = /* @__PURE__ */ new Map();
2655
2696
  let tailnet = null;
2656
2697
  const linkBase = linkUrl();
2657
2698
  let lastActivity = Date.now();
@@ -2808,7 +2849,7 @@ data: ${JSON.stringify({ state: "revoked" })}
2808
2849
  }
2809
2850
  function readFileSafe(file3) {
2810
2851
  try {
2811
- return fs13.readFileSync(file3, "utf8");
2852
+ return fs14.readFileSync(file3, "utf8");
2812
2853
  } catch {
2813
2854
  return null;
2814
2855
  }
@@ -2817,17 +2858,17 @@ data: ${JSON.stringify({ state: "revoked" })}
2817
2858
  if (watchers.has(s.id)) return;
2818
2859
  const html = readFileSafe(s.file);
2819
2860
  const art = html == null ? null : readArtifact(html);
2820
- const w = { html, program: art?.program ?? null, shell: art?.shell ?? null, timer: null };
2861
+ const w = { html, program: art?.program ?? null, shell: art?.shell ?? null, timer: void 0 };
2821
2862
  watchers.set(s.id, w);
2822
2863
  if (art?.title) s.title = art.title;
2823
- fs13.watchFile(s.file, { interval: 300 }, () => {
2864
+ fs14.watchFile(s.file, { interval: 300 }, () => {
2824
2865
  clearTimeout(w.timer);
2825
2866
  w.timer = setTimeout(() => onFileChange(s, w), 120);
2826
2867
  });
2827
2868
  }
2828
2869
  function unwatch(id) {
2829
2870
  const s = store.get(id);
2830
- if (s) fs13.unwatchFile(s.file);
2871
+ if (s) fs14.unwatchFile(s.file);
2831
2872
  watchers.delete(id);
2832
2873
  }
2833
2874
  function onFileChange(s, w) {
@@ -2841,7 +2882,7 @@ data: ${JSON.stringify({ state: "revoked" })}
2841
2882
  s.revision += 1;
2842
2883
  if (art.title) s.title = art.title;
2843
2884
  log("change", s.id, `rev=${s.revision}`, shellChanged ? "shell" : "program");
2844
- const touched = s.transcript.filter((e) => e.role === "user" && e.seenAt && !e.doneAt && !e.workAt);
2885
+ const touched = s.transcript.filter((e) => e.role === "user" && Boolean(e.seenAt) && !e.doneAt && !e.workAt);
2845
2886
  if (touched.length) {
2846
2887
  store.markWorking(s, touched);
2847
2888
  broadcastTranscript(s, touched);
@@ -3004,7 +3045,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3004
3045
  const file3 = typeof body.file === "string" ? body.file : "";
3005
3046
  if (!file3) return send(res, 400, { error: "file is required" });
3006
3047
  if (route === "share") {
3007
- if (!fs13.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3048
+ if (!fs14.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3008
3049
  await ensureTailnetListener();
3009
3050
  if (!remoteBase()) {
3010
3051
  return send(res, 409, {
@@ -3024,7 +3065,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3024
3065
  return send(res, 200, { id: inviteId(inv), name: inv.name, auto: inv.auto, link: inviteLink(inv), file: inv.file });
3025
3066
  }
3026
3067
  if (route === "open") {
3027
- if (!fs13.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3068
+ if (!fs14.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3028
3069
  await ensureTailnetListener();
3029
3070
  const s2 = store.ensure(file3);
3030
3071
  if (s2.closedAt && !body.reopen) {
@@ -3157,7 +3198,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3157
3198
  }
3158
3199
  const file3 = installedPackFile(spec.name, spec.version);
3159
3200
  if (!file3) return send(res, 404, `pack ${spec.name}@${spec.version} is not installed (austere-axi components add ${spec.name}@${spec.version})`);
3160
- fs13.readFile(file3, (err, buf) => {
3201
+ fs14.readFile(file3, (err, buf) => {
3161
3202
  if (err) return send(res, 404, "not found");
3162
3203
  const actual = `sha384-${crypto5.createHash("sha384").update(buf).digest("base64")}`;
3163
3204
  const locked = readLock().packs[spec.name]?.integrity;
@@ -3181,13 +3222,13 @@ data: ${JSON.stringify({ state: "revoked" })}
3181
3222
  }
3182
3223
  function serveAsset(res, rel, url2) {
3183
3224
  if (rel.startsWith("installed-packs/")) return serveInstalledPack(res, rel, url2);
3184
- const clean = path12.normalize(rel).replace(/^(\.\.[/\\])+/, "");
3185
- const file3 = path12.join(ASSET_DIR, clean);
3186
- if (!file3.startsWith(ASSET_DIR + path12.sep)) return send(res, 404, "not found");
3187
- fs13.stat(file3, (err, st2) => {
3225
+ const clean = path13.normalize(rel).replace(/^(\.\.[/\\])+/, "");
3226
+ const file3 = path13.join(ASSET_DIR, clean);
3227
+ if (!file3.startsWith(ASSET_DIR + path13.sep)) return send(res, 404, "not found");
3228
+ fs14.stat(file3, (err, st2) => {
3188
3229
  if (err || !st2.isFile()) return send(res, 404, "not found");
3189
3230
  streamFile(res, file3, {
3190
- "content-type": MIME[path12.extname(file3)] || "application/octet-stream",
3231
+ "content-type": MIME[path13.extname(file3)] || "application/octet-stream",
3191
3232
  "content-length": st2.size,
3192
3233
  "cache-control": "no-cache",
3193
3234
  "x-content-type-options": "nosniff"
@@ -3196,7 +3237,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3196
3237
  }
3197
3238
  const ARTIFACT_CSP = "sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms";
3198
3239
  function streamFile(res, file3, headers) {
3199
- const rs = fs13.createReadStream(file3);
3240
+ const rs = fs14.createReadStream(file3);
3200
3241
  rs.once("open", () => {
3201
3242
  res.writeHead(200, headers);
3202
3243
  rs.pipe(res);
@@ -3209,8 +3250,8 @@ data: ${JSON.stringify({ state: "revoked" })}
3209
3250
  res.once("close", () => rs.destroy());
3210
3251
  }
3211
3252
  function serveArtifact(req, res, s, rest) {
3212
- const dir = path12.dirname(s.file);
3213
- const base = path12.basename(s.file);
3253
+ const dir = path13.dirname(s.file);
3254
+ const base = path13.basename(s.file);
3214
3255
  if (rest === base || rest === "") {
3215
3256
  const html = readFileSafe(s.file);
3216
3257
  if (html == null) return send(res, 404, "artifact file is missing");
@@ -3224,19 +3265,19 @@ data: ${JSON.stringify({ state: "revoked" })}
3224
3265
  return send(res, 400, "bad path");
3225
3266
  }
3226
3267
  if (decoded.includes("\0")) return send(res, 404, "not found");
3227
- const file3 = path12.resolve(dir, decoded);
3228
- if (!file3.startsWith(dir + path12.sep) || decoded.split(/[/\\]/).some((p) => p.startsWith("."))) {
3268
+ const file3 = path13.resolve(dir, decoded);
3269
+ if (!file3.startsWith(dir + path13.sep) || decoded.split(/[/\\]/).some((p) => p.startsWith("."))) {
3229
3270
  return send(res, 404, "not found");
3230
3271
  }
3231
- fs13.realpath(dir, (e1, realDir) => {
3272
+ fs14.realpath(dir, (e1, realDir) => {
3232
3273
  if (e1) return send(res, 404, "not found");
3233
- fs13.realpath(file3, (e2, real) => {
3234
- if (e2 || !real.startsWith(realDir + path12.sep)) return send(res, 404, "not found");
3235
- if (path12.relative(realDir, real).split(path12.sep).some((p) => p.startsWith("."))) return send(res, 404, "not found");
3236
- fs13.stat(real, (err, st2) => {
3274
+ fs14.realpath(file3, (e2, real) => {
3275
+ if (e2 || !real.startsWith(realDir + path13.sep)) return send(res, 404, "not found");
3276
+ if (path13.relative(realDir, real).split(path13.sep).some((p) => p.startsWith("."))) return send(res, 404, "not found");
3277
+ fs14.stat(real, (err, st2) => {
3237
3278
  if (err || !st2.isFile() || st2.size > 64 * 1024 * 1024) return send(res, 404, "not found");
3238
3279
  streamFile(res, real, {
3239
- "content-type": MIME[path12.extname(real).toLowerCase()] || "application/octet-stream",
3280
+ "content-type": MIME[path13.extname(real).toLowerCase()] || "application/octet-stream",
3240
3281
  "content-length": st2.size,
3241
3282
  "cache-control": "no-cache",
3242
3283
  "x-content-type-options": "nosniff",
@@ -3252,8 +3293,8 @@ data: ${JSON.stringify({ state: "revoked" })}
3252
3293
  title: s.title,
3253
3294
  file: viewer.role === "owner" ? s.file : "",
3254
3295
  viewer: { role: viewer.role, name: viewer.name, auto: viewer.role === "reviewer" ? viewer.auto : void 0 },
3255
- name: path12.basename(s.file),
3256
- artifactUrl: `/s/${s.id}/a/${encodeURIComponent(path12.basename(s.file))}`,
3296
+ name: path13.basename(s.file),
3297
+ artifactUrl: `/s/${s.id}/a/${encodeURIComponent(path13.basename(s.file))}`,
3257
3298
  version: VERSION
3258
3299
  };
3259
3300
  return `<!doctype html>
@@ -3287,7 +3328,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3287
3328
  function hubOpen(res, id) {
3288
3329
  const a = /^[a-f0-9]{12}$/.test(id) ? hub.artifact(id) : null;
3289
3330
  if (!a) return send(res, 404, "not in the hub");
3290
- if (!fs13.existsSync(a.file)) return send(res, 410, `${a.file} no longer exists`);
3331
+ if (!fs14.existsSync(a.file)) return send(res, 410, `${a.file} no longer exists`);
3291
3332
  const existed = store.byFile(a.file);
3292
3333
  const s = store.ensure(a.file);
3293
3334
  if (!existed) store.touch(s);
@@ -3303,7 +3344,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3303
3344
  <body><main><h1>austere-axi <small style="color:#777;font-weight:400">${VERSION}</small></h1><ul>${rows || "<li>No sessions yet.</li>"}</ul></main></body></html>`;
3304
3345
  }
3305
3346
  async function handle(req, res) {
3306
- const url2 = new URL(req.url, "http://x");
3347
+ const url2 = new URL(String(req.url), "http://x");
3307
3348
  const host = hostOnly(req.headers.host);
3308
3349
  if (!hostsAllowed().has(host) && process.env.AUSTERE_AXI_ALLOWED_HOSTS !== "*") {
3309
3350
  return send(res, 403, "host not allowed");
@@ -3420,8 +3461,9 @@ data: ${JSON.stringify(snapshot)}
3420
3461
 
3421
3462
  `);
3422
3463
  const client = { res, viewer: who };
3423
- if (!clients.has(s.id)) clients.set(s.id, /* @__PURE__ */ new Set());
3424
- clients.get(s.id).add(client);
3464
+ let set2 = clients.get(s.id);
3465
+ if (!set2) clients.set(s.id, set2 = /* @__PURE__ */ new Set());
3466
+ set2.add(client);
3425
3467
  browserSeen.add(s.id);
3426
3468
  clearTimeout(graceTimers.get(s.id));
3427
3469
  graceTimers.delete(s.id);
@@ -3462,6 +3504,30 @@ data: ${JSON.stringify(snapshot)}
3462
3504
  broadcastPresence(s);
3463
3505
  return send(res, 200, { ok: true, entry });
3464
3506
  }
3507
+ if (sub === "/render-errors" && req.method === "POST") {
3508
+ const body = await readBody(req);
3509
+ if (s.closedAt || body.revision !== s.revision) return send(res, 200, { ok: true, count: 0, stale: true });
3510
+ if (thirdPartyPacks(watchers.get(s.id)?.html).length) return send(res, 409, { error: "this page uses third-party packs; queue render errors by hand" });
3511
+ let seen = renderReported.get(s.id);
3512
+ if (!seen || seen.revision !== s.revision) renderReported.set(s.id, seen = { revision: s.revision, keys: /* @__PURE__ */ new Set() });
3513
+ const items = [];
3514
+ for (const e of Array.isArray(body.errors) ? body.errors.slice(0, MAX_RENDER_ERRORS) : []) {
3515
+ if (!e || typeof e !== "object" || typeof e.message !== "string" || !e.message) continue;
3516
+ const statement = typeof e.statement === "string" ? e.statement.slice(0, 200) : "";
3517
+ const message = e.message.slice(0, 1e3);
3518
+ const key3 = `${statement}
3519
+ ${message}`;
3520
+ if (seen.keys.has(key3)) continue;
3521
+ seen.keys.add(key3);
3522
+ items.push({ kind: "render-error", statement: statement || void 0, target: statement || void 0, message, note: "Reported when the page rendered." });
3523
+ }
3524
+ if (!items.length) return send(res, 200, { ok: true, count: 0 });
3525
+ const entry = store.addUserBatch(s, items, { author: RENDER_CHECK_AUTHOR });
3526
+ broadcastTranscript(s, [entry]);
3527
+ wake(s);
3528
+ broadcastPresence(s);
3529
+ return send(res, 200, { ok: true, count: items.length });
3530
+ }
3465
3531
  if ((sub === "/release" || sub === "/discard") && req.method === "POST") {
3466
3532
  const body = await readBody(req);
3467
3533
  const ids = Array.isArray(body.ids) ? body.ids.map(String) : sub === "/discard" && body.all ? null : [];
@@ -3502,7 +3568,7 @@ data: ${JSON.stringify(snapshot)}
3502
3568
  if (html == null) return send(res, 404, "artifact file is missing");
3503
3569
  try {
3504
3570
  const out = exportHtml(html);
3505
- const name = path12.basename(s.file).replace(/\.html?$/i, "") + ".export.html";
3571
+ const name = path13.basename(s.file).replace(/\.html?$/i, "") + ".export.html";
3506
3572
  return send(res, 200, out.html, {
3507
3573
  "content-type": "text/html; charset=utf-8",
3508
3574
  "content-disposition": `attachment; filename="${name.replace(/"/g, "")}"`,
@@ -3517,9 +3583,9 @@ data: ${JSON.stringify(snapshot)}
3517
3583
  function listen(host) {
3518
3584
  return new Promise((resolve) => {
3519
3585
  if (listeners.has(host)) return resolve(true);
3520
- const server = http2.createServer((req, res) => {
3586
+ const server = http3.createServer((req, res) => {
3521
3587
  handle(req, res).catch((err) => {
3522
- log("error", req.method, req.url.split("?")[0], String(err?.stack || err));
3588
+ log("error", req.method, String(req.url).split("?")[0], String(err?.stack || err));
3523
3589
  if (!res.headersSent) send(res, err.status || 500, { error: err.status ? String(err.message) : "internal error" });
3524
3590
  else res.end();
3525
3591
  });
@@ -3528,12 +3594,7 @@ data: ${JSON.stringify(snapshot)}
3528
3594
  server.requestTimeout = 0;
3529
3595
  server.headersTimeout = 6e4;
3530
3596
  server.once("error", (err) => {
3531
- log(
3532
- "listen-failed",
3533
- host,
3534
- /** @type {NodeJS.ErrnoException} */
3535
- err.code || err.message
3536
- );
3597
+ log("listen-failed", host, err.code || err.message);
3537
3598
  resolve(false);
3538
3599
  });
3539
3600
  server.listen(port2, host, () => {
@@ -3599,9 +3660,10 @@ data: ${JSON.stringify({ state: "stopped", reason })}
3599
3660
  log("started", `v${VERSION}`, `pid=${process.pid}`);
3600
3661
  return { port: port2, shutdown };
3601
3662
  }
3602
- var MIME, SHORT_GRACE_MS, LOGIN_TTL_MS, OWNER, REVIEWER_KINDS, PAGE, SSE_MAX_BUFFER, SSE_STALL_MS, SSE_HARD_MAX, SSE_MAX_PER_REVIEWER, MAX_HELD_PER_REVIEWER, SSE_MAX_PER_SESSION;
3663
+ var MIME, SHORT_GRACE_MS, LOGIN_TTL_MS, OWNER, REVIEWER_KINDS, RENDER_CHECK_AUTHOR, MAX_RENDER_ERRORS, PAGE, SSE_MAX_BUFFER, SSE_STALL_MS, SSE_HARD_MAX, SSE_MAX_PER_REVIEWER, MAX_HELD_PER_REVIEWER, SSE_MAX_PER_SESSION;
3603
3664
  var init_server = __esm({
3604
- "src/cli/server.js"() {
3665
+ "src/cli/server.ts"() {
3666
+ "use strict";
3605
3667
  init_artifact();
3606
3668
  init_export();
3607
3669
  init_net();
@@ -3644,6 +3706,8 @@ var init_server = __esm({
3644
3706
  LOGIN_TTL_MS = 6e4;
3645
3707
  OWNER = Object.freeze({ role: "owner", name: "" });
3646
3708
  REVIEWER_KINDS = /* @__PURE__ */ new Set(["annotation", "message"]);
3709
+ RENDER_CHECK_AUTHOR = "Render check";
3710
+ MAX_RENDER_ERRORS = 20;
3647
3711
  PAGE = 200;
3648
3712
  SSE_MAX_BUFFER = 1 << 20;
3649
3713
  SSE_STALL_MS = 5e3;
@@ -3654,17 +3718,17 @@ var init_server = __esm({
3654
3718
  }
3655
3719
  });
3656
3720
 
3657
- // src/cli/mcp.js
3721
+ // src/cli/mcp.ts
3658
3722
  var mcp_exports = {};
3659
3723
  __export(mcp_exports, {
3660
3724
  runMcp: () => runMcp
3661
3725
  });
3662
- import { spawn as spawn2 } from "node:child_process";
3663
- import path13 from "node:path";
3726
+ import { spawn as spawn3 } from "node:child_process";
3727
+ import path14 from "node:path";
3664
3728
  import readline from "node:readline";
3665
3729
  function runCli({ args, stdin }, signal) {
3666
3730
  return new Promise((resolve) => {
3667
- const child = spawn2(process.execPath, [CLI_ENTRY, ...args], {
3731
+ const child = spawn3(process.execPath, [CLI_ENTRY, ...args], {
3668
3732
  env: process.env,
3669
3733
  stdio: ["pipe", "pipe", "ignore"],
3670
3734
  windowsHide: true
@@ -3687,12 +3751,13 @@ error: ${err.message}
3687
3751
  }
3688
3752
  function validate2(tool, a) {
3689
3753
  for (const key2 of tool.required || []) {
3690
- if (typeof a[key2] !== "string" || !a[key2].trim()) return `${key2} is required`;
3754
+ const v = a[key2];
3755
+ if (typeof v !== "string" || !v.trim()) return `${key2} is required`;
3691
3756
  }
3692
3757
  for (const [key2, v] of Object.entries(a)) {
3693
3758
  const p = tool.properties[key2];
3694
3759
  if (!p) return `unknown argument: ${key2}`;
3695
- const ok = p.type === "array" ? Array.isArray(v) && v.every((x) => typeof x === "string") : p.type === "integer" ? Number.isInteger(v) && v >= 0 : typeof v === p.type;
3760
+ const ok = p.type === "array" ? Array.isArray(v) && v.every((x) => typeof x === "string") : p.type === "integer" ? typeof v === "number" && Number.isInteger(v) && v >= 0 : typeof v === p.type;
3696
3761
  if (!ok) return `${key2} must be ${p.type === "integer" ? "a non-negative integer" : `a ${p.type}`}`;
3697
3762
  }
3698
3763
  return null;
@@ -3702,10 +3767,11 @@ async function callTool(name, a, signal) {
3702
3767
  const bad = validate2(tool, a);
3703
3768
  if (bad) return { content: [{ type: "text", text: `status: error
3704
3769
  error: ${bad}` }], isError: true };
3705
- if (typeof a.file === "string") a = { ...a, file: path13.resolve(a.file) };
3770
+ if (typeof a.file === "string") a = { ...a, file: path14.resolve(a.file) };
3771
+ const args = a;
3706
3772
  let text = "";
3707
3773
  let isError = false;
3708
- for (const run2 of tool.run(a)) {
3774
+ for (const run2 of tool.run(args)) {
3709
3775
  const r = await runCli(run2, signal);
3710
3776
  text += r.text;
3711
3777
  if (r.code !== 0) {
@@ -3731,7 +3797,7 @@ function runMcp() {
3731
3797
  async function handle(msg) {
3732
3798
  const { id, method, params = {} } = msg;
3733
3799
  if (id === void 0 || id === null) {
3734
- if (method === "notifications/cancelled") inflight.get(params.requestId)?.abort();
3800
+ if (method === "notifications/cancelled" && params.requestId !== void 0) inflight.get(params.requestId)?.abort();
3735
3801
  return;
3736
3802
  }
3737
3803
  switch (method) {
@@ -3740,7 +3806,7 @@ function runMcp() {
3740
3806
  return send({
3741
3807
  id,
3742
3808
  result: {
3743
- protocolVersion: PROTOCOLS.includes(asked) ? asked : PROTOCOLS[0],
3809
+ protocolVersion: asked !== void 0 && PROTOCOLS.includes(asked) ? asked : PROTOCOLS[0],
3744
3810
  capabilities: { tools: { listChanged: false } },
3745
3811
  serverInfo: { name: "austere-axi", title: "austere-axi", version: VERSION },
3746
3812
  instructions: INSTRUCTIONS
@@ -3752,9 +3818,9 @@ function runMcp() {
3752
3818
  case "tools/list":
3753
3819
  return send({ id, result: { tools: listTools() } });
3754
3820
  case "tools/call": {
3755
- if (!Object.hasOwn(TOOLS, params.name)) return error62(id, -32602, `unknown tool: ${params.name}`);
3821
+ if (typeof params.name !== "string" || !Object.hasOwn(TOOLS, params.name)) return error62(id, -32602, `unknown tool: ${params.name}`);
3756
3822
  const args = params.arguments ?? {};
3757
- if (typeof args !== "object" || Array.isArray(args)) return error62(id, -32602, "arguments must be an object");
3823
+ if (!isRecord2(args) || Array.isArray(args)) return error62(id, -32602, "arguments must be an object");
3758
3824
  const ctl = new AbortController();
3759
3825
  inflight.set(id, ctl);
3760
3826
  try {
@@ -3779,12 +3845,13 @@ function runMcp() {
3779
3845
  return error62(null, -32700, "parse error");
3780
3846
  }
3781
3847
  if (Array.isArray(msg)) return error62(null, -32600, "batch not supported");
3782
- if (!msg || typeof msg !== "object" || typeof msg.method !== "string") {
3783
- if (msg && typeof msg === "object" && "id" in msg && !("result" in msg || "error" in msg)) error62(msg.id ?? null, -32600, "invalid request");
3848
+ if (!isRecord2(msg) || typeof msg.method !== "string") {
3849
+ if (isRecord2(msg) && "id" in msg && !("result" in msg || "error" in msg)) error62(msg.id ?? null, -32600, "invalid request");
3784
3850
  return;
3785
3851
  }
3786
- handle(msg).catch((err) => {
3787
- if (msg.id !== void 0 && msg.id !== null) error62(msg.id, -32603, String(err?.message || err));
3852
+ const req = msg;
3853
+ handle(req).catch((err) => {
3854
+ if (req.id !== void 0 && req.id !== null) error62(req.id, -32603, String((isRecord2(err) ? err.message : void 0) || err));
3788
3855
  });
3789
3856
  });
3790
3857
  return new Promise((resolve) => {
@@ -3794,13 +3861,15 @@ function runMcp() {
3794
3861
  });
3795
3862
  });
3796
3863
  }
3797
- var PROTOCOLS, POLL_TIMEOUT_MS, INSTRUCTIONS, file2, TOOLS;
3864
+ var isRecord2, PROTOCOLS, POLL_TIMEOUT_MS, INSTRUCTIONS, file2, TOOLS;
3798
3865
  var init_mcp = __esm({
3799
- "src/cli/mcp.js"() {
3866
+ "src/cli/mcp.ts"() {
3867
+ "use strict";
3800
3868
  init_paths();
3869
+ isRecord2 = (v) => !!v && typeof v === "object";
3801
3870
  PROTOCOLS = ["2025-11-25", "2025-06-18", "2024-11-05"];
3802
3871
  POLL_TIMEOUT_MS = 5e4;
3803
- INSTRUCTIONS = `austere-axi turns plans, comparisons, decisions, code reviews and reports into review pages the user annotates in the browser. Call \`spec\` once per session for the workflow, the file skeleton and the component reference. Results are the CLI's output: where a \`next:\` hint names an \`austere-axi <command>\`, call the tool of that name instead. Pass absolute file paths.`;
3872
+ INSTRUCTIONS = `austere-axi turns plans, comparisons, decisions, code reviews and reports into review pages the user annotates in the browser. Call \`spec\` once per session for the workflow, the file skeleton and the component reference. Results are the CLI's output: where a \`next:\` hint names an \`austere-axi <command>\`, call the tool of that name instead. Pass absolute file paths. The reviewer judges how a page looks: do not open, screenshot or inspect it yourself unless the user asks.`;
3804
3873
  file2 = { type: "string", description: "Path of the .html page. Use an absolute path; a relative one resolves against the server's working directory." };
3805
3874
  TOOLS = {
3806
3875
  spec: {
@@ -3815,9 +3884,13 @@ var init_mcp = __esm({
3815
3884
  check: {
3816
3885
  title: "Validate a page",
3817
3886
  description: "Validate the page's OpenUI Lang program without a browser: syntax, component names, required props, unresolved references, unreachable statements.",
3818
- properties: { file: file2, fix: { type: "boolean", description: 'Write the <meta name="austere-packs"> tag for the packs the page uses.' } },
3887
+ properties: {
3888
+ file: file2,
3889
+ fix: { type: "boolean", description: 'Write the <meta name="austere-packs"> tag for the packs the page uses.' },
3890
+ render: { type: "boolean", description: "Also render the page in headless Chrome and report render errors and sideways scrolling as text. Only when the user asks to verify the page." }
3891
+ },
3819
3892
  required: ["file"],
3820
- run: (a) => [{ args: ["check", ...a.fix ? ["--fix"] : [], "--", a.file] }]
3893
+ run: (a) => [{ args: ["check", ...a.fix ? ["--fix"] : [], ...a.render ? ["--render"] : [], "--", a.file] }]
3821
3894
  },
3822
3895
  open: {
3823
3896
  title: "Validate and open the review",
@@ -3878,9 +3951,9 @@ var init_mcp = __esm({
3878
3951
  }
3879
3952
  });
3880
3953
 
3881
- // src/cli/main.js
3882
- import fs14 from "node:fs";
3883
- import path14 from "node:path";
3954
+ // src/cli/main.ts
3955
+ import fs15 from "node:fs";
3956
+ import path15 from "node:path";
3884
3957
 
3885
3958
  // node_modules/@openuidev/lang-core/dist/shared-Dzgc28Wo.mjs
3886
3959
  var TELEMETRY_REQUEST_TIMEOUT_MS = 2e3;
@@ -4741,10 +4814,10 @@ function mergeDefs(...defs) {
4741
4814
  function cloneDef(schema) {
4742
4815
  return mergeDefs(schema._zod.def);
4743
4816
  }
4744
- function getElementAtPath(obj, path15) {
4745
- if (!path15)
4817
+ function getElementAtPath(obj, path16) {
4818
+ if (!path16)
4746
4819
  return obj;
4747
- return path15.reduce((acc, key2) => acc?.[key2], obj);
4820
+ return path16.reduce((acc, key2) => acc?.[key2], obj);
4748
4821
  }
4749
4822
  function promiseAllObject(promisesObj) {
4750
4823
  const keys = Object.keys(promisesObj);
@@ -5084,11 +5157,11 @@ function explicitlyAborted(x, startIndex = 0) {
5084
5157
  }
5085
5158
  return false;
5086
5159
  }
5087
- function prefixIssues(path15, issues) {
5160
+ function prefixIssues(path16, issues) {
5088
5161
  return issues.map((iss) => {
5089
5162
  var _a3;
5090
5163
  (_a3 = iss).path ?? (_a3.path = []);
5091
- iss.path.unshift(path15);
5164
+ iss.path.unshift(path16);
5092
5165
  return iss;
5093
5166
  });
5094
5167
  }
@@ -5550,16 +5623,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
5550
5623
  }
5551
5624
  function formatError(error62, mapper = (issue2) => issue2.message) {
5552
5625
  const fieldErrors = { _errors: [] };
5553
- const processError = (error63, path15 = []) => {
5626
+ const processError = (error63, path16 = []) => {
5554
5627
  for (const issue2 of error63.issues) {
5555
5628
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5556
- issue2.errors.map((issues) => processError({ issues }, [...path15, ...issue2.path]));
5629
+ issue2.errors.map((issues) => processError({ issues }, [...path16, ...issue2.path]));
5557
5630
  } else if (issue2.code === "invalid_key") {
5558
- processError({ issues: issue2.issues }, [...path15, ...issue2.path]);
5631
+ processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5559
5632
  } else if (issue2.code === "invalid_element") {
5560
- processError({ issues: issue2.issues }, [...path15, ...issue2.path]);
5633
+ processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5561
5634
  } else {
5562
- const fullpath = [...path15, ...issue2.path];
5635
+ const fullpath = [...path16, ...issue2.path];
5563
5636
  if (fullpath.length === 0) {
5564
5637
  fieldErrors._errors.push(mapper(issue2));
5565
5638
  } else {
@@ -5598,17 +5671,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
5598
5671
  }
5599
5672
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
5600
5673
  const result = { errors: [] };
5601
- const processError = (error63, path15 = []) => {
5674
+ const processError = (error63, path16 = []) => {
5602
5675
  var _a3;
5603
5676
  for (const issue2 of error63.issues) {
5604
5677
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5605
- issue2.errors.map((issues) => processError({ issues }, [...path15, ...issue2.path]));
5678
+ issue2.errors.map((issues) => processError({ issues }, [...path16, ...issue2.path]));
5606
5679
  } else if (issue2.code === "invalid_key") {
5607
- processError({ issues: issue2.issues }, [...path15, ...issue2.path]);
5680
+ processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5608
5681
  } else if (issue2.code === "invalid_element") {
5609
- processError({ issues: issue2.issues }, [...path15, ...issue2.path]);
5682
+ processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5610
5683
  } else {
5611
- const fullpath = [...path15, ...issue2.path];
5684
+ const fullpath = [...path16, ...issue2.path];
5612
5685
  if (fullpath.length === 0) {
5613
5686
  result.errors.push(mapper(issue2));
5614
5687
  continue;
@@ -5647,8 +5720,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
5647
5720
  }
5648
5721
  function toDotPath(_path) {
5649
5722
  const segs = [];
5650
- const path15 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5651
- for (const seg of path15) {
5723
+ const path16 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5724
+ for (const seg of path16) {
5652
5725
  if (typeof seg === "number")
5653
5726
  segs.push(`[${seg}]`);
5654
5727
  else if (typeof seg === "symbol")
@@ -22786,13 +22859,13 @@ function resolveRef(ref, ctx) {
22786
22859
  if (!ref.startsWith("#")) {
22787
22860
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
22788
22861
  }
22789
- const path15 = ref.slice(1).split("/").filter(Boolean);
22790
- if (path15.length === 0) {
22862
+ const path16 = ref.slice(1).split("/").filter(Boolean);
22863
+ if (path16.length === 0) {
22791
22864
  return ctx.rootSchema;
22792
22865
  }
22793
22866
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
22794
- if (path15[0] === defsKey) {
22795
- const key2 = path15[1] === void 0 ? void 0 : decodeJSONPointerSegment(path15[1]);
22867
+ if (path16[0] === defsKey) {
22868
+ const key2 = path16[1] === void 0 ? void 0 : decodeJSONPointerSegment(path16[1]);
22796
22869
  if (!key2 || !ctx.defs[key2]) {
22797
22870
  throw new Error(`Reference not found: ${ref}`);
22798
22871
  }
@@ -23674,11 +23747,11 @@ function defineAction(config2) {
23674
23747
  ref: actionRef(config2.name, data)
23675
23748
  };
23676
23749
  }
23677
- function resolveField(obj, path15) {
23678
- if (!path15 || obj == null) return void 0;
23679
- if (!path15.includes(".")) return obj[path15];
23750
+ function resolveField(obj, path16) {
23751
+ if (!path16 || obj == null) return void 0;
23752
+ if (!path16.includes(".")) return obj[path16];
23680
23753
  let cur = obj;
23681
- for (const p of path15.split(".")) {
23754
+ for (const p of path16.split(".")) {
23682
23755
  if (cur == null) return void 0;
23683
23756
  cur = cur[p];
23684
23757
  }
@@ -25054,14 +25127,14 @@ function checkTypeMismatch(value, info) {
25054
25127
  };
25055
25128
  return null;
25056
25129
  }
25057
- function validationMessage(component, path15, issue2) {
25130
+ function validationMessage(component, path16, issue2) {
25058
25131
  switch (issue2.code) {
25059
25132
  case "type-mismatch":
25060
- return `field "${path15}" expects ${issue2.expected} but got ${issue2.actual}`;
25133
+ return `field "${path16}" expects ${issue2.expected} but got ${issue2.actual}`;
25061
25134
  case "missing-required":
25062
- return `missing required field "${path15}"${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25135
+ return `missing required field "${path16}"${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25063
25136
  case "null-required":
25064
- return `required field "${path15}" cannot be null${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25137
+ return `required field "${path16}" cannot be null${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25065
25138
  case "unknown-component":
25066
25139
  return `Unknown component "${component}" \u2014 not found in catalog or builtins${issue2.available?.length ? `. Available components: ${issue2.available.join(", ")}` : ""}`;
25067
25140
  case "inline-reserved":
@@ -25076,12 +25149,12 @@ function validationMessage(component, path15, issue2) {
25076
25149
  return component ? `No root statement, and no statement calls the root component ${component}` : "No root statement";
25077
25150
  }
25078
25151
  }
25079
- function pushValidationIssue(ctx, component, path15, issue2) {
25152
+ function pushValidationIssue(ctx, component, path16, issue2) {
25080
25153
  ctx.errors.push({
25081
25154
  code: issue2.code,
25082
25155
  component,
25083
- path: path15,
25084
- message: validationMessage(component, path15, issue2),
25156
+ path: path16,
25157
+ message: validationMessage(component, path16, issue2),
25085
25158
  statementId: ctx.currentStatementId,
25086
25159
  ...issue2.code === "no-root" && issue2.reason === "fallback" && { severity: "warning" }
25087
25160
  });
@@ -25091,10 +25164,10 @@ function resolveInvalidValue(required2, defaultValue) {
25091
25164
  if (defaultValue !== void 0) return defaultValue;
25092
25165
  return required2 ? INVALID2 : void 0;
25093
25166
  }
25094
- function validateElementPosition(element, s, component, path15, ctx) {
25167
+ function validateElementPosition(element, s, component, path16, ctx) {
25095
25168
  if (isCompositeSchema(s)) return false;
25096
25169
  if (typeof s["type"] === "string" || Array.isArray(s["enum"]) || "const" in s) {
25097
- pushValidationIssue(ctx, component, path15, {
25170
+ pushValidationIssue(ctx, component, path16, {
25098
25171
  code: "type-mismatch",
25099
25172
  expected: typeof s["type"] === "string" ? s["type"] : "a literal value",
25100
25173
  actual: `component "${element.typeName}"`
@@ -25110,9 +25183,9 @@ function isOnlyComponentSlot(s, ctx) {
25110
25183
  const names = slotRefNames(s);
25111
25184
  return names.length > 0 && names.every((n) => n !== void 0 && ctx.cat?.has(n));
25112
25185
  }
25113
- function validateObjectValue(value, s, component, path15, ctx) {
25186
+ function validateObjectValue(value, s, component, path16, ctx) {
25114
25187
  if (typeof value !== "object" || Array.isArray(value)) {
25115
- pushValidationIssue(ctx, component, path15, {
25188
+ pushValidationIssue(ctx, component, path16, {
25116
25189
  code: "type-mismatch",
25117
25190
  expected: "object",
25118
25191
  actual: jsType(value)
@@ -25132,14 +25205,14 @@ function validateObjectValue(value, s, component, path15, ctx) {
25132
25205
  (out ??= { ...obj })[key2] = fallback;
25133
25206
  continue;
25134
25207
  }
25135
- pushValidationIssue(ctx, component, `${path15}/${key2}`, { code: present ? "null-required" : "missing-required" });
25208
+ pushValidationIssue(ctx, component, `${path16}/${key2}`, { code: present ? "null-required" : "missing-required" });
25136
25209
  invalid = true;
25137
25210
  }
25138
25211
  }
25139
25212
  for (const key2 of Object.keys(props)) {
25140
25213
  if (!(key2 in obj)) continue;
25141
25214
  const sub = props[key2];
25142
- let next = validateSchemaValue(obj[key2], sub, component, `${path15}/${key2}`, ctx);
25215
+ let next = validateSchemaValue(obj[key2], sub, component, `${path16}/${key2}`, ctx);
25143
25216
  if (next === INVALID2) {
25144
25217
  next = resolveInvalidValue(required2.includes(key2), getSchemaDefaultValue(sub));
25145
25218
  if (next === INVALID2) {
@@ -25154,9 +25227,9 @@ function validateObjectValue(value, s, component, path15, ctx) {
25154
25227
  }
25155
25228
  return invalid ? INVALID2 : out ?? obj;
25156
25229
  }
25157
- function validateArrayValue(value, s, component, path15, ctx) {
25230
+ function validateArrayValue(value, s, component, path16, ctx) {
25158
25231
  if (!Array.isArray(value)) {
25159
- pushValidationIssue(ctx, component, path15, {
25232
+ pushValidationIssue(ctx, component, path16, {
25160
25233
  code: "type-mismatch",
25161
25234
  expected: "array",
25162
25235
  actual: jsType(value)
@@ -25167,19 +25240,19 @@ function validateArrayValue(value, s, component, path15, ctx) {
25167
25240
  if (!items || typeof items !== "object" || Array.isArray(items)) return value;
25168
25241
  let out;
25169
25242
  for (let i = 0; i < value.length; i++) {
25170
- const next = validateSchemaValue(value[i], items, component, `${path15}/${i}`, ctx);
25243
+ const next = validateSchemaValue(value[i], items, component, `${path16}/${i}`, ctx);
25171
25244
  if (next !== value[i] && !out) out = value.slice(0, i);
25172
25245
  if (out && next !== INVALID2) out.push(next);
25173
25246
  }
25174
25247
  return out ?? value;
25175
25248
  }
25176
- function validateLeafValue(value, s, component, path15, ctx) {
25249
+ function validateLeafValue(value, s, component, path16, ctx) {
25177
25250
  const leaf = getScalarTypeInfo(s);
25178
25251
  if (leaf.expectedType == null && leaf.enumValues == null) return false;
25179
25252
  if (leaf.enumValues && ctx.partial) return false;
25180
25253
  const mismatch = checkTypeMismatch(value, leaf);
25181
25254
  if (mismatch) {
25182
- pushValidationIssue(ctx, component, path15, {
25255
+ pushValidationIssue(ctx, component, path16, {
25183
25256
  code: "type-mismatch",
25184
25257
  ...mismatch
25185
25258
  });
@@ -25187,15 +25260,15 @@ function validateLeafValue(value, s, component, path15, ctx) {
25187
25260
  }
25188
25261
  return false;
25189
25262
  }
25190
- function validateSchemaValue(value, schema, component, path15, ctx) {
25263
+ function validateSchemaValue(value, schema, component, path16, ctx) {
25191
25264
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) return value;
25192
25265
  const s = schema;
25193
25266
  if (value == null) return value;
25194
25267
  if (isASTNode(value)) return value;
25195
- if (isElementNode(value)) return validateElementPosition(value, s, component, path15, ctx) ? INVALID2 : value;
25268
+ if (isElementNode(value)) return validateElementPosition(value, s, component, path16, ctx) ? INVALID2 : value;
25196
25269
  if (isCompositeSchema(s)) {
25197
25270
  if (!isOnlyComponentSlot(s, ctx)) return value;
25198
- pushValidationIssue(ctx, component, path15, {
25271
+ pushValidationIssue(ctx, component, path16, {
25199
25272
  code: "type-mismatch",
25200
25273
  expected: slotRefNames(s).join(" | "),
25201
25274
  actual: Array.isArray(value) ? "array" : typeof value === "object" ? "plain object" : typeof value
@@ -25203,9 +25276,9 @@ function validateSchemaValue(value, schema, component, path15, ctx) {
25203
25276
  return INVALID2;
25204
25277
  }
25205
25278
  const type = s["type"];
25206
- if (type === "object") return validateObjectValue(value, s, component, path15, ctx);
25207
- if (type === "array") return validateArrayValue(value, s, component, path15, ctx);
25208
- return validateLeafValue(value, s, component, path15, ctx) ? INVALID2 : value;
25279
+ if (type === "object") return validateObjectValue(value, s, component, path16, ctx);
25280
+ if (type === "array") return validateArrayValue(value, s, component, path16, ctx);
25281
+ return validateLeafValue(value, s, component, path16, ctx) ? INVALID2 : value;
25209
25282
  }
25210
25283
  function containsDynamicValue(v) {
25211
25284
  if (v == null || typeof v !== "object") return false;
@@ -26090,16 +26163,16 @@ function mergeStatements(existing, patch, rootId = "root") {
26090
26163
  return order.filter((id) => merged.has(id)).map((id) => merged.get(id)).join("\n");
26091
26164
  }
26092
26165
 
26093
- // src/cli/main.js
26166
+ // src/cli/main.ts
26094
26167
  init_artifact();
26095
26168
 
26096
- // src/cli/check.js
26169
+ // src/cli/check.ts
26097
26170
  import fs3 from "node:fs";
26098
26171
  init_artifact();
26099
26172
  init_paths();
26100
26173
  init_packs();
26101
26174
 
26102
- // src/shared/statements.js
26175
+ // src/shared/statements.ts
26103
26176
  var IDENT_START = /[A-Za-z_$]/;
26104
26177
  function splitStatements(source) {
26105
26178
  const out = [];
@@ -28078,7 +28151,8 @@ function Ce(e) {
28078
28151
  var xr = { graphlib: ie, version: ue, layout: Oe, debug: Ce, util: { time: le, notime: q } };
28079
28152
  var Uo = xr;
28080
28153
 
28081
- // src/runtime/components/_architecture-lanes.js
28154
+ // src/runtime/components/_architecture-lanes.ts
28155
+ var known = (map2, key2) => map2.get(key2);
28082
28156
  var LANES = {
28083
28157
  margin: 16,
28084
28158
  // around the drawing
@@ -28116,7 +28190,8 @@ function lanesPlanWidth(model) {
28116
28190
  }
28117
28191
  var plans = /* @__PURE__ */ new WeakMap();
28118
28192
  function planColumns(model) {
28119
- if (plans.has(model)) return plans.get(model);
28193
+ const cached2 = plans.get(model);
28194
+ if (cached2 !== void 0) return cached2;
28120
28195
  const lanes = prepareLanes(model);
28121
28196
  if (!lanes) {
28122
28197
  plans.set(model, null);
@@ -28185,9 +28260,10 @@ function assignLanes(model) {
28185
28260
  const top = model.groups.filter((g) => !g.parent);
28186
28261
  if (!top.length) return null;
28187
28262
  const laneOf = /* @__PURE__ */ new Map();
28188
- const lanes = top.map((group) => ({ group, nodes: [] }));
28263
+ const newLane = (group, nodes) => ({ group, nodes, index: 0, laneOf, rows: [], phys: /* @__PURE__ */ new Map(), inner: 0, headLine: "", frame: { x: 0, y: 0, w: 0, h: 0 } });
28264
+ const lanes = top.map((group) => newLane(group, []));
28189
28265
  for (const lane of lanes) {
28190
- for (const m of lane.group.members) {
28266
+ for (const m of lane.group?.members ?? []) {
28191
28267
  if (laneOf.has(m)) continue;
28192
28268
  laneOf.set(m, lane);
28193
28269
  lane.nodes.push(m);
@@ -28202,7 +28278,7 @@ function assignLanes(model) {
28202
28278
  if (isLoose.has(e.from) && !isLoose.has(e.to)) out++;
28203
28279
  if (!isLoose.has(e.from) && isLoose.has(e.to)) inn++;
28204
28280
  }
28205
- const lane = { group: null, nodes: loose };
28281
+ const lane = newLane(null, loose);
28206
28282
  for (const id of loose) laneOf.set(id, lane);
28207
28283
  if (out >= inn) lanes.unshift(lane);
28208
28284
  else lanes.push(lane);
@@ -28221,14 +28297,14 @@ function laneRows(lane, edges) {
28221
28297
  const parent = new Map(ids.map((id) => [id, id]));
28222
28298
  const find = (x) => {
28223
28299
  while (parent.get(x) !== x) {
28224
- parent.set(x, parent.get(parent.get(x)));
28225
- x = parent.get(x);
28300
+ parent.set(x, known(parent, known(parent, x)));
28301
+ x = known(parent, x);
28226
28302
  }
28227
28303
  return x;
28228
28304
  };
28229
28305
  for (const e of intra) parent.set(find(e.from), find(e.to));
28230
28306
  const adj = new Map(ids.map((id) => [id, []]));
28231
- for (const e of intra) adj.get(e.from).push(e.to);
28307
+ for (const e of intra) known(adj, e.from).push(e.to);
28232
28308
  const state2 = /* @__PURE__ */ new Map();
28233
28309
  const kept = [];
28234
28310
  for (const root of ids) {
@@ -28238,7 +28314,7 @@ function laneRows(lane, edges) {
28238
28314
  while (stack.length) {
28239
28315
  const top = stack[stack.length - 1];
28240
28316
  const [u, k] = top;
28241
- const next = adj.get(u)[k];
28317
+ const next = known(adj, u)[k];
28242
28318
  if (next === void 0) {
28243
28319
  state2.set(u, 2);
28244
28320
  stack.pop();
@@ -28257,8 +28333,8 @@ function laneRows(lane, edges) {
28257
28333
  for (let changed = true, guard = 0; changed && guard <= ids.length; guard++) {
28258
28334
  changed = false;
28259
28335
  for (const [u, v] of kept) {
28260
- if (rank.get(v) < rank.get(u) + 1) {
28261
- rank.set(v, rank.get(u) + 1);
28336
+ if (known(rank, v) < known(rank, u) + 1) {
28337
+ rank.set(v, known(rank, u) + 1);
28262
28338
  changed = true;
28263
28339
  }
28264
28340
  }
@@ -28267,20 +28343,20 @@ function laneRows(lane, edges) {
28267
28343
  for (const id of ids) {
28268
28344
  const root = find(id);
28269
28345
  if (!blocks.has(root)) blocks.set(root, []);
28270
- blocks.get(root).push(id);
28346
+ known(blocks, root).push(id);
28271
28347
  }
28272
28348
  const rows = [];
28273
- for (const members2 of [...blocks.values()].sort((a, b) => pos.get(a[0]) - pos.get(b[0]))) {
28349
+ for (const members2 of [...blocks.values()].sort((a, b) => known(pos, a[0]) - known(pos, b[0]))) {
28274
28350
  const byRank = /* @__PURE__ */ new Map();
28275
28351
  for (const id of members2) {
28276
- const r = rank.get(id);
28352
+ const r = known(rank, id);
28277
28353
  if (!byRank.has(r)) byRank.set(r, []);
28278
- byRank.get(r).push(id);
28354
+ known(byRank, r).push(id);
28279
28355
  }
28280
28356
  for (const r of [...byRank.keys()].sort((a, b) => a - b)) {
28281
- const ids2 = byRank.get(r);
28282
- if (ids2.length <= 2) rows.push({ ids: ids2, pin: null });
28283
- else for (const id of ids2) rows.push({ ids: [id], pin: null });
28357
+ const ids2 = known(byRank, r);
28358
+ if (ids2.length <= 2) rows.push({ ids: ids2, pin: null, g: 0 });
28359
+ else for (const id of ids2) rows.push({ ids: [id], pin: null, g: 0 });
28284
28360
  }
28285
28361
  }
28286
28362
  return rows;
@@ -28297,13 +28373,13 @@ function applyPins(rows, byId) {
28297
28373
  continue;
28298
28374
  }
28299
28375
  if (!pinned.has(pin)) {
28300
- const pr2 = { ids: [], pin };
28376
+ const pr2 = { ids: [], pin, g: 0 };
28301
28377
  pinned.set(pin, pr2);
28302
28378
  out.push(pr2);
28303
28379
  }
28304
- pinned.get(pin).ids.push(id);
28380
+ known(pinned, pin).ids.push(id);
28305
28381
  }
28306
- if (free.length) out.push({ ids: free, pin: null });
28382
+ if (free.length) out.push({ ids: free, pin: null, g: 0 });
28307
28383
  }
28308
28384
  return out;
28309
28385
  }
@@ -28332,7 +28408,7 @@ function assignGlobalRows(lanes, edges) {
28332
28408
  const neighbors = /* @__PURE__ */ new Map();
28333
28409
  const link = (a, b) => {
28334
28410
  if (!neighbors.has(a)) neighbors.set(a, []);
28335
- neighbors.get(a).push(b);
28411
+ known(neighbors, a).push(b);
28336
28412
  };
28337
28413
  for (const e of edges) {
28338
28414
  const a = rowOf.get(e.from);
@@ -28368,9 +28444,9 @@ function assignGlobalRows(lanes, edges) {
28368
28444
  for (const lane of lanes) {
28369
28445
  lane.phys = /* @__PURE__ */ new Map();
28370
28446
  for (const r of lane.rows) {
28371
- const g = index.get(r.g);
28447
+ const g = known(index, r.g);
28372
28448
  if (!lane.phys.has(g)) lane.phys.set(g, []);
28373
- lane.phys.get(g).push(...r.ids);
28449
+ known(lane.phys, g).push(...r.ids);
28374
28450
  }
28375
28451
  }
28376
28452
  return used.length;
@@ -28411,7 +28487,7 @@ function placeColumns(lanes, byId, rowCount, sizes, model) {
28411
28487
  for (const lane of lanes) {
28412
28488
  let inner = L.laneMinW;
28413
28489
  for (const ids of lane.phys.values()) {
28414
- const w = ids.reduce((sum, id) => sum + natural.get(id).width, 0) + L.colGap * (ids.length - 1);
28490
+ const w = ids.reduce((sum, id) => sum + known(natural, id).width, 0) + L.colGap * (ids.length - 1);
28415
28491
  inner = Math.max(inner, w);
28416
28492
  }
28417
28493
  const label = lane.group?.label || "";
@@ -28422,13 +28498,13 @@ function placeColumns(lanes, byId, rowCount, sizes, model) {
28422
28498
  const sized = /* @__PURE__ */ new Map();
28423
28499
  for (const lane of lanes) {
28424
28500
  for (const ids of lane.phys.values()) {
28425
- const sum = ids.reduce((acc, id) => acc + natural.get(id).width, 0);
28501
+ const sum = ids.reduce((acc, id) => acc + known(natural, id).width, 0);
28426
28502
  const extra = (lane.inner - sum - L.colGap * (ids.length - 1)) / ids.length;
28427
- for (const id of ids) sized.set(id, sizeNode(byId.get(id), Math.floor(natural.get(id).width + extra)));
28503
+ for (const id of ids) sized.set(id, sizeNode(known(byId, id), Math.floor(known(natural, id).width + extra)));
28428
28504
  }
28429
28505
  }
28430
28506
  const rowH = new Array(rowCount).fill(0);
28431
- for (const lane of lanes) for (const [g, ids] of lane.phys) for (const id of ids) rowH[g] = Math.max(rowH[g], sized.get(id).height);
28507
+ for (const lane of lanes) for (const [g, ids] of lane.phys) for (const id of ids) rowH[g] = Math.max(rowH[g], known(sized, id).height);
28432
28508
  const rowTop = [];
28433
28509
  let y = L.margin + headH + L.lanePad;
28434
28510
  for (let g = 0; g < rowCount; g++) {
@@ -28446,7 +28522,7 @@ function placeColumns(lanes, byId, rowCount, sizes, model) {
28446
28522
  for (const [g, ids] of lane.phys) {
28447
28523
  let bx = x + L.lanePad;
28448
28524
  for (const id of ids) {
28449
- const b = sized.get(id);
28525
+ const b = known(sized, id);
28450
28526
  boxes.set(id, { ...b, x: bx, y: Math.round(rowTop[g] + (rowH[g] - b.height) / 2), rowIndex: g, laneIndex: lane.index });
28451
28527
  bx += b.width + L.colGap;
28452
28528
  }
@@ -28465,8 +28541,8 @@ function placeColumns(lanes, byId, rowCount, sizes, model) {
28465
28541
  channels.push({ key: "outB", axis: "y", lo: frameBottom, hi: height, fixed: true });
28466
28542
  const frameLines = { xs: lanes.flatMap((l) => [l.frame.x, l.frame.x + l.frame.w]), ys: [frameTop, frameBottom] };
28467
28543
  const sides = (e) => {
28468
- const a = boxes.get(e.from);
28469
- const b = boxes.get(e.to);
28544
+ const a = known(boxes, e.from);
28545
+ const b = known(boxes, e.to);
28470
28546
  if (a.laneIndex !== b.laneIndex) return b.laneIndex > a.laneIndex ? ["right", "left"] : ["left", "right"];
28471
28547
  if (b.rowIndex > a.rowIndex) return ["bottom", "top"];
28472
28548
  if (b.rowIndex < a.rowIndex) return ["top", "bottom"];
@@ -28476,8 +28552,8 @@ function placeColumns(lanes, byId, rowCount, sizes, model) {
28476
28552
  }
28477
28553
  function placeBands(lanes, byId, sizes, model, targetWidth) {
28478
28554
  const L = LANES;
28479
- const left = sizes.get("left");
28480
- const right = sizes.get("right");
28555
+ const left = known(sizes, "left");
28556
+ const right = known(sizes, "right");
28481
28557
  const target = (targetWidth || 360) / 0.85;
28482
28558
  const room = Math.max(180, Math.min(720, target - left - right - L.lanePad * 2 - 4));
28483
28559
  sizeLaneHeads(lanes, () => room);
@@ -28495,7 +28571,7 @@ function placeBands(lanes, byId, sizes, model, targetWidth) {
28495
28571
  if (prevBottom !== null) channels.push({ key: `band${lane.index}`, axis: "y", lo: prevBottom, hi: top, fixed: true });
28496
28572
  let first = true;
28497
28573
  for (const row of lane.rows) {
28498
- const natural = row.ids.map((id) => sizeNode(byId.get(id)));
28574
+ const natural = row.ids.map((id) => sizeNode(known(byId, id)));
28499
28575
  const fits = natural.reduce((sum, n) => sum + n.width, 0) + L.colGap * (row.ids.length - 1) <= room;
28500
28576
  const lines = fits ? [row.ids] : row.ids.map((id) => [id]);
28501
28577
  for (const ids of lines) {
@@ -28512,7 +28588,7 @@ function placeBands(lanes, byId, sizes, model, targetWidth) {
28512
28588
  }
28513
28589
  first = false;
28514
28590
  const w = (room - L.colGap * (ids.length - 1)) / ids.length;
28515
- const sized = ids.map((id) => sizeNode(byId.get(id), Math.floor(w)));
28591
+ const sized = ids.map((id) => sizeNode(known(byId, id), Math.floor(w)));
28516
28592
  const h = Math.max(...sized.map((b) => b.height));
28517
28593
  let bx = x0;
28518
28594
  ids.forEach((id, k) => {
@@ -28534,8 +28610,8 @@ function placeBands(lanes, byId, sizes, model, targetWidth) {
28534
28610
  channels.push({ key: "left", axis: "x", lo: 0, hi: left }, { key: "right", axis: "x", lo: width - right, hi: width });
28535
28611
  const frameLines = { xs: [left, width - right], ys: lanes.flatMap((l) => [l.frame.y, l.frame.y + l.frame.h]) };
28536
28612
  const sides = (e) => {
28537
- const a = boxes.get(e.from);
28538
- const b = boxes.get(e.to);
28613
+ const a = known(boxes, e.from);
28614
+ const b = known(boxes, e.to);
28539
28615
  if (a.rowIndex === b.rowIndex) return b.x > a.x ? ["right", "left"] : ["left", "right"];
28540
28616
  if (b.rowIndex === a.rowIndex + 1 && b.x < a.x + a.width && a.x < b.x + b.width) return ["bottom", "top"];
28541
28617
  return b.rowIndex > a.rowIndex ? ["right", "right"] : ["left", "left"];
@@ -28560,8 +28636,8 @@ function routeAll(geo, model) {
28560
28636
  continue;
28561
28637
  }
28562
28638
  const [fromSide, toSide] = geo.sides(e);
28563
- const a = boxes.get(e.from);
28564
- const b = boxes.get(e.to);
28639
+ const a = known(boxes, e.from);
28640
+ const b = known(boxes, e.to);
28565
28641
  edges.push({ id: e.id, from: e.from, to: e.to, fromSide: openSide(a, fromSide, b, boxes), toSide: openSide(b, toSide, a, boxes) });
28566
28642
  }
28567
28643
  const xs = [];
@@ -28576,7 +28652,7 @@ function routeAll(geo, model) {
28576
28652
  bounds: { x0: 1, y0: 1, x1: geo.width - 1, y1: geo.height - 1 }
28577
28653
  });
28578
28654
  for (const e of loops) {
28579
- const b = boxes.get(e.from);
28655
+ const b = known(boxes, e.from);
28580
28656
  const r = b.x + b.width;
28581
28657
  const bottom = b.y + b.height;
28582
28658
  routes.set(e.id, [
@@ -28635,19 +28711,19 @@ function placePorts(boxes, edges) {
28635
28711
  const add = (node2, side, other, edge, end) => {
28636
28712
  const key2 = `${node2}\0${side}`;
28637
28713
  if (!sides.has(key2)) sides.set(key2, { node: node2, side, items: [] });
28638
- sides.get(key2).items.push({ other, edge, end });
28714
+ known(sides, key2).items.push({ other, edge, end, want: 0, pos: 0 });
28639
28715
  };
28640
28716
  for (const e of edges) {
28641
28717
  add(e.from, e.fromSide, e.to, e, "fromPort");
28642
28718
  add(e.to, e.toSide, e.from, e, "toPort");
28643
28719
  }
28644
28720
  for (const { node: node2, side, items } of sides.values()) {
28645
- const b = boxes.get(node2);
28721
+ const b = known(boxes, node2);
28646
28722
  const horizontal = side === "left" || side === "right";
28647
28723
  const lo = horizontal ? b.y + 9 : b.x + 12;
28648
28724
  const hi = horizontal ? b.y + b.height - 9 : b.x + b.width - 12;
28649
28725
  for (const it2 of items) {
28650
- const o = boxes.get(it2.other);
28726
+ const o = known(boxes, it2.other);
28651
28727
  const oLo = horizontal ? o.y : o.x;
28652
28728
  const oHi = horizontal ? o.y + o.height : o.x + o.width;
28653
28729
  const sLo = horizontal ? b.y : b.x;
@@ -28728,10 +28804,11 @@ var Heap = class {
28728
28804
  };
28729
28805
  var COST = { bend: 36, overlap: 8, cross: 22, hug: 0.6 };
28730
28806
  var SEARCH = { weight: 1.5, maxSteps: 12e4, maxTracks: 14 };
28731
- function routeOrthogonal({ boxes, edges, xLines = [], yLines = [], avoid = { xs: [], ys: [] }, bounds }) {
28807
+ function routeOrthogonal({ boxes, edges: input2, xLines = [], yLines = [], avoid = { xs: [], ys: [] }, bounds }) {
28732
28808
  const C = LANES.clear;
28733
28809
  const S2 = LANES.stub;
28734
- placePorts(boxes, edges);
28810
+ placePorts(boxes, input2);
28811
+ const edges = input2;
28735
28812
  const stubOf = (p, side) => ({ x: p.x + NORMAL[side][0] * S2, y: p.y + NORMAL[side][1] * S2 });
28736
28813
  const rnd = (v) => Math.round(v * 2) / 2;
28737
28814
  const hugXs = /* @__PURE__ */ new Set();
@@ -28827,11 +28904,11 @@ function routeOrthogonal({ boxes, edges, xLines = [], yLines = [], avoid = { xs:
28827
28904
  const blocked = solid.some(([id, r]) => id !== e.from && id !== e.to && seg.x0 < r.x + r.width && r.x < seg.x1 && seg.y0 < r.y + r.height && r.y < seg.y1);
28828
28905
  if (!blocked) {
28829
28906
  routes.set(e.id, [a, b]);
28830
- const path16 = [];
28907
+ const path17 = [];
28831
28908
  const [i0, j0, i1, j1] = [ix(a.x), iy(a.y), ix(b.x), iy(b.y)];
28832
- if (j0 === j1) for (let i = Math.min(i0, i1); i <= Math.max(i0, i1); i++) path16.push([i, j0]);
28833
- else for (let j = Math.min(j0, j1); j <= Math.max(j0, j1); j++) path16.push([i0, j]);
28834
- if (path16.every(([i, j]) => i !== void 0 && j !== void 0)) mark(path16);
28909
+ if (j0 === j1) for (let i = Math.min(i0, i1); i <= Math.max(i0, i1); i++) path17.push([i, j0]);
28910
+ else for (let j = Math.min(j0, j1); j <= Math.max(j0, j1); j++) path17.push([i0, j]);
28911
+ if (path17.every((pt2) => pt2[0] !== void 0 && pt2[1] !== void 0)) mark(path17);
28835
28912
  continue;
28836
28913
  }
28837
28914
  }
@@ -28841,12 +28918,12 @@ function routeOrthogonal({ boxes, edges, xLines = [], yLines = [], avoid = { xs:
28841
28918
  const sj = iy(st2.y);
28842
28919
  const ti = ix(tt2.x);
28843
28920
  const tj = iy(tt2.y);
28844
- let path15 = null;
28921
+ let path16 = null;
28845
28922
  if (si !== void 0 && sj !== void 0 && ti !== void 0 && tj !== void 0) {
28846
- path15 = search(si, sj, dirOf(e.fromSide), ti, tj, reverse[dirOf(e.toSide)]);
28923
+ path16 = search(si, sj, dirOf(e.fromSide), ti, tj, reverse[dirOf(e.toSide)]);
28847
28924
  }
28848
- const pts = path15 ? path15.map(([i, j]) => ({ x: xs[i], y: ys[j] })) : elbow(st2, tt2, e.fromSide);
28849
- if (path15) mark(path15);
28925
+ const pts = path16 ? path16.map(([i, j]) => ({ x: xs[i], y: ys[j] })) : elbow(st2, tt2, e.fromSide);
28926
+ if (path16) mark(path16);
28850
28927
  routes.set(e.id, simplify([e.fromPort, ...pts, e.toPort]));
28851
28928
  }
28852
28929
  return routes;
@@ -28915,10 +28992,10 @@ function routeOrthogonal({ boxes, edges, xLines = [], yLines = [], avoid = { xs:
28915
28992
  }
28916
28993
  return out.reverse();
28917
28994
  }
28918
- function mark(path15) {
28919
- for (let k = 1; k < path15.length; k++) {
28920
- const [i0, j0] = path15[k - 1];
28921
- const [i1, j1] = path15[k];
28995
+ function mark(path16) {
28996
+ for (let k = 1; k < path16.length; k++) {
28997
+ const [i0, j0] = path16[k - 1];
28998
+ const [i1, j1] = path16[k];
28922
28999
  if (j0 === j1) {
28923
29000
  hUse[j0 * nx + Math.min(i0, i1)]++;
28924
29001
  hPass[j0 * nx + i0]++;
@@ -29003,7 +29080,7 @@ function placeLabels(edges, routes, geo) {
29003
29080
  segs.push({ a, b, vertical, score });
29004
29081
  }
29005
29082
  segs.sort((p, q2) => q2.score - p.score);
29006
- const sizesToTry = [full, ...[GUTTER_LABEL_W, 76].filter((w) => w < full.width).map((w) => narrowLabel(e.label, w)).filter(Boolean)];
29083
+ const sizesToTry = [full, ...[GUTTER_LABEL_W, 76].filter((w) => w < full.width).map((w) => narrowLabel(e.label, w)).filter((lb) => !!lb)];
29007
29084
  let choice = null;
29008
29085
  let best = null;
29009
29086
  for (const lb of sizesToTry) {
@@ -29048,9 +29125,9 @@ function placeLabels(edges, routes, geo) {
29048
29125
  }
29049
29126
  function finish({ geo, routed }, model) {
29050
29127
  const { boxes, lanes } = geo;
29051
- const nodes = model.nodes.filter((n) => boxes.has(n.id)).map((n) => ({ ...n, ...boxes.get(n.id) }));
29128
+ const nodes = model.nodes.filter((n) => boxes.has(n.id)).map((n) => ({ ...n, ...known(boxes, n.id) }));
29052
29129
  const labels = placeLabels(model.edges, routed, geo);
29053
- const edges = model.edges.filter((e) => routed.has(e.id)).map((e) => ({ ...e, orthogonal: true, points: routed.get(e.id), labelBox: labels.get(e.id) || null }));
29130
+ const edges = model.edges.filter((e) => routed.has(e.id)).map((e) => ({ ...e, orthogonal: true, points: known(routed, e.id), labelBox: labels.get(e.id) || null }));
29054
29131
  const groups = [];
29055
29132
  for (const lane of lanes) {
29056
29133
  if (!lane.group) continue;
@@ -29059,7 +29136,7 @@ function finish({ geo, routed }, model) {
29059
29136
  }
29060
29137
  for (const g of model.groups) {
29061
29138
  if (!g.parent) continue;
29062
- const ms = g.members.map((id) => boxes.get(id)).filter(Boolean);
29139
+ const ms = g.members.map((id) => boxes.get(id)).filter((b) => !!b);
29063
29140
  if (!ms.length) continue;
29064
29141
  const pad = 9;
29065
29142
  const x0 = Math.min(...ms.map((b) => b.x)) - pad;
@@ -29084,7 +29161,7 @@ function finish({ geo, routed }, model) {
29084
29161
  return { rankdir: "lanes", nodes, edges, groups, width, height };
29085
29162
  }
29086
29163
 
29087
- // src/runtime/components/_architecture-model.js
29164
+ // src/runtime/components/_architecture-model.ts
29088
29165
  var NODE_KINDS = ["frontend", "backend", "database", "messagebus", "cloud", "security", "external"];
29089
29166
  var EDGE_STYLES = ["solid", "dashed", "emphasis", "security"];
29090
29167
  var GROUP_KINDS = ["region", "security", "plain"];
@@ -29156,8 +29233,9 @@ function wrapText(text, size, maxWidth, maxLines = 2, mono = false) {
29156
29233
  kept[maxLines - 1] = `${last.join("").trimEnd()}\u2026`;
29157
29234
  return kept;
29158
29235
  }
29159
- var isElement = (v) => v && typeof v === "object" && v.props && typeof v.props === "object";
29236
+ var isElement = (v) => !!v && typeof v === "object" && "props" in v && !!v.props && typeof v.props === "object";
29160
29237
  var str = (v) => typeof v === "string" ? v : v == null ? "" : String(v);
29238
+ var oneOf = (list, v) => list.includes(v);
29161
29239
  function buildModel(props) {
29162
29240
  const problems = [];
29163
29241
  const nodes = /* @__PURE__ */ new Map();
@@ -29165,7 +29243,8 @@ function buildModel(props) {
29165
29243
  let anon = 0;
29166
29244
  const addNode = (el) => {
29167
29245
  if (!isElement(el)) return null;
29168
- if (seenEls.has(el)) return seenEls.get(el);
29246
+ const seen = seenEls.get(el);
29247
+ if (seen !== void 0) return seen;
29169
29248
  const id = el.statementId || `_node${anon++}`;
29170
29249
  if (!nodes.has(id)) {
29171
29250
  const p = el.props;
@@ -29174,11 +29253,11 @@ function buildModel(props) {
29174
29253
  id,
29175
29254
  named: !!el.statementId,
29176
29255
  label,
29177
- kind: NODE_KINDS.includes(p.kind) ? p.kind : "backend",
29256
+ kind: oneOf(NODE_KINDS, p.kind) ? p.kind : "backend",
29178
29257
  detail: str(p.detail).trim(),
29179
29258
  tag: str(p.tag).trim(),
29180
- tone: NODE_TONES.includes(p.tone) ? p.tone : "",
29181
- row: Number.isInteger(p.row) && p.row > 0 ? Math.min(p.row, 200) : null
29259
+ tone: oneOf(NODE_TONES, p.tone) ? p.tone : "",
29260
+ row: typeof p.row === "number" && Number.isInteger(p.row) && p.row > 0 ? Math.min(p.row, 200) : null
29182
29261
  });
29183
29262
  }
29184
29263
  seenEls.set(el, id);
@@ -29201,8 +29280,8 @@ function buildModel(props) {
29201
29280
  from,
29202
29281
  to,
29203
29282
  label: str(el.props.label).trim(),
29204
- style: EDGE_STYLES.includes(el.props.style) ? el.props.style : "solid",
29205
- tone: EDGE_TONES.includes(el.props.tone) ? el.props.tone : ""
29283
+ style: oneOf(EDGE_STYLES, el.props.style) ? el.props.style : "solid",
29284
+ tone: oneOf(EDGE_TONES, el.props.tone) ? el.props.tone : ""
29206
29285
  });
29207
29286
  }
29208
29287
  const groups = [];
@@ -29211,19 +29290,19 @@ function buildModel(props) {
29211
29290
  if (!isElement(el)) continue;
29212
29291
  const id = el.statementId || `_group${anon++}`;
29213
29292
  if (groupIds.has(id)) continue;
29214
- const members2 = [...new Set((Array.isArray(el.props.members) ? el.props.members : []).map(addNode).filter(Boolean))];
29293
+ const members2 = [...new Set((Array.isArray(el.props.members) ? el.props.members : []).map(addNode).filter((m) => !!m))];
29215
29294
  if (!members2.length) continue;
29216
29295
  groupIds.add(id);
29217
29296
  groups.push({
29218
29297
  id,
29219
29298
  named: !!el.statementId,
29220
29299
  label: str(el.props.label).trim(),
29221
- kind: GROUP_KINDS.includes(el.props.kind) ? el.props.kind : "region",
29300
+ kind: oneOf(GROUP_KINDS, el.props.kind) ? el.props.kind : "region",
29222
29301
  members: members2
29223
29302
  });
29224
29303
  }
29225
29304
  nestGroups(groups, problems);
29226
- const direction = DIRECTIONS.includes(props?.direction) ? props.direction : "auto";
29305
+ const direction = oneOf(DIRECTIONS, props?.direction) ? props.direction : "auto";
29227
29306
  return { nodes: [...nodes.values()], edges, groups, direction, problems };
29228
29307
  }
29229
29308
  function nestGroups(groups, problems) {
@@ -29240,8 +29319,9 @@ function nestGroups(groups, problems) {
29240
29319
  else {
29241
29320
  let first = groups.find((x) => x.id === owner.get(m));
29242
29321
  if (!isAncestor(groups, g.id, first.id)) {
29243
- for (let up = groups.find((x) => x.id === first.parent); up; up = groups.find((x) => x.id === up.parent)) {
29244
- if (g.members.every((x) => up.memberSet.has(x))) break;
29322
+ for (let up = groups.find((x) => x.id === first.parent); up; up = groups.find((x) => x.id === up?.parent)) {
29323
+ const set2 = up.memberSet;
29324
+ if (g.members.every((x) => set2.has(x))) break;
29245
29325
  first = up;
29246
29326
  }
29247
29327
  problems.push(
@@ -29257,7 +29337,8 @@ function isAncestor(groups, ancestorId, id) {
29257
29337
  let cur = groups.find((g) => g.id === id);
29258
29338
  while (cur?.parent) {
29259
29339
  if (cur.parent === ancestorId) return true;
29260
- cur = groups.find((g) => g.id === cur.parent);
29340
+ const parent = cur.parent;
29341
+ cur = groups.find((g) => g.id === parent);
29261
29342
  }
29262
29343
  return false;
29263
29344
  }
@@ -29267,7 +29348,7 @@ function detailLinesFor(detail, width) {
29267
29348
  const lines = [];
29268
29349
  for (let i = 0; i < parts.length; i++) {
29269
29350
  if (lines.length === 2) {
29270
- const last = lines.pop().replace(/…$/, "");
29351
+ const last = (lines.pop() ?? "").replace(/…$/, "");
29271
29352
  lines.push(wrapText(`${last} ${parts.slice(i).join(" ")}`, SIZES.detail, width, 1, true)[0]);
29272
29353
  break;
29273
29354
  }
@@ -29311,7 +29392,7 @@ function runLayout(model, rankdir) {
29311
29392
  for (const grp of model.groups) g.setNode(`group:${grp.id}`, {});
29312
29393
  for (const grp of model.groups) {
29313
29394
  if (grp.parent) g.setParent(`group:${grp.id}`, `group:${grp.parent}`);
29314
- for (const m of grp.directMembers) g.setParent(m, `group:${grp.id}`);
29395
+ for (const m of grp.directMembers ?? []) g.setParent(m, `group:${grp.id}`);
29315
29396
  }
29316
29397
  const labels = new Map(model.edges.map((e) => [e.id, sizeEdgeLabel(e.label)]));
29317
29398
  for (const e of model.edges) {
@@ -29351,8 +29432,10 @@ function runLayout(model, rankdir) {
29351
29432
  const cx = n.x + n.width / 2;
29352
29433
  const cy = n.y + n.height / 2;
29353
29434
  const placed = Number.isFinite(d.x) && Number.isFinite(d.y);
29435
+ const dx = d.x;
29436
+ const dy = d.y;
29354
29437
  if (rankdir === "TB") {
29355
- const lx = placed ? d.x : r + 30 + (e.labelBox ? e.labelBox.width / 2 : 0);
29438
+ const lx = placed ? dx : r + 30 + (e.labelBox ? e.labelBox.width / 2 : 0);
29356
29439
  const reach = Math.max(r + 18, e.labelBox ? lx - e.labelBox.width / 2 - 3 : lx);
29357
29440
  const y1 = cy - n.height * 0.22;
29358
29441
  const y2 = cy + n.height * 0.22;
@@ -29364,7 +29447,7 @@ function runLayout(model, rankdir) {
29364
29447
  ];
29365
29448
  if (e.labelBox) e.labelBox = { ...e.labelBox, cx: Math.max(lx, reach + 3 + e.labelBox.width / 2), cy };
29366
29449
  } else {
29367
- const ly = placed ? d.y : b + 30 + (e.labelBox ? e.labelBox.height / 2 : 0);
29450
+ const ly = placed ? dy : b + 30 + (e.labelBox ? e.labelBox.height / 2 : 0);
29368
29451
  const reach = Math.max(b + 16, e.labelBox ? ly - e.labelBox.height / 2 - 3 : ly);
29369
29452
  const spread = Math.min(30, n.width * 0.3);
29370
29453
  e.points = [
@@ -29431,7 +29514,7 @@ function architectureNotes(props) {
29431
29514
  return notes;
29432
29515
  }
29433
29516
 
29434
- // src/shared/check-program.js
29517
+ // src/shared/check-program.ts
29435
29518
  function elementsOf(root, typeName) {
29436
29519
  const out = [];
29437
29520
  const seen = /* @__PURE__ */ new Set();
@@ -29440,8 +29523,9 @@ function elementsOf(root, typeName) {
29440
29523
  const v = stack.pop();
29441
29524
  if (!v || typeof v !== "object" || seen.has(v)) continue;
29442
29525
  seen.add(v);
29443
- if (v.typeName === typeName && v.props) out.push(v);
29444
- for (const child of Array.isArray(v) ? v : Object.values(v.props || {})) stack.push(child);
29526
+ const node2 = v;
29527
+ if (node2.typeName === typeName && node2.props) out.push(node2);
29528
+ for (const child of Array.isArray(v) ? v : Object.values(node2.props || {})) stack.push(child);
29445
29529
  }
29446
29530
  return out;
29447
29531
  }
@@ -29452,10 +29536,7 @@ function checkProgram(program, { parser, lineOf = () => void 0, decorate = (entr
29452
29536
  try {
29453
29537
  parsed = parser().parse(program);
29454
29538
  } catch (err) {
29455
- errors.push({ code: "parse-failed", message: String(
29456
- /** @type {any} */
29457
- err?.message || err
29458
- ) });
29539
+ errors.push({ code: "parse-failed", message: String((err && typeof err === "object" && "message" in err ? err.message : void 0) || err) });
29459
29540
  return { statements: 0, errors, warnings };
29460
29541
  }
29461
29542
  const seen = /* @__PURE__ */ new Set();
@@ -29513,22 +29594,24 @@ function checkProgram(program, { parser, lineOf = () => void 0, decorate = (entr
29513
29594
  return { statements: parsed.meta.statementCount, errors, warnings };
29514
29595
  }
29515
29596
 
29516
- // src/cli/check.js
29597
+ // src/cli/check.ts
29517
29598
  var coreSpec = null;
29518
29599
  var packParsers = /* @__PURE__ */ new Map();
29519
29600
  var customParsers = /* @__PURE__ */ new WeakMap();
29520
29601
  function getParser(schema, packs = []) {
29521
29602
  if (schema) {
29522
- if (!customParsers.has(schema)) customParsers.set(schema, createParser(schema, schema.root || "Stack"));
29523
- return customParsers.get(schema);
29603
+ let custom2 = customParsers.get(schema);
29604
+ if (!custom2) customParsers.set(schema, custom2 = createParser(schema, schema.root || "Stack"));
29605
+ return custom2;
29524
29606
  }
29525
29607
  const key2 = packs.map((p) => `${p.name}@${p.version}:${p.integrity || ""}`).join(",");
29526
- if (!packParsers.has(key2)) {
29527
- coreSpec ||= JSON.parse(fs3.readFileSync(assetPath("spec.json"), "utf8"));
29528
- const spec = mergePackSchemas(coreSpec, packs.map((p) => p.catalog));
29529
- packParsers.set(key2, createParser(spec, spec.root || "Stack"));
29608
+ let parser = packParsers.get(key2);
29609
+ if (!parser) {
29610
+ const core2 = coreSpec ||= JSON.parse(fs3.readFileSync(assetPath("spec.json"), "utf8"));
29611
+ const spec = mergePackSchemas(core2, packs.map((p) => p.catalog));
29612
+ packParsers.set(key2, parser = createParser(spec, spec.root || "Stack"));
29530
29613
  }
29531
- return packParsers.get(key2);
29614
+ return parser;
29532
29615
  }
29533
29616
  function unknownComponent(entry, packs, file3) {
29534
29617
  const name = entry.component;
@@ -29561,7 +29644,7 @@ function statementLines(html) {
29561
29644
  }
29562
29645
  function checkHtml(html, { schema, file: file3 = "<file>" } = {}) {
29563
29646
  const art = readArtifact(html);
29564
- const packs = schema ? { installed: [], missing: [], mismatched: [], stale: [], declared: [] } : pagePacks(html);
29647
+ const packs = schema ? { installed: [], missing: [], mismatched: [], stale: [], declared: [], thirdParty: [] } : pagePacks(html);
29565
29648
  const result = { ok: true, errors: [], warnings: [], statements: 0, title: art.title, hasMermaid: false };
29566
29649
  if (art.program == null) {
29567
29650
  result.ok = false;
@@ -29609,7 +29692,7 @@ function checkHtml(html, { schema, file: file3 = "<file>" } = {}) {
29609
29692
  return result;
29610
29693
  }
29611
29694
 
29612
- // src/cli/main.js
29695
+ // src/cli/main.ts
29613
29696
  init_client();
29614
29697
  init_hub();
29615
29698
  init_invites();
@@ -29617,7 +29700,281 @@ init_lock();
29617
29700
  init_sessions();
29618
29701
  init_export();
29619
29702
 
29620
- // src/cli/output.js
29703
+ // src/cli/render-check.ts
29704
+ init_export();
29705
+ import { spawn as spawn2 } from "node:child_process";
29706
+ import fs10 from "node:fs";
29707
+ import http2 from "node:http";
29708
+ import os2 from "node:os";
29709
+ import path9 from "node:path";
29710
+ var WIDTHS = [1280, 390];
29711
+ var LAUNCH_TIMEOUT_MS = 15e3;
29712
+ var RENDER_TIMEOUT_MS = 3e4;
29713
+ var FRAME_SANDBOX = "allow-scripts";
29714
+ function findChrome() {
29715
+ const fromEnv = process.env.CHROME_PATH;
29716
+ if (fromEnv) return fs10.existsSync(fromEnv) ? fromEnv : null;
29717
+ const candidates = [];
29718
+ if (process.platform === "darwin") {
29719
+ for (const p of ["Google Chrome.app/Contents/MacOS/Google Chrome", "Chromium.app/Contents/MacOS/Chromium", "Microsoft Edge.app/Contents/MacOS/Microsoft Edge"]) {
29720
+ candidates.push(path9.join("/Applications", p));
29721
+ }
29722
+ } else if (process.platform === "win32") {
29723
+ const roots = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA].filter((x) => typeof x === "string");
29724
+ for (const r of roots) candidates.push(path9.join(r, "Google/Chrome/Application/chrome.exe"), path9.join(r, "Microsoft/Edge/Application/msedge.exe"));
29725
+ } else {
29726
+ const dirs = (process.env.PATH || "").split(path9.delimiter).filter(Boolean);
29727
+ for (const name of ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "microsoft-edge"]) {
29728
+ for (const d of dirs) candidates.push(path9.join(d, name));
29729
+ }
29730
+ }
29731
+ return candidates.find((c) => fs10.existsSync(c)) ?? null;
29732
+ }
29733
+ var MEASURE = `<script>(() => {
29734
+ const exceptions = [];
29735
+ addEventListener("error", (e) => exceptions.push(String((e.error && e.error.message) || e.message || "error")));
29736
+ addEventListener("unhandledrejection", (e) => exceptions.push(String((e.reason && e.reason.message) || e.reason)));
29737
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
29738
+ addEventListener("message", async (e) => {
29739
+ const d = e.data;
29740
+ if (e.source !== parent || !d || d.austereMeasure !== 1) return;
29741
+ for (let i = 0; i < 100; i++) {
29742
+ const pending = [...document.querySelectorAll(".austere-mermaid")].filter((m) => !m.querySelector("svg, .austere-render-error"));
29743
+ if (!pending.length) break;
29744
+ await sleep(100);
29745
+ }
29746
+ await sleep(200);
29747
+ const shown = [...document.querySelectorAll(".austere-render-error")].map((el) => ({
29748
+ statement: (el.closest("[data-oui-id]") && el.closest("[data-oui-id]").getAttribute("data-oui-id")) || "",
29749
+ error: el.textContent.trim().slice(0, 300),
29750
+ }));
29751
+ const se = document.scrollingElement;
29752
+ let past = null;
29753
+ if (se.scrollWidth > se.clientWidth + 1) {
29754
+ const vw = se.clientWidth;
29755
+ // Past the right edge and not inside a box that clips or scrolls it (a wide table in its own scroller is fine).
29756
+ const sticksOut = (el) => {
29757
+ if (el.getBoundingClientRect().right <= vw + 1) return false;
29758
+ for (let a = el.parentElement; a && a !== document.body; a = a.parentElement) {
29759
+ if (getComputedStyle(a).overflowX !== "visible" && a.getBoundingClientRect().right <= vw + 1) return false;
29760
+ }
29761
+ return true;
29762
+ };
29763
+ // Statement wrappers have no box of their own (display: contents), so find the innermost
29764
+ // elements that stick out and name the statement around each.
29765
+ const out = [...document.body.querySelectorAll("*")].slice(0, 20000).filter(sticksOut);
29766
+ const outSet = new Set(out);
29767
+ const leaves = out.filter((el) => ![...el.children].some((c) => outSet.has(c)));
29768
+ const ids = leaves.map((el) => el.closest("[data-oui-id]") && el.closest("[data-oui-id]").getAttribute("data-oui-id")).filter(Boolean);
29769
+ past = [...new Set(ids)].slice(0, 8);
29770
+ }
29771
+ const statements = new Set([...document.querySelectorAll("[data-oui-id]")].map((el) => el.getAttribute("data-oui-id"))).size;
29772
+ parent.postMessage({ austereMeasured: 1, id: d.id, shown, exceptions: exceptions.slice(0, 20), past, statements }, "*");
29773
+ });
29774
+ })();</script>`;
29775
+ var HARNESS = `<!doctype html><html><head><meta charset="utf-8"></head><body style="margin:0">
29776
+ <iframe id="f" sandbox="${FRAME_SANDBOX}" src="/page.html" style="border:0;width:${WIDTHS[0]}px;height:900px"></iframe>
29777
+ <script>
29778
+ const f = document.getElementById("f");
29779
+ const errors = [];
29780
+ const answers = new Map();
29781
+ let ready = false;
29782
+ addEventListener("message", (e) => {
29783
+ const d = e.data;
29784
+ if (e.source !== f.contentWindow || !d) return;
29785
+ if (d.austereMeasured === 1) return answers.get(d.id)?.(d);
29786
+ if (d.austere !== 1) return;
29787
+ if (d.type === "hello") f.contentWindow.postMessage({ austere: 1, type: "init", mode: "explore", scrollY: 0 }, "*");
29788
+ else if (d.type === "ready") ready = true;
29789
+ else if (d.type === "errors") for (const x of d.errors || []) errors.push({ statement: String(x.statementId || ""), error: String(x.message || "") });
29790
+ else if (d.type === "runtime-error" && d.error) errors.push({ statement: String(d.error.statementId || ""), error: String(d.error.message || "") });
29791
+ });
29792
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
29793
+ let next = 0;
29794
+ const measure = () => new Promise((resolve) => {
29795
+ const id = ++next;
29796
+ answers.set(id, resolve);
29797
+ f.contentWindow.postMessage({ austereMeasure: 1, id }, "*");
29798
+ });
29799
+ window.__austereReport = async (widths) => {
29800
+ for (let i = 0; i < 150 && !ready; i++) await sleep(100);
29801
+ const overflow = [];
29802
+ let last = null;
29803
+ for (const w of widths) {
29804
+ f.style.width = w + "px";
29805
+ await sleep(300);
29806
+ last = await measure();
29807
+ if (last.past) overflow.push({ width: w, statements: last.past });
29808
+ }
29809
+ const all = [...errors];
29810
+ for (const s of last.shown) if (!all.some((x) => x.statement === s.statement)) all.push(s);
29811
+ for (const x of last.exceptions) if (!all.some((y) => y.error === x)) all.push({ statement: "", error: x.slice(0, 300) });
29812
+ return { rendered: ready, statements: last.statements, errors: all, overflow };
29813
+ };
29814
+ </script></body></html>`;
29815
+ function connect(url2) {
29816
+ const ws = new WebSocket(url2);
29817
+ let next = 0;
29818
+ const pending = /* @__PURE__ */ new Map();
29819
+ const listeners = [];
29820
+ ws.onmessage = (ev) => {
29821
+ const m = JSON.parse(String(ev.data));
29822
+ const p = m.id != null ? pending.get(m.id) : void 0;
29823
+ if (p && m.id != null) {
29824
+ pending.delete(m.id);
29825
+ if (m.error) p.reject(new Error(m.error.message));
29826
+ else p.resolve(m.result);
29827
+ } else for (const fn2 of listeners) fn2(m);
29828
+ };
29829
+ const opened = new Promise((resolve, reject) => {
29830
+ ws.onopen = () => resolve();
29831
+ ws.onerror = () => reject(new Error("could not connect to Chrome's DevTools endpoint"));
29832
+ });
29833
+ return {
29834
+ opened,
29835
+ send(method, params = {}, sessionId2) {
29836
+ const id = ++next;
29837
+ ws.send(JSON.stringify({ id, method, params, sessionId: sessionId2 }));
29838
+ return new Promise((resolve, reject) => pending.set(id, { resolve: (v) => resolve(v), reject }));
29839
+ },
29840
+ on(fn2) {
29841
+ listeners.push(fn2);
29842
+ },
29843
+ close() {
29844
+ ws.close();
29845
+ }
29846
+ };
29847
+ }
29848
+ function launch(bin, profile) {
29849
+ const args = [
29850
+ "--headless=new",
29851
+ "--remote-debugging-port=0",
29852
+ `--user-data-dir=${profile}`,
29853
+ "--no-first-run",
29854
+ "--no-default-browser-check",
29855
+ "--disable-gpu",
29856
+ "--disable-extensions",
29857
+ "--disable-background-networking",
29858
+ "--disable-component-update",
29859
+ "--disable-breakpad",
29860
+ "--disable-sync",
29861
+ "--disable-dev-shm-usage",
29862
+ // Without these, Chrome waits about 25 s for a desktop keyring (Linux) or keychain (macOS) that may never answer.
29863
+ "--password-store=basic",
29864
+ "--use-mock-keychain",
29865
+ "--mute-audio",
29866
+ "--window-size=1400,1000"
29867
+ ];
29868
+ if (process.getuid?.() === 0) args.push("--no-sandbox");
29869
+ args.push("about:blank");
29870
+ const child = spawn2(bin, args, { stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
29871
+ const exited = new Promise((resolve) => {
29872
+ child.once("exit", () => resolve());
29873
+ child.once("error", () => resolve());
29874
+ });
29875
+ const endpoint = new Promise((resolve, reject) => {
29876
+ let err = "";
29877
+ const timer = setTimeout(() => reject(new Error(`Chrome did not start within ${LAUNCH_TIMEOUT_MS / 1e3} s`)), LAUNCH_TIMEOUT_MS);
29878
+ child.stderr?.on("data", (c) => {
29879
+ err += c;
29880
+ const m = /DevTools listening on (ws:\/\/\S+)/.exec(err);
29881
+ if (m?.[1]) {
29882
+ clearTimeout(timer);
29883
+ resolve(m[1]);
29884
+ }
29885
+ });
29886
+ child.once("error", (e) => {
29887
+ clearTimeout(timer);
29888
+ reject(e);
29889
+ });
29890
+ child.once("exit", (code) => {
29891
+ clearTimeout(timer);
29892
+ reject(new Error(`Chrome exited (${code}): ${err.trim().split("\n").pop() || "no output"}`));
29893
+ });
29894
+ });
29895
+ return { child, endpoint, exited };
29896
+ }
29897
+ function serve(page) {
29898
+ const server = http2.createServer((req, res) => {
29899
+ if (req.url === "/") {
29900
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" }).end(HARNESS);
29901
+ } else if (req.url === "/page.html") {
29902
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "content-security-policy": `sandbox ${FRAME_SANDBOX}` }).end(page);
29903
+ } else res.writeHead(404).end();
29904
+ });
29905
+ return new Promise((resolve, reject) => {
29906
+ server.on("error", reject);
29907
+ server.listen(0, "127.0.0.1", () => {
29908
+ const addr = server.address();
29909
+ resolve({ url: `http://127.0.0.1:${addr.port}/`, close: () => server.close() });
29910
+ });
29911
+ });
29912
+ }
29913
+ function withMeasure(html) {
29914
+ const at2 = html.toLowerCase().lastIndexOf("</body>");
29915
+ return at2 < 0 ? html + MEASURE : html.slice(0, at2) + MEASURE + html.slice(at2);
29916
+ }
29917
+ async function renderCheck(html) {
29918
+ const bin = findChrome();
29919
+ if (!bin) {
29920
+ const reason = process.env.CHROME_PATH ? `CHROME_PATH does not exist: ${process.env.CHROME_PATH}` : "no Chrome, Chromium or Edge found; install one or set CHROME_PATH";
29921
+ return { status: "unavailable", reason };
29922
+ }
29923
+ const { html: page, notices } = exportHtml(html);
29924
+ const profile = fs10.mkdtempSync(path9.join(os2.tmpdir(), "austere-render-"));
29925
+ const site = await serve(withMeasure(page));
29926
+ const { child, endpoint, exited } = launch(bin, profile);
29927
+ let cdp = null;
29928
+ let timer;
29929
+ try {
29930
+ const run2 = (async () => {
29931
+ const conn = connect(await endpoint);
29932
+ cdp = conn;
29933
+ await conn.opened;
29934
+ const { targetId } = await conn.send("Target.createTarget", { url: "about:blank" });
29935
+ const { sessionId: sessionId2 } = await conn.send("Target.attachToTarget", { targetId, flatten: true });
29936
+ await conn.send("Page.enable", {}, sessionId2);
29937
+ const loaded = new Promise((resolve) => conn.on((m) => m.method === "Page.loadEventFired" && m.sessionId === sessionId2 && resolve()));
29938
+ await conn.send("Page.navigate", { url: site.url }, sessionId2);
29939
+ await loaded;
29940
+ const r = await conn.send(
29941
+ "Runtime.evaluate",
29942
+ { expression: `window.__austereReport(${JSON.stringify(WIDTHS)})`, awaitPromise: true, returnByValue: true },
29943
+ sessionId2
29944
+ );
29945
+ if (r.exceptionDetails) throw new Error(`render report failed: ${r.exceptionDetails.text}`);
29946
+ const report = r.result.value;
29947
+ if (!report.rendered && !report.errors.length) report.errors.push({ statement: "", error: "the page did not finish rendering" });
29948
+ return { status: "ok", statements: report.statements, errors: report.errors, overflow: report.overflow, notices };
29949
+ })();
29950
+ const timeout = new Promise((_, reject) => {
29951
+ timer = setTimeout(() => reject(new Error(`rendering took longer than ${RENDER_TIMEOUT_MS / 1e3} s`)), RENDER_TIMEOUT_MS);
29952
+ });
29953
+ return await Promise.race([run2, timeout]);
29954
+ } catch (err) {
29955
+ return { status: "unavailable", reason: err instanceof Error ? err.message : String(err) };
29956
+ } finally {
29957
+ clearTimeout(timer);
29958
+ const conn = cdp;
29959
+ conn?.send("Browser.close").catch(() => {
29960
+ });
29961
+ let grace;
29962
+ await Promise.race([exited, new Promise((r) => grace = setTimeout(r, 3e3))]);
29963
+ clearTimeout(grace);
29964
+ conn?.close();
29965
+ if (child.exitCode == null && child.pid != null) child.kill();
29966
+ site.close();
29967
+ let killGrace;
29968
+ await Promise.race([exited, new Promise((r) => killGrace = setTimeout(r, 3e3))]);
29969
+ clearTimeout(killGrace);
29970
+ try {
29971
+ fs10.rmSync(profile, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
29972
+ } catch {
29973
+ }
29974
+ }
29975
+ }
29976
+
29977
+ // src/cli/output.ts
29621
29978
  var PLAIN = /^[A-Za-z0-9_./@:+-][A-Za-z0-9 _./@:+(),=<>*#%?!'-]*$/;
29622
29979
  var json2 = (v) => JSON.stringify(v).replace(/[\u0085\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
29623
29980
  function scalar(v) {
@@ -29629,13 +29986,14 @@ function scalar(v) {
29629
29986
  return json2(s);
29630
29987
  }
29631
29988
  function isPlainObject2(v) {
29632
- return v && typeof v === "object" && !Array.isArray(v);
29989
+ return !!v && typeof v === "object" && !Array.isArray(v);
29633
29990
  }
29991
+ var isList = (v) => Array.isArray(v);
29634
29992
  var KEY = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
29635
29993
  var key = (k) => KEY.test(k) ? k : json2(k);
29636
29994
  function emit(value, indent, lines) {
29637
29995
  const pad = " ".repeat(indent);
29638
- if (Array.isArray(value)) {
29996
+ if (isList(value)) {
29639
29997
  if (!value.length) return;
29640
29998
  for (const item of value) {
29641
29999
  if (isPlainObject2(item)) {
@@ -29655,7 +30013,7 @@ function emit(value, indent, lines) {
29655
30013
  }
29656
30014
  for (const [k, v] of Object.entries(value)) {
29657
30015
  if (v === void 0) continue;
29658
- if (Array.isArray(v)) {
30016
+ if (isList(v)) {
29659
30017
  if (!v.length) continue;
29660
30018
  if (v.every((x) => !isPlainObject2(x) && !Array.isArray(x)) && v.join(", ").length < 100) {
29661
30019
  lines.push(`${pad}${key(k)}: [${v.map(scalar).join(", ")}]`);
@@ -29694,10 +30052,10 @@ function print(obj) {
29694
30052
  process.stdout.write(format(obj) + "\n");
29695
30053
  }
29696
30054
 
29697
- // src/shared/feedback.js
30055
+ // src/shared/feedback.ts
29698
30056
  function locator(lines) {
29699
30057
  return (id) => {
29700
- const l = lines.get(id);
30058
+ const l = id === void 0 ? void 0 : lines.get(id);
29701
30059
  if (!l) return {};
29702
30060
  const first = l.text.split("\n")[0];
29703
30061
  return { line: l.line, source: first.length > 110 ? first.slice(0, 109) + "\u2026" : first };
@@ -29726,10 +30084,10 @@ function describeItem(it2, at2) {
29726
30084
  return { message: it2.text ?? it2.note ?? "" };
29727
30085
  }
29728
30086
 
29729
- // src/cli/main.js
30087
+ // src/cli/main.ts
29730
30088
  init_paths();
29731
30089
 
29732
- // src/cli/components.js
30090
+ // src/cli/components.ts
29733
30091
  init_packs();
29734
30092
  init_catalog();
29735
30093
  var BIN = "austere-axi";
@@ -29826,9 +30184,13 @@ function showComponents(names) {
29826
30184
  return out;
29827
30185
  }
29828
30186
 
29829
- // src/cli/main.js
30187
+ // src/cli/main.ts
29830
30188
  init_packs();
29831
30189
  process.env.OPENUI_TELEMETRY_DISABLED = process.env.OPENUI_TELEMETRY_DISABLED || "1";
30190
+ function messageOf(err) {
30191
+ const e = err;
30192
+ return e.message;
30193
+ }
29832
30194
  var BIN2 = "austere-axi";
29833
30195
  var HELP = `${BIN2} ${VERSION} \u2014 review artifacts written in OpenUI Lang. You write a short
29834
30196
  program instead of a whole HTML page; the reviewer annotates it in the browser;
@@ -29845,85 +30207,69 @@ WORKFLOW
29845
30207
  intro = Card([CardHeader("Title", "Subtitle"), introText])
29846
30208
  introText = MarkDownRenderer("...")
29847
30209
  </script></body></html>
29848
- The CDN tag lets the file open on its own (needs network); review and \`export\` work offline.
29849
- 3. ${BIN2} <file.html> validate + open for review (fix any errors it lists).
29850
- With --no-open the session still exists; it prints the url
29851
- (and a phone url when Tailscale is up) for the user. A browser
29852
- the CLI did not open signs in once with a link from \`${BIN2} login\`.
29853
- 4. ${BIN2} poll <file.html> wait for feedback. Leave it running in the foreground;
29854
- it returns when the reviewer presses Send. If your tool call
29855
- times out first, run the same poll again: nothing is lost.
29856
- 5. Apply feedback by editing ONLY the named statements (keep names stable). The page
29857
- live-updates and highlights what changed. Then reply, passing the poll's cursor:
30210
+ Arguments are positional and typed: follow each signature in spec exactly (order and
30211
+ type; an array stays an array). Pass null to skip an optional one.
30212
+ 3. ${BIN2} <file.html> validate + open for review; fix any errors it lists.
30213
+ --no-open: print the url for the user instead.
30214
+ 4. ${BIN2} poll <file.html> wait for feedback (foreground; if the call times out, poll again).
30215
+ 5. Revise with patch only: send just the changed statements; do not rewrite or re-read
30216
+ the file (each note carries its statement's line and source):
30217
+ ${BIN2} patch <file.html> <<'EOF'
30218
+ risks = Table([...]) same name replaces, new name adds, \`name = null\` deletes
30219
+ EOF
30220
+ If a changed value appears in several statements, patch each of them.
30221
+ Then acknowledge and reply in one step:
29858
30222
  ${BIN2} poll <file.html> --cursor <n> --agent-reply "one line: what changed"
29859
- Feedback stays unread until you acknowledge it (--cursor, or any reply); a poll
29860
- without either returns it again, so feedback lost on the way is not lost.
29861
- 6. On status: ended, stop polling and continue in chat. Never reopen uninvited. Notes
29862
- the reviewer adds later are kept, and the next poll returns them.
30223
+ 6. On status: ended, stop polling and continue in chat. Never reopen uninvited.
29863
30224
 
29864
- FEEDBACK (what poll returns)
29865
- status: feedback | ended | browser_disconnected | interrupted | replaced, plus a next: hint.
29866
- cursor: the number to acknowledge this feedback with.
29867
- items, each one of:
29868
- annotation: <statement> (<Component>) line + source of that statement in the file,
29869
- within (parent statements), selection | excerpt, detail (table row/column, or
29870
- part: the exact piece clicked, e.g. a diff file:+line or a diagram node), note
29871
- action: <@ToAssistant message> form: {<formName>: {<field>: value}}, context
29872
- message: <free text from the composer>
29873
- render_error: <statement> a render failure the reviewer asked you to fix
30225
+ FEEDBACK (poll)
30226
+ status: feedback | ended | browser_disconnected | interrupted | replaced, plus next:
30227
+ cursor: pass it back to acknowledge; unacknowledged feedback is returned again.
30228
+ items: annotation: <statement> (<Component>) with line, source, note
30229
+ action: <@ToAssistant message> with form values | message: <free text>
30230
+ render_error: <statement> sent by the open review page on its own
29874
30231
 
29875
30232
  CHECK verifies syntax, component names, required props, unresolved references and
29876
- statements unreachable from root. Mermaid syntax is only checked when it renders; failures
29877
- show in the reviewer's panel and can come back as render_error. CHECK does not judge how
29878
- a page looks: open it for that. It does warn when an Architecture diagram will not fit the
30233
+ statements unreachable from root. It warns when an Architecture diagram will not fit the
29879
30234
  page (it would scroll sideways).
29880
30235
 
29881
- WHEN TO USE
29882
- Plans, comparisons, decisions, reviews, reports: anything easier to grasp as a page.
29883
- Not for a few sentences of prose.
30236
+ LOOKS
30237
+ The reviewer judges how the page looks. Do not open, screenshot or inspect the page
30238
+ yourself (browser tools, curl, headless runs) unless the user asks: check ok + open is
30239
+ done. Render failures come back through poll. When the user asks you to verify the
30240
+ page, run \`${BIN2} check --render <file>\` (headless Chrome, text report, no image).
30241
+
30242
+ COMMANDS
30243
+ ${BIN2} check <file> validate without a browser: syntax, components, props,
30244
+ references [--fix: declare packs] [--render]
30245
+ ${BIN2} reply <file> reply without polling --agent-reply "..." [--cursor n]
30246
+ ${BIN2} end <file> stop as the agent; later notes wait for the next poll
30247
+ ${BIN2} feedback <file> notes no agent has answered yet [--all] [--peek]
30248
+ ${BIN2} login [file] single-use sign-in link for a browser the CLI did not open
30249
+ ${BIN2} components search <words> | show <Name...> components spec leaves out
30250
+ ${BIN2} <command> --help one command's flags and an example
30251
+ ${BIN2} help all every command, notes and environment
30252
+ `;
30253
+ function helpAll() {
30254
+ const rows = Object.entries(COMMANDS).map(([, c]) => ` ${BIN2} ${c.usage}
30255
+ ${c.summary}`);
30256
+ return `
30257
+ ALL COMMANDS
30258
+ ${rows.join("\n")}
29884
30259
 
29885
- COMMANDS (\`${BIN2} <command> --help\` for one command's flags and an example)
29886
- ${BIN2} sessions + command list
29887
- ${BIN2} <file> open/resume a review [--no-open] [--reopen]
29888
- ${BIN2} poll <file> long-poll for feedback [--cursor n] [--agent-reply "..."] [--agent-reply-file f|-]
29889
- --cursor n acknowledges feedback up to n; --after n returns
29890
- everything after n again. One poll per session; --takeover
29891
- replaces a stuck one
29892
- ${BIN2} reply <file> post a reply without polling --agent-reply "..." | --agent-reply-file f|- [--cursor n]
29893
- ${BIN2} end <file> stop as the agent; the reviewer can still add notes for later
29894
- ${BIN2} close <file> close the review for the owner: no more notes until reopen
29895
- ${BIN2} reopen <file> reopen an ended or closed review
29896
- ${BIN2} login [file] single-use link that signs a browser in as you (SSH, --no-open,
29897
- another browser), to the file's review or the session list
29898
- ${BIN2} feedback <file> feedback no agent has answered yet (sent after polling stopped,
29899
- or in an earlier session) [--all] [--peek]; answer with reply
29900
- ${BIN2} check <file> validate the program (no browser) [--fix: declare the packs it uses]
29901
- ${BIN2} patch <file> merge statements from stdin into the program: same name
29902
- replaces, new name adds, \`name = null\` deletes;
29903
- reports added statements dropped as unreferenced
29904
- ${BIN2} spec [group|pack...] component reference (groups: Layout Content Data Charts Forms Buttons)
29905
- ${BIN2} components [group] every component, including ones spec leaves out
29906
- ${BIN2} components search <words> | show <Name...>
29907
- ${BIN2} components add <npm-package|folder|file.tgz> [--trust] | remove <package>
29908
- packs add components; a page that uses one declares it
29909
- (check --fix writes the <meta name="austere-packs"> tag)
29910
- ${BIN2} components new <dir> | build [dir] write your own pack
29911
- ${BIN2} export <file> single self-contained HTML [--out path]
29912
- ${BIN2} hub on|off|status|list|notes|open the owner's home page: every artifact by project/date, every note
29913
- ${BIN2} share <file> --as "<name>" [--auto] invite link for a teammate on your tailnet. Their notes
29914
- wait for the owner to send them on; --auto skips the wait
29915
- ${BIN2} share list [--links] | revoke <name|id>
29916
- ${BIN2} stop stop the background server
29917
- ${BIN2} server [--persist] run the server in the foreground; --persist: never idle out
29918
- (behind a reverse proxy, under systemd)
29919
- ${BIN2} mcp MCP server on stdio: spec check open poll reply patch end as tools
30260
+ NOTES
30261
+ The CDN script tag lets the file open on its own (needs network); review and export work
30262
+ offline. A browser the CLI did not open gets 403 until it signs in with \`${BIN2} login\`.
30263
+ Mermaid syntax is only checked when it renders. check warns when an Architecture diagram
30264
+ will not fit the page. check --render needs Chrome, Chromium or Edge (or CHROME_PATH).
29920
30265
 
29921
30266
  ENV
29922
30267
  AUSTERE_AXI_PORT (4388) AUSTERE_AXI_STATE_DIR (~/.austere-axi) AUSTERE_AXI_NO_OPEN=1
29923
30268
  AUSTERE_AXI_HOST AUSTERE_AXI_NO_TAILSCALE=1 AUSTERE_AXI_DISCONNECT_GRACE_MS (300000)
29924
30269
  AUSTERE_AXI_IDLE_TIMEOUT_MS (1800000) AUSTERE_AXI_PERSIST=1 AUSTERE_AXI_ALLOWED_HOSTS AUSTERE_AXI_LINK_URL
29925
- AUSTERE_AXI_NPM_REGISTRY
30270
+ AUSTERE_AXI_NPM_REGISTRY CHROME_PATH (check --render)
29926
30271
  `;
30272
+ }
29927
30273
  var COMMANDS = {
29928
30274
  open: {
29929
30275
  usage: "<file> [--no-open] [--reopen]",
@@ -29971,9 +30317,12 @@ var COMMANDS = {
29971
30317
  example: "feedback plan.html"
29972
30318
  },
29973
30319
  check: {
29974
- usage: "check <file> [--fix]",
30320
+ usage: "check <file> [--fix] [--render]",
29975
30321
  summary: "validate the program without a browser: syntax, components, props, references, reachability",
29976
- flags: { fix: 'declare the packs the page uses (writes the <meta name="austere-packs"> tag)' },
30322
+ flags: {
30323
+ fix: 'declare the packs the page uses (writes the <meta name="austere-packs"> tag)',
30324
+ render: "also render it in headless Chrome and report render errors and sideways scrolling as text (needs Chrome or CHROME_PATH); only when the user asks"
30325
+ },
29977
30326
  example: "check plan.html"
29978
30327
  },
29979
30328
  patch: {
@@ -30043,7 +30392,7 @@ function parseArgs(argv) {
30043
30392
  if (i + 1 >= argv.length) return { flags, pos, error: `--${key2} needs a value` };
30044
30393
  val = argv[++i];
30045
30394
  }
30046
- if (key2 === "also-listen") (flags[key2] ||= []).push(val);
30395
+ if (key2 === "also-listen") (flags["also-listen"] ||= []).push(String(val));
30047
30396
  else flags[key2] = val;
30048
30397
  } else if (a === "-h") flags.help = true;
30049
30398
  else if (a === "-v" || a === "-V") flags.version = true;
@@ -30078,7 +30427,7 @@ function needFile(file3, cmd) {
30078
30427
  return null;
30079
30428
  }
30080
30429
  const abs = canonicalFile(file3);
30081
- if (!fs14.existsSync(abs)) {
30430
+ if (!fs15.existsSync(abs)) {
30082
30431
  fail(`no such file: ${abs}`);
30083
30432
  return null;
30084
30433
  }
@@ -30095,7 +30444,7 @@ function readStdin() {
30095
30444
  async function replyText(flags) {
30096
30445
  if (typeof flags["agent-reply-file"] === "string") {
30097
30446
  const f = flags["agent-reply-file"];
30098
- return (f === "-" ? await readStdin() : fs14.readFileSync(f, "utf8")).trim();
30447
+ return (f === "-" ? await readStdin() : fs15.readFileSync(f, "utf8")).trim();
30099
30448
  }
30100
30449
  if (typeof flags["agent-reply"] === "string") return flags["agent-reply"].trim();
30101
30450
  return "";
@@ -30133,8 +30482,8 @@ async function cmdHome() {
30133
30482
  }
30134
30483
  async function cmdOpen(file3, flags) {
30135
30484
  const abs = needFile(file3, "open");
30136
- if (!abs) return;
30137
- const html = fs14.readFileSync(abs, "utf8");
30485
+ if (!abs || file3 === void 0) return;
30486
+ const html = fs15.readFileSync(abs, "utf8");
30138
30487
  const check2 = checkHtml(html, { file: quote(file3) });
30139
30488
  await ensureServer();
30140
30489
  const r = await request("POST", "/api/open", { file: abs, reopen: !!flags.reopen });
@@ -30173,14 +30522,15 @@ function quote(file3) {
30173
30522
  function describeItems(abs, items) {
30174
30523
  let lines = /* @__PURE__ */ new Map();
30175
30524
  try {
30176
- lines = statementLines(fs14.readFileSync(abs, "utf8"));
30525
+ lines = statementLines(fs15.readFileSync(abs, "utf8"));
30177
30526
  } catch {
30178
30527
  }
30179
30528
  const at2 = locator(lines);
30180
30529
  return items.map((it2) => {
30181
30530
  let o;
30182
30531
  try {
30183
- o = describeItem(it2, at2);
30532
+ const item = it2;
30533
+ o = describeItem(item, at2);
30184
30534
  } catch {
30185
30535
  o = { malformed: `could not read this ${typeof it2?.kind === "string" ? it2.kind : "feedback"} item` };
30186
30536
  }
@@ -30189,14 +30539,14 @@ function describeItems(abs, items) {
30189
30539
  }
30190
30540
  function thirdPartyOf(abs) {
30191
30541
  try {
30192
- return pagePacks(fs14.readFileSync(abs, "utf8")).thirdParty.map((p) => `${p.name}@${p.version}`);
30542
+ return pagePacks(fs15.readFileSync(abs, "utf8")).thirdParty.map((p) => `${p.name}@${p.version}`);
30193
30543
  } catch {
30194
30544
  return [];
30195
30545
  }
30196
30546
  }
30197
30547
  var NEXT = {
30198
- feedback: (f, cursor) => `Apply it by editing the named statements in place (keep names stable; the page live-updates and highlights changes). Run \`${BIN2} check ${f}\` if you restructured, then \`${BIN2} poll ${f} --cursor ${cursor} --agent-reply "<one line: what changed>"\`.`,
30199
- feedbackEnded: (f, cursor) => `The reviewer sent this and ended the review. Apply it, then \`${BIN2} reply ${f} --cursor ${cursor} --agent-reply "<what changed>"\` and summarize in chat. Do not poll or reopen.`,
30548
+ feedback: (f, cursor) => `Revise with \`${BIN2} patch ${f}\`: send only the changed statements on stdin (same name replaces, new name adds); do not rewrite or re-read the file. Then \`${BIN2} poll ${f} --cursor ${cursor} --agent-reply "<one line: what changed>"\`.`,
30549
+ feedbackEnded: (f, cursor) => `The reviewer sent this and ended the review. Apply it with \`${BIN2} patch ${f}\` (changed statements only), then \`${BIN2} reply ${f} --cursor ${cursor} --agent-reply "<what changed>"\` and summarize in chat. Do not poll or reopen.`,
30200
30550
  ended: (f) => `The review ended. Stop polling and continue in chat. Notes the reviewer adds later are kept for the next poll. Reopen only if the user asks (\`${BIN2} ${f} --reopen\`).`,
30201
30551
  browser_disconnected: (f) => `Every review tab has been closed for a while. Stop polling and continue in chat; do not end the review. Notes the reviewer adds later are kept, and \`${BIN2} poll ${f}\` returns them.`,
30202
30552
  replaced: () => "Another poll took over this session. Stop polling here.",
@@ -30204,17 +30554,18 @@ var NEXT = {
30204
30554
  timeout: (f) => `No feedback yet. Run \`${BIN2} poll ${f}\` again.`
30205
30555
  };
30206
30556
  function seqFlag(flags, name) {
30207
- return typeof flags[name] === "string" ? flags[name] : void 0;
30557
+ const v = flags[name];
30558
+ return typeof v === "string" ? v : void 0;
30208
30559
  }
30209
30560
  async function cmdPoll(file3, flags) {
30210
30561
  const abs = needFile(file3, "poll");
30211
- if (!abs) return;
30562
+ if (!abs || file3 === void 0) return;
30212
30563
  await ensureServer();
30213
30564
  const reply = await replyText(flags);
30214
30565
  if (!reply && (flags["agent-reply"] !== void 0 || flags["agent-reply-file"] !== void 0)) {
30215
30566
  return fail("the agent reply is empty");
30216
30567
  }
30217
- process.stderr.write(`[${BIN2}] waiting for feedback on ${path14.basename(abs)} (interrupting is safe: feedback stays queued)
30568
+ process.stderr.write(`[${BIN2}] waiting for feedback on ${path15.basename(abs)} (interrupting is safe: feedback stays queued)
30218
30569
  `);
30219
30570
  const ticker = process.stderr.isTTY ? setInterval(() => process.stderr.write(`[${BIN2}] still waiting\u2026
30220
30571
  `), 6e4) : null;
@@ -30229,7 +30580,7 @@ async function cmdPoll(file3, flags) {
30229
30580
  takeover: !!flags.takeover,
30230
30581
  timeoutMs: flags["timeout-ms"] ? Number(flags["timeout-ms"]) : void 0
30231
30582
  });
30232
- } catch (err) {
30583
+ } catch {
30233
30584
  if (ticker) clearInterval(ticker);
30234
30585
  return print({ status: "interrupted", file: abs, next: NEXT.interrupted(quote(file3)) });
30235
30586
  }
@@ -30261,7 +30612,7 @@ async function cmdPoll(file3, flags) {
30261
30612
  const out = { status: d.status || "unknown", file: abs };
30262
30613
  if (d.endedBy) out.ended_by = d.endedBy;
30263
30614
  if (d.closed) out.closed = "by the owner";
30264
- if (NEXT[d.status]) out.next = NEXT[d.status](f);
30615
+ if (d.status && NEXT[d.status]) out.next = NEXT[d.status](f);
30265
30616
  print(out);
30266
30617
  }
30267
30618
  async function cmdReply(file3, flags) {
@@ -30285,7 +30636,7 @@ async function cmdEnd(file3) {
30285
30636
  }
30286
30637
  async function cmdLock(file3, route) {
30287
30638
  const abs = needFile(file3, route);
30288
- if (!abs) return;
30639
+ if (!abs || file3 === void 0) return;
30289
30640
  await ensureServer();
30290
30641
  const r = await request("POST", `/api/${route}`, { file: abs }, { timeoutMs: 1e4 });
30291
30642
  if (r.status === 404) return fail(`no review session for this file; open it first: ${BIN2} ${quote(file3)}`);
@@ -30311,13 +30662,13 @@ function fixPacks(abs, html) {
30311
30662
  return { html, changes };
30312
30663
  }
30313
30664
  const out = setPacksMeta(html, next);
30314
- if (out !== html) fs14.writeFileSync(abs, out);
30665
+ if (out !== html) fs15.writeFileSync(abs, out);
30315
30666
  return { html: out, changes: changes.length ? changes : ["updated integrity hashes"] };
30316
30667
  }
30317
30668
  async function cmdCheck(file3, flags = {}) {
30318
30669
  const abs = needFile(file3, "check");
30319
- if (!abs) return;
30320
- let html = fs14.readFileSync(abs, "utf8");
30670
+ if (!abs || file3 === void 0) return;
30671
+ let html = fs15.readFileSync(abs, "utf8");
30321
30672
  let fixed = [];
30322
30673
  if (flags.fix) ({ html, changes: fixed } = fixPacks(abs, html));
30323
30674
  const result = checkHtml(html, { file: quote(file3) });
@@ -30326,17 +30677,38 @@ async function cmdCheck(file3, flags = {}) {
30326
30677
  if (fixed.length) out.fixed = fixed;
30327
30678
  if (result.errors.length) out.errors = result.errors;
30328
30679
  if (result.warnings.length) out.warnings = result.warnings;
30329
- if (result.ok && result.hasMermaid) out.note = "Mermaid sources are checked only when they render in the review page.";
30680
+ if (flags.render) {
30681
+ if (!result.ok) out.render = "skipped: fix the errors above first";
30682
+ else {
30683
+ const render = await renderOut(html);
30684
+ out.render = render;
30685
+ if (render.errors) out.status = "errors";
30686
+ else if (render.status === "unavailable") out.status = "render_unavailable";
30687
+ }
30688
+ } else if (result.ok && result.hasMermaid) {
30689
+ out.note = "Mermaid sources are checked when the review page renders them; failures come back through poll.";
30690
+ }
30330
30691
  print(out);
30331
- if (!result.ok) process.exitCode = 1;
30692
+ if (out.status !== "ok") process.exitCode = 1;
30693
+ }
30694
+ async function renderOut(html) {
30695
+ const r = await renderCheck(html);
30696
+ if (r.status === "unavailable") return { status: "unavailable", reason: r.reason };
30697
+ const out = { status: r.errors.length ? "errors" : "clean", widths: "1280, 390", statements: r.statements };
30698
+ if (r.errors.length) out.errors = r.errors.map((e) => ({ statement: e.statement || "(page)", message: e.error }));
30699
+ if (r.overflow.length) {
30700
+ out.scrolls_sideways = r.overflow.map((o) => ({ width: o.width, statements: o.statements.length ? o.statements.join(", ") : "(not one statement)" }));
30701
+ }
30702
+ if (r.notices.length) out.notices = r.notices;
30703
+ return out;
30332
30704
  }
30333
30705
  async function cmdPatch(file3) {
30334
30706
  const abs = needFile(file3, "patch");
30335
- if (!abs) return;
30707
+ if (!abs || file3 === void 0) return;
30336
30708
  if (process.stdin.isTTY) return usage(`pipe the changed statements on stdin: ${BIN2} patch ${quote(file3)} <<'EOF' ... EOF`, "patch");
30337
30709
  const patch = await readStdin();
30338
30710
  if (!patch.trim()) return fail("stdin was empty");
30339
- const html = fs14.readFileSync(abs, "utf8");
30711
+ const html = fs15.readFileSync(abs, "utf8");
30340
30712
  const art = readArtifact(html);
30341
30713
  if (art.program == null) return fail('no <script type="text/openui"> block to patch');
30342
30714
  const merged = mergeStatements(art.program.trim(), patch.trim(), "root");
@@ -30344,7 +30716,7 @@ async function cmdPatch(file3) {
30344
30716
  const patched = new Set(splitStatements(patch).filter((s) => !/=\s*null\s*;?$/.test(s.text)).map((s) => s.id));
30345
30717
  const dropped = [...patched].filter((id) => !kept.has(id));
30346
30718
  const next = replaceProgram(html, merged);
30347
- fs14.writeFileSync(abs, next);
30719
+ fs15.writeFileSync(abs, next);
30348
30720
  const result = checkHtml(next, { file: quote(file3) });
30349
30721
  await registerWithHub(abs, result.title, sessionId(abs));
30350
30722
  const out = { status: "patched", file: abs };
@@ -30359,7 +30731,7 @@ function packSpec(p) {
30359
30731
  return lines.join("\n") + "\n";
30360
30732
  }
30361
30733
  function cmdSpec(groups) {
30362
- let text = fs14.readFileSync(assetPath("prompt.md"), "utf8");
30734
+ let text = fs15.readFileSync(assetPath("prompt.md"), "utf8");
30363
30735
  const packs = installedPacks();
30364
30736
  const packArgs = groups.filter((g) => packs.some((p) => p.name === g));
30365
30737
  if (packArgs.length) {
@@ -30393,14 +30765,14 @@ ${trimmed.join("\n")}
30393
30765
  function cmdExport(file3, flags) {
30394
30766
  const abs = needFile(file3, "export");
30395
30767
  if (!abs) return;
30396
- const out = typeof flags.out === "string" ? path14.resolve(flags.out) : abs.replace(/\.html?$/i, "") + ".export.html";
30397
- const { html, notices } = exportHtml(fs14.readFileSync(abs, "utf8"));
30398
- fs14.writeFileSync(out, html);
30768
+ const out = typeof flags.out === "string" ? path15.resolve(flags.out) : abs.replace(/\.html?$/i, "") + ".export.html";
30769
+ const { html, notices } = exportHtml(fs15.readFileSync(abs, "utf8"));
30770
+ fs15.writeFileSync(out, html);
30399
30771
  print({ status: "exported", file: out, bytes: html.length, notices: notices.length ? notices : void 0 });
30400
30772
  }
30401
30773
  function printResult(out) {
30402
30774
  print(out);
30403
- if (out && out.status === "error") process.exitCode = 1;
30775
+ if (out && "status" in out && out.status === "error") process.exitCode = 1;
30404
30776
  }
30405
30777
  async function cmdComponents(sub, args, flags) {
30406
30778
  switch (sub) {
@@ -30417,7 +30789,8 @@ async function cmdComponents(sub, args, flags) {
30417
30789
  let r;
30418
30790
  try {
30419
30791
  r = await addPack2(args[0], { trust: !!flags.trust });
30420
- } catch (err) {
30792
+ } catch (caught) {
30793
+ const err = caught;
30421
30794
  const out = { status: err.code === "untrusted" ? "needs --trust" : "error", error: err.message };
30422
30795
  if (err.network) out.next = `Offline or behind a proxy? \`npm pack ${args[0]}\` downloads it; then \`${BIN2} components add ./<file>.tgz\`.`;
30423
30796
  print(out);
@@ -30454,10 +30827,10 @@ async function cmdComponents(sub, args, flags) {
30454
30827
  pack: r.name,
30455
30828
  dir: r.dir,
30456
30829
  files: r.files,
30457
- next: `cd ${quote(path14.relative(process.cwd(), r.dir) || ".")} && npm install && npm run build, then \`${BIN2} components add .\``
30830
+ next: `cd ${quote(path15.relative(process.cwd(), r.dir) || ".")} && npm install && npm run build, then \`${BIN2} components add .\``
30458
30831
  });
30459
30832
  } catch (err) {
30460
- return fail(err.message);
30833
+ return fail(messageOf(err));
30461
30834
  }
30462
30835
  }
30463
30836
  case "build": {
@@ -30474,7 +30847,7 @@ async function cmdComponents(sub, args, flags) {
30474
30847
  next: `\`${BIN2} components add ${quote(args[0] || ".")}\` to use it here (again after each build), then \`${BIN2} check --fix <file>\` on pages that use it.`
30475
30848
  });
30476
30849
  } catch (err) {
30477
- return fail(err.message);
30850
+ return fail(messageOf(err));
30478
30851
  }
30479
30852
  }
30480
30853
  default: {
@@ -30491,7 +30864,7 @@ function shortDate(ms) {
30491
30864
  }
30492
30865
  async function cmdFeedback(file3, flags) {
30493
30866
  const abs = needFile(file3, "feedback");
30494
- if (!abs) return;
30867
+ if (!abs || file3 === void 0) return;
30495
30868
  const opts = { all: !!flags.all, peek: !!flags.peek };
30496
30869
  let entries = null;
30497
30870
  let status = null;
@@ -30536,7 +30909,7 @@ async function cmdFeedback(file3, flags) {
30536
30909
  file: abs,
30537
30910
  review: status,
30538
30911
  items,
30539
- next: `Apply what still applies (edit the named statements; names are stable), then \`${BIN2} reply ${f} --agent-reply "<what changed>"\`, which marks it addressed. To continue live with the reviewer: \`${BIN2} ${f}\`${status === "ended" ? " --reopen, if the user asks" : ""}, then poll.`
30912
+ next: `Apply what still applies with \`${BIN2} patch ${f}\` (changed statements only; names are stable), then \`${BIN2} reply ${f} --agent-reply "<what changed>"\`, which marks it addressed. To continue live with the reviewer: \`${BIN2} ${f}\`${status === "ended" ? " --reopen, if the user asks" : ""}, then poll.`
30540
30913
  });
30541
30914
  }
30542
30915
  async function cmdHub(sub, args, flags) {
@@ -30565,7 +30938,7 @@ async function cmdHub(sub, args, flags) {
30565
30938
  server: h ? `running on ${port()}${h.persist ? " (persist)" : ""}` : "not running"
30566
30939
  };
30567
30940
  if (hubEnabled() && h) out.url = url2;
30568
- if (fs14.existsSync(hubDbFile())) {
30941
+ if (fs15.existsSync(hubDbFile())) {
30569
30942
  try {
30570
30943
  const hub = await Hub.open();
30571
30944
  try {
@@ -30574,20 +30947,20 @@ async function cmdHub(sub, args, flags) {
30574
30947
  hub.close();
30575
30948
  }
30576
30949
  } catch (err) {
30577
- out.index = `${hubDbFile()} cannot be opened: ${err.message}`;
30950
+ out.index = `${hubDbFile()} cannot be opened: ${messageOf(err)}`;
30578
30951
  }
30579
30952
  }
30580
30953
  if (!hubEnabled()) out.next = `\`${BIN2} hub on\` shows a home page with every artifact and note.`;
30581
30954
  return print(out);
30582
30955
  }
30583
30956
  case "list": {
30584
- if (!fs14.existsSync(hubDbFile())) return print({ artifacts: "none", next: `\`${BIN2} hub on\` starts indexing.` });
30585
- const project = typeof flags.project === "string" ? path14.resolve(flags.project) : "";
30957
+ if (!fs15.existsSync(hubDbFile())) return print({ artifacts: "none", next: `\`${BIN2} hub on\` starts indexing.` });
30958
+ const project = typeof flags.project === "string" ? path15.resolve(flags.project) : "";
30586
30959
  let hub;
30587
30960
  try {
30588
30961
  hub = await Hub.open();
30589
30962
  } catch (err) {
30590
- return fail(`${hubDbFile()} cannot be opened: ${err.message}`);
30963
+ return fail(`${hubDbFile()} cannot be opened: ${messageOf(err)}`);
30591
30964
  }
30592
30965
  let rows;
30593
30966
  try {
@@ -30607,13 +30980,13 @@ async function cmdHub(sub, args, flags) {
30607
30980
  });
30608
30981
  }
30609
30982
  case "notes": {
30610
- if (!fs14.existsSync(hubDbFile())) return print({ notes: "none", next: `\`${BIN2} hub on\` starts indexing.` });
30611
- const project = typeof flags.project === "string" ? path14.resolve(flags.project) : "";
30983
+ if (!fs15.existsSync(hubDbFile())) return print({ notes: "none", next: `\`${BIN2} hub on\` starts indexing.` });
30984
+ const project = typeof flags.project === "string" ? path15.resolve(flags.project) : "";
30612
30985
  let hub;
30613
30986
  try {
30614
30987
  hub = await Hub.open();
30615
30988
  } catch (err) {
30616
- return fail(`${hubDbFile()} cannot be opened: ${err.message}`);
30989
+ return fail(`${hubDbFile()} cannot be opened: ${messageOf(err)}`);
30617
30990
  }
30618
30991
  let rows;
30619
30992
  try {
@@ -30628,7 +31001,7 @@ async function cmdHub(sub, args, flags) {
30628
31001
  text: r.text || void 0,
30629
31002
  target: r.target || void 0,
30630
31003
  status: r.status === "addressed" ? "answered" : r.status === "held" ? "held" : "unanswered",
30631
- page: r.title || path14.basename(r.file),
31004
+ page: r.title || path15.basename(r.file),
30632
31005
  file: r.file,
30633
31006
  sent: shortDate(r.at)
30634
31007
  })),
@@ -30721,7 +31094,7 @@ async function cmdLogin(file3) {
30721
31094
  if (!abs) return;
30722
31095
  }
30723
31096
  await ensureServer();
30724
- if (abs) {
31097
+ if (abs && file3) {
30725
31098
  const r = await request("GET", "/api/sessions");
30726
31099
  if (r.status !== 200) return fail(r.data?.error || `login failed (${r.status})`);
30727
31100
  const s = (r.data.sessions || []).find((x) => x.file === abs);
@@ -30752,7 +31125,7 @@ async function main() {
30752
31125
  let name = null;
30753
31126
  if (cmd !== void 0) {
30754
31127
  if (Object.hasOwn(COMMANDS, cmd)) name = cmd;
30755
- else if (/\.html?$/i.test(cmd) || fs14.existsSync(cmd)) name = "open";
31128
+ else if (/\.html?$/i.test(cmd) || fs15.existsSync(cmd)) name = "open";
30756
31129
  else if (flags.help) return process.stdout.write(HELP);
30757
31130
  else return usage(`unknown command: ${cmd}. One of: ${Object.keys(COMMANDS).join(" ")}, or a page file`);
30758
31131
  }
@@ -30808,14 +31181,16 @@ async function main() {
30808
31181
  return cmdOpen(rest[0], flags);
30809
31182
  case "help":
30810
31183
  if (!rest[0]) return process.stdout.write(HELP);
30811
- if (!Object.hasOwn(COMMANDS, rest[0])) return usage(`no command named ${rest[0]}. One of: ${Object.keys(COMMANDS).join(" ")}`, "help");
31184
+ if (rest[0] === "all") return process.stdout.write(HELP + helpAll());
31185
+ if (!Object.hasOwn(COMMANDS, rest[0])) return usage(`no command named ${rest[0]}. One of: all ${Object.keys(COMMANDS).join(" ")}`, "help");
30812
31186
  return commandHelp(rest[0]);
30813
31187
  default:
30814
31188
  return cmdOpen(cmd, flags);
30815
31189
  }
30816
31190
  }
30817
- main().catch((err) => {
30818
- process.stdout.write(format({ status: "error", error: String(err?.message || err) }) + "\n");
31191
+ main().catch((caught) => {
31192
+ const err = caught;
31193
+ process.stdout.write(format({ status: "error", error: String(err?.message || caught) }) + "\n");
30819
31194
  process.exitCode = 1;
30820
31195
  });
30821
31196
  /*! Bundled license information: