document360-writer 0.5.289 → 0.5.291
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/dist/cli.js +2 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -298,7 +298,7 @@ try {
|
|
|
298
298
|
`,a=o.endsWith(`
|
|
299
299
|
`)?"":i;um(t,`${o}${a}${s.join(i)}${i}`,"utf8")}async function Qc(){let e=process.cwd(),t=mm(e);if(mr(t)&&!await Tx({message:`${t} already exists. Overwrite?`,default:!1}))return console.log(p("init cancelled.")),{kind:"continue"};let n=bm(e)??pm(e),o=await Rs({message:"Project ID (used to scope sessions, screenshots, etc.):",default:n}),r=await Rs({message:"Capture directory (where document360-capture .spec.ts files live):",default:wm}),s=await Rs({message:"Screenshot output directory:",default:ym}),i=await Rs({message:"Default connection profile name:",default:"berlin"}),a=await Rs({message:"Document360 environment for this profile (baked preset):",default:"berlin"}),l={projectId:o,docsDir:gm,captureDir:r,outputDir:s,profiles:{[i]:{connection:{environment:a},production:!1}},defaultProfile:i,authoritativeSourceFiles:km(e)},c=Ex(e);c?.terminologyGlossary&&(l.terminologyGlossary=c.terminologyGlossary),fm(l,e),Xc(e);let m=hr(e);console.log(""),console.log(X(`\u2713 Wrote ${t}`)),console.log(""),console.log(`Next: ${ee(`d360-writer login --profile ${i}`)} ${p("(sign in; project & workspace auto-selected)")}`),console.log("");for(let w of fr(m))console.log(w);return console.log(""),console.log(`${p("Then ask:")} ${ee('"write the user docs for this repo"')}`),console.log(""),{kind:"continue"}}function bm(e){let t=oa(e,"package.json");if(!mr(t))return null;try{return JSON.parse(hm(t,"utf8")).name??null}catch{return null}}function km(e){let t=[];for(let n of["README.md","ARCHITECTURE.md","CLAUDE.md"])mr(oa(e,n))&&t.push(n);for(let n of["src","api","docs"]){let o=oa(e,n);mr(o)&&!Dx(o)&&t.push(n)}return t}function Dx(e){try{return Ax(e,{withFileTypes:!0}).filter(n=>n.isDirectory()&&!n.name.startsWith(".")).length>6}catch{return!1}}var dm,ra=ys(()=>{"use strict";ie();jn();dm=[".sessions/","cache/"]});import{createRequire as Vx}from"node:module";import{existsSync as Kx,readFileSync as Yx}from"node:fs";import{dirname as Nm,join as Dm}from"node:path";function bt(){let e;try{e=zx.resolve("document360-capture/helpers")}catch{return null}let t=Nm(e);for(let n=0;n<5&&!Kx(Dm(t,"package.json"));n++)t=Nm(t);try{let n=JSON.parse(Yx(Dm(t,"package.json"),"utf8"));return n.name==="document360-capture"&&n.version?n.version:null}catch{return null}}var zx,gr=ys(()=>{"use strict";zx=Vx(import.meta.url)});var Zw={};Af(Zw,{doctorCommand:()=>Ks,renderDoctorChecks:()=>Jw,runDoctorChecks:()=>Qw});import{existsSync as KC}from"node:fs";import{d360GetAll as YC,getAccessToken as zC,isExpired as Za,loadProfileMap as XC,loadTokens as QC,listStoredSessions as JC,tokensProjectId as qd,packageSkillsDir as Xw,ENGINE_VERSION as ZC,projectConfigPath as eP,readProjectConfig as tP,DEFAULT_DOCS_DIR as nP,fetchManifestSummary as oP,resolveActiveProfile as rP,classifyClaudeAuth as sP,resolveAuthOrigin as iP,authOriginLine as aP,authOriginFix as lP,CLAUDE_CODE_SETUP_HINT as el,resolveModelSetting as cP,probeModel as uP,probeAgentTurn as dP,stderrHeadline as pP,detectAppType as hP,playbookSkillName as fP,D360AuthError as mP,applyStructureOrder as gP}from"document360-engine";async function Qw(e,t){let n=[],o=bt();n.push({level:"ok",label:`d360-writer ${t?`v${t}`:"(version unknown)"} \xB7 engine v${ZC}${o?` \xB7 capture v${o}`:" \xB7 capture not installed"}`});let r=Number(process.versions.node.split(".")[0]);n.push(r>=20?{level:"ok",label:`Node ${process.versions.node}`}:{level:"fail",label:`Node ${process.versions.node} \u2014 20+ required`,fix:"Install Node 20 or later (nodejs.org)"});let s=sP();if(s.state==="ready"){let S=iP(),D=lP(S);n.push({level:D?"warn":"ok",label:`Claude auth: ${aP(S).replace(/^Claude: /,"")}`,...D?{fix:D}:{}})}else s.state==="maybe"?n.push({level:"warn",label:"Claude: no Claude Code login found (may be in the macOS Keychain)",fix:`If a docs turn ends with no output, sign in to Claude Code. ${el}`}):n.push({level:"fail",label:"Claude: not signed in \u2014 the agent cannot run (no Claude Code credentials on this machine)",fix:el});let i=cP(e);if(n.push({level:"ok",label:`Model: ${i.model} (${i.source==="auto"?"auto \u2014 right-sized for docs; /model to change":i.source})`}),s.state==="ready"){let S=await uP(e,i.model??void 0);if(S.ok){n.push({level:"ok",label:`Model reachable \u2014 replied in ${(S.latencyMs/1e3).toFixed(1)}s`});let D=await dP(e,i.model??void 0);if(D.ok)n.push({level:"ok",label:`Agent turn works \u2014 ran with your skills and tools in ${(D.latencyMs/1e3).toFixed(1)}s`});else{let I=pP(D.stderr),Y=/auth|sign.?in|login|credential|token|401|expired/i.test(`${D.error??""} ${D.stderr??""}`);if(n.push({level:"fail",label:D.timedOut?"Agent turn never finished \u2014 the model answers, but a real turn with your skills and tools hangs":Y?"Agent turn failed \u2014 your Claude sign-in has expired, so every /write, /review and /catch-up will do nothing":`Agent turn produced nothing \u2014 the model answers, but a real turn with your skills and tools ${D.producedNothing?"ends instantly with no output":`fails: ${D.error}`}`,fix:(I?`Claude Code said: "${I}". `:"")+(Y?`Sign in again, then re-run /doctor. ${el}`:"This is the failure behind an empty /write, /review or /catch-up. Run /logs for the whole thing \u2014 it offers to copy it so you can send it to the team.")}),Y){let G=n.find(W=>W.label.startsWith("Claude auth:"));G&&(G.level="warn",G.label="Claude auth: a sign-in exists on this machine, but it is no longer valid",G.fix=`Sign in again. ${el}`)}}}else S.timedOut?n.push({level:"fail",label:"Model unreachable \u2014 no response in 30s (a docs turn will hang, then end with 0 tokens / no output)",fix:`Usually a corporate proxy/firewall or org policy blocking the Claude model API. Check this machine's network access to the Claude endpoint; confirm with: claude -p "say hi"`}):n.push({level:"fail",label:`Model call failed \u2014 ${S.error??"no output"} (a docs turn will end with 0 tokens / no output)`,fix:'Check sign-in, the selected model (/model), and network access to the Claude endpoint; confirm with: claude -p "say hi"'})}let a=tP(e);if(!a)return n.push({level:"fail",label:`No ${eP(e)}`,fix:"Run: /init (or d360-writer init)"}),n;n.push({level:"ok",label:`Project config: ${a.projectId}`});let l=(a.docsDir??nP).replace(/\/+$/,"");n.push(a.mode==="engineer"?{level:"warn",label:"Mode: engineer \u2014 agent may modify any source file (dogfooding)",fix:'Remove "mode" from .d360-writer/config.json for the writer-mode boundary'}:{level:"ok",label:`Mode: writer \u2014 edits limited to ${l}/ and .d360-writer/ (docs, capture specs, config); product source is read-only`});let c=a.authoritativeSourceFiles??[];n.push(c.length>0?{level:"ok",label:`Sources: docs grounded in ${c.length} path(s) (${c.slice(0,3).join(", ")}${c.length>3?", \u2026":""})`}:{level:"warn",label:"Sources: not set \u2014 docs are grounded in the whole repo",fix:"Run: /scope to choose which files/folders the docs are written from"});let m=a.appType??hP(e);if(m){let S=a.appType?"from config":"auto-detected",D=m==="web"||m==="cli";n.push({level:"ok",label:`Genre: ${m} (${S})${D?` \u2192 ${fP(m)} loaded`:" \u2014 no doc-type playbook for this genre yet (generic skills)"}`})}else n.push({level:"warn",label:"Genre: unclassified \u2014 no doc-type playbook is loading (generic output)",fix:"Run /genre <web|cli|service|library> to pick \u2014 raises docs to that genre's craft bar"});let w;try{w=rP(e),n.push({level:"ok",label:`Profile: ${w.name} (${w.connection.name})${w.production?" \u26A0 PRODUCTION":""}`})}catch(S){return n.push({level:"fail",label:`Profile config: ${S.message.split(".")[0]}`,fix:"Run: /init to scaffold the profiles map"}),n}let R=QC(w.name,w.project.projectId),E=JC(w.name).filter(S=>{let D=qd(S);return!!D&&D!==w.project.projectId&&!(Za(S)&&!S.refreshToken)}),A=w.project.projectName?`"${w.project.projectName}"`:"this project";R?Za(R)&&!R.refreshToken?n.push({level:"fail",label:R.refreshRejectedAt?`Document360: session ended \u2014 Document360 refused to renew it (${new Date(R.refreshRejectedAt).toLocaleString()})`:"Document360: session expired (no refresh token)",fix:"/login"}):Za(R)?n.push({level:"ok",label:"Document360: logged in (access token expired \u2014 refreshes automatically on the next call)"}):n.push({level:"ok",label:`Document360: logged in to ${A} (until ${new Date(R.expiresAt).toLocaleString()})`}):n.push(E.length>0?{level:"fail",label:`Document360: not signed in to ${A} \u2014 signed in to ${E.map(S=>S.projectName?`"${S.projectName}"`:(qd(S)??"").slice(0,8)+"\u2026").join(", ")} instead`,fix:`Run: /login and pick ${A} \u2014 each project needs its own sign-in, and both are then kept`}:{level:"fail",label:"Document360: not logged in",fix:`Run: /login (or d360-writer login --profile ${w.name})`}),E.length>0&&n.push({level:"ok",label:`Other Document360 projects signed in on this machine: ${E.map(S=>S.projectName?`"${S.projectName}"`:(qd(S)??"").slice(0,8)+"\u2026").join(", ")} (kept \u2014 each repo uses its own)`}),n.push(w.project.workspaceId?{level:"ok",label:`Workspace: ${w.project.workspaceName??w.project.workspaceId}`}:{level:"warn",label:"No workspace selected",fix:"Run: /workspace"});let $=XC(e,w.name);$?$.projectId&&w.project.projectId&&$.projectId!==w.project.projectId?n.push({level:"fail",label:`Category map projectId (${$.projectId}) \u2260 profile projectId (${w.project.projectId})`,fix:"The map section belongs to a different project \u2014 fix .d360-writer/config.json or the map before publishing"}):n.push({level:"ok",label:`Category map: ${Object.keys($.articles).length} articles, ${Object.keys($.categories).length} categories`}):n.push({level:"ok",label:`Category map: none yet for "${w.name}" (created on first /push)`}),n.push(KC(Xw())?{level:"ok",label:"Skills bundle present"}:{level:"fail",label:`Skills folder missing at ${Xw()}`,fix:"Reinstall document360-writer (broken install)"});let P=Kt(e);if(P.status==="current"?n.push({level:"ok",label:`Dev hand-off: builder up to date (devhints v${_e})`}):P.status==="stale"&&n.push({level:"warn",label:`Dev hand-off: builder on devhints v${P.completedVersion}, current is v${_e}`,fix:"Run /devhints and have the repo's coding agent re-run the prompt (adds two-way messages + on-demand docs)"}),R&&(!Za(R)||R.refreshToken)){let S={profile:w.name,connection:w.connection,projectId:w.project.projectId,projectName:w.project.projectName};try{await zC(S);let I=await YC(S,"/v3/projects");n.push({level:"ok",label:`API reachable (${w.connection.apiUrl}) \u2014 ${I.length} project(s) visible`})}catch(I){n.push({level:"fail",label:`API call failed: ${I.message.slice(0,120)}`,fix:"/login if auth-related; otherwise check the apiUrl/network"})}let D=$?.projectId??w.project.projectId;if(D&&w.project.workspaceId){try{let I=await oP(S,D,w.project.workspaceId);if(I){let Y=(I.workspace_hash??"").replace(/^sha256:/,"").slice(0,12);n.push({level:"ok",label:`Your Document360 project: reachable on ${w.connection.name} (${I.languages?.length??0} language${(I.languages?.length??0)===1?"":"s"}) \xB7 version ${Y||"(none)"}`})}else n.push({level:"ok",label:`Workspace sync manifest: not live on ${w.connection.name} yet \u2014 sync uses the per-article path (nothing to do)`})}catch(I){I instanceof mP||/scoped to a (?:specific|different) project|cannot access the requested project/i.test(I.message)?n.push({level:"fail",label:`Project scope: your Document360 session is signed into a different project than this repo expects (${w.project.projectName??D})`,fix:"Run /login and pick this repo's project \u2014 your token is scoped to another project"}):n.push({level:"warn",label:`Workspace sync manifest: probe failed (${I.message.slice(0,80)})`})}try{let I=await gP({cwd:e,profileName:w.name,dryRun:!0}),Y=I.categoriesReordered+I.articlesReordered;if(I.skipped==="no category map")n.push({level:"ok",label:"Reading order: nothing to check yet (no articles published)"});else if(I.skipped)n.push({level:"warn",label:`Reading order: can't be checked (${I.skipped})`,fix:"Your article order on Document360 may not match your structure \u2014 /push syncs it once this resolves"});else if(I.unverified)n.push({level:"warn",label:`Reading order: ${I.unverified} of your articles aren't listed by Document360, so their order can't be set`,fix:"Usually means those articles are not live yet (the listing returns published articles). /publish takes them live, then /push re-syncs the order"});else if(Y>0){let G=[I.categoriesReordered>0?`${I.categoriesReordered} categor${I.categoriesReordered===1?"y":"ies"}`:"",I.articlesReordered>0?`${I.articlesReordered} article${I.articlesReordered===1?"":"s"}`:""].filter(Boolean);n.push({level:"warn",label:`Reading order: ${G.join(" + ")} out of order on Document360`,fix:"Run /push \u2014 it re-syncs the order from your NN- folder/file prefixes"})}else n.push({level:"ok",label:"Reading order: Document360 matches your structure"});if(I.orphans?.length){let G=I.orphans;n.push({level:"warn",label:`${G.length} article${G.length===1?"":"s"} on Document360 ${G.length===1?"is":"are"} not in your docs folder: ${G.slice(0,3).map(W=>W.path).join(", ")}${G.length>3?`, +${G.length-3} more`:""}`,fix:"/retire <path> takes one off your readers \xB7 /pull <path> brings one back into your docs folder \u2014 until you choose, it stays live and d360-writer leaves it alone"})}}catch(I){n.push({level:"warn",label:`Reading order: check failed (${I.message.slice(0,80)})`})}}}return n}function Jw(e){let t=[""];for(let r of e){let s=r.level==="ok"?X("\u2713"):r.level==="warn"?K("\u26A0"):_("\u2717");t.push(` ${s} ${r.label}${r.detail?p(` \u2014 ${r.detail}`):""}`),r.fix&&t.push(` ${p("fix:")} ${ee(r.fix)}`)}let n=e.filter(r=>r.level==="fail").length,o=e.filter(r=>r.level==="warn").length;return t.push(""),t.push(n===0?X(`\u2713 ${o===0?"All checks passed":`Healthy (${o} warning${o===1?"":"s"})`}`):_(`\u2717 ${n} problem${n===1?"":"s"} found`)),t.push(""),t}async function Ks(e,t){console.log(p("Running checks\u2026"));for(let n of Jw(await Qw(t.cwd,t.version)))console.log(n);return{kind:"continue"}}var tl=ys(()=>{"use strict";jn();ie();gr()});import{Command as oL}from"commander";import{createRequire as rL}from"node:module";import{AUTH_MODES as sL}from"document360-engine";import{input as Lv}from"@inquirer/prompts";import{loginPkce as Ov,getAccessToken as _v,toStoredTokens as Mv,clearTokens as Fv,decodeJwtClaims as Of,isExpired as Uv,loadTokens as Lc,saveTokens as Wv,listStoredSessions as Oc,tokensProjectId as Nn,rememberSessionProject as _f,resolveActiveProfile as vs,setProfileProject as Bv,readProjectConfig as Hv}from"document360-engine";ie();import{select as bv}from"@inquirer/prompts";import{resolveActiveProfile as kv,setProfileProject as $v,resolveProjectId as vv,listWorkspaces as xv}from"document360-engine";async function ks(e,t){let n=kv(e,t),o={profile:n.name,connection:n.connection,projectId:n.project.projectId,projectName:n.project.projectName},r=n.project.projectId??vv(o);return{workspaces:await xv(o,r),projectId:r,projectName:n.project.projectName,profile:n.name,environment:n.connection.name,current:n.project.workspaceId}}var Nf=e=>`${e.name??e.id}${e.workspace_type?` \xB7 ${e.workspace_type}`:""}`;function jc(e,t){let n=t.toLowerCase();return e.find(o=>(o.name??"").toLowerCase()===n)??e.find(o=>(o.name??"").toLowerCase().startsWith(n))??e.find(o=>o.id.startsWith(t))}function Bi(e,t,n,o,r){$v(e,t,{projectId:n,workspaceId:o,workspaceName:r})}async function Df(e,t,n){let o;try{o=await ks(e,n)}catch(s){return console.log(_(`Could not list workspaces: ${s.message}`)),1}let r=jc(o.workspaces,t);return r?(Bi(e,o.profile,o.projectId,r.id,r.name),console.log(X(`\u2713 Workspace set to "${r.name??r.id}" for profile "${o.profile}".`)),0):(console.log(_(`No workspace matches "${t}". Available: ${o.workspaces.map(s=>s.name??s.id).join(", ")}`)),1)}async function ur(e,t){let n;try{n=await ks(e,t)}catch(c){console.log(_(`Could not list workspaces: ${c.message}`));return}let{workspaces:o,projectId:r,profile:s,current:i}=n;if(o.length===0){console.log(p("No workspaces found in this project."));return}if(!process.stdin.isTTY){console.log("");for(let c of o)console.log(` ${c.id===i?ee("\u25CF"):" "} ${Nf(c)} ${p(c.id)}`);console.log(p("Run: d360-writer workspace use <name>"));return}let a=await bv({message:"Select the Document360 workspace for this repo:",choices:o.map(c=>({name:`${Nf(c)}${c.id===i?" (current)":""}`,value:c.id}))}),l=o.find(c=>c.id===a);Bi(e,s,r,a,l?.name),console.log(X(`\u2713 Workspace set to "${l?.name??a}" for profile "${s}".`))}ie();import{select as Rv}from"@inquirer/prompts";import{resolveActiveProfile as Sv,setProfileProject as Cv,listProjects as Pv}from"document360-engine";async function wn(e,t){let n=Sv(e,t),o={profile:n.name,connection:n.connection};return{projects:await Pv(o),profile:n.name,environment:n.connection.name,current:n.project.projectId}}var jf=e=>`${e.name??e.id}${e.sub_domain?` \xB7 ${e.sub_domain}`:""}`;function Ic(e,t){let n=t.toLowerCase();return e.find(o=>(o.name??"").toLowerCase()===n)??e.find(o=>(o.name??"").toLowerCase().startsWith(n))??e.find(o=>o.id.startsWith(t))}function Hi(e,t,n,o){Cv(e,t,{projectId:n,projectName:o,workspaceId:void 0,workspaceName:void 0})}async function If(e,t,n){let o;try{o=await wn(e,n)}catch(s){return console.log(_(`Could not list projects: ${s.message}`)),1}let r=Ic(o.projects,t);return r?(Hi(e,o.profile,r.id,r.name),console.log(X(`\u2713 Project set to "${r.name??r.id}" for profile "${o.profile}". Next: pick a workspace (d360-writer workspace).`)),0):(console.log(_(`No project matches "${t}". Available: ${o.projects.map(s=>s.name??s.id).join(", ")}`)),1)}async function Gi(e,t){let n;try{n=await wn(e,t)}catch(l){console.log(_(`Could not list projects: ${l.message}`));return}let{projects:o,profile:r,current:s}=n;if(o.length===0){console.log(p("No projects found for this identity."));return}if(!process.stdin.isTTY){console.log("");for(let l of o)console.log(` ${l.id===s?ee("\u25CF"):" "} ${jf(l)} ${p(l.id)}`);console.log(p("Run: d360-writer project use <name>"));return}let i=await Rv({message:"Select the Document360 project for this repo:",choices:o.map(l=>({name:`${jf(l)}${l.id===s?" (current)":""}`,value:l.id}))}),a=o.find(l=>l.id===i);Hi(e,r,i,a?.name),console.log(X(`\u2713 Project set to "${a?.name??i}" for profile "${r}". Next: pick a workspace.`))}ie();import Tv from"picocolors";function Av(e=process.env){return e.FORCE_HYPERLINK==="0"||!Tv.isColorSupported?!1:e.FORCE_HYPERLINK?!0:!!(e.WT_SESSION||e.TERM_PROGRAM==="vscode"||e.TERM_PROGRAM==="iTerm.app"||e.TERM_PROGRAM==="WezTerm"||e.TERM_PROGRAM==="ghostty"||e.VTE_VERSION||e.KONSOLE_VERSION)}function Ev(e,t=e,n){return Av(n)?`\x1B]8;;${e}\x07${t}\x1B]8;;\x07`:t}var Nv=/https?:\/\/[^\s\x1b]+/g;function $s(e,t){return e.replace(Nv,n=>Ev(n,n,t))}import{readProjectConfig as Lf,sunsetEnvironment as Dv,resolveActiveProfile as jv}from"document360-engine";function Iv(e,t){let n=Lf(e);return Object.entries(n?.profiles??{}).find(([,o])=>(o.connection?.environment??"berlin")===t)?.[0]??null}function En(e,t){try{if(!Lf(e))return null;let n=jv(e,t),o=Dv(n.connection.name);if(!o)return null;let r=Iv(e,o.replacement);return{name:o.name,replacement:o.replacement,why:o.why,replacementProfile:r,cmd:r?`/profile ${r}`:`/profile add ${o.replacement}`}}catch{return null}}function Mf(e){return{...Of(e.idToken)??{},...Of(e.accessToken)??{}}}function qi(e){let t=Mf(e),n=t.email??t.preferred_username??t.sub??"unknown",o=Nn(e),r=e.projectName?` \xB7 project "${e.projectName}"`:o?` \xB7 project ${o}`:"";return`${n}${r}`}async function _c(e,t,n){n&&_f(e,t,n);try{let{projects:o}=await wn(process.cwd(),e);for(let r of Oc(e)){let s=Nn(r);s&&_f(e,s,o.find(i=>i.id===s)?.name)}}catch{}}function Gv(e,t,n){let o=Nn(e);if(!o||!t||o===t)return null;let r=n?`"${n}"`:t.slice(0,8)+"\u2026";return`\u26A0 You signed in to project ${e.projectName?`"${e.projectName}"`:o.slice(0,8)+"\u2026"}, but this repo's docs live in ${r}. Run /login again and pick ${r} (or /project to point this repo somewhere else).`}async function Vi(e){let t=vs(process.cwd(),e.profile),n=t.connection;console.log(p(`Profile "${t.name}" \u2192 ${n.name} (${n.apiUrl})${t.production?" \u26A0 PRODUCTION":""}`));let o=En(process.cwd(),e.profile);o&&(console.log(K(`\u26A0 ${o.why} \u2014 and signing in does NOT change it (the environment comes from the profile, not the login).`)),console.log(K(` To move: ${o.cmd}${o.replacementProfile?"":", then /login"}`)));let r=await Ov(n,{manual:e.manual,promptForRedirect:m=>Lv({message:m})},m=>console.log($s(m))),s=Mv(t.name,r);Wv(s),await Mc(s,t.name,m=>console.log(p(m)));let i=Nn(s),a=vs(process.cwd(),e.profile).project;i&&await _c(t.name,i,a.projectId===i?a.projectName:void 0);let l=Lc(t.name,i??void 0)??s;console.log(""),console.log(X(`\u2713 Logged in to "${t.name}" as ${qi(l)}`));let c=Gv(l,a.projectId,a.projectName);if(c&&console.log(K(c)),console.log(p(` access token expires: ${s.expiresAt}`)),console.log(p(` refresh token: ${s.refreshToken?"yes":"NO \u2014 session ends at expiry"}`)),process.stdin.isTTY)try{Hv(process.cwd())?.profiles?.[t.name]&&(console.log(""),await ur(process.cwd(),t.name))}catch{}}async function Mc(e,t,n){let r=Mf(e).doc360_project_id;if(!(typeof r!="string"||!r))try{if(vs(process.cwd(),t).project.projectId)return;let i;try{let{projects:a}=await wn(process.cwd(),t);i=a.find(l=>l.id===r)?.name}catch{}Bv(process.cwd(),t,{projectId:r,projectName:i}),n(` Project "${i??r}" written to profile "${t}".`)}catch{}}async function Ff(e){let t=vs(process.cwd(),e.profile),n=t.connection,o=Lc(t.name,t.project.projectId);if(!o){console.log(_(`Not logged in to Document360 (profile "${t.name}").`));let r=Oc(t.name).filter(s=>Nn(s)&&Nn(s)!==t.project.projectId);r.length>0&&console.log(p(`This repo's project needs its own sign-in. Signed in on this machine: ${r.map(s=>s.projectName??Nn(s)).join(", ")}`)),console.log(p(`Run: d360-writer login --profile ${t.name}`)),process.exitCode=1;return}if(console.log(`Profile ${ee(t.name)}${t.production?" \u26A0 PRODUCTION":""}: ${qi(o)}`),Uv(o))if(o.refreshToken)try{await _v({profile:t.name,connection:n,projectId:t.project.projectId,projectName:t.project.projectName});let r=Lc(t.name,t.project.projectId);console.log(X(`\u2713 Session refreshed${r?` \u2014 expires ${r.expiresAt}`:""}`))}catch(r){console.log(K(`Session expired and refresh failed (${r.message.slice(0,120)})`)),console.log(p(`Run: d360-writer login --profile ${t.name}`)),process.exitCode=1}else console.log(K("Session expired (no refresh token).")),console.log(p(`Run: d360-writer login --profile ${t.name}`)),process.exitCode=1;else console.log(p(` expires: ${o.expiresAt}`))}async function Uf(e){let t=vs(process.cwd(),e.profile),n=e.allProjects?void 0:t.project.projectId,o=t.project.projectName?`"${t.project.projectName}"`:`profile "${t.name}"`;if(Fv(t.name,n)){console.log(X(e.allProjects?`\u2713 Logged out of every Document360 project for profile "${t.name}".`:`\u2713 Logged out of ${o}.`));let r=Oc(t.name).filter(s=>Nn(s)).length;r>0&&console.log(p(`Still signed in to ${r} other project${r===1?"":"s"} on this machine.`))}else console.log(p(`No Document360 session for ${o} \u2014 nothing to do.`))}ie();import{readProjectConfig as Wf,writeProjectConfig as qv,resolveActiveProfile as Vv,loadTokens as Kv,isExpired as Yv}from"document360-engine";function Ki(e){let t=Wf(e);if(!t?.profiles||Object.keys(t.profiles).length===0){console.log(_("No profiles in .d360-writer/config.json. Run: d360-writer init")),process.exitCode=1;return}console.log("");for(let[n,o]of Object.entries(t.profiles)){let r=n===t.defaultProfile?ee("\u25CF "):" ",s=o.production?K(" \u26A0 PRODUCTION"):"",i=o.connection.environment??"(inline)",a=Kv(n,o.project?.projectId),l=a?Yv(a)&&!a.refreshToken?K("expired"):p("logged in"):p("not logged in");console.log(`${r}${ee(n)} \u2192 ${i}${s} [${l}]`)}console.log(""),console.log(p("\u25CF = default. Switch with: d360-writer profile use <name>")),console.log("")}function Yi(e,t){let n=Wf(e);if(!n?.profiles?.[t]){let r=n?.profiles?Object.keys(n.profiles).join(", "):"(none \u2014 run init)";console.log(_(`Unknown profile "${t}". Available: ${r}`)),process.exitCode=1;return}n.defaultProfile=t,qv(n,e);let o=n.profiles[t].production?K(" \u26A0 PRODUCTION"):"";console.log(X(`\u2713 Default profile is now "${t}"${o}`))}function zi(e,t){try{let n=Vv(e,t);console.log(""),console.log(`Profile ${ee(n.name)}${n.production?K(" \u26A0 PRODUCTION"):""}`),console.log(p(` api: ${n.connection.apiUrl}`)),console.log(p(` identity: ${n.connection.authorizationUrl}`)),console.log(p(` clientId: ${n.connection.clientId}`)),console.log(p(` scopes: ${n.connection.scopes.join(" ")}`)),console.log(p(` project: ${n.project.projectId??"(set at login)"}`)),console.log(p(` workspace:${n.project.workspaceId?" "+n.project.workspaceId:" (none)"}`)),console.log("")}catch(n){console.log(_(n.message)),process.exitCode=1}}ie();import{existsSync as Gf,readdirSync as zv,readFileSync as Xv,statSync as Qv}from"node:fs";import{join as qf}from"node:path";import{apiLogDir as Jv}from"document360-engine";var Zv=30,ex=12e3;function tx(e){if(!Gf(e))return{api:[],crash:[]};try{let t=zv(e);return{api:t.filter(n=>n.endsWith(".jsonl")).sort().reverse(),crash:t.filter(n=>/^crash-\d{4}-\d{2}-\d{2}\.log$/.test(n)).sort().reverse()}}catch{return{api:[],crash:[]}}}var nx=/·\s*(empty-turn|agent-subprocess|uncaught|unhandled)\s*===/i,ox=/·\s*FATAL\s*·/i,rx=e=>ox.test(e)||nx.test(e);function sx(e){let n=e.split(/\n(?==== \d{4}-\d{2}-\d{2}T)/).map(s=>s.trim()).filter(Boolean).filter(s=>s.startsWith("==="));if(n.length===0)return null;let o=[...n].reverse().find(rx),r=o??n[n.length-1];return{text:r,when:/^=== (\S+)/.exec(r)?.[1]??"",count:n.length,notable:!!o}}function ix(e,t){let n=t[0];if(!n)return null;try{return sx(Xv(qf(e,n),"utf8"))}catch{return null}}function Bf(e){let t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()}var Hf=(e,t)=>{try{return`${(Qv(qf(e,t)).size/1024).toFixed(1)} KB`}catch{return""}};function Fc(){let e=Jv(),{api:t,crash:n}=tx(e),o=ix(e,n),r=[];if(o){let s=o.text.split(/\r?\n/),i=s.slice(0,Zv);r.push(o.notable?ne("\u2717",`The last thing that went wrong \u2014 ${Bf(o.when)}:`):ne("\u2139",`Nothing has failed. The most recent note d360-writer recorded \u2014 ${Bf(o.when)}:`)),r.push("");for(let a of i)r.push(` ${a}`);s.length>i.length&&r.push(p(` \u2026${s.length-i.length} more line${s.length-i.length===1?"":"s"} \u2014 the whole thing is in ${n[0]}, and c copies all of it.`)),r.push(""),o.count>1&&r.push(p(` ${o.count} entries recorded today; this is the one worth reading.`)),o.notable&&r.push(p(" If this is not obviously your own doing, send it to the team \u2014 it names what failed.")),r.push("")}else n.length===0&&(r.push(ne("\u2713","Nothing has failed \u2014 d360-writer has recorded no problems.")),r.push(""));if(r.push(`Logs live in ${ee(e)}`),!Gf(e)||t.length===0&&n.length===0)return r.push(p(" Nothing here yet \u2014 logs appear after the first Document360 call.")),{lines:r,copyText:null,notable:!1};if(n.length>0){r.push(""),r.push(`Problem logs ${p("(send these if something crashed or a run did nothing)")}:`);for(let s of n.slice(0,14))r.push(` ${_(s)} ${p(Hf(e,s))}`)}if(t.length>0){r.push(""),r.push("Document360 call logs:");for(let s of t.slice(0,14))r.push(` ${s} ${p(Hf(e,s))}`)}return r.push(""),r.push(p("Failed calls include what was sent and returned (sign-in details removed, 4 KB cap).")),r.push(p("Set D360_LOG_BODIES=1 to record successful calls too.")),{lines:r,copyText:o?o.text.slice(0,ex):null,notable:!!o?.notable}}async function Vf(){return Uc(),{kind:"continue"}}function Uc(){let{lines:e,copyText:t}=Fc();console.log("");for(let n of e)console.log(n);t&&console.log(p("Inside d360-writer, /logs offers to copy this for you.")),console.log("")}ie();import{createSession as ax,resolveAuth as lx,findByName as cx,slugify as ux,touchSession as dx,upsertSession as px,resolveActiveProfile as hx}from"document360-engine";async function Kf(e,t,n,o,r,s){let i=lx(n);i.kind==="none"&&(console.error(""),console.error(_("ANTHROPIC_API_KEY is not set (required for --auth api).")),console.error(`Get a key at ${ee("https://console.anthropic.com/settings/keys")}`),console.error(`Or use your Claude subscription instead: ${ee("d360-writer --auth subscription")}`),process.exit(2)),i.kind==="subscription"&&console.error(p("Using your Claude subscription (no API key set)."));let a=null;try{a=hx(e,r)}catch(R){console.error(_(`Document360 profile error: ${R.message}`)),process.exit(2)}a.production&&(console.error(K(`\u26A0 Profile "${a.name}" is PRODUCTION.`)),s||(console.error(_("Refusing to run against a production profile without --yes.")),process.exit(2)),console.error(p(" --yes given \u2014 proceeding against production.")));let l;if(o){let R=cx(e,o);R||(console.error(_(`No saved session matches "${o}" in this repo.`)),console.error(p("List sessions inside the REPL with /resume.")),process.exit(2)),l=R.uuid,console.error(p(`Resuming "${R.name}"`))}let c=ax({cwd:e,resume:l,profileName:r,allowProdWrites:s===!0}),m=l??null,w=1;for await(let R of c.send(t))switch(R.type){case"session":if(!m){m=R.sessionId;let E=new Date().toISOString();px({uuid:m,name:ux(t),renamed:!1,titled:!1,cwd:e,firstPrompt:t,createdAt:E,updatedAt:E})}break;case"text":process.stdout.write(R.delta);break;case"tool":console.error(Re(` \u2699 ${R.name}`));break;case"result":w=R.ok?0:1,console.error(p(`(${R.inputTokens}\u2192${R.outputTokens} tokens`+(R.costUsd>0?`, $${R.costUsd<.01?R.costUsd.toFixed(4):R.costUsd.toFixed(2)}`:"")+")")),R.ok||console.error(_("agent finished with an error result"));break;case"error":console.error(""),console.error(_(`agent error: ${R.message}`)),process.exit(1)}m&&dx(m),process.stdout.write(`
|
|
300
300
|
`),process.exit(w)}import{createInterface as l1}from"node:readline/promises";import{createSession as gh,resolveAuth as c1,resolveAuthOrigin as u1,authOriginLine as mk,authOriginFix as d1,getSession as p1,setTitle as h1,slugify as f1,touchSession as kk,upsertSession as m1,generateTitle as g1,resolveActiveProfile as $k,resolveModelSetting as w1,readProjectConfig as y1,readUserConfig as b1,resolveAndPersistAppType as k1,decodeJwtClaims as gk,isExpired as $1,loadTokens as v1,isDirectiveMention as x1,stripDirectiveMention as R1,appendDirective as S1,reconcileUpgradeActions as C1,stampArticles as P1,snapshotArticles as wk,listArticleFiles as T1,applyStructureOrder as A1,orderSyncLine as E1,computeSyncStatus as N1}from"document360-engine";import{ENGINE_VERSION as gx}from"document360-engine";function tn(e,t){let n=[],o="";for(let r of e.split(/\s+/).filter(Boolean))for(o.length?o.length+1+r.length<=t?o+=` ${r}`:(n.push(o),o=r):o=r;o.length>t;)n.push(o.slice(0,t)),o=o.slice(t);return o&&n.push(o),n}function Xi(e){if(e.length===0)return[];let t=(s,i)=>`${s}${"\u2500".repeat(78)}${i}`,n=(s="")=>`\u2502 ${s.padEnd(76)} \u2502`,o=[`\u2B06 d360-writer upgraded \u2014 ${e.length===1?"one improvement affects":`${e.length} improvements affect`} the docs you've already written:`,"",t("\u250C","\u2510")];e.forEach((s,i)=>{i>0&&o.push(t("\u251C","\u2524")),o.push(n());for(let a of tn(`${i+1}. ${s.feature}`,76))o.push(n(a));o.push(n());for(let a of tn(s.why,73))o.push(n(` ${a}`));o.push(n())}),o.push(t("\u2514","\u2518")),o.push("");let r=e.length>1;return o.push(`\u2713 ${r?"These are":"This is"} now on your docs roadmap \u2014 each launch shows your next step below (/roadmap shows the full campaign).`),o}function Qi(e){return[`\u26A0 ${e} screenshot${e===1?"":"s"} that hide${e===1?"s":""} sensitive areas ${e===1?"is":"are"} back to needing a check.`," A blacked-out area only covered the first matching spot on the screen \u2014 so on a list or a table, the",` rows below it were still readable. Blackouts now cover every match, and ${e===1?"that picture needs":"those pictures need"} a fresh look.`].join(`
|
|
301
|
-
`)}var yt=[{key:"start",label:"Getting Started",rows:[{t:"text",s:"D360-Writer reads your codebase, generates end-user documentation, and publishes it to Document360."},{t:"text",s:"It needs Claude access \u2014 a Claude subscription (default) or an ANTHROPIC_API_KEY."},{t:"gap"},{t:"sub",s:"Quick start"},{t:"step",n:1,s:"Run /init \u2014 pick your environment, sign in, choose your project & workspace. It ends by printing a one-paste hand-off prompt."},{t:"step",n:2,s:"Paste that prompt into your repo's coding agent (Claude Code / Codex / Cursor). Let it finish, then follow its closing line. (Re-show it: /devhints)"},{t:"step",n:3,s:"Run /doctor to confirm every check is green."},{t:"step",n:4,s:'Type "write the docs for this repository" and follow the prompts.'},{t:"gap"},{t:"dim",s:"That's it \u2014 D360-Writer drafts your docs without you writing a line."},{t:"gap"},{t:"sub",s:"Tips"},{t:"text",s:"Just talk to it \u2014 anything not starting with / goes to the agent."},{t:"dim",s:"/ browses commands \xB7 \u2191 recalls history \xB7 esc interrupts a run \xB7 ctrl+c twice exits"},{t:"dim",s:"tab runs the suggestion \xB7 1-9 pick from a list \xB7 c copies whatever was offered for copying"},{t:"dim",s:"shift+tab switches between auto and manual \u2014 in auto I run the next step myself (/auto)"}]},{key:"agentic",label:"Agentic",rows:[{t:"text",s:"Agentic Mode is designed for applications built using AI coding agents. D360-Writer works alongside your coding agent to continuously create and maintain your user documentation. Think of it as hiring a Technical Writer dedicated to your project."},{t:"gap"},{t:"text",s:"As your coding agent ships new features, it leaves documentation notes for D360-Writer. All you need to do is run the documentation loop."},{t:"gap"},{t:"sub",s:"Auto or manual"},{t:"text",s:"In MANUAL mode D360-Writer works out the next step, offers it, and waits for you to press tab. In AUTO mode it runs that step itself and keeps going \u2014 write, review, check the style, save, go live \u2014 so you can start a build and come back to a finished knowledge base."},{t:"cmd",name:"/auto on",s:"Let D360-Writer run its own next step. Shift+tab cycles between auto and manual at any time, and the mode is shown at the bottom of the screen."},{t:"cmd",name:"/auto --budget <amount>",s:"How much one automatic run may spend before it stops and reports. Every repo has a limit; the standard one is $200."},{t:"dim",s:" esc stops a running automatic pass. Everything already written, saved or published stays \u2014 stopping never undoes work."},{t:"text",s:"Auto mode changes who presses the key, not what is checked. The editorial pass still reads every article, and an article that fails a check is held back as a draft and named in the report rather than being pushed past it. Screenshots are never taken automatically: they need your product running with the right data on screen."},{t:"gap"},{t:"sub",s:"Every few days"},{t:"cmd",name:"/catch-up",s:"Analyze the latest code changes, read the notes left by your coding agent, and update the documentation. Everything lands as a draft."},{t:"cmd",name:"/review --fix",s:"A senior editor reads every article and applies its fixes. Required before anything goes live \u2014 these were written by an AI."},{t:"cmd",name:"/review --blocked --fix",s:"The same editorial pass over only the articles publishing is blocked on \u2014 after a run that rewrote two of forty, this reads those two."},{t:"cmd",name:"/review --stale --fix",s:"The same pass over every article the current editor has not read yet \u2014 never reviewed, or reviewed before the editor last improved. Run it twice and the second run does nothing, so it is the safe way to catch a knowledge base up."},{t:"cmd",name:"/push --all",s:"Save the reviewed articles to Document360, as drafts."},{t:"cmd",name:"/publish --all",s:"Take those drafts live to your readers \u2014 it lists what will become visible and asks first."},{t:"dim",s:" (/catch-up --push runs the writing and the save back-to-back; going live stays a deliberate step)"},{t:"gap"},{t:"sub",s:"Whenever you need"},{t:"cmd",name:"/discover",s:"Interview your coding agent to understand the product (what it is, who it's for, the core idea, common misconceptions) \u2014 pins a product brief that grounds every article. Runs before writing a repo's docs; loops until confident."},{t:"cmd",name:"/audit",s:"Preview what has changed and identify missing documentation, without writing or publishing anything."},{t:"cmd",name:"/pull",s:"Bring Document360 down \u2014 pull edits made in the portal and import any new portal articles."},{t:"cmd",name:"/resolve",s:"Resolve conflicts when an article was edited both here and in the portal \u2014 review the diff, then keep yours, take the portal's, or skip."},{t:"cmd",name:"/comments",s:"The comments reviewers left on passages of your articles in the Document360 portal. You get a table \u2014 category, article, how many comments \u2014 and pick a number to read that article's comments in full (the highlighted passage beside what they said), then answer them or go back to the list. `a` answers everything. Each article is fixed, pushed to Document360 as an updated draft, and answered in the thread. Threads are left OPEN for the reviewer to close once they've checked the change; add --resolve to close the ones fully handled. --answered lists the ones already waiting on your reviewers; --full re-reads every article instead of only the ones that can have changed."},{t:"cmd",name:"/inbox",s:"Review and act on messages left by your coding agent."},{t:"cmd",name:"/ask <question>",s:"Ask your developers something the code cannot tell you: why a feature exists, when to use it, whether it can be undone. Costs nothing and does not stop your work."}]},{key:"assistant",label:"Assistant",rows:[{t:"text",s:"D360-Writer is your writing assistant \u2014 it does the writing, you decide what gets written and when."},{t:"text",s:"Best for large or complex monorepos: focus it on one feature, component, or set of source folders."},{t:"gap"},{t:"sub",s:"Just ask"},{t:"good",s:'"Write the documentation for the Multiple JWT feature we shipped in the latest release."'},{t:"gap"},{t:"sub",s:"Or use the pipeline"},{t:"cmd",name:"/scope",s:"Focus D360-Writer on specific source folders (monorepos)."},{t:"cmd",name:"/draft <feature>",s:"Plan a feature's articles from its PRDs in .d360-writer/input/ + the code."},{t:"cmd",name:"/write",s:"Write the planned articles (--preview shows the plan + cost first)."},{t:"cmd",name:"/push",s:"Save the articles to Document360 as drafts. Requires an editorial review first \u2014 /review --fix."},{t:"cmd",name:"/push --preview <path>",s:"Show exactly what Document360 would receive for one article \u2014 details at the top and title removed, screenshots in place, links resolved. Sends nothing."},{t:"cmd",name:"/publish",s:"Take the drafts live to your readers, once they have been signed off."},{t:"cmd",name:"/status",s:"What stands between your work and your readers: which articles no editor has read, which break a blocking style rule, which would show placeholder text, and which are saved as drafts your readers cannot see yet. Each group names the one command that clears it. Costs nothing and calls nothing \u2014 run it before you publish, not after. --all shows every article gate by gate."},{t:"cmd",name:"/retire <path>",s:"Take an article off your readers: it stops being visible on Document360 and nothing offers to publish it again. Nothing is deleted \u2014 the article, its history and every reviewer comment stay, so a retirement is always reversible. Bare /retire lists what is worth retiring. /push puts one back."},{t:"cmd",name:"/untrack <path>",s:"Disown a page that came from Document360 but is not one of your docs. /catch-up and /pull import every article they find in your categories, which is usually what you want \u2014 this is how you say no to one. Nothing on Document360 changes and nothing is deleted: the markdown moves to .d360-writer/declined/, and the decision is recorded so no later run imports it again. Bare /untrack lists what you have declined; --undo puts one back."},{t:"cmd",name:"/reuse",s:'The terms, values and blocks your project already maintains on Document360, so your articles reference them instead of repeating them. A variable holds a value like your product name in one place: change it there and every article changes, and if the writer types it out by hand /lint flags it. A snippet does the same for a whole block, like who to contact when something goes wrong, and the writer references yours by name instead of writing its own version. A term is defined once and every article is checked for explaining it the first time it appears. Reading costs nothing; /reuse pull refreshes from Document360, and /reuse add variable <name> "<value>" creates one there (a snippet takes --desc "<one line>" too).'},{t:"cmd",name:"/reuse audit",s:"The occasional whole-knowledge-base pass: it reads every article, works out which values and blocks have to stay identical everywhere, and creates the ones you pick. The reading is free and it tells you what it found before spending anything. One editorial turn then judges it, because a count cannot tell duplication from two pages that genuinely need the same sentence. Nothing is created until you pick it. Takes a category to look at part of the knowledge base."},{t:"cmd",name:"/terms",s:'Is your knowledge base calling the same thing by the same name? It reads every article and reports terms spelled more than one way, short names that might be an abbreviation of a longer one, and titles out of step with the rest of their kind. This is the consistency an editor reading three articles at a time structurally cannot see. Costs nothing. --approve "<term>" records your choice so every later run enforces it.'}]},{key:"shots",label:"Screenshots",rows:[{t:"text",s:"Documenting a web app? D360-Writer marks where an image helps with a <!-- SCREENSHOT --> placeholder."},{t:"text",s:"The companion CLI d360-capture turns those into real, annotated screenshots by driving your app in a browser."},{t:"gap"},{t:"sub",s:"Flow"},{t:"step",n:1,s:"Write your docs as usual \u2014 placeholders are added where visuals help."},{t:"step",n:2,s:"Run /screenshot author --all to write the capture specs and refresh the capture checklist (data + test-ids the shots need)."},{t:"step",n:3,s:"Stage what the checklist asks for, then /screenshot capture \u2014 the writer drives a real browser that signs in, navigates and takes the shots."},{t:"step",n:4,s:"The callouts are drawn on straight after, no browser needed. /screenshot annotate finishes any that were left, and re-draws after you reword a callout or change the marker style."},{t:"step",n:5,s:"Each finished picture is then checked automatically: D360-Writer looks at it and marks it \u2714 verified, or flags what is wrong and fixes the spec for you."},{t:"step",n:6,s:"Run /push --screenshots \u2014 it re-saves the articles with finished shots so the images embed on Document360. Put a category in front (/push 04-publishing --screenshots) to re-save only that one."},{t:"gap"},{t:"sub",s:"One command family"},{t:"cmd",name:"/screenshot",s:"Bare: pick what to do next (author or capture, per shot)."},{t:"cmd",name:"/screenshot author",s:"Write capture specs (<id>, a path, or --all)."},{t:"cmd",name:"/screenshot capture",s:"Take the screenshots (<id>, a path, or --all; bare = pick ready specs)."},{t:"cmd",name:"/screenshot capture --failed",s:"Re-run just the shots that did not work \u2014 a failed run, or one that failed verification."},{t:"cmd",name:"/screenshot capture --blocked",s:"Re-run the shots waiting on app data, once you have staged it (a bulk run leaves them out by default)."},{t:"cmd",name:"/screenshot annotate",s:"Draw the callouts on shots already taken (<id>, a path, or --all). No browser and no sign-in \u2014 it re-uses the pictures you have, so it takes about a second each. --force re-draws ones that are already finished."},{t:"cmd",name:"/screenshot verify",s:"Look at the finished pictures and mark them verified (<id> for one). Runs automatically after a capture \u2014 run it yourself for shots taken before this existed, or after an interrupted run. No browser needed."},{t:"cmd",name:"/screenshot checklist",s:"What to stage + dev test-id requests (a report; auto-runs after bulk authoring)."},{t:"cmd",name:"/screenshot list",s:'Progress by chapter (\u2714 verified \xB7 \u25C8 to verify \xB7 \u25CF to annotate \xB7 \u25D0 ready \xB7 \u2691 rejected \xB7 \u2298 blocked \xB7 \u2717 broken \xB7 \u25CB to-spec) plus the grouped "stage this data" worklist; list <chapter#> or --detail drills in.'},{t:"gap"},{t:"sub",s:"What done means"},{t:"text",s:"\u2714 verified is the finish line. \u25CF to annotate means the browser has taken the shot but the callouts are not on it yet, so there is nothing to publish; \u25C8 to verify means the picture is finished and waiting to be looked at. It counts as done once it has been judged fit for the article."},{t:"gap"},{t:"sub",s:"When a shot does not produce a usable image"},{t:"text",s:"\u25CF to annotate means the shot was taken but the callouts were never drawn on \u2014 the browser work is already paid for, so /screenshot annotate finishes it in about a second."},{t:"text",s:"\u2298 blocked means your app lacked the data the shot needs \u2014 /screenshot list groups every blocker into the handful of things to go and stage."},{t:"text",s:"\u2717 broken means the spec itself is out of date. Re-author it with /screenshot <id> (which carries the error into the fix); re-running the capture would fail the same way."},{t:"text",s:"\u2691 rejected means the shot ran but the picture is wrong \u2014 an empty placeholder where content should be, a callout on the wrong element, the subject unreadable. The spec is usually fixed for you; /screenshot capture --failed re-shoots. /screenshot list --detail shows each reason."},{t:"gap"},{t:"dim",s:"One-time setup in the app repo: npm i -g document360-capture \xB7 d360-capture init \xB7 d360-capture auth (real login, once per machine)."}]},{key:"cmds",label:"Commands",rows:[{t:"dim",s:"Grouped by job. Type any command, or just talk to the agent."},{t:"gap"},{t:"sub",s:"Getting started"},{t:"cmd",name:"/init",s:"Set up Document360 (environment, sign-in, project, hand-off prompt)."},{t:"cmd",name:"/login",s:"Sign in to Document360. One sign-in per project, all kept \u2014 switching repos never signs you out of the others."},{t:"cmd",name:"/project",s:"Choose the Document360 project."},{t:"cmd",name:"/workspace",s:"Switch the workspace."},{t:"cmd",name:"/scope",s:"Choose which repo folders back the docs (monorepos)."},{t:"cmd",name:"/devhints",s:"Re-show the builder hand-off prompt."},{t:"gap"},{t:"sub",s:"Agentic \u2014 keep docs in sync"},{t:"cmd",name:"/roadmap",s:"The docs campaign, grouped by the part of the job: progress + the next step (`--all` also shows replaced steps, `done` marks the next step finished)."},{t:"cmd",name:"/catch-up",s:"Do all pending work \u2192 drafts (--push then saves them to Document360)."},{t:"cmd",name:"/share",s:"Exchange work with your team: get your builder's latest, send yours (automatic; `off` to stop)."},{t:"cmd",name:"/push",s:"Save to Document360 as drafts (--all or a path). Needs /review --fix first."},{t:"cmd",name:"/publish",s:"Take the drafts live to your readers (--all or a path). Lists them and asks first."},{t:"cmd",name:"/audit",s:"Preview what changed / what's missing."},{t:"cmd",name:"/pull",s:"Pull portal edits + new articles (--status: report only)."},{t:"cmd",name:"/resolve",s:"Resolve local\u2194portal conflicts (diff \u2192 keep local / take portal / skip \u2014 one at a time, or all at once)."},{t:"cmd",name:"/comments",s:"Reviewer comments on your articles \u2014 a table by article; pick one to read it, then answer (--resolve also closes the threads)."},{t:"cmd",name:"/inbox",s:"Act on your coding agent's messages."},{t:"cmd",name:"/ask",s:"Ask your developers what the code cannot answer."},{t:"gap"},{t:"sub",s:"Quality \u2014 before you publish"},{t:"cmd",name:"/style-guide",s:"Choose the writing rules your docs are held to \u2014 the Microsoft or Google style guide, or just the house rules. Adopting one records what your articles already contain, so nothing you have written suddenly breaks. `keep` settles on the rules you already have, so the question stops being asked. `pull` brings your Document360 project's own style guide down so the writer works to it; `learn` reads what your reviewers keep asking for and turns the general parts into standing rules the writer follows from then on; `compile` turns mechanical statements into checks and tells you which parts only an editor can judge."},{t:"cmd",name:"/lint",s:"The machine pass: leaked internals, invented commands, stale facts, AI-tells, em dashes, callout shape. Costs nothing to run; --fix has the agent correct what it found, which is a real pass over every article that has an issue. --blocked narrows both to the errors that block publishing, leaving warnings and suggestions alone."},{t:"cmd",name:"/review",s:"The editor pass: a senior editor reads the articles against the house style guide and judges what no rule can. --fix applies the corrections, --blocked narrows to the articles publishing is blocked on, --stale to the ones the current editor has not read. Required before anything goes live."},{t:"gap"},{t:"sub",s:"Assistant \u2014 directed writing"},{t:"cmd",name:"/discover",s:"Interview your builder \u2192 product brief that grounds the docs."},{t:"cmd",name:"/draft <feature>",s:"Plan a feature's articles from PRDs + code."},{t:"cmd",name:"/write",s:"Write the planned articles (--preview first)."},{t:"cmd",name:"/preview",s:"Render an article locally."},{t:"gap"},{t:"sub",s:"Screenshots"},{t:"cmd",name:"/screenshot",s:"The screenshot pipeline: bare = pick; author / capture / checklist / list."},{t:"gap"},{t:"sub",s:"Setup & health"},{t:"cmd",name:"/model",s:"Set the Claude model."},{t:"cmd",name:"/verbose",s:"Raw transcript: every tool call, path and full result (bare: toggle)."},{t:"cmd",name:"/profile",s:"Switch/create a Document360 connection."},{t:"cmd",name:"/mcp",s:"Connect MCP servers (Notion, Linear, \u2026)."},{t:"cmd",name:"/allow-prod",s:"Authorize writes to a production profile."},{t:"cmd",name:"/doctor",s:"Health check: auth, profile, workspace, API \u2014 and a real agent turn, so a setup that looks fine but cannot actually write anything is caught here rather than by an empty `/write`."},{t:"cmd",name:"/logs",s:"Shows the most recent problem d360-writer recorded, in full, and where the logs are kept. Run it whenever a command did nothing or something crashed: the reason is in there. Press `c` to copy it and send it to the team."},{t:"cmd",name:"/update",s:"Update D360-Writer."},{t:"cmd",name:"/feedback",s:"Copy logged D360-Writer feedback to your clipboard (relay it to the team)."},{t:"cmd",name:"/reset",s:"Delete all D360-Writer files (confirms first)."},{t:"gap"},{t:"sub",s:"Session"},{t:"cmd",name:"/resume",s:"Resume a saved session."},{t:"cmd",name:"/rename",s:"Name the session."},{t:"cmd",name:"/clear",s:"Clear the screen and reset the conversation (resumable)."},{t:"cmd",name:"/help",s:"Show this help."},{t:"cmd",name:"/exit",s:"Quit."}]}],Yf=24;function Wc(e){let t=e.filter(n=>n.t==="cmd").map(n=>n.name.length);return t.length===0?Yf:Math.min(Yf,Math.max(...t))}function Ji(e,t,n,o){let r=" ".repeat(2+n+1),s=Math.max(24,o-r.length-2),i=tn(t,s);return e.length>n?{nameCell:e,descLines:i,indent:r,nameOwnLine:!0}:{nameCell:e.padEnd(n),descLines:i,indent:r,nameOwnLine:!1}}function fx(e,t,n){switch(e.t){case"head":return e.s;case"text":return` ${e.s}`;case"dim":return` ${e.s}`;case"sub":return e.s;case"good":return` ${e.s}`;case"step":return` ${e.n}. ${e.s}`;case"cmd":{let o=Ji(e.name,e.s,t,n),r=o.descLines.slice(o.nameOwnLine?0:1).map(i=>`${o.indent}${i}`);return[o.nameOwnLine?` ${o.nameCell}`:` ${o.nameCell} ${o.descLines[0]??""}`,...r].join(`
|
|
301
|
+
`)}var yt=[{key:"start",label:"Getting Started",rows:[{t:"text",s:"D360-Writer reads your codebase, generates end-user documentation, and publishes it to Document360."},{t:"text",s:"It needs Claude access \u2014 a Claude subscription (default) or an ANTHROPIC_API_KEY."},{t:"gap"},{t:"sub",s:"Quick start"},{t:"step",n:1,s:"Run /init \u2014 pick your environment, sign in, choose your project & workspace. It ends by printing a one-paste hand-off prompt."},{t:"step",n:2,s:"Paste that prompt into your repo's coding agent (Claude Code / Codex / Cursor). Let it finish, then follow its closing line. (Re-show it: /devhints)"},{t:"step",n:3,s:"Run /doctor to confirm every check is green."},{t:"step",n:4,s:'Type "write the docs for this repository" and follow the prompts.'},{t:"gap"},{t:"dim",s:"That's it \u2014 D360-Writer drafts your docs without you writing a line."},{t:"gap"},{t:"sub",s:"Tips"},{t:"text",s:"Just talk to it \u2014 anything not starting with / goes to the agent."},{t:"dim",s:"/ browses commands \xB7 \u2191 recalls history \xB7 esc interrupts a run \xB7 ctrl+c twice exits"},{t:"dim",s:"tab runs the suggestion \xB7 1-9 pick from a list \xB7 c copies whatever was offered for copying"},{t:"dim",s:"shift+tab switches between auto and manual \u2014 in auto I run the next step myself (/auto)"}]},{key:"agentic",label:"Agentic",rows:[{t:"text",s:"Agentic Mode is designed for applications built using AI coding agents. D360-Writer works alongside your coding agent to continuously create and maintain your user documentation. Think of it as hiring a Technical Writer dedicated to your project."},{t:"gap"},{t:"text",s:"As your coding agent ships new features, it leaves documentation notes for D360-Writer. All you need to do is run the documentation loop."},{t:"gap"},{t:"sub",s:"Auto or manual"},{t:"text",s:"In MANUAL mode D360-Writer works out the next step, offers it, and waits for you to press tab. In AUTO mode it runs that step itself and keeps going \u2014 write, review, check the style, save, go live \u2014 so you can start a build and come back to a finished knowledge base."},{t:"cmd",name:"/auto on",s:"Let D360-Writer run its own next step. Shift+tab cycles between auto and manual at any time, and the mode is shown at the bottom of the screen."},{t:"cmd",name:"/auto --budget <amount>",s:"How much one automatic run may spend before it stops and reports. Every repo has a limit; the standard one is $200."},{t:"dim",s:" esc stops a running automatic pass. Everything already written, saved or published stays \u2014 stopping never undoes work."},{t:"text",s:"Auto mode changes who presses the key, not what is checked. The editorial pass still reads every article, and an article that fails a check is held back as a draft and named in the report rather than being pushed past it. Screenshots are never taken automatically: they need your product running with the right data on screen."},{t:"gap"},{t:"sub",s:"Every few days"},{t:"cmd",name:"/catch-up",s:"Analyze the latest code changes, read the notes left by your coding agent, and update the documentation. Everything lands as a draft."},{t:"cmd",name:"/review --fix",s:"A senior editor reads every article and applies its fixes. Required before anything goes live \u2014 these were written by an AI."},{t:"cmd",name:"/review --blocked --fix",s:"The same editorial pass over only the articles publishing is blocked on \u2014 after a run that rewrote two of forty, this reads those two."},{t:"cmd",name:"/review --stale --fix",s:"The same pass over every article the current editor has not read yet \u2014 never reviewed, or reviewed before the editor last improved. Run it twice and the second run does nothing, so it is the safe way to catch a knowledge base up."},{t:"cmd",name:"/push --all",s:"Save the reviewed articles to Document360, as drafts."},{t:"cmd",name:"/publish --all",s:"Take those drafts live to your readers \u2014 it lists what will become visible and asks first."},{t:"dim",s:" (/catch-up --push runs the writing and the save back-to-back; going live stays a deliberate step)"},{t:"gap"},{t:"sub",s:"Whenever you need"},{t:"cmd",name:"/discover",s:"Interview your coding agent to understand the product (what it is, who it's for, the core idea, common misconceptions) \u2014 pins a product brief that grounds every article. Runs before writing a repo's docs; loops until confident."},{t:"cmd",name:"/audit",s:"Preview what has changed and identify missing documentation, without writing or publishing anything."},{t:"cmd",name:"/pull",s:"Bring Document360 down \u2014 pull edits made in the portal and import any new portal articles."},{t:"cmd",name:"/resolve",s:"Resolve conflicts when an article was edited both here and in the portal \u2014 review the diff, then keep yours, take the portal's, or skip."},{t:"cmd",name:"/comments",s:"The comments reviewers left on passages of your articles in the Document360 portal. You get a table \u2014 category, article, how many comments \u2014 and pick a number to read that article's comments in full (the highlighted passage beside what they said), then answer them or go back to the list. `a` answers everything. Each article is fixed, pushed to Document360 as an updated draft, and answered in the thread. Threads are left OPEN for the reviewer to close once they've checked the change; add --resolve to close the ones fully handled. --answered lists the ones already waiting on your reviewers; --full re-reads every article instead of only the ones that can have changed."},{t:"cmd",name:"/inbox",s:"Review and act on messages left by your coding agent."},{t:"cmd",name:"/ask <question>",s:"Ask your developers something the code cannot tell you: why a feature exists, when to use it, whether it can be undone. Costs nothing and does not stop your work."}]},{key:"assistant",label:"Assistant",rows:[{t:"text",s:"D360-Writer is your writing assistant \u2014 it does the writing, you decide what gets written and when."},{t:"text",s:"Best for large or complex monorepos: focus it on one feature, component, or set of source folders."},{t:"gap"},{t:"sub",s:"Just ask"},{t:"good",s:'"Write the documentation for the Multiple JWT feature we shipped in the latest release."'},{t:"gap"},{t:"sub",s:"Or use the pipeline"},{t:"cmd",name:"/scope",s:"Focus D360-Writer on specific source folders (monorepos)."},{t:"cmd",name:"/draft <feature>",s:"Plan a feature's articles from its PRDs in .d360-writer/input/ + the code."},{t:"cmd",name:"/write",s:"Write the planned articles (--preview shows the plan + cost first)."},{t:"cmd",name:"/push",s:"Save the articles to Document360 as drafts. Requires an editorial review first \u2014 /review --fix."},{t:"cmd",name:"/push --preview <path>",s:"Show exactly what Document360 would receive for one article \u2014 details at the top and title removed, screenshots in place, links resolved. Sends nothing."},{t:"cmd",name:"/publish",s:"Take the drafts live to your readers, once they have been signed off."},{t:"cmd",name:"/status",s:"What stands between your work and your readers: which articles no editor has read, which break a blocking style rule, which would show placeholder text, and which are saved as drafts your readers cannot see yet. Each group names the one command that clears it. Costs nothing and calls nothing \u2014 run it before you publish, not after. --all shows every article gate by gate."},{t:"cmd",name:"/retire <path>",s:"Take an article off your readers: it stops being visible on Document360 and nothing offers to publish it again. Nothing is deleted \u2014 the article, its history and every reviewer comment stay, so a retirement is always reversible. Bare /retire lists what is worth retiring. /push puts one back."},{t:"cmd",name:"/untrack <path>",s:"Disown a page that came from Document360 but is not one of your docs. /catch-up and /pull import every article they find in your categories, which is usually what you want \u2014 this is how you say no to one. Nothing on Document360 changes and nothing is deleted: the markdown moves to .d360-writer/declined/, and the decision is recorded so no later run imports it again. Bare /untrack lists what you have declined; --undo puts one back."},{t:"cmd",name:"/reuse",s:'The terms, values and blocks your project already maintains on Document360, so your articles reference them instead of repeating them. A variable holds a value like your product name in one place: change it there and every article changes, and if the writer types it out by hand /lint flags it. A snippet does the same for a whole block, like who to contact when something goes wrong, and the writer references yours by name instead of writing its own version. A term is defined once and every article is checked for explaining it the first time it appears. Reading costs nothing; /reuse pull refreshes from Document360, and /reuse add variable <name> "<value>" creates one there (a snippet takes --desc "<one line>" too).'},{t:"cmd",name:"/reuse audit",s:"The occasional whole-knowledge-base pass: it reads every article, works out which values and blocks have to stay identical everywhere, and creates the ones you pick. The reading is free and it tells you what it found before spending anything. One editorial turn then judges it, because a count cannot tell duplication from two pages that genuinely need the same sentence. Nothing is created until you pick it. Takes a category to look at part of the knowledge base."},{t:"cmd",name:"/terms",s:'Is your knowledge base calling the same thing by the same name? It reads every article and reports terms spelled more than one way, short names that might be an abbreviation of a longer one, and titles out of step with the rest of their kind. This is the consistency an editor reading three articles at a time structurally cannot see. Costs nothing. --approve "<term>" records your choice so every later run enforces it.'}]},{key:"shots",label:"Screenshots",rows:[{t:"text",s:"Documenting a web app? D360-Writer marks where an image helps with a <!-- SCREENSHOT --> placeholder."},{t:"text",s:"The companion CLI d360-capture turns those into real, annotated screenshots by driving your app in a browser."},{t:"gap"},{t:"sub",s:"Flow"},{t:"step",n:1,s:"Write your docs as usual \u2014 placeholders are added where visuals help."},{t:"step",n:2,s:"Run /screenshot author --all to write the capture specs and refresh the capture checklist (data + test-ids the shots need)."},{t:"step",n:3,s:"Stage what the checklist asks for, then /screenshot capture \u2014 the writer drives a real browser that signs in, navigates and takes the shots."},{t:"step",n:4,s:"The callouts are drawn on straight after, no browser needed. /screenshot annotate finishes any that were left, and re-draws after you reword a callout or change the marker style."},{t:"step",n:5,s:"Each finished picture is then checked automatically: D360-Writer looks at it and marks it \u2714 verified, or flags what is wrong and fixes the spec for you."},{t:"step",n:6,s:"Run /push --screenshots \u2014 it re-saves the articles with finished shots so the images embed on Document360. Put a category in front (/push 04-publishing --screenshots) to re-save only that one."},{t:"gap"},{t:"sub",s:"One command family"},{t:"cmd",name:"/screenshot",s:"Bare: pick what to do next (author or capture, per shot)."},{t:"cmd",name:"/screenshot author",s:"Write capture specs (<id>, a path, or --all)."},{t:"cmd",name:"/screenshot capture",s:"Take the screenshots (<id>, a path, or --all; bare = pick ready specs)."},{t:"cmd",name:"/screenshot capture --failed",s:"Re-run just the shots that did not work \u2014 a failed run, or one that failed verification."},{t:"cmd",name:"/screenshot capture --blocked",s:"Re-run the shots waiting on app data, once you have staged it (a bulk run leaves them out by default)."},{t:"cmd",name:"/screenshot annotate",s:"Draw the callouts on shots already taken (<id>, a path, or --all). No browser and no sign-in \u2014 it re-uses the pictures you have, so it takes about a second each. --force re-draws ones that are already finished."},{t:"cmd",name:"/screenshot verify",s:"Look at the finished pictures and mark them verified (<id> for one). Runs automatically after a capture \u2014 run it yourself for shots taken before this existed, or after an interrupted run. No browser needed."},{t:"cmd",name:"/screenshot checklist",s:"What to stage + dev test-id requests (a report; auto-runs after bulk authoring)."},{t:"cmd",name:"/screenshot list",s:'Progress by chapter (\u2714 verified \xB7 \u25C8 to verify \xB7 \u25CF to annotate \xB7 \u25D0 ready \xB7 \u2691 rejected \xB7 \u2298 blocked \xB7 \u2717 broken \xB7 \u25CB to-spec) plus the grouped "stage this data" worklist; list <chapter#> or --detail drills in.'},{t:"gap"},{t:"sub",s:"What done means"},{t:"text",s:"\u2714 verified is the finish line. \u25CF to annotate means the browser has taken the shot but the callouts are not on it yet, so there is nothing to publish; \u25C8 to verify means the picture is finished and waiting to be looked at. It counts as done once it has been judged fit for the article."},{t:"gap"},{t:"sub",s:"When a shot does not produce a usable image"},{t:"text",s:"\u25CF to annotate means the shot was taken but the callouts were never drawn on \u2014 the browser work is already paid for, so /screenshot annotate finishes it in about a second."},{t:"text",s:"\u2298 blocked means your app lacked the data the shot needs \u2014 /screenshot list groups every blocker into the handful of things to go and stage."},{t:"text",s:"\u2717 broken means the spec itself is out of date. Re-author it with /screenshot <id> (which carries the error into the fix); re-running the capture would fail the same way."},{t:"text",s:"\u2691 rejected means the shot ran but the picture is wrong \u2014 an empty placeholder where content should be, a callout on the wrong element, the subject unreadable. The spec is usually fixed for you; /screenshot capture --failed re-shoots. /screenshot list --detail shows each reason."},{t:"gap"},{t:"dim",s:"One-time setup in the app repo: npm i -g document360-capture \xB7 d360-capture init \xB7 d360-capture auth (real login, once per machine)."}]},{key:"cmds",label:"Commands",rows:[{t:"dim",s:"Every command, one line each \u2014 type / for the full usage."},{t:"dim",s:"The tabs above teach the ones worth more than a line."},{t:"gap"},{t:"sub",s:"Getting started"},{t:"cmd",name:"/init",s:"Set up Document360 and the coding-agent hand-off."},{t:"cmd",name:"/login",s:"Sign in \u2014 one sign-in per project, all kept."},{t:"cmd",name:"/project",s:"Choose the Document360 project."},{t:"cmd",name:"/workspace",s:"Switch the workspace."},{t:"cmd",name:"/scope",s:"Choose which repo folders back the docs."},{t:"cmd",name:"/genre",s:"What kind of product this is \u2014 picks the writing guide."},{t:"cmd",name:"/devhints",s:"Re-show the prompt to paste into your coding agent."},{t:"cmd",name:"/mcp",s:"Connect MCP servers (Notion, Linear, \u2026)."},{t:"gap"},{t:"sub",s:"Agentic \u2014 keep docs in sync"},{t:"cmd",name:"/roadmap",s:"The docs campaign: progress and the next step."},{t:"cmd",name:"/catch-up",s:"Do all pending work \u2192 drafts. --push also saves them."},{t:"cmd",name:"/auto",s:"Run the next step myself instead of waiting for you."},{t:"cmd",name:"/audit",s:"What changed in the code, what's missing from the docs."},{t:"cmd",name:"/inbox",s:"Act on your coding agent's messages."},{t:"cmd",name:"/ask",s:"Ask your developers what the code cannot answer."},{t:"cmd",name:"/share",s:"Get your builder's latest, send yours (automatic)."},{t:"gap"},{t:"sub",s:"Assistant \u2014 directed writing"},{t:"cmd",name:"/discover",s:"Interview your builder \u2192 a product brief for the docs."},{t:"cmd",name:"/draft <feature>",s:"Plan a feature's articles from its PRDs and the code."},{t:"cmd",name:"/write",s:"Write the planned articles. --preview costs first."},{t:"cmd",name:"/preview",s:"Render an article locally."},{t:"cmd",name:"/reorder",s:"Put a category's articles in the best reading order."},{t:"cmd",name:"/structure",s:"The reading order, as it will appear on Document360."},{t:"gap"},{t:"sub",s:"Quality \u2014 before you publish"},{t:"cmd",name:"/style-guide",s:"Choose the writing rules your docs are held to."},{t:"cmd",name:"/lint",s:"The machine pass over those rules. Free. --fix corrects."},{t:"cmd",name:"/review",s:"The editor pass. Required before anything goes live."},{t:"cmd",name:"/terms",s:"Is the same thing called the same name everywhere? Free."},{t:"cmd",name:"/reuse",s:"The terms, values and blocks kept on Document360."},{t:"gap"},{t:"sub",s:"Publish & sync"},{t:"cmd",name:"/status",s:"What stands between your work and your readers. Free."},{t:"cmd",name:"/push",s:"Save your articles to Document360 as drafts."},{t:"cmd",name:"/publish",s:"Take the drafts live to your readers. Asks first."},{t:"cmd",name:"/pull",s:"Portal edits + new portal articles. --status: report only."},{t:"cmd",name:"/resolve",s:"Settle a local\u2194portal conflict: keep, take, or skip."},{t:"cmd",name:"/comments",s:"Reviewer comments on your articles \u2014 read and answer."},{t:"cmd",name:"/retire",s:"Take an article off your readers. Nothing is deleted."},{t:"cmd",name:"/untrack",s:"Disown an imported page that is not one of your docs."},{t:"gap"},{t:"sub",s:"Screenshots"},{t:"cmd",name:"/screenshot",s:"The screenshot pipeline. Bare: pick what to do next."},{t:"dim",s:" author \xB7 capture \xB7 annotate \xB7 verify \xB7 checklist \xB7 list"},{t:"gap"},{t:"sub",s:"Setup & health"},{t:"cmd",name:"/model",s:"Set the Claude model."},{t:"cmd",name:"/verbose",s:"Raw transcript: every tool call, path and result."},{t:"cmd",name:"/profile",s:"Switch or create a Document360 connection."},{t:"cmd",name:"/allow-prod",s:"Authorize writes to a production profile."},{t:"cmd",name:"/doctor",s:"Health check \u2014 including a real agent turn."},{t:"cmd",name:"/logs",s:"The last problem, in full. Press `c` to copy it."},{t:"cmd",name:"/update",s:"Update D360-Writer, and d360-capture if installed."},{t:"cmd",name:"/feedback",s:"Copy this repo's open feedback, to relay to the team."},{t:"cmd",name:"/logout",s:"Sign out of this project. --all-projects: all of them."},{t:"cmd",name:"/reset",s:"Delete all D360-Writer files (confirms first)."},{t:"gap"},{t:"sub",s:"Session"},{t:"cmd",name:"/resume",s:"Resume a saved session."},{t:"cmd",name:"/rename",s:"Name the session."},{t:"cmd",name:"/clear",s:"Clear the screen and reset the conversation."},{t:"cmd",name:"/help",s:"Show this help."},{t:"cmd",name:"/exit",s:"Quit."}]}],Yf=24;function Wc(e){let t=e.filter(n=>n.t==="cmd").map(n=>n.name.length);return t.length===0?Yf:Math.min(Yf,Math.max(...t))}function Ji(e,t,n,o){let r=" ".repeat(2+n+1),s=Math.max(24,o-r.length-2),i=tn(t,s);return e.length>n?{nameCell:e,descLines:i,indent:r,nameOwnLine:!0}:{nameCell:e.padEnd(n),descLines:i,indent:r,nameOwnLine:!1}}function fx(e,t,n){switch(e.t){case"head":return e.s;case"text":return` ${e.s}`;case"dim":return` ${e.s}`;case"sub":return e.s;case"good":return` ${e.s}`;case"step":return` ${e.n}. ${e.s}`;case"cmd":{let o=Ji(e.name,e.s,t,n),r=o.descLines.slice(o.nameOwnLine?0:1).map(i=>`${o.indent}${i}`);return[o.nameOwnLine?` ${o.nameCell}`:` ${o.nameCell} ${o.descLines[0]??""}`,...r].join(`
|
|
302
302
|
`)}case"gap":return""}}function zf(e){let n=[e?`document360-writer \u2014 help \xB7 v${e.version} \xB7 engine v${e.engineVersion}`:"document360-writer \u2014 help",""];for(let o of yt){n.push(`\u2500\u2500 ${o.label} \u2500\u2500`,"");let r=Wc(o.rows);for(let s of o.rows)n.push(fx(s,r,100));n.push("")}return n.push("Tip: anything not starting with / goes to the agent."),n}function Bc(e,t){return tn(e,Math.max(10,Math.max(20,t)-2))}function mx(e,t,n){let o=Math.max(20,n),r=s=>Math.max(1,Bc(s,o).length);switch(e.t){case"gap":return 1;case"cmd":{let s=Ji(e.name,e.s,t,o);return s.nameOwnLine?1+s.descLines.length:1+Math.max(0,s.descLines.length-1)}case"step":return r(`${e.n}. ${e.s}`);default:return r(e.s)}}function Xf(e,t){let n=Math.min(Math.max(0,t.scroll),Math.max(0,e.length-1)),o=[],r=0;for(let s=n;s<e.length;s++){let i=mx(e[s],t.nameW,t.width);if(r+i>t.budget&&o.length>0)break;o.push(e[s]),r+=i}return{visible:o,above:n,below:e.length-n-o.length,start:n}}function Qf(e){let n=r=>Math.max(1,Math.ceil(r/Math.max(10,e.inner))),o=n(e.barWidth)+n(e.barWidth)+1+1+n(e.footerWidth)+7;return Math.max(3,Math.min(22,(e.rows||24)-o))}async function Hc(e=[],t){console.log("");let n=t?.version?{version:t.version,engineVersion:gx}:void 0;for(let o of zf(n))console.log(o);return console.log(""),console.log("Reporting a problem? Run `/logs` \u2014 it shows the last problem and offers to copy it."),console.log(""),{kind:"continue"}}ie();import{getSession as wx}from"document360-engine";async function Jf(e,t){let n=t.currentUuid(),o=n?wx(n):void 0;return console.log(o?p(`
|
|
303
303
|
(conversation reset \u2014 "${o.name}" is still available via /resume)
|
|
304
304
|
`):p(`
|
|
@@ -334,7 +334,7 @@ ${o.body}`:"","",`Close the loop IN THE SAME TURN the step's goal state is reach
|
|
|
334
334
|
`)}async function Ug(e,t){let n=t?.cwd??process.cwd();return oS(n)?{kind:"forward-to-agent",prompt:Yu(),display:"/discover"}:(console.log(_("No d360-writer config here. Run /init first.")),{kind:"continue"})}ie();import{checkbox as rS}from"@inquirer/prompts";import{inventoryRepo as sS,readProjectConfig as Wg,writeProjectConfig as iS}from"document360-engine";function zu(e,t){let n=Wg(e);n&&(n.authoritativeSourceFiles=t,iS(n,e))}function La(e){let t=[`${e.fileCount}${e.fileCount>=5e3?"+":""} files`];return e.stacks.length&&t.push(e.stacks.join("+")),t.push(e.reason),t.join(" \xB7 ")}async function Bg(e,t){let n=t?.cwd??process.cwd();if(!Wg(n))return console.log(_("No .d360-writer/config.json here. Run /init first.")),{kind:"continue"};let o=sS(n);if(o.length===0)return console.log(p('No candidate source folders found. Set "authoritativeSourceFiles" in .d360-writer/config.json manually.')),{kind:"continue"};if(!process.stdin.isTTY||!t){console.log(""),console.log(ee("Recommended documentation scope (run /scope in the REPL to choose):"));for(let s of o)console.log(` ${s.recommended?"\u25C9":"\u25CB"} ${s.path} ${p(La(s))}`);return console.log(""),{kind:"continue"}}let r;try{r=await t.withPausedInput(()=>rS({message:"Which folders back the user docs? (space toggles, enter confirms)",choices:o.map(s=>({name:`${s.path} (${La(s)})`,value:s.path,checked:s.recommended})),pageSize:20}))}catch{return console.log(p("Cancelled.")),{kind:"continue"}}if(r.length===0)return console.log(p("Nothing selected \u2014 scope unchanged.")),{kind:"continue"};zu(n,r),console.log(X(`\u2713 Scoped to ${r.length} folder(s) \u2014 written to .d360-writer/config.json`));for(let s of r)console.log(` ${s}`);return console.log(""),{kind:"continue"}}ie();import{confirm as gS}from"@inquirer/prompts";import{applyPull as Zu,computeSyncStatus as ew,compareRemoteSources as wS,planPull as tw,planImport as yS,applyImport as bS,establishBaselines as kS,rebaselineServerMeta as $S,D360AuthError as vS}from"document360-engine";ie();var Hg=[{status:"conflict",header:"Changed in both places \u2014 pick a direction (/pull <path> or /push <path>):",paint:_,mark:"!"},{status:"local-ahead",header:"Changed here \u2014 push with /push <path>:",paint:K,mark:"M"},{status:"remote-ahead",header:"Changed on Document360 \u2014 pull with /pull <path> (or just /pull):",paint:ee,mark:"M"},{status:"deleted-local",header:"Removed locally \u2014 still on Document360 (pull to restore, or leave it):",paint:_,mark:"D"},{status:"deleted-remote",header:"Gone from Document360 \u2014 still here (publish to recreate, or delete the file):",paint:_,mark:"D"},{status:"orphaned",header:"Gone from both sides \u2014 these tracking entries point at nothing now:",paint:Re,mark:"X"},{status:"untracked-local",header:"New here \u2014 not on Document360 yet (publish to add them):",paint:p,mark:"?"},{status:"untracked-remote",header:"On Document360 \u2014 not in your local docs yet (run /pull to import them):",paint:p,mark:"?"},{status:"unknown-base",header:"Tracked, not yet compared \u2014 run /pull --baseline to set a baseline (or the next publish/pull sets one):",paint:Re,mark:"\xB7"}];function aS(e){return e.path?e.path:`${e.title??"(untitled)"} ${Re(`[${e.articleId}]`)}`}function lS(e){let t=e/1e3;if(t<60)return`${t.toFixed(1)}s`;let n=Math.floor(t/60);return`${n}m ${Math.round(t-n*60)}s`}function Oa(e,t){let n=[],o=e.entries.filter(m=>m.link);if(o.length>0){n.push(""),n.push(Qe("Already on Document360, just not linked \u2014 ask me to link these (a republish would duplicate them):"));for(let m of o)n.push(` ${K("~")} ${K(m.path??"")} ${Re(`\u2192 "${m.link.title}"`)}`)}for(let m of Hg){let w=e.entries.filter(R=>R.status===m.status&&!(m.status==="untracked-local"&&R.link));if(w.length!==0){n.push(""),n.push(Qe(m.header));for(let R of w)n.push(` ${m.paint(m.mark)} ${m.paint(aS(R))}${R.detail?Re(` (${R.detail})`):""}`)}}let r=e.counts["in-sync"]??0,s=e.entries.length;n.push("");let i={"in-sync":"up to date","local-ahead":"changed here","remote-ahead":"changed on Document360",conflict:"changed both places","untracked-local":"new here","untracked-remote":"only on Document360","deleted-local":"removed locally","deleted-remote":"gone from Document360",orphaned:"gone both sides","unknown-base":"not yet compared"},a=o.length,l=s===r?X("\u2713 everything up to date"):[r>0?X(`\u2713 ${r} up to date`):null,a>0?`${a} to link`:null,...Hg.map(m=>{let w=(e.counts[m.status]??0)-(m.status==="untracked-local"?a:0);return w>0?`${w} ${i[m.status]}`:null})].filter(Boolean).join(Re(" \xB7 ")),c=t!==void 0?` \xB7 synced in ${lS(t)}`:"";return n.push(l+Re(` \xB7 ${s} tracked+seen \xB7 profile "${e.profile}" \xB7 docs root ${e.docsRoot}/${c}`)),n}import{structuredPatch as cS}from"diff";import Gg from"picocolors";var uS=80,Rr=400,dS="\x1B[48;2;74;28;28m",pS="\x1B[48;2;24;66;24m",qg="\x1B[49m",Xu=e=>String(e).padStart(5);function Vg(e,t){let n=Math.min(e.length,t.length),o=0;for(;o<n&&e[o]===t[o];)o++;return o}function Kg(e,t,n){if(n===void 0||n<t-4)return e.slice(0,t);let o=Math.max(0,n-12);return`\u2026${e.slice(o).slice(0,Math.max(8,t-1))}`}function It(e,t,n,o=uS){let r=A=>{let $=A.replace(/\r\n/g,`
|
|
335
335
|
`);return $.endsWith(`
|
|
336
336
|
`)||$===""?$:$+`
|
|
337
|
-
`},s=r(e),i=r(t);if(s===i)return null;let a=Math.max(20,n-10),l=cS("a","b",s,i,"","",{context:3}),c=0,m=0,w=[];l.hunks.forEach((A,$)=>{$>0&&w.push(Gg.gray(" \u2026"));let P=A.oldStart,S=A.newStart,D=[],I=[],Y=()=>{for(let G=0;G<D.length;G++){let W=G<I.length?Vg(D[G],I[G]):void 0;m++,w.push(`${dS}${Xu(P++)} - ${Kg(D[G],a,W)}${qg}`)}for(let G=0;G<I.length;G++){let W=G<D.length?Vg(D[G],I[G]):void 0;c++,w.push(`${pS}${Xu(S++)} + ${Kg(I[G],a,W)}${qg}`)}D=[],I=[]};for(let G of A.lines){let W=G[0],de=G.slice(1);W==="-"?D.push(de):W==="+"?I.push(de):(Y(),w.push(Gg.gray(Xu(S))+" "+de.slice(0,a)),P++,S++)}Y()});let R=Math.max(1,o),E=w.slice(0,R);return{added:c,removed:m,lines:E,hidden:Math.max(0,w.length-R)}}import{select as Qg}from"@inquirer/prompts";import{applyPull as Jg,computeSyncStatus as hS,isPullWouldBlankError as zg,localArticleTitle as fS,planPull as Xg}from"document360-engine";function _a(){return["Document360 will not give this article back as markdown \u2014 it is not on the Markdown (Editor 3.0) editor, so there is no portal copy to show you or to take.","Your local copy is intact, and publishing it over the portal settles this. Skipping leaves the article flagged, and it will be waiting again next time."]}function Ma(){return"Publish your copy over the portal (settles it) \xB7 Skip?"}function Yg(){return"(l publish yours \xB7 s skip \u2014 anything else stops, keeping your answers so far)"}function Fa(e){if(e.length===0)return[];let t=e.length,n=e.slice(0,3).join(", ")+(t>3?`, +${t-3} more`:"");return[`${t} article${t===1?"":"s"} stay${t===1?"s":""} flagged \u2014 Document360 cannot hand ${t===1?"its":"their"} markdown back: ${n}`,"Two ways out: run /resolve and press l to publish your copy over the portal, or ask your Document360 admin to move the article back to the Markdown (Editor 3.0) editor, then run /resolve again."]}function Qu(e){return`Cannot take the portal copy of ${e} \u2014 Document360 will not return it as markdown. Left as it was.`}ie();var Ua=e=>e===1?"":"s";async function Ju(e,t,n=1){console.log(""),console.log(`${ee("\u25CF")} ${Qe(t.title)} ${Re(`(${t.path})`)}`);for(let i of t.notes)console.log(K(` \u26A0 ${i}`));let o=It(t.oldContent,t.newContent,Math.max(40,(process.stdout.columns??80)-1),Rr);if(o){console.log(Re(` \u23BF local \u2192 portal: +${o.added} / -${o.removed} line${Ua(o.added+o.removed)}`));for(let i of o.lines)console.log(i);o.hidden>0&&console.log(p(` \u2026 +${o.hidden} more diff lines`))}else console.log(p(" Local already matches the portal content \u2014 resolving just advances the sync base."));let r=n>1,s=await e.withPausedInput(()=>Qg({message:`Conflict \u2014 ${t.path}${r?` (${n} left)`:""}`,choices:[{name:"Keep local (publish \u2014 overwrites the portal edit)",value:"local"},{name:"Take portal (overwrites your local edits)",value:"portal"},{name:"Skip (leave it a conflict)",value:"skip"},...r?[{name:`Keep local for ALL ${n} remaining`,value:"local-all"},{name:`Take portal for ALL ${n} remaining`,value:"portal-all"},{name:`Skip ALL ${n} remaining`,value:"skip-all"}]:[]]})).catch(()=>"skip");return s==="portal"?(Jg({cwd:e.cwd,profileName:e.profileName},t),console.log(X(" \u2713 Took the portal copy (local overwritten, sync base advanced).")),"took-portal"):s==="local"?"keep-local":s==="skip"?(console.log(p(" Skipped \u2014 still a conflict.")),"skipped"):s==="local-all"?"keep-local-all":s==="portal-all"?"took-portal-all":"skipped-all"}async function mS(e,t){console.log(""),console.log(`${ee("\u25CF")} ${Qe(fS(e.cwd,t)??t)} ${Re(`(${t})`)}`);for(let o of _a())console.log(K(` \u26A0 ${o}`));return await e.withPausedInput(()=>Qg({message:Ma(),choices:[{name:"Publish your copy over the portal (settles it)",value:"local"},{name:"Skip (leave it a conflict)",value:"skip"}]})).catch(()=>"skip")==="local"?"keep-local":(console.log(p(" Skipped \u2014 still a conflict.")),"skipped")}async function Zg(e,t){let n=Je(t.cwd,t.profileName);if(!n.ok)return console.log(_(n.reason)),{kind:"continue"};let o=e.find(r=>!r.startsWith("--"))?.replace(/\\/g,"/");try{let s=(await hS({cwd:t.cwd,profileName:t.profileName})).entries.filter(l=>l.status==="conflict"&&l.path).map(l=>l.path);if(o){if(!s.includes(o))return console.log(X(`${o} isn't in conflict \u2014 nothing to resolve.`)),{kind:"continue"};s=[o]}if(s.length===0)return console.log(X("\u2713 No conflicts \u2014 local and Document360 agree on every tracked article.")),{kind:"continue"};let i=[],a=[];for(let[l,c]of s.entries()){let m;try{m=await Xg({cwd:t.cwd,profileName:t.profileName,relPath:c})}catch(E){if(!zg(E))throw E;await mS(t,c)==="keep-local"?i.push(c):a.push(c);continue}let w=await Ju(t,m,s.length-l);if(w==="keep-local"&&i.push(c),!w.endsWith("-all"))continue;let R=s.slice(l);if(w==="keep-local-all")i.push(...R),console.log(p(` Keeping local for all ${R.length} \u2014 publishing them over the portal.`));else if(w==="took-portal-all"){let E=0;for(let A of R)try{Jg({cwd:t.cwd,profileName:t.profileName},await Xg({cwd:t.cwd,profileName:t.profileName,relPath:A})),E++}catch($){console.log(_(` \u2717 ${A}: ${$.message}`)),zg($)&&a.push(A)}console.log(X(` \u2713 Took the portal copy of ${E} article${Ua(E)} (local overwritten, sync base advanced).`))}else console.log(p(` Skipped all ${R.length} \u2014 still conflict${Ua(R.length)}.`));break}for(let l of Fa(a))console.log(K(`\u26A0 ${l}`));if(i.length>0)return console.log(p(`Publishing ${i.length} kept-local article${Ua(i.length)} over the portal\u2026`)),await zt(t,i,0,{allowUnpullable:!0})}catch(r){console.log(_(`Resolve failed: ${r.message}`))}return{kind:"continue"}}async function nw(e,t){let n=Je(t.cwd,t.profileName);if(!n.ok)return console.log(_(n.reason)),{kind:"continue"};let o=e.find(r=>r.startsWith("--"));try{if(o==="--status")return await xS(t),{kind:"continue"};if(o==="--baseline")return await RS(t,e.filter(i=>i!=="--baseline")),{kind:"continue"};if(o==="--compare")return await SS(t),{kind:"continue"};if(o)return console.log(_(`Unknown option: ${o}`)),console.log(p("Usage: /pull pull portal edits + import new portal articles")),console.log(p(" /pull <path> pull one article (or resolve a conflict)")),console.log(p(" /pull --status drift report only (no writes)")),console.log(p(" /pull --baseline set a sync baseline for not-yet-compared articles")),{kind:"continue"};let r=e[0];if(r)return await CS(t,r),{kind:"continue"};console.log(p("Pulling changes from Document360\u2026"));let s=await ed({cwd:t.cwd,profileName:t.profileName});for(let i of td(s))console.log(i.tone==="ok"?X(i.text):i.tone==="warn"?K(i.text):p(i.text));console.log("")}catch(r){r instanceof vS?console.log(_(r.message)):console.log(_(`Pull failed: ${r.message}`))}return{kind:"continue"}}async function ed(e){let t=await ew({cwd:e.cwd,profileName:e.profileName}),n={pulled:[],imported:[],conflicts:[],toPublish:0,failed:[]};for(let o of t.entries)if(o.status==="remote-ahead"&&o.path)try{let r=await tw({cwd:e.cwd,profileName:e.profileName,relPath:o.path});Zu({cwd:e.cwd,profileName:e.profileName},r),n.pulled.push(o.path)}catch(r){n.failed.push({ref:o.path,error:r.message})}else if(o.status==="untracked-remote"&&o.articleId)try{let r=await yS({cwd:e.cwd,profileName:e.profileName,articleId:o.articleId});bS({cwd:e.cwd,profileName:e.profileName},r),n.imported.push({title:r.title,path:r.path})}catch(r){n.failed.push({ref:o.title??o.articleId,error:r.message})}else o.status==="conflict"&&o.path?n.conflicts.push(o.path):(o.status==="local-ahead"||o.status==="untracked-local"&&!o.link)&&o.path&&n.toPublish++;return n}var Wa=(e,t=5)=>`${e.slice(0,t).join(", ")}${e.length>t?`, +${e.length-t} more`:""}`,Co=e=>e===1?"":"s";function td(e){let t=[];return e.pulled.length&&t.push({tone:"ok",text:`\u2713 Pulled ${e.pulled.length} portal edit${Co(e.pulled.length)} into local: ${Wa(e.pulled)}`}),e.imported.length&&t.push({tone:"ok",text:`\u2713 Imported ${e.imported.length} new portal article${Co(e.imported.length)}: ${Wa(e.imported.map(n=>n.path))}`}),e.toPublish&&t.push({tone:"info",text:`\u2139 ${e.toPublish} article${Co(e.toPublish)} changed locally \u2014 /push saves them to Document360.`}),e.conflicts.length&&t.push({tone:"warn",text:`\u26A0 ${e.conflicts.length} article${Co(e.conflicts.length)} changed both here and on Document360 \u2014 left untouched. Resolve them: /resolve (or /push <path> to keep yours, /pull <path> to take the portal's). (${Wa(e.conflicts)})`}),e.failed.length&&t.push({tone:"warn",text:`\u26A0 ${e.failed.length} item${Co(e.failed.length)} failed: ${Wa(e.failed.map(n=>n.ref))}`}),t.length===0&&t.push({tone:"ok",text:"\u2713 Already in sync with Document360 \u2014 nothing to pull."}),t}async function xS(e){console.log(p("Checking Document360 for drift\u2026"));let t=Date.now(),n=await ew({cwd:e.cwd,profileName:e.profileName});for(let o of Oa(n,Date.now()-t))console.log(o);console.log("")}async function RS(e,t){let n=t[0]&&t[0]!=="--all"?t[0].replace(/\\/g,"/"):void 0;console.log(p(n?`Setting the sync baseline for ${n}\u2026`:"Setting a sync baseline for tracked-but-not-yet-compared articles\u2026"));let o=await kS({cwd:e.cwd,profileName:e.profileName,relPaths:n?[n]:void 0}),r=n?{rebaselined:[],skipped:[]}:await $S({cwd:e.cwd,profileName:e.profileName});if(o.baselined.length===0&&o.skipped.length===0&&r.rebaselined.length===0){console.log(X("\u2713 Nothing to baseline \u2014 every tracked article already has a sync baseline."));return}if(o.baselined.length>0&&console.log(X(`\u2713 Recorded ${o.baselined.length} article(s) as matching Document360 \u2014 they read in-sync until either side changes.`)),r.rebaselined.length>0&&console.log(X(`\u2713 Refreshed the metadata baseline for ${r.rebaselined.length} article(s) \u2014 cleared false "metadata changed" drift.`)),o.skipped.length>0){console.log(K(`${o.skipped.length} skipped:`));for(let s of o.skipped)console.log(p(` - ${s.path} (${s.reason})`))}}async function SS(e){console.log(p("Comparing the manifest against the per-article path (read-only)\u2026"));let t=await wS({cwd:e.cwd,profileName:e.profileName});console.log(` Legacy listing: ${Qe(String(t.legacyCount))} \xB7 Manifest: ${Qe(String(t.manifestCount))} \xB7 in both: ${t.inBoth}`),t.onlyInLegacy.length===0&&t.onlyInManifest.length===0&&t.mappedMissingFromManifest.length===0?console.log(X("\u2713 Parity \u2014 the manifest sees exactly the same articles. Safe to make it the default sync source.")):(t.mappedMissingFromManifest.length&&console.log(_(`\u2717 ${t.mappedMissingFromManifest.length} MAPPED article(s) missing from the manifest: ${t.mappedMissingFromManifest.slice(0,5).join(", ")}`)),t.onlyInLegacy.length&&console.log(K(` ${t.onlyInLegacy.length} only in the legacy listing: ${t.onlyInLegacy.slice(0,5).join(", ")}`)),t.onlyInManifest.length&&console.log(K(` ${t.onlyInManifest.length} only in the manifest: ${t.onlyInManifest.slice(0,5).join(", ")}`)),console.log(p(" Send this output \u2014 a mismatch pinpoints where the manifest differs before we flip."))),console.log("")}async function CS(e,t){let n=t.replace(/\\/g,"/"),o=await tw({cwd:e.cwd,profileName:e.profileName,relPath:n});if(o.overwritesLocalChanges){await Ju(e,o)==="keep-local"&&(console.log(p(`Publishing ${o.path} over the portal\u2026`)),await zt(e,[o.path]));return}console.log(""),console.log(`${ee("\u25CF")} ${Qe(o.title)} ${Re(`(${o.path})`)}`);for(let i of o.notes)console.log(K(` \u26A0 ${i}`));let r=It(o.oldContent,o.newContent,Math.max(40,(process.stdout.columns??80)-1),Rr);if(!r){console.log(p(" Local file already matches the remote content \u2014 advancing the sync base only.")),Zu({cwd:e.cwd,profileName:e.profileName},o);return}console.log(Re(` \u23BF Added ${r.added} line${Co(r.added)}, removed ${r.removed} line${Co(r.removed)}`));for(let i of r.lines)console.log(i);if(r.hidden>0&&console.log(p(` \u2026 +${r.hidden} more diff lines`)),!await e.withPausedInput(()=>gS({message:`Write ${o.path}?`,default:!o.overwritesLocalChanges}))){console.log(p(" Skipped."));return}Zu({cwd:e.cwd,profileName:e.profileName},o),console.log(X(` \u2713 Pulled ${o.path} (sync base advanced).`))}ie();import{statSync as _S}from"node:fs";import{resolve as MS}from"node:path";import{estimateBulkCost as FS,planPartitions as US,readProjectConfig as WS,resolveModelForOperation as BS,runPartitioned as HS,trackedArticlePaths as GS}from"document360-engine";function Fn(e,t){return e.aborted?null:e.blocked?[`\u2717 ${t} didn\u2019t start \u2014 ${e.blocked}.`,"Nothing was changed. Fix that, then re-run."]:e.allEmpty?[`\u2717 ${t} did nothing \u2014 every batch produced no output, so no agent actually ran.`,"Run /doctor \u2014 it runs a real turn with your skills and tools and reports what failed. /logs shows Claude Code\u2019s own error, which was recorded either way, and offers to copy it.","A Claude usage or session limit also does this, and it ends every turn instantly \u2014 if you have just hit one, wait for the reset and re-run."]:null}function ow(e){return!e.bailedEmpty||!e.notRun?null:`\u26A0 Stopped early \u2014 ${e.notRun} batch${e.notRun===1?" was":"es were"} not attempted, because two in a row produced no output (that is systemic, not this work: usually a Claude usage limit). Re-run to finish the rest; anything already done is skipped.`}var Sr=class{activeMs=0;last=0;timer=null;startedAt=0;stopped=null;start(t=Date.now()){this.timer||(this.startedAt=t,this.last=t,this.timer=setInterval(()=>this.tick(),1e3),this.timer.unref?.())}tick(t=Date.now()){let n=t-this.last;this.last=t,!(n<=0)&&(this.activeMs+=Math.min(n,5e3))}stop(t=Date.now()){return this.stopped?this.stopped:(this.timer&&(clearInterval(this.timer),this.timer=null),this.tick(t),this.stopped={activeSeconds:Math.max(0,Math.round(this.activeMs/1e3)),elapsedSeconds:Math.max(0,Math.round((t-this.startedAt)/1e3))},this.stopped)}};function Ba(e,t){let n=t-e;return n>=300&&n>=t*.2}function Lt(e,t,n,o=Date.now(),r){let s=t.reduce((a,l)=>a+(l.inputTokens??0),0),i=t.reduce((a,l)=>a+(l.cacheReadTokens??0),0);return{seconds:Math.max(0,Math.round((o-e)/1e3)),outputTokens:t.reduce((a,l)=>a+l.outputTokens,0),inputTokens:s,...s>0?{cachedPct:Math.round(i/s*100)}:{},...r!==void 0?{activeSeconds:r}:{},costUsd:n}}function Ha(e){return e.activeSeconds!==void 0&&Ba(e.activeSeconds,e.seconds)?`${nd(e.seconds)} elapsed \xB7 ${nd(e.activeSeconds)} working`:nd(e.seconds)}var rw=e=>e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:`${e}`,nd=e=>{if(e<60)return`${e}s`;let t=Math.floor(e/60);return t<60?`${t}m ${e%60}s`:`${Math.floor(t/60)}h ${t%60}m`};function rn(e){let t=e.cachedPct!==void 0?`, ${e.cachedPct}% cached`:"",n=e.costUsd>0?`, this turn ${e.costUsd<.01?e.costUsd.toFixed(4):e.costUsd.toFixed(2)}`:"";return` (took ${Ha(e)}, ${rw(e.inputTokens)}\u2192${rw(e.outputTokens)} tokens${t}${n})`}import ve from"picocolors";import id from"wrap-ansi";import ad from"string-width";ie();import Un from"picocolors";import qa from"wrap-ansi";import Ga from"string-width";var Po=[{name:"init",usage:"/init",desc:"Pick an environment, scaffold .d360-writer/config.json, and set up the builder hand-off (prints the one-paste prompt)",group:"start"},{name:"login",usage:"/login",desc:"Sign in to Document360 (browser)",group:"start"},{name:"project",usage:"/project [name]",desc:"Choose the Document360 project (picker; lists all you can access)",group:"start"},{name:"devhints",usage:"/devhints",desc:"Re-show/refresh the builder hand-off prompt (set up by /init; run anytime to hand your repo agent the latest)",group:"start"},{name:"scope",usage:"/scope",desc:"Choose which repo folders back the docs (monorepos)",group:"start"},{name:"genre",usage:"/genre [web|cli|service|library|auto]",desc:"Set/show the product genre \u2014 picks the docs craft playbook (usually auto-detected; set it if it guessed wrong)",group:"start"},{name:"mcp",usage:"/mcp [list|add|remove]",desc:"Connect MCP servers (Notion, Linear, \u2026)",group:"start"},{name:"discover",usage:"/discover",desc:"Interview your builder to understand the product (identity, mental model, misconceptions, positioning) \u2192 pins a product brief that grounds the docs; asks focused questions and loops until confident",group:"docs"},{name:"draft",usage:"/draft [<feature>]",desc:"Draft a feature's articles from its PRDs/specs in .d360-writer/input/ + the code",group:"docs"},{name:"document",usage:"/document [<what>] [--push]",desc:"Document a shipped change \u2014 the agent picks the action (new section/article/update); bare: act on builder requests. --push then saves the result to Document360 as drafts, which still needs an editorial review first",group:"docs"},{name:"write",usage:"/write [--preview|--scope <cat>|<path>] [--rewrite] [--concurrency N] [--skip-style]",desc:"Write the planned articles in parallel (starts immediately; --preview shows plan + cost first; --rewrite regenerates already-written articles; --concurrency default 3). Anything that would block publishing is corrected in one automatic pass before the run finishes; --skip-style leaves it to /lint",group:"docs"},{name:"screenshot",usage:"/screenshot [author|capture|annotate|verify|checklist|list] [id|path|--all|--failed|--blocked]",desc:"Screenshots, one family \u2014 bare: pick what to do; author: write capture specs; capture: take the shots (--all re-shoots everything; --failed re-runs the ones that did not work; --blocked re-runs the ones waiting on app data); annotate: draw the callouts on shots already taken, no browser needed (--all --force re-draws after a wording or style change); verify: judge the finished pictures and mark them verified (both run automatically after a capture); checklist: what to stage + dev test-id requests; list: status + what to stage",group:"docs"},{name:"preview",usage:"/preview [path|id]",desc:"Render an article (no arg: pick from tracked)",group:"docs"},{name:"push",usage:"/push [path|--all|[<category>] --screenshots] [--preview] [--skip-review]",desc:"Save your articles to Document360 as drafts \u2014 readers don't see them until /publish (no arg: pick; --all: every candidate; --screenshots: re-save articles with captured shots so images embed, optionally narrowed to one category; --preview <path>: show exactly what would be sent, and send nothing). Blocked until each article has had /review --fix \u2014 AI-written text gets an editor first; --skip-review overrides deliberately",group:"publish"},{name:"publish",usage:"/publish [path|<category>|--all] [--skip-review]",desc:"Take the drafts on Document360 LIVE to your readers (the portal's own Publish). Always lists what would become visible and asks first; no arg or a category narrows it. Needs an editorial review \u2014 /review --blocked --fix",group:"publish"},{name:"style-guide",usage:"/style-guide [list | use <preset> | keep | pull [<n>] | learn | compile | explain <rule>]",desc:"Choose the writing rules your docs are held to. Bare: what is active now; list: every pack you can turn on; use: switch packs (records what is already there so nothing suddenly breaks); keep: settle on the rules you have today; pull: bring your Document360 project's own style guide down; learn: turn what your reviewers keep asking for into standing rules; compile: turn its mechanical statements into checks; explain: what one rule means and how to silence it",group:"docs"},{name:"lint",usage:"/lint [<category|path>] [--blocked] [--fix] [--full]",desc:"Lint the docs against the house rules: leaked internals, invented commands, stale facts, AI-tells, em dashes, callout shape (deterministic, no tokens). Every finding names the rule that produced it. Scope to a category/path; --blocked narrows to the errors that block publishing and leaves warnings and suggestions alone; --fix has the agent correct them in place; --full lists every finding instead of a capped summary",group:"docs"},{name:"review",usage:"/review [<category|path> | --all | --blocked | --stale] [--fix]",desc:"Independent senior-editor review: style guide, terminology/voice, grammar/clarity, structure, improvement suggestions. Bare: pick a category or everything; --blocked reads only the articles publishing is blocked on; --stale reads the ones the current editor has not read yet, so running it twice costs nothing; --all reviews the whole KB; --fix applies the corrections in place",group:"docs"},{name:"reorder",usage:"/reorder [<category> | --all]",desc:"Reorder articles within a category to the best reading order (renumbers NN- prefixes, fixes references + map); bare: pick a category or everything; new order syncs to Document360 on next /push",group:"docs"},{name:"structure",usage:"/structure",desc:"Show the documentation reading order (categories \u2192 articles) as it will appear on Document360",group:"docs"},{name:"status",usage:"/status [--all]",desc:'What stands between your work and your readers, before you spend anything: which articles no editor has read, which break a style rule your config makes blocking, which would show "[Screenshot: \u2026]" text to a reader, which are in conflict with the portal, and which are saved as drafts readers cannot see yet. Each group names the one command that clears it. Reads local files and the last sync \u2014 no network, no tokens. --all lists every article gate by gate',group:"publish"},{name:"retire",usage:"/retire [<path>]",desc:"Take an article off your readers on Document360 \u2014 it stops being visible, and nothing offers to publish it again. Nothing is deleted: the article, its version history and every reviewer comment stay. Bare: lists what is worth retiring (articles on Document360 that are no longer in your docs folder). /push puts one back \u2014 reviving is a publish like any other",group:"publish"},{name:"untrack",usage:"/untrack [<path>] [--undo] [--list]",desc:"Disown a page that was imported from Document360 but is not one of your docs. /catch-up and /pull import every article they find in your categories; this records the ones you do not want, so they are never imported again \u2014 and the decision travels with the repo, so a teammate\u2019s clone inherits it. Nothing on Document360 changes and nothing is deleted: the markdown moves to .d360-writer/declined/. Bare: lists what this repo has declined. --undo puts one back",group:"publish"},{name:"reuse",usage:"/reuse [pull|add|audit]",desc:"The terms, values and blocks your project already maintains on Document360, so your articles reference them instead of repeating them. A variable holds a value like your product name in one place \u2014 change it there and every article changes; type it out by hand and /lint flags it. A snippet does the same for a whole block, and the writer references yours by name instead of writing its own version. A term is defined once and every article is checked for explaining it the first time it appears. Reading is free; pull refreshes from Document360, add creates a new one there, and audit reads every article to work out what belongs in one place",group:"docs"},{name:"terms",usage:'/terms [<category>] [--all] [--approve "<term>"]',desc:"Is your knowledge base calling the same thing by the same name? Reads every article and reports terms spelled more than one way, short names that may be the long name abbreviated, and article titles out of step with the rest of their kind \u2014 the consistency an editor reading three articles at a time cannot see. Costs nothing and calls nothing. --approve records a spelling as the approved one, so every later run enforces it",group:"docs"},{name:"roadmap",usage:"/roadmap [--all | done [step]]",desc:"Show the docs campaign (the multi-session plan the agent maintains), grouped by the part of the job \u2014 progress + next step; `--all` also lists the steps a later pass replaced; `done` marks the next open step finished (add words from a title to pick another)",group:"docs"},{name:"audit",usage:"/audit [--auto on|off]",desc:"Gap analysis: code vs docs vs Document360 (what changed); --auto runs it at launch when code changed",group:"publish"},{name:"inbox",usage:"/inbox",desc:"Act on messages from the repo's coding agent (requests to document, answers to your questions)",group:"publish"},{name:"ask",usage:"/ask <question>",desc:"Ask your developers something the code cannot tell you: why a feature exists, when to use it, whether it can be undone",group:"publish"},{name:"share",usage:"/share [status|on|off]",desc:"Share your docs work with the team: get your builder's latest, then save + send yours (runs automatically; `off` leaves it to your coding agent)",group:"publish"},{name:"catch-up",usage:"/catch-up [--push]",desc:"Process ALL pending work (builder hints + requests + Document360 portal drift) \u2192 write \u2192 save to Document360 as drafts. --push does that last step for you, which still needs an editorial review first. For code changes, that's /audit",group:"publish"},{name:"comments",usage:"/comments [<article>] [--scope <category>] [--answer] [--all] [--resolve] [--answered] [--full]",desc:"Reviewer comments left on passages of your articles in the Document360 portal \u2014 bare: a table by article, pick a number to read its comments in full, then answer or go back; --answer: fix, push the draft, reply (threads stay open unless you add --resolve); --full re-reads every article",group:"publish"},{name:"pull",usage:"/pull [<path> | --status | --baseline]",desc:"Bring Document360 down: pull portal edits + import NEW portal articles. --status: drift report only \xB7 --baseline: set a sync base \xB7 <path>: one article / resolve a conflict",group:"publish"},{name:"resolve",usage:"/resolve [<path>]",desc:"Resolve conflicts (edited both here and on the portal): shows the diff, then keep local (push), take portal (pull), or skip \u2014 per article, or one answer for all remaining. Bare: walk them all",group:"publish"},{name:"convert",usage:"/convert [--scope <folder>] [--run]",desc:"Convert tracked articles to DFM (one-off legacy migration)",group:"publish"},{name:"profile",usage:"/profile [name|add <name> [env]]",desc:"Switch/create a Document360 connection (picker; s = session)",group:"setup"},{name:"workspace",usage:"/workspace [name]",desc:"Switch the Document360 workspace (picker)",group:"setup"},{name:"auto",usage:"/auto [on|off|status] [--budget <amount>]",desc:"Auto or manual: in auto I run the next step myself \u2014 write, review, check style, save, go live \u2014 instead of waiting for you (shift+tab cycles it; esc stops a run)",group:"setup"},{name:"model",usage:"/model [name|default]",desc:"Set the Claude model for d360-writer",group:"setup"},{name:"verbose",usage:"/verbose [on|off]",desc:"Show the raw transcript \u2014 every tool call with its file path and full result, instead of article-shaped cards with my bookkeeping collapsed (bare: toggle)",group:"setup"},{name:"logout",usage:"/logout [--all-projects]",desc:"Sign out of this project + clear its workspace selection (then /login to re-pick)",group:"setup"},{name:"allow-prod",usage:"/allow-prod",desc:"Authorize writes to a production profile",group:"setup"},{name:"doctor",usage:"/doctor",desc:"Health-check: auth, profile, workspace, map, API, and a real agent turn",group:"setup"},{name:"logs",usage:"/logs",desc:"Show the most recent problem d360-writer recorded, and where the logs are \u2014 press c to copy it for the team",group:"setup"},{name:"update",usage:"/update",desc:"Update d360-writer \u2014 and d360-capture when installed \u2014 to the latest versions (then relaunch)",group:"setup"},{name:"feedback",usage:"/feedback",desc:"Copy the repo's open d360-writer tool-feedback to your clipboard, to relay to the d360-writer team",group:"setup"},{name:"reset",usage:"/reset",desc:"[DANGER] Delete all d360-writer files (docs, config, screenshots); types repo name to confirm",group:"setup"},{name:"resume",usage:"/resume [name]",desc:"Resume a session (no arg lists them)",group:"session"},{name:"rename",usage:"/rename [name]",desc:"Name the session (no arg: suggest one)",group:"session"},{name:"clear",usage:"/clear",desc:"Clear the screen + reset the conversation (resumable)",group:"session"},{name:"help",usage:"/help",desc:"Show this help",group:"session"},{name:"exit",usage:"/exit",desc:"Quit",group:"session"}],sw={"/catch-up":"write up everything your builder flagged","/audit":"check the code for anything new or missing in the docs","/discover":"interview your builder so the docs are grounded in the real product","/pull":"pull edits + new articles from Document360","/push":"save your articles to Document360 as drafts","/publish":"take the signed-off drafts live to your readers","/write":"author the planned articles","/draft":"plan articles from PRDs + the code","/login":"sign in to Document360","/workspace":"choose the Document360 workspace","/project":"choose the Document360 project","/scope":"choose which folders back the docs","/genre":"set this repo's product type (picks the docs playbook)","/init":"set up this repo","/screenshot":"work the screenshot pipeline (author specs, take shots, draw the callouts)","/doctor":"run a health check","/logs":"show the last problem, and copy it for the team","/auto":"choose whether I run the next step myself or wait for you","/style-guide":"choose the writing rules your docs are held to","/lint":"check the docs against those rules (costs nothing)"};function iw(e){let t=e.trim().replace(/^\//,"").split(/\s+/)[0]??"",n=`/${t}`;if(sw[n])return sw[n];let o=Po.find(r=>r.name===t);return o?o.desc:""}function aw(e){if(!e.startsWith("/"))return[];let t=e.slice(1).toLowerCase().split(/\s/)[0]??"";return Po.filter(n=>n.name.startsWith(t))}function lw(e){let t=0,n="";for(let o of e){if(o==="["){t++;continue}if(o==="]"){t=Math.max(0,t-1);continue}t===0&&(n+=o)}return/<[^>]+>/.test(n)}var PS=new RegExp(`^\\s*/(?:${Po.map(e=>e.name).join("|")})(?:\\s|$)`),cw=e=>PS.test(e),uw=e=>/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(e),dw=e=>/^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(e)&&e.includes("-"),pw=e=>e.replace(/^\s*\|/,"").replace(/\|\s*$/,"").split("|").map(t=>t.trim());function TS(e){let t=e.replace(/\r/g,"").split(`
|
|
337
|
+
`},s=r(e),i=r(t);if(s===i)return null;let a=Math.max(20,n-10),l=cS("a","b",s,i,"","",{context:3}),c=0,m=0,w=[];l.hunks.forEach((A,$)=>{$>0&&w.push(Gg.gray(" \u2026"));let P=A.oldStart,S=A.newStart,D=[],I=[],Y=()=>{for(let G=0;G<D.length;G++){let W=G<I.length?Vg(D[G],I[G]):void 0;m++,w.push(`${dS}${Xu(P++)} - ${Kg(D[G],a,W)}${qg}`)}for(let G=0;G<I.length;G++){let W=G<D.length?Vg(D[G],I[G]):void 0;c++,w.push(`${pS}${Xu(S++)} + ${Kg(I[G],a,W)}${qg}`)}D=[],I=[]};for(let G of A.lines){let W=G[0],de=G.slice(1);W==="-"?D.push(de):W==="+"?I.push(de):(Y(),w.push(Gg.gray(Xu(S))+" "+de.slice(0,a)),P++,S++)}Y()});let R=Math.max(1,o),E=w.slice(0,R);return{added:c,removed:m,lines:E,hidden:Math.max(0,w.length-R)}}import{select as Qg}from"@inquirer/prompts";import{applyPull as Jg,computeSyncStatus as hS,isPullWouldBlankError as zg,localArticleTitle as fS,planPull as Xg}from"document360-engine";function _a(){return["Document360 will not give this article back as markdown \u2014 it is not on the Markdown (Editor 3.0) editor, so there is no portal copy to show you or to take.","Your local copy is intact, and publishing it over the portal settles this. Skipping leaves the article flagged, and it will be waiting again next time."]}function Ma(){return"Publish your copy over the portal (settles it) \xB7 Skip?"}function Yg(){return"(l publish yours \xB7 s skip \u2014 anything else stops, keeping your answers so far)"}function Fa(e){if(e.length===0)return[];let t=e.length,n=e.slice(0,3).join(", ")+(t>3?`, +${t-3} more`:"");return[`${t} article${t===1?"":"s"} stay${t===1?"s":""} flagged \u2014 Document360 cannot hand ${t===1?"its":"their"} markdown back: ${n}`,"Two ways out: run /resolve and press l to publish your copy over the portal, or ask your Document360 admin to move the article back to the Markdown (Editor 3.0) editor, then run /resolve again."]}function Qu(e){return`Cannot take the portal copy of ${e} \u2014 Document360 will not return it as markdown. Left as it was.`}ie();var Ua=e=>e===1?"":"s";async function Ju(e,t,n=1){console.log(""),console.log(`${ee("\u25CF")} ${Qe(t.title)} ${Re(`(${t.path})`)}`);for(let i of t.notes)console.log(K(` \u26A0 ${i}`));let o=It(t.oldContent,t.newContent,Math.max(40,(process.stdout.columns??80)-1),Rr);if(o){console.log(Re(` \u23BF local \u2192 portal: +${o.added} / -${o.removed} line${Ua(o.added+o.removed)}`));for(let i of o.lines)console.log(i);o.hidden>0&&console.log(p(` \u2026 +${o.hidden} more diff lines`))}else console.log(p(" Local already matches the portal content \u2014 resolving just advances the sync base."));let r=n>1,s=await e.withPausedInput(()=>Qg({message:`Conflict \u2014 ${t.path}${r?` (${n} left)`:""}`,choices:[{name:"Keep local (publish \u2014 overwrites the portal edit)",value:"local"},{name:"Take portal (overwrites your local edits)",value:"portal"},{name:"Skip (leave it a conflict)",value:"skip"},...r?[{name:`Keep local for ALL ${n} remaining`,value:"local-all"},{name:`Take portal for ALL ${n} remaining`,value:"portal-all"},{name:`Skip ALL ${n} remaining`,value:"skip-all"}]:[]]})).catch(()=>"skip");return s==="portal"?(Jg({cwd:e.cwd,profileName:e.profileName},t),console.log(X(" \u2713 Took the portal copy (local overwritten, sync base advanced).")),"took-portal"):s==="local"?"keep-local":s==="skip"?(console.log(p(" Skipped \u2014 still a conflict.")),"skipped"):s==="local-all"?"keep-local-all":s==="portal-all"?"took-portal-all":"skipped-all"}async function mS(e,t){console.log(""),console.log(`${ee("\u25CF")} ${Qe(fS(e.cwd,t)??t)} ${Re(`(${t})`)}`);for(let o of _a())console.log(K(` \u26A0 ${o}`));return await e.withPausedInput(()=>Qg({message:Ma(),choices:[{name:"Publish your copy over the portal (settles it)",value:"local"},{name:"Skip (leave it a conflict)",value:"skip"}]})).catch(()=>"skip")==="local"?"keep-local":(console.log(p(" Skipped \u2014 still a conflict.")),"skipped")}async function Zg(e,t){let n=Je(t.cwd,t.profileName);if(!n.ok)return console.log(_(n.reason)),{kind:"continue"};let o=e.find(r=>!r.startsWith("--"))?.replace(/\\/g,"/");try{let s=(await hS({cwd:t.cwd,profileName:t.profileName})).entries.filter(l=>l.status==="conflict"&&l.path).map(l=>l.path);if(o){if(!s.includes(o))return console.log(X(`${o} isn't in conflict \u2014 nothing to resolve.`)),{kind:"continue"};s=[o]}if(s.length===0)return console.log(X("\u2713 No conflicts \u2014 local and Document360 agree on every tracked article.")),{kind:"continue"};let i=[],a=[];for(let[l,c]of s.entries()){let m;try{m=await Xg({cwd:t.cwd,profileName:t.profileName,relPath:c})}catch(E){if(!zg(E))throw E;await mS(t,c)==="keep-local"?i.push(c):a.push(c);continue}let w=await Ju(t,m,s.length-l);if(w==="keep-local"&&i.push(c),!w.endsWith("-all"))continue;let R=s.slice(l);if(w==="keep-local-all")i.push(...R),console.log(p(` Keeping local for all ${R.length} \u2014 publishing them over the portal.`));else if(w==="took-portal-all"){let E=0;for(let A of R)try{Jg({cwd:t.cwd,profileName:t.profileName},await Xg({cwd:t.cwd,profileName:t.profileName,relPath:A})),E++}catch($){console.log(_(` \u2717 ${A}: ${$.message}`)),zg($)&&a.push(A)}console.log(X(` \u2713 Took the portal copy of ${E} article${Ua(E)} (local overwritten, sync base advanced).`))}else console.log(p(` Skipped all ${R.length} \u2014 still conflict${Ua(R.length)}.`));break}for(let l of Fa(a))console.log(K(`\u26A0 ${l}`));if(i.length>0)return console.log(p(`Publishing ${i.length} kept-local article${Ua(i.length)} over the portal\u2026`)),await zt(t,i,0,{allowUnpullable:!0})}catch(r){console.log(_(`Resolve failed: ${r.message}`))}return{kind:"continue"}}async function nw(e,t){let n=Je(t.cwd,t.profileName);if(!n.ok)return console.log(_(n.reason)),{kind:"continue"};let o=e.find(r=>r.startsWith("--"));try{if(o==="--status")return await xS(t),{kind:"continue"};if(o==="--baseline")return await RS(t,e.filter(i=>i!=="--baseline")),{kind:"continue"};if(o==="--compare")return await SS(t),{kind:"continue"};if(o)return console.log(_(`Unknown option: ${o}`)),console.log(p("Usage: /pull pull portal edits + import new portal articles")),console.log(p(" /pull <path> pull one article (or resolve a conflict)")),console.log(p(" /pull --status drift report only (no writes)")),console.log(p(" /pull --baseline set a sync baseline for not-yet-compared articles")),{kind:"continue"};let r=e[0];if(r)return await CS(t,r),{kind:"continue"};console.log(p("Pulling changes from Document360\u2026"));let s=await ed({cwd:t.cwd,profileName:t.profileName});for(let i of td(s))console.log(i.tone==="ok"?X(i.text):i.tone==="warn"?K(i.text):p(i.text));console.log("")}catch(r){r instanceof vS?console.log(_(r.message)):console.log(_(`Pull failed: ${r.message}`))}return{kind:"continue"}}async function ed(e){let t=await ew({cwd:e.cwd,profileName:e.profileName}),n={pulled:[],imported:[],conflicts:[],toPublish:0,failed:[]};for(let o of t.entries)if(o.status==="remote-ahead"&&o.path)try{let r=await tw({cwd:e.cwd,profileName:e.profileName,relPath:o.path});Zu({cwd:e.cwd,profileName:e.profileName},r),n.pulled.push(o.path)}catch(r){n.failed.push({ref:o.path,error:r.message})}else if(o.status==="untracked-remote"&&o.articleId)try{let r=await yS({cwd:e.cwd,profileName:e.profileName,articleId:o.articleId});bS({cwd:e.cwd,profileName:e.profileName},r),n.imported.push({title:r.title,path:r.path})}catch(r){n.failed.push({ref:o.title??o.articleId,error:r.message})}else o.status==="conflict"&&o.path?n.conflicts.push(o.path):(o.status==="local-ahead"||o.status==="untracked-local"&&!o.link)&&o.path&&n.toPublish++;return n}var Wa=(e,t=5)=>`${e.slice(0,t).join(", ")}${e.length>t?`, +${e.length-t} more`:""}`,Co=e=>e===1?"":"s";function td(e){let t=[];return e.pulled.length&&t.push({tone:"ok",text:`\u2713 Pulled ${e.pulled.length} portal edit${Co(e.pulled.length)} into local: ${Wa(e.pulled)}`}),e.imported.length&&t.push({tone:"ok",text:`\u2713 Imported ${e.imported.length} new portal article${Co(e.imported.length)}: ${Wa(e.imported.map(n=>n.path))}`}),e.toPublish&&t.push({tone:"info",text:`\u2139 ${e.toPublish} article${Co(e.toPublish)} changed locally \u2014 /push saves them to Document360.`}),e.conflicts.length&&t.push({tone:"warn",text:`\u26A0 ${e.conflicts.length} article${Co(e.conflicts.length)} changed both here and on Document360 \u2014 left untouched. Resolve them: /resolve (or /push <path> to keep yours, /pull <path> to take the portal's). (${Wa(e.conflicts)})`}),e.failed.length&&t.push({tone:"warn",text:`\u26A0 ${e.failed.length} item${Co(e.failed.length)} failed: ${Wa(e.failed.map(n=>n.ref))}`}),t.length===0&&t.push({tone:"ok",text:"\u2713 Already in sync with Document360 \u2014 nothing to pull."}),t}async function xS(e){console.log(p("Checking Document360 for drift\u2026"));let t=Date.now(),n=await ew({cwd:e.cwd,profileName:e.profileName});for(let o of Oa(n,Date.now()-t))console.log(o);console.log("")}async function RS(e,t){let n=t[0]&&t[0]!=="--all"?t[0].replace(/\\/g,"/"):void 0;console.log(p(n?`Setting the sync baseline for ${n}\u2026`:"Setting a sync baseline for tracked-but-not-yet-compared articles\u2026"));let o=await kS({cwd:e.cwd,profileName:e.profileName,relPaths:n?[n]:void 0}),r=n?{rebaselined:[],skipped:[]}:await $S({cwd:e.cwd,profileName:e.profileName});if(o.baselined.length===0&&o.skipped.length===0&&r.rebaselined.length===0){console.log(X("\u2713 Nothing to baseline \u2014 every tracked article already has a sync baseline."));return}if(o.baselined.length>0&&console.log(X(`\u2713 Recorded ${o.baselined.length} article(s) as matching Document360 \u2014 they read in-sync until either side changes.`)),r.rebaselined.length>0&&console.log(X(`\u2713 Refreshed the metadata baseline for ${r.rebaselined.length} article(s) \u2014 cleared false "metadata changed" drift.`)),o.skipped.length>0){console.log(K(`${o.skipped.length} skipped:`));for(let s of o.skipped)console.log(p(` - ${s.path} (${s.reason})`))}}async function SS(e){console.log(p("Comparing the manifest against the per-article path (read-only)\u2026"));let t=await wS({cwd:e.cwd,profileName:e.profileName});console.log(` Legacy listing: ${Qe(String(t.legacyCount))} \xB7 Manifest: ${Qe(String(t.manifestCount))} \xB7 in both: ${t.inBoth}`),t.onlyInLegacy.length===0&&t.onlyInManifest.length===0&&t.mappedMissingFromManifest.length===0?console.log(X("\u2713 Parity \u2014 the manifest sees exactly the same articles. Safe to make it the default sync source.")):(t.mappedMissingFromManifest.length&&console.log(_(`\u2717 ${t.mappedMissingFromManifest.length} MAPPED article(s) missing from the manifest: ${t.mappedMissingFromManifest.slice(0,5).join(", ")}`)),t.onlyInLegacy.length&&console.log(K(` ${t.onlyInLegacy.length} only in the legacy listing: ${t.onlyInLegacy.slice(0,5).join(", ")}`)),t.onlyInManifest.length&&console.log(K(` ${t.onlyInManifest.length} only in the manifest: ${t.onlyInManifest.slice(0,5).join(", ")}`)),console.log(p(" Send this output \u2014 a mismatch pinpoints where the manifest differs before we flip."))),console.log("")}async function CS(e,t){let n=t.replace(/\\/g,"/"),o=await tw({cwd:e.cwd,profileName:e.profileName,relPath:n});if(o.overwritesLocalChanges){await Ju(e,o)==="keep-local"&&(console.log(p(`Publishing ${o.path} over the portal\u2026`)),await zt(e,[o.path]));return}console.log(""),console.log(`${ee("\u25CF")} ${Qe(o.title)} ${Re(`(${o.path})`)}`);for(let i of o.notes)console.log(K(` \u26A0 ${i}`));let r=It(o.oldContent,o.newContent,Math.max(40,(process.stdout.columns??80)-1),Rr);if(!r){console.log(p(" Local file already matches the remote content \u2014 advancing the sync base only.")),Zu({cwd:e.cwd,profileName:e.profileName},o);return}console.log(Re(` \u23BF Added ${r.added} line${Co(r.added)}, removed ${r.removed} line${Co(r.removed)}`));for(let i of r.lines)console.log(i);if(r.hidden>0&&console.log(p(` \u2026 +${r.hidden} more diff lines`)),!await e.withPausedInput(()=>gS({message:`Write ${o.path}?`,default:!o.overwritesLocalChanges}))){console.log(p(" Skipped."));return}Zu({cwd:e.cwd,profileName:e.profileName},o),console.log(X(` \u2713 Pulled ${o.path} (sync base advanced).`))}ie();import{statSync as _S}from"node:fs";import{resolve as MS}from"node:path";import{estimateBulkCost as FS,planPartitions as US,readProjectConfig as WS,resolveModelForOperation as BS,runPartitioned as HS,trackedArticlePaths as GS}from"document360-engine";function Fn(e,t){return e.aborted?null:e.blocked?[`\u2717 ${t} didn\u2019t start \u2014 ${e.blocked}.`,"Nothing was changed. Fix that, then re-run."]:e.allEmpty?[`\u2717 ${t} did nothing \u2014 every batch produced no output, so no agent actually ran.`,"Run /doctor \u2014 it runs a real turn with your skills and tools and reports what failed. /logs shows Claude Code\u2019s own error, which was recorded either way, and offers to copy it.","A Claude usage or session limit also does this, and it ends every turn instantly \u2014 if you have just hit one, wait for the reset and re-run."]:null}function ow(e){return!e.bailedEmpty||!e.notRun?null:`\u26A0 Stopped early \u2014 ${e.notRun} batch${e.notRun===1?" was":"es were"} not attempted, because two in a row produced no output (that is systemic, not this work: usually a Claude usage limit). Re-run to finish the rest; anything already done is skipped.`}var Sr=class{activeMs=0;last=0;timer=null;startedAt=0;stopped=null;start(t=Date.now()){this.timer||(this.startedAt=t,this.last=t,this.timer=setInterval(()=>this.tick(),1e3),this.timer.unref?.())}tick(t=Date.now()){let n=t-this.last;this.last=t,!(n<=0)&&(this.activeMs+=Math.min(n,5e3))}stop(t=Date.now()){return this.stopped?this.stopped:(this.timer&&(clearInterval(this.timer),this.timer=null),this.tick(t),this.stopped={activeSeconds:Math.max(0,Math.round(this.activeMs/1e3)),elapsedSeconds:Math.max(0,Math.round((t-this.startedAt)/1e3))},this.stopped)}};function Ba(e,t){let n=t-e;return n>=300&&n>=t*.2}function Lt(e,t,n,o=Date.now(),r){let s=t.reduce((a,l)=>a+(l.inputTokens??0),0),i=t.reduce((a,l)=>a+(l.cacheReadTokens??0),0);return{seconds:Math.max(0,Math.round((o-e)/1e3)),outputTokens:t.reduce((a,l)=>a+l.outputTokens,0),inputTokens:s,...s>0?{cachedPct:Math.round(i/s*100)}:{},...r!==void 0?{activeSeconds:r}:{},costUsd:n}}function Ha(e){return e.activeSeconds!==void 0&&Ba(e.activeSeconds,e.seconds)?`${nd(e.seconds)} elapsed \xB7 ${nd(e.activeSeconds)} working`:nd(e.seconds)}var rw=e=>e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:`${e}`,nd=e=>{if(e<60)return`${e}s`;let t=Math.floor(e/60);return t<60?`${t}m ${e%60}s`:`${Math.floor(t/60)}h ${t%60}m`};function rn(e){let t=e.cachedPct!==void 0?`, ${e.cachedPct}% cached`:"",n=e.costUsd>0?`, this turn ${e.costUsd<.01?e.costUsd.toFixed(4):e.costUsd.toFixed(2)}`:"";return` (took ${Ha(e)}, ${rw(e.inputTokens)}\u2192${rw(e.outputTokens)} tokens${t}${n})`}import ve from"picocolors";import id from"wrap-ansi";import ad from"string-width";ie();import Un from"picocolors";import qa from"wrap-ansi";import Ga from"string-width";var Po=[{name:"init",usage:"/init",desc:"Pick an environment, scaffold .d360-writer/config.json, and set up the builder hand-off (prints the one-paste prompt)",group:"start"},{name:"login",usage:"/login",desc:"Sign in to Document360 (browser)",group:"start"},{name:"project",usage:"/project [name]",desc:"Choose the Document360 project (picker; lists all you can access)",group:"start"},{name:"devhints",usage:"/devhints",desc:"Re-show/refresh the builder hand-off prompt (set up by /init; run anytime to hand your repo agent the latest)",group:"start"},{name:"scope",usage:"/scope",desc:"Choose which repo folders back the docs (monorepos)",group:"start"},{name:"genre",usage:"/genre [web|cli|service|library|auto]",desc:"Set/show the product genre \u2014 picks the docs craft playbook (usually auto-detected; set it if it guessed wrong)",group:"start"},{name:"mcp",usage:"/mcp [list|add|remove]",desc:"Connect MCP servers (Notion, Linear, \u2026)",group:"start"},{name:"discover",usage:"/discover",desc:"Interview your builder to understand the product (identity, mental model, misconceptions, positioning) \u2192 pins a product brief that grounds the docs; asks focused questions and loops until confident",group:"docs"},{name:"draft",usage:"/draft [<feature>]",desc:"Draft a feature's articles from its PRDs/specs in .d360-writer/input/ + the code",group:"docs"},{name:"document",usage:"/document [<what>] [--push]",desc:"Document a shipped change \u2014 the agent picks the action (new section/article/update); bare: act on builder requests. --push then saves the result to Document360 as drafts, which still needs an editorial review first",group:"docs"},{name:"write",usage:"/write [--preview|--scope <cat>|<path>] [--rewrite] [--concurrency N] [--skip-style]",desc:"Write the planned articles in parallel (starts immediately; --preview shows plan + cost first; --rewrite regenerates already-written articles; --concurrency default 3). Anything that would block publishing is corrected in one automatic pass before the run finishes; --skip-style leaves it to /lint",group:"docs"},{name:"screenshot",usage:"/screenshot [author|capture|annotate|verify|checklist|list] [id|path|--all|--failed|--blocked]",desc:"Screenshots, one family \u2014 bare: pick what to do; author: write capture specs; capture: take the shots (--all re-shoots everything; --failed re-runs the ones that did not work; --blocked re-runs the ones waiting on app data); annotate: draw the callouts on shots already taken, no browser needed (--all --force re-draws after a wording or style change); verify: judge the finished pictures and mark them verified (both run automatically after a capture); checklist: what to stage + dev test-id requests; list: status + what to stage",group:"docs"},{name:"preview",usage:"/preview [path|id]",desc:"Render an article (no arg: pick from tracked)",group:"docs"},{name:"push",usage:"/push [path|--all|--related <path>|[<category>] --screenshots] [--preview] [--skip-review]",desc:"Save your articles to Document360 as drafts \u2014 readers don't see them until /publish (no arg: pick; --all: every candidate; --screenshots: re-save articles with captured shots so images embed, optionally narrowed to one category; --related <path>: also save that article's related articles that were never saved; --preview <path>: show exactly what would be sent, and send nothing). Blocked until each article has had /review --fix \u2014 AI-written text gets an editor first; --skip-review overrides deliberately",group:"publish"},{name:"publish",usage:"/publish [path|<category>|--all] [--skip-review]",desc:"Take the drafts on Document360 LIVE to your readers (the portal's own Publish). Always lists what would become visible and asks first; no arg or a category narrows it. Needs an editorial review \u2014 /review --blocked --fix",group:"publish"},{name:"style-guide",usage:"/style-guide [list | use <preset> | keep | pull [<n>] | learn | compile | explain <rule>]",desc:"Choose the writing rules your docs are held to. Bare: what is active now; list: every pack you can turn on; use: switch packs (records what is already there so nothing suddenly breaks); keep: settle on the rules you have today; pull: bring your Document360 project's own style guide down; learn: turn what your reviewers keep asking for into standing rules; compile: turn its mechanical statements into checks; explain: what one rule means and how to silence it",group:"docs"},{name:"lint",usage:"/lint [<category|path>] [--blocked] [--fix] [--full] [--baselined]",desc:"Lint the docs against the house rules: leaked internals, invented commands, stale facts, AI-tells, em dashes, callout shape (deterministic, no tokens). Every finding names the rule that produced it. Scope to a category/path; --blocked narrows to the errors that block publishing and leaves warnings and suggestions alone; --fix has the agent correct them in place; --full lists every finding instead of a capped summary; --baselined shows the issues that were already in your articles when you chose this style guide, which never block publishing",group:"docs"},{name:"review",usage:"/review [<category|path> | --all | --blocked | --stale] [--fix]",desc:"Independent senior-editor review: style guide, terminology/voice, grammar/clarity, structure, improvement suggestions. Bare: pick a category or everything; --blocked reads only the articles publishing is blocked on; --stale reads the ones the current editor has not read yet, so running it twice costs nothing; --all reviews the whole KB; --fix applies the corrections in place",group:"docs"},{name:"reorder",usage:"/reorder [<category> | --all]",desc:"Reorder articles within a category to the best reading order (renumbers NN- prefixes, fixes references + map); bare: pick a category or everything; new order syncs to Document360 on next /push",group:"docs"},{name:"structure",usage:"/structure",desc:"Show the documentation reading order (categories \u2192 articles) as it will appear on Document360",group:"docs"},{name:"status",usage:"/status [--all]",desc:'What stands between your work and your readers, before you spend anything: which articles no editor has read, which break a style rule your config makes blocking, which would show "[Screenshot: \u2026]" text to a reader, which are in conflict with the portal, and which are saved as drafts readers cannot see yet. Each group names the one command that clears it. Reads local files and the last sync \u2014 no network, no tokens. --all lists every article gate by gate',group:"publish"},{name:"retire",usage:"/retire [<path>] [--yes]",desc:"Take an article off your readers on Document360 \u2014 it stops being visible, and nothing offers to publish it again. Nothing is deleted: the article, its version history and every reviewer comment stay. Bare: lists what is worth retiring (articles on Document360 that are no longer in your docs folder). /push puts one back \u2014 reviving is a publish like any other",group:"publish"},{name:"untrack",usage:"/untrack [<path>] [--undo] [--list] [--yes]",desc:"Disown a page that was imported from Document360 but is not one of your docs. /catch-up and /pull import every article they find in your categories; this records the ones you do not want, so they are never imported again \u2014 and the decision travels with the repo, so a teammate\u2019s clone inherits it. Nothing on Document360 changes and nothing is deleted: the markdown moves to .d360-writer/declined/. Bare: lists what this repo has declined. --undo puts one back",group:"publish"},{name:"reuse",usage:'/reuse [pull | add <variable|snippet> <name> "<value>" | audit [<category>] [--create <n\u2026>]]',desc:"The terms, values and blocks your project already maintains on Document360, so your articles reference them instead of repeating them. A variable holds a value like your product name in one place \u2014 change it there and every article changes; type it out by hand and /lint flags it. A snippet does the same for a whole block, and the writer references yours by name instead of writing its own version. A term is defined once and every article is checked for explaining it the first time it appears. Reading is free; pull refreshes from Document360, add creates a new one there, and audit reads every article to work out what belongs in one place",group:"docs"},{name:"terms",usage:'/terms [<category>] [--all] [--approve "<term>"]',desc:"Is your knowledge base calling the same thing by the same name? Reads every article and reports terms spelled more than one way, short names that may be the long name abbreviated, and article titles out of step with the rest of their kind \u2014 the consistency an editor reading three articles at a time cannot see. Costs nothing and calls nothing. --approve records a spelling as the approved one, so every later run enforces it",group:"docs"},{name:"roadmap",usage:"/roadmap [--all | done [step]]",desc:"Show the docs campaign (the multi-session plan the agent maintains), grouped by the part of the job \u2014 progress + next step; `--all` also lists the steps a later pass replaced; `done` marks the next open step finished (add words from a title to pick another)",group:"docs"},{name:"audit",usage:"/audit [--auto on|off]",desc:"Gap analysis: code vs docs vs Document360 (what changed); --auto runs it at launch when code changed",group:"publish"},{name:"inbox",usage:"/inbox",desc:"Act on messages from the repo's coding agent (requests to document, answers to your questions)",group:"publish"},{name:"ask",usage:"/ask <question>",desc:"Ask your developers something the code cannot tell you: why a feature exists, when to use it, whether it can be undone",group:"publish"},{name:"share",usage:"/share [status|on|off]",desc:"Share your docs work with the team: get your builder's latest, then save + send yours (runs automatically; `off` leaves it to your coding agent)",group:"publish"},{name:"catch-up",usage:"/catch-up [--push]",desc:"Process ALL pending work (builder hints + requests + Document360 portal drift) \u2192 write \u2192 save to Document360 as drafts. --push does that last step for you, which still needs an editorial review first. For code changes, that's /audit",group:"publish"},{name:"comments",usage:"/comments [<article>] [--scope <category>] [--answer] [--all] [--resolve] [--answered] [--full]",desc:"Reviewer comments left on passages of your articles in the Document360 portal \u2014 bare: a table by article, pick a number to read its comments in full, then answer or go back; --answer: fix, push the draft, reply (threads stay open unless you add --resolve); --full re-reads every article",group:"publish"},{name:"pull",usage:"/pull [<path> | --status | --baseline]",desc:"Bring Document360 down: pull portal edits + import NEW portal articles. --status: drift report only \xB7 --baseline: set a sync base \xB7 <path>: one article / resolve a conflict",group:"publish"},{name:"resolve",usage:"/resolve [<path>]",desc:"Resolve conflicts (edited both here and on the portal): shows the diff, then keep local (push), take portal (pull), or skip \u2014 per article, or one answer for all remaining. Bare: walk them all",group:"publish"},{name:"convert",usage:"/convert [--scope <folder>] [--run]",desc:"Convert tracked articles to DFM (one-off legacy migration)",group:"publish"},{name:"profile",usage:"/profile [name|add <name> [env]]",desc:"Switch/create a Document360 connection (picker; s = session)",group:"setup"},{name:"workspace",usage:"/workspace [name]",desc:"Switch the Document360 workspace (picker)",group:"setup"},{name:"auto",usage:"/auto [on|off|status] [--budget <amount>]",desc:"Auto or manual: in auto I run the next step myself \u2014 write, review, check style, save, go live \u2014 instead of waiting for you (shift+tab cycles it; esc stops a run)",group:"setup"},{name:"model",usage:"/model [name|default]",desc:"Set the Claude model for d360-writer",group:"setup"},{name:"verbose",usage:"/verbose [on|off]",desc:"Show the raw transcript \u2014 every tool call with its file path and full result, instead of article-shaped cards with my bookkeeping collapsed (bare: toggle)",group:"setup"},{name:"logout",usage:"/logout [--all-projects]",desc:"Sign out of this project + clear its workspace selection (then /login to re-pick)",group:"setup"},{name:"allow-prod",usage:"/allow-prod",desc:"Authorize writes to a production profile",group:"setup"},{name:"doctor",usage:"/doctor",desc:"Health-check: auth, profile, workspace, map, API, and a real agent turn",group:"setup"},{name:"logs",usage:"/logs",desc:"Show the most recent problem d360-writer recorded, and where the logs are \u2014 press c to copy it for the team",group:"setup"},{name:"update",usage:"/update",desc:"Update d360-writer \u2014 and d360-capture when installed \u2014 to the latest versions (then relaunch)",group:"setup"},{name:"feedback",usage:"/feedback",desc:"Copy the repo's open d360-writer tool-feedback to your clipboard, to relay to the d360-writer team",group:"setup"},{name:"reset",usage:"/reset",desc:"[DANGER] Delete all d360-writer files (docs, config, screenshots); types repo name to confirm",group:"setup"},{name:"resume",usage:"/resume [name]",desc:"Resume a session (no arg lists them)",group:"session"},{name:"rename",usage:"/rename [name]",desc:"Name the session (no arg: suggest one)",group:"session"},{name:"clear",usage:"/clear",desc:"Clear the screen + reset the conversation (resumable)",group:"session"},{name:"help",usage:"/help",desc:"Show this help",group:"session"},{name:"exit",usage:"/exit",desc:"Quit",group:"session"}],sw={"/catch-up":"write up everything your builder flagged","/audit":"check the code for anything new or missing in the docs","/discover":"interview your builder so the docs are grounded in the real product","/pull":"pull edits + new articles from Document360","/push":"save your articles to Document360 as drafts","/publish":"take the signed-off drafts live to your readers","/write":"author the planned articles","/draft":"plan articles from PRDs + the code","/login":"sign in to Document360","/workspace":"choose the Document360 workspace","/project":"choose the Document360 project","/scope":"choose which folders back the docs","/genre":"set this repo's product type (picks the docs playbook)","/init":"set up this repo","/screenshot":"work the screenshot pipeline (author specs, take shots, draw the callouts)","/doctor":"run a health check","/logs":"show the last problem, and copy it for the team","/auto":"choose whether I run the next step myself or wait for you","/style-guide":"choose the writing rules your docs are held to","/lint":"check the docs against those rules (costs nothing)"};function iw(e){let t=e.trim().replace(/^\//,"").split(/\s+/)[0]??"",n=`/${t}`;if(sw[n])return sw[n];let o=Po.find(r=>r.name===t);return o?o.desc:""}function aw(e){if(!e.startsWith("/"))return[];let t=e.slice(1).toLowerCase().split(/\s/)[0]??"";return Po.filter(n=>n.name.startsWith(t))}function lw(e){let t=0,n="";for(let o of e){if(o==="["){t++;continue}if(o==="]"){t=Math.max(0,t-1);continue}t===0&&(n+=o)}return/<[^>]+>/.test(n)}var PS=new RegExp(`^\\s*/(?:${Po.map(e=>e.name).join("|")})(?:\\s|$)`),cw=e=>PS.test(e),uw=e=>/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(e),dw=e=>/^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(e)&&e.includes("-"),pw=e=>e.replace(/^\s*\|/,"").replace(/\|\s*$/,"").split("|").map(t=>t.trim());function TS(e){let t=e.replace(/\r/g,"").split(`
|
|
338
338
|
`),n=[],o=0;for(;o<t.length;){let r=t[o];if(/^\s*```/.test(r)){let a=[];for(o++;o<t.length&&!/^\s*```/.test(t[o]);)a.push(t[o++]);o++,n.push({kind:"code",lines:a});continue}if(uw(r)){n.push({kind:"hr"}),o++;continue}if(r.includes("|")&&o+1<t.length&&dw(t[o+1])){let a=pw(r);o+=2;let l=[];for(;o<t.length&&t[o].includes("|")&&t[o].trim()!=="";)l.push(pw(t[o++]));n.push({kind:"table",header:a,rows:l});continue}let s=r.match(/^(#{1,6})\s+(.*)$/);if(s){n.push({kind:"heading",level:s[1].length,text:s[2]}),o++;continue}if(/^\s*([-*]|\d+\.)\s+/.test(r)){let a=[];for(;o<t.length&&/^\s*([-*]|\d+\.)\s+/.test(t[o]);){let l=t[o++];a.push({ordered:/^\s*\d+\.\s+/.test(l),text:l.replace(/^\s*([-*]|\d+\.)\s+/,"")})}n.push({kind:"list",items:a});continue}if(r.trim()===""){o++;continue}if(cw(r)){n.push({kind:"para",text:r.trim()}),o++;continue}let i=[];for(;o<t.length&&t[o].trim()!==""&&!/^\s*```/.test(t[o])&&!/^(#{1,6})\s/.test(t[o])&&!/^\s*([-*]|\d+\.)\s+/.test(t[o])&&!cw(t[o])&&!uw(t[o])&&!(t[o].includes("|")&&o+1<t.length&&dw(t[o+1]));)i.push(t[o++]);n.push({kind:"para",text:i.join(" ")})}return n}function od(e){return e.replace(/(\*\*[^*]+\*\*|`[^`]+`|\*[^*]+\*)/g,t=>t.startsWith("**")?Un.bold(t.slice(2,-2)):t.startsWith("`")?ee(t.slice(1,-1)):Un.italic(t.slice(1,-1)))}var AS=(e,t)=>e+" ".repeat(Math.max(0,t-Ga(e)));function Ms(e,t,n,o={}){return hw(e,t,n,o)}function hw(e,t,n,o={}){let r=e.length,s=P=>Math.max(0,...P.split(`
|
|
339
339
|
`).map(S=>Ga(S))),i=e.map((P,S)=>Math.max(Ga(P),...t.map(D=>s(D[S]??"")))),a=Math.max(24,n),l=3*r+1,c=[...i],m=()=>c.reduce((P,S)=>P+S,0)+l,w=0;for(;m()>a&&w++<1e4;){let P=-1,S=6;for(let D=0;D<r;D++)c[D]>S&&(S=c[D],P=D);if(P===-1)break;c[P]-=1}let R=(P,S,D)=>Un.gray(P+c.map(I=>"\u2500".repeat(I+2)).join(S)+D),E=Un.gray("\u2502"),A=(P,S)=>{let D=c.map((W,de)=>{let $e=P[de]??"",Ie=S?Un.bold($e):o.plain?$e:od($e);return $e.includes(`
|
|
340
340
|
`)?Ie.split(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document360-writer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.291",
|
|
4
4
|
"description": "Standalone documentation agent CLI. Reads your code, writes your docs. Specialized for Document360 publishing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@inquirer/prompts": "^8.4.3",
|
|
35
35
|
"commander": "^14.0.3",
|
|
36
36
|
"diff": "^8.0.4",
|
|
37
|
-
"document360-engine": "^0.2.
|
|
37
|
+
"document360-engine": "^0.2.258",
|
|
38
38
|
"ink": "^5.2.1",
|
|
39
39
|
"picocolors": "^1.1.1",
|
|
40
40
|
"react": "^18.3.1",
|