dockito 0.0.1-beta.3 → 0.0.1-beta.5
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 +2 -1
- package/dockito.js +88 -48
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -13,11 +13,12 @@ npx dockito@beta publish plan.md -m "Sent to the client"
|
|
|
13
13
|
| `login` | Sign in through the browser and choose what this login may do in each workspace |
|
|
14
14
|
| `sync <folder> --to <project>` | Mirror a folder into a project: subfolders become folders, only changed files are sent, a plan first |
|
|
15
15
|
| `publish <file...>` | Publish a file; one with the same name in that place updates its doc |
|
|
16
|
+
| `share <doc>` | Who can open it. Docs are private; `publish --public` makes a new doc public, `share --private` takes it back |
|
|
16
17
|
| `tree`, `mkdir` | Every workspace, project and folder you can use; make new ones |
|
|
17
18
|
| `ls`, `open`, `get` | List, open and download docs |
|
|
18
19
|
| `history`, `restore`, `save` | A doc's History |
|
|
19
20
|
| `whoami`, `logout` | Your login and workspaces |
|
|
20
21
|
|
|
21
|
-
The CLI keeps nothing but your login: where docs go is passed with `--to` each time. Run `dockito --help` for everything, and `dockito agent` for what an AI agent needs to know. `--json` gives machine-readable output for agents, and `DOCKITO_TOKEN` uses a token from Account → Tokens instead of a login.
|
|
22
|
+
The CLI keeps nothing but your login: where docs go is passed with `--to` each time. Run `dockito --help` for everything, `dockito <command> --help` for one command, and `dockito agent` for what an AI agent needs to know. `--json` gives machine-readable output for agents, and `DOCKITO_TOKEN` uses a token from Account → Tokens instead of a login.
|
|
22
23
|
|
|
23
24
|
This is a beta: commands may still change.
|
package/dockito.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
3
|
-
`)
|
|
4
|
-
`),
|
|
5
|
-
`)};
|
|
6
|
-
\x1B[?25l`),
|
|
7
|
-
`),
|
|
8
|
-
`),o(
|
|
9
|
-
`);return!!t&&t.status===0&&!/error/i.test(t.stderr??"")}if(process.platform==="linux"){let t=
|
|
10
|
-
`),{choices:o})}async function
|
|
11
|
-
To make it, run again with --create.`,{workspace:
|
|
12
|
-
Ask the user, then run again with --fold.`,{files:
|
|
13
|
-
${
|
|
14
|
-
`)}`);let
|
|
15
|
-
${
|
|
16
|
-
`)}`);let
|
|
17
|
-
Nothing changed yet. Show the user this plan, then run: ${i}`,{next:i})}if(!await
|
|
18
|
-
${S}`))}async function cn(e,n){e.length||w("Name a file: `dockito publish notes.md --to <project/folder>`"),n.doc&&e.length>1&&w("--doc takes one file.");for(let f of e)(!Jt(f)||!Gt(f).isFile())&&w(`${f} isn't a file.`);let t=_(),o=[];if(n.doc){let f=await t.doc(q(n.doc)),h=new Uint8Array(at(e[0])),g=await t.update(f.doc.id,Ce(e[0]),h,{note:n.message,commit:lt(e[0])});return n.json?u(JSON.stringify([{file:e[0],...g,link:t.linkOf(g.slug)}],null,2)):u(g.unchanged?`${p("=")} ${e[0]} ${p("unchanged")} ${t.linkOf(g.slug)}`:`${k("\u2713")} ${e[0]} \u2192 ${t.linkOf(g.slug)}`)}let s=await t.tree(),r=he(s,n.workspace);L(r)||w(`This login has View access in ${r.slug}. Run \`dockito login --update\`.`);let c=Ae(r,n.to),a=await t.files(r.id,c.project);for(let f of e){let h=new Uint8Array(at(f)),g=Ce(f),d=n.new?[]:we(a,c.folder,g);d.length>1&&w(`${d.length} docs in ${c.label} are named ${g}: ${d.map($=>t.linkOf($.slug)).join(", ")}. Pick one with --doc, or publish a new one with --new.`);let y=d[0];if(y&&y.sha256===nn(h)&&!n.message){o.push({file:f,id:y.id,slug:y.slug,version:y.version,unchanged:!0,link:t.linkOf(y.slug)}),n.json||u(`${p("=")} ${f} ${p("unchanged")} ${t.linkOf(y.slug)}`);continue}let x=y?await t.update(y.id,g,h,{note:n.message,commit:lt(f)}):await t.create(r.id,g,h,{project:c.project,folder:c.folder});!y&&n.message&&await t.save(x.id,x.version,n.message),o.push({file:f,...x,link:t.linkOf(x.slug),new:!y}),n.json||u(`${k("\u2713")} ${f} \u2192 ${t.linkOf(x.slug)} ${p(y?x.unchanged?"unchanged":"updated":`new in ${c.label}`)}${n.message?p(` \xB7 saved: ${n.message}`):""}`)}n.json?u(JSON.stringify(o,null,2)):!n.to&&o.some(f=>f.new)&&b(p("In the workspace's Docs. To put docs in a project: --to <project/folder>; `dockito tree` lists them."))}async function an(e){let n=await _().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?U[o.level]:o.role}`)}`);let s=[[p("Docs"),"",t(o.docs)]];for(let r of o.projects){s.push([r.slug,r.name,t(r.docs)]);let c=(a,f)=>{for(let h of r.folders.filter(g=>g.parentId===a))s.push([`${f}/${h.name}`,"",t(h.docs)]),c(h.id,`${f}/${h.name}`)};c(null,r.slug)}Q(s.map(r=>[" ",...r])),u()}b(p("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function ln(e,n){let t=_(),o=await t.tree(),s=he(o,n.workspace),r=Ae(s,e),c=await t.docs(s.id,{project:r.project,folder:r.folder}),a=r.project?s.projects.find(d=>d.id===r.project):null,f=e?[]:s.projects,h=a?a.folders.filter(d=>d.parentId===r.folder):[];if(n.json)return u(JSON.stringify({workspace:s.slug,place:e??null,projects:f,folders:h,docs:c},null,2));let g=[];for(let d of f)g.push([T(`${d.slug}/`),d.name,p(`${d.docs} doc${d.docs===1?"":"s"}`),""]);for(let d of h)g.push([T(`${d.name}/`),"",p(`${d.docs} doc${d.docs===1?"":"s"}`),""]);for(let d of c)g.push([d.slug,d.title+p(d.kind==="html"?" .html":" .md"),p(fe(d.updatedAt)),p(d.updatedByName??"")]);if(!g.length)return b(p(`Nothing in ${r.label} yet.`));Q(g)}async function dn(e,n){let[t,...o]=(e??"").split("/").filter(Boolean);t||w("`dockito mkdir <project>[/folder[/subfolder]]`"),o.length>2&&w("Folders go at most 2 deep inside a project.");let s=_(),r=he(await s.tree(),n.workspace),c=G(r,t),a=c?.id??(await s.createProject(r.id,t)).id,f=!c;c||u(`${k("\u2713")} Project ${t}`);let h=ne(c),g=null;for(let d=0;d<o.length;d++){let y=h.idOf(o.slice(0,d+1));if(y){g=y;continue}g=(await s.createFolder(a,o[d],g)).id,h.add(o.slice(0,d+1),g),f=!0,u(`${k("\u2713")} Folder ${o.slice(0,d+1).join("/")}`)}f||b(p("Already there."))}async function un(e){e||w("Name a doc: `dockito open <link or code>`");let n=`${S}/${q(e)}`;dt(n)||u(n)}async function pn(e,n){e||w("Name a doc: `dockito get <link or code> [-o file]`");let t=_(),o=await t.doc(q(e)),s=n.entry?Number(n.entry):null;n.entry&&(!Number.isInteger(s)||s<1)&&w("--entry takes an entry number from `dockito history`.");let{bytes:r}=await t.content(o.doc.id,s);if(!n.output)return void process.stdout.write(r);zt(n.output,r),b(`${k("\u2713")} ${n.output} ${p(`\u2190 ${t.linkOf(o.doc.slug)}${s?` entry ${s}`:""}`)}`)}async function fn(e,n){e||w("Name a doc: `dockito history <link or code>`");let t=await _().doc(q(e));if(n.json)return u(JSON.stringify(t.versions,null,2));u(`${T(t.doc.title)} ${p(`${S}/${t.doc.slug}`)}`),Q(t.versions.map((o,s)=>[String(o.number),s===0?k("current"):p(fe(o.createdAt)),s===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 gn(e,n){let t=Number(n);(!e||!Number.isInteger(t)||t<1)&&w("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let o=_(),s=await o.doc(q(e)),r=await o.restore(s.doc.id,t,s.versions[0]?.number??null);u(`${k("\u2713")} Restored entry ${t} as entry ${r.version} ${p(o.linkOf(r.slug))}`)}async function mn(e,n,t){e||w('`dockito save <doc> [entry] -m "note"`');let o=_(),s=await o.doc(q(e)),r=n?Number(n):s.versions[0]?.number;(!Number.isInteger(r)||r<1)&&w("Entries are in `dockito history`."),await o.save(s.doc.id,r,t.message??null),u(`${k("\u2713")} Saved entry ${r}${t.message?p(` \xB7 ${t.message}`):""}`)}var ut=`Dockito hosts Markdown and HTML docs at a short link, with History.
|
|
2
|
+
import{spawn as Yt,spawnSync as pt}from"node:child_process";import{createHash as Vt}from"node:crypto";import{existsSync as Xt,readFileSync as ft,statSync as Qt,writeFileSync as Zt}from"node:fs";import{hostname as en}from"node:os";import{basename as Re,dirname as tn,isAbsolute as nn,resolve as mt}from"node:path";import{parseArgs as on}from"node:util";var _=e=>e.level===null||e.level==="publish"||e.level==="everything",R=(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 jt=e=>e.fileName??`${e.title}.${e.kind}`,ee=(e,n,t)=>e.filter(o=>(o.folderId??null)===n&&jt(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 fe(e){let n=e.url.replace(/\/+$/,"");async function t(s,a,c={}){let f=new URL(`${n}/api${a}`);for(let[j,b]of Object.entries(c.query??{}))b!=null&&b!==""&&f.searchParams.set(j,String(b));let w={},g=c.token===void 0?e.token:c.token;g&&(w.authorization=`Bearer ${g}`),e.userAgent&&(w["user-agent"]=e.userAgent);let u;c.json!==void 0?(w["content-type"]="application/json",u=JSON.stringify(c.json)):c.body&&(w["content-type"]="application/octet-stream",u=c.body);let k;try{k=e.fetch?await e.fetch(new Request(f,{method:s,headers:w,body:u})):await fetch(f,{method:s,headers:w,body:u})}catch{throw new x("offline",0)}return k}async function o(s){let a=await s.json().catch(()=>({}));if(!s.ok){let{error:c,...f}=a;throw new x(typeof c=="string"?c:`http_${s.status}`,s.status,f)}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)=>{let c=await t("GET",`/docs/${r(s)}/content`,{query:{v:a}});return c.ok||await o(c),{bytes:new Uint8Array(await c.arrayBuffer()),entry:Number(c.headers.get("x-doc-version"))}},create:async(s,a,c,f={})=>o(await t("POST",`/workspaces/${r(s)}/docs`,{query:{name:a,title:f.title,project:f.project,folder:f.folder,public:f.public?1:null,source:e.source},body:c})),update:async(s,a,c,f={})=>o(await t("POST",`/docs/${r(s)}/versions`,{query:{name:a,note:f.note,commit:f.commit,base:f.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 xt}from"node:readline";import{createInterface as Tt}from"node:readline/promises";var vt=!process.env.NO_COLOR&&process.stdout.isTTY,q=e=>n=>vt?`\x1B[${e}m${n}\x1B[0m`:n,p=q("2"),T=q("1"),y=q("32"),I=q("31"),E=q("33"),Pe=q("36"),d=(e="")=>process.stdout.write(e+`
|
|
3
|
+
`),$=(e="")=>process.stderr.write(e+`
|
|
4
|
+
`),B=class extends Error{},h=e=>{throw new B(e)},J={view:"View",publish:"Publish",everything:"Everything"};function ge(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)d(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`Dockito said: ${e.code}`}}var Se=()=>!!process.stdin.isTTY&&!!process.stdout.isTTY&&!process.env.CI,G=e=>process.stdout.write(e);function et(e,n,t=0){return new Promise((o,r)=>{let s=t,a=w=>{w||G(`\x1B[${n.length}A`);for(let[g,u]of n.entries())G(`\x1B[2K${g===s?Pe("\u276F "+u.label):" "+u.label}${u.hint?p(" "+u.hint):""}
|
|
5
|
+
`)};G(`${y("?")} ${T(e)} ${p("(\u2191\u2193, Enter)")}
|
|
6
|
+
\x1B[?25l`),a(!0),xt(process.stdin),process.stdin.setRawMode(!0),process.stdin.resume();let c=()=>{process.stdin.off("keypress",f),process.stdin.setRawMode(!1),process.stdin.pause(),G("\x1B[?25h")},f=(w,g)=>{if(g.ctrl&&g.name==="c")return c(),G(`
|
|
7
|
+
`),r(new oe);if(g.name==="up"||g.name==="k")s=(s-1+n.length)%n.length;else if(g.name==="down"||g.name==="j")s=(s+1)%n.length;else return g.name==="return"?(c(),G(`\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",f)})}async function he(e,n=""){let t=Tt({input:process.stdin,output:process.stdout});try{return(await t.question(`${y("?")} ${T(e)}${n?p(` (${n})`):""} `)).trim()||n}finally{t.close()}}async function Ae(e,n=!0){let t=(await he(`${e} ${p(n?"[Y/n]":"[y/N]")}`)).toLowerCase();return t?t.startsWith("y"):n}var oe=class extends Error{};import{homedir as Ot}from"node:os";import{join as Ne}from"node:path";var Dt="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?Ne(process.env.APPDATA,"dockito"):Ne(process.env.XDG_CONFIG_HOME||Ne(Ot(),".config"),"dockito")}var nt=()=>(process.env.DOCKITO_URL||Dt).replace(/\/+$/,""),ot=e=>new URL(e).host;import{spawnSync as Pt}from"node:child_process";import{chmodSync as St,existsSync as _e,mkdirSync as At,readFileSync as Nt,rmSync as rt,writeFileSync as _t}from"node:fs";import{dirname as It,join as Et}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 re=e=>Et(tt(),"tokens",`${e.replace(/[^a-z0-9.-]/gi,"_")}`);function Ct(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 Lt(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 st(e,n){if(Ct(e,n))return _e(re(e))&&rt(re(e)),"keychain";let t=re(e);return At(It(t),{recursive:!0,mode:448}),_t(t,n,{mode:384}),St(t,384),"file"}function me(e){if(process.env.DOCKITO_TOKEN)return process.env.DOCKITO_TOKEN.trim();let n=Lt(e);if(n)return n;let t=re(e);return _e(t)&&Nt(t,"utf8").trim()||null}function it(e){Mt(e);let n=re(e);_e(n)&&rt(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)??h(`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 Ie(e,n){if(!n)return{project:null,folder:null,label:`${e.slug} Docs`};let[t,...o]=n.split("/").filter(Boolean),r=R(e,t);if(!r)return h(`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?h(`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 Rt}from"node:crypto";import{spawnSync as Ft}from"node:child_process";import{existsSync as ie,readFileSync as Kt,readdirSync as Ut,statSync as lt}from"node:fs";import{basename as at,join as C,relative as Wt,resolve as Ee,sep as Ht}from"node:path";var Bt=4,se=e=>e<1024*1024?`${e?Math.max(1,Math.round(e/1024)):0} KB`:`${(e/1048576).toFixed(1)} MB`,Jt=e=>Rt("sha256").update(e).digest("hex"),dt=e=>e.split(Ht).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=Ft("git",e,{cwd:n,encoding:"utf8",maxBuffer:67108864});return t.error||t.status!==0?null:t.stdout}function Gt(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 Ut(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))&<(C(e,r)).isFile()).sort()}function zt(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(_)||h("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=Ee(a);(!ie(c)||!lt(c).isDirectory())&&h(`${a} isn't a folder.`);let f=dt(Wt(process.cwd(),c))||".",w;t.workspace&&(w=r.workspaces.find(i=>i.slug===t.workspace)??h(`This login can't reach workspace "${t.workspace}".`));let g=t.to;if(!g){o||ct(r,f);let i=(w?[w]:r.workspaces.filter(_)).flatMap(m=>[...m.projects.map(v=>({w:m,p:v.slug,label:v.name,hint:`${m.slug} \xB7 ${V(v.docs,"doc")}`})),{w:m,p:null,label:"New project",hint:m.slug}]),l=i[await et("Project",i)];w=l.w,g=l.p??await he("Project name",at(c===process.cwd()?c:Ee(c,".."))),t.create||=!l.p}if(!w){let i=r.workspaces.filter(m=>_(m)&&R(m,g.split("/")[0])),l=r.workspaces.find(m=>m.kind==="personal"&&m.role==="owner");if(w=i.length===1?i[0]:r.workspaces.length===1?r.workspaces[0]:!i.length&&l?l:void 0,!w)throw new D("needs_workspace",`Which workspace? Run again with -w: ${(i.length?i:r.workspaces).map(m=>m.slug).join(", ")}`)}let u=w;_(u)||h(`This login has View access in ${u.slug}; syncing needs Publish. Run \`dockito login --update\`.`);let[k,...j]=g.split("/").filter(Boolean);k||h("--to takes a project, or project/folder."),j.length>2&&h(`Folders go at most ${2} deep inside a project.`);let b=R(u,k);if(!b&&!t.create)throw new D("no_project",`No project "${k}" in ${u.slug}. Projects: ${u.projects.map(i=>i.slug).join(", ")||"none"}.
|
|
11
|
+
To make it, run again with --create.`,{workspace:u.slug,projects:u.projects.map(i=>i.slug)});let K=Gt(c,s);K.length||h(`No ${r.kinds.flatMap(i=>i.extensions).join(", ")} files in ${f}.`);let ce=2-j.length,X=K.filter(i=>i.split("/").length-1>ce);if(X.length&&!t.fold){let i=m=>m.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 Ae("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]}},$e=new Map;for(let i of K){let l=Ue(i),m=[...l.folder,l.file].join("/").toLowerCase();$e.set(m,($e.get(m)??0)+1)}let We=b?await e.files(u.id,b.id):[],Q=H(b),M=K.map(i=>{let{folder:l,dropped:m,file:v}=Ue(i),L=m.length&&$e.get([...l,v].join("/").toLowerCase())>1?[...m,v].join("-"):v,S=new Uint8Array(Kt(C(c,i))),W={rel:i,name:L,folder:l,bytes:S,hash:Jt(S),action:"new",doc:null,others:[],assets:[]};/\.html?$/i.test(i)&&(W.assets=zt(new TextDecoder().decode(S),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=M.filter(i=>i.bytes.length>u.maxFile);He.length&&h(`Over the ${se(u.maxFile)} limit per file in ${u.slug}:
|
|
13
|
+
${He.map(i=>` ${i.rel} ${se(i.bytes.length)}`).join(`
|
|
14
|
+
`)}`);let je=new Map;for(let i of M){let l=[...i.folder,i.name].join("/").toLowerCase();je.set(l,[...je.get(l)??[],i.rel])}let Be=[...je.values()].filter(i=>i.length>1);Be.length&&h(`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 M)for(let l=1;l<=i.folder.length;l++){let m=i.folder.slice(0,l),v=m.join("/").toLowerCase();Q.idOf(m)===void 0&&!Je.has(v)&&(Je.add(v),N.push(m))}N.sort((i,l)=>i.length-l.length);let O=M.filter(i=>i.action==="new"||i.action==="update"),qe=M.filter(i=>i.action==="ambiguous"),ve=M.filter(i=>i.action==="same").length,kt=new Set(M.flatMap(i=>i.doc?[i.doc.id]:i.others.map(l=>l.id))),xe=We.filter(i=>!kt.has(i.id)).length,Ge=`${u.slug}/${b?.slug??k}${j.length?"/"+j.join("/"):""}`,le={workspace:u.slug,to:Ge,create:{project:b?null:k,folders:N.map(i=>i.join("/"))},docs:O.map(i=>({file:i.rel,action:i.action,folder:i.folder.join("/")||null,name:i.name,link:i.doc?e.linkOf(i.doc.slug):null})),unchanged:ve,ambiguous:qe.map(i=>({file:i.rel,docs:i.others.map(l=>e.linkOf(l.slug))})),notHere:xe};if(!t.json){d(`${T(f==="."?"./":f+"/")} \u2192 ${T(Ge)}`),b||d(` ${y("+ project")} ${k}`);for(let l of N)d(` ${y("+ folder ")} ${l.join("/")}`);if(O.length>15)for(let[l,m]of[["new",y("+ new ")],["update",E("~ update ")]]){let v=new Map;for(let L of O.filter(S=>S.action===l))v.set(L.folder.join("/"),[...v.get(L.folder.join("/"))??[],L.name]);for(let[L,S]of v)d(` ${m} ${S.length} in ${L||"the project's top"}: ${p(S.slice(0,4).join(", ")+(S.length>4?`, ${S.length-4} more`:""))}`)}else for(let l of O){let m=l.name!==at(l.rel)?p(` \u2192 ${[...l.folder,l.name].join("/")}`):"";d(` ${l.action==="new"?y("+ new "):E("~ update ")} ${l.rel}${m}`)}ve&&d(p(` ${ve} unchanged`));for(let l of qe)d(` ${I("? skip ")} ${l.rel} ${p(`${l.others.length} docs there have this name: ${l.others.map(m=>e.linkOf(m.slug)).join(", ")}`)}`);xe&&d(p(` ${V(xe,"doc")} in the project with no file here, left as they are`)),O.length&&d(p(` ${V(O.length,"file")} to send, ${se(O.reduce((l,m)=>l+m.bytes.length,0))}`));let i=O.filter(l=>l.assets.length);i.length&&d(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?d(JSON.stringify({...le,applied:!0},null,2)):d(`${y("\u2713")} Up to date`);return}if(t["dry-run"]){t.json&&d(JSON.stringify({...le,applied:!1},null,2));return}if((!b||N.length)&&!t.yes){let i=["dockito sync",f,`-w ${u.slug}`,`--to ${g}`,t.create&&!b?"--create":"",t.fold?"--fold":"","--yes"].filter(Boolean).join(" ");if(!o){if(t.json)return d(JSON.stringify({...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 Ae("Apply?"))return $(p("Nothing changed."))}if(!b){let i=await e.createProject(u.id,k);b={id:i.id,slug:i.slug,name:k,docs:0,folders:[]},t.json||d(`${y("\u2713")} Project ${k}`)}let Te=b;for(let i of N){let l=await e.createFolder(Te.id,i[i.length-1],i.length>1?Q.idOf(i.slice(0,-1)):null);Q.add(i,l.id)}N.length&&!t.json&&d(`${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,yt=new Set(de?(ye(["status","--porcelain","-z","--","."],c)??"").split("\0").filter(Boolean).map(i=>Ee(de,i.slice(3))):[]),U=[],ue=null,Oe=[...O],pe=!t.json&&Se(),bt=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 ${se(Ye)} of ${se(bt)}`)}`);async function $t(i){let l=ze&&!yt.has(C(c,i.rel))?ze:null;for(;;)try{let m=i.doc?await e.update(i.doc.id,i.name,i.bytes,{commit:l}):await e.create(u.id,i.name,i.bytes,{project:Te.id,folder:Q.idOf(i.folder)??null});return{file:i.rel,action:i.action,link:e.linkOf(m.slug)}}catch(m){if(m instanceof x&&m.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:m instanceof x?ne(m,e.url):String(m)}}}Ve(),await Promise.all(Array.from({length:Math.min(Bt,Oe.length)},async()=>{for(let i=Oe.shift();i;i=Oe.shift()){let l=await $t(i);U.push(l),Ye+=i.bytes.length,pe&&process.stdout.write("\r\x1B[2K"),l.error&&!t.json?d(`${I("\u2717")} ${l.file} ${I(l.error)}`):!t.json&&!pe&&d(`${y("\u2713")} ${l.file} ${p(l.link)}`),Ve()}})),pe&&process.stdout.write("\r\x1B[2K");let De=U.filter(i=>i.error),Xe=`${e.url}/${u.slug}/project/${Te.slug}`;t.json?d(JSON.stringify({...le,applied:!0,link:Xe,results:U},null,2)):d(`${De.length?E(`${U.length-De.length} of ${U.length} sent`):y("\u2713 Synced")} ${p(Xe)}`),De.length&&(process.exitCode=1)}var Ce=`Dockito hosts Markdown and HTML docs at a short link, with History.
|
|
19
18
|
|
|
20
19
|
workspace a person's or a team's space; its slug is in every link
|
|
21
20
|
project docs grouped in a workspace, with folders up to 2 deep
|
|
@@ -28,43 +27,84 @@ ${S}`))}async function cn(e,n){e.length||w("Name a file: `dockito publish notes.
|
|
|
28
27
|
For AI agents:
|
|
29
28
|
- The CLI remembers nothing between runs. Run \`dockito tree\` to see
|
|
30
29
|
where docs can go and \`dockito ls <place>\` for what's there.
|
|
31
|
-
-
|
|
32
|
-
|
|
30
|
+
- Just a link to share: publish with no --to; it goes to the workspace's
|
|
31
|
+
Docs. When it belongs in a project, ask the user where, never guess,
|
|
32
|
+
and pass it with --to every time.
|
|
33
|
+
- Docs are private: only people in the workspace can open them. Add
|
|
34
|
+
--public (anyone with the link, no sign-in) only when the user clearly
|
|
35
|
+
asked for a public link. If they didn't say, keep it private.
|
|
33
36
|
- A folder of docs: \`dockito sync <folder> --to <project>\`. It shows a
|
|
34
37
|
plan; show it to the user before running with --yes.
|
|
35
|
-
-
|
|
38
|
+
- \`dockito <command> --help\` explains each command. Use --json; on
|
|
39
|
+
failure, read "error" and the commands it suggests.`,Le={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
|
+
file updates the doc with the same file name in the same folder, or becomes
|
|
41
|
+
a new doc there. Only files whose content changed are sent. Nothing is moved
|
|
42
|
+
or deleted in Dockito. The CLI remembers nothing: pass --to every time.
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
Docs made by sync are private.
|
|
45
|
+
|
|
46
|
+
--to <project[/folder]> Where it goes (required; \`dockito tree\` lists places)
|
|
47
|
+
-w, --workspace <slug> Another workspace
|
|
48
|
+
--create Make the project if it doesn't exist
|
|
49
|
+
--fold Put folders deeper than 2 into the deepest allowed;
|
|
50
|
+
clashing names get the left-out folders in front
|
|
51
|
+
--dry-run Show the plan, change nothing
|
|
52
|
+
--yes Apply a plan that makes a project or folders
|
|
53
|
+
|
|
54
|
+
It always shows a plan. Making a project or folders waits for --yes: show
|
|
55
|
+
the plan to the user first. Sending docs into folders that exist just runs.
|
|
56
|
+
|
|
57
|
+
dockito sync docs --to handbook
|
|
58
|
+
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
|
+
updates its doc (same link, a new History entry); otherwise it's a new doc.
|
|
60
|
+
Unchanged files are skipped.
|
|
61
|
+
|
|
62
|
+
Docs are private: only people in the workspace can open them.
|
|
63
|
+
Add --public only when the user clearly asked for a public link (anyone
|
|
64
|
+
with the link, no sign-in). If they didn't say, keep it private. --public
|
|
65
|
+
works only for a new doc; an existing private doc can be made public only
|
|
66
|
+
from Share in the app.
|
|
67
|
+
|
|
68
|
+
--to <project[/folder]> Where (default: the workspace's Docs)
|
|
69
|
+
-w, --workspace <slug> Another workspace (default: your personal one)
|
|
70
|
+
-m, --message <note> Keep this entry as a saved point, with a note
|
|
71
|
+
--public Make the new doc public (only when the user asked)
|
|
72
|
+
--title <title> A new doc's title (default: the file name); an
|
|
73
|
+
existing doc keeps its own
|
|
74
|
+
--doc <doc> Update this doc (link or code) instead
|
|
75
|
+
--new A new doc even if the name is already there
|
|
76
|
+
|
|
77
|
+
dockito publish plan.md private, in your Docs
|
|
78
|
+
dockito publish plan.md --to handbook/specs private, in a folder
|
|
79
|
+
dockito publish plan.md --public the user asked for a public link
|
|
80
|
+
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
|
|
81
|
+
(anyone with the link). --private makes it private again at once.
|
|
82
|
+
|
|
83
|
+
An existing doc is made public only from Share in the app; a new doc can be
|
|
84
|
+
published public with \`dockito publish <file> --public\` when the user asks.
|
|
85
|
+
|
|
86
|
+
dockito share <link or code>
|
|
87
|
+
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
|
|
88
|
+
workspace's top level, a project, or a folder. -w for another workspace.`},mkdir:{use:"mkdir <project/folder>",about:"Make a project or folders",page:`Makes whatever is missing: the project, then up to 2 levels of folders.
|
|
89
|
+
|
|
90
|
+
dockito mkdir handbook/specs/drafts`},open:{use:"open <doc>",about:"Open in the browser",page:"Opens a doc (link or code) in the browser."},get:{use:"get <doc> [-o file]",about:"Download a doc",page:"Prints a doc's file, or writes it with -o. --entry <n> gets a History entry\n(numbers from `dockito history`)."},history:{use:"history <doc>",about:"History entries",page:`Lists a doc's History: entry, when, who, from where (upload, edit, cli,
|
|
91
|
+
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
|
|
92
|
+
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
|
|
93
|
+
in each workspace: View, Publish or Everything. --update changes that access.
|
|
94
|
+
--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.5",A=nt(),ae=ot(A),Ke=`dockito-cli/${Fe} (${process.platform})`;function P(){let e=me(ae);return e||h("Not logged in. Run `dockito login`."),fe({url:A,token:e,source:"cli",userAgent:Ke})}function wt(e){let[n,t]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];try{let o=Yt(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),sn=e=>new Promise(n=>setTimeout(n,e)),an=()=>en().replace(/\.local$/,"").slice(0,60)||"Unknown device";function F(e){return Ze(e)??h(`"${e}" isn't a doc link or code. \`dockito ls\` lists docs with their codes.`)}function gt(e){let n=tn(mt(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","--",Re(e)],{cwd:n,encoding:"utf8"});return o.status!==0||o.stdout.trim()?null:t.stdout.trim()}var cn=e=>Vt("sha256").update(e).digest("hex");async function ln(e){let n=me(ae);e.update&&!n&&h("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=fe({url:A,token:e.update?n:null,userAgent:Ke}),o=await t.startLogin("Dockito CLI",an()),r=!e["no-browser"]&&rn()&&wt(o.verification_uri_complete);d(r?`Opening ${o.verification_uri_complete}`:`Open ${T(o.verification_uri)}`),d(`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 sn(s);try{let c=await t.pollLogin(o.device_code);if(!c)continue;let f=st(ae,c.token);d(`${y("\u2713")} Logged in as ${c.user.name}${c.user.username?p(` (${c.user.username})`):""}`);for(let w of c.workspaces)d(` ${w.name} ${p(`${w.slug} \xB7 ${J[w.level]}`)}`);f==="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"&&h("Denied in the browser."),c instanceof x&&c.code==="expired_token"&&h("The code expired. Run `dockito login` again."),c instanceof x&&c.code==="offline"){s=Math.min(s*2,3e4);continue}throw c}}h("The code expired. Run `dockito login` again.")}async function dn(){let e=me(ae);if(!e)return d("Not logged in.");process.env.DOCKITO_TOKEN&&h("DOCKITO_TOKEN is set; unset it or revoke that token in Account \u2192 Tokens.");try{await fe({url:A,token:e,userAgent:Ke}).logout()}catch(n){if(!(n instanceof x)||n.code!=="invalid_token"&&n.code!=="not_found")throw n}it(ae),d(`${y("\u2713")} Logged out`)}async function un(e){let n=await P().whoami();if(e.json)return d(JSON.stringify(n,null,2));d(`${n.user.name} ${p(`${n.user.username} \xB7 ${n.user.email}`)}`),n.token&&d(p(`${n.token.name} \xB7 ${n.token.source==="cli"?"ends after 90 days without use":`expires ${new Date(n.token.expiresAt).toLocaleDateString()}`}`)),d(),te(n.workspaces.map(t=>[t.slug,t.name,p(t.level?J[t.level]:t.role)])),A!=="https://dockito-app.dockito.workers.dev"&&$(p(`
|
|
95
|
+
${A}`))}async function pn(e,n){e.length||h("Name a file: `dockito publish notes.md --to <project/folder>`"),n.doc&&e.length>1&&h("--doc takes one file."),n.title!==void 0&&e.length>1&&h("--title takes one file."),n.title!==void 0&&!n.title.trim()&&h("--title needs a title."),n.public&&n.doc&&h("--public is only for a new doc; an existing one can be made public from Share in the app.");for(let f of e)(!Xt(f)||!Qt(f).isFile())&&h(`${f} isn't a file${nn(f)?"":` (looked for ${mt(f)})`}.`);let t=P(),o=[];if(n.doc){let f=await t.doc(F(n.doc)),w=new Uint8Array(ft(e[0])),g=await t.update(f.doc.id,Re(e[0]),w,{note:n.message,commit:gt(e[0])});return n.json?d(JSON.stringify([{file:e[0],...g,link:t.linkOf(g.slug)}],null,2)):d(g.unchanged?`${p("=")} ${e[0]} ${p("unchanged")} ${t.linkOf(g.slug)}`:`${y("\u2713")} ${e[0]} \u2192 ${t.linkOf(g.slug)}`)}let r=await t.tree(),s=we(r,n.workspace);_(s)||h(`This login has View access in ${s.slug}. Run \`dockito login --update\`.`);let a=Ie(s,n.to),c=await t.files(s.id,a.project);for(let f of e){let w=new Uint8Array(ft(f)),g=Re(f),u=n.new?[]:ee(c,a.folder,g);u.length>1&&h(`${u.length} docs in ${a.label} are named ${g}: ${u.map(K=>t.linkOf(K.slug)).join(", ")}. Pick one with --doc, or publish a new one with --new.`);let k=u[0];if(k&&n.public&&!(await t.doc(k.slug)).doc.public&&h(`${g} is already a private doc in ${a.label} (${t.linkOf(k.slug)}). Only Share in the app can make it public; \`--new --public\` publishes a new public doc.`),k&&k.sha256===cn(w)&&!n.message){o.push({file:f,id:k.id,slug:k.slug,version:k.version,unchanged:!0,link:t.linkOf(k.slug)}),n.json||d(`${p("=")} ${f} ${p("unchanged")} ${t.linkOf(k.slug)}`);continue}let j=k?await t.update(k.id,g,w,{note:n.message,commit:gt(f)}):await t.create(s.id,g,w,{project:a.project,folder:a.folder,public:n.public,title:n.title});!k&&n.message&&await t.save(j.id,j.version,n.message);let b="public"in j&&j.public===!0;o.push({file:f,...j,link:t.linkOf(j.slug),new:!k}),n.json||d(`${y("\u2713")} ${f} \u2192 ${t.linkOf(j.slug)} ${p(k?j.unchanged?"unchanged":"updated":`new in ${a.label}`)}${b?` ${y("\xB7 public")}`:""}${n.message?p(` \xB7 saved: ${n.message}`):""}`)}n.json?d(JSON.stringify(o,null,2)):!n.to&&o.some(f=>f.new)&&$(p("In the workspace's Docs. To put docs in a project: --to <project/folder>; `dockito tree` lists them."))}async function fn(e){let n=await P().tree();if(e.json)return d(JSON.stringify(n,null,2));let t=o=>p(`${o} doc${o===1?"":"s"}`);for(let o of n.workspaces){d(`${T(o.slug)} ${o.name} ${p(`\xB7 ${o.level?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,f)=>{for(let w of s.folders.filter(g=>g.parentId===c))r.push([`${f}/${w.name}`,"",t(w.docs)]),a(w.id,`${f}/${w.name}`)};a(null,s.slug)}te(r.map(s=>[" ",...s])),d()}$(p("Publish into a place: dockito publish <file> -w <workspace> --to <project/folder>"))}async function gn(e,n){let t=P(),o=await t.tree(),r=we(o,n.workspace),s=Ie(r,e),a=await t.docs(r.id,{project:s.project,folder:s.folder}),c=s.project?r.projects.find(u=>u.id===s.project):null,f=e?[]:r.projects,w=c?c.folders.filter(u=>u.parentId===s.folder):[];if(n.json)return d(JSON.stringify({workspace:r.slug,place:e??null,projects:f,folders:w,docs:a},null,2));let g=[];for(let u of f)g.push([T(`${u.slug}/`),u.name,p(`${u.docs} doc${u.docs===1?"":"s"}`),""]);for(let u of w)g.push([T(`${u.name}/`),"",p(`${u.docs} doc${u.docs===1?"":"s"}`),""]);for(let u of a)g.push([u.slug,u.title+p(u.kind==="html"?" .html":" .md"),p(ge(u.updatedAt)),p(u.updatedByName??"")]);if(!g.length)return $(p(`Nothing in ${s.label} yet.`));te(g)}async function hn(e,n){let[t,...o]=(e??"").split("/").filter(Boolean);t||h("`dockito mkdir <project>[/folder[/subfolder]]`"),o.length>2&&h("Folders go at most 2 deep inside a project.");let r=P(),s=we(await r.tree(),n.workspace),a=R(s,t),c=a?.id??(await r.createProject(s.id,t)).id,f=!a;a||d(`${y("\u2713")} Project ${t}`);let w=H(a),g=null;for(let u=0;u<o.length;u++){let k=w.idOf(o.slice(0,u+1));if(k){g=k;continue}g=(await r.createFolder(c,o[u],g)).id,w.add(o.slice(0,u+1),g),f=!0,d(`${y("\u2713")} Folder ${o.slice(0,u+1).join("/")}`)}f||$(p("Already there."))}async function mn(e){e||h("Name a doc: `dockito open <link or code>`");let n=`${A}/${F(e)}`;wt(n)||d(n)}async function wn(e,n){e||h("Name a doc: `dockito get <link or code> [-o file]`");let t=P(),o=await t.doc(F(e)),r=n.entry?Number(n.entry):null;n.entry&&(!Number.isInteger(r)||r<1)&&h("--entry takes an entry number from `dockito history`.");let{bytes:s}=await t.content(o.doc.id,r);if(!n.output)return void process.stdout.write(s);Zt(n.output,s),$(`${y("\u2713")} ${n.output} ${p(`\u2190 ${t.linkOf(o.doc.slug)}${r?` entry ${r}`:""}`)}`)}async function kn(e,n){e||h("Name a doc: `dockito history <link or code>`");let t=await P().doc(F(e));if(n.json)return d(JSON.stringify(t.versions,null,2));d(`${T(t.doc.title)} ${p(`${A}/${t.doc.slug}`)}`),te(t.versions.map((o,r)=>[String(o.number),r===0?y("current"):p(ge(o.createdAt)),r===0?p(ge(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 yn(e,n){let t=Number(n);(!e||!Number.isInteger(t)||t<1)&&h("`dockito restore <doc> <entry>`; entries are in `dockito history`.");let o=P(),r=await o.doc(F(e)),s=await o.restore(r.doc.id,t,r.versions[0]?.number??null);d(`${y("\u2713")} Restored entry ${t} as entry ${s.version} ${p(o.linkOf(s.slug))}`)}async function bn(e,n,t){e||h('`dockito save <doc> [entry] -m "note"`');let o=P(),r=await o.doc(F(e)),s=n?Number(n):r.versions[0]?.number;(!Number.isInteger(s)||s<1)&&h("Entries are in `dockito history`."),await o.save(r.doc.id,s,t.message??null),d(`${y("\u2713")} Saved entry ${s}${t.message?p(` \xB7 ${t.message}`):""}`)}async function $n(e,n){e||h("`dockito share <link or code> [--private]`"),n.public&&h("An existing doc can be made public only from Share in the app. `dockito publish <file> --public` makes a new doc public.");let t=P(),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?d(JSON.stringify({public:!1,link:r},null,2)):d(`${y("\u2713")} Private: only people in ${o.workspace.name} can open it ${p(r)}`);if(n.json)return d(JSON.stringify({public:o.doc.public,link:r},null,2));d(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 Me=`Dockito CLI ${Fe}
|
|
96
|
+
|
|
97
|
+
${Ce}
|
|
38
98
|
|
|
39
99
|
Usage: dockito <command>
|
|
40
100
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
--yes Apply without asking; --dry-run shows the plan only
|
|
46
|
-
publish <file...> Publish files; a file with the same name in that
|
|
47
|
-
place updates its doc
|
|
48
|
-
--to <project/folder> Where (default: the workspace's Docs)
|
|
49
|
-
-m, --message <note> Keep this entry as a saved point
|
|
50
|
-
--doc <doc> Update this doc instead
|
|
51
|
-
--new Publish as a new doc even if the name is there
|
|
52
|
-
tree Every workspace, project and folder you can use
|
|
53
|
-
ls [project/folder] What's in a place
|
|
54
|
-
mkdir <project/folder> Make a project or folders
|
|
55
|
-
open <doc> Open in the browser
|
|
56
|
-
get <doc> [-o file] Download (--entry <n> for a History entry)
|
|
57
|
-
history <doc> History entries
|
|
58
|
-
restore <doc> <entry> Make a History entry current again
|
|
59
|
-
save <doc> [entry] -m ... Keep an entry as a saved point
|
|
60
|
-
|
|
61
|
-
login [--update] Sign in through the browser (--update changes access)
|
|
62
|
-
logout Sign out this machine
|
|
63
|
-
whoami Who you are and what this login can reach
|
|
64
|
-
agent What an AI agent needs to know, on its own
|
|
65
|
-
|
|
66
|
-
<doc> is a link or a code.
|
|
101
|
+
${Object.values(Le).map(e=>` ${e.use.padEnd(30)}${e.about}`).join(`
|
|
102
|
+
`)}
|
|
103
|
+
|
|
104
|
+
<doc> is a link or a code. \`dockito <command> --help\` for more.
|
|
67
105
|
-w, --workspace <slug> Another workspace (default: your personal one)
|
|
68
106
|
--json Machine-readable output
|
|
69
107
|
|
|
70
|
-
Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)
|
|
108
|
+
Environment: DOCKITO_TOKEN (use this token), DOCKITO_URL (another Dockito)`,ht=e=>{let n=Le[e];return n?`dockito ${n.use}
|
|
109
|
+
|
|
110
|
+
${n.page}`:null};async function jn(){let{values:e,positionals:n}=on({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"},json:{type:"boolean"},update:{type:"boolean"},yes:{type:"boolean",short:"y"},"dry-run":{type:"boolean"},create:{type:"boolean"},fold:{type:"boolean"},"no-browser":{type:"boolean"},public:{type:"boolean"},private:{type:"boolean"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"}}}),[t,...o]=n;if(e.version)return d(Fe);if(t==="help"&&o[0])return d(ht(o[0])??Me);if(e.help&&t)return d(ht(t)??Me);if(e.help||!t||t==="help")return d(Me);switch(t){case"login":return ln(e);case"logout":return dn();case"whoami":return un(e);case"sync":return ut(P(),o[0],e);case"publish":return pn(o,e);case"tree":return fn(e);case"ls":return gn(o[0],e);case"mkdir":return hn(o[0],e);case"share":return $n(o[0],e);case"open":return mn(o[0]);case"get":return wn(o[0],e);case"history":return kn(o[0],e);case"restore":return yn(o[0],o[1]);case"save":return bn(o[0],o[1],e);case"agent":return d(Ce);default:h(`Unknown command "${t}". Run \`dockito --help\`.`)}}function be(e){let n=2,t=()=>--n===0&&process.exit(e);process.stdout.write("",t),process.stderr.write("",t)}jn().then(()=>be(Number(process.exitCode??0)),e=>{let n=process.argv.includes("--json");if(e instanceof D)return n?d(JSON.stringify({error:e.code,message:e.message.replace(/\x1b\[[0-9;]*m/g,""),...e.data},null,2)):$(e.message),be(2);if(e instanceof oe)return be(130);n&&e instanceof x?d(JSON.stringify({error:e.code,message:ne(e,A),...e.detail},null,2)):n&&e instanceof B?d(JSON.stringify({error:"failed",message:e.message},null,2)):e instanceof B?$(I(e.message)):e instanceof x?$(I(ne(e,A))):e instanceof TypeError&&/parseArgs|option|positional/i.test(String(e.message))?$(I(`${e.message}. Run \`dockito --help\`.`)):$(I(String(e?.stack??e))),be(1)});
|
package/package.json
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dockito",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.5",
|
|
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
|
}
|