latticesql 4.2.2 → 4.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.js +357 -36
- package/dist/desktop-entry.js +76390 -0
- package/dist/index.cjs +378 -53
- package/dist/index.d.cts +76 -1
- package/dist/index.d.ts +76 -1
- package/dist/index.js +358 -35
- package/docs/desktop.md +75 -0
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -240,7 +240,7 @@ var init_manifest = __esm({
|
|
|
240
240
|
"src/lifecycle/manifest.ts"() {
|
|
241
241
|
"use strict";
|
|
242
242
|
init_writer();
|
|
243
|
-
TEMPLATE_VERSION =
|
|
243
|
+
TEMPLATE_VERSION = 3;
|
|
244
244
|
}
|
|
245
245
|
});
|
|
246
246
|
|
|
@@ -719,17 +719,179 @@ var init_sqlite = __esm({
|
|
|
719
719
|
}
|
|
720
720
|
});
|
|
721
721
|
|
|
722
|
+
// src/db/sqlite-deno.ts
|
|
723
|
+
import { createRequire as createRequire2 } from "module";
|
|
724
|
+
function runtimeRequire2() {
|
|
725
|
+
const importMetaUrl = import.meta.url;
|
|
726
|
+
return importMetaUrl ? createRequire2(importMetaUrl) : __require;
|
|
727
|
+
}
|
|
728
|
+
function loadNodeSqlite() {
|
|
729
|
+
if (_ctor2) return _ctor2;
|
|
730
|
+
const mod = runtimeRequire2()("node:sqlite");
|
|
731
|
+
if (!mod.DatabaseSync) {
|
|
732
|
+
throw new Error(
|
|
733
|
+
"node:sqlite is unavailable in this runtime \u2014 cannot open the Deno SQLite adapter"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
_ctor2 = mod.DatabaseSync;
|
|
737
|
+
return _ctor2;
|
|
738
|
+
}
|
|
739
|
+
var _ctor2, DenoSqliteAdapter;
|
|
740
|
+
var init_sqlite_deno = __esm({
|
|
741
|
+
"src/db/sqlite-deno.ts"() {
|
|
742
|
+
"use strict";
|
|
743
|
+
_ctor2 = null;
|
|
744
|
+
DenoSqliteAdapter = class {
|
|
745
|
+
dialect = "sqlite";
|
|
746
|
+
_db = null;
|
|
747
|
+
_path;
|
|
748
|
+
_wal;
|
|
749
|
+
_busyTimeout;
|
|
750
|
+
constructor(path3, options) {
|
|
751
|
+
this._path = path3;
|
|
752
|
+
this._wal = options?.wal ?? true;
|
|
753
|
+
this._busyTimeout = options?.busyTimeout ?? 5e3;
|
|
754
|
+
}
|
|
755
|
+
get db() {
|
|
756
|
+
if (!this._db) throw new Error("DenoSqliteAdapter: not open \u2014 call open() first");
|
|
757
|
+
return this._db;
|
|
758
|
+
}
|
|
759
|
+
open() {
|
|
760
|
+
const Ctor = loadNodeSqlite();
|
|
761
|
+
this._db = new Ctor(this._path);
|
|
762
|
+
this._db.exec(`PRAGMA busy_timeout = ${this._busyTimeout.toString()}`);
|
|
763
|
+
if (this._wal) {
|
|
764
|
+
this._db.exec("PRAGMA journal_mode = WAL");
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
close() {
|
|
768
|
+
this._db?.close();
|
|
769
|
+
this._db = null;
|
|
770
|
+
}
|
|
771
|
+
run(sql, params = []) {
|
|
772
|
+
this.db.prepare(sql).run(...params);
|
|
773
|
+
}
|
|
774
|
+
get(sql, params = []) {
|
|
775
|
+
return this.db.prepare(sql).get(...params);
|
|
776
|
+
}
|
|
777
|
+
all(sql, params = []) {
|
|
778
|
+
return this.db.prepare(sql).all(...params);
|
|
779
|
+
}
|
|
780
|
+
prepare(sql) {
|
|
781
|
+
const stmt = this.db.prepare(sql);
|
|
782
|
+
return {
|
|
783
|
+
run: (...params) => {
|
|
784
|
+
const info = stmt.run(...params);
|
|
785
|
+
return {
|
|
786
|
+
changes: Number(info.changes),
|
|
787
|
+
lastInsertRowid: info.lastInsertRowid
|
|
788
|
+
};
|
|
789
|
+
},
|
|
790
|
+
get: (...params) => stmt.get(...params),
|
|
791
|
+
all: (...params) => stmt.all(...params)
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
introspectColumns(table) {
|
|
795
|
+
const rows = this.all(`PRAGMA table_info("${table}")`);
|
|
796
|
+
return rows.map((r6) => r6.name);
|
|
797
|
+
}
|
|
798
|
+
/** Mirror of SQLiteAdapter.addColumn — SQLite ALTER quirks are binding-agnostic. */
|
|
799
|
+
addColumn(table, column, typeSpec) {
|
|
800
|
+
const upperType = typeSpec.toUpperCase();
|
|
801
|
+
if (upperType.includes("PRIMARY KEY")) return;
|
|
802
|
+
const hasNonConstantDefault = upperType.includes("CURRENT_TIMESTAMP") || /DATETIME\s*\(\s*'NOW'\s*\)/i.test(typeSpec) || upperType.includes("RANDOM()");
|
|
803
|
+
if (hasNonConstantDefault) {
|
|
804
|
+
const safeType = typeSpec.replace(/\bNOT\s+NULL\b/gi, "").replace(/\bDEFAULT\s+\(?\s*CURRENT_TIMESTAMP\s*\)?/gi, "").replace(/\bDEFAULT\s+\(?\s*datetime\([^)]*\)\s*\)?/gi, "").replace(/\bDEFAULT\s+\(?\s*RANDOM\(\)\s*\)?/gi, "").replace(/\s+/g, " ").trim();
|
|
805
|
+
this.run(`ALTER TABLE "${table}" ADD COLUMN "${column}" ${safeType || "TEXT"}`);
|
|
806
|
+
this.run(`UPDATE "${table}" SET "${column}" = CURRENT_TIMESTAMP WHERE "${column}" IS NULL`);
|
|
807
|
+
} else {
|
|
808
|
+
this.run(`ALTER TABLE "${table}" ADD COLUMN "${column}" ${typeSpec}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* O(1) watch-loop change-probe — same composition as SQLiteAdapter, but
|
|
813
|
+
* `data_version` is read with a plain prepared statement because node:sqlite
|
|
814
|
+
* has no `.pragma(name, { simple: true })` scalar helper.
|
|
815
|
+
*/
|
|
816
|
+
changeProbe() {
|
|
817
|
+
const dataVersion = this.db.prepare("PRAGMA data_version").get().data_version;
|
|
818
|
+
const totalChanges = this.db.prepare("SELECT total_changes() AS n").get().n;
|
|
819
|
+
return `${String(dataVersion)}:${String(totalChanges)}`;
|
|
820
|
+
}
|
|
821
|
+
// ── Async surface (sync under the hood; mirrors SQLiteAdapter) ──────────
|
|
822
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
823
|
+
async runAsync(sql, params) {
|
|
824
|
+
this.run(sql, params);
|
|
825
|
+
}
|
|
826
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
827
|
+
async getAsync(sql, params) {
|
|
828
|
+
return this.get(sql, params);
|
|
829
|
+
}
|
|
830
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
831
|
+
async allAsync(sql, params) {
|
|
832
|
+
return this.all(sql, params);
|
|
833
|
+
}
|
|
834
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
835
|
+
async introspectColumnsAsync(table) {
|
|
836
|
+
return this.introspectColumns(table);
|
|
837
|
+
}
|
|
838
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
839
|
+
async introspectAllColumns(tables) {
|
|
840
|
+
const map = /* @__PURE__ */ new Map();
|
|
841
|
+
for (const t8 of tables) {
|
|
842
|
+
try {
|
|
843
|
+
const cols = this.introspectColumns(t8);
|
|
844
|
+
if (cols.length > 0) map.set(t8, new Set(cols));
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return map;
|
|
849
|
+
}
|
|
850
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
851
|
+
async addColumnAsync(table, column, typeSpec) {
|
|
852
|
+
this.addColumn(table, column, typeSpec);
|
|
853
|
+
}
|
|
854
|
+
/** BEGIN/COMMIT around an awaited fn; ROLLBACK on throw. Mirror of SQLiteAdapter. */
|
|
855
|
+
async withClient(fn) {
|
|
856
|
+
const dbRef = this.db;
|
|
857
|
+
const getSync = this.get.bind(this);
|
|
858
|
+
const allSync = this.all.bind(this);
|
|
859
|
+
const tx = {
|
|
860
|
+
run: (sql, params) => {
|
|
861
|
+
const info = dbRef.prepare(sql).run(...params ?? []);
|
|
862
|
+
return Promise.resolve({ changes: Number(info.changes) });
|
|
863
|
+
},
|
|
864
|
+
get: (sql, params) => Promise.resolve(getSync(sql, params ?? [])),
|
|
865
|
+
all: (sql, params) => Promise.resolve(allSync(sql, params ?? []))
|
|
866
|
+
};
|
|
867
|
+
this.run("BEGIN");
|
|
868
|
+
try {
|
|
869
|
+
const result = await fn(tx);
|
|
870
|
+
this.run("COMMIT");
|
|
871
|
+
return result;
|
|
872
|
+
} catch (err) {
|
|
873
|
+
try {
|
|
874
|
+
this.run("ROLLBACK");
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
throw err;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
722
884
|
// src/db/postgres.ts
|
|
723
885
|
import path2 from "path";
|
|
724
886
|
import { fileURLToPath } from "url";
|
|
725
|
-
import { createRequire as
|
|
887
|
+
import { createRequire as createRequire3 } from "module";
|
|
726
888
|
function moduleContext() {
|
|
727
889
|
if (_moduleContext) return _moduleContext;
|
|
728
890
|
const importMetaUrl = import.meta.url;
|
|
729
891
|
if (importMetaUrl) {
|
|
730
892
|
_moduleContext = {
|
|
731
893
|
dir: path2.dirname(fileURLToPath(importMetaUrl)),
|
|
732
|
-
require:
|
|
894
|
+
require: createRequire3(importMetaUrl)
|
|
733
895
|
};
|
|
734
896
|
} else {
|
|
735
897
|
_moduleContext = { dir: __dirname, require: __require };
|
|
@@ -3437,6 +3599,64 @@ function cleanupEntityContexts(outputDir, entityContexts, currentSlugsByTable, m
|
|
|
3437
3599
|
warnings: []
|
|
3438
3600
|
};
|
|
3439
3601
|
if (manifest === null) return result;
|
|
3602
|
+
if (options.removeOrphanedDirectories !== false) {
|
|
3603
|
+
for (const [table, entry] of Object.entries(manifest.entityContexts)) {
|
|
3604
|
+
if (entityContexts.has(table)) continue;
|
|
3605
|
+
const directoryRoot = entry.directoryRoot;
|
|
3606
|
+
const rootPath = join3(outputDir, directoryRoot);
|
|
3607
|
+
if (!existsSync3(rootPath)) continue;
|
|
3608
|
+
const globalProtected = new Set(options.protectedFiles ?? []);
|
|
3609
|
+
for (const [slug, files] of Object.entries(entry.entities)) {
|
|
3610
|
+
const entityDir = join3(rootPath, slug);
|
|
3611
|
+
if (!existsSync3(entityDir)) continue;
|
|
3612
|
+
for (const filename of entityFileNames(files)) {
|
|
3613
|
+
if (globalProtected.has(filename)) continue;
|
|
3614
|
+
const filePath = join3(entityDir, filename);
|
|
3615
|
+
if (!existsSync3(filePath)) continue;
|
|
3616
|
+
if (!options.dryRun) unlinkSync2(filePath);
|
|
3617
|
+
options.onOrphan?.(filePath, "file");
|
|
3618
|
+
result.filesRemoved.push(filePath);
|
|
3619
|
+
}
|
|
3620
|
+
let remaining;
|
|
3621
|
+
try {
|
|
3622
|
+
remaining = existsSync3(entityDir) ? readdirSync(entityDir) : [];
|
|
3623
|
+
} catch {
|
|
3624
|
+
remaining = [];
|
|
3625
|
+
}
|
|
3626
|
+
if (remaining.length === 0) {
|
|
3627
|
+
if (!options.dryRun) {
|
|
3628
|
+
try {
|
|
3629
|
+
rmdirSync(entityDir);
|
|
3630
|
+
} catch {
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
options.onOrphan?.(entityDir, "directory");
|
|
3634
|
+
result.directoriesRemoved.push(entityDir);
|
|
3635
|
+
} else {
|
|
3636
|
+
result.directoriesSkipped.push(entityDir);
|
|
3637
|
+
result.warnings.push(
|
|
3638
|
+
`${entityDir}: left in place (contains user files: ${remaining.join(", ")})`
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
let rootRemaining;
|
|
3643
|
+
try {
|
|
3644
|
+
rootRemaining = existsSync3(rootPath) ? readdirSync(rootPath) : [];
|
|
3645
|
+
} catch {
|
|
3646
|
+
rootRemaining = [];
|
|
3647
|
+
}
|
|
3648
|
+
if (rootRemaining.length === 0) {
|
|
3649
|
+
if (!options.dryRun) {
|
|
3650
|
+
try {
|
|
3651
|
+
rmdirSync(rootPath);
|
|
3652
|
+
} catch {
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
options.onOrphan?.(rootPath, "directory");
|
|
3656
|
+
result.directoriesRemoved.push(rootPath);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3440
3660
|
for (const [table, def] of entityContexts) {
|
|
3441
3661
|
const entry = manifest.entityContexts[table];
|
|
3442
3662
|
if (!entry) continue;
|
|
@@ -3933,7 +4153,8 @@ var init_engine = __esm({
|
|
|
3933
4153
|
const currentSlugsByTable = /* @__PURE__ */ new Map();
|
|
3934
4154
|
for (const [table, def] of entityContexts) {
|
|
3935
4155
|
const rows = await this._schema.queryTable(this._adapter, table, this._schema.readRel);
|
|
3936
|
-
const
|
|
4156
|
+
const entityPk = this._schema.getPrimaryKey(table)[0] ?? "id";
|
|
4157
|
+
const slugs = new Set(_RenderEngine._disambiguateSlugs(rows, def.slug, entityPk));
|
|
3937
4158
|
currentSlugsByTable.set(table, slugs);
|
|
3938
4159
|
}
|
|
3939
4160
|
return cleanupEntityContexts(
|
|
@@ -3982,6 +4203,71 @@ var init_engine = __esm({
|
|
|
3982
4203
|
static _normKey(v2) {
|
|
3983
4204
|
return String(v2);
|
|
3984
4205
|
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Sanitize and validate ONE base slug.
|
|
4208
|
+
*
|
|
4209
|
+
* Replaces non-ASCII whitespace (e.g. the macOS narrow no-break space U+202F
|
|
4210
|
+
* that shows up in screenshot filenames) with a regular space, strips control
|
|
4211
|
+
* characters, then rejects any slug that still contains a character outside the
|
|
4212
|
+
* allowed set (the path-traversal guard). Throws on an invalid slug — never
|
|
4213
|
+
* silently rewrites it.
|
|
4214
|
+
*/
|
|
4215
|
+
static _sanitizeSlug(rawSlug) {
|
|
4216
|
+
const slug = rawSlug.replace(/[\u00A0\u2000-\u200B\u202F\u205F\u3000]/g, " ").replace(/[\u0000-\u001F\u007F]/g, "");
|
|
4217
|
+
if (/[^a-zA-Z0-9.\-_ @(),#&'+:;!~[\]]/.test(slug)) {
|
|
4218
|
+
throw new Error(`Invalid slug "${slug}": contains characters outside the allowed set`);
|
|
4219
|
+
}
|
|
4220
|
+
return slug;
|
|
4221
|
+
}
|
|
4222
|
+
/**
|
|
4223
|
+
* Disambiguate per-row slugs so two rows that produce the SAME base slug do not
|
|
4224
|
+
* write to (and clobber) the same directory.
|
|
4225
|
+
*
|
|
4226
|
+
* Returns one final slug per row, in the SAME order as `rows`. A base slug used
|
|
4227
|
+
* by exactly one row is returned unchanged (no churn for the common case). When
|
|
4228
|
+
* a base slug is shared by >1 row, EVERY colliding row gets a short, stable
|
|
4229
|
+
* suffix derived from its primary key (`<base>-<pk8>`), so the result is
|
|
4230
|
+
* order-independent: the same row gets the same slug on every render regardless
|
|
4231
|
+
* of row order. The suffix lengthens only if two rows' 8-char PK prefixes still
|
|
4232
|
+
* collide (e.g. shared prefix), guaranteeing uniqueness without changing the
|
|
4233
|
+
* common-case output. Slugs are sanitized + path-traversal-validated via
|
|
4234
|
+
* {@link _sanitizeSlug}; `def.slug` itself is never modified.
|
|
4235
|
+
*/
|
|
4236
|
+
static _disambiguateSlugs(rows, slugFn, pkCol) {
|
|
4237
|
+
const baseSlugs = rows.map((row) => _RenderEngine._sanitizeSlug(slugFn(row)));
|
|
4238
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
4239
|
+
for (let i6 = 0; i6 < baseSlugs.length; i6++) {
|
|
4240
|
+
const base = baseSlugs[i6];
|
|
4241
|
+
const bucket = byBase.get(base);
|
|
4242
|
+
if (bucket) bucket.push(i6);
|
|
4243
|
+
else byBase.set(base, [i6]);
|
|
4244
|
+
}
|
|
4245
|
+
const final = baseSlugs.map(() => "");
|
|
4246
|
+
const pkOf = (i6) => {
|
|
4247
|
+
const v2 = rows[i6]?.[pkCol];
|
|
4248
|
+
let s2;
|
|
4249
|
+
if (v2 == null) s2 = "";
|
|
4250
|
+
else if (typeof v2 === "object") s2 = JSON.stringify(v2);
|
|
4251
|
+
else s2 = String(v2);
|
|
4252
|
+
return _RenderEngine._sanitizeSlug(s2).replace(/[ /\\]/g, "");
|
|
4253
|
+
};
|
|
4254
|
+
for (const [base, indices] of byBase) {
|
|
4255
|
+
if (indices.length === 1) {
|
|
4256
|
+
final[indices[0]] = base;
|
|
4257
|
+
continue;
|
|
4258
|
+
}
|
|
4259
|
+
const pks = indices.map(pkOf);
|
|
4260
|
+
const maxLen = Math.max(...pks.map((p3) => p3.length), 1);
|
|
4261
|
+
let len = 8;
|
|
4262
|
+
while (len < maxLen && new Set(pks.map((p3) => p3.slice(0, len))).size !== pks.length) {
|
|
4263
|
+
len += 4;
|
|
4264
|
+
}
|
|
4265
|
+
for (let k6 = 0; k6 < indices.length; k6++) {
|
|
4266
|
+
final[indices[k6]] = `${base}-${pks[k6].slice(0, len)}`;
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
return final;
|
|
4270
|
+
}
|
|
3985
4271
|
/**
|
|
3986
4272
|
* Prefetch the batchable belongsTo sources for one entity-context table.
|
|
3987
4273
|
* For each (target+filters+softDelete) group, issue exactly ONE
|
|
@@ -4062,6 +4348,7 @@ var init_engine = __esm({
|
|
|
4062
4348
|
const baseRows = await this._schema.queryTable(this._adapter, table, this._schema.readRel);
|
|
4063
4349
|
const allRows = this._foldRows ? await this._foldRows(table, baseRows) : baseRows;
|
|
4064
4350
|
const directoryRoot = def.directoryRoot ?? table;
|
|
4351
|
+
const finalSlugs = _RenderEngine._disambiguateSlugs(allRows, def.slug, entityPk);
|
|
4065
4352
|
const belongsToBatches = await this._prefetchBelongsToBatches(
|
|
4066
4353
|
def,
|
|
4067
4354
|
allRows,
|
|
@@ -4103,11 +4390,7 @@ var init_engine = __esm({
|
|
|
4103
4390
|
if (i6 > 0 && i6 % YIELD_EVERY_ENTITIES === 0) {
|
|
4104
4391
|
await new Promise((r6) => setImmediate(r6));
|
|
4105
4392
|
}
|
|
4106
|
-
const
|
|
4107
|
-
const slug = rawSlug.replace(/[\u00A0\u2000-\u200B\u202F\u205F\u3000]/g, " ").replace(/[\u0000-\u001F\u007F]/g, "");
|
|
4108
|
-
if (/[^a-zA-Z0-9.\-_ @(),#&'+:;!~[\]]/.test(slug)) {
|
|
4109
|
-
throw new Error(`Invalid slug "${slug}": contains characters outside the allowed set`);
|
|
4110
|
-
}
|
|
4393
|
+
const slug = finalSlugs[i6];
|
|
4111
4394
|
const entityDir = def.directory ? join4(outputDir, def.directory(entityRow)) : join4(outputDir, directoryRoot, slug);
|
|
4112
4395
|
const resolvedDir = resolve(entityDir);
|
|
4113
4396
|
const resolvedBase = resolve(outputDir);
|
|
@@ -9816,6 +10099,9 @@ function buildAdapter(dbPath, options) {
|
|
|
9816
10099
|
const adapterOpts = {};
|
|
9817
10100
|
if (options.wal !== void 0) adapterOpts.wal = options.wal;
|
|
9818
10101
|
if (options.busyTimeout !== void 0) adapterOpts.busyTimeout = options.busyTimeout;
|
|
10102
|
+
if (typeof globalThis.Deno !== "undefined") {
|
|
10103
|
+
return new DenoSqliteAdapter(sqlitePath, adapterOpts);
|
|
10104
|
+
}
|
|
9819
10105
|
return new SQLiteAdapter(sqlitePath, adapterOpts);
|
|
9820
10106
|
}
|
|
9821
10107
|
function _resolveTemplateName(render) {
|
|
@@ -9835,6 +10121,7 @@ var init_lattice = __esm({
|
|
|
9835
10121
|
init_render_cursor();
|
|
9836
10122
|
init_adapter();
|
|
9837
10123
|
init_sqlite();
|
|
10124
|
+
init_sqlite_deno();
|
|
9838
10125
|
init_postgres();
|
|
9839
10126
|
init_pk();
|
|
9840
10127
|
init_manager();
|
|
@@ -52363,7 +52650,7 @@ var init_table_policy = __esm({
|
|
|
52363
52650
|
});
|
|
52364
52651
|
|
|
52365
52652
|
// src/ai/llm-client.ts
|
|
52366
|
-
import { createRequire as
|
|
52653
|
+
import { createRequire as createRequire4 } from "module";
|
|
52367
52654
|
var DEFAULT_MODEL, CHEAPEST_MODEL;
|
|
52368
52655
|
var init_llm_client = __esm({
|
|
52369
52656
|
"src/ai/llm-client.ts"() {
|
|
@@ -52537,7 +52824,7 @@ var init_summarize = __esm({
|
|
|
52537
52824
|
import { JSDOM } from "jsdom";
|
|
52538
52825
|
import { Readability } from "@mozilla/readability";
|
|
52539
52826
|
import { basename as basename5 } from "path";
|
|
52540
|
-
import { createRequire as
|
|
52827
|
+
import { createRequire as createRequire5 } from "module";
|
|
52541
52828
|
async function crawlUrl(rawUrl, opts = {}) {
|
|
52542
52829
|
const u2 = await assertSafeUrl(rawUrl, opts.allowPrivate ?? false);
|
|
52543
52830
|
const fetchImpl = opts.fetcher ?? fetch;
|
|
@@ -52784,7 +53071,7 @@ async function renderViaPlaywright(url, timeoutMs, warnIfMissing = false) {
|
|
|
52784
53071
|
let chromium;
|
|
52785
53072
|
try {
|
|
52786
53073
|
const importMetaUrl = import.meta.url;
|
|
52787
|
-
const req = importMetaUrl ?
|
|
53074
|
+
const req = importMetaUrl ? createRequire5(importMetaUrl) : __require;
|
|
52788
53075
|
const pw = req("playwright");
|
|
52789
53076
|
chromium = pw.chromium;
|
|
52790
53077
|
} catch {
|
|
@@ -53984,9 +54271,6 @@ async function resolveClaudeAuth(db) {
|
|
|
53984
54271
|
const apiKey = await resolveAnthropicKey(db);
|
|
53985
54272
|
return apiKey ? { apiKey } : null;
|
|
53986
54273
|
}
|
|
53987
|
-
async function hasClaudeAuth(db) {
|
|
53988
|
-
return Boolean(await readMachineCredential(db, CLAUDE_OAUTH_KIND)) || await hasCredential(db, "anthropic", "ANTHROPIC_API_KEY");
|
|
53989
|
-
}
|
|
53990
54274
|
async function claudeAuthKind(db) {
|
|
53991
54275
|
if (await readMachineCredential(db, CLAUDE_OAUTH_KIND)) return "oauth";
|
|
53992
54276
|
if (await hasCredential(db, "anthropic", "ANTHROPIC_API_KEY")) return "key";
|
|
@@ -54014,7 +54298,6 @@ async function dispatchAssistantRoute(req, res, ctx) {
|
|
|
54014
54298
|
hasAnthropicKey,
|
|
54015
54299
|
hasOpenaiKey,
|
|
54016
54300
|
hasElevenlabsKey,
|
|
54017
|
-
hasClaudeAuth: await hasClaudeAuth(db),
|
|
54018
54301
|
claudeAuthKind: await claudeAuthKind(db),
|
|
54019
54302
|
hasVoiceKey: voice !== null,
|
|
54020
54303
|
sttProvider: voice?.provider ?? null,
|
|
@@ -54142,13 +54425,17 @@ async function dispatchAssistantRoute(req, res, ctx) {
|
|
|
54142
54425
|
const verifier = generatePkceVerifier();
|
|
54143
54426
|
const state2 = generateState();
|
|
54144
54427
|
const cookieOpts = "HttpOnly; Path=/; Max-Age=600; SameSite=Lax";
|
|
54145
|
-
|
|
54146
|
-
|
|
54147
|
-
|
|
54148
|
-
|
|
54149
|
-
|
|
54150
|
-
|
|
54151
|
-
|
|
54428
|
+
const setCookie = [
|
|
54429
|
+
`lat_oauth_verifier=${verifier}; ${cookieOpts}`,
|
|
54430
|
+
`lat_oauth_state=${state2}; ${cookieOpts}`
|
|
54431
|
+
];
|
|
54432
|
+
const authorizeUrl = buildAuthorizeUrl(cfg, state2, pkceChallengeFor(verifier));
|
|
54433
|
+
if ((req.headers.accept ?? "").includes("application/json")) {
|
|
54434
|
+
res.writeHead(200, { "Content-Type": "application/json", "Set-Cookie": setCookie });
|
|
54435
|
+
res.end(JSON.stringify({ authorizeUrl }));
|
|
54436
|
+
return true;
|
|
54437
|
+
}
|
|
54438
|
+
res.writeHead(302, { Location: authorizeUrl, "Set-Cookie": setCookie });
|
|
54152
54439
|
res.end();
|
|
54153
54440
|
return true;
|
|
54154
54441
|
}
|
|
@@ -57459,7 +57746,7 @@ var init_tools = __esm({
|
|
|
57459
57746
|
});
|
|
57460
57747
|
|
|
57461
57748
|
// src/gui/ai/chat.ts
|
|
57462
|
-
import { createRequire as
|
|
57749
|
+
import { createRequire as createRequire8 } from "module";
|
|
57463
57750
|
function capToolResult(s2) {
|
|
57464
57751
|
if (s2.length <= MAX_TOOL_RESULT_CHARS) return s2;
|
|
57465
57752
|
if (s2.length > MAX_TOOL_RESULT_SKIP)
|
|
@@ -57692,7 +57979,7 @@ async function* runChat(opts) {
|
|
|
57692
57979
|
function loadSdk() {
|
|
57693
57980
|
if (!_sdk) {
|
|
57694
57981
|
const importMetaUrl = import.meta.url;
|
|
57695
|
-
const req = importMetaUrl ?
|
|
57982
|
+
const req = importMetaUrl ? createRequire8(importMetaUrl) : __require;
|
|
57696
57983
|
try {
|
|
57697
57984
|
_sdk = req("@anthropic-ai/sdk");
|
|
57698
57985
|
} catch (err) {
|
|
@@ -58307,6 +58594,7 @@ async function autoUpdate(opts) {
|
|
|
58307
58594
|
|
|
58308
58595
|
// src/index.ts
|
|
58309
58596
|
init_sqlite();
|
|
58597
|
+
init_sqlite_deno();
|
|
58310
58598
|
init_postgres();
|
|
58311
58599
|
init_computed();
|
|
58312
58600
|
init_governance();
|
|
@@ -59630,7 +59918,7 @@ function isBetter(next, prev) {
|
|
|
59630
59918
|
|
|
59631
59919
|
// src/ai/vision.ts
|
|
59632
59920
|
init_llm_client();
|
|
59633
|
-
import { createRequire as
|
|
59921
|
+
import { createRequire as createRequire6 } from "module";
|
|
59634
59922
|
import { readFile as readFile5 } from "fs/promises";
|
|
59635
59923
|
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.";
|
|
59636
59924
|
var MAX_DIM = 1568;
|
|
@@ -59692,7 +59980,7 @@ function buildVisionAnthropicConfig(auth) {
|
|
|
59692
59980
|
function defaultSender(auth) {
|
|
59693
59981
|
return async (input) => {
|
|
59694
59982
|
const importMetaUrl = import.meta.url;
|
|
59695
|
-
const req = importMetaUrl ?
|
|
59983
|
+
const req = importMetaUrl ? createRequire6(importMetaUrl) : __require;
|
|
59696
59984
|
const sdk = req("@anthropic-ai/sdk");
|
|
59697
59985
|
const Anthropic = sdk.Anthropic ?? sdk.default;
|
|
59698
59986
|
if (!Anthropic) throw new Error("Could not resolve Anthropic from '@anthropic-ai/sdk'");
|
|
@@ -59719,7 +60007,7 @@ function defaultSender(auth) {
|
|
|
59719
60007
|
function defaultPdfSender(auth) {
|
|
59720
60008
|
return async (input) => {
|
|
59721
60009
|
const importMetaUrl = import.meta.url;
|
|
59722
|
-
const req = importMetaUrl ?
|
|
60010
|
+
const req = importMetaUrl ? createRequire6(importMetaUrl) : __require;
|
|
59723
60011
|
const sdk = req("@anthropic-ai/sdk");
|
|
59724
60012
|
const Anthropic = sdk.Anthropic ?? sdk.default;
|
|
59725
60013
|
if (!Anthropic) throw new Error("Could not resolve Anthropic from '@anthropic-ai/sdk'");
|
|
@@ -60030,12 +60318,12 @@ init_postgres();
|
|
|
60030
60318
|
|
|
60031
60319
|
// src/gui/realtime.ts
|
|
60032
60320
|
import { EventEmitter } from "events";
|
|
60033
|
-
import { createRequire as
|
|
60321
|
+
import { createRequire as createRequire7 } from "module";
|
|
60034
60322
|
var _pgModule = null;
|
|
60035
60323
|
function loadPg() {
|
|
60036
60324
|
if (_pgModule) return _pgModule;
|
|
60037
60325
|
const importMetaUrl = import.meta.url;
|
|
60038
|
-
const requireFromHere = importMetaUrl ?
|
|
60326
|
+
const requireFromHere = importMetaUrl ? createRequire7(importMetaUrl) : (
|
|
60039
60327
|
// CJS fallback — Node provides `require` on every CJS module scope.
|
|
60040
60328
|
__require
|
|
60041
60329
|
);
|
|
@@ -63394,6 +63682,20 @@ var displayConfigJs = `
|
|
|
63394
63682
|
});
|
|
63395
63683
|
}
|
|
63396
63684
|
|
|
63685
|
+
// SINGLE SOURCE OF TRUTH for the assistant's Claude connection state, derived
|
|
63686
|
+
// from /api/assistant/config's claudeAuthKind (oauth | key | null). EVERY
|
|
63687
|
+
// place that shows "Connected with Claude" / opens the API-key panel / gates
|
|
63688
|
+
// on "the assistant has auth" MUST go through this \u2014 never re-derive from raw
|
|
63689
|
+
// fields, or the signals disagree (a stray "or hasAnthropicKey" once made
|
|
63690
|
+
// onboarding show "Connected with Claude" for an API-key-only setup while the
|
|
63691
|
+
// settings panel showed not-connected).
|
|
63692
|
+
// .oauth -> a Claude SUBSCRIPTION is connected ("Connected with Claude")
|
|
63693
|
+
// .any -> some working auth exists (subscription OR API key)
|
|
63694
|
+
function claudeAuth(cfg) {
|
|
63695
|
+
var kind = (cfg && cfg.claudeAuthKind) || null; // 'oauth' | 'key' | null
|
|
63696
|
+
return { kind: kind, oauth: kind === 'oauth', any: kind != null };
|
|
63697
|
+
}
|
|
63698
|
+
|
|
63397
63699
|
// Disable a button + show an inline spinner for the duration of an
|
|
63398
63700
|
// async action so a slow server round-trip can't be double-clicked.
|
|
63399
63701
|
// The fn arg should return a Promise; the button is restored on settle.
|
|
@@ -64184,7 +64486,12 @@ var offlineEditQueueJs = ` // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
|
|
|
64184
64486
|
if (eventStreamClosed) return;
|
|
64185
64487
|
// Unexpected drop: show the disconnect on the pill and auto-reconnect with
|
|
64186
64488
|
// backoff (the server replays state + render snapshot on reconnect).
|
|
64187
|
-
|
|
64489
|
+
// Preserve the KNOWN mode (cloudMode is the single source of truth, set
|
|
64490
|
+
// from the server's realtime-state message) \u2014 never hardcode 'cloud',
|
|
64491
|
+
// which on a LOCAL (SQLite) workspace would flip cloudMode=true and divert
|
|
64492
|
+
// writes into the offline queue with a bogus "will sync when cloud
|
|
64493
|
+
// reconnects" toast against a workspace that has no cloud.
|
|
64494
|
+
setStatusPill(cloudMode ? 'cloud' : 'local', 'disconnected');
|
|
64188
64495
|
scheduleEventStreamReconnect();
|
|
64189
64496
|
};
|
|
64190
64497
|
}
|
|
@@ -68092,7 +68399,7 @@ var rowContextJs = ` // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250
|
|
|
68092
68399
|
var cfg = res[1];
|
|
68093
68400
|
st.name = (id && id.display_name) || '';
|
|
68094
68401
|
st.email = (id && id.email) || '';
|
|
68095
|
-
st.connected =
|
|
68402
|
+
st.connected = claudeAuth(cfg).oauth;
|
|
68096
68403
|
if (!st.wsName && st.name) st.wsName = st.name + "'s Workspace";
|
|
68097
68404
|
render();
|
|
68098
68405
|
});
|
|
@@ -68753,7 +69060,7 @@ var dataModelJs = ` // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
|
68753
69060
|
'</p>' +
|
|
68754
69061
|
// Connect-with-Claude is the primary path (use your subscription, no
|
|
68755
69062
|
// API key). A pasted API key is demoted to an "Advanced" disclosure.
|
|
68756
|
-
(cfg.
|
|
69063
|
+
(claudeAuth(cfg).oauth
|
|
68757
69064
|
? '<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">' +
|
|
68758
69065
|
'<span class="feed-source" style="background:var(--accent-soft);color:var(--accent)">Connected with Claude</span>' +
|
|
68759
69066
|
'<button id="asst-oauth-disconnect" class="btn">Disconnect</button>' +
|
|
@@ -68775,7 +69082,7 @@ var dataModelJs = ` // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
|
68775
69082
|
'</div>' +
|
|
68776
69083
|
'<div id="connect-claude-msg" style="margin-top:6px;font-size:12px;color:var(--text-muted)"></div>' +
|
|
68777
69084
|
'</div>') +
|
|
68778
|
-
'<details style="margin-bottom:12px"' + (cfg.
|
|
69085
|
+
'<details style="margin-bottom:12px"' + (claudeAuth(cfg).kind === 'key' ? ' open' : '') + '>' +
|
|
68779
69086
|
'<summary style="cursor:pointer;font-size:12px;color:var(--text-muted)">Advanced \u2014 use an API key instead</summary>' +
|
|
68780
69087
|
'<div style="margin-top:8px">' +
|
|
68781
69088
|
rowHtml('asst-anthropic', 'Claude API token (chat)', !!cfg.hasAnthropicKey, 'sk-ant-\u2026') +
|
|
@@ -70767,7 +71074,7 @@ var createDatabaseWizardJs = ` // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\
|
|
|
70767
71074
|
function renderComposer() {
|
|
70768
71075
|
var host = document.getElementById('rail-composer'); if (!host) return;
|
|
70769
71076
|
fetchJson('/api/assistant/config').then(function (cfg) {
|
|
70770
|
-
if (cfg
|
|
71077
|
+
if (claudeAuth(cfg).any) {
|
|
70771
71078
|
var micHtml = cfg.hasVoiceKey
|
|
70772
71079
|
? '<button class="composer-mic" id="chat-mic" title="Record voice">\u{1F399}</button>'
|
|
70773
71080
|
: '';
|
|
@@ -77118,6 +77425,7 @@ async function startGuiServer(options) {
|
|
|
77118
77425
|
}
|
|
77119
77426
|
const autoRender = options.autoRender ?? false;
|
|
77120
77427
|
const guiVersion = options.version ?? "";
|
|
77428
|
+
const desktopOpenExternal = options.desktopOpenExternal;
|
|
77121
77429
|
const sessionId = crypto.randomUUID();
|
|
77122
77430
|
let updateService = null;
|
|
77123
77431
|
let activeRef = bootConfigPath && bootOutputDir ? await openConfig(bootConfigPath, bootOutputDir, autoRender, options.realtimeWatchdogMs) : null;
|
|
@@ -77300,6 +77608,20 @@ async function startGuiServer(options) {
|
|
|
77300
77608
|
sendJson(res, { version: guiVersion });
|
|
77301
77609
|
return;
|
|
77302
77610
|
}
|
|
77611
|
+
if (method === "GET" && pathname === "/api/desktop/open") {
|
|
77612
|
+
if (!desktopOpenExternal) {
|
|
77613
|
+
sendJson(res, { error: "not found" }, 404);
|
|
77614
|
+
return;
|
|
77615
|
+
}
|
|
77616
|
+
const target = new URL(req.url ?? "", "http://localhost").searchParams.get("url");
|
|
77617
|
+
if (!target || !/^https?:\/\//i.test(target)) {
|
|
77618
|
+
sendJson(res, { error: "url must be http(s)" }, 400);
|
|
77619
|
+
return;
|
|
77620
|
+
}
|
|
77621
|
+
desktopOpenExternal(target);
|
|
77622
|
+
sendJson(res, { ok: true });
|
|
77623
|
+
return;
|
|
77624
|
+
}
|
|
77303
77625
|
if (method === "GET" && pathname === "/api/update/status") {
|
|
77304
77626
|
sendJson(
|
|
77305
77627
|
res,
|
|
@@ -77867,6 +78189,7 @@ export {
|
|
|
77867
78189
|
DEFAULT_ENTRY_TYPES,
|
|
77868
78190
|
DEFAULT_MAX_NODES,
|
|
77869
78191
|
DEFAULT_TYPE_ALIASES,
|
|
78192
|
+
DenoSqliteAdapter,
|
|
77870
78193
|
EMBEDDINGS_TABLE,
|
|
77871
78194
|
EmbeddingDimensionMismatchError,
|
|
77872
78195
|
EmbeddingScanTooLargeError,
|