@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/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)` |
|
|
84
|
-
| `kb.
|
|
85
|
-
| `kb.
|
|
86
|
-
| `kb.
|
|
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
|
|
142
|
-
scope
|
|
143
|
-
client_id
|
|
144
|
-
project_id
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
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
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
322
|
-
|
|
323
|
-
|
|
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 =
|
|
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":"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","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"]}
|