dockito 0.0.1-beta.2 → 0.0.1-beta.4

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 +5 -4
  2. package/dockito.js +95 -88
  3. package/package.json +13 -3
package/README.md CHANGED
@@ -11,13 +11,14 @@ npx dockito@beta publish plan.md -m "Sent to the client"
11
11
  | Command | Does |
12
12
  |---|---|
13
13
  | `login` | Sign in through the browser and choose what this login may do in each workspace |
14
- | `sync [folder]` | Mirror a folder into a project: subfolders become folders, only changed files are sent, a plan first |
15
- | `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 |
16
17
  | `tree`, `mkdir` | Every workspace, project and folder you can use; make new ones |
17
18
  | `ls`, `open`, `get` | List, open and download docs |
18
19
  | `history`, `restore`, `save` | A doc's History |
19
- | `whoami`, `switch`, `logout` | Your login and workspaces |
20
+ | `whoami`, `logout` | Your login and workspaces |
20
21
 
21
- In a repo that's set up, `dockito` alone syncs. Run `dockito --help` for everything, 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.
22
+ 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.
22
23
 
23
24
  This is a beta: commands may still change.
package/dockito.js CHANGED
@@ -1,100 +1,107 @@
1
1
  #!/usr/bin/env node
2
- // @bun
3
- import{spawn as Yr,spawnSync as Vn}from"child_process";import{createHash as Xr}from"crypto";import{existsSync as Jt,readFileSync as Qr,statSync as Zr,writeFileSync as Cr}from"fs";import{hostname as eo}from"os";import{basename as Gt,dirname as to,resolve as Ve}from"path";import{parseArgs as no}from"util";class K extends Error{code;status;detail;constructor(e,n,r={}){super(e);this.code=e;this.status=n;this.detail=r}}function Ye(e){let n=e.url.replace(/\/+$/,"");async function r(o,c,i={}){let l=new URL(`${n}/api${c}`);for(let[v,A]of Object.entries(i.query??{}))if(A!==void 0&&A!==null&&A!=="")l.searchParams.set(v,String(A));let f={},u=i.token===void 0?e.token:i.token;if(u)f.authorization=`Bearer ${u}`;if(e.userAgent)f["user-agent"]=e.userAgent;let p;if(i.json!==void 0)f["content-type"]="application/json",p=JSON.stringify(i.json);else if(i.body)f["content-type"]="application/octet-stream",p=i.body;let g;try{g=await fetch(l,{method:o,headers:f,body:p})}catch{throw new K("offline",0)}return g}async function t(o){let c=await o.json().catch(()=>({}));if(!o.ok){let{error:i,...l}=c;throw new K(typeof i==="string"?i:`http_${o.status}`,o.status,l)}return c}let s=encodeURIComponent;return{url:n,linkOf:(o)=>`${n}/${o}`,startLogin:async(o,c)=>t(await r("POST","/device/code",{json:{client:o,device:c}})),pollLogin:async(o)=>{let c=await r("POST","/device/token",{json:{device_code:o},token:null}),i=await c.json().catch(()=>({}));if(c.ok)return i;if(i.error==="authorization_pending"||i.error==="slow_down")return null;throw new K(i.error??`http_${c.status}`,c.status)},logout:async()=>t(await r("DELETE","/tokens/current")),whoami:async()=>t(await r("GET","/whoami")),tree:async()=>t(await r("GET","/tree")),projects:async(o)=>(await t(await r("GET",`/workspaces/${s(o)}/projects`))).projects,docs:async(o,c={})=>(await t(await r("GET",`/workspaces/${s(o)}/docs`,{query:c}))).docs,places:async(o)=>(await t(await r("GET",`/workspaces/${s(o)}/docs/tree`))).docs,createProject:async(o,c)=>t(await r("POST",`/workspaces/${s(o)}/projects`,{json:{name:c}})),createFolder:async(o,c,i)=>t(await r("POST",`/projects/${s(o)}/folders`,{json:{name:c,parentId:i}})),recent:async(o)=>(await t(await r("GET",`/workspaces/${s(o)}/docs/recent`))).docs,doc:async(o)=>t(await r("GET",`/docs/by-code/${s(o)}`)),content:async(o,c)=>{let i=await r("GET",`/docs/${s(o)}/content`,{query:{v:c}});if(!i.ok)await t(i);return{bytes:new Uint8Array(await i.arrayBuffer()),entry:Number(i.headers.get("x-doc-version"))}},create:async(o,c,i,l={})=>t(await r("POST",`/workspaces/${s(o)}/docs`,{query:{name:c,...l,source:e.source},body:i})),update:async(o,c,i,l={})=>t(await r("POST",`/docs/${s(o)}/versions`,{query:{name:c,note:l.note,commit:l.commit,base:l.base,source:e.source},body:i})),restore:async(o,c,i)=>t(await r("POST",`/docs/${s(o)}/history/${c}/restore`,{query:{base:i}})),save:async(o,c,i)=>t(await r("PUT",`/docs/${s(o)}/history/${c}/saved`,{json:{note:i}})),rename:async(o,c)=>t(await r("PATCH",`/docs/${s(o)}`,{json:{title:c}}))}}var Xn=!process.env.NO_COLOR&&process.stdout.isTTY,we=(e)=>(n)=>Xn?`\x1B[${e}m${n}\x1B[0m`:n,D=we("2"),re=we("1"),U=we("32"),Xe=we("31"),Qe=we("33"),le=we("36"),R=(e="")=>process.stdout.write(e+`
4
- `),Le=(e="")=>process.stderr.write(e+`
5
- `);class rn extends Error{}var X=(e)=>{throw new rn(e)},nn={view:"View",publish:"Publish",everything:"Everything"};function on(e,n){switch(e.code){case"offline":return`Couldn't reach Dockito at ${n}.`;case"invalid_token":return"Your login has ended or was signed out. Run `dockito login`.";case"token_level":return`This needs ${nn[e.detail.needs]??e.detail.needs} access; this login has ${nn[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"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`Dockito said: ${e.code}`}}var Tt=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI;class vt extends Error{}import{createHash as mr}from"node:crypto";import{spawnSync as hr}from"node:child_process";import{appendFileSync as wr,existsSync as fe,readFileSync as Tn,readdirSync as kr,statSync as vn}from"node:fs";import{basename as Fe,join as B,relative as Pt,resolve as jn,sep as yr}from"node:path";class Se extends Error{code;status;detail;constructor(e,n,r={}){super(e);this.code=e;this.status=n;this.detail=r}}import{emitKeypressEvents as Qn}from"node:readline";import{createInterface as Zn}from"node:readline/promises";var xt=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI,ke=(e)=>process.stdout.write(e);function Ot(e,n,r=0){return new Promise((t,s)=>{let o=r,c=(f)=>{if(!f)ke(`\x1B[${n.length}A`);for(let[u,p]of n.entries())ke(`\x1B[2K${u===o?le("❯ "+p.label):" "+p.label}${p.hint?D(" "+p.hint):""}
6
- `)};ke(`${U("?")} ${re(e)} ${D("(↑↓, Enter)")}
7
- \x1B[?25l`),c(!0),Qn(process.stdin),process.stdin.setRawMode(!0),process.stdin.resume();let i=()=>{process.stdin.off("keypress",l),process.stdin.setRawMode(!1),process.stdin.pause(),ke("\x1B[?25h")},l=(f,u)=>{if(u.ctrl&&u.name==="c")return i(),ke(`
8
- `),s(new sn);if(u.name==="up"||u.name==="k")o=(o-1+n.length)%n.length;else if(u.name==="down"||u.name==="j")o=(o+1)%n.length;else if(u.name==="return")return i(),ke(`\x1B[${n.length+1}A\x1B[0J${U("?")} ${re(e)} ${le(n[o].label)}
9
- `),t(o);else return;c(!1)};process.stdin.on("keypress",l)})}async function Ze(e,n=""){let r=Zn({input:process.stdin,output:process.stdout});try{return(await r.question(`${U("?")} ${re(e)}${n?D(` (${n})`):""} `)).trim()||n}finally{r.close()}}async function Ce(e,n=!0){let r=(await Ze(`${e} ${D(n?"[Y/n]":"[y/N]")}`)).toLowerCase();return r?r.startsWith("y"):n}class sn extends Error{}import{existsSync as Ro,mkdirSync as Cn,readFileSync as er,writeFileSync as tr}from"node:fs";import{homedir as nr}from"node:os";import{join as et}from"node:path";function tt(){if(process.env.DOCKITO_CONFIG_DIR)return process.env.DOCKITO_CONFIG_DIR;if(process.platform==="win32"&&process.env.APPDATA)return et(process.env.APPDATA,"dockito");return et(process.env.XDG_CONFIG_HOME||et(nr(),".config"),"dockito")}var cn=()=>et(tt(),"state.json");function ln(){try{return JSON.parse(er(cn(),"utf8"))}catch{return{}}}function At(e){return ln().sites?.[e]??{}}function rr(e,n){let r=ln();r.sites??={};let t=r.sites[e]??={};n(t),Cn(tt(),{recursive:!0,mode:448}),tr(cn(),JSON.stringify(r,null,2)+`
10
- `)}var Lt=(e,n,r,t)=>rr(e,(s)=>{if((s.entries??={})[n]=r,t)(s.hashes??={})[n]=t});import{existsSync as fr,readFileSync as wn,writeFileSync as kn}from"node:fs";import{dirname as hn,join as yn,relative as dr,resolve as bn,sep as pr}from"node:path";function Ee(e,n=!1){let r=e.length,t=0,s="",o=0,c=16,i=0,l=0,f=0,u=0,p=0;function g(m,E){let b=0,y=0;while(b<m||!E){let d=e.charCodeAt(t);if(d>=48&&d<=57)y=y*16+d-48;else if(d>=65&&d<=70)y=y*16+d-65+10;else if(d>=97&&d<=102)y=y*16+d-97+10;else break;t++,b++}if(b<m)y=-1;return y}function v(m){t=m,s="",o=0,c=16,p=0}function A(){let m=t;if(e.charCodeAt(t)===48)t++;else{t++;while(t<e.length&&ye(e.charCodeAt(t)))t++}if(t<e.length&&e.charCodeAt(t)===46)if(t++,t<e.length&&ye(e.charCodeAt(t))){t++;while(t<e.length&&ye(e.charCodeAt(t)))t++}else return p=3,e.substring(m,t);let E=t;if(t<e.length&&(e.charCodeAt(t)===69||e.charCodeAt(t)===101)){if(t++,t<e.length&&e.charCodeAt(t)===43||e.charCodeAt(t)===45)t++;if(t<e.length&&ye(e.charCodeAt(t))){t++;while(t<e.length&&ye(e.charCodeAt(t)))t++;E=t}else p=3}return e.substring(m,E)}function j(){let m="",E=t;while(!0){if(t>=r){m+=e.substring(E,t),p=2;break}let b=e.charCodeAt(t);if(b===34){m+=e.substring(E,t),t++;break}if(b===92){if(m+=e.substring(E,t),t++,t>=r){p=2;break}switch(e.charCodeAt(t++)){case 34:m+='"';break;case 92:m+="\\";break;case 47:m+="/";break;case 98:m+="\b";break;case 102:m+="\f";break;case 110:m+=`
11
- `;break;case 114:m+="\r";break;case 116:m+="\t";break;case 117:let d=g(4,!0);if(d>=0)m+=String.fromCharCode(d);else p=4;break;default:p=5}E=t;continue}if(b>=0&&b<=31)if(Ne(b)){m+=e.substring(E,t),p=2;break}else p=6;t++}return m}function T(){if(s="",p=0,o=t,l=i,u=f,t>=r)return o=r,c=17;let m=e.charCodeAt(t);if(St(m)){do t++,s+=String.fromCharCode(m),m=e.charCodeAt(t);while(St(m));return c=15}if(Ne(m)){if(t++,s+=String.fromCharCode(m),m===13&&e.charCodeAt(t)===10)t++,s+=`
12
- `;return i++,f=t,c=14}switch(m){case 123:return t++,c=1;case 125:return t++,c=2;case 91:return t++,c=3;case 93:return t++,c=4;case 58:return t++,c=6;case 44:return t++,c=5;case 34:return t++,s=j(),c=10;case 47:let E=t-1;if(e.charCodeAt(t+1)===47){t+=2;while(t<r){if(Ne(e.charCodeAt(t)))break;t++}return s=e.substring(E,t),c=12}if(e.charCodeAt(t+1)===42){t+=2;let b=r-1,y=!1;while(t<b){let d=e.charCodeAt(t);if(d===42&&e.charCodeAt(t+1)===47){t+=2,y=!0;break}if(t++,Ne(d)){if(d===13&&e.charCodeAt(t)===10)t++;i++,f=t}}if(!y)t++,p=1;return s=e.substring(E,t),c=13}return s+=String.fromCharCode(m),t++,c=16;case 45:if(s+=String.fromCharCode(m),t++,t===r||!ye(e.charCodeAt(t)))return c=16;case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return s+=A(),c=11;default:while(t<r&&P(m))t++,m=e.charCodeAt(t);if(o!==t){switch(s=e.substring(o,t),s){case"true":return c=8;case"false":return c=9;case"null":return c=7}return c=16}return s+=String.fromCharCode(m),t++,c=16}}function P(m){if(St(m)||Ne(m))return!1;switch(m){case 125:case 93:case 123:case 91:case 34:case 58:case 44:case 47:return!1}return!0}function L(){let m;do m=T();while(m>=12&&m<=15);return m}return{setPosition:v,getPosition:()=>t,scan:n?L:T,getToken:()=>c,getTokenValue:()=>s,getTokenOffset:()=>o,getTokenLength:()=>t-o,getTokenStartLine:()=>l,getTokenStartCharacter:()=>o-u,getTokenError:()=>p}}function St(e){return e===32||e===9}function Ne(e){return e===10||e===13}function ye(e){return e>=48&&e<=57}var an;(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"})(an||(an={}));var z=Array(20).fill(0).map((e,n)=>" ".repeat(n));var Nt={" ":{"\n":Array(200).fill(0).map((e,n)=>`
13
- `+" ".repeat(n)),"\r":Array(200).fill(0).map((e,n)=>"\r"+" ".repeat(n)),"\r\n":Array(200).fill(0).map((e,n)=>`\r
14
- `+" ".repeat(n))},"\t":{"\n":Array(200).fill(0).map((e,n)=>`
15
- `+"\t".repeat(n)),"\r":Array(200).fill(0).map((e,n)=>"\r"+"\t".repeat(n)),"\r\n":Array(200).fill(0).map((e,n)=>`\r
16
- `+"\t".repeat(n))}},un=[`
17
- `,"\r",`\r
18
- `];function Et(e,n,r){let t,s,o,c,i;if(n){c=n.offset,i=c+n.length,o=c;while(o>0&&!Ie(e,o-1))o--;let b=i;while(b<e.length&&!Ie(e,b))b++;s=e.substring(o,b),t=sr(s,r)}else s=e,t=0,o=0,c=0,i=e.length;let l=ir(r,e),f=un.includes(l),u=0,p=0,g;if(r.insertSpaces)g=z[r.tabSize||4]??be(z[1],r.tabSize||4);else g="\t";let v=g==="\t"?"\t":" ",A=Ee(s,!1),j=!1;function T(){if(u>1)return be(l,u)+be(g,t+p);let b=g.length*(t+p);if(!f||b>Nt[v][l].length)return l+be(g,t+p);if(b<=0)return l;return Nt[v][l][b]}function P(){let b=A.scan();u=0;while(b===15||b===14){if(b===14&&r.keepLines)u+=1;else if(b===14)u=1;b=A.scan()}return j=b===16||A.getTokenError()!==0,b}let L=[];function m(b,y,d){if(!j&&(!n||y<i&&d>c)&&e.substring(y,d)!==b)L.push({offset:y,length:d-y,content:b})}let E=P();if(r.keepLines&&u>0)m(be(l,u),0,0);if(E!==17){let b=A.getTokenOffset()+o,y=g.length*t<20&&r.insertSpaces?z[g.length*t]:be(g,t);m(y,o,b)}while(E!==17){let b=A.getTokenOffset()+A.getTokenLength()+o,y=P(),d="",H=!1;while(u===0&&(y===12||y===13)){let Y=A.getTokenOffset()+o;m(z[1],b,Y),b=A.getTokenOffset()+A.getTokenLength()+o,H=y===12,d=H?T():"",y=P()}if(y===2){if(E!==1)p--;if(r.keepLines&&u>0||!r.keepLines&&E!==1)d=T();else if(r.keepLines)d=z[1]}else if(y===4){if(E!==3)p--;if(r.keepLines&&u>0||!r.keepLines&&E!==3)d=T();else if(r.keepLines)d=z[1]}else{switch(E){case 3:case 1:if(p++,r.keepLines&&u>0||!r.keepLines)d=T();else d=z[1];break;case 5:if(r.keepLines&&u>0||!r.keepLines)d=T();else d=z[1];break;case 12:d=T();break;case 13:if(u>0)d=T();else if(!H)d=z[1];break;case 6:if(r.keepLines&&u>0)d=T();else if(!H)d=z[1];break;case 10:if(r.keepLines&&u>0)d=T();else if(y===6&&!H)d="";break;case 7:case 8:case 9:case 11:case 2:case 4:if(r.keepLines&&u>0)d=T();else if((y===12||y===13)&&!H)d=z[1];else if(y!==5&&y!==17)j=!0;break;case 16:j=!0;break}if(u>0&&(y===12||y===13))d=T()}if(y===17)if(r.keepLines&&u>0)d=T();else d=r.insertFinalNewline?l:"";let se=A.getTokenOffset()+o;m(d,b,se),E=y}return L}function be(e,n){let r="";for(let t=0;t<n;t++)r+=e;return r}function sr(e,n){let r=0,t=0,s=n.tabSize||4;while(r<e.length){let o=e.charAt(r);if(o===z[1])t++;else if(o==="\t")t+=s;else break;r++}return Math.floor(t/s)}function ir(e,n){for(let r=0;r<n.length;r++){let t=n.charAt(r);if(t==="\r"){if(r+1<n.length&&n.charAt(r+1)===`
19
- `)return`\r
20
- `;return"\r"}else if(t===`
21
- `)return`
22
- `}return e&&e.eol||`
23
- `}function Ie(e,n){return`\r
24
- `.indexOf(e.charAt(n))!==-1}var _e;(function(e){e.DEFAULT={allowTrailingComma:!1}})(_e||(_e={}));function fn(e,n=[],r=_e.DEFAULT){let t=null,s=[],o=[];function c(l){if(Array.isArray(s))s.push(l);else if(t!==null)s[t]=l}return _t(e,{onObjectBegin:()=>{let l={};c(l),o.push(s),s=l,t=null},onObjectProperty:(l)=>{t=l},onObjectEnd:()=>{s=o.pop()},onArrayBegin:()=>{let l=[];c(l),o.push(s),s=l,t=null},onArrayEnd:()=>{s=o.pop()},onLiteralValue:c,onError:(l,f,u)=>{n.push({error:l,offset:f,length:u})}},r),s[0]}function It(e,n=[],r=_e.DEFAULT){let t={type:"array",offset:-1,length:-1,children:[],parent:void 0};function s(l){if(t.type==="property")t.length=l-t.offset,t=t.parent}function o(l){return t.children.push(l),l}_t(e,{onObjectBegin:(l)=>{t=o({type:"object",offset:l,length:-1,parent:t,children:[]})},onObjectProperty:(l,f,u)=>{t=o({type:"property",offset:f,length:-1,parent:t,children:[]}),t.children.push({type:"string",value:l,offset:f,length:u,parent:t})},onObjectEnd:(l,f)=>{s(l+f),t.length=l+f-t.offset,t=t.parent,s(l+f)},onArrayBegin:(l,f)=>{t=o({type:"array",offset:l,length:-1,parent:t,children:[]})},onArrayEnd:(l,f)=>{t.length=l+f-t.offset,t=t.parent,s(l+f)},onLiteralValue:(l,f,u)=>{o({type:lr(l),offset:f,length:u,parent:t,value:l}),s(f+u)},onSeparator:(l,f,u)=>{if(t.type==="property"){if(l===":")t.colonOffset=f;else if(l===",")s(f)}},onError:(l,f,u)=>{n.push({error:l,offset:f,length:u})}},r);let i=t.children[0];if(i)delete i.parent;return i}function nt(e,n){if(!e)return;let r=e;for(let t of n)if(typeof t==="string"){if(r.type!=="object"||!Array.isArray(r.children))return;let s=!1;for(let o of r.children)if(Array.isArray(o.children)&&o.children[0].value===t&&o.children.length===2){r=o.children[1],s=!0;break}if(!s)return}else{let s=t;if(r.type!=="array"||s<0||!Array.isArray(r.children)||s>=r.children.length)return;r=r.children[s]}return r}function _t(e,n,r=_e.DEFAULT){let t=Ee(e,!1),s=[],o=0;function c(S){return S?()=>o===0&&S(t.getTokenOffset(),t.getTokenLength(),t.getTokenStartLine(),t.getTokenStartCharacter()):()=>!0}function i(S){return S?(F)=>o===0&&S(F,t.getTokenOffset(),t.getTokenLength(),t.getTokenStartLine(),t.getTokenStartCharacter()):()=>!0}function l(S){return S?(F)=>o===0&&S(F,t.getTokenOffset(),t.getTokenLength(),t.getTokenStartLine(),t.getTokenStartCharacter(),()=>s.slice()):()=>!0}function f(S){return S?()=>{if(o>0)o++;else if(S(t.getTokenOffset(),t.getTokenLength(),t.getTokenStartLine(),t.getTokenStartCharacter(),()=>s.slice())===!1)o=1}:()=>!0}function u(S){return S?()=>{if(o>0)o--;if(o===0)S(t.getTokenOffset(),t.getTokenLength(),t.getTokenStartLine(),t.getTokenStartCharacter())}:()=>!0}let p=f(n.onObjectBegin),g=l(n.onObjectProperty),v=u(n.onObjectEnd),A=f(n.onArrayBegin),j=u(n.onArrayEnd),T=l(n.onLiteralValue),P=i(n.onSeparator),L=c(n.onComment),m=i(n.onError),E=r&&r.disallowComments,b=r&&r.allowTrailingComma;function y(){while(!0){let S=t.scan();switch(t.getTokenError()){case 4:d(14);break;case 5:d(15);break;case 3:d(13);break;case 1:if(!E)d(11);break;case 2:d(12);break;case 6:d(16);break}switch(S){case 12:case 13:if(E)d(10);else L();break;case 16:d(1);break;case 15:case 14:break;default:return S}}}function d(S,F=[],Ae=[]){if(m(S),F.length+Ae.length>0){let me=t.getToken();while(me!==17){if(F.indexOf(me)!==-1){y();break}else if(Ae.indexOf(me)!==-1)break;me=y()}}}function H(S){let F=t.getTokenValue();if(S)T(F);else g(F),s.push(F);return y(),!0}function se(){switch(t.getToken()){case 11:let S=t.getTokenValue(),F=Number(S);if(isNaN(F))d(2),F=0;T(F);break;case 7:T(null);break;case 8:T(!0);break;case 9:T(!1);break;default:return!1}return y(),!0}function Y(){if(t.getToken()!==10)return d(3,[],[2,5]),!1;if(H(!1),t.getToken()===6){if(P(":"),y(),!ge())d(4,[],[2,5])}else d(5,[],[2,5]);return s.pop(),!0}function Je(){p(),y();let S=!1;while(t.getToken()!==2&&t.getToken()!==17){if(t.getToken()===5){if(!S)d(4,[],[]);if(P(","),y(),t.getToken()===2&&b)break}else if(S)d(6,[],[]);if(!Y())d(4,[],[2,5]);S=!0}if(v(),t.getToken()!==2)d(7,[2],[]);else y();return!0}function ee(){A(),y();let S=!0,F=!1;while(t.getToken()!==4&&t.getToken()!==17){if(t.getToken()===5){if(!F)d(4,[],[]);if(P(","),y(),t.getToken()===4&&b)break}else if(F)d(6,[],[]);if(S)s.push(0),S=!1;else s[s.length-1]++;if(!ge())d(4,[],[4,5]);F=!0}if(j(),!S)s.pop();if(t.getToken()!==4)d(8,[4],[]);else y();return!0}function ge(){switch(t.getToken()){case 3:return ee();case 1:return Je();case 10:return H(!0);default:return se()}}if(y(),t.getToken()===17){if(r.allowEmptyContent)return!0;return d(4,[],[]),!1}if(!ge())return d(4,[],[]),!1;if(t.getToken()!==17)d(9,[],[]);return!0}function lr(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 dn(e,n,r,t){let s=n.slice(),c=It(e,[]),i=void 0,l=void 0;while(s.length>0)if(l=s.pop(),i=nt(c,s),i===void 0&&r!==void 0)if(typeof l==="string")r={[l]:r};else r=[r];else break;if(!i){if(r===void 0)throw Error("Can not delete in empty document");return ae(e,{offset:c?c.offset:0,length:c?c.length:0,content:JSON.stringify(r)},t)}else if(i.type==="object"&&typeof l==="string"&&Array.isArray(i.children)){let f=nt(i,[l]);if(f!==void 0)if(r===void 0){if(!f.parent)throw Error("Malformed AST");let u=i.children.indexOf(f.parent),p,g=f.parent.offset+f.parent.length;if(u>0){let v=i.children[u-1];p=v.offset+v.length}else if(p=i.offset+1,i.children.length>1)g=i.children[1].offset;return ae(e,{offset:p,length:g-p,content:""},t)}else return ae(e,{offset:f.offset,length:f.length,content:JSON.stringify(r)},t);else{if(r===void 0)return[];let u=`${JSON.stringify(l)}: ${JSON.stringify(r)}`,p=t.getInsertionIndex?t.getInsertionIndex(i.children.map((v)=>v.children[0].value)):i.children.length,g;if(p>0){let v=i.children[p-1];g={offset:v.offset+v.length,length:0,content:","+u}}else if(i.children.length===0)g={offset:i.offset+1,length:0,content:u};else g={offset:i.offset+1,length:0,content:u+","};return ae(e,g,t)}}else if(i.type==="array"&&typeof l==="number"&&Array.isArray(i.children)){let f=l;if(f===-1){let u=`${JSON.stringify(r)}`,p;if(i.children.length===0)p={offset:i.offset+1,length:0,content:u};else{let g=i.children[i.children.length-1];p={offset:g.offset+g.length,length:0,content:","+u}}return ae(e,p,t)}else if(r===void 0&&i.children.length>=0){let u=l,p=i.children[u],g;if(i.children.length===1)g={offset:i.offset+1,length:i.length-2,content:""};else if(i.children.length-1===u){let v=i.children[u-1],A=v.offset+v.length,j=i.offset+i.length;g={offset:A,length:j-2-A,content:""}}else g={offset:p.offset,length:i.children[u+1].offset-p.offset,content:""};return ae(e,g,t)}else if(r!==void 0){let u,p=`${JSON.stringify(r)}`;if(!t.isArrayInsertion&&i.children.length>l){let g=i.children[l];u={offset:g.offset,length:g.length,content:p}}else if(i.children.length===0||l===0)u={offset:i.offset+1,length:0,content:i.children.length===0?p:p+","};else{let g=l>i.children.length?i.children.length:l,v=i.children[g-1];u={offset:v.offset+v.length,length:0,content:","+p}}return ae(e,u,t)}else throw Error(`Can not ${r===void 0?"remove":t.isArrayInsertion?"insert":"modify"} Array index ${f} as length is not sufficient`)}else throw Error(`Can not add ${typeof l!=="number"?"index":"property"} to parent of type ${i.type}`)}function ae(e,n,r){if(!r.formattingOptions)return[n];let t=rt(e,n),s=n.offset,o=n.offset+n.content.length;if(n.length===0||n.content.length===0){while(s>0&&!Ie(t,s-1))s--;while(o<t.length&&!Ie(t,o))o++}let c=Et(t,{offset:s,length:o-s},{...r.formattingOptions,keepLines:!1});for(let l=c.length-1;l>=0;l--){let f=c[l];t=rt(t,f),s=Math.min(s,f.offset),o=Math.max(o,f.offset+f.length),o+=f.content.length-f.length}let i=e.length-(t.length-o)-s;return[{offset:s,length:i,content:t.substring(s,o)}]}function rt(e,n){return e.substring(0,n.offset)+n.content+e.substring(n.offset+n.length)}var pn;(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"})(pn||(pn={}));var gn;(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"})(gn||(gn={}));var ot=fn;var mn;(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"})(mn||(mn={}));function st(e,n,r,t){return dn(e,n,r,t)}function it(e,n){let r=n.slice(0).sort((s,o)=>{let c=s.offset-o.offset;if(c===0)return s.length-o.length;return c}),t=e.length;for(let s=r.length-1;s>=0;s--){let o=r[s];if(o.offset+o.length<=t)e=rt(e,o);else throw Error("Overlapping edit");t=o.offset}return e}var ue="dockito.jsonc";function Rt(e=process.cwd()){for(let n=bn(e);;n=hn(n)){let r=yn(n,ue);if(fr(r)){let t=[],s=ot(wn(r,"utf8"),t,{allowTrailingComma:!0});if(t.length||!s||typeof s!=="object")throw Error(`${r} isn't valid JSONC.`);return{file:r,dir:n,config:s}}if(hn(n)===n)return null}}var gr=(e,n)=>dr(e.dir,bn(n)).split(pr).join("/");function oe(e,n,r){let t=wn(e.file,"utf8"),s=st(t,n,r,{formattingOptions:{insertSpaces:!0,tabSize:2}});kn(e.file,it(t,s));let o=e.config;for(let c of n.slice(0,-1))o=o[c]??={};o[n[n.length-1]]=r}var Dt=(e,n,r)=>oe(e,["docs",gr(e,n)],r);function $n(e,n){let r=["{"," // Where Dockito docs from this repo go. No secrets in this file."," // Run `dockito agent` to learn the CLI, `dockito tree` to see places.",` "workspace": ${JSON.stringify(n.workspace)},`,n.project?` "project": ${JSON.stringify(n.project)},`:' // "project": "my-project",',n.folder?` "folder": ${JSON.stringify(n.folder)},`:' // "folder": "Folder/Subfolder",',n.dir?` // \`dockito sync\` mirrors this folder into the project, subfolders as folders.
25
- "dir": ${JSON.stringify(n.dir)},`:' // "dir": "docs",'," // Local files and the docs they publish to. Filled in by Dockito.",' "docs": {}',"}",""],t=yn(e,ue);return kn(t,r.join(`
26
- `)),t}var Re=2,br=4,De=(e)=>e<1048576?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,Ut=[],lt=(e)=>{let n=/\.[^./]+$/.exec(e.toLowerCase())?.[0]??"";return Ut.find((r)=>r.extensions.includes(n))?.id??null},$r=(e)=>Fe(e).replace(/\.[^.]+$/,"").trim(),jr=(e)=>mr("sha256").update(e).digest("hex"),Pe=(e)=>e.split(yr).join("/");class de extends Error{code;data;constructor(e,n,r={}){super(n);this.code=e;this.data=r}}function at(e,n){let r=hr("git",e,{cwd:n,encoding:"utf8",maxBuffer:67108864});return r.error||r.status!==0?null:r.stdout}var Tr=(e=process.cwd())=>at(["rev-parse","--show-toplevel"],e)?.trim()||null;function vr(e){let n=at(["ls-files","-z","--cached","--others","--exclude-standard","--","."],e);return(n!==null?n.split("\x00").filter(Boolean):function t(s){return kr(B(e,s),{withFileTypes:!0}).flatMap((o)=>o.name.startsWith(".")||o.name==="node_modules"?[]:o.isDirectory()?t(B(s,o.name)):[Pe(B(s,o.name))])}("")).filter((t)=>lt(t)&&fe(B(e,t))&&vn(B(e,t)).isFile()).sort()}function xr(e,n){let r=[...e.matchAll(/\b(?:src|href)=["']([^"'#?]+)/gi)].map((t)=>t[1]);return[...new Set(r.filter((t)=>!/^([a-z]+:|\/\/|\/)/i.test(t)&&!lt(t)&&fe(B(n,t))))]}var ct=(e)=>e.level===null||e.level==="publish"||e.level==="everything",Or=(e,n)=>e.projects.find((r)=>r.slug===n)??e.projects.find((r)=>r.name.toLowerCase()===n.toLowerCase())??null,Ft=(e)=>e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");function Bt(e,n,r){let t=e.filter(ct),s=(i,l,f="")=>`dockito sync ${n==="."?"":n+" "}-w ${i} --to ${l}${f}`,o=t.flatMap((i)=>[...i.projects.map((l)=>({workspace:i.slug,project:l.slug,name:l.name,docs:l.docs,command:s(i.slug,l.slug)})),{workspace:i.slug,project:null,name:"a new project",docs:0,command:s(i.slug,"<name>"," --create")}]),c=Math.max(...o.map((i)=>i.command.length));throw new de("needs_place",[`${r} Ask the user which project it goes to, then run one of:`,...o.map((i)=>` ${i.command.padEnd(c)} ${D(i.project?`${i.name} · ${i.docs} doc${i.docs===1?"":"s"}`:i.name)}`),D("Subfolders become folders. `dockito tree` shows every place.")].join(`
27
- `),{choices:o})}async function ut(e,n,r){let{c:t,url:s}=e,o=xt()&&!r.json,c=Rt(),i=c?.dir??Tr()??process.cwd(),l=n?Pe(Pt(i,jn(n)))||".":c?.config.dir;if(l?.startsWith(".."))X(`${n} is outside ${c?ue:"this repo"} at ${i}.`);if(n&&c?.config.dir&&l!==c.config.dir)X(`This repo syncs ${c.config.dir} (${ue}). Change "dir" there to sync another folder.`);let f=!c?.config.dir||!c.config.project,{kinds:u,workspaces:p}=await t.tree();if(Ut=u,!p.some(ct))X("This login can't publish anywhere. Run `dockito login --update`.");let g=r.workspace??c?.config.workspace??At(s).workspace,v=g?p.find((a)=>a.slug===g):void 0;if(g&&!v)X(`This login can't reach workspace "${g}". \`dockito tree\` lists the ones it can.`);let A=r.to??(c?.config.project?[c.config.project,c.config.folder].filter(Boolean).join("/"):void 0);if(!l){let a=fe(B(i,"docs"))?"docs":Pe(Pt(i,process.cwd()))||".";if(o)l=Pe(Pt(i,jn(i,await Ze("Folder to sync",a))))||".";else if(!A)Bt(p,a,`Nothing says where ${a==="."?"this folder":a+"/"} goes yet.`);else l=a}if(!fe(B(i,l))||!vn(B(i,l)).isDirectory())X(`${l} isn't a folder.`);if(!v){let a=p.filter(ct);if(a.length===1)v=a[0];else if(o)v=a[await Ot("Workspace",a.map((k)=>({label:k.name,hint:k.slug})))];else Bt(p,l,"Pick a workspace and project.")}if(!ct(v))X(`This login has View access in ${v.slug}; syncing needs Publish. Run \`dockito login --update\`.`);let j=v,T,P;if(A){if([T,...P]=A.split("/").filter(Boolean),!T)X("--to takes a project, or project/folder.")}else if(o){let a=Fe(i),k=[...j.projects.map((I)=>({label:I.name,hint:`${I.slug} · ${I.docs} doc${I.docs===1?"":"s"}`})),{label:"New project",hint:""}],h=j.projects.findIndex((I)=>I.slug===Ft(a)),w=await Ot("Project",k,h>=0?h:k.length-1);T=w<j.projects.length?j.projects[w].slug:await Ze("Project name",a),P=[]}else return Bt(p,l,`Nothing says where ${l==="."?"this folder":l+"/"} goes yet.`);let L=Or(j,T),m=!L;if(m&&!r.create&&!(o&&!A))throw new de("no_project",`No project "${T}" in ${j.slug}. Projects: ${j.projects.map((a)=>a.slug).join(", ")||"none"}.
28
- To make it, run again with --create.`,{workspace:j.slug,projects:j.projects.map((a)=>a.slug)});if(P.length>Re)X(`Folders go at most ${Re} deep inside a project.`);let E=B(i,l),b=vr(E);if(!b.length)X(`No ${Ut.flatMap((a)=>a.extensions).join(", ")} files in ${l}.`);let y=Object.entries(c?.config.map??{}).map(([a,k])=>({from:a.replace(/\/\*\*$/,""),to:k.split("/").filter(Boolean)})),d=Re-P.length,H=b.map((a)=>{let k=a.split("/").slice(0,-1),h=y.filter((w)=>a.startsWith(w.from+"/")).sort((w,I)=>I.from.length-w.from.length)[0];return{f:a,folder:h?h.to:k}}),se=null,Y=H.filter((a)=>a.folder.length>d);if(Y.length){let a={};for(let w of Y){let I=w.folder.slice(0,d);a[[...I,w.folder[d]].join("/")+"/**"]=I.join("/")}let k=Object.entries(a).map(([w,I])=>` "${w}": "${I||"(the project's top)"}"`);if(!(r.fold||o&&(Le(`${Y.length} file${Y.length===1?" is":"s are"} more than ${Re} folders deep. Dockito would put them here:
29
- ${k.join(`
30
- `)}`),await Ce("Fold them like that?"))))throw new de("too_deep",`${Y.length} file${Y.length===1?" is":"s are"} more than ${Re} folders deep inside the project. Run again with --fold to put them here (kept in ${ue} as "map"):
31
- ${k.join(`
32
- `)}`,{map:a});for(let w of Y)w.folder=w.folder.slice(0,d);se=a}let Je=m?[]:await t.places(j.id),ee=new Map,ge=new Map;if(L){let a=new Map(L.folders.map((h)=>[h.id,h])),k=(h)=>{let w=a.get(h);return[...w.parentId?k(w.parentId):[],w.name]};for(let h of L.folders){let w=k(h.id);ee.set(w.join("/").toLowerCase(),h.id),ge.set(h.id,w.join("/"))}}let S=(a)=>a.projectId!==L?.id?a.projectId?"another project":"Docs":a.folderId?ge.get(a.folderId)??"a folder":"the project's top",F=At(s),Ae=at(["rev-parse","HEAD"],i)?.trim()??null,me=new Set((at(["status","--porcelain","-z","--",l],i)??"").split("\x00").map((a)=>a.slice(3)).filter(Boolean)),yt=new Set,ie=[];for(let{f:a,folder:k}of H){let h=[...P,...k],w=Pe(B(l,a)),I=B(E,a),te=new Uint8Array(Tn(I)),ne=lt(a),G={path:w,name:Fe(a),folder:h,kind:ne,bytes:te,hash:jr(te),action:"new",doc:null,base:null,stays:null,assets:[]};if(ne==="html")G.assets=xr(new TextDecoder().decode(te),B(E,a,".."));let Yn=h.length?ee.get(h.join("/").toLowerCase())??"missing":null,Ke=c?.config.docs?.[w],ce=Ke?Je.find((q)=>q.slug===Ke):void 0;if(Ke&&!ce)try{let q=await t.doc(Ke);G.doc={id:q.doc.id,slug:q.doc.slug},G.stays=`${q.workspace.slug}${q.project?"/"+q.project.slug:""}`}catch(q){if(!(q instanceof Se)||q.code!=="not_found"&&q.code!=="deleted")throw q}if(ce){if(G.doc={id:ce.id,slug:ce.slug},ce.projectId!==L?.id||(ce.folderId??null)!==Yn)G.stays=S(ce)}if(G.doc)yt.add(G.doc.id),G.base=F.entries?.[G.doc.id]??null,G.action=F.hashes?.[G.doc.id]===G.hash?"same":"update";ie.push(G)}for(let a of ie){if(a.doc||!L)continue;let k=a.folder.length?ee.get(a.folder.join("/").toLowerCase()):null;if(k===void 0)continue;let h=Je.find((w)=>!yt.has(w.id)&&w.projectId===L.id&&(w.folderId??null)===k&&w.kind===a.kind&&w.title===$r(a.name));if(h)yt.add(h.id),a.doc={id:h.id,slug:h.slug},a.action="link"}let Ge=l==="."?"":l+"/",Yt=Object.entries(c?.config.docs??{}).filter(([a])=>a.startsWith(Ge)&&lt(a)&&!fe(B(i,a))),Q=[],Xt=new Set;for(let a of ie)for(let k=1;k<=a.folder.length;k++){let h=a.folder.slice(0,k),w=h.join("/").toLowerCase();if(!ee.has(w)&&!Xt.has(w))Xt.add(w),Q.push(h)}Q.sort((a,k)=>a.length-k.length);let Qt=ie.filter((a)=>a.bytes.length>j.maxFile);if(Qt.length)X(`Over the ${De(j.maxFile)} limit per file in ${j.slug}:
33
- ${Qt.map((a)=>` ${a.path} ${De(a.bytes.length)}`).join(`
34
- `)}`);let zn=`${j.slug}/${L?.slug??Ft(T)}${P.length?"/"+P.join("/"):""}`,V=ie.filter((a)=>a.action!=="same"),bt=ie.length-V.length,Hn=m||Q.length>0||ie.some((a)=>a.action==="link");if(!r.json){if(R(`${re(l==="."?"./":l+"/")} → ${re(zn)}`),m)R(` ${U("+ project")} ${T}`);for(let h of Q)R(` ${U("+ folder ")} ${h.join("/")}`);let a=V.length>15;if(a)for(let[h,w]of[["new",U("+ new ")],["link",le("= link ")]]){let I=new Map;for(let te of V.filter((ne)=>ne.action===h))I.set(te.folder.join("/"),[...I.get(te.folder.join("/"))??[],te.name]);for(let[te,ne]of I)R(` ${w} ${ne.length} in ${te||"the project's top"}: ${D(ne.slice(0,4).join(", ")+(ne.length>4?`, ${ne.length-4} more`:""))}`)}if(a&&V.some((h)=>h.action==="link"))R(D(" = link: a doc with the same name is already there; the file updates it"));for(let h of V){let w=h.path.slice(Ge.length),I=h.folder.join("/")!==[...P,...w.split("/").slice(0,-1)].join("/")?D(` → ${h.folder.join("/")||"the project's top"}`):"";if(h.action==="new")!a&&R(` ${U("+ new ")} ${w}${I}`);else if(h.action==="link")!a&&R(` ${le("= link ")} ${w} ${D(`updates the doc already there, ${t.linkOf(h.doc.slug)}`)}`);else R(` ${Qe("~ update ")} ${w}${h.stays?D(` (stays in ${h.stays}; moving comes later)`):""}`)}if(bt)R(D(` ${bt} unchanged`));if(V.length)R(D(` ${V.length} file${V.length===1?"":"s"} to send, ${De(V.reduce((h,w)=>h+w.bytes.length,0))}`));for(let[h,w]of Yt)R(` ${Xe("- gone ")} ${h.slice(Ge.length)} ${D(`file deleted; its doc stays, ${t.linkOf(w)}`)}`);let k=V.filter((h)=>h.assets.length);if(k.length)R(Qe(` ! ${k.length===1?k[0].path.slice(Ge.length):`${k.length} HTML files`} load${k.length===1?"s":""} ${[...new Set(k.flatMap((h)=>h.assets))].join(", ")} from next to ${k.length===1?"it":"them"}; only HTML files are published`))}let ze={workspace:j.slug,project:L?.slug??Ft(T),dir:l,create:{project:m?T:null,folders:Q.map((a)=>a.join("/"))},docs:V.map((a)=>({file:a.path,action:a.action,folder:a.folder.join("/")||null,link:a.doc?t.linkOf(a.doc.slug):null,stays:a.stays})),unchanged:bt,gone:Yt.map(([a,k])=>({file:a,link:t.linkOf(k)}))};if(!V.length&&!m&&!Q.length){if(r.json)R(JSON.stringify({...ze,applied:!0},null,2));else R(`${U("✓")} Up to date`);return Zt()}if(r["dry-run"]){if(r.json)R(JSON.stringify({...ze,applied:!1},null,2));return}if((Hn||f)&&!r.yes){if(!o){let a=["dockito sync",n?l:"",r.workspace?`-w ${j.slug}`:"",r.to?`--to ${r.to}`:"",r.create?"--create":"",r.fold?"--fold":"","--yes"].filter(Boolean).join(" ");if(r.json)return R(JSON.stringify({...ze,applied:!1,next:a},null,2));throw new de("confirm",`
35
- Nothing changed yet. Show the user this plan, then run: ${a}`,{next:a})}if(!await Ce("Apply?"))return Le(D("Nothing changed."))}Zt();function Zt(){if(!c){if($n(i,{workspace:j.slug,dir:l}),c=Rt(i),!r.json)Le(D(`Wrote ${ue}`))}if(c.config.workspace!==j.slug)oe(c,["workspace"],j.slug);if(c.config.dir!==l)oe(c,["dir"],l);if(!c.config.project&&L)oe(c,["project"],L.slug);if(!c.config.folder&&P.length)oe(c,["folder"],P.join("/"));if(se){for(let[a,k]of Object.entries(se))if(c.config.map?.[a]===void 0)oe(c,["map",a],k)}}if(m){let a=await t.createProject(j.id,T);if(L={id:a.id,slug:a.slug,name:T,docs:0,folders:[]},oe(c,["project"],a.slug),!r.json)R(`${U("✓")} Project ${T}`)}for(let a of Q){let k=a.length>1?ee.get(a.slice(0,-1).join("/").toLowerCase()):null,h=await t.createFolder(L.id,a[a.length-1],k);ee.set(a.join("/").toLowerCase(),h.id)}if(Q.length&&!r.json)R(`${U("✓")} ${Q.length} folder${Q.length===1?"":"s"}`);let he=[],He=null,$t=[...V],qe=!r.json&&xt(),qn=V.reduce((a,k)=>a+k.bytes.length,0),Ct=0,en=()=>qe&&process.stdout.write(`\r\x1B[2K${D(`Sending ${he.length} of ${V.length} · ${De(Ct)} of ${De(qn)}`)}`);en();async function Kn(a){let k=Ae&&!me.has(a.path)?Ae:null;for(;;)try{if(a.doc){let I=await t.update(a.doc.id,a.name,a.bytes,{commit:k,base:a.action==="link"?null:a.base});if(Lt(s,I.id,I.version,a.hash),a.action==="link")Dt(c,B(i,a.path),I.slug);return{file:a.path,action:a.action,link:t.linkOf(I.slug)}}let h=a.folder.length?ee.get(a.folder.join("/").toLowerCase()):null,w=await t.create(j.id,a.name,a.bytes,{project:L.id,folder:h});return Lt(s,w.id,w.version,a.hash),Dt(c,B(i,a.path),w.slug),{file:a.path,action:"new",link:t.linkOf(w.slug)}}catch(h){if(h instanceof Se&&h.code==="rate_limited"){if(!He){if(!r.json)Le(D("Upload limit reached; carrying on in a minute…"));He=new Promise((w)=>setTimeout(w,30000)).then(()=>void(He=null))}await He;continue}return{file:a.path,action:a.action,error:h instanceof Se?on(h,s):String(h)}}}if(await Promise.all(Array.from({length:Math.min(br,$t.length)},async()=>{for(let a=$t.shift();a;a=$t.shift()){let k=await Kn(a);if(he.push(k),Ct+=a.bytes.length,qe)process.stdout.write("\r\x1B[2K");if(k.error&&!r.json)R(`${Xe("✗")} ${k.file} ${Xe(k.error)}`);else if(!r.json&&!qe)R(`${U("✓")} ${k.file} ${D(k.link)}`);en()}})),qe)process.stdout.write("\r\x1B[2K");let jt=he.filter((a)=>a.error),tn=`${s}/${j.slug}/project/${L.slug}`;if(r.json)R(JSON.stringify({...ze,project:L.slug,applied:!0,link:tn,results:he},null,2));else R(`
36
- ${jt.length?Qe(`${he.length-jt.length} of ${he.length} sent`):U("✓ Synced")} ${D(tn)}`);if(jt.length)process.exitCode=1;if(f&&o)await Lr(i)}var Ar="\n## Dockito\n\nDocs here publish to Dockito (see `dockito.jsonc`). After changing them, run `dockito sync` (or `npx dockito@beta sync`).\nRun `dockito agent` first to learn the CLI; never guess where a doc goes.\n";async function Lr(e){let n=["AGENTS.md","CLAUDE.md"].map((r)=>B(e,r)).find(fe)??B(e,"AGENTS.md");if(fe(n)&&/dockito/i.test(Tn(n,"utf8")))return;if(!await Ce(`Add a Dockito note to ${Fe(n)} so AI agents know how to publish?`))return;wr(n,Ar),R(`${U("✓")} ${Fe(n)}`)}var Sr=!process.env.NO_COLOR&&process.stdout.isTTY,je=(e)=>(n)=>Sr?`\x1B[${e}m${n}\x1B[0m`:n,x=je("2"),pe=je("1"),M=je("32"),Be=je("31"),ft=je("33"),us=je("36"),O=(e="")=>process.stdout.write(e+`
37
- `),W=(e="")=>process.stderr.write(e+`
38
- `);class dt extends Error{}var N=(e)=>{throw new dt(e)},$e={view:"View",publish:"Publish",everything:"Everything"};function pt(e){let n=Math.round((Date.now()-new Date(e).getTime())/1000);if(n<60)return"just now";let r=Math.round(n/60);if(r<60)return`${r} min ago`;let t=Math.round(r/60);if(t<24)return`${t} h ago`;let s=Math.round(t/24);if(s<30)return s===1?"yesterday":`${s} days ago`;return new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}function Te(e){let n=(t)=>t.replace(/\x1b\[[0-9;]*m/g,""),r=e[0]?.map((t,s)=>Math.max(...e.map((o)=>n(o[s]??"").length)))??[];for(let t of e)O(t.map((s,o)=>o===t.length-1?s:s+" ".repeat(r[o]-n(s).length)).join(" ").trimEnd())}function Mt(e,n){switch(e.code){case"offline":return`Couldn't reach Dockito at ${n}.`;case"invalid_token":return"Your login has ended or was signed out. Run `dockito login`.";case"token_level":return`This needs ${$e[e.detail.needs]??e.detail.needs} access; this login has ${$e[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"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`Dockito said: ${e.code}`}}import{existsSync as ps,mkdirSync as Nr,readFileSync as Er,writeFileSync as Ir}from"node:fs";import{homedir as _r}from"node:os";import{join as gt}from"node:path";var Rr="https://dockito-app.dockito.workers.dev";function xn(){if(process.env.DOCKITO_CONFIG_DIR)return process.env.DOCKITO_CONFIG_DIR;if(process.platform==="win32"&&process.env.APPDATA)return gt(process.env.APPDATA,"dockito");return gt(process.env.XDG_CONFIG_HOME||gt(_r(),".config"),"dockito")}var On=()=>gt(xn(),"state.json");function An(){try{return JSON.parse(Er(On(),"utf8"))}catch{return{}}}function Z(e){return An().sites?.[e]??{}}function mt(e,n){let r=An();r.sites??={};let t=r.sites[e]??={};n(t),Nr(xn(),{recursive:!0,mode:448}),Ir(On(),JSON.stringify(r,null,2)+`
39
- `)}var Ln=()=>(process.env.DOCKITO_URL||Rr).replace(/\/+$/,""),Sn=(e)=>new URL(e).host;var Nn=(e,n,r,t)=>mt(e,(s)=>{if((s.entries??={})[n]=r,t)(s.hashes??={})[n]=t});import{spawnSync as Dr}from"node:child_process";import{chmodSync as Pr,existsSync as Vt,mkdirSync as Fr,readFileSync as Br,rmSync as En,writeFileSync as Ur}from"node:fs";import{dirname as Mr,join as Vr}from"node:path";var ve="dockito";function xe(e,n,r){try{let t=Dr(e,n,{input:r,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return t.error?null:t}catch{return null}}var Ue=(e)=>Vr(tt(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function Wr(e,n){if(process.platform==="darwin"){let r=xe("security",["-i"],`add-generic-password -U -s ${ve} -a ${e} -l Dockito -w ${n}
40
- `);return!!r&&r.status===0&&!/error/i.test(r.stderr??"")}if(process.platform==="linux"){let r=xe("secret-tool",["store","--label=Dockito","service",ve,"account",e],n);return!!r&&r.status===0}return!1}function Jr(e){let n=process.platform==="darwin"?xe("security",["find-generic-password","-s",ve,"-a",e,"-w"]):process.platform==="linux"?xe("secret-tool",["lookup","service",ve,"account",e]):null;return(n?.status===0?n.stdout.trim():"")||null}function Gr(e){if(process.platform==="darwin")xe("security",["delete-generic-password","-s",ve,"-a",e]);else if(process.platform==="linux")xe("secret-tool",["clear","service",ve,"account",e])}function In(e,n){if(Wr(e,n)){if(Vt(Ue(e)))En(Ue(e));return"keychain"}let r=Ue(e);return Fr(Mr(r),{recursive:!0,mode:448}),Ur(r,n,{mode:384}),Pr(r,384),"file"}function ht(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let n=Jr(e);if(n)return n;let r=Ue(e);return Vt(r)?Br(r,"utf8").trim()||null:null}function _n(e){Gr(e);let n=Ue(e);if(Vt(n))En(n)}import{existsSync as zr,readFileSync as Dn,writeFileSync as Pn}from"node:fs";import{dirname as Rn,join as Fn,relative as Hr,resolve as Bn,sep as qr}from"node:path";var Me="dockito.jsonc";function C(e=process.cwd()){for(let n=Bn(e);;n=Rn(n)){let r=Fn(n,Me);if(zr(r)){let t=[],s=ot(Dn(r,"utf8"),t,{allowTrailingComma:!0});if(t.length||!s||typeof s!=="object")throw Error(`${r} isn't valid JSONC.`);return{file:r,dir:n,config:s}}if(Rn(n)===n)return null}}var wt=(e,n)=>Hr(e.dir,Bn(n)).split(qr).join("/");function Kr(e,n,r){let t=Dn(e.file,"utf8"),s=st(t,n,r,{formattingOptions:{insertSpaces:!0,tabSize:2}});Pn(e.file,it(t,s));let o=e.config;for(let c of n.slice(0,-1))o=o[c]??={};o[n[n.length-1]]=r}var Un=(e,n,r)=>Kr(e,["docs",wt(e,n)],r);function Mn(e,n){let r=["{"," // Where Dockito docs from this repo go. No secrets in this file."," // Run `dockito agent` to learn the CLI, `dockito tree` to see places.",` "workspace": ${JSON.stringify(n.workspace)},`,n.project?` "project": ${JSON.stringify(n.project)},`:' // "project": "my-project",',n.folder?` "folder": ${JSON.stringify(n.folder)},`:' // "folder": "Folder/Subfolder",',n.dir?` // \`dockito sync\` mirrors this folder into the project, subfolders as folders.
41
- "dir": ${JSON.stringify(n.dir)},`:' // "dir": "docs",'," // Local files and the docs they publish to. Filled in by Dockito.",' "docs": {}',"}",""],t=Fn(e,Me);return Pn(t,r.join(`
42
- `)),t}var zt="0.0.1-beta.2",_=Ln(),We=Sn(_),Ht=`dockito-cli/${zt} (${process.platform})`;function J(){let e=ht(We);if(!e)N("Not logged in. Run `dockito login`.");return Ye({url:_,token:e,source:"cli",userAgent:Ht})}function Jn(e){let[n,r]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let t=Yr(n,r,{stdio:"ignore",detached:!0});return t.on("error",()=>{}),t.unref(),!0}catch{return!1}}var ro=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),oo=(e)=>new Promise((n)=>setTimeout(n,e)),so=()=>eo().replace(/\.local$/,"").slice(0,60)||"Unknown device";function qt(e,n,r){let t=n??r?.config.workspace??Z(_).workspace;if(t){let o=e.workspaces.find((c)=>c.slug===t);if(!o)N(`This login can't reach workspace "${t}". \`dockito whoami\` lists the ones it can.`);return o}if(e.workspaces.length===1)return e.workspaces[0];let s=e.workspaces.find((o)=>o.kind==="personal"&&o.role==="owner");if(s)return s;return N(`Pick a workspace with --workspace, or set one with \`dockito switch\`: ${e.workspaces.map((o)=>o.slug).join(", ")}`)}async function Kt(e,n,r){if(!r)return{project:null,folder:null,label:""};let[t,...s]=r.split("/").filter(Boolean),o=await e.projects(n),c=o.find((f)=>f.slug===t)??o.find((f)=>f.name.toLowerCase()===t.toLowerCase());if(!c)return N(`No project "${t}". Projects: ${o.map((f)=>f.slug).join(", ")||"none"}`);let i=null,l=null;for(let f of s){let u=c.folders.find((p)=>p.parentId===i&&p.name.toLowerCase()===f.toLowerCase());if(!u)return N(`No folder "${f}" in ${c.slug}${i?" at that level":""}.`);l=i=u.id}return{project:c.id,folder:l,label:[c.slug,...s].join("/")}}var io=/^[a-z0-9]{12}$/;function Oe(e,n){let t=(/^https?:\/\//.test(e)?new URL(e).pathname.split("/").filter(Boolean).pop()??"":e).replace(/\.(md|html)$/,"");if(io.test(t)&&/\d/.test(t))return t;if(Jt(e)){let s=(n&&n.config.docs?.[wt(n,e)])??Z(_).files?.[Ve(e)];if(s)return s;N(`${e} hasn't been published yet. Run \`dockito publish ${e}\`.`)}return N(`"${e}" isn't a doc link, code or published file.`)}function Wt(e,n,r){if(e&&Ve(n).startsWith(e.dir))Un(e,n,r);else mt(_,(t)=>(t.files??={})[Ve(n)]=r)}var kt=(e,n,r)=>Nn(_,e,n,r&&Xr("sha256").update(r).digest("hex"));function co(e){let n=to(Ve(e)),r=Vn("git",["rev-parse","HEAD"],{cwd:n,encoding:"utf8"});if(r.error||r.status!==0)return null;let t=Vn("git",["status","--porcelain","--",Gt(e)],{cwd:n,encoding:"utf8"});if(t.status!==0||t.stdout.trim())return null;return r.stdout.trim()}async function lo(e){let n=ht(We);if(e.update&&!n)N("Not logged in. Run `dockito login` first.");if(process.env.DOCKITO_TOKEN)W(ft("DOCKITO_TOKEN is set, so commands will keep using it instead of this login."));let r=Ye({url:_,token:e.update?n:null,userAgent:Ht}),t=await r.startLogin("Dockito CLI",so()),s=!e["no-browser"]&&ro()&&Jn(t.verification_uri_complete);O(s?`Opening ${t.verification_uri_complete}`:`Open ${pe(t.verification_uri)}`),O(`Code: ${pe(t.user_code)}`),W(x("Waiting for approval\u2026"));let o=t.interval*1000,c=Date.now()+t.expires_in*1000;while(Date.now()<c){await oo(o);try{let i=await r.pollLogin(t.device_code);if(!i)continue;let l=In(We,i.token);O(`${M("\u2713")} Logged in as ${i.user.name}${i.user.username?x(` (${i.user.username})`):""}`);for(let f of i.workspaces)O(` ${f.name} ${x(`${f.slug} \xB7 ${$e[f.level]}`)}`);if(l==="file")W(ft("No keychain found, so the token is in a file only you can read, in ~/.config/dockito."));return}catch(i){if(i instanceof K&&i.code==="access_denied")N("Denied in the browser.");if(i instanceof K&&i.code==="expired_token")N("The code expired. Run `dockito login` again.");if(i instanceof K&&i.code==="offline"){o=Math.min(o*2,30000);continue}throw i}}N("The code expired. Run `dockito login` again.")}async function ao(){let e=ht(We);if(!e)return O("Not logged in.");if(process.env.DOCKITO_TOKEN)N("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await Ye({url:_,token:e,userAgent:Ht}).logout()}catch(n){if(!(n instanceof K)||n.code!=="invalid_token"&&n.code!=="not_found")throw n}_n(We),O(`${M("\u2713")} Logged out`)}async function uo(e){let n=await J().whoami();if(e.json)return O(JSON.stringify(n,null,2));let r=Z(_).workspace;if(O(`${n.user.name} ${x(`${n.user.username} \xB7 ${n.user.email}`)}`),n.token)O(x(`${n.token.name} \xB7 ${n.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(n.token.expiresAt).toLocaleDateString()}`}`));if(O(),Te(n.workspaces.map((t)=>[t.slug===r?M("\u25CF"):" ",t.slug,t.name,x(t.level?$e[t.level]:t.role)])),_!=="https://dockito-app.dockito.workers.dev")W(x(`
43
- ${_}`))}async function fo(e){let n=await J().whoami();if(!e){let t=Z(_).workspace;return Te(n.workspaces.map((s)=>[s.slug===t?M("\u25CF"):" ",s.slug,s.name]))}let r=n.workspaces.find((t)=>t.slug===e);if(!r)N(`This login can't reach "${e}": ${n.workspaces.map((t)=>t.slug).join(", ")}`);mt(_,(t)=>t.workspace=r.slug),O(`${M("\u2713")} Commands now use ${r.name} ${x(r.slug)}`)}async function po(e){if(Jt(Me))N(`${Me} is already here.`);let n=J(),r=await n.whoami(),t=qt(r,e.workspace,null),[s,...o]=(e.to??"").split("/").filter(Boolean);if(s)await Kt(n,t.id,e.to);let c=Mn(process.cwd(),{workspace:t.slug,project:s,folder:o.join("/")||void 0});O(`${M("\u2713")} Wrote ${Gt(c)} ${x(`\u2192 ${[t.slug,s,...o].filter(Boolean).join("/")}`)}`)}async function go(e,n){if(!e.length)N("Name a file: `dockito publish notes.md`");if(n.doc&&e.length>1)N("--doc takes one file.");for(let i of e)if(!Jt(i)||!Zr(i).isFile())N(`${i} isn't a file.`);let r=J(),t=C(),s=null,o=[],c="";for(let i of e){let l=new Uint8Array(Qr(i)),f=Gt(i),u=n.new?null:n.doc?Oe(n.doc,t):(t&&t.config.docs?.[wt(t,i)])??Z(_).files?.[Ve(i)]??null,p=co(i);if(u){let T;try{T=await r.doc(u)}catch(m){if(m instanceof K&&(m.code==="deleted"||m.code==="not_found"))N(`${i} was published to ${r.linkOf(u)}, which ${m.code==="deleted"?"was deleted":"this login can't reach"}. Publish it as a new doc with --new.`);throw m}let P=n.force?null:Z(_).entries?.[T.doc.id]??null,L=await r.update(T.doc.id,f,l,{note:n.message,commit:p,base:P});if(kt(L.id,L.version,l),n.doc)Wt(t,i,L.slug);if(o.push({file:i,...L,link:r.linkOf(L.slug)}),!n.json)O(L.unchanged?`${x("=")} ${i} ${x("unchanged")} ${r.linkOf(L.slug)}`:`${M("\u2713")} ${i} \u2192 ${r.linkOf(L.slug)}${n.message?x(` \xB7 saved: ${n.message}`):""}`);continue}s??=await r.whoami();let g=qt(s,n.workspace,t),v=n.to??(t&&g.slug===t.config.workspace&&t.config.project?[t.config.project,t.config.folder].filter(Boolean).join("/"):void 0),A=await Kt(r,g.id,v),j=await r.create(g.id,f,l,{project:A.project,folder:A.folder});if(n.message)await r.save(j.id,j.version,n.message);if(kt(j.id,j.version,l),Wt(t,i,j.slug),o.push({file:i,...j,link:r.linkOf(j.slug),new:!0}),!n.json)O(`${M("\u2713")} ${i} \u2192 ${r.linkOf(j.slug)} ${x(`new in ${[g.slug,A.label||"Docs"].join("/")}`)}${n.message?x(` \xB7 saved: ${n.message}`):""}`);if(!v)c=t?.config.dir?"`dockito sync` puts files from "+t.config.dir+"/ in their folders.":"In the workspace's Docs. To put docs in a project: --to <project>; `dockito tree` lists them."}if(c&&!n.json)W(x(c));if(n.json)O(JSON.stringify(o,null,2))}async function mo(e,n){let r=J(),t=await r.whoami(),s=qt(t,n.workspace,C()),o=await Kt(r,s.id,e),[c,i]=await Promise.all([r.docs(s.id,{project:o.project,folder:o.folder}),e?Promise.resolve([]):r.projects(s.id)]),l=e?(await r.projects(s.id)).find((u)=>u.id===o.project)?.folders.filter((u)=>u.parentId===o.folder)??[]:[];if(n.json)return O(JSON.stringify({workspace:s.slug,place:o.label||null,projects:i,folders:l,docs:c},null,2));let f=[];for(let u of i)f.push([pe(`${u.slug}/`),u.name,x(`${u.docs} doc${u.docs===1?"":"s"}`),""]);for(let u of l)f.push([pe(`${u.name}/`),"",x(`${u.docs} doc${u.docs===1?"":"s"}`),""]);for(let u of c)f.push([u.slug,u.title+x(u.kind==="html"?" .html":" .md"),x(pt(u.updatedAt)),x(u.updatedByName??"")]);if(!f.length)return W(x(`Nothing in ${[s.slug,o.label].filter(Boolean).join("/")} yet.`));Te(f)}async function ho(e){if(!e)N("Name a doc: `dockito open <link, code or file>`");let n=`${_}/${Oe(e,C())}`;if(!Jn(n))O(n)}async function wo(e,n){if(!e)N("Name a doc: `dockito get <link, code or file> [-o file]`");let r=J(),t=C(),s=await r.doc(Oe(e,t)),o=n.entry?Number(n.entry):null;if(n.entry&&(!Number.isInteger(o)||o<1))N("--entry takes an entry number from `dockito history`.");let{bytes:c,entry:i}=await r.content(s.doc.id,o);if(!o)kt(s.doc.id,i,c);if(!n.output)return void process.stdout.write(c);if(Cr(n.output,c),!o)Wt(t,n.output,s.doc.slug);W(`${M("\u2713")} ${n.output} ${x(`\u2190 ${r.linkOf(s.doc.slug)}${o?` entry ${o}`:""}`)}`)}async function ko(e,n){if(!e)N("Name a doc: `dockito history <link, code or file>`");let r=await J().doc(Oe(e,C()));if(n.json)return O(JSON.stringify(r.versions,null,2));O(`${pe(r.doc.title)} ${x(`${_}/${r.doc.slug}`)}`),Te(r.versions.map((t,s)=>[String(t.number),s===0?M("current"):x(pt(t.createdAt)),s===0?x(pt(t.createdAt)):"",t.byName??"",x(t.source),t.savedAt?`${ft("saved")}${t.note?` ${t.note}`:""}`:"",t.gitCommit?x(t.gitCommit.slice(0,7)):""]))}async function yo(e,n){let r=Number(n);if(!e||!Number.isInteger(r)||r<1)N("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let t=J(),s=await t.doc(Oe(e,C())),o=Z(_).entries?.[s.doc.id]??s.versions[0]?.number??null,c=await t.restore(s.doc.id,r,o);kt(c.id,c.version),O(`${M("\u2713")} Restored entry ${r} as entry ${c.version} ${x(t.linkOf(c.slug))}`)}async function bo(e,n,r){if(!e)N('`dockito save <doc> [entry] -m "note"`');let t=J(),s=await t.doc(Oe(e,C())),o=n?Number(n):s.versions[0]?.number;if(!Number.isInteger(o)||o<1)N("Entries are in `dockito history`.");await t.save(s.doc.id,o,r.message??null),O(`${M("\u2713")} Saved entry ${o}${r.message?x(` \xB7 ${r.message}`):""}`)}async function $o(e){let n=await J().tree();if(e.json)return O(JSON.stringify(n,null,2));let r=n.workspaces,t=Z(_).workspace;for(let s of r){let o=(i)=>x(`${i} doc${i===1?"":"s"}`);O(`${s.slug===t?M("\u25CF"):" "} ${pe(s.slug)} ${s.name} ${x(`\xB7 ${s.level?$e[s.level]:s.role}`)}`);let c=[[x("Docs"),"",o(s.docs)]];for(let i of s.projects){c.push([i.slug,i.name,o(i.docs)]);let l=(f,u)=>{for(let p of i.folders.filter((g)=>g.parentId===f))c.push([`${u}/${p.name}`,"",o(p.docs)]),l(p.id,`${u}/${p.name}`)};l(null,i.slug)}Te(c.map((i)=>[" ",...i])),O()}W(x("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function jo(e,n){let[r,...t]=(e??"").split("/").filter(Boolean);if(!r)N("`dockito mkdir <project>[/folder[/subfolder]]`");if(t.length>2)N("Folders go at most 2 deep inside a project.");let s=J(),{workspaces:o}=await s.tree(),c=n.workspace??C()?.config.workspace??Z(_).workspace,i=c?o.find((g)=>g.slug===c):o.length===1?o[0]:o.find((g)=>g.kind==="personal"&&g.role==="owner")??null;if(!i)return N(c?`This login can't reach workspace "${c}".`:`Pick a workspace with -w: ${o.map((g)=>g.slug).join(", ")}`);let l=i.projects.find((g)=>g.slug===r)??i.projects.find((g)=>g.name.toLowerCase()===r.toLowerCase()),f=l?.id;if(!f){let g=await s.createProject(i.id,r);f=g.id,O(`${M("\u2713")} Project ${r} ${x(`${i.slug}/${g.slug}`)}`)}let u=null,p=!l;for(let g of t){let v=l?.folders.find((A)=>A.parentId===u&&A.name.toLowerCase()===g.toLowerCase());if(v){u=v.id;continue}u=(await s.createFolder(f,g,u)).id,l=void 0,p=!0,O(`${M("\u2713")} Folder ${g}`)}if(!p)W(x("Already there."))}var Gn=`Dockito hosts Markdown and HTML docs at a short link, with History.
2
+ import{spawn as qt,spawnSync as lt}from"node:child_process";import{createHash as zt}from"node:crypto";import{existsSync as Vt,readFileSync as dt,statSync as Yt,writeFileSync as Xt}from"node:fs";import{hostname as Qt}from"node:os";import{basename as Le,dirname as Zt,resolve as en}from"node:path";import{parseArgs as tn}from"node:util";var x=class extends Error{constructor(t,o,s={}){super(t);this.code=t;this.status=o;this.detail=s}code;status;detail};function pe(e){let n=e.url.replace(/\/+$/,"");async function t(r,a,c={}){let f=new URL(`${n}/api${a}`);for(let[j,b]of Object.entries(c.query??{}))b!=null&&b!==""&&f.searchParams.set(j,String(b));let w={},g=c.token===void 0?e.token:c.token;g&&(w.authorization=`Bearer ${g}`),e.userAgent&&(w["user-agent"]=e.userAgent);let u;c.json!==void 0?(w["content-type"]="application/json",u=JSON.stringify(c.json)):c.body&&(w["content-type"]="application/octet-stream",u=c.body);let k;try{k=await fetch(f,{method:r,headers:w,body:u})}catch{throw new x("offline",0)}return k}async function o(r){let a=await r.json().catch(()=>({}));if(!r.ok){let{error:c,...f}=a;throw new x(typeof c=="string"?c:`http_${r.status}`,r.status,f)}return a}let s=encodeURIComponent;return{url:n,linkOf:r=>`${n}/${r}`,startLogin:async(r,a)=>o(await t("POST","/device/code",{json:{client:r,device:a}})),pollLogin:async r=>{let a=await t("POST","/device/token",{json:{device_code:r},token:null}),c=await a.json().catch(()=>({}));if(a.ok)return c;if(c.error==="authorization_pending"||c.error==="slow_down")return null;throw new x(c.error??`http_${a.status}`,a.status)},logout:async()=>o(await t("DELETE","/tokens/current")),whoami:async()=>o(await t("GET","/whoami")),tree:async()=>o(await t("GET","/tree")),projects:async r=>(await o(await t("GET",`/workspaces/${s(r)}/projects`))).projects,docs:async(r,a={})=>(await o(await t("GET",`/workspaces/${s(r)}/docs`,{query:a}))).docs,files:async(r,a)=>(await o(await t("GET",`/workspaces/${s(r)}/files`,{query:{project:a}}))).docs,createProject:async(r,a)=>o(await t("POST",`/workspaces/${s(r)}/projects`,{json:{name:a}})),createFolder:async(r,a,c)=>o(await t("POST",`/projects/${s(r)}/folders`,{json:{name:a,parentId:c}})),recent:async r=>(await o(await t("GET",`/workspaces/${s(r)}/docs/recent`))).docs,doc:async r=>o(await t("GET",`/docs/by-code/${s(r)}`)),content:async(r,a)=>{let c=await t("GET",`/docs/${s(r)}/content`,{query:{v:a}});return c.ok||await o(c),{bytes:new Uint8Array(await c.arrayBuffer()),entry:Number(c.headers.get("x-doc-version"))}},create:async(r,a,c,f={})=>o(await t("POST",`/workspaces/${s(r)}/docs`,{query:{name:a,project:f.project,folder:f.folder,public:f.public?1:null,source:e.source},body:c})),update:async(r,a,c,f={})=>o(await t("POST",`/docs/${s(r)}/versions`,{query:{name:a,note:f.note,commit:f.commit,base:f.base,source:e.source},body:c})),restore:async(r,a,c)=>o(await t("POST",`/docs/${s(r)}/history/${a}/restore`,{query:{base:c}})),save:async(r,a,c)=>o(await t("PUT",`/docs/${s(r)}/history/${a}/saved`,{json:{note:c}})),makePrivate:async r=>o(await t("PATCH",`/docs/${s(r)}`,{json:{public:!1}})),rename:async(r,a)=>o(await t("PATCH",`/docs/${s(r)}`,{json:{title:a}}))}}import{emitKeypressEvents as $t}from"node:readline";import{createInterface as jt}from"node:readline/promises";var bt=!process.env.NO_COLOR&&process.stdout.isTTY,B=e=>n=>bt?`\x1B[${e}m${n}\x1B[0m`:n,p=B("2"),T=B("1"),y=B("32"),_=B("31"),I=B("33"),Se=B("36"),d=(e="")=>process.stdout.write(e+`
3
+ `),$=(e="")=>process.stderr.write(e+`
4
+ `),W=class extends Error{},m=e=>{throw new W(e)},H={view:"View",publish:"Publish",everything:"Everything"};function fe(e){let n=Math.round((Date.now()-new Date(e).getTime())/1e3);if(n<60)return"just now";let t=Math.round(n/60);if(t<60)return`${t} min ago`;let o=Math.round(t/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 Z(e){let n=o=>o.replace(/\x1b\[[0-9;]*m/g,""),t=e[0]?.map((o,s)=>Math.max(...e.map(r=>n(r[s]??"").length)))??[];for(let o of e)d(o.map((s,r)=>r===o.length-1?s:s+" ".repeat(t[r]-n(s).length)).join(" ").trimEnd())}function ee(e,n){switch(e.code){case"offline":return`Couldn't reach Dockito at ${n}.`;case"invalid_token":return"Your login has ended or was signed out. Run `dockito login`.";case"token_level":return`This needs ${H[e.detail.needs]??e.detail.needs} access; this login has ${H[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"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`Dockito said: ${e.code}`}}var Pe=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI,J=e=>process.stdout.write(e);function Xe(e,n,t=0){return new Promise((o,s)=>{let r=t,a=w=>{w||J(`\x1B[${n.length}A`);for(let[g,u]of n.entries())J(`\x1B[2K${g===r?Se("\u276F "+u.label):" "+u.label}${u.hint?p(" "+u.hint):""}
5
+ `)};J(`${y("?")} ${T(e)} ${p("(\u2191\u2193, Enter)")}
6
+ \x1B[?25l`),a(!0),$t(process.stdin),process.stdin.setRawMode(!0),process.stdin.resume();let c=()=>{process.stdin.off("keypress",f),process.stdin.setRawMode(!1),process.stdin.pause(),J("\x1B[?25h")},f=(w,g)=>{if(g.ctrl&&g.name==="c")return c(),J(`
7
+ `),s(new te);if(g.name==="up"||g.name==="k")r=(r-1+n.length)%n.length;else if(g.name==="down"||g.name==="j")r=(r+1)%n.length;else return g.name==="return"?(c(),J(`\x1B[${n.length+1}A\x1B[0J${y("?")} ${T(e)} ${Se(n[r].label)}
8
+ `),o(r)):void 0;a(!1)};process.stdin.on("keypress",f)})}async function ge(e,n=""){let t=jt({input:process.stdin,output:process.stdout});try{return(await t.question(`${y("?")} ${T(e)}${n?p(` (${n})`):""} `)).trim()||n}finally{t.close()}}async function Ae(e,n=!0){let t=(await ge(`${e} ${p(n?"[Y/n]":"[y/N]")}`)).toLowerCase();return t?t.startsWith("y"):n}var te=class extends Error{};import{homedir as vt}from"node:os";import{join as Ne}from"node:path";var xt="https://dockito-app.dockito.workers.dev";function Qe(){return process.env.DOCKITO_CONFIG_DIR?process.env.DOCKITO_CONFIG_DIR:process.platform==="win32"&&process.env.APPDATA?Ne(process.env.APPDATA,"dockito"):Ne(process.env.XDG_CONFIG_HOME||Ne(vt(),".config"),"dockito")}var Ze=()=>(process.env.DOCKITO_URL||xt).replace(/\/+$/,""),et=e=>new URL(e).host;import{spawnSync as Tt}from"node:child_process";import{chmodSync as Ot,existsSync as _e,mkdirSync as Dt,readFileSync as St,rmSync as tt,writeFileSync as Pt}from"node:fs";import{dirname as At,join as Nt}from"node:path";var G="dockito";function q(e,n,t){try{let o=Tt(e,n,{input:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return o.error?null:o}catch{return null}}var ne=e=>Nt(Qe(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function _t(e,n){if(process.platform==="darwin"){let t=q("security",["-i"],`add-generic-password -U -s ${G} -a ${e} -l Dockito -w ${n}
9
+ `);return!!t&&t.status===0&&!/error/i.test(t.stderr??"")}if(process.platform==="linux"){let t=q("secret-tool",["store","--label=Dockito","service",G,"account",e],n);return!!t&&t.status===0}return!1}function It(e){let n=process.platform==="darwin"?q("security",["find-generic-password","-s",G,"-a",e,"-w"]):process.platform==="linux"?q("secret-tool",["lookup","service",G,"account",e]):null;return(n?.status===0?n.stdout.trim():"")||null}function Et(e){process.platform==="darwin"?q("security",["delete-generic-password","-s",G,"-a",e]):process.platform==="linux"&&q("secret-tool",["clear","service",G,"account",e])}function nt(e,n){if(_t(e,n))return _e(ne(e))&&tt(ne(e)),"keychain";let t=ne(e);return Dt(At(t),{recursive:!0,mode:448}),Pt(t,n,{mode:384}),Ot(t,384),"file"}function he(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let n=It(e);if(n)return n;let t=ne(e);return _e(t)&&St(t,"utf8").trim()||null}function ot(e){Et(e);let n=ne(e);_e(n)&&tt(n)}var D=class extends Error{constructor(t,o,s={}){super(o);this.code=t;this.data=s}code;data},L=e=>e.level===null||e.level==="publish"||e.level==="everything";function me(e,n){let t=e.workspaces;if(n)return t.find(s=>s.slug===n)??m(`This login can't reach workspace "${n}". \`dockito tree\` lists the ones it can.`);let o=t.length===1?t[0]:t.find(s=>s.kind==="personal"&&s.role==="owner");if(o)return o;throw new D("needs_workspace",`Pick a workspace with -w: ${t.map(s=>s.slug).join(", ")}`,{workspaces:t.map(s=>s.slug)})}var z=(e,n)=>e.projects.find(t=>t.slug===n)??e.projects.find(t=>t.name.toLowerCase()===n.toLowerCase())??null;function oe(e){let n=new Map((e?.folders??[]).map(s=>[s.id,s])),t=s=>{let r=n.get(s);return[...r.parentId?t(r.parentId):[],r.name]},o=new Map((e?.folders??[]).map(s=>[t(s.id).join("/").toLowerCase(),s.id]));return{idOf:s=>s.length?o.get(s.join("/").toLowerCase()):null,pathOf:s=>s&&n.has(s)?t(s):[],add:(s,r)=>{o.set(s.join("/").toLowerCase(),r)}}}function Ie(e,n){if(!n)return{project:null,folder:null,label:`${e.slug} Docs`};let[t,...o]=n.split("/").filter(Boolean),s=z(e,t);if(!s)return m(`No project "${t}" in ${e.slug}. \`dockito tree\` lists them; \`dockito mkdir ${n}\` makes it.`);let r=oe(s).idOf(o);return r===void 0?m(`No folder "${o.join("/")}" in ${s.slug}. \`dockito mkdir ${s.slug}/${o.join("/")}\` makes it.`):{project:s.id,folder:r,label:[e.slug,s.slug,...o].join("/")}}var Ct=e=>e.fileName??`${e.title}.${e.kind}`,we=(e,n,t)=>e.filter(o=>(o.folderId??null)===n&&Ct(o).toLowerCase()===t.toLowerCase());import{createHash as Lt}from"node:crypto";import{spawnSync as Mt}from"node:child_process";import{existsSync as re,readFileSync as Ft,readdirSync as Rt,statSync as it}from"node:fs";import{basename as st,join as E,relative as Kt,resolve as Ee,sep as Ut}from"node:path";var ke=2,Wt=4,se=e=>e<1024*1024?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,Ht=e=>Lt("sha256").update(e).digest("hex"),at=e=>e.split(Ut).join("/"),V=(e,n)=>`${e} ${n}${e===1?"":"s"}`,Bt=(e,n)=>{let t=/\.[^./]+$/.exec(n.toLowerCase())?.[0]??"";return e.find(o=>o.extensions.includes(t))?.id??null};function ye(e,n){let t=Mt("git",e,{cwd:n,encoding:"utf8",maxBuffer:67108864});return t.error||t.status!==0?null:t.stdout}function Jt(e,n){let t=ye(["ls-files","-z","--cached","--others","--exclude-standard","--","."],e);return(t!==null?t.split("\0").filter(Boolean):(function s(r){return Rt(E(e,r),{withFileTypes:!0}).flatMap(a=>a.name.startsWith(".")||a.name==="node_modules"?[]:a.isDirectory()?s(E(r,a.name)):[at(E(r,a.name))])})("")).filter(s=>n(s)&&re(E(e,s))&&it(E(e,s)).isFile()).sort()}function Gt(e,n,t){let o=[...e.matchAll(/\b(?:src|href)=["']([^"'#?]+)/gi)].map(s=>s[1]);return[...new Set(o.filter(s=>!/^([a-z]+:|\/\/|\/)/i.test(s)&&!t(s)&&re(E(n,s))))]}function rt(e,n){let t=(r,a,c="")=>`dockito sync ${n} -w ${r} --to ${a}${c}`,o=e.workspaces.filter(L).flatMap(r=>[...r.projects.map(a=>({workspace:r.slug,project:a.slug,name:a.name,docs:a.docs,command:t(r.slug,a.slug)})),{workspace:r.slug,project:null,name:"a new project",docs:0,command:t(r.slug,"<name>"," --create")}]),s=Math.max(...o.map(r=>r.command.length));throw new D("needs_place",[`Which project should ${n==="."?"this folder":n+"/"} go to? Ask the user, then run one of:`,...o.map(r=>` ${r.command.padEnd(s)} ${p(r.project?`${r.name} \xB7 ${V(r.docs,"doc")}`:r.name)}`),p("Subfolders become folders. `dockito tree` shows every place.")].join(`
10
+ `),{choices:o})}async function ct(e,n,t){let o=Pe()&&!t.json,s=await e.tree(),r=i=>!!Bt(s.kinds,i);s.workspaces.some(L)||m("This login can't publish anywhere. Run `dockito login --update`.");let a=n;a||(o||rt(s,re("docs")?"docs":"."),a=await ge("Folder to sync",re("docs")?"docs":"."));let c=Ee(a);(!re(c)||!it(c).isDirectory())&&m(`${a} isn't a folder.`);let f=at(Kt(process.cwd(),c))||".",w;t.workspace&&(w=s.workspaces.find(i=>i.slug===t.workspace)??m(`This login can't reach workspace "${t.workspace}".`));let g=t.to;if(!g){o||rt(s,f);let i=(w?[w]:s.workspaces.filter(L)).flatMap(h=>[...h.projects.map(v=>({w:h,p:v.slug,label:v.name,hint:`${h.slug} \xB7 ${V(v.docs,"doc")}`})),{w:h,p:null,label:"New project",hint:h.slug}]),l=i[await Xe("Project",i)];w=l.w,g=l.p??await ge("Project name",st(c===process.cwd()?c:Ee(c,".."))),t.create||=!l.p}if(!w){let i=s.workspaces.filter(h=>L(h)&&z(h,g.split("/")[0])),l=s.workspaces.find(h=>h.kind==="personal"&&h.role==="owner");if(w=i.length===1?i[0]:s.workspaces.length===1?s.workspaces[0]:!i.length&&l?l:void 0,!w)throw new D("needs_workspace",`Which workspace? Run again with -w: ${(i.length?i:s.workspaces).map(h=>h.slug).join(", ")}`)}let u=w;L(u)||m(`This login has View access in ${u.slug}; syncing needs Publish. Run \`dockito login --update\`.`);let[k,...j]=g.split("/").filter(Boolean);k||m("--to takes a project, or project/folder."),j.length>ke&&m(`Folders go at most ${ke} deep inside a project.`);let b=z(u,k);if(!b&&!t.create)throw new D("no_project",`No project "${k}" in ${u.slug}. Projects: ${u.projects.map(i=>i.slug).join(", ")||"none"}.
11
+ To make it, run again with --create.`,{workspace:u.slug,projects:u.projects.map(i=>i.slug)});let R=Jt(c,r);R.length||m(`No ${s.kinds.flatMap(i=>i.extensions).join(", ")} files in ${f}.`);let ae=ke-j.length,Y=R.filter(i=>i.split("/").length-1>ae);if(Y.length&&!t.fold){let i=h=>h.split("/").slice(0,ae).join("/")||"the project's top",l=`${V(Y.length,"file")} ${Y.length===1?"is":"are"} more than ${ke} folders deep inside the project. With --fold they go in ${[...new Set(Y.map(i))].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 D("too_deep",`${l}
12
+ Ask the user, then run again with --fold.`,{files:Y});if($(l),!await Ae("Fold them like that?"))return $(p("Nothing changed."));t.fold=!0}let Re=i=>{let l=i.split("/");return{folder:[...j,...l.slice(0,-1).slice(0,ae)],dropped:l.slice(0,-1).slice(ae),file:l[l.length-1]}},$e=new Map;for(let i of R){let l=Re(i),h=[...l.folder,l.file].join("/").toLowerCase();$e.set(h,($e.get(h)??0)+1)}let Ke=b?await e.files(u.id,b.id):[],X=oe(b),M=R.map(i=>{let{folder:l,dropped:h,file:v}=Re(i),C=h.length&&$e.get([...l,v].join("/").toLowerCase())>1?[...h,v].join("-"):v,P=new Uint8Array(Ft(E(c,i))),U={rel:i,name:C,folder:l,bytes:P,hash:Ht(P),action:"new",doc:null,others:[],assets:[]};/\.html?$/i.test(i)&&(U.assets=Gt(new TextDecoder().decode(P),E(c,i,".."),r));let Ye=X.idOf(l);if(Ye===void 0)return U;let Q=we(Ke,Ye,C);return Q.length>1?{...U,action:"ambiguous",others:Q}:Q.length?{...U,doc:Q[0],action:Q[0].sha256===U.hash?"same":"update"}:U}),Ue=M.filter(i=>i.bytes.length>u.maxFile);Ue.length&&m(`Over the ${se(u.maxFile)} limit per file in ${u.slug}:
13
+ ${Ue.map(i=>` ${i.rel} ${se(i.bytes.length)}`).join(`
14
+ `)}`);let je=new Map;for(let i of M){let l=[...i.folder,i.name].join("/").toLowerCase();je.set(l,[...je.get(l)??[],i.rel])}let We=[...je.values()].filter(i=>i.length>1);We.length&&m(`These files would be the same doc; rename one of each:
15
+ ${We.map(i=>" "+i.join(" and ")).join(`
16
+ `)}`);let N=[],He=new Set;for(let i of M)for(let l=1;l<=i.folder.length;l++){let h=i.folder.slice(0,l),v=h.join("/").toLowerCase();X.idOf(h)===void 0&&!He.has(v)&&(He.add(v),N.push(h))}N.sort((i,l)=>i.length-l.length);let O=M.filter(i=>i.action==="new"||i.action==="update"),Be=M.filter(i=>i.action==="ambiguous"),ve=M.filter(i=>i.action==="same").length,mt=new Set(M.flatMap(i=>i.doc?[i.doc.id]:i.others.map(l=>l.id))),xe=Ke.filter(i=>!mt.has(i.id)).length,Je=`${u.slug}/${b?.slug??k}${j.length?"/"+j.join("/"):""}`,ce={workspace:u.slug,to:Je,create:{project:b?null:k,folders:N.map(i=>i.join("/"))},docs:O.map(i=>({file:i.rel,action:i.action,folder:i.folder.join("/")||null,name:i.name,link:i.doc?e.linkOf(i.doc.slug):null})),unchanged:ve,ambiguous:Be.map(i=>({file:i.rel,docs:i.others.map(l=>e.linkOf(l.slug))})),notHere:xe};if(!t.json){d(`${T(f==="."?"./":f+"/")} \u2192 ${T(Je)}`),b||d(` ${y("+ project")} ${k}`);for(let l of N)d(` ${y("+ folder ")} ${l.join("/")}`);if(O.length>15)for(let[l,h]of[["new",y("+ new ")],["update",I("~ update ")]]){let v=new Map;for(let C of O.filter(P=>P.action===l))v.set(C.folder.join("/"),[...v.get(C.folder.join("/"))??[],C.name]);for(let[C,P]of v)d(` ${h} ${P.length} in ${C||"the project's top"}: ${p(P.slice(0,4).join(", ")+(P.length>4?`, ${P.length-4} more`:""))}`)}else for(let l of O){let h=l.name!==st(l.rel)?p(` \u2192 ${[...l.folder,l.name].join("/")}`):"";d(` ${l.action==="new"?y("+ new "):I("~ update ")} ${l.rel}${h}`)}ve&&d(p(` ${ve} unchanged`));for(let l of Be)d(` ${_("? skip ")} ${l.rel} ${p(`${l.others.length} docs there have this name: ${l.others.map(h=>e.linkOf(h.slug)).join(", ")}`)}`);xe&&d(p(` ${V(xe,"doc")} in the project with no file here, left as they are`)),O.length&&d(p(` ${V(O.length,"file")} to send, ${se(O.reduce((l,h)=>l+h.bytes.length,0))}`));let i=O.filter(l=>l.assets.length);i.length&&d(I(` ! ${i.length===1?i[0].rel:`${i.length} HTML files`} load ${[...new Set(i.flatMap(l=>l.assets))].join(", ")} from next to ${i.length===1?"it":"them"}; only HTML files are published`))}if(!O.length&&b&&!N.length){t.json?d(JSON.stringify({...ce,applied:!0},null,2)):d(`${y("\u2713")} Up to date`);return}if(t["dry-run"]){t.json&&d(JSON.stringify({...ce,applied:!1},null,2));return}if((!b||N.length)&&!t.yes){let i=["dockito sync",f,`-w ${u.slug}`,`--to ${g}`,t.create&&!b?"--create":"",t.fold?"--fold":"","--yes"].filter(Boolean).join(" ");if(!o){if(t.json)return d(JSON.stringify({...ce,applied:!1,next:i},null,2));throw new D("confirm",`
17
+ Nothing changed yet. Show the user this plan, then run: ${i}`,{next:i})}if(!await Ae("Apply?"))return $(p("Nothing changed."))}if(!b){let i=await e.createProject(u.id,k);b={id:i.id,slug:i.slug,name:k,docs:0,folders:[]},t.json||d(`${y("\u2713")} Project ${k}`)}let Te=b;for(let i of N){let l=await e.createFolder(Te.id,i[i.length-1],i.length>1?X.idOf(i.slice(0,-1)):null);X.add(i,l.id)}N.length&&!t.json&&d(`${y("\u2713")} ${V(N.length,"folder")}`);let le=ye(["rev-parse","--show-toplevel"],c)?.trim()??null,Ge=le?ye(["rev-parse","HEAD"],le)?.trim()??null:null,wt=new Set(le?(ye(["status","--porcelain","-z","--","."],c)??"").split("\0").filter(Boolean).map(i=>Ee(le,i.slice(3))):[]),K=[],de=null,Oe=[...O],ue=!t.json&&Pe(),kt=O.reduce((i,l)=>i+l.bytes.length,0),qe=0,ze=()=>ue&&process.stdout.write(`\r\x1B[2K${p(`Sending ${K.length} of ${O.length} \xB7 ${se(qe)} of ${se(kt)}`)}`);async function yt(i){let l=Ge&&!wt.has(E(c,i.rel))?Ge:null;for(;;)try{let h=i.doc?await e.update(i.doc.id,i.name,i.bytes,{commit:l}):await e.create(u.id,i.name,i.bytes,{project:Te.id,folder:X.idOf(i.folder)??null});return{file:i.rel,action:i.action,link:e.linkOf(h.slug)}}catch(h){if(h instanceof x&&h.code==="rate_limited"){de||(t.json||$(p("\rUpload limit reached; carrying on in a minute\u2026")),de=new Promise(v=>setTimeout(v,3e4)).then(()=>{de=null})),await de;continue}return{file:i.rel,action:i.action,error:h instanceof x?ee(h,e.url):String(h)}}}ze(),await Promise.all(Array.from({length:Math.min(Wt,Oe.length)},async()=>{for(let i=Oe.shift();i;i=Oe.shift()){let l=await yt(i);K.push(l),qe+=i.bytes.length,ue&&process.stdout.write("\r\x1B[2K"),l.error&&!t.json?d(`${_("\u2717")} ${l.file} ${_(l.error)}`):!t.json&&!ue&&d(`${y("\u2713")} ${l.file} ${p(l.link)}`),ze()}})),ue&&process.stdout.write("\r\x1B[2K");let De=K.filter(i=>i.error),Ve=`${e.url}/${u.slug}/project/${Te.slug}`;t.json?d(JSON.stringify({...ce,applied:!0,link:Ve,results:K},null,2)):d(`${De.length?I(`${K.length-De.length} of ${K.length} sent`):y("\u2713 Synced")} ${p(Ve)}`),De.length&&(process.exitCode=1)}var Me="0.0.1-beta.4",A=Ze(),ie=et(A),Fe=`dockito-cli/${Me} (${process.platform})`;function S(){let e=he(ie);return e||m("Not logged in. Run `dockito login`."),pe({url:A,token:e,source:"cli",userAgent:Fe})}function ft(e){let[n,t]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let o=qt(n,t,{stdio:"ignore",detached:!0});return o.on("error",()=>{}),o.unref(),!0}catch{return!1}}var nn=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),on=e=>new Promise(n=>setTimeout(n,e)),sn=()=>Qt().replace(/\.local$/,"").slice(0,60)||"Unknown device",rn=/^[a-z0-9]{12}$/;function F(e){let t=(/^https?:\/\//.test(e)?new URL(e).pathname.split("/").filter(Boolean).pop()??"":e).replace(/\.[a-z]+$/,"");return rn.test(t)&&/\d/.test(t)?t:m(`"${e}" isn't a doc link or code. \`dockito ls\` lists docs with their codes.`)}function ut(e){let n=Zt(en(e)),t=lt("git",["rev-parse","HEAD"],{cwd:n,encoding:"utf8"});if(t.error||t.status!==0)return null;let o=lt("git",["status","--porcelain","--",Le(e)],{cwd:n,encoding:"utf8"});return o.status!==0||o.stdout.trim()?null:t.stdout.trim()}var an=e=>zt("sha256").update(e).digest("hex");async function cn(e){let n=he(ie);e.update&&!n&&m("Not logged in. Run `dockito login` first."),process.env.DOCKITO_TOKEN&&$(I("DOCKITO_TOKEN is set, so commands will keep using it instead of this login."));let t=pe({url:A,token:e.update?n:null,userAgent:Fe}),o=await t.startLogin("Dockito CLI",sn()),s=!e["no-browser"]&&nn()&&ft(o.verification_uri_complete);d(s?`Opening ${o.verification_uri_complete}`:`Open ${T(o.verification_uri)}`),d(`Code: ${T(o.user_code)}`),$(p("Waiting for approval\u2026"));let r=o.interval*1e3,a=Date.now()+o.expires_in*1e3;for(;Date.now()<a;){await on(r);try{let c=await t.pollLogin(o.device_code);if(!c)continue;let f=nt(ie,c.token);d(`${y("\u2713")} Logged in as ${c.user.name}${c.user.username?p(` (${c.user.username})`):""}`);for(let w of c.workspaces)d(` ${w.name} ${p(`${w.slug} \xB7 ${H[w.level]}`)}`);f==="file"&&$(I("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"&&m("Denied in the browser."),c instanceof x&&c.code==="expired_token"&&m("The code expired. Run `dockito login` again."),c instanceof x&&c.code==="offline"){r=Math.min(r*2,3e4);continue}throw c}}m("The code expired. Run `dockito login` again.")}async function ln(){let e=he(ie);if(!e)return d("Not logged in.");process.env.DOCKITO_TOKEN&&m("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await pe({url:A,token:e,userAgent:Fe}).logout()}catch(n){if(!(n instanceof x)||n.code!=="invalid_token"&&n.code!=="not_found")throw n}ot(ie),d(`${y("\u2713")} Logged out`)}async function dn(e){let n=await S().whoami();if(e.json)return d(JSON.stringify(n,null,2));d(`${n.user.name} ${p(`${n.user.username} \xB7 ${n.user.email}`)}`),n.token&&d(p(`${n.token.name} \xB7 ${n.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(n.token.expiresAt).toLocaleDateString()}`}`)),d(),Z(n.workspaces.map(t=>[t.slug,t.name,p(t.level?H[t.level]:t.role)])),A!=="https://dockito-app.dockito.workers.dev"&&$(p(`
18
+ ${A}`))}async function un(e,n){e.length||m("Name a file: `dockito publish notes.md --to <project/folder>`"),n.doc&&e.length>1&&m("--doc takes one file."),n.public&&n.doc&&m("--public is only for a new doc; an existing one can be made public from Share in the app.");for(let f of e)(!Vt(f)||!Yt(f).isFile())&&m(`${f} isn't a file.`);let t=S(),o=[];if(n.doc){let f=await t.doc(F(n.doc)),w=new Uint8Array(dt(e[0])),g=await t.update(f.doc.id,Le(e[0]),w,{note:n.message,commit:ut(e[0])});return n.json?d(JSON.stringify([{file:e[0],...g,link:t.linkOf(g.slug)}],null,2)):d(g.unchanged?`${p("=")} ${e[0]} ${p("unchanged")} ${t.linkOf(g.slug)}`:`${y("\u2713")} ${e[0]} \u2192 ${t.linkOf(g.slug)}`)}let s=await t.tree(),r=me(s,n.workspace);L(r)||m(`This login has View access in ${r.slug}. Run \`dockito login --update\`.`);let a=Ie(r,n.to),c=await t.files(r.id,a.project);for(let f of e){let w=new Uint8Array(dt(f)),g=Le(f),u=n.new?[]:we(c,a.folder,g);u.length>1&&m(`${u.length} docs in ${a.label} are named ${g}: ${u.map(R=>t.linkOf(R.slug)).join(", ")}. Pick one with --doc, or publish a new one with --new.`);let k=u[0];if(k&&n.public&&!(await t.doc(k.slug)).doc.public&&m(`${g} is already a private doc in ${a.label} (${t.linkOf(k.slug)}). Only Share in the app can make it public; \`--new --public\` publishes a new public doc.`),k&&k.sha256===an(w)&&!n.message){o.push({file:f,id:k.id,slug:k.slug,version:k.version,unchanged:!0,link:t.linkOf(k.slug)}),n.json||d(`${p("=")} ${f} ${p("unchanged")} ${t.linkOf(k.slug)}`);continue}let j=k?await t.update(k.id,g,w,{note:n.message,commit:ut(f)}):await t.create(r.id,g,w,{project:a.project,folder:a.folder,public:n.public});!k&&n.message&&await t.save(j.id,j.version,n.message);let b="public"in j&&j.public===!0;o.push({file:f,...j,link:t.linkOf(j.slug),new:!k}),n.json||d(`${y("\u2713")} ${f} \u2192 ${t.linkOf(j.slug)} ${p(k?j.unchanged?"unchanged":"updated":`new in ${a.label}`)}${b?` ${y("\xB7 public")}`:""}${n.message?p(` \xB7 saved: ${n.message}`):""}`)}n.json?d(JSON.stringify(o,null,2)):!n.to&&o.some(f=>f.new)&&$(p("In the workspace's Docs. To put docs in a project: --to <project/folder>; `dockito tree` lists them."))}async function pn(e){let n=await S().tree();if(e.json)return d(JSON.stringify(n,null,2));let t=o=>p(`${o} doc${o===1?"":"s"}`);for(let o of n.workspaces){d(`${T(o.slug)} ${o.name} ${p(`\xB7 ${o.level?H[o.level]:o.role}`)}`);let s=[[p("Docs"),"",t(o.docs)]];for(let r of o.projects){s.push([r.slug,r.name,t(r.docs)]);let a=(c,f)=>{for(let w of r.folders.filter(g=>g.parentId===c))s.push([`${f}/${w.name}`,"",t(w.docs)]),a(w.id,`${f}/${w.name}`)};a(null,r.slug)}Z(s.map(r=>[" ",...r])),d()}$(p("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function fn(e,n){let t=S(),o=await t.tree(),s=me(o,n.workspace),r=Ie(s,e),a=await t.docs(s.id,{project:r.project,folder:r.folder}),c=r.project?s.projects.find(u=>u.id===r.project):null,f=e?[]:s.projects,w=c?c.folders.filter(u=>u.parentId===r.folder):[];if(n.json)return d(JSON.stringify({workspace:s.slug,place:e??null,projects:f,folders:w,docs:a},null,2));let g=[];for(let u of f)g.push([T(`${u.slug}/`),u.name,p(`${u.docs} doc${u.docs===1?"":"s"}`),""]);for(let u of w)g.push([T(`${u.name}/`),"",p(`${u.docs} doc${u.docs===1?"":"s"}`),""]);for(let u of a)g.push([u.slug,u.title+p(u.kind==="html"?" .html":" .md"),p(fe(u.updatedAt)),p(u.updatedByName??"")]);if(!g.length)return $(p(`Nothing in ${r.label} yet.`));Z(g)}async function gn(e,n){let[t,...o]=(e??"").split("/").filter(Boolean);t||m("`dockito mkdir <project>[/folder[/subfolder]]`"),o.length>2&&m("Folders go at most 2 deep inside a project.");let s=S(),r=me(await s.tree(),n.workspace),a=z(r,t),c=a?.id??(await s.createProject(r.id,t)).id,f=!a;a||d(`${y("\u2713")} Project ${t}`);let w=oe(a),g=null;for(let u=0;u<o.length;u++){let k=w.idOf(o.slice(0,u+1));if(k){g=k;continue}g=(await s.createFolder(c,o[u],g)).id,w.add(o.slice(0,u+1),g),f=!0,d(`${y("\u2713")} Folder ${o.slice(0,u+1).join("/")}`)}f||$(p("Already there."))}async function hn(e){e||m("Name a doc: `dockito open <link or code>`");let n=`${A}/${F(e)}`;ft(n)||d(n)}async function mn(e,n){e||m("Name a doc: `dockito get <link or code> [-o file]`");let t=S(),o=await t.doc(F(e)),s=n.entry?Number(n.entry):null;n.entry&&(!Number.isInteger(s)||s<1)&&m("--entry takes an entry number from `dockito history`.");let{bytes:r}=await t.content(o.doc.id,s);if(!n.output)return void process.stdout.write(r);Xt(n.output,r),$(`${y("\u2713")} ${n.output} ${p(`\u2190 ${t.linkOf(o.doc.slug)}${s?` entry ${s}`:""}`)}`)}async function wn(e,n){e||m("Name a doc: `dockito history <link or code>`");let t=await S().doc(F(e));if(n.json)return d(JSON.stringify(t.versions,null,2));d(`${T(t.doc.title)} ${p(`${A}/${t.doc.slug}`)}`),Z(t.versions.map((o,s)=>[String(o.number),s===0?y("current"):p(fe(o.createdAt)),s===0?p(fe(o.createdAt)):"",o.byName??"",p(o.source),o.savedAt?`${I("saved")}${o.note?` ${o.note}`:""}`:"",o.gitCommit?p(o.gitCommit.slice(0,7)):""]))}async function kn(e,n){let t=Number(n);(!e||!Number.isInteger(t)||t<1)&&m("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let o=S(),s=await o.doc(F(e)),r=await o.restore(s.doc.id,t,s.versions[0]?.number??null);d(`${y("\u2713")} Restored entry ${t} as entry ${r.version} ${p(o.linkOf(r.slug))}`)}async function yn(e,n,t){e||m('`dockito save <doc> [entry] -m "note"`');let o=S(),s=await o.doc(F(e)),r=n?Number(n):s.versions[0]?.number;(!Number.isInteger(r)||r<1)&&m("Entries are in `dockito history`."),await o.save(s.doc.id,r,t.message??null),d(`${y("\u2713")} Saved entry ${r}${t.message?p(` \xB7 ${t.message}`):""}`)}async function bn(e,n){e||m("`dockito share <link or code> [--private]`"),n.public&&m("An existing doc can be made public only from Share in the app. `dockito publish <file> --public` makes a new doc public.");let t=S(),o=await t.doc(F(e)),s=t.linkOf(o.doc.slug);if(n.private)return o.doc.public&&await t.makePrivate(o.doc.id),n.json?d(JSON.stringify({public:!1,link:s},null,2)):d(`${y("\u2713")} Private: only people in ${o.workspace.name} can open it ${p(s)}`);if(n.json)return d(JSON.stringify({public:o.doc.public,link:s},null,2));d(o.doc.public?`${y("Public")}: anyone with the link can open it ${s}`:`Private: only people in ${o.workspace.name} can open it ${p(s)}`)}var gt=`Dockito hosts Markdown and HTML docs at a short link, with History.
44
19
 
45
20
  workspace a person's or a team's space; its slug is in every link
46
21
  project docs grouped in a workspace, with folders up to 2 deep
47
- Docs a workspace's docs outside any project (where a publish
48
- with no --to goes)
49
- doc one .md or .html file. Publishing the same file again
50
- updates the same doc and keeps its link
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
51
25
  History every publish is an entry; -m keeps one as a saved point
52
26
  access a login has View, Publish or Everything per workspace
53
27
 
54
28
  For AI agents:
55
- - Run \`dockito tree\` to see every place a doc can go. Never guess
56
- one: ask the user, then use --to.
57
- - A folder of docs: \`dockito sync <folder> --to <project>\`. It shows
58
- a plan first; show it to the user before running with --yes.
59
- - Use --json. On failure, read "error" and the commands it suggests.
60
- - dockito.jsonc in a repo remembers places and which file is which doc.`,Wn=`Dockito CLI ${zt}
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.`,ht={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
41
+ file updates the doc with the same file name in the same folder, or becomes
42
+ a new doc there. Only files whose content changed are sent. Nothing is moved
43
+ or deleted in Dockito. The CLI remembers nothing: pass --to every time.
61
44
 
62
- ${Gn}
45
+ Docs made by sync are private.
46
+
47
+ --to <project[/folder]> Where it goes (required; \`dockito tree\` lists places)
48
+ -w, --workspace <slug> Another workspace
49
+ --create Make the project if it doesn't exist
50
+ --fold Put folders deeper than 2 into the deepest allowed;
51
+ clashing names get the left-out folders in front
52
+ --dry-run Show the plan, change nothing
53
+ --yes Apply a plan that makes a project or folders
54
+
55
+ It always shows a plan. Making a project or folders waits for --yes: show
56
+ the plan to the user first. Sending docs into folders that exist just runs.
57
+
58
+ dockito sync docs --to handbook
59
+ 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
60
+ updates its doc (same link, a new History entry); otherwise it's a new doc.
61
+ Unchanged files are skipped.
62
+
63
+ Docs are private: only people in the workspace can open them.
64
+ Add --public only when the user clearly asked for a public link (anyone
65
+ with the link, no sign-in). If they didn't say, keep it private. --public
66
+ works only for a new doc; an existing private doc can be made public only
67
+ from Share in the app.
68
+
69
+ --to <project[/folder]> Where (default: the workspace's Docs)
70
+ -w, --workspace <slug> Another workspace (default: your personal one)
71
+ -m, --message <note> Keep this entry as a saved point, with a note
72
+ --public Make the new doc public (only when the user asked)
73
+ --doc <doc> Update this doc (link or code) instead
74
+ --new A new doc even if the name is already there
75
+
76
+ dockito publish plan.md private, in your Docs
77
+ dockito publish plan.md --to handbook/specs private, in a folder
78
+ dockito publish plan.md --public the user asked for a public link`},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
79
+ (anyone with the link). --private makes it private again at once.
80
+
81
+ An existing doc is made public only from Share in the app; a new doc can be
82
+ published public with \`dockito publish <file> --public\` when the user asks.
83
+
84
+ dockito share <link or code>
85
+ 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
86
+ workspace's top level, a project, or a folder. -w for another workspace.`},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.
87
+
88
+ 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",page:"Prints a doc's file, or writes it with -o. --entry <n> gets a History entry\n(numbers from `dockito history`)."},history:{use:"history <doc>",about:"History entries",page:`Lists a doc's History: entry, when, who, from where (upload, edit, cli,
89
+ 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
90
+ never drops it, with an optional note.`},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
91
+ in each workspace: View, Publish or Everything. --update changes that access.
92
+ --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."}},Ce=`Dockito CLI ${Me}
93
+
94
+ ${gt}
63
95
 
64
96
  Usage: dockito <command>
65
97
 
66
- sync [folder] Mirror a folder into a project: subfolders become
67
- folders, only changed files are sent, plan first
68
- --to <project> The project (or project/folder) it goes to
69
- --create Make the project if it doesn't exist
70
- --fold Put folders deeper than 2 into the deepest allowed
71
- --yes Apply without asking; --dry-run shows the plan only
72
- publish <file...> Publish files; the same file updates the same doc
73
- -m, --message <note> Keep this entry as a saved point
74
- --to <project/folder> Where new docs go (default: the workspace's Docs)
75
- --doc <doc> Update this doc instead
76
- --new Publish as a new doc
77
- --force Publish even if someone published since
78
- tree Every workspace, project and folder you can use
79
- ls [project/folder] Docs in a place
80
- mkdir <project/folder> Make a project or folders
81
- open <doc> Open in the browser
82
- get <doc> [-o file] Download (--entry <n> for a History entry)
83
- history <doc> History entries
84
- restore <doc> <entry> Make a History entry current again
85
- save <doc> [entry] -m ... Keep an entry as a saved point
86
-
87
- login [--update] Sign in through the browser (--update changes access)
88
- logout Sign out this machine
89
- whoami Who you are and what this login can reach
90
- switch [workspace] Set the workspace commands use
91
- init [--to project/folder] Write a dockito.jsonc here
92
- agent What an AI agent needs to know, on its own
93
-
94
- <doc> is a link, a code, or a file published before.
95
- -w, --workspace <slug> Use another workspace
96
- --json Machine-readable output
97
-
98
- In a repo with dockito.jsonc, \`dockito\` alone runs sync.
99
-
100
- Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`;async function To(){let{values:e,positionals:n}=no({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"},yes:{type:"boolean",short:"y"},"dry-run":{type:"boolean"},create:{type:"boolean"},fold:{type:"boolean"},"no-browser":{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[r,...t]=n;if(e.version)return O(zt);if(e.help||r==="help")return O(Wn);if(!r)return C()?.config.dir||Tt()?ut({c:J(),url:_},void 0,e):O(Wn);switch(r){case"login":return lo(e);case"logout":return ao();case"whoami":return uo(e);case"switch":return fo(t[0]);case"init":return e.to||e.workspace||!Tt()?po(e):ut({c:J(),url:_},void 0,e);case"sync":return ut({c:J(),url:_},t[0],e);case"publish":return go(t,e);case"tree":return $o(e);case"ls":return mo(t[0],e);case"mkdir":return jo(t[0],e);case"open":return ho(t[0]);case"get":return wo(t[0],e);case"history":return ko(t[0],e);case"restore":return yo(t[0],t[1]);case"save":return bo(t[0],t[1],e);case"agent":return O(Gn);default:N(`Unknown command "${r}". Run \`dockito --help\`.`)}}To().then(()=>process.exit(process.exitCode??0),(e)=>{let n=process.argv.includes("--json");if(e instanceof de){if(n)O(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2));else W(e.message);process.exit(2)}if(e instanceof vt)process.exit(130);if(n&&e instanceof K)O(JSON.stringify({error:e.code,message:Mt(e,_),...e.detail},null,2));else if(e instanceof dt)n?O(JSON.stringify({error:"failed",message:e.message},null,2)):W(Be(e.message));else if(e instanceof K)W(Be(Mt(e,_)));else if(e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message)))W(Be(`${e.message}. Run \`dockito --help\`.`));else W(Be(String(e?.stack??e)));process.exit(1)});
98
+ ${Object.values(ht).map(e=>` ${e.use.padEnd(30)}${e.about}`).join(`
99
+ `)}
100
+
101
+ <doc> is a link or a code. \`dockito <command> --help\` for more.
102
+ -w, --workspace <slug> Another workspace (default: your personal one)
103
+ --json Machine-readable output
104
+
105
+ Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`,pt=e=>{let n=ht[e];return n?`dockito ${n.use}
106
+
107
+ ${n.page}`:null};async function $n(){let{values:e,positionals:n}=tn({allowPositionals:!0,options:{message:{type:"string",short:"m"},to:{type:"string"},doc:{type:"string"},new:{type:"boolean"},workspace:{type:"string",short:"w"},output:{type:"string",short:"o"},entry:{type:"string"},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"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[t,...o]=n;if(e.version)return d(Me);if(t==="help"&&o[0])return d(pt(o[0])??Ce);if(e.help&&t)return d(pt(t)??Ce);if(e.help||!t||t==="help")return d(Ce);switch(t){case"login":return cn(e);case"logout":return ln();case"whoami":return dn(e);case"sync":return ct(S(),o[0],e);case"publish":return un(o,e);case"tree":return pn(e);case"ls":return fn(o[0],e);case"mkdir":return gn(o[0],e);case"share":return bn(o[0],e);case"open":return hn(o[0]);case"get":return mn(o[0],e);case"history":return wn(o[0],e);case"restore":return kn(o[0],o[1]);case"save":return yn(o[0],o[1],e);case"agent":return d(gt);default:m(`Unknown command "${t}". Run \`dockito --help\`.`)}}function be(e){let n=2,t=()=>--n===0&&process.exit(e);process.stdout.write("",t),process.stderr.write("",t)}$n().then(()=>be(Number(process.exitCode??0)),e=>{let n=process.argv.includes("--json");if(e instanceof D)return n?d(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2)):$(e.message),be(2);if(e instanceof te)return be(130);n&&e instanceof x?d(JSON.stringify({error:e.code,message:ee(e,A),...e.detail},null,2)):n&&e instanceof W?d(JSON.stringify({error:"failed",message:e.message},null,2)):e instanceof W?$(_(e.message)):e instanceof x?$(_(ee(e,A))):e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message))?$(_(`${e.message}. Run \`dockito --help\`.`)):$(_(String(e?.stack??e))),be(1)});
package/package.json CHANGED
@@ -1,15 +1,25 @@
1
1
  {
2
2
  "name": "dockito",
3
- "version": "0.0.1-beta.2",
3
+ "version": "0.0.1-beta.4",
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
  }