@digitalvibes/ai-knowledge-db 0.1.0 → 0.2.0

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 CHANGED
@@ -79,13 +79,34 @@ await kb.close();
79
79
 
80
80
  | Method | Purpose |
81
81
  |--------|---------|
82
- | `kb.init()` | Create extension, table, indexes (idempotent). |
83
- | `kb.add(input)` | Chunk + embed + store. Scope/client/project fall back to env. |
84
- | `kb.upsertSource(input)` | Idempotent re-ingest of a `source` (delete + re-add). |
85
- | `kb.search(query, opts)` | Scoped semantic search ranked `SearchResult[]`. |
86
- | `kb.delete(filter)` | Delete by id / source / project / client / scope. |
82
+ | `kb.init()` | Create/upgrade extension, table, indexes (idempotent). |
83
+ | `kb.add(input)` | Append free-form knowledge (never overrides). |
84
+ | `kb.put(input)` | Versioned write by `key` supersedes prior version, keeps history. |
85
+ | `kb.upsertSource(input)` | Re-ingest a `source`, superseding its prior version. |
86
+ | `kb.search(query, opts)` | Scoped semantic search ranked `SearchResult[]`. Current versions only unless `includeHistory`/`asOf`. |
87
+ | `kb.history(key, opts)` | All versions of a `key`, newest first. |
88
+ | `kb.delete(filter)` | Hard-delete by id / key / source / project / client / scope. |
87
89
  | `kb.close()` | Close the pool. |
88
90
 
91
+ ### Keeping facts current (pricing, hours, …)
92
+
93
+ Semantic search has no notion of "newest", so changeable facts use a stable
94
+ **`key`**. Writing the key again supersedes the old version (kept as history) and
95
+ makes the new value the only one returned in normal search:
96
+
97
+ ```ts
98
+ await kb.put({ key: "pricing.basic-plan", content: "Basic plan is €50/mo.", scope: "client" });
99
+ await kb.put({ key: "pricing.basic-plan", content: "Basic plan is €80/mo.", scope: "client" });
100
+
101
+ await kb.search("how much is the basic plan?"); // → €80 only
102
+ await kb.search("…", { includeHistory: true }); // → both versions
103
+ await kb.search("…", { asOf: "2026-01-01" }); // → what was current then
104
+ await kb.history("pricing.basic-plan", { scope: "client" }); // → [€80 current, €50 superseded]
105
+ ```
106
+
107
+ Use `add` for accumulating notes, `put --key` for facts that change, and
108
+ `add-file --source` for documents.
109
+
89
110
  ### Scoping a search
90
111
 
