dockito 0.0.1-beta.7 → 0.0.1-beta.8

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 +1 -0
  2. package/dockito.js +100 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -17,6 +17,7 @@ npx dockito@beta publish plan.md -m "Sent to the client"
17
17
  | `tree`, `mkdir` | Every workspace, project and folder you can use; make new ones |
18
18
  | `ls`, `open`, `get` | List, open and download docs |
19
19
  | `history`, `restore`, `save` | A doc's History |
20
+ | `read`, `apply`, `check`, `changes` | Live sheets: read rows, change cells at the version you read, find broken formulas, see what changed. `publish data.csv` makes one |
20
21
  | `whoami`, `logout` | Your login and workspaces |
21
22
 
22
23
  The CLI keeps nothing but your login: where docs go is passed with `--to` each time. Run `dockito --help` for everything, `dockito <command> --help` for one command, and `dockito agent` for what an AI agent needs to know. `--json` gives machine-readable output for agents, and `DOCKITO_TOKEN` uses a token from Account → Tokens instead of a login.
package/dockito.js CHANGED
@@ -1,20 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as Vt,spawnSync as pt}from"node:child_process";import{createHash as Xt}from"node:crypto";import{existsSync as Qt,readFileSync as gt,statSync as Zt,writeFileSync as en}from"node:fs";import{hostname as tn}from"node:os";import{basename as Me,dirname as nn,isAbsolute as on,resolve as wt}from"node:path";import{parseArgs as sn}from"node:util";var _=e=>e.level===null||e.level==="publish"||e.level==="everything",M=(e,n)=>e.projects.find(t=>t.slug===n)??e.projects.find(t=>t.name.toLowerCase()===n.toLowerCase())??null;function H(e){let n=new Map((e?.folders??[]).map(r=>[r.id,r])),t=r=>{let s=n.get(r);return[...s.parentId?t(s.parentId):[],s.name]},o=new Map((e?.folders??[]).map(r=>[t(r.id).join("/").toLowerCase(),r.id]));return{idOf:r=>r.length?o.get(r.join("/").toLowerCase()):null,pathOf:r=>r&&n.has(r)?t(r):[],add:(r,s)=>{o.set(r.join("/").toLowerCase(),s)}}}var vt=e=>e.fileName??`${e.title}.${e.kind}`,ee=(e,n,t)=>e.filter(o=>(o.folderId??null)===n&&vt(o).toLowerCase()===t.toLowerCase());function Ze(e){let n=e.trim();if(/^https?:\/\//.test(n))try{n=new URL(n).pathname}catch{return null}let t=(n.split("/").filter(Boolean).pop()??"").replace(/\.[a-z]+$/,"");return/^[a-z0-9]{12}$/.test(t)&&/\d/.test(t)?t:null}var x=class extends Error{constructor(t,o,r={}){super(t);this.code=t;this.status=o;this.detail=r}code;status;detail};function ge(e){let n=e.url.replace(/\/+$/,"");async function t(s,a,c={}){let d=new URL(`${n}/api${a}`);for(let[j,b]of Object.entries(c.query??{}))b!=null&&b!==""&&d.searchParams.set(j,String(b));let m={},k=c.token===void 0?e.token:c.token;k&&(m.authorization=`Bearer ${k}`),e.userAgent&&(m["user-agent"]=e.userAgent);let h;c.json!==void 0?(m["content-type"]="application/json",h=JSON.stringify(c.json)):c.body&&(m["content-type"]="application/octet-stream",h=c.body);let g;try{g=e.fetch?await e.fetch(new Request(d,{method:s,headers:m,body:h})):await fetch(d,{method:s,headers:m,body:h})}catch{throw new x("offline",0)}return g}async function o(s){let a=await s.json().catch(()=>({}));if(!s.ok){let{error:c,...d}=a;throw new x(typeof c=="string"?c:`http_${s.status}`,s.status,d)}return a}let r=encodeURIComponent;return{url:n,linkOf:s=>`${n}/${s}`,startLogin:async(s,a)=>o(await t("POST","/device/code",{json:{client:s,device:a}})),pollLogin:async s=>{let a=await t("POST","/device/token",{json:{device_code:s},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 s=>(await o(await t("GET",`/workspaces/${r(s)}/projects`))).projects,docs:async(s,a={})=>(await o(await t("GET",`/workspaces/${r(s)}/docs`,{query:a}))).docs,files:async(s,a)=>(await o(await t("GET",`/workspaces/${r(s)}/files`,{query:{project:a}}))).docs,createProject:async(s,a)=>o(await t("POST",`/workspaces/${r(s)}/projects`,{json:{name:a}})),createFolder:async(s,a,c)=>o(await t("POST",`/projects/${r(s)}/folders`,{json:{name:a,parentId:c}})),recent:async s=>(await o(await t("GET",`/workspaces/${r(s)}/docs/recent`))).docs,doc:async s=>o(await t("GET",`/docs/by-code/${r(s)}`)),content:async(s,a,c={})=>{let d=await t("GET",`/docs/${r(s)}/content`,{query:{v:a,outline:c.outline?1:null,lines:c.lines,text:c.text?1:null}});d.ok||await o(d);let m=d.headers.get("x-doc-lines");return{bytes:new Uint8Array(await d.arrayBuffer()),entry:Number(d.headers.get("x-doc-version")),lines:m?Number(m):null,shown:d.headers.get("x-doc-shown")}},create:async(s,a,c,d={})=>o(await t("POST",`/workspaces/${r(s)}/docs`,{query:{name:a,title:d.title,project:d.project,folder:d.folder,public:d.public?1:null,source:e.source},body:c})),update:async(s,a,c,d={})=>o(await t("POST",`/docs/${r(s)}/versions`,{query:{name:a,note:d.note,commit:d.commit,base:d.base,source:e.source},body:c})),restore:async(s,a,c)=>o(await t("POST",`/docs/${r(s)}/history/${a}/restore`,{query:{base:c}})),save:async(s,a,c)=>o(await t("PUT",`/docs/${r(s)}/history/${a}/saved`,{json:{note:c}})),makePrivate:async s=>o(await t("PATCH",`/docs/${r(s)}`,{json:{public:!1}})),rename:async(s,a)=>o(await t("PATCH",`/docs/${r(s)}`,{json:{title:a}}))}}import{emitKeypressEvents as Tt}from"node:readline";import{createInterface as Ot}from"node:readline/promises";var xt=!process.env.NO_COLOR&&process.stdout.isTTY,G=e=>n=>xt?`\x1B[${e}m${n}\x1B[0m`:n,p=G("2"),T=G("1"),y=G("32"),I=G("31"),E=G("33"),Pe=G("36"),u=(e="")=>process.stdout.write(e+`
2
+ import{spawn as rn,spawnSync as kt}from"node:child_process";import{createHash as an}from"node:crypto";import{existsSync as xt,readFileSync as xe,statSync as cn,writeFileSync as bt}from"node:fs";import{hostname as ln}from"node:os";import{basename as Be,dirname as dn,isAbsolute as un,resolve as Tt}from"node:path";import{parseArgs as pn}from"node:util";var I=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 Et=e=>e.fileName??`${e.title}.${e.kind==="sheet"?"csv":e.kind}`,te=(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 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 It}from"node:readline";import{createInterface as _t}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"),Ce=z("36"),h=(e="")=>process.stdout.write(e+`
3
3
  `),$=(e="")=>process.stderr.write(e+`
4
- `),B=class extends Error{},f=e=>{throw new B(e)},J={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 r=Math.round(o/24);return r<30?r===1?"yesterday":`${r} days ago`:new Date(e).toLocaleDateString(void 0,{day:"numeric",month:"short",year:"numeric"})}function te(e){let n=o=>o.replace(/\x1b\[[0-9;]*m/g,""),t=e[0]?.map((o,r)=>Math.max(...e.map(s=>n(s[r]??"").length)))??[];for(let o of e)u(o.map((r,s)=>s===o.length-1?r:r+" ".repeat(t[s]-n(r).length)).join(" ").trimEnd())}function ne(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 ${J[e.detail.needs]??e.detail.needs} access; this login has ${J[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"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 Se=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI,q=e=>process.stdout.write(e);function et(e,n,t=0){return new Promise((o,r)=>{let s=t,a=m=>{m||q(`\x1B[${n.length}A`);for(let[k,h]of n.entries())q(`\x1B[2K${k===s?Pe("\u276F "+h.label):" "+h.label}${h.hint?p(" "+h.hint):""}
5
- `)};q(`${y("?")} ${T(e)} ${p("(\u2191\u2193, Enter)")}
6
- \x1B[?25l`),a(!0),Tt(process.stdin),process.stdin.setRawMode(!0),process.stdin.resume();let c=()=>{process.stdin.off("keypress",d),process.stdin.setRawMode(!1),process.stdin.pause(),q("\x1B[?25h")},d=(m,k)=>{if(k.ctrl&&k.name==="c")return c(),q(`
7
- `),r(new oe);if(k.name==="up"||k.name==="k")s=(s-1+n.length)%n.length;else if(k.name==="down"||k.name==="j")s=(s+1)%n.length;else return k.name==="return"?(c(),q(`\x1B[${n.length+1}A\x1B[0J${y("?")} ${T(e)} ${Pe(n[s].label)}
8
- `),o(s)):void 0;a(!1)};process.stdin.on("keypress",d)})}async function he(e,n=""){let t=Ot({input:process.stdin,output:process.stdout});try{return(await t.question(`${y("?")} ${T(e)}${n?p(` (${n})`):""} `)).trim()||n}finally{t.close()}}async function Ne(e,n=!0){let t=(await he(`${e} ${p(n?"[Y/n]":"[y/N]")}`)).toLowerCase();return t?t.startsWith("y"):n}var oe=class extends Error{};import{homedir as Dt}from"node:os";import{join as _e}from"node:path";var At="https://dockito-app.dockito.workers.dev";function tt(){return process.env.DOCKITO_CONFIG_DIR?process.env.DOCKITO_CONFIG_DIR:process.platform==="win32"&&process.env.APPDATA?_e(process.env.APPDATA,"dockito"):_e(process.env.XDG_CONFIG_HOME||_e(Dt(),".config"),"dockito")}var nt=()=>(process.env.DOCKITO_URL||At).replace(/\/+$/,""),ot=e=>new URL(e).host;import{spawnSync as Pt}from"node:child_process";import{chmodSync as St,existsSync as Ie,mkdirSync as Nt,readFileSync as _t,rmSync as st,writeFileSync as It}from"node:fs";import{dirname as Et,join as Ct}from"node:path";var z="dockito";function Y(e,n,t){try{let o=Pt(e,n,{input:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return o.error?null:o}catch{return null}}var se=e=>Ct(tt(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function Lt(e,n){if(process.platform==="darwin"){let t=Y("security",["-i"],`add-generic-password -U -s ${z} -a ${e} -l Dockito -w ${n}
9
- `);return!!t&&t.status===0&&!/error/i.test(t.stderr??"")}if(process.platform==="linux"){let t=Y("secret-tool",["store","--label=Dockito","service",z,"account",e],n);return!!t&&t.status===0}return!1}function Rt(e){let n=process.platform==="darwin"?Y("security",["find-generic-password","-s",z,"-a",e,"-w"]):process.platform==="linux"?Y("secret-tool",["lookup","service",z,"account",e]):null;return(n?.status===0?n.stdout.trim():"")||null}function Mt(e){process.platform==="darwin"?Y("security",["delete-generic-password","-s",z,"-a",e]):process.platform==="linux"&&Y("secret-tool",["clear","service",z,"account",e])}function rt(e,n){if(Lt(e,n))return Ie(se(e))&&st(se(e)),"keychain";let t=se(e);return Nt(Et(t),{recursive:!0,mode:448}),It(t,n,{mode:384}),St(t,384),"file"}function me(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let n=Rt(e);if(n)return n;let t=se(e);return Ie(t)&&_t(t,"utf8").trim()||null}function it(e){Mt(e);let n=se(e);Ie(n)&&st(n)}var D=class extends Error{constructor(t,o,r={}){super(o);this.code=t;this.data=r}code;data};function we(e,n){let t=e.workspaces;if(n)return t.find(r=>r.slug===n)??f(`This login can't reach workspace "${n}". \`dockito tree\` lists the ones it can.`);let o=t.length===1?t[0]:t.find(r=>r.kind==="personal"&&r.role==="owner");if(o)return o;throw new D("needs_workspace",`Pick a workspace with -w: ${t.map(r=>r.slug).join(", ")}`,{workspaces:t.map(r=>r.slug)})}function Ee(e,n){if(!n)return{project:null,folder:null,label:`${e.slug} Docs`};let[t,...o]=n.split("/").filter(Boolean),r=M(e,t);if(!r)return f(`No project "${t}" in ${e.slug}. \`dockito tree\` lists them; \`dockito mkdir ${n}\` makes it.`);let s=H(r).idOf(o);return s===void 0?f(`No folder "${o.join("/")}" in ${r.slug}. \`dockito mkdir ${r.slug}/${o.join("/")}\` makes it.`):{project:r.id,folder:s,label:[e.slug,r.slug,...o].join("/")}}import{createHash as Ft}from"node:crypto";import{spawnSync as Kt}from"node:child_process";import{existsSync as ie,readFileSync as Ut,readdirSync as Wt,statSync as lt}from"node:fs";import{basename as at,join as C,relative as Ht,resolve as Ce,sep as Bt}from"node:path";var Jt=4,re=e=>e<1024*1024?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,Gt=e=>Ft("sha256").update(e).digest("hex"),dt=e=>e.split(Bt).join("/"),V=(e,n)=>`${e} ${n}${e===1?"":"s"}`,qt=(e,n)=>{let t=/\.[^./]+$/.exec(n.toLowerCase())?.[0]??"";return e.find(o=>o.extensions.includes(t))?.id??null};function ye(e,n){let t=Kt("git",e,{cwd:n,encoding:"utf8",maxBuffer:67108864});return t.error||t.status!==0?null:t.stdout}function zt(e,n){let t=ye(["ls-files","-z","--cached","--others","--exclude-standard","--","."],e);return(t!==null?t.split("\0").filter(Boolean):(function r(s){return Wt(C(e,s),{withFileTypes:!0}).flatMap(a=>a.name.startsWith(".")||a.name==="node_modules"?[]:a.isDirectory()?r(C(s,a.name)):[dt(C(s,a.name))])})("")).filter(r=>n(r)&&ie(C(e,r))&&lt(C(e,r)).isFile()).sort()}function Yt(e,n,t){let o=[...e.matchAll(/\b(?:src|href)=["']([^"'#?]+)/gi)].map(r=>r[1]);return[...new Set(o.filter(r=>!/^([a-z]+:|\/\/|\/)/i.test(r)&&!t(r)&&ie(C(n,r))))]}function ct(e,n){let t=(s,a,c="")=>`dockito sync ${n} -w ${s} --to ${a}${c}`,o=e.workspaces.filter(_).flatMap(s=>[...s.projects.map(a=>({workspace:s.slug,project:a.slug,name:a.name,docs:a.docs,command:t(s.slug,a.slug)})),{workspace:s.slug,project:null,name:"a new project",docs:0,command:t(s.slug,"<name>"," --create")}]),r=Math.max(...o.map(s=>s.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(s=>` ${s.command.padEnd(r)} ${p(s.project?`${s.name} \xB7 ${V(s.docs,"doc")}`:s.name)}`),p("Subfolders become folders. `dockito tree` shows every place.")].join(`
10
- `),{choices:o})}async function ut(e,n,t){let o=Se()&&!t.json,r=await e.tree(),s=i=>!!qt(r.kinds,i);r.workspaces.some(_)||f("This login can't publish anywhere. Run `dockito login --update`.");let a=n;a||(o||ct(r,ie("docs")?"docs":"."),a=await he("Folder to sync",ie("docs")?"docs":"."));let c=Ce(a);(!ie(c)||!lt(c).isDirectory())&&f(`${a} isn't a folder.`);let d=dt(Ht(process.cwd(),c))||".",m;t.workspace&&(m=r.workspaces.find(i=>i.slug===t.workspace)??f(`This login can't reach workspace "${t.workspace}".`));let k=t.to;if(!k){o||ct(r,d);let i=(m?[m]:r.workspaces.filter(_)).flatMap(w=>[...w.projects.map(v=>({w,p:v.slug,label:v.name,hint:`${w.slug} \xB7 ${V(v.docs,"doc")}`})),{w,p:null,label:"New project",hint:w.slug}]),l=i[await et("Project",i)];m=l.w,k=l.p??await he("Project name",at(c===process.cwd()?c:Ce(c,".."))),t.create||=!l.p}if(!m){let i=r.workspaces.filter(w=>_(w)&&M(w,k.split("/")[0])),l=r.workspaces.find(w=>w.kind==="personal"&&w.role==="owner");if(m=i.length===1?i[0]:r.workspaces.length===1?r.workspaces[0]:!i.length&&l?l:void 0,!m)throw new D("needs_workspace",`Which workspace? Run again with -w: ${(i.length?i:r.workspaces).map(w=>w.slug).join(", ")}`)}let h=m;_(h)||f(`This login has View access in ${h.slug}; syncing needs Publish. Run \`dockito login --update\`.`);let[g,...j]=k.split("/").filter(Boolean);g||f("--to takes a project, or project/folder."),j.length>2&&f(`Folders go at most ${2} deep inside a project.`);let b=M(h,g);if(!b&&!t.create)throw new D("no_project",`No project "${g}" in ${h.slug}. Projects: ${h.projects.map(i=>i.slug).join(", ")||"none"}.
11
- To make it, run again with --create.`,{workspace:h.slug,projects:h.projects.map(i=>i.slug)});let K=zt(c,s);K.length||f(`No ${r.kinds.flatMap(i=>i.extensions).join(", ")} files in ${d}.`);let ce=2-j.length,X=K.filter(i=>i.split("/").length-1>ce);if(X.length&&!t.fold){let i=w=>w.split("/").slice(0,ce).join("/")||"the project's top",l=`${V(X.length,"file")} ${X.length===1?"is":"are"} more than ${2} folders deep inside the project. With --fold they go in ${[...new Set(X.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:X});if($(l),!await Ne("Fold them like that?"))return $(p("Nothing changed."));t.fold=!0}let Ue=i=>{let l=i.split("/");return{folder:[...j,...l.slice(0,-1).slice(0,ce)],dropped:l.slice(0,-1).slice(ce),file:l[l.length-1]}},je=new Map;for(let i of K){let l=Ue(i),w=[...l.folder,l.file].join("/").toLowerCase();je.set(w,(je.get(w)??0)+1)}let We=b?await e.files(h.id,b.id):[],Q=H(b),R=K.map(i=>{let{folder:l,dropped:w,file:v}=Ue(i),L=w.length&&je.get([...l,v].join("/").toLowerCase())>1?[...w,v].join("-"):v,P=new Uint8Array(Ut(C(c,i))),W={rel:i,name:L,folder:l,bytes:P,hash:Gt(P),action:"new",doc:null,others:[],assets:[]};/\.html?$/i.test(i)&&(W.assets=Yt(new TextDecoder().decode(P),C(c,i,".."),s));let Qe=Q.idOf(l);if(Qe===void 0)return W;let Z=ee(We,Qe,L);return Z.length>1?{...W,action:"ambiguous",others:Z}:Z.length?{...W,doc:Z[0],action:Z[0].sha256===W.hash?"same":"update"}:W}),He=R.filter(i=>i.bytes.length>h.maxFile);He.length&&f(`Over the ${re(h.maxFile)} limit per file in ${h.slug}:
13
- ${He.map(i=>` ${i.rel} ${re(i.bytes.length)}`).join(`
14
- `)}`);let ve=new Map;for(let i of R){let l=[...i.folder,i.name].join("/").toLowerCase();ve.set(l,[...ve.get(l)??[],i.rel])}let Be=[...ve.values()].filter(i=>i.length>1);Be.length&&f(`These files would be the same doc; rename one of each:
15
- ${Be.map(i=>" "+i.join(" and ")).join(`
16
- `)}`);let N=[],Je=new Set;for(let i of R)for(let l=1;l<=i.folder.length;l++){let w=i.folder.slice(0,l),v=w.join("/").toLowerCase();Q.idOf(w)===void 0&&!Je.has(v)&&(Je.add(v),N.push(w))}N.sort((i,l)=>i.length-l.length);let O=R.filter(i=>i.action==="new"||i.action==="update"),Ge=R.filter(i=>i.action==="ambiguous"),xe=R.filter(i=>i.action==="same").length,yt=new Set(R.flatMap(i=>i.doc?[i.doc.id]:i.others.map(l=>l.id))),Te=We.filter(i=>!yt.has(i.id)).length,qe=`${h.slug}/${b?.slug??g}${j.length?"/"+j.join("/"):""}`,le={workspace:h.slug,to:qe,create:{project:b?null:g,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:xe,ambiguous:Ge.map(i=>({file:i.rel,docs:i.others.map(l=>e.linkOf(l.slug))})),notHere:Te};if(!t.json){u(`${T(d==="."?"./":d+"/")} \u2192 ${T(qe)}`),b||u(` ${y("+ project")} ${g}`);for(let l of N)u(` ${y("+ folder ")} ${l.join("/")}`);if(O.length>15)for(let[l,w]of[["new",y("+ new ")],["update",E("~ update ")]]){let v=new Map;for(let L of O.filter(P=>P.action===l))v.set(L.folder.join("/"),[...v.get(L.folder.join("/"))??[],L.name]);for(let[L,P]of v)u(` ${w} ${P.length} in ${L||"the project's top"}: ${p(P.slice(0,4).join(", ")+(P.length>4?`, ${P.length-4} more`:""))}`)}else for(let l of O){let w=l.name!==at(l.rel)?p(` \u2192 ${[...l.folder,l.name].join("/")}`):"";u(` ${l.action==="new"?y("+ new "):E("~ update ")} ${l.rel}${w}`)}xe&&u(p(` ${xe} unchanged`));for(let l of Ge)u(` ${I("? skip ")} ${l.rel} ${p(`${l.others.length} docs there have this name: ${l.others.map(w=>e.linkOf(w.slug)).join(", ")}`)}`);Te&&u(p(` ${V(Te,"doc")} in the project with no file here, left as they are`)),O.length&&u(p(` ${V(O.length,"file")} to send, ${re(O.reduce((l,w)=>l+w.bytes.length,0))}`));let i=O.filter(l=>l.assets.length);i.length&&u(E(` ! ${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?u(JSON.stringify({...le,applied:!0},null,2)):u(`${y("\u2713")} Up to date`);return}if(t["dry-run"]){t.json&&u(JSON.stringify({...le,applied:!1},null,2));return}if((!b||N.length)&&!t.yes){let i=["dockito sync",d,`-w ${h.slug}`,`--to ${k}`,t.create&&!b?"--create":"",t.fold?"--fold":"","--yes"].filter(Boolean).join(" ");if(!o){if(t.json)return u(JSON.stringify({...le,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 Ne("Apply?"))return $(p("Nothing changed."))}if(!b){let i=await e.createProject(h.id,g);b={id:i.id,slug:i.slug,name:g,docs:0,folders:[]},t.json||u(`${y("\u2713")} Project ${g}`)}let Oe=b;for(let i of N){let l=await e.createFolder(Oe.id,i[i.length-1],i.length>1?Q.idOf(i.slice(0,-1)):null);Q.add(i,l.id)}N.length&&!t.json&&u(`${y("\u2713")} ${V(N.length,"folder")}`);let de=ye(["rev-parse","--show-toplevel"],c)?.trim()??null,ze=de?ye(["rev-parse","HEAD"],de)?.trim()??null:null,bt=new Set(de?(ye(["status","--porcelain","-z","--","."],c)??"").split("\0").filter(Boolean).map(i=>Ce(de,i.slice(3))):[]),U=[],ue=null,De=[...O],pe=!t.json&&Se(),$t=O.reduce((i,l)=>i+l.bytes.length,0),Ye=0,Ve=()=>pe&&process.stdout.write(`\r\x1B[2K${p(`Sending ${U.length} of ${O.length} \xB7 ${re(Ye)} of ${re($t)}`)}`);async function jt(i){let l=ze&&!bt.has(C(c,i.rel))?ze:null;for(;;)try{let w=i.doc?await e.update(i.doc.id,i.name,i.bytes,{commit:l}):await e.create(h.id,i.name,i.bytes,{project:Oe.id,folder:Q.idOf(i.folder)??null});return{file:i.rel,action:i.action,link:e.linkOf(w.slug)}}catch(w){if(w instanceof x&&w.code==="rate_limited"){ue||(t.json||$(p("\rUpload limit reached; carrying on in a minute\u2026")),ue=new Promise(v=>setTimeout(v,3e4)).then(()=>{ue=null})),await ue;continue}return{file:i.rel,action:i.action,error:w instanceof x?ne(w,e.url):String(w)}}}Ve(),await Promise.all(Array.from({length:Math.min(Jt,De.length)},async()=>{for(let i=De.shift();i;i=De.shift()){let l=await jt(i);U.push(l),Ye+=i.bytes.length,pe&&process.stdout.write("\r\x1B[2K"),l.error&&!t.json?u(`${I("\u2717")} ${l.file} ${I(l.error)}`):!t.json&&!pe&&u(`${y("\u2713")} ${l.file} ${p(l.link)}`),Ve()}})),pe&&process.stdout.write("\r\x1B[2K");let Ae=U.filter(i=>i.error),Xe=`${e.url}/${h.slug}/project/${Oe.slug}`;t.json?u(JSON.stringify({...le,applied:!0,link:Xe,results:U},null,2)):u(`${Ae.length?E(`${U.length-Ae.length} of ${U.length} sent`):y("\u2713 Synced")} ${p(Xe)}`),Ae.length&&(process.exitCode=1)}var Le=`Dockito hosts Markdown and HTML docs at a short link, with History.
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"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,V=e=>process.stdout.write(e);function at(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?Ce("\u276F "+f.label):" "+f.label}${f.hint?g(" "+f.hint):""}
5
+ `)};V(`${b("?")} ${S(e)} ${g("(\u2191\u2193, Enter)")}
6
+ \x1B[?25l`),a(!0),It(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)} ${Ce(t[i].label)}
8
+ `),o(i)):void 0;a(!1)};process.stdin.on("keypress",u)})}async function we(e,t=""){let n=_t({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 Rt}from"node:os";import{join as Re}from"node:path";var Lt="https://dockito-app.dockito.workers.dev";function ct(){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(Rt(),".config"),"dockito")}var lt=()=>(process.env.DOCKITO_URL||Lt).replace(/\/+$/,""),dt=e=>new URL(e).host;import{spawnSync as Ft}from"node:child_process";import{chmodSync as Mt,existsSync as Le,mkdirSync as Ht,readFileSync as Kt,rmSync as ut,writeFileSync as Ut}from"node:fs";import{dirname as Wt,join as Bt}from"node:path";var Y="dockito";function X(e,t,n){try{let o=Ft(e,t,{input:n,encoding:"utf8",stdio:["pipe","pipe","pipe"]});return o.error?null:o}catch{return null}}var re=e=>Bt(ct(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function qt(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 Jt(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 Gt(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 pt(e,t){if(qt(e,t))return Le(re(e))&&ut(re(e)),"keychain";let n=re(e);return Ht(Wt(n),{recursive:!0,mode:448}),Ut(n,t,{mode:384}),Mt(n,384),"file"}function ye(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let t=Jt(e);if(t)return t;let n=re(e);return Le(n)&&Kt(n,"utf8").trim()||null}function ht(e){Gt(e);let t=re(e);Le(t)&&ut(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 zt}from"node:crypto";import{spawnSync as Vt}from"node:child_process";import{existsSync as ae,readFileSync as Yt,readdirSync as Xt,statSync as mt}from"node:fs";import{basename as gt,join as L,relative as Qt,resolve as Me,sep as Zt}from"node:path";var en=4,ie=e=>e<1024*1024?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,tn=e=>zt("sha256").update(e).digest("hex"),wt=e=>e.split(Zt).join("/"),Q=(e,t)=>`${e} ${t}${e===1?"":"s"}`,nn=(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 on(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 Xt(L(e,i),{withFileTypes:!0}).flatMap(a=>a.name.startsWith(".")||a.name==="node_modules"?[]:a.isDirectory()?s(L(i,a.name)):[wt(L(i,a.name))])})("")).filter(s=>t(s)&&ae(L(e,s))&&mt(L(e,s)).isFile()).sort()}function sn(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 ft(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 yt(e,t,n){let o=Ie()&&!n.json,s=await e.tree(),i=r=>!!nn(s.kinds,r);s.workspaces.some(I)||p("This login can't publish anywhere. Run `dockito login --update`.");let a=t;a||(o||ft(s,ae("docs")?"docs":"."),a=await we("Folder to sync",ae("docs")?"docs":"."));let c=Me(a);(!ae(c)||!mt(c).isDirectory())&&p(`${a} isn't a folder.`);let u=wt(Qt(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||ft(s,u);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}]),d=r[await at("Project",r)];l=d.w,m=d.p??await we("Project name",gt(c===process.cwd()?c:Me(c,".."))),n.create||=!d.p}if(!l){let r=s.workspaces.filter(y=>I(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;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=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=on(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(Yt(L(c,r))),B={rel:r,name:F,folder:d,bytes:P,hash:tn(P),action:"new",doc:null,others:[],assets:[]};/\.html?$/i.test(r)&&(B.assets=sn(new TextDecoder().decode(P),L(c,r,".."),i));let rt=Z.idOf(d);if(rt===void 0)return B;let ee=te(Ve,rt,F);return ee.length>1?{...B,action:"ambiguous",others:ee}:ee.length?{...B,doc:ee[0],action:ee[0].sha256===B.hash?"same":"update"}:B}),Ye=H.filter(r=>r.bytes.length>f.maxFile);Ye.length&&p(`Over the ${ie(f.maxFile)} limit per file in ${f.slug}:
13
+ ${Ye.map(r=>` ${r.rel} ${ie(r.bytes.length)}`).join(`
14
+ `)}`);let Se=new Map;for(let r of H){let d=[...r.folder,r.name].join("/").toLowerCase();Se.set(d,[...Se.get(d)??[],r.rel])}let Xe=[...Se.values()].filter(r=>r.length>1);Xe.length&&p(`These files would be the same doc; rename one of each:
15
+ ${Xe.map(r=>" "+r.join(" and ")).join(`
16
+ `)}`);let C=[],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),C.push(y))}C.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,At=new Set(H.flatMap(r=>r.doc?[r.doc.id]:r.others.map(d=>d.id))),De=Ve.filter(r=>!At.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:C.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 C)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!==gt(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&&!C.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||C.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 C){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)}C.length&&!n.json&&h(`${b("\u2713")} ${Q(C.length,"folder")}`);let pe=$e(["rev-parse","--show-toplevel"],c)?.trim()??null,tt=pe?$e(["rev-parse","HEAD"],pe)?.trim()??null:null,Dt=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(),Nt=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(Nt)}`)}`);async function Pt(r){let d=tt&&!Dt.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(en,Pe.length)},async()=>{for(let r=Pe.shift();r;r=Pe.shift()){let d=await Pt(r);W.push(d),nt+=r.bytes.length,ge&&process.stdout.write("\r\x1B[2K"),d.error&&!n.json?h(`${_("\u2717")} ${d.file} ${_(d.error)}`):!n.json&&!ge&&h(`${b("\u2713")} ${d.file} ${g(d.link)}`),ot()}})),ge&&process.stdout.write("\r\x1B[2K");let Ee=W.filter(r=>r.error),st=`${e.url}/${f.slug}/project/${Ne.slug}`;n.json?h(JSON.stringify({...ue,applied:!0,link:st,results:W},null,2)):h(`${Ee.length?R(`${W.length-Ee.length} of ${W.length} sent`):b("\u2713 Synced")} ${g(st)}`),Ee.length&&(process.exitCode=1)}var He=`Dockito hosts Markdown and HTML docs, and live sheets, at a short link,
18
+ with History.
18
19
 
19
20
  workspace a person's or a team's space; its slug is in every link
20
21
  project docs grouped in a workspace, with folders up to 2 deep
@@ -36,7 +37,17 @@ For AI agents:
36
37
  - A folder of docs: \`dockito sync <folder> --to <project>\`. It shows a
37
38
  plan; show it to the user before running with --yes.
38
39
  - \`dockito <command> --help\` explains each command. Use --json; on
39
- failure, read "error" and the commands it suggests.`,be={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
40
+ failure, read "error" and the commands it suggests.
41
+
42
+ Sheets: people and agents edit the same cells, live. Publishing a .csv
43
+ makes a new one.
44
+ - \`dockito get <sheet>\` gives its outline (sheets, columns, what's in
45
+ them) and its version, v N. \`read\` gives rows, \`check\` finds broken
46
+ formulas, \`changes --since N\` lists what changed.
47
+ - \`dockito apply <sheet> changes.json --base N\`, with N the version you
48
+ last read. Addresses and rows mean what they meant then. If someone
49
+ changed the same cells meanwhile, nothing applies and it says who
50
+ (exit 1): read again, then apply on the new version.`,ve={sync:{use:"sync <folder> --to <project>",about:"Mirror a folder into a project",page:`Mirrors a local folder into a project: subfolders become folders, and each
40
51
  file updates the doc with the same file name in the same folder, or becomes
41
52
  a new doc there. Only files whose content changed are sent. Nothing is moved
42
53
  or deleted in Dockito. The CLI remembers nothing: pass --to every time.
@@ -57,7 +68,8 @@ the plan to the user first. Sending docs into folders that exist just runs.
57
68
  dockito sync docs --to handbook
58
69
  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
59
70
  updates its doc (same link, a new History entry); otherwise it's a new doc.
60
- Unchanged files are skipped.
71
+ Unchanged files are skipped. A .csv becomes a new live sheet, typed as Excel
72
+ opens it; after that it changes with \`dockito apply\`, never by a file.
61
73
 
62
74
  Docs are private: only people in the workspace can open them.
63
75
  Add --public only when the user clearly asked for a public link (anyone
@@ -92,6 +104,8 @@ as --to, like publish. -w for another workspace.
92
104
  dockito ls --to handbook/specs`},mkdir:{use:"mkdir <project/folder>",about:"Make a project or folders",page:`Makes whatever is missing: the project, then up to 2 levels of folders.
93
105
 
94
106
  dockito mkdir handbook/specs/drafts`},open:{use:"open <doc>",about:"Open in the browser",page:"Opens a doc (link or code) in the browser."},get:{use:"get <doc> [-o file]",about:"Download a doc, or read it in parts",page:`Prints a doc's file, or writes it with -o. Big docs can be read in parts.
107
+ A sheet prints its outline: each sheet's columns, what's in them, and the
108
+ version to apply changes at; \`dockito read\` gives its rows.
95
109
 
96
110
  --outline Only the headings, each with the line it starts on
97
111
  --lines <range> Only these lines: 120-260, 120- (to the end) or 120
@@ -101,24 +115,86 @@ as --to, like publish. -w for another workspace.
101
115
  -o, --output <file> Write it to a file
102
116
 
103
117
  dockito get <doc> --outline
104
- dockito get <doc> --lines 120-260 --text`},history:{use:"history <doc>",about:"History entries",page:`Lists a doc's History: entry, when, who, from where (upload, edit, cli,
118
+ 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
119
+ the version (v N) to apply changes at. A range, or a sheet's rows under its
120
+ header row, filtered and sorted; with --group or --totals, totals instead.
121
+ 200 rows unless --limit says, at most 1,000; page on with --from.
122
+
123
+ [range] Tasks!A1:G40, Tasks!B:B, Tasks!3:5, a defined name,
124
+ or E17 when there's one sheet
125
+ --sheet <name> A sheet or table's rows under its header row
126
+ --where <filter> "Area = Kitchen and Status <> Done or Hours > 4":
127
+ = <> < > <= >= contains, is empty, is not empty;
128
+ dates as yyyy-mm-dd
129
+ --select <cols> Columns to show, by header or letter: ID,Status
130
+ --order-by <cols> "Due desc, ID"; empty cells go last
131
+ --group <cols> Group the rows by these columns
132
+ --totals <list> count, sum Hours, avg Hours, min Due, max Due
133
+ --from <n>, --limit <n> Which rows: from 1; 200 by default, 1,000 at most
134
+ --formulas Formulas with their results: =F2*50 \u2192 50
135
+ --styles Formatting in words, merges, sizes, frozen panes
136
+ --csv As CSV instead of a table
137
+
138
+ dockito read <doc> --where "Status = Blocked" --select ID,Owner,Due
139
+ dockito read <doc> --group Owner --totals "count,sum Hours"
140
+ 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
141
+ changes showed. Addresses, rows and headers mean what they meant then,
142
+ wherever they moved since. Every change applies, or none: a cell someone
143
+ changed since is refused, naming who (exit 1). Read again and apply on the
144
+ new version. Prints each cell old \u2192 new and the new version.
145
+
146
+ The file (or - for stdin) is a list of changes, or {"base": N, "ops": [...],
147
+ "expect": {...}}. "sheet" can be left out when there's one. Each change:
148
+ {"set": "Tasks!E17", "value": "Done"}
149
+ {"set": "Tasks!B2:C3", "values": [["Tile grout", "Kitchen"], ["Vent", "Bath"]]}
150
+ {"update": {"sheet": "Tasks", "where": "ID = T-104", "set": {"Status": "Done"}}}
151
+ {"append": {"sheet": "Tasks", "rows": [{"ID": "T-214", "Status": "Open"}]}}
152
+ {"insertRows": {"sheet": "Tasks", "after": 40, "count": 2}}, deleteRows
153
+ {"rows": "41:42"}, moveRows {"rows": "5:6", "after": 10}; insertCols,
154
+ deleteCols, moveCols the same with letters
155
+ {"format": {"range": "Tasks!1:1", "bold": true, "fill": "#fff2cc"}}
156
+ {"resize": {"range": "Tasks!B:C", "size": 120}}, merge, unmerge
157
+ addSheet, renameSheet, moveSheet, deleteSheet, sheetProps (frozenRows,
158
+ hidden, tabColor), setName, deleteName, addTable, setTable, deleteTable
159
+ Values: a number, true, false, null (empty), a string ("=\u2026" is a formula),
160
+ {"text": "=as typed"} or {"date": "2026-10-19"}.
161
+
162
+ --base <n> The version you read
163
+ -m, --message <note> Why, shown with the change in the sheet
164
+ --dry-run Show what would happen, save nothing
165
+ --undo <n> Take back your own change that made version n
166
+ --key <key> A retry key (default: made from the request, so
167
+ the same request on the same version applies once)
168
+
169
+ dockito apply <doc> changes.json --base 412 -m "close finished tasks"
170
+ echo '[{"set": "E17", "value": "Done"}]' | dockito apply <doc> - --base 412`},check:{use:"check <doc>",about:"Problems in a sheet",page:`Finds problems in a sheet, by rules: formula errors, a formula that breaks
171
+ its column's pattern, a value typed over a column's formulas, numbers or
172
+ dates typed as text, totals that stop before their column does.
173
+
174
+ --sheet <name> One sheet (default: every sheet)`},changes:{use:"changes <doc> --since <v>",about:"What changed in a sheet",page:`Lists what changed after a version, or since a time in UTC, oldest first:
175
+ who, when, through what, with each cell old \u2192 new, named where it is now.
176
+ Ends at the current version, to apply changes at.
177
+
178
+ --since <v|time> 412, 2026-10-13 or "2026-10-13 09:00"
179
+ --max <n> At most this many changes (default 50, at most 200)`},history:{use:"history <doc>",about:"History entries",page:`Lists a doc's History: entry, when, who, from where (upload, edit, cli,
105
180
  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
106
181
  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
107
182
  in each workspace: View, Publish or Everything. --update changes that access.
108
- --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 Fe="0.0.1-beta.7",S=nt(),ae=ot(S),Ke=`dockito-cli/${Fe} (${process.platform})`;function A(){let e=me(ae);return e||f("Not logged in. Run `dockito login`."),ge({url:S,token:e,source:"cli",userAgent:Ke})}function kt(e){let[n,t]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let o=Vt(n,t,{stdio:"ignore",detached:!0});return o.on("error",()=>{}),o.unref(),!0}catch{return!1}}var rn=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),an=e=>new Promise(n=>setTimeout(n,e)),cn=()=>tn().replace(/\.local$/,"").slice(0,60)||"Unknown device";function F(e){return Ze(e)??f(`"${e}" isn't a doc link or code. \`dockito ls\` lists docs with their codes.`)}function ft(e){let n=nn(wt(e)),t=pt("git",["rev-parse","HEAD"],{cwd:n,encoding:"utf8"});if(t.error||t.status!==0)return null;let o=pt("git",["status","--porcelain","--",Me(e)],{cwd:n,encoding:"utf8"});return o.status!==0||o.stdout.trim()?null:t.stdout.trim()}var ln=e=>Xt("sha256").update(e).digest("hex");async function dn(e){let n=me(ae);e.update&&!n&&f("Not logged in. Run `dockito login` first."),process.env.DOCKITO_TOKEN&&$(E("DOCKITO_TOKEN is set, so commands will keep using it instead of this login."));let t=ge({url:S,token:e.update?n:null,userAgent:Ke}),o=await t.startLogin("Dockito CLI",cn()),r=!e["no-browser"]&&rn()&&kt(o.verification_uri_complete);u(r?`Opening ${o.verification_uri_complete}`:`Open ${T(o.verification_uri)}`),u(`Code: ${T(o.user_code)}`),$(p("Waiting for approval\u2026"));let s=o.interval*1e3,a=Date.now()+o.expires_in*1e3;for(;Date.now()<a;){await an(s);try{let c=await t.pollLogin(o.device_code);if(!c)continue;let d=rt(ae,c.token);u(`${y("\u2713")} Logged in as ${c.user.name}${c.user.username?p(` (${c.user.username})`):""}`);for(let m of c.workspaces)u(` ${m.name} ${p(`${m.slug} \xB7 ${J[m.level]}`)}`);d==="file"&&$(E("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"&&f("Denied in the browser."),c instanceof x&&c.code==="expired_token"&&f("The code expired. Run `dockito login` again."),c instanceof x&&c.code==="offline"){s=Math.min(s*2,3e4);continue}throw c}}f("The code expired. Run `dockito login` again.")}async function un(){let e=me(ae);if(!e)return u("Not logged in.");process.env.DOCKITO_TOKEN&&f("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await ge({url:S,token:e,userAgent:Ke}).logout()}catch(n){if(!(n instanceof x)||n.code!=="invalid_token"&&n.code!=="not_found")throw n}it(ae),u(`${y("\u2713")} Logged out`)}async function pn(e){let n=await A().whoami();if(e.json)return u(JSON.stringify(n,null,2));u(`${n.user.name} ${p(`${n.user.username} \xB7 ${n.user.email}`)}`),n.token&&u(p(`${n.token.name} \xB7 ${n.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(n.token.expiresAt).toLocaleDateString()}`}`)),u(),te(n.workspaces.map(t=>[t.slug,t.name,p(t.level?J[t.level]:t.role)])),S!=="https://dockito-app.dockito.workers.dev"&&$(p(`
109
- ${S}`))}async function gn(e,n){e.length||f("Name a file: `dockito publish notes.md --to <project/folder>`"),n.doc&&e.length>1&&f("--doc takes one file."),n.title!==void 0&&e.length>1&&f("--title takes one file."),n.title!==void 0&&!n.title.trim()&&f("--title needs a title."),n.public&&n.doc&&f("--public is only for a new doc; an existing one can be made public from Share in the app.");for(let d of e)(!Qt(d)||!Zt(d).isFile())&&f(`${d} isn't a file${on(d)?"":` (looked for ${wt(d)})`}.`);let t=A(),o=[];if(n.doc){let d=await t.doc(F(n.doc)),m=new Uint8Array(gt(e[0])),k=await t.update(d.doc.id,Me(e[0]),m,{note:n.message,commit:ft(e[0])});return n.json?u(JSON.stringify([{file:e[0],...k,link:t.linkOf(k.slug)}],null,2)):u(k.unchanged?`${p("=")} ${e[0]} ${p("unchanged")} ${t.linkOf(k.slug)}`:`${y("\u2713")} ${e[0]} \u2192 ${t.linkOf(k.slug)}`)}let r=await t.tree(),s=we(r,n.workspace);_(s)||f(`This login has View access in ${s.slug}. Run \`dockito login --update\`.`);let a=Ee(s,n.to),c=await t.files(s.id,a.project);for(let d of e){let m=new Uint8Array(gt(d)),k=Me(d),h=n.new?[]:ee(c,a.folder,k);h.length>1&&f(`${h.length} docs in ${a.label} are named ${k}: ${h.map(K=>t.linkOf(K.slug)).join(", ")}. Pick one with --doc, or publish a new one with --new.`);let g=h[0];if(g&&n.public&&!(await t.doc(g.slug)).doc.public&&f(`${k} is already a private doc in ${a.label} (${t.linkOf(g.slug)}). Only Share in the app can make it public; \`--new --public\` publishes a new public doc.`),g&&g.sha256===ln(m)&&!n.message){o.push({file:d,id:g.id,slug:g.slug,version:g.version,unchanged:!0,link:t.linkOf(g.slug)}),n.json||u(`${p("=")} ${d} ${p("unchanged")} ${t.linkOf(g.slug)}`);continue}let j=g?await t.update(g.id,k,m,{note:n.message,commit:ft(d)}):await t.create(s.id,k,m,{project:a.project,folder:a.folder,public:n.public,title:n.title});!g&&n.message&&await t.save(j.id,j.version,n.message);let b="public"in j&&j.public===!0;o.push({file:d,...j,link:t.linkOf(j.slug),new:!g}),n.json||u(`${y("\u2713")} ${d} \u2192 ${t.linkOf(j.slug)} ${p(g?j.unchanged?"unchanged":"updated":`new in ${a.label}`)}${b?` ${y("\xB7 public")}`:""}${n.message?p(` \xB7 saved: ${n.message}`):""}`)}n.json?u(JSON.stringify(o,null,2)):!n.to&&o.some(d=>d.new)&&$(p("In the workspace's Docs. To put docs in a project: --to <project/folder>; `dockito tree` lists them."))}async function fn(e){let n=await A().tree();if(e.json)return u(JSON.stringify(n,null,2));let t=o=>p(`${o} doc${o===1?"":"s"}`);for(let o of n.workspaces){u(`${T(o.slug)} ${o.name} ${p(`\xB7 ${o.level?J[o.level]:o.role}`)}`);let r=[[p("Docs"),"",t(o.docs)]];for(let s of o.projects){r.push([s.slug,s.name,t(s.docs)]);let a=(c,d)=>{for(let m of s.folders.filter(k=>k.parentId===c))r.push([`${d}/${m.name}`,"",t(m.docs)]),a(m.id,`${d}/${m.name}`)};a(null,s.slug)}te(r.map(s=>[" ",...s])),u()}$(p("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function hn(e,n){e&&n.to&&e!==n.to&&f(`Two places: ${e} and --to ${n.to}. Give one.`);let t=e??n.to,o=A(),r=await o.tree(),s=we(r,n.workspace),a=Ee(s,t),c=await o.docs(s.id,{project:a.project,folder:a.folder}),d=a.project?s.projects.find(g=>g.id===a.project):null,m=t?[]:s.projects,k=d?d.folders.filter(g=>g.parentId===a.folder):[];if(n.json)return u(JSON.stringify({workspace:s.slug,place:t??null,projects:m,folders:k,docs:c},null,2));let h=[];for(let g of m)h.push([T(`${g.slug}/`),g.name,p(`${g.docs} doc${g.docs===1?"":"s"}`),""]);for(let g of k)h.push([T(`${g.name}/`),"",p(`${g.docs} doc${g.docs===1?"":"s"}`),""]);for(let g of c)h.push([g.slug,g.title+p(g.kind==="html"?" .html":" .md"),p(fe(g.updatedAt)),p(g.updatedByName??"")]);if(!h.length)return $(p(`Nothing in ${a.label} yet.`));te(h)}async function mn(e,n){let[t,...o]=(e??"").split("/").filter(Boolean);t||f("`dockito mkdir <project>[/folder[/subfolder]]`"),o.length>2&&f("Folders go at most 2 deep inside a project.");let r=A(),s=we(await r.tree(),n.workspace),a=M(s,t),c=a?.id??(await r.createProject(s.id,t)).id,d=!a;a||u(`${y("\u2713")} Project ${t}`);let m=H(a),k=null;for(let h=0;h<o.length;h++){let g=m.idOf(o.slice(0,h+1));if(g){k=g;continue}k=(await r.createFolder(c,o[h],k)).id,m.add(o.slice(0,h+1),k),d=!0,u(`${y("\u2713")} Folder ${o.slice(0,h+1).join("/")}`)}d||$(p("Already there."))}async function wn(e){e||f("Name a doc: `dockito open <link or code>`");let n=`${S}/${F(e)}`;kt(n)||u(n)}async function kn(e,n){e||f("Name a doc: `dockito get <link or code> [-o file]`"),n.outline&&(n.lines!==void 0||n.text)&&f("--outline is the headings only; leave out --lines and --text.");let t=A(),o=await t.doc(F(e)),r=n.entry?Number(n.entry):null;n.entry&&(!Number.isInteger(r)||r<1)&&f("--entry takes an entry number from `dockito history`.");let s=await t.content(o.doc.id,r,{outline:n.outline,lines:n.lines,text:n.text}),a=s.bytes.length&&s.lines!==null&&!n.output&&s.bytes[s.bytes.length-1]!==10?new Uint8Array([...s.bytes,10]):s.bytes;if(!n.output)return void process.stdout.write(a);en(n.output,a);let c=n.outline?" outline":s.shown?` lines ${s.shown} of ${s.lines}`:n.text?" as text":"";$(`${y("\u2713")} ${n.output} ${p(`\u2190 ${t.linkOf(o.doc.slug)}${r?` entry ${r}`:""}${c}`)}`)}async function yn(e,n){e||f("Name a doc: `dockito history <link or code>`");let t=await A().doc(F(e));if(n.json)return u(JSON.stringify(t.versions,null,2));u(`${T(t.doc.title)} ${p(`${S}/${t.doc.slug}`)}`),te(t.versions.map((o,r)=>[String(o.number),r===0?y("current"):p(fe(o.createdAt)),r===0?p(fe(o.createdAt)):"",o.byName??"",p(o.source),o.savedAt?`${E("saved")}${o.note?` ${o.note}`:""}`:"",o.gitCommit?p(o.gitCommit.slice(0,7)):""]))}async function bn(e,n){let t=Number(n);(!e||!Number.isInteger(t)||t<1)&&f("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let o=A(),r=await o.doc(F(e)),s=await o.restore(r.doc.id,t,r.versions[0]?.number??null);u(`${y("\u2713")} Restored entry ${t} as entry ${s.version} ${p(o.linkOf(s.slug))}`)}async function $n(e,n,t){e||f('`dockito save <doc> [entry] -m "note"`');let o=A(),r=await o.doc(F(e)),s=n?Number(n):r.versions[0]?.number;(!Number.isInteger(s)||s<1)&&f("Entries are in `dockito history`."),await o.save(r.doc.id,s,t.message??null),u(`${y("\u2713")} Saved entry ${s}${t.message?p(` \xB7 ${t.message}`):""}`)}async function jn(e,n){e||f("`dockito share <link or code> [--private]`"),n.public&&f("An existing doc can be made public only from Share in the app. `dockito publish <file> --public` makes a new doc public.");let t=A(),o=await t.doc(F(e)),r=t.linkOf(o.doc.slug);if(n.private)return o.doc.public&&await t.makePrivate(o.doc.id),n.json?u(JSON.stringify({public:!1,link:r},null,2)):u(`${y("\u2713")} Private: only people in ${o.workspace.name} can open it ${p(r)}`);if(n.json)return u(JSON.stringify({public:o.doc.public,link:r},null,2));u(o.doc.public?`${y("Public")}: anyone with the link can open it ${r}`:`Private: only people in ${o.workspace.name} can open it ${p(r)}`)}var Re=`Dockito CLI ${Fe}
183
+ --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.8",E=lt(),ce=dt(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 Ot(e){let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let o=rn(t,n,{stdio:"ignore",detached:!0});return o.on("error",()=>{}),o.unref(),!0}catch{return!1}}var hn=()=>!process.env.SSH_CONNECTION&&!process.env.CI&&(process.platform!=="linux"||!!process.env.DISPLAY||!!process.env.WAYLAND_DISPLAY),gn=e=>new Promise(t=>setTimeout(t,e)),fn=()=>ln().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 $t(e){let t=dn(Tt(e)),n=kt("git",["rev-parse","HEAD"],{cwd:t,encoding:"utf8"});if(n.error||n.status!==0)return null;let o=kt("git",["status","--porcelain","--",Be(e)],{cwd:t,encoding:"utf8"});return o.status!==0||o.stdout.trim()?null:n.stdout.trim()}var St=e=>an("sha256").update(e).digest("hex");async function mn(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",fn()),s=!e["no-browser"]&&hn()&&Ot(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 gn(i);try{let c=await n.pollLogin(o.device_code);if(!c)continue;let u=pt(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 wn(){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}ht(ce),h(`${b("\u2713")} Logged out`)}async function yn(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(`
184
+ ${E}`))}async function kn(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)(!xt(l)||!cn(l).isFile())&&p(`${l} isn't a file${un(l)?"":` (looked for ${Tt(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:$t(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),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===St(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:$t(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 bn(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 $n(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 vn(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 jn(e){e||p("Name a doc: `dockito open <link or code>`");let t=`${E}/${M(e)}`;Ot(t)||h(t)}async function xn(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?(bt(t.output,`${l.text.trimEnd()}
185
+ `),$(`${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);bt(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 Tn(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 On(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 Sn(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 An(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 Dn(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 Nn(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"):xt(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??Pn(a);de(await o.changeSheet(s.doc.id,{key:c,...a}),n.json)}var Pn=e=>St(new TextEncoder().encode(JSON.stringify(e))).slice(0,32);async function En(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}
110
186
 
111
- ${Le}
187
+ ${He}
112
188
 
113
189
  Usage: dockito <command>
114
190
 
115
- ${Object.values(be).map(e=>` ${e.use.padEnd(30)}${e.about}`).join(`
191
+ ${Object.values(ve).map(e=>` ${e.use.padEnd(30)}${e.about}`).join(`
116
192
  `)}
117
193
 
118
194
  <doc> is a link or a code. \`dockito <command> --help\` for more.
119
195
  -w, --workspace <slug> Another workspace (default: your personal one)
120
196
  --json Machine-readable output
121
197
 
122
- Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`,ht=e=>{let n=be[e];return n?`dockito ${n.use}
198
+ Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`,vt=e=>{let t=ve[e];return t?`dockito ${t.use}
123
199
 
124
- ${n.page}`:null},vn={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}},mt={message:"-m",workspace:"-w",output:"-o",yes:"-y"};function xn(e,n,t){let o=vn[e];if(!o)return;let r=`\`dockito ${be[e]?.use??e}\`; \`dockito ${e} --help\` for more.`,s=n.filter(a=>!o.flags.includes(a));if(s.length){let a=s.map(d=>`--${d}`).join(", "),c=o.flags.length?` It takes ${o.flags.map(d=>mt[d]?`${mt[d]}/--${d}`:`--${d}`).join(", ")}.`:"";f(`${e} doesn't take ${a}.${c} Use: ${r}`)}t.length>o.args&&f(`${e} takes ${o.args===0?"no arguments":`at most ${o.args} argument${o.args===1?"":"s"}`}; got ${t.map(a=>`"${a}"`).join(" ")}. Use: ${r}`)}async function Tn(){let{values:e,positionals:n}=sn({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"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[t,...o]=n;if(e.version)return u(Fe);if(t==="help"&&o[0])return u(ht(o[0])??Re);if(e.help&&t)return u(ht(t)??Re);if(e.help||!t||t==="help")return u(Re);switch(xn(t,Object.keys(e).filter(r=>e[r]!==void 0),o),t){case"login":return dn(e);case"logout":return un();case"whoami":return pn(e);case"sync":return ut(A(),o[0],e);case"publish":return gn(o,e);case"tree":return fn(e);case"ls":return hn(o[0],e);case"mkdir":return mn(o[0],e);case"share":return jn(o[0],e);case"open":return wn(o[0]);case"get":return kn(o[0],e);case"history":return yn(o[0],e);case"restore":return bn(o[0],o[1]);case"save":return $n(o[0],o[1],e);case"agent":return u(Le);default:f(`Unknown command "${t}". Run \`dockito --help\`.`)}}function $e(e){let n=2,t=()=>--n===0&&process.exit(e);process.stdout.write("",t),process.stderr.write("",t)}Tn().then(()=>$e(Number(process.exitCode??0)),e=>{let n=process.argv.includes("--json");if(e instanceof D)return n?u(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2)):$(e.message),$e(2);if(e instanceof oe)return $e(130);n&&e instanceof x?u(JSON.stringify({error:e.code,message:ne(e,S),...e.detail},null,2)):n&&e instanceof B?u(JSON.stringify({error:"failed",message:e.message},null,2)):e instanceof B?$(I(e.message)):e instanceof x?$(I(ne(e,S))):e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message))?$(I(`${e.message}. Run \`dockito --help\`.`)):$(I(String(e?.stack??e))),$e(1)});
200
+ ${t.page}`:null},In={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}},jt={message:"-m",workspace:"-w",output:"-o",yes:"-y"};function _n(e,t,n){let o=In[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=>jt[u]?`${jt[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 Rn(){let{values:e,positionals:t}=pn({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(vt(o[0])??We);if(e.help&&n)return h(vt(n)??We);if(e.help||!n||n==="help")return h(We);switch(_n(n,Object.keys(e).filter(s=>e[s]!==void 0),o),n){case"login":return mn(e);case"logout":return wn();case"whoami":return yn(e);case"sync":return yt(T(),o[0],e);case"publish":return kn(o,e);case"tree":return bn(e);case"ls":return $n(o[0],e);case"mkdir":return vn(o[0],e);case"share":return An(o[0],e);case"open":return jn(o[0]);case"get":return xn(o[0],e);case"history":return Tn(o[0],e);case"restore":return On(o[0],o[1]);case"save":return Sn(o[0],o[1],e);case"agent":return h(He);case"read":return Dn(o[0],o[1],e);case"apply":return Nn(o[0],o[1],e);case"check":return En(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)}Rn().then(()=>je(Number(process.exitCode??0)),e=>{let t=process.argv.includes("--json");if(e instanceof N)return t?h(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2)):$(e.message),je(2);if(e instanceof se)return je(130);t&&e instanceof x?h(JSON.stringify({error:e.code,message:oe(e,E),...e.detail},null,2)):t&&e instanceof J?h(JSON.stringify({error:"failed",message:e.message},null,2)):e instanceof J?$(_(e.message)):e instanceof x?$(_(oe(e,E))):e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message))?$(_(`${e.message}. Run \`dockito --help\`.`)):$(_(String(e?.stack??e))),je(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dockito",
3
- "version": "0.0.1-beta.7",
3
+ "version": "0.0.1-beta.8",
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",