dockito 0.0.1-beta.1 → 0.0.1-beta.10
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.
- package/README.md +8 -3
- package/dockito.js +218 -46
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -4,17 +4,22 @@ Publish the docs your AI writes as links you can share, from the terminal.
|
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npx dockito@beta login
|
|
7
|
+
npx dockito@beta sync docs --to my-project --create # a folder of docs, as a project
|
|
7
8
|
npx dockito@beta publish plan.md -m "Sent to the client"
|
|
8
9
|
```
|
|
9
10
|
|
|
10
11
|
| Command | Does |
|
|
11
12
|
|---|---|
|
|
12
13
|
| `login` | Sign in through the browser and choose what this login may do in each workspace |
|
|
13
|
-
| `
|
|
14
|
+
| `sync <folder> --to <project>` | Mirror a folder into a project: subfolders become folders, only changed files are sent, a plan first |
|
|
15
|
+
| `publish <file...>` | Publish a file; one with the same name in that place updates its doc |
|
|
16
|
+
| `share <doc>` | Who can open it. Docs are private; `publish --public` makes a new doc public, `share --private` takes it back |
|
|
17
|
+
| `tree`, `mkdir` | Every workspace, project and folder you can use; make new ones |
|
|
14
18
|
| `ls`, `open`, `get` | List, open and download docs |
|
|
15
19
|
| `history`, `restore`, `save` | A doc's History |
|
|
16
|
-
| `
|
|
20
|
+
| `read`, `apply`, `check`, `changes` | Live sheets: read rows, change cells at the version you read, find broken formulas, see what changed. `publish data.csv` makes one |
|
|
21
|
+
| `whoami`, `logout` | Your login and workspaces |
|
|
17
22
|
|
|
18
|
-
Run `dockito --help` for everything. `--json` gives machine-readable output for agents, and `DOCKITO_TOKEN` uses a token from Account → Tokens instead of a login.
|
|
23
|
+
The CLI keeps nothing but your login: where docs go is passed with `--to` each time. Run `dockito --help` for everything, `dockito <command> --help` for one command, and `dockito agent` for what an AI agent needs to know. `--json` gives machine-readable output for agents, and `DOCKITO_TOKEN` uses a token from Account → Tokens instead of a login.
|
|
19
24
|
|
|
20
25
|
This is a beta: commands may still change.
|
package/dockito.js
CHANGED
|
@@ -1,50 +1,222 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
`)}
|
|
5
|
-
`);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`)
|
|
15
|
-
|
|
16
|
-
`)return
|
|
17
|
-
`}return e&&e.
|
|
18
|
-
|
|
19
|
-
`.indexOf(e.charAt(r))!==-1}var Q;(function(e){e.DEFAULT={allowTrailingComma:!1}})(Q||(Q={}));function ze(e,r=[],t=Q.DEFAULT){let n=null,i=[],o=[];function c(l){if(Array.isArray(i))i.push(l);else if(n!==null)i[n]=l}return ve(e,{onObjectBegin:()=>{let l={};c(l),o.push(i),i=l,n=null},onObjectProperty:(l)=>{n=l},onObjectEnd:()=>{i=o.pop()},onArrayBegin:()=>{let l=[];c(l),o.push(i),i=l,n=null},onArrayEnd:()=>{i=o.pop()},onLiteralValue:c,onError:(l,u,a)=>{r.push({error:l,offset:u,length:a})}},t),i[0]}function Te(e,r=[],t=Q.DEFAULT){let n={type:"array",offset:-1,length:-1,children:[],parent:void 0};function i(l){if(n.type==="property")n.length=l-n.offset,n=n.parent}function o(l){return n.children.push(l),l}ve(e,{onObjectBegin:(l)=>{n=o({type:"object",offset:l,length:-1,parent:n,children:[]})},onObjectProperty:(l,u,a)=>{n=o({type:"property",offset:u,length:-1,parent:n,children:[]}),n.children.push({type:"string",value:l,offset:u,length:a,parent:n})},onObjectEnd:(l,u)=>{i(l+u),n.length=l+u-n.offset,n=n.parent,i(l+u)},onArrayBegin:(l,u)=>{n=o({type:"array",offset:l,length:-1,parent:n,children:[]})},onArrayEnd:(l,u)=>{n.length=l+u-n.offset,n=n.parent,i(l+u)},onLiteralValue:(l,u,a)=>{o({type:_n(l),offset:u,length:a,parent:n,value:l}),i(u+a)},onSeparator:(l,u,a)=>{if(n.type==="property"){if(l===":")n.colonOffset=u;else if(l===",")i(u)}},onError:(l,u,a)=>{r.push({error:l,offset:u,length:a})}},t);let s=n.children[0];if(s)delete s.parent;return s}function se(e,r){if(!e)return;let t=e;for(let n of r)if(typeof n==="string"){if(t.type!=="object"||!Array.isArray(t.children))return;let i=!1;for(let o of t.children)if(Array.isArray(o.children)&&o.children[0].value===n&&o.children.length===2){t=o.children[1],i=!0;break}if(!i)return}else{let i=n;if(t.type!=="array"||i<0||!Array.isArray(t.children)||i>=t.children.length)return;t=t.children[i]}return t}function ve(e,r,t=Q.DEFAULT){let n=q(e,!1),i=[],o=0;function c(w){return w?()=>o===0&&w(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function s(w){return w?(j)=>o===0&&w(j,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter()):()=>!0}function l(w){return w?(j)=>o===0&&w(j,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>i.slice()):()=>!0}function u(w){return w?()=>{if(o>0)o++;else if(w(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter(),()=>i.slice())===!1)o=1}:()=>!0}function a(w){return w?()=>{if(o>0)o--;if(o===0)w(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:()=>!0}let p=u(r.onObjectBegin),h=l(r.onObjectProperty),b=a(r.onObjectEnd),k=u(r.onArrayBegin),S=a(r.onArrayEnd),A=l(r.onLiteralValue),N=s(r.onSeparator),_=c(r.onComment),d=s(r.onError),v=t&&t.disallowComments,m=t&&t.allowTrailingComma;function g(){while(!0){let w=n.scan();switch(n.getTokenError()){case 4:f(14);break;case 5:f(15);break;case 3:f(13);break;case 1:if(!v)f(11);break;case 2:f(12);break;case 6:f(16);break}switch(w){case 12:case 13:if(v)f(10);else _();break;case 16:f(1);break;case 15:case 14:break;default:return w}}}function f(w,j=[],xe=[]){if(d(w),j.length+xe.length>0){let ne=n.getToken();while(ne!==17){if(j.indexOf(ne)!==-1){g();break}else if(xe.indexOf(ne)!==-1)break;ne=g()}}}function R(w){let j=n.getTokenValue();if(w)A(j);else h(j),i.push(j);return g(),!0}function de(){switch(n.getToken()){case 11:let w=n.getTokenValue(),j=Number(w);if(isNaN(j))f(2),j=0;A(j);break;case 7:A(null);break;case 8:A(!0);break;case 9:A(!1);break;default:return!1}return g(),!0}function ge(){if(n.getToken()!==10)return f(3,[],[2,5]),!1;if(R(!1),n.getToken()===6){if(N(":"),g(),!me())f(4,[],[2,5])}else f(5,[],[2,5]);return i.pop(),!0}function an(){p(),g();let w=!1;while(n.getToken()!==2&&n.getToken()!==17){if(n.getToken()===5){if(!w)f(4,[],[]);if(N(","),g(),n.getToken()===2&&m)break}else if(w)f(6,[],[]);if(!ge())f(4,[],[2,5]);w=!0}if(b(),n.getToken()!==2)f(7,[2],[]);else g();return!0}function un(){k(),g();let w=!0,j=!1;while(n.getToken()!==4&&n.getToken()!==17){if(n.getToken()===5){if(!j)f(4,[],[]);if(N(","),g(),n.getToken()===4&&m)break}else if(j)f(6,[],[]);if(w)i.push(0),w=!1;else i[i.length-1]++;if(!me())f(4,[],[4,5]);j=!0}if(S(),!w)i.pop();if(n.getToken()!==4)f(8,[4],[]);else g();return!0}function me(){switch(n.getToken()){case 3:return un();case 1:return an();case 10:return R(!0);default:return de()}}if(g(),n.getToken()===17){if(t.allowEmptyContent)return!0;return f(4,[],[]),!1}if(!me())return f(4,[],[]),!1;if(n.getToken()!==17)f(9,[],[]);return!0}function _n(e){switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":return"string";case"object":{if(!e)return"null";else if(Array.isArray(e))return"array";return"object"}default:return"null"}}function He(e,r,t,n){let i=r.slice(),c=Te(e,[]),s=void 0,l=void 0;while(i.length>0)if(l=i.pop(),s=se(c,i),s===void 0&&t!==void 0)if(typeof l==="string")t={[l]:t};else t=[t];else break;if(!s){if(t===void 0)throw Error("Can not delete in empty document");return P(e,{offset:c?c.offset:0,length:c?c.length:0,content:JSON.stringify(t)},n)}else if(s.type==="object"&&typeof l==="string"&&Array.isArray(s.children)){let u=se(s,[l]);if(u!==void 0)if(t===void 0){if(!u.parent)throw Error("Malformed AST");let a=s.children.indexOf(u.parent),p,h=u.parent.offset+u.parent.length;if(a>0){let b=s.children[a-1];p=b.offset+b.length}else if(p=s.offset+1,s.children.length>1)h=s.children[1].offset;return P(e,{offset:p,length:h-p,content:""},n)}else return P(e,{offset:u.offset,length:u.length,content:JSON.stringify(t)},n);else{if(t===void 0)return[];let a=`${JSON.stringify(l)}: ${JSON.stringify(t)}`,p=n.getInsertionIndex?n.getInsertionIndex(s.children.map((b)=>b.children[0].value)):s.children.length,h;if(p>0){let b=s.children[p-1];h={offset:b.offset+b.length,length:0,content:","+a}}else if(s.children.length===0)h={offset:s.offset+1,length:0,content:a};else h={offset:s.offset+1,length:0,content:a+","};return P(e,h,n)}}else if(s.type==="array"&&typeof l==="number"&&Array.isArray(s.children)){let u=l;if(u===-1){let a=`${JSON.stringify(t)}`,p;if(s.children.length===0)p={offset:s.offset+1,length:0,content:a};else{let h=s.children[s.children.length-1];p={offset:h.offset+h.length,length:0,content:","+a}}return P(e,p,n)}else if(t===void 0&&s.children.length>=0){let a=l,p=s.children[a],h;if(s.children.length===1)h={offset:s.offset+1,length:s.length-2,content:""};else if(s.children.length-1===a){let b=s.children[a-1],k=b.offset+b.length,S=s.offset+s.length;h={offset:k,length:S-2-k,content:""}}else h={offset:p.offset,length:s.children[a+1].offset-p.offset,content:""};return P(e,h,n)}else if(t!==void 0){let a,p=`${JSON.stringify(t)}`;if(!n.isArrayInsertion&&s.children.length>l){let h=s.children[l];a={offset:h.offset,length:h.length,content:p}}else if(s.children.length===0||l===0)a={offset:s.offset+1,length:0,content:s.children.length===0?p:p+","};else{let h=l>s.children.length?s.children.length:l,b=s.children[h-1];a={offset:b.offset+b.length,length:0,content:","+p}}return P(e,a,n)}else throw Error(`Can not ${t===void 0?"remove":n.isArrayInsertion?"insert":"modify"} Array index ${u} as length is not sufficient`)}else throw Error(`Can not add ${typeof l!=="number"?"index":"property"} to parent of type ${s.type}`)}function P(e,r,t){if(!t.formattingOptions)return[r];let n=ce(e,r),i=r.offset,o=r.offset+r.content.length;if(r.length===0||r.content.length===0){while(i>0&&!Y(n,i-1))i--;while(o<n.length&&!Y(n,o))o++}let c=be(n,{offset:i,length:o-i},{...t.formattingOptions,keepLines:!1});for(let l=c.length-1;l>=0;l--){let u=c[l];n=ce(n,u),i=Math.min(i,u.offset),o=Math.max(o,u.offset+u.length),o+=u.content.length-u.length}let s=e.length-(n.length-o)-i;return[{offset:i,length:s,content:n.substring(i,o)}]}function ce(e,r){return e.substring(0,r.offset)+r.content+e.substring(r.offset+r.length)}var qe;(function(e){e[e.None=0]="None",e[e.UnexpectedEndOfComment=1]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=2]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=3]="UnexpectedEndOfNumber",e[e.InvalidUnicode=4]="InvalidUnicode",e[e.InvalidEscapeCharacter=5]="InvalidEscapeCharacter",e[e.InvalidCharacter=6]="InvalidCharacter"})(qe||(qe={}));var Ye;(function(e){e[e.OpenBraceToken=1]="OpenBraceToken",e[e.CloseBraceToken=2]="CloseBraceToken",e[e.OpenBracketToken=3]="OpenBracketToken",e[e.CloseBracketToken=4]="CloseBracketToken",e[e.CommaToken=5]="CommaToken",e[e.ColonToken=6]="ColonToken",e[e.NullKeyword=7]="NullKeyword",e[e.TrueKeyword=8]="TrueKeyword",e[e.FalseKeyword=9]="FalseKeyword",e[e.StringLiteral=10]="StringLiteral",e[e.NumericLiteral=11]="NumericLiteral",e[e.LineCommentTrivia=12]="LineCommentTrivia",e[e.BlockCommentTrivia=13]="BlockCommentTrivia",e[e.LineBreakTrivia=14]="LineBreakTrivia",e[e.Trivia=15]="Trivia",e[e.Unknown=16]="Unknown",e[e.EOF=17]="EOF"})(Ye||(Ye={}));var Xe=ze;var Qe;(function(e){e[e.InvalidSymbol=1]="InvalidSymbol",e[e.InvalidNumberFormat=2]="InvalidNumberFormat",e[e.PropertyNameExpected=3]="PropertyNameExpected",e[e.ValueExpected=4]="ValueExpected",e[e.ColonExpected=5]="ColonExpected",e[e.CommaExpected=6]="CommaExpected",e[e.CloseBraceExpected=7]="CloseBraceExpected",e[e.CloseBracketExpected=8]="CloseBracketExpected",e[e.EndOfFileExpected=9]="EndOfFileExpected",e[e.InvalidCommentToken=10]="InvalidCommentToken",e[e.UnexpectedEndOfComment=11]="UnexpectedEndOfComment",e[e.UnexpectedEndOfString=12]="UnexpectedEndOfString",e[e.UnexpectedEndOfNumber=13]="UnexpectedEndOfNumber",e[e.InvalidUnicode=14]="InvalidUnicode",e[e.InvalidEscapeCharacter=15]="InvalidEscapeCharacter",e[e.InvalidCharacter=16]="InvalidCharacter"})(Qe||(Qe={}));function Ze(e,r,t,n){return He(e,r,t,n)}function Ke(e,r){let t=r.slice(0).sort((i,o)=>{let c=i.offset-o.offset;if(c===0)return i.length-o.length;return c}),n=e.length;for(let i=t.length-1;i>=0;i--){let o=t[i];if(o.offset+o.length<=n)e=ce(e,o);else throw Error("Overlapping edit");n=o.offset}return e}var X="dockito.jsonc";function F(e=process.cwd()){for(let r=rn(e);;r=Ce(r)){let t=tn(r,X);if(Dn(t)){let n=[],i=Xe(en(t,"utf8"),n,{allowTrailingComma:!0});if(n.length||!i||typeof i!=="object")throw Error(`${t} isn't valid JSONC.`);return{file:t,dir:r,config:i}}if(Ce(r)===r)return null}}var le=(e,r)=>Rn(e.dir,rn(r)).split(Bn).join("/");function on(e,r,t){let n=en(e.file,"utf8"),i=Ze(n,["docs",le(e,r)],t,{formattingOptions:{insertSpaces:!0,tabSize:2}});nn(e.file,Ke(n,i))}function sn(e,r){let t=["{"," // Where `dockito publish` puts new docs. No secrets in this file.",` "workspace": ${JSON.stringify(r.workspace)},`,r.project?` "project": ${JSON.stringify(r.project)},`:' // "project": "my-project",',r.folder?` "folder": ${JSON.stringify(r.folder)},`:' // "folder": "Folder/Subfolder",'," // Local files and the docs they publish to. Filled in by `dockito publish`.",' "docs": {}',"}",""],n=tn(e,X);return nn(n,t.join(`
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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",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}`,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
|
+
`),$=(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"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 Kt,readFileSync as Ht,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 Kt(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)&&Ht(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=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 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)&&H(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=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=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),K=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=K.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 K){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 K)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=K.filter(r=>r.action==="new"||r.action==="update"),Ze=K.filter(r=>r.action==="ambiguous"),Ae=K.filter(r=>r.action==="same").length,At=new Set(K.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 Ke=`Dockito hosts Markdown and HTML docs, and live sheets, at a short link,
|
|
18
|
+
with History.
|
|
19
|
+
|
|
20
|
+
workspace a person's or a team's space; its slug is in every link
|
|
21
|
+
project docs grouped in a workspace, with folders up to 2 deep
|
|
22
|
+
Docs a workspace's docs outside any project
|
|
23
|
+
doc one file. Publishing a file with the same name to the same
|
|
24
|
+
place updates that doc and keeps its link
|
|
25
|
+
History every publish is an entry; -m keeps one as a saved point
|
|
26
|
+
access a login has View, Publish or Everything per workspace
|
|
27
|
+
|
|
28
|
+
For AI agents:
|
|
29
|
+
- The CLI remembers nothing between runs. Run \`dockito tree\` to see
|
|
30
|
+
where docs can go and \`dockito ls <place>\` for what's there.
|
|
31
|
+
- Just a link to share: publish with no --to; it goes to the workspace's
|
|
32
|
+
Docs. When it belongs in a project, ask the user where, never guess,
|
|
33
|
+
and pass it with --to every time.
|
|
34
|
+
- Docs are private: only people in the workspace can open them. Add
|
|
35
|
+
--public (anyone with the link, no sign-in) only when the user clearly
|
|
36
|
+
asked for a public link. If they didn't say, keep it private.
|
|
37
|
+
- A folder of docs: \`dockito sync <folder> --to <project>\`. It shows a
|
|
38
|
+
plan; show it to the user before running with --yes.
|
|
39
|
+
- \`dockito <command> --help\` explains each command. Use --json; on
|
|
40
|
+
failure, read "error" and the commands it suggests.
|
|
41
|
+
|
|
42
|
+
Sheets: people and agents edit the same cells, live. Publishing a .csv
|
|
43
|
+
makes a new one.
|
|
44
|
+
- \`dockito get <sheet>\` gives its outline (sheets, columns, what's in
|
|
45
|
+
them) and its version, v N. \`read\` gives rows, \`check\` finds broken
|
|
46
|
+
formulas, \`changes --since N\` lists what changed.
|
|
47
|
+
- \`dockito apply <sheet> changes.json --base N\`, with N the version you
|
|
48
|
+
last read. Addresses and rows mean what they meant then. If someone
|
|
49
|
+
changed the same cells meanwhile, nothing applies and it says who
|
|
50
|
+
(exit 1): read again, then apply on the new version.
|
|
51
|
+
- Change rows by what's in them, not by row number: \`update\` with a
|
|
52
|
+
filter ("ID = T-104"), and "max" when you mean several rows.`,ve={sync:{use:"sync <folder> --to <project>",about:"Mirror a folder into a project",page:`Mirrors a local folder into a project: subfolders become folders, and each
|
|
53
|
+
file updates the doc with the same file name in the same folder, or becomes
|
|
54
|
+
a new doc there. Only files whose content changed are sent. Nothing is moved
|
|
55
|
+
or deleted in Dockito. The CLI remembers nothing: pass --to every time.
|
|
56
|
+
|
|
57
|
+
Docs made by sync are private.
|
|
58
|
+
|
|
59
|
+
--to <project[/folder]> Where it goes (required; \`dockito tree\` lists places)
|
|
60
|
+
-w, --workspace <slug> Another workspace
|
|
61
|
+
--create Make the project if it doesn't exist
|
|
62
|
+
--fold Put folders deeper than 2 into the deepest allowed;
|
|
63
|
+
clashing names get the left-out folders in front
|
|
64
|
+
--dry-run Show the plan, change nothing
|
|
65
|
+
--yes Apply a plan that makes a project or folders
|
|
66
|
+
|
|
67
|
+
It always shows a plan. Making a project or folders waits for --yes: show
|
|
68
|
+
the plan to the user first. Sending docs into folders that exist just runs.
|
|
69
|
+
|
|
70
|
+
dockito sync docs --to handbook
|
|
71
|
+
dockito sync docs --to handbook --create --fold --dry-run`},publish:{use:"publish <file...>",about:"Publish files; the same name in that place updates its doc",page:`Publishes Markdown or HTML files. A file with the same name in that place
|
|
72
|
+
updates its doc (same link, a new History entry); otherwise it's a new doc.
|
|
73
|
+
Unchanged files are skipped. A .csv becomes a new live sheet, typed as Excel
|
|
74
|
+
opens it; after that it changes with \`dockito apply\`, never by a file.
|
|
75
|
+
|
|
76
|
+
Docs are private: only people in the workspace can open them.
|
|
77
|
+
Add --public only when the user clearly asked for a public link (anyone
|
|
78
|
+
with the link, no sign-in). If they didn't say, keep it private. --public
|
|
79
|
+
works only for a new doc; an existing private doc can be made public only
|
|
80
|
+
from Share in the app.
|
|
81
|
+
|
|
82
|
+
--to <project[/folder]> Where (default: the workspace's Docs)
|
|
83
|
+
-w, --workspace <slug> Another workspace (default: your personal one)
|
|
84
|
+
-m, --message <note> Keep this entry as a saved point, with a note
|
|
85
|
+
--public Make the new doc public (only when the user asked)
|
|
86
|
+
--title <title> A new doc's title (default: the file name); an
|
|
87
|
+
existing doc keeps its own
|
|
88
|
+
--doc <doc> Update this doc (link or code) instead
|
|
89
|
+
--new A new doc even if the name is already there
|
|
90
|
+
|
|
91
|
+
dockito publish plan.md private, in your Docs
|
|
92
|
+
dockito publish plan.md --to handbook/specs private, in a folder
|
|
93
|
+
dockito publish plan.md --public the user asked for a public link
|
|
94
|
+
dockito publish q3.html --title "Q3 plan" named Q3 plan, not q3`},share:{use:"share <doc>",about:"Who can open a doc; --private takes a public link back",page:`Says whether a doc is private (only people in its workspace) or public
|
|
95
|
+
(anyone with the link). --private makes it private again at once.
|
|
96
|
+
|
|
97
|
+
An existing doc is made public only from Share in the app; a new doc can be
|
|
98
|
+
published public with \`dockito publish <file> --public\` when the user asks.
|
|
99
|
+
|
|
100
|
+
dockito share <link or code>
|
|
101
|
+
dockito share <link or code> --private`},tree:{use:"tree",about:"Every workspace, project and folder you can use",page:"Lists every workspace this login can reach, its projects and their folders,\nwith doc counts, each written as the --to it takes. Docs themselves: `dockito ls`."},ls:{use:"ls [project/folder]",about:"What's in a place",page:`Lists the docs (with their codes), projects and folders in a place: your
|
|
102
|
+
workspace's top level, a project, or a folder. The place can also be given
|
|
103
|
+
as --to, like publish. -w for another workspace.
|
|
104
|
+
|
|
105
|
+
dockito ls handbook/specs
|
|
106
|
+
dockito ls --to handbook/specs`},mkdir:{use:"mkdir <project/folder>",about:"Make a project or folders",page:`Makes whatever is missing: the project, then up to 2 levels of folders.
|
|
107
|
+
|
|
108
|
+
dockito mkdir handbook/specs/drafts`},open:{use:"open <doc>",about:"Open in the browser",page:"Opens a doc (link or code) in the browser."},get:{use:"get <doc> [-o file]",about:"Download a doc, or read it in parts",page:`Prints a doc's file, or writes it with -o. Big docs can be read in parts.
|
|
109
|
+
A sheet prints its outline: each sheet's columns, what's in them, and the
|
|
110
|
+
version to apply changes at; \`dockito read\` gives its rows.
|
|
111
|
+
|
|
112
|
+
--outline Only the headings, each with the line it starts on
|
|
113
|
+
--lines <range> Only these lines: 120-260, 120- (to the end) or 120
|
|
114
|
+
--text HTML as readable text: no tags, scripts or styles,
|
|
115
|
+
often a few times shorter (Markdown is already text)
|
|
116
|
+
--entry <n> A History entry (numbers from \`dockito history\`)
|
|
117
|
+
-o, --output <file> Write it to a file
|
|
118
|
+
|
|
119
|
+
dockito get <doc> --outline
|
|
120
|
+
dockito get <doc> --lines 120-260 --text`},read:{use:"read <doc> [range]",about:"A sheet's rows, a range or totals",page:`Prints rows of a sheet as a text table: row numbers, values as shown, and
|
|
121
|
+
the version (v N) to apply changes at. A range, or a sheet's rows under its
|
|
122
|
+
header row, filtered and sorted; with --group or --totals, totals instead.
|
|
123
|
+
200 rows unless --limit says, at most 1,000; page on with --from.
|
|
124
|
+
|
|
125
|
+
[range] Tasks!A1:G40, Tasks!B:B, Tasks!3:5, a defined name,
|
|
126
|
+
or E17 when there's one sheet
|
|
127
|
+
--sheet <name> A sheet or table's rows under its header row
|
|
128
|
+
--where <filter> "Area = Kitchen and Status <> Done or Hours > 4":
|
|
129
|
+
= <> < > <= >= contains, is empty, is not empty;
|
|
130
|
+
dates as yyyy-mm-dd
|
|
131
|
+
--select <cols> Columns to show, by header or letter: ID,Status
|
|
132
|
+
--order-by <cols> "Due desc, ID"; empty cells go last
|
|
133
|
+
--group <cols> Group the rows by these columns
|
|
134
|
+
--totals <list> count, sum Hours, avg Hours, min Due, max Due
|
|
135
|
+
--from <n>, --limit <n> Which rows: from 1; 200 by default, 1,000 at most
|
|
136
|
+
--formulas Formulas with their results: =F2*50 \u2192 50
|
|
137
|
+
--styles Formatting in words, merges, sizes, frozen panes
|
|
138
|
+
--csv As CSV instead of a table
|
|
139
|
+
|
|
140
|
+
dockito read <doc> --where "Status = Blocked" --select ID,Owner,Due
|
|
141
|
+
dockito read <doc> --group Owner --totals "count,sum Hours"
|
|
142
|
+
dockito read <doc> "Tasks!A1:G20" --formulas`},apply:{use:"apply <doc> <changes.json>",about:"Change a sheet at the version you read",page:`Changes a sheet, read at --base: the version (v N) your last get, read or
|
|
143
|
+
changes showed. Addresses, rows and headers mean what they meant then,
|
|
144
|
+
wherever they moved since. Every change applies, or none: a cell someone
|
|
145
|
+
changed since is refused, naming who (exit 1). Read again and apply on the
|
|
146
|
+
new version. Prints each cell old \u2192 new and the new version.
|
|
147
|
+
|
|
148
|
+
The file (or - for stdin) is a list of changes, or {"base": N, "ops": [...],
|
|
149
|
+
"expect": {...}}. "sheet" can be left out when there's one. Each change:
|
|
150
|
+
{"set": "Tasks!E17", "value": "Done"}
|
|
151
|
+
{"set": "Tasks!B2:C3", "values": [["Tile grout", "Kitchen"], ["Vent", "Bath"]]}
|
|
152
|
+
{"update": {"sheet": "Tasks", "where": "ID = T-104", "set": {"Status": "Done"}}}
|
|
153
|
+
{"update": {"sheet": "Tasks", "where": "Area = Kitchen and Status = Open",
|
|
154
|
+
"set": {"Status": "Done"}, "max": 50}} max is 1 unless set; more
|
|
155
|
+
matches than max are refused
|
|
156
|
+
{"append": {"sheet": "Tasks", "rows": [{"ID": "T-214", "Status": "Open"}]}}
|
|
157
|
+
a column's formula in the rows above carries
|
|
158
|
+
into new rows, as in Excel
|
|
159
|
+
{"insertRows": {"sheet": "Tasks", "after": 40, "count": 2}}, deleteRows
|
|
160
|
+
{"rows": "41:42"} or a list, "9, 16, 41:42"; moveRows {"rows": "5:6",
|
|
161
|
+
"after": 10}; insertCols, deleteCols, moveCols the same with letters
|
|
162
|
+
{"format": {"range": "Tasks!1:1", "bold": true, "fill": "#fff2cc"}}
|
|
163
|
+
cells, rows "1:1" or columns "F:F"; null clears a field:
|
|
164
|
+
bold italic underline strike wrap true or false
|
|
165
|
+
size points, 1 to 409
|
|
166
|
+
font a font's name, as "Inter"
|
|
167
|
+
color fill "#rrggbb"
|
|
168
|
+
hAlign left, center, right or justify
|
|
169
|
+
vAlign top, middle or bottom
|
|
170
|
+
indent 0 to 250
|
|
171
|
+
borderTop borderRight borderBottom borderLeft
|
|
172
|
+
"thin #d0d5dd", or {"line": "thin",
|
|
173
|
+
"color": "#d0d5dd"}; lines are thin,
|
|
174
|
+
medium, thick, dashed, dotted or double
|
|
175
|
+
numFmt Excel's, as "$#,##0.00", "d mmm yyyy"
|
|
176
|
+
{"resize": {"range": "Tasks!B:C", "size": 120}}, merge, unmerge
|
|
177
|
+
addSheet, renameSheet, moveSheet, deleteSheet, sheetProps (frozenRows,
|
|
178
|
+
frozenCols, hidden, tabColor), setName, deleteName, setTable, deleteTable
|
|
179
|
+
{"addTable": {"range": "Tasks!A1:D40", "name": "Tasks"}}: a header row
|
|
180
|
+
and banded rows in one change; a cell's own fill shows over them
|
|
181
|
+
Values: a number, true, false, null (empty), a string ("=\u2026" is a formula),
|
|
182
|
+
{"text": "=as typed"} or {"date": "2026-10-19"}.
|
|
183
|
+
|
|
184
|
+
--base <n> The version you read
|
|
185
|
+
-m, --message <note> Why, shown with the change in the sheet
|
|
186
|
+
--dry-run Show what would happen, save nothing
|
|
187
|
+
--undo <n> Take back your own change that made version n
|
|
188
|
+
--key <key> A retry key (default: made from the request, so
|
|
189
|
+
the same request on the same version applies once)
|
|
190
|
+
|
|
191
|
+
dockito apply <doc> changes.json --base 412 -m "close finished tasks"
|
|
192
|
+
echo '[{"set": "E17", "value": "Done"}]' | dockito apply <doc> - --base 412`},check:{use:"check <doc>",about:"Problems in a sheet",page:`Finds problems in a sheet, by rules: formula errors, a formula that breaks
|
|
193
|
+
its column's pattern, a value typed over a column's formulas, numbers or
|
|
194
|
+
dates typed as text, totals that stop before their column does.
|
|
195
|
+
|
|
196
|
+
--sheet <name> One sheet (default: every sheet)`},changes:{use:"changes <doc> --since <v>",about:"What changed in a sheet",page:`Lists what changed after a version, or since a time in UTC, oldest first:
|
|
197
|
+
who, when, through what, with each cell old \u2192 new, named where it is now.
|
|
198
|
+
Ends at the current version, to apply changes at.
|
|
199
|
+
|
|
200
|
+
--since <v|time> 412, 2026-10-13 or "2026-10-13 09:00"
|
|
201
|
+
--max <n> At most this many changes (default 50, at most 200)`},history:{use:"history <doc>",about:"History entries",page:`Lists a doc's History: entry, when, who, from where (upload, edit, cli,
|
|
202
|
+
mcp), saved points with their notes, and the git commit.`},restore:{use:"restore <doc> <entry>",about:"Make a History entry current again",page:"Makes an older entry the current one, as a new entry. Nothing is lost."},save:{use:"save <doc> [entry] -m <note>",about:"Keep an entry as a saved point",page:`Keeps an entry (default: the current one) as a saved point, so History
|
|
203
|
+
never drops it, with an optional note.`},login:{use:"login [--update]",about:"Sign in through the browser",page:`Opens the browser, where you check a code and choose this login's access
|
|
204
|
+
in each workspace: View, Publish or Everything. --update changes that access.
|
|
205
|
+
--no-browser prints the link instead.`},logout:{use:"logout",about:"Sign out this machine",page:"Signs this machine out and removes its token."},whoami:{use:"whoami",about:"Who you are and what this login can reach",page:"Your name, the login's expiry, and its access in each workspace."},agent:{use:"agent",about:"What an AI agent needs to know",page:"Prints what Dockito is and how an AI agent should use this CLI."}};var qe="0.0.1-beta.10",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(`
|
|
206
|
+
${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(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:$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,...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 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=H(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()}
|
|
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);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 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 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}
|
|
208
|
+
|
|
209
|
+
${Ke}
|
|
24
210
|
|
|
25
211
|
Usage: dockito <command>
|
|
26
212
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
--force Publish even if someone published since
|
|
38
|
-
ls [project/folder] Docs and projects
|
|
39
|
-
open <doc> Open in the browser
|
|
40
|
-
get <doc> [-o file] Download (--entry <n> for a History entry)
|
|
41
|
-
history <doc> History entries
|
|
42
|
-
restore <doc> <entry> Make a History entry current again
|
|
43
|
-
save <doc> [entry] -m ... Keep an entry as a saved point
|
|
44
|
-
init [--to project/folder] Write a dockito.jsonc here
|
|
45
|
-
|
|
46
|
-
<doc> is a link, a code, or a file published before.
|
|
47
|
-
-w, --workspace <slug> Use another workspace
|
|
48
|
-
--json Machine-readable output (whoami, publish, ls, history)
|
|
49
|
-
|
|
50
|
-
Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`;async function ut(){let{values:e,positionals:r}=Wn({allowPositionals:!0,options:{message:{type:"string",short:"m"},to:{type:"string"},doc:{type:"string"},new:{type:"boolean"},force:{type:"boolean"},workspace:{type:"string",short:"w"},output:{type:"string",short:"o"},entry:{type:"string"},json:{type:"boolean"},update:{type:"boolean"},"no-browser":{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[t,...n]=r;if(e.version)return O(Ne);if(e.help||!t||t==="help")return O(at);switch(t){case"login":return Zn(e);case"logout":return Kn();case"whoami":return Cn(e);case"switch":return et(n[0]);case"init":return nt(e);case"publish":return tt(n,e);case"ls":return rt(n[0],e);case"open":return ot(n[0]);case"get":return it(n[0],e);case"history":return st(n[0],e);case"restore":return ct(n[0],n[1]);case"save":return lt(n[0],n[1],e);default:T(`Unknown command "${t}". Run \`dockito --help\`.`)}}ut().then(()=>process.exit(0),(e)=>{if(e instanceof Se)D(ae(e.message));else if(e instanceof x)D(ae(zn(e,L)));else if(e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message)))D(ae(`${e.message}. Run \`dockito --help\`.`));else D(ae(String(e?.stack??e)));process.exit(1)});
|
|
213
|
+
${Object.values(ve).map(e=>` ${e.use.padEnd(30)}${e.about}`).join(`
|
|
214
|
+
`)}
|
|
215
|
+
|
|
216
|
+
<doc> is a link or a code. \`dockito <command> --help\` for more.
|
|
217
|
+
-w, --workspace <slug> Another workspace (default: your personal one)
|
|
218
|
+
--json Machine-readable output
|
|
219
|
+
|
|
220
|
+
Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`,vt=e=>{let t=ve[e];return t?`dockito ${t.use}
|
|
221
|
+
|
|
222
|
+
${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(Ke);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,15 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dockito",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.10",
|
|
4
4
|
"description": "Dockito CLI: publish the docs your AI writes as links you can share.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://dockito-site.dockito.workers.dev",
|
|
7
|
-
"keywords": [
|
|
7
|
+
"keywords": [
|
|
8
|
+
"dockito",
|
|
9
|
+
"docs",
|
|
10
|
+
"markdown",
|
|
11
|
+
"publish",
|
|
12
|
+
"cli",
|
|
13
|
+
"ai"
|
|
14
|
+
],
|
|
8
15
|
"type": "module",
|
|
9
16
|
"bin": {
|
|
10
17
|
"dockito": "dockito.js"
|
|
11
18
|
},
|
|
12
|
-
"files": [
|
|
19
|
+
"files": [
|
|
20
|
+
"dockito.js",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
13
23
|
"engines": {
|
|
14
24
|
"node": ">=18.17"
|
|
15
25
|
}
|