91
112
  ```ts
package/dist/cli.cjs CHANGED
@@ -138,20 +138,28 @@ create extension if not exists vector;
138
138
  create extension if not exists "pgcrypto"; -- for gen_random_uuid()
139
139
 
140
140
  create table if not exists knowledge (
141
- id uuid primary key default gen_random_uuid(),
142
- scope text not null check (scope in ('global', 'client', 'project')),
143
- client_id text,
144
- project_id text,
145
- source text,
146
- content text not null,
147
- embedding vector(1536) not null,
148
- metadata jsonb not null default '{}',
149
- created_at timestamptz not null default now()
141
+ id uuid primary key default gen_random_uuid(),
142
+ scope text not null check (scope in ('global', 'client', 'project')),
143
+ client_id text,
144
+ project_id text,
145
+ key text, -- stable identity for a versioned fact (e.g. 'pricing.basic-plan')
146
+ source text,
147
+ content text not null,
148
+ embedding vector(1536) not null,
149
+ metadata jsonb not null default '{}',
150
+ created_at timestamptz not null default now(), -- valid from
151
+ superseded_at timestamptz -- valid to; null = current
150
152
  );
151
153
 
154
+ -- Upgrade existing (v0.1) installs in place.
155
+ alter table knowledge add column if not exists key text;
156
+ alter table knowledge add column if not exists superseded_at timestamptz;
157
+
152
158
  create index if not exists knowledge_client_idx on knowledge (client_id);
153
159
  create index if not exists knowledge_project_idx on knowledge (project_id);
154
160
  create index if not exists knowledge_scope_idx on knowledge (scope);
161
+ create index if not exists knowledge_key_idx on knowledge (key);
162
+ create index if not exists knowledge_active_idx on knowledge (superseded_at);
155
163
  create index if not exists knowledge_metadata_idx on knowledge using gin (metadata);
156
164
 
157
165
  create index if not exists knowledge_embedding_idx
@@ -169,59 +177,37 @@ var KnowledgeDB = class {
169
177
  this.pool = new Pool({ connectionString: this.config.connectionString });
170
178
  this.embedder = new Embedder(this.config);
171
179
  }
172
- /** Create the extension, table, and indexes if they don't exist. Safe to call repeatedly. */
180
+ /** Create/upgrade the extension, table, and indexes. Safe to call repeatedly. */
173
181
  async init() {
174
182
  await this.pool.query(SCHEMA_SQL);
175
183
  }
176
184
  /**
177
- * Add knowledge. The content is chunked, embedded, and stored. Returns the
178
- * ids of the stored rows (one per chunk). Scope/client/project fall back to
179
- * the env-configured defaults.
185
+ * Append free-form knowledge (no version identity). Use for notes that
186
+ * accumulate. For facts that change over time (pricing, hours, contact), use
187
+ * put() or upsertSource() so newer versions supersede older ones.
180
188
  */
181
189
  async add(input) {
182
- const clientId = input.clientId ?? this.config.clientId ?? null;
183
- const projectId = input.projectId ?? this.config.projectId ?? null;
184
- const scope = input.scope ?? defaultScope(clientId, projectId);
185
- const source = input.source ?? null;
186
- const metadata = input.metadata ?? {};
187
- const chunks = chunkText(input.content, input.chunking);
188
- if (chunks.length === 0) return [];
189
- const vectors = await this.embedder.embed(chunks);
190
- const ids = [];
191
- const client = await this.pool.connect();
192
- try {
193
- await client.query("begin");
194
- for (let i = 0; i < chunks.length; i++) {
195
- const chunkMeta = chunks.length > 1 ? { ...metadata, chunk: i, chunks: chunks.length } : metadata;
196
- const { rows } = await client.query(
197
- `insert into knowledge (scope, client_id, project_id, source, content, embedding, metadata)
198
- values ($1, $2, $3, $4, $5, $6, $7) returning id`,
199
- [scope, clientId, projectId, source, chunks[i], toVector(vectors[i]), chunkMeta]
200
- );
201
- ids.push(rows[0].id);
202
- }
203
- await client.query("commit");
204
- } catch (err) {
205
- await client.query("rollback");
206
- throw err;
207
- } finally {
208
- client.release();
209
- }
210
- return ids;
190
+ const t = this.resolveTarget(input);
191
+ return this.insertChunks(this.pool, t, input, null);
192
+ }
193
+ /**
194
+ * Versioned write keyed by `key`. Any existing *active* rows with the same
195
+ * key (+ scope/client/project) are stamped superseded_at = now() and kept as
196
+ * history; the new content becomes the current version. So updated pricing
197
+ * wins in search while the old value remains for audit / point-in-time.
198
+ */
199
+ async put(input) {
200
+ return this.versionedWrite("key", input.key, input);
211
201
  }
212
202
  /**
213
- * Replace knowledge from a given source. Deletes existing rows that match the
214
- * same scope + ids + source, then re-adds. Use this for idempotent re-ingest
215
- * of a file or URL so you don't accumulate duplicates.
203
+ * Re-ingest a document by `source`, superseding (not deleting) the prior
204
+ * active version for that source. Idempotent: re-running keeps history and
205
+ * makes the latest content current.
216
206
  */
217
207
  async upsertSource(input) {
218
- const clientId = input.clientId ?? this.config.clientId ?? null;
219
- const projectId = input.projectId ?? this.config.projectId ?? null;
220
- const scope = input.scope ?? defaultScope(clientId, projectId);
221
- await this.delete({ scope, clientId: clientId ?? void 0, projectId: projectId ?? void 0, source: input.source });
222
- return this.add(input);
208
+ return this.versionedWrite("source", input.source, input);
223
209
  }
224
- /** Semantic search, scoped to client / project / global knowledge. */
210
+ /** Semantic search. Returns only current versions unless includeHistory/asOf. */
225
211
  async search(query, opts = {}) {
226
212
  const clientId = opts.clientId ?? this.config.clientId;
227
213
  const projectId = opts.projectId ?? this.config.projectId;
@@ -230,9 +216,9 @@ var KnowledgeDB = class {
230
216
  const limit = opts.limit ?? 8;
231
217
  const minScore = opts.minScore ?? 0;
232
218
  const queryVec = toVector(await this.embedder.embedOne(query));
233
- const orClauses = [];
234
219
  const params = [queryVec];
235
220
  const p = (v) => `$${params.push(v)}`;
221
+ const orClauses = [];
236
222
  if (projectId && allows(opts.scopes, "project")) {
237
223
  orClauses.push(`(scope = 'project' and project_id = ${p(projectId)})`);
238
224
  }
@@ -247,8 +233,16 @@ var KnowledgeDB = class {
247
233
  if (opts.metadata) {
248
234
  where.push(`metadata @> ${p(JSON.stringify(opts.metadata))}::jsonb`);
249
235
  }
236
+ if (opts.asOf !== void 0) {
237
+ const at = typeof opts.asOf === "string" ? opts.asOf : opts.asOf.toISOString();
238
+ where.push(
239
+ `created_at <= ${p(at)}::timestamptz and (superseded_at is null or superseded_at > ${p(at)}::timestamptz)`
240
+ );
241
+ } else if (!opts.includeHistory) {
242
+ where.push(`superseded_at is null`);
243
+ }
250
244
  const { rows } = await this.pool.query(
251
- `select id, scope, client_id, project_id, source, content, metadata, created_at,
245
+ `select id, scope, client_id, project_id, key, source, content, metadata, created_at, superseded_at,
252
246
  1 - (embedding <=> $1) as score
253
247
  from knowledge
254
248
  where ${where.join(" and ")}
@@ -258,7 +252,33 @@ var KnowledgeDB = class {
258
252
  );
259
253
  return rows.map(rowToResult).filter((r) => r.score >= minScore);
260
254
  }
261
- /** Delete rows matching a filter. Returns the number deleted. */
255
+ /**
256
+ * Return all versions of a `key` (or all rows for a source via metadata),
257
+ * newest first, including superseded ones. Useful for audit / "what did we
258
+ * say before".
259
+ */
260
+ async history(key, opts = {}) {
261
+ const clientId = opts.clientId ?? this.config.clientId ?? null;
262
+ const projectId = opts.projectId ?? this.config.projectId ?? null;
263
+ const scope = opts.scope ?? defaultScope(clientId, projectId);
264
+ const includeHistory = opts.includeHistory ?? true;
265
+ const where = [
266
+ `key = $1`,
267
+ `scope = $2`,
268
+ `client_id is not distinct from $3`,
269
+ `project_id is not distinct from $4`
270
+ ];
271
+ if (!includeHistory) where.push(`superseded_at is null`);
272
+ const { rows } = await this.pool.query(
273
+ `select id, scope, client_id, project_id, key, source, content, metadata, created_at, superseded_at
274
+ from knowledge
275
+ where ${where.join(" and ")}
276
+ order by created_at desc`,
277
+ [key, scope, clientId, projectId]
278
+ );
279
+ return rows.map(rowToRecord);
280
+ }
281
+ /** Hard-delete rows matching a filter (removes history too). Returns count. */
262
282
  async delete(filter) {
263
283
  const where = [];
264
284
  const params = [];
@@ -268,6 +288,7 @@ var KnowledgeDB = class {
268
288
  if (filter.clientId) where.push(`client_id = ${p(filter.clientId)}`);
269
289
  if (filter.projectId) where.push(`project_id = ${p(filter.projectId)}`);
270
290
  if (filter.source) where.push(`source = ${p(filter.source)}`);
291
+ if (filter.key) where.push(`key = ${p(filter.key)}`);
271
292
  if (where.length === 0) {
272
293
  throw new Error("[ai-knowledge-db] delete() requires at least one filter to avoid wiping the table.");
273
294
  }
@@ -281,6 +302,78 @@ var KnowledgeDB = class {
281
302
  async close() {
282
303
  await this.pool.end();
283
304
  }
305
+ // --- internals ---------------------------------------------------------
306
+ resolveTarget(input) {
307
+ const clientId = input.clientId ?? this.config.clientId ?? null;
308
+ const projectId = input.projectId ?? this.config.projectId ?? null;
309
+ return {
310
+ clientId,
311
+ projectId,
312
+ scope: input.scope ?? defaultScope(clientId, projectId),
313
+ source: input.source ?? null
314
+ };
315
+ }
316
+ /** Supersede prior active rows matching column=value, then insert new active rows. */
317
+ async versionedWrite(matchColumn, matchValue, input) {
318
+ const t = this.resolveTarget(input);
319
+ const key = matchColumn === "key" ? matchValue : null;
320
+ const chunks = chunkText(input.content, input.chunking);
321
+ if (chunks.length === 0) return [];
322
+ const vectors = await this.embedder.embed(chunks);
323
+ const client = await this.pool.connect();
324
+ try {
325
+ await client.query("begin");
326
+ await client.query(
327
+ `update knowledge set superseded_at = now()
328
+ where ${matchColumn} = $1 and scope = $2
329
+ and client_id is not distinct from $3
330
+ and project_id is not distinct from $4
331
+ and superseded_at is null`,
332
+ [matchValue, t.scope, t.clientId, t.projectId]
333
+ );
334
+ const ids = await this.insertChunksTx(client, t, input, key, chunks, vectors);
335
+ await client.query("commit");
336
+ return ids;
337
+ } catch (err) {
338
+ await client.query("rollback");
339
+ throw err;
340
+ } finally {
341
+ client.release();
342
+ }
343
+ }
344
+ /** Insert chunks on a fresh connection (embeds inside). */
345
+ async insertChunks(runner, t, input, key) {
346
+ const chunks = chunkText(input.content, input.chunking);
347
+ if (chunks.length === 0) return [];
348
+ const vectors = await this.embedder.embed(chunks);
349
+ const client = await runner.connect();
350
+ try {
351
+ await client.query("begin");
352
+ const ids = await this.insertChunksTx(client, t, input, key, chunks, vectors);
353
+ await client.query("commit");
354
+ return ids;
355
+ } catch (err) {
356
+ await client.query("rollback");
357
+ throw err;
358
+ } finally {
359
+ client.release();
360
+ }
361
+ }
362
+ /** Insert chunk rows on an existing transaction client. */
363
+ async insertChunksTx(client, t, input, key, chunks, vectors) {
364
+ const metadata = input.metadata ?? {};
365
+ const ids = [];
366
+ for (let i = 0; i < chunks.length; i++) {
367
+ const chunkMeta = chunks.length > 1 ? { ...metadata, chunk: i, chunks: chunks.length } : metadata;
368
+ const { rows } = await client.query(
369
+ `insert into knowledge (scope, client_id, project_id, key, source, content, embedding, metadata)
370
+ values ($1, $2, $3, $4, $5, $6, $7, $8) returning id`,
371
+ [t.scope, t.clientId, t.projectId, key, t.source, chunks[i], toVector(vectors[i]), chunkMeta]
372
+ );
373
+ ids.push(rows[0].id);
374
+ }
375
+ return ids;
376
+ }
284
377
  };
285
378
  function createKnowledgeDB(config) {
286
379
  return new KnowledgeDB(config);
@@ -305,26 +398,34 @@ function rowToRecord(row) {
305
398
  scope: row.scope,
306
399
  clientId: row.client_id,
307
400
  projectId: row.project_id,
401
+ key: row.key ?? null,
308
402
  source: row.source,
309
403
  content: row.content,
310
404
  metadata: row.metadata ?? {},
311
- createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at
405
+ createdAt: toIso(row.created_at),
406
+ supersededAt: row.superseded_at ? toIso(row.superseded_at) : null
312
407
  };
313
408
  }
409
+ function toIso(v) {
410
+ return v instanceof Date ? v.toISOString() : String(v);
411
+ }
314
412
 
315
413
  // src/cli.ts
414
+ var BOOL_FLAGS = /* @__PURE__ */ new Set(["json", "history"]);
316
415
  function parse(argv) {
317
416
  const positional = [];
318
417
  const flags = {};
319
418
  for (let i = 0; i < argv.length; i++) {
320
419
  const a = argv[i];
321
- if (a === "--json") flags.json = true;
322
- else if (a.startsWith("--")) flags[a.slice(2)] = argv[++i];
323
- else positional.push(a);
420
+ if (a.startsWith("--")) {
421
+ const name = a.slice(2);
422
+ if (BOOL_FLAGS.has(name)) flags[name] = true;
423
+ else flags[name] = argv[++i];
424
+ } else positional.push(a);
324
425
  }
325
426
  return { positional, flags };
326
427
  }
327
- var USAGE = "Usage: knowledge-db <init|add|add-file|search|delete> [args] [flags]\nFlags: --scope --client --project --source --meta --limit --id --json";
428
+ var USAGE = 'Usage: knowledge-db <init|add|put|add-file|search|history|delete> [args] [flags]\n put "text" --key pricing.basic versioned write (supersedes old, keeps history)\n search "query" [--history] [--as-of <iso>]\n history --key pricing.basic list all versions of a key\nFlags: --scope --client --project --source --key --meta --limit --id --as-of --history --json';
328
429
  async function main() {
329
430
  const [command, ...rest] = process.argv.slice(2);
330
431
  if (!command || command === "help" || command === "-h" || command === "--help") {
@@ -354,6 +455,41 @@ async function main() {
354
455
  console.log(`\u2713 stored ${ids.length} chunk(s)`);
355
456
  break;
356
457
  }
458
+ case "put": {
459
+ if (!flags.key) throw new Error("put requires --key (e.g. --key pricing.basic-plan)");
460
+ const ids = await kb.put({
461
+ key: flags.key,
462
+ content: positional.join(" "),
463
+ scope: flags.scope,
464
+ clientId: flags.client,
465
+ projectId: flags.project,
466
+ source: flags.source,
467
+ metadata: meta
468
+ });
469
+ console.log(`\u2713 '${flags.key}' updated \u2192 ${ids.length} chunk(s) now current (old kept as history)`);
470
+ break;
471
+ }
472
+ case "history": {
473
+ if (!flags.key) throw new Error("history requires --key");
474
+ const rows = await kb.history(flags.key, {
475
+ scope: flags.scope,
476
+ clientId: flags.client,
477
+ projectId: flags.project
478
+ });
479
+ if (flags.json) {
480
+ console.log(JSON.stringify(rows, null, 2));
481
+ } else if (rows.length === 0) {
482
+ console.log("(no versions)");
483
+ } else {
484
+ for (const r of rows) {
485
+ const status = r.supersededAt ? `superseded ${r.supersededAt}` : "CURRENT";
486
+ console.log(`
487
+ [${r.createdAt}] (${status})
488
+ ${r.content}`);
489
+ }
490
+ }
491
+ break;
492
+ }
357
493
  case "add-file": {
358
494
  const path = positional[0];
359
495
  const content = (0, import_node_fs.readFileSync)(path, "utf8");
@@ -372,7 +508,9 @@ async function main() {
372
508
  const results = await kb.search(positional.join(" "), {
373
509
  clientId: flags.client,
374
510
  projectId: flags.project,
375
- limit: flags.limit ? Number(flags.limit) : void 0
511
+ limit: flags.limit ? Number(flags.limit) : void 0,
512
+ includeHistory: flags.history,
513
+ asOf: flags["as-of"]
376
514
  });
377
515
  if (flags.json) {
378
516
  console.log(JSON.stringify(results, null, 2));
@@ -393,6 +531,7 @@ ${r.content}`
393
531
  const n = await kb.delete({
394
532
  id: flags.id,
395
533
  source: flags.source,
534
+ key: flags.key,
396
535
  projectId: flags.project,
397
536
  clientId: flags.client,
398
537
  scope: flags.scope
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/client.ts","../src/config.ts","../src/embeddings.ts","../src/schema.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { createKnowledgeDB } from \"./client.js\";\nimport type { Scope } from \"./types.js\";\n\n/**\n * Thin CLI over the library so the skill (and humans) can manage knowledge\n * without writing code. Reads all connection/scope config from env\n * (KNOWLEDGE_DB_URL, OPENAI_API_KEY, KNOWLEDGE_CLIENT_ID, KNOWLEDGE_PROJECT_ID).\n *\n * knowledge-db init\n * knowledge-db add \"text...\" [--scope project] [--client X] [--project Y] [--source S] [--meta '{\"k\":\"v\"}']\n * knowledge-db add-file ./path.md [same flags] (re-ingest = idempotent upsert by source)\n * knowledge-db search \"query\" [--client X] [--project Y] [--limit 8] [--json]\n * knowledge-db delete [--id ID | --source S | --project Y | --client X | --scope S]\n */\n\ninterface Flags {\n scope?: Scope;\n client?: string;\n project?: string;\n source?: string;\n meta?: string;\n limit?: string;\n id?: string;\n json?: boolean;\n}\n\nfunction parse(argv: string[]): { positional: string[]; flags: Flags } {\n const positional: string[] = [];\n const flags: Flags = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--json\") flags.json = true;\n else if (a.startsWith(\"--\")) flags[a.slice(2) as keyof Flags] = argv[++i] as any;\n else positional.push(a);\n }\n return { positional, flags };\n}\n\nconst USAGE =\n \"Usage: knowledge-db <init|add|add-file|search|delete> [args] [flags]\\n\" +\n \"Flags: --scope --client --project --source --meta --limit --id --json\";\n\nasync function main() {\n const [command, ...rest] = process.argv.slice(2);\n\n if (!command || command === \"help\" || command === \"-h\" || command === \"--help\") {\n console.log(USAGE);\n process.exitCode = command ? 0 : 1;\n return;\n }\n\n const { positional, flags } = parse(rest);\n const kb = createKnowledgeDB();\n const meta = flags.meta ? JSON.parse(flags.meta) : undefined;\n\n try {\n switch (command) {\n case \"init\": {\n await kb.init();\n console.log(\"✓ schema ready\");\n break;\n }\n case \"add\": {\n const ids = await kb.add({\n content: positional.join(\" \"),\n scope: flags.scope,\n clientId: flags.client,\n projectId: flags.project,\n source: flags.source,\n metadata: meta,\n });\n console.log(`✓ stored ${ids.length} chunk(s)`);\n break;\n }\n case \"add-file\": {\n const path = positional[0];\n const content = readFileSync(path, \"utf8\");\n const ids = await kb.upsertSource({\n content,\n source: flags.source ?? path,\n scope: flags.scope,\n clientId: flags.client,\n projectId: flags.project,\n metadata: meta,\n });\n console.log(`✓ re-ingested ${path} → ${ids.length} chunk(s)`);\n break;\n }\n case \"search\": {\n const results = await kb.search(positional.join(\" \"), {\n clientId: flags.client,\n projectId: flags.project,\n limit: flags.limit ? Number(flags.limit) : undefined,\n });\n if (flags.json) {\n console.log(JSON.stringify(results, null, 2));\n } else if (results.length === 0) {\n console.log(\"(no matches)\");\n } else {\n for (const r of results) {\n console.log(\n `\\n[${r.score.toFixed(3)}] ${r.scope}${r.source ? ` · ${r.source}` : \"\"}\\n${r.content}`,\n );\n }\n }\n break;\n }\n case \"delete\": {\n const n = await kb.delete({\n id: flags.id,\n source: flags.source,\n projectId: flags.project,\n clientId: flags.client,\n scope: flags.scope,\n });\n console.log(`✓ deleted ${n} row(s)`);\n break;\n }\n default:\n console.log(`Unknown command: ${command}\\n${USAGE}`);\n process.exitCode = 1;\n }\n } finally {\n await kb.close();\n }\n}\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import pg from \"pg\";\nimport { resolveConfig, type KnowledgeConfig, type ResolvedConfig } from \"./config.js\";\nimport { Embedder, chunkText } from \"./embeddings.js\";\nimport { SCHEMA_SQL } from \"./schema.js\";\nimport type {\n AddInput,\n DeleteFilter,\n KnowledgeRecord,\n Scope,\n SearchOptions,\n SearchResult,\n} from \"./types.js\";\n\nconst { Pool } = pg;\n\nexport class KnowledgeDB {\n private pool: pg.Pool;\n private embedder: Embedder;\n readonly config: ResolvedConfig;\n\n constructor(config: KnowledgeConfig = {}) {\n this.config = resolveConfig(config);\n this.pool = new Pool({ connectionString: this.config.connectionString });\n this.embedder = new Embedder(this.config);\n }\n\n /** Create the extension, table, and indexes if they don't exist. Safe to call repeatedly. */\n async init(): Promise<void> {\n await this.pool.query(SCHEMA_SQL);\n }\n\n /**\n * Add knowledge. The content is chunked, embedded, and stored. Returns the\n * ids of the stored rows (one per chunk). Scope/client/project fall back to\n * the env-configured defaults.\n */\n async add(input: AddInput): Promise<string[]> {\n const clientId = input.clientId ?? this.config.clientId ?? null;\n const projectId = input.projectId ?? this.config.projectId ?? null;\n const scope = input.scope ?? defaultScope(clientId, projectId);\n const source = input.source ?? null;\n const metadata = input.metadata ?? {};\n\n const chunks = chunkText(input.content, input.chunking);\n if (chunks.length === 0) return [];\n\n const vectors = await this.embedder.embed(chunks);\n const ids: string[] = [];\n\n const client = await this.pool.connect();\n try {\n await client.query(\"begin\");\n for (let i = 0; i < chunks.length; i++) {\n const chunkMeta =\n chunks.length > 1\n ? { ...metadata, chunk: i, chunks: chunks.length }\n : metadata;\n const { rows } = await client.query(\n `insert into knowledge (scope, client_id, project_id, source, content, embedding, metadata)\n values ($1, $2, $3, $4, $5, $6, $7) returning id`,\n [scope, clientId, projectId, source, chunks[i], toVector(vectors[i]), chunkMeta],\n );\n ids.push(rows[0].id);\n }\n await client.query(\"commit\");\n } catch (err) {\n await client.query(\"rollback\");\n throw err;\n } finally {\n client.release();\n }\n return ids;\n }\n\n /**\n * Replace knowledge from a given source. Deletes existing rows that match the\n * same scope + ids + source, then re-adds. Use this for idempotent re-ingest\n * of a file or URL so you don't accumulate duplicates.\n */\n async upsertSource(input: AddInput & { source: string }): Promise<string[]> {\n const clientId = input.clientId ?? this.config.clientId ?? null;\n const projectId = input.projectId ?? this.config.projectId ?? null;\n const scope = input.scope ?? defaultScope(clientId, projectId);\n await this.delete({ scope, clientId: clientId ?? undefined, projectId: projectId ?? undefined, source: input.source });\n return this.add(input);\n }\n\n /** Semantic search, scoped to client / project / global knowledge. */\n async search(query: string, opts: SearchOptions = {}): Promise<SearchResult[]> {\n const clientId = opts.clientId ?? this.config.clientId;\n const projectId = opts.projectId ?? this.config.projectId;\n const includeClient = opts.includeClientKnowledge ?? true;\n const includeGlobal = opts.includeGlobal ?? true;\n const limit = opts.limit ?? 8;\n const minScore = opts.minScore ?? 0;\n\n const queryVec = toVector(await this.embedder.embedOne(query));\n\n // Build a scope clause: project rows, optionally the client's shared rows,\n // optionally global rows — restricted to the requested scopes if given.\n const orClauses: string[] = [];\n const params: unknown[] = [queryVec];\n const p = (v: unknown) => `$${params.push(v)}`;\n\n if (projectId && allows(opts.scopes, \"project\")) {\n orClauses.push(`(scope = 'project' and project_id = ${p(projectId)})`);\n }\n if (clientId && includeClient && allows(opts.scopes, \"client\")) {\n orClauses.push(`(scope = 'client' and client_id = ${p(clientId)})`);\n }\n if (includeGlobal && allows(opts.scopes, \"global\")) {\n orClauses.push(`scope = 'global'`);\n }\n // If nothing matched (e.g. no ids at all), fall back to global-only.\n const scopeClause = orClauses.length ? `(${orClauses.join(\" or \")})` : `scope = 'global'`;\n\n const where: string[] = [scopeClause];\n if (opts.metadata) {\n where.push(`metadata @> ${p(JSON.stringify(opts.metadata))}::jsonb`);\n }\n\n const { rows } = await this.pool.query(\n `select id, scope, client_id, project_id, source, content, metadata, created_at,\n 1 - (embedding <=> $1) as score\n from knowledge\n where ${where.join(\" and \")}\n order by embedding <=> $1\n limit ${p(limit)}`,\n params,\n );\n\n return rows\n .map(rowToResult)\n .filter((r) => r.score >= minScore);\n }\n\n /** Delete rows matching a filter. Returns the number deleted. */\n async delete(filter: DeleteFilter): Promise<number> {\n const where: string[] = [];\n const params: unknown[] = [];\n const p = (v: unknown) => `$${params.push(v)}`;\n if (filter.id) where.push(`id = ${p(filter.id)}`);\n if (filter.scope) where.push(`scope = ${p(filter.scope)}`);\n if (filter.clientId) where.push(`client_id = ${p(filter.clientId)}`);\n if (filter.projectId) where.push(`project_id = ${p(filter.projectId)}`);\n if (filter.source) where.push(`source = ${p(filter.source)}`);\n if (where.length === 0) {\n throw new Error(\"[ai-knowledge-db] delete() requires at least one filter to avoid wiping the table.\");\n }\n const { rowCount } = await this.pool.query(\n `delete from knowledge where ${where.join(\" and \")}`,\n params,\n );\n return rowCount ?? 0;\n }\n\n /** Close the connection pool. Call on shutdown. */\n async close(): Promise<void> {\n await this.pool.end();\n }\n}\n\nexport function createKnowledgeDB(config?: KnowledgeConfig): KnowledgeDB {\n return new KnowledgeDB(config);\n}\n\nfunction defaultScope(clientId: string | null, projectId: string | null): Scope {\n if (projectId) return \"project\";\n if (clientId) return \"client\";\n return \"global\";\n}\n\nfunction allows(scopes: Scope[] | undefined, scope: Scope): boolean {\n return !scopes || scopes.includes(scope);\n}\n\n/** pgvector accepts a vector literal like '[0.1,0.2,...]'. */\nfunction toVector(vec: number[]): string {\n return `[${vec.join(\",\")}]`;\n}\n\nfunction rowToResult(row: any): SearchResult {\n return { ...rowToRecord(row), score: Number(row.score) };\n}\n\nfunction rowToRecord(row: any): KnowledgeRecord {\n return {\n id: row.id,\n scope: row.scope,\n clientId: row.client_id,\n projectId: row.project_id,\n source: row.source,\n content: row.content,\n metadata: row.metadata ?? {},\n createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,\n };\n}\n","/**\n * All sensitive / per-deployment values live in the *consuming* project's\n * environment — never in this package. A website repo that installs\n * `@dibe/ai-knowledge-db` sets these in its own `.env`:\n *\n * KNOWLEDGE_DB_URL=postgres://user:pass@host:5432/knowledge (Hetzner/EasyPanel)\n * OPENAI_API_KEY=sk-...\n * KNOWLEDGE_CLIENT_ID=acme-corp # default client for this repo\n * KNOWLEDGE_PROJECT_ID=acme-website-2026 # default project for this repo\n *\n * Anything passed explicitly to createKnowledgeDB() overrides the env value,\n * but env is the intended default so callers usually pass nothing.\n */\n\nexport interface KnowledgeConfig {\n /** Postgres connection string. Defaults to env KNOWLEDGE_DB_URL. */\n connectionString?: string;\n /** OpenAI API key. Defaults to env OPENAI_API_KEY. */\n openaiApiKey?: string;\n /** Embedding model. Defaults to env KNOWLEDGE_EMBED_MODEL or text-embedding-3-small. */\n embeddingModel?: string;\n /** Default client scope for this repo. Defaults to env KNOWLEDGE_CLIENT_ID. */\n clientId?: string;\n /** Default project scope for this repo. Defaults to env KNOWLEDGE_PROJECT_ID. */\n projectId?: string;\n}\n\nexport interface ResolvedConfig {\n connectionString: string;\n openaiApiKey: string;\n embeddingModel: string;\n embeddingDimensions: number;\n clientId?: string;\n projectId?: string;\n}\n\n/** text-embedding-3-small → 1536, text-embedding-3-large → 3072. */\nconst MODEL_DIMENSIONS: Record<string, number> = {\n \"text-embedding-3-small\": 1536,\n \"text-embedding-3-large\": 3072,\n \"text-embedding-ada-002\": 1536,\n};\n\nconst env = (key: string): string | undefined => {\n const v = process.env[key];\n return v && v.trim() !== \"\" ? v.trim() : undefined;\n};\n\nexport function resolveConfig(config: KnowledgeConfig = {}): ResolvedConfig {\n const connectionString = config.connectionString ?? env(\"KNOWLEDGE_DB_URL\");\n if (!connectionString) {\n throw new Error(\n \"[ai-knowledge-db] Missing connection string. Set KNOWLEDGE_DB_URL in your project's .env \" +\n \"or pass { connectionString } to createKnowledgeDB().\",\n );\n }\n\n const openaiApiKey = config.openaiApiKey ?? env(\"OPENAI_API_KEY\");\n if (!openaiApiKey) {\n throw new Error(\n \"[ai-knowledge-db] Missing OpenAI key. Set OPENAI_API_KEY in your project's .env \" +\n \"or pass { openaiApiKey } to createKnowledgeDB().\",\n );\n }\n\n const embeddingModel =\n config.embeddingModel ?? env(\"KNOWLEDGE_EMBED_MODEL\") ?? \"text-embedding-3-small\";\n const embeddingDimensions = MODEL_DIMENSIONS[embeddingModel] ?? 1536;\n\n return {\n connectionString,\n openaiApiKey,\n embeddingModel,\n embeddingDimensions,\n clientId: config.clientId ?? env(\"KNOWLEDGE_CLIENT_ID\"),\n projectId: config.projectId ?? env(\"KNOWLEDGE_PROJECT_ID\"),\n };\n}\n","import OpenAI from \"openai\";\nimport type { ResolvedConfig } from \"./config.js\";\nimport type { ChunkOptions } from \"./types.js\";\n\nexport class Embedder {\n private client: OpenAI;\n private model: string;\n\n constructor(config: ResolvedConfig) {\n this.client = new OpenAI({ apiKey: config.openaiApiKey });\n this.model = config.embeddingModel;\n }\n\n /** Embed a batch of strings in one API call. */\n async embed(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n const res = await this.client.embeddings.create({\n model: this.model,\n input: texts,\n });\n // OpenAI preserves input order in the response.\n return res.data\n .sort((a, b) => a.index - b.index)\n .map((d) => d.embedding as number[]);\n }\n\n async embedOne(text: string): Promise<number[]> {\n const [vec] = await this.embed([text]);\n return vec;\n }\n}\n\n/**\n * Split text into overlapping chunks. Prefers paragraph boundaries, then\n * sentence boundaries, falling back to hard character cuts for very long runs.\n */\nexport function chunkText(text: string, opts: ChunkOptions = {}): string[] {\n const maxChars = opts.maxChars ?? 1200;\n const overlap = opts.overlap ?? 150;\n const clean = text.replace(/\\r\\n/g, \"\\n\").trim();\n if (clean.length <= maxChars) return clean ? [clean] : [];\n\n // Split into paragraph-ish units first.\n const units = clean.split(/\\n{2,}/).flatMap((p) => splitLongUnit(p, maxChars));\n\n const chunks: string[] = [];\n let current = \"\";\n for (const unit of units) {\n if (current && current.length + unit.length + 2 > maxChars) {\n chunks.push(current.trim());\n // carry overlap from the tail of the previous chunk\n current = overlap > 0 ? current.slice(-overlap) + \"\\n\\n\" + unit : unit;\n } else {\n current = current ? current + \"\\n\\n\" + unit : unit;\n }\n }\n if (current.trim()) chunks.push(current.trim());\n return chunks;\n}\n\n/** Break a single oversized paragraph on sentence, then hard, boundaries. */\nfunction splitLongUnit(unit: string, maxChars: number): string[] {\n if (unit.length <= maxChars) return [unit];\n const sentences = unit.match(/[^.!?\\n]+[.!?]?\\s*/g) ?? [unit];\n const out: string[] = [];\n let buf = \"\";\n for (const s of sentences) {\n if (s.length > maxChars) {\n if (buf) {\n out.push(buf);\n buf = \"\";\n }\n for (let i = 0; i < s.length; i += maxChars) out.push(s.slice(i, i + maxChars));\n } else if (buf.length + s.length > maxChars) {\n out.push(buf);\n buf = s;\n } else {\n buf += s;\n }\n }\n if (buf) out.push(buf);\n return out;\n}\n","/**\n * Canonical schema (Postgres + pgvector), kept as a string so the library never\n * has to read from disk — works identically in the ESM and CJS builds. The\n * build also writes this out to dist/schema.sql for the `./schema.sql` export\n * and for running by hand. Vector size matches text-embedding-3-small (1536).\n */\nexport const SCHEMA_SQL = `-- AI Knowledge DB schema (Postgres + pgvector)\n-- Run once against your Hetzner/EasyPanel Postgres instance, or via kb.init().\n-- If you switch to text-embedding-3-large, change 1536 -> 3072 and re-index.\n\ncreate extension if not exists vector;\ncreate extension if not exists \"pgcrypto\"; -- for gen_random_uuid()\n\ncreate table if not exists knowledge (\n id uuid primary key default gen_random_uuid(),\n scope text not null check (scope in ('global', 'client', 'project')),\n client_id text,\n project_id text,\n source text,\n content text not null,\n embedding vector(1536) not null,\n metadata jsonb not null default '{}',\n created_at timestamptz not null default now()\n);\n\ncreate index if not exists knowledge_client_idx on knowledge (client_id);\ncreate index if not exists knowledge_project_idx on knowledge (project_id);\ncreate index if not exists knowledge_scope_idx on knowledge (scope);\ncreate index if not exists knowledge_metadata_idx on knowledge using gin (metadata);\n\ncreate index if not exists knowledge_embedding_idx\n on knowledge using hnsw (embedding vector_cosine_ops);\n`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACA,qBAA6B;;;ACD7B,gBAAe;;;ACqCf,IAAM,mBAA2C;AAAA,EAC/C,0BAA0B;AAAA,EAC1B,0BAA0B;AAAA,EAC1B,0BAA0B;AAC5B;AAEA,IAAM,MAAM,CAAC,QAAoC;AAC/C,QAAM,IAAI,QAAQ,IAAI,GAAG;AACzB,SAAO,KAAK,EAAE,KAAK,MAAM,KAAK,EAAE,KAAK,IAAI;AAC3C;AAEO,SAAS,cAAc,SAA0B,CAAC,GAAmB;AAC1E,QAAM,mBAAmB,OAAO,oBAAoB,IAAI,kBAAkB;AAC1E,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,gBAAgB,IAAI,gBAAgB;AAChE,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,iBACJ,OAAO,kBAAkB,IAAI,uBAAuB,KAAK;AAC3D,QAAM,sBAAsB,iBAAiB,cAAc,KAAK;AAEhE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,OAAO,YAAY,IAAI,qBAAqB;AAAA,IACtD,WAAW,OAAO,aAAa,IAAI,sBAAsB;AAAA,EAC3D;AACF;;;AC7EA,oBAAmB;AAIZ,IAAM,WAAN,MAAe;AAAA,EACZ;AAAA,EACA;AAAA,EAER,YAAY,QAAwB;AAClC,SAAK,SAAS,IAAI,cAAAA,QAAO,EAAE,QAAQ,OAAO,aAAa,CAAC;AACxD,SAAK,QAAQ,OAAO;AAAA,EACtB;AAAA;AAAA,EAGA,MAAM,MAAM,OAAsC;AAChD,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,UAAM,MAAM,MAAM,KAAK,OAAO,WAAW,OAAO;AAAA,MAC9C,OAAO,KAAK;AAAA,MACZ,OAAO;AAAA,IACT,CAAC;AAED,WAAO,IAAI,KACR,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAI,CAAC,MAAM,EAAE,SAAqB;AAAA,EACvC;AAAA,EAEA,MAAM,SAAS,MAAiC;AAC9C,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC;AACrC,WAAO;AAAA,EACT;AACF;AAMO,SAAS,UAAU,MAAc,OAAqB,CAAC,GAAa;AACzE,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,QAAQ,KAAK,QAAQ,SAAS,IAAI,EAAE,KAAK;AAC/C,MAAI,MAAM,UAAU,SAAU,QAAO,QAAQ,CAAC,KAAK,IAAI,CAAC;AAGxD,QAAM,QAAQ,MAAM,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC;AAE7E,QAAM,SAAmB,CAAC;AAC1B,MAAI,UAAU;AACd,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,QAAQ,SAAS,KAAK,SAAS,IAAI,UAAU;AAC1D,aAAO,KAAK,QAAQ,KAAK,CAAC;AAE1B,gBAAU,UAAU,IAAI,QAAQ,MAAM,CAAC,OAAO,IAAI,SAAS,OAAO;AAAA,IACpE,OAAO;AACL,gBAAU,UAAU,UAAU,SAAS,OAAO;AAAA,IAChD;AAAA,EACF;AACA,MAAI,QAAQ,KAAK,EAAG,QAAO,KAAK,QAAQ,KAAK,CAAC;AAC9C,SAAO;AACT;AAGA,SAAS,cAAc,MAAc,UAA4B;AAC/D,MAAI,KAAK,UAAU,SAAU,QAAO,CAAC,IAAI;AACzC,QAAM,YAAY,KAAK,MAAM,qBAAqB,KAAK,CAAC,IAAI;AAC5D,QAAM,MAAgB,CAAC;AACvB,MAAI,MAAM;AACV,aAAW,KAAK,WAAW;AACzB,QAAI,EAAE,SAAS,UAAU;AACvB,UAAI,KAAK;AACP,YAAI,KAAK,GAAG;AACZ,cAAM;AAAA,MACR;AACA,eAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,SAAU,KAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC;AAAA,IAChF,WAAW,IAAI,SAAS,EAAE,SAAS,UAAU;AAC3C,UAAI,KAAK,GAAG;AACZ,YAAM;AAAA,IACR,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,IAAK,KAAI,KAAK,GAAG;AACrB,SAAO;AACT;;;AC5EO,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AHO1B,IAAM,EAAE,KAAK,IAAI,UAAAC;AAEV,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EACC;AAAA,EAET,YAAY,SAA0B,CAAC,GAAG;AACxC,SAAK,SAAS,cAAc,MAAM;AAClC,SAAK,OAAO,IAAI,KAAK,EAAE,kBAAkB,KAAK,OAAO,iBAAiB,CAAC;AACvE,SAAK,WAAW,IAAI,SAAS,KAAK,MAAM;AAAA,EAC1C;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,UAAM,KAAK,KAAK,MAAM,UAAU;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAI,OAAoC;AAC5C,UAAM,WAAW,MAAM,YAAY,KAAK,OAAO,YAAY;AAC3D,UAAM,YAAY,MAAM,aAAa,KAAK,OAAO,aAAa;AAC9D,UAAM,QAAQ,MAAM,SAAS,aAAa,UAAU,SAAS;AAC7D,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,WAAW,MAAM,YAAY,CAAC;AAEpC,UAAM,SAAS,UAAU,MAAM,SAAS,MAAM,QAAQ;AACtD,QAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,UAAM,UAAU,MAAM,KAAK,SAAS,MAAM,MAAM;AAChD,UAAM,MAAgB,CAAC;AAEvB,UAAM,SAAS,MAAM,KAAK,KAAK,QAAQ;AACvC,QAAI;AACF,YAAM,OAAO,MAAM,OAAO;AAC1B,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,cAAM,YACJ,OAAO,SAAS,IACZ,EAAE,GAAG,UAAU,OAAO,GAAG,QAAQ,OAAO,OAAO,IAC/C;AACN,cAAM,EAAE,KAAK,IAAI,MAAM,OAAO;AAAA,UAC5B;AAAA;AAAA,UAEA,CAAC,OAAO,UAAU,WAAW,QAAQ,OAAO,CAAC,GAAG,SAAS,QAAQ,CAAC,CAAC,GAAG,SAAS;AAAA,QACjF;AACA,YAAI,KAAK,KAAK,CAAC,EAAE,EAAE;AAAA,MACrB;AACA,YAAM,OAAO,MAAM,QAAQ;AAAA,IAC7B,SAAS,KAAK;AACZ,YAAM,OAAO,MAAM,UAAU;AAC7B,YAAM;AAAA,IACR,UAAE;AACA,aAAO,QAAQ;AAAA,IACjB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,OAAyD;AAC1E,UAAM,WAAW,MAAM,YAAY,KAAK,OAAO,YAAY;AAC3D,UAAM,YAAY,MAAM,aAAa,KAAK,OAAO,aAAa;AAC9D,UAAM,QAAQ,MAAM,SAAS,aAAa,UAAU,SAAS;AAC7D,UAAM,KAAK,OAAO,EAAE,OAAO,UAAU,YAAY,QAAW,WAAW,aAAa,QAAW,QAAQ,MAAM,OAAO,CAAC;AACrH,WAAO,KAAK,IAAI,KAAK;AAAA,EACvB;AAAA;AAAA,EAGA,MAAM,OAAO,OAAe,OAAsB,CAAC,GAA4B;AAC7E,UAAM,WAAW,KAAK,YAAY,KAAK,OAAO;AAC9C,UAAM,YAAY,KAAK,aAAa,KAAK,OAAO;AAChD,UAAM,gBAAgB,KAAK,0BAA0B;AACrD,UAAM,gBAAgB,KAAK,iBAAiB;AAC5C,UAAM,QAAQ,KAAK,SAAS;AAC5B,UAAM,WAAW,KAAK,YAAY;AAElC,UAAM,WAAW,SAAS,MAAM,KAAK,SAAS,SAAS,KAAK,CAAC;AAI7D,UAAM,YAAsB,CAAC;AAC7B,UAAM,SAAoB,CAAC,QAAQ;AACnC,UAAM,IAAI,CAAC,MAAe,IAAI,OAAO,KAAK,CAAC,CAAC;AAE5C,QAAI,aAAa,OAAO,KAAK,QAAQ,SAAS,GAAG;AAC/C,gBAAU,KAAK,uCAAuC,EAAE,SAAS,CAAC,GAAG;AAAA,IACvE;AACA,QAAI,YAAY,iBAAiB,OAAO,KAAK,QAAQ,QAAQ,GAAG;AAC9D,gBAAU,KAAK,qCAAqC,EAAE,QAAQ,CAAC,GAAG;AAAA,IACpE;AACA,QAAI,iBAAiB,OAAO,KAAK,QAAQ,QAAQ,GAAG;AAClD,gBAAU,KAAK,kBAAkB;AAAA,IACnC;AAEA,UAAM,cAAc,UAAU,SAAS,IAAI,UAAU,KAAK,MAAM,CAAC,MAAM;AAEvE,UAAM,QAAkB,CAAC,WAAW;AACpC,QAAI,KAAK,UAAU;AACjB,YAAM,KAAK,eAAe,EAAE,KAAK,UAAU,KAAK,QAAQ,CAAC,CAAC,SAAS;AAAA,IACrE;AAEA,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAK;AAAA,MAC/B;AAAA;AAAA;AAAA,eAGS,MAAM,KAAK,OAAO,CAAC;AAAA;AAAA,eAEnB,EAAE,KAAK,CAAC;AAAA,MACjB;AAAA,IACF;AAEA,WAAO,KACJ,IAAI,WAAW,EACf,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ;AAAA,EACtC;AAAA;AAAA,EAGA,MAAM,OAAO,QAAuC;AAClD,UAAM,QAAkB,CAAC;AACzB,UAAM,SAAoB,CAAC;AAC3B,UAAM,IAAI,CAAC,MAAe,IAAI,OAAO,KAAK,CAAC,CAAC;AAC5C,QAAI,OAAO,GAAI,OAAM,KAAK,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE;AAChD,QAAI,OAAO,MAAO,OAAM,KAAK,WAAW,EAAE,OAAO,KAAK,CAAC,EAAE;AACzD,QAAI,OAAO,SAAU,OAAM,KAAK,eAAe,EAAE,OAAO,QAAQ,CAAC,EAAE;AACnE,QAAI,OAAO,UAAW,OAAM,KAAK,gBAAgB,EAAE,OAAO,SAAS,CAAC,EAAE;AACtE,QAAI,OAAO,OAAQ,OAAM,KAAK,YAAY,EAAE,OAAO,MAAM,CAAC,EAAE;AAC5D,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,IAAI,MAAM,oFAAoF;AAAA,IACtG;AACA,UAAM,EAAE,SAAS,IAAI,MAAM,KAAK,KAAK;AAAA,MACnC,+BAA+B,MAAM,KAAK,OAAO,CAAC;AAAA,MAClD;AAAA,IACF;AACA,WAAO,YAAY;AAAA,EACrB;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,UAAM,KAAK,KAAK,IAAI;AAAA,EACtB;AACF;AAEO,SAAS,kBAAkB,QAAuC;AACvE,SAAO,IAAI,YAAY,MAAM;AAC/B;AAEA,SAAS,aAAa,UAAyB,WAAiC;AAC9E,MAAI,UAAW,QAAO;AACtB,MAAI,SAAU,QAAO;AACrB,SAAO;AACT;AAEA,SAAS,OAAO,QAA6B,OAAuB;AAClE,SAAO,CAAC,UAAU,OAAO,SAAS,KAAK;AACzC;AAGA,SAAS,SAAS,KAAuB;AACvC,SAAO,IAAI,IAAI,KAAK,GAAG,CAAC;AAC1B;AAEA,SAAS,YAAY,KAAwB;AAC3C,SAAO,EAAE,GAAG,YAAY,GAAG,GAAG,OAAO,OAAO,IAAI,KAAK,EAAE;AACzD;AAEA,SAAS,YAAY,KAA2B;AAC9C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI;AAAA,IACb,UAAU,IAAI,YAAY,CAAC;AAAA,IAC3B,WAAW,IAAI,sBAAsB,OAAO,IAAI,WAAW,YAAY,IAAI,IAAI;AAAA,EACjF;AACF;;;ADxKA,SAAS,MAAM,MAAwD;AACrE,QAAM,aAAuB,CAAC;AAC9B,QAAM,QAAe,CAAC;AACtB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,MAAM,SAAU,OAAM,OAAO;AAAA,aACxB,EAAE,WAAW,IAAI,EAAG,OAAM,EAAE,MAAM,CAAC,CAAgB,IAAI,KAAK,EAAE,CAAC;AAAA,QACnE,YAAW,KAAK,CAAC;AAAA,EACxB;AACA,SAAO,EAAE,YAAY,MAAM;AAC7B;AAEA,IAAM,QACJ;AAGF,eAAe,OAAO;AACpB,QAAM,CAAC,SAAS,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC;AAE/C,MAAI,CAAC,WAAW,YAAY,UAAU,YAAY,QAAQ,YAAY,UAAU;AAC9E,YAAQ,IAAI,KAAK;AACjB,YAAQ,WAAW,UAAU,IAAI;AACjC;AAAA,EACF;AAEA,QAAM,EAAE,YAAY,MAAM,IAAI,MAAM,IAAI;AACxC,QAAM,KAAK,kBAAkB;AAC7B,QAAM,OAAO,MAAM,OAAO,KAAK,MAAM,MAAM,IAAI,IAAI;AAEnD,MAAI;AACF,YAAQ,SAAS;AAAA,MACf,KAAK,QAAQ;AACX,cAAM,GAAG,KAAK;AACd,gBAAQ,IAAI,qBAAgB;AAC5B;AAAA,MACF;AAAA,MACA,KAAK,OAAO;AACV,cAAM,MAAM,MAAM,GAAG,IAAI;AAAA,UACvB,SAAS,WAAW,KAAK,GAAG;AAAA,UAC5B,OAAO,MAAM;AAAA,UACb,UAAU,MAAM;AAAA,UAChB,WAAW,MAAM;AAAA,UACjB,QAAQ,MAAM;AAAA,UACd,UAAU;AAAA,QACZ,CAAC;AACD,gBAAQ,IAAI,iBAAY,IAAI,MAAM,WAAW;AAC7C;AAAA,MACF;AAAA,MACA,KAAK,YAAY;AACf,cAAM,OAAO,WAAW,CAAC;AACzB,cAAM,cAAU,6BAAa,MAAM,MAAM;AACzC,cAAM,MAAM,MAAM,GAAG,aAAa;AAAA,UAChC;AAAA,UACA,QAAQ,MAAM,UAAU;AAAA,UACxB,OAAO,MAAM;AAAA,UACb,UAAU,MAAM;AAAA,UAChB,WAAW,MAAM;AAAA,UACjB,UAAU;AAAA,QACZ,CAAC;AACD,gBAAQ,IAAI,sBAAiB,IAAI,WAAM,IAAI,MAAM,WAAW;AAC5D;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,cAAM,UAAU,MAAM,GAAG,OAAO,WAAW,KAAK,GAAG,GAAG;AAAA,UACpD,UAAU,MAAM;AAAA,UAChB,WAAW,MAAM;AAAA,UACjB,OAAO,MAAM,QAAQ,OAAO,MAAM,KAAK,IAAI;AAAA,QAC7C,CAAC;AACD,YAAI,MAAM,MAAM;AACd,kBAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,QAC9C,WAAW,QAAQ,WAAW,GAAG;AAC/B,kBAAQ,IAAI,cAAc;AAAA,QAC5B,OAAO;AACL,qBAAW,KAAK,SAAS;AACvB,oBAAQ;AAAA,cACN;AAAA,GAAM,EAAE,MAAM,QAAQ,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,EAAE,SAAS,SAAM,EAAE,MAAM,KAAK,EAAE;AAAA,EAAK,EAAE,OAAO;AAAA,YACvF;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,cAAM,IAAI,MAAM,GAAG,OAAO;AAAA,UACxB,IAAI,MAAM;AAAA,UACV,QAAQ,MAAM;AAAA,UACd,WAAW,MAAM;AAAA,UACjB,UAAU,MAAM;AAAA,UAChB,OAAO,MAAM;AAAA,QACf,CAAC;AACD,gBAAQ,IAAI,kBAAa,CAAC,SAAS;AACnC;AAAA,MACF;AAAA,MACA;AACE,gBAAQ,IAAI,oBAAoB,OAAO;AAAA,EAAK,KAAK,EAAE;AACnD,gBAAQ,WAAW;AAAA,IACvB;AAAA,EACF,UAAE;AACA,UAAM,GAAG,MAAM;AAAA,EACjB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,GAAG;AACtD,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["OpenAI","pg"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/client.ts","../src/config.ts","../src/embeddings.ts","../src/schema.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { createKnowledgeDB } from \"./client.js\";\nimport type { Scope } from \"./types.js\";\n\n/**\n * Thin CLI over the library so the skill (and humans) can manage knowledge\n * without writing code. Reads all connection/scope config from env\n * (KNOWLEDGE_DB_URL, OPENAI_API_KEY, KNOWLEDGE_CLIENT_ID, KNOWLEDGE_PROJECT_ID).\n *\n * knowledge-db init\n * knowledge-db add \"text...\" [--scope project] [--client X] [--project Y] [--source S] [--meta '{\"k\":\"v\"}']\n * knowledge-db add-file ./path.md [same flags] (re-ingest = idempotent upsert by source)\n * knowledge-db search \"query\" [--client X] [--project Y] [--limit 8] [--json]\n * knowledge-db delete [--id ID | --source S | --project Y | --client X | --scope S]\n */\n\ninterface Flags {\n scope?: Scope;\n client?: string;\n project?: string;\n source?: string;\n key?: string;\n meta?: string;\n limit?: string;\n id?: string;\n \"as-of\"?: string;\n json?: boolean;\n history?: boolean;\n}\n\nconst BOOL_FLAGS = new Set([\"json\", \"history\"]);\n\nfunction parse(argv: string[]): { positional: string[]; flags: Flags } {\n const positional: string[] = [];\n const flags: Flags = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a.startsWith(\"--\")) {\n const name = a.slice(2) as keyof Flags;\n if (BOOL_FLAGS.has(name)) (flags[name] as any) = true;\n else (flags[name] as any) = argv[++i];\n } else positional.push(a);\n }\n return { positional, flags };\n}\n\nconst USAGE =\n \"Usage: knowledge-db <init|add|put|add-file|search|history|delete> [args] [flags]\\n\" +\n \" put \\\"text\\\" --key pricing.basic versioned write (supersedes old, keeps history)\\n\" +\n \" search \\\"query\\\" [--history] [--as-of <iso>]\\n\" +\n \" history --key pricing.basic list all versions of a key\\n\" +\n \"Flags: --scope --client --project --source --key --meta --limit --id --as-of --history --json\";\n\nasync function main() {\n const [command, ...rest] = process.argv.slice(2);\n\n if (!command || command === \"help\" || command === \"-h\" || command === \"--help\") {\n console.log(USAGE);\n process.exitCode = command ? 0 : 1;\n return;\n }\n\n const { positional, flags } = parse(rest);\n const kb = createKnowledgeDB();\n const meta = flags.meta ? JSON.parse(flags.meta) : undefined;\n\n try {\n switch (command) {\n case \"init\": {\n await kb.init();\n console.log(\"✓ schema ready\");\n break;\n }\n case \"add\": {\n const ids = await kb.add({\n content: positional.join(\" \"),\n scope: flags.scope,\n clientId: flags.client,\n projectId: flags.project,\n source: flags.source,\n metadata: meta,\n });\n console.log(`✓ stored ${ids.length} chunk(s)`);\n break;\n }\n case \"put\": {\n if (!flags.key) throw new Error(\"put requires --key (e.g. --key pricing.basic-plan)\");\n const ids = await kb.put({\n key: flags.key,\n content: positional.join(\" \"),\n scope: flags.scope,\n clientId: flags.client,\n projectId: flags.project,\n source: flags.source,\n metadata: meta,\n });\n console.log(`✓ '${flags.key}' updated → ${ids.length} chunk(s) now current (old kept as history)`);\n break;\n }\n case \"history\": {\n if (!flags.key) throw new Error(\"history requires --key\");\n const rows = await kb.history(flags.key, {\n scope: flags.scope,\n clientId: flags.client,\n projectId: flags.project,\n });\n if (flags.json) {\n console.log(JSON.stringify(rows, null, 2));\n } else if (rows.length === 0) {\n console.log(\"(no versions)\");\n } else {\n for (const r of rows) {\n const status = r.supersededAt ? `superseded ${r.supersededAt}` : \"CURRENT\";\n console.log(`\\n[${r.createdAt}] (${status})\\n${r.content}`);\n }\n }\n break;\n }\n case \"add-file\": {\n const path = positional[0];\n const content = readFileSync(path, \"utf8\");\n const ids = await kb.upsertSource({\n content,\n source: flags.source ?? path,\n scope: flags.scope,\n clientId: flags.client,\n projectId: flags.project,\n metadata: meta,\n });\n console.log(`✓ re-ingested ${path} → ${ids.length} chunk(s)`);\n break;\n }\n case \"search\": {\n const results = await kb.search(positional.join(\" \"), {\n clientId: flags.client,\n projectId: flags.project,\n limit: flags.limit ? Number(flags.limit) : undefined,\n includeHistory: flags.history,\n asOf: flags[\"as-of\"],\n });\n if (flags.json) {\n console.log(JSON.stringify(results, null, 2));\n } else if (results.length === 0) {\n console.log(\"(no matches)\");\n } else {\n for (const r of results) {\n console.log(\n `\\n[${r.score.toFixed(3)}] ${r.scope}${r.source ? ` · ${r.source}` : \"\"}\\n${r.content}`,\n );\n }\n }\n break;\n }\n case \"delete\": {\n const n = await kb.delete({\n id: flags.id,\n source: flags.source,\n key: flags.key,\n projectId: flags.project,\n clientId: flags.client,\n scope: flags.scope,\n });\n console.log(`✓ deleted ${n} row(s)`);\n break;\n }\n default:\n console.log(`Unknown command: ${command}\\n${USAGE}`);\n process.exitCode = 1;\n }\n } finally {\n await kb.close();\n }\n}\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import pg from \"pg\";\nimport { resolveConfig, type KnowledgeConfig, type ResolvedConfig } from \"./config.js\";\nimport { Embedder, chunkText } from \"./embeddings.js\";\nimport { SCHEMA_SQL } from \"./schema.js\";\nimport type {\n AddInput,\n DeleteFilter,\n HistoryOptions,\n KnowledgeRecord,\n PutInput,\n Scope,\n SearchOptions,\n SearchResult,\n} from \"./types.js\";\n\nconst { Pool } = pg;\n\nexport class KnowledgeDB {\n private pool: pg.Pool;\n private embedder: Embedder;\n readonly config: ResolvedConfig;\n\n constructor(config: KnowledgeConfig = {}) {\n this.config = resolveConfig(config);\n this.pool = new Pool({ connectionString: this.config.connectionString });\n this.embedder = new Embedder(this.config);\n }\n\n /** Create/upgrade the extension, table, and indexes. Safe to call repeatedly. */\n async init(): Promise<void> {\n await this.pool.query(SCHEMA_SQL);\n }\n\n /**\n * Append free-form knowledge (no version identity). Use for notes that\n * accumulate. For facts that change over time (pricing, hours, contact), use\n * put() or upsertSource() so newer versions supersede older ones.\n */\n async add(input: AddInput): Promise<string[]> {\n const t = this.resolveTarget(input);\n return this.insertChunks(this.pool, t, input, null);\n }\n\n /**\n * Versioned write keyed by `key`. Any existing *active* rows with the same\n * key (+ scope/client/project) are stamped superseded_at = now() and kept as\n * history; the new content becomes the current version. So updated pricing\n * wins in search while the old value remains for audit / point-in-time.\n */\n async put(input: PutInput): Promise<string[]> {\n return this.versionedWrite(\"key\", input.key, input);\n }\n\n /**\n * Re-ingest a document by `source`, superseding (not deleting) the prior\n * active version for that source. Idempotent: re-running keeps history and\n * makes the latest content current.\n */\n async upsertSource(input: AddInput & { source: string }): Promise<string[]> {\n return this.versionedWrite(\"source\", input.source, input);\n }\n\n /** Semantic search. Returns only current versions unless includeHistory/asOf. */\n async search(query: string, opts: SearchOptions = {}): Promise<SearchResult[]> {\n const clientId = opts.clientId ?? this.config.clientId;\n const projectId = opts.projectId ?? this.config.projectId;\n const includeClient = opts.includeClientKnowledge ?? true;\n const includeGlobal = opts.includeGlobal ?? true;\n const limit = opts.limit ?? 8;\n const minScore = opts.minScore ?? 0;\n\n const queryVec = toVector(await this.embedder.embedOne(query));\n\n const params: unknown[] = [queryVec];\n const p = (v: unknown) => `$${params.push(v)}`;\n\n const orClauses: string[] = [];\n if (projectId && allows(opts.scopes, \"project\")) {\n orClauses.push(`(scope = 'project' and project_id = ${p(projectId)})`);\n }\n if (clientId && includeClient && allows(opts.scopes, \"client\")) {\n orClauses.push(`(scope = 'client' and client_id = ${p(clientId)})`);\n }\n if (includeGlobal && allows(opts.scopes, \"global\")) {\n orClauses.push(`scope = 'global'`);\n }\n const scopeClause = orClauses.length ? `(${orClauses.join(\" or \")})` : `scope = 'global'`;\n\n const where: string[] = [scopeClause];\n if (opts.metadata) {\n where.push(`metadata @> ${p(JSON.stringify(opts.metadata))}::jsonb`);\n }\n // Recency / history filtering.\n if (opts.asOf !== undefined) {\n const at = typeof opts.asOf === \"string\" ? opts.asOf : opts.asOf.toISOString();\n where.push(\n `created_at <= ${p(at)}::timestamptz and (superseded_at is null or superseded_at > ${p(at)}::timestamptz)`,\n );\n } else if (!opts.includeHistory) {\n where.push(`superseded_at is null`);\n }\n\n const { rows } = await this.pool.query(\n `select id, scope, client_id, project_id, key, source, content, metadata, created_at, superseded_at,\n 1 - (embedding <=> $1) as score\n from knowledge\n where ${where.join(\" and \")}\n order by embedding <=> $1\n limit ${p(limit)}`,\n params,\n );\n\n return rows.map(rowToResult).filter((r) => r.score >= minScore);\n }\n\n /**\n * Return all versions of a `key` (or all rows for a source via metadata),\n * newest first, including superseded ones. Useful for audit / \"what did we\n * say before\".\n */\n async history(key: string, opts: HistoryOptions = {}): Promise<KnowledgeRecord[]> {\n const clientId = opts.clientId ?? this.config.clientId ?? null;\n const projectId = opts.projectId ?? this.config.projectId ?? null;\n const scope = opts.scope ?? defaultScope(clientId, projectId);\n const includeHistory = opts.includeHistory ?? true;\n const where = [\n `key = $1`,\n `scope = $2`,\n `client_id is not distinct from $3`,\n `project_id is not distinct from $4`,\n ];\n if (!includeHistory) where.push(`superseded_at is null`);\n const { rows } = await this.pool.query(\n `select id, scope, client_id, project_id, key, source, content, metadata, created_at, superseded_at\n from knowledge\n where ${where.join(\" and \")}\n order by created_at desc`,\n [key, scope, clientId, projectId],\n );\n return rows.map(rowToRecord);\n }\n\n /** Hard-delete rows matching a filter (removes history too). Returns count. */\n async delete(filter: DeleteFilter): Promise<number> {\n const where: string[] = [];\n const params: unknown[] = [];\n const p = (v: unknown) => `$${params.push(v)}`;\n if (filter.id) where.push(`id = ${p(filter.id)}`);\n if (filter.scope) where.push(`scope = ${p(filter.scope)}`);\n if (filter.clientId) where.push(`client_id = ${p(filter.clientId)}`);\n if (filter.projectId) where.push(`project_id = ${p(filter.projectId)}`);\n if (filter.source) where.push(`source = ${p(filter.source)}`);\n if (filter.key) where.push(`key = ${p(filter.key)}`);\n if (where.length === 0) {\n throw new Error(\"[ai-knowledge-db] delete() requires at least one filter to avoid wiping the table.\");\n }\n const { rowCount } = await this.pool.query(\n `delete from knowledge where ${where.join(\" and \")}`,\n params,\n );\n return rowCount ?? 0;\n }\n\n /** Close the connection pool. Call on shutdown. */\n async close(): Promise<void> {\n await this.pool.end();\n }\n\n // --- internals ---------------------------------------------------------\n\n private resolveTarget(input: AddInput): {\n scope: Scope;\n clientId: string | null;\n projectId: string | null;\n source: string | null;\n } {\n const clientId = input.clientId ?? this.config.clientId ?? null;\n const projectId = input.projectId ?? this.config.projectId ?? null;\n return {\n clientId,\n projectId,\n scope: input.scope ?? defaultScope(clientId, projectId),\n source: input.source ?? null,\n };\n }\n\n /** Supersede prior active rows matching column=value, then insert new active rows. */\n private async versionedWrite(\n matchColumn: \"key\" | \"source\",\n matchValue: string,\n input: AddInput,\n ): Promise<string[]> {\n const t = this.resolveTarget(input);\n const key = matchColumn === \"key\" ? matchValue : null;\n const chunks = chunkText(input.content, input.chunking);\n if (chunks.length === 0) return [];\n const vectors = await this.embedder.embed(chunks);\n\n const client = await this.pool.connect();\n try {\n await client.query(\"begin\");\n await client.query(\n `update knowledge set superseded_at = now()\n where ${matchColumn} = $1 and scope = $2\n and client_id is not distinct from $3\n and project_id is not distinct from $4\n and superseded_at is null`,\n [matchValue, t.scope, t.clientId, t.projectId],\n );\n const ids = await this.insertChunksTx(client, t, input, key, chunks, vectors);\n await client.query(\"commit\");\n return ids;\n } catch (err) {\n await client.query(\"rollback\");\n throw err;\n } finally {\n client.release();\n }\n }\n\n /** Insert chunks on a fresh connection (embeds inside). */\n private async insertChunks(\n runner: pg.Pool,\n t: { scope: Scope; clientId: string | null; projectId: string | null; source: string | null },\n input: AddInput,\n key: string | null,\n ): Promise<string[]> {\n const chunks = chunkText(input.content, input.chunking);\n if (chunks.length === 0) return [];\n const vectors = await this.embedder.embed(chunks);\n const client = await runner.connect();\n try {\n await client.query(\"begin\");\n const ids = await this.insertChunksTx(client, t, input, key, chunks, vectors);\n await client.query(\"commit\");\n return ids;\n } catch (err) {\n await client.query(\"rollback\");\n throw err;\n } finally {\n client.release();\n }\n }\n\n /** Insert chunk rows on an existing transaction client. */\n private async insertChunksTx(\n client: pg.PoolClient,\n t: { scope: Scope; clientId: string | null; projectId: string | null; source: string | null },\n input: AddInput,\n key: string | null,\n chunks: string[],\n vectors: number[][],\n ): Promise<string[]> {\n const metadata = input.metadata ?? {};\n const ids: string[] = [];\n for (let i = 0; i < chunks.length; i++) {\n const chunkMeta =\n chunks.length > 1 ? { ...metadata, chunk: i, chunks: chunks.length } : metadata;\n const { rows } = await client.query(\n `insert into knowledge (scope, client_id, project_id, key, source, content, embedding, metadata)\n values ($1, $2, $3, $4, $5, $6, $7, $8) returning id`,\n [t.scope, t.clientId, t.projectId, key, t.source, chunks[i], toVector(vectors[i]), chunkMeta],\n );\n ids.push(rows[0].id);\n }\n return ids;\n }\n}\n\nexport function createKnowledgeDB(config?: KnowledgeConfig): KnowledgeDB {\n return new KnowledgeDB(config);\n}\n\nfunction defaultScope(clientId: string | null, projectId: string | null): Scope {\n if (projectId) return \"project\";\n if (clientId) return \"client\";\n return \"global\";\n}\n\nfunction allows(scopes: Scope[] | undefined, scope: Scope): boolean {\n return !scopes || scopes.includes(scope);\n}\n\n/** pgvector accepts a vector literal like '[0.1,0.2,...]'. */\nfunction toVector(vec: number[]): string {\n return `[${vec.join(\",\")}]`;\n}\n\nfunction rowToResult(row: any): SearchResult {\n return { ...rowToRecord(row), score: Number(row.score) };\n}\n\nfunction rowToRecord(row: any): KnowledgeRecord {\n return {\n id: row.id,\n scope: row.scope,\n clientId: row.client_id,\n projectId: row.project_id,\n key: row.key ?? null,\n source: row.source,\n content: row.content,\n metadata: row.metadata ?? {},\n createdAt: toIso(row.created_at),\n supersededAt: row.superseded_at ? toIso(row.superseded_at) : null,\n };\n}\n\nfunction toIso(v: unknown): string {\n return v instanceof Date ? v.toISOString() : String(v);\n}\n","/**\n * All sensitive / per-deployment values live in the *consuming* project's\n * environment — never in this package. A website repo that installs\n * `@dibe/ai-knowledge-db` sets these in its own `.env`:\n *\n * KNOWLEDGE_DB_URL=postgres://user:pass@host:5432/knowledge (Hetzner/EasyPanel)\n * OPENAI_API_KEY=sk-...\n * KNOWLEDGE_CLIENT_ID=acme-corp # default client for this repo\n * KNOWLEDGE_PROJECT_ID=acme-website-2026 # default project for this repo\n *\n * Anything passed explicitly to createKnowledgeDB() overrides the env value,\n * but env is the intended default so callers usually pass nothing.\n */\n\nexport interface KnowledgeConfig {\n /** Postgres connection string. Defaults to env KNOWLEDGE_DB_URL. */\n connectionString?: string;\n /** OpenAI API key. Defaults to env OPENAI_API_KEY. */\n openaiApiKey?: string;\n /** Embedding model. Defaults to env KNOWLEDGE_EMBED_MODEL or text-embedding-3-small. */\n embeddingModel?: string;\n /** Default client scope for this repo. Defaults to env KNOWLEDGE_CLIENT_ID. */\n clientId?: string;\n /** Default project scope for this repo. Defaults to env KNOWLEDGE_PROJECT_ID. */\n projectId?: string;\n}\n\nexport interface ResolvedConfig {\n connectionString: string;\n openaiApiKey: string;\n embeddingModel: string;\n embeddingDimensions: number;\n clientId?: string;\n projectId?: string;\n}\n\n/** text-embedding-3-small → 1536, text-embedding-3-large → 3072. */\nconst MODEL_DIMENSIONS: Record<string, number> = {\n \"text-embedding-3-small\": 1536,\n \"text-embedding-3-large\": 3072,\n \"text-embedding-ada-002\": 1536,\n};\n\nconst env = (key: string): string | undefined => {\n const v = process.env[key];\n return v && v.trim() !== \"\" ? v.trim() : undefined;\n};\n\nexport function resolveConfig(config: KnowledgeConfig = {}): ResolvedConfig {\n const connectionString = config.connectionString ?? env(\"KNOWLEDGE_DB_URL\");\n if (!connectionString) {\n throw new Error(\n \"[ai-knowledge-db] Missing connection string. Set KNOWLEDGE_DB_URL in your project's .env \" +\n \"or pass { connectionString } to createKnowledgeDB().\",\n );\n }\n\n const openaiApiKey = config.openaiApiKey ?? env(\"OPENAI_API_KEY\");\n if (!openaiApiKey) {\n throw new Error(\n \"[ai-knowledge-db] Missing OpenAI key. Set OPENAI_API_KEY in your project's .env \" +\n \"or pass { openaiApiKey } to createKnowledgeDB().\",\n );\n }\n\n const embeddingModel =\n config.embeddingModel ?? env(\"KNOWLEDGE_EMBED_MODEL\") ?? \"text-embedding-3-small\";\n const embeddingDimensions = MODEL_DIMENSIONS[embeddingModel] ?? 1536;\n\n return {\n connectionString,\n openaiApiKey,\n embeddingModel,\n embeddingDimensions,\n clientId: config.clientId ?? env(\"KNOWLEDGE_CLIENT_ID\"),\n projectId: config.projectId ?? env(\"KNOWLEDGE_PROJECT_ID\"),\n };\n}\n","import OpenAI from \"openai\";\nimport type { ResolvedConfig } from \"./config.js\";\nimport type { ChunkOptions } from \"./types.js\";\n\nexport class Embedder {\n private client: OpenAI;\n private model: string;\n\n constructor(config: ResolvedConfig) {\n this.client = new OpenAI({ apiKey: config.openaiApiKey });\n this.model = config.embeddingModel;\n }\n\n /** Embed a batch of strings in one API call. */\n async embed(texts: string[]): Promise<number[][]> {\n if (texts.length === 0) return [];\n const res = await this.client.embeddings.create({\n model: this.model,\n input: texts,\n });\n // OpenAI preserves input order in the response.\n return res.data\n .sort((a, b) => a.index - b.index)\n .map((d) => d.embedding as number[]);\n }\n\n async embedOne(text: string): Promise<number[]> {\n const [vec] = await this.embed([text]);\n return vec;\n }\n}\n\n/**\n * Split text into overlapping chunks. Prefers paragraph boundaries, then\n * sentence boundaries, falling back to hard character cuts for very long runs.\n */\nexport function chunkText(text: string, opts: ChunkOptions = {}): string[] {\n const maxChars = opts.maxChars ?? 1200;\n const overlap = opts.overlap ?? 150;\n const clean = text.replace(/\\r\\n/g, \"\\n\").trim();\n if (clean.length <= maxChars) return clean ? [clean] : [];\n\n // Split into paragraph-ish units first.\n const units = clean.split(/\\n{2,}/).flatMap((p) => splitLongUnit(p, maxChars));\n\n const chunks: string[] = [];\n let current = \"\";\n for (const unit of units) {\n if (current && current.length + unit.length + 2 > maxChars) {\n chunks.push(current.trim());\n // carry overlap from the tail of the previous chunk\n current = overlap > 0 ? current.slice(-overlap) + \"\\n\\n\" + unit : unit;\n } else {\n current = current ? current + \"\\n\\n\" + unit : unit;\n }\n }\n if (current.trim()) chunks.push(current.trim());\n return chunks;\n}\n\n/** Break a single oversized paragraph on sentence, then hard, boundaries. */\nfunction splitLongUnit(unit: string, maxChars: number): string[] {\n if (unit.length <= maxChars) return [unit];\n const sentences = unit.match(/[^.!?\\n]+[.!?]?\\s*/g) ?? [unit];\n const out: string[] = [];\n let buf = \"\";\n for (const s of sentences) {\n if (s.length > maxChars) {\n if (buf) {\n out.push(buf);\n buf = \"\";\n }\n for (let i = 0; i < s.length; i += maxChars) out.push(s.slice(i, i + maxChars));\n } else if (buf.length + s.length > maxChars) {\n out.push(buf);\n buf = s;\n } else {\n buf += s;\n }\n }\n if (buf) out.push(buf);\n return out;\n}\n","/**\n * Canonical schema (Postgres + pgvector), kept as a string so the library never\n * has to read from disk — works identically in the ESM and CJS builds. It is\n * idempotent AND upgrade-safe: running it via kb.init() on a fresh database\n * creates everything, and on an existing v0.1 database it adds the v0.2 history\n * columns in place. Vector size matches text-embedding-3-small (1536).\n *\n * Versioning model (v0.2): a row's lifetime is [created_at, superseded_at).\n * superseded_at IS NULL => the row is the current/active version.\n * Writing a new version of the same `key` (or `source`) stamps the old rows\n * with superseded_at = now() instead of deleting them, so history is retained\n * and searches return only the newest version by default.\n */\nexport const SCHEMA_SQL = `-- AI Knowledge DB schema (Postgres + pgvector)\n-- Run once against your Hetzner/EasyPanel Postgres instance, or via kb.init().\n-- If you switch to text-embedding-3-large, change 1536 -> 3072 and re-index.\n\ncreate extension if not exists vector;\ncreate extension if not exists \"pgcrypto\"; -- for gen_random_uuid()\n\ncreate table if not exists knowledge (\n id uuid primary key default gen_random_uuid(),\n scope text not null check (scope in ('global', 'client', 'project')),\n client_id text,\n project_id text,\n key text, -- stable identity for a versioned fact (e.g. 'pricing.basic-plan')\n source text,\n content text not null,\n embedding vector(1536) not null,\n metadata jsonb not null default '{}',\n created_at timestamptz not null default now(), -- valid from\n superseded_at timestamptz -- valid to; null = current\n);\n\n-- Upgrade existing (v0.1) installs in place.\nalter table knowledge add column if not exists key text;\nalter table knowledge add column if not exists superseded_at timestamptz;\n\ncreate index if not exists knowledge_client_idx on knowledge (client_id);\ncreate index if not exists knowledge_project_idx on knowledge (project_id);\ncreate index if not exists knowledge_scope_idx on knowledge (scope);\ncreate index if not exists knowledge_key_idx on knowledge (key);\ncreate index if not exists knowledge_active_idx on knowledge (superseded_at);\ncreate index if not exists knowledge_metadata_idx on knowledge using gin (metadata);\n\ncreate index if not exists knowledge_embedding_idx\n on knowledge using hnsw (embedding vector_cosine_ops);\n`;\n"],"mappings":"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","names":["OpenAI","pg"]}