Add the following to your query to refine the search:
Use path:"Library" to restrict your results\n to paths which include "Library"
Use ext:pdf,jpg or ext:png, or\n a plain .png to restrict your search to certain filetypes
Use "exact expressions" in quotes to further filter\n the results returned by the query
Use -apple -pie to exclude notes containing the words\n "apple" and "pie"
`);function $s(e){var t=Js();C(e,t)}let k=Je({text:"",buffer:"",historyIndex:-1,history:[]});var el=Y(\'\');function ii(e,t){pt(t,!0);let n;wt(()=>{n.showModal()});function r(){syscall("editor.hidePanel","modal")}let i=Z(Je([])),s=Z(!1),f=Z(0);wt(()=>{k.historyIndex>=0&&(k.history[k.historyIndex]=k.text)}),wt(()=>{k.text?o():(j(i,[],!0),j(s,!1))});let l=null;async function o(){j(s,!0),l&&(l.reject(),l=null),l=Promise.withResolvers();try{j(i,await Promise.race([t.search(k.text),l.promise]),!0),l=null,j(f,0),h(),j(s,!1)}catch{}}async function a(g,R){E(i).length!==0&&(k.text&&k.history.unshift(k.text),k.history=k.history.filter(ne=>ne),k.history.length>50&&k.history.pop(),syscall("clientStore.set","silversearch-history",[...k.history]),await t.open(E(i)[E(f)],g,R),await syscall("editor.hidePanel","modal"))}function c(g){if(g.key==="Enter"){a(g.ctrlKey,g.altKey);return}if(g.ctrlKey){const R=k.historyIndex;if(R===-1&&(k.buffer=k.text),g.key==="ArrowUp")k.historyIndex++;else if(g.key==="ArrowDown")k.historyIndex--;else return;k.historyIndex=Math.max(-1,Math.min(k.historyIndex,k.history.length-1)),R!=k.historyIndex&&k.historyIndex>=0?k.text=k.history[k.historyIndex]:k.historyIndex===-1&&(k.text=k.buffer)}else{if(g.key==="ArrowUp")xr(f,-1);else if(g.key==="ArrowDown")xr(f);else return;j(f,Math.max(0,Math.min(E(f),E(i).length-1)),!0)}g.preventDefault(),h()}async function h(){await Yr();const g=document.querySelector(".silversearch-selected");g&&g.scrollIntoView({block:"nearest"})}var v=el();Xr("click",Tn,r);var p=K(v),d=he(K(p),2),_=he(p,2),x=K(_);it(x,()=>t.helpText);var w=he(_,2),A=K(w);ks(A,17,()=>E(i),Ts,(g,R,ne)=>{{const Hn=at=>{var Se=Le(),Xt=me(Se);{var zn=ut=>{var Zt=Le(),Bn=me(Zt);it(Bn,()=>t.elementTitle,()=>E(R)),C(ut,Zt)};De(Xt,ut=>{t.elementTitle&&ut(zn)})}C(at,Se)},cl=at=>{var Se=Le(),Xt=me(Se);it(Xt,()=>t.elementDescription,()=>E(R)),C(at,Se)},dl=at=>{var Se=Le(),Xt=me(Se);{var zn=ut=>{var Zt=Le(),Bn=me(Zt);it(Bn,()=>t.elementInfo,()=>E(R)),C(ut,Zt)};De(Xt,ut=>{t.elementInfo&&ut(zn)})}C(at,Se)};let hl=mr(()=>ne===E(f));zs(g,{get selected(){return E(hl)},onclick:({ctrlKey:at,altKey:Se})=>a(at,Se),onmousemove:()=>j(f,ne,!0),title:Hn,description:cl,info:dl})}});var U=he(A,2);{var ze=g=>{Ks(g)},te=g=>{$s(g)},ft=g=>{Zs(g,{})};De(U,g=>{!E(i).length&&!E(s)&&k.text?g(ze):!E(i).length&&!E(s)?g(te,1):!E(i).length&&E(s)&&g(ft,2)})}Ns(v,g=>n=g,()=>n),Xr("cancel",v,g=>{g.preventDefault(),syscall("editor.hidePanel","modal")}),Bt("click",p,g=>g.stopPropagation()),Bt("keydown",p,c),Rs(d,()=>k.text,g=>k.text=g),C(e,v),_t()}In(["click","keydown"]);function tl(e){return e.replace(/[.*+?^${}()|[\\]\\\\]/g,"\\\\$&")}const si=/( |<|>|"|')/g;function Un(e,t){const n="silversearch-highlight";if(!t.length)return e;try{return e.split(si).map(r=>r.match(si)?r:r.replace(new RegExp(`(${t.map(i=>tl(i.match)).join("|")})`,"giu"),`$1`)).join("")}catch(r){return console.error("[Silversearch] Error in highlightText()",r),e}}var nl=Y("Press Ctrl-Enter to open in new Tab. Use Tab to switch scope.",1);function rl(e,t){pt(t,!0);let n=[],r=null;async function i(f){let l=await syscall("silversearch.search",f,{singleFilePath:t.currentPath,silent:!0});return n=l[0].matches,r=l[0].name,l[0].excerpts}async function s(f,l,o){r&&await syscall("editor.navigate",`${r}@${f.offset}`,!1,l)}ii(e,{search:i,open:s,helpText:o=>{var a=nl();C(o,a)},elementDescription:(o,a=It)=>{var c=Le(),h=me(c);on(h,()=>Un(a().excerpt,n)),C(o,c)},$$slots:{helpText:!0,elementDescription:!0}}),_t()}function il(e,t){for(const n of e)if(n.type==="range"&&n.from<=t&&t<=n.to)return n.tail;return""}var sl=Y("Use Tab to switch scope.",1),ll=Y("Press Ctrl-Enter to open in new Tab and Alt-Enter to insert a link. ",1);function ol(e,t){pt(t,!1);const n=or("isDocumentEditor");async function r(f,l,o){var h,v;if(o)return await i(f);const a=((v=(h=f.matches)==null?void 0:h[0])==null?void 0:v.offset)??0;let c=`@${a}`;f.navigationMap&&(c=il(f.navigationMap,a)),await syscall("editor.navigate",`${f.name}${c}`,!1,l)}async function i(f){const l=`[[${f.name}]]`;await syscall("editor.insertAtCursor",l),await syscall("editor.hidePanel","modal")}async function s(f){return syscall("silversearch.search",f,{silent:!0})}Fs(),ii(e,{search:s,open:r,helpText:c=>{var h=ll(),v=he(me(h),5);{var p=d=>{var _=sl();C(d,_)};De(v,d=>{n||d(p)})}C(c,h)},elementTitle:(c,h=It)=>{var v=Le(),p=me(v);on(p,()=>Un(h().name,h().matchesName)),C(c,v)},elementDescription:(c,h=It)=>{var v=Le(),p=me(v);on(p,()=>Un(h().excerpts[0].excerpt,h().matches)),C(c,v)},elementInfo:(c,h=It)=>{var v=ys();Ut(()=>Fn(v,`${h().matches.length??""} Matches`)),C(c,v)},$$slots:{helpText:!0,elementTitle:!0,elementDescription:!0,elementInfo:!0}}),_t()}var fl=Y(" ",1);function al(e,t){pt(t,!0),syscall("clientStore.get","silversearch-history").then(h=>{k.history=h||[]});const n=or("isDocumentEditor");k.text=t.defaultQuery;let r=Z(!0);function i(h){h.stopPropagation(),h.key==="Tab"&&(n||(j(r,!E(r)),h.preventDefault()))}var s=fl(),f=me(s);on(f,()=>t.customStyles);var l=he(f,2),o=K(l);{var a=h=>{ol(h,{})},c=h=>{rl(h,{get currentPath(){return t.currentPath}})};De(o,h=>{E(r)?h(a):h(c,-1)})}Bt("keydown",l,i),C(e,s),_t()}In(["keydown"]);async function ul(){const e=syscall("editor.getCurrentPath"),t=syscall("editor.getCurrentEditor"),n=syscall("editor.getUiOption","customStyles");await Promise.race([new Promise(r=>setTimeout(r,75)),new Promise(r=>{const i=document.querySelector("link#stylesheet");i&&(i.onload=r)})]),bs(al,{target:document.querySelector("#container"),props:{defaultQuery:globalThis.DEFAULT_QUERY??"",currentPath:await e,customStyles:await n},context:new Map([["isDocumentEditor",await t!=="page"]])})}ul()})();\n';var Tt=`
+
+`;var Yi="ENTRIES",It="KEYS",zt="VALUES";var Y=class{constructor(e,n){let r=e._tree,i=Array.from(r.keys());this.set=e,this._type=n,this._path=i.length>0?[{node:r,keys:i}]:[]}next(){let e=this.dive();return this.backtrack(),e}dive(){if(this._path.length===0)return{done:!0,value:void 0};let{node:e,keys:n}=B(this._path);if(B(n)==="")return{done:!1,value:this.result()};let r=e.get(B(n));return this._path.push({node:r,keys:Array.from(r.keys())}),this.dive()}backtrack(){if(this._path.length===0)return;let e=B(this._path).keys;e.pop(),!(e.length>0)&&(this._path.pop(),this.backtrack())}key(){return this.set._prefix+this._path.map(({keys:e})=>B(e)).filter(e=>e!=="").join("")}value(){return B(this._path).node.get("")}result(){switch(this._type){case zt:return this.value();case It:return this.key();default:return[this.key(),this.value()]}}[Symbol.iterator](){return this}},B=t=>t[t.length-1],es=(t,e,n)=>{let r=new Map;if(e===void 0)return r;let i=e.length+1,s=i+n,o=new Uint8Array(s*i).fill(n+1);for(let u=0;u{let a=s*o;e:for(let l of t.keys())if(l===""){let f=i[a-1];f<=n&&r.set(u,[t.get(l),f])}else{let f=s;for(let p=0;pn)continue e}Ot(t.get(l),e,n,r,i,f,o,u+l)}},ee=class t{constructor(e=new Map,n=""){this._size=void 0,this._tree=e,this._prefix=n}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");let[n,r]=he(this._tree,e.slice(this._prefix.length));if(n===void 0){let[i,s]=Be(r);for(let o of i.keys())if(o!==""&&o.startsWith(s)){let u=new Map;return u.set(o.slice(s.length),i.get(o)),new t(u,e)}}return new t(n,e)}clear(){this._size=void 0,this._tree.clear()}delete(e){return this._size=void 0,ts(this._tree,e)}entries(){return new Y(this,Yi)}forEach(e){for(let[n,r]of this)e(n,r,this)}fuzzyGet(e,n){return es(this._tree,e,n)}get(e){let n=Ne(this._tree,e);return n!==void 0?n.get(""):void 0}has(e){let n=Ne(this._tree,e);return n!==void 0&&n.has("")}keys(){return new Y(this,It)}set(e,n){if(typeof e!="string")throw new Error("key must be a string");return this._size=void 0,Oe(this._tree,e).set("",n),this}get size(){if(this._size)return this._size;this._size=0;let e=this.entries();for(;!e.next().done;)this._size+=1;return this._size}update(e,n){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;let r=Oe(this._tree,e);return r.set("",n(r.get(""))),this}fetch(e,n){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;let r=Oe(this._tree,e),i=r.get("");return i===void 0&&r.set("",i=n()),i}values(){return new Y(this,zt)}[Symbol.iterator](){return this.entries()}static from(e){let n=new t;for(let[r,i]of e)n.set(r,i);return n}static fromObject(e){return t.from(Object.entries(e))}},he=(t,e,n=[])=>{if(e.length===0||t==null)return[t,n];for(let r of t.keys())if(r!==""&&e.startsWith(r))return n.push([t,r]),he(t.get(r),e.slice(r.length),n);return n.push([t,e]),he(void 0,"",n)},Ne=(t,e)=>{if(e.length===0||t==null)return t;for(let n of t.keys())if(n!==""&&e.startsWith(n))return Ne(t.get(n),e.slice(n.length))},Oe=(t,e)=>{let n=e.length;e:for(let r=0;t&&r{let[n,r]=he(t,e);if(n!==void 0){if(n.delete(""),n.size===0)Rt(r);else if(n.size===1){let[i,s]=n.entries().next().value;Lt(r,i,s)}}},Rt=t=>{if(t.length===0)return;let[e,n]=Be(t);if(e.delete(n),e.size===0)Rt(t.slice(0,-1));else if(e.size===1){let[r,i]=e.entries().next().value;r!==""&&Lt(t.slice(0,-1),r,i)}},Lt=(t,e,n)=>{if(t.length===0)return;let[r,i]=Be(t);r.set(i+e,n),r.delete(i)},Be=t=>t[t.length-1],Ue="or",jt="and",ns="and_not",U=class t{constructor(e){if(e?.fields==null)throw new Error('MiniSearch: option "fields" must be provided');let n=e.autoVacuum==null||e.autoVacuum===!0?je:e.autoVacuum;this._options={...Le,...e,autoVacuum:n,searchOptions:{...Ct,...e.searchOptions||{}},autoSuggestOptions:{...us,...e.autoSuggestOptions||{}}},this._index=new ee,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=We,this.addFields(this._options.fields)}add(e){let{extractField:n,stringifyField:r,tokenize:i,processTerm:s,fields:o,idField:u}=this._options,a=n(e,u);if(a==null)throw new Error(`MiniSearch: document does not have ID field "${u}"`);if(this._idToShortId.has(a))throw new Error(`MiniSearch: duplicate ID ${a}`);let l=this.addDocumentId(a);this.saveStoredFields(l,e);for(let f of o){let p=n(e,f);if(p==null)continue;let h=i(r(p,f),f),d=this._fieldIds[f],g=new Set(h).size;this.addFieldLength(l,d,this._documentCount-1,g);for(let v of h){let m=s(v,f);if(Array.isArray(m))for(let y of m)this.addTerm(d,l,y);else m&&this.addTerm(d,l,m)}}}addAll(e){for(let n of e)this.add(n)}addAllAsync(e,n={}){let{chunkSize:r=10}=n,i={chunk:[],promise:Promise.resolve()},{chunk:s,promise:o}=e.reduce(({chunk:u,promise:a},l,f)=>(u.push(l),(f+1)%r===0?{chunk:[],promise:a.then(()=>new Promise(p=>setTimeout(p,0))).then(()=>this.addAll(u))}:{chunk:u,promise:a}),i);return o.then(()=>this.addAll(s))}remove(e){let{tokenize:n,processTerm:r,extractField:i,stringifyField:s,fields:o,idField:u}=this._options,a=i(e,u);if(a==null)throw new Error(`MiniSearch: document does not have ID field "${u}"`);let l=this._idToShortId.get(a);if(l==null)throw new Error(`MiniSearch: cannot remove document with ID ${a}: it is not in the index`);for(let f of o){let p=i(e,f);if(p==null)continue;let h=n(s(p,f),f),d=this._fieldIds[f],g=new Set(h).size;this.removeFieldLength(l,d,this._documentCount,g);for(let v of h){let m=r(v,f);if(Array.isArray(m))for(let y of m)this.removeTerm(d,l,y);else m&&this.removeTerm(d,l,m)}}this._storedFields.delete(l),this._documentIds.delete(l),this._idToShortId.delete(a),this._fieldLength.delete(l),this._documentCount-=1}removeAll(e){if(e)for(let n of e)this.remove(n);else{if(arguments.length>0)throw new Error("Expected documents to be present. Omit the argument to remove all documents.");this._index=new ee,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldLength=new Map,this._avgFieldLength=[],this._storedFields=new Map,this._nextId=0}}discard(e){let n=this._idToShortId.get(e);if(n==null)throw new Error(`MiniSearch: cannot discard document with ID ${e}: it is not in the index`);this._idToShortId.delete(e),this._documentIds.delete(n),this._storedFields.delete(n),(this._fieldLength.get(n)||[]).forEach((r,i)=>{this.removeFieldLength(n,i,this._documentCount,r)}),this._fieldLength.delete(n),this._documentCount-=1,this._dirtCount+=1,this.maybeAutoVacuum()}maybeAutoVacuum(){if(this._options.autoVacuum===!1)return;let{minDirtFactor:e,minDirtCount:n,batchSize:r,batchWait:i}=this._options.autoVacuum;this.conditionalVacuum({batchSize:r,batchWait:i},{minDirtCount:n,minDirtFactor:e})}discardAll(e){let n=this._options.autoVacuum;try{this._options.autoVacuum=!1;for(let r of e)this.discard(r)}finally{this._options.autoVacuum=n}this.maybeAutoVacuum()}replace(e){let{idField:n,extractField:r}=this._options,i=r(e,n);this.discard(i),this.add(e)}vacuum(e={}){return this.conditionalVacuum(e)}conditionalVacuum(e,n){return this._currentVacuum?(this._enqueuedVacuumConditions=this._enqueuedVacuumConditions&&n,this._enqueuedVacuum!=null?this._enqueuedVacuum:(this._enqueuedVacuum=this._currentVacuum.then(()=>{let r=this._enqueuedVacuumConditions;return this._enqueuedVacuumConditions=We,this.performVacuuming(e,r)}),this._enqueuedVacuum)):this.vacuumConditionsMet(n)===!1?Promise.resolve():(this._currentVacuum=this.performVacuuming(e),this._currentVacuum)}async performVacuuming(e,n){let r=this._dirtCount;if(this.vacuumConditionsMet(n)){let i=e.batchSize||$e.batchSize,s=e.batchWait||$e.batchWait,o=1;for(let[u,a]of this._index){for(let[l,f]of a)for(let[p]of f)this._documentIds.has(p)||(f.size<=1?a.delete(l):f.delete(p));this._index.get(u).size===0&&this._index.delete(u),o%i===0&&await new Promise(l=>setTimeout(l,s)),o+=1}this._dirtCount-=r}await null,this._currentVacuum=this._enqueuedVacuum,this._enqueuedVacuum=null}vacuumConditionsMet(e){if(e==null)return!0;let{minDirtCount:n,minDirtFactor:r}=e;return n=n||je.minDirtCount,r=r||je.minDirtFactor,this.dirtCount>=n&&this.dirtFactor>=r}get isVacuuming(){return this._currentVacuum!=null}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}has(e){return this._idToShortId.has(e)}getStoredFields(e){let n=this._idToShortId.get(e);if(n!=null)return this._storedFields.get(n)}search(e,n={}){let{searchOptions:r}=this._options,i={...r,...n},s=this.executeQuery(e,n),o=[];for(let[u,{score:a,terms:l,match:f}]of s){let p=l.length||1,h={id:this._documentIds.get(u),score:a*p,terms:Object.keys(f),queryTerms:l,match:f};Object.assign(h,this._storedFields.get(u)),(i.filter==null||i.filter(h))&&o.push(h)}return e===t.wildcard&&i.boostDocument==null||o.sort(Mt),o}autoSuggest(e,n={}){n={...this._options.autoSuggestOptions,...n};let r=new Map;for(let{score:s,terms:o}of this.search(e,n)){let u=o.join(" "),a=r.get(u);a!=null?(a.score+=s,a.count+=1):r.set(u,{score:s,terms:o,count:1})}let i=[];for(let[s,{score:o,terms:u,count:a}]of r)i.push({suggestion:s,terms:u,score:o/a});return i.sort(Mt),i}get documentCount(){return this._documentCount}get termCount(){return this._index.size}static loadJSON(e,n){if(n==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJS(JSON.parse(e),n)}static async loadJSONAsync(e,n){if(n==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJSAsync(JSON.parse(e),n)}static getDefault(e){if(Le.hasOwnProperty(e))return Re(Le,e);throw new Error(`MiniSearch: unknown option "${e}"`)}static loadJS(e,n){let{index:r,documentIds:i,fieldLength:s,storedFields:o,serializationVersion:u}=e,a=this.instantiateMiniSearch(e,n);a._documentIds=fe(i),a._fieldLength=fe(s),a._storedFields=fe(o);for(let[l,f]of a._documentIds)a._idToShortId.set(f,l);for(let[l,f]of r){let p=new Map;for(let h of Object.keys(f)){let d=f[h];u===1&&(d=d.ds),p.set(parseInt(h,10),fe(d))}a._index.set(l,p)}return a}static async loadJSAsync(e,n){let{index:r,documentIds:i,fieldLength:s,storedFields:o,serializationVersion:u}=e,a=this.instantiateMiniSearch(e,n);a._documentIds=await pe(i),a._fieldLength=await pe(s),a._storedFields=await pe(o);for(let[f,p]of a._documentIds)a._idToShortId.set(p,f);let l=0;for(let[f,p]of r){let h=new Map;for(let d of Object.keys(p)){let g=p[d];u===1&&(g=g.ds),h.set(parseInt(d,10),await pe(g))}++l%1e3===0&&await Nt(0),a._index.set(f,h)}return a}static instantiateMiniSearch(e,n){let{documentCount:r,nextId:i,fieldIds:s,averageFieldLength:o,dirtCount:u,serializationVersion:a}=e;if(a!==1&&a!==2)throw new Error("MiniSearch: cannot deserialize an index created with an incompatible version");let l=new t(n);return l._documentCount=r,l._nextId=i,l._idToShortId=new Map,l._fieldIds=s,l._avgFieldLength=o,l._dirtCount=u||0,l._index=new ee,l}executeQuery(e,n={}){if(e===t.wildcard)return this.executeWildcardQuery(n);if(typeof e!="string"){let h={...n,...e,queries:void 0},d=e.queries.map(g=>this.executeQuery(g,h));return this.combineResults(d,h.combineWith)}let{tokenize:r,processTerm:i,searchOptions:s}=this._options,o={tokenize:r,processTerm:i,...s,...n},{tokenize:u,processTerm:a}=o,p=u(e).flatMap(h=>a(h)).filter(h=>!!h).map(os(o)).map(h=>this.executeQuerySpec(h,o));return this.combineResults(p,o.combineWith)}executeQuerySpec(e,n){let r={...this._options.searchOptions,...n},i=(r.fields||this._options.fields).reduce((v,m)=>({...v,[m]:Re(r.boost,m)||1}),{}),{boostDocument:s,weights:o,maxFuzzy:u,bm25:a}=r,{fuzzy:l,prefix:f}={...Ct.weights,...o},p=this._index.get(e.term),h=this.termResults(e.term,e.term,1,e.termBoost,p,i,s,a),d,g;if(e.prefix&&(d=this._index.atPrefix(e.term)),e.fuzzy){let v=e.fuzzy===!0?.2:e.fuzzy,m=v<1?Math.min(u,Math.round(e.term.length*v)):v;m&&(g=this._index.fuzzyGet(e.term,m))}if(d)for(let[v,m]of d){let y=v.length-e.term.length;if(!y)continue;g?.delete(v);let x=f*v.length/(v.length+.3*y);this.termResults(e.term,v,x,e.termBoost,m,i,s,a,h)}if(g)for(let v of g.keys()){let[m,y]=g.get(v);if(!y)continue;let x=l*v.length/(v.length+y);this.termResults(e.term,v,x,e.termBoost,m,i,s,a,h)}return h}executeWildcardQuery(e){let n=new Map,r={...this._options.searchOptions,...e};for(let[i,s]of this._documentIds){let o=r.boostDocument?r.boostDocument(s,"",this._storedFields.get(i)):1;n.set(i,{score:o,terms:[],match:{}})}return n}combineResults(e,n=Ue){if(e.length===0)return new Map;let r=n.toLowerCase(),i=rs[r];if(!i)throw new Error(`Invalid combination operator: ${n}`);return e.reduce(i)||new Map}toJSON(){let e=[];for(let[n,r]of this._index){let i={};for(let[s,o]of r)i[s]=Object.fromEntries(o);e.push([n,i])}return{documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:e,serializationVersion:2}}termResults(e,n,r,i,s,o,u,a,l=new Map){if(s==null)return l;for(let f of Object.keys(o)){let p=o[f],h=this._fieldIds[f],d=s.get(h);if(d==null)continue;let g=d.size,v=this._avgFieldLength[h];for(let m of d.keys()){if(!this._documentIds.has(m)){this.removeTerm(h,m,n),g-=1;continue}let y=u?u(this._documentIds.get(m),n,this._storedFields.get(m)):1;if(!y)continue;let x=d.get(m),w=this._fieldLength.get(m)[h],k=ss(x,g,this._documentCount,w,v,a),E=r*i*p*y*k,T=l.get(m);if(T){T.score+=E,ls(T.terms,e);let M=Re(T.match,n);M?M.push(f):T.match[n]=[f]}else l.set(m,{score:E,terms:[e],match:{[n]:[f]}})}}return l}addTerm(e,n,r){let i=this._index.fetch(r,Dt),s=i.get(e);if(s==null)s=new Map,s.set(n,1),i.set(e,s);else{let o=s.get(n);s.set(n,(o||0)+1)}}removeTerm(e,n,r){if(!this._index.has(r)){this.warnDocumentChanged(n,e,r);return}let i=this._index.fetch(r,Dt),s=i.get(e);s==null||s.get(n)==null?this.warnDocumentChanged(n,e,r):s.get(n)<=1?s.size<=1?i.delete(e):s.delete(n):s.set(n,s.get(n)-1),this._index.get(r).size===0&&this._index.delete(r)}warnDocumentChanged(e,n,r){for(let i of Object.keys(this._fieldIds))if(this._fieldIds[i]===n){this._options.logger("warn",`MiniSearch: document with ID ${this._documentIds.get(e)} has changed before removal: term "${r}" was not present in field "${i}". Removing a document after it has changed can corrupt the index!`,"version_conflict");return}}addDocumentId(e){let n=this._nextId;return this._idToShortId.set(e,n),this._documentIds.set(n,e),this._documentCount+=1,this._nextId+=1,n}addFields(e){for(let n=0;nObject.prototype.hasOwnProperty.call(t,e)?t[e]:void 0,rs={[Ue]:(t,e)=>{for(let n of e.keys()){let r=t.get(n);if(r==null)t.set(n,e.get(n));else{let{score:i,terms:s,match:o}=e.get(n);r.score=r.score+i,r.match=Object.assign(r.match,o),Ft(r.terms,s)}}return t},[jt]:(t,e)=>{let n=new Map;for(let r of e.keys()){let i=t.get(r);if(i==null)continue;let{score:s,terms:o,match:u}=e.get(r);Ft(i.terms,o),n.set(r,{score:i.score+s,terms:i.terms,match:Object.assign(i.match,u)})}return n},[ns]:(t,e)=>{for(let n of e.keys())t.delete(n);return t}},is={k:1.2,b:.7,d:.5},ss=(t,e,n,r,i,s)=>{let{k:o,b:u,d:a}=s;return Math.log(1+(n-e+.5)/(e+.5))*(a+t*(o+1)/(t+o*(1-u+u*r/i)))},os=t=>(e,n,r)=>{let i=typeof t.fuzzy=="function"?t.fuzzy(e,n,r):t.fuzzy||!1,s=typeof t.prefix=="function"?t.prefix(e,n,r):t.prefix===!0,o=typeof t.boostTerm=="function"?t.boostTerm(e,n,r):1;return{term:e,fuzzy:i,prefix:s,termBoost:o}},Le={idField:"id",extractField:(t,e)=>t[e],stringifyField:(t,e)=>t.toString(),tokenize:t=>t.split(as),processTerm:t=>t.toLowerCase(),fields:void 0,searchOptions:void 0,storeFields:[],logger:(t,e)=>{typeof console?.[t]=="function"&&console[t](e)},autoVacuum:!0},Ct={combineWith:Ue,prefix:!1,fuzzy:!1,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:is},us={combineWith:jt,prefix:(t,e,n)=>e===n.length-1},$e={batchSize:1e3,batchWait:10},We={minDirtFactor:.1,minDirtCount:20},je={...$e,...We},ls=(t,e)=>{t.includes(e)||t.push(e)},Ft=(t,e)=>{for(let n of e)t.includes(n)||t.push(n)},Mt=({score:t},{score:e})=>e-t,Dt=()=>new Map,fe=t=>{let e=new Map;for(let n of Object.keys(t))e.set(parseInt(n,10),t[n]);return e},pe=async t=>{let e=new Map,n=0;for(let r of Object.keys(t))e.set(parseInt(r,10),t[r]),++n%1e3===0&&await Nt(0);return e},Nt=t=>new Promise(e=>setTimeout(e,t)),as=/[\n\r\p{Z}\p{P}]+/u;var Ve;function qe(t){return{lang:t?.lang??Ve?.lang,message:t?.message,abortEarly:t?.abortEarly??Ve?.abortEarly,abortPipeEarly:t?.abortPipeEarly??Ve?.abortPipeEarly}}var cs;function fs(t){return cs?.get(t)}var ps;function hs(t){return ps?.get(t)}var ds;function ms(t,e){return ds?.get(t)?.get(e)}function de(t){let e=typeof t;return e==="string"?`"${t}"`:e==="number"||e==="bigint"||e==="boolean"?`${t}`:e==="object"||e==="function"?(t&&Object.getPrototypeOf(t)?.constructor?.name)??"null":e}function _(t,e,n,r,i){let s=i&&"input"in i?i.input:n.value,o=i?.expected??t.expects??null,u=i?.received??de(s),a={kind:t.kind,type:t.type,input:s,expected:o,received:u,message:`Invalid ${e}: ${o?`Expected ${o} but r`:"R"}eceived ${u}`,requirement:t.requirement,path:i?.path,issues:i?.issues,lang:r.lang,abortEarly:r.abortEarly,abortPipeEarly:r.abortPipeEarly},l=t.kind==="schema",f=i?.message??t.message??ms(t.reference,a.lang)??(l?hs(a.lang):null)??r.message??fs(a.lang);f!==void 0&&(a.message=typeof f=="function"?f(a):f),l&&(n.typed=!1),n.issues?n.issues.push(a):n.issues=[a]}function P(t){return{version:1,vendor:"valibot",validate(e){return t["~run"]({value:e},qe())}}}function ys(t,e){return Object.hasOwn(t,e)&&e!=="__proto__"&&e!=="prototype"&&e!=="constructor"}function Ge(t,e){let n=[...new Set(t)];return n.length>1?`(${n.join(` ${e} `)})`:n[0]??"never"}function Wt(t){if(t.path){let e="";for(let n of t.path)if(typeof n.key=="string"||typeof n.key=="number")e?e+=`.${n.key}`:e+=n.key;else return null;return e}return null}var me=class extends Error{constructor(t){super(t[0].message),this.name="ValiError",this.issues=t}};function te(t){return{kind:"transformation",type:"args",reference:te,async:!1,schema:t,"~run"(e,n){let r=e.value;return e.value=(...i)=>{let s=this.schema["~run"]({value:i},n);if(s.issues)throw new me(s.issues);return r(...s.value)},e}}}function Ke(t){return{kind:"validation",type:"non_empty",reference:Ke,async:!1,expects:"!0",message:t,"~run"(e,n){return e.typed&&e.value.length===0&&_(this,"length",e,n,{received:"0"}),e}}}function ye(t){return{kind:"transformation",type:"returns",reference:ye,async:!1,schema:t,"~run"(e,n){let r=e.value;return e.value=(...i)=>{let s=this.schema["~run"]({value:r(...i)},n);if(s.issues)throw new me(s.issues);return s.value},e}}}function He(t){return{kind:"transformation",type:"returns",reference:He,async:!1,schema:t,"~run"(e,n){let r=e.value;return e.value=async(...i)=>{let s=await this.schema["~run"]({value:await r(...i)},n);if(s.issues)throw new me(s.issues);return s.value},e}}}function Bt(t,e,n){return typeof t.fallback=="function"?t.fallback(e,n):t.fallback}function Ut(t){let e={};for(let n of t)if(n.path){let r=Wt(n);r?(e.nested||(e.nested={}),e.nested[r]?e.nested[r].push(n.message):e.nested[r]=[n.message]):e.other?e.other.push(n.message):e.other=[n.message]}else e.root?e.root.push(n.message):e.root=[n.message];return e}function ve(t,e,n){return typeof t.default=="function"?t.default(e,n):t.default}function R(t,e){return{kind:"schema",type:"array",reference:R,expects:"Array",async:!1,item:t,message:e,get"~standard"(){return P(this)},"~run"(n,r){let i=n.value;if(Array.isArray(i)){n.typed=!0,n.value=[];for(let s=0;sn.expects),"|"),async:!1,options:t,message:e,get"~standard"(){return P(this)},"~run"(n,r){let i,s,o;for(let u of this.options){let a=u["~run"]({value:n.value},r);if(a.typed)if(a.issues)s?s.push(a):s=[a];else{i=a;break}else o?o.push(a):o=[a]}if(i)return i;if(s){if(s.length===1)return s[0];_(this,"type",n,r,{issues:$t(s)}),n.typed=!0}else{if(o?.length===1)return o[0];_(this,"type",n,r,{issues:$t(o)})}return n}}}function nt(t){return{kind:"schema",type:"void",reference:nt,expects:"void",async:!1,message:t,get"~standard"(){return P(this)},"~run"(e,n){return e.value===void 0?e.typed=!0:_(this,"type",e,n),e}}}function rt(t,e,n){let r=t["~run"]({value:e},qe(n));if(r.issues)throw new me(r.issues);return r.value}function q(...t){return{...t[0],pipe:t,get"~standard"(){return P(this)},"~run"(e,n){for(let r of t)if(r.kind!=="metadata"){if(e.issues&&(r.kind==="schema"||r.kind==="transformation")){e.typed=!1;break}(!e.issues||!n.abortEarly&&!n.abortPipeEarly)&&(e=r["~run"](e,n))}return e}}}function G(t,e,n){let r=t["~run"]({value:e},qe(n));return{typed:r.typed,success:!r.issues,output:r.value,issues:r.issues}}function Vt(t){let e="";for(let n of t){e&&(e+=`
+`),e+=`\xD7 ${n.message}`;let r=Wt(n);r&&(e+=`
+ \u2192 at ${r}`)}return e}var qt=100,ge=300,xe=(i=>(i.Disabled="disabled",i.Day="day",i.Week="week",i.Month="month",i))(xe||{}),vs='/[|\\t\\n\\r\\^"= -#%-*,.`\\/<>:;?@[-\\]_{}\\u00A0\\u00A1\\u00A7\\u00AB\\u00B6\\u00B7\\u00BB\\u00BF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u09FD\\u0A76\\u0AF0\\u0C77\\u0C84\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166E\\u1680\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2000-\\u200A\\u2010-\\u2029\\u202F-\\u2043\\u2045-\\u2051\\u2053-\\u205F\\u207D\\u207E\\u208D\\u208E\\u2308-\\u230B\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E4F\\u3000-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA8FC\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]/'.slice(1,-1),Gt=new RegExp(`${vs}+`,"u"),Kt=/[|\[\]\(\)<>\{\} \t\n\r]/u;var Ht=!1,se=null,oe=Je(()=>tt([A(),I(),z(),Ze(),V(A(),oe),R(oe)])),Xt=j({content:b(I(),1),basename:b(I(),10),displayName:b(I(),10),directory:b(I(),7),aliases:b(I(),1),tags:b(I(),1)}),Jt=j({weights:b(Xt,rt(Xt,{})),weightCustomProperties:b(V(A(),I()),{}),recencyBoost:b(Xe(xe),"disabled"),downrankedFoldersFilters:b(R(A()),[]),ignoreDiacritics:b(z(),!0),ignoreArabicDiacritics:b(z(),!1),tokenizeUrls:b(z(),!0),splitCamelCase:b(z(),!0),fuzziness:b(re(["0","1","2"]),"1"),renderLineReturnInExcerpts:b(z(),!0),ignore:b(R(A()),[]),tokenizers:b(V(q(A(),Ke()),V(A(),oe)),{})});async function N(){if(se)return se;let t=await ae.getConfig("silversearch",{}),e=G(Jt,t);if(e.success)se=e.output;else{if(!Ht){let n=Object.entries(Ut(e.issues).nested??{}).map(([r,i])=>i?`${i.join(" & ")} in "${r}"`:"").filter(r=>r).join("; ");await C.flashNotification(`Silersearch - There was an error in your CONFIG: ${n}`),Ht=!0}se=rt(Jt,{})}return se}var gs=["\\u30FC","\\u309A","\\u3099"],xs=gs.join("|"),Qt=new RegExp(`(?!${xs})\\p{Diacritic}`,"gu");function Zt(t){return t.replace(/(\*|_)+(.+?)(\*|_)+/g,(e,n,r)=>r)}function st(t){return/[a-z][A-Z]/.test(t)?t.replace(/([a-z](?=[A-Z]))/g,"$1 ").split(" ").filter(n=>n):[]}function ot(t){return t.includes("-")?t.split("-").filter(e=>e):[]}function $(t,e=!1){if(t==null)return"";if(e){t=t.replace(/([^\u0621-\u063A\u0641-\u064A\u0660-\u0669a-zA-Z 0-9])/g,"").replace(/(آ|إ|أ)/g,"\u0627").replace(/(ة)/g,"\u0647").replace(/(ئ|ؤ)/g,"\u0621").replace(/(ى)/g,"\u064A");for(let n=0;n<10;n++)t.replace(String.fromCharCode(1632+n),String.fromCharCode(48+n))}return t=t.replaceAll("`","[__silversearch__backtick__]"),t=t.replaceAll("^","[__silversearch__caret__]"),t=t.normalize("NFD").replace(Qt,"").normalize("NFC"),t=t.replaceAll("[__silversearch__backtick__]","`"),t=t.replaceAll("[__silversearch__caret__]","^"),t}function Yt(t){return t.replace(new RegExp(`(?!\\^|\`)${Qt.source}`,"gu"),"")}function ut(t){let e=[],n=-1,r=0;for(;++r<100;){let i=ws(t,n,ge);if(!i.length)break;n=i.at(-1).offset,e.push(i)}return e}function ws(t,e,n){let r=t.find(i=>i.offset>e);return r?t.filter(i=>i.offset>e&&i.offset<=r.offset+n):[]}var bs=/\r\n?|\n/g,ks=/\0/g;function lt(t){let e=t;return e=e.replace(bs,`
+`),e=e.replace(ks,"\uFFFD"),e}var at=(t,e)=>{let n=0,r=0,i=0;for(;nt.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),_s=({link:t,startSyntax:e,endSyntax:n,links:r})=>{if(t.startsWith(e)){if(t.endsWith(n))return!1;if(t.includes(n)){let o=t.replace(/.?http/g,"http").substring(0,t.lastIndexOf(n)).replace(new RegExp(`\\${n}$`,"g"),"");return!r.some(u=>u.href===o)}}return!0},Es=t=>{let e=/mailto:([^\s[;]+)/g;return t.map(n=>n.href.match(e)||n.href.includes("@")?{...n,href:n.href.startsWith("mailto")?n.href:`mailto:${n.href}`,type:"email"}:n)},en=t=>t.startsWith("http")||t.startsWith("/")||t.includes("@"),ct=({line:t,url:e,lineLinks:n=[]})=>{var r;let i=((r=t?.match(new RegExp(tn(e),"g")))===null||r===void 0?void 0:r.length)||0,s=n?.reduce((o,u)=>o+(u.href.includes(e)?1:0)+(u.format==="[]()"&&u.text.includes(e)?1:0),0);return i>0&&s{var r,i;let s="",o=(((r=n?.match(/https:\/\/.*/g))===null||r===void 0?void 0:r[0])||"").replace("https://","");for(let u of e){let a=at(o,u.href);a.length>s.length&&(s=a)}if(s.length>0){let u=((i=t?.match(new RegExp(tn(s),"g")))===null||i===void 0?void 0:i.length)||0,a=e?.reduce((l,f)=>l+(f.href.includes(s)?1:0)+(f.format==="[]()"&&f.text.includes(s)?1:0),0);return u>0&&a{var e,n,r,i;let s=lt(t).split(`
+`),o=/.?\[([^\]]*)\]\(([^\][]*)\)/g,u=/<[^>]*>/g,a=/.?\(([^)]*)\)/g,l=/.?https?:\/\/[^\s[;]+/g,f=[];for(let p of s){let h=p.match(o)||[],d=p.match(u)||[],g=p.match(a)||[],v=p.match(l)||[],m=[];for(let y of h)if(!y.startsWith("!")){let x=y?.replace(/^.?\[/g,"[")||"",w=x.indexOf("["),k=x.indexOf("]",w+1),E=x;if(w!==-1&&k!==-1&&x[k+1]==="("){let Bn=k+1,Se=0,Te=-1;for(let le=Bn;le]*)>/))===null||i===void 0?void 0:i[1])||"").trim();if(en(x)){let w=x;if(ct({line:p,url:w,lineLinks:m})){let k={text:x,href:w,line:p,raw:y,type:"link",format:"<>"};f.push(k),m.push(k)}}}for(let y of g){let x=y.replace(/^.?\(/g,"("),w=x.match(/\(([^)]*)\)/),k=(w?.[1]||"").trim();if(!y.startsWith("]")&&en(k)){let E=k;if(ct({line:p,url:E,lineLinks:m})){let T={text:k,href:E,line:p,raw:x,type:"link",format:"()"};f.push(T),m.push(T)}}}for(let y of v)if([{start:"[",end:"]"},{start:"<",end:">"},{start:"(",end:")"}].every(w=>_s({link:y,startSyntax:w.start,endSyntax:w.end,links:m}))&&Ps({line:p,links:m,link:y})){let w=y.replace(/^.?http/g,"http"),k=w;if(ct({line:p,url:k,lineLinks:m})){let E={text:w,href:k,line:p,raw:w,type:"link",format:"raw"};f.push(E),m.push(E)}}}return Es(f)};var As=j({init:q(ne(),te(ie([oe])),He(nt())),isTokenizable:q(ne(),te(ie([A()])),ye(z())),tokenize:q(ne(),te(ie([A()])),ye(R(A())))}),we=class t{constructor(e){this.implementation=e}static async loadFromPath(e,n){let r;try{let s=await D.readDocument(e),o=new Blob([s],{type:"text/javascript"}),u=URL.createObjectURL(o);r=await import(u),URL.revokeObjectURL(u)}catch{return console.warn(`[Silversearch] Failed to load tokenizer at ${e}. Maybe the path is wrong?`),null}let i=G(As,r);if(i.success){let s=new t(i.output);return await s.implementation.init(n),s}else return console.warn(`[Silversearch] Failed to load tokenizer at ${e}.
+ ${Vt(i.issues)}`),null}tryTokenization(e){try{if(this.implementation.isTokenizable(e))return this.implementation.tokenize(e)}catch(n){console.warn(`[Silversearch] Tokenizer failed with error ${n}. Ignoring!`)}return null}};function nn(t,e){try{let n=sn(t,e.tokenizers),r=[];return e.tokenizeUrls&&(r=ft(t).map(o=>o.href)),[...on(t).flatMap(o=>[o,...ot(o),...st(o)]),...n,...r]}catch(n){return console.error("[Silversearch] Error tokenizing text, skipping document",n),[]}}function rn(t,e){let n=ft(t).map(i=>i.href);t=n.reduce((i,s)=>i.replace(s,""),t);let r=[...on(t),...n];return{combineWith:"OR",queries:[{combineWith:"AND",queries:r},{combineWith:"AND",queries:sn(t,e.tokenizers)},{combineWith:"AND",queries:r.flatMap(ot)},{combineWith:"AND",queries:r.flatMap(st)}]}}function sn(t,e){return Ss(t,e)??t.split(Kt).filter(Boolean)}function on(t){return t.split(Gt).filter(Boolean)}function Ss(t,e){let n=e.flatMap(r=>r.tryTokenization(t)??[]);return n.length===0?null:n}function un(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Ts(t){if(!t.length)return/^$/g;t.sort((n,r)=>r.length-n.length);let e=`(${t.map(n=>`\\b${un(n)}\\b|${un(n)}`).join("|")})`;return new RegExp(`${e}`,"gui")}function ln(t,e){if(t=an(t),e.renderLineReturnInExcerpts){let n=new RegExp(/(?:\r\n|\r|\n)/g);t=t.split(n).filter(r=>r).join(`
+`),t=t.replaceAll(`
+`," ")}return t.trim()}async function pt(t,e,n){let r=await N();e=e.map(an);let i=Ts(e),s=t;r.ignoreDiacritics&&(t=$(t,r.ignoreArabicDiacritics));let o=new Date().getTime(),u=null,a=[],l=0;for(;(u=i.exec(t))!==null&&!(++l>=100||new Date().getTime()-o>50);){let f=u.index,p=f+u[0].length,h=s.substring(f,p).trim();h&&u.index>=0&&a.push({match:h,offset:u.index})}if(n&&(n.query.text.length>1||n.getExactTerms().length>0)){let f=t.indexOf(n.getBestStringForExcerpt());f>-1&&a.find(p=>p.offset===f)&&a.unshift({offset:f,match:n.getBestStringForExcerpt()})}return a.map(f=>({...f,match:ln(f.match,{renderLineReturnInExcerpts:r.renderLineReturnInExcerpts})}))}async function ht(t,e){let n=await N();try{let r=Math.max(0,e-qt),i=Math.min(t.length,e+ge);if(t=(r>0?"\u2026":"")+t.slice(r,i).trim()+(i0&&(t=t.slice(s+1))}return{excerpt:ln(t,{renderLineReturnInExcerpts:n.renderLineReturnInExcerpts}),offset:e}}catch(r){return console.error("[Silversearch] Error while creating excerpt",r),{excerpt:"",offset:0}}}function an(t){return t.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function Cs(t){return t!==""?t.split(".").pop().toLowerCase():"md"}function O(t){return Fs({path:t})}function K(t){return Cs(t)==="md"}function Fs(t){let e=t.path;return K(t.path)&&(e=e.slice(0,-3)),t.details?.type==="linecolumn"?(e+=`@L${t.details.line}`,t.details.column!==1&&(e+=`C${t.details.column}`)):t.details?.type==="position"?e+=`@${t.details.pos}`:t.details?.type==="header"&&(e+=`#${t.details.header}`),e}function cn(t){return t.split("/").slice(0,-1).join("/")}function fn(t){return t.split("/").pop()}var Ms=j({content:A(),cacheMode:et(re(["persistent","session"]),"session"),navigationMap:b(R(Ye({type:Qe("range"),from:I(),to:I(),tail:A()})))});async function pn(t,e){if(K(t))try{return{content:await D.readPage(O(t)),cacheMode:"session"}}catch{return null}else{let n;try{n=e??await D.getDocumentMeta(O(t))}catch{return null}let r;try{r=await ce.dispatchEvent("silversearch:index",{meta:n})}catch{return console.warn("[silversearch] Eventhandler threw error. Discarding it."),null}if(r.length===0)return null;r.length>1&&console.warn(`[silversearch] We got multiple responses while indexing ${n.name}. Can't handle that.`);let i=G(Ms,r[0]);return i.success?i.output:(console.warn("[silversearch] Eventhandler didn't return well-formed result. Discarding it."),null)}}var _n=Et(bn()),kn=4,J=class t{constructor(e,n){this.minisearch=new U(t.getOptions(e,n)),this.entryCache=new Map,this.tokenizers=n,this.ignore=(0,_n.default)().add(e.ignore)}static async create(e){let n=(await Promise.all(Object.entries(e.tokenizers).map(async([r,i])=>await we.loadFromPath(r,i)))).filter(r=>r!==null);return new t(e,n)}static async loadFromCache(e){if(await F.get("silversearch-cache-lock"))return await C.flashNotification("Silversearch - Last cache failed, resetting index"),t.deleteCache(),null;let n=await F.get("silversearch-cache");if(!n||n.version!==kn||typeof n.minisearch!="string"||typeof n.entries!="object")return console.log("[Silversearch] Couldn't find cache or cache version mismatched"),t.deleteCache(),null;let r=await this.create(e);return r.minisearch=await U.loadJSONAsync(n.minisearch,t.getOptions(e,r.tokenizers)),r.entryCache=new Map(n.entries),r}static async deleteCache(){await F.del("silversearch-cache"),await F.del("silversearch-cache-lock")}async writeToCache(){clearTimeout(this.cacheWritingTimer),await F.set("silversearch-cache-lock",!0),this.cacheWritingTimer=setTimeout(async()=>{await F.set("silversearch-cache",{version:kn,minisearch:JSON.stringify(this.minisearch),entries:Array.from(this.entryCache.entries()).filter(e=>e[1].cacheMode==="persistent")}),await F.set("silversearch-cache-lock",!1)},250)}async reindex(e=!1){e||await C.flashNotification("Silversearch - Reindexing, this can cause performance problems"),this.minisearch.removeAll();let n=(await D.listFiles()).map(r=>r.name).filter(this.isIndexedPath);console.log(`[Silversearch] Indexing ${n.length} pages`),e||await C.showProgress(0,"index"),await this.indexByPaths(n,(r,i)=>{e||C.showProgress(Math.round(r/i*100),"index"),F.set("silversearch-progress",{done:r,all:i})}),F.del("silversearch-progress"),e||(await C.showProgress(),await C.flashNotification("Silversearch - Full reindex done!"))}async indexByPath(e){await this.indexByPaths([e])}async indexByPaths(e,n){let r=0;for(let i of e)try{if(this.entryCache.delete(i),this.ignore.ignores(i))console.log(`[Silversearch] Ignoring ${O(i)}`),this.minisearch.discard(i);else{console.log(`[Silversearch] Indexing ${O(i)}`);let s=await this.getCacheEntry(i);if(!s)throw null;this.minisearch.has(s.path)?this.minisearch.replace(s):this.minisearch.add(s)}n&&n(++r,e.length)}catch{console.warn(`[Silversearch] Failed to index ${O(i)}. Skipping!`)}await this.writeToCache()}async deleteByPath(e){await this.deleteByPaths([e])}async deleteByPaths(e){for(let n of e)try{this.entryCache.delete(n),this.minisearch.discard(n)}catch{console.error("[Silversearch] Something went wrong. Failed to delete page")}await this.writeToCache()}async search(e,n){let r=await N();if(e.isEmpty())return[];let i={0:0,1:.1,2:.2}[r.fuzziness],s=rn(e.segmentsToStr(),{tokenizers:this.tokenizers}),o=this.minisearch.search(s,{prefix:p=>p.length>=n.prefixLength,fuzzy:p=>p.length<=3?0:p.length<=5?i/2:i,boost:{basename:r.weights.basename,aliases:r.weights.aliases,displayName:r.weights.displayName,directory:r.weights.directory,tags:r.weights.tags,unmarkedTags:r.weights.tags},tokenize:p=>[p],boostDocument(p,h,d){if(!d?.lastModified||r.recencyBoost==="disabled")return 1;let g=d?.lastModified,m=(new Date().getTime()-g)/(24*3600*1e3),y={day:-3,week:-.3,month:-.1};return 1+Math.exp(y[r.recencyBoost]*(m/1e3))}});if(e.query.ext?.length&&(o=o.filter(p=>{let h=p.id.includes(".")?"."+p.id.split(".").pop():".md";return e.query.ext?.some(d=>h.startsWith(d.startsWith(".")?d:"."+d))})),e.query.path&&(o=o.filter(p=>e.query.path?.some(h=>p.id.toLowerCase().includes(h.toLowerCase())))),e.query.exclude.path&&(o=o.filter(p=>!e.query.exclude.path?.some(h=>p.id.toLowerCase().includes(h.toLowerCase())))),!o.length)return[];if(n.singleFilePath)return o.filter(p=>p.id===n.singleFilePath);let u=await this.getCacheEntries(o.map(p=>p.id)),a=e.getTags();for(let p of o){let h=p.id,d=O(h);r.downrankedFoldersFilters.length>0&&(r.downrankedFoldersFilters.some(m=>d===m||d.startsWith(m+"/"))||d.split("/").some(m=>r.downrankedFoldersFilters.includes(m)))&&(p.score/=10);let g=u.find(v=>v.path===h);if(g)for(let[v,m]of Object.entries(r.weightCustomProperties)){let y=g.metadata[v];y&&p.terms.some(x=>y.includes(x))&&(p.score*=m)}for(let v of a)(g?.tags??[]).includes(v)&&(p.score*=100)}o=o.sort((p,h)=>h.score-p.score).slice(0,50);let l=e.getExactTerms();l.length&&(o=o.filter(p=>{let h=u.find(v=>v.path===p.id),d=h?.name.toLowerCase()??"",g=(h?.cleanedContent??"").toLowerCase();return l.every(v=>g.includes(v)||$(d,r.ignoreArabicDiacritics).includes(v))}));let f=e.query.exclude.text;return f.length&&(o=o.filter(p=>{let h=(u.find(d=>d.path===p.id)?.content??"").toLowerCase();return f.every(d=>!h.includes(d))})),o}async getSuggestions(e,n){let r=await this.search(e,{prefixLength:1,singleFilePath:n?.singleFilePath}),i=await this.getCacheEntries(r.map(o=>o.id));return(await Promise.all(r.map(async o=>{let u=i.find(d=>d.path===o.id);if(!u)return console.warn(`[Silversearch] Note "${o.name}" disappeared. Skipping`),null;let a=[...o.terms,...e.getExactTerms(),...e.getTags()],l=await pt(u.content,a,e),f=await pt(u.name,a),p;if(n?.singleFilePath){let d=ut(l),g=e.getExactTerms();g.length&&(d=d.filter(m=>g.every(y=>m.some(x=>x.match.includes(y)))));let v=d.map(m=>m[0].offset);p=await Promise.all(v.map(m=>ht(u?.content??"",m)))}else{let d=ut(l),g=0;d.length&&(g=d.reduce((m,y)=>y.length>m.length?y:m)[0].offset),p=[await ht(u.content,g)]}return{score:o.score,foundWords:a,matches:l,matchesName:f,excerpts:p,name:u.name,basename:u.basename,content:u.content,navigationMap:u.navigationMap}}))).filter(o=>o!==null)}async getCacheEntries(e){return(await Promise.all(e.map(r=>this.getCacheEntry(r)))).filter(r=>!!r)}async getCacheEntry(e){let n,r=this.entryCache.get(e);if(r)return r;try{let u=O(e);n=K(e)?await D.getPageMeta(u):await D.getDocumentMeta(u)}catch{return console.log("[Silversearch] Couldn't find the specified file:",e),null}let i=await t.pathToIndexableEntry(e,n);if(!i)return null;let s=Object.fromEntries(Object.entries(n).filter(([u,a])=>!["ref","tag","tags","itags","name","created","lastModified","perm","lastOpened","pageDecoration","aliases","extension","size","contentType"].includes(u))),o={...i.entry,...i.info,cleanedContent:Zt($(i.entry.content)),metadata:s,tags:n.tags?.map(u=>"#"+u)??[]};return this.entryCache.set(e,o),o}static async pathToIndexableEntry(e,n){let r=O(e),i;try{i=n??(K(e)?await D.getPageMeta(r):await D.getDocumentMeta(r))}catch{return null}let s=await pn(e,i);if(!s)return null;let o=Yt(s.content),u={path:e,name:r,content:o,basename:fn(r),directory:cn(r),aliases:i.aliases??[],displayName:i.displayName??"",unmarkedTags:i.tags??[],lastModified:new Date(i.lastModified).getTime()},{content:a,...l}=s;return{entry:u,info:l}}isIndexedPath(e){return!e.endsWith(".plug.js")}static getOptions(e,n){return{tokenize:r=>nn(r,{tokenizers:n,tokenizeUrls:e.tokenizeUrls}),processTerm:r=>(e.ignoreDiacritics?$(r,e.ignoreArabicDiacritics):r).toLowerCase(),idField:"path",fields:["content","basename","directory","aliases","displayName","unmarkedTags"],storeFields:["lastModified"]}}};var Sn=Et(An()),wt=["ext","path"],Pe=class{#e;constructor(e="",n){n.ignoreDiacritics&&(e=$(e,n.ignoreArabicDiacritics));let r=(0,Sn.parse)(e.toLowerCase(),{tokenize:!0,keywords:wt});r.text=r.text??[],r.exclude=r.exclude??{text:[]},r.exclude.text=r.exclude.text??[],Array.isArray(r.exclude.text)||(r.exclude.text=[r.exclude.text]),r.exclude.text=r.exclude.text.filter(s=>s.length);for(let s of wt){let o=r[s];o&&(r[s]=Array.isArray(o)?o:[o]);let u=r.exclude[s];u&&(r.exclude[s]=Array.isArray(u)?u:[u])}this.query=r;let i=this.query.text.filter(s=>s.startsWith(".")).map(s=>s.slice(1));this.query.ext=[...new Set([...i,...this.query.ext??[]])],this.query.text=this.query.text.filter(s=>!s.startsWith(".")),this.#e=e.match(/"([^"]+)"/g)?.map(s=>s.replace(/"/g,""))??[]}isEmpty(){for(let e of wt)if(this.query[e]?.length||this.query.text.length)return!1;return!0}segmentsToStr(){return this.query.text.join(" ")}getTags(){return this.query.text.filter(e=>e.startsWith("#"))}getTagsWithoutHashtag(){return this.getTags().map(e=>e.replace(/^#/,""))}getExactTerms(){return[...new Set([...this.query.text.filter(e=>e.split(" ").length>1),...this.#e].map(e=>e.toLowerCase()))]}getBestStringForExcerpt(){return this.#e.length?this.#e.sort((e,n)=>n.length-e.length)[0]??"":this.segmentsToStr()}};var Tn="1.2.5";var S=null,W=[],Ae=null;function bt(t=!0,e=!1){return Ae||(Ae=Zs(t,e).then(()=>{Ae=null})),Ae}async function Zs(t,e){if(S)return;let n=await N();S=await J.loadFromCache(n),!S&&t?(S=await J.create(n),await S.reindex(e)):S&&W.length&&(await S.indexByPaths(W.filter(r=>r.action==="index").map(r=>r.path)),await S.deleteByPaths(W.filter(r=>r.action==="delete").map(r=>r.path))),W=[]}async function Cn(t=""){await C.showPanel("modal",1,Tt,t?`globalThis.DEFAULT_QUERY = ${JSON.stringify(t)};`+ze:ze)}async function Fn(){await c("silversearch.openSearch")}async function Mn(){await bt(!1)}async function Dn({name:t}){let e=`${t}.md`;S?await S.indexByPath(e):W.push({action:"index",path:e})}async function In(t){let e=t;S?await S.indexByPath(e):W.push({action:"index",path:e})}async function zn(t){S?await S.deleteByPath(t):W.push({action:"delete",path:t})}async function On(t,e){await bt(!0,e.silent??!1);let n=await N(),r=new Pe(t,{ignoreDiacritics:n.ignoreDiacritics,ignoreArabicDiacritics:n.ignoreArabicDiacritics});return S.getSuggestions(r,{singleFilePath:e.singleFilePath})}async function Rn(t=!1){await J.deleteCache(),S=null,await bt(!0,t)}async function Ln(){await C.flashNotification(`Silversearch - Version ${Tn}`)}async function jn(){await C.flashNotification("Silversearch - Cleared History"),F.set("silversearch-history",[])}var Nn={startSearch:Fn,getVersion:Ln,clearHistory:jn,openSearch:Cn,reindex:Rn,init:Mn,indexPage:Dn,indexDocument:In,deleted:zn,search:On},$n={name:"silversearch",functions:{startSearch:{path:"./silversearch.ts:startSearch",command:{name:"Silversearch: Search",key:"Ctrl-s",mac:"Cmd-s"}},getVersion:{path:"./silversearch.ts:showVersion",command:{name:"Silversearch: Version"}},clearHistory:{path:"./silversearch.ts:clearHistory",command:{name:"Silversearch: Clear History"}},openSearch:{path:"./silversearch.ts:openSearch",syscall:"silversearch.openSearch"},reindex:{path:"./silversearch.ts:reindex",syscall:"silversearch.reindex",command:{name:"Silversearch: Reindex"}},init:{path:"./silversearch.ts:init",events:["editor:init"]},indexPage:{path:"./silversearch.ts:indexPage",events:["page:index"]},indexDocument:{path:"./silversearch.ts:indexDocument",events:["document:index"]},deleted:{path:"./silversearch.ts:deleted",events:["file:deleted"]},search:{path:"./silversearch.ts:search",syscall:"silversearch.search"}},assets:{}},ol={manifest:$n,functionMapping:Nn};St(Nn,$n,self.postMessage);export{ol as plug};
+/*! Bundled license information:
+
+search-query-parser/lib/search-query-parser.js:
+ (*!
+ * search-query-parser.js
+ * Copyright(c) 2014-2019
+ * MIT Licensed
+ *)
+*/
+//# sourceMappingURL=silversearch.plug.js.map
diff --git a/configs/notes/space/_plug/treeview.plug.js b/configs/notes/space/_plug/treeview.plug.js
new file mode 100644
index 0000000..e963797
--- /dev/null
+++ b/configs/notes/space/_plug/treeview.plug.js
@@ -0,0 +1,39 @@
+var Bt=Object.defineProperty;var te=(n,e)=>{for(var t in e)Bt(n,t,{get:e[t],enumerable:!0})};var le=n=>{throw new Error("Not initialized yet")},je=typeof window>"u"&&typeof globalThis.WebSocketPair>"u";typeof Deno>"u"&&(self.Deno={args:[],build:{arch:"x86_64"},env:{get(){}}});var Me=new Map,Ue=0;je&&(globalThis.syscall=async(n,...e)=>await new Promise((t,r)=>{Ue++,Me.set(Ue,{resolve:t,reject:r}),le({type:"sys",id:Ue,name:n,args:e})}));function tt(n,e,t){je&&(le=t,self.addEventListener("message",r=>{(async()=>{let s=r.data;switch(s.type){case"inv":{let a=n[s.name];if(!a)throw new Error(`Function not loaded: ${s.name}`);try{let o=await Promise.resolve(a(...s.args||[]));le({type:"invr",id:s.id,result:o})}catch(o){console.error("An exception was thrown as a result of invoking function",s.name,"error:",o.message),le({type:"invr",id:s.id,error:o.message})}}break;case"sysr":{let a=s.id,o=Me.get(a);if(!o)throw Error("Invalid request id");Me.delete(a),s.error?o.reject(new Error(s.error)):o.resolve(s.result)}break}})().catch(console.error)}),le({type:"manifest",manifest:e}))}function Vt(n){let e=atob(n),t=e.length,r=new Uint8Array(t);for(let s=0;s0?nt(t):void 0;e={method:n.method,headers:Object.fromEntries(n.headers.entries()),base64Body:r},n=n.url}return syscall("sandboxFetch.fetch",n,e)}globalThis.nativeFetch=globalThis.fetch;function _t(){globalThis.fetch=async function(n,e){let t=e&&e.body?nt(new Uint8Array(await new Response(e.body).arrayBuffer())):void 0,r=await Nt(n,e&&{method:e.method,headers:e.headers,base64Body:t});return new Response(r.base64Body?Vt(r.base64Body):null,{status:r.status,headers:r.headers})}}je&&_t();var O={};te(O,{confirm:()=>fn,copyToClipboard:()=>Rn,deleteLine:()=>Pn,dispatch:()=>mn,downloadFile:()=>nn,filterBox:()=>an,flashNotification:()=>sn,fold:()=>hn,foldAll:()=>Tn,getCurrentPage:()=>Lt,getCurrentPageMeta:()=>Ot,getCursor:()=>Et,getSelection:()=>Yt,getText:()=>Ht,getUiOption:()=>Zn,goHistory:()=>tn,hidePanel:()=>dn,insertAtCursor:()=>yn,insertAtPos:()=>un,moveCursor:()=>ln,moveCursorToLine:()=>pn,moveLineDown:()=>wn,moveLineUp:()=>Sn,navigate:()=>Mt,newWindow:()=>en,openCommandPalette:()=>Ft,openPageNavigator:()=>jt,openSearchPanel:()=>An,openUrl:()=>$t,prompt:()=>gn,rebuildEditorState:()=>qt,redo:()=>kn,reloadConfigAndCommands:()=>Qt,reloadPage:()=>Jt,reloadUI:()=>zt,replaceRange:()=>cn,save:()=>Ut,setSelection:()=>Dt,setText:()=>Xt,setUiOption:()=>In,showPanel:()=>on,toggleFold:()=>xn,undo:()=>vn,unfold:()=>bn,unfoldAll:()=>Cn,uploadFile:()=>rn,vimEx:()=>Gn});typeof self>"u"&&(self={syscall:()=>{throw new Error("Not implemented here")}});function d(n,...e){return globalThis.syscall(n,...e)}function Lt(){return d("editor.getCurrentPage")}function Ot(){return d("editor.getCurrentPageMeta")}function Ht(){return d("editor.getText")}function Xt(n,e=!1){return d("editor.setText",n,e)}function Et(){return d("editor.getCursor")}function Yt(){return d("editor.getSelection")}function Dt(n,e){return d("editor.setSelection",n,e)}function Ut(){return d("editor.save")}function Mt(n,e=!1,t=!1){return d("editor.navigate",n,e,t)}function jt(n="page"){return d("editor.openPageNavigator",n)}function Ft(){return d("editor.openCommandPalette")}function Jt(){return d("editor.reloadPage")}function zt(){return d("editor.reloadUI")}function qt(){return d("editor.rebuildEditorState")}function Qt(){return d("editor.reloadConfigAndCommands")}function $t(n,e=!1){return d("editor.openUrl",n,e)}function en(){return d("editor.newWindow")}function tn(n){return d("editor.goHistory",n)}function nn(n,e){return d("editor.downloadFile",n,e)}function rn(n,e){return d("editor.uploadFile",n,e)}function sn(n,e="info"){return d("editor.flashNotification",n,e)}function an(n,e,t="",r=""){return d("editor.filterBox",n,e,t,r)}function on(n,e,t,r=""){return d("editor.showPanel",n,e,t,r)}function dn(n){return d("editor.hidePanel",n)}function un(n,e){return d("editor.insertAtPos",n,e)}function cn(n,e,t){return d("editor.replaceRange",n,e,t)}function ln(n,e=!1){return d("editor.moveCursor",n,e)}function pn(n,e=1,t=!1){return d("editor.moveCursorToLine",n,e,t)}function yn(n,e=!1){return d("editor.insertAtCursor",n,e)}function mn(n){return d("editor.dispatch",n)}function gn(n,e=""){return d("editor.prompt",n,e)}function fn(n){return d("editor.confirm",n)}function Zn(n){return d("editor.getUiOption",n)}function In(n,e){return d("editor.setUiOption",n,e)}function hn(){return d("editor.fold")}function bn(){return d("editor.unfold")}function xn(){return d("editor.toggleFold")}function Tn(){return d("editor.foldAll")}function Cn(){return d("editor.unfoldAll")}function vn(){return d("editor.undo")}function kn(){return d("editor.redo")}function An(){return d("editor.openSearchPanel")}function Rn(n){return d("editor.copyToClipboard",n)}function Pn(){return d("editor.deleteLine")}function Sn(){return d("editor.moveLineUp")}function wn(){return d("editor.moveLineDown")}function Gn(n){return d("editor.vimEx",n)}var Se={};te(Se,{deleteAttachment:()=>Yn,deleteFile:()=>Fn,deletePage:()=>_n,fileExists:()=>Jn,getAttachmentMeta:()=>Hn,getFileMeta:()=>Mn,getPageMeta:()=>Bn,listAttachments:()=>On,listFiles:()=>Dn,listPages:()=>Wn,listPlugs:()=>Ln,readAttachment:()=>Xn,readFile:()=>Un,readPage:()=>Vn,writeAttachment:()=>En,writeFile:()=>jn,writePage:()=>Nn});function Wn(){return d("space.listPages")}function Bn(n){return d("space.getPageMeta",n)}function Vn(n){return d("space.readPage",n)}function Nn(n,e){return d("space.writePage",n,e)}function _n(n){return d("space.deletePage",n)}function Ln(){return d("space.listPlugs")}function On(){return d("space.listAttachments")}function Hn(n){return d("space.getAttachmentMeta",n)}function Xn(n){return d("space.readAttachment",n)}function En(n,e){return d("space.writeAttachment",n,e)}function Yn(n){return d("space.deleteAttachment",n)}function Dn(){return d("space.listFiles")}function Un(n){return d("space.readFile",n)}function Mn(n){return d("space.getFileMeta",n)}function jn(n,e){return d("space.writeFile",n,e)}function Fn(n){return d("space.deleteFile",n)}function Jn(n){return d("space.fileExists",n)}var _={};te(_,{applyAttributeExtractors:()=>tr,getEnv:()=>ar,getMode:()=>or,getSpaceConfig:()=>nr,getVersion:()=>ir,invokeCommand:()=>qn,invokeFunction:()=>zn,invokeSpaceFunction:()=>er,listCommands:()=>Qn,listSyscalls:()=>$n,reloadConfig:()=>sr,reloadPlugs:()=>rr});function zn(n,...e){return d("system.invokeFunction",n,...e)}function qn(n,e){return d("system.invokeCommand",n,e)}function Qn(){return d("system.listCommands")}function $n(){return d("system.listSyscalls")}function er(n,...e){return d("system.invokeSpaceFunction",n,...e)}function tr(n,e,t){return d("system.applyAttributeExtractors",n,e,t)}async function nr(n,e){return await d("system.getSpaceConfig",n)??e}function rr(){return d("system.reloadPlugs")}function sr(){return d("system.reloadConfig")}function ar(){return d("system.getEnv")}function or(){return d("system.getMode")}function ir(){return d("system.getVersion")}var E={};te(E,{del:()=>cr,get:()=>ur,set:()=>dr});function dr(n,e){return d("clientStore.set",n,e)}function ur(n){return d("clientStore.get",n)}function cr(n){return d("clientStore.delete",n)}var w={};te(w,{getFileMeta:()=>hr,listFiles:()=>Ir,readAsset:()=>Zr});function fr(n){let e=atob(n),t=e.length,r=new Uint8Array(t);for(let s=0;sjr,DIRTY:()=>Q,EMPTY_PATH:()=>Gr,INVALID:()=>f,NEVER:()=>Gs,OK:()=>P,ParseStatus:()=>R,Schema:()=>h,ZodAny:()=>ie,ZodArray:()=>ee,ZodBigInt:()=>me,ZodBoolean:()=>ge,ZodBranded:()=>Ae,ZodCatch:()=>ke,ZodDate:()=>fe,ZodDefault:()=>ve,ZodDiscriminatedUnion:()=>Be,ZodEffects:()=>z,ZodEnum:()=>de,ZodError:()=>G,ZodFirstPartyTypeKind:()=>pt,ZodFunction:()=>Le,ZodIntersection:()=>be,ZodIssueCode:()=>u,ZodLazy:()=>xe,ZodLiteral:()=>Te,ZodMap:()=>Ne,ZodNaN:()=>Oe,ZodNativeEnum:()=>Ce,ZodNever:()=>M,ZodNull:()=>Ie,ZodNullable:()=>q,ZodNumber:()=>ye,ZodObject:()=>F,ZodOptional:()=>H,ZodParsedType:()=>p,ZodPipeline:()=>Re,ZodPromise:()=>ue,ZodReadonly:()=>Pe,ZodRecord:()=>Ve,ZodSchema:()=>h,ZodSet:()=>_e,ZodString:()=>oe,ZodSymbol:()=>Ke,ZodTransformer:()=>z,ZodTuple:()=>J,ZodType:()=>h,ZodUndefined:()=>Ze,ZodUnion:()=>he,ZodUnknown:()=>$,ZodVoid:()=>We,addIssueToContext:()=>c,any:()=>ns,array:()=>os,bigint:()=>qr,boolean:()=>gt,coerce:()=>ws,custom:()=>lt,date:()=>Qr,datetimeRegex:()=>ut,defaultErrorMap:()=>D,discriminatedUnion:()=>cs,effect:()=>Ts,enum:()=>hs,function:()=>fs,getErrorMap:()=>ne,getParsedType:()=>Y,instanceof:()=>Jr,intersection:()=>ls,isAborted:()=>we,isAsync:()=>se,isDirty:()=>Ge,isValid:()=>re,late:()=>Fr,lazy:()=>Zs,literal:()=>Is,makeIssue:()=>pe,map:()=>ms,nan:()=>zr,nativeEnum:()=>bs,never:()=>ss,null:()=>ts,nullable:()=>vs,number:()=>mt,object:()=>is,objectUtil:()=>st,oboolean:()=>Ss,onumber:()=>Ps,optional:()=>Cs,ostring:()=>Rs,pipeline:()=>As,preprocess:()=>ks,promise:()=>xs,quotelessJson:()=>Pr,record:()=>ys,set:()=>gs,setErrorMap:()=>wr,strictObject:()=>ds,string:()=>yt,symbol:()=>$r,transformer:()=>Ts,tuple:()=>ps,undefined:()=>es,union:()=>us,unknown:()=>rs,util:()=>v,void:()=>as});var v;(k=>{k.assertEqual=x=>x;function e(x){}k.assertIs=e;function t(x){throw new Error}k.assertNever=t,k.arrayToEnum=x=>{let g={};for(let b of x)g[b]=b;return g},k.getValidEnumValues=x=>{let g=(0,k.objectKeys)(x).filter(A=>typeof x[x[A]]!="number"),b={};for(let A of g)b[A]=x[A];return(0,k.objectValues)(b)},k.objectValues=x=>(0,k.objectKeys)(x).map(function(g){return x[g]}),k.objectKeys=typeof Object.keys=="function"?x=>Object.keys(x):x=>{let g=[];for(let b in x)Object.prototype.hasOwnProperty.call(x,b)&&g.push(b);return g},k.find=(x,g)=>{for(let b of x)if(g(b))return b},k.isInteger=typeof Number.isInteger=="function"?x=>Number.isInteger(x):x=>typeof x=="number"&&isFinite(x)&&Math.floor(x)===x;function l(x,g=" | "){return x.map(b=>typeof b=="string"?`'${b}'`:b).join(g)}k.joinValues=l,k.jsonStringifyReplacer=(x,g)=>typeof g=="bigint"?g.toString():g})(v||={});var st;(e=>e.mergeShapes=(t,r)=>({...t,...r}))(st||={});var p=v.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),Y=n=>{switch(typeof n){case"undefined":return p.undefined;case"string":return p.string;case"number":return isNaN(n)?p.nan:p.number;case"boolean":return p.boolean;case"function":return p.function;case"bigint":return p.bigint;case"symbol":return p.symbol;case"object":return Array.isArray(n)?p.array:n===null?p.null:n.then&&typeof n.then=="function"&&n.catch&&typeof n.catch=="function"?p.promise:typeof Map<"u"&&n instanceof Map?p.map:typeof Set<"u"&&n instanceof Set?p.set:typeof Date<"u"&&n instanceof Date?p.date:p.object;default:return p.unknown}};var u=v.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of","not_finite"]),Pr=n=>JSON.stringify(n,null,2).replace(/"([^"]+)":/g,"$1:"),G=class n extends Error{issues=[];get errors(){return this.issues}constructor(e){super();let t=new.target.prototype;Object.setPrototypeOf?Object.setPrototypeOf(this,t):this.__proto__=t,this.name="ZodError",this.issues=e}format(e){let t=e||function(a){return a.message},r={_errors:[]},s=a=>{for(let o of a.issues)if(o.code==="invalid_union")o.unionErrors.map(s);else if(o.code==="invalid_return_type")s(o.returnTypeError);else if(o.code==="invalid_arguments")s(o.argumentsError);else if(o.path.length===0)r._errors.push(t(o));else{let i=r,y=0;for(;ynew n(e);static assert(e){if(!(e instanceof n))throw new Error(`Not a ZodError: ${e}`)}toString(){return this.message}get message(){return JSON.stringify(this.issues,v.jsonStringifyReplacer,2)}get isEmpty(){return this.issues.length===0}addIssue=e=>{this.issues=[...this.issues,e]};addIssues=(e=[])=>{this.issues=[...this.issues,...e]};flatten(e=t=>t.message){let t={},r=[];for(let s of this.issues)s.path.length>0?(t[s.path[0]]=t[s.path[0]]||[],t[s.path[0]].push(e(s))):r.push(e(s));return{formErrors:r,fieldErrors:t}}get formErrors(){return this.flatten()}};var Sr=(n,e)=>{let t;switch(n.code){case u.invalid_type:n.received===p.undefined?t="Required":t=`Expected ${n.expected}, received ${n.received}`;break;case u.invalid_literal:t=`Invalid literal value, expected ${JSON.stringify(n.expected,v.jsonStringifyReplacer)}`;break;case u.unrecognized_keys:t=`Unrecognized key(s) in object: ${v.joinValues(n.keys,", ")}`;break;case u.invalid_union:t="Invalid input";break;case u.invalid_union_discriminator:t=`Invalid discriminator value. Expected ${v.joinValues(n.options)}`;break;case u.invalid_enum_value:t=`Invalid enum value. Expected ${v.joinValues(n.options)}, received '${n.received}'`;break;case u.invalid_arguments:t="Invalid function arguments";break;case u.invalid_return_type:t="Invalid function return type";break;case u.invalid_date:t="Invalid date";break;case u.invalid_string:typeof n.validation=="object"?"includes"in n.validation?(t=`Invalid input: must include "${n.validation.includes}"`,typeof n.validation.position=="number"&&(t=`${t} at one or more positions greater than or equal to ${n.validation.position}`)):"startsWith"in n.validation?t=`Invalid input: must start with "${n.validation.startsWith}"`:"endsWith"in n.validation?t=`Invalid input: must end with "${n.validation.endsWith}"`:v.assertNever(n.validation):n.validation!=="regex"?t=`Invalid ${n.validation}`:t="Invalid";break;case u.too_small:n.type==="array"?t=`Array must contain ${n.exact?"exactly":n.inclusive?"at least":"more than"} ${n.minimum} element(s)`:n.type==="string"?t=`String must contain ${n.exact?"exactly":n.inclusive?"at least":"over"} ${n.minimum} character(s)`:n.type==="number"?t=`Number must be ${n.exact?"exactly equal to ":n.inclusive?"greater than or equal to ":"greater than "}${n.minimum}`:n.type==="date"?t=`Date must be ${n.exact?"exactly equal to ":n.inclusive?"greater than or equal to ":"greater than "}${new Date(Number(n.minimum))}`:t="Invalid input";break;case u.too_big:n.type==="array"?t=`Array must contain ${n.exact?"exactly":n.inclusive?"at most":"less than"} ${n.maximum} element(s)`:n.type==="string"?t=`String must contain ${n.exact?"exactly":n.inclusive?"at most":"under"} ${n.maximum} character(s)`:n.type==="number"?t=`Number must be ${n.exact?"exactly":n.inclusive?"less than or equal to":"less than"} ${n.maximum}`:n.type==="bigint"?t=`BigInt must be ${n.exact?"exactly":n.inclusive?"less than or equal to":"less than"} ${n.maximum}`:n.type==="date"?t=`Date must be ${n.exact?"exactly":n.inclusive?"smaller than or equal to":"smaller than"} ${new Date(Number(n.maximum))}`:t="Invalid input";break;case u.custom:t="Invalid input";break;case u.invalid_intersection_types:t="Intersection results could not be merged";break;case u.not_multiple_of:t=`Number must be a multiple of ${n.multipleOf}`;break;case u.not_finite:t="Number must be finite";break;default:t=e.defaultError,v.assertNever(n)}return{message:t}},D=Sr;var at=D;function wr(n){at=n}function ne(){return at}var pe=n=>{let{data:e,path:t,errorMaps:r,issueData:s}=n,a=[...t,...s.path||[]],o={...s,path:a};if(s.message!==void 0)return{...s,path:a,message:s.message};let i="",y=r.filter(l=>!!l).slice().reverse();for(let l of y)i=l(o,{data:e,defaultError:i}).message;return{...s,path:a,message:i}},Gr=[];function c(n,e){let t=ne(),r=pe({issueData:e,data:n.data,path:n.path,errorMaps:[n.common.contextualErrorMap,n.schemaErrorMap,t,t===D?void 0:D].filter(s=>!!s)});n.common.issues.push(r)}var R=class n{value="valid";dirty(){this.value==="valid"&&(this.value="dirty")}abort(){this.value!=="aborted"&&(this.value="aborted")}static mergeArray(e,t){let r=[];for(let s of t){if(s.status==="aborted")return f;s.status==="dirty"&&e.dirty(),r.push(s.value)}return{status:e.value,value:r}}static async mergeObjectAsync(e,t){let r=[];for(let s of t){let a=await s.key,o=await s.value;r.push({key:a,value:o})}return n.mergeObjectSync(e,r)}static mergeObjectSync(e,t){let r={};for(let s of t){let{key:a,value:o}=s;if(a.status==="aborted"||o.status==="aborted")return f;a.status==="dirty"&&e.dirty(),o.status==="dirty"&&e.dirty(),a.value!=="__proto__"&&(typeof o.value<"u"||s.alwaysSet)&&(r[a.value]=o.value)}return{status:e.value,value:r}}},f=Object.freeze({status:"aborted"}),Q=n=>({status:"dirty",value:n}),P=n=>({status:"valid",value:n}),we=n=>n.status==="aborted",Ge=n=>n.status==="dirty",re=n=>n.status==="valid",se=n=>typeof Promise<"u"&&n instanceof Promise;var m;(t=>(t.errToObj=r=>typeof r=="string"?{message:r}:r||{},t.toString=r=>typeof r=="string"?r:r?.message))(m||={});var V=class{parent;data;_path;_key;_cachedPath=[];constructor(e,t,r,s){this.parent=e,this.data=t,this._path=r,this._key=s}get path(){return this._cachedPath.length||(this._key instanceof Array?this._cachedPath.push(...this._path,...this._key):this._cachedPath.push(...this._path,this._key)),this._cachedPath}},ot=(n,e)=>{if(re(e))return{success:!0,data:e.value};if(!n.common.issues.length)throw new Error("Validation failed but no issues detected.");return{success:!1,get error(){if(this._error)return this._error;let t=new G(n.common.issues);return this._error=t,this._error}}};function Z(n){if(!n)return{};let{errorMap:e,invalid_type_error:t,required_error:r,description:s}=n;if(e&&(t||r))throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);return e?{errorMap:e,description:s}:{errorMap:(o,i)=>{let{message:y}=n;return o.code==="invalid_enum_value"?{message:y??i.defaultError}:typeof i.data>"u"?{message:y??r??i.defaultError}:o.code!=="invalid_type"?{message:i.defaultError}:{message:y??t??i.defaultError}},description:s}}var h=class{_type;_output;_input;_def;get description(){return this._def.description}_getType(e){return Y(e.data)}_getOrReturnCtx(e,t){return t||{common:e.parent.common,data:e.data,parsedType:Y(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}_processInputParams(e){return{status:new R,ctx:{common:e.parent.common,data:e.data,parsedType:Y(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}}_parseSync(e){let t=this._parse(e);if(se(t))throw new Error("Synchronous parse encountered promise.");return t}_parseAsync(e){let t=this._parse(e);return Promise.resolve(t)}parse(e,t){let r=this.safeParse(e,t);if(r.success)return r.data;throw r.error}safeParse(e,t){let r={common:{issues:[],async:t?.async??!1,contextualErrorMap:t?.errorMap},path:t?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:Y(e)},s=this._parseSync({data:e,path:r.path,parent:r});return ot(r,s)}async parseAsync(e,t){let r=await this.safeParseAsync(e,t);if(r.success)return r.data;throw r.error}async safeParseAsync(e,t){let r={common:{issues:[],contextualErrorMap:t?.errorMap,async:!0},path:t?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:Y(e)},s=this._parse({data:e,path:r.path,parent:r}),a=await(se(s)?s:Promise.resolve(s));return ot(r,a)}spa=this.safeParseAsync;refine(e,t){let r=s=>typeof t=="string"||typeof t>"u"?{message:t}:typeof t=="function"?t(s):t;return this._refinement((s,a)=>{let o=e(s),i=()=>a.addIssue({code:u.custom,...r(s)});return typeof Promise<"u"&&o instanceof Promise?o.then(y=>y?!0:(i(),!1)):o?!0:(i(),!1)})}refinement(e,t){return this._refinement((r,s)=>e(r)?!0:(s.addIssue(typeof t=="function"?t(r,s):t),!1))}_refinement(e){return new z({schema:this,typeName:"ZodEffects",effect:{type:"refinement",refinement:e}})}superRefine(e){return this._refinement(e)}constructor(e){this._def=e,this.parse=this.parse.bind(this),this.safeParse=this.safeParse.bind(this),this.parseAsync=this.parseAsync.bind(this),this.safeParseAsync=this.safeParseAsync.bind(this),this.spa=this.spa.bind(this),this.refine=this.refine.bind(this),this.refinement=this.refinement.bind(this),this.superRefine=this.superRefine.bind(this),this.optional=this.optional.bind(this),this.nullable=this.nullable.bind(this),this.nullish=this.nullish.bind(this),this.array=this.array.bind(this),this.promise=this.promise.bind(this),this.or=this.or.bind(this),this.and=this.and.bind(this),this.transform=this.transform.bind(this),this.brand=this.brand.bind(this),this.default=this.default.bind(this),this.catch=this.catch.bind(this),this.describe=this.describe.bind(this),this.pipe=this.pipe.bind(this),this.readonly=this.readonly.bind(this),this.isNullable=this.isNullable.bind(this),this.isOptional=this.isOptional.bind(this)}optional(){return H.create(this,this._def)}nullable(){return q.create(this,this._def)}nullish(){return this.nullable().optional()}array(){return ee.create(this,this._def)}promise(){return ue.create(this,this._def)}or(e){return he.create([this,e],this._def)}and(e){return be.create(this,e,this._def)}transform(e){return new z({...Z(this._def),schema:this,typeName:"ZodEffects",effect:{type:"transform",transform:e}})}default(e){let t=typeof e=="function"?e:()=>e;return new ve({...Z(this._def),innerType:this,defaultValue:t,typeName:"ZodDefault"})}brand(){return new Ae({typeName:"ZodBranded",type:this,...Z(this._def)})}catch(e){let t=typeof e=="function"?e:()=>e;return new ke({...Z(this._def),innerType:this,catchValue:t,typeName:"ZodCatch"})}describe(e){let t=this.constructor;return new t({...this._def,description:e})}pipe(e){return Re.create(this,e)}readonly(){return Pe.create(this)}isOptional(){return this.safeParse(void 0).success}isNullable(){return this.safeParse(null).success}},Kr=/^c[^\s-]{8,}$/i,Wr=/^[0-9a-z]+$/,Br=/^[0-9A-HJKMNP-TV-Z]{26}$/,Vr=/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i,Nr=/^[a-z0-9_-]{21}$/i,_r=/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/,Lr=/^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i,Or="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$",Fe,Hr=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,Xr=/^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/,Er=/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,it="((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))",Yr=new RegExp(`^${it}$`);function dt(n){let e="([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d";return n.precision?e=`${e}\\.\\d{${n.precision}}`:n.precision==null&&(e=`${e}(\\.\\d+)?`),e}function Dr(n){return new RegExp(`^${dt(n)}$`)}function ut(n){let e=`${it}T${dt(n)}`,t=[];return t.push(n.local?"Z?":"Z"),n.offset&&t.push("([+-]\\d{2}:?\\d{2})"),e=`${e}(${t.join("|")})`,new RegExp(`^${e}$`)}function Ur(n,e){return!!((e==="v4"||!e)&&Hr.test(n)||(e==="v6"||!e)&&Xr.test(n))}var oe=class n extends h{_parse(e){if(this._def.coerce&&(e.data=String(e.data)),this._getType(e)!==p.string){let a=this._getOrReturnCtx(e);return c(a,{code:u.invalid_type,expected:p.string,received:a.parsedType}),f}let r=new R,s;for(let a of this._def.checks)if(a.kind==="min")e.data.lengtha.value&&(s=this._getOrReturnCtx(e,s),c(s,{code:u.too_big,maximum:a.value,type:"string",inclusive:!0,exact:!1,message:a.message}),r.dirty());else if(a.kind==="length"){let o=e.data.length>a.value,i=e.data.lengthe.test(s),{validation:t,code:u.invalid_string,...m.errToObj(r)})}_addCheck(e){return new n({...this._def,checks:[...this._def.checks,e]})}email(e){return this._addCheck({kind:"email",...m.errToObj(e)})}url(e){return this._addCheck({kind:"url",...m.errToObj(e)})}emoji(e){return this._addCheck({kind:"emoji",...m.errToObj(e)})}uuid(e){return this._addCheck({kind:"uuid",...m.errToObj(e)})}nanoid(e){return this._addCheck({kind:"nanoid",...m.errToObj(e)})}cuid(e){return this._addCheck({kind:"cuid",...m.errToObj(e)})}cuid2(e){return this._addCheck({kind:"cuid2",...m.errToObj(e)})}ulid(e){return this._addCheck({kind:"ulid",...m.errToObj(e)})}base64(e){return this._addCheck({kind:"base64",...m.errToObj(e)})}ip(e){return this._addCheck({kind:"ip",...m.errToObj(e)})}datetime(e){return typeof e=="string"?this._addCheck({kind:"datetime",precision:null,offset:!1,local:!1,message:e}):this._addCheck({kind:"datetime",precision:typeof e?.precision>"u"?null:e?.precision,offset:e?.offset??!1,local:e?.local??!1,...m.errToObj(e?.message)})}date(e){return this._addCheck({kind:"date",message:e})}time(e){return typeof e=="string"?this._addCheck({kind:"time",precision:null,message:e}):this._addCheck({kind:"time",precision:typeof e?.precision>"u"?null:e?.precision,...m.errToObj(e?.message)})}duration(e){return this._addCheck({kind:"duration",...m.errToObj(e)})}regex(e,t){return this._addCheck({kind:"regex",regex:e,...m.errToObj(t)})}includes(e,t){return this._addCheck({kind:"includes",value:e,position:t?.position,...m.errToObj(t?.message)})}startsWith(e,t){return this._addCheck({kind:"startsWith",value:e,...m.errToObj(t)})}endsWith(e,t){return this._addCheck({kind:"endsWith",value:e,...m.errToObj(t)})}min(e,t){return this._addCheck({kind:"min",value:e,...m.errToObj(t)})}max(e,t){return this._addCheck({kind:"max",value:e,...m.errToObj(t)})}length(e,t){return this._addCheck({kind:"length",value:e,...m.errToObj(t)})}nonempty(e){return this.min(1,m.errToObj(e))}trim(){return new n({...this._def,checks:[...this._def.checks,{kind:"trim"}]})}toLowerCase(){return new n({...this._def,checks:[...this._def.checks,{kind:"toLowerCase"}]})}toUpperCase(){return new n({...this._def,checks:[...this._def.checks,{kind:"toUpperCase"}]})}get isDatetime(){return!!this._def.checks.find(e=>e.kind==="datetime")}get isDate(){return!!this._def.checks.find(e=>e.kind==="date")}get isTime(){return!!this._def.checks.find(e=>e.kind==="time")}get isDuration(){return!!this._def.checks.find(e=>e.kind==="duration")}get isEmail(){return!!this._def.checks.find(e=>e.kind==="email")}get isURL(){return!!this._def.checks.find(e=>e.kind==="url")}get isEmoji(){return!!this._def.checks.find(e=>e.kind==="emoji")}get isUUID(){return!!this._def.checks.find(e=>e.kind==="uuid")}get isNANOID(){return!!this._def.checks.find(e=>e.kind==="nanoid")}get isCUID(){return!!this._def.checks.find(e=>e.kind==="cuid")}get isCUID2(){return!!this._def.checks.find(e=>e.kind==="cuid2")}get isULID(){return!!this._def.checks.find(e=>e.kind==="ulid")}get isIP(){return!!this._def.checks.find(e=>e.kind==="ip")}get isBase64(){return!!this._def.checks.find(e=>e.kind==="base64")}get minLength(){let e=null;for(let t of this._def.checks)t.kind==="min"&&(e===null||t.value>e)&&(e=t.value);return e}get maxLength(){let e=null;for(let t of this._def.checks)t.kind==="max"&&(e===null||t.valuenew n({checks:[],typeName:"ZodString",coerce:e?.coerce??!1,...Z(e)})};function Mr(n,e){let t=(n.toString().split(".")[1]||"").length,r=(e.toString().split(".")[1]||"").length,s=t>r?t:r,a=parseInt(n.toFixed(s).replace(".","")),o=parseInt(e.toFixed(s).replace(".",""));return a%o/Math.pow(10,s)}var ye=class n extends h{_parse(e){if(this._def.coerce&&(e.data=Number(e.data)),this._getType(e)!==p.number){let a=this._getOrReturnCtx(e);return c(a,{code:u.invalid_type,expected:p.number,received:a.parsedType}),f}let r,s=new R;for(let a of this._def.checks)a.kind==="int"?v.isInteger(e.data)||(r=this._getOrReturnCtx(e,r),c(r,{code:u.invalid_type,expected:"integer",received:"float",message:a.message}),s.dirty()):a.kind==="min"?(a.inclusive?e.dataa.value:e.data>=a.value)&&(r=this._getOrReturnCtx(e,r),c(r,{code:u.too_big,maximum:a.value,type:"number",inclusive:a.inclusive,exact:!1,message:a.message}),s.dirty()):a.kind==="multipleOf"?Mr(e.data,a.value)!==0&&(r=this._getOrReturnCtx(e,r),c(r,{code:u.not_multiple_of,multipleOf:a.value,message:a.message}),s.dirty()):a.kind==="finite"?Number.isFinite(e.data)||(r=this._getOrReturnCtx(e,r),c(r,{code:u.not_finite,message:a.message}),s.dirty()):v.assertNever(a);return{status:s.value,value:e.data}}static create=e=>new n({checks:[],typeName:"ZodNumber",coerce:e?.coerce||!1,...Z(e)});gte(e,t){return this.setLimit("min",e,!0,m.toString(t))}min=this.gte;gt(e,t){return this.setLimit("min",e,!1,m.toString(t))}lte(e,t){return this.setLimit("max",e,!0,m.toString(t))}max=this.lte;lt(e,t){return this.setLimit("max",e,!1,m.toString(t))}setLimit(e,t,r,s){return new n({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:r,message:m.toString(s)}]})}_addCheck(e){return new n({...this._def,checks:[...this._def.checks,e]})}int(e){return this._addCheck({kind:"int",message:m.toString(e)})}positive(e){return this._addCheck({kind:"min",value:0,inclusive:!1,message:m.toString(e)})}negative(e){return this._addCheck({kind:"max",value:0,inclusive:!1,message:m.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:0,inclusive:!0,message:m.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:0,inclusive:!0,message:m.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:m.toString(t)})}step=this.multipleOf;finite(e){return this._addCheck({kind:"finite",message:m.toString(e)})}safe(e){return this._addCheck({kind:"min",inclusive:!0,value:Number.MIN_SAFE_INTEGER,message:m.toString(e)})._addCheck({kind:"max",inclusive:!0,value:Number.MAX_SAFE_INTEGER,message:m.toString(e)})}get minValue(){let e=null;for(let t of this._def.checks)t.kind==="min"&&(e===null||t.value>e)&&(e=t.value);return e}get maxValue(){let e=null;for(let t of this._def.checks)t.kind==="max"&&(e===null||t.valuee.kind==="int"||e.kind==="multipleOf"&&v.isInteger(e.value))}get isFinite(){let e=null,t=null;for(let r of this._def.checks){if(r.kind==="finite"||r.kind==="int"||r.kind==="multipleOf")return!0;r.kind==="min"?(t===null||r.value>t)&&(t=r.value):r.kind==="max"&&(e===null||r.valuea.value:e.data>=a.value)&&(r=this._getOrReturnCtx(e,r),c(r,{code:u.too_big,type:"bigint",maximum:a.value,inclusive:a.inclusive,message:a.message}),s.dirty()):a.kind==="multipleOf"?e.data%a.value!==BigInt(0)&&(r=this._getOrReturnCtx(e,r),c(r,{code:u.not_multiple_of,multipleOf:a.value,message:a.message}),s.dirty()):v.assertNever(a);return{status:s.value,value:e.data}}static create=e=>new n({checks:[],typeName:"ZodBigInt",coerce:e?.coerce??!1,...Z(e)});gte(e,t){return this.setLimit("min",e,!0,m.toString(t))}min=this.gte;gt(e,t){return this.setLimit("min",e,!1,m.toString(t))}lte(e,t){return this.setLimit("max",e,!0,m.toString(t))}max=this.lte;lt(e,t){return this.setLimit("max",e,!1,m.toString(t))}setLimit(e,t,r,s){return new n({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:r,message:m.toString(s)}]})}_addCheck(e){return new n({...this._def,checks:[...this._def.checks,e]})}positive(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!1,message:m.toString(e)})}negative(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!1,message:m.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!0,message:m.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!0,message:m.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:m.toString(t)})}get minValue(){let e=null;for(let t of this._def.checks)t.kind==="min"&&(e===null||t.value>e)&&(e=t.value);return e}get maxValue(){let e=null;for(let t of this._def.checks)t.kind==="max"&&(e===null||t.valuenew n({typeName:"ZodBoolean",coerce:e?.coerce||!1,...Z(e)})},fe=class n extends h{_parse(e){if(this._def.coerce&&(e.data=new Date(e.data)),this._getType(e)!==p.date){let a=this._getOrReturnCtx(e);return c(a,{code:u.invalid_type,expected:p.date,received:a.parsedType}),f}if(isNaN(e.data.getTime())){let a=this._getOrReturnCtx(e);return c(a,{code:u.invalid_date}),f}let r=new R,s;for(let a of this._def.checks)a.kind==="min"?e.data.getTime()a.value&&(s=this._getOrReturnCtx(e,s),c(s,{code:u.too_big,message:a.message,inclusive:!0,exact:!1,maximum:a.value,type:"date"}),r.dirty()):v.assertNever(a);return{status:r.value,value:new Date(e.data.getTime())}}_addCheck(e){return new n({...this._def,checks:[...this._def.checks,e]})}min(e,t){return this._addCheck({kind:"min",value:e.getTime(),message:m.toString(t)})}max(e,t){return this._addCheck({kind:"max",value:e.getTime(),message:m.toString(t)})}get minDate(){let e=null;for(let t of this._def.checks)t.kind==="min"&&(e===null||t.value>e)&&(e=t.value);return e!=null?new Date(e):null}get maxDate(){let e=null;for(let t of this._def.checks)t.kind==="max"&&(e===null||t.valuenew n({checks:[],coerce:e?.coerce||!1,typeName:"ZodDate",...Z(e)})},Ke=class n extends h{_parse(e){if(this._getType(e)!==p.symbol){let r=this._getOrReturnCtx(e);return c(r,{code:u.invalid_type,expected:p.symbol,received:r.parsedType}),f}return P(e.data)}static create=e=>new n({typeName:"ZodSymbol",...Z(e)})},Ze=class n extends h{_parse(e){if(this._getType(e)!==p.undefined){let r=this._getOrReturnCtx(e);return c(r,{code:u.invalid_type,expected:p.undefined,received:r.parsedType}),f}return P(e.data)}params;static create=e=>new n({typeName:"ZodUndefined",...Z(e)})},Ie=class n extends h{_parse(e){if(this._getType(e)!==p.null){let r=this._getOrReturnCtx(e);return c(r,{code:u.invalid_type,expected:p.null,received:r.parsedType}),f}return P(e.data)}static create=e=>new n({typeName:"ZodNull",...Z(e)})},ie=class n extends h{_any=!0;_parse(e){return P(e.data)}static create=e=>new n({typeName:"ZodAny",...Z(e)})},$=class n extends h{_unknown=!0;_parse(e){return P(e.data)}static create=e=>new n({typeName:"ZodUnknown",...Z(e)})},M=class n extends h{_parse(e){let t=this._getOrReturnCtx(e);return c(t,{code:u.invalid_type,expected:p.never,received:t.parsedType}),f}static create=e=>new n({typeName:"ZodNever",...Z(e)})},We=class n extends h{_parse(e){if(this._getType(e)!==p.undefined){let r=this._getOrReturnCtx(e);return c(r,{code:u.invalid_type,expected:p.void,received:r.parsedType}),f}return P(e.data)}static create=e=>new n({typeName:"ZodVoid",...Z(e)})},ee=class n extends h{_parse(e){let{ctx:t,status:r}=this._processInputParams(e),s=this._def;if(t.parsedType!==p.array)return c(t,{code:u.invalid_type,expected:p.array,received:t.parsedType}),f;if(s.exactLength!==null){let o=t.data.length>s.exactLength.value,i=t.data.lengths.maxLength.value&&(c(t,{code:u.too_big,maximum:s.maxLength.value,type:"array",inclusive:!0,exact:!1,message:s.maxLength.message}),r.dirty()),t.common.async)return Promise.all([...t.data].map((o,i)=>s.type._parseAsync(new V(t,o,t.path,i)))).then(o=>R.mergeArray(r,o));let a=[...t.data].map((o,i)=>s.type._parseSync(new V(t,o,t.path,i)));return R.mergeArray(r,a)}get element(){return this._def.type}min(e,t){return new n({...this._def,minLength:{value:e,message:m.toString(t)}})}max(e,t){return new n({...this._def,maxLength:{value:e,message:m.toString(t)}})}length(e,t){return new n({...this._def,exactLength:{value:e,message:m.toString(t)}})}nonempty(e){return this.min(1,e)}static create=(e,t)=>new n({type:e,minLength:null,maxLength:null,exactLength:null,typeName:"ZodArray",...Z(t)})};function ae(n){if(n instanceof F){let e={};for(let t in n.shape){let r=n.shape[t];e[t]=H.create(ae(r))}return new F({...n._def,shape:()=>e})}else return n instanceof ee?new ee({...n._def,type:ae(n.element)}):n instanceof H?H.create(ae(n.unwrap())):n instanceof q?q.create(ae(n.unwrap())):n instanceof J?J.create(n.items.map(e=>ae(e))):n}var F=class n extends h{_cached=null;_getCached(){if(this._cached!==null)return this._cached;let e=this._def.shape(),t=v.objectKeys(e);return this._cached={shape:e,keys:t}}_parse(e){if(this._getType(e)!==p.object){let l=this._getOrReturnCtx(e);return c(l,{code:u.invalid_type,expected:p.object,received:l.parsedType}),f}let{status:r,ctx:s}=this._processInputParams(e),{shape:a,keys:o}=this._getCached(),i=[];if(!(this._def.catchall instanceof M&&this._def.unknownKeys==="strip"))for(let l in s.data)o.includes(l)||i.push(l);let y=[];for(let l of o){let I=a[l],k=s.data[l];y.push({key:{status:"valid",value:l},value:I._parse(new V(s,k,s.path,l)),alwaysSet:l in s.data})}if(this._def.catchall instanceof M){let l=this._def.unknownKeys;if(l==="passthrough")for(let I of i)y.push({key:{status:"valid",value:I},value:{status:"valid",value:s.data[I]}});else if(l==="strict")i.length>0&&(c(s,{code:u.unrecognized_keys,keys:i}),r.dirty());else if(l!=="strip")throw new Error("Internal ZodObject error: invalid unknownKeys value.")}else{let l=this._def.catchall;for(let I of i){let k=s.data[I];y.push({key:{status:"valid",value:I},value:l._parse(new V(s,k,s.path,I)),alwaysSet:I in s.data})}}return s.common.async?Promise.resolve().then(async()=>{let l=[];for(let I of y){let k=await I.key,x=await I.value;l.push({key:k,value:x,alwaysSet:I.alwaysSet})}return l}).then(l=>R.mergeObjectSync(r,l)):R.mergeObjectSync(r,y)}get shape(){return this._def.shape()}strict(e){return m.errToObj,new n({...this._def,unknownKeys:"strict",...e!==void 0?{errorMap:(t,r)=>{let s=this._def.errorMap?.(t,r).message??r.defaultError;return t.code==="unrecognized_keys"?{message:m.errToObj(e).message??s}:{message:s}}}:{}})}strip(){return new n({...this._def,unknownKeys:"strip"})}passthrough(){return new n({...this._def,unknownKeys:"passthrough"})}nonstrict=this.passthrough;extend(e){return new n({...this._def,shape:()=>({...this._def.shape(),...e})})}augment=this.extend;merge(e){return new n({unknownKeys:e._def.unknownKeys,catchall:e._def.catchall,shape:()=>({...this._def.shape(),...e._def.shape()}),typeName:"ZodObject"})}setKey(e,t){return this.augment({[e]:t})}catchall(e){return new n({...this._def,catchall:e})}pick(e){let t={};return v.objectKeys(e).forEach(r=>{e[r]&&this.shape[r]&&(t[r]=this.shape[r])}),new n({...this._def,shape:()=>t})}omit(e){let t={};return v.objectKeys(this.shape).forEach(r=>{e[r]||(t[r]=this.shape[r])}),new n({...this._def,shape:()=>t})}deepPartial(){return ae(this)}partial(e){let t={};return v.objectKeys(this.shape).forEach(r=>{let s=this.shape[r];e&&!e[r]?t[r]=s:t[r]=s.optional()}),new n({...this._def,shape:()=>t})}required(e){let t={};return v.objectKeys(this.shape).forEach(r=>{if(e&&!e[r])t[r]=this.shape[r];else{let a=this.shape[r];for(;a instanceof H;)a=a._def.innerType;t[r]=a}}),new n({...this._def,shape:()=>t})}keyof(){return ct(v.objectKeys(this.shape))}static create=(e,t)=>new n({shape:()=>e,unknownKeys:"strip",catchall:M.create(),typeName:"ZodObject",...Z(t)});static strictCreate=(e,t)=>new n({shape:()=>e,unknownKeys:"strict",catchall:M.create(),typeName:"ZodObject",...Z(t)});static lazycreate=(e,t)=>new n({shape:e,unknownKeys:"strip",catchall:M.create(),typeName:"ZodObject",...Z(t)})},he=class n extends h{_parse(e){let{ctx:t}=this._processInputParams(e),r=this._def.options;function s(a){for(let i of a)if(i.result.status==="valid")return i.result;for(let i of a)if(i.result.status==="dirty")return t.common.issues.push(...i.ctx.common.issues),i.result;let o=a.map(i=>new G(i.ctx.common.issues));return c(t,{code:u.invalid_union,unionErrors:o}),f}if(t.common.async)return Promise.all(r.map(async a=>{let o={...t,common:{...t.common,issues:[]},parent:null};return{result:await a._parseAsync({data:t.data,path:t.path,parent:o}),ctx:o}})).then(s);{let a,o=[];for(let y of r){let l={...t,common:{...t.common,issues:[]},parent:null},I=y._parseSync({data:t.data,path:t.path,parent:l});if(I.status==="valid")return I;I.status==="dirty"&&!a&&(a={result:I,ctx:l}),l.common.issues.length&&o.push(l.common.issues)}if(a)return t.common.issues.push(...a.ctx.common.issues),a.result;let i=o.map(y=>new G(y));return c(t,{code:u.invalid_union,unionErrors:i}),f}}get options(){return this._def.options}static create=(e,t)=>new n({options:e,typeName:"ZodUnion",...Z(t)})},U=n=>n instanceof xe?U(n.schema):n instanceof z?U(n.innerType()):n instanceof Te?[n.value]:n instanceof de?n.options:n instanceof Ce?v.objectValues(n.enum):n instanceof ve?U(n._def.innerType):n instanceof Ze?[void 0]:n instanceof Ie?[null]:n instanceof H?[void 0,...U(n.unwrap())]:n instanceof q?[null,...U(n.unwrap())]:n instanceof Ae||n instanceof Pe?U(n.unwrap()):n instanceof ke?U(n._def.innerType):[],Be=class n extends h{_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==p.object)return c(t,{code:u.invalid_type,expected:p.object,received:t.parsedType}),f;let r=this.discriminator,s=t.data[r],a=this.optionsMap.get(s);return a?t.common.async?a._parseAsync({data:t.data,path:t.path,parent:t}):a._parseSync({data:t.data,path:t.path,parent:t}):(c(t,{code:u.invalid_union_discriminator,options:Array.from(this.optionsMap.keys()),path:[r]}),f)}get discriminator(){return this._def.discriminator}get options(){return this._def.options}get optionsMap(){return this._def.optionsMap}static create(e,t,r){let s=new Map;for(let a of t){let o=U(a.shape[e]);if(!o.length)throw new Error(`A discriminator value for key \`${e}\` could not be extracted from all schema options`);for(let i of o){if(s.has(i))throw new Error(`Discriminator property ${String(e)} has duplicate value ${String(i)}`);s.set(i,a)}}return new n({typeName:"ZodDiscriminatedUnion",discriminator:e,options:t,optionsMap:s,...Z(r)})}};function Je(n,e){let t=Y(n),r=Y(e);if(n===e)return{valid:!0,data:n};if(t===p.object&&r===p.object){let s=v.objectKeys(e),a=v.objectKeys(n).filter(i=>s.indexOf(i)!==-1),o={...n,...e};for(let i of a){let y=Je(n[i],e[i]);if(!y.valid)return{valid:!1};o[i]=y.data}return{valid:!0,data:o}}else if(t===p.array&&r===p.array){if(n.length!==e.length)return{valid:!1};let s=[];for(let a=0;a{if(we(a)||we(o))return f;let i=Je(a.value,o.value);return i.valid?((Ge(a)||Ge(o))&&t.dirty(),{status:t.value,value:i.data}):(c(r,{code:u.invalid_intersection_types}),f)};return r.common.async?Promise.all([this._def.left._parseAsync({data:r.data,path:r.path,parent:r}),this._def.right._parseAsync({data:r.data,path:r.path,parent:r})]).then(([a,o])=>s(a,o)):s(this._def.left._parseSync({data:r.data,path:r.path,parent:r}),this._def.right._parseSync({data:r.data,path:r.path,parent:r}))}static create=(e,t,r)=>new n({left:e,right:t,typeName:"ZodIntersection",...Z(r)})},J=class n extends h{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==p.array)return c(r,{code:u.invalid_type,expected:p.array,received:r.parsedType}),f;if(r.data.lengththis._def.items.length&&(c(r,{code:u.too_big,maximum:this._def.items.length,inclusive:!0,exact:!1,type:"array"}),t.dirty());let a=[...r.data].map((o,i)=>{let y=this._def.items[i]||this._def.rest;return y?y._parse(new V(r,o,r.path,i)):null}).filter(o=>!!o);return r.common.async?Promise.all(a).then(o=>R.mergeArray(t,o)):R.mergeArray(t,a)}get items(){return this._def.items}rest(e){return new n({...this._def,rest:e})}static create=(e,t)=>{if(!Array.isArray(e))throw new Error("You must pass an array of schemas to z.tuple([ ... ])");return new n({items:e,typeName:"ZodTuple",rest:null,...Z(t)})}},Ve=class n extends h{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==p.object)return c(r,{code:u.invalid_type,expected:p.object,received:r.parsedType}),f;let s=[],a=this._def.keyType,o=this._def.valueType;for(let i in r.data)s.push({key:a._parse(new V(r,i,r.path,i)),value:o._parse(new V(r,r.data[i],r.path,i)),alwaysSet:i in r.data});return r.common.async?R.mergeObjectAsync(t,s):R.mergeObjectSync(t,s)}get element(){return this._def.valueType}static create(e,t,r){return t instanceof h?new n({keyType:e,valueType:t,typeName:"ZodRecord",...Z(r)}):new n({keyType:oe.create(),valueType:e,typeName:"ZodRecord",...Z(t)})}},Ne=class n extends h{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==p.map)return c(r,{code:u.invalid_type,expected:p.map,received:r.parsedType}),f;let s=this._def.keyType,a=this._def.valueType,o=[...r.data.entries()].map(([i,y],l)=>({key:s._parse(new V(r,i,r.path,[l,"key"])),value:a._parse(new V(r,y,r.path,[l,"value"]))}));if(r.common.async){let i=new Map;return Promise.resolve().then(async()=>{for(let y of o){let l=await y.key,I=await y.value;if(l.status==="aborted"||I.status==="aborted")return f;(l.status==="dirty"||I.status==="dirty")&&t.dirty(),i.set(l.value,I.value)}return{status:t.value,value:i}})}else{let i=new Map;for(let y of o){let l=y.key,I=y.value;if(l.status==="aborted"||I.status==="aborted")return f;(l.status==="dirty"||I.status==="dirty")&&t.dirty(),i.set(l.value,I.value)}return{status:t.value,value:i}}}static create=(e,t,r)=>new n({valueType:t,keyType:e,typeName:"ZodMap",...Z(r)})},_e=class n extends h{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.parsedType!==p.set)return c(r,{code:u.invalid_type,expected:p.set,received:r.parsedType}),f;let s=this._def;s.minSize!==null&&r.data.sizes.maxSize.value&&(c(r,{code:u.too_big,maximum:s.maxSize.value,type:"set",inclusive:!0,exact:!1,message:s.maxSize.message}),t.dirty());let a=this._def.valueType;function o(y){let l=new Set;for(let I of y){if(I.status==="aborted")return f;I.status==="dirty"&&t.dirty(),l.add(I.value)}return{status:t.value,value:l}}let i=[...r.data.values()].map((y,l)=>a._parse(new V(r,y,r.path,l)));return r.common.async?Promise.all(i).then(y=>o(y)):o(i)}min(e,t){return new n({...this._def,minSize:{value:e,message:m.toString(t)}})}max(e,t){return new n({...this._def,maxSize:{value:e,message:m.toString(t)}})}size(e,t){return this.min(e,t).max(e,t)}nonempty(e){return this.min(1,e)}static create=(e,t)=>new n({valueType:e,minSize:null,maxSize:null,typeName:"ZodSet",...Z(t)})},Le=class n extends h{_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==p.function)return c(t,{code:u.invalid_type,expected:p.function,received:t.parsedType}),f;function r(i,y){return pe({data:i,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,ne(),D].filter(l=>!!l),issueData:{code:u.invalid_arguments,argumentsError:y}})}function s(i,y){return pe({data:i,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,ne(),D].filter(l=>!!l),issueData:{code:u.invalid_return_type,returnTypeError:y}})}let a={errorMap:t.common.contextualErrorMap},o=t.data;if(this._def.returns instanceof ue){let i=this;return P(async function(...y){let l=new G([]),I=await i._def.args.parseAsync(y,a).catch(g=>{throw l.addIssue(r(y,g)),l}),k=await Reflect.apply(o,this,I);return await i._def.returns._def.type.parseAsync(k,a).catch(g=>{throw l.addIssue(s(k,g)),l})})}else{let i=this;return P(function(...y){let l=i._def.args.safeParse(y,a);if(!l.success)throw new G([r(y,l.error)]);let I=Reflect.apply(o,this,l.data),k=i._def.returns.safeParse(I,a);if(!k.success)throw new G([s(I,k.error)]);return k.data})}}parameters(){return this._def.args}returnType(){return this._def.returns}args(...e){return new n({...this._def,args:J.create(e).rest($.create())})}returns(e){return new n({...this._def,returns:e})}implement(e){return this.parse(e)}strictImplement(e){return this.parse(e)}validate=this.implement;static create(e,t,r){return new n({args:e||J.create([]).rest($.create()),returns:t||$.create(),typeName:"ZodFunction",...Z(r)})}},xe=class n extends h{get schema(){return this._def.getter()}_parse(e){let{ctx:t}=this._processInputParams(e);return this._def.getter()._parse({data:t.data,path:t.path,parent:t})}static create=(e,t)=>new n({getter:e,typeName:"ZodLazy",...Z(t)})},Te=class n extends h{_parse(e){if(e.data!==this._def.value){let t=this._getOrReturnCtx(e);return c(t,{received:t.data,code:u.invalid_literal,expected:this._def.value}),f}return{status:"valid",value:e.data}}get value(){return this._def.value}static create=(e,t)=>new n({value:e,typeName:"ZodLiteral",...Z(t)})};function ct(n,e){return new de({values:n,typeName:"ZodEnum",...Z(e)})}var de=class n extends h{#e;_parse(e){if(typeof e.data!="string"){let t=this._getOrReturnCtx(e),r=this._def.values;return c(t,{expected:v.joinValues(r),received:t.parsedType,code:u.invalid_type}),f}if(this.#e||(this.#e=new Set(this._def.values)),!this.#e.has(e.data)){let t=this._getOrReturnCtx(e),r=this._def.values;return c(t,{received:t.data,code:u.invalid_enum_value,options:r}),f}return P(e.data)}get options(){return this._def.values}get enum(){let e={};for(let t of this._def.values)e[t]=t;return e}get Values(){let e={};for(let t of this._def.values)e[t]=t;return e}get Enum(){let e={};for(let t of this._def.values)e[t]=t;return e}extract(e,t=this._def){return n.create(e,{...this._def,...t})}exclude(e,t=this._def){return n.create(this.options.filter(r=>!e.includes(r)),{...this._def,...t})}static create=ct},Ce=class n extends h{#e;_parse(e){let t=v.getValidEnumValues(this._def.values),r=this._getOrReturnCtx(e);if(r.parsedType!==p.string&&r.parsedType!==p.number){let s=v.objectValues(t);return c(r,{expected:v.joinValues(s),received:r.parsedType,code:u.invalid_type}),f}if(this.#e||(this.#e=new Set(v.getValidEnumValues(this._def.values))),!this.#e.has(e.data)){let s=v.objectValues(t);return c(r,{received:r.data,code:u.invalid_enum_value,options:s}),f}return P(e.data)}get enum(){return this._def.values}static create=(e,t)=>new n({values:e,typeName:"ZodNativeEnum",...Z(t)})},ue=class n extends h{unwrap(){return this._def.type}_parse(e){let{ctx:t}=this._processInputParams(e);if(t.parsedType!==p.promise&&t.common.async===!1)return c(t,{code:u.invalid_type,expected:p.promise,received:t.parsedType}),f;let r=t.parsedType===p.promise?t.data:Promise.resolve(t.data);return P(r.then(s=>this._def.type.parseAsync(s,{path:t.path,errorMap:t.common.contextualErrorMap})))}static create=(e,t)=>new n({type:e,typeName:"ZodPromise",...Z(t)})},z=class n extends h{innerType(){return this._def.schema}sourceType(){return this._def.schema._def.typeName==="ZodEffects"?this._def.schema.sourceType():this._def.schema}_parse(e){let{status:t,ctx:r}=this._processInputParams(e),s=this._def.effect||null,a={addIssue:o=>{c(r,o),o.fatal?t.abort():t.dirty()},get path(){return r.path}};if(a.addIssue=a.addIssue.bind(a),s.type==="preprocess"){let o=s.transform(r.data,a);if(r.common.async)return Promise.resolve(o).then(async i=>{if(t.value==="aborted")return f;let y=await this._def.schema._parseAsync({data:i,path:r.path,parent:r});return y.status==="aborted"?f:y.status==="dirty"?Q(y.value):t.value==="dirty"?Q(y.value):y});{if(t.value==="aborted")return f;let i=this._def.schema._parseSync({data:o,path:r.path,parent:r});return i.status==="aborted"?f:i.status==="dirty"?Q(i.value):t.value==="dirty"?Q(i.value):i}}if(s.type==="refinement"){let o=i=>{let y=s.refinement(i,a);if(r.common.async)return Promise.resolve(y);if(y instanceof Promise)throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");return i};if(r.common.async===!1){let i=this._def.schema._parseSync({data:r.data,path:r.path,parent:r});return i.status==="aborted"?f:(i.status==="dirty"&&t.dirty(),o(i.value),{status:t.value,value:i.value})}else return this._def.schema._parseAsync({data:r.data,path:r.path,parent:r}).then(i=>i.status==="aborted"?f:(i.status==="dirty"&&t.dirty(),o(i.value).then(()=>({status:t.value,value:i.value}))))}if(s.type==="transform")if(r.common.async===!1){let o=this._def.schema._parseSync({data:r.data,path:r.path,parent:r});if(!re(o))return o;let i=s.transform(o.value,a);if(i instanceof Promise)throw new Error("Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.");return{status:t.value,value:i}}else return this._def.schema._parseAsync({data:r.data,path:r.path,parent:r}).then(o=>re(o)?Promise.resolve(s.transform(o.value,a)).then(i=>({status:t.value,value:i})):o);v.assertNever(s)}static create=(e,t,r)=>new n({schema:e,typeName:"ZodEffects",effect:t,...Z(r)});static createWithPreprocess=(e,t,r)=>new n({schema:t,effect:{type:"preprocess",transform:e},typeName:"ZodEffects",...Z(r)})};var H=class n extends h{_parse(e){return this._getType(e)===p.undefined?P(void 0):this._def.innerType._parse(e)}unwrap(){return this._def.innerType}static create=(e,t)=>new n({innerType:e,typeName:"ZodOptional",...Z(t)})},q=class n extends h{_parse(e){return this._getType(e)===p.null?P(null):this._def.innerType._parse(e)}unwrap(){return this._def.innerType}static create=(e,t)=>new n({innerType:e,typeName:"ZodNullable",...Z(t)})},ve=class n extends h{_parse(e){let{ctx:t}=this._processInputParams(e),r=t.data;return t.parsedType===p.undefined&&(r=this._def.defaultValue()),this._def.innerType._parse({data:r,path:t.path,parent:t})}removeDefault(){return this._def.innerType}static create=(e,t)=>new n({innerType:e,typeName:"ZodDefault",defaultValue:typeof t.default=="function"?t.default:()=>t.default,...Z(t)})},ke=class n extends h{_parse(e){let{ctx:t}=this._processInputParams(e),r={...t,common:{...t.common,issues:[]}},s=this._def.innerType._parse({data:r.data,path:r.path,parent:{...r}});return se(s)?s.then(a=>({status:"valid",value:a.status==="valid"?a.value:this._def.catchValue({get error(){return new G(r.common.issues)},input:r.data})})):{status:"valid",value:s.status==="valid"?s.value:this._def.catchValue({get error(){return new G(r.common.issues)},input:r.data})}}removeCatch(){return this._def.innerType}static create=(e,t)=>new n({innerType:e,typeName:"ZodCatch",catchValue:typeof t.catch=="function"?t.catch:()=>t.catch,...Z(t)})},Oe=class n extends h{_parse(e){if(this._getType(e)!==p.nan){let r=this._getOrReturnCtx(e);return c(r,{code:u.invalid_type,expected:p.nan,received:r.parsedType}),f}return{status:"valid",value:e.data}}static create=e=>new n({typeName:"ZodNaN",...Z(e)})},jr=Symbol("zod_brand"),Ae=class extends h{_parse(e){let{ctx:t}=this._processInputParams(e),r=t.data;return this._def.type._parse({data:r,path:t.path,parent:t})}unwrap(){return this._def.type}},Re=class n extends h{_parse(e){let{status:t,ctx:r}=this._processInputParams(e);if(r.common.async)return(async()=>{let a=await this._def.in._parseAsync({data:r.data,path:r.path,parent:r});return a.status==="aborted"?f:a.status==="dirty"?(t.dirty(),Q(a.value)):this._def.out._parseAsync({data:a.value,path:r.path,parent:r})})();{let s=this._def.in._parseSync({data:r.data,path:r.path,parent:r});return s.status==="aborted"?f:s.status==="dirty"?(t.dirty(),{status:"dirty",value:s.value}):this._def.out._parseSync({data:s.value,path:r.path,parent:r})}}static create(e,t){return new n({in:e,out:t,typeName:"ZodPipeline"})}},Pe=class n extends h{_parse(e){let t=this._def.innerType._parse(e),r=s=>(re(s)&&(s.value=Object.freeze(s.value)),s);return se(t)?t.then(s=>r(s)):r(t)}static create=(e,t)=>new n({innerType:e,typeName:"ZodReadonly",...Z(t)});unwrap(){return this._def.innerType}};function lt(n,e={},t){return n?ie.create().superRefine((r,s)=>{if(!n(r)){let a=typeof e=="function"?e(r):typeof e=="string"?{message:e}:e,o=a.fatal??t??!0,i=typeof a=="string"?{message:a}:a;s.addIssue({code:"custom",...i,fatal:o})}}):ie.create()}var Fr={object:F.lazycreate},pt=(C=>(C.ZodString="ZodString",C.ZodNumber="ZodNumber",C.ZodNaN="ZodNaN",C.ZodBigInt="ZodBigInt",C.ZodBoolean="ZodBoolean",C.ZodDate="ZodDate",C.ZodSymbol="ZodSymbol",C.ZodUndefined="ZodUndefined",C.ZodNull="ZodNull",C.ZodAny="ZodAny",C.ZodUnknown="ZodUnknown",C.ZodNever="ZodNever",C.ZodVoid="ZodVoid",C.ZodArray="ZodArray",C.ZodObject="ZodObject",C.ZodUnion="ZodUnion",C.ZodDiscriminatedUnion="ZodDiscriminatedUnion",C.ZodIntersection="ZodIntersection",C.ZodTuple="ZodTuple",C.ZodRecord="ZodRecord",C.ZodMap="ZodMap",C.ZodSet="ZodSet",C.ZodFunction="ZodFunction",C.ZodLazy="ZodLazy",C.ZodLiteral="ZodLiteral",C.ZodEnum="ZodEnum",C.ZodEffects="ZodEffects",C.ZodNativeEnum="ZodNativeEnum",C.ZodOptional="ZodOptional",C.ZodNullable="ZodNullable",C.ZodDefault="ZodDefault",C.ZodCatch="ZodCatch",C.ZodPromise="ZodPromise",C.ZodBranded="ZodBranded",C.ZodPipeline="ZodPipeline",C.ZodReadonly="ZodReadonly",C))(pt||{});var Jr=(n,e={message:`Input not instance of ${n.name}`})=>lt(t=>t instanceof n,e),yt=oe.create,mt=ye.create,zr=Oe.create,qr=me.create,gt=ge.create,Qr=fe.create,$r=Ke.create,es=Ze.create,ts=Ie.create,ns=ie.create,rs=$.create,ss=M.create,as=We.create,os=ee.create,is=F.create,ds=F.strictCreate,us=he.create,cs=Be.create,ls=be.create,ps=J.create,ys=Ve.create,ms=Ne.create,gs=_e.create,fs=Le.create,Zs=xe.create,Is=Te.create,hs=de.create,bs=Ce.create,xs=ue.create,Ts=z.create,Cs=H.create,vs=q.create,ks=z.createWithPreprocess,As=Re.create,Rs=()=>yt().optional(),Ps=()=>mt().optional(),Ss=()=>gt().optional(),ws={string:n=>oe.create({...n,coerce:!0}),number:n=>ye.create({...n,coerce:!0}),boolean:n=>ge.create({...n,coerce:!0}),bigint:n=>me.create({...n,coerce:!0}),date:n=>fe.create({...n,coerce:!0})};var Gs=f;var B="treeview",K="TreeView Plug",Zt="enableTreeView",It="treeViewSizeOverride",ht="treeViewShowHidden",Ks=["rhs","lhs","bhs","modal"],Ws=T.object({type:T.literal("regex"),rule:T.string(),negate:T.boolean().optional().default(!1)}),Bs=T.object({type:T.literal("tags"),tags:T.array(T.string()),negate:T.boolean().optional().default(!1),attribute:T.enum(["tags","itags","tag"]).optional().default("tags")}),Vs=T.object({type:T.literal("space-function"),name:T.string(),negate:T.boolean().optional().default(!1)}),Ns=["asc","desc"],ft=T.object({position:T.enum(Ks).optional().default("lhs"),size:T.number().gt(0).optional().default(1),sort:T.object({order:T.enum(Ns).optional().default("asc"),positions:T.object({folders:T.number().optional().default(1),folderNotes:T.number().optional().default(1),pages:T.number().optional().default(2),attachments:T.number().optional().default(2)}).optional().default({})}).optional().default({}),dragAndDrop:T.object({enabled:T.boolean().optional().default(!0),confirmOnRename:T.boolean().optional().default(!0)}).optional().default({}),revealOnLoad:T.boolean().optional().default(!0),attachments:T.object({enabled:T.boolean().optional().default(!1),extensionExcludeRegex:T.string().optional().default("")}).optional().default({}),pageExcludeRegex:T.string().optional().default(""),exclusions:T.array(T.discriminatedUnion("type",[Ws,Bs,Vs])).optional()});async function _s(n){if(He)return;He=!0;let e=`${typeof n}: ${String(n)}`;if(n instanceof G){let{formErrors:t,fieldErrors:r}=n.flatten(),s=Object.keys(r).map(a=>`\`${a}\` - ${r[a].join(", ")}`);e=[...t,...s].join("; ")}await O.flashNotification(`There was an error with your ${B} configuration. Check your SETTINGS file: ${e}`,"error")}var He=!1;async function Xe(){let n=await _.getSpaceConfig("treeview",{});console.log(`${K}: raw config from space:`,JSON.stringify(n));try{let e=ft.parse(n||{});return console.log(`${K}: parsed config:`,JSON.stringify(e)),e}catch(e){return He||(_s(e),He=!0),ft.parse({})}}async function ze(){return!!await E.get(Zt)}async function qe(n){return await E.set(Zt,n)}async function Ee(){return await E.get(It)}async function Qe(n){return await E.set(It,n)}async function $e(){return!!await E.get(ht)}async function bt(n){return await E.set(ht,n)}async function xt(){return await O.getUiOption("customStyles")}var Tt=async(n,{name:e,negate:t})=>{try{let r=await Promise.all(n.map(s=>_.invokeSpaceFunction(e,s)));return n.filter((s,a)=>t?r[a]:!r[a])}catch(r){return console.error(`${K}: filtering pages by function "${e}" failed`,r),n}};var et=(n,{rule:e,negate:t})=>{try{let r=new RegExp(e);return n.filter(({name:s})=>r.test(s)===t)}catch(r){return console.error(`${K}: filtering pages by regex "${e}" failed`,r),n}};var Ct=(n,{attribute:e,negate:t,tags:r})=>n.filter(s=>{let a=s[e],o=a!==void 0&&(Array.isArray(a)?a.some(i=>r.includes(i)):r.includes(a));return t?o:!o});async function vt(n,e=!1){let t=await O.getCurrentPage(),r=await _.invokeFunction("index.queryLuaObjects","page",{}),s={nodes:[]};if(!e){if(n.pageExcludeRegex){let g=`${K}:
+\`pageExcludeRegex\` setting is deprecated. Please use \`exclusions\`:
+
+\`\`\`yaml
+treeview:
+ exclusions:
+ - type: regex
+ rule: "${n.pageExcludeRegex}"
+\`\`\`
+ `;console.warn(g),r=et(r,{rule:n.pageExcludeRegex,negate:!1})}if(console.log(`${K}: exclusions config:`,JSON.stringify(n.exclusions)),console.log(`${K}: pages before exclusions:`,r.map(g=>g.name)),n.exclusions)for(let g of n.exclusions){console.log(`${K}: applying exclusion:`,JSON.stringify(g));let b=r.length;switch(g.type){case"regex":{r=et(r,g),console.log(`${K}: regex "${g.rule}" filtered ${b} -> ${r.length}`);break}case"tags":{r=Ct(r,g);break}case"space-function":{r=await Tt(r,g);break}}}}r.sort((g,b)=>g.name.localeCompare(b.name));let a=n.sort?.order??"asc",o=n.sort?.positions??{},i=o.folders??1,y=o.folderNotes??1,l=o.pages??2,I=o.attachments??2,k=g=>g.data.nodeType==="folder"?i:g.data.nodeType==="attachment"?I:g.nodes.length>0?y:l,x=g=>{g.sort((b,A)=>{let S=k(b),X=k(A);if(S!==X)return S-X;let L=b.data.title.localeCompare(A.data.title);return a==="desc"?-L:L}),g.forEach(b=>x(b.nodes))};if(r.forEach(g=>{g.name.split("/").reduce((b,A,S,X)=>{let L=X.length-1===S,N=b.nodes.find(W=>W.data.title===A);if(N)return L&&!("created"in N.data)&&(N.data={...g,title:A,isCurrentPage:t===g.name,nodeType:"page"}),N;if(L)N={data:{...g,title:A,isCurrentPage:t===g.name,nodeType:"page"},nodes:[]};else{let W=X.slice(0,S+1).join("/");N={data:{title:A,isCurrentPage:t===W,name:W,nodeType:"folder"},nodes:[]}}return b.nodes.push(N),N},s)}),n.attachments?.enabled){let g=await Se.listAttachments();if(!e&&n.attachments.extensionExcludeRegex){let b=new RegExp(n.attachments.extensionExcludeRegex);g=g.filter(A=>!b.test(A.name))}if(!e&&n.exclusions){for(let b of n.exclusions)if(b.type==="regex"){let A=new RegExp(b.rule);g=g.filter(S=>A.test(S.name)===b.negate)}}g.forEach(b=>{b.name.split("/").reduce((A,S,X,L)=>{let N=L.length-1===X,W=A.nodes.find(De=>De.data.title===S);if(W)return N&&W.data.nodeType==="folder"&&(W.data={...b,title:S,isCurrentPage:!1,nodeType:"attachment"}),W;if(N)W={data:{...b,title:S,isCurrentPage:!1,nodeType:"attachment"},nodes:[]};else{let De=L.slice(0,X+1).join("/");W={data:{title:S,isCurrentPage:!1,name:De,nodeType:"folder"},nodes:[]}}return A.nodes.push(W),W},s)})}return x(s.nodes),{nodes:s.nodes,currentPage:t}}var ce;async function kt(){await ze()?await Ye():await j()}async function Ye(){ce&&(await O.hidePanel(ce),ce=void 0,await qe(!1))}async function At(){try{if(await _.getEnv()==="server")return;if(await ze())return await j()}catch(n){console.error(`${K}: showTreeIfEnabled failed`,n)}}async function j(){let n=await Xe();ce&&n.position!==ce&&await Ye();let e=await $e(),[t,r,s,a,o,i,y,l,I,k,x]=await Promise.all([w.readAsset(B,"assets/sortable-tree/sortable-tree.css"),w.readAsset(B,"assets/sortable-tree/sortable-tree.js"),w.readAsset(B,"assets/treeview.css"),w.readAsset(B,"assets/treeview.js"),w.readAsset(B,"assets/icons/folder-minus.svg"),w.readAsset(B,"assets/icons/folder-plus.svg"),w.readAsset(B,"assets/icons/navigation-2.svg"),w.readAsset(B,"assets/icons/refresh-cw.svg"),w.readAsset(B,"assets/icons/x-circle.svg"),w.readAsset(B,"assets/icons/eye.svg"),w.readAsset(B,"assets/icons/eye-off.svg")]),{currentPage:g,nodes:b}=await vt(n,e),A=await xt(),S={nodes:b,currentPage:g,treeElementId:"treeview-tree",dragAndDrop:{...n.dragAndDrop,enabled:n.dragAndDrop.enabled},revealOnLoad:n.revealOnLoad},L=await Ee()??n.size;await O.showPanel(n.position,L,`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`,`
+ ${r}
+ ${a}
+ initializeTreeViewPanel(${JSON.stringify(S)});
+ `),await qe(!0),ce=n.position}async function Rt(){let n=await $e();await bt(!n),await j()}async function Pt(){await _.invokeCommand("Page: Delete")}var St=.1;async function wt(){let n=await Xe(),e=await Ee()??n.size;await Qe(e+St),await j()}async function Gt(){let n=await Xe(),e=await Ee()??n.size,t=Math.max(.1,e-St);await Qe(t),await j()}var Kt={hide:Ye,show:j,showIfEnabled:At,toggle:kt,reveal:j,deletePage:Pt,increaseWidth:wt,decreaseWidth:Gt,toggleHidden:Rt},Wt={name:"treeview",version:.15,assets:{"assets/treeview.css":{data:"data:text/css;base64,LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgogKiBQYWdlIHZhcmlhYmxlcwogKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiovCgogaHRtbCB7CiAgLS10cmVldmlldy1ub2RlLWJvcmRlci13aWR0aDogMnB4OwogIC0tdHJlZXZpZXctbm9kZS1ib3JkZXItcmFkaXVzOiA1cHg7CgogIC0tdHJlZXZpZXctcGFnZS1jb2xvcjogdmFyKC0tZWRpdG9yLXdpa2ktbGluay1wYWdlLWNvbG9yKTsKICAtLXRyZWV2aWV3LXBhZ2UtYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tZWRpdG9yLXdpa2ktbGluay1wYWdlLWJhY2tncm91bmQtY29sb3IpOwogIC0tdHJlZXZpZXctcGFnZS1ib3JkZXItY29sb3I6IHZhcigtLXRyZWV2aWV3LXBhZ2UtYmFja2dyb3VuZC1jb2xvcik7CgogIC0tdHJlZXZpZXctZm9sZGVyLWNvbG9yOiB2YXIoLS1lZGl0b3Itd2lraS1saW5rLXBhZ2UtbWlzc2luZy1jb2xvcik7CiAgLS10cmVldmlldy1mb2xkZXItYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tdHJlZXZpZXctcGFnZS1iYWNrZ3JvdW5kLWNvbG9yKTsKICAtLXRyZWV2aWV3LWZvbGRlci1ib3JkZXItY29sb3I6IHZhcigtLXRyZWV2aWV3LXBhZ2UtYmFja2dyb3VuZC1jb2xvcik7CgogIC0tdHJlZXZpZXctYXR0YWNobWVudC1jb2xvcjogdmFyKC0tc3VidGxlLWNvbG9yKTsKICAtLXRyZWV2aWV3LWF0dGFjaG1lbnQtYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tdHJlZXZpZXctcGFnZS1iYWNrZ3JvdW5kLWNvbG9yKTsKICAtLXRyZWV2aWV3LWF0dGFjaG1lbnQtYm9yZGVyLWNvbG9yOiB2YXIoLS10cmVldmlldy1wYWdlLWJhY2tncm91bmQtY29sb3IpOwoKICAtLXRyZWV2aWV3LWN1cnJlbnQtcGFnZS1jb2xvcjogI2VlZTsKCS0tdHJlZXZpZXctY3VycmVudC1wYWdlLWJhY2tncm91bmQtY29sb3I6IHZhcigtLXVpLWFjY2VudC1jb2xvcik7CiAgLS10cmVldmlldy1jdXJyZW50LXBhZ2UtYm9yZGVyLWNvbG9yOiB2YXIoLS10cmVldmlldy1wYWdlLWJvcmRlci1jb2xvcik7Cn0KCi50cmVldmlldy1yb290IHsKICAvKiBTb3J0YWJsZVRyZWUgdmFyaWFibGVzICovCiAgLS1zdC1sYWJlbC1oZWlnaHQ6IGF1dG87CiAgLS1zdC1zdWJub2Rlcy1wYWRkaW5nLWxlZnQ6IDEuMjVyZW07CiAgLS1zdC1jb2xsYXBzZS1pY29uLWhlaWdodDogMi4xcmVtOwogIC0tc3QtY29sbGFwc2UtaWNvbi13aWR0aDogMS4yNXJlbTsKICAtLXN0LWNvbGxhcHNlLWljb24tc2l6ZTogMXJlbTsKfQoKLyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgogKiBQYWdlIHN0eWxlcwogKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiovCgpodG1sLCBib2R5IHsKICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1yb290LWJhY2tncm91bmQtY29sb3IpOwogIHBhZGRpbmc6IDA7CiAgbWFyZ2luOiAwOwogIG92ZXJmbG93OiBpbml0aWFsICFpbXBvcnRhbnQ7Cn0KCi50cmVldmlldy1yb290IHsKICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1yb290LWJhY2tncm91bmQtY29sb3IpOwogIGNvbG9yOiB2YXIoLS1yb290LWNvbG9yKTsKICBmb250LWZhbWlseTogdmFyKC0tdWktZm9udCk7Cn0KCi8qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKICogSGVhZGVyIHN0eWxlcwogKgogKiBBY3Rpb24gc3R5bGVzIGFyZSBtZWFudCB0byBtaW1pYyB0aGUgVG9wIEJhciBpY29ucyAoYHNiLWFjdGlvbnNgKQogKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiovCgoudHJlZXZpZXctaGVhZGVyIHsKICBwb3NpdGlvbjogc3RpY2t5OwogIHRvcDogMDsKICB6LWluZGV4OiAyOwogIHBhZGRpbmc6IDAuNXJlbSAwLjVyZW0gMHJlbTsKICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1yb290LWJhY2tncm91bmQtY29sb3IpOwp9CgoudHJlZXZpZXctYWN0aW9ucyB7CiAgZGlzcGxheTogZmxleDsKICBqdXN0aWZ5LWNvbnRlbnQ6IHNwYWNlLWJldHdlZW47CiAgYWxpZ24taXRlbXM6IGZsZXgtc3RhcnQ7CiAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tZWRpdG9yLXdpZGdldC1iYWNrZ3JvdW5kLWNvbG9yKTsKICBib3JkZXI6IHZhcigtLXRvcC1ib3JkZXItY29sb3IpIDFweCBzb2xpZDsKICBib3JkZXItcmFkaXVzOiA1cHg7CiAgY29sb3I6IHZhcigtLXRvcC1jb2xvcik7Cn0KCi50cmVldmlldy1hY3Rpb25zLWxlZnQsCi50cmVldmlldy1hY3Rpb25zLXJpZ2h0IHsKICBkaXNwbGF5OiBmbGV4Owp9CgoudHJlZXZpZXctYWN0aW9ucy1sZWZ0IHsKICBmbGV4OiAxOwogIGZsZXgtd3JhcDogd3JhcDsKfQoKLnRyZWV2aWV3LWFjdGlvbnMgYnV0dG9uIHsKICBkaXNwbGF5OiBmbGV4OwogIGFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgbWFyZ2luOiAzcHg7CiAgcGFkZGluZzogNXB4OwogIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWFjdGlvbi1idXR0b24tYmFja2dyb3VuZC1jb2xvcik7CiAgYm9yZGVyOiAwOwogIGNvbG9yOiB2YXIoLS1hY3Rpb24tYnV0dG9uLWNvbG9yKTsKICBjdXJzb3I6IHBvaW50ZXI7Cn0KCi50cmVldmlldy1hY3Rpb25zIGJ1dHRvbjpob3ZlciB7CiAgY29sb3I6IHZhcigtLWFjdGlvbi1idXR0b24taG92ZXItY29sb3IpOwp9CgoudHJlZXZpZXctYWN0aW9ucyBidXR0b246aG92ZXIgewogIGNvbG9yOiB2YXIoLS1hY3Rpb24tYnV0dG9uLWFjdGl2ZS1jb2xvcik7Cn0KCi50cmVldmlldy1hY3Rpb25zIGJ1dHRvbiA+IHN2ZyB7CiAgaGVpZ2h0OiAxOHB4OwogIHdpZHRoOiAxOHB4Owp9CgovKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqCiAqIFNvcnRhYmxlVHJlZSBzdHlsZXMKICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqLwoKLnRyZWUgewoJZGlzcGxheTogZmxleDsKCWZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgbWFyZ2luOiAwLjI1cmVtIDAuNXJlbSAxcmVtOwogIHBhZGRpbmctbGVmdDogdmFyKC0tc3Qtc3Vibm9kZXMtcGFkZGluZy1sZWZ0KTsKfQoKLnRyZWVfX2xhYmVsIHsKICBkaXNwbGF5OiBmbGV4OwogIHBhZGRpbmc6IDJweDsKfQoKLnRyZWVfX2xhYmVsOmFmdGVyIHsKICBjb250ZW50OiAnICc7CiAgcG9zaXRpb246IGFic29sdXRlOwogIHotaW5kZXg6IDQ7CiAgaW5zZXQ6IGNhbGModmFyKC0tdHJlZXZpZXctbm9kZS1ib3JkZXItd2lkdGgpICogLTEpIHZhcigtLXRyZWV2aWV3LW5vZGUtYm9yZGVyLXdpZHRoKTsKICBib3JkZXItdG9wOiBjYWxjKHZhcigtLXRyZWV2aWV3LW5vZGUtYm9yZGVyLXdpZHRoKSAqIDIpIHNvbGlkIHRyYW5zcGFyZW50OwogIGJvcmRlci1ib3R0b206IGNhbGModmFyKC0tdHJlZXZpZXctbm9kZS1ib3JkZXItd2lkdGgpICogMikgc29saWQgdHJhbnNwYXJlbnQ7CiAgdHJhbnNpdGlvbjogYm9yZGVyLWNvbG9yIDAuMnM7CiAgcG9pbnRlci1ldmVudHM6IG5vbmU7Cn0KCi50cmVlX19ub2RlLS1kcm9wLWJlZm9yZSA+IC50cmVlX19sYWJlbDphZnRlciB7CiAgYm9yZGVyLXRvcC1jb2xvcjogdmFyKC0tdWktYWNjZW50LWNvbG9yKTsKfQoKLnRyZWVfX25vZGUtLWRyb3AtYWZ0ZXIgPiAudHJlZV9fbGFiZWw6YWZ0ZXIgewogIGJvcmRlci1ib3R0b20tY29sb3I6IHZhcigtLXVpLWFjY2VudC1jb2xvcik7Cn0KCi50cmVlX19sYWJlbCA+IHNwYW4gewogIG92ZXJmbG93OiBoaWRkZW47CiAgcGFkZGluZzogMCA1cHg7CiAgYm9yZGVyLWNvbG9yOiB0cmFuc3BhcmVudDsKICBib3JkZXItd2lkdGg6IHZhcigtLXRyZWV2aWV3LW5vZGUtYm9yZGVyLXdpZHRoKTsKICBib3JkZXItc3R5bGU6IHNvbGlkOwogIGJvcmRlci1yYWRpdXM6IHZhcigtLXRyZWV2aWV3LW5vZGUtYm9yZGVyLXJhZGl1cyk7CiAgY3Vyc29yOiBwb2ludGVyOwogIGZvbnQtZmFtaWx5OiB2YXIoLS1lZGl0b3ItZm9udCk7CiAgZm9udC1zaXplOiAxOHB4OwogIGxpbmUtaGVpZ2h0OiAxLjQ7CiAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwogIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzOwogIHdoaXRlLXNwYWNlOiBub3dyYXA7Cn0KCi50cmVlX19sYWJlbCA+IHNwYW5bZGF0YS1ub2RlLXR5cGU9InBhZ2UiXSB7CiAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tdHJlZXZpZXctcGFnZS1iYWNrZ3JvdW5kLWNvbG9yKTsKICBib3JkZXItY29sb3I6IHZhcigtLXRyZWV2aWV3LXBhZ2UtYm9yZGVyLWNvbG9yKTsKICBjb2xvcjogdmFyKC0tdHJlZXZpZXctcGFnZS1jb2xvcik7Cn0KCi50cmVlX19sYWJlbCA+IHNwYW5bZGF0YS1ub2RlLXR5cGU9ImZvbGRlciJdIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS10cmVldmlldy1mb2xkZXItYmFja2dyb3VuZC1jb2xvcik7CiAgYm9yZGVyLWNvbG9yOiB2YXIoLS10cmVldmlldy1mb2xkZXItYm9yZGVyLWNvbG9yKTsKICBjb2xvcjogdmFyKC0tdHJlZXZpZXctZm9sZGVyLWNvbG9yKTsKfQoKLnRyZWVfX2xhYmVsID4gc3BhbltkYXRhLW5vZGUtdHlwZT0iYXR0YWNobWVudCJdIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS10cmVldmlldy1hdHRhY2htZW50LWJhY2tncm91bmQtY29sb3IpOwogIGJvcmRlci1jb2xvcjogdmFyKC0tdHJlZXZpZXctYXR0YWNobWVudC1ib3JkZXItY29sb3IpOwogIGNvbG9yOiB2YXIoLS10cmVldmlldy1hdHRhY2htZW50LWNvbG9yKTsKfQoKCi50cmVlX19sYWJlbCA+IHNwYW5bZGF0YS1jdXJyZW50LXBhZ2U9InRydWUiXSB7CgliYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS10cmVldmlldy1jdXJyZW50LXBhZ2UtYmFja2dyb3VuZC1jb2xvcik7CiAgYm9yZGVyLWNvbG9yOiB2YXIoLS10cmVldmlldy1jdXJyZW50LXBhZ2UtYm9yZGVyLWNvbG9yKTsKCWNvbG9yOiB2YXIoLS10cmVldmlldy1jdXJyZW50LXBhZ2UtY29sb3IpOwp9CgoudHJlZV9fY29sbGFwc2UgewogIGNvbG9yOiB2YXIoLS1lZGl0b3ItY29tbWFuZC1idXR0b24tY29sb3IpOwp9CgoudHJlZV9fbm9kZS0tZHJhZ2dpbmcgewogIG1hcmdpbjogY2FsYyh2YXIoLS10cmVldmlldy1ub2RlLWJvcmRlci13aWR0aCkgKiAtMSk7CiAgYm9yZGVyOiB2YXIoLS10cmVldmlldy1ub2RlLWJvcmRlci13aWR0aCkgZGFzaGVkIHZhcigtLXN1YnRsZS1jb2xvcik7CiAgYm9yZGVyLXJhZGl1czogdmFyKC0tdHJlZXZpZXctbm9kZS1ib3JkZXItcmFkaXVzKTsKICBjdXJzb3I6IG1vdmUgIWltcG9ydGFudDsKfQoKLnRyZWVfX25vZGUtLWRyYWdnaW5nIC50cmVlX19sYWJlbCA+IHNwYW4gewogIG9wYWNpdHk6IDAuNzU7Cn0KCi50cmVlX19ub2RlLS1kcm9wLWluc2lkZSA+IC50cmVlX19sYWJlbCA+IHNwYW4gewogIHotaW5kZXg6IDM7CiAgYmFja2dyb3VuZC1jb2xvcjogdmFyKC0tc3VidGxlLWJhY2tncm91bmQtY29sb3IpOwogIGJvcmRlci1jb2xvcjogdmFyKC0tdWktYWNjZW50LWNvbG9yKTsKfQoKLnRyZWVfX25vZGUtLWRyb3AtaW5zaWRlID4gLnRyZWVfX2xhYmVsID4gc3BhbltkYXRhLWN1cnJlbnQtcGFnZT0idHJ1ZSJdIHsKCWNvbG9yOiB2YXIoLS10cmVldmlldy1wYWdlLWNvbG9yKTsKfQ==",mtime:1770926754871},"assets/sortable-tree/sortable-tree.css":{data:"data:text/css;base64,LyohIFNvcnRhYmxlIFRyZWUgMC43LjIsIChjKSAyMDIzIE1hcmMgQW50b24gRGFobWVuLCBNSVQgbGljZW5zZSAqLwo6cm9vdCB7CiAgICAtLXN0LWxhYmVsLWhlaWdodDogMi41cmVtOwogICAgLS1zdC1zdWJub2Rlcy1wYWRkaW5nLWxlZnQ6IDEuNXJlbTsKICAgIC0tc3QtY29sbGFwc2UtaWNvbi1oZWlnaHQ6IHZhcigtLXN0LWxhYmVsLWhlaWdodCk7CiAgICAtLXN0LWNvbGxhcHNlLWljb24td2lkdGg6IDEuMjVyZW07CiAgICAtLXN0LWNvbGxhcHNlLWljb24tc2l6ZTogMC43NXJlbTsKICB9CiAgc29ydGFibGUtdHJlZS1ub2RlIHsKICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsKICAgIHotaW5kZXg6IDE7CiAgICBkaXNwbGF5OiBmbGV4OwogICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICB9CiAgc29ydGFibGUtdHJlZS1ub2RlICogewogICAgdXNlci1zZWxlY3Q6IG5vbmU7CiAgfQogIHNvcnRhYmxlLXRyZWUtbm9kZSA+IDpmaXJzdC1jaGlsZCB7CiAgICBkaXNwbGF5OiBmbGV4OwogICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgIGhlaWdodDogdmFyKC0tc3QtbGFiZWwtaGVpZ2h0KTsKICB9CiAgc29ydGFibGUtdHJlZS1ub2RlID4gOm50aC1jaGlsZCgyKSB7CiAgICBkaXNwbGF5OiBub25lOwogICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICAgIHBhZGRpbmctbGVmdDogdmFyKC0tc3Qtc3Vibm9kZXMtcGFkZGluZy1sZWZ0KTsKICB9CiAgc29ydGFibGUtdHJlZS1ub2RlW29wZW5dID4gZGl2Om50aC1jaGlsZCgyKSB7CiAgICBkaXNwbGF5OiBmbGV4OwogIH0KICBzb3J0YWJsZS10cmVlLW5vZGUgPiA6bnRoLWNoaWxkKDMpIHsKICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgIGRpc3BsYXk6IGZsZXg7CiAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgdG9wOiAwOwogICAgbGVmdDogY2FsYyh2YXIoLS1zdC1jb2xsYXBzZS1pY29uLXdpZHRoKSAqIC0xKTsKICAgIGhlaWdodDogdmFyKC0tc3QtY29sbGFwc2UtaWNvbi1oZWlnaHQpOwogICAgZm9udC1zaXplOiB2YXIoLS1zdC1jb2xsYXBzZS1pY29uLXNpemUpOwogICAgY3Vyc29yOiBwb2ludGVyOwogIH0KICBzb3J0YWJsZS10cmVlLW5vZGUgPiA6bnRoLWNoaWxkKDIpOmVtcHR5ICsgc3BhbiB7CiAgICBkaXNwbGF5OiBub25lOwogIH0KICAKICA=",mtime:1770926754872},"assets/sortable-tree/sortable-tree.js":{data:"data:application/javascript;base64,LyohIFNvcnRhYmxlIFRyZWUgMC43LjIsIChjKSAyMDI0IE1hcmMgQW50b24gRGFobWVuLCBNSVQgbGljZW5zZSAqLwovKiBNb2RpZmllZCBieSBNck11Z2FtZSAoaHR0cHM6Ly9naXRodWIuY29tL21hcmNhbnRvbmRhaG1lbi9zb3J0YWJsZS10cmVlL3B1bGwvMTEpICovCiFmdW5jdGlvbiAoZSwgdCkgeyAib2JqZWN0IiA9PSB0eXBlb2YgZXhwb3J0cyAmJiAib2JqZWN0IiA9PSB0eXBlb2YgbW9kdWxlID8gbW9kdWxlLmV4cG9ydHMgPSB0KCkgOiAiZnVuY3Rpb24iID09IHR5cGVvZiBkZWZpbmUgJiYgZGVmaW5lLmFtZCA/IGRlZmluZShbXSwgdCkgOiAib2JqZWN0IiA9PSB0eXBlb2YgZXhwb3J0cyA/IGV4cG9ydHMuU29ydGFibGVUcmVlID0gdCgpIDogZS5Tb3J0YWJsZVRyZWUgPSB0KCkgfShzZWxmLCAoKCkgPT4gKCgpID0+IHsgInVzZSBzdHJpY3QiOyB2YXIgZSA9IHsgZDogKHQsIG4pID0+IHsgZm9yICh2YXIgbyBpbiBuKSBlLm8obiwgbykgJiYgIWUubyh0LCBvKSAmJiBPYmplY3QuZGVmaW5lUHJvcGVydHkodCwgbywgeyBlbnVtZXJhYmxlOiAhMCwgZ2V0OiBuW29dIH0pIH0sIG86IChlLCB0KSA9PiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZSwgdCkgfSwgdCA9IHt9OyBlLmQodCwgeyBkZWZhdWx0OiAoKSA9PiBMIH0pOyB2YXIgbiA9IGZ1bmN0aW9uIChlLCB0LCBuLCBvKSB7IHJldHVybiBuZXcgKG4gfHwgKG4gPSBQcm9taXNlKSkoKGZ1bmN0aW9uIChyLCBzKSB7IGZ1bmN0aW9uIGkoZSkgeyB0cnkgeyBsKG8ubmV4dChlKSkgfSBjYXRjaCAoZSkgeyBzKGUpIH0gfSBmdW5jdGlvbiBhKGUpIHsgdHJ5IHsgbChvLnRocm93KGUpKSB9IGNhdGNoIChlKSB7IHMoZSkgfSB9IGZ1bmN0aW9uIGwoZSkgeyB2YXIgdDsgZS5kb25lID8gcihlLnZhbHVlKSA6ICh0ID0gZS52YWx1ZSwgdCBpbnN0YW5jZW9mIG4gPyB0IDogbmV3IG4oKGZ1bmN0aW9uIChlKSB7IGUodCkgfSkpKS50aGVuKGksIGEpIH0gbCgobyA9IG8uYXBwbHkoZSwgdCB8fCBbXSkpLm5leHQoKSkgfSkpIH07IGNvbnN0IG8gPSBlID0+IGA8c3Bhbj4ke2UudGl0bGV9PC9zcGFuPmAsIHIgPSB7IGNvbGxhcHNlZDogJ1xuXHRcdDxzdmdcblx0XHR4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIFxuXHRcdHdpZHRoPSIxZW0iIFxuXHRcdGhlaWdodD0iMWVtIiBcblx0XHRmaWxsPSJjdXJyZW50Q29sb3IiIFxuXHRcdGNsYXNzPSJiaSBiaS1jYXJldC1yaWdodC1maWxsIiBcblx0XHR2aWV3Qm94PSIwIDAgMTYgMTYiPlxuXHRcdFx0PHBhdGggZD0ibTEyLjE0IDguNzUzLTUuNDgyIDQuNzk2Yy0uNjQ2LjU2Ni0xLjY1OC4xMDYtMS42NTgtLjc1M1YzLjIwNGExIDEgMCAwIDEgMS42NTktLjc1M2w1LjQ4IDQuNzk2YTEgMSAwIDAgMSAwIDEuNTA2eiIvPlxuXHRcdDwvc3ZnPicsIG9wZW46ICdcblx0XHQ8c3ZnIFxuXHRcdHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgXG5cdFx0d2lkdGg9IjFlbSIgXG5cdFx0aGVpZ2h0PSIxZW0iIFxuXHRcdGZpbGw9ImN1cnJlbnRDb2xvciIgXG5cdFx0Y2xhc3M9ImJpIGJpLWNhcmV0LWRvd24tZmlsbCIgXG5cdFx0dmlld0JveD0iMCAwIDE2IDE2Ij5cblx0XHRcdDxwYXRoIGQ9Ik03LjI0NyAxMS4xNCAyLjQ1MSA1LjY1OEMxLjg4NSA1LjAxMyAyLjM0NSA0IDMuMjA0IDRoOS41OTJhMSAxIDAgMCAxIC43NTMgMS42NTlsLTQuNzk2IDUuNDhhMSAxIDAgMCAxLTEuNTA2IDB6Ii8+XG5cdFx0PC9zdmc+XG5cdCcgfSwgcyA9IHsgdHJlZTogInRyZWUiLCBub2RlOiAidHJlZV9fbm9kZSIsIG5vZGVIb3ZlcjogInRyZWVfX25vZGUtLWhvdmVyIiwgbm9kZURyYWdnaW5nOiAidHJlZV9fbm9kZS0tZHJhZ2dpbmciLCBub2RlRHJvcEJlZm9yZTogInRyZWVfX25vZGUtLWRyb3AtYmVmb3JlIiwgbm9kZURyb3BJbnNpZGU6ICJ0cmVlX19ub2RlLS1kcm9wLWluc2lkZSIsIG5vZGVEcm9wQWZ0ZXI6ICJ0cmVlX19ub2RlLS1kcm9wLWFmdGVyIiwgbGFiZWw6ICJ0cmVlX19sYWJlbCIsIHN1Ym5vZGVzOiAidHJlZV9fc3Vibm9kZXMiLCBjb2xsYXBzZTogInRyZWVfX2NvbGxhcHNlIiB9LCBpID0gKHsgbm9kZXM6IGUsIG1vdmVkTm9kZTogdCwgc3JjUGFyZW50Tm9kZTogbywgdGFyZ2V0UGFyZW50Tm9kZTogciB9KSA9PiBuKHZvaWQgMCwgdm9pZCAwLCB2b2lkIDAsIChmdW5jdGlvbiogKCkgeyB9KSksIGEgPSAoZSwgdCkgPT4gbih2b2lkIDAsIHZvaWQgMCwgdm9pZCAwLCAoZnVuY3Rpb24qICgpIHsgfSkpLCBsID0gKGUsIHQpID0+IG4odm9pZCAwLCB2b2lkIDAsIHZvaWQgMCwgKGZ1bmN0aW9uKiAoKSB7IHJldHVybiAhMCB9KSk7IGNvbnN0IGQgPSAoZSwgdCA9IFtdLCBuID0gbnVsbCkgPT4geyBjb25zdCBvID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChlKTsgcmV0dXJuIHQuZm9yRWFjaCgoZSA9PiB7IG8uY2xhc3NMaXN0LmFkZChlKSB9KSksIG4gJiYgbi5hcHBlbmRDaGlsZChvKSwgbyB9LCBjID0gKCkgPT4gKChlID0gMjEpID0+IGNyeXB0by5nZXRSYW5kb21WYWx1ZXMobmV3IFVpbnQ4QXJyYXkoZSkpLnJlZHVjZSgoKGUsIHQpID0+IGUgKyAoKHQgJj0gNjMpIDwgMzYgPyB0LnRvU3RyaW5nKDM2KSA6IHQgPCA2MiA/ICh0IC0gMjYpLnRvU3RyaW5nKDM2KS50b1VwcGVyQ2FzZSgpIDogdCA+IDYyID8gIi0iIDogIl8iKSksICIiKSkoKTsgY2xhc3MgdSBleHRlbmRzIEhUTUxFbGVtZW50IHsgc3RhdGljIGNyZWF0ZSh7IGRhdGE6IGUsIHJlbmRlckxhYmVsOiB0LCBpY29uczogbiwgc3R5bGVzOiBvLCBwYXJlbnQ6IHIsIG9uQ2xpY2s6IHMsIGRyYWdnYWJsZTogaSB9KSB7IGNvbnN0IGEgPSBkKHUuVEFHX05BTUUsIFtvLm5vZGVdLCByKSwgbCA9IGQoImRpdiIsIFtvLmxhYmVsXSwgYSksIGMgPSBkKCJkaXYiLCBbby5zdWJub2Rlc10sIGEpLCBoID0gZCgic3BhbiIsIFtvLmNvbGxhcHNlXSwgYSk7IHJldHVybiBsLmlubmVySFRNTCA9IHQoZSksIGguaW5uZXJIVE1MID0gbi5jb2xsYXBzZWQsIGguYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLCBhLnRvZ2dsZS5iaW5kKGEpKSwgbC5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsIChlID0+IHsgcyhlLCBhKSB9KSksIGkgJiYgYS5zZXRBdHRyaWJ1dGUoImRyYWdnYWJsZSIsICJ0cnVlIiksIGEuX2RhdGEgPSBlLCBhLl9pY29ucyA9IG4sIGEuX2xhYmVsID0gbCwgYS5fbm9kZXMgPSBjLCBhLl9jb2xsYXBzZUJ1dHRvbiA9IGgsIGEgfSBnZXQgZGF0YSgpIHsgcmV0dXJuIHRoaXMuX2RhdGEgfSBnZXQgbGFiZWwoKSB7IHJldHVybiB0aGlzLl9sYWJlbCB9IGdldCBzdWJub2RlcygpIHsgcmV0dXJuIHRoaXMuX25vZGVzIH0gZ2V0IHN1Ym5vZGVzRGF0YSgpIHsgY29uc3QgZSA9IFtdOyByZXR1cm4gQXJyYXkuZnJvbSh0aGlzLl9ub2Rlcy5jaGlsZHJlbikuZm9yRWFjaCgodCA9PiB7IGUucHVzaCh0LmRhdGEpIH0pKSwgZSB9IGdldCBpZCgpIHsgcmV0dXJuIHRoaXMuX2lkIH0gY29uc3RydWN0b3IoKSB7IHN1cGVyKCksIHRoaXMuX2lkID0gYygpIH0gY29sbGFwc2UoZSkgeyBlID8gKHRoaXMucmVtb3ZlQXR0cmlidXRlKCJvcGVuIiksIHRoaXMuX2NvbGxhcHNlQnV0dG9uLmlubmVySFRNTCA9IHRoaXMuX2ljb25zLmNvbGxhcHNlZCkgOiAodGhpcy5zZXRBdHRyaWJ1dGUoIm9wZW4iLCAidHJ1ZSIpLCB0aGlzLl9jb2xsYXBzZUJ1dHRvbi5pbm5lckhUTUwgPSB0aGlzLl9pY29ucy5vcGVuKSB9IHRvZ2dsZSgpIHsgdGhpcy5jb2xsYXBzZSh0aGlzLmhhc0F0dHJpYnV0ZSgib3BlbiIpKSB9IHJldmVhbCgpIHsgKChlLCB0KSA9PiB7IGNvbnN0IG4gPSBbXTsgbGV0IG8gPSB0LmNsb3Nlc3QoZSk7IGZvciAoOyBudWxsICE9PSBvOyluLnB1c2gobyksIG8gPSBvLnBhcmVudE5vZGUuY2xvc2VzdChlKTsgcmV0dXJuIG4gfSkodS5UQUdfTkFNRSwgdGhpcykuZm9yRWFjaCgoZSA9PiB7IGUuY29sbGFwc2UoITEpIH0pKSB9IH0gdS5UQUdfTkFNRSA9ICJzb3J0YWJsZS10cmVlLW5vZGUiOyB2YXIgaCA9IGZ1bmN0aW9uIChlLCB0LCBuLCBvKSB7IHJldHVybiBuZXcgKG4gfHwgKG4gPSBQcm9taXNlKSkoKGZ1bmN0aW9uIChyLCBzKSB7IGZ1bmN0aW9uIGkoZSkgeyB0cnkgeyBsKG8ubmV4dChlKSkgfSBjYXRjaCAoZSkgeyBzKGUpIH0gfSBmdW5jdGlvbiBhKGUpIHsgdHJ5IHsgbChvLnRocm93KGUpKSB9IGNhdGNoIChlKSB7IHMoZSkgfSB9IGZ1bmN0aW9uIGwoZSkgeyB2YXIgdDsgZS5kb25lID8gcihlLnZhbHVlKSA6ICh0ID0gZS52YWx1ZSwgdCBpbnN0YW5jZW9mIG4gPyB0IDogbmV3IG4oKGZ1bmN0aW9uIChlKSB7IGUodCkgfSkpKS50aGVuKGksIGEpIH0gbCgobyA9IG8uYXBwbHkoZSwgdCB8fCBbXSkpLm5leHQoKSkgfSkpIH07IGNvbnN0IHAgPSAoeyBub2RlOiBlLCBldmVudE5hbWU6IHQsIGhhbmRsZXI6IG4sIHRyZWU6IG8gfSkgPT4geyBlLmFkZEV2ZW50TGlzdGVuZXIodCwgKGUgPT4geyBuKGUsIG8pIH0pLCAhMSkgfTsgdmFyIGY7ICFmdW5jdGlvbiAoZSkgeyBlLkJFRk9SRSA9ICJCRUZPUkUiLCBlLklOU0lERSA9ICJJTlNJREUiLCBlLkFGVEVSID0gIkFGVEVSIiB9KGYgfHwgKGYgPSB7fSkpOyBjb25zdCBnID0gZSA9PiB7IGNvbnN0IHQgPSBlLmNsaWVudFksIG4gPSB2KGUpLmxhYmVsLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLCBvID0gTWF0aC5yb3VuZChuLmhlaWdodCAvIDQpOyByZXR1cm4gbi50b3AgKyBvID4gdCA/IGYuQkVGT1JFIDogbi5ib3R0b20gLSBvIDwgdCA/IGYuQUZURVIgOiBmLklOU0lERSB9LCB2ID0gZSA9PiBlLnRhcmdldC5jbG9zZXN0KHUuVEFHX05BTUUpLCBiID0gZSA9PiB7IGNvbnN0IHQgPSBudWxsID09IGUgPyB2b2lkIDAgOiBlLnBhcmVudEVsZW1lbnQucGFyZW50RWxlbWVudDsgcmV0dXJuIChudWxsID09IHQgPyB2b2lkIDAgOiB0LnRhZ05hbWUudG9Mb3dlckNhc2UoKSkgIT09IHUuVEFHX05BTUUgPyBudWxsIDogdCB9LCBtID0gKGUsIHQpID0+IHsgdihlKS5jbGFzc0xpc3QucmVtb3ZlKHQuc3R5bGVzLm5vZGVEcm9wQWZ0ZXIsIHQuc3R5bGVzLm5vZGVEcm9wQmVmb3JlLCB0LnN0eWxlcy5ub2RlRHJvcEluc2lkZSkgfSwgRSA9IChlLCB0KSA9PiB7IGUuc3RvcFByb3BhZ2F0aW9uKCk7IGNvbnN0IG4gPSBlLnRhcmdldDsgbi50YWdOYW1lLnRvTG93ZXJDYXNlKCkgPT09IHUuVEFHX05BTUUgJiYgKGUuZGF0YVRyYW5zZmVyLnNldERhdGEoInRleHQiLCBuLmlkKSwgZS5kYXRhVHJhbnNmZXIuZWZmZWN0QWxsb3dlZCA9ICJtb3ZlIiwgbi5jbGFzc0xpc3QuYWRkKHQuc3R5bGVzLm5vZGVEcmFnZ2luZykpIH0sIF8gPSAoZSwgdCkgPT4gaCh2b2lkIDAsIHZvaWQgMCwgdm9pZCAwLCAoZnVuY3Rpb24qICgpIHsgZS5zdG9wUHJvcGFnYXRpb24oKSwgZS5wcmV2ZW50RGVmYXVsdCgpLCBtKGUsIHQpOyBjb25zdCBuID0gdihlKTsgaWYgKCFuKSByZXR1cm4gITE7IGNvbnN0IG8gPSBiKG4pLCByID0gZS5kYXRhVHJhbnNmZXIuZ2V0RGF0YSgidGV4dCIpLCBzID0gZyhlKSwgaSA9IHQuZ2V0Tm9kZShyKTsgaWYgKCFpIHx8IChudWxsID09IGkgPyB2b2lkIDAgOiBpLmNvbnRhaW5zKG4pKSkgcmV0dXJuICExOyBjb25zdCBhID0gYihpKTsgaWYgKHQubG9ja1Jvb3RMZXZlbCAmJiAhKG51bGwgPT0gbiA/IHZvaWQgMCA6IG4ucGFyZW50RWxlbWVudC5jbG9zZXN0KHUuVEFHX05BTUUpKSAmJiAocyA9PT0gZi5CRUZPUkUgfHwgcyA9PT0gZi5BRlRFUikpIHJldHVybiAhMTsgaWYgKHMgPT09IGYuQkVGT1JFKSByZXR1cm4gISEoeWllbGQgdC5jb25maXJtKGksIG8pKSAmJiAobi5wYXJlbnROb2RlLmluc2VydEJlZm9yZShpLCBuKSwgdC5vbkRyb3AoaSwgYSwgbyksICExKTsgaWYgKHMgPT09IGYuQUZURVIpIHsgaWYgKCEoeWllbGQgdC5jb25maXJtKGksIG8pKSkgcmV0dXJuICExOyBjb25zdCBlID0gbi5uZXh0RWxlbWVudFNpYmxpbmc7IHJldHVybiBlID8gKG4ucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUoaSwgZSksIHQub25Ecm9wKGksIGEsIG8pKSA6IChuLnBhcmVudE5vZGUuYXBwZW5kQ2hpbGQoaSksIHQub25Ecm9wKGksIGEsIG8pKSwgITEgfSByZXR1cm4gISEoeWllbGQgdC5jb25maXJtKGksIG4pKSAmJiAobi5zdWJub2Rlcy5hcHBlbmRDaGlsZChpKSwgdC5vbkRyb3AoaSwgYSwgbiksICExKSB9KSksIHkgPSAoZSwgdCkgPT4geyBlLnByZXZlbnREZWZhdWx0KCksIGUuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAibW92ZSIsICgoZSwgdCkgPT4geyBjb25zdCBuID0gdihlKSwgbyA9IGcoZSk7IG4uY2xhc3NMaXN0LnRvZ2dsZSh0LnN0eWxlcy5ub2RlRHJvcEJlZm9yZSwgbyA9PT0gZi5CRUZPUkUpLCBuLmNsYXNzTGlzdC50b2dnbGUodC5zdHlsZXMubm9kZURyb3BJbnNpZGUsIG8gPT09IGYuSU5TSURFKSwgbi5jbGFzc0xpc3QudG9nZ2xlKHQuc3R5bGVzLm5vZGVEcm9wQWZ0ZXIsIG8gPT09IGYuQUZURVIpIH0pKGUsIHQpIH0sIEEgPSAoZSwgdCkgPT4geyBtKGUsIHQpIH0sIFQgPSAoZSwgdCkgPT4geyBlLnRhcmdldC5jbGFzc0xpc3QucmVtb3ZlKHQuc3R5bGVzLm5vZGVEcmFnZ2luZyksIG0oZSwgdCkgfSwgTiA9IGUgPT4gYHNvcnRhYmxlVHJlZVN0YXRlLSR7ZS5yZXBsYWNlKC9bXlx3XSsvLCAiLSIpfWA7IGNvbnN0IEwgPSBjbGFzcyB7IGNvbnN0cnVjdG9yKHsgbm9kZXM6IGUsIGVsZW1lbnQ6IHQsIHJlbmRlckxhYmVsOiBuLCBpY29uczogZCwgc3R5bGVzOiBjLCBsb2NrUm9vdExldmVsOiB1LCBvbkNoYW5nZTogaCwgb25DbGljazogcCwgaW5pdENvbGxhcHNlTGV2ZWw6IGYsIGNvbmZpcm06IGcsIGRpc2FibGVTb3J0aW5nOiB2LCBzdGF0ZUlkOiBiIH0pIHsgdGhpcy5ub2RlQ29sbGVjdGlvbiA9IHt9LCBlICYmICh0ID8gKHRoaXMuZGVmaW5lRWxlbWVudHMoKSwgdGhpcy5yb290ID0gdCwgdGhpcy5pY29ucyA9IE9iamVjdC5hc3NpZ24oT2JqZWN0LmFzc2lnbih7fSwgciksIGQpLCB0aGlzLnN0eWxlcyA9IE9iamVjdC5hc3NpZ24oT2JqZWN0LmFzc2lnbih7fSwgcyksIGMpLCB0aGlzLnJlbmRlckxhYmVsID0gbiB8fCBvLCB0aGlzLmxvY2tSb290TGV2ZWwgPSB2b2lkIDAgPT09IHUgfHwgdSwgdGhpcy5vbkNoYW5nZSA9IGggfHwgaSwgdGhpcy5vbkNsaWNrID0gcCB8fCBhLCB0aGlzLmNvbmZpcm0gPSBnIHx8IGwsIHRoaXMuaW5pdENvbGxhcHNlTGV2ZWwgPSB2b2lkIDAgPT09IGYgPyAyIDogZiwgdGhpcy5kaXNhYmxlU29ydGluZyA9IHYgfHwgITEsIHQuY2xhc3NMaXN0LmFkZCh0aGlzLnN0eWxlcy50cmVlKSwgdGhpcy5yZW5kZXIoeyBub2RlczogZSwgZWxlbWVudDogdCB9KSwgYiAmJiAoKChlLCB0KSA9PiB7IGNvbnN0IG4gPSBzZXNzaW9uU3RvcmFnZS5nZXRJdGVtKE4oZSkpOyBpZiAoIW4pIHJldHVybjsgY29uc3QgbyA9IEpTT04ucGFyc2UobiksIHIgPSAoZSwgdCkgPT4geyBjb25zdCBbbiwgb10gPSB0OyBlLmVsZW1lbnQuY29sbGFwc2UoMCA9PT0gbiksIG8gJiYgZS5zdWJub2Rlcy5mb3JFYWNoKCgoZSwgdCkgPT4geyB2b2lkIDAgIT09IG9bdF0gJiYgcihlLCBvW3RdKSB9KSkgfTsgdC5mb3JFYWNoKCgoZSwgdCkgPT4geyB2b2lkIDAgIT09IG9bdF0gJiYgcihlLCBvW3RdKSB9KSkgfSkoYiwgdGhpcy5wYXJzZVRyZWUodGhpcy5yb290KSksIHRoaXMuaW5pdFN0YXRlT2JzZXJ2ZXIoYikpKSA6IGNvbnNvbGUuZXJyb3IoJ0Vycm9yOiAiZWxlbWVudCIgaXMgbm90IGEgdmFsaWQgSFRNTCBlbGVtZW50IScpKSB9IGdldE5vZGUoZSkgeyByZXR1cm4gdGhpcy5ub2RlQ29sbGVjdGlvbltlXSB9IGZpbmROb2RlKGUsIHQpIHsgY29uc3QgbiA9IE9iamVjdC52YWx1ZXModGhpcy5ub2RlQ29sbGVjdGlvbik7IGZvciAobGV0IG8gPSAwOyBvIDwgbi5sZW5ndGg7IG8rKykgeyBjb25zdCByID0gbltvXSwgcyA9IHIuZGF0YTsgaWYgKGUgaW4gcyAmJiBzW2VdID09IHQpIHJldHVybiByIH0gcmV0dXJuIG51bGwgfSBvbkRyb3AoZSwgdCwgbikgeyBjb25zdCBvID0geyBub2RlczogdGhpcy5wYXJzZVRyZWUodGhpcy5yb290KSwgbW92ZWROb2RlOiBlLCBzcmNQYXJlbnROb2RlOiB0LCB0YXJnZXRQYXJlbnROb2RlOiBuIH07IG51bGwgPT0gbiB8fCBuLmNvbGxhcHNlKCExKSwgdGhpcy5vbkNoYW5nZShvKSB9IGRlc3Ryb3koKSB7IHRoaXMub2JzZXJ2ZXIuZGlzY29ubmVjdCgpLCB0aGlzLm9ic2VydmVyID0gbnVsbCB9IGluaXRTdGF0ZU9ic2VydmVyKGUpIHsgdGhpcy5vYnNlcnZlciA9IG5ldyBNdXRhdGlvbk9ic2VydmVyKCgoKSA9PiB7ICgoZSwgdCkgPT4geyBjb25zdCBuID0gW10sIG8gPSBlID0+IHsgY29uc3QgdCA9IFtdLCBuID0gW2UuZWxlbWVudC5nZXRBdHRyaWJ1dGUoIm9wZW4iKSA/IDEgOiAwLCB0XTsgcmV0dXJuIGUuc3Vibm9kZXMuZm9yRWFjaCgoZSA9PiB7IHQucHVzaChvKGUpKSB9KSksIG4gfTsgdC5mb3JFYWNoKChlID0+IHsgbi5wdXNoKG8oZSkpIH0pKSwgc2Vzc2lvblN0b3JhZ2Uuc2V0SXRlbShOKGUpLCBKU09OLnN0cmluZ2lmeShuKSkgfSkoZSwgdGhpcy5wYXJzZVRyZWUodGhpcy5yb290KSkgfSkpLCB0aGlzLm9ic2VydmVyLm9ic2VydmUodGhpcy5yb290LCB7IGNoaWxkTGlzdDogITAsIGF0dHJpYnV0ZXM6ICEwLCBzdWJ0cmVlOiAhMCB9KSB9IGRlZmluZUVsZW1lbnRzKCkgeyB0cnkgeyBjdXN0b21FbGVtZW50cy5kZWZpbmUodS5UQUdfTkFNRSwgdSkgfSBjYXRjaCAoZSkgeyB9IH0gcmVuZGVyKHsgbm9kZXM6IGUsIGVsZW1lbnQ6IHQgfSwgbiA9IDApIHsgbisrLCBlLmZvckVhY2goKGUgPT4geyBjb25zdCBvID0gdS5jcmVhdGUoeyBpY29uczogdGhpcy5pY29ucywgc3R5bGVzOiB0aGlzLnN0eWxlcywgcGFyZW50OiB0LCByZW5kZXJMYWJlbDogdGhpcy5yZW5kZXJMYWJlbCwgZGF0YTogZS5kYXRhLCBvbkNsaWNrOiB0aGlzLm9uQ2xpY2ssIGRyYWdnYWJsZTogIXRoaXMuZGlzYWJsZVNvcnRpbmcgfSk7IHRoaXMuZGlzYWJsZVNvcnRpbmcgfHwgKChlLCB0KSA9PiB7IGNvbnN0IG4gPSB7IGRyYWdzdGFydDogRSwgZHJvcDogXywgZHJhZ292ZXI6IHksIGRyYWdlbmQ6IFQsIGRyYWdsZWF2ZTogQSB9OyBmb3IgKGNvbnN0IFtvLCByXSBvZiBPYmplY3QuZW50cmllcyhuKSkgcCh7IG5vZGU6IGUsIGV2ZW50TmFtZTogbywgaGFuZGxlcjogciwgdHJlZTogdCB9KSB9KShvLCB0aGlzKSwgby5jb2xsYXBzZShuID4gdGhpcy5pbml0Q29sbGFwc2VMZXZlbCksIHRoaXMubm9kZUNvbGxlY3Rpb25bby5pZF0gPSBvLCBlLm5vZGVzICYmIHRoaXMucmVuZGVyKHsgbm9kZXM6IGUubm9kZXMsIGVsZW1lbnQ6IG8uc3Vibm9kZXMgfSwgbikgfSkpIH0gcGFyc2VUcmVlKGUpIHsgY29uc3QgdCA9IEFycmF5LmZyb20oZS5xdWVyeVNlbGVjdG9yQWxsKGA6c2NvcGUgPiAke3UuVEFHX05BTUV9YCkpLCBuID0gW107IHJldHVybiB0LmZvckVhY2goKGUgPT4geyBuLnB1c2goeyBlbGVtZW50OiBlLCBpZDogZS5pZCwgc3Vibm9kZXM6IHRoaXMucGFyc2VUcmVlKGUuc3Vibm9kZXMpIH0pIH0pKSwgbiB9IH07IHJldHVybiB0ID0gdC5kZWZhdWx0IH0pKCkpKTs=",mtime:1770926754872},"assets/icons/navigation-2.svg":{data:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIgogICAgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIgogICAgY2xhc3M9ImZlYXRoZXIgZmVhdGhlci1uYXZpZ2F0aW9uLTIiPgogICAgPHBvbHlnb24gcG9pbnRzPSIxMiAyIDE5IDIxIDEyIDE3IDUgMjEgMTIgMiI+PC9wb2x5Z29uPgo8L3N2Zz4=",mtime:1770926754873},"assets/icons/eye-off.svg":{data:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNy45NCAxNy45NEExMC4wNyAxMC4wNyAwIDAgMSAxMiAyMGMtNyAwLTExLTgtMTEtOGExOC40NSAxOC40NSAwIDAgMSA1LjA2LTUuOTRNOS45IDQuMjRBOS4xMiA5LjEyIDAgMCAxIDEyIDRjNyAwIDExIDggMTEgOGExOC41IDE4LjUgMCAwIDEtMi4xNiAzLjE5bS02LjcyLTEuMDdhMyAzIDAgMSAxLTQuMjQtNC4yNCI+PC9wYXRoPjxsaW5lIHgxPSIxIiB5MT0iMSIgeDI9IjIzIiB5Mj0iMjMiPjwvbGluZT48L3N2Zz4K",mtime:1770926754873},"assets/icons/x-circle.svg":{data:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXIteC1jaXJjbGUiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIj48L2NpcmNsZT48bGluZSB4MT0iMTUiIHkxPSI5IiB4Mj0iOSIgeTI9IjE1Ij48L2xpbmU+PGxpbmUgeDE9IjkiIHkxPSI5IiB4Mj0iMTUiIHkyPSIxNSI+PC9saW5lPjwvc3ZnPg==",mtime:1770926754873},"assets/icons/eye.svg":{data:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xIDEyczQtOCAxMS04IDExIDggMTEgOC00IDgtMTEgOC0xMS04LTExLTh6Ij48L3BhdGg+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMyI+PC9jaXJjbGU+PC9zdmc+Cg==",mtime:1770926754873},"assets/icons/folder-minus.svg":{data:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItZm9sZGVyLW1pbnVzIj48cGF0aCBkPSJNMjIgMTlhMiAyIDAgMCAxLTIgMkg0YTIgMiAwIDAgMS0yLTJWNWEyIDIgMCAwIDEgMi0yaDVsMiAzaDlhMiAyIDAgMCAxIDIgMnoiPjwvcGF0aD48bGluZSB4MT0iOSIgeTE9IjE0IiB4Mj0iMTUiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==",mtime:1770926754873},"assets/icons/folder-plus.svg":{data:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItZm9sZGVyLXBsdXMiPjxwYXRoIGQ9Ik0yMiAxOWEyIDIgMCAwIDEtMiAySDRhMiAyIDAgMCAxLTItMlY1YTIgMiAwIDAgMSAyLTJoNWwyIDNoOWEyIDIgMCAwIDEgMiAyeiI+PC9wYXRoPjxsaW5lIHgxPSIxMiIgeTE9IjExIiB4Mj0iMTIiIHkyPSIxNyI+PC9saW5lPjxsaW5lIHgxPSI5IiB5MT0iMTQiIHgyPSIxNSIgeTI9IjE0Ij48L2xpbmU+PC9zdmc+",mtime:1770926754873},"assets/icons/refresh-cw.svg":{data:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItcmVmcmVzaC1jdyI+PHBvbHlsaW5lIHBvaW50cz0iMjMgNCAyMyAxMCAxNyAxMCI+PC9wb2x5bGluZT48cG9seWxpbmUgcG9pbnRzPSIxIDIwIDEgMTQgNyAxNCI+PC9wb2x5bGluZT48cGF0aCBkPSJNMy41MSA5YTkgOSAwIDAgMSAxNC44NS0zLjM2TDIzIDEwTTEgMTRsNC42NCA0LjM2QTkgOSAwIDAgMCAyMC40OSAxNSI+PC9wYXRoPjwvc3ZnPg==",mtime:1770926754873},"assets/treeview.js":{data:"data:application/javascript;base64,LyoqCiAqIEB0eXBlZGVmIHtpbXBvcnQoIi4uL2FwaS50cyIpLlRyZWVOb2RlfSBUcmVlTm9kZQogKi8KCi8qKgogKiBAdHlwZWRlZiBTb3J0YWJsZVRyZWVOb2RlCiAqIEB0eXBlIHtPYmplY3R9CiAqIEBwcm9wZXJ0eSB7VHJlZU5vZGVbImRhdGEiXX0gZGF0YQogKi8KCi8qKgogKgogKiBAdHlwZWRlZiBUcmVlVmlld0NvbmZpZwogKiBAdHlwZSB7T2JqZWN0fQogKiBAcHJvcGVydHkge3N0cmluZ30gY3VycmVudFBhZ2UgLSB0aGUgY3VycmVudCBwYWdlIHNob3duIGluIFNpbHZlckJ1bGxldC4KICogQHByb3BlcnR5IHtUcmVlTm9kZVtdfSBub2RlcyAtIGEgdHJlZSBvZiBhbGwgcGFnZXMgaW4gdGhlIGN1cnJlbnQgc3BhY2UuCiAqIEBwcm9wZXJ0eSB7T2JqZWN0fSBkcmFnQW5kRHJvcCAtIGRyYWcgYW5kIGRyb3AgcmVsYXRlZCBjb25maWcKICogQHByb3BlcnR5IHtib29sZWFufSBkcmFnQW5kRHJvcC5lbmFibGVkIC0gdHJ1ZSBpZiBkcmFnIGFuZCBkcm9wIGlzIGVuYWJsZWQKICogQHByb3BlcnR5IHtib29sZWFufSBkcmFnQW5kRHJvcC5jb25maXJtT25SZW5hbWUgLSB0cnVlIGlmIGEgY29uZmlybWF0aW9uIHNob3VsZCBiZSBzaG93bgogKiAgd2hlbiBhIG5vZGUgaXMgZHJhZ2dlZCBhbmQgZHJvcHBlZC4KKi8KCgpjb25zdCBUUkVFX1NUQVRFX0lEID0gInRyZWV2aWV3IjsKCi8qKgogKiBJbml0aWFsaXplcyB0aGUgVHJlZVZpZXcncyBgU29ydGFibGVUcmVlYCBpbnN0YW5jZS4KICogQHBhcmFtIHtUcmVlVmlld0NvbmZpZ30gY29uZmlnCiAqIEByZXR1cm5zIHtTb3J0YWJsZVRyZWV9CiAqLwpmdW5jdGlvbiBjcmVhdGVUcmVlVmlldyhjb25maWcpIHsKICByZXR1cm4gbmV3IFNvcnRhYmxlVHJlZSh7CiAgICBub2RlczogY29uZmlnLm5vZGVzLAogICAgZGlzYWJsZVNvcnRpbmc6ICFjb25maWcuZHJhZ0FuZERyb3AuZW5hYmxlZCwKICAgIGVsZW1lbnQ6IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGNvbmZpZy50cmVlRWxlbWVudElkKSwKICAgIHN0YXRlSWQ6IFRSRUVfU1RBVEVfSUQsCiAgICBpbml0Q29sbGFwc2VMZXZlbDogMCwKICAgIGxvY2tSb290TGV2ZWw6IGZhbHNlLAoKICAgIC8qKgogICAgICogQHBhcmFtIHtTb3J0YWJsZVRyZWVOb2RlfSBtb3ZlZE5vZGUKICAgICAqIEBwYXJhbSB7U29ydGFibGVUcmVlTm9kZX0gdGFyZ2V0UGFyZW50Tm9kZQogICAgICovCiAgICBjb25maXJtOiBhc3luYyAobW92ZWROb2RlLCB0YXJnZXRQYXJlbnROb2RlKSA9PiB7CiAgICAgIGNvbnN0IG9sZFByZWZpeCA9IG1vdmVkTm9kZS5kYXRhLm5hbWU7CiAgICAgIGNvbnN0IG5ld1ByZWZpeCA9IHRhcmdldFBhcmVudE5vZGUgPyBgJHt0YXJnZXRQYXJlbnROb2RlLmRhdGEubmFtZX0vJHttb3ZlZE5vZGUuZGF0YS50aXRsZX1gIDogbW92ZWROb2RlLmRhdGEudGl0bGU7CgogICAgICBpZiAob2xkUHJlZml4ID09PSBuZXdQcmVmaXgpIHsKICAgICAgICByZXR1cm47CiAgICAgIH0KCiAgICAgIGNvbnN0IHN1Y2Nlc3MgPSBhd2FpdCBzeXNjYWxsKCJzeXN0ZW0uaW52b2tlRnVuY3Rpb24iLCAiaW5kZXgucmVuYW1lUHJlZml4Q29tbWFuZCIsIHsKICAgICAgICBvbGRQcmVmaXgsCiAgICAgICAgbmV3UHJlZml4LAogICAgICAgIGRpc2FibGVDb25maXJtYXRpb246ICFjb25maWcuZHJhZ0FuZERyb3AuY29uZmlybU9uUmVuYW1lLAogICAgICB9KTsKCiAgICAgIGlmIChzdWNjZXNzICYmIGNvbmZpZy5jdXJyZW50UGFnZS5pbmRleE9mKG9sZFByZWZpeCkgPT09IDApIHsKICAgICAgICAvLyBJZiB0aGlzIHJlbmFtZWQgdGhlIGN1cnJlbnQgcGFnZSwgbmF2aWdhdGUgdG8gaXQgYXQgaXQncyB1cGRhdGVkIG5hbWUuCiAgICAgICAgYXdhaXQgc3lzY2FsbCgiZWRpdG9yLm5hdmlnYXRlIiwgY29uZmlnLmN1cnJlbnRQYWdlLnJlcGxhY2Uob2xkUHJlZml4LCBuZXdQcmVmaXgpLCBmYWxzZSwgZmFsc2UpOwogICAgICB9CgogICAgICByZXR1cm4gc3VjY2VzczsKICAgIH0sCgogICAgb25DaGFuZ2U6IGFzeW5jICgpID0+IHsKICAgICAgYXdhaXQgc3lzY2FsbCgic3lzdGVtLmludm9rZUZ1bmN0aW9uIiwgInRyZWV2aWV3LnNob3ciKTsKICAgIH0sCgogICAgLyoqCiAgICAgKiBAcGFyYW0ge1NvcnRhYmxlVHJlZU5vZGV9IG5vZGUKICAgICAqLwogICAgb25DbGljazogYXN5bmMgKF9ldmVudCwgbm9kZSkgPT4gewogICAgICBpZiAobm9kZS5kYXRhLm5vZGVUeXBlID09PSAiYXR0YWNobWVudCIpIHsKICAgICAgICAvLyBPcGVuIGF0dGFjaG1lbnQgaW4gbmV3IHRhYgogICAgICAgIHdpbmRvdy5vcGVuKGAvJHtub2RlLmRhdGEubmFtZX1gLCAiX2JsYW5rIik7CiAgICAgIH0gZWxzZSB7CiAgICAgICAgYXdhaXQgc3lzY2FsbCgiZWRpdG9yLm5hdmlnYXRlIiwgbm9kZS5kYXRhLm5hbWUsIGZhbHNlLCBmYWxzZSk7CiAgICAgIH0KICAgIH0sCgogICAgLyoqCiAgICAgKiBAcGFyYW0ge1NvcnRhYmxlVHJlZU5vZGVbImRhdGEiXX0gZGF0YQogICAgICogQHJldHVybnMge3N0cmluZ30KICAgICAqLwogICAgcmVuZGVyTGFiZWw6IChkYXRhKSA9PiBgCiAgICAgIDxzcGFuCiAgICAgICAgZGF0YS1jdXJyZW50LXBhZ2U9IiR7SlNPTi5zdHJpbmdpZnkoZGF0YS5pc0N1cnJlbnRQYWdlIHx8IGZhbHNlKX0iCiAgICAgICAgZGF0YS1ub2RlLXR5cGU9IiR7ZGF0YS5ub2RlVHlwZX0iCiAgICAgICAgZGF0YS1wZXJtaXNzaW9uPSIke2RhdGEucGVybX0iCiAgICAgICAgdGl0bGU9IiR7ZGF0YS5uYW1lfSIgPgogICAgICAgICR7ZGF0YS50aXRsZX0KICAgICAgPC9zcGFuPmAKICAgICwKICB9KTsKfQoKLyoqCiAqIEluaXRpYWxpemVzIHRoZSB0cmVlIHZpZXcgYW5kIGl0J3MgYWN0aW9uIGJhci4KICogQHBhcmFtIHtUcmVlVmlld0NvbmZpZ30gY29uZmlnCiAqLwovLyBkZW5vLWxpbnQtaWdub3JlIG5vLXVudXNlZC12YXJzCmZ1bmN0aW9uIGluaXRpYWxpemVUcmVlVmlld1BhbmVsKGNvbmZpZykgewogIGNvbnN0IHRyZWUgPSBjcmVhdGVUcmVlVmlldyhjb25maWcpOwogIGNvbnN0IGhhbmRsZUFjdGlvbiA9IChhY3Rpb24pID0+IHsKICAgIHN3aXRjaCAoYWN0aW9uKSB7CiAgICAgIGNhc2UgImNvbGxhcHNlLWFsbCI6IHsKICAgICAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCJzb3J0YWJsZS10cmVlLW5vZGVbb3Blbj0ndHJ1ZSddIikuZm9yRWFjaCgobm9kZSkgPT4gbm9kZS5jb2xsYXBzZSh0cnVlKSk7CiAgICAgICAgcmV0dXJuIHRydWU7CiAgICAgIH0KICAgICAgY2FzZSAiZXhwYW5kLWFsbCI6IHsKICAgICAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCJzb3J0YWJsZS10cmVlLW5vZGU6bm90KFtvcGVuPSd0cnVlJ10pIikuZm9yRWFjaCgobm9kZSkgPT4gbm9kZS5jb2xsYXBzZShmYWxzZSkpOwogICAgICAgIHJldHVybiB0cnVlOwogICAgICB9CiAgICAgIGNhc2UgImNsb3NlLXBhbmVsIjogewogICAgICAgIHN5c2NhbGwoInN5c3RlbS5pbnZva2VGdW5jdGlvbiIsICJ0cmVldmlldy5oaWRlIik7CiAgICAgICAgcmV0dXJuIHRydWU7CiAgICAgIH0KICAgICAgY2FzZSAicmVmcmVzaCI6IHsKICAgICAgICBzeXNjYWxsKCJzeXN0ZW0uaW52b2tlRnVuY3Rpb24iLCAidHJlZXZpZXcuc2hvdyIpOwogICAgICAgIHJldHVybiB0cnVlOwogICAgICB9CiAgICAgIGNhc2UgInJldmVhbC1jdXJyZW50LXBhZ2UiOiB7CiAgICAgICAgY29uc3QgY3VycmVudE5vZGUgPSB0cmVlLmZpbmROb2RlKCJpc0N1cnJlbnRQYWdlIiwgdHJ1ZSk7CiAgICAgICAgaWYgKGN1cnJlbnROb2RlKSB7CiAgICAgICAgICBjdXJyZW50Tm9kZS5yZXZlYWwoKTsKICAgICAgICAgIGN1cnJlbnROb2RlLnNjcm9sbEludG9WaWV3KHsKICAgICAgICAgICAgYmVoYXZpb3I6ICJhdXRvIiwKICAgICAgICAgICAgYmxvY2s6ICJuZWFyZXN0IiwKICAgICAgICAgICAgaW5saW5lOiAibmVhcmVzdCIsCiAgICAgICAgICB9KTsKICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgIH0KICAgICAgY2FzZSAiaW5jcmVhc2Utd2lkdGgiOiB7CiAgICAgICAgc3lzY2FsbCgic3lzdGVtLmludm9rZUZ1bmN0aW9uIiwgInRyZWV2aWV3LmluY3JlYXNlV2lkdGgiKTsKICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfQogICAgICBjYXNlICJkZWNyZWFzZS13aWR0aCI6IHsKICAgICAgICBzeXNjYWxsKCJzeXN0ZW0uaW52b2tlRnVuY3Rpb24iLCAidHJlZXZpZXcuZGVjcmVhc2VXaWR0aCIpOwogICAgICAgIHJldHVybiB0cnVlOwogICAgICB9CiAgICAgIGNhc2UgInRvZ2dsZS1oaWRkZW4iOiB7CiAgICAgICAgc3lzY2FsbCgic3lzdGVtLmludm9rZUZ1bmN0aW9uIiwgInRyZWV2aWV3LnRvZ2dsZUhpZGRlbiIpOwogICAgICAgIHJldHVybiB0cnVlOwogICAgICB9CiAgICB9CgogICAgcmV0dXJuIGZhbHNlOwogIH0KCiAgaWYgKGNvbmZpZy5yZXZlYWxPbkxvYWQpIHsKICAgIGhhbmRsZUFjdGlvbigicmV2ZWFsLWN1cnJlbnQtcGFnZSIpOwogIH0KCiAgZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiW2RhdGEtdHJlZXZpZXctYWN0aW9uXSIpLmZvckVhY2goKGVsKSA9PiB7CiAgICBlbC5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsIChlKSA9PiB7CiAgICAgIGlmIChoYW5kbGVBY3Rpb24oZWwuZGF0YXNldFsidHJlZXZpZXdBY3Rpb24iXSkpIHsKICAgICAgICBlLnN0b3BQcm9wYWdhdGlvbigpOwogICAgICAgIGUucHJldmVudERlZmF1bHQoKTsKICAgICAgfQogICAgfSk7CiAgfSkKfQo=",mtime:1770926754873}},functions:{hide:{path:"./treeview.ts:hideTree"},show:{path:"./treeview.ts:showTree"},showIfEnabled:{path:"./treeview.ts:showTreeIfEnabled",events:["editor:init","editor:pageLoaded","editor:pageSaved","file:deleted"]},toggle:{path:"./treeview.ts:toggleTree",command:{name:"Tree View: Toggle",key:"Ctrl-alt-b",mac:"Cmd-alt-b"}},reveal:{path:"./treeview.ts:showTree",command:{name:"Tree View: Reveal Current Page"}},deletePage:{path:"./treeview.ts:deletePage",command:{name:"Page: Delete (shortcut)",key:"Ctrl-Shift-d",mac:"Cmd-Shift-d"}},increaseWidth:{path:"./treeview.ts:increaseWidth",command:{name:"Tree View: Increase Width"}},decreaseWidth:{path:"./treeview.ts:decreaseWidth",command:{name:"Tree View: Decrease Width"}},toggleHidden:{path:"./treeview.ts:toggleHidden",command:{name:"Tree View: Toggle Hidden Files"}}}},No={manifest:Wt,functionMapping:Kt};tt(Kt,Wt,self.postMessage);export{No as plug};
diff --git a/configs/notes/space/inbox.md b/configs/notes/space/inbox.md
new file mode 100644
index 0000000..16341df
--- /dev/null
+++ b/configs/notes/space/inbox.md
@@ -0,0 +1,13 @@
+# Inbox
+
+Apple **Quick Note** analogue. Dump here first; triage into [[TODO]] (Now / Next / Waiting / Someday) or a dedicated page.
+
+Rules:
+
+- Empty capture OK — fill the next `- [ ]` line and save.
+- Do not create a new folder just to capture.
+- After triage, clear or check off lines so Inbox stays short.
+
+## Capture
+
+- [ ]
diff --git a/configs/notes/space/index.md b/configs/notes/space/index.md
new file mode 100644
index 0000000..bfa625c
--- /dev/null
+++ b/configs/notes/space/index.md
@@ -0,0 +1,46 @@
+---
+# SilverBullet index page (SB_INDEX_PAGE=index)
+# Apple-Notes-like home: pins + capture path + search
+---
+
+# Notes
+
+Cross-host notes (magikoopa + clementine) via koopa SilverBullet.
+Mental model = Apple Notes: **folders/pages · list · editor**, pin primary TODO, Quick Note → triage.
+
+| Page | Apple analogue | Role |
+|------|----------------|------|
+| [[TODO]] | Pinned primary note | ADHD SoT: Now≤3 / Next / Waiting / Someday / Stats |
+| [[inbox]] | Quick Note | Capture before triage |
+
+## Pins
+
+- [[TODO]]
+- [[inbox]]
+
+## How to use (Apple habits → here)
+
+1. **Capture** into [[inbox]] (or SB quick-note if enabled) — do not invent folders first.
+2. **Triage** into [[TODO]] sections (Now max 3). Same rules as former Apple Notes guide.
+3. **Find** with Page Picker / search (**Cmd/Ctrl-K**).
+4. **New page** when a topic is not a todo (meetings, dumps) — link from TODO by title, do not dump transcripts into TODO.
+5. Optional later: TreeView plug (folder sidebar), Space Lua query page = “smart folder” for `#tag` open tasks.
+
+## Shortcuts (target)
+
+| Action | Typical SB |
+|--------|------------|
+| Open page / filter | Cmd/Ctrl-K (Page Picker) |
+| Quick note | Ctrl-q (if client supports) → prefer landing in [[inbox]] |
+| Search body | built-in search / SilverSearch plug |
+
+## Hygiene (from Apple Notes ADHD pack)
+
+- One primary [[TODO]] — no competing systems.
+- Capture first; tags (`#taler` …) optional and light.
+- On “I’m back”: re-read **Now** only.
+- No passwords/tokens in notes.
+
+## Apps
+
+- [[PLUGS]] — TreeView + SilverSearch
diff --git a/configs/paivana/README.md b/configs/paivana/README.md
index 643fdb4..8cac2f0 100644
--- a/configs/paivana/README.md
+++ b/configs/paivana/README.md
@@ -2,27 +2,32 @@
GNU Taler **paivana-httpd** reverse-proxy paywall (DD 95 / DD 76 style), GOA payments.
+One process = one `BASE_URL` + one `DESTINATION_BASE_URL` + one `MERCHANT_BACKEND_URL`.
+Shared **image / secrets / entrypoint / conf template**; per-service env sets
+`PAIVANA_BASE_URL` + `PAIVANA_DESTINATION_BASE_URL`. Portal on **9025**; app
+paywalls on **9028–9031** (Caddy unchanged). App backends listen loopback only
+(`19128` SilverBullet, `19129` Galene HTTP, `19130` CryptPad edge, `19131` Meet).
+
| Item | Value |
|------|--------|
| Live | `/home/hernani/koopa-paivana/` |
-| Containers | `koopa-paivana`, `koopa-paivana-upstream` |
+| Containers | `koopa-paivana`, `koopa-paivana-{silverbullet,galene,cryptpad,meet}`, `koopa-paivana-upstream` |
| Image | `localhost/koopa-paivana:latest` (built from `Containerfile`); process **uid 1000** `paivana-httpd` |
-| Upstream | `nginxinc/nginx-unprivileged` on **:8080** (not root nginx:80) |
-| Host port | **9025** → Caddy `paivana.hacktivism.ch` |
-| Currency | **GOA** |
+| Upstream | portal: `nginxinc/nginx-unprivileged` on **:8080**; apps via host `172.17.0.1:19128–19131` |
+| Host ports | **9025** portal; **9028/9029/9030/9031** app paywalls → Caddy |
+| Currency | **GOA** (goa-shop); other units need other merchant backends / later instances |
| Merchant | `https://taler.hacktivism.ch/instances/goa-shop/` |
-| Template | `paivana` (`template_type: paivana`, amount **`GOA:4200`**, `website_regex: .*`) |
-| Access | **`-g`** site-wide: pay once → example upstream free to browse |
-| Upstream | example site in `upstream/` (compose service) |
+| Template | `paivana` (`template_type: paivana`, amount **`GOA:42`**, `website_regex: .*`) |
+| Access | **`-g`** site-wide: pay once → that hostname free to browse |
## Layout
| Path | Role |
|------|------|
| `Containerfile` | Multi-stage: build paivana-httpd from `git.taler.net/paivana` + Taler libs |
-| `compose.yml` | `koopa-paivana` + nginx upstream |
-| `conf/paivana.conf.template` | Config; secrets substituted at start |
-| `entrypoint.sh` | Inject secrets → run `paivana-httpd -g -f` |
+| `compose.yml` | portal + 4 app paywalls + nginx upstream (YAML anchor `x-paivana-common`) |
+| `conf/paivana.conf.template` | Shared conf; `@BASE_URL@` / `@DESTINATION_BASE_URL@` / secrets at start |
+| `entrypoint.sh` | Env + secrets → run `paivana-httpd -g -f` |
| `secrets/` | **not in git** — live only |
| `upstream/` | Protected demo content |
| `container-koopa-paivana.service` | systemd --user oneshot compose up |
@@ -90,9 +95,10 @@ extra_hosts:
| Check | Expect |
|-------|--------|
| `curl -si http://127.0.0.1:9025/` | **302** → `/.well-known/paivana/templates/paivana#…` |
-| `https://paivana.hacktivism.ch/` | **402** paywall HTML + `paivana: taler://pay-template/…` |
+| `curl -si http://127.0.0.1:902{8,9,0}/` | **302** same template path (per-host BASE_URL) |
+| `https://{paivana,silverbullet,galene,cryptpad,meet}.hacktivism.ch/` | unpaid → **302** `…/templates/paivana#…` (or paywall HTML) |
| monitoring `urls` | `www.paivana` OK |
-| e2e (GOA, lokal) | `e2e.paivana` Template-Pay **GOA:4200** (wenn `E2E_PAIVANA=1`) |
+| e2e (GOA, lokal) | `e2e.paivana` Template-Pay **GOA:42** (wenn `E2E_PAIVANA=1`) |
---
@@ -137,22 +143,27 @@ Flags `-g` (site-wide payment) and `-f` (X-Forwarded-For).
## Merchant template
+Create (or replace via PATCH / DELETE+POST — see live merchant API):
+
```bash
curl -X POST 'https://taler.hacktivism.ch/instances/goa-shop/private/templates' \
-H "Authorization: Bearer secret-token:…" \
-H 'Content-Type: application/json' \
-d '{
"template_id": "paivana",
- "template_description": "Paivana paywall (GOA)",
+ "template_description": "Paivana paywall portal (GOA)",
"template_contract": {
"template_type": "paivana",
- "summary": "Access to paivana.hacktivism.ch",
+ "summary": "Access to paivana.hacktivism.ch portal",
"website_regex": ".*",
- "choices": [{ "amount": "GOA:4200", "description": "Unlock example site (then free to browse)" }]
+ "choices": [{ "amount": "GOA:42", "description": "Unlock portal (cryptpad / galene / silverbullet links)" }]
}
}'
```
+goa-shop is GOA-only. TESTPAYSAN / FLOODS / talersticker CHF / stage-CHF need
+other `MERCHANT_BACKEND_URL` values (separate Paivana process or later phase).
+
## Docs
- https://docs.taler.net/taler-paivana-manual.html
diff --git a/configs/paivana/compose.yml b/configs/paivana/compose.yml
index 221ec41..688c10f 100644
--- a/configs/paivana/compose.yml
+++ b/configs/paivana/compose.yml
@@ -1,29 +1,45 @@
-# koopa-paivana — host :9025 → Caddy paivana.hacktivism.ch
+# koopa-paivana — shared image/secrets/entrypoint for portal + app paywalls
# Live: /home/hernani/koopa-paivana/
+#
+# Public Caddy targets (unchanged):
+# 9025 → portal (paivana.hacktivism.ch)
+# 9028 → paywall silverbullet
+# 9029 → paywall galene
+# 9030 → paywall cryptpad
+# 9031 → paywall meet (Jitsi web)
+# App backends (loopback only; see app compose):
+# 127.0.0.1:19128 / 19129 / 19130 / 19131
+
+x-paivana-common: &paivana-common
+ image: localhost/koopa-paivana:latest
+ restart: unless-stopped
+ user: "1000:1000"
+ userns_mode: keep-id
+ extra_hosts:
+ - "taler.hacktivism.ch:172.17.0.1"
+ - "bank.hacktivism.ch:172.17.0.1"
+ - "exchange.hacktivism.ch:172.17.0.1"
+ volumes:
+ - ./conf/paivana.conf.template:/etc/paivana/paivana.conf.template:ro
+ - ./entrypoint.sh:/usr/local/bin/paivana-entrypoint:ro
+ - ./secrets/merchant-access-token:/run/secrets/merchant-access-token:ro
+ - ./secrets/paivana-secret:/run/secrets/paivana-secret:ro
+ labels:
+ org.hacktivism.managed_by: koopa-admin
+ org.hacktivism.currency: GOA
+
services:
paivana:
+ <<: *paivana-common
build:
context: .
dockerfile: Containerfile
- image: localhost/koopa-paivana:latest
container_name: koopa-paivana
- restart: unless-stopped
- # Image already USER paivana-httpd; pin numeric + keep-id for host secrets/volumes.
- user: "1000:1000"
- userns_mode: keep-id
ports:
- "9025:9967"
- extra_hosts:
- # pasta publishes merchant on host :9010. Prefer fixed IPv4 — host-gateway
- # alone still loses to public AAAA and times out from the container.
- - "taler.hacktivism.ch:172.17.0.1"
- - "bank.hacktivism.ch:172.17.0.1"
- - "exchange.hacktivism.ch:172.17.0.1"
- volumes:
- - ./conf/paivana.conf.template:/etc/paivana/paivana.conf.template:ro
- - ./entrypoint.sh:/usr/local/bin/paivana-entrypoint:ro
- - ./secrets/merchant-access-token:/run/secrets/merchant-access-token:ro
- - ./secrets/paivana-secret:/run/secrets/paivana-secret:ro
+ environment:
+ PAIVANA_BASE_URL: https://paivana.hacktivism.ch/
+ PAIVANA_DESTINATION_BASE_URL: http://upstream:8080/
depends_on:
- upstream
labels:
@@ -33,8 +49,68 @@ services:
org.hacktivism.managed_by: koopa-admin
org.hacktivism.currency: GOA
+ # App paywalls: same image/secrets; BASE/DEST via env. Caddy still hits 9028–9031.
+ paivana-silverbullet:
+ <<: *paivana-common
+ container_name: koopa-paivana-silverbullet
+ ports:
+ - "9028:9967"
+ environment:
+ PAIVANA_BASE_URL: https://silverbullet.hacktivism.ch/
+ PAIVANA_DESTINATION_BASE_URL: http://172.17.0.1:19128/
+ labels:
+ org.hacktivism.service: paivana-silverbullet
+ org.hacktivism.host_port: "9028"
+ org.hacktivism.site: silverbullet.hacktivism.ch
+ org.hacktivism.managed_by: koopa-admin
+ org.hacktivism.currency: GOA
+
+ paivana-galene:
+ <<: *paivana-common
+ container_name: koopa-paivana-galene
+ ports:
+ - "9029:9967"
+ environment:
+ PAIVANA_BASE_URL: https://galene.hacktivism.ch/
+ PAIVANA_DESTINATION_BASE_URL: http://172.17.0.1:19129/
+ labels:
+ org.hacktivism.service: paivana-galene
+ org.hacktivism.host_port: "9029"
+ org.hacktivism.site: galene.hacktivism.ch
+ org.hacktivism.managed_by: koopa-admin
+ org.hacktivism.currency: GOA
+
+ paivana-cryptpad:
+ <<: *paivana-common
+ container_name: koopa-paivana-cryptpad
+ ports:
+ - "9030:9967"
+ environment:
+ PAIVANA_BASE_URL: https://cryptpad.hacktivism.ch/
+ PAIVANA_DESTINATION_BASE_URL: http://172.17.0.1:19130/
+ labels:
+ org.hacktivism.service: paivana-cryptpad
+ org.hacktivism.host_port: "9030"
+ org.hacktivism.site: cryptpad.hacktivism.ch
+ org.hacktivism.managed_by: koopa-admin
+ org.hacktivism.currency: GOA
+
+ paivana-meet:
+ <<: *paivana-common
+ container_name: koopa-paivana-meet
+ ports:
+ - "9031:9967"
+ environment:
+ PAIVANA_BASE_URL: https://meet.hacktivism.ch/
+ PAIVANA_DESTINATION_BASE_URL: http://172.17.0.1:19131/
+ labels:
+ org.hacktivism.service: paivana-meet
+ org.hacktivism.host_port: "9031"
+ org.hacktivism.site: meet.hacktivism.ch
+ org.hacktivism.managed_by: koopa-admin
+ org.hacktivism.currency: GOA
+
upstream:
- # Non-root nginx (listens on 8080). See DESTINATION_BASE_URL in conf template.
image: docker.io/nginxinc/nginx-unprivileged:1.27-alpine
container_name: koopa-paivana-upstream
restart: unless-stopped
diff --git a/configs/paivana/conf/paivana.conf.template b/configs/paivana/conf/paivana.conf.template
index 6dec110..fe8996c 100644
--- a/configs/paivana/conf/paivana.conf.template
+++ b/configs/paivana/conf/paivana.conf.template
@@ -1,14 +1,14 @@
-# paivana.hacktivism.ch — paywall reverse proxy
-# Secrets injected by entrypoint from /run/secrets/
+# Shared paivana-httpd conf — BASE/DEST injected by entrypoint (env).
+# Secrets injected from /run/secrets/
[paivana]
SERVE = tcp
PORT = 9967
BIND_TO = 0.0.0.0
-BASE_URL = https://paivana.hacktivism.ch/
+BASE_URL = @BASE_URL@
-DESTINATION_BASE_URL = http://upstream:8080/
+DESTINATION_BASE_URL = @DESTINATION_BASE_URL@
MERCHANT_BACKEND_URL = https://taler.hacktivism.ch/instances/goa-shop/
MERCHANT_ACCESS_TOKEN = @MERCHANT_ACCESS_TOKEN@
diff --git a/configs/paivana/entrypoint.sh b/configs/paivana/entrypoint.sh
index e751b47..56461e4 100755
--- a/configs/paivana/entrypoint.sh
+++ b/configs/paivana/entrypoint.sh
@@ -5,6 +5,10 @@ SECRET_FILE="${PAIVANA_SECRET_FILE:-/run/secrets/paivana-secret}"
TEMPLATE="${PAIVANA_CONF_TEMPLATE:-/etc/paivana/paivana.conf.template}"
OUT="${PAIVANA_CONF:-/tmp/paivana.conf}"
+# Per-instance (portal + app paywalls share this entrypoint/image).
+BASE_URL="${PAIVANA_BASE_URL:-https://paivana.hacktivism.ch/}"
+DEST_URL="${PAIVANA_DESTINATION_BASE_URL:-http://upstream:8080/}"
+
if [ ! -r "$TOKEN_FILE" ]; then
echo "missing merchant token: $TOKEN_FILE" >&2
exit 1
@@ -17,10 +21,12 @@ fi
TOKEN=$(tr -d '\n\r' < "$TOKEN_FILE")
SECRET=$(tr -d '\n\r' < "$SECRET_FILE")
-awk -v t="$TOKEN" -v s="$SECRET" '
+awk -v t="$TOKEN" -v s="$SECRET" -v b="$BASE_URL" -v d="$DEST_URL" '
{
gsub(/@MERCHANT_ACCESS_TOKEN@/, t)
gsub(/@PAIVANA_SECRET@/, s)
+ gsub(/@BASE_URL@/, b)
+ gsub(/@DESTINATION_BASE_URL@/, d)
print
}
' "$TEMPLATE" > "$OUT"
diff --git a/configs/paivana/upstream/index.html b/configs/paivana/upstream/index.html
index 064d3d7..6cf7a1f 100644
--- a/configs/paivana/upstream/index.html
+++ b/configs/paivana/upstream/index.html
@@ -3,7 +3,7 @@
- Example site — free after GOA:4200
+ hacktivism tools — unlockedunlocked
-
Example site
+
hacktivism tools
- You paid GOA:4200 once via GNU Taler / Paivana.
- This whole example site is now free for you to browse (site-wide access cookie).
+ You paid once via GNU Taler / Paivana
+ (GOA:42 on this merchant).
+ Site-wide access cookie is set for paivana.hacktivism.ch.
Paywall: paivana-httpd in front of a normal upstream HTTP site
-
Currency: exploration money GOA on exchange.hacktivism.ch
-
After payment: no further charge for pages behind this proxy
+
One Paivana process = one DESTINATION_BASE_URL → this portal is the gated upstream.
+
Merchant: goa-shop · template paivana.
+
Host: https://paivana.hacktivism.ch/
-
Host: https://paivana.hacktivism.ch/
diff --git a/configs/ports.md b/configs/ports.md
index ff863de..0df00bf 100644
--- a/configs/ports.md
+++ b/configs/ports.md
@@ -16,16 +16,27 @@
| 9022 | podman `koopa-prime` (Jellyfin) → Caddy `prime.hacktivism.ch` |
| 9023 | podman qBittorrent → Caddy `bt.hacktivism.ch` |
| **9024** | podman **`koopa-forgejo`** (HTTP) → Caddy **`git.hacktivism.ch`** |
-| **9025** | podman **`koopa-paivana`** (paivana-httpd) → Caddy **`paivana.hacktivism.ch`** |
+| **9025** | podman **`koopa-paivana`** (portal paywall) → Caddy **`paivana.hacktivism.ch`** |
| **9026** | podman **`koopa-lemmy-proxy`** → Caddy **`lemmy.hacktivism.ch`** |
| **9027** | podman **`koopa-decidim`** → Caddy **`decidim.hacktivism.ch`** |
+| **9028** | podman **`koopa-paivana-silverbullet`** → Caddy **`silverbullet.hacktivism.ch`** (app backend `127.0.0.1:19128`) |
+| **9029** | podman **`koopa-paivana-galene`** → Caddy **`galene.hacktivism.ch`** (app backend `127.0.0.1:19129`; UDP 10000–10099 still Galene) |
+| **9030** | podman **`koopa-paivana-cryptpad`** → Caddy **`cryptpad.hacktivism.ch`** (edge backend `127.0.0.1:19130`) |
+| **9031** | podman **`koopa-paivana-meet`** → Caddy **`meet.hacktivism.ch`** (app backend `127.0.0.1:19131`) |
+| **9032** | podman **`koopa-bbb-front`** (Ubuntu) → Caddy **`bbb.hacktivism.ch`** |
| **9200** | podman **Forgejo git-SSH** (host-direct; not Caddy) |
| 9090–9092 | podman `koopa-tops-ng1` … `ng3` → Caddy `tops.ng{1,2,3}.hacktivism.ch` |
| 8080 | Tor ORPort |
VeciGate: WAN **80→9000**, WAN **443→9001**.
-Public apps: Caddy vhosts on **9001** → 127.0.0.1:{9010–9015, 9020–9027, 9090–9092}.
-Git SSH needs separate NAT/firewall **9200/tcp** if exposed to WAN.
+Public apps: Caddy vhosts on **9001** → 127.0.0.1:{9010–9015, 9020–9032, 9090–9092}.
+Git SSH needs separate NAT/firewall **9200/tcp** if exposed to WAN.
+Meet/Galene/BBB media UDP is separate from these HTTP ports.
+
+| UDP range | Role |
+|-----------|------|
+| **10000–10099/udp** | **Galene** WebRTC (host publish + planned VeciGate DNAT → koopa) |
+| (TBD) | Meet JVB / BBB media — allocate when Phase‑2 / full BBB leaves park |
| **1789** | podman **`koopa-nym`** mixnet (nym.com nym-node) |
| **1790** | podman **`koopa-nym`** verloc |
diff --git a/host/caddy/Caddyfile b/host/caddy/Caddyfile
index 7b960b9..70d8b08 100644
--- a/host/caddy/Caddyfile
+++ b/host/caddy/Caddyfile
@@ -1,7 +1,7 @@
# Internal only (not in the browser URL):
# 9010 merchant API | 9011 exchange API | 9012 bank API
# 9013 bank landing | 9014 exchange landing | 9015 merchant landing
-# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9200 forgejo-ssh
+# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9029 galene | 9030 cryptpad | 9031 meet | 9032 bbb | 9200 forgejo-ssh
# 9090 tops ng1 | 9091 tops ng2 | 9092 tops ng3
{
@@ -37,22 +37,85 @@ taler.hacktivism.ch {
}
}
+ # surface + aptdeploy HTML (taler.hacktivism.ch only)
+ handle /taler-monitoring-surface_err {
+ redir /taler-monitoring-surface_err/ 302
+ }
+ handle /taler-monitoring-surface_err* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+ handle /taler-monitoring-surface {
+ redir /taler-monitoring-surface/ 302
+ }
+ handle /taler-monitoring-surface* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+ handle /taler-monitoring-aptdeploy_err {
+ redir /taler-monitoring-aptdeploy_err/ 302
+ }
+ handle /taler-monitoring-aptdeploy_err* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+ handle /taler-monitoring-aptdeploy {
+ redir /taler-monitoring-aptdeploy/ 302
+ }
+ handle /taler-monitoring-aptdeploy* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+
+ handle /taler-monitoring-mattermost_err {
+ redir /taler-monitoring-mattermost_err/ 302
+ }
+ handle /taler-monitoring-mattermost_err* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+ handle /taler-monitoring-mattermost {
+ redir /taler-monitoring-mattermost/ 302
+ }
+ handle /taler-monitoring-mattermost* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+
+ handle /taler-monitoring-mail_err {
+ redir /taler-monitoring-mail_err/ 302
+ }
+ handle /taler-monitoring-mail_err* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+ handle /taler-monitoring-mail {
+ redir /taler-monitoring-mail/ 302
+ }
+ handle /taler-monitoring-mail* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
+ file_server
+ }
+
# taler-monitoring console HTML (host static — not in app containers)
handle /monitoring_err {
redir /monitoring_err/ 302
}
- handle /monitoring_err/ {
- root * /var/www/monitoring-sites/{host}/monitoring_err
+ handle /monitoring_err* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
file_server
}
handle /monitoring {
redir /monitoring/ 302
}
- handle /monitoring/ {
- root * /var/www/monitoring-sites/{host}/monitoring
+ handle /monitoring* {
+ root * /var/www/monitoring-sites/taler.hacktivism.ch
file_server
}
+
+
+
# SPA: /webui → /webui/
redir /webui /webui/ 302
@@ -82,18 +145,19 @@ exchange.hacktivism.ch {
handle /monitoring_err {
redir /monitoring_err/ 302
}
- handle /monitoring_err/ {
- root * /var/www/monitoring-sites/{host}/monitoring_err
+ handle /monitoring_err* {
+ root * /var/www/monitoring-sites/exchange.hacktivism.ch
file_server
}
handle /monitoring {
redir /monitoring/ 302
}
- handle /monitoring/ {
- root * /var/www/monitoring-sites/{host}/monitoring
+ handle /monitoring* {
+ root * /var/www/monitoring-sites/exchange.hacktivism.ch
file_server
}
+
reverse_proxy 127.0.0.1:9011 {
import proxy_public
}
@@ -115,18 +179,19 @@ bank.hacktivism.ch {
handle /monitoring_err {
redir /monitoring_err/ 302
}
- handle /monitoring_err/ {
- root * /var/www/monitoring-sites/{host}/monitoring_err
+ handle /monitoring_err* {
+ root * /var/www/monitoring-sites/bank.hacktivism.ch
file_server
}
handle /monitoring {
redir /monitoring/ 302
}
- handle /monitoring/ {
- root * /var/www/monitoring-sites/{host}/monitoring
+ handle /monitoring* {
+ root * /var/www/monitoring-sites/bank.hacktivism.ch
file_server
}
+
# Static terms/privacy on landing nginx :9013
handle /terms* {
reverse_proxy 127.0.0.1:9013 {
@@ -235,7 +300,114 @@ paivana.hacktivism.ch {
}
}
-http://taler.hacktivism.ch, http://exchange.hacktivism.ch, http://bank.hacktivism.ch, http://castopod.hacktivism.ch, http://bonfire.hacktivism.ch, http://prime.hacktivism.ch, http://bt.hacktivism.ch, http://git.hacktivism.ch, http://paivana.hacktivism.ch, http://tops.ng1.hacktivism.ch, http://tops.ng2.hacktivism.ch, http://tops.ng3.hacktivism.ch {
+# 9026 lemmy (+ ui/pictrs via internal nginx)
+lemmy.hacktivism.ch {
+ header Alt-Svc "clear"
+ reverse_proxy 127.0.0.1:9026 {
+ header_up Host {host}
+ header_up X-Forwarded-For {remote_host}
+ header_up X-Forwarded-Proto {scheme}
+ flush_interval -1
+ transport http {
+ read_timeout 3600s
+ write_timeout 3600s
+ }
+ }
+}
+
+# 9027 decidim (rails)
+decidim.hacktivism.ch {
+ header Alt-Svc "clear"
+ reverse_proxy 127.0.0.1:9027 {
+ header_up Host {host}
+ header_up X-Forwarded-For {remote_host}
+ header_up X-Forwarded-Host {host}
+ header_up X-Forwarded-Proto {scheme}
+ header_up X-Forwarded-Port 443
+ flush_interval -1
+ transport http {
+ read_timeout 3600s
+ write_timeout 3600s
+ }
+ }
+}
+
+# 9029 galene — allocated 2026-09-08
+# Merge: scripts/caddy/caddy-apply.sh --site galene.hacktivism.ch --port 9029 --label galene
+galene.hacktivism.ch {
+ header Alt-Svc "clear"
+ reverse_proxy 127.0.0.1:9029 {
+ header_up Host {host}
+ header_up X-Forwarded-For {remote_host}
+ header_up X-Forwarded-Host {host}
+ header_up X-Forwarded-Proto {scheme}
+ header_up X-Forwarded-Port 443
+ flush_interval -1
+ transport http {
+ read_timeout 3600s
+ write_timeout 3600s
+ }
+ }
+}
+
+# 9030 cryptpad — allocated 2026-09-08
+# Merge: scripts/caddy/caddy-apply.sh --site cryptpad.hacktivism.ch --port 9030 --label cryptpad
+# CryptPad also needs sandbox subdomain (cryptpad-ui / sandbox) — wire after first boot.
+cryptpad.hacktivism.ch {
+ header Alt-Svc "clear"
+ reverse_proxy 127.0.0.1:9030 {
+ header_up Host {host}
+ header_up X-Forwarded-For {remote_host}
+ header_up X-Forwarded-Host {host}
+ header_up X-Forwarded-Proto {scheme}
+ header_up X-Forwarded-Port 443
+ flush_interval -1
+ transport http {
+ read_timeout 3600s
+ write_timeout 3600s
+ }
+ }
+}
+
+# 9031 meet (Jitsi) — allocated 2026-09-08
+# Merge: scripts/caddy/caddy-apply.sh --site meet.hacktivism.ch --port 9031 --label meet
+# WebRTC media (UDP/JVB) is separate from this HTTPS vhost — VeciGate/firewall later.
+meet.hacktivism.ch {
+ header Alt-Svc "clear"
+ reverse_proxy 127.0.0.1:9031 {
+ header_up Host {host}
+ header_up X-Forwarded-For {remote_host}
+ header_up X-Forwarded-Host {host}
+ header_up X-Forwarded-Proto {scheme}
+ header_up X-Forwarded-Port 443
+ flush_interval -1
+ transport http {
+ read_timeout 3600s
+ write_timeout 3600s
+ }
+ }
+}
+
+# 9032 bbb (BigBlueButton) — allocated 2026-09-08
+# Merge: scripts/caddy/caddy-apply.sh --site bbb.hacktivism.ch --port 9032 --label bbb
+# Stack image base: Ubuntu (not CachyOS). Media/UDP ports TBD with install.
+bbb.hacktivism.ch {
+ header Alt-Svc "clear"
+ reverse_proxy 127.0.0.1:9032 {
+ header_up Host {host}
+ header_up X-Forwarded-For {remote_host}
+ header_up X-Forwarded-Host {host}
+ header_up X-Forwarded-Proto {scheme}
+ header_up X-Forwarded-Port 443
+ flush_interval -1
+ transport http {
+ read_timeout 3600s
+ write_timeout 3600s
+ }
+ }
+}
+
+http://taler.hacktivism.ch, http://exchange.hacktivism.ch, http://bank.hacktivism.ch, http://castopod.hacktivism.ch, http://bonfire.hacktivism.ch, http://prime.hacktivism.ch, http://bt.hacktivism.ch, http://git.hacktivism.ch, http://paivana.hacktivism.ch, http://tops.ng1.hacktivism.ch, http://tops.ng2.hacktivism.ch, http://tops.ng3.hacktivism.ch, http://lemmy.hacktivism.ch, http://decidim.hacktivism.ch, http://galene.hacktivism.ch, http://cryptpad.hacktivism.ch, http://meet.hacktivism.ch, http://bbb.hacktivism.ch {
handle /.well-known/acme-challenge/* {
root * /var/www/acme
file_server
diff --git a/scripts/bbb/build.sh b/scripts/bbb/build.sh
new file mode 100755
index 0000000..cf0a4c0
--- /dev/null
+++ b/scripts/bbb/build.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# Build BBB front stub on Ubuntu (run on koopa).
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CTX="${ROOT}/configs/bbb"
+TAG="${IMAGE_TAG:-localhost/koopa-bbb-front:ubuntu}"
+cd "$CTX"
+[[ -f Containerfile ]] || { echo "ERROR: missing $CTX/Containerfile" >&2; exit 1; }
+echo "build $TAG ctx=$CTX"
+podman build -t "$TAG" -f Containerfile .
+echo "OK: $TAG"
diff --git a/scripts/caddy/README.md b/scripts/caddy/README.md
index a9f5139..362254f 100644
--- a/scripts/caddy/README.md
+++ b/scripts/caddy/README.md
@@ -15,12 +15,52 @@ sudo systemctl reload caddy
Caddy **ignores** Apache `.htaccess`. Use:
```bash
-sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
+# clone koopa-admin-log from Forgejo when needed (no permanent ~/ clone)
+sudo bash /path/to/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
```
-Password file (not git): `/home/hernani/koopa-secrets/dossiers-ngi-basicauth.txt`
+Host runtime: `/etc/caddy/secrets/dossiers-ngi-basicauth.txt` (root-only).
+SoT: `koopa-admin-secrets-enc` (git-crypt) → `koopa/home-hernani/caddy/`.
Hosts: `dossiers.ngi` / `dossiers.2.ngi` / `dossiers.3.ngi`.hacktivism.ch
-## New HTTPS vhost
+## silverbullet.hacktivism.ch — merge + basic_auth
-Add a site block to the Caddyfile (live + mirror), then validate and reload. Include the host in the shared HTTP site list (ACME + redirect) at the bottom of the file.
+After podman cutover (`scripts/notes/cutover-to-silverbullet.sh --apply`) and DNS:
+
+```bash
+# laptop: SoT → /tmp seed (no secret echo)
+bash /path/to/koopa-admin-log/scripts/notes/prepare-basicauth-seed.sh
+scp /tmp/silverbullet-basicauth-seed.txt hernani@koopa:/tmp/
+
+# koopa root:
+sudo bash /path/to/koopa-admin-log/scripts/caddy/merge-notes-site.sh
+sudo NOTES_AUTH_SEED=/tmp/silverbullet-basicauth-seed.txt \
+ bash /path/to/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh
+```
+
+`merge-notes-site.sh` renames leftover `freenotes`/`notes` site blocks → `silverbullet.hacktivism.ch`.
+Host runtime: `/etc/caddy/secrets/silverbullet-basicauth.txt` (root-only).
+SoT: `koopa-admin-secrets-enc` (git-crypt) → `koopa/home-hernani/caddy/silverbullet-basicauth.txt`.
+
+## New HTTPS vhost — `caddy-apply.sh`
+
+Generic merge (site snippet + ACME `http://` list + ports comment):
+
+```bash
+# laptop mirror (no reload):
+bash scripts/caddy/caddy-apply.sh \
+ --config configs/caddy/Caddyfile \
+ --snippet configs/caddy/galene.hacktivism.ch.caddy \
+ --site galene.hacktivism.ch --port 9029 --label galene \
+ --no-reload
+
+# koopa live (root):
+sudo bash …/scripts/caddy/caddy-apply.sh \
+ --snippet …/configs/caddy/SITE.caddy \
+ --site SITE.hacktivism.ch --port NNNN --label NAME
+```
+
+Allocated collab vhosts (2026-09-08): `galene` **9029**, `cryptpad` **9030**, `meet` **9031**, `bbb` **9032**.
+Snippets under `configs/caddy/*.hacktivism.ch.caddy`. Image bases: CachyOS except **bbb=Ubuntu**.
+
+Still valid: hand-edit live + mirror, then validate/reload.
diff --git a/scripts/caddy/apply-dossiers-ngi-basicauth.sh b/scripts/caddy/apply-dossiers-ngi-basicauth.sh
index 4587f6a..9582170 100755
--- a/scripts/caddy/apply-dossiers-ngi-basicauth.sh
+++ b/scripts/caddy/apply-dossiers-ngi-basicauth.sh
@@ -4,12 +4,12 @@
#
# Caddy file_server does NOT honor Apache .htaccess — this edits the Caddyfile.
#
-# On koopa (password for sudo):
-# sudo -n true 2>/dev/null || sudo -v
-# sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
+# On koopa (clone admin-log from Forgejo when needed; no home git dump):
+# sudo bash /path/to/koopa-admin-log/scripts/caddy/apply-dossiers-ngi-basicauth.sh
#
-# Idempotent. Password file is NOT in git:
-# /home/hernani/koopa-secrets/dossiers-ngi-basicauth.txt
+# Host runtime file (root-only, not under ~):
+# /etc/caddy/secrets/dossiers-ngi-basicauth.txt
+# SoT (git-crypt): koopa-admin-secrets-enc → koopa/home-hernani/caddy/
set -euo pipefail
if [[ "$(id -u)" -ne 0 ]]; then
@@ -19,8 +19,9 @@ fi
CADDY=/etc/caddy/Caddyfile
USER_NAME="${DOSSIERS_AUTH_USER:-hernani}"
-SECRETS_DIR=/home/hernani/koopa-secrets
+SECRETS_DIR=/etc/caddy/secrets
PASS_FILE="${SECRETS_DIR}/dossiers-ngi-basicauth.txt"
+OLD_PASS_FILE=/home/hernani/koopa-secrets/dossiers-ngi-basicauth.txt
SITES=(
dossiers.ngi.hacktivism.ch
dossiers.2.ngi.hacktivism.ch
@@ -34,9 +35,14 @@ fi
command -v caddy >/dev/null || { echo "ERROR: caddy not in PATH" >&2; exit 1; }
mkdir -p "$SECRETS_DIR"
-chown hernani:hernani "$SECRETS_DIR"
chmod 700 "$SECRETS_DIR"
+if [[ ! -f "$PASS_FILE" && -f "$OLD_PASS_FILE" ]]; then
+ cp -a "$OLD_PASS_FILE" "$PASS_FILE"
+ chmod 600 "$PASS_FILE"
+ echo "OK: migrated $OLD_PASS_FILE → $PASS_FILE"
+fi
+
if [[ -f "$PASS_FILE" ]]; then
# shellcheck disable=SC1090
# file format: USER=... PASS=... HASH=...
diff --git a/scripts/caddy/apply-notes-basicauth.sh b/scripts/caddy/apply-notes-basicauth.sh
new file mode 100755
index 0000000..e45db3f
--- /dev/null
+++ b/scripts/caddy/apply-notes-basicauth.sh
@@ -0,0 +1,179 @@
+#!/usr/bin/env bash
+# Apply HTTP basic_auth to silverbullet.hacktivism.ch (dossiers pattern).
+#
+# On koopa (clone admin-log from Forgejo when needed; no home git dump):
+# sudo bash /path/to/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh
+#
+# Host runtime file (root-only, not under ~):
+# /etc/caddy/secrets/silverbullet-basicauth.txt
+# SoT (git-crypt): koopa-admin-secrets-enc → koopa/home-hernani/caddy/
+set -euo pipefail
+
+if [[ "$(id -u)" -ne 0 ]]; then
+ echo "ERROR: run as root: sudo bash $0" >&2
+ exit 1
+fi
+
+CADDY=/etc/caddy/Caddyfile
+USER_NAME="${NOTES_AUTH_USER:-hernani}"
+SECRETS_DIR=/etc/caddy/secrets
+PASS_FILE="${SECRETS_DIR}/silverbullet-basicauth.txt"
+# legacy home dumps (migrate once, then delete)
+OLD_PASS_FILE=/home/hernani/koopa-secrets/silverbullet-basicauth.txt
+OLD_NOTES_FILE=/home/hernani/koopa-secrets/notes-basicauth.txt
+OLD_FREENOTES_FILE=/home/hernani/koopa-secrets/freenotes-basicauth.txt
+SITE=silverbullet.hacktivism.ch
+
+if [[ ! -f "$CADDY" ]]; then
+ echo "ERROR: missing $CADDY" >&2
+ exit 1
+fi
+command -v caddy >/dev/null || { echo "ERROR: caddy not in PATH" >&2; exit 1; }
+
+mkdir -p "$SECRETS_DIR"
+chmod 700 "$SECRETS_DIR"
+
+# Prefer explicit seed (SoT from secrets-enc), then legacy home dumps once.
+SEED_FILE="${NOTES_AUTH_SEED:-}"
+if [[ ! -f "$PASS_FILE" && -n "$SEED_FILE" && -f "$SEED_FILE" ]]; then
+ cp -a "$SEED_FILE" "$PASS_FILE"
+ chmod 600 "$PASS_FILE"
+ echo "OK: seeded $SEED_FILE → $PASS_FILE"
+fi
+if [[ ! -f "$PASS_FILE" && -f "$OLD_PASS_FILE" ]]; then
+ cp -a "$OLD_PASS_FILE" "$PASS_FILE"
+ chmod 600 "$PASS_FILE"
+ echo "OK: migrated $OLD_PASS_FILE → $PASS_FILE"
+fi
+if [[ ! -f "$PASS_FILE" && -f "$OLD_NOTES_FILE" ]]; then
+ cp -a "$OLD_NOTES_FILE" "$PASS_FILE"
+ chmod 600 "$PASS_FILE"
+ echo "OK: migrated $OLD_NOTES_FILE → $PASS_FILE"
+fi
+if [[ ! -f "$PASS_FILE" && -f "$OLD_FREENOTES_FILE" ]]; then
+ cp -a "$OLD_FREENOTES_FILE" "$PASS_FILE"
+ chmod 600 "$PASS_FILE"
+ echo "OK: migrated $OLD_FREENOTES_FILE → $PASS_FILE"
+fi
+
+if [[ -f "$PASS_FILE" ]]; then
+ USER_NAME="$(awk -F= '/^USER=/{print substr($0,6); exit}' "$PASS_FILE")"
+ PASS="$(awk -F= '/^PASS=/{print substr($0,6); exit}' "$PASS_FILE")"
+ HASH="$(awk -F= '/^HASH=/{print substr($0,6); exit}' "$PASS_FILE")"
+ if [[ -z "$USER_NAME" || -z "$HASH" ]]; then
+ echo "ERROR: $PASS_FILE incomplete — move it aside and re-run" >&2
+ exit 1
+ fi
+ echo "OK: reusing credentials in $PASS_FILE"
+else
+ PASS="$(openssl rand -base64 18 | tr -d '/+=' | head -c 20)"
+ HASH="$(caddy hash-password --plaintext "$PASS")"
+ umask 077
+ cat >"$PASS_FILE" < tuple[int, int]:
+ marker = f"{site_name} {{"
+ i = src.find(marker)
+ if i < 0:
+ raise SystemExit(
+ f"ERROR: site block not found: {site_name} — merge silverbullet.hacktivism.ch.caddy first"
+ )
+ depth = 0
+ for k in range(i + len(marker) - 1, len(src)):
+ ch = src[k]
+ if ch == "{":
+ depth += 1
+ elif ch == "}":
+ depth -= 1
+ if depth == 0:
+ return i, k + 1
+ raise SystemExit(f"ERROR: unclosed block: {site_name}")
+
+
+def has_basic_auth_directive(block: str) -> bool:
+ # Ignore comments; only real directives count.
+ stripped = "\n".join(
+ ln for ln in block.splitlines() if not ln.lstrip().startswith("#")
+ )
+ return bool(re.search(r"(?m)^\s*basic_?auth\b", stripped))
+
+
+i, j = site_span(text, site)
+block = text[i:j]
+auth_lines = (
+ "\tbasic_auth {\n"
+ f"\t\t{user} {hash_caddy}\n"
+ "\t}\n"
+)
+# Replace existing basic_auth block (also fixes legacy "$$" hashes that never matched).
+block_new, n = re.subn(
+ r"(?m)^[ \t]*basic_auth\s*\{.*?\n[ \t]*\}\n?",
+ auth_lines,
+ block,
+ count=1,
+ flags=re.S,
+)
+if n:
+ text = text[:i] + block_new + text[j:]
+ print(f"OK: replaced basic_auth in {site}")
+elif has_basic_auth_directive(block):
+ print(f"OK: {site} already has basic_auth")
+else:
+ m = re.search(r'(?m)^[ \t]*header Alt-Svc "clear"[ \t]*\n', block)
+ if m:
+ insert_at = m.end()
+ new_block = block[:insert_at] + auth_lines + block[insert_at:]
+ else:
+ nl = block.find("\n")
+ new_block = block[: nl + 1] + auth_lines + block[nl + 1 :]
+ text = text[:i] + new_block + text[j:]
+ print(f"OK: injected basic_auth in {site}")
+
+if text != orig:
+ path.write_text(text)
+ print(f"wrote {path}")
+else:
+ print("no Caddyfile change")
+PY
+
+echo "== validate =="
+caddy validate --config "$CADDY"
+
+echo "== reload =="
+if systemctl is-active --quiet caddy; then
+ systemctl reload caddy
+else
+ echo "WARN: caddy unit not active — start it yourself" >&2
+fi
+
+echo
+echo "Done. Browser: https://${SITE}/"
+echo "User/pass: $PASS_FILE"
diff --git a/scripts/caddy/caddy-apply.sh b/scripts/caddy/caddy-apply.sh
new file mode 100755
index 0000000..f43e4e5
--- /dev/null
+++ b/scripts/caddy/caddy-apply.sh
@@ -0,0 +1,217 @@
+#!/usr/bin/env bash
+# Generic: merge a site snippet into a Caddyfile + ACME http:// list + ports comment.
+#
+# Laptop (mirror only, no reload):
+# bash scripts/caddy/caddy-apply.sh \
+# --config configs/caddy/Caddyfile \
+# --snippet configs/caddy/galene.hacktivism.ch.caddy \
+# --site galene.hacktivism.ch --port 9029 --label galene \
+# --no-reload
+#
+# Koopa live (root, Freigabe):
+# sudo bash …/caddy-apply.sh \
+# --snippet /home/hernani/koopa-admin-log/configs/caddy/galene.hacktivism.ch.caddy \
+# --site galene.hacktivism.ch --port 9029 --label galene
+set -euo pipefail
+
+CADDY=/etc/caddy/Caddyfile
+SNIPPET=""
+SITE=""
+PORT=""
+LABEL=""
+INSERT_AFTER="decidim.hacktivism.ch"
+ACME_ANCHOR="http://decidim.hacktivism.ch"
+NO_RELOAD=0
+DRY=0
+OLD_SITES=()
+
+usage() {
+ sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
+ echo "Options: --config PATH --snippet PATH --site HOST --port N --label NAME"
+ echo " --insert-after HOST --acme-anchor http://HOST --old-site HOST"
+ echo " --no-reload --dry-run"
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --config) CADDY=$2; shift 2 ;;
+ --snippet) SNIPPET=$2; shift 2 ;;
+ --site) SITE=$2; shift 2 ;;
+ --port) PORT=$2; shift 2 ;;
+ --label) LABEL=$2; shift 2 ;;
+ --insert-after) INSERT_AFTER=$2; shift 2 ;;
+ --acme-anchor) ACME_ANCHOR=$2; shift 2 ;;
+ --old-site) OLD_SITES+=("$2"); shift 2 ;;
+ --no-reload) NO_RELOAD=1; shift ;;
+ --dry-run) DRY=1; shift ;;
+ -h|--help) usage; exit 0 ;;
+ *) echo "ERROR: unknown arg: $1" >&2; usage >&2; exit 2 ;;
+ esac
+done
+
+[[ -n "$SNIPPET" && -n "$SITE" ]] || { echo "ERROR: --snippet and --site required" >&2; exit 2; }
+[[ -f "$CADDY" ]] || { echo "ERROR: missing $CADDY" >&2; exit 1; }
+[[ -f "$SNIPPET" ]] || { echo "ERROR: missing $SNIPPET" >&2; exit 1; }
+LABEL="${LABEL:-$SITE}"
+
+if [[ "$CADDY" == /etc/caddy/Caddyfile && "$(id -u)" -ne 0 && "$DRY" -eq 0 ]]; then
+ echo "ERROR: live Caddyfile needs root (or --dry-run / --config mirror)" >&2
+ exit 1
+fi
+
+ts=$(date +%Y%m%d-%H%M%S)
+if [[ "$DRY" -eq 0 ]]; then
+ cp -a "$CADDY" "${CADDY}.bak-caddy-apply-${ts}"
+ echo "backup ${CADDY}.bak-caddy-apply-${ts}"
+fi
+
+export CADDY SNIPPET SITE PORT LABEL INSERT_AFTER ACME_ANCHOR DRY
+export OLD_SITES_CSV
+OLD_SITES_CSV=$(IFS=,; echo "${OLD_SITES[*]-}")
+
+python3 - <<'PY'
+import os, re, sys
+from pathlib import Path
+
+caddy = Path(os.environ["CADDY"])
+snip = Path(os.environ["SNIPPET"]).read_text().rstrip() + "\n\n"
+site = os.environ["SITE"]
+port = os.environ.get("PORT") or ""
+label = os.environ.get("LABEL") or site
+insert_after = os.environ["INSERT_AFTER"]
+acme_anchor = os.environ["ACME_ANCHOR"]
+dry = os.environ.get("DRY") == "1"
+old_sites = [s for s in os.environ.get("OLD_SITES_CSV", "").split(",") if s]
+
+text = caddy.read_text()
+orig = text
+
+def drop_site_block(text: str, old_site: str) -> str:
+ marker_old = f"{old_site} {{"
+ i = text.find(marker_old)
+ if i < 0:
+ return text
+ j = text.find("\n}", i)
+ if j < 0:
+ return text
+ end = j + 2
+ while end < len(text) and text[end] == "\n":
+ end += 1
+ text = text[:i] + text[end:]
+ text = text.replace(f", http://{old_site}", "").replace(f"http://{old_site}, ", "")
+ text = text.replace(f"http://{old_site}", "")
+ print(f"OK: removed duplicate old block {old_site}")
+ return text
+
+for old_site in old_sites:
+ if old_site in text and site not in text:
+ text = text.replace(old_site, site)
+ print(f"OK: renamed {old_site} → {site}")
+ elif old_site in text and site in text:
+ text = drop_site_block(text, old_site)
+
+marker = f"{site} {{"
+if marker in text:
+ print(f"OK: site block already present: {site}")
+else:
+ insert_at = None
+ after = text.find(f"{insert_after} {{")
+ if after >= 0:
+ j = text.find("\n}", after)
+ if j >= 0:
+ insert_at = j + 2
+ while insert_at < len(text) and text[insert_at] == "\n":
+ insert_at += 1
+ if insert_at is None:
+ acme = text.find("http://taler.hacktivism.ch")
+ insert_at = acme if acme >= 0 else len(text)
+ text = text[:insert_at] + snip + text[insert_at:]
+ print(f"OK: inserted site block {site}")
+
+http_tok = f"http://{site}"
+if http_tok in text:
+ print(f"OK: ACME http list already has {http_tok}")
+else:
+ # Prefer exact "anchor {" form; else append before " {" of the long http list
+ old = f"{acme_anchor} {{"
+ new = f"{acme_anchor}, {http_tok} {{"
+ if old in text:
+ text = text.replace(old, new, 1)
+ print(f"OK: added {http_tok} to ACME http list (anchor)")
+ else:
+ # Find the shared ACME line (starts with http://taler…)
+ m = re.search(r"(http://taler\.hacktivism\.ch[^\n]*?)(\s*\{)", text)
+ if not m:
+ raise SystemExit("ERROR: ACME http list not found")
+ line = m.group(1)
+ if http_tok in line:
+ print(f"OK: ACME http list already has {http_tok}")
+ else:
+ text = text[: m.start(1)] + line.rstrip() + f", {http_tok}" + text[m.end(1) :]
+ print(f"OK: added {http_tok} to ACME http list (append)")
+
+# Ports comment header (first matching line starting with "# 9020 castopod")
+if port:
+ token = f"{port} {label}"
+ def upd_hdr(line: str) -> str:
+ if token in line or f"| {port} " in line or f"| {port}|" in line:
+ return line
+ # insert before " | 9200 forgejo-ssh" if present, else before end
+ if "9200 forgejo-ssh" in line:
+ return line.replace(" | 9200 forgejo-ssh", f" | {token} | 9200 forgejo-ssh", 1)
+ if line.rstrip().endswith("forgejo-ssh"):
+ return line.rstrip() + f" | {token}\n"
+ return line.rstrip() + f" | {token}\n"
+
+ lines = text.splitlines(keepends=True)
+ changed = False
+ for i, line in enumerate(lines):
+ if line.startswith("# 9020 castopod"):
+ new_line = upd_hdr(line)
+ if new_line != line:
+ lines[i] = new_line if new_line.endswith("\n") else new_line + "\n"
+ print(f"OK: updated ports comment (+{token})")
+ changed = True
+ else:
+ print(f"OK: ports comment already has {token}")
+ break
+ if changed:
+ text = "".join(lines)
+
+if text != orig:
+ if dry:
+ print(f"DRY: would write {caddy} ({len(text) - len(orig):+d} bytes)")
+ else:
+ caddy.write_text(text)
+ print(f"wrote {caddy}")
+else:
+ print("no Caddyfile change")
+PY
+
+if [[ "$DRY" -eq 1 ]]; then
+ echo "dry-run done (no validate/reload)"
+ exit 0
+fi
+
+if command -v caddy >/dev/null 2>&1; then
+ echo "== validate =="
+ caddy validate --config "$CADDY"
+else
+ echo "WARN: caddy binary missing — skip validate" >&2
+fi
+
+if [[ "$NO_RELOAD" -eq 1 ]]; then
+ echo "skip reload (--no-reload)"
+ exit 0
+fi
+
+if [[ "$CADDY" == /etc/caddy/Caddyfile ]] && command -v systemctl >/dev/null 2>&1; then
+ echo "== reload =="
+ if systemctl is-active --quiet caddy; then
+ systemctl reload caddy
+ else
+ echo "WARN: caddy unit not active — start it yourself" >&2
+ fi
+else
+ echo "skip reload (not live path or no systemctl)"
+fi
diff --git a/scripts/caddy/merge-notes-site.sh b/scripts/caddy/merge-notes-site.sh
new file mode 100755
index 0000000..402d644
--- /dev/null
+++ b/scripts/caddy/merge-notes-site.sh
@@ -0,0 +1,133 @@
+#!/usr/bin/env bash
+# Merge silverbullet.hacktivism.ch into live /etc/caddy/Caddyfile + ACME http list.
+# Run on koopa as root AFTER podman is up on :9028:
+# sudo bash /home/hernani/koopa-admin-log/scripts/caddy/merge-notes-site.sh
+# Then:
+# sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh
+set -euo pipefail
+
+if [[ "$(id -u)" -ne 0 ]]; then
+ echo "ERROR: run as root: sudo bash $0" >&2
+ exit 1
+fi
+
+CADDY=/etc/caddy/Caddyfile
+SNIPPET=/home/hernani/koopa-admin-log/configs/caddy/silverbullet.hacktivism.ch.caddy
+SITE=silverbullet.hacktivism.ch
+# Prior hostnames to rename into SITE (order: newest leftovers first)
+OLD_SITES="freenotes.hacktivism.ch notes.hacktivism.ch"
+
+[[ -f "$CADDY" ]] || { echo "ERROR: missing $CADDY" >&2; exit 1; }
+[[ -f "$SNIPPET" ]] || { echo "ERROR: missing $SNIPPET" >&2; exit 1; }
+command -v caddy >/dev/null || { echo "ERROR: caddy not in PATH" >&2; exit 1; }
+
+ts=$(date +%Y%m%d-%H%M%S)
+cp -a "$CADDY" "${CADDY}.bak-silverbullet-merge-${ts}"
+echo "backup ${CADDY}.bak-silverbullet-merge-${ts}"
+
+python3 - "$CADDY" "$SNIPPET" "$SITE" $OLD_SITES <<'PY'
+import sys
+from pathlib import Path
+
+caddy = Path(sys.argv[1])
+snippet_path = Path(sys.argv[2])
+site = sys.argv[3]
+old_sites = sys.argv[4:]
+text = caddy.read_text()
+orig = text
+snip = snippet_path.read_text().rstrip() + "\n\n"
+
+def drop_site_block(text: str, old_site: str) -> str:
+ marker_old = f"{old_site} {{"
+ i = text.find(marker_old)
+ if i < 0:
+ return text
+ j = text.find("\n}", i)
+ if j < 0:
+ return text
+ end = j + 2
+ while end < len(text) and text[end] == "\n":
+ end += 1
+ text = text[:i] + text[end:]
+ text = text.replace(f", http://{old_site}", "").replace(f"http://{old_site}, ", "")
+ text = text.replace(f"http://{old_site}", "")
+ print(f"OK: removed duplicate old block {old_site}")
+ return text
+
+# Rename leftover freenotes/notes.hacktivism.ch → silverbullet.hacktivism.ch
+for old_site in old_sites:
+ if old_site in text and site not in text:
+ text = text.replace(old_site, site)
+ text = text.replace("9028 freenotes", "9028 silverbullet")
+ text = text.replace("9028 notes", "9028 silverbullet")
+ print(f"OK: renamed {old_site} → {site}")
+ elif old_site in text and site in text:
+ text = drop_site_block(text, old_site)
+
+marker = f"{site} {{"
+if marker in text:
+ print(f"OK: site block already present: {site}")
+else:
+ # Insert after decidim block if present, else before ACME http list
+ insert_at = None
+ dec = text.find("decidim.hacktivism.ch {")
+ if dec >= 0:
+ j = text.find("\n}", dec)
+ if j >= 0:
+ insert_at = j + 2 # after closing brace newline
+ while insert_at < len(text) and text[insert_at] == "\n":
+ insert_at += 1
+ if insert_at is None:
+ acme = text.find("http://taler.hacktivism.ch")
+ insert_at = acme if acme >= 0 else len(text)
+ text = text[:insert_at] + snip + text[insert_at:]
+ print(f"OK: inserted site block {site}")
+
+http_tok = f"http://{site}"
+if http_tok in text:
+ print(f"OK: ACME http list already has {http_tok}")
+else:
+ old = "http://decidim.hacktivism.ch {"
+ new = f"http://decidim.hacktivism.ch, {http_tok} {{"
+ if old not in text:
+ # maybe already on a long list without trailing brace-only form
+ raise SystemExit("ERROR: ACME http list marker not found (decidim)")
+ text = text.replace(old, new, 1)
+ print(f"OK: added {http_tok} to ACME http list")
+
+# Comment header line near top ports comment
+for old_hdr, new_hdr in (
+ (
+ "# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9200 forgejo-ssh",
+ "# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9028 silverbullet | 9200 forgejo-ssh",
+ ),
+ (
+ "# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9028 notes | 9200 forgejo-ssh",
+ "# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | 9025 paivana | 9026 lemmy | 9027 decidim | 9028 silverbullet | 9200 forgejo-ssh",
+ ),
+):
+ if old_hdr in text:
+ text = text.replace(old_hdr, new_hdr, 1)
+ print("OK: updated ports comment header")
+ break
+
+if text != orig:
+ caddy.write_text(text)
+ print(f"wrote {caddy}")
+else:
+ print("no Caddyfile change")
+PY
+
+echo "== validate =="
+caddy validate --config "$CADDY"
+
+echo "== reload =="
+if systemctl is-active --quiet caddy; then
+ systemctl reload caddy
+else
+ echo "WARN: caddy unit not active — start it yourself" >&2
+fi
+
+echo
+echo "Next: sudo bash /home/hernani/koopa-admin-log/scripts/caddy/apply-notes-basicauth.sh"
+echo "DNS/VeciGate for ${SITE} still separate."
diff --git a/scripts/cryptpad/build.sh b/scripts/cryptpad/build.sh
new file mode 100755
index 0000000..ae8f6dd
--- /dev/null
+++ b/scripts/cryptpad/build.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# Build CryptPad image on CachyOS base (run on koopa).
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CTX="${ROOT}/configs/cryptpad"
+TAG="${IMAGE_TAG:-localhost/koopa-cryptpad:cachyos}"
+CRYPTPAD_VERSION="${CRYPTPAD_VERSION:-2025.9.0}"
+cd "$CTX"
+[[ -f Containerfile ]] || { echo "ERROR: missing $CTX/Containerfile" >&2; exit 1; }
+echo "build $TAG (CRYPTPAD_VERSION=$CRYPTPAD_VERSION) ctx=$CTX"
+podman build \
+ --build-arg "CRYPTPAD_VERSION=${CRYPTPAD_VERSION}" \
+ -t "$TAG" \
+ -f Containerfile \
+ .
+echo "OK: $TAG"
+podman image inspect "$TAG" --format '{{.Id}} {{.Size}} {{.Architecture}}' 2>/dev/null || true
diff --git a/scripts/galene/build.sh b/scripts/galene/build.sh
new file mode 100755
index 0000000..0b1aa55
--- /dev/null
+++ b/scripts/galene/build.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# Build Galene image on CachyOS base (run on koopa).
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CTX="${ROOT}/configs/galene"
+TAG="${IMAGE_TAG:-localhost/koopa-galene:cachyos}"
+GALENE_VERSION="${GALENE_VERSION:-0.9.1}"
+cd "$CTX"
+[[ -f Containerfile ]] || { echo "ERROR: missing $CTX/Containerfile" >&2; exit 1; }
+echo "build $TAG (GALENE_VERSION=$GALENE_VERSION) ctx=$CTX"
+podman build \
+ --build-arg "GALENE_VERSION=${GALENE_VERSION}" \
+ -t "$TAG" \
+ -f Containerfile \
+ .
+echo "OK: $TAG"
+podman image inspect "$TAG" --format '{{.Id}} {{.Size}} {{.Architecture}}' 2>/dev/null || true
diff --git a/scripts/meet/build.sh b/scripts/meet/build.sh
new file mode 100755
index 0000000..377b0ec
--- /dev/null
+++ b/scripts/meet/build.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# Build Meet web stub on CachyOS (run on koopa).
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CTX="${ROOT}/configs/meet"
+TAG="${IMAGE_TAG:-localhost/koopa-meet-web:cachyos}"
+cd "$CTX"
+[[ -f Containerfile ]] || { echo "ERROR: missing $CTX/Containerfile" >&2; exit 1; }
+echo "build $TAG ctx=$CTX"
+podman build -t "$TAG" -f Containerfile .
+echo "OK: $TAG"
diff --git a/scripts/notes/README.md b/scripts/notes/README.md
new file mode 100644
index 0000000..4d6bbf9
--- /dev/null
+++ b/scripts/notes/README.md
@@ -0,0 +1,40 @@
+# SilverBullet — scripts (koopa)
+
+Product **FreeNotes** is separate (native enc). These scripts operate the parked **SilverBullet** web stack on port **9028**.
+
+| Script | Role |
+|--------|------|
+| `status.sh` | Read-only dirs / containers / `:9028` |
+| `build.sh` | `podman build` → `localhost/koopa-silverbullet:cachyos` |
+| `up.sh` | `podman-compose up` in `~/koopa-silverbullet` + health |
+| `cutover-to-silverbullet.sh` | Live freenotes → silverbullet (default **dry-run**; `--apply` mutates) |
+| `prepare-basicauth-seed.sh` | SoT → `/tmp/silverbullet-basicauth-seed.txt` (no sudo) |
+
+Caddy (sudo, separate): `scripts/caddy/merge-notes-site.sh`, `apply-notes-basicauth.sh`.
+
+## Cutover order (needs Freigabe + DNS)
+
+On koopa as `hernani` (admin-log clone from Forgejo when needed):
+
+```bash
+bash …/scripts/notes/status.sh
+bash …/scripts/notes/cutover-to-silverbullet.sh # dry-run
+bash …/scripts/notes/cutover-to-silverbullet.sh --apply # Freigabe
+```
+
+Then Caddy (root) + seed from laptop SoT:
+
+```bash
+# laptop (unlocked secrets-enc):
+bash …/scripts/notes/prepare-basicauth-seed.sh
+scp /tmp/silverbullet-basicauth-seed.txt hernani@koopa:/tmp/
+
+# koopa root:
+sudo bash …/scripts/caddy/merge-notes-site.sh
+sudo NOTES_AUTH_SEED=/tmp/silverbullet-basicauth-seed.txt \
+ bash …/scripts/caddy/apply-notes-basicauth.sh
+```
+
+DNS: `silverbullet.hacktivism.ch` → koopa (human). Public: `https://silverbullet.hacktivism.ch/`.
+
+Keeps on cutover: `space/`, `.env`. Refreshes compose/Containerfile/entrypoint/unit from `configs/notes/`.
diff --git a/scripts/notes/build.sh b/scripts/notes/build.sh
new file mode 100755
index 0000000..80b4bc3
--- /dev/null
+++ b/scripts/notes/build.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# Build SilverBullet image on CachyOS base (run on koopa).
+# bash ~/koopa-admin-log/scripts/notes/build.sh
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CTX="${ROOT}/configs/notes"
+TAG="${IMAGE_TAG:-localhost/koopa-silverbullet:cachyos}"
+SB_VERSION="${SB_VERSION:-2.10.0}"
+
+cd "$CTX"
+[[ -f Containerfile ]] || { echo "ERROR: missing $CTX/Containerfile" >&2; exit 1; }
+[[ -f entrypoint.sh ]] || { echo "ERROR: missing $CTX/entrypoint.sh" >&2; exit 1; }
+
+echo "build $TAG (SB_VERSION=$SB_VERSION) ctx=$CTX"
+podman build \
+ --build-arg "SB_VERSION=${SB_VERSION}" \
+ -t "$TAG" \
+ -f Containerfile \
+ .
+
+echo "OK: $TAG"
+podman image inspect "$TAG" --format '{{.Id}} {{.Size}} {{.Architecture}}' 2>/dev/null || true
diff --git a/scripts/notes/cutover-to-silverbullet.sh b/scripts/notes/cutover-to-silverbullet.sh
new file mode 100755
index 0000000..af3f37b
--- /dev/null
+++ b/scripts/notes/cutover-to-silverbullet.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env bash
+# Cut over live stack freenotes → silverbullet on koopa (hernani).
+# Default: dry-run. Mutate only with --apply.
+#
+# bash /path/to/koopa-admin-log/scripts/notes/cutover-to-silverbullet.sh
+# bash /path/to/koopa-admin-log/scripts/notes/cutover-to-silverbullet.sh --apply
+#
+# Keeps: space/, .env. Refreshes: compose.yml, Containerfile, entrypoint.sh, systemd unit.
+# Image: retag localhost/koopa-freenotes:cachyos → localhost/koopa-silverbullet:cachyos (no rebuild).
+# Does NOT touch Caddy (sudo) — print next steps only.
+set -euo pipefail
+
+APPLY=0
+for a in "$@"; do
+ case "$a" in
+ --apply) APPLY=1 ;;
+ -h|--help)
+ sed -n '2,12p' "$0"
+ exit 0
+ ;;
+ *)
+ echo "ERROR: unknown arg: $a (use --apply)" >&2
+ exit 2
+ ;;
+ esac
+done
+
+OLD_DIR="${HOME}/koopa-freenotes"
+NEW_DIR="${HOME}/koopa-silverbullet"
+OLD_CT=koopa-freenotes
+NEW_CT=koopa-silverbullet
+OLD_IMG=localhost/koopa-freenotes:cachyos
+NEW_IMG=localhost/koopa-silverbullet:cachyos
+PORT=9028
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CFG="${ROOT}/configs/notes"
+
+[[ -d "$CFG" ]] || { echo "ERROR: missing $CFG" >&2; exit 1; }
+[[ -f "$CFG/compose.yml" ]] || { echo "ERROR: missing $CFG/compose.yml" >&2; exit 1; }
+
+mode=dry-run
+[[ "$APPLY" -eq 1 ]] && mode=apply
+echo "STATUS=start MODE=$mode HOST=$(hostname -s) OLD_DIR=$OLD_DIR NEW_DIR=$NEW_DIR"
+
+have_old=0
+have_new=0
+[[ -d "$OLD_DIR" ]] && have_old=1
+[[ -d "$NEW_DIR" ]] && have_new=1
+echo "STATUS=dirs OLD=$have_old NEW=$have_new"
+
+ct_old=$(podman ps -a --format '{{.Names}}' 2>/dev/null | grep -Fx "$OLD_CT" || true)
+ct_new=$(podman ps -a --format '{{.Names}}' 2>/dev/null | grep -Fx "$NEW_CT" || true)
+echo "STATUS=containers OLD=${ct_old:-none} NEW=${ct_new:-none}"
+
+img_old=$(podman images -q "$OLD_IMG" 2>/dev/null || true)
+img_new=$(podman images -q "$NEW_IMG" 2>/dev/null || true)
+echo "STATUS=images OLD=${img_old:-none} NEW=${img_new:-none}"
+
+dir_err=0
+if [[ "$have_old" -eq 0 && "$have_new" -eq 1 ]]; then
+ echo "STATUS=already_cutover DIR=$NEW_DIR"
+elif [[ "$have_old" -eq 0 && "$have_new" -eq 0 ]]; then
+ echo "STATUS=warn neither $OLD_DIR nor $NEW_DIR (ok for laptop dry-run; apply needs koopa)"
+ dir_err=1
+elif [[ "$have_old" -eq 1 && "$have_new" -eq 1 ]]; then
+ echo "STATUS=warn both dirs exist — resolve manually before --apply"
+ dir_err=1
+fi
+
+plan() { echo "PLAN: $*"; }
+
+plan "stop+rm container $OLD_CT (if present)"
+plan "mv $OLD_DIR → $NEW_DIR (if old present)"
+plan "copy compose/Containerfile/entrypoint/unit from $CFG (keep space .env)"
+plan "podman tag $OLD_IMG $NEW_IMG (if new image missing)"
+plan "podman-compose up -d in $NEW_DIR"
+plan "health curl :$PORT"
+plan "optional: systemctl --user enable container-koopa-silverbullet.service"
+
+if [[ "$APPLY" -ne 1 ]]; then
+ echo "STATUS=dry-run NEXT=re-run on koopa with --apply (Freigabe)"
+ echo "STATUS=caddy_next sudo bash $ROOT/scripts/caddy/merge-notes-site.sh"
+ echo "STATUS=caddy_next sudo NOTES_AUTH_SEED=/tmp/silverbullet-basicauth-seed.txt bash $ROOT/scripts/caddy/apply-notes-basicauth.sh"
+ echo "STATUS=dns_need silverbullet.hacktivism.ch → koopa"
+ exit 0
+fi
+
+if [[ "$dir_err" -eq 1 ]]; then
+ echo "ERROR: refuse --apply with ambiguous/missing live dirs" >&2
+ exit 1
+fi
+
+if [[ -n "$ct_old" ]]; then
+ echo "STATUS=stop $OLD_CT"
+ podman stop -t 20 "$OLD_CT" || true
+ podman rm "$OLD_CT" || true
+fi
+
+if [[ "$have_old" -eq 1 ]]; then
+ echo "STATUS=mv $OLD_DIR → $NEW_DIR"
+ mv "$OLD_DIR" "$NEW_DIR"
+fi
+
+cd "$NEW_DIR"
+# Refresh stack files from admin-log; never overwrite space/ or .env
+cp -a "$CFG/compose.yml" "$NEW_DIR/compose.yml"
+cp -a "$CFG/Containerfile" "$NEW_DIR/Containerfile"
+cp -a "$CFG/entrypoint.sh" "$NEW_DIR/entrypoint.sh"
+chmod +x "$NEW_DIR/entrypoint.sh"
+cp -a "$CFG/container-koopa-silverbullet.service" "$NEW_DIR/container-koopa-silverbullet.service"
+cp -a "$CFG/.env.example" "$NEW_DIR/.env.example"
+# Drop obsolete unit name if present
+rm -f "$NEW_DIR/container-koopa-freenotes.service"
+echo "STATUS=refreshed_stack_files DIR=$NEW_DIR"
+
+if [[ -z "$img_new" ]]; then
+ if [[ -n "$img_old" ]]; then
+ echo "STATUS=tag $OLD_IMG → $NEW_IMG"
+ podman tag "$OLD_IMG" "$NEW_IMG"
+ else
+ echo "STATUS=build missing image via build.sh"
+ bash "$ROOT/scripts/notes/build.sh"
+ fi
+fi
+
+if [[ -n "$ct_new" ]]; then
+ echo "STATUS=rm stale $NEW_CT"
+ podman stop -t 10 "$NEW_CT" || true
+ podman rm "$NEW_CT" || true
+fi
+
+echo "STATUS=compose_up"
+podman-compose up -d
+
+ok=0
+for i in $(seq 1 40); do
+ code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 "http://127.0.0.1:${PORT}/" || true)
+ echo "STATUS=health try=$i code=$code"
+ case "$code" in
+ 2*|3*) ok=1; break ;;
+ esac
+ sleep 3
+done
+[[ "$ok" -eq 1 ]] || { echo "ERROR: no answer on :${PORT}" >&2; podman-compose ps; exit 1; }
+
+# User systemd unit (optional; ignore failures)
+UNIT_SRC="$NEW_DIR/container-koopa-silverbullet.service"
+UNIT_DST="${HOME}/.config/systemd/user/container-koopa-silverbullet.service"
+if [[ -f "$UNIT_SRC" ]] && command -v systemctl >/dev/null; then
+ mkdir -p "${HOME}/.config/systemd/user"
+ cp -a "$UNIT_SRC" "$UNIT_DST"
+ systemctl --user daemon-reload || true
+ systemctl --user disable --now container-koopa-freenotes.service 2>/dev/null || true
+ systemctl --user enable --now container-koopa-silverbullet.service 2>/dev/null || true
+ echo "STATUS=systemd_user unit=container-koopa-silverbullet.service"
+fi
+
+echo "STATUS=ok CONTAINER=$NEW_CT DIR=$NEW_DIR PORT=$PORT"
+echo "STATUS=caddy_next sudo bash $ROOT/scripts/caddy/merge-notes-site.sh"
+echo "STATUS=caddy_next sudo NOTES_AUTH_SEED=/tmp/silverbullet-basicauth-seed.txt bash $ROOT/scripts/caddy/apply-notes-basicauth.sh"
+echo "STATUS=dns_need silverbullet.hacktivism.ch → koopa"
diff --git a/scripts/notes/prepare-basicauth-seed.sh b/scripts/notes/prepare-basicauth-seed.sh
new file mode 100755
index 0000000..a9a6f1a
--- /dev/null
+++ b/scripts/notes/prepare-basicauth-seed.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# Copy silverbullet basicauth SoT → /tmp seed for apply-notes-basicauth.sh.
+# Run on magikoopa (or any host with unlocked koopa-admin-secrets-enc).
+# Does NOT touch /etc/caddy (no sudo). Does NOT print secret contents.
+#
+# bash scripts/notes/prepare-basicauth-seed.sh
+# # then on koopa (after scp seed or shared path):
+# sudo NOTES_AUTH_SEED=/tmp/silverbullet-basicauth-seed.txt \
+# bash …/scripts/caddy/apply-notes-basicauth.sh
+set -euo pipefail
+
+OUT="${NOTES_AUTH_SEED_OUT:-/tmp/silverbullet-basicauth-seed.txt}"
+SOT_DEFAULT="${HOME}/git/koopa-admin-secrets-enc/koopa/home-hernani/caddy/silverbullet-basicauth.txt"
+SOT="${NOTES_AUTH_SOT:-$SOT_DEFAULT}"
+
+[[ -f "$SOT" ]] || {
+ echo "ERROR: missing SoT $SOT (unlock git-crypt / set NOTES_AUTH_SOT=)" >&2
+ exit 1
+}
+
+umask 077
+cp -a "$SOT" "$OUT"
+chmod 600 "$OUT"
+echo "STATUS=ok OUT=$OUT BYTES=$(wc -c <"$OUT" | tr -d ' ') NEXT=scp_or_use_NOTES_AUTH_SEED"
+echo "STATUS=hint scp $OUT hernani@koopa:/tmp/ && sudo NOTES_AUTH_SEED=$OUT bash …/apply-notes-basicauth.sh"
diff --git a/scripts/notes/status.sh b/scripts/notes/status.sh
new file mode 100755
index 0000000..6ae116e
--- /dev/null
+++ b/scripts/notes/status.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# Thin status for SilverBullet stack on koopa (hernani). Safe read-only.
+# bash scripts/notes/status.sh
+set -euo pipefail
+
+OLD_DIR="${HOME}/koopa-freenotes"
+NEW_DIR="${HOME}/koopa-silverbullet"
+OLD_CT=koopa-freenotes
+NEW_CT=koopa-silverbullet
+OLD_IMG=localhost/koopa-freenotes:cachyos
+NEW_IMG=localhost/koopa-silverbullet:cachyos
+PORT=9028
+
+echo "STATUS=host HOST=$(hostname -s)"
+echo "STATUS=dirs OLD=$([ -d "$OLD_DIR" ] && echo 1 || echo 0) NEW=$([ -d "$NEW_DIR" ] && echo 1 || echo 0)"
+
+if command -v podman >/dev/null 2>&1; then
+ ct_old=$(podman ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null | grep -E "^${OLD_CT} " || true)
+ ct_new=$(podman ps -a --format '{{.Names}} {{.Status}}' 2>/dev/null | grep -E "^${NEW_CT} " || true)
+ echo "STATUS=ct_old ${ct_old:-none}"
+ echo "STATUS=ct_new ${ct_new:-none}"
+ img_old=$(podman images -q "$OLD_IMG" 2>/dev/null || true)
+ img_new=$(podman images -q "$NEW_IMG" 2>/dev/null || true)
+ echo "STATUS=img_old ${img_old:-none}"
+ echo "STATUS=img_new ${img_new:-none}"
+else
+ echo "STATUS=podman missing"
+fi
+
+code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 "http://127.0.0.1:${PORT}/" 2>/dev/null || echo fail)
+echo "STATUS=local_http PORT=$PORT CODE=$code"
+
+if [[ -d "$NEW_DIR" ]]; then
+ echo "STATUS=cutover done DIR=$NEW_DIR"
+elif [[ -d "$OLD_DIR" ]]; then
+ echo "STATUS=cutover pending DIR=$OLD_DIR NEXT=cutover-to-silverbullet.sh"
+else
+ echo "STATUS=cutover unknown (no live dir)"
+fi
diff --git a/scripts/notes/up.sh b/scripts/notes/up.sh
new file mode 100755
index 0000000..0d44305
--- /dev/null
+++ b/scripts/notes/up.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# Bring up koopa-silverbullet / SilverBullet.
+set -euo pipefail
+DIR="${HOME}/koopa-silverbullet"
+cd "${DIR}"
+set -a
+# shellcheck disable=SC1091
+[[ -f .env ]] && source .env
+set +a
+PORT="${HOST_PORT:-9028}"
+podman-compose up -d
+echo "== wait for :${PORT} =="
+ok=0
+for i in $(seq 1 40); do
+ code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 "http://127.0.0.1:${PORT}/" || true)
+ echo "try $i: $code"
+ case "$code" in
+ 2*|3*) ok=1; break ;;
+ esac
+ sleep 3
+done
+[[ "$ok" -eq 1 ]] || { echo "ERROR: no answer on ${PORT}"; podman-compose ps; exit 1; }
+echo "local ok. public (after Caddy+DNS): https://silverbullet.hacktivism.ch/"
diff --git a/scripts/podman-rss.sh b/scripts/podman-rss.sh
new file mode 100644
index 0000000..5e88d6e
--- /dev/null
+++ b/scripts/podman-rss.sh
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+# Human-readable container RSS when rootless podman stats shows 0B / host RAM.
+# Cause on openSUSE: user@.service Delegate= (20-defaults-SUSE.conf) clears
+# memory/cpu controllers → no memory.current. CPU% from podman stats still works.
+# Usage: podman-rss.sh [--json] [NAME_FILTER…]
+set -euo pipefail
+
+JSON=0
+FILTERS=()
+for a in "$@"; do
+ case "$a" in
+ --json|-j) JSON=1 ;;
+ -h|--help)
+ echo "usage: $0 [--json] [NAME_FILTER…]" >&2
+ exit 0
+ ;;
+ *) FILTERS+=("$a") ;;
+ esac
+done
+
+human() {
+ # stdin: kibibytes integer
+ awk '
+ {
+ k=$1+0
+ if (k >= 1048576) printf "%.1f GiB", k/1048576
+ else if (k >= 1024) printf "%.0f MiB", k/1024
+ else printf "%d KiB", k
+ }'
+}
+
+# CPU map: name -> cpu%
+declare -A CPU=()
+while IFS=$'\t' read -r name cpu; do
+ [ -n "$name" ] || continue
+ CPU["$name"]="$cpu"
+done < <(podman stats --no-stream --format '{{.Name}}\t{{.CPUPerc}}' 2>/dev/null || true)
+
+rows=()
+total_kib=0
+while IFS= read -r cid; do
+ [ -n "$cid" ] || continue
+ name=$(podman inspect -f '{{.Name}}' "$cid" 2>/dev/null || true)
+ [ -n "$name" ] || continue
+ if [ ${#FILTERS[@]} -gt 0 ]; then
+ ok=0
+ for f in "${FILTERS[@]}"; do
+ case "$name" in *"$f"*) ok=1; break ;; esac
+ done
+ [ "$ok" -eq 1 ] || continue
+ fi
+ # podman top rss is KiB per process; sum
+ kib=$(podman top "$cid" -o rss 2>/dev/null | awk 'NR>1{s+=$1} END{print s+0}')
+ cpu="${CPU[$name]:-?}"
+ rows+=("${kib}|${name}|${cpu}")
+ total_kib=$((total_kib + kib))
+done < <(podman ps -q)
+
+IFS=$'\n' sorted=($(printf '%s\n' "${rows[@]:-}" | sort -t'|' -k1,1nr))
+unset IFS
+
+if [ "$JSON" -eq 1 ]; then
+ python3 - "$total_kib" "${sorted[@]:-}" <<'PY'
+import json, sys
+total = int(sys.argv[1]) if len(sys.argv) > 1 else 0
+items = []
+for row in sys.argv[2:]:
+ if not row.strip():
+ continue
+ kib, name, cpu = row.split("|", 2)
+ items.append({"name": name, "rss_kib": int(kib), "cpu": cpu})
+print(json.dumps({"ok": True, "total_rss_kib": total, "containers": items}, indent=2))
+PY
+ exit 0
+fi
+
+printf '%-44s %10s %8s\n' "NAME" "RSS" "CPU%"
+printf '%-44s %10s %8s\n' "----" "---" "----"
+for row in "${sorted[@]:-}"; do
+ [ -n "$row" ] || continue
+ kib="${row%%|*}"
+ rest="${row#*|}"
+ name="${rest%%|*}"
+ cpu="${rest##*|}"
+ hum=$(printf '%s' "$kib" | human)
+ printf '%-44s %10s %8s\n' "$name" "$hum" "$cpu"
+done
+printf '%-44s %10s\n' "TOTAL" "$(printf '%s' "$total_kib" | human)"
+echo "# note: RSS via podman top (no memory cgroup); CPU via podman stats" >&2
diff --git a/scripts/upgrade-containers.sh b/scripts/upgrade-containers.sh
new file mode 100755
index 0000000..6a54d6e
--- /dev/null
+++ b/scripts/upgrade-containers.sh
@@ -0,0 +1,654 @@
+#!/usr/bin/env bash
+# Upgrade hernani podman containers on koopa: per-target dry-run (prod-safe),
+# then full backup (compose + mounts/volumes + DB dumps), then apply.
+#
+# Run on koopa as hernani:
+# ~/upgrade-containers.sh # dry-run only (default)
+# ~/upgrade-containers.sh --apply # dry-run → backup → upgrade per target
+# ~/upgrade-containers.sh --apply --images-only
+# ~/upgrade-containers.sh --apply --os-only
+# ~/upgrade-containers.sh --apply --recreate-only
+# ~/upgrade-containers.sh --apply --only koopa-forgejo,koopa-notes
+# ~/upgrade-containers.sh --apply --skip 'taler-hacktivism*,goa-regio-ng'
+# ~/upgrade-containers.sh --apply --taler-debs
+# ~/upgrade-containers.sh --apply --rebuild-local
+# ~/upgrade-containers.sh --apply --include-stopped
+# ~/upgrade-containers.sh --apply --backup-max-gb 80
+# ~/upgrade-containers.sh --apply --no-backup # emergency only
+#
+# Per-target --apply flow:
+# 1) dry-run checks that do not recreate/restart or mutate packages
+# 2) if dry-run fails → skip that target (others continue)
+# 3) full backup under ~/backups/container-upgrades///
+# 4) if backup fails → skip apply for that target
+# 5) pull / recreate / OS upgrade as selected
+set -euo pipefail
+
+APPLY=0
+DO_IMAGES=1
+DO_RECREATE=1
+DO_OS=1
+DO_TALER_DEBS=0
+REBUILD_LOCAL=0
+INCLUDE_STOPPED=0
+DO_BACKUP=1
+BACKUP_MAX_GB=50
+ONLY_RAW=""
+SKIP_RAW=""
+HOME_DIR="${HOME:-/home/hernani}"
+ADMIN_LOG="${ADMIN_LOG:-$HOME_DIR/koopa-admin-log}"
+BACKUP_ROOT="${BACKUP_ROOT:-$HOME_DIR/backups/container-upgrades}"
+RUN_TS="$(date '+%Y%m%d-%H%M%S')"
+BACKUP_RUN_DIR="$BACKUP_ROOT/$RUN_TS"
+
+LOG_TS() { date '+%Y-%m-%dT%H:%M:%S%z'; }
+log() { printf '[%s] %s\n' "$(LOG_TS)" "$*"; }
+run() {
+ if [[ "$APPLY" -eq 1 ]]; then
+ log "RUN: $*"
+ "$@"
+ else
+ log "DRY: $*"
+ fi
+}
+
+usage() {
+ sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'
+ exit "${1:-0}"
+}
+
+match_glob_list() {
+ local name="$1" raw="$2" g
+ [[ -z "$raw" ]] && return 1
+ raw="${raw//,/ }"
+ for g in $raw; do
+ # shellcheck disable=SC2254
+ case "$name" in
+ $g) return 0 ;;
+ esac
+ done
+ return 1
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h|--help) usage 0 ;;
+ --apply) APPLY=1; shift ;;
+ --dry-run) APPLY=0; shift ;;
+ --images-only) DO_IMAGES=1; DO_RECREATE=0; DO_OS=0; shift ;;
+ --os-only) DO_IMAGES=0; DO_RECREATE=0; DO_OS=1; shift ;;
+ --recreate-only) DO_IMAGES=0; DO_RECREATE=1; DO_OS=0; shift ;;
+ --taler-debs) DO_TALER_DEBS=1; shift ;;
+ --rebuild-local) REBUILD_LOCAL=1; shift ;;
+ --include-stopped) INCLUDE_STOPPED=1; shift ;;
+ --no-backup) DO_BACKUP=0; shift ;;
+ --backup-max-gb) BACKUP_MAX_GB="${2:-50}"; shift 2 ;;
+ --backup-max-gb=*) BACKUP_MAX_GB="${1#*=}"; shift ;;
+ --only) ONLY_RAW="${2:-}"; shift 2 ;;
+ --skip) SKIP_RAW="${2:-}"; shift 2 ;;
+ --only=*) ONLY_RAW="${1#*=}"; shift ;;
+ --skip=*) SKIP_RAW="${1#*=}"; shift ;;
+ *) log "ERROR: unknown arg: $1"; usage 2 ;;
+ esac
+done
+
+if [[ "$(id -un)" != "hernani" ]] && [[ "${FORCE_USER:-}" != "1" ]]; then
+ log "WARN: expected user hernani (got $(id -un)); set FORCE_USER=1 to override"
+fi
+
+declare -a NAMES=()
+declare -A IMG=()
+declare -A CDIR=()
+declare -A STATUS=() # ok|dry_fail|backup_fail|upgraded|skipped
+declare -A NOTE=()
+
+list_cmd=(podman ps --format '{{.Names}}|{{.Image}}')
+if [[ "$INCLUDE_STOPPED" -eq 1 ]]; then
+ list_cmd=(podman ps -a --format '{{.Names}}|{{.Image}}')
+fi
+
+while IFS='|' read -r name image; do
+ [[ -z "$name" ]] && continue
+ if [[ ! "$name" =~ ^(koopa-|taler-|goa-|mytops-) ]]; then
+ continue
+ fi
+ if [[ -n "$ONLY_RAW" ]] && ! match_glob_list "$name" "$ONLY_RAW"; then
+ continue
+ fi
+ if match_glob_list "$name" "$SKIP_RAW"; then
+ log "SKIP name=$name (matched --skip)"
+ continue
+ fi
+ NAMES+=("$name")
+ IMG["$name"]="$image"
+ dir=$(podman inspect -f '{{index .Config.Labels "com.docker.compose.project.working_dir"}}' "$name" 2>/dev/null || true)
+ CDIR["$name"]="$dir"
+ STATUS["$name"]="pending"
+done < <("${list_cmd[@]}")
+
+if [[ ${#NAMES[@]} -eq 0 ]]; then
+ log "No matching containers."
+ exit 0
+fi
+
+log "MODE=$([ "$APPLY" -eq 1 ] && echo apply || echo dry-run) images=$DO_IMAGES recreate=$DO_RECREATE os=$DO_OS backup=$DO_BACKUP backup_max_gb=$BACKUP_MAX_GB"
+log "Targets (${#NAMES[@]}): ${NAMES[*]}"
+if [[ "$APPLY" -eq 1 && "$DO_BACKUP" -eq 1 ]]; then
+ mkdir -p "$BACKUP_RUN_DIR"
+ log "BACKUP_DIR=$BACKUP_RUN_DIR"
+fi
+
+is_pinned_tag() {
+ local ref="$1" tag="${1##*:}"
+ [[ "$ref" != *:* ]] && return 1
+ case "$tag" in
+ latest|stable|stable-*|main|master|landing|live|setup|rootless|*-rootless|alpine|*-alpine|[0-9]|[0-9][0-9]|[0-9].*-alpine|[0-9][0-9]-alpine) return 1 ;;
+ esac
+ if [[ "$tag" =~ ^[0-9]+\.[0-9]+ ]]; then
+ return 0
+ fi
+ return 1
+}
+
+# --- prod-safe dry-run for one container (no recreate, no package mutate) ---
+dry_run_one() {
+ local name="$1" ref="${IMG[$name]}" dir="${CDIR[$name]}"
+ local rc=0
+ local tmp
+ tmp=$(mktemp)
+
+ # running?
+ if ! podman inspect "$name" >/dev/null 2>&1; then
+ log "DRYFAIL $name: inspect failed"
+ NOTE["$name"]="inspect failed"
+ rm -f "$tmp"
+ return 1
+ fi
+
+ # image phase checks
+ if [[ "$DO_IMAGES" -eq 1 ]]; then
+ if [[ "$ref" == localhost/* ]]; then
+ if ! podman image exists "$ref" 2>/dev/null; then
+ log "DRYFAIL $name: local image missing $ref"
+ NOTE["$name"]="local image missing"
+ rm -f "$tmp"
+ return 1
+ fi
+ log "DRYOK $name: local image present $ref"
+ else
+ # pull --dry-run is not universal; probe registry without replacing running CT
+ if podman pull --help 2>&1 | grep -q -- '--quiet'; then
+ :
+ fi
+ if ! podman image exists "$ref" 2>/dev/null; then
+ log "DRYWARN $name: image not local yet ($ref) — apply will pull"
+ else
+ log "DRYOK $name: image present locally $ref"
+ fi
+ # non-mutating registry probe when possible
+ if command -v curl >/dev/null 2>&1; then
+ :
+ fi
+ # Try a no-restart pull into local store is mutating cache but not prod CT.
+ # For dry-run we only validate the ref string / inspect local digest.
+ if is_pinned_tag "$ref"; then
+ log "DRYOK $name: pinned tag $ref (compose bump needed to move)"
+ fi
+ fi
+ fi
+
+ # compose config validate (no up)
+ if [[ "$DO_RECREATE" -eq 1 ]]; then
+ if [[ -n "$dir" ]]; then
+ if [[ ! -f "$dir/compose.yml" && ! -f "$dir/compose.yaml" && ! -f "$dir/docker-compose.yml" ]]; then
+ log "DRYFAIL $name: compose dir without compose file: $dir"
+ NOTE["$name"]="no compose file"
+ rm -f "$tmp"
+ return 1
+ fi
+ if ! (cd "$dir" && podman-compose config >/dev/null 2>"$tmp"); then
+ log "DRYFAIL $name: podman-compose config failed"
+ sed -n '1,20p' "$tmp" | while IFS= read -r line; do log " $line"; done
+ NOTE["$name"]="compose config failed"
+ rm -f "$tmp"
+ return 1
+ fi
+ log "DRYOK $name: compose config ok ($dir)"
+ else
+ log "DRYOK $name: no compose label — recreate will be skipped"
+ fi
+ fi
+
+ # OS simulate (no real upgrade)
+ if [[ "$DO_OS" -eq 1 ]]; then
+ if podman ps --format '{{.Names}}' | grep -qx "$name"; then
+ local os_script
+ os_script=$(cat <<'INNER'
+set -e
+export DEBIAN_FRONTEND=noninteractive
+if command -v apt-get >/dev/null 2>&1; then
+ apt-get update -qq
+ apt-get -s -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold upgrade >/tmp/os-dry.out
+ echo OS_DRY=apt ok
+elif command -v apk >/dev/null 2>&1; then
+ apk update >/dev/null
+ apk upgrade --simulate >/tmp/os-dry.out 2>&1 || apk upgrade -s >/tmp/os-dry.out 2>&1 || true
+ echo OS_DRY=apk ok
+elif command -v zypper >/dev/null 2>&1; then
+ zypper -n refresh >/dev/null
+ zypper -n update -D >/tmp/os-dry.out 2>&1 || true
+ echo OS_DRY=zypper ok
+elif command -v microdnf >/dev/null 2>&1; then
+ echo OS_DRY=microdnf none-sim
+elif command -v dnf >/dev/null 2>&1; then
+ dnf -y check-update >/tmp/os-dry.out 2>&1 || true
+ echo OS_DRY=dnf ok
+else
+ echo OS_DRY=none
+fi
+INNER
+)
+ if podman exec -u 0 "$name" bash -lc "$os_script" >"$tmp" 2>&1 \
+ || podman exec -u 0 "$name" sh -lc "$os_script" >"$tmp" 2>&1; then
+ log "DRYOK $name: OS simulate ($(grep -E '^OS_DRY=' "$tmp" | tail -1 || echo ok))"
+ else
+ # many distroless/app images have no pkg manager — treat as ok/skip
+ if grep -q 'OS_DRY=none' "$tmp" 2>/dev/null; then
+ log "DRYOK $name: no package manager (OS skip)"
+ else
+ log "DRYWARN $name: OS simulate soft-fail (will try apply carefully)"
+ sed -n '1,8p' "$tmp" | while IFS= read -r line; do log " $line"; done
+ fi
+ fi
+ else
+ log "DRYOK $name: not running — OS phase will skip"
+ fi
+ fi
+
+ rm -f "$tmp"
+ return "$rc"
+}
+
+# --- full backup with data ---
+backup_one() {
+ local name="$1"
+ local dest="$BACKUP_RUN_DIR/$name"
+ local meta="$dest/META.txt"
+ local max_bytes=$((BACKUP_MAX_GB * 1024 * 1024 * 1024))
+ mkdir -p "$dest/binds" "$dest/volumes" "$dest/db" "$dest/compose"
+
+ {
+ echo "name=$name"
+ echo "image=${IMG[$name]}"
+ echo "compose_dir=${CDIR[$name]:-}"
+ echo "ts=$RUN_TS"
+ } >"$meta"
+
+ podman inspect "$name" >"$dest/inspect.json" 2>/dev/null || true
+
+ # compose project files
+ local dir="${CDIR[$name]}"
+ if [[ -n "$dir" && -d "$dir" ]]; then
+ for f in compose.yml compose.yaml docker-compose.yml docker-compose.yaml .env; do
+ if [[ -f "$dir/$f" ]]; then
+ cp -a "$dir/$f" "$dest/compose/$f"
+ fi
+ done
+ # shallow copy of known config sidecars (not whole tree)
+ for f in Caddyfile Containerfile Dockerfile; do
+ [[ -f "$dir/$f" ]] && cp -a "$dir/$f" "$dest/compose/$f"
+ done
+ fi
+
+ # bind mounts + named volumes from inspect
+ local mounts
+ mounts=$(podman inspect -f '{{range .Mounts}}{{.Type}}|{{.Name}}|{{.Source}}|{{.Destination}}|{{.RW}}{{println}}{{end}}' "$name" 2>/dev/null || true)
+ while IFS='|' read -r mtype mname src dst rw; do
+ [[ -z "$mtype" ]] && continue
+ # skip ephemeral / host tz
+ case "$dst" in
+ /etc/localtime|/etc/timezone|/run|/run/*|/tmp|/tmp/*|/dev|/dev/*|/proc|/proc/*|/sys|/sys/*) continue ;;
+ esac
+ case "$src" in
+ /usr/share/zoneinfo/*|/etc/localtime) continue ;;
+ esac
+
+ if [[ "$mtype" == "bind" ]]; then
+ if [[ ! -e "$src" ]]; then
+ log "BACKUPWARN $name: bind missing $src"
+ continue
+ fi
+ local sz
+ sz=$(du -sb "$src" 2>/dev/null | awk '{print $1}')
+ if [[ -n "$sz" && "$sz" -gt "$max_bytes" ]]; then
+ log "BACKUPWARN $name: skip huge bind $src (${sz}B > ${BACKUP_MAX_GB}G); raise --backup-max-gb"
+ echo "SKIP_HUGE_BIND $src size=$sz" >>"$meta"
+ continue
+ fi
+ local base
+ base=$(echo "$dst" | sed 's#^/##; s#/#_#g')
+ [[ -z "$base" ]] && base="root"
+ log "BACKUP $name: bind $src → binds/${base}.tar.gz"
+ if ! tar -C "$(dirname "$src")" -czf "$dest/binds/${base}.tar.gz" "$(basename "$src")" 2>>"$dest/backup.err"; then
+ log "BACKUPFAIL $name: tar bind $src"
+ NOTE["$name"]="backup bind failed: $src"
+ return 1
+ fi
+ echo "BIND $src -> binds/${base}.tar.gz" >>"$meta"
+ elif [[ "$mtype" == "volume" ]]; then
+ local vname="${mname:-}"
+ if [[ -z "$vname" ]]; then
+ # fallback: export via source path
+ if [[ -d "$src" ]]; then
+ local vbase
+ vbase=$(basename "$(dirname "$src")")
+ log "BACKUP $name: volume-path $src → volumes/${vbase}.tar.gz"
+ if ! tar -C "$src" -czf "$dest/volumes/${vbase}.tar.gz" . 2>>"$dest/backup.err"; then
+ log "BACKUPFAIL $name: tar volume path $src"
+ NOTE["$name"]="backup volume failed"
+ return 1
+ fi
+ echo "VOLPATH $src -> volumes/${vbase}.tar.gz" >>"$meta"
+ fi
+ continue
+ fi
+ local sz
+ sz=$(du -sb "$src" 2>/dev/null | awk '{print $1}')
+ if [[ -n "$sz" && "$sz" -gt "$max_bytes" ]]; then
+ log "BACKUPWARN $name: skip huge volume $vname (${sz}B > ${BACKUP_MAX_GB}G)"
+ echo "SKIP_HUGE_VOL $vname size=$sz" >>"$meta"
+ continue
+ fi
+ log "BACKUP $name: volume $vname → volumes/${vname}.tar"
+ if podman volume export "$vname" -o "$dest/volumes/${vname}.tar" 2>>"$dest/backup.err"; then
+ gzip -f "$dest/volumes/${vname}.tar" 2>/dev/null || true
+ echo "VOLUME $vname -> volumes/${vname}.tar.gz" >>"$meta"
+ else
+ # fallback tar of mount source
+ if [[ -d "$src" ]] && tar -C "$src" -czf "$dest/volumes/${vname}.tar.gz" . 2>>"$dest/backup.err"; then
+ echo "VOLPATH $src -> volumes/${vname}.tar.gz" >>"$meta"
+ else
+ log "BACKUPFAIL $name: volume $vname"
+ NOTE["$name"]="backup volume failed: $vname"
+ return 1
+ fi
+ fi
+ fi
+ done <<<"$mounts"
+
+ # DB dumps when obvious
+ if podman ps --format '{{.Names}}' | grep -qx "$name"; then
+ local img_l
+ img_l=$(echo "${IMG[$name]}" | tr '[:upper:]' '[:lower:]')
+ case "$img_l" in
+ *postgres*|*postgis*)
+ local dbuser
+ dbuser=$(podman exec "$name" printenv POSTGRES_USER 2>/dev/null || echo postgres)
+ local dbname
+ dbname=$(podman exec "$name" printenv POSTGRES_DB 2>/dev/null || echo postgres)
+ log "BACKUP $name: pg_dumpall"
+ if podman exec -u 0 "$name" bash -lc "pg_dumpall -U '$dbuser'" >"$dest/db/pg_dumpall.sql" 2>>"$dest/backup.err"; then
+ gzip -f "$dest/db/pg_dumpall.sql"
+ echo "PG_DUMPALL ok db=$dbname user=$dbuser" >>"$meta"
+ else
+ # single-db fallback
+ if podman exec -u 0 "$name" bash -lc "pg_dump -U '$dbuser' '$dbname'" >"$dest/db/pg_dump.sql" 2>>"$dest/backup.err"; then
+ gzip -f "$dest/db/pg_dump.sql"
+ echo "PG_DUMP ok db=$dbname" >>"$meta"
+ else
+ log "BACKUPWARN $name: postgres dump failed (mounts still saved)"
+ fi
+ fi
+ ;;
+ *mariadb*|*mysql*)
+ local muser mpass mdb
+ muser=$(podman exec "$name" printenv MYSQL_USER 2>/dev/null || podman exec "$name" printenv MARIADB_USER 2>/dev/null || echo root)
+ mpass=$(podman exec "$name" printenv MYSQL_ROOT_PASSWORD 2>/dev/null || podman exec "$name" printenv MARIADB_ROOT_PASSWORD 2>/dev/null || podman exec "$name" printenv MYSQL_PASSWORD 2>/dev/null || true)
+ mdb=$(podman exec "$name" printenv MYSQL_DATABASE 2>/dev/null || podman exec "$name" printenv MARIADB_DATABASE 2>/dev/null || echo "")
+ log "BACKUP $name: mariadb/mysql dump"
+ if [[ -n "$mpass" ]]; then
+ if podman exec -u 0 "$name" bash -lc "MYSQL_PWD='$mpass' mysqldump -u root --all-databases" >"$dest/db/mysql_all.sql" 2>>"$dest/backup.err" \
+ || podman exec -u 0 "$name" bash -lc "MYSQL_PWD='$mpass' mariadb-dump -u root --all-databases" >"$dest/db/mysql_all.sql" 2>>"$dest/backup.err"; then
+ gzip -f "$dest/db/mysql_all.sql"
+ echo "MYSQL_DUMPALL ok" >>"$meta"
+ else
+ log "BACKUPWARN $name: mysql dump failed (mounts still saved)"
+ fi
+ else
+ log "BACKUPWARN $name: no mysql password in env — skip dump"
+ fi
+ ;;
+ esac
+ fi
+
+ echo "BACKUP_OK" >>"$meta"
+ log "BACKUPOK $name → $dest"
+ return 0
+}
+
+# --- apply phases for one container ---
+apply_images_one() {
+ local name="$1" ref="${IMG[$name]}"
+ [[ "$DO_IMAGES" -eq 1 ]] || return 0
+ if [[ "$ref" == localhost/* ]]; then
+ log "LOCAL image=$ref (container=$name) — pull skipped"
+ return 0
+ fi
+ if is_pinned_tag "$ref"; then
+ log "PINNED image=$ref (container=$name) — pull same tag only"
+ fi
+ run podman pull "$ref"
+}
+
+rebuild_one_local() {
+ local name="$1" ref="${IMG[$name]}" dir="${CDIR[$name]}"
+ [[ "$ref" == localhost/* ]] || return 0
+ local cf=""
+ if [[ -n "$dir" && -f "$dir/Containerfile" ]]; then
+ cf="$dir/Containerfile"
+ elif [[ -n "$dir" && -f "$dir/Dockerfile" ]]; then
+ cf="$dir/Dockerfile"
+ fi
+ case "$name" in
+ koopa-nym)
+ [[ -x "$ADMIN_LOG/scripts/nym/build.sh" ]] && { run bash "$ADMIN_LOG/scripts/nym/build.sh"; return; }
+ ;;
+ koopa-tor-relay)
+ if [[ -f "$HOME_DIR/koopa-tor-relay/build/Containerfile" ]]; then
+ run podman build -t localhost/koopa-tor-relay:latest -f "$HOME_DIR/koopa-tor-relay/build/Containerfile" "$HOME_DIR/koopa-tor-relay/build"
+ return
+ fi
+ ;;
+ koopa-paivana)
+ if [[ -f "$dir/Containerfile" ]]; then
+ run podman build -t localhost/koopa-paivana:latest -f "$dir/Containerfile" "$dir"
+ return
+ fi
+ ;;
+ koopa-bbb-front)
+ [[ -x "$ADMIN_LOG/scripts/bbb/build.sh" ]] && { run bash "$ADMIN_LOG/scripts/bbb/build.sh"; return; }
+ ;;
+ koopa-meet-web)
+ [[ -x "$ADMIN_LOG/scripts/meet/build.sh" ]] && { run bash "$ADMIN_LOG/scripts/meet/build.sh"; return; }
+ ;;
+ koopa-galene|koopa-paivana-galene)
+ [[ -x "$ADMIN_LOG/scripts/galene/build.sh" ]] && { run bash "$ADMIN_LOG/scripts/galene/build.sh"; return; }
+ ;;
+ koopa-cryptpad|koopa-cryptpad-edge|koopa-paivana-cryptpad)
+ [[ -x "$ADMIN_LOG/scripts/cryptpad/build.sh" ]] && { run bash "$ADMIN_LOG/scripts/cryptpad/build.sh"; return; }
+ ;;
+ koopa-notes|koopa-silverbullet)
+ [[ -x "$ADMIN_LOG/scripts/notes/build.sh" ]] && { run bash "$ADMIN_LOG/scripts/notes/build.sh"; return; }
+ ;;
+ esac
+ if [[ -n "$cf" ]]; then
+ run podman build -t "$ref" -f "$cf" "$(dirname "$cf")"
+ else
+ log "NO-REBUILD path for $name ($ref)"
+ fi
+}
+
+apply_recreate_one() {
+ local name="$1" dir="${CDIR[$name]}"
+ [[ "$DO_RECREATE" -eq 1 ]] || return 0
+ if [[ -z "$dir" ]]; then
+ log "NO-COMPOSE container=$name — recreate skipped"
+ return 0
+ fi
+ if [[ ! -f "$dir/compose.yml" && ! -f "$dir/compose.yaml" && ! -f "$dir/docker-compose.yml" ]]; then
+ log "WARN: compose dir without compose file: $dir"
+ return 0
+ fi
+ if [[ "$APPLY" -eq 1 ]]; then
+ log "RUN: (cd $dir && podman-compose pull && podman-compose up -d)"
+ (cd "$dir" && podman-compose pull && podman-compose up -d)
+ else
+ log "DRY: (cd $dir && podman-compose pull && podman-compose up -d)"
+ fi
+}
+
+os_upgrade_one() {
+ local name="$1"
+ [[ "$DO_OS" -eq 1 ]] || return 0
+ if ! podman ps --format '{{.Names}}' | grep -qx "$name"; then
+ log "SKIP os name=$name (not running)"
+ return 0
+ fi
+ local script
+ script=$(cat <<'INNER'
+set -e
+export DEBIAN_FRONTEND=noninteractive
+if command -v apt-get >/dev/null 2>&1; then
+ apt-get update -qq
+ apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold upgrade
+ echo OS=apt ok
+elif command -v apk >/dev/null 2>&1; then
+ apk update
+ apk upgrade
+ echo OS=apk ok
+elif command -v zypper >/dev/null 2>&1; then
+ zypper -n refresh
+ zypper -n update
+ echo OS=zypper ok
+elif command -v microdnf >/dev/null 2>&1; then
+ microdnf -y upgrade
+ echo OS=microdnf ok
+elif command -v dnf >/dev/null 2>&1; then
+ dnf -y upgrade
+ echo OS=dnf ok
+else
+ echo OS=none
+ exit 0
+fi
+INNER
+)
+ if [[ "$APPLY" -eq 1 ]]; then
+ log "RUN: podman exec -u 0 $name OS-upgrade"
+ if ! podman exec -u 0 "$name" bash -lc "$script"; then
+ podman exec -u 0 "$name" sh -lc "$script" || log "WARN: OS upgrade failed for $name"
+ fi
+ else
+ log "DRY: podman exec -u 0 $name OS-upgrade"
+ fi
+}
+
+# Deduplicate compose recreate by dir
+declare -A COMPOSE_DONE=()
+declare -A IMAGE_DONE=()
+declare -A BACKUP_DONE=()
+
+process_one() {
+ local name="$1"
+ log "==== target: $name (${IMG[$name]}) ===="
+
+ # 1) always dry-run first (prod-safe)
+ if ! dry_run_one "$name"; then
+ STATUS["$name"]="dry_fail"
+ log "SKIP apply $name (dry-run failed)"
+ return 0
+ fi
+ STATUS["$name"]="dry_ok"
+
+ # global dry-run mode: stop after checks
+ if [[ "$APPLY" -eq 0 ]]; then
+ log "DRY-RUN only for $name (no backup/apply)"
+ return 0
+ fi
+
+ # 2) full backup with data BEFORE any mutate (even after dry-run ok)
+ # every container — do not skip siblings in a compose stack (DB/data)
+ if [[ "$DO_BACKUP" -eq 1 ]]; then
+ if [[ -z "${BACKUP_DONE[$name]:-}" ]]; then
+ if ! backup_one "$name"; then
+ STATUS["$name"]="backup_fail"
+ log "SKIP apply $name (backup failed)"
+ return 0
+ fi
+ BACKUP_DONE["$name"]=1
+ else
+ log "BACKUP skip $name (already backed this container)"
+ fi
+ else
+ log "WARN: --no-backup set; proceeding without backup for $name"
+ fi
+
+ # 3) apply
+ if [[ -z "${IMAGE_DONE[${IMG[$name]}]:-}" ]]; then
+ apply_images_one "$name" || { STATUS["$name"]="apply_fail"; NOTE["$name"]="image pull failed"; return 0; }
+ IMAGE_DONE["${IMG[$name]}"]=1
+ fi
+ if [[ "$REBUILD_LOCAL" -eq 1 ]]; then
+ rebuild_one_local "$name" || true
+ fi
+ local dir="${CDIR[$name]}"
+ if [[ -n "$dir" ]]; then
+ if [[ -z "${COMPOSE_DONE[$dir]:-}" ]]; then
+ apply_recreate_one "$name" || { STATUS["$name"]="apply_fail"; NOTE["$name"]="recreate failed"; return 0; }
+ COMPOSE_DONE["$dir"]=1
+ else
+ log "recreate skip $name (compose dir already done)"
+ fi
+ else
+ apply_recreate_one "$name" || true
+ fi
+ os_upgrade_one "$name" || { STATUS["$name"]="apply_fail"; NOTE["$name"]="os upgrade failed"; return 0; }
+ STATUS["$name"]="upgraded"
+ log "DONE $name"
+}
+
+# --- main loop: per container ---
+for name in "${NAMES[@]}"; do
+ process_one "$name"
+done
+
+# optional Taler GOA debs (only on --apply, after per-CT work)
+if [[ "$DO_TALER_DEBS" -eq 1 ]]; then
+ log "==== phase: taler-debs ===="
+ tscript="$ADMIN_LOG/scripts/taler-shared/upgrade-goa-debs.sh"
+ if [[ "$APPLY" -eq 0 ]]; then
+ log "DRY: bash $tscript"
+ elif [[ -x "$tscript" ]]; then
+ run bash "$tscript"
+ else
+ log "ERROR: missing $tscript"
+ exit 1
+ fi
+fi
+
+log "==== summary ===="
+fail=0
+for name in "${NAMES[@]}"; do
+ st="${STATUS[$name]:-?}"
+ printf ' %-42s %-12s %s %s\n' "$name" "$st" "${IMG[$name]}" "${NOTE[$name]:-}"
+ case "$st" in
+ dry_fail|backup_fail|apply_fail) fail=1 ;;
+ esac
+done
+if [[ "$APPLY" -eq 0 ]]; then
+ log "Dry-run only (default). Re-run with --apply for backup+upgrade per target that passed dry-run."
+fi
+if [[ "$fail" -eq 1 ]]; then
+ log "STATUS=partial_fail"
+ exit 1
+fi
+log "STATUS=ok"
From 5196ac5db8c7dcf4385035e563eb8ab754405273 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hern=C3=A2ni=20Marques?=
Date: Wed, 9 Sep 2026 01:31:47 +0200
Subject: [PATCH 11/69] ops: Forgejo tip=master unify + silverbucket rename
note
---
2026/2026-09-09--forgejo-tip-master-only.md | 31 +++++++++++++++++++++
ops/local-branches-sync.md | 4 +--
2 files changed, 33 insertions(+), 2 deletions(-)
create mode 100644 2026/2026-09-09--forgejo-tip-master-only.md
diff --git a/2026/2026-09-09--forgejo-tip-master-only.md b/2026/2026-09-09--forgejo-tip-master-only.md
new file mode 100644
index 0000000..e8c54f6
--- /dev/null
+++ b/2026/2026-09-09--forgejo-tip-master-only.md
@@ -0,0 +1,31 @@
+# Forgejo HT tip = `master` only (2026-09-09)
+
+## Rule
+
+On `git.hacktivism.ch` every hernani tip is **`master` only** — no dual `main`+`master`, no tip named `main`.
+
+- Instance: `[repository] DEFAULT_BRANCH = master` in Forgejo `app.ini` (live after container restart).
+- Sync/clone/push defaults: `origin/master`.
+- Docs: `llm-configs` → `git-skills.md`, `ESYNC-RULE.md`, `REPOS-INDEX.md`, `forgejo-llm-config.md`.
+
+## Unify (done)
+
+Authenticated inventory: 32 hernani repos. Mutated 15 NEED → verify **OK 32 / BAD 0**.
+
+Notable:
+
+| Case | Repos |
+|------|--------|
+| MAIN_ONLY → master + drop main | `_grok_sync`, admin-logs, `elelmon` (kept `main-legacy`), `silverbucket` (ex-`freenotes` space), `grok-work-log`, `llm-work-output-enc`, `openkknd-ng`, `taler-monitoring`, `taler-wallets-enc`, `vecirex-www`, … |
+| EMPTY | `vecigate-admin-log` (PATCH default only) |
+| DUAL_DIFF (stale master dropped; tip from former main) | `git`, `llm-configs` |
+
+Feature branches left in place where present (`koopa-admin-log`, `taler-monitoring`, …).
+
+## Rename (same day)
+
+Forgejo space repo **`hernani/freenotes` → `hernani/silverbucket`** (tip `master`). Local clone: `~/git/freenotes-space` → origin `silverbucket`. **Not** the FreeNotes tool/apps tree (`~/git/freenotes`).
+
+## Local clones
+
+Retarget checkout/tracking to `origin/master` on magikoopa trees that had `origin`.
diff --git a/ops/local-branches-sync.md b/ops/local-branches-sync.md
index 60be734..0af0ea4 100644
--- a/ops/local-branches-sync.md
+++ b/ops/local-branches-sync.md
@@ -1,6 +1,6 @@
# Local-only copy — /home/hernani/koopa-admin-log
#
-# main → Forgejo origin (ok to push)
+# master → Forgejo origin (ok to push)
# local/* → NEVER push to origin; sync only via home/bundle between kisten
#
# Branches:
@@ -15,7 +15,7 @@
# cd /home/hernani/koopa-admin-log && git fetch /tmp/koopa-local.bundle "+refs/heads/local/*:refs/heads/local/*"
#
# Pull main:
-# git fetch origin && git checkout main && git merge --ff-only origin/main
+# git fetch origin && git checkout master && git merge --ff-only origin/master
## 2026-07-17 clone merge
From b19b1d3cf667b90838fdedf352a50f40ed554f90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hern=C3=A2ni=20Marques?=
Date: Wed, 9 Sep 2026 04:22:13 +0200
Subject: [PATCH 12/69] docs: forgejo/ht default_branch = master (not main)
---
2026/2026-09-07--git-meta-origin-forgejo.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/2026/2026-09-07--git-meta-origin-forgejo.md b/2026/2026-09-07--git-meta-origin-forgejo.md
index d795832..c70e899 100644
--- a/2026/2026-09-07--git-meta-origin-forgejo.md
+++ b/2026/2026-09-07--git-meta-origin-forgejo.md
@@ -7,7 +7,7 @@
| Host | **Forgejo rootless** `https://git.hacktivism.ch/` |
| Repo | **hernani/git** (meta-repo: non-secret globals only) |
| Visibility | **private** (`DEFAULT_PRIVATE` template is public — create with `private:true`) |
-| Default branch | `main` |
+| Default branch | `master` (forgejo/ht SoT — never `main`) |
| SSH clone (FQDN form) | `git@git.hacktivism.ch:hernani/git.git` |
| SSH URL (API form) | `ssh://git@git.hacktivism.ch:9200/hernani/git.git` |
| Local tree | `$HOME/git/` (`PATH[git-root]`) |
@@ -19,7 +19,7 @@ Unauth HTTPS browse of a private repo → **404** (expected).
```bash
cd "$HOME/git"
git remote add origin git@git.hacktivism.ch:hernani/git.git
-git push -u origin main
+git push -u origin master
```
SSH Host `git.hacktivism.ch`: `HostName 127.0.0.1`, `Port 9200`, `User git`, `ProxyJump hernani@koopa`, `IdentityFile ~/.ssh/id_ed25519`.
@@ -40,7 +40,7 @@ podman exec koopa-forgejo gitea admin user generate-access-token \
curl -sS -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
- -d '{"name":"git","private":true,"auto_init":false,"default_branch":"main"}' \
+ -d '{"name":"git","private":true,"auto_init":false,"default_branch":"master"}' \
http://127.0.0.1:9024/api/v1/admin/users/hernani/repos
# expect HTTP 201, full_name=hernani/git
```
From 2eb44c95d9bd2e5e8dd6112d9abf56a67259aa67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hern=C3=A2ni=20Marques?=
Date: Sat, 12 Sep 2026 21:37:05 +0200
Subject: [PATCH 13/69] config: update READMEs and compose files for various
services
---
2026/2026-09-11--forgejo-gitleaks-enc-gate.md | 55 +++++++++++++++++
.../2026-09-12--enc-inventur-rewrite-hooks.md | 60 +++++++++++++++++++
configs/cryptpad/README.md | 2 +-
configs/cryptpad/compose.yml | 4 +-
configs/forgejo/README.md | 4 ++
configs/galene/compose.yml | 4 +-
configs/meet/compose.yml | 4 +-
configs/notes/compose.yml | 4 +-
configs/paivana/README.md | 7 +--
configs/paivana/compose.yml | 16 ++---
configs/paivana/upstream/index.html | 4 +-
configs/ports.md | 8 +--
12 files changed, 145 insertions(+), 27 deletions(-)
create mode 100644 2026/2026-09-11--forgejo-gitleaks-enc-gate.md
create mode 100644 2026/2026-09-12--enc-inventur-rewrite-hooks.md
diff --git a/2026/2026-09-11--forgejo-gitleaks-enc-gate.md b/2026/2026-09-11--forgejo-gitleaks-enc-gate.md
new file mode 100644
index 0000000..8a89620
--- /dev/null
+++ b/2026/2026-09-11--forgejo-gitleaks-enc-gate.md
@@ -0,0 +1,55 @@
+# 2026-09-11 — Forgejo/ht gitleaks + *enc.git Klartext-Sperre (TODO320)
+
+**Status:** live on koopa Forgejo (`koopa-forgejo` / `git.hacktivism.ch`)
+**Freigabe:** Auftrag `TODO=320` this session (host mutation ht)
+**Analog:** firefly/taler `admin-log/firefly/28-gitolite-enc-gitleaks.md` + `hooks/common/pre-receive.d-gitleaks`
+**Tip:** k stays **319** (no tip-advance)
+
+## What
+
+1. **gitleaks** pre-receive on non-`*enc.git` — reject pushes with detected secrets.
+2. **Skip** `*enc.git` for gitleaks (ciphertext / FP).
+3. **`git-crypt-enforce`** on `*enc.git` — require `.gitattributes` + `.git-crypt/`; reject blobs under `filter=git-crypt` missing `\0GITCRYPT` (9-byte header).
+
+## Live paths (koopa)
+
+| Item | Path |
+|------|------|
+| Data root | `~/koopa-forgejo/data` → container `/var/lib/gitea` |
+| Binary | `data/custom/bin/gitleaks` (v8.21.2 linux_x64) |
+| Shared hooks | `data/custom/hooks/pre-receive.d/{gitleaks,git-crypt-enforce}` |
+| Smoke | `data/custom/bin/smoke-todo320.sh` (run **inside** container) |
+| Per-repo | `data/git/repositories/*/*.git/hooks/pre-receive.d/` → symlinks to shared hooks (container paths) |
+| Staging / installer SoT | laptop `PATH[llm-configs]/bin/forgejo-hooks/` + koopa `~/koopa-forgejo/hooks-staging/` |
+
+Installer: `install-hooks-ht.sh` (idempotent; podman; does not restart container).
+After install: **~32** repos linked. **New repos:** re-run installer (custom/hooks alone not relied on for auto-sync into every bare repo).
+
+## Smoke (Done-when a/b/c) — PASSED 2026-09-11
+
+```bash
+podman exec koopa-forgejo /var/lib/gitea/custom/bin/smoke-todo320.sh
+# ALL SMOKES PASSED
+# (a) secret push denied — private-key fixture (not AWS EXAMPLE keys)
+# (b) enc ciphertext allowed
+# (c) enc plaintext denied
+```
+
+Fixes needed during smoke: park incoming objects on `refs/tmp/*` (not `refs/heads/*`, else `rev-list tip --not --branches` is empty); magic check `head -c 9` for `\0GITCRYPT`.
+
+## Rollback
+
+```bash
+# Per repo or all: remove custom hooks from pre-receive.d (keep Forgejo `gitea` hook)
+rm -f ~/koopa-forgejo/data/git/repositories/*/*.git/hooks/pre-receive.d/gitleaks
+rm -f ~/koopa-forgejo/data/git/repositories/*/*.git/hooks/pre-receive.d/git-crypt-enforce
+# Optional: remove shared copies under data/custom/hooks/pre-receive.d/
+```
+
+## Ops notes
+
+- Use **podman** (not docker sock) for `koopa-forgejo`.
+- Symlink targets must be **container** paths (`/var/lib/gitea/custom/...`).
+- Hook logs redact via gitleaks `--redact`; do not echo secrets.
+- Laptop SSH: `Host git.hacktivism.ch` ProxyJump koopa, Port 9200, User git.
+- Local SoT scripts: `$HOME/git/llm/llm-configs/bin/forgejo-hooks/`.
diff --git a/2026/2026-09-12--enc-inventur-rewrite-hooks.md b/2026/2026-09-12--enc-inventur-rewrite-hooks.md
new file mode 100644
index 0000000..980f31d
--- /dev/null
+++ b/2026/2026-09-12--enc-inventur-rewrite-hooks.md
@@ -0,0 +1,60 @@
+# 2026-09-12 — *enc inventur → rewrite → hooks smoke (REPORT ONLY)
+
+**Status:** report only — **no** admin-log / firefly / Taler push this stretch
+**Freigabe:** YES rewrite / force-with-lease / hooks; admin-log = report only
+**Host:** magikoopa (ops) + koopa Forgejo (`koopa-forgejo`)
+**Credit stand:** PCT=0.0 @14:29 renew (authoritative); host `/c` also 0%
+**Inventur park:** `$HOME/git/llm/llm-work-input/tmp/enc-inventur-20260912-STATUS.txt`
+
+## Priority table (tip)
+
+| Priority | Repo | Outcome |
+|----------|------|---------|
+| 1 | `grok-work-log-credit-enc` | **DONE** — was Klartext; git-crypt + orphan tip `877d3e7`; force-with-lease to `hernani/grok-work-log-credit-enc.git` (`e21c1fe`→`877d3e7`). Subject: `git-crypt: encrypt *enc tree (attrs+keys; wipe plain remote next)`. Dirty parked on `backup/pre-crypt-20260912` (`7f00501`). |
+| 2 | `llm-work-output-enc` | **ORIGIN FIXED** — was empty index + wrong origin `hernani/git.git`. Reattached: clone correct `hernani/llm-work-output-enc.git`, unlock, overlay live. Tip `f853969` already `\0GITCRYPT`. `ORIGIN_OK` `LAYOUT_OK`. Local dirty overlay ≈2090 (not pushed this turn). |
+| 2 | `llm-control-data-enc` | **LOCAL CRYPT ONLY** — wrong origin cleared; `git init` + sibling attrs + both GPG users; tip `6aec2c7` (encrypted `status.json`/`stats.json`/sqlite). Origin URL set to `hernani/llm-control-data-enc.git`. **Forgejo repo MISSING** — **no push** until create Freigabe. |
+| 3 | `osaurus-work-log-enc` | **OPEN** — tree at `$HOME/git/llm/llm-work-logs/osaurus-work-log-enc`; no commits, no attrs/crypt, no usable remote. |
+| 3 | `freenotes-data-enc` | **OPEN** — tree at `$HOME/git/src/freenotes-data-enc`; has `.gitattributes` + `.git-crypt`; **NO_REMOTE**. |
+| tip OK | `dot-grok-enc`, `secrets-enc` family, `buerokratie-enc`, `llm-work-db-data-enc`, `grok-work-log-enc`, (+ credit-enc now) | tip encrypted; history-clean handoff list unchanged for remaining. |
+
+## Wrong origin (cleared)
+
+Both control + output previously pointed at **`hernani/git.git`** (ASC board). Cleared. **HALT** remains: never force-push *enc content to `hernani/git.git`.
+
+## Hooks (TODO320) — reinstall + smoke 2026-09-12
+
+Installer SoT: `$HOME/git/llm/llm-configs/bin/forgejo-hooks/`
+Synced → koopa `~/koopa-forgejo/hooks-staging/` → `install-hooks-ht.sh`
+
+```
+OK gitleaks=8.21.2 repos_linked=32
+podman exec koopa-forgejo /var/lib/gitea/custom/bin/smoke-todo320.sh
+ALL SMOKES PASSED
+ (a) secret push denied
+ (b) enc ciphertext allowed
+ (c) enc plaintext denied
+```
+
+`grok-work-log-credit-enc.git` bare has `pre-receive.d/{gitleaks,git-crypt-enforce}` symlinks.
+Bare `hernani/llm-control-data-enc.git` **absent** on Forgejo (matches ls-remote MISSING).
+
+**Re-smoke 2026-09-12T15:37Z:** `ALL SMOKES PASSED` again (a deny secret / b allow ciphertext / c deny plaintext).
+Inventur STATUS close block appended same turn.
+
+## GPG recipients (credit + control)
+
+- `31733E0C598D3A1CF70955D6CB5738652768F7E9` (pep)
+- `8DB88672C5C9FD08373194B6EA1C30038C1D6498` (magikoopa)
+
+## Blocked / needs next Freigabe
+
+1. **Create** Forgejo `hernani/llm-control-data-enc` (private) → then first push of `6aec2c7` + re-run installer for new bare hooks.
+2. Optional: commit/push work-output dirty overlay (2090) after review.
+3. P3: osaurus init+crypt+remote; freenotes add correct remote + push policy.
+4. Admin-log **push** of this file (report-only until Freigabe).
+
+## HALT (unchanged)
+
+- No ladder restart `@START_N=333`
+- No admin-log / firefly / Taler push without Freigabe
+- No force-push to `hernani/git.git`
diff --git a/configs/cryptpad/README.md b/configs/cryptpad/README.md
index 2be16ff..2a91ced 100644
--- a/configs/cryptpad/README.md
+++ b/configs/cryptpad/README.md
@@ -5,7 +5,7 @@
| Stack | [CryptPad](https://cryptpad.org/) |
| Image | `localhost/koopa-cryptpad:cachyos` |
| Live dir | `/home/hernani/koopa-cryptpad/` |
-| Host port | **9030** = Paivana paywall; nginx edge backend **127.0.0.1:19130** → app |
+| Host port | **9030** = Paivana paywall; nginx edge backend **172.17.0.1:19130** → app (pasta host IP; Paivana DEST) |
| App ports (internal) | **3000** HTTP, **3003** websocket |
| Public site | `cryptpad.hacktivism.ch` |
diff --git a/configs/cryptpad/compose.yml b/configs/cryptpad/compose.yml
index 1b37a6f..6452461 100644
--- a/configs/cryptpad/compose.yml
+++ b/configs/cryptpad/compose.yml
@@ -3,7 +3,7 @@
# Build: bash ~/koopa-admin-log/scripts/cryptpad/build.sh → localhost/koopa-cryptpad:cachyos
#
# Public :9030 = koopa-paivana-cryptpad (shared paivana image).
-# Edge backend loopback only — Paivana DEST → 127.0.0.1:19130
+# Edge on pasta host IP — Paivana DEST → 172.17.0.1:19130 (not LAN 0.0.0.0)
# App listens 3000 (http) + 3003 (websocket) inside the net.
services:
cryptpad:
@@ -44,7 +44,7 @@ services:
volumes:
- ./edge.conf:/etc/nginx/conf.d/default.conf:ro
ports:
- - "127.0.0.1:${BACKEND_PORT:-19130}:80"
+ - "172.17.0.1:${BACKEND_PORT:-19130}:80"
labels:
org.hacktivism.service: cryptpad-edge
org.hacktivism.host_port: "9030"
diff --git a/configs/forgejo/README.md b/configs/forgejo/README.md
index a5d9a6a..9359a0b 100644
--- a/configs/forgejo/README.md
+++ b/configs/forgejo/README.md
@@ -58,3 +58,7 @@ Or: Profile → Appearance → theme **hacktivism**, then hard-reload.
### Registration
Public signup is disabled (`DISABLE_REGISTRATION`, no registration button).
+
+### pre-receive: gitleaks + *enc.git gate (TODO320, 2026-09-11)
+
+Live hooks under `~/koopa-forgejo/data/custom/hooks/pre-receive.d/` (gitleaks skip `*enc.git`; `git-crypt-enforce` Klartext-Sperre). Binary `data/custom/bin/gitleaks` v8.21.2. Installer + smoke: laptop `llm-configs/bin/forgejo-hooks/`. Ops note: `2026/2026-09-11--forgejo-gitleaks-enc-gate.md`. New repos → re-run `install-hooks-ht.sh`.
diff --git a/configs/galene/compose.yml b/configs/galene/compose.yml
index d162f36..1919f87 100644
--- a/configs/galene/compose.yml
+++ b/configs/galene/compose.yml
@@ -3,7 +3,7 @@
# Build: bash ~/koopa-admin-log/scripts/galene/build.sh → localhost/koopa-galene:cachyos
#
# Public :9029 = koopa-paivana-galene (shared paivana image).
-# Backend loopback only — Paivana DEST → 127.0.0.1:19129 (HTTP -insecure).
+# Backend on pasta host IP — Paivana DEST → 172.17.0.1:19129 (HTTP -insecure; not LAN 0.0.0.0).
# WebRTC UDP: still published (see README / ports.md)
services:
galene:
@@ -17,7 +17,7 @@ services:
- ./data:/data
- ./entrypoint.sh:/docker-entrypoint.sh:ro
ports:
- - "127.0.0.1:${BACKEND_PORT:-19129}:8443"
+ - "172.17.0.1:${BACKEND_PORT:-19129}:8443"
- "${GALENE_UDP_LO:-10000}-${GALENE_UDP_HI:-10099}:${GALENE_UDP_LO:-10000}-${GALENE_UDP_HI:-10099}/udp"
labels:
org.hacktivism.service: galene
diff --git a/configs/meet/compose.yml b/configs/meet/compose.yml
index 9e2cd52..9aacb27 100644
--- a/configs/meet/compose.yml
+++ b/configs/meet/compose.yml
@@ -1,7 +1,7 @@
# koopa-meet — Jitsi Meet (CachyOS web stub + follow-up signaling/media)
# Live: /home/hernani/koopa-meet/
# Public :9031 = koopa-paivana-meet (shared paivana image).
-# Backend loopback only — Paivana DEST → 127.0.0.1:19131
+# Backend on pasta host IP — Paivana DEST → 172.17.0.1:19131 (not LAN 0.0.0.0)
#
# Next: add prosody / jicofo / jvb services (CachyOS images or pinned binaries).
# Official docker-jitsi-meet is Debian-based — do not pull those as default here.
@@ -16,7 +16,7 @@ services:
environment:
- TZ=Europe/Zurich
ports:
- - "127.0.0.1:${BACKEND_PORT:-19131}:80"
+ - "172.17.0.1:${BACKEND_PORT:-19131}:80"
labels:
org.hacktivism.service: meet
org.hacktivism.backend_port: "${BACKEND_PORT:-19131}"
diff --git a/configs/notes/compose.yml b/configs/notes/compose.yml
index 4a874d4..fec8d42 100644
--- a/configs/notes/compose.yml
+++ b/configs/notes/compose.yml
@@ -3,7 +3,7 @@
# Build: bash ~/koopa-admin-log/scripts/notes/build.sh → localhost/koopa-silverbullet:cachyos
#
# Public :9028 = koopa-paivana-silverbullet (shared paivana image).
-# Backend loopback only — Paivana DEST → 127.0.0.1:19128
+# Backend on pasta host IP — Paivana DEST → 172.17.0.1:19128 (not LAN 0.0.0.0)
services:
notes:
image: localhost/koopa-silverbullet:cachyos
@@ -26,7 +26,7 @@ services:
volumes:
- ./space:/space
ports:
- - "127.0.0.1:${BACKEND_PORT:-19128}:3000"
+ - "172.17.0.1:${BACKEND_PORT:-19128}:3000"
labels:
org.hacktivism.service: silverbullet
org.hacktivism.host_port: "9028"
diff --git a/configs/paivana/README.md b/configs/paivana/README.md
index 8cac2f0..285cec8 100644
--- a/configs/paivana/README.md
+++ b/configs/paivana/README.md
@@ -13,9 +13,9 @@ paywalls on **9028–9031** (Caddy unchanged). App backends listen loopback only
| Live | `/home/hernani/koopa-paivana/` |
| Containers | `koopa-paivana`, `koopa-paivana-{silverbullet,galene,cryptpad,meet}`, `koopa-paivana-upstream` |
| Image | `localhost/koopa-paivana:latest` (built from `Containerfile`); process **uid 1000** `paivana-httpd` |
-| Upstream | portal: `nginxinc/nginx-unprivileged` on **:8080**; apps via host `172.17.0.1:19128–19131` |
+| Upstream | portal: `nginxinc/nginx-unprivileged` on **:8080**; apps via pasta host IP `172.17.0.1:19128–19131` (compose publish, not `127.0.0.1`) |
| Host ports | **9025** portal; **9028/9029/9030/9031** app paywalls → Caddy |
-| Currency | **GOA** (goa-shop); other units need other merchant backends / later instances |
+| Currency | **multi** via goa-shop template `paivana` choices (CHF / FLOODS / GOA / TESTPAYSAN); wallet still filters by held coins |
| Merchant | `https://taler.hacktivism.ch/instances/goa-shop/` |
| Template | `paivana` (`template_type: paivana`, amount **`GOA:42`**, `website_regex: .*`) |
| Access | **`-g`** site-wide: pay once → that hostname free to browse |
@@ -161,8 +161,7 @@ curl -X POST 'https://taler.hacktivism.ch/instances/goa-shop/private/templates'
}'
```
-goa-shop is GOA-only. TESTPAYSAN / FLOODS / talersticker CHF / stage-CHF need
-other `MERCHANT_BACKEND_URL` values (separate Paivana process or later phase).
+goa-shop template `paivana` exposes multi-currency **choices**. Wallet/UI still only offers currencies the wallet holds. Live conf must use `instances/goa-shop/` + matching merchant token (not `goa-demo-*`).
## Docs
diff --git a/configs/paivana/compose.yml b/configs/paivana/compose.yml
index 688c10f..160cfb7 100644
--- a/configs/paivana/compose.yml
+++ b/configs/paivana/compose.yml
@@ -7,8 +7,8 @@
# 9029 → paywall galene
# 9030 → paywall cryptpad
# 9031 → paywall meet (Jitsi web)
-# App backends (loopback only; see app compose):
-# 127.0.0.1:19128 / 19129 / 19130 / 19131
+# App backends on pasta host IP (see app compose; not LAN 0.0.0.0):
+# 172.17.0.1:19128 / 19129 / 19130 / 19131
x-paivana-common: &paivana-common
image: localhost/koopa-paivana:latest
@@ -26,7 +26,7 @@ x-paivana-common: &paivana-common
- ./secrets/paivana-secret:/run/secrets/paivana-secret:ro
labels:
org.hacktivism.managed_by: koopa-admin
- org.hacktivism.currency: GOA
+ org.hacktivism.currency: multi
services:
paivana:
@@ -47,7 +47,7 @@ services:
org.hacktivism.host_port: "9025"
org.hacktivism.site: paivana.hacktivism.ch
org.hacktivism.managed_by: koopa-admin
- org.hacktivism.currency: GOA
+ org.hacktivism.currency: multi
# App paywalls: same image/secrets; BASE/DEST via env. Caddy still hits 9028–9031.
paivana-silverbullet:
@@ -63,7 +63,7 @@ services:
org.hacktivism.host_port: "9028"
org.hacktivism.site: silverbullet.hacktivism.ch
org.hacktivism.managed_by: koopa-admin
- org.hacktivism.currency: GOA
+ org.hacktivism.currency: multi
paivana-galene:
<<: *paivana-common
@@ -78,7 +78,7 @@ services:
org.hacktivism.host_port: "9029"
org.hacktivism.site: galene.hacktivism.ch
org.hacktivism.managed_by: koopa-admin
- org.hacktivism.currency: GOA
+ org.hacktivism.currency: multi
paivana-cryptpad:
<<: *paivana-common
@@ -93,7 +93,7 @@ services:
org.hacktivism.host_port: "9030"
org.hacktivism.site: cryptpad.hacktivism.ch
org.hacktivism.managed_by: koopa-admin
- org.hacktivism.currency: GOA
+ org.hacktivism.currency: multi
paivana-meet:
<<: *paivana-common
@@ -108,7 +108,7 @@ services:
org.hacktivism.host_port: "9031"
org.hacktivism.site: meet.hacktivism.ch
org.hacktivism.managed_by: koopa-admin
- org.hacktivism.currency: GOA
+ org.hacktivism.currency: multi
upstream:
image: docker.io/nginxinc/nginx-unprivileged:1.27-alpine
diff --git a/configs/paivana/upstream/index.html b/configs/paivana/upstream/index.html
index 6cf7a1f..fe9706b 100644
--- a/configs/paivana/upstream/index.html
+++ b/configs/paivana/upstream/index.html
@@ -45,7 +45,7 @@
hacktivism tools
You paid once via GNU Taler / Paivana
- (GOA:42 on this merchant).
+ (template paivana on goa-shop: CHF / FLOODS / GOA / TESTPAYSAN choices).
Site-wide access cookie is set for paivana.hacktivism.ch.
@@ -58,7 +58,7 @@
Notes
One Paivana process = one DESTINATION_BASE_URL → this portal is the gated upstream.
Views/list layer on top of design (local tip; origin bbafc38).
+
Blank relational stub. Frozen Flask build for this tip is separate; this page only shows cascade relation and path root.
+
+
views gap vs dossiers.ngi-0.eu:
+Frozen Flask on this tip exposes project_list / project_detail / dossier_list / blank.
+It does not yet mirror the Next.js category-style views on dossiers.ngi-0.eu.
+This path documents that gap; category-parity views remain open work.