@digitalvibes/ai-knowledge-db 0.1.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/dist/cli.js ADDED
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { readFileSync } from "fs";
5
+
6
+ // src/client.ts
7
+ import pg from "pg";
8
+
9
+ // src/config.ts
10
+ var MODEL_DIMENSIONS = {
11
+ "text-embedding-3-small": 1536,
12
+ "text-embedding-3-large": 3072,
13
+ "text-embedding-ada-002": 1536
14
+ };
15
+ var env = (key) => {
16
+ const v = process.env[key];
17
+ return v && v.trim() !== "" ? v.trim() : void 0;
18
+ };
19
+ function resolveConfig(config = {}) {
20
+ const connectionString = config.connectionString ?? env("KNOWLEDGE_DB_URL");
21
+ if (!connectionString) {
22
+ throw new Error(
23
+ "[ai-knowledge-db] Missing connection string. Set KNOWLEDGE_DB_URL in your project's .env or pass { connectionString } to createKnowledgeDB()."
24
+ );
25
+ }
26
+ const openaiApiKey = config.openaiApiKey ?? env("OPENAI_API_KEY");
27
+ if (!openaiApiKey) {
28
+ throw new Error(
29
+ "[ai-knowledge-db] Missing OpenAI key. Set OPENAI_API_KEY in your project's .env or pass { openaiApiKey } to createKnowledgeDB()."
30
+ );
31
+ }
32
+ const embeddingModel = config.embeddingModel ?? env("KNOWLEDGE_EMBED_MODEL") ?? "text-embedding-3-small";
33
+ const embeddingDimensions = MODEL_DIMENSIONS[embeddingModel] ?? 1536;
34
+ return {
35
+ connectionString,
36
+ openaiApiKey,
37
+ embeddingModel,
38
+ embeddingDimensions,
39
+ clientId: config.clientId ?? env("KNOWLEDGE_CLIENT_ID"),
40
+ projectId: config.projectId ?? env("KNOWLEDGE_PROJECT_ID")
41
+ };
42
+ }
43
+
44
+ // src/embeddings.ts
45
+ import OpenAI from "openai";
46
+ var Embedder = class {
47
+ client;
48
+ model;
49
+ constructor(config) {
50
+ this.client = new OpenAI({ apiKey: config.openaiApiKey });
51
+ this.model = config.embeddingModel;
52
+ }
53
+ /** Embed a batch of strings in one API call. */
54
+ async embed(texts) {
55
+ if (texts.length === 0) return [];
56
+ const res = await this.client.embeddings.create({
57
+ model: this.model,
58
+ input: texts
59
+ });
60
+ return res.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
61
+ }
62
+ async embedOne(text) {
63
+ const [vec] = await this.embed([text]);
64
+ return vec;
65
+ }
66
+ };
67
+ function chunkText(text, opts = {}) {
68
+ const maxChars = opts.maxChars ?? 1200;
69
+ const overlap = opts.overlap ?? 150;
70
+ const clean = text.replace(/\r\n/g, "\n").trim();
71
+ if (clean.length <= maxChars) return clean ? [clean] : [];
72
+ const units = clean.split(/\n{2,}/).flatMap((p) => splitLongUnit(p, maxChars));
73
+ const chunks = [];
74
+ let current = "";
75
+ for (const unit of units) {
76
+ if (current && current.length + unit.length + 2 > maxChars) {
77
+ chunks.push(current.trim());
78
+ current = overlap > 0 ? current.slice(-overlap) + "\n\n" + unit : unit;
79
+ } else {
80
+ current = current ? current + "\n\n" + unit : unit;
81
+ }
82
+ }
83
+ if (current.trim()) chunks.push(current.trim());
84
+ return chunks;
85
+ }
86
+ function splitLongUnit(unit, maxChars) {
87
+ if (unit.length <= maxChars) return [unit];
88
+ const sentences = unit.match(/[^.!?\n]+[.!?]?\s*/g) ?? [unit];
89
+ const out = [];
90
+ let buf = "";
91
+ for (const s of sentences) {
92
+ if (s.length > maxChars) {
93
+ if (buf) {
94
+ out.push(buf);
95
+ buf = "";
96
+ }
97
+ for (let i = 0; i < s.length; i += maxChars) out.push(s.slice(i, i + maxChars));
98
+ } else if (buf.length + s.length > maxChars) {
99
+ out.push(buf);
100
+ buf = s;
101
+ } else {
102
+ buf += s;
103
+ }
104
+ }
105
+ if (buf) out.push(buf);
106
+ return out;
107
+ }
108
+
109
+ // src/schema.ts
110
+ var SCHEMA_SQL = `-- AI Knowledge DB schema (Postgres + pgvector)
111
+ -- Run once against your Hetzner/EasyPanel Postgres instance, or via kb.init().
112
+ -- If you switch to text-embedding-3-large, change 1536 -> 3072 and re-index.
113
+
114
+ create extension if not exists vector;
115
+ create extension if not exists "pgcrypto"; -- for gen_random_uuid()
116
+
117
+ create table if not exists knowledge (
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
+ source text,
123
+ content text not null,
124
+ embedding vector(1536) not null,
125
+ metadata jsonb not null default '{}',
126
+ created_at timestamptz not null default now()
127
+ );
128
+
129
+ create index if not exists knowledge_client_idx on knowledge (client_id);
130
+ create index if not exists knowledge_project_idx on knowledge (project_id);
131
+ create index if not exists knowledge_scope_idx on knowledge (scope);
132
+ create index if not exists knowledge_metadata_idx on knowledge using gin (metadata);
133
+
134
+ create index if not exists knowledge_embedding_idx
135
+ on knowledge using hnsw (embedding vector_cosine_ops);
136
+ `;
137
+
138
+ // src/client.ts
139
+ var { Pool } = pg;
140
+ var KnowledgeDB = class {
141
+ pool;
142
+ embedder;
143
+ config;
144
+ constructor(config = {}) {
145
+ this.config = resolveConfig(config);
146
+ this.pool = new Pool({ connectionString: this.config.connectionString });
147
+ this.embedder = new Embedder(this.config);
148
+ }
149
+ /** Create the extension, table, and indexes if they don't exist. Safe to call repeatedly. */
150
+ async init() {
151
+ await this.pool.query(SCHEMA_SQL);
152
+ }
153
+ /**
154
+ * Add knowledge. The content is chunked, embedded, and stored. Returns the
155
+ * ids of the stored rows (one per chunk). Scope/client/project fall back to
156
+ * the env-configured defaults.
157
+ */
158
+ async add(input) {
159
+ const clientId = input.clientId ?? this.config.clientId ?? null;
160
+ const projectId = input.projectId ?? this.config.projectId ?? null;
161
+ const scope = input.scope ?? defaultScope(clientId, projectId);
162
+ const source = input.source ?? null;
163
+ const metadata = input.metadata ?? {};
164
+ const chunks = chunkText(input.content, input.chunking);
165
+ if (chunks.length === 0) return [];
166
+ const vectors = await this.embedder.embed(chunks);
167
+ const ids = [];
168
+ const client = await this.pool.connect();
169
+ try {
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;
188
+ }
189
+ /**
190
+ * Replace knowledge from a given source. Deletes existing rows that match the
191
+ * same scope + ids + source, then re-adds. Use this for idempotent re-ingest
192
+ * of a file or URL so you don't accumulate duplicates.
193
+ */
194
+ async upsertSource(input) {
195
+ const clientId = input.clientId ?? this.config.clientId ?? null;
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);
200
+ }
201
+ /** Semantic search, scoped to client / project / global knowledge. */
202
+ async search(query, opts = {}) {
203
+ const clientId = opts.clientId ?? this.config.clientId;
204
+ const projectId = opts.projectId ?? this.config.projectId;
205
+ const includeClient = opts.includeClientKnowledge ?? true;
206
+ const includeGlobal = opts.includeGlobal ?? true;
207
+ const limit = opts.limit ?? 8;
208
+ const minScore = opts.minScore ?? 0;
209
+ const queryVec = toVector(await this.embedder.embedOne(query));
210
+ const orClauses = [];
211
+ const params = [queryVec];
212
+ const p = (v) => `$${params.push(v)}`;
213
+ if (projectId && allows(opts.scopes, "project")) {
214
+ orClauses.push(`(scope = 'project' and project_id = ${p(projectId)})`);
215
+ }
216
+ if (clientId && includeClient && allows(opts.scopes, "client")) {
217
+ orClauses.push(`(scope = 'client' and client_id = ${p(clientId)})`);
218
+ }
219
+ if (includeGlobal && allows(opts.scopes, "global")) {
220
+ orClauses.push(`scope = 'global'`);
221
+ }
222
+ const scopeClause = orClauses.length ? `(${orClauses.join(" or ")})` : `scope = 'global'`;
223
+ const where = [scopeClause];
224
+ if (opts.metadata) {
225
+ where.push(`metadata @> ${p(JSON.stringify(opts.metadata))}::jsonb`);
226
+ }
227
+ const { rows } = await this.pool.query(
228
+ `select id, scope, client_id, project_id, source, content, metadata, created_at,
229
+ 1 - (embedding <=> $1) as score
230
+ from knowledge
231
+ where ${where.join(" and ")}
232
+ order by embedding <=> $1
233
+ limit ${p(limit)}`,
234
+ params
235
+ );
236
+ return rows.map(rowToResult).filter((r) => r.score >= minScore);
237
+ }
238
+ /** Delete rows matching a filter. Returns the number deleted. */
239
+ async delete(filter) {
240
+ const where = [];
241
+ const params = [];
242
+ const p = (v) => `$${params.push(v)}`;
243
+ if (filter.id) where.push(`id = ${p(filter.id)}`);
244
+ if (filter.scope) where.push(`scope = ${p(filter.scope)}`);
245
+ if (filter.clientId) where.push(`client_id = ${p(filter.clientId)}`);
246
+ if (filter.projectId) where.push(`project_id = ${p(filter.projectId)}`);
247
+ if (filter.source) where.push(`source = ${p(filter.source)}`);
248
+ if (where.length === 0) {
249
+ throw new Error("[ai-knowledge-db] delete() requires at least one filter to avoid wiping the table.");
250
+ }
251
+ const { rowCount } = await this.pool.query(
252
+ `delete from knowledge where ${where.join(" and ")}`,
253
+ params
254
+ );
255
+ return rowCount ?? 0;
256
+ }
257
+ /** Close the connection pool. Call on shutdown. */
258
+ async close() {
259
+ await this.pool.end();
260
+ }
261
+ };
262
+ function createKnowledgeDB(config) {
263
+ return new KnowledgeDB(config);
264
+ }
265
+ function defaultScope(clientId, projectId) {
266
+ if (projectId) return "project";
267
+ if (clientId) return "client";
268
+ return "global";
269
+ }
270
+ function allows(scopes, scope) {
271
+ return !scopes || scopes.includes(scope);
272
+ }
273
+ function toVector(vec) {
274
+ return `[${vec.join(",")}]`;
275
+ }
276
+ function rowToResult(row) {
277
+ return { ...rowToRecord(row), score: Number(row.score) };
278
+ }
279
+ function rowToRecord(row) {
280
+ return {
281
+ id: row.id,
282
+ scope: row.scope,
283
+ clientId: row.client_id,
284
+ projectId: row.project_id,
285
+ source: row.source,
286
+ content: row.content,
287
+ metadata: row.metadata ?? {},
288
+ createdAt: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at
289
+ };
290
+ }
291
+
292
+ // src/cli.ts
293
+ function parse(argv) {
294
+ const positional = [];
295
+ const flags = {};
296
+ for (let i = 0; i < argv.length; i++) {
297
+ const a = argv[i];
298
+ if (a === "--json") flags.json = true;
299
+ else if (a.startsWith("--")) flags[a.slice(2)] = argv[++i];
300
+ else positional.push(a);
301
+ }
302
+ return { positional, flags };
303
+ }
304
+ var USAGE = "Usage: knowledge-db <init|add|add-file|search|delete> [args] [flags]\nFlags: --scope --client --project --source --meta --limit --id --json";
305
+ async function main() {
306
+ const [command, ...rest] = process.argv.slice(2);
307
+ if (!command || command === "help" || command === "-h" || command === "--help") {
308
+ console.log(USAGE);
309
+ process.exitCode = command ? 0 : 1;
310
+ return;
311
+ }
312
+ const { positional, flags } = parse(rest);
313
+ const kb = createKnowledgeDB();
314
+ const meta = flags.meta ? JSON.parse(flags.meta) : void 0;
315
+ try {
316
+ switch (command) {
317
+ case "init": {
318
+ await kb.init();
319
+ console.log("\u2713 schema ready");
320
+ break;
321
+ }
322
+ case "add": {
323
+ const ids = await kb.add({
324
+ content: positional.join(" "),
325
+ scope: flags.scope,
326
+ clientId: flags.client,
327
+ projectId: flags.project,
328
+ source: flags.source,
329
+ metadata: meta
330
+ });
331
+ console.log(`\u2713 stored ${ids.length} chunk(s)`);
332
+ break;
333
+ }
334
+ case "add-file": {
335
+ const path = positional[0];
336
+ const content = readFileSync(path, "utf8");
337
+ const ids = await kb.upsertSource({
338
+ content,
339
+ source: flags.source ?? path,
340
+ scope: flags.scope,
341
+ clientId: flags.client,
342
+ projectId: flags.project,
343
+ metadata: meta
344
+ });
345
+ console.log(`\u2713 re-ingested ${path} \u2192 ${ids.length} chunk(s)`);
346
+ break;
347
+ }
348
+ case "search": {
349
+ const results = await kb.search(positional.join(" "), {
350
+ clientId: flags.client,
351
+ projectId: flags.project,
352
+ limit: flags.limit ? Number(flags.limit) : void 0
353
+ });
354
+ if (flags.json) {
355
+ console.log(JSON.stringify(results, null, 2));
356
+ } else if (results.length === 0) {
357
+ console.log("(no matches)");
358
+ } else {
359
+ for (const r of results) {
360
+ console.log(
361
+ `
362
+ [${r.score.toFixed(3)}] ${r.scope}${r.source ? ` \xB7 ${r.source}` : ""}
363
+ ${r.content}`
364
+ );
365
+ }
366
+ }
367
+ break;
368
+ }
369
+ case "delete": {
370
+ const n = await kb.delete({
371
+ id: flags.id,
372
+ source: flags.source,
373
+ projectId: flags.project,
374
+ clientId: flags.client,
375
+ scope: flags.scope
376
+ });
377
+ console.log(`\u2713 deleted ${n} row(s)`);
378
+ break;
379
+ }
380
+ default:
381
+ console.log(`Unknown command: ${command}
382
+ ${USAGE}`);
383
+ process.exitCode = 1;
384
+ }
385
+ } finally {
386
+ await kb.close();
387
+ }
388
+ }
389
+ main().catch((err) => {
390
+ console.error(err instanceof Error ? err.message : err);
391
+ process.exit(1);
392
+ });
393
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +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":[]}