basalt-sync 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +137 -0
  2. package/dist/basalt.mjs +33 -0
  3. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # Basalt client
2
+
3
+ [Docs index](../docs/index.md)
4
+
5
+ One sync engine, two things to run it: the Obsidian plugin and a headless client.
6
+
7
+ ```
8
+ src/core/ platform-free. crypto, chunking, merging, the index, the transport
9
+ src/plugin/ the Vault API adapter and the plugin shell
10
+ src/cli/ the filesystem adapter and the headless CLI
11
+ ```
12
+
13
+ `core` is the whole client except the parts that have to know where files live.
14
+ Sealing uses WebCrypto, chunking and merging are arithmetic, and the transport
15
+ uses the `WebSocket` that Node 22 and every Obsidian target both provide. So the
16
+ headless client is not a second client, it is the same one with a different
17
+ adapter, which is how Obsidian's own `obsidian-headless` is built.
18
+
19
+ Any sync decision that appears in a shell is in the wrong file.
20
+
21
+ ## Working on it
22
+
23
+ ```
24
+ bun install
25
+ bun run test # everything, including against a real server
26
+ bun run typecheck
27
+ bun run bench # throughput and bandwidth, reported not asserted
28
+ bun run bench:sync # a whole vault, timed and checked
29
+ ```
30
+
31
+ The tests need a Go toolchain. `src/core/server-harness.test.ts` builds
32
+ `cmd/basaltd`, runs it on a loopback port, and talks to it with the real
33
+ transport. Nothing is mocked, and its assertions are checked by asking the
34
+ server's own `verify -deep` whether what it stored can be served.
35
+ `src/core/transport.test.ts` is the other half: a fake socket that says things a
36
+ correct server never would.
37
+
38
+ ## The headless client
39
+
40
+ `bun run build` produces `dist/basalt.mjs`: one 85 KB file with nothing to
41
+ install beside it, because everything is bundled and the only imports that
42
+ survive are `node:` builtins. The npm package declares no dependencies at all. The command is `basalt`; the server's is `basaltd`, so a homelab can run both.
43
+
44
+ ```
45
+ basalt init --server wss://host --token TOKEN # the first device
46
+ basalt pair basalt2_... # every other device
47
+ basalt sync # once, and exit
48
+ basalt sync --watch # and keep going
49
+ basalt status
50
+ basalt invite # reprint the pairing string
51
+ basalt unlink # forget the pairing, keep the notes
52
+ ```
53
+
54
+ `--dir` chooses the vault, defaulting to the current directory. `--json` on any
55
+ command gives machine-readable output. State lives in `.basalt/` inside the
56
+ vault, which is never synced: `config.json` (0600, holds the root secret) and
57
+ `index.json`.
58
+
59
+ `cli.ts` takes an argv and two output functions and returns an exit code, so
60
+ `cli.test.ts` drives the whole client against a real server with no subprocess.
61
+ `bin.ts` is the six lines connecting that to a terminal, and the only part no
62
+ test covers.
63
+
64
+ **Exit codes.** `0` worked. `1` failed, or finished with files that can never
65
+ sync, or could not reach the server. `2` the command line was wrong. A sync that
66
+ skipped a file for good exits non-zero on purpose: a broken vault that exits zero
67
+ is a broken vault nobody hears about.
68
+
69
+ ## The plugin
70
+
71
+ `src/plugin/main.ts` does the same job and then draws a status bar. No settings
72
+ tab, on purpose; one modal, which pairs a vault and says what is happening.
73
+
74
+ ```
75
+ bun run build
76
+ cp -r dist/plugin /path/to/vault/.obsidian/plugins/basalt
77
+ ```
78
+
79
+ Then enable it in the community plugins list. Config lives in the plugin's own
80
+ `data.json`. The root secret is in there in the clear, which is the same exposure
81
+ as the headless client's `config.json` and is inherent: the device has to decrypt
82
+ the vault without asking anybody.
83
+
84
+ ### Tested without Obsidian
85
+
86
+ The `obsidian` package is type declarations with no runtime (`"main": ""`), so
87
+ `main.ts` and `obsidian/vault.ts` would otherwise compile and never run.
88
+
89
+ - `src/plugin/fake.ts` implements `DataAdapter`, declared against the real
90
+ declarations so the compiler catches drift. Its `normalizePath` matches what
91
+ the shipped app does, which was read rather than assumed.
92
+ - `src/plugin/stub.ts` is the runtime `obsidian` module. `vitest.config.ts`
93
+ aliases to it **for tests only**: `tsconfig.json` does not, so `tsc` checks
94
+ against the genuine declarations, and `esbuild.config.mjs` marks it external so
95
+ the shipped plugin gets Obsidian's.
96
+ - `src/build.test.ts` loads the built `dist/plugin/main.js`, hands it the stub,
97
+ and pairs two of them against a real Go server. It also checks the bundle needs
98
+ nothing but `obsidian` and contains no `node:` import, the regression that
99
+ would otherwise pass every test and fail only on a phone.
100
+
101
+ Deliberate breakages of the plugin and its adapter are all caught. What this
102
+ cannot tell you is whether Obsidian calls these methods when the plugin expects,
103
+ or draws what it builds.
104
+
105
+ ## Recovery
106
+
107
+ The server has kept every version and every deletion since the first commit.
108
+
109
+ ```
110
+ basalt deleted what the server has and this vault does not
111
+ basalt history "Quarterly plan.md" every version, newest first
112
+ basalt restore "Quarterly plan.md" the newest version with content
113
+ basalt restore "Q.md" --uid 42 one exact version
114
+ basalt restore "Q.md" --to old/Q.md somewhere else
115
+ ```
116
+
117
+ In the plugin there are two ways in. Right-click a note for "Basalt: version
118
+ history", or the "Show version history" command, which opens a sidebar of every
119
+ version newest first with a diff against what is on disk. Deleted notes have
120
+ their own command, "Recover a deleted note", because a note that is gone cannot
121
+ be right-clicked. Both are also registered as `basalt:history` and
122
+ `basalt:restore` on Obsidian's own command line, next to its `sync:history`.
123
+
124
+ Restoring never overwrites: if the path is occupied the copy lands beside it
125
+ under `(restored N)` and says so. A restored note keeps the timestamp it was
126
+ written with, so a note from March does not sort to the top. And restoring is not
127
+ a server operation. The client fetches the version with an ordinary `get`,
128
+ writes it, and the ordinary sync sends it on, so the server keeps one way to
129
+ change a vault.
130
+
131
+ ### Renames become deletions, headless only
132
+
133
+ A filesystem scan cannot tell a rename from a delete plus a create. Obsidian can,
134
+ and its rename event carries the old path, so the plugin sends the rename as one
135
+ operation and it stays out of the deleted list. The headless client reports what
136
+ it saw. Nothing is lost either way: the content is on the server under both
137
+ names, and deduplication means the second name cost nothing.
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ var jn=Object.create;var Mt=Object.defineProperty;var Gn=Object.getOwnPropertyDescriptor;var qn=Object.getOwnPropertyNames;var Zn=Object.getPrototypeOf,Xn=Object.prototype.hasOwnProperty;var Yn=(n,e)=>()=>{try{return e||n((e={exports:{}}).exports,e),e.exports}catch(t){throw e=0,t}};var Qn=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of qn(e))!Xn.call(n,i)&&i!==t&&Mt(n,i,{get:()=>e[i],enumerable:!(r=Gn(e,i))||r.enumerable});return n};var er=(n,e,t)=>(t=n!=null?jn(Zn(n)):{},Qn(e||!n||!n.__esModule?Mt(t,"default",{value:n,enumerable:!0}):t,n));var on=Yn((Bi,me)=>{var d=function(){this.Diff_Timeout=1,this.Diff_EditCost=4,this.Match_Threshold=.5,this.Match_Distance=1e3,this.Patch_DeleteThreshold=.5,this.Patch_Margin=4,this.Match_MaxBits=32},_=-1,T=1,k=0;d.Diff=function(n,e){return[n,e]};d.prototype.diff_main=function(n,e,t,r){typeof r>"u"&&(this.Diff_Timeout<=0?r=Number.MAX_VALUE:r=new Date().getTime()+this.Diff_Timeout*1e3);var i=r;if(n==null||e==null)throw new Error("Null input. (diff_main)");if(n==e)return n?[new d.Diff(k,n)]:[];typeof t>"u"&&(t=!0);var s=t,o=this.diff_commonPrefix(n,e),a=n.substring(0,o);n=n.substring(o),e=e.substring(o),o=this.diff_commonSuffix(n,e);var l=n.substring(n.length-o);n=n.substring(0,n.length-o),e=e.substring(0,e.length-o);var c=this.diff_compute_(n,e,s,i);return a&&c.unshift(new d.Diff(k,a)),l&&c.push(new d.Diff(k,l)),this.diff_cleanupMerge(c),c};d.prototype.diff_compute_=function(n,e,t,r){var i;if(!n)return[new d.Diff(T,e)];if(!e)return[new d.Diff(_,n)];var s=n.length>e.length?n:e,o=n.length>e.length?e:n,a=s.indexOf(o);if(a!=-1)return i=[new d.Diff(T,s.substring(0,a)),new d.Diff(k,o),new d.Diff(T,s.substring(a+o.length))],n.length>e.length&&(i[0][0]=i[2][0]=_),i;if(o.length==1)return[new d.Diff(_,n),new d.Diff(T,e)];var l=this.diff_halfMatch_(n,e);if(l){var c=l[0],h=l[1],u=l[2],f=l[3],g=l[4],m=this.diff_main(c,u,t,r),v=this.diff_main(h,f,t,r);return m.concat([new d.Diff(k,g)],v)}return t&&n.length>100&&e.length>100?this.diff_lineMode_(n,e,r):this.diff_bisect_(n,e,r)};d.prototype.diff_lineMode_=function(n,e,t){var r=this.diff_linesToChars_(n,e);n=r.chars1,e=r.chars2;var i=r.lineArray,s=this.diff_main(n,e,!1,t);this.diff_charsToLines_(s,i),this.diff_cleanupSemantic(s),s.push(new d.Diff(k,""));for(var o=0,a=0,l=0,c="",h="";o<s.length;){switch(s[o][0]){case T:l++,h+=s[o][1];break;case _:a++,c+=s[o][1];break;case k:if(a>=1&&l>=1){s.splice(o-a-l,a+l),o=o-a-l;for(var u=this.diff_main(c,h,!1,t),f=u.length-1;f>=0;f--)s.splice(o,0,u[f]);o=o+u.length}l=0,a=0,c="",h="";break}o++}return s.pop(),s};d.prototype.diff_bisect_=function(n,e,t){for(var r=n.length,i=e.length,s=Math.ceil((r+i)/2),o=s,a=2*s,l=new Array(a),c=new Array(a),h=0;h<a;h++)l[h]=-1,c[h]=-1;l[o+1]=0,c[o+1]=0;for(var u=r-i,f=u%2!=0,g=0,m=0,v=0,x=0,w=0;w<s&&!(new Date().getTime()>t);w++){for(var A=-w+g;A<=w-m;A+=2){var b=o+A,S;A==-w||A!=w&&l[b-1]<l[b+1]?S=l[b+1]:S=l[b-1]+1;for(var M=S-A;S<r&&M<i&&n.charAt(S)==e.charAt(M);)S++,M++;if(l[b]=S,S>r)m+=2;else if(M>i)g+=2;else if(f){var $=o+u-A;if($>=0&&$<a&&c[$]!=-1){var E=r-c[$];if(S>=E)return this.diff_bisectSplit_(n,e,S,M,t)}}}for(var y=-w+v;y<=w-x;y+=2){var $=o+y,E;y==-w||y!=w&&c[$-1]<c[$+1]?E=c[$+1]:E=c[$-1]+1;for(var p=E-y;E<r&&p<i&&n.charAt(r-E-1)==e.charAt(i-p-1);)E++,p++;if(c[$]=E,E>r)x+=2;else if(p>i)v+=2;else if(!f){var b=o+u-y;if(b>=0&&b<a&&l[b]!=-1){var S=l[b],M=o+S-b;if(E=r-E,S>=E)return this.diff_bisectSplit_(n,e,S,M,t)}}}}return[new d.Diff(_,n),new d.Diff(T,e)]};d.prototype.diff_bisectSplit_=function(n,e,t,r,i){var s=n.substring(0,t),o=e.substring(0,r),a=n.substring(t),l=e.substring(r),c=this.diff_main(s,o,!1,i),h=this.diff_main(a,l,!1,i);return c.concat(h)};d.prototype.diff_linesToChars_=function(n,e){var t=[],r={};t[0]="";function i(l){for(var c="",h=0,u=-1,f=t.length;u<l.length-1;){u=l.indexOf(`
3
+ `,h),u==-1&&(u=l.length-1);var g=l.substring(h,u+1);(r.hasOwnProperty?r.hasOwnProperty(g):r[g]!==void 0)?c+=String.fromCharCode(r[g]):(f==s&&(g=l.substring(h),u=l.length),c+=String.fromCharCode(f),r[g]=f,t[f++]=g),h=u+1}return c}var s=4e4,o=i(n);s=65535;var a=i(e);return{chars1:o,chars2:a,lineArray:t}};d.prototype.diff_charsToLines_=function(n,e){for(var t=0;t<n.length;t++){for(var r=n[t][1],i=[],s=0;s<r.length;s++)i[s]=e[r.charCodeAt(s)];n[t][1]=i.join("")}};d.prototype.diff_commonPrefix=function(n,e){if(!n||!e||n.charAt(0)!=e.charAt(0))return 0;for(var t=0,r=Math.min(n.length,e.length),i=r,s=0;t<i;)n.substring(s,i)==e.substring(s,i)?(t=i,s=t):r=i,i=Math.floor((r-t)/2+t);return i};d.prototype.diff_commonSuffix=function(n,e){if(!n||!e||n.charAt(n.length-1)!=e.charAt(e.length-1))return 0;for(var t=0,r=Math.min(n.length,e.length),i=r,s=0;t<i;)n.substring(n.length-i,n.length-s)==e.substring(e.length-i,e.length-s)?(t=i,s=t):r=i,i=Math.floor((r-t)/2+t);return i};d.prototype.diff_commonOverlap_=function(n,e){var t=n.length,r=e.length;if(t==0||r==0)return 0;t>r?n=n.substring(t-r):t<r&&(e=e.substring(0,t));var i=Math.min(t,r);if(n==e)return i;for(var s=0,o=1;;){var a=n.substring(i-o),l=e.indexOf(a);if(l==-1)return s;o+=l,(l==0||n.substring(i-o)==e.substring(0,o))&&(s=o,o++)}};d.prototype.diff_halfMatch_=function(n,e){if(this.Diff_Timeout<=0)return null;var t=n.length>e.length?n:e,r=n.length>e.length?e:n;if(t.length<4||r.length*2<t.length)return null;var i=this;function s(m,v,x){for(var w=m.substring(x,x+Math.floor(m.length/4)),A=-1,b="",S,M,$,E;(A=v.indexOf(w,A+1))!=-1;){var y=i.diff_commonPrefix(m.substring(x),v.substring(A)),p=i.diff_commonSuffix(m.substring(0,x),v.substring(0,A));b.length<p+y&&(b=v.substring(A-p,A)+v.substring(A,A+y),S=m.substring(0,x-p),M=m.substring(x+y),$=v.substring(0,A-p),E=v.substring(A+y))}return b.length*2>=m.length?[S,M,$,E,b]:null}var o=s(t,r,Math.ceil(t.length/4)),a=s(t,r,Math.ceil(t.length/2)),l;if(!o&&!a)return null;a?o?l=o[4].length>a[4].length?o:a:l=a:l=o;var c,h,u,f;n.length>e.length?(c=l[0],h=l[1],u=l[2],f=l[3]):(u=l[0],f=l[1],c=l[2],h=l[3]);var g=l[4];return[c,h,u,f,g]};d.prototype.diff_cleanupSemantic=function(n){for(var e=!1,t=[],r=0,i=null,s=0,o=0,a=0,l=0,c=0;s<n.length;)n[s][0]==k?(t[r++]=s,o=l,a=c,l=0,c=0,i=n[s][1]):(n[s][0]==T?l+=n[s][1].length:c+=n[s][1].length,i&&i.length<=Math.max(o,a)&&i.length<=Math.max(l,c)&&(n.splice(t[r-1],0,new d.Diff(_,i)),n[t[r-1]+1][0]=T,r--,r--,s=r>0?t[r-1]:-1,o=0,a=0,l=0,c=0,i=null,e=!0)),s++;for(e&&this.diff_cleanupMerge(n),this.diff_cleanupSemanticLossless(n),s=1;s<n.length;){if(n[s-1][0]==_&&n[s][0]==T){var h=n[s-1][1],u=n[s][1],f=this.diff_commonOverlap_(h,u),g=this.diff_commonOverlap_(u,h);f>=g?(f>=h.length/2||f>=u.length/2)&&(n.splice(s,0,new d.Diff(k,u.substring(0,f))),n[s-1][1]=h.substring(0,h.length-f),n[s+1][1]=u.substring(f),s++):(g>=h.length/2||g>=u.length/2)&&(n.splice(s,0,new d.Diff(k,h.substring(0,g))),n[s-1][0]=T,n[s-1][1]=u.substring(0,u.length-g),n[s+1][0]=_,n[s+1][1]=h.substring(g),s++),s++}s++}};d.prototype.diff_cleanupSemanticLossless=function(n){function e(g,m){if(!g||!m)return 6;var v=g.charAt(g.length-1),x=m.charAt(0),w=v.match(d.nonAlphaNumericRegex_),A=x.match(d.nonAlphaNumericRegex_),b=w&&v.match(d.whitespaceRegex_),S=A&&x.match(d.whitespaceRegex_),M=b&&v.match(d.linebreakRegex_),$=S&&x.match(d.linebreakRegex_),E=M&&g.match(d.blanklineEndRegex_),y=$&&m.match(d.blanklineStartRegex_);return E||y?5:M||$?4:w&&!b&&S?3:b||S?2:w||A?1:0}for(var t=1;t<n.length-1;){if(n[t-1][0]==k&&n[t+1][0]==k){var r=n[t-1][1],i=n[t][1],s=n[t+1][1],o=this.diff_commonSuffix(r,i);if(o){var a=i.substring(i.length-o);r=r.substring(0,r.length-o),i=a+i.substring(0,i.length-o),s=a+s}for(var l=r,c=i,h=s,u=e(r,i)+e(i,s);i.charAt(0)===s.charAt(0);){r+=i.charAt(0),i=i.substring(1)+s.charAt(0),s=s.substring(1);var f=e(r,i)+e(i,s);f>=u&&(u=f,l=r,c=i,h=s)}n[t-1][1]!=l&&(l?n[t-1][1]=l:(n.splice(t-1,1),t--),n[t][1]=c,h?n[t+1][1]=h:(n.splice(t+1,1),t--))}t++}};d.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;d.whitespaceRegex_=/\s/;d.linebreakRegex_=/[\r\n]/;d.blanklineEndRegex_=/\n\r?\n$/;d.blanklineStartRegex_=/^\r?\n\r?\n/;d.prototype.diff_cleanupEfficiency=function(n){for(var e=!1,t=[],r=0,i=null,s=0,o=!1,a=!1,l=!1,c=!1;s<n.length;)n[s][0]==k?(n[s][1].length<this.Diff_EditCost&&(l||c)?(t[r++]=s,o=l,a=c,i=n[s][1]):(r=0,i=null),l=c=!1):(n[s][0]==_?c=!0:l=!0,i&&(o&&a&&l&&c||i.length<this.Diff_EditCost/2&&o+a+l+c==3)&&(n.splice(t[r-1],0,new d.Diff(_,i)),n[t[r-1]+1][0]=T,r--,i=null,o&&a?(l=c=!0,r=0):(r--,s=r>0?t[r-1]:-1,l=c=!1),e=!0)),s++;e&&this.diff_cleanupMerge(n)};d.prototype.diff_cleanupMerge=function(n){n.push(new d.Diff(k,""));for(var e=0,t=0,r=0,i="",s="",o;e<n.length;)switch(n[e][0]){case T:r++,s+=n[e][1],e++;break;case _:t++,i+=n[e][1],e++;break;case k:t+r>1?(t!==0&&r!==0&&(o=this.diff_commonPrefix(s,i),o!==0&&(e-t-r>0&&n[e-t-r-1][0]==k?n[e-t-r-1][1]+=s.substring(0,o):(n.splice(0,0,new d.Diff(k,s.substring(0,o))),e++),s=s.substring(o),i=i.substring(o)),o=this.diff_commonSuffix(s,i),o!==0&&(n[e][1]=s.substring(s.length-o)+n[e][1],s=s.substring(0,s.length-o),i=i.substring(0,i.length-o))),e-=t+r,n.splice(e,t+r),i.length&&(n.splice(e,0,new d.Diff(_,i)),e++),s.length&&(n.splice(e,0,new d.Diff(T,s)),e++),e++):e!==0&&n[e-1][0]==k?(n[e-1][1]+=n[e][1],n.splice(e,1)):e++,r=0,t=0,i="",s="";break}n[n.length-1][1]===""&&n.pop();var a=!1;for(e=1;e<n.length-1;)n[e-1][0]==k&&n[e+1][0]==k&&(n[e][1].substring(n[e][1].length-n[e-1][1].length)==n[e-1][1]?(n[e][1]=n[e-1][1]+n[e][1].substring(0,n[e][1].length-n[e-1][1].length),n[e+1][1]=n[e-1][1]+n[e+1][1],n.splice(e-1,1),a=!0):n[e][1].substring(0,n[e+1][1].length)==n[e+1][1]&&(n[e-1][1]+=n[e+1][1],n[e][1]=n[e][1].substring(n[e+1][1].length)+n[e+1][1],n.splice(e+1,1),a=!0)),e++;a&&this.diff_cleanupMerge(n)};d.prototype.diff_xIndex=function(n,e){var t=0,r=0,i=0,s=0,o;for(o=0;o<n.length&&(n[o][0]!==T&&(t+=n[o][1].length),n[o][0]!==_&&(r+=n[o][1].length),!(t>e));o++)i=t,s=r;return n.length!=o&&n[o][0]===_?s:s+(e-i)};d.prototype.diff_prettyHtml=function(n){for(var e=[],t=/&/g,r=/</g,i=/>/g,s=/\n/g,o=0;o<n.length;o++){var a=n[o][0],l=n[o][1],c=l.replace(t,"&amp;").replace(r,"&lt;").replace(i,"&gt;").replace(s,"&para;<br>");switch(a){case T:e[o]='<ins style="background:#e6ffe6;">'+c+"</ins>";break;case _:e[o]='<del style="background:#ffe6e6;">'+c+"</del>";break;case k:e[o]="<span>"+c+"</span>";break}}return e.join("")};d.prototype.diff_text1=function(n){for(var e=[],t=0;t<n.length;t++)n[t][0]!==T&&(e[t]=n[t][1]);return e.join("")};d.prototype.diff_text2=function(n){for(var e=[],t=0;t<n.length;t++)n[t][0]!==_&&(e[t]=n[t][1]);return e.join("")};d.prototype.diff_levenshtein=function(n){for(var e=0,t=0,r=0,i=0;i<n.length;i++){var s=n[i][0],o=n[i][1];switch(s){case T:t+=o.length;break;case _:r+=o.length;break;case k:e+=Math.max(t,r),t=0,r=0;break}}return e+=Math.max(t,r),e};d.prototype.diff_toDelta=function(n){for(var e=[],t=0;t<n.length;t++)switch(n[t][0]){case T:e[t]="+"+encodeURI(n[t][1]);break;case _:e[t]="-"+n[t][1].length;break;case k:e[t]="="+n[t][1].length;break}return e.join(" ").replace(/%20/g," ")};d.prototype.diff_fromDelta=function(n,e){for(var t=[],r=0,i=0,s=e.split(/\t/g),o=0;o<s.length;o++){var a=s[o].substring(1);switch(s[o].charAt(0)){case"+":try{t[r++]=new d.Diff(T,decodeURI(a))}catch{throw new Error("Illegal escape in diff_fromDelta: "+a)}break;case"-":case"=":var l=parseInt(a,10);if(isNaN(l)||l<0)throw new Error("Invalid number in diff_fromDelta: "+a);var c=n.substring(i,i+=l);s[o].charAt(0)=="="?t[r++]=new d.Diff(k,c):t[r++]=new d.Diff(_,c);break;default:if(s[o])throw new Error("Invalid diff operation in diff_fromDelta: "+s[o])}}if(i!=n.length)throw new Error("Delta length ("+i+") does not equal source text length ("+n.length+").");return t};d.prototype.match_main=function(n,e,t){if(n==null||e==null||t==null)throw new Error("Null input. (match_main)");return t=Math.max(0,Math.min(t,n.length)),n==e?0:n.length?n.substring(t,t+e.length)==e?t:this.match_bitap_(n,e,t):-1};d.prototype.match_bitap_=function(n,e,t){if(e.length>this.Match_MaxBits)throw new Error("Pattern too long for this browser.");var r=this.match_alphabet_(e),i=this;function s(S,M){var $=S/e.length,E=Math.abs(t-M);return i.Match_Distance?$+E/i.Match_Distance:E?1:$}var o=this.Match_Threshold,a=n.indexOf(e,t);a!=-1&&(o=Math.min(s(0,a),o),a=n.lastIndexOf(e,t+e.length),a!=-1&&(o=Math.min(s(0,a),o)));var l=1<<e.length-1;a=-1;for(var c,h,u=e.length+n.length,f,g=0;g<e.length;g++){for(c=0,h=u;c<h;)s(g,t+h)<=o?c=h:u=h,h=Math.floor((u-c)/2+c);u=h;var m=Math.max(1,t-h+1),v=Math.min(t+h,n.length)+e.length,x=Array(v+2);x[v+1]=(1<<g)-1;for(var w=v;w>=m;w--){var A=r[n.charAt(w-1)];if(g===0?x[w]=(x[w+1]<<1|1)&A:x[w]=(x[w+1]<<1|1)&A|((f[w+1]|f[w])<<1|1)|f[w+1],x[w]&l){var b=s(g,w-1);if(b<=o)if(o=b,a=w-1,a>t)m=Math.max(1,2*t-a);else break}}if(s(g+1,t)>o)break;f=x}return a};d.prototype.match_alphabet_=function(n){for(var e={},t=0;t<n.length;t++)e[n.charAt(t)]=0;for(var t=0;t<n.length;t++)e[n.charAt(t)]|=1<<n.length-t-1;return e};d.prototype.patch_addContext_=function(n,e){if(e.length!=0){if(n.start2===null)throw Error("patch not initialized");for(var t=e.substring(n.start2,n.start2+n.length1),r=0;e.indexOf(t)!=e.lastIndexOf(t)&&t.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)r+=this.Patch_Margin,t=e.substring(n.start2-r,n.start2+n.length1+r);r+=this.Patch_Margin;var i=e.substring(n.start2-r,n.start2);i&&n.diffs.unshift(new d.Diff(k,i));var s=e.substring(n.start2+n.length1,n.start2+n.length1+r);s&&n.diffs.push(new d.Diff(k,s)),n.start1-=i.length,n.start2-=i.length,n.length1+=i.length+s.length,n.length2+=i.length+s.length}};d.prototype.patch_make=function(n,e,t){var r,i;if(typeof n=="string"&&typeof e=="string"&&typeof t>"u")r=n,i=this.diff_main(r,e,!0),i.length>2&&(this.diff_cleanupSemantic(i),this.diff_cleanupEfficiency(i));else if(n&&typeof n=="object"&&typeof e>"u"&&typeof t>"u")i=n,r=this.diff_text1(i);else if(typeof n=="string"&&e&&typeof e=="object"&&typeof t>"u")r=n,i=e;else if(typeof n=="string"&&typeof e=="string"&&t&&typeof t=="object")r=n,i=t;else throw new Error("Unknown call format to patch_make.");if(i.length===0)return[];for(var s=[],o=new d.patch_obj,a=0,l=0,c=0,h=r,u=r,f=0;f<i.length;f++){var g=i[f][0],m=i[f][1];switch(!a&&g!==k&&(o.start1=l,o.start2=c),g){case T:o.diffs[a++]=i[f],o.length2+=m.length,u=u.substring(0,c)+m+u.substring(c);break;case _:o.length1+=m.length,o.diffs[a++]=i[f],u=u.substring(0,c)+u.substring(c+m.length);break;case k:m.length<=2*this.Patch_Margin&&a&&i.length!=f+1?(o.diffs[a++]=i[f],o.length1+=m.length,o.length2+=m.length):m.length>=2*this.Patch_Margin&&a&&(this.patch_addContext_(o,h),s.push(o),o=new d.patch_obj,a=0,h=u,l=c);break}g!==T&&(l+=m.length),g!==_&&(c+=m.length)}return a&&(this.patch_addContext_(o,h),s.push(o)),s};d.prototype.patch_deepCopy=function(n){for(var e=[],t=0;t<n.length;t++){var r=n[t],i=new d.patch_obj;i.diffs=[];for(var s=0;s<r.diffs.length;s++)i.diffs[s]=new d.Diff(r.diffs[s][0],r.diffs[s][1]);i.start1=r.start1,i.start2=r.start2,i.length1=r.length1,i.length2=r.length2,e[t]=i}return e};d.prototype.patch_apply=function(n,e){if(n.length==0)return[e,[]];n=this.patch_deepCopy(n);var t=this.patch_addPadding(n);e=t+e+t,this.patch_splitMax(n);for(var r=0,i=[],s=0;s<n.length;s++){var o=n[s].start2+r,a=this.diff_text1(n[s].diffs),l,c=-1;if(a.length>this.Match_MaxBits?(l=this.match_main(e,a.substring(0,this.Match_MaxBits),o),l!=-1&&(c=this.match_main(e,a.substring(a.length-this.Match_MaxBits),o+a.length-this.Match_MaxBits),(c==-1||l>=c)&&(l=-1))):l=this.match_main(e,a,o),l==-1)i[s]=!1,r-=n[s].length2-n[s].length1;else{i[s]=!0,r=l-o;var h;if(c==-1?h=e.substring(l,l+a.length):h=e.substring(l,c+this.Match_MaxBits),a==h)e=e.substring(0,l)+this.diff_text2(n[s].diffs)+e.substring(l+a.length);else{var u=this.diff_main(a,h,!1);if(a.length>this.Match_MaxBits&&this.diff_levenshtein(u)/a.length>this.Patch_DeleteThreshold)i[s]=!1;else{this.diff_cleanupSemanticLossless(u);for(var f=0,g,m=0;m<n[s].diffs.length;m++){var v=n[s].diffs[m];v[0]!==k&&(g=this.diff_xIndex(u,f)),v[0]===T?e=e.substring(0,l+g)+v[1]+e.substring(l+g):v[0]===_&&(e=e.substring(0,l+g)+e.substring(l+this.diff_xIndex(u,f+v[1].length))),v[0]!==_&&(f+=v[1].length)}}}}}return e=e.substring(t.length,e.length-t.length),[e,i]};d.prototype.patch_addPadding=function(n){for(var e=this.Patch_Margin,t="",r=1;r<=e;r++)t+=String.fromCharCode(r);for(var r=0;r<n.length;r++)n[r].start1+=e,n[r].start2+=e;var i=n[0],s=i.diffs;if(s.length==0||s[0][0]!=k)s.unshift(new d.Diff(k,t)),i.start1-=e,i.start2-=e,i.length1+=e,i.length2+=e;else if(e>s[0][1].length){var o=e-s[0][1].length;s[0][1]=t.substring(s[0][1].length)+s[0][1],i.start1-=o,i.start2-=o,i.length1+=o,i.length2+=o}if(i=n[n.length-1],s=i.diffs,s.length==0||s[s.length-1][0]!=k)s.push(new d.Diff(k,t)),i.length1+=e,i.length2+=e;else if(e>s[s.length-1][1].length){var o=e-s[s.length-1][1].length;s[s.length-1][1]+=t.substring(0,o),i.length1+=o,i.length2+=o}return t};d.prototype.patch_splitMax=function(n){for(var e=this.Match_MaxBits,t=0;t<n.length;t++)if(!(n[t].length1<=e)){var r=n[t];n.splice(t--,1);for(var i=r.start1,s=r.start2,o="";r.diffs.length!==0;){var a=new d.patch_obj,l=!0;for(a.start1=i-o.length,a.start2=s-o.length,o!==""&&(a.length1=a.length2=o.length,a.diffs.push(new d.Diff(k,o)));r.diffs.length!==0&&a.length1<e-this.Patch_Margin;){var c=r.diffs[0][0],h=r.diffs[0][1];c===T?(a.length2+=h.length,s+=h.length,a.diffs.push(r.diffs.shift()),l=!1):c===_&&a.diffs.length==1&&a.diffs[0][0]==k&&h.length>2*e?(a.length1+=h.length,i+=h.length,l=!1,a.diffs.push(new d.Diff(c,h)),r.diffs.shift()):(h=h.substring(0,e-a.length1-this.Patch_Margin),a.length1+=h.length,i+=h.length,c===k?(a.length2+=h.length,s+=h.length):l=!1,a.diffs.push(new d.Diff(c,h)),h==r.diffs[0][1]?r.diffs.shift():r.diffs[0][1]=r.diffs[0][1].substring(h.length))}o=this.diff_text2(a.diffs),o=o.substring(o.length-this.Patch_Margin);var u=this.diff_text1(r.diffs).substring(0,this.Patch_Margin);u!==""&&(a.length1+=u.length,a.length2+=u.length,a.diffs.length!==0&&a.diffs[a.diffs.length-1][0]===k?a.diffs[a.diffs.length-1][1]+=u:a.diffs.push(new d.Diff(k,u))),l||n.splice(++t,0,a)}}};d.prototype.patch_toText=function(n){for(var e=[],t=0;t<n.length;t++)e[t]=n[t];return e.join("")};d.prototype.patch_fromText=function(n){var e=[];if(!n)return e;for(var t=n.split(`
4
+ `),r=0,i=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;r<t.length;){var s=t[r].match(i);if(!s)throw new Error("Invalid patch string: "+t[r]);var o=new d.patch_obj;for(e.push(o),o.start1=parseInt(s[1],10),s[2]===""?(o.start1--,o.length1=1):s[2]=="0"?o.length1=0:(o.start1--,o.length1=parseInt(s[2],10)),o.start2=parseInt(s[3],10),s[4]===""?(o.start2--,o.length2=1):s[4]=="0"?o.length2=0:(o.start2--,o.length2=parseInt(s[4],10)),r++;r<t.length;){var a=t[r].charAt(0);try{var l=decodeURI(t[r].substring(1))}catch{throw new Error("Illegal escape in patch_fromText: "+l)}if(a=="-")o.diffs.push(new d.Diff(_,l));else if(a=="+")o.diffs.push(new d.Diff(T,l));else if(a==" ")o.diffs.push(new d.Diff(k,l));else{if(a=="@")break;if(a!=="")throw new Error('Invalid patch mode "'+a+'" in: '+l)}r++}}return e};d.patch_obj=function(){this.diffs=[],this.start1=null,this.start2=null,this.length1=0,this.length2=0};d.patch_obj.prototype.toString=function(){var n,e;this.length1===0?n=this.start1+",0":this.length1==1?n=this.start1+1:n=this.start1+1+","+this.length1,this.length2===0?e=this.start2+",0":this.length2==1?e=this.start2+1:e=this.start2+1+","+this.length2;for(var t=["@@ -"+n+" +"+e+` @@
5
+ `],r,i=0;i<this.diffs.length;i++){switch(this.diffs[i][0]){case T:r="+";break;case _:r="-";break;case k:r=" ";break}t[i+1]=r+encodeURI(this.diffs[i][1])+`
6
+ `}return t.join("").replace(/%20/g," ")};me.exports=d;me.exports.diff_match_patch=d;me.exports.DIFF_DELETE=_;me.exports.DIFF_INSERT=T;me.exports.DIFF_EQUAL=k});import{hostname as yi}from"node:os";import{resolve as wi}from"node:path";import{createRequire as tr}from"module";var nr=tr("/"),fe,rr,ir;try{fe=nr("worker_threads"),rr=fe.Worker,ir=fe.isMarkedAsUntransferable}catch{}var N=Uint8Array,K=Uint16Array,at=Int32Array,De=new N([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Oe=new N([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),rt=new N([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Rt=function(n,e){for(var t=new K(31),r=0;r<31;++r)t[r]=e+=1<<n[r-1];for(var i=new at(t[30]),r=1;r<30;++r)for(var s=t[r];s<t[r+1];++s)i[s]=s-t[r]<<5|r;return{b:t,r:i}},fe=Rt(De,2),It=fe.b,it=fe.r;It[28]=258,it[258]=28;var zt=Rt(Oe,0),sr=zt.b,Ct=zt.r,st=new K(32768);for(C=0;C<32768;++C)ee=(C&43690)>>1|(C&21845)<<1,ee=(ee&52428)>>2|(ee&13107)<<2,ee=(ee&61680)>>4|(ee&3855)<<4,st[C]=((ee&65280)>>8|(ee&255)<<8)>>1;var ee,C,Y=(function(n,e,t){for(var r=n.length,i=0,s=new K(e);i<r;++i)n[i]&&++s[n[i]-1];var o=new K(e);for(i=1;i<e;++i)o[i]=o[i-1]+s[i-1]<<1;var a;if(t){a=new K(1<<e);var l=15-e;for(i=0;i<r;++i)if(n[i])for(var c=i<<4|n[i],h=e-n[i],u=o[n[i]-1]++<<h,f=u|(1<<h)-1;u<=f;++u)a[st[u]>>l]=c}else for(a=new K(r),i=0;i<r;++i)n[i]&&(a[i]=st[o[n[i]-1]++]>>15-n[i]);return a}),ie=new N(288);for(C=0;C<144;++C)ie[C]=8;var C;for(C=144;C<256;++C)ie[C]=9;var C;for(C=256;C<280;++C)ie[C]=7;var C;for(C=280;C<288;++C)ie[C]=8;var C,Ee=new N(32);for(C=0;C<32;++C)Ee[C]=5;var C,or=Y(ie,9,0),ar=Y(ie,9,1),lr=Y(Ee,5,0),hr=Y(Ee,5,1),et=function(n){for(var e=n[0],t=1;t<n.length;++t)n[t]>e&&(e=n[t]);return e},q=function(n,e,t){var r=e/8|0;return(n[r]|n[r+1]<<8)>>(e&7)&t},tt=function(n,e){var t=e/8|0;return(n[t]|n[t+1]<<8|n[t+2]<<16)>>(e&7)},lt=function(n){return(n+7)/8|0},Ut=function(n,e,t){return(e==null||e<0)&&(e=0),(t==null||t>n.length)&&(t=n.length),new N(n.subarray(e,t))};var cr=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],ne=function(n,e,t){var r=new Error(e||cr[n]);if(r.code=n,Error.captureStackTrace&&Error.captureStackTrace(r,ne),!t)throw r;return r},ur=function(n,e,t,r){var i=n.length,s=r?r.length:0;if(!i||e.f&&!e.l)return t||new N(0);var o=!t,a=o||e.i!=2,l=e.i;o&&(t=new N(i*3));var c=function(we){var be=t.length;if(we>be){var ue=new N(Math.max(be*2,we));ue.set(t),t=ue}},h=e.f||0,u=e.p||0,f=e.b||0,g=e.l,m=e.d,v=e.m,x=e.n,w=i*8;do{if(!g){h=q(n,u,1);var A=q(n,u+1,3);if(u+=3,A)if(A==1)g=ar,m=hr,v=9,x=5;else if(A==2){var $=q(n,u,31)+257,E=q(n,u+10,15)+4,y=$+q(n,u+5,31)+1;u+=14;for(var p=new N(y),U=new N(19),z=0;z<E;++z)U[rt[z]]=q(n,u+z*3,7);u+=E*3;for(var F=et(U),re=(1<<F)-1,W=Y(U,F,1),z=0;z<y;){var B=W[q(n,u,re)];u+=B&15;var b=B>>4;if(b<16)p[z++]=b;else{var D=0,R=0;for(b==16?(R=3+q(n,u,3),u+=2,D=p[z-1]):b==17?(R=3+q(n,u,7),u+=3):b==18&&(R=11+q(n,u,127),u+=7);R--;)p[z++]=D}}var L=p.subarray(0,$),O=p.subarray($);v=et(L),x=et(O),g=Y(L,v,1),m=Y(O,x,1)}else ne(1);else{var b=lt(u)+4,S=n[b-4]|n[b-3]<<8,M=b+S;if(M>i){l&&ne(0);break}a&&c(f+S),t.set(n.subarray(b,M),f),e.b=f+=S,e.p=u=M*8,e.f=h;continue}if(u>w){l&&ne(0);break}}a&&c(f+131072);for(var ye=(1<<v)-1,G=(1<<x)-1,Q=u;;Q=u){var D=g[tt(n,u)&ye],V=D>>4;if(u+=D&15,u>w){l&&ne(0);break}if(D||ne(2),V<256)t[f++]=V;else if(V==256){Q=u,g=null;break}else{var J=V-254;if(V>264){var z=V-257,I=De[z];J=q(n,u,(1<<I)-1)+It[z],u+=I}var X=m[tt(n,u)&G],he=X>>4;X||ne(3),u+=X&15;var O=sr[he];if(he>3){var I=Oe[he];O+=tt(n,u)&(1<<I)-1,u+=I}if(u>w){l&&ne(0);break}a&&c(f+131072);var ce=f+J;if(f<O){var ze=s-O,Ue=Math.min(O,ce);for(ze+f<0&&ne(3);f<Ue;++f)t[f]=r[ze+f]}for(;f<ce;++f)t[f]=t[f-O]}}e.l=g,e.p=Q,e.b=f,e.f=h,g&&(h=1,e.m=v,e.d=m,e.n=x)}while(!h);return f!=t.length&&o?Ut(t,0,f):t.subarray(0,f)},te=function(n,e,t){t<<=e&7;var r=e/8|0;n[r]|=t,n[r+1]|=t>>8},ke=function(n,e,t){t<<=e&7;var r=e/8|0;n[r]|=t,n[r+1]|=t>>8,n[r+2]|=t>>16},nt=function(n,e){for(var t=[],r=0;r<n.length;++r)n[r]&&t.push({s:r,f:n[r]});var i=t.length,s=t.slice();if(!i)return{t:Ot,l:0};if(i==1){var o=new N(t[0].s+1);return o[t[0].s]=1,{t:o,l:1}}t.sort(function(M,$){return M.f-$.f}),t.push({s:-1,f:25001});var a=t[0],l=t[1],c=0,h=1,u=2;for(t[0]={s:-1,f:a.f+l.f,l:a,r:l};h!=i-1;)a=t[t[c].f<t[u].f?c++:u++],l=t[c!=h&&t[c].f<t[u].f?c++:u++],t[h++]={s:-1,f:a.f+l.f,l:a,r:l};for(var f=s[0].s,r=1;r<i;++r)s[r].s>f&&(f=s[r].s);var g=new K(f+1),m=ot(t[h-1],g,0);if(m>e){var r=0,v=0,x=m-e,w=1<<x;for(s.sort(function($,E){return g[E.s]-g[$.s]||$.f-E.f});r<i;++r){var A=s[r].s;if(g[A]>e)v+=w-(1<<m-g[A]),g[A]=e;else break}for(v>>=x;v>0;){var b=s[r].s;g[b]<e?v-=1<<e-g[b]++-1:++r}for(;r>=0&&v;--r){var S=s[r].s;g[S]==e&&(--g[S],++v)}m=e}return{t:new N(g),l:m}},ot=function(n,e,t){return n.s==-1?Math.max(ot(n.l,e,t+1),ot(n.r,e,t+1)):e[n.s]=t},_t=function(n){for(var e=n.length;e&&!n[--e];);for(var t=new K(++e),r=0,i=n[0],s=1,o=function(l){t[r++]=l},a=1;a<=e;++a)if(n[a]==i&&a!=e)++s;else{if(!i&&s>2){for(;s>138;s-=138)o(32754);s>2&&(o(s>10?s-11<<5|28690:s-3<<5|12305),s=0)}else if(s>3){for(o(i),--s;s>6;s-=6)o(8304);s>2&&(o(s-3<<5|8208),s=0)}for(;s--;)o(i);s=1,i=n[a]}return{c:t.subarray(0,r),n:e}},Se=function(n,e){for(var t=0,r=0;r<e.length;++r)t+=n[r]*e[r];return t},Dt=function(n,e,t){var r=t.length,i=lt(e+2);n[i]=r&255,n[i+1]=r>>8,n[i+2]=n[i]^255,n[i+3]=n[i+1]^255;for(var s=0;s<r;++s)n[i+s+4]=t[s];return(i+4+r)*8},Tt=function(n,e,t,r,i,s,o,a,l,c,h){te(e,h++,t),++i[256];for(var u=nt(i,15),f=u.t,g=u.l,m=nt(s,15),v=m.t,x=m.l,w=_t(f),A=w.c,b=w.n,S=_t(v),M=S.c,$=S.n,E=new K(19),y=0;y<A.length;++y)++E[A[y]&31];for(var y=0;y<M.length;++y)++E[M[y]&31];for(var p=nt(E,7),U=p.t,z=p.l,F=19;F>4&&!U[rt[F-1]];--F);var re=c+5<<3,W=Se(i,ie)+Se(s,Ee)+o,B=Se(i,f)+Se(s,v)+o+14+3*F+Se(E,U)+2*E[16]+3*E[17]+7*E[18];if(l>=0&&re<=W&&re<=B)return Dt(e,h,n.subarray(l,l+c));var D,R,L,O;if(te(e,h,1+(B<W)),h+=2,B<W){D=Y(f,g,0),R=f,L=Y(v,x,0),O=v;var ye=Y(U,z,0);te(e,h,b-257),te(e,h+5,$-1),te(e,h+10,F-4),h+=14;for(var y=0;y<F;++y)te(e,h+3*y,U[rt[y]]);h+=3*F;for(var G=[A,M],Q=0;Q<2;++Q)for(var V=G[Q],y=0;y<V.length;++y){var J=V[y]&31;te(e,h,ye[J]),h+=U[J],J>15&&(te(e,h,V[y]>>5&127),h+=V[y]>>12)}}else D=or,R=ie,L=lr,O=Ee;for(var y=0;y<a;++y){var I=r[y];if(I>255){var J=I>>18&31;ke(e,h,D[J+257]),h+=R[J+257],J>7&&(te(e,h,I>>23&31),h+=De[J]);var X=I&31;ke(e,h,L[X]),h+=O[X],X>3&&(ke(e,h,I>>5&8191),h+=Oe[X])}else ke(e,h,D[I]),h+=R[I]}return ke(e,h,D[256]),h+R[256]},fr=new at([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),Ot=new N(0),dr=function(n,e,t,r,i,s){var o=s.z||n.length,a=new N(r+o+5*(1+Math.ceil(o/7e3))+i),l=a.subarray(r,a.length-i),c=s.l,h=(s.r||0)&7;if(e){h&&(l[0]=s.r>>3);for(var u=fr[e-1],f=u>>13,g=u&8191,m=(1<<t)-1,v=s.p||new K(32768),x=s.h||new K(m+1),w=Math.ceil(t/3),A=2*w,b=function(Qe){return(n[Qe]^n[Qe+1]<<w^n[Qe+2]<<A)&m},S=new at(25e3),M=new K(288),$=new K(32),E=0,y=0,p=s.i||0,U=0,z=s.w||0,F=0;p+2<o;++p){var re=b(p),W=p&32767,B=x[re];if(v[W]=B,x[re]=W,z<=p){var D=o-p;if((E>7e3||U>24576)&&(D>423||!c)){h=Tt(n,l,0,S,M,$,y,U,F,p-F,h),U=E=y=0,F=p;for(var R=0;R<286;++R)M[R]=0;for(var R=0;R<30;++R)$[R]=0}var L=2,O=0,ye=g,G=W-B&32767;if(D>2&&re==b(p-G))for(var Q=Math.min(f,D)-1,V=Math.min(32767,p),J=Math.min(258,D);G<=V&&--ye&&W!=B;){if(n[p+L]==n[p+L-G]){for(var I=0;I<J&&n[p+I]==n[p+I-G];++I);if(I>L){if(L=I,O=G,I>Q)break;for(var X=Math.min(G,I-2),he=0,R=0;R<X;++R){var ce=p-G+R&32767,ze=v[ce],Ue=ce-ze&32767;Ue>he&&(he=Ue,B=ce)}}}W=B,B=v[W],G+=W-B&32767}if(O){S[U++]=268435456|it[L]<<18|Ct[O];var we=it[L]&31,be=Ct[O]&31;y+=De[we]+Oe[be],++M[257+we],++$[be],z=p+L,++E}else S[U++]=n[p],++M[n[p]]}}for(p=Math.max(p,z);p<o;++p)S[U++]=n[p],++M[n[p]];h=Tt(n,l,c,S,M,$,y,U,F,p-F,h),c||(s.r=h&7|l[h/8|0]<<3,h-=7,s.h=x,s.p=v,s.i=p,s.w=z)}else{for(var p=s.w||0;p<o+c;p+=65535){var ue=p+65535;ue>=o&&(l[h/8|0]=c,ue=o),h=Dt(l,h+1,n.subarray(p,ue))}s.i=o}return Ut(a,0,r+lt(h)+i)};var gr=function(n,e,t,r,i){if(!i&&(i={l:1},e.dictionary)){var s=e.dictionary.subarray(-32768),o=new N(s.length+n.length);o.set(s),o.set(n,s.length),n=o,i.w=s.length}return dr(n,e.level==null?6:e.level,e.mem==null?i.l?Math.ceil(Math.max(8,Math.min(13,Math.log(n.length)))*1.5):20:12+e.mem,t,r,i)};function ht(n,e){return gr(n,e||{},0,0)}function Ft(n,e){return ur(n,{i:2},e&&e.out,e&&e.dictionary)}var mr=typeof TextDecoder<"u"&&new TextDecoder,vr=0;try{mr.decode(Ot,{stream:!0}),vr=1}catch{}var Bt="basalt/hkdf-aes-gcm/1",oe=12,Ne=128,Lt=oe+Ne/8+1,Z=20,Wt=new TextEncoder,pr=new TextDecoder,Fe={auth:"basalt/auth/1",path:"basalt/path/1",content:"basalt/content/1",nonce:"basalt/nonce/1"};function Ae(){let n=globalThis.crypto;if(!n?.subtle)throw new Error("WebCrypto is unavailable, so this vault cannot be opened");return n.subtle}function Vt(){let n=globalThis.crypto;if(!n?.getRandomValues)throw new Error("no secure random source is available, so a vault cannot be created here");return n.getRandomValues(new Uint8Array(Z))}async function Jt(n){if(n.length<16)throw new Error(`root secret is ${n.length} bytes, need at least 16`);let e=Ae(),t=await e.importKey("raw",ae(n),"HKDF",!1,["deriveKey","deriveBits"]),r=l=>({name:"HKDF",hash:"SHA-256",salt:new Uint8Array(0),info:Wt.encode(l)}),[i,s,o,a]=await Promise.all([e.deriveBits(r(Fe.auth),t,256),e.deriveKey(r(Fe.path),t,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"]),e.deriveKey(r(Fe.content),t,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"]),e.deriveKey(r(Fe.nonce),t,{name:"HMAC",hash:"SHA-256"},!1,["sign"])]);return{auth:new Uint8Array(i),path:s,content:o,nonce:a}}async function Kt(n,e,t){let r=Ae(),i=await yr(e,t),s=await r.encrypt({name:"AES-GCM",iv:ae(i),tagLength:Ne},n,ae(t)),o=new Uint8Array(oe+s.byteLength);return o.set(i,0),o.set(new Uint8Array(s),oe),o}async function Ht(n,e){if(e.length<oe+Ne/8)throw new Error(`sealed value is ${e.length} bytes, too short to contain a nonce and a tag`);let t=Ae(),r=e.subarray(0,oe),i=e.subarray(oe);try{let s=await t.decrypt({name:"AES-GCM",iv:ae(r),tagLength:Ne},n,ae(i));return new Uint8Array(s)}catch(s){throw new Error("sealed value failed authentication, so it is not what was stored",{cause:s})}}async function yr(n,e){let t=await Ae().sign("HMAC",n,ae(e));return new Uint8Array(t,0,oe)}async function Be(n,e){return xe(await Kt(n.path,n.nonce,Wt.encode(e)))}async function Le(n,e){let t=await Ht(n.path,We(e));return pr.decode(t)}var jt=0,Gt=1;async function wr(n,e){let t=br(e)?ht(e,{level:6}):void 0,r=t!==void 0&&t.length<e.length,i=r?t:e,s=new Uint8Array(1+i.length);return s[0]=r?Gt:jt,s.set(i,1),Kt(n.content,n.nonce,s)}function br(n){if(n.length===0)return!1;if(n.length<=Nt*2)return!0;let e=n.subarray(0,Nt);return ht(e,{level:6}).length<e.length}var Nt=4096;async function ge(n,e){return Promise.all([...e].map(async t=>{let r=await wr(n,t);return{name:await ct(r),bytes:r}}))}async function qt(n,e){let t=await Ht(n.content,e);if(t.length===0)throw new Error("sealed chunk carries no marker byte");let r=t[0],i=t.subarray(1);if(r===jt)return i;if(r===Gt)try{return Ft(i)}catch(s){throw new Error("sealed chunk claims to be deflated and is not",{cause:s})}throw new Error(`sealed chunk has an unknown marker byte ${r}`)}async function ct(n){let e=await Ae().digest("SHA-256",ae(n));return kr(new Uint8Array(e))}function Zt(n){return xe(n.auth)}function ae(n){return n.slice().buffer}function kr(n){let e="";for(let t of n)e+=t.toString(16).padStart(2,"0");return e}var de="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";function xe(n){let e="";for(let t=0;t<n.length;t+=3){let r=n[t],i=t+1<n.length?n[t+1]:void 0,s=t+2<n.length?n[t+2]:void 0;if(e+=de[r>>2],e+=de[(r&3)<<4|(i??0)>>4],i===void 0||(e+=de[(i&15)<<2|(s??0)>>6],s===void 0))break;e+=de[s&63]}return e}var Sr=(()=>{let n=new Int16Array(128).fill(-1);for(let e=0;e<de.length;e++)n[de.charCodeAt(e)]=e;return n})();function We(n){let e=n.length,t=new Uint8Array(Math.floor(e*3/4)),r=0,i=0,s=0;for(let o=0;o<e;o++){let a=n.charCodeAt(o),l=a<128?Sr[a]:-1;if(l<0)throw new Error(`invalid base64url character ${JSON.stringify(n[o])} at position ${o}`);i=i<<6|l,s+=6,s>=8&&(s-=8,t[r++]=i>>s&255)}return t.subarray(0,r)}var se=48,le=31,Xt=1,Yt={min:512,avg:1024,max:4096},Er=64,Ar=Yt.avg,xr=64*1024;function Pr(n){let e=Math.sqrt(Er*Math.max(n,1)),t=Math.min(xr,Math.max(Ar,Math.round(e/512)*512));return{min:Math.max(Yt.min,t/2),avg:t,max:t*4}}var ut={min:128*1024,avg:256*1024,max:1024*1024},$r=4*1024*1024;function Qt(n,e,t=ut.max){(!Number.isFinite(t)||t<=0)&&(t=ut.max);let r=e&&n<$r?Pr(n):ut,i=Math.min(r.max,t-Lt),s=Math.max(i,se*4);return{min:Math.min(r.min,s),avg:Math.min(r.avg,s),max:s}}function en(n,e,t){let r=t-1;for(;r>e&&(n[r]&192)===128;)r--;if(r<e)return t;let i=n[r],s=i<128?1:(i&224)===192?2:(i&240)===224?3:(i&248)===240?4:1;return t-r>=s?t:r>e?r:t}function*tn(n,e,t){let{min:r,avg:i,max:s}=e,o=n.length,a=1;for(let h=0;h<se-1;h++)a=Math.imul(a,le);let l=0,c=0;for(let h=0;h<o;h++){let u=n[h];h>=l+se?(c=c-Math.imul(n[h-se],a)|0,c=Math.imul(c,le),c=c+u|0):(c=Math.imul(c,le),c=c+u|0);let f=h-l+1,g=f>=r&&(c>>>0)%i===Xt;if(f>=s&&(g=!0),g){let m=t?en(n,l,h+1):h+1;yield{offset:l,bytes:n.subarray(l,m)},l=m,c=0,h=m-1}}l<o&&(yield{offset:l,bytes:n.subarray(l,o)})}async function*nn(n,e,t){let{min:r,avg:i,max:s}=e,o=1;for(let f=0;f<se-1;f++)o=Math.imul(o,le);let a=new Uint8Array(Math.max(s,se*2)),l=0,c=0,h=0,u=()=>{let f=t?en(a,0,l):l,g={offset:h,bytes:a.slice(0,f)};h+=f;let m=l-f;a.copyWithin(0,f,l),l=m,c=0;for(let v=0;v<m;v++)c=Math.imul(c,le),c=c+a[v]|0;return g};for await(let f of n)for(let g=0;g<f.length;g++){let m=f[g];a[l++]=m,l>=se+1?(c=c-Math.imul(a[l-1-se],o)|0,c=Math.imul(c,le),c=c+m|0):(c=Math.imul(c,le),c=c+m|0),(l>=s||l>=r&&(c>>>0)%i===Xt)&&(yield u())}if(l>0){let f={offset:h,bytes:a.slice(0,l)};h+=l,l=0,yield f}}var Mr=new Set(["md","txt","canvas","json","csv","yml","yaml","xml","html","css","js","ts","svg","bib","tex"]);function rn(n){let e=n.lastIndexOf(".");return e<0?!1:Mr.has(n.slice(e+1).toLowerCase())}var Cr=new Set(["canvas","json"]);function sn(n){let e=n.lastIndexOf(".");return e<0?!1:Cr.has(n.slice(e+1).toLowerCase())}var hn=er(on(),1),_r=-1,Tr=0,Rr=1;function an(n){let e=[],t=0;for(let[r,i]of n)r===Tr?t+=i.length:r===_r?(e.push({start:t,end:t+i.length}),t+=i.length):e.push({start:t,end:t});return e}function Ir(n,e){for(let t of n)for(let r of e){let i=t.start===t.end,s=r.start===r.end;if(!(i&&s)){if(i){if(r.start<t.start&&t.start<r.end)return t;continue}if(s){if(t.start<r.start&&r.start<t.end)return r;continue}if(t.start<r.end&&r.start<t.end)return t}}}function cn(n,e,t,r=()=>!0){if(e===t)return{kind:"take",text:e,why:"both sides already agree"};if(n===e)return{kind:"take",text:t,why:"no local change since the last sync"};if(n===t)return{kind:"take",text:e,why:"no remote change since the last sync"};let i=new hn.diff_match_patch,s=i.diff_main(n,e,!0,0);s.length>2&&(i.diff_cleanupSemantic(s),i.diff_cleanupEfficiency(s));let o=i.diff_main(n,t,!0,0);o.length>2&&(i.diff_cleanupSemantic(o),i.diff_cleanupEfficiency(o));let a=Ir(an(s),an(o));if(a!==void 0)return{kind:"conflict",why:`both devices changed the same text, at characters ${a.start} to ${a.end} of the last synced version`};let l=ln(i,n,s,t),c=ln(i,n,o,e);if(l.failed>0||c.failed>0){let u=Math.max(l.failed,c.failed),f=Math.max(l.total,c.total);return{kind:"conflict",why:`${u} of ${f} changes could not be placed in the other version`}}if(!zr(l.text,c.text))return{kind:"conflict",why:"merging the two versions in either order gives different content, so at least one change was placed wrongly"};let h=Ur(s,l.text);return h!==void 0?{kind:"conflict",why:`the merge reported success but ${Dr(h)} is not in the result`}:r(l.text)?{kind:"merged",text:l.text}:{kind:"conflict",why:"both sides merged cleanly and the result is no longer a valid file of its kind"}}function zr(n,e){if(n===e)return!0;let t=n.split(`
7
+ `).sort(),r=e.split(`
8
+ `).sort();if(t.length!==r.length)return!1;for(let i=0;i<t.length;i++)if(t[i]!==r[i])return!1;return!0}function ln(n,e,t,r){let[i,s]=n.patch_apply(n.patch_make(e,t),r);return{text:i,failed:s.filter(o=>!o).length,total:s.length}}function Ur(n,e){for(let[t,r]of n)if(t===Rr&&!e.includes(r))return r}function Dr(n){let e=n.replace(/\s+/g," ").trim(),t=e.length>60?`${e.slice(0,57)}...`:e;return`a local edit (${JSON.stringify(t)})`}function un(n,e,t){let r=n.lastIndexOf("."),i=n.lastIndexOf("/"),s=r>i,o=s?n.slice(0,r):n,a=s?n.slice(r):"",l=(h,u=2)=>String(h).padStart(u,"0"),c=`${t.getFullYear()}${l(t.getMonth()+1)}${l(t.getDate())}${l(t.getHours())}${l(t.getMinutes())}`;return`${o} (Conflicted copy ${Or(e)} ${c})${a}`}function Or(n){return n.replace(/[-\\/:*?"<>|\s]/g,"-").replace(/-{2,}/g,"-").replace(/^[.\-\s]+|[.\-\s]+$/g,"").slice(0,32)||"device"}function ft(n){return{path:n,prev:"",folder:!1,ctime:0,mtime:0,size:0,hash:"",chunks:[],synchash:"",syncuid:0,synctime:0}}function fn(n){let{local:e,remote:t,index:r,mergeable:i}=n,s=r.synchash;return e?.folder||t?.folder?Fr(e,t):e===void 0&&(t===void 0||t.deleted)?{kind:"nothing",why:"absent on both sides"}:t===void 0?{kind:"upload",why:"new file, the server has never held this path"}:e===void 0?Nr(t,r):t.deleted?e.hash===s&&s!==""?{kind:"deleteLocal",why:"deleted on another device and unchanged here"}:s===""?{kind:"upload",why:"deleted on the server but never synced from here"}:{kind:"upload",why:"deleted on another device but edited here, so the edit is kept and re-sent"}:e.hash===t.hash?{kind:"nothing",why:"the two sides hold the same content"}:s===""?{kind:"conflict",why:"both sides have content and there is no last-synced version to merge from"}:e.hash===s?{kind:"download",why:"changed on another device and unchanged here"}:t.hash===s?{kind:"upload",why:"changed here and unchanged on the server"}:i?{kind:"merge",why:"changed on both sides since the last sync"}:{kind:"conflict",why:"changed on both sides and this file cannot be merged"}}function Fr(n,e){return n!==void 0&&n.folder?e===void 0?{kind:"upload",why:"new folder, the server has never held this path"}:e.deleted?{kind:"nothing",why:"folder deleted elsewhere; its files decide"}:e.folder?{kind:"nothing",why:"folder exists on both sides"}:{kind:"clash",why:"a folder here and a file of the same name on another device"}:e?.folder&&!e.deleted?n!==void 0?{kind:"clash",why:"a file here and a folder of the same name on another device"}:{kind:"createLocalFolder",why:"folder exists on the server and not here"}:{kind:"nothing",why:"folder absent on both sides"}}function Nr(n,e){let t=e.synchash;return n.deleted?{kind:"nothing",why:"deleted on both sides"}:t===""?{kind:"download",why:"new on the server"}:n.hash===t?{kind:"deleteRemote",why:"deleted here and unchanged on the server"}:{kind:"restoreLocal",why:"deleted here but changed on another device, so the newer content is restored"}}function dt(n,e,t){return n.hash===""||n.chunks.length===0?!0:n.mtime!==e||n.size!==t}function Pe(n,e){let t=Math.ceil(e.mtime),r=Math.ceil(e.ctime);if(e.folder){n.folder=!0,n.mtime=0,n.ctime=0,n.size=0,n.hash="",n.chunks=[];return}dt(n,t,Br(e))&&(n.hash="",n.chunks=[]),n.folder=!1,n.mtime=t,n.ctime=r,n.size=e.size}function Br(n){return n.size}function dn(n,e,t){n.path=t,n.prev===""&&(n.prev=e),n.prev===t&&(n.prev=""),n.synctime=0}function ve(n,e,t,r,i){n.hash=e,n.chunks=[...t],n.synchash=e,n.syncuid=r,n.synctime=i,n.prev=""}function gn(n,e){if(!n.synctime)return!0;let t=n.size>102400?30:n.size>10240?20:10;return e-n.synctime>t*1e3}var gt=1,mn=6e4,$e=256;function vn(n){return{size:n.size,ctime:n.ctime,mtime:n.mtime,folder:n.folder??!1,deleted:n.deleted??!1,...n.prev?{prev:n.prev}:{}}}var P=class extends Error{constructor(t,r){super(r);this.code=t;this.name="ProtocolError"}code;get fatal(){return["proto","auth","cursor","busy","protostate","badchunk","internal"].includes(this.code)}};function Lr(n){return n.startsWith("wss://")?`. If that server has no TLS in front of it, pair with ws://${n.slice(6)} instead`:""}var j=class extends Error{constructor(e){super(e),this.name="ConnectionError"}},Ve=class{constructor(e,t){this.url=e;this.opts=t}url;opts;socket;replyWaiter;replyTimer;bodyWaiter;bodyQueue=[];expecting=0;requestsSent=0;closed=!1;closeReason;cursor=0;notifying=Promise.resolve();log(e,...t){this.opts.log?.(e,...t)}get appliedCursor(){return this.cursor}async connect(){if(this.socket)throw new Error("already connected");let t=(this.opts.socketFactory??Wr)(this.url);t.binaryType="arraybuffer",this.socket=t,await new Promise((r,i)=>{t.onopen=()=>r(),t.onerror=()=>i(new j(`could not connect to ${this.url}${Lr(this.url)}`)),t.onclose=s=>i(new j(`connection closed before opening: ${pn(s)}`))}),t.onerror=()=>this.die(new j("the connection failed")),t.onclose=r=>this.die(new j(`the connection closed: ${pn(r)}`)),t.onmessage=r=>this.onFrame(r.data)}die(e){if(this.closed)return;this.closed=!0,this.closeReason=e,this.log("transport closed",e.message),this.disarmReply();let t=this.replyWaiter,r=this.bodyWaiter;this.replyWaiter=void 0,this.bodyWaiter=void 0,t?.reject(e),r?.reject(e);try{this.socket?.close()}catch{}try{this.opts.onClosed?.(e)}catch{}}disarmReply(){this.replyTimer!==void 0&&(clearTimeout(this.replyTimer),this.replyTimer=void 0)}close(){this.die(new j("closed by this device"))}get isClosed(){return this.closed}onFrame(e){if(typeof e=="string"){let i;try{i=JSON.parse(e)}catch{this.die(new P("protostate",`server sent a frame that is not JSON: ${e.slice(0,120)}`));return}this.onTextFrame(i);return}let t=Vr(e);if(t===void 0){this.die(new P("protostate","server sent a frame of an unexpected type"));return}let r=this.bodyWaiter;if(r){this.bodyWaiter=void 0,r.resolve(t);return}if(this.bodyQueue.length>=this.expecting){this.die(new P("protostate",`server sent a ${t.length} byte body with nothing outstanding to receive it`));return}this.bodyQueue.push(t)}onTextFrame(e){if(e.op==="batch"){this.queueNotification(()=>this.onBatchFrame(e));return}if(e.op==="caught-up"){let r=H(e.cursor);this.queueNotification(async()=>{if(r!==this.cursor){this.die(new P("protostate",`server says caught up at ${r}, this device reached ${this.cursor}`));return}this.opts.onCaughtUp?.(r)});return}let t=this.replyWaiter;if(!t){this.die(new P("protostate",`server sent an unexpected reply: ${JSON.stringify(e)}`));return}this.replyWaiter=void 0,t.resolve(e)}queueNotification(e){this.notifying=this.notifying.then(e).catch(t=>{this.die(t instanceof Error?t:new Error(String(t)))})}async onBatchFrame(e){let t=H(e.from),r=H(e.to),i=e.entries;if(!Array.isArray(i))throw new P("protostate",`batch ${t} to ${r} carries no entries array, so an empty batch cannot be told from a lost one`);let s=i;if(t!==this.cursor+1)throw new P("protostate",`batch covers ${t} to ${r} but this device has applied up to ${this.cursor}, so something was skipped`);if(r<t)throw new P("protostate",`batch covers an empty range, ${t} to ${r}`);for(let o of s){if(typeof o?.uid!="number"||!Number.isFinite(o.uid))throw new P("protostate",`batch ${t}..${r} contains an entry with no uid`);if(typeof o.path!="string"||o.path==="")throw new P("protostate",`batch ${t}..${r} contains uid ${o.uid} with no path`);if(!Array.isArray(o.chunks))throw new P("protostate",`batch ${t}..${r} contains uid ${o.uid} with no chunks array`);if(o.uid<t||o.uid>r)throw new P("protostate",`batch ${t}..${r} contains uid ${o.uid}`)}await this.opts.onBatch({from:t,to:r,entries:s}),this.cursor=r}send(e){if(this.closed||!this.socket)throw this.closeReason??new j("not connected");this.socket.send(JSON.stringify(e))}sendBody(e){if(this.closed||!this.socket)throw this.closeReason??new j("not connected");this.socket.send(e)}async request(e){if(this.replyWaiter)throw new Error("a request is already in flight");this.requestsSent++;let t=this.opts.timeoutMs??mn,r=await new Promise((i,s)=>{let o=setTimeout(()=>{this.die(new j(`no reply within ${t}ms`))},t);this.replyTimer=o,this.replyWaiter={resolve:a=>{this.disarmReply(),i(a)},reject:a=>{this.disarmReply(),s(a)}};try{this.send(e)}catch(a){this.disarmReply(),this.replyWaiter=void 0,s(a instanceof Error?a:new Error(String(a)))}});if(r.res==="err"){let i=new P(String(r.code??"unknown"),String(r.msg??"no message"));throw i.fatal&&this.die(i),i}return r}async body(){let e=this.bodyQueue.shift();if(e)return e;if(this.closed)throw this.closeReason??new j("not connected");return new Promise((t,r)=>{this.bodyWaiter={resolve:t,reject:r}})}async hello(e){this.cursor=e.cursor;let t=await this.request({op:"hello",proto:gt,crypto:Bt,vault:e.vault,token:e.token,device:e.device,cursor:e.cursor,...e.claim!==void 0?{claim:e.claim}:{}});if(t.res!=="ready")throw new P("protostate",`expected ready, got ${JSON.stringify(t)}`);let r={proto:H(t.proto),cursor:H(t.cursor),perFileMax:H(t.perFileMax),chunkMax:H(t.chunkMax),maxChunks:H(t.maxChunks)};if(r.proto!==gt)throw new P("proto",`server speaks protocol ${r.proto}, this client speaks ${gt}`);return this.log("ready",r),r}async put(e,t,r,i){let s=await this.request({op:"put",path:e,meta:vn(t),chunks:[...r]});if(s.res==="have")return{uid:H(s.uid),uploaded:0,bytes:0};if(s.res!=="want")throw new P("protostate",`expected want or have, got ${JSON.stringify(s)}`);let o=mt(s.chunks),a=new Set(r),l=0;for(let h of o){if(!a.has(h))throw new P("badchunk",`server asked for ${h}, which this put does not contain`);let u=await i(h);this.sendBody(u),l+=u.length}return{uid:await this.awaitAck(),uploaded:o.length,bytes:l}}async putMany(e,t){if(e.length===0)return{results:[],uploaded:0,bytes:0};if(e.length>$e)throw new P("toolarge",`${e.length} entries in one batch, the limit is ${$e}`);let r=await this.request({op:"putmany",entries:e.map(c=>({path:c.path,meta:vn(c.meta),chunks:[...c.names]}))}),i=r,s=0,o=0;if(r.res==="want"){let c=mt(r.chunks),h=new Set;for(let u of e)for(let f of u.names)h.add(f);for(let u of c){if(!h.has(u))throw new P("badchunk",`server asked for ${u}, which this batch does not contain`);let f=await t(u);this.sendBody(f),o+=f.length}s=c.length,i=await this.awaitReply()}if(i.res!=="acks")throw new P("protostate",`expected acks, got ${JSON.stringify(i)}`);let a=i.results;if(!Array.isArray(a)||a.length!==e.length)throw new P("protostate",`${e.length} entries went up and ${Array.isArray(a)?a.length:"no"} results came back`);let l=a.map(c=>{let h=c??{};return h.code!==void 0?{uid:0,error:new P(String(h.code),String(h.msg??"no message"))}:{uid:H(h.uid)}});for(let c of l)c.error?.fatal&&this.die(c.error);return{results:l,uploaded:s,bytes:o}}async awaitAck(){let e=await this.awaitReply();if(e.res!=="ack")throw new P("protostate",`expected ack, got ${JSON.stringify(e)}`);return H(e.uid)}async awaitReply(){let e=await new Promise((t,r)=>{if(this.closed){r(this.closeReason??new j("not connected"));return}let i=this.opts.timeoutMs??mn,s=setTimeout(()=>{this.die(new j(`no acknowledgement within ${i}ms`))},i);this.replyWaiter={resolve:o=>{clearTimeout(s),t(o)},reject:o=>{clearTimeout(s),r(o)}}});if(e.res==="err"){let t=new P(String(e.code??"unknown"),String(e.msg??"no message"));throw t.fatal&&this.die(t),t}return e}async get(e){let t=await this.request({op:"get",uid:e});if(t.res!=="chunks")throw new P("protostate",`expected chunks, got ${JSON.stringify(t)}`);return{uid:H(t.uid),size:H(t.size),chunks:mt(t.chunks)}}async history(e,t={}){let r=await this.request({op:"history",path:e,...t.before!==void 0?{before:t.before}:{},...t.limit!==void 0?{limit:t.limit}:{}});if(r.res!=="history")throw new P("protostate",`expected history, got ${JSON.stringify(r)}`);return yn(r.entries,"history")}async deleted(e){let t=await this.request({op:"deleted",...e!==void 0?{limit:e}:{}});if(t.res!=="deleted")throw new P("protostate",`expected deleted, got ${JSON.stringify(t)}`);return{entries:yn(t.entries,"deleted"),more:t.more===!0}}async fetch(e){if(e.length===0)return[];this.expecting+=e.length;let t=this.request({op:"fetch",chunks:[...e]}),r=[];try{for(let i=0;i<e.length;i++){let s=await Promise.race([this.body(),t.then(a=>{throw new P("protostate",`expected a chunk body, got ${JSON.stringify(a)}`)})]),o=await ct(s);if(o!==e[i])throw new P("badchunk",`asked for ${e[i]} and received ${s.length} bytes that hash to ${o}`);r.push(s)}}catch(i){throw this.expecting=0,this.disarmReply(),this.replyWaiter=void 0,i instanceof P&&i.code==="badchunk"&&this.die(i),i}return this.expecting=Math.max(0,this.expecting-e.length),this.disarmReply(),this.replyWaiter=void 0,r}async ping(){let e=await this.request({op:"ping"});if(e.res!=="pong")throw new P("protostate",`expected pong, got ${JSON.stringify(e)}`)}},Je=class{constructor(e=0,t=3e5,r=5e3,i=!0,s=Math.random){this.min=e;this.max=t;this.base=r;this.jitter=i;this.random=s}min;max;base;jitter;random;count=0;nextAt=0;success(e){this.count=0,this.nextAt=e+this.delay()}fail(e){this.count++,this.nextAt=e+this.delay()}delay(){if(this.count===0)return this.min;let e=this.base*Math.pow(2,this.count-1);return this.jitter&&(e*=.5+.5*this.random()),Math.floor(Math.min(this.max,this.min+e))}readyAt(){return this.nextAt}isReady(e){return e>=this.nextAt}get failures(){return this.count}};function Wr(n){let e=globalThis.WebSocket;if(!e)throw new Error("no WebSocket available in this environment");return new e(n)}function Vr(n){if(n instanceof Uint8Array)return n;if(n instanceof ArrayBuffer)return new Uint8Array(n);if(ArrayBuffer.isView(n))return new Uint8Array(n.buffer,n.byteOffset,n.byteLength)}function H(n){return typeof n=="number"&&Number.isFinite(n)?n:0}function mt(n){return Array.isArray(n)?n.filter(e=>typeof e=="string"):[]}function pn(n){let e=n.code??0,t=n.reason?`, ${n.reason}`:"";return`code ${e}${t}`}function yn(n,e){if(!Array.isArray(n))throw new P("protostate",`${e} came back without a list of entries`);return n}function wn(n){let e=n.split("/");e.pop();let t=[],r="";for(let i of e)r=r?`${r}/${i}`:i,t.push(r);return t}function Me(n){return n.length===0?"-empty-":n.join(",")}function Jr(n){return n===""||n==="-empty-"?[]:n.split(",")}function bn(){return{uploaded:0,downloaded:0,merged:0,conflicted:0,deletedLocally:0,deletedRemotely:0,restored:0,foldersCreated:0,unchanged:0,waiting:0,retrying:0,skipped:0,blocked:0,chunksSent:0,bytesSent:0}}var Ke=class{constructor(e){this.opts=e}opts;entries=new Map;remote=new Map;pending=new Set;retries=new Map;skipped=new Map;blocked=new Set;cannotStream=!1;outbox=[];outboxBytes=0;inbox=[];inboxBytes=0;limits;unsealed=new Map;cursor=0;syncing=!1;again=!1;started=!1;now(){return this.opts.now?.()??Date.now()}log(e,...t){this.opts.log?.(e,...t)}mergeable(e){return(this.opts.mergeable??rn)(e)}sizesFor(e,t){return Qt(e,t,this.limits?.chunkMax)}get coalesce(){return this.opts.coalesceWrites??!0}status(){let e=0;for(let t of this.entries.values())t.folder||e++;return{cursor:this.cursor,files:e,pending:this.pending.size,retrying:this.retries.size,skipped:this.skipped.size,syncing:this.syncing}}async start(){if(this.started)throw new Error("already started");this.started=!0;let e=await this.opts.store.load();if(e){this.cursor=e.cursor;for(let[r,i]of Object.entries(e.entries))this.entries.set(r,{...ft(r),...i});for(let[r,i]of Object.entries(e.remote))this.remote.set(r,i);for(let r of e.pending)this.pending.add(r);this.log("index loaded",{cursor:this.cursor,entries:this.entries.size,pending:this.pending.size})}let t=await this.opts.transport.hello({vault:this.opts.vaultId,token:this.opts.token,device:this.opts.device,cursor:this.cursor,...this.opts.claim!==void 0?{claim:this.opts.claim}:{}});return this.limits=t,this.log("connected",t),t}async acceptBatch(e){for(let t of e.entries){let r=await this.plaintextPath(t.path);if(this.remote.set(r,{uid:t.uid,folder:t.folder,deleted:t.deleted,mtime:t.mtime,size:t.size,hash:Me(t.chunks)}),this.pending.add(r),t.prev){let i=await this.plaintextPath(t.prev);this.remote.set(i,{uid:t.uid,folder:!1,deleted:!0,mtime:t.mtime,size:0,hash:""}),this.pending.add(i)}}this.cursor=e.to}async plaintextPath(e){let t=this.unsealed.get(e);if(t!==void 0)return t;let r=await Le(this.opts.keys,e);return this.unsealed.set(e,r),r}async sync(e={}){if(this.syncing)return this.again=!0,bn();this.syncing=!0;try{let t=await this.pass(e);for(;this.again;){this.again=!1;let r=await this.pass(e);t=Kr(t,r)}return t}finally{this.syncing=!1}}async pass(e={}){let t=bn(),r=this.now(),i=e.coalesceWrites??this.coalesce;(this.outbox.length>0||this.inbox.length>0)&&(this.log("a pass ended early, discarding what it had queued",{writes:this.outbox.length,reads:this.inbox.length}),this.outbox=[],this.outboxBytes=0,this.inbox=[],this.inboxBytes=0);let s=await this.opts.vault.list(),o=new Map(s.map(h=>[h.path,h]));for(let h of s){let u=this.entryFor(h.path);Pe(u,h)}let a=new Set;for(let[h,u]of o)u.folder||a.add(h);let l=new Set,c=new Set([...o.keys(),...this.entries.keys(),...this.remote.keys()]);for(let h of[...c].sort()){let u=this.skipped.get(h);if(u){if(vt(this.entries.get(h))===u.fingerprint){t.skipped++;continue}this.skipped.delete(h),this.log("skipped file changed, trying again",h)}let f=wn(h).find(m=>a.has(m));if(f!==void 0&&!o.has(h)){l.add(h),this.blocked.has(h)||this.log("cannot be both",h,`${f} is a file here and a folder elsewhere`),t.blocked++;continue}let g=this.retries.get(h);if(g&&g.at>r){t.retrying++;continue}try{this.opts.onProgress?.(h),await this.reconcile(h,o.get(h),t,r,i),this.retries.delete(h)}catch(m){this.recordFailure(h,m,t)}}return await this.fill(t),await this.flush(t),this.opts.onProgress?.(void 0),this.blocked=l,this.prune(o),await this.save(),t}entryFor(e){let t=this.entries.get(e);return t||(t=ft(e),this.entries.set(e,t)),t}async reconcile(e,t,r,i,s){let o=this.entryFor(e),a=this.remote.get(e),l=this.limits?.perFileMax??0;if(t&&!t.folder&&l>0&&t.size>l){this.recordFailure(e,qr(t.size,l),r);return}let c,h;t&&(!t.folder&&dt(o,Math.ceil(t.mtime),t.size)&&(h=await this.rehash(o,e,t.size)),c={folder:t.folder,mtime:o.mtime,size:o.size,hash:o.hash});let u=fn({local:c,remote:a,index:o,mergeable:this.mergeable(e)});if(s&&u.kind!=="nothing"&&c&&!t?.folder&&!gn(o,i)){r.waiting++;return}await this.act(e,u,o,c,a,r,h),this.pending.delete(e)}async rehash(e,t,r){let i=await this.streamScan(e,t,r);if(i)return i;let s=await this.opts.vault.read(t),o=this.mergeable(t),a=[...tn(s,this.sizesFor(s.length,o),o)],l=a.map(c=>c.bytes);if(s.length<=kn){let c=await ge(this.opts.keys,l);return e.chunks=c.map(h=>h.name),e.hash=Me(e.chunks),e.size=s.length,{bytes:s,pieces:a,names:e.chunks,sealed:c}}return e.chunks=await this.namesOf(l),e.hash=Me(e.chunks),e.size=s.length,{bytes:s,pieces:a,names:e.chunks}}async streamScan(e,t,r){let i=this.opts.vault;if(!(!i.readBlocks||!i.readRange||this.cannotStream)&&!(r===void 0||r<=kn))try{return await this.streamed(e,t,r)}catch(s){this.cannotStream=!0,this.log("streaming is not available here, reading whole files instead",t,{why:s.message});return}}async streamed(e,t,r){let i=this.opts.vault,s=this.mergeable(t),o=[],a=[],l=0;for await(let c of nn(i.readBlocks(t),this.sizesFor(r,s),s)){let h=await ge(this.opts.keys,[c.bytes]);o.push(h[0].name),a.push({start:c.offset,end:c.offset+c.bytes.length}),l+=c.bytes.length}return e.chunks=o,e.hash=Me(o),e.size=l,{names:o,spans:a,path:t,size:l}}namesOf(e){return jr(this.opts.keys,e)}async act(e,t,r,i,s,o,a){switch(t.kind){case"nothing":o.unchanged++,i&&s&&!s.deleted&&i.hash===s.hash&&ve(r,i.hash,r.chunks,s.uid,this.now());return;case"upload":await this.upload(e,r,o,!0,a);return;case"download":case"restoreLocal":{if(!s)return;await this.receive(e,r,s,t.kind,t.why,o);return}case"createLocalFolder":await this.opts.vault.mkdir(e),r.folder=!0,s&&ve(r,"",[],s.uid,this.now()),o.foldersCreated++;return;case"clash":this.skipped.set(e,{why:`${t.why}. Rename one of them, and it will sync.`,fingerprint:vt(r)}),o.skipped++,this.log("cannot be both",e,t.why);return;case"deleteLocal":await this.opts.vault.remove(e),this.entries.delete(e),o.deletedLocally++,this.log("deleted locally",e,t.why);return;case"deleteRemote":await this.queue({path:e,size:0,entry:{path:await this.sealedPath(e),meta:{size:0,ctime:0,mtime:this.now(),deleted:!0},names:[]},bodyOf:En,commit:l=>{this.remote.set(e,{uid:l,folder:!1,deleted:!0,mtime:this.now(),size:0,hash:""}),this.entries.delete(e),o.deletedRemotely++,this.log("deleted on the server",e,t.why)}},o);return;case"merge":await this.merge(e,r,s,o);return;case"conflict":await this.conflict(e,r,s,o,t.why);return}}async upload(e,t,r,i=!1,s){if(t.folder){await this.queue({path:e,size:0,entry:{path:await this.sealedPath(e),meta:{size:0,ctime:0,mtime:0,folder:!0},names:[]},bodyOf:En,commit:u=>{ve(t,"",[],u,this.now()),this.remote.set(e,{uid:u,folder:!0,deleted:!1,mtime:0,size:0,hash:""}),i&&r.uploaded++}},r);return}let o=await this.planUpload(t,e,s),a=t.hash,l=[...t.chunks],c=t.size,h=t.mtime;await this.queue({path:e,size:c,entry:{path:await this.sealedPath(e),meta:{size:c,ctime:t.ctime,mtime:h,...t.prev?{prev:await this.sealedPath(t.prev)}:{}},names:o.names},bodyOf:o.bodyOf,commit:u=>{ve(t,a,l,u,this.now()),this.remote.set(e,{uid:u,folder:!1,deleted:!1,mtime:h,size:c,hash:a}),i&&r.uploaded++,this.log("uploaded",e)}},r)}async queue(e,t){this.outbox.push(e),this.outboxBytes+=e.size,(this.outbox.length>=$e||this.outboxBytes>=Sn)&&await this.flush(t)}async flush(e){if(this.outbox.length===0)return;let t=this.outbox;this.outbox=[],this.outboxBytes=0;let r=new Map;for(let s of t)for(let o of s.entry.names)r.has(o)||r.set(o,s.bodyOf);let i;try{i=await this.opts.transport.putMany(t.map(s=>s.entry),async s=>{let o=r.get(s);if(!o)throw new Error(`server asked for ${s}, which no queued file contains`);return o(s)})}catch(s){for(let o of t)this.recordFailure(o.path,s,e);return}e.chunksSent+=i.uploaded,e.bytesSent+=i.bytes;for(let s=0;s<t.length;s++){let o=t[s],a=i.results[s];if(a.error){this.recordFailure(o.path,a.error,e);continue}o.commit(a.uid)}}async planUpload(e,t,r){let i=r??await this.scan(e,t);if(i.sealed){let c=new Map(i.sealed.map(h=>[h.name,h.bytes]));return{names:i.names,bodyOf:async h=>{let u=c.get(h);if(!u)throw new Error(`no sealed body for ${h} of ${t}`);return u}}}let s=this.opts.keys,o=this.opts.vault;if(i.spans){let c=new Map(i.names.map((h,u)=>[h,i.spans[u]]));return{names:i.names,bodyOf:async h=>{let u=c.get(h);if(!u)throw new Error(`no chunk named ${h} in ${t}`);let f=await o.readRange(i.path,u.start,u.end),g=await ge(s,[f]);if(g[0].name!==h)throw new Error(`${t} changed while it was being sent, so it was not sent`);return g[0].bytes}}}let a=new Map;for(let c=0;c<i.names.length;c++){let h=i.pieces[c];a.set(i.names[c],{start:h.offset,end:h.offset+h.bytes.length})}let l=i.bytes;return{names:i.names,bodyOf:async c=>{let h=a.get(c);if(!h)throw new Error(`no chunk named ${c} in ${t}`);return(await ge(s,[l.subarray(h.start,h.end)]))[0].bytes}}}scan(e,t){return this.rehash(e,t)}async receive(e,t,r,i,s,o){let a=Jr(r.hash);this.checkChunkCount(r.uid,a.length),this.inbox.push({path:e,entry:t,remote:r,chunks:a,kind:i,why:s}),this.inboxBytes+=r.size,(this.inbox.length>=$e||this.inboxBytes>=Sn)&&await this.fill(o)}async fill(e){if(this.inbox.length===0)return;let t=this.inbox;this.inbox=[],this.inboxBytes=0;let r=[],i=new Set;for(let o of t)for(let a of o.chunks)i.has(a)||(i.add(a),r.push(a));let s=new Map;if(r.length>0)try{let o=await this.opts.transport.fetch(r);for(let a=0;a<r.length;a++)s.set(r[a],o[a])}catch(o){for(let a of t)this.recordFailure(a.path,o,e);return}for(let o of t)try{await this.land(o,s),o.kind==="download"?e.downloaded++:e.restored++,this.log(o.kind,o.path,o.why)}catch(a){this.recordFailure(o.path,a,e)}}async land(e,t){let r=e.chunks.map(s=>{let o=t.get(s);if(!o)throw new Error(`the server did not send ${s}, which ${e.path} is made of`);return o}),i=await this.assemble(e.remote.uid,r);await this.opts.vault.write(e.path,i,{mtime:e.remote.mtime,ctime:e.remote.mtime}),Pe(e.entry,{folder:!1,mtime:e.remote.mtime,ctime:e.remote.mtime,size:i.length}),e.entry.chunks=[...e.chunks],e.entry.hash=Me(e.chunks),e.entry.size=i.length,ve(e.entry,e.entry.hash,e.entry.chunks,e.remote.uid,this.now())}async contentOf(e,t){let r=t!==void 0?{chunks:t}:await this.opts.transport.get(e);return r.chunks.length===0?new Uint8Array(0):(this.checkChunkCount(e,r.chunks.length),this.assemble(e,await this.opts.transport.fetch(r.chunks)))}checkChunkCount(e,t){let r=this.limits?.maxChunks??0;if(r>0&&t>r)throw new Error(`version ${e} names ${t} chunks, and this server said it stores at most ${r}`)}async assemble(e,t){if(t.length===0)return new Uint8Array(0);let r=[],i=0,s=this.limits?.perFileMax??0;for(let l of t){let c=await qt(this.opts.keys,l);if(i+=c.length,s>0&&i>s)throw new Error(`version ${e} is over ${i} bytes, and this server said it stores at most ${s}`);r.push(c)}let o=new Uint8Array(i),a=0;for(let l of r)o.set(l,a),a+=l.length;return o}async merge(e,t,r,i){if(!r)return;let s=new TextDecoder("utf-8",{fatal:!0}),o,a,l;try{o=s.decode(await this.contentOf(t.syncuid)),a=s.decode(await this.opts.vault.read(e)),l=s.decode(await this.contentOf(r.uid))}catch{let u="one side is not valid UTF-8, so merging it would rewrite bytes nobody edited";this.log("merge refused",e,u),await this.conflict(e,t,r,i,u);return}let c=cn(o,a,l,sn(e)?Zr:void 0);if(c.kind==="conflict"){this.log("merge refused",e,c.why),await this.conflict(e,t,r,i,c.why);return}let h=c.text;h!==a&&await this.opts.vault.write(e,new TextEncoder().encode(h),{mtime:this.now(),ctime:t.ctime}),Pe(t,{folder:!1,mtime:this.now(),ctime:t.ctime,size:h.length}),await this.upload(e,t,i),i.merged++,this.log("merged",e,c.kind==="merged"?"three-way":c.why)}freeConflictPath(e){return Gr(un(e,this.opts.device,new Date(this.now())),t=>this.opts.vault.exists(t))}async conflict(e,t,r,i,s){if(!r)return;let o=await this.freeConflictPath(e),a=await this.contentOf(r.uid);await this.opts.vault.write(o,a,{mtime:r.mtime,ctime:r.mtime});let l=this.entryFor(o);Pe(l,{folder:!1,mtime:r.mtime,ctime:r.mtime,size:a.length}),await this.upload(o,l,i),await this.upload(e,t,i),i.conflicted++,this.log("kept both",e,{copy:o,why:s})}async sealedPath(e){let t=await Be(this.opts.keys,e);return this.unsealed.set(t,e),t}recordFailure(e,t,r){let i=t instanceof Error?t.message:String(t),s=t?.code;if(s!==void 0&&["badentry","badname","toolarge"].includes(s)){this.skipped.set(e,{why:i,fingerprint:vt(this.entries.get(e))}),r.skipped++,this.log("skipped for good",e,i);return}let a=this.retries.get(e)??{count:0,error:"",at:0};a.count++,a.error=i,a.at=this.now()+Math.min(3e5,5e3*Math.pow(2,a.count)),this.retries.set(e,a),r.retrying++,this.log("will retry",e,{attempt:a.count,error:i})}prune(e){for(let[t,r]of this.entries){if(e.has(t))continue;let i=this.remote.get(t);i&&!i.deleted||r.synchash===""&&r.hash===""&&this.entries.delete(t)}for(let[t,r]of this.remote)r.deleted&&(e.has(t)||this.entries.has(t)||this.pending.has(t)||this.remote.delete(t))}async save(){let e={};for(let[r,i]of this.entries)e[r]=i;let t={};for(let[r,i]of this.remote)t[r]=i;await this.opts.store.save({cursor:this.cursor,entries:e,remote:t,pending:[...this.pending]})}noteRename(e,t){let r=this.entries.get(e);r&&(this.entries.delete(e),dn(r,e,t),this.entries.set(t,r))}};function Kr(n,e){let t={...n};for(let r of Object.keys(t))t[r]=n[r]+e[r];return t}function vt(n){return n?`${n.mtime}:${n.size}`:"gone"}var kn=8*1024*1024,Hr=16;async function jr(n,e,t=Hr){let r=[];for(let i=0;i<e.length;i+=t){let s=await ge(n,e.slice(i,i+t));for(let o of s)r.push(o.name)}return r}var Sn=8*1024*1024;async function Gr(n,e){if(!await e(n))return n;let t=n.lastIndexOf("."),r=n.lastIndexOf("/"),i=t>r,s=i?n.slice(0,t):n,o=i?n.slice(t):"";for(let a=2;a<1e3;a++){let l=`${s} ${a}${o}`;if(!await e(l))return l}throw new Error(`cannot find an unused name beside ${n}`)}function qr(n,e){let t=new Error(`${n} bytes, and this server said it stores at most ${e}, so it was not read`);return t.code="toolarge",t}async function En(n){throw new Error(`this put has no bodies, and the server asked for ${n}`)}function Zr(n){try{return JSON.parse(n),!0}catch{return!1}}var Xr=150,Ce=class{constructor(e){this.opts=e;let t;this.transport=new Ve(e.url,{onBatch:async r=>{await t.acceptBatch(r),r.entries.length>0&&this.soon()},onCaughtUp:()=>{this.caughtUp=!0},onClosed:r=>{this.endedWith=r,this.notifyEnded?.(r)},...e.timeoutMs!==void 0?{timeoutMs:e.timeoutMs}:{},...e.log!==void 0?{log:e.log}:{},...e.socketFactory!==void 0?{socketFactory:e.socketFactory}:{}}),t=new Ke({vault:e.vault,store:e.store,keys:e.keys,transport:this.transport,device:e.device,vaultId:e.vaultId,token:e.token,...e.claim!==void 0?{claim:e.claim}:{},...e.coalesceWrites!==void 0?{coalesceWrites:e.coalesceWrites}:{},...e.log!==void 0?{log:e.log}:{},...e.onProgress!==void 0?{onProgress:e.onProgress}:{}}),this.engine=t}opts;engine;transport;limits;soonTimer;caughtUp=!1;endedWith;notifyEnded;queue=Promise.resolve();serial(e){let t=this.queue.then(e,e);return this.queue=t.catch(()=>{}),t}get requestsSent(){return this.transport.requestsSent}get serverCursor(){return this.limits?.cursor??0}async connect(){await this.transport.connect(),this.limits=await this.engine.start();let e=this.opts.timeoutMs??3e4,t=Date.now()+e;for(;!this.caughtUp&&Date.now()<t;){if(this.endedWith)throw this.endedWith;await pt(25)}if(!this.caughtUp)throw new Error("the server never finished sending what it already had");return this.limits}async settle(e={},t=8){let r=await this.pass(e),i=r;for(let s=0;s<t&&ti(r);s++)await pt(60),r=await this.pass(e),i=ei(i,r);return i}pass(e){return this.serial(()=>this.engine.sync(e))}async runUntilClosed(e=3e4){if(this.endedWith)return this.endedWith;let t=this.opts.vault.watch?.(()=>{this.sync()}),r=setInterval(()=>{this.sync().then(()=>this.keepalive())},e);try{return await new Promise(i=>{this.notifyEnded=i})}finally{clearInterval(r),t?.()}}async keepalive(){try{await this.transport.ping()}catch{}}soon(){this.soonTimer===void 0&&(this.soonTimer=setTimeout(()=>{this.soonTimer=void 0,this.sync()},Xr))}async sync(e={}){try{return await this.pass(e)}catch(t){this.opts.log?.("sync failed",t.message);return}}async history(e,t={}){let r=await Be(this.opts.keys,e);return(await this.serial(()=>this.transport.history(r,t))).map(s=>this.asVersion(s,e))}async deleted(e){let t=await this.serial(()=>this.transport.deleted(e)),r=[];for(let i of t.entries)r.push({...this.asVersion(i,await Le(this.opts.keys,i.path)),restorable:i.restorable??0});return{notes:r,more:t.more}}async contentAt(e){return e.deleted||e.folder?new Uint8Array(0):this.serial(()=>this.engine.contentOf(e.uid))}async restore(e,t){if(e.deleted)throw new Error(`version ${e.uid} of ${e.path} is the deletion itself, not a version to restore`);if(e.folder){let o=t??e.path;return await this.opts.vault.mkdir(o),{path:o,bytes:0}}let r=await this.serial(()=>this.engine.contentOf(e.uid)),i=t??e.path,s=await this.opts.vault.exists(i)?Yr(i,e):i;return await this.opts.vault.write(s,r,{mtime:e.mtime,ctime:e.ctime}),{path:s,bytes:r.length}}async newestContentVersion(e){return(await this.history(e,{limit:50})).find(r=>!r.deleted)}asVersion(e,t){return{uid:e.uid,path:t,size:e.size,ctime:e.ctime,mtime:e.mtime,folder:e.folder,deleted:e.deleted,device:e.device,chunks:e.chunks?.length??0}}close(){this.soonTimer!==void 0&&(clearTimeout(this.soonTimer),this.soonTimer=void 0),this.transport.close()}};function Yr(n,e){let t=n.lastIndexOf("/"),r=t===-1?"":n.slice(0,t+1),i=t===-1?n:n.slice(t+1),s=i.lastIndexOf("."),o=s<=0?i:i.slice(0,s),a=s<=0?"":i.slice(s);return`${r}${o} (restored ${e.uid})${a}`}async function An(n,e={}){let t=new Je(0,3e5,5e3,!0);for(;e.keepGoing?.()??!0;){let r,i,s=!1;try{r=new Ce(n),await r.connect(),s=!0,t.success(Date.now()),e.onClient?.(r),e.onSynced?.(await r.settle(),r.serverCursor),i=await r.runUntilClosed()}catch(l){i=l}finally{e.onClient?.(void 0),r?.close()}if(i&&Qr(i)){e.onFatal?.(i);return}if(!(e.keepGoing?.()??!0))return;t.fail(Date.now());let o=t.delay(),a=i??new Error("the connection ended");s?e.onDisconnected?.(a,o):e.onUnreachable?.(a,o),await pt(o)}}function Qr(n){return n instanceof P&&n.fatal}function ei(n,e){return{uploaded:n.uploaded+e.uploaded,downloaded:n.downloaded+e.downloaded,merged:n.merged+e.merged,conflicted:n.conflicted+e.conflicted,deletedLocally:n.deletedLocally+e.deletedLocally,deletedRemotely:n.deletedRemotely+e.deletedRemotely,restored:n.restored+e.restored,foldersCreated:n.foldersCreated+e.foldersCreated,chunksSent:n.chunksSent+e.chunksSent,bytesSent:n.bytesSent+e.bytesSent,unchanged:e.unchanged,waiting:e.waiting,retrying:e.retrying,skipped:e.skipped,blocked:e.blocked}}function ti(n){return n.uploaded+n.downloaded+n.merged+n.conflicted+n.deletedLocally+n.deletedRemotely+n.restored+n.foldersCreated+n.waiting>0}var pt=n=>new Promise(e=>setTimeout(e,n));var He="basalt2_",yt=2,_e=4,xn=new TextEncoder,ni=new TextDecoder;function wt(n){if(n.secret.length!==Z)throw new Error(`a root secret is ${Z} bytes, not ${n.secret.length}`);let e=[xn.encode(n.url),xn.encode(n.vaultId)];for(let o of e)if(o.length>255)throw new Error("a pairing field is too long to encode");let t=1+Z+e.reduce((o,a)=>o+1+a.length,0),r=new Uint8Array(t);r[0]=yt,r.set(n.secret,1);let i=1+Z;for(let o of e)r[i++]=o.length,r.set(o,i),i+=o.length;let s=new Uint8Array(t+_e);return s.set(r,0),s.set($n(r),t),He+xe(s)}function Pn(n){let e=n.trim();if(e.startsWith("basalt1_"))throw new Error("this is a version 1 pairing string, from before the server token was folded into the root secret. Run basalt invite on a device that is already paired to get a current one.");if(!e.startsWith(He))throw new Error(`not a pairing string: it should start with ${He}`);let t;try{t=We(e.slice(He.length))}catch{throw new Error("this pairing string is damaged: it is not valid base64url")}if(t.length<1+Z+2+_e)throw new Error("this pairing string is too short to be complete");let r=t.subarray(0,t.length-_e),i=t.subarray(t.length-_e),s=$n(r);for(let u=0;u<_e;u++)if(i[u]!==s[u])throw new Error("this pairing string is damaged: it did not survive being copied");if(r[0]!==yt)throw new Error(`this pairing string is version ${r[0]}, and this device understands ${yt}`);let o=r.slice(1,1+Z),a=1+Z,l=u=>{if(a>=r.length)throw new Error(`this pairing string ends before its ${u}`);let f=r[a++];if(a+f>r.length)throw new Error(`this pairing string ends inside its ${u}`);let g=ni.decode(r.subarray(a,a+f));return a+=f,g},c=l("server address"),h=l("vault name");if(a!==r.length)throw new Error("this pairing string has more in it than it should");if(c===""||h==="")throw new Error("this pairing string has an empty field");return{url:c,secret:o,vaultId:h}}function $n(n){let e=4294967295;for(let t of n){e^=t;for(let r=0;r<8;r++)e=e&1?e>>>1^3988292384:e>>>1}return e=(e^4294967295)>>>0,new Uint8Array([e>>>24&255,e>>>16&255,e>>>8&255,e&255])}function Mn(n){return{url:n.url,vaultId:n.vaultId,device:n.device,secret:xe(n.secret),...n.bootstrap?{bootstrap:n.bootstrap}:{}}}function Cn(n,e){if(typeof n!="object"||n===null)throw new Error(`${e} does not hold a configuration`);let t=n,r=o=>{let a=t[o];if(typeof a!="string"||a==="")throw new Error(`${e} has no ${o}`);return a},i=We(r("secret"));if(i.length!==Z)throw new Error(`${e} holds a ${i.length} byte secret, and a root secret is ${Z}`);let s=t.bootstrap;return{url:r("url"),vaultId:r("vaultId"),device:r("device"),secret:i,...typeof s=="string"&&s!==""?{bootstrap:s}:{}}}function _n(n){let e=n.trim().replace(/\/+$/,"");if(e==="")throw new Error("that is not a server address");if(e.startsWith("ws://")||e.startsWith("wss://"))return e;if(e.startsWith("http://"))return"ws://"+e.slice(7);if(e.startsWith("https://"))return"wss://"+e.slice(8);if(e.includes("://"))throw new Error(`a server address is ws:// or wss://, not ${e.split("://")[0]}://`);return"wss://"+e}import{constants as bt,watch as ri}from"node:fs";import{access as kt,cp as ii,mkdir as je,open as Ge,readFile as Un,readdir as si,rename as Dn,rm as oi,stat as ai,utimes as li}from"node:fs/promises";import{basename as St,dirname as qe,join as Tn,relative as hi,resolve as Rn,sep as In}from"node:path";var On=".trash",ci=new Set([".obsidian",".basalt",On,".git",".DS_Store","node_modules"]),Ze=class{root;ignore;constructor(e,t={}){this.root=Rn(e),this.ignore=new Set([...ci,...t.alsoIgnore??[]])}absolute(e){let t=Rn(this.root,e),r=hi(this.root,t);if(r===""||r.startsWith("..")||r.startsWith(`..${In}`))throw new Error(`refusing a path outside the vault: ${e}`);return t}async list(){let e=[],t=async(r,i)=>{let s;try{s=await si(r,{withFileTypes:!0})}catch(o){throw new Error(`cannot read ${r}: ${o.message}`)}for(let o of s){if(this.ignore.has(o.name)||zn(o.name))continue;let a=i?`${i}/${o.name}`:o.name,l=Tn(r,o.name);if(o.isDirectory())e.push({path:a,folder:!0,mtime:0,ctime:0,size:0}),await t(l,a);else if(o.isFile()){let c=await ai(l);e.push({path:a,folder:!1,mtime:c.mtimeMs,ctime:c.birthtimeMs||c.ctimeMs,size:c.size})}}};return await t(this.root,""),e}async read(e){return new Uint8Array(await Un(this.absolute(e)))}async*readBlocks(e,t=1024*1024){let r=await Ge(this.absolute(e),"r");try{let i=new Uint8Array(t);for(;;){let{bytesRead:s}=await r.read(i,0,t,null);if(s===0)return;yield i.slice(0,s)}}finally{await r.close()}}async readRange(e,t,r){let i=await Ge(this.absolute(e),"r");try{let s=new Uint8Array(r-t),o=0;for(;o<s.length;){let{bytesRead:a}=await i.read(s,o,s.length-o,t+o);if(a===0)break;o+=a}return o===s.length?s:s.subarray(0,o)}finally{await i.close()}}async write(e,t,r){let i=this.absolute(e);if(await je(qe(i),{recursive:!0}),await Nn(i,t),r.mtime>0){let s=r.mtime/1e3;await li(i,s,s)}}async remove(e){let t=this.absolute(e);try{await kt(t,bt.F_OK)}catch{return}let r=await this.freeTrashPath(e);await je(qe(r),{recursive:!0});try{await Dn(t,r);return}catch(i){if(i.code!=="EXDEV")throw i}await ii(t,r,{recursive:!0}),await oi(t,{recursive:!0,force:!0})}async freeTrashPath(e){let t=Tn(this.root,On,e),r=St(e).lastIndexOf("."),[i,s]=r<=0?[t,""]:[t.slice(0,t.length-(St(e).length-r)),t.slice(t.length-(St(e).length-r))];for(let o=0;o<1e3;o++){let a=o===0?t:`${i} (${o})${s}`;try{await kt(a,bt.F_OK)}catch{return a}}throw new Error(`the trash already holds a thousand copies of ${e}`)}async mkdir(e){await je(this.absolute(e),{recursive:!0})}async exists(e){try{return await kt(this.absolute(e),bt.F_OK),!0}catch{return!1}}watch(e){let t,r="",i;try{i=ri(this.root,{recursive:!0,persistent:!0},(s,o)=>{if(!o)return;let a=o.toString().split(In).join("/");a.split("/").some(l=>this.ignore.has(l))||zn(a)||(r=a,t&&clearTimeout(t),t=setTimeout(()=>{t=void 0,e(r)},150))}),i.on("error",()=>{})}catch{return()=>{}}return()=>{t&&clearTimeout(t),i?.close()}}},Te=class{constructor(e){this.file=e}file;async load(){let e;try{e=await Un(this.file,"utf8")}catch(t){if(t.code==="ENOENT")return;throw new Error(`cannot read the index at ${this.file}: ${t.message}`)}try{return JSON.parse(e)}catch(t){throw new Error(`the index at ${this.file} is not valid JSON, so it cannot be trusted: ${t.message}`)}}async save(e){await je(qe(this.file),{recursive:!0}),await Nn(this.file,new TextEncoder().encode(JSON.stringify(e)))}},Fn=".basalt-tmp-";function zn(n){return n.includes(Fn)}var ui=0;async function fi(n){for(let e=0;e<64;e++){let t=`${n}${Fn}${(ui++).toString(36)}${e?`-${e}`:""}`;try{return{tmp:t,handle:await Ge(t,"wx")}}catch(r){if(r.code!=="EEXIST")throw r}}throw new Error(`could not find an unused temporary name beside ${n}`)}async function Nn(n,e){let{tmp:t,handle:r}=await fi(n);try{await r.write(e),await r.sync()}finally{await r.close()}await Dn(t,n);let i=await Ge(qe(n),"r");try{await i.sync()}finally{await i.close()}}import{chmod as di,mkdir as gi,readFile as mi,rename as vi,rm as Bn,writeFile as pi}from"node:fs/promises";import{join as Et}from"node:path";var At=".basalt",xt=n=>Et(n,At,"config.json"),Xe=n=>Et(n,At,"index.json");async function Re(n){let e=xt(n),t;try{t=await mi(e,"utf8")}catch(i){if(i.code==="ENOENT")return;throw new Error(`cannot read ${e}: ${i.message}`)}let r;try{r=JSON.parse(t)}catch(i){throw new Error(`${e} is not valid JSON, so it cannot be trusted: ${i.message}`)}return Cn(r,e)}async function Ye(n,e){let t=Et(n,At);await gi(t,{recursive:!0});let r=xt(n),i=`${r}.tmp`;await pi(i,JSON.stringify(Mn(e),null,2)+`
9
+ `,{mode:384}),await di(i,384),await vi(i,r)}async function Ln(n){await Bn(xt(n),{force:!0}),await Bn(Xe(n),{force:!0})}var Wn=`basalt: self-hosted sync for Obsidian
10
+
11
+ basalt init --server URL --token TOKEN claim a new vault, with the server's first-run token
12
+ basalt pair PAIRING-STRING pair this vault with an existing one
13
+ basalt invite print the string another device needs
14
+ basalt sync sync once and exit
15
+ basalt sync --watch sync, then keep syncing
16
+ basalt status what this device thinks the state is
17
+ basalt deleted notes the server still has and you do not
18
+ basalt history PATH every version the server holds of one note
19
+ basalt restore PATH put a note back, newest version first
20
+ basalt unlink forget the pairing, keep the notes
21
+
22
+ Options
23
+ --dir DIR the vault (default: the current directory)
24
+ --device NAME what this device calls itself (default: its hostname)
25
+ --vault-id ID which vault on the server (default: default)
26
+ --json machine-readable output
27
+ --timeout MS how long to wait on the server (default: 30000)
28
+ --uid N restore one exact version, from basalt history
29
+ --to PATH restore somewhere other than where it came from
30
+ --limit N how many versions history shows (default: 20)
31
+ `;async function Jn(n,e){let t;try{t=_i(n)}catch(r){return e.err(String(r.message)),2}if(t.help||t.command===void 0)return e.out(Wn),t.command===void 0&&!t.help?2:0;try{switch(t.command){case"init":return await bi(t,e);case"pair":return await ki(t,e);case"invite":return await Si(t,e);case"sync":return await Ei(t,e);case"status":return await xi(t,e);case"deleted":return await Pi(t,e);case"history":return await $i(t,e);case"restore":return await Mi(t,e);case"unlink":return await Ci(t,e);default:return e.err(`no such command: ${t.command}`),e.err(Wn),2}}catch(r){let i=r instanceof Error?r.message:String(r);return t.json?e.out(JSON.stringify({ok:!1,error:i})):e.err(`basalt: ${i}`),1}}async function bi(n,e){if(!n.server||!n.token)throw new Error("init needs --server and --token, from the server's first run");if(await Re(n.dir))throw new Error(`${n.dir} is already paired. Use unlink first if that is really what you want.`);let t={url:_n(n.server),vaultId:n.vaultId,device:n.device,secret:Vt(),bootstrap:n.token};await Ye(n.dir,t);let r=wt(t);return n.json?e.out(JSON.stringify({ok:!0,paired:n.dir,device:t.device,pairing:r})):(e.out(`Paired ${n.dir} as "${t.device}".`),e.out(""),e.out("Give this to every other device. Anyone who has it has the vault:"),e.out(""),e.out(` ${r}`),e.out(""),e.out("It is the only copy. The server cannot reissue it, because the server"),e.out("has never seen the secret in it.")),0}async function ki(n,e){if(!n.rest[0])throw new Error("pair needs the string another device printed");if(await Re(n.dir))throw new Error(`${n.dir} is already paired. Use unlink first if that is really what you want.`);let r={...Pn(n.rest[0]),device:n.device};return await Ye(n.dir,r),n.json?e.out(JSON.stringify({ok:!0,paired:n.dir,device:r.device,url:r.url})):e.out(`Paired ${n.dir} with ${r.url} as "${r.device}". Run basalt sync.`),0}async function Si(n,e){let t=await pe(n.dir),r=wt(t);return n.json?e.out(JSON.stringify({ok:!0,pairing:r})):(e.out(r),e.err("Anyone who has that string has this vault.")),0}async function Ei(n,e){let t=await pe(n.dir);if(n.watch)return await Ai(t,n,e);let r=await Ie(t,n,e);try{let i=await r.settle();return Hn(i,n,e,r.serverCursor),i.skipped>0||i.retrying>0?1:0}finally{r.close()}}async function Ai(n,e,t){let r;return await An(await Kn(n,e,t),{onSynced:(i,s)=>{Hn(i,e,t,s),e.json||t.err("Watching for changes. Ctrl-C to stop.")},onDisconnected:(i,s)=>{t.err(`Disconnected: ${i.message}. Trying again in ${Vn(s)}.`)},onUnreachable:(i,s)=>{t.err(`Cannot reach the server: ${i.message}. Trying again in ${Vn(s)}.`)},onFatal:i=>{r=i}}),r?(t.err(`basalt: ${r.message}`),t.err("That will not fix itself by trying again."),1):0}async function xi(n,e){let t=await pe(n.dir),r=await new Te(Xe(n.dir)).load(),i={vault:n.dir,device:t.device,server:t.url,vaultId:t.vaultId,cursor:r?.cursor??0,tracked:r?Object.keys(r.entries).length:0,pending:r?.pending.length??0},s;try{let o=await Ie(t,n,e);s={reachable:!0,cursor:o.serverCursor,behind:Math.max(0,o.serverCursor-i.cursor)},o.close()}catch(o){s={reachable:!1,error:o.message}}return n.json?(e.out(JSON.stringify({ok:!0,...i,server:s})),s.reachable?0:1):(e.out(`vault ${i.vault}`),e.out(`device ${i.device}`),e.out(`server ${i.server} (vault "${i.vaultId}")`),e.out(`tracked ${i.tracked} files`),e.out(`cursor ${i.cursor}`),i.pending>0&&e.out(`pending ${i.pending} files with work outstanding`),s.reachable?(e.out(s.behind===0?"state up to date with the server":`state ${s.behind} changes behind`),0):(e.out(`state cannot reach the server: ${s.error}`),1))}async function Pi(n,e){let t=await pe(n.dir),r=await Ie(t,n,e);try{let i=await r.deleted(n.limit>20?n.limit:void 0);if(n.json)return e.out(JSON.stringify({ok:!0,deleted:i.notes,more:i.more})),0;if(i.notes.length===0)return e.out("Nothing has been deleted from this vault."),0;let s=0;for(let a of i.notes){let l=a.restorable===0?" (content purged)":"";a.restorable===0&&s++,e.out(`${Pt(a.mtime)} ${a.device.padEnd(12)} ${a.path}${l}`)}e.out("");let o=i.notes.length-s;return s===0?e.out(`${o} deleted, all still recoverable. basalt restore PATH brings one back.`):e.out(`${i.notes.length} deleted. ${o} can be restored; ${s} had their history purged and cannot be.`),i.more&&e.out("There are older deletions than these. --limit N shows more."),0}finally{r.close()}}async function $i(n,e){let t=n.rest[0];if(!t)throw new Error("history needs the path of a note");let r=await pe(n.dir),i=await Ie(r,n,e);try{let s=await i.history(t,{limit:n.limit});if(n.json)return e.out(JSON.stringify({ok:!0,path:t,versions:s})),0;if(s.length===0)return e.out(`The server holds no versions of ${t}.`),0;for(let o of s){let a=o.deleted?"deleted":o.folder?"folder":`${$t(o.size)}`;e.out(`${String(o.uid).padStart(7)} ${Pt(o.mtime)} ${o.device.padEnd(12)} ${a}`)}return e.out(""),e.out("basalt restore PATH --uid N brings one of these back."),0}finally{i.close()}}async function Mi(n,e){let t=n.rest[0];if(!t)throw new Error("restore needs the path of a note");let r=await pe(n.dir),i=await Ie(r,n,e);try{let s;if(n.uid!==void 0){if(s=(await i.history(t,{limit:500})).find(c=>c.uid===n.uid),!s)throw new Error(`the server has no version ${n.uid} of ${t}`)}else if(s=await i.newestContentVersion(t),!s)throw new Error(`the server holds no version of ${t} with any content in it`);let o=await i.restore(s,n.to),a=await i.settle({coalesceWrites:!1});return n.json?(e.out(JSON.stringify({ok:!0,path:o.path,uid:s.uid,bytes:o.bytes,sync:a})),0):(e.out(`Restored version ${s.uid} of ${t} (${$t(o.bytes)}, from ${Pt(s.mtime)}).`),o.path!==(n.to??t)&&e.out(`Written to ${o.path}, because something is already at ${n.to??t}.`),a.uploaded>0&&e.out("Sent to the server, so your other devices will pick it up."),0)}finally{i.close()}}async function Ci(n,e){let t=await Re(n.dir).catch(()=>{});return await Ln(n.dir),n.json?e.out(JSON.stringify({ok:!0,unlinked:n.dir,wasPaired:t!==void 0})):(e.out(`Forgot the pairing for ${n.dir}. Every note is where it was.`),e.out("Nothing was removed from the server.")),0}async function Ie(n,e,t){let r=new Ce(await Kn(n,e,t));if(await r.connect(),n.bootstrap){let{bootstrap:i,...s}=n;await Ye(e.dir,s)}return r}async function Kn(n,e,t){let r=await Jt(n.secret),i=Zt(r);return{vault:new Ze(e.dir),store:new Te(Xe(e.dir)),keys:r,url:n.url,token:n.bootstrap??i,claim:i,vaultId:n.vaultId,device:n.device,timeoutMs:e.timeout,coalesceWrites:e.watch,...e.watch&&t?{onProgress:s=>{s!==void 0&&t.err(` ... ${s}`)}}:{},...e.verbose&&t?{log:(s,...o)=>t.err(` ${s} ${o.map(Ti).join(" ")}`.trimEnd())}:{}}}function Hn(n,e,t,r){if(e.json){t.out(JSON.stringify({ok:!0,...n,serverCursor:r}));return}let i=[],s=(o,a)=>{o>0&&i.push(`${String(o).padStart(5)} ${a}`)};if(s(n.uploaded,"uploaded"),s(n.downloaded,"downloaded"),s(n.merged,"merged"),s(n.conflicted,"kept both versions"),s(n.deletedLocally,"deleted here"),s(n.deletedRemotely,"deleted on the server"),s(n.restored,"brought back, having been edited elsewhere"),s(n.foldersCreated,"folders created"),s(n.waiting,"waiting for a write to settle"),s(n.retrying,"failed, will try again"),s(n.skipped,"cannot sync and will not be retried"),s(n.blocked,"waiting on a name that is a file here and a folder elsewhere"),i.length===0)t.out("Nothing to do. Everything here matches the server.");else for(let o of i)t.out(o);n.chunksSent>0&&t.out(`${String(n.chunksSent).padStart(5)} chunks sent, ${$t(n.bytesSent)}`),n.conflicted>0&&t.err('Look for files with "Conflicted copy" in the name. Both versions are kept.')}function _i(n){let e={rest:[],dir:process.cwd(),device:yi().split(".")[0]||"device",vaultId:"default",json:!1,watch:!1,limit:20,verbose:!1,help:!1,timeout:3e4},t=new Set(["--dir","--device","--vault-id","--server","--token","--timeout","--uid","--to","--limit"]);for(let r=0;r<n.length;r++){let i=n[r],s;if(t.has(i)&&(s=n[++r],s===void 0||s.startsWith("--")))throw new Error(`${i} needs a value`);switch(i){case"--dir":e.dir=wi(s);break;case"--device":e.device=s;break;case"--vault-id":e.vaultId=s;break;case"--server":e.server=s;break;case"--token":e.token=s;break;case"--timeout":{let o=Number(s);if(!Number.isFinite(o)||o<=0)throw new Error(`--timeout wants a number of milliseconds, not ${s}`);e.timeout=o;break}case"--uid":{let o=Number(s);if(!Number.isInteger(o)||o<=0)throw new Error(`--uid wants a version number, not ${s}`);e.uid=o;break}case"--to":e.to=s;break;case"--limit":{let o=Number(s);if(!Number.isInteger(o)||o<=0)throw new Error(`--limit wants a count, not ${s}`);e.limit=o;break}case"--json":e.json=!0;break;case"--watch":e.watch=!0;break;case"--verbose":case"-v":e.verbose=!0;break;case"--help":case"-h":e.help=!0;break;default:if(i.startsWith("-"))throw new Error(`no such option: ${i}`);e.command===void 0?e.command=i:e.rest.push(i)}}return e}async function pe(n){let e=await Re(n);if(!e)throw new Error(`${n} is not paired. Run basalt init or basalt pair first.`);return e}function Pt(n){if(!Number.isFinite(n)||n<=0)return"unknown ";let e=new Date(n),t=r=>String(r).padStart(2,"0");return`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())} ${t(e.getHours())}:${t(e.getMinutes())}`}function Vn(n){return`${Math.max(1,Math.round(n/1e3))}s`}function $t(n){return n<1024?`${n} B`:n<1024*1024?`${(n/1024).toFixed(1)} KiB`:`${(n/(1024*1024)).toFixed(1)} MiB`}function Ti(n){return typeof n=="string"?n:JSON.stringify(n)}var Ri=await Jn(process.argv.slice(2),{out:n=>process.stdout.write(n+`
32
+ `),err:n=>process.stderr.write(n+`
33
+ `)});process.exit(Ri);
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "basalt-sync",
3
+ "version": "0.1.1",
4
+ "description": "Headless Obsidian vault sync for a Basalt server.",
5
+ "keywords": [
6
+ "obsidian",
7
+ "sync",
8
+ "self-hosted",
9
+ "end-to-end-encryption",
10
+ "notes"
11
+ ],
12
+ "homepage": "https://github.com/waynehoover/basalt-sync",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/waynehoover/basalt-sync.git",
16
+ "directory": "client"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/waynehoover/basalt-sync/issues"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Wayne Hoover",
23
+ "engines": {
24
+ "node": ">=22"
25
+ },
26
+ "files": [
27
+ "dist/basalt.mjs",
28
+ "README.md"
29
+ ],
30
+ "type": "module",
31
+ "scripts": {
32
+ "build": "node esbuild.config.mjs production",
33
+ "dev": "node esbuild.config.mjs",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "typecheck": "tsc --noEmit",
37
+ "bench": "bun run bench.ts",
38
+ "build:cli": "node esbuild.config.mjs production",
39
+ "bench:sync": "bun run bench-sync.ts"
40
+ },
41
+ "devDependencies": {
42
+ "@types/diff-match-patch": "^1.0.36",
43
+ "@types/node": "^24.10.13",
44
+ "diff-match-patch": "^1.0.5",
45
+ "esbuild": "^0.28.1",
46
+ "fflate": "^0.8.3",
47
+ "obsidian": "^1.13.1",
48
+ "typescript": "^5.9.3",
49
+ "vitest": "^4.1.8"
50
+ },
51
+ "bin": {
52
+ "basalt": "./dist/basalt.mjs"
53
+ }
54
+ }