@kehto/paja 0.6.3 → 0.8.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.
@@ -32,7 +32,12 @@ var PAJA_SIMULATION_DOMAINS = [
32
32
  "cvm",
33
33
  "inc"
34
34
  ];
35
- var DEFAULT_RELAY_URLS = ["wss://relay.kehto.dev"];
35
+ var DEFAULT_RELAY_URLS = [
36
+ "wss://relay.damus.io",
37
+ "wss://nos.lol",
38
+ "wss://relay.primal.net",
39
+ "wss://relay.snort.social"
40
+ ];
36
41
  var DEFAULT_CONFIG_VALUES = {
37
42
  runtime: "kehto paja",
38
43
  mode: "development",
@@ -46,7 +51,7 @@ function normalizePajaSimulation(raw) {
46
51
  const domains = Object.fromEntries(
47
52
  PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true])
48
53
  );
49
- const relayMode = raw?.relay?.mode ?? (domains.relay ? "memory" : "disabled");
54
+ const relayMode = raw?.relay?.mode ?? (domains.relay ? "live" : "disabled");
50
55
  if (!domains.relay && relayMode !== "disabled") {
51
56
  throw new PajaSimulationError('Invalid simulation: relay.mode must be "disabled" when capabilities.domains.relay is false.');
52
57
  }
@@ -94,8 +99,8 @@ function normalizePajaSimulation(raw) {
94
99
  throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is "fixed".');
95
100
  }
96
101
  const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;
