dockito 0.0.1-beta.11 → 0.0.1-beta.13

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 (2) hide show
  1. package/dockito.js +71 -23
  2. package/package.json +1 -1
package/dockito.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as an,spawnSync as bt}from"node:child_process";import{createHash as cn}from"node:crypto";import{existsSync as Tt,readFileSync as xe,statSync as ln,writeFileSync as $t}from"node:fs";import{hostname as dn}from"node:os";import{basename as Be,dirname as un,isAbsolute as pn,resolve as Ot}from"node:path";import{parseArgs as hn}from"node:util";var C=e=>e.level===null||e.level==="publish"||e.level==="everything",K=(e,t)=>e.projects.find(n=>n.slug===t)??e.projects.find(n=>n.name.toLowerCase()===t.toLowerCase())??null;function q(e){let t=new Map((e?.folders??[]).map(s=>[s.id,s])),n=s=>{let i=t.get(s);return[...i.parentId?n(i.parentId):[],i.name]},o=new Map((e?.folders??[]).map(s=>[n(s.id).join("/").toLowerCase(),s.id]));return{idOf:s=>s.length?o.get(s.join("/").toLowerCase()):null,pathOf:s=>s&&t.has(s)?n(s):[],add:(s,i)=>{o.set(s.join("/").toLowerCase(),i)}}}var It=e=>e.fileName??`${e.title}.${e.kind==="sheet"?"csv":e.kind}`,te=(e,t,n)=>e.filter(o=>(o.folderId??null)===t&&It(o).toLowerCase()===n.toLowerCase());function it(e){let t=e.trim();if(/^https?:\/\//.test(t))try{t=new URL(t).pathname}catch{return null}let n=(t.split("/").filter(Boolean).pop()??"").replace(/\.[a-z]+$/,"");return/^[a-z0-9]{12}$/.test(n)&&/\d/.test(n)?n:null}var x=class extends Error{constructor(n,o,s={}){super(n);this.code=n;this.status=o;this.detail=s}code;status;detail};function at(e){let t=e;if(!t?.number)return"";let n=t.at?new Date(t.at).toISOString().slice(0,10):"";return`The oldest is entry ${t.number}${t.note?` ("${t.note}")`:""}${n?`, saved ${n}`:""}.`}function fe(e){let t=e.url.replace(/\/+$/,"");async function n(a,c,u={}){let l=new URL(`${t}/api${c}`);for(let[k,v]of Object.entries(u.query??{}))v!=null&&v!==""&&l.searchParams.set(k,String(v));let m={},f=u.token===void 0?e.token:u.token;f&&(m.authorization=`Bearer ${f}`),e.userAgent&&(m["user-agent"]=e.userAgent);let w;u.json!==void 0?(m["content-type"]="application/json",w=JSON.stringify(u.json)):u.body&&(m["content-type"]="application/octet-stream",w=u.body);let O;try{O=e.fetch?await e.fetch(new Request(l,{method:a,headers:m,body:w})):await fetch(l,{method:a,headers:m,body:w})}catch{throw new x("offline",0)}return O}async function o(a){let c=await a.json().catch(()=>({}));if(!a.ok){let{error:u,...l}=c;throw new x(typeof u=="string"?u:`http_${a.status}`,a.status,l)}return c}async function s(a){let c=await a.json().catch(()=>({}));if(typeof c.error=="string"||typeof c.text!="string"||a.status>=500){let{error:u,...l}=c;throw new x(typeof u=="string"?u:`http_${a.status}`,a.status,l)}return{ok:a.ok,status:String(c.status),version:c.version,seq:c.seq,text:c.text}}let i=encodeURIComponent;return{url:t,linkOf:a=>`${t}/${a}`,startLogin:async(a,c)=>o(await n("POST","/device/code",{json:{client:a,device:c}})),pollLogin:async a=>{let c=await n("POST","/device/token",{json:{device_code:a},token:null}),u=await c.json().catch(()=>({}));if(c.ok)return u;if(u.error==="authorization_pending"||u.error==="slow_down")return null;throw new x(u.error??`http_${c.status}`,c.status)},logout:async()=>o(await n("DELETE","/tokens/current")),whoami:async()=>o(await n("GET","/whoami")),tree:async()=>o(await n("GET","/tree")),projects:async a=>(await o(await n("GET",`/workspaces/${i(a)}/projects`))).projects,docs:async(a,c={})=>(await o(await n("GET",`/workspaces/${i(a)}/docs`,{query:c}))).docs,files:async(a,c)=>(await o(await n("GET",`/workspaces/${i(a)}/files`,{query:{project:c}}))).docs,createProject:async(a,c)=>o(await n("POST",`/workspaces/${i(a)}/projects`,{json:{name:c}})),createFolder:async(a,c,u)=>o(await n("POST",`/projects/${i(a)}/folders`,{json:{name:c,parentId:u}})),recent:async a=>(await o(await n("GET",`/workspaces/${i(a)}/docs/recent`))).docs,doc:async a=>o(await n("GET",`/docs/by-code/${i(a)}`)),content:async(a,c,u={})=>{let l=await n("GET",`/docs/${i(a)}/content`,{query:{v:c,outline:u.outline?1:null,lines:u.lines,text:u.text?1:null}});l.ok||await o(l);let m=l.headers.get("x-doc-lines");return{bytes:new Uint8Array(await l.arrayBuffer()),entry:Number(l.headers.get("x-doc-version")),lines:m?Number(m):null,shown:l.headers.get("x-doc-shown")}},create:async(a,c,u,l={})=>o(await n("POST",`/workspaces/${i(a)}/docs`,{query:{name:c,title:l.title,project:l.project,folder:l.folder,public:l.public?1:null,source:e.source},body:u})),update:async(a,c,u,l={})=>o(await n("POST",`/docs/${i(a)}/versions`,{query:{name:c,note:l.note,commit:l.commit,base:l.base,source:e.source},body:u})),restore:async(a,c,u)=>o(await n("POST",`/docs/${i(a)}/history/${c}/restore`,{query:{base:u}})),save:async(a,c,u)=>o(await n("PUT",`/docs/${i(a)}/history/${c}/saved`,{json:{note:u}})),makePrivate:async a=>o(await n("PATCH",`/docs/${i(a)}`,{json:{public:!1}})),rename:async(a,c)=>o(await n("PATCH",`/docs/${i(a)}`,{json:{title:c}})),readSheet:async(a,c)=>s(await n("POST",`/docs/${i(a)}/sheet`,{query:{source:e.source},json:c})),changeSheet:async(a,c)=>s(await n("POST",`/docs/${i(a)}/sheet/change`,{query:{source:e.source},json:c}))}}import{emitKeypressEvents as _t}from"node:readline";import{createInterface as Rt}from"node:readline/promises";var Ct=!process.env.NO_COLOR&&process.stdout.isTTY,z=e=>t=>Ct?`\x1B[${e}m${t}\x1B[0m`:t,g=z("2"),S=z("1"),b=z("32"),_=z("31"),R=z("33"),Ie=z("36"),h=(e="")=>process.stdout.write(e+`
2
+ import{spawn as an,spawnSync as bt}from"node:child_process";import{createHash as cn}from"node:crypto";import{existsSync as Tt,readFileSync as xe,statSync as ln,writeFileSync as $t}from"node:fs";import{hostname as dn}from"node:os";import{basename as Be,dirname as un,isAbsolute as pn,resolve as Ot}from"node:path";import{parseArgs as hn}from"node:util";var I=e=>e.level===null||e.level==="publish"||e.level==="everything",H=(e,t)=>e.projects.find(n=>n.slug===t)??e.projects.find(n=>n.name.toLowerCase()===t.toLowerCase())??null;function q(e){let t=new Map((e?.folders??[]).map(s=>[s.id,s])),n=s=>{let i=t.get(s);return[...i.parentId?n(i.parentId):[],i.name]},o=new Map((e?.folders??[]).map(s=>[n(s.id).join("/").toLowerCase(),s.id]));return{idOf:s=>s.length?o.get(s.join("/").toLowerCase()):null,pathOf:s=>s&&t.has(s)?n(s):[],add:(s,i)=>{o.set(s.join("/").toLowerCase(),i)}}}var Et=e=>e.fileName??`${e.title}.${e.kind==="sheet"?"csv":e.kind}`,oe=(e,t,n)=>e.filter(o=>(o.folderId??null)===t&&Et(o).toLowerCase()===n.toLowerCase());function it(e){let t=e.trim();if(/^https?:\/\//.test(t))try{t=new URL(t).pathname}catch{return null}let n=(t.split("/").filter(Boolean).pop()??"").replace(/\.[a-z]+$/,"");return/^[a-z0-9]{12}$/.test(n)&&/\d/.test(n)?n:null}var x=class extends Error{constructor(n,o,s={}){super(n);this.code=n;this.status=o;this.detail=s}code;status;detail};function at(e){let t=e;if(!t?.number)return"";let n=t.at?new Date(t.at).toISOString().slice(0,10):"";return`The oldest is entry ${t.number}${t.note?` ("${t.note}")`:""}${n?`, saved ${n}`:""}.`}function fe(e){let t=e.url.replace(/\/+$/,"");async function n(a,c,d={}){let l=new URL(`${t}/api${c}`);for(let[k,v]of Object.entries(d.query??{}))v!=null&&v!==""&&l.searchParams.set(k,String(v));let m={},f=d.token===void 0?e.token:d.token;f&&(m.authorization=`Bearer ${f}`),e.userAgent&&(m["user-agent"]=e.userAgent);let w;d.json!==void 0?(m["content-type"]="application/json",w=JSON.stringify(d.json)):d.body&&(m["content-type"]="application/octet-stream",w=d.body);let O;try{O=e.fetch?await e.fetch(new Request(l,{method:a,headers:m,body:w})):await fetch(l,{method:a,headers:m,body:w})}catch{throw new x("offline",0)}return O}async function o(a){let c=await a.json().catch(()=>({}));if(!a.ok){let{error:d,...l}=c;throw new x(typeof d=="string"?d:`http_${a.status}`,a.status,l)}return c}async function s(a){let c=await a.json().catch(()=>({}));if(typeof c.error=="string"||typeof c.text!="string"||a.status>=500){let{error:d,...l}=c;throw new x(typeof d=="string"?d:`http_${a.status}`,a.status,l)}return{ok:a.ok,status:String(c.status),version:c.version,seq:c.seq,text:c.text}}let i=encodeURIComponent;return{url:t,linkOf:a=>`${t}/${a}`,startLogin:async(a,c)=>o(await n("POST","/device/code",{json:{client:a,device:c}})),pollLogin:async a=>{let c=await n("POST","/device/token",{json:{device_code:a},token:null}),d=await c.json().catch(()=>({}));if(c.ok)return d;if(d.error==="authorization_pending"||d.error==="slow_down")return null;throw new x(d.error??`http_${c.status}`,c.status)},logout:async()=>o(await n("DELETE","/tokens/current")),whoami:async()=>o(await n("GET","/whoami")),tree:async()=>o(await n("GET","/tree")),projects:async a=>(await o(await n("GET",`/workspaces/${i(a)}/projects`))).projects,docs:async(a,c={})=>(await o(await n("GET",`/workspaces/${i(a)}/docs`,{query:c}))).docs,files:async(a,c)=>(await o(await n("GET",`/workspaces/${i(a)}/files`,{query:{project:c}}))).docs,createProject:async(a,c)=>o(await n("POST",`/workspaces/${i(a)}/projects`,{json:{name:c}})),createFolder:async(a,c,d)=>o(await n("POST",`/projects/${i(a)}/folders`,{json:{name:c,parentId:d}})),recent:async a=>(await o(await n("GET",`/workspaces/${i(a)}/docs/recent`))).docs,doc:async a=>o(await n("GET",`/docs/by-code/${i(a)}`)),content:async(a,c,d={})=>{let l=await n("GET",`/docs/${i(a)}/content`,{query:{v:c,outline:d.outline?1:null,lines:d.lines,text:d.text?1:null}});l.ok||await o(l);let m=l.headers.get("x-doc-lines");return{bytes:new Uint8Array(await l.arrayBuffer()),entry:Number(l.headers.get("x-doc-version")),lines:m?Number(m):null,shown:l.headers.get("x-doc-shown")}},create:async(a,c,d,l={})=>o(await n("POST",`/workspaces/${i(a)}/docs`,{query:{name:c,title:l.title,project:l.project,folder:l.folder,public:l.public?1:null,source:e.source},body:d})),update:async(a,c,d,l={})=>o(await n("POST",`/docs/${i(a)}/versions`,{query:{name:c,note:l.note,commit:l.commit,base:l.base,source:e.source},body:d})),restore:async(a,c,d)=>o(await n("POST",`/docs/${i(a)}/history/${c}/restore`,{query:{base:d}})),save:async(a,c,d)=>o(await n("PUT",`/docs/${i(a)}/history/${c}/saved`,{json:{note:d}})),makePrivate:async a=>o(await n("PATCH",`/docs/${i(a)}`,{json:{public:!1}})),rename:async(a,c)=>o(await n("PATCH",`/docs/${i(a)}`,{json:{title:c}})),readSheet:async(a,c)=>s(await n("POST",`/docs/${i(a)}/sheet`,{query:{source:e.source},json:c})),changeSheet:async(a,c)=>s(await n("POST",`/docs/${i(a)}/sheet/change`,{query:{source:e.source},json:c}))}}import{emitKeypressEvents as _t}from"node:readline";import{createInterface as Rt}from"node:readline/promises";var It=!process.env.NO_COLOR&&process.stdout.isTTY,z=e=>t=>It?`\x1B[${e}m${t}\x1B[0m`:t,g=z("2"),S=z("1"),b=z("32"),_=z("31"),R=z("33"),Ee=z("36"),h=(e="")=>process.stdout.write(e+`
3
3
  `),$=(e="")=>process.stderr.write(e+`
4
- `),J=class extends Error{},p=e=>{throw new J(e)},G={view:"View",publish:"Publish",everything:"Everything"};function me(e){let t=Math.round((Date.now()-new Date(e).getTime())/1e3);if(t<60)return"just now";let n=Math.round(t/60);if(n<60)return`${n} min ago`;let o=Math.round(n/60);if(o<24)return`${o} h ago`;let s=Math.round(o/24);return s<30?s===1?"yesterday":`${s} days ago`:new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}function ne(e){let t=o=>o.replace(/\x1b\[[0-9;]*m/g,""),n=e[0]?.map((o,s)=>Math.max(...e.map(i=>t(i[s]??"").length)))??[];for(let o of e)h(o.map((s,i)=>i===o.length-1?s:s+" ".repeat(n[i]-t(s).length)).join(" ").trimEnd())}function oe(e,t){switch(e.code){case"offline":return`Couldn't reach Dockito at ${t}.`;case"invalid_token":return"Your login has ended or was signed out. Run `dockito login`.";case"token_level":return`This needs ${G[e.detail.needs]??e.detail.needs} access; this login has ${G[e.detail.has]??e.detail.has}. Run \`dockito login --update\`.`;case"not_for_tokens":return"That can only be done in the browser.";case"not_found":return"Not found, or this login has no access to it. `dockito whoami` lists what it can reach.";case"forbidden":return"You can open this but not change it. Ask its owner or a workspace admin for Edit.";case"public_off":return"This workspace doesn't allow public links. Its owner can turn them on in Workspace settings.";case"deleted":return"That doc was deleted.";case"conflict":return"Someone published a newer version since yours. Get it with `dockito get`, or publish anyway with --force.";case"rate_limited":return"Too many requests. Try again in a minute.";case"too_large":return`The file is too large (limit ${Math.round(Number(e.detail.limit??0)/1048576)} MB).`;case"out_of_space":return"The workspace is out of storage.";case"too_many_saved":return`This doc already keeps ${e.detail.limit} saved points, the most it can, so nothing changed. ${at(e.detail.oldest)} In the app, use Don't keep on one in History, then save again.`;case"unsupported":return"Dockito takes .md and .html files.";case"other_kind":return"That doc is a different kind of file (Markdown and HTML can't replace each other).";case"empty":return"The file is empty.";case"not_text":return"The file isn't text.";case"too_deep":return"Folders go at most 2 deep inside a project.";case"invalid_name":return"Names need 1 to 80 characters.";case"folder_gone":return"That project or folder no longer exists.";default:return typeof e.detail.message=="string"?e.detail.message:`Dockito said: ${e.code}`}}var Ce=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI,V=e=>process.stdout.write(e);function ct(e,t,n=0){return new Promise((o,s)=>{let i=n,a=l=>{l||V(`\x1B[${t.length}A`);for(let[m,f]of t.entries())V(`\x1B[2K${m===i?Ie("\u276F "+f.label):" "+f.label}${f.hint?g(" "+f.hint):""}
5
- `)};V(`${b("?")} ${S(e)} ${g("(\u2191\u2193, Enter)")}
6
- \x1B[?25l`),a(!0),_t(process.stdin),process.stdin.setRawMode(!0),process.stdin.resume();let c=()=>{process.stdin.off("keypress",u),process.stdin.setRawMode(!1),process.stdin.pause(),V("\x1B[?25h")},u=(l,m)=>{if(m.ctrl&&m.name==="c")return c(),V(`
7
- `),s(new se);if(m.name==="up"||m.name==="k")i=(i-1+t.length)%t.length;else if(m.name==="down"||m.name==="j")i=(i+1)%t.length;else return m.name==="return"?(c(),V(`\x1B[${t.length+1}A\x1B[0J${b("?")} ${S(e)} ${Ie(t[i].label)}
8
- `),o(i)):void 0;a(!1)};process.stdin.on("keypress",u)})}async function we(e,t=""){let n=Rt({input:process.stdin,output:process.stdout});try{return(await n.question(`${b("?")} ${S(e)}${t?g(` (${t})`):""} `)).trim()||t}finally{n.close()}}async function _e(e,t=!0){let n=(await we(`${e} ${g(t?"[Y/n]":"[y/N]")}`)).toLowerCase();return n?n.startsWith("y"):t}var se=class extends Error{};import{homedir as Lt}from"node:os";import{join as Re}from"node:path";var Ft="https://dockito-app.dockito.workers.dev";function lt(){return process.env.DOCKITO_CONFIG_DIR?process.env.DOCKITO_CONFIG_DIR:process.platform==="win32"&&process.env.APPDATA?Re(process.env.APPDATA,"dockito"):Re(process.env.XDG_CONFIG_HOME||Re(Lt(),".config"),"dockito")}var dt=()=>(process.env.DOCKITO_URL||Ft).replace(/\/+$/,""),ut=e=>new URL(e).host;import{spawnSync as Mt}from"node:child_process";import{chmodSync as Ht,existsSync as Le,mkdirSync as Kt,readFileSync as Ut,rmSync as pt,writeFileSync as Wt}from"node:fs";import{dirname as Bt,join as qt}from"node:path";var Y="dockito";function X(e,t,n){try{let o=Mt(e,t,{input:n,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return o.error?null:o}catch{return null}}var re=e=>qt(lt(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function Jt(e,t){if(process.platform==="darwin"){let n=X("security",["-i"],`add-generic-password -U -s ${Y} -a ${e} -l Dockito -w ${t}
9
- `);return!!n&&n.status===0&&!/error/i.test(n.stderr??"")}if(process.platform==="linux"){let n=X("secret-tool",["store","--label=Dockito","service",Y,"account",e],t);return!!n&&n.status===0}return!1}function Gt(e){let t=process.platform==="darwin"?X("security",["find-generic-password","-s",Y,"-a",e,"-w"]):process.platform==="linux"?X("secret-tool",["lookup","service",Y,"account",e]):null;return(t?.status===0?t.stdout.trim():"")||null}function zt(e){process.platform==="darwin"?X("security",["delete-generic-password","-s",Y,"-a",e]):process.platform==="linux"&&X("secret-tool",["clear","service",Y,"account",e])}function ht(e,t){if(Jt(e,t))return Le(re(e))&&pt(re(e)),"keychain";let n=re(e);return Kt(Bt(n),{recursive:!0,mode:448}),Wt(n,t,{mode:384}),Ht(n,384),"file"}function ye(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let t=Gt(e);if(t)return t;let n=re(e);return Le(n)&&Ut(n,"utf8").trim()||null}function gt(e){zt(e);let t=re(e);Le(t)&&pt(t)}var N=class extends Error{constructor(n,o,s={}){super(o);this.code=n;this.data=s}code;data};function ke(e,t){let n=e.workspaces;if(t)return n.find(s=>s.slug===t)??p(`This login can't reach workspace "${t}". \`dockito tree\` lists the ones it can.`);let o=n.length===1?n[0]:n.find(s=>s.kind==="personal"&&s.role==="owner");if(o)return o;throw new N("needs_workspace",`Pick a workspace with -w: ${n.map(s=>s.slug).join(", ")}`,{workspaces:n.map(s=>s.slug)})}function Fe(e,t){if(!t)return{project:null,folder:null,label:`${e.slug} Docs`};let[n,...o]=t.split("/").filter(Boolean),s=K(e,n);if(!s)return p(`No project "${n}" in ${e.slug}. \`dockito tree\` lists them; \`dockito mkdir ${t}\` makes it.`);let i=q(s).idOf(o);return i===void 0?p(`No folder "${o.join("/")}" in ${s.slug}. \`dockito mkdir ${s.slug}/${o.join("/")}\` makes it.`):{project:s.id,folder:i,label:[e.slug,s.slug,...o].join("/")}}import{createHash as Vt}from"node:crypto";import{spawnSync as Yt}from"node:child_process";import{existsSync as ae,readFileSync as Xt,readdirSync as Qt,statSync as wt}from"node:fs";import{basename as ft,join as L,relative as Zt,resolve as Me,sep as en}from"node:path";var tn=4,ie=e=>e<1024*1024?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,nn=e=>Vt("sha256").update(e).digest("hex"),yt=e=>e.split(en).join("/"),Q=(e,t)=>`${e} ${t}${e===1?"":"s"}`,on=(e,t)=>{let n=/\.[^./]+$/.exec(t.toLowerCase())?.[0]??"";return e.find(o=>o.extensions.includes(n))?.id??null};function $e(e,t){let n=Yt("git",e,{cwd:t,encoding:"utf8",maxBuffer:67108864});return n.error||n.status!==0?null:n.stdout}function sn(e,t){let n=$e(["ls-files","-z","--cached","--others","--exclude-standard","--","."],e);return(n!==null?n.split("\0").filter(Boolean):(function s(i){return Qt(L(e,i),{withFileTypes:!0}).flatMap(a=>a.name.startsWith(".")||a.name==="node_modules"?[]:a.isDirectory()?s(L(i,a.name)):[yt(L(i,a.name))])})("")).filter(s=>t(s)&&ae(L(e,s))&&wt(L(e,s)).isFile()).sort()}function rn(e,t,n){let o=[...e.matchAll(/\b(?:src|href)=["']([^"'#?]+)/gi)].map(s=>s[1]);return[...new Set(o.filter(s=>!/^([a-z]+:|\/\/|\/)/i.test(s)&&!n(s)&&ae(L(t,s))))]}function mt(e,t){let n=(i,a,c="")=>`dockito sync ${t} -w ${i} --to ${a}${c}`,o=e.workspaces.filter(C).flatMap(i=>[...i.projects.map(a=>({workspace:i.slug,project:a.slug,name:a.name,docs:a.docs,command:n(i.slug,a.slug)})),{workspace:i.slug,project:null,name:"a new project",docs:0,command:n(i.slug,"<name>"," --create")}]),s=Math.max(...o.map(i=>i.command.length));throw new N("needs_place",[`Which project should ${t==="."?"this folder":t+"/"} go to? Ask the user, then run one of:`,...o.map(i=>` ${i.command.padEnd(s)} ${g(i.project?`${i.name} \xB7 ${Q(i.docs,"doc")}`:i.name)}`),g("Subfolders become folders. `dockito tree` shows every place.")].join(`
10
- `),{choices:o})}async function kt(e,t,n){let o=Ce()&&!n.json,s=await e.tree(),i=r=>!!on(s.kinds,r);s.workspaces.some(C)||p("This login can't publish anywhere. Run `dockito login --update`.");let a=t;a||(o||mt(s,ae("docs")?"docs":"."),a=await we("Folder to sync",ae("docs")?"docs":"."));let c=Me(a);(!ae(c)||!wt(c).isDirectory())&&p(`${a} isn't a folder.`);let u=yt(Zt(process.cwd(),c))||".",l;n.workspace&&(l=s.workspaces.find(r=>r.slug===n.workspace)??p(`This login can't reach workspace "${n.workspace}".`));let m=n.to;if(!m){o||mt(s,u);let r=(l?[l]:s.workspaces.filter(C)).flatMap(y=>[...y.projects.map(j=>({w:y,p:j.slug,label:j.name,hint:`${y.slug} \xB7 ${Q(j.docs,"doc")}`})),{w:y,p:null,label:"New project",hint:y.slug}]),d=r[await ct("Project",r)];l=d.w,m=d.p??await we("Project name",ft(c===process.cwd()?c:Me(c,".."))),n.create||=!d.p}if(!l){let r=s.workspaces.filter(y=>C(y)&&K(y,m.split("/")[0])),d=s.workspaces.find(y=>y.kind==="personal"&&y.role==="owner");if(l=r.length===1?r[0]:s.workspaces.length===1?s.workspaces[0]:!r.length&&d?d:void 0,!l)throw new N("needs_workspace",`Which workspace? Run again with -w: ${(r.length?r:s.workspaces).map(y=>y.slug).join(", ")}`)}let f=l;C(f)||p(`This login has View access in ${f.slug}; syncing needs Publish. Run \`dockito login --update\`.`);let[w,...O]=m.split("/").filter(Boolean);w||p("--to takes a project, or project/folder."),O.length>2&&p(`Folders go at most ${2} deep inside a project.`);let k=K(f,w);if(!k&&!n.create)throw new N("no_project",`No project "${w}" in ${f.slug}. Projects: ${f.projects.map(r=>r.slug).join(", ")||"none"}.
11
- To make it, run again with --create.`,{workspace:f.slug,projects:f.projects.map(r=>r.slug)});let v=sn(c,i);v.length||p(`No ${s.kinds.flatMap(r=>r.extensions).join(", ")} files in ${u}.`);let U=2-O.length,D=v.filter(r=>r.split("/").length-1>U);if(D.length&&!n.fold){let r=y=>y.split("/").slice(0,U).join("/")||"the project's top",d=`${Q(D.length,"file")} ${D.length===1?"is":"are"} more than ${2} folders deep inside the project. With --fold they go in ${[...new Set(D.map(r))].join(", ")}; where two would share a name, the folders left out go in front (plans/archive/grok/README.md \u2192 grok-README.md).`;if(!o)throw new N("too_deep",`${d}
12
- Ask the user, then run again with --fold.`,{files:D});if($(d),!await _e("Fold them like that?"))return $(g("Nothing changed."));n.fold=!0}let ze=r=>{let d=r.split("/");return{folder:[...O,...d.slice(0,-1).slice(0,U)],dropped:d.slice(0,-1).slice(U),file:d[d.length-1]}},Oe=new Map;for(let r of v){let d=ze(r),y=[...d.folder,d.file].join("/").toLowerCase();Oe.set(y,(Oe.get(y)??0)+1)}let Ve=k?await e.files(f.id,k.id):[],Z=q(k),H=v.map(r=>{let{folder:d,dropped:y,file:j}=ze(r),F=y.length&&Oe.get([...d,j].join("/").toLowerCase())>1?[...y,j].join("-"):j,P=new Uint8Array(Xt(L(c,r))),B={rel:r,name:F,folder:d,bytes:P,hash:nn(P),action:"new",doc:null,others:[],assets:[]};/\.html?$/i.test(r)&&(B.assets=rn(new TextDecoder().decode(P),L(c,r,".."),i));let rt=Z.idOf(d);if(rt===void 0)return B;let ee=te(Ve,rt,F);return ee.length>1?{...B,action:"ambiguous",others:ee}:ee.length?{...B,doc:ee[0],action:ee[0].sha256===B.hash?"same":"update"}:B}),Ye=H.filter(r=>r.bytes.length>f.maxFile);Ye.length&&p(`Over the ${ie(f.maxFile)} limit per file in ${f.slug}:
13
- ${Ye.map(r=>` ${r.rel} ${ie(r.bytes.length)}`).join(`
14
- `)}`);let Se=new Map;for(let r of H){let d=[...r.folder,r.name].join("/").toLowerCase();Se.set(d,[...Se.get(d)??[],r.rel])}let Xe=[...Se.values()].filter(r=>r.length>1);Xe.length&&p(`These files would be the same doc; rename one of each:
4
+ `),J=class extends Error{},p=e=>{throw new J(e)},G={view:"View",publish:"Publish",everything:"Everything"};function me(e){let t=Math.round((Date.now()-new Date(e).getTime())/1e3);if(t<60)return"just now";let n=Math.round(t/60);if(n<60)return`${n} min ago`;let o=Math.round(n/60);if(o<24)return`${o} h ago`;let s=Math.round(o/24);return s<30?s===1?"yesterday":`${s} days ago`:new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}function se(e){let t=o=>o.replace(/\x1b\[[0-9;]*m/g,""),n=e[0]?.map((o,s)=>Math.max(...e.map(i=>t(i[s]??"").length)))??[];for(let o of e)h(o.map((s,i)=>i===o.length-1?s:s+" ".repeat(n[i]-t(s).length)).join(" ").trimEnd())}function re(e,t){switch(e.code){case"offline":return`Couldn't reach Dockito at ${t}.`;case"invalid_token":return"Your login has ended or was signed out. Run `dockito login`.";case"token_level":return`This needs ${G[e.detail.needs]??e.detail.needs} access; this login has ${G[e.detail.has]??e.detail.has}. Run \`dockito login --update\`.`;case"not_for_tokens":return"That can only be done in the browser.";case"not_found":return"Not found, or this login has no access to it. `dockito whoami` lists what it can reach.";case"forbidden":return"You can open this but not change it. Ask its owner or a workspace admin for Edit.";case"public_off":return"This workspace doesn't allow public links. Its owner can turn them on in Workspace settings.";case"deleted":return"That doc was deleted.";case"conflict":return"Someone published a newer version since yours. Get it with `dockito get`, or publish anyway with --force.";case"rate_limited":return"Too many requests. Try again in a minute.";case"too_large":return`The file is too large (limit ${Math.round(Number(e.detail.limit??0)/1048576)} MB).`;case"out_of_space":return"The workspace is out of storage.";case"too_many_saved":return`This doc already keeps ${e.detail.limit} saved points, the most it can, so nothing changed. ${at(e.detail.oldest)} In the app, use Don't keep on one in History, then save again.`;case"unsupported":return"Dockito takes .md and .html files.";case"other_kind":return"That doc is a different kind of file (Markdown and HTML can't replace each other).";case"empty":return"The file is empty.";case"not_text":return"The file isn't text.";case"too_deep":return"Folders go at most 2 deep inside a project.";case"invalid_name":return"Names need 1 to 80 characters.";case"folder_gone":return"That project or folder no longer exists.";default:return typeof e.detail.message=="string"?e.detail.message:`Dockito said: ${e.code}`}}var Ie=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI,Y=e=>process.stdout.write(e);function ct(e,t,n=0){return new Promise((o,s)=>{let i=n,a=l=>{l||Y(`\x1B[${t.length}A`);for(let[m,f]of t.entries())Y(`\x1B[2K${m===i?Ee("\u276F "+f.label):" "+f.label}${f.hint?g(" "+f.hint):""}
5
+ `)};Y(`${b("?")} ${S(e)} ${g("(\u2191\u2193, Enter)")}
6
+ \x1B[?25l`),a(!0),_t(process.stdin),process.stdin.setRawMode(!0),process.stdin.resume();let c=()=>{process.stdin.off("keypress",d),process.stdin.setRawMode(!1),process.stdin.pause(),Y("\x1B[?25h")},d=(l,m)=>{if(m.ctrl&&m.name==="c")return c(),Y(`
7
+ `),s(new ie);if(m.name==="up"||m.name==="k")i=(i-1+t.length)%t.length;else if(m.name==="down"||m.name==="j")i=(i+1)%t.length;else return m.name==="return"?(c(),Y(`\x1B[${t.length+1}A\x1B[0J${b("?")} ${S(e)} ${Ee(t[i].label)}
8
+ `),o(i)):void 0;a(!1)};process.stdin.on("keypress",d)})}async function we(e,t=""){let n=Rt({input:process.stdin,output:process.stdout});try{return(await n.question(`${b("?")} ${S(e)}${t?g(` (${t})`):""} `)).trim()||t}finally{n.close()}}async function _e(e,t=!0){let n=(await we(`${e} ${g(t?"[Y/n]":"[y/N]")}`)).toLowerCase();return n?n.startsWith("y"):t}var ie=class extends Error{};import{homedir as Lt}from"node:os";import{join as Re}from"node:path";var Ft="https://dockito-app.dockito.workers.dev";function lt(){return process.env.DOCKITO_CONFIG_DIR?process.env.DOCKITO_CONFIG_DIR:process.platform==="win32"&&process.env.APPDATA?Re(process.env.APPDATA,"dockito"):Re(process.env.XDG_CONFIG_HOME||Re(Lt(),".config"),"dockito")}var dt=()=>(process.env.DOCKITO_URL||Ft).replace(/\/+$/,""),ut=e=>new URL(e).host;import{spawnSync as Mt}from"node:child_process";import{chmodSync as Kt,existsSync as Le,mkdirSync as Ht,readFileSync as Ut,rmSync as pt,writeFileSync as Wt}from"node:fs";import{dirname as Bt,join as qt}from"node:path";var V="dockito";function X(e,t,n){try{let o=Mt(e,t,{input:n,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return o.error?null:o}catch{return null}}var ae=e=>qt(lt(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function Jt(e,t){if(process.platform==="darwin"){let n=X("security",["-i"],`add-generic-password -U -s ${V} -a ${e} -l Dockito -w ${t}
9
+ `);return!!n&&n.status===0&&!/error/i.test(n.stderr??"")}if(process.platform==="linux"){let n=X("secret-tool",["store","--label=Dockito","service",V,"account",e],t);return!!n&&n.status===0}return!1}function Gt(e){let t=process.platform==="darwin"?X("security",["find-generic-password","-s",V,"-a",e,"-w"]):process.platform==="linux"?X("secret-tool",["lookup","service",V,"account",e]):null;return(t?.status===0?t.stdout.trim():"")||null}function zt(e){process.platform==="darwin"?X("security",["delete-generic-password","-s",V,"-a",e]):process.platform==="linux"&&X("secret-tool",["clear","service",V,"account",e])}function ht(e,t){if(Jt(e,t))return Le(ae(e))&&pt(ae(e)),"keychain";let n=ae(e);return Ht(Bt(n),{recursive:!0,mode:448}),Wt(n,t,{mode:384}),Kt(n,384),"file"}function ye(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let t=Gt(e);if(t)return t;let n=ae(e);return Le(n)&&Ut(n,"utf8").trim()||null}function gt(e){zt(e);let t=ae(e);Le(t)&&pt(t)}var N=class extends Error{constructor(n,o,s={}){super(o);this.code=n;this.data=s}code;data};function ke(e,t){let n=e.workspaces;if(t)return n.find(s=>s.slug===t)??p(`This login can't reach workspace "${t}". \`dockito tree\` lists the ones it can.`);let o=n.length===1?n[0]:n.find(s=>s.kind==="personal"&&s.role==="owner");if(o)return o;throw new N("needs_workspace",`Pick a workspace with -w: ${n.map(s=>s.slug).join(", ")}`,{workspaces:n.map(s=>s.slug)})}function Fe(e,t){if(!t)return{project:null,folder:null,label:`${e.slug} Docs`};let[n,...o]=t.split("/").filter(Boolean),s=H(e,n);if(!s)return p(`No project "${n}" in ${e.slug}. \`dockito tree\` lists them; \`dockito mkdir ${t}\` makes it.`);let i=q(s).idOf(o);return i===void 0?p(`No folder "${o.join("/")}" in ${s.slug}. \`dockito mkdir ${s.slug}/${o.join("/")}\` makes it.`):{project:s.id,folder:i,label:[e.slug,s.slug,...o].join("/")}}import{createHash as Yt}from"node:crypto";import{spawnSync as Vt}from"node:child_process";import{existsSync as le,readFileSync as Xt,readdirSync as Qt,statSync as wt}from"node:fs";import{basename as ft,join as L,relative as Zt,resolve as Me,sep as en}from"node:path";var tn=4,ce=e=>e<1024*1024?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,nn=e=>Yt("sha256").update(e).digest("hex"),yt=e=>e.split(en).join("/"),Q=(e,t)=>`${e} ${t}${e===1?"":"s"}`,on=(e,t)=>{let n=/\.[^./]+$/.exec(t.toLowerCase())?.[0]??"";return e.find(o=>o.extensions.includes(n))?.id??null};function $e(e,t){let n=Vt("git",e,{cwd:t,encoding:"utf8",maxBuffer:67108864});return n.error||n.status!==0?null:n.stdout}function sn(e,t){let n=$e(["ls-files","-z","--cached","--others","--exclude-standard","--","."],e);return(n!==null?n.split("\0").filter(Boolean):(function s(i){return Qt(L(e,i),{withFileTypes:!0}).flatMap(a=>a.name.startsWith(".")||a.name==="node_modules"?[]:a.isDirectory()?s(L(i,a.name)):[yt(L(i,a.name))])})("")).filter(s=>t(s)&&le(L(e,s))&&wt(L(e,s)).isFile()).sort()}function rn(e,t,n){let o=[...e.matchAll(/\b(?:src|href)=["']([^"'#?]+)/gi)].map(s=>s[1]);return[...new Set(o.filter(s=>!/^([a-z]+:|\/\/|\/)/i.test(s)&&!n(s)&&le(L(t,s))))]}function mt(e,t){let n=(i,a,c="")=>`dockito sync ${t} -w ${i} --to ${a}${c}`,o=e.workspaces.filter(I).flatMap(i=>[...i.projects.map(a=>({workspace:i.slug,project:a.slug,name:a.name,docs:a.docs,command:n(i.slug,a.slug)})),{workspace:i.slug,project:null,name:"a new project",docs:0,command:n(i.slug,"<name>"," --create")}]),s=Math.max(...o.map(i=>i.command.length));throw new N("needs_place",[`Which project should ${t==="."?"this folder":t+"/"} go to? Ask the user, then run one of:`,...o.map(i=>` ${i.command.padEnd(s)} ${g(i.project?`${i.name} \xB7 ${Q(i.docs,"doc")}`:i.name)}`),g("Subfolders become folders. `dockito tree` shows every place.")].join(`
10
+ `),{choices:o})}async function kt(e,t,n){let o=Ie()&&!n.json,s=await e.tree(),i=r=>!!on(s.kinds,r);s.workspaces.some(I)||p("This login can't publish anywhere. Run `dockito login --update`.");let a=t;a||(o||mt(s,le("docs")?"docs":"."),a=await we("Folder to sync",le("docs")?"docs":"."));let c=Me(a);(!le(c)||!wt(c).isDirectory())&&p(`${a} isn't a folder.`);let d=yt(Zt(process.cwd(),c))||".",l;n.workspace&&(l=s.workspaces.find(r=>r.slug===n.workspace)??p(`This login can't reach workspace "${n.workspace}".`));let m=n.to;if(!m){o||mt(s,d);let r=(l?[l]:s.workspaces.filter(I)).flatMap(y=>[...y.projects.map(j=>({w:y,p:j.slug,label:j.name,hint:`${y.slug} \xB7 ${Q(j.docs,"doc")}`})),{w:y,p:null,label:"New project",hint:y.slug}]),u=r[await ct("Project",r)];l=u.w,m=u.p??await we("Project name",ft(c===process.cwd()?c:Me(c,".."))),n.create||=!u.p}if(!l){let r=s.workspaces.filter(y=>I(y)&&H(y,m.split("/")[0])),u=s.workspaces.find(y=>y.kind==="personal"&&y.role==="owner");if(l=r.length===1?r[0]:s.workspaces.length===1?s.workspaces[0]:!r.length&&u?u:void 0,!l)throw new N("needs_workspace",`Which workspace? Run again with -w: ${(r.length?r:s.workspaces).map(y=>y.slug).join(", ")}`)}let f=l;I(f)||p(`This login has View access in ${f.slug}; syncing needs Publish. Run \`dockito login --update\`.`);let[w,...O]=m.split("/").filter(Boolean);w||p("--to takes a project, or project/folder."),O.length>2&&p(`Folders go at most ${2} deep inside a project.`);let k=H(f,w);if(!k&&!n.create)throw new N("no_project",`No project "${w}" in ${f.slug}. Projects: ${f.projects.map(r=>r.slug).join(", ")||"none"}.
11
+ To make it, run again with --create.`,{workspace:f.slug,projects:f.projects.map(r=>r.slug)});let v=sn(c,i);v.length||p(`No ${s.kinds.flatMap(r=>r.extensions).join(", ")} files in ${d}.`);let U=2-O.length,D=v.filter(r=>r.split("/").length-1>U);if(D.length&&!n.fold){let r=y=>y.split("/").slice(0,U).join("/")||"the project's top",u=`${Q(D.length,"file")} ${D.length===1?"is":"are"} more than ${2} folders deep inside the project. With --fold they go in ${[...new Set(D.map(r))].join(", ")}; where two would share a name, the folders left out go in front (plans/archive/grok/README.md \u2192 grok-README.md).`;if(!o)throw new N("too_deep",`${u}
12
+ Ask the user, then run again with --fold.`,{files:D});if($(u),!await _e("Fold them like that?"))return $(g("Nothing changed."));n.fold=!0}let ze=r=>{let u=r.split("/");return{folder:[...O,...u.slice(0,-1).slice(0,U)],dropped:u.slice(0,-1).slice(U),file:u[u.length-1]}},Oe=new Map;for(let r of v){let u=ze(r),y=[...u.folder,u.file].join("/").toLowerCase();Oe.set(y,(Oe.get(y)??0)+1)}let Ye=k?await e.files(f.id,k.id):[],te=q(k),K=v.map(r=>{let{folder:u,dropped:y,file:j}=ze(r),F=y.length&&Oe.get([...u,j].join("/").toLowerCase())>1?[...y,j].join("-"):j,P=new Uint8Array(Xt(L(c,r))),B={rel:r,name:F,folder:u,bytes:P,hash:nn(P),action:"new",doc:null,others:[],assets:[]};/\.html?$/i.test(r)&&(B.assets=rn(new TextDecoder().decode(P),L(c,r,".."),i));let rt=te.idOf(u);if(rt===void 0)return B;let ne=oe(Ye,rt,F);return ne.length>1?{...B,action:"ambiguous",others:ne}:ne.length?{...B,doc:ne[0],action:ne[0].sha256===B.hash?"same":"update"}:B}),Ve=K.filter(r=>r.bytes.length>f.maxFile);Ve.length&&p(`Over the ${ce(f.maxFile)} limit per file in ${f.slug}:
13
+ ${Ve.map(r=>` ${r.rel} ${ce(r.bytes.length)}`).join(`
14
+ `)}`);let Se=new Map;for(let r of K){let u=[...r.folder,r.name].join("/").toLowerCase();Se.set(u,[...Se.get(u)??[],r.rel])}let Xe=[...Se.values()].filter(r=>r.length>1);Xe.length&&p(`These files would be the same doc; rename one of each:
15
15
  ${Xe.map(r=>" "+r.join(" and ")).join(`
16
- `)}`);let I=[],Qe=new Set;for(let r of H)for(let d=1;d<=r.folder.length;d++){let y=r.folder.slice(0,d),j=y.join("/").toLowerCase();Z.idOf(y)===void 0&&!Qe.has(j)&&(Qe.add(j),I.push(y))}I.sort((r,d)=>r.length-d.length);let A=H.filter(r=>r.action==="new"||r.action==="update"),Ze=H.filter(r=>r.action==="ambiguous"),Ae=H.filter(r=>r.action==="same").length,Dt=new Set(H.flatMap(r=>r.doc?[r.doc.id]:r.others.map(d=>d.id))),De=Ve.filter(r=>!Dt.has(r.id)).length,et=`${f.slug}/${k?.slug??w}${O.length?"/"+O.join("/"):""}`,ue={workspace:f.slug,to:et,create:{project:k?null:w,folders:I.map(r=>r.join("/"))},docs:A.map(r=>({file:r.rel,action:r.action,folder:r.folder.join("/")||null,name:r.name,link:r.doc?e.linkOf(r.doc.slug):null})),unchanged:Ae,ambiguous:Ze.map(r=>({file:r.rel,docs:r.others.map(d=>e.linkOf(d.slug))})),notHere:De};if(!n.json){h(`${S(u==="."?"./":u+"/")} \u2192 ${S(et)}`),k||h(` ${b("+ project")} ${w}`);for(let d of I)h(` ${b("+ folder ")} ${d.join("/")}`);if(A.length>15)for(let[d,y]of[["new",b("+ new ")],["update",R("~ update ")]]){let j=new Map;for(let F of A.filter(P=>P.action===d))j.set(F.folder.join("/"),[...j.get(F.folder.join("/"))??[],F.name]);for(let[F,P]of j)h(` ${y} ${P.length} in ${F||"the project's top"}: ${g(P.slice(0,4).join(", ")+(P.length>4?`, ${P.length-4} more`:""))}`)}else for(let d of A){let y=d.name!==ft(d.rel)?g(` \u2192 ${[...d.folder,d.name].join("/")}`):"";h(` ${d.action==="new"?b("+ new "):R("~ update ")} ${d.rel}${y}`)}Ae&&h(g(` ${Ae} unchanged`));for(let d of Ze)h(` ${_("? skip ")} ${d.rel} ${g(`${d.others.length} docs there have this name: ${d.others.map(y=>e.linkOf(y.slug)).join(", ")}`)}`);De&&h(g(` ${Q(De,"doc")} in the project with no file here, left as they are`)),A.length&&h(g(` ${Q(A.length,"file")} to send, ${ie(A.reduce((d,y)=>d+y.bytes.length,0))}`));let r=A.filter(d=>d.assets.length);r.length&&h(R(` ! ${r.length===1?r[0].rel:`${r.length} HTML files`} load ${[...new Set(r.flatMap(d=>d.assets))].join(", ")} from next to ${r.length===1?"it":"them"}; only HTML files are published`))}if(!A.length&&k&&!I.length){n.json?h(JSON.stringify({...ue,applied:!0},null,2)):h(`${b("\u2713")} Up to date`);return}if(n["dry-run"]){n.json&&h(JSON.stringify({...ue,applied:!1},null,2));return}if((!k||I.length)&&!n.yes){let r=["dockito sync",u,`-w ${f.slug}`,`--to ${m}`,n.create&&!k?"--create":"",n.fold?"--fold":"","--yes"].filter(Boolean).join(" ");if(!o){if(n.json)return h(JSON.stringify({...ue,applied:!1,next:r},null,2));throw new N("confirm",`
17
- Nothing changed yet. Show the user this plan, then run: ${r}`,{next:r})}if(!await _e("Apply?"))return $(g("Nothing changed."))}if(!k){let r=await e.createProject(f.id,w);k={id:r.id,slug:r.slug,name:w,docs:0,folders:[]},n.json||h(`${b("\u2713")} Project ${w}`)}let Ne=k;for(let r of I){let d=await e.createFolder(Ne.id,r[r.length-1],r.length>1?Z.idOf(r.slice(0,-1)):null);Z.add(r,d.id)}I.length&&!n.json&&h(`${b("\u2713")} ${Q(I.length,"folder")}`);let pe=$e(["rev-parse","--show-toplevel"],c)?.trim()??null,tt=pe?$e(["rev-parse","HEAD"],pe)?.trim()??null:null,Nt=new Set(pe?($e(["status","--porcelain","-z","--","."],c)??"").split("\0").filter(Boolean).map(r=>Me(pe,r.slice(3))):[]),W=[],he=null,Pe=[...A],ge=!n.json&&Ce(),Pt=A.reduce((r,d)=>r+d.bytes.length,0),nt=0,ot=()=>ge&&process.stdout.write(`\r\x1B[2K${g(`Sending ${W.length} of ${A.length} \xB7 ${ie(nt)} of ${ie(Pt)}`)}`);async function Et(r){let d=tt&&!Nt.has(L(c,r.rel))?tt:null;for(;;)try{let y=r.doc?await e.update(r.doc.id,r.name,r.bytes,{commit:d}):await e.create(f.id,r.name,r.bytes,{project:Ne.id,folder:Z.idOf(r.folder)??null});return{file:r.rel,action:r.action,link:e.linkOf(y.slug)}}catch(y){if(y instanceof x&&y.code==="rate_limited"){he||(n.json||$(g("\rUpload limit reached; carrying on in a minute\u2026")),he=new Promise(j=>setTimeout(j,3e4)).then(()=>{he=null})),await he;continue}return{file:r.rel,action:r.action,error:y instanceof x?oe(y,e.url):String(y)}}}ot(),await Promise.all(Array.from({length:Math.min(tn,Pe.length)},async()=>{for(let r=Pe.shift();r;r=Pe.shift()){let d=await Et(r);W.push(d),nt+=r.bytes.length,ge&&process.stdout.write("\r\x1B[2K"),d.error&&!n.json?h(`${_("\u2717")} ${d.file} ${_(d.error)}`):!n.json&&!ge&&h(`${b("\u2713")} ${d.file} ${g(d.link)}`),ot()}})),ge&&process.stdout.write("\r\x1B[2K");let Ee=W.filter(r=>r.error),st=`${e.url}/${f.slug}/project/${Ne.slug}`;n.json?h(JSON.stringify({...ue,applied:!0,link:st,results:W},null,2)):h(`${Ee.length?R(`${W.length-Ee.length} of ${W.length} sent`):b("\u2713 Synced")} ${g(st)}`),Ee.length&&(process.exitCode=1)}var He=`Dockito hosts Markdown and HTML docs, and live sheets, at a short link,
16
+ `)}`);let E=[],Qe=new Set;for(let r of K)for(let u=1;u<=r.folder.length;u++){let y=r.folder.slice(0,u),j=y.join("/").toLowerCase();te.idOf(y)===void 0&&!Qe.has(j)&&(Qe.add(j),E.push(y))}E.sort((r,u)=>r.length-u.length);let A=K.filter(r=>r.action==="new"||r.action==="update"),Ze=K.filter(r=>r.action==="ambiguous"),Ae=K.filter(r=>r.action==="same").length,Dt=new Set(K.flatMap(r=>r.doc?[r.doc.id]:r.others.map(u=>u.id))),De=Ye.filter(r=>!Dt.has(r.id)).length,et=`${f.slug}/${k?.slug??w}${O.length?"/"+O.join("/"):""}`,ue={workspace:f.slug,to:et,create:{project:k?null:w,folders:E.map(r=>r.join("/"))},docs:A.map(r=>({file:r.rel,action:r.action,folder:r.folder.join("/")||null,name:r.name,link:r.doc?e.linkOf(r.doc.slug):null})),unchanged:Ae,ambiguous:Ze.map(r=>({file:r.rel,docs:r.others.map(u=>e.linkOf(u.slug))})),notHere:De};if(!n.json){h(`${S(d==="."?"./":d+"/")} \u2192 ${S(et)}`),k||h(` ${b("+ project")} ${w}`);for(let u of E)h(` ${b("+ folder ")} ${u.join("/")}`);if(A.length>15)for(let[u,y]of[["new",b("+ new ")],["update",R("~ update ")]]){let j=new Map;for(let F of A.filter(P=>P.action===u))j.set(F.folder.join("/"),[...j.get(F.folder.join("/"))??[],F.name]);for(let[F,P]of j)h(` ${y} ${P.length} in ${F||"the project's top"}: ${g(P.slice(0,4).join(", ")+(P.length>4?`, ${P.length-4} more`:""))}`)}else for(let u of A){let y=u.name!==ft(u.rel)?g(` \u2192 ${[...u.folder,u.name].join("/")}`):"";h(` ${u.action==="new"?b("+ new "):R("~ update ")} ${u.rel}${y}`)}Ae&&h(g(` ${Ae} unchanged`));for(let u of Ze)h(` ${_("? skip ")} ${u.rel} ${g(`${u.others.length} docs there have this name: ${u.others.map(y=>e.linkOf(y.slug)).join(", ")}`)}`);De&&h(g(` ${Q(De,"doc")} in the project with no file here, left as they are`)),A.length&&h(g(` ${Q(A.length,"file")} to send, ${ce(A.reduce((u,y)=>u+y.bytes.length,0))}`));let r=A.filter(u=>u.assets.length);r.length&&h(R(` ! ${r.length===1?r[0].rel:`${r.length} HTML files`} load ${[...new Set(r.flatMap(u=>u.assets))].join(", ")} from next to ${r.length===1?"it":"them"}; only HTML files are published`))}if(!A.length&&k&&!E.length){n.json?h(JSON.stringify({...ue,applied:!0},null,2)):h(`${b("\u2713")} Up to date`);return}if(n["dry-run"]){n.json&&h(JSON.stringify({...ue,applied:!1},null,2));return}if((!k||E.length)&&!n.yes){let r=["dockito sync",d,`-w ${f.slug}`,`--to ${m}`,n.create&&!k?"--create":"",n.fold?"--fold":"","--yes"].filter(Boolean).join(" ");if(!o){if(n.json)return h(JSON.stringify({...ue,applied:!1,next:r},null,2));throw new N("confirm",`
17
+ Nothing changed yet. Show the user this plan, then run: ${r}`,{next:r})}if(!await _e("Apply?"))return $(g("Nothing changed."))}if(!k){let r=await e.createProject(f.id,w);k={id:r.id,slug:r.slug,name:w,docs:0,folders:[]},n.json||h(`${b("\u2713")} Project ${w}`)}let Ne=k;for(let r of E){let u=await e.createFolder(Ne.id,r[r.length-1],r.length>1?te.idOf(r.slice(0,-1)):null);te.add(r,u.id)}E.length&&!n.json&&h(`${b("\u2713")} ${Q(E.length,"folder")}`);let pe=$e(["rev-parse","--show-toplevel"],c)?.trim()??null,tt=pe?$e(["rev-parse","HEAD"],pe)?.trim()??null:null,Nt=new Set(pe?($e(["status","--porcelain","-z","--","."],c)??"").split("\0").filter(Boolean).map(r=>Me(pe,r.slice(3))):[]),W=[],he=null,Pe=[...A],ge=!n.json&&Ie(),Pt=A.reduce((r,u)=>r+u.bytes.length,0),nt=0,ot=()=>ge&&process.stdout.write(`\r\x1B[2K${g(`Sending ${W.length} of ${A.length} \xB7 ${ce(nt)} of ${ce(Pt)}`)}`);async function Ct(r){let u=tt&&!Nt.has(L(c,r.rel))?tt:null;for(;;)try{let y=r.doc?await e.update(r.doc.id,r.name,r.bytes,{commit:u}):await e.create(f.id,r.name,r.bytes,{project:Ne.id,folder:te.idOf(r.folder)??null});return{file:r.rel,action:r.action,link:e.linkOf(y.slug)}}catch(y){if(y instanceof x&&y.code==="rate_limited"){he||(n.json||$(g("\rUpload limit reached; carrying on in a minute\u2026")),he=new Promise(j=>setTimeout(j,3e4)).then(()=>{he=null})),await he;continue}return{file:r.rel,action:r.action,error:y instanceof x?re(y,e.url):String(y)}}}ot(),await Promise.all(Array.from({length:Math.min(tn,Pe.length)},async()=>{for(let r=Pe.shift();r;r=Pe.shift()){let u=await Ct(r);W.push(u),nt+=r.bytes.length,ge&&process.stdout.write("\r\x1B[2K"),u.error&&!n.json?h(`${_("\u2717")} ${u.file} ${_(u.error)}`):!n.json&&!ge&&h(`${b("\u2713")} ${u.file} ${g(u.link)}`),ot()}})),ge&&process.stdout.write("\r\x1B[2K");let Ce=W.filter(r=>r.error),st=`${e.url}/${f.slug}/project/${Ne.slug}`;n.json?h(JSON.stringify({...ue,applied:!0,link:st,results:W},null,2)):h(`${Ce.length?R(`${W.length-Ce.length} of ${W.length} sent`):b("\u2713 Synced")} ${g(st)}`),Ce.length&&(process.exitCode=1)}var Ke=`Dockito hosts Markdown and HTML docs, and live sheets, at a short link,
18
18
  with History.
19
19
 
20
20
  workspace a person's or a team's space; its slug is in every link
@@ -120,7 +120,8 @@ version to apply changes at; \`dockito read\` gives its rows.
120
120
  dockito get <doc> --lines 120-260 --text`},read:{use:"read <doc> [range]",about:"A sheet's rows, a range or totals",page:`Prints rows of a sheet as a text table: row numbers, values as shown, and
121
121
  the version (v N) to apply changes at. A range, or a sheet's rows under its
122
122
  header row, filtered and sorted; with --group or --totals, totals instead.
123
- 200 rows unless --limit says, at most 1,000; page on with --from.
123
+ 200 rows unless --limit says, at most 1,000; page on with --from. --chart
124
+ gives a chart's points instead.
124
125
 
125
126
  [range] Tasks!A1:G40, Tasks!B:B, Tasks!3:5, a defined name,
126
127
  or E17 when there's one sheet
@@ -134,14 +135,18 @@ header row, filtered and sorted; with --group or --totals, totals instead.
134
135
  --totals <list> count, sum Hours, avg Hours, min Due, max Due
135
136
  --from <n>, --limit <n> Which rows: from 1; 200 by default, 1,000 at most
136
137
  --formulas Formulas with their results: =F2*50 \u2192 50
137
- --styles Formatting in words, merges, sizes, frozen panes
138
+ --styles Formatting in words, merges, sizes, frozen panes;
139
+ cells a conditional format colours say so
138
140
  --csv As CSV instead of a table
141
+ --chart <n> Chart #n's points as a table, as drawn (alone)
139
142
 
140
143
  dockito read <doc> --where "Status = Blocked" --select ID,Owner,Due
141
144
  dockito read <doc> --group Owner --totals "count,sum Hours"
142
- dockito read <doc> "Tasks!A1:G20" --formulas`},apply:{use:"apply <doc> <changes.json>",about:"Change a sheet at the version you read",page:`Changes a sheet, read at --base: the version (v N) your last get, read or
145
+ dockito read <doc> "Tasks!A1:G20" --formulas
146
+ dockito read <doc> --chart 2`},apply:{use:"apply <doc> <changes.json>",about:"Change a sheet at the version you read",page:`Changes a sheet, read at --base: the version (v N) your last get, read or
143
147
  changes showed. Addresses, rows and headers mean what they meant then,
144
- wherever they moved since. Every change applies, or none: a cell someone
148
+ wherever they moved since; so do chart and format numbers (#N, by order in
149
+ the outline). Every change applies, or none: a cell someone
145
150
  changed since is refused, naming who (exit 1). Read again and apply on the
146
151
  new version. Prints each cell old \u2192 new and the new version.
147
152
 
@@ -178,6 +183,49 @@ The file (or - for stdin) is a list of changes, or {"base": N, "ops": [...],
178
183
  frozenCols, hidden, tabColor), setName, deleteName, setTable, deleteTable
179
184
  {"addTable": {"range": "Tasks!A1:D40", "name": "Tasks"}}: a header row
180
185
  and banded rows in one change; a cell's own fill shows over them
186
+ {"validate": {"range": "Tasks!D2:D", "list": ["Open", "Done"], "reject": true}}:
187
+ a rule from row 2 down (rows added later too); reject refuses other
188
+ values, else they're taken and marked. One kind each: list,
189
+ from ("Lists!A2:A20"), checkbox (true), number ({"between": [0, 100]},
190
+ "at most", "at least", \u2026), date ({"after": "2026-01-01"}), text
191
+ ({"contains": "x"}), email, url, length ({"at most": 50}), formula
192
+ ("=C2<D2"); "help" is shown with a refusal; "remove": true takes it off
193
+ {"conditional": {"range": "Tasks!D2:D", "text is": "Blocked",
194
+ "format": {"fill": "#f6d3d0", "bold": true}}}: the outline lists rules
195
+ in order (format #N); the first that matches a cell wins; new ones go
196
+ last. One condition each: empty, "not empty", "text contains", "text not
197
+ contains", "text starts with", "text ends with", "text is", "date is",
198
+ "date before", "date after" ("today", "past week", "2026-01-01", \u2026),
199
+ number ({"greater than": 8}), formula ("=$F2<TODAY()"), scale ({"min":
200
+ "#ffffff", "max": "#57bb8a"}, no format), duplicate, unique, top (10 or
201
+ "10%"), bottom, "above average", "below average". Format: bold, italic,
202
+ underline, strike, color, fill. "before": 2 or "first": true places it;
203
+ {"rule": 3, ...} changes or moves rule 3; "remove": true with rule or
204
+ range takes rules off
205
+ {"replace": {"find": "Lee", "with": "Priya", "in": "Owner"}}: find and
206
+ replace as one change; "in" is a column by header or letter (its rows
207
+ under the header) or a range ("Tasks!C2:C"); left out, the whole sheet
208
+ ("sheet" names it). "with" may be "". "case": true, "whole": true (the
209
+ whole cell), "regex": true (RE2; $1 in "with"), "formulas": true
210
+ (change formulas; else one matches by its result and is left). Says
211
+ what was left and why; over 100,000 cells changes nothing
212
+ {"chart": {"range": "Tasks!A1:C40", "type": "column", "title": "Costs"}}:
213
+ the outline lists charts as chart #N. "header" (first row names the
214
+ series) and "labels" (first column labels the points) are guessed when
215
+ left out. Type: column, bar, line, area, pie, donut, scatter (first
216
+ column: x), or "stacked column", "stacked bar", "stacked area",
217
+ "doughnut", "horizontal bar"; "stacked": true for column, bar, area.
218
+ "at": "E2", "below", "beside", "below chart #2" or "beside chart #2";
219
+ left out, beside the range on its first row, past whatever fills those
220
+ rows (under a wide range when that's clear), never over filled cells
221
+ or another chart (the sheet grows to fit). "width",
222
+ "height" in px, 40 to 4,000 (left out: fitted, 480 \xD7 300, wider past
223
+ 24 points; a pie 420 \xD7 300). "title" (left out: the series by the
224
+ label column, "Kitchen and Bath by Month"), "x title", "y title";
225
+ "legend": right, bottom, top, none; "colors": ["#4285f4", null] per
226
+ series. The answer says what it draws and warns about a total row or
227
+ column, or a column with no numbers. {"chart": 2, ...} changes chart 2;
228
+ {"chart": 2, "remove": true} removes it
181
229
  Values: a number, true, false, null (empty), a string ("=\u2026" is a formula),
182
230
  {"text": "=as typed"} or {"date": "2026-10-19"}.
183
231
 
@@ -202,11 +250,11 @@ Ends at the current version, to apply changes at.
202
250
  mcp), saved points with their notes, and the git commit.`},restore:{use:"restore <doc> <entry>",about:"Make a History entry current again",page:"Makes an older entry the current one, as a new entry. Nothing is lost."},save:{use:"save <doc> [entry] -m <note>",about:"Keep an entry as a saved point",page:`Keeps an entry (default: the current one) as a saved point, so History
203
251
  never drops it, with an optional note. A doc keeps up to 10.`},login:{use:"login [--update]",about:"Sign in through the browser",page:`Opens the browser, where you check a code and choose this login's access
204
252
  in each workspace: View, Publish or Everything. --update changes that access.
205
- --no-browser prints the link instead.`},logout:{use:"logout",about:"Sign out this machine",page:"Signs this machine out and removes its token."},whoami:{use:"whoami",about:"Who you are and what this login can reach",page:"Your name, the login's expiry, and its access in each workspace."},agent:{use:"agent",about:"What an AI agent needs to know",page:"Prints what Dockito is and how an AI agent should use this CLI."}};var qe="0.0.1-beta.11",E=dt(),ce=ut(E),Je=`dockito-cli/${qe} (${process.platform})`;function T(){let e=ye(ce);return e||p("Not logged in. Run `dockito login`."),fe({url:E,token:e,source:"cli",userAgent:Je})}function St(e){let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let o=an(t,n,{stdio:"ignore",detached:!0});return o.on("error",()=>{}),o.unref(),!0}catch{return!1}}var gn=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),fn=e=>new Promise(t=>setTimeout(t,e)),mn=()=>dn().replace(/\.local$/,"").slice(0,60)||"Unknown device";function M(e){return it(e)??p(`"${e}" isn't a doc link or code. \`dockito ls\` lists docs with their codes.`)}function vt(e){let t=un(Ot(e)),n=bt("git",["rev-parse","HEAD"],{cwd:t,encoding:"utf8"});if(n.error||n.status!==0)return null;let o=bt("git",["status","--porcelain","--",Be(e)],{cwd:t,encoding:"utf8"});return o.status!==0||o.stdout.trim()?null:n.stdout.trim()}var At=e=>cn("sha256").update(e).digest("hex");async function wn(e){let t=ye(ce);e.update&&!t&&p("Not logged in. Run `dockito login` first."),process.env.DOCKITO_TOKEN&&$(R("DOCKITO_TOKEN is set, so commands will keep using it instead of this login."));let n=fe({url:E,token:e.update?t:null,userAgent:Je}),o=await n.startLogin("Dockito CLI",mn()),s=!e["no-browser"]&&gn()&&St(o.verification_uri_complete);h(s?`Opening ${o.verification_uri_complete}`:`Open ${S(o.verification_uri)}`),h(`Code: ${S(o.user_code)}`),$(g("Waiting for approval\u2026"));let i=o.interval*1e3,a=Date.now()+o.expires_in*1e3;for(;Date.now()<a;){await fn(i);try{let c=await n.pollLogin(o.device_code);if(!c)continue;let u=ht(ce,c.token);h(`${b("\u2713")} Logged in as ${c.user.name}${c.user.username?g(` (${c.user.username})`):""}`);for(let l of c.workspaces)h(` ${l.name} ${g(`${l.slug} \xB7 ${G[l.level]}`)}`);u==="file"&&$(R("No keychain found, so the token is in a file only you can read, in ~/.config/dockito."));return}catch(c){if(c instanceof x&&c.code==="access_denied"&&p("Denied in the browser."),c instanceof x&&c.code==="expired_token"&&p("The code expired. Run `dockito login` again."),c instanceof x&&c.code==="offline"){i=Math.min(i*2,3e4);continue}throw c}}p("The code expired. Run `dockito login` again.")}async function yn(){let e=ye(ce);if(!e)return h("Not logged in.");process.env.DOCKITO_TOKEN&&p("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await fe({url:E,token:e,userAgent:Je}).logout()}catch(t){if(!(t instanceof x)||t.code!=="invalid_token"&&t.code!=="not_found")throw t}gt(ce),h(`${b("\u2713")} Logged out`)}async function kn(e){let t=await T().whoami();if(e.json)return h(JSON.stringify(t,null,2));h(`${t.user.name} ${g(`${t.user.username} \xB7 ${t.user.email}`)}`),t.token&&h(g(`${t.token.name} \xB7 ${t.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(t.token.expiresAt).toLocaleDateString()}`}`)),h(),ne(t.workspaces.map(n=>[n.slug,n.name,g(n.level?G[n.level]:n.role)])),E!=="https://dockito-app.dockito.workers.dev"&&$(g(`
206
- ${E}`))}async function bn(e,t){e.length||p("Name a file: `dockito publish notes.md --to <project/folder>`"),t.doc&&e.length>1&&p("--doc takes one file."),t.title!==void 0&&e.length>1&&p("--title takes one file."),t.title!==void 0&&!t.title.trim()&&p("--title needs a title."),t.public&&t.doc&&p("--public is only for a new doc; an existing one can be made public from Share in the app.");for(let l of e)(!Tt(l)||!ln(l).isFile())&&p(`${l} isn't a file${pn(l)?"":` (looked for ${Ot(l)})`}.`);let n=e.filter(Ke);n.length&&t.doc&&p("A .csv makes a new sheet; it can't replace a doc. Leave out --doc, or change a sheet with `dockito apply`."),n.length&&t.message&&p("Sheets don't keep saved points yet: publish the .csv without -m.");let o=T(),s=[];if(t.doc){let l=await o.doc(M(t.doc)),m=new Uint8Array(xe(e[0])),f=await o.update(l.doc.id,Be(e[0]),m,{note:t.message,commit:vt(e[0])});return t.json?h(JSON.stringify([{file:e[0],...f,link:o.linkOf(f.slug)}],null,2)):h(f.unchanged?`${g("=")} ${e[0]} ${g("unchanged")} ${o.linkOf(f.slug)}`:`${b("\u2713")} ${e[0]} \u2192 ${o.linkOf(f.slug)}`)}let i=await o.tree(),a=ke(i,t.workspace);C(a)||p(`This login has View access in ${a.slug}. Run \`dockito login --update\`.`);let c=Fe(a,t.to),u=await o.files(a.id,c.project);for(let l of e){let m=new Uint8Array(xe(l)),f=Be(l),w=t.new?[]:te(u,c.folder,f),O=w.filter(D=>D.kind==="sheet");O.length&&p(`${f} in ${c.label} is ${O.length>1?`${O.length} live sheets`:"a live sheet"} (${O.map(D=>o.linkOf(D.slug)).join(", ")}): change ${O.length>1?"one":"it"} with \`dockito apply\`. --new makes a new sheet from the file instead.`),w.length>1&&p(`${w.length} docs in ${c.label} are named ${f}: ${w.map(D=>o.linkOf(D.slug)).join(", ")}. Pick one with --doc, or publish a new one with --new.`);let k=w[0];if(k&&t.public&&!(await o.doc(k.slug)).doc.public&&p(`${f} is already a private doc in ${c.label} (${o.linkOf(k.slug)}). Only Share in the app can make it public; \`--new --public\` publishes a new public doc.`),k&&k.sha256===At(m)&&!t.message){s.push({file:l,id:k.id,slug:k.slug,version:k.version,unchanged:!0,link:o.linkOf(k.slug)}),t.json||h(`${g("=")} ${l} ${g("unchanged")} ${o.linkOf(k.slug)}`);continue}let v=k?await o.update(k.id,f,m,{note:t.message,commit:vt(l)}):await o.create(a.id,f,m,{project:c.project,folder:c.folder,public:t.public,title:t.title});!k&&t.message&&await o.save(v.id,v.version,t.message);let U="public"in v&&v.public===!0;s.push({file:l,...v,...Ke(l)?{type:"sheet"}:{},link:o.linkOf(v.slug),new:!k}),t.json||h(`${b("\u2713")} ${l} \u2192 ${o.linkOf(v.slug)} ${g(k?v.unchanged?"unchanged":"updated":`new ${Ke(l)?"sheet ":""}in ${c.label}`)}${U?` ${b("\xB7 public")}`:""}${t.message?g(` \xB7 saved: ${t.message}`):""}`)}t.json?h(JSON.stringify(s,null,2)):!t.to&&s.some(l=>l.new)&&$(g("In the workspace's Docs. To put docs in a project: --to <project/folder>; `dockito tree` lists them."))}async function $n(e){let t=await T().tree();if(e.json)return h(JSON.stringify(t,null,2));let n=o=>g(`${o} doc${o===1?"":"s"}`);for(let o of t.workspaces){h(`${S(o.slug)} ${o.name} ${g(`\xB7 ${o.level?G[o.level]:o.role}`)}`);let s=[[g("Docs"),"",n(o.docs)]];for(let i of o.projects){s.push([i.slug,i.name,n(i.docs)]);let a=(c,u)=>{for(let l of i.folders.filter(m=>m.parentId===c))s.push([`${u}/${l.name}`,"",n(l.docs)]),a(l.id,`${u}/${l.name}`)};a(null,i.slug)}ne(s.map(i=>[" ",...i])),h()}$(g("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function vn(e,t){e&&t.to&&e!==t.to&&p(`Two places: ${e} and --to ${t.to}. Give one.`);let n=e??t.to,o=T(),s=await o.tree(),i=ke(s,t.workspace),a=Fe(i,n),c=await o.docs(i.id,{project:a.project,folder:a.folder}),u=a.project?i.projects.find(w=>w.id===a.project):null,l=n?[]:i.projects,m=u?u.folders.filter(w=>w.parentId===a.folder):[];if(t.json)return h(JSON.stringify({workspace:i.slug,place:n??null,projects:l,folders:m,docs:c},null,2));let f=[];for(let w of l)f.push([S(`${w.slug}/`),w.name,g(`${w.docs} doc${w.docs===1?"":"s"}`),""]);for(let w of m)f.push([S(`${w.name}/`),"",g(`${w.docs} doc${w.docs===1?"":"s"}`),""]);for(let w of c)f.push([w.slug,w.title+g(w.kind==="html"?" .html":w.kind==="sheet"?" sheet":" .md"),g(me(w.updatedAt)),g(w.updatedByName??"")]);if(!f.length)return $(g(`Nothing in ${a.label} yet.`));ne(f)}async function jn(e,t){let[n,...o]=(e??"").split("/").filter(Boolean);n||p("`dockito mkdir <project>[/folder[/subfolder]]`"),o.length>2&&p("Folders go at most 2 deep inside a project.");let s=T(),i=ke(await s.tree(),t.workspace),a=K(i,n),c=a?.id??(await s.createProject(i.id,n)).id,u=!a;a||h(`${b("\u2713")} Project ${n}`);let l=q(a),m=null;for(let f=0;f<o.length;f++){let w=l.idOf(o.slice(0,f+1));if(w){m=w;continue}m=(await s.createFolder(c,o[f],m)).id,l.add(o.slice(0,f+1),m),u=!0,h(`${b("\u2713")} Folder ${o.slice(0,f+1).join("/")}`)}u||$(g("Already there."))}async function xn(e){e||p("Name a doc: `dockito open <link or code>`");let t=`${E}/${M(e)}`;St(t)||h(t)}async function Tn(e,t){e||p("Name a doc: `dockito get <link or code> [-o file]`"),t.outline&&(t.lines!==void 0||t.text)&&p("--outline is the headings only; leave out --lines and --text.");let n=T(),o=await n.doc(M(e));if(o.doc.kind==="sheet"){let u=["entry","outline","lines","text"].filter(m=>t[m]!==void 0);u.length&&p(`A sheet has no file to read by ${u.map(m=>`--${m}`).join(" or ")}: \`dockito read\` gives its rows, a range or totals.`);let l=await n.readSheet(o.doc.id,{outline:!0});return t.output?($t(t.output,`${l.text.trimEnd()}
207
- `),$(`${b("\u2713")} ${t.output} ${g(`\u2190 ${n.linkOf(o.doc.slug)} outline`)}`)):de(l)}let s=t.entry?Number(t.entry):null;t.entry&&(!Number.isInteger(s)||s<1)&&p("--entry takes an entry number from `dockito history`.");let i=await n.content(o.doc.id,s,{outline:t.outline,lines:t.lines,text:t.text}),a=i.bytes.length&&i.lines!==null&&!t.output&&i.bytes[i.bytes.length-1]!==10?new Uint8Array([...i.bytes,10]):i.bytes;if(!t.output)return void process.stdout.write(a);$t(t.output,a);let c=t.outline?" outline":i.shown?` lines ${i.shown} of ${i.lines}`:t.text?" as text":"";$(`${b("\u2713")} ${t.output} ${g(`\u2190 ${n.linkOf(o.doc.slug)}${s?` entry ${s}`:""}${c}`)}`)}async function On(e,t){e||p("Name a doc: `dockito history <link or code>`");let n=await T().doc(M(e));if(n.doc.kind==="sheet"&&p(Ge),t.json)return h(JSON.stringify(n.versions,null,2));h(`${S(n.doc.title)} ${g(`${E}/${n.doc.slug}`)}`),ne(n.versions.map((o,s)=>[String(o.number),s===0?b("current"):g(me(o.createdAt)),s===0?g(me(o.createdAt)):"",o.byName??"",g(o.source),o.savedAt?`${R("saved")}${o.note?` ${o.note}`:""}`:"",o.gitCommit?g(o.gitCommit.slice(0,7)):""]))}async function Sn(e,t){let n=Number(t);(!e||!Number.isInteger(n)||n<1)&&p("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let o=T(),s=await o.doc(M(e));s.doc.kind==="sheet"&&p(`${Ge} \`dockito apply <doc> --undo <version>\` takes back a change of yours.`);let i=await o.restore(s.doc.id,n,s.versions[0]?.number??null);h(`${b("\u2713")} Restored entry ${n} as entry ${i.version} ${g(o.linkOf(i.slug))}`)}async function An(e,t,n){e||p('`dockito save <doc> [entry] -m "note"`');let o=T(),s=await o.doc(M(e));s.doc.kind==="sheet"&&p(Ge);let i=t?Number(t):s.versions[0]?.number;(!Number.isInteger(i)||i<1)&&p("Entries are in `dockito history`."),await o.save(s.doc.id,i,n.message??null),h(`${b("\u2713")} Saved entry ${i}${n.message?g(` \xB7 ${n.message}`):""}`)}async function Dn(e,t){e||p("`dockito share <link or code> [--private]`"),t.public&&p("An existing doc can be made public only from Share in the app. `dockito publish <file> --public` makes a new doc public.");let n=T(),o=await n.doc(M(e)),s=n.linkOf(o.doc.slug);if(t.private)return o.doc.public&&await n.makePrivate(o.doc.id),t.json?h(JSON.stringify({public:!1,link:s},null,2)):h(`${b("\u2713")} Private: only people in ${o.workspace.name} can open it ${g(s)}`);if(t.json)return h(JSON.stringify({public:o.doc.public,link:s},null,2));h(o.doc.public?`${b("Public")}: anyone with the link can open it ${s}`:`Private: only people in ${o.workspace.name} can open it ${g(s)}`)}var Ke=e=>/\.csv$/i.test(e),Ge="Sheets don't keep History entries yet; `dockito changes <doc> --since <version>` lists what changed.";async function Te(e,t,n){t||p(`Name a sheet: \`dockito ${n}\``);let o=await e.doc(M(t));return o.doc.kind!=="sheet"&&p(`${o.doc.title} is a ${o.doc.kind==="md"?"Markdown":"HTML"} doc, not a sheet: \`dockito get\` reads it.`),o}function de(e,t){t?h(JSON.stringify(e,null,2)):h(e.text.trimEnd()),e.ok||(process.exitCode=1)}var le=(e,t,n)=>{if(e===void 0)return;let o=Number(e);return(!/^\d+$/.test(e.trim())||o<n)&&p(`--${t} takes a whole number, ${n} or more.`),o},Ue=e=>e===void 0?void 0:e.split(",").map(t=>t.trim()).filter(Boolean);async function Nn(e,t,n){let o=T(),s=await Te(o,e,"read <doc> [range]"),i=await o.readSheet(s.doc.id,{read:{range:t,sheet:n.sheet,where:n.where,select:Ue(n.select),orderBy:n["order-by"],group:Ue(n.group),totals:Ue(n.totals),from:le(n.from,"from",1),limit:le(n.limit,"limit",1),formulas:n.formulas,styles:n.styles,format:n.csv?"csv":void 0}});de(i,n.json)}async function Pn(e,t,n){let o=T(),s=await Te(o,e,"apply <doc> <changes.json>"),i=le(n.undo,"undo",1),a;if(i!==void 0)(t||n.base!==void 0)&&p("--undo goes alone: no changes file and no --base with it."),a={undo:i,note:n.message,dryRun:n["dry-run"]||void 0};else{!t&&process.stdin.isTTY&&p("Give the changes: `dockito apply <doc> changes.json`, or pipe them in with `-`. `dockito apply --help` shows their form.");let u=!t||t==="-"?xe(0,"utf8"):Tt(t)?xe(t,"utf8"):p(`${t} isn't a file.`),l;try{l=JSON.parse(u)}catch{p(`${t&&t!=="-"?t:"The input"} isn't JSON: a list of changes, or {"base": N, "ops": [...]}.`)}let m=Array.isArray(l)?{ops:l}:l;(typeof m!="object"||m===null)&&p('The changes are a list, or {"base": N, "ops": [...]}.');let f=le(n.base,"base",0)??m.base;f===void 0&&p("--base is the version you read: v N in `dockito get` or `dockito read`."),a={...m,base:f,note:n.message??m.note,dryRun:n["dry-run"]||m.dryRun||void 0}}let c=n.key??En(a);de(await o.changeSheet(s.doc.id,{key:c,...a}),n.json)}var En=e=>At(new TextEncoder().encode(JSON.stringify(e))).slice(0,32);async function In(e,t){let n=T(),o=await Te(n,e,"check <doc>");de(await n.readSheet(o.doc.id,{check:t.sheet?{sheet:t.sheet}:{}}),t.json)}async function Cn(e,t){let n=T(),o=await Te(n,e,"changes <doc> --since <version>");t.since===void 0&&p('--since is a version (412) or a time in UTC (2026-10-13, "2026-10-13 09:00").');let s=/^\s*\d+\s*$/.test(t.since)?Number(t.since):t.since;de(await n.readSheet(o.doc.id,{changes:{since:s,max:le(t.max,"max",1)}}),t.json)}var We=`Dockito CLI ${qe}
253
+ --no-browser prints the link instead.`},logout:{use:"logout",about:"Sign out this machine",page:"Signs this machine out and removes its token."},whoami:{use:"whoami",about:"Who you are and what this login can reach",page:"Your name, the login's expiry, and its access in each workspace."},agent:{use:"agent",about:"What an AI agent needs to know",page:"Prints what Dockito is and how an AI agent should use this CLI."}};var qe="0.0.1-beta.13",C=dt(),de=ut(C),Je=`dockito-cli/${qe} (${process.platform})`;function T(){let e=ye(de);return e||p("Not logged in. Run `dockito login`."),fe({url:C,token:e,source:"cli",userAgent:Je})}function St(e){let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let o=an(t,n,{stdio:"ignore",detached:!0});return o.on("error",()=>{}),o.unref(),!0}catch{return!1}}var gn=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),fn=e=>new Promise(t=>setTimeout(t,e)),mn=()=>dn().replace(/\.local$/,"").slice(0,60)||"Unknown device";function M(e){return it(e)??p(`"${e}" isn't a doc link or code. \`dockito ls\` lists docs with their codes.`)}function vt(e){let t=un(Ot(e)),n=bt("git",["rev-parse","HEAD"],{cwd:t,encoding:"utf8"});if(n.error||n.status!==0)return null;let o=bt("git",["status","--porcelain","--",Be(e)],{cwd:t,encoding:"utf8"});return o.status!==0||o.stdout.trim()?null:n.stdout.trim()}var At=e=>cn("sha256").update(e).digest("hex");async function wn(e){let t=ye(de);e.update&&!t&&p("Not logged in. Run `dockito login` first."),process.env.DOCKITO_TOKEN&&$(R("DOCKITO_TOKEN is set, so commands will keep using it instead of this login."));let n=fe({url:C,token:e.update?t:null,userAgent:Je}),o=await n.startLogin("Dockito CLI",mn()),s=!e["no-browser"]&&gn()&&St(o.verification_uri_complete);h(s?`Opening ${o.verification_uri_complete}`:`Open ${S(o.verification_uri)}`),h(`Code: ${S(o.user_code)}`),$(g("Waiting for approval\u2026"));let i=o.interval*1e3,a=Date.now()+o.expires_in*1e3;for(;Date.now()<a;){await fn(i);try{let c=await n.pollLogin(o.device_code);if(!c)continue;let d=ht(de,c.token);h(`${b("\u2713")} Logged in as ${c.user.name}${c.user.username?g(` (${c.user.username})`):""}`);for(let l of c.workspaces)h(` ${l.name} ${g(`${l.slug} \xB7 ${G[l.level]}`)}`);d==="file"&&$(R("No keychain found, so the token is in a file only you can read, in ~/.config/dockito."));return}catch(c){if(c instanceof x&&c.code==="access_denied"&&p("Denied in the browser."),c instanceof x&&c.code==="expired_token"&&p("The code expired. Run `dockito login` again."),c instanceof x&&c.code==="offline"){i=Math.min(i*2,3e4);continue}throw c}}p("The code expired. Run `dockito login` again.")}async function yn(){let e=ye(de);if(!e)return h("Not logged in.");process.env.DOCKITO_TOKEN&&p("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await fe({url:C,token:e,userAgent:Je}).logout()}catch(t){if(!(t instanceof x)||t.code!=="invalid_token"&&t.code!=="not_found")throw t}gt(de),h(`${b("\u2713")} Logged out`)}async function kn(e){let t=await T().whoami();if(e.json)return h(JSON.stringify(t,null,2));h(`${t.user.name} ${g(`${t.user.username} \xB7 ${t.user.email}`)}`),t.token&&h(g(`${t.token.name} \xB7 ${t.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(t.token.expiresAt).toLocaleDateString()}`}`)),h(),se(t.workspaces.map(n=>[n.slug,n.name,g(n.level?G[n.level]:n.role)])),C!=="https://dockito-app.dockito.workers.dev"&&$(g(`
254
+ ${C}`))}async function bn(e,t){e.length||p("Name a file: `dockito publish notes.md --to <project/folder>`"),t.doc&&e.length>1&&p("--doc takes one file."),t.title!==void 0&&e.length>1&&p("--title takes one file."),t.title!==void 0&&!t.title.trim()&&p("--title needs a title."),t.public&&t.doc&&p("--public is only for a new doc; an existing one can be made public from Share in the app.");for(let l of e)(!Tt(l)||!ln(l).isFile())&&p(`${l} isn't a file${pn(l)?"":` (looked for ${Ot(l)})`}.`);let n=e.filter(He);n.length&&t.doc&&p("A .csv makes a new sheet; it can't replace a doc. Leave out --doc, or change a sheet with `dockito apply`."),n.length&&t.message&&p("Sheets don't keep saved points yet: publish the .csv without -m.");let o=T(),s=[];if(t.doc){let l=await o.doc(M(t.doc)),m=new Uint8Array(xe(e[0])),f=await o.update(l.doc.id,Be(e[0]),m,{note:t.message,commit:vt(e[0])});return t.json?h(JSON.stringify([{file:e[0],...f,link:o.linkOf(f.slug)}],null,2)):h(f.unchanged?`${g("=")} ${e[0]} ${g("unchanged")} ${o.linkOf(f.slug)}`:`${b("\u2713")} ${e[0]} \u2192 ${o.linkOf(f.slug)}`)}let i=await o.tree(),a=ke(i,t.workspace);I(a)||p(`This login has View access in ${a.slug}. Run \`dockito login --update\`.`);let c=Fe(a,t.to),d=await o.files(a.id,c.project);for(let l of e){let m=new Uint8Array(xe(l)),f=Be(l),w=t.new?[]:oe(d,c.folder,f),O=w.filter(D=>D.kind==="sheet");O.length&&p(`${f} in ${c.label} is ${O.length>1?`${O.length} live sheets`:"a live sheet"} (${O.map(D=>o.linkOf(D.slug)).join(", ")}): change ${O.length>1?"one":"it"} with \`dockito apply\`. --new makes a new sheet from the file instead.`),w.length>1&&p(`${w.length} docs in ${c.label} are named ${f}: ${w.map(D=>o.linkOf(D.slug)).join(", ")}. Pick one with --doc, or publish a new one with --new.`);let k=w[0];if(k&&t.public&&!(await o.doc(k.slug)).doc.public&&p(`${f} is already a private doc in ${c.label} (${o.linkOf(k.slug)}). Only Share in the app can make it public; \`--new --public\` publishes a new public doc.`),k&&k.sha256===At(m)&&!t.message){s.push({file:l,id:k.id,slug:k.slug,version:k.version,unchanged:!0,link:o.linkOf(k.slug)}),t.json||h(`${g("=")} ${l} ${g("unchanged")} ${o.linkOf(k.slug)}`);continue}let v=k?await o.update(k.id,f,m,{note:t.message,commit:vt(l)}):await o.create(a.id,f,m,{project:c.project,folder:c.folder,public:t.public,title:t.title});!k&&t.message&&await o.save(v.id,v.version,t.message);let U="public"in v&&v.public===!0;s.push({file:l,...v,...He(l)?{type:"sheet"}:{},link:o.linkOf(v.slug),new:!k}),t.json||h(`${b("\u2713")} ${l} \u2192 ${o.linkOf(v.slug)} ${g(k?v.unchanged?"unchanged":"updated":`new ${He(l)?"sheet ":""}in ${c.label}`)}${U?` ${b("\xB7 public")}`:""}${t.message?g(` \xB7 saved: ${t.message}`):""}`)}t.json?h(JSON.stringify(s,null,2)):!t.to&&s.some(l=>l.new)&&$(g("In the workspace's Docs. To put docs in a project: --to <project/folder>; `dockito tree` lists them."))}async function $n(e){let t=await T().tree();if(e.json)return h(JSON.stringify(t,null,2));let n=o=>g(`${o} doc${o===1?"":"s"}`);for(let o of t.workspaces){h(`${S(o.slug)} ${o.name} ${g(`\xB7 ${o.level?G[o.level]:o.role}`)}`);let s=[[g("Docs"),"",n(o.docs)]];for(let i of o.projects){s.push([i.slug,i.name,n(i.docs)]);let a=(c,d)=>{for(let l of i.folders.filter(m=>m.parentId===c))s.push([`${d}/${l.name}`,"",n(l.docs)]),a(l.id,`${d}/${l.name}`)};a(null,i.slug)}se(s.map(i=>[" ",...i])),h()}$(g("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function vn(e,t){e&&t.to&&e!==t.to&&p(`Two places: ${e} and --to ${t.to}. Give one.`);let n=e??t.to,o=T(),s=await o.tree(),i=ke(s,t.workspace),a=Fe(i,n),c=await o.docs(i.id,{project:a.project,folder:a.folder}),d=a.project?i.projects.find(w=>w.id===a.project):null,l=n?[]:i.projects,m=d?d.folders.filter(w=>w.parentId===a.folder):[];if(t.json)return h(JSON.stringify({workspace:i.slug,place:n??null,projects:l,folders:m,docs:c},null,2));let f=[];for(let w of l)f.push([S(`${w.slug}/`),w.name,g(`${w.docs} doc${w.docs===1?"":"s"}`),""]);for(let w of m)f.push([S(`${w.name}/`),"",g(`${w.docs} doc${w.docs===1?"":"s"}`),""]);for(let w of c)f.push([w.slug,w.title+g(w.kind==="html"?" .html":w.kind==="sheet"?" sheet":" .md"),g(me(w.updatedAt)),g(w.updatedByName??"")]);if(!f.length)return $(g(`Nothing in ${a.label} yet.`));se(f)}async function jn(e,t){let[n,...o]=(e??"").split("/").filter(Boolean);n||p("`dockito mkdir <project>[/folder[/subfolder]]`"),o.length>2&&p("Folders go at most 2 deep inside a project.");let s=T(),i=ke(await s.tree(),t.workspace),a=H(i,n),c=a?.id??(await s.createProject(i.id,n)).id,d=!a;a||h(`${b("\u2713")} Project ${n}`);let l=q(a),m=null;for(let f=0;f<o.length;f++){let w=l.idOf(o.slice(0,f+1));if(w){m=w;continue}m=(await s.createFolder(c,o[f],m)).id,l.add(o.slice(0,f+1),m),d=!0,h(`${b("\u2713")} Folder ${o.slice(0,f+1).join("/")}`)}d||$(g("Already there."))}async function xn(e){e||p("Name a doc: `dockito open <link or code>`");let t=`${C}/${M(e)}`;St(t)||h(t)}async function Tn(e,t){e||p("Name a doc: `dockito get <link or code> [-o file]`"),t.outline&&(t.lines!==void 0||t.text)&&p("--outline is the headings only; leave out --lines and --text.");let n=T(),o=await n.doc(M(e));if(o.doc.kind==="sheet"){let d=["entry","outline","lines","text"].filter(m=>t[m]!==void 0);d.length&&p(`A sheet has no file to read by ${d.map(m=>`--${m}`).join(" or ")}: \`dockito read\` gives its rows, a range or totals.`);let l=await n.readSheet(o.doc.id,{outline:!0});return t.output?($t(t.output,`${l.text.trimEnd()}
255
+ `),$(`${b("\u2713")} ${t.output} ${g(`\u2190 ${n.linkOf(o.doc.slug)} outline`)}`)):ee(l)}let s=t.entry?Number(t.entry):null;t.entry&&(!Number.isInteger(s)||s<1)&&p("--entry takes an entry number from `dockito history`.");let i=await n.content(o.doc.id,s,{outline:t.outline,lines:t.lines,text:t.text}),a=i.bytes.length&&i.lines!==null&&!t.output&&i.bytes[i.bytes.length-1]!==10?new Uint8Array([...i.bytes,10]):i.bytes;if(!t.output)return void process.stdout.write(a);$t(t.output,a);let c=t.outline?" outline":i.shown?` lines ${i.shown} of ${i.lines}`:t.text?" as text":"";$(`${b("\u2713")} ${t.output} ${g(`\u2190 ${n.linkOf(o.doc.slug)}${s?` entry ${s}`:""}${c}`)}`)}async function On(e,t){e||p("Name a doc: `dockito history <link or code>`");let n=await T().doc(M(e));if(n.doc.kind==="sheet"&&p(Ge),t.json)return h(JSON.stringify(n.versions,null,2));h(`${S(n.doc.title)} ${g(`${C}/${n.doc.slug}`)}`),se(n.versions.map((o,s)=>[String(o.number),s===0?b("current"):g(me(o.createdAt)),s===0?g(me(o.createdAt)):"",o.byName??"",g(o.source),o.savedAt?`${R("saved")}${o.note?` ${o.note}`:""}`:"",o.gitCommit?g(o.gitCommit.slice(0,7)):""]))}async function Sn(e,t){let n=Number(t);(!e||!Number.isInteger(n)||n<1)&&p("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let o=T(),s=await o.doc(M(e));s.doc.kind==="sheet"&&p(`${Ge} \`dockito apply <doc> --undo <version>\` takes back a change of yours.`);let i=await o.restore(s.doc.id,n,s.versions[0]?.number??null);h(`${b("\u2713")} Restored entry ${n} as entry ${i.version} ${g(o.linkOf(i.slug))}`)}async function An(e,t,n){e||p('`dockito save <doc> [entry] -m "note"`');let o=T(),s=await o.doc(M(e));s.doc.kind==="sheet"&&p(Ge);let i=t?Number(t):s.versions[0]?.number;(!Number.isInteger(i)||i<1)&&p("Entries are in `dockito history`."),await o.save(s.doc.id,i,n.message??null),h(`${b("\u2713")} Saved entry ${i}${n.message?g(` \xB7 ${n.message}`):""}`)}async function Dn(e,t){e||p("`dockito share <link or code> [--private]`"),t.public&&p("An existing doc can be made public only from Share in the app. `dockito publish <file> --public` makes a new doc public.");let n=T(),o=await n.doc(M(e)),s=n.linkOf(o.doc.slug);if(t.private)return o.doc.public&&await n.makePrivate(o.doc.id),t.json?h(JSON.stringify({public:!1,link:s},null,2)):h(`${b("\u2713")} Private: only people in ${o.workspace.name} can open it ${g(s)}`);if(t.json)return h(JSON.stringify({public:o.doc.public,link:s},null,2));h(o.doc.public?`${b("Public")}: anyone with the link can open it ${s}`:`Private: only people in ${o.workspace.name} can open it ${g(s)}`)}var He=e=>/\.csv$/i.test(e),Ge="Sheets don't keep History entries yet; `dockito changes <doc> --since <version>` lists what changed.";async function Te(e,t,n){t||p(`Name a sheet: \`dockito ${n}\``);let o=await e.doc(M(t));return o.doc.kind!=="sheet"&&p(`${o.doc.title} is a ${o.doc.kind==="md"?"Markdown":"HTML"} doc, not a sheet: \`dockito get\` reads it.`),o}function ee(e,t){t?h(JSON.stringify(e,null,2)):h(e.text.trimEnd()),e.ok||(process.exitCode=1)}var Z=(e,t,n)=>{if(e===void 0)return;let o=Number(e);return(!/^\d+$/.test(e.trim())||o<n)&&p(`--${t} takes a whole number, ${n} or more.`),o},Ue=e=>e===void 0?void 0:e.split(",").map(t=>t.trim()).filter(Boolean);async function Nn(e,t,n){let o=T(),s=await Te(o,e,"read <doc> [range]"),i=Z(n.chart,"chart",1);if(i!==void 0){let c=Object.entries(n).filter(([d,l])=>d!=="chart"&&d!=="json"&&l!==void 0);return(t||c.length)&&p(`--chart goes alone: it gives the chart's points as drawn, not ${[...t?["a range"]:[],...c.map(([d])=>`--${d}`)].join(", ")}.`),ee(await o.readSheet(s.doc.id,{read:{chart:i}}),n.json)}let a=await o.readSheet(s.doc.id,{read:{range:t,sheet:n.sheet,where:n.where,select:Ue(n.select),orderBy:n["order-by"],group:Ue(n.group),totals:Ue(n.totals),from:Z(n.from,"from",1),limit:Z(n.limit,"limit",1),formulas:n.formulas,styles:n.styles,format:n.csv?"csv":void 0}});ee(a,n.json)}async function Pn(e,t,n){let o=T(),s=await Te(o,e,"apply <doc> <changes.json>"),i=Z(n.undo,"undo",1),a;if(i!==void 0)(t||n.base!==void 0)&&p("--undo goes alone: no changes file and no --base with it."),a={undo:i,note:n.message,dryRun:n["dry-run"]||void 0};else{!t&&process.stdin.isTTY&&p("Give the changes: `dockito apply <doc> changes.json`, or pipe them in with `-`. `dockito apply --help` shows their form.");let d=!t||t==="-"?xe(0,"utf8"):Tt(t)?xe(t,"utf8"):p(`${t} isn't a file.`),l;try{l=JSON.parse(d)}catch{p(`${t&&t!=="-"?t:"The input"} isn't JSON: a list of changes, or {"base": N, "ops": [...]}.`)}let m=Array.isArray(l)?{ops:l}:l;(typeof m!="object"||m===null)&&p('The changes are a list, or {"base": N, "ops": [...]}.');let f=Z(n.base,"base",0)??m.base;f===void 0&&p("--base is the version you read: v N in `dockito get` or `dockito read`."),a={...m,base:f,note:n.message??m.note,dryRun:n["dry-run"]||m.dryRun||void 0}}let c=n.key??Cn(a);ee(await o.changeSheet(s.doc.id,{key:c,...a}),n.json)}var Cn=e=>At(new TextEncoder().encode(JSON.stringify(e))).slice(0,32);async function En(e,t){let n=T(),o=await Te(n,e,"check <doc>");ee(await n.readSheet(o.doc.id,{check:t.sheet?{sheet:t.sheet}:{}}),t.json)}async function In(e,t){let n=T(),o=await Te(n,e,"changes <doc> --since <version>");t.since===void 0&&p('--since is a version (412) or a time in UTC (2026-10-13, "2026-10-13 09:00").');let s=/^\s*\d+\s*$/.test(t.since)?Number(t.since):t.since;ee(await n.readSheet(o.doc.id,{changes:{since:s,max:Z(t.max,"max",1)}}),t.json)}var We=`Dockito CLI ${qe}
208
256
 
209
- ${He}
257
+ ${Ke}
210
258
 
211
259
  Usage: dockito <command>
212
260
 
@@ -219,4 +267,4 @@ ${Object.values(ve).map(e=>` ${e.use.padEnd(30)}${e.about}`).join(`
219
267
 
220
268
  Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`,jt=e=>{let t=ve[e];return t?`dockito ${t.use}
221
269
 
222
- ${t.page}`:null},_n={login:{flags:["update","no-browser"],args:0},logout:{flags:[],args:0},whoami:{flags:["json"],args:0},sync:{flags:["to","workspace","json","yes","dry-run","create","fold"],args:1},publish:{flags:["message","to","doc","new","title","workspace","json","public"],args:1/0},tree:{flags:["json"],args:0},ls:{flags:["to","workspace","json"],args:1},mkdir:{flags:["workspace"],args:1},share:{flags:["public","private","json"],args:1},open:{flags:[],args:1},get:{flags:["output","entry","outline","lines","text"],args:1},history:{flags:["json"],args:1},restore:{flags:[],args:2},save:{flags:["message"],args:2},agent:{flags:[],args:0},read:{flags:["sheet","where","select","order-by","group","totals","from","limit","formulas","styles","csv","json"],args:2},apply:{flags:["base","message","dry-run","undo","key","json"],args:2},check:{flags:["sheet","json"],args:1},changes:{flags:["since","max","json"],args:1}},xt={message:"-m",workspace:"-w",output:"-o",yes:"-y"};function Rn(e,t,n){let o=_n[e];if(!o)return;let s=`\`dockito ${ve[e]?.use??e}\`; \`dockito ${e} --help\` for more.`,i=t.filter(a=>!o.flags.includes(a));if(i.length){let a=i.map(u=>`--${u}`).join(", "),c=o.flags.length?` It takes ${o.flags.map(u=>xt[u]?`${xt[u]}/--${u}`:`--${u}`).join(", ")}.`:"";p(`${e} doesn't take ${a}.${c} Use: ${s}`)}n.length>o.args&&p(`${e} takes ${o.args===0?"no arguments":`at most ${o.args} argument${o.args===1?"":"s"}`}; got ${n.map(a=>`"${a}"`).join(" ")}. Use: ${s}`)}async function Ln(){let{values:e,positionals:t}=hn({allowPositionals:!0,options:{message:{type:"string",short:"m"},to:{type:"string"},doc:{type:"string"},new:{type:"boolean"},title:{type:"string"},workspace:{type:"string",short:"w"},output:{type:"string",short:"o"},entry:{type:"string"},outline:{type:"boolean"},lines:{type:"string"},text:{type:"boolean"},json:{type:"boolean"},update:{type:"boolean"},yes:{type:"boolean",short:"y"},"dry-run":{type:"boolean"},create:{type:"boolean"},fold:{type:"boolean"},"no-browser":{type:"boolean"},public:{type:"boolean"},private:{type:"boolean"},sheet:{type:"string"},where:{type:"string"},select:{type:"string"},"order-by":{type:"string"},group:{type:"string"},totals:{type:"string"},from:{type:"string"},limit:{type:"string"},formulas:{type:"boolean"},styles:{type:"boolean"},csv:{type:"boolean"},base:{type:"string"},undo:{type:"string"},key:{type:"string"},since:{type:"string"},max:{type:"string"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[n,...o]=t;if(e.version)return h(qe);if(n==="help"&&o[0])return h(jt(o[0])??We);if(e.help&&n)return h(jt(n)??We);if(e.help||!n||n==="help")return h(We);switch(Rn(n,Object.keys(e).filter(s=>e[s]!==void 0),o),n){case"login":return wn(e);case"logout":return yn();case"whoami":return kn(e);case"sync":return kt(T(),o[0],e);case"publish":return bn(o,e);case"tree":return $n(e);case"ls":return vn(o[0],e);case"mkdir":return jn(o[0],e);case"share":return Dn(o[0],e);case"open":return xn(o[0]);case"get":return Tn(o[0],e);case"history":return On(o[0],e);case"restore":return Sn(o[0],o[1]);case"save":return An(o[0],o[1],e);case"agent":return h(He);case"read":return Nn(o[0],o[1],e);case"apply":return Pn(o[0],o[1],e);case"check":return In(o[0],e);case"changes":return Cn(o[0],e);default:p(`Unknown command "${n}". Run \`dockito --help\`.`)}}function je(e){let t=2,n=()=>--t===0&&process.exit(e);process.stdout.write("",n),process.stderr.write("",n)}Ln().then(()=>je(Number(process.exitCode??0)),e=>{let t=process.argv.includes("--json");if(e instanceof N)return t?h(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2)):$(e.message),je(2);if(e instanceof se)return je(130);t&&e instanceof x?h(JSON.stringify({error:e.code,message:oe(e,E),...e.detail},null,2)):t&&e instanceof J?h(JSON.stringify({error:"failed",message:e.message},null,2)):e instanceof J?$(_(e.message)):e instanceof x?$(_(oe(e,E))):e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message))?$(_(`${e.message}. Run \`dockito --help\`.`)):$(_(String(e?.stack??e))),je(1)});
270
+ ${t.page}`:null},_n={login:{flags:["update","no-browser"],args:0},logout:{flags:[],args:0},whoami:{flags:["json"],args:0},sync:{flags:["to","workspace","json","yes","dry-run","create","fold"],args:1},publish:{flags:["message","to","doc","new","title","workspace","json","public"],args:1/0},tree:{flags:["json"],args:0},ls:{flags:["to","workspace","json"],args:1},mkdir:{flags:["workspace"],args:1},share:{flags:["public","private","json"],args:1},open:{flags:[],args:1},get:{flags:["output","entry","outline","lines","text"],args:1},history:{flags:["json"],args:1},restore:{flags:[],args:2},save:{flags:["message"],args:2},agent:{flags:[],args:0},read:{flags:["sheet","where","select","order-by","group","totals","from","limit","formulas","styles","csv","chart","json"],args:2},apply:{flags:["base","message","dry-run","undo","key","json"],args:2},check:{flags:["sheet","json"],args:1},changes:{flags:["since","max","json"],args:1}},xt={message:"-m",workspace:"-w",output:"-o",yes:"-y"};function Rn(e,t,n){let o=_n[e];if(!o)return;let s=`\`dockito ${ve[e]?.use??e}\`; \`dockito ${e} --help\` for more.`,i=t.filter(a=>!o.flags.includes(a));if(i.length){let a=i.map(d=>`--${d}`).join(", "),c=o.flags.length?` It takes ${o.flags.map(d=>xt[d]?`${xt[d]}/--${d}`:`--${d}`).join(", ")}.`:"";p(`${e} doesn't take ${a}.${c} Use: ${s}`)}n.length>o.args&&p(`${e} takes ${o.args===0?"no arguments":`at most ${o.args} argument${o.args===1?"":"s"}`}; got ${n.map(a=>`"${a}"`).join(" ")}. Use: ${s}`)}async function Ln(){let{values:e,positionals:t}=hn({allowPositionals:!0,options:{message:{type:"string",short:"m"},to:{type:"string"},doc:{type:"string"},new:{type:"boolean"},title:{type:"string"},workspace:{type:"string",short:"w"},output:{type:"string",short:"o"},entry:{type:"string"},outline:{type:"boolean"},lines:{type:"string"},text:{type:"boolean"},json:{type:"boolean"},update:{type:"boolean"},yes:{type:"boolean",short:"y"},"dry-run":{type:"boolean"},create:{type:"boolean"},fold:{type:"boolean"},"no-browser":{type:"boolean"},public:{type:"boolean"},private:{type:"boolean"},sheet:{type:"string"},where:{type:"string"},select:{type:"string"},"order-by":{type:"string"},group:{type:"string"},totals:{type:"string"},from:{type:"string"},limit:{type:"string"},formulas:{type:"boolean"},styles:{type:"boolean"},csv:{type:"boolean"},chart:{type:"string"},base:{type:"string"},undo:{type:"string"},key:{type:"string"},since:{type:"string"},max:{type:"string"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[n,...o]=t;if(e.version)return h(qe);if(n==="help"&&o[0])return h(jt(o[0])??We);if(e.help&&n)return h(jt(n)??We);if(e.help||!n||n==="help")return h(We);switch(Rn(n,Object.keys(e).filter(s=>e[s]!==void 0),o),n){case"login":return wn(e);case"logout":return yn();case"whoami":return kn(e);case"sync":return kt(T(),o[0],e);case"publish":return bn(o,e);case"tree":return $n(e);case"ls":return vn(o[0],e);case"mkdir":return jn(o[0],e);case"share":return Dn(o[0],e);case"open":return xn(o[0]);case"get":return Tn(o[0],e);case"history":return On(o[0],e);case"restore":return Sn(o[0],o[1]);case"save":return An(o[0],o[1],e);case"agent":return h(Ke);case"read":return Nn(o[0],o[1],e);case"apply":return Pn(o[0],o[1],e);case"check":return En(o[0],e);case"changes":return In(o[0],e);default:p(`Unknown command "${n}". Run \`dockito --help\`.`)}}function je(e){let t=2,n=()=>--t===0&&process.exit(e);process.stdout.write("",n),process.stderr.write("",n)}Ln().then(()=>je(Number(process.exitCode??0)),e=>{let t=process.argv.includes("--json");if(e instanceof N)return t?h(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2)):$(e.message),je(2);if(e instanceof ie)return je(130);t&&e instanceof x?h(JSON.stringify({error:e.code,message:re(e,C),...e.detail},null,2)):t&&e instanceof J?h(JSON.stringify({error:"failed",message:e.message},null,2)):e instanceof J?$(_(e.message)):e instanceof x?$(_(re(e,C))):e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message))?$(_(`${e.message}. Run \`dockito --help\`.`)):$(_(String(e?.stack??e))),je(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dockito",
3
- "version": "0.0.1-beta.11",
3
+ "version": "0.0.1-beta.13",
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",