hacktivism.ch already runs Lemmy, Bonfire, Castopod and Decidim. This process asks what must stay readable without a login — and what may require an account to write.
",
+ "
hacktivism.ch betreibt bereits Lemmy, Bonfire, Castopod und Decidim. Dieser Prozess fragt, was ohne Anmeldung lesbar bleiben muss — und wofür ein Konto zum Schreiben reicht.
",
+ "
hacktivism.ch fait déjà tourner Lemmy, Bonfire, Castopod et Decidim. Ce processus demande ce qui doit rester lisible sans compte — et ce qui peut exiger un compte pour écrire.
"
+ ),
+ description: t(
+ "
Reading civic material should not depend on creating an account. Writing (proposals, comments, votes) can.
" \
+ "
Concrete questions:
" \
+ "
Process pages, meetings and last activities stay public.
" \
+ "
No third-party trackers; cookies only what the software needs.
" \
+ "
Which other stacks on hacktivism.ch (Lemmy feeds, Castopod episodes, Bonfire public posts) follow the same rule.
" \
+ "
Anyone can read this process. Sign in only to take part.
",
+ "
Öffentliches Beteiligungsmaterial soll ohne Konto lesbar sein. Schreiben (Vorschläge, Kommentare, Stimmen) kann ein Konto brauchen.
" \
+ "
Konkrete Fragen:
" \
+ "
Prozessseiten, Treffen und letzte Aktivitäten bleiben öffentlich.
" \
+ "
Keine Drittanbieter-Tracker; Cookies nur, was die Software braucht.
" \
+ "
Welche anderen Dienste auf hacktivism.ch (Lemmy, Castopod, Bonfire) derselben Regel folgen.
" \
+ "
Lesen ohne Anmeldung. Anmelden nur zum Mitmachen.
",
+ "
Lire du matériel civique ne doit pas exiger un compte. Écrire (propositions, commentaires, votes) peut le faire.
" \
+ "
Questions concrètes :
" \
+ "
Pages de processus, rencontres et dernières activités restent publiques.
" \
+ "
Pas de traqueurs tiers ; cookies uniquement pour le logiciel.
" \
+ "
Quels autres services sur hacktivism.ch (Lemmy, Castopod, Bonfire) suivent la même règle.
" \
+ "
Lecture sans compte. Connexion seulement pour participer.
ISOC-style public-interest internet: people should inspect how a civic tool works without first handing over an identity.
" \
+ "
On this instance the stock cookie banner is hidden; only essential consent is set. The same idea applies to participation spaces: the text of a process is not a privilege of registered users.
" \
+ "
This page is part of that argument. It is published on purpose.
",
+ "
Internet im öffentlichen Interesse: man soll sehen können, wie ein Beteiligungswerkzeug arbeitet, ohne zuerst eine Identität abzugeben.
" \
+ "
Auf dieser Instanz ist der Vorrats-Cookie-Banner ausgeblendet; es gilt nur essential. Dieselbe Idee gilt für Beteiligungsräume: der Text eines Prozesses ist kein Privileg registrierter Konten.
" \
+ "
Diese Seite ist absichtlich öffentlich.
",
+ "
Internet d'intérêt public : on doit pouvoir voir comment un outil civique fonctionne sans d'abord céder une identité.
" \
+ "
Ici, la bannière cookies par défaut est masquée ; seul l'essentiel est posé. Même idée pour les espaces de participation : le texte d'un processus n'est pas un privilège des comptes.
What is already public on hacktivism.ch (Lemmy, Decidim, Castopod).
" \
+ "
Privacy by default — no tracker banner as a gate.
" \
+ "
Where login is justified (writing, admin).
" \
+ "
Online / public. Signing in is optional.
",
+ "
Öffentliche Traktanden (Lesen ohne Anmeldung):
" \
+ "
Was auf hacktivism.ch schon öffentlich ist (Lemmy, Decidim, Castopod).
" \
+ "
Privatsphäre als Vorgabe — kein Tracker-Banner als Tür.
" \
+ "
Wo ein Login gerechtfertigt ist (Schreiben, Admin).
" \
+ "
Online / öffentlich. Anmelden ist freiwillig.
",
+ "
Ordre du jour public (lecture sans inscription) :
" \
+ "
Ce qui est déjà public sur hacktivism.ch (Lemmy, Decidim, Castopod).
" \
+ "
Vie privée par défaut — pas de bannière traqueur comme porte.
" \
+ "
Où un compte se justifie (écriture, admin).
" \
+ "
En ligne / public. Connexion facultative.
"
+ ),
+ start_time: meeting.start_time || start_at,
+ end_time: meeting.end_time || finish_at,
+ address: "hacktivism.ch",
+ location: t("Online, public stream notes", "Online, öffentliche Notizen", "En ligne, notes publiques"),
+ location_hints: t(
+ "No ticket. Page stays readable if you do not attend.",
+ "Kein Ticket. Die Seite bleibt lesbar, wenn man nicht teilnimmt.",
+ "Pas de billet. La page reste lisible sans y assister."
+ ),
+ type_of_meeting: "in_person",
+ registration_type: "registration_disabled",
+ registrations_enabled: false,
+ private_meeting: false,
+ transparent: true,
+ published_at: meeting.published_at || Time.current,
+ comments_enabled: true
+)
+meeting.save || fail_model!(meeting)
+puts "meeting id=#{meeting.id} published=#{meeting.published_at} start=#{meeting.start_time}"
+
+proposals = Decidim::Component.find_or_initialize_by(
+ participatory_space: process,
+ manifest_name: "proposals"
+)
+proposals.assign_attributes(
+ name: t("Proposals", "Vorschläge", "Propositions"),
+ published_at: proposals.published_at || Time.current,
+ visible: true,
+ weight: 2
+)
+proposals.save || fail_model!(proposals)
+
+proposal = Decidim::Proposals::Proposal.find_or_initialize_by(component: proposals)
+if proposal.new_record? || proposal.title.blank?
+ proposal.title = t(
+ "Keep public processes readable without login",
+ "Öffentliche Prozesse ohne Anmeldung lesbar halten",
+ "Garder les processus publics lisibles sans connexion"
+ )
+ proposal.body = t(
+ "
Decide as a standing rule: published process pages, meetings and this proposal list stay readable without an account. Login is only for creating or endorsing proposals, commenting, and administration.
",
+ "
Als Regel festhalten: veröffentlichte Prozessseiten, Treffen und diese Vorschlagsliste bleiben ohne Konto lesbar. Login nur zum Erstellen oder Unterstützen von Vorschlägen, Kommentieren und für die Administration.
",
+ "
En faire une règle : les pages de processus publiées, les rencontres et cette liste de propositions restent lisibles sans compte. Connexion seulement pour créer ou soutenir, commenter, et pour l'administration.
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 16/74] 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 17/74] 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 18/74] 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.