97
- if (relayMode === "memory" && relayUrls.length === 0) {
98
- throw new PajaSimulationError('Invalid simulation: relay.urls must contain at least one URL when relay.mode is "memory".');
102
+ if (relayMode !== "disabled" && relayUrls.length === 0) {
103
+ throw new PajaSimulationError("Invalid simulation: relay.urls must contain at least one URL when relay.mode is enabled.");
99
104
  }
100
105
  for (const url of relayUrls) {
101
106
  if (typeof url !== "string" || url.trim().length === 0) {
@@ -106,6 +111,13 @@ function normalizePajaSimulation(raw) {
106
111
  if (uploadMode === "memory" && uploadRail.length === 0) {
107
112
  throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is "memory".');
108
113
  }
114
+ const uploadServers = normalizeUploadServers(raw?.upload?.servers ?? []);
115
+ const discoverServers = raw?.upload?.discoverServers ?? uploadMode === "blossom";
116
+ const maxBytes = raw?.upload?.maxBytes;
117
+ if (maxBytes !== void 0 && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {
118
+ throw new PajaSimulationError("Invalid simulation: upload.maxBytes must be a positive safe integer.");
119
+ }
120
+ const mimeTypes = normalizeMimeTypes(raw?.upload?.mimeTypes);
109
121
  return {
110
122
  capabilities: {
111
123
  domains,
@@ -134,7 +146,11 @@ function normalizePajaSimulation(raw) {
134
146
  },
135
147
  upload: {
136
148
  mode: uploadMode,
137
- rail: uploadRail
149
+ ...uploadMode === "memory" ? { rail: uploadRail } : {},
150
+ servers: uploadServers,
151
+ discoverServers,
152
+ ...maxBytes !== void 0 ? { maxBytes } : {},
153
+ ...mimeTypes ? { mimeTypes } : {}
138
154
  },
139
155
  media: {
140
156
  enabled: mediaEnabled
@@ -159,12 +175,54 @@ function normalizePajaSimulation(raw) {
159
175
  };
160
176
  }
161
177
  function summarizePajaSimulation(simulation) {
162
- const relay = simulation.relay.mode === "memory" ? `relay:${simulation.relay.urls.length}` : "relay:off";
178
+ const relay = simulation.relay.mode === "disabled" ? "relay:off" : `relay:${simulation.relay.mode}:${simulation.relay.urls.length}`;
163
179
  const identity = simulation.identity.mode === "fixed" ? "identity:fixed" : "identity:anon";
164
180
  const storage = `storage:${simulation.storage.mode}`;
181
+ const upload = simulation.upload.mode === "memory" ? "upload:memory:simulator" : simulation.upload.mode === "blossom" ? `upload:blossom:${simulation.upload.servers.length}` : "upload:off";
165
182
  const theme = `theme:${simulation.theme.mode}`;
166
183
  const disabled = simulation.capabilities.disabledDomains.length > 0 ? `off:${simulation.capabilities.disabledDomains.join(",")}` : "off:none";
167
- return `${identity} ${relay} ${storage} ${theme} ${disabled}`;
184
+ return `${identity} ${relay} ${storage} ${upload} ${theme} ${disabled}`;
185
+ }
186
+ function normalizeUploadServers(servers) {
187
+ const normalized = [];
188
+ for (const value of servers) {
189
+ if (typeof value !== "string" || value.trim().length === 0) {
190
+ throw new PajaSimulationError("Invalid simulation: upload.servers entries must be non-empty URLs.");
191
+ }
192
+ let url;
193
+ try {
194
+ url = new URL(value.trim());
195
+ } catch {
196
+ throw new PajaSimulationError(`Invalid simulation: upload server "${value}" is not a valid URL.`);
197
+ }
198
+ if (url.username || url.password) {
199
+ throw new PajaSimulationError("Invalid simulation: upload server URLs must not contain credentials.");
200
+ }
201
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) {
202
+ throw new PajaSimulationError("Invalid simulation: upload servers require HTTPS, except loopback HTTP for development.");
203
+ }
204
+ if (url.search || url.hash) {
205
+ throw new PajaSimulationError("Invalid simulation: upload server URLs must not contain a query or fragment.");
206
+ }
207
+ const result = url.href.replace(/\/+$/, "");
208
+ if (!normalized.includes(result)) normalized.push(result);
209
+ }
210
+ return normalized;
211
+ }
212
+ function isLoopbackHost(hostname) {
213
+ return hostname === "localhost" || hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(hostname);
214
+ }
215
+ function normalizeMimeTypes(mimeTypes) {
216
+ if (mimeTypes === void 0) return void 0;
217
+ const normalized = [];
218
+ for (const value of mimeTypes) {
219
+ if (typeof value !== "string" || value.trim().length === 0) {
220
+ throw new PajaSimulationError("Invalid simulation: upload.mimeTypes entries must be non-empty strings.");
221
+ }
222
+ const mimeType = value.trim();
223
+ if (!normalized.includes(mimeType)) normalized.push(mimeType);
224
+ }
225
+ return normalized;
168
226
  }
169
227
  function isHexPubkey(value) {
170
228
  return /^[0-9a-fA-F]{64}$/.test(value);
@@ -416,22 +474,32 @@ function renderPajaHtml(config) {
416
474
  --text: #f4f0df;
417
475
  --muted: #a9ad9f;
418
476
  --accent: #d8c36a;
477
+ --paja-console-column: minmax(320px, 380px);
419
478
  }
420
479
  * { box-sizing: border-box; }
421
480
  html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
422
481
  body { height: 100vh; display: grid; grid-template-rows: 38px minmax(0, 1fr) 30px; overflow: hidden; }
423
482
  .bar { display: flex; align-items: center; gap: 14px; min-width: 0; padding: 0 12px; background: var(--bar); border-color: var(--line); }
424
- .top { border-bottom: 1px solid var(--line); }
483
+ .top { display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); align-items: stretch; gap: 0; padding: 0; border-bottom: 1px solid var(--line); }
484
+ .top-console { min-width: 0; display: flex; align-items: center; gap: 14px; padding: 0 12px; border-right: 1px solid var(--line); }
485
+ .top-stage { min-width: 0; display: flex; align-items: stretch; gap: 14px; padding: 0 12px 0 0; overflow: hidden; }
425
486
  .bottom { border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }
426
487
  .brand { font-weight: 700; letter-spacing: 0; color: var(--accent); white-space: nowrap; }
427
488
  .brand-product { color: var(--text); }
428
489
  .target { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
429
490
  .spacer { flex: 1; min-width: 0; }
491
+ .tabs { display: flex; align-items: stretch; align-self: flex-end; gap: 4px; min-width: 120px; max-width: min(100%, 760px); overflow-x: auto; scrollbar-width: thin; }
492
+ .tabs:empty { display: none; }
493
+ .tab { min-width: 116px; max-width: 240px; height: 30px; display: grid; grid-template-columns: minmax(0, 1fr) 24px 24px; align-items: center; gap: 2px; border: 1px solid var(--line); border-bottom-color: transparent; background: #151815; color: var(--muted); border-radius: 5px 5px 0 0; padding: 0 2px 0 9px; }
494
+ .tab[data-active="true"] { color: var(--text); border-color: var(--accent); border-bottom-color: #151815; background: #20241f; }
495
+ .tab-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; text-align: left; }
496
+ .tab-share, .tab-close { width: 20px; height: 20px; padding: 0; border: 0; background: transparent; color: var(--muted); display: inline-grid; place-items: center; }
497
+ .tab-share:hover, .tab-close:hover { color: var(--text); background: #2d302b; border-color: transparent; }
430
498
  button { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; padding: 0 10px; border-radius: 4px; font: inherit; cursor: pointer; }
431
499
  button:hover { border-color: var(--accent); }
432
500
  label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); white-space: nowrap; }
433
501
  select, input { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; border-radius: 4px; font: inherit; }
434
- main { min-height: 0; display: grid; grid-template-columns: minmax(320px, 380px) minmax(0, 1fr); }
502
+ main { min-height: 0; display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); }
435
503
  .console { min-height: 0; overflow: auto; border-right: 1px solid var(--line); background: #121512; padding: 10px; display: flex; flex-direction: column; gap: 12px; }
436
504
  .section { display: grid; gap: 8px; }
437
505
  .section-title { color: var(--accent); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0; }
@@ -454,10 +522,22 @@ function renderPajaHtml(config) {
454
522
  .log-body { min-width: 0; display: grid; gap: 2px; }
455
523
  .log-type { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
456
524
  .log-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #e6a5a5; }
457
- .stage { min-width: 0; min-height: 0; }
525
+ .stage { min-width: 0; min-height: 0; position: relative; background: #050705; }
526
+ .empty-stage { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); font-size: 12px; }
527
+ .empty-stage[hidden] { display: none; }
528
+ .tab-panel { position: absolute; inset: 0; min-width: 0; min-height: 0; }
529
+ .tab-panel[hidden] { display: none; }
458
530
  iframe { width: 100%; height: 100%; border: 0; background: white; display: block; }
459
531
  code { color: var(--text); }
532
+ .dialog-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgb(0 0 0 / 0.58); }
533
+ .dialog-backdrop[hidden] { display: none; }
534
+ .dialog { width: min(420px, 100%); border: 1px solid var(--line); border-radius: 6px; background: #181b19; box-shadow: 0 18px 60px rgb(0 0 0 / 0.45); padding: 16px; display: grid; gap: 14px; }
535
+ .dialog-title { font-weight: 700; color: var(--text); }
536
+ .dialog-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }
460
537
  @media (max-width: 900px) {
538
+ .top { grid-template-columns: minmax(0, 1fr); }
539
+ .top-console { display: none; }
540
+ .top-stage { padding-left: 0; }
461
541
  main { grid-template-columns: 1fr; grid-template-rows: minmax(240px, 40vh) minmax(0, 1fr); }
462
542
  .console { border-right: 0; border-bottom: 1px solid var(--line); }
463
543
  }
@@ -465,16 +545,21 @@ function renderPajaHtml(config) {
465
545
  </head>
466
546
  <body>
467
547
  <header class="bar top">
468
- <div class="brand">@kehto/<span class="brand-product">paja</span></div>
469
- <div class="target" title="${targetLabel}">${targetLabel}</div>
470
- <div class="spacer"></div>
471
- <label>theme
472
- <select id="simulation-theme" aria-label="Simulation theme">
473
- <option value="dark"${config.simulation.theme.mode === "dark" ? " selected" : ""}>dark</option>
474
- <option value="light"${config.simulation.theme.mode === "light" ? " selected" : ""}>light</option>
475
- </select>
476
- </label>
477
- <button type="button" id="reload-target">Reload</button>
548
+ <div class="top-console">
549
+ <div class="brand">@kehto/<span class="brand-product">paja</span></div>
550
+ <div class="target" title="${targetLabel}">${targetLabel}</div>
551
+ </div>
552
+ <div class="top-stage">
553
+ <div class="tabs" id="napplet-tabs" role="tablist" aria-label="Loaded napplets"></div>
554
+ <div class="spacer"></div>
555
+ <label>theme
556
+ <select id="simulation-theme" aria-label="Simulation theme">
557
+ <option value="dark"${config.simulation.theme.mode === "dark" ? " selected" : ""}>dark</option>
558
+ <option value="light"${config.simulation.theme.mode === "light" ? " selected" : ""}>light</option>
559
+ </select>
560
+ </label>
561
+ <button type="button" id="reload-target">Reload</button>
562
+ </div>
478
563
  </header>
479
564
  <main>
480
565
  <aside class="console" aria-label="Paja development controls">
@@ -501,10 +586,9 @@ function renderPajaHtml(config) {
501
586
  <div class="log-list" id="message-log" aria-live="polite"></div>
502
587
  </section>
503
588
  </aside>
504
- <section class="stage">
505
- <iframe id="napplet-frame" title="Napplet ${config.target.mode === "runtime-pointer" ? "runtime" : "development"} target" sandbox="allow-scripts" data-target-url="${targetLabel}"></iframe>
506
- </section>
589
+ ${renderStage(config, targetLabel)}
507
590
  </main>
591
+ ${renderDuplicateDialog()}
508
592
  <footer class="bar bottom">
509
593
  <span>mode: <code>${escapeHtml(getModeLabel(config))}</code></span>
510
594
  <span>hmr: <code>${config.target.hmrStrategy}</code></span>
@@ -517,6 +601,28 @@ function renderPajaHtml(config) {
517
601
  </body>
518
602
  </html>`;
519
603
  }
604
+ function renderStage(config, targetLabel) {
605
+ if (config.target.mode === "runtime-pointer") {
606
+ return `<section class="stage" id="napplet-stage" aria-label="Loaded napplet runtimes">
607
+ <div class="empty-stage" id="empty-runtime-stage">Load a napplet pointer to start a runtime tab.</div>
608
+ </section>`;
609
+ }
610
+ return `<section class="stage" id="napplet-stage">
611
+ <iframe id="napplet-frame" title="Napplet development target" sandbox="allow-scripts" data-target-url="${targetLabel}"></iframe>
612
+ </section>`;
613
+ }
614
+ function renderDuplicateDialog() {
615
+ return `<div class="dialog-backdrop" id="duplicate-pointer-dialog" hidden>
616
+ <div class="dialog" role="dialog" aria-modal="true" aria-labelledby="duplicate-pointer-title">
617
+ <div class="dialog-title" id="duplicate-pointer-title">this napplet is already running.</div>
618
+ <div class="dialog-actions">
619
+ <button type="button" id="duplicate-load-again">load it again</button>
620
+ <button type="button" id="duplicate-open-tab">open it in tab</button>
621
+ <button type="button" id="duplicate-cancel">cancel</button>
622
+ </div>
623
+ </div>
624
+ </div>`;
625
+ }
520
626
  function renderPointerControls(config) {
521
627
  if (config.target.mode !== "runtime-pointer") return "";
522
628
  const value = escapeAttribute(config.target.pointer?.value ?? "");
@@ -849,8 +955,7 @@ async function startPajaServer(input) {
849
955
  configJson = JSON.stringify(hostConfig, null, 2);
850
956
  return hostConfig;
851
957
  };
852
- const server = createServer((request, response) => {
853
- const requestUrl = request.url ?? "/";
958
+ const handleRequest = async (requestUrl, response) => {
854
959
  if (requestUrl === "/" || requestUrl.startsWith("/?")) {
855
960
  response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
856
961
  response.end(html);
@@ -861,6 +966,15 @@ async function startPajaServer(input) {
861
966
  response.end(configJson);
862
967
  return;
863
968
  }
969
+ if (requestUrl === "/__kehto/target.html") {
970
+ const targetHtml = await fetchTargetHtml(hostConfig.target.url);
971
+ response.writeHead(200, {
972
+ "cache-control": "no-store",
973
+ "content-type": "text/html; charset=utf-8"
974
+ });
975
+ response.end(targetHtml);
976
+ return;
977
+ }
864
978
  if (requestUrl === "/__kehto/browser-host.js") {
865
979
  const browserScript = readBrowserHostScript();
866
980
  response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
@@ -869,6 +983,13 @@ async function startPajaServer(input) {
869
983
  }
870
984
  response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
871
985
  response.end("Not found");
986
+ };
987
+ const server = createServer((request, response) => {
988
+ void handleRequest(request.url ?? "/", response).catch((error) => {
989
+ const message = error instanceof Error ? error.message : String(error);
990
+ response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
991
+ response.end(message);
992
+ });
872
993
  });
873
994
  await listen(server, options.host, options.port);
874
995
  const servedOptions = withBoundPort(options, getBoundPort(server, options.port));
@@ -887,6 +1008,17 @@ async function startPajaServer(input) {
887
1008
  function readBrowserHostScript() {
888
1009
  return readFileSync2(new URL("./browser-host.js", import.meta.url), "utf8");
889
1010
  }
1011
+ async function fetchTargetHtml(targetUrl) {
1012
+ const response = await fetch(targetUrl, {
1013
+ headers: {
1014
+ accept: "text/html, application/xhtml+xml;q=0.9, */*;q=0.8"
1015
+ }
1016
+ });
1017
+ if (!response.ok) {
1018
+ throw new Error(`Target ${targetUrl} returned ${response.status} ${response.statusText}`);
1019
+ }
1020
+ return response.text();
1021
+ }
890
1022
  function listen(server, host, port) {
891
1023
  return new Promise((resolve2, reject) => {
892
1024
  server.once("error", reject);
@@ -898,13 +1030,18 @@ function listen(server, host, port) {
898
1030
  }
899
1031
  function close(server) {
900
1032
  return new Promise((resolve2, reject) => {
1033
+ const forceClose = setTimeout(() => {
1034
+ server.closeAllConnections?.();
1035
+ }, 500);
901
1036
  server.close((error) => {
1037
+ clearTimeout(forceClose);
902
1038
  if (error) {
903
1039
  reject(error);
904
1040
  return;
905
1041
  }
906
1042
  resolve2();
907
1043
  });
1044
+ server.closeIdleConnections?.();
908
1045
  });
909
1046
  }
910
1047
  function getBoundPort(server, fallback) {
@@ -969,4 +1106,4 @@ export {
969
1106
  waitForTargetUrl,
970
1107
  startPajaServer
971
1108
  };
972
- //# sourceMappingURL=chunk-ADMLM2LH.js.map
1109
+ //# sourceMappingURL=chunk-NFJCS5PG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config-file.ts","../src/simulation.ts","../src/options.ts","../src/host-page.ts","../src/runtime-resolver.ts","../src/parity.ts","../src/readiness.ts","../src/server.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { PajaRawOptions } from './options.js';\nimport type { PajaSimulationRawOptions } from './simulation.js';\nimport { PajaSimulationError } from './simulation.js';\n\n/**\n * Load a JSON Paja config file from disk.\n *\n * @param configPath - Path to a JSON config file.\n * @returns Raw options parsed from the config file.\n */\nexport function loadPajaConfigFile(configPath: string): PajaRawOptions {\n const resolved = resolve(configPath);\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(resolved, 'utf8'));\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n throw new PajaSimulationError(`Unable to read --config \"${configPath}\": ${detail}`);\n }\n\n if (!isRecord(parsed)) {\n throw new PajaSimulationError(`Invalid --config \"${configPath}\": expected a JSON object.`);\n }\n\n return parsed as PajaRawOptions;\n}\n\n/**\n * Resolve raw options by merging an optional config file with explicit values.\n *\n * @param raw - CLI/config options, optionally including `configPath`.\n * @returns Raw options with config-file defaults applied.\n */\nexport function resolvePajaRawOptions(raw: PajaRawOptions): PajaRawOptions {\n if (!raw.configPath?.trim()) return raw;\n return mergePajaRawOptions(loadPajaConfigFile(raw.configPath), raw);\n}\n\n/**\n * Merge two raw Paja option objects, with override values taking precedence.\n *\n * @param base - Base options, usually from a config file.\n * @param override - Explicit options, usually from CLI args.\n * @returns Merged raw options.\n */\nexport function mergePajaRawOptions(\n base: PajaRawOptions,\n override: PajaRawOptions,\n): PajaRawOptions {\n return {\n targetUrl: override.targetUrl ?? base.targetUrl,\n command: override.command ?? base.command,\n host: override.host ?? base.host,\n port: override.port ?? base.port,\n readyTimeoutMs: override.readyTimeoutMs ?? base.readyTimeoutMs,\n configPath: override.configPath ?? base.configPath,\n simulation: mergeSimulationOptions(base.simulation, override.simulation),\n };\n}\n\nfunction mergeSimulationOptions(\n base: PajaSimulationRawOptions | undefined,\n override: PajaSimulationRawOptions | undefined,\n): PajaSimulationRawOptions | undefined {\n if (!base) return override;\n if (!override) return base;\n\n return {\n capabilities: {\n ...base.capabilities,\n ...override.capabilities,\n domains: {\n ...base.capabilities?.domains,\n ...override.capabilities?.domains,\n },\n },\n acl: { ...base.acl, ...override.acl },\n firewall: { ...base.firewall, ...override.firewall },\n identity: { ...base.identity, ...override.identity },\n relay: { ...base.relay, ...override.relay },\n storage: { ...base.storage, ...override.storage },\n cache: { ...base.cache, ...override.cache },\n upload: { ...base.upload, ...override.upload },\n media: { ...base.media, ...override.media },\n notifications: { ...base.notifications, ...override.notifications },\n config: {\n ...base.config,\n ...override.config,\n values: { ...base.config?.values, ...override.config?.values },\n },\n theme: {\n ...base.theme,\n ...override.theme,\n values: { ...base.theme?.values, ...override.theme?.values },\n },\n intent: { ...base.intent, ...override.intent },\n cvm: { ...base.cvm, ...override.cvm },\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","/** JSON primitive accepted in Paja simulation config values. */\nexport type JsonPrimitive = string | number | boolean | null;\n/** JSON value accepted in Paja simulation config values. */\nexport type JsonValue = JsonPrimitive | JsonValue[] | { readonly [key: string]: JsonValue };\n/** JSON object used for Paja simulation fixtures and config/theme values. */\nexport type JsonRecord = Record<string, JsonValue>;\n\n/** Capability domain names Paja can advertise or disable. */\nexport type PajaCapabilityDomain =\n | 'relay'\n | 'outbox'\n | 'storage'\n | 'identity'\n | 'keys'\n | 'config'\n | 'resource'\n | 'theme'\n | 'notify'\n | 'media'\n | 'upload'\n | 'intent'\n | 'count'\n | 'link'\n | 'common'\n | 'lists'\n | 'serial'\n | 'ble'\n | 'webrtc'\n | 'cvm'\n | 'inc';\n\n/** Raw simulation controls accepted from CLI args or JSON config. */\nexport interface PajaSimulationRawOptions {\n /** Per-domain capability overrides. */\n readonly capabilities?: {\n readonly domains?: Partial<Record<PajaCapabilityDomain, boolean>>;\n };\n /** ACL behavior mode for simulated services. */\n readonly acl?: {\n readonly mode?: 'allow' | 'deny';\n };\n readonly firewall?: {\n readonly mode?: 'allow' | 'deny' | 'observe';\n };\n readonly identity?: {\n readonly mode?: 'anonymous' | 'fixed';\n readonly pubkey?: string;\n };\n readonly relay?: {\n readonly mode?: 'live' | 'memory' | 'disabled';\n readonly urls?: readonly string[];\n readonly fixtures?: readonly JsonRecord[];\n };\n readonly storage?: {\n readonly mode?: 'local' | 'memory' | 'disabled';\n };\n readonly cache?: {\n readonly mode?: 'memory' | 'disabled';\n };\n readonly upload?: {\n readonly mode?: 'memory' | 'blossom' | 'disabled';\n readonly rail?: string;\n readonly servers?: readonly string[];\n readonly discoverServers?: boolean;\n readonly maxBytes?: number;\n readonly mimeTypes?: readonly string[];\n };\n readonly media?: {\n readonly enabled?: boolean;\n };\n readonly notifications?: {\n readonly enabled?: boolean;\n readonly grant?: boolean;\n };\n readonly config?: {\n readonly values?: JsonRecord;\n };\n readonly theme?: {\n readonly mode?: 'dark' | 'light';\n readonly values?: JsonRecord;\n };\n readonly intent?: {\n readonly enabled?: boolean;\n };\n readonly cvm?: {\n readonly enabled?: boolean;\n };\n}\n\n/** Fully normalized Paja simulation model served to the browser host. */\nexport interface PajaSimulation {\n /** Capability domains and disabled-domain summary. */\n readonly capabilities: {\n readonly domains: Record<PajaCapabilityDomain, boolean>;\n readonly disabledDomains: readonly PajaCapabilityDomain[];\n };\n readonly acl: {\n readonly mode: 'allow' | 'deny';\n };\n readonly firewall: {\n readonly mode: 'allow' | 'deny' | 'observe';\n };\n readonly identity: {\n readonly mode: 'anonymous' | 'fixed';\n readonly pubkey: string;\n };\n readonly relay: {\n readonly mode: 'live' | 'memory' | 'disabled';\n readonly urls: readonly string[];\n readonly fixtures: readonly JsonRecord[];\n };\n readonly storage: {\n readonly mode: 'local' | 'memory' | 'disabled';\n };\n readonly cache: {\n readonly mode: 'memory' | 'disabled';\n };\n readonly upload: {\n readonly mode: 'memory' | 'blossom' | 'disabled';\n readonly rail?: string;\n readonly servers: readonly string[];\n readonly discoverServers: boolean;\n readonly maxBytes?: number;\n readonly mimeTypes?: readonly string[];\n };\n readonly media: {\n readonly enabled: boolean;\n };\n readonly notifications: {\n readonly enabled: boolean;\n readonly grant: boolean;\n };\n readonly config: {\n readonly values: JsonRecord;\n };\n readonly theme: {\n readonly mode: 'dark' | 'light';\n readonly values: JsonRecord;\n };\n readonly intent: {\n readonly enabled: boolean;\n };\n readonly cvm: {\n readonly enabled: boolean;\n };\n}\n\n/** Thrown when Paja simulation options are internally inconsistent. */\nexport class PajaSimulationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaSimulationError';\n }\n}\n\n/** Capability domains exposed by Paja simulation controls. */\nexport const PAJA_SIMULATION_DOMAINS: readonly PajaCapabilityDomain[] = [\n 'relay',\n 'outbox',\n 'storage',\n 'identity',\n 'keys',\n 'config',\n 'resource',\n 'theme',\n 'notify',\n 'media',\n 'upload',\n 'intent',\n 'count',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'cvm',\n 'inc',\n];\n\nconst DEFAULT_RELAY_URLS = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.primal.net',\n 'wss://relay.snort.social',\n] as const;\nconst DEFAULT_CONFIG_VALUES: JsonRecord = {\n runtime: 'kehto paja',\n mode: 'development',\n target: 'single-window',\n};\n\nconst DEFAULT_THEME_VALUES: JsonRecord = {\n title: 'Kehto Paja',\n};\n\n/**\n * Normalize raw simulation options into a complete deterministic model.\n *\n * @param raw - Partial simulation options from CLI args or config.\n * @returns Complete simulation model.\n */\nexport function normalizePajaSimulation(\n raw: PajaSimulationRawOptions | undefined,\n): PajaSimulation {\n const domainOverrides = raw?.capabilities?.domains ?? {};\n const domains = Object.fromEntries(\n PAJA_SIMULATION_DOMAINS.map((domain) => [domain, domainOverrides[domain] ?? true]),\n ) as Record<PajaCapabilityDomain, boolean>;\n\n const relayMode = raw?.relay?.mode ?? (domains.relay ? 'live' : 'disabled');\n if (!domains.relay && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.relay is false.');\n }\n if (!domains.outbox && relayMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: relay.mode must be \"disabled\" when capabilities.domains.outbox is false.');\n }\n if (relayMode === 'disabled') {\n domains.relay = false;\n domains.outbox = false;\n }\n\n const uploadMode = raw?.upload?.mode ?? (domains.upload ? 'memory' : 'disabled');\n if (!domains.upload && uploadMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: upload.mode must be \"disabled\" when capabilities.domains.upload is false.');\n }\n if (uploadMode === 'disabled') domains.upload = false;\n\n const intentEnabled = raw?.intent?.enabled ?? domains.intent;\n if (!domains.intent && intentEnabled) {\n throw new PajaSimulationError('Invalid simulation: intent.enabled must be false when capabilities.domains.intent is false.');\n }\n if (!intentEnabled) domains.intent = false;\n\n if (!domains.relay) domains.count = false;\n\n const mediaEnabled = raw?.media?.enabled ?? domains.media;\n if (!domains.media && mediaEnabled) {\n throw new PajaSimulationError('Invalid simulation: media.enabled must be false when capabilities.domains.media is false.');\n }\n if (!mediaEnabled) domains.media = false;\n\n const cvmEnabled = raw?.cvm?.enabled ?? domains.cvm;\n if (!domains.cvm && cvmEnabled) {\n throw new PajaSimulationError('Invalid simulation: cvm.enabled must be false when capabilities.domains.cvm is false.');\n }\n if (!cvmEnabled) domains.cvm = false;\n\n const notificationsEnabled = raw?.notifications?.enabled ?? domains.notify;\n if (!domains.notify && notificationsEnabled) {\n throw new PajaSimulationError('Invalid simulation: notifications.enabled must be false when capabilities.domains.notify is false.');\n }\n if (!notificationsEnabled) domains.notify = false;\n\n const storageMode = raw?.storage?.mode ?? (domains.storage ? 'local' : 'disabled');\n if (!domains.storage && storageMode !== 'disabled') {\n throw new PajaSimulationError('Invalid simulation: storage.mode must be \"disabled\" when capabilities.domains.storage is false.');\n }\n if (storageMode === 'disabled') domains.storage = false;\n\n const identityMode = raw?.identity?.mode ?? 'anonymous';\n const pubkey = raw?.identity?.pubkey?.trim() ?? '';\n if (identityMode === 'fixed' && !isHexPubkey(pubkey)) {\n throw new PajaSimulationError('Invalid simulation: identity.pubkey must be a 64-character hex string when identity.mode is \"fixed\".');\n }\n\n const relayUrls = raw?.relay?.urls ?? DEFAULT_RELAY_URLS;\n if (relayMode !== 'disabled' && relayUrls.length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls must contain at least one URL when relay.mode is enabled.');\n }\n for (const url of relayUrls) {\n if (typeof url !== 'string' || url.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: relay.urls entries must be non-empty strings.');\n }\n }\n\n const uploadRail = raw?.upload?.rail?.trim() || 'dev-memory';\n if (uploadMode === 'memory' && uploadRail.length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.rail must be non-empty when upload.mode is \"memory\".');\n }\n const uploadServers = normalizeUploadServers(raw?.upload?.servers ?? []);\n const discoverServers = raw?.upload?.discoverServers ?? uploadMode === 'blossom';\n const maxBytes = raw?.upload?.maxBytes;\n if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)) {\n throw new PajaSimulationError('Invalid simulation: upload.maxBytes must be a positive safe integer.');\n }\n const mimeTypes = normalizeMimeTypes(raw?.upload?.mimeTypes);\n\n return {\n capabilities: {\n domains,\n disabledDomains: PAJA_SIMULATION_DOMAINS.filter((domain) => !domains[domain]),\n },\n acl: {\n mode: raw?.acl?.mode ?? 'allow',\n },\n firewall: {\n mode: raw?.firewall?.mode ?? 'observe',\n },\n identity: {\n mode: identityMode,\n pubkey: identityMode === 'fixed' ? pubkey : '',\n },\n relay: {\n mode: relayMode,\n urls: relayUrls.map((url) => url.trim()),\n fixtures: raw?.relay?.fixtures ?? [],\n },\n storage: {\n mode: storageMode,\n },\n cache: {\n mode: raw?.cache?.mode ?? 'memory',\n },\n upload: {\n mode: uploadMode,\n ...(uploadMode === 'memory' ? { rail: uploadRail } : {}),\n servers: uploadServers,\n discoverServers,\n ...(maxBytes !== undefined ? { maxBytes } : {}),\n ...(mimeTypes ? { mimeTypes } : {}),\n },\n media: {\n enabled: mediaEnabled,\n },\n notifications: {\n enabled: notificationsEnabled,\n grant: raw?.notifications?.grant ?? true,\n },\n config: {\n values: { ...DEFAULT_CONFIG_VALUES, ...raw?.config?.values },\n },\n theme: {\n mode: raw?.theme?.mode ?? 'dark',\n values: { ...DEFAULT_THEME_VALUES, ...raw?.theme?.values },\n },\n intent: {\n enabled: intentEnabled,\n },\n cvm: {\n enabled: cvmEnabled,\n },\n };\n}\n\n/**\n * Format a compact, human-readable simulation summary.\n *\n * @param simulation - Normalized simulation model.\n * @returns One-line summary used by CLI and host-page chrome.\n */\nexport function summarizePajaSimulation(simulation: PajaSimulation): string {\n const relay = simulation.relay.mode === 'disabled' ? 'relay:off' : `relay:${simulation.relay.mode}:${simulation.relay.urls.length}`;\n const identity = simulation.identity.mode === 'fixed' ? 'identity:fixed' : 'identity:anon';\n const storage = `storage:${simulation.storage.mode}`;\n const upload = simulation.upload.mode === 'memory'\n ? 'upload:memory:simulator'\n : simulation.upload.mode === 'blossom'\n ? `upload:blossom:${simulation.upload.servers.length}`\n : 'upload:off';\n const theme = `theme:${simulation.theme.mode}`;\n const disabled = simulation.capabilities.disabledDomains.length > 0\n ? `off:${simulation.capabilities.disabledDomains.join(',')}`\n : 'off:none';\n return `${identity} ${relay} ${storage} ${upload} ${theme} ${disabled}`;\n}\n\n/** Normalize an ordered list of shell-owned Blossom server base URLs. */\nexport function normalizeUploadServers(servers: readonly string[]): string[] {\n const normalized: string[] = [];\n for (const value of servers) {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.servers entries must be non-empty URLs.');\n }\n let url: URL;\n try {\n url = new URL(value.trim());\n } catch {\n throw new PajaSimulationError(`Invalid simulation: upload server \"${value}\" is not a valid URL.`);\n }\n if (url.username || url.password) {\n throw new PajaSimulationError('Invalid simulation: upload server URLs must not contain credentials.');\n }\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopbackHost(url.hostname))) {\n throw new PajaSimulationError('Invalid simulation: upload servers require HTTPS, except loopback HTTP for development.');\n }\n if (url.search || url.hash) {\n throw new PajaSimulationError('Invalid simulation: upload server URLs must not contain a query or fragment.');\n }\n const result = url.href.replace(/\\/+$/, '');\n if (!normalized.includes(result)) normalized.push(result);\n }\n return normalized;\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost'\n || hostname === '[::1]'\n || /^127(?:\\.\\d{1,3}){3}$/.test(hostname);\n}\n\nfunction normalizeMimeTypes(mimeTypes: readonly string[] | undefined): string[] | undefined {\n if (mimeTypes === undefined) return undefined;\n const normalized: string[] = [];\n for (const value of mimeTypes) {\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new PajaSimulationError('Invalid simulation: upload.mimeTypes entries must be non-empty strings.');\n }\n const mimeType = value.trim();\n if (!normalized.includes(mimeType)) normalized.push(mimeType);\n }\n return normalized;\n}\n\nfunction isHexPubkey(value: string): boolean {\n return /^[0-9a-fA-F]{64}$/.test(value);\n}\n","import {\n normalizePajaSimulation,\n type PajaSimulation,\n type PajaSimulationRawOptions,\n} from './simulation.js';\n\n/** Command configuration for a managed Paja target process. */\nexport type PajaCommand =\n /** Spawn an executable with argv preserved exactly after `--`. */\n | { readonly mode: 'argv'; readonly argv: readonly string[] }\n /** Run a shell command string. */\n | { readonly mode: 'shell'; readonly command: string };\n\n/** Raw, unnormalized options accepted from CLI arguments or config files. */\nexport interface PajaRawOptions {\n /** App dev-server URL that Paja should load. */\n readonly targetUrl?: string;\n /** Optional managed command to start before serving Paja. */\n readonly command?: PajaCommand;\n /** Hostname or IP address for the Paja HTTP server. */\n readonly host?: string;\n /** Runtime HTTP port. */\n readonly port?: number | string;\n /** Target readiness timeout in milliseconds. */\n readonly readyTimeoutMs?: number | string;\n /** Optional JSON config file path. */\n readonly configPath?: string;\n /** Simulation controls for injected shell domains. */\n readonly simulation?: PajaSimulationRawOptions;\n}\n\n/** Paja target loading mode. */\nexport type PajaTargetMode = 'iframe-url' | 'runtime-pointer';\n/** Hot-module-reload strategy for the hosted target. */\nexport type PajaHmrStrategy = 'iframe-target-url' | 'none';\n\n/** Runtime-pointer resolution config embedded into the Paja host page. */\nexport interface PajaPointerRuntimeConfig {\n /** Optional naddr/nevent pointer to resolve at boot. */\n readonly value?: string;\n /** Relay hints used to resolve the pointer. */\n readonly relays: readonly string[];\n /** Blossom server hints used for artifact blob fetches. */\n readonly blossomServers: readonly string[];\n /** Maximum pointer-resolution wait in milliseconds. */\n readonly maxWaitMs: number;\n}\n\n/** Normalized Paja runtime options. */\nexport interface PajaOptions {\n /** Absolute target URL for iframe-url mode. */\n readonly targetUrl: string;\n /** Optional managed target command. */\n readonly command?: PajaCommand;\n /** Paja server host. */\n readonly host: string;\n /** Paja server port. */\n readonly port: number;\n /** Target readiness timeout in milliseconds. */\n readonly readyTimeoutMs: number;\n /** Optional config path retained for host diagnostics. */\n readonly configPath?: string;\n /** Normalized simulation model. */\n readonly simulation: PajaSimulation;\n /** Whether Paja owns a managed command or attaches to an external target. */\n readonly mode: 'external-target' | 'managed-command';\n}\n\n/** Serialized host-page config served to the browser runtime. */\nexport interface PajaHostConfig {\n /** Config schema version. */\n readonly version: 1;\n /** Window identity used for the development target. */\n readonly window: {\n readonly id: string;\n readonly dTag: string;\n readonly aggregateHash: string;\n };\n readonly target: {\n readonly mode: PajaTargetMode;\n readonly url: string;\n readonly hmrStrategy: PajaHmrStrategy;\n readonly command?: PajaCommand;\n readonly pointer?: PajaPointerRuntimeConfig;\n };\n readonly runtime: {\n readonly host: string;\n readonly port: number;\n readonly readyTimeoutMs: number;\n readonly configPath?: string;\n readonly reloadToken: string;\n readonly createdAt: string;\n };\n readonly chrome: {\n readonly topBar: true;\n readonly bottomBar: true;\n readonly sidePanels: false;\n };\n readonly simulation: PajaSimulation;\n}\n\n/** Thrown when raw Paja options cannot be normalized. */\nexport class PajaOptionsError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PajaOptionsError';\n }\n}\n\n/** Default loopback host for the Paja HTTP server. */\nexport const DEFAULT_PAJA_HOST = '127.0.0.1';\n/** Default Paja HTTP port. */\nexport const DEFAULT_PAJA_PORT = 5197;\n/** Default readiness timeout for the target URL. */\nexport const DEFAULT_READY_TIMEOUT_MS = 30_000;\n/** Default development window id used in host config. */\nexport const DEFAULT_PAJA_WINDOW_ID = 'kehto-paja-window';\n/** Default development `d` tag used for target identity. */\nexport const DEFAULT_PAJA_DTAG = 'dev-target';\n/** Default development aggregate hash used for target identity. */\nexport const DEFAULT_PAJA_AGGREGATE_HASH = 'paja';\n/** Default pointer resolution wait in runtime-pointer mode. */\nexport const DEFAULT_PAJA_RUNTIME_WAIT_MS = 5_000;\n\n/**\n * Normalize raw CLI/config options into a validated Paja runtime config.\n *\n * @param raw - Raw options from CLI parsing or a config file.\n * @returns Fully normalized runtime options.\n */\nexport function normalizePajaOptions(raw: PajaRawOptions): PajaOptions {\n const targetUrl = normalizeTargetUrl(raw.targetUrl);\n const host = normalizeHost(raw.host);\n const port = normalizePort(raw.port, 'port');\n const readyTimeoutMs = normalizePositiveInteger(\n raw.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,\n 'ready-timeout',\n );\n const command = normalizeCommand(raw.command);\n const configPath = raw.configPath?.trim();\n const simulation = normalizePajaSimulation(raw.simulation);\n\n return {\n targetUrl,\n command,\n host,\n port,\n readyTimeoutMs,\n configPath: configPath && configPath.length > 0 ? configPath : undefined,\n simulation,\n mode: command ? 'managed-command' : 'external-target',\n };\n}\n\n/**\n * Create browser host-page config for iframe-url mode.\n *\n * @param options - Normalized Paja options.\n * @param now - Clock value used for reload token and timestamps.\n * @returns Serializable host-page config.\n */\nexport function createPajaHostConfig(\n options: PajaOptions,\n now: Date = new Date(),\n): PajaHostConfig {\n const base = createPajaHostConfigBase(options.simulation);\n return {\n ...base,\n target: {\n mode: 'iframe-url',\n url: options.targetUrl,\n hmrStrategy: 'iframe-target-url',\n command: options.command,\n },\n runtime: {\n host: options.host,\n port: options.port,\n readyTimeoutMs: options.readyTimeoutMs,\n configPath: options.configPath,\n reloadToken: createReloadToken(now),\n createdAt: now.toISOString(),\n },\n };\n}\n\n/**\n * Create browser host-page config for runtime-pointer mode.\n *\n * @param options - Pointer, relay, and Blossom hints.\n * @param now - Clock value used for reload token and timestamps.\n * @returns Serializable host-page config.\n */\nexport function createPajaRuntimeHostConfig(\n options: {\n readonly pointer?: string;\n readonly relays?: readonly string[];\n readonly blossomServers?: readonly string[];\n readonly maxWaitMs?: number;\n } = {},\n now: Date = new Date(),\n): PajaHostConfig {\n const base = createPajaHostConfigBase(normalizePajaSimulation({}));\n return {\n ...base,\n target: {\n mode: 'runtime-pointer',\n url: 'about:blank',\n hmrStrategy: 'none',\n pointer: {\n ...(options.pointer ? { value: options.pointer } : {}),\n relays: [...(options.relays ?? [])],\n blossomServers: [...(options.blossomServers ?? [])],\n maxWaitMs: normalizePositiveInteger(\n options.maxWaitMs ?? DEFAULT_PAJA_RUNTIME_WAIT_MS,\n 'runtime-wait',\n ),\n },\n },\n runtime: {\n host: 'static',\n port: 0,\n readyTimeoutMs: 1,\n reloadToken: createReloadToken(now),\n createdAt: now.toISOString(),\n },\n };\n}\n\n/**\n * Format the local Paja runtime URL for display and CLI output.\n *\n * @param options - Host and port pair.\n * @returns HTTP URL for the Paja runtime.\n */\nexport function formatPajaUrl(options: Pick<PajaOptions, 'host' | 'port'>): string {\n return `http://${options.host}:${options.port}/`;\n}\n\nfunction normalizeTargetUrl(value: string | undefined): string {\n const raw = value?.trim();\n if (!raw) {\n throw new PajaOptionsError('Missing --target-url. Provide the napplet app URL that the iframe should load.');\n }\n\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new PajaOptionsError(`Invalid --target-url \"${raw}\". Expected an absolute http(s) URL.`);\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new PajaOptionsError(`Invalid --target-url \"${raw}\". Only http: and https: URLs are supported.`);\n }\n\n return parsed.href;\n}\n\nfunction normalizeHost(value: string | undefined): string {\n const host = value?.trim() || DEFAULT_PAJA_HOST;\n if (host.length === 0 || host.includes('/')) {\n throw new PajaOptionsError(`Invalid --host \"${value ?? ''}\". Provide a hostname or IP address.`);\n }\n return host;\n}\n\nfunction normalizePort(value: number | string | undefined, label: string): number {\n return normalizeIntegerInRange(value ?? DEFAULT_PAJA_PORT, label, 0, 65_535);\n}\n\nfunction normalizePositiveInteger(value: number | string, label: string, max = Number.MAX_SAFE_INTEGER): number {\n return normalizeIntegerInRange(value, label, 1, max);\n}\n\nfunction createPajaHostConfigBase(simulation: PajaSimulation): Pick<PajaHostConfig, 'version' | 'window' | 'chrome' | 'simulation'> {\n return {\n version: 1,\n window: {\n id: DEFAULT_PAJA_WINDOW_ID,\n dTag: DEFAULT_PAJA_DTAG,\n aggregateHash: DEFAULT_PAJA_AGGREGATE_HASH,\n },\n chrome: {\n topBar: true,\n bottomBar: true,\n sidePanels: false,\n },\n simulation,\n };\n}\n\nfunction normalizeIntegerInRange(value: number | string, label: string, min: number, max: number): number {\n const parsed = typeof value === 'number' ? value : Number(value);\n if (!Number.isInteger(parsed) || parsed < min || parsed > max) {\n throw new PajaOptionsError(`Invalid --${label} \"${String(value)}\". Expected an integer from ${min} to ${max}.`);\n }\n return parsed;\n}\n\nfunction normalizeCommand(command: PajaCommand | undefined): PajaCommand | undefined {\n if (!command) return undefined;\n\n if (command.mode === 'argv') {\n const argv = command.argv.map((part) => part.trim()).filter((part) => part.length > 0);\n if (argv.length === 0) {\n throw new PajaOptionsError('Command mode requires at least one command argument after --.');\n }\n return { mode: 'argv', argv };\n }\n\n const shellCommand = command.command.trim();\n if (shellCommand.length === 0) {\n throw new PajaOptionsError('--command requires a non-empty command string.');\n }\n return { mode: 'shell', command: shellCommand };\n}\n\nfunction createReloadToken(now: Date): string {\n const iso = now.toISOString();\n return `reload-${iso.replaceAll(/[^0-9A-Za-z]/g, '')}`;\n}\n","import type { PajaHostConfig } from './options.js';\nimport { summarizePajaSimulation } from './simulation.js';\n\n/**\n * Render the browser host page for a Paja runtime config.\n *\n * @param config - Serializable host-page config.\n * @returns Complete HTML document served by Paja.\n */\nexport function renderPajaHtml(config: PajaHostConfig): string {\n const configJson = escapeJsonForHtml(JSON.stringify(config));\n const targetLabel = escapeAttribute(getTargetLabel(config));\n\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>@kehto/paja</title>\n <style>\n :root {\n color-scheme: dark;\n --bg: #101211;\n --bar: #181b19;\n --line: #30352f;\n --text: #f4f0df;\n --muted: #a9ad9f;\n --accent: #d8c36a;\n --paja-console-column: minmax(320px, 380px);\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; min-height: 100%; background: var(--bg); color: var(--text); font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; }\n body { height: 100vh; display: grid; grid-template-rows: 38px minmax(0, 1fr) 30px; overflow: hidden; }\n .bar { display: flex; align-items: center; gap: 14px; min-width: 0; padding: 0 12px; background: var(--bar); border-color: var(--line); }\n .top { display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); align-items: stretch; gap: 0; padding: 0; border-bottom: 1px solid var(--line); }\n .top-console { min-width: 0; display: flex; align-items: center; gap: 14px; padding: 0 12px; border-right: 1px solid var(--line); }\n .top-stage { min-width: 0; display: flex; align-items: stretch; gap: 14px; padding: 0 12px 0 0; overflow: hidden; }\n .bottom { border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }\n .brand { font-weight: 700; letter-spacing: 0; color: var(--accent); white-space: nowrap; }\n .brand-product { color: var(--text); }\n .target { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }\n .spacer { flex: 1; min-width: 0; }\n .tabs { display: flex; align-items: stretch; align-self: flex-end; gap: 4px; min-width: 120px; max-width: min(100%, 760px); overflow-x: auto; scrollbar-width: thin; }\n .tabs:empty { display: none; }\n .tab { min-width: 116px; max-width: 240px; height: 30px; display: grid; grid-template-columns: minmax(0, 1fr) 24px 24px; align-items: center; gap: 2px; border: 1px solid var(--line); border-bottom-color: transparent; background: #151815; color: var(--muted); border-radius: 5px 5px 0 0; padding: 0 2px 0 9px; }\n .tab[data-active=\"true\"] { color: var(--text); border-color: var(--accent); border-bottom-color: #151815; background: #20241f; }\n .tab-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; text-align: left; }\n .tab-share, .tab-close { width: 20px; height: 20px; padding: 0; border: 0; background: transparent; color: var(--muted); display: inline-grid; place-items: center; }\n .tab-share:hover, .tab-close:hover { color: var(--text); background: #2d302b; border-color: transparent; }\n button { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; padding: 0 10px; border-radius: 4px; font: inherit; cursor: pointer; }\n button:hover { border-color: var(--accent); }\n label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); white-space: nowrap; }\n select, input { border: 1px solid var(--line); color: var(--text); background: #20241f; height: 26px; border-radius: 4px; font: inherit; }\n main { min-height: 0; display: grid; grid-template-columns: var(--paja-console-column) minmax(0, 1fr); }\n .console { min-height: 0; overflow: auto; border-right: 1px solid var(--line); background: #121512; padding: 10px; display: flex; flex-direction: column; gap: 12px; }\n .section { display: grid; gap: 8px; }\n .section-title { color: var(--accent); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0; }\n .switch-grid, .acl-grid { display: flex; flex-wrap: wrap; gap: 6px; }\n .toggle { height: 24px; padding: 0 8px; color: var(--muted); }\n .toggle[data-enabled=\"true\"] { color: var(--text); border-color: #5f724f; background: #24301f; }\n .toggle[data-enabled=\"false\"] { color: #8d9187; border-color: #453536; background: #241d1d; }\n .signer { color: var(--muted); word-break: break-all; font-size: 12px; }\n .signer-controls { display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto; gap: 6px; }\n .signer-controls button[data-active=\"true\"] { border-color: var(--accent); color: var(--text); background: #2a2a1d; }\n .signer-controls input { min-width: 0; padding: 0 8px; }\n .pointer-controls { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px; }\n .pointer-status { min-width: 0; color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }\n .log-tools { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px; }\n .log-list { min-height: 160px; max-height: 38vh; overflow: auto; border: 1px solid var(--line); border-radius: 4px; background: #0b0d0b; }\n .log-row { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 6px; padding: 5px 7px; border-bottom: 1px solid #1b1f1a; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; }\n .log-row[data-error=\"true\"] { background: #1d1414; }\n .log-row:last-child { border-bottom: 0; }\n .log-dir { color: var(--muted); }\n .log-body { min-width: 0; display: grid; gap: 2px; }\n .log-type { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .log-detail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #e6a5a5; }\n .stage { min-width: 0; min-height: 0; position: relative; background: #050705; }\n .empty-stage { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); font-size: 12px; }\n .empty-stage[hidden] { display: none; }\n .tab-panel { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n .tab-panel[hidden] { display: none; }\n iframe { width: 100%; height: 100%; border: 0; background: white; display: block; }\n code { color: var(--text); }\n .dialog-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 24px; background: rgb(0 0 0 / 0.58); }\n .dialog-backdrop[hidden] { display: none; }\n .dialog { width: min(420px, 100%); border: 1px solid var(--line); border-radius: 6px; background: #181b19; box-shadow: 0 18px 60px rgb(0 0 0 / 0.45); padding: 16px; display: grid; gap: 14px; }\n .dialog-title { font-weight: 700; color: var(--text); }\n .dialog-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; }\n @media (max-width: 900px) {\n .top { grid-template-columns: minmax(0, 1fr); }\n .top-console { display: none; }\n .top-stage { padding-left: 0; }\n main { grid-template-columns: 1fr; grid-template-rows: minmax(240px, 40vh) minmax(0, 1fr); }\n .console { border-right: 0; border-bottom: 1px solid var(--line); }\n }\n </style>\n </head>\n <body>\n <header class=\"bar top\">\n <div class=\"top-console\">\n <div class=\"brand\">@kehto/<span class=\"brand-product\">paja</span></div>\n <div class=\"target\" title=\"${targetLabel}\">${targetLabel}</div>\n </div>\n <div class=\"top-stage\">\n <div class=\"tabs\" id=\"napplet-tabs\" role=\"tablist\" aria-label=\"Loaded napplets\"></div>\n <div class=\"spacer\"></div>\n <label>theme\n <select id=\"simulation-theme\" aria-label=\"Simulation theme\">\n <option value=\"dark\"${config.simulation.theme.mode === 'dark' ? ' selected' : ''}>dark</option>\n <option value=\"light\"${config.simulation.theme.mode === 'light' ? ' selected' : ''}>light</option>\n </select>\n </label>\n <button type=\"button\" id=\"reload-target\">Reload</button>\n </div>\n </header>\n <main>\n <aside class=\"console\" aria-label=\"Paja development controls\">\n ${renderPointerControls(config)}\n <section class=\"section\">\n <div class=\"section-title\">Interfaces</div>\n <div class=\"switch-grid\" id=\"interface-toggles\"></div>\n </section>\n <section class=\"section\">\n <div class=\"section-title\">ACL</div>\n <div class=\"acl-grid\" id=\"acl-controls\"></div>\n </section>\n <section class=\"section\">\n <div class=\"section-title\">Signer</div>\n <div class=\"signer\" id=\"signer-status\">loading</div>\n <div class=\"signer-controls\" id=\"signer-controls\"></div>\n </section>\n <section class=\"section\">\n <div class=\"section-title\">Messages</div>\n <div class=\"log-tools\">\n <input id=\"message-filter\" type=\"search\" autocomplete=\"off\" placeholder=\"filter messages\" aria-label=\"Filter message log\">\n <button type=\"button\" id=\"clear-log\">Clear</button>\n </div>\n <div class=\"log-list\" id=\"message-log\" aria-live=\"polite\"></div>\n </section>\n </aside>\n ${renderStage(config, targetLabel)}\n </main>\n ${renderDuplicateDialog()}\n <footer class=\"bar bottom\">\n <span>mode: <code>${escapeHtml(getModeLabel(config))}</code></span>\n <span>hmr: <code>${config.target.hmrStrategy}</code></span>\n <span>runtime: <code>${escapeHtml(config.runtime.host)}:${config.runtime.port}</code></span>\n <span>sim: <code id=\"simulation-status\">${escapeHtml(summarizePajaSimulation(config.simulation))}</code></span>\n <span>state: <code id=\"lifecycle-status\">booting</code></span>\n </footer>\n <script type=\"application/json\" id=\"kehto-paja-config\">${configJson}</script>\n <script type=\"module\" src=\"./__kehto/browser-host.js\"></script>\n </body>\n</html>`;\n}\n\nfunction renderStage(config: PajaHostConfig, targetLabel: string): string {\n if (config.target.mode === 'runtime-pointer') {\n return `<section class=\"stage\" id=\"napplet-stage\" aria-label=\"Loaded napplet runtimes\">\n <div class=\"empty-stage\" id=\"empty-runtime-stage\">Load a napplet pointer to start a runtime tab.</div>\n </section>`;\n }\n return `<section class=\"stage\" id=\"napplet-stage\">\n <iframe id=\"napplet-frame\" title=\"Napplet development target\" sandbox=\"allow-scripts\" data-target-url=\"${targetLabel}\"></iframe>\n </section>`;\n}\n\nfunction renderDuplicateDialog(): string {\n return `<div class=\"dialog-backdrop\" id=\"duplicate-pointer-dialog\" hidden>\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"duplicate-pointer-title\">\n <div class=\"dialog-title\" id=\"duplicate-pointer-title\">this napplet is already running.</div>\n <div class=\"dialog-actions\">\n <button type=\"button\" id=\"duplicate-load-again\">load it again</button>\n <button type=\"button\" id=\"duplicate-open-tab\">open it in tab</button>\n <button type=\"button\" id=\"duplicate-cancel\">cancel</button>\n </div>\n </div>\n </div>`;\n}\n\nfunction renderPointerControls(config: PajaHostConfig): string {\n if (config.target.mode !== 'runtime-pointer') return '';\n const value = escapeAttribute(config.target.pointer?.value ?? '');\n return `<section class=\"section\" id=\"runtime-pointer-section\">\n <div class=\"section-title\">Pointer</div>\n <form class=\"pointer-controls\" id=\"runtime-pointer-form\">\n <input id=\"runtime-pointer-input\" type=\"text\" inputmode=\"url\" autocomplete=\"off\" spellcheck=\"false\" placeholder=\"naddr or nevent\" aria-label=\"Runtime napplet pointer\" value=\"${value}\">\n <button type=\"submit\" id=\"runtime-pointer-load\">Load</button>\n </form>\n <div class=\"pointer-status\" id=\"runtime-pointer-status\" aria-live=\"polite\">idle</div>\n </section>`;\n}\n\nfunction getModeLabel(config: PajaHostConfig): string {\n if (config.target.mode === 'runtime-pointer') return 'runtime-pointer';\n return config.target.command ? 'managed-command' : 'external-target';\n}\n\nfunction getTargetLabel(config: PajaHostConfig): string {\n if (config.target.mode === 'runtime-pointer') {\n return config.target.pointer?.value ?? 'runtime pointer';\n }\n return config.target.url;\n}\n\nfunction escapeAttribute(value: string): string {\n return escapeHtml(value).replaceAll('\"', '&quot;');\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;');\n}\n\nfunction escapeJsonForHtml(value: string): string {\n return value.replaceAll('<', '\\\\u003c');\n}\n","import type { NostrEvent } from 'nostr-tools';\nimport type { Filter } from 'nostr-tools/filter';\nimport { SimplePool } from 'nostr-tools/pool';\nimport { decode } from 'nostr-tools/nip19';\nimport {\n fetchBlob,\n isNappletManifestKind,\n NAPPLET_KIND_NAMED,\n NAPPLET_KINDS,\n openNappletArtifactCache,\n resolveNapplet,\n type ResolvedNapplet,\n} from '@kehto/nip/5d';\n\n/** Named/addressable NIP-5D napplet manifest kind (`35129`). */\nexport const PAJA_NAPPLET_MANIFEST_KIND = NAPPLET_KIND_NAMED;\n/** All NIP-5D napplet manifest kinds accepted by Paja pointers (`5129`, `15129`, `35129`). */\nexport const PAJA_NAPPLET_MANIFEST_KINDS = NAPPLET_KINDS;\n\n/** Decoded naddr/nevent pointer accepted by Paja runtime-pointer mode. */\nexport type PajaDecodedPointer =\n | {\n readonly type: 'naddr';\n readonly value: string;\n readonly identifier: string;\n readonly pubkey: string;\n readonly kind: number;\n readonly relays: readonly string[];\n }\n | {\n readonly type: 'nevent';\n readonly value: string;\n readonly id: string;\n readonly author?: string;\n readonly kind?: number;\n readonly relays: readonly string[];\n };\n\n/** Fully resolved pointer plus verified napplet artifact bytes. */\nexport interface PajaResolvedPointer {\n /** Decoded pointer input. */\n readonly pointer: PajaDecodedPointer;\n /** Manifest event resolved from relays. */\n readonly event: NostrEvent;\n /** Relay URLs queried for the pointer. */\n readonly relays: readonly string[];\n /** Blossom server hints used for blob fetches. */\n readonly blossomServers: readonly string[];\n /** Resolved manifest `d` tag. */\n readonly dTag: string;\n /** Verified NIP-5A aggregate hash. */\n readonly aggregateHash: string;\n /** Verified target `/index.html` content. */\n readonly indexHtml: string;\n /** Parsed NIP-5D manifest. */\n readonly manifest: ResolvedNapplet['manifest'];\n}\n\n/** Relay-pool contract for runtime-pointer resolution. */\nexport interface PajaPointerRelayPool {\n /** Query relays synchronously for matching manifest events. */\n querySync(\n relays: string[],\n filter: Filter,\n params?: { readonly maxWait?: number; readonly label?: string; readonly id?: string },\n ): Promise<NostrEvent[]>;\n close?(relays: string[]): void;\n destroy?(): void;\n}\n\n/** Options for resolving a Paja runtime pointer. */\nexport interface ResolvePajaPointerOptions {\n /** Relay pool override; defaults to a `nostr-tools` `SimplePool`. */\n readonly pool?: PajaPointerRelayPool;\n /** Fetch override used for Blossom blob URLs. */\n readonly fetcher?: (url: string) => Promise<Response>;\n /** Extra relay hints appended to pointer relays. */\n readonly relays?: readonly string[];\n /** Extra Blossom server hints appended during artifact fetch. */\n readonly blossomServers?: readonly string[];\n /** Maximum relay query wait in milliseconds. */\n readonly maxWaitMs?: number;\n}\n\n/**\n * Decode a NIP-19 naddr/nevent pointer for Paja runtime-pointer mode.\n *\n * @param value - NIP-19 pointer string.\n * @returns Structured pointer metadata.\n */\nexport function decodePajaPointer(value: string): PajaDecodedPointer {\n const trimmed = value.trim();\n const decoded = decode(trimmed);\n if (decoded.type === 'naddr') {\n return {\n type: 'naddr',\n value: trimmed,\n identifier: decoded.data.identifier,\n pubkey: decoded.data.pubkey,\n kind: decoded.data.kind,\n relays: [...(decoded.data.relays ?? [])],\n };\n }\n if (decoded.type === 'nevent') {\n return {\n type: 'nevent',\n value: trimmed,\n id: decoded.data.id,\n ...(decoded.data.author ? { author: decoded.data.author } : {}),\n ...(decoded.data.kind ? { kind: decoded.data.kind } : {}),\n relays: [...(decoded.data.relays ?? [])],\n };\n }\n throw new Error(`Expected naddr or nevent pointer, got ${decoded.type}.`);\n}\n\n/**\n * Resolve a Paja runtime pointer into verified napplet HTML and manifest data.\n *\n * @param value - NIP-19 naddr or nevent pointer.\n * @param options - Relay, fetch, and Blossom overrides.\n * @returns Verified napplet artifact ready for srcdoc injection.\n */\nexport async function resolvePajaPointer(\n value: string,\n options: ResolvePajaPointerOptions = {},\n): Promise<PajaResolvedPointer> {\n const pointer = decodePajaPointer(value);\n const relays = uniqueRelayUrls([...pointer.relays, ...(options.relays ?? [])]);\n if (relays.length === 0) {\n throw new Error('Pointer does not include relay hints; add a relay-bearing naddr or nevent.');\n }\n\n const pool = options.pool ?? new SimplePool();\n const ownsPool = options.pool === undefined;\n try {\n const event = await resolvePointerEvent(pointer, pool, relays, options.maxWaitMs ?? 5_000);\n const blossomServers = [...(options.blossomServers ?? [])];\n const resolved = await resolveNapplet({\n event,\n cache: await openNappletArtifactCache({ requireStorageEstimate: false }),\n fetchBlob: (sha256Hex, servers) =>\n fetchBlob([...servers, ...blossomServers], sha256Hex, async (url) => {\n const response = await (options.fetcher ?? fetch)(url);\n if (!response.ok) throw new Error(`[paja-runtime] blob ${url}: ${response.status}`);\n return new Uint8Array(await response.arrayBuffer());\n }),\n });\n return {\n pointer,\n event,\n relays,\n blossomServers,\n dTag: resolved.dTag,\n aggregateHash: resolved.aggregateHash,\n indexHtml: resolved.indexHtml,\n manifest: resolved.manifest,\n };\n } finally {\n if (ownsPool) {\n pool.close?.(relays);\n pool.destroy?.();\n }\n }\n}\n\n/**\n * Inject a restrictive connect-src CSP for runtime-pointer srcdoc output.\n *\n * @param html - Verified target HTML.\n * @param origins - Origins the resolved target may connect to.\n * @returns HTML with a CSP meta tag inserted.\n */\nexport function injectPajaRuntimeCsp(html: string, origins: readonly string[]): string {\n const value = origins.length > 0\n ? `connect-src ${[...new Set(origins)].sort().join(' ')}`\n : \"connect-src 'none'\";\n const meta = `<meta http-equiv=\"Content-Security-Policy\" content=\"${escapeAttribute(value)}\">`;\n if (/<head[^>]*>/i.test(html)) {\n return html.replace(/<head[^>]*>/i, (open) => `${open}${meta}`);\n }\n if (/<html[^>]*>/i.test(html)) {\n return html.replace(/<html[^>]*>/i, (open) => `${open}<head>${meta}</head>`);\n }\n return `${meta}${html}`;\n}\n\nasync function resolvePointerEvent(\n pointer: PajaDecodedPointer,\n pool: PajaPointerRelayPool,\n relays: readonly string[],\n maxWaitMs: number,\n): Promise<NostrEvent> {\n const filter = pointer.type === 'naddr'\n ? addressableFilter(pointer)\n : eventFilter(pointer);\n const events = await pool.querySync([...relays], filter, {\n maxWait: maxWaitMs,\n label: 'kehto-paja-runtime',\n });\n const event = events\n .filter((candidate) => matchesPointer(pointer, candidate))\n .sort((a, b) => b.created_at - a.created_at)[0];\n if (!event) {\n throw new Error(`No napplet manifest event found for ${pointer.type} pointer.`);\n }\n if (!isNappletManifestKind(event.kind)) {\n throw new Error(`Pointer resolved kind ${event.kind}; expected ${formatNappletManifestKinds()}.`);\n }\n return event;\n}\n\nfunction addressableFilter(pointer: Extract<PajaDecodedPointer, { type: 'naddr' }>): Filter {\n if (pointer.kind !== NAPPLET_KIND_NAMED) {\n throw new Error(`naddr kind ${pointer.kind} is not a named NIP-5D napplet manifest kind.`);\n }\n return {\n kinds: [NAPPLET_KIND_NAMED],\n authors: [pointer.pubkey],\n '#d': [pointer.identifier],\n limit: 1,\n };\n}\n\nfunction eventFilter(pointer: Extract<PajaDecodedPointer, { type: 'nevent' }>): Filter {\n return {\n ids: [pointer.id],\n ...(pointer.kind ? { kinds: [pointer.kind] } : {}),\n ...(pointer.author ? { authors: [pointer.author] } : {}),\n limit: 1,\n };\n}\n\nfunction matchesPointer(pointer: PajaDecodedPointer, event: NostrEvent): boolean {\n if (pointer.type === 'nevent') {\n if (event.id !== pointer.id) return false;\n if (pointer.author && event.pubkey !== pointer.author) return false;\n if (pointer.kind && event.kind !== pointer.kind) return false;\n return true;\n }\n return event.kind === pointer.kind\n && event.pubkey === pointer.pubkey\n && event.tags.some((tag) => tag[0] === 'd' && tag[1] === pointer.identifier);\n}\n\nfunction uniqueRelayUrls(values: readonly string[]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const url = normalizeRelayUrl(value);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction normalizeRelayUrl(value: string): string | undefined {\n const raw = value.trim();\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n if (url.protocol !== 'wss:' && url.protocol !== 'ws:') return undefined;\n url.hash = '';\n url.search = '';\n return url.href.replace(/\\/$/, '');\n } catch {\n return undefined;\n }\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('\"', '&quot;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;');\n}\n\nfunction formatNappletManifestKinds(): string {\n return PAJA_NAPPLET_MANIFEST_KINDS.join(' / ');\n}\n","import type { ShellCapabilities } from '@kehto/shell';\n\nconst PAJA_LEGACY_COMPATIBILITY_DOMAIN: string = `i${'fc'}`;\n\n/** Domain list mirrored from the upstream web runtime for parity checks. */\nexport const PAJA_UPSTREAM_WEB_DOMAINS = [\n 'ble',\n 'common',\n 'config',\n 'count',\n 'cvm',\n 'dm',\n 'identity',\n PAJA_LEGACY_COMPATIBILITY_DOMAIN,\n 'inc',\n 'intent',\n 'keys',\n 'link',\n 'lists',\n 'media',\n 'notify',\n 'outbox',\n 'relay',\n 'resource',\n 'serial',\n 'storage',\n 'theme',\n 'upload',\n 'webrtc',\n] as const;\n\n/** Domains required during the initial Paja handshake. */\nexport const PAJA_HANDSHAKE_DOMAINS = ['shell'] as const;\n/** Domains intentionally deferred until their runtime service exists. */\nexport const PAJA_DEFERRED_DOMAINS = ['dm'] as const;\n\n/** Compatibility aliases for renamed upstream capability domains. */\nexport const PAJA_COMPATIBILITY_ALIASES = {\n [PAJA_LEGACY_COMPATIBILITY_DOMAIN]: 'inc',\n} as const;\n\n/** Domains Paja advertises through its simulated shell capability surface. */\nexport const PAJA_ADVERTISED_DOMAINS = [\n 'relay',\n 'outbox',\n 'identity',\n 'storage',\n 'inc',\n 'theme',\n 'keys',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'media',\n 'notify',\n 'config',\n 'resource',\n 'cvm',\n 'upload',\n 'intent',\n 'count',\n] as const;\n\n/** Service names Paja expects the host runtime to provide. */\nexport const PAJA_REQUIRED_SERVICES = [\n 'config',\n 'cvm',\n 'identity',\n 'intent',\n 'keys',\n 'link',\n 'common',\n 'lists',\n 'serial',\n 'ble',\n 'webrtc',\n 'media',\n 'notifications',\n 'notify',\n 'outbox',\n 'relay',\n 'resource',\n 'theme',\n 'upload',\n 'count',\n] as const;\n\n/**\n * Find advertised Paja domains missing from a shell capability snapshot.\n *\n * @param capabilities - Shell capability snapshot to inspect.\n * @returns Advertised domains not present in the snapshot.\n */\nexport function getMissingAdvertisedDomains(capabilities: ShellCapabilities): string[] {\n const advertised = new Set(capabilities.domains);\n return PAJA_ADVERTISED_DOMAINS.filter((domain) => !advertised.has(domain));\n}\n\n/**\n * Find required service names missing from a runtime service list.\n *\n * @param services - Runtime service names to inspect.\n * @returns Required service names that are absent.\n */\nexport function getMissingServices(services: readonly string[]): string[] {\n const wired = new Set(services);\n return PAJA_REQUIRED_SERVICES.filter((service) => !wired.has(service));\n}\n","/** Options for polling a target URL until it is ready. */\nexport interface WaitForTargetUrlOptions {\n /** Total wait budget in milliseconds. */\n readonly timeoutMs: number;\n /** Poll interval in milliseconds. Defaults to 250. */\n readonly intervalMs?: number;\n /** Fetch implementation override for tests. */\n readonly fetch?: ReadinessFetch;\n /** Sleep implementation override for tests. */\n readonly sleep?: (ms: number) => Promise<void>;\n /** Clock override for tests. */\n readonly now?: () => number;\n}\n\n/** Fetch-like target readiness probe. */\nexport type ReadinessFetch = (url: string) => Promise<{ readonly status: number }>;\n\n/** Thrown when a target URL does not become ready before the timeout. */\nexport class ReadinessError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ReadinessError';\n }\n}\n\n/**\n * Poll a target URL until it returns a non-5xx response or times out.\n *\n * @param url - Target URL to poll.\n * @param options - Timeout and polling controls.\n */\nexport async function waitForTargetUrl(url: string, options: WaitForTargetUrlOptions): Promise<void> {\n const intervalMs = options.intervalMs ?? 250;\n const fetcher = options.fetch ?? defaultFetch;\n const sleep = options.sleep ?? defaultSleep;\n const now = options.now ?? Date.now;\n const deadline = now() + options.timeoutMs;\n let lastError = 'no response';\n\n while (now() <= deadline) {\n try {\n const response = await fetcher(url);\n if (response.status < 500) {\n return;\n }\n lastError = `status ${response.status}`;\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error);\n }\n\n await sleep(intervalMs);\n }\n\n throw new ReadinessError(`Timed out waiting ${options.timeoutMs}ms for target URL ${url}: ${lastError}`);\n}\n\nasync function defaultFetch(url: string): Promise<{ readonly status: number }> {\n return fetch(url, { method: 'GET', redirect: 'follow' });\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","import { createServer } from 'node:http';\nimport { readFileSync } from 'node:fs';\nimport {\n createPajaHostConfig,\n formatPajaUrl,\n normalizePajaOptions,\n type PajaHostConfig,\n type PajaOptions,\n type PajaRawOptions,\n} from './options.js';\nimport { renderPajaHtml } from './host-page.js';\nimport { resolvePajaRawOptions } from './config-file.js';\n\n/** Options for starting the Paja local HTTP server. */\nexport interface PajaServerOptions {\n /** Raw options accepted by Paja; config-file references are resolved before serving. */\n readonly options: PajaRawOptions;\n /** Clock override used for deterministic host config in tests. */\n readonly now?: Date;\n}\n\n/** Running Paja server handle. */\nexport interface PajaServer {\n /** Runtime URL where the Paja host page is served. */\n readonly url: string;\n /** Current host-page config. */\n readonly hostConfig: PajaHostConfig;\n /** Update the target iframe URL without restarting the server. */\n updateTargetUrl(targetUrl: string): PajaHostConfig;\n /** Stop the HTTP server. */\n close(): Promise<void>;\n}\n\n/**\n * Start the local Paja development HTTP server.\n *\n * @param input - Raw options and optional clock override.\n * @returns Running server handle.\n */\nexport async function startPajaServer(input: PajaServerOptions): Promise<PajaServer> {\n const rawOptions = resolvePajaRawOptions(input.options);\n const options = normalizePajaOptions(rawOptions);\n const createdAt = input.now ?? new Date();\n let hostConfig = createPajaHostConfig(options, createdAt);\n let html = renderPajaHtml(hostConfig);\n let configJson = JSON.stringify(hostConfig, null, 2);\n let currentOptions = options;\n\n const setHostConfig = (nextOptions: PajaOptions): PajaHostConfig => {\n currentOptions = nextOptions;\n hostConfig = createPajaHostConfig(currentOptions, createdAt);\n html = renderPajaHtml(hostConfig);\n configJson = JSON.stringify(hostConfig, null, 2);\n return hostConfig;\n };\n\n const handleRequest = async (requestUrl: string, response: HttpResponse): Promise<void> => {\n if (requestUrl === '/' || requestUrl.startsWith('/?')) {\n response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n response.end(html);\n return;\n }\n\n if (requestUrl === '/__kehto/config.json') {\n response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });\n response.end(configJson);\n return;\n }\n\n if (requestUrl === '/__kehto/target.html') {\n const targetHtml = await fetchTargetHtml(hostConfig.target.url);\n response.writeHead(200, {\n 'cache-control': 'no-store',\n 'content-type': 'text/html; charset=utf-8',\n });\n response.end(targetHtml);\n return;\n }\n\n if (requestUrl === '/__kehto/browser-host.js') {\n const browserScript = readBrowserHostScript();\n response.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' });\n response.end(browserScript);\n return;\n }\n\n response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });\n response.end('Not found');\n };\n\n const server = createServer((request, response) => {\n void handleRequest(request.url ?? '/', response).catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n response.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });\n response.end(message);\n });\n });\n\n await listen(server, options.host, options.port);\n const servedOptions = withBoundPort(options, getBoundPort(server, options.port));\n setHostConfig(servedOptions);\n\n return {\n url: formatPajaUrl(servedOptions),\n get hostConfig() {\n return hostConfig;\n },\n updateTargetUrl(targetUrl) {\n return setHostConfig(withTargetUrl(currentOptions, targetUrl));\n },\n close: () => close(server),\n };\n}\n\nfunction readBrowserHostScript(): string {\n return readFileSync(new URL('./browser-host.js', import.meta.url), 'utf8');\n}\n\nasync function fetchTargetHtml(targetUrl: string): Promise<string> {\n const response = await fetch(targetUrl, {\n headers: {\n accept: 'text/html, application/xhtml+xml;q=0.9, */*;q=0.8',\n },\n });\n if (!response.ok) {\n throw new Error(`Target ${targetUrl} returned ${response.status} ${response.statusText}`);\n }\n return response.text();\n}\n\nfunction listen(server: HttpServer, host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, host, () => {\n server.off('error', reject);\n resolve();\n });\n });\n}\n\nfunction close(server: HttpServer): Promise<void> {\n return new Promise((resolve, reject) => {\n const forceClose = setTimeout(() => {\n server.closeAllConnections?.();\n }, 500);\n server.close((error?: Error) => {\n clearTimeout(forceClose);\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n server.closeIdleConnections?.();\n });\n}\n\ninterface HttpServer {\n listen(port: number, host: string, callback: () => void): void;\n once(event: 'error', callback: (error: Error) => void): void;\n off(event: 'error', callback: (error: Error) => void): void;\n close(callback: (error?: Error) => void): void;\n closeAllConnections?(): void;\n closeIdleConnections?(): void;\n address(): string | { port: number } | null;\n}\n\ninterface HttpResponse {\n writeHead(statusCode: number, headers: Record<string, string>): void;\n end(chunk: string): void;\n}\n\nfunction getBoundPort(server: HttpServer, fallback: number): number {\n const address = server.address();\n return typeof address === 'object' && address !== null ? address.port : fallback;\n}\n\nfunction withBoundPort(options: PajaOptions, port: number): PajaOptions {\n return port === options.port ? options : { ...options, port };\n}\n\nfunction withTargetUrl(options: PajaOptions, targetUrl: string): PajaOptions {\n const url = normalizeServerTargetUrl(targetUrl);\n return url === options.targetUrl ? options : { ...options, targetUrl: url };\n}\n\nfunction normalizeServerTargetUrl(value: string): string {\n const raw = value.trim();\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`Invalid managed target URL \"${raw}\". Expected an absolute http(s) URL.`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`Invalid managed target URL \"${raw}\". Only http: and https: URLs are supported.`);\n }\n return parsed.href;\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;;;ACmJjB,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAC7C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,0BAA2D;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,wBAAoC;AAAA,EACxC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AACV;AAEA,IAAM,uBAAmC;AAAA,EACvC,OAAO;AACT;AAQO,SAAS,wBACd,KACgB;AAChB,QAAM,kBAAkB,KAAK,cAAc,WAAW,CAAC;AACvD,QAAM,UAAU,OAAO;AAAA,IACrB,wBAAwB,IAAI,CAAC,WAAW,CAAC,QAAQ,gBAAgB,MAAM,KAAK,IAAI,CAAC;AAAA,EACnF;AAEA,QAAM,YAAY,KAAK,OAAO,SAAS,QAAQ,QAAQ,SAAS;AAChE,MAAI,CAAC,QAAQ,SAAS,cAAc,YAAY;AAC9C,UAAM,IAAI,oBAAoB,6FAA6F;AAAA,EAC7H;AACA,MAAI,CAAC,QAAQ,UAAU,cAAc,YAAY;AAC/C,UAAM,IAAI,oBAAoB,8FAA8F;AAAA,EAC9H;AACA,MAAI,cAAc,YAAY;AAC5B,YAAQ,QAAQ;AAChB,YAAQ,SAAS;AAAA,EACnB;AAEA,QAAM,aAAa,KAAK,QAAQ,SAAS,QAAQ,SAAS,WAAW;AACrE,MAAI,CAAC,QAAQ,UAAU,eAAe,YAAY;AAChD,UAAM,IAAI,oBAAoB,+FAA+F;AAAA,EAC/H;AACA,MAAI,eAAe,WAAY,SAAQ,SAAS;AAEhD,QAAM,gBAAgB,KAAK,QAAQ,WAAW,QAAQ;AACtD,MAAI,CAAC,QAAQ,UAAU,eAAe;AACpC,UAAM,IAAI,oBAAoB,6FAA6F;AAAA,EAC7H;AACA,MAAI,CAAC,cAAe,SAAQ,SAAS;AAErC,MAAI,CAAC,QAAQ,MAAO,SAAQ,QAAQ;AAEpC,QAAM,eAAe,KAAK,OAAO,WAAW,QAAQ;AACpD,MAAI,CAAC,QAAQ,SAAS,cAAc;AAClC,UAAM,IAAI,oBAAoB,2FAA2F;AAAA,EAC3H;AACA,MAAI,CAAC,aAAc,SAAQ,QAAQ;AAEnC,QAAM,aAAa,KAAK,KAAK,WAAW,QAAQ;AAChD,MAAI,CAAC,QAAQ,OAAO,YAAY;AAC9B,UAAM,IAAI,oBAAoB,uFAAuF;AAAA,EACvH;AACA,MAAI,CAAC,WAAY,SAAQ,MAAM;AAE/B,QAAM,uBAAuB,KAAK,eAAe,WAAW,QAAQ;AACpE,MAAI,CAAC,QAAQ,UAAU,sBAAsB;AAC3C,UAAM,IAAI,oBAAoB,oGAAoG;AAAA,EACpI;AACA,MAAI,CAAC,qBAAsB,SAAQ,SAAS;AAE5C,QAAM,cAAc,KAAK,SAAS,SAAS,QAAQ,UAAU,UAAU;AACvE,MAAI,CAAC,QAAQ,WAAW,gBAAgB,YAAY;AAClD,UAAM,IAAI,oBAAoB,iGAAiG;AAAA,EACjI;AACA,MAAI,gBAAgB,WAAY,SAAQ,UAAU;AAElD,QAAM,eAAe,KAAK,UAAU,QAAQ;AAC5C,QAAM,SAAS,KAAK,UAAU,QAAQ,KAAK,KAAK;AAChD,MAAI,iBAAiB,WAAW,CAAC,YAAY,MAAM,GAAG;AACpD,UAAM,IAAI,oBAAoB,sGAAsG;AAAA,EACtI;AAEA,QAAM,YAAY,KAAK,OAAO,QAAQ;AACtC,MAAI,cAAc,cAAc,UAAU,WAAW,GAAG;AACtD,UAAM,IAAI,oBAAoB,0FAA0F;AAAA,EAC1H;AACA,aAAW,OAAO,WAAW;AAC3B,QAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,EAAE,WAAW,GAAG;AACtD,YAAM,IAAI,oBAAoB,mEAAmE;AAAA,IACnG;AAAA,EACF;AAEA,QAAM,aAAa,KAAK,QAAQ,MAAM,KAAK,KAAK;AAChD,MAAI,eAAe,YAAY,WAAW,WAAW,GAAG;AACtD,UAAM,IAAI,oBAAoB,iFAAiF;AAAA,EACjH;AACA,QAAM,gBAAgB,uBAAuB,KAAK,QAAQ,WAAW,CAAC,CAAC;AACvE,QAAM,kBAAkB,KAAK,QAAQ,mBAAmB,eAAe;AACvE,QAAM,WAAW,KAAK,QAAQ;AAC9B,MAAI,aAAa,WAAc,CAAC,OAAO,cAAc,QAAQ,KAAK,YAAY,IAAI;AAChF,UAAM,IAAI,oBAAoB,sEAAsE;AAAA,EACtG;AACA,QAAM,YAAY,mBAAmB,KAAK,QAAQ,SAAS;AAE3D,SAAO;AAAA,IACL,cAAc;AAAA,MACZ;AAAA,MACA,iBAAiB,wBAAwB,OAAO,CAAC,WAAW,CAAC,QAAQ,MAAM,CAAC;AAAA,IAC9E;AAAA,IACA,KAAK;AAAA,MACH,MAAM,KAAK,KAAK,QAAQ;AAAA,IAC1B;AAAA,IACA,UAAU;AAAA,MACR,MAAM,KAAK,UAAU,QAAQ;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,QAAQ,iBAAiB,UAAU,SAAS;AAAA,IAC9C;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,UAAU,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;AAAA,MACvC,UAAU,KAAK,OAAO,YAAY,CAAC;AAAA,IACrC;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA,OAAO;AAAA,MACL,MAAM,KAAK,OAAO,QAAQ;AAAA,IAC5B;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,GAAI,eAAe,WAAW,EAAE,MAAM,WAAW,IAAI,CAAC;AAAA,MACtD,SAAS;AAAA,MACT;AAAA,MACA,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACnC;AAAA,IACA,OAAO;AAAA,MACL,SAAS;AAAA,IACX;AAAA,IACA,eAAe;AAAA,MACb,SAAS;AAAA,MACT,OAAO,KAAK,eAAe,SAAS;AAAA,IACtC;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ,EAAE,GAAG,uBAAuB,GAAG,KAAK,QAAQ,OAAO;AAAA,IAC7D;AAAA,IACA,OAAO;AAAA,MACL,MAAM,KAAK,OAAO,QAAQ;AAAA,MAC1B,QAAQ,EAAE,GAAG,sBAAsB,GAAG,KAAK,OAAO,OAAO;AAAA,IAC3D;AAAA,IACA,QAAQ;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,KAAK;AAAA,MACH,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAQO,SAAS,wBAAwB,YAAoC;AAC1E,QAAM,QAAQ,WAAW,MAAM,SAAS,aAAa,cAAc,SAAS,WAAW,MAAM,IAAI,IAAI,WAAW,MAAM,KAAK,MAAM;AACjI,QAAM,WAAW,WAAW,SAAS,SAAS,UAAU,mBAAmB;AAC3E,QAAM,UAAU,WAAW,WAAW,QAAQ,IAAI;AAClD,QAAM,SAAS,WAAW,OAAO,SAAS,WACtC,4BACA,WAAW,OAAO,SAAS,YACzB,kBAAkB,WAAW,OAAO,QAAQ,MAAM,KAClD;AACN,QAAM,QAAQ,SAAS,WAAW,MAAM,IAAI;AAC5C,QAAM,WAAW,WAAW,aAAa,gBAAgB,SAAS,IAC9D,OAAO,WAAW,aAAa,gBAAgB,KAAK,GAAG,CAAC,KACxD;AACJ,SAAO,GAAG,QAAQ,IAAI,KAAK,IAAI,OAAO,IAAI,MAAM,IAAI,KAAK,IAAI,QAAQ;AACvE;AAGO,SAAS,uBAAuB,SAAsC;AAC3E,QAAM,aAAuB,CAAC;AAC9B,aAAW,SAAS,SAAS;AAC3B,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,WAAW,GAAG;AAC1D,YAAM,IAAI,oBAAoB,oEAAoE;AAAA,IACpG;AACA,QAAI;AACJ,QAAI;AACF,YAAM,IAAI,IAAI,MAAM,KAAK,CAAC;AAAA,IAC5B,QAAQ;AACN,YAAM,IAAI,oBAAoB,sCAAsC,KAAK,uBAAuB;AAAA,IAClG;AACA,QAAI,IAAI,YAAY,IAAI,UAAU;AAChC,YAAM,IAAI,oBAAoB,sEAAsE;AAAA,IACtG;AACA,QAAI,IAAI,aAAa,YAAY,EAAE,IAAI,aAAa,WAAW,eAAe,IAAI,QAAQ,IAAI;AAC5F,YAAM,IAAI,oBAAoB,yFAAyF;AAAA,IACzH;AACA,QAAI,IAAI,UAAU,IAAI,MAAM;AAC1B,YAAM,IAAI,oBAAoB,8EAA8E;AAAA,IAC9G;AACA,UAAM,SAAS,IAAI,KAAK,QAAQ,QAAQ,EAAE;AAC1C,QAAI,CAAC,WAAW,SAAS,MAAM,EAAG,YAAW,KAAK,MAAM;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,SAAS,eAAe,UAA2B;AACjD,SAAO,aAAa,eACf,aAAa,WACb,wBAAwB,KAAK,QAAQ;AAC5C;AAEA,SAAS,mBAAmB,WAAgE;AAC1F,MAAI,cAAc,OAAW,QAAO;AACpC,QAAM,aAAuB,CAAC;AAC9B,aAAW,SAAS,WAAW;AAC7B,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,WAAW,GAAG;AAC1D,YAAM,IAAI,oBAAoB,yEAAyE;AAAA,IACzG;AACA,UAAM,WAAW,MAAM,KAAK;AAC5B,QAAI,CAAC,WAAW,SAAS,QAAQ,EAAG,YAAW,KAAK,QAAQ;AAAA,EAC9D;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAAwB;AAC3C,SAAO,oBAAoB,KAAK,KAAK;AACvC;;;ADnZO,SAAS,mBAAmB,YAAoC;AACrE,QAAM,WAAW,QAAQ,UAAU;AACnC,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AAAA,EACpD,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAM,IAAI,oBAAoB,4BAA4B,UAAU,MAAM,MAAM,EAAE;AAAA,EACpF;AAEA,MAAI,CAAC,SAAS,MAAM,GAAG;AACrB,UAAM,IAAI,oBAAoB,qBAAqB,UAAU,4BAA4B;AAAA,EAC3F;AAEA,SAAO;AACT;AAQO,SAAS,sBAAsB,KAAqC;AACzE,MAAI,CAAC,IAAI,YAAY,KAAK,EAAG,QAAO;AACpC,SAAO,oBAAoB,mBAAmB,IAAI,UAAU,GAAG,GAAG;AACpE;AASO,SAAS,oBACd,MACA,UACgB;AAChB,SAAO;AAAA,IACL,WAAW,SAAS,aAAa,KAAK;AAAA,IACtC,SAAS,SAAS,WAAW,KAAK;AAAA,IAClC,MAAM,SAAS,QAAQ,KAAK;AAAA,IAC5B,MAAM,SAAS,QAAQ,KAAK;AAAA,IAC5B,gBAAgB,SAAS,kBAAkB,KAAK;AAAA,IAChD,YAAY,SAAS,cAAc,KAAK;AAAA,IACxC,YAAY,uBAAuB,KAAK,YAAY,SAAS,UAAU;AAAA,EACzE;AACF;AAEA,SAAS,uBACP,MACA,UACsC;AACtC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO;AAAA,IACL,cAAc;AAAA,MACZ,GAAG,KAAK;AAAA,MACR,GAAG,SAAS;AAAA,MACZ,SAAS;AAAA,QACP,GAAG,KAAK,cAAc;AAAA,QACtB,GAAG,SAAS,cAAc;AAAA,MAC5B;AAAA,IACF;AAAA,IACA,KAAK,EAAE,GAAG,KAAK,KAAK,GAAG,SAAS,IAAI;AAAA,IACpC,UAAU,EAAE,GAAG,KAAK,UAAU,GAAG,SAAS,SAAS;AAAA,IACnD,UAAU,EAAE,GAAG,KAAK,UAAU,GAAG,SAAS,SAAS;AAAA,IACnD,OAAO,EAAE,GAAG,KAAK,OAAO,GAAG,SAAS,MAAM;AAAA,IAC1C,SAAS,EAAE,GAAG,KAAK,SAAS,GAAG,SAAS,QAAQ;AAAA,IAChD,OAAO,EAAE,GAAG,KAAK,OAAO,GAAG,SAAS,MAAM;AAAA,IAC1C,QAAQ,EAAE,GAAG,KAAK,QAAQ,GAAG,SAAS,OAAO;AAAA,IAC7C,OAAO,EAAE,GAAG,KAAK,OAAO,GAAG,SAAS,MAAM;AAAA,IAC1C,eAAe,EAAE,GAAG,KAAK,eAAe,GAAG,SAAS,cAAc;AAAA,IAClE,QAAQ;AAAA,MACN,GAAG,KAAK;AAAA,MACR,GAAG,SAAS;AAAA,MACZ,QAAQ,EAAE,GAAG,KAAK,QAAQ,QAAQ,GAAG,SAAS,QAAQ,OAAO;AAAA,IAC/D;AAAA,IACA,OAAO;AAAA,MACL,GAAG,KAAK;AAAA,MACR,GAAG,SAAS;AAAA,MACZ,QAAQ,EAAE,GAAG,KAAK,OAAO,QAAQ,GAAG,SAAS,OAAO,OAAO;AAAA,IAC7D;AAAA,IACA,QAAQ,EAAE,GAAG,KAAK,QAAQ,GAAG,SAAS,OAAO;AAAA,IAC7C,KAAK,EAAE,GAAG,KAAK,KAAK,GAAG,SAAS,IAAI;AAAA,EACtC;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;AEHO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,oBAAoB;AAE1B,IAAM,oBAAoB;AAE1B,IAAM,2BAA2B;AAEjC,IAAM,yBAAyB;AAE/B,IAAM,oBAAoB;AAE1B,IAAM,8BAA8B;AAEpC,IAAM,+BAA+B;AAQrC,SAAS,qBAAqB,KAAkC;AACrE,QAAM,YAAY,mBAAmB,IAAI,SAAS;AAClD,QAAM,OAAO,cAAc,IAAI,IAAI;AACnC,QAAM,OAAO,cAAc,IAAI,MAAM,MAAM;AAC3C,QAAM,iBAAiB;AAAA,IACrB,IAAI,kBAAkB;AAAA,IACtB;AAAA,EACF;AACA,QAAM,UAAU,iBAAiB,IAAI,OAAO;AAC5C,QAAM,aAAa,IAAI,YAAY,KAAK;AACxC,QAAM,aAAa,wBAAwB,IAAI,UAAU;AAEzD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,cAAc,WAAW,SAAS,IAAI,aAAa;AAAA,IAC/D;AAAA,IACA,MAAM,UAAU,oBAAoB;AAAA,EACtC;AACF;AASO,SAAS,qBACd,SACA,MAAY,oBAAI,KAAK,GACL;AAChB,QAAM,OAAO,yBAAyB,QAAQ,UAAU;AACxD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,KAAK,QAAQ;AAAA,MACb,aAAa;AAAA,MACb,SAAS,QAAQ;AAAA,IACnB;AAAA,IACA,SAAS;AAAA,MACP,MAAM,QAAQ;AAAA,MACd,MAAM,QAAQ;AAAA,MACd,gBAAgB,QAAQ;AAAA,MACxB,YAAY,QAAQ;AAAA,MACpB,aAAa,kBAAkB,GAAG;AAAA,MAClC,WAAW,IAAI,YAAY;AAAA,IAC7B;AAAA,EACF;AACF;AASO,SAAS,4BACd,UAKI,CAAC,GACL,MAAY,oBAAI,KAAK,GACL;AAChB,QAAM,OAAO,yBAAyB,wBAAwB,CAAC,CAAC,CAAC;AACjE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,aAAa;AAAA,MACb,SAAS;AAAA,QACP,GAAI,QAAQ,UAAU,EAAE,OAAO,QAAQ,QAAQ,IAAI,CAAC;AAAA,QACpD,QAAQ,CAAC,GAAI,QAAQ,UAAU,CAAC,CAAE;AAAA,QAClC,gBAAgB,CAAC,GAAI,QAAQ,kBAAkB,CAAC,CAAE;AAAA,QAClD,WAAW;AAAA,UACT,QAAQ,aAAa;AAAA,UACrB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,gBAAgB;AAAA,MAChB,aAAa,kBAAkB,GAAG;AAAA,MAClC,WAAW,IAAI,YAAY;AAAA,IAC7B;AAAA,EACF;AACF;AAQO,SAAS,cAAc,SAAqD;AACjF,SAAO,UAAU,QAAQ,IAAI,IAAI,QAAQ,IAAI;AAC/C;AAEA,SAAS,mBAAmB,OAAmC;AAC7D,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,iBAAiB,gFAAgF;AAAA,EAC7G;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,GAAG;AAAA,EACtB,QAAQ;AACN,UAAM,IAAI,iBAAiB,yBAAyB,GAAG,sCAAsC;AAAA,EAC/F;AAEA,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UAAU;AAC/D,UAAM,IAAI,iBAAiB,yBAAyB,GAAG,8CAA8C;AAAA,EACvG;AAEA,SAAO,OAAO;AAChB;AAEA,SAAS,cAAc,OAAmC;AACxD,QAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,MAAI,KAAK,WAAW,KAAK,KAAK,SAAS,GAAG,GAAG;AAC3C,UAAM,IAAI,iBAAiB,mBAAmB,SAAS,EAAE,sCAAsC;AAAA,EACjG;AACA,SAAO;AACT;AAEA,SAAS,cAAc,OAAoC,OAAuB;AAChF,SAAO,wBAAwB,SAAS,mBAAmB,OAAO,GAAG,KAAM;AAC7E;AAEA,SAAS,yBAAyB,OAAwB,OAAe,MAAM,OAAO,kBAA0B;AAC9G,SAAO,wBAAwB,OAAO,OAAO,GAAG,GAAG;AACrD;AAEA,SAAS,yBAAyB,YAAkG;AAClI,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,MACN,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,eAAe;AAAA,IACjB;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,YAAY;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,wBAAwB,OAAwB,OAAe,KAAa,KAAqB;AACxG,QAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAC/D,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,OAAO,SAAS,KAAK;AAC7D,UAAM,IAAI,iBAAiB,aAAa,KAAK,KAAK,OAAO,KAAK,CAAC,+BAA+B,GAAG,OAAO,GAAG,GAAG;AAAA,EAChH;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,SAA2D;AACnF,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,QAAQ,SAAS,QAAQ;AAC3B,UAAM,OAAO,QAAQ,KAAK,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EAAE,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AACrF,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,IAAI,iBAAiB,+DAA+D;AAAA,IAC5F;AACA,WAAO,EAAE,MAAM,QAAQ,KAAK;AAAA,EAC9B;AAEA,QAAM,eAAe,QAAQ,QAAQ,KAAK;AAC1C,MAAI,aAAa,WAAW,GAAG;AAC7B,UAAM,IAAI,iBAAiB,gDAAgD;AAAA,EAC7E;AACA,SAAO,EAAE,MAAM,SAAS,SAAS,aAAa;AAChD;AAEA,SAAS,kBAAkB,KAAmB;AAC5C,QAAM,MAAM,IAAI,YAAY;AAC5B,SAAO,UAAU,IAAI,WAAW,iBAAiB,EAAE,CAAC;AACtD;;;ACvTO,SAAS,eAAe,QAAgC;AAC7D,QAAM,aAAa,kBAAkB,KAAK,UAAU,MAAM,CAAC;AAC3D,QAAM,cAAc,gBAAgB,eAAe,MAAM,CAAC;AAE1D,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAwF4B,WAAW,KAAK,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAO9B,OAAO,WAAW,MAAM,SAAS,SAAS,cAAc,EAAE;AAAA,mCACzD,OAAO,WAAW,MAAM,SAAS,UAAU,cAAc,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAQpF,sBAAsB,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAuB/B,YAAY,QAAQ,WAAW,CAAC;AAAA;AAAA,MAElC,sBAAsB,CAAC;AAAA;AAAA,0BAEH,WAAW,aAAa,MAAM,CAAC,CAAC;AAAA,yBACjC,OAAO,OAAO,WAAW;AAAA,6BACrB,WAAW,OAAO,QAAQ,IAAI,CAAC,IAAI,OAAO,QAAQ,IAAI;AAAA,gDACnC,WAAW,wBAAwB,OAAO,UAAU,CAAC,CAAC;AAAA;AAAA;AAAA,6DAGzC,UAAU;AAAA;AAAA;AAAA;AAIvE;AAEA,SAAS,YAAY,QAAwB,aAA6B;AACxE,MAAI,OAAO,OAAO,SAAS,mBAAmB;AAC5C,WAAO;AAAA;AAAA;AAAA,EAGT;AACA,SAAO;AAAA,iHACwG,WAAW;AAAA;AAE5H;AAEA,SAAS,wBAAgC;AACvC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUT;AAEA,SAAS,sBAAsB,QAAgC;AAC7D,MAAI,OAAO,OAAO,SAAS,kBAAmB,QAAO;AACrD,QAAM,QAAQ,gBAAgB,OAAO,OAAO,SAAS,SAAS,EAAE;AAChE,SAAO;AAAA;AAAA;AAAA,4LAGmL,KAAK;AAAA;AAAA;AAAA;AAAA;AAKjM;AAEA,SAAS,aAAa,QAAgC;AACpD,MAAI,OAAO,OAAO,SAAS,kBAAmB,QAAO;AACrD,SAAO,OAAO,OAAO,UAAU,oBAAoB;AACrD;AAEA,SAAS,eAAe,QAAgC;AACtD,MAAI,OAAO,OAAO,SAAS,mBAAmB;AAC5C,WAAO,OAAO,OAAO,SAAS,SAAS;AAAA,EACzC;AACA,SAAO,OAAO,OAAO;AACvB;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,WAAW,KAAK,EAAE,WAAW,KAAK,QAAQ;AACnD;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,MACJ,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM;AAC3B;AAEA,SAAS,kBAAkB,OAAuB;AAChD,SAAO,MAAM,WAAW,KAAK,SAAS;AACxC;;;ACxNA,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAGA,IAAM,6BAA6B;AAEnC,IAAM,8BAA8B;AAyEpC,SAAS,kBAAkB,OAAmC;AACnE,QAAM,UAAU,MAAM,KAAK;AAC3B,QAAM,UAAU,OAAO,OAAO;AAC9B,MAAI,QAAQ,SAAS,SAAS;AAC5B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,YAAY,QAAQ,KAAK;AAAA,MACzB,QAAQ,QAAQ,KAAK;AAAA,MACrB,MAAM,QAAQ,KAAK;AAAA,MACnB,QAAQ,CAAC,GAAI,QAAQ,KAAK,UAAU,CAAC,CAAE;AAAA,IACzC;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,UAAU;AAC7B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,IAAI,QAAQ,KAAK;AAAA,MACjB,GAAI,QAAQ,KAAK,SAAS,EAAE,QAAQ,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,MAC7D,GAAI,QAAQ,KAAK,OAAO,EAAE,MAAM,QAAQ,KAAK,KAAK,IAAI,CAAC;AAAA,MACvD,QAAQ,CAAC,GAAI,QAAQ,KAAK,UAAU,CAAC,CAAE;AAAA,IACzC;AAAA,EACF;AACA,QAAM,IAAI,MAAM,yCAAyC,QAAQ,IAAI,GAAG;AAC1E;AASA,eAAsB,mBACpB,OACA,UAAqC,CAAC,GACR;AAC9B,QAAM,UAAU,kBAAkB,KAAK;AACvC,QAAM,SAAS,gBAAgB,CAAC,GAAG,QAAQ,QAAQ,GAAI,QAAQ,UAAU,CAAC,CAAE,CAAC;AAC7E,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,4EAA4E;AAAA,EAC9F;AAEA,QAAM,OAAO,QAAQ,QAAQ,IAAI,WAAW;AAC5C,QAAM,WAAW,QAAQ,SAAS;AAClC,MAAI;AACF,UAAM,QAAQ,MAAM,oBAAoB,SAAS,MAAM,QAAQ,QAAQ,aAAa,GAAK;AACzF,UAAM,iBAAiB,CAAC,GAAI,QAAQ,kBAAkB,CAAC,CAAE;AACzD,UAAM,WAAW,MAAM,eAAe;AAAA,MACpC;AAAA,MACA,OAAO,MAAM,yBAAyB,EAAE,wBAAwB,MAAM,CAAC;AAAA,MACvE,WAAW,CAAC,WAAW,YACrB,UAAU,CAAC,GAAG,SAAS,GAAG,cAAc,GAAG,WAAW,OAAO,QAAQ;AACnE,cAAM,WAAW,OAAO,QAAQ,WAAW,OAAO,GAAG;AACrD,YAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,uBAAuB,GAAG,KAAK,SAAS,MAAM,EAAE;AAClF,eAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AAAA,MACpD,CAAC;AAAA,IACL,CAAC;AACD,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,SAAS;AAAA,MACf,eAAe,SAAS;AAAA,MACxB,WAAW,SAAS;AAAA,MACpB,UAAU,SAAS;AAAA,IACrB;AAAA,EACF,UAAE;AACA,QAAI,UAAU;AACZ,WAAK,QAAQ,MAAM;AACnB,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AACF;AASO,SAAS,qBAAqB,MAAc,SAAoC;AACrF,QAAM,QAAQ,QAAQ,SAAS,IAC3B,eAAe,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,KACrD;AACJ,QAAM,OAAO,uDAAuDA,iBAAgB,KAAK,CAAC;AAC1F,MAAI,eAAe,KAAK,IAAI,GAAG;AAC7B,WAAO,KAAK,QAAQ,gBAAgB,CAAC,SAAS,GAAG,IAAI,GAAG,IAAI,EAAE;AAAA,EAChE;AACA,MAAI,eAAe,KAAK,IAAI,GAAG;AAC7B,WAAO,KAAK,QAAQ,gBAAgB,CAAC,SAAS,GAAG,IAAI,SAAS,IAAI,SAAS;AAAA,EAC7E;AACA,SAAO,GAAG,IAAI,GAAG,IAAI;AACvB;AAEA,eAAe,oBACb,SACA,MACA,QACA,WACqB;AACrB,QAAM,SAAS,QAAQ,SAAS,UAC5B,kBAAkB,OAAO,IACzB,YAAY,OAAO;AACvB,QAAM,SAAS,MAAM,KAAK,UAAU,CAAC,GAAG,MAAM,GAAG,QAAQ;AAAA,IACvD,SAAS;AAAA,IACT,OAAO;AAAA,EACT,CAAC;AACD,QAAM,QAAQ,OACX,OAAO,CAAC,cAAc,eAAe,SAAS,SAAS,CAAC,EACxD,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AAChD,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,uCAAuC,QAAQ,IAAI,WAAW;AAAA,EAChF;AACA,MAAI,CAAC,sBAAsB,MAAM,IAAI,GAAG;AACtC,UAAM,IAAI,MAAM,yBAAyB,MAAM,IAAI,cAAc,2BAA2B,CAAC,GAAG;AAAA,EAClG;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAiE;AAC1F,MAAI,QAAQ,SAAS,oBAAoB;AACvC,UAAM,IAAI,MAAM,cAAc,QAAQ,IAAI,+CAA+C;AAAA,EAC3F;AACA,SAAO;AAAA,IACL,OAAO,CAAC,kBAAkB;AAAA,IAC1B,SAAS,CAAC,QAAQ,MAAM;AAAA,IACxB,MAAM,CAAC,QAAQ,UAAU;AAAA,IACzB,OAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,SAAkE;AACrF,SAAO;AAAA,IACL,KAAK,CAAC,QAAQ,EAAE;AAAA,IAChB,GAAI,QAAQ,OAAO,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE,IAAI,CAAC;AAAA,IAChD,GAAI,QAAQ,SAAS,EAAE,SAAS,CAAC,QAAQ,MAAM,EAAE,IAAI,CAAC;AAAA,IACtD,OAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAe,SAA6B,OAA4B;AAC/E,MAAI,QAAQ,SAAS,UAAU;AAC7B,QAAI,MAAM,OAAO,QAAQ,GAAI,QAAO;AACpC,QAAI,QAAQ,UAAU,MAAM,WAAW,QAAQ,OAAQ,QAAO;AAC9D,QAAI,QAAQ,QAAQ,MAAM,SAAS,QAAQ,KAAM,QAAO;AACxD,WAAO;AAAA,EACT;AACA,SAAO,MAAM,SAAS,QAAQ,QACzB,MAAM,WAAW,QAAQ,UACzB,MAAM,KAAK,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,QAAQ,UAAU;AAC/E;AAEA,SAAS,gBAAgB,QAAqC;AAC5D,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,kBAAkB,KAAK;AACnC,QAAI,CAAC,OAAO,KAAK,IAAI,GAAG,EAAG;AAC3B,SAAK,IAAI,GAAG;AACZ,QAAI,KAAK,GAAG;AAAA,EACd;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAmC;AAC5D,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,GAAG;AACvB,QAAI,IAAI,aAAa,UAAU,IAAI,aAAa,MAAO,QAAO;AAC9D,QAAI,OAAO;AACX,QAAI,SAAS;AACb,WAAO,IAAI,KAAK,QAAQ,OAAO,EAAE;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAASA,iBAAgB,OAAuB;AAC9C,SAAO,MACJ,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM;AAC3B;AAEA,SAAS,6BAAqC;AAC5C,SAAO,4BAA4B,KAAK,KAAK;AAC/C;;;ACvRA,IAAM,mCAA2C,IAAI,IAAI;AAGlD,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,yBAAyB,CAAC,OAAO;AAKvC,IAAM,6BAA6B;AAAA,EACxC,CAAC,gCAAgC,GAAG;AACtC;AAGO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,SAAS,4BAA4B,cAA2C;AACrF,QAAM,aAAa,IAAI,IAAI,aAAa,OAAO;AAC/C,SAAO,wBAAwB,OAAO,CAAC,WAAW,CAAC,WAAW,IAAI,MAAM,CAAC;AAC3E;AAQO,SAAS,mBAAmB,UAAuC;AACxE,QAAM,QAAQ,IAAI,IAAI,QAAQ;AAC9B,SAAO,uBAAuB,OAAO,CAAC,YAAY,CAAC,MAAM,IAAI,OAAO,CAAC;AACvE;;;AC5FO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAQA,eAAsB,iBAAiB,KAAa,SAAiD;AACnG,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,WAAW,IAAI,IAAI,QAAQ;AACjC,MAAI,YAAY;AAEhB,SAAO,IAAI,KAAK,UAAU;AACxB,QAAI;AACF,YAAM,WAAW,MAAM,QAAQ,GAAG;AAClC,UAAI,SAAS,SAAS,KAAK;AACzB;AAAA,MACF;AACA,kBAAY,UAAU,SAAS,MAAM;AAAA,IACvC,SAAS,OAAO;AACd,kBAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IACnE;AAEA,UAAM,MAAM,UAAU;AAAA,EACxB;AAEA,QAAM,IAAI,eAAe,qBAAqB,QAAQ,SAAS,qBAAqB,GAAG,KAAK,SAAS,EAAE;AACzG;AAEA,eAAe,aAAa,KAAmD;AAC7E,SAAO,MAAM,KAAK,EAAE,QAAQ,OAAO,UAAU,SAAS,CAAC;AACzD;AAEA,SAAS,aAAa,IAA2B;AAC/C,SAAO,IAAI,QAAQ,CAACC,aAAY;AAC9B,eAAWA,UAAS,EAAE;AAAA,EACxB,CAAC;AACH;;;AChEA,SAAS,oBAAoB;AAC7B,SAAS,gBAAAC,qBAAoB;AAsC7B,eAAsB,gBAAgB,OAA+C;AACnF,QAAM,aAAa,sBAAsB,MAAM,OAAO;AACtD,QAAM,UAAU,qBAAqB,UAAU;AAC/C,QAAM,YAAY,MAAM,OAAO,oBAAI,KAAK;AACxC,MAAI,aAAa,qBAAqB,SAAS,SAAS;AACxD,MAAI,OAAO,eAAe,UAAU;AACpC,MAAI,aAAa,KAAK,UAAU,YAAY,MAAM,CAAC;AACnD,MAAI,iBAAiB;AAErB,QAAM,gBAAgB,CAAC,gBAA6C;AAClE,qBAAiB;AACjB,iBAAa,qBAAqB,gBAAgB,SAAS;AAC3D,WAAO,eAAe,UAAU;AAChC,iBAAa,KAAK,UAAU,YAAY,MAAM,CAAC;AAC/C,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,OAAO,YAAoB,aAA0C;AACzF,QAAI,eAAe,OAAO,WAAW,WAAW,IAAI,GAAG;AACrD,eAAS,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACtE,eAAS,IAAI,IAAI;AACjB;AAAA,IACF;AAEA,QAAI,eAAe,wBAAwB;AACzC,eAAS,UAAU,KAAK,EAAE,gBAAgB,kCAAkC,CAAC;AAC7E,eAAS,IAAI,UAAU;AACvB;AAAA,IACF;AAEA,QAAI,eAAe,wBAAwB;AACzC,YAAM,aAAa,MAAM,gBAAgB,WAAW,OAAO,GAAG;AAC9D,eAAS,UAAU,KAAK;AAAA,QACtB,iBAAiB;AAAA,QACjB,gBAAgB;AAAA,MAClB,CAAC;AACD,eAAS,IAAI,UAAU;AACvB;AAAA,IACF;AAEA,QAAI,eAAe,4BAA4B;AAC7C,YAAM,gBAAgB,sBAAsB;AAC5C,eAAS,UAAU,KAAK,EAAE,gBAAgB,iCAAiC,CAAC;AAC5E,eAAS,IAAI,aAAa;AAC1B;AAAA,IACF;AAEA,aAAS,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AACvE,aAAS,IAAI,WAAW;AAAA,EAC1B;AAEA,QAAM,SAAS,aAAa,CAAC,SAAS,aAAa;AACjD,SAAK,cAAc,QAAQ,OAAO,KAAK,QAAQ,EAAE,MAAM,CAAC,UAAU;AAChE,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,eAAS,UAAU,KAAK,EAAE,gBAAgB,4BAA4B,CAAC;AACvE,eAAS,IAAI,OAAO;AAAA,IACtB,CAAC;AAAA,EACH,CAAC;AAED,QAAM,OAAO,QAAQ,QAAQ,MAAM,QAAQ,IAAI;AAC/C,QAAM,gBAAgB,cAAc,SAAS,aAAa,QAAQ,QAAQ,IAAI,CAAC;AAC/E,gBAAc,aAAa;AAE3B,SAAO;AAAA,IACL,KAAK,cAAc,aAAa;AAAA,IAChC,IAAI,aAAa;AACf,aAAO;AAAA,IACT;AAAA,IACA,gBAAgB,WAAW;AACzB,aAAO,cAAc,cAAc,gBAAgB,SAAS,CAAC;AAAA,IAC/D;AAAA,IACA,OAAO,MAAM,MAAM,MAAM;AAAA,EAC3B;AACF;AAEA,SAAS,wBAAgC;AACvC,SAAOC,cAAa,IAAI,IAAI,qBAAqB,YAAY,GAAG,GAAG,MAAM;AAC3E;AAEA,eAAe,gBAAgB,WAAoC;AACjE,QAAM,WAAW,MAAM,MAAM,WAAW;AAAA,IACtC,SAAS;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACD,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,UAAU,SAAS,aAAa,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EAC1F;AACA,SAAO,SAAS,KAAK;AACvB;AAEA,SAAS,OAAO,QAAoB,MAAc,MAA6B;AAC7E,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,MAAM,MAAM,MAAM;AAC9B,aAAO,IAAI,SAAS,MAAM;AAC1B,MAAAA,SAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,MAAM,QAAmC;AAChD,SAAO,IAAI,QAAQ,CAACA,UAAS,WAAW;AACtC,UAAM,aAAa,WAAW,MAAM;AAClC,aAAO,sBAAsB;AAAA,IAC/B,GAAG,GAAG;AACN,WAAO,MAAM,CAAC,UAAkB;AAC9B,mBAAa,UAAU;AACvB,UAAI,OAAO;AACT,eAAO,KAAK;AACZ;AAAA,MACF;AACA,MAAAA,SAAQ;AAAA,IACV,CAAC;AACD,WAAO,uBAAuB;AAAA,EAChC,CAAC;AACH;AAiBA,SAAS,aAAa,QAAoB,UAA0B;AAClE,QAAM,UAAU,OAAO,QAAQ;AAC/B,SAAO,OAAO,YAAY,YAAY,YAAY,OAAO,QAAQ,OAAO;AAC1E;AAEA,SAAS,cAAc,SAAsB,MAA2B;AACtE,SAAO,SAAS,QAAQ,OAAO,UAAU,EAAE,GAAG,SAAS,KAAK;AAC9D;AAEA,SAAS,cAAc,SAAsB,WAAgC;AAC3E,QAAM,MAAM,yBAAyB,SAAS;AAC9C,SAAO,QAAQ,QAAQ,YAAY,UAAU,EAAE,GAAG,SAAS,WAAW,IAAI;AAC5E;AAEA,SAAS,yBAAyB,OAAuB;AACvD,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,GAAG;AAAA,EACtB,QAAQ;AACN,UAAM,IAAI,MAAM,+BAA+B,GAAG,sCAAsC;AAAA,EAC1F;AACA,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UAAU;AAC/D,UAAM,IAAI,MAAM,+BAA+B,GAAG,8CAA8C;AAAA,EAClG;AACA,SAAO,OAAO;AAChB;","names":["escapeAttribute","resolve","readFileSync","readFileSync","resolve"]}
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { P as PajaRawOptions, s as startPajaServer, a as PajaCommand, w as waitForTargetUrl } from './server-qAXYczds.js';
2
+ import { P as PajaRawOptions, s as startPajaServer, a as PajaCommand, w as waitForTargetUrl } from './server-efRTBcMR.js';
3
3
 
4
4
  /**
5
5
  * Minimal output streams used by the CLI runner.
@@ -40,10 +40,27 @@ interface RunPajaCliOptions {
40
40
  * @returns Parsed raw options, or a help flag when `--help` is present.
41
41
  */
42
42
  declare function parsePajaArgs(args: readonly string[]): CliParseResult;
43
+ /**
44
+ * Run the Paja CLI command.
45
+ *
46
+ * @param args - Argument vector without the node executable or script path.
47
+ * @param io - Output streams used by the command.
48
+ * @param runOptions - Test/embedding controls for server and process spawning.
49
+ * @returns Process exit code.
50
+ */
43
51
  declare function runPajaCli(args?: readonly string[], io?: CliIo, runOptions?: RunPajaCliOptions): Promise<number>;
52
+ /**
53
+ * Detect whether this module is running as the direct Paja CLI entrypoint.
54
+ *
55
+ * @param entryPath - Process entry path to inspect.
56
+ * @returns `true` when the entry path looks like Paja's built CLI.
57
+ */
44
58
  declare function isDirectPajaCli(entryPath?: string): boolean;
59
+ /** Managed child process handle. */
45
60
  interface ManagedChildProcess {
61
+ /** Stop the managed target process. */
46
62
  kill(): void;
63
+ /** First local target URL detected from process output. */
47
64
  readonly detectedTargetUrl: Promise<string>;
48
65
  }
49
66
 
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  startPajaServer,
10
10
  summarizePajaSimulation,
11
11
  waitForTargetUrl
12
- } from "./chunk-ADMLM2LH.js";
12
+ } from "./chunk-NFJCS5PG.js";
13
13
 
14
14
  // src/cli.ts
15
15
  import { spawn } from "child_process";
@@ -117,12 +117,13 @@ Options:
117
117
  --config <path> JSON config file using the same option schema.
118
118
  --identity-mode <mode> anonymous or fixed.
119
119
  --identity-pubkey <hex> Required when identity mode is fixed.
120
- --relay-mode <mode> memory or disabled.
121
- --relay-url <url> Add a relay URL for memory relay simulation.
120
+ --relay-mode <mode> live, memory, or disabled.
121
+ --relay-url <url> Add a relay URL for live or memory relay backends.
122
122
  --storage-mode <mode> local, memory, or disabled.
123
123
  --cache-mode <mode> memory or disabled.
124
- --upload-mode <mode> memory or disabled.
124
+ --upload-mode <mode> memory, blossom, or disabled.
125
125
  --upload-rail <name> Upload rail name for memory uploads.
126
+ --upload-server <url> Add a shell-owned Blossom server (repeatable).
126
127
  --media <state> enabled or disabled.
127
128
  --notifications <state> enabled or disabled.
128
129
  --notify-grant <bool> true or false default notification grant.
@@ -174,7 +175,7 @@ function applySimulationOption(arg, args, index, raw) {
174
175
  setIdentity(raw, { pubkey: readValue(args, index, arg) });
175
176
  return true;
176
177
  case "--relay-mode":
177
- setRelay(raw, { mode: readEnum(args, index, arg, ["memory", "disabled"]) });
178
+ setRelay(raw, { mode: readEnum(args, index, arg, ["live", "memory", "disabled"]) });
178
179
  return true;
179
180
  case "--relay-url":
180
181
  setRelay(raw, { urls: [...raw.simulation?.relay?.urls ?? [], readValue(args, index, arg)] });
@@ -186,11 +187,14 @@ function applySimulationOption(arg, args, index, raw) {
186
187
  ensureSimulation(raw).cache = { ...raw.simulation?.cache, mode: readEnum(args, index, arg, ["memory", "disabled"]) };
187
188
  return true;
188
189
  case "--upload-mode":
189
- setUpload(raw, { mode: readEnum(args, index, arg, ["memory", "disabled"]) });
190
+ setUpload(raw, { mode: readEnum(args, index, arg, ["memory", "blossom", "disabled"]) });
190
191
  return true;
191
192
  case "--upload-rail":
192
193
  setUpload(raw, { rail: readValue(args, index, arg) });
193
194
  return true;
195
+ case "--upload-server":
196
+ setUpload(raw, { servers: [...raw.simulation?.upload?.servers ?? [], readValue(args, index, arg)] });
197
+ return true;
194
198
  case "--media":
195
199
  ensureSimulation(raw).media = { ...raw.simulation?.media, enabled: readEnabled(args, index, arg) };
196
200
  return true;