@cerefox/memory 1.22.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.
@@ -1,2 +1,2 @@
1
- import{n as e,r as t,t as n}from"./index-Dhpoy6w3.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-YrYQtnOl.js.map
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-YrYQtnOl.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
+ {"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-Dhpoy6w3.js";import{exportableUrl as n,parseMarkdown as r}from"./markdown-YrYQtnOl.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(`
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-Bpjaodjd.js.map
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-Bpjaodjd.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"}
@@ -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-Dhpoy6w3.js"></script>
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.22.0";
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.22.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.22.0";
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).
@@ -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`, plus `update_command` when it is newer).",
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
@@ -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`, plus `update_command` when it is newer). |
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. |
@@ -218,6 +218,11 @@ case:
218
218
  machine, which `cerefox backup restore` can load into a new Cerefox, on the
219
219
  NAS or elsewhere.
220
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
+
221
226
  ## B. The web container, with your Supabase backend
222
227
 
223
228
  You need a working Supabase-backed Cerefox first (see
@@ -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
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.22.0",
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",