@tensorix/metahub 0.1.3 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +235 -0
- package/README.md +87 -147
- package/README.zh-CN.md +127 -0
- package/dist/cli.js +882 -142
- package/dist/cli.js.map +21 -17
- package/dist/core/blocks.d.ts +23 -4
- package/dist/core/blocks.d.ts.map +1 -1
- package/dist/core/databases.d.ts.map +1 -1
- package/dist/core/db.d.ts +6 -0
- package/dist/core/db.d.ts.map +1 -1
- package/dist/core/documents.d.ts.map +1 -1
- package/dist/core/integrity.d.ts +32 -0
- package/dist/core/integrity.d.ts.map +1 -0
- package/dist/core/properties.d.ts.map +1 -1
- package/dist/core/schema.d.ts +1 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/search.d.ts +2 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/snapshot.d.ts +2 -0
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/sync/server.d.ts +14 -0
- package/dist/core/sync/server.d.ts.map +1 -1
- package/dist/core/sync/webui-routes.d.ts.map +1 -1
- package/dist/core/sync/webui.d.ts +11 -0
- package/dist/core/sync/webui.d.ts.map +1 -1
- package/dist/index.js +406 -125
- package/dist/index.js.map +13 -13
- package/dist/webui.js +32 -33
- package/package.json +7 -3
package/dist/cli.js
CHANGED
|
@@ -891,7 +891,7 @@ function observeHlc(db, node, remote, now = Date.now()) {
|
|
|
891
891
|
}
|
|
892
892
|
|
|
893
893
|
// src/core/blocks.ts
|
|
894
|
-
function
|
|
894
|
+
function parseDocBlocks(md) {
|
|
895
895
|
if (!md)
|
|
896
896
|
return [];
|
|
897
897
|
const lines = md.replace(/\r\n?/g, `
|
|
@@ -901,24 +901,24 @@ function parseBlocks(md) {
|
|
|
901
901
|
let cur = [];
|
|
902
902
|
let fenceChar = null;
|
|
903
903
|
let fenceLen = 0;
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
cur.
|
|
907
|
-
|
|
908
|
-
cur
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
904
|
+
const closeBlock = () => {
|
|
905
|
+
if (cur.length) {
|
|
906
|
+
blocks.push({ text: cur.join(`
|
|
907
|
+
`), blankAfter: 0 });
|
|
908
|
+
cur = [];
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
const onBlank = () => {
|
|
912
|
+
closeBlock();
|
|
913
|
+
if (blocks.length)
|
|
914
|
+
blocks[blocks.length - 1].blankAfter++;
|
|
913
915
|
};
|
|
914
916
|
for (const raw of lines) {
|
|
915
917
|
if (fenceChar) {
|
|
916
918
|
cur.push(raw);
|
|
917
919
|
const m2 = raw.match(/^\s*(`{3,}|~{3,})\s*$/);
|
|
918
920
|
if (m2 && m2[1][0] === fenceChar && m2[1].length >= fenceLen) {
|
|
919
|
-
|
|
920
|
-
`));
|
|
921
|
-
cur = [];
|
|
921
|
+
closeBlock();
|
|
922
922
|
fenceChar = null;
|
|
923
923
|
fenceLen = 0;
|
|
924
924
|
}
|
|
@@ -926,28 +926,34 @@ function parseBlocks(md) {
|
|
|
926
926
|
}
|
|
927
927
|
const open = raw.match(/^\s*(`{3,}|~{3,})/);
|
|
928
928
|
if (open) {
|
|
929
|
-
|
|
929
|
+
closeBlock();
|
|
930
930
|
fenceChar = open[1][0];
|
|
931
931
|
fenceLen = open[1].length;
|
|
932
932
|
cur.push(raw);
|
|
933
933
|
} else if (raw.trim() === "") {
|
|
934
|
-
|
|
934
|
+
onBlank();
|
|
935
935
|
} else {
|
|
936
936
|
cur.push(raw);
|
|
937
937
|
}
|
|
938
938
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
`));
|
|
943
|
-
} else {
|
|
944
|
-
flushPara();
|
|
945
|
-
}
|
|
939
|
+
closeBlock();
|
|
940
|
+
for (let i2 = 0;i2 < blocks.length - 1; i2++)
|
|
941
|
+
blocks[i2].blankAfter = Math.max(0, blocks[i2].blankAfter - 1);
|
|
946
942
|
return blocks;
|
|
947
943
|
}
|
|
948
|
-
function
|
|
949
|
-
|
|
950
|
-
|
|
944
|
+
function serializeDocBlocks(blocks) {
|
|
945
|
+
const live = blocks.filter((b2) => !!b2.text && b2.text.length > 0);
|
|
946
|
+
const out = [];
|
|
947
|
+
live.forEach((b2, i2) => {
|
|
948
|
+
out.push(...b2.text.split(`
|
|
949
|
+
`));
|
|
950
|
+
const extra = Math.max(0, b2.blankAfter ?? 0);
|
|
951
|
+
if (i2 < live.length - 1)
|
|
952
|
+
out.push("");
|
|
953
|
+
for (let k2 = 0;k2 < extra; k2++)
|
|
954
|
+
out.push("");
|
|
955
|
+
});
|
|
956
|
+
return out.join(`
|
|
951
957
|
`);
|
|
952
958
|
}
|
|
953
959
|
function reconcile(oldTexts, newTexts) {
|
|
@@ -1004,9 +1010,9 @@ function isBlockManaged(db, docId) {
|
|
|
1004
1010
|
return db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId) != null;
|
|
1005
1011
|
}
|
|
1006
1012
|
function recomputeDocBody(db, docId) {
|
|
1007
|
-
const rows = db.query("SELECT text FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
1013
|
+
const rows = db.query("SELECT text, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
1008
1014
|
ensureRow(db, "documents", docId);
|
|
1009
|
-
db.query("UPDATE documents SET body = ? WHERE id = ?").run(
|
|
1015
|
+
db.query("UPDATE documents SET body = ? WHERE id = ?").run(serializeDocBlocks(rows.map((r3) => ({ text: r3.text, blankAfter: r3.blank_after ?? 0 }))), docId);
|
|
1010
1016
|
}
|
|
1011
1017
|
function materialize(db, dataset, rowId, col, valueJson) {
|
|
1012
1018
|
if (dataset === "records") {
|
|
@@ -1096,7 +1102,7 @@ var init_crdt = __esm(() => {
|
|
|
1096
1102
|
},
|
|
1097
1103
|
doc_blocks: {
|
|
1098
1104
|
table: "doc_blocks",
|
|
1099
|
-
cols: new Set(["doc_id", "text", "order_key", "__deleted"])
|
|
1105
|
+
cols: new Set(["doc_id", "text", "order_key", "blank_after", "__deleted"])
|
|
1100
1106
|
},
|
|
1101
1107
|
sites: {
|
|
1102
1108
|
table: "sites",
|
|
@@ -1349,9 +1355,14 @@ var require_src = __commonJS((exports, module) => {
|
|
|
1349
1355
|
// src/core/sync/webui.ts
|
|
1350
1356
|
var exports_webui = {};
|
|
1351
1357
|
__export(exports_webui, {
|
|
1358
|
+
warmWebui: () => warmWebui,
|
|
1359
|
+
setWebuiBundle: () => setWebuiBundle,
|
|
1352
1360
|
serveWebui: () => serveWebui
|
|
1353
1361
|
});
|
|
1354
1362
|
import { fileURLToPath } from "url";
|
|
1363
|
+
function setWebuiBundle(js) {
|
|
1364
|
+
cachedJs = js;
|
|
1365
|
+
}
|
|
1355
1366
|
async function getJs() {
|
|
1356
1367
|
if (cachedJs != null)
|
|
1357
1368
|
return cachedJs;
|
|
@@ -1367,6 +1378,11 @@ async function getJs() {
|
|
|
1367
1378
|
cachedJs = await res.outputs[0].text();
|
|
1368
1379
|
return cachedJs;
|
|
1369
1380
|
}
|
|
1381
|
+
async function warmWebui() {
|
|
1382
|
+
try {
|
|
1383
|
+
await getJs();
|
|
1384
|
+
} catch {}
|
|
1385
|
+
}
|
|
1370
1386
|
async function serveWebui(req) {
|
|
1371
1387
|
if (req.method !== "GET")
|
|
1372
1388
|
return null;
|
|
@@ -1393,7 +1409,13 @@ var HTML = `<!doctype html>
|
|
|
1393
1409
|
<title>Metahub</title>
|
|
1394
1410
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1395
1411
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1396
|
-
|
|
1412
|
+
<!-- Load web fonts without blocking first paint: media="print" keeps the
|
|
1413
|
+
stylesheet non-render-blocking, then onload flips it to "all". The page
|
|
1414
|
+
paints instantly with the system-font fallbacks baked into --ui/--mono and
|
|
1415
|
+
swaps to the web fonts when they arrive (or stays on system fonts offline).
|
|
1416
|
+
This is what kept the desktop cold-start white for seconds on slow networks. -->
|
|
1417
|
+
<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">
|
|
1418
|
+
<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>
|
|
1397
1419
|
<script>try{document.documentElement.dataset.theme=localStorage.getItem('mh-theme')||'system'}catch(e){}</script>
|
|
1398
1420
|
<style>
|
|
1399
1421
|
:root {
|
|
@@ -1407,6 +1429,7 @@ var HTML = `<!doctype html>
|
|
|
1407
1429
|
--radius:8px; --radius-sm:6px;
|
|
1408
1430
|
--ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
|
|
1409
1431
|
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
|
|
1432
|
+
--note-lh:1.6;
|
|
1410
1433
|
--code-bg:#f8f8f6;
|
|
1411
1434
|
--hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
|
|
1412
1435
|
--hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
|
|
@@ -1439,7 +1462,7 @@ var HTML = `<!doctype html>
|
|
|
1439
1462
|
* { box-sizing: border-box; }
|
|
1440
1463
|
html, body { margin: 0; height: 100%; }
|
|
1441
1464
|
body { font-family: var(--ui); font-size: 14px; line-height: 1.55; color: var(--fg); background: var(--bg);
|
|
1442
|
-
overflow: hidden;
|
|
1465
|
+
overflow: hidden; text-rendering: auto; }
|
|
1443
1466
|
button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
|
|
1444
1467
|
input, textarea { font: inherit; color: inherit; }
|
|
1445
1468
|
.muted { color: var(--muted); }
|
|
@@ -1495,6 +1518,8 @@ var HTML = `<!doctype html>
|
|
|
1495
1518
|
.dragging { opacity:.35; }
|
|
1496
1519
|
.sb-footer { flex:none; padding:6px 8px; border-top:1px solid var(--line); }
|
|
1497
1520
|
.sb-footer .navitem { font-size:13.5px; }
|
|
1521
|
+
.nav-dot { flex:none; width:7px; height:7px; border-radius:50%; background:var(--danger);
|
|
1522
|
+
box-shadow:0 0 0 2px var(--surface); margin-right:2px; }
|
|
1498
1523
|
|
|
1499
1524
|
/* settings page */
|
|
1500
1525
|
.set-page { max-width:680px; margin:0 auto; padding:40px 28px 64px; }
|
|
@@ -1503,6 +1528,20 @@ var HTML = `<!doctype html>
|
|
|
1503
1528
|
.set-section { margin-top:34px; }
|
|
1504
1529
|
.set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
|
|
1505
1530
|
.set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
|
|
1531
|
+
.set-footer { margin-top:40px; padding-top:20px; border-top:1px solid var(--line); display:flex;
|
|
1532
|
+
flex-wrap:wrap; justify-content:center; align-items:center; gap:7px; color:var(--muted); font-size:12px; }
|
|
1533
|
+
.set-footer-sep { opacity:.45; }
|
|
1534
|
+
.ver-num { font-family:var(--mono); font-size:11px; color:var(--fg-soft); letter-spacing:-.01em; }
|
|
1535
|
+
.ver-up { display:inline-flex; align-items:center; gap:6px; }
|
|
1536
|
+
.ver-up.err .ver-msg { color:var(--danger); }
|
|
1537
|
+
.ver-act { background:none; border:0; padding:0; margin:0; font:inherit; font-size:12px; line-height:1;
|
|
1538
|
+
color:var(--muted); cursor:pointer; transition:color .12s; }
|
|
1539
|
+
.ver-act:hover { color:var(--accent); }
|
|
1540
|
+
.ver-act.accent { color:var(--accent); font-weight:600; }
|
|
1541
|
+
.ver-act[aria-disabled="true"] { opacity:.6; cursor:default; pointer-events:none; }
|
|
1542
|
+
.ver-dot { width:6px; height:6px; border-radius:50%; background:var(--accent); flex:none; }
|
|
1543
|
+
.ver-dot.pulse { animation:verpulse 1.8s ease-in-out infinite; }
|
|
1544
|
+
@keyframes verpulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }
|
|
1506
1545
|
.theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
|
|
1507
1546
|
.theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
|
|
1508
1547
|
text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
|
|
@@ -1540,6 +1579,7 @@ var HTML = `<!doctype html>
|
|
|
1540
1579
|
/* main */
|
|
1541
1580
|
.main { flex:1; display:flex; flex-direction:column; overflow:hidden; min-width:0; }
|
|
1542
1581
|
.topbar { display:flex; align-items:center; gap:6px; padding:10px 18px; border-bottom:1px solid var(--line); min-height:49px; }
|
|
1582
|
+
.topbar.bare { border-bottom:0; }
|
|
1543
1583
|
.topbar .hamburger { display:none; }
|
|
1544
1584
|
.topbar .hamburger.show-collapsed { display:grid; }
|
|
1545
1585
|
.sidebar.collapsed { overflow:hidden; }
|
|
@@ -1594,8 +1634,10 @@ var HTML = `<!doctype html>
|
|
|
1594
1634
|
/* The list item's host gutter controls the whole unit, so suppress the nested
|
|
1595
1635
|
code block's own gutter (it would overlap the list marker on the same row). */
|
|
1596
1636
|
.block.list-code-host + .block-wrap.nested .gutter { display:none; }
|
|
1597
|
-
.editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:
|
|
1637
|
+
.editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:var(--note-lh); }
|
|
1598
1638
|
.editable:empty::before { content:attr(data-ph); color:var(--muted); pointer-events:none; }
|
|
1639
|
+
/* The generic "/" prompt only shows on the focused line; idle blank lines (paragraphs and list items, any depth) stay blank. */
|
|
1640
|
+
.editable[data-ph-hint="slash"]:empty:not(:focus)::before { content:""; }
|
|
1599
1641
|
.b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
|
|
1600
1642
|
.b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
|
|
1601
1643
|
.b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
|
|
@@ -1647,7 +1689,8 @@ var HTML = `<!doctype html>
|
|
|
1647
1689
|
.hljs-emphasis { font-style:italic; }
|
|
1648
1690
|
.hljs-strong { font-weight:700; }
|
|
1649
1691
|
.marker { width:24px; flex:none; text-align:center; color:var(--fg-soft); user-select:none; padding-top:4px; }
|
|
1650
|
-
.b-todo .marker { padding-top:
|
|
1692
|
+
.b-todo .marker { box-sizing:content-box; padding-top:3px; height:calc(1em * var(--note-lh));
|
|
1693
|
+
display:flex; align-items:center; justify-content:center; }
|
|
1651
1694
|
.b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
|
|
1652
1695
|
.b-divider { padding:10px 0; }
|
|
1653
1696
|
.b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
|
|
@@ -1834,14 +1877,14 @@ var HTML = `<!doctype html>
|
|
|
1834
1877
|
.optrow .x:hover { background:var(--hover-2); color:var(--danger); }
|
|
1835
1878
|
|
|
1836
1879
|
/* modal */
|
|
1837
|
-
.modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:
|
|
1880
|
+
.modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:flex; align-items:center; justify-content:center;
|
|
1838
1881
|
opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
|
|
1839
1882
|
.modal-scrim.open { opacity:1; pointer-events:auto; }
|
|
1840
|
-
.modal { background:var(--bg); border-radius:16px; box-shadow:var(--shadow-lg); width:440px; max-width:100%; overflow:hidden; }
|
|
1883
|
+
.modal { background:var(--bg); border-radius:16px; box-shadow:var(--shadow-lg); width:440px; max-width:100%; max-height:100%; display:flex; flex-direction:column; overflow:hidden; }
|
|
1841
1884
|
.modal-head { padding:20px 22px 0; }
|
|
1842
1885
|
.modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
|
|
1843
1886
|
.modal-head p { margin:6px 0 0; color:var(--muted); font-size:13.5px; }
|
|
1844
|
-
.modal-body { padding:18px 22px; }
|
|
1887
|
+
.modal-body { padding:18px 22px; overflow-y:auto; }
|
|
1845
1888
|
.modal-foot { display:flex; justify-content:flex-end; gap:8px; padding:14px 22px; background:var(--surface); border-top:1px solid var(--line); }
|
|
1846
1889
|
.field-label { font-size:12px; font-weight:600; color:var(--fg-soft); margin:12px 0 5px; }
|
|
1847
1890
|
.field-label:first-child { margin-top:0; }
|
|
@@ -1864,7 +1907,71 @@ var HTML = `<!doctype html>
|
|
|
1864
1907
|
@keyframes slidein { from { opacity:0; transform:translateX(-12px); } }
|
|
1865
1908
|
|
|
1866
1909
|
/* misc */
|
|
1867
|
-
.empty { display:
|
|
1910
|
+
.empty { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%;
|
|
1911
|
+
color:var(--muted); text-align:center; padding:40px; }
|
|
1912
|
+
|
|
1913
|
+
/* ---- empty state: \u77E5\u8BC6\u751F\u957F\u63D2\u753B ---- */
|
|
1914
|
+
.estate-art { width:240px; height:208px; overflow:visible; animation:idleFloat 5s ease-in-out 1.6s infinite; }
|
|
1915
|
+
.estate-art .stroke-a { fill:none; stroke:var(--accent); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
|
|
1916
|
+
.estate-art .stroke { fill:none; stroke:var(--line-strong); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
|
|
1917
|
+
.estate-art .fill-a { fill:var(--accent); }
|
|
1918
|
+
.estate-art .fill-soft { fill:var(--accent-soft); }
|
|
1919
|
+
.estate-art .fill-surface { fill:var(--bg); }
|
|
1920
|
+
.estate-art .ground { fill:var(--accent); }
|
|
1921
|
+
.estate-title { margin:28px 0 0; font-size:18px; font-weight:600; letter-spacing:.2px; color:var(--fg);
|
|
1922
|
+
opacity:0; animation:fadeUp .5s ease forwards 1.0s; }
|
|
1923
|
+
.estate-hint { margin:9px 0 0; font-size:13.5px; line-height:1.6; color:var(--muted); max-width:300px;
|
|
1924
|
+
opacity:0; animation:fadeUp .5s ease forwards 1.15s; }
|
|
1925
|
+
.estate-link { margin:18px 0 0; font-size:14px; font-weight:600; color:var(--accent);
|
|
1926
|
+
display:inline-flex; align-items:center; gap:6px; padding:9px 8px; border-radius:var(--radius-sm);
|
|
1927
|
+
opacity:0; animation:fadeUp .5s ease forwards 1.3s; transition:opacity .15s; }
|
|
1928
|
+
.estate-link:hover { opacity:.7; }
|
|
1929
|
+
/* intro */
|
|
1930
|
+
.draw { stroke-dasharray:100; stroke-dashoffset:100; }
|
|
1931
|
+
@keyframes draw { to { stroke-dashoffset:0; } }
|
|
1932
|
+
@keyframes leafGrow { from { opacity:0; transform:scale(.2) rotate(-10deg); } to { opacity:1; transform:scale(1) rotate(0); } }
|
|
1933
|
+
@keyframes cardUp { from { opacity:0; transform:translateY(16px) scale(.96); } to { opacity:1; transform:translateY(0) scale(1); } }
|
|
1934
|
+
@keyframes groundIn { to { opacity:.1; } }
|
|
1935
|
+
@keyframes glowIn { to { opacity:.6; } }
|
|
1936
|
+
@keyframes fadeUp { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
|
|
1937
|
+
/* idle loops \u2014 each 0% equals the settled rest state to avoid a jump */
|
|
1938
|
+
@keyframes idleFloat { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-5px); } }
|
|
1939
|
+
@keyframes plantSway { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-1.3deg); } 75% { transform:rotate(1.3deg); } }
|
|
1940
|
+
@keyframes leafSwayA { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-2.6deg); } 75% { transform:rotate(2.6deg); } }
|
|
1941
|
+
@keyframes leafSwayB { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(2.6deg); } 75% { transform:rotate(-2.6deg); } }
|
|
1942
|
+
@keyframes groundBreathe { 0%,100% { transform:scale(1); opacity:.10; } 50% { transform:scale(1.07); opacity:.15; } }
|
|
1943
|
+
@keyframes glowPulse { 0%,100% { transform:scale(1); opacity:.6; } 50% { transform:scale(1.1); opacity:.85; } }
|
|
1944
|
+
@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); } }
|
|
1945
|
+
/* wiring */
|
|
1946
|
+
.kg-ground { transform-box:fill-box; transform-origin:center; opacity:0;
|
|
1947
|
+
animation:groundIn .5s ease forwards .1s, groundBreathe 6s ease-in-out 1.6s infinite; }
|
|
1948
|
+
.kg-glow { filter:blur(9px); transform-box:fill-box; transform-origin:center; opacity:0;
|
|
1949
|
+
animation:glowIn .6s ease forwards .1s, glowPulse 4.6s ease-in-out 1.6s infinite; }
|
|
1950
|
+
.kg-cardB { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .2s; }
|
|
1951
|
+
.kg-cardA { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .32s; }
|
|
1952
|
+
.kg-plant { transform-box:fill-box; transform-origin:50% 100%; animation:plantSway 6s ease-in-out 1.7s infinite; }
|
|
1953
|
+
.kg-stem { animation:draw .9s ease-out forwards .4s; }
|
|
1954
|
+
.kg-leaf { transform-box:fill-box; }
|
|
1955
|
+
.kg-grow { transform-box:fill-box; opacity:0; }
|
|
1956
|
+
.kg-leaf.l1 { transform-origin:90% 100%; animation:leafSwayA 4.6s ease-in-out 1.7s infinite; }
|
|
1957
|
+
.kg-leaf.l2 { transform-origin:0% 80%; animation:leafSwayB 5.2s ease-in-out 1.8s infinite; }
|
|
1958
|
+
.kg-leaf.l3 { transform-origin:0% 100%; animation:leafSwayA 5.0s ease-in-out 1.9s infinite; }
|
|
1959
|
+
.kg-leaf.l1 .kg-grow { transform-origin:90% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards .85s; }
|
|
1960
|
+
.kg-leaf.l2 .kg-grow { transform-origin:0% 80%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.0s; }
|
|
1961
|
+
.kg-leaf.l3 .kg-grow { transform-origin:0% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.15s; }
|
|
1962
|
+
.kg-particle { transform-box:fill-box; transform-origin:center; opacity:0; }
|
|
1963
|
+
.kg-particle.p1 { animation:particle 4.5s ease-in-out 1.6s infinite; }
|
|
1964
|
+
.kg-particle.p2 { animation:particle 5.2s ease-in-out 2.0s infinite; }
|
|
1965
|
+
.kg-particle.p3 { animation:particle 4.8s ease-in-out 2.6s infinite; }
|
|
1966
|
+
.kg-particle.p4 { animation:particle 5.6s ease-in-out 3.1s infinite; }
|
|
1967
|
+
.kg-particle.p5 { animation:particle 4.2s ease-in-out 2.3s infinite; }
|
|
1968
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1969
|
+
.estate-art, .estate-art *, .estate-title, .estate-hint, .estate-link { animation:none !important; opacity:1 !important; transform:none !important; }
|
|
1970
|
+
.draw { stroke-dashoffset:0 !important; }
|
|
1971
|
+
.kg-ground { opacity:.1 !important; }
|
|
1972
|
+
.kg-glow { opacity:.6 !important; }
|
|
1973
|
+
.kg-particle { opacity:0 !important; }
|
|
1974
|
+
}
|
|
1868
1975
|
.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; }
|
|
1869
1976
|
.view-placeholder { padding:40px; text-align:center; border:1px dashed var(--line-strong); border-radius:var(--radius); color:var(--muted); margin-top:8px; }
|
|
1870
1977
|
.ico.flip { transform:scaleX(-1); }
|
|
@@ -1996,6 +2103,17 @@ var HTML = `<!doctype html>
|
|
|
1996
2103
|
.qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
|
|
1997
2104
|
.qn-body .doc-title { font-size:24px; margin-bottom:2px; }
|
|
1998
2105
|
.qn-body .doc-meta { display:none; }
|
|
2106
|
+
/* A native checkbox paints an opaque white box when unchecked, which breaks
|
|
2107
|
+
the window's vibrancy blur. Draw a custom box so the unchecked background
|
|
2108
|
+
stays transparent; checked falls back to the accent fill + checkmark. */
|
|
2109
|
+
body.quicknote .b-todo .marker input {
|
|
2110
|
+
appearance:none; -webkit-appearance:none; box-sizing:border-box;
|
|
2111
|
+
border:1.5px solid color-mix(in srgb, var(--fg-soft) 55%, transparent);
|
|
2112
|
+
border-radius:4px; background:transparent; }
|
|
2113
|
+
body.quicknote .b-todo .marker input:checked {
|
|
2114
|
+
border-color:var(--accent);
|
|
2115
|
+
background:var(--accent) center/10px no-repeat
|
|
2116
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12l5 5L20 7'/%3E%3C/svg%3E"); }
|
|
1999
2117
|
/* Compact menus inside the quick-note window only (dropdown + slash command
|
|
2000
2118
|
share .pop/.item/.lico with the main app; scope under body.quicknote so the
|
|
2001
2119
|
main app keeps its roomier density). */
|
|
@@ -3456,8 +3574,9 @@ async function runMain(cmd, opts = {}) {
|
|
|
3456
3574
|
// package.json
|
|
3457
3575
|
var package_default = {
|
|
3458
3576
|
name: "@tensorix/metahub",
|
|
3459
|
-
version: "0.1.
|
|
3577
|
+
version: "0.1.6",
|
|
3460
3578
|
description: "Metahub core library and CLI",
|
|
3579
|
+
license: "AGPL-3.0-only",
|
|
3461
3580
|
type: "module",
|
|
3462
3581
|
main: "./dist/index.js",
|
|
3463
3582
|
module: "./dist/index.js",
|
|
@@ -3475,14 +3594,17 @@ var package_default = {
|
|
|
3475
3594
|
},
|
|
3476
3595
|
files: [
|
|
3477
3596
|
"dist",
|
|
3478
|
-
"README.md"
|
|
3597
|
+
"README.md",
|
|
3598
|
+
"README.zh-CN.md",
|
|
3599
|
+
"LICENSE"
|
|
3479
3600
|
],
|
|
3480
3601
|
scripts: {
|
|
3481
3602
|
dev: "bun --hot src/cli/index.ts",
|
|
3482
3603
|
build: "bun run scripts/build.ts",
|
|
3483
3604
|
"build:binaries": "bun run scripts/compile-binaries.ts",
|
|
3484
3605
|
prepublishOnly: "bun run build",
|
|
3485
|
-
test: "bun test"
|
|
3606
|
+
test: "bun test",
|
|
3607
|
+
release: "bun run scripts/tag-release.ts"
|
|
3486
3608
|
},
|
|
3487
3609
|
dependencies: {
|
|
3488
3610
|
"@clack/prompts": "^1.5.0",
|
|
@@ -3607,11 +3729,12 @@ CREATE TABLE IF NOT EXISTS documents (
|
|
|
3607
3729
|
-- independent CRDT register so concurrent edits to different blocks merge.
|
|
3608
3730
|
-- order_key is a fractional index (ties broken by id) giving display order.
|
|
3609
3731
|
CREATE TABLE IF NOT EXISTS doc_blocks (
|
|
3610
|
-
id
|
|
3611
|
-
doc_id
|
|
3612
|
-
text
|
|
3613
|
-
order_key
|
|
3614
|
-
|
|
3732
|
+
id TEXT PRIMARY KEY,
|
|
3733
|
+
doc_id TEXT,
|
|
3734
|
+
text TEXT,
|
|
3735
|
+
order_key TEXT,
|
|
3736
|
+
blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)
|
|
3737
|
+
__deleted INTEGER NOT NULL DEFAULT 0
|
|
3615
3738
|
);
|
|
3616
3739
|
CREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);
|
|
3617
3740
|
|
|
@@ -3683,9 +3806,20 @@ function deleteDatabase(db, id) {
|
|
|
3683
3806
|
if (!getDatabase(db, id))
|
|
3684
3807
|
return false;
|
|
3685
3808
|
emit(db, "databases", id, "__deleted", 1);
|
|
3809
|
+
const tombstone = (table) => {
|
|
3810
|
+
for (const r3 of liveChildren(db, table, "database_id", id))
|
|
3811
|
+
emit(db, table, r3, "__deleted", 1);
|
|
3812
|
+
};
|
|
3813
|
+
tombstone("properties");
|
|
3814
|
+
tombstone("records");
|
|
3815
|
+
for (const d2 of liveChildren(db, "documents", "database_id", id))
|
|
3816
|
+
emit(db, "documents", d2, "database_id", null);
|
|
3686
3817
|
db.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(id);
|
|
3687
3818
|
return true;
|
|
3688
3819
|
}
|
|
3820
|
+
function liveChildren(db, table, col, parentId) {
|
|
3821
|
+
return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r3) => r3.id);
|
|
3822
|
+
}
|
|
3689
3823
|
|
|
3690
3824
|
// src/core/properties.ts
|
|
3691
3825
|
init_ids();
|
|
@@ -3814,9 +3948,13 @@ function setPropertyWidth(db, id, width) {
|
|
|
3814
3948
|
return updateProperty(db, id, { config: { ...cur.config ?? {}, width: w2 } });
|
|
3815
3949
|
}
|
|
3816
3950
|
function removeProperty(db, id) {
|
|
3817
|
-
|
|
3951
|
+
const prop = getProperty(db, id);
|
|
3952
|
+
if (!prop)
|
|
3818
3953
|
return false;
|
|
3819
3954
|
emit(db, "properties", id, "__deleted", 1);
|
|
3955
|
+
const rows = db.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(prop.database_id, id);
|
|
3956
|
+
for (const r3 of rows)
|
|
3957
|
+
emit(db, "records", r3.id, id, undefined);
|
|
3820
3958
|
return true;
|
|
3821
3959
|
}
|
|
3822
3960
|
|
|
@@ -4284,6 +4422,10 @@ function migratePeers(db) {
|
|
|
4284
4422
|
db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
|
|
4285
4423
|
}
|
|
4286
4424
|
}
|
|
4425
|
+
function migrateDocBlocks(db) {
|
|
4426
|
+
if (!hasColumn(db, "doc_blocks", "blank_after"))
|
|
4427
|
+
db.exec("ALTER TABLE doc_blocks ADD COLUMN blank_after INTEGER NOT NULL DEFAULT 0");
|
|
4428
|
+
}
|
|
4287
4429
|
function openMetahub() {
|
|
4288
4430
|
ensureDirs();
|
|
4289
4431
|
const db = new Database(dbPath(), { create: true });
|
|
@@ -4291,6 +4433,7 @@ function openMetahub() {
|
|
|
4291
4433
|
runSchema(db);
|
|
4292
4434
|
migrateRecords(db);
|
|
4293
4435
|
migratePeers(db);
|
|
4436
|
+
migrateDocBlocks(db);
|
|
4294
4437
|
return db;
|
|
4295
4438
|
}
|
|
4296
4439
|
|
|
@@ -4462,7 +4605,7 @@ var use_default = defineCommand({
|
|
|
4462
4605
|
init_ids();
|
|
4463
4606
|
init_crdt();
|
|
4464
4607
|
function liveBlocks(db, docId) {
|
|
4465
|
-
return db.query("SELECT id, text, order_key FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
4608
|
+
return db.query("SELECT id, text, order_key, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
4466
4609
|
}
|
|
4467
4610
|
function makeBlockId(text) {
|
|
4468
4611
|
return newId("blk", text.split(`
|
|
@@ -4472,29 +4615,37 @@ function emitBlock(db, docId, fields) {
|
|
|
4472
4615
|
const id = makeBlockId(fields.text);
|
|
4473
4616
|
emit(db, "doc_blocks", id, "doc_id", docId);
|
|
4474
4617
|
emit(db, "doc_blocks", id, "order_key", fields.order_key);
|
|
4618
|
+
if (fields.blankAfter)
|
|
4619
|
+
emit(db, "doc_blocks", id, "blank_after", fields.blankAfter);
|
|
4475
4620
|
emit(db, "doc_blocks", id, "text", fields.text);
|
|
4476
4621
|
}
|
|
4477
|
-
function insertBlocks(db, docId,
|
|
4478
|
-
const keys = keysBetween(after, before,
|
|
4479
|
-
|
|
4622
|
+
function insertBlocks(db, docId, blocks, after, before) {
|
|
4623
|
+
const keys = keysBetween(after, before, blocks.length);
|
|
4624
|
+
blocks.forEach((b2, i2) => emitBlock(db, docId, { text: b2.text, order_key: keys[i2], blankAfter: b2.blankAfter }));
|
|
4480
4625
|
}
|
|
4481
4626
|
function ensureBlocks(db, docId) {
|
|
4482
4627
|
if (db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId))
|
|
4483
4628
|
return;
|
|
4484
4629
|
const row = db.query("SELECT body FROM documents WHERE id = ?").get(docId);
|
|
4485
|
-
const
|
|
4486
|
-
if (
|
|
4487
|
-
insertBlocks(db, docId,
|
|
4630
|
+
const blocks = parseDocBlocks(row?.body ?? "");
|
|
4631
|
+
if (blocks.length)
|
|
4632
|
+
insertBlocks(db, docId, blocks, null, null);
|
|
4488
4633
|
}
|
|
4489
4634
|
function reconcileBody(db, docId, body) {
|
|
4490
4635
|
const old = liveBlocks(db, docId);
|
|
4491
|
-
const
|
|
4636
|
+
const next = parseDocBlocks(body);
|
|
4637
|
+
const plan = reconcile(old.map((b2) => b2.text), next.map((b2) => b2.text));
|
|
4492
4638
|
for (const oi of plan.deleted)
|
|
4493
4639
|
emit(db, "doc_blocks", old[oi].id, "__deleted", 1);
|
|
4494
4640
|
const { items } = plan;
|
|
4495
4641
|
let i2 = 0;
|
|
4496
4642
|
while (i2 < items.length) {
|
|
4497
|
-
|
|
4643
|
+
const it = items[i2];
|
|
4644
|
+
if ("keep" in it) {
|
|
4645
|
+
const oldB = old[it.keep];
|
|
4646
|
+
const want = next[i2].blankAfter;
|
|
4647
|
+
if ((oldB.blank_after ?? 0) !== want)
|
|
4648
|
+
emit(db, "doc_blocks", oldB.id, "blank_after", want);
|
|
4498
4649
|
i2++;
|
|
4499
4650
|
continue;
|
|
4500
4651
|
}
|
|
@@ -4503,8 +4654,7 @@ function reconcileBody(db, docId, body) {
|
|
|
4503
4654
|
j++;
|
|
4504
4655
|
const left = i2 > 0 ? old[items[i2 - 1].keep].order_key : null;
|
|
4505
4656
|
const right = j < items.length ? old[items[j].keep].order_key : null;
|
|
4506
|
-
|
|
4507
|
-
insertBlocks(db, docId, texts, left, right);
|
|
4657
|
+
insertBlocks(db, docId, next.slice(i2, j), left, right);
|
|
4508
4658
|
i2 = j;
|
|
4509
4659
|
}
|
|
4510
4660
|
}
|
|
@@ -4523,9 +4673,9 @@ function createDocument(db, opts) {
|
|
|
4523
4673
|
if (opts.parent_id !== undefined)
|
|
4524
4674
|
emit(db, "documents", id, "parent_id", opts.parent_id);
|
|
4525
4675
|
if (opts.body !== undefined) {
|
|
4526
|
-
const
|
|
4527
|
-
if (
|
|
4528
|
-
insertBlocks(db, id,
|
|
4676
|
+
const blocks = parseDocBlocks(opts.body);
|
|
4677
|
+
if (blocks.length)
|
|
4678
|
+
insertBlocks(db, id, blocks, null, null);
|
|
4529
4679
|
}
|
|
4530
4680
|
return getDocument(db, id);
|
|
4531
4681
|
}
|
|
@@ -4571,7 +4721,7 @@ function editDocument(db, id, opts) {
|
|
|
4571
4721
|
throw new Error("--old must not be empty");
|
|
4572
4722
|
ensureBlocks(db, id);
|
|
4573
4723
|
const blocks = liveBlocks(db, id);
|
|
4574
|
-
const body =
|
|
4724
|
+
const body = serializeDocBlocks(blocks.map((b2) => ({ text: b2.text, blankAfter: b2.blank_after })));
|
|
4575
4725
|
const count = countOccurrences(body, opts.old);
|
|
4576
4726
|
if (count === 0)
|
|
4577
4727
|
throw new Error(`anchor not found: no match for --old`);
|
|
@@ -4598,32 +4748,39 @@ function appendDocument(db, id, body) {
|
|
|
4598
4748
|
if (!getDocument(db, id))
|
|
4599
4749
|
throw new Error(`no such document: ${id}`);
|
|
4600
4750
|
ensureBlocks(db, id);
|
|
4601
|
-
const
|
|
4602
|
-
if (
|
|
4751
|
+
const next = parseDocBlocks(body);
|
|
4752
|
+
if (next.length) {
|
|
4603
4753
|
const blocks = liveBlocks(db, id);
|
|
4604
4754
|
const last = blocks.length ? blocks[blocks.length - 1].order_key : null;
|
|
4605
|
-
insertBlocks(db, id,
|
|
4755
|
+
insertBlocks(db, id, next, last, null);
|
|
4606
4756
|
}
|
|
4607
|
-
return { id, changed:
|
|
4757
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
4608
4758
|
}
|
|
4609
4759
|
function prependDocument(db, id, body) {
|
|
4610
4760
|
if (!getDocument(db, id))
|
|
4611
4761
|
throw new Error(`no such document: ${id}`);
|
|
4612
4762
|
ensureBlocks(db, id);
|
|
4613
|
-
const
|
|
4614
|
-
if (
|
|
4763
|
+
const next = parseDocBlocks(body);
|
|
4764
|
+
if (next.length) {
|
|
4615
4765
|
const blocks = liveBlocks(db, id);
|
|
4616
4766
|
const first = blocks.length ? blocks[0].order_key : null;
|
|
4617
|
-
insertBlocks(db, id,
|
|
4767
|
+
insertBlocks(db, id, next, null, first);
|
|
4618
4768
|
}
|
|
4619
|
-
return { id, changed:
|
|
4769
|
+
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
4620
4770
|
}
|
|
4621
4771
|
function deleteDocument(db, id) {
|
|
4622
4772
|
if (!getDocument(db, id))
|
|
4623
4773
|
return false;
|
|
4624
4774
|
emit(db, "documents", id, "__deleted", 1);
|
|
4775
|
+
for (const b2 of liveChildIds(db, "doc_blocks", "doc_id", id))
|
|
4776
|
+
emit(db, "doc_blocks", b2, "__deleted", 1);
|
|
4777
|
+
for (const c3 of liveChildIds(db, "documents", "parent_id", id))
|
|
4778
|
+
emit(db, "documents", c3, "parent_id", null);
|
|
4625
4779
|
return true;
|
|
4626
4780
|
}
|
|
4781
|
+
function liveChildIds(db, table2, col, parentId) {
|
|
4782
|
+
return db.query(`SELECT id FROM ${table2} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r3) => r3.id);
|
|
4783
|
+
}
|
|
4627
4784
|
function countOccurrences(haystack, needle) {
|
|
4628
4785
|
if (needle === "")
|
|
4629
4786
|
return 0;
|
|
@@ -5148,26 +5305,106 @@ var edit_default = defineCommand({
|
|
|
5148
5305
|
});
|
|
5149
5306
|
|
|
5150
5307
|
// src/core/search.ts
|
|
5308
|
+
init_crdt();
|
|
5151
5309
|
var TEXT_TYPES = "('text','url','select','multi_select','date')";
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5310
|
+
var SEARCH_INDEX_VERSION = "1";
|
|
5311
|
+
function readMeta(db, key) {
|
|
5312
|
+
return db.query("SELECT value FROM meta WHERE key = ?").get(key)?.value ?? null;
|
|
5313
|
+
}
|
|
5314
|
+
function writeMeta(db, key, value) {
|
|
5315
|
+
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
|
|
5316
|
+
}
|
|
5317
|
+
function reindexDocument(db, id) {
|
|
5318
|
+
db.query("DELETE FROM search_fts WHERE kind = 'document' AND id = ?").run(id);
|
|
5319
|
+
const d2 = db.query("SELECT id, title, body, database_id FROM documents WHERE id = ? AND __deleted = 0").get(id);
|
|
5320
|
+
if (d2)
|
|
5161
5321
|
db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d2.id, d2.database_id, d2.title ?? "", d2.body ?? "");
|
|
5162
|
-
|
|
5163
|
-
|
|
5322
|
+
}
|
|
5323
|
+
function recordBodyRows(db, where, ...args) {
|
|
5324
|
+
return db.query(`SELECT r.id AS id, r.database_id AS database_id, group_concat(je.value, ' ') AS body
|
|
5164
5325
|
FROM records r, json_each(r.data) je
|
|
5165
5326
|
JOIN properties p ON p.id = je.key AND p.__deleted = 0
|
|
5166
|
-
WHERE r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
|
|
5167
|
-
GROUP BY r.id`).all()
|
|
5168
|
-
|
|
5327
|
+
WHERE ${where} AND r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
|
|
5328
|
+
GROUP BY r.id`).all(...args);
|
|
5329
|
+
}
|
|
5330
|
+
function insertRecordRow(db, r3) {
|
|
5331
|
+
db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('record', ?, ?, ?, ?)").run(r3.id, r3.database_id, r3.id, r3.body ?? "");
|
|
5332
|
+
}
|
|
5333
|
+
function reindexRecord(db, id) {
|
|
5334
|
+
db.query("DELETE FROM search_fts WHERE kind = 'record' AND id = ?").run(id);
|
|
5335
|
+
const r3 = recordBodyRows(db, "r.id = ?", id)[0];
|
|
5336
|
+
if (r3)
|
|
5337
|
+
insertRecordRow(db, r3);
|
|
5338
|
+
}
|
|
5339
|
+
function reindexDatabaseRecords(db, databaseId) {
|
|
5340
|
+
db.query("DELETE FROM search_fts WHERE kind = 'record' AND database_id = ?").run(databaseId);
|
|
5341
|
+
for (const r3 of recordBodyRows(db, "r.database_id = ?", databaseId))
|
|
5342
|
+
insertRecordRow(db, r3);
|
|
5343
|
+
}
|
|
5344
|
+
function fullRebuild(db) {
|
|
5345
|
+
db.query("DELETE FROM search_fts").run();
|
|
5346
|
+
for (const d2 of db.query("SELECT id, title, body, database_id FROM documents WHERE __deleted = 0").all())
|
|
5347
|
+
db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d2.id, d2.database_id, d2.title ?? "", d2.body ?? "");
|
|
5348
|
+
for (const r3 of recordBodyRows(db, "1 = 1"))
|
|
5349
|
+
insertRecordRow(db, r3);
|
|
5350
|
+
const max = db.query("SELECT MAX(rowid) AS m FROM crdt_changes").get().m ?? 0;
|
|
5351
|
+
writeMeta(db, "search_seq", String(max));
|
|
5352
|
+
writeMeta(db, "search_index_version", SEARCH_INDEX_VERSION);
|
|
5353
|
+
}
|
|
5354
|
+
function incrementalUpdate(db) {
|
|
5355
|
+
const cursor = Number(readMeta(db, "search_seq") ?? "0");
|
|
5356
|
+
const { changes, cursor: next } = changesAfterSeq(db, cursor);
|
|
5357
|
+
if (changes.length === 0)
|
|
5358
|
+
return;
|
|
5359
|
+
const docIds = new Set;
|
|
5360
|
+
const recordIds = new Set;
|
|
5361
|
+
const dbIds = new Set;
|
|
5362
|
+
for (const c3 of changes) {
|
|
5363
|
+
switch (c3.dataset) {
|
|
5364
|
+
case "documents":
|
|
5365
|
+
docIds.add(c3.row_id);
|
|
5366
|
+
break;
|
|
5367
|
+
case "doc_blocks": {
|
|
5368
|
+
const blk = db.query("SELECT doc_id FROM doc_blocks WHERE id = ?").get(c3.row_id);
|
|
5369
|
+
if (blk?.doc_id)
|
|
5370
|
+
docIds.add(blk.doc_id);
|
|
5371
|
+
break;
|
|
5372
|
+
}
|
|
5373
|
+
case "records":
|
|
5374
|
+
recordIds.add(c3.row_id);
|
|
5375
|
+
break;
|
|
5376
|
+
case "properties":
|
|
5377
|
+
if (c3.col === "type" || c3.col === "__deleted" || c3.col === "database_id") {
|
|
5378
|
+
const p = db.query("SELECT database_id FROM properties WHERE id = ?").get(c3.row_id);
|
|
5379
|
+
if (p?.database_id)
|
|
5380
|
+
dbIds.add(p.database_id);
|
|
5381
|
+
}
|
|
5382
|
+
break;
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
for (const id of docIds)
|
|
5386
|
+
reindexDocument(db, id);
|
|
5387
|
+
for (const dbId of dbIds)
|
|
5388
|
+
reindexDatabaseRecords(db, dbId);
|
|
5389
|
+
for (const id of recordIds) {
|
|
5390
|
+
const r3 = db.query("SELECT database_id FROM records WHERE id = ?").get(id);
|
|
5391
|
+
if (r3?.database_id && dbIds.has(r3.database_id))
|
|
5392
|
+
continue;
|
|
5393
|
+
reindexRecord(db, id);
|
|
5169
5394
|
}
|
|
5170
|
-
db
|
|
5395
|
+
writeMeta(db, "search_seq", String(next));
|
|
5396
|
+
}
|
|
5397
|
+
function ensureIndex(db) {
|
|
5398
|
+
if (!ftsAvailable(db))
|
|
5399
|
+
return false;
|
|
5400
|
+
const version = readMeta(db, "search_index_version");
|
|
5401
|
+
const seq = readMeta(db, "search_seq");
|
|
5402
|
+
db.transaction(() => {
|
|
5403
|
+
if (version !== SEARCH_INDEX_VERSION || seq === null)
|
|
5404
|
+
fullRebuild(db);
|
|
5405
|
+
else
|
|
5406
|
+
incrementalUpdate(db);
|
|
5407
|
+
})();
|
|
5171
5408
|
return true;
|
|
5172
5409
|
}
|
|
5173
5410
|
function ftsSearch(db, query, limit) {
|
|
@@ -20166,11 +20403,262 @@ var sync_default = defineCommand({
|
|
|
20166
20403
|
init_crdt();
|
|
20167
20404
|
import { readdirSync, existsSync } from "fs";
|
|
20168
20405
|
import { join as join5 } from "path";
|
|
20406
|
+
|
|
20407
|
+
// src/core/integrity.ts
|
|
20408
|
+
init_crdt();
|
|
20409
|
+
var REF_RULES = [
|
|
20410
|
+
{ srcTable: "properties", fk: "database_id", tgtTable: "databases", fix: "tombstone-src" },
|
|
20411
|
+
{ srcTable: "records", fk: "database_id", tgtTable: "databases", fix: "tombstone-src" },
|
|
20412
|
+
{ srcTable: "documents", fk: "database_id", tgtTable: "databases", fix: "null-fk" },
|
|
20413
|
+
{ srcTable: "documents", fk: "parent_id", tgtTable: "documents", fix: "null-fk" },
|
|
20414
|
+
{ srcTable: "doc_blocks", fk: "doc_id", tgtTable: "documents", fix: "tombstone-src" },
|
|
20415
|
+
{ srcTable: "site_files", fk: "site_id", tgtTable: "sites", fix: "tombstone-src" }
|
|
20416
|
+
];
|
|
20417
|
+
function brokenRefRows(db, r3) {
|
|
20418
|
+
return db.query(`SELECT s.id AS id, s.${r3.fk} AS ref FROM ${r3.srcTable} s
|
|
20419
|
+
JOIN ${r3.tgtTable} t ON t.id = s.${r3.fk} AND t.__deleted = 1
|
|
20420
|
+
WHERE s.__deleted = 0 AND s.${r3.fk} IS NOT NULL
|
|
20421
|
+
ORDER BY s.id`).all();
|
|
20422
|
+
}
|
|
20423
|
+
function detectBrokenRefs(db) {
|
|
20424
|
+
const out = [];
|
|
20425
|
+
for (const r3 of REF_RULES) {
|
|
20426
|
+
for (const row of brokenRefRows(db, r3)) {
|
|
20427
|
+
out.push({
|
|
20428
|
+
category: "broken_ref",
|
|
20429
|
+
entity: r3.srcTable,
|
|
20430
|
+
id: row.id,
|
|
20431
|
+
detail: `${r3.fk} \u2192 deleted ${r3.tgtTable} ${row.ref} (${r3.fix === "tombstone-src" ? "tombstone" : `clear ${r3.fk}`})`,
|
|
20432
|
+
fixable: true
|
|
20433
|
+
});
|
|
20434
|
+
}
|
|
20435
|
+
}
|
|
20436
|
+
return out;
|
|
20437
|
+
}
|
|
20438
|
+
function repairBrokenRefs(db) {
|
|
20439
|
+
let n2 = 0;
|
|
20440
|
+
for (const r3 of REF_RULES) {
|
|
20441
|
+
for (const row of brokenRefRows(db, r3)) {
|
|
20442
|
+
if (r3.fix === "tombstone-src")
|
|
20443
|
+
emit(db, r3.srcTable, row.id, "__deleted", 1);
|
|
20444
|
+
else
|
|
20445
|
+
emit(db, r3.srcTable, row.id, r3.fk, null);
|
|
20446
|
+
n2++;
|
|
20447
|
+
}
|
|
20448
|
+
}
|
|
20449
|
+
return n2;
|
|
20450
|
+
}
|
|
20451
|
+
function orphanCellRows(db) {
|
|
20452
|
+
return db.query(`SELECT r.id AS rec, j.key AS prop
|
|
20453
|
+
FROM records r, json_each(coalesce(r.data, '{}')) j
|
|
20454
|
+
JOIN properties p ON p.id = j.key AND p.__deleted = 1
|
|
20455
|
+
WHERE r.__deleted = 0
|
|
20456
|
+
ORDER BY r.id, j.key`).all();
|
|
20457
|
+
}
|
|
20458
|
+
function detectOrphanCells(db) {
|
|
20459
|
+
return orphanCellRows(db).map((row) => ({
|
|
20460
|
+
category: "orphan_cell",
|
|
20461
|
+
entity: "records",
|
|
20462
|
+
id: row.rec,
|
|
20463
|
+
detail: `cell for deleted property ${row.prop}`,
|
|
20464
|
+
fixable: true
|
|
20465
|
+
}));
|
|
20466
|
+
}
|
|
20467
|
+
function repairOrphanCells(db) {
|
|
20468
|
+
const rows = orphanCellRows(db);
|
|
20469
|
+
for (const row of rows)
|
|
20470
|
+
emit(db, "records", row.rec, row.prop, undefined);
|
|
20471
|
+
return rows.length;
|
|
20472
|
+
}
|
|
20473
|
+
function dupPathLosers(db) {
|
|
20474
|
+
const rows = db.query(`SELECT id, site_id, path FROM site_files
|
|
20475
|
+
WHERE __deleted = 0 AND site_id IS NOT NULL AND path IS NOT NULL
|
|
20476
|
+
ORDER BY site_id, path, created_hlc, id`).all();
|
|
20477
|
+
const losers = [];
|
|
20478
|
+
let group = null;
|
|
20479
|
+
for (const r3 of rows) {
|
|
20480
|
+
const key = `${r3.site_id}\x00${r3.path}`;
|
|
20481
|
+
if (key === group)
|
|
20482
|
+
losers.push({ id: r3.id });
|
|
20483
|
+
else
|
|
20484
|
+
group = key;
|
|
20485
|
+
}
|
|
20486
|
+
return losers;
|
|
20487
|
+
}
|
|
20488
|
+
function detectDupPaths(db) {
|
|
20489
|
+
return dupPathLosers(db).map((row) => ({
|
|
20490
|
+
category: "dup_path",
|
|
20491
|
+
entity: "site_files",
|
|
20492
|
+
id: row.id,
|
|
20493
|
+
detail: "redundant duplicate (site_id, path); loser tombstoned",
|
|
20494
|
+
fixable: true
|
|
20495
|
+
}));
|
|
20496
|
+
}
|
|
20497
|
+
function repairDupPaths(db) {
|
|
20498
|
+
const losers = dupPathLosers(db);
|
|
20499
|
+
for (const row of losers)
|
|
20500
|
+
emit(db, "site_files", row.id, "__deleted", 1);
|
|
20501
|
+
return losers.length;
|
|
20502
|
+
}
|
|
20503
|
+
function detectParentCycles(db) {
|
|
20504
|
+
const rows = db.query(`SELECT d.id AS id, d.parent_id AS parent, d.created_hlc AS hlc
|
|
20505
|
+
FROM documents d
|
|
20506
|
+
WHERE d.__deleted = 0 AND d.parent_id IS NOT NULL
|
|
20507
|
+
AND EXISTS (SELECT 1 FROM documents p WHERE p.id = d.parent_id AND p.__deleted = 0)
|
|
20508
|
+
ORDER BY d.id`).all();
|
|
20509
|
+
const parent = new Map(rows.map((r3) => [r3.id, r3.parent]));
|
|
20510
|
+
const hlc = new Map(rows.map((r3) => [r3.id, r3.hlc]));
|
|
20511
|
+
const seen = new Set;
|
|
20512
|
+
const cycles = [];
|
|
20513
|
+
for (const start of parent.keys()) {
|
|
20514
|
+
if (seen.has(start))
|
|
20515
|
+
continue;
|
|
20516
|
+
const path = [];
|
|
20517
|
+
const pos = new Map;
|
|
20518
|
+
let cur = start;
|
|
20519
|
+
while (cur != null && parent.has(cur) && !seen.has(cur)) {
|
|
20520
|
+
if (pos.has(cur)) {
|
|
20521
|
+
cycles.push(path.slice(pos.get(cur)));
|
|
20522
|
+
break;
|
|
20523
|
+
}
|
|
20524
|
+
pos.set(cur, path.length);
|
|
20525
|
+
path.push(cur);
|
|
20526
|
+
cur = parent.get(cur);
|
|
20527
|
+
}
|
|
20528
|
+
for (const n2 of path)
|
|
20529
|
+
seen.add(n2);
|
|
20530
|
+
}
|
|
20531
|
+
return { cycles, hlc };
|
|
20532
|
+
}
|
|
20533
|
+
function geHlcId(hlc, a2, b2) {
|
|
20534
|
+
const ha = hlc.get(a2) ?? "";
|
|
20535
|
+
const hb = hlc.get(b2) ?? "";
|
|
20536
|
+
if (ha !== hb)
|
|
20537
|
+
return ha > hb;
|
|
20538
|
+
return a2 >= b2;
|
|
20539
|
+
}
|
|
20540
|
+
function detectCycleIssues(db) {
|
|
20541
|
+
const { cycles } = detectParentCycles(db);
|
|
20542
|
+
return cycles.map((cyc) => ({
|
|
20543
|
+
category: "parent_cycle",
|
|
20544
|
+
entity: "documents",
|
|
20545
|
+
id: [...cyc].sort()[0],
|
|
20546
|
+
detail: `parent cycle: ${[...cyc].sort().join(" \u2192 ")}`,
|
|
20547
|
+
fixable: true
|
|
20548
|
+
}));
|
|
20549
|
+
}
|
|
20550
|
+
function repairParentCycles(db) {
|
|
20551
|
+
const { cycles, hlc } = detectParentCycles(db);
|
|
20552
|
+
for (const cyc of cycles) {
|
|
20553
|
+
const brk = cyc.reduce((a2, b2) => geHlcId(hlc, a2, b2) ? a2 : b2);
|
|
20554
|
+
emit(db, "documents", brk, "parent_id", null);
|
|
20555
|
+
}
|
|
20556
|
+
return cycles.length;
|
|
20557
|
+
}
|
|
20558
|
+
function detectDupNames(db) {
|
|
20559
|
+
const out = [];
|
|
20560
|
+
const props = db.query(`SELECT database_id AS db, lower(name) AS n, count(*) AS c, group_concat(id) AS ids
|
|
20561
|
+
FROM properties
|
|
20562
|
+
WHERE __deleted = 0 AND name IS NOT NULL AND database_id IS NOT NULL
|
|
20563
|
+
GROUP BY database_id, lower(name) HAVING c > 1
|
|
20564
|
+
ORDER BY database_id, n`).all();
|
|
20565
|
+
for (const r3 of props)
|
|
20566
|
+
out.push({
|
|
20567
|
+
category: "dup_name",
|
|
20568
|
+
entity: "properties",
|
|
20569
|
+
id: r3.ids.split(",").sort()[0],
|
|
20570
|
+
detail: `${r3.c} properties named "${r3.n}" in database ${r3.db}: ${r3.ids}`,
|
|
20571
|
+
fixable: false
|
|
20572
|
+
});
|
|
20573
|
+
const dbs = db.query(`SELECT lower(name) AS n, count(*) AS c, group_concat(id) AS ids
|
|
20574
|
+
FROM databases WHERE __deleted = 0 AND name IS NOT NULL
|
|
20575
|
+
GROUP BY lower(name) HAVING c > 1 ORDER BY n`).all();
|
|
20576
|
+
for (const r3 of dbs)
|
|
20577
|
+
out.push({
|
|
20578
|
+
category: "dup_name",
|
|
20579
|
+
entity: "databases",
|
|
20580
|
+
id: r3.ids.split(",").sort()[0],
|
|
20581
|
+
detail: `${r3.c} databases named "${r3.n}": ${r3.ids}`,
|
|
20582
|
+
fixable: false
|
|
20583
|
+
});
|
|
20584
|
+
return out;
|
|
20585
|
+
}
|
|
20586
|
+
function configIssue(type, config2) {
|
|
20587
|
+
if (!PROP_TYPES.has(type))
|
|
20588
|
+
return `unknown property type: ${type}`;
|
|
20589
|
+
if (type === "select" || type === "multi_select") {
|
|
20590
|
+
const opts = config2?.options;
|
|
20591
|
+
if (!Array.isArray(opts) || opts.length === 0 || !opts.every((o3) => typeof o3 === "string"))
|
|
20592
|
+
return `${type} missing config.options: string[]`;
|
|
20593
|
+
}
|
|
20594
|
+
if (type === "relation" && typeof config2?.database !== "string")
|
|
20595
|
+
return "relation missing config.database";
|
|
20596
|
+
return null;
|
|
20597
|
+
}
|
|
20598
|
+
function detectBadConfig(db) {
|
|
20599
|
+
const rows = db.query("SELECT id, type, config FROM properties WHERE __deleted = 0 ORDER BY id").all();
|
|
20600
|
+
const out = [];
|
|
20601
|
+
for (const r3 of rows) {
|
|
20602
|
+
let config2 = null;
|
|
20603
|
+
try {
|
|
20604
|
+
config2 = r3.config ? JSON.parse(r3.config) : null;
|
|
20605
|
+
} catch {
|
|
20606
|
+
out.push({ category: "bad_config", entity: "properties", id: r3.id, detail: "config is not valid JSON", fixable: false });
|
|
20607
|
+
continue;
|
|
20608
|
+
}
|
|
20609
|
+
const problem = configIssue(r3.type, config2);
|
|
20610
|
+
if (problem)
|
|
20611
|
+
out.push({ category: "bad_config", entity: "properties", id: r3.id, detail: problem, fixable: false });
|
|
20612
|
+
}
|
|
20613
|
+
return out;
|
|
20614
|
+
}
|
|
20615
|
+
function validateHub(db) {
|
|
20616
|
+
const issues = [
|
|
20617
|
+
...detectBrokenRefs(db),
|
|
20618
|
+
...detectOrphanCells(db),
|
|
20619
|
+
...detectDupPaths(db),
|
|
20620
|
+
...detectCycleIssues(db),
|
|
20621
|
+
...detectDupNames(db),
|
|
20622
|
+
...detectBadConfig(db)
|
|
20623
|
+
];
|
|
20624
|
+
const counts = {};
|
|
20625
|
+
for (const i2 of issues)
|
|
20626
|
+
counts[i2.category] = (counts[i2.category] ?? 0) + 1;
|
|
20627
|
+
return { ok: issues.length === 0, total: issues.length, counts, issues };
|
|
20628
|
+
}
|
|
20629
|
+
function repairPass(db) {
|
|
20630
|
+
const fixed = {};
|
|
20631
|
+
const bump = (cat, n2) => {
|
|
20632
|
+
if (n2)
|
|
20633
|
+
fixed[cat] = (fixed[cat] ?? 0) + n2;
|
|
20634
|
+
};
|
|
20635
|
+
bump("broken_ref", repairBrokenRefs(db));
|
|
20636
|
+
bump("orphan_cell", repairOrphanCells(db));
|
|
20637
|
+
bump("dup_path", repairDupPaths(db));
|
|
20638
|
+
bump("parent_cycle", repairParentCycles(db));
|
|
20639
|
+
const applied = Object.values(fixed).reduce((a2, b2) => a2 + b2, 0);
|
|
20640
|
+
return { applied, fixed };
|
|
20641
|
+
}
|
|
20642
|
+
function repairHub(db) {
|
|
20643
|
+
const fixed = {};
|
|
20644
|
+
let applied = 0;
|
|
20645
|
+
for (let i2 = 0;i2 < 20; i2++) {
|
|
20646
|
+
const pass = repairPass(db);
|
|
20647
|
+
if (pass.applied === 0)
|
|
20648
|
+
break;
|
|
20649
|
+
applied += pass.applied;
|
|
20650
|
+
for (const [cat, n2] of Object.entries(pass.fixed))
|
|
20651
|
+
fixed[cat] = (fixed[cat] ?? 0) + n2;
|
|
20652
|
+
}
|
|
20653
|
+
return { applied, fixed, remaining: validateHub(db) };
|
|
20654
|
+
}
|
|
20655
|
+
|
|
20656
|
+
// src/core/snapshot.ts
|
|
20169
20657
|
init_paths();
|
|
20170
20658
|
init_cache();
|
|
20171
20659
|
var SNAPSHOT_FORMAT = "metahub-snapshot";
|
|
20172
20660
|
var SNAPSHOT_VERSION = 1;
|
|
20173
|
-
function
|
|
20661
|
+
function readMeta2(db, key) {
|
|
20174
20662
|
const row = db.query("SELECT value FROM meta WHERE key = ?").get(key);
|
|
20175
20663
|
return row?.value ?? null;
|
|
20176
20664
|
}
|
|
@@ -20187,8 +20675,8 @@ async function readBlobs() {
|
|
|
20187
20675
|
}
|
|
20188
20676
|
async function createSnapshot(db) {
|
|
20189
20677
|
const changes = changesAfterSeq(db, 0).changes;
|
|
20190
|
-
const node_id =
|
|
20191
|
-
const hlc =
|
|
20678
|
+
const node_id = readMeta2(db, "node_id");
|
|
20679
|
+
const hlc = readMeta2(db, "hlc");
|
|
20192
20680
|
const peers = db.query("SELECT url, pull_cursor, push_cursor FROM peers").all();
|
|
20193
20681
|
const blobs = await readBlobs();
|
|
20194
20682
|
return {
|
|
@@ -20249,8 +20737,9 @@ async function restoreSnapshot(db, pkg, opts = {}) {
|
|
|
20249
20737
|
if (!opts.reset) {
|
|
20250
20738
|
const applied2 = ingest(db, pkg.changes);
|
|
20251
20739
|
rebuildDeclaredIndexes(db);
|
|
20740
|
+
const repaired2 = repairHub(db).applied;
|
|
20252
20741
|
const blobs2 = await writeBlobs(pkg.blobs);
|
|
20253
|
-
return { mode: "merge", applied: applied2, blobs: blobs2 };
|
|
20742
|
+
return { mode: "merge", applied: applied2, blobs: blobs2, repaired: repaired2 };
|
|
20254
20743
|
}
|
|
20255
20744
|
if (!opts.force)
|
|
20256
20745
|
throw new Error("refusing to replace local data without --force (a safety snapshot is saved first)");
|
|
@@ -20268,7 +20757,7 @@ async function restoreSnapshot(db, pkg, opts = {}) {
|
|
|
20268
20757
|
"peers"
|
|
20269
20758
|
])
|
|
20270
20759
|
db.query(`DELETE FROM ${t2}`).run();
|
|
20271
|
-
db.query("DELETE FROM meta WHERE key
|
|
20760
|
+
db.query("DELETE FROM meta WHERE key IN ('search_seq', 'search_index_version')").run();
|
|
20272
20761
|
if (ftsAvailable(db))
|
|
20273
20762
|
db.query("DELETE FROM search_fts").run();
|
|
20274
20763
|
const setMeta2 = db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value");
|
|
@@ -20282,8 +20771,9 @@ async function restoreSnapshot(db, pkg, opts = {}) {
|
|
|
20282
20771
|
});
|
|
20283
20772
|
tx();
|
|
20284
20773
|
rebuildDeclaredIndexes(db);
|
|
20774
|
+
const repaired = repairHub(db).applied;
|
|
20285
20775
|
const blobs = await writeBlobs(pkg.blobs);
|
|
20286
|
-
return { mode: "reset", applied, blobs, safetyPath };
|
|
20776
|
+
return { mode: "reset", applied, blobs, repaired, safetyPath };
|
|
20287
20777
|
}
|
|
20288
20778
|
|
|
20289
20779
|
// src/cli/commands/snapshot.ts
|
|
@@ -20332,10 +20822,78 @@ var restore_default = defineCommand({
|
|
|
20332
20822
|
reset: !!args.reset,
|
|
20333
20823
|
force: !!args.force
|
|
20334
20824
|
});
|
|
20335
|
-
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`);
|
|
20825
|
+
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`);
|
|
20336
20826
|
})
|
|
20337
20827
|
});
|
|
20338
20828
|
|
|
20829
|
+
// src/cli/commands/doctor.ts
|
|
20830
|
+
var doctor_default = defineCommand({
|
|
20831
|
+
meta: {
|
|
20832
|
+
name: "doctor",
|
|
20833
|
+
description: "Scan the hub for logical-integrity issues (read-only; run `mh repair` to fix)"
|
|
20834
|
+
},
|
|
20835
|
+
run: () => {
|
|
20836
|
+
const report = validateHub(openMetahub());
|
|
20837
|
+
print(report, () => {
|
|
20838
|
+
if (report.ok)
|
|
20839
|
+
return "No integrity issues found.";
|
|
20840
|
+
const head = `${report.total} issue(s): ${Object.entries(report.counts).map(([k2, v2]) => `${k2}=${v2}`).join(", ")}`;
|
|
20841
|
+
const rows = report.issues.map((i2) => ({
|
|
20842
|
+
category: i2.category,
|
|
20843
|
+
entity: i2.entity,
|
|
20844
|
+
id: i2.id,
|
|
20845
|
+
fix: i2.fixable ? "auto" : "report",
|
|
20846
|
+
detail: i2.detail
|
|
20847
|
+
}));
|
|
20848
|
+
return `${head}
|
|
20849
|
+
|
|
20850
|
+
${table(rows)}`;
|
|
20851
|
+
});
|
|
20852
|
+
}
|
|
20853
|
+
});
|
|
20854
|
+
|
|
20855
|
+
// src/cli/commands/repair.ts
|
|
20856
|
+
var repair_default = defineCommand({
|
|
20857
|
+
meta: {
|
|
20858
|
+
name: "repair",
|
|
20859
|
+
description: "Deterministically fix logical-integrity issues (idempotent; replicates over sync)"
|
|
20860
|
+
},
|
|
20861
|
+
args: {
|
|
20862
|
+
"dry-run": {
|
|
20863
|
+
type: "boolean",
|
|
20864
|
+
description: "Report what would be fixed without changing anything (same as `mh doctor`)"
|
|
20865
|
+
}
|
|
20866
|
+
},
|
|
20867
|
+
run: ({ args }) => {
|
|
20868
|
+
const db = openMetahub();
|
|
20869
|
+
if (args["dry-run"]) {
|
|
20870
|
+
const report = validateHub(db);
|
|
20871
|
+
const fixable = report.issues.filter((i2) => i2.fixable);
|
|
20872
|
+
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.
|
|
20873
|
+
|
|
20874
|
+
` + table(report.issues.map((i2) => ({
|
|
20875
|
+
category: i2.category,
|
|
20876
|
+
id: i2.id,
|
|
20877
|
+
fix: i2.fixable ? "auto" : "report",
|
|
20878
|
+
detail: i2.detail
|
|
20879
|
+
}))));
|
|
20880
|
+
return;
|
|
20881
|
+
}
|
|
20882
|
+
const result = repairHub(db);
|
|
20883
|
+
print(result, () => {
|
|
20884
|
+
const fixed = Object.entries(result.fixed).map(([k2, v2]) => `${k2}=${v2}`).join(", ");
|
|
20885
|
+
const head = result.applied === 0 ? "No fixable issues." : `Applied ${result.applied} repair(s): ${fixed}`;
|
|
20886
|
+
const left = result.remaining.issues;
|
|
20887
|
+
if (left.length === 0)
|
|
20888
|
+
return head;
|
|
20889
|
+
return `${head}
|
|
20890
|
+
${left.length} report-only issue(s) need manual attention:
|
|
20891
|
+
|
|
20892
|
+
` + table(left.map((i2) => ({ category: i2.category, id: i2.id, detail: i2.detail })));
|
|
20893
|
+
});
|
|
20894
|
+
}
|
|
20895
|
+
});
|
|
20896
|
+
|
|
20339
20897
|
// src/cli/commands/config.ts
|
|
20340
20898
|
init_node();
|
|
20341
20899
|
|
|
@@ -22251,7 +22809,15 @@ function handle(fn) {
|
|
|
22251
22809
|
}
|
|
22252
22810
|
};
|
|
22253
22811
|
}
|
|
22812
|
+
var VersionSchema = exports_external.object({ version: exports_external.string() });
|
|
22254
22813
|
var webuiRoutes = [
|
|
22814
|
+
{
|
|
22815
|
+
method: "GET",
|
|
22816
|
+
path: "/api/version",
|
|
22817
|
+
summary: "Version of the running core (sidecar). Used by the desktop app's settings footer.",
|
|
22818
|
+
response: VersionSchema,
|
|
22819
|
+
handler: () => Response.json({ version: package_default.version })
|
|
22820
|
+
},
|
|
22255
22821
|
{
|
|
22256
22822
|
method: "GET",
|
|
22257
22823
|
path: "/api/databases",
|
|
@@ -22875,6 +23441,16 @@ function unauthorized() {
|
|
|
22875
23441
|
}
|
|
22876
23442
|
|
|
22877
23443
|
// src/core/sync/server.ts
|
|
23444
|
+
class PortInUseError extends Error {
|
|
23445
|
+
port;
|
|
23446
|
+
host;
|
|
23447
|
+
constructor(port, host) {
|
|
23448
|
+
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>.`);
|
|
23449
|
+
this.port = port;
|
|
23450
|
+
this.host = host;
|
|
23451
|
+
this.name = "PortInUseError";
|
|
23452
|
+
}
|
|
23453
|
+
}
|
|
22878
23454
|
function scalarHtml(specUrl) {
|
|
22879
23455
|
return `<!doctype html><html><head>
|
|
22880
23456
|
<meta charset="utf-8"><title>Metahub API</title>
|
|
@@ -22899,48 +23475,60 @@ function startServer(opts = {}) {
|
|
|
22899
23475
|
ttlMs: DEFAULT_TTL_MS,
|
|
22900
23476
|
graceMs: DEFAULT_GRACE_MS
|
|
22901
23477
|
};
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
22913
|
-
|
|
23478
|
+
let server;
|
|
23479
|
+
try {
|
|
23480
|
+
server = Bun.serve({
|
|
23481
|
+
port,
|
|
23482
|
+
hostname: host,
|
|
23483
|
+
async fetch(req) {
|
|
23484
|
+
const url2 = new URL(req.url);
|
|
23485
|
+
if (url2.pathname === SYNC_PATH) {
|
|
23486
|
+
if (!acceptsSyncToken(req, url2, auth, db))
|
|
23487
|
+
return unauthorized();
|
|
23488
|
+
} else {
|
|
23489
|
+
const exempt = url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH || url2.pathname === PAIR_PATH;
|
|
23490
|
+
if (!exempt && !hasValidToken(req, url2, auth)) {
|
|
23491
|
+
return wantsHtml(req) ? new Response(unlockPage(), { headers: HTML_HEADERS }) : unauthorized();
|
|
23492
|
+
}
|
|
22914
23493
|
}
|
|
23494
|
+
if (url2.pathname === RENEW_PATH) {
|
|
23495
|
+
const r3 = renewToken(req, url2, auth);
|
|
23496
|
+
return r3 ? Response.json(r3) : unauthorized();
|
|
23497
|
+
}
|
|
23498
|
+
if (req.method === "GET" && url2.pathname === "/docs.json") {
|
|
23499
|
+
return Response.json(buildOpenApi(package_default.version));
|
|
23500
|
+
}
|
|
23501
|
+
if (req.method === "GET" && url2.pathname === "/docs") {
|
|
23502
|
+
return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
|
|
23503
|
+
}
|
|
23504
|
+
if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
|
|
23505
|
+
const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
|
|
23506
|
+
const res = await serveWebui2(req);
|
|
23507
|
+
if (res)
|
|
23508
|
+
return withShim(res, auth);
|
|
23509
|
+
}
|
|
23510
|
+
if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
|
|
23511
|
+
const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
|
|
23512
|
+
const res = await serveSite2(req, ctx);
|
|
23513
|
+
if (res)
|
|
23514
|
+
return withShim(res, auth);
|
|
23515
|
+
}
|
|
23516
|
+
const route = routes.find((r3) => r3.method === req.method && r3.path === url2.pathname);
|
|
23517
|
+
if (route)
|
|
23518
|
+
return route.handler(req, ctx);
|
|
23519
|
+
return new Response("not found", { status: 404 });
|
|
22915
23520
|
}
|
|
22916
|
-
|
|
22917
|
-
|
|
22918
|
-
|
|
22919
|
-
|
|
22920
|
-
|
|
22921
|
-
return Response.json(buildOpenApi(package_default.version));
|
|
22922
|
-
}
|
|
22923
|
-
if (req.method === "GET" && url2.pathname === "/docs") {
|
|
22924
|
-
return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
|
|
22925
|
-
}
|
|
22926
|
-
if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
|
|
22927
|
-
const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
|
|
22928
|
-
const res = await serveWebui2(req);
|
|
22929
|
-
if (res)
|
|
22930
|
-
return withShim(res, auth);
|
|
22931
|
-
}
|
|
22932
|
-
if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
|
|
22933
|
-
const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
|
|
22934
|
-
const res = await serveSite2(req, ctx);
|
|
22935
|
-
if (res)
|
|
22936
|
-
return withShim(res, auth);
|
|
22937
|
-
}
|
|
22938
|
-
const route = routes.find((r3) => r3.method === req.method && r3.path === url2.pathname);
|
|
22939
|
-
if (route)
|
|
22940
|
-
return route.handler(req, ctx);
|
|
22941
|
-
return new Response("not found", { status: 404 });
|
|
23521
|
+
});
|
|
23522
|
+
} catch (e3) {
|
|
23523
|
+
const code = e3?.code;
|
|
23524
|
+
if (code === "EADDRINUSE") {
|
|
23525
|
+
throw new PortInUseError(port, host);
|
|
22942
23526
|
}
|
|
22943
|
-
|
|
23527
|
+
if (code === "EACCES") {
|
|
23528
|
+
throw new Error(`permission denied binding ${host}:${port} \u2014 ports below 1024 need elevated privileges; pick a higher port with --port`);
|
|
23529
|
+
}
|
|
23530
|
+
throw e3;
|
|
23531
|
+
}
|
|
22944
23532
|
let timer = null;
|
|
22945
23533
|
if (autoSync && syncIntervalMs > 0) {
|
|
22946
23534
|
timer = setInterval(() => {
|
|
@@ -22949,12 +23537,17 @@ function startServer(opts = {}) {
|
|
|
22949
23537
|
timer.unref?.();
|
|
22950
23538
|
}
|
|
22951
23539
|
const active = activeToken(auth);
|
|
23540
|
+
const authMode = opts.debug ? "disabled" : opts.token ? "static" : "managed";
|
|
22952
23541
|
return {
|
|
22953
23542
|
server,
|
|
22954
23543
|
node,
|
|
22955
23544
|
port: server.port ?? port,
|
|
23545
|
+
host,
|
|
22956
23546
|
token: active?.token ?? null,
|
|
22957
23547
|
exp: active?.exp ?? null,
|
|
23548
|
+
authMode,
|
|
23549
|
+
autoSync: Boolean(autoSync && syncIntervalMs > 0),
|
|
23550
|
+
syncIntervalMs,
|
|
22958
23551
|
stop() {
|
|
22959
23552
|
if (timer)
|
|
22960
23553
|
clearInterval(timer);
|
|
@@ -22963,6 +23556,129 @@ function startServer(opts = {}) {
|
|
|
22963
23556
|
};
|
|
22964
23557
|
}
|
|
22965
23558
|
|
|
23559
|
+
// src/cli/banner.ts
|
|
23560
|
+
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
23561
|
+
function supportsColor() {
|
|
23562
|
+
return Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
23563
|
+
}
|
|
23564
|
+
function visibleWidth(s2) {
|
|
23565
|
+
return s2.replace(ANSI, "").length;
|
|
23566
|
+
}
|
|
23567
|
+
var CODES = {
|
|
23568
|
+
reset: "\x1B[0m",
|
|
23569
|
+
dim: "\x1B[2m",
|
|
23570
|
+
bold: "\x1B[1m",
|
|
23571
|
+
green: "\x1B[32m",
|
|
23572
|
+
cyan: "\x1B[36m",
|
|
23573
|
+
yellow: "\x1B[33m",
|
|
23574
|
+
magenta: "\x1B[35m",
|
|
23575
|
+
red: "\x1B[31m"
|
|
23576
|
+
};
|
|
23577
|
+
function makePalette(on) {
|
|
23578
|
+
const wrap = (code) => (s2) => on ? `${code}${s2}${CODES.reset}` : s2;
|
|
23579
|
+
return {
|
|
23580
|
+
reset: (s2) => s2,
|
|
23581
|
+
dim: wrap(CODES.dim),
|
|
23582
|
+
bold: wrap(CODES.bold),
|
|
23583
|
+
green: wrap(CODES.green),
|
|
23584
|
+
cyan: wrap(CODES.cyan),
|
|
23585
|
+
yellow: wrap(CODES.yellow),
|
|
23586
|
+
magenta: wrap(CODES.magenta),
|
|
23587
|
+
red: wrap(CODES.red)
|
|
23588
|
+
};
|
|
23589
|
+
}
|
|
23590
|
+
var MONTHS = [
|
|
23591
|
+
"Jan",
|
|
23592
|
+
"Feb",
|
|
23593
|
+
"Mar",
|
|
23594
|
+
"Apr",
|
|
23595
|
+
"May",
|
|
23596
|
+
"Jun",
|
|
23597
|
+
"Jul",
|
|
23598
|
+
"Aug",
|
|
23599
|
+
"Sep",
|
|
23600
|
+
"Oct",
|
|
23601
|
+
"Nov",
|
|
23602
|
+
"Dec"
|
|
23603
|
+
];
|
|
23604
|
+
function humanizeExpiry(exp, now = Date.now()) {
|
|
23605
|
+
if (exp == null)
|
|
23606
|
+
return "";
|
|
23607
|
+
if (!Number.isFinite(exp))
|
|
23608
|
+
return "never expires";
|
|
23609
|
+
const ms = exp - now;
|
|
23610
|
+
if (ms <= 0)
|
|
23611
|
+
return "expired";
|
|
23612
|
+
const d2 = new Date(exp);
|
|
23613
|
+
const date5 = `${MONTHS[d2.getMonth()]} ${d2.getDate()}`;
|
|
23614
|
+
const mins = Math.round(ms / 60000);
|
|
23615
|
+
let rel;
|
|
23616
|
+
if (mins < 60)
|
|
23617
|
+
rel = `${mins}m`;
|
|
23618
|
+
else if (mins < 60 * 24)
|
|
23619
|
+
rel = `${Math.round(mins / 60)}h`;
|
|
23620
|
+
else
|
|
23621
|
+
rel = `${Math.round(mins / (60 * 24))} days`;
|
|
23622
|
+
return `expires in ${rel} \xB7 ${date5}`;
|
|
23623
|
+
}
|
|
23624
|
+
function formatSync(autoSync, ms) {
|
|
23625
|
+
if (!autoSync || ms <= 0)
|
|
23626
|
+
return "auto-sync off";
|
|
23627
|
+
const secs2 = Math.round(ms / 1000);
|
|
23628
|
+
const human = secs2 % 3600 === 0 ? `${secs2 / 3600}h` : secs2 % 60 === 0 ? `${secs2 / 60}m` : `${secs2}s`;
|
|
23629
|
+
return `every ${human}`;
|
|
23630
|
+
}
|
|
23631
|
+
function isLoopback(host) {
|
|
23632
|
+
return host === "127.0.0.1" || host === "::1" || host === "localhost" || host === "::";
|
|
23633
|
+
}
|
|
23634
|
+
function renderStartupBanner(info) {
|
|
23635
|
+
const on = info.color ?? supportsColor();
|
|
23636
|
+
const c3 = makePalette(on);
|
|
23637
|
+
const LABEL_W = 7;
|
|
23638
|
+
const label = (t2) => c3.dim(t2.padEnd(LABEL_W));
|
|
23639
|
+
const rows = [];
|
|
23640
|
+
const push = (plain, painted) => rows.push([plain, painted]);
|
|
23641
|
+
push(`\u25CF listening ${info.host}:${info.port}`, `${c3.green("\u25CF")} ${c3.dim("listening")} ${c3.bold(`${info.host}:${info.port}`)}`);
|
|
23642
|
+
if (!isLoopback(info.host)) {
|
|
23643
|
+
push("\u26A0 reachable on your network", c3.yellow("\u26A0 reachable on your network"));
|
|
23644
|
+
}
|
|
23645
|
+
push("", "");
|
|
23646
|
+
push(`${"WebUI".padEnd(LABEL_W)}${info.webuiUrl}`, `${label("WebUI")}${c3.cyan(info.webuiUrl)}`);
|
|
23647
|
+
push(`${"Docs".padEnd(LABEL_W)}${info.docsUrl}`, `${label("Docs")}${c3.cyan(info.docsUrl)}`);
|
|
23648
|
+
if (info.authMode === "disabled" || !info.token) {
|
|
23649
|
+
push(`${"Auth".padEnd(LABEL_W)}disabled (--debug)`, `${label("Auth")}${c3.red("disabled")} ${c3.dim("(--debug)")}`);
|
|
23650
|
+
} else {
|
|
23651
|
+
push(`${"Token".padEnd(LABEL_W)}${info.token}`, `${label("Token")}${c3.yellow(info.token)}`);
|
|
23652
|
+
const exp = humanizeExpiry(info.exp);
|
|
23653
|
+
if (exp)
|
|
23654
|
+
push(`${" ".repeat(LABEL_W)}${exp}`, `${" ".repeat(LABEL_W)}${c3.dim(exp)}`);
|
|
23655
|
+
}
|
|
23656
|
+
const sync = formatSync(info.autoSync, info.syncIntervalMs);
|
|
23657
|
+
const syncLabel = info.autoSync && info.syncIntervalMs > 0 ? `sync ${sync}` : sync;
|
|
23658
|
+
push(`${"Node".padEnd(LABEL_W)}${info.node} \xB7 ${syncLabel}`, `${label("Node")}${info.node} ${c3.dim("\xB7")} ${c3.dim(syncLabel)}`);
|
|
23659
|
+
const PAD = 2;
|
|
23660
|
+
const title = "metahub";
|
|
23661
|
+
const ver = `v${info.version}`;
|
|
23662
|
+
const footer = "Ctrl-C to stop";
|
|
23663
|
+
const contentW = Math.max(...rows.map(([plain]) => plain.length), title.length + ver.length + 6, footer.length + 4);
|
|
23664
|
+
const innerW = contentW + PAD * 2;
|
|
23665
|
+
const totalW = innerW + 2;
|
|
23666
|
+
const border = (lc, left, right, rc) => {
|
|
23667
|
+
const leftSeg = left ? `\u2500 ${left} ` : "\u2500";
|
|
23668
|
+
const rightSeg = right ? ` ${right} \u2500` : "\u2500";
|
|
23669
|
+
const dashes = totalW - 2 - visibleWidth(leftSeg) - visibleWidth(rightSeg);
|
|
23670
|
+
return `${lc}${leftSeg}${"\u2500".repeat(Math.max(0, dashes))}${rightSeg}${rc}`;
|
|
23671
|
+
};
|
|
23672
|
+
const top = border("\u256D", c3.bold(c3.magenta(title)), c3.dim(ver), "\u256E");
|
|
23673
|
+
const bottom = border("\u2570", c3.dim(footer), "", "\u256F");
|
|
23674
|
+
const body = rows.map(([plain, painted]) => {
|
|
23675
|
+
const fill = innerW - PAD * 2 - plain.length;
|
|
23676
|
+
return `\u2502${" ".repeat(PAD)}${painted}${" ".repeat(Math.max(0, fill) + PAD)}\u2502`;
|
|
23677
|
+
});
|
|
23678
|
+
return [top, ...body, bottom].join(`
|
|
23679
|
+
`);
|
|
23680
|
+
}
|
|
23681
|
+
|
|
22966
23682
|
// src/cli/help.ts
|
|
22967
23683
|
var ROOT_GUIDE = `metahub \u2014 local knowledge base for AI agents
|
|
22968
23684
|
Notion-like typed databases (tables + rows) and markdown documents, stored in
|
|
@@ -23196,6 +23912,8 @@ var main = defineCommand({
|
|
|
23196
23912
|
sync: sync_default,
|
|
23197
23913
|
snapshot: snapshot_default,
|
|
23198
23914
|
restore: restore_default,
|
|
23915
|
+
doctor: doctor_default,
|
|
23916
|
+
repair: repair_default,
|
|
23199
23917
|
config: config_default
|
|
23200
23918
|
}
|
|
23201
23919
|
});
|
|
@@ -23210,26 +23928,48 @@ var argv2 = process.argv.slice(2);
|
|
|
23210
23928
|
if (argv2.includes("--server")) {
|
|
23211
23929
|
const portFlag = flagValue(argv2, "port");
|
|
23212
23930
|
const intervalFlag = flagValue(argv2, "sync-interval");
|
|
23213
|
-
|
|
23214
|
-
|
|
23215
|
-
|
|
23216
|
-
|
|
23217
|
-
|
|
23218
|
-
|
|
23219
|
-
|
|
23220
|
-
|
|
23221
|
-
|
|
23931
|
+
let s2;
|
|
23932
|
+
try {
|
|
23933
|
+
s2 = startServer({
|
|
23934
|
+
port: portFlag != null ? Number(portFlag) : undefined,
|
|
23935
|
+
host: flagValue(argv2, "host"),
|
|
23936
|
+
debug: argv2.includes("--debug"),
|
|
23937
|
+
token: flagValue(argv2, "token") ?? process.env.METAHUB_TOKEN,
|
|
23938
|
+
syncIntervalMs: intervalFlag != null ? parseDuration(intervalFlag, 30000) : undefined,
|
|
23939
|
+
autoSync: argv2.includes("--no-auto-sync") ? false : undefined
|
|
23940
|
+
});
|
|
23941
|
+
} catch (e3) {
|
|
23942
|
+
fail(e3 instanceof Error ? e3.message : String(e3), e3 instanceof PortInUseError ? 98 : 1);
|
|
23943
|
+
}
|
|
23944
|
+
const webui = `http://localhost:${s2.port}`;
|
|
23945
|
+
const docs = `${webui}/docs`;
|
|
23222
23946
|
print({
|
|
23223
23947
|
server: "listening",
|
|
23224
23948
|
port: s2.port,
|
|
23949
|
+
host: s2.host,
|
|
23225
23950
|
nodeId: s2.node,
|
|
23226
23951
|
docs: `/docs`,
|
|
23952
|
+
webui,
|
|
23953
|
+
authMode: s2.authMode,
|
|
23227
23954
|
token: s2.token,
|
|
23228
|
-
exp: s2.exp != null && Number.isFinite(s2.exp) ? s2.exp : null
|
|
23229
|
-
|
|
23230
|
-
|
|
23955
|
+
exp: s2.exp != null && Number.isFinite(s2.exp) ? s2.exp : null,
|
|
23956
|
+
autoSync: s2.autoSync,
|
|
23957
|
+
syncIntervalMs: s2.syncIntervalMs
|
|
23958
|
+
}, () => renderStartupBanner({
|
|
23959
|
+
version: package_default.version,
|
|
23960
|
+
host: s2.host,
|
|
23961
|
+
port: s2.port,
|
|
23962
|
+
webuiUrl: webui,
|
|
23963
|
+
docsUrl: docs,
|
|
23964
|
+
authMode: s2.authMode,
|
|
23965
|
+
token: s2.token,
|
|
23966
|
+
exp: s2.exp,
|
|
23967
|
+
node: s2.node,
|
|
23968
|
+
autoSync: s2.autoSync,
|
|
23969
|
+
syncIntervalMs: s2.syncIntervalMs
|
|
23970
|
+
}));
|
|
23231
23971
|
} else {
|
|
23232
23972
|
runMain(main, { showUsage: showUsage2 });
|
|
23233
23973
|
}
|
|
23234
23974
|
|
|
23235
|
-
//# debugId=
|
|
23975
|
+
//# debugId=25AC050A4B6CCBA264756E2164756E21
|