@cerefox/memory 1.1.0-beta.1 → 1.1.0-beta.2
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 +336 -32
- package/dist/frontend/assets/index-CMNl_LF9.js +121 -0
- package/dist/frontend/assets/index-CMNl_LF9.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +2 -2
- package/dist/server-assets/_shared/mcp-tools/feature-flags.ts +68 -0
- package/dist/server-assets/_shared/mcp-tools/index.ts +31 -1
- package/dist/server-assets/db/migrations/0014_document_relations.sql +5 -0
- package/dist/server-assets/db/rpcs.sql +4 -2
- package/dist/server-assets/db/schema.sql +6 -1
- package/dist/server-assets/supabase/functions/cerefox-mcp/index.ts +25 -8
- package/docs/guides/configuration.md +6 -0
- package/docs/guides/content-format.md +8 -1
- package/docs/guides/setup-supabase.md +35 -4
- package/package.json +1 -1
- package/dist/frontend/assets/index-BMFGsK0D.js +0 -121
- package/dist/frontend/assets/index-BMFGsK0D.js.map +0 -1
package/dist/bin/cerefox.js
CHANGED
|
@@ -7438,7 +7438,7 @@ var exports_meta = {};
|
|
|
7438
7438
|
__export(exports_meta, {
|
|
7439
7439
|
PKG_VERSION: () => PKG_VERSION
|
|
7440
7440
|
});
|
|
7441
|
-
var PKG_VERSION = "1.1.0-beta.
|
|
7441
|
+
var PKG_VERSION = "1.1.0-beta.2";
|
|
7442
7442
|
var init_meta = () => {};
|
|
7443
7443
|
|
|
7444
7444
|
// ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
|
|
@@ -23362,9 +23362,12 @@ function loadEnv(opts = {}) {
|
|
|
23362
23362
|
} catch {
|
|
23363
23363
|
return { path: envPath, vars: 0 };
|
|
23364
23364
|
}
|
|
23365
|
+
const configDirNamed = (env2.CEREFOX_CONFIG_DIR ?? "").trim() !== "";
|
|
23365
23366
|
let count = 0;
|
|
23366
23367
|
for (const [k, v] of Object.entries(parseDotenv(content))) {
|
|
23367
|
-
if (
|
|
23368
|
+
if (k === "CEREFOX_CONFIG_DIR")
|
|
23369
|
+
continue;
|
|
23370
|
+
if (env2[k] === undefined || configDirNamed) {
|
|
23368
23371
|
env2[k] = v;
|
|
23369
23372
|
count++;
|
|
23370
23373
|
}
|
|
@@ -55186,6 +55189,36 @@ var init_audit_log = __esm(() => {
|
|
|
55186
55189
|
};
|
|
55187
55190
|
});
|
|
55188
55191
|
|
|
55192
|
+
// ../../_shared/mcp-tools/feature-flags.ts
|
|
55193
|
+
async function relationsEnabled(supabase) {
|
|
55194
|
+
if (cached && Date.now() - cached.at < CACHE_TTL_MS)
|
|
55195
|
+
return cached.value;
|
|
55196
|
+
try {
|
|
55197
|
+
const { data, error: error2 } = await supabase.rpc("cerefox_get_config", {
|
|
55198
|
+
p_key: "relations_enabled"
|
|
55199
|
+
});
|
|
55200
|
+
if (error2)
|
|
55201
|
+
throw new Error(error2.message);
|
|
55202
|
+
const value = String(data ?? "").trim().toLowerCase() === "true";
|
|
55203
|
+
cached = { value, at: Date.now() };
|
|
55204
|
+
return value;
|
|
55205
|
+
} catch {
|
|
55206
|
+
return false;
|
|
55207
|
+
}
|
|
55208
|
+
}
|
|
55209
|
+
function disabledToolMessage(name) {
|
|
55210
|
+
return `${name} is part of the document-relations feature, which is off by default. ` + `Enable it with: cerefox config set relations_enabled true ` + `(deployment-wide; every access path picks it up).`;
|
|
55211
|
+
}
|
|
55212
|
+
var RELATION_TOOL_NAMES, CACHE_TTL_MS = 60000, cached = null;
|
|
55213
|
+
var init_feature_flags = __esm(() => {
|
|
55214
|
+
RELATION_TOOL_NAMES = new Set([
|
|
55215
|
+
"cerefox_set_relation",
|
|
55216
|
+
"cerefox_delete_relation",
|
|
55217
|
+
"cerefox_get_relations",
|
|
55218
|
+
"cerefox_get_neighbors"
|
|
55219
|
+
]);
|
|
55220
|
+
});
|
|
55221
|
+
|
|
55189
55222
|
// ../../_shared/mcp-tools/types.ts
|
|
55190
55223
|
var McpInvalidParams;
|
|
55191
55224
|
var init_types3 = __esm(() => {
|
|
@@ -56281,9 +56314,21 @@ var init_set_document_projects = __esm(() => {
|
|
|
56281
56314
|
});
|
|
56282
56315
|
|
|
56283
56316
|
// ../../_shared/mcp-tools/index.ts
|
|
56317
|
+
async function listEnabledTools(supabase) {
|
|
56318
|
+
const relations = await relationsEnabled(supabase);
|
|
56319
|
+
return ALL_TOOLS.filter((t) => relations || !RELATION_TOOL_NAMES.has(t.name));
|
|
56320
|
+
}
|
|
56321
|
+
async function assertToolEnabled(supabase, name) {
|
|
56322
|
+
if (!RELATION_TOOL_NAMES.has(name))
|
|
56323
|
+
return;
|
|
56324
|
+
if (await relationsEnabled(supabase))
|
|
56325
|
+
return;
|
|
56326
|
+
throw new McpInvalidParams(disabledToolMessage(name));
|
|
56327
|
+
}
|
|
56284
56328
|
var ALL_TOOLS, TOOLS_BY_NAME;
|
|
56285
56329
|
var init_mcp_tools = __esm(() => {
|
|
56286
56330
|
init_audit_log();
|
|
56331
|
+
init_feature_flags();
|
|
56287
56332
|
init_relations();
|
|
56288
56333
|
init_get_document();
|
|
56289
56334
|
init_get_help();
|
|
@@ -56295,6 +56340,7 @@ var init_mcp_tools = __esm(() => {
|
|
|
56295
56340
|
init_search();
|
|
56296
56341
|
init_set_document_projects();
|
|
56297
56342
|
init_types3();
|
|
56343
|
+
init_types3();
|
|
56298
56344
|
ALL_TOOLS = [
|
|
56299
56345
|
searchTool,
|
|
56300
56346
|
ingestTool,
|
|
@@ -56500,7 +56546,7 @@ __export(exports_util, {
|
|
|
56500
56546
|
cleanRegex: () => cleanRegex,
|
|
56501
56547
|
cleanEnum: () => cleanEnum,
|
|
56502
56548
|
captureStackTrace: () => captureStackTrace,
|
|
56503
|
-
cached: () =>
|
|
56549
|
+
cached: () => cached2,
|
|
56504
56550
|
assignProp: () => assignProp,
|
|
56505
56551
|
assertNotEqual: () => assertNotEqual,
|
|
56506
56552
|
assertNever: () => assertNever,
|
|
@@ -56537,7 +56583,7 @@ function jsonStringifyReplacer(_, value) {
|
|
|
56537
56583
|
return value.toString();
|
|
56538
56584
|
return value;
|
|
56539
56585
|
}
|
|
56540
|
-
function
|
|
56586
|
+
function cached2(getter) {
|
|
56541
56587
|
const set = false;
|
|
56542
56588
|
return {
|
|
56543
56589
|
get value() {
|
|
@@ -56949,7 +56995,7 @@ var captureStackTrace, allowsEval, getParsedType2 = (data) => {
|
|
|
56949
56995
|
}, propertyKeyTypes, primitiveTypes, NUMBER_FORMAT_RANGES, BIGINT_FORMAT_RANGES;
|
|
56950
56996
|
var init_util2 = __esm(() => {
|
|
56951
56997
|
captureStackTrace = Error.captureStackTrace ? Error.captureStackTrace : (..._args) => {};
|
|
56952
|
-
allowsEval =
|
|
56998
|
+
allowsEval = cached2(() => {
|
|
56953
56999
|
if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) {
|
|
56954
57000
|
return false;
|
|
56955
57001
|
}
|
|
@@ -58254,7 +58300,7 @@ var init_schemas = __esm(() => {
|
|
|
58254
58300
|
});
|
|
58255
58301
|
$ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
58256
58302
|
$ZodType.init(inst, def);
|
|
58257
|
-
const _normalized =
|
|
58303
|
+
const _normalized = cached2(() => {
|
|
58258
58304
|
const keys = Object.keys(def.shape);
|
|
58259
58305
|
for (const k of keys) {
|
|
58260
58306
|
if (!(def.shape[k] instanceof $ZodType)) {
|
|
@@ -58474,7 +58520,7 @@ var init_schemas = __esm(() => {
|
|
|
58474
58520
|
}
|
|
58475
58521
|
return propValues;
|
|
58476
58522
|
});
|
|
58477
|
-
const disc =
|
|
58523
|
+
const disc = cached2(() => {
|
|
58478
58524
|
const opts = def.options;
|
|
58479
58525
|
const map = new Map;
|
|
58480
58526
|
for (const o of opts) {
|
|
@@ -69455,7 +69501,7 @@ function buildServer() {
|
|
|
69455
69501
|
};
|
|
69456
69502
|
const server = new Server({ name: SERVER_NAME, version: PKG_VERSION }, { capabilities: { tools: {} } });
|
|
69457
69503
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
69458
|
-
tools:
|
|
69504
|
+
tools: (await listEnabledTools(supabase)).map((t) => ({
|
|
69459
69505
|
name: t.name,
|
|
69460
69506
|
description: t.description,
|
|
69461
69507
|
inputSchema: t.inputSchema
|
|
@@ -69468,6 +69514,7 @@ function buildServer() {
|
|
|
69468
69514
|
throw new McpInvalidParams(`Unknown tool: ${name}`);
|
|
69469
69515
|
}
|
|
69470
69516
|
try {
|
|
69517
|
+
await assertToolEnabled(supabase, name);
|
|
69471
69518
|
const text = await tool.handler(supabase, args, ctx);
|
|
69472
69519
|
return { content: [{ type: "text", text }] };
|
|
69473
69520
|
} catch (err) {
|
|
@@ -69574,6 +69621,7 @@ async function fetchAllPages(makeQuery, batchSize = 200) {
|
|
|
69574
69621
|
|
|
69575
69622
|
// src/cli/commands/backup.ts
|
|
69576
69623
|
init_client();
|
|
69624
|
+
init_meta();
|
|
69577
69625
|
function expandHome(path) {
|
|
69578
69626
|
if (path === "~")
|
|
69579
69627
|
return homedir2();
|
|
@@ -69587,18 +69635,67 @@ function utcStamp() {
|
|
|
69587
69635
|
return d.getUTCFullYear().toString() + pad(d.getUTCMonth() + 1) + pad(d.getUTCDate()) + "T" + pad(d.getUTCHours()) + pad(d.getUTCMinutes()) + pad(d.getUTCSeconds()) + "Z";
|
|
69588
69636
|
}
|
|
69589
69637
|
async function action(options) {
|
|
69590
|
-
const
|
|
69638
|
+
const configuredDir = options.outputDir ?? process.env.CEREFOX_BACKUP_DIR;
|
|
69639
|
+
const outDir = resolve(expandHome(configuredDir ?? "~/.cerefox/backups"));
|
|
69640
|
+
if (configuredDir !== undefined && !configuredDir.startsWith("/") && !configuredDir.startsWith("~")) {
|
|
69641
|
+
println(c.yellow("⚠ ") + `Backup directory "${configuredDir}" is relative — it resolves against the ` + "current working directory, so backups land in different places depending " + "on where you run this from.");
|
|
69642
|
+
println(c.dim(` Writing to: ${outDir}`));
|
|
69643
|
+
println(c.dim(" Set an absolute path (e.g. ~/.cerefox/backups) to keep them together."));
|
|
69644
|
+
}
|
|
69591
69645
|
if (!existsSync2(outDir))
|
|
69592
69646
|
mkdirSync(outDir, { recursive: true });
|
|
69593
69647
|
const stamp = utcStamp();
|
|
69594
69648
|
const filename = `cerefox-${stamp}${options.label ? "-" + options.label : ""}.json`;
|
|
69595
69649
|
const dest = join2(outDir, filename);
|
|
69596
69650
|
const client = getClient();
|
|
69651
|
+
let schemaVersion = "unknown";
|
|
69652
|
+
try {
|
|
69653
|
+
schemaVersion = await client.rpc("cerefox_schema_version", {}) ?? "unknown";
|
|
69654
|
+
} catch {}
|
|
69655
|
+
const includeTrash = options.trash !== false;
|
|
69656
|
+
const BASE_COLUMNS = "id, title, content_hash, source, metadata, total_chars, chunk_count, " + "review_status, created_at, updated_at, deleted_at";
|
|
69657
|
+
const fetchDocs = (columns) => fetchAllPages((from, to) => {
|
|
69658
|
+
const q = client.raw.from("cerefox_documents").select(columns);
|
|
69659
|
+
return (includeTrash ? q : q.is("deleted_at", null)).order("created_at", { ascending: true }).order("id", { ascending: true }).range(from, to);
|
|
69660
|
+
});
|
|
69597
69661
|
let docs;
|
|
69662
|
+
let lifecycleCaptured = true;
|
|
69663
|
+
try {
|
|
69664
|
+
docs = await fetchDocs(`${BASE_COLUMNS}, lifecycle_status`);
|
|
69665
|
+
} catch (err) {
|
|
69666
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
69667
|
+
if (!/lifecycle_status/.test(message)) {
|
|
69668
|
+
throw systemError(`Document fetch failed: ${message}`);
|
|
69669
|
+
}
|
|
69670
|
+
lifecycleCaptured = false;
|
|
69671
|
+
try {
|
|
69672
|
+
docs = await fetchDocs(BASE_COLUMNS);
|
|
69673
|
+
} catch (retryErr) {
|
|
69674
|
+
throw systemError(`Document fetch failed: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
|
|
69675
|
+
}
|
|
69676
|
+
println(c.dim(" (server predates lifecycle_status — captured without it)"));
|
|
69677
|
+
}
|
|
69678
|
+
const trashedCount = docs.filter((d) => d.deleted_at != null).length;
|
|
69679
|
+
let projects = [];
|
|
69680
|
+
let memberships = [];
|
|
69598
69681
|
try {
|
|
69599
|
-
|
|
69682
|
+
projects = await fetchAllPages((from, to) => client.raw.from("cerefox_projects").select("id, name, description, created_at, updated_at").order("id", { ascending: true }).range(from, to));
|
|
69683
|
+
memberships = await fetchAllPages((from, to) => client.raw.from("cerefox_document_projects").select("document_id, project_id").order("document_id", { ascending: true }).order("project_id", { ascending: true }).range(from, to));
|
|
69600
69684
|
} catch (err) {
|
|
69601
|
-
throw systemError(`
|
|
69685
|
+
throw systemError(`Project/membership fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
69686
|
+
}
|
|
69687
|
+
let relations = [];
|
|
69688
|
+
try {
|
|
69689
|
+
relations = await fetchAllPages((from, to) => client.raw.from("cerefox_document_relations").select("source_id, target_id, rel_type, metadata, author, author_type, created_at").order("source_id", { ascending: true }).order("target_id", { ascending: true }).order("rel_type", { ascending: true }).range(from, to));
|
|
69690
|
+
} catch {}
|
|
69691
|
+
{
|
|
69692
|
+
const captured = new Set(docs.map((d) => d.id));
|
|
69693
|
+
const before = memberships.length;
|
|
69694
|
+
memberships = memberships.filter((m) => captured.has(m.document_id));
|
|
69695
|
+
const dropped = before - memberships.length;
|
|
69696
|
+
if (dropped > 0) {
|
|
69697
|
+
println(c.dim(` (skipped ${dropped} membership(s) belonging to trashed documents)`));
|
|
69698
|
+
}
|
|
69602
69699
|
}
|
|
69603
69700
|
let chunkTotal = 0;
|
|
69604
69701
|
const enriched = [];
|
|
@@ -69622,22 +69719,38 @@ async function action(options) {
|
|
|
69622
69719
|
`);
|
|
69623
69720
|
const payload = {
|
|
69624
69721
|
created_at: new Date().toISOString(),
|
|
69625
|
-
cerefox_version:
|
|
69722
|
+
cerefox_version: PKG_VERSION,
|
|
69723
|
+
backup_format: 4,
|
|
69724
|
+
schema_version: schemaVersion,
|
|
69626
69725
|
document_count: docs.length,
|
|
69726
|
+
trashed_count: trashedCount,
|
|
69727
|
+
includes_trash: includeTrash,
|
|
69728
|
+
includes_lifecycle_status: lifecycleCaptured,
|
|
69627
69729
|
chunk_count: chunkTotal,
|
|
69730
|
+
project_count: projects.length,
|
|
69731
|
+
membership_count: memberships.length,
|
|
69732
|
+
relation_count: relations.length,
|
|
69733
|
+
projects,
|
|
69734
|
+
memberships,
|
|
69735
|
+
relations,
|
|
69628
69736
|
documents: enriched
|
|
69629
69737
|
};
|
|
69630
69738
|
writeFileSync(dest, JSON.stringify(payload, null, 2), "utf8");
|
|
69631
69739
|
println("");
|
|
69632
69740
|
println(c.green("✓ ") + `Backup written: ${dest}`);
|
|
69633
|
-
println(c.dim(` documents: ${docs.length} · chunks: ${chunkTotal}`));
|
|
69741
|
+
println(c.dim(` documents: ${docs.length} · chunks: ${chunkTotal} · ` + `projects: ${projects.length} · memberships: ${memberships.length}` + (relations.length > 0 ? ` · relations: ${relations.length}` : "")));
|
|
69742
|
+
if (!includeTrash) {
|
|
69743
|
+
println(c.dim(" trashed documents: excluded (--no-trash)"));
|
|
69744
|
+
} else if (trashedCount > 0) {
|
|
69745
|
+
println(c.dim(` of which trashed: ${trashedCount} (restored as trash, not resurrected)`));
|
|
69746
|
+
}
|
|
69634
69747
|
if (options.git) {
|
|
69635
69748
|
println(c.yellow("⚠ ") + "--git commit is not implemented; the snapshot was written without a git checkpoint.");
|
|
69636
69749
|
println(c.dim(" Commit the backup directory yourself if you want it version-controlled."));
|
|
69637
69750
|
}
|
|
69638
69751
|
}
|
|
69639
69752
|
function registerBackup(program2) {
|
|
69640
|
-
program2.command("backup").description("Write a JSON snapshot of the knowledge base.").option("-o, --output-dir <dir>", "Snapshot output directory (default: CEREFOX_BACKUP_DIR or ~/.cerefox/backups).").option("-l, --label <label>", "Optional suffix added to the filename.").option("--include-versions", "Include archived versions in the snapshot. (v0.5: ignored — current chunks only.)").option("--git", "Commit the snapshot to the output dir as a git checkpoint. (v0.5: ignored.)").action(action);
|
|
69753
|
+
program2.command("backup").description("Write a JSON snapshot of the knowledge base.").option("-o, --output-dir <dir>", "Snapshot output directory (default: CEREFOX_BACKUP_DIR or ~/.cerefox/backups).").option("-l, --label <label>", "Optional suffix added to the filename.").option("--include-versions", "Include archived versions in the snapshot. (v0.5: ignored — current chunks only.)").option("--git", "Commit the snapshot to the output dir as a git checkpoint. (v0.5: ignored.)").option("--no-trash", "Exclude soft-deleted documents. Default: they are captured and restored as trash.").action(action);
|
|
69641
69754
|
}
|
|
69642
69755
|
|
|
69643
69756
|
// src/cli/commands/completion.ts
|
|
@@ -69957,6 +70070,10 @@ var CONFIG_KEYS = [
|
|
|
69957
70070
|
key: "min_term_coverage",
|
|
69958
70071
|
description: "0–1 — fraction of a query's meaningful terms a keyword OR-fallback match must cover to count as confident. Default 0.5."
|
|
69959
70072
|
},
|
|
70073
|
+
{
|
|
70074
|
+
key: "relations_enabled",
|
|
70075
|
+
description: "'true'/'false' — expose the document-relation tools to agents. Off by default; the feature is dormant until enabled (iteration 29)."
|
|
70076
|
+
},
|
|
69960
70077
|
{
|
|
69961
70078
|
key: "search_alpha",
|
|
69962
70079
|
description: "0–1 — hybrid fusion weight: 1 = pure semantic, 0 = pure keyword. Default 0.7."
|
|
@@ -75502,8 +75619,8 @@ import { homedir as homedir6 } from "node:os";
|
|
|
75502
75619
|
import { join as join9 } from "node:path";
|
|
75503
75620
|
|
|
75504
75621
|
// ../../_shared/ef-meta/index.ts
|
|
75505
|
-
var EF_VERSION = "1.1.0-beta.
|
|
75506
|
-
var EF_LAST_CHANGED = "1.1.0-beta.
|
|
75622
|
+
var EF_VERSION = "1.1.0-beta.2";
|
|
75623
|
+
var EF_LAST_CHANGED = "1.1.0-beta.2";
|
|
75507
75624
|
|
|
75508
75625
|
// src/cli/util/checks.ts
|
|
75509
75626
|
init_config();
|
|
@@ -75852,7 +75969,7 @@ async function checkContentFormat() {
|
|
|
75852
75969
|
name: CONTENT_FORMAT_CHECK_NAME,
|
|
75853
75970
|
status: "skipped",
|
|
75854
75971
|
detail: `${legacy} of ${total} document(s) use the legacy reconstruction format (format 1).`,
|
|
75855
|
-
hint: "
|
|
75972
|
+
hint: "Harmless — they auto-convert on next edit. To convert them all now: `cerefox server migrate-format` (re-embeds, so try `--dry-run` first). To read what chunk formats are: `cerefox guides show content-format`."
|
|
75856
75973
|
};
|
|
75857
75974
|
} catch (err) {
|
|
75858
75975
|
return {
|
|
@@ -77997,6 +78114,114 @@ function registerReindex(program2) {
|
|
|
77997
78114
|
program2.command("reindex").description("Re-embed existing document chunks (v0.7+).").option("--all", "Reindex every chunk regardless of embedder.").option("--batch <n>", "Chunks per OpenAI batch call. Capped at 96 internally.", "32").option("--dry-run", "Show counts without re-embedding.").option("-i, --document-id <uuid>", "Limit reindex to a single document.").action(action27);
|
|
77998
78115
|
}
|
|
77999
78116
|
|
|
78117
|
+
// src/cli/commands/migrate-format.ts
|
|
78118
|
+
init_cli_core();
|
|
78119
|
+
init_config();
|
|
78120
|
+
init_client();
|
|
78121
|
+
var CURRENT_FORMAT = 2;
|
|
78122
|
+
async function action28(options) {
|
|
78123
|
+
const settings = loadSettings();
|
|
78124
|
+
const client = getClient();
|
|
78125
|
+
const supabase = client.raw;
|
|
78126
|
+
let legacyChunkRows;
|
|
78127
|
+
try {
|
|
78128
|
+
legacyChunkRows = await fetchAllPages((from, to) => {
|
|
78129
|
+
let q = supabase.from("cerefox_chunks").select("document_id").is("version_id", null).lt("content_format", CURRENT_FORMAT);
|
|
78130
|
+
if (options.documentId)
|
|
78131
|
+
q = q.eq("document_id", options.documentId);
|
|
78132
|
+
return q.order("document_id", { ascending: true }).range(from, to);
|
|
78133
|
+
}, 1000);
|
|
78134
|
+
} catch (err) {
|
|
78135
|
+
throw systemError(`Could not list legacy chunks: ${err instanceof Error ? err.message : String(err)}`);
|
|
78136
|
+
}
|
|
78137
|
+
const docIds = [...new Set(legacyChunkRows.map((r) => r.document_id))];
|
|
78138
|
+
const limit = options.limit ? parsePositiveInt(options.limit, "--limit", docIds.length) : docIds.length;
|
|
78139
|
+
const targets = docIds.slice(0, limit);
|
|
78140
|
+
if (targets.length === 0) {
|
|
78141
|
+
println(c.green("✓ Nothing to migrate — every document already uses the current format."));
|
|
78142
|
+
return;
|
|
78143
|
+
}
|
|
78144
|
+
println(c.bold(`${docIds.length} document(s) on the legacy format` + (targets.length < docIds.length ? `; converting ${targets.length} (--limit)` : "")));
|
|
78145
|
+
if (options.dryRun) {
|
|
78146
|
+
println(c.yellow("⚠ --dry-run: nothing was written."));
|
|
78147
|
+
println(c.dim(" Each document would be re-chunked and RE-EMBEDDED (embedding spend)."));
|
|
78148
|
+
return;
|
|
78149
|
+
}
|
|
78150
|
+
println(c.dim("Each document is re-chunked and re-embedded — this costs embedding spend."));
|
|
78151
|
+
println("");
|
|
78152
|
+
const author = resolveAuthor(options.author);
|
|
78153
|
+
const authorType = resolveAuthorType(undefined);
|
|
78154
|
+
const pipeline2 = new IngestionPipeline({
|
|
78155
|
+
supabase,
|
|
78156
|
+
openAiApiKey: settings.openaiApiKey
|
|
78157
|
+
});
|
|
78158
|
+
let converted = 0;
|
|
78159
|
+
let skipped = 0;
|
|
78160
|
+
const duplicates = [];
|
|
78161
|
+
const failures = [];
|
|
78162
|
+
for (let i = 0;i < targets.length; i++) {
|
|
78163
|
+
const id = targets[i];
|
|
78164
|
+
if (process.stdout.isTTY) {
|
|
78165
|
+
process.stderr.write(`\r Converting ${i + 1}/${targets.length}…`);
|
|
78166
|
+
}
|
|
78167
|
+
let doc2 = null;
|
|
78168
|
+
try {
|
|
78169
|
+
const rows = await client.rpc("cerefox_get_document", { p_document_id: id, p_version_id: null });
|
|
78170
|
+
doc2 = rows?.[0] ?? null;
|
|
78171
|
+
} catch (err) {
|
|
78172
|
+
failures.push({ document: id, reason: `read: ${err instanceof Error ? err.message : String(err)}` });
|
|
78173
|
+
continue;
|
|
78174
|
+
}
|
|
78175
|
+
if (!doc2) {
|
|
78176
|
+
failures.push({ document: id, reason: "read: document not found" });
|
|
78177
|
+
continue;
|
|
78178
|
+
}
|
|
78179
|
+
try {
|
|
78180
|
+
await pipeline2.ingestText({
|
|
78181
|
+
text: doc2.full_content,
|
|
78182
|
+
title: doc2.doc_title,
|
|
78183
|
+
documentId: id,
|
|
78184
|
+
source: "migrate-format",
|
|
78185
|
+
author,
|
|
78186
|
+
authorType,
|
|
78187
|
+
expectedContentHash: doc2.content_hash
|
|
78188
|
+
});
|
|
78189
|
+
converted++;
|
|
78190
|
+
} catch (err) {
|
|
78191
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78192
|
+
if (/conflict/i.test(message)) {
|
|
78193
|
+
skipped++;
|
|
78194
|
+
} else if (/identical content already exists/i.test(message)) {
|
|
78195
|
+
duplicates.push({ document: `${doc2.doc_title} (${id})`, reason: message });
|
|
78196
|
+
} else {
|
|
78197
|
+
failures.push({ document: `${doc2.doc_title} (${id})`, reason: message });
|
|
78198
|
+
}
|
|
78199
|
+
}
|
|
78200
|
+
}
|
|
78201
|
+
if (process.stdout.isTTY)
|
|
78202
|
+
process.stderr.write(`
|
|
78203
|
+
`);
|
|
78204
|
+
println("");
|
|
78205
|
+
println(c.bold(`Converted ${converted} · skipped ${skipped} (changed mid-run) · failed ${failures.length}`));
|
|
78206
|
+
if (targets.length < docIds.length) {
|
|
78207
|
+
println(c.dim(` ${docIds.length - targets.length} document(s) still pending — re-run to continue.`));
|
|
78208
|
+
}
|
|
78209
|
+
if (duplicates.length > 0) {
|
|
78210
|
+
println("");
|
|
78211
|
+
println(c.yellow(`⚠ ${duplicates.length} document(s) could not be converted because their content is ` + "byte-identical to another document."));
|
|
78212
|
+
println(c.dim(" Re-ingesting them would collide with the content-hash dedup check. They keep working " + "on the legacy format; de-duplicate them if you want them converted."));
|
|
78213
|
+
printTable(duplicates.map((d) => ({ document: d.document })));
|
|
78214
|
+
}
|
|
78215
|
+
if (failures.length > 0) {
|
|
78216
|
+
println("");
|
|
78217
|
+
printTable(failures);
|
|
78218
|
+
throw systemError(`${failures.length} document(s) failed to convert.`);
|
|
78219
|
+
}
|
|
78220
|
+
}
|
|
78221
|
+
function registerMigrateFormat(program2) {
|
|
78222
|
+
program2.command("migrate-format").description("Convert legacy-format documents to the current chunk format (re-chunks + re-embeds).").option("--dry-run", "Report how many documents would be converted; write nothing.").option("-l, --limit <n>", "Convert at most N documents (re-run to continue).").option("--document-id <uuid>", "Convert a single document.").option("--author <name>", "Recorded in the audit log for each conversion.").action(action28);
|
|
78223
|
+
}
|
|
78224
|
+
|
|
78000
78225
|
// src/cli/commands/restore.ts
|
|
78001
78226
|
init_cli_core();
|
|
78002
78227
|
init_client();
|
|
@@ -78024,7 +78249,7 @@ function resolveBackupFile(target) {
|
|
|
78024
78249
|
}
|
|
78025
78250
|
return join12(path, candidates[0].name);
|
|
78026
78251
|
}
|
|
78027
|
-
async function
|
|
78252
|
+
async function action29(target, options) {
|
|
78028
78253
|
const file = resolveBackupFile(target);
|
|
78029
78254
|
let payload;
|
|
78030
78255
|
try {
|
|
@@ -78036,7 +78261,17 @@ async function action28(target, options) {
|
|
|
78036
78261
|
throw userError(`Backup file is missing "documents" array: ${file}`);
|
|
78037
78262
|
}
|
|
78038
78263
|
println(c.bold(`Restoring from ${file}`));
|
|
78039
|
-
|
|
78264
|
+
const hasMemberships = Array.isArray(payload.memberships);
|
|
78265
|
+
println(c.dim(` cerefox_version: ${payload.cerefox_version ?? "?"} · ` + `schema: ${payload.schema_version ?? "?"} · ` + `documents in file: ${payload.documents.length} · chunks in file: ${payload.chunk_count ?? "?"}`));
|
|
78266
|
+
if (hasMemberships) {
|
|
78267
|
+
println(c.dim(` projects: ${payload.projects?.length ?? 0} · memberships: ${payload.memberships?.length ?? 0}`));
|
|
78268
|
+
} else {
|
|
78269
|
+
warn("This backup predates project-membership capture (format 1) — documents " + "will be restored WITHOUT their project assignments.");
|
|
78270
|
+
}
|
|
78271
|
+
const trashedInFile = payload.documents.filter((d) => d.deleted_at != null).length;
|
|
78272
|
+
if (trashedInFile > 0) {
|
|
78273
|
+
println(c.dim(` trashed documents in file: ${trashedInFile} — restored as trash ` + "(still deleted; recover with `cerefox document restore`)"));
|
|
78274
|
+
}
|
|
78040
78275
|
println("");
|
|
78041
78276
|
const client = getClient();
|
|
78042
78277
|
let restored = 0;
|
|
@@ -78044,7 +78279,7 @@ async function action28(target, options) {
|
|
|
78044
78279
|
let errors4 = 0;
|
|
78045
78280
|
const errorDetails = [];
|
|
78046
78281
|
for (const doc2 of payload.documents) {
|
|
78047
|
-
const { data: existing } = await client.raw.from("cerefox_documents").select("id").eq
|
|
78282
|
+
const { data: existing } = await client.raw.from("cerefox_documents").select("id").or(`id.eq.${doc2.id},content_hash.eq.${doc2.content_hash}`).limit(1).maybeSingle();
|
|
78048
78283
|
if (existing) {
|
|
78049
78284
|
skipped++;
|
|
78050
78285
|
continue;
|
|
@@ -78071,8 +78306,70 @@ async function action28(target, options) {
|
|
|
78071
78306
|
}
|
|
78072
78307
|
restored++;
|
|
78073
78308
|
}
|
|
78309
|
+
let projectsRestored = 0;
|
|
78310
|
+
let membershipsRestored = 0;
|
|
78311
|
+
let relationsRestored = 0;
|
|
78312
|
+
if (!options.dryRun && hasMemberships) {
|
|
78313
|
+
const projects = payload.projects ?? [];
|
|
78314
|
+
if (projects.length > 0) {
|
|
78315
|
+
const { error: projErr } = await client.raw.from("cerefox_projects").upsert(projects, { onConflict: "id", ignoreDuplicates: true });
|
|
78316
|
+
if (projErr) {
|
|
78317
|
+
errors4++;
|
|
78318
|
+
errorDetails.push({ title: "(projects)", error: projErr.message });
|
|
78319
|
+
} else {
|
|
78320
|
+
projectsRestored = projects.length;
|
|
78321
|
+
}
|
|
78322
|
+
}
|
|
78323
|
+
const presentDocIds = new Set;
|
|
78324
|
+
{
|
|
78325
|
+
const ids = (payload.documents ?? []).map((d) => d.id);
|
|
78326
|
+
for (let i = 0;i < ids.length; i += 200) {
|
|
78327
|
+
const { data } = await client.raw.from("cerefox_documents").select("id").in("id", ids.slice(i, i + 200));
|
|
78328
|
+
for (const row of data ?? [])
|
|
78329
|
+
presentDocIds.add(row.id);
|
|
78330
|
+
}
|
|
78331
|
+
}
|
|
78332
|
+
const links = (payload.memberships ?? []).filter((m) => presentDocIds.has(m.document_id));
|
|
78333
|
+
for (let i = 0;i < links.length; i += 500) {
|
|
78334
|
+
const { error: linkErr } = await client.raw.from("cerefox_document_projects").upsert(links.slice(i, i + 500), {
|
|
78335
|
+
onConflict: "document_id,project_id",
|
|
78336
|
+
ignoreDuplicates: true
|
|
78337
|
+
});
|
|
78338
|
+
if (linkErr) {
|
|
78339
|
+
errors4++;
|
|
78340
|
+
errorDetails.push({ title: "(memberships)", error: linkErr.message });
|
|
78341
|
+
break;
|
|
78342
|
+
}
|
|
78343
|
+
membershipsRestored += links.slice(i, i + 500).length;
|
|
78344
|
+
}
|
|
78345
|
+
const relations = (payload.relations ?? []).filter((r) => presentDocIds.has(r.source_id) && presentDocIds.has(r.target_id));
|
|
78346
|
+
if (relations.length > 0) {
|
|
78347
|
+
for (let i = 0;i < relations.length; i += 500) {
|
|
78348
|
+
const { error: relErr } = await client.raw.from("cerefox_document_relations").upsert(relations.slice(i, i + 500), {
|
|
78349
|
+
onConflict: "source_id,target_id,rel_type",
|
|
78350
|
+
ignoreDuplicates: true
|
|
78351
|
+
});
|
|
78352
|
+
if (relErr) {
|
|
78353
|
+
errorDetails.push({ title: "(relations)", error: relErr.message });
|
|
78354
|
+
errors4++;
|
|
78355
|
+
break;
|
|
78356
|
+
}
|
|
78357
|
+
relationsRestored += relations.slice(i, i + 500).length;
|
|
78358
|
+
}
|
|
78359
|
+
const dropped = (payload.relations?.length ?? 0) - relations.length;
|
|
78360
|
+
if (dropped > 0) {
|
|
78361
|
+
warn(`${dropped} relation(s) skipped — one or both documents were not restored.`);
|
|
78362
|
+
}
|
|
78363
|
+
}
|
|
78364
|
+
}
|
|
78074
78365
|
println("");
|
|
78075
78366
|
println((options.dryRun ? c.yellow("(dry-run) ") : "") + c.bold(`Summary: ${restored} restored · ${skipped} skipped · ${errors4} errors`));
|
|
78367
|
+
if (hasMemberships && !options.dryRun) {
|
|
78368
|
+
println(c.dim(` projects: ${projectsRestored} · memberships: ${membershipsRestored}` + (relationsRestored > 0 ? ` · relations: ${relationsRestored}` : "")));
|
|
78369
|
+
}
|
|
78370
|
+
if (trashedInFile > 0) {
|
|
78371
|
+
println(c.dim(` ${trashedInFile} of those are trashed and stay trashed.`));
|
|
78372
|
+
}
|
|
78076
78373
|
if (errors4 > 0) {
|
|
78077
78374
|
println("");
|
|
78078
78375
|
printTable(errorDetails);
|
|
@@ -78080,7 +78377,7 @@ async function action28(target, options) {
|
|
|
78080
78377
|
}
|
|
78081
78378
|
}
|
|
78082
78379
|
function registerRestore(program2) {
|
|
78083
|
-
program2.command("restore").description("Restore a JSON-snapshot backup into the knowledge base.").argument("<snapshot>", "Backup file (or directory; most recent is picked) produced by `cerefox backup`.").option("--dry-run", "Print what would be restored without writing.").option("-p, --project-name <name>", "Reserved for future use; currently ignored
|
|
78380
|
+
program2.command("restore").description("Restore a JSON-snapshot backup into the knowledge base.").argument("<snapshot>", "Backup file (or directory; most recent is picked) produced by `cerefox backup`.").option("--dry-run", "Print what would be restored without writing.").option("-p, --project-name <name>", "Reserved for future use; currently ignored. Project memberships are restored from the backup itself (format 2+).").action(action29);
|
|
78084
78381
|
}
|
|
78085
78382
|
|
|
78086
78383
|
// src/cli/commands/search.ts
|
|
@@ -78105,7 +78402,7 @@ async function embedQuery(query) {
|
|
|
78105
78402
|
}
|
|
78106
78403
|
|
|
78107
78404
|
// src/cli/commands/search.ts
|
|
78108
|
-
async function
|
|
78405
|
+
async function action30(query, options) {
|
|
78109
78406
|
if (!query || query.trim() === "") {
|
|
78110
78407
|
throw userError("Empty query.");
|
|
78111
78408
|
}
|
|
@@ -78271,7 +78568,7 @@ async function action29(query, options) {
|
|
|
78271
78568
|
}
|
|
78272
78569
|
}
|
|
78273
78570
|
function registerSearch(program2) {
|
|
78274
|
-
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: CEREFOX_SEARCH_ALPHA; else 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("--min-term-coverage <float>", "OR-fallback keyword matches must cover at least this fraction of the query's meaningful terms to count as confident hits (default: CEREFOX_MIN_TERM_COVERAGE; else the server default 0.5; needs schema ≥ 0.9.1).").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(
|
|
78571
|
+
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: CEREFOX_SEARCH_ALPHA; else 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("--min-term-coverage <float>", "OR-fallback keyword matches must cover at least this fraction of the query's meaningful terms to count as confident hits (default: CEREFOX_MIN_TERM_COVERAGE; else the server default 0.5; needs schema ≥ 0.9.1).").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(action30);
|
|
78275
78572
|
}
|
|
78276
78573
|
|
|
78277
78574
|
// src/cli/commands/self-update.ts
|
|
@@ -78318,7 +78615,7 @@ async function fetchLatestVersion() {
|
|
|
78318
78615
|
}
|
|
78319
78616
|
return body.version;
|
|
78320
78617
|
}
|
|
78321
|
-
async function
|
|
78618
|
+
async function action31(options) {
|
|
78322
78619
|
let target;
|
|
78323
78620
|
try {
|
|
78324
78621
|
target = options.version ?? await fetchLatestVersion();
|
|
@@ -78371,7 +78668,7 @@ async function action30(options) {
|
|
|
78371
78668
|
}
|
|
78372
78669
|
function registerSelfUpdate(program2) {
|
|
78373
78670
|
const desc = "Upgrade Cerefox in place. Alias: `cerefox upgrade`.";
|
|
78374
|
-
const declaration = (cmd) => cmd.description(desc).option("--check", "Print current vs latest; do nothing.").option("--yes", "Non-interactive (skip confirmation).").option("--version <version>", "Pin a specific version (e.g. 0.5.1 or 0.6.0-rc.1).").action(
|
|
78671
|
+
const declaration = (cmd) => cmd.description(desc).option("--check", "Print current vs latest; do nothing.").option("--yes", "Non-interactive (skip confirmation).").option("--version <version>", "Pin a specific version (e.g. 0.5.1 or 0.6.0-rc.1).").action(action31);
|
|
78375
78672
|
declaration(program2.command("self-update"));
|
|
78376
78673
|
declaration(program2.command("upgrade"));
|
|
78377
78674
|
}
|
|
@@ -78390,7 +78687,7 @@ function symbol2(status) {
|
|
|
78390
78687
|
return cErr.dim("ℹ");
|
|
78391
78688
|
}
|
|
78392
78689
|
}
|
|
78393
|
-
async function
|
|
78690
|
+
async function action32(options) {
|
|
78394
78691
|
const useSpinner = !options.json && process.stderr.isTTY;
|
|
78395
78692
|
const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
|
|
78396
78693
|
const results = await runFastChecks({
|
|
@@ -78411,7 +78708,7 @@ async function action31(options) {
|
|
|
78411
78708
|
}
|
|
78412
78709
|
}
|
|
78413
78710
|
function registerStatus(program2) {
|
|
78414
|
-
program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(
|
|
78711
|
+
program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action32);
|
|
78415
78712
|
}
|
|
78416
78713
|
|
|
78417
78714
|
// src/cli/commands/token.ts
|
|
@@ -78444,10 +78741,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
|
|
|
78444
78741
|
}
|
|
78445
78742
|
const re = new RegExp(`^(\\s*)${escapeRegExp(key)}=.*$`, "m");
|
|
78446
78743
|
let next;
|
|
78447
|
-
let
|
|
78744
|
+
let action33;
|
|
78448
78745
|
if (re.test(original)) {
|
|
78449
78746
|
next = original.replace(re, `$1${line}`);
|
|
78450
|
-
|
|
78747
|
+
action33 = "updated";
|
|
78451
78748
|
} else {
|
|
78452
78749
|
const base = original.endsWith(`
|
|
78453
78750
|
`) ? original : `${original}
|
|
@@ -78455,10 +78752,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
|
|
|
78455
78752
|
next = `${base}
|
|
78456
78753
|
${header}${line}
|
|
78457
78754
|
`;
|
|
78458
|
-
|
|
78755
|
+
action33 = "added";
|
|
78459
78756
|
}
|
|
78460
78757
|
writeFileSync5(path, next);
|
|
78461
|
-
return { path, action:
|
|
78758
|
+
return { path, action: action33, backupPath };
|
|
78462
78759
|
}
|
|
78463
78760
|
function readEnvVar(path, key) {
|
|
78464
78761
|
if (!existsSync14(path))
|
|
@@ -83707,7 +84004,13 @@ import {
|
|
|
83707
84004
|
} from "node:fs";
|
|
83708
84005
|
import { homedir as homedir9 } from "node:os";
|
|
83709
84006
|
import { join as join18 } from "node:path";
|
|
83710
|
-
|
|
84007
|
+
function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home = homedir9()) {
|
|
84008
|
+
override = (override ?? "").trim();
|
|
84009
|
+
if (!override)
|
|
84010
|
+
return join18(home, ".cerefox");
|
|
84011
|
+
return override === "~" || override.startsWith("~/") ? join18(home, override.slice(2)) : override;
|
|
84012
|
+
}
|
|
84013
|
+
var STATE_DIR = resolveStateDir();
|
|
83711
84014
|
var PID_FILE = join18(STATE_DIR, "web.pid");
|
|
83712
84015
|
var LOG_FILE = join18(STATE_DIR, "web.log");
|
|
83713
84016
|
var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
|
|
@@ -84075,6 +84378,7 @@ Learn more:
|
|
|
84075
84378
|
const server = program2.command("server").description("Server side: deploy, reindex.");
|
|
84076
84379
|
moveInto(server, registerDeployServer, "deploy");
|
|
84077
84380
|
moveInto(server, registerReindex, "reindex");
|
|
84381
|
+
registerMigrateFormat(server);
|
|
84078
84382
|
const guides = program2.command("guides").description("Bundled docs: list, open, show, ingest (into the KB).");
|
|
84079
84383
|
registerGuides(guides);
|
|
84080
84384
|
moveInto(guides, registerSyncSelfDocs, "ingest");
|