dockito 0.0.1-beta.1 → 0.0.1-beta.11

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 +8 -3
  2. package/dockito.js +218 -46
  3. package/package.json +13 -3
package/README.md CHANGED
@@ -4,17 +4,22 @@ Publish the docs your AI writes as links you can share, from the terminal.
4
4
 
5
5
  ```sh
6
6
  npx dockito@beta login
7
+ npx dockito@beta sync docs --to my-project --create # a folder of docs, as a project
7
8
  npx dockito@beta publish plan.md -m "Sent to the client"
8
9
  ```
9
10
 
10
11
  | Command | Does |
11
12
  |---|---|
12
13
  | `login` | Sign in through the browser and choose what this login may do in each workspace |
13
- | `publish <file...>` | Publish Markdown or HTML; the same file updates the same doc |
14
+ | `sync <folder> --to <project>` | Mirror a folder into a project: subfolders become folders, only changed files are sent, a plan first |
15
+ | `publish <file...>` | Publish a file; one with the same name in that place updates its doc |
16
+ | `share <doc>` | Who can open it. Docs are private; `publish --public` makes a new doc public, `share --private` takes it back |
17
+ | `tree`, `mkdir` | Every workspace, project and folder you can use; make new ones |
14
18
  | `ls`, `open`, `get` | List, open and download docs |
15
19
  | `history`, `restore`, `save` | A doc's History |
16
- | `whoami`, `switch`, `logout` | Your login and workspaces |
20
+ | `read`, `apply`, `check`, `changes` | Live sheets: read rows, change cells at the version you read, find broken formulas, see what changed. `publish data.csv` makes one |
21
+ | `whoami`, `logout` | Your login and workspaces |
17
22
 
18
- Run `dockito --help` for everything. `--json` gives machine-readable output for agents, and `DOCKITO_TOKEN` uses a token from Account → Tokens instead of a login.
23
+ The CLI keeps nothing but your login: where docs go is passed with `--to` each time. Run `dockito --help` for everything, `dockito <command> --help` for one command, and `dockito agent` for what an AI agent needs to know. `--json` gives machine-readable output for agents, and `DOCKITO_TOKEN` uses a token from Account → Tokens instead of a login.
19
24
 
20
25
  This is a beta: commands may still change.
package/dockito.js CHANGED
@@ -1,50 +1,222 @@
1
1
  #!/usr/bin/env node
