@modeldesk/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/modeldesk.js +26 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,10 @@ a `MODELDESK_*` environment variable, then a committed `.modeldesk/config.json`,
|
|
|
48
48
|
only) a built-in default. A configured repository therefore runs `modeldesk bundle pull` with no
|
|
49
49
|
arguments, while CI can override any value per build with an environment variable.
|
|
50
50
|
|
|
51
|
+
The CLI operates on the current working directory: run it from the directory that holds
|
|
52
|
+
`.modeldesk/config.json` (typically the repository root). Config in parent directories is not
|
|
53
|
+
searched, and a pull writes its content and manifest under the directory you run it from.
|
|
54
|
+
|
|
51
55
|
```jsonc
|
|
52
56
|
// .modeldesk/config.json (committed to your repository)
|
|
53
57
|
{
|
package/dist/modeldesk.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
3
|
-
import{chmodSync as fe,existsSync as
|
|
4
|
-
`,{mode:he}),fe(this.dir,ue),fe(this.file,he)}};var
|
|
5
|
-
`)},M=async(e
|
|
6
|
-
${c}`),ye(c);let m=await a.waitForCode();return await Te(n,{code:m,redirectUri:a.redirectUri,codeVerifier:r.verifier})}finally{a.close()}},
|
|
7
|
-
`)},k=
|
|
3
|
+
import{chmodSync as fe,existsSync as pt,mkdirSync as ft,readFileSync as gt,writeFileSync as ut}from"node:fs";import{homedir as ht}from"node:os";import{join as ge}from"node:path";var ue=448,he=384,u=class{dir;file;constructor(e=ge(ht(),".modeldesk")){this.dir=e,this.file=ge(e,"credentials.json")}getRefreshToken(e){let o=this.read().hosts[e]?.refreshToken;return typeof o=="string"?o:void 0}saveRefreshToken(e,o){let n=this.read();n.hosts[e]={refreshToken:o},this.write(n)}saveRefreshTokenIfPresent(e,o){o&&this.saveRefreshToken(e,o)}clearHost(e){let o=this.read();return o.hosts[e]?(delete o.hosts[e],this.write(o),!0):!1}read(){if(!pt(this.file))return{hosts:{}};try{let e=JSON.parse(gt(this.file,"utf8"));if(e&&typeof e=="object"&&e.hosts&&typeof e.hosts=="object")return e}catch{}return{hosts:{}}}write(e){ft(this.dir,{recursive:!0,mode:ue}),ut(this.file,`${JSON.stringify(e,null,2)}
|
|
4
|
+
`,{mode:he}),fe(this.dir,ue),fe(this.file,he)}};var yt=t=>{let e=t.split(".");if(e.length===3)try{let o=Buffer.from(e[1],"base64url").toString("utf8"),n=JSON.parse(o);return n&&typeof n=="object"?n:void 0}catch{return}},I=t=>{let e=yt(t);if(e)return e.name&&e.email?`${e.name} <${e.email}>`:e.email??e.name??e.sub};import{spawn as wt}from"node:child_process";import{platform as vt}from"node:process";var ye=t=>{if(!kt(t))return;let{command:e,args:o,verbatim:n}=Ct(t);try{let r=wt(e,o,{stdio:"ignore",detached:!0,windowsVerbatimArguments:n});r.on("error",()=>{}),r.unref()}catch{}},kt=t=>{try{let{protocol:e}=new URL(t);return e==="http:"||e==="https:"}catch{return!1}},Ct=(t,e=vt)=>{switch(e){case"darwin":return{command:"open",args:[t]};case"win32":return{command:"cmd",args:["/c","start",'""',xt(t)],verbatim:!0};default:return{command:"xdg-open",args:[t]}}},xt=t=>t.replace(/[&|<>^()%"]/g,e=>`^${e}`);var P="modeldesk-cli",we="openid",G="/callback";var i=class extends Error{exitCode;constructor(e,o=1){super(e),this.name="CliError",this.exitCode=o}};var w=async t=>{let e=`${t.replace(/\/+$/,"")}/.well-known/openid-configuration`,o;try{o=await fetch(e,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(3e4)})}catch(r){throw new i(`Could not reach the auth server at ${t} (${r.message}).`)}if(!o.ok)throw new i(`OpenID discovery failed at ${e}: ${o.status} ${o.statusText}`);let n=await o.json();if(!n.authorization_endpoint||!n.token_endpoint)throw new i(`OpenID discovery at ${e} did not return the authorization and token endpoints.`);return{authorizationEndpoint:n.authorization_endpoint,tokenEndpoint:n.token_endpoint}};import{createServer as bt}from"node:http";var Tt=3e5,ve=(t,e=Tt)=>new Promise((o,n)=>{let r,s=new Promise((l,f)=>{r={resolve:l,reject:f}}),a=bt((l,f)=>{let p=new URL(l.url??"/","http://127.0.0.1");if(p.pathname!==G){f.writeHead(404,{"Content-Type":"text/plain",Connection:"close"}).end("Not found");return}Pt(p,f,t,r)}),c=setTimeout(()=>r.reject(new i("Timed out waiting for the browser login to complete.")),e);c.unref();let m=!1,d=()=>{m||(m=!0,clearTimeout(c),a.close())};s.then(d,d),a.once("error",l=>n(new i(`Could not start the loopback listener: ${l.message}`))),a.listen(0,"127.0.0.1",()=>{let{port:l}=a.address();o({redirectUri:`http://127.0.0.1:${l}${G}`,waitForCode:()=>s,close:d})})}),Pt=(t,e,o,n)=>{if(t.searchParams.get("state")!==o){e.writeHead(404,{"Content-Type":"text/plain",Connection:"close"}).end("Unexpected request");return}let r=t.searchParams.get("error");if(r){let a=t.searchParams.get("error_description");q(e,"Sign-in failed","Sign-in failed. You can close this window and return to the terminal."),n.reject(new i(`Authorization failed: ${a?`${r} (${a})`:r}`));return}let s=t.searchParams.get("code");if(!s){q(e,"Sign-in failed","No authorization code was returned. You can close this window."),n.reject(new i("Login failed: no authorization code was returned."));return}q(e,"Signed in","You are signed in to the ModelDesk CLI. You can close this window and return to the terminal."),n.resolve(s)},q=(t,e,o)=>{let n=`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${e}</title></head><body style="font-family: system-ui, sans-serif; text-align: center; padding: 3rem;"><h1>${e}</h1><p>${o}</p></body></html>`;t.writeHead(200,{"Content-Type":"text/html; charset=utf-8",Connection:"close"}).end(n)};import{createHash as Et,randomBytes as ke}from"node:crypto";var V=t=>t.toString("base64url"),Ce=()=>{let t=V(ke(32)),e=V(Et("sha256").update(t).digest());return{verifier:t,challenge:e,method:"S256"}},xe=()=>V(ke(16));var L=class extends i{oauthError;constructor(e,o){super(e),this.name="TokenEndpointError",this.oauthError=o}},be=t=>t instanceof L&&t.oauthError==="invalid_grant",Te=(t,e)=>Pe(t,new URLSearchParams({grant_type:"authorization_code",code:e.code,redirect_uri:e.redirectUri,client_id:P,code_verifier:e.codeVerifier})),A=(t,e)=>Pe(t,new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:P})),Pe=async(t,e)=>{let o;try{o=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:e.toString(),signal:AbortSignal.timeout(3e4)})}catch(r){throw new i(`Could not reach the token endpoint (${r.message}).`)}if(!o.ok){let{message:r,code:s}=await $t(o);throw new L(`Token request failed: ${r}`,s)}let n=await o.json();if(!n.access_token)throw new i("The token response did not include an access token.");return{accessToken:n.access_token,refreshToken:n.refresh_token,expiresInSeconds:n.expires_in??0}},$t=async t=>{try{if((t.headers.get("content-type")??"").includes("json")){let o=await t.json();if(o.error)return{message:o.error_description?`${o.error} (${o.error_description})`:o.error,code:o.error}}}catch{}return{message:`${t.status} ${t.statusText}`.trim()}};var _={info:t=>process.stderr.write(`${t}
|
|
5
|
+
`)},M=async(t,e)=>{let{authorizationEndpoint:o,tokenEndpoint:n}=await w(t.issuer),r=Ce(),s=xe(),a=await ve(s);try{let c=St(o,a.redirectUri,r.challenge,s);e.info(`Opening your browser to sign in to ${t.host}...`),e.info(`If it does not open automatically, visit:
|
|
6
|
+
${c}`),ye(c);let m=await a.waitForCode();return await Te(n,{code:m,redirectUri:a.redirectUri,codeVerifier:r.verifier})}finally{a.close()}},St=(t,e,o,n)=>{let r=new URL(t);return r.searchParams.set("response_type","code"),r.searchParams.set("client_id",P),r.searchParams.set("redirect_uri",e),r.searchParams.set("scope",we),r.searchParams.set("state",n),r.searchParams.set("code_challenge",o),r.searchParams.set("code_challenge_method","S256"),r.toString()};var Rt="http://localhost:8080",It="http://localhost:8082",Lt="localhost",At=t=>{let e=t.trim(),o=/:\/\//.test(e)?e:`https://${e}`;try{return new URL(o).hostname}catch{return e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,"").split("/")[0].split(":")[0]}},_t=t=>t==="localhost"||t==="127.0.0.1"||t==="::1"||t==="[::1]",Y=t=>{let e=At(t);return _t(e)?{host:Lt,apiBase:Rt,issuer:It}:{host:e,apiBase:`https://api.${e}`,issuer:`https://auth.${e}`}};import{existsSync as Mt,readFileSync as Ot}from"node:fs";import{join as Dt}from"node:path";var O=".modeldesk",Ft="config.json",D=t=>t.configPath!==void 0,Q=t=>{let e=Dt(t,O,Ft);return Mt(e)?{config:Nt(e),configPath:e,repoRoot:t}:{config:{},repoRoot:t}},Nt=t=>{let e;try{e=Ot(t,"utf8")}catch(r){throw new i(`Could not read ${t}: ${r.message}`)}let o;try{o=JSON.parse(e)}catch(r){throw new i(`${t} is not valid JSON: ${r.message}`)}if(o===null||typeof o!="object"||Array.isArray(o))throw new i(`${t} must contain a JSON object.`);let n=o;return{host:E(n,"host",t),model:E(n,"model",t),stack:E(n,"stack",t),template:E(n,"template",t),version:E(n,"version",t)}},E=(t,e,o)=>{let n=t[e];if(n!==void 0){if(typeof n!="string")throw new i(`${o}: "${e}" must be a string.`);return n}};var X="modeldesk.io",g={host:"MODELDESK_HOST",model:"MODELDESK_MODEL",stack:"MODELDESK_STACK",template:"MODELDESK_TEMPLATE",version:"MODELDESK_VERSION",apiKey:"MODELDESK_API_KEY"},$=(...t)=>t.find(e=>e!==void 0&&e!==""),Z=(t,e,o)=>({host:$(t.host,e[g.host],o.host)??X,model:$(t.model,e[g.model],o.model),stack:$(t.stack,e[g.stack],o.stack),template:$(t.template,e[g.template],o.template),version:$(t.version,e[g.version],o.version)});import{parseArgs as jt}from"node:util";var v=(t,e)=>{try{return jt({args:t,options:e,allowPositionals:!1,strict:!0})}catch(o){throw new i(o.message)}};var Ee=(t,e,o)=>{let n=e!==void 0||o[g.host]!==void 0,r=t.config.host;!n&&D(t)&&r&&r!==X&&process.stderr.write(`note: using host '${r}' from ${t.configPath} (it drives login and API calls).
|
|
7
|
+
`)},k=t=>{let{values:e}=v(t.argv,{host:{type:"string"}}),o=Q(t.cwd),n=Z({host:e.host},t.env,o.config);return Ee(o,e.host,t.env),Y(n.host)},F=(t,e)=>{let o=Q(t.cwd),n=Z(e,t.env,o.config);if(Ee(o,e.host,t.env),!n.model)throw new i(`No model specified. Pass --model, set ${g.model}, or add "model" to .modeldesk/config.json.`);return{instance:Y(n.host),model:n.model,stack:n.stack,template:n.template,version:n.version,loaded:o}},ee=(t,e,o,n)=>{let r=t?.trim();if(!r)throw new i(`No ${e} specified. Pass ${n}, set ${o}, or add "${e}" to .modeldesk/config.json. Run 'modeldesk bundle templates' to list a model's stacks and templates.`);return r};var $e=async t=>{let e=k(t),o=await M(e,_),n=new u;o.refreshToken?n.saveRefreshToken(e.host,o.refreshToken):n.clearHost(e.host);let r=I(o.accessToken);return process.stdout.write(`Logged in to ${e.host}${r?` as ${r}`:""}.
|
|
8
8
|
`),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.
|
|
9
|
-
`),0};var
|
|
10
|
-
`:`No stored credential for ${
|
|
11
|
-
`),0};var
|
|
12
|
-
`)};o(`Host: ${
|
|
13
|
-
`;import{existsSync as
|
|
14
|
-
`),o=n}},done(){e
|
|
15
|
-
`)}}};import{createInterface as
|
|
16
|
-
`)},
|
|
17
|
-
`),
|
|
18
|
-
`),
|
|
19
|
-
`),1;let d=0;for(let l of s)try{let f=await
|
|
9
|
+
`),0};var Se=async t=>{let e=k(t),o=new u().clearHost(e.host);return process.stdout.write(o?`Logged out of ${e.host}.
|
|
10
|
+
`:`No stored credential for ${e.host}.
|
|
11
|
+
`),0};var Re=async t=>{let e=k(t),o=s=>{process.stdout.write(`${s}
|
|
12
|
+
`)};o(`Host: ${e.host}`),o(`API: ${e.apiBase}`);let n=new u,r=n.getRefreshToken(e.host);if(!r)return o("Status: not logged in. Run 'modeldesk auth login'."),1;try{let{tokenEndpoint:s}=await w(e.issuer),a=await A(s,r);n.saveRefreshTokenIfPresent(e.host,a.refreshToken);let c=I(a.accessToken);return o(`Status: logged in${c?` as ${c}`:""} (credential valid).`),0}catch{return o("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 Bt=6e4,N=class{constructor(e,o={}){this.instance=e;this.store=o.store??new u,this.io=o.io??_,this.now=o.now??Date.now}instance;store;io;now;accessToken;accessTokenExpiresAt=0;endpoints;async getAccessToken(){if(this.accessToken&&this.now()<this.accessTokenExpiresAt-Bt)return this.accessToken;let e=this.store.getRefreshToken(this.instance.host);if(e){let o=await this.tryRefresh(e);if(o)return o}return this.login()}async tryRefresh(e){let{tokenEndpoint:o}=await this.getEndpoints();try{return this.apply(await A(o,e))}catch(n){if(be(n))return;throw n}}async login(){return this.apply(await M(this.instance,this.io))}apply(e){return this.accessToken=e.accessToken,this.accessTokenExpiresAt=this.now()+e.expiresInSeconds*1e3,this.store.saveRefreshTokenIfPresent(this.instance.host,e.refreshToken),e.accessToken}async getEndpoints(){return this.endpoints||(this.endpoints=await w(this.instance.issuer)),this.endpoints}};var j=(t,e)=>{if(e[g.apiKey])throw new i("CI API-key auth is not yet available. Unset MODELDESK_API_KEY to authenticate with interactive browser login.");return new N(t)};import{existsSync as te,lstatSync as Ie,realpathSync as Ht}from"node:fs";import{homedir as Ut}from"node:os";import{basename as zt,dirname as Jt,isAbsolute as B,join as Le,relative as oe,resolve as C}from"node:path";var U=(t,e,o)=>{if(B(o))throw new i(`Refusing an absolute path from the bundle manifest: ${o}`);let n=C(t,e),r=C(t,o),s=oe(n,r);if(s===""||s.startsWith("..")||B(s))throw new i(`Refusing a bundle path that escapes the content root: ${o}`);return r},Wt=t=>{try{return Ie(t)}catch{return}},H=t=>{let e=[],o=t;for(;!te(o);){let r=Jt(o);if(r===o)return t;e.unshift(zt(o)),o=r}let n=Ht(o);return e.length>0?Le(n,...e):n},ne=t=>{if(Wt(t)?.isSymbolicLink())throw new i(`Refusing to write through an existing symlink at a managed path: ${t}`)},z=(t,e)=>{let o=H(t),n=H(e),r=oe(o,n);if(r!==""&&(r.startsWith("..")||B(r)))throw new i(`Refusing a path that escapes the content root through a symlink: ${e}`)},re=(t,e)=>{let o=C(t,e);if(o===C(t))throw new i(`The content root resolves to the repository root (${o}); refusing to sync it.`);if(o===C(Ut()))throw new i("The content root resolves to your home directory; refusing to sync it.");if(te(o)){if(Ie(o).isSymbolicLink())throw new i(`The content root ${o} is a symlink; refusing to sync it.`);if(te(Le(o,".git")))throw new i(`The content root ${o} contains a .git directory (it looks like a repo root); refusing.`)}let n=H(C(t)),r=oe(n,H(o));if(r!==""&&(r.startsWith("..")||B(r)))throw new i(`The content root ${o} resolves outside the repository via a symlinked ancestor; refusing.`);return o};var Kt={respec:"data/model"},Ae=t=>{let e=Kt[t];if(!e)throw new i(`The CLI does not know the content root for stack '${t}'.`);return e};var Gt=1500,qt=6e5,Vt=t=>new Promise(e=>setTimeout(e,t)),Yt=t=>t==="COMPLETED"||t==="COMPLETED_WITH_ERRORS"||t==="FAILED",_e=async(t,e,o)=>{let n=await t.postForJson(`/models/${e}/content-bundle/diagrams`,{template:o.template,version:o.version});if(!n.commit)throw new i("The diagram render did not return a resolved commit; cannot pin the pull.");return n},Me=async(t,e,o,n,r,s={})=>{let a=s.sleep??Vt,c=s.now??Date.now,m=c()+qt;for(n.update(0,r);;){let d=await t.getJson(`/models/${e}/export-jobs/${o}`);if(d.progress&&n.update(d.progress.completed,d.progress.total,d.progress.currentItem),Yt(d.status))return n.done(),d;if(c()>=m)throw new i("Timed out waiting for the diagram render to finish.");await a(Gt)}},Oe=(t,e,o,n)=>t.getBytes(`/models/${e}/content-bundle/media`,{path:o,template:n.template,commit:n.commit});var De=t=>{let e=new Map;for(let o of t.fragments??[])o.realization?.path&&e.set(o.elementId,o.realization.path);return e},Fe=(t,e)=>{let o=De(t),n=[];for(let[r,s]of De(e)){let a=o.get(r);a&&a!==s&&n.push({elementId:r,oldPath:a,newPath:s})}return n};var se=t=>{let e=t.payloads??{},o=[];for(let n of t.manifest.fragments??[]){let r=n.realization?.path;if(!r)continue;let s=e[n.address];s!==void 0&&o.push({path:r,content:s})}return o},ie=t=>{let e=[];for(let o of t.fragments??[])for(let n of o.realization?.media??[])e.push(n);return[...new Set(e)]};var Ne=t=>`${JSON.stringify(t,null,2)}
|
|
13
|
+
`;import{existsSync as Qt,mkdirSync as Xt,readFileSync as Zt,writeFileSync as eo}from"node:fs";import{join as je}from"node:path";var Be=(t,e)=>je(t,O,`${e}.manifest.json`),He=(t,e)=>{let o=Be(t,e);if(!Qt(o))return;let n;try{n=JSON.parse(Zt(o,"utf8"))}catch{return}return to(n)?n:void 0},to=t=>{if(t===null||typeof t!="object"||Array.isArray(t))return!1;let{fragments:e}=t;return e===void 0?!0:Array.isArray(e)&&e.every(o=>o!==null&&typeof o=="object")},Ue=(t,e,o)=>{Xt(je(t,O),{recursive:!0}),eo(Be(t,e),Ne(o))},ze=t=>{let e=[];for(let o of t.fragments??[]){o.realization?.path&&e.push(o.realization.path);for(let n of o.realization?.media??[])e.push(n)}return e};import{existsSync as Je,mkdirSync as We,readdirSync as oo,rmSync as no,writeFileSync as Ke}from"node:fs";import{dirname as Ge,join as S,relative as ro,sep as so}from"node:path";var qe=(t,e)=>{let o=S(t,e);if(!Je(o))return[];let n=[],r=s=>{for(let a of oo(s,{withFileTypes:!0})){let c=S(s,a.name);a.isDirectory()?r(c):a.isFile()&&n.push(ro(t,c).split(so).join("/"))}};return r(o),n},Ve=(t,e,o)=>{let n=S(t,e);for(let r of o){let s=U(t,e,r.path);ne(s),z(n,s),We(Ge(s),{recursive:!0}),Ke(s,r.content)}},Ye=(t,e,o,n)=>{let r=S(t,e),s=U(t,e,o);ne(s),z(r,s),We(Ge(s),{recursive:!0}),Ke(s,n)},Qe=(t,e,o)=>{let n=S(t,e);for(let r of o){let s=U(t,e,r);z(n,s),Je(s)&&no(s)}};var io=100,Ze=t=>{if(t.newTextPaths.length===0&&!t.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 e=Xe([...t.newTextPaths,...t.newMediaPaths]).sort(),o=new Set(e),n=new Set(t.existingFiles),r=e.filter(l=>n.has(l)),s=t.priorManifestPaths!==void 0?"manifest-tracked":"full-sync",a=t.priorManifestPaths??t.existingFiles,c=Xe(a).filter(l=>!o.has(l)).sort(),m=c.length>0&&!t.adopted,d=c.length>0&&(c.length>e.length||c.length>io);return{writes:e,overwrites:r,orphanCandidates:c,mode:s,adoptionNeeded:m,outsized:d}},ae=(t,e)=>{if(t.orphanCandidates.length===0)return{deletes:[],warnings:[]};if(t.adoptionNeeded&&!e.adopt)return{deletes:[],warnings:[`${t.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(t.outsized&&!e.yes)throw new i(`This pull would remove ${t.orphanCandidates.length} file(s), more than the ${t.writes.length} it writes. That often signals a wrong model or format. Nothing was changed. Re-run with --yes if intended.`);return{deletes:t.orphanCandidates,warnings:[]}},Xe=t=>[...new Set(t)];var ao=6e4,x=class{constructor(e,o){this.apiBase=e;this.tokens=o}apiBase;tokens;async getJson(e,o){return await(await this.request(e,{method:"GET"},o)).json()}async postForJson(e,o){return await(await this.request(e,{method:"POST"},o)).json()}async getBytes(e,o){let n=await this.request(e,{method:"GET",headers:{Accept:"image/png"}},o);return new Uint8Array(await n.arrayBuffer())}async request(e,o,n){let r=await this.tokens.getAccessToken(),s=co(this.apiBase,e,n),a;try{a=await fetch(s,{...o,headers:{Authorization:`Bearer ${r}`,Accept:"application/json",...o.headers??{}},signal:AbortSignal.timeout(ao)})}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 mo(a,e);return a}},co=(t,e,o)=>{let n=new URL(e,t);if(o)for(let[r,s]of Object.entries(o))s!==void 0&&n.searchParams.set(r,s);return n.toString()},mo=async(t,e)=>{let o=`${t.status} ${t.statusText}`.trim();try{if((t.headers.get("content-type")??"").includes("json")){let r=await t.json(),s=r.detail??r.title;s&&(o=`${t.status} ${s}`)}}catch{}return t.status===401?new i(`Authentication failed (${o}). Run 'modeldesk auth login' and try again.`):t.status===403?new i(`Not authorized (${o}). Your account may lack access to this model.`):new i(`Request to ${e} failed: ${o}`)};var lo=(t,e)=>{let o=e>0?Math.min(1,t/e):0,n=Math.round(o*24);return`[${"#".repeat(n)}${"-".repeat(24-n)}]`},et=(t,e=o=>{process.stderr.write(o)})=>{let o=-1;return{update(n,r,s){if(t){let a=s?` ${s}`:"";e(`\r${lo(n,r)} ${n}/${r}${a}\x1B[K`)}else if(n!==o){let a=s?` (${s})`:"";e(`Rendering diagrams: ${n}/${r}${a}
|
|
14
|
+
`),o=n}},done(){t&&e(`
|
|
15
|
+
`)}}};import{createInterface as po}from"node:readline";var J=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),ce=async t=>{if(!J())return!1;let e=po({input:process.stdin,output:process.stderr});try{let o=await new Promise(n=>e.question(`${t} [y/N] `,n));return/^y(es)?$/i.test(o.trim())}finally{e.close()}};var h=t=>{process.stdout.write(t)},y=t=>{process.stderr.write(t)},tt=t=>{for(let e of t)y(` ${e}
|
|
16
|
+
`)},nt=async t=>{let{values:e}=v(t.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"}}),o=F(t,{host:e.host,model:e.model,stack:e.stack,template:e.template,version:e.version}),n={noDiagrams:!!e["no-diagrams"],dryRun:!!e["dry-run"],allowEmpty:!!e["allow-empty"],adopt:!!e.adopt,yes:!!e.yes},r=new x(o.instance.apiBase,j(o.instance,t.env)),s=ee(o.stack,"stack",g.stack,"--stack"),a=ee(o.template,"template",g.template,"--template"),m=!n.noDiagrams&&!n.dryRun?await _e(r,o.model,{template:a,version:o.version}):void 0,d=m?.commit,l=await r.getJson(`/models/${o.model}/content-bundle`,{template:a,commit:d,version:d?void 0:o.version});if(l.manifest.stack!==s)throw new i(`Requested stack '${s}', but template '${a}' renders under stack '${l.manifest.stack}'. Run 'modeldesk bundle templates' to see valid stack/template pairs.`);let f=Ae(s);re(o.loaded.repoRoot,f);let p=se(l),b=ie(l.manifest),T=He(o.loaded.repoRoot,s),K=Ze({newTextPaths:p.map(lt=>lt.path),newMediaPaths:b,priorManifestPaths:T?ze(T):void 0,existingFiles:qe(o.loaded.repoRoot,f),adopted:T!==void 0||D(o.loaded),allowEmpty:n.allowEmpty}),me=T?Fe(T,l.manifest):[],le={stack:s,version:l.manifest.stackVersion};if(n.dryRun)return ot(o,a,le,d,K,void 0,me,b.length,!0),0;let pe=await fo(K,n);return ot(o,a,le,d,K,pe,me,b.length,!1),re(o.loaded.repoRoot,f),Ve(o.loaded.repoRoot,f,p),Qe(o.loaded.repoRoot,f,pe.deletes),Ue(o.loaded.repoRoot,s,l.manifest),b.length===0||!m?0:go(r,o,a,d,m,b,f)},fo=async(t,e)=>{let{adopt:o,yes:n}=e;if(t.orphanCandidates.length===0)return ae(t,{adopt:o,yes:n});let r=J();return t.adoptionNeeded&&!o&&(y(`The content root has ${t.orphanCandidates.length} file(s) the CLI does not track:
|
|
17
|
+
`),tt(t.orphanCandidates),r&&(o=await ce("Adopt this directory and allow the CLI to remove these files?"))),t.outsized&&!n&&(o||!t.adoptionNeeded)&&(y(`This pull would remove ${t.orphanCandidates.length} file(s), more than the ${t.writes.length} it writes:
|
|
18
|
+
`),tt(t.orphanCandidates),r&&(n=await ce("Proceed with removing them?"))),ae(t,{adopt:o,yes:n})},go=async(t,e,o,n,r,s,a)=>{let c=et(J()),m=await Me(t,e.model,r.id,c,s.length);if(m.status==="FAILED")return y(`warning: the diagram render failed. Text fragments are written; media was not fetched. Re-run to retry.
|
|
19
|
+
`),1;let d=0;for(let l of s)try{let f=await Oe(t,e.model,l,{template:o,commit:n});Ye(e.loaded.repoRoot,a,l,f)}catch(f){d+=1,y(`warning: could not fetch media ${l}: ${f instanceof Error?f.message:String(f)}
|
|
20
20
|
`)}return m.status==="COMPLETED_WITH_ERRORS"||d>0?(y(`warning: ${d} media file(s) could not be written; the pull is re-runnable.
|
|
21
|
-
`),1):0},
|
|
21
|
+
`),1):0},ot=(t,e,o,n,r,s,a,c,m)=>{let d=n?`commit ${n}`:t.version?`version ${t.version}`:"HEAD",l=`${o.stack} ${o.version}`;h(`${m?"Dry run: ":""}Pulling ${t.model} (stack ${l}, template ${e}) at ${d} into ${t.instance.host}.
|
|
22
22
|
`),h(` Write: ${r.writes.length} file(s)${r.overwrites.length?` (${r.overwrites.length} overwritten)`:""}
|
|
23
23
|
`),h(` Diagrams: ${c}
|
|
24
24
|
`);let f=m?r.orphanCandidates:s?.deletes??[];h(` Remove: ${f.length} file(s) [${r.mode}]
|
|
@@ -28,14 +28,14 @@ import{chmodSync as fe,existsSync as gt,mkdirSync as ut,readFileSync as ht,write
|
|
|
28
28
|
`);else for(let p of s?.warnings??[])y(`warning: ${p}
|
|
29
29
|
`);for(let p of a)h(` moved: ${p.oldPath} -> ${p.newPath}
|
|
30
30
|
`);if(m)for(let p of r.writes)h(` write ${p}
|
|
31
|
-
`)};var W=
|
|
32
|
-
`),0;let d=
|
|
33
|
-
`),0;let f=l.map(p=>[p.stack??"",p.key??p.id,p.name]);for(let p of R([
|
|
34
|
-
`);return 0};var de={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:
|
|
35
|
-
`),o.exitCode):(
|
|
36
|
-
`),1)}},
|
|
37
|
-
`),0;let r=de[o];if(!r)throw new i(`Unknown command '${o}'. Run 'modeldesk --help' to see available commands.`);let[s,...a]=n;if(s===void 0){let l=Object.keys(r).join(", ");throw new i(`'${o}' requires a subcommand (${l}). Run 'modeldesk ${o} --help'.`)}if(
|
|
31
|
+
`)};var W=t=>{let e=[];for(let o of t)o.forEach((n,r)=>{e[r]=Math.max(e[r]??0,n.length)});return e},R=(t,{indent:e="",gap:o=2,widths:n}={})=>{let s=W(t).map((c,m)=>n?.[m]??c),a=" ".repeat(o);return t.map(c=>e+c.map((m,d)=>d===c.length-1?m:m.padEnd(s[d])).join(a))};var uo=["STACK","TEMPLATE","NAME"],rt=async t=>{let{values:e}=v(t.argv,{host:{type:"string"},model:{type:"string"},stack:{type:"string"}}),o=F(t,{host:e.host,model:e.model}),n=new x(o.instance.apiBase,j(o.instance,t.env)),r=await n.getJson(`/models/${o.model}`),s=(r.metamodelExtensions??[]).map(p=>p.id),a=s.length>0?{metamodelExtensions:s.join(",")}:void 0,{items:c}=await n.getJson(`/metamodels/${r.metamodel.id}/templates`,a),m=c??[];if(m.length===0)return process.stdout.write(`This model has no document templates.
|
|
32
|
+
`),0;let d=e.stack,l=d?m.filter(p=>p.stack===d):m;if(l.length===0)return process.stdout.write(`No document templates for stack '${d}'.
|
|
33
|
+
`),0;let f=l.map(p=>[p.stack??"",p.key??p.id,p.name]);for(let p of R([uo,...f]))process.stdout.write(`${p}
|
|
34
|
+
`);return 0};var de={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:nt},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:$e},logout:{summary:"Remove the stored credential for the active host",usage:"auth logout [--host <host>]",handler:Se},status:{summary:"Show the active host, logged-in identity, and token validity",usage:"auth status [--host <host>]",handler:Re}}};var st={name:"@modeldesk/cli",version:"0.1.1",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:">=20"},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.1",eslint:"^10.7.0","eslint-config-prettier":"^10.1.5","eslint-plugin-prettier":"^5.5.6",globals:"^17.7.0",prettier:"^3.9.5",typescript:"^6.0.3","typescript-eslint":"^8.64.0",vitest:"^4.1.9"}};var it=st.version;var yo={out:t=>process.stdout.write(t),err:t=>process.stderr.write(t)},ct=new Set(["--help","-h","help"]),mt=async(t,e=yo)=>{try{return await wo(t,e)}catch(o){return o instanceof i?(e.err(`error: ${o.message}
|
|
35
|
+
`),o.exitCode):(e.err(`error: ${o instanceof Error?o.message:String(o)}
|
|
36
|
+
`),1)}},wo=async(t,e)=>{let[o,...n]=t;if(o===void 0||ct.has(o))return e.out(vo()),0;if(o==="--version"||o==="-v")return e.out(`${it}
|
|
37
|
+
`),0;let r=de[o];if(!r)throw new i(`Unknown command '${o}'. Run 'modeldesk --help' to see available commands.`);let[s,...a]=n;if(s===void 0){let l=Object.keys(r).join(", ");throw new i(`'${o}' requires a subcommand (${l}). Run 'modeldesk ${o} --help'.`)}if(ct.has(s))return e.out(ko(o,r)),0;let c=r[s];if(!c)throw new i(`Unknown '${o}' command '${s}'. Run 'modeldesk ${o} --help'.`);let m={argv:a,env:process.env,cwd:process.cwd()},d=await c.handler(m);return typeof d=="number"?d:0},dt=[["--help, -h","Show help"],["--version, -v","Show the CLI version"]],vo=()=>{let t=[];for(let[n,r]of Object.entries(de))for(let[s,a]of Object.entries(r))t.push([`${n} ${s}`,a.summary]);let[e]=W([...t,...dt]);return`${["ModelDesk CLI","","Usage: modeldesk <command> <subcommand> [options]","","Commands:",...R(t,{indent:" ",widths:[e]}),"","Global options:",...R(dt,{indent:" ",widths:[e]}),""].join(`
|
|
38
38
|
`)}
|
|
39
|
-
`},
|
|
39
|
+
`},ko=(t,e)=>{let[o=0]=W(Object.keys(e).map(r=>[r])),n=[`Usage: modeldesk ${t} <subcommand> [options]`,"","Subcommands:"];for(let[r,s]of Object.entries(e))n.push(` ${r.padEnd(o)}${" ".repeat(2)}${s.summary}`),n.push(` ${" ".repeat(o+2)}modeldesk ${s.usage}`);return n.push(""),`${n.join(`
|
|
40
40
|
`)}
|
|
41
|
-
`};var
|
|
41
|
+
`};var Co=await mt(process.argv.slice(2));process.exitCode=Co;
|