@cerefox/memory 1.0.0-rc.2 → 1.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/cerefox.js
CHANGED
|
@@ -7184,7 +7184,7 @@ var exports_meta = {};
|
|
|
7184
7184
|
__export(exports_meta, {
|
|
7185
7185
|
PKG_VERSION: () => PKG_VERSION
|
|
7186
7186
|
});
|
|
7187
|
-
var PKG_VERSION = "1.0.0-rc.
|
|
7187
|
+
var PKG_VERSION = "1.0.0-rc.4";
|
|
7188
7188
|
var init_meta = () => {};
|
|
7189
7189
|
|
|
7190
7190
|
// ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
|
|
@@ -25008,10 +25008,11 @@ function getMaxResponseBytes() {
|
|
|
25008
25008
|
}
|
|
25009
25009
|
function getMinSearchScore() {
|
|
25010
25010
|
const raw = globalThis.process?.env?.CEREFOX_MIN_SEARCH_SCORE;
|
|
25011
|
+
const fallback = globalThis.process?.env?.CEREFOX_EMBEDDER === "local" ? DEFAULT_MIN_SEARCH_SCORE_LOCAL : DEFAULT_MIN_SEARCH_SCORE;
|
|
25011
25012
|
if (raw === undefined || raw === "")
|
|
25012
|
-
return
|
|
25013
|
+
return fallback;
|
|
25013
25014
|
const n = Number.parseFloat(raw);
|
|
25014
|
-
return Number.isNaN(n) || n < 0 || n > 1 ?
|
|
25015
|
+
return Number.isNaN(n) || n < 0 || n > 1 ? fallback : n;
|
|
25015
25016
|
}
|
|
25016
25017
|
function applyByteBudget(rows, maxBytes) {
|
|
25017
25018
|
const accepted = [];
|
|
@@ -25040,7 +25041,7 @@ function logUsage(supabase, params) {
|
|
|
25040
25041
|
p_extra: params.extra ?? {}
|
|
25041
25042
|
})).catch(() => {});
|
|
25042
25043
|
}
|
|
25043
|
-
var MAX_RESPONSE_BYTES = 200000, DEFAULT_MIN_SEARCH_SCORE = 0.5;
|
|
25044
|
+
var MAX_RESPONSE_BYTES = 200000, DEFAULT_MIN_SEARCH_SCORE = 0.5, DEFAULT_MIN_SEARCH_SCORE_LOCAL = 0.6;
|
|
25044
25045
|
|
|
25045
25046
|
// ../../_shared/mcp-tools/_projects.ts
|
|
25046
25047
|
async function ensureDocumentInProject(supabase, documentId, projectName) {
|
|
@@ -25532,9 +25533,22 @@ async function ensurePipeline() {
|
|
|
25532
25533
|
async function warmup() {
|
|
25533
25534
|
await ensurePipeline();
|
|
25534
25535
|
}
|
|
25536
|
+
function onnxBatchSize() {
|
|
25537
|
+
const env4 = globalThis.process?.env ?? {};
|
|
25538
|
+
const n = Number.parseInt(env4.CEREFOX_ONNX_BATCH ?? "", 10);
|
|
25539
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_ONNX_BATCH;
|
|
25540
|
+
}
|
|
25535
25541
|
async function onnxEmbed(texts, role) {
|
|
25536
25542
|
if (texts.length === 0)
|
|
25537
25543
|
return [];
|
|
25544
|
+
const batch = onnxBatchSize();
|
|
25545
|
+
if (texts.length > batch) {
|
|
25546
|
+
const out2 = [];
|
|
25547
|
+
for (let i = 0;i < texts.length; i += batch) {
|
|
25548
|
+
out2.push(...await onnxEmbed(texts.slice(i, i + batch), role));
|
|
25549
|
+
}
|
|
25550
|
+
return out2;
|
|
25551
|
+
}
|
|
25538
25552
|
const pipeline = await ensurePipeline();
|
|
25539
25553
|
const inputs = buildPrefixedInputs(texts, role);
|
|
25540
25554
|
const out = await pipeline(inputs, { pooling: "mean", normalize: true });
|
|
@@ -25549,7 +25563,7 @@ async function onnxEmbed(texts, role) {
|
|
|
25549
25563
|
}
|
|
25550
25564
|
return vectors;
|
|
25551
25565
|
}
|
|
25552
|
-
var ONNX_MODEL_ID = "nomic-ai/nomic-embed-text-v1.5", ONNX_MODEL_NAME = "nomic-embed-text-v1.5", ONNX_MODEL_DTYPE = "q8", ONNX_MODEL_DIM = 768, ONNX_MODEL_APPROX_MB = 130, transformersModule = null, pipelinePromise = null;
|
|
25566
|
+
var ONNX_MODEL_ID = "nomic-ai/nomic-embed-text-v1.5", ONNX_MODEL_NAME = "nomic-embed-text-v1.5", ONNX_MODEL_DTYPE = "q8", ONNX_MODEL_DIM = 768, ONNX_MODEL_APPROX_MB = 130, transformersModule = null, pipelinePromise = null, DEFAULT_ONNX_BATCH = 4;
|
|
25553
25567
|
var init_onnx_embedder = () => {};
|
|
25554
25568
|
|
|
25555
25569
|
// ../../_shared/embeddings/index.ts
|
|
@@ -74646,7 +74660,7 @@ import { homedir as homedir6 } from "node:os";
|
|
|
74646
74660
|
import { join as join9 } from "node:path";
|
|
74647
74661
|
|
|
74648
74662
|
// ../../_shared/ef-meta/index.ts
|
|
74649
|
-
var EF_VERSION = "1.0.0-
|
|
74663
|
+
var EF_VERSION = "1.0.0-rc.4";
|
|
74650
74664
|
|
|
74651
74665
|
// src/cli/util/checks.ts
|
|
74652
74666
|
init_config();
|
|
@@ -74704,6 +74718,14 @@ function checkConfig() {
|
|
|
74704
74718
|
};
|
|
74705
74719
|
}
|
|
74706
74720
|
if (!existsSync10(envPath)) {
|
|
74721
|
+
const settings = loadSettings();
|
|
74722
|
+
if (settings.supabaseUrl && settings.supabaseKey) {
|
|
74723
|
+
return {
|
|
74724
|
+
name: "config",
|
|
74725
|
+
status: "ok",
|
|
74726
|
+
detail: "configured via environment variables (no .env file — normal for Cerefox Local)"
|
|
74727
|
+
};
|
|
74728
|
+
}
|
|
74707
74729
|
return {
|
|
74708
74730
|
name: "config",
|
|
74709
74731
|
status: "error",
|
|
@@ -75023,9 +75045,9 @@ function checkMcpConfigs() {
|
|
|
75023
75045
|
if (found.length === 0) {
|
|
75024
75046
|
return {
|
|
75025
75047
|
name: "mcp clients",
|
|
75026
|
-
status: "warn",
|
|
75027
|
-
detail: "No MCP client configs reference Cerefox.",
|
|
75028
|
-
hint: "Run `cerefox configure-agent --tool claude-code` (or `--tool claude-desktop`) to wire up a client."
|
|
75048
|
+
status: isLocalBackend() ? "skipped" : "warn",
|
|
75049
|
+
detail: isLocalBackend() ? "checked from inside the container — host MCP configs are not visible here." : "No MCP client configs reference Cerefox.",
|
|
75050
|
+
hint: isLocalBackend() ? "Configure agents on the host with `cerefox-local configure-agent`." : "Run `cerefox configure-agent --tool claude-code` (or `--tool claude-desktop`) to wire up a client."
|
|
75029
75051
|
};
|
|
75030
75052
|
}
|
|
75031
75053
|
return {
|
|
@@ -75097,7 +75119,17 @@ async function checkPostgres() {
|
|
|
75097
75119
|
await sql.end({ timeout: 1 }).catch(() => {});
|
|
75098
75120
|
}
|
|
75099
75121
|
}
|
|
75122
|
+
function isLocalBackend() {
|
|
75123
|
+
return Boolean(process.env.CEREFOX_POSTGREST_UPSTREAM);
|
|
75124
|
+
}
|
|
75100
75125
|
async function checkEdgeFunctionsCompat() {
|
|
75126
|
+
if (isLocalBackend()) {
|
|
75127
|
+
return {
|
|
75128
|
+
name: "edge functions",
|
|
75129
|
+
status: "skipped",
|
|
75130
|
+
detail: "local backend — Edge Functions are not used (cloud-only surface)."
|
|
75131
|
+
};
|
|
75132
|
+
}
|
|
75101
75133
|
const settings = loadSettings();
|
|
75102
75134
|
if (!settings.supabaseUrl) {
|
|
75103
75135
|
return {
|
|
@@ -77234,7 +77266,7 @@ async function action29(query, options) {
|
|
|
77234
77266
|
}
|
|
77235
77267
|
}
|
|
77236
77268
|
function registerSearch(program2) {
|
|
77237
|
-
program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE or 0.
|
|
77269
|
+
program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action29);
|
|
77238
77270
|
}
|
|
77239
77271
|
|
|
77240
77272
|
// src/cli/commands/self-update.ts
|
|
@@ -208,8 +208,32 @@ export async function warmup(): Promise<void> {
|
|
|
208
208
|
* Mean pooling + L2 normalisation (sentence-transformers convention; nomic
|
|
209
209
|
* expects both). Returns plain `number[][]` to match the OpenAI path.
|
|
210
210
|
*/
|
|
211
|
+
/**
|
|
212
|
+
* Per-inference sub-batch. Peak tensor memory scales with the batch, and the
|
|
213
|
+
* container shares a (often small) Docker VM with Postgres + PostgREST + the
|
|
214
|
+
* web server — a 12-text single call was OOM-killed on Colima's default 2 GB
|
|
215
|
+
* VM (rc.2 dogfood, exit 137). 4 keeps peak memory flat at personal scale;
|
|
216
|
+
* override with CEREFOX_ONNX_BATCH.
|
|
217
|
+
*/
|
|
218
|
+
const DEFAULT_ONNX_BATCH = 4;
|
|
219
|
+
|
|
220
|
+
function onnxBatchSize(): number {
|
|
221
|
+
const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })
|
|
222
|
+
.process?.env ?? {};
|
|
223
|
+
const n = Number.parseInt(env.CEREFOX_ONNX_BATCH ?? "", 10);
|
|
224
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_ONNX_BATCH;
|
|
225
|
+
}
|
|
226
|
+
|
|
211
227
|
export async function onnxEmbed(texts: string[], role: EmbedRole): Promise<number[][]> {
|
|
212
228
|
if (texts.length === 0) return [];
|
|
229
|
+
const batch = onnxBatchSize();
|
|
230
|
+
if (texts.length > batch) {
|
|
231
|
+
const out: number[][] = [];
|
|
232
|
+
for (let i = 0; i < texts.length; i += batch) {
|
|
233
|
+
out.push(...(await onnxEmbed(texts.slice(i, i + batch), role)));
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
213
237
|
const pipeline = await ensurePipeline();
|
|
214
238
|
const inputs = buildPrefixedInputs(texts, role);
|
|
215
239
|
const out = await pipeline(inputs, { pooling: "mean", normalize: true });
|
|
@@ -35,6 +35,15 @@ export function getMaxResponseBytes(): number {
|
|
|
35
35
|
/** Built-in default cosine-similarity floor for hybrid/semantic search. */
|
|
36
36
|
export const DEFAULT_MIN_SEARCH_SCORE = 0.5;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Nomic's cosine-score distribution sits higher than OpenAI's: unrelated text
|
|
40
|
+
* lands ~0.4–0.55 (vs ~0.1–0.3), so the 0.5 floor calibrated for
|
|
41
|
+
* text-embedding-3-small lets weak matches through on the local embedder
|
|
42
|
+
* (rc.3 dogfood: an unrelated doc passed at vec≈0.54). 0.6 restores the
|
|
43
|
+
* intended precision; relevant nomic matches score ~0.7+.
|
|
44
|
+
*/
|
|
45
|
+
export const DEFAULT_MIN_SEARCH_SCORE_LOCAL = 0.6;
|
|
46
|
+
|
|
38
47
|
/**
|
|
39
48
|
* Resolve the minimum cosine-similarity floor for hybrid/semantic search
|
|
40
49
|
* (vector-only matches below this are dropped; FTS matches always pass).
|
|
@@ -49,9 +58,14 @@ export const DEFAULT_MIN_SEARCH_SCORE = 0.5;
|
|
|
49
58
|
export function getMinSearchScore(): number {
|
|
50
59
|
const raw = (globalThis as { process?: { env?: Record<string, string | undefined> } })
|
|
51
60
|
.process?.env?.CEREFOX_MIN_SEARCH_SCORE;
|
|
52
|
-
|
|
61
|
+
const fallback =
|
|
62
|
+
(globalThis as { process?: { env?: Record<string, string | undefined> } })
|
|
63
|
+
.process?.env?.CEREFOX_EMBEDDER === "local"
|
|
64
|
+
? DEFAULT_MIN_SEARCH_SCORE_LOCAL
|
|
65
|
+
: DEFAULT_MIN_SEARCH_SCORE;
|
|
66
|
+
if (raw === undefined || raw === "") return fallback;
|
|
53
67
|
const n = Number.parseFloat(raw);
|
|
54
|
-
return Number.isNaN(n) || n < 0 || n > 1 ?
|
|
68
|
+
return Number.isNaN(n) || n < 0 || n > 1 ? fallback : n;
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
export function applyByteBudget(
|
|
@@ -49,6 +49,10 @@ install; switching later requires a re-index (see below).
|
|
|
49
49
|
The local model (~130 MB) downloads once — at install/init when selected — into the
|
|
50
50
|
data volume, so it survives `cerefox-local upgrade`.
|
|
51
51
|
|
|
52
|
+
> **Memory**: give the Docker VM **≥ 4 GB** for comfortable local-embedder use
|
|
53
|
+
> (Colima defaults to 2 GB: `colima start --memory 4`). Inference is
|
|
54
|
+
> sub-batched to keep peak memory flat, so smaller VMs work — just slower.
|
|
55
|
+
|
|
52
56
|
> **Switching embedders on existing data is breaking**: the two models produce
|
|
53
57
|
> incompatible vector spaces, so documents embedded with one are invisible to
|
|
54
58
|
> semantic search under the other. `cerefox-local init` warns and requires
|
|
@@ -56,6 +60,10 @@ data volume, so it survives `cerefox-local upgrade`.
|
|
|
56
60
|
> `cerefox-local server reindex` to re-embed everything. `cerefox-local doctor`
|
|
57
61
|
> flags any mismatch.
|
|
58
62
|
|
|
63
|
+
> Scores are calibrated per embedder: with the local model the default semantic
|
|
64
|
+
> threshold is **0.6** (vs 0.5 for OpenAI) because nomic scores unrelated text
|
|
65
|
+
> higher. Override per call with `--min-score` or via `CEREFOX_MIN_SEARCH_SCORE`.
|
|
66
|
+
|
|
59
67
|
---
|
|
60
68
|
|
|
61
69
|
## Step 1 — Install
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cerefox/memory",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.4",
|
|
4
4
|
"description": "Cerefox — user-owned shared memory for AI agents. The local TypeScript runtime: stdio MCP server in v0.4; CLI binary added in v0.5; in-process web server in v0.6; ingestion pipeline in v0.7.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/fstamatelopoulos/cerefox",
|