document360-engine 0.2.10 → 0.2.12
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/index.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import{readdirSync as
|
|
2
|
-
`,"utf8")}function te(){let e=we();if(!Se(e))return{};try{return JSON.parse(se(e,"utf8"))}catch{return{}}}function
|
|
3
|
-
`,"utf8")}function
|
|
4
|
-
`,"utf8")}var ie=class{resolvers=[];buffer=[];closed=!1;push(t){if(this.closed)return;let r={type:"user",message:{role:"user",content:t},parent_tool_use_id:null},n=this.resolvers.shift();n?n(r):this.buffer.push(r)}close(){this.closed=!0;for(let t of this.resolvers)t(null);this.resolvers=[]}async*[Symbol.asyncIterator](){for(;;){if(this.buffer.length>0){yield this.buffer.shift();continue}if(this.closed)return;let t=await new Promise(r=>this.resolvers.push(r));if(t===null)return;yield t}}};import{existsSync as
|
|
5
|
-
${n}`),
|
|
6
|
-
`,"utf8")}catch{}}var
|
|
7
|
-
`,"utf8"),!0}import{createHash as
|
|
1
|
+
import{readdirSync as fn,readFileSync as Ut,existsSync as Ue,statSync as mn}from"node:fs";import{join as Ne}from"node:path";import{query as gn}from"@anthropic-ai/claude-agent-sdk";import{existsSync as Kt}from"node:fs";import{isAbsolute as Yt,relative as Qt,resolve as He}from"node:path";var ze=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function he(e,t,r){let n=Yt(t)?t:He(e,t),o=Qt(e,n).replace(/\\/g,"/");return o===""||o.startsWith("..")?!1:/\.(md|markdown)$/i.test(o)||o===".d360-writer.json"||o==="d360-category-map.json"||o===".d360-writer"||o.startsWith(".d360-writer/")?!0:[r?.captureDir,r?.outputDir].filter(i=>typeof i=="string"&&i.length>0).map(i=>i.replace(/\\/g,"/").replace(/\/+$/,"")).some(i=>o===i||o.startsWith(`${i}/`))}var Zt=/^(\/dev\/null|nul|null)$/i,oe=e=>e.replace(/^['"]/,"").replace(/['"]$/,""),Xt=new Set(["rm","rmdir","mv","truncate","touch","mkdir","chmod","chown"]),er=new Set(["cp","ln","install"]);function Je(e,t,r){let n=i=>{let a=oe(i);return!a||a.startsWith("&")||a.startsWith("-")||Zt.test(a)?!1:!he(t,a,r)},o=/(?:^|[^0-9>])>>?\s*("[^"]+"|'[^']+'|[^\s|&;<>]+)/g,s;for(;s=o.exec(e);)if(n(s[1]))return oe(s[1]);for(let i of e.split(/&&|\|\||[;|]/)){let a=i.trim().split(/\s+/).filter(Boolean);if(a.length===0)continue;let d=a[0].replace(/.*[/\\]/,""),l=a.slice(1),u=l.filter(p=>!p.startsWith("-")),f=[];Xt.has(d)?f=u:er.has(d)?f=u.slice(-1):d==="tee"?f=u:d==="dd"?f=l.filter(p=>p.startsWith("of=")).map(p=>p.slice(3)):(d==="sed"||d==="perl")&&l.some(p=>/^-i/.test(p)||p==="--in-place")&&(f=u.filter(p=>Kt(He(t,oe(p)))));for(let p of f)if(n(p))return oe(p)}return null}function ye(e){return`Writer mode: "${e}" is outside the documentation scope (markdown, capture specs, d360 config). Note the needed change for the engineering team instead of making it. Repos that want the agent to modify source code set "mode": "engineer" in .d360-writer.json.`}import{homedir as tr}from"node:os";import{join as N,dirname as Ve}from"node:path";import{fileURLToPath as rr}from"node:url";import{existsSync as Ke,mkdirSync as nr}from"node:fs";var or=rr(import.meta.url),Ge=Ve(or);function sr(){let e=Ge;for(let t=0;t<4;t++){if(Ke(N(e,"package.json")))return e;e=Ve(e)}return N(Ge,"..","..")}function Ye(){return N(sr(),"skills")}function A(){return N(tr(),".document360-writer")}function we(){return N(A(),"config.json")}function ke(){return N(A(),"mcp.json")}function be(){return N(A(),"sessions.json")}function _e(e=process.cwd()){return N(e,".d360-writer.json")}function Qe(e=process.cwd()){return N(e,".d360-writer","skills")}function L(e){Ke(e)||nr(e,{recursive:!0})}import{readFileSync as se,writeFileSync as ve,existsSync as Se}from"node:fs";import{homedir as ir}from"node:os";import{join as ar}from"node:path";var ee=class extends Error{};function Ze(e,t){if(!e?.profiles||Object.keys(e.profiles).length===0)throw new ee("No connection profiles in .d360-writer.json. This version requires a `profiles` map + `defaultProfile` (the old d360.environment/projectId shape is no longer supported). Run `d360-writer init` to scaffold the new shape.");let r=t??e.defaultProfile;if(!r)throw new ee(`No profile specified and no defaultProfile set. Available: ${Object.keys(e.profiles).join(", ")}`);let n=e.profiles[r];if(!n)throw new ee(`Unknown profile "${r}". Available: ${Object.keys(e.profiles).join(", ")}`);return{name:r,profile:n}}function I(e=process.cwd()){let t=_e(e);return Se(t)?JSON.parse(se(t,"utf8")):null}function Xe(e,t=process.cwd()){ve(_e(t),JSON.stringify(e,null,2)+`
|
|
2
|
+
`,"utf8")}function te(){let e=we();if(!Se(e))return{};try{return JSON.parse(se(e,"utf8"))}catch{return{}}}function ho(e){L(A()),ve(we(),JSON.stringify(e,null,2)+`
|
|
3
|
+
`,"utf8")}function cr(e,t,r,n){return e?{model:e,source:"project"}:t?{model:t,source:"user"}:r?{model:r,source:"env"}:n?{model:n,source:"claude-settings"}:{model:null,source:"claude-default"}}function lr(){try{let e=JSON.parse(se(ar(ir(),".claude","settings.json"),"utf8"));return typeof e.model=="string"&&e.model?e.model:void 0}catch{return}}function yo(e=process.cwd()){return cr(I(e)?.defaultModel,te().defaultModel,process.env.ANTHROPIC_MODEL,lr())}function et(){let e=ke();if(!Se(e))return{servers:{}};try{return JSON.parse(se(e,"utf8"))}catch{return{servers:{}}}}function wo(e){L(A()),ve(ke(),JSON.stringify(e,null,2)+`
|
|
4
|
+
`,"utf8")}var ie=class{resolvers=[];buffer=[];closed=!1;push(t){if(this.closed)return;let r={type:"user",message:{role:"user",content:t},parent_tool_use_id:null},n=this.resolvers.shift();n?n(r):this.buffer.push(r)}close(){this.closed=!0;for(let t of this.resolvers)t(null);this.resolvers=[]}async*[Symbol.asyncIterator](){for(;;){if(this.buffer.length>0){yield this.buffer.shift();continue}if(this.closed)return;let t=await new Promise(r=>this.resolvers.push(r));if(t===null)return;yield t}}};import{existsSync as cn,readFileSync as At,statSync as ln}from"node:fs";import{basename as un,isAbsolute as dn,resolve as $e}from"node:path";import{createSdkMcpServer as pn,tool as S}from"@anthropic-ai/claude-agent-sdk";import{z as m}from"zod";import{createHash as ur,randomBytes as tt}from"node:crypto";import{createServer as dr}from"node:http";import{spawn as xe}from"node:child_process";var rt=600*1e3;function Pe(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function pr(e){let t=Pe(tt(32)),r=Pe(ur("sha256").update(t).digest()),n=Pe(tt(16)),o=new URL(e.authorizationUrl);return o.searchParams.set("client_id",e.clientId),o.searchParams.set("response_type","code"),o.searchParams.set("redirect_uri",e.redirectUri),o.searchParams.set("scope",e.scopes.join(" ")),o.searchParams.set("state",n),o.searchParams.set("code_challenge",r),o.searchParams.set("code_challenge_method","S256"),e.acrValues&&o.searchParams.set("acr_values",e.acrValues),e.prompt&&o.searchParams.set("prompt",e.prompt),{url:o.toString(),verifier:t,state:n}}function fr(e){if(process.env.D360_NO_BROWSER)return;let t=process.platform;if(t==="win32"){let r=e.replace(/'/g,"''");xe("powershell",["-NoProfile","-NonInteractive","-Command",`Start-Process '${r}'`],{detached:!0,stdio:"ignore"}).unref()}else t==="darwin"?xe("open",[e],{detached:!0,stdio:"ignore"}).unref():xe("xdg-open",[e],{detached:!0,stdio:"ignore"}).unref()}function mr(e,t){let r=new URL(e),n,o=new Promise((s,i)=>{let a=setTimeout(()=>{i(new Error(`Timed out after ${rt/6e4} minutes waiting for the browser login.`)),n.close()},rt);n=dr((d,l)=>{let u=new URL(d.url??"/",`http://${r.host}`);if(u.pathname!==r.pathname){l.writeHead(404).end();return}let f=u.searchParams.get("error"),p=u.searchParams.get("code"),y=u.searchParams.get("state"),b=h=>{l.writeHead(400,{"content-type":"text/html"}),l.end(`<html><body><h3>Login failed</h3><p>${h}</p><p>Return to the terminal.</p></body></html>`),clearTimeout(a),i(new Error(h)),n.close()};if(f)return b(`${f}: ${u.searchParams.get("error_description")??""}`);if(!p)return b("No authorization code in the callback.");if(y!==t)return b("State mismatch \u2014 possible CSRF; try again.");l.writeHead(200,{"content-type":"text/html"}),l.end("<html><body><h3>\u2713 Document360 login complete</h3><p>You can close this tab and return to the terminal.</p></body></html>"),clearTimeout(a),s(p),n.close()}),n.on("error",d=>{clearTimeout(a),i(new Error(`Could not listen on ${r.host} (${d.message}). Another process may hold the port \u2014 set D360_REDIRECT_URI to a free port and retry.`))}),n.listen(Number(r.port)||80,r.hostname)});return{server:n,result:o}}async function ot(e,t){let r=await fetch(e.tokenUrl,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:t}),n=await r.text();if(!r.ok)throw new Error(`Token endpoint ${r.status}: ${n.slice(0,400)}`);return JSON.parse(n)}async function nt(e,t,r){return ot(e,new URLSearchParams({grant_type:"authorization_code",code:t,redirect_uri:e.redirectUri,client_id:e.clientId,code_verifier:r}))}async function st(e,t){return ot(e,new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:e.clientId}))}function it(e,t){let r=Date.now();return{profile:e,accessToken:t.access_token,refreshToken:t.refresh_token,idToken:t.id_token,scope:t.scope,obtainedAt:new Date(r).toISOString(),expiresAt:new Date(r+(t.expires_in??3600)*1e3).toISOString()}}async function xo(e,t,r){let{url:n,verifier:o,state:s}=pr(e);if(t.manual){r("Open this URL in a browser and sign in:"),r(n);let d=await t.promptForRedirect("Paste the full URL you were redirected to (it contains ?code=...):"),l=new URL(d.trim()),u=l.searchParams.get("code"),f=l.searchParams.get("state");if(!u)throw new Error("No code parameter found in the pasted URL.");if(f!==s)throw new Error("State mismatch in the pasted URL \u2014 restart the login.");return nt(e,u,o)}let{result:i}=mr(e.redirectUri,s);r("Opening your browser for Document360 sign-in\u2026"),r(`If it did not open, visit:
|
|
5
|
+
${n}`),fr(n);let a=await i;return nt(e,a,o)}import{existsSync as at,readFileSync as gr,unlinkSync as hr,writeFileSync as yr}from"node:fs";import{join as ct}from"node:path";function lt(){return ct(A(),"auth")}function Te(e){return ct(lt(),`${e}.json`)}function ut(e){L(lt()),yr(Te(e.profile),JSON.stringify(e,null,2))}function ae(e){let t=Te(e);if(!at(t))return null;try{return JSON.parse(gr(t,"utf8"))}catch{return null}}function jo(e){let t=Te(e);return at(t)?(hr(t),!0):!1}function Ce(e,t=60){return Date.now()>=new Date(e.expiresAt).getTime()-t*1e3}function dt(e){if(!e)return null;let t=e.split(".");if(t.length!==3)return null;try{let r=Buffer.from(t[1].replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8");return JSON.parse(r)}catch{return null}}function pt(e,t,r){let n=e.get(t);if(n)return n;let o=r().finally(()=>{e.get(t)===o&&e.delete(t)});return e.set(t,o),o}import{appendFileSync as wr,readdirSync as kr,unlinkSync as br}from"node:fs";import{join as Re}from"node:path";var _r=14,vr=4096;function Sr(){return Re(A(),"logs")}function xr(e){return e.replace(/Bearer\s+[A-Za-z0-9._~+/=-]{8,}/g,"Bearer [redacted]").replace(/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+/g,"[redacted-jwt]")}function Pr(e,t=vr){return e.length<=t?e:`${e.slice(0,t)}\u2026 [+${e.length-t} chars]`}function ce(e){return e===void 0?void 0:Pr(xr(e))}var je=()=>process.env.D360_LOG_BODIES==="1",Tr=/^d360-api-(\d{4}-\d{2}-\d{2})\.jsonl$/;function Cr(e){let t=new Date(Date.now()-_r*24*60*60*1e3).toISOString().slice(0,10);for(let r of kr(e)){let n=r.match(Tr);if(n&&n[1]<t)try{br(Re(e,r))}catch{}}}var ft=!1;function Ee(e){try{let t=Sr();L(t),ft||(ft=!0,Cr(t));let r=Re(t,`d360-api-${e.ts.slice(0,10)}.jsonl`);wr(r,JSON.stringify(e)+`
|
|
6
|
+
`,"utf8")}catch{}}var Rr=new Map,B=class extends Error{},le=class extends Error{constructor(r,n,o){super(r);this.status=n;this.requestId=o}status;requestId};async function mt(e){let t=ae(e.profile);if(!t)throw new B(`Not logged in to Document360 (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);if(!Ce(t))return t.accessToken;if(!t.refreshToken)throw new B(`Document360 session expired (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);return pt(Rr,e.profile,async()=>{let r=ae(e.profile);if(r&&!Ce(r))return r.accessToken;let n=r?.refreshToken??t.refreshToken;try{let o=it(e.profile,await st(e.connection,n));return ut(o),o.accessToken}catch(o){throw new B(`Document360 session expired and refresh failed (${o.message.slice(0,120)}). Run /login (or: d360-writer login --profile ${e.profile})`)}})}function V(e,t){if(t)return t;let r=ae(e.profile),o=(r?dt(r.accessToken)??{}:{}).doc360_project_id;if(typeof o=="string"&&o)return o;throw new B("No Document360 project resolved. Log in (the project is chosen during login) or set the profile's project.projectId in .d360-writer.json.")}async function ue(e,t,r,n={}){let o=await mt(e),s=new URL(r.startsWith("http")?r:`${e.connection.apiUrl}${r}`);for(let[p,y]of Object.entries(n.query??{}))y!==void 0&&s.searchParams.set(p,String(y));let i=n.body!==void 0?JSON.stringify(n.body):void 0,a=Date.now(),d=p=>Ee({ts:new Date().toISOString(),profile:e.profile,method:t,url:s.toString(),ms:Date.now()-a,ok:p.ok,status:p.status,requestId:p.requestId,error:p.error,...p.withBodies?{requestBody:ce(i),responseBody:ce(p.responseBody)}:{}}),l;try{l=await fetch(s,{method:t,headers:{authorization:`Bearer ${o}`,accept:"application/json",...i!==void 0?{"content-type":"application/json"}:{}},body:i})}catch(p){throw d({ok:!1,error:`network: ${p.message}`,withBodies:!0}),p}let u=await l.text(),f={};if(u)try{f=JSON.parse(u)}catch{}if(!l.ok||f.success===!1){let p=f.errors?.map(b=>b.message??b.code).filter(Boolean).join("; ")||u.slice(0,300)||l.statusText;if(d({ok:!1,status:l.status,requestId:f.request_id,error:p,responseBody:u,withBodies:!0}),l.status===401)throw new B(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let y=f.request_id?` [request_id: ${f.request_id}]`:"";throw new le(`Document360 API ${l.status}: ${p}${y}`,l.status,f.request_id)}return d({ok:!0,status:l.status,requestId:f.request_id,responseBody:u,withBodies:je()}),f}async function O(e,t,r){return(await ue(e,"GET",t,r)).data}async function K(e,t,r){return(await ue(e,"POST",t,r)).data}async function gt(e,t,r){return(await ue(e,"PATCH",t,r)).data}async function ht(e,t,r){let n=await mt(e),o=`${e.connection.apiUrl}${t}`,s=Date.now(),i=u=>Ee({ts:new Date().toISOString(),profile:e.profile,method:"POST",url:o,ms:Date.now()-s,ok:u.ok,status:u.status,requestId:u.requestId,error:u.error,...u.withBodies?{requestBody:"[multipart form-data]",responseBody:ce(u.responseBody)}:{}}),a;try{a=await fetch(o,{method:"POST",headers:{authorization:`Bearer ${n}`,accept:"application/json"},body:r})}catch(u){throw i({ok:!1,error:`network: ${u.message}`,withBodies:!0}),u}let d=await a.text(),l={};if(d)try{l=JSON.parse(d)}catch{}if(!a.ok||l.success===!1){let u=l.errors?.map(p=>p.message??p.code).filter(Boolean).join("; ")||d.slice(0,300)||a.statusText;if(i({ok:!1,status:a.status,requestId:l.request_id,error:u,responseBody:d,withBodies:!0}),a.status===401)throw new B(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let f=l.request_id?` [request_id: ${l.request_id}]`:"";throw new le(`Document360 upload ${a.status}: ${u}${f}`,a.status,l.request_id)}return i({ok:!0,status:a.status,requestId:l.request_id,responseBody:d,withBodies:je()}),l.data}async function q(e,t,r={}){let n=[],o;for(let s=0;s<50;s++){let i=await ue(e,"GET",t,{...r,query:{...r.query,cursor:o}});if(Array.isArray(i.data)&&n.push(...i.data),!i.pagination?.has_more||!i.pagination.next_cursor)break;o=i.pagination.next_cursor}return n}async function Bo(e,t){return q(e,`/v3/projects/${t}/workspaces`)}async function qo(e,t,r){return O(e,`/v3/projects/${t}/articles/${r}`,{query:{content_mode:"raw"}})}import{existsSync as jr,readFileSync as Er,writeFileSync as Dr}from"node:fs";import{join as Ar}from"node:path";function yt(e){return Ar(e,"d360-category-map.json")}function wt(e){let t=yt(e);if(!jr(t))return null;try{return JSON.parse(Er(t,"utf8"))}catch{return null}}function kt(e){return typeof e=="string"?{id:e}:{...e}}function $(e,t){let n=wt(e)?.[t];if(!n)return null;let o={};for(let[s,i]of Object.entries(n.articles??{}))o[s]=kt(i);return{environment:n.environment,projectId:n.projectId,workspaceId:n.workspaceId,lastAnalyzedCommit:n.lastAnalyzedCommit,categories:{...n.categories??{}},articles:o}}function bt(e,t){for(let[r,n]of Object.entries(e.articles))if(n.id===t)return r;return null}function de(e,t,r,n){let o=wt(e),s=o?.[t];if(!o||!s)return!1;s.articles??={};let i=s.articles[r],a=i!==void 0?kt(i):null;return s.articles[r]={...a??{},...n,lastSyncedAt:new Date().toISOString()},Dr(yt(e),JSON.stringify(o,null,2)+`
|
|
7
|
+
`,"utf8"),!0}import{createHash as Ir}from"node:crypto";function Mr(e){let t=e.replace(/^\uFEFF/,"").replace(/\r\n/g,`
|
|
8
8
|
`);return t=t.replace(/\n*$/,""),t===""?"":t+`
|
|
9
|
-
`}function H(e){return"sha256:"+
|
|
10
|
-
${t??""}`)}import{existsSync as Mr,readdirSync as Or,readFileSync as $r,statSync as Ur}from"node:fs";import{join as De}from"node:path";var bt={berlin:{apiUrl:"https://apihub.berlin.document360.net",portalUrl:"https://portal.berlin.document360.net",authorizationUrl:"https://identity.berlin.document360.net/connect/authorize",tokenUrl:"https://identity.berlin.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"},sharjah:{apiUrl:"https://apihub.sharjah.document360.net",portalUrl:"https://portal.sharjah.document360.net",authorizationUrl:"https://identity.sharjah.document360.net/connect/authorize",tokenUrl:"https://identity.sharjah.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"}};function Ar(){return Object.keys(bt)}function qo(e="berlin"){return pe({environment:e})}function pe(e){let t=e.environment??"berlin",r=bt[t];if(!r)throw new Error(`Unknown Document360 environment "${t}". Known: ${Ar().join(", ")}`);let n=process.env.D360_SCOPES?.split(/[\s,]+/).filter(Boolean);return{name:t,apiUrl:process.env.D360_API_URL??e.apiUrl??r.apiUrl,portalUrl:process.env.D360_PORTAL_URL??e.portalUrl??r.portalUrl,authorizationUrl:process.env.D360_AUTHORIZATION_URL??e.authorizationUrl??r.authorizationUrl,tokenUrl:process.env.D360_TOKEN_URL??e.tokenUrl??r.tokenUrl,clientId:process.env.D360_CLIENT_ID??e.clientId??r.clientId,scopes:n??e.scopes??r.scopes,acrValues:process.env.D360_ACR_VALUES??e.acrValues??r.acrValues,redirectUri:process.env.D360_REDIRECT_URI??e.redirectUri??r.redirectUri,prompt:process.env.D360_PROMPT??e.prompt??r.prompt}}function F(e,t){let r=M(e);if(r===null){let s=t??"berlin";return{name:s,connection:pe({environment:s}),project:{},production:!1}}let{name:n,profile:o}=Qe(r,t);return{name:n,connection:pe(o.connection),project:o.project??{},production:o.production===!0}}function zo(e,t,r){let n=M(e);n?.profiles?.[t]&&(n.profiles[t].project={...n.profiles[t].project,...r},Ze(n,e))}function _t(e,t){if(e?.docsDir)return e.docsDir.replace(/\\/g,"/").replace(/\/+$/,"");let r=new Set(Object.keys(t?.articles??{}).map(n=>n.split("/")[0]).filter(Boolean));if(r.size===1)return[...r][0];throw new Error(r.size===0?'Cannot locate the docs folder: no mapped articles yet. Set "docsDir" in .d360-writer.json.':`Cannot locate the docs folder: mapped articles span multiple roots (${[...r].join(", ")}). Set "docsDir" in .d360-writer.json.`)}var Ir=4;function vt(e,t,r,n={getAll:q,get:O}){let o=`/v3/projects/${t}`;return{async fetch(s){let[i,a]=await Promise.all([n.getAll(e,`${o}/workspaces/${r}/articles`),n.getAll(e,`${o}/workspaces/${r}/categories`)]),d=new Map;for(let y of i)d.set(y.id,{id:y.id,title:y.title,categoryId:y.category_id,hidden:y.hidden});let u=new Map;for(let y of a)u.set(y.id,{id:y.id,name:y.name,parentId:y.parent_category_id});let l=s.filter(y=>d.has(y)),f=0,p=async()=>{for(;f<l.length;){let y=l[f++],b=await n.get(e,`${o}/articles/${y}`,{query:{content_mode:"raw"}}),h=d.get(y);h.title=b.title??h.title,h.content=b.content,h.latestVersion=b.latest_version,h.modifiedAt=b.modified_at,h.contentHash=Y(b.title??h.title,b.content)}};return await Promise.all(Array.from({length:Math.min(Ir,l.length)},p)),{articles:d,categories:u}}}}function Nr(e){if(!e.mapped)return e.localExists?"untracked-local":"untracked-remote";if(!e.localExists&&!e.remoteExists)return"orphaned";if(!e.localExists)return"deleted-local";if(!e.remoteExists)return"deleted-remote";if(!e.hasBase)return"unknown-base";let t=e.localHashNow!==e.baseLocalHash,r=e.remoteContentHashNow!==e.baseRemoteContentHash;return t&&r?"conflict":t?"local-ahead":r?"remote-ahead":"in-sync"}function Lr(e,t){let r=De(e,t);if(!Mr(r))return[];let n=[],o=(s,i)=>{for(let a of Or(s)){let d=De(s,a),u=i?`${i}/${a}`:a;Ur(d).isDirectory()?o(d,u):a.endsWith(".md")&&n.push(u)}};return o(r,t.replace(/\\/g,"/")),n}function Br(e,t){let r=new Set(Object.values(e.categories)),n=new Map;for(let i of t.categories.values()){if(!i.parentId)continue;let a=n.get(i.parentId)??[];a.push(i.id),n.set(i.parentId,a)}let o=new Set,s=[...r];for(;s.length>0;){let i=s.shift();o.has(i)||(o.add(i),s.push(...n.get(i)??[]))}return o}async function St(e){let t=F(e.cwd,e.profileName),r=M(e.cwd),n=$(e.cwd,t.name);if(!n)throw new Error(`No d360-category-map.json section for profile "${t.name}". Publish at least one article first (/publish).`);let o={profile:t.name,connection:t.connection},s=n.projectId??t.project.projectId??V(o),i=n.workspaceId??t.project.workspaceId;if(!i)throw new Error(`No workspace recorded for profile "${t.name}". Run /workspace to select one.`);let a=_t(r,n),d=Object.values(n.articles).map(w=>w.id),l=await(e.provider??vt(o,s,i)).fetch(d),f=new Set(Lr(e.cwd,a)),p=[];for(let[w,R]of Object.entries(n.articles)){let z=f.has(w);f.delete(w);let C=l.articles.get(R.id),Q=!!(R.localHash&&R.remoteContentHash),j=Nr({mapped:!0,localExists:z,remoteExists:C!==void 0,hasBase:Q,localHashNow:z?H($r(De(e.cwd,w),"utf8")):void 0,baseLocalHash:R.localHash,remoteContentHashNow:C?.contentHash,baseRemoteContentHash:R.remoteContentHash}),U=[];if(j==="remote-ahead"||j==="conflict"){let J=C?.modifiedAt?.slice(0,10);U.push(`remote edited${J?` ${J}`:""}${C?.latestVersion?` (v${C.latestVersion})`:""}`)}C?.hidden&&U.push("hidden on D360"),p.push({path:w,articleId:R.id,title:C?.title,status:j,detail:U.length>0?U.join("; "):void 0})}for(let w of[...f].sort())p.push({path:w,articleId:null,status:"untracked-local"});let y=new Set(d),b=Br(n,l);for(let w of l.articles.values())y.has(w.id)||!w.categoryId||!b.has(w.categoryId)||p.push({path:null,articleId:w.id,title:w.title,status:"untracked-remote",detail:w.hidden?"hidden on D360":void 0});let h={};for(let w of p)h[w.status]=(h[w.status]??0)+1;return{profile:t.name,projectId:s,workspaceId:i,docsRoot:a,entries:p,counts:h,generatedAt:new Date().toISOString()}}import{posix as Ae}from"node:path";var qr=/\[([^\]]+)\]\(([^)\s]+)\)/g;function Ie(e,t,r){if(!r)return{content:e,resolved:0,unresolved:0};let n=Ae.dirname(t.replace(/\\/g,"/")),o=0,s=0;return{content:e.replace(qr,(a,d,u)=>{if(/^(https?:|mailto:|#|\/)/i.test(u)||!/\.(md|markdown)(#[^)]*)?$/i.test(u))return a;let l=u.indexOf("#"),f=l===-1?u:u.slice(0,l),p=l===-1?"":u.slice(l),y=Ae.normalize(Ae.join(n,f)).replace(/^\.\//,""),b=r.articles[y]?.url;return b?(o++,`[${d}](${b}${p})`):(s++,d)}),resolved:o,unresolved:s}}import{readdirSync as Fr}from"node:fs";import{join as fe}from"node:path";var Wr=new Set([".git","node_modules","dist","build","bin","obj","out","target",".vs",".vscode",".idea",".next","coverage",".turbo",".cache","packages"]),Hr=40,zr=3,Me=5e3,Jr=/\.(csproj|vbproj|fsproj)$|^(package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|pom\.xml|build\.gradle(\.kts)?)$/i,Gr=/\.sln$|^(Directory\.(Build|Packages)\.props|global\.json|go\.work)$/i;function Vr(e){return/\.(csproj|vbproj|fsproj|sln)$/i.test(e)||/^(Directory\.(Build|Packages)\.props|global\.json)$/i.test(e)?".NET":/^(package\.json|angular\.json|tsconfig\.json)$/i.test(e)?"JS/TS":/^(pyproject\.toml|requirements\.txt|setup\.py)$/i.test(e)?"Python":/^(go\.mod|go\.work)$/i.test(e)?"Go":/^Cargo\.toml$/i.test(e)?"Rust":/^(pom\.xml|build\.gradle(\.kts)?)$/i.test(e)?"Java":null}var Kr=/(?:^|[.\-_])(web|portal|ui|app|apps|client|site|widget|admin|dashboard|frontend|api)(?:$|[.\-_])/i,Yr=/(?:^|[.\-_])(tests?|specs?|helm|docker|migrations?|functions?|ci|builds?|scripts|tools|proxy|coverage|release|libs?|redis|signalr|websocket|dataaccess|nuget|sdk|infra|infrastructure|deploy|deployment|e2e)(?:$|[.\-_])/i;function Oe(e){let t=[],r=[];try{for(let n of Fr(e,{withFileTypes:!0}))n.isDirectory()?!n.name.startsWith(".")&&!Wr.has(n.name)&&t.push(n.name):n.isFile()&&r.push(n.name)}catch{}return{dirs:t,files:r}}function Qr(e){let t=new Set;for(let r of e){let n=Vr(r);n&&t.add(n)}return t}var xt=e=>e.some(t=>Jr.test(t)),Zr=e=>e.some(t=>Gr.test(t));function Xr(e){let t=0,r=n=>{if(t>=Me)return;let{dirs:o,files:s}=Oe(n);t+=s.length;for(let i of o){if(t>=Me)return;r(fe(n,i))}};return r(e),Math.min(t,Me)}function en(e,t){let r=e.split("/").pop()??e,n=t.size?[...t].join("+"):"";return Yr.test(r)?{recommended:!1,reason:`tests/infrastructure${n?` \xB7 ${n}`:""}`}:Kr.test(r)?{recommended:!0,reason:`user-facing surface${n?` \xB7 ${n}`:""}`}:{recommended:!1,reason:n?`${n} project`:"source folder"}}function Pt(e){let t=[],r=(s,i)=>{let a=Qr(i.files),{recommended:d,reason:u}=en(s,a);t.push({path:s,fileCount:Xr(fe(e,s)),stacks:[...a],recommended:d,reason:u})},n=(s,i)=>{let a=s?fe(e,s):e,d=Oe(a);if(s&&xt(d.files)){r(s,d);return}if(s&&d.dirs.length===0){r(s,d);return}let u=f=>xt(Oe(fe(a,f)).files);if((Zr(d.files)||d.dirs.some(u)||s==="")&&i<zr){for(let f of d.dirs)n(s?`${s}/${f}`:f,i+1);return}s&&r(s,d)};n("",0),t.sort((s,i)=>Number(i.recommended)-Number(s.recommended)||s.path.localeCompare(i.path));let o=t.filter(s=>s.recommended);return t.slice(0,Math.max(Hr,o.length))}var re={"claude-fable-5":{inputPerMTok:10,outputPerMTok:50},"claude-opus-4-8":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-7":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-6":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-5":{inputPerMTok:5,outputPerMTok:25},"claude-sonnet-4-6":{inputPerMTok:3,outputPerMTok:15},"claude-sonnet-4-5":{inputPerMTok:3,outputPerMTok:15},"claude-haiku-4-5":{inputPerMTok:1,outputPerMTok:5}},tn={opus:"claude-opus-4-8",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5",fable:"claude-fable-5"},Tt="claude-opus-4-8";function Rt(e){let t=e&&(tn[e.toLowerCase()]??e);if(t&&re[t])return{model:t,rate:re[t],assumed:!1};if(t){let r=Object.keys(re).filter(n=>t.startsWith(n)).sort((n,o)=>o.length-n.length)[0];if(r)return{model:r,rate:re[r],assumed:!1}}return{model:Tt,rate:re[Tt],assumed:!0}}function ne(e,t){return e/1e6*t}var rn=3.5,nn={convert:{inFactor:1.3,outFactor:1.1,overheadInput:85e3,overheadOutput:16e3},rewrite:{inFactor:1.4,outFactor:1.2,overheadInput:95e3,overheadOutput:19e3},publish:{inFactor:1.1,outFactor:.2,overheadInput:35e3,overheadOutput:4e3},audit:{inFactor:1.2,outFactor:.15,overheadInput:7e4,overheadOutput:9e3}},Ct=.7,jt=1.6,me=e=>Math.round(e);function Et(e){let{op:t,model:r}=e,n=nn[t],{model:o,rate:s,assumed:i}=Rt(r),a=e.files.filter(h=>!(h.bytes>0)).map(h=>h.path),d=e.files.filter(h=>h.bytes>0),u=0,l=0;for(let h of d){let w=h.bytes/rn;u+=n.overheadInput+w*n.inFactor,l+=n.overheadOutput+w*n.outFactor}let f=[me(u*Ct),me(u*jt)],p=[me(l*Ct),me(l*jt)],y=[ne(f[0],s.inputPerMTok)+ne(p[0],s.outputPerMTok),ne(f[1],s.inputPerMTok)+ne(p[1],s.outputPerMTok)],b=`Rough estimate (per-article agent overhead + content), priced at ${o} list rates`+(i?" (model unset \u2014 assumed)":"")+". Actuals vary with cache reuse and reasoning depth; the live cost meter reconciles as the run proceeds.";return{model:o,modelAssumed:i,op:t,articles:d.length,skipped:a,inputTokens:f,outputTokens:p,usd:y,note:b}}var At="/v3/projects",P=e=>`${At}/${e}`;function x(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)??'{"success":true}'}]}}function v(e){return{content:[{type:"text",text:e instanceof Error?e.message:String(e)}],isError:!0}}function It(e,t){let r={profile:e.name,connection:e.connection},n=e.project.projectId,o=e.project.workspaceId,s=()=>V(r,n),i=c=>{let g=c??o;if(!g)throw new Error("No workspace_id given and none in the profile. Call d360_list_workspaces first.");return g},a=(c,g)=>{if(t.cwd)try{let k=$e(t.cwd,c);de(t.cwd,e.name,c.replace(/\\/g,"/"),{...g,...on(k)?{localHash:H(Dt(k,"utf8"))}:{}})}catch{}},d=()=>t.writesAllowed?null:v(`Refusing to write to PRODUCTION profile "${e.name}". Authorize this session first: run /allow-prod in the REPL, or pass --yes in one-shot/CI.`),u=S("d360_context","Report the active Document360 connection: profile name, environment, project/workspace id, and whether it is a production profile. Call this before maintaining d360-category-map.json so you scope IDs to the right profile.",{},async()=>{try{return x({profile:e.name,environment:e.connection.name,production:e.production,projectId:n??V(r),workspaceId:o??null})}catch(c){return v(c)}}),l=S("d360_list_projects","List all Document360 projects the signed-in user can access (id, name, sub-domain, status).",{},async()=>{try{return x(await q(r,At))}catch(c){return v(c)}}),f=S("d360_list_workspaces","List workspaces (project versions) for the current project. Each has id, name, slug, is_default, workspace_type.",{project_id:m.string().optional().describe("Defaults to the logged-in/config project.")},async c=>{try{return x(await q(r,`${P(c.project_id??s())}/workspaces`))}catch(g){return v(g)}}),p=S("d360_list_categories","List categories in a workspace (the folder structure for articles).",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await q(r,`${P(c.project_id??s())}/workspaces/${g}/categories`)):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),y=S("d360_list_articles","List articles in a workspace (id, title, status, category). Use to see existing docs before writing.",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await q(r,`${P(c.project_id??s())}/workspaces/${g}/articles`)):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),b=S("d360_get_article","Get a single article including its content. content_mode=raw returns the stored markdown source (best for editing); display returns processed content.",{article_id:m.string(),content_mode:m.enum(["raw","display"]).optional().describe("raw = stored markdown source (default), display = processed."),published:m.boolean().optional().describe("Read the published version instead of the latest draft."),project_id:m.string().optional()},async c=>{try{return x(await O(r,`${P(c.project_id??s())}/articles/${c.article_id}`,{query:{content_mode:c.content_mode,published:c.published}}))}catch(g){return v(g)}}),h=S("d360_ai_query","Ask Document360's AI search over a workspace's published content. Returns an answer grounded in existing articles \u2014 use to check what's already documented.",{query:m.string(),workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await K(r,`${P(c.project_id??s())}/workspaces/ai/query`,{body:{query:c.query,workspace_id:g}})):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),w=S("d360_sync_status","Deterministic drift report: local docs tree vs Document360, classified against the sync bases in d360-category-map.json. Call this BEFORE any docs gap/coverage analysis \u2014 it proves whether the local docs tree can be trusted as the Document360 inventory (no need to fetch article content). Statuses: local-ahead (push pending), remote-ahead (portal edit to pull), conflict, untracked-local/remote, deleted-local/remote, unknown-base (no base recorded yet).",{},async()=>{try{if(!t.cwd)return v("Sync status needs a repo context (no cwd configured for this session).");let c=await St({cwd:t.cwd,profileName:e.name});return x({profile:c.profile,docsRoot:c.docsRoot,generatedAt:c.generatedAt,counts:c.counts,attention:c.entries.filter(g=>g.status!=="in-sync")})}catch(c){return v(c)}}),R=S("d360_repo_inventory","Deterministic inventory of candidate source folders for documentation scope (no file reads of your own needed). Returns project-level folders with: path, fileCount, detected stacks, a `recommended` flag (user-facing surfaces pre-recommended; tests/infrastructure not), and a one-line reason. Use this when analyzing a large/multi-project repo to decide which folders back the docs \u2014 it is the same data the /scope picker pre-ticks, so your recommendation and the picker agree. Cheap; safe to call before reading any source.",{},async()=>{try{return t.cwd?x(Pt(t.cwd)):v("Repo inventory needs a repo context (no cwd configured for this session).")}catch(c){return v(c)}}),z=S("d360_estimate_cost",'Deterministic, no-network token/cost ESTIMATE for a bulk operation. Call this BEFORE you propose a repo-scale run (convert / rewrite / publish / audit MANY articles) so you can show the user the article count and a cost band and get an explicit go-ahead \u2014 see the "Confirm before bulk or irreversible actions" rule. It sizes work from local file BYTES (coarse \u2014 returns [low, high] bands, not a quote). Omit `paths` to estimate over every article tracked in d360-category-map.json; pass `paths` for a subset. Cheap and read-only; safe to call before any write.',{op:m.enum(["convert","rewrite","publish","audit"]).describe("What you intend to do to each article (drives the output-size heuristic)."),paths:m.array(m.string()).optional().describe("Repo-relative .md paths. Omit or leave empty to estimate over all articles in the category map.")},async({op:c,paths:g})=>{try{if(!t.cwd)return v("Cost estimate needs a repo context (no cwd configured for this session).");let k;if(g&&g.length>0)k=g.map(_=>_.replace(/\\/g,"/"));else{let _=$(t.cwd,e.name);if(k=_?Object.keys(_.articles):[],k.length===0)return v("No articles are tracked in d360-category-map.json for this profile, and no `paths` were given. Pass `paths` explicitly (the repo-relative .md files you intend to operate on).")}let E=k.map(_=>{let D=0;try{D=sn($e(t.cwd,_)).size}catch{D=0}return{path:_,bytes:D}});return x(Et({files:E,op:c,model:t.model}))}catch(k){return v(k)}}),C=S("d360_create_category","Create a category (a docs folder). Returns the new category id.",{name:m.string(),workspace_id:m.string().optional(),parent_category_id:m.string().optional().describe("Omit for a top-level category."),content:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/categories`,{body:{name:c.name,workspace_id:i(c.workspace_id),parent_category_id:c.parent_category_id,content:c.content,slug:c.slug,order:c.order,content_type:"markdown"}}))}catch(k){return v(k)}}),Q=S("d360_create_article","Create a DRAFT article in a category. The body is always Markdown (product rule \u2014 we never create WYSIWYG/Block articles). Returns the new article id. Does not publish.",{title:m.string(),category_id:m.string(),content:m.string().optional().describe("Markdown body."),workspace_id:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional(),local_path:m.string().optional().describe("Repo-relative path of the local .md this article mirrors (e.g. user-docs/01-intro/01-a.md). When given, the new article id + sync base are recorded into d360-category-map.json automatically \u2014 do not edit the articles map yourself.")},async c=>{let g=d();if(g)return g;try{let k=c.project_id??s(),E=c.content;typeof E=="string"&&c.local_path&&t.cwd&&(E=Ie(E,c.local_path,$(t.cwd,e.name)).content);let _=await K(r,`${P(k)}/articles/bulk`,{body:{articles:[{title:c.title,category_id:c.category_id,workspace_id:i(c.workspace_id),content:E,slug:c.slug,order:c.order,content_type:"markdown"}]}}),D=Array.isArray(_)?_[0]:void 0;if(c.local_path&&typeof D?.id=="string"){let I;try{let W=await O(r,`${P(k)}/articles/${D.id}`,{});I=typeof W?.url=="string"?W.url:void 0}catch{}a(c.local_path,{id:D.id,remoteContentHash:Y(c.title,E),remoteVersion:1,...I?{url:I}:{}})}return x(_)}catch(k){return v(k)}}),j=S("d360_update_article","Update an article's title/content/category. Edits the latest draft by default; set auto_fork to safely edit a published article (creates a new draft version).",{article_id:m.string(),title:m.string().optional(),content:m.string().optional().describe("Markdown body."),category_id:m.string().optional(),hidden:m.boolean().optional(),version_number:m.number().optional(),auto_fork:m.boolean().optional().describe("If the target version is published, fork a new draft instead of erroring."),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{let{article_id:k,project_id:E,..._}=c,D=E??s(),I=typeof _.content=="string"&&t.cwd?$(t.cwd,e.name):null,W=I?kt(I,k):null;typeof _.content=="string"&&W&&I&&(_.content=Ie(_.content,W,I).content);let G=null;if(typeof _.content=="string"&&t.onUiEvent)try{G=await O(r,`${P(D)}/articles/${k}`,{query:{content_mode:"raw"}})}catch{}let Be=e.project.languageCode??G?.lang_code,qe=await mt(r,`${P(D)}/articles/${k}`,{body:_,...Be?{query:{lang_code:Be}}:{}});if(G&&typeof _.content=="string"&&(G.content??"")!==_.content&&t.onUiEvent?.({type:"article_diff",articleId:k,title:G.title??null,oldContent:G.content??"",newContent:_.content}),typeof _.content=="string"&&W)try{let X=qe,Fe=_.title??X?.title??G?.title;typeof Fe=="string"&&a(W,{id:k,remoteContentHash:Y(Fe,_.content),...typeof X?.latest_version=="number"?{remoteVersion:X.latest_version}:{},...typeof X?.modified_at=="string"?{remoteModifiedAt:X.modified_at}:{}})}catch{}return x(qe)}catch(k){return v(k)}}),U=S("d360_fork_article","Fork a published article into a new draft version \u2014 the safe way to start editing live content.",{article_id:m.string(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/fork`))}catch(k){return v(k)}}),J=S("d360_publish_article","Publish an article version. This makes the draft live to readers \u2014 only call when the user explicitly asks to publish.",{article_id:m.string(),version_number:m.number(),workspace_id:m.string().optional(),message:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/publish`,{body:{workspace_id:i(c.workspace_id),version_number:c.version_number,message:c.message}}))}catch(k){return v(k)}}),T=S("d360_unpublish_article","Unpublish an article version, reverting it to draft (removes it from readers).",{article_id:m.string(),version_number:m.number().describe("The published version to unpublish (see d360_get_article)."),workspace_id:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/unpublish`,{body:{workspace_id:i(c.workspace_id),version_number:c.version_number}}))}catch(k){return v(k)}}),Ht=S("d360_upload_drive_file","Upload a local file (e.g. a captured screenshot PNG) to Drive and return its URL for embedding in an article. Uploads to the default folder unless folder_id is given.",{file_path:m.string().describe("Local path to the file (absolute, or relative to the repo)."),folder_id:m.string().optional(),title:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{let k=c.project_id??s(),E=cn(c.file_path)?c.file_path:$e(process.cwd(),c.file_path),_=c.folder_id;if(!_&&(_=(await O(r,`${P(k)}/drive/folders/default`))?.id,!_))return v("Could not resolve the default Drive folder.");let D=Dt(E),I=new FormData;return I.append("file",new Blob([D]),c.title??an(E)),x(await gt(r,`${P(k)}/drive/folders/${_}/files`,I))}catch(k){return v(k)}}),zt=S("d360_search_drive","Search Drive files (e.g. to reuse an already-uploaded screenshot instead of uploading a duplicate).",{search_keyword:m.string().optional(),allow_images_only:m.boolean().optional(),project_id:m.string().optional()},async c=>{try{return x(await q(r,`${P(c.project_id??s())}/drive/search`,{query:{search_keyword:c.search_keyword,allow_images_only:c.allow_images_only}}))}catch(g){return v(g)}});return ln({name:"document360",version:"0.2.0",instructions:"First-party Document360 tools. The signed-in user's permissions apply server-side. Project/workspace default from the active profile; pass ids explicitly to override. Create articles as DRAFTS; only call d360_publish_article when the user explicitly asks to publish. All articles are Markdown \u2014 the tools enforce this; never attempt WYSIWYG/Block content. To edit a published article, fork it (d360_fork_article) or update with auto_fork. Before any docs gap/coverage analysis, call d360_sync_status to verify the local docs tree matches Document360. When scoping a large repo (which folders back the docs), call d360_repo_inventory for the candidate folders. Before a bulk run (converting/publishing/auditing many articles), call d360_estimate_cost and show the user the count + cost band first. Auth errors mean the session expired \u2014 tell the user to run /login (works inside this session).",tools:[u,l,f,p,y,b,h,w,R,z,C,Q,j,U,J,T,Ht,zt]})}var Ut="CLAUDE.md";function Mt(e){if(!Ue(e))return[];let t=[];for(let r of un(e)){let n=Ne(e,r);if(r===Ut||!dn(n).isDirectory())continue;let o=Ne(n,"SKILL.md");Ue(o)&&t.push({name:r,body:$t(o,"utf8")})}return t}function Ot(e){let t=Ne(e,Ut);return Ue(t)?$t(t,"utf8"):null}function fn(e,t){let r=Ke(),n=Ye(e),o=Ot(r),s=Ot(n),i=Mt(r),a=Mt(n),d=new Set(a.map(f=>f.name)),u=[...i.filter(f=>!d.has(f.name)),...a],l=[];if(o&&l.push(o),s&&l.push(`# Project addendum
|
|
9
|
+
`}function H(e){return"sha256:"+Ir("sha256").update(Mr(e),"utf8").digest("hex")}function Y(e,t){return H(`${e??""}
|
|
10
|
+
${t??""}`)}import{existsSync as Ur,readdirSync as Nr,readFileSync as Lr,statSync as Br}from"node:fs";import{join as De}from"node:path";var _t={berlin:{apiUrl:"https://apihub.berlin.document360.net",portalUrl:"https://portal.berlin.document360.net",authorizationUrl:"https://identity.berlin.document360.net/connect/authorize",tokenUrl:"https://identity.berlin.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"},sharjah:{apiUrl:"https://apihub.sharjah.document360.net",portalUrl:"https://portal.sharjah.document360.net",authorizationUrl:"https://identity.sharjah.document360.net/connect/authorize",tokenUrl:"https://identity.sharjah.document360.net/connect/token",clientId:"d360WriterAgentClient",scopes:["openid","profile","email","customerApi","offline_access"],acrValues:"project_select",redirectUri:"http://127.0.0.1:3223/callback",prompt:"login"}};function Or(){return Object.keys(_t)}function Vo(e="berlin"){return pe({environment:e})}function pe(e){let t=e.environment??"berlin",r=_t[t];if(!r)throw new Error(`Unknown Document360 environment "${t}". Known: ${Or().join(", ")}`);let n=process.env.D360_SCOPES?.split(/[\s,]+/).filter(Boolean);return{name:t,apiUrl:process.env.D360_API_URL??e.apiUrl??r.apiUrl,portalUrl:process.env.D360_PORTAL_URL??e.portalUrl??r.portalUrl,authorizationUrl:process.env.D360_AUTHORIZATION_URL??e.authorizationUrl??r.authorizationUrl,tokenUrl:process.env.D360_TOKEN_URL??e.tokenUrl??r.tokenUrl,clientId:process.env.D360_CLIENT_ID??e.clientId??r.clientId,scopes:n??e.scopes??r.scopes,acrValues:process.env.D360_ACR_VALUES??e.acrValues??r.acrValues,redirectUri:process.env.D360_REDIRECT_URI??e.redirectUri??r.redirectUri,prompt:process.env.D360_PROMPT??e.prompt??r.prompt}}function F(e,t){let r=I(e);if(r===null){let s=t??"berlin";return{name:s,connection:pe({environment:s}),project:{},production:!1}}let{name:n,profile:o}=Ze(r,t);return{name:n,connection:pe(o.connection),project:o.project??{},production:o.production===!0}}function Zo(e,t,r){let n=I(e);n?.profiles?.[t]&&(n.profiles[t].project={...n.profiles[t].project,...r},Xe(n,e))}function vt(e,t){if(e?.docsDir)return e.docsDir.replace(/\\/g,"/").replace(/\/+$/,"");let r=new Set(Object.keys(t?.articles??{}).map(n=>n.split("/")[0]).filter(Boolean));if(r.size===1)return[...r][0];throw new Error(r.size===0?'Cannot locate the docs folder: no mapped articles yet. Set "docsDir" in .d360-writer.json.':`Cannot locate the docs folder: mapped articles span multiple roots (${[...r].join(", ")}). Set "docsDir" in .d360-writer.json.`)}var $r=4;function St(e,t,r,n={getAll:q,get:O}){let o=`/v3/projects/${t}`;return{async fetch(s){let[i,a]=await Promise.all([n.getAll(e,`${o}/workspaces/${r}/articles`),n.getAll(e,`${o}/workspaces/${r}/categories`)]),d=new Map;for(let y of i)d.set(y.id,{id:y.id,title:y.title,categoryId:y.category_id,hidden:y.hidden});let l=new Map;for(let y of a)l.set(y.id,{id:y.id,name:y.name,parentId:y.parent_category_id});let u=s.filter(y=>d.has(y)),f=0,p=async()=>{for(;f<u.length;){let y=u[f++],b=await n.get(e,`${o}/articles/${y}`,{query:{content_mode:"raw"}}),h=d.get(y);h.title=b.title??h.title,h.content=b.content,h.latestVersion=b.latest_version,h.modifiedAt=b.modified_at,h.contentHash=Y(b.title??h.title,b.content)}};return await Promise.all(Array.from({length:Math.min($r,u.length)},p)),{articles:d,categories:l}}}}function qr(e){if(!e.mapped)return e.localExists?"untracked-local":"untracked-remote";if(!e.localExists&&!e.remoteExists)return"orphaned";if(!e.localExists)return"deleted-local";if(!e.remoteExists)return"deleted-remote";if(!e.hasBase)return"unknown-base";let t=e.localHashNow!==e.baseLocalHash,r=e.remoteContentHashNow!==e.baseRemoteContentHash;return t&&r?"conflict":t?"local-ahead":r?"remote-ahead":"in-sync"}function Fr(e,t){let r=De(e,t);if(!Ur(r))return[];let n=[],o=(s,i)=>{for(let a of Nr(s)){let d=De(s,a),l=i?`${i}/${a}`:a;Br(d).isDirectory()?o(d,l):a.endsWith(".md")&&n.push(l)}};return o(r,t.replace(/\\/g,"/")),n}function Wr(e,t){let r=new Set(Object.values(e.categories)),n=new Map;for(let i of t.categories.values()){if(!i.parentId)continue;let a=n.get(i.parentId)??[];a.push(i.id),n.set(i.parentId,a)}let o=new Set,s=[...r];for(;s.length>0;){let i=s.shift();o.has(i)||(o.add(i),s.push(...n.get(i)??[]))}return o}async function xt(e){let t=F(e.cwd,e.profileName),r=I(e.cwd),n=$(e.cwd,t.name);if(!n)throw new Error(`No d360-category-map.json section for profile "${t.name}". Publish at least one article first (/publish).`);let o={profile:t.name,connection:t.connection},s=n.projectId??t.project.projectId??V(o),i=n.workspaceId??t.project.workspaceId;if(!i)throw new Error(`No workspace recorded for profile "${t.name}". Run /workspace to select one.`);let a=vt(r,n),d=Object.values(n.articles).map(w=>w.id),u=await(e.provider??St(o,s,i)).fetch(d),f=new Set(Fr(e.cwd,a)),p=[];for(let[w,C]of Object.entries(n.articles)){let z=f.has(w);f.delete(w);let R=u.articles.get(C.id),Q=!!(C.localHash&&C.remoteContentHash),j=qr({mapped:!0,localExists:z,remoteExists:R!==void 0,hasBase:Q,localHashNow:z?H(Lr(De(e.cwd,w),"utf8")):void 0,baseLocalHash:C.localHash,remoteContentHashNow:R?.contentHash,baseRemoteContentHash:C.remoteContentHash}),U=[];if(j==="remote-ahead"||j==="conflict"){let J=R?.modifiedAt?.slice(0,10);U.push(`remote edited${J?` ${J}`:""}${R?.latestVersion?` (v${R.latestVersion})`:""}`)}R?.hidden&&U.push("hidden on D360"),p.push({path:w,articleId:C.id,title:R?.title,status:j,detail:U.length>0?U.join("; "):void 0})}for(let w of[...f].sort())p.push({path:w,articleId:null,status:"untracked-local"});let y=new Set(d),b=Wr(n,u);for(let w of u.articles.values())y.has(w.id)||!w.categoryId||!b.has(w.categoryId)||p.push({path:null,articleId:w.id,title:w.title,status:"untracked-remote",detail:w.hidden?"hidden on D360":void 0});let h={};for(let w of p)h[w.status]=(h[w.status]??0)+1;return{profile:t.name,projectId:s,workspaceId:i,docsRoot:a,entries:p,counts:h,generatedAt:new Date().toISOString()}}import{posix as Ae}from"node:path";var Hr=/\[([^\]]+)\]\(([^)\s]+)\)/g;function Ie(e,t,r){if(!r)return{content:e,resolved:0,unresolved:0};let n=Ae.dirname(t.replace(/\\/g,"/")),o=0,s=0;return{content:e.replace(Hr,(a,d,l)=>{if(/^(https?:|mailto:|#|\/)/i.test(l)||!/\.(md|markdown)(#[^)]*)?$/i.test(l))return a;let u=l.indexOf("#"),f=u===-1?l:l.slice(0,u),p=u===-1?"":l.slice(u),y=Ae.normalize(Ae.join(n,f)).replace(/^\.\//,""),b=r.articles[y]?.url;return b?(o++,`[${d}](${b}${p})`):(s++,d)}),resolved:o,unresolved:s}}import{readdirSync as zr}from"node:fs";import{join as fe}from"node:path";var Jr=new Set([".git","node_modules","dist","build","bin","obj","out","target",".vs",".vscode",".idea",".next","coverage",".turbo",".cache","packages"]),Gr=40,Vr=3,Me=5e3,Kr=/\.(csproj|vbproj|fsproj)$|^(package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|pom\.xml|build\.gradle(\.kts)?)$/i,Yr=/\.sln$|^(Directory\.(Build|Packages)\.props|global\.json|go\.work)$/i;function Qr(e){return/\.(csproj|vbproj|fsproj|sln)$/i.test(e)||/^(Directory\.(Build|Packages)\.props|global\.json)$/i.test(e)?".NET":/^(package\.json|angular\.json|tsconfig\.json)$/i.test(e)?"JS/TS":/^(pyproject\.toml|requirements\.txt|setup\.py)$/i.test(e)?"Python":/^(go\.mod|go\.work)$/i.test(e)?"Go":/^Cargo\.toml$/i.test(e)?"Rust":/^(pom\.xml|build\.gradle(\.kts)?)$/i.test(e)?"Java":null}var Zr=/(?:^|[.\-_])(web|portal|ui|app|apps|client|site|widget|admin|dashboard|frontend|api)(?:$|[.\-_])/i,Xr=/(?:^|[.\-_])(tests?|specs?|helm|docker|migrations?|functions?|ci|builds?|scripts|tools|proxy|coverage|release|libs?|redis|signalr|websocket|dataaccess|nuget|sdk|infra|infrastructure|deploy|deployment|e2e)(?:$|[.\-_])/i;function Oe(e){let t=[],r=[];try{for(let n of zr(e,{withFileTypes:!0}))n.isDirectory()?!n.name.startsWith(".")&&!Jr.has(n.name)&&t.push(n.name):n.isFile()&&r.push(n.name)}catch{}return{dirs:t,files:r}}function en(e){let t=new Set;for(let r of e){let n=Qr(r);n&&t.add(n)}return t}var Pt=e=>e.some(t=>Kr.test(t)),tn=e=>e.some(t=>Yr.test(t));function rn(e){let t=0,r=n=>{if(t>=Me)return;let{dirs:o,files:s}=Oe(n);t+=s.length;for(let i of o){if(t>=Me)return;r(fe(n,i))}};return r(e),Math.min(t,Me)}function nn(e,t){let r=e.split("/").pop()??e,n=t.size?[...t].join("+"):"";return Xr.test(r)?{recommended:!1,reason:`tests/infrastructure${n?` \xB7 ${n}`:""}`}:Zr.test(r)?{recommended:!0,reason:`user-facing surface${n?` \xB7 ${n}`:""}`}:{recommended:!1,reason:n?`${n} project`:"source folder"}}function Tt(e){let t=[],r=(s,i)=>{let a=en(i.files),{recommended:d,reason:l}=nn(s,a);t.push({path:s,fileCount:rn(fe(e,s)),stacks:[...a],recommended:d,reason:l})},n=(s,i)=>{let a=s?fe(e,s):e,d=Oe(a);if(s&&Pt(d.files)){r(s,d);return}if(s&&d.dirs.length===0){r(s,d);return}let l=f=>Pt(Oe(fe(a,f)).files);if((tn(d.files)||d.dirs.some(l)||s==="")&&i<Vr){for(let f of d.dirs)n(s?`${s}/${f}`:f,i+1);return}s&&r(s,d)};n("",0),t.sort((s,i)=>Number(i.recommended)-Number(s.recommended)||s.path.localeCompare(i.path));let o=t.filter(s=>s.recommended);return t.slice(0,Math.max(Gr,o.length))}var re={"claude-fable-5":{inputPerMTok:10,outputPerMTok:50},"claude-opus-4-8":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-7":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-6":{inputPerMTok:5,outputPerMTok:25},"claude-opus-4-5":{inputPerMTok:5,outputPerMTok:25},"claude-sonnet-4-6":{inputPerMTok:3,outputPerMTok:15},"claude-sonnet-4-5":{inputPerMTok:3,outputPerMTok:15},"claude-haiku-4-5":{inputPerMTok:1,outputPerMTok:5}},on={opus:"claude-opus-4-8",sonnet:"claude-sonnet-4-6",haiku:"claude-haiku-4-5",fable:"claude-fable-5"},Ct="claude-opus-4-8";function Rt(e){let t=e&&(on[e.toLowerCase()]??e);if(t&&re[t])return{model:t,rate:re[t],assumed:!1};if(t){let r=Object.keys(re).filter(n=>t.startsWith(n)).sort((n,o)=>o.length-n.length)[0];if(r)return{model:r,rate:re[r],assumed:!1}}return{model:Ct,rate:re[Ct],assumed:!0}}function ne(e,t){return e/1e6*t}var sn=3.5,an={convert:{inFactor:1.3,outFactor:1.1,overheadInput:85e3,overheadOutput:16e3},rewrite:{inFactor:1.4,outFactor:1.2,overheadInput:95e3,overheadOutput:19e3},publish:{inFactor:1.1,outFactor:.2,overheadInput:35e3,overheadOutput:4e3},audit:{inFactor:1.2,outFactor:.15,overheadInput:7e4,overheadOutput:9e3}},jt=.7,Et=1.6,me=e=>Math.round(e);function Dt(e){let{op:t,model:r}=e,n=an[t],{model:o,rate:s,assumed:i}=Rt(r),a=e.files.filter(h=>!(h.bytes>0)).map(h=>h.path),d=e.files.filter(h=>h.bytes>0),l=0,u=0;for(let h of d){let w=h.bytes/sn;l+=n.overheadInput+w*n.inFactor,u+=n.overheadOutput+w*n.outFactor}let f=[me(l*jt),me(l*Et)],p=[me(u*jt),me(u*Et)],y=[ne(f[0],s.inputPerMTok)+ne(p[0],s.outputPerMTok),ne(f[1],s.inputPerMTok)+ne(p[1],s.outputPerMTok)],b=`Rough estimate (per-article agent overhead + content), priced at ${o} list rates`+(i?" (model unset \u2014 assumed)":"")+". Actuals vary with cache reuse and reasoning depth; the live cost meter reconciles as the run proceeds.";return{model:o,modelAssumed:i,op:t,articles:d.length,skipped:a,inputTokens:f,outputTokens:p,usd:y,note:b}}var It="/v3/projects",P=e=>`${It}/${e}`;function x(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)??'{"success":true}'}]}}function v(e){return{content:[{type:"text",text:e instanceof Error?e.message:String(e)}],isError:!0}}function Mt(e,t){let r={profile:e.name,connection:e.connection},n=e.project.projectId,o=e.project.workspaceId,s=()=>V(r,n),i=c=>{let g=c??o;if(!g)throw new Error("No workspace_id given and none in the profile. Call d360_list_workspaces first.");return g},a=(c,g)=>{if(t.cwd)try{let k=$e(t.cwd,c);de(t.cwd,e.name,c.replace(/\\/g,"/"),{...g,...cn(k)?{localHash:H(At(k,"utf8"))}:{}})}catch{}},d=()=>t.writesAllowed?null:v(`Refusing to write to PRODUCTION profile "${e.name}". Authorize this session first: run /allow-prod in the REPL, or pass --yes in one-shot/CI.`),l=S("d360_context","Report the active Document360 connection: profile name, environment, project/workspace id, and whether it is a production profile. Call this before maintaining d360-category-map.json so you scope IDs to the right profile.",{},async()=>{try{return x({profile:e.name,environment:e.connection.name,production:e.production,projectId:n??V(r),workspaceId:o??null})}catch(c){return v(c)}}),u=S("d360_list_projects","List all Document360 projects the signed-in user can access (id, name, sub-domain, status).",{},async()=>{try{return x(await q(r,It))}catch(c){return v(c)}}),f=S("d360_list_workspaces","List workspaces (project versions) for the current project. Each has id, name, slug, is_default, workspace_type.",{project_id:m.string().optional().describe("Defaults to the logged-in/config project.")},async c=>{try{return x(await q(r,`${P(c.project_id??s())}/workspaces`))}catch(g){return v(g)}}),p=S("d360_list_categories","List categories in a workspace (the folder structure for articles).",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await q(r,`${P(c.project_id??s())}/workspaces/${g}/categories`)):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),y=S("d360_list_articles","List articles in a workspace (id, title, status, category). Use to see existing docs before writing.",{workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await q(r,`${P(c.project_id??s())}/workspaces/${g}/articles`)):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),b=S("d360_get_article","Get a single article including its content. content_mode=raw returns the stored markdown source (best for editing); display returns processed content.",{article_id:m.string(),content_mode:m.enum(["raw","display"]).optional().describe("raw = stored markdown source (default), display = processed."),published:m.boolean().optional().describe("Read the published version instead of the latest draft."),project_id:m.string().optional()},async c=>{try{return x(await O(r,`${P(c.project_id??s())}/articles/${c.article_id}`,{query:{content_mode:c.content_mode,published:c.published}}))}catch(g){return v(g)}}),h=S("d360_ai_query","Ask Document360's AI search over a workspace's published content. Returns an answer grounded in existing articles \u2014 use to check what's already documented.",{query:m.string(),workspace_id:m.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:m.string().optional()},async c=>{try{let g=c.workspace_id??o;return g?x(await K(r,`${P(c.project_id??s())}/workspaces/ai/query`,{body:{query:c.query,workspace_id:g}})):v("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return v(g)}}),w=S("d360_sync_status","Deterministic drift report: local docs tree vs Document360, classified against the sync bases in d360-category-map.json. Call this BEFORE any docs gap/coverage analysis \u2014 it proves whether the local docs tree can be trusted as the Document360 inventory (no need to fetch article content). Statuses: local-ahead (push pending), remote-ahead (portal edit to pull), conflict, untracked-local/remote, deleted-local/remote, unknown-base (no base recorded yet).",{},async()=>{try{if(!t.cwd)return v("Sync status needs a repo context (no cwd configured for this session).");let c=await xt({cwd:t.cwd,profileName:e.name});return x({profile:c.profile,docsRoot:c.docsRoot,generatedAt:c.generatedAt,counts:c.counts,attention:c.entries.filter(g=>g.status!=="in-sync")})}catch(c){return v(c)}}),C=S("d360_repo_inventory","Deterministic inventory of candidate source folders for documentation scope (no file reads of your own needed). Returns project-level folders with: path, fileCount, detected stacks, a `recommended` flag (user-facing surfaces pre-recommended; tests/infrastructure not), and a one-line reason. Use this when analyzing a large/multi-project repo to decide which folders back the docs \u2014 it is the same data the /scope picker pre-ticks, so your recommendation and the picker agree. Cheap; safe to call before reading any source.",{},async()=>{try{return t.cwd?x(Tt(t.cwd)):v("Repo inventory needs a repo context (no cwd configured for this session).")}catch(c){return v(c)}}),z=S("d360_estimate_cost",'Deterministic, no-network token/cost ESTIMATE for a bulk operation. Call this BEFORE you propose a repo-scale run (convert / rewrite / publish / audit MANY articles) so you can show the user the article count and a cost band and get an explicit go-ahead \u2014 see the "Confirm before bulk or irreversible actions" rule. It sizes work from local file BYTES (coarse \u2014 returns [low, high] bands, not a quote). Omit `paths` to estimate over every article tracked in d360-category-map.json; pass `paths` for a subset. Cheap and read-only; safe to call before any write.',{op:m.enum(["convert","rewrite","publish","audit"]).describe("What you intend to do to each article (drives the output-size heuristic)."),paths:m.array(m.string()).optional().describe("Repo-relative .md paths. Omit or leave empty to estimate over all articles in the category map.")},async({op:c,paths:g})=>{try{if(!t.cwd)return v("Cost estimate needs a repo context (no cwd configured for this session).");let k;if(g&&g.length>0)k=g.map(_=>_.replace(/\\/g,"/"));else{let _=$(t.cwd,e.name);if(k=_?Object.keys(_.articles):[],k.length===0)return v("No articles are tracked in d360-category-map.json for this profile, and no `paths` were given. Pass `paths` explicitly (the repo-relative .md files you intend to operate on).")}let E=k.map(_=>{let D=0;try{D=ln($e(t.cwd,_)).size}catch{D=0}return{path:_,bytes:D}});return x(Dt({files:E,op:c,model:t.model}))}catch(k){return v(k)}}),R=S("d360_create_category","Create a category (a docs folder). Returns the new category id.",{name:m.string(),workspace_id:m.string().optional(),parent_category_id:m.string().optional().describe("Omit for a top-level category."),content:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/categories`,{body:{name:c.name,workspace_id:i(c.workspace_id),parent_category_id:c.parent_category_id,content:c.content,slug:c.slug,order:c.order,content_type:"markdown"}}))}catch(k){return v(k)}}),Q=S("d360_create_article","Create a DRAFT article in a category. The body is always Markdown (product rule \u2014 we never create WYSIWYG/Block articles). Returns the new article id. Does not publish.",{title:m.string(),category_id:m.string(),content:m.string().optional().describe("Markdown body."),workspace_id:m.string().optional(),slug:m.string().optional(),order:m.number().optional(),project_id:m.string().optional(),local_path:m.string().optional().describe("Repo-relative path of the local .md this article mirrors (e.g. user-docs/01-intro/01-a.md). When given, the new article id + sync base are recorded into d360-category-map.json automatically \u2014 do not edit the articles map yourself.")},async c=>{let g=d();if(g)return g;try{let k=c.project_id??s(),E=c.content;typeof E=="string"&&c.local_path&&t.cwd&&(E=Ie(E,c.local_path,$(t.cwd,e.name)).content);let _=await K(r,`${P(k)}/articles/bulk`,{body:{articles:[{title:c.title,category_id:c.category_id,workspace_id:i(c.workspace_id),content:E,slug:c.slug,order:c.order,content_type:"markdown"}]}}),D=Array.isArray(_)?_[0]:void 0;if(c.local_path&&typeof D?.id=="string"){let M;try{let W=await O(r,`${P(k)}/articles/${D.id}`,{});M=typeof W?.url=="string"?W.url:void 0}catch{}a(c.local_path,{id:D.id,remoteContentHash:Y(c.title,E),remoteVersion:1,...M?{url:M}:{}})}return x(_)}catch(k){return v(k)}}),j=S("d360_update_article","Update an article's title/content/category. Edits the latest draft by default; set auto_fork to safely edit a published article (creates a new draft version).",{article_id:m.string(),title:m.string().optional(),content:m.string().optional().describe("Markdown body."),category_id:m.string().optional(),hidden:m.boolean().optional(),version_number:m.number().optional(),auto_fork:m.boolean().optional().describe("If the target version is published, fork a new draft instead of erroring."),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{let{article_id:k,project_id:E,..._}=c,D=E??s(),M=typeof _.content=="string"&&t.cwd?$(t.cwd,e.name):null,W=M?bt(M,k):null;typeof _.content=="string"&&W&&M&&(_.content=Ie(_.content,W,M).content);let G=null;if(typeof _.content=="string"&&t.onUiEvent)try{G=await O(r,`${P(D)}/articles/${k}`,{query:{content_mode:"raw"}})}catch{}let qe=e.project.languageCode??G?.lang_code,Fe=await gt(r,`${P(D)}/articles/${k}`,{body:_,...qe?{query:{lang_code:qe}}:{}});if(G&&typeof _.content=="string"&&(G.content??"")!==_.content&&t.onUiEvent?.({type:"article_diff",articleId:k,title:G.title??null,oldContent:G.content??"",newContent:_.content}),typeof _.content=="string"&&W)try{let X=Fe,We=_.title??X?.title??G?.title;typeof We=="string"&&a(W,{id:k,remoteContentHash:Y(We,_.content),...typeof X?.latest_version=="number"?{remoteVersion:X.latest_version}:{},...typeof X?.modified_at=="string"?{remoteModifiedAt:X.modified_at}:{}})}catch{}return x(Fe)}catch(k){return v(k)}}),U=S("d360_fork_article","Fork a published article into a new draft version \u2014 the safe way to start editing live content.",{article_id:m.string(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/fork`))}catch(k){return v(k)}}),J=S("d360_publish_article","Publish an article version. This makes the draft live to readers \u2014 only call when the user explicitly asks to publish.",{article_id:m.string(),version_number:m.number(),workspace_id:m.string().optional(),message:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/publish`,{body:{workspace_id:i(c.workspace_id),version_number:c.version_number,message:c.message}}))}catch(k){return v(k)}}),T=S("d360_unpublish_article","Unpublish an article version, reverting it to draft (removes it from readers).",{article_id:m.string(),version_number:m.number().describe("The published version to unpublish (see d360_get_article)."),workspace_id:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{return x(await K(r,`${P(c.project_id??s())}/articles/${c.article_id}/unpublish`,{body:{workspace_id:i(c.workspace_id),version_number:c.version_number}}))}catch(k){return v(k)}}),Gt=S("d360_upload_drive_file","Upload a local file (e.g. a captured screenshot PNG) to Drive and return its URL for embedding in an article. Uploads to the default folder unless folder_id is given.",{file_path:m.string().describe("Local path to the file (absolute, or relative to the repo)."),folder_id:m.string().optional(),title:m.string().optional(),project_id:m.string().optional()},async c=>{let g=d();if(g)return g;try{let k=c.project_id??s(),E=dn(c.file_path)?c.file_path:$e(process.cwd(),c.file_path),_=c.folder_id;if(!_&&(_=(await O(r,`${P(k)}/drive/folders/default`))?.id,!_))return v("Could not resolve the default Drive folder.");let D=At(E),M=new FormData;return M.append("file",new Blob([D]),c.title??un(E)),x(await ht(r,`${P(k)}/drive/folders/${_}/files`,M))}catch(k){return v(k)}}),Vt=S("d360_search_drive","Search Drive files (e.g. to reuse an already-uploaded screenshot instead of uploading a duplicate).",{search_keyword:m.string().optional(),allow_images_only:m.boolean().optional(),project_id:m.string().optional()},async c=>{try{return x(await q(r,`${P(c.project_id??s())}/drive/search`,{query:{search_keyword:c.search_keyword,allow_images_only:c.allow_images_only}}))}catch(g){return v(g)}});return pn({name:"document360",version:"0.2.0",instructions:"First-party Document360 tools. The signed-in user's permissions apply server-side. Project/workspace default from the active profile; pass ids explicitly to override. Create articles as DRAFTS; only call d360_publish_article when the user explicitly asks to publish. All articles are Markdown \u2014 the tools enforce this; never attempt WYSIWYG/Block content. To edit a published article, fork it (d360_fork_article) or update with auto_fork. Before any docs gap/coverage analysis, call d360_sync_status to verify the local docs tree matches Document360. When scoping a large repo (which folders back the docs), call d360_repo_inventory for the candidate folders. Before a bulk run (converting/publishing/auditing many articles), call d360_estimate_cost and show the user the count + cost band first. Auth errors mean the session expired \u2014 tell the user to run /login (works inside this session).",tools:[l,u,f,p,y,b,h,w,C,z,R,Q,j,U,J,T,Gt,Vt]})}var Nt="CLAUDE.md";function Ot(e){if(!Ue(e))return[];let t=[];for(let r of fn(e)){let n=Ne(e,r);if(r===Nt||!mn(n).isDirectory())continue;let o=Ne(n,"SKILL.md");Ue(o)&&t.push({name:r,body:Ut(o,"utf8")})}return t}function $t(e){let t=Ne(e,Nt);return Ue(t)?Ut(t,"utf8"):null}function hn(e,t){let r=Ye(),n=Qe(e),o=$t(r),s=$t(n),i=Ot(r),a=Ot(n),d=new Set(a.map(f=>f.name)),l=[...i.filter(f=>!d.has(f.name)),...a],u=[];if(o&&u.push(o),s&&u.push(`# Project addendum
|
|
11
11
|
|
|
12
|
-
${s}`),t&&(
|
|
12
|
+
${s}`),t&&(u.push("# Project configuration"),u.push("```json"),u.push(JSON.stringify(t,null,2)),u.push("```")),l.length>0){u.push("# Capabilities"),u.push('You have the following specialized documentation skills. Activate the one whose preconditions match the user request. If multiple match, run them in order. These skills are INLINE INSTRUCTIONS in this prompt \u2014 "activating" one means following its steps directly. They are NOT registered with any Skill tool; never attempt to invoke them via a Skill/skill-runner tool.');for(let f of l)u.push(`## Skill: ${f.name}`),u.push(f.body)}return u.join(`
|
|
13
13
|
|
|
14
|
-
`)}function
|
|
15
|
-
`):""}function
|
|
16
|
-
`),options:{cwd:t,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You generate terse kebab-case session titles. Output only the title, nothing else.",allowedTools:[]}});for await(let n of r){let o=n;if(o.type==="result")return o.subtype==="success"&&typeof o.result=="string"?
|
|
17
|
-
`),options:{cwd:r,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You propose the next user prompt for a docs-writing CLI. Output only the prompt, or NONE.",allowedTools:[]}});for await(let o of n){let s=o;if(s.type==="result")return s.subtype==="success"&&typeof s.result=="string"?
|
|
18
|
-
`)[0].replace(/^["'`]+|["'`]+$/g,"").trim();return!t||/^none\b/i.test(t)||t.length<4||t.length>100?null:t}var
|
|
14
|
+
`)}function yn(){let e=et(),t={};for(let[r,n]of Object.entries(e.servers))n.type==="stdio"?t[r]={type:"stdio",command:n.command,args:n.args,env:n.env}:n.type==="http"?t[r]={type:"http",url:n.url,headers:n.headers}:n.type==="sse"&&(t[r]={type:"sse",url:n.url,headers:n.headers});return t}var wn=/auth|api key|login|credential|401|forbidden/i,Le=class{queue;iterator;sdkQuery;uiEvents=[];sessionId=null;constructor(t={}){let r=t.cwd??process.cwd(),n=I(r),o=hn(r,n),s=yn(),i=t.model??n?.defaultModel??te().defaultModel,a=i==="auto"?void 0:i;try{let p=F(r,t.profileName),y=!p.production||t.allowProdWrites===!0;s.document360=Mt(p,{writesAllowed:y,cwd:r,model:a,onUiEvent:b=>this.uiEvents.push(b)})}catch{}this.queue=new ie;let d=async(p,y)=>{if(ze.has(p)){let b=y.file_path??y.notebook_path;if(b&&!he(r,b,n))return{behavior:"deny",message:ye(b)}}if(p==="Bash"||p==="PowerShell"){let b=y.command;if(typeof b=="string"){let h=Je(b,r,n);if(h)return{behavior:"deny",message:ye(h)}}}return{behavior:"allow",updatedInput:y}},l=n?.mode==="engineer",u={cwd:r,systemPrompt:o,mcpServers:s,...l?{permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0}:{permissionMode:"default",canUseTool:d},disallowedTools:["Skill","AskUserQuestion"],includePartialMessages:!0,settingSources:[],model:a,resume:t.resume},f=gn({prompt:this.queue,options:u});this.sdkQuery=f,this.iterator=f[Symbol.asyncIterator]()}async interrupt(){try{await this.sdkQuery.interrupt()}catch{}}async setModel(t){await this.sdkQuery.setModel(t)}async*send(t){for(this.queue.push(t);;){let r;try{r=await this.iterator.next()}catch(s){let i=s.message;yield{type:"error",kind:wn.test(i)?"auth":"agent",message:i};return}if(r.done)return;for(;this.uiEvents.length>0;)yield this.uiEvents.shift();let n=r.value?.session_id;n&&!this.sessionId&&(this.sessionId=n,yield{type:"session",sessionId:n});let o=bn(r.value);if(o){for(let s of o)yield s;if(o.some(s=>s.type==="result"))return}}}close(){this.queue.close()}};function Lt(e={}){return new Le(e)}function kn(e){return typeof e=="string"?e:Array.isArray(e)?e.map(t=>{let r=t;return r.type==="text"&&typeof r.text=="string"?r.text:""}).filter(Boolean).join(`
|
|
15
|
+
`):""}function bn(e){if(!e||typeof e!="object")return null;let t=e;if(t.type==="stream_event"){let r=t.event;return r?.delta&&typeof r.delta.text=="string"?[{type:"text",delta:r.delta.text}]:null}if(t.type==="assistant"){let r=t.message,n=[];for(let o of r?.content??[]){let s=o;s.type==="tool_use"&&n.push({type:"tool",id:s.id??"",name:s.name??"tool",input:s.input??{}})}return n.length>0?n:null}if(t.type==="user"){let r=t.message;if(!Array.isArray(r?.content))return null;let n=[];for(let o of r.content){let s=o;s.type==="tool_result"&&n.push({type:"tool_result",id:s.tool_use_id??"",output:kn(s.content),isError:s.is_error===!0})}return n.length>0?n:null}if(t.type==="result"){let r=t;return[{type:"result",inputTokens:r.usage?.input_tokens??0,outputTokens:r.usage?.output_tokens??0,cacheReadTokens:r.usage?.cache_read_input_tokens??0,cacheCreationTokens:r.usage?.cache_creation_input_tokens??0,costUsd:r.total_cost_usd??0,ok:r.subtype==="success"}]}return null}import{existsSync as _n}from"node:fs";import{homedir as vn}from"node:os";import{join as Sn}from"node:path";var Gs=["auto","api","subscription"];function Bt(){return process.env.CLAUDE_CODE_OAUTH_TOKEN?!0:_n(Sn(vn(),".claude",".credentials.json"))}function Vs(e){let t=!!process.env.ANTHROPIC_API_KEY;return e==="api"?t?{kind:"api"}:{kind:"none"}:e==="subscription"?(delete process.env.ANTHROPIC_API_KEY,{kind:"subscription",stored:Bt()}):t?{kind:"api"}:{kind:"subscription",stored:Bt()}}import{existsSync as xn,readFileSync as Pn,writeFileSync as Tn}from"node:fs";var Cn=100;function Z(){let e=be();if(!xn(e))return[];try{let t=JSON.parse(Pn(e,"utf8"));return Array.isArray(t)?t:[]}catch{return[]}}function ge(e){L(A());let t=[...e].sort((r,n)=>n.updatedAt.localeCompare(r.updatedAt)).slice(0,Cn);Tn(be(),JSON.stringify(t,null,2))}function Zs(e){let t=Z().filter(r=>r.uuid!==e.uuid);t.push(e),ge(t)}function Xs(e){return Z().find(t=>t.uuid===e)}function Rn(e){return Z().filter(t=>t.cwd===e).sort((t,r)=>r.updatedAt.localeCompare(t.updatedAt))}function ei(e,t){let r=Rn(e),n=t.toLowerCase(),o=r.find(a=>a.name.toLowerCase()===n);if(o)return o;let s=r.filter(a=>a.name.toLowerCase().startsWith(n));if(s.length===1)return s[0];let i=r.filter(a=>a.uuid.startsWith(t));if(i.length===1)return i[0]}function ti(e){let t=Z(),r=t.find(n=>n.uuid===e);r&&(r.updatedAt=new Date().toISOString(),ge(t))}function ri(e,t){let r=Z(),n=r.find(o=>o.uuid===e);return n?(n.name=t,n.renamed=!0,n.updatedAt=new Date().toISOString(),ge(r),!0):!1}function ni(e,t){let r=Z(),n=r.find(o=>o.uuid===e);!n||n.renamed||(n.name=t,n.titled=!0,ge(r))}function oi(e,t=6){return e.toLowerCase().replace(/[^a-z0-9\s-]/g,"").split(/\s+/).filter(Boolean).slice(0,t).join("-")||"session"}function si(e){let t=Date.now()-new Date(e).getTime(),r=Math.floor(t/6e4);if(r<1)return"just now";if(r<60)return`${r}m ago`;let n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}import{query as jn}from"@anthropic-ai/claude-agent-sdk";var En=6e4;async function ci(e,t){try{return await Promise.race([Dn(e,t),An()])}catch{return null}}async function Dn(e,t){let r=jn({prompt:["Generate a short session title for a documentation-writing session that started with this request:","",e.slice(0,500),"","Reply with ONLY the title: 3-6 words, lowercase, kebab-case, no quotes, no punctuation."].join(`
|
|
16
|
+
`),options:{cwd:t,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You generate terse kebab-case session titles. Output only the title, nothing else.",allowedTools:[]}});for await(let n of r){let o=n;if(o.type==="result")return o.subtype==="success"&&typeof o.result=="string"?In(o.result):null}return null}function An(){return new Promise(e=>setTimeout(()=>e(null),En).unref())}function In(e){let t=e.trim().toLowerCase().replace(/[^a-z0-9\s-]/g,"").replace(/\s+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").split("-").slice(0,8).join("-").slice(0,60);return t.length>=3?t:null}import{query as Mn}from"@anthropic-ai/claude-agent-sdk";var On=3e4;async function di(e,t,r){try{return await Promise.race([$n(e,t,r),Un()])}catch{return null}}async function $n(e,t,r){let n=Mn({prompt:["In a documentation-writing session, the user asked:","",e.slice(0,800),"","The assistant's reply ended with:","",t.slice(-4e3),"","Suggest the user's most likely next prompt. Reply with ONLY that prompt:","imperative, specific, at most 15 words, no quotes \u2014 a complete, grammatical","sentence the user could send verbatim. If the assistant asked a question,",'answer it. If the assistant offered options AND recommended one ("the right','approach", "recommended", "I suggest"), your suggestion must accept the',"RECOMMENDED option \u2014 never an alternative the assistant advised against or","deferred. If there is no clear next action, reply with exactly: NONE"].join(`
|
|
17
|
+
`),options:{cwd:r,model:"haiku",maxTurns:1,settingSources:[],systemPrompt:"You propose the next user prompt for a docs-writing CLI. Output only the prompt, or NONE.",allowedTools:[]}});for await(let o of n){let s=o;if(s.type==="result")return s.subtype==="success"&&typeof s.result=="string"?Nn(s.result):null}return null}function Un(){return new Promise(e=>setTimeout(()=>e(null),On).unref())}function Nn(e){let t=e.trim().split(`
|
|
18
|
+
`)[0].replace(/^["'`]+|["'`]+$/g,"").trim();return!t||/^none\b/i.test(t)||t.length<4||t.length>100?null:t}var Ln=/^---\r?\n[\s\S]*?\r?\n---\r?\n/;function qt(e){let t=e.match(Ln);return t?{frontmatter:t[0],body:e.slice(t[0].length)}:{frontmatter:null,body:e}}import{existsSync as Bn,mkdirSync as qn,readFileSync as Fn,writeFileSync as Wn}from"node:fs";import{dirname as Hn,join as Ft}from"node:path";var Wt=/^\[Screenshot: [^\]]+\]\s*$/;function zn(e){let t=e.replace(/\r\n/g,`
|
|
19
19
|
`).split(`
|
|
20
|
-
`),r=new Map;for(let n=0;n<t.length;n++){if(!t[n].startsWith("<!-- SCREENSHOT"))continue;let o=n;for(;n<t.length&&!t[n].includes("-->");)n++;if(n>=t.length)break;let s=t.slice(o,n+1),i=n+1;for(;i<t.length&&t[i].trim()==="";)i++;i<t.length&&
|
|
21
|
-
`).replace(/^\n+/,""),s=r?
|
|
22
|
-
`).flatMap(
|
|
20
|
+
`),r=new Map;for(let n=0;n<t.length;n++){if(!t[n].startsWith("<!-- SCREENSHOT"))continue;let o=n;for(;n<t.length&&!t[n].includes("-->");)n++;if(n>=t.length)break;let s=t.slice(o,n+1),i=n+1;for(;i<t.length&&t[i].trim()==="";)i++;i<t.length&&Wt.test(t[i])&&r.set(t[i].trim(),[...s,...t.slice(n+1,i)])}return r}function Jn(e,t,r){let n=[],o=t.replace(/\r\n/g,`
|
|
21
|
+
`).replace(/^\n+/,""),s=r?qt(r).frontmatter:null,i=o,a=0;if(r){let l=zn(r);l.size>0&&(i=o.split(`
|
|
22
|
+
`).flatMap(u=>{let f=l.get(u.trim());return f&&Wt.test(u)?(a++,[...f,u]):[u]}).join(`
|
|
23
23
|
`))}return a>0&&n.push(`${a} screenshot spec comment(s) re-attached from the existing local file.`),r&&/\]\((\.\.?\/)[^)]+\.md\)/.test(r)&&n.push("The existing local file has relative cross-article links; publishing flattened them to plain text on Document360, so this pull does not restore them."),/!\[[^\]]*\]\(https?:\/\/[^)]*\)/.test(o)&&n.push("Remote content embeds uploaded images (Drive URLs); they are kept as-is locally."),s&&n.push("Existing frontmatter preserved."),{content:`${s??""}# ${e}
|
|
24
24
|
|
|
25
25
|
${i}`.replace(/\n*$/,`
|
|
26
|
-
`),notes:n}}async function
|
|
26
|
+
`),notes:n}}async function _i(e,t=O){let r=F(e.cwd,e.profileName),n=$(e.cwd,r.name),o=n?.articles[e.relPath];if(!n||!o)throw new Error(`"${e.relPath}" is not in d360-category-map.json for profile "${r.name}". Run /sync to see tracked articles.`);let s={profile:r.name,connection:r.connection},i=n.projectId??r.project.projectId??V(s),a=await t(s,`/v3/projects/${i}/articles/${o.id}`,{query:{content_mode:"raw"}}),d=Ft(e.cwd,e.relPath),l=Bn(d)?Fn(d,"utf8"):null,u=a.title??"",{content:f,notes:p}=Jn(u,a.content??"",l);return{path:e.relPath,articleId:o.id,title:u,oldContent:l??"",newContent:f,remoteContentHash:Y(a.title,a.content),latestVersion:a.latest_version,modifiedAt:a.modified_at,overwritesLocalChanges:l!==null&&o.localHash!==void 0&&H(l)!==o.localHash,notes:p}}function vi(e,t){let r=F(e.cwd,e.profileName),n=Ft(e.cwd,t.path),o=t.oldContent.includes(`\r
|
|
27
27
|
`)?t.newContent.replace(/\n/g,`\r
|
|
28
|
-
`):t.newContent;
|
|
28
|
+
`):t.newContent;qn(Hn(n),{recursive:!0}),Wn(n,o,"utf8"),de(e.cwd,r.name,t.path,{id:t.articleId,localHash:H(o),remoteContentHash:t.remoteContentHash,remoteVersion:t.latestVersion,remoteModifiedAt:t.modifiedAt})}import{readdirSync as Gn,readFileSync as Vn,existsSync as Kn}from"node:fs";import{join as Be,relative as Ht}from"node:path";var Yn=12,zt=e=>e.replace(/\\/g,"/");function Qn(e){let t=new Map;for(let r of e){let n=zt(r),o=n.lastIndexOf("/"),s=o>=0?n.slice(0,o):"(root)",i=t.get(s);i||(i=[],t.set(s,i)),i.push(n)}return[...t.entries()].map(([r,n])=>({label:r,paths:n.sort()})).sort((r,n)=>r.label.localeCompare(n.label))}function Zn(e,t){let r=Math.max(1,t),n=e.map(zt).sort(),o=[];for(let s=0;s<n.length;s+=r)o.push(n.slice(s,s+r));return o.map((s,i)=>({label:`chunk ${i+1}/${o.length}`,paths:s}))}function Li(e,t){let r=t?.maxPerPartition??Yn,n=[];for(let o of Qn(e)){if(o.paths.length<=r){n.push(o);continue}let s=Zn(o.paths,r);s.forEach((i,a)=>n.push({label:`${o.label} (${a+1}/${s.length})`,paths:i.paths}))}return n}function Xn(e){let t=[];for(let r of e.matchAll(/<!--\s*SCREENSHOT\b([\s\S]*?)-->/g)){let n=/(?:^|\n)\s*id:\s*([^\s]+)/.exec(r[1]??"");n?.[1]&&t.push(n[1])}return t}function Jt(e){let t=[];for(let r of Gn(e,{withFileTypes:!0}))if(r.isDirectory()){if(r.name.startsWith(".")||r.name.startsWith("_")||r.name==="node_modules")continue;t.push(...Jt(Be(e,r.name)))}else r.name.endsWith(".md")&&t.push(Be(e,r.name));return t}function Bi(e,t){let r=(I(e)?.docsDir??"user-docs").replace(/\/+$/,""),n=Be(e,r);if(!Kn(n))return[];let o=t?.scope?.replace(/\\/g,"/").replace(/\/+$/,""),s=(a,d)=>!o||[a,d].some(l=>l===o||l.startsWith(`${o}/`)),i=[];for(let a of Jt(n)){let d=Ht(e,a).replace(/\\/g,"/"),l=Ht(n,a).replace(/\\/g,"/");if(s(d,l))for(let u of Xn(Vn(a,"utf8")))i.push({id:u,file:d})}return i}function qi(e,t){let r;try{r=F(e,t).name}catch{return[]}let n=$(e,r);return n?Object.keys(n.articles):[]}var eo=e=>Lt(e);async function*Hi(e){let{cwd:t,partitions:r,promptFor:n,signal:o}=e,s=e.spawn??eo,i=Math.max(1,Math.min(e.concurrency??3,Math.max(1,r.length))),a=[],d=[],l=null,u=h=>{d.push(h);let w=l;l=null,w?.()},f=async h=>{let w=r[h];u({type:"partition_status",index:h,label:w.label,status:"running"});let C=s({cwd:t,profileName:e.profileName,allowProdWrites:e.allowProdWrites,model:e.model}),z=0,R=0,Q=!1,j,U=()=>{C.interrupt?.()};o?.addEventListener("abort",U,{once:!0});try{for await(let T of C.send(n(w)))u({type:"partition_event",index:h,label:w.label,event:T}),T.type==="result"?(z+=T.costUsd,R+=T.outputTokens,Q=T.ok):T.type==="error"&&(j=T.message)}catch(T){j=T instanceof Error?T.message:String(T)}finally{o?.removeEventListener("abort",U);try{C.close()}catch{}}o?.aborted&&!j&&(j="aborted");let J=Q&&!j;a.push({index:h,label:w.label,ok:J,costUsd:z,outputTokens:R,error:j}),u({type:"partition_status",index:h,label:w.label,status:J?"done":"failed"})},p=0,y=(async()=>{let h=async()=>{for(;;){if(o?.aborted)return;let w=p++;if(w>=r.length)return;await f(w)}};await Promise.all(Array.from({length:i},()=>h()))})(),b=!1;for(y.then(()=>{b=!0;let h=l;l=null,h?.()});;){if(d.length>0){yield d.shift();continue}if(b)break;await new Promise(h=>{l=h})}a.sort((h,w)=>h.index-w.index),yield{type:"run_done",ok:!o?.aborted&&a.length>0&&a.every(h=>h.ok),aborted:o?.aborted??!1,totalCostUsd:a.reduce((h,w)=>h+w.costUsd,0),results:a}}var to={light:"claude-sonnet-4-6",standard:"claude-sonnet-4-6",deep:"claude-opus-4-8",background:"claude-haiku-4-5"};function ro(e,t){let r=e&&e!=="auto"?e:void 0;return r?{model:r,tier:t,forced:!0}:{model:to[t],tier:t,forced:!1}}function Gi(e,t){let r=I(e)?.defaultModel??te().defaultModel;return ro(r,t)}export{Gs as AUTH_MODES,le as D360ApiError,B as D360AuthError,Yn as DEFAULT_MAX_PER_PARTITION,Le as EngineSession,ee as ProfileConfigError,to as TIER_MODEL,Sr as apiLogDir,vi as applyPull,pr as buildAuthorizationUrl,yt as categoryMapPath,qr as classify,jo as clearTokens,xt as computeSyncStatus,Lt as createSession,St as createV3RemoteProvider,O as d360Get,q as d360GetAll,gt as d360Patch,K as d360Post,ht as d360Upload,dt as decodeJwtClaims,L as ensureDir,Dt as estimateBulkCost,nt as exchangeCode,Xn as extractScreenshotIds,ei as findByName,bt as findPathByArticleId,ci as generateTitle,mt as getAccessToken,qo as getArticle,Xs as getSession,H as hashContent,Y as hashRemote,Tt as inventoryRepo,Ce as isExpired,Or as knownEnvironments,Rn as listSessions,Bo as listWorkspaces,$ as loadProfileMap,Z as loadSessions,ae as loadTokens,Ee as logApiCall,je as logBodiesAlways,ce as loggableBody,xo as loginPkce,Mr as normalizeMarkdown,sr as packageRoot,Ye as packageSkillsDir,Qn as partitionByCategory,Zn as partitionEvenly,cr as pickModel,ro as pickOperationModel,Li as planPartitions,_i as planPull,_e as projectConfigPath,Qe as projectSkillsDir,et as readMcpConfig,I as readProjectConfig,te as readUserConfig,Jn as reconstructLocalMarkdown,de as recordSyncBase,xr as redactSecrets,st as refreshTokens,si as relativeTime,ri as renameSession,F as resolveActiveProfile,Vs as resolveAuth,pe as resolveConnection,Ie as resolveCrossLinks,vt as resolveDocsRoot,Vo as resolveEnvironment,Gi as resolveModelForOperation,yo as resolveModelSetting,Ze as resolveProfile,V as resolveProjectId,Hi as runPartitioned,ut as saveTokens,Bi as screenshotPlaceholderIds,be as sessionsFilePath,Zo as setProfileProject,ni as setTitle,oi as slugify,qt as splitFrontmatter,di as suggestNextAction,it as toStoredTokens,ti as touchSession,qi as trackedArticlePaths,Pr as truncateBody,Zs as upsertSession,we as userConfigPath,A as userDir,ke as userMcpConfigPath,wo as writeMcpConfig,Xe as writeProjectConfig,ho as writeUserConfig};
|
|
@@ -25,6 +25,20 @@ export declare function partitionEvenly(paths: string[], maxPer: number): Partit
|
|
|
25
25
|
export declare function planPartitions(articlePaths: string[], opts?: {
|
|
26
26
|
maxPerPartition?: number;
|
|
27
27
|
}): Partition[];
|
|
28
|
+
/** Pull the `id:` of each `<!-- SCREENSHOT ... -->` block out of an article's markdown. Pure. */
|
|
29
|
+
export declare function extractScreenshotIds(markdown: string): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Every SCREENSHOT placeholder declared in the docs (optionally narrowed to a `scope` folder or
|
|
32
|
+
* .md file). The unit of work for bulk spec authoring — partition these and fan out. `scope`
|
|
33
|
+
* matches against the path relative to either the repo root or the docs dir, so both
|
|
34
|
+
* `user-docs/04-x/a.md` and `04-x/a.md` work.
|
|
35
|
+
*/
|
|
36
|
+
export declare function screenshotPlaceholderIds(cwd: string, opts?: {
|
|
37
|
+
scope?: string;
|
|
38
|
+
}): {
|
|
39
|
+
id: string;
|
|
40
|
+
file: string;
|
|
41
|
+
}[];
|
|
28
42
|
/**
|
|
29
43
|
* Convenience: every article path tracked in d360-category-map.json for the active profile.
|
|
30
44
|
* Resolves the default profile when `profileName` is omitted (consistent with computeSyncStatus);
|
package/package.json
CHANGED
package/skills/CLAUDE.md
CHANGED
|
@@ -112,6 +112,7 @@ The "Capabilities" section below lists specialized skills. Pick the right one ba
|
|
|
112
112
|
- Any "convert/wrap this into a callout/tabs/FAQ/accordion" request, or authoring with rich components → apply `d360-markdown` (always-on syntax reference).
|
|
113
113
|
- "/publish ..." → `publish-to-d360`.
|
|
114
114
|
- "/screenshot ..." or any time you generate a screenshot placeholder → also call `emit-screenshot-spec`.
|
|
115
|
+
- "/capture-setup" / "what data do I need for screenshots" / after authoring screenshot specs in bulk → `capture-setup-checklist`.
|
|
115
116
|
- Pulling extra context from other MCP sources during write-article → `gather-context-from-mcp`.
|
|
116
117
|
|
|
117
118
|
If the user's intent is ambiguous, ask before invoking a skill that writes files or publishes.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Skill: capture-setup-checklist
|
|
2
|
+
|
|
3
|
+
**Activate when** the user asks how to prepare data for screenshots ("what data do I need for captures", "capture setup", `/capture-setup`), OR right after you've emitted screenshot specs in bulk.
|
|
4
|
+
|
|
5
|
+
**Goal:** one reviewable "stage this data" checklist, so the user dresses the demo context ONCE before running `d360-capture` — the way a technical writer prepares a demo account before a screenshot session. Screenshots can't show data that isn't there; this tells the user exactly what to create.
|
|
6
|
+
|
|
7
|
+
## How
|
|
8
|
+
|
|
9
|
+
1. **Collect.** Find every `<!-- SCREENSHOT ... -->` block across `<docsDir>/**/*.md`. From each, read the `id`, the title, and the `prerequisites:` lines.
|
|
10
|
+
2. **Anchor to the scope.** Read the capture `scope` keys from `.d360-capture.json` (e.g. `project`, or `project`+`workspace`, or `org` — whatever THIS product scopes by; do not assume "project"). If no scope is set yet, recommend the keys you saw the product use and tell the user to set them.
|
|
11
|
+
3. **Synthesize — don't dump.** A raw per-spec list is noise. Instead:
|
|
12
|
+
- **Group by context** (e.g. "In the demo project «`scope.project`»:").
|
|
13
|
+
- **Dedupe** overlapping needs — if eight shots each need "≥1 suite", say it once.
|
|
14
|
+
- **Order foundational → specific**: create the context first, then the records, then per-shot specifics.
|
|
15
|
+
- **Separate the un-stageable**: shots needing a transient state (a toast, a mid-import modal) go under "Set up by hand at capture time", not the standing-data list.
|
|
16
|
+
4. **Write it** to `<captureDir>/CAPTURE-SETUP.md` (versioned + reviewable). Re-running refreshes it.
|
|
17
|
+
5. **Close** by telling the user the path, to set `scope` in `.d360-capture.json` to point at the prepared context, then `d360-capture auth` (once) and `d360-capture capture`.
|
|
18
|
+
|
|
19
|
+
## Quality bar
|
|
20
|
+
|
|
21
|
+
- **Concrete:** real record names and counts ("create a suite named *Smoke*", "import one API spec"), not "have some data".
|
|
22
|
+
- **Generic:** use the product's actual scope keys; never hardcode FlowForge's "project".
|
|
23
|
+
- **Honest:** if a spec's `prerequisites:` are missing or vague, list that spec under "Needs a clearer prerequisite" rather than inventing data. Don't pretend a checklist item exists when the source doesn't say so.
|
|
24
|
+
|
|
25
|
+
## Shape of the output (illustrative)
|
|
26
|
+
|
|
27
|
+
```markdown
|
|
28
|
+
# Capture data setup
|
|
29
|
+
|
|
30
|
+
Prepare this once, then run `d360-capture capture`.
|
|
31
|
+
|
|
32
|
+
## In the demo project "Docs Demo"
|
|
33
|
+
- [ ] At least 1 **suite** (e.g. "Smoke") — needed by: suites-tab-list, suite-run-dashboard, …
|
|
34
|
+
- [ ] At least 3 **schedules** — needed by: schedules-list-populated, schedule-context-menu
|
|
35
|
+
- [ ] 1 **capture** with a click-gallery + a video — needed by: captures-detail-*
|
|
36
|
+
- [ ] 1 imported **API spec** (with a version folder) — needed by: spec-import-*
|
|
37
|
+
|
|
38
|
+
## Set up by hand at capture time (transient states)
|
|
39
|
+
- flow-generation-timeout-banner — trigger a generation timeout, then capture.
|
|
40
|
+
|
|
41
|
+
## Needs a clearer prerequisite
|
|
42
|
+
- <spec-id> — its SCREENSHOT block doesn't say what data it needs; refine it.
|
|
43
|
+
```
|
|
@@ -101,6 +101,7 @@ Don't write a fragile selector. Instead:
|
|
|
101
101
|
|
|
102
102
|
- Report the spec's path.
|
|
103
103
|
- List anything that needs the user: the `data-testid`s a dev must add (TODO hatch), and the **data prerequisites** to stage (project/workspace + the records each shot needs).
|
|
104
|
+
- After authoring several specs, run `capture-setup-checklist` to refresh the consolidated `CAPTURE-SETUP.md` staging guide.
|
|
104
105
|
- Remind how to capture (screenshots are a separate tool — not bundled with the writer):
|
|
105
106
|
1. First time only: `npm i -g document360-capture`
|
|
106
107
|
2. Set the capture `scope` for the prepared demo context in `.d360-capture.json`.
|