document360-engine 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Deterministic, no-network token/cost estimator for a BULK operation, so the agent can
3
+ * project spend and get the user's go-ahead BEFORE a repo-scale run (the #3-A gate; see
4
+ * plans/bulk-operations-and-cost.md). Coarse by construction — it sizes work from file
5
+ * BYTES, not a real token count — so it returns a band, not a quote, and the live meter
6
+ * (#3-B, the SDK's `total_cost_usd`) reconciles the actuals afterward.
7
+ */
8
+ /** What the agent intends to do to each article — drives the output-token heuristic. */
9
+ export type BulkOp = 'convert' | 'rewrite' | 'publish' | 'audit';
10
+ /** One target article: a repo-relative path and its on-disk size in bytes. */
11
+ export type TargetFile = {
12
+ path: string;
13
+ bytes: number;
14
+ };
15
+ export type CostEstimate = {
16
+ /** The model the estimate was priced at. */
17
+ model: string;
18
+ /** True when the model was unknown/unset and DEFAULT_PRICING_MODEL was assumed. */
19
+ modelAssumed: boolean;
20
+ op: BulkOp;
21
+ /** Articles actually sized (missing/zero-byte files are excluded; see `skipped`). */
22
+ articles: number;
23
+ /** Target paths that could not be sized (missing on disk). */
24
+ skipped: string[];
25
+ /** Estimated input tokens, [low, high]. */
26
+ inputTokens: [number, number];
27
+ /** Estimated output tokens, [low, high]. */
28
+ outputTokens: [number, number];
29
+ /** Estimated cost in USD, [low, high]. */
30
+ usd: [number, number];
31
+ /** Human-facing caveat — always surface this; it is an estimate, not a quote. */
32
+ note: string;
33
+ };
34
+ /**
35
+ * Estimate the token/cost band for running `op` over `files`. Pure: no fs, no network —
36
+ * the caller supplies file sizes (the tool stats them). Empty/all-skipped input yields a
37
+ * zero estimate with a note.
38
+ */
39
+ export declare function estimateBulkCost(params: {
40
+ files: TargetFile[];
41
+ op: BulkOp;
42
+ model?: string | null;
43
+ }): CostEstimate;
@@ -19,6 +19,9 @@ export type ToolServerOptions = {
19
19
  /** Repo root for sync bookkeeping (d360-category-map.json). When absent — or the
20
20
  repo has no map/profile section — article writes skip base recording entirely. */
21
21
  cwd?: string;
22
+ /** Active model id, for pricing bulk-cost estimates (d360_estimate_cost). Undefined
23
+ ("Claude Code default") → the estimator assumes the default model and says so. */
24
+ model?: string;
22
25
  };
23
26
  /**
24
27
  * Build the in-process Document360 tool server for the active profile. Project resolves
package/dist/index.js CHANGED
@@ -1,28 +1,28 @@
1
- import{readdirSync as Jr,readFileSync as jt,existsSync as Ae,statSync as Vr}from"node:fs";import{join as Ee}from"node:path";import{query as Gr}from"@anthropic-ai/claude-agent-sdk";import{existsSync as $t}from"node:fs";import{isAbsolute as Ot,relative as Ut,resolve as Me}from"node:path";var Ne=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function de(e,t,r){let n=Ot(t)?t:Me(e,t),o=Ut(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 Mt=/^(\/dev\/null|nul|null)$/i,X=e=>e.replace(/^['"]/,"").replace(/['"]$/,""),Nt=new Set(["rm","rmdir","mv","truncate","touch","mkdir","chmod","chown"]),qt=new Set(["cp","ln","install"]);function qe(e,t,r){let n=i=>{let c=X(i);return!c||c.startsWith("&")||c.startsWith("-")||Mt.test(c)?!1:!de(t,c,r)},o=/(?:^|[^0-9>])>>?\s*("[^"]+"|'[^']+'|[^\s|&;<>]+)/g,s;for(;s=o.exec(e);)if(n(s[1]))return X(s[1]);for(let i of e.split(/&&|\|\||[;|]/)){let c=i.trim().split(/\s+/).filter(Boolean);if(c.length===0)continue;let u=c[0].replace(/.*[/\\]/,""),d=c.slice(1),l=d.filter(f=>!f.startsWith("-")),p=[];Nt.has(u)?p=l:qt.has(u)?p=l.slice(-1):u==="tee"?p=l:u==="dd"?p=d.filter(f=>f.startsWith("of=")).map(f=>f.slice(3)):(u==="sed"||u==="perl")&&d.some(f=>/^-i/.test(f)||f==="--in-place")&&(p=l.filter(f=>$t(Me(t,X(f)))));for(let f of p)if(n(f))return X(f)}return null}function ue(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 Lt}from"node:os";import{join as A,dirname as Be}from"node:path";import{fileURLToPath as Bt}from"node:url";import{existsSync as Fe,mkdirSync as Ft}from"node:fs";var Ht=Bt(import.meta.url),Le=Be(Ht);function Wt(){let e=Le;for(let t=0;t<4;t++){if(Fe(A(e,"package.json")))return e;e=Be(e)}return A(Le,"..","..")}function He(){return A(Wt(),"skills")}function j(){return A(Lt(),".document360-writer")}function pe(){return A(j(),"config.json")}function fe(){return A(j(),"mcp.json")}function ge(){return A(j(),"sessions.json")}function me(e=process.cwd()){return A(e,".d360-writer.json")}function We(e=process.cwd()){return A(e,".d360-writer","skills")}function E(e){Fe(e)||Ft(e,{recursive:!0})}import{readFileSync as ee,writeFileSync as he,existsSync as ye}from"node:fs";import{homedir as zt}from"node:os";import{join as Jt}from"node:path";var Y=class extends Error{};function ze(e,t){if(!e?.profiles||Object.keys(e.profiles).length===0)throw new Y("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 Y(`No profile specified and no defaultProfile set. Available: ${Object.keys(e.profiles).join(", ")}`);let n=e.profiles[r];if(!n)throw new Y(`Unknown profile "${r}". Available: ${Object.keys(e.profiles).join(", ")}`);return{name:r,profile:n}}function N(e=process.cwd()){let t=me(e);return ye(t)?JSON.parse(ee(t,"utf8")):null}function Je(e,t=process.cwd()){he(me(t),JSON.stringify(e,null,2)+`
2
- `,"utf8")}function we(){let e=pe();if(!ye(e))return{};try{return JSON.parse(ee(e,"utf8"))}catch{return{}}}function qn(e){E(j()),he(pe(),JSON.stringify(e,null,2)+`
3
- `,"utf8")}function Vt(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 Gt(){try{let e=JSON.parse(ee(Jt(zt(),".claude","settings.json"),"utf8"));return typeof e.model=="string"&&e.model?e.model:void 0}catch{return}}function Ln(e=process.cwd()){return Vt(N(e)?.defaultModel,we().defaultModel,process.env.ANTHROPIC_MODEL,Gt())}function Ve(){let e=fe();if(!ye(e))return{servers:{}};try{return JSON.parse(ee(e,"utf8"))}catch{return{servers:{}}}}function Bn(e){E(j()),he(fe(),JSON.stringify(e,null,2)+`
4
- `,"utf8")}var te=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 Fr,readFileSync as kt}from"node:fs";import{basename as Hr,isAbsolute as Wr,resolve as bt}from"node:path";import{createSdkMcpServer as zr,tool as v}from"@anthropic-ai/claude-agent-sdk";import{z as g}from"zod";import{createHash as Kt,randomBytes as Ge}from"node:crypto";import{createServer as Yt}from"node:http";import{spawn as _e}from"node:child_process";var Ke=600*1e3;function ke(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Qt(e){let t=ke(Ge(32)),r=ke(Kt("sha256").update(t).digest()),n=ke(Ge(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 Zt(e){if(process.env.D360_NO_BROWSER)return;let t=process.platform;if(t==="win32"){let r=e.replace(/'/g,"''");_e("powershell",["-NoProfile","-NonInteractive","-Command",`Start-Process '${r}'`],{detached:!0,stdio:"ignore"}).unref()}else t==="darwin"?_e("open",[e],{detached:!0,stdio:"ignore"}).unref():_e("xdg-open",[e],{detached:!0,stdio:"ignore"}).unref()}function Xt(e,t){let r=new URL(e),n,o=new Promise((s,i)=>{let c=setTimeout(()=>{i(new Error(`Timed out after ${Ke/6e4} minutes waiting for the browser login.`)),n.close()},Ke);n=Yt((u,d)=>{let l=new URL(u.url??"/",`http://${r.host}`);if(l.pathname!==r.pathname){d.writeHead(404).end();return}let p=l.searchParams.get("error"),f=l.searchParams.get("code"),h=l.searchParams.get("state"),k=x=>{d.writeHead(400,{"content-type":"text/html"}),d.end(`<html><body><h3>Login failed</h3><p>${x}</p><p>Return to the terminal.</p></body></html>`),clearTimeout(c),i(new Error(x)),n.close()};if(p)return k(`${p}: ${l.searchParams.get("error_description")??""}`);if(!f)return k("No authorization code in the callback.");if(h!==t)return k("State mismatch \u2014 possible CSRF; try again.");d.writeHead(200,{"content-type":"text/html"}),d.end("<html><body><h3>\u2713 Document360 login complete</h3><p>You can close this tab and return to the terminal.</p></body></html>"),clearTimeout(c),s(f),n.close()}),n.on("error",u=>{clearTimeout(c),i(new Error(`Could not listen on ${r.host} (${u.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 Qe(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 Ye(e,t,r){return Qe(e,new URLSearchParams({grant_type:"authorization_code",code:t,redirect_uri:e.redirectUri,client_id:e.clientId,code_verifier:r}))}async function Ze(e,t){return Qe(e,new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:e.clientId}))}function Xe(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 Vn(e,t,r){let{url:n,verifier:o,state:s}=Qt(e);if(t.manual){r("Open this URL in a browser and sign in:"),r(n);let u=await t.promptForRedirect("Paste the full URL you were redirected to (it contains ?code=...):"),d=new URL(u.trim()),l=d.searchParams.get("code"),p=d.searchParams.get("state");if(!l)throw new Error("No code parameter found in the pasted URL.");if(p!==s)throw new Error("State mismatch in the pasted URL \u2014 restart the login.");return Ye(e,l,o)}let{result:i}=Xt(e.redirectUri,s);r("Opening your browser for Document360 sign-in\u2026"),r(`If it did not open, visit:
5
- ${n}`),Zt(n);let c=await i;return Ye(e,c,o)}import{existsSync as et,readFileSync as er,unlinkSync as tr,writeFileSync as rr}from"node:fs";import{join as tt}from"node:path";function rt(){return tt(j(),"auth")}function be(e){return tt(rt(),`${e}.json`)}function nt(e){E(rt()),rr(be(e.profile),JSON.stringify(e,null,2))}function ve(e){let t=be(e);if(!et(t))return null;try{return JSON.parse(er(t,"utf8"))}catch{return null}}function Zn(e){let t=be(e);return et(t)?(tr(t),!0):!1}function ot(e,t=60){return Date.now()>=new Date(e.expiresAt).getTime()-t*1e3}function st(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}}import{appendFileSync as nr,readdirSync as or,unlinkSync as sr}from"node:fs";import{join as Se}from"node:path";var ir=14,ar=4096;function cr(){return Se(j(),"logs")}function lr(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 dr(e,t=ar){return e.length<=t?e:`${e.slice(0,t)}\u2026 [+${e.length-t} chars]`}function re(e){return e===void 0?void 0:dr(lr(e))}var xe=()=>process.env.D360_LOG_BODIES==="1",ur=/^d360-api-(\d{4}-\d{2}-\d{2})\.jsonl$/;function pr(e){let t=new Date(Date.now()-ir*24*60*60*1e3).toISOString().slice(0,10);for(let r of or(e)){let n=r.match(ur);if(n&&n[1]<t)try{sr(Se(e,r))}catch{}}}var it=!1;function Pe(e){try{let t=cr();E(t),it||(it=!0,pr(t));let r=Se(t,`d360-api-${e.ts.slice(0,10)}.jsonl`);nr(r,JSON.stringify(e)+`
6
- `,"utf8")}catch{}}var I=class extends Error{},ne=class extends Error{constructor(r,n,o){super(r);this.status=n;this.requestId=o}status;requestId};async function at(e){let t=ve(e.profile);if(!t)throw new I(`Not logged in to Document360 (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);if(!ot(t))return t.accessToken;if(!t.refreshToken)throw new I(`Document360 session expired (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);try{let r=Xe(e.profile,await Ze(e.connection,t.refreshToken));return nt(r),r.accessToken}catch(r){throw new I(`Document360 session expired and refresh failed (${r.message.slice(0,120)}). Run /login (or: d360-writer login --profile ${e.profile})`)}}function B(e,t){if(t)return t;let r=ve(e.profile),o=(r?st(r.accessToken)??{}:{}).doc360_project_id;if(typeof o=="string"&&o)return o;throw new I("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 oe(e,t,r,n={}){let o=await at(e),s=new URL(r.startsWith("http")?r:`${e.connection.apiUrl}${r}`);for(let[f,h]of Object.entries(n.query??{}))h!==void 0&&s.searchParams.set(f,String(h));let i=n.body!==void 0?JSON.stringify(n.body):void 0,c=Date.now(),u=f=>Pe({ts:new Date().toISOString(),profile:e.profile,method:t,url:s.toString(),ms:Date.now()-c,ok:f.ok,status:f.status,requestId:f.requestId,error:f.error,...f.withBodies?{requestBody:re(i),responseBody:re(f.responseBody)}:{}}),d;try{d=await fetch(s,{method:t,headers:{authorization:`Bearer ${o}`,accept:"application/json",...i!==void 0?{"content-type":"application/json"}:{}},body:i})}catch(f){throw u({ok:!1,error:`network: ${f.message}`,withBodies:!0}),f}let l=await d.text(),p={};if(l)try{p=JSON.parse(l)}catch{}if(!d.ok||p.success===!1){let f=p.errors?.map(k=>k.message??k.code).filter(Boolean).join("; ")||l.slice(0,300)||d.statusText;if(u({ok:!1,status:d.status,requestId:p.request_id,error:f,responseBody:l,withBodies:!0}),d.status===401)throw new I(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let h=p.request_id?` [request_id: ${p.request_id}]`:"";throw new ne(`Document360 API ${d.status}: ${f}${h}`,d.status,p.request_id)}return u({ok:!0,status:d.status,requestId:p.request_id,responseBody:l,withBodies:xe()}),p}async function R(e,t,r){return(await oe(e,"GET",t,r)).data}async function F(e,t,r){return(await oe(e,"POST",t,r)).data}async function ct(e,t,r){return(await oe(e,"PATCH",t,r)).data}async function lt(e,t,r){let n=await at(e),o=`${e.connection.apiUrl}${t}`,s=Date.now(),i=l=>Pe({ts:new Date().toISOString(),profile:e.profile,method:"POST",url:o,ms:Date.now()-s,ok:l.ok,status:l.status,requestId:l.requestId,error:l.error,...l.withBodies?{requestBody:"[multipart form-data]",responseBody:re(l.responseBody)}:{}}),c;try{c=await fetch(o,{method:"POST",headers:{authorization:`Bearer ${n}`,accept:"application/json"},body:r})}catch(l){throw i({ok:!1,error:`network: ${l.message}`,withBodies:!0}),l}let u=await c.text(),d={};if(u)try{d=JSON.parse(u)}catch{}if(!c.ok||d.success===!1){let l=d.errors?.map(f=>f.message??f.code).filter(Boolean).join("; ")||u.slice(0,300)||c.statusText;if(i({ok:!1,status:c.status,requestId:d.request_id,error:l,responseBody:u,withBodies:!0}),c.status===401)throw new I(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let p=d.request_id?` [request_id: ${d.request_id}]`:"";throw new ne(`Document360 upload ${c.status}: ${l}${p}`,c.status,d.request_id)}return i({ok:!0,status:c.status,requestId:d.request_id,responseBody:u,withBodies:xe()}),d.data}async function $(e,t,r={}){let n=[],o;for(let s=0;s<50;s++){let i=await oe(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 ao(e,t){return $(e,`/v3/projects/${t}/workspaces`)}async function co(e,t,r){return R(e,`/v3/projects/${t}/articles/${r}`,{query:{content_mode:"raw"}})}import{existsSync as fr,readFileSync as gr,writeFileSync as mr}from"node:fs";import{join as hr}from"node:path";function dt(e){return hr(e,"d360-category-map.json")}function ut(e){let t=dt(e);if(!fr(t))return null;try{return JSON.parse(gr(t,"utf8"))}catch{return null}}function pt(e){return typeof e=="string"?{id:e}:{...e}}function H(e,t){let n=ut(e)?.[t];if(!n)return null;let o={};for(let[s,i]of Object.entries(n.articles??{}))o[s]=pt(i);return{environment:n.environment,projectId:n.projectId,workspaceId:n.workspaceId,lastAnalyzedCommit:n.lastAnalyzedCommit,categories:{...n.categories??{}},articles:o}}function ft(e,t){for(let[r,n]of Object.entries(e.articles))if(n.id===t)return r;return null}function se(e,t,r,n){let o=ut(e),s=o?.[t];if(!o||!s)return!1;s.articles??={};let i=s.articles[r],c=i!==void 0?pt(i):null;return s.articles[r]={...c??{},...n,lastSyncedAt:new Date().toISOString()},mr(dt(e),JSON.stringify(o,null,2)+`
7
- `,"utf8"),!0}import{createHash as yr}from"node:crypto";function wr(e){let t=e.replace(/^\uFEFF/,"").replace(/\r\n/g,`
1
+ import{readdirSync as on,readFileSync as Mt,existsSync as Oe,statSync as sn}from"node:fs";import{join as $e}from"node:path";import{query as an}from"@anthropic-ai/claude-agent-sdk";import{existsSync as Ht}from"node:fs";import{isAbsolute as zt,relative as Wt,resolve as qe}from"node:path";var Fe=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function fe(e,t,r){let n=zt(t)?t:qe(e,t),o=Wt(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 Jt=/^(\/dev\/null|nul|null)$/i,te=e=>e.replace(/^['"]/,"").replace(/['"]$/,""),Gt=new Set(["rm","rmdir","mv","truncate","touch","mkdir","chmod","chown"]),Vt=new Set(["cp","ln","install"]);function He(e,t,r){let n=i=>{let c=te(i);return!c||c.startsWith("&")||c.startsWith("-")||Jt.test(c)?!1:!fe(t,c,r)},o=/(?:^|[^0-9>])>>?\s*("[^"]+"|'[^']+'|[^\s|&;<>]+)/g,s;for(;s=o.exec(e);)if(n(s[1]))return te(s[1]);for(let i of e.split(/&&|\|\||[;|]/)){let c=i.trim().split(/\s+/).filter(Boolean);if(c.length===0)continue;let d=c[0].replace(/.*[/\\]/,""),u=c.slice(1),l=u.filter(f=>!f.startsWith("-")),p=[];Gt.has(d)?p=l:Vt.has(d)?p=l.slice(-1):d==="tee"?p=l:d==="dd"?p=u.filter(f=>f.startsWith("of=")).map(f=>f.slice(3)):(d==="sed"||d==="perl")&&u.some(f=>/^-i/.test(f)||f==="--in-place")&&(p=l.filter(f=>Ht(qe(t,te(f)))));for(let f of p)if(n(f))return te(f)}return null}function me(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 Kt}from"node:os";import{join as A,dirname as We}from"node:path";import{fileURLToPath as Yt}from"node:url";import{existsSync as Je,mkdirSync as Qt}from"node:fs";var Zt=Yt(import.meta.url),ze=We(Zt);function Xt(){let e=ze;for(let t=0;t<4;t++){if(Je(A(e,"package.json")))return e;e=We(e)}return A(ze,"..","..")}function Ge(){return A(Xt(),"skills")}function j(){return A(Kt(),".document360-writer")}function ge(){return A(j(),"config.json")}function he(){return A(j(),"mcp.json")}function ye(){return A(j(),"sessions.json")}function we(e=process.cwd()){return A(e,".d360-writer.json")}function Ve(e=process.cwd()){return A(e,".d360-writer","skills")}function I(e){Je(e)||Qt(e,{recursive:!0})}import{readFileSync as re,writeFileSync as ke,existsSync as _e}from"node:fs";import{homedir as er}from"node:os";import{join as tr}from"node:path";var Y=class extends Error{};function Ke(e,t){if(!e?.profiles||Object.keys(e.profiles).length===0)throw new Y("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 Y(`No profile specified and no defaultProfile set. Available: ${Object.keys(e.profiles).join(", ")}`);let n=e.profiles[r];if(!n)throw new Y(`Unknown profile "${r}". Available: ${Object.keys(e.profiles).join(", ")}`);return{name:r,profile:n}}function N(e=process.cwd()){let t=we(e);return _e(t)?JSON.parse(re(t,"utf8")):null}function Ye(e,t=process.cwd()){ke(we(t),JSON.stringify(e,null,2)+`
2
+ `,"utf8")}function be(){let e=ge();if(!_e(e))return{};try{return JSON.parse(re(e,"utf8"))}catch{return{}}}function Qn(e){I(j()),ke(ge(),JSON.stringify(e,null,2)+`
3
+ `,"utf8")}function rr(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 nr(){try{let e=JSON.parse(re(tr(er(),".claude","settings.json"),"utf8"));return typeof e.model=="string"&&e.model?e.model:void 0}catch{return}}function Zn(e=process.cwd()){return rr(N(e)?.defaultModel,be().defaultModel,process.env.ANTHROPIC_MODEL,nr())}function Qe(){let e=he();if(!_e(e))return{servers:{}};try{return JSON.parse(re(e,"utf8"))}catch{return{servers:{}}}}function Xn(e){I(j()),ke(he(),JSON.stringify(e,null,2)+`
4
+ `,"utf8")}var ne=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 Xr,readFileSync as Rt,statSync as en}from"node:fs";import{basename as tn,isAbsolute as rn,resolve as Me}from"node:path";import{createSdkMcpServer as nn,tool as S}from"@anthropic-ai/claude-agent-sdk";import{z as m}from"zod";import{createHash as or,randomBytes as Ze}from"node:crypto";import{createServer as sr}from"node:http";import{spawn as ve}from"node:child_process";var Xe=600*1e3;function Se(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function ir(e){let t=Se(Ze(32)),r=Se(or("sha256").update(t).digest()),n=Se(Ze(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 ar(e){if(process.env.D360_NO_BROWSER)return;let t=process.platform;if(t==="win32"){let r=e.replace(/'/g,"''");ve("powershell",["-NoProfile","-NonInteractive","-Command",`Start-Process '${r}'`],{detached:!0,stdio:"ignore"}).unref()}else t==="darwin"?ve("open",[e],{detached:!0,stdio:"ignore"}).unref():ve("xdg-open",[e],{detached:!0,stdio:"ignore"}).unref()}function cr(e,t){let r=new URL(e),n,o=new Promise((s,i)=>{let c=setTimeout(()=>{i(new Error(`Timed out after ${Xe/6e4} minutes waiting for the browser login.`)),n.close()},Xe);n=sr((d,u)=>{let l=new URL(d.url??"/",`http://${r.host}`);if(l.pathname!==r.pathname){u.writeHead(404).end();return}let p=l.searchParams.get("error"),f=l.searchParams.get("code"),h=l.searchParams.get("state"),_=b=>{u.writeHead(400,{"content-type":"text/html"}),u.end(`<html><body><h3>Login failed</h3><p>${b}</p><p>Return to the terminal.</p></body></html>`),clearTimeout(c),i(new Error(b)),n.close()};if(p)return _(`${p}: ${l.searchParams.get("error_description")??""}`);if(!f)return _("No authorization code in the callback.");if(h!==t)return _("State mismatch \u2014 possible CSRF; try again.");u.writeHead(200,{"content-type":"text/html"}),u.end("<html><body><h3>\u2713 Document360 login complete</h3><p>You can close this tab and return to the terminal.</p></body></html>"),clearTimeout(c),s(f),n.close()}),n.on("error",d=>{clearTimeout(c),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 tt(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 et(e,t,r){return tt(e,new URLSearchParams({grant_type:"authorization_code",code:t,redirect_uri:e.redirectUri,client_id:e.clientId,code_verifier:r}))}async function rt(e,t){return tt(e,new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:e.clientId}))}function nt(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 so(e,t,r){let{url:n,verifier:o,state:s}=ir(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=...):"),u=new URL(d.trim()),l=u.searchParams.get("code"),p=u.searchParams.get("state");if(!l)throw new Error("No code parameter found in the pasted URL.");if(p!==s)throw new Error("State mismatch in the pasted URL \u2014 restart the login.");return et(e,l,o)}let{result:i}=cr(e.redirectUri,s);r("Opening your browser for Document360 sign-in\u2026"),r(`If it did not open, visit:
5
+ ${n}`),ar(n);let c=await i;return et(e,c,o)}import{existsSync as ot,readFileSync as lr,unlinkSync as ur,writeFileSync as dr}from"node:fs";import{join as st}from"node:path";function it(){return st(j(),"auth")}function xe(e){return st(it(),`${e}.json`)}function at(e){I(it()),dr(xe(e.profile),JSON.stringify(e,null,2))}function Pe(e){let t=xe(e);if(!ot(t))return null;try{return JSON.parse(lr(t,"utf8"))}catch{return null}}function uo(e){let t=xe(e);return ot(t)?(ur(t),!0):!1}function ct(e,t=60){return Date.now()>=new Date(e.expiresAt).getTime()-t*1e3}function lt(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}}import{appendFileSync as pr,readdirSync as fr,unlinkSync as mr}from"node:fs";import{join as Te}from"node:path";var gr=14,hr=4096;function yr(){return Te(j(),"logs")}function wr(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 kr(e,t=hr){return e.length<=t?e:`${e.slice(0,t)}\u2026 [+${e.length-t} chars]`}function oe(e){return e===void 0?void 0:kr(wr(e))}var Ce=()=>process.env.D360_LOG_BODIES==="1",_r=/^d360-api-(\d{4}-\d{2}-\d{2})\.jsonl$/;function br(e){let t=new Date(Date.now()-gr*24*60*60*1e3).toISOString().slice(0,10);for(let r of fr(e)){let n=r.match(_r);if(n&&n[1]<t)try{mr(Te(e,r))}catch{}}}var ut=!1;function je(e){try{let t=yr();I(t),ut||(ut=!0,br(t));let r=Te(t,`d360-api-${e.ts.slice(0,10)}.jsonl`);pr(r,JSON.stringify(e)+`
6
+ `,"utf8")}catch{}}var M=class extends Error{},se=class extends Error{constructor(r,n,o){super(r);this.status=n;this.requestId=o}status;requestId};async function dt(e){let t=Pe(e.profile);if(!t)throw new M(`Not logged in to Document360 (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);if(!ct(t))return t.accessToken;if(!t.refreshToken)throw new M(`Document360 session expired (profile "${e.profile}"). Run /login (or: d360-writer login --profile ${e.profile})`);try{let r=nt(e.profile,await rt(e.connection,t.refreshToken));return at(r),r.accessToken}catch(r){throw new M(`Document360 session expired and refresh failed (${r.message.slice(0,120)}). Run /login (or: d360-writer login --profile ${e.profile})`)}}function F(e,t){if(t)return t;let r=Pe(e.profile),o=(r?lt(r.accessToken)??{}:{}).doc360_project_id;if(typeof o=="string"&&o)return o;throw new M("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 ie(e,t,r,n={}){let o=await dt(e),s=new URL(r.startsWith("http")?r:`${e.connection.apiUrl}${r}`);for(let[f,h]of Object.entries(n.query??{}))h!==void 0&&s.searchParams.set(f,String(h));let i=n.body!==void 0?JSON.stringify(n.body):void 0,c=Date.now(),d=f=>je({ts:new Date().toISOString(),profile:e.profile,method:t,url:s.toString(),ms:Date.now()-c,ok:f.ok,status:f.status,requestId:f.requestId,error:f.error,...f.withBodies?{requestBody:oe(i),responseBody:oe(f.responseBody)}:{}}),u;try{u=await fetch(s,{method:t,headers:{authorization:`Bearer ${o}`,accept:"application/json",...i!==void 0?{"content-type":"application/json"}:{}},body:i})}catch(f){throw d({ok:!1,error:`network: ${f.message}`,withBodies:!0}),f}let l=await u.text(),p={};if(l)try{p=JSON.parse(l)}catch{}if(!u.ok||p.success===!1){let f=p.errors?.map(_=>_.message??_.code).filter(Boolean).join("; ")||l.slice(0,300)||u.statusText;if(d({ok:!1,status:u.status,requestId:p.request_id,error:f,responseBody:l,withBodies:!0}),u.status===401)throw new M(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let h=p.request_id?` [request_id: ${p.request_id}]`:"";throw new se(`Document360 API ${u.status}: ${f}${h}`,u.status,p.request_id)}return d({ok:!0,status:u.status,requestId:p.request_id,responseBody:l,withBodies:Ce()}),p}async function D(e,t,r){return(await ie(e,"GET",t,r)).data}async function H(e,t,r){return(await ie(e,"POST",t,r)).data}async function pt(e,t,r){return(await ie(e,"PATCH",t,r)).data}async function ft(e,t,r){let n=await dt(e),o=`${e.connection.apiUrl}${t}`,s=Date.now(),i=l=>je({ts:new Date().toISOString(),profile:e.profile,method:"POST",url:o,ms:Date.now()-s,ok:l.ok,status:l.status,requestId:l.requestId,error:l.error,...l.withBodies?{requestBody:"[multipart form-data]",responseBody:oe(l.responseBody)}:{}}),c;try{c=await fetch(o,{method:"POST",headers:{authorization:`Bearer ${n}`,accept:"application/json"},body:r})}catch(l){throw i({ok:!1,error:`network: ${l.message}`,withBodies:!0}),l}let d=await c.text(),u={};if(d)try{u=JSON.parse(d)}catch{}if(!c.ok||u.success===!1){let l=u.errors?.map(f=>f.message??f.code).filter(Boolean).join("; ")||d.slice(0,300)||c.statusText;if(i({ok:!1,status:c.status,requestId:u.request_id,error:l,responseBody:d,withBodies:!0}),c.status===401)throw new M(`Document360 rejected the token (401). Run /login (or: d360-writer login --profile ${e.profile})`);let p=u.request_id?` [request_id: ${u.request_id}]`:"";throw new se(`Document360 upload ${c.status}: ${l}${p}`,c.status,u.request_id)}return i({ok:!0,status:c.status,requestId:u.request_id,responseBody:d,withBodies:Ce()}),u.data}async function O(e,t,r={}){let n=[],o;for(let s=0;s<50;s++){let i=await ie(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 _o(e,t){return O(e,`/v3/projects/${t}/workspaces`)}async function bo(e,t,r){return D(e,`/v3/projects/${t}/articles/${r}`,{query:{content_mode:"raw"}})}import{existsSync as vr,readFileSync as Sr,writeFileSync as xr}from"node:fs";import{join as Pr}from"node:path";function mt(e){return Pr(e,"d360-category-map.json")}function gt(e){let t=mt(e);if(!vr(t))return null;try{return JSON.parse(Sr(t,"utf8"))}catch{return null}}function ht(e){return typeof e=="string"?{id:e}:{...e}}function B(e,t){let n=gt(e)?.[t];if(!n)return null;let o={};for(let[s,i]of Object.entries(n.articles??{}))o[s]=ht(i);return{environment:n.environment,projectId:n.projectId,workspaceId:n.workspaceId,lastAnalyzedCommit:n.lastAnalyzedCommit,categories:{...n.categories??{}},articles:o}}function yt(e,t){for(let[r,n]of Object.entries(e.articles))if(n.id===t)return r;return null}function ae(e,t,r,n){let o=gt(e),s=o?.[t];if(!o||!s)return!1;s.articles??={};let i=s.articles[r],c=i!==void 0?ht(i):null;return s.articles[r]={...c??{},...n,lastSyncedAt:new Date().toISOString()},xr(mt(e),JSON.stringify(o,null,2)+`
7
+ `,"utf8"),!0}import{createHash as Tr}from"node:crypto";function Cr(e){let t=e.replace(/^\uFEFF/,"").replace(/\r\n/g,`
8
8
  `);return t=t.replace(/\n*$/,""),t===""?"":t+`
9
- `}function q(e){return"sha256:"+yr("sha256").update(wr(e),"utf8").digest("hex")}function W(e,t){return q(`${e??""}
10
- ${t??""}`)}import{existsSync as br,readdirSync as vr,readFileSync as Sr,statSync as xr}from"node:fs";import{join as je}from"node:path";var gt={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 _r(){return Object.keys(gt)}function ho(e="berlin"){return ie({environment:e})}function ie(e){let t=e.environment??"berlin",r=gt[t];if(!r)throw new Error(`Unknown Document360 environment "${t}". Known: ${_r().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 z(e,t){let r=N(e);if(r===null){let s=t??"berlin";return{name:s,connection:ie({environment:s}),project:{},production:!1}}let{name:n,profile:o}=ze(r,t);return{name:n,connection:ie(o.connection),project:o.project??{},production:o.production===!0}}function ko(e,t,r){let n=N(e);n?.profiles?.[t]&&(n.profiles[t].project={...n.profiles[t].project,...r},Je(n,e))}function mt(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 kr=4;function ht(e,t,r,n={getAll:$,get:R}){let o=`/v3/projects/${t}`;return{async fetch(s){let[i,c]=await Promise.all([n.getAll(e,`${o}/workspaces/${r}/articles`),n.getAll(e,`${o}/workspaces/${r}/categories`)]),u=new Map;for(let h of i)u.set(h.id,{id:h.id,title:h.title,categoryId:h.category_id,hidden:h.hidden});let d=new Map;for(let h of c)d.set(h.id,{id:h.id,name:h.name,parentId:h.parent_category_id});let l=s.filter(h=>u.has(h)),p=0,f=async()=>{for(;p<l.length;){let h=l[p++],k=await n.get(e,`${o}/articles/${h}`,{query:{content_mode:"raw"}}),x=u.get(h);x.title=k.title??x.title,x.content=k.content,x.latestVersion=k.latest_version,x.modifiedAt=k.modified_at,x.contentHash=W(k.title??x.title,k.content)}};return await Promise.all(Array.from({length:Math.min(kr,l.length)},f)),{articles:u,categories:d}}}}function Pr(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 jr(e,t){let r=je(e,t);if(!br(r))return[];let n=[],o=(s,i)=>{for(let c of vr(s)){let u=je(s,c),d=i?`${i}/${c}`:c;xr(u).isDirectory()?o(u,d):c.endsWith(".md")&&n.push(d)}};return o(r,t.replace(/\\/g,"/")),n}function Cr(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 c=n.get(i.parentId)??[];c.push(i.id),n.set(i.parentId,c)}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 yt(e){let t=z(e.cwd,e.profileName),r=N(e.cwd),n=H(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??B(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 c=mt(r,n),u=Object.values(n.articles).map(b=>b.id),l=await(e.provider??ht(o,s,i)).fetch(u),p=new Set(jr(e.cwd,c)),f=[];for(let[b,O]of Object.entries(n.articles)){let Q=p.has(b);p.delete(b);let T=l.articles.get(O.id),le=!!(O.localHash&&O.remoteContentHash),G=Pr({mapped:!0,localExists:Q,remoteExists:T!==void 0,hasBase:le,localHashNow:Q?q(Sr(je(e.cwd,b),"utf8")):void 0,baseLocalHash:O.localHash,remoteContentHashNow:T?.contentHash,baseRemoteContentHash:O.remoteContentHash}),J=[];if(G==="remote-ahead"||G==="conflict"){let Z=T?.modifiedAt?.slice(0,10);J.push(`remote edited${Z?` ${Z}`:""}${T?.latestVersion?` (v${T.latestVersion})`:""}`)}T?.hidden&&J.push("hidden on D360"),f.push({path:b,articleId:O.id,title:T?.title,status:G,detail:J.length>0?J.join("; "):void 0})}for(let b of[...p].sort())f.push({path:b,articleId:null,status:"untracked-local"});let h=new Set(u),k=Cr(n,l);for(let b of l.articles.values())h.has(b.id)||!b.categoryId||!k.has(b.categoryId)||f.push({path:null,articleId:b.id,title:b.title,status:"untracked-remote",detail:b.hidden?"hidden on D360":void 0});let x={};for(let b of f)x[b.status]=(x[b.status]??0)+1;return{profile:t.name,projectId:s,workspaceId:i,docsRoot:c,entries:f,counts:x,generatedAt:new Date().toISOString()}}import{posix as Ce}from"node:path";var Dr=/\[([^\]]+)\]\(([^)\s]+)\)/g;function De(e,t,r){if(!r)return{content:e,resolved:0,unresolved:0};let n=Ce.dirname(t.replace(/\\/g,"/")),o=0,s=0;return{content:e.replace(Dr,(c,u,d)=>{if(/^(https?:|mailto:|#|\/)/i.test(d)||!/\.(md|markdown)(#[^)]*)?$/i.test(d))return c;let l=d.indexOf("#"),p=l===-1?d:d.slice(0,l),f=l===-1?"":d.slice(l),h=Ce.normalize(Ce.join(n,p)).replace(/^\.\//,""),k=r.articles[h]?.url;return k?(o++,`[${u}](${k}${f})`):(s++,u)}),resolved:o,unresolved:s}}import{readdirSync as Rr}from"node:fs";import{join as ae}from"node:path";var Tr=new Set([".git","node_modules","dist","build","bin","obj","out","target",".vs",".vscode",".idea",".next","coverage",".turbo",".cache","packages"]),Ar=40,Er=3,Re=5e3,Ir=/\.(csproj|vbproj|fsproj)$|^(package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|pom\.xml|build\.gradle(\.kts)?)$/i,$r=/\.sln$|^(Directory\.(Build|Packages)\.props|global\.json|go\.work)$/i;function Or(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 Ur=/(?:^|[.\-_])(web|portal|ui|app|apps|client|site|widget|admin|dashboard|frontend|api)(?:$|[.\-_])/i,Mr=/(?:^|[.\-_])(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 Te(e){let t=[],r=[];try{for(let n of Rr(e,{withFileTypes:!0}))n.isDirectory()?!n.name.startsWith(".")&&!Tr.has(n.name)&&t.push(n.name):n.isFile()&&r.push(n.name)}catch{}return{dirs:t,files:r}}function Nr(e){let t=new Set;for(let r of e){let n=Or(r);n&&t.add(n)}return t}var wt=e=>e.some(t=>Ir.test(t)),qr=e=>e.some(t=>$r.test(t));function Lr(e){let t=0,r=n=>{if(t>=Re)return;let{dirs:o,files:s}=Te(n);t+=s.length;for(let i of o){if(t>=Re)return;r(ae(n,i))}};return r(e),Math.min(t,Re)}function Br(e,t){let r=e.split("/").pop()??e,n=t.size?[...t].join("+"):"";return Mr.test(r)?{recommended:!1,reason:`tests/infrastructure${n?` \xB7 ${n}`:""}`}:Ur.test(r)?{recommended:!0,reason:`user-facing surface${n?` \xB7 ${n}`:""}`}:{recommended:!1,reason:n?`${n} project`:"source folder"}}function _t(e){let t=[],r=(s,i)=>{let c=Nr(i.files),{recommended:u,reason:d}=Br(s,c);t.push({path:s,fileCount:Lr(ae(e,s)),stacks:[...c],recommended:u,reason:d})},n=(s,i)=>{let c=s?ae(e,s):e,u=Te(c);if(s&&wt(u.files)){r(s,u);return}if(s&&u.dirs.length===0){r(s,u);return}let d=p=>wt(Te(ae(c,p)).files);if((qr(u.files)||u.dirs.some(d)||s==="")&&i<Er){for(let p of u.dirs)n(s?`${s}/${p}`:p,i+1);return}s&&r(s,u)};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(Ar,o.length))}var vt="/v3/projects",P=e=>`${vt}/${e}`;function S(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)??'{"success":true}'}]}}function w(e){return{content:[{type:"text",text:e instanceof Error?e.message:String(e)}],isError:!0}}function St(e,t){let r={profile:e.name,connection:e.connection},n=e.project.projectId,o=e.project.workspaceId,s=()=>B(r,n),i=a=>{let m=a??o;if(!m)throw new Error("No workspace_id given and none in the profile. Call d360_list_workspaces first.");return m},c=(a,m)=>{if(t.cwd)try{let y=bt(t.cwd,a);se(t.cwd,e.name,a.replace(/\\/g,"/"),{...m,...Fr(y)?{localHash:q(kt(y,"utf8"))}:{}})}catch{}},u=()=>t.writesAllowed?null:w(`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.`),d=v("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 S({profile:e.name,environment:e.connection.name,production:e.production,projectId:n??B(r),workspaceId:o??null})}catch(a){return w(a)}}),l=v("d360_list_projects","List all Document360 projects the signed-in user can access (id, name, sub-domain, status).",{},async()=>{try{return S(await $(r,vt))}catch(a){return w(a)}}),p=v("d360_list_workspaces","List workspaces (project versions) for the current project. Each has id, name, slug, is_default, workspace_type.",{project_id:g.string().optional().describe("Defaults to the logged-in/config project.")},async a=>{try{return S(await $(r,`${P(a.project_id??s())}/workspaces`))}catch(m){return w(m)}}),f=v("d360_list_categories","List categories in a workspace (the folder structure for articles).",{workspace_id:g.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:g.string().optional()},async a=>{try{let m=a.workspace_id??o;return m?S(await $(r,`${P(a.project_id??s())}/workspaces/${m}/categories`)):w("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(m){return w(m)}}),h=v("d360_list_articles","List articles in a workspace (id, title, status, category). Use to see existing docs before writing.",{workspace_id:g.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:g.string().optional()},async a=>{try{let m=a.workspace_id??o;return m?S(await $(r,`${P(a.project_id??s())}/workspaces/${m}/articles`)):w("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(m){return w(m)}}),k=v("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:g.string(),content_mode:g.enum(["raw","display"]).optional().describe("raw = stored markdown source (default), display = processed."),published:g.boolean().optional().describe("Read the published version instead of the latest draft."),project_id:g.string().optional()},async a=>{try{return S(await R(r,`${P(a.project_id??s())}/articles/${a.article_id}`,{query:{content_mode:a.content_mode,published:a.published}}))}catch(m){return w(m)}}),x=v("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:g.string(),workspace_id:g.string().optional().describe("Defaults to d360.workspaceId in config."),project_id:g.string().optional()},async a=>{try{let m=a.workspace_id??o;return m?S(await F(r,`${P(a.project_id??s())}/workspaces/ai/query`,{body:{query:a.query,workspace_id:m}})):w("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(m){return w(m)}}),b=v("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 w("Sync status needs a repo context (no cwd configured for this session).");let a=await yt({cwd:t.cwd,profileName:e.name});return S({profile:a.profile,docsRoot:a.docsRoot,generatedAt:a.generatedAt,counts:a.counts,attention:a.entries.filter(m=>m.status!=="in-sync")})}catch(a){return w(a)}}),O=v("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?S(_t(t.cwd)):w("Repo inventory needs a repo context (no cwd configured for this session).")}catch(a){return w(a)}}),Q=v("d360_create_category","Create a category (a docs folder). Returns the new category id.",{name:g.string(),workspace_id:g.string().optional(),parent_category_id:g.string().optional().describe("Omit for a top-level category."),content:g.string().optional(),slug:g.string().optional(),order:g.number().optional(),project_id:g.string().optional()},async a=>{let m=u();if(m)return m;try{return S(await F(r,`${P(a.project_id??s())}/categories`,{body:{name:a.name,workspace_id:i(a.workspace_id),parent_category_id:a.parent_category_id,content:a.content,slug:a.slug,order:a.order,content_type:"markdown"}}))}catch(y){return w(y)}}),T=v("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:g.string(),category_id:g.string(),content:g.string().optional().describe("Markdown body."),workspace_id:g.string().optional(),slug:g.string().optional(),order:g.number().optional(),project_id:g.string().optional(),local_path:g.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 a=>{let m=u();if(m)return m;try{let y=a.project_id??s(),D=a.content;typeof D=="string"&&a.local_path&&t.cwd&&(D=De(D,a.local_path,H(t.cwd,e.name)).content);let _=await F(r,`${P(y)}/articles/bulk`,{body:{articles:[{title:a.title,category_id:a.category_id,workspace_id:i(a.workspace_id),content:D,slug:a.slug,order:a.order,content_type:"markdown"}]}}),U=Array.isArray(_)?_[0]:void 0;if(a.local_path&&typeof U?.id=="string"){let C;try{let M=await R(r,`${P(y)}/articles/${U.id}`,{});C=typeof M?.url=="string"?M.url:void 0}catch{}c(a.local_path,{id:U.id,remoteContentHash:W(a.title,D),remoteVersion:1,...C?{url:C}:{}})}return S(_)}catch(y){return w(y)}}),le=v("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:g.string(),title:g.string().optional(),content:g.string().optional().describe("Markdown body."),category_id:g.string().optional(),hidden:g.boolean().optional(),version_number:g.number().optional(),auto_fork:g.boolean().optional().describe("If the target version is published, fork a new draft instead of erroring."),project_id:g.string().optional()},async a=>{let m=u();if(m)return m;try{let{article_id:y,project_id:D,..._}=a,U=D??s(),C=typeof _.content=="string"&&t.cwd?H(t.cwd,e.name):null,M=C?ft(C,y):null;typeof _.content=="string"&&M&&C&&(_.content=De(_.content,M,C).content);let L=null;if(typeof _.content=="string"&&t.onUiEvent)try{L=await R(r,`${P(U)}/articles/${y}`,{query:{content_mode:"raw"}})}catch{}let $e=e.project.languageCode??L?.lang_code,Oe=await ct(r,`${P(U)}/articles/${y}`,{body:_,...$e?{query:{lang_code:$e}}:{}});if(L&&typeof _.content=="string"&&(L.content??"")!==_.content&&t.onUiEvent?.({type:"article_diff",articleId:y,title:L.title??null,oldContent:L.content??"",newContent:_.content}),typeof _.content=="string"&&M)try{let K=Oe,Ue=_.title??K?.title??L?.title;typeof Ue=="string"&&c(M,{id:y,remoteContentHash:W(Ue,_.content),...typeof K?.latest_version=="number"?{remoteVersion:K.latest_version}:{},...typeof K?.modified_at=="string"?{remoteModifiedAt:K.modified_at}:{}})}catch{}return S(Oe)}catch(y){return w(y)}}),G=v("d360_fork_article","Fork a published article into a new draft version \u2014 the safe way to start editing live content.",{article_id:g.string(),project_id:g.string().optional()},async a=>{let m=u();if(m)return m;try{return S(await F(r,`${P(a.project_id??s())}/articles/${a.article_id}/fork`))}catch(y){return w(y)}}),J=v("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:g.string(),version_number:g.number(),workspace_id:g.string().optional(),message:g.string().optional(),project_id:g.string().optional()},async a=>{let m=u();if(m)return m;try{return S(await F(r,`${P(a.project_id??s())}/articles/${a.article_id}/publish`,{body:{workspace_id:i(a.workspace_id),version_number:a.version_number,message:a.message}}))}catch(y){return w(y)}}),Z=v("d360_unpublish_article","Unpublish an article version, reverting it to draft (removes it from readers).",{article_id:g.string(),version_number:g.number().describe("The published version to unpublish (see d360_get_article)."),workspace_id:g.string().optional(),project_id:g.string().optional()},async a=>{let m=u();if(m)return m;try{return S(await F(r,`${P(a.project_id??s())}/articles/${a.article_id}/unpublish`,{body:{workspace_id:i(a.workspace_id),version_number:a.version_number}}))}catch(y){return w(y)}}),Et=v("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:g.string().describe("Local path to the file (absolute, or relative to the repo)."),folder_id:g.string().optional(),title:g.string().optional(),project_id:g.string().optional()},async a=>{let m=u();if(m)return m;try{let y=a.project_id??s(),D=Wr(a.file_path)?a.file_path:bt(process.cwd(),a.file_path),_=a.folder_id;if(!_&&(_=(await R(r,`${P(y)}/drive/folders/default`))?.id,!_))return w("Could not resolve the default Drive folder.");let U=kt(D),C=new FormData;return C.append("file",new Blob([U]),a.title??Hr(D)),S(await lt(r,`${P(y)}/drive/folders/${_}/files`,C))}catch(y){return w(y)}}),It=v("d360_search_drive","Search Drive files (e.g. to reuse an already-uploaded screenshot instead of uploading a duplicate).",{search_keyword:g.string().optional(),allow_images_only:g.boolean().optional(),project_id:g.string().optional()},async a=>{try{return S(await $(r,`${P(a.project_id??s())}/drive/search`,{query:{search_keyword:a.search_keyword,allow_images_only:a.allow_images_only}}))}catch(m){return w(m)}});return zr({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. Auth errors mean the session expired \u2014 tell the user to run /login (works inside this session).",tools:[d,l,p,f,h,k,x,b,O,Q,T,le,G,J,Z,Et,It]})}var Ct="CLAUDE.md";function xt(e){if(!Ae(e))return[];let t=[];for(let r of Jr(e)){let n=Ee(e,r);if(r===Ct||!Vr(n).isDirectory())continue;let o=Ee(n,"SKILL.md");Ae(o)&&t.push({name:r,body:jt(o,"utf8")})}return t}function Pt(e){let t=Ee(e,Ct);return Ae(t)?jt(t,"utf8"):null}function Kr(e,t){let r=He(),n=We(e),o=Pt(r),s=Pt(n),i=xt(r),c=xt(n),u=new Set(c.map(p=>p.name)),d=[...i.filter(p=>!u.has(p.name)),...c],l=[];if(o&&l.push(o),s&&l.push(`# Project addendum
9
+ `}function L(e){return"sha256:"+Tr("sha256").update(Cr(e),"utf8").digest("hex")}function z(e,t){return L(`${e??""}
10
+ ${t??""}`)}import{existsSync as Dr,readdirSync as Er,readFileSync as Ar,statSync as Ir}from"node:fs";import{join as Re}from"node:path";var wt={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 jr(){return Object.keys(wt)}function jo(e="berlin"){return ce({environment:e})}function ce(e){let t=e.environment??"berlin",r=wt[t];if(!r)throw new Error(`Unknown Document360 environment "${t}". Known: ${jr().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 W(e,t){let r=N(e);if(r===null){let s=t??"berlin";return{name:s,connection:ce({environment:s}),project:{},production:!1}}let{name:n,profile:o}=Ke(r,t);return{name:n,connection:ce(o.connection),project:o.project??{},production:o.production===!0}}function Ao(e,t,r){let n=N(e);n?.profiles?.[t]&&(n.profiles[t].project={...n.profiles[t].project,...r},Ye(n,e))}function kt(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 Rr=4;function _t(e,t,r,n={getAll:O,get:D}){let o=`/v3/projects/${t}`;return{async fetch(s){let[i,c]=await Promise.all([n.getAll(e,`${o}/workspaces/${r}/articles`),n.getAll(e,`${o}/workspaces/${r}/categories`)]),d=new Map;for(let h of i)d.set(h.id,{id:h.id,title:h.title,categoryId:h.category_id,hidden:h.hidden});let u=new Map;for(let h of c)u.set(h.id,{id:h.id,name:h.name,parentId:h.parent_category_id});let l=s.filter(h=>d.has(h)),p=0,f=async()=>{for(;p<l.length;){let h=l[p++],_=await n.get(e,`${o}/articles/${h}`,{query:{content_mode:"raw"}}),b=d.get(h);b.title=_.title??b.title,b.content=_.content,b.latestVersion=_.latest_version,b.modifiedAt=_.modified_at,b.contentHash=z(_.title??b.title,_.content)}};return await Promise.all(Array.from({length:Math.min(Rr,l.length)},f)),{articles:d,categories:u}}}}function Mr(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 Or(e,t){let r=Re(e,t);if(!Dr(r))return[];let n=[],o=(s,i)=>{for(let c of Er(s)){let d=Re(s,c),u=i?`${i}/${c}`:c;Ir(d).isDirectory()?o(d,u):c.endsWith(".md")&&n.push(u)}};return o(r,t.replace(/\\/g,"/")),n}function $r(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 c=n.get(i.parentId)??[];c.push(i.id),n.set(i.parentId,c)}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 bt(e){let t=W(e.cwd,e.profileName),r=N(e.cwd),n=B(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??F(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 c=kt(r,n),d=Object.values(n.articles).map(v=>v.id),l=await(e.provider??_t(o,s,i)).fetch(d),p=new Set(Or(e.cwd,c)),f=[];for(let[v,$]of Object.entries(n.articles)){let X=p.has(v);p.delete(v);let E=l.articles.get($.id),pe=!!($.localHash&&$.remoteContentHash),V=Mr({mapped:!0,localExists:X,remoteExists:E!==void 0,hasBase:pe,localHashNow:X?L(Ar(Re(e.cwd,v),"utf8")):void 0,baseLocalHash:$.localHash,remoteContentHashNow:E?.contentHash,baseRemoteContentHash:$.remoteContentHash}),J=[];if(V==="remote-ahead"||V==="conflict"){let ee=E?.modifiedAt?.slice(0,10);J.push(`remote edited${ee?` ${ee}`:""}${E?.latestVersion?` (v${E.latestVersion})`:""}`)}E?.hidden&&J.push("hidden on D360"),f.push({path:v,articleId:$.id,title:E?.title,status:V,detail:J.length>0?J.join("; "):void 0})}for(let v of[...p].sort())f.push({path:v,articleId:null,status:"untracked-local"});let h=new Set(d),_=$r(n,l);for(let v of l.articles.values())h.has(v.id)||!v.categoryId||!_.has(v.categoryId)||f.push({path:null,articleId:v.id,title:v.title,status:"untracked-remote",detail:v.hidden?"hidden on D360":void 0});let b={};for(let v of f)b[v.status]=(b[v.status]??0)+1;return{profile:t.name,projectId:s,workspaceId:i,docsRoot:c,entries:f,counts:b,generatedAt:new Date().toISOString()}}import{posix as De}from"node:path";var Ur=/\[([^\]]+)\]\(([^)\s]+)\)/g;function Ee(e,t,r){if(!r)return{content:e,resolved:0,unresolved:0};let n=De.dirname(t.replace(/\\/g,"/")),o=0,s=0;return{content:e.replace(Ur,(c,d,u)=>{if(/^(https?:|mailto:|#|\/)/i.test(u)||!/\.(md|markdown)(#[^)]*)?$/i.test(u))return c;let l=u.indexOf("#"),p=l===-1?u:u.slice(0,l),f=l===-1?"":u.slice(l),h=De.normalize(De.join(n,p)).replace(/^\.\//,""),_=r.articles[h]?.url;return _?(o++,`[${d}](${_}${f})`):(s++,d)}),resolved:o,unresolved:s}}import{readdirSync as Nr}from"node:fs";import{join as le}from"node:path";var Br=new Set([".git","node_modules","dist","build","bin","obj","out","target",".vs",".vscode",".idea",".next","coverage",".turbo",".cache","packages"]),Lr=40,qr=3,Ae=5e3,Fr=/\.(csproj|vbproj|fsproj)$|^(package\.json|go\.mod|Cargo\.toml|pyproject\.toml|setup\.py|pom\.xml|build\.gradle(\.kts)?)$/i,Hr=/\.sln$|^(Directory\.(Build|Packages)\.props|global\.json|go\.work)$/i;function zr(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 Wr=/(?:^|[.\-_])(web|portal|ui|app|apps|client|site|widget|admin|dashboard|frontend|api)(?:$|[.\-_])/i,Jr=/(?:^|[.\-_])(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 Ie(e){let t=[],r=[];try{for(let n of Nr(e,{withFileTypes:!0}))n.isDirectory()?!n.name.startsWith(".")&&!Br.has(n.name)&&t.push(n.name):n.isFile()&&r.push(n.name)}catch{}return{dirs:t,files:r}}function Gr(e){let t=new Set;for(let r of e){let n=zr(r);n&&t.add(n)}return t}var vt=e=>e.some(t=>Fr.test(t)),Vr=e=>e.some(t=>Hr.test(t));function Kr(e){let t=0,r=n=>{if(t>=Ae)return;let{dirs:o,files:s}=Ie(n);t+=s.length;for(let i of o){if(t>=Ae)return;r(le(n,i))}};return r(e),Math.min(t,Ae)}function Yr(e,t){let r=e.split("/").pop()??e,n=t.size?[...t].join("+"):"";return Jr.test(r)?{recommended:!1,reason:`tests/infrastructure${n?` \xB7 ${n}`:""}`}:Wr.test(r)?{recommended:!0,reason:`user-facing surface${n?` \xB7 ${n}`:""}`}:{recommended:!1,reason:n?`${n} project`:"source folder"}}function St(e){let t=[],r=(s,i)=>{let c=Gr(i.files),{recommended:d,reason:u}=Yr(s,c);t.push({path:s,fileCount:Kr(le(e,s)),stacks:[...c],recommended:d,reason:u})},n=(s,i)=>{let c=s?le(e,s):e,d=Ie(c);if(s&&vt(d.files)){r(s,d);return}if(s&&d.dirs.length===0){r(s,d);return}let u=p=>vt(Ie(le(c,p)).files);if((Vr(d.files)||d.dirs.some(u)||s==="")&&i<qr){for(let p of d.dirs)n(s?`${s}/${p}`:p,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(Lr,o.length))}var Q={"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}},xt="claude-opus-4-8";function Pt(e){if(e&&Q[e])return{model:e,rate:Q[e],assumed:!1};if(e){let t=Object.keys(Q).filter(r=>e.startsWith(r)).sort((r,n)=>n.length-r.length)[0];if(t)return{model:t,rate:Q[t],assumed:!1}}return{model:xt,rate:Q[xt],assumed:!0}}function Z(e,t){return e/1e6*t}var Qr=3.5,Zr={convert:{in:1.3,out:1.1,outFloor:0},rewrite:{in:1.4,out:1.2,outFloor:0},publish:{in:1.1,out:.2,outFloor:200},audit:{in:1.2,out:.15,outFloor:0}},Tt=.7,Ct=1.5,ue=e=>Math.round(e);function jt(e){let{op:t,model:r}=e,n=Zr[t],{model:o,rate:s,assumed:i}=Pt(r),c=e.files.filter(b=>!(b.bytes>0)).map(b=>b.path),d=e.files.filter(b=>b.bytes>0),u=0,l=0;for(let b of d){let v=b.bytes/Qr;u+=v*n.in,l+=Math.max(v*n.out,n.outFloor)}let p=[ue(u*Tt),ue(u*Ct)],f=[ue(l*Tt),ue(l*Ct)],h=[Z(p[0],s.inputPerMTok)+Z(f[0],s.outputPerMTok),Z(p[1],s.inputPerMTok)+Z(f[1],s.outputPerMTok)],_=`Rough estimate from file sizes, 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:c,inputTokens:p,outputTokens:f,usd:h,note:_}}var Dt="/v3/projects",P=e=>`${Dt}/${e}`;function x(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)??'{"success":true}'}]}}function k(e){return{content:[{type:"text",text:e instanceof Error?e.message:String(e)}],isError:!0}}function Et(e,t){let r={profile:e.name,connection:e.connection},n=e.project.projectId,o=e.project.workspaceId,s=()=>F(r,n),i=a=>{let g=a??o;if(!g)throw new Error("No workspace_id given and none in the profile. Call d360_list_workspaces first.");return g},c=(a,g)=>{if(t.cwd)try{let y=Me(t.cwd,a);ae(t.cwd,e.name,a.replace(/\\/g,"/"),{...g,...Xr(y)?{localHash:L(Rt(y,"utf8"))}:{}})}catch{}},d=()=>t.writesAllowed?null:k(`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??F(r),workspaceId:o??null})}catch(a){return k(a)}}),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 O(r,Dt))}catch(a){return k(a)}}),p=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 a=>{try{return x(await O(r,`${P(a.project_id??s())}/workspaces`))}catch(g){return k(g)}}),f=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 a=>{try{let g=a.workspace_id??o;return g?x(await O(r,`${P(a.project_id??s())}/workspaces/${g}/categories`)):k("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return k(g)}}),h=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 a=>{try{let g=a.workspace_id??o;return g?x(await O(r,`${P(a.project_id??s())}/workspaces/${g}/articles`)):k("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return k(g)}}),_=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 a=>{try{return x(await D(r,`${P(a.project_id??s())}/articles/${a.article_id}`,{query:{content_mode:a.content_mode,published:a.published}}))}catch(g){return k(g)}}),b=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 a=>{try{let g=a.workspace_id??o;return g?x(await H(r,`${P(a.project_id??s())}/workspaces/ai/query`,{body:{query:a.query,workspace_id:g}})):k("No workspace_id given and none in config. Call d360_list_workspaces first.")}catch(g){return k(g)}}),v=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 k("Sync status needs a repo context (no cwd configured for this session).");let a=await bt({cwd:t.cwd,profileName:e.name});return x({profile:a.profile,docsRoot:a.docsRoot,generatedAt:a.generatedAt,counts:a.counts,attention:a.entries.filter(g=>g.status!=="in-sync")})}catch(a){return k(a)}}),$=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(St(t.cwd)):k("Repo inventory needs a repo context (no cwd configured for this session).")}catch(a){return k(a)}}),X=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:a,paths:g})=>{try{if(!t.cwd)return k("Cost estimate needs a repo context (no cwd configured for this session).");let y;if(g&&g.length>0)y=g.map(w=>w.replace(/\\/g,"/"));else{let w=B(t.cwd,e.name);if(y=w?Object.keys(w.articles):[],y.length===0)return k("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 T=y.map(w=>{let C=0;try{C=en(Me(t.cwd,w)).size}catch{C=0}return{path:w,bytes:C}});return x(jt({files:T,op:a,model:t.model}))}catch(y){return k(y)}}),E=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 a=>{let g=d();if(g)return g;try{return x(await H(r,`${P(a.project_id??s())}/categories`,{body:{name:a.name,workspace_id:i(a.workspace_id),parent_category_id:a.parent_category_id,content:a.content,slug:a.slug,order:a.order,content_type:"markdown"}}))}catch(y){return k(y)}}),pe=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 a=>{let g=d();if(g)return g;try{let y=a.project_id??s(),T=a.content;typeof T=="string"&&a.local_path&&t.cwd&&(T=Ee(T,a.local_path,B(t.cwd,e.name)).content);let w=await H(r,`${P(y)}/articles/bulk`,{body:{articles:[{title:a.title,category_id:a.category_id,workspace_id:i(a.workspace_id),content:T,slug:a.slug,order:a.order,content_type:"markdown"}]}}),C=Array.isArray(w)?w[0]:void 0;if(a.local_path&&typeof C?.id=="string"){let R;try{let U=await D(r,`${P(y)}/articles/${C.id}`,{});R=typeof U?.url=="string"?U.url:void 0}catch{}c(a.local_path,{id:C.id,remoteContentHash:z(a.title,T),remoteVersion:1,...R?{url:R}:{}})}return x(w)}catch(y){return k(y)}}),V=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 a=>{let g=d();if(g)return g;try{let{article_id:y,project_id:T,...w}=a,C=T??s(),R=typeof w.content=="string"&&t.cwd?B(t.cwd,e.name):null,U=R?yt(R,y):null;typeof w.content=="string"&&U&&R&&(w.content=Ee(w.content,U,R).content);let q=null;if(typeof w.content=="string"&&t.onUiEvent)try{q=await D(r,`${P(C)}/articles/${y}`,{query:{content_mode:"raw"}})}catch{}let Ne=e.project.languageCode??q?.lang_code,Be=await pt(r,`${P(C)}/articles/${y}`,{body:w,...Ne?{query:{lang_code:Ne}}:{}});if(q&&typeof w.content=="string"&&(q.content??"")!==w.content&&t.onUiEvent?.({type:"article_diff",articleId:y,title:q.title??null,oldContent:q.content??"",newContent:w.content}),typeof w.content=="string"&&U)try{let K=Be,Le=w.title??K?.title??q?.title;typeof Le=="string"&&c(U,{id:y,remoteContentHash:z(Le,w.content),...typeof K?.latest_version=="number"?{remoteVersion:K.latest_version}:{},...typeof K?.modified_at=="string"?{remoteModifiedAt:K.modified_at}:{}})}catch{}return x(Be)}catch(y){return k(y)}}),J=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 a=>{let g=d();if(g)return g;try{return x(await H(r,`${P(a.project_id??s())}/articles/${a.article_id}/fork`))}catch(y){return k(y)}}),ee=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 a=>{let g=d();if(g)return g;try{return x(await H(r,`${P(a.project_id??s())}/articles/${a.article_id}/publish`,{body:{workspace_id:i(a.workspace_id),version_number:a.version_number,message:a.message}}))}catch(y){return k(y)}}),Lt=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 a=>{let g=d();if(g)return g;try{return x(await H(r,`${P(a.project_id??s())}/articles/${a.article_id}/unpublish`,{body:{workspace_id:i(a.workspace_id),version_number:a.version_number}}))}catch(y){return k(y)}}),qt=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 a=>{let g=d();if(g)return g;try{let y=a.project_id??s(),T=rn(a.file_path)?a.file_path:Me(process.cwd(),a.file_path),w=a.folder_id;if(!w&&(w=(await D(r,`${P(y)}/drive/folders/default`))?.id,!w))return k("Could not resolve the default Drive folder.");let C=Rt(T),R=new FormData;return R.append("file",new Blob([C]),a.title??tn(T)),x(await ft(r,`${P(y)}/drive/folders/${w}/files`,R))}catch(y){return k(y)}}),Ft=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 a=>{try{return x(await O(r,`${P(a.project_id??s())}/drive/search`,{query:{search_keyword:a.search_keyword,allow_images_only:a.allow_images_only}}))}catch(g){return k(g)}});return nn({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,p,f,h,_,b,v,$,X,E,pe,V,J,ee,Lt,qt,Ft]})}var Ot="CLAUDE.md";function At(e){if(!Oe(e))return[];let t=[];for(let r of on(e)){let n=$e(e,r);if(r===Ot||!sn(n).isDirectory())continue;let o=$e(n,"SKILL.md");Oe(o)&&t.push({name:r,body:Mt(o,"utf8")})}return t}function It(e){let t=$e(e,Ot);return Oe(t)?Mt(t,"utf8"):null}function cn(e,t){let r=Ge(),n=Ve(e),o=It(r),s=It(n),i=At(r),c=At(n),d=new Set(c.map(p=>p.name)),u=[...i.filter(p=>!d.has(p.name)),...c],l=[];if(o&&l.push(o),s&&l.push(`# Project addendum
11
11
 
12
- ${s}`),t&&(l.push("# Project configuration"),l.push("```json"),l.push(JSON.stringify(t,null,2)),l.push("```")),d.length>0){l.push("# Capabilities"),l.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 p of d)l.push(`## Skill: ${p.name}`),l.push(p.body)}return l.join(`
12
+ ${s}`),t&&(l.push("# Project configuration"),l.push("```json"),l.push(JSON.stringify(t,null,2)),l.push("```")),u.length>0){l.push("# Capabilities"),l.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 p of u)l.push(`## Skill: ${p.name}`),l.push(p.body)}return l.join(`
13
13
 
14
- `)}function Yr(){let e=Ve(),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 Qr=/auth|api key|login|credential|401|forbidden/i,Ie=class{queue;iterator;sdkQuery;uiEvents=[];sessionId=null;constructor(t={}){let r=t.cwd??process.cwd(),n=N(r),o=Kr(r,n),s=Yr();try{let l=z(r,t.profileName),p=!l.production||t.allowProdWrites===!0;s.document360=St(l,{writesAllowed:p,cwd:r,onUiEvent:f=>this.uiEvents.push(f)})}catch{}this.queue=new te;let i=async(l,p)=>{if(Ne.has(l)){let f=p.file_path??p.notebook_path;if(f&&!de(r,f,n))return{behavior:"deny",message:ue(f)}}if(l==="Bash"||l==="PowerShell"){let f=p.command;if(typeof f=="string"){let h=qe(f,r,n);if(h)return{behavior:"deny",message:ue(h)}}}return{behavior:"allow",updatedInput:p}},c=n?.mode==="engineer",u={cwd:r,systemPrompt:o,mcpServers:s,...c?{permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0}:{permissionMode:"default",canUseTool:i},disallowedTools:["Skill","AskUserQuestion"],includePartialMessages:!0,settingSources:[],model:n?.defaultModel??we().defaultModel,resume:t.resume},d=Gr({prompt:this.queue,options:u});this.sdkQuery=d,this.iterator=d[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:Qr.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=Xr(r.value);if(o){for(let s of o)yield s;if(o.some(s=>s.type==="result"))return}}}close(){this.queue.close()}};function as(e={}){return new Ie(e)}function Zr(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 Xr(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:Zr(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 en}from"node:fs";import{homedir as tn}from"node:os";import{join as rn}from"node:path";var ps=["auto","api","subscription"];function Dt(){return process.env.CLAUDE_CODE_OAUTH_TOKEN?!0:en(rn(tn(),".claude",".credentials.json"))}function fs(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:Dt()}):t?{kind:"api"}:{kind:"subscription",stored:Dt()}}import{existsSync as nn,readFileSync as on,writeFileSync as sn}from"node:fs";var an=100;function V(){let e=ge();if(!nn(e))return[];try{let t=JSON.parse(on(e,"utf8"));return Array.isArray(t)?t:[]}catch{return[]}}function ce(e){E(j());let t=[...e].sort((r,n)=>n.updatedAt.localeCompare(r.updatedAt)).slice(0,an);sn(ge(),JSON.stringify(t,null,2))}function ys(e){let t=V().filter(r=>r.uuid!==e.uuid);t.push(e),ce(t)}function ws(e){return V().find(t=>t.uuid===e)}function cn(e){return V().filter(t=>t.cwd===e).sort((t,r)=>r.updatedAt.localeCompare(t.updatedAt))}function _s(e,t){let r=cn(e),n=t.toLowerCase(),o=r.find(c=>c.name.toLowerCase()===n);if(o)return o;let s=r.filter(c=>c.name.toLowerCase().startsWith(n));if(s.length===1)return s[0];let i=r.filter(c=>c.uuid.startsWith(t));if(i.length===1)return i[0]}function ks(e){let t=V(),r=t.find(n=>n.uuid===e);r&&(r.updatedAt=new Date().toISOString(),ce(t))}function bs(e,t){let r=V(),n=r.find(o=>o.uuid===e);return n?(n.name=t,n.renamed=!0,n.updatedAt=new Date().toISOString(),ce(r),!0):!1}function vs(e,t){let r=V(),n=r.find(o=>o.uuid===e);!n||n.renamed||(n.name=t,n.titled=!0,ce(r))}function Ss(e,t=6){return e.toLowerCase().replace(/[^a-z0-9\s-]/g,"").split(/\s+/).filter(Boolean).slice(0,t).join("-")||"session"}function xs(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 ln}from"@anthropic-ai/claude-agent-sdk";var dn=6e4;async function Cs(e,t){try{return await Promise.race([un(e,t),pn()])}catch{return null}}async function un(e,t){let r=ln({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"?fn(o.result):null}return null}function pn(){return new Promise(e=>setTimeout(()=>e(null),dn).unref())}function fn(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 gn}from"@anthropic-ai/claude-agent-sdk";var mn=3e4;async function Ts(e,t,r){try{return await Promise.race([hn(e,t,r),yn()])}catch{return null}}async function hn(e,t,r){let n=gn({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"?wn(s.result):null}return null}function yn(){return new Promise(e=>setTimeout(()=>e(null),mn).unref())}function wn(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 _n=/^---\r?\n[\s\S]*?\r?\n---\r?\n/;function Rt(e){let t=e.match(_n);return t?{frontmatter:t[0],body:e.slice(t[0].length)}:{frontmatter:null,body:e}}import{existsSync as kn,mkdirSync as bn,readFileSync as vn,writeFileSync as Sn}from"node:fs";import{dirname as xn,join as Tt}from"node:path";var At=/^\[Screenshot: [^\]]+\]\s*$/;function Pn(e){let t=e.replace(/\r\n/g,`
14
+ `)}function ln(){let e=Qe(),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 un=/auth|api key|login|credential|401|forbidden/i,Ue=class{queue;iterator;sdkQuery;uiEvents=[];sessionId=null;constructor(t={}){let r=t.cwd??process.cwd(),n=N(r),o=cn(r,n),s=ln(),i=n?.defaultModel??be().defaultModel;try{let p=W(r,t.profileName),f=!p.production||t.allowProdWrites===!0;s.document360=Et(p,{writesAllowed:f,cwd:r,model:i,onUiEvent:h=>this.uiEvents.push(h)})}catch{}this.queue=new ne;let c=async(p,f)=>{if(Fe.has(p)){let h=f.file_path??f.notebook_path;if(h&&!fe(r,h,n))return{behavior:"deny",message:me(h)}}if(p==="Bash"||p==="PowerShell"){let h=f.command;if(typeof h=="string"){let _=He(h,r,n);if(_)return{behavior:"deny",message:me(_)}}}return{behavior:"allow",updatedInput:f}},d=n?.mode==="engineer",u={cwd:r,systemPrompt:o,mcpServers:s,...d?{permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0}:{permissionMode:"default",canUseTool:c},disallowedTools:["Skill","AskUserQuestion"],includePartialMessages:!0,settingSources:[],model:i,resume:t.resume},l=an({prompt:this.queue,options:u});this.sdkQuery=l,this.iterator=l[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:un.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=pn(r.value);if(o){for(let s of o)yield s;if(o.some(s=>s.type==="result"))return}}}close(){this.queue.close()}};function Ss(e={}){return new Ue(e)}function dn(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 pn(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:dn(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 fn}from"node:fs";import{homedir as mn}from"node:os";import{join as gn}from"node:path";var js=["auto","api","subscription"];function $t(){return process.env.CLAUDE_CODE_OAUTH_TOKEN?!0:fn(gn(mn(),".claude",".credentials.json"))}function Rs(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:$t()}):t?{kind:"api"}:{kind:"subscription",stored:$t()}}import{existsSync as hn,readFileSync as yn,writeFileSync as wn}from"node:fs";var kn=100;function G(){let e=ye();if(!hn(e))return[];try{let t=JSON.parse(yn(e,"utf8"));return Array.isArray(t)?t:[]}catch{return[]}}function de(e){I(j());let t=[...e].sort((r,n)=>n.updatedAt.localeCompare(r.updatedAt)).slice(0,kn);wn(ye(),JSON.stringify(t,null,2))}function Is(e){let t=G().filter(r=>r.uuid!==e.uuid);t.push(e),de(t)}function Ms(e){return G().find(t=>t.uuid===e)}function _n(e){return G().filter(t=>t.cwd===e).sort((t,r)=>r.updatedAt.localeCompare(t.updatedAt))}function Os(e,t){let r=_n(e),n=t.toLowerCase(),o=r.find(c=>c.name.toLowerCase()===n);if(o)return o;let s=r.filter(c=>c.name.toLowerCase().startsWith(n));if(s.length===1)return s[0];let i=r.filter(c=>c.uuid.startsWith(t));if(i.length===1)return i[0]}function $s(e){let t=G(),r=t.find(n=>n.uuid===e);r&&(r.updatedAt=new Date().toISOString(),de(t))}function Us(e,t){let r=G(),n=r.find(o=>o.uuid===e);return n?(n.name=t,n.renamed=!0,n.updatedAt=new Date().toISOString(),de(r),!0):!1}function Ns(e,t){let r=G(),n=r.find(o=>o.uuid===e);!n||n.renamed||(n.name=t,n.titled=!0,de(r))}function Bs(e,t=6){return e.toLowerCase().replace(/[^a-z0-9\s-]/g,"").split(/\s+/).filter(Boolean).slice(0,t).join("-")||"session"}function Ls(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 bn}from"@anthropic-ai/claude-agent-sdk";var vn=6e4;async function Hs(e,t){try{return await Promise.race([Sn(e,t),xn()])}catch{return null}}async function Sn(e,t){let r=bn({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"?Pn(o.result):null}return null}function xn(){return new Promise(e=>setTimeout(()=>e(null),vn).unref())}function Pn(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 Tn}from"@anthropic-ai/claude-agent-sdk";var Cn=3e4;async function Js(e,t,r){try{return await Promise.race([jn(e,t,r),Rn()])}catch{return null}}async function jn(e,t,r){let n=Tn({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"?Dn(s.result):null}return null}function Rn(){return new Promise(e=>setTimeout(()=>e(null),Cn).unref())}function Dn(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 En=/^---\r?\n[\s\S]*?\r?\n---\r?\n/;function Ut(e){let t=e.match(En);return t?{frontmatter:t[0],body:e.slice(t[0].length)}:{frontmatter:null,body:e}}import{existsSync as An,mkdirSync as In,readFileSync as Mn,writeFileSync as On}from"node:fs";import{dirname as $n,join as Nt}from"node:path";var Bt=/^\[Screenshot: [^\]]+\]\s*$/;function Un(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&&At.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?Rt(r).frontmatter:null,i=o,c=0;if(r){let d=Pn(r);d.size>0&&(i=o.split(`
22
- `).flatMap(l=>{let p=d.get(l.trim());return p&&At.test(l)?(c++,[...p,l]):[l]}).join(`
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&&Bt.test(t[i])&&r.set(t[i].trim(),[...s,...t.slice(n+1,i)])}return r}function Nn(e,t,r){let n=[],o=t.replace(/\r\n/g,`
21
+ `).replace(/^\n+/,""),s=r?Ut(r).frontmatter:null,i=o,c=0;if(r){let u=Un(r);u.size>0&&(i=o.split(`
22
+ `).flatMap(l=>{let p=u.get(l.trim());return p&&Bt.test(l)?(c++,[...p,l]):[l]}).join(`
23
23
  `))}return c>0&&n.push(`${c} 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 Ls(e,t=R){let r=z(e.cwd,e.profileName),n=H(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??B(s),c=await t(s,`/v3/projects/${i}/articles/${o.id}`,{query:{content_mode:"raw"}}),u=Tt(e.cwd,e.relPath),d=kn(u)?vn(u,"utf8"):null,l=c.title??"",{content:p,notes:f}=jn(l,c.content??"",d);return{path:e.relPath,articleId:o.id,title:l,oldContent:d??"",newContent:p,remoteContentHash:W(c.title,c.content),latestVersion:c.latest_version,modifiedAt:c.modified_at,overwritesLocalChanges:d!==null&&o.localHash!==void 0&&q(d)!==o.localHash,notes:f}}function Bs(e,t){let r=z(e.cwd,e.profileName),n=Tt(e.cwd,t.path),o=t.oldContent.includes(`\r
26
+ `),notes:n}}async function ri(e,t=D){let r=W(e.cwd,e.profileName),n=B(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??F(s),c=await t(s,`/v3/projects/${i}/articles/${o.id}`,{query:{content_mode:"raw"}}),d=Nt(e.cwd,e.relPath),u=An(d)?Mn(d,"utf8"):null,l=c.title??"",{content:p,notes:f}=Nn(l,c.content??"",u);return{path:e.relPath,articleId:o.id,title:l,oldContent:u??"",newContent:p,remoteContentHash:z(c.title,c.content),latestVersion:c.latest_version,modifiedAt:c.modified_at,overwritesLocalChanges:u!==null&&o.localHash!==void 0&&L(u)!==o.localHash,notes:f}}function ni(e,t){let r=W(e.cwd,e.profileName),n=Nt(e.cwd,t.path),o=t.oldContent.includes(`\r
27
27
  `)?t.newContent.replace(/\n/g,`\r
28
- `):t.newContent;bn(xn(n),{recursive:!0}),Sn(n,o,"utf8"),se(e.cwd,r.name,t.path,{id:t.articleId,localHash:q(o),remoteContentHash:t.remoteContentHash,remoteVersion:t.latestVersion,remoteModifiedAt:t.modifiedAt})}export{ps as AUTH_MODES,ne as D360ApiError,I as D360AuthError,Ie as EngineSession,Y as ProfileConfigError,cr as apiLogDir,Bs as applyPull,Qt as buildAuthorizationUrl,dt as categoryMapPath,Pr as classify,Zn as clearTokens,yt as computeSyncStatus,as as createSession,ht as createV3RemoteProvider,R as d360Get,$ as d360GetAll,ct as d360Patch,F as d360Post,lt as d360Upload,st as decodeJwtClaims,E as ensureDir,Ye as exchangeCode,_s as findByName,ft as findPathByArticleId,Cs as generateTitle,at as getAccessToken,co as getArticle,ws as getSession,q as hashContent,W as hashRemote,_t as inventoryRepo,ot as isExpired,_r as knownEnvironments,cn as listSessions,ao as listWorkspaces,H as loadProfileMap,V as loadSessions,ve as loadTokens,Pe as logApiCall,xe as logBodiesAlways,re as loggableBody,Vn as loginPkce,wr as normalizeMarkdown,Wt as packageRoot,He as packageSkillsDir,Vt as pickModel,Ls as planPull,me as projectConfigPath,We as projectSkillsDir,Ve as readMcpConfig,N as readProjectConfig,we as readUserConfig,jn as reconstructLocalMarkdown,se as recordSyncBase,lr as redactSecrets,Ze as refreshTokens,xs as relativeTime,bs as renameSession,z as resolveActiveProfile,fs as resolveAuth,ie as resolveConnection,De as resolveCrossLinks,mt as resolveDocsRoot,ho as resolveEnvironment,Ln as resolveModelSetting,ze as resolveProfile,B as resolveProjectId,nt as saveTokens,ge as sessionsFilePath,ko as setProfileProject,vs as setTitle,Ss as slugify,Rt as splitFrontmatter,Ts as suggestNextAction,Xe as toStoredTokens,ks as touchSession,dr as truncateBody,ys as upsertSession,pe as userConfigPath,j as userDir,fe as userMcpConfigPath,Bn as writeMcpConfig,Je as writeProjectConfig,qn as writeUserConfig};
28
+ `):t.newContent;In($n(n),{recursive:!0}),On(n,o,"utf8"),ae(e.cwd,r.name,t.path,{id:t.articleId,localHash:L(o),remoteContentHash:t.remoteContentHash,remoteVersion:t.latestVersion,remoteModifiedAt:t.modifiedAt})}export{js as AUTH_MODES,se as D360ApiError,M as D360AuthError,Ue as EngineSession,Y as ProfileConfigError,yr as apiLogDir,ni as applyPull,ir as buildAuthorizationUrl,mt as categoryMapPath,Mr as classify,uo as clearTokens,bt as computeSyncStatus,Ss as createSession,_t as createV3RemoteProvider,D as d360Get,O as d360GetAll,pt as d360Patch,H as d360Post,ft as d360Upload,lt as decodeJwtClaims,I as ensureDir,et as exchangeCode,Os as findByName,yt as findPathByArticleId,Hs as generateTitle,dt as getAccessToken,bo as getArticle,Ms as getSession,L as hashContent,z as hashRemote,St as inventoryRepo,ct as isExpired,jr as knownEnvironments,_n as listSessions,_o as listWorkspaces,B as loadProfileMap,G as loadSessions,Pe as loadTokens,je as logApiCall,Ce as logBodiesAlways,oe as loggableBody,so as loginPkce,Cr as normalizeMarkdown,Xt as packageRoot,Ge as packageSkillsDir,rr as pickModel,ri as planPull,we as projectConfigPath,Ve as projectSkillsDir,Qe as readMcpConfig,N as readProjectConfig,be as readUserConfig,Nn as reconstructLocalMarkdown,ae as recordSyncBase,wr as redactSecrets,rt as refreshTokens,Ls as relativeTime,Us as renameSession,W as resolveActiveProfile,Rs as resolveAuth,ce as resolveConnection,Ee as resolveCrossLinks,kt as resolveDocsRoot,jo as resolveEnvironment,Zn as resolveModelSetting,Ke as resolveProfile,F as resolveProjectId,at as saveTokens,ye as sessionsFilePath,Ao as setProfileProject,Ns as setTitle,Bs as slugify,Ut as splitFrontmatter,Js as suggestNextAction,nt as toStoredTokens,$s as touchSession,kr as truncateBody,Is as upsertSession,ge as userConfigPath,j as userDir,he as userMcpConfigPath,Xn as writeMcpConfig,Ye as writeProjectConfig,Qn as writeUserConfig};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Model list pricing — the ONLY place rates live. Used by the bulk-cost estimator
3
+ * (cost/estimate.ts) to project spend BEFORE a large run. The post-run actuals come
4
+ * from the SDK's authoritative `total_cost_usd` (engine result event), not from here —
5
+ * so this table only ever drives estimates, never billed figures.
6
+ *
7
+ * Rates are USD per million tokens, from the claude-api reference (cached 2026-06-13).
8
+ * Update here when the reference table moves; nothing else should hardcode a rate.
9
+ */
10
+ export type ModelRate = {
11
+ /** USD per 1M input tokens (full price, uncached). */
12
+ inputPerMTok: number;
13
+ /** USD per 1M output tokens. */
14
+ outputPerMTok: number;
15
+ };
16
+ /** Fallback when the model is unknown or unset (e.g. "Claude Code default"). */
17
+ export declare const DEFAULT_PRICING_MODEL = "claude-opus-4-8";
18
+ /**
19
+ * Cache multipliers on the INPUT rate (documented for the estimator + #3-B reconciliation).
20
+ * A read is ~0.1x; a 5-minute-TTL write is ~1.25x. The estimator prices at full input rate
21
+ * (a cold first run has no cache reads) — these are here so a future refinement can model reuse.
22
+ */
23
+ export declare const CACHE_READ_MULTIPLIER = 0.1;
24
+ export declare const CACHE_WRITE_MULTIPLIER = 1.25;
25
+ /**
26
+ * Resolve a model id (or undefined) to a rate. Matches exact id first, then the longest
27
+ * known id that is a prefix (so `claude-opus-4-8-20260101`-style suffixes still price).
28
+ * Unknown/unset → DEFAULT_PRICING_MODEL with `assumed: true` so callers can say so.
29
+ */
30
+ export declare function resolveRate(model: string | undefined | null): {
31
+ model: string;
32
+ rate: ModelRate;
33
+ assumed: boolean;
34
+ };
35
+ /** Cost in USD for a token count at a given per-MTok rate. */
36
+ export declare function priceTokens(tokens: number, perMTok: number): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document360-engine",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Headless documentation agent engine for document360-writer / -desktop. Emits a typed event stream; no UI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/skills/CLAUDE.md CHANGED
@@ -21,7 +21,8 @@ You are a documentation engineer working from inside the user's source repositor
21
21
 
22
22
  Before an action that writes, publishes, overwrites, or converts MANY articles at once — or any single step that is hard to undo (bulk publish, full-repo re-convert + re-push, mass update, delete) — STOP and get an explicit go-ahead first:
23
23
 
24
- - State the scope concretely before asking: how many articles, which categories, draft vs publish, and the token/cost estimate if you have it.
24
+ - **First call `d360_estimate_cost`** (the `op` you intend, plus the target `paths` — or omit `paths` for all tracked articles). It returns the article count and a token/cost band, deterministically and without spending anything. Use it to ground the scope; don't guess the numbers.
25
+ - State the scope concretely before asking: how many articles, which categories, draft vs publish, and the estimate's cost band (relay it as a band, with the tool's caveat — it's an estimate, not a quote).
25
26
  - Lay out the options as a numbered list (see "Offering next actions") and END YOUR TURN there.
26
27
  - Then wait. Do NOT proceed on a default, an assumption, or silence — **no answer is not a yes.** Act only on the user's next message.
27
28