@cerefox/memory 1.21.0 → 1.22.1
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 +180 -123
- package/dist/frontend/assets/{docx-DeQjAnSd.js → docx-BvP4Jo4k.js} +2 -2
- package/dist/frontend/assets/{docx-DeQjAnSd.js.map → docx-BvP4Jo4k.js.map} +1 -1
- package/dist/frontend/assets/{index-Dd1nIOAB.js → index-nwj65c23.js} +31 -31
- package/dist/frontend/assets/{index-Dd1nIOAB.js.map → index-nwj65c23.js.map} +1 -1
- package/dist/frontend/assets/{markdown-CsTKTR78.js → markdown-C3Pl9aLl.js} +2 -2
- package/dist/frontend/assets/{markdown-CsTKTR78.js.map → markdown-C3Pl9aLl.js.map} +1 -1
- package/dist/frontend/assets/{pdf-CkRjSAWX.js → pdf-nplQ05zY.js} +2 -2
- package/dist/frontend/assets/{pdf-CkRjSAWX.js.map → pdf-nplQ05zY.js.map} +1 -1
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +3 -3
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +1 -0
- package/dist/server-assets/_shared/mcp-tools/audit-ops.ts +23 -2
- package/docs/api/openapi.json +5 -1
- package/docs/guides/api.md +1 -1
- package/docs/guides/configuration.md +5 -2
- package/docs/guides/connect-agents.md +4 -0
- package/docs/guides/securing-local-access.md +17 -0
- package/docs/guides/setup-lan.md +165 -9
- package/docs/guides/setup-local.md +11 -0
- package/docs/guides/staging-env.md +12 -2
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{n as e,r as t,t as n}from"./index-
|
|
2
|
-
//# sourceMappingURL=markdown-
|
|
1
|
+
import{n as e,r as t,t as n}from"./index-nwj65c23.js";function r(r){let a=t(r,{extensions:[n()],mdastExtensions:[e()]});return i(a),a}function i(e){let t=new Map,n=e=>{if(e.type===`definition`&&!t.has(e.identifier.toLowerCase())&&t.set(e.identifier.toLowerCase(),e.url),`children`in e)for(let t of e.children)n(t)};n(e);let r=e=>{if(!(`children`in e))return;let n=e.children;n.forEach((e,i)=>{e.type===`linkReference`?n[i]={type:`link`,url:t.get(e.identifier.toLowerCase())??``,children:e.children}:e.type===`imageReference`&&(n[i]={type:`image`,url:t.get(e.identifier.toLowerCase())??``,alt:e.alt}),r(n[i])})};r(e)}function a(e){return/^(https?:|mailto:)/i.test(e.trim())?e.trim():null}function o(e,t){return`${e.replace(/\.(md|markdown|txt)$/i,``)||`document`}.${t}`}export{a as exportableUrl,r as parseMarkdown,o as withExtension};
|
|
2
|
+
//# sourceMappingURL=markdown-C3Pl9aLl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-
|
|
1
|
+
{"version":3,"file":"markdown-C3Pl9aLl.js","names":["gfmFromMarkdown","fromMarkdown","gfm","parseMarkdown","root","markdown","resolveReferences","urls","collect","node","child","rewrite","children","exportableUrl","url","withExtension","filename","ext"],"sources":["../../src/lib/export/markdown.ts"],"sourcesContent":["/**\n * Markdown → syntax tree, shared by the Word and PDF exports.\n *\n * The same parser family the page renders with (react-markdown + remark-gfm),\n * so a document exports with the structure it shows: GFM tables, task lists,\n * strikethrough, autolinks. Raw HTML is kept as text, never interpreted.\n */\n\nimport type { Nodes, Root } from \"mdast\";\nimport { fromMarkdown } from \"mdast-util-from-markdown\";\nimport { gfmFromMarkdown } from \"mdast-util-gfm\";\nimport { gfm } from \"micromark-extension-gfm\";\n\nexport function parseMarkdown(markdown: string): Root {\n const root = fromMarkdown(markdown, {\n extensions: [gfm()],\n mdastExtensions: [gfmFromMarkdown()],\n });\n resolveReferences(root);\n return root;\n}\n\n/**\n * Turn reference-style links and images (`[text][id]`, `![alt][id]` with a\n * `[id]: url` definition elsewhere) into plain links and images, so the\n * converters handle one form and neither the target nor the alt text is lost.\n */\nfunction resolveReferences(root: Root): void {\n const urls = new Map<string, string>();\n const collect = (node: Nodes) => {\n // The first definition of a label wins, as in CommonMark.\n if (node.type === \"definition\" && !urls.has(node.identifier.toLowerCase())) {\n urls.set(node.identifier.toLowerCase(), node.url);\n }\n if (\"children\" in node) for (const child of node.children) collect(child);\n };\n collect(root);\n\n const rewrite = (node: Nodes) => {\n if (!(\"children\" in node)) return;\n const children = node.children as Nodes[];\n children.forEach((child, i) => {\n if (child.type === \"linkReference\") {\n children[i] = { type: \"link\", url: urls.get(child.identifier.toLowerCase()) ?? \"\", children: child.children };\n } else if (child.type === \"imageReference\") {\n children[i] = { type: \"image\", url: urls.get(child.identifier.toLowerCase()) ?? \"\", alt: child.alt };\n }\n rewrite(children[i]);\n });\n };\n rewrite(root);\n}\n\n/**\n * The link targets an export may keep: absolute web and mail links. A relative\n * link (an `/api/v1/…` path, a `#anchor`) points at nothing once the file\n * leaves this server, and a `javascript:` link has no business in a document.\n */\nexport function exportableUrl(url: string): string | null {\n return /^(https?:|mailto:)/i.test(url.trim()) ? url.trim() : null;\n}\n\n/** `notes.md` → `notes.docx`; a name with no extension gets one. */\nexport function withExtension(filename: string, ext: string): string {\n const stem = filename.replace(/\\.(md|markdown|txt)$/i, \"\");\n return `${stem || \"document\"}.${ext}`;\n}\n"],"mappings":"A,sDAaA,SAAgBG,EAAc,EAAwB,CACpD,IAAMC,EAAOH,EAAaI,EAAU,CAClC,WAAY,CAACH,EAAI,CAAC,EAClB,gBAAiB,CAACF,EAAgB,CAAC,CACrC,CAAC,EAED,OADAM,EAAkBF,CAAI,EACfA,CACT,CAOA,SAASE,EAAkB,EAAkB,CAC3C,IAAMC,EAAO,IAAI,IACXC,EAAW,GAAgB,CAK/B,GAHIC,EAAK,OAAS,cAAgB,CAACF,EAAK,IAAIE,EAAK,WAAW,YAAY,CAAC,GACvEF,EAAK,IAAIE,EAAK,WAAW,YAAY,EAAGA,EAAK,GAAG,EAE9C,aAAcA,EAAM,IAAK,IAAMC,KAASD,EAAK,SAAUD,EAAQE,CAAK,CAC1E,EACAF,EAAQJ,CAAI,EAEZ,IAAMO,EAAW,GAAgB,CAC/B,GAAI,EAAE,aAAcF,GAAO,OAC3B,IAAMG,EAAWH,EAAK,SACtBG,EAAS,SAAS,EAAO,IAAM,CACzBF,EAAM,OAAS,gBACjB,EAAS,GAAK,CAAE,KAAM,OAAQ,IAAKH,EAAK,IAAIG,EAAM,WAAW,YAAY,CAAC,GAAK,GAAI,SAAUA,EAAM,QAAS,EACnGA,EAAM,OAAS,mBACxB,EAAS,GAAK,CAAE,KAAM,QAAS,IAAKH,EAAK,IAAIG,EAAM,WAAW,YAAY,CAAC,GAAK,GAAI,IAAKA,EAAM,GAAI,GAErGC,EAAQC,EAAS,EAAE,CACrB,CAAC,CACH,EACAD,EAAQP,CAAI,CACd,CAOA,SAAgBS,EAAc,EAA4B,CACxD,MAAO,sBAAsB,KAAKC,EAAI,KAAK,CAAC,EAAIA,EAAI,KAAK,EAAI,IAC/D,CAGA,SAAgBC,EAAc,EAAkB,EAAqB,CAEnE,MAAO,GADMC,EAAS,QAAQ,wBAAyB,EAC7C,GAAQ,WAAW,GAAGC,GAClC,C"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/pdfmake-L7DGtDcB.js","assets/rolldown-runtime-COiEfhsA.js","assets/vfs_fonts-D9BViC6C.js","assets/Courier-BRJ-B_ND.js"])))=>i.map(i=>d[i]);
|
|
2
|
-
import{i as e}from"./rolldown-runtime-COiEfhsA.js";import{i as t}from"./index-
|
|
2
|
+
import{i as e}from"./rolldown-runtime-COiEfhsA.js";import{i as t}from"./index-nwj65c23.js";import{exportableUrl as n,parseMarkdown as r}from"./markdown-C3Pl9aLl.js";var i=`#f2f2f2`,a=515,o=16,s=24;function c(e,t){let n=499-t;return Math.max(16,Math.floor(n/(u(e).font?5.5:7.8)))}function l(e,t){return e.split(`
|
|
3
3
|
`).map(e=>{let n=Array.from(e);if(n.length<=t)return e;let r=[];for(let e=0;e<n.length;e+=t)r.push(n.slice(e,e+t).join(``));return r.join(`
|
|
4
4
|
`)}).join(`
|
|
5
5
|
`)}function u(e){return/^[\t\n\r\x20-\x7e\xa0-\xff]*$/.test(e)?{font:`Courier`}:{}}var d=`#bbbbbb`;function f(e,t={}){let r=[];for(let a of e)switch(a.type){case`text`:r.push({text:a.value.replace(/\n/g,` `),...t});break;case`strong`:r.push(...f(a.children,{...t,bold:!0}));break;case`emphasis`:r.push(...f(a.children,{...t,italics:!0}));break;case`delete`:r.push(...f(a.children,{...t,decoration:`lineThrough`}));break;case`inlineCode`:r.push({text:a.value,...t,...u(a.value),background:i,fontSize:9.5,preserveLeadingSpaces:!0});break;case`break`:r.push({text:`
|
|
6
6
|
`});break;case`link`:{let e=n(a.url),i=f(a.children,t);e?r.push({text:i.length?i:e,link:e,color:`#1a5fb4`,decoration:`underline`}):r.push(...i);break}case`image`:r.push({text:`[image${a.alt?`: ${a.alt}`:``}]`,...t,italics:!0});break;case`html`:r.push({text:a.value,...t});break;case`footnoteReference`:r.push({text:`[^${a.identifier}]`,...t,sup:!0});break;default:`children`in a?r.push(...f(a.children,t)):`value`in a&&r.push({text:String(a.value),...t})}return r}function p(e,t=0){let n=[];for(let r of e)switch(r.type){case`heading`:n.push({text:f(r.children),style:`h${r.depth}`});break;case`paragraph`:n.push({text:f(r.children),style:`p`});break;case`blockquote`:n.push({table:{widths:[`*`],body:[[{stack:p(r.children,t+o),italics:!0,color:`#555555`}]]},layout:{hLineWidth:()=>0,vLineWidth:e=>e===0?2:0,vLineColor:()=>d,paddingLeft:()=>10},margin:[0,2,0,8]});break;case`code`:n.push({table:{widths:[`*`],body:[[{text:l(r.value,c(r.value,t)),...u(r.value),fontSize:9,preserveLeadingSpaces:!0,lineHeight:1.3}]]},layout:{hLineWidth:()=>0,vLineWidth:()=>0,fillColor:()=>i,paddingLeft:()=>8,paddingRight:()=>8,paddingTop:()=>6,paddingBottom:()=>6},margin:[0,2,0,8]});break;case`list`:n.push(m(r,t));break;case`table`:n.push(h(r));break;case`thematicBreak`:n.push({canvas:[{type:`line`,x1:0,y1:0,x2:a-t,y2:0,lineWidth:.5,lineColor:d}],margin:[0,6,0,10]});break;case`html`:n.push({text:r.value,style:`p`,fontSize:9,preserveLeadingSpaces:!0});break;case`footnoteDefinition`:n.push({stack:[{text:`[^${r.identifier}]`,sup:!0},...p(r.children,t)],fontSize:9})}return n}function m(e,t){let n=e.children.map(e=>{let n=e.checked===!0?`[x] `:e.checked===!1?`[ ] `:``,[r,...i]=e.children,a=r&&r.type===`paragraph`?{text:[n,...f(r.children)],margin:[0,0,0,2]}:{text:n},o=t+s,c=r&&r.type===`paragraph`?p(i,o):p(e.children,o);return c.length?{stack:[a,...c]}:a});return e.ordered?{ol:n,start:e.start??1,margin:[0,0,0,8]}:{ul:n,margin:[0,0,0,8]}}function h(e){let t=Math.max(1,...e.children.map(e=>e.children.length)),n=e.align??[],r=e=>n[e]===`center`?`center`:n[e]===`right`?`right`:`left`;return{table:{headerRows:1,widths:Array.from({length:t},()=>`*`),body:e.children.map((e,n)=>Array.from({length:t},(t,i)=>{let a=e.children[i];return{text:a?f(a.children,n===0?{bold:!0}:{}):``,alignment:r(i),fillColor:n===0?`#ededed`:void 0}}))},layout:`lightHorizontalLines`,margin:[0,2,0,10]}}function g(e,t){let n=p(r(e).children);return{info:{title:t,creator:`Cerefox`,producer:`Cerefox`},pageSize:`A4`,pageMargins:[40,48,40,48],content:n.length?n:[{text:``}],defaultStyle:{font:`Roboto`,fontSize:10.5,lineHeight:1.25},styles:{p:{margin:[0,0,0,7]},h1:{fontSize:20,bold:!0,margin:[0,10,0,8]},h2:{fontSize:16,bold:!0,margin:[0,10,0,6]},h3:{fontSize:13.5,bold:!0,margin:[0,8,0,5]},h4:{fontSize:12,bold:!0,margin:[0,6,0,4]},h5:{fontSize:11,bold:!0,margin:[0,6,0,4]},h6:{fontSize:10.5,bold:!0,italics:!0,margin:[0,6,0,4]}},footer:(e,t)=>({text:`${e} / ${t}`,alignment:`center`,fontSize:8,color:`#888888`,margin:[0,16,0,0]})}}async function _(n,r){let[{default:i},{default:a},{default:o}]=await Promise.all([t(()=>import(`./pdfmake-L7DGtDcB.js`).then(t=>e(t.default,1)),__vite__mapDeps([0,1])),t(()=>import(`./vfs_fonts-D9BViC6C.js`).then(t=>e(t.default,1)),__vite__mapDeps([2,1])),t(()=>import(`./Courier-BRJ-B_ND.js`).then(t=>e(t.default,1)),__vite__mapDeps([3,1]))]);return i.addVirtualFileSystem(a),i.addFontContainer(o),i.setFonts({Roboto:{normal:`Roboto-Regular.ttf`,bold:`Roboto-Medium.ttf`,italics:`Roboto-Italic.ttf`,bolditalics:`Roboto-MediumItalic.ttf`},Courier:{normal:`Courier`,bold:`Courier-Bold`,italics:`Courier-Oblique`,bolditalics:`Courier-BoldOblique`}}),i.createPdf(g(n,r)).getBlob()}export{_ as markdownToPdf};
|
|
7
|
-
//# sourceMappingURL=pdf-
|
|
7
|
+
//# sourceMappingURL=pdf-nplQ05zY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"mappings":"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","names":["CODE_FILL","CONTENT_WIDTH","QUOTE_INSET","LIST_INSET","codeColumns","width","inset","codeFont","text","wrapCode","chars","line","columns","parts","i","RULE","inline","out","node","nodes","marks","url","exportableUrl","children","blocks","list","table","items","marker","item","first","rest","head","nested","tail","row","align","alignment","cell","r","markdownToPdfDefinition","content","parseMarkdown","markdown","page","pages","markdownToPdf","pdfMake","vfs","courier","__vitePreload","__toESM","m","title"],"ignoreList":[],"sources":["../../src/lib/export/pdf.ts"],"sourcesContent":["/**\n * Markdown → PDF, in the browser, with pdfmake.\n *\n * Loaded on demand (`api/documents.ts` imports it dynamically): pdfmake and its\n * embedded Roboto font are about 2 MB, which the app does not pay on first\n * load. The definition builder ({@link markdownToPdfDefinition}) is a pure\n * function, tested without a browser; only {@link markdownToPdf} needs pdfmake.\n *\n * Roboto covers Latin, Greek and Cyrillic. Characters outside it (CJK, emoji)\n * print as blanks: the Word export keeps them, since Word picks its own fonts.\n * Code is set in Courier, one of the PDF standard fonts (nothing embedded),\n * when it fits Courier's Latin-1 range; anything else stays in Roboto, so a\n * Greek comment or an arrow in a code block still prints.\n */\n\nimport type { Content, ContentText, TDocumentDefinitions } from \"pdfmake/interfaces\";\nimport type { List, PhrasingContent, RootContent, Table as MdTable } from \"mdast\";\n\nimport { exportableUrl, parseMarkdown } from \"./markdown\";\n\nconst CODE_FILL = \"#f2f2f2\";\n\n/** A4 width minus the page margins, in points. */\nconst CONTENT_WIDTH = 515;\n/** What a blockquote's bar and padding take from the width inside it. */\nconst QUOTE_INSET = 16;\n/** What a list's marker column takes from the width of its nested blocks. */\nconst LIST_INSET = 24;\n\n/**\n * How many characters of 9 pt code fit on a line `inset` points in from the\n * margin: Courier is 5.4 pt per character (5.5 for slack); for Roboto, 7.8 is\n * about its widest common glyph, so the wrap is conservative, never short.\n */\nexport function codeColumns(text: string, inset: number): number {\n const width = CONTENT_WIDTH - 16 - inset; // 16: the code block's own padding\n return Math.max(16, Math.floor(width / (codeFont(text).font ? 5.5 : 7.8)));\n}\n\n/**\n * Hard-wrap code lines that cannot fit. pdfmake breaks lines only at spaces,\n * so a long unbroken token (a hash, a URL, minified code) ran off the page\n * and was clipped; {@link codeColumns} gives the width at that nesting depth.\n */\nexport function wrapCode(text: string, columns: number): string {\n return text\n .split(\"\\n\")\n .map((line) => {\n const chars = Array.from(line);\n if (chars.length <= columns) return line;\n const parts: string[] = [];\n for (let i = 0; i < chars.length; i += columns) parts.push(chars.slice(i, i + columns).join(\"\"));\n return parts.join(\"\\n\");\n })\n .join(\"\\n\");\n}\n\n/** Courier when every character is in its Latin-1 range; otherwise Roboto. */\nexport function codeFont(text: string): { font?: string } {\n return /^[\\t\\n\\r\\x20-\\x7e\\xa0-\\xff]*$/.test(text) ? { font: \"Courier\" } : {};\n}\nconst RULE = \"#bbbbbb\";\n\ninterface Marks {\n bold?: boolean;\n italics?: boolean;\n decoration?: \"lineThrough\";\n}\n\ntype Inline = string | ContentText;\n\nfunction inline(nodes: PhrasingContent[], marks: Marks = {}): Inline[] {\n const out: Inline[] = [];\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n // A newline inside a paragraph is a soft break: a space, not a new line.\n out.push({ text: node.value.replace(/\\n/g, \" \"), ...marks });\n break;\n case \"strong\":\n out.push(...inline(node.children, { ...marks, bold: true }));\n break;\n case \"emphasis\":\n out.push(...inline(node.children, { ...marks, italics: true }));\n break;\n case \"delete\":\n out.push(...inline(node.children, { ...marks, decoration: \"lineThrough\" }));\n break;\n case \"inlineCode\":\n out.push({ text: node.value, ...marks, ...codeFont(node.value), background: CODE_FILL, fontSize: 9.5, preserveLeadingSpaces: true });\n break;\n case \"break\":\n out.push({ text: \"\\n\" });\n break;\n case \"link\": {\n const url = exportableUrl(node.url);\n const children = inline(node.children, marks);\n if (url) {\n out.push({ text: children.length ? children : url, link: url, color: \"#1a5fb4\", decoration: \"underline\" });\n } else {\n out.push(...children);\n }\n break;\n }\n case \"image\":\n out.push({ text: `[image${node.alt ? `: ${node.alt}` : \"\"}]`, ...marks, italics: true });\n break;\n case \"html\":\n out.push({ text: node.value, ...marks });\n break;\n case \"footnoteReference\":\n out.push({ text: `[^${node.identifier}]`, ...marks, sup: true });\n break;\n default:\n if (\"children\" in node) out.push(...inline(node.children as PhrasingContent[], marks));\n else if (\"value\" in node) out.push({ text: String(node.value), ...marks });\n }\n }\n return out;\n}\n\n/** `inset`: points already taken from the left by enclosing quotes and lists. */\nfunction blocks(nodes: RootContent[], inset = 0): Content[] {\n const out: Content[] = [];\n for (const node of nodes) {\n switch (node.type) {\n case \"heading\":\n out.push({ text: inline(node.children), style: `h${node.depth}` });\n break;\n case \"paragraph\":\n out.push({ text: inline(node.children), style: \"p\" });\n break;\n case \"blockquote\":\n out.push({\n table: {\n widths: [\"*\"],\n body: [[{ stack: blocks(node.children, inset + QUOTE_INSET), italics: true, color: \"#555555\" }]],\n },\n layout: {\n hLineWidth: () => 0,\n vLineWidth: (i: number) => (i === 0 ? 2 : 0),\n vLineColor: () => RULE,\n paddingLeft: () => 10,\n },\n margin: [0, 2, 0, 8],\n });\n break;\n case \"code\":\n out.push({\n table: {\n widths: [\"*\"],\n body: [\n [\n {\n text: wrapCode(node.value, codeColumns(node.value, inset)),\n ...codeFont(node.value),\n fontSize: 9,\n preserveLeadingSpaces: true,\n lineHeight: 1.3,\n },\n ],\n ],\n },\n layout: {\n hLineWidth: () => 0,\n vLineWidth: () => 0,\n fillColor: () => CODE_FILL,\n paddingLeft: () => 8,\n paddingRight: () => 8,\n paddingTop: () => 6,\n paddingBottom: () => 6,\n },\n margin: [0, 2, 0, 8],\n });\n break;\n case \"list\":\n out.push(list(node, inset));\n break;\n case \"table\":\n out.push(table(node));\n break;\n case \"thematicBreak\":\n out.push({\n canvas: [{ type: \"line\", x1: 0, y1: 0, x2: CONTENT_WIDTH - inset, y2: 0, lineWidth: 0.5, lineColor: RULE }],\n margin: [0, 6, 0, 10],\n });\n break;\n case \"html\":\n out.push({ text: node.value, style: \"p\", fontSize: 9, preserveLeadingSpaces: true });\n break;\n case \"footnoteDefinition\":\n out.push({ stack: [{ text: `[^${node.identifier}]`, sup: true }, ...blocks(node.children, inset)], fontSize: 9 });\n break;\n default:\n break;\n }\n }\n return out;\n}\n\nfunction list(node: List, inset: number): Content {\n const items: Content[] = node.children.map((item) => {\n const marker = item.checked === true ? \"[x] \" : item.checked === false ? \"[ ] \" : \"\";\n const [first, ...rest] = item.children;\n const head: Content =\n first && first.type === \"paragraph\"\n ? { text: [marker, ...inline(first.children)], margin: [0, 0, 0, 2] }\n : { text: marker };\n const nested = inset + LIST_INSET;\n const tail = first && first.type === \"paragraph\" ? blocks(rest, nested) : blocks(item.children, nested);\n return tail.length ? { stack: [head, ...tail] } : head;\n });\n return node.ordered\n ? { ol: items, start: node.start ?? 1, margin: [0, 0, 0, 8] }\n : { ul: items, margin: [0, 0, 0, 8] };\n}\n\nfunction table(node: MdTable): Content {\n const columns = Math.max(1, ...node.children.map((row) => row.children.length));\n const align = node.align ?? [];\n const alignment = (i: number) => (align[i] === \"center\" ? \"center\" : align[i] === \"right\" ? \"right\" : \"left\");\n return {\n table: {\n headerRows: 1,\n widths: Array.from({ length: columns }, () => \"*\"),\n body: node.children.map((row, r) =>\n Array.from({ length: columns }, (_, i) => {\n const cell = row.children[i];\n return {\n text: cell ? inline(cell.children, r === 0 ? { bold: true } : {}) : \"\",\n alignment: alignment(i),\n fillColor: r === 0 ? \"#ededed\" : undefined,\n };\n }),\n ),\n },\n layout: \"lightHorizontalLines\",\n margin: [0, 2, 0, 10],\n };\n}\n\nexport function markdownToPdfDefinition(markdown: string, title: string): TDocumentDefinitions {\n const content = blocks(parseMarkdown(markdown).children);\n return {\n info: { title, creator: \"Cerefox\", producer: \"Cerefox\" },\n pageSize: \"A4\",\n pageMargins: [40, 48, 40, 48],\n content: content.length ? content : [{ text: \"\" }],\n defaultStyle: { font: \"Roboto\", fontSize: 10.5, lineHeight: 1.25 },\n styles: {\n p: { margin: [0, 0, 0, 7] },\n h1: { fontSize: 20, bold: true, margin: [0, 10, 0, 8] },\n h2: { fontSize: 16, bold: true, margin: [0, 10, 0, 6] },\n h3: { fontSize: 13.5, bold: true, margin: [0, 8, 0, 5] },\n h4: { fontSize: 12, bold: true, margin: [0, 6, 0, 4] },\n h5: { fontSize: 11, bold: true, margin: [0, 6, 0, 4] },\n h6: { fontSize: 10.5, bold: true, italics: true, margin: [0, 6, 0, 4] },\n },\n footer: (page: number, pages: number) => ({\n text: `${page} / ${pages}`,\n alignment: \"center\",\n fontSize: 8,\n color: \"#888888\",\n margin: [0, 16, 0, 0],\n }),\n };\n}\n\nexport async function markdownToPdf(markdown: string, title: string): Promise<Blob> {\n const [{ default: pdfMake }, { default: vfs }, { default: courier }] = await Promise.all([\n import(\"pdfmake/build/pdfmake\"),\n import(\"pdfmake/build/vfs_fonts\"),\n import(\"pdfmake/build/standard-fonts/Courier\"),\n ]);\n pdfMake.addVirtualFileSystem(vfs);\n pdfMake.addFontContainer(courier);\n pdfMake.setFonts({\n Roboto: {\n normal: \"Roboto-Regular.ttf\",\n bold: \"Roboto-Medium.ttf\",\n italics: \"Roboto-Italic.ttf\",\n bolditalics: \"Roboto-MediumItalic.ttf\",\n },\n Courier: {\n normal: \"Courier\",\n bold: \"Courier-Bold\",\n italics: \"Courier-Oblique\",\n bolditalics: \"Courier-BoldOblique\",\n },\n });\n return pdfMake.createPdf(markdownToPdfDefinition(markdown, title)).getBlob();\n}\n"],"file":"pdf-CkRjSAWX.js"}
|
|
1
|
+
{"version":3,"mappings":"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","names":["CODE_FILL","CONTENT_WIDTH","QUOTE_INSET","LIST_INSET","codeColumns","width","inset","codeFont","text","wrapCode","chars","line","columns","parts","i","RULE","inline","out","node","nodes","marks","url","exportableUrl","children","blocks","list","table","items","marker","item","first","rest","head","nested","tail","row","align","alignment","cell","r","markdownToPdfDefinition","content","parseMarkdown","markdown","page","pages","markdownToPdf","pdfMake","vfs","courier","__vitePreload","__toESM","m","title"],"ignoreList":[],"sources":["../../src/lib/export/pdf.ts"],"sourcesContent":["/**\n * Markdown → PDF, in the browser, with pdfmake.\n *\n * Loaded on demand (`api/documents.ts` imports it dynamically): pdfmake and its\n * embedded Roboto font are about 2 MB, which the app does not pay on first\n * load. The definition builder ({@link markdownToPdfDefinition}) is a pure\n * function, tested without a browser; only {@link markdownToPdf} needs pdfmake.\n *\n * Roboto covers Latin, Greek and Cyrillic. Characters outside it (CJK, emoji)\n * print as blanks: the Word export keeps them, since Word picks its own fonts.\n * Code is set in Courier, one of the PDF standard fonts (nothing embedded),\n * when it fits Courier's Latin-1 range; anything else stays in Roboto, so a\n * Greek comment or an arrow in a code block still prints.\n */\n\nimport type { Content, ContentText, TDocumentDefinitions } from \"pdfmake/interfaces\";\nimport type { List, PhrasingContent, RootContent, Table as MdTable } from \"mdast\";\n\nimport { exportableUrl, parseMarkdown } from \"./markdown\";\n\nconst CODE_FILL = \"#f2f2f2\";\n\n/** A4 width minus the page margins, in points. */\nconst CONTENT_WIDTH = 515;\n/** What a blockquote's bar and padding take from the width inside it. */\nconst QUOTE_INSET = 16;\n/** What a list's marker column takes from the width of its nested blocks. */\nconst LIST_INSET = 24;\n\n/**\n * How many characters of 9 pt code fit on a line `inset` points in from the\n * margin: Courier is 5.4 pt per character (5.5 for slack); for Roboto, 7.8 is\n * about its widest common glyph, so the wrap is conservative, never short.\n */\nexport function codeColumns(text: string, inset: number): number {\n const width = CONTENT_WIDTH - 16 - inset; // 16: the code block's own padding\n return Math.max(16, Math.floor(width / (codeFont(text).font ? 5.5 : 7.8)));\n}\n\n/**\n * Hard-wrap code lines that cannot fit. pdfmake breaks lines only at spaces,\n * so a long unbroken token (a hash, a URL, minified code) ran off the page\n * and was clipped; {@link codeColumns} gives the width at that nesting depth.\n */\nexport function wrapCode(text: string, columns: number): string {\n return text\n .split(\"\\n\")\n .map((line) => {\n const chars = Array.from(line);\n if (chars.length <= columns) return line;\n const parts: string[] = [];\n for (let i = 0; i < chars.length; i += columns) parts.push(chars.slice(i, i + columns).join(\"\"));\n return parts.join(\"\\n\");\n })\n .join(\"\\n\");\n}\n\n/** Courier when every character is in its Latin-1 range; otherwise Roboto. */\nexport function codeFont(text: string): { font?: string } {\n return /^[\\t\\n\\r\\x20-\\x7e\\xa0-\\xff]*$/.test(text) ? { font: \"Courier\" } : {};\n}\nconst RULE = \"#bbbbbb\";\n\ninterface Marks {\n bold?: boolean;\n italics?: boolean;\n decoration?: \"lineThrough\";\n}\n\ntype Inline = string | ContentText;\n\nfunction inline(nodes: PhrasingContent[], marks: Marks = {}): Inline[] {\n const out: Inline[] = [];\n for (const node of nodes) {\n switch (node.type) {\n case \"text\":\n // A newline inside a paragraph is a soft break: a space, not a new line.\n out.push({ text: node.value.replace(/\\n/g, \" \"), ...marks });\n break;\n case \"strong\":\n out.push(...inline(node.children, { ...marks, bold: true }));\n break;\n case \"emphasis\":\n out.push(...inline(node.children, { ...marks, italics: true }));\n break;\n case \"delete\":\n out.push(...inline(node.children, { ...marks, decoration: \"lineThrough\" }));\n break;\n case \"inlineCode\":\n out.push({ text: node.value, ...marks, ...codeFont(node.value), background: CODE_FILL, fontSize: 9.5, preserveLeadingSpaces: true });\n break;\n case \"break\":\n out.push({ text: \"\\n\" });\n break;\n case \"link\": {\n const url = exportableUrl(node.url);\n const children = inline(node.children, marks);\n if (url) {\n out.push({ text: children.length ? children : url, link: url, color: \"#1a5fb4\", decoration: \"underline\" });\n } else {\n out.push(...children);\n }\n break;\n }\n case \"image\":\n out.push({ text: `[image${node.alt ? `: ${node.alt}` : \"\"}]`, ...marks, italics: true });\n break;\n case \"html\":\n out.push({ text: node.value, ...marks });\n break;\n case \"footnoteReference\":\n out.push({ text: `[^${node.identifier}]`, ...marks, sup: true });\n break;\n default:\n if (\"children\" in node) out.push(...inline(node.children as PhrasingContent[], marks));\n else if (\"value\" in node) out.push({ text: String(node.value), ...marks });\n }\n }\n return out;\n}\n\n/** `inset`: points already taken from the left by enclosing quotes and lists. */\nfunction blocks(nodes: RootContent[], inset = 0): Content[] {\n const out: Content[] = [];\n for (const node of nodes) {\n switch (node.type) {\n case \"heading\":\n out.push({ text: inline(node.children), style: `h${node.depth}` });\n break;\n case \"paragraph\":\n out.push({ text: inline(node.children), style: \"p\" });\n break;\n case \"blockquote\":\n out.push({\n table: {\n widths: [\"*\"],\n body: [[{ stack: blocks(node.children, inset + QUOTE_INSET), italics: true, color: \"#555555\" }]],\n },\n layout: {\n hLineWidth: () => 0,\n vLineWidth: (i: number) => (i === 0 ? 2 : 0),\n vLineColor: () => RULE,\n paddingLeft: () => 10,\n },\n margin: [0, 2, 0, 8],\n });\n break;\n case \"code\":\n out.push({\n table: {\n widths: [\"*\"],\n body: [\n [\n {\n text: wrapCode(node.value, codeColumns(node.value, inset)),\n ...codeFont(node.value),\n fontSize: 9,\n preserveLeadingSpaces: true,\n lineHeight: 1.3,\n },\n ],\n ],\n },\n layout: {\n hLineWidth: () => 0,\n vLineWidth: () => 0,\n fillColor: () => CODE_FILL,\n paddingLeft: () => 8,\n paddingRight: () => 8,\n paddingTop: () => 6,\n paddingBottom: () => 6,\n },\n margin: [0, 2, 0, 8],\n });\n break;\n case \"list\":\n out.push(list(node, inset));\n break;\n case \"table\":\n out.push(table(node));\n break;\n case \"thematicBreak\":\n out.push({\n canvas: [{ type: \"line\", x1: 0, y1: 0, x2: CONTENT_WIDTH - inset, y2: 0, lineWidth: 0.5, lineColor: RULE }],\n margin: [0, 6, 0, 10],\n });\n break;\n case \"html\":\n out.push({ text: node.value, style: \"p\", fontSize: 9, preserveLeadingSpaces: true });\n break;\n case \"footnoteDefinition\":\n out.push({ stack: [{ text: `[^${node.identifier}]`, sup: true }, ...blocks(node.children, inset)], fontSize: 9 });\n break;\n default:\n break;\n }\n }\n return out;\n}\n\nfunction list(node: List, inset: number): Content {\n const items: Content[] = node.children.map((item) => {\n const marker = item.checked === true ? \"[x] \" : item.checked === false ? \"[ ] \" : \"\";\n const [first, ...rest] = item.children;\n const head: Content =\n first && first.type === \"paragraph\"\n ? { text: [marker, ...inline(first.children)], margin: [0, 0, 0, 2] }\n : { text: marker };\n const nested = inset + LIST_INSET;\n const tail = first && first.type === \"paragraph\" ? blocks(rest, nested) : blocks(item.children, nested);\n return tail.length ? { stack: [head, ...tail] } : head;\n });\n return node.ordered\n ? { ol: items, start: node.start ?? 1, margin: [0, 0, 0, 8] }\n : { ul: items, margin: [0, 0, 0, 8] };\n}\n\nfunction table(node: MdTable): Content {\n const columns = Math.max(1, ...node.children.map((row) => row.children.length));\n const align = node.align ?? [];\n const alignment = (i: number) => (align[i] === \"center\" ? \"center\" : align[i] === \"right\" ? \"right\" : \"left\");\n return {\n table: {\n headerRows: 1,\n widths: Array.from({ length: columns }, () => \"*\"),\n body: node.children.map((row, r) =>\n Array.from({ length: columns }, (_, i) => {\n const cell = row.children[i];\n return {\n text: cell ? inline(cell.children, r === 0 ? { bold: true } : {}) : \"\",\n alignment: alignment(i),\n fillColor: r === 0 ? \"#ededed\" : undefined,\n };\n }),\n ),\n },\n layout: \"lightHorizontalLines\",\n margin: [0, 2, 0, 10],\n };\n}\n\nexport function markdownToPdfDefinition(markdown: string, title: string): TDocumentDefinitions {\n const content = blocks(parseMarkdown(markdown).children);\n return {\n info: { title, creator: \"Cerefox\", producer: \"Cerefox\" },\n pageSize: \"A4\",\n pageMargins: [40, 48, 40, 48],\n content: content.length ? content : [{ text: \"\" }],\n defaultStyle: { font: \"Roboto\", fontSize: 10.5, lineHeight: 1.25 },\n styles: {\n p: { margin: [0, 0, 0, 7] },\n h1: { fontSize: 20, bold: true, margin: [0, 10, 0, 8] },\n h2: { fontSize: 16, bold: true, margin: [0, 10, 0, 6] },\n h3: { fontSize: 13.5, bold: true, margin: [0, 8, 0, 5] },\n h4: { fontSize: 12, bold: true, margin: [0, 6, 0, 4] },\n h5: { fontSize: 11, bold: true, margin: [0, 6, 0, 4] },\n h6: { fontSize: 10.5, bold: true, italics: true, margin: [0, 6, 0, 4] },\n },\n footer: (page: number, pages: number) => ({\n text: `${page} / ${pages}`,\n alignment: \"center\",\n fontSize: 8,\n color: \"#888888\",\n margin: [0, 16, 0, 0],\n }),\n };\n}\n\nexport async function markdownToPdf(markdown: string, title: string): Promise<Blob> {\n const [{ default: pdfMake }, { default: vfs }, { default: courier }] = await Promise.all([\n import(\"pdfmake/build/pdfmake\"),\n import(\"pdfmake/build/vfs_fonts\"),\n import(\"pdfmake/build/standard-fonts/Courier\"),\n ]);\n pdfMake.addVirtualFileSystem(vfs);\n pdfMake.addFontContainer(courier);\n pdfMake.setFonts({\n Roboto: {\n normal: \"Roboto-Regular.ttf\",\n bold: \"Roboto-Medium.ttf\",\n italics: \"Roboto-Italic.ttf\",\n bolditalics: \"Roboto-MediumItalic.ttf\",\n },\n Courier: {\n normal: \"Courier\",\n bold: \"Courier-Bold\",\n italics: \"Courier-Oblique\",\n bolditalics: \"Courier-BoldOblique\",\n },\n });\n return pdfMake.createPdf(markdownToPdfDefinition(markdown, title)).getBlob();\n}\n"],"file":"pdf-nplQ05zY.js"}
|
package/dist/frontend/index.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&display=swap"
|
|
16
16
|
/>
|
|
17
17
|
<title>Cerefox</title>
|
|
18
|
-
<script type="module" crossorigin src="/app/assets/index-
|
|
18
|
+
<script type="module" crossorigin src="/app/assets/index-nwj65c23.js"></script>
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/app/assets/rolldown-runtime-COiEfhsA.js">
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/app/assets/index-CV1iy1p2.css">
|
|
21
21
|
</head>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* doesn't touch `supabase/functions/` leaves it alone).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
export const EF_VERSION = "1.
|
|
21
|
+
export const EF_VERSION = "1.22.1";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* The Cerefox RELEASE version — what `cerefox --version` reports and what npm
|
|
@@ -36,7 +36,7 @@ export const EF_VERSION = "1.21.0";
|
|
|
36
36
|
* is imported by the Deno Edge Functions, which cannot reach into the npm
|
|
37
37
|
* package.
|
|
38
38
|
*/
|
|
39
|
-
export const CEREFOX_VERSION = "1.
|
|
39
|
+
export const CEREFOX_VERSION = "1.22.1";
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* The most recent version whose EF-side SOURCE actually changed (#127).
|
|
@@ -46,7 +46,7 @@ export const CEREFOX_VERSION = "1.21.0";
|
|
|
46
46
|
* `cut_release.ts` ONLY when EF source changed since the last tag; doctor
|
|
47
47
|
* uses it to stay silent on label-only drift.
|
|
48
48
|
*/
|
|
49
|
-
export const EF_LAST_CHANGED = "1.
|
|
49
|
+
export const EF_LAST_CHANGED = "1.22.0";
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
|
|
@@ -33,8 +33,9 @@ export const AUDIT_OPERATIONS = [
|
|
|
33
33
|
] as const;
|
|
34
34
|
|
|
35
35
|
/** The document-column label for an audit row — title, short id, or the
|
|
36
|
-
* NULL-document story:
|
|
37
|
-
*
|
|
36
|
+
* NULL-document story: what a store-level op changed ("(setting)" for a config
|
|
37
|
+
* change, "(project)" for a project op, "(store)" for any other), "(deleted)" for rows whose document
|
|
38
|
+
* was purged. One implementation across MCP, CLI, and web. */
|
|
38
39
|
export function auditDocLabel(
|
|
39
40
|
docTitle: string | null | undefined,
|
|
40
41
|
documentId: string | null | undefined,
|
|
@@ -42,9 +43,29 @@ export function auditDocLabel(
|
|
|
42
43
|
): string {
|
|
43
44
|
if (docTitle) return docTitle;
|
|
44
45
|
if (documentId) return documentId.slice(0, 8) + "…";
|
|
46
|
+
if (operation === "config-change") return "(setting)";
|
|
47
|
+
if (operation?.startsWith("project-")) return "(project)";
|
|
45
48
|
return isStoreLevelAuditOp(operation) ? "(store)" : "(deleted)";
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
/**
|
|
52
|
+
* What a store-level entry changed, for a surface that shows a short "change"
|
|
53
|
+
* per row (the web audit log). The RPCs write the change into the
|
|
54
|
+
* description ("config: trash_auto_purge_enabled: 'false' → 'true'",
|
|
55
|
+
* "Project 'x' created"); a config change drops its "config: " prefix, since
|
|
56
|
+
* the row already says it is a setting. Null for document operations, which
|
|
57
|
+
* show a size or a metadata change instead.
|
|
58
|
+
*/
|
|
59
|
+
export function storeChangeSummary(
|
|
60
|
+
operation: string | null | undefined,
|
|
61
|
+
description: string | null | undefined,
|
|
62
|
+
): string | null {
|
|
63
|
+
if (!isStoreLevelAuditOp(operation)) return null;
|
|
64
|
+
const d = (description ?? "").trim();
|
|
65
|
+
if (!d) return null;
|
|
66
|
+
return operation === "config-change" ? d.replace(/^config:\s*/, "") : d;
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
/**
|
|
49
70
|
* One line describing a metadata change (#333): the keys set (with their new
|
|
50
71
|
* values) and the keys removed. Built from an `update-metadata` audit entry's
|
package/docs/api/openapi.json
CHANGED
|
@@ -5797,7 +5797,7 @@
|
|
|
5797
5797
|
},
|
|
5798
5798
|
"/api/v1/version": {
|
|
5799
5799
|
"get": {
|
|
5800
|
-
"summary": "Server version, environment label, and the newest release on npm as the server last checked it (`latest
|
|
5800
|
+
"summary": "Server version, environment label, and the newest release on npm as the server last checked it (`latest`; `update_available` when it is newer, with `update_command` unless the server runs in a container, where it cannot tell how it was installed).",
|
|
5801
5801
|
"operationId": "get_version",
|
|
5802
5802
|
"responses": {
|
|
5803
5803
|
"200": {
|
|
@@ -5834,6 +5834,9 @@
|
|
|
5834
5834
|
"null"
|
|
5835
5835
|
]
|
|
5836
5836
|
},
|
|
5837
|
+
"update_available": {
|
|
5838
|
+
"type": "boolean"
|
|
5839
|
+
},
|
|
5837
5840
|
"update_command": {
|
|
5838
5841
|
"type": [
|
|
5839
5842
|
"string",
|
|
@@ -5847,6 +5850,7 @@
|
|
|
5847
5850
|
"build_date",
|
|
5848
5851
|
"env_label",
|
|
5849
5852
|
"latest",
|
|
5853
|
+
"update_available",
|
|
5850
5854
|
"update_command"
|
|
5851
5855
|
],
|
|
5852
5856
|
"additionalProperties": false
|
package/docs/guides/api.md
CHANGED
|
@@ -179,7 +179,7 @@ default; Cerefox Local picks its own port and `cerefox-local status` prints it).
|
|
|
179
179
|
|
|
180
180
|
| Method + path | Purpose |
|
|
181
181
|
|---|---|
|
|
182
|
-
| `GET /version` | Server version, environment label, and the newest release on npm as the server last checked it (`latest
|
|
182
|
+
| `GET /version` | Server version, environment label, and the newest release on npm as the server last checked it (`latest`; `update_available` when it is newer, with `update_command` unless the server runs in a container, where it cannot tell how it was installed). |
|
|
183
183
|
| `GET /schema-version` | Deployed schema version. |
|
|
184
184
|
| `GET /openapi.json` | This API's OpenAPI 3.1 description, served by the version you are talking to. Every `/api/v1` response points at it with `Link: </api/v1/openapi.json>; rel="service-desc"` (RFC 8631). |
|
|
185
185
|
| `GET /search?q=…` | Hybrid search (FTS + semantic). `&review_status=approved\|pending_review` filters server-side; `400` while the review workflow is off. The response's `embedder` (`openai` or `local`) says which scale the scores are on. |
|
|
@@ -341,6 +341,7 @@ there matters.
|
|
|
341
341
|
|----------|---------|-------------|
|
|
342
342
|
| `CEREFOX_BACKUP_DIR` | `~/.cerefox/backups` | Local directory where file system backups are stored. Created automatically if it doesn't exist. **Use an absolute path** — a relative value (such as the pre-v0.3.0 `./backups`) resolves against the current working directory, so snapshots scatter depending on where you run the command; `backup create` warns when it sees one. Does **not** follow `CEREFOX_CONFIG_DIR`, so a second environment must set it explicitly. |
|
|
343
343
|
| `CEREFOX_ENV_LABEL` | _(unset)_ | Names a non-production environment (e.g. `staging`). Purely cosmetic and inert when unset. When set: the web UI shows a banner on every page, `doctor` shows `[LABEL]` on its title line, `backup create` puts the label in the snapshot filename and payload, and `backup restore` warns when a snapshot's environment differs from the target's, and `configure-agent` registers the MCP server as `cerefox-<label>` so a labeled environment sits alongside production instead of overwriting it (v1.4.0, #168). See [`staging-env.md`](staging-env.md). |
|
|
344
|
+
| `CEREFOX_PROG_NAME` | _(unset)_ | The command you type for this environment, used in every hint and message that tells you what to run next (and in `--help`). Set it in an environment's `.env` when you reach it through an alias, for example `CEREFOX_PROG_NAME=cfx-stg` in `~/.cerefox/staging/.env`. A value in the `.env` wins over an inherited one, since it names that environment. `cerefox-local` sets it itself. When it is unset and `CEREFOX_CONFIG_DIR` is set, hints spell out `CEREFOX_CONFIG_DIR=<dir> cerefox …`, so a copied command never reaches your default environment (#352). |
|
|
344
345
|
| `CEREFOX_VERSION_RETENTION_HOURS` | **Retired in v1.1.0 — no longer read.** | Version retention is now a property of the store: `cerefox config set version_retention_hours <hours>`, or the **Settings** page. It moved because it used to be passed per-call from each client's environment, so the surviving history depended on which client wrote last. `cerefox doctor` reports the variable if it is still set. |
|
|
345
346
|
|
|
346
347
|
---
|
|
@@ -354,8 +355,10 @@ container it points at `cerefox-local upgrade` instead of `cerefox self-update`.
|
|
|
354
355
|
|
|
355
356
|
The only network call is a GET of the `latest` release from the npm registry
|
|
356
357
|
(`registry.npmjs.org`), the same request `cerefox self-update` makes. It is made
|
|
357
|
-
by `cerefox web` (at startup, then
|
|
358
|
-
|
|
358
|
+
by `cerefox web` (at startup, then whenever the saved answer is over an hour old,
|
|
359
|
+
so a new release shows in the web UI within about an hour), `cerefox mcp` (at
|
|
360
|
+
startup, when the last answer is over an hour old), `cerefox doctor` and
|
|
361
|
+
`cerefox self-update`.
|
|
359
362
|
Every other command only reads the saved answer, `update-check.json` in
|
|
360
363
|
`~/.cerefox` (or in `CEREFOX_CONFIG_DIR`, when set), so it never adds latency.
|
|
361
364
|
Offline, the check stays quiet.
|
|
@@ -77,6 +77,10 @@ in the container.
|
|
|
77
77
|
to `cerefox mcp` in the container; the same 15 core tools, identical behavior to every other path.
|
|
78
78
|
- The cloud paths above (remote Edge Function, GPT Actions) **do not apply** to a local-only
|
|
79
79
|
install — there are no Edge Functions.
|
|
80
|
+
- **Cerefox Local on another machine** (a NAS or home server, v1.22.0+): `docker exec` is not
|
|
81
|
+
available from your other machines, so they use the ordinary local MCP server instead, with
|
|
82
|
+
the host's URL and its API key in a separate config directory, and
|
|
83
|
+
`cerefox-lan configure-agent`. See [`setup-lan.md`](setup-lan.md#the-cli-and-coding-agents-cerefox-local-only).
|
|
80
84
|
|
|
81
85
|
---
|
|
82
86
|
|
|
@@ -181,6 +181,23 @@ The key authenticates; it does not identify. `X-Cerefox-Requestor` and
|
|
|
181
181
|
described in [`api.md`](api.md#attribution-is-not-authentication). Holding the
|
|
182
182
|
key does not make the name you send true.
|
|
183
183
|
|
|
184
|
+
## What the key opens on Cerefox Local
|
|
185
|
+
|
|
186
|
+
On Cerefox Local the gate covers two routes on the same port: `/api/v1`, the
|
|
187
|
+
HTTP API, and `/rest/v1`, the database API the CLI and the local MCP server use.
|
|
188
|
+
Since v1.22.0 (#351) a request that presents a valid key on `/rest/v1` reaches
|
|
189
|
+
the database as the container's service role: the server swaps the key for the
|
|
190
|
+
container's internal credential on the way in, the way Supabase's gateway does
|
|
191
|
+
for an `sb_secret_` key, and that credential never leaves the container. This
|
|
192
|
+
is what lets the CLI and coding agents on other machines use a Cerefox Local
|
|
193
|
+
on a LAN host ([`setup-lan.md`](setup-lan.md)).
|
|
194
|
+
|
|
195
|
+
It also means the key is the store's master key there: the database API can
|
|
196
|
+
read and change every table directly, below the safeguards the RPCs apply
|
|
197
|
+
(version history, the audit log). Give it only to clients you would give the
|
|
198
|
+
whole store to. A request let in without a key (the gate off, or the loopback
|
|
199
|
+
exemption) is forwarded exactly as before and gains nothing.
|
|
200
|
+
|
|
184
201
|
## Recipe: a harness in its own container
|
|
185
202
|
|
|
186
203
|
The case where a key is genuinely needed, and the one worth automating.
|
package/docs/guides/setup-lan.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Run Cerefox on an always-on machine on your home or office network (a NAS, a
|
|
4
4
|
mini PC, a home server) and use it from your other machines: agents, the CLI,
|
|
5
|
-
and the web UI in a browser. Requires **v1.20.0 or newer
|
|
5
|
+
and the web UI in a browser. Requires **v1.20.0 or newer** (the CLI and coding
|
|
6
|
+
agents against Cerefox Local: **v1.22.0**).
|
|
6
7
|
|
|
7
8
|
There are two ways to do it, and they do not depend on each other:
|
|
8
9
|
|
|
@@ -122,6 +123,106 @@ recreate the container. The data folder carries the database and, if you let the
|
|
|
122
123
|
container mint it, the key. Cerefox Local applies pending schema migrations on
|
|
123
124
|
boot.
|
|
124
125
|
|
|
126
|
+
### On a Synology NAS
|
|
127
|
+
|
|
128
|
+
Step by step with **Container Manager** on DSM 7.2. Menu names can differ a
|
|
129
|
+
little between DSM versions.
|
|
130
|
+
|
|
131
|
+
**1. Check the model can run it.** Container Manager has to be in Package
|
|
132
|
+
Center for your model; if it is not there, the model cannot run containers. The
|
|
133
|
+
image is published for both CPU types Synology uses, and Docker picks the right
|
|
134
|
+
one when it pulls. You only need to know which one when you copy the image over
|
|
135
|
+
as a file (step 3): **Control Panel → Info Center → General** shows the CPU. An
|
|
136
|
+
Intel or AMD processor needs `linux/amd64`; an ARM one (Realtek, for example)
|
|
137
|
+
needs `linux/arm64`.
|
|
138
|
+
|
|
139
|
+
**2. Install Container Manager and make two folders.** Install it from Package
|
|
140
|
+
Center; it creates a shared folder named `docker`. In File Station, create two
|
|
141
|
+
folders inside it:
|
|
142
|
+
|
|
143
|
+
- `docker/cerefox-db`: the database. Nothing else goes here.
|
|
144
|
+
- `docker/cerefox-project`: the compose file. Keep it separate from the data.
|
|
145
|
+
|
|
146
|
+
**3. Get the image.** Try **Container Manager → Registry** first. If the search
|
|
147
|
+
or the download fails (some DSM versions answer `ghcr.io` with "registry
|
|
148
|
+
returned bad result"), copy the image over as a file instead
|
|
149
|
+
([above](#if-the-host-cannot-pull-the-image), with the `--platform` from step
|
|
150
|
+
1), then **Image → Import → Add from file**. The image list must then show
|
|
151
|
+
`ghcr.io/fstamatelopoulos/cerefox-local` with the tag you will put in the
|
|
152
|
+
compose file, or Container Manager will try to pull it again.
|
|
153
|
+
|
|
154
|
+
**4. Create the Project.** **Project → Create**:
|
|
155
|
+
|
|
156
|
+
- **Project name:** `cerefox`.
|
|
157
|
+
- **Path:** `docker/cerefox-project`.
|
|
158
|
+
- **Source:** "Create docker-compose.yml", and paste:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
services:
|
|
162
|
+
cerefox:
|
|
163
|
+
image: ghcr.io/fstamatelopoulos/cerefox-local:v1.22.0
|
|
164
|
+
container_name: cerefox
|
|
165
|
+
restart: unless-stopped
|
|
166
|
+
ports:
|
|
167
|
+
- "8000:8000"
|
|
168
|
+
volumes:
|
|
169
|
+
- /volume1/docker/cerefox-db:/var/lib/postgresql/data
|
|
170
|
+
environment:
|
|
171
|
+
CEREFOX_API_REQUIRE_KEY: "1"
|
|
172
|
+
CEREFOX_API_KEY: "cfx_lak_YOUR-KEY"
|
|
173
|
+
CEREFOX_EMBEDDER: openai
|
|
174
|
+
OPENAI_API_KEY: "YOUR-OPENAI-KEY"
|
|
175
|
+
mem_limit: 2g
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **The volume path** is the full path on the NAS. `/volume1` is the first
|
|
179
|
+
storage volume; if your `docker` shared folder lives on another volume, File
|
|
180
|
+
Station shows which (right-click the folder, Properties).
|
|
181
|
+
- **The left port** (`8000:`) is the one your other machines will use. Change
|
|
182
|
+
it if something on the NAS already uses 8000; leave the right one alone.
|
|
183
|
+
- Skip the web portal (Web Station) step, then finish. Container Manager starts
|
|
184
|
+
the container.
|
|
185
|
+
|
|
186
|
+
**5. Check it started.** **Container → cerefox → Log.** The first boot creates
|
|
187
|
+
the database, which takes a minute; later boots apply any pending schema
|
|
188
|
+
migrations and then the server starts. Then, from another machine, run the
|
|
189
|
+
`curl` checks under [Using it from your other machines](#using-it-from-your-other-machines).
|
|
190
|
+
|
|
191
|
+
**6. Keep it on your network.** Do not forward the port on your router, and do
|
|
192
|
+
not publish it through QuickConnect, DSM's reverse proxy (Login Portal) or any
|
|
193
|
+
other remote-access feature. To go one step further, **Control Panel → Security
|
|
194
|
+
→ Firewall** can allow the port only from your local subnet.
|
|
195
|
+
|
|
196
|
+
**Upgrading.** Back up first (below). Then:
|
|
197
|
+
|
|
198
|
+
1. Stop the Project.
|
|
199
|
+
2. Get the new image (step 3).
|
|
200
|
+
3. Change the tag on the `image:` line in the Project's YAML.
|
|
201
|
+
4. Build the Project again.
|
|
202
|
+
|
|
203
|
+
The data folder carries the database, and the key stays in the Project's YAML
|
|
204
|
+
(a key set there is not saved in the data folder), so nothing else changes.
|
|
205
|
+
Once the new version is running, you can delete the old image under **Image**.
|
|
206
|
+
|
|
207
|
+
**Protecting the data.** The NAS gives you three layers; use the last one in any
|
|
208
|
+
case:
|
|
209
|
+
|
|
210
|
+
- **Snapshots** (the Snapshot Replication package, on a Btrfs volume) of the
|
|
211
|
+
`docker` shared folder. A snapshot of a running database is only
|
|
212
|
+
*crash-consistent*, like pulling the power cord: Postgres recovers from it,
|
|
213
|
+
but stop the Project first if you want a clean copy.
|
|
214
|
+
- **Hyper Backup** of the same folder to another disk or NAS. The same caveat
|
|
215
|
+
applies.
|
|
216
|
+
- **`cerefox backup create`** from one of your machines, with the CLI set up as
|
|
217
|
+
below. It writes a JSON snapshot of your documents and projects to that
|
|
218
|
+
machine, which `cerefox backup restore` can load into a new Cerefox, on the
|
|
219
|
+
NAS or elsewhere.
|
|
220
|
+
|
|
221
|
+
A backup made on the NAS itself (`cerefox backup create` in the container's
|
|
222
|
+
terminal) is saved in the data folder, under `.cerefox-backups`, so it
|
|
223
|
+
survives a rebuild and the layers above include it. Before v1.22.1 it was
|
|
224
|
+
saved inside the container and lost on the next build (#359).
|
|
225
|
+
|
|
125
226
|
## B. The web container, with your Supabase backend
|
|
126
227
|
|
|
127
228
|
You need a working Supabase-backed Cerefox first (see
|
|
@@ -186,19 +287,69 @@ read it. It is readable by scripts running in the Cerefox page itself; the page
|
|
|
186
287
|
renders documents without raw HTML, and the project's tests fail on any change
|
|
187
288
|
that would let a document inject script.
|
|
188
289
|
|
|
189
|
-
|
|
290
|
+
### The CLI and coding agents (Cerefox Local only)
|
|
291
|
+
|
|
292
|
+
Requires **v1.22.0 or newer** on the LAN host. Your other machines run the
|
|
293
|
+
ordinary `cerefox` client (`npm install -g @cerefox/memory`) against the host,
|
|
294
|
+
the same way they would run it against a Supabase project: a URL and a key in a
|
|
295
|
+
`.env`. With the web container (B), point the client at your Supabase project as
|
|
296
|
+
usual instead; the web container plays no part in it.
|
|
297
|
+
|
|
298
|
+
Keep this setup in its own config directory, as you would a staging
|
|
299
|
+
environment ([`staging-env.md`](staging-env.md)), so it never mixes with another
|
|
300
|
+
Cerefox you use:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
mkdir -p ~/.cerefox/lan && chmod 700 ~/.cerefox/lan
|
|
304
|
+
cat > ~/.cerefox/lan/.env <<'ENV'
|
|
305
|
+
CEREFOX_SUPABASE_URL=http://192.168.1.20:8000
|
|
306
|
+
CEREFOX_SUPABASE_KEY=cfx_lak_YOUR-KEY
|
|
307
|
+
CEREFOX_ENV_LABEL=lan
|
|
308
|
+
CEREFOX_PROG_NAME=cerefox-lan
|
|
309
|
+
CEREFOX_EMBEDDER=openai
|
|
310
|
+
OPENAI_API_KEY=sk-YOUR-OPENAI-KEY
|
|
311
|
+
ENV
|
|
312
|
+
chmod 600 ~/.cerefox/lan/.env
|
|
313
|
+
alias cerefox-lan='CEREFOX_CONFIG_DIR=~/.cerefox/lan cerefox'
|
|
314
|
+
|
|
315
|
+
cerefox-lan doctor
|
|
316
|
+
cerefox-lan configure-agent --tool claude-code # adds an MCP server named cerefox-lan
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
- **The embedder must match the LAN host's.** The client computes the
|
|
320
|
+
embeddings for what it ingests and searches, as it does against Supabase, so
|
|
321
|
+
it needs the same `CEREFOX_EMBEDDER` as the host, and for `openai` its own
|
|
322
|
+
OpenAI key. A host that runs the local embedder needs `CEREFOX_EMBEDDER=local`
|
|
323
|
+
here, and the client then downloads the model once (about 130 MB).
|
|
324
|
+
- **How the key gets in.** The client sends the key where a Supabase client
|
|
325
|
+
sends its secret key. The server checks it, then passes the request to the
|
|
326
|
+
database with the container's own internal credential, which never leaves
|
|
327
|
+
the container. This is the same exchange Supabase's gateway makes for an
|
|
328
|
+
`sb_secret_` key.
|
|
329
|
+
- **`CEREFOX_PROG_NAME`** is the alias's name. Hints that tell you what to run
|
|
330
|
+
next (`doctor`'s suggestions, for example) then say `cerefox-lan …`, so a
|
|
331
|
+
copied one acts on the LAN host and not on another Cerefox you use.
|
|
332
|
+
- **`CEREFOX_ENV_LABEL`** puts `[LAN]` on `doctor`'s title line and in backup
|
|
333
|
+
file names, and names the agent entry `cerefox-lan`.
|
|
334
|
+
- **Agents without MCP.** A coding agent can also run `cerefox-lan` commands in
|
|
335
|
+
its shell, or call the HTTP API ([`api.md`](api.md)) with the key.
|
|
336
|
+
- `configure-agent` pins the config directory into the agent's MCP entry, so
|
|
337
|
+
the agent reaches the LAN host whichever directory it starts in.
|
|
338
|
+
|
|
190
339
|
|
|
191
340
|
**Traffic on the wire.** Plain `http://` on your network sends the key, and
|
|
192
|
-
everything else, unencrypted. That is
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
the gate stays on, since a proxy makes every request look local.
|
|
341
|
+
everything else, unencrypted. That is acceptable on a home network you control;
|
|
342
|
+
do not run these setups on a network you share with people or devices you do
|
|
343
|
+
not trust.
|
|
196
344
|
|
|
197
|
-
**The internet.**
|
|
198
|
-
|
|
345
|
+
**The internet.** These setups are for your own network only. Do not forward
|
|
346
|
+
the port from your router, and do not publish the host's Cerefox port through
|
|
347
|
+
a remote-access service or a proxy.
|
|
199
348
|
|
|
200
349
|
**Anyone with the key**, or with access to the host's files, the data folder or
|
|
201
|
-
the compose file.
|
|
350
|
+
the compose file. On Cerefox Local the key is the store's master key: besides
|
|
351
|
+
the web UI and the HTTP API, it opens the database API the CLI uses, which can
|
|
352
|
+
read and change every table directly.
|
|
202
353
|
|
|
203
354
|
## Troubleshooting
|
|
204
355
|
|
|
@@ -209,6 +360,11 @@ planned.
|
|
|
209
360
|
**Every data route returns 500 with the key, but `/version` works.** The image
|
|
210
361
|
is older than v1.19.6. Upgrade.
|
|
211
362
|
|
|
363
|
+
**The CLI or an agent fails with `Expected 3 parts in JWT; got 1`.** The
|
|
364
|
+
Cerefox Local image on the LAN host is older than v1.22.0, which passed the key
|
|
365
|
+
to the database unchanged. Upgrade the image; the client needs no change.
|
|
366
|
+
`cerefox doctor` says the same.
|
|
367
|
+
|
|
212
368
|
**The web UI shows a white page or keeps failing after the key.** The image is
|
|
213
369
|
older than v1.20.0, which is the first version whose web UI can send a key.
|
|
214
370
|
|
|
@@ -163,8 +163,18 @@ cerefox-local upgrade v1.2.3 # pin an exact version (also how you downgrade)
|
|
|
163
163
|
cerefox-local upgrade --latest # follow the moving :latest tag from now on
|
|
164
164
|
cerefox-local uninstall # remove the container, KEEP the data volume
|
|
165
165
|
cerefox-local uninstall --purge # remove the container AND delete the data volume (asks you to confirm; --yes in scripts)
|
|
166
|
+
cerefox-local backup create # JSON snapshot: kept in the data volume, copied to ~/.cerefox/local/backups
|
|
167
|
+
cerefox-local backup restore ~/.cerefox/local/backups/cerefox-<stamp>.json
|
|
166
168
|
```
|
|
167
169
|
|
|
170
|
+
**Backups.** `backup create` writes the snapshot into the data volume, so it
|
|
171
|
+
survives `upgrade` and `init`, and copies it to `~/.cerefox/local/backups` on
|
|
172
|
+
your machine, so it also survives losing the volume (`uninstall --purge`).
|
|
173
|
+
`backup restore` takes a file on your machine and copies it into the container
|
|
174
|
+
for you. Before v1.22.1 the snapshot stayed inside the container and the next
|
|
175
|
+
`upgrade` or `init` deleted it (#359); the first upgrade to v1.22.1 still runs
|
|
176
|
+
the old script, so make that one's backup from the new version, right after.
|
|
177
|
+
|
|
168
178
|
> **Coming from an image older than v1.1.0?** Run `cerefox-local upgrade v1.1.1`
|
|
169
179
|
> (an explicit tag) once. A bare `upgrade` on those versions re-pulled the pinned
|
|
170
180
|
> tag and reported success without changing anything (#153), and it cannot fix
|
|
@@ -186,6 +196,7 @@ sync.
|
|
|
186
196
|
| Container | name `cerefox-local` (override: `CEREFOX_LOCAL_CONTAINER`) |
|
|
187
197
|
| Your data | Docker volume `cerefox_local_pgdata` (survives `stop`/`upgrade`) |
|
|
188
198
|
| Host config | `~/.cerefox/local/.env` (OPENAI key + port only — **no token**) |
|
|
199
|
+
| Backups | `~/.cerefox/local/backups` (override: `CEREFOX_LOCAL_BACKUP_DIR`), plus a copy in the volume's `.cerefox-backups` folder |
|
|
189
200
|
| Host command | `~/.cerefox/local/cerefox-local`, symlinked to `~/.local/bin/cerefox-local` |
|
|
190
201
|
|
|
191
202
|
---
|
|
@@ -73,8 +73,12 @@ CEREFOX_CONFIG_DIR=~/.cerefox/staging cerefox init
|
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
CEREFOX_ENV_LABEL=staging
|
|
76
|
+
# The alias below; hints then say `cfx-stg …`. Comments go on their own line:
|
|
77
|
+
# the .env parser keeps anything after `=` as the value.
|
|
78
|
+
CEREFOX_PROG_NAME=cfx-stg
|
|
76
79
|
CEREFOX_SUPABASE_URL=https://<staging-ref>.supabase.co
|
|
77
|
-
|
|
80
|
+
# The staging project's secret key:
|
|
81
|
+
CEREFOX_SUPABASE_KEY=sb_secret_…
|
|
78
82
|
CEREFOX_DATABASE_URL=postgresql://postgres.<staging-ref>:…@aws-0-<region>.pooler.supabase.com:5432/postgres?sslmode=require
|
|
79
83
|
|
|
80
84
|
# Isolate snapshots. CEREFOX_BACKUP_DIR does NOT follow the config dir, so
|
|
@@ -84,7 +88,8 @@ CEREFOX_DATABASE_URL=postgresql://postgres.<staging-ref>:…@aws-0-<region>.pool
|
|
|
84
88
|
CEREFOX_BACKUP_DIR=~/.cerefox/staging/backups
|
|
85
89
|
|
|
86
90
|
OPENAI_API_KEY=sk-…
|
|
87
|
-
|
|
91
|
+
# Account-level; the same token as production:
|
|
92
|
+
SUPABASE_ACCESS_TOKEN=sbp_…
|
|
88
93
|
```
|
|
89
94
|
|
|
90
95
|
> **Snapshot isolation needs v1.1.0-beta.3 or later.** On **beta.2 and
|
|
@@ -262,6 +267,11 @@ otherwise:
|
|
|
262
267
|
| `backup create` | Filename becomes `cerefox-staging-<stamp>.json`, and the label is stored in the payload |
|
|
263
268
|
| `backup restore` | Warns when the snapshot's environment differs from the target's |
|
|
264
269
|
|
|
270
|
+
Also set `CEREFOX_PROG_NAME` to your alias (`cfx-stg`). Hints that tell you what
|
|
271
|
+
to run next, such as `doctor`'s `config set …` suggestions, then name the alias,
|
|
272
|
+
so copying one acts on staging. Without it, they spell out
|
|
273
|
+
`CEREFOX_CONFIG_DIR=… cerefox …`, which is just as safe, only longer.
|
|
274
|
+
|
|
265
275
|
The filename and restore warning matter because `CEREFOX_BACKUP_DIR` does not
|
|
266
276
|
follow `CEREFOX_CONFIG_DIR`: staging and production snapshots can end up in one
|
|
267
277
|
directory, and a restore that picks "the most recent file" there would
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cerefox/memory",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.1",
|
|
4
4
|
"description": "Cerefox \u2014 user-owned shared memory for AI agents. CLI + stdio MCP server + web UI + ingestion for a knowledge base on your own Supabase project (or fully self-hosted with Cerefox Local).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/fstamatelopoulos/cerefox",
|