@tensorix/metahub 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +50 -5
  2. package/dist/cli.js +3160 -196
  3. package/dist/cli.js.map +37 -21
  4. package/dist/core/config.d.ts +14 -0
  5. package/dist/core/config.d.ts.map +1 -0
  6. package/dist/core/databases.d.ts.map +1 -1
  7. package/dist/core/db.d.ts +6 -0
  8. package/dist/core/db.d.ts.map +1 -1
  9. package/dist/core/documents.d.ts +1 -0
  10. package/dist/core/documents.d.ts.map +1 -1
  11. package/dist/core/integrity.d.ts +32 -0
  12. package/dist/core/integrity.d.ts.map +1 -0
  13. package/dist/core/properties.d.ts.map +1 -1
  14. package/dist/core/schema.d.ts +1 -1
  15. package/dist/core/schema.d.ts.map +1 -1
  16. package/dist/core/search.d.ts +2 -0
  17. package/dist/core/search.d.ts.map +1 -1
  18. package/dist/core/snapshot.d.ts +2 -0
  19. package/dist/core/snapshot.d.ts.map +1 -1
  20. package/dist/core/sync/auth.d.ts +6 -0
  21. package/dist/core/sync/auth.d.ts.map +1 -1
  22. package/dist/core/sync/client.d.ts +7 -2
  23. package/dist/core/sync/client.d.ts.map +1 -1
  24. package/dist/core/sync/pairing.d.ts +50 -0
  25. package/dist/core/sync/pairing.d.ts.map +1 -0
  26. package/dist/core/sync/peers-routes.d.ts +3 -0
  27. package/dist/core/sync/peers-routes.d.ts.map +1 -0
  28. package/dist/core/sync/peers.d.ts +45 -0
  29. package/dist/core/sync/peers.d.ts.map +1 -0
  30. package/dist/core/sync/protocol.d.ts +13 -0
  31. package/dist/core/sync/protocol.d.ts.map +1 -1
  32. package/dist/core/sync/routes.d.ts.map +1 -1
  33. package/dist/core/sync/server.d.ts +20 -0
  34. package/dist/core/sync/server.d.ts.map +1 -1
  35. package/dist/core/sync/webui-routes.d.ts.map +1 -1
  36. package/dist/core/sync/webui.d.ts +11 -0
  37. package/dist/core/sync/webui.d.ts.map +1 -1
  38. package/dist/index.js +806 -143
  39. package/dist/index.js.map +21 -17
  40. package/dist/webui.js +33 -32
  41. package/package.json +4 -2
package/dist/cli.js CHANGED
@@ -1,6 +1,22 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
3
5
  var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
4
20
  var __export = (target, all) => {
5
21
  for (var name in all)
6
22
  __defProp(target, name, {
@@ -1272,12 +1288,75 @@ var init_sites = __esm(() => {
1272
1288
  };
1273
1289
  });
1274
1290
 
1291
+ // node_modules/sisteransi/src/index.js
1292
+ var require_src = __commonJS((exports, module) => {
1293
+ var ESC2 = "\x1B";
1294
+ var CSI2 = `${ESC2}[`;
1295
+ var beep = "\x07";
1296
+ var cursor = {
1297
+ to(x2, y3) {
1298
+ if (!y3)
1299
+ return `${CSI2}${x2 + 1}G`;
1300
+ return `${CSI2}${y3 + 1};${x2 + 1}H`;
1301
+ },
1302
+ move(x2, y3) {
1303
+ let ret = "";
1304
+ if (x2 < 0)
1305
+ ret += `${CSI2}${-x2}D`;
1306
+ else if (x2 > 0)
1307
+ ret += `${CSI2}${x2}C`;
1308
+ if (y3 < 0)
1309
+ ret += `${CSI2}${-y3}A`;
1310
+ else if (y3 > 0)
1311
+ ret += `${CSI2}${y3}B`;
1312
+ return ret;
1313
+ },
1314
+ up: (count = 1) => `${CSI2}${count}A`,
1315
+ down: (count = 1) => `${CSI2}${count}B`,
1316
+ forward: (count = 1) => `${CSI2}${count}C`,
1317
+ backward: (count = 1) => `${CSI2}${count}D`,
1318
+ nextLine: (count = 1) => `${CSI2}E`.repeat(count),
1319
+ prevLine: (count = 1) => `${CSI2}F`.repeat(count),
1320
+ left: `${CSI2}G`,
1321
+ hide: `${CSI2}?25l`,
1322
+ show: `${CSI2}?25h`,
1323
+ save: `${ESC2}7`,
1324
+ restore: `${ESC2}8`
1325
+ };
1326
+ var scroll = {
1327
+ up: (count = 1) => `${CSI2}S`.repeat(count),
1328
+ down: (count = 1) => `${CSI2}T`.repeat(count)
1329
+ };
1330
+ var erase = {
1331
+ screen: `${CSI2}2J`,
1332
+ up: (count = 1) => `${CSI2}1J`.repeat(count),
1333
+ down: (count = 1) => `${CSI2}J`.repeat(count),
1334
+ line: `${CSI2}2K`,
1335
+ lineEnd: `${CSI2}K`,
1336
+ lineStart: `${CSI2}1K`,
1337
+ lines(count) {
1338
+ let clear = "";
1339
+ for (let i2 = 0;i2 < count; i2++)
1340
+ clear += this.line + (i2 < count - 1 ? cursor.up() : "");
1341
+ if (count)
1342
+ clear += cursor.left;
1343
+ return clear;
1344
+ }
1345
+ };
1346
+ module.exports = { cursor, scroll, erase, beep };
1347
+ });
1348
+
1275
1349
  // src/core/sync/webui.ts
1276
1350
  var exports_webui = {};
1277
1351
  __export(exports_webui, {
1352
+ warmWebui: () => warmWebui,
1353
+ setWebuiBundle: () => setWebuiBundle,
1278
1354
  serveWebui: () => serveWebui
1279
1355
  });
1280
1356
  import { fileURLToPath } from "url";
1357
+ function setWebuiBundle(js) {
1358
+ cachedJs = js;
1359
+ }
1281
1360
  async function getJs() {
1282
1361
  if (cachedJs != null)
1283
1362
  return cachedJs;
@@ -1293,6 +1372,11 @@ async function getJs() {
1293
1372
  cachedJs = await res.outputs[0].text();
1294
1373
  return cachedJs;
1295
1374
  }
1375
+ async function warmWebui() {
1376
+ try {
1377
+ await getJs();
1378
+ } catch {}
1379
+ }
1296
1380
  async function serveWebui(req) {
1297
1381
  if (req.method !== "GET")
1298
1382
  return null;
@@ -1305,8 +1389,8 @@ async function serveWebui(req) {
1305
1389
  return new Response(await getJs(), {
1306
1390
  headers: { "content-type": "text/javascript; charset=utf-8" }
1307
1391
  });
1308
- } catch (e2) {
1309
- return new Response(`webui build failed: ${e2}`, { status: 500 });
1392
+ } catch (e3) {
1393
+ return new Response(`webui build failed: ${e3}`, { status: 500 });
1310
1394
  }
1311
1395
  }
1312
1396
  return null;
@@ -1319,7 +1403,13 @@ var HTML = `<!doctype html>
1319
1403
  <title>Metahub</title>
1320
1404
  <link rel="preconnect" href="https://fonts.googleapis.com">
1321
1405
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1322
- <link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
1406
+ <!-- Load web fonts without blocking first paint: media="print" keeps the
1407
+ stylesheet non-render-blocking, then onload flips it to "all". The page
1408
+ paints instantly with the system-font fallbacks baked into --ui/--mono and
1409
+ swaps to the web fonts when they arrive (or stays on system fonts offline).
1410
+ This is what kept the desktop cold-start white for seconds on slow networks. -->
1411
+ <link rel="stylesheet" media="print" onload="this.media='all'" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap">
1412
+ <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"></noscript>
1323
1413
  <script>try{document.documentElement.dataset.theme=localStorage.getItem('mh-theme')||'system'}catch(e){}</script>
1324
1414
  <style>
1325
1415
  :root {
@@ -1429,6 +1519,9 @@ var HTML = `<!doctype html>
1429
1519
  .set-section { margin-top:34px; }
1430
1520
  .set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
1431
1521
  .set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
1522
+ .set-footer { margin-top:40px; padding-top:20px; border-top:1px solid var(--line); display:flex;
1523
+ justify-content:center; gap:8px; color:var(--muted); font-size:12px; }
1524
+ .set-footer-sep { opacity:.5; }
1432
1525
  .theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
1433
1526
  .theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
1434
1527
  text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
@@ -1448,9 +1541,25 @@ var HTML = `<!doctype html>
1448
1541
  .theme-card.sel .tc-check { opacity:1; transform:scale(1); }
1449
1542
  @media (max-width:560px) { .theme-grid { grid-template-columns:1fr; } }
1450
1543
 
1544
+ /* sync devices */
1545
+ .peer-actions { display:flex; gap:10px; margin-bottom:16px; flex-wrap:wrap; }
1546
+ .peer-actions .btn { display:inline-flex; align-items:center; gap:6px; }
1547
+ .peer-list { display:flex; flex-direction:column; gap:8px; }
1548
+ .peer-row { display:flex; align-items:center; gap:12px; padding:12px 14px; border:1px solid var(--line);
1549
+ border-radius:10px; background:var(--surface); }
1550
+ .peer-dot { width:9px; height:9px; border-radius:50%; flex:none; background:var(--muted); }
1551
+ .peer-dot.on { background:#3fb950; } .peer-dot.err { background:#f85149; } .peer-dot.off { background:var(--muted); }
1552
+ .peer-main { flex:1; min-width:0; }
1553
+ .peer-url { font-weight:600; font-size:14px; color:var(--fg); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
1554
+ .peer-sub { font-size:12px; color:var(--muted); margin-top:2px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
1555
+ .peer-menu { flex:none; padding:6px; }
1556
+ .pair-code { font-family:var(--mono); font-size:26px; font-weight:700; letter-spacing:.12em;
1557
+ text-align:center; padding:16px; border:1px dashed var(--line); border-radius:10px; background:var(--surface); color:var(--fg); }
1558
+
1451
1559
  /* main */
1452
1560
  .main { flex:1; display:flex; flex-direction:column; overflow:hidden; min-width:0; }
1453
1561
  .topbar { display:flex; align-items:center; gap:6px; padding:10px 18px; border-bottom:1px solid var(--line); min-height:49px; }
1562
+ .topbar.bare { border-bottom:0; }
1454
1563
  .topbar .hamburger { display:none; }
1455
1564
  .topbar .hamburger.show-collapsed { display:grid; }
1456
1565
  .sidebar.collapsed { overflow:hidden; }
@@ -1775,7 +1884,71 @@ var HTML = `<!doctype html>
1775
1884
  @keyframes slidein { from { opacity:0; transform:translateX(-12px); } }
1776
1885
 
1777
1886
  /* misc */
1778
- .empty { display:grid; place-items:center; height:100%; color:var(--muted); text-align:center; padding:40px; }
1887
+ .empty { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%;
1888
+ color:var(--muted); text-align:center; padding:40px; }
1889
+
1890
+ /* ---- empty state: \u77E5\u8BC6\u751F\u957F\u63D2\u753B ---- */
1891
+ .estate-art { width:240px; height:208px; overflow:visible; animation:idleFloat 5s ease-in-out 1.6s infinite; }
1892
+ .estate-art .stroke-a { fill:none; stroke:var(--accent); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
1893
+ .estate-art .stroke { fill:none; stroke:var(--line-strong); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
1894
+ .estate-art .fill-a { fill:var(--accent); }
1895
+ .estate-art .fill-soft { fill:var(--accent-soft); }
1896
+ .estate-art .fill-surface { fill:var(--bg); }
1897
+ .estate-art .ground { fill:var(--accent); }
1898
+ .estate-title { margin:28px 0 0; font-size:18px; font-weight:600; letter-spacing:.2px; color:var(--fg);
1899
+ opacity:0; animation:fadeUp .5s ease forwards 1.0s; }
1900
+ .estate-hint { margin:9px 0 0; font-size:13.5px; line-height:1.6; color:var(--muted); max-width:300px;
1901
+ opacity:0; animation:fadeUp .5s ease forwards 1.15s; }
1902
+ .estate-link { margin:18px 0 0; font-size:14px; font-weight:600; color:var(--accent);
1903
+ display:inline-flex; align-items:center; gap:6px; padding:9px 8px; border-radius:var(--radius-sm);
1904
+ opacity:0; animation:fadeUp .5s ease forwards 1.3s; transition:opacity .15s; }
1905
+ .estate-link:hover { opacity:.7; }
1906
+ /* intro */
1907
+ .draw { stroke-dasharray:100; stroke-dashoffset:100; }
1908
+ @keyframes draw { to { stroke-dashoffset:0; } }
1909
+ @keyframes leafGrow { from { opacity:0; transform:scale(.2) rotate(-10deg); } to { opacity:1; transform:scale(1) rotate(0); } }
1910
+ @keyframes cardUp { from { opacity:0; transform:translateY(16px) scale(.96); } to { opacity:1; transform:translateY(0) scale(1); } }
1911
+ @keyframes groundIn { to { opacity:.1; } }
1912
+ @keyframes glowIn { to { opacity:.6; } }
1913
+ @keyframes fadeUp { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
1914
+ /* idle loops \u2014 each 0% equals the settled rest state to avoid a jump */
1915
+ @keyframes idleFloat { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-5px); } }
1916
+ @keyframes plantSway { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-1.3deg); } 75% { transform:rotate(1.3deg); } }
1917
+ @keyframes leafSwayA { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-2.6deg); } 75% { transform:rotate(2.6deg); } }
1918
+ @keyframes leafSwayB { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(2.6deg); } 75% { transform:rotate(-2.6deg); } }
1919
+ @keyframes groundBreathe { 0%,100% { transform:scale(1); opacity:.10; } 50% { transform:scale(1.07); opacity:.15; } }
1920
+ @keyframes glowPulse { 0%,100% { transform:scale(1); opacity:.6; } 50% { transform:scale(1.1); opacity:.85; } }
1921
+ @keyframes particle { 0% { opacity:0; transform:translate(0,0) scale(.4); } 35% { opacity:.85; transform:translate(2px,-4px) scale(1); } 70% { opacity:0; transform:translate(4px,-9px) scale(.5); } 100% { opacity:0; transform:translate(0,0) scale(.4); } }
1922
+ /* wiring */
1923
+ .kg-ground { transform-box:fill-box; transform-origin:center; opacity:0;
1924
+ animation:groundIn .5s ease forwards .1s, groundBreathe 6s ease-in-out 1.6s infinite; }
1925
+ .kg-glow { filter:blur(9px); transform-box:fill-box; transform-origin:center; opacity:0;
1926
+ animation:glowIn .6s ease forwards .1s, glowPulse 4.6s ease-in-out 1.6s infinite; }
1927
+ .kg-cardB { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .2s; }
1928
+ .kg-cardA { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .32s; }
1929
+ .kg-plant { transform-box:fill-box; transform-origin:50% 100%; animation:plantSway 6s ease-in-out 1.7s infinite; }
1930
+ .kg-stem { animation:draw .9s ease-out forwards .4s; }
1931
+ .kg-leaf { transform-box:fill-box; }
1932
+ .kg-grow { transform-box:fill-box; opacity:0; }
1933
+ .kg-leaf.l1 { transform-origin:90% 100%; animation:leafSwayA 4.6s ease-in-out 1.7s infinite; }
1934
+ .kg-leaf.l2 { transform-origin:0% 80%; animation:leafSwayB 5.2s ease-in-out 1.8s infinite; }
1935
+ .kg-leaf.l3 { transform-origin:0% 100%; animation:leafSwayA 5.0s ease-in-out 1.9s infinite; }
1936
+ .kg-leaf.l1 .kg-grow { transform-origin:90% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards .85s; }
1937
+ .kg-leaf.l2 .kg-grow { transform-origin:0% 80%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.0s; }
1938
+ .kg-leaf.l3 .kg-grow { transform-origin:0% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.15s; }
1939
+ .kg-particle { transform-box:fill-box; transform-origin:center; opacity:0; }
1940
+ .kg-particle.p1 { animation:particle 4.5s ease-in-out 1.6s infinite; }
1941
+ .kg-particle.p2 { animation:particle 5.2s ease-in-out 2.0s infinite; }
1942
+ .kg-particle.p3 { animation:particle 4.8s ease-in-out 2.6s infinite; }
1943
+ .kg-particle.p4 { animation:particle 5.6s ease-in-out 3.1s infinite; }
1944
+ .kg-particle.p5 { animation:particle 4.2s ease-in-out 2.3s infinite; }
1945
+ @media (prefers-reduced-motion: reduce) {
1946
+ .estate-art, .estate-art *, .estate-title, .estate-hint, .estate-link { animation:none !important; opacity:1 !important; transform:none !important; }
1947
+ .draw { stroke-dashoffset:0 !important; }
1948
+ .kg-ground { opacity:.1 !important; }
1949
+ .kg-glow { opacity:.6 !important; }
1950
+ .kg-particle { opacity:0 !important; }
1951
+ }
1779
1952
  .error-bar { margin:12px 24px; padding:10px 14px; background:var(--danger-soft); color:var(--danger); border-radius:var(--radius-sm); font-size:13px; cursor:pointer; }
1780
1953
  .view-placeholder { padding:40px; text-align:center; border:1px dashed var(--line-strong); border-radius:var(--radius); color:var(--muted); margin-top:8px; }
1781
1954
  .ico.flip { transform:scaleX(-1); }
