@modeldesk/cli 0.3.0 → 0.5.0

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.
Files changed (2) hide show
  1. package/dist/modeldesk.js +49 -37
  2. package/package.json +7 -7
package/dist/modeldesk.js CHANGED
@@ -1,43 +1,55 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
- import{chmodSync as xe,existsSync as Ft,mkdirSync as Ht,readFileSync as Ut,writeFileSync as jt}from"node:fs";import{homedir as Bt}from"node:os";import{join as $e}from"node:path";var Ie=448,Pe=384,f=class{dir;file;constructor(t=$e(Bt(),".modeldesk")){this.dir=t,this.file=$e(t,"credentials.json")}getRefreshToken(t){let n=this.read().hosts[t]?.refreshToken;return typeof n=="string"?n:void 0}saveRefreshToken(t,n){let o=this.read();o.hosts[t]={refreshToken:n},this.write(o)}saveRefreshTokenIfPresent(t,n){n&&this.saveRefreshToken(t,n)}clearHost(t){let n=this.read();return n.hosts[t]?(delete n.hosts[t],this.write(n),!0):!1}read(){if(!Ft(this.file))return{hosts:{}};try{let t=JSON.parse(Ut(this.file,"utf8"));if(t&&typeof t=="object"&&t.hosts&&typeof t.hosts=="object")return t}catch{}return{hosts:{}}}write(t){Ht(this.dir,{recursive:!0,mode:Ie}),jt(this.file,`${JSON.stringify(t,null,2)}
4
- `,{mode:Pe}),xe(this.dir,Ie),xe(this.file,Pe)}};var te=e=>{let t=e.split(".");if(t.length===3)try{let n=Buffer.from(t[1],"base64url").toString("utf8"),o=JSON.parse(n);return o&&typeof o=="object"&&!Array.isArray(o)?o:void 0}catch{return}},D=e=>{let t=te(e);if(t)return t.name&&t.email?`${t.name} <${t.email}>`:t.email??t.name??t.sub};import{spawn as zt}from"node:child_process";import{platform as Jt}from"node:process";var Re=e=>{if(!Gt(e))return;let{command:t,args:n,verbatim:o}=qt(e);try{let r=zt(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:o});r.on("error",()=>{}),r.unref()}catch{}},Gt=e=>{try{let{protocol:t}=new URL(e);return t==="http:"||t==="https:"}catch{return!1}},qt=(e,t=Jt)=>{switch(t){case"darwin":return{command:"open",args:[e]};case"win32":return{command:"cmd",args:["/c","start",'""',Wt(e)],verbatim:!0};default:return{command:"xdg-open",args:[e]}}},Wt=e=>e.replace(/[&|<>^()%"]/g,t=>`^${t}`);var R="modeldesk-cli",Ae="modeldesk-federation",_e="openid",ne="/callback";var i=class extends Error{exitCode;constructor(t,n=1){super(t),this.name="CliError",this.exitCode=n}};var w=async e=>{let t=`${e.replace(/\/+$/,"")}/.well-known/openid-configuration`,n;try{n=await fetch(t,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(3e4)})}catch(r){throw new i(`Could not reach the auth server at ${e} (${r.message}).`)}if(!n.ok)throw new i(`OpenID discovery failed at ${t}: ${n.status} ${n.statusText}`);let o=await n.json();if(!o.authorization_endpoint||!o.token_endpoint)throw new i(`OpenID discovery at ${t} did not return the authorization and token endpoints.`);return{authorizationEndpoint:o.authorization_endpoint,tokenEndpoint:o.token_endpoint}};import{createServer as Kt}from"node:http";var Vt=3e5,Me=(e,t=Vt)=>new Promise((n,o)=>{let r,s=new Promise((p,u)=>{r={resolve:p,reject:u}}),a=Kt((p,u)=>{let h=new URL(p.url??"/","http://127.0.0.1");if(h.pathname!==ne){u.writeHead(404,{"Content-Type":"text/plain",Connection:"close"}).end("Not found");return}Yt(h,u,e,r)}),c=setTimeout(()=>r.reject(new i("Timed out waiting for the browser login to complete.")),t);c.unref();let l=!1,d=()=>{l||(l=!0,clearTimeout(c),a.close())};s.then(d,d),a.once("error",p=>o(new i(`Could not start the loopback listener: ${p.message}`))),a.listen(0,"127.0.0.1",()=>{let{port:p}=a.address();n({redirectUri:`http://127.0.0.1:${p}${ne}`,waitForCode:()=>s,close:d})})}),Yt=(e,t,n,o)=>{if(e.searchParams.get("state")!==n){t.writeHead(404,{"Content-Type":"text/plain",Connection:"close"}).end("Unexpected request");return}let r=e.searchParams.get("error");if(r){let a=e.searchParams.get("error_description");oe(t,"Sign-in failed","Sign-in failed. You can close this window and return to the terminal."),o.reject(new i(`Authorization failed: ${a?`${r} (${a})`:r}`));return}let s=e.searchParams.get("code");if(!s){oe(t,"Sign-in failed","No authorization code was returned. You can close this window."),o.reject(new i("Login failed: no authorization code was returned."));return}oe(t,"Signed in","You are signed in to the ModelDesk CLI. You can close this window and return to the terminal."),o.resolve(s)},oe=(e,t,n)=>{let o=`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${t}</title></head><body style="font-family: system-ui, sans-serif; text-align: center; padding: 3rem;"><h1>${t}</h1><p>${n}</p></body></html>`;e.writeHead(200,{"Content-Type":"text/html; charset=utf-8",Connection:"close"}).end(o)};import{createHash as Qt,randomBytes as Le}from"node:crypto";var re=e=>e.toString("base64url"),Oe=()=>{let e=re(Le(32)),t=re(Qt("sha256").update(e).digest());return{verifier:e,challenge:t,method:"S256"}},De=()=>re(Le(16));var b=class extends i{oauthError;errorDescription;constructor(t,n,o){super(t),this.name="TokenEndpointError",this.oauthError=n,this.errorDescription=o}},Ne=e=>e instanceof b&&e.oauthError==="invalid_grant",Fe=(e,t)=>F(e,new URLSearchParams({grant_type:"authorization_code",code:t.code,redirect_uri:t.redirectUri,client_id:R,code_verifier:t.codeVerifier})),N=(e,t)=>F(e,new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:R})),He=(e,t)=>F(e,new URLSearchParams({grant_type:"client_credentials",client_id:t.clientId,client_secret:t.clientSecret})),Ue=(e,t)=>F(e,new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:token-exchange",subject_token:t,subject_token_type:"urn:ietf:params:oauth:token-type:jwt",client_id:Ae})),F=async(e,t)=>{let n;try{n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:t.toString(),signal:AbortSignal.timeout(3e4)})}catch(r){throw new i(`Could not reach the token endpoint (${r.message}).`)}if(!n.ok){let{message:r,code:s,description:a}=await Xt(n);throw new b(`Token request failed: ${r}`,s,a)}let o=await n.json();if(!o.access_token)throw new i("The token response did not include an access token.");return{accessToken:o.access_token,refreshToken:o.refresh_token,expiresInSeconds:o.expires_in??0}},Xt=async e=>{try{if((e.headers.get("content-type")??"").includes("json")){let n=await e.json();if(n.error)return{message:n.error_description?`${n.error} (${n.error_description})`:n.error,code:n.error,description:n.error_description}}}catch{}return{message:`${e.status} ${e.statusText}`.trim()}};var H={info:e=>process.stderr.write(`${e}
5
- `)},U=async(e,t)=>{let{authorizationEndpoint:n,tokenEndpoint:o}=await w(e.issuer),r=Oe(),s=De(),a=await Me(s);try{let c=Zt(n,a.redirectUri,r.challenge,s);t.info(`Opening your browser to sign in to ${e.host}...`),t.info(`If it does not open automatically, visit:
6
- ${c}`),Re(c);let l=await a.waitForCode();return await Fe(o,{code:l,redirectUri:a.redirectUri,codeVerifier:r.verifier})}finally{a.close()}},Zt=(e,t,n,o)=>{let r=new URL(e);return r.searchParams.set("response_type","code"),r.searchParams.set("client_id",R),r.searchParams.set("redirect_uri",t),r.searchParams.set("scope",_e),r.searchParams.set("state",o),r.searchParams.set("code_challenge",n),r.searchParams.set("code_challenge_method","S256"),r.toString()};var se="modeldesk.io",m={host:"MODELDESK_HOST",model:"MODELDESK_MODEL",stack:"MODELDESK_STACK",template:"MODELDESK_TEMPLATE",version:"MODELDESK_VERSION",clientId:"MODELDESK_CLIENT_ID",clientSecret:"MODELDESK_CLIENT_SECRET",apiKey:"MODELDESK_API_KEY"},A=(...e)=>e.find(t=>t!==void 0&&t!==""),ie=(e,t,n)=>({host:A(e.host,t[m.host],n.host)??se,model:A(e.model,t[m.model],n.model),stack:A(e.stack,t[m.stack],n.stack),template:A(e.template,t[m.template],n.template),version:A(e.version,t[m.version],n.version)});var je={requestUrl:"ACTIONS_ID_TOKEN_REQUEST_URL",requestToken:"ACTIONS_ID_TOKEN_REQUEST_TOKEN"},k=e=>e!==void 0&&e!=="",Be=e=>e==="true"||e==="1",en=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,ae="mdsk_",ze=57,C=(e,t)=>({mode:"error",reason:e,message:t}),v="org settings \u2192 Service accounts",_=e=>{if(k(e[m.apiKey]))return C("legacy-api-key",`${m.apiKey} is no longer supported: the CI credential shipped as service accounts, not bearer keys. Use secret mode (set ${m.clientId} and ${m.clientSecret} from ${v}) or, on GitHub Actions, federated auth (grant 'permissions: id-token: write' and add a trust rule). Unset ${m.apiKey}.`);let t=e[m.clientId],n=e[m.clientSecret];if(k(t)!==k(n)){let[s,a]=k(t)?[m.clientId,m.clientSecret]:[m.clientSecret,m.clientId];return C("partial-secret-config",`${s} is set but ${a} is not \u2014 both are required for secret-mode auth. Check the pipeline's secret configuration.`)}if(k(t)&&k(n))return en.test(t)?n.startsWith(ae)?n.length!==ze?C("invalid-client-secret",`${m.clientSecret} has the wrong length: expected ${ze} characters, got ${n.length}. Check for truncation in the pipeline's secret store.`):{mode:"secret",clientId:t,clientSecret:n}:C("invalid-client-secret",`${m.clientSecret} does not start with '${ae}' (an issued secret always does). Check for quoting or truncation in the pipeline's secret store.`):C("invalid-client-id",`${m.clientId} is not a service-account id: expected a UUID, got a ${t.length}-character value. Copy the account id from ${v}.`);let o=e[je.requestUrl],r=e[je.requestToken];return k(o)&&k(r)?{mode:"federated",oidcRequestUrl:o,oidcRequestToken:r}:Be(e.GITHUB_ACTIONS)?C("headless-ci",`Running on GitHub Actions without machine credentials. Grant 'permissions: id-token: write' to use federated auth (with a trust rule in ${v}), or set ${m.clientId} and ${m.clientSecret}. The CLI never opens a browser login in CI.`):Be(e.CI)?C("headless-ci",`Running in CI without machine credentials. Set ${m.clientId} and ${m.clientSecret} (${v}). The CLI never opens a browser login in CI.`):{mode:"interactive"}},T=e=>{let t=_(e);return t.mode==="secret"||t.mode==="federated"?t.mode:void 0},Je=e=>`${e.slice(0,ae.length+8)}\u2026`,Ge=`Check ${m.clientId} and ${m.clientSecret}; issue or rotate a secret in ${v}.`,qe=`The service account may be disabled or deleted \u2014 check ${v}.`,We={issuer:"the auth server does not trust this token issuer \u2014 federated auth currently supports GitHub Actions only.",signature:"the OIDC token's signature could not be verified \u2014 re-run the job; if it persists, the issuer's keys and the auth server disagree.",audience:`the workflow's OIDC audience does not match this host \u2014 check that --host/${m.host} names the ModelDesk instance the trust rule lives on.`,expired:"the OIDC token expired before the exchange \u2014 re-run the job.",repository:"no active trust rule matches this repository. This is also the failure for a pull-request-triggered run unless the rule enables 'Allow pull requests', and for a revoked rule.",binding:"the trust rule pins a branch or environment that does not match this run."},j={unauthorized:`The machine token was rejected. A disabled or deleted service account fails on the next request; a revoked secret within five minutes. Check ${v}.`,forbidden:`The service account's scopes do not permit this \u2014 pulling content needs the 'model:content' scope (${v}).`,notFound:"Check the model id. A machine credential reads SHARED models only (creator-private models are not visible), and the model's metamodel must be entitled under the organization's subscription."};var tn="http://localhost:8080",nn="http://localhost:8082",on="localhost",rn=e=>{let t=e.trim(),n=/:\/\//.test(t)?t:`https://${t}`;try{return new URL(n).hostname}catch{return t.replace(/^[a-z][a-z0-9+.-]*:\/\//i,"").split("/")[0].split(":")[0]}},sn=e=>e==="localhost"||e==="127.0.0.1"||e==="::1"||e==="[::1]",ce=e=>{let t=rn(e);return sn(t)?{host:on,apiBase:tn,issuer:nn}:{host:t,apiBase:`https://api.${t}`,issuer:`https://auth.${t}`}};import{existsSync as an,readFileSync as cn}from"node:fs";import{join as dn}from"node:path";var B=".modeldesk",mn="config.json",z=e=>e.configPath!==void 0,de=e=>{let t=dn(e,B,mn);return an(t)?{config:ln(t),configPath:t,repoRoot:e}:{config:{},repoRoot:e}},ln=e=>{let t;try{t=cn(e,"utf8")}catch(r){throw new i(`Could not read ${e}: ${r.message}`)}let n;try{n=JSON.parse(t)}catch(r){throw new i(`${e} is not valid JSON: ${r.message}`)}if(n===null||typeof n!="object"||Array.isArray(n))throw new i(`${e} must contain a JSON object.`);let o=n;return{host:M(o,"host",e),model:M(o,"model",e),stack:M(o,"stack",e),template:M(o,"template",e),version:M(o,"version",e)}},M=(e,t,n)=>{let o=e[t];if(o!==void 0){if(typeof o!="string")throw new i(`${n}: "${t}" must be a string.`);return o}};import{parseArgs as pn}from"node:util";var S=(e,t)=>{try{return pn({args:e,options:t,allowPositionals:!1,strict:!0})}catch(n){throw new i(n.message)}};var Ke=(e,t,n)=>{let o=n[m.host];return!(t!==void 0&&t!==""||o!==void 0&&o!=="")&&z(e)&&!!e.config.host},Ve=(e,t,n)=>{Ke(e,t,n)&&e.config.host!==se&&process.stderr.write(`note: using host '${e.config.host}' from ${e.configPath} (it drives login and API calls).
7
- `)},Ye=(e,t,n)=>{if(T(n)&&Ke(e,t,n))throw new i(`In a machine-auth run the host must be set explicitly: pass --host or set ${m.host}. The committed ${e.configPath??".modeldesk/config.json"} is not a trusted source for where machine credentials are sent.`)},x=(e,t={})=>{let{values:n}=S(e.argv,{host:{type:"string"}}),o=de(e.cwd),r=ie({host:n.host},e.env,o.config);return t.gateMachineHost&&Ye(o,n.host,e.env),Ve(o,n.host,e.env),ce(r.host)},J=(e,t)=>{let n=de(e.cwd),o=ie(t,e.env,n.config);if(Ye(n,t.host,e.env),Ve(n,t.host,e.env),!o.model)throw new i(`No model specified. Pass --model, set ${m.model}, or add "model" to .modeldesk/config.json.`);return{instance:ce(o.host),model:o.model,stack:o.stack,template:o.template,version:o.version,loaded:n}},me=(e,t,n,o)=>{let r=e?.trim();if(!r)throw new i(`No ${t} specified. Pass ${o}, set ${n}, or add "${t}" to .modeldesk/config.json. Run 'modeldesk bundle templates' to list a model's stacks and templates.`);return r};var Qe=async e=>{let t=T(e.env);t&&process.stderr.write(`warning: machine auth (${t} mode) is active in this environment and takes precedence for API commands; this login only affects interactive use.
8
- `);let n=x(e),o=await U(n,H),r=new f;o.refreshToken?r.saveRefreshToken(n.host,o.refreshToken):r.clearHost(n.host);let s=D(o.accessToken);return process.stdout.write(`Logged in to ${n.host}${s?` as ${s}`:""}.
9
- `),o.refreshToken||process.stderr.write(`warning: the server returned no refresh token, so later commands will re-open the browser each time instead of running unattended.
10
- `),0};var Xe=async e=>{let t=T(e.env);t&&process.stderr.write(`warning: machine auth (${t} mode) is active in this environment and takes precedence for API commands; this logout does not remove machine credentials.
11
- `);let n=x(e),o=new f().clearHost(n.host);return process.stdout.write(o?`Logged out of ${n.host}.
3
+ import{chmodSync as It,existsSync as xr,mkdirSync as br,readFileSync as Tr,writeFileSync as Cr}from"node:fs";import{homedir as vr}from"node:os";import{join as $t}from"node:path";var At=448,Rt=384,A=class{dir;file;constructor(t=$t(vr(),".modeldesk")){this.dir=t,this.file=$t(t,"credentials.json")}getRefreshToken(t){let n=this.read().hosts[t]?.refreshToken;return typeof n=="string"?n:void 0}saveRefreshToken(t,n){let r=this.read();r.hosts[t]={refreshToken:n},this.write(r)}saveRefreshTokenIfPresent(t,n){n&&this.saveRefreshToken(t,n)}clearHost(t){let n=this.read();return n.hosts[t]?(delete n.hosts[t],this.write(n),!0):!1}read(){if(!xr(this.file))return{hosts:{}};try{let t=JSON.parse(Tr(this.file,"utf8"));if(t&&typeof t=="object"&&t.hosts&&typeof t.hosts=="object")return t}catch{}return{hosts:{}}}write(t){br(this.dir,{recursive:!0,mode:At}),Cr(this.file,`${JSON.stringify(t,null,2)}
4
+ `,{mode:Rt}),It(this.dir,At),It(this.file,Rt)}};var De=e=>{let t=e.split(".");if(t.length===3)try{let n=Buffer.from(t[1],"base64url").toString("utf8"),r=JSON.parse(n);return r&&typeof r=="object"&&!Array.isArray(r)?r:void 0}catch{return}},ce=e=>{let t=De(e);if(t)return t.name&&t.email?`${t.name} <${t.email}>`:t.email??t.name??t.sub};import{spawn as Sr}from"node:child_process";import{platform as Ir}from"node:process";var _t=e=>{if(!$r(e))return;let{command:t,args:n,verbatim:r}=Ar(e);try{let o=Sr(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}catch{}},$r=e=>{try{let{protocol:t}=new URL(e);return t==="http:"||t==="https:"}catch{return!1}},Ar=(e,t=Ir)=>{switch(t){case"darwin":return{command:"open",args:[e]};case"win32":return{command:"cmd",args:["/c","start",'""',Rr(e)],verbatim:!0};default:return{command:"xdg-open",args:[e]}}},Rr=e=>e.replace(/[&|<>^()%"]/g,t=>`^${t}`);var q="modeldesk-cli",Pt="modeldesk-federation",Mt="openid",Ne="/callback";var c=class extends Error{exitCode;constructor(t,n=1){super(t),this.name="CliError",this.exitCode=n}};var D=async e=>{let t=`${e.replace(/\/+$/,"")}/.well-known/openid-configuration`,n;try{n=await fetch(t,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(3e4)})}catch(o){throw new c(`Could not reach the auth server at ${e} (${o.message}).`)}if(!n.ok)throw new c(`OpenID discovery failed at ${t}: ${n.status} ${n.statusText}`);let r=await n.json();if(!r.authorization_endpoint||!r.token_endpoint)throw new c(`OpenID discovery at ${t} did not return the authorization and token endpoints.`);return{authorizationEndpoint:r.authorization_endpoint,tokenEndpoint:r.token_endpoint}};import{createServer as _r}from"node:http";var Pr=3e5,Lt=(e,t=Pr)=>new Promise((n,r)=>{let o,s=new Promise((p,l)=>{o={resolve:p,reject:l}}),i=_r((p,l)=>{let f=new URL(p.url??"/","http://127.0.0.1");if(f.pathname!==Ne){l.writeHead(404,{"Content-Type":"text/plain",Connection:"close"}).end("Not found");return}Mr(f,l,e,o)}),a=setTimeout(()=>o.reject(new c("Timed out waiting for the browser login to complete.")),t);a.unref();let d=!1,m=()=>{d||(d=!0,clearTimeout(a),i.close())};s.then(m,m),i.once("error",p=>r(new c(`Could not start the loopback listener: ${p.message}`))),i.listen(0,"127.0.0.1",()=>{let{port:p}=i.address();n({redirectUri:`http://127.0.0.1:${p}${Ne}`,waitForCode:()=>s,close:m})})}),Mr=(e,t,n,r)=>{if(e.searchParams.get("state")!==n){t.writeHead(404,{"Content-Type":"text/plain",Connection:"close"}).end("Unexpected request");return}let o=e.searchParams.get("error");if(o){let i=e.searchParams.get("error_description");Oe(t,"Sign-in failed","Sign-in failed. You can close this window and return to the terminal."),r.reject(new c(`Authorization failed: ${i?`${o} (${i})`:o}`));return}let s=e.searchParams.get("code");if(!s){Oe(t,"Sign-in failed","No authorization code was returned. You can close this window."),r.reject(new c("Login failed: no authorization code was returned."));return}Oe(t,"Signed in","You are signed in to the ModelDesk CLI. You can close this window and return to the terminal."),r.resolve(s)},Oe=(e,t,n)=>{let r=`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${t}</title></head><body style="font-family: system-ui, sans-serif; text-align: center; padding: 3rem;"><h1>${t}</h1><p>${n}</p></body></html>`;e.writeHead(200,{"Content-Type":"text/html; charset=utf-8",Connection:"close"}).end(r)};import{createHash as Lr,randomBytes as Dt}from"node:crypto";var Fe=e=>e.toString("base64url"),Nt=()=>{let e=Fe(Dt(32)),t=Fe(Lr("sha256").update(e).digest());return{verifier:e,challenge:t,method:"S256"}},Ot=()=>Fe(Dt(16));var j=class extends c{oauthError;errorDescription;constructor(t,n,r){super(t),this.name="TokenEndpointError",this.oauthError=n,this.errorDescription=r}},Ft=e=>e instanceof j&&e.oauthError==="invalid_grant",Ht=(e,t)=>me(e,new URLSearchParams({grant_type:"authorization_code",code:t.code,redirect_uri:t.redirectUri,client_id:q,code_verifier:t.codeVerifier})),le=(e,t)=>me(e,new URLSearchParams({grant_type:"refresh_token",refresh_token:t,client_id:q})),Ut=(e,t)=>me(e,new URLSearchParams({grant_type:"client_credentials",client_id:t.clientId,client_secret:t.clientSecret})),Wt=(e,t)=>me(e,new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:token-exchange",subject_token:t,subject_token_type:"urn:ietf:params:oauth:token-type:jwt",client_id:Pt})),me=async(e,t)=>{let n;try{n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:t.toString(),signal:AbortSignal.timeout(3e4)})}catch(o){throw new c(`Could not reach the token endpoint (${o.message}).`)}if(!n.ok){let{message:o,code:s,description:i}=await Dr(n);throw new j(`Token request failed: ${o}`,s,i)}let r=await n.json();if(!r.access_token)throw new c("The token response did not include an access token.");return{accessToken:r.access_token,refreshToken:r.refresh_token,expiresInSeconds:r.expires_in??0}},Dr=async e=>{try{if((e.headers.get("content-type")??"").includes("json")){let n=await e.json();if(n.error)return{message:n.error_description?`${n.error} (${n.error_description})`:n.error,code:n.error,description:n.error_description}}}catch{}return{message:`${e.status} ${e.statusText}`.trim()}};var pe={info:e=>process.stderr.write(`${e}
5
+ `)},fe=async(e,t)=>{let{authorizationEndpoint:n,tokenEndpoint:r}=await D(e.issuer),o=Nt(),s=Ot(),i=await Lt(s);try{let a=Nr(n,i.redirectUri,o.challenge,s);t.info(`Opening your browser to sign in to ${e.host}...`),t.info(`If it does not open automatically, visit:
6
+ ${a}`),_t(a);let d=await i.waitForCode();return await Ht(r,{code:d,redirectUri:i.redirectUri,codeVerifier:o.verifier})}finally{i.close()}},Nr=(e,t,n,r)=>{let o=new URL(e);return o.searchParams.set("response_type","code"),o.searchParams.set("client_id",q),o.searchParams.set("redirect_uri",t),o.searchParams.set("scope",Mt),o.searchParams.set("state",r),o.searchParams.set("code_challenge",n),o.searchParams.set("code_challenge_method","S256"),o.toString()};var He="modeldesk.io",h={host:"MODELDESK_HOST",model:"MODELDESK_MODEL",stack:"MODELDESK_STACK",template:"MODELDESK_TEMPLATE",version:"MODELDESK_VERSION",clientId:"MODELDESK_CLIENT_ID",clientSecret:"MODELDESK_CLIENT_SECRET",apiKey:"MODELDESK_API_KEY"},Z=(...e)=>e.find(t=>t!==void 0&&t!==""),Ue=(e,t,n)=>({host:Z(e.host,t[h.host],n.host)??He,model:Z(e.model,t[h.model],n.model),stack:Z(e.stack,t[h.stack],n.stack),template:Z(e.template,t[h.template],n.template),version:Z(e.version,t[h.version],n.version)});var Bt={requestUrl:"ACTIONS_ID_TOKEN_REQUEST_URL",requestToken:"ACTIONS_ID_TOKEN_REQUEST_TOKEN"},N=e=>e!==void 0&&e!=="",zt=e=>e==="true"||e==="1",Or=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,We="mdsk_",jt=57,W=(e,t)=>({mode:"error",reason:e,message:t}),O="org settings \u2192 Service accounts",Q=e=>{if(N(e[h.apiKey]))return W("legacy-api-key",`${h.apiKey} is no longer supported: the CI credential shipped as service accounts, not bearer keys. Use secret mode (set ${h.clientId} and ${h.clientSecret} from ${O}) or, on GitHub Actions, federated auth (grant 'permissions: id-token: write' and add a trust rule). Unset ${h.apiKey}.`);let t=e[h.clientId],n=e[h.clientSecret];if(N(t)!==N(n)){let[s,i]=N(t)?[h.clientId,h.clientSecret]:[h.clientSecret,h.clientId];return W("partial-secret-config",`${s} is set but ${i} is not \u2014 both are required for secret-mode auth. Check the pipeline's secret configuration.`)}if(N(t)&&N(n))return Or.test(t)?n.startsWith(We)?n.length!==jt?W("invalid-client-secret",`${h.clientSecret} has the wrong length: expected ${jt} characters, got ${n.length}. Check for truncation in the pipeline's secret store.`):{mode:"secret",clientId:t,clientSecret:n}:W("invalid-client-secret",`${h.clientSecret} does not start with '${We}' (an issued secret always does). Check for quoting or truncation in the pipeline's secret store.`):W("invalid-client-id",`${h.clientId} is not a service-account id: expected a UUID, got a ${t.length}-character value. Copy the account id from ${O}.`);let r=e[Bt.requestUrl],o=e[Bt.requestToken];return N(r)&&N(o)?{mode:"federated",oidcRequestUrl:r,oidcRequestToken:o}:zt(e.GITHUB_ACTIONS)?W("headless-ci",`Running on GitHub Actions without machine credentials. Grant 'permissions: id-token: write' to use federated auth (with a trust rule in ${O}), or set ${h.clientId} and ${h.clientSecret}. The CLI never opens a browser login in CI.`):zt(e.CI)?W("headless-ci",`Running in CI without machine credentials. Set ${h.clientId} and ${h.clientSecret} (${O}). The CLI never opens a browser login in CI.`):{mode:"interactive"}},F=e=>{let t=Q(e);return t.mode==="secret"||t.mode==="federated"?t.mode:void 0},Gt=e=>`${e.slice(0,We.length+8)}\u2026`,Vt=`Check ${h.clientId} and ${h.clientSecret}; issue or rotate a secret in ${O}.`,Kt=`The service account may be disabled or deleted \u2014 check ${O}.`,Yt={issuer:"the auth server does not trust this token issuer \u2014 federated auth currently supports GitHub Actions only.",signature:"the OIDC token's signature could not be verified \u2014 re-run the job; if it persists, the issuer's keys and the auth server disagree.",audience:`the workflow's OIDC audience does not match this host \u2014 check that --host/${h.host} names the ModelDesk instance the trust rule lives on.`,expired:"the OIDC token expired before the exchange \u2014 re-run the job.",repository:"no active trust rule matches this repository. This is also the failure for a pull-request-triggered run unless the rule enables 'Allow pull requests', and for a revoked rule.",binding:"the trust rule pins a branch or environment that does not match this run."},ue={unauthorized:`The machine token was rejected. A disabled or deleted service account fails on the next request; a revoked secret within five minutes. Check ${O}.`,forbidden:`The service account's scopes do not permit this \u2014 pulling content needs the 'model:content' scope (${O}).`,notFound:"Check the model id. A machine credential reads SHARED models only (creator-private models are not visible), and the model's metamodel must be entitled under the organization's subscription."};var Fr="http://localhost:8080",Hr="http://localhost:8082",Ur="localhost",Wr=e=>{let t=e.trim(),n=/:\/\//.test(t)?t:`https://${t}`;try{return new URL(n).hostname}catch{return t.replace(/^[a-z][a-z0-9+.-]*:\/\//i,"").split("/")[0].split(":")[0]}},Br=e=>e==="localhost"||e==="127.0.0.1"||e==="::1"||e==="[::1]",Be=e=>{let t=Wr(e);return Br(t)?{host:Ur,apiBase:Fr,issuer:Hr}:{host:t,apiBase:`https://api.${t}`,issuer:`https://auth.${t}`}};import{existsSync as zr,readFileSync as jr}from"node:fs";import{join as Gr}from"node:path";var he=".modeldesk",Vr="config.json",ge=e=>e.configPath!==void 0,ze=e=>{let t=Gr(e,he,Vr);return zr(t)?{config:Kr(t),configPath:t,repoRoot:e}:{config:{},repoRoot:e}},Kr=e=>{let t;try{t=jr(e,"utf8")}catch(o){throw new c(`Could not read ${e}: ${o.message}`)}let n;try{n=JSON.parse(t)}catch(o){throw new c(`${e} is not valid JSON: ${o.message}`)}if(n===null||typeof n!="object"||Array.isArray(n))throw new c(`${e} must contain a JSON object.`);let r=n;return{host:ee(r,"host",e),model:ee(r,"model",e),stack:ee(r,"stack",e),template:ee(r,"template",e),version:ee(r,"version",e)}},ee=(e,t,n)=>{let r=e[t];if(r!==void 0){if(typeof r!="string")throw new c(`${n}: "${t}" must be a string.`);return r}};import{parseArgs as Yr}from"node:util";var G=(e,t)=>{try{return Yr({args:e,options:t,allowPositionals:!1,strict:!0})}catch(n){throw new c(n.message)}};var Xt=(e,t,n)=>{let r=n[h.host];return!(t!==void 0&&t!==""||r!==void 0&&r!=="")&&ge(e)&&!!e.config.host},Jt=(e,t,n)=>{Xt(e,t,n)&&e.config.host!==He&&process.stderr.write(`note: using host '${e.config.host}' from ${e.configPath} (it drives login and API calls).
7
+ `)},qt=(e,t,n)=>{if(F(n)&&Xt(e,t,n))throw new c(`In a machine-auth run the host must be set explicitly: pass --host or set ${h.host}. The committed ${e.configPath??".modeldesk/config.json"} is not a trusted source for where machine credentials are sent.`)},V=(e,t={})=>{let{values:n}=G(e.argv,{host:{type:"string"}}),r=ze(e.cwd),o=Ue({host:n.host},e.env,r.config);return t.gateMachineHost&&qt(r,n.host,e.env),Jt(r,n.host,e.env),Be(o.host)},ye=(e,t)=>{let n=ze(e.cwd),r=Ue(t,e.env,n.config);if(qt(n,t.host,e.env),Jt(n,t.host,e.env),!r.model)throw new c(`No model specified. Pass --model, set ${h.model}, or add "model" to .modeldesk/config.json.`);return{instance:Be(r.host),model:r.model,stack:r.stack,template:r.template,version:r.version,loaded:n}},je=(e,t,n,r)=>{let o=e?.trim();if(!o)throw new c(`No ${t} specified. Pass ${r}, set ${n}, or add "${t}" to .modeldesk/config.json. Run 'modeldesk bundle templates' to list a model's stacks and templates.`);return o};var Zt=async e=>{let t=F(e.env);t&&process.stderr.write(`warning: machine auth (${t} mode) is active in this environment and takes precedence for API commands; this login only affects interactive use.
8
+ `);let n=V(e),r=await fe(n,pe),o=new A;r.refreshToken?o.saveRefreshToken(n.host,r.refreshToken):o.clearHost(n.host);let s=ce(r.accessToken);return process.stdout.write(`Logged in to ${n.host}${s?` as ${s}`:""}.
9
+ `),r.refreshToken||process.stderr.write(`warning: the server returned no refresh token, so later commands will re-open the browser each time instead of running unattended.
10
+ `),0};var Qt=async e=>{let t=F(e.env);t&&process.stderr.write(`warning: machine auth (${t} mode) is active in this environment and takes precedence for API commands; this logout does not remove machine credentials.
11
+ `);let n=V(e),r=new A().clearHost(n.host);return process.stdout.write(r?`Logged out of ${n.host}.
12
12
  `:`No stored credential for ${n.host}.
13
- `),0};var le=6e4,G=class{constructor(t,n={}){this.instance=t;this.store=n.store??new f,this.io=n.io??H,this.now=n.now??Date.now}instance;store;io;now;accessToken;accessTokenExpiresAt=0;endpoints;async getAccessToken(){if(this.accessToken&&this.now()<this.accessTokenExpiresAt-le)return this.accessToken;let t=this.store.getRefreshToken(this.instance.host);if(t){let n=await this.tryRefresh(t);if(n)return n}return this.login()}async tryRefresh(t){let{tokenEndpoint:n}=await this.getEndpoints();try{return this.apply(await N(n,t))}catch(o){if(Ne(o))return;throw o}}async login(){return this.apply(await U(this.instance,this.io))}apply(t){return this.accessToken=t.accessToken,this.accessTokenExpiresAt=this.now()+t.expiresInSeconds*1e3,this.store.saveRefreshTokenIfPresent(this.instance.host,t.refreshToken),t.accessToken}async getEndpoints(){return this.endpoints||(this.endpoints=await w(this.instance.issuer)),this.endpoints}};var pe=class{constructor(t,n,o,r={}){this.instance=t;this.mode=n;this.mint=o;this.now=r.now??Date.now,this.discover=r.discover??w}instance;mode;mint;now;discover;accessToken;accessTokenExpiresAt=0;endpoints;inflight;async getAccessToken(){return this.accessToken&&this.now()<this.accessTokenExpiresAt-le?this.accessToken:(this.inflight||(this.inflight=this.mintFresh().finally(()=>{this.inflight=void 0})),this.inflight)}async mintFresh(){let{tokenEndpoint:t}=await this.getEndpoints(),n;try{n=await this.mint(t)}catch(o){throw un(o,this.mode)}if(n.expiresInSeconds<=0)throw new i("The token response did not include a token lifetime (expires_in), so the machine token cannot be cached. This indicates a server problem.");return this.accessToken=n.accessToken,this.accessTokenExpiresAt=this.now()+n.expiresInSeconds*1e3,n.accessToken}async getEndpoints(){return this.endpoints||(this.endpoints=await this.discover(this.instance.issuer)),this.endpoints}},hn=async(e,t,n)=>{let o;try{o=await fetch(`${e}&audience=${encodeURIComponent(n)}`,{headers:{Authorization:`bearer ${t}`,Accept:"application/json"},signal:AbortSignal.timeout(3e4)})}catch(s){throw new i(`Could not reach the GitHub OIDC token endpoint (${s.message}).`)}if(!o.ok)throw new i(`The GitHub OIDC token request failed (${o.status} ${o.statusText}). Check that the workflow grants 'permissions: id-token: write'.`);let r=await o.json().catch(()=>{});if(!r?.value)throw new i("The GitHub OIDC token response did not include a token value. Check that the workflow grants 'permissions: id-token: write'.");return r.value},q=(e,t,n={})=>{let o=t.mode==="secret"?r=>He(r,{clientId:t.clientId,clientSecret:t.clientSecret}):async r=>Ue(r,await hn(t.oidcRequestUrl,t.oidcRequestToken,e.issuer));return new pe(e,t.mode,o,n)},un=(e,t)=>{if(!(e instanceof b)||!e.oauthError)return e;if(t==="secret")return e.oauthError==="invalid_client"?new i(`${e.message} ${Ge}`):e;if(e.errorDescription===void 0)return e.oauthError==="invalid_grant"?new i(`${e.message} ${qe}`):e;let n=We[e.errorDescription];return n?new i(`${e.message} \u2014 ${n}`):e};var g=e=>{process.stdout.write(`${e}
14
- `)},Ze=async e=>{let t=_(e.env);if(t.mode==="error")throw new i(t.message);let n=t.mode==="secret"||t.mode==="federated",o=x(e,{gateMachineHost:n});return g(`Host: ${o.host}`),g(`API: ${o.apiBase}`),t.mode==="secret"||t.mode==="federated"?fn(o,t,e.env):gn(o)},fn=async(e,t,n)=>{t.mode==="secret"?g(`Auth: service account (secret mode), client ${t.clientId}, secret ${Je(t.clientSecret)}`):g(`Auth: federated (GitHub Actions), repository ${n.GITHUB_REPOSITORY??"unknown"}, ref ${n.GITHUB_REF??"unknown"}`);let o=await q(e,t).getAccessToken(),r=te(o)??{};g(`Status: credential valid. Account ${r.sub??"unknown"}, organization ${r.mdsk_org??"unknown"}.`);let s=typeof r.scope=="string"?r.scope.trim():"";return g(s?`Scopes: ${s}`:"Scopes: enforced server-side from the account's current scopes."),0},gn=async e=>{let t=new f,n=t.getRefreshToken(e.host);if(!n)return g("Status: not logged in. Run 'modeldesk auth login'."),1;try{let{tokenEndpoint:o}=await w(e.issuer),r=await N(o,n);t.saveRefreshTokenIfPresent(e.host,r.refreshToken);let s=D(r.accessToken);return g(`Status: logged in${s?` as ${s}`:""} (credential valid).`),0}catch{return g("Status: the stored credential could not be verified (it may be expired or revoked, or the server is unreachable). Run 'modeldesk auth login'."),1}};import{existsSync as he,lstatSync as et,realpathSync as yn}from"node:fs";import{homedir as wn}from"node:os";import{basename as kn,dirname as vn,isAbsolute as W,join as tt,relative as ue,resolve as $}from"node:path";var V=(e,t,n)=>{if(W(n))throw new i(`Refusing an absolute path from the bundle manifest: ${n}`);let o=$(e,t),r=$(e,n),s=ue(o,r);if(s===""||s.startsWith("..")||W(s))throw new i(`Refusing a bundle path that escapes the content root: ${n}`);return r},Tn=e=>{try{return et(e)}catch{return}},K=e=>{let t=[],n=e;for(;!he(n);){let r=vn(n);if(r===n)return e;t.unshift(kn(n)),n=r}let o=yn(n);return t.length>0?tt(o,...t):o},fe=e=>{if(Tn(e)?.isSymbolicLink())throw new i(`Refusing to write through an existing symlink at a managed path: ${e}`)},Y=(e,t)=>{let n=K(e),o=K(t),r=ue(n,o);if(r!==""&&(r.startsWith("..")||W(r)))throw new i(`Refusing a path that escapes the content root through a symlink: ${t}`)},ge=(e,t)=>{let n=$(e,t);if(n===$(e))throw new i(`The content root resolves to the repository root (${n}); refusing to sync it.`);if(n===$(wn()))throw new i("The content root resolves to your home directory; refusing to sync it.");if(he(n)){if(et(n).isSymbolicLink())throw new i(`The content root ${n} is a symlink; refusing to sync it.`);if(he(tt(n,".git")))throw new i(`The content root ${n} contains a .git directory (it looks like a repo root); refusing.`)}let o=K($(e)),r=ue(o,K(n));if(r!==""&&(r.startsWith("..")||W(r)))throw new i(`The content root ${n} resolves outside the repository via a symlinked ancestor; refusing.`);return n};var Cn={respec:"data/model"},nt=e=>{let t=Cn[e];if(!t)throw new i(`The CLI does not know the content root for stack '${e}'.`);return t};var En=1500,bn=6e5,Sn=e=>new Promise(t=>setTimeout(t,e)),xn=e=>e==="COMPLETED"||e==="COMPLETED_WITH_ERRORS"||e==="FAILED",ot=async(e,t,n)=>{let o=await e.postForJson(`/models/${t}/content-bundle/diagrams`,{template:n.template,version:n.version});if(!o.commit)throw new i("The diagram render did not return a resolved commit; cannot pin the pull.");return o},rt=async(e,t,n,o,r,s={})=>{let a=s.sleep??Sn,c=s.now??Date.now,l=c()+bn;for(o.update(0,r);;){let d=await e.getJson(`/models/${t}/export-jobs/${n}`);if(d.progress&&o.update(d.progress.completed,d.progress.total,d.progress.currentItem),xn(d.status))return o.done(),d;if(c()>=l)throw new i("Timed out waiting for the diagram render to finish.");await a(En)}},st=(e,t,n,o)=>e.getBytes(`/models/${t}/content-bundle/media`,{path:n,template:o.template,commit:o.commit});var it=e=>{let t=new Map;for(let n of e.fragments??[])n.realization?.path&&t.set(n.elementId,n.realization.path);return t},at=(e,t)=>{let n=it(e),o=[];for(let[r,s]of it(t)){let a=n.get(r);a&&a!==s&&o.push({elementId:r,oldPath:a,newPath:s})}return o};var ye=e=>{let t=e.payloads??{},n=[];for(let o of e.manifest.fragments??[]){let r=o.realization?.path;if(!r)continue;let s=t[o.address];s!==void 0&&n.push({path:r,content:s})}return n},we=e=>{let t=[];for(let n of e.fragments??[])for(let o of n.realization?.media??[])t.push(o);return[...new Set(t)]};var ct=e=>`${JSON.stringify(e,null,2)}
15
- `;import{existsSync as $n,mkdirSync as In,readFileSync as Pn,writeFileSync as Rn}from"node:fs";import{join as dt}from"node:path";var mt=(e,t)=>dt(e,B,`${t}.manifest.json`),lt=(e,t)=>{let n=mt(e,t);if(!$n(n))return;let o;try{o=JSON.parse(Pn(n,"utf8"))}catch{return}return An(o)?o:void 0},An=e=>{if(e===null||typeof e!="object"||Array.isArray(e))return!1;let{fragments:t}=e;return t===void 0?!0:Array.isArray(t)&&t.every(n=>n!==null&&typeof n=="object")},pt=(e,t,n)=>{In(dt(e,B),{recursive:!0}),Rn(mt(e,t),ct(n))},ht=e=>{let t=[];for(let n of e.fragments??[]){n.realization?.path&&t.push(n.realization.path);for(let o of n.realization?.media??[])t.push(o)}return t};import{existsSync as ut,mkdirSync as ft,readdirSync as _n,rmSync as Mn,writeFileSync as gt}from"node:fs";import{dirname as yt,join as L,relative as Ln,sep as On}from"node:path";var wt=(e,t)=>{let n=L(e,t);if(!ut(n))return[];let o=[],r=s=>{for(let a of _n(s,{withFileTypes:!0})){let c=L(s,a.name);a.isDirectory()?r(c):a.isFile()&&o.push(Ln(e,c).split(On).join("/"))}};return r(n),o},kt=(e,t,n)=>{let o=L(e,t);for(let r of n){let s=V(e,t,r.path);fe(s),Y(o,s),ft(yt(s),{recursive:!0}),gt(s,r.content)}},vt=(e,t,n,o)=>{let r=L(e,t),s=V(e,t,n);fe(s),Y(r,s),ft(yt(s),{recursive:!0}),gt(s,o)},Tt=(e,t,n)=>{let o=L(e,t);for(let r of n){let s=V(e,t,r);Y(o,s),ut(s)&&Mn(s)}};var Dn=100,Et=e=>{if(e.newTextPaths.length===0&&!e.allowEmpty)throw new i("The content fetch returned no fragments (usually the wrong model or format, or a server error). Nothing was written or deleted. Re-run with --allow-empty only if the model is genuinely empty.");let t=Ct([...e.newTextPaths,...e.newMediaPaths]).sort(),n=new Set(t),o=new Set(e.existingFiles),r=t.filter(p=>o.has(p)),s=e.priorManifestPaths!==void 0?"manifest-tracked":"full-sync",a=e.priorManifestPaths??e.existingFiles,c=Ct(a).filter(p=>!n.has(p)).sort(),l=c.length>0&&!e.adopted,d=c.length>0&&(c.length>t.length||c.length>Dn);return{writes:t,overwrites:r,orphanCandidates:c,mode:s,adoptionNeeded:l,outsized:d}},ke=(e,t)=>{if(e.orphanCandidates.length===0)return{deletes:[],warnings:[]};if(e.adoptionNeeded&&!t.adopt)return{deletes:[],warnings:[`${e.orphanCandidates.length} file(s) under the content root are not tracked by the CLI and were left in place. Pass --adopt to let the CLI manage (and prune) this directory.`]};if(e.outsized&&!t.yes)throw new i(`This pull would remove ${e.orphanCandidates.length} file(s), more than the ${e.writes.length} it writes. That often signals a wrong model or format. Nothing was changed. Re-run with --yes if intended.`);return{deletes:e.orphanCandidates,warnings:[]}},Ct=e=>[...new Set(e)];var bt=(e,t)=>{let n=_(t);switch(n.mode){case"error":throw new i(n.message);case"secret":case"federated":return q(e,n);case"interactive":return new G(e)}};var Nn=6e4,St={unauthorized:"Run 'modeldesk auth login' and try again.",forbidden:"Your account may lack access to this model."},Fn={unauthorized:j.unauthorized,forbidden:j.forbidden,notFound:j.notFound},Q=(e,t)=>new ve(e.apiBase,bt(e,t),T(t)?Fn:St),ve=class{constructor(t,n,o=St){this.apiBase=t;this.tokens=n;this.hints=o}apiBase;tokens;hints;async getJson(t,n){return await(await this.request(t,{method:"GET"},n)).json()}async postForJson(t,n){return await(await this.request(t,{method:"POST"},n)).json()}async getBytes(t,n){let o=await this.request(t,{method:"GET",headers:{Accept:"image/png"}},n);return new Uint8Array(await o.arrayBuffer())}async request(t,n,o){let r=await this.tokens.getAccessToken(),s=Hn(this.apiBase,t,o),a;try{a=await fetch(s,{...n,headers:{Authorization:`Bearer ${r}`,Accept:"application/json",...n.headers??{}},signal:AbortSignal.timeout(Nn)})}catch(c){throw new i(`Could not reach ${this.apiBase} (${c.message}). Check the host and that the ModelDesk server is reachable.`)}if(!a.ok)throw await Un(a,t,this.hints);return a}},Hn=(e,t,n)=>{let o=new URL(t,e);if(n)for(let[r,s]of Object.entries(n))s!==void 0&&o.searchParams.set(r,s);return o.toString()},Un=async(e,t,n)=>{let o=`${e.status} ${e.statusText}`.trim();try{if((e.headers.get("content-type")??"").includes("json")){let s=await e.json(),a=s.detail??s.title;a&&(o=`${e.status} ${a}`)}}catch{}return e.status===401?new i(`Authentication failed (${o}). ${n.unauthorized}`):e.status===403?new i(`Not authorized (${o}). ${n.forbidden}`):e.status===404&&n.notFound?new i(`Not found (${o}). ${n.notFound}`):new i(`Request to ${t} failed: ${o}`)};var jn=(e,t)=>{let n=t>0?Math.min(1,e/t):0,o=Math.round(n*24);return`[${"#".repeat(o)}${"-".repeat(24-o)}]`},xt=(e,t=n=>{process.stderr.write(n)})=>{let n=-1;return{update(o,r,s){if(e){let a=s?` ${s}`:"";t(`\r${jn(o,r)} ${o}/${r}${a}\x1B[K`)}else if(o!==n){let a=s?` (${s})`:"";t(`Rendering diagrams: ${o}/${r}${a}
16
- `),n=o}},done(){e&&t(`
17
- `)}}};import{createInterface as Bn}from"node:readline";var X=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),Te=async e=>{if(!X())return!1;let t=Bn({input:process.stdin,output:process.stderr});try{let n=await new Promise(o=>t.question(`${e} [y/N] `,o));return/^y(es)?$/i.test(n.trim())}finally{t.close()}};var y=e=>{process.stdout.write(e)},E=e=>{process.stderr.write(e)},$t=e=>{for(let t of e)E(` ${t}
18
- `)},Pt=async e=>{let{values:t}=S(e.argv,{host:{type:"string"},model:{type:"string"},stack:{type:"string"},template:{type:"string"},version:{type:"string"},"no-diagrams":{type:"boolean"},"dry-run":{type:"boolean"},"allow-empty":{type:"boolean"},adopt:{type:"boolean"},yes:{type:"boolean"}}),n=J(e,{host:t.host,model:t.model,stack:t.stack,template:t.template,version:t.version}),o={noDiagrams:!!t["no-diagrams"],dryRun:!!t["dry-run"],allowEmpty:!!t["allow-empty"],adopt:!!t.adopt,yes:!!t.yes},r=Q(n.instance,e.env),s=me(n.stack,"stack",m.stack,"--stack"),a=me(n.template,"template",m.template,"--template"),l=!o.noDiagrams&&!o.dryRun?await ot(r,n.model,{template:a,version:n.version}):void 0,d=l?.commit,p=await r.getJson(`/models/${n.model}/content-bundle`,{template:a,commit:d,version:d?void 0:n.version});if(p.manifest.stack!==s)throw new i(`Requested stack '${s}', but template '${a}' renders under stack '${p.manifest.stack}'. Run 'modeldesk bundle templates' to see valid stack/template pairs.`);let u=nt(s);ge(n.loaded.repoRoot,u);let h=ye(p),I=we(p.manifest),P=lt(n.loaded.repoRoot,s),ee=Et({newTextPaths:h.map(Nt=>Nt.path),newMediaPaths:I,priorManifestPaths:P?ht(P):void 0,existingFiles:wt(n.loaded.repoRoot,u),adopted:P!==void 0||z(n.loaded),allowEmpty:o.allowEmpty}),Ee=P?at(P,p.manifest):[],be={stack:s,version:p.manifest.stackVersion};if(o.dryRun)return It(n,a,be,d,ee,void 0,Ee,I.length,!0),0;let Se=await zn(ee,o);return It(n,a,be,d,ee,Se,Ee,I.length,!1),ge(n.loaded.repoRoot,u),kt(n.loaded.repoRoot,u,h),Tt(n.loaded.repoRoot,u,Se.deletes),pt(n.loaded.repoRoot,s,p.manifest),I.length===0||!l?0:Jn(r,n,a,d,l,I,u)},zn=async(e,t)=>{let{adopt:n,yes:o}=t;if(e.orphanCandidates.length===0)return ke(e,{adopt:n,yes:o});let r=X();return e.adoptionNeeded&&!n&&(E(`The content root has ${e.orphanCandidates.length} file(s) the CLI does not track:
19
- `),$t(e.orphanCandidates),r&&(n=await Te("Adopt this directory and allow the CLI to remove these files?"))),e.outsized&&!o&&(n||!e.adoptionNeeded)&&(E(`This pull would remove ${e.orphanCandidates.length} file(s), more than the ${e.writes.length} it writes:
20
- `),$t(e.orphanCandidates),r&&(o=await Te("Proceed with removing them?"))),ke(e,{adopt:n,yes:o})},Jn=async(e,t,n,o,r,s,a)=>{let c=xt(X()),l=await rt(e,t.model,r.id,c,s.length);if(l.status==="FAILED")return E(`warning: the diagram render failed. Text fragments are written; media was not fetched. Re-run to retry.
21
- `),1;let d=0;for(let p of s)try{let u=await st(e,t.model,p,{template:n,commit:o});vt(t.loaded.repoRoot,a,p,u)}catch(u){d+=1,E(`warning: could not fetch media ${p}: ${u instanceof Error?u.message:String(u)}
22
- `)}return l.status==="COMPLETED_WITH_ERRORS"||d>0?(E(`warning: ${d} media file(s) could not be written; the pull is re-runnable.
23
- `),1):0},It=(e,t,n,o,r,s,a,c,l)=>{let d=o?`commit ${o}`:e.version?`version ${e.version}`:"HEAD",p=`${n.stack} ${n.version}`;y(`${l?"Dry run: ":""}Pulling ${e.model} (stack ${p}, template ${t}) at ${d} into ${e.instance.host}.
24
- `),y(` Write: ${r.writes.length} file(s)${r.overwrites.length?` (${r.overwrites.length} overwritten)`:""}
25
- `),y(` Diagrams: ${c}
26
- `);let u=l?r.orphanCandidates:s?.deletes??[];y(` Remove: ${u.length} file(s) [${r.mode}]
27
- `);for(let h of u)y(` - ${h}
28
- `);if(l)r.adoptionNeeded&&y(` note: removing these needs --adopt (the root is not CLI-managed yet).
29
- `),r.outsized&&y(` note: this removal is larger than the write set and needs --yes.
30
- `);else for(let h of s?.warnings??[])E(`warning: ${h}
31
- `);for(let h of a)y(` moved: ${h.oldPath} -> ${h.newPath}
32
- `);if(l)for(let h of r.writes)y(` write ${h}
33
- `)};var Z=e=>{let t=[];for(let n of e)n.forEach((o,r)=>{t[r]=Math.max(t[r]??0,o.length)});return t},O=(e,{indent:t="",gap:n=2,widths:o}={})=>{let s=Z(e).map((c,l)=>o?.[l]??c),a=" ".repeat(n);return e.map(c=>t+c.map((l,d)=>d===c.length-1?l:l.padEnd(s[d])).join(a))};var Gn=["STACK","TEMPLATE","NAME"],Rt=async e=>{let{values:t}=S(e.argv,{host:{type:"string"},model:{type:"string"},stack:{type:"string"}}),n=J(e,{host:t.host,model:t.model}),o=Q(n.instance,e.env),r=await o.getJson(`/models/${n.model}`),s=(r.metamodelExtensions??[]).map(h=>h.id),a=s.length>0?{metamodelExtensions:s.join(",")}:void 0,{items:c}=await o.getJson(`/metamodels/${r.metamodel.id}/templates`,a),l=c??[];if(l.length===0)return process.stdout.write(`This model has no document templates.
34
- `),0;let d=t.stack,p=d?l.filter(h=>h.stack===d):l;if(p.length===0)return process.stdout.write(`No document templates for stack '${d}'.
35
- `),0;let u=p.map(h=>[h.stack??"",h.key??h.id,h.name]);for(let h of O([Gn,...u]))process.stdout.write(`${h}
36
- `);return 0};var Ce={bundle:{pull:{summary:"Materialize a model's content bundle into a local folder",usage:"bundle pull --model <id> --stack <id> --template <slug> [--host <host>] [--version <name>] [--no-diagrams] [--dry-run]",handler:Pt},templates:{summary:"List a model's document templates",usage:"bundle templates --model <id> [--stack <id>] [--host <host>]",handler:Rt}},auth:{login:{summary:"Log in through the browser (OAuth authorization code + PKCE)",usage:"auth login [--host <host>]",handler:Qe},logout:{summary:"Remove the stored credential for the active host",usage:"auth logout [--host <host>]",handler:Xe},status:{summary:"Show the active host, logged-in identity, and token validity",usage:"auth status [--host <host>]",handler:Ze}}};var At={name:"@modeldesk/cli",version:"0.3.0",description:"Command-line interface (CLI) for the ModelDesk platform",keywords:["modeldesk","cli","documentation","content-bundle","respec","mim","information-model"],homepage:"https://modeldesk.io",repository:{type:"git",url:"git+https://github.com/model-desk/modeldesk.git",directory:"packages/cli"},license:"MIT",type:"module",bin:{modeldesk:"dist/modeldesk.js"},files:["dist"],publishConfig:{access:"public"},engines:{node:">=22"},scripts:{build:"node esbuild.config.mjs",prepublishOnly:"npm run build",typecheck:"tsc --noEmit",lint:"eslint","lint:fix":"eslint --fix",test:"vitest run","test:watch":"vitest"},devDependencies:{"@eslint/js":"^10.0.1","@modeldesk/content-bundle":"*","@types/node":"^24.13.2",esbuild:"^0.28.2",eslint:"^10.9.1","eslint-config-prettier":"^10.1.5","eslint-plugin-prettier":"^5.5.6",globals:"^17.11.0",prettier:"^3.9.5",typescript:"^6.0.3","typescript-eslint":"^8.69.0",vitest:"^5.0.0"}};var _t=At.version;var Wn={out:e=>process.stdout.write(e),err:e=>process.stderr.write(e)},Lt=new Set(["--help","-h","help"]),Dt=async(e,t=Wn)=>{try{return await Kn(e,t)}catch(n){return n instanceof i?(t.err(`error: ${n.message}
13
+ `),0};var Ge=6e4,we=class{constructor(t,n={}){this.instance=t;this.store=n.store??new A,this.io=n.io??pe,this.now=n.now??Date.now}instance;store;io;now;accessToken;accessTokenExpiresAt=0;endpoints;async getAccessToken(){if(this.accessToken&&this.now()<this.accessTokenExpiresAt-Ge)return this.accessToken;let t=this.store.getRefreshToken(this.instance.host);if(t){let n=await this.tryRefresh(t);if(n)return n}return this.login()}async tryRefresh(t){let{tokenEndpoint:n}=await this.getEndpoints();try{return this.apply(await le(n,t))}catch(r){if(Ft(r))return;throw r}}async login(){return this.apply(await fe(this.instance,this.io))}apply(t){return this.accessToken=t.accessToken,this.accessTokenExpiresAt=this.now()+t.expiresInSeconds*1e3,this.store.saveRefreshTokenIfPresent(this.instance.host,t.refreshToken),t.accessToken}async getEndpoints(){return this.endpoints||(this.endpoints=await D(this.instance.issuer)),this.endpoints}};var Ve=class{constructor(t,n,r,o={}){this.instance=t;this.mode=n;this.mint=r;this.now=o.now??Date.now,this.discover=o.discover??D}instance;mode;mint;now;discover;accessToken;accessTokenExpiresAt=0;endpoints;inflight;async getAccessToken(){return this.accessToken&&this.now()<this.accessTokenExpiresAt-Ge?this.accessToken:(this.inflight||(this.inflight=this.mintFresh().finally(()=>{this.inflight=void 0})),this.inflight)}async mintFresh(){let{tokenEndpoint:t}=await this.getEndpoints(),n;try{n=await this.mint(t)}catch(r){throw Jr(r,this.mode)}if(n.expiresInSeconds<=0)throw new c("The token response did not include a token lifetime (expires_in), so the machine token cannot be cached. This indicates a server problem.");return this.accessToken=n.accessToken,this.accessTokenExpiresAt=this.now()+n.expiresInSeconds*1e3,n.accessToken}async getEndpoints(){return this.endpoints||(this.endpoints=await this.discover(this.instance.issuer)),this.endpoints}},Xr=async(e,t,n)=>{let r;try{r=await fetch(`${e}&audience=${encodeURIComponent(n)}`,{headers:{Authorization:`bearer ${t}`,Accept:"application/json"},signal:AbortSignal.timeout(3e4)})}catch(s){throw new c(`Could not reach the GitHub OIDC token endpoint (${s.message}).`)}if(!r.ok)throw new c(`The GitHub OIDC token request failed (${r.status} ${r.statusText}). Check that the workflow grants 'permissions: id-token: write'.`);let o=await r.json().catch(()=>{});if(!o?.value)throw new c("The GitHub OIDC token response did not include a token value. Check that the workflow grants 'permissions: id-token: write'.");return o.value},ke=(e,t,n={})=>{let r=t.mode==="secret"?o=>Ut(o,{clientId:t.clientId,clientSecret:t.clientSecret}):async o=>Wt(o,await Xr(t.oidcRequestUrl,t.oidcRequestToken,e.issuer));return new Ve(e,t.mode,r,n)},Jr=(e,t)=>{if(!(e instanceof j)||!e.oauthError)return e;if(t==="secret")return e.oauthError==="invalid_client"?new c(`${e.message} ${Vt}`):e;if(e.errorDescription===void 0)return e.oauthError==="invalid_grant"?new c(`${e.message} ${Kt}`):e;let n=Yt[e.errorDescription];return n?new c(`${e.message} \u2014 ${n}`):e};var _=e=>{process.stdout.write(`${e}
14
+ `)},en=async e=>{let t=Q(e.env);if(t.mode==="error")throw new c(t.message);let n=t.mode==="secret"||t.mode==="federated",r=V(e,{gateMachineHost:n});return _(`Host: ${r.host}`),_(`API: ${r.apiBase}`),t.mode==="secret"||t.mode==="federated"?qr(r,t,e.env):Zr(r)},qr=async(e,t,n)=>{t.mode==="secret"?_(`Auth: service account (secret mode), client ${t.clientId}, secret ${Gt(t.clientSecret)}`):_(`Auth: federated (GitHub Actions), repository ${n.GITHUB_REPOSITORY??"unknown"}, ref ${n.GITHUB_REF??"unknown"}`);let r=await ke(e,t).getAccessToken(),o=De(r)??{};_(`Status: credential valid. Account ${o.sub??"unknown"}, organization ${o.mdsk_org??"unknown"}.`);let s=typeof o.scope=="string"?o.scope.trim():"";return _(s?`Scopes: ${s}`:"Scopes: enforced server-side from the account's current scopes."),0},Zr=async e=>{let t=new A,n=t.getRefreshToken(e.host);if(!n)return _("Status: not logged in. Run 'modeldesk auth login'."),1;try{let{tokenEndpoint:r}=await D(e.issuer),o=await le(r,n);t.saveRefreshTokenIfPresent(e.host,o.refreshToken);let s=ce(o.accessToken);return _(`Status: logged in${s?` as ${s}`:""} (credential valid).`),0}catch{return _("Status: the stored credential could not be verified (it may be expired or revoked, or the server is unreachable). Run 'modeldesk auth login'."),1}};var Ke=e=>`${JSON.stringify(e,null,2)}
15
+ `;var Qr=/[ \t\n\r\f]+/g,eo=/^[ \t\n\r\f]+|[ \t\n\r\f]+$/g,to=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,te=e=>e.replace(to,"");var no=(e,t)=>{if(!Number.isFinite(e))throw new Error(`Cannot write ${e} as an SVG number`);let n=10**t,r=Math.sign(e)*Math.round(Math.abs(e)*n)/n;return String(r===0?0:r)},Ye=e=>no(e,2);var ro=e=>e.replace(Qr," ").replace(eo,""),Xe=e=>ro(te(e)).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;");var ne="http://www.w3.org/2000/svg",rn="Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",Je="group",qe="spacingAndGlyphs",Ze="italic",Qe="underline",K="none";var Ee="hidden",xe="stroke",et="auto";var oo="start",be="clip",so=/^[A-Za-z0-9_-]+$/,io=/[^A-Za-z0-9_-]+/g,tt=e=>so.test(e),on=e=>e.replace(io,"-");var nt=(e,t)=>t?`${e}-${oo}`:e,tn=String.raw`-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?`,nn=String.raw`[a-zA-Z]+\(\s*(?:${tn}(?:[\s,]+${tn})*)?\s*\)`,ao=new RegExp(String.raw`^${nn}(?:\s+${nn})*$`),sn=e=>ao.test(e);var co=/[\u0000- \u007F]/g,lo=/^([a-zA-Z][a-zA-Z0-9+.-]*):/,mo=new Set(["http","https","mailto"]),Te=e=>{let t=te(e).replace(co,"");if(t==="")return!1;let n=lo.exec(t);return n===null||mo.has(n[1].toLowerCase())};var g="#4b5563";var S="white";var rt="strokeWidth",Ce=[{id:"triangle-filled",markerWidth:20,markerHeight:20,refX:20,refY:10,shapes:[{element:"line",x1:16,y1:10,x2:20,y2:10,stroke:"white",strokeWidth:2},{element:"path",d:"M 6 4 L 19 10 L 6 16 L 10 10 Z",fill:g,stroke:g,strokeWidth:1}]},{id:"triangle-hollow",markerWidth:24,markerHeight:24,refX:24,refY:12,shapes:[{element:"line",x1:20,y1:12,x2:24,y2:12,stroke:"white",strokeWidth:2},{element:"path",d:"M 6 2 L 22 12 L 6 22 Z",fill:S,stroke:g,strokeWidth:2}]},{id:"diamond-filled",markerWidth:20,markerHeight:20,refX:20,refY:10,shapes:[{element:"path",d:"M 10 5 L 19 10 L 10 15 L 1 10 Z",fill:g,stroke:g,strokeWidth:1.5}]},{id:"diamond-hollow",markerWidth:20,markerHeight:20,refX:20,refY:10,shapes:[{element:"path",d:"M 10 5 L 19 10 L 10 15 L 1 10 Z",fill:S,stroke:g,strokeWidth:1.5}]},{id:"arrow-open",markerWidth:20,markerHeight:20,refX:20,refY:10,shapes:[{element:"path",d:"M 6 4 L 19 10 L 6 16",fill:"none",stroke:g,strokeWidth:2}]},{id:"bar",markerWidth:18,markerHeight:12,refX:18,refY:6,shapes:[{element:"line",x1:13,y1:1,x2:13,y2:11,stroke:g,strokeWidth:1.5}]},{id:"circle",markerWidth:18,markerHeight:12,refX:18,refY:6,shapes:[{element:"circle",cx:13.5,cy:6,r:2.75,fill:S,stroke:g,strokeWidth:1.5}]},{id:"crowsfoot",markerWidth:18,markerHeight:12,refX:18,refY:6,shapes:[{element:"path",d:"M 18 1 L 8 6 L 18 11 M 8 6 L 18 6",fill:"none",stroke:g,strokeWidth:1.5}]},{id:"crowsfoot-bar",markerWidth:18,markerHeight:12,refX:18,refY:6,shapes:[{element:"path",d:"M 18 1 L 8 6 L 18 11 M 8 6 L 18 6",fill:"none",stroke:g,strokeWidth:1.5},{element:"line",x1:4,y1:1,x2:4,y2:11,stroke:g,strokeWidth:1.5}]},{id:"crowsfoot-circle",markerWidth:18,markerHeight:12,refX:18,refY:6,shapes:[{element:"path",d:"M 18 1 L 8 6 L 18 11 M 8 6 L 18 6",fill:"none",stroke:g,strokeWidth:1.5},{element:"circle",cx:4,cy:6,r:2.75,fill:S,stroke:g,strokeWidth:1.5}]},{id:"bar-circle",markerWidth:18,markerHeight:12,refX:18,refY:6,shapes:[{element:"line",x1:13,y1:1,x2:13,y2:11,stroke:g,strokeWidth:1.5},{element:"circle",cx:4,cy:6,r:2.75,fill:S,stroke:g,strokeWidth:1.5}]},{id:"crowsfoot-triangle-circle",markerWidth:24,markerHeight:12,refX:24,refY:6,shapes:[{element:"path",d:"M 24 3 L 19 3 M 24 6 L 19 6 M 24 9 L 19 9",fill:"none",stroke:g,strokeWidth:1.5},{element:"path",d:"M 19 1 L 19 11 L 11 6 Z",fill:S,stroke:g,strokeWidth:1.5,strokeLinejoin:"round"},{element:"circle",cx:8.25,cy:6,r:2.75,fill:S,stroke:g,strokeWidth:1.5}]},{id:"crowsfoot-triangle-bar",markerWidth:24,markerHeight:12,refX:24,refY:6,shapes:[{element:"path",d:"M 24 3 L 19 3 M 24 6 L 19 6 M 24 9 L 19 9",fill:"none",stroke:g,strokeWidth:1.5},{element:"path",d:"M 19 1 L 19 11 L 11 6 Z",fill:S,stroke:g,strokeWidth:1.5,strokeLinejoin:"round"},{element:"line",x1:8,y1:1,x2:8,y2:11,stroke:g,strokeWidth:1.5}]},{id:"triangle-circle",markerWidth:24,markerHeight:12,refX:24,refY:6,shapes:[{element:"path",d:"M 19 1 L 19 11 L 11 6 Z",fill:S,stroke:g,strokeWidth:1.5,strokeLinejoin:"round"},{element:"circle",cx:8.25,cy:6,r:2.75,fill:S,stroke:g,strokeWidth:1.5}]},{id:"triangle-bar",markerWidth:24,markerHeight:12,refX:24,refY:6,shapes:[{element:"path",d:"M 19 1 L 19 11 L 11 6 Z",fill:S,stroke:g,strokeWidth:1.5,strokeLinejoin:"round"},{element:"line",x1:8,y1:1,x2:8,y2:11,stroke:g,strokeWidth:1.5}]},{id:"cup",markerWidth:18,markerHeight:12,refX:18,refY:6,shapes:[{element:"path",d:"M 13 1 A 5 5 0 0 0 13 11",fill:"none",stroke:g,strokeWidth:1.5}]}],va=new Map(Ce.map(e=>[e.id,e]));var go=" ",ot=["fill","fill-opacity","stroke","stroke-opacity","stroke-width","stroke-dasharray"],yo={svg:["xmlns","viewBox","width","height","role","aria-label","font-family"],defs:[],marker:["id","markerWidth","markerHeight","refX","refY","orient","markerUnits"],clipPath:["id"],g:["data-md-element","clip-path","transform"],a:["href"],title:[],desc:[],rect:["x","y","width","height","rx",...ot],path:["d",...ot,"stroke-linejoin","marker-start","marker-end","visibility","pointer-events"],polygon:["points",...ot],line:["x1","y1","x2","y2","fill","stroke","stroke-opacity","stroke-width","stroke-dasharray"],circle:["cx","cy","r","fill","stroke","stroke-width"],text:["x","y","font-size","font-weight","font-style","text-decoration","fill","fill-opacity","textLength","lengthAdjust"]},pn=new Map(Object.entries(yo).map(([e,t])=>[e,new Set(t)])),wo=new Set(["title","desc","text"]),an="svg",Y="g",P="a",fn="title",re="data-md-element",un="path",hn=["visibility","pointer-events"],gn=new Set(["rect","text","polygon","line"]),ko={"clip-path":"clipPath","marker-start":"marker","marker-end":"marker"},yn=/&(amp|lt|gt|quot|apos);/g,Eo={amp:"&",lt:"<",gt:">",quot:'"',apos:"'"},xo=/[<>"']/,bo=/[\u0000-\u001F]/,To=/^<([^\s/>]+)/,Co=/^<\/([^\s/>]+)>/,st=/ ([^\s=/>"'<]+)=/y,it=/"([^"]*)"/y,vo=/^on/i,So=/^ */,Io=64,dn=/[\p{Cc}\p{Cf}\u2028\u2029]/gu,M=String.raw`-?\d+(?:\.\d+)?(?:e\+\d+)?`,$o=new RegExp(`^${M}$`),Ao=/^(?:[1-9]\d{0,2}|1000)$/,Ro=new RegExp(`^0 0 ${M} ${M}$`),_o=new RegExp(`^${M}(?:[ ,]${M})*$`),Po=new RegExp(`^(?:${M},${M}(?: ${M},${M})*)?$`),Mo=/^[MmZzLlHhVvCcSsQqTtAa0-9eE.,+\- ]*$/,wn=/^url\(#([A-Za-z0-9_-]+)\)$/,kn=/^#[0-9a-f]{6}$/,cn=Array.from(new Set([K,...Ce.flatMap(({shapes:e})=>e.flatMap(t=>["fill"in t?t.fill:K,t.stroke])).filter(e=>!kn.test(e))])),En=Ce.flatMap(({id:e})=>[nt(e,!1),nt(e,!0)]),Lo=new RegExp(`^${be}-[1-9]\\d*$`),Do=new RegExp(`-${be}-[1-9]\\d*$`),ct=`${"md"}-`,lt=e=>`U+${e.codePointAt(0).toString(16).toUpperCase().padStart(4,"0")}`,E=e=>{let t=[];for(let n of e){if(t.length===Io)return`${t.join("").replace(dn,lt)}\u2026`;t.push(n)}return e.replace(dn,lt)},xn=e=>{if(e.includes("&#"))return"a numeric character reference is not allowed";let t=e.replace(yn,"");if(t.includes("&"))return"& does not start one of the five XML entities";let n=xo.exec(t);return n===null?void 0:`an unescaped ${n[0]} is not allowed`},bn=e=>e.replace(yn,(t,n)=>Eo[n]),B=(e,t)=>n=>e.test(n)?void 0:`"${E(n)}" is not ${t}`,I=(e,t)=>n=>e.includes(bn(n))?void 0:`"${E(n)}" is not ${t}`,ln=()=>{},k=B($o,"a number as the serializer writes one"),mn=e=>kn.test(e)||cn.includes(e)?void 0:`"${E(e)}" is not a paint the serializer writes (#rrggbb, ${cn.join(", ")})`,at=B(wn,"a reference to a definition in the file, as url(#\u2026)"),No=e=>Lo.test(e)?"clipPath":En.includes(e)?"marker":void 0,Oo=(e,t)=>{if(!e.startsWith(ct))return;let n=e.slice(ct.length);if(t!==void 0)return n.startsWith(`${t}-`)?No(n.slice(t.length+1)):void 0;if(tt(n))return(Do.exec(n)?.index??0)>0?"clipPath":En.some(r=>n.length>r.length+1&&n.endsWith(`-${r}`))?"marker":void 0},Fo=(e,t,{idPrefix:n})=>{if(Oo(e,n)===t)return;let r=n??"<prefix>",o=t==="marker"?"<marker>[-start]":`${be}-<n>`;return`"${E(e)}" is not an id the serializer gives a <${t}> (${ct}${r}-${o})`},Ho=e=>{let t=bn(e);return t.startsWith("#")&&Te(t)?void 0:`"${E(e)}" does not point at a section of the page (#\u2026)`},Uo={xmlns:I([ne],"the SVG namespace"),viewBox:B(Ro,"a viewBox the serializer writes (0 0 width height)"),width:k,height:k,role:I([Je],`the role the serializer writes (${Je})`),"aria-label":ln,"font-family":I([rn],"the serializer's font stack"),id:Fo,markerWidth:k,markerHeight:k,refX:k,refY:k,orient:I([et],`the marker orientation the serializer writes (${et})`),markerUnits:I([rt],`the marker units the serializer writes (${rt})`),"data-md-element":ln,"clip-path":at,transform:e=>sn(e)?void 0:`"${E(e)}" is not a transform of numbers the serializer writes`,href:Ho,x:k,y:k,rx:k,fill:mn,"fill-opacity":k,stroke:mn,"stroke-opacity":k,"stroke-width":k,"stroke-dasharray":B(_o,"a dash pattern of numbers"),d:B(Mo,"path data (commands and numbers only)"),"stroke-linejoin":I(["round"],"the line join the serializer writes (round)"),"marker-start":at,"marker-end":at,points:B(Po,"polygon points as the serializer writes them (x,y x,y \u2026)"),x1:k,y1:k,x2:k,y2:k,cx:k,cy:k,r:k,"font-size":k,"font-weight":B(Ao,"a numeric font weight (1 to 1000)"),"font-style":I([Ze],`the font style the serializer writes (${Ze})`),"text-decoration":I([Qe],`the decoration the serializer writes (${Qe})`),textLength:k,lengthAdjust:I([qe],`the length adjustment the serializer writes (${qe})`),visibility:I([Ee],`the visibility of a hit area (${Ee})`),"pointer-events":I([xe],`the pointer events of a hit area (${xe})`)},Wo=(e,t)=>{if(vo.test(t))return`the event-handler attribute ${E(t)} is not allowed`;if(t.toLowerCase()==="style")return"a style attribute is not allowed";if(t.toLowerCase()==="class")return"a class attribute is not allowed";if(t.includes(":"))return`the namespaced attribute ${E(t)} is not allowed`;if(!pn.get(e).has(t))return`<${e}> does not take the attribute ${E(t)}`},Bo=(e,t,n,r)=>{let o=xn(n)??Uo[t](n,e,r);return o===void 0?void 0:`${t}: ${o}`},zo=(e,t,n,r)=>{let o=new Map,s=n;for(;t[s]!==">"&&!t.startsWith("/>",s);){st.lastIndex=s;let i=st.exec(t)?.[1];if(i===void 0)return{problem:'a tag the serializer would not write: expected ` name="value"`, `>` or `/>`'};it.lastIndex=st.lastIndex;let a=it.exec(t)?.[1];if(a===void 0)return{problem:`the value of ${E(i)} is not double-quoted`};let d=Wo(e,i)??Bo(e,i,a,r);if(d!==void 0)return{problem:d};if(o.has(i))return{problem:`<${e}> repeats the attribute ${i}`};o.set(i,a),s=it.lastIndex}return{attributes:o,end:s}},dt=e=>e.startsWith("<")?"more than one element on one line":"text outside <title> and <text>",jo=(e,t)=>{if(e.includes("<!"))return{problem:"a markup declaration (<!\u2026>: a DOCTYPE, an ENTITY, a comment or CDATA) is not allowed"};if(e.includes("<?"))return{problem:"a processing instruction (<?\u2026?>) is not allowed"};if(e.startsWith("</")){let l=Co.exec(e);if(l===null)return{problem:"a closing tag the serializer would not write"};let f=e.slice(l[0].length);return f!==""?{problem:dt(f)}:{line:{kind:"close",name:l[1]}}}let n=To.exec(e)?.[1];if(n===void 0)return{problem:"the line does not start with a tag"};if(!pn.has(n))return{problem:`<${E(n)}> is not an element the diagram serializer writes`};let r=zo(n,e,n.length+1,t);if("problem"in r)return r;let{attributes:o,end:s}=r,i=e.startsWith("/>",s),a=e.slice(s+(i?2:1));if(!wo.has(n))return a!==""?{problem:dt(a)}:{line:{kind:i?"empty":"open",name:n,attributes:o}};if(i||a==="")return{problem:`<${n}> has to hold its text on one line, as <${n}>\u2026</${n}>`};let d=`</${n}>`,m=a.indexOf(d);if(m===-1)return{problem:`<${n}> is not closed on its line`};if(m+d.length!==a.length)return{problem:dt(a.slice(m+d.length))};let p=xn(a.slice(0,m));return p===void 0?{line:{kind:"text",name:n,attributes:o}}:{problem:`text: ${p}`}},Go=(e,t)=>{if(e.name===an!=(t===0))return t===0?`the file opens with <${e.name}>, not the serializer's <svg xmlns="${ne}"> root`:"<svg> is only the root, never nested";if(e.name===an&&(e.kind!=="open"||e.attributes.get("xmlns")!==ne))return`the root is not the serializer's <svg xmlns="${ne}">`;if(e.name===P&&!e.attributes.has("href"))return"<a> without an href"},Vo=(e,t,n)=>{if(t.name===P&&t.children===0&&e.name!==fn)return"<a> has to open with the <title> of the group it links";if(e.name===P)return e.kind!=="open"?"<a> is only written open, around the title and body of the group it links":t.name!==Y||!t.stamped||t.children!==0?`<a> is only written as the first child of a <g ${re}>`:n>0?"an <a> inside another <a>":void 0},Tn=e=>hn.some(t=>e.attributes.has(t)),Ko=(e,t)=>e.size===t.size&&Array.from(t).every(([n,r])=>e.get(n)===r),Cn=e=>e.findLast(t=>t.stamped),vn="a line's group draws no box: it holds no <rect>, <text>, <polygon> or <line>",Sn="a line's group holds no stamped group of its own",ve="desc",Yo=e=>e!==void 0&&(e.hitArea||e.described),Xo={d:()=>!0,fill:e=>e===K,stroke:e=>e===g,"stroke-opacity":e=>e==="0","stroke-width":e=>e===Ye(1),"stroke-dasharray":e=>e==="6 4","marker-start":()=>!0,"marker-end":()=>!0},In=e=>e.name===un&&e.attributes.get("fill")===K&&e.attributes.get("stroke")===g&&Array.from(e.attributes).every(([t,n])=>Xo[t]?.(n)??!1),$n=e=>{let t=e.at(-1),n=t?.name===P?e.at(-2):t;return n?.name===Y&&n.stamped?n:void 0},Jo=(e,t)=>{if(!Tn(e))return;let n=t.at(-1),r=n.name===P?t.at(-2):n;if(r?.name!==Y||!r.stamped)return`a hit area (${hn.join(", ")}) is only written in a <g ${re}>`;if(r.hitArea)return"a stamped group holds one hit area";if(r===n&&r.linked)return"in a linked group, the hit area is inside the link";if(r.drawsBox)return vn;if(r.nestsStamp)return Sn;let o=n.previous;if(o===void 0||o!==n.firstPath||!In(o))return"a hit area follows the line's route, the first path of its group";let s=new Map([["d",o.attributes.get("d")??""],["fill",K],["stroke",g],["stroke-width",Ye(10)],["visibility",Ee],["pointer-events",xe]]);return Ko(e.attributes,s)?void 0:`a hit area is the route before it as <path ${Array.from(s.keys()).join(" ")}>, with the serializer's values`},qo=(e,t)=>{let n=Cn(t);if(Yo(n))return gn.has(e.name)?vn:e.name===Y&&e.attributes.has(re)?Sn:void 0},Zo=(e,t)=>{if(e.name!==ve)return;let n=t.at(-1);return $n(t)===void 0||n.children!==1||n.previous?.name!==fn?`a <${ve}> is only written right after the <title> that opens a stamped group`:void 0},mt=(e,t={})=>{if(t.idPrefix!==void 0&&!tt(t.idPrefix))return`the id prefix "${E(t.idPrefix)}" is not one the serializer accepts`;let n=e.split(`
16
+ `);if(n.at(-1)===""&&n.pop(),n.length===0)return"the file is empty";let r=[],o=new Map,s=[],i=0,a=!1;for(let[p,l]of n.entries()){let f=p+1,y=de=>`line ${f}: ${de}`;if(l==="")return y("a blank line");let b=bo.exec(l);if(b!==null)return y(`the control character ${lt(b[0])} is not allowed`);if(te(l)!==l)return y("a code point XML cannot carry is not allowed");if(a)return y("content after the root element");let C=l.replace(So,""),R=jo(C,t);if("problem"in R)return y(R.problem);let{line:u}=R,w=u.kind==="close"?r.pop():void 0;if(u.kind==="close"&&w?.name!==u.name)return y(w===void 0?`</${E(u.name)}> closes nothing`:`</${E(u.name)}> closes <${w.name}>, opened on line ${w.lineNumber}`);let x=l.length-C.length,U=r.length*go.length;if(x!==U)return y(`indented ${x} spaces where the nesting calls for ${U}`);if(u.kind==="close"){if(w.name===P){if(w.children===0)return y("<a> has to open with the <title> of the group it links");i-=1}if(w.described&&!w.drawsRoute)return y(`a <${ve}> belongs to a line's route group, and this group draws no route`);a=r.length===0;continue}let v=r.at(-1),vt=Go(u,r.length)??(v&&(Vo(u,v,i)??Jo(u,r)??Zo(u,r)??qo(u,r)));if(vt!==void 0)return y(vt);let ie=u.attributes.get("id");if(ie!==void 0){if(o.has(ie))return y(`the id ${E(ie)} is declared twice`);o.set(ie,u.name)}for(let[de,Er]of u.attributes){let St=ko[de];St!==void 0&&s.push({attribute:de,id:wn.exec(Er)[1],kind:St,lineNumber:f})}v!==void 0&&(v.children+=1,v.previous=u,v.firstPath??=u.name===un?u:void 0,v.linked||=v.stamped&&u.name===P),Tn(u)&&((v.name===P?r.at(-2):v).hitArea=!0);let Le=$n(r);Le!==void 0&&(Le.described||=u.name===ve,Le.drawsRoute||=v.firstPath===u&&In(u));let ae=Cn(r);ae!==void 0&&gn.has(u.name)&&(ae.drawsBox=!0),ae!==void 0&&u.name===Y&&u.attributes.has(re)&&(ae.nestsStamp=!0),u.kind==="open"&&(r.push({name:u.name,lineNumber:f,stamped:u.name===Y&&u.attributes.has(re),children:0,linked:!1,hitArea:!1,drawsBox:!1,described:!1,drawsRoute:!1,nestsStamp:!1}),i+=u.name===P?1:0)}let d=r.at(-1);if(d!==void 0)return`<${d.name}> opened on line ${d.lineNumber} is never closed`;let m=s.find(({id:p,kind:l})=>o.get(p)!==l);return m===void 0?void 0:`line ${m.lineNumber}: ${m.attribute}: url(#${E(m.id)}) names no <${m.kind}> the file declares`};var pt=" ",_n=/^( *)<g data-md-element="([^"]*)">$/,Qo=/^<title>[^<]*<\/title>$/,ft=".svg",An=".png",Rn=[137,80,78,71,13,10,26,10],es=e=>{let t=new Map,n=(r,o)=>{o&&!t.has(r)&&t.set(r,`#${o}`)};for(let r of e.fragments??[]){n(r.elementId,r.realization?.anchor);for(let o of r.anchors??[])n(o.elementId,o.anchor)}return t},ts=e=>{let t=new Map;for(let[n,r]of e){let o=Xe(n);!t.has(o)&&Te(r)&&t.set(o,r)}return t},ns=(e,t,n)=>{let r=n+pt,o=`${n}</g>`,s=`${r}<g data-md-element="`;for(let i=t+1;i<e.length;i+=1){let a=e[i];if(a===o||a.startsWith(s))return i;if(!a.startsWith(r))return}},rs=(e,t)=>{if(t.size===0)return e;let n=e.split(`
17
+ `),r=[],o=0;for(;o<n.length;){let s=n[o];r.push(s),o+=1;let i=_n.exec(s),a=i?t.get(i[2]):void 0;if(!i||a===void 0)continue;let d=i[1],m=d+pt,p=n[o];if(p===void 0||!p.startsWith(m)||!Qo.test(p.slice(m.length)))continue;let l=ns(n,o-1,d);if(l!==void 0){for(r.push(`${m}<a href="${Xe(a)}">`);o<l;o+=1)r.push(pt+n[o]);r.push(`${m}</a>`)}}return r.join(`
18
+ `)},os=e=>e.split(`
19
+ `).some(t=>_n.test(t)),Pn=(e,t)=>e.toLowerCase().endsWith(ft)&&!os(new TextDecoder().decode(t));var L=class extends Error{path;problem;constructor(t,n){super(`Refused the diagram file ${t}: ${n}`),this.name="RefusedMediaError",this.path=t,this.problem=n}},ss=e=>e.length>=Rn.length&&Rn.every((t,n)=>e[n]===t),is=(e,t)=>{let n=e!==void 0&&Object.hasOwn(e,t)?e[t]:void 0;return typeof n=="string"?{idPrefix:on(n)}:{}},Mn=({manifest:e,mediaIndex:t})=>{let n=ts(es(e)),r=new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}),o=new TextEncoder,s=(i,a)=>{let d;try{d=r.decode(a)}catch{throw new L(i,"the bytes are not UTF-8 text")}let m=is(t,i),p=mt(d,m);if(p!==void 0)throw new L(i,p);let l=rs(d,n);if(l===d)return a;let f=mt(l,m);if(f!==void 0)throw new L(i,`once linked, ${f}`);return o.encode(l)};return(i,a)=>{let d=i.toLowerCase();if(d.endsWith(ft))return s(i,a);if(!d.endsWith(An))throw new L(i,`a diagram file is an ${ft} or a ${An}, and this is neither`);if(!ss(a))throw new L(i,"the bytes are not a PNG image");return a}};var z=e=>{let t=e.payloads??{},n=[];for(let r of e.manifest.fragments??[]){let o=r.realization?.path;if(!o)continue;let s=t[r.address];s!==void 0&&n.push({path:o,content:s})}return n},Ln="image/svg+xml, image/png",Se=e=>{let t=[];for(let n of e.fragments??[])for(let r of n.realization?.media??[])t.push(r);return[...new Set(t)]},X=(e,t={})=>{let n=new Set,r=new Set;for(let o of e)Object.hasOwn(t,o)?n.add(t[o]):r.add(o);return n.size+r.size};var Dn=(e,t)=>{let n=Mn(e),r=Nn(e),o=new Map,s=[],i=[];for(let{path:l,bytes:f}of t){if(f===void 0){s.push(l);continue}try{o.set(l,n(l,f))}catch(y){if(!(y instanceof L))throw y;s.push(l),i.push({path:l,problem:y.problem})}}let a=new Set;for(let[l,f]of o){let y=r(l);y!==void 0&&Pn(l,f)&&a.add(y)}let d=l=>{let f=r(l);return f!==void 0&&a.has(f)},m=new Map([...o].filter(([l])=>!d(l))),p=s.filter(l=>!d(l));return{mediaBytesByPath:m,missingPaths:p,refusals:i.filter(({path:l})=>!d(l)),emptyDiagramIds:a,emptyDiagramPaths:t.map(({path:l})=>l).filter(d),...as(e,p,m)}},Nn=e=>{let t=e.mediaIndex??{};return n=>Object.hasOwn(t,n)?t[n]:void 0},as=(e,t,n)=>{let r=e.mediaIndex??{},o=Nn(e),s=new Set([...n.keys()].map(o)),i=a=>{let d=o(a);return d!==void 0&&s.has(d)};return{missingDiagrams:X(t.filter(a=>!i(a)),r),partialDiagrams:X(t.filter(i),r)}};import{existsSync as ut,lstatSync as On,realpathSync as ds}from"node:fs";import{homedir as cs}from"node:os";import{basename as ls,dirname as ms,isAbsolute as Ie,join as Fn,relative as ht,resolve as J}from"node:path";var Ae=(e,t,n)=>{if(Ie(n))throw new c(`Refusing an absolute path from the bundle manifest: ${n}`);let r=J(e,t),o=J(e,n),s=ht(r,o);if(s===""||s.startsWith("..")||Ie(s))throw new c(`Refusing a bundle path that escapes the content root: ${n}`);return o},ps=e=>{try{return On(e)}catch{return}},$e=e=>{let t=[],n=e;for(;!ut(n);){let o=ms(n);if(o===n)return e;t.unshift(ls(n)),n=o}let r=ds(n);return t.length>0?Fn(r,...t):r},gt=e=>{if(ps(e)?.isSymbolicLink())throw new c(`Refusing to write through an existing symlink at a managed path: ${e}`)},Re=(e,t)=>{let n=$e(e),r=$e(t),o=ht(n,r);if(o!==""&&(o.startsWith("..")||Ie(o)))throw new c(`Refusing a path that escapes the content root through a symlink: ${t}`)},yt=(e,t)=>{let n=J(e,t);if(n===J(e))throw new c(`The content root resolves to the repository root (${n}); refusing to sync it.`);if(n===J(cs()))throw new c("The content root resolves to your home directory; refusing to sync it.");if(ut(n)){if(On(n).isSymbolicLink())throw new c(`The content root ${n} is a symlink; refusing to sync it.`);if(ut(Fn(n,".git")))throw new c(`The content root ${n} contains a .git directory (it looks like a repo root); refusing.`)}let r=$e(J(e)),o=ht(r,$e(n));if(o!==""&&(o.startsWith("..")||Ie(o)))throw new c(`The content root ${n} resolves outside the repository via a symlinked ancestor; refusing.`);return n};var fs={respec:"data/model"},Hn=e=>{let t=fs[e];if(!t)throw new c(`The CLI does not know the content root for stack '${e}'.`);return t};var us=1500,hs=6e5,gs=e=>new Promise(t=>setTimeout(t,e)),ys=e=>e==="COMPLETED"||e==="COMPLETED_WITH_ERRORS"||e==="FAILED",Un=async(e,t,n)=>{let r=await e.postForJson(`/models/${t}/content-bundle/diagrams`,{template:n.template,version:n.version});if(!r.commit)throw new c("The diagram render did not return a resolved commit; cannot pin the pull.");return r},Wn=async(e,t,n,r,o,s={})=>{let i=s.sleep??gs,a=s.now??Date.now,d=a()+hs;for(r.update(0,o);;){let m=await e.getJson(`/models/${t}/export-jobs/${n}`);if(m.progress&&r.update(m.progress.completed,m.progress.total,m.progress.currentItem),ys(m.status))return r.done(),m;if(a()>=d)throw new c("Timed out waiting for the diagram render to finish.");await i(us)}},Bn=(e,t,n,r)=>e.getBytes(`/models/${t}/content-bundle/media`,{path:n,template:r.template,commit:r.commit});var zn=e=>{let t=new Map;for(let n of e.fragments??[])n.realization?.path&&t.set(n.elementId,n.realization.path);return t},jn=(e,t)=>{let n=zn(e),r=[];for(let[o,s]of zn(t)){let i=n.get(o);i&&i!==s&&r.push({elementId:o,oldPath:i,newPath:s})}return r};import{existsSync as ws,mkdirSync as ks,readFileSync as Es,writeFileSync as xs}from"node:fs";import{join as Gn}from"node:path";var Vn=(e,t)=>Gn(e,he,`${t}.manifest.json`),Kn=(e,t)=>{let n=Vn(e,t);if(!ws(n))return;let r;try{r=JSON.parse(Es(n,"utf8"))}catch{return}return bs(r)?r:void 0},bs=e=>{if(e===null||typeof e!="object"||Array.isArray(e))return!1;let{fragments:t}=e;return t===void 0?!0:Array.isArray(t)&&t.every(n=>n!==null&&typeof n=="object")},Yn=(e,t,n)=>{ks(Gn(e,he),{recursive:!0}),xs(Vn(e,t),Ke(n))},Xn=e=>{let t=[];for(let n of e.fragments??[]){n.realization?.path&&t.push(n.realization.path);for(let r of n.realization?.media??[])t.push(r)}return t};var Ts=e=>`<figure data-md-diagram="${e}">`,Cs="</figure>",Jn=(e,t)=>{if(t.size===0||e.payloads===void 0)return e;let n=new Set([...t].map(Ts)),r=Object.fromEntries(Object.entries(e.payloads).map(([o,s])=>[o,vs(s,n)]));return{...e,payloads:r}},vs=(e,t)=>{let n=e.split(`
20
+ `),r=[];for(let o=0;o<n.length;o+=1){let s=t.has(n[o])?n.indexOf(Cs,o):-1;if(s===-1){r.push(n[o]);continue}o=s,r.at(-1)===""&&n[o+1]===""&&(o+=1)}return r.join(`
21
+ `)};import{existsSync as qn,mkdirSync as Zn,readdirSync as Ss,rmSync as Is,writeFileSync as Qn}from"node:fs";import{dirname as er,join as oe,relative as $s,sep as As}from"node:path";var tr=(e,t)=>{let n=oe(e,t);if(!qn(n))return[];let r=[],o=s=>{for(let i of Ss(s,{withFileTypes:!0})){let a=oe(s,i.name);i.isDirectory()?o(a):i.isFile()&&r.push($s(e,a).split(As).join("/"))}};return o(n),r},wt=(e,t,n)=>{let r=oe(e,t);for(let o of n){let s=Ae(e,t,o.path);gt(s),Re(r,s),Zn(er(s),{recursive:!0}),Qn(s,o.content)}},Rs=(e,t,n,r)=>{let o=oe(e,t),s=Ae(e,t,n);gt(s),Re(o,s),Zn(er(s),{recursive:!0}),Qn(s,r)},nr=(e,t,n,{mediaBytesByPath:r,emptyDiagramIds:o})=>{let s=[];for(let[d,m]of r)try{Rs(e,t,d,m)}catch(p){s.push({path:d,error:p})}if(o.size===0)return s;let i=new Map(z(n).map(d=>[d.path,d.content])),a=z(Jn(n,o)).filter(d=>i.get(d.path)!==d.content);return wt(e,t,a),s},kt=(e,t,n)=>{let r=Ae(e,t,n);return Re(oe(e,t),r),qn(r)?(Is(r),!0):!1},rr=(e,t,n)=>{for(let r of n)kt(e,t,r)};var _s=100,sr=e=>{if(e.newTextPaths.length===0&&!e.allowEmpty)throw new c("The content fetch returned no fragments (usually the wrong model or format, or a server error). Nothing was written or deleted. Re-run with --allow-empty only if the model is genuinely empty.");let t=or([...e.newTextPaths,...e.newMediaPaths]).sort(),n=new Set(t),r=new Set(e.existingFiles),o=t.filter(p=>r.has(p)),s=e.priorManifestPaths!==void 0?"manifest-tracked":"full-sync",i=e.priorManifestPaths??e.existingFiles,a=or(i).filter(p=>!n.has(p)).sort(),d=a.length>0&&!e.adopted,m=a.length>0&&(a.length>t.length||a.length>_s);return{writes:t,overwrites:o,orphanCandidates:a,mode:s,adoptionNeeded:d,outsized:m}},Et=(e,t)=>{if(e.orphanCandidates.length===0)return{deletes:[],warnings:[]};if(e.adoptionNeeded&&!t.adopt)return{deletes:[],warnings:[`${e.orphanCandidates.length} file(s) under the content root are not tracked by the CLI and were left in place. Pass --adopt to let the CLI manage (and prune) this directory.`]};if(e.outsized&&!t.yes)throw new c(`This pull would remove ${e.orphanCandidates.length} file(s), more than the ${e.writes.length} it writes. That often signals a wrong model or format. Nothing was changed. Re-run with --yes if intended.`);return{deletes:e.orphanCandidates,warnings:[]}},or=e=>[...new Set(e)];var ir=(e,t)=>{let n=Q(t);switch(n.mode){case"error":throw new c(n.message);case"secret":case"federated":return ke(e,n);case"interactive":return new we(e)}};var Ps=6e4,ar={unauthorized:"Run 'modeldesk auth login' and try again.",forbidden:"Your account may lack access to this model."},Ms={unauthorized:ue.unauthorized,forbidden:ue.forbidden,notFound:ue.notFound},H=class extends c{status;constructor(t,n){super(t),this.name="ApiError",this.status=n}},_e=(e,t)=>new xt(e.apiBase,ir(e,t),F(t)?Ms:ar),xt=class{constructor(t,n,r=ar){this.apiBase=t;this.tokens=n;this.hints=r}apiBase;tokens;hints;async getJson(t,n){return await(await this.request(t,{method:"GET"},n)).json()}async postForJson(t,n){return await(await this.request(t,{method:"POST"},n)).json()}async getBytes(t,n){let r=await this.request(t,{method:"GET",headers:{Accept:Ln}},n);return new Uint8Array(await r.arrayBuffer())}async request(t,n,r){let o=await this.tokens.getAccessToken(),s=Ls(this.apiBase,t,r),i;try{i=await fetch(s,{...n,headers:{Authorization:`Bearer ${o}`,Accept:"application/json",...n.headers??{}},signal:AbortSignal.timeout(Ps)})}catch(a){throw new c(`Could not reach ${this.apiBase} (${a.message}). Check the host and that the ModelDesk server is reachable.`)}if(!i.ok)throw await Ds(i,t,this.hints);return i}},Ls=(e,t,n)=>{let r=new URL(t,e);if(n)for(let[o,s]of Object.entries(n))s!==void 0&&r.searchParams.set(o,s);return r.toString()},Ds=async(e,t,n)=>{let r=`${e.status} ${e.statusText}`.trim();try{if((e.headers.get("content-type")??"").includes("json")){let s=await e.json(),i=s.detail??s.title;i&&(r=`${e.status} ${i}`)}}catch{}return e.status===401?new H(`Authentication failed (${r}). ${n.unauthorized}`,e.status):e.status===403?new H(`Not authorized (${r}). ${n.forbidden}`,e.status):e.status===404&&n.notFound?new H(`Not found (${r}). ${n.notFound}`,e.status):new H(`Request to ${t} failed: ${r}`,e.status)};var Ns=(e,t)=>{let n=t>0?Math.min(1,e/t):0,r=Math.round(n*24);return`[${"#".repeat(r)}${"-".repeat(24-r)}]`},dr=(e,t=n=>{process.stderr.write(n)})=>{let n=-1;return{update(r,o,s){if(e){let i=s?` ${s}`:"";t(`\r${Ns(r,o)} ${r}/${o}${i}\x1B[K`)}else if(r!==n){let i=s?` (${s})`:"";t(`Rendering diagrams: ${r}/${o}${i}
22
+ `),n=r}},done(){e&&t(`
23
+ `)}}};import{createInterface as Os}from"node:readline";var Pe=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),bt=async e=>{if(!Pe())return!1;let t=Os({input:process.stdin,output:process.stderr});try{let n=await new Promise(r=>t.question(`${e} [y/N] `,r));return/^y(es)?$/i.test(n.trim())}finally{t.close()}};var T=e=>{process.stdout.write(e)},$=e=>{process.stderr.write(e)},cr=e=>{for(let t of e)$(` ${t}
24
+ `)},mr=async e=>{let{values:t}=G(e.argv,{host:{type:"string"},model:{type:"string"},stack:{type:"string"},template:{type:"string"},version:{type:"string"},"no-diagrams":{type:"boolean"},"dry-run":{type:"boolean"},"allow-empty":{type:"boolean"},adopt:{type:"boolean"},yes:{type:"boolean"}}),n=ye(e,{host:t.host,model:t.model,stack:t.stack,template:t.template,version:t.version}),r={noDiagrams:!!t["no-diagrams"],dryRun:!!t["dry-run"],allowEmpty:!!t["allow-empty"],adopt:!!t.adopt,yes:!!t.yes},o=_e(n.instance,e.env),s=je(n.stack,"stack",h.stack,"--stack"),i=je(n.template,"template",h.template,"--template"),d=!r.noDiagrams&&!r.dryRun?await Un(o,n.model,{template:i,version:n.version}):void 0,m=d?.commit,p=await o.getJson(`/models/${n.model}/content-bundle`,{template:i,commit:m,version:m?void 0:n.version});if(p.manifest.stack!==s)throw new c(`Requested stack '${s}', but template '${i}' renders under stack '${p.manifest.stack}'. Run 'modeldesk bundle templates' to see valid stack/template pairs.`);let l=Hn(s);yt(n.loaded.repoRoot,l);let f=z(p),y=Se(p.manifest),b=X(y,p.mediaIndex),C=Kn(n.loaded.repoRoot,s),R=sr({newTextPaths:f.map(U=>U.path),newMediaPaths:y,priorManifestPaths:C?Xn(C):void 0,existingFiles:tr(n.loaded.repoRoot,l),adopted:C!==void 0||ge(n.loaded),allowEmpty:r.allowEmpty}),u=C?jn(C,p.manifest):[],w={stack:s,version:p.manifest.stackVersion};if(r.dryRun)return lr(n,i,w,m,R,void 0,u,b,!0),0;let x=await Fs(R,r);return lr(n,i,w,m,R,x,u,b,!1),yt(n.loaded.repoRoot,l),wt(n.loaded.repoRoot,l,f),rr(n.loaded.repoRoot,l,x.deletes),Yn(n.loaded.repoRoot,s,p.manifest),y.length===0||!d?0:Hs(o,n,i,m,d,p,{media:y,diagramCount:b},l)},Fs=async(e,t)=>{let{adopt:n,yes:r}=t;if(e.orphanCandidates.length===0)return Et(e,{adopt:n,yes:r});let o=Pe();return e.adoptionNeeded&&!n&&($(`The content root has ${e.orphanCandidates.length} file(s) the CLI does not track:
25
+ `),cr(e.orphanCandidates),o&&(n=await bt("Adopt this directory and allow the CLI to remove these files?"))),e.outsized&&!r&&(n||!e.adoptionNeeded)&&($(`This pull would remove ${e.orphanCandidates.length} file(s), more than the ${e.writes.length} it writes:
26
+ `),cr(e.orphanCandidates),o&&(r=await bt("Proceed with removing them?"))),Et(e,{adopt:n,yes:r})},Hs=async(e,t,n,r,o,s,{media:i,diagramCount:a},d)=>{let m=dr(Pe()),p=await Wn(e,t.model,o.id,m,a);if(p.status==="FAILED")return $(`warning: the diagram render failed. Text fragments are written; media was not fetched. Re-run to retry.
27
+ `),1;let{repoRoot:l}=t.loaded,f=[],y=new Map;for(let w of i)try{f.push({path:w,bytes:await Bn(e,t.model,w,{template:n,commit:r})})}catch(x){f.push({path:w}),y.set(w,x)}let b=Dn(s,f),C=nr(l,d,s,b),R=new Map(b.refusals.map(({path:w,problem:x})=>[w,x]));for(let w of b.missingPaths){let x=R.get(w),U=y.get(w);$(x!==void 0?`warning: refused the diagram file ${w} and did not write it: ${x}
28
+ `:`warning: could not fetch media ${w}: ${Tt(U)}
29
+ `),(x!==void 0||Bs(U))&&pr(l,d,w)}for(let{path:w,error:x}of C)$(`warning: could not write media ${w}: ${Tt(x)}
30
+ `);b.emptyDiagramIds.size>0&&Us(l,d,b.emptyDiagramIds.size,b.emptyDiagramPaths);let u=b.missingPaths.length+C.length;return p.status==="COMPLETED_WITH_ERRORS"||u>0?($(`warning: ${u} media file(s) could not be written; the pull is re-runnable.
31
+ `),1):0},Us=(e,t,n,r)=>{T(` Left out ${n} empty diagram(s), which draw no model element:
32
+ `);for(let o of r.filter(s=>s.toLowerCase().endsWith(".svg")))T(` - ${o}
33
+ `);for(let o of r)pr(e,t,o,s=>T(` ${s}`))},Tt=e=>e instanceof Error?e.message:String(e),Ws=404,Bs=e=>e instanceof H&&e.status===Ws,pr=(e,t,n,r=o=>$(`warning: ${o}`))=>{try{kt(e,t,n)&&r(`removed the copy of ${n} an earlier pull wrote.
34
+ `)}catch(o){$(`warning: could not remove the copy of ${n} an earlier pull wrote: ${Tt(o)}
35
+ `)}},lr=(e,t,n,r,o,s,i,a,d)=>{let m=r?`commit ${r}`:e.version?`version ${e.version}`:"HEAD",p=`${n.stack} ${n.version}`;T(`${d?"Dry run: ":""}Pulling ${e.model} (stack ${p}, template ${t}) at ${m} into ${e.instance.host}.
36
+ `),T(` Write: ${o.writes.length} file(s)${o.overwrites.length?` (${o.overwrites.length} overwritten)`:""}
37
+ `),T(` Diagrams: ${a}
38
+ `);let l=d?o.orphanCandidates:s?.deletes??[];T(` Remove: ${l.length} file(s) [${o.mode}]
39
+ `);for(let f of l)T(` - ${f}
40
+ `);if(d)o.adoptionNeeded&&T(` note: removing these needs --adopt (the root is not CLI-managed yet).
41
+ `),o.outsized&&T(` note: this removal is larger than the write set and needs --yes.
42
+ `);else for(let f of s?.warnings??[])$(`warning: ${f}
43
+ `);for(let f of i)T(` moved: ${f.oldPath} -> ${f.newPath}
44
+ `);if(d)for(let f of o.writes)T(` write ${f}
45
+ `)};var Me=e=>{let t=[];for(let n of e)n.forEach((r,o)=>{t[o]=Math.max(t[o]??0,r.length)});return t},se=(e,{indent:t="",gap:n=2,widths:r}={})=>{let s=Me(e).map((a,d)=>r?.[d]??a),i=" ".repeat(n);return e.map(a=>t+a.map((d,m)=>m===a.length-1?d:d.padEnd(s[m])).join(i))};var zs=["STACK","TEMPLATE","NAME"],fr=async e=>{let{values:t}=G(e.argv,{host:{type:"string"},model:{type:"string"},stack:{type:"string"}}),n=ye(e,{host:t.host,model:t.model}),r=_e(n.instance,e.env),o=await r.getJson(`/models/${n.model}`),s=(o.metamodelExtensions??[]).map(f=>f.id),i=s.length>0?{metamodelExtensions:s.join(",")}:void 0,{items:a}=await r.getJson(`/metamodels/${o.metamodel.id}/templates`,i),d=a??[];if(d.length===0)return process.stdout.write(`This model has no document templates.
46
+ `),0;let m=t.stack,p=m?d.filter(f=>f.stack===m):d;if(p.length===0)return process.stdout.write(`No document templates for stack '${m}'.
47
+ `),0;let l=p.map(f=>[f.stack??"",f.key??f.id,f.name]);for(let f of se([zs,...l]))process.stdout.write(`${f}
48
+ `);return 0};var Ct={bundle:{pull:{summary:"Materialize a model's content bundle into a local folder",usage:"bundle pull --model <id> --stack <id> --template <slug> [--host <host>] [--version <name>] [--no-diagrams] [--dry-run]",handler:mr},templates:{summary:"List a model's document templates",usage:"bundle templates --model <id> [--stack <id>] [--host <host>]",handler:fr}},auth:{login:{summary:"Log in through the browser (OAuth authorization code + PKCE)",usage:"auth login [--host <host>]",handler:Zt},logout:{summary:"Remove the stored credential for the active host",usage:"auth logout [--host <host>]",handler:Qt},status:{summary:"Show the active host, logged-in identity, and token validity",usage:"auth status [--host <host>]",handler:en}}};var ur={name:"@modeldesk/cli",version:"0.5.0",description:"Command-line interface (CLI) for the ModelDesk platform",keywords:["modeldesk","cli","documentation","content-bundle","respec","mim","information-model"],homepage:"https://modeldesk.io",repository:{type:"git",url:"git+https://github.com/model-desk/modeldesk.git",directory:"packages/cli"},license:"MIT",type:"module",bin:{modeldesk:"dist/modeldesk.js"},files:["dist"],publishConfig:{access:"public"},engines:{node:">=22"},scripts:{build:"node esbuild.config.mjs",prepublishOnly:"npm run build",typecheck:"tsc --noEmit",lint:"eslint","lint:fix":"eslint --fix",test:"vitest run","test:watch":"vitest"},devDependencies:{"@eslint/js":"^10.0.1","@modeldesk/content-bundle":"*","@types/node":"^24.19.1",esbuild:"^0.28.2",eslint:"^10.12.0","eslint-config-prettier":"^10.1.5","eslint-plugin-prettier":"^5.5.6",globals:"^17.13.0",prettier:"^3.9.9",typescript:"^6.0.3","typescript-eslint":"^8.71.1",vitest:"^5.0.3"}};var hr=ur.version;var Gs={out:e=>process.stdout.write(e),err:e=>process.stderr.write(e)},yr=new Set(["--help","-h","help"]),kr=async(e,t=Gs)=>{try{return await Vs(e,t)}catch(n){return n instanceof c?(t.err(`error: ${n.message}
37
49
  `),n.exitCode):(t.err(`error: ${n instanceof Error?n.message:String(n)}
38
- `),1)}},Kn=async(e,t)=>{let[n,...o]=e;if(n===void 0||Lt.has(n))return t.out(Vn()),0;if(n==="--version"||n==="-v")return t.out(`${_t}
39
- `),0;let r=Ce[n];if(!r)throw new i(`Unknown command '${n}'. Run 'modeldesk --help' to see available commands.`);let[s,...a]=o;if(s===void 0){let p=Object.keys(r).join(", ");throw new i(`'${n}' requires a subcommand (${p}). Run 'modeldesk ${n} --help'.`)}if(Lt.has(s))return t.out(Yn(n,r)),0;let c=r[s];if(!c)throw new i(`Unknown '${n}' command '${s}'. Run 'modeldesk ${n} --help'.`);let l={argv:a,env:process.env,cwd:process.cwd()},d=await c.handler(l);return typeof d=="number"?d:0},Ot=[["--help, -h","Show help"],["--version, -v","Show the CLI version"]],Vn=()=>{let e=[];for(let[o,r]of Object.entries(Ce))for(let[s,a]of Object.entries(r))e.push([`${o} ${s}`,a.summary]);let[t]=Z([...e,...Ot]);return`${["ModelDesk CLI","","Usage: modeldesk <command> <subcommand> [options]","","Commands:",...O(e,{indent:" ",widths:[t]}),"","Global options:",...O(Ot,{indent:" ",widths:[t]}),""].join(`
50
+ `),1)}},Vs=async(e,t)=>{let[n,...r]=e;if(n===void 0||yr.has(n))return t.out(Ks()),0;if(n==="--version"||n==="-v")return t.out(`${hr}
51
+ `),0;let o=Ct[n];if(!o)throw new c(`Unknown command '${n}'. Run 'modeldesk --help' to see available commands.`);let[s,...i]=r;if(s===void 0){let p=Object.keys(o).join(", ");throw new c(`'${n}' requires a subcommand (${p}). Run 'modeldesk ${n} --help'.`)}if(yr.has(s))return t.out(Ys(n,o)),0;let a=o[s];if(!a)throw new c(`Unknown '${n}' command '${s}'. Run 'modeldesk ${n} --help'.`);let d={argv:i,env:process.env,cwd:process.cwd()},m=await a.handler(d);return typeof m=="number"?m:0},wr=[["--help, -h","Show help"],["--version, -v","Show the CLI version"]],Ks=()=>{let e=[];for(let[r,o]of Object.entries(Ct))for(let[s,i]of Object.entries(o))e.push([`${r} ${s}`,i.summary]);let[t]=Me([...e,...wr]);return`${["ModelDesk CLI","","Usage: modeldesk <command> <subcommand> [options]","","Commands:",...se(e,{indent:" ",widths:[t]}),"","Global options:",...se(wr,{indent:" ",widths:[t]}),""].join(`
40
52
  `)}
41
- `},Yn=(e,t)=>{let[n=0]=Z(Object.keys(t).map(r=>[r])),o=[`Usage: modeldesk ${e} <subcommand> [options]`,"","Subcommands:"];for(let[r,s]of Object.entries(t))o.push(` ${r.padEnd(n)}${" ".repeat(2)}${s.summary}`),o.push(` ${" ".repeat(n+2)}modeldesk ${s.usage}`);return o.push(""),`${o.join(`
53
+ `},Ys=(e,t)=>{let[n=0]=Me(Object.keys(t).map(o=>[o])),r=[`Usage: modeldesk ${e} <subcommand> [options]`,"","Subcommands:"];for(let[o,s]of Object.entries(t))r.push(` ${o.padEnd(n)}${" ".repeat(2)}${s.summary}`),r.push(` ${" ".repeat(n+2)}modeldesk ${s.usage}`);return r.push(""),`${r.join(`
42
54
  `)}
43
- `};var Qn=await Dt(process.argv.slice(2));process.exitCode=Qn;
55
+ `};var Xs=await kr(process.argv.slice(2));process.exitCode=Xs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modeldesk/cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Command-line interface (CLI) for the ModelDesk platform",
5
5
  "keywords": [
6
6
  "modeldesk",
@@ -43,15 +43,15 @@
43
43
  "devDependencies": {
44
44
  "@eslint/js": "^10.0.1",
45
45
  "@modeldesk/content-bundle": "*",
46
- "@types/node": "^24.13.2",
46
+ "@types/node": "^24.19.1",
47
47
  "esbuild": "^0.28.2",
48
- "eslint": "^10.9.1",
48
+ "eslint": "^10.12.0",
49
49
  "eslint-config-prettier": "^10.1.5",
50
50
  "eslint-plugin-prettier": "^5.5.6",
51
- "globals": "^17.11.0",
52
- "prettier": "^3.9.5",
51
+ "globals": "^17.13.0",
52
+ "prettier": "^3.9.9",
53
53
  "typescript": "^6.0.3",
54
- "typescript-eslint": "^8.69.0",
55
- "vitest": "^5.0.0"
54
+ "typescript-eslint": "^8.71.1",
55
+ "vitest": "^5.0.3"
56
56
  }
57
57
  }