From dba7cfe57e7c2a2c453f40fe5269018ea65523c6 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Thu, 26 Feb 2026 16:56:16 -0600 Subject: [PATCH] Add timezone date fix for blog-backup --- .obsidian/community-plugins.json | 4 +- .../obsidian42-brat/brat-migrations.json | 5 + .obsidian/plugins/obsidian42-brat/data.json | 24 ++ .obsidian/plugins/obsidian42-brat/main.js | 45 +++ .../plugins/obsidian42-brat/manifest.json | 14 + .obsidian/plugins/obsidian42-brat/styles.css | 152 +++++++++ .obsidian/plugins/qmd-search/data.json | 21 ++ .obsidian/plugins/qmd-search/main.js | 8 + .obsidian/plugins/qmd-search/manifest.json | 1 + .obsidian/plugins/qmd-search/styles.css | 312 ++++++++++++++++++ .obsidian/workspace.json | 48 ++- memory/2026-02-26.md | 24 ++ scripts/skrybe | Bin 0 -> 113928 bytes scripts/skrybe.swift | 202 ++++++++++++ 14 files changed, 846 insertions(+), 14 deletions(-) create mode 100644 .obsidian/plugins/obsidian42-brat/brat-migrations.json create mode 100644 .obsidian/plugins/obsidian42-brat/data.json create mode 100644 .obsidian/plugins/obsidian42-brat/main.js create mode 100644 .obsidian/plugins/obsidian42-brat/manifest.json create mode 100644 .obsidian/plugins/obsidian42-brat/styles.css create mode 100644 .obsidian/plugins/qmd-search/data.json create mode 100644 .obsidian/plugins/qmd-search/main.js create mode 100644 .obsidian/plugins/qmd-search/manifest.json create mode 100644 .obsidian/plugins/qmd-search/styles.css create mode 100755 scripts/skrybe create mode 100644 scripts/skrybe.swift diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json index d3f66fa..ea98e90 100644 --- a/.obsidian/community-plugins.json +++ b/.obsidian/community-plugins.json @@ -1,3 +1,5 @@ [ - "obsidian-git" + "obsidian-git", + "obsidian42-brat", + "qmd-search" ] \ No newline at end of file diff --git a/.obsidian/plugins/obsidian42-brat/brat-migrations.json b/.obsidian/plugins/obsidian42-brat/brat-migrations.json new file mode 100644 index 0000000..e51fb8b --- /dev/null +++ b/.obsidian/plugins/obsidian42-brat/brat-migrations.json @@ -0,0 +1,5 @@ +{ + "appliedMigrations": [ + "tokens-to-secretstorage-v1" + ] +} \ No newline at end of file diff --git a/.obsidian/plugins/obsidian42-brat/data.json b/.obsidian/plugins/obsidian42-brat/data.json new file mode 100644 index 0000000..cf3cbe7 --- /dev/null +++ b/.obsidian/plugins/obsidian42-brat/data.json @@ -0,0 +1,24 @@ +{ + "pluginList": [ + "achekulaev/obsidian-qmd" + ], + "pluginSubListFrozenVersion": [ + { + "repo": "achekulaev/obsidian-qmd", + "version": "" + } + ], + "themesList": [], + "updateAtStartup": true, + "updateThemesAtStartup": true, + "enableAfterInstall": true, + "loggingEnabled": false, + "loggingPath": "BRAT-log", + "loggingVerboseEnabled": false, + "debuggingMode": false, + "notificationsEnabled": true, + "globalTokenName": "", + "personalAccessToken": "", + "selectLatestPluginVersionByDefault": false, + "allowIncompatiblePlugins": false +} \ No newline at end of file diff --git a/.obsidian/plugins/obsidian42-brat/main.js b/.obsidian/plugins/obsidian42-brat/main.js new file mode 100644 index 0000000..932435c --- /dev/null +++ b/.obsidian/plugins/obsidian42-brat/main.js @@ -0,0 +1,45 @@ +"use strict";var tn=Object.create;var pe=Object.defineProperty;var nn=Object.getOwnPropertyDescriptor;var sn=Object.getOwnPropertyNames;var on=Object.getPrototypeOf,rn=Object.prototype.hasOwnProperty;var x=(s,e)=>()=>(e||s((e={exports:{}}).exports,e),e.exports),an=(s,e)=>{for(var t in e)pe(s,t,{get:e[t],enumerable:!0})},Ze=(s,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of sn(e))!rn.call(s,i)&&i!==t&&pe(s,i,{get:()=>e[i],enumerable:!(n=nn(e,i))||n.enumerable});return s};var ln=(s,e,t)=>(t=s!=null?tn(on(s)):{},Ze(e||!s||!s.__esModule?pe(t,"default",{value:s,enumerable:!0}):t,s)),un=s=>Ze(pe({},"__esModule",{value:!0}),s);var $e=x((ps,et)=>{"use strict";var cn=typeof process=="object"&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...s)=>console.error("SEMVER",...s):()=>{};et.exports=cn});var xe=x((ms,tt)=>{"use strict";var gn="2.0.0",dn=Number.MAX_SAFE_INTEGER||9007199254740991,pn=16,mn=250,hn=["major","premajor","minor","preminor","patch","prepatch","prerelease"];tt.exports={MAX_LENGTH:256,MAX_SAFE_COMPONENT_LENGTH:pn,MAX_SAFE_BUILD_LENGTH:mn,MAX_SAFE_INTEGER:dn,RELEASE_TYPES:hn,SEMVER_SPEC_VERSION:gn,FLAG_INCLUDE_PRERELEASE:1,FLAG_LOOSE:2}});var Be=x((F,nt)=>{"use strict";var{MAX_SAFE_COMPONENT_LENGTH:Oe,MAX_SAFE_BUILD_LENGTH:fn,MAX_LENGTH:bn}=xe(),wn=$e();F=nt.exports={};var Tn=F.re=[],En=F.safeRe=[],h=F.src=[],yn=F.safeSrc=[],f=F.t={},vn=0,Fe="[a-zA-Z0-9-]",Pn=[["\\s",1],["\\d",bn],[Fe,fn]],Rn=s=>{for(let[e,t]of Pn)s=s.split(`${e}*`).join(`${e}{0,${t}}`).split(`${e}+`).join(`${e}{1,${t}}`);return s},y=(s,e,t)=>{let n=Rn(e),i=vn++;wn(s,i,e),f[s]=i,h[i]=e,yn[i]=n,Tn[i]=new RegExp(e,t?"g":void 0),En[i]=new RegExp(n,t?"g":void 0)};y("NUMERICIDENTIFIER","0|[1-9]\\d*");y("NUMERICIDENTIFIERLOOSE","\\d+");y("NONNUMERICIDENTIFIER",`\\d*[a-zA-Z-]${Fe}*`);y("MAINVERSION",`(${h[f.NUMERICIDENTIFIER]})\\.(${h[f.NUMERICIDENTIFIER]})\\.(${h[f.NUMERICIDENTIFIER]})`);y("MAINVERSIONLOOSE",`(${h[f.NUMERICIDENTIFIERLOOSE]})\\.(${h[f.NUMERICIDENTIFIERLOOSE]})\\.(${h[f.NUMERICIDENTIFIERLOOSE]})`);y("PRERELEASEIDENTIFIER",`(?:${h[f.NONNUMERICIDENTIFIER]}|${h[f.NUMERICIDENTIFIER]})`);y("PRERELEASEIDENTIFIERLOOSE",`(?:${h[f.NONNUMERICIDENTIFIER]}|${h[f.NUMERICIDENTIFIERLOOSE]})`);y("PRERELEASE",`(?:-(${h[f.PRERELEASEIDENTIFIER]}(?:\\.${h[f.PRERELEASEIDENTIFIER]})*))`);y("PRERELEASELOOSE",`(?:-?(${h[f.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${h[f.PRERELEASEIDENTIFIERLOOSE]})*))`);y("BUILDIDENTIFIER",`${Fe}+`);y("BUILD",`(?:\\+(${h[f.BUILDIDENTIFIER]}(?:\\.${h[f.BUILDIDENTIFIER]})*))`);y("FULLPLAIN",`v?${h[f.MAINVERSION]}${h[f.PRERELEASE]}?${h[f.BUILD]}?`);y("FULL",`^${h[f.FULLPLAIN]}$`);y("LOOSEPLAIN",`[v=\\s]*${h[f.MAINVERSIONLOOSE]}${h[f.PRERELEASELOOSE]}?${h[f.BUILD]}?`);y("LOOSE",`^${h[f.LOOSEPLAIN]}$`);y("GTLT","((?:<|>)?=?)");y("XRANGEIDENTIFIERLOOSE",`${h[f.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);y("XRANGEIDENTIFIER",`${h[f.NUMERICIDENTIFIER]}|x|X|\\*`);y("XRANGEPLAIN",`[v=\\s]*(${h[f.XRANGEIDENTIFIER]})(?:\\.(${h[f.XRANGEIDENTIFIER]})(?:\\.(${h[f.XRANGEIDENTIFIER]})(?:${h[f.PRERELEASE]})?${h[f.BUILD]}?)?)?`);y("XRANGEPLAINLOOSE",`[v=\\s]*(${h[f.XRANGEIDENTIFIERLOOSE]})(?:\\.(${h[f.XRANGEIDENTIFIERLOOSE]})(?:\\.(${h[f.XRANGEIDENTIFIERLOOSE]})(?:${h[f.PRERELEASELOOSE]})?${h[f.BUILD]}?)?)?`);y("XRANGE",`^${h[f.GTLT]}\\s*${h[f.XRANGEPLAIN]}$`);y("XRANGELOOSE",`^${h[f.GTLT]}\\s*${h[f.XRANGEPLAINLOOSE]}$`);y("COERCEPLAIN",`(^|[^\\d])(\\d{1,${Oe}})(?:\\.(\\d{1,${Oe}}))?(?:\\.(\\d{1,${Oe}}))?`);y("COERCE",`${h[f.COERCEPLAIN]}(?:$|[^\\d])`);y("COERCEFULL",h[f.COERCEPLAIN]+`(?:${h[f.PRERELEASE]})?(?:${h[f.BUILD]})?(?:$|[^\\d])`);y("COERCERTL",h[f.COERCE],!0);y("COERCERTLFULL",h[f.COERCEFULL],!0);y("LONETILDE","(?:~>?)");y("TILDETRIM",`(\\s*)${h[f.LONETILDE]}\\s+`,!0);F.tildeTrimReplace="$1~";y("TILDE",`^${h[f.LONETILDE]}${h[f.XRANGEPLAIN]}$`);y("TILDELOOSE",`^${h[f.LONETILDE]}${h[f.XRANGEPLAINLOOSE]}$`);y("LONECARET","(?:\\^)");y("CARETTRIM",`(\\s*)${h[f.LONECARET]}\\s+`,!0);F.caretTrimReplace="$1^";y("CARET",`^${h[f.LONECARET]}${h[f.XRANGEPLAIN]}$`);y("CARETLOOSE",`^${h[f.LONECARET]}${h[f.XRANGEPLAINLOOSE]}$`);y("COMPARATORLOOSE",`^${h[f.GTLT]}\\s*(${h[f.LOOSEPLAIN]})$|^$`);y("COMPARATOR",`^${h[f.GTLT]}\\s*(${h[f.FULLPLAIN]})$|^$`);y("COMPARATORTRIM",`(\\s*)${h[f.GTLT]}\\s*(${h[f.LOOSEPLAIN]}|${h[f.XRANGEPLAIN]})`,!0);F.comparatorTrimReplace="$1$2$3";y("HYPHENRANGE",`^\\s*(${h[f.XRANGEPLAIN]})\\s+-\\s+(${h[f.XRANGEPLAIN]})\\s*$`);y("HYPHENRANGELOOSE",`^\\s*(${h[f.XRANGEPLAINLOOSE]})\\s+-\\s+(${h[f.XRANGEPLAINLOOSE]})\\s*$`);y("STAR","(<|>)?=?\\s*\\*");y("GTE0","^\\s*>=\\s*0\\.0\\.0\\s*$");y("GTE0PRE","^\\s*>=\\s*0\\.0\\.0-0\\s*$")});var it=x((hs,st)=>{"use strict";var In=Object.freeze({loose:!0}),Nn=Object.freeze({}),An=s=>s?typeof s!="object"?In:s:Nn;st.exports=An});var lt=x((fs,at)=>{"use strict";var ot=/^[0-9]+$/,rt=(s,e)=>{if(typeof s=="number"&&typeof e=="number")return s===e?0:srt(e,s);at.exports={compareIdentifiers:rt,rcompareIdentifiers:Cn}});var we=x((bs,ct)=>{"use strict";var me=$e(),{MAX_LENGTH:ut,MAX_SAFE_INTEGER:he}=xe(),{safeRe:fe,t:be}=Be(),Ln=it(),{compareIdentifiers:Me}=lt(),Ve=class s{constructor(e,t){if(t=Ln(t),e instanceof s){if(e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease)return e;e=e.version}else if(typeof e!="string")throw new TypeError(`Invalid version. Must be a string. Got type "${typeof e}".`);if(e.length>ut)throw new TypeError(`version is longer than ${ut} characters`);me("SemVer",e,t),this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease;let n=e.trim().match(t.loose?fe[be.LOOSE]:fe[be.FULL]);if(!n)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+n[1],this.minor=+n[2],this.patch=+n[3],this.major>he||this.major<0)throw new TypeError("Invalid major version");if(this.minor>he||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>he||this.patch<0)throw new TypeError("Invalid patch version");n[4]?this.prerelease=n[4].split(".").map(i=>{if(/^[0-9]+$/.test(i)){let o=+i;if(o>=0&&oe.major?1:this.minore.minor?1:this.patche.patch?1:0}comparePre(e){if(e instanceof s||(e=new s(e,this.options)),this.prerelease.length&&!e.prerelease.length)return-1;if(!this.prerelease.length&&e.prerelease.length)return 1;if(!this.prerelease.length&&!e.prerelease.length)return 0;let t=0;do{let n=this.prerelease[t],i=e.prerelease[t];if(me("prerelease compare",t,n,i),n===void 0&&i===void 0)return 0;if(i===void 0)return 1;if(n===void 0)return-1;if(n===i)continue;return Me(n,i)}while(++t)}compareBuild(e){e instanceof s||(e=new s(e,this.options));let t=0;do{let n=this.build[t],i=e.build[t];if(me("build compare",t,n,i),n===void 0&&i===void 0)return 0;if(i===void 0)return 1;if(n===void 0)return-1;if(n===i)continue;return Me(n,i)}while(++t)}inc(e,t,n){if(e.startsWith("pre")){if(!t&&n===!1)throw new Error("invalid increment argument: identifier is empty");if(t){let i=`-${t}`.match(this.options.loose?fe[be.PRERELEASELOOSE]:fe[be.PRERELEASE]);if(!i||i[1]!==t)throw new Error(`invalid identifier: ${t}`)}}switch(e){case"premajor":this.prerelease.length=0,this.patch=0,this.minor=0,this.major++,this.inc("pre",t,n);break;case"preminor":this.prerelease.length=0,this.patch=0,this.minor++,this.inc("pre",t,n);break;case"prepatch":this.prerelease.length=0,this.inc("patch",t,n),this.inc("pre",t,n);break;case"prerelease":this.prerelease.length===0&&this.inc("patch",t,n),this.inc("pre",t,n);break;case"release":if(this.prerelease.length===0)throw new Error(`version ${this.raw} is not a prerelease`);this.prerelease.length=0;break;case"major":(this.minor!==0||this.patch!==0||this.prerelease.length===0)&&this.major++,this.minor=0,this.patch=0,this.prerelease=[];break;case"minor":(this.patch!==0||this.prerelease.length===0)&&this.minor++,this.patch=0,this.prerelease=[];break;case"patch":this.prerelease.length===0&&this.patch++,this.prerelease=[];break;case"pre":{let i=Number(n)?1:0;if(this.prerelease.length===0)this.prerelease=[i];else{let o=this.prerelease.length;for(;--o>=0;)typeof this.prerelease[o]=="number"&&(this.prerelease[o]++,o=-2);if(o===-1){if(t===this.prerelease.join(".")&&n===!1)throw new Error("invalid increment argument: identifier already exists");this.prerelease.push(i)}}if(t){let o=[t,i];n===!1&&(o=[t]),Me(this.prerelease[0],t)===0?isNaN(this.prerelease[1])&&(this.prerelease=o):this.prerelease=o}break}default:throw new Error(`invalid increment argument: ${e}`)}return this.raw=this.format(),this.build.length&&(this.raw+=`+${this.build.join(".")}`),this}};ct.exports=Ve});var Ue=x((ws,dt)=>{"use strict";var gt=we(),kn=(s,e,t)=>new gt(s,t).compare(new gt(e,t));dt.exports=kn});var ht=x((Ts,mt)=>{"use strict";var pt=we(),Sn=(s,e,t=!1)=>{if(s instanceof pt)return s;try{return new pt(s,e)}catch(n){if(!t)return null;throw n}};mt.exports=Sn});var _e=x((Es,ft)=>{"use strict";var Dn=we(),$n=ht(),{safeRe:Te,t:Ee}=Be(),xn=(s,e)=>{if(s instanceof Dn)return s;if(typeof s=="number"&&(s=String(s)),typeof s!="string")return null;e=e||{};let t=null;if(!e.rtl)t=s.match(e.includePrerelease?Te[Ee.COERCEFULL]:Te[Ee.COERCE]);else{let u=e.includePrerelease?Te[Ee.COERCERTLFULL]:Te[Ee.COERCERTL],g;for(;(g=u.exec(s))&&(!t||t.index+t[0].length!==s.length);)(!t||g.index+g[0].length!==t.index+t[0].length)&&(t=g),u.lastIndex=g.index+g[1].length+g[2].length;u.lastIndex=-1}if(t===null)return null;let n=t[2],i=t[3]||"0",o=t[4]||"0",a=e.includePrerelease&&t[5]?`-${t[5]}`:"",c=e.includePrerelease&&t[6]?`+${t[6]}`:"";return $n(`${n}.${i}.${o}${a}${c}`,e)};ft.exports=xn});var Kt=x(v=>{"use strict";Object.defineProperty(v,"__esModule",{value:!0});var I=require("obsidian"),qe="YYYY-MM-DD",Ye="gggg-[W]ww",Mt="YYYY-MM",Vt="YYYY-[Q]Q",Ut="YYYY";function oe(s){var t,n;let e=window.app.plugins.getPlugin("periodic-notes");return e&&((n=(t=e.settings)==null?void 0:t[s])==null?void 0:n.enabled)}function re(){var s,e,t,n;try{let{internalPlugins:i,plugins:o}=window.app;if(oe("daily")){let{format:g,folder:m,template:l}=((e=(s=o.getPlugin("periodic-notes"))==null?void 0:s.settings)==null?void 0:e.daily)||{};return{format:g||qe,folder:(m==null?void 0:m.trim())||"",template:(l==null?void 0:l.trim())||""}}let{folder:a,format:c,template:u}=((n=(t=i.getPluginById("daily-notes"))==null?void 0:t.instance)==null?void 0:n.options)||{};return{format:c||qe,folder:(a==null?void 0:a.trim())||"",template:(u==null?void 0:u.trim())||""}}catch(i){console.info("No custom daily note settings found!",i)}}function ae(){var s,e,t,n,i,o,a;try{let c=window.app.plugins,u=(s=c.getPlugin("calendar"))==null?void 0:s.options,g=(t=(e=c.getPlugin("periodic-notes"))==null?void 0:e.settings)==null?void 0:t.weekly;if(oe("weekly"))return{format:g.format||Ye,folder:((n=g.folder)==null?void 0:n.trim())||"",template:((i=g.template)==null?void 0:i.trim())||""};let m=u||{};return{format:m.weeklyNoteFormat||Ye,folder:((o=m.weeklyNoteFolder)==null?void 0:o.trim())||"",template:((a=m.weeklyNoteTemplate)==null?void 0:a.trim())||""}}catch(c){console.info("No custom weekly note settings found!",c)}}function le(){var e,t,n,i;let s=window.app.plugins;try{let o=oe("monthly")&&((t=(e=s.getPlugin("periodic-notes"))==null?void 0:e.settings)==null?void 0:t.monthly)||{};return{format:o.format||Mt,folder:((n=o.folder)==null?void 0:n.trim())||"",template:((i=o.template)==null?void 0:i.trim())||""}}catch(o){console.info("No custom monthly note settings found!",o)}}function ue(){var e,t,n,i;let s=window.app.plugins;try{let o=oe("quarterly")&&((t=(e=s.getPlugin("periodic-notes"))==null?void 0:e.settings)==null?void 0:t.quarterly)||{};return{format:o.format||Vt,folder:((n=o.folder)==null?void 0:n.trim())||"",template:((i=o.template)==null?void 0:i.trim())||""}}catch(o){console.info("No custom quarterly note settings found!",o)}}function ce(){var e,t,n,i;let s=window.app.plugins;try{let o=oe("yearly")&&((t=(e=s.getPlugin("periodic-notes"))==null?void 0:e.settings)==null?void 0:t.yearly)||{};return{format:o.format||Ut,folder:((n=o.folder)==null?void 0:n.trim())||"",template:((i=o.template)==null?void 0:i.trim())||""}}catch(o){console.info("No custom yearly note settings found!",o)}}function _t(...s){let e=[];for(let n=0,i=s.length;n{let C=n(),N=s.clone().set({hour:C.get("hour"),minute:C.get("minute"),second:C.get("second")});return b&&N.add(parseInt(w,10),p),T?N.format(T.substring(1).trim()):N.format(o)}).replace(/{{\s*yesterday\s*}}/gi,s.clone().subtract(1,"day").format(o)).replace(/{{\s*tomorrow\s*}}/gi,s.clone().add(1,"d").format(o)));return e.foldManager.save(l,u),l}catch(l){console.error(`Failed to create file: '${m}'`,l),new I.Notice("Unable to create new file.")}}function qn(s,e){var t;return(t=e[$(s,"day")])!=null?t:null}function Yn(){let{vault:s}=window.app,{folder:e}=re(),t=s.getAbstractFileByPath(I.normalizePath(e));if(!t)throw new Xe("Failed to find daily notes folder");let n={};return I.Vault.recurseChildren(t,i=>{if(i instanceof I.TFile){let o=X(i,"day");if(o){let a=$(o,"day");n[a]=i}}}),n}var We=class extends Error{};function Xn(){let{moment:s}=window,e=s.localeData()._week.dow,t=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"];for(;e;)t.push(t.shift()),e--;return t}function Wn(s){return Xn().indexOf(s.toLowerCase())}async function zt(s){let{vault:e}=window.app,{template:t,format:n,folder:i}=ae(),[o,a]=await Y(t),c=s.format(n),u=await ge(i,c);try{let g=await e.create(u,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(m,l,r,d,b,w)=>{let p=window.moment(),T=s.clone().set({hour:p.get("hour"),minute:p.get("minute"),second:p.get("second")});return r&&T.add(parseInt(d,10),b),w?T.format(w.substring(1).trim()):T.format(n)}).replace(/{{\s*title\s*}}/gi,c).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*:(.*?)}}/gi,(m,l,r)=>{let d=Wn(l);return s.weekday(d).format(r.trim())}));return window.app.foldManager.save(g,a),g}catch(g){console.error(`Failed to create file: '${u}'`,g),new I.Notice("Unable to create new file.")}}function Jn(s,e){var t;return(t=e[$(s,"week")])!=null?t:null}function Kn(){let s={};if(!Yt())return s;let{vault:e}=window.app,{folder:t}=ae(),n=e.getAbstractFileByPath(I.normalizePath(t));if(!n)throw new We("Failed to find weekly notes folder");return I.Vault.recurseChildren(n,i=>{if(i instanceof I.TFile){let o=X(i,"week");if(o){let a=$(o,"week");s[a]=i}}}),s}var Je=class extends Error{};async function qt(s){let{vault:e}=window.app,{template:t,format:n,folder:i}=le(),[o,a]=await Y(t),c=s.format(n),u=await ge(i,c);try{let g=await e.create(u,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(m,l,r,d,b,w)=>{let p=window.moment(),T=s.clone().set({hour:p.get("hour"),minute:p.get("minute"),second:p.get("second")});return r&&T.add(parseInt(d,10),b),w?T.format(w.substring(1).trim()):T.format(n)}).replace(/{{\s*date\s*}}/gi,c).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi,c));return window.app.foldManager.save(g,a),g}catch(g){console.error(`Failed to create file: '${u}'`,g),new I.Notice("Unable to create new file.")}}function Qn(s,e){var t;return(t=e[$(s,"month")])!=null?t:null}function Zn(){let s={};if(!Xt())return s;let{vault:e}=window.app,{folder:t}=le(),n=e.getAbstractFileByPath(I.normalizePath(t));if(!n)throw new Je("Failed to find monthly notes folder");return I.Vault.recurseChildren(n,i=>{if(i instanceof I.TFile){let o=X(i,"month");if(o){let a=$(o,"month");s[a]=i}}}),s}var Ke=class extends Error{};async function es(s){let{vault:e}=window.app,{template:t,format:n,folder:i}=ue(),[o,a]=await Y(t),c=s.format(n),u=await ge(i,c);try{let g=await e.create(u,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(m,l,r,d,b,w)=>{let p=window.moment(),T=s.clone().set({hour:p.get("hour"),minute:p.get("minute"),second:p.get("second")});return r&&T.add(parseInt(d,10),b),w?T.format(w.substring(1).trim()):T.format(n)}).replace(/{{\s*date\s*}}/gi,c).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi,c));return window.app.foldManager.save(g,a),g}catch(g){console.error(`Failed to create file: '${u}'`,g),new I.Notice("Unable to create new file.")}}function ts(s,e){var t;return(t=e[$(s,"quarter")])!=null?t:null}function ns(){let s={};if(!Wt())return s;let{vault:e}=window.app,{folder:t}=ue(),n=e.getAbstractFileByPath(I.normalizePath(t));if(!n)throw new Ke("Failed to find quarterly notes folder");return I.Vault.recurseChildren(n,i=>{if(i instanceof I.TFile){let o=X(i,"quarter");if(o){let a=$(o,"quarter");s[a]=i}}}),s}var Qe=class extends Error{};async function ss(s){let{vault:e}=window.app,{template:t,format:n,folder:i}=ce(),[o,a]=await Y(t),c=s.format(n),u=await ge(i,c);try{let g=await e.create(u,o.replace(/{{\s*(date|time)\s*(([+-]\d+)([yqmwdhs]))?\s*(:.+?)?}}/gi,(m,l,r,d,b,w)=>{let p=window.moment(),T=s.clone().set({hour:p.get("hour"),minute:p.get("minute"),second:p.get("second")});return r&&T.add(parseInt(d,10),b),w?T.format(w.substring(1).trim()):T.format(n)}).replace(/{{\s*date\s*}}/gi,c).replace(/{{\s*time\s*}}/gi,window.moment().format("HH:mm")).replace(/{{\s*title\s*}}/gi,c));return window.app.foldManager.save(g,a),g}catch(g){console.error(`Failed to create file: '${u}'`,g),new I.Notice("Unable to create new file.")}}function is(s,e){var t;return(t=e[$(s,"year")])!=null?t:null}function os(){let s={};if(!Jt())return s;let{vault:e}=window.app,{folder:t}=ce(),n=e.getAbstractFileByPath(I.normalizePath(t));if(!n)throw new Qe("Failed to find yearly notes folder");return I.Vault.recurseChildren(n,i=>{if(i instanceof I.TFile){let o=X(i,"year");if(o){let a=$(o,"year");s[a]=i}}}),s}function rs(){var n,i;let{app:s}=window,e=s.internalPlugins.plugins["daily-notes"];if(e&&e.enabled)return!0;let t=s.plugins.getPlugin("periodic-notes");return t&&((i=(n=t.settings)==null?void 0:n.daily)==null?void 0:i.enabled)}function Yt(){var t,n;let{app:s}=window;if(s.plugins.getPlugin("calendar"))return!0;let e=s.plugins.getPlugin("periodic-notes");return e&&((n=(t=e.settings)==null?void 0:t.weekly)==null?void 0:n.enabled)}function Xt(){var t,n;let{app:s}=window,e=s.plugins.getPlugin("periodic-notes");return e&&((n=(t=e.settings)==null?void 0:t.monthly)==null?void 0:n.enabled)}function Wt(){var t,n;let{app:s}=window,e=s.plugins.getPlugin("periodic-notes");return e&&((n=(t=e.settings)==null?void 0:t.quarterly)==null?void 0:n.enabled)}function Jt(){var t,n;let{app:s}=window,e=s.plugins.getPlugin("periodic-notes");return e&&((n=(t=e.settings)==null?void 0:t.yearly)==null?void 0:n.enabled)}function as(s){let e={day:re,week:ae,month:le,quarter:ue,year:ce}[s];return e()}function ls(s,e){return{day:jt,month:qt,week:zt}[s](e)}v.DEFAULT_DAILY_NOTE_FORMAT=qe;v.DEFAULT_MONTHLY_NOTE_FORMAT=Mt;v.DEFAULT_QUARTERLY_NOTE_FORMAT=Vt;v.DEFAULT_WEEKLY_NOTE_FORMAT=Ye;v.DEFAULT_YEARLY_NOTE_FORMAT=Ut;v.appHasDailyNotesPluginLoaded=rs;v.appHasMonthlyNotesPluginLoaded=Xt;v.appHasQuarterlyNotesPluginLoaded=Wt;v.appHasWeeklyNotesPluginLoaded=Yt;v.appHasYearlyNotesPluginLoaded=Jt;v.createDailyNote=jt;v.createMonthlyNote=qt;v.createPeriodicNote=ls;v.createQuarterlyNote=es;v.createWeeklyNote=zt;v.createYearlyNote=ss;v.getAllDailyNotes=Yn;v.getAllMonthlyNotes=Zn;v.getAllQuarterlyNotes=ns;v.getAllWeeklyNotes=Kn;v.getAllYearlyNotes=os;v.getDailyNote=qn;v.getDailyNoteSettings=re;v.getDateFromFile=X;v.getDateFromPath=zn;v.getDateUID=$;v.getMonthlyNote=Qn;v.getMonthlyNoteSettings=le;v.getPeriodicNoteSettings=as;v.getQuarterlyNote=ts;v.getQuarterlyNoteSettings=ue;v.getTemplateInfo=Y;v.getWeeklyNote=Jn;v.getWeeklyNoteSettings=ae;v.getYearlyNote=is;v.getYearlyNoteSettings=ce});var us={};an(us,{default:()=>ke});module.exports=un(us);var en=require("obsidian");var k=require("obsidian");var J=require("obsidian"),Se=class extends J.Modal{constructor(t,n){super(t.app);this.resolve=n;this.isConfirmed=!1;let i={app:t.app,cancelButtonText:"Cancel",cssClass:"",message:t.message,okButtonText:"OK",title:""};this.options={...i,...t},this.containerEl.addClass("confirm-modal")}onClose(){super.onClose(),this.resolve(this.isConfirmed)}onOpen(){super.onOpen(),this.titleEl.setText(this.options.title),this.contentEl.createEl("p",{text:this.options.message});let t=new J.ButtonComponent(this.contentEl);t.setClass("ok-button"),t.setButtonText(this.options.okButtonText),t.setCta(),t.onClick(()=>{this.isConfirmed=!0,this.close()});let n=new J.ButtonComponent(this.contentEl);n.setButtonText(this.options.cancelButtonText),n.onClick(this.close.bind(this))}};async function De(s){return await new Promise(e=>{new Se(s,e).open()})}var D=class extends Error{constructor(t,n,i,o){let a=Math.ceil((i-Math.floor(Date.now()/1e3))/60);super(`GitHub API rate limit exceeded. Reset in ${a} minutes.`);this.limit=t;this.remaining=n;this.reset=i;this.requestUrl=o}getMinutesToReset(){return Math.ceil((this.reset-Math.floor(Date.now()/1e3))/60)}},O=class extends Error{constructor(e){var n,i;super(`GitHub API error ${e}: ${e.message}`),this.message=e.message;let t=e;this.status=(n=t.status)!=null?n:400,this.headers=(i=t.headers)!=null?i:{},this.name="GitHubResponseError"}};var V=require("obsidian");var On=Ue(),bt=_e();var ye=s=>{let e=s.replace(/https?:\/\/github\.com\//i,"");return e.endsWith("/")&&(e=e.slice(0,-1)),e.toLowerCase().endsWith(".git")&&(e=e.slice(0,-4)),e},wt=["ghp_","github_pat_"],Fn=/^(gh[ps]_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})$/,Tt=async(s,e)=>{var o,a,c,u,g,m,l,r,d,b,w;let t=["repo","public_repo","metadata=read"],n=wt.some(p=>s.toLowerCase().startsWith(p.toLowerCase())),i=Fn.test(s);if(!n||!i)return{validToken:!1,currentScopes:[],acceptedScopes:[],acceptedPermissions:[],expirationDate:null,rateLimit:{limit:0,remaining:0,reset:0,resource:"",used:0},error:{type:n?"invalid_format":"invalid_prefix",message:"Invalid token format",details:{validPrefixes:wt}}};try{let p=Date.now()%1e3,T=e||`user${p}/repo${p%100}`;if(await ve({url:`https://api.github.com/repos/${T}`,headers:{Authorization:`Token ${s}`,Accept:"application/vnd.github.v3+json"}}),e)return{validToken:!0,currentScopes:[],acceptedScopes:[],acceptedPermissions:[],expirationDate:null,rateLimit:{limit:0,remaining:0,reset:0,resource:"",used:0},error:{type:"none",message:"No error",details:{}}};throw new Error("Expected request to fail")}catch(p){if(!(p instanceof O))throw p;let T=p.headers;if(!T)throw new Error("No headers in GitHub response");let C=T["github-authentication-token-expiration"],N=C?new Date(C):null,R=N&&!Number.isNaN(N.getTime())?N.toISOString():null,P={validToken:!1,currentScopes:(a=(o=T["x-oauth-scopes"])==null?void 0:o.split(", "))!=null?a:[],acceptedScopes:(u=(c=T["x-accepted-oauth-scopes"])==null?void 0:c.split(", "))!=null?u:[],acceptedPermissions:(m=(g=T["x-accepted-github-permissions"])==null?void 0:g.split(", "))!=null?m:[],expirationDate:R,rateLimit:{limit:Number.parseInt((l=T["x-ratelimit-limit"])!=null?l:"0",10),remaining:Number.parseInt((r=T["x-ratelimit-remaining"])!=null?r:"0",10),reset:Number.parseInt((d=T["x-ratelimit-reset"])!=null?d:"0",10),resource:(b=T["x-ratelimit-resource"])!=null?b:"",used:Number.parseInt((w=T["x-ratelimit-used"])!=null?w:"0",10)},error:{type:"none",message:"No error",details:{}}};return P.expirationDate&&new Date(P.expirationDate)t.includes(M))||P.acceptedPermissions.some(M=>t.includes(M))?(P.validToken=p.status===404,P):(P.error={type:"insufficient_scope",message:"Token lacks required scopes. Check documentation for requirements.",details:{currentScopes:[...P.acceptedScopes,...P.acceptedPermissions]}},P)}},He=async(s,e=!0,t="")=>{let n=`https://api.github.com/repos/${s}`;try{return(await ve({url:n,headers:t?{Authorization:`Token ${t}`}:{}})).json.private}catch(i){if(i instanceof D)throw i;return e&&console.log("error in isPrivateRepo",n,i),!1}},Et=async(s,e=!0,t="")=>{let n=`https://api.github.com/repos/${s}/releases`;try{return(await ve({url:`${n}?per_page=100`,headers:t?{Authorization:`Token ${t}`}:{}})).json.map(a=>({version:a.tag_name,prerelease:a.prerelease}))}catch(i){if(i instanceof D||i instanceof O)throw i;return e&&console.log("Error in fetchReleaseVersions",n,i),null}},K=async(s,e,t=!0,n=!1,i="")=>{try{let o=s.assets.find(g=>g.name===e);if(!o)return null;let a={Accept:"application/octet-stream"};n&&i&&(a.Authorization=`Token ${i}`);let c=n?o.url:o.browser_download_url,u=await(0,V.requestUrl)({url:c,headers:a});return u.status!==200?null:u.text}catch(o){if(o instanceof D)throw o;return t&&console.log("error in grabReleaseFileFromRepository",s,o),null}},yt=async(s=!0)=>{let e="https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json";try{let t=await(0,V.requestUrl)({url:e});return t.status===404?null:t.json}catch(t){return s&&console.log("error in grabCommmunityPluginList",t),null}},vt=async(s=!0)=>{let e="https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-css-themes.json";try{let t=await(0,V.requestUrl)({url:e});return t.status===404?null:t.json}catch(t){return s&&console.log("error in grabCommmunityThemesList",t),null}},G=async(s,e=!1,t=!1)=>{let n=`https://raw.githubusercontent.com/${s}/HEAD/theme${e?"-beta":""}.css`;try{let i=await(0,V.requestUrl)({url:n});return i.status===404?null:i.text}catch(i){return t&&console.log("error in grabCommmunityThemeCssFile",i),null}},Pt=async(s,e=!0)=>{let t=`https://raw.githubusercontent.com/${s}/HEAD/manifest.json`;try{let n=await(0,V.requestUrl)({url:t});return n.status===404?null:n.text}catch(n){return e&&console.log("error in grabCommmunityThemeManifestFile",n),null}},Bn=s=>{let e=0;for(let t=0;tBn(s).toString(),Z=async(s,e,t)=>{let n=await G(s,e,t);return n?Q(n):"0"},Mn=async(s,e,t=!0)=>{let n=`https://api.github.com/repos/${s}/commits?path=${e}&page=1&per_page=1`;try{let i=await(0,V.requestUrl)({url:n});return i.status===404?null:i.json}catch(i){return t&&console.log("error in grabLastCommitInfoForAFile",i),null}},Rt=async(s,e)=>{var n;let t=await Mn(s,e);return t&&t.length>0&&((n=t[0].commit.committer)!=null&&n.date)?t[0].commit.committer.date:""},Ge=async(s,e,t=!1,n=!1,i=!1,o)=>{var a;try{let c=e&&e!=="latest"?`https://api.github.com/repos/${s}/releases/tags/${e}`:`https://api.github.com/repos/${s}/releases`,u={Accept:"application/vnd.github.v3+json"};(i&&o||o)&&(u.Authorization=`Token ${o}`);let g=await ve({url:c,headers:u});if(g.status===404)return null;let m=e&&e!=="latest"?[g.json]:g.json;return n&&console.log(`grabReleaseFromRepository for ${s}:`,m),(a=m.sort((l,r)=>{try{let d=bt(l.tag_name,{includePrerelease:!0,loose:!0}),b=bt(r.tag_name,{includePrerelease:!0,loose:!0});return On(b,d)}catch(d){let b=new Date(l.published_at).getTime(),w=new Date(r.published_at).getTime();return bw?-1:0}}).filter(l=>t||!l.prerelease)[0])!=null?a:null}catch(c){throw n&&console.log(`Error in grabReleaseFromRepository for ${s}:`,c),c}},ve=async(s,e)=>{let t=0,n=0,i=0;s.headers={...s.headers,"User-Agent":"Obsidian/BRAT-Plugin"};try{return await(0,V.requestUrl)(s)}catch(o){let a=new O(o),c=a.headers;if(c&&(t=Number.parseInt(c["x-ratelimit-limit"],10),n=Number.parseInt(c["x-ratelimit-remaining"],10),i=Number.parseInt(c["x-ratelimit-reset"],10)),a.status===403&&n===0){let u=new D(t,n,i,s.url);throw e&&console.error(`BRAT +GitHub API rate limit exceeded:`,` +Request: ${u.requestUrl}`,` +Rate limits - Remaining: ${u.remaining}`,` +Reset in: ${u.getMinutesToReset()} minutes`),u}throw e&&console.log("GitHub request failed:",o),a}};var je={pluginList:[],pluginSubListFrozenVersion:[],themesList:[],updateAtStartup:!0,updateThemesAtStartup:!0,enableAfterInstall:!0,loggingEnabled:!1,loggingPath:"BRAT-log",loggingVerboseEnabled:!1,debuggingMode:!1,notificationsEnabled:!0,globalTokenName:"",personalAccessToken:"",selectLatestPluginVersionByDefault:!1,allowIncompatiblePlugins:!1};function It(s,e,t="latest",n=!1,i=""){let o=!1;s.settings.pluginList.contains(e)||(s.settings.pluginList.unshift(e),o=!0);let a=s.settings.pluginSubListFrozenVersion.find(c=>c.repo===e);a?(Object.assign(a,{repo:e,version:t,token:void 0,tokenName:i||a.tokenName,isIncompatible:n||void 0}),o=!0):(s.settings.pluginSubListFrozenVersion.unshift({repo:e,version:t,token:void 0,tokenName:i||void 0,isIncompatible:n||void 0}),o=!0),o&&s.saveSettings()}function Pe(s,e){return s.settings.pluginList.contains(e)}function Nt(s,e,t){let n={repo:e,lastUpdate:Q(t)};s.settings.themesList.unshift(n),s.saveSettings()}function At(s,e){return!!s.settings.themesList.find(n=>n.repo===e)}function ze(s,e,t){let n=s.settings.pluginSubListFrozenVersion.find(i=>i.repo===e);n&&(n.tokenName=t||void 0,s.saveSettings())}function Ct(s,e,t){for(let n of s.settings.themesList)n.repo===e&&(n.lastUpdate=t,s.saveSettings())}var L=require("obsidian");var j=class{constructor(e){this.statusEl=e}async validateToken(e,t){var n,i,o,a,c,u,g,m,l;if(!e)return(n=this.statusEl)==null||n.setText("No token provided"),(i=this.statusEl)==null||i.addClass("invalid"),(o=this.statusEl)==null||o.removeClass("valid"),!1;try{let r=await Tt(e,t);return(a=this.statusEl)==null||a.removeClass("invalid","valid"),(c=this.statusEl)==null||c.empty(),r.validToken?((u=this.statusEl)==null||u.addClass("valid"),this.showValidTokenInfo(r),!0):((g=this.statusEl)==null||g.addClass("invalid"),this.showErrorMessage(r.error),!1)}catch(r){return console.error("Token validation error:",r),(m=this.statusEl)==null||m.setText("Failed to validate token"),(l=this.statusEl)==null||l.addClass("invalid"),!1}}showValidTokenInfo(e){var n,i;let t=(n=this.statusEl)==null?void 0:n.createDiv({cls:"brat-token-details"});if(t&&(t.createDiv({text:"\u2713 Valid token",cls:"brat-token-status valid"}),(i=e.currentScopes)!=null&&i.length&&t.createDiv({text:`Scopes: ${e.currentScopes.join(", ")}`,cls:"brat-token-scopes"}),e.rateLimit&&t.createDiv({text:`Rate Limit: ${e.rateLimit.remaining}/${e.rateLimit.limit}`,cls:"brat-token-rate"}),e.expirationDate)){let o=new Date(e.expirationDate),a=Math.ceil((o.getTime()-Date.now())/(1e3*60*60*24));a<7&&t.createDiv({text:`\u26A0\uFE0F Token expires in ${a} days`,cls:"brat-token-warning"})}}showErrorMessage(e){var n,i,o;let t=(n=this.statusEl)==null?void 0:n.createDiv({cls:"brat-token-error"});if(t&&(t.createDiv({text:e.message}),e.details))switch(e.type){case"invalid_prefix":t.createDiv({text:`Valid prefixes: ${(i=e.details.validPrefixes)==null?void 0:i.join(", ")}`});break;case"insufficient_scope":t.createDiv({text:`Required scopes: ${(o=e.details.requiredScopes)==null?void 0:o.join(", ")}`});break}}};function ee(s,e){let t=new DocumentFragment,n=document.createElement("a");if(n.textContent=s,n.href=`https://github.com/${s}`,n.target="_blank",t.appendChild(n),e){let i=document.createTextNode(e);t.appendChild(i)}return t}function Lt({prependText:s,url:e,text:t,appendText:n}){let i=new DocumentFragment,o=document.createElement("a");if(o.textContent=t,o.href=e,s){let a=document.createTextNode(s);i.appendChild(a)}if(i.appendChild(o),n){let a=document.createTextNode(n);i.appendChild(a)}return i}var Re=require("obsidian");function E(s,e,t=10,n){if(!s.settings.notificationsEnabled)return;let i=n?Re.Platform.isDesktop?"(click=dismiss, right-click=Info)":"(click=dismiss)":"",o=new Re.Notice(`BRAT +${e} +${i}`,t*1e3);n&&(o.noticeEl.oncontextmenu=()=>{n()})}var z=(s,e=!0)=>{let t=s.createEl("div");t.style.float="right",e?(t.style.padding="15px",t.style.paddingLeft="15px",t.style.paddingRight="15px",t.style.marginLeft="15px"):(t.style.padding="10px",t.style.paddingLeft="15px",t.style.paddingRight="15px");let n=t.createDiv("coffee");n.addClass("ex-twitter-span"),n.style.paddingLeft="10px";let i=n.createDiv();i.innerText="Learn more about my work at:",n.appendChild(i);let o=n.createEl("a",{href:"https://tfthacker.com"});return o.innerText="https://tfthacker.com",t};var kt=require("obsidian"),Ie=class extends kt.SuggestModal{constructor(e,t,n,i,o){super(e),this.versions=n,this.selected=i,this.onChoose=o,this.setTitle("Select a version"),this.setPlaceholder(`Type to search for a version for ${t}`),this.setInstructions([{command:"\u2191\u2193",purpose:"Navigate versions"},{command:"\u21B5",purpose:"Select version"},{command:"esc",purpose:"Dismiss modal"}])}getSuggestions(e){let t=e.toLowerCase();return this.versions.filter(n=>n.version.toLowerCase().contains(t))}renderSuggestion(e,t){t.createEl("div",{text:`${e.version} ${e.prerelease?"(Prerelease)":""}`})}onChooseSuggestion(e){this.onChoose(e.version)}onNoSuggestion(){this.onChoose(this.selected?this.selected:""),this.close()}};var U=class extends L.Modal{constructor(t,n,i=!1,o=!1,a="",c="",u=""){super(t.app);this.versionSetting=null;this.repositoryAddressEl=null;this.tokenInputEl=null;this.validateButton=null;this.validator=null;this.addPluginButton=null;this.cancelButton=null;this.plugin=t,this.betaPlugins=n,this.address=a,this.version=c,this.secretName=u,this.openSettingsTabAfterwards=i,this.updateVersion=o,this.enableAfterInstall=t.settings.enableAfterInstall}async submitForm(){var o,a,c,u,g,m,l,r;if(this.address==="")return;let t=ye(this.address);if(this.plugin.settings.pluginSubListFrozenVersion.find(d=>d.repo===t)){await this.betaPlugins.addPlugin(t,!1,!1,!1,this.version,!0,this.enableAfterInstall,this.secretName)&&this.close(),(o=this.cancelButton)==null||o.setDisabled(!1),(a=this.addPluginButton)==null||a.setDisabled(!1),(c=this.addPluginButton)==null||c.setButtonText("Add Plugin"),(u=this.versionSetting)==null||u.setDisabled(!1);return}if(!this.version&&Pe(this.plugin,t)){E(this.plugin,"This plugin is already in the list for beta testing",10);return}await this.betaPlugins.addPlugin(t,!1,!1,!1,this.version,!1,this.enableAfterInstall,this.secretName)&&this.close(),(g=this.cancelButton)==null||g.setDisabled(!1),(m=this.addPluginButton)==null||m.setDisabled(!1),(l=this.addPluginButton)==null||l.setButtonText("Add Plugin"),(r=this.versionSetting)==null||r.setDisabled(!1)}updateVersionDropdown(t,n,i=""){let o;t.clear(),n.length>0&&!i&&this.plugin.settings.selectLatestPluginVersionByDefault?(o="latest",this.version="latest"):o=i,n.length<20||L.Platform.isMobile?t.addDropdown(c=>{c.addOption("","Select a version"),c.addOption("latest","Latest version");for(let u of n)c.addOption(u.version,`${u.version} ${u.prerelease?"(Prerelease)":""}`);c.onChange(u=>{var g;this.version=u,(g=this.addPluginButton)==null||g.setDisabled(this.version==="")}),c.setValue(o),c.selectEl.addClass("brat-version-selector"),c.selectEl.style.width="100%"}):t.addButton(c=>{c.setButtonText(o==="latest"?"Latest version":o||"Select a version...").setClass("brat-version-selector").setClass("button").onClick(()=>{let g=[{version:"latest",prerelease:!1},...n];new Ie(this.app,this.address,g,o,l=>{var r;this.version=l,c.setButtonText(l==="latest"?"Latest version":l||"Select a version..."),(r=this.addPluginButton)==null||r.setDisabled(this.version==="")}).open()})})}onOpen(){let t=this.contentEl.createEl("h4");this.address?(t.appendText("Change plugin version: "),t.appendChild(ee(this.address))):t.setText("Github repository for beta plugin:"),this.contentEl.createEl("form",{},n=>{var g;n.addClass("brat-modal"),(!this.address||!this.updateVersion)&&new L.Setting(n).setClass("repository-setting").then(l=>{l.addText(r=>{this.repositoryAddressEl=r,r.setPlaceholder("Repository (example: https://github.com/GitHubUserName/repository-name)"),r.setValue(this.address),r.onChange(d=>{var b,w;this.address=ye(d.trim()),this.version!==""&&(!this.address||!this.isGitHubRepositoryMatch(this.address))&&this.versionSetting&&(this.updateVersionDropdown(this.versionSetting,[]),this.versionSetting.settingEl.classList.add("disabled-setting"),this.versionSetting.setDisabled(!0),r.inputEl.classList.remove("valid-repository"),r.inputEl.classList.remove("invalid-repository")),this.version||(this.isGitHubRepositoryMatch(this.address)?(b=this.addPluginButton)==null||b.setDisabled(!1):(w=this.addPluginButton)==null||w.setDisabled(!0))}),r.inputEl.addEventListener("keydown",async d=>{var b,w,p;d.key==="Enter"&&(this.address&&(this.updateVersion&&this.version!==""||!this.updateVersion)&&(d.preventDefault(),(b=this.addPluginButton)==null||b.setDisabled(!0),(w=this.cancelButton)==null||w.setDisabled(!0),(p=this.versionSetting)==null||p.setDisabled(!0),this.submitForm()),await this.updateRepositoryVersionInfo(this.version,i))}),r.inputEl.addEventListener("blur",async()=>{await this.updateRepositoryVersionInfo(this.version,i)}),l.setDesc("Repository"),r.inputEl.style.width="100%"})});let i=n.createDiv("validation-status");this.address||i.setText("Enter a GitHub repository address to validate it."),this.versionSetting=new L.Setting(n).setClass("version-setting").setClass("disabled-setting"),this.updateVersionDropdown(this.versionSetting,[],this.version),this.versionSetting.setDisabled(!0);let o=n.createDiv("token-setting");if(new L.Setting(o).setName("GitHub Token").setDesc("Select a secret as token for this repository (optional)").addComponent(m=>new L.SecretComponent(this.plugin.app,m).setValue(this.secretName).onChange(async l=>{var d,b,w,p,T;if(this.secretName=(l==null?void 0:l.trim())||"",!this.secretName){this.address&&Pe(this.plugin,this.address)&&(ze(this.plugin,this.address,""),E(this.plugin,`Token setting cleared for ${this.address}`,3)),this.updateRepositoryVersionInfo(this.version,i);return}let r=this.secretName?this.plugin.app.secretStorage.getSecret(this.secretName):null;r&&(this.validToken=await((d=this.validator)==null?void 0:d.validateToken(r,this.address)),this.validToken?((p=this.validateButton)==null||p.setButtonText("Valid"),(T=this.validateButton)==null||T.setDisabled(!0),this.address&&(await this.updateRepositoryVersionInfo(this.version,i),Pe(this.plugin,this.address)&&(ze(this.plugin,this.address,this.secretName),E(this.plugin,`Token setting updated for ${this.address}`,3)))):((b=this.validateButton)==null||b.setButtonText("Invalid"),(w=this.validateButton)==null||w.setDisabled(!1)))})),this.validator=new j,this.secretName){let m=this.plugin.app.secretStorage.getSecret(this.secretName);m&&((g=this.validator)==null||g.validateToken(m,this.address).then(l=>{var r,d;this.validToken=l,this.validToken&&((r=this.validateButton)==null||r.setButtonText("Valid"),(d=this.validateButton)==null||d.setDisabled(!0))}))}n.createDiv("modal-button-container",m=>{var l;m.createEl("label",{cls:"mod-checkbox"},r=>{let d=r.createEl("input",{attr:{tabindex:-1},type:"checkbox"});d.checked=this.enableAfterInstall,d.addEventListener("click",()=>{this.enableAfterInstall=d.checked}),r.appendText("Enable after installing the plugin")}),this.cancelButton=new L.ButtonComponent(m).setButtonText("Never mind").setClass("mod-cancel").onClick(()=>{this.close()}),this.addPluginButton=new L.ButtonComponent(m).setButtonText(this.updateVersion&&this.address?"Change version":"Add plugin").setCta().onClick(()=>{var r,d,b,w;this.address!==""&&(this.updateVersion&&this.version!==""||!this.updateVersion)&&((r=this.addPluginButton)==null||r.setDisabled(!0),(d=this.addPluginButton)==null||d.setButtonText("Installing \u2026"),(b=this.cancelButton)==null||b.setDisabled(!0),(w=this.versionSetting)==null||w.setDisabled(!0),this.submitForm())}),(this.updateVersion||this.address==="")&&((l=this.addPluginButton)==null||l.setDisabled(!0))});let a=n.createDiv();a.style.borderTop="1px solid #ccc",a.style.marginTop="30px";let c=a.createSpan();c.createEl("a",{href:"https://bit.ly/o42-twitter",text:"TFTHacker"}),c.appendText(" and "),c.createEl("a",{href:"https://github.com/johannrichard",text:"johannrichard"}),c.style.fontStyle="italic",a.appendChild(c),z(a,!1);let u=n.querySelectorAll("button");for(let m of Array.from(u))m.setAttribute("type","button");n.addEventListener("submit",m=>{var l;m.preventDefault(),this.address!==""&&(this.updateVersion&&this.version!==""||!this.updateVersion)&&((l=this.addPluginButton)==null||l.setDisabled(!0),this.submitForm())})}),this.address&&window.setTimeout(async()=>{await this.updateRepositoryVersionInfo(this.version)},100)}async updateRepositoryVersionInfo(t="",n){var a,c,u,g,m,l;let i=this.repositoryAddressEl;if(this.plugin.settings.debuggingMode&&console.log(`[BRAT] Updating version dropdown for ${this.address} with selected version ${t}`),!this.address){n==null||n.setText("Repository address is required."),n==null||n.addClass("validation-status-error");return}n==null||n.setText("Validating repository address..."),n==null||n.removeClass("validation-status-error"),this.versionSetting&&this.updateVersion&&this.updateVersionDropdown(this.versionSetting,[]);let o=ye(this.address);try{let r="";if(this.secretName){let b=this.plugin.app.secretStorage.getSecret(this.secretName);b&&(r=b)}else if(this.plugin.settings.globalTokenName){let b=this.plugin.app.secretStorage.getSecret(this.plugin.settings.globalTokenName);b&&(r=b)}let d=await Et(o,this.plugin.settings.debuggingMode,r);d&&d.length>0?(i==null||i.inputEl.classList.remove("invalid-repository"),i==null||i.inputEl.classList.add("valid-repository"),n==null||n.setText(""),this.versionSetting&&(this.versionSetting.settingEl.classList.remove("disabled-setting"),this.versionSetting.setDisabled(!1),this.updateVersionDropdown(this.versionSetting,d,t))):(i==null||i.inputEl.classList.remove("valid-repository"),i==null||i.inputEl.classList.add("invalid-repository"),n==null||n.setText("Error: No releases found in this repository."),n==null||n.addClass("validation-status-error"),(a=this.versionSetting)==null||a.settingEl.classList.add("disabled-setting"),(c=this.versionSetting)==null||c.setDisabled(!0),(u=this.addPluginButton)==null||u.setDisabled(!0))}catch(r){if(r instanceof D&&(i==null||i.inputEl.classList.remove("valid-repository"),i==null||i.inputEl.classList.add("validation-error"),n==null||n.setText(`GitHub API rate limit exceeded. Try again in ${r.getMinutesToReset()} minutes.`),this.versionSetting&&(this.versionSetting.settingEl.classList.add("disabled-setting"),this.versionSetting.setDisabled(!0),(g=this.addPluginButton)==null||g.setDisabled(!0)),E(this.plugin,`${r.message} Consider adding a personal access token in BRAT settings for higher limits. See documentation for details.`,20,()=>{window.open("https://github.com/TfTHacker/obsidian42-brat/blob/main/BRAT-DEVELOPER-GUIDE.md#github-api-rate-limits")})),r instanceof O){let d=r;switch(d.status){case 404:n==null||n.setText("Repository not found. Check the address or provide a valid token for access to a private repository.");break;case 403:n==null||n.setText("Access denied. Check your personal access token.");break;default:n==null||n.setText(`Error: ${d.message}`);break}n==null||n.addClass("validation-status-error"),(m=this.versionSetting)==null||m.setDisabled(!0),(l=this.addPluginButton)==null||l.setDisabled(!0),E(this.plugin,`${d.message} `,20)}}}onClose(){this.openSettingsTabAfterwards&&(this.plugin.app.setting.open(),this.plugin.app.setting.openTabById(this.plugin.APP_ID))}isGitHubRepositoryMatch(t){let n=t.trim().replace(/\.git$/,"").toLowerCase();return/^(?:https?:\/\/github\.com\/)?([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)$/i.test(n)}};var St=require("obsidian");async function Ne(){try{let s=await(0,St.requestUrl)(`https://obsidian.md/?${Math.random()}`);return s.status>=200&&s.status<300}catch(s){return!1}}var Dt=Ue(),Ae=_e(),te=class{constructor(e){this.plugin=e}displayAddNewPluginModal(e=!1,t=!1,n="",i="",o=""){new U(this.plugin,this,e,t,n,i,o).open()}async validateRepository(e,t=!1,n=!1,i="",o=""){let c=o;try{let u=await He(e,this.plugin.settings.debuggingMode,c),g=await Ge(e,i,t,this.plugin.settings.debuggingMode,u,c);if(!g)return n&&(E(this.plugin,`${e} +This does not seem to be an obsidian plugin with valid releases, as there are no releases available.`,15),console.error("BRAT: validateRepository",e,t,n)),null;let m=await K(g,"manifest.json",this.plugin.settings.debuggingMode,u,c);if(!m)return n&&(E(this.plugin,`${e} +This does not seem to be an obsidian plugin, as there is no manifest.json file.`,15),console.error("BRAT: validateRepository",e,t,n)),null;let l=JSON.parse(m);if(!("id"in l))return n&&E(this.plugin,`${e} +The plugin id attribute for the release is missing from the manifest file`,15),null;if(!("version"in l))return n&&E(this.plugin,`${e} +The version attribute for the release is missing from the manifest file`,15),null;try{let r=Ae(g.tag_name,{includePrerelease:!0,loose:!0}),d=Ae(l.version,{includePrerelease:!0,loose:!0});Dt(r,d)!==0&&(n&&E(this.plugin,`${e} +Version mismatch detected: +Release tag version: ${g.tag_name} +Manifest version: ${l.version} + +The release tag version will be used to ensure consistency.`,15),l.version=r.version)}catch(r){}return l}catch(u){if(u instanceof D){let g=`GitHub API rate limit exceeded. Reset in ${u.getMinutesToReset()} minutes.`;throw n&&E(this.plugin,g,15),console.error(`BRAT: validateRepository ${u}`),E(this.plugin,`${u.message} Consider adding a personal access token in BRAT settings for higher limits. See documentation for details.`,20,()=>{window.open("https://github.com/TfTHacker/obsidian42-brat/blob/main/BRAT-DEVELOPER-GUIDE.md#github-api-rate-limits")}),u}if(u instanceof O)throw n&&(u.status===401?E(this.plugin,`${e} +GitHub API Authentication error. Please verify that your personal access token is valid and set correctly.`,15):E(this.plugin,`${e} +GitHub API error ${u.status}: ${u.message}`,15)),console.error(`BRAT: validateRepository ${u}`),u;return n&&E(this.plugin,`${e} +Unspecified error encountered: ${u}, verify debug for more information.`,15),null}}async getAllReleaseFiles(e,t,n="",i=""){let o=i,a=await He(e,this.plugin.settings.debuggingMode,o),c=await Ge(e,n,t,this.plugin.settings.debuggingMode,a,o);if(!c)return Promise.reject("No release found");let u=t||n!=="";return console.log({reallyGetManifestOrNot:u,version:c.tag_name}),{mainJs:await K(c,"main.js",this.plugin.settings.debuggingMode,a,o),manifest:u?await K(c,"manifest.json",this.plugin.settings.debuggingMode,a,o):"",styles:await K(c,"styles.css",this.plugin.settings.debuggingMode,a,o)}}async writeReleaseFilesToPluginFolder(e,t){var o,a;let n=`${(0,k.normalizePath)(`${this.plugin.app.vault.configDir}/plugins/${e}`)}/`,{adapter:i}=this.plugin.app.vault;await i.exists(n)||await i.mkdir(n),await i.write(`${n}main.js`,(o=t.mainJs)!=null?o:""),await i.write(`${n}manifest.json`,(a=t.manifest)!=null?a:""),t.styles&&await i.write(`${n}styles.css`,t.styles)}async addPlugin(e,t=!1,n=!1,i=!1,o="",a=!1,c=this.plugin.settings.enableAfterInstall,u=""){try{this.plugin.settings.debuggingMode&&console.log("BRAT: addPlugin",e,t,n,i,o,a,c,u?"with secret":"public");let g="";u&&u.trim()!==""?(g=await this.plugin.app.secretStorage.getSecret(u)||"",g||E(this.plugin,`Secret not found for token name: ${u}. Please add it to SecretStorage or clear the token name for this plugin.`,10)):this.plugin.settings.globalTokenName&&(g=await this.plugin.app.secretStorage.getSecret(this.plugin.settings.globalTokenName)||"");let m=10,l=await this.validateRepository(e,!0,!0,o,g),r=!!l;if(r||(l=await this.validateRepository(e,!1,!0,o,g)),l===null){let w=`${e} +A manifest.json file does not exist in the latest release of the repository. This plugin cannot be installed.`;return await this.plugin.log(w,!0),E(this.plugin,w,m),!1}if(!Object.hasOwn(l,"version")){let w=`${e} +The manifest.json file in the latest release or pre-release of the repository does not have a version number in the file. This plugin cannot be installed.`;return await this.plugin.log(w,!0),E(this.plugin,w,m),!1}let d=!1;if(Object.hasOwn(l,"minAppVersion")&&!(0,k.requireApiVersion)(l.minAppVersion)){if(o===""||o==="latest"||!this.plugin.settings.allowIncompatiblePlugins){let p=`Plugin: ${e} + +The manifest.json for this plugin indicates that the Obsidian version of the app needs to be ${l.minAppVersion}, but this installation of Obsidian is ${k.apiVersion}. + +You will need to update your Obsidian to use this plugin or contact the plugin developer for more information.`;return await this.plugin.log(p,!0),E(this.plugin,p,30),!1}if(!await De({app:this.plugin.app,message:createFragment(p=>{p.appendText("Plugin: "),p.createEl("code",{text:e}),p.createEl("br"),p.appendText("The "),p.createEl("code",{text:"manifest.json"}),p.appendText(" for this plugin indicates that the Obsidian version of the app needs to be "),p.createEl("code",{text:l.minAppVersion}),p.appendText(", but this installation of Obsidian is "),p.createEl("code",{text:k.apiVersion}),p.appendText("."),p.createEl("br"),p.appendText("Using this plugin is not recommended and may not work as expected. Use at your own risk."),p.createEl("br"),p.appendText("Do you want to install it anyways?")})}))return!1;d=!0}let b=async()=>{var T,C;let w=await this.getAllReleaseFiles(e,r,o,g);console.log("rFiles",w),(r||w.manifest==="")&&(w.manifest=JSON.stringify(l));let p=JSON.parse((T=w.manifest)!=null?T:"");if(d&&(p.brat={isIncompatible:!0,minAppVersionOriginal:p.minAppVersion},p.minAppVersion=k.apiVersion),k.Platform.isMobile&&p.isDesktopOnly)if(this.plugin.settings.allowIncompatiblePlugins){if(!await De({app:this.plugin.app,message:createFragment(R=>{R.appendText("Plugin: "),R.createEl("code",{text:e}),R.createEl("br"),R.appendText("The "),R.createEl("code",{text:"manifest.json"}),R.appendText(" for this plugin indicates that the plugin has "),R.createEl("code",{text:"isDesktopOnly: true"}),R.appendText(", but you are using a mobile device."),R.createEl("br"),R.appendText("Using this plugin is not recommended and may not work as expected. Use at your own risk."),R.createEl("br"),R.appendText("Do you want to forcefully run it on mobile anyways?")})}))return null;p.isDesktopOnly=!1,(C=p.brat)!=null||(p.brat={}),p.brat.isDesktopOnlyOriginal=!0,p.brat.isIncompatible=!0,d=!0}else{let N=`Plugin: ${e} + +The manifest.json for this plugin indicates that the plugin has isDesktopOnly: true, but you are using a mobile device. + +The plugin will not be installed.`;return await this.plugin.log(N,!0),E(this.plugin,N,30),null}if(d&&(w.manifest=JSON.stringify(p)),this.plugin.settings.debuggingMode&&console.log("BRAT: rFiles.manifest",r,w),w.mainJs===null){let N=`${e} +The release is not complete and cannot be downloaded. main.js is missing from the Release`;return await this.plugin.log(N,!0),E(this.plugin,N,m),null}return w};if(!t||a){let w=await b();if(w===null)return!1;if(await this.writeReleaseFilesToPluginFolder(l.id,w),It(this.plugin,e,o,d,u),c){let{plugins:p}=this.plugin.app,T=(0,k.normalizePath)(`${p.getPluginFolder()}/${l.id}`);await p.loadManifest(T),await p.enablePluginAndSave(l.id)}if(await this.plugin.app.plugins.loadManifests(),a)await this.reloadPlugin(l.id),await this.plugin.log(`${e} reinstalled`,!0),E(this.plugin,`${e} +Plugin has been reinstalled and reloaded with version ${l.version}`,m);else{let p=o===""?"":` (version: ${o})`,T=`${e}${p} +The plugin has been registered with BRAT.`;c||(T+=" You may still need to enable it the Community Plugin List."),await this.plugin.log(T,!0),E(this.plugin,T,m)}}else{let w=`${this.plugin.app.vault.configDir}/plugins/${l.id}/`,p="";try{p=await this.plugin.app.vault.adapter.read(`${w}manifest.json`)}catch(R){if(R.errno===-4058||R.errno===-2)return await this.addPlugin(e,!1,r,!1,o,!1,c,u),!0;console.log("BRAT - Local Manifest Load",l.id,JSON.stringify(R,null,2))}if(o!==""&&o!=="latest")return E(this.plugin,`The version of ${e} is frozen, not updating.`,3),!1;let T=await JSON.parse(p),C=Ae(T.version,{includePrerelease:!0,loose:!0}),N=Ae(l.version,{includePrerelease:!0,loose:!0});if(Dt(C,N)===-1){let R=await b();if(R===null)return!1;if(n){let W=`There is an update available for ${l.id} from version ${T.version} to ${l.version}. `;return await this.plugin.log(`${W}[Release Info](https://github.com/${e}/releases/tag/${l.version})`,!0),E(this.plugin,W,30,()=>{l&&window.open(`https://github.com/${e}/releases/tag/${l.version}`)}),!1}await this.writeReleaseFilesToPluginFolder(l.id,R),await this.plugin.app.plugins.loadManifests(),await this.reloadPlugin(l.id);let P=`${l.id} +Plugin has been updated from version ${T.version} to ${l.version}. `;return await this.plugin.log(`${P}[Release Info](https://github.com/${e}/releases/tag/${l.version})`,!0),E(this.plugin,P,30,()=>{l&&window.open(`https://github.com/${e}/releases/tag/${l.version}`)}),!0}return i&&E(this.plugin,`No update available for ${e}`,3),!0}}catch(g){console.error(`BRAT: Error adding plugin ${e}:`,{error:g,updatePluginFiles:t,seeIfUpdatedOnly:n,specifyVersion:o,forceReinstall:a});let m=g instanceof Error?g.message:"Unknown error occurred";return await this.plugin.log(`Error ${t?"updating":"adding"} plugin ${e}: ${m}`,!0),!1}return!0}async reloadPlugin(e){let{plugins:t}=this.plugin.app;try{await t.disablePlugin(e),await t.enablePlugin(e)}catch(n){this.plugin.settings.debuggingMode&&console.log("reload plugin",n)}}async updatePlugin(e,t=!1,n=!1,i=!1,o=""){let a=await this.addPlugin(e,!0,t,n,"",i,!1,o);return!a&&!t&&E(this.plugin,`${e} +Update of plugin failed.`),a}async checkForPluginUpdatesAndInstallUpdates(e=!1,t=!1){if(!await Ne()){console.log("BRAT: No internet detected.");return}let n,i="Checking for plugin updates STARTED";await this.plugin.log(i,!0),e&&this.plugin.settings.notificationsEnabled&&(n=new k.Notice(`BRAT +${i}`,3e4));let o=new Map(this.plugin.settings.pluginSubListFrozenVersion.map(u=>[u.repo,u.version])),a=new Map(this.plugin.settings.pluginSubListFrozenVersion.map(u=>[u.repo,u.tokenName||""]));for(let u of this.plugin.settings.pluginList){let g=o.get(u);g&&g!=="latest"||await this.updatePlugin(u,t,!1,!1,a.get(u)||"")}let c="Checking for plugin updates COMPLETED";await this.plugin.log(c,!0),e&&(n&&n.hide(),E(this.plugin,c,10))}deletePlugin(e){let t=`Removed ${e} from BRAT plugin list`;this.plugin.log(t,!0),this.plugin.settings.pluginList=this.plugin.settings.pluginList.filter(n=>n!==e),this.plugin.settings.pluginSubListFrozenVersion=this.plugin.settings.pluginSubListFrozenVersion.filter(n=>n.repo!==e),this.plugin.saveSettings()}getEnabledDisabledPlugins(e){let t=this.plugin.app.plugins,n=Object.values(t.manifests),i=Object.values(t.plugins).map(o=>o.manifest);return e?n.filter(o=>i.find(a=>o.id===a.id)):n.filter(o=>!i.find(a=>o.id===a.id))}checkIncompatiblePlugins(){let e=this.plugin.settings.pluginSubListFrozenVersion.filter(t=>t.isIncompatible).map(t=>t.repo);e.length>0&&E(this.plugin,`The following incompatible plugins were forcefully installed by BRAT and may not work as expected: +${e.join(` +`)}`,30)}};var _=require("obsidian");var ne=async(s,e,t)=>{let n=await G(e,!0,s.settings.debuggingMode);if(n||(n=await G(e,!1,s.settings.debuggingMode)),!n)return E(s,"There is no theme.css or theme-beta.css file in the root path of this repository, so there is no theme to install."),!1;let i=await Pt(e,s.settings.debuggingMode);if(!i)return E(s,"There is no manifest.json file in the root path of this repository, so theme cannot be installed."),!1;let o=await JSON.parse(i),a=(0,_.normalizePath)(Vn(s)+o.name),{adapter:c}=s.app.vault;await c.exists(a)||await c.mkdir(a),await c.write((0,_.normalizePath)(`${a}/theme.css`),n),await c.write((0,_.normalizePath)(`${a}/manifest.json`),i),Ct(s,e,Q(n));let u="";return t?(Nt(s,e,n),u=`${o.name} theme installed from ${e}. `,setTimeout(()=>{s.app.customCss.setTheme(o.name)},500)):u=`${o.name} theme updated from ${e}.`,s.log(`${u}[Theme Info](https://github.com/${e})`,!1),E(s,u,20,()=>{window.open(`https://github.com/${e}`)}),!0},q=async(s,e)=>{if(!await Ne()){console.log("BRAT: No internet detected.");return}let t,n="Checking for beta theme updates STARTED";await s.log(n,!0),e&&s.settings.notificationsEnabled&&(t=new _.Notice(`BRAT +${n}`,3e4));for(let o of s.settings.themesList){let a=await Z(o.repo,!0,s.settings.debuggingMode);a==="0"&&(a=await Z(o.repo,!1,s.settings.debuggingMode)),console.log("BRAT: lastUpdateOnline",a),a!==o.lastUpdate&&await ne(s,o.repo,!1)}let i="Checking for beta theme updates COMPLETED";(async()=>await s.log(i,!0))(),e&&(s.settings.notificationsEnabled&&t&&t.hide(),E(s,i))},Ce=(s,e)=>{s.settings.themesList=s.settings.themesList.filter(n=>n.repo!==e),s.saveSettings();let t=`Removed ${e} from BRAT themes list and will no longer be updated. However, the theme files still exist in the vault. To remove them, go into Settings > Appearance and remove the theme.`;s.log(t,!0),E(s,t)},Vn=s=>`${(0,_.normalizePath)(`${s.app.vault.configDir}/themes`)}/`;var $t="brat-migrations";async function Un(s,e){try{let t=await s.vault.adapter.read(`${s.vault.configDir}/plugins/obsidian42-brat/${$t}.json`);return JSON.parse(t).appliedMigrations.includes(e)}catch(t){return!1}}async function _n(s,e){try{let t=`${s.vault.configDir}/plugins/obsidian42-brat/${$t}.json`,n={appliedMigrations:[]};try{let i=await s.vault.adapter.read(t);n=JSON.parse(i)}catch(i){}n.appliedMigrations.includes(e)||(n.appliedMigrations.push(e),await s.vault.adapter.write(t,JSON.stringify(n,null,2)))}catch(t){console.error(`BRAT: Failed to mark migration ${e} complete:`,t)}}async function xt(s,e,t){let n="tokens-to-secretstorage-v1";if(!await Un(s,n))try{let i=0,o=u=>{let m=`brat-gh-${u.toLowerCase().replace(/[^a-z0-9-]/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")}`;return m.length>64?m.substring(0,64).replace(/-$/,""):m},a=u=>{let g=s.secretStorage.listSecrets();for(let m of g)if(s.secretStorage.getSecret(m)===u)return m;return null},c=(u,g)=>{let m=a(u);return m?(console.log(`BRAT: Reusing existing secret "${m}"`),m):(s.secretStorage.setSecret(g,u),console.log(`BRAT: Created new secret "${g}"`),g)};if(e.personalAccessToken&&e.personalAccessToken.trim()!==""){let u=e.personalAccessToken.trim(),m=c(u,"brat-gh-global");e.globalTokenName=m,e.personalAccessToken="",i++}if(e.pluginSubListFrozenVersion){for(let u of e.pluginSubListFrozenVersion)if(u.token&&u.token.trim()!==""){let g=u.token.trim(),m=o(u.repo),l=c(g,m);u.tokenName=l,u.token=void 0,i++}}i>0&&(await t(),console.log(`BRAT: Migrated ${i} token(s) to SecretStorage`)),await _n(s,n)}catch(i){console.error("BRAT: Failed to migrate tokens to SecretStorage:",i)}}var H=require("obsidian");var B=class extends H.Modal{constructor(e,t=!1){super(e.app),this.plugin=e,this.address="",this.openSettingsTabAfterwards=t}async submitForm(){if(this.address==="")return;let e=this.address.replace("https://github.com/","");if(At(this.plugin,e)){E(this.plugin,"This theme is already in the list for beta testing",10);return}await ne(this.plugin,e,!0)&&this.close()}onOpen(){this.contentEl.createEl("h4",{text:"Github repository for beta theme:"}),this.contentEl.createEl("form",{},e=>{e.addClass("brat-modal"),new H.Setting(e).addText(i=>{i.setPlaceholder("Repository (example: https://github.com/GitHubUserName/repository-name"),i.setValue(this.address),i.onChange(o=>{this.address=o.trim()}),i.inputEl.addEventListener("keydown",o=>{o.key==="Enter"&&this.address!==" "&&(o.preventDefault(),this.submitForm())}),i.inputEl.style.width="100%",window.setTimeout(()=>{let o=document.querySelector(".setting-item-info");o&&o.remove(),i.inputEl.focus()},10)}),e.createDiv("modal-button-container",i=>{new H.ButtonComponent(i).setButtonText("Never mind").onClick(()=>{this.close()}),new H.ButtonComponent(i).setButtonText("Add theme").setCta().onClick(o=>{o.preventDefault(),console.log("Add theme button clicked"),this.address!==""&&this.submitForm()})});let t=e.createDiv();t.style.borderTop="1px solid #ccc",t.style.marginTop="30px";let n=t.createSpan();n.createEl("a",{href:"https://bit.ly/o42-twitter",text:"TFTHacker"}),n.appendText(" and "),n.createEl("a",{href:"https://github.com/johannrichard",text:"johannrichard"}),n.style.fontStyle="italic",t.appendChild(n),z(t,!1),window.setTimeout(()=>{let i=e.querySelectorAll(".brat-modal .setting-item-info");for(let o of Array.from(i))o.remove()},50)})}onClose(){this.openSettingsTabAfterwards&&(this.plugin.app.setting.openTab(),this.plugin.app.setting.openTabById(this.plugin.APP_ID))}};var Ot=require("obsidian");function Ft(){(0,Ot.addIcon)("BratIcon",'')}var Bt=require("obsidian"),S=class extends Bt.FuzzySuggestModal{constructor(t){super(t.app);this.data=[];this.scope.register(["Shift"],"Enter",n=>{this.enterTrigger(n)}),this.scope.register(["Ctrl"],"Enter",n=>{this.enterTrigger(n)})}setSuggesterData(t){this.data=t}display(t){this.callbackFunction=t,this.open()}getItems(){return this.data}getItemText(t){return t.display}onChooseItem(){}renderSuggestion(t,n){n.createEl("div",{text:t.item.display})}enterTrigger(t){var o;let n=(o=document.querySelector(".suggestion-item.is-selected div"))==null?void 0:o.textContent,i=this.data.find(a=>a.display===n);i&&(this.invokeCallback(i,t),this.close())}onChooseSuggestion(t,n){this.invokeCallback(t.item,n)}invokeCallback(t,n){typeof this.callbackFunction=="function"&&this.callbackFunction(t,n)}};var se=class{constructor(e){this.bratCommands=[{id:"AddBetaPlugin",icon:"BratIcon",name:"Plugins: Add a beta plugin for testing (with or without version)",showInRibbon:!0,callback:()=>{this.plugin.betaPlugins.displayAddNewPluginModal(!1,!0)}},{id:"checkForUpdatesAndUpdate",icon:"BratIcon",name:"Plugins: Check for updates to all beta plugins and UPDATE",showInRibbon:!0,callback:async()=>{await this.plugin.betaPlugins.checkForPluginUpdatesAndInstallUpdates(!0,!1)}},{id:"checkForUpdatesAndDontUpdate",icon:"BratIcon",name:"Plugins: Only check for updates to beta plugins, but don't Update",showInRibbon:!0,callback:async()=>{await this.plugin.betaPlugins.checkForPluginUpdatesAndInstallUpdates(!0,!0)}},{id:"updateOnePlugin",icon:"BratIcon",name:"Plugins: Choose a single plugin version to update",showInRibbon:!0,callback:()=>{let e=new Map(this.plugin.settings.pluginSubListFrozenVersion.map(i=>[i.repo,{version:i.version,token:i.token}])),t=Object.values(this.plugin.settings.pluginList).filter(i=>{let o=e.get(i);return!(o!=null&&o.version)||o.version==="latest"}).map(i=>({display:i,info:i})),n=new S(this.plugin);n.setSuggesterData(t),n.display(i=>{let o=`Checking for updates for ${i.info}`,a=e.get(i.info);this.plugin.log(o,!0),E(this.plugin,` +${o}`,3),this.plugin.betaPlugins.updatePlugin(i.info,!1,!0,!1,a==null?void 0:a.token)})}},{id:"reinstallOnePlugin",icon:"BratIcon",name:"Plugins: Choose a single plugin to reinstall",showInRibbon:!0,callback:()=>{let e=new Set(this.plugin.settings.pluginSubListFrozenVersion.map(i=>i.repo)),t=Object.values(this.plugin.settings.pluginList).filter(i=>!e.has(i)).map(i=>({display:i,info:i})),n=new S(this.plugin);n.setSuggesterData(t),n.display(i=>{let o=`Reinstalling ${i.info}`;E(this.plugin,` +${o}`,3),this.plugin.log(o,!0),this.plugin.betaPlugins.updatePlugin(i.info,!1,!1,!0)})}},{id:"restartPlugin",icon:"BratIcon",name:"Plugins: Restart a plugin that is already installed",showInRibbon:!0,callback:()=>{let e=Object.values(this.plugin.app.plugins.manifests).map(n=>({display:n.id,info:n.id})),t=new S(this.plugin);t.setSuggesterData(e),t.display(n=>{E(this.plugin,`${n.info} +Plugin reloading .....`,5),this.plugin.betaPlugins.reloadPlugin(n.info)})}},{id:"disablePlugin",icon:"BratIcon",name:"Plugins: Disable a plugin - toggle it off",showInRibbon:!0,callback:()=>{let e=this.plugin.betaPlugins.getEnabledDisabledPlugins(!0).map(n=>({display:`${n.name} (${n.id})`,info:n.id})),t=new S(this.plugin);t.setSuggesterData(e),t.display(n=>{this.plugin.log(`${n.display} plugin disabled`,!1),this.plugin.settings.debuggingMode&&console.log(n.info),this.plugin.app.plugins.disablePluginAndSave(n.info)})}},{id:"enablePlugin",icon:"BratIcon",name:"Plugins: Enable a plugin - toggle it on",showInRibbon:!0,callback:()=>{let e=this.plugin.betaPlugins.getEnabledDisabledPlugins(!1).map(n=>({display:`${n.name} (${n.id})`,info:n.id})),t=new S(this.plugin);t.setSuggesterData(e),t.display(n=>{this.plugin.log(`${n.display} plugin enabled`,!1),this.plugin.app.plugins.enablePluginAndSave(n.info)})}},{id:"openGitHubZRepository",icon:"BratIcon",name:"Plugins: Open the GitHub repository for a plugin",showInRibbon:!0,callback:async()=>{let e=await yt(this.plugin.settings.debuggingMode);if(e){let t=Object.values(e).map(o=>({display:`Plugin: ${o.name} (${o.repo})`,info:o.repo})),n=Object.values(this.plugin.settings.pluginList).map(o=>({display:`BRAT: ${o}`,info:o}));for(let o of t)n.push(o);let i=new S(this.plugin);i.setSuggesterData(n),i.display(o=>{o.info&&window.open(`https://github.com/${o.info}`)})}}},{id:"openGitHubRepoTheme",icon:"BratIcon",name:"Themes: Open the GitHub repository for a theme (appearance)",showInRibbon:!0,callback:async()=>{let e=await vt(this.plugin.settings.debuggingMode);if(e){let t=Object.values(e).map(i=>({display:`Theme: ${i.name} (${i.repo})`,info:i.repo})),n=new S(this.plugin);n.setSuggesterData(t),n.display(i=>{i.info&&window.open(`https://github.com/${i.info}`)})}}},{id:"opentPluginSettings",icon:"BratIcon",name:"Plugins: Open Plugin Settings Tab",showInRibbon:!0,callback:()=>{let e=this.plugin.app.setting,t=Object.values(e.pluginTabs).map(o=>({display:`Plugin: ${o.name}`,info:o.id})),n=new S(this.plugin),i=Object.values(e.settingTabs).map(o=>({display:`Core: ${o.name}`,info:o.id}));for(let o of t)i.push(o);n.setSuggesterData(i),n.display(o=>{e.open(),e.openTabById(o.info)})}},{id:"GrabBetaTheme",icon:"BratIcon",name:"Themes: Grab a beta theme for testing from a Github repository",showInRibbon:!0,callback:()=>{new B(this.plugin).open()}},{id:"updateBetaThemes",icon:"BratIcon",name:"Themes: Update beta themes",showInRibbon:!0,callback:async()=>{await q(this.plugin,!0)}},{id:"allCommands",icon:"BratIcon",name:"All Commands list",showInRibbon:!1,callback:()=>{this.ribbonDisplayCommands()}}];this.plugin=e;for(let t of this.bratCommands)this.plugin.addCommand({id:t.id,name:t.name,icon:t.icon,callback:()=>{t.callback()}})}ribbonDisplayCommands(){let e=[];for(let a of this.bratCommands)a.showInRibbon&&e.push({display:a.name,info:a.callback});let t=new S(this.plugin),n=this.plugin.app.setting,i=Object.values(n.settingTabs).map(a=>({display:`Core: ${a.name}`,info:()=>{n.open(),n.openTabById(a.id)}})),o=Object.values(n.pluginTabs).map(a=>({display:`Plugin: ${a.name}`,info:()=>{n.open(),n.openTabById(a.id)}}));e.push({display:"---- Core Plugin Settings ----",info:()=>{this.ribbonDisplayCommands()}});for(let a of i)e.push(a);e.push({display:"---- Plugin Settings ----",info:()=>{this.ribbonDisplayCommands()}});for(let a of o)e.push(a);t.setSuggesterData(e),t.display(a=>{typeof a.info=="function"&&a.info()})}};var A=require("obsidian");var Le=class extends A.PluginSettingTab{constructor(t,n){super(t,n);this.accessTokenSetting=null;this.accessTokenButton=null;this.tokenInfo=null;this.validator=null;this.plugin=n}display(){let{containerEl:t}=this;t.empty(),t.addClass("brat-settings"),new A.Setting(t).setName("Auto-enable plugins after installation").setDesc('If enabled beta plugins will be automatically enabled after installtion by default. Note: you can toggle this on and off for each plugin in the "Add Plugin" form.').addToggle(l=>{l.setValue(this.plugin.settings.enableAfterInstall).onChange(async r=>{this.plugin.settings.enableAfterInstall=r,await this.plugin.saveSettings()})}),new A.Setting(t).setName("Auto-update plugins at startup").setDesc("If enabled all beta plugins will be checked for updates each time Obsidian starts. Note: this does not update frozen version plugins.").addToggle(l=>{l.setValue(this.plugin.settings.updateAtStartup).onChange(async r=>{this.plugin.settings.updateAtStartup=r,await this.plugin.saveSettings()})}),new A.Setting(t).setName("Auto-update themes at startup").setDesc("If enabled all beta themes will be checked for updates each time Obsidian starts.").addToggle(l=>{l.setValue(this.plugin.settings.updateThemesAtStartup).onChange(async r=>{this.plugin.settings.updateThemesAtStartup=r,await this.plugin.saveSettings()})}),new A.Setting(t).setName("Select latest plugin version by default").setDesc("If enabled the latest version will be selected by default when adding a new plugin.").addToggle(l=>{l.setValue(this.plugin.settings.selectLatestPluginVersionByDefault).onChange(async r=>{this.plugin.settings.selectLatestPluginVersionByDefault=r,await this.plugin.saveSettings()})}),new A.Setting(t).setName("Allow incompatible plugins").setDesc("If enabled, plugins with higher app versions will be allowed to be installed. Also it allows desktop-only plugins to be installed on mobile devices.").addToggle(l=>{l.setValue(this.plugin.settings.allowIncompatiblePlugins).onChange(async r=>{this.plugin.settings.allowIncompatiblePlugins=r,await this.plugin.saveSettings()})}),z(t,!0),t.createEl("hr");let n=new Map(this.plugin.settings.pluginSubListFrozenVersion.map(l=>[l.repo,l])),i=new Map,o=new A.SettingGroup(t).setHeading("Beta plugin list");o.addSearch(l=>{l.setPlaceholder("Filter plugins"),l.onChange(r=>{let d=r.toLowerCase().trim();i.forEach(({container:b,pluginName:w})=>{d===""||w.includes(d)?b.removeAttribute("hidden"):b.setAttribute("hidden","true")})})}),o.addSetting(l=>{let r=document.createDocumentFragment();r.createEl("div",{text:'The following is a list of beta plugins added via the command "Add a beta plugin for testing". You can chose to add the latest version or a frozen version. A frozen version is a specific release of a plugin based on its release tag.'}),r.createEl("p"),r.createEl("div",{text:"Click the 'Edit' button next to a plugin to change the installed version and the x button next to a plugin to remove it from the list."}),r.createEl("p"),r.createEl("span").createEl("b",{text:"Note: "}),r.createSpan({text:"Removing from the list does not delete the plugin, this should be done from the Community Plugins tab in Settings."}),l.setDesc(r),l.addButton(d=>{d.setButtonText("Add beta plugin").setCta().onClick(()=>{this.plugin.betaPlugins.displayAddNewPluginModal(!0)})})});for(let l of this.plugin.settings.pluginList){let r=n.get(l);o.addSetting(d=>{let b=(r==null?void 0:r.tokenName)||"",w=b?this.plugin.app.secretStorage.getSecret(b):"",p=!!(b&&!w),T=document.createDocumentFragment(),C=r!=null&&r.version?` Tracked version: ${r.version} ${r.version==="latest"?"":"(frozen)"}`:"",N=r!=null&&r.isIncompatible?" (incompatible)":"";T.createDiv({text:`${C}${N}`}),p&&T.createDiv({text:` Secret not defined or empty: ${b}`,cls:"mod-warning",title:"Token name configured but secret is missing. Add the secret or update the plugin configuration."}),d.setName(ee(l)).setDesc(T);let R=d.settingEl;R.addClass("brat-plugin-item"),i.set(l,{container:R,pluginName:l.toLowerCase()}),(!(r!=null&&r.version)||r.version==="latest")&&d.addButton(P=>{p?P.setIcon("sync").setTooltip(`Secret missing: ${b}. Please add the secret or update the plugin configuration.`).setWarning().setDisabled(!0):P.setIcon("sync").setTooltip("Check and update plugin").onClick(async()=>{await this.plugin.betaPlugins.updatePlugin(l,!1,!0,!1,(r==null?void 0:r.tokenName)||"")})}),d.addButton(P=>{P.setIcon("edit").setTooltip("Change version and update settings"),p&&P.setWarning(),P.onClick(()=>{this.plugin.betaPlugins.displayAddNewPluginModal(!0,!0,l,r==null?void 0:r.version,(r==null?void 0:r.tokenName)||""),this.plugin.app.setting.updatePluginSection()})}).addButton(P=>{P.setIcon("cross").setTooltip("Remove this beta plugin").setWarning().onClick(()=>{if(P.buttonEl.textContent==="")P.setButtonText("Click once more to confirm removal");else{let{buttonEl:W}=P,{parentElement:M}=W;M!=null&&M.parentElement&&(M.parentElement.remove(),this.plugin.betaPlugins.deletePlugin(l))}})})})}let a=new Map,c=new A.SettingGroup(t).setHeading("Beta themes list");c.addSetting(l=>l.addButton(r=>{r.setButtonText("Add beta theme").setCta().onClick(()=>{this.plugin.app.setting.close(),new B(this.plugin).open()})})),c.addSearch(l=>{l.setPlaceholder("Filter themes"),l.onChange(r=>{let d=r.toLowerCase().trim();a.forEach(({container:b,themeName:w})=>{d===""||w.includes(d)?b.removeAttribute("hidden"):b.setAttribute("hidden","true")})})});for(let l of this.plugin.settings.themesList)c.addSetting(r=>{r.setName(ee(l.repo));let d=r.settingEl;d.addClass("brat-theme-item"),a.set(l.repo,{container:d,themeName:l.repo.toLowerCase()}),r.addButton(b=>{b.setIcon("cross").setTooltip("Delete this beta theme").onClick(()=>{if(b.buttonEl.textContent==="")b.setButtonText("Click once more to confirm removal");else{let{buttonEl:w}=b,{parentElement:p}=w;p!=null&&p.parentElement&&(p.parentElement.remove(),Ce(this.plugin,l.repo))}})})});new A.SettingGroup(t).setHeading("Monitoring").addSetting(l=>l.setName("Enable notifications").setDesc("BRAT will provide popup notifications for its various activities. Turn this off means no notifications from BRAT.").addToggle(r=>{r.setValue(this.plugin.settings.notificationsEnabled),r.onChange(async d=>{this.plugin.settings.notificationsEnabled=d,await this.plugin.saveSettings()})})).addSetting(l=>l.setName("Enable logging").setDesc("Plugin updates will be logged to a file in the log file.").addToggle(r=>{r.setValue(this.plugin.settings.loggingEnabled).onChange(async d=>{this.plugin.settings.loggingEnabled=d,await this.plugin.saveSettings()})})).addSetting(l=>l.setName("BRAT log file location").setDesc("Logs will be saved to this file. Don't add .md to the file name.").addSearch(r=>{r.setPlaceholder("Example: BRAT-log").setValue(this.plugin.settings.loggingPath).onChange(async d=>{this.plugin.settings.loggingPath=d,await this.plugin.saveSettings()})})).addSetting(l=>l.setName("Enable verbose logging").setDesc("Get a lot more information in the log.").addToggle(r=>{r.setValue(this.plugin.settings.loggingVerboseEnabled).onChange(async d=>{this.plugin.settings.loggingVerboseEnabled=d,await this.plugin.saveSettings()})})).addSetting(l=>l.setName("Debugging mode").setDesc("Atomic Bomb level console logging. Can be used for troubleshooting and development.").addToggle(r=>{r.setValue(this.plugin.settings.debuggingMode).onChange(async d=>{this.plugin.settings.debuggingMode=d,await this.plugin.saveSettings()})}));let g=new A.SettingGroup(t).setHeading("GitHub Personal Access Token"),m="";g.addSetting(l=>{l.setName("Personal access token").setDesc(Lt({prependText:"Set a personal access token to increase rate limits for public repositories on GitHub. You can create one in ",url:"https://github.com/settings/tokens/new?scopes=public_repo",text:"your GitHub account settings",appendText:" and then add it here. Please consult the documentation for more details."})),this.accessTokenSetting=new A.SecretComponent(this.plugin.app,l.controlEl),this.accessTokenSetting.setValue(this.plugin.settings.globalTokenName||"").onChange(async r=>{var b,w,p;let d=(r==null?void 0:r.trim())||"";this.plugin.settings.globalTokenName=d,await this.plugin.saveSettings(),d?(m=this.plugin.app.secretStorage.getSecret(d)||"",(b=this.accessTokenButton)==null||b.setDisabled(!1)):(m="",(w=this.accessTokenButton)==null||w.setDisabled(!0),await((p=this.validator)==null?void 0:p.validateToken("")))}),this.plugin.settings.globalTokenName&&(m=this.plugin.app.secretStorage.getSecret(this.plugin.settings.globalTokenName)||""),l.addExtraButton(r=>{r.setIcon("cross").setTooltip("Clear personal access token").onClick(async()=>{var d,b;this.plugin.settings.globalTokenName="",await this.plugin.saveSettings(),(d=this.accessTokenSetting)==null||d.setValue(""),m="",await((b=this.validator)==null?void 0:b.validateToken(""))})}).addButton(r=>{this.accessTokenButton=r,r.setButtonText("Validate").setCta().onClick(async()=>{var d;m&&await((d=this.validator)==null?void 0:d.validateToken(m))})}).then(()=>{var r;this.tokenInfo=this.createTokenInfoElement(t),this.validator=new j(this.tokenInfo),(r=this.validator)==null||r.validateToken(m).then(d=>{var b;(b=this.accessTokenButton)==null||b.setDisabled(d||!this.plugin.settings.globalTokenName)})})})}createTokenInfoElement(t){let n=t.createDiv({cls:"brat-token-info"});return n.createDiv({cls:"brat-token-status"}),n.createDiv({cls:"brat-token-details"}),n}};var ie=class{constructor(e){this.console=(e,...t)=>{console.log(`BRAT: ${e}`,...t)};this.themes={themeseCheckAndUpates:async e=>{await q(this.plugin,e)},themeInstallTheme:async e=>{let t=e.replace("https://github.com/","");await ne(this.plugin,t,!0)},themesDelete:e=>{let t=e.replace("https://github.com/","");Ce(this.plugin,t)},grabCommmunityThemeCssFile:async(e,t=!1)=>await G(e,t,this.plugin.settings.debuggingMode),grabChecksumOfThemeCssFile:async(e,t=!1)=>await Z(e,t,this.plugin.settings.debuggingMode),grabLastCommitDateForFile:async(e,t)=>await Rt(e,t)};this.plugin=e}};var de=require("obsidian"),Qt=ln(Kt());async function Zt(s,e,t=!1){if(s.settings.debuggingMode&&console.log(`BRAT: ${e}`),s.settings.loggingEnabled){if(!s.settings.loggingVerboseEnabled&&t)return;let n=`${s.settings.loggingPath}.md`,i=`[[${(0,de.moment)().format((0,Qt.getDailyNoteSettings)().format).toString()}]] ${(0,de.moment)().format("HH:mm")}`,o=window.require("os"),a=de.Platform.isDesktop?o.hostname():"MOBILE",c=`${i} ${a} ${e.replace(` +`," ")} +`,u=s.app.vault.getAbstractFileByPath(n);u?await s.app.vault.append(u,c):u=await s.app.vault.create(n,c)}}var ke=class extends en.Plugin{constructor(){super(...arguments);this.APP_NAME="BRAT";this.APP_ID="obsidian42-brat";this.settings=je;this.betaPlugins=new te(this);this.commands=new se(this);this.bratApi=new ie(this);this.obsidianProtocolHandler=t=>{if(!t.plugin&&!t.theme){E(this,"Could not locate the repository from the URL.",10);return}for(let n of["plugin","theme"])if(t[n]){let i;switch(n){case"plugin":i=new U(this,this.betaPlugins,!0,!1,t[n],t.version?t.version:void 0),i.open();break;case"theme":i=new B(this),i.address=t[n],i.open();break}return}}}onload(){console.log(`loading ${this.APP_NAME}`),Ft(),this.addRibbonIcon("BratIcon","BRAT",()=>{this.commands.ribbonDisplayCommands()}),this.loadSettings().then(async()=>{await xt(this.app,this.settings,()=>this.saveSettings()),this.app.workspace.onLayoutReady(()=>{this.addSettingTab(new Le(this.app,this)),this.registerObsidianProtocolHandler("brat",this.obsidianProtocolHandler),this.betaPlugins.checkIncompatiblePlugins(),this.settings.updateAtStartup&&setTimeout(()=>{this.betaPlugins.checkForPluginUpdatesAndInstallUpdates(!1)},6e4),this.settings.updateThemesAtStartup&&setTimeout(()=>{q(this,!1)},12e4),setTimeout(()=>{window.bratAPI=this.bratApi},500)})}).catch(t=>{console.error("Failed to load settings:",t)})}async log(t,n=!1){await Zt(this,t,n)}onunload(){console.log(`unloading ${this.APP_NAME}`)}async loadSettings(){this.settings=Object.assign({},je,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; + +/* nosourcemap */ \ No newline at end of file diff --git a/.obsidian/plugins/obsidian42-brat/manifest.json b/.obsidian/plugins/obsidian42-brat/manifest.json new file mode 100644 index 0000000..e7591a9 --- /dev/null +++ b/.obsidian/plugins/obsidian42-brat/manifest.json @@ -0,0 +1,14 @@ +{ + "id": "obsidian42-brat", + "name": "BRAT", + "version": "2.0.2", + "minAppVersion": "1.11.4", + "description": "Easily install a beta version of a plugin for testing.", + "author": "TfTHacker", + "authorUrl": "https://github.com/TfTHacker/obsidian42-brat", + "helpUrl": "https://tfthacker.com/BRAT", + "isDesktopOnly": false, + "fundingUrl": { + "Visit my site": "https://tfthacker.com" + } +} diff --git a/.obsidian/plugins/obsidian42-brat/styles.css b/.obsidian/plugins/obsidian42-brat/styles.css new file mode 100644 index 0000000..b9a3684 --- /dev/null +++ b/.obsidian/plugins/obsidian42-brat/styles.css @@ -0,0 +1,152 @@ +.brat-modal .modal-button-container { + margin-top: 5px; +} + +.brat-modal .disabled-setting { + opacity: 0.5; +} + +.brat-modal .disabled-setting:hover { + cursor: not-allowed; +} + +/* Input validation styles */ +.brat-settings .valid-input, +.brat-modal .valid-repository { + border-color: var(--color-green); +} +.brat-settings .invalid-input, +.brat-modal .invalid-repository { + border-color: var(--color-red); +} +.brat-settings .validation-error, +.brat-modal .validation-error { + border-color: var(--color-orange); +} + +/* Version selector */ +.brat-version-selector { + width: 100%; + max-width: 400px; + justify-content: left; +} + +.brat-token-input { + min-width: 33%; +} + +/* Token info container styles */ +.brat-token-info { + margin-top: 8px; + font-size: 0.8em; + padding: 8px; + border-radius: 4px; + background-color: var(--background-secondary); +} + +/* Token status indicators */ +.brat-token-info.valid, +.brat-token-status.valid { + color: var(--color-green); +} + +.brat-token-info.invalid, +.brat-token-status.invalid { + color: var(--color-red); +} + +.brat-token-info.valid { + border-left: 3px solid var(--color-green); +} + +.brat-token-info.invalid { + border-left: 3px solid var(--color-red); +} + +/* Token details and status */ +.brat-token-status { + margin-bottom: 4px; +} + +.brat-token-details { + margin-top: 4px; + color: var(--text-muted); +} + +/* Token warnings */ +.brat-token-warning { + color: var(--color-orange); + margin-top: 4px; +} + +/* Token additional info */ +.brat-token-scopes, +.brat-token-rate { + color: var(--text-muted); + margin-top: 2px; +} + +/* Flex break utility */ +.brat-modal .break { + flex-basis: 100%; + height: 0; +} + +/* Validation status */ +.brat-modal .validation-status-error { + color: var(--text-error); +} + +.brat-modal .validation-status { + margin-top: 0.5em; + margin-bottom: 0.5em; + font-size: 0.8em; + text-align: left; +} + +.confirm-modal .ok-button { + margin-right: 10px; + margin-top: 20px; +} + +/* Hide filtered plugin items */ +.brat-plugin-item[hidden] { + display: none !important; +} + +/* Hide filtered theme items */ +.brat-theme-item[hidden] { + display: none !important; +} + +/* Filter and button layout */ +.brat-filter-and-button { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin: 0.75em 0; +} + +.brat-filter-input { + max-width: 300px; + padding: 4px 8px; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + background-color: var(--background-secondary); + color: var(--text-normal); +} + +.brat-filter-input:focus { + outline: none; + border-color: var(--interactive-accent); +} + +.brat-filter-and-button .setting-item { + border: none; + padding: 0; +} + +.brat-filter-and-button .setting-item-control { + justify-content: flex-end; +} diff --git a/.obsidian/plugins/qmd-search/data.json b/.obsidian/plugins/qmd-search/data.json new file mode 100644 index 0000000..33f5dd0 --- /dev/null +++ b/.obsidian/plugins/qmd-search/data.json @@ -0,0 +1,21 @@ +{ + "qmdBinaryPath": "/opt/homebrew/bin/qmd", + "collectionName": "", + "indexName": null, + "fileMask": "**/*.md", + "debounceMs": 45000, + "enablePeriodicUpdates": true, + "periodicUpdateMinutes": 15, + "defaultSearchMode": "semantic", + "fallbackOnSemanticFailure": true, + "fallbackOnZeroResults": false, + "showEmbeddingsBanner": true, + "autoGenerateEmbeddings": true, + "enableRibbonIcon": true, + "enableSearchPane": false, + "showScoresInResults": true, + "lastIndexUpdateTime": "2026-02-26T22:48:31.431Z", + "lastEmbeddingRunTime": "2026-02-26T22:48:32.734Z", + "lastSearchMode": "semantic", + "lastError": null +} \ No newline at end of file diff --git a/.obsidian/plugins/qmd-search/main.js b/.obsidian/plugins/qmd-search/main.js new file mode 100644 index 0000000..471d47a --- /dev/null +++ b/.obsidian/plugins/qmd-search/main.js @@ -0,0 +1,8 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ + +var M=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var T=(h,i)=>{for(var e in i)M(h,e,{get:i[e],enumerable:!0})},N=(h,i,e,t)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of k(i))!q.call(h,s)&&s!==e&&M(h,s,{get:()=>i[s],enumerable:!(t=I(i,s))||t.enumerable});return h};var B=h=>N(M({},"__esModule",{value:!0}),h);var F={};T(F,{default:()=>w});module.exports=B(F);var d=require("obsidian");var y={qmdBinaryPath:"qmd",collectionName:"",indexName:null,fileMask:"**/*.md",debounceMs:45e3,enablePeriodicUpdates:!0,periodicUpdateMinutes:15,defaultSearchMode:"semantic",fallbackOnSemanticFailure:!0,fallbackOnZeroResults:!1,showEmbeddingsBanner:!0,autoGenerateEmbeddings:!0,enableRibbonIcon:!0,enableSearchPane:!1,showScoresInResults:!0,lastIndexUpdateTime:null,lastEmbeddingRunTime:null,lastSearchMode:null,lastError:null};function D(h){return h.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}var E=require("child_process"),P=require("util"),x=(0,P.promisify)(E.exec),u=class extends Error{constructor(i,e,t){super(i),this.name="QMDError",this.type=e,this.stderr=t}},f=class{constructor(i,e,t,s,n){this.commandQueue=[];this.isProcessing=!1;this.currentSearchProcess=null;this.binaryPath=i,this.collectionName=e,this.indexName=t,this.vaultPath=s,this.execAsync=n!=null?n:x}abortSearch(){this.currentSearchProcess&&(this.currentSearchProcess.kill(),this.currentSearchProcess=null)}updateConfig(i,e,t){this.binaryPath=i,this.collectionName=e,this.indexName=t}buildBinaryWithIndex(){let i=`"${this.binaryPath}"`;return this.indexName&&(i+=` --index "${this.indexName}"`),i}buildBaseCommand(i){return`${this.buildBinaryWithIndex()} ${i}`}async queueCommand(i){return new Promise((e,t)=>{let s=async()=>{try{let n=await i();e(n)}catch(n){t(n instanceof Error?n:new Error(String(n)))}};this.commandQueue.push(s),this.processQueue()})}async processQueue(){if(!(this.isProcessing||this.commandQueue.length===0)){for(this.isProcessing=!0;this.commandQueue.length>0;){let i=this.commandQueue.shift();i&&await i()}this.isProcessing=!1}}async execCommand(i){var e,t,s,n;try{let{stdout:r,stderr:o}=await this.execAsync(i,{maxBuffer:10485760,cwd:this.vaultPath});return{stdout:r,stderr:o}}catch(r){let o=r;throw o.code===127||(e=o.message)!=null&&e.includes("not found")?new u(`QMD binary not found at: ${this.binaryPath}`,"not_found",o.stderr):(t=o.stderr)!=null&&t.includes("no collection")?new u(`Collection "${this.collectionName}" not found`,"no_collection",o.stderr):(s=o.stderr)!=null&&s.includes("no embeddings")||(n=o.stderr)!=null&&n.includes("embeddings not found")?new u("Embeddings not generated for this collection","no_embeddings",o.stderr):new u(o.message||"Unknown QMD error","execution_error",o.stderr)}}execSearchCommand(i){return this.execAsync!==x?this.execCommand(i):new Promise((e,t)=>{this.currentSearchProcess=(0,E.exec)(i,{maxBuffer:10*1024*1024,cwd:this.vaultPath},(s,n,r)=>{var o;if(this.currentSearchProcess=null,s){if(s.killed||s.signal==="SIGTERM"){t(new u("Search cancelled","execution_error"));return}let a=s;if(a.code===127||(o=a.message)!=null&&o.includes("not found")){t(new u(`QMD binary not found at: ${this.binaryPath}`,"not_found",r));return}if(r!=null&&r.includes("no collection")){t(new u(`Collection "${this.collectionName}" not found`,"no_collection",r));return}if(r!=null&&r.includes("no embeddings")||r!=null&&r.includes("embeddings not found")){t(new u("Embeddings not generated for this collection","no_embeddings",r));return}t(new u(a.message||"Unknown QMD error","execution_error",r));return}e({stdout:n,stderr:r})})})}async testConnection(){return this.queueCommand(async()=>{try{let i=this.buildBaseCommand("status"),{stdout:e,stderr:t}=await this.execCommand(i);return{success:!0,data:this.parseStatusOutput(e),stderr:t}}catch(i){let e=i;return{success:!1,error:e.message,stderr:e.stderr}}})}parseStatusOutput(i){let e=i.match(/Total:\s+(\d+)\s+files?\s+indexed/i),t=e?parseInt(e[1],10):0,s=i.match(/Vectors:\s+(\d+)\s+embedded/i),n=s?parseInt(s[1],10):0,r=i.includes(this.collectionName)||i.includes(`qmd://${this.collectionName}/`),o=i.toLowerCase().includes("no collections");return{collection:r?this.collectionName:"",path:this.vaultPath,fileCount:t,indexedCount:t,embeddingsCount:n,hasEmbeddings:n>0,hasCollection:r&&!o}}async ensureCollection(i){return this.queueCommand(async()=>{try{let e=`${this.buildBinaryWithIndex()} collection list`;try{let{stdout:n}=await this.execCommand(e);if(n.includes(this.collectionName)||n.includes(`qmd://${this.collectionName}/`))return{success:!0}}catch(n){}let t=`${this.buildBinaryWithIndex()} collection add "${this.vaultPath}" --name "${this.collectionName}" --mask "${i}"`,{stderr:s}=await this.execCommand(t);return{success:!0,stderr:s}}catch(e){let t=e;return{success:!1,error:t.message,stderr:t.stderr}}})}async updateIndex(){return this.queueCommand(async()=>{try{let i=this.buildBaseCommand("update"),{stderr:e}=await this.execCommand(i);return{success:!0,stderr:e}}catch(i){let e=i;return{success:!1,error:e.message,stderr:e.stderr}}})}async generateEmbeddings(i=!1){return this.queueCommand(async()=>{try{let e=this.buildBaseCommand("embed");i&&(e+=" -f");let{stderr:t}=await this.execCommand(e);return{success:!0,stderr:t}}catch(e){let t=e;return{success:!1,error:t.message,stderr:t.stderr}}})}parseSearchResults(i){return i.map(e=>{let t=e.file;if(t.startsWith("qmd://")){let n=t.substring(6),r=n.indexOf("/");r!==-1&&(t=n.substring(r+1))}let s=e.snippet;return s&&(s=s.replace(/@@ -\d+,\d+ @@\s*\(\d+ before, \d+ after\)\s*/g,"").trim()),{path:t,score:e.score,title:e.title,snippet:s,docid:e.docid}})}extractJsonArray(i){let e=i.indexOf("[");if(e===-1)return"[]";let t=0,s=e;for(let n=e;n{try{let e=i.replace(/"/g,'\\"'),t=this.buildBaseCommand("vsearch")+` "${e}" --json`,{stdout:s,stderr:n}=await this.execSearchCommand(t);try{let r=this.extractJsonArray(s),o=JSON.parse(r);return{success:!0,data:this.parseSearchResults(o),stderr:n}}catch(r){throw new u("Failed to parse search results","parse_error",s)}}catch(e){let t=e;return{success:!1,error:t.message,stderr:t.stderr}}})}async keywordSearch(i){return this.queueCommand(async()=>{try{let e=i.replace(/"/g,'\\"'),t=this.buildBaseCommand("search")+` "${e}" --json`,{stdout:s,stderr:n}=await this.execSearchCommand(t);try{let r=this.extractJsonArray(s),o=JSON.parse(r);return{success:!0,data:this.parseSearchResults(o),stderr:n}}catch(r){throw new u("Failed to parse search results","parse_error",s)}}catch(e){let t=e;return{success:!1,error:t.message,stderr:t.stderr}}})}async hasEmbeddings(){var e;let i=await this.testConnection();return i.success&&((e=i.data)==null?void 0:e.hasEmbeddings)===!0}getQueueLength(){return this.commandQueue.length}isBusy(){return this.isProcessing}};var g=require("obsidian"),b=class extends g.SuggestModal{constructor(e,t,s,n){super(e);this.currentQuery="";this.lastCompletedQuery="";this.lastResults=[];this.isSearching=!1;this.searchId=0;this.usedFallback=!1;this.searchError=null;this.progressBar=null;this.searchModeIndicator=null;this.qmd=t,this.settings=s,this.onResultSelect=n.onResultSelect,this.onSearchModeUsed=n.onSearchModeUsed,this.onError=n.onError,this.setPlaceholder("Search your vault with QMD..."),this.setInstructions([{command:"\u2191\u2193",purpose:"Navigate"},{command:"\u21B5",purpose:"Open file"},{command:"esc",purpose:"Close"}]),this.debouncedSearch=(0,g.debounce)(r=>this.performSearch(r),1e3,!1)}async onOpen(){await super.onOpen(),this.injectStatusElements();let e=this.getInputElement();e&&(e.maxLength=50)}injectStatusElements(){let e=this.containerEl.querySelector(".prompt-input-container");e&&(this.progressBar||(this.progressBar=createDiv({cls:"qmd-progress-container qmd-hidden"}),this.progressBar.createDiv({cls:"qmd-progress-bar"}).createDiv({cls:"qmd-progress-bar-fill"}),this.progressBar.createSpan({cls:"qmd-progress-text",text:"Searching..."}),e.insertAdjacentElement("afterend",this.progressBar)),this.searchModeIndicator||(this.searchModeIndicator=createDiv({cls:"qmd-search-mode-indicator qmd-hidden"}),e.appendChild(this.searchModeIndicator)))}showProgressBar(){var e;(e=this.progressBar)==null||e.removeClass("qmd-hidden")}hideProgressBar(){var e;(e=this.progressBar)==null||e.addClass("qmd-hidden")}updateSearchModeIndicator(e,t){if(!this.searchModeIndicator)return;let s=t?"keyword (fallback)":e;this.searchModeIndicator.textContent=s,this.searchModeIndicator.removeClass("qmd-hidden");let n=this.getInputElement();n&&n.addClass("qmd-input-with-pill")}hideSearchModeIndicator(){var t;(t=this.searchModeIndicator)==null||t.addClass("qmd-hidden");let e=this.getInputElement();e&&e.removeClass("qmd-input-with-pill")}getSuggestions(e){return this.currentQuery=e,!e||e.trim().length<2?(this.searchError=null,this.lastCompletedQuery="",this.lastResults=[],this.hideProgressBar(),this.hideSearchModeIndicator(),[]):(this.isSearching&&(this.qmd.abortSearch(),this.searchId++),e!==this.lastCompletedQuery&&this.debouncedSearch(e),this.searchError&&this.lastResults.length===0?[{path:"",score:0,title:`Error: ${this.searchError}`,snippet:"Check that QMD is installed and accessible. Use 'Test QMD' in settings.",searchMode:this.settings.defaultSearchMode,isFallback:!1}]:this.lastResults)}async performSearch(e){var s;if(e!==this.currentQuery)return;let t=++this.searchId;this.isSearching=!0,this.usedFallback=!1,this.searchError=null,this.showProgressBar();try{let n=[],r=this.settings.defaultSearchMode;if(r==="semantic"){let a=await this.qmd.semanticSearch(e);if(t!==this.searchId)return;if(a.success&&a.data){if(a.data.length>0||!this.settings.fallbackOnZeroResults)n=this.mapResults(a.data,"semantic",!1);else if(this.settings.fallbackOnZeroResults){let l=await this.qmd.keywordSearch(e);if(t!==this.searchId)return;l.success&&l.data&&(n=this.mapResults(l.data,"keyword",!0),this.usedFallback=!0,r="keyword")}}else if(this.settings.fallbackOnSemanticFailure){(s=a.error)!=null&&s.includes("embeddings")&&this.showEmbeddingsNotice();let l=await this.qmd.keywordSearch(e);if(t!==this.searchId)return;l.success&&l.data&&(n=this.mapResults(l.data,"keyword",!0),this.usedFallback=!0,r="keyword",new g.Notice("Semantic search unavailable \u2014 using keyword search."))}else this.searchError=a.error||"Search failed",this.onError(this.searchError)}else{let a=await this.qmd.keywordSearch(e);if(t!==this.searchId)return;a.success&&a.data?n=this.mapResults(a.data,"keyword",!1):(this.searchError=a.error||"Search failed",this.onError(this.searchError))}this.lastResults=n,this.lastCompletedQuery=e,this.isSearching=!1,this.hideProgressBar(),n.length>0?this.updateSearchModeIndicator(r,this.usedFallback):this.hideSearchModeIndicator(),this.onSearchModeUsed(r,this.usedFallback);let o=this.getInputElement();if(o&&o.value===e){let a=this.containerEl.querySelector(".suggestion-container"),l=(a==null?void 0:a.scrollTop)||0;o.dispatchEvent(new Event("input")),a&&l>0&&queueMicrotask(()=>{a.scrollTop=l})}}catch(n){if(t!==this.searchId)return;let r=n instanceof Error?n.message:"Unknown error";this.searchError=r,this.onError(r)}finally{t===this.searchId&&(this.isSearching=!1,this.hideProgressBar())}}toSlug(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}buildSlugMap(){let e=new Map;for(let t of this.app.vault.getFiles()){let s=this.toSlug(t.basename);e.has(s)||e.set(s,t);let n=this.toSlug(t.path.replace(/\.md$/i,""));e.has(n)||e.set(n,t)}return e}mapResults(e,t,s){let n=this.buildSlugMap();return e.map(r=>{let o;if(r.title&&(o=this.app.vault.getFiles().find(a=>a.basename===r.title)),!o){let a=this.toSlug(r.path.replace(/\.md$/i,""));o=n.get(a)}return{...r,file:o,searchMode:t,isFallback:s}})}showEmbeddingsNotice(){this.settings.showEmbeddingsBanner&&new g.Notice("Semantic search requires embeddings. Run 'QMD: Generate embeddings' to enable.",1e4)}renderSuggestion(e,t){var o;let s=e.path===""&&((o=e.title)==null?void 0:o.startsWith("Error:")),n=t.createDiv({cls:s?"qmd-search-error":"qmd-search-result"}),r=n.createDiv({cls:"qmd-search-result-title"});if(r.createSpan({text:e.title||e.path.replace(/\.md$/i,"").split("/").pop()||e.path,cls:s?"qmd-search-error-title":"qmd-search-result-name"}),!s&&this.settings.showScoresInResults){let a=Math.round(e.score*100);r.createSpan({text:` (Score: ${a}%)`,cls:"qmd-search-result-score"})}e.snippet&&n.createDiv({text:e.snippet,cls:s?"qmd-search-error-hint":"qmd-search-result-snippet"})}onChooseSuggestion(e,t){var s;e.path===""&&((s=e.title)!=null&&s.startsWith("Error:"))||this.onResultSelect(e)}getInputElement(){return this.containerEl.querySelector("input")}};var m=require("obsidian"),p="qmd-search-view",v=class extends m.ItemView{constructor(e,t,s,n){super(e);this.searchInput=null;this.resultsContainer=null;this.statusContainer=null;this.currentQuery="";this.isSearching=!1;this.qmd=t,this.settings=s,this.onResultSelect=n.onResultSelect,this.onSearchModeUsed=n.onSearchModeUsed,this.onError=n.onError,this.debouncedSearch=(0,m.debounce)(r=>{this.performSearch(r)},300,!0)}getViewType(){return p}getDisplayText(){return"QMD search"}getIcon(){return"search"}async onOpen(){await super.onOpen();let e=this.containerEl.children[1];e.empty(),e.addClass("qmd-search-pane");let s=e.createDiv({cls:"qmd-search-input-container"}).createDiv({cls:"qmd-search-input-wrapper"}),n=s.createSpan({cls:"qmd-search-icon"});(0,m.setIcon)(n,"search");let r=s.createEl("input",{type:"text",placeholder:"Search with QMD...",cls:"qmd-search-input"});r.addEventListener("input",o=>{let a=o.target.value;this.currentQuery=a,a.trim().length>=2?this.debouncedSearch(a):this.clearResults()}),r.addEventListener("keydown",o=>{o.key==="Enter"&&this.currentQuery.trim().length>=2&&this.performSearch(this.currentQuery)}),this.statusContainer=e.createDiv({cls:"qmd-search-status"}),this.resultsContainer=e.createDiv({cls:"qmd-search-results"})}async onClose(){}updateReferences(e,t){this.qmd=e,this.settings=t}async performSearch(e){var t;if(!this.isSearching&&!(!e||e.trim().length<2)){this.isSearching=!0,this.showStatus("Searching...","loading");try{let s=[],n=this.settings.defaultSearchMode,r=!1;if(n==="semantic"){let a=await this.qmd.semanticSearch(e);if(a.success&&a.data){if(a.data.length>0||!this.settings.fallbackOnZeroResults)s=a.data;else if(this.settings.fallbackOnZeroResults){let l=await this.qmd.keywordSearch(e);l.success&&l.data&&(s=l.data,r=!0,n="keyword")}}else if(this.settings.fallbackOnSemanticFailure){(t=a.error)!=null&&t.includes("embeddings")&&this.showEmbeddingsNotice();let l=await this.qmd.keywordSearch(e);l.success&&l.data&&(s=l.data,r=!0,n="keyword",new m.Notice("Semantic search unavailable \u2014 using keyword search."))}else throw new Error(a.error||"Search failed")}else{let a=await this.qmd.keywordSearch(e);if(a.success&&a.data)s=a.data;else throw new Error(a.error||"Search failed")}this.onSearchModeUsed(n,r),this.renderResults(s,n,r);let o=r?`${s.length} results (keyword fallback)`:`${s.length} results (${n})`;this.showStatus(o,"success")}catch(s){let n=s instanceof Error?s.message:"Unknown error";this.onError(n),this.showStatus(`Error: ${n}`,"error")}finally{this.isSearching=!1}}}showEmbeddingsNotice(){this.settings.showEmbeddingsBanner&&new m.Notice("Semantic search requires embeddings. Run 'QMD: Generate embeddings' to enable.",1e4)}renderResults(e,t,s){if(this.resultsContainer){if(this.resultsContainer.empty(),e.length===0){this.resultsContainer.createDiv({text:"No results found",cls:"qmd-search-no-results"});return}for(let n of e){let r=this.resultsContainer.createDiv({cls:"qmd-search-result-item"}),o=this.app.vault.getFiles().find(C=>C.path===n.path||C.path.endsWith(n.path)),a={...n,file:o,searchMode:t,isFallback:s};r.createDiv({cls:"qmd-result-title"}).createSpan({text:n.title||n.path.split("/").pop()||n.path}),n.title&&r.createDiv({text:n.path,cls:"qmd-result-path"}),n.snippet&&r.createDiv({text:n.snippet,cls:"qmd-result-snippet"}),this.settings.showScoresInResults&&r.createDiv({text:`Score: ${n.score.toFixed(3)}`,cls:"qmd-result-score"}),r.addEventListener("click",()=>{this.onResultSelect(a)})}}}clearResults(){this.resultsContainer&&this.resultsContainer.empty(),this.hideStatus()}showStatus(e,t){if(this.statusContainer){if(this.statusContainer.empty(),this.statusContainer.removeClass("qmd-status-loading","qmd-status-success","qmd-status-error"),this.statusContainer.addClass(`qmd-status-${t}`),t==="loading"){let s=this.statusContainer.createDiv({cls:"qmd-pane-loading-row"});s.createSpan({cls:"qmd-pane-spinner"}),s.createSpan({text:e})}else this.statusContainer.setText(e);this.statusContainer.show()}}hideStatus(){this.statusContainer&&this.statusContainer.hide()}};var c=require("obsidian"),S=class extends c.PluginSettingTab{constructor(i,e){super(i,e),this.plugin=e}display(){let{containerEl:i}=this;i.empty(),new c.Setting(i).setName("QMD configuration").setHeading(),new c.Setting(i).setName("QMD binary path").setDesc("Path to the QMD executable. Use 'qmd' if it's in your PATH.").addText(t=>t.setPlaceholder("qmd").setValue(this.plugin.settings.qmdBinaryPath).onChange(async s=>{this.plugin.settings.qmdBinaryPath=s||"qmd",await this.plugin.saveSettings()})),new c.Setting(i).setName("Collection name").setDesc("Name for the QMD collection. Leave empty to use vault name.").addText(t=>t.setPlaceholder("(derived from vault name)").setValue(this.plugin.settings.collectionName).onChange(async s=>{this.plugin.settings.collectionName=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Index name").setDesc("Optional QMD index name override. Leave empty for default.").addText(t=>t.setPlaceholder("(default)").setValue(this.plugin.settings.indexName||"").onChange(async s=>{this.plugin.settings.indexName=s||null,await this.plugin.saveSettings()})),new c.Setting(i).setName("File mask").setDesc("Glob pattern for markdown files to index.").addText(t=>t.setPlaceholder("**/*.md").setValue(this.plugin.settings.fileMask).onChange(async s=>{this.plugin.settings.fileMask=s||"**/*.md",await this.plugin.saveSettings()})),new c.Setting(i).setName("Test QMD connection").setDesc("Verify that QMD is installed and accessible.").addButton(t=>t.setButtonText("Test QMD").setCta().onClick(async()=>{t.setButtonText("Testing..."),t.setDisabled(!0);let s=await this.plugin.testQMDConnection();s.success?(new c.Notice("QMD is working correctly."),s.data&&new c.Notice(`Collection: ${s.data.collection} +Files: ${s.data.fileCount} +Embeddings: ${s.data.hasEmbeddings?"Yes":"No"}`)):new c.Notice(`QMD error: ${s.error}`,1e4),t.setButtonText("Test QMD"),t.setDisabled(!1)})),new c.Setting(i).setName("Indexing & updates").setHeading(),new c.Setting(i).setName("Debounce delay (ms)").setDesc("Wait time after file changes before updating index.").addSlider(t=>t.setLimits(1e3,12e4,1e3).setValue(this.plugin.settings.debounceMs).setDynamicTooltip().onChange(async s=>{this.plugin.settings.debounceMs=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Enable periodic updates").setDesc("Automatically run index updates on a timer.").addToggle(t=>t.setValue(this.plugin.settings.enablePeriodicUpdates).onChange(async s=>{this.plugin.settings.enablePeriodicUpdates=s,await this.plugin.saveSettings(),this.plugin.setupPeriodicUpdates()})),new c.Setting(i).setName("Periodic update interval (minutes)").setDesc("How often to run automatic index updates.").addSlider(t=>t.setLimits(5,120,5).setValue(this.plugin.settings.periodicUpdateMinutes).setDynamicTooltip().onChange(async s=>{this.plugin.settings.periodicUpdateMinutes=s,await this.plugin.saveSettings(),this.plugin.setupPeriodicUpdates()})),new c.Setting(i).setName("Search behavior").setHeading(),new c.Setting(i).setName("Default search mode").setDesc("Primary search method. Semantic uses AI embeddings, keyword uses BM25.").addDropdown(t=>t.addOption("semantic","Semantic (AI)").addOption("keyword","Keyword (BM25)").setValue(this.plugin.settings.defaultSearchMode).onChange(async s=>{this.plugin.settings.defaultSearchMode=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Fallback on semantic failure").setDesc("Use keyword search if semantic search fails or has no embeddings.").addToggle(t=>t.setValue(this.plugin.settings.fallbackOnSemanticFailure).onChange(async s=>{this.plugin.settings.fallbackOnSemanticFailure=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Fallback on zero results").setDesc("Use keyword search if semantic search returns no results.").addToggle(t=>t.setValue(this.plugin.settings.fallbackOnZeroResults).onChange(async s=>{this.plugin.settings.fallbackOnZeroResults=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Show embeddings banner").setDesc("Display a notice when semantic search is unavailable due to missing embeddings.").addToggle(t=>t.setValue(this.plugin.settings.showEmbeddingsBanner).onChange(async s=>{this.plugin.settings.showEmbeddingsBanner=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Auto-generate embeddings").setDesc("Automatically generate embeddings when missing. First run downloads ~3GB of local AI models.").addToggle(t=>t.setValue(this.plugin.settings.autoGenerateEmbeddings).onChange(async s=>{this.plugin.settings.autoGenerateEmbeddings=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("User interface").setHeading(),new c.Setting(i).setName("Show ribbon icon").setDesc("Display QMD search icon in the left sidebar.").addToggle(t=>t.setValue(this.plugin.settings.enableRibbonIcon).onChange(async s=>{this.plugin.settings.enableRibbonIcon=s,await this.plugin.saveSettings(),this.plugin.setupRibbonIcon()})),new c.Setting(i).setName("Enable search pane").setDesc("Allow opening QMD search as a persistent sidebar pane.").addToggle(t=>t.setValue(this.plugin.settings.enableSearchPane).onChange(async s=>{this.plugin.settings.enableSearchPane=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Show scores in results").setDesc("Display numeric relevance scores in search results.").addToggle(t=>t.setValue(this.plugin.settings.showScoresInResults).onChange(async s=>{this.plugin.settings.showScoresInResults=s,await this.plugin.saveSettings()})),new c.Setting(i).setName("Diagnostics").setHeading();let e=i.createDiv({cls:"qmd-diagnostics"});this.renderDiagnostics(e),new c.Setting(i).setName("Actions").setHeading(),new c.Setting(i).setName("Update index now").setDesc("Manually trigger an index update.").addButton(t=>t.setButtonText("Update index").onClick(async()=>{t.setButtonText("Updating..."),t.setDisabled(!0),await this.plugin.updateIndexNow(),t.setButtonText("Update index"),t.setDisabled(!1)})),new c.Setting(i).setName("Generate embeddings").setDesc("Build AI embeddings for semantic search. This may take a while for large vaults.").addButton(t=>t.setButtonText("Generate embeddings").onClick(async()=>{t.setButtonText("Generating..."),t.setDisabled(!0),await this.plugin.generateEmbeddings(!1),t.setButtonText("Generate embeddings"),t.setDisabled(!1)})),new c.Setting(i).setName("Force rebuild embeddings").setDesc("Rebuild all embeddings from scratch. Use if embeddings seem corrupted.").addButton(t=>t.setButtonText("Force rebuild").setWarning().onClick(async()=>{t.setButtonText("Rebuilding..."),t.setDisabled(!0),await this.plugin.generateEmbeddings(!0),t.setButtonText("Force rebuild"),t.setDisabled(!1)})),new c.Setting(i).setName("Ensure collection exists").setDesc("Create the QMD collection if it doesn't exist.").addButton(t=>t.setButtonText("Ensure collection").onClick(async()=>{t.setButtonText("Checking..."),t.setDisabled(!0),await this.plugin.ensureCollection(),t.setButtonText("Ensure collection"),t.setDisabled(!1)}))}renderDiagnostics(i){i.empty();let e=this.plugin.settings,t=[{label:"Last index update",value:e.lastIndexUpdateTime||"Never"},{label:"Last embedding run",value:e.lastEmbeddingRunTime||"Never"},{label:"Last search mode",value:e.lastSearchMode||"N/A"},{label:"Last error",value:e.lastError||"None"}];for(let s of t){let n=i.createDiv({cls:"qmd-diagnostic-row"});n.createSpan({text:s.label+":",cls:"qmd-diagnostic-label"}),n.createSpan({text:s.value,cls:"qmd-diagnostic-value"})}}};var R=require("fs"),Q=require("os"),U=["qmd",`${(0,Q.homedir)()}/.bun/bin/qmd`,"/usr/local/bin/qmd","/opt/homebrew/bin/qmd","/usr/bin/qmd"],w=class extends d.Plugin{constructor(e,t){super(e,t);this.settings=y;this.qmd=null;this.ribbonIcon=null;this.periodicUpdateInterval=null;this.debouncedUpdate=null;this.hasPendingChanges=!1}async onload(){if(await this.loadSettings(),!this.isDesktop()){new d.Notice("QMD requires desktop filesystem access \u2014 plugin disabled on mobile.");return}this.initializeQMD(),this.registerView(p,e=>new v(e,this.qmd,this.settings,{onResultSelect:t=>{this.openSearchResult(t)},onSearchModeUsed:(t,s)=>{this.recordSearchMode(t,s)},onError:t=>{this.recordError(t)}})),this.registerCommands(),this.setupRibbonIcon(),this.setupFileWatchers(),this.setupPeriodicUpdates(),this.addSettingTab(new S(this.app,this)),this.ensureCollectionOnLoad()}onunload(){this.periodicUpdateInterval!==null&&window.clearInterval(this.periodicUpdateInterval)}isDesktop(){return typeof this.app.vault.adapter.getBasePath=="function"}getVaultPath(){return this.app.vault.adapter.getBasePath()}findQMDBinary(){if(this.settings.qmdBinaryPath!=="qmd")return this.settings.qmdBinaryPath;for(let e of U)if(e!=="qmd")try{if((0,R.existsSync)(e))return e}catch(t){}return this.settings.qmdBinaryPath}initializeQMD(){let e=this.getVaultPath(),t=this.settings.collectionName||D(this.app.vault.getName()),s=this.findQMDBinary();s!==this.settings.qmdBinaryPath&&(this.settings.qmdBinaryPath=s,this.saveSettings()),this.qmd=new f(s,t,this.settings.indexName,e)}registerCommands(){this.addCommand({id:"search",name:"Search",callback:()=>this.openSearchModal()}),this.addCommand({id:"open-search-pane",name:"Open search pane",checkCallback:e=>this.settings.enableSearchPane?(e||this.openSearchPane(),!0):!1}),this.addCommand({id:"update-index",name:"Update index now",callback:()=>{this.updateIndexNow()}}),this.addCommand({id:"generate-embeddings",name:"Generate embeddings",callback:()=>{this.generateEmbeddings(!1)}}),this.addCommand({id:"force-rebuild-embeddings",name:"Force rebuild embeddings",callback:()=>{this.generateEmbeddings(!0)}}),this.addCommand({id:"ensure-collection",name:"Ensure collection",callback:()=>{this.ensureCollection()}})}setupRibbonIcon(){this.ribbonIcon&&(this.ribbonIcon.remove(),this.ribbonIcon=null),this.settings.enableRibbonIcon&&(this.ribbonIcon=this.addRibbonIcon("search","QMD search",()=>this.openSearchModal()))}setupFileWatchers(){this.debouncedUpdate=(0,d.debounce)(()=>{this.triggerIndexUpdate()},this.settings.debounceMs,!0);let e=t=>{var s;t instanceof d.TFile&&t.extension==="md"&&(this.hasPendingChanges=!0,(s=this.debouncedUpdate)==null||s.call(this))};this.registerEvent(this.app.vault.on("create",e)),this.registerEvent(this.app.vault.on("modify",e)),this.registerEvent(this.app.vault.on("delete",e)),this.registerEvent(this.app.vault.on("rename",e))}setupPeriodicUpdates(){if(this.periodicUpdateInterval!==null&&(window.clearInterval(this.periodicUpdateInterval),this.periodicUpdateInterval=null),this.settings.enablePeriodicUpdates){let e=this.settings.periodicUpdateMinutes*60*1e3;this.periodicUpdateInterval=window.setInterval(()=>{this.triggerIndexUpdate()},e)}}async triggerIndexUpdate(){if(!this.qmd||!this.hasPendingChanges)return;this.hasPendingChanges=!1;let e=await this.qmd.updateIndex();if(e.success){this.settings.lastIndexUpdateTime=new Date().toISOString(),await this.saveSettings();let t=await this.qmd.generateEmbeddings(!1);t.success?(this.settings.lastEmbeddingRunTime=new Date().toISOString(),await this.saveSettings()):t.error&&this.recordError(t.error)}else e.error&&this.recordError(e.error)}async ensureCollectionOnLoad(){var t,s,n,r;if(!this.qmd)return;let e=await this.qmd.testConnection();if(!e.success){if((t=e.error)!=null&&t.toLowerCase().includes("not found")&&((s=e.error)!=null&&s.toLowerCase().includes("binary"))){new d.Notice("QMD binary not found. Install QMD and configure the path in settings.",15e3),this.recordError(e.error);return}this.recordError(e.error||"Unknown QMD error");return}(n=e.data)!=null&&n.hasCollection||await this.ensureCollection(),(r=e.data)!=null&&r.hasEmbeddings||await this.autoGenerateEmbeddingsIfNeeded()}async autoGenerateEmbeddingsIfNeeded(){var n;if(!this.qmd||!this.settings.autoGenerateEmbeddings)return;let e=await this.qmd.testConnection();if(e.success&&((n=e.data)!=null&&n.hasEmbeddings))return;let t=!this.settings.lastEmbeddingRunTime;t?new d.Notice("QMD: generating embeddings for the first time. This will download ~300MB of AI models and may take several minutes. Semantic search will be available when complete.",15e3):new d.Notice("Generating embeddings for semantic search...",5e3);let s=await this.qmd.generateEmbeddings(!1);s.success?(this.settings.lastEmbeddingRunTime=new Date().toISOString(),await this.saveSettings(),new d.Notice("Embeddings generated. Semantic search is now available.")):(t&&new d.Notice(`Failed to generate embeddings: ${s.error}. Check that QMD is properly installed and you have internet access for model download.`,15e3),this.recordError(s.error||"Failed to auto-generate embeddings"))}async loadSettings(){this.settings=Object.assign({},y,await this.loadData())}async saveSettings(){if(await this.saveData(this.settings),this.qmd){let e=this.settings.collectionName||D(this.app.vault.getName());this.qmd.updateConfig(this.settings.qmdBinaryPath,e,this.settings.indexName)}}async testQMDConnection(){return this.qmd?this.qmd.testConnection():{success:!1,error:"QMD not initialized"}}async updateIndexNow(){if(!this.qmd){new d.Notice("QMD not initialized");return}new d.Notice("Updating QMD index...");let e=await this.qmd.updateIndex();e.success?(this.settings.lastIndexUpdateTime=new Date().toISOString(),await this.saveSettings(),new d.Notice("QMD index updated successfully.")):(new d.Notice(`Failed to update index: ${e.error}`,1e4),this.recordError(e.error||"Unknown error"))}async generateEmbeddings(e){if(!this.qmd){new d.Notice("QMD not initialized");return}let t=e?"Rebuilding":"Generating";new d.Notice(`${t} embeddings... this may take a while.`);let s=await this.qmd.generateEmbeddings(e);s.success?(this.settings.lastEmbeddingRunTime=new Date().toISOString(),await this.saveSettings(),new d.Notice("Embeddings generated successfully.")):(new d.Notice(`Failed to generate embeddings: ${s.error}`,1e4),this.recordError(s.error||"Unknown error"))}async ensureCollection(){if(!this.qmd){new d.Notice("QMD not initialized");return}new d.Notice("Ensuring QMD collection exists...");let e=await this.qmd.ensureCollection(this.settings.fileMask);e.success?new d.Notice("QMD collection is ready."):(new d.Notice(`Failed to ensure collection: ${e.error}`,1e4),this.recordError(e.error||"Unknown error"))}openSearchModal(){if(!this.qmd){new d.Notice("QMD not initialized");return}new b(this.app,this.qmd,this.settings,{onResultSelect:t=>{this.openSearchResult(t)},onSearchModeUsed:(t,s)=>{this.recordSearchMode(t,s)},onError:t=>{this.recordError(t)}}).open()}async openSearchPane(){let e=this.app.workspace.getLeavesOfType(p);if(e.length>0)await this.app.workspace.revealLeaf(e[0]);else{let t=this.app.workspace.getRightLeaf(!1);t&&(await t.setViewState({type:p,active:!0}),await this.app.workspace.revealLeaf(t))}}async openSearchResult(e){if(e.file){let t=this.app.workspace.getLeaf(!1);await t.openFile(e.file),e.line!==void 0&&e.line>0&&setTimeout(()=>{let s=t.view;if(s&&"editor"in s){let n=s.editor,r=e.line-1;n.setCursor({line:r,ch:0}),n.scrollIntoView({from:{line:r},to:{line:r}})}},100)}else new d.Notice(`File not found: ${e.path}`)}async recordSearchMode(e,t){this.settings.lastSearchMode=e,await this.saveSettings()}async recordError(e){this.settings.lastError=`${new Date().toISOString()}: ${e}`,await this.saveSettings()}}; diff --git a/.obsidian/plugins/qmd-search/manifest.json b/.obsidian/plugins/qmd-search/manifest.json new file mode 100644 index 0000000..d9b7e6c --- /dev/null +++ b/.obsidian/plugins/qmd-search/manifest.json @@ -0,0 +1 @@ +{"id":"qmd-search","name":"QMD Semantic Search","version":"1.1.3","minAppVersion":"1.11.0","description":"Semantic-first search for your vault using QMD (Quick Markdown Search). Provides vector-based semantic search with keyword fallback.","author":"Oleksii Chekulaiev","authorUrl":"https://github.com/achekulaev","isDesktopOnly":true} \ No newline at end of file diff --git a/.obsidian/plugins/qmd-search/styles.css b/.obsidian/plugins/qmd-search/styles.css new file mode 100644 index 0000000..40536e3 --- /dev/null +++ b/.obsidian/plugins/qmd-search/styles.css @@ -0,0 +1,312 @@ +/** + * QMD Semantic Search - Obsidian Plugin Styles + * + * These styles are loaded by Obsidian automatically when the plugin is enabled. + */ + +/* Utility */ +.qmd-hidden { + display: none !important; +} + +/* Search Modal - Input */ +.qmd-input-with-pill { + padding-right: 120px !important; +} + +/* Search Modal - Results */ +.qmd-search-result { + padding: 8px 12px; +} + +.qmd-search-result-title { + display: flex; + align-items: baseline; + font-weight: 500; + margin-bottom: 2px; +} + +.qmd-search-result-name { + color: var(--text-normal); +} + +.qmd-search-result-path { + font-size: 0.85em; + color: var(--text-muted); + margin-bottom: 4px; +} + +.qmd-search-result-snippet { + font-size: 0.9em; + color: var(--text-muted); + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.qmd-search-result-score { + font-weight: normal; + font-style: italic; + font-size: 0.75em; + color: var(--text-faint); +} + +.qmd-search-result-meta { + display: flex; + gap: 12px; + font-size: 0.8em; + color: var(--text-faint); +} + +.qmd-search-result-mode { + padding: 1px 6px; + border-radius: 3px; + background: var(--background-modifier-hover); +} + +.qmd-mode-semantic { + color: var(--text-accent); +} + +.qmd-mode-keyword { + color: var(--text-muted); +} + +/* Search mode pill inside input field */ +.qmd-search-mode-indicator { + position: absolute; + right: 44px; + top: 50%; + transform: translateY(-50%); + padding: 2px 8px; + font-size: 0.75em; + color: rgba(255, 255, 255, 0.85); + background: rgba(var(--interactive-accent-rgb, 124, 77, 255), 0.5); + border-radius: 10px; + pointer-events: none; + white-space: nowrap; +} + +/* Search Modal - Error display */ +.qmd-search-error { + padding: 12px; + background: var(--background-modifier-error); + border-radius: 4px; + cursor: default; +} + +.qmd-search-error-title { + color: var(--text-error); + font-weight: 500; +} + +.qmd-search-error-hint { + font-size: 0.9em; + color: var(--text-muted); + margin-top: 4px; +} + +/* Search Modal - Progress bar */ +.qmd-progress-container { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: var(--background-secondary); + border-bottom: 1px solid var(--background-modifier-border); +} + +.qmd-progress-bar { + flex: 1; + height: 3px; + background: var(--background-modifier-border); + border-radius: 2px; + overflow: hidden; +} + +.qmd-progress-bar-fill { + height: 100%; + width: 30%; + background: var(--text-accent); + border-radius: 2px; + animation: qmd-progress-slide 1s ease-in-out infinite; +} + +@keyframes qmd-progress-slide { + 0% { transform: translateX(-100%); } + 50% { transform: translateX(333%); } + 100% { transform: translateX(-100%); } +} + +.qmd-progress-text { + font-size: 0.8em; + color: var(--text-muted); + white-space: nowrap; +} + +/* Search Pane */ +.qmd-search-pane { + display: flex; + flex-direction: column; + height: 100%; + padding: 8px; +} + +.qmd-search-input-container { + margin-bottom: 8px; +} + +.qmd-search-input-wrapper { + display: flex; + align-items: center; + background: var(--background-modifier-form-field); + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + padding: 4px 8px; +} + +.qmd-search-icon { + color: var(--text-muted); + margin-right: 8px; +} + +.qmd-search-input { + flex: 1; + border: none; + background: transparent; + outline: none; + color: var(--text-normal); +} + +.qmd-search-status { + padding: 4px 8px; + font-size: 0.85em; + margin-bottom: 8px; + border-radius: 4px; +} + +.qmd-status-loading { + color: var(--text-muted); + background: var(--background-modifier-hover); +} + +.qmd-status-success { + color: var(--text-success); +} + +.qmd-status-error { + color: var(--text-error); + background: var(--background-modifier-error); +} + +.qmd-search-results { + flex: 1; + overflow-y: auto; +} + +.qmd-search-result-item { + padding: 8px; + margin-bottom: 4px; + border-radius: 4px; + cursor: pointer; + background: var(--background-secondary); +} + +.qmd-search-result-item:hover { + background: var(--background-modifier-hover); +} + +.qmd-result-title { + font-weight: 500; + color: var(--text-normal); + margin-bottom: 2px; +} + +.qmd-result-path { + font-size: 0.85em; + color: var(--text-muted); + margin-bottom: 4px; +} + +.qmd-result-snippet { + font-size: 0.9em; + color: var(--text-muted); + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.qmd-result-score { + font-size: 0.8em; + color: var(--text-faint); +} + +.qmd-search-no-results { + padding: 16px; + text-align: center; + color: var(--text-muted); +} + +/* Search Pane - Loading */ +.qmd-pane-loading-row { + display: flex; + align-items: center; + gap: 8px; +} + +.qmd-pane-spinner { + width: 12px; + height: 12px; + border: 2px solid var(--background-modifier-border); + border-top-color: var(--text-accent); + border-radius: 50%; + animation: qmd-pane-spin 0.8s linear infinite; +} + +@keyframes qmd-pane-spin { + to { transform: rotate(360deg); } +} + +/* Settings Tab */ +.qmd-diagnostics { + padding: 12px; + background: var(--background-secondary); + border-radius: 4px; + margin-bottom: 16px; +} + +.qmd-diagnostic-row { + display: flex; + justify-content: space-between; + padding: 4px 0; +} + +.qmd-diagnostic-label { + font-weight: 500; + color: var(--text-muted); +} + +.qmd-diagnostic-value { + color: var(--text-normal); + font-family: var(--font-monospace); + font-size: 0.9em; +} + +/* Loading spinner animation */ +@keyframes qmd-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.qmd-loading-spinner { + width: 16px; + height: 16px; + border: 2px solid var(--background-modifier-border); + border-top-color: var(--text-accent); + border-radius: 50%; + animation: qmd-spin 0.8s linear infinite; +} diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 8f770f8..999b26a 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -13,12 +13,12 @@ "state": { "type": "markdown", "state": { - "file": "TOOLS.md", + "file": "memory/2026-02-26.md", "mode": "source", "source": false }, "icon": "lucide-file", - "title": "TOOLS" + "title": "2026-02-26" } } ] @@ -95,7 +95,7 @@ "state": { "type": "backlink", "state": { - "file": "TOOLS.md", + "file": "memory/2026-02-26-alice-debug.md", "collapseAll": false, "extraContext": false, "sortOrder": "alphabetical", @@ -105,7 +105,7 @@ "unlinkedCollapsed": true }, "icon": "links-coming-in", - "title": "Backlinks for TOOLS" + "title": "Backlinks for 2026-02-26-alice-debug" } }, { @@ -114,12 +114,12 @@ "state": { "type": "outgoing-link", "state": { - "file": "TOOLS.md", + "file": "memory/2026-02-26-alice-debug.md", "linksCollapsed": false, "unlinkedCollapsed": true }, "icon": "links-going-out", - "title": "Outgoing links from TOOLS" + "title": "Outgoing links from 2026-02-26-alice-debug" } }, { @@ -157,16 +157,27 @@ "state": { "type": "outline", "state": { - "file": "TOOLS.md", + "file": "memory/2026-02-26-alice-debug.md", "followCursor": false, "showSearch": false, "searchQuery": "" }, "icon": "lucide-list", - "title": "Outline of TOOLS" + "title": "Outline of 2026-02-26-alice-debug" + } + }, + { + "id": "353be6f48ab39071", + "type": "leaf", + "state": { + "type": "git-view", + "state": {}, + "icon": "git-pull-request", + "title": "Source Control" } } - ] + ], + "currentTab": 5 } ], "direction": "horizontal", @@ -181,15 +192,26 @@ "daily-notes:Open today's daily note": false, "templates:Insert template": false, "command-palette:Open command palette": false, - "bases:Create new base": false + "bases:Create new base": false, + "obsidian-git:Open Git source control": false, + "obsidian42-brat:BRAT": false, + "qmd-search:QMD search": false } }, - "active": "1914b3cb53aaf523", + "active": "09c562b295be8994", "lastOpenFiles": [ - "MEMORY.md", - "USER.md", + "scripts/daily-digest.sh", + "memory/2026-02-26-blog-fix.md", + "memory/2026-02-26-blog-api-fix.md", + "memory/2026-02-26-digest-failed.md", + "scripts/skrybe", + "scripts/skrybe.swift", "AGENTS.md", + "HEARTBEAT.md", + "MEMORY.md", + "memory/2026-02-26-alice-debug.md", "TOOLS.md", + "USER.md", "Untitled 2.base", "Untitled 1.base", "Untitled.base" diff --git a/memory/2026-02-26.md b/memory/2026-02-26.md index a7e1a30..fe5a8d6 100644 --- a/memory/2026-02-26.md +++ b/memory/2026-02-26.md @@ -11,6 +11,30 @@ Morning: Gantt Board for hardening (test if complete), git commit workspace. Bui --- +## [2026-02-26 16:46] ✅ Created daily-digest.sh script + +### What was requested +Matt wanted a shell script to generate the daily digest reliably (to fix the broken cron job). + +### What was done +- Created `/Users/mattbruce/.openclaw/workspace/scripts/daily-digest.sh` +- Script handles JSON escaping properly using `jq` +- Uses temp files to avoid argument length issues +- Checks for existing digest before creating +- Generates proper format with emojis and `[Read more →](URL)` links +- Can be run manually: `./daily-digest.sh [YYYY-MM-DD]` + +### Usage +```bash +# Run for today +./scripts/daily-digest.sh + +# Run for specific date +./scripts/daily-digest.sh 2026-02-26 +``` + +--- + ## [2026-02-26 15:52] ✅ Fixed blog-backup skill to match mission-control-docs pattern ### What was requested diff --git a/scripts/skrybe b/scripts/skrybe new file mode 100755 index 0000000000000000000000000000000000000000..5b243f1aca566eba257771bb2893b00092786eca GIT binary patch literal 113928 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 literal 0 HcmV?d00001 diff --git a/scripts/skrybe.swift b/scripts/skrybe.swift new file mode 100644 index 0000000..5eab38b --- /dev/null +++ b/scripts/skrybe.swift @@ -0,0 +1,202 @@ +import Foundation +import AppKit +import CoreGraphics +import ApplicationServices + +enum SkrybeError: LocalizedError { + case invalidUsage(String) + case unsupportedSubcommand(String) + case unsupportedQuotesOption(String) + case apiURLInvalid + case apiUnreachable(String) + case invalidHTTPStatus(Int) + case invalidResponse + case pasteboardWriteFailed + case accessibilityPermissionMissing + case eventCreationFailed + + var errorDescription: String? { + switch self { + case .invalidUsage(let message): + return message + case .unsupportedSubcommand(let command): + return "Unsupported subcommand: \(command)" + case .unsupportedQuotesOption(let option): + return "Unsupported quotes option: \(option)" + case .apiURLInvalid: + return "Invalid quotes API URL." + case .apiUnreachable(let reason): + return "Unable to reach quotes API: \(reason)" + case .invalidHTTPStatus(let status): + return "Quotes API returned HTTP status \(status)." + case .invalidResponse: + return "Quotes API returned an invalid response format." + case .pasteboardWriteFailed: + return "Failed to write quote text to the macOS pasteboard." + case .accessibilityPermissionMissing: + return "Accessibility permission is required to simulate paste (Cmd+V). Enable it in System Settings > Privacy & Security > Accessibility." + case .eventCreationFailed: + return "Unable to create keyboard events for paste operation." + } + } +} + +struct QuoteResponse: Decodable { + let quote: String + let author: String +} + +struct QuoteService { + private let endpoint = "http://localhost:3001/quote" + + func fetchRandomQuote(timeout: TimeInterval = 5.0) throws -> QuoteResponse { + guard let url = URL(string: endpoint) else { + throw SkrybeError.apiURLInvalid + } + + var request = URLRequest(url: url) + request.httpMethod = "GET" + request.timeoutInterval = timeout + + let semaphore = DispatchSemaphore(value: 0) + var responseData: Data? + var response: URLResponse? + var requestError: Error? + + let task = URLSession.shared.dataTask(with: request) { data, urlResponse, error in + responseData = data + response = urlResponse + requestError = error + semaphore.signal() + } + + task.resume() + _ = semaphore.wait(timeout: .now() + timeout + 1.0) + + if let requestError { + throw SkrybeError.apiUnreachable(requestError.localizedDescription) + } + + guard let http = response as? HTTPURLResponse else { + throw SkrybeError.invalidResponse + } + + guard (200...299).contains(http.statusCode) else { + throw SkrybeError.invalidHTTPStatus(http.statusCode) + } + + guard let responseData else { + throw SkrybeError.invalidResponse + } + + do { + let decoded = try JSONDecoder().decode(QuoteResponse.self, from: responseData) + return decoded + } catch { + throw SkrybeError.invalidResponse + } + } +} + +struct QuotePaster { + func paste(_ text: String) throws { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + + guard pasteboard.setString(text, forType: .string) else { + throw SkrybeError.pasteboardWriteFailed + } + + guard AXIsProcessTrusted() else { + throw SkrybeError.accessibilityPermissionMissing + } + + try sendCommandV() + } + + private func sendCommandV() throws { + guard + let source = CGEventSource(stateID: .hidSystemState), + let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 9, keyDown: true), // 9 = 'v' + let keyUp = CGEvent(keyboardEventSource: source, virtualKey: 9, keyDown: false) + else { + throw SkrybeError.eventCreationFailed + } + + keyDown.flags = .maskCommand + keyUp.flags = .maskCommand + + keyDown.post(tap: .cghidEventTap) + keyUp.post(tap: .cghidEventTap) + } +} + +struct SkrybeCLI { + private let quoteService = QuoteService() + private let quotePaster = QuotePaster() + + func run(arguments: [String]) throws { + guard arguments.count >= 2 else { + throw SkrybeError.invalidUsage(usage()) + } + + let command = arguments[1] + switch command { + case "quotes": + try runQuotes(arguments: Array(arguments.dropFirst(2))) + case "--help", "-h", "help": + print(usage()) + default: + throw SkrybeError.unsupportedSubcommand(command) + } + } + + private func runQuotes(arguments: [String]) throws { + guard let option = arguments.first else { + throw SkrybeError.invalidUsage(quotesUsage()) + } + + switch option { + case "--paste": + let quote = try quoteService.fetchRandomQuote() + let rendered = "\(quote.quote) — \(quote.author)" + try quotePaster.paste(rendered) + print("✅ Pasted quote at cursor position") + case "--list", "--add": + throw SkrybeError.invalidUsage("\(option) is not implemented yet. Use `skrybe quotes --paste`.") + case "--help", "-h", "help": + print(quotesUsage()) + default: + throw SkrybeError.unsupportedQuotesOption(option) + } + } + + private func usage() -> String { + return """ + Usage: + skrybe quotes --paste + skrybe quotes --list (planned) + skrybe quotes --add (planned) + """ + } + + private func quotesUsage() -> String { + return """ + Quotes subcommand: + skrybe quotes --paste Fetch random quote from localhost:3001/quote and paste at current cursor + skrybe quotes --list Planned + skrybe quotes --add Planned + """ + } +} + +do { + try SkrybeCLI().run(arguments: CommandLine.arguments) +} catch { + if let localized = error as? LocalizedError, let message = localized.errorDescription { + fputs("❌ \(message)\n", stderr) + } else { + fputs("❌ \(error.localizedDescription)\n", stderr) + } + exit(EXIT_FAILURE) +}