@@ -1884,6 +2057,49 @@ var HTML = `<!doctype html>
1884
2057
  .db { padding:16px 14px 90px; }
1885
2058
  .peek { width:100%; }
1886
2059
  }
2060
+
2061
+ /* Quick Notes window (desktop, loaded at #quick). A transparent body lets the
2062
+ macOS vibrancy blur show through; a translucent panel keeps text readable.
2063
+ The top bar is the window drag region; its buttons opt back out. */
2064
+ body.quicknote { background:transparent; }
2065
+ /* The main app lays #app out as a flex row (sidebar + main); the quick-note
2066
+ route has a single child, so override to a full-bleed block or the panel
2067
+ shrinks to its content width and the window's vibrancy shows through. */
2068
+ body.quicknote #app { display:block; width:100vw; height:100vh; }
2069
+ .qn { display:flex; flex-direction:column; width:100%; height:100vh; overflow:hidden;
2070
+ background:color-mix(in srgb, var(--bg) 20%, transparent); }
2071
+ .qn-bar { display:flex; align-items:center; gap:6px; padding:4px 10px 4px 76px;
2072
+ user-select:none; -webkit-app-region:drag;
2073
+ background:color-mix(in srgb, var(--surface) 30%, transparent); }
2074
+ .qn-brand { flex:1; min-width:0; font-size:12px; font-weight:600; color:var(--fg-soft);
2075
+ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
2076
+ .qn-actions { display:flex; gap:2px; -webkit-app-region:no-drag; }
2077
+ .qn-actions .iconbtn { width:26px; height:26px; }
2078
+ .qn-actions .iconbtn.active { color:var(--accent); background:var(--hover-2); }
2079
+ .qn-body { flex:1; overflow:auto; -webkit-app-region:no-drag; }
2080
+ .qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
2081
+ .qn-body .doc-title { font-size:24px; margin-bottom:2px; }
2082
+ .qn-body .doc-meta { display:none; }
2083
+ /* Compact menus inside the quick-note window only (dropdown + slash command
2084
+ share .pop/.item/.lico with the main app; scope under body.quicknote so the
2085
+ main app keeps its roomier density). */
2086
+ body.quicknote .pop { padding:5px; border-radius:9px; }
2087
+ body.quicknote .pop .lbl { padding:4px 8px 2px; font-size:10px; }
2088
+ body.quicknote .pop .item { padding:5px 9px; gap:8px; font-size:12.5px; }
2089
+ body.quicknote .pop .item .t { font-size:12.5px; }
2090
+ body.quicknote .pop .item .d { font-size:10.5px; }
2091
+ body.quicknote .pop .item .lico { width:22px; height:22px; }
2092
+ body.quicknote .pop .item .lico.plain { width:20px; height:20px; }
2093
+ body.quicknote .pop .sep { margin:4px 4px; }
2094
+
2095
+ /* Quick Notes settings rows (desktop only). */
2096
+ .qn-set-row { display:flex; align-items:center; gap:14px; padding:12px 0; border-top:1px solid var(--line); }
2097
+ .qn-set-row:first-of-type { border-top:0; }
2098
+ .qn-set-main { flex:1; min-width:0; }
2099
+ .qn-set-name { font-weight:600; }
2100
+ .qn-set-desc { color:var(--muted); font-size:13px; margin-top:2px; }
2101
+ .qn-shortcut { min-width:140px; justify-content:center; font-family:var(--mono); }
2102
+ .qn-shortcut.capturing { outline:2px solid var(--accent); color:var(--accent); }
1887
2103
  </style>
1888
2104
  </head>
1889
2105
  <body>
@@ -3324,7 +3540,7 @@ async function runMain(cmd, opts = {}) {
3324
3540
  // package.json
3325
3541
  var package_default = {
3326
3542
  name: "@tensorix/metahub",
3327
- version: "0.1.2",
3543
+ version: "0.1.4",
3328
3544
  description: "Metahub core library and CLI",
3329
3545
  type: "module",
3330
3546
  main: "./dist/index.js",
@@ -3350,9 +3566,11 @@ var package_default = {
3350
3566
  build: "bun run scripts/build.ts",
3351
3567
  "build:binaries": "bun run scripts/compile-binaries.ts",
3352
3568
  prepublishOnly: "bun run build",
3353
- test: "bun test"
3569
+ test: "bun test",
3570
+ release: "bun run scripts/tag-release.ts"
3354
3571
  },
3355
3572
  dependencies: {
3573
+ "@clack/prompts": "^1.5.0",
3356
3574
  citty: "^0.1.6",
3357
3575
  "highlight.js": "^11.11.1",
3358
3576
  preact: "^10.29.2",
@@ -3389,11 +3607,41 @@ CREATE TABLE IF NOT EXISTS crdt_changes (
3389
3607
  CREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);
3390
3608
 
3391
3609
  -- Per-peer replication cursors (SQLite rowids: monotonic in insertion order,
3392
- -- so no change is ever skipped regardless of HLC/clock skew).
3610
+ -- so no change is ever skipped regardless of HLC/clock skew). Outbound side of
3611
+ -- a pairing: "token" is the credential the *remote* issued to us (sent as a
3612
+ -- Bearer header when we sync to them). See migratePeers in db.ts for the columns
3613
+ -- added to legacy databases.
3393
3614
  CREATE TABLE IF NOT EXISTS peers (
3394
- url TEXT PRIMARY KEY,
3395
- pull_cursor INTEGER NOT NULL DEFAULT 0,
3396
- push_cursor INTEGER NOT NULL DEFAULT 0
3615
+ url TEXT PRIMARY KEY,
3616
+ pull_cursor INTEGER NOT NULL DEFAULT 0,
3617
+ push_cursor INTEGER NOT NULL DEFAULT 0,
3618
+ token TEXT,
3619
+ label TEXT,
3620
+ node_id TEXT,
3621
+ enabled INTEGER NOT NULL DEFAULT 1,
3622
+ last_sync_at INTEGER,
3623
+ last_status TEXT,
3624
+ last_error TEXT
3625
+ );
3626
+
3627
+ -- Inbound side of a pairing: credentials *we* issued to remote devices. A peer
3628
+ -- presents one of these on /sync; acceptsSyncToken() checks it alongside the
3629
+ -- managed master token. Revoke by deleting the row.
3630
+ CREATE TABLE IF NOT EXISTS peer_grants (
3631
+ token TEXT PRIMARY KEY,
3632
+ peer_url TEXT,
3633
+ node_id TEXT,
3634
+ created_at INTEGER
3635
+ );
3636
+
3637
+ -- One-time pairing codes minted by "mh config peer code" / POST /api/pair/new.
3638
+ -- Short-lived and single-use: redeemed during the pairing handshake, then the
3639
+ -- two devices exchange durable peer_grants. used/exp gate redemption.
3640
+ CREATE TABLE IF NOT EXISTS pairing_codes (
3641
+ code TEXT PRIMARY KEY,
3642
+ exp INTEGER NOT NULL,
3643
+ used INTEGER NOT NULL DEFAULT 0,
3644
+ created_at INTEGER
3397
3645
  );
3398
3646
 
3399
3647
  CREATE TABLE IF NOT EXISTS databases (
@@ -3520,9 +3768,20 @@ function deleteDatabase(db, id) {
3520
3768
  if (!getDatabase(db, id))
3521
3769
  return false;
3522
3770
  emit(db, "databases", id, "__deleted", 1);
3771
+ const tombstone = (table) => {
3772
+ for (const r3 of liveChildren(db, table, "database_id", id))
3773
+ emit(db, table, r3, "__deleted", 1);
3774
+ };
3775
+ tombstone("properties");
3776
+ tombstone("records");
3777
+ for (const d2 of liveChildren(db, "documents", "database_id", id))
3778
+ emit(db, "documents", d2, "database_id", null);
3523
3779
  db.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(id);
3524
3780
  return true;
3525
3781
  }
3782
+ function liveChildren(db, table, col, parentId) {
3783
+ return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r3) => r3.id);
3784
+ }
3526
3785
 
3527
3786
  // src/core/properties.ts
3528
3787
  init_ids();
@@ -3651,9 +3910,13 @@ function setPropertyWidth(db, id, width) {
3651
3910
  return updateProperty(db, id, { config: { ...cur.config ?? {}, width: w2 } });
3652
3911
  }
3653
3912
  function removeProperty(db, id) {
3654
- if (!getProperty(db, id))
3913
+ const prop = getProperty(db, id);
3914
+ if (!prop)
3655
3915
  return false;
3656
3916
  emit(db, "properties", id, "__deleted", 1);
3917
+ const rows = db.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(prop.database_id, id);
3918
+ for (const r3 of rows)
3919
+ emit(db, "records", r3.id, id, undefined);
3657
3920
  return true;
3658
3921
  }
3659
3922
 
@@ -4106,12 +4369,28 @@ function migrateRecords(db) {
4106
4369
  tx();
4107
4370
  backfillRecordOrderKeys(db);
4108
4371
  }
4372
+ function migratePeers(db) {
4373
+ const add = [
4374
+ ["token", "TEXT"],
4375
+ ["label", "TEXT"],
4376
+ ["node_id", "TEXT"],
4377
+ ["enabled", "INTEGER NOT NULL DEFAULT 1"],
4378
+ ["last_sync_at", "INTEGER"],
4379
+ ["last_status", "TEXT"],
4380
+ ["last_error", "TEXT"]
4381
+ ];
4382
+ for (const [col, decl] of add) {
4383
+ if (!hasColumn(db, "peers", col))
4384
+ db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
4385
+ }
4386
+ }
4109
4387
  function openMetahub() {
4110
4388
  ensureDirs();
4111
4389
  const db = new Database(dbPath(), { create: true });
4112
4390
  db.exec("PRAGMA journal_mode = WAL;");
4113
4391
  runSchema(db);
4114
4392
  migrateRecords(db);
4393
+ migratePeers(db);
4115
4394
  return db;
4116
4395
  }
4117
4396
 
@@ -4354,9 +4633,12 @@ function getDocument(db, id) {
4354
4633
  return db.query("SELECT id, title, body, database_id, parent_id, created_hlc FROM documents WHERE id = ? AND __deleted = 0").get(id);
4355
4634
  }
4356
4635
  function listDocuments(db, opts = {}) {
4636
+ const cols = "SELECT id, title, database_id, parent_id, created_hlc FROM documents WHERE __deleted = 0";
4637
+ if (opts.parent_id !== undefined)
4638
+ return db.query(`${cols} AND parent_id = ? ORDER BY created_hlc`).all(opts.parent_id);
4357
4639
  if (opts.database_id)
4358
- return db.query("SELECT id, title, database_id, parent_id, created_hlc FROM documents WHERE database_id = ? AND __deleted = 0 ORDER BY created_hlc").all(opts.database_id);
4359
- return db.query("SELECT id, title, database_id, parent_id, created_hlc FROM documents WHERE __deleted = 0 ORDER BY created_hlc").all();
4640
+ return db.query(`${cols} AND database_id = ? ORDER BY created_hlc`).all(opts.database_id);
4641
+ return db.query(`${cols} ORDER BY created_hlc`).all();
4360
4642
  }
4361
4643
  function updateDocument(db, id, fields) {
4362
4644
  if (!getDocument(db, id))
@@ -4440,8 +4722,15 @@ function deleteDocument(db, id) {
4440
4722
  if (!getDocument(db, id))
4441
4723
  return false;
4442
4724
  emit(db, "documents", id, "__deleted", 1);
4725
+ for (const b2 of liveChildIds(db, "doc_blocks", "doc_id", id))
4726
+ emit(db, "doc_blocks", b2, "__deleted", 1);
4727
+ for (const c3 of liveChildIds(db, "documents", "parent_id", id))
4728
+ emit(db, "documents", c3, "parent_id", null);
4443
4729
  return true;
4444
4730
  }
4731
+ function liveChildIds(db, table2, col, parentId) {
4732
+ return db.query(`SELECT id FROM ${table2} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r3) => r3.id);
4733
+ }
4445
4734
  function countOccurrences(haystack, needle) {
4446
4735
  if (needle === "")
4447
4736
  return 0;
@@ -4675,6 +4964,7 @@ function docTree(rows) {
4675
4964
  if (rows.length === 0)
4676
4965
  return "(empty)";
4677
4966
  const ids = new Set(rows.map((r3) => r3.id));
4967
+ const idWidth = Math.max(...rows.map((r3) => r3.id.length));
4678
4968
  const children = new Map;
4679
4969
  const roots = [];
4680
4970
  for (const r3 of rows) {
@@ -4685,13 +4975,15 @@ function docTree(rows) {
4685
4975
  (children.get(key) ?? children.set(key, []).get(key)).push(r3);
4686
4976
  }
4687
4977
  const lines = [];
4688
- const walk = (node, depth) => {
4689
- lines.push(`${node.id} ${" ".repeat(depth)}${node.title || "(untitled)"}`);
4690
- for (const child of children.get(node.id) ?? [])
4691
- walk(child, depth + 1);
4692
- };
4693
- for (const root of roots)
4694
- walk(root, 0);
4978
+ const walk = (node, prefix, isRoot, isLast) => {
4979
+ const branch = isRoot ? "" : isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4980
+ const title = node.title || "(untitled)";
4981
+ lines.push(`${node.id.padEnd(idWidth)} ${prefix}${branch}${title}`);
4982
+ const kids = children.get(node.id) ?? [];
4983
+ const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
4984
+ kids.forEach((child, i2) => walk(child, childPrefix, false, i2 === kids.length - 1));
4985
+ };
4986
+ roots.forEach((root) => walk(root, "", true, true));
4695
4987
  return lines.join(`
4696
4988
  `);
4697
4989
  }
@@ -4963,26 +5255,106 @@ var edit_default = defineCommand({
4963
5255
  });
4964
5256
 
4965
5257
  // src/core/search.ts
5258
+ init_crdt();
4966
5259
  var TEXT_TYPES = "('text','url','select','multi_select','date')";
4967
- function ensureIndex(db) {
4968
- if (!ftsAvailable(db))
4969
- return false;
4970
- const max = db.query("SELECT MAX(hlc) AS h FROM crdt_changes").get().h ?? "";
4971
- const indexed = db.query("SELECT value FROM meta WHERE key = 'search_hlc'").get()?.value ?? "";
4972
- if (max === indexed)
4973
- return true;
4974
- db.query("DELETE FROM search_fts").run();
4975
- for (const d2 of db.query("SELECT id, title, body, database_id FROM documents WHERE __deleted = 0").all()) {
5260
+ var SEARCH_INDEX_VERSION = "1";
5261
+ function readMeta(db, key) {
5262
+ return db.query("SELECT value FROM meta WHERE key = ?").get(key)?.value ?? null;
5263
+ }
5264
+ function writeMeta(db, key, value) {
5265
+ db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
5266
+ }
5267
+ function reindexDocument(db, id) {
5268
+ db.query("DELETE FROM search_fts WHERE kind = 'document' AND id = ?").run(id);
5269
+ const d2 = db.query("SELECT id, title, body, database_id FROM documents WHERE id = ? AND __deleted = 0").get(id);
5270
+ if (d2)
4976
5271
  db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d2.id, d2.database_id, d2.title ?? "", d2.body ?? "");
4977
- }
4978
- for (const r3 of db.query(`SELECT r.id AS id, r.database_id AS database_id, group_concat(je.value, ' ') AS body
5272
+ }
5273
+ function recordBodyRows(db, where, ...args) {
5274
+ return db.query(`SELECT r.id AS id, r.database_id AS database_id, group_concat(je.value, ' ') AS body
4979
5275
  FROM records r, json_each(r.data) je
4980
5276
  JOIN properties p ON p.id = je.key AND p.__deleted = 0
4981
- WHERE r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
4982
- GROUP BY r.id`).all()) {
4983
- db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('record', ?, ?, ?, ?)").run(r3.id, r3.database_id, r3.id, r3.body ?? "");
5277
+ WHERE ${where} AND r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
5278
+ GROUP BY r.id`).all(...args);
5279
+ }
5280
+ function insertRecordRow(db, r3) {
5281
+ db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('record', ?, ?, ?, ?)").run(r3.id, r3.database_id, r3.id, r3.body ?? "");
5282
+ }
5283
+ function reindexRecord(db, id) {
5284
+ db.query("DELETE FROM search_fts WHERE kind = 'record' AND id = ?").run(id);
5285
+ const r3 = recordBodyRows(db, "r.id = ?", id)[0];
5286
+ if (r3)
5287
+ insertRecordRow(db, r3);
5288
+ }
5289
+ function reindexDatabaseRecords(db, databaseId) {
5290
+ db.query("DELETE FROM search_fts WHERE kind = 'record' AND database_id = ?").run(databaseId);
5291
+ for (const r3 of recordBodyRows(db, "r.database_id = ?", databaseId))
5292
+ insertRecordRow(db, r3);
5293
+ }
5294
+ function fullRebuild(db) {
5295
+ db.query("DELETE FROM search_fts").run();
5296
+ for (const d2 of db.query("SELECT id, title, body, database_id FROM documents WHERE __deleted = 0").all())
5297
+ db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d2.id, d2.database_id, d2.title ?? "", d2.body ?? "");
5298
+ for (const r3 of recordBodyRows(db, "1 = 1"))
5299
+ insertRecordRow(db, r3);
5300
+ const max = db.query("SELECT MAX(rowid) AS m FROM crdt_changes").get().m ?? 0;
5301
+ writeMeta(db, "search_seq", String(max));
5302
+ writeMeta(db, "search_index_version", SEARCH_INDEX_VERSION);
5303
+ }
5304
+ function incrementalUpdate(db) {
5305
+ const cursor = Number(readMeta(db, "search_seq") ?? "0");
5306
+ const { changes, cursor: next } = changesAfterSeq(db, cursor);
5307
+ if (changes.length === 0)
5308
+ return;
5309
+ const docIds = new Set;
5310
+ const recordIds = new Set;
5311
+ const dbIds = new Set;
5312
+ for (const c3 of changes) {
5313
+ switch (c3.dataset) {
5314
+ case "documents":
5315
+ docIds.add(c3.row_id);
5316
+ break;
5317
+ case "doc_blocks": {
5318
+ const blk = db.query("SELECT doc_id FROM doc_blocks WHERE id = ?").get(c3.row_id);
5319
+ if (blk?.doc_id)
5320
+ docIds.add(blk.doc_id);
5321
+ break;
5322
+ }
5323
+ case "records":
5324
+ recordIds.add(c3.row_id);
5325
+ break;
5326
+ case "properties":
5327
+ if (c3.col === "type" || c3.col === "__deleted" || c3.col === "database_id") {
5328
+ const p = db.query("SELECT database_id FROM properties WHERE id = ?").get(c3.row_id);
5329
+ if (p?.database_id)
5330
+ dbIds.add(p.database_id);
5331
+ }
5332
+ break;
5333
+ }
5334
+ }
5335
+ for (const id of docIds)
5336
+ reindexDocument(db, id);
5337
+ for (const dbId of dbIds)
5338
+ reindexDatabaseRecords(db, dbId);
5339
+ for (const id of recordIds) {
5340
+ const r3 = db.query("SELECT database_id FROM records WHERE id = ?").get(id);
5341
+ if (r3?.database_id && dbIds.has(r3.database_id))
5342
+ continue;
5343
+ reindexRecord(db, id);
4984
5344
  }
4985
- db.query("INSERT INTO meta (key, value) VALUES ('search_hlc', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(max);
5345
+ writeMeta(db, "search_seq", String(next));
5346
+ }
5347
+ function ensureIndex(db) {
5348
+ if (!ftsAvailable(db))
5349
+ return false;
5350
+ const version = readMeta(db, "search_index_version");
5351
+ const seq = readMeta(db, "search_seq");
5352
+ db.transaction(() => {
5353
+ if (version !== SEARCH_INDEX_VERSION || seq === null)
5354
+ fullRebuild(db);
5355
+ else
5356
+ incrementalUpdate(db);
5357
+ })();
4986
5358
  return true;
4987
5359
  }
4988
5360
  function ftsSearch(db, query, limit) {
@@ -19674,25 +20046,40 @@ var HealthResponseSchema = exports_external.object({
19674
20046
  ok: exports_external.boolean(),
19675
20047
  node: exports_external.string().describe("Server's node id")
19676
20048
  });
20049
+ var PairRequestSchema = exports_external.object({
20050
+ code: exports_external.string().describe("One-time pairing code minted by this server"),
20051
+ node_id: exports_external.string().describe("Calling device's node id"),
20052
+ grant: exports_external.string().describe("Durable credential the caller issues to us"),
20053
+ self_url: exports_external.string().optional().describe("Caller's reachable server URL (for mutual sync)")
20054
+ });
20055
+ var PairResponseSchema = exports_external.object({
20056
+ node_id: exports_external.string().describe("This server's node id"),
20057
+ grant: exports_external.string().describe("Durable credential we issue to the caller")
20058
+ });
19677
20059
  var SYNC_PATH = "/sync";
19678
20060
  var HEALTH_PATH = "/health";
19679
20061
  var RENEW_PATH = "/auth/token";
20062
+ var PAIR_PATH = "/api/pair";
19680
20063
 
19681
20064
  // src/core/sync/client.ts
19682
20065
  function getPeer(db, url2) {
19683
- const row = db.query("SELECT pull_cursor, push_cursor FROM peers WHERE url = ?").get(url2);
19684
- return row ?? { pull_cursor: 0, push_cursor: 0 };
20066
+ const row = db.query("SELECT pull_cursor, push_cursor, token FROM peers WHERE url = ?").get(url2);
20067
+ return row ?? { pull_cursor: 0, push_cursor: 0, token: null };
19685
20068
  }
19686
20069
  function setPeer(db, url2, c3) {
19687
20070
  db.query("INSERT INTO peers (url, pull_cursor, push_cursor) VALUES (?, ?, ?) ON CONFLICT(url) DO UPDATE SET pull_cursor = excluded.pull_cursor, push_cursor = excluded.push_cursor").run(url2, c3.pull_cursor, c3.push_cursor);
19688
20071
  }
19689
- async function syncWithPeer(db, url2) {
20072
+ async function syncWithPeer(db, url2, token) {
19690
20073
  const node = getNodeId(db);
19691
20074
  const peer = getPeer(db, url2);
20075
+ const bearer = token ?? peer.token ?? undefined;
19692
20076
  const toPush = changesAfterSeq(db, peer.push_cursor);
20077
+ const headers = { "content-type": "application/json" };
20078
+ if (bearer)
20079
+ headers.authorization = `Bearer ${bearer}`;
19693
20080
  const res = await fetch(new URL(SYNC_PATH, url2), {
19694
20081
  method: "POST",
19695
- headers: { "content-type": "application/json" },
20082
+ headers,
19696
20083
  body: JSON.stringify({ node_id: node, since: peer.pull_cursor, changes: toPush.changes })
19697
20084
  });
19698
20085
  if (!res.ok)
@@ -19704,6 +20091,55 @@ async function syncWithPeer(db, url2) {
19704
20091
  return { pushed: toPush.changes.length, pulled };
19705
20092
  }
19706
20093
 
20094
+ // src/core/sync/peers.ts
20095
+ function listPeers(db) {
20096
+ return db.query("SELECT url, pull_cursor, push_cursor, token, label, node_id, enabled, last_sync_at, last_status, last_error FROM peers ORDER BY url").all();
20097
+ }
20098
+ function addPeer(db, input) {
20099
+ db.query(`INSERT INTO peers (url, token, label, node_id, enabled, pull_cursor, push_cursor)
20100
+ VALUES (?, ?, ?, ?, 1, 0, 0)
20101
+ ON CONFLICT(url) DO UPDATE SET
20102
+ token = coalesce(excluded.token, peers.token),
20103
+ label = coalesce(excluded.label, peers.label),
20104
+ node_id = coalesce(excluded.node_id, peers.node_id),
20105
+ enabled = 1`).run(input.url, input.token ?? null, input.label ?? null, input.node_id ?? null);
20106
+ }
20107
+ function removePeer(db, url2) {
20108
+ const tx = db.transaction(() => {
20109
+ const changed = db.query("DELETE FROM peers WHERE url = ?").run(url2).changes > 0;
20110
+ db.query("DELETE FROM peer_grants WHERE peer_url = ?").run(url2);
20111
+ return changed;
20112
+ });
20113
+ return tx();
20114
+ }
20115
+ function setPeerEnabled(db, url2, enabled) {
20116
+ return db.query("UPDATE peers SET enabled = ? WHERE url = ?").run(enabled ? 1 : 0, url2).changes > 0;
20117
+ }
20118
+ function setPeerLabel(db, url2, label) {
20119
+ return db.query("UPDATE peers SET label = ? WHERE url = ?").run(label, url2).changes > 0;
20120
+ }
20121
+ function updatePeerStatus(db, url2, status, error51) {
20122
+ db.query("UPDATE peers SET last_sync_at = ?, last_status = ?, last_error = ? WHERE url = ?").run(Date.now(), status, error51 ?? null, url2);
20123
+ }
20124
+ async function syncPeer(db, url2) {
20125
+ try {
20126
+ const result = await syncWithPeer(db, url2);
20127
+ updatePeerStatus(db, url2, "ok", null);
20128
+ return { url: url2, ok: true, pushed: result.pushed, pulled: result.pulled };
20129
+ } catch (e2) {
20130
+ const error51 = e2.message;
20131
+ updatePeerStatus(db, url2, "error", error51);
20132
+ return { url: url2, ok: false, error: error51 };
20133
+ }
20134
+ }
20135
+ async function syncAllPeers(db) {
20136
+ const peers = listPeers(db).filter((p) => p.enabled);
20137
+ const out = [];
20138
+ for (const p of peers)
20139
+ out.push(await syncPeer(db, p.url));
20140
+ return out;
20141
+ }
20142
+
19707
20143
  // src/core/csv.ts
19708
20144
  var NEEDS_QUOTE = /[",\r\n]/;
19709
20145
  function encodeCell(value) {
@@ -19856,6 +20292,31 @@ async function syncFiles(db, src2, dst) {
19856
20292
  }
19857
20293
 
19858
20294
  // src/cli/commands/sync.ts
20295
+ function isAuthError(e2) {
20296
+ return e2 instanceof Error && /\b401\b|unauthorized/i.test(e2.message);
20297
+ }
20298
+ var isTTY = () => Boolean(process.stdout.isTTY && process.stdin.isTTY);
20299
+ async function peerSync(db, url2, token) {
20300
+ try {
20301
+ const result = await syncWithPeer(db, url2, token);
20302
+ if (token)
20303
+ addPeer(db, { url: url2, token });
20304
+ return result;
20305
+ } catch (e2) {
20306
+ if (token || !isAuthError(e2))
20307
+ throw e2;
20308
+ if (!isTTY()) {
20309
+ throw new Error(`${e2.message}
20310
+ this server requires a token \u2014 pass --token <token> or run in an interactive terminal`);
20311
+ }
20312
+ const entered = (globalThis.prompt("\u8BE5\u670D\u52A1\u5668\u9700\u8981\u4EE4\u724C\uFF0C\u8BF7\u8F93\u5165 token:") ?? "").trim();
20313
+ if (!entered)
20314
+ throw new Error("no token provided");
20315
+ const result = await syncWithPeer(db, url2, entered);
20316
+ addPeer(db, { url: url2, token: entered });
20317
+ return result;
20318
+ }
20319
+ }
19859
20320
  var sync_default = defineCommand({
19860
20321
  meta: {
19861
20322
  name: "sync",
@@ -19871,12 +20332,16 @@ var sync_default = defineCommand({
19871
20332
  type: "positional",
19872
20333
  required: false,
19873
20334
  description: "File path or entity ref; when given, export/import instead of peer sync"
20335
+ },
20336
+ token: {
20337
+ type: "string",
20338
+ description: "Auth token for a protected sync server (saved for next time)"
19874
20339
  }
19875
20340
  },
19876
20341
  run: guard(async (args) => {
19877
20342
  const db = openMetahub();
19878
20343
  if (args.dst == null) {
19879
- const result = await syncWithPeer(db, args.src);
20344
+ const result = await peerSync(db, args.src, args.token);
19880
20345
  return print(result, () => `pushed ${result.pushed}, pulled ${result.pulled}`);
19881
20346
  }
19882
20347
  const r3 = await syncFiles(db, args.src, args.dst);
@@ -19888,11 +20353,262 @@ var sync_default = defineCommand({
19888
20353
  init_crdt();
19889
20354
  import { readdirSync, existsSync } from "fs";
19890
20355
  import { join as join5 } from "path";
20356
+
20357
+ // src/core/integrity.ts
20358
+ init_crdt();
20359
+ var REF_RULES = [
20360
+ { srcTable: "properties", fk: "database_id", tgtTable: "databases", fix: "tombstone-src" },
20361
+ { srcTable: "records", fk: "database_id", tgtTable: "databases", fix: "tombstone-src" },
20362
+ { srcTable: "documents", fk: "database_id", tgtTable: "databases", fix: "null-fk" },
20363
+ { srcTable: "documents", fk: "parent_id", tgtTable: "documents", fix: "null-fk" },
20364
+ { srcTable: "doc_blocks", fk: "doc_id", tgtTable: "documents", fix: "tombstone-src" },
20365
+ { srcTable: "site_files", fk: "site_id", tgtTable: "sites", fix: "tombstone-src" }
20366
+ ];
20367
+ function brokenRefRows(db, r3) {
20368
+ return db.query(`SELECT s.id AS id, s.${r3.fk} AS ref FROM ${r3.srcTable} s
20369
+ JOIN ${r3.tgtTable} t ON t.id = s.${r3.fk} AND t.__deleted = 1
20370
+ WHERE s.__deleted = 0 AND s.${r3.fk} IS NOT NULL
20371
+ ORDER BY s.id`).all();
20372
+ }
20373
+ function detectBrokenRefs(db) {
20374
+ const out = [];
20375
+ for (const r3 of REF_RULES) {
20376
+ for (const row of brokenRefRows(db, r3)) {
20377
+ out.push({
20378
+ category: "broken_ref",
20379
+ entity: r3.srcTable,
20380
+ id: row.id,
20381
+ detail: `${r3.fk} \u2192 deleted ${r3.tgtTable} ${row.ref} (${r3.fix === "tombstone-src" ? "tombstone" : `clear ${r3.fk}`})`,
20382
+ fixable: true
20383
+ });
20384
+ }
20385
+ }
20386
+ return out;
20387
+ }
20388
+ function repairBrokenRefs(db) {
20389
+ let n2 = 0;
20390
+ for (const r3 of REF_RULES) {
20391
+ for (const row of brokenRefRows(db, r3)) {
20392
+ if (r3.fix === "tombstone-src")
20393
+ emit(db, r3.srcTable, row.id, "__deleted", 1);
20394
+ else
20395
+ emit(db, r3.srcTable, row.id, r3.fk, null);
20396
+ n2++;
20397
+ }
20398
+ }
20399
+ return n2;
20400
+ }
20401
+ function orphanCellRows(db) {
20402
+ return db.query(`SELECT r.id AS rec, j.key AS prop
20403
+ FROM records r, json_each(coalesce(r.data, '{}')) j
20404
+ JOIN properties p ON p.id = j.key AND p.__deleted = 1
20405
+ WHERE r.__deleted = 0
20406
+ ORDER BY r.id, j.key`).all();
20407
+ }
20408
+ function detectOrphanCells(db) {
20409
+ return orphanCellRows(db).map((row) => ({
20410
+ category: "orphan_cell",
20411
+ entity: "records",
20412
+ id: row.rec,
20413
+ detail: `cell for deleted property ${row.prop}`,
20414
+ fixable: true
20415
+ }));
20416
+ }
20417
+ function repairOrphanCells(db) {
20418
+ const rows = orphanCellRows(db);
20419
+ for (const row of rows)
20420
+ emit(db, "records", row.rec, row.prop, undefined);
20421
+ return rows.length;
20422
+ }
20423
+ function dupPathLosers(db) {
20424
+ const rows = db.query(`SELECT id, site_id, path FROM site_files
20425
+ WHERE __deleted = 0 AND site_id IS NOT NULL AND path IS NOT NULL
20426
+ ORDER BY site_id, path, created_hlc, id`).all();
20427
+ const losers = [];
20428
+ let group = null;
20429
+ for (const r3 of rows) {
20430
+ const key = `${r3.site_id}\x00${r3.path}`;
20431
+ if (key === group)
20432
+ losers.push({ id: r3.id });
20433
+ else
20434
+ group = key;
20435
+ }
20436
+ return losers;
20437
+ }
20438
+ function detectDupPaths(db) {
20439
+ return dupPathLosers(db).map((row) => ({
20440
+ category: "dup_path",
20441
+ entity: "site_files",
20442
+ id: row.id,
20443
+ detail: "redundant duplicate (site_id, path); loser tombstoned",
20444
+ fixable: true
20445
+ }));
20446
+ }
20447
+ function repairDupPaths(db) {
20448
+ const losers = dupPathLosers(db);
20449
+ for (const row of losers)
20450
+ emit(db, "site_files", row.id, "__deleted", 1);
20451
+ return losers.length;
20452
+ }
20453
+ function detectParentCycles(db) {
20454
+ const rows = db.query(`SELECT d.id AS id, d.parent_id AS parent, d.created_hlc AS hlc
20455
+ FROM documents d
20456
+ WHERE d.__deleted = 0 AND d.parent_id IS NOT NULL
20457
+ AND EXISTS (SELECT 1 FROM documents p WHERE p.id = d.parent_id AND p.__deleted = 0)
20458
+ ORDER BY d.id`).all();
20459
+ const parent = new Map(rows.map((r3) => [r3.id, r3.parent]));
20460
+ const hlc = new Map(rows.map((r3) => [r3.id, r3.hlc]));
20461
+ const seen = new Set;
20462
+ const cycles = [];
20463
+ for (const start of parent.keys()) {
20464
+ if (seen.has(start))
20465
+ continue;
20466
+ const path = [];
20467
+ const pos = new Map;
20468
+ let cur = start;
20469
+ while (cur != null && parent.has(cur) && !seen.has(cur)) {
20470
+ if (pos.has(cur)) {
20471
+ cycles.push(path.slice(pos.get(cur)));
20472
+ break;
20473
+ }
20474
+ pos.set(cur, path.length);
20475
+ path.push(cur);
20476
+ cur = parent.get(cur);
20477
+ }
20478
+ for (const n2 of path)
20479
+ seen.add(n2);
20480
+ }
20481
+ return { cycles, hlc };
20482
+ }
20483
+ function geHlcId(hlc, a2, b2) {
20484
+ const ha = hlc.get(a2) ?? "";
20485
+ const hb = hlc.get(b2) ?? "";
20486
+ if (ha !== hb)
20487
+ return ha > hb;
20488
+ return a2 >= b2;
20489
+ }
20490
+ function detectCycleIssues(db) {
20491
+ const { cycles } = detectParentCycles(db);
20492
+ return cycles.map((cyc) => ({
20493
+ category: "parent_cycle",
20494
+ entity: "documents",
20495
+ id: [...cyc].sort()[0],
20496
+ detail: `parent cycle: ${[...cyc].sort().join(" \u2192 ")}`,
20497
+ fixable: true
20498
+ }));
20499
+ }
20500
+ function repairParentCycles(db) {
20501
+ const { cycles, hlc } = detectParentCycles(db);
20502
+ for (const cyc of cycles) {
20503
+ const brk = cyc.reduce((a2, b2) => geHlcId(hlc, a2, b2) ? a2 : b2);
20504
+ emit(db, "documents", brk, "parent_id", null);
20505
+ }
20506
+ return cycles.length;
20507
+ }
20508
+ function detectDupNames(db) {
20509
+ const out = [];
20510
+ const props = db.query(`SELECT database_id AS db, lower(name) AS n, count(*) AS c, group_concat(id) AS ids
20511
+ FROM properties
20512
+ WHERE __deleted = 0 AND name IS NOT NULL AND database_id IS NOT NULL
20513
+ GROUP BY database_id, lower(name) HAVING c > 1
20514
+ ORDER BY database_id, n`).all();
20515
+ for (const r3 of props)
20516
+ out.push({
20517
+ category: "dup_name",
20518
+ entity: "properties",
20519
+ id: r3.ids.split(",").sort()[0],
20520
+ detail: `${r3.c} properties named "${r3.n}" in database ${r3.db}: ${r3.ids}`,
20521
+ fixable: false
20522
+ });
20523
+ const dbs = db.query(`SELECT lower(name) AS n, count(*) AS c, group_concat(id) AS ids
20524
+ FROM databases WHERE __deleted = 0 AND name IS NOT NULL
20525
+ GROUP BY lower(name) HAVING c > 1 ORDER BY n`).all();
20526
+ for (const r3 of dbs)
20527
+ out.push({
20528
+ category: "dup_name",
20529
+ entity: "databases",
20530
+ id: r3.ids.split(",").sort()[0],
20531
+ detail: `${r3.c} databases named "${r3.n}": ${r3.ids}`,
20532
+ fixable: false
20533
+ });
20534
+ return out;
20535
+ }
20536
+ function configIssue(type, config2) {
20537
+ if (!PROP_TYPES.has(type))
20538
+ return `unknown property type: ${type}`;
20539
+ if (type === "select" || type === "multi_select") {
20540
+ const opts = config2?.options;
20541
+ if (!Array.isArray(opts) || opts.length === 0 || !opts.every((o3) => typeof o3 === "string"))
20542
+ return `${type} missing config.options: string[]`;
20543
+ }
20544
+ if (type === "relation" && typeof config2?.database !== "string")
20545
+ return "relation missing config.database";
20546
+ return null;
20547
+ }
20548
+ function detectBadConfig(db) {
20549
+ const rows = db.query("SELECT id, type, config FROM properties WHERE __deleted = 0 ORDER BY id").all();
20550
+ const out = [];
20551
+ for (const r3 of rows) {
20552
+ let config2 = null;
20553
+ try {
20554
+ config2 = r3.config ? JSON.parse(r3.config) : null;
20555
+ } catch {
20556
+ out.push({ category: "bad_config", entity: "properties", id: r3.id, detail: "config is not valid JSON", fixable: false });
20557
+ continue;
20558
+ }
20559
+ const problem = configIssue(r3.type, config2);
20560
+ if (problem)
20561
+ out.push({ category: "bad_config", entity: "properties", id: r3.id, detail: problem, fixable: false });
20562
+ }
20563
+ return out;
20564
+ }
20565
+ function validateHub(db) {
20566
+ const issues = [
20567
+ ...detectBrokenRefs(db),
20568
+ ...detectOrphanCells(db),
20569
+ ...detectDupPaths(db),
20570
+ ...detectCycleIssues(db),
20571
+ ...detectDupNames(db),
20572
+ ...detectBadConfig(db)
20573
+ ];
20574
+ const counts = {};
20575
+ for (const i2 of issues)
20576
+ counts[i2.category] = (counts[i2.category] ?? 0) + 1;
20577
+ return { ok: issues.length === 0, total: issues.length, counts, issues };
20578
+ }
20579
+ function repairPass(db) {
20580
+ const fixed = {};
20581
+ const bump = (cat, n2) => {
20582
+ if (n2)
20583
+ fixed[cat] = (fixed[cat] ?? 0) + n2;
20584
+ };
20585
+ bump("broken_ref", repairBrokenRefs(db));
20586
+ bump("orphan_cell", repairOrphanCells(db));
20587
+ bump("dup_path", repairDupPaths(db));
20588
+ bump("parent_cycle", repairParentCycles(db));
20589
+ const applied = Object.values(fixed).reduce((a2, b2) => a2 + b2, 0);
20590
+ return { applied, fixed };
20591
+ }
20592
+ function repairHub(db) {
20593
+ const fixed = {};
20594
+ let applied = 0;
20595
+ for (let i2 = 0;i2 < 20; i2++) {
20596
+ const pass = repairPass(db);
20597
+ if (pass.applied === 0)
20598
+ break;
20599
+ applied += pass.applied;
20600
+ for (const [cat, n2] of Object.entries(pass.fixed))
20601
+ fixed[cat] = (fixed[cat] ?? 0) + n2;
20602
+ }
20603
+ return { applied, fixed, remaining: validateHub(db) };
20604
+ }
20605
+
20606
+ // src/core/snapshot.ts
19891
20607
  init_paths();
19892
20608
  init_cache();
19893
20609
  var SNAPSHOT_FORMAT = "metahub-snapshot";
19894
20610
  var SNAPSHOT_VERSION = 1;
19895
- function readMeta(db, key) {
20611
+ function readMeta2(db, key) {
19896
20612
  const row = db.query("SELECT value FROM meta WHERE key = ?").get(key);
19897
20613
  return row?.value ?? null;
19898
20614
  }
@@ -19909,8 +20625,8 @@ async function readBlobs() {
19909
20625
  }
19910
20626
  async function createSnapshot(db) {
19911
20627
  const changes = changesAfterSeq(db, 0).changes;
19912
- const node_id = readMeta(db, "node_id");
19913
- const hlc = readMeta(db, "hlc");
20628
+ const node_id = readMeta2(db, "node_id");
20629
+ const hlc = readMeta2(db, "hlc");
19914
20630
  const peers = db.query("SELECT url, pull_cursor, push_cursor FROM peers").all();
19915
20631
  const blobs = await readBlobs();
19916
20632
  return {
@@ -19971,8 +20687,9 @@ async function restoreSnapshot(db, pkg, opts = {}) {
19971
20687
  if (!opts.reset) {
19972
20688
  const applied2 = ingest(db, pkg.changes);
19973
20689
  rebuildDeclaredIndexes(db);
20690
+ const repaired2 = repairHub(db).applied;
19974
20691
  const blobs2 = await writeBlobs(pkg.blobs);
19975
- return { mode: "merge", applied: applied2, blobs: blobs2 };
20692
+ return { mode: "merge", applied: applied2, blobs: blobs2, repaired: repaired2 };
19976
20693
  }
19977
20694
  if (!opts.force)
19978
20695
  throw new Error("refusing to replace local data without --force (a safety snapshot is saved first)");
@@ -19990,7 +20707,7 @@ async function restoreSnapshot(db, pkg, opts = {}) {
19990
20707
  "peers"
19991
20708
  ])
19992
20709
  db.query(`DELETE FROM ${t2}`).run();
19993
- db.query("DELETE FROM meta WHERE key = 'search_hlc'").run();
20710
+ db.query("DELETE FROM meta WHERE key IN ('search_seq', 'search_index_version')").run();
19994
20711
  if (ftsAvailable(db))
19995
20712
  db.query("DELETE FROM search_fts").run();
19996
20713
  const setMeta2 = db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value");
@@ -20004,8 +20721,9 @@ async function restoreSnapshot(db, pkg, opts = {}) {
20004
20721
  });
20005
20722
  tx();
20006
20723
  rebuildDeclaredIndexes(db);
20724
+ const repaired = repairHub(db).applied;
20007
20725
  const blobs = await writeBlobs(pkg.blobs);
20008
- return { mode: "reset", applied, blobs, safetyPath };
20726
+ return { mode: "reset", applied, blobs, repaired, safetyPath };
20009
20727
  }
20010
20728
 
20011
20729
  // src/cli/commands/snapshot.ts
@@ -20054,93 +20772,1980 @@ var restore_default = defineCommand({
20054
20772
  reset: !!args.reset,
20055
20773
  force: !!args.force
20056
20774
  });
20057
- print(result, () => result.mode === "reset" ? `Restored (reset) from ${args.file}: ${result.applied} changes, ${result.blobs} blobs. Safety snapshot: ${result.safetyPath}` : `Merged ${args.file}: ${result.applied} new changes, ${result.blobs} blobs`);
20775
+ print(result, () => result.mode === "reset" ? `Restored (reset) from ${args.file}: ${result.applied} changes, ${result.blobs} blobs, ${result.repaired} repairs. Safety snapshot: ${result.safetyPath}` : `Merged ${args.file}: ${result.applied} new changes, ${result.blobs} blobs, ${result.repaired} repairs`);
20058
20776
  })
20059
20777
  });
20060
20778
 
20061
- // src/core/sync/server.ts
20062
- init_node();
20063
-
20064
- // src/core/sync/routes.ts
20065
- init_crdt();
20779
+ // src/cli/commands/doctor.ts
20780
+ var doctor_default = defineCommand({
20781
+ meta: {
20782
+ name: "doctor",
20783
+ description: "Scan the hub for logical-integrity issues (read-only; run `mh repair` to fix)"
20784
+ },
20785
+ run: () => {
20786
+ const report = validateHub(openMetahub());
20787
+ print(report, () => {
20788
+ if (report.ok)
20789
+ return "No integrity issues found.";
20790
+ const head = `${report.total} issue(s): ${Object.entries(report.counts).map(([k2, v2]) => `${k2}=${v2}`).join(", ")}`;
20791
+ const rows = report.issues.map((i2) => ({
20792
+ category: i2.category,
20793
+ entity: i2.entity,
20794
+ id: i2.id,
20795
+ fix: i2.fixable ? "auto" : "report",
20796
+ detail: i2.detail
20797
+ }));
20798
+ return `${head}
20066
20799
 
20067
- // src/core/sync/webui-routes.ts
20068
- var DatabaseSchema = exports_external.object({
20069
- id: exports_external.string(),
20070
- name: exports_external.string(),
20071
- icon: exports_external.string().nullable(),
20072
- created_hlc: exports_external.string()
20073
- });
20074
- var PropertySchema = exports_external.object({
20075
- id: exports_external.string(),
20076
- database_id: exports_external.string(),
20077
- name: exports_external.string(),
20078
- type: exports_external.string(),
20079
- config: exports_external.any().nullable(),
20080
- position: exports_external.number()
20081
- });
20082
- var RecordSchema = exports_external.object({
20083
- id: exports_external.string(),
20084
- database_id: exports_external.string(),
20085
- values: exports_external.record(exports_external.string(), exports_external.any())
20086
- });
20087
- var MoveRecordReq = exports_external.object({
20088
- target: exports_external.string(),
20089
- where: exports_external.enum(["before", "after"])
20090
- });
20091
- var DocumentSummarySchema = exports_external.object({
20092
- id: exports_external.string(),
20093
- title: exports_external.string(),
20094
- database_id: exports_external.string().nullable(),
20095
- parent_id: exports_external.string().nullable(),
20096
- created_hlc: exports_external.string()
20097
- });
20098
- var DocumentSchema = DocumentSummarySchema.extend({ body: exports_external.string().nullable() });
20099
- var SearchHitSchema = exports_external.object({
20100
- type: exports_external.string(),
20101
- id: exports_external.string(),
20102
- database_id: exports_external.string().nullable(),
20103
- title: exports_external.string().optional(),
20104
- snippet: exports_external.string()
20105
- });
20106
- var OkSchema = exports_external.object({ ok: exports_external.boolean() });
20107
- var CreateDatabaseReq = exports_external.object({ name: exports_external.string(), icon: exports_external.string().optional() });
20108
- var UpdateDatabaseReq = exports_external.object({
20109
- name: exports_external.string().optional(),
20110
- icon: exports_external.string().nullable().optional()
20111
- });
20112
- var CreatePropertyReq = exports_external.object({
20113
- db: exports_external.string(),
20114
- name: exports_external.string(),
20115
- type: exports_external.string(),
20116
- config: exports_external.any().optional()
20117
- });
20118
- var UpdatePropertyReq = exports_external.object({
20119
- name: exports_external.string().optional(),
20120
- type: exports_external.string().optional(),
20121
- config: exports_external.any().optional(),
20122
- position: exports_external.number().optional()
20123
- });
20124
- var SetWidthReq = exports_external.object({ width: exports_external.number() });
20125
- var RecordValuesReq = exports_external.record(exports_external.string(), exports_external.any());
20126
- var CreateDocumentReq = exports_external.object({
20127
- title: exports_external.string(),
20128
- body: exports_external.string().optional(),
20129
- database_id: exports_external.string().optional()
20800
+ ${table(rows)}`;
20801
+ });
20802
+ }
20130
20803
  });
20131
- var UpdateDocumentReq = exports_external.object({
20132
- title: exports_external.string().optional(),
20133
- body: exports_external.string().optional(),
20134
- parent_id: exports_external.string().nullable().optional()
20804
+
20805
+ // src/cli/commands/repair.ts
20806
+ var repair_default = defineCommand({
20807
+ meta: {
20808
+ name: "repair",
20809
+ description: "Deterministically fix logical-integrity issues (idempotent; replicates over sync)"
20810
+ },
20811
+ args: {
20812
+ "dry-run": {
20813
+ type: "boolean",
20814
+ description: "Report what would be fixed without changing anything (same as `mh doctor`)"
20815
+ }
20816
+ },
20817
+ run: ({ args }) => {
20818
+ const db = openMetahub();
20819
+ if (args["dry-run"]) {
20820
+ const report = validateHub(db);
20821
+ const fixable = report.issues.filter((i2) => i2.fixable);
20822
+ print({ dryRun: true, ...report }, () => report.ok ? "No integrity issues found." : `Would fix ${fixable.length} of ${report.total} issue(s); ${report.total - fixable.length} report-only.
20823
+
20824
+ ` + table(report.issues.map((i2) => ({
20825
+ category: i2.category,
20826
+ id: i2.id,
20827
+ fix: i2.fixable ? "auto" : "report",
20828
+ detail: i2.detail
20829
+ }))));
20830
+ return;
20831
+ }
20832
+ const result = repairHub(db);
20833
+ print(result, () => {
20834
+ const fixed = Object.entries(result.fixed).map(([k2, v2]) => `${k2}=${v2}`).join(", ");
20835
+ const head = result.applied === 0 ? "No fixable issues." : `Applied ${result.applied} repair(s): ${fixed}`;
20836
+ const left = result.remaining.issues;
20837
+ if (left.length === 0)
20838
+ return head;
20839
+ return `${head}
20840
+ ${left.length} report-only issue(s) need manual attention:
20841
+
20842
+ ` + table(left.map((i2) => ({ category: i2.category, id: i2.id, detail: i2.detail })));
20843
+ });
20844
+ }
20135
20845
  });
20136
- function need(req, key) {
20137
- const v2 = new URL(req.url).searchParams.get(key);
20138
- if (!v2)
20139
- throw new Error(`missing query param: ${key}`);
20140
- return v2;
20846
+
20847
+ // src/cli/commands/config.ts
20848
+ init_node();
20849
+
20850
+ // src/core/config.ts
20851
+ var K_HOST = "cfg_host";
20852
+ var K_PORT = "cfg_port";
20853
+ var K_SYNC_INTERVAL = "cfg_sync_interval";
20854
+ var K_AUTO_SYNC = "cfg_auto_sync";
20855
+ var DEFAULT_CONFIG = {
20856
+ host: "127.0.0.1",
20857
+ port: 7777,
20858
+ syncIntervalMs: parseDuration(process.env.METAHUB_SYNC_INTERVAL, 30000),
20859
+ autoSync: true
20860
+ };
20861
+ function getMeta2(db, key) {
20862
+ const row = db.query("SELECT value FROM meta WHERE key = ?").get(key);
20863
+ return row ? row.value : null;
20141
20864
  }
20142
- function opt(req, key) {
20143
- return new URL(req.url).searchParams.get(key) ?? undefined;
20865
+ function setMeta2(db, key, value) {
20866
+ db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
20867
+ }
20868
+ function getServerConfig(db) {
20869
+ const host = getMeta2(db, K_HOST);
20870
+ const port = getMeta2(db, K_PORT);
20871
+ const interval = getMeta2(db, K_SYNC_INTERVAL);
20872
+ const auto = getMeta2(db, K_AUTO_SYNC);
20873
+ return {
20874
+ host: host ?? DEFAULT_CONFIG.host,
20875
+ port: port != null ? Number(port) : DEFAULT_CONFIG.port,
20876
+ syncIntervalMs: interval != null ? Number(interval) : DEFAULT_CONFIG.syncIntervalMs,
20877
+ autoSync: auto != null ? auto === "1" : DEFAULT_CONFIG.autoSync
20878
+ };
20879
+ }
20880
+ function setServerConfig(db, partial2) {
20881
+ if (partial2.host != null)
20882
+ setMeta2(db, K_HOST, partial2.host);
20883
+ if (partial2.port != null)
20884
+ setMeta2(db, K_PORT, String(partial2.port));
20885
+ if (partial2.syncIntervalMs != null)
20886
+ setMeta2(db, K_SYNC_INTERVAL, String(partial2.syncIntervalMs));
20887
+ if (partial2.autoSync != null)
20888
+ setMeta2(db, K_AUTO_SYNC, partial2.autoSync ? "1" : "0");
20889
+ return getServerConfig(db);
20890
+ }
20891
+
20892
+ // src/core/sync/pairing.ts
20893
+ init_ids();
20894
+ var DEFAULT_PAIR_TTL_MS = parseDuration(process.env.METAHUB_PAIR_TTL, 10 * 60000);
20895
+ function generatePairingCode(db, ttlMs = DEFAULT_PAIR_TTL_MS) {
20896
+ const now = Date.now();
20897
+ db.query("DELETE FROM pairing_codes WHERE used = 1 OR exp < ?").run(now);
20898
+ const code = randomSuffix(12);
20899
+ const exp = now + ttlMs;
20900
+ db.query("INSERT INTO pairing_codes (code, exp, used, created_at) VALUES (?, ?, 0, ?)").run(code, exp, now);
20901
+ return { code, exp };
20902
+ }
20903
+ function redeemPairingCode(db, code) {
20904
+ return db.query("UPDATE pairing_codes SET used = 1 WHERE code = ? AND used = 0 AND exp >= ?").run(code, Date.now()).changes > 0;
20905
+ }
20906
+ function mintGrant(db, peerUrl, nodeId) {
20907
+ const token = randomSuffix(32);
20908
+ db.query("INSERT INTO peer_grants (token, peer_url, node_id, created_at) VALUES (?, ?, ?, ?)").run(token, peerUrl, nodeId, Date.now());
20909
+ return token;
20910
+ }
20911
+ function isAcceptedGrant(db, token) {
20912
+ return db.query("SELECT 1 FROM peer_grants WHERE token = ?").get(token) != null;
20913
+ }
20914
+ function listGrants(db) {
20915
+ return db.query("SELECT token, peer_url, node_id, created_at FROM peer_grants ORDER BY created_at DESC").all();
20916
+ }
20917
+ function revokeGrant(db, tokenOrPrefix) {
20918
+ return db.query("DELETE FROM peer_grants WHERE token = ? OR token LIKE ? || '%'").run(tokenOrPrefix, tokenOrPrefix).changes;
20919
+ }
20920
+ function handlePairRequest(db, node, body) {
20921
+ if (!redeemPairingCode(db, body.code)) {
20922
+ throw new Error("invalid or expired pairing code");
20923
+ }
20924
+ if (body.self_url) {
20925
+ addPeer(db, { url: body.self_url, token: body.grant, node_id: body.node_id });
20926
+ }
20927
+ const grant = mintGrant(db, body.self_url ?? null, body.node_id);
20928
+ return { node_id: node, grant };
20929
+ }
20930
+ async function performPairing(db, node, remoteUrl, code, selfUrl) {
20931
+ const ourGrant = mintGrant(db, remoteUrl, null);
20932
+ const reqBody = { code, node_id: node, grant: ourGrant, self_url: selfUrl };
20933
+ const res = await fetch(new URL(PAIR_PATH, remoteUrl), {
20934
+ method: "POST",
20935
+ headers: { "content-type": "application/json" },
20936
+ body: JSON.stringify(reqBody)
20937
+ });
20938
+ if (!res.ok)
20939
+ throw new Error(`pairing failed: ${res.status} ${await res.text()}`);
20940
+ const data = await res.json();
20941
+ addPeer(db, { url: remoteUrl, token: data.grant, node_id: data.node_id });
20942
+ return { node_id: data.node_id, url: remoteUrl };
20943
+ }
20944
+
20945
+ // node_modules/@clack/core/dist/index.mjs
20946
+ import { styleText as v2 } from "util";
20947
+ import { stdout as x2, stdin as D2 } from "process";
20948
+ import * as b2 from "readline";
20949
+ import G3 from "readline";
20950
+
20951
+ // node_modules/fast-string-truncated-width/dist/utils.js
20952
+ var getCodePointsLength = (() => {
20953
+ const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
20954
+ return (input) => {
20955
+ let surrogatePairsNr = 0;
20956
+ SURROGATE_PAIR_RE.lastIndex = 0;
20957
+ while (SURROGATE_PAIR_RE.test(input)) {
20958
+ surrogatePairsNr += 1;
20959
+ }
20960
+ return input.length - surrogatePairsNr;
20961
+ };
20962
+ })();
20963
+ var isFullWidth2 = (x2) => {
20964
+ return x2 === 12288 || x2 >= 65281 && x2 <= 65376 || x2 >= 65504 && x2 <= 65510;
20965
+ };
20966
+ var isWideNotCJKTNotEmoji = (x2) => {
20967
+ return x2 === 8987 || x2 === 9001 || x2 >= 12272 && x2 <= 12287 || x2 >= 12289 && x2 <= 12350 || x2 >= 12441 && x2 <= 12543 || x2 >= 12549 && x2 <= 12591 || x2 >= 12593 && x2 <= 12686 || x2 >= 12688 && x2 <= 12771 || x2 >= 12783 && x2 <= 12830 || x2 >= 12832 && x2 <= 12871 || x2 >= 12880 && x2 <= 19903 || x2 >= 65040 && x2 <= 65049 || x2 >= 65072 && x2 <= 65106 || x2 >= 65108 && x2 <= 65126 || x2 >= 65128 && x2 <= 65131 || x2 >= 127488 && x2 <= 127490 || x2 >= 127504 && x2 <= 127547 || x2 >= 127552 && x2 <= 127560 || x2 >= 131072 && x2 <= 196605 || x2 >= 196608 && x2 <= 262141;
20968
+ };
20969
+
20970
+ // node_modules/fast-string-truncated-width/dist/index.js
20971
+ var ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
20972
+ var CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
20973
+ var CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu;
20974
+ var TAB_RE = /\t{1,1000}/y;
20975
+ var EMOJI_RE = /[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/yu;
20976
+ var LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
20977
+ var MODIFIER_RE = /\p{M}+/gu;
20978
+ var NO_TRUNCATION = { limit: Infinity, ellipsis: "" };
20979
+ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {}) => {
20980
+ const LIMIT = truncationOptions.limit ?? Infinity;
20981
+ const ELLIPSIS = truncationOptions.ellipsis ?? "";
20982
+ const ELLIPSIS_WIDTH = truncationOptions?.ellipsisWidth ?? (ELLIPSIS ? getStringTruncatedWidth(ELLIPSIS, NO_TRUNCATION, widthOptions).width : 0);
20983
+ const ANSI_WIDTH = 0;
20984
+ const CONTROL_WIDTH = widthOptions.controlWidth ?? 0;
20985
+ const TAB_WIDTH = widthOptions.tabWidth ?? 8;
20986
+ const EMOJI_WIDTH = widthOptions.emojiWidth ?? 2;
20987
+ const FULL_WIDTH_WIDTH = 2;
20988
+ const REGULAR_WIDTH = widthOptions.regularWidth ?? 1;
20989
+ const WIDE_WIDTH = widthOptions.wideWidth ?? FULL_WIDTH_WIDTH;
20990
+ const PARSE_BLOCKS = [
20991
+ [LATIN_RE, REGULAR_WIDTH],
20992
+ [ANSI_RE, ANSI_WIDTH],
20993
+ [CONTROL_RE, CONTROL_WIDTH],
20994
+ [TAB_RE, TAB_WIDTH],
20995
+ [EMOJI_RE, EMOJI_WIDTH],
20996
+ [CJKT_WIDE_RE, WIDE_WIDTH]
20997
+ ];
20998
+ let indexPrev = 0;
20999
+ let index = 0;
21000
+ let length = input.length;
21001
+ let lengthExtra = 0;
21002
+ let truncationEnabled = false;
21003
+ let truncationIndex = length;
21004
+ let truncationLimit = Math.max(0, LIMIT - ELLIPSIS_WIDTH);
21005
+ let unmatchedStart = 0;
21006
+ let unmatchedEnd = 0;
21007
+ let width = 0;
21008
+ let widthExtra = 0;
21009
+ outer:
21010
+ while (true) {
21011
+ if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
21012
+ const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
21013
+ lengthExtra = 0;
21014
+ for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
21015
+ const codePoint = char.codePointAt(0) || 0;
21016
+ if (isFullWidth2(codePoint)) {
21017
+ widthExtra = FULL_WIDTH_WIDTH;
21018
+ } else if (isWideNotCJKTNotEmoji(codePoint)) {
21019
+ widthExtra = WIDE_WIDTH;
21020
+ } else {
21021
+ widthExtra = REGULAR_WIDTH;
21022
+ }
21023
+ if (width + widthExtra > truncationLimit) {
21024
+ truncationIndex = Math.min(truncationIndex, Math.max(unmatchedStart, indexPrev) + lengthExtra);
21025
+ }
21026
+ if (width + widthExtra > LIMIT) {
21027
+ truncationEnabled = true;
21028
+ break outer;
21029
+ }
21030
+ lengthExtra += char.length;
21031
+ width += widthExtra;
21032
+ }
21033
+ unmatchedStart = unmatchedEnd = 0;
21034
+ }
21035
+ if (index >= length) {
21036
+ break outer;
21037
+ }
21038
+ for (let i2 = 0, l2 = PARSE_BLOCKS.length;i2 < l2; i2++) {
21039
+ const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i2];
21040
+ BLOCK_RE.lastIndex = index;
21041
+ if (BLOCK_RE.test(input)) {
21042
+ lengthExtra = BLOCK_RE === CJKT_WIDE_RE ? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex)) : BLOCK_RE === EMOJI_RE ? 1 : BLOCK_RE.lastIndex - index;
21043
+ widthExtra = lengthExtra * BLOCK_WIDTH;
21044
+ if (width + widthExtra > truncationLimit) {
21045
+ truncationIndex = Math.min(truncationIndex, index + Math.floor((truncationLimit - width) / BLOCK_WIDTH));
21046
+ }
21047
+ if (width + widthExtra > LIMIT) {
21048
+ truncationEnabled = true;
21049
+ break outer;
21050
+ }
21051
+ width += widthExtra;
21052
+ unmatchedStart = indexPrev;
21053
+ unmatchedEnd = index;
21054
+ index = indexPrev = BLOCK_RE.lastIndex;
21055
+ continue outer;
21056
+ }
21057
+ }
21058
+ index += 1;
21059
+ }
21060
+ return {
21061
+ width: truncationEnabled ? truncationLimit : width,
21062
+ index: truncationEnabled ? truncationIndex : length,
21063
+ truncated: truncationEnabled,
21064
+ ellipsed: truncationEnabled && LIMIT >= ELLIPSIS_WIDTH
21065
+ };
21066
+ };
21067
+ var dist_default = getStringTruncatedWidth;
21068
+
21069
+ // node_modules/fast-string-width/dist/index.js
21070
+ var NO_TRUNCATION2 = {
21071
+ limit: Infinity,
21072
+ ellipsis: "",
21073
+ ellipsisWidth: 0
21074
+ };
21075
+ var fastStringWidth = (input, options = {}) => {
21076
+ return dist_default(input, NO_TRUNCATION2, options).width;
21077
+ };
21078
+ var dist_default2 = fastStringWidth;
21079
+
21080
+ // node_modules/fast-wrap-ansi/lib/main.js
21081
+ var ESC = "\x1B";
21082
+ var CSI = "\x9B";
21083
+ var END_CODE = 39;
21084
+ var ANSI_ESCAPE_BELL = "\x07";
21085
+ var ANSI_CSI = "[";
21086
+ var ANSI_OSC = "]";
21087
+ var ANSI_SGR_TERMINATOR = "m";
21088
+ var ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
21089
+ var GROUP_REGEX = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`, "y");
21090
+ var getClosingCode = (openingCode) => {
21091
+ if (openingCode >= 30 && openingCode <= 37)
21092
+ return 39;
21093
+ if (openingCode >= 90 && openingCode <= 97)
21094
+ return 39;
21095
+ if (openingCode >= 40 && openingCode <= 47)
21096
+ return 49;
21097
+ if (openingCode >= 100 && openingCode <= 107)
21098
+ return 49;
21099
+ if (openingCode === 1 || openingCode === 2)
21100
+ return 22;
21101
+ if (openingCode === 3)
21102
+ return 23;
21103
+ if (openingCode === 4)
21104
+ return 24;
21105
+ if (openingCode === 7)
21106
+ return 27;
21107
+ if (openingCode === 8)
21108
+ return 28;
21109
+ if (openingCode === 9)
21110
+ return 29;
21111
+ if (openingCode === 0)
21112
+ return 0;
21113
+ return;
21114
+ };
21115
+ var wrapAnsiCode = (code) => `${ESC}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
21116
+ var wrapAnsiHyperlink = (url2) => `${ESC}${ANSI_ESCAPE_LINK}${url2}${ANSI_ESCAPE_BELL}`;
21117
+ var wrapWord = (rows, word, columns) => {
21118
+ const characters = word[Symbol.iterator]();
21119
+ let isInsideEscape = false;
21120
+ let isInsideLinkEscape = false;
21121
+ let lastRow = rows.at(-1);
21122
+ let visible = lastRow === undefined ? 0 : dist_default2(lastRow);
21123
+ let currentCharacter = characters.next();
21124
+ let nextCharacter = characters.next();
21125
+ let rawCharacterIndex = 0;
21126
+ while (!currentCharacter.done) {
21127
+ const character = currentCharacter.value;
21128
+ const characterLength = dist_default2(character);
21129
+ if (visible + characterLength <= columns) {
21130
+ rows[rows.length - 1] += character;
21131
+ } else {
21132
+ rows.push(character);
21133
+ visible = 0;
21134
+ }
21135
+ if (character === ESC || character === CSI) {
21136
+ isInsideEscape = true;
21137
+ isInsideLinkEscape = word.startsWith(ANSI_ESCAPE_LINK, rawCharacterIndex + 1);
21138
+ }
21139
+ if (isInsideEscape) {
21140
+ if (isInsideLinkEscape) {
21141
+ if (character === ANSI_ESCAPE_BELL) {
21142
+ isInsideEscape = false;
21143
+ isInsideLinkEscape = false;
21144
+ }
21145
+ } else if (character === ANSI_SGR_TERMINATOR) {
21146
+ isInsideEscape = false;
21147
+ }
21148
+ } else {
21149
+ visible += characterLength;
21150
+ if (visible === columns && !nextCharacter.done) {
21151
+ rows.push("");
21152
+ visible = 0;
21153
+ }
21154
+ }
21155
+ currentCharacter = nextCharacter;
21156
+ nextCharacter = characters.next();
21157
+ rawCharacterIndex += character.length;
21158
+ }
21159
+ lastRow = rows.at(-1);
21160
+ if (!visible && lastRow !== undefined && lastRow.length && rows.length > 1) {
21161
+ rows[rows.length - 2] += rows.pop();
21162
+ }
21163
+ };
21164
+ var stringVisibleTrimSpacesRight = (string4) => {
21165
+ const words = string4.split(" ");
21166
+ let last = words.length;
21167
+ while (last) {
21168
+ if (dist_default2(words[last - 1])) {
21169
+ break;
21170
+ }
21171
+ last--;
21172
+ }
21173
+ if (last === words.length) {
21174
+ return string4;
21175
+ }
21176
+ return words.slice(0, last).join(" ") + words.slice(last).join("");
21177
+ };
21178
+ var exec = (string4, columns, options = {}) => {
21179
+ if (options.trim !== false && string4.trim() === "") {
21180
+ return "";
21181
+ }
21182
+ let returnValue = "";
21183
+ let escapeCode;
21184
+ let escapeUrl;
21185
+ const words = string4.split(" ");
21186
+ let rows = [""];
21187
+ let rowLength = 0;
21188
+ for (let index = 0;index < words.length; index++) {
21189
+ const word = words[index];
21190
+ if (options.trim !== false) {
21191
+ const row = rows.at(-1) ?? "";
21192
+ const trimmed = row.trimStart();
21193
+ if (row.length !== trimmed.length) {
21194
+ rows[rows.length - 1] = trimmed;
21195
+ rowLength = dist_default2(trimmed);
21196
+ }
21197
+ }
21198
+ if (index !== 0) {
21199
+ if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
21200
+ rows.push("");
21201
+ rowLength = 0;
21202
+ }
21203
+ if (rowLength || options.trim === false) {
21204
+ rows[rows.length - 1] += " ";
21205
+ rowLength++;
21206
+ }
21207
+ }
21208
+ const wordLength = dist_default2(word);
21209
+ if (options.hard && wordLength > columns) {
21210
+ const remainingColumns = columns - rowLength;
21211
+ const breaksStartingThisLine = 1 + Math.floor((wordLength - remainingColumns - 1) / columns);
21212
+ const breaksStartingNextLine = Math.floor((wordLength - 1) / columns);
21213
+ if (breaksStartingNextLine < breaksStartingThisLine) {
21214
+ rows.push("");
21215
+ }
21216
+ wrapWord(rows, word, columns);
21217
+ rowLength = dist_default2(rows.at(-1) ?? "");
21218
+ continue;
21219
+ }
21220
+ if (rowLength + wordLength > columns && rowLength && wordLength) {
21221
+ if (options.wordWrap === false && rowLength < columns) {
21222
+ wrapWord(rows, word, columns);
21223
+ rowLength = dist_default2(rows.at(-1) ?? "");
21224
+ continue;
21225
+ }
21226
+ rows.push("");
21227
+ rowLength = 0;
21228
+ }
21229
+ if (rowLength + wordLength > columns && options.wordWrap === false) {
21230
+ wrapWord(rows, word, columns);
21231
+ rowLength = dist_default2(rows.at(-1) ?? "");
21232
+ continue;
21233
+ }
21234
+ rows[rows.length - 1] += word;
21235
+ rowLength += wordLength;
21236
+ }
21237
+ if (options.trim !== false) {
21238
+ rows = rows.map((row) => stringVisibleTrimSpacesRight(row));
21239
+ }
21240
+ const preString = rows.join(`
21241
+ `);
21242
+ let inSurrogate = false;
21243
+ for (let i2 = 0;i2 < preString.length; i2++) {
21244
+ const character = preString[i2];
21245
+ returnValue += character;
21246
+ if (!inSurrogate) {
21247
+ inSurrogate = character >= "\uD800" && character <= "\uDBFF";
21248
+ if (inSurrogate) {
21249
+ continue;
21250
+ }
21251
+ } else {
21252
+ inSurrogate = false;
21253
+ }
21254
+ if (character === ESC || character === CSI) {
21255
+ GROUP_REGEX.lastIndex = i2 + 1;
21256
+ const groupsResult = GROUP_REGEX.exec(preString);
21257
+ const groups = groupsResult?.groups;
21258
+ if (groups?.code !== undefined) {
21259
+ const code = Number.parseFloat(groups.code);
21260
+ escapeCode = code === END_CODE ? undefined : code;
21261
+ } else if (groups?.uri !== undefined) {
21262
+ escapeUrl = groups.uri.length === 0 ? undefined : groups.uri;
21263
+ }
21264
+ }
21265
+ if (preString[i2 + 1] === `
21266
+ `) {
21267
+ if (escapeUrl) {
21268
+ returnValue += wrapAnsiHyperlink("");
21269
+ }
21270
+ const closingCode = escapeCode ? getClosingCode(escapeCode) : undefined;
21271
+ if (escapeCode && closingCode) {
21272
+ returnValue += wrapAnsiCode(closingCode);
21273
+ }
21274
+ } else if (character === `
21275
+ `) {
21276
+ if (escapeCode && getClosingCode(escapeCode)) {
21277
+ returnValue += wrapAnsiCode(escapeCode);
21278
+ }
21279
+ if (escapeUrl) {
21280
+ returnValue += wrapAnsiHyperlink(escapeUrl);
21281
+ }
21282
+ }
21283
+ }
21284
+ return returnValue;
21285
+ };
21286
+ var CRLF_OR_LF = /\r?\n/;
21287
+ function wrapAnsi(string4, columns, options) {
21288
+ return String(string4).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns, options)).join(`
21289
+ `);
21290
+ }
21291
+
21292
+ // node_modules/@clack/core/dist/index.mjs
21293
+ var import_sisteransi = __toESM(require_src(), 1);
21294
+ import { ReadStream as O3 } from "tty";
21295
+ function f3(r3, t2, s2) {
21296
+ if (!s2.some((o3) => !o3.disabled))
21297
+ return r3;
21298
+ const e2 = r3 + t2, i2 = Math.max(s2.length - 1, 0), n2 = e2 < 0 ? i2 : e2 > i2 ? 0 : e2;
21299
+ return s2[n2].disabled ? f3(n2, t2 < 0 ? -1 : 1, s2) : n2;
21300
+ }
21301
+ function I3(r3, t2, s2, e2) {
21302
+ const i2 = e2.split(`
21303
+ `);
21304
+ let n2 = 0, o3 = r3;
21305
+ for (const a2 of i2) {
21306
+ if (o3 <= a2.length)
21307
+ break;
21308
+ o3 -= a2.length + 1, n2++;
21309
+ }
21310
+ for (n2 = Math.max(0, Math.min(i2.length - 1, n2 + s2)), o3 = Math.min(o3, i2[n2].length) + t2;o3 < 0 && n2 > 0; )
21311
+ n2--, o3 += i2[n2].length + 1;
21312
+ for (;o3 > i2[n2].length && n2 < i2.length - 1; )
21313
+ o3 -= i2[n2].length + 1, n2++;
21314
+ o3 = Math.max(0, Math.min(i2[n2].length, o3));
21315
+ let u3 = 0;
21316
+ for (let a2 = 0;a2 < n2; a2++)
21317
+ u3 += i2[a2].length + 1;
21318
+ return u3 + o3;
21319
+ }
21320
+ var K = ["up", "down", "left", "right", "space", "enter", "cancel"];
21321
+ var j = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
21322
+ var h2 = { actions: new Set(K), aliases: new Map([["k", "up"], ["j", "down"], ["h", "left"], ["l", "right"], ["\x03", "cancel"], ["escape", "cancel"]]), messages: { cancel: "Canceled", error: "Something went wrong" }, withGuide: true, date: { monthNames: [...j], messages: { required: "Please enter a valid date", invalidMonth: "There are only 12 months in a year", invalidDay: (r3, t2) => `There are only ${r3} days in ${t2}`, afterMin: (r3) => `Date must be on or after ${r3.toISOString().slice(0, 10)}`, beforeMax: (r3) => `Date must be on or before ${r3.toISOString().slice(0, 10)}` } } };
21323
+ function C3(r3, t2) {
21324
+ if (typeof r3 == "string")
21325
+ return h2.aliases.get(r3) === t2;
21326
+ for (const s2 of r3)
21327
+ if (s2 !== undefined && C3(s2, t2))
21328
+ return true;
21329
+ return false;
21330
+ }
21331
+ function Y2(r3, t2) {
21332
+ if (r3 === t2)
21333
+ return;
21334
+ const s2 = r3.split(`
21335
+ `), e2 = t2.split(`
21336
+ `), i2 = Math.max(s2.length, e2.length), n2 = [];
21337
+ for (let o3 = 0;o3 < i2; o3++)
21338
+ s2[o3] !== e2[o3] && n2.push(o3);
21339
+ return { lines: n2, numLinesBefore: s2.length, numLinesAfter: e2.length, numLines: i2 };
21340
+ }
21341
+ var q2 = globalThis.process.platform.startsWith("win");
21342
+ var k2 = Symbol("clack:cancel");
21343
+ function R3(r3) {
21344
+ return r3 === k2;
21345
+ }
21346
+ function w2(r3, t2) {
21347
+ const s2 = r3;
21348
+ s2.isTTY && s2.setRawMode(t2);
21349
+ }
21350
+ function W2({ input: r3 = D2, output: t2 = x2, overwrite: s2 = true, hideCursor: e2 = true } = {}) {
21351
+ const i2 = b2.createInterface({ input: r3, output: t2, prompt: "", tabSize: 1 });
21352
+ b2.emitKeypressEvents(r3, i2), r3 instanceof O3 && r3.isTTY && r3.setRawMode(true);
21353
+ const n2 = (o3, { name: u3, sequence: a2 }) => {
21354
+ const l2 = String(o3);
21355
+ if (C3([l2, u3, a2], "cancel")) {
21356
+ e2 && t2.write(import_sisteransi.cursor.show), process.exit(0);
21357
+ return;
21358
+ }
21359
+ if (!s2)
21360
+ return;
21361
+ const c3 = u3 === "return" ? 0 : -1, y3 = u3 === "return" ? -1 : 0;
21362
+ b2.moveCursor(t2, c3, y3, () => {
21363
+ b2.clearLine(t2, 1, () => {
21364
+ r3.once("keypress", n2);
21365
+ });
21366
+ });
21367
+ };
21368
+ return e2 && t2.write(import_sisteransi.cursor.hide), r3.once("keypress", n2), () => {
21369
+ r3.off("keypress", n2), e2 && t2.write(import_sisteransi.cursor.show), r3 instanceof O3 && r3.isTTY && !q2 && r3.setRawMode(false), i2.terminal = false, i2.close();
21370
+ };
21371
+ }
21372
+ var A3 = (r3) => ("columns" in r3) && typeof r3.columns == "number" ? r3.columns : 80;
21373
+ var L3 = (r3) => ("rows" in r3) && typeof r3.rows == "number" ? r3.rows : 20;
21374
+ function B2(r3, t2, s2, e2 = s2, i2 = s2, n2) {
21375
+ const o3 = A3(r3 ?? x2);
21376
+ return wrapAnsi(t2, o3 - s2.length, { hard: true, trim: false }).split(`
21377
+ `).map((u3, a2, l2) => {
21378
+ const c3 = n2 ? n2(u3, a2) : u3;
21379
+ return a2 === 0 ? `${e2}${c3}` : a2 === l2.length - 1 ? `${i2}${c3}` : `${s2}${c3}`;
21380
+ }).join(`
21381
+ `);
21382
+ }
21383
+ function P3(r3, t2) {
21384
+ if ("~standard" in r3) {
21385
+ const s2 = r3["~standard"].validate(t2);
21386
+ if (s2 instanceof Promise)
21387
+ throw new TypeError("Schema validation must be synchronous. Update `validate()` and remove any asynchronous logic.");
21388
+ return s2.issues?.at(0)?.message;
21389
+ }
21390
+ return r3(t2);
21391
+ }
21392
+
21393
+ class m2 {
21394
+ input;
21395
+ output;
21396
+ _abortSignal;
21397
+ rl;
21398
+ opts;
21399
+ _render;
21400
+ _track = false;
21401
+ _prevFrame = "";
21402
+ _subscribers = new Map;
21403
+ _cursor = 0;
21404
+ state = "initial";
21405
+ error = "";
21406
+ value;
21407
+ userInput = "";
21408
+ constructor(t2, s2 = true) {
21409
+ const { input: e2 = D2, output: i2 = x2, render: n2, signal: o3, ...u3 } = t2;
21410
+ this.opts = u3, this.onKeypress = this.onKeypress.bind(this), this.close = this.close.bind(this), this.render = this.render.bind(this), this._render = n2.bind(this), this._track = s2, this._abortSignal = o3, this.input = e2, this.output = i2;
21411
+ }
21412
+ unsubscribe() {
21413
+ this._subscribers.clear();
21414
+ }
21415
+ setSubscriber(t2, s2) {
21416
+ const e2 = this._subscribers.get(t2) ?? [];
21417
+ e2.push(s2), this._subscribers.set(t2, e2);
21418
+ }
21419
+ on(t2, s2) {
21420
+ this.setSubscriber(t2, { cb: s2 });
21421
+ }
21422
+ once(t2, s2) {
21423
+ this.setSubscriber(t2, { cb: s2, once: true });
21424
+ }
21425
+ emit(t2, ...s2) {
21426
+ const e2 = this._subscribers.get(t2) ?? [], i2 = [];
21427
+ for (const n2 of e2)
21428
+ n2.cb(...s2), n2.once && i2.push(() => e2.splice(e2.indexOf(n2), 1));
21429
+ for (const n2 of i2)
21430
+ n2();
21431
+ }
21432
+ prompt() {
21433
+ return new Promise((t2) => {
21434
+ if (this._abortSignal) {
21435
+ if (this._abortSignal.aborted)
21436
+ return this.state = "cancel", this.close(), t2(k2);
21437
+ this._abortSignal.addEventListener("abort", () => {
21438
+ this.state = "cancel", this.close();
21439
+ }, { once: true });
21440
+ }
21441
+ this.rl = G3.createInterface({ input: this.input, tabSize: 2, prompt: "", escapeCodeTimeout: 50, terminal: true }), this.rl.prompt(), this.opts.initialUserInput !== undefined && this._setUserInput(this.opts.initialUserInput, true), this.input.on("keypress", this.onKeypress), w2(this.input, true), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
21442
+ this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), w2(this.input, false), t2(this.value);
21443
+ }), this.once("cancel", () => {
21444
+ this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), w2(this.input, false), t2(k2);
21445
+ });
21446
+ });
21447
+ }
21448
+ _isActionKey(t2, s2) {
21449
+ return t2 === "\t";
21450
+ }
21451
+ _shouldSubmit(t2, s2) {
21452
+ return true;
21453
+ }
21454
+ _setValue(t2) {
21455
+ this.value = t2, this.emit("value", this.value);
21456
+ }
21457
+ _setUserInput(t2, s2) {
21458
+ this.userInput = t2 ?? "", this.emit("userInput", this.userInput), s2 && this._track && this.rl && (this.rl.write(this.userInput), this._cursor = this.rl.cursor);
21459
+ }
21460
+ _clearUserInput() {
21461
+ this.rl?.write(null, { ctrl: true, name: "u" }), this._setUserInput("");
21462
+ }
21463
+ onKeypress(t2, s2) {
21464
+ if (this._track && s2.name !== "return" && (s2.name && this._isActionKey(t2, s2) && this.rl?.write(null, { ctrl: true, name: "h" }), this._cursor = this.rl?.cursor ?? 0, this._setUserInput(this.rl?.line)), this.state === "error" && (this.state = "active"), s2?.name && (!this._track && h2.aliases.has(s2.name) && this.emit("cursor", h2.aliases.get(s2.name)), h2.actions.has(s2.name) && this.emit("cursor", s2.name)), t2 && (t2.toLowerCase() === "y" || t2.toLowerCase() === "n") && this.emit("confirm", t2.toLowerCase() === "y"), this.emit("key", t2, s2), s2?.name === "return" && this._shouldSubmit(t2, s2)) {
21465
+ if (this.opts.validate) {
21466
+ const e2 = P3(this.opts.validate, this.value);
21467
+ e2 && (this.error = e2 instanceof Error ? e2.message : e2, this.state = "error", this.rl?.write(this.userInput));
21468
+ }
21469
+ this.state !== "error" && (this.state = "submit");
21470
+ }
21471
+ C3([t2, s2?.name, s2?.sequence], "cancel") && (this.state = "cancel"), (this.state === "submit" || this.state === "cancel") && this.emit("finalize"), this.render(), (this.state === "submit" || this.state === "cancel") && this.close();
21472
+ }
21473
+ close() {
21474
+ this.input.unpipe(), this.input.removeListener("keypress", this.onKeypress), this.output.write(`
21475
+ `), w2(this.input, false), this.rl?.close(), this.rl = undefined, this.emit(`${this.state}`, this.value), this.unsubscribe();
21476
+ }
21477
+ restoreCursor() {
21478
+ const t2 = wrapAnsi(this._prevFrame, process.stdout.columns, { hard: true, trim: false }).split(`
21479
+ `).length - 1;
21480
+ this.output.write(import_sisteransi.cursor.move(-999, t2 * -1));
21481
+ }
21482
+ render() {
21483
+ const t2 = wrapAnsi(this._render(this) ?? "", process.stdout.columns, { hard: true, trim: false });
21484
+ if (t2 !== this._prevFrame) {
21485
+ if (this.state === "initial")
21486
+ this.output.write(import_sisteransi.cursor.hide);
21487
+ else {
21488
+ const s2 = Y2(this._prevFrame, t2), e2 = L3(this.output);
21489
+ if (this.restoreCursor(), s2) {
21490
+ const i2 = Math.max(0, s2.numLinesAfter - e2), n2 = Math.max(0, s2.numLinesBefore - e2);
21491
+ let o3 = s2.lines.find((u3) => u3 >= i2);
21492
+ if (o3 === undefined) {
21493
+ this._prevFrame = t2;
21494
+ return;
21495
+ }
21496
+ if (s2.lines.length === 1) {
21497
+ this.output.write(import_sisteransi.cursor.move(0, o3 - n2)), this.output.write(import_sisteransi.erase.lines(1));
21498
+ const u3 = t2.split(`
21499
+ `);
21500
+ this.output.write(u3[o3]), this._prevFrame = t2, this.output.write(import_sisteransi.cursor.move(0, u3.length - o3 - 1));
21501
+ return;
21502
+ } else if (s2.lines.length > 1) {
21503
+ if (i2 < n2)
21504
+ o3 = i2;
21505
+ else {
21506
+ const a2 = o3 - n2;
21507
+ a2 > 0 && this.output.write(import_sisteransi.cursor.move(0, a2));
21508
+ }
21509
+ this.output.write(import_sisteransi.erase.down());
21510
+ const u3 = t2.split(`
21511
+ `).slice(o3);
21512
+ this.output.write(u3.join(`
21513
+ `)), this._prevFrame = t2;
21514
+ return;
21515
+ }
21516
+ }
21517
+ this.output.write(import_sisteransi.erase.down());
21518
+ }
21519
+ this.output.write(t2), this.state === "initial" && (this.state = "active"), this._prevFrame = t2;
21520
+ }
21521
+ }
21522
+ }
21523
+ function J2(r3, t2) {
21524
+ if (r3 === undefined || t2.length === 0)
21525
+ return 0;
21526
+ const s2 = t2.findIndex((e2) => e2.value === r3);
21527
+ return s2 !== -1 ? s2 : 0;
21528
+ }
21529
+ function H(r3, t2) {
21530
+ return (t2.label ?? String(t2.value)).toLowerCase().includes(r3.toLowerCase());
21531
+ }
21532
+ function Q2(r3, t2) {
21533
+ if (t2)
21534
+ return r3 ? t2 : t2[0];
21535
+ }
21536
+ var X2 = class extends m2 {
21537
+ filteredOptions;
21538
+ multiple;
21539
+ isNavigating = false;
21540
+ selectedValues = [];
21541
+ focusedValue;
21542
+ #s = 0;
21543
+ #r = "";
21544
+ #t;
21545
+ #n;
21546
+ #u;
21547
+ get cursor() {
21548
+ return this.#s;
21549
+ }
21550
+ get userInputWithCursor() {
21551
+ if (!this.userInput)
21552
+ return v2(["inverse", "hidden"], "_");
21553
+ if (this._cursor >= this.userInput.length)
21554
+ return `${this.userInput}\u2588`;
21555
+ const t2 = this.userInput.slice(0, this._cursor), [s2, ...e2] = this.userInput.slice(this._cursor);
21556
+ return `${t2}${v2("inverse", s2)}${e2.join("")}`;
21557
+ }
21558
+ get options() {
21559
+ return typeof this.#n == "function" ? this.#n() : this.#n;
21560
+ }
21561
+ constructor(t2) {
21562
+ super(t2), this.#n = t2.options, this.#u = t2.placeholder;
21563
+ const s2 = this.options;
21564
+ this.filteredOptions = [...s2], this.multiple = t2.multiple === true, this.#t = typeof t2.options == "function" ? t2.filter : t2.filter ?? H;
21565
+ let e2;
21566
+ if (t2.initialValue && Array.isArray(t2.initialValue) ? this.multiple ? e2 = t2.initialValue : e2 = t2.initialValue.slice(0, 1) : !this.multiple && this.options.length > 0 && (e2 = [this.options[0].value]), e2)
21567
+ for (const i2 of e2) {
21568
+ const n2 = s2.findIndex((o3) => o3.value === i2);
21569
+ n2 !== -1 && (this.toggleSelected(i2), this.#s = n2);
21570
+ }
21571
+ this.focusedValue = this.options[this.#s]?.value, this.on("key", (i2, n2) => this.#e(i2, n2)), this.on("userInput", (i2) => this.#i(i2));
21572
+ }
21573
+ _isActionKey(t2, s2) {
21574
+ return t2 === "\t" || this.multiple && this.isNavigating && s2.name === "space" && t2 !== undefined && t2 !== "";
21575
+ }
21576
+ #e(t2, s2) {
21577
+ const e2 = s2.name === "up", i2 = s2.name === "down", n2 = s2.name === "return", o3 = this.userInput === "" || this.userInput === "\t", u3 = this.#u, a2 = this.options, l2 = u3 !== undefined && u3 !== "" && a2.some((c3) => !c3.disabled && (this.#t ? this.#t(u3, c3) : true));
21578
+ if (s2.name === "tab" && o3 && l2) {
21579
+ this.userInput === "\t" && this._clearUserInput(), this._setUserInput(u3, true), this.isNavigating = false;
21580
+ return;
21581
+ }
21582
+ e2 || i2 ? (this.#s = f3(this.#s, e2 ? -1 : 1, this.filteredOptions), this.focusedValue = this.filteredOptions[this.#s]?.value, this.multiple || (this.selectedValues = [this.focusedValue]), this.isNavigating = true) : n2 ? this.value = Q2(this.multiple, this.selectedValues) : this.multiple ? this.focusedValue !== undefined && (s2.name === "tab" || this.isNavigating && s2.name === "space") ? this.toggleSelected(this.focusedValue) : this.isNavigating = false : (this.focusedValue && (this.selectedValues = [this.focusedValue]), this.isNavigating = false);
21583
+ }
21584
+ deselectAll() {
21585
+ this.selectedValues = [];
21586
+ }
21587
+ toggleSelected(t2) {
21588
+ this.filteredOptions.length !== 0 && (this.multiple ? this.selectedValues.includes(t2) ? this.selectedValues = this.selectedValues.filter((s2) => s2 !== t2) : this.selectedValues = [...this.selectedValues, t2] : this.selectedValues = [t2]);
21589
+ }
21590
+ #i(t2) {
21591
+ if (t2 !== this.#r) {
21592
+ this.#r = t2;
21593
+ const s2 = this.options;
21594
+ t2 && this.#t ? this.filteredOptions = s2.filter((n2) => this.#t?.(t2, n2)) : this.filteredOptions = [...s2];
21595
+ const e2 = J2(this.focusedValue, this.filteredOptions);
21596
+ this.#s = f3(e2, 0, this.filteredOptions);
21597
+ const i2 = this.filteredOptions[this.#s];
21598
+ i2 && !i2.disabled ? this.focusedValue = i2.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
21599
+ }
21600
+ }
21601
+ };
21602
+
21603
+ class Z extends m2 {
21604
+ get cursor() {
21605
+ return this.value ? 0 : 1;
21606
+ }
21607
+ get _value() {
21608
+ return this.cursor === 0;
21609
+ }
21610
+ constructor(t2) {
21611
+ super(t2, false), this.value = !!t2.initialValue, this.on("userInput", () => {
21612
+ this.value = this._value;
21613
+ }), this.on("confirm", (s2) => {
21614
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s2, this.state = "submit", this.close();
21615
+ }), this.on("cursor", () => {
21616
+ this.value = !this.value;
21617
+ });
21618
+ }
21619
+ }
21620
+ var tt = { Y: { type: "year", len: 4 }, M: { type: "month", len: 2 }, D: { type: "day", len: 2 } };
21621
+ function F3(r3) {
21622
+ return [...r3].map((t2) => tt[t2]);
21623
+ }
21624
+ function st(r3) {
21625
+ const t2 = new Intl.DateTimeFormat(r3, { year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(new Date(2000, 0, 15)), s2 = [];
21626
+ let e2 = "/";
21627
+ for (const i2 of t2)
21628
+ i2.type === "literal" ? e2 = i2.value.trim() || i2.value : (i2.type === "year" || i2.type === "month" || i2.type === "day") && s2.push({ type: i2.type, len: i2.type === "year" ? 4 : 2 });
21629
+ return { segments: s2, separator: e2 };
21630
+ }
21631
+ function $(r3) {
21632
+ return Number.parseInt((r3 || "0").replace(/_/g, "0"), 10) || 0;
21633
+ }
21634
+ function S3(r3) {
21635
+ return { year: $(r3.year), month: $(r3.month), day: $(r3.day) };
21636
+ }
21637
+ function U(r3, t2) {
21638
+ return new Date(r3 || 2001, t2 || 1, 0).getDate();
21639
+ }
21640
+ function N3(r3) {
21641
+ const { year: t2, month: s2, day: e2 } = S3(r3);
21642
+ if (!t2 || t2 < 0 || t2 > 9999 || !s2 || s2 < 1 || s2 > 12 || !e2 || e2 < 1)
21643
+ return;
21644
+ const i2 = new Date(Date.UTC(t2, s2 - 1, e2));
21645
+ if (!(i2.getUTCFullYear() !== t2 || i2.getUTCMonth() !== s2 - 1 || i2.getUTCDate() !== e2))
21646
+ return { year: t2, month: s2, day: e2 };
21647
+ }
21648
+ function E(r3) {
21649
+ const t2 = N3(r3);
21650
+ return t2 ? new Date(Date.UTC(t2.year, t2.month - 1, t2.day)) : undefined;
21651
+ }
21652
+ function et(r3, t2, s2, e2) {
21653
+ const i2 = s2 ? { year: s2.getUTCFullYear(), month: s2.getUTCMonth() + 1, day: s2.getUTCDate() } : null, n2 = e2 ? { year: e2.getUTCFullYear(), month: e2.getUTCMonth() + 1, day: e2.getUTCDate() } : null;
21654
+ return r3 === "year" ? { min: i2?.year ?? 1, max: n2?.year ?? 9999 } : r3 === "month" ? { min: i2 && t2.year === i2.year ? i2.month : 1, max: n2 && t2.year === n2.year ? n2.month : 12 } : { min: i2 && t2.year === i2.year && t2.month === i2.month ? i2.day : 1, max: n2 && t2.year === n2.year && t2.month === n2.month ? n2.day : U(t2.year, t2.month) };
21655
+ }
21656
+
21657
+ class it extends m2 {
21658
+ #s;
21659
+ #r;
21660
+ #t;
21661
+ #n;
21662
+ #u;
21663
+ #e = { segmentIndex: 0, positionInSegment: 0 };
21664
+ #i = true;
21665
+ #o = null;
21666
+ inlineError = "";
21667
+ get segmentCursor() {
21668
+ return { ...this.#e };
21669
+ }
21670
+ get segmentValues() {
21671
+ return { ...this.#t };
21672
+ }
21673
+ get segments() {
21674
+ return this.#s;
21675
+ }
21676
+ get separator() {
21677
+ return this.#r;
21678
+ }
21679
+ get formattedValue() {
21680
+ return this.#c(this.#t);
21681
+ }
21682
+ #c(t2) {
21683
+ return this.#s.map((s2) => t2[s2.type]).join(this.#r);
21684
+ }
21685
+ #a() {
21686
+ this._setUserInput(this.#c(this.#t)), this._setValue(E(this.#t) ?? undefined);
21687
+ }
21688
+ constructor(t2) {
21689
+ const s2 = t2.format ? { segments: F3(t2.format), separator: t2.separator ?? "/" } : st(t2.locale), e2 = t2.separator ?? s2.separator, i2 = t2.format ? F3(t2.format) : s2.segments, n2 = t2.initialValue ?? t2.defaultValue, o3 = n2 ? { year: String(n2.getUTCFullYear()).padStart(4, "0"), month: String(n2.getUTCMonth() + 1).padStart(2, "0"), day: String(n2.getUTCDate()).padStart(2, "0") } : { year: "____", month: "__", day: "__" }, u3 = i2.map((a2) => o3[a2.type]).join(e2);
21690
+ super({ ...t2, initialUserInput: u3 }, false), this.#s = i2, this.#r = e2, this.#t = o3, this.#n = t2.minDate, this.#u = t2.maxDate, this.#a(), this.on("cursor", (a2) => this.#d(a2)), this.on("key", (a2, l2) => this.#f(a2, l2)), this.on("finalize", () => this.#g(t2));
21691
+ }
21692
+ #h() {
21693
+ const t2 = Math.max(0, Math.min(this.#e.segmentIndex, this.#s.length - 1)), s2 = this.#s[t2];
21694
+ if (s2)
21695
+ return this.#e.positionInSegment = Math.max(0, Math.min(this.#e.positionInSegment, s2.len - 1)), { segment: s2, index: t2 };
21696
+ }
21697
+ #l(t2) {
21698
+ this.inlineError = "", this.#o = null;
21699
+ const s2 = this.#h();
21700
+ s2 && (this.#e.segmentIndex = Math.max(0, Math.min(this.#s.length - 1, s2.index + t2)), this.#e.positionInSegment = 0, this.#i = true);
21701
+ }
21702
+ #p(t2) {
21703
+ const s2 = this.#h();
21704
+ if (!s2)
21705
+ return;
21706
+ const { segment: e2 } = s2, i2 = this.#t[e2.type], n2 = !i2 || i2.replace(/_/g, "") === "", o3 = Number.parseInt((i2 || "0").replace(/_/g, "0"), 10) || 0, u3 = et(e2.type, S3(this.#t), this.#n, this.#u);
21707
+ let a2;
21708
+ n2 ? a2 = t2 === 1 ? u3.min : u3.max : a2 = Math.max(Math.min(u3.max, o3 + t2), u3.min), this.#t = { ...this.#t, [e2.type]: a2.toString().padStart(e2.len, "0") }, this.#i = true, this.#o = null, this.#a();
21709
+ }
21710
+ #d(t2) {
21711
+ if (t2)
21712
+ switch (t2) {
21713
+ case "right":
21714
+ return this.#l(1);
21715
+ case "left":
21716
+ return this.#l(-1);
21717
+ case "up":
21718
+ return this.#p(1);
21719
+ case "down":
21720
+ return this.#p(-1);
21721
+ }
21722
+ }
21723
+ #f(t2, s2) {
21724
+ if (s2?.name === "backspace" || s2?.sequence === "\x7F" || s2?.sequence === "\b" || t2 === "\x7F" || t2 === "\b") {
21725
+ this.inlineError = "";
21726
+ const e2 = this.#h();
21727
+ if (!e2)
21728
+ return;
21729
+ if (!this.#t[e2.segment.type].replace(/_/g, "")) {
21730
+ this.#l(-1);
21731
+ return;
21732
+ }
21733
+ this.#t[e2.segment.type] = "_".repeat(e2.segment.len), this.#i = true, this.#e.positionInSegment = 0, this.#a();
21734
+ return;
21735
+ }
21736
+ if (s2?.name === "tab") {
21737
+ this.inlineError = "";
21738
+ const e2 = this.#h();
21739
+ if (!e2)
21740
+ return;
21741
+ const i2 = s2.shift ? -1 : 1, n2 = e2.index + i2;
21742
+ n2 >= 0 && n2 < this.#s.length && (this.#e.segmentIndex = n2, this.#e.positionInSegment = 0, this.#i = true);
21743
+ return;
21744
+ }
21745
+ if (t2 && /^[0-9]$/.test(t2)) {
21746
+ const e2 = this.#h();
21747
+ if (!e2)
21748
+ return;
21749
+ const { segment: i2 } = e2, n2 = !this.#t[i2.type].replace(/_/g, "");
21750
+ if (this.#i && this.#o !== null && !n2) {
21751
+ const d2 = this.#o + t2, g3 = { ...this.#t, [i2.type]: d2 }, _3 = this.#m(g3, i2);
21752
+ if (_3) {
21753
+ this.inlineError = _3, this.#o = null, this.#i = false;
21754
+ return;
21755
+ }
21756
+ this.inlineError = "", this.#t[i2.type] = d2, this.#o = null, this.#i = false, this.#a(), e2.index < this.#s.length - 1 && (this.#e.segmentIndex = e2.index + 1, this.#e.positionInSegment = 0, this.#i = true);
21757
+ return;
21758
+ }
21759
+ this.#i && !n2 && (this.#t[i2.type] = "_".repeat(i2.len), this.#e.positionInSegment = 0), this.#i = false, this.#o = null;
21760
+ const o3 = this.#t[i2.type], u3 = o3.indexOf("_"), a2 = u3 >= 0 ? u3 : Math.min(this.#e.positionInSegment, i2.len - 1);
21761
+ if (a2 < 0 || a2 >= i2.len)
21762
+ return;
21763
+ let l2 = o3.slice(0, a2) + t2 + o3.slice(a2 + 1), c3 = false;
21764
+ if (a2 === 0 && o3 === "__" && (i2.type === "month" || i2.type === "day")) {
21765
+ const d2 = Number.parseInt(t2, 10);
21766
+ l2 = `0${t2}`, c3 = d2 <= (i2.type === "month" ? 1 : 2);
21767
+ }
21768
+ if (i2.type === "year" && (l2 = (o3.replace(/_/g, "") + t2).padStart(i2.len, "_")), !l2.includes("_")) {
21769
+ const d2 = { ...this.#t, [i2.type]: l2 }, g3 = this.#m(d2, i2);
21770
+ if (g3) {
21771
+ this.inlineError = g3;
21772
+ return;
21773
+ }
21774
+ }
21775
+ this.inlineError = "", this.#t[i2.type] = l2;
21776
+ const y3 = l2.includes("_") ? undefined : N3(this.#t);
21777
+ if (y3) {
21778
+ const { year: d2, month: g3 } = y3, _3 = U(d2, g3);
21779
+ this.#t = { year: String(Math.max(0, Math.min(9999, d2))).padStart(4, "0"), month: String(Math.max(1, Math.min(12, g3))).padStart(2, "0"), day: String(Math.max(1, Math.min(_3, y3.day))).padStart(2, "0") };
21780
+ }
21781
+ this.#a();
21782
+ const T3 = l2.indexOf("_");
21783
+ c3 ? (this.#i = true, this.#o = t2) : T3 >= 0 ? this.#e.positionInSegment = T3 : u3 >= 0 && e2.index < this.#s.length - 1 ? (this.#e.segmentIndex = e2.index + 1, this.#e.positionInSegment = 0, this.#i = true) : this.#e.positionInSegment = Math.min(a2 + 1, i2.len - 1);
21784
+ }
21785
+ }
21786
+ #m(t2, s2) {
21787
+ const { month: e2, day: i2 } = S3(t2);
21788
+ if (s2.type === "month" && (e2 < 0 || e2 > 12))
21789
+ return h2.date.messages.invalidMonth;
21790
+ if (s2.type === "day" && (i2 < 0 || i2 > 31))
21791
+ return h2.date.messages.invalidDay(31, "any month");
21792
+ }
21793
+ #g(t2) {
21794
+ const { year: s2, month: e2, day: i2 } = S3(this.#t);
21795
+ if (s2 && e2 && i2) {
21796
+ const n2 = U(s2, e2);
21797
+ this.#t = { ...this.#t, day: String(Math.min(i2, n2)).padStart(2, "0") };
21798
+ }
21799
+ this.value = E(this.#t) ?? t2.defaultValue ?? undefined;
21800
+ }
21801
+ }
21802
+ var rt = class extends m2 {
21803
+ options;
21804
+ cursor = 0;
21805
+ #s;
21806
+ getGroupItems(t2) {
21807
+ return this.options.filter((s2) => s2.group === t2);
21808
+ }
21809
+ isGroupSelected(t2) {
21810
+ const s2 = this.getGroupItems(t2), e2 = this.value;
21811
+ return e2 === undefined ? false : s2.every((i2) => e2.includes(i2.value));
21812
+ }
21813
+ toggleValue() {
21814
+ const t2 = this.options[this.cursor];
21815
+ if (this.value === undefined && (this.value = []), t2.group === true) {
21816
+ const s2 = t2.value, e2 = this.getGroupItems(s2);
21817
+ this.isGroupSelected(s2) ? this.value = this.value.filter((i2) => e2.findIndex((n2) => n2.value === i2) === -1) : this.value = [...this.value, ...e2.map((i2) => i2.value)], this.value = Array.from(new Set(this.value));
21818
+ } else {
21819
+ const s2 = this.value.includes(t2.value);
21820
+ this.value = s2 ? this.value.filter((e2) => e2 !== t2.value) : [...this.value, t2.value];
21821
+ }
21822
+ }
21823
+ constructor(t2) {
21824
+ super(t2, false);
21825
+ const { options: s2 } = t2;
21826
+ this.#s = t2.selectableGroups !== false, this.options = Object.entries(s2).flatMap(([e2, i2]) => [{ value: e2, group: true, label: e2 }, ...i2.map((n2) => ({ ...n2, group: e2 }))]), this.value = [...t2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e2 }) => e2 === t2.cursorAt), this.#s ? 0 : 1), this.on("cursor", (e2) => {
21827
+ switch (e2) {
21828
+ case "left":
21829
+ case "up": {
21830
+ this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
21831
+ const i2 = this.options[this.cursor]?.group === true;
21832
+ !this.#s && i2 && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
21833
+ break;
21834
+ }
21835
+ case "down":
21836
+ case "right": {
21837
+ this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
21838
+ const i2 = this.options[this.cursor]?.group === true;
21839
+ !this.#s && i2 && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
21840
+ break;
21841
+ }
21842
+ case "space":
21843
+ this.toggleValue();
21844
+ break;
21845
+ }
21846
+ });
21847
+ }
21848
+ };
21849
+ var nt = new Set(["up", "down", "left", "right"]);
21850
+
21851
+ class ot extends m2 {
21852
+ #s = false;
21853
+ #r;
21854
+ focused = "editor";
21855
+ get userInputWithCursor() {
21856
+ if (this.state === "submit")
21857
+ return this.userInput;
21858
+ const t2 = this.userInput;
21859
+ if (this.cursor >= t2.length)
21860
+ return `${t2}\u2588`;
21861
+ const s2 = t2.slice(0, this.cursor), e2 = t2[this.cursor], i2 = t2.slice(this.cursor + 1);
21862
+ return e2 === `
21863
+ ` ? `${s2}\u2588
21864
+ ${i2}` : `${s2}${v2("inverse", e2)}${i2}`;
21865
+ }
21866
+ get cursor() {
21867
+ return this._cursor;
21868
+ }
21869
+ #t(t2) {
21870
+ if (this.userInput.length === 0) {
21871
+ this._setUserInput(t2);
21872
+ return;
21873
+ }
21874
+ this._setUserInput(this.userInput.slice(0, this.cursor) + t2 + this.userInput.slice(this.cursor));
21875
+ }
21876
+ #n(t2) {
21877
+ const s2 = this.value ?? "";
21878
+ switch (t2) {
21879
+ case "up":
21880
+ this._cursor = I3(this._cursor, 0, -1, s2);
21881
+ return;
21882
+ case "down":
21883
+ this._cursor = I3(this._cursor, 0, 1, s2);
21884
+ return;
21885
+ case "left":
21886
+ this._cursor = I3(this._cursor, -1, 0, s2);
21887
+ return;
21888
+ case "right":
21889
+ this._cursor = I3(this._cursor, 1, 0, s2);
21890
+ return;
21891
+ }
21892
+ }
21893
+ _shouldSubmit(t2, s2) {
21894
+ if (this.#r)
21895
+ return this.focused === "submit" ? true : (this.#t(`
21896
+ `), this._cursor++, false);
21897
+ const e2 = this.#s;
21898
+ return this.#s = true, e2 ? (this.userInput[this.cursor - 1] === `
21899
+ ` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#t(`
21900
+ `), this._cursor++, false);
21901
+ }
21902
+ constructor(t2) {
21903
+ super(t2, false), this.#r = t2.showSubmit ?? false, this.on("key", (s2, e2) => {
21904
+ if (e2?.name && nt.has(e2.name)) {
21905
+ this.#n(e2.name);
21906
+ return;
21907
+ }
21908
+ if (s2 === "\t" && this.#r) {
21909
+ this.focused = this.focused === "editor" ? "submit" : "editor";
21910
+ return;
21911
+ }
21912
+ if (e2?.name !== "return") {
21913
+ if (this.#s = false, e2?.name === "backspace" && this.cursor > 0) {
21914
+ this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--;
21915
+ return;
21916
+ }
21917
+ if (e2?.name === "delete" && this.cursor < this.userInput.length) {
21918
+ this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
21919
+ return;
21920
+ }
21921
+ s2 && (this.#r && this.focused === "submit" && (this.focused = "editor"), this.#t(s2 ?? ""), this._cursor++);
21922
+ }
21923
+ }), this.on("userInput", (s2) => {
21924
+ this._setValue(s2);
21925
+ }), this.on("finalize", () => {
21926
+ this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
21927
+ });
21928
+ }
21929
+ }
21930
+ class ht extends m2 {
21931
+ options;
21932
+ cursor = 0;
21933
+ get _selectedValue() {
21934
+ return this.options[this.cursor];
21935
+ }
21936
+ changeValue() {
21937
+ this.value = this._selectedValue.value;
21938
+ }
21939
+ constructor(t2) {
21940
+ super(t2, false), this.options = t2.options;
21941
+ const s2 = this.options.findIndex(({ value: i2 }) => i2 === t2.initialValue), e2 = s2 === -1 ? 0 : s2;
21942
+ this.cursor = this.options[e2].disabled ? f3(e2, 1, this.options) : e2, this.changeValue(), this.on("cursor", (i2) => {
21943
+ switch (i2) {
21944
+ case "left":
21945
+ case "up":
21946
+ this.cursor = f3(this.cursor, -1, this.options);
21947
+ break;
21948
+ case "down":
21949
+ case "right":
21950
+ this.cursor = f3(this.cursor, 1, this.options);
21951
+ break;
21952
+ }
21953
+ this.changeValue();
21954
+ });
21955
+ }
21956
+ }
21957
+ class ct extends m2 {
21958
+ get userInputWithCursor() {
21959
+ if (this.state === "submit")
21960
+ return this.userInput;
21961
+ const t2 = this.userInput;
21962
+ if (this.cursor >= t2.length)
21963
+ return `${this.userInput}\u2588`;
21964
+ const s2 = t2.slice(0, this.cursor), [e2, ...i2] = t2.slice(this.cursor);
21965
+ return `${s2}${v2("inverse", e2)}${i2.join("")}`;
21966
+ }
21967
+ get cursor() {
21968
+ return this._cursor;
21969
+ }
21970
+ constructor(t2) {
21971
+ super({ ...t2, initialUserInput: t2.initialUserInput ?? t2.initialValue }), this.on("userInput", (s2) => {
21972
+ this._setValue(s2);
21973
+ }), this.on("finalize", () => {
21974
+ this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
21975
+ });
21976
+ }
21977
+ }
21978
+
21979
+ // node_modules/@clack/prompts/dist/index.mjs
21980
+ import { styleText as e2, stripVTControlCharacters as ot2 } from "util";
21981
+ import V3 from "process";
21982
+ var import_sisteransi2 = __toESM(require_src(), 1);
21983
+ function se() {
21984
+ return V3.platform !== "win32" ? V3.env.TERM !== "linux" : !!V3.env.CI || !!V3.env.WT_SESSION || !!V3.env.TERMINUS_SUBLIME || V3.env.ConEmuTask === "{cmd::Cmder}" || V3.env.TERM_PROGRAM === "Terminus-Sublime" || V3.env.TERM_PROGRAM === "vscode" || V3.env.TERM === "xterm-256color" || V3.env.TERM === "alacritty" || V3.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
21985
+ }
21986
+ var tt2 = se();
21987
+ var at2 = () => process.env.CI === "true";
21988
+ var w3 = (t2, i2) => tt2 ? t2 : i2;
21989
+ var _t = w3("\u25C6", "*");
21990
+ var ut2 = w3("\u25A0", "x");
21991
+ var lt2 = w3("\u25B2", "x");
21992
+ var H2 = w3("\u25C7", "o");
21993
+ var ct2 = w3("\u250C", "T");
21994
+ var $2 = w3("\u2502", "|");
21995
+ var x3 = w3("\u2514", "\u2014");
21996
+ var xt = w3("\u2510", "T");
21997
+ var Et = w3("\u2518", "\u2014");
21998
+ var z4 = w3("\u25CF", ">");
21999
+ var U2 = w3("\u25CB", " ");
22000
+ var et2 = w3("\u25FB", "[\u2022]");
22001
+ var K2 = w3("\u25FC", "[+]");
22002
+ var Y3 = w3("\u25FB", "[ ]");
22003
+ var Gt = w3("\u25AA", "\u2022");
22004
+ var st2 = w3("\u2500", "-");
22005
+ var $t = w3("\u256E", "+");
22006
+ var Mt = w3("\u251C", "+");
22007
+ var dt = w3("\u256F", "+");
22008
+ var ht2 = w3("\u2570", "+");
22009
+ var Ot = w3("\u256D", "+");
22010
+ var pt = w3("\u25CF", "\u2022");
22011
+ var mt = w3("\u25C6", "*");
22012
+ var gt = w3("\u25B2", "!");
22013
+ var yt = w3("\u25A0", "x");
22014
+ var P4 = (t2) => {
22015
+ switch (t2) {
22016
+ case "initial":
22017
+ case "active":
22018
+ return e2("cyan", _t);
22019
+ case "cancel":
22020
+ return e2("red", ut2);
22021
+ case "error":
22022
+ return e2("yellow", lt2);
22023
+ case "submit":
22024
+ return e2("green", H2);
22025
+ }
22026
+ };
22027
+ var ft = (t2) => {
22028
+ switch (t2) {
22029
+ case "initial":
22030
+ case "active":
22031
+ return e2("cyan", $2);
22032
+ case "cancel":
22033
+ return e2("red", $2);
22034
+ case "error":
22035
+ return e2("yellow", $2);
22036
+ case "submit":
22037
+ return e2("green", $2);
22038
+ }
22039
+ };
22040
+ var Pt = (t2, i2, s2, r3, u3, n2 = false) => {
22041
+ let a2 = i2, c3 = 0;
22042
+ if (n2)
22043
+ for (let o3 = r3 - 1;o3 >= s2 && (a2 -= t2[o3].length, c3++, !(a2 <= u3)); o3--)
22044
+ ;
22045
+ else
22046
+ for (let o3 = s2;o3 < r3 && (a2 -= t2[o3].length, c3++, !(a2 <= u3)); o3++)
22047
+ ;
22048
+ return { lineCount: a2, removals: c3 };
22049
+ };
22050
+ var F4 = ({ cursor: t2, options: i2, style: s2, output: r3 = process.stdout, maxItems: u3 = Number.POSITIVE_INFINITY, columnPadding: n2 = 0, rowPadding: a2 = 4 }) => {
22051
+ const c3 = A3(r3) - n2, o3 = L3(r3), l2 = e2("dim", "..."), d2 = Math.max(o3 - a2, 0), g3 = Math.max(Math.min(u3, d2), 5);
22052
+ let p2 = 0;
22053
+ t2 >= g3 - 3 && (p2 = Math.max(Math.min(t2 - g3 + 3, i2.length - g3), 0));
22054
+ let f4 = g3 < i2.length && p2 > 0, h3 = g3 < i2.length && p2 + g3 < i2.length;
22055
+ const I4 = Math.min(p2 + g3, i2.length), m3 = [];
22056
+ let y3 = 0;
22057
+ f4 && y3++, h3 && y3++;
22058
+ const v3 = p2 + (f4 ? 1 : 0), C4 = I4 - (h3 ? 1 : 0);
22059
+ for (let b3 = v3;b3 < C4; b3++) {
22060
+ const G4 = wrapAnsi(s2(i2[b3], b3 === t2), c3, { hard: true, trim: false }).split(`
22061
+ `);
22062
+ m3.push(G4), y3 += G4.length;
22063
+ }
22064
+ if (y3 > d2) {
22065
+ let b3 = 0, G4 = 0, M = y3;
22066
+ const N4 = t2 - v3;
22067
+ let O4 = d2;
22068
+ const j2 = () => Pt(m3, M, 0, N4, O4), k3 = () => Pt(m3, M, N4 + 1, m3.length, O4, true);
22069
+ f4 ? ({ lineCount: M, removals: b3 } = j2(), M > O4 && (h3 || (O4 -= 1), { lineCount: M, removals: G4 } = k3())) : (h3 || (O4 -= 1), { lineCount: M, removals: G4 } = k3(), M > O4 && (O4 -= 1, { lineCount: M, removals: b3 } = j2())), b3 > 0 && (f4 = true, m3.splice(0, b3)), G4 > 0 && (h3 = true, m3.splice(m3.length - G4, G4));
22070
+ }
22071
+ const S4 = [];
22072
+ f4 && S4.push(l2);
22073
+ for (const b3 of m3)
22074
+ for (const G4 of b3)
22075
+ S4.push(G4);
22076
+ return h3 && S4.push(l2), S4;
22077
+ };
22078
+ var le2 = (t2) => {
22079
+ const i2 = t2.active ?? "Yes", s2 = t2.inactive ?? "No";
22080
+ return new Z({ active: i2, inactive: s2, signal: t2.signal, input: t2.input, output: t2.output, initialValue: t2.initialValue ?? true, render() {
22081
+ const r3 = t2.withGuide ?? h2.withGuide, u3 = `${P4(this.state)} `, n2 = r3 ? `${e2("gray", $2)} ` : "", a2 = B2(t2.output, t2.message, n2, u3), c3 = `${r3 ? `${e2("gray", $2)}
22082
+ ` : ""}${a2}
22083
+ `, o3 = this.value ? i2 : s2;
22084
+ switch (this.state) {
22085
+ case "submit": {
22086
+ const l2 = r3 ? `${e2("gray", $2)} ` : "";
22087
+ return `${c3}${l2}${e2("dim", o3)}`;
22088
+ }
22089
+ case "cancel": {
22090
+ const l2 = r3 ? `${e2("gray", $2)} ` : "";
22091
+ return `${c3}${l2}${e2(["strikethrough", "dim"], o3)}${r3 ? `
22092
+ ${e2("gray", $2)}` : ""}`;
22093
+ }
22094
+ default: {
22095
+ const l2 = r3 ? `${e2("cyan", $2)} ` : "", d2 = r3 ? e2("cyan", x3) : "";
22096
+ return `${c3}${l2}${this.value ? `${e2("green", z4)} ${i2}` : `${e2("dim", U2)} ${e2("dim", i2)}`}${t2.vertical ? r3 ? `
22097
+ ${e2("cyan", $2)} ` : `
22098
+ ` : ` ${e2("dim", "/")} `}${this.value ? `${e2("dim", U2)} ${e2("dim", s2)}` : `${e2("green", z4)} ${s2}`}
22099
+ ${d2}
22100
+ `;
22101
+ }
22102
+ }
22103
+ } }).prompt();
22104
+ };
22105
+ var ge = (t2 = "", i2) => {
22106
+ const s2 = i2?.output ?? process.stdout, r3 = i2?.withGuide ?? h2.withGuide ? `${e2("gray", x3)} ` : "";
22107
+ s2.write(`${r3}${e2("red", t2)}
22108
+
22109
+ `);
22110
+ };
22111
+ var ye2 = (t2 = "", i2) => {
22112
+ const s2 = i2?.output ?? process.stdout, r3 = i2?.withGuide ?? h2.withGuide ? `${e2("gray", ct2)} ` : "";
22113
+ s2.write(`${r3}${t2}
22114
+ `);
22115
+ };
22116
+ var fe2 = (t2 = "", i2) => {
22117
+ const s2 = i2?.output ?? process.stdout, r3 = i2?.withGuide ?? h2.withGuide ? `${e2("gray", $2)}
22118
+ ${e2("gray", x3)} ` : "";
22119
+ s2.write(`${r3}${t2}
22120
+
22121
+ `);
22122
+ };
22123
+ var be = (t2) => e2("dim", t2);
22124
+ var Se = (t2, i2, s2) => {
22125
+ const r3 = { hard: true, trim: false }, u3 = wrapAnsi(t2, i2, r3).split(`
22126
+ `), n2 = u3.reduce((o3, l2) => Math.max(dist_default2(l2), o3), 0), a2 = u3.map(s2).reduce((o3, l2) => Math.max(dist_default2(l2), o3), 0), c3 = i2 - (a2 - n2);
22127
+ return wrapAnsi(t2, c3, r3);
22128
+ };
22129
+ var Ce = (t2 = "", i2 = "", s2) => {
22130
+ const r3 = s2?.output ?? V3.stdout, u3 = s2?.withGuide ?? h2.withGuide, n2 = s2?.format ?? be, a2 = ["", ...Se(t2, A3(r3) - 6, n2).split(`
22131
+ `).map(n2), ""], c3 = dist_default2(i2), o3 = Math.max(a2.reduce((p2, f4) => {
22132
+ const h3 = dist_default2(f4);
22133
+ return h3 > p2 ? h3 : p2;
22134
+ }, 0), c3) + 2, l2 = a2.map((p2) => `${e2("gray", $2)} ${p2}${" ".repeat(o3 - dist_default2(p2))}${e2("gray", $2)}`).join(`
22135
+ `), d2 = u3 ? `${e2("gray", $2)}
22136
+ ` : "", g3 = u3 ? Mt : ht2;
22137
+ r3.write(`${d2}${e2("green", H2)} ${e2("reset", i2)} ${e2("gray", st2.repeat(Math.max(o3 - c3 - 1, 1)) + $t)}
22138
+ ${l2}
22139
+ ${e2("gray", g3 + st2.repeat(o3 + 2) + dt)}
22140
+ `);
22141
+ };
22142
+ var _e = (t2) => e2("magenta", t2);
22143
+ var vt = ({ indicator: t2 = "dots", onCancel: i2, output: s2 = process.stdout, cancelMessage: r3, errorMessage: u3, frames: n2 = tt2 ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: a2 = tt2 ? 80 : 120, signal: c3, ...o3 } = {}) => {
22144
+ const l2 = at2();
22145
+ let d2, g3, p2 = false, f4 = false, h3 = "", I4, m3 = performance.now();
22146
+ const y3 = A3(s2), v3 = o3?.styleFrame ?? _e, C4 = (_3) => {
22147
+ const A4 = _3 > 1 ? u3 ?? h2.messages.error : r3 ?? h2.messages.cancel;
22148
+ f4 = _3 === 1, p2 && (W3(A4, _3), f4 && typeof i2 == "function" && i2());
22149
+ }, S4 = () => C4(2), b3 = () => C4(1), G4 = () => {
22150
+ process.on("uncaughtExceptionMonitor", S4), process.on("unhandledRejection", S4), process.on("SIGINT", b3), process.on("SIGTERM", b3), process.on("exit", C4), c3 && c3.addEventListener("abort", b3);
22151
+ }, M = () => {
22152
+ process.removeListener("uncaughtExceptionMonitor", S4), process.removeListener("unhandledRejection", S4), process.removeListener("SIGINT", b3), process.removeListener("SIGTERM", b3), process.removeListener("exit", C4), c3 && c3.removeEventListener("abort", b3);
22153
+ }, N4 = () => {
22154
+ if (I4 === undefined)
22155
+ return;
22156
+ l2 && s2.write(`
22157
+ `);
22158
+ const _3 = wrapAnsi(I4, y3, { hard: true, trim: false }).split(`
22159
+ `);
22160
+ _3.length > 1 && s2.write(import_sisteransi2.cursor.up(_3.length - 1)), s2.write(import_sisteransi2.cursor.to(0)), s2.write(import_sisteransi2.erase.down());
22161
+ }, O4 = (_3) => _3.replace(/\.+$/, ""), j2 = (_3) => {
22162
+ const A4 = (performance.now() - _3) / 1000, L4 = Math.floor(A4 / 60), D3 = Math.floor(A4 % 60);
22163
+ return L4 > 0 ? `[${L4}m ${D3}s]` : `[${D3}s]`;
22164
+ }, k3 = o3.withGuide ?? h2.withGuide, rt2 = (_3 = "") => {
22165
+ p2 = true, d2 = W2({ output: s2 }), h3 = O4(_3), m3 = performance.now(), k3 && s2.write(`${e2("gray", $2)}
22166
+ `);
22167
+ let A4 = 0, L4 = 0;
22168
+ G4(), g3 = setInterval(() => {
22169
+ if (l2 && h3 === I4)
22170
+ return;
22171
+ N4(), I4 = h3;
22172
+ const D3 = v3(n2[A4]);
22173
+ let Z2;
22174
+ if (l2)
22175
+ Z2 = `${D3} ${h3}...`;
22176
+ else if (t2 === "timer")
22177
+ Z2 = `${D3} ${h3} ${j2(m3)}`;
22178
+ else {
22179
+ const Lt = ".".repeat(Math.floor(L4)).slice(0, 3);
22180
+ Z2 = `${D3} ${h3}${Lt}`;
22181
+ }
22182
+ const kt = wrapAnsi(Z2, y3, { hard: true, trim: false });
22183
+ s2.write(kt), A4 = A4 + 1 < n2.length ? A4 + 1 : 0, L4 = L4 < 4 ? L4 + 0.125 : 0;
22184
+ }, a2);
22185
+ }, W3 = (_3 = "", A4 = 0, L4 = false) => {
22186
+ if (!p2)
22187
+ return;
22188
+ p2 = false, clearInterval(g3), N4();
22189
+ const D3 = A4 === 0 ? e2("green", H2) : A4 === 1 ? e2("red", ut2) : e2("red", lt2);
22190
+ h3 = _3 ?? h3, L4 || (t2 === "timer" ? s2.write(`${D3} ${h3} ${j2(m3)}
22191
+ `) : s2.write(`${D3} ${h3}
22192
+ `)), M(), d2();
22193
+ };
22194
+ return { start: rt2, stop: (_3 = "") => W3(_3, 0), message: (_3 = "") => {
22195
+ h3 = O4(_3 ?? h3);
22196
+ }, cancel: (_3 = "") => W3(_3, 1), error: (_3 = "") => W3(_3, 2), clear: () => W3("", 0, true), get isCancelled() {
22197
+ return f4;
22198
+ } };
22199
+ };
22200
+ var Nt = { light: w3("\u2500", "-"), heavy: w3("\u2501", "="), block: w3("\u2588", "#") };
22201
+ var it2 = (t2, i2) => t2.includes(`
22202
+ `) ? t2.split(`
22203
+ `).map((s2) => i2(s2)).join(`
22204
+ `) : i2(t2);
22205
+ var Ee = (t2) => {
22206
+ const i2 = (s2, r3) => {
22207
+ const u3 = s2.label ?? String(s2.value);
22208
+ switch (r3) {
22209
+ case "disabled":
22210
+ return `${e2("gray", U2)} ${it2(u3, (n2) => e2("gray", n2))}${s2.hint ? ` ${e2("dim", `(${s2.hint ?? "disabled"})`)}` : ""}`;
22211
+ case "selected":
22212
+ return `${it2(u3, (n2) => e2("dim", n2))}`;
22213
+ case "active":
22214
+ return `${e2("green", z4)} ${u3}${s2.hint ? ` ${e2("dim", `(${s2.hint})`)}` : ""}`;
22215
+ case "cancelled":
22216
+ return `${it2(u3, (n2) => e2(["strikethrough", "dim"], n2))}`;
22217
+ default:
22218
+ return `${e2("dim", U2)} ${it2(u3, (n2) => e2("dim", n2))}`;
22219
+ }
22220
+ };
22221
+ return new ht({ options: t2.options, signal: t2.signal, input: t2.input, output: t2.output, initialValue: t2.initialValue, render() {
22222
+ const s2 = t2.withGuide ?? h2.withGuide, r3 = `${P4(this.state)} `, u3 = `${ft(this.state)} `, n2 = B2(t2.output, t2.message, u3, r3), a2 = `${s2 ? `${e2("gray", $2)}
22223
+ ` : ""}${n2}
22224
+ `;
22225
+ switch (this.state) {
22226
+ case "submit": {
22227
+ const c3 = s2 ? `${e2("gray", $2)} ` : "", o3 = B2(t2.output, i2(this.options[this.cursor], "selected"), c3);
22228
+ return `${a2}${o3}`;
22229
+ }
22230
+ case "cancel": {
22231
+ const c3 = s2 ? `${e2("gray", $2)} ` : "", o3 = B2(t2.output, i2(this.options[this.cursor], "cancelled"), c3);
22232
+ return `${a2}${o3}${s2 ? `
22233
+ ${e2("gray", $2)}` : ""}`;
22234
+ }
22235
+ default: {
22236
+ const c3 = s2 ? `${e2("cyan", $2)} ` : "", o3 = s2 ? e2("cyan", x3) : "", l2 = a2.split(`
22237
+ `).length, d2 = s2 ? 2 : 1;
22238
+ return `${a2}${c3}${F4({ output: t2.output, cursor: this.cursor, options: this.options, maxItems: t2.maxItems, columnPadding: c3.length, rowPadding: l2 + d2, style: (g3, p2) => i2(g3, g3.disabled ? "disabled" : p2 ? "active" : "inactive") }).join(`
22239
+ ${c3}`)}
22240
+ ${o3}
22241
+ `;
22242
+ }
22243
+ }
22244
+ } }).prompt();
22245
+ };
22246
+ var Bt = `${e2("gray", $2)} `;
22247
+ var Re = (t2) => new ct({ validate: t2.validate, placeholder: t2.placeholder, defaultValue: t2.defaultValue, initialValue: t2.initialValue, output: t2.output, signal: t2.signal, input: t2.input, render() {
22248
+ const i2 = t2?.withGuide ?? h2.withGuide, s2 = `${`${i2 ? `${e2("gray", $2)}
22249
+ ` : ""}${P4(this.state)} `}${t2.message}
22250
+ `, r3 = t2.placeholder ? e2("inverse", t2.placeholder[0]) + e2("dim", t2.placeholder.slice(1)) : e2(["inverse", "hidden"], "_"), u3 = this.userInput ? this.userInputWithCursor : r3, n2 = this.value ?? "";
22251
+ switch (this.state) {
22252
+ case "error": {
22253
+ const a2 = this.error ? ` ${e2("yellow", this.error)}` : "", c3 = i2 ? `${e2("yellow", $2)} ` : "", o3 = i2 ? e2("yellow", x3) : "";
22254
+ return `${s2.trim()}
22255
+ ${c3}${u3}
22256
+ ${o3}${a2}
22257
+ `;
22258
+ }
22259
+ case "submit": {
22260
+ const a2 = n2 ? ` ${e2("dim", n2)}` : "", c3 = i2 ? e2("gray", $2) : "";
22261
+ return `${s2}${c3}${a2}`;
22262
+ }
22263
+ case "cancel": {
22264
+ const a2 = n2 ? ` ${e2(["strikethrough", "dim"], n2)}` : "", c3 = i2 ? e2("gray", $2) : "";
22265
+ return `${s2}${c3}${a2}${n2.trim() ? `
22266
+ ${c3}` : ""}`;
22267
+ }
22268
+ default: {
22269
+ const a2 = i2 ? `${e2("cyan", $2)} ` : "", c3 = i2 ? e2("cyan", x3) : "";
22270
+ return `${s2}${a2}${u3}
22271
+ ${c3}
22272
+ `;
22273
+ }
22274
+ }
22275
+ } }).prompt();
22276
+
22277
+ // src/cli/commands/config.ts
22278
+ var iso2 = (ms) => ms ? new Date(ms).toISOString() : "";
22279
+ var isTTY2 = () => process.stdout.isTTY && process.stdin.isTTY;
22280
+ function ask(label, def) {
22281
+ const suffix = def != null && def !== "" ? ` [${def}]` : "";
22282
+ const raw = globalThis.prompt(`${label}${suffix}:`);
22283
+ const val = (raw ?? "").trim();
22284
+ return val === "" && def != null ? def : val;
22285
+ }
22286
+ function flagOrAsk(flag, label) {
22287
+ if (typeof flag === "string" && flag !== "")
22288
+ return flag;
22289
+ if (isTTY2()) {
22290
+ const v3 = ask(label);
22291
+ if (v3)
22292
+ return v3;
22293
+ }
22294
+ throw new Error(`missing --${label}`);
22295
+ }
22296
+ function showConfig(db) {
22297
+ const cfg = getServerConfig(db);
22298
+ const peers = listPeers(db).map(peerView);
22299
+ print({ config: cfg, peers }, () => {
22300
+ const lines = [
22301
+ "server config:",
22302
+ ` host: ${cfg.host}`,
22303
+ ` port: ${cfg.port}`,
22304
+ ` sync-interval: ${Math.round(cfg.syncIntervalMs / 1000)}s`,
22305
+ ` auto-sync: ${cfg.autoSync}`,
22306
+ "",
22307
+ "peers:",
22308
+ peers.length ? table(peers) : " (none)"
22309
+ ];
22310
+ return lines.join(`
22311
+ `);
22312
+ });
22313
+ }
22314
+ function peerView(p2) {
22315
+ return {
22316
+ url: p2.url,
22317
+ label: p2.label ?? "",
22318
+ enabled: p2.enabled ? "yes" : "no",
22319
+ status: p2.last_status ?? "",
22320
+ last_sync: iso2(p2.last_sync_at)
22321
+ };
22322
+ }
22323
+ function applySet(db, args) {
22324
+ const partial2 = {};
22325
+ if (typeof args.host === "string" && args.host !== "")
22326
+ partial2.host = args.host;
22327
+ if (args.port != null && args.port !== "")
22328
+ partial2.port = Number(args.port);
22329
+ if (args["sync-interval"] != null && args["sync-interval"] !== "")
22330
+ partial2.syncIntervalMs = parseDuration(String(args["sync-interval"]), 30000);
22331
+ if (args["auto-sync"] != null)
22332
+ partial2.autoSync = parseBool(args["auto-sync"]);
22333
+ const next = setServerConfig(db, partial2);
22334
+ print(next, () => `updated. (restart --server to apply host/port/interval changes)`);
22335
+ }
22336
+ function parseBool(v3) {
22337
+ if (typeof v3 === "boolean")
22338
+ return v3;
22339
+ const s2 = String(v3).trim().toLowerCase();
22340
+ return s2 === "1" || s2 === "true" || s2 === "yes" || s2 === "on";
22341
+ }
22342
+ async function peerDispatch(db, action, args) {
22343
+ const node = getNodeId(db);
22344
+ switch (action) {
22345
+ case "add": {
22346
+ const url2 = flagOrAsk(args.url, "url");
22347
+ const code = flagOrAsk(args.code, "code");
22348
+ const selfUrl = typeof args["self-url"] === "string" ? args["self-url"] : undefined;
22349
+ const r3 = await performPairing(db, node, url2, code, selfUrl);
22350
+ const sync = await syncPeer(db, url2);
22351
+ print({ paired: r3, sync }, () => `paired with ${r3.url} (node ${r3.node_id}); ${syncLine(sync)}`);
22352
+ return;
22353
+ }
22354
+ case "code": {
22355
+ const c3 = generatePairingCode(db);
22356
+ print(c3, () => `pairing code: ${c3.code}
22357
+ expires: ${iso2(c3.exp)}
22358
+ (redeem with: mh config peer add --url <this-server-url> --code ${c3.code})`);
22359
+ return;
22360
+ }
22361
+ case "list":
22362
+ print(listPeers(db).map(peerView), () => {
22363
+ const rows = listPeers(db).map(peerView);
22364
+ return rows.length ? table(rows) : "(no peers)";
22365
+ });
22366
+ return;
22367
+ case "rm": {
22368
+ const url2 = flagOrAsk(args.url, "url");
22369
+ print({ ok: removePeer(db, url2) }, () => `removed ${url2}`);
22370
+ return;
22371
+ }
22372
+ case "enable":
22373
+ case "disable": {
22374
+ const url2 = flagOrAsk(args.url, "url");
22375
+ setPeerEnabled(db, url2, action === "enable");
22376
+ print({ ok: true }, () => `${action}d ${url2}`);
22377
+ return;
22378
+ }
22379
+ case "sync": {
22380
+ if (typeof args.url === "string" && args.url !== "") {
22381
+ const r3 = await syncPeer(db, args.url);
22382
+ print(r3, () => syncLine(r3));
22383
+ } else {
22384
+ const r3 = await syncAllPeers(db);
22385
+ print(r3, () => r3.length ? r3.map(syncLine).join(`
22386
+ `) : "(no enabled peers)");
22387
+ }
22388
+ return;
22389
+ }
22390
+ default:
22391
+ throw new Error(`unknown peer action '${action}' (add|code|list|rm|enable|disable|sync)`);
22392
+ }
22393
+ }
22394
+ function syncLine(o3) {
22395
+ return o3.ok ? `${o3.url}: pushed ${o3.pushed}, pulled ${o3.pulled}` : `${o3.url}: error \u2014 ${o3.error}`;
22396
+ }
22397
+ var maskToken = (t2) => t2.length > 10 ? `${t2.slice(0, 8)}\u2026` : t2;
22398
+ function grantView(g3) {
22399
+ return {
22400
+ token: maskToken(g3.token),
22401
+ peer_url: g3.peer_url ?? "(unknown)",
22402
+ node_id: g3.node_id ?? "",
22403
+ issued: iso2(g3.created_at)
22404
+ };
22405
+ }
22406
+ function grantDispatch(db, action, args) {
22407
+ switch (action) {
22408
+ case "list": {
22409
+ const rows = listGrants(db).map(grantView);
22410
+ print(rows, () => rows.length ? table(rows) : "(no issued credentials)");
22411
+ return;
22412
+ }
22413
+ case "revoke": {
22414
+ const token = flagOrAsk(args.token, "token");
22415
+ const n2 = revokeGrant(db, token);
22416
+ print({ revoked: n2 }, () => n2 > 0 ? `revoked ${n2} credential(s)` : "no matching credential");
22417
+ return;
22418
+ }
22419
+ default:
22420
+ throw new Error(`unknown grant action '${action}' (list|revoke)`);
22421
+ }
22422
+ }
22423
+ var secs = (ms) => `${Math.round(ms / 1000)}s`;
22424
+ function cancelled(v3) {
22425
+ if (R3(v3)) {
22426
+ ge("\u5DF2\u53D6\u6D88");
22427
+ return true;
22428
+ }
22429
+ return false;
22430
+ }
22431
+ function peerChoices(peers) {
22432
+ return peers.map((pr) => ({
22433
+ value: pr.url,
22434
+ label: pr.label ? `${pr.label} (${pr.url})` : pr.url,
22435
+ hint: pr.enabled ? "enabled" : "disabled"
22436
+ }));
22437
+ }
22438
+ function grantChoices(grants) {
22439
+ return grants.map((g3) => ({
22440
+ value: g3.token,
22441
+ label: maskToken(g3.token),
22442
+ hint: g3.peer_url ?? "(unknown)"
22443
+ }));
22444
+ }
22445
+ function validatePort(s2) {
22446
+ const n2 = Number(s2);
22447
+ if (!Number.isInteger(n2) || n2 <= 0 || n2 > 65535)
22448
+ return "\u7AEF\u53E3\u5FC5\u987B\u662F 1-65535 \u7684\u6574\u6570";
22449
+ return;
22450
+ }
22451
+ function validateInterval(s2) {
22452
+ return parseDuration(s2, -1) > 0 ? undefined : "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u65F6\u957F (\u5982 30s, 5m)";
22453
+ }
22454
+ var required2 = (v3) => v3 && v3.trim() ? undefined : "\u5FC5\u586B";
22455
+ async function serverWizard(db) {
22456
+ const working = { ...getServerConfig(db) };
22457
+ for (;; ) {
22458
+ const field = await Ee({
22459
+ message: "\u670D\u52A1\u5668\u8BBE\u7F6E",
22460
+ options: [
22461
+ { value: "host", label: "Host", hint: working.host },
22462
+ { value: "port", label: "Port", hint: String(working.port) },
22463
+ { value: "interval", label: "\u540C\u6B65\u95F4\u9694", hint: secs(working.syncIntervalMs) },
22464
+ { value: "auto", label: "Auto-sync", hint: working.autoSync ? "on" : "off" },
22465
+ { value: "save", label: "\u4FDD\u5B58\u5E76\u8FD4\u56DE" },
22466
+ { value: "back", label: "\u8FD4\u56DE (\u653E\u5F03\u4FEE\u6539)" }
22467
+ ]
22468
+ });
22469
+ if (cancelled(field) || field === "back")
22470
+ return;
22471
+ if (field === "save") {
22472
+ setServerConfig(db, working);
22473
+ Ce("restart --server to apply host/port/interval changes", "\u2713 \u5DF2\u4FDD\u5B58");
22474
+ return;
22475
+ }
22476
+ if (field === "host") {
22477
+ const v3 = await Re({ message: "Host", initialValue: working.host });
22478
+ if (cancelled(v3))
22479
+ return;
22480
+ working.host = v3;
22481
+ } else if (field === "port") {
22482
+ const v3 = await Re({ message: "Port", initialValue: String(working.port), validate: validatePort });
22483
+ if (cancelled(v3))
22484
+ return;
22485
+ working.port = Number(v3);
22486
+ } else if (field === "interval") {
22487
+ const v3 = await Re({ message: "\u540C\u6B65\u95F4\u9694 (\u5982 30s, 5m)", initialValue: secs(working.syncIntervalMs), validate: validateInterval });
22488
+ if (cancelled(v3))
22489
+ return;
22490
+ working.syncIntervalMs = parseDuration(v3, working.syncIntervalMs);
22491
+ } else if (field === "auto") {
22492
+ const v3 = await le2({ message: "\u542F\u7528 auto-sync?", initialValue: working.autoSync });
22493
+ if (cancelled(v3))
22494
+ return;
22495
+ working.autoSync = v3;
22496
+ }
22497
+ }
22498
+ }
22499
+ async function peerWizard(db) {
22500
+ const node = getNodeId(db);
22501
+ for (;; ) {
22502
+ const action = await Ee({
22503
+ message: "\u540C\u6B65\u8BBE\u5907",
22504
+ options: [
22505
+ { value: "add", label: "\u6DFB\u52A0\u8BBE\u5907", hint: "\u5BF9\u65B9\u5730\u5740 + \u914D\u5BF9\u7801" },
22506
+ { value: "code", label: "\u751F\u6210\u672C\u673A\u914D\u5BF9\u7801" },
22507
+ { value: "list", label: "\u5217\u51FA\u8BBE\u5907" },
22508
+ { value: "rm", label: "\u79FB\u9664\u8BBE\u5907" },
22509
+ { value: "toggle", label: "\u542F\u7528 / \u7981\u7528\u8BBE\u5907" },
22510
+ { value: "sync", label: "\u7ACB\u5373\u540C\u6B65\u5168\u90E8" },
22511
+ { value: "back", label: "\u8FD4\u56DE" }
22512
+ ]
22513
+ });
22514
+ if (cancelled(action) || action === "back")
22515
+ return;
22516
+ try {
22517
+ if (action === "add") {
22518
+ const url2 = await Re({ message: "\u5BF9\u65B9\u670D\u52A1\u5668\u5730\u5740", placeholder: "http://192.168.1.10:7777", validate: required2 });
22519
+ if (cancelled(url2))
22520
+ continue;
22521
+ const code = await Re({ message: "\u914D\u5BF9\u7801", validate: required2 });
22522
+ if (cancelled(code))
22523
+ continue;
22524
+ const selfUrl = await Re({ message: "\u672C\u673A\u53EF\u8FBE\u5730\u5740 (\u53EF\u9009, \u7559\u7A7A\u5219\u5355\u5411)" });
22525
+ if (cancelled(selfUrl))
22526
+ continue;
22527
+ const s2 = vt();
22528
+ s2.start("\u914D\u5BF9\u4E2D\u2026");
22529
+ try {
22530
+ const r3 = await performPairing(db, node, url2, code, selfUrl || undefined);
22531
+ const sync = await syncPeer(db, r3.url);
22532
+ s2.stop(`\u2713 \u5DF2\u914D\u5BF9 ${r3.url} (node ${r3.node_id})`);
22533
+ Ce(syncLine(sync), "\u540C\u6B65\u7ED3\u679C");
22534
+ } catch (e3) {
22535
+ s2.stop(`\u2717 ${e3.message}`);
22536
+ }
22537
+ } else if (action === "code") {
22538
+ const code = generatePairingCode(db);
22539
+ Ce(`\u914D\u5BF9\u7801: ${code.code}
22540
+ \u6709\u6548\u81F3: ${iso2(code.exp)}`, "\u672C\u673A\u914D\u5BF9\u7801");
22541
+ } else if (action === "list") {
22542
+ const rows = listPeers(db).map(peerView);
22543
+ Ce(rows.length ? table(rows) : "(\u65E0\u8BBE\u5907)", "\u8BBE\u5907\u5217\u8868");
22544
+ } else if (action === "rm") {
22545
+ const peers = listPeers(db);
22546
+ if (!peers.length) {
22547
+ Ce("(\u65E0\u8BBE\u5907)");
22548
+ continue;
22549
+ }
22550
+ const url2 = await Ee({ message: "\u9009\u62E9\u8981\u79FB\u9664\u7684\u8BBE\u5907", options: peerChoices(peers) });
22551
+ if (cancelled(url2))
22552
+ continue;
22553
+ Ce(removePeer(db, url2) ? `\u2713 \u5DF2\u79FB\u9664 ${url2}` : "\u672A\u627E\u5230\u8BE5\u8BBE\u5907");
22554
+ } else if (action === "toggle") {
22555
+ const peers = listPeers(db);
22556
+ if (!peers.length) {
22557
+ Ce("(\u65E0\u8BBE\u5907)");
22558
+ continue;
22559
+ }
22560
+ const url2 = await Ee({ message: "\u9009\u62E9\u8BBE\u5907", options: peerChoices(peers) });
22561
+ if (cancelled(url2))
22562
+ continue;
22563
+ const cur = peers.find((x4) => x4.url === url2);
22564
+ const en = await le2({ message: "\u542F\u7528\u8BE5\u8BBE\u5907?", initialValue: cur ? !!cur.enabled : true });
22565
+ if (cancelled(en))
22566
+ continue;
22567
+ setPeerEnabled(db, url2, en);
22568
+ Ce(`\u2713 ${en ? "\u5DF2\u542F\u7528" : "\u5DF2\u7981\u7528"} ${url2}`);
22569
+ } else if (action === "sync") {
22570
+ const s2 = vt();
22571
+ s2.start("\u540C\u6B65\u4E2D\u2026");
22572
+ const r3 = await syncAllPeers(db);
22573
+ s2.stop("\u540C\u6B65\u5B8C\u6210");
22574
+ Ce(r3.length ? r3.map(syncLine).join(`
22575
+ `) : "(\u65E0\u542F\u7528\u7684\u8BBE\u5907)", "\u540C\u6B65\u7ED3\u679C");
22576
+ }
22577
+ } catch (e3) {
22578
+ Ce(`\u2717 ${e3.message}`, "\u9519\u8BEF");
22579
+ }
22580
+ }
22581
+ }
22582
+ async function grantWizard(db) {
22583
+ for (;; ) {
22584
+ const grants = listGrants(db);
22585
+ Ce(grants.length ? table(grants.map(grantView)) : "(\u65E0\u5DF2\u7B7E\u53D1\u51ED\u636E)", "\u5DF2\u7B7E\u53D1\u51ED\u636E");
22586
+ if (!grants.length)
22587
+ return;
22588
+ const token = await Ee({
22589
+ message: "\u9009\u62E9\u8981\u540A\u9500\u7684\u51ED\u636E",
22590
+ options: [...grantChoices(grants), { value: "__back__", label: "\u8FD4\u56DE" }]
22591
+ });
22592
+ if (cancelled(token) || token === "__back__")
22593
+ return;
22594
+ const ok = await le2({ message: `\u786E\u8BA4\u540A\u9500 ${maskToken(token)}?`, initialValue: false });
22595
+ if (cancelled(ok) || !ok)
22596
+ continue;
22597
+ const n2 = revokeGrant(db, token);
22598
+ Ce(n2 > 0 ? `\u2713 \u5DF2\u540A\u9500 ${n2} \u6761` : "\u65E0\u5339\u914D\u51ED\u636E");
22599
+ }
22600
+ }
22601
+ async function wizard(db) {
22602
+ ye2("Metahub \u914D\u7F6E");
22603
+ for (;; ) {
22604
+ const choice = await Ee({
22605
+ message: "\u9009\u62E9\u64CD\u4F5C",
22606
+ options: [
22607
+ { value: "server", label: "\u670D\u52A1\u5668\u8BBE\u7F6E", hint: "host / port / \u540C\u6B65\u95F4\u9694 / auto-sync" },
22608
+ { value: "peers", label: "\u540C\u6B65\u8BBE\u5907", hint: "\u914D\u5BF9 / \u5217\u51FA / \u79FB\u9664 / \u540C\u6B65" },
22609
+ { value: "grants", label: "\u5DF2\u7B7E\u53D1\u51ED\u636E", hint: "\u5217\u51FA / \u540A\u9500" },
22610
+ { value: "exit", label: "\u9000\u51FA" }
22611
+ ]
22612
+ });
22613
+ if (cancelled(choice) || choice === "exit") {
22614
+ fe2("\u518D\u89C1");
22615
+ return;
22616
+ }
22617
+ if (choice === "server")
22618
+ await serverWizard(db);
22619
+ else if (choice === "peers")
22620
+ await peerWizard(db);
22621
+ else if (choice === "grants")
22622
+ await grantWizard(db);
22623
+ }
22624
+ }
22625
+ var config_default = defineCommand({
22626
+ meta: {
22627
+ name: "config",
22628
+ description: "Configure server + sync devices. Run with no args for an interactive wizard, or use flags directly (e.g. `config --port 7777`, `config peer add --url <url> --code <code>`)."
22629
+ },
22630
+ args: {
22631
+ section: { type: "positional", required: false, description: "show | set | peer | grant (omit for wizard)" },
22632
+ action: { type: "positional", required: false, description: "peer: add|code|list|rm|enable|disable|sync \xB7 grant: list|revoke" },
22633
+ host: { type: "string", description: "Bind address" },
22634
+ port: { type: "string", description: "Server port" },
22635
+ "sync-interval": { type: "string", description: "Auto-sync interval (e.g. 30s, 5m)" },
22636
+ "auto-sync": { type: "string", description: "Enable auto-sync timer (true/false)" },
22637
+ url: { type: "string", description: "Peer URL (peer add/rm/enable/disable/sync)" },
22638
+ code: { type: "string", description: "One-time pairing code (peer add)" },
22639
+ "self-url": { type: "string", description: "This device's reachable URL (peer add, optional)" },
22640
+ token: { type: "string", description: "Issued credential token or prefix (grant revoke)" }
22641
+ },
22642
+ run: guard(async (args) => {
22643
+ const db = openMetahub();
22644
+ const section = typeof args.section === "string" ? args.section : undefined;
22645
+ const hasSettingFlag = args.host != null || args.port != null || args["sync-interval"] != null || args["auto-sync"] != null;
22646
+ if (!section) {
22647
+ if (hasSettingFlag)
22648
+ return applySet(db, args);
22649
+ if (isTTY2())
22650
+ return wizard(db);
22651
+ return showConfig(db);
22652
+ }
22653
+ if (section === "show")
22654
+ return showConfig(db);
22655
+ if (section === "set")
22656
+ return applySet(db, args);
22657
+ if (section === "peer")
22658
+ return peerDispatch(db, args.action ?? "list", args);
22659
+ if (section === "grant")
22660
+ return grantDispatch(db, args.action ?? "list", args);
22661
+ throw new Error(`unknown config section '${section}' (show | set | peer | grant)`);
22662
+ })
22663
+ });
22664
+
22665
+ // src/core/sync/server.ts
22666
+ init_node();
22667
+
22668
+ // src/core/sync/routes.ts
22669
+ init_crdt();
22670
+
22671
+ // src/core/sync/webui-routes.ts
22672
+ var DatabaseSchema = exports_external.object({
22673
+ id: exports_external.string(),
22674
+ name: exports_external.string(),
22675
+ icon: exports_external.string().nullable(),
22676
+ created_hlc: exports_external.string()
22677
+ });
22678
+ var PropertySchema = exports_external.object({
22679
+ id: exports_external.string(),
22680
+ database_id: exports_external.string(),
22681
+ name: exports_external.string(),
22682
+ type: exports_external.string(),
22683
+ config: exports_external.any().nullable(),
22684
+ position: exports_external.number()
22685
+ });
22686
+ var RecordSchema = exports_external.object({
22687
+ id: exports_external.string(),
22688
+ database_id: exports_external.string(),
22689
+ values: exports_external.record(exports_external.string(), exports_external.any())
22690
+ });
22691
+ var MoveRecordReq = exports_external.object({
22692
+ target: exports_external.string(),
22693
+ where: exports_external.enum(["before", "after"])
22694
+ });
22695
+ var DocumentSummarySchema = exports_external.object({
22696
+ id: exports_external.string(),
22697
+ title: exports_external.string(),
22698
+ database_id: exports_external.string().nullable(),
22699
+ parent_id: exports_external.string().nullable(),
22700
+ created_hlc: exports_external.string()
22701
+ });
22702
+ var DocumentSchema = DocumentSummarySchema.extend({ body: exports_external.string().nullable() });
22703
+ var SearchHitSchema = exports_external.object({
22704
+ type: exports_external.string(),
22705
+ id: exports_external.string(),
22706
+ database_id: exports_external.string().nullable(),
22707
+ title: exports_external.string().optional(),
22708
+ snippet: exports_external.string()
22709
+ });
22710
+ var OkSchema = exports_external.object({ ok: exports_external.boolean() });
22711
+ var CreateDatabaseReq = exports_external.object({ name: exports_external.string(), icon: exports_external.string().optional() });
22712
+ var UpdateDatabaseReq = exports_external.object({
22713
+ name: exports_external.string().optional(),
22714
+ icon: exports_external.string().nullable().optional()
22715
+ });
22716
+ var CreatePropertyReq = exports_external.object({
22717
+ db: exports_external.string(),
22718
+ name: exports_external.string(),
22719
+ type: exports_external.string(),
22720
+ config: exports_external.any().optional()
22721
+ });
22722
+ var UpdatePropertyReq = exports_external.object({
22723
+ name: exports_external.string().optional(),
22724
+ type: exports_external.string().optional(),
22725
+ config: exports_external.any().optional(),
22726
+ position: exports_external.number().optional()
22727
+ });
22728
+ var SetWidthReq = exports_external.object({ width: exports_external.number() });
22729
+ var RecordValuesReq = exports_external.record(exports_external.string(), exports_external.any());
22730
+ var CreateDocumentReq = exports_external.object({
22731
+ title: exports_external.string(),
22732
+ body: exports_external.string().optional(),
22733
+ database_id: exports_external.string().optional(),
22734
+ parent_id: exports_external.string().optional()
22735
+ });
22736
+ var UpdateDocumentReq = exports_external.object({
22737
+ title: exports_external.string().optional(),
22738
+ body: exports_external.string().optional(),
22739
+ parent_id: exports_external.string().nullable().optional()
22740
+ });
22741
+ function need(req, key) {
22742
+ const v3 = new URL(req.url).searchParams.get(key);
22743
+ if (!v3)
22744
+ throw new Error(`missing query param: ${key}`);
22745
+ return v3;
22746
+ }
22747
+ function opt(req, key) {
22748
+ return new URL(req.url).searchParams.get(key) ?? undefined;
20144
22749
  }
20145
22750
  function handle(fn) {
20146
22751
  return async (req, ctx) => {
@@ -20149,12 +22754,20 @@ function handle(fn) {
20149
22754
  if (out instanceof Response)
20150
22755
  return out;
20151
22756
  return Response.json(out ?? null);
20152
- } catch (e2) {
20153
- return Response.json({ error: e2.message }, { status: 400 });
22757
+ } catch (e3) {
22758
+ return Response.json({ error: e3.message }, { status: 400 });
20154
22759
  }
20155
22760
  };
20156
22761
  }
22762
+ var VersionSchema = exports_external.object({ version: exports_external.string() });
20157
22763
  var webuiRoutes = [
22764
+ {
22765
+ method: "GET",
22766
+ path: "/api/version",
22767
+ summary: "Version of the running core (sidecar). Used by the desktop app's settings footer.",
22768
+ response: VersionSchema,
22769
+ handler: () => Response.json({ version: package_default.version })
22770
+ },
20158
22771
  {
20159
22772
  method: "GET",
20160
22773
  path: "/api/databases",
@@ -20304,9 +22917,9 @@ var webuiRoutes = [
20304
22917
  {
20305
22918
  method: "GET",
20306
22919
  path: "/api/documents",
20307
- summary: "List documents. Optional query: ?db=<id>",
22920
+ summary: "List documents. Optional query: ?db=<id> or ?parent=<id>",
20308
22921
  response: exports_external.array(DocumentSummarySchema),
20309
- handler: handle((req, { db }) => listDocuments(db, { database_id: opt(req, "db") }))
22922
+ handler: handle((req, { db }) => listDocuments(db, { database_id: opt(req, "db"), parent_id: opt(req, "parent") }))
20310
22923
  },
20311
22924
  {
20312
22925
  method: "POST",
@@ -20375,17 +22988,17 @@ var SiteFileSchema = exports_external.object({
20375
22988
  encoding: exports_external.string()
20376
22989
  });
20377
22990
  function need2(req, key) {
20378
- const v2 = new URL(req.url).searchParams.get(key);
20379
- if (!v2)
22991
+ const v3 = new URL(req.url).searchParams.get(key);
22992
+ if (!v3)
20380
22993
  throw new Error(`missing query param: ${key}`);
20381
- return v2;
22994
+ return v3;
20382
22995
  }
20383
22996
  function handle2(fn) {
20384
22997
  return async (req, ctx) => {
20385
22998
  try {
20386
22999
  return Response.json(await fn(req, ctx) ?? null);
20387
- } catch (e2) {
20388
- return Response.json({ error: e2.message }, { status: 400 });
23000
+ } catch (e3) {
23001
+ return Response.json({ error: e3.message }, { status: 400 });
20389
23002
  }
20390
23003
  };
20391
23004
  }
@@ -20406,6 +23019,146 @@ var sitesRoutes = [
20406
23019
  }
20407
23020
  ];
20408
23021
 
23022
+ // src/core/sync/peers-routes.ts
23023
+ var PeerSchema = exports_external.object({
23024
+ url: exports_external.string(),
23025
+ pull_cursor: exports_external.number(),
23026
+ push_cursor: exports_external.number(),
23027
+ token: exports_external.string().nullable(),
23028
+ label: exports_external.string().nullable(),
23029
+ node_id: exports_external.string().nullable(),
23030
+ enabled: exports_external.number(),
23031
+ last_sync_at: exports_external.number().nullable(),
23032
+ last_status: exports_external.string().nullable(),
23033
+ last_error: exports_external.string().nullable()
23034
+ });
23035
+ var PairingCodeSchema = exports_external.object({ code: exports_external.string(), exp: exports_external.number() });
23036
+ var AddPeerReq = exports_external.object({
23037
+ url: exports_external.string(),
23038
+ code: exports_external.string(),
23039
+ self_url: exports_external.string().optional()
23040
+ });
23041
+ var AddPeerRes = exports_external.object({ node_id: exports_external.string(), url: exports_external.string() });
23042
+ var UpdatePeerReq = exports_external.object({
23043
+ enabled: exports_external.boolean().optional(),
23044
+ label: exports_external.string().optional()
23045
+ });
23046
+ var PeerSyncRes = exports_external.object({
23047
+ url: exports_external.string(),
23048
+ ok: exports_external.boolean(),
23049
+ pushed: exports_external.number().optional(),
23050
+ pulled: exports_external.number().optional(),
23051
+ error: exports_external.string().optional()
23052
+ });
23053
+ var GrantSchema = exports_external.object({
23054
+ token: exports_external.string(),
23055
+ peer_url: exports_external.string().nullable(),
23056
+ node_id: exports_external.string().nullable(),
23057
+ created_at: exports_external.number().nullable()
23058
+ });
23059
+ var RevokeRes = exports_external.object({ revoked: exports_external.number() });
23060
+ var OkSchema2 = exports_external.object({ ok: exports_external.boolean() });
23061
+ function need3(req, key) {
23062
+ const v3 = new URL(req.url).searchParams.get(key);
23063
+ if (!v3)
23064
+ throw new Error(`missing query param: ${key}`);
23065
+ return v3;
23066
+ }
23067
+ function handle3(fn) {
23068
+ return async (req, ctx) => {
23069
+ try {
23070
+ const out = await fn(req, ctx);
23071
+ if (out instanceof Response)
23072
+ return out;
23073
+ return Response.json(out ?? null);
23074
+ } catch (e3) {
23075
+ return Response.json({ error: e3.message }, { status: 400 });
23076
+ }
23077
+ };
23078
+ }
23079
+ var peersRoutes = [
23080
+ {
23081
+ method: "POST",
23082
+ path: PAIR_PATH,
23083
+ summary: "Pairing handshake: redeem a one-time code and exchange credentials",
23084
+ request: PairRequestSchema,
23085
+ response: PairResponseSchema,
23086
+ handler: handle3(async (req, { db, node }) => {
23087
+ const body = await req.json();
23088
+ return handlePairRequest(db, node, body);
23089
+ })
23090
+ },
23091
+ {
23092
+ method: "POST",
23093
+ path: "/api/pair/new",
23094
+ summary: "Mint a one-time pairing code for another device to redeem",
23095
+ response: PairingCodeSchema,
23096
+ handler: handle3((_req, { db }) => generatePairingCode(db))
23097
+ },
23098
+ {
23099
+ method: "POST",
23100
+ path: "/api/peers/pair",
23101
+ summary: "Pair this server with a remote one using its one-time code",
23102
+ request: AddPeerReq,
23103
+ response: AddPeerRes,
23104
+ handler: handle3(async (req, { db, node }) => {
23105
+ const body = await req.json();
23106
+ return performPairing(db, node, body.url, body.code, body.self_url);
23107
+ })
23108
+ },
23109
+ {
23110
+ method: "GET",
23111
+ path: "/api/peers",
23112
+ summary: "List configured sync peers with status",
23113
+ response: exports_external.array(PeerSchema),
23114
+ handler: handle3((_req, { db }) => listPeers(db))
23115
+ },
23116
+ {
23117
+ method: "PATCH",
23118
+ path: "/api/peer",
23119
+ summary: "Update a peer (enable/disable or label). Query: ?url=<url>",
23120
+ request: UpdatePeerReq,
23121
+ response: OkSchema2,
23122
+ handler: handle3(async (req, { db }) => {
23123
+ const url2 = need3(req, "url");
23124
+ const body = await req.json();
23125
+ if (body.enabled != null)
23126
+ setPeerEnabled(db, url2, body.enabled);
23127
+ if (body.label != null)
23128
+ setPeerLabel(db, url2, body.label);
23129
+ return { ok: true };
23130
+ })
23131
+ },
23132
+ {
23133
+ method: "DELETE",
23134
+ path: "/api/peer",
23135
+ summary: "Remove a sync peer. Query: ?url=<url>",
23136
+ response: OkSchema2,
23137
+ handler: handle3((req, { db }) => ({ ok: removePeer(db, need3(req, "url")) }))
23138
+ },
23139
+ {
23140
+ method: "POST",
23141
+ path: "/api/peer/sync",
23142
+ summary: "Manually sync once with a peer. Query: ?url=<url>",
23143
+ response: PeerSyncRes,
23144
+ handler: handle3((req, { db }) => syncPeer(db, need3(req, "url")))
23145
+ },
23146
+ {
23147
+ method: "GET",
23148
+ path: "/api/grants",
23149
+ summary: "List credentials this server issued and accepts on /sync (inbound)",
23150
+ response: exports_external.array(GrantSchema),
23151
+ handler: handle3((_req, { db }) => listGrants(db))
23152
+ },
23153
+ {
23154
+ method: "DELETE",
23155
+ path: "/api/grant",
23156
+ summary: "Revoke an issued credential by token or prefix. Query: ?token=<token>",
23157
+ response: RevokeRes,
23158
+ handler: handle3((req, { db }) => ({ revoked: revokeGrant(db, need3(req, "token")) }))
23159
+ }
23160
+ ];
23161
+
20409
23162
  // src/core/sync/routes.ts
20410
23163
  var syncRoutes = [
20411
23164
  {
@@ -20431,7 +23184,7 @@ var syncRoutes = [
20431
23184
  }
20432
23185
  }
20433
23186
  ];
20434
- var routes = [...syncRoutes, ...webuiRoutes, ...sitesRoutes];
23187
+ var routes = [...syncRoutes, ...webuiRoutes, ...sitesRoutes, ...peersRoutes];
20435
23188
 
20436
23189
  // src/core/sync/openapi.ts
20437
23190
  function buildOpenApi(version2) {
@@ -20468,9 +23221,9 @@ function cookieToken(req) {
20468
23221
  if (!header)
20469
23222
  return null;
20470
23223
  for (const part of header.split(";")) {
20471
- const [k2, ...v2] = part.trim().split("=");
20472
- if (k2 === COOKIE)
20473
- return decodeURIComponent(v2.join("="));
23224
+ const [k3, ...v3] = part.trim().split("=");
23225
+ if (k3 === COOKIE)
23226
+ return decodeURIComponent(v3.join("="));
20474
23227
  }
20475
23228
  return null;
20476
23229
  }
@@ -20500,6 +23253,17 @@ function hasValidToken(req, url2, cfg) {
20500
23253
  return true;
20501
23254
  return extractToken(req, url2) === cur.token;
20502
23255
  }
23256
+ function acceptsSyncToken(req, url2, cfg, db) {
23257
+ if (!authActive(cfg))
23258
+ return true;
23259
+ const presented = extractToken(req, url2);
23260
+ if (!presented)
23261
+ return false;
23262
+ const cur = activeToken(cfg);
23263
+ if (cur && presented === cur.token)
23264
+ return true;
23265
+ return isAcceptedGrant(db, presented);
23266
+ }
20503
23267
  function renewToken(req, url2, cfg) {
20504
23268
  if (cfg.debug || cfg.staticToken != null || !cfg.db)
20505
23269
  return null;
@@ -20615,8 +23379,8 @@ function injectShim(html) {
20615
23379
  async function withShim(res, cfg) {
20616
23380
  if (!authActive(cfg))
20617
23381
  return res;
20618
- const ct = res.headers.get("content-type") ?? "";
20619
- if (!ct.includes("text/html"))
23382
+ const ct3 = res.headers.get("content-type") ?? "";
23383
+ if (!ct3.includes("text/html"))
20620
23384
  return res;
20621
23385
  const html = injectShim(await res.text());
20622
23386
  const headers = new Headers(res.headers);
@@ -20627,6 +23391,16 @@ function unauthorized() {
20627
23391
  }
20628
23392
 
20629
23393
  // src/core/sync/server.ts
23394
+ class PortInUseError extends Error {
23395
+ port;
23396
+ host;
23397
+ constructor(port, host) {
23398
+ super(`port ${port} is already in use \u2014 another mh server may be running. ` + `Stop it, or start on a free port with --port <n>.`);
23399
+ this.port = port;
23400
+ this.host = host;
23401
+ this.name = "PortInUseError";
23402
+ }
23403
+ }
20630
23404
  function scalarHtml(specUrl) {
20631
23405
  return `<!doctype html><html><head>
20632
23406
  <meta charset="utf-8"><title>Metahub API</title>
@@ -20639,6 +23413,11 @@ function startServer(opts = {}) {
20639
23413
  const db = openMetahub();
20640
23414
  const node = getNodeId(db);
20641
23415
  const ctx = { db, node };
23416
+ const cfg = getServerConfig(db);
23417
+ const port = opts.port ?? cfg.port;
23418
+ const host = opts.host ?? cfg.host;
23419
+ const syncIntervalMs = opts.syncIntervalMs ?? cfg.syncIntervalMs;
23420
+ const autoSync = opts.autoSync ?? cfg.autoSync;
20642
23421
  const auth = opts.debug ? { debug: true, staticToken: null, db: null, ttlMs: 0, graceMs: 0 } : opts.token ? { debug: false, staticToken: opts.token, db: null, ttlMs: 0, graceMs: 0 } : {
20643
23422
  debug: false,
20644
23423
  staticToken: null,
@@ -20646,53 +23425,210 @@ function startServer(opts = {}) {
20646
23425
  ttlMs: DEFAULT_TTL_MS,
20647
23426
  graceMs: DEFAULT_GRACE_MS
20648
23427
  };
20649
- const server = Bun.serve({
20650
- port: opts.port ?? 7777,
20651
- hostname: opts.host ?? "127.0.0.1",
20652
- async fetch(req) {
20653
- const url2 = new URL(req.url);
20654
- const exempt = url2.pathname === SYNC_PATH || url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH;
20655
- if (!exempt && !hasValidToken(req, url2, auth)) {
20656
- return wantsHtml(req) ? new Response(unlockPage(), { headers: HTML_HEADERS }) : unauthorized();
20657
- }
20658
- if (url2.pathname === RENEW_PATH) {
20659
- const r3 = renewToken(req, url2, auth);
20660
- return r3 ? Response.json(r3) : unauthorized();
20661
- }
20662
- if (req.method === "GET" && url2.pathname === "/docs.json") {
20663
- return Response.json(buildOpenApi(package_default.version));
20664
- }
20665
- if (req.method === "GET" && url2.pathname === "/docs") {
20666
- return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
20667
- }
20668
- if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
20669
- const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
20670
- const res = await serveWebui2(req);
20671
- if (res)
20672
- return withShim(res, auth);
20673
- }
20674
- if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
20675
- const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
20676
- const res = await serveSite2(req, ctx);
20677
- if (res)
20678
- return withShim(res, auth);
20679
- }
20680
- const route = routes.find((r3) => r3.method === req.method && r3.path === url2.pathname);
20681
- if (route)
20682
- return route.handler(req, ctx);
20683
- return new Response("not found", { status: 404 });
23428
+ let server;
23429
+ try {
23430
+ server = Bun.serve({
23431
+ port,
23432
+ hostname: host,
23433
+ async fetch(req) {
23434
+ const url2 = new URL(req.url);
23435
+ if (url2.pathname === SYNC_PATH) {
23436
+ if (!acceptsSyncToken(req, url2, auth, db))
23437
+ return unauthorized();
23438
+ } else {
23439
+ const exempt = url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH || url2.pathname === PAIR_PATH;
23440
+ if (!exempt && !hasValidToken(req, url2, auth)) {
23441
+ return wantsHtml(req) ? new Response(unlockPage(), { headers: HTML_HEADERS }) : unauthorized();
23442
+ }
23443
+ }
23444
+ if (url2.pathname === RENEW_PATH) {
23445
+ const r3 = renewToken(req, url2, auth);
23446
+ return r3 ? Response.json(r3) : unauthorized();
23447
+ }
23448
+ if (req.method === "GET" && url2.pathname === "/docs.json") {
23449
+ return Response.json(buildOpenApi(package_default.version));
23450
+ }
23451
+ if (req.method === "GET" && url2.pathname === "/docs") {
23452
+ return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
23453
+ }
23454
+ if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
23455
+ const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
23456
+ const res = await serveWebui2(req);
23457
+ if (res)
23458
+ return withShim(res, auth);
23459
+ }
23460
+ if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
23461
+ const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
23462
+ const res = await serveSite2(req, ctx);
23463
+ if (res)
23464
+ return withShim(res, auth);
23465
+ }
23466
+ const route = routes.find((r3) => r3.method === req.method && r3.path === url2.pathname);
23467
+ if (route)
23468
+ return route.handler(req, ctx);
23469
+ return new Response("not found", { status: 404 });
23470
+ }
23471
+ });
23472
+ } catch (e3) {
23473
+ const code = e3?.code;
23474
+ if (code === "EADDRINUSE") {
23475
+ throw new PortInUseError(port, host);
20684
23476
  }
20685
- });
23477
+ if (code === "EACCES") {
23478
+ throw new Error(`permission denied binding ${host}:${port} \u2014 ports below 1024 need elevated privileges; pick a higher port with --port`);
23479
+ }
23480
+ throw e3;
23481
+ }
23482
+ let timer = null;
23483
+ if (autoSync && syncIntervalMs > 0) {
23484
+ timer = setInterval(() => {
23485
+ syncAllPeers(db).catch(() => {});
23486
+ }, syncIntervalMs);
23487
+ timer.unref?.();
23488
+ }
20686
23489
  const active = activeToken(auth);
23490
+ const authMode = opts.debug ? "disabled" : opts.token ? "static" : "managed";
20687
23491
  return {
20688
23492
  server,
20689
23493
  node,
20690
- port: server.port ?? opts.port ?? 7777,
23494
+ port: server.port ?? port,
23495
+ host,
20691
23496
  token: active?.token ?? null,
20692
- exp: active?.exp ?? null
23497
+ exp: active?.exp ?? null,
23498
+ authMode,
23499
+ autoSync: Boolean(autoSync && syncIntervalMs > 0),
23500
+ syncIntervalMs,
23501
+ stop() {
23502
+ if (timer)
23503
+ clearInterval(timer);
23504
+ server.stop();
23505
+ }
20693
23506
  };
20694
23507
  }
20695
23508
 
23509
+ // src/cli/banner.ts
23510
+ var ANSI = /\x1b\[[0-9;]*m/g;
23511
+ function supportsColor() {
23512
+ return Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
23513
+ }
23514
+ function visibleWidth(s2) {
23515
+ return s2.replace(ANSI, "").length;
23516
+ }
23517
+ var CODES = {
23518
+ reset: "\x1B[0m",
23519
+ dim: "\x1B[2m",
23520
+ bold: "\x1B[1m",
23521
+ green: "\x1B[32m",
23522
+ cyan: "\x1B[36m",
23523
+ yellow: "\x1B[33m",
23524
+ magenta: "\x1B[35m",
23525
+ red: "\x1B[31m"
23526
+ };
23527
+ function makePalette(on) {
23528
+ const wrap = (code) => (s2) => on ? `${code}${s2}${CODES.reset}` : s2;
23529
+ return {
23530
+ reset: (s2) => s2,
23531
+ dim: wrap(CODES.dim),
23532
+ bold: wrap(CODES.bold),
23533
+ green: wrap(CODES.green),
23534
+ cyan: wrap(CODES.cyan),
23535
+ yellow: wrap(CODES.yellow),
23536
+ magenta: wrap(CODES.magenta),
23537
+ red: wrap(CODES.red)
23538
+ };
23539
+ }
23540
+ var MONTHS = [
23541
+ "Jan",
23542
+ "Feb",
23543
+ "Mar",
23544
+ "Apr",
23545
+ "May",
23546
+ "Jun",
23547
+ "Jul",
23548
+ "Aug",
23549
+ "Sep",
23550
+ "Oct",
23551
+ "Nov",
23552
+ "Dec"
23553
+ ];
23554
+ function humanizeExpiry(exp, now = Date.now()) {
23555
+ if (exp == null)
23556
+ return "";
23557
+ if (!Number.isFinite(exp))
23558
+ return "never expires";
23559
+ const ms = exp - now;
23560
+ if (ms <= 0)
23561
+ return "expired";
23562
+ const d2 = new Date(exp);
23563
+ const date5 = `${MONTHS[d2.getMonth()]} ${d2.getDate()}`;
23564
+ const mins = Math.round(ms / 60000);
23565
+ let rel;
23566
+ if (mins < 60)
23567
+ rel = `${mins}m`;
23568
+ else if (mins < 60 * 24)
23569
+ rel = `${Math.round(mins / 60)}h`;
23570
+ else
23571
+ rel = `${Math.round(mins / (60 * 24))} days`;
23572
+ return `expires in ${rel} \xB7 ${date5}`;
23573
+ }
23574
+ function formatSync(autoSync, ms) {
23575
+ if (!autoSync || ms <= 0)
23576
+ return "auto-sync off";
23577
+ const secs2 = Math.round(ms / 1000);
23578
+ const human = secs2 % 3600 === 0 ? `${secs2 / 3600}h` : secs2 % 60 === 0 ? `${secs2 / 60}m` : `${secs2}s`;
23579
+ return `every ${human}`;
23580
+ }
23581
+ function isLoopback(host) {
23582
+ return host === "127.0.0.1" || host === "::1" || host === "localhost" || host === "::";
23583
+ }
23584
+ function renderStartupBanner(info) {
23585
+ const on = info.color ?? supportsColor();
23586
+ const c3 = makePalette(on);
23587
+ const LABEL_W = 7;
23588
+ const label = (t2) => c3.dim(t2.padEnd(LABEL_W));
23589
+ const rows = [];
23590
+ const push = (plain, painted) => rows.push([plain, painted]);
23591
+ push(`\u25CF listening ${info.host}:${info.port}`, `${c3.green("\u25CF")} ${c3.dim("listening")} ${c3.bold(`${info.host}:${info.port}`)}`);
23592
+ if (!isLoopback(info.host)) {
23593
+ push("\u26A0 reachable on your network", c3.yellow("\u26A0 reachable on your network"));
23594
+ }
23595
+ push("", "");
23596
+ push(`${"WebUI".padEnd(LABEL_W)}${info.webuiUrl}`, `${label("WebUI")}${c3.cyan(info.webuiUrl)}`);
23597
+ push(`${"Docs".padEnd(LABEL_W)}${info.docsUrl}`, `${label("Docs")}${c3.cyan(info.docsUrl)}`);
23598
+ if (info.authMode === "disabled" || !info.token) {
23599
+ push(`${"Auth".padEnd(LABEL_W)}disabled (--debug)`, `${label("Auth")}${c3.red("disabled")} ${c3.dim("(--debug)")}`);
23600
+ } else {
23601
+ push(`${"Token".padEnd(LABEL_W)}${info.token}`, `${label("Token")}${c3.yellow(info.token)}`);
23602
+ const exp = humanizeExpiry(info.exp);
23603
+ if (exp)
23604
+ push(`${" ".repeat(LABEL_W)}${exp}`, `${" ".repeat(LABEL_W)}${c3.dim(exp)}`);
23605
+ }
23606
+ const sync = formatSync(info.autoSync, info.syncIntervalMs);
23607
+ const syncLabel = info.autoSync && info.syncIntervalMs > 0 ? `sync ${sync}` : sync;
23608
+ push(`${"Node".padEnd(LABEL_W)}${info.node} \xB7 ${syncLabel}`, `${label("Node")}${info.node} ${c3.dim("\xB7")} ${c3.dim(syncLabel)}`);
23609
+ const PAD = 2;
23610
+ const title = "metahub";
23611
+ const ver = `v${info.version}`;
23612
+ const footer = "Ctrl-C to stop";
23613
+ const contentW = Math.max(...rows.map(([plain]) => plain.length), title.length + ver.length + 6, footer.length + 4);
23614
+ const innerW = contentW + PAD * 2;
23615
+ const totalW = innerW + 2;
23616
+ const border = (lc, left, right, rc) => {
23617
+ const leftSeg = left ? `\u2500 ${left} ` : "\u2500";
23618
+ const rightSeg = right ? ` ${right} \u2500` : "\u2500";
23619
+ const dashes = totalW - 2 - visibleWidth(leftSeg) - visibleWidth(rightSeg);
23620
+ return `${lc}${leftSeg}${"\u2500".repeat(Math.max(0, dashes))}${rightSeg}${rc}`;
23621
+ };
23622
+ const top = border("\u256D", c3.bold(c3.magenta(title)), c3.dim(ver), "\u256E");
23623
+ const bottom = border("\u2570", c3.dim(footer), "", "\u256F");
23624
+ const body = rows.map(([plain, painted]) => {
23625
+ const fill = innerW - PAD * 2 - plain.length;
23626
+ return `\u2502${" ".repeat(PAD)}${painted}${" ".repeat(Math.max(0, fill) + PAD)}\u2502`;
23627
+ });
23628
+ return [top, ...body, bottom].join(`
23629
+ `);
23630
+ }
23631
+
20696
23632
  // src/cli/help.ts
20697
23633
  var ROOT_GUIDE = `metahub \u2014 local knowledge base for AI agents
20698
23634
  Notion-like typed databases (tables + rows) and markdown documents, stored in
@@ -20886,7 +23822,7 @@ async function showUsage2(cmd, parent) {
20886
23822
 
20887
23823
  EXAMPLES
20888
23824
 
20889
- ` + examples.map((e2) => " " + e2).join(`
23825
+ ` + examples.map((e3) => " " + e3).join(`
20890
23826
  `);
20891
23827
  }
20892
23828
  console.log(usage + `
@@ -20905,7 +23841,9 @@ var main = defineCommand({
20905
23841
  port: { type: "string", description: "Server port (with --server)", default: "7777" },
20906
23842
  host: { type: "string", description: "Bind address (with --server)", default: "127.0.0.1" },
20907
23843
  debug: { type: "boolean", description: "Disable auth on the server (with --server)" },
20908
- token: { type: "string", description: "Server auth token; generated if omitted (with --server)" }
23844
+ token: { type: "string", description: "Server auth token; generated if omitted (with --server)" },
23845
+ "sync-interval": { type: "string", description: "Auto-sync interval, e.g. 30s/5m (with --server)" },
23846
+ "no-auto-sync": { type: "boolean", description: "Disable the auto-sync timer (with --server)" }
20909
23847
  },
20910
23848
  subCommands: {
20911
23849
  init: init_default,
@@ -20923,7 +23861,10 @@ var main = defineCommand({
20923
23861
  __complete: complete,
20924
23862
  sync: sync_default,
20925
23863
  snapshot: snapshot_default,
20926
- restore: restore_default
23864
+ restore: restore_default,
23865
+ doctor: doctor_default,
23866
+ repair: repair_default,
23867
+ config: config_default
20927
23868
  }
20928
23869
  });
20929
23870
  function flagValue(argv2, name) {
@@ -20933,29 +23874,52 @@ function flagValue(argv2, name) {
20933
23874
  const a2 = argv2[i2];
20934
23875
  return a2.includes("=") ? a2.slice(a2.indexOf("=") + 1) : argv2[i2 + 1];
20935
23876
  }
20936
- function parsePort(argv2) {
20937
- return Number(flagValue(argv2, "port")) || 7777;
20938
- }
20939
23877
  var argv2 = process.argv.slice(2);
20940
23878
  if (argv2.includes("--server")) {
20941
- const s2 = startServer({
20942
- port: parsePort(argv2),
20943
- host: flagValue(argv2, "host"),
20944
- debug: argv2.includes("--debug"),
20945
- token: flagValue(argv2, "token") ?? process.env.METAHUB_TOKEN
20946
- });
20947
- const expNote = s2.token && s2.exp != null && Number.isFinite(s2.exp) ? ` (expires ${new Date(s2.exp).toISOString()})` : "";
23879
+ const portFlag = flagValue(argv2, "port");
23880
+ const intervalFlag = flagValue(argv2, "sync-interval");
23881
+ let s2;
23882
+ try {
23883
+ s2 = startServer({
23884
+ port: portFlag != null ? Number(portFlag) : undefined,
23885
+ host: flagValue(argv2, "host"),
23886
+ debug: argv2.includes("--debug"),
23887
+ token: flagValue(argv2, "token") ?? process.env.METAHUB_TOKEN,
23888
+ syncIntervalMs: intervalFlag != null ? parseDuration(intervalFlag, 30000) : undefined,
23889
+ autoSync: argv2.includes("--no-auto-sync") ? false : undefined
23890
+ });
23891
+ } catch (e3) {
23892
+ fail(e3 instanceof Error ? e3.message : String(e3), e3 instanceof PortInUseError ? 98 : 1);
23893
+ }
23894
+ const webui = `http://localhost:${s2.port}`;
23895
+ const docs = `${webui}/docs`;
20948
23896
  print({
20949
23897
  server: "listening",
20950
23898
  port: s2.port,
23899
+ host: s2.host,
20951
23900
  nodeId: s2.node,
20952
23901
  docs: `/docs`,
23902
+ webui,
23903
+ authMode: s2.authMode,
20953
23904
  token: s2.token,
20954
- exp: s2.exp != null && Number.isFinite(s2.exp) ? s2.exp : null
20955
- }, () => `metahub sync server on :${s2.port} (node ${s2.node}) \u2014 docs at http://localhost:${s2.port}/docs
20956
- ` + (s2.token ? `auth token: ${s2.token}${expNote}` : `auth: disabled (--debug)`));
23905
+ exp: s2.exp != null && Number.isFinite(s2.exp) ? s2.exp : null,
23906
+ autoSync: s2.autoSync,
23907
+ syncIntervalMs: s2.syncIntervalMs
23908
+ }, () => renderStartupBanner({
23909
+ version: package_default.version,
23910
+ host: s2.host,
23911
+ port: s2.port,
23912
+ webuiUrl: webui,
23913
+ docsUrl: docs,
23914
+ authMode: s2.authMode,
23915
+ token: s2.token,
23916
+ exp: s2.exp,
23917
+ node: s2.node,
23918
+ autoSync: s2.autoSync,
23919
+ syncIntervalMs: s2.syncIntervalMs
23920
+ }));
20957
23921
  } else {
20958
23922
  runMain(main, { showUsage: showUsage2 });
20959
23923
  }
20960
23924
 
20961
- //# debugId=A1E4C7B52077935E64756E2164756E21
23925
+ //# debugId=74D4BFCB8CE1AB2064756E2164756E21