2
- // @bun
3
- import{spawn as Un,spawnSync as cn}from"child_process";import{existsSync as je,readFileSync as Fn,statSync as Pn,writeFileSync as Vn}from"fs";import{hostname as Mn}from"os";import{basename as Le,dirname as Jn,resolve as Z}from"path";import{parseArgs as Wn}from"util";class x extends Error{code;status;detail;constructor(e,r,t={}){super(e);this.code=e;this.status=r;this.detail=t}}function te(e){let r=e.url.replace(/\/+$/,"");async function t(o,c,s={}){let l=new URL(`${r}/api${c}`);for(let[b,k]of Object.entries(s.query??{}))if(k!==void 0&&k!==null&&k!=="")l.searchParams.set(b,String(k));let u={},a=s.token===void 0?e.token:s.token;if(a)u.authorization=`Bearer ${a}`;if(e.userAgent)u["user-agent"]=e.userAgent;let p;if(s.json!==void 0)u["content-type"]="application/json",p=JSON.stringify(s.json);else if(s.body)u["content-type"]="application/octet-stream",p=s.body;let h;try{h=await fetch(l,{method:o,headers:u,body:p})}catch{throw new x("offline",0)}return h}async function n(o){let c=await o.json().catch(()=>({}));if(!o.ok){let{error:s,...l}=c;throw new x(typeof s==="string"?s:`http_${o.status}`,o.status,l)}return c}let i=encodeURIComponent;return{url:r,linkOf:(o)=>`${r}/${o}`,startLogin:async(o,c)=>n(await t("POST","/device/code",{json:{client:o,device:c}})),pollLogin:async(o)=>{let c=await t("POST","/device/token",{json:{device_code:o},token:null}),s=await c.json().catch(()=>({}));if(c.ok)return s;if(s.error==="authorization_pending"||s.error==="slow_down")return null;throw new x(s.error??`http_${c.status}`,c.status)},logout:async()=>n(await t("DELETE","/tokens/current")),whoami:async()=>n(await t("GET","/whoami")),projects:async(o)=>(await n(await t("GET",`/workspaces/${i(o)}/projects`))).projects,docs:async(o,c={})=>(await n(await t("GET",`/workspaces/${i(o)}/docs`,{query:c}))).docs,recent:async(o)=>(await n(await t("GET",`/workspaces/${i(o)}/docs/recent`))).docs,doc:async(o)=>n(await t("GET",`/docs/by-code/${i(o)}`)),content:async(o,c)=>{let s=await t("GET",`/docs/${i(o)}/content`,{query:{v:c}});if(!s.ok)await n(s);return{bytes:new Uint8Array(await s.arrayBuffer()),entry:Number(s.headers.get("x-doc-version"))}},create:async(o,c,s,l={})=>n(await t("POST",`/workspaces/${i(o)}/docs`,{query:{name:c,...l,source:e.source},body:s})),update:async(o,c,s,l={})=>n(await t("POST",`/docs/${i(o)}/versions`,{query:{name:c,note:l.note,commit:l.commit,base:l.base,source:e.source},body:s})),restore:async(o,c,s)=>n(await t("POST",`/docs/${i(o)}/history/${c}/restore`,{query:{base:s}})),save:async(o,c,s)=>n(await t("PUT",`/docs/${i(o)}/history/${c}/saved`,{json:{note:s}})),rename:async(o,c)=>n(await t("PATCH",`/docs/${i(o)}`,{json:{title:c}}))}}import{existsSync as dt,mkdirSync as fn,readFileSync as pn,writeFileSync as dn}from"node:fs";import{homedir as gn}from"node:os";import{join as re}from"node:path";var mn="https://dockito-app.dockito.workers.dev";function De(){if(process.env.DOCKITO_CONFIG_DIR)return process.env.DOCKITO_CONFIG_DIR;if(process.platform==="win32"&&process.env.APPDATA)return re(process.env.APPDATA,"dockito");return re(process.env.XDG_CONFIG_HOME||re(gn(),".config"),"dockito")}var Re=()=>re(De(),"state.json");function Be(){try{return JSON.parse(pn(Re(),"utf8"))}catch{return{}}}function U(e){return Be().sites?.[e]??{}}function oe(e,r){let t=Be();t.sites??={};let n=t.sites[e]??={};r(n),fn(De(),{recursive:!0,mode:448}),dn(Re(),JSON.stringify(t,null,2)+`
4
- `)}var Ue=()=>(process.env.DOCKITO_URL||mn).replace(/\/+$/,""),Fe=(e)=>new URL(e).host;import{spawnSync as kn}from"node:child_process";import{chmodSync as wn,existsSync as ke,mkdirSync as yn,readFileSync as bn,rmSync as Ve,writeFileSync as Tn}from"node:fs";import{dirname as vn,join as On}from"node:path";import{homedir as hn}from"node:os";import{join as he}from"node:path";function Pe(){if(process.env.DOCKITO_CONFIG_DIR)return process.env.DOCKITO_CONFIG_DIR;if(process.platform==="win32"&&process.env.APPDATA)return he(process.env.APPDATA,"dockito");return he(process.env.XDG_CONFIG_HOME||he(hn(),".config"),"dockito")}var V="dockito";function M(e,r,t){try{let n=kn(e,r,{input:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return n.error?null:n}catch{return null}}var z=(e)=>On(Pe(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function $n(e,r){if(process.platform==="darwin"){let t=M("security",["-i"],`add-generic-password -U -s ${V} -a ${e} -l Dockito -w ${r}
5
- `);return!!t&&t.status===0&&!/error/i.test(t.stderr??"")}if(process.platform==="linux"){let t=M("secret-tool",["store","--label=Dockito","service",V,"account",e],r);return!!t&&t.status===0}return!1}function An(e){let r=process.platform==="darwin"?M("security",["find-generic-password","-s",V,"-a",e,"-w"]):process.platform==="linux"?M("secret-tool",["lookup","service",V,"account",e]):null;return(r?.status===0?r.stdout.trim():"")||null}function jn(e){if(process.platform==="darwin")M("security",["delete-generic-password","-s",V,"-a",e]);else if(process.platform==="linux")M("secret-tool",["clear","service",V,"account",e])}function Me(e,r){if($n(e,r)){if(ke(z(e)))Ve(z(e));return"keychain"}let t=z(e);return yn(vn(t),{recursive:!0,mode:448}),Tn(t,r,{mode:384}),wn(t,384),"file"}function ie(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let r=An(e);if(r)return r;let t=z(e);return ke(t)?bn(t,"utf8").trim()||null:null}function Je(e){jn(e);let r=z(e);if(ke(r))Ve(r)}import{existsSync as Dn,readFileSync as en,writeFileSync as nn}from"node:fs";import{dirname as Ce,join as tn,relative as Rn,resolve as rn,sep as Bn}from"node:path";function q(e,r=!1){let t=e.length,n=0,i="",o=0,c=16,s=0,l=0,u=0,a=0,p=0;function h(d,v){let m=0,g=0;while(m<d||!v){let f=e.charCodeAt(n);if(f>=48&&f<=57)g=g*16+f-48;else if(f>=65&&f<=70)g=g*16+f-65+10;else if(f>=97&&f<=102)g=g*16+f-97+10;else break;n++,m++}if(m<d)g=-1;return g}function b(d){n=d,i="",o=0,c=16,p=0}function k(){let d=n;if(e.charCodeAt(n)===48)n++;else{n++;while(n<e.length&&J(e.charCodeAt(n)))n++}if(n<e.length&&e.charCodeAt(n)===46)if(n++,n<e.length&&J(e.charCodeAt(n))){n++;while(n<e.length&&J(e.charCodeAt(n)))n++}else return p=3,e.substring(d,n);let v=n;if(n<e.length&&(e.charCodeAt(n)===69||e.charCodeAt(n)===101)){if(n++,n<e.length&&e.charCodeAt(n)===43||e.charCodeAt(n)===45)n++;if(n<e.length&&J(e.charCodeAt(n))){n++;while(n<e.length&&J(e.charCodeAt(n)))n++;v=n}else p=3}return e.substring(d,v)}function S(){let d="",v=n;while(!0){if(n>=t){d+=e.substring(v,n),p=2;break}let m=e.charCodeAt(n);if(m===34){d+=e.substring(v,n),n++;break}if(m===92){if(d+=e.substring(v,n),n++,n>=t){p=2;break}switch(e.charCodeAt(n++)){case 34:d+='"';break;case 92:d+="\\";break;case 47:d+="/";break;case 98:d+="\b";break;case 102:d+="\f";break;case 110:d+=`
6
- `;break;case 114:d+="\r";break;case 116:d+="\t";break;case 117:let f=h(4,!0);if(f>=0)d+=String.fromCharCode(f);else p=4;break;default:p=5}v=n;continue}if(m>=0&&m<=31)if(H(m)){d+=e.substring(v,n),p=2;break}else p=6;n++}return d}function A(){if(i="",p=0,o=n,l=s,a=u,n>=t)return o=t,c=17;let d=e.charCodeAt(n);if(we(d)){do n++,i+=String.fromCharCode(d),d=e.charCodeAt(n);while(we(d));return c=15}if(H(d)){if(n++,i+=String.fromCharCode(d),d===13&&e.charCodeAt(n)===10)n++,i+=`
7
- `;return s++,u=n,c=14}switch(d){case 123:return n++,c=1;case 125:return n++,c=2;case 91:return n++,c=3;case 93:return n++,c=4;case 58:return n++,c=6;case 44:return n++,c=5;case 34:return n++,i=S(),c=10;case 47:let v=n-1;if(e.charCodeAt(n+1)===47){n+=2;while(n<t){if(H(e.charCodeAt(n)))break;n++}return i=e.substring(v,n),c=12}if(e.charCodeAt(n+1)===42){n+=2;let m=t-1,g=!1;while(n<m){let f=e.charCodeAt(n);if(f===42&&e.charCodeAt(n+1)===47){n+=2,g=!0;break}if(n++,H(f)){if(f===13&&e.charCodeAt(n)===10)n++;s++,u=n}}if(!g)n++,p=1;return i=e.substring(v,n),c=13}return i+=String.fromCharCode(d),n++,c=16;case 45:if(i+=String.fromCharCode(d),n++,n===t||!J(e.charCodeAt(n)))return c=16;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return i+=k(),c=11;default:while(n<t&&N(d))n++,d=e.charCodeAt(n);if(o!==n){switch(i=e.substring(o,n),i){case"true":return c=8;case"false":return c=9;case"null":return c=7}return c=16}return i+=String.fromCharCode(d),n++,c=16}}function N(d){if(we(d)||H(d))return!1;switch(d){case 125:case 93:case 123:case 91:case 34:case 58:case 44:case 47:return!1}return!0}function _(){let d;do d=A();while(d>=12&&d<=15);return d}return{setPosition:b,getPosition:()=>n,scan:r?_:A,getToken:()=>c,getTokenValue:()=>i,getTokenOffset:()=>o,getTokenLength:()=>n-o,getTokenStartLine:()=>l,getTokenStartCharacter:()=>o-a,getTokenError:()=>p}}function we(e){return e===32||e===9}function H(e){return e===10||e===13}function J(e){return e>=48&&e<=57}var We;(function(e){e[e.lineFeed=10]="lineFeed",e[e.carriageReturn=13]="carriageReturn",e[e.space=32]="space",e[e._0=48]="_0",e[e._1=49]="_1",e[e._2=50]="_2",e[e._3=51]="_3",e[e._4=52]="_4",e[e._5=53]="_5",e[e._6=54]="_6",e[e._7=55]="_7",e[e._8=56]="_8",e[e._9=57]="_9",e[e.a=97]="a",e[e.b=98]="b",e[e.c=99]="c",e[e.d=100]="d",e[e.e=101]="e",e[e.f=102]="f",e[e.g=103]="g",e[e.h=104]="h",e[e.i=105]="i",e[e.j=106]="j",e[e.k=107]="k",e[e.l=108]="l",e[e.m=109]="m",e[e.n=110]="n",e[e.o=111]="o",e[e.p=112]="p",e[e.q=113]="q",e[e.r=114]="r",e[e.s=115]="s",e[e.t=116]="t",e[e.u=117]="u",e[e.v=118]="v",e[e.w=119]="w",e[e.x=120]="x",e[e.y=121]="y",e[e.z=122]="z",e[e.A=65]="A",e[e.B=66]="B",e[e.C=67]="C",e[e.D=68]="D",e[e.E=69]="E",e[e.F=70]="F",e[e.G=71]="G",e[e.H=72]="H",e[e.I=73]="I",e[e.J=74]="J",e[e.K=75]="K",e[e.L=76]="L",e[e.M=77]="M",e[e.N=78]="N",e[e.O=79]="O",e[e.P=80]="P",e[e.Q=81]="Q",e[e.R=82]="R",e[e.S=83]="S",e[e.T=84]="T",e[e.U=85]="U",e[e.V=86]="V",e[e.W=87]="W",e[e.X=88]="X",e[e.Y=89]="Y",e[e.Z=90]="Z",e[e.asterisk=42]="asterisk",e[e.backslash=92]="backslash",e[e.closeBrace=125]="closeBrace",e[e.closeBracket=93]="closeBracket",e[e.colon=58]="colon",e[e.comma=44]="comma",e[e.dot=46]="dot",e[e.doubleQuote=34]="doubleQuote",e[e.minus=45]="minus",e[e.openBrace=123]="openBrace",e[e.openBracket=91]="openBracket",e[e.plus=43]="plus",e[e.slash=47]="slash",e[e.formFeed=12]="formFeed",e[e.tab=9]="tab"})(We||(We={}));var E=Array(20).fill(0).map((e,r)=>" ".repeat(r));var ye={" ":{"\n":Array(200).fill(0).map((e,r)=>`
8
- `+" ".repeat(r)),"\r":Array(200).fill(0).map((e,r)=>"\r"+" ".repeat(r)),"\r\n":Array(200).fill(0).map((e,r)=>`\r
9
- `+" ".repeat(r))},"\t":{"\n":Array(200).fill(0).map((e,r)=>`
10
- `+"\t".repeat(r)),"\r":Array(200).fill(0).map((e,r)=>"\r"+"\t".repeat(r)),"\r\n":Array(200).fill(0).map((e,r)=>`\r
11
- `+"\t".repeat(r))}},Ge=[`
12
- `,"\r",`\r
13
- `];function be(e,r,t){let n,i,o,c,s;if(r){c=r.offset,s=c+r.length,o=c;while(o>0&&!Y(e,o-1))o--;let m=s;while(m<e.length&&!Y(e,m))m++;i=e.substring(o,m),n=Nn(i,t)}else i=e,n=0,o=0,c=0,s=e.length;let l=Sn(t,e),u=Ge.includes(l),a=0,p=0,h;if(t.insertSpaces)h=E[t.tabSize||4]??W(E[1],t.tabSize||4);else h="\t";let b=h==="\t"?"\t":" ",k=q(i,!1),S=!1;function A(){if(a>1)return W(l,a)+W(h,n+p);let m=h.length*(n+p);if(!u||m>ye[b][l].length)return l+W(h,n+p);if(m<=0)return l;return ye[b][l][m]}function N(){let m=k.scan();a=0;while(m===15||m===14){if(m===14&&t.keepLines)a+=1;else if(m===14)a=1;m=k.scan()}return S=m===16||k.getTokenError()!==0,m}let _=[];function d(m,g,f){if(!S&&(!r||g<s&&f>c)&&e.substring(g,f)!==m)_.push({offset:g,length:f-g,content:m})}let v=N();if(t.keepLines&&a>0)d(W(l,a),0,0);if(v!==17){let m=k.getTokenOffset()+o,g=h.length*n<20&&t.insertSpaces?E[h.length*n]:W(h,n);d(g,o,m)}while(v!==17){let m=k.getTokenOffset()+k.getTokenLength()+o,g=N(),f="",R=!1;while(a===0&&(g===12||g===13)){let ge=k.getTokenOffset()+o;d(E[1],m,ge),m=k.getTokenOffset()+k.getTokenLength()+o,R=g===12,f=R?A():"",g=N()}if(g===2){if(v!==1)p--;if(t.keepLines&&a>0||!t.keepLines&&v!==1)f=A();else if(t.keepLines)f=E[1]}else if(g===4){if(v!==3)p--;if(t.keepLines&&a>0||!t.keepLines&&v!==3)f=A();else if(t.keepLines)f=E[1]}else{switch(v){case 3:case 1:if(p++,t.keepLines&&a>0||!t.keepLines)f=A();else f=E[1];break;case 5:if(t.keepLines&&a>0||!t.keepLines)f=A();else f=E[1];break;case 12:f=A();break;case 13:if(a>0)f=A();else if(!R)f=E[1];break;case 6:if(t.keepLines&&a>0)f=A();else if(!R)f=E[1];break;case 10:if(t.keepLines&&a>0)f=A();else if(g===6&&!R)f="";break;case 7:case 8:case 9:case 11:case 2:case 4:if(t.keepLines&&a>0)f=A();else if((g===12||g===13)&&!R)f=E[1];else if(g!==5&&g!==17)S=!0;break;case 16:S=!0;break}if(a>0&&(g===12||g===13))f=A()}if(g===17)if(t.keepLines&&a>0)f=A();else f=t.insertFinalNewline?l:"";let de=k.getTokenOffset()+o;d(f,m,de),v=g}return _}function W(e,r){let t="";for(let n=0;n<r;n++)t+=e;return t}function Nn(e,r){let t=0,n=0,i=r.tabSize||4;while(t<e.length){let o=e.charAt(t);if(o===E[1])n++;else if(o==="\t")n+=i;else break;t++}return Math.floor(n/i)}function Sn(e,r){for(let t=0;t<r.length;t++){let n=r.charAt(t);if(n==="\r"){if(t+1<r.length&&r.charAt(t+1)===`
14
- `)return`\r
15
- `;return"\r"}else if(n===`
16
- `)return`
17
- `}return e&&e.eol||`
18
- `}function Y(e,r){return`\r
19
- `.indexOf(e.charAt(r))!==-1}var Q;(function(e){e.DEFAULT={allowTrailingComma:!1}})(Q||(Q={}));function ze(e,r=[],t=Q.DEFAULT){let n=null,i=[],o=[];function c(l){if(Array.isArray(i))i.push(l);else if(n!==null)i[n]=l}return ve(e,{onObjectBegin:()=>{let l={};c(l),o.push(i),i=l,n=null},onObjectProperty:(l)=>{n=l},onObjectEnd:()=>{i=o.pop()},onArrayBegin:()=>{let l=[];c(l),o.push(i),i=l,n=null},onArrayEnd:()=>{i=o.pop()},onLiteralValue:c,onError:(l,u,a)=>{r.push({error:l,offset:u,length:a})}},t),i[0]}function Te(e,r=[],t=Q.DEFAULT){let n={type:"array",offset:-1,length:-1,children:[],parent:void 0};function i(l){if(n.type==="property")n.length=l-n.offset,n=n.parent}function o(l){return n.children.push(l),l}ve(e,{onObjectBegin:(l)=>{n=o({type:"object",offset:l,length:-1,parent:n,children:[]})},onObjectProperty:(l,u,a)=>{n=o({type:"property",offset:u,length:-1,parent:n,children:[]}),n.children.push({type:"string",value:l,offset:u,length:a,parent:n})},onObjectEnd:(l,u)=>{i(l+u),n.length=l+u-n.offset,n=n.parent,i(l+u)},onArrayBegin:(l,u)=>{n=o({type:"array",offset:l,length:-1,parent:n,children:[]})},onArrayEnd:(l,u)=>{n.length=l+u-n.offset,n=n.parent,i(l+u)},onLiteralValue:(l,u,a)=>{o({type:_n(l),offset:u,length:a,parent:n,value:l}),i(u+a)},onSeparator:(l,u,a)=>{if(n.type==="property"){if(l===":")n.colonOffset=u;else if(l===",")i(u)}},onError:(l,u,a)=>{r.push({error:l,offset:u,length:a})}},t);let s=n.children[0];if(s)delete s.parent;return s}function se(e,r){if(!e)return;let t=e;for(let n of r)if(typeof n==="string"){if(t.type!=="object"||!Array.isArray(t.children))return;let i=!1;for(let o of t.children)if(Array.isArray(o.children)&&o.children[0].value===n&&o.children.length===2){t=o.children[1],i=!0;break}if(!i)return}else{let i=n;if(t.type!=="array"||i<0||!Array.isArray(t.children)||i>=t.children.length)return;t=t.children[i]}return t}function ve(e,r,t=Q.DEFAULT){let n=q(e,!1),i=[],o=0;function c(w){return w?()=>o===0&&w(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function s(w){return w?(j)=>o===0&&w(j,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function l(w){return w?(j)=>o===0&&w(j,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>i.slice()):()=>!0}function u(w){return w?()=>{if(o>0)o++;else if(w(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>i.slice())===!1)o=1}:()=>!0}function a(w){return w?()=>{if(o>0)o--;if(o===0)w(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:()=>!0}let p=u(r.onObjectBegin),h=l(r.onObjectProperty),b=a(r.onObjectEnd),k=u(r.onArrayBegin),S=a(r.onArrayEnd),A=l(r.onLiteralValue),N=s(r.onSeparator),_=c(r.onComment),d=s(r.onError),v=t&&t.disallowComments,m=t&&t.allowTrailingComma;function g(){while(!0){let w=n.scan();switch(n.getTokenError()){case 4:f(14);break;case 5:f(15);break;case 3:f(13);break;case 1:if(!v)f(11);break;case 2:f(12);break;case 6:f(16);break}switch(w){case 12:case 13:if(v)f(10);else _();break;case 16:f(1);break;case 15:case 14:break;default:return w}}}function f(w,j=[],xe=[]){if(d(w),j.length+xe.length>0){let ne=n.getToken();while(ne!==17){if(j.indexOf(ne)!==-1){g();break}else if(xe.indexOf(ne)!==-1)break;ne=g()}}}function R(w){let j=n.getTokenValue();if(w)A(j);else h(j),i.push(j);return g(),!0}function de(){switch(n.getToken()){case 11:let w=n.getTokenValue(),j=Number(w);if(isNaN(j))f(2),j=0;A(j);break;case 7:A(null);break;case 8:A(!0);break;case 9:A(!1);break;default:return!1}return g(),!0}function ge(){if(n.getToken()!==10)return f(3,[],[2,5]),!1;if(R(!1),n.getToken()===6){if(N(":"),g(),!me())f(4,[],[2,5])}else f(5,[],[2,5]);return i.pop(),!0}function an(){p(),g();let w=!1;while(n.getToken()!==2&&n.getToken()!==17){if(n.getToken()===5){if(!w)f(4,[],[]);if(N(","),g(),n.getToken()===2&&m)break}else if(w)f(6,[],[]);if(!ge())f(4,[],[2,5]);w=!0}if(b(),n.getToken()!==2)f(7,[2],[]);else g();return!0}function un(){k(),g();let w=!0,j=!1;while(n.getToken()!==4&&n.getToken()!==17){if(n.getToken()===5){if(!j)f(4,[],[]);if(N(","),g(),n.getToken()===4&&m)break}else if(j)f(6,[],[]);if(w)i.push(0),w=!1;else i[i.length-1]++;if(!me())f(4,[],[4,5]);j=!0}if(S(),!w)i.pop();if(n.getToken()!==4)f(8,[4],[]);else g();return!0}function me(){switch(n.getToken()){case 3:return un();case 1:return an();case 10:return R(!0);default:return de()}}if(g(),n.getToken()===17){if(t.allowEmptyContent)return!0;return f(4,[],[]),!1}if(!me())return f(4,[],[]),!1;if(n.getToken()!==17)f(9,[],[]);return!0}function _n(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string";case"object":{if(!e)return"null";else if(Array.isArray(e))return"array";return"object"}default:return"null"}}function He(e,r,t,n){let i=r.slice(),c=Te(e,[]),s=void 0,l=void 0;while(i.length>0)if(l=i.pop(),s=se(c,i),s===void 0&&t!==void 0)if(typeof l==="string")t={[l]:t};else t=[t];else break;if(!s){if(t===void 0)throw Error("Can not delete in empty document");return P(e,{offset:c?c.offset:0,length:c?c.length:0,content:JSON.stringify(t)},n)}else if(s.type==="object"&&typeof l==="string"&&Array.isArray(s.children)){let u=se(s,[l]);if(u!==void 0)if(t===void 0){if(!u.parent)throw Error("Malformed AST");let a=s.children.indexOf(u.parent),p,h=u.parent.offset+u.parent.length;if(a>0){let b=s.children[a-1];p=b.offset+b.length}else if(p=s.offset+1,s.children.length>1)h=s.children[1].offset;return P(e,{offset:p,length:h-p,content:""},n)}else return P(e,{offset:u.offset,length:u.length,content:JSON.stringify(t)},n);else{if(t===void 0)return[];let a=`${JSON.stringify(l)}: ${JSON.stringify(t)}`,p=n.getInsertionIndex?n.getInsertionIndex(s.children.map((b)=>b.children[0].value)):s.children.length,h;if(p>0){let b=s.children[p-1];h={offset:b.offset+b.length,length:0,content:","+a}}else if(s.children.length===0)h={offset:s.offset+1,length:0,content:a};else h={offset:s.offset+1,length:0,content:a+","};return P(e,h,n)}}else if(s.type==="array"&&typeof l==="number"&&Array.isArray(s.children)){let u=l;if(u===-1){let a=`${JSON.stringify(t)}`,p;if(s.children.length===0)p={offset:s.offset+1,length:0,content:a};else{let h=s.children[s.children.length-1];p={offset:h.offset+h.length,length:0,content:","+a}}return P(e,p,n)}else if(t===void 0&&s.children.length>=0){let a=l,p=s.children[a],h;if(s.children.length===1)h={offset:s.offset+1,length:s.length-2,content:""};else if(s.children.length-1===a){let b=s.children[a-1],k=b.offset+b.length,S=s.offset+s.length;h={offset:k,length:S-2-k,content:""}}else h={offset:p.offset,length:s.children[a+1].offset-p.offset,content:""};return P(e,h,n)}else if(t!==void 0){let a,p=`${JSON.stringify(t)}`;if(!n.isArrayInsertion&&s.children.length>l){let h=s.children[l];a={offset:h.offset,length:h.length,content:p}}else if(s.children.length===0||l===0)a={offset:s.offset+1,length:0,content:s.children.length===0?p:p+","};else{let h=l>s.children.length?s.children.length:l,b=s.children[h-1];a={offset:b.offset+b.length,length:0,content:","+p}}return P(e,a,n)}else throw Error(`Can not ${t===void 0?"remove":n.isArrayInsertion?"insert":"modify"} Array index ${u} as length is not sufficient`)}else throw Error(`Can not add ${typeof l!=="number"?"index":"property"} to parent of type ${s.type}`)}function P(e,r,t){if(!t.formattingOptions)return[r];let n=ce(e,r),i=r.offset,o=r.offset+r.content.length;if(r.length===0||r.content.length===0){while(i>0&&!Y(n,i-1))i--;while(o<n.length&&!Y(n,o))o++}let c=be(n,{offset:i,length:o-i},{...t.formattingOptions,keepLines:!1});for(let l=c.length-1;l>=0;l--){let u=c[l];n=ce(n,u),i=Math.min(i,u.offset),o=Math.max(o,u.offset+u.length),o+=u.content.length-u.length}let s=e.length-(n.length-o)-i;return[{offset:i,length:s,content:n.substring(i,o)}]}function ce(e,r){return e.substring(0,r.offset)+r.content+e.substring(r.offset+r.length)}var qe;(function(e){e[e.None=0]="None",e[e.UnexpectedEndOfComment=1]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=2]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=3]="UnexpectedEndOfNumber",e[e.InvalidUnicode=4]="InvalidUnicode",e[e.InvalidEscapeCharacter=5]="InvalidEscapeCharacter",e[e.InvalidCharacter=6]="InvalidCharacter"})(qe||(qe={}));var Ye;(function(e){e[e.OpenBraceToken=1]="OpenBraceToken",e[e.CloseBraceToken=2]="CloseBraceToken",e[e.OpenBracketToken=3]="OpenBracketToken",e[e.CloseBracketToken=4]="CloseBracketToken",e[e.CommaToken=5]="CommaToken",e[e.ColonToken=6]="ColonToken",e[e.NullKeyword=7]="NullKeyword",e[e.TrueKeyword=8]="TrueKeyword",e[e.FalseKeyword=9]="FalseKeyword",e[e.StringLiteral=10]="StringLiteral",e[e.NumericLiteral=11]="NumericLiteral",e[e.LineCommentTrivia=12]="LineCommentTrivia",e[e.BlockCommentTrivia=13]="BlockCommentTrivia",e[e.LineBreakTrivia=14]="LineBreakTrivia",e[e.Trivia=15]="Trivia",e[e.Unknown=16]="Unknown",e[e.EOF=17]="EOF"})(Ye||(Ye={}));var Xe=ze;var Qe;(function(e){e[e.InvalidSymbol=1]="InvalidSymbol",e[e.InvalidNumberFormat=2]="InvalidNumberFormat",e[e.PropertyNameExpected=3]="PropertyNameExpected",e[e.ValueExpected=4]="ValueExpected",e[e.ColonExpected=5]="ColonExpected",e[e.CommaExpected=6]="CommaExpected",e[e.CloseBraceExpected=7]="CloseBraceExpected",e[e.CloseBracketExpected=8]="CloseBracketExpected",e[e.EndOfFileExpected=9]="EndOfFileExpected",e[e.InvalidCommentToken=10]="InvalidCommentToken",e[e.UnexpectedEndOfComment=11]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=12]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=13]="UnexpectedEndOfNumber",e[e.InvalidUnicode=14]="InvalidUnicode",e[e.InvalidEscapeCharacter=15]="InvalidEscapeCharacter",e[e.InvalidCharacter=16]="InvalidCharacter"})(Qe||(Qe={}));function Ze(e,r,t,n){return He(e,r,t,n)}function Ke(e,r){let t=r.slice(0).sort((i,o)=>{let c=i.offset-o.offset;if(c===0)return i.length-o.length;return c}),n=e.length;for(let i=t.length-1;i>=0;i--){let o=t[i];if(o.offset+o.length<=n)e=ce(e,o);else throw Error("Overlapping edit");n=o.offset}return e}var X="dockito.jsonc";function F(e=process.cwd()){for(let r=rn(e);;r=Ce(r)){let t=tn(r,X);if(Dn(t)){let n=[],i=Xe(en(t,"utf8"),n,{allowTrailingComma:!0});if(n.length||!i||typeof i!=="object")throw Error(`${t} isn't valid JSONC.`);return{file:t,dir:r,config:i}}if(Ce(r)===r)return null}}var le=(e,r)=>Rn(e.dir,rn(r)).split(Bn).join("/");function on(e,r,t){let n=en(e.file,"utf8"),i=Ze(n,["docs",le(e,r)],t,{formattingOptions:{insertSpaces:!0,tabSize:2}});nn(e.file,Ke(n,i))}function sn(e,r){let t=["{"," // Where `dockito publish` puts new docs. No secrets in this file.",` "workspace": ${JSON.stringify(r.workspace)},`,r.project?` "project": ${JSON.stringify(r.project)},`:' // "project": "my-project",',r.folder?` "folder": ${JSON.stringify(r.folder)},`:' // "folder": "Folder/Subfolder",'," // Local files and the docs they publish to. Filled in by `dockito publish`.",' "docs": {}',"}",""],n=tn(e,X);return nn(n,t.join(`
20
- `)),n}var Ne="0.0.1-beta.1",Gn=!process.env.NO_COLOR&&process.stdout.isTTY,ee=(e)=>(r)=>Gn?`\x1B[${e}m${r}\x1B[0m`:r,y=ee("2"),K=ee("1"),I=ee("32"),ae=ee("31"),Oe=ee("33"),O=(e="")=>process.stdout.write(e+`
21
- `),D=(e="")=>process.stderr.write(e+`
22
- `);class Se extends Error{}var T=(e)=>{throw new Se(e)},ue={view:"View",publish:"Publish",everything:"Everything"};function $e(e){let r=Math.round((Date.now()-new Date(e).getTime())/1000);if(r<60)return"just now";let t=Math.round(r/60);if(t<60)return`${t} min ago`;let n=Math.round(t/60);if(n<24)return`${n} h ago`;let i=Math.round(n/24);if(i<30)return i===1?"yesterday":`${i} days ago`;return new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}function pe(e){let r=(n)=>n.replace(/\x1b\[[0-9;]*m/g,""),t=e[0]?.map((n,i)=>Math.max(...e.map((o)=>r(o[i]??"").length)))??[];for(let n of e)O(n.map((i,o)=>o===n.length-1?i:i+" ".repeat(t[o]-r(i).length)).join(" ").trimEnd())}function zn(e,r){switch(e.code){case"offline":return`Couldn't reach Dockito at ${r}.`;case"invalid_token":return"Your login has ended or was signed out. Run `dockito login`.";case"token_level":return`This needs ${ue[e.detail.needs]??e.detail.needs} access; this login has ${ue[e.detail.has]??e.detail.has}. Run \`dockito login --update\`.`;case"not_for_tokens":return"That can only be done in the browser.";case"not_found":return"Not found, or this login has no access to it. `dockito whoami` lists what it can reach.";case"deleted":return"That doc was deleted.";case"conflict":return"Someone published a newer version since yours. Get it with `dockito get`, or publish anyway with --force.";case"rate_limited":return"Too many requests. Try again in a minute.";case"too_large":return`The file is too large (limit ${Math.round(Number(e.detail.limit??0)/1048576)} MB).`;case"out_of_space":return"The workspace is out of storage.";case"unsupported":return"Dockito takes .md and .html files.";case"other_kind":return"That doc is a different kind of file (Markdown and HTML can't replace each other).";case"empty":return"The file is empty.";case"not_text":return"The file isn't text.";case"folder_gone":return"That project or folder no longer exists.";default:return`Dockito said: ${e.code}`}}var L=Ue(),C=Fe(L),Ee=`dockito-cli/${Ne} (${process.platform})`;function B(){let e=ie(C);if(!e)T("Not logged in. Run `dockito login`.");return te({url:L,token:e,source:"cli",userAgent:Ee})}function ln(e){let[r,t]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let n=Un(r,t,{stdio:"ignore",detached:!0});return n.on("error",()=>{}),n.unref(),!0}catch{return!1}}var Hn=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),qn=(e)=>new Promise((r)=>setTimeout(r,e)),Yn=()=>Mn().replace(/\.local$/,"").slice(0,60)||"Unknown device";function _e(e,r,t){let n=r??t?.config.workspace??U(L).workspace;if(n){let o=e.workspaces.find((c)=>c.slug===n);if(!o)T(`This login can't reach workspace "${n}". \`dockito whoami\` lists the ones it can.`);return o}if(e.workspaces.length===1)return e.workspaces[0];let i=e.workspaces.find((o)=>o.kind==="personal"&&o.role==="owner");if(i)return i;return T(`Pick a workspace with --workspace, or set one with \`dockito switch\`: ${e.workspaces.map((o)=>o.slug).join(", ")}`)}async function Ie(e,r,t){if(!t)return{project:null,folder:null,label:""};let[n,...i]=t.split("/").filter(Boolean),o=await e.projects(r),c=o.find((u)=>u.slug===n)??o.find((u)=>u.name.toLowerCase()===n.toLowerCase());if(!c)return T(`No project "${n}". Projects: ${o.map((u)=>u.slug).join(", ")||"none"}`);let s=null,l=null;for(let u of i){let a=c.folders.find((p)=>p.parentId===s&&p.name.toLowerCase()===u.toLowerCase());if(!a)return T(`No folder "${u}" in ${c.slug}${s?" at that level":""}.`);l=s=a.id}return{project:c.id,folder:l,label:[c.slug,...i].join("/")}}var Qn=/^[a-z0-9]{12}$/;function G(e,r){let n=(/^https?:\/\//.test(e)?new URL(e).pathname.split("/").filter(Boolean).pop()??"":e).replace(/\.(md|html)$/,"");if(Qn.test(n)&&/\d/.test(n))return n;if(je(e)){let i=(r&&r.config.docs?.[le(r,e)])??U(L).files?.[Z(e)];if(i)return i;T(`${e} hasn't been published yet. Run \`dockito publish ${e}\`.`)}return T(`"${e}" isn't a doc link, code or published file.`)}function Ae(e,r,t){if(e&&Z(r).startsWith(e.dir))on(e,r,t);else oe(L,(n)=>(n.files??={})[Z(r)]=t)}var fe=(e,r)=>oe(L,(t)=>(t.entries??={})[e]=r);function Xn(e){let r=Jn(Z(e)),t=cn("git",["rev-parse","HEAD"],{cwd:r,encoding:"utf8"});if(t.error||t.status!==0)return null;let n=cn("git",["status","--porcelain","--",Le(e)],{cwd:r,encoding:"utf8"});if(n.status!==0||n.stdout.trim())return null;return t.stdout.trim()}async function Zn(e){let r=ie(C);if(e.update&&!r)T("Not logged in. Run `dockito login` first.");if(process.env.DOCKITO_TOKEN)D(Oe("DOCKITO_TOKEN is set, so commands will keep using it instead of this login."));let t=te({url:L,token:e.update?r:null,userAgent:Ee}),n=await t.startLogin("Dockito CLI",Yn()),i=!e["no-browser"]&&Hn()&&ln(n.verification_uri_complete);O(i?`Opening ${n.verification_uri_complete}`:`Open ${K(n.verification_uri)}`),O(`Code: ${K(n.user_code)}`),D(y("Waiting for approval\u2026"));let o=n.interval*1000,c=Date.now()+n.expires_in*1000;while(Date.now()<c){await qn(o);try{let s=await t.pollLogin(n.device_code);if(!s)continue;let l=Me(C,s.token);O(`${I("\u2713")} Logged in as ${s.user.name}${s.user.username?y(` (${s.user.username})`):""}`);for(let u of s.workspaces)O(` ${u.name} ${y(`${u.slug} \xB7 ${ue[u.level]}`)}`);if(l==="file")D(Oe("No keychain found, so the token is in a file only you can read, in ~/.config/dockito."));return}catch(s){if(s instanceof x&&s.code==="access_denied")T("Denied in the browser.");if(s instanceof x&&s.code==="expired_token")T("The code expired. Run `dockito login` again.");if(s instanceof x&&s.code==="offline"){o=Math.min(o*2,30000);continue}throw s}}T("The code expired. Run `dockito login` again.")}async function Kn(){let e=ie(C);if(!e)return O("Not logged in.");if(process.env.DOCKITO_TOKEN)T("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await te({url:L,token:e,userAgent:Ee}).logout()}catch(r){if(!(r instanceof x)||r.code!=="invalid_token"&&r.code!=="not_found")throw r}Je(C),O(`${I("\u2713")} Logged out`)}async function Cn(e){let r=await B().whoami();if(e.json)return O(JSON.stringify(r,null,2));let t=U(L).workspace;if(O(`${r.user.name} ${y(`${r.user.username} \xB7 ${r.user.email}`)}`),r.token)O(y(`${r.token.name} \xB7 ${r.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(r.token.expiresAt).toLocaleDateString()}`}`));if(O(),pe(r.workspaces.map((n)=>[n.slug===t?I("\u25CF"):" ",n.slug,n.name,y(n.level?ue[n.level]:n.role)])),L!=="https://dockito-app.dockito.workers.dev")D(y(`
23
- ${L}`))}async function et(e){let r=await B().whoami();if(!e){let n=U(L).workspace;return pe(r.workspaces.map((i)=>[i.slug===n?I("\u25CF"):" ",i.slug,i.name]))}let t=r.workspaces.find((n)=>n.slug===e);if(!t)T(`This login can't reach "${e}": ${r.workspaces.map((n)=>n.slug).join(", ")}`);oe(L,(n)=>n.workspace=t.slug),O(`${I("\u2713")} Commands now use ${t.name} ${y(t.slug)}`)}async function nt(e){if(je(X))T(`${X} is already here.`);let r=B(),t=await r.whoami(),n=_e(t,e.workspace,null),[i,...o]=(e.to??"").split("/").filter(Boolean);if(i)await Ie(r,n.id,e.to);let c=sn(process.cwd(),{workspace:n.slug,project:i,folder:o.join("/")||void 0});O(`${I("\u2713")} Wrote ${Le(c)} ${y(`\u2192 ${[n.slug,i,...o].filter(Boolean).join("/")}`)}`)}async function tt(e,r){if(!e.length)T("Name a file: `dockito publish notes.md`");if(r.doc&&e.length>1)T("--doc takes one file.");for(let c of e)if(!je(c)||!Pn(c).isFile())T(`${c} isn't a file.`);let t=B(),n=F(),i=null,o=[];for(let c of e){let s=new Uint8Array(Fn(c)),l=Le(c),u=r.new?null:r.doc?G(r.doc,n):(n&&n.config.docs?.[le(n,c)])??U(L).files?.[Z(c)]??null,a=Xn(c);if(u){let S;try{S=await t.doc(u)}catch(_){if(_ instanceof x&&(_.code==="deleted"||_.code==="not_found"))T(`${c} was published to ${t.linkOf(u)}, which ${_.code==="deleted"?"was deleted":"this login can't reach"}. Publish it as a new doc with --new.`);throw _}let A=r.force?null:U(L).entries?.[S.doc.id]??null,N=await t.update(S.doc.id,l,s,{note:r.message,commit:a,base:A});if(fe(N.id,N.version),r.doc)Ae(n,c,N.slug);if(o.push({file:c,...N,link:t.linkOf(N.slug)}),!r.json)O(N.unchanged?`${y("=")} ${c} ${y("unchanged")} ${t.linkOf(N.slug)}`:`${I("\u2713")} ${c} \u2192 ${t.linkOf(N.slug)}${r.message?y(` \xB7 saved: ${r.message}`):""}`);continue}i??=await t.whoami();let p=_e(i,r.workspace,n),h=r.to??(n&&p.slug===n.config.workspace&&n.config.project?[n.config.project,n.config.folder].filter(Boolean).join("/"):void 0),b=await Ie(t,p.id,h),k=await t.create(p.id,l,s,{project:b.project,folder:b.folder});if(r.message)await t.save(k.id,k.version,r.message);if(fe(k.id,k.version),Ae(n,c,k.slug),o.push({file:c,...k,link:t.linkOf(k.slug),new:!0}),!r.json)O(`${I("\u2713")} ${c} \u2192 ${t.linkOf(k.slug)} ${y(`new in ${[p.slug,b.label].filter(Boolean).join("/")}`)}${r.message?y(` \xB7 saved: ${r.message}`):""}`)}if(r.json)O(JSON.stringify(o,null,2))}async function rt(e,r){let t=B(),n=await t.whoami(),i=_e(n,r.workspace,F()),o=await Ie(t,i.id,e),[c,s]=await Promise.all([t.docs(i.id,{project:o.project,folder:o.folder}),e?Promise.resolve([]):t.projects(i.id)]),l=e?(await t.projects(i.id)).find((a)=>a.id===o.project)?.folders.filter((a)=>a.parentId===o.folder)??[]:[];if(r.json)return O(JSON.stringify({workspace:i.slug,place:o.label||null,projects:s,folders:l,docs:c},null,2));let u=[];for(let a of s)u.push([K(`${a.slug}/`),a.name,y(`${a.docs} doc${a.docs===1?"":"s"}`),""]);for(let a of l)u.push([K(`${a.name}/`),"",y(`${a.docs} doc${a.docs===1?"":"s"}`),""]);for(let a of c)u.push([a.slug,a.title+y(a.kind==="html"?" .html":" .md"),y($e(a.updatedAt)),y(a.updatedByName??"")]);if(!u.length)return D(y(`Nothing in ${[i.slug,o.label].filter(Boolean).join("/")} yet.`));pe(u)}async function ot(e){if(!e)T("Name a doc: `dockito open <link, code or file>`");let r=`${L}/${G(e,F())}`;if(!ln(r))O(r)}async function it(e,r){if(!e)T("Name a doc: `dockito get <link, code or file> [-o file]`");let t=B(),n=F(),i=await t.doc(G(e,n)),o=r.entry?Number(r.entry):null;if(r.entry&&(!Number.isInteger(o)||o<1))T("--entry takes an entry number from `dockito history`.");let{bytes:c,entry:s}=await t.content(i.doc.id,o);if(!o)fe(i.doc.id,s);if(!r.output)return void process.stdout.write(c);if(Vn(r.output,c),!o)Ae(n,r.output,i.doc.slug);D(`${I("\u2713")} ${r.output} ${y(`\u2190 ${t.linkOf(i.doc.slug)}${o?` entry ${o}`:""}`)}`)}async function st(e,r){if(!e)T("Name a doc: `dockito history <link, code or file>`");let t=await B().doc(G(e,F()));if(r.json)return O(JSON.stringify(t.versions,null,2));O(`${K(t.doc.title)} ${y(`${L}/${t.doc.slug}`)}`),pe(t.versions.map((n,i)=>[String(n.number),i===0?I("current"):y($e(n.createdAt)),i===0?y($e(n.createdAt)):"",n.byName??"",y(n.source),n.savedAt?`${Oe("saved")}${n.note?` ${n.note}`:""}`:"",n.gitCommit?y(n.gitCommit.slice(0,7)):""]))}async function ct(e,r){let t=Number(r);if(!e||!Number.isInteger(t)||t<1)T("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let n=B(),i=await n.doc(G(e,F())),o=U(L).entries?.[i.doc.id]??i.versions[0]?.number??null,c=await n.restore(i.doc.id,t,o);fe(c.id,c.version),O(`${I("\u2713")} Restored entry ${t} as entry ${c.version} ${y(n.linkOf(c.slug))}`)}async function lt(e,r,t){if(!e)T('`dockito save <doc> [entry] -m "note"`');let n=B(),i=await n.doc(G(e,F())),o=r?Number(r):i.versions[0]?.number;if(!Number.isInteger(o)||o<1)T("Entries are in `dockito history`.");await n.save(i.doc.id,o,t.message??null),O(`${I("\u2713")} Saved entry ${o}${t.message?y(` \xB7 ${t.message}`):""}`)}var at=`Dockito CLI ${Ne}
2
+ import{spawn as an,spawnSync as bt}from"node:child_process";import{createHash as cn}from"node:crypto";import{existsSync as Tt,readFileSync as xe,statSync as ln,writeFileSync as $t}from"node:fs";import{hostname as dn}from"node:os";import{basename as Be,dirname as un,isAbsolute as pn,resolve as Ot}from"node:path";import{parseArgs as hn}from"node:util";var C=e=>e.level===null||e.level==="publish"||e.level==="everything",K=(e,t)=>e.projects.find(n=>n.slug===t)??e.projects.find(n=>n.name.toLowerCase()===t.toLowerCase())??null;function q(e){let t=new Map((e?.folders??[]).map(s=>[s.id,s])),n=s=>{let i=t.get(s);return[...i.parentId?n(i.parentId):[],i.name]},o=new Map((e?.folders??[]).map(s=>[n(s.id).join("/").toLowerCase(),s.id]));return{idOf:s=>s.length?o.get(s.join("/").toLowerCase()):null,pathOf:s=>s&&t.has(s)?n(s):[],add:(s,i)=>{o.set(s.join("/").toLowerCase(),i)}}}var It=e=>e.fileName??`${e.title}.${e.kind==="sheet"?"csv":e.kind}`,te=(e,t,n)=>e.filter(o=>(o.folderId??null)===t&&It(o).toLowerCase()===n.toLowerCase());function it(e){let t=e.trim();if(/^https?:\/\//.test(t))try{t=new URL(t).pathname}catch{return null}let n=(t.split("/").filter(Boolean).pop()??"").replace(/\.[a-z]+$/,"");return/^[a-z0-9]{12}$/.test(n)&&/\d/.test(n)?n:null}var x=class extends Error{constructor(n,o,s={}){super(n);this.code=n;this.status=o;this.detail=s}code;status;detail};function at(e){let t=e;if(!t?.number)return"";let n=t.at?new Date(t.at).toISOString().slice(0,10):"";return`The oldest is entry ${t.number}${t.note?` ("${t.note}")`:""}${n?`, saved ${n}`:""}.`}function fe(e){let t=e.url.replace(/\/+$/,"");async function n(a,c,u={}){let l=new URL(`${t}/api${c}`);for(let[k,v]of Object.entries(u.query??{}))v!=null&&v!==""&&l.searchParams.set(k,String(v));let m={},f=u.token===void 0?e.token:u.token;f&&(m.authorization=`Bearer ${f}`),e.userAgent&&(m["user-agent"]=e.userAgent);let w;u.json!==void 0?(m["content-type"]="application/json",w=JSON.stringify(u.json)):u.body&&(m["content-type"]="application/octet-stream",w=u.body);let O;try{O=e.fetch?await e.fetch(new Request(l,{method:a,headers:m,body:w})):await fetch(l,{method:a,headers:m,body:w})}catch{throw new x("offline",0)}return O}async function o(a){let c=await a.json().catch(()=>({}));if(!a.ok){let{error:u,...l}=c;throw new x(typeof u=="string"?u:`http_${a.status}`,a.status,l)}return c}async function s(a){let c=await a.json().catch(()=>({}));if(typeof c.error=="string"||typeof c.text!="string"||a.status>=500){let{error:u,...l}=c;throw new x(typeof u=="string"?u:`http_${a.status}`,a.status,l)}return{ok:a.ok,status:String(c.status),version:c.version,seq:c.seq,text:c.text}}let i=encodeURIComponent;return{url:t,linkOf:a=>`${t}/${a}`,startLogin:async(a,c)=>o(await n("POST","/device/code",{json:{client:a,device:c}})),pollLogin:async a=>{let c=await n("POST","/device/token",{json:{device_code:a},token:null}),u=await c.json().catch(()=>({}));if(c.ok)return u;if(u.error==="authorization_pending"||u.error==="slow_down")return null;throw new x(u.error??`http_${c.status}`,c.status)},logout:async()=>o(await n("DELETE","/tokens/current")),whoami:async()=>o(await n("GET","/whoami")),tree:async()=>o(await n("GET","/tree")),projects:async a=>(await o(await n("GET",`/workspaces/${i(a)}/projects`))).projects,docs:async(a,c={})=>(await o(await n("GET",`/workspaces/${i(a)}/docs`,{query:c}))).docs,files:async(a,c)=>(await o(await n("GET",`/workspaces/${i(a)}/files`,{query:{project:c}}))).docs,createProject:async(a,c)=>o(await n("POST",`/workspaces/${i(a)}/projects`,{json:{name:c}})),createFolder:async(a,c,u)=>o(await n("POST",`/projects/${i(a)}/folders`,{json:{name:c,parentId:u}})),recent:async a=>(await o(await n("GET",`/workspaces/${i(a)}/docs/recent`))).docs,doc:async a=>o(await n("GET",`/docs/by-code/${i(a)}`)),content:async(a,c,u={})=>{let l=await n("GET",`/docs/${i(a)}/content`,{query:{v:c,outline:u.outline?1:null,lines:u.lines,text:u.text?1:null}});l.ok||await o(l);let m=l.headers.get("x-doc-lines");return{bytes:new Uint8Array(await l.arrayBuffer()),entry:Number(l.headers.get("x-doc-version")),lines:m?Number(m):null,shown:l.headers.get("x-doc-shown")}},create:async(a,c,u,l={})=>o(await n("POST",`/workspaces/${i(a)}/docs`,{query:{name:c,title:l.title,project:l.project,folder:l.folder,public:l.public?1:null,source:e.source},body:u})),update:async(a,c,u,l={})=>o(await n("POST",`/docs/${i(a)}/versions`,{query:{name:c,note:l.note,commit:l.commit,base:l.base,source:e.source},body:u})),restore:async(a,c,u)=>o(await n("POST",`/docs/${i(a)}/history/${c}/restore`,{query:{base:u}})),save:async(a,c,u)=>o(await n("PUT",`/docs/${i(a)}/history/${c}/saved`,{json:{note:u}})),makePrivate:async a=>o(await n("PATCH",`/docs/${i(a)}`,{json:{public:!1}})),rename:async(a,c)=>o(await n("PATCH",`/docs/${i(a)}`,{json:{title:c}})),readSheet:async(a,c)=>s(await n("POST",`/docs/${i(a)}/sheet`,{query:{source:e.source},json:c})),changeSheet:async(a,c)=>s(await n("POST",`/docs/${i(a)}/sheet/change`,{query:{source:e.source},json:c}))}}import{emitKeypressEvents as _t}from"node:readline";import{createInterface as Rt}from"node:readline/promises";var Ct=!process.env.NO_COLOR&&process.stdout.isTTY,z=e=>t=>Ct?`\x1B[${e}m${t}\x1B[0m`:t,g=z("2"),S=z("1"),b=z("32"),_=z("31"),R=z("33"),Ie=z("36"),h=(e="")=>process.stdout.write(e+`
3
+ `),$=(e="")=>process.stderr.write(e+`
4
+ `),J=class extends Error{},p=e=>{throw new J(e)},G={view:"View",publish:"Publish",everything:"Everything"};function me(e){let t=Math.round((Date.now()-new Date(e).getTime())/1e3);if(t<60)return"just now";let n=Math.round(t/60);if(n<60)return`${n} min ago`;let o=Math.round(n/60);if(o<24)return`${o} h ago`;let s=Math.round(o/24);return s<30?s===1?"yesterday":`${s} days ago`:new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}function ne(e){let t=o=>o.replace(/\x1b\[[0-9;]*m/g,""),n=e[0]?.map((o,s)=>Math.max(...e.map(i=>t(i[s]??"").length)))??[];for(let o of e)h(o.map((s,i)=>i===o.length-1?s:s+" ".repeat(n[i]-t(s).length)).join(" ").trimEnd())}function oe(e,t){switch(e.code){case"offline":return`Couldn't reach Dockito at ${t}.`;case"invalid_token":return"Your login has ended or was signed out. Run `dockito login`.";case"token_level":return`This needs ${G[e.detail.needs]??e.detail.needs} access; this login has ${G[e.detail.has]??e.detail.has}. Run \`dockito login --update\`.`;case"not_for_tokens":return"That can only be done in the browser.";case"not_found":return"Not found, or this login has no access to it. `dockito whoami` lists what it can reach.";case"forbidden":return"You can open this but not change it. Ask its owner or a workspace admin for Edit.";case"public_off":return"This workspace doesn't allow public links. Its owner can turn them on in Workspace settings.";case"deleted":return"That doc was deleted.";case"conflict":return"Someone published a newer version since yours. Get it with `dockito get`, or publish anyway with --force.";case"rate_limited":return"Too many requests. Try again in a minute.";case"too_large":return`The file is too large (limit ${Math.round(Number(e.detail.limit??0)/1048576)} MB).`;case"out_of_space":return"The workspace is out of storage.";case"too_many_saved":return`This doc already keeps ${e.detail.limit} saved points, the most it can, so nothing changed. ${at(e.detail.oldest)} In the app, use Don't keep on one in History, then save again.`;case"unsupported":return"Dockito takes .md and .html files.";case"other_kind":return"That doc is a different kind of file (Markdown and HTML can't replace each other).";case"empty":return"The file is empty.";case"not_text":return"The file isn't text.";case"too_deep":return"Folders go at most 2 deep inside a project.";case"invalid_name":return"Names need 1 to 80 characters.";case"folder_gone":return"That project or folder no longer exists.";default:return typeof e.detail.message=="string"?e.detail.message:`Dockito said: ${e.code}`}}var Ce=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI,V=e=>process.stdout.write(e);function ct(e,t,n=0){return new Promise((o,s)=>{let i=n,a=l=>{l||V(`\x1B[${t.length}A`);for(let[m,f]of t.entries())V(`\x1B[2K${m===i?Ie("\u276F "+f.label):" "+f.label}${f.hint?g(" "+f.hint):""}
5
+ `)};V(`${b("?")} ${S(e)} ${g("(\u2191\u2193, Enter)")}
6
+ \x1B[?25l`),a(!0),_t(process.stdin),process.stdin.setRawMode(!0),process.stdin.resume();let c=()=>{process.stdin.off("keypress",u),process.stdin.setRawMode(!1),process.stdin.pause(),V("\x1B[?25h")},u=(l,m)=>{if(m.ctrl&&m.name==="c")return c(),V(`
7
+ `),s(new se);if(m.name==="up"||m.name==="k")i=(i-1+t.length)%t.length;else if(m.name==="down"||m.name==="j")i=(i+1)%t.length;else return m.name==="return"?(c(),V(`\x1B[${t.length+1}A\x1B[0J${b("?")} ${S(e)} ${Ie(t[i].label)}
8
+ `),o(i)):void 0;a(!1)};process.stdin.on("keypress",u)})}async function we(e,t=""){let n=Rt({input:process.stdin,output:process.stdout});try{return(await n.question(`${b("?")} ${S(e)}${t?g(` (${t})`):""} `)).trim()||t}finally{n.close()}}async function _e(e,t=!0){let n=(await we(`${e} ${g(t?"[Y/n]":"[y/N]")}`)).toLowerCase();return n?n.startsWith("y"):t}var se=class extends Error{};import{homedir as Lt}from"node:os";import{join as Re}from"node:path";var Ft="https://dockito-app.dockito.workers.dev";function lt(){return process.env.DOCKITO_CONFIG_DIR?process.env.DOCKITO_CONFIG_DIR:process.platform==="win32"&&process.env.APPDATA?Re(process.env.APPDATA,"dockito"):Re(process.env.XDG_CONFIG_HOME||Re(Lt(),".config"),"dockito")}var dt=()=>(process.env.DOCKITO_URL||Ft).replace(/\/+$/,""),ut=e=>new URL(e).host;import{spawnSync as Mt}from"node:child_process";import{chmodSync as Ht,existsSync as Le,mkdirSync as Kt,readFileSync as Ut,rmSync as pt,writeFileSync as Wt}from"node:fs";import{dirname as Bt,join as qt}from"node:path";var Y="dockito";function X(e,t,n){try{let o=Mt(e,t,{input:n,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return o.error?null:o}catch{return null}}var re=e=>qt(lt(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function Jt(e,t){if(process.platform==="darwin"){let n=X("security",["-i"],`add-generic-password -U -s ${Y} -a ${e} -l Dockito -w ${t}
9
+ `);return!!n&&n.status===0&&!/error/i.test(n.stderr??"")}if(process.platform==="linux"){let n=X("secret-tool",["store","--label=Dockito","service",Y,"account",e],t);return!!n&&n.status===0}return!1}function Gt(e){let t=process.platform==="darwin"?X("security",["find-generic-password","-s",Y,"-a",e,"-w"]):process.platform==="linux"?X("secret-tool",["lookup","service",Y,"account",e]):null;return(t?.status===0?t.stdout.trim():"")||null}function zt(e){process.platform==="darwin"?X("security",["delete-generic-password","-s",Y,"-a",e]):process.platform==="linux"&&X("secret-tool",["clear","service",Y,"account",e])}function ht(e,t){if(Jt(e,t))return Le(re(e))&&pt(re(e)),"keychain";let n=re(e);return Kt(Bt(n),{recursive:!0,mode:448}),Wt(n,t,{mode:384}),Ht(n,384),"file"}function ye(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let t=Gt(e);if(t)return t;let n=re(e);return Le(n)&&Ut(n,"utf8").trim()||null}function gt(e){zt(e);let t=re(e);Le(t)&&pt(t)}var N=class extends Error{constructor(n,o,s={}){super(o);this.code=n;this.data=s}code;data};function ke(e,t){let n=e.workspaces;if(t)return n.find(s=>s.slug===t)??p(`This login can't reach workspace "${t}". \`dockito tree\` lists the ones it can.`);let o=n.length===1?n[0]:n.find(s=>s.kind==="personal"&&s.role==="owner");if(o)return o;throw new N("needs_workspace",`Pick a workspace with -w: ${n.map(s=>s.slug).join(", ")}`,{workspaces:n.map(s=>s.slug)})}function Fe(e,t){if(!t)return{project:null,folder:null,label:`${e.slug} Docs`};let[n,...o]=t.split("/").filter(Boolean),s=K(e,n);if(!s)return p(`No project "${n}" in ${e.slug}. \`dockito tree\` lists them; \`dockito mkdir ${t}\` makes it.`);let i=q(s).idOf(o);return i===void 0?p(`No folder "${o.join("/")}" in ${s.slug}. \`dockito mkdir ${s.slug}/${o.join("/")}\` makes it.`):{project:s.id,folder:i,label:[e.slug,s.slug,...o].join("/")}}import{createHash as Vt}from"node:crypto";import{spawnSync as Yt}from"node:child_process";import{existsSync as ae,readFileSync as Xt,readdirSync as Qt,statSync as wt}from"node:fs";import{basename as ft,join as L,relative as Zt,resolve as Me,sep as en}from"node:path";var tn=4,ie=e=>e<1024*1024?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,nn=e=>Vt("sha256").update(e).digest("hex"),yt=e=>e.split(en).join("/"),Q=(e,t)=>`${e} ${t}${e===1?"":"s"}`,on=(e,t)=>{let n=/\.[^./]+$/.exec(t.toLowerCase())?.[0]??"";return e.find(o=>o.extensions.includes(n))?.id??null};function $e(e,t){let n=Yt("git",e,{cwd:t,encoding:"utf8",maxBuffer:67108864});return n.error||n.status!==0?null:n.stdout}function sn(e,t){let n=$e(["ls-files","-z","--cached","--others","--exclude-standard","--","."],e);return(n!==null?n.split("\0").filter(Boolean):(function s(i){return Qt(L(e,i),{withFileTypes:!0}).flatMap(a=>a.name.startsWith(".")||a.name==="node_modules"?[]:a.isDirectory()?s(L(i,a.name)):[yt(L(i,a.name))])})("")).filter(s=>t(s)&&ae(L(e,s))&&wt(L(e,s)).isFile()).sort()}function rn(e,t,n){let o=[...e.matchAll(/\b(?:src|href)=["']([^"'#?]+)/gi)].map(s=>s[1]);return[...new Set(o.filter(s=>!/^([a-z]+:|\/\/|\/)/i.test(s)&&!n(s)&&ae(L(t,s))))]}function mt(e,t){let n=(i,a,c="")=>`dockito sync ${t} -w ${i} --to ${a}${c}`,o=e.workspaces.filter(C).flatMap(i=>[...i.projects.map(a=>({workspace:i.slug,project:a.slug,name:a.name,docs:a.docs,command:n(i.slug,a.slug)})),{workspace:i.slug,project:null,name:"a new project",docs:0,command:n(i.slug,"<name>"," --create")}]),s=Math.max(...o.map(i=>i.command.length));throw new N("needs_place",[`Which project should ${t==="."?"this folder":t+"/"} go to? Ask the user, then run one of:`,...o.map(i=>` ${i.command.padEnd(s)} ${g(i.project?`${i.name} \xB7 ${Q(i.docs,"doc")}`:i.name)}`),g("Subfolders become folders. `dockito tree` shows every place.")].join(`
10
+ `),{choices:o})}async function kt(e,t,n){let o=Ce()&&!n.json,s=await e.tree(),i=r=>!!on(s.kinds,r);s.workspaces.some(C)||p("This login can't publish anywhere. Run `dockito login --update`.");let a=t;a||(o||mt(s,ae("docs")?"docs":"."),a=await we("Folder to sync",ae("docs")?"docs":"."));let c=Me(a);(!ae(c)||!wt(c).isDirectory())&&p(`${a} isn't a folder.`);let u=yt(Zt(process.cwd(),c))||".",l;n.workspace&&(l=s.workspaces.find(r=>r.slug===n.workspace)??p(`This login can't reach workspace "${n.workspace}".`));let m=n.to;if(!m){o||mt(s,u);let r=(l?[l]:s.workspaces.filter(C)).flatMap(y=>[...y.projects.map(j=>({w:y,p:j.slug,label:j.name,hint:`${y.slug} \xB7 ${Q(j.docs,"doc")}`})),{w:y,p:null,label:"New project",hint:y.slug}]),d=r[await ct("Project",r)];l=d.w,m=d.p??await we("Project name",ft(c===process.cwd()?c:Me(c,".."))),n.create||=!d.p}if(!l){let r=s.workspaces.filter(y=>C(y)&&K(y,m.split("/")[0])),d=s.workspaces.find(y=>y.kind==="personal"&&y.role==="owner");if(l=r.length===1?r[0]:s.workspaces.length===1?s.workspaces[0]:!r.length&&d?d:void 0,!l)throw new N("needs_workspace",`Which workspace? Run again with -w: ${(r.length?r:s.workspaces).map(y=>y.slug).join(", ")}`)}let f=l;C(f)||p(`This login has View access in ${f.slug}; syncing needs Publish. Run \`dockito login --update\`.`);let[w,...O]=m.split("/").filter(Boolean);w||p("--to takes a project, or project/folder."),O.length>2&&p(`Folders go at most ${2} deep inside a project.`);let k=K(f,w);if(!k&&!n.create)throw new N("no_project",`No project "${w}" in ${f.slug}. Projects: ${f.projects.map(r=>r.slug).join(", ")||"none"}.
11
+ To make it, run again with --create.`,{workspace:f.slug,projects:f.projects.map(r=>r.slug)});let v=sn(c,i);v.length||p(`No ${s.kinds.flatMap(r=>r.extensions).join(", ")} files in ${u}.`);let U=2-O.length,D=v.filter(r=>r.split("/").length-1>U);if(D.length&&!n.fold){let r=y=>y.split("/").slice(0,U).join("/")||"the project's top",d=`${Q(D.length,"file")} ${D.length===1?"is":"are"} more than ${2} folders deep inside the project. With --fold they go in ${[...new Set(D.map(r))].join(", ")}; where two would share a name, the folders left out go in front (plans/archive/grok/README.md \u2192 grok-README.md).`;if(!o)throw new N("too_deep",`${d}
12
+ Ask the user, then run again with --fold.`,{files:D});if($(d),!await _e("Fold them like that?"))return $(g("Nothing changed."));n.fold=!0}let ze=r=>{let d=r.split("/");return{folder:[...O,...d.slice(0,-1).slice(0,U)],dropped:d.slice(0,-1).slice(U),file:d[d.length-1]}},Oe=new Map;for(let r of v){let d=ze(r),y=[...d.folder,d.file].join("/").toLowerCase();Oe.set(y,(Oe.get(y)??0)+1)}let Ve=k?await e.files(f.id,k.id):[],Z=q(k),H=v.map(r=>{let{folder:d,dropped:y,file:j}=ze(r),F=y.length&&Oe.get([...d,j].join("/").toLowerCase())>1?[...y,j].join("-"):j,P=new Uint8Array(Xt(L(c,r))),B={rel:r,name:F,folder:d,bytes:P,hash:nn(P),action:"new",doc:null,others:[],assets:[]};/\.html?$/i.test(r)&&(B.assets=rn(new TextDecoder().decode(P),L(c,r,".."),i));let rt=Z.idOf(d);if(rt===void 0)return B;let ee=te(Ve,rt,F);return ee.length>1?{...B,action:"ambiguous",others:ee}:ee.length?{...B,doc:ee[0],action:ee[0].sha256===B.hash?"same":"update"}:B}),Ye=H.filter(r=>r.bytes.length>f.maxFile);Ye.length&&p(`Over the ${ie(f.maxFile)} limit per file in ${f.slug}:
13
+ ${Ye.map(r=>` ${r.rel} ${ie(r.bytes.length)}`).join(`
14
+ `)}`);let Se=new Map;for(let r of H){let d=[...r.folder,r.name].join("/").toLowerCase();Se.set(d,[...Se.get(d)??[],r.rel])}let Xe=[...Se.values()].filter(r=>r.length>1);Xe.length&&p(`These files would be the same doc; rename one of each:
15
+ ${Xe.map(r=>" "+r.join(" and ")).join(`
16
+ `)}`);let I=[],Qe=new Set;for(let r of H)for(let d=1;d<=r.folder.length;d++){let y=r.folder.slice(0,d),j=y.join("/").toLowerCase();Z.idOf(y)===void 0&&!Qe.has(j)&&(Qe.add(j),I.push(y))}I.sort((r,d)=>r.length-d.length);let A=H.filter(r=>r.action==="new"||r.action==="update"),Ze=H.filter(r=>r.action==="ambiguous"),Ae=H.filter(r=>r.action==="same").length,Dt=new Set(H.flatMap(r=>r.doc?[r.doc.id]:r.others.map(d=>d.id))),De=Ve.filter(r=>!Dt.has(r.id)).length,et=`${f.slug}/${k?.slug??w}${O.length?"/"+O.join("/"):""}`,ue={workspace:f.slug,to:et,create:{project:k?null:w,folders:I.map(r=>r.join("/"))},docs:A.map(r=>({file:r.rel,action:r.action,folder:r.folder.join("/")||null,name:r.name,link:r.doc?e.linkOf(r.doc.slug):null})),unchanged:Ae,ambiguous:Ze.map(r=>({file:r.rel,docs:r.others.map(d=>e.linkOf(d.slug))})),notHere:De};if(!n.json){h(`${S(u==="."?"./":u+"/")} \u2192 ${S(et)}`),k||h(` ${b("+ project")} ${w}`);for(let d of I)h(` ${b("+ folder ")} ${d.join("/")}`);if(A.length>15)for(let[d,y]of[["new",b("+ new ")],["update",R("~ update ")]]){let j=new Map;for(let F of A.filter(P=>P.action===d))j.set(F.folder.join("/"),[...j.get(F.folder.join("/"))??[],F.name]);for(let[F,P]of j)h(` ${y} ${P.length} in ${F||"the project's top"}: ${g(P.slice(0,4).join(", ")+(P.length>4?`, ${P.length-4} more`:""))}`)}else for(let d of A){let y=d.name!==ft(d.rel)?g(` \u2192 ${[...d.folder,d.name].join("/")}`):"";h(` ${d.action==="new"?b("+ new "):R("~ update ")} ${d.rel}${y}`)}Ae&&h(g(` ${Ae} unchanged`));for(let d of Ze)h(` ${_("? skip ")} ${d.rel} ${g(`${d.others.length} docs there have this name: ${d.others.map(y=>e.linkOf(y.slug)).join(", ")}`)}`);De&&h(g(` ${Q(De,"doc")} in the project with no file here, left as they are`)),A.length&&h(g(` ${Q(A.length,"file")} to send, ${ie(A.reduce((d,y)=>d+y.bytes.length,0))}`));let r=A.filter(d=>d.assets.length);r.length&&h(R(` ! ${r.length===1?r[0].rel:`${r.length} HTML files`} load ${[...new Set(r.flatMap(d=>d.assets))].join(", ")} from next to ${r.length===1?"it":"them"}; only HTML files are published`))}if(!A.length&&k&&!I.length){n.json?h(JSON.stringify({...ue,applied:!0},null,2)):h(`${b("\u2713")} Up to date`);return}if(n["dry-run"]){n.json&&h(JSON.stringify({...ue,applied:!1},null,2));return}if((!k||I.length)&&!n.yes){let r=["dockito sync",u,`-w ${f.slug}`,`--to ${m}`,n.create&&!k?"--create":"",n.fold?"--fold":"","--yes"].filter(Boolean).join(" ");if(!o){if(n.json)return h(JSON.stringify({...ue,applied:!1,next:r},null,2));throw new N("confirm",`
17
+ Nothing changed yet. Show the user this plan, then run: ${r}`,{next:r})}if(!await _e("Apply?"))return $(g("Nothing changed."))}if(!k){let r=await e.createProject(f.id,w);k={id:r.id,slug:r.slug,name:w,docs:0,folders:[]},n.json||h(`${b("\u2713")} Project ${w}`)}let Ne=k;for(let r of I){let d=await e.createFolder(Ne.id,r[r.length-1],r.length>1?Z.idOf(r.slice(0,-1)):null);Z.add(r,d.id)}I.length&&!n.json&&h(`${b("\u2713")} ${Q(I.length,"folder")}`);let pe=$e(["rev-parse","--show-toplevel"],c)?.trim()??null,tt=pe?$e(["rev-parse","HEAD"],pe)?.trim()??null:null,Nt=new Set(pe?($e(["status","--porcelain","-z","--","."],c)??"").split("\0").filter(Boolean).map(r=>Me(pe,r.slice(3))):[]),W=[],he=null,Pe=[...A],ge=!n.json&&Ce(),Pt=A.reduce((r,d)=>r+d.bytes.length,0),nt=0,ot=()=>ge&&process.stdout.write(`\r\x1B[2K${g(`Sending ${W.length} of ${A.length} \xB7 ${ie(nt)} of ${ie(Pt)}`)}`);async function Et(r){let d=tt&&!Nt.has(L(c,r.rel))?tt:null;for(;;)try{let y=r.doc?await e.update(r.doc.id,r.name,r.bytes,{commit:d}):await e.create(f.id,r.name,r.bytes,{project:Ne.id,folder:Z.idOf(r.folder)??null});return{file:r.rel,action:r.action,link:e.linkOf(y.slug)}}catch(y){if(y instanceof x&&y.code==="rate_limited"){he||(n.json||$(g("\rUpload limit reached; carrying on in a minute\u2026")),he=new Promise(j=>setTimeout(j,3e4)).then(()=>{he=null})),await he;continue}return{file:r.rel,action:r.action,error:y instanceof x?oe(y,e.url):String(y)}}}ot(),await Promise.all(Array.from({length:Math.min(tn,Pe.length)},async()=>{for(let r=Pe.shift();r;r=Pe.shift()){let d=await Et(r);W.push(d),nt+=r.bytes.length,ge&&process.stdout.write("\r\x1B[2K"),d.error&&!n.json?h(`${_("\u2717")} ${d.file} ${_(d.error)}`):!n.json&&!ge&&h(`${b("\u2713")} ${d.file} ${g(d.link)}`),ot()}})),ge&&process.stdout.write("\r\x1B[2K");let Ee=W.filter(r=>r.error),st=`${e.url}/${f.slug}/project/${Ne.slug}`;n.json?h(JSON.stringify({...ue,applied:!0,link:st,results:W},null,2)):h(`${Ee.length?R(`${W.length-Ee.length} of ${W.length} sent`):b("\u2713 Synced")} ${g(st)}`),Ee.length&&(process.exitCode=1)}var He=`Dockito hosts Markdown and HTML docs, and live sheets, at a short link,
18
+ with History.
19
+
20
+ workspace a person's or a team's space; its slug is in every link
21
+ project docs grouped in a workspace, with folders up to 2 deep
22
+ Docs a workspace's docs outside any project
23
+ doc one file. Publishing a file with the same name to the same
24
+ place updates that doc and keeps its link
25
+ History every publish is an entry; -m keeps one as a saved point
26
+ access a login has View, Publish or Everything per workspace
27
+
28
+ For AI agents:
29
+ - The CLI remembers nothing between runs. Run \`dockito tree\` to see
30
+ where docs can go and \`dockito ls <place>\` for what's there.
31
+ - Just a link to share: publish with no --to; it goes to the workspace's
32
+ Docs. When it belongs in a project, ask the user where, never guess,
33
+ and pass it with --to every time.
34
+ - Docs are private: only people in the workspace can open them. Add
35
+ --public (anyone with the link, no sign-in) only when the user clearly
36
+ asked for a public link. If they didn't say, keep it private.
37
+ - A folder of docs: \`dockito sync <folder> --to <project>\`. It shows a
38
+ plan; show it to the user before running with --yes.
39
+ - \`dockito <command> --help\` explains each command. Use --json; on
40
+ failure, read "error" and the commands it suggests.
41
+
42
+ Sheets: people and agents edit the same cells, live. Publishing a .csv
43
+ makes a new one.
44
+ - \`dockito get <sheet>\` gives its outline (sheets, columns, what's in
45
+ them) and its version, v N. \`read\` gives rows, \`check\` finds broken
46
+ formulas, \`changes --since N\` lists what changed.
47
+ - \`dockito apply <sheet> changes.json --base N\`, with N the version you
48
+ last read. Addresses and rows mean what they meant then. If someone
49
+ changed the same cells meanwhile, nothing applies and it says who
50
+ (exit 1): read again, then apply on the new version.
51
+ - Change rows by what's in them, not by row number: \`update\` with a
52
+ filter ("ID = T-104"), and "max" when you mean several rows.`,ve={sync:{use:"sync <folder> --to <project>",about:"Mirror a folder into a project",page:`Mirrors a local folder into a project: subfolders become folders, and each
53
+ file updates the doc with the same file name in the same folder, or becomes
54
+ a new doc there. Only files whose content changed are sent. Nothing is moved
55
+ or deleted in Dockito. The CLI remembers nothing: pass --to every time.
56
+
57
+ Docs made by sync are private.
58
+
59
+ --to <project[/folder]> Where it goes (required; \`dockito tree\` lists places)
60
+ -w, --workspace <slug> Another workspace
61
+ --create Make the project if it doesn't exist
62
+ --fold Put folders deeper than 2 into the deepest allowed;
63
+ clashing names get the left-out folders in front
64
+ --dry-run Show the plan, change nothing
65
+ --yes Apply a plan that makes a project or folders
66
+
67
+ It always shows a plan. Making a project or folders waits for --yes: show
68
+ the plan to the user first. Sending docs into folders that exist just runs.
69
+
70
+ dockito sync docs --to handbook
71
+ dockito sync docs --to handbook --create --fold --dry-run`},publish:{use:"publish <file...>",about:"Publish files; the same name in that place updates its doc",page:`Publishes Markdown or HTML files. A file with the same name in that place
72
+ updates its doc (same link, a new History entry); otherwise it's a new doc.
73
+ Unchanged files are skipped. A .csv becomes a new live sheet, typed as Excel
74
+ opens it; after that it changes with \`dockito apply\`, never by a file.
75
+
76
+ Docs are private: only people in the workspace can open them.
77
+ Add --public only when the user clearly asked for a public link (anyone
78
+ with the link, no sign-in). If they didn't say, keep it private. --public
79
+ works only for a new doc; an existing private doc can be made public only
80
+ from Share in the app.
81
+
82
+ --to <project[/folder]> Where (default: the workspace's Docs)
83
+ -w, --workspace <slug> Another workspace (default: your personal one)
84
+ -m, --message <note> Keep this entry as a saved point, with a note
85
+ --public Make the new doc public (only when the user asked)
86
+ --title <title> A new doc's title (default: the file name); an
87
+ existing doc keeps its own
88
+ --doc <doc> Update this doc (link or code) instead
89
+ --new A new doc even if the name is already there
90
+
91
+ dockito publish plan.md private, in your Docs
92
+ dockito publish plan.md --to handbook/specs private, in a folder
93
+ dockito publish plan.md --public the user asked for a public link
94
+ dockito publish q3.html --title "Q3 plan" named Q3 plan, not q3`},share:{use:"share <doc>",about:"Who can open a doc; --private takes a public link back",page:`Says whether a doc is private (only people in its workspace) or public
95
+ (anyone with the link). --private makes it private again at once.
96
+
97
+ An existing doc is made public only from Share in the app; a new doc can be
98
+ published public with \`dockito publish <file> --public\` when the user asks.
99
+
100
+ dockito share <link or code>
101
+ dockito share <link or code> --private`},tree:{use:"tree",about:"Every workspace, project and folder you can use",page:"Lists every workspace this login can reach, its projects and their folders,\nwith doc counts, each written as the --to it takes. Docs themselves: `dockito ls`."},ls:{use:"ls [project/folder]",about:"What's in a place",page:`Lists the docs (with their codes), projects and folders in a place: your
102
+ workspace's top level, a project, or a folder. The place can also be given
103
+ as --to, like publish. -w for another workspace.
104
+
105
+ dockito ls handbook/specs
106
+ dockito ls --to handbook/specs`},mkdir:{use:"mkdir <project/folder>",about:"Make a project or folders",page:`Makes whatever is missing: the project, then up to 2 levels of folders.
107
+
108
+ dockito mkdir handbook/specs/drafts`},open:{use:"open <doc>",about:"Open in the browser",page:"Opens a doc (link or code) in the browser."},get:{use:"get <doc> [-o file]",about:"Download a doc, or read it in parts",page:`Prints a doc's file, or writes it with -o. Big docs can be read in parts.
109
+ A sheet prints its outline: each sheet's columns, what's in them, and the
110
+ version to apply changes at; \`dockito read\` gives its rows.
111
+
112
+ --outline Only the headings, each with the line it starts on
113
+ --lines <range> Only these lines: 120-260, 120- (to the end) or 120
114
+ --text HTML as readable text: no tags, scripts or styles,
115
+ often a few times shorter (Markdown is already text)
116
+ --entry <n> A History entry (numbers from \`dockito history\`)
117
+ -o, --output <file> Write it to a file
118
+
119
+ dockito get <doc> --outline
120
+ dockito get <doc> --lines 120-260 --text`},read:{use:"read <doc> [range]",about:"A sheet's rows, a range or totals",page:`Prints rows of a sheet as a text table: row numbers, values as shown, and
121
+ the version (v N) to apply changes at. A range, or a sheet's rows under its
122
+ header row, filtered and sorted; with --group or --totals, totals instead.
123
+ 200 rows unless --limit says, at most 1,000; page on with --from.
124
+
125
+ [range] Tasks!A1:G40, Tasks!B:B, Tasks!3:5, a defined name,
126
+ or E17 when there's one sheet
127
+ --sheet <name> A sheet or table's rows under its header row
128
+ --where <filter> "Area = Kitchen and Status <> Done or Hours > 4":
129
+ = <> < > <= >= contains, is empty, is not empty;
130
+ dates as yyyy-mm-dd
131
+ --select <cols> Columns to show, by header or letter: ID,Status
132
+ --order-by <cols> "Due desc, ID"; empty cells go last
133
+ --group <cols> Group the rows by these columns
134
+ --totals <list> count, sum Hours, avg Hours, min Due, max Due
135
+ --from <n>, --limit <n> Which rows: from 1; 200 by default, 1,000 at most
136
+ --formulas Formulas with their results: =F2*50 \u2192 50
137
+ --styles Formatting in words, merges, sizes, frozen panes
138
+ --csv As CSV instead of a table
139
+
140
+ dockito read <doc> --where "Status = Blocked" --select ID,Owner,Due
141
+ dockito read <doc> --group Owner --totals "count,sum Hours"
142
+ dockito read <doc> "Tasks!A1:G20" --formulas`},apply:{use:"apply <doc> <changes.json>",about:"Change a sheet at the version you read",page:`Changes a sheet, read at --base: the version (v N) your last get, read or
143
+ changes showed. Addresses, rows and headers mean what they meant then,
144
+ wherever they moved since. Every change applies, or none: a cell someone
145
+ changed since is refused, naming who (exit 1). Read again and apply on the
146
+ new version. Prints each cell old \u2192 new and the new version.
147
+
148
+ The file (or - for stdin) is a list of changes, or {"base": N, "ops": [...],
149
+ "expect": {...}}. "sheet" can be left out when there's one. Each change:
150
+ {"set": "Tasks!E17", "value": "Done"}
151
+ {"set": "Tasks!B2:C3", "values": [["Tile grout", "Kitchen"], ["Vent", "Bath"]]}
152
+ {"update": {"sheet": "Tasks", "where": "ID = T-104", "set": {"Status": "Done"}}}
153
+ {"update": {"sheet": "Tasks", "where": "Area = Kitchen and Status = Open",
154
+ "set": {"Status": "Done"}, "max": 50}} max is 1 unless set; more
155
+ matches than max are refused
156
+ {"append": {"sheet": "Tasks", "rows": [{"ID": "T-214", "Status": "Open"}]}}
157
+ a column's formula in the rows above carries
158
+ into new rows, as in Excel
159
+ {"insertRows": {"sheet": "Tasks", "after": 40, "count": 2}}, deleteRows
160
+ {"rows": "41:42"} or a list, "9, 16, 41:42"; moveRows {"rows": "5:6",
161
+ "after": 10}; insertCols, deleteCols, moveCols the same with letters
162
+ {"format": {"range": "Tasks!1:1", "bold": true, "fill": "#fff2cc"}}
163
+ cells, rows "1:1" or columns "F:F"; null clears a field:
164
+ bold italic underline strike wrap true or false
165
+ size points, 1 to 409
166
+ font a font's name, as "Inter"
167
+ color fill "#rrggbb"
168
+ hAlign left, center, right or justify
169
+ vAlign top, middle or bottom
170
+ indent 0 to 250
171
+ borderTop borderRight borderBottom borderLeft
172
+ "thin #d0d5dd", or {"line": "thin",
173
+ "color": "#d0d5dd"}; lines are thin,
174
+ medium, thick, dashed, dotted or double
175
+ numFmt Excel's, as "$#,##0.00", "d mmm yyyy"
176
+ {"resize": {"range": "Tasks!B:C", "size": 120}}, merge, unmerge
177
+ addSheet, renameSheet, moveSheet, deleteSheet, sheetProps (frozenRows,
178
+ frozenCols, hidden, tabColor), setName, deleteName, setTable, deleteTable
179
+ {"addTable": {"range": "Tasks!A1:D40", "name": "Tasks"}}: a header row
180
+ and banded rows in one change; a cell's own fill shows over them
181
+ Values: a number, true, false, null (empty), a string ("=\u2026" is a formula),
182
+ {"text": "=as typed"} or {"date": "2026-10-19"}.
183
+
184
+ --base <n> The version you read
185
+ -m, --message <note> Why, shown with the change in the sheet
186
+ --dry-run Show what would happen, save nothing
187
+ --undo <n> Take back your own change that made version n
188
+ --key <key> A retry key (default: made from the request, so
189
+ the same request on the same version applies once)
190
+
191
+ dockito apply <doc> changes.json --base 412 -m "close finished tasks"
192
+ echo '[{"set": "E17", "value": "Done"}]' | dockito apply <doc> - --base 412`},check:{use:"check <doc>",about:"Problems in a sheet",page:`Finds problems in a sheet, by rules: formula errors, a formula that breaks
193
+ its column's pattern, a value typed over a column's formulas, numbers or
194
+ dates typed as text, totals that stop before their column does.
195
+
196
+ --sheet <name> One sheet (default: every sheet)`},changes:{use:"changes <doc> --since <v>",about:"What changed in a sheet",page:`Lists what changed after a version, or since a time in UTC, oldest first:
197
+ who, when, through what, with each cell old \u2192 new, named where it is now.
198
+ Ends at the current version, to apply changes at.
199
+
200
+ --since <v|time> 412, 2026-10-13 or "2026-10-13 09:00"
201
+ --max <n> At most this many changes (default 50, at most 200)`},history:{use:"history <doc>",about:"History entries",page:`Lists a doc's History: entry, when, who, from where (upload, edit, cli,
202
+ mcp), saved points with their notes, and the git commit.`},restore:{use:"restore <doc> <entry>",about:"Make a History entry current again",page:"Makes an older entry the current one, as a new entry. Nothing is lost."},save:{use:"save <doc> [entry] -m <note>",about:"Keep an entry as a saved point",page:`Keeps an entry (default: the current one) as a saved point, so History
203
+ never drops it, with an optional note. A doc keeps up to 10.`},login:{use:"login [--update]",about:"Sign in through the browser",page:`Opens the browser, where you check a code and choose this login's access
204
+ in each workspace: View, Publish or Everything. --update changes that access.
205
+ --no-browser prints the link instead.`},logout:{use:"logout",about:"Sign out this machine",page:"Signs this machine out and removes its token."},whoami:{use:"whoami",about:"Who you are and what this login can reach",page:"Your name, the login's expiry, and its access in each workspace."},agent:{use:"agent",about:"What an AI agent needs to know",page:"Prints what Dockito is and how an AI agent should use this CLI."}};var qe="0.0.1-beta.11",E=dt(),ce=ut(E),Je=`dockito-cli/${qe} (${process.platform})`;function T(){let e=ye(ce);return e||p("Not logged in. Run `dockito login`."),fe({url:E,token:e,source:"cli",userAgent:Je})}function St(e){let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let o=an(t,n,{stdio:"ignore",detached:!0});return o.on("error",()=>{}),o.unref(),!0}catch{return!1}}var gn=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),fn=e=>new Promise(t=>setTimeout(t,e)),mn=()=>dn().replace(/\.local$/,"").slice(0,60)||"Unknown device";function M(e){return it(e)??p(`"${e}" isn't a doc link or code. \`dockito ls\` lists docs with their codes.`)}function vt(e){let t=un(Ot(e)),n=bt("git",["rev-parse","HEAD"],{cwd:t,encoding:"utf8"});if(n.error||n.status!==0)return null;let o=bt("git",["status","--porcelain","--",Be(e)],{cwd:t,encoding:"utf8"});return o.status!==0||o.stdout.trim()?null:n.stdout.trim()}var At=e=>cn("sha256").update(e).digest("hex");async function wn(e){let t=ye(ce);e.update&&!t&&p("Not logged in. Run `dockito login` first."),process.env.DOCKITO_TOKEN&&$(R("DOCKITO_TOKEN is set, so commands will keep using it instead of this login."));let n=fe({url:E,token:e.update?t:null,userAgent:Je}),o=await n.startLogin("Dockito CLI",mn()),s=!e["no-browser"]&&gn()&&St(o.verification_uri_complete);h(s?`Opening ${o.verification_uri_complete}`:`Open ${S(o.verification_uri)}`),h(`Code: ${S(o.user_code)}`),$(g("Waiting for approval\u2026"));let i=o.interval*1e3,a=Date.now()+o.expires_in*1e3;for(;Date.now()<a;){await fn(i);try{let c=await n.pollLogin(o.device_code);if(!c)continue;let u=ht(ce,c.token);h(`${b("\u2713")} Logged in as ${c.user.name}${c.user.username?g(` (${c.user.username})`):""}`);for(let l of c.workspaces)h(` ${l.name} ${g(`${l.slug} \xB7 ${G[l.level]}`)}`);u==="file"&&$(R("No keychain found, so the token is in a file only you can read, in ~/.config/dockito."));return}catch(c){if(c instanceof x&&c.code==="access_denied"&&p("Denied in the browser."),c instanceof x&&c.code==="expired_token"&&p("The code expired. Run `dockito login` again."),c instanceof x&&c.code==="offline"){i=Math.min(i*2,3e4);continue}throw c}}p("The code expired. Run `dockito login` again.")}async function yn(){let e=ye(ce);if(!e)return h("Not logged in.");process.env.DOCKITO_TOKEN&&p("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await fe({url:E,token:e,userAgent:Je}).logout()}catch(t){if(!(t instanceof x)||t.code!=="invalid_token"&&t.code!=="not_found")throw t}gt(ce),h(`${b("\u2713")} Logged out`)}async function kn(e){let t=await T().whoami();if(e.json)return h(JSON.stringify(t,null,2));h(`${t.user.name} ${g(`${t.user.username} \xB7 ${t.user.email}`)}`),t.token&&h(g(`${t.token.name} \xB7 ${t.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(t.token.expiresAt).toLocaleDateString()}`}`)),h(),ne(t.workspaces.map(n=>[n.slug,n.name,g(n.level?G[n.level]:n.role)])),E!=="https://dockito-app.dockito.workers.dev"&&$(g(`
206
+ ${E}`))}async function bn(e,t){e.length||p("Name a file: `dockito publish notes.md --to <project/folder>`"),t.doc&&e.length>1&&p("--doc takes one file."),t.title!==void 0&&e.length>1&&p("--title takes one file."),t.title!==void 0&&!t.title.trim()&&p("--title needs a title."),t.public&&t.doc&&p("--public is only for a new doc; an existing one can be made public from Share in the app.");for(let l of e)(!Tt(l)||!ln(l).isFile())&&p(`${l} isn't a file${pn(l)?"":` (looked for ${Ot(l)})`}.`);let n=e.filter(Ke);n.length&&t.doc&&p("A .csv makes a new sheet; it can't replace a doc. Leave out --doc, or change a sheet with `dockito apply`."),n.length&&t.message&&p("Sheets don't keep saved points yet: publish the .csv without -m.");let o=T(),s=[];if(t.doc){let l=await o.doc(M(t.doc)),m=new Uint8Array(xe(e[0])),f=await o.update(l.doc.id,Be(e[0]),m,{note:t.message,commit:vt(e[0])});return t.json?h(JSON.stringify([{file:e[0],...f,link:o.linkOf(f.slug)}],null,2)):h(f.unchanged?`${g("=")} ${e[0]} ${g("unchanged")} ${o.linkOf(f.slug)}`:`${b("\u2713")} ${e[0]} \u2192 ${o.linkOf(f.slug)}`)}let i=await o.tree(),a=ke(i,t.workspace);C(a)||p(`This login has View access in ${a.slug}. Run \`dockito login --update\`.`);let c=Fe(a,t.to),u=await o.files(a.id,c.project);for(let l of e){let m=new Uint8Array(xe(l)),f=Be(l),w=t.new?[]:te(u,c.folder,f),O=w.filter(D=>D.kind==="sheet");O.length&&p(`${f} in ${c.label} is ${O.length>1?`${O.length} live sheets`:"a live sheet"} (${O.map(D=>o.linkOf(D.slug)).join(", ")}): change ${O.length>1?"one":"it"} with \`dockito apply\`. --new makes a new sheet from the file instead.`),w.length>1&&p(`${w.length} docs in ${c.label} are named ${f}: ${w.map(D=>o.linkOf(D.slug)).join(", ")}. Pick one with --doc, or publish a new one with --new.`);let k=w[0];if(k&&t.public&&!(await o.doc(k.slug)).doc.public&&p(`${f} is already a private doc in ${c.label} (${o.linkOf(k.slug)}). Only Share in the app can make it public; \`--new --public\` publishes a new public doc.`),k&&k.sha256===At(m)&&!t.message){s.push({file:l,id:k.id,slug:k.slug,version:k.version,unchanged:!0,link:o.linkOf(k.slug)}),t.json||h(`${g("=")} ${l} ${g("unchanged")} ${o.linkOf(k.slug)}`);continue}let v=k?await o.update(k.id,f,m,{note:t.message,commit:vt(l)}):await o.create(a.id,f,m,{project:c.project,folder:c.folder,public:t.public,title:t.title});!k&&t.message&&await o.save(v.id,v.version,t.message);let U="public"in v&&v.public===!0;s.push({file:l,...v,...Ke(l)?{type:"sheet"}:{},link:o.linkOf(v.slug),new:!k}),t.json||h(`${b("\u2713")} ${l} \u2192 ${o.linkOf(v.slug)} ${g(k?v.unchanged?"unchanged":"updated":`new ${Ke(l)?"sheet ":""}in ${c.label}`)}${U?` ${b("\xB7 public")}`:""}${t.message?g(` \xB7 saved: ${t.message}`):""}`)}t.json?h(JSON.stringify(s,null,2)):!t.to&&s.some(l=>l.new)&&$(g("In the workspace's Docs. To put docs in a project: --to <project/folder>; `dockito tree` lists them."))}async function $n(e){let t=await T().tree();if(e.json)return h(JSON.stringify(t,null,2));let n=o=>g(`${o} doc${o===1?"":"s"}`);for(let o of t.workspaces){h(`${S(o.slug)} ${o.name} ${g(`\xB7 ${o.level?G[o.level]:o.role}`)}`);let s=[[g("Docs"),"",n(o.docs)]];for(let i of o.projects){s.push([i.slug,i.name,n(i.docs)]);let a=(c,u)=>{for(let l of i.folders.filter(m=>m.parentId===c))s.push([`${u}/${l.name}`,"",n(l.docs)]),a(l.id,`${u}/${l.name}`)};a(null,i.slug)}ne(s.map(i=>[" ",...i])),h()}$(g("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function vn(e,t){e&&t.to&&e!==t.to&&p(`Two places: ${e} and --to ${t.to}. Give one.`);let n=e??t.to,o=T(),s=await o.tree(),i=ke(s,t.workspace),a=Fe(i,n),c=await o.docs(i.id,{project:a.project,folder:a.folder}),u=a.project?i.projects.find(w=>w.id===a.project):null,l=n?[]:i.projects,m=u?u.folders.filter(w=>w.parentId===a.folder):[];if(t.json)return h(JSON.stringify({workspace:i.slug,place:n??null,projects:l,folders:m,docs:c},null,2));let f=[];for(let w of l)f.push([S(`${w.slug}/`),w.name,g(`${w.docs} doc${w.docs===1?"":"s"}`),""]);for(let w of m)f.push([S(`${w.name}/`),"",g(`${w.docs} doc${w.docs===1?"":"s"}`),""]);for(let w of c)f.push([w.slug,w.title+g(w.kind==="html"?" .html":w.kind==="sheet"?" sheet":" .md"),g(me(w.updatedAt)),g(w.updatedByName??"")]);if(!f.length)return $(g(`Nothing in ${a.label} yet.`));ne(f)}async function jn(e,t){let[n,...o]=(e??"").split("/").filter(Boolean);n||p("`dockito mkdir <project>[/folder[/subfolder]]`"),o.length>2&&p("Folders go at most 2 deep inside a project.");let s=T(),i=ke(await s.tree(),t.workspace),a=K(i,n),c=a?.id??(await s.createProject(i.id,n)).id,u=!a;a||h(`${b("\u2713")} Project ${n}`);let l=q(a),m=null;for(let f=0;f<o.length;f++){let w=l.idOf(o.slice(0,f+1));if(w){m=w;continue}m=(await s.createFolder(c,o[f],m)).id,l.add(o.slice(0,f+1),m),u=!0,h(`${b("\u2713")} Folder ${o.slice(0,f+1).join("/")}`)}u||$(g("Already there."))}async function xn(e){e||p("Name a doc: `dockito open <link or code>`");let t=`${E}/${M(e)}`;St(t)||h(t)}async function Tn(e,t){e||p("Name a doc: `dockito get <link or code> [-o file]`"),t.outline&&(t.lines!==void 0||t.text)&&p("--outline is the headings only; leave out --lines and --text.");let n=T(),o=await n.doc(M(e));if(o.doc.kind==="sheet"){let u=["entry","outline","lines","text"].filter(m=>t[m]!==void 0);u.length&&p(`A sheet has no file to read by ${u.map(m=>`--${m}`).join(" or ")}: \`dockito read\` gives its rows, a range or totals.`);let l=await n.readSheet(o.doc.id,{outline:!0});return t.output?($t(t.output,`${l.text.trimEnd()}
207
+ `),$(`${b("\u2713")} ${t.output} ${g(`\u2190 ${n.linkOf(o.doc.slug)} outline`)}`)):de(l)}let s=t.entry?Number(t.entry):null;t.entry&&(!Number.isInteger(s)||s<1)&&p("--entry takes an entry number from `dockito history`.");let i=await n.content(o.doc.id,s,{outline:t.outline,lines:t.lines,text:t.text}),a=i.bytes.length&&i.lines!==null&&!t.output&&i.bytes[i.bytes.length-1]!==10?new Uint8Array([...i.bytes,10]):i.bytes;if(!t.output)return void process.stdout.write(a);$t(t.output,a);let c=t.outline?" outline":i.shown?` lines ${i.shown} of ${i.lines}`:t.text?" as text":"";$(`${b("\u2713")} ${t.output} ${g(`\u2190 ${n.linkOf(o.doc.slug)}${s?` entry ${s}`:""}${c}`)}`)}async function On(e,t){e||p("Name a doc: `dockito history <link or code>`");let n=await T().doc(M(e));if(n.doc.kind==="sheet"&&p(Ge),t.json)return h(JSON.stringify(n.versions,null,2));h(`${S(n.doc.title)} ${g(`${E}/${n.doc.slug}`)}`),ne(n.versions.map((o,s)=>[String(o.number),s===0?b("current"):g(me(o.createdAt)),s===0?g(me(o.createdAt)):"",o.byName??"",g(o.source),o.savedAt?`${R("saved")}${o.note?` ${o.note}`:""}`:"",o.gitCommit?g(o.gitCommit.slice(0,7)):""]))}async function Sn(e,t){let n=Number(t);(!e||!Number.isInteger(n)||n<1)&&p("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let o=T(),s=await o.doc(M(e));s.doc.kind==="sheet"&&p(`${Ge} \`dockito apply <doc> --undo <version>\` takes back a change of yours.`);let i=await o.restore(s.doc.id,n,s.versions[0]?.number??null);h(`${b("\u2713")} Restored entry ${n} as entry ${i.version} ${g(o.linkOf(i.slug))}`)}async function An(e,t,n){e||p('`dockito save <doc> [entry] -m "note"`');let o=T(),s=await o.doc(M(e));s.doc.kind==="sheet"&&p(Ge);let i=t?Number(t):s.versions[0]?.number;(!Number.isInteger(i)||i<1)&&p("Entries are in `dockito history`."),await o.save(s.doc.id,i,n.message??null),h(`${b("\u2713")} Saved entry ${i}${n.message?g(` \xB7 ${n.message}`):""}`)}async function Dn(e,t){e||p("`dockito share <link or code> [--private]`"),t.public&&p("An existing doc can be made public only from Share in the app. `dockito publish <file> --public` makes a new doc public.");let n=T(),o=await n.doc(M(e)),s=n.linkOf(o.doc.slug);if(t.private)return o.doc.public&&await n.makePrivate(o.doc.id),t.json?h(JSON.stringify({public:!1,link:s},null,2)):h(`${b("\u2713")} Private: only people in ${o.workspace.name} can open it ${g(s)}`);if(t.json)return h(JSON.stringify({public:o.doc.public,link:s},null,2));h(o.doc.public?`${b("Public")}: anyone with the link can open it ${s}`:`Private: only people in ${o.workspace.name} can open it ${g(s)}`)}var Ke=e=>/\.csv$/i.test(e),Ge="Sheets don't keep History entries yet; `dockito changes <doc> --since <version>` lists what changed.";async function Te(e,t,n){t||p(`Name a sheet: \`dockito ${n}\``);let o=await e.doc(M(t));return o.doc.kind!=="sheet"&&p(`${o.doc.title} is a ${o.doc.kind==="md"?"Markdown":"HTML"} doc, not a sheet: \`dockito get\` reads it.`),o}function de(e,t){t?h(JSON.stringify(e,null,2)):h(e.text.trimEnd()),e.ok||(process.exitCode=1)}var le=(e,t,n)=>{if(e===void 0)return;let o=Number(e);return(!/^\d+$/.test(e.trim())||o<n)&&p(`--${t} takes a whole number, ${n} or more.`),o},Ue=e=>e===void 0?void 0:e.split(",").map(t=>t.trim()).filter(Boolean);async function Nn(e,t,n){let o=T(),s=await Te(o,e,"read <doc> [range]"),i=await o.readSheet(s.doc.id,{read:{range:t,sheet:n.sheet,where:n.where,select:Ue(n.select),orderBy:n["order-by"],group:Ue(n.group),totals:Ue(n.totals),from:le(n.from,"from",1),limit:le(n.limit,"limit",1),formulas:n.formulas,styles:n.styles,format:n.csv?"csv":void 0}});de(i,n.json)}async function Pn(e,t,n){let o=T(),s=await Te(o,e,"apply <doc> <changes.json>"),i=le(n.undo,"undo",1),a;if(i!==void 0)(t||n.base!==void 0)&&p("--undo goes alone: no changes file and no --base with it."),a={undo:i,note:n.message,dryRun:n["dry-run"]||void 0};else{!t&&process.stdin.isTTY&&p("Give the changes: `dockito apply <doc> changes.json`, or pipe them in with `-`. `dockito apply --help` shows their form.");let u=!t||t==="-"?xe(0,"utf8"):Tt(t)?xe(t,"utf8"):p(`${t} isn't a file.`),l;try{l=JSON.parse(u)}catch{p(`${t&&t!=="-"?t:"The input"} isn't JSON: a list of changes, or {"base": N, "ops": [...]}.`)}let m=Array.isArray(l)?{ops:l}:l;(typeof m!="object"||m===null)&&p('The changes are a list, or {"base": N, "ops": [...]}.');let f=le(n.base,"base",0)??m.base;f===void 0&&p("--base is the version you read: v N in `dockito get` or `dockito read`."),a={...m,base:f,note:n.message??m.note,dryRun:n["dry-run"]||m.dryRun||void 0}}let c=n.key??En(a);de(await o.changeSheet(s.doc.id,{key:c,...a}),n.json)}var En=e=>At(new TextEncoder().encode(JSON.stringify(e))).slice(0,32);async function In(e,t){let n=T(),o=await Te(n,e,"check <doc>");de(await n.readSheet(o.doc.id,{check:t.sheet?{sheet:t.sheet}:{}}),t.json)}async function Cn(e,t){let n=T(),o=await Te(n,e,"changes <doc> --since <version>");t.since===void 0&&p('--since is a version (412) or a time in UTC (2026-10-13, "2026-10-13 09:00").');let s=/^\s*\d+\s*$/.test(t.since)?Number(t.since):t.since;de(await n.readSheet(o.doc.id,{changes:{since:s,max:le(t.max,"max",1)}}),t.json)}var We=`Dockito CLI ${qe}
208
+
209
+ ${He}
24
210
 
25
211
  Usage: dockito <command>
26
212
 
27
- login [--update] Sign in through the browser (--update changes access)
28
- logout Sign out this machine
29
- whoami Who you are and what this login can reach
30
- switch [workspace] Set the workspace commands use
31
-
32
- publish <file...> Publish files; the same file updates the same doc
33
- -m, --message <note> Keep this entry as a saved point
34
- --to <project/folder> Where new docs go
35
- --doc <doc> Update this doc instead
36
- --new Publish as a new doc
37
- --force Publish even if someone published since
38
- ls [project/folder] Docs and projects
39
- open <doc> Open in the browser
40
- get <doc> [-o file] Download (--entry <n> for a History entry)
41
- history <doc> History entries
42
- restore <doc> <entry> Make a History entry current again
43
- save <doc> [entry] -m ... Keep an entry as a saved point
44
- init [--to project/folder] Write a dockito.jsonc here
45
-
46
- <doc> is a link, a code, or a file published before.
47
- -w, --workspace <slug> Use another workspace
48
- --json Machine-readable output (whoami, publish, ls, history)
49
-
50
- Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`;async function ut(){let{values:e,positionals:r}=Wn({allowPositionals:!0,options:{message:{type:"string",short:"m"},to:{type:"string"},doc:{type:"string"},new:{type:"boolean"},force:{type:"boolean"},workspace:{type:"string",short:"w"},output:{type:"string",short:"o"},entry:{type:"string"},json:{type:"boolean"},update:{type:"boolean"},"no-browser":{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[t,...n]=r;if(e.version)return O(Ne);if(e.help||!t||t==="help")return O(at);switch(t){case"login":return Zn(e);case"logout":return Kn();case"whoami":return Cn(e);case"switch":return et(n[0]);case"init":return nt(e);case"publish":return tt(n,e);case"ls":return rt(n[0],e);case"open":return ot(n[0]);case"get":return it(n[0],e);case"history":return st(n[0],e);case"restore":return ct(n[0],n[1]);case"save":return lt(n[0],n[1],e);default:T(`Unknown command "${t}". Run \`dockito --help\`.`)}}ut().then(()=>process.exit(0),(e)=>{if(e instanceof Se)D(ae(e.message));else if(e instanceof x)D(ae(zn(e,L)));else if(e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message)))D(ae(`${e.message}. Run \`dockito --help\`.`));else D(ae(String(e?.stack??e)));process.exit(1)});
213
+ ${Object.values(ve).map(e=>` ${e.use.padEnd(30)}${e.about}`).join(`
214
+ `)}
215
+
216
+ <doc> is a link or a code. \`dockito <command> --help\` for more.
217
+ -w, --workspace <slug> Another workspace (default: your personal one)
218
+ --json Machine-readable output
219
+
220
+ Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`,jt=e=>{let t=ve[e];return t?`dockito ${t.use}
221
+
222
+ ${t.page}`:null},_n={login:{flags:["update","no-browser"],args:0},logout:{flags:[],args:0},whoami:{flags:["json"],args:0},sync:{flags:["to","workspace","json","yes","dry-run","create","fold"],args:1},publish:{flags:["message","to","doc","new","title","workspace","json","public"],args:1/0},tree:{flags:["json"],args:0},ls:{flags:["to","workspace","json"],args:1},mkdir:{flags:["workspace"],args:1},share:{flags:["public","private","json"],args:1},open:{flags:[],args:1},get:{flags:["output","entry","outline","lines","text"],args:1},history:{flags:["json"],args:1},restore:{flags:[],args:2},save:{flags:["message"],args:2},agent:{flags:[],args:0},read:{flags:["sheet","where","select","order-by","group","totals","from","limit","formulas","styles","csv","json"],args:2},apply:{flags:["base","message","dry-run","undo","key","json"],args:2},check:{flags:["sheet","json"],args:1},changes:{flags:["since","max","json"],args:1}},xt={message:"-m",workspace:"-w",output:"-o",yes:"-y"};function Rn(e,t,n){let o=_n[e];if(!o)return;let s=`\`dockito ${ve[e]?.use??e}\`; \`dockito ${e} --help\` for more.`,i=t.filter(a=>!o.flags.includes(a));if(i.length){let a=i.map(u=>`--${u}`).join(", "),c=o.flags.length?` It takes ${o.flags.map(u=>xt[u]?`${xt[u]}/--${u}`:`--${u}`).join(", ")}.`:"";p(`${e} doesn't take ${a}.${c} Use: ${s}`)}n.length>o.args&&p(`${e} takes ${o.args===0?"no arguments":`at most ${o.args} argument${o.args===1?"":"s"}`}; got ${n.map(a=>`"${a}"`).join(" ")}. Use: ${s}`)}async function Ln(){let{values:e,positionals:t}=hn({allowPositionals:!0,options:{message:{type:"string",short:"m"},to:{type:"string"},doc:{type:"string"},new:{type:"boolean"},title:{type:"string"},workspace:{type:"string",short:"w"},output:{type:"string",short:"o"},entry:{type:"string"},outline:{type:"boolean"},lines:{type:"string"},text:{type:"boolean"},json:{type:"boolean"},update:{type:"boolean"},yes:{type:"boolean",short:"y"},"dry-run":{type:"boolean"},create:{type:"boolean"},fold:{type:"boolean"},"no-browser":{type:"boolean"},public:{type:"boolean"},private:{type:"boolean"},sheet:{type:"string"},where:{type:"string"},select:{type:"string"},"order-by":{type:"string"},group:{type:"string"},totals:{type:"string"},from:{type:"string"},limit:{type:"string"},formulas:{type:"boolean"},styles:{type:"boolean"},csv:{type:"boolean"},base:{type:"string"},undo:{type:"string"},key:{type:"string"},since:{type:"string"},max:{type:"string"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[n,...o]=t;if(e.version)return h(qe);if(n==="help"&&o[0])return h(jt(o[0])??We);if(e.help&&n)return h(jt(n)??We);if(e.help||!n||n==="help")return h(We);switch(Rn(n,Object.keys(e).filter(s=>e[s]!==void 0),o),n){case"login":return wn(e);case"logout":return yn();case"whoami":return kn(e);case"sync":return kt(T(),o[0],e);case"publish":return bn(o,e);case"tree":return $n(e);case"ls":return vn(o[0],e);case"mkdir":return jn(o[0],e);case"share":return Dn(o[0],e);case"open":return xn(o[0]);case"get":return Tn(o[0],e);case"history":return On(o[0],e);case"restore":return Sn(o[0],o[1]);case"save":return An(o[0],o[1],e);case"agent":return h(He);case"read":return Nn(o[0],o[1],e);case"apply":return Pn(o[0],o[1],e);case"check":return In(o[0],e);case"changes":return Cn(o[0],e);default:p(`Unknown command "${n}". Run \`dockito --help\`.`)}}function je(e){let t=2,n=()=>--t===0&&process.exit(e);process.stdout.write("",n),process.stderr.write("",n)}Ln().then(()=>je(Number(process.exitCode??0)),e=>{let t=process.argv.includes("--json");if(e instanceof N)return t?h(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2)):$(e.message),je(2);if(e instanceof se)return je(130);t&&e instanceof x?h(JSON.stringify({error:e.code,message:oe(e,E),...e.detail},null,2)):t&&e instanceof J?h(JSON.stringify({error:"failed",message:e.message},null,2)):e instanceof J?$(_(e.message)):e instanceof x?$(_(oe(e,E))):e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message))?$(_(`${e.message}. Run \`dockito --help\`.`)):$(_(String(e?.stack??e))),je(1)});
package/package.json CHANGED
@@ -1,15 +1,25 @@
1
1
  {
2
2
  "name": "dockito",
3
- "version": "0.0.1-beta.1",
3
+ "version": "0.0.1-beta.11",
4
4
  "description": "Dockito CLI: publish the docs your AI writes as links you can share.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://dockito-site.dockito.workers.dev",
7
- "keywords": ["dockito", "docs", "markdown", "publish", "cli", "ai"],
7
+ "keywords": [
8
+ "dockito",
9
+ "docs",
10
+ "markdown",
11
+ "publish",
12
+ "cli",
13
+ "ai"
14
+ ],
8
15
  "type": "module",
9
16
  "bin": {
10
17
  "dockito": "dockito.js"
11
18
  },
12
- "files": ["dockito.js", "README.md"],
19
+ "files": [
20
+ "dockito.js",
21
+ "README.md"
22
+ ],
13
23
  "engines": {
14
24
  "node": ">=18.17"
15
25
  }