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/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 = 2;
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 createRequire2 } from "module";
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: createRequire2(importMetaUrl)
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 slugs = new Set(rows.map((row) => def.slug(row)));
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 rawSlug = def.slug(entityRow);
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 createRequire3 } from "module";
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 createRequire4 } from "module";
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 ? createRequire4(importMetaUrl) : __require;
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
- res.writeHead(302, {
54146
- Location: buildAuthorizeUrl(cfg, state2, pkceChallengeFor(verifier)),
54147
- "Set-Cookie": [
54148
- `lat_oauth_verifier=${verifier}; ${cookieOpts}`,
54149
- `lat_oauth_state=${state2}; ${cookieOpts}`
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 createRequire7 } from "module";
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 ? createRequire7(importMetaUrl) : __require;
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 createRequire5 } from "module";
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 ? createRequire5(importMetaUrl) : __require;
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 ? createRequire5(importMetaUrl) : __require;
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 createRequire6 } from "module";
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 ? createRequire6(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
- setStatusPill('cloud', 'disconnected');
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 = !!(cfg && (cfg.claudeAuthKind === 'oauth' || cfg.hasAnthropicKey));
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.claudeAuthKind === 'oauth'
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.claudeAuthKind === 'key' ? ' open' : '') + '>' +
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 && cfg.hasClaudeAuth) {
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,