latticesql 2.2.2 → 2.2.3
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/README.md +6 -4
- package/dist/cli.js +625 -86
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -2137,8 +2137,9 @@ Optional extras, each enabled by its own key/binary:
|
|
|
2137
2137
|
- **Voice** — set an OpenAI (Whisper) or ElevenLabs key to dictate into the composer.
|
|
2138
2138
|
- **File ingest** — reference a local file or paste text; it becomes a row in the
|
|
2139
2139
|
native `files` entity with extracted text + (with a Claude key) an
|
|
2140
|
-
LLM-written description and links to related records.
|
|
2141
|
-
|
|
2140
|
+
LLM-written description and links to related records. Documents (PDF, Word,
|
|
2141
|
+
PowerPoint, Excel, OpenDocument, EPUB, RTF) are parsed natively in-process —
|
|
2142
|
+
no external CLI.
|
|
2142
2143
|
|
|
2143
2144
|
Chat threads, files, and secrets are all stored as native Lattice entities.
|
|
2144
2145
|
|
|
@@ -2295,8 +2296,9 @@ the library API is unchanged and fully backwards-compatible.
|
|
|
2295
2296
|
entity. A subscription **Connect** link (PKCE) appears when the `ANTHROPIC_OAUTH_*`
|
|
2296
2297
|
values are set (see [`.env.example`](.env.example)).
|
|
2297
2298
|
- **Drop files / paste text / images / URLs.** Sources become native `files` rows
|
|
2298
|
-
(referenced, not copied) and are extracted —
|
|
2299
|
-
|
|
2299
|
+
(referenced, not copied) and are extracted — documents (PDF / Office /
|
|
2300
|
+
OpenDocument / EPUB / RTF) parsed **natively in-process**, **images via Claude
|
|
2301
|
+
vision**, a pasted **URL crawled** for readable text —
|
|
2300
2302
|
then summarized with **Claude Haiku** and classified against your records, and
|
|
2301
2303
|
**added, enriched, and linked** automatically, **auto-creating the junction table
|
|
2302
2304
|
when none exists** (and a new object when a source fits nothing). All audited and
|
package/dist/cli.js
CHANGED
|
@@ -6279,7 +6279,7 @@ async function checkForUpdate(pkgName, currentVersion) {
|
|
|
6279
6279
|
|
|
6280
6280
|
// src/gui/server.ts
|
|
6281
6281
|
import { createServer } from "http";
|
|
6282
|
-
import { spawn as
|
|
6282
|
+
import { spawn as spawn2 } from "child_process";
|
|
6283
6283
|
import {
|
|
6284
6284
|
existsSync as existsSync21,
|
|
6285
6285
|
mkdirSync as mkdirSync10,
|
|
@@ -7584,6 +7584,13 @@ var css = `
|
|
|
7584
7584
|
.grants-panel .grants-title { font-weight: 600; margin-bottom: 6px; }
|
|
7585
7585
|
.grants-panel .grants-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; cursor: pointer; }
|
|
7586
7586
|
.grants-panel .grants-row input { accent-color: var(--accent); }
|
|
7587
|
+
/* Reconnect-required notice: a cloud opened via an unsupported direct
|
|
7588
|
+
database connection serves no data until reconnected through a server. */
|
|
7589
|
+
.cloud-reconnect {
|
|
7590
|
+
padding: 10px 16px; font-size: 13px; line-height: 1.4;
|
|
7591
|
+
background: rgba(239, 68, 68, 0.12); color: var(--text);
|
|
7592
|
+
border-bottom: 1px solid rgba(239, 68, 68, 0.5);
|
|
7593
|
+
}
|
|
7587
7594
|
|
|
7588
7595
|
/* Inline create-row at the bottom of every table */
|
|
7589
7596
|
tr.create-row td { background: var(--surface-2); }
|
|
@@ -9074,6 +9081,20 @@ var appJs = `
|
|
|
9074
9081
|
|
|
9075
9082
|
window.addEventListener('hashchange', renderRoute);
|
|
9076
9083
|
|
|
9084
|
+
// 2.2.3: a cloud reached via a raw postgres:// connection is refused (it
|
|
9085
|
+
// can't enforce per-user access). The server serves no cloud data; tell the
|
|
9086
|
+
// operator to reconnect through a user-authenticated server.
|
|
9087
|
+
function initCloudReconnectNotice() {
|
|
9088
|
+
fetchJson('/api/dbconfig').then(function (d) {
|
|
9089
|
+
if (!d || !d.cloudReconnectRequired) return;
|
|
9090
|
+
var bar = document.getElementById('cloud-reconnect');
|
|
9091
|
+
if (!bar) return;
|
|
9092
|
+
bar.textContent = 'This cloud is connected with a direct database connection, which is no longer supported. Reconnect through a server (sign in as a user) to access it securely.';
|
|
9093
|
+
bar.hidden = false;
|
|
9094
|
+
}).catch(function () { /* dbconfig unavailable (server mode) \u2014 nothing to show */ });
|
|
9095
|
+
}
|
|
9096
|
+
initCloudReconnectNotice();
|
|
9097
|
+
|
|
9077
9098
|
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
9078
9099
|
// Sidebar
|
|
9079
9100
|
// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -14198,6 +14219,7 @@ var guiAppHtml = `<!doctype html>
|
|
|
14198
14219
|
</svg>
|
|
14199
14220
|
</button>
|
|
14200
14221
|
</header>
|
|
14222
|
+
<div class="cloud-reconnect" id="cloud-reconnect" hidden></div>
|
|
14201
14223
|
<div class="layout">
|
|
14202
14224
|
<nav class="sidebar">
|
|
14203
14225
|
<label class="sidebar-advanced toggle" title="Advanced mode \u2014 row/table editor instead of the file workspace">
|
|
@@ -19359,7 +19381,10 @@ async function dispatchDbConfigRoute(req, res, ctx) {
|
|
|
19359
19381
|
// without a local `__lattice_team_connections` row (which doesn't
|
|
19360
19382
|
// exist when the team cloud itself is the active database).
|
|
19361
19383
|
teamId: ctx.teamMembership?.teamId ?? null,
|
|
19362
|
-
myUserId: ctx.teamMembership?.myUserId ?? null
|
|
19384
|
+
myUserId: ctx.teamMembership?.myUserId ?? null,
|
|
19385
|
+
// 2.2.3: a direct postgres:// cloud connection is refused — the SPA
|
|
19386
|
+
// shows a "reconnect through a server" prompt instead of cloud data.
|
|
19387
|
+
cloudReconnectRequired: ctx.cloudReconnectRequired
|
|
19363
19388
|
});
|
|
19364
19389
|
});
|
|
19365
19390
|
return true;
|
|
@@ -21533,10 +21558,538 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
21533
21558
|
import { basename as basename10, extname as extname2, resolve as resolve8, join as join20 } from "path";
|
|
21534
21559
|
|
|
21535
21560
|
// src/gui/ai/extract.ts
|
|
21536
|
-
import { readFile } from "fs/promises";
|
|
21561
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
21537
21562
|
import { extname, basename as basename7 } from "path";
|
|
21538
|
-
|
|
21563
|
+
|
|
21564
|
+
// src/gui/ai/doc-extractors.ts
|
|
21565
|
+
import { readFile } from "fs/promises";
|
|
21539
21566
|
var MAX_TEXT = 2e5;
|
|
21567
|
+
var MAX_ENTRY_BYTES = 64 * 1024 * 1024;
|
|
21568
|
+
var MAX_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
21569
|
+
var PDF_TIMEOUT_MS = 3e4;
|
|
21570
|
+
var textDecoder = new TextDecoder("utf-8");
|
|
21571
|
+
function decodeUtf8(bytes) {
|
|
21572
|
+
return textDecoder.decode(bytes);
|
|
21573
|
+
}
|
|
21574
|
+
async function loadOptional(specifier) {
|
|
21575
|
+
try {
|
|
21576
|
+
return await import(specifier);
|
|
21577
|
+
} catch {
|
|
21578
|
+
return null;
|
|
21579
|
+
}
|
|
21580
|
+
}
|
|
21581
|
+
function nullIfEmpty(s) {
|
|
21582
|
+
const t = s.trim();
|
|
21583
|
+
return t ? t : null;
|
|
21584
|
+
}
|
|
21585
|
+
function withTimeout(p, ms, label) {
|
|
21586
|
+
let timer;
|
|
21587
|
+
const timeout = new Promise((_, reject) => {
|
|
21588
|
+
timer = setTimeout(() => {
|
|
21589
|
+
reject(new Error(label));
|
|
21590
|
+
}, ms);
|
|
21591
|
+
timer.unref?.();
|
|
21592
|
+
});
|
|
21593
|
+
return Promise.race([
|
|
21594
|
+
p.finally(() => {
|
|
21595
|
+
clearTimeout(timer);
|
|
21596
|
+
}),
|
|
21597
|
+
timeout
|
|
21598
|
+
]);
|
|
21599
|
+
}
|
|
21600
|
+
function decodeXmlEntities(s) {
|
|
21601
|
+
return s.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&#x([0-9a-fA-F]+);/g, (_, h) => safeCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => safeCodePoint(parseInt(d, 10))).replace(/&/g, "&");
|
|
21602
|
+
}
|
|
21603
|
+
function safeCodePoint(n) {
|
|
21604
|
+
if (!Number.isFinite(n) || n < 0 || n > 1114111) return "";
|
|
21605
|
+
try {
|
|
21606
|
+
return String.fromCodePoint(n);
|
|
21607
|
+
} catch {
|
|
21608
|
+
return "";
|
|
21609
|
+
}
|
|
21610
|
+
}
|
|
21611
|
+
function stripTags(s) {
|
|
21612
|
+
let out = "";
|
|
21613
|
+
let i = 0;
|
|
21614
|
+
while (i < s.length) {
|
|
21615
|
+
const lt = s.indexOf("<", i);
|
|
21616
|
+
if (lt < 0) {
|
|
21617
|
+
out += s.slice(i);
|
|
21618
|
+
break;
|
|
21619
|
+
}
|
|
21620
|
+
out += s.slice(i, lt);
|
|
21621
|
+
const gt = s.indexOf(">", lt + 1);
|
|
21622
|
+
if (gt < 0) break;
|
|
21623
|
+
i = gt + 1;
|
|
21624
|
+
}
|
|
21625
|
+
return out;
|
|
21626
|
+
}
|
|
21627
|
+
function isNameBoundary(code) {
|
|
21628
|
+
return code === 32 || // space
|
|
21629
|
+
code === 9 || // tab
|
|
21630
|
+
code === 10 || // \n
|
|
21631
|
+
code === 13 || // \r
|
|
21632
|
+
code === 62 || // >
|
|
21633
|
+
code === 47 || // /
|
|
21634
|
+
Number.isNaN(code);
|
|
21635
|
+
}
|
|
21636
|
+
function eachElement(xml, tag, cb) {
|
|
21637
|
+
const open = "<" + tag;
|
|
21638
|
+
const close = "</" + tag + ">";
|
|
21639
|
+
let i = 0;
|
|
21640
|
+
while (i < xml.length) {
|
|
21641
|
+
const s = xml.indexOf(open, i);
|
|
21642
|
+
if (s < 0) break;
|
|
21643
|
+
const ne = s + open.length;
|
|
21644
|
+
if (!isNameBoundary(xml.charCodeAt(ne))) {
|
|
21645
|
+
i = ne;
|
|
21646
|
+
continue;
|
|
21647
|
+
}
|
|
21648
|
+
const gt = xml.indexOf(">", ne);
|
|
21649
|
+
if (gt < 0) break;
|
|
21650
|
+
const selfClose = xml.charCodeAt(gt - 1) === 47;
|
|
21651
|
+
const attrs = xml.slice(ne, selfClose ? gt - 1 : gt);
|
|
21652
|
+
if (selfClose) {
|
|
21653
|
+
cb(attrs, "", s);
|
|
21654
|
+
i = gt + 1;
|
|
21655
|
+
continue;
|
|
21656
|
+
}
|
|
21657
|
+
const e = xml.indexOf(close, gt + 1);
|
|
21658
|
+
if (e < 0) break;
|
|
21659
|
+
cb(attrs, xml.slice(gt + 1, e), s);
|
|
21660
|
+
i = e + close.length;
|
|
21661
|
+
}
|
|
21662
|
+
}
|
|
21663
|
+
function stripElement(xml, tag) {
|
|
21664
|
+
const open = "<" + tag;
|
|
21665
|
+
const close = "</" + tag + ">";
|
|
21666
|
+
let out = "";
|
|
21667
|
+
let i = 0;
|
|
21668
|
+
while (i < xml.length) {
|
|
21669
|
+
const s = xml.indexOf(open, i);
|
|
21670
|
+
if (s < 0) {
|
|
21671
|
+
out += xml.slice(i);
|
|
21672
|
+
break;
|
|
21673
|
+
}
|
|
21674
|
+
const ne = s + open.length;
|
|
21675
|
+
if (!isNameBoundary(xml.charCodeAt(ne))) {
|
|
21676
|
+
out += xml.slice(i, ne);
|
|
21677
|
+
i = ne;
|
|
21678
|
+
continue;
|
|
21679
|
+
}
|
|
21680
|
+
const gt = xml.indexOf(">", ne);
|
|
21681
|
+
if (gt < 0) {
|
|
21682
|
+
out += xml.slice(i);
|
|
21683
|
+
break;
|
|
21684
|
+
}
|
|
21685
|
+
out += xml.slice(i, s);
|
|
21686
|
+
if (xml.charCodeAt(gt - 1) === 47) {
|
|
21687
|
+
i = gt + 1;
|
|
21688
|
+
continue;
|
|
21689
|
+
}
|
|
21690
|
+
const e = xml.indexOf(close, gt + 1);
|
|
21691
|
+
if (e < 0) break;
|
|
21692
|
+
i = e + close.length;
|
|
21693
|
+
}
|
|
21694
|
+
return out;
|
|
21695
|
+
}
|
|
21696
|
+
function concatTagText(xml, tag) {
|
|
21697
|
+
let out = "";
|
|
21698
|
+
eachElement(xml, tag, (_, inner) => {
|
|
21699
|
+
out += decodeXmlEntities(stripTags(inner));
|
|
21700
|
+
});
|
|
21701
|
+
return out;
|
|
21702
|
+
}
|
|
21703
|
+
function firstTagText(xml, tag) {
|
|
21704
|
+
let found = "";
|
|
21705
|
+
let done = false;
|
|
21706
|
+
eachElement(xml, tag, (_, inner) => {
|
|
21707
|
+
if (done) return;
|
|
21708
|
+
found = inner;
|
|
21709
|
+
done = true;
|
|
21710
|
+
});
|
|
21711
|
+
return found;
|
|
21712
|
+
}
|
|
21713
|
+
function stripHtml(html) {
|
|
21714
|
+
const noScript = stripElement(stripElement(html, "script"), "style");
|
|
21715
|
+
const text = decodeXmlEntities(stripTags(noScript));
|
|
21716
|
+
return text.replace(/[ \t\f\r]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
21717
|
+
}
|
|
21718
|
+
async function unzip(path2) {
|
|
21719
|
+
const fflate = await loadOptional("fflate");
|
|
21720
|
+
if (!fflate || typeof fflate.unzipSync !== "function") return null;
|
|
21721
|
+
try {
|
|
21722
|
+
const buf = await readFile(path2);
|
|
21723
|
+
let total = 0;
|
|
21724
|
+
return fflate.unzipSync(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength), {
|
|
21725
|
+
filter: (file) => {
|
|
21726
|
+
const size = file.originalSize || 0;
|
|
21727
|
+
if (size > MAX_ENTRY_BYTES) throw new Error("zip entry exceeds size cap");
|
|
21728
|
+
total += size;
|
|
21729
|
+
if (total > MAX_TOTAL_BYTES) throw new Error("zip total exceeds size cap");
|
|
21730
|
+
return true;
|
|
21731
|
+
}
|
|
21732
|
+
});
|
|
21733
|
+
} catch {
|
|
21734
|
+
return null;
|
|
21735
|
+
}
|
|
21736
|
+
}
|
|
21737
|
+
async function extractDocx(path2) {
|
|
21738
|
+
const mod = await loadOptional("mammoth");
|
|
21739
|
+
const lib = mod?.default ?? mod;
|
|
21740
|
+
if (!lib || typeof lib.extractRawText !== "function") return null;
|
|
21741
|
+
try {
|
|
21742
|
+
const { value } = await lib.extractRawText({ path: path2 });
|
|
21743
|
+
return nullIfEmpty(value);
|
|
21744
|
+
} catch {
|
|
21745
|
+
return null;
|
|
21746
|
+
}
|
|
21747
|
+
}
|
|
21748
|
+
async function extractDoc(path2) {
|
|
21749
|
+
const mod = await loadOptional(
|
|
21750
|
+
"word-extractor"
|
|
21751
|
+
);
|
|
21752
|
+
const Ctor = mod && "default" in mod ? mod.default : mod;
|
|
21753
|
+
if (typeof Ctor !== "function") return null;
|
|
21754
|
+
try {
|
|
21755
|
+
const doc = await new Ctor().extract(path2);
|
|
21756
|
+
return nullIfEmpty(doc.getBody());
|
|
21757
|
+
} catch {
|
|
21758
|
+
return null;
|
|
21759
|
+
}
|
|
21760
|
+
}
|
|
21761
|
+
async function extractPdf(path2) {
|
|
21762
|
+
const unpdf = await loadOptional("unpdf");
|
|
21763
|
+
if (!unpdf || typeof unpdf.getDocumentProxy !== "function") return null;
|
|
21764
|
+
try {
|
|
21765
|
+
const buf = await readFile(path2);
|
|
21766
|
+
const data = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
21767
|
+
const text = await withTimeout(
|
|
21768
|
+
(async () => {
|
|
21769
|
+
const pdf = await unpdf.getDocumentProxy(data);
|
|
21770
|
+
const out = await unpdf.extractText(pdf, { mergePages: true });
|
|
21771
|
+
return out.text;
|
|
21772
|
+
})(),
|
|
21773
|
+
PDF_TIMEOUT_MS,
|
|
21774
|
+
"pdf extract timeout"
|
|
21775
|
+
);
|
|
21776
|
+
return nullIfEmpty(text);
|
|
21777
|
+
} catch {
|
|
21778
|
+
return null;
|
|
21779
|
+
}
|
|
21780
|
+
}
|
|
21781
|
+
function partNumber(name) {
|
|
21782
|
+
const m = /(\d+)\.xml$/.exec(name);
|
|
21783
|
+
return m?.[1] ? parseInt(m[1], 10) : 0;
|
|
21784
|
+
}
|
|
21785
|
+
function slideText(xml) {
|
|
21786
|
+
const paras = [];
|
|
21787
|
+
eachElement(xml, "a:p", (_, inner) => {
|
|
21788
|
+
const runs = concatTagText(inner, "a:t");
|
|
21789
|
+
if (runs.trim()) paras.push(runs);
|
|
21790
|
+
});
|
|
21791
|
+
if (paras.length === 0) {
|
|
21792
|
+
const runs = concatTagText(xml, "a:t");
|
|
21793
|
+
if (runs.trim()) paras.push(runs);
|
|
21794
|
+
}
|
|
21795
|
+
return paras.join("\n");
|
|
21796
|
+
}
|
|
21797
|
+
async function extractPptx(path2) {
|
|
21798
|
+
const entries = await unzip(path2);
|
|
21799
|
+
if (!entries) return null;
|
|
21800
|
+
const slides = Object.keys(entries).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n)).sort((a, b) => partNumber(a) - partNumber(b));
|
|
21801
|
+
if (slides.length === 0) return null;
|
|
21802
|
+
const parts = [];
|
|
21803
|
+
let total = 0;
|
|
21804
|
+
for (const n of slides) {
|
|
21805
|
+
if (total >= MAX_TEXT) break;
|
|
21806
|
+
const bytes = entries[n];
|
|
21807
|
+
if (!bytes) continue;
|
|
21808
|
+
const text = slideText(decodeUtf8(bytes)).replace(/[ \t]+/g, " ").trim();
|
|
21809
|
+
if (text) {
|
|
21810
|
+
parts.push(text);
|
|
21811
|
+
total += text.length + 2;
|
|
21812
|
+
}
|
|
21813
|
+
}
|
|
21814
|
+
return nullIfEmpty(parts.join("\n\n"));
|
|
21815
|
+
}
|
|
21816
|
+
async function extractXlsx(path2) {
|
|
21817
|
+
const entries = await unzip(path2);
|
|
21818
|
+
if (!entries) return null;
|
|
21819
|
+
const shared = [];
|
|
21820
|
+
const ssBytes = entries["xl/sharedStrings.xml"];
|
|
21821
|
+
if (ssBytes) {
|
|
21822
|
+
eachElement(decodeUtf8(ssBytes), "si", (_, inner) => {
|
|
21823
|
+
shared.push(concatTagText(stripElement(inner, "rPh"), "t"));
|
|
21824
|
+
});
|
|
21825
|
+
}
|
|
21826
|
+
const sheetNames = Object.keys(entries).filter((n) => /^xl\/worksheets\/sheet\d+\.xml$/.test(n)).sort((a, b) => partNumber(a) - partNumber(b));
|
|
21827
|
+
const rowsOut = [];
|
|
21828
|
+
let total = 0;
|
|
21829
|
+
for (const n of sheetNames) {
|
|
21830
|
+
if (total >= MAX_TEXT) break;
|
|
21831
|
+
const bytes = entries[n];
|
|
21832
|
+
if (!bytes) continue;
|
|
21833
|
+
eachElement(decodeUtf8(bytes), "row", (_, rowInner) => {
|
|
21834
|
+
if (total >= MAX_TEXT) return;
|
|
21835
|
+
const cells = [];
|
|
21836
|
+
eachElement(rowInner, "c", (attrs, body) => {
|
|
21837
|
+
const type = /\bt="([^"]+)"/.exec(attrs)?.[1];
|
|
21838
|
+
let val = "";
|
|
21839
|
+
if (type === "s") {
|
|
21840
|
+
const idx = parseInt(firstTagText(body, "v"), 10);
|
|
21841
|
+
val = Number.isInteger(idx) ? shared[idx] ?? "" : "";
|
|
21842
|
+
} else if (type === "inlineStr") {
|
|
21843
|
+
val = concatTagText(body, "t");
|
|
21844
|
+
} else {
|
|
21845
|
+
val = decodeXmlEntities(firstTagText(body, "v"));
|
|
21846
|
+
}
|
|
21847
|
+
if (val) cells.push(val);
|
|
21848
|
+
});
|
|
21849
|
+
if (cells.length) {
|
|
21850
|
+
const line = cells.join(" ");
|
|
21851
|
+
rowsOut.push(line);
|
|
21852
|
+
total += line.length + 1;
|
|
21853
|
+
}
|
|
21854
|
+
});
|
|
21855
|
+
}
|
|
21856
|
+
return nullIfEmpty(rowsOut.join("\n"));
|
|
21857
|
+
}
|
|
21858
|
+
function odfWhitespace(s) {
|
|
21859
|
+
return s.replace(/<text:tab\b[^>]{0,400}\/?>/g, " ").replace(/<text:line-break\b[^>]{0,400}\/?>/g, "\n").replace(
|
|
21860
|
+
/<text:s\b[^>]{0,400}\btext:c="(\d+)"[^>]{0,400}\/?>/g,
|
|
21861
|
+
(_, c) => " ".repeat(Math.min(parseInt(c, 10) || 1, 100))
|
|
21862
|
+
).replace(/<text:s\b[^>]{0,400}\/?>/g, " ");
|
|
21863
|
+
}
|
|
21864
|
+
function odfParagraph(inner) {
|
|
21865
|
+
return decodeXmlEntities(stripTags(odfWhitespace(inner))).trim();
|
|
21866
|
+
}
|
|
21867
|
+
async function extractOdfText(path2) {
|
|
21868
|
+
const entries = await unzip(path2);
|
|
21869
|
+
if (!entries) return null;
|
|
21870
|
+
const contentBytes = entries["content.xml"];
|
|
21871
|
+
if (!contentBytes) return null;
|
|
21872
|
+
const xml = decodeUtf8(contentBytes);
|
|
21873
|
+
const items = [];
|
|
21874
|
+
const collect = (_, inner, start) => {
|
|
21875
|
+
const line = odfParagraph(inner);
|
|
21876
|
+
if (line) items.push([start, line]);
|
|
21877
|
+
};
|
|
21878
|
+
eachElement(xml, "text:p", collect);
|
|
21879
|
+
eachElement(xml, "text:h", collect);
|
|
21880
|
+
items.sort((a, b) => a[0] - b[0]);
|
|
21881
|
+
const lines = [];
|
|
21882
|
+
let total = 0;
|
|
21883
|
+
for (const [, line] of items) {
|
|
21884
|
+
if (total >= MAX_TEXT) break;
|
|
21885
|
+
lines.push(line);
|
|
21886
|
+
total += line.length + 1;
|
|
21887
|
+
}
|
|
21888
|
+
return nullIfEmpty(lines.join("\n"));
|
|
21889
|
+
}
|
|
21890
|
+
async function extractOds(path2) {
|
|
21891
|
+
const entries = await unzip(path2);
|
|
21892
|
+
if (!entries) return null;
|
|
21893
|
+
const contentBytes = entries["content.xml"];
|
|
21894
|
+
if (!contentBytes) return null;
|
|
21895
|
+
const xml = decodeUtf8(contentBytes);
|
|
21896
|
+
const rows = [];
|
|
21897
|
+
let total = 0;
|
|
21898
|
+
eachElement(xml, "table:table-row", (_, rowInner) => {
|
|
21899
|
+
if (total >= MAX_TEXT) return;
|
|
21900
|
+
const cells = [];
|
|
21901
|
+
eachElement(rowInner, "table:table-cell", (attrs, body) => {
|
|
21902
|
+
const parts = [];
|
|
21903
|
+
eachElement(body, "text:p", (__, p) => {
|
|
21904
|
+
const t = odfParagraph(p);
|
|
21905
|
+
if (t) parts.push(t);
|
|
21906
|
+
});
|
|
21907
|
+
let val = parts.join(" ").trim();
|
|
21908
|
+
if (!val) {
|
|
21909
|
+
const ov = /\boffice:(?:value|date-value|time-value|string-value|boolean-value)="([^"]*)"/.exec(
|
|
21910
|
+
attrs
|
|
21911
|
+
)?.[1];
|
|
21912
|
+
if (ov) val = decodeXmlEntities(ov);
|
|
21913
|
+
}
|
|
21914
|
+
if (val) cells.push(val);
|
|
21915
|
+
});
|
|
21916
|
+
if (cells.length) {
|
|
21917
|
+
const line = cells.join(" ");
|
|
21918
|
+
rows.push(line);
|
|
21919
|
+
total += line.length + 1;
|
|
21920
|
+
}
|
|
21921
|
+
});
|
|
21922
|
+
return nullIfEmpty(rows.join("\n"));
|
|
21923
|
+
}
|
|
21924
|
+
function normalizeZipPath(p) {
|
|
21925
|
+
const parts = [];
|
|
21926
|
+
for (const seg of p.split("/")) {
|
|
21927
|
+
if (seg === "" || seg === ".") continue;
|
|
21928
|
+
if (seg === "..") parts.pop();
|
|
21929
|
+
else parts.push(seg);
|
|
21930
|
+
}
|
|
21931
|
+
return parts.join("/");
|
|
21932
|
+
}
|
|
21933
|
+
function resolveHref(baseDir, href) {
|
|
21934
|
+
let h = href.split("#")[0]?.split("?")[0] ?? "";
|
|
21935
|
+
try {
|
|
21936
|
+
h = decodeURIComponent(h);
|
|
21937
|
+
} catch {
|
|
21938
|
+
}
|
|
21939
|
+
return normalizeZipPath(baseDir + h);
|
|
21940
|
+
}
|
|
21941
|
+
async function extractEpub(path2) {
|
|
21942
|
+
const entries = await unzip(path2);
|
|
21943
|
+
if (!entries) return null;
|
|
21944
|
+
let order = [];
|
|
21945
|
+
const container = entries["META-INF/container.xml"];
|
|
21946
|
+
const opfPath = container ? /full-path="([^"]+)"/.exec(decodeUtf8(container))?.[1] : void 0;
|
|
21947
|
+
if (opfPath && entries[opfPath]) {
|
|
21948
|
+
const opf = decodeUtf8(entries[opfPath]);
|
|
21949
|
+
const manifest = {};
|
|
21950
|
+
eachElement(opf, "item", (attrs) => {
|
|
21951
|
+
const id = /\bid="([^"]+)"/.exec(attrs)?.[1];
|
|
21952
|
+
const href = /\bhref="([^"]+)"/.exec(attrs)?.[1];
|
|
21953
|
+
if (id && href) manifest[id] = href;
|
|
21954
|
+
});
|
|
21955
|
+
const baseDir = opfPath.includes("/") ? opfPath.slice(0, opfPath.lastIndexOf("/") + 1) : "";
|
|
21956
|
+
eachElement(opf, "itemref", (attrs) => {
|
|
21957
|
+
const idref = /\bidref="([^"]+)"/.exec(attrs)?.[1];
|
|
21958
|
+
const href = idref ? manifest[idref] : void 0;
|
|
21959
|
+
if (href) order.push(resolveHref(baseDir, href));
|
|
21960
|
+
});
|
|
21961
|
+
}
|
|
21962
|
+
if (order.length === 0) {
|
|
21963
|
+
order = Object.keys(entries).filter((n) => /\.x?html?$/i.test(n)).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
21964
|
+
}
|
|
21965
|
+
const parts = [];
|
|
21966
|
+
let total = 0;
|
|
21967
|
+
for (const n of order) {
|
|
21968
|
+
if (total >= MAX_TEXT) break;
|
|
21969
|
+
const bytes = entries[n];
|
|
21970
|
+
if (!bytes) continue;
|
|
21971
|
+
const body = stripHtml(decodeUtf8(bytes));
|
|
21972
|
+
if (body) {
|
|
21973
|
+
parts.push(body);
|
|
21974
|
+
total += body.length + 2;
|
|
21975
|
+
}
|
|
21976
|
+
}
|
|
21977
|
+
return nullIfEmpty(parts.join("\n\n"));
|
|
21978
|
+
}
|
|
21979
|
+
var RTF_IGNORED_DESTINATIONS = /^\\(?:\*|(?:fonttbl|colortbl|stylesheet|info|pict|themedata|colorschememapping|latentstyles|datastore|listtable|listoverridetable|rsidtbl|generator|operator|xmlnstbl|wgrffmtfilter|mmathPr)(?![a-zA-Z]))/;
|
|
21980
|
+
function stripRtfDestinations(s) {
|
|
21981
|
+
const kept = [];
|
|
21982
|
+
let keepFrom = 0;
|
|
21983
|
+
let i = 0;
|
|
21984
|
+
while (i < s.length) {
|
|
21985
|
+
if (s[i] === "{" && RTF_IGNORED_DESTINATIONS.test(s.slice(i + 1, i + 40))) {
|
|
21986
|
+
const pre = s.slice(keepFrom, i);
|
|
21987
|
+
kept.push(pre);
|
|
21988
|
+
if (/\\[a-zA-Z]+$/.test(pre.slice(-40))) kept.push(" ");
|
|
21989
|
+
let depth = 1;
|
|
21990
|
+
let j = i + 1;
|
|
21991
|
+
for (; j < s.length && depth > 0; j++) {
|
|
21992
|
+
const ch = s[j];
|
|
21993
|
+
if (ch === "\\")
|
|
21994
|
+
j++;
|
|
21995
|
+
else if (ch === "{") depth++;
|
|
21996
|
+
else if (ch === "}") depth--;
|
|
21997
|
+
}
|
|
21998
|
+
i = j;
|
|
21999
|
+
keepFrom = i;
|
|
22000
|
+
} else {
|
|
22001
|
+
i++;
|
|
22002
|
+
}
|
|
22003
|
+
}
|
|
22004
|
+
kept.push(s.slice(keepFrom));
|
|
22005
|
+
return kept.join("");
|
|
22006
|
+
}
|
|
22007
|
+
var CP1252_HIGH = {
|
|
22008
|
+
128: 8364,
|
|
22009
|
+
130: 8218,
|
|
22010
|
+
131: 402,
|
|
22011
|
+
132: 8222,
|
|
22012
|
+
133: 8230,
|
|
22013
|
+
134: 8224,
|
|
22014
|
+
135: 8225,
|
|
22015
|
+
136: 710,
|
|
22016
|
+
137: 8240,
|
|
22017
|
+
138: 352,
|
|
22018
|
+
139: 8249,
|
|
22019
|
+
140: 338,
|
|
22020
|
+
142: 381,
|
|
22021
|
+
145: 8216,
|
|
22022
|
+
146: 8217,
|
|
22023
|
+
147: 8220,
|
|
22024
|
+
148: 8221,
|
|
22025
|
+
149: 8226,
|
|
22026
|
+
150: 8211,
|
|
22027
|
+
151: 8212,
|
|
22028
|
+
152: 732,
|
|
22029
|
+
153: 8482,
|
|
22030
|
+
154: 353,
|
|
22031
|
+
155: 8250,
|
|
22032
|
+
156: 339,
|
|
22033
|
+
158: 382,
|
|
22034
|
+
159: 376
|
|
22035
|
+
};
|
|
22036
|
+
function cp1252Char(byte) {
|
|
22037
|
+
if (byte >= 128 && byte <= 159) {
|
|
22038
|
+
const cp = CP1252_HIGH[byte];
|
|
22039
|
+
return cp ? safeCodePoint(cp) : "";
|
|
22040
|
+
}
|
|
22041
|
+
return safeCodePoint(byte);
|
|
22042
|
+
}
|
|
22043
|
+
function rtfToText(rtf) {
|
|
22044
|
+
let s = stripRtfDestinations(rtf);
|
|
22045
|
+
s = s.replace(/\\'([0-9a-fA-F]{2})/g, (_, h) => cp1252Char(parseInt(h, 16)));
|
|
22046
|
+
s = s.replace(/\\u(-?\d+)\s?\??/g, (_, d) => {
|
|
22047
|
+
let n = parseInt(d, 10);
|
|
22048
|
+
if (n < 0) n += 65536;
|
|
22049
|
+
return safeCodePoint(n);
|
|
22050
|
+
});
|
|
22051
|
+
s = s.replace(/\\par[d]?\b/g, "\n").replace(/\\line\b/g, "\n").replace(/\\sect\b/g, "\n").replace(/\\page\b/g, "\n").replace(/\\tab\b/g, " ");
|
|
22052
|
+
s = s.replace(/\\[a-zA-Z]+-?\d*\s?/g, "").replace(/\\[^a-zA-Z]/g, "");
|
|
22053
|
+
s = s.replace(/[{}]/g, "");
|
|
22054
|
+
return s.replace(/[ \t]+/g, (m) => m.includes(" ") ? " " : " ").replace(/[ \t]\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
22055
|
+
}
|
|
22056
|
+
async function extractRtf(path2) {
|
|
22057
|
+
try {
|
|
22058
|
+
const raw = await readFile(path2, "latin1");
|
|
22059
|
+
if (!raw.startsWith("{\\rtf")) return null;
|
|
22060
|
+
return nullIfEmpty(rtfToText(raw));
|
|
22061
|
+
} catch {
|
|
22062
|
+
return null;
|
|
22063
|
+
}
|
|
22064
|
+
}
|
|
22065
|
+
async function extractDocument(path2, ext) {
|
|
22066
|
+
switch (ext) {
|
|
22067
|
+
case ".docx":
|
|
22068
|
+
return extractDocx(path2);
|
|
22069
|
+
case ".doc":
|
|
22070
|
+
return extractDoc(path2);
|
|
22071
|
+
case ".pdf":
|
|
22072
|
+
return extractPdf(path2);
|
|
22073
|
+
case ".pptx":
|
|
22074
|
+
return extractPptx(path2);
|
|
22075
|
+
case ".xlsx":
|
|
22076
|
+
return extractXlsx(path2);
|
|
22077
|
+
case ".odt":
|
|
22078
|
+
case ".odp":
|
|
22079
|
+
return extractOdfText(path2);
|
|
22080
|
+
case ".ods":
|
|
22081
|
+
return extractOds(path2);
|
|
22082
|
+
case ".epub":
|
|
22083
|
+
return extractEpub(path2);
|
|
22084
|
+
case ".rtf":
|
|
22085
|
+
return extractRtf(path2);
|
|
22086
|
+
default:
|
|
22087
|
+
return null;
|
|
22088
|
+
}
|
|
22089
|
+
}
|
|
22090
|
+
|
|
22091
|
+
// src/gui/ai/extract.ts
|
|
22092
|
+
var MAX_TEXT2 = 2e5;
|
|
21540
22093
|
var CODE_LANGS = {
|
|
21541
22094
|
".ts": "typescript",
|
|
21542
22095
|
".tsx": "typescript",
|
|
@@ -21584,81 +22137,25 @@ var TEXT_EXT = /* @__PURE__ */ new Set([
|
|
|
21584
22137
|
".htm"
|
|
21585
22138
|
]);
|
|
21586
22139
|
var TEXT_MIME = /^(text\/|application\/(json|xml|xhtml\+xml|x-yaml|yaml|toml))/;
|
|
21587
|
-
var MARKITDOWN_EXT = /* @__PURE__ */ new Set([
|
|
21588
|
-
".pdf",
|
|
21589
|
-
".docx",
|
|
21590
|
-
".doc",
|
|
21591
|
-
".pptx",
|
|
21592
|
-
".ppt",
|
|
21593
|
-
".xlsx",
|
|
21594
|
-
".xls",
|
|
21595
|
-
".epub",
|
|
21596
|
-
".rtf",
|
|
21597
|
-
".odt",
|
|
21598
|
-
".ods",
|
|
21599
|
-
".odp"
|
|
21600
|
-
]);
|
|
21601
|
-
var MARKITDOWN_TIMEOUT_MS = 12e4;
|
|
21602
|
-
var MARKITDOWN_MAX_BYTES = 5e7;
|
|
21603
|
-
function runMarkitdown(path2) {
|
|
21604
|
-
return new Promise((resolve12) => {
|
|
21605
|
-
const bin = process.env.MARKITDOWN_BIN ?? "markitdown";
|
|
21606
|
-
let child;
|
|
21607
|
-
try {
|
|
21608
|
-
child = spawn2(bin, [path2], { stdio: ["ignore", "pipe", "ignore"] });
|
|
21609
|
-
} catch {
|
|
21610
|
-
resolve12(null);
|
|
21611
|
-
return;
|
|
21612
|
-
}
|
|
21613
|
-
let out = "";
|
|
21614
|
-
let bytes = 0;
|
|
21615
|
-
let settled = false;
|
|
21616
|
-
const finish = (v) => {
|
|
21617
|
-
if (settled) return;
|
|
21618
|
-
settled = true;
|
|
21619
|
-
clearTimeout(timer);
|
|
21620
|
-
resolve12(v);
|
|
21621
|
-
};
|
|
21622
|
-
const timer = setTimeout(() => {
|
|
21623
|
-
child.kill();
|
|
21624
|
-
finish(null);
|
|
21625
|
-
}, MARKITDOWN_TIMEOUT_MS);
|
|
21626
|
-
child.stdout.on("data", (c) => {
|
|
21627
|
-
bytes += c.length;
|
|
21628
|
-
if (bytes > MARKITDOWN_MAX_BYTES) {
|
|
21629
|
-
child.kill();
|
|
21630
|
-
finish(null);
|
|
21631
|
-
} else {
|
|
21632
|
-
out += c.toString("utf8");
|
|
21633
|
-
}
|
|
21634
|
-
});
|
|
21635
|
-
child.on("error", () => {
|
|
21636
|
-
finish(null);
|
|
21637
|
-
});
|
|
21638
|
-
child.on("close", (code) => {
|
|
21639
|
-
finish(code === 0 && out.trim() ? out.trim() : null);
|
|
21640
|
-
});
|
|
21641
|
-
});
|
|
21642
|
-
}
|
|
21643
22140
|
function languageOf(name) {
|
|
21644
22141
|
return CODE_LANGS[extname(name).toLowerCase()] ?? null;
|
|
21645
22142
|
}
|
|
21646
22143
|
function truncate(s) {
|
|
21647
|
-
return s.length >
|
|
22144
|
+
return s.length > MAX_TEXT2 ? s.slice(0, MAX_TEXT2) : s;
|
|
21648
22145
|
}
|
|
21649
22146
|
async function parseFile(path2, mimeHint, originalName) {
|
|
21650
22147
|
const name = originalName ?? basename7(path2);
|
|
21651
22148
|
const ext = extname(name).toLowerCase();
|
|
21652
22149
|
const lang = languageOf(name);
|
|
21653
22150
|
if (lang) {
|
|
21654
|
-
return { text: truncate(await
|
|
22151
|
+
return { text: truncate(await readFile2(path2, "utf8")), language: lang };
|
|
21655
22152
|
}
|
|
21656
22153
|
if (mimeHint && TEXT_MIME.test(mimeHint) || TEXT_EXT.has(ext)) {
|
|
21657
|
-
return { text: truncate(await
|
|
22154
|
+
return { text: truncate(await readFile2(path2, "utf8")) };
|
|
21658
22155
|
}
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
22156
|
+
const doc = await extractDocument(path2, ext);
|
|
22157
|
+
if (doc != null) {
|
|
22158
|
+
return { text: truncate(doc) };
|
|
21662
22159
|
}
|
|
21663
22160
|
return { text: "", skip: true };
|
|
21664
22161
|
}
|
|
@@ -21672,7 +22169,7 @@ function describe(text, mime, name) {
|
|
|
21672
22169
|
|
|
21673
22170
|
// src/ai/vision.ts
|
|
21674
22171
|
import { createRequire as createRequire5 } from "module";
|
|
21675
|
-
import { readFile as
|
|
22172
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
21676
22173
|
var DEFAULT_PROMPT = "Describe this image for a knowledge base in 2-4 factual sentences: what it shows, any visible text, and notable details. No preamble.";
|
|
21677
22174
|
var MAX_DIM = 1568;
|
|
21678
22175
|
async function describeImage(auth, path2, opts = {}) {
|
|
@@ -21688,7 +22185,7 @@ async function describeImage(auth, path2, opts = {}) {
|
|
|
21688
22185
|
}
|
|
21689
22186
|
var DEFAULT_PDF_PROMPT = "Read this document for a knowledge base. First transcribe its readable text, then add a 2-4 sentence factual summary of what it is and its key details. It may be a scanned/image-only PDF \u2014 read the text from the page images. No preamble.";
|
|
21690
22187
|
async function describePdf(auth, path2, opts = {}) {
|
|
21691
|
-
const buf = await
|
|
22188
|
+
const buf = await readFile3(path2);
|
|
21692
22189
|
const maxBytes = opts.maxBytes ?? 3e7;
|
|
21693
22190
|
if (buf.length > maxBytes) {
|
|
21694
22191
|
throw new Error(
|
|
@@ -22037,6 +22534,10 @@ function fileSlug(name, id) {
|
|
|
22037
22534
|
const base = slugify(name.replace(/\.[^./\\]+$/, "")) || "file";
|
|
22038
22535
|
return `${base}-${id.slice(0, 8)}`;
|
|
22039
22536
|
}
|
|
22537
|
+
function fileIdentity(displayName, id) {
|
|
22538
|
+
const label = displayName.trim() || "file";
|
|
22539
|
+
return { slug: fileSlug(displayName, id), name: label, title: label };
|
|
22540
|
+
}
|
|
22040
22541
|
var MIME_BY_EXT = {
|
|
22041
22542
|
".pdf": "application/pdf",
|
|
22042
22543
|
".png": "image/png",
|
|
@@ -22341,7 +22842,8 @@ function looksLikeUrl(s) {
|
|
|
22341
22842
|
const t = s.trim();
|
|
22342
22843
|
return /^https?:\/\/\S+$/i.test(t) && !/\s/.test(t);
|
|
22343
22844
|
}
|
|
22344
|
-
|
|
22845
|
+
var MAX_INGEST_BYTES = 5e7;
|
|
22846
|
+
function readBuffer2(req, maxBytes = MAX_INGEST_BYTES) {
|
|
22345
22847
|
return new Promise((resolve_, reject) => {
|
|
22346
22848
|
const chunks = [];
|
|
22347
22849
|
let size = 0;
|
|
@@ -22407,7 +22909,7 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
22407
22909
|
const fileId = crypto.randomUUID();
|
|
22408
22910
|
const { id: id2 } = await createRow(mctx, "files", {
|
|
22409
22911
|
id: fileId,
|
|
22410
|
-
|
|
22912
|
+
...fileIdentity(name2, fileId),
|
|
22411
22913
|
original_name: name2,
|
|
22412
22914
|
mime: mime2,
|
|
22413
22915
|
size_bytes: buf.length,
|
|
@@ -22461,7 +22963,7 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
22461
22963
|
const textFileId = crypto.randomUUID();
|
|
22462
22964
|
const { id: id2 } = await createRow(mctx, "files", {
|
|
22463
22965
|
id: textFileId,
|
|
22464
|
-
|
|
22966
|
+
...fileIdentity(title, textFileId),
|
|
22465
22967
|
original_name: title,
|
|
22466
22968
|
mime: mime2,
|
|
22467
22969
|
size_bytes: Buffer.byteLength(content, "utf8"),
|
|
@@ -22493,12 +22995,16 @@ async function dispatchIngestRoute(req, res, ctx) {
|
|
|
22493
22995
|
sendJson5(res, { error: `file not found: ${abs}` }, 400);
|
|
22494
22996
|
return true;
|
|
22495
22997
|
}
|
|
22998
|
+
if (size > MAX_INGEST_BYTES) {
|
|
22999
|
+
sendJson5(res, { error: "file too large" }, 413);
|
|
23000
|
+
return true;
|
|
23001
|
+
}
|
|
22496
23002
|
const name = basename10(abs);
|
|
22497
23003
|
const mime = mimeFor(name);
|
|
22498
23004
|
const localFileId = crypto.randomUUID();
|
|
22499
23005
|
const { id } = await createRow(mctx, "files", {
|
|
22500
23006
|
id: localFileId,
|
|
22501
|
-
|
|
23007
|
+
...fileIdentity(name, localFileId),
|
|
22502
23008
|
path: abs,
|
|
22503
23009
|
original_name: name,
|
|
22504
23010
|
mime,
|
|
@@ -22608,7 +23114,7 @@ function sendText(res, body, status = 200, contentType = "text/plain; charset=ut
|
|
|
22608
23114
|
function openUrl(url) {
|
|
22609
23115
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
22610
23116
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
22611
|
-
const child =
|
|
23117
|
+
const child = spawn2(command, args, { stdio: "ignore", detached: true });
|
|
22612
23118
|
child.unref();
|
|
22613
23119
|
}
|
|
22614
23120
|
function listen(server, port, host) {
|
|
@@ -22858,7 +23364,7 @@ function resolveOutputDirForConfig(configPath) {
|
|
|
22858
23364
|
}
|
|
22859
23365
|
return resolve9(base, "context");
|
|
22860
23366
|
}
|
|
22861
|
-
async function openConfig(configPath, outputDir, autoRender = false) {
|
|
23367
|
+
async function openConfig(configPath, outputDir, autoRender = false, teamCloud = false) {
|
|
22862
23368
|
const parsed = parseConfigFile(configPath);
|
|
22863
23369
|
if (!/^postgres(ql)?:\/\//i.test(parsed.dbPath) && !parsed.dbPath.startsWith("file:") && parsed.dbPath !== ":memory:") {
|
|
22864
23370
|
mkdirSync10(dirname11(parsed.dbPath), { recursive: true });
|
|
@@ -22987,6 +23493,7 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
22987
23493
|
}
|
|
22988
23494
|
}
|
|
22989
23495
|
let teamContext = null;
|
|
23496
|
+
let cloudReconnectRequired = false;
|
|
22990
23497
|
if (db.getDialect() === "postgres") {
|
|
22991
23498
|
let teamEnabled = false;
|
|
22992
23499
|
try {
|
|
@@ -22994,7 +23501,14 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
22994
23501
|
} catch {
|
|
22995
23502
|
teamEnabled = false;
|
|
22996
23503
|
}
|
|
22997
|
-
|
|
23504
|
+
const directGuiPostgres = !teamCloud && isPostgresUrl(parsed.dbPath);
|
|
23505
|
+
if (directGuiPostgres) {
|
|
23506
|
+
if (teamEnabled) {
|
|
23507
|
+
cloudReconnectRequired = true;
|
|
23508
|
+
teamEnabled = false;
|
|
23509
|
+
validTables.clear();
|
|
23510
|
+
}
|
|
23511
|
+
} else if (!teamEnabled) {
|
|
22998
23512
|
try {
|
|
22999
23513
|
const rawDb = parseDocument3(readFileSync15(configPath, "utf8")).get("db");
|
|
23000
23514
|
const dbLine = typeof rawDb === "string" ? rawDb.trim() : "";
|
|
@@ -23036,7 +23550,7 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
23036
23550
|
}
|
|
23037
23551
|
}
|
|
23038
23552
|
let realtime = null;
|
|
23039
|
-
if (db.getDialect() === "postgres") {
|
|
23553
|
+
if (db.getDialect() === "postgres" && !cloudReconnectRequired) {
|
|
23040
23554
|
try {
|
|
23041
23555
|
realtime = new RealtimeBroker(parsed.dbPath);
|
|
23042
23556
|
await realtime.start();
|
|
@@ -23075,6 +23589,8 @@ async function openConfig(configPath, outputDir, autoRender = false) {
|
|
|
23075
23589
|
teamsClient,
|
|
23076
23590
|
validTables,
|
|
23077
23591
|
teamContext,
|
|
23592
|
+
teamCloud,
|
|
23593
|
+
cloudReconnectRequired,
|
|
23078
23594
|
junctionTables,
|
|
23079
23595
|
entityContextByTable,
|
|
23080
23596
|
manifest,
|
|
@@ -23169,7 +23685,7 @@ async function disposeActive(active) {
|
|
|
23169
23685
|
async function reopenSameConfig(active, autoRender) {
|
|
23170
23686
|
const feed = active.feed;
|
|
23171
23687
|
await disposeActive(active);
|
|
23172
|
-
const next = await openConfig(active.configPath, active.outputDir, autoRender);
|
|
23688
|
+
const next = await openConfig(active.configPath, active.outputDir, autoRender, active.teamCloud);
|
|
23173
23689
|
next.feed = feed;
|
|
23174
23690
|
return next;
|
|
23175
23691
|
}
|
|
@@ -23304,7 +23820,7 @@ async function applySchemaConfig(active, entry, direction, autoRender) {
|
|
|
23304
23820
|
for (const sql of ddl) await execSql(active.db, sql);
|
|
23305
23821
|
saveConfigDoc(active.configPath, doc);
|
|
23306
23822
|
await disposeActive(active);
|
|
23307
|
-
return openConfig(active.configPath, active.outputDir, autoRender);
|
|
23823
|
+
return openConfig(active.configPath, active.outputDir, autoRender, active.teamCloud);
|
|
23308
23824
|
}
|
|
23309
23825
|
function schemaReverseSummary(verb, entry) {
|
|
23310
23826
|
const what = entry.operation.replace("schema.", "").replace(/_/g, " ");
|
|
@@ -23318,7 +23834,7 @@ async function startGuiServer(options) {
|
|
|
23318
23834
|
const teamCloud = options.teamCloud ?? false;
|
|
23319
23835
|
const autoRender = options.autoRender ?? false;
|
|
23320
23836
|
const sessionId = crypto.randomUUID();
|
|
23321
|
-
let active = await openConfig(configPath, outputDir, autoRender);
|
|
23837
|
+
let active = await openConfig(configPath, outputDir, autoRender, teamCloud);
|
|
23322
23838
|
const latticeRoot = findLatticeRoot(dirname11(configPath));
|
|
23323
23839
|
let currentWorkspaceId = null;
|
|
23324
23840
|
if (latticeRoot) {
|
|
@@ -24508,7 +25024,7 @@ data: ${JSON.stringify(data)}
|
|
|
24508
25024
|
const paths = resolveWorkspacePaths(latticeRoot, ws);
|
|
24509
25025
|
let next;
|
|
24510
25026
|
try {
|
|
24511
|
-
next = await openConfig(paths.configPath, paths.contextDir, autoRender);
|
|
25027
|
+
next = await openConfig(paths.configPath, paths.contextDir, autoRender, teamCloud);
|
|
24512
25028
|
} catch (e) {
|
|
24513
25029
|
const err = e;
|
|
24514
25030
|
sendJson(
|
|
@@ -24550,7 +25066,12 @@ data: ${JSON.stringify(data)}
|
|
|
24550
25066
|
const newPaths = resolveWorkspacePaths(latticeRoot, created);
|
|
24551
25067
|
let newActive;
|
|
24552
25068
|
try {
|
|
24553
|
-
newActive = await openConfig(
|
|
25069
|
+
newActive = await openConfig(
|
|
25070
|
+
newPaths.configPath,
|
|
25071
|
+
newPaths.contextDir,
|
|
25072
|
+
autoRender,
|
|
25073
|
+
teamCloud
|
|
25074
|
+
);
|
|
24554
25075
|
} catch (e) {
|
|
24555
25076
|
sendJson(
|
|
24556
25077
|
res,
|
|
@@ -24609,7 +25130,12 @@ data: ${JSON.stringify(data)}
|
|
|
24609
25130
|
const fbPaths = resolveWorkspacePaths(latticeRoot, fallback);
|
|
24610
25131
|
let next;
|
|
24611
25132
|
try {
|
|
24612
|
-
next = await openConfig(
|
|
25133
|
+
next = await openConfig(
|
|
25134
|
+
fbPaths.configPath,
|
|
25135
|
+
fbPaths.contextDir,
|
|
25136
|
+
autoRender,
|
|
25137
|
+
teamCloud
|
|
25138
|
+
);
|
|
24613
25139
|
} catch (e) {
|
|
24614
25140
|
const err = e;
|
|
24615
25141
|
const codePrefix = err.code ? `[${err.code}] ` : "";
|
|
@@ -24701,7 +25227,12 @@ data: ${JSON.stringify(data)}
|
|
|
24701
25227
|
}
|
|
24702
25228
|
let next;
|
|
24703
25229
|
try {
|
|
24704
|
-
next = await openConfig(
|
|
25230
|
+
next = await openConfig(
|
|
25231
|
+
newPath,
|
|
25232
|
+
resolveOutputDirForConfig(newPath),
|
|
25233
|
+
autoRender,
|
|
25234
|
+
teamCloud
|
|
25235
|
+
);
|
|
24705
25236
|
} catch (e) {
|
|
24706
25237
|
const err = e;
|
|
24707
25238
|
console.error(`[dbconfig.switch] openConfig(${newPath}) failed:`, err);
|
|
@@ -24728,7 +25259,8 @@ data: ${JSON.stringify(data)}
|
|
|
24728
25259
|
const next = await openConfig(
|
|
24729
25260
|
newConfigPath,
|
|
24730
25261
|
resolveOutputDirForConfig(newConfigPath),
|
|
24731
|
-
autoRender
|
|
25262
|
+
autoRender,
|
|
25263
|
+
teamCloud
|
|
24732
25264
|
);
|
|
24733
25265
|
await disposeActive(active);
|
|
24734
25266
|
active = next;
|
|
@@ -24770,7 +25302,8 @@ data: ${JSON.stringify(data)}
|
|
|
24770
25302
|
next = await openConfig(
|
|
24771
25303
|
fallback.path,
|
|
24772
25304
|
resolveOutputDirForConfig(fallback.path),
|
|
24773
|
-
autoRender
|
|
25305
|
+
autoRender,
|
|
25306
|
+
teamCloud
|
|
24774
25307
|
);
|
|
24775
25308
|
} catch (e) {
|
|
24776
25309
|
const err = e;
|
|
@@ -25170,8 +25703,14 @@ data: ${JSON.stringify(data)}
|
|
|
25170
25703
|
teamId: active.teamContext.teamId,
|
|
25171
25704
|
myUserId: active.teamContext.myUserId
|
|
25172
25705
|
} : null,
|
|
25706
|
+
cloudReconnectRequired: active.cloudReconnectRequired,
|
|
25173
25707
|
swap: async () => {
|
|
25174
|
-
const next = await openConfig(
|
|
25708
|
+
const next = await openConfig(
|
|
25709
|
+
active.configPath,
|
|
25710
|
+
active.outputDir,
|
|
25711
|
+
autoRender,
|
|
25712
|
+
active.teamCloud
|
|
25713
|
+
);
|
|
25175
25714
|
await disposeActive(active);
|
|
25176
25715
|
active = next;
|
|
25177
25716
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -4509,8 +4509,8 @@ interface PdfOptions {
|
|
|
4509
4509
|
}
|
|
4510
4510
|
/**
|
|
4511
4511
|
* Read a PDF with Claude's native document support — works on text PDFs AND
|
|
4512
|
-
* scanned/image-only PDFs (no text layer),
|
|
4513
|
-
* AI-gated; the model call is injectable for tests.
|
|
4512
|
+
* scanned/image-only PDFs (no text layer), where in-process text extraction
|
|
4513
|
+
* finds nothing. AI-gated; the model call is injectable for tests.
|
|
4514
4514
|
*/
|
|
4515
4515
|
declare function describePdf(auth: ClaudeAuth, path: string, opts?: PdfOptions): Promise<string>;
|
|
4516
4516
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4509,8 +4509,8 @@ interface PdfOptions {
|
|
|
4509
4509
|
}
|
|
4510
4510
|
/**
|
|
4511
4511
|
* Read a PDF with Claude's native document support — works on text PDFs AND
|
|
4512
|
-
* scanned/image-only PDFs (no text layer),
|
|
4513
|
-
* AI-gated; the model call is injectable for tests.
|
|
4512
|
+
* scanned/image-only PDFs (no text layer), where in-process text extraction
|
|
4513
|
+
* finds nothing. AI-gated; the model call is injectable for tests.
|
|
4514
4514
|
*/
|
|
4515
4515
|
declare function describePdf(auth: ClaudeAuth, path: string, opts?: PdfOptions): Promise<string>;
|
|
4516
4516
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latticesql",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "Persistent structured memory for AI agent systems — pluggable SQLite or Postgres backend, LLM context bridge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -57,10 +57,14 @@
|
|
|
57
57
|
},
|
|
58
58
|
"optionalDependencies": {
|
|
59
59
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
60
|
+
"fflate": "^0.8.3",
|
|
60
61
|
"file-type": "^19.6.0",
|
|
62
|
+
"mammoth": "^1.12.0",
|
|
61
63
|
"pg": "^8.11.0",
|
|
62
64
|
"playwright": "^1.48.0",
|
|
63
|
-
"sharp": "^0.33.5"
|
|
65
|
+
"sharp": "^0.33.5",
|
|
66
|
+
"unpdf": "^1.6.2",
|
|
67
|
+
"word-extractor": "^1.0.4"
|
|
64
68
|
},
|
|
65
69
|
"devDependencies": {
|
|
66
70
|
"@anthropic-ai/sdk": "^0.71.0",
|