@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 +26 -5
- package/dist/cli.cjs +199 -60
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +199 -60
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +153 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -10
- package/dist/index.d.ts +56 -10
- package/dist/index.js +153 -55
- package/dist/index.js.map +1 -1
- package/migrations/001_init_knowledge.sql +23 -17
- package/migrations/002_add_history.sql +13 -0
- package/package.json +1 -1
- package/skills/knowledge-db/SKILL.md +32 -6
package/dist/cli.js
CHANGED
|
@@ -115,20 +115,28 @@ create extension if not exists vector;
|
|
|
115
115
|
create extension if not exists "pgcrypto"; -- for gen_random_uuid()
|
|
116
116
|
|
|
117
117
|
create table if not exists knowledge (
|
|
118
|
-
id
|
|
119
|
-
scope
|
|
120
|
-
client_id
|
|
121
|
-
project_id
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
id uuid primary key default gen_random_uuid(),
|
|
119
|
+
scope text not null check (scope in ('global', 'client', 'project')),
|
|
120
|
+
client_id text,
|
|
121
|
+
project_id text,
|
|
122
|
+
key text, -- stable identity for a versioned fact (e.g. 'pricing.basic-plan')
|
|
123
|
+
source text,
|
|
124
|
+
content text not null,
|
|
125
|
+
embedding vector(1536) not null,
|
|
126
|
+
metadata jsonb not null default '{}',
|
|
127
|
+
created_at timestamptz not null default now(), -- valid from
|
|
128
|
+
superseded_at timestamptz -- valid to; null = current
|
|
127
129
|
);
|
|
128
130
|
|
|
131
|
+
-- Upgrade existing (v0.1) installs in place.
|
|
132
|
+
alter table knowledge add column if not exists key text;
|
|
133
|
+
alter table knowledge add column if not exists superseded_at timestamptz;
|
|
134
|
+
|
|
129
135
|
create index if not exists knowledge_client_idx on knowledge (client_id);
|
|
130
136
|
create index if not exists knowledge_project_idx on knowledge (project_id);
|
|
131
137
|
create index if not exists knowledge_scope_idx on knowledge (scope);
|
|
138
|
+
create index if not exists knowledge_key_idx on knowledge (key);
|
|
139
|
+
create index if not exists knowledge_active_idx on knowledge (superseded_at);
|
|
132
140
|
create index if not exists knowledge_metadata_idx on knowledge using gin (metadata);
|
|
133
141
|
|
|
134
142
|
create index if not exists knowledge_embedding_idx
|
|
@@ -146,59 +154,37 @@ var KnowledgeDB = class {
|
|
|
146
154
|
this.pool = new Pool({ connectionString: this.config.connectionString });
|
|
147
155
|
this.embedder = new Embedder(this.config);
|
|
148
156
|
}
|
|
149
|
-
/** Create the extension, table, and indexes
|
|
157
|
+
/** Create/upgrade the extension, table, and indexes. Safe to call repeatedly. */
|
|
150
158
|
async init() {
|
|
151
159
|
await this.pool.query(SCHEMA_SQL);
|
|
152
160
|
}
|
|
153
161
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
162
|
+
* Append free-form knowledge (no version identity). Use for notes that
|
|
163
|
+
* accumulate. For facts that change over time (pricing, hours, contact), use
|
|
164
|
+
* put() or upsertSource() so newer versions supersede older ones.
|
|
157
165
|
*/
|
|
158
166
|
async add(input) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
await client.query("begin");
|
|
171
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
172
|
-
const chunkMeta = chunks.length > 1 ? { ...metadata, chunk: i, chunks: chunks.length } : metadata;
|
|
173
|
-
const { rows } = await client.query(
|
|
174
|
-
`insert into knowledge (scope, client_id, project_id, source, content, embedding, metadata)
|
|
175
|
-
values ($1, $2, $3, $4, $5, $6, $7) returning id`,
|
|
176
|
-
[scope, clientId, projectId, source, chunks[i], toVector(vectors[i]), chunkMeta]
|
|
177
|
-
);
|
|
178
|
-
ids.push(rows[0].id);
|
|
179
|
-
}
|
|
180
|
-
await client.query("commit");
|
|
181
|
-
} catch (err) {
|
|
182
|
-
await client.query("rollback");
|
|
183
|
-
throw err;
|
|
184
|
-
} finally {
|
|
185
|
-
client.release();
|
|
186
|
-
}
|
|
187
|
-
return ids;
|
|
167
|
+
const t = this.resolveTarget(input);
|
|
168
|
+
return this.insertChunks(this.pool, t, input, null);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Versioned write keyed by `key`. Any existing *active* rows with the same
|
|
172
|
+
* key (+ scope/client/project) are stamped superseded_at = now() and kept as
|
|
173
|
+
* history; the new content becomes the current version. So updated pricing
|
|
174
|
+
* wins in search while the old value remains for audit / point-in-time.
|
|
175
|
+
*/
|
|
176
|
+
async put(input) {
|
|
177
|
+
return this.versionedWrite("key", input.key, input);
|
|
188
178
|
}
|
|
189
179
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
180
|
+
* Re-ingest a document by `source`, superseding (not deleting) the prior
|
|
181
|
+
* active version for that source. Idempotent: re-running keeps history and
|
|
182
|
+
* makes the latest content current.
|
|
193
183
|
*/
|
|
194
184
|
async upsertSource(input) {
|
|
195
|
-
|
|
196
|
-
const projectId = input.projectId ?? this.config.projectId ?? null;
|
|
197
|
-
const scope = input.scope ?? defaultScope(clientId, projectId);
|
|
198
|
-
await this.delete({ scope, clientId: clientId ?? void 0, projectId: projectId ?? void 0, source: input.source });
|
|
199
|
-
return this.add(input);
|
|
185
|
+
return this.versionedWrite("source", input.source, input);
|
|
200
186
|
}
|
|
201
|
-
/** Semantic search
|
|
187
|
+
/** Semantic search. Returns only current versions unless includeHistory/asOf. */
|
|
202
188
|
async search(query, opts = {}) {
|
|
203
189
|
const clientId = opts.clientId ?? this.config.clientId;
|
|
204
190
|
const projectId = opts.projectId ?? this.config.projectId;
|
|
@@ -207,9 +193,9 @@ var KnowledgeDB = class {
|
|
|
207
193
|
const limit = opts.limit ?? 8;
|
|
208
194
|
const minScore = opts.minScore ?? 0;
|
|
209
195
|
const queryVec = toVector(await this.embedder.embedOne(query));
|
|
210
|
-
const orClauses = [];
|
|
211
196
|
const params = [queryVec];
|
|
212
197
|
const p = (v) => `$${params.push(v)}`;
|
|
198
|
+
const orClauses = [];
|
|
213
199
|
if (projectId && allows(opts.scopes, "project")) {
|
|
214
200
|
orClauses.push(`(scope = 'project' and project_id = ${p(projectId)})`);
|
|
215
201
|
}
|
|
@@ -224,8 +210,16 @@ var KnowledgeDB = class {
|
|
|
224
210
|
if (opts.metadata) {
|
|
225
211
|
where.push(`metadata @> ${p(JSON.stringify(opts.metadata))}::jsonb`);
|
|
226
212
|
}
|
|
213
|
+
if (opts.asOf !== void 0) {
|
|
214
|
+
const at = typeof opts.asOf === "string" ? opts.asOf : opts.asOf.toISOString();
|
|
215
|
+
where.push(
|
|
216
|
+
`created_at <= ${p(at)}::timestamptz and (superseded_at is null or superseded_at > ${p(at)}::timestamptz)`
|
|
217
|
+
);
|
|
218
|
+
} else if (!opts.includeHistory) {
|
|
219
|
+
where.push(`superseded_at is null`);
|
|
220
|
+
}
|
|
227
221
|
const { rows } = await this.pool.query(
|
|
228
|
-
`select id, scope, client_id, project_id, source, content, metadata, created_at,
|
|
222
|
+
`select id, scope, client_id, project_id, key, source, content, metadata, created_at, superseded_at,
|
|
229
223
|
1 - (embedding <=> $1) as score
|
|
230
224
|
from knowledge
|
|
231
225
|
where ${where.join(" and ")}
|
|
@@ -235,7 +229,33 @@ var KnowledgeDB = class {
|
|
|
235
229
|
);
|
|
236
230
|
return rows.map(rowToResult).filter((r) => r.score >= minScore);
|
|
237
231
|
}
|
|
238
|
-
/**
|
|
232
|
+
/**
|
|
233
|
+
* Return all versions of a `key` (or all rows for a source via metadata),
|
|
234
|
+
* newest first, including superseded ones. Useful for audit / "what did we
|
|
235
|
+
* say before".
|
|
236
|
+
*/
|
|
237
|
+
async history(key, opts = {}) {
|
|
238
|
+
const clientId = opts.clientId ?? this.config.clientId ?? null;
|
|
239
|
+
const projectId = opts.projectId ?? this.config.projectId ?? null;
|
|
240
|
+
const scope = opts.scope ?? defaultScope(clientId, projectId);
|
|
241
|
+
const includeHistory = opts.includeHistory ?? true;
|
|
242
|
+
const where = [
|
|
243
|
+
`key = $1`,
|
|
244
|
+
`scope = $2`,
|
|
245
|
+
`client_id is not distinct from $3`,
|
|
246
|
+
`project_id is not distinct from $4`
|
|
247
|
+
];
|
|
248
|
+
if (!includeHistory) where.push(`superseded_at is null`);
|
|
249
|
+
const { rows } = await this.pool.query(
|
|
250
|
+
`select id, scope, client_id, project_id, key, source, content, metadata, created_at, superseded_at
|
|
251
|
+
from knowledge
|
|
252
|
+
where ${where.join(" and ")}
|
|
253
|
+
order by created_at desc`,
|
|
254
|
+
[key, scope, clientId, projectId]
|
|
255
|
+
);
|
|
256
|
+
return rows.map(rowToRecord);
|
|
257
|
+
}
|
|
258
|
+
/** Hard-delete rows matching a filter (removes history too). Returns count. */
|
|
239
259
|
async delete(filter) {
|
|
240
260
|
const where = [];
|
|
241
261
|
const params = [];
|
|
@@ -245,6 +265,7 @@ var KnowledgeDB = class {
|
|
|
245
265
|
if (filter.clientId) where.push(`client_id = ${p(filter.clientId)}`);
|
|
246
266
|
if (filter.projectId) where.push(`project_id = ${p(filter.projectId)}`);
|
|
247
267
|
if (filter.source) where.push(`source = ${p(filter.source)}`);
|
|
268
|
+
if (filter.key) where.push(`key = ${p(filter.key)}`);
|
|
248
269
|
if (where.length === 0) {
|
|
249
270
|
throw new Error("[ai-knowledge-db] delete() requires at least one filter to avoid wiping the table.");
|
|
250
271
|
}
|
|
@@ -258,6 +279,78 @@ var KnowledgeDB = class {
|
|
|
258
279
|
async close() {
|
|
259
280
|
await this.pool.end();
|
|
260
281
|
}
|
|
282
|
+
// --- internals ---------------------------------------------------------
|
|
283
|
+
resolveTarget(input) {
|
|
284
|
+
const clientId = input.clientId ?? this.config.clientId ?? null;
|
|
285
|
+
const projectId = input.projectId ?? this.config.projectId ?? null;
|
|
286
|
+
return {
|
|
287
|
+
clientId,
|
|
288
|
+
projectId,
|
|
289
|
+
scope: input.scope ?? defaultScope(clientId, projectId),
|
|
290
|
+
source: input.source ?? null
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/** Supersede prior active rows matching column=value, then insert new active rows. */
|
|
294
|
+
async versionedWrite(matchColumn, matchValue, input) {
|
|
295
|
+
const t = this.resolveTarget(input);
|
|
296
|
+
const key = matchColumn === "key" ? matchValue : null;
|
|
297
|
+
const chunks = chunkText(input.content, input.chunking);
|
|
298
|
+
if (chunks.length === 0) return [];
|
|
299
|
+
const vectors = await this.embedder.embed(chunks);
|
|
300
|
+
const client = await this.pool.connect();
|
|
301
|
+
try {
|
|
302
|
+
await client.query("begin");
|
|
303
|
+
await client.query(
|
|
304
|
+
`update knowledge set superseded_at = now()
|
|
305
|
+
where ${matchColumn} = $1 and scope = $2
|
|
306
|
+
and client_id is not distinct from $3
|
|
307
|
+
and project_id is not distinct from $4
|
|
308
|
+
and superseded_at is null`,
|
|
309
|
+
[matchValue, t.scope, t.clientId, t.projectId]
|
|
310
|
+
);
|
|
311
|
+
const ids = await this.insertChunksTx(client, t, input, key, chunks, vectors);
|
|
312
|
+
await client.query("commit");
|
|
313
|
+
return ids;
|
|
314
|
+
} catch (err) {
|
|
315
|
+
await client.query("rollback");
|
|
316
|
+
throw err;
|
|
317
|
+
} finally {
|
|
318
|
+
client.release();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** Insert chunks on a fresh connection (embeds inside). */
|
|
322
|
+
async insertChunks(runner, t, input, key) {
|
|
323
|
+
const chunks = chunkText(input.content, input.chunking);
|
|
324
|
+
if (chunks.length === 0) return [];
|
|
325
|
+
const vectors = await this.embedder.embed(chunks);
|
|
326
|
+
const client = await runner.connect();
|
|
327
|
+
try {
|
|
328
|
+
await client.query("begin");
|
|
329
|
+
const ids = await this.insertChunksTx(client, t, input, key, chunks, vectors);
|
|
330
|
+
await client.query("commit");
|
|
331
|
+
return ids;
|
|
332
|
+
} catch (err) {
|
|
333
|
+
await client.query("rollback");
|
|
334
|
+
throw err;
|
|
335
|
+
} finally {
|
|
336
|
+
client.release();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/** Insert chunk rows on an existing transaction client. */
|
|
340
|
+
async insertChunksTx(client, t, input, key, chunks, vectors) {
|
|
341
|
+
const metadata = input.metadata ?? {};
|
|
342
|
+
const ids = [];
|
|
343
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
344
|
+
const chunkMeta = chunks.length > 1 ? { ...metadata, chunk: i, chunks: chunks.length } : metadata;
|
|
345
|
+
const { rows } = await client.query(
|
|
346
|
+
`insert into knowledge (scope, client_id, project_id, key, source, content, embedding, metadata)
|
|
347
|
+
values ($1, $2, $3, $4, $5, $6, $7, $8) returning id`,
|
|
348
|
+
[t.scope, t.clientId, t.projectId, key, t.source, chunks[i], toVector(vectors[i]), chunkMeta]
|
|
349
|
+
);
|
|
350
|
+
ids.push(rows[0].id);
|
|
351
|
+
}
|
|
352
|
+
return ids;
|
|
353
|
+
}
|
|
261
354
|
};
|
|
262
355
|
function createKnowledgeDB(config) {
|
|
263
356
|
return new KnowledgeDB(config);
|
|
@@ -282,26 +375,34 @@ function rowToRecord(row) {
|
|
|
282
375
|
scope: row.scope,
|
|
283
376
|
clientId: row.client_id,
|
|
284
377
|
projectId: row.project_id,
|
|
378
|
+
key: row.key ?? null,
|
|
285
379
|
source: row.source,
|
|
286
380
|
content: row.content,
|
|
287
381
|
metadata: row.metadata ?? {},
|
|
288
|
-
createdAt: row.created_at
|
|
382
|
+
createdAt: toIso(row.created_at),
|
|
383
|
+
supersededAt: row.superseded_at ? toIso(row.superseded_at) : null
|
|
289
384
|
};
|
|
290
385
|
}
|
|
386
|
+
function toIso(v) {
|
|
387
|
+
return v instanceof Date ? v.toISOString() : String(v);
|
|
388
|
+
}
|
|
291
389
|
|
|
292
390
|
// src/cli.ts
|
|
391
|
+
var BOOL_FLAGS = /* @__PURE__ */ new Set(["json", "history"]);
|
|
293
392
|
function parse(argv) {
|
|
294
393
|
const positional = [];
|
|
295
394
|
const flags = {};
|
|
296
395
|
for (let i = 0; i < argv.length; i++) {
|
|
297
396
|
const a = argv[i];
|
|
298
|
-
if (a
|
|
299
|
-
|
|
300
|
-
|
|
397
|
+
if (a.startsWith("--")) {
|
|
398
|
+
const name = a.slice(2);
|
|
399
|
+
if (BOOL_FLAGS.has(name)) flags[name] = true;
|
|
400
|
+
else flags[name] = argv[++i];
|
|
401
|
+
} else positional.push(a);
|
|
301
402
|
}
|
|
302
403
|
return { positional, flags };
|
|
303
404
|
}
|
|
304
|
-
var USAGE =
|
|
405
|
+
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';
|
|
305
406
|
async function main() {
|
|
306
407
|
const [command, ...rest] = process.argv.slice(2);
|
|
307
408
|
if (!command || command === "help" || command === "-h" || command === "--help") {
|
|
@@ -331,6 +432,41 @@ async function main() {
|
|
|
331
432
|
console.log(`\u2713 stored ${ids.length} chunk(s)`);
|
|
332
433
|
break;
|
|
333
434
|
}
|
|
435
|
+
case "put": {
|
|
436
|
+
if (!flags.key) throw new Error("put requires --key (e.g. --key pricing.basic-plan)");
|
|
437
|
+
const ids = await kb.put({
|
|
438
|
+
key: flags.key,
|
|
439
|
+
content: positional.join(" "),
|
|
440
|
+
scope: flags.scope,
|
|
441
|
+
clientId: flags.client,
|
|
442
|
+
projectId: flags.project,
|
|
443
|
+
source: flags.source,
|
|
444
|
+
metadata: meta
|
|
445
|
+
});
|
|
446
|
+
console.log(`\u2713 '${flags.key}' updated \u2192 ${ids.length} chunk(s) now current (old kept as history)`);
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
case "history": {
|
|
450
|
+
if (!flags.key) throw new Error("history requires --key");
|
|
451
|
+
const rows = await kb.history(flags.key, {
|
|
452
|
+
scope: flags.scope,
|
|
453
|
+
clientId: flags.client,
|
|
454
|
+
projectId: flags.project
|
|
455
|
+
});
|
|
456
|
+
if (flags.json) {
|
|
457
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
458
|
+
} else if (rows.length === 0) {
|
|
459
|
+
console.log("(no versions)");
|
|
460
|
+
} else {
|
|
461
|
+
for (const r of rows) {
|
|
462
|
+
const status = r.supersededAt ? `superseded ${r.supersededAt}` : "CURRENT";
|
|
463
|
+
console.log(`
|
|
464
|
+
[${r.createdAt}] (${status})
|
|
465
|
+
${r.content}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
334
470
|
case "add-file": {
|
|
335
471
|
const path = positional[0];
|
|
336
472
|
const content = readFileSync(path, "utf8");
|
|
@@ -349,7 +485,9 @@ async function main() {
|
|
|
349
485
|
const results = await kb.search(positional.join(" "), {
|
|
350
486
|
clientId: flags.client,
|
|
351
487
|
projectId: flags.project,
|
|
352
|
-
limit: flags.limit ? Number(flags.limit) : void 0
|
|
488
|
+
limit: flags.limit ? Number(flags.limit) : void 0,
|
|
489
|
+
includeHistory: flags.history,
|
|
490
|
+
asOf: flags["as-of"]
|
|
353
491
|
});
|
|
354
492
|
if (flags.json) {
|
|
355
493
|
console.log(JSON.stringify(results, null, 2));
|
|
@@ -370,6 +508,7 @@ ${r.content}`
|
|
|
370
508
|
const n = await kb.delete({
|
|
371
509
|
id: flags.id,
|
|
372
510
|
source: flags.source,
|
|
511
|
+
key: flags.key,
|
|
373
512
|
projectId: flags.project,
|
|
374
513
|
clientId: flags.client,
|
|
375
514
|
scope: flags.scope
|
package/dist/cli.js.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":"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","names":[]}
|
|
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":";;;AACA,SAAS,oBAAoB;;;ACD7B,OAAO,QAAQ;;;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,OAAO,YAAY;AAIZ,IAAM,WAAN,MAAe;AAAA,EACZ;AAAA,EACA;AAAA,EAER,YAAY,QAAwB;AAClC,SAAK,SAAS,IAAI,OAAO,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;;;ACrEO,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AHE1B,IAAM,EAAE,KAAK,IAAI;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,IAAI,KAAK,cAAc,KAAK;AAClC,WAAO,KAAK,aAAa,KAAK,MAAM,GAAG,OAAO,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,IAAI,OAAoC;AAC5C,WAAO,KAAK,eAAe,OAAO,MAAM,KAAK,KAAK;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,OAAyD;AAC1E,WAAO,KAAK,eAAe,UAAU,MAAM,QAAQ,KAAK;AAAA,EAC1D;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;AAE7D,UAAM,SAAoB,CAAC,QAAQ;AACnC,UAAM,IAAI,CAAC,MAAe,IAAI,OAAO,KAAK,CAAC,CAAC;AAE5C,UAAM,YAAsB,CAAC;AAC7B,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;AACA,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,QAAI,KAAK,SAAS,QAAW;AAC3B,YAAM,KAAK,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,KAAK,KAAK,YAAY;AAC7E,YAAM;AAAA,QACJ,iBAAiB,EAAE,EAAE,CAAC,+DAA+D,EAAE,EAAE,CAAC;AAAA,MAC5F;AAAA,IACF,WAAW,CAAC,KAAK,gBAAgB;AAC/B,YAAM,KAAK,uBAAuB;AAAA,IACpC;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,KAAK,IAAI,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,KAAa,OAAuB,CAAC,GAA+B;AAChF,UAAM,WAAW,KAAK,YAAY,KAAK,OAAO,YAAY;AAC1D,UAAM,YAAY,KAAK,aAAa,KAAK,OAAO,aAAa;AAC7D,UAAM,QAAQ,KAAK,SAAS,aAAa,UAAU,SAAS;AAC5D,UAAM,iBAAiB,KAAK,kBAAkB;AAC9C,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,eAAgB,OAAM,KAAK,uBAAuB;AACvD,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,KAAK;AAAA,MAC/B;AAAA;AAAA,eAES,MAAM,KAAK,OAAO,CAAC;AAAA;AAAA,MAE5B,CAAC,KAAK,OAAO,UAAU,SAAS;AAAA,IAClC;AACA,WAAO,KAAK,IAAI,WAAW;AAAA,EAC7B;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,OAAO,IAAK,OAAM,KAAK,SAAS,EAAE,OAAO,GAAG,CAAC,EAAE;AACnD,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;AAAA;AAAA,EAIQ,cAAc,OAKpB;AACA,UAAM,WAAW,MAAM,YAAY,KAAK,OAAO,YAAY;AAC3D,UAAM,YAAY,MAAM,aAAa,KAAK,OAAO,aAAa;AAC9D,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,MAAM,SAAS,aAAa,UAAU,SAAS;AAAA,MACtD,QAAQ,MAAM,UAAU;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,eACZ,aACA,YACA,OACmB;AACnB,UAAM,IAAI,KAAK,cAAc,KAAK;AAClC,UAAM,MAAM,gBAAgB,QAAQ,aAAa;AACjD,UAAM,SAAS,UAAU,MAAM,SAAS,MAAM,QAAQ;AACtD,QAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,UAAM,UAAU,MAAM,KAAK,SAAS,MAAM,MAAM;AAEhD,UAAM,SAAS,MAAM,KAAK,KAAK,QAAQ;AACvC,QAAI;AACF,YAAM,OAAO,MAAM,OAAO;AAC1B,YAAM,OAAO;AAAA,QACX;AAAA,iBACS,WAAW;AAAA;AAAA;AAAA;AAAA,QAIpB,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS;AAAA,MAC/C;AACA,YAAM,MAAM,MAAM,KAAK,eAAe,QAAQ,GAAG,OAAO,KAAK,QAAQ,OAAO;AAC5E,YAAM,OAAO,MAAM,QAAQ;AAC3B,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,OAAO,MAAM,UAAU;AAC7B,YAAM;AAAA,IACR,UAAE;AACA,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,aACZ,QACA,GACA,OACA,KACmB;AACnB,UAAM,SAAS,UAAU,MAAM,SAAS,MAAM,QAAQ;AACtD,QAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,UAAM,UAAU,MAAM,KAAK,SAAS,MAAM,MAAM;AAChD,UAAM,SAAS,MAAM,OAAO,QAAQ;AACpC,QAAI;AACF,YAAM,OAAO,MAAM,OAAO;AAC1B,YAAM,MAAM,MAAM,KAAK,eAAe,QAAQ,GAAG,OAAO,KAAK,QAAQ,OAAO;AAC5E,YAAM,OAAO,MAAM,QAAQ;AAC3B,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,OAAO,MAAM,UAAU;AAC7B,YAAM;AAAA,IACR,UAAE;AACA,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,eACZ,QACA,GACA,OACA,KACA,QACA,SACmB;AACnB,UAAM,WAAW,MAAM,YAAY,CAAC;AACpC,UAAM,MAAgB,CAAC;AACvB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,YACJ,OAAO,SAAS,IAAI,EAAE,GAAG,UAAU,OAAO,GAAG,QAAQ,OAAO,OAAO,IAAI;AACzE,YAAM,EAAE,KAAK,IAAI,MAAM,OAAO;AAAA,QAC5B;AAAA;AAAA,QAEA,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,KAAK,EAAE,QAAQ,OAAO,CAAC,GAAG,SAAS,QAAQ,CAAC,CAAC,GAAG,SAAS;AAAA,MAC9F;AACA,UAAI,KAAK,KAAK,CAAC,EAAE,EAAE;AAAA,IACrB;AACA,WAAO;AAAA,EACT;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,KAAK,IAAI,OAAO;AAAA,IAChB,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI;AAAA,IACb,UAAU,IAAI,YAAY,CAAC;AAAA,IAC3B,WAAW,MAAM,IAAI,UAAU;AAAA,IAC/B,cAAc,IAAI,gBAAgB,MAAM,IAAI,aAAa,IAAI;AAAA,EAC/D;AACF;AAEA,SAAS,MAAM,GAAoB;AACjC,SAAO,aAAa,OAAO,EAAE,YAAY,IAAI,OAAO,CAAC;AACvD;;;ADtRA,IAAM,aAAa,oBAAI,IAAI,CAAC,QAAQ,SAAS,CAAC;AAE9C,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,EAAE,WAAW,IAAI,GAAG;AACtB,YAAM,OAAO,EAAE,MAAM,CAAC;AACtB,UAAI,WAAW,IAAI,IAAI,EAAG,CAAC,MAAM,IAAI,IAAY;AAAA,UAC5C,CAAC,MAAM,IAAI,IAAY,KAAK,EAAE,CAAC;AAAA,IACtC,MAAO,YAAW,KAAK,CAAC;AAAA,EAC1B;AACA,SAAO,EAAE,YAAY,MAAM;AAC7B;AAEA,IAAM,QACJ;AAMF,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,OAAO;AACV,YAAI,CAAC,MAAM,IAAK,OAAM,IAAI,MAAM,oDAAoD;AACpF,cAAM,MAAM,MAAM,GAAG,IAAI;AAAA,UACvB,KAAK,MAAM;AAAA,UACX,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,WAAM,MAAM,GAAG,oBAAe,IAAI,MAAM,6CAA6C;AACjG;AAAA,MACF;AAAA,MACA,KAAK,WAAW;AACd,YAAI,CAAC,MAAM,IAAK,OAAM,IAAI,MAAM,wBAAwB;AACxD,cAAM,OAAO,MAAM,GAAG,QAAQ,MAAM,KAAK;AAAA,UACvC,OAAO,MAAM;AAAA,UACb,UAAU,MAAM;AAAA,UAChB,WAAW,MAAM;AAAA,QACnB,CAAC;AACD,YAAI,MAAM,MAAM;AACd,kBAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,QAC3C,WAAW,KAAK,WAAW,GAAG;AAC5B,kBAAQ,IAAI,eAAe;AAAA,QAC7B,OAAO;AACL,qBAAW,KAAK,MAAM;AACpB,kBAAM,SAAS,EAAE,eAAe,cAAc,EAAE,YAAY,KAAK;AACjE,oBAAQ,IAAI;AAAA,GAAM,EAAE,SAAS,MAAM,MAAM;AAAA,EAAM,EAAE,OAAO,EAAE;AAAA,UAC5D;AAAA,QACF;AACA;AAAA,MACF;AAAA,MACA,KAAK,YAAY;AACf,cAAM,OAAO,WAAW,CAAC;AACzB,cAAM,UAAU,aAAa,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,UAC3C,gBAAgB,MAAM;AAAA,UACtB,MAAM,MAAM,OAAO;AAAA,QACrB,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,KAAK,MAAM;AAAA,UACX,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":[]}
|