@mynthio/cli 0.0.16 → 0.0.17

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 (3) hide show
  1. package/README.md +13 -0
  2. package/dist/bin.js +7 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -25,6 +25,19 @@ mynth image generate --prompt "A cinematic product photo of a glass keyboard"
25
25
  mynth image generate -p "A watercolor city skyline" --size 16:9 --count 2
26
26
  ```
27
27
 
28
+ ### Cost and balance
29
+
30
+ Check spend before a batch run:
31
+
32
+ ```bash
33
+ mynth balance # balance, reserved, available (+ key spending limit)
34
+ mynth image generate -p "A neon koi pond" -m black-forest-labs/flux.1-dev -c 10 --dry-run
35
+ ```
36
+
37
+ `--dry-run` validates the request server-side and prints the estimated cost without generating
38
+ anything. Estimates for `--model auto` are an upper bound. Add `--json` to either command for
39
+ machine-readable output.
40
+
28
41
  ### Tasks
29
42
 
30
43
  Async workflows: fire a generation with `--async`, do other work, then wait for the result.
package/dist/bin.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import {Command,Option,Help}from'commander';import*as b from'cross-keychain';import {readFile,mkdir,writeFile,chmod,rm,stat}from'fs/promises';import {homedir}from'os';import {resolve,join,extname,basename}from'path';import {z}from'zod';import pt from'chalk';import _n from'ora';var v=class extends Error{_tag="MynthCliError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},d=class extends Error{_tag="CliUsageError";constructor(e){super(e),this.name=this._tag;}},T=class extends Error{_tag="NotAuthenticatedError";constructor(e={}){super(e.reason??"not authenticated"),this.name=this._tag;}},A=class extends Error{_tag="CredentialsStoreError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},S=class extends Error{_tag="WorkOSError";code;status;cause;constructor(e){super(e.message),this.name=this._tag,this.code=e.code,this.status=e.status,this.cause=e.cause;}},$=class extends Error{_tag="AuthorizationPendingError";slowDown;constructor(e){super("authorization pending"),this.name=this._tag,this.slowDown=e.slowDown;}},E=class extends Error{_tag="AuthorizationExpiredError";constructor(){super("authorization expired"),this.name=this._tag;}},O=class extends Error{_tag="AuthorizationDeniedError";constructor(){super("authorization denied"),this.name=this._tag;}},w={error:1,usage:2,auth:3,insufficientCredits:4,moderation:5,rateLimited:6},_t={UNAUTHORIZED:w.auth,VALIDATION_ERROR:w.usage,INSUFFICIENT_BALANCE:w.insufficientCredits,RESTRICTED_CONTENT:w.moderation},je=t=>{if(t instanceof d)return w.usage;if(t instanceof T)return w.auth;if(t instanceof l){let n=t.code!==void 0?_t[t.code]:void 0;return n!==void 0?n:t.status===401||t.status===403?w.auth:t.status===429?w.rateLimited:w.error}let e=t.code;return typeof e=="string"&&e.startsWith("commander.")?w.usage:w.error},ue=t=>{let e=t.result?.images,n=[...(t.errors??[]).map(s=>s.code),...(e??[]).map(s=>s.error?.code)].filter(s=>typeof s=="string");return n.find(s=>s==="RESTRICTED_CONTENT")??n[0]},Me=t=>ue(t)==="RESTRICTED_CONTENT"?w.moderation:w.error,l=class extends Error{_tag="MynthApiError";status;code;cause;constructor(e){super(e.message),this.name=this._tag,this.status=e.status,this.code=e.code,this.cause=e.cause;}};var xt=6e4,W=t=>t?{user:t}:{},me=(t,e)=>new T({reason:e instanceof Error?`${t}: ${e.message}`:t}),q=class{constructor(e,n,s){this.config=e;this.store=n;this.workos=s;this.envApiKey=e.apiKeyEnvOverride,this.envApiKeySet=this.envApiKey!==void 0&&this.envApiKey.length>0;}envApiKeySet;envApiKey;async resolve(){if(this.envApiKeySet)return {kind:"api_key",apiKey:this.envApiKey,source:"env"};let e;try{e=await this.store.get();}catch(s){throw me("could not read credentials",s)}if(e===void 0)throw new T({reason:"no credentials configured"});if(e.kind==="api_key")return {kind:"api_key",apiKey:e.api_key,source:"stored"};let n=await this.refreshIfNeeded(e);return {kind:"oauth",accessToken:n.access_token,...W(n.user)}}async status(){if(this.envApiKeySet)return {kind:"env",source:"MYNTH_API_KEY"};let e;try{e=await this.store.get();}catch{e=void 0;}return e===void 0?{kind:"none"}:e.kind==="api_key"?{kind:"api_key"}:{kind:"oauth",expiresAt:e.expires_at,...W(e.user)}}async setApiKey(e){await this.store.set({kind:"api_key",api_key:e});}async saveOAuth(e){await this.store.set({kind:"oauth",access_token:e.accessToken,refresh_token:e.refreshToken,expires_at:e.expiresAt,...W(e.user)});}async logout(){await this.store.clear();}async refreshIfNeeded(e){if(e.expires_at-Date.now()>xt)return e;let n;try{n=await this.workos.refresh(e.refresh_token);}catch(a){throw me("token refresh failed",a)}let s={kind:"oauth",access_token:n.token.access_token,refresh_token:n.token.refresh_token,expires_at:n.expiresAt,...W(n.token.user??e.user)};try{await this.store.set(s);}catch(a){throw me("could not persist refreshed token",a)}return s}};var De=()=>{let t=process.env.MYNTH_API_KEY;return {mynthApiUrl:process.env.MYNTH_API_URL??"https://api.mynth.io",mynthDocsUrl:process.env.MYNTH_DOCS_URL??"https://docs.mynth.io",...t!==void 0?{apiKeyEnvOverride:t}:{}}};var Le=z.object({id:z.string(),email:z.string(),first_name:z.string().nullable().optional(),last_name:z.string().nullable().optional()}),Ue=z.object({device_code:z.string(),user_code:z.string(),verification_uri:z.string(),verification_uri_complete:z.string().optional(),expires_in:z.number(),interval:z.number().optional()}),Fe=z.object({access_token:z.string(),refresh_token:z.string(),user:Le.optional(),organization_id:z.string().optional()}),Je=z.object({error:z.string().optional(),error_description:z.string().optional(),message:z.string().optional(),code:z.string().optional()}),Tt=z.object({kind:z.literal("oauth"),access_token:z.string(),refresh_token:z.string(),expires_at:z.number(),user:Le.optional()}),bt=z.object({kind:z.literal("api_key"),api_key:z.string()}),ze=z.union([Tt,bt]),H=z.lazy(()=>z.union([z.string(),z.number(),z.boolean(),z.null(),z.array(H),z.record(H)])),Ke=z.object({data:z.object({urls:z.array(z.string())})}),Rt=z.union([z.object({status:z.literal("success"),url:z.string(),level:z.string()}),z.object({status:z.literal("failed"),url:z.string(),error:z.object({code:z.string()})})]),We=z.object({data:z.object({task:z.object({id:z.string(),status:z.literal("completed"),cost:z.string()}),results:z.array(Rt)})}),qe=z.object({data:z.object({taskId:z.string(),access:z.object({publicAccessToken:z.string()}).optional()})}),B=z.object({data:z.object({status:z.union([z.literal("pending"),z.literal("completed"),z.literal("failed")])})}),Ct=z.object({id:z.string(),type:z.union([z.literal("image.generate"),z.literal("image.rate")]),status:z.union([z.literal("pending"),z.literal("completed"),z.literal("failed")]),userId:z.string(),apiKeyId:z.string().nullable(),cost:z.string().nullable(),request:H,result:H,errors:z.array(z.object({code:z.string()})).nullable().optional(),createdAt:z.string(),updatedAt:z.string()}),Y=z.object({data:Ct}),$t=z.object({id:z.string(),type:z.string(),status:z.string(),cost:z.string().nullable(),createdAt:z.string(),updatedAt:z.string()}),He=z.object({data:z.array($t)}),Et=z.object({perImage:z.object({base:z.string(),"4k":z.string().optional()}),perInput:z.string().optional()}),Ot=z.object({id:z.string(),displayName:z.string().nullable(),pricing:Et.nullable()}),Be=z.object({data:z.array(Ot)});var pe="mynth-cli",ge="default",Ut="credentials.json",Ft=t=>{let e=t?.name;return e==="NoKeyringError"||e==="InitError"},G=async(t,e)=>{try{return {available:true,value:await t()}}catch(n){if(Ft(n))return {available:false};throw new A({message:e,cause:n})}},Ye=t=>{let e;try{e=JSON.parse(t);}catch(s){throw new A({message:"credentials JSON parse failed",cause:s})}let n=ze.safeParse(e);if(!n.success)throw new A({message:"credentials shape invalid",cause:n.error});return n.data},Ge=t=>JSON.stringify(t),Jt=()=>{let t=process.env.XDG_CONFIG_HOME,e=t&&t.length>0?t:join(homedir(),".config");return join(e,"mynth")},Ve=async t=>{try{return await stat(t),true}catch{return false}},V=class{filePath;dir;constructor(){this.dir=Jt(),this.filePath=join(this.dir,Ut);}async get(){let e=await G(()=>b.getPassword(pe,ge),"keychain get failed");if(e.available)return e.value===null?void 0:Ye(e.value);if(await Ve(this.filePath))try{return Ye(await readFile(this.filePath,"utf8"))}catch(n){throw n instanceof A?n:new A({message:"read credentials file failed",cause:n})}}async set(e){if((await G(()=>b.setPassword(pe,ge,Ge(e)),"keychain set failed")).available){await this.deleteFileSilently();return}await mkdir(this.dir,{recursive:true}).catch(s=>{throw new A({message:"create config dir failed",cause:s})}),await writeFile(this.filePath,Ge(e),"utf8").catch(s=>{throw new A({message:"write credentials file failed",cause:s})}),await chmod(this.filePath,384).catch(()=>{});}async clear(){await G(()=>b.deletePassword(pe,ge),"keychain delete failed").catch(()=>{}),await this.deleteFileSilently();}async usingKeychain(){let e=await G(()=>b.getKeyring(),"keychain probe failed");return e.available&&e.value!==null}async deleteFileSilently(){await Ve(this.filePath)&&await rm(this.filePath).catch(e=>{throw new A({message:"delete credentials file failed",cause:e})});}};var zt=t=>t.kind==="api_key"?t.apiKey:t.accessToken,R=async t=>{try{return await t.json()}catch(e){throw new l({message:`invalid JSON response: ${e.message}`,status:t.status,cause:e})}},N=async t=>{try{return await t.text()}catch{return ""}},_=async(t,e)=>{if(t.status>=200&&t.status<300)return;let n=await N(t),s;try{let a=JSON.parse(n);typeof a.code=="string"&&(s=a.code);}catch{}throw new l({message:`${e} failed (${t.status}): ${n||"no body"}`,status:t.status,...s!==void 0?{code:s}:{}})},X=class{constructor(e,n){this.auth=n;this.baseUrl=e.mynthApiUrl;}baseUrl;cachedAuth;async execute(e,n={}){let s=await this.attempt(e,n,false);return s.status!==401?s:this.attempt(e,n,true)}async executePublic(e,n={}){try{return await fetch(`${this.baseUrl}${e}`,n)}catch(s){throw new l({message:`request failed: ${s.message}`,status:0,cause:s})}}async attempt(e,n,s){let a=await this.getAuth(s),o=new Headers(n.headers);o.set("Authorization",`Bearer ${zt(a)}`);try{return await fetch(`${this.baseUrl}${e}`,{...n,headers:o})}catch(c){throw new l({message:`request failed: ${c.message}`,status:0,cause:c})}}async getAuth(e){return e&&(this.cachedAuth=void 0),this.cachedAuth!==void 0?this.cachedAuth:(this.cachedAuth=await this.auth.resolve(),this.cachedAuth)}};var Kt=t=>{if(t.length===0)return "no response body";try{let e=JSON.parse(t);if(typeof e.error=="string"&&e.error.length>0)return e.error}catch{}return t.length>500?`${t.slice(0,500)}\u2026`:t},Xe=async(t,e)=>{if(t.ok)return;let n=Kt(await N(t));throw new l({message:`${e} failed (${t.status}): ${n}`,status:t.status})},Ze=async(t,e,n)=>{try{return await fetch(t,n)}catch(s){throw new l({message:`${e} failed: ${s.message}`,status:0,cause:s})}},Qe=async(t,e)=>{try{return await t.text()}catch(n){throw new l({message:`${e} failed while reading the response: ${n.message}`,status:t.status,cause:n})}},Wt=t=>{let e=t.trim();if(e.length===0)throw new d("documentation path must not be empty");if(e.length>2048)throw new d("documentation path is too long");if(e.startsWith("//")||e.includes("://"))throw new d("documentation path must be a path, not a URL");if(e.includes("?")||e.includes("#")||e.includes("\\"))throw new d("documentation path must not contain a query, fragment, or backslash");let n=e.startsWith("/")?e.slice(1):e;if(n.endsWith(".md"))throw new d("documentation path must not include the .md suffix");let s=n.split("/");if(s.some(a=>a.length===0||a==="."||a===".."||!/^[A-Za-z0-9._~%-]+$/.test(a)))throw new d("documentation path contains an invalid segment");return s.join("/")},Z=class{docsUrl;constructor(e){this.docsUrl=e.mynthDocsUrl.replace(/\/$/,"");}async get(e){let n=Wt(e),s=n.split("/").map(encodeURIComponent).join("/"),a=await Ze(`${this.docsUrl}/${s}.md`,`documentation page fetch for ${n}`);return await Xe(a,`documentation page fetch for ${n}`),{path:n,content:await Qe(a,`documentation page fetch for ${n}`)}}async list(){let e=await Ze(`${this.docsUrl}/llms.txt`,"documentation index fetch");return await Xe(e,"documentation index fetch"),Qe(e,"documentation index fetch")}};var M=10,D=10,L=2,U=7,et=300*1e3,Zt=12e3,Qt=2500,en=5e3,tn={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},nn=t=>new Promise(e=>setTimeout(e,t)),sn=(t,e,n)=>{try{let a=new URL(t).pathname.split("/").filter(Boolean).pop();if(a&&a.length>0)return decodeURIComponent(a)}catch{}return `${e}-${n}`},an=async t=>{let e=extname(t).toLowerCase(),n=tn[e];if(!n)throw new l({message:`unsupported image extension "${e}" for ${t} (allowed: .jpg, .jpeg, .png, .webp)`,status:0});let s;try{s=await readFile(t);}catch(o){throw new l({message:`could not read ${t}: ${o.message}`,status:0,cause:o})}let a=s.buffer.slice(s.byteOffset,s.byteOffset+s.byteLength);return new File([new Uint8Array(a)],basename(t),{type:n})},j=async(t,e,n)=>{let s=e.safeParse(await R(t));if(!s.success)throw new l({message:n,status:t.status,cause:s.error});return s.data},rn=async(t,e,n)=>{let s=Array.from({length:t.length}),a=0,o=async()=>{for(;;){let c=a++,h=t[c];if(h===void 0)return;s[c]=await n(h,c);}};return await Promise.all(Array.from({length:Math.min(e,t.length)},o)),s},Q=class{constructor(e){this.api=e;}async upload(e){if(e.length===0)throw new l({message:"no files to upload",status:0});if(e.length>M)throw new l({message:`too many files: ${e.length} (max ${M})`,status:0});let n=await Promise.all(e.map(an)),s=new FormData;for(let c of n)s.append("images",c);let a=await this.api.execute("/image/upload",{method:"POST",body:s});await _(a,"upload");let o=await j(a,Ke,"invalid upload response");return e.map((c,h)=>({path:c,url:o.data.urls[h]}))}async rate(e){if(e.urls.length===0)throw new l({message:"no image URLs to rate",status:0});if(e.urls.length>D)throw new l({message:`too many images: ${e.urls.length} (max ${D})`,status:0});if(e.levels!==void 0&&(e.levels.length<L||e.levels.length>U))throw new l({message:`levels must have between ${L} and ${U} items (got ${e.levels.length})`,status:0});let n=e.levels!==void 0?{urls:e.urls,mode:"custom",levels:e.levels}:{urls:e.urls,mode:"nsfw_sfw"},s=await this.api.execute("/image/rate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});return await _(s,"rate"),(await j(s,We,"invalid rate response")).data}async generate(e){let n=e.requestPat?{...e.request,access:{...e.request.access,pat:{enabled:true}}}:e.request,s=await this.api.execute("/image/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});await _(s,"generate");let a=await j(s,qe,"invalid generate response"),o=a.data.access?.publicAccessToken;return {taskId:a.data.taskId,...o!==void 0?{pat:o}:{}}}async waitForTask(e,n){let s=Date.now();for(;;){let a=Date.now()-s;if(a>=et)throw new l({message:`task ${e} polling timed out after ${et}ms`,status:0});let o=await this.getTaskStatus(e,n);if(o==="completed")return this.getTaskDetails(e);if(o==="failed"){let h=await this.getTaskDetails(e).catch(()=>{}),y=h!==void 0?ue(h):void 0;throw new l({message:`task ${e} failed during generation${y!==void 0?` (${y})`:""}`,status:0,...y!==void 0?{code:y}:{}})}let c=a<Zt?Qt:en;await nn(c+Math.floor(Math.random()*500));}}async getTaskDetails(e){let n=await this.api.execute(`/tasks/${e}`);return await _(n,"task details"),(await j(n,Y,"invalid task details response")).data}async downloadImages(e){let n=resolve(e.destinationDir);try{await mkdir(n,{recursive:true});}catch(s){throw new l({message:`could not create output directory ${n}: ${s.message}`,status:0,cause:s})}return rn(e.urls,4,async(s,a)=>{let o;try{o=await fetch(s);}catch(f){throw new l({message:`download failed for ${s}: ${f.message}`,status:0,cause:f})}if(o.status<200||o.status>=300)throw new l({message:`download failed for ${s} (${o.status})`,status:o.status});let c;try{c=await o.arrayBuffer();}catch(f){throw new l({message:`could not read body for ${s}: ${f.message}`,status:o.status,cause:f})}let h=sn(s,e.taskId,a),y=join(n,h);try{await writeFile(y,new Uint8Array(c));}catch(f){throw new l({message:`could not write ${y}: ${f.message}`,status:0,cause:f})}return y})}async getTaskStatus(e,n){let s=`/tasks/${e}/status`,a;if(n!==void 0)try{a=await fetch(`${this.api.baseUrl}${s}`,{headers:{Authorization:`Bearer ${n}`}});}catch(c){throw new l({message:`task status request failed: ${c.message}`,status:0,cause:c})}else a=await this.api.execute(s);return await _(a,"task status"),(await j(a,B,"invalid task status response")).data.status}};var ee=class{constructor(e){this.api=e;}async list(){let e=await this.api.executePublic("/models");if(e.status<200||e.status>=300){let s=await N(e);throw new l({message:`models fetch failed (${e.status}): ${s||"no body"}`,status:e.status})}let n=Be.safeParse(await R(e));if(!n.success)throw new l({message:"invalid models response",status:e.status,cause:n.error});return n.data.data}};var on=300*1e3,cn=12e3,ln=2500,dn=5e3,un=t=>new Promise(e=>setTimeout(e,t)),te=class{constructor(e){this.api=e;}async getTask(e){let n=await this.api.execute(`/tasks/${e}`);await _(n,"task fetch");let s=Y.safeParse(await R(n));if(!s.success)throw new l({message:"invalid task response",status:n.status,cause:s.error});return s.data.data}async listTasks(e={}){let n=new URLSearchParams;e.limit!==void 0&&n.set("limit",String(e.limit)),e.after!==void 0&&n.set("after",e.after);let s=n.size>0?`?${n}`:"",a=await this.api.execute(`/tasks${s}`);await _(a,"task list");let o=He.safeParse(await R(a));if(!o.success)throw new l({message:"invalid task list response",status:a.status,cause:o.error});return o.data.data}async getTaskStatus(e){let n=await this.api.execute(`/tasks/${e}/status`);await _(n,"task status");let s=B.safeParse(await R(n));if(!s.success)throw new l({message:"invalid task status response",status:n.status,cause:s.error});return s.data.data.status}async waitForTask(e,n=on){let s=Date.now();for(;;){if(await this.getTaskStatus(e)!=="pending")return this.getTask(e);let o=Date.now()-s;if(o>=n)throw new l({message:`task ${e} did not complete within ${Math.round(n/1e3)}s`,status:0});let c=o<cn?ln:dn;await un(c+Math.floor(Math.random()*500));}}};var ne="client_01KATK792RR5ZCHMF5YMNN1ZSE",tt="https://api.workos.com";var mn="urn:ietf:params:oauth:grant-type:device_code",pn="refresh_token",gn=t=>{try{let e=t.split(".");if(e.length<2)throw new Error("malformed jwt");let n=JSON.parse(Buffer.from(e[1],"base64url").toString("utf8"));if(typeof n.exp!="number")throw new Error("jwt missing exp claim");return n.exp*1e3}catch(e){throw new S({message:"could not decode access token",cause:e})}},fe=async t=>{try{return await t.json()}catch{return {}}},st=async t=>Je.catch({}).parse(await fe(t)),nt=async(t,e)=>{let n=await st(t),s=n.error??n.code;throw new S({message:n.error_description??n.message??e,status:t.status,...s!==void 0?{code:s}:{}})},at=async(t,e)=>{let n=Fe.safeParse(await fe(t));if(!n.success)throw new S({message:`invalid ${e} response`,status:t.status,cause:n.error});return {token:n.data,expiresAt:gn(n.data.access_token)}},hn=async t=>{if(t.status===200)return at(t,"token");let e=await st(t),n=e.error??e.code;switch(n){case "authorization_pending":throw new $({slowDown:false});case "slow_down":throw new $({slowDown:true});case "expired_token":throw new E;case "access_denied":throw new O;default:throw new S({message:e.error_description??e.message??"WorkOS error",status:t.status,...n!==void 0?{code:n}:{}})}},se=class{baseUrl=tt;async requestDeviceAuthorization(){let e=await this.post("/user_management/authorize/device",new URLSearchParams({client_id:ne}),"device authorize request failed");if(e.status!==200)return nt(e,"device authorize failed");let n=Ue.safeParse(await fe(e));if(!n.success)throw new S({message:"invalid device authorize response",status:e.status,cause:n.error});return n.data}async exchangeDeviceCode(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:mn,client_id:ne,device_code:e}),"authenticate request failed",{"Content-Type":"application/json"});return hn(n)}async refresh(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:pn,client_id:ne,refresh_token:e}),"refresh request failed",{"Content-Type":"application/json"});return n.status===200?at(n,"refresh"):nt(n,"refresh failed")}async post(e,n,s,a={}){try{return await fetch(`${this.baseUrl}${e}`,{method:"POST",body:n,headers:{Accept:"application/json",...a}})}catch(o){throw new S({message:s,cause:o})}}};var rt=()=>{let t=De(),e=new V,n=new se,s=new q(t,e,n),a=new X(t,s);return {auth:s,credentialsStore:e,docs:new Z(t),images:new Q(a),models:new ee(a),tasks:new te(a),workos:n}};var i=(t="")=>{process.stdout.write(`${t}
3
- `);},ae=(t="")=>{process.stderr.write(`${t}
4
- `);};var yn=t=>new Date(t).toISOString(),wn=t=>new Promise(e=>setTimeout(e,t)),ot=pt.green("\u2713"),it=(t,e)=>new v({message:e instanceof Error?`${t}: ${e.message}`:t,cause:e}),vn=async(t,e,n,s)=>{let a=n;for(;;){if(Date.now()>=s)throw new v({message:"device code expired before approval"});try{return await t.workos.exchangeDeviceCode(e)}catch(o){if(o instanceof $){o.slowDown&&(a+=5e3),await wn(a);continue}throw o instanceof O?new v({message:"login denied by user"}):o instanceof E?new v({message:"device code expired"}):o instanceof S?new v({message:o.message,cause:o}):o}}},ye=t=>{let e=new Command("auth");return e.command("login").description("Authenticate with Mynth using OAuth device login").action(async()=>{if(t.auth.envApiKeySet)throw i(`MYNTH_API_KEY is set in your environment; that takes precedence over login.
5
- Unset it to use OAuth, or just continue using the env API key.`),new v({message:"env api key takes precedence"});let n;try{n=await t.workos.requestDeviceAuthorization();}catch(o){throw it("device authorize",o)}i(""),i(` First copy your one-time code: ${n.user_code}`),i(` Then open: ${n.verification_uri_complete??n.verification_uri}`),i(""),i("Waiting for confirmation...");let s=await vn(t,n.device_code,(n.interval??5)*1e3,Date.now()+n.expires_in*1e3);try{await t.auth.saveOAuth({accessToken:s.token.access_token,refreshToken:s.token.refresh_token,expiresAt:s.expiresAt,...s.token.user?{user:s.token.user}:{}});}catch(o){throw it("could not save credentials",o)}let a=s.token.user?.email??s.token.user?.id??"unknown user";i(`${ot} Logged in as ${a}`);}),e.command("logout").description("Clear local Mynth credentials").action(async()=>{await t.auth.logout(),i(`${ot} Local credentials cleared`),t.auth.envApiKeySet&&i("Note: MYNTH_API_KEY is still set in your environment and will be used.");}),e.command("status").description("Show current authentication status").action(async()=>{let n=await t.auth.status(),a=await t.credentialsStore.usingKeychain()?"system keychain":`file (${t.credentialsStore.filePath})`;switch(n.kind){case "env":i("Authenticated via env: MYNTH_API_KEY");return;case "none":i("Not authenticated. Run `mynth auth login` or set an API key.");return;case "api_key":i(`Authenticated via stored API key (${a})`);return;case "oauth":{let o=n.user?.email??n.user?.id??"unknown user";i(`Authenticated via OAuth as ${o} (${a})`),i(` access token expires: ${yn(n.expiresAt)}`);}}}),e.addCommand(re(t)),e},re=t=>new Command("whoami").description("Print the active Mynth identity").action(async()=>{let e=await t.auth.status();switch(e.kind){case "none":throw i("not authenticated"),new T;case "env":i("env:MYNTH_API_KEY");return;case "api_key":i("api-key");return;case "oauth":i(e.user?.email??e.user?.id??"oauth");}});var lt=pt.green("\u2713"),An=async()=>{try{let t="";process.stdin.setEncoding("utf8");for await(let e of process.stdin)t+=e;return t.trim()}catch(t){throw new v({message:"could not read stdin",cause:t})}},ve=t=>{let e=new Command("config"),n=new Command("set").description("Set local CLI configuration");n.command("api-key").description("Save a Mynth API key").argument("<value>","API key value, or `-` to read from stdin").action(async a=>{let o=a==="-"?await An():a;if(o.length===0)throw new v({message:"API key is empty"});try{await t.auth.setApiKey(o);}catch(c){throw new v({message:`could not save API key: ${c.message}`,cause:c})}i(`${lt} API key saved`),t.auth.envApiKeySet&&i("Note: MYNTH_API_KEY is also set in your environment and will take precedence.");});let s=new Command("unset").description("Unset local CLI configuration");return s.command("api-key").description("Clear stored Mynth credentials").action(async()=>{await t.auth.logout(),i(`${lt} Stored credentials cleared`);}),e.addCommand(n),e.addCommand(s),e};var ke=t=>{let e=new Command("docs").description("Read Mynth documentation");return e.command("get").description("Fetch a documentation page as Markdown").argument("<path>","Documentation path without the .md suffix").option("--json","Output machine-readable JSON").action(async(n,s)=>{let a=await t.docs.get(n);i(s.json?JSON.stringify(a,null,2):a.content);}),e.command("list").description("Fetch the complete documentation index").option("--json","Output machine-readable JSON").action(async n=>{let s=await t.docs.list();i(n.json?JSON.stringify({content:s},null,2):s);}),e};var xn=["Priming the canvas","Summoning pixels","Mixing digital pigments","Whispering to the model","Dreaming up details","Sketching silhouettes","Arranging composition","Weaving light and shadow","Polishing reflections","Sculpting atmosphere","Tracing final strokes"],Tn=()=>typeof process<"u"&&process.stderr&&process.stderr.isTTY===true,bn=t=>{let e=t.slice();for(let n=e.length-1;n>0;n--){let s=Math.floor(Math.random()*(n+1));[e[n],e[s]]=[e[s],e[n]];}return e},oe=async(t,e={})=>{if(!Tn())return t;let n=bn(e.messages??xn),s=0,a=_n({text:n[0]??"Working",stream:process.stderr}).start(),o=setInterval(()=>{s++,a.text=n[s%n.length]??"Working";},2800);try{return await t}finally{clearInterval(o),a.stop();}};var Ae=20,En="webp",On=80,xe=["auto","person","garment","pose","source","reference"],C=pt.green("\u2713"),In=pt.red("\u2717"),Se=()=>new Option("--json","Output machine-readable JSON instead of a human-readable summary"),Pn=z.array(z.object({value:z.string(),description:z.string()})),ce=t=>/^https?:\/\//i.test(t),gt=(t,e=[])=>[...e,t],ht=t=>{let e=Number.parseInt(t,10);if(!Number.isInteger(e)||String(e)!==t)throw new d(`invalid integer: "${t}"`);return e},Nn=t=>{let e=ht(t);if(e<1||e>100)throw new d(`invalid quality: "${t}" (expected 1-100)`);return e},jn=t=>{let e=t.indexOf("=");if(e<=0)throw new d(`invalid --level "${t}": expected "value=description"`);let n={value:t.slice(0,e).trim(),description:t.slice(e+1).trim()};if(n.value.length===0||n.description.length===0)throw new d(`invalid --level "${t}": value and description must be non-empty`);return n},dt=(t,e)=>{let n;try{n=JSON.parse(t);}catch(a){throw new d(`invalid JSON in ${e}: ${a.message}`)}let s=Pn.safeParse(n);if(!s.success)throw new d(`invalid levels in ${e}: expected array of { value, description }`);return s.data},ut=async t=>{let e=[t.levelPairs.length>0?"--level":null,t.levelsFile!==void 0?"--levels-file":null,t.levelsJson!==void 0?"--levels-json":null].filter(a=>a!==null);if(e.length===0)return;if(e.length>1)throw new d(`conflicting level options: ${e.join(", ")} - use only one`);let n;if(t.levelPairs.length>0)n=t.levelPairs.map(jn);else if(t.levelsFile!==void 0){let a;try{a=await readFile(t.levelsFile,"utf8");}catch(o){throw new d(`could not read ${t.levelsFile}: ${o.message}`)}n=dt(a,t.levelsFile);}else n=dt(t.levelsJson??"[]","--levels-json");if(n.length<L||n.length>U)throw new d(`levels must have between ${L} and ${U} items (got ${n.length})`);let s=new Set;for(let a of n){if(s.has(a.value))throw new d(`duplicate level value: "${a.value}"`);s.add(a.value);}return n},Mn=t=>{let e=t.indexOf(":"),n=/^https?:/i.test(t),s,a=t;if(e>0&&!n){let o=t.slice(0,e);if(!xe.includes(o))throw new d(`invalid --input as "${o}". Expected one of: ${xe.join(", ")}`);s=o,a=t.slice(e+1);}if(a.length===0)throw new d(`invalid --input "${t}": missing path or URL`);return {...s!==void 0?{as:s}:{},value:a,isFile:!ce(a)}},Dn=t=>{let e;try{e=JSON.parse(t);}catch(n){throw new d(`invalid --metadata JSON: ${n.message}`)}if(e===null||typeof e!="object"||Array.isArray(e))throw new d("--metadata must be a JSON object");return e},mt=t=>t.option("-l, --level <value>",'Custom rating level as "value=description" (repeatable, 2-7 items). Example: -l safe="No explicit content" -l nsfw="Contains nudity"',gt).option("--levels-file <path>",'Path to a JSON file containing an array of { "value": string, "description": string } (2-7 items). Alternative to --level when descriptions contain special characters.').option("--levels-json <json>",'Inline JSON array of { "value": string, "description": string } (2-7 items). Alternative to --level / --levels-file.'),Te=(t,e)=>{let n=t.result??{},s=n.images??[];e>0&&i(`${C} Uploaded ${e} input image${e===1?"":"s"}`);let a=s.filter(o=>o.status==="success");if(i(`${C} Generated ${a.length}/${s.length} image${s.length===1?"":"s"} (task ${t.id})`),n.model!==void 0&&i(` Model: ${n.model}`),t.cost!==null&&i(` Cost: ${t.cost}`),n.magic_prompt?.positive!==void 0&&(i(""),i("Enhanced prompt (mynth):"),i(` ${n.magic_prompt.positive}`),n.magic_prompt.negative!==void 0&&n.magic_prompt.negative.length>0&&i(` negative: ${n.magic_prompt.negative}`)),s.length>0){i("");for(let o of s){let c=o;if(c.status==="success"){let h=c.rating,y=h?.level!==void 0?` [${h.level}]`:"",f=c.url??c.mynth_url;i(` ${C} ${f}${y}`);}else i(` ${In} ${Ln(c.error)}`);}}},Ln=t=>{if(typeof t=="string")return t;if(t!==null&&typeof t=="object"){let e=t,n=typeof e.code=="string"?e.code:"unknown error",s=typeof e.message=="string"?e.message:void 0;return s!==void 0?`${n}: ${s}`:n}return "unknown error"},be=t=>{let e=t.result??{},n=(e.images??[]).map(s=>{let a=s;return a.status==="success"?{status:"success",url:a.url??null,mynth_url:a.mynth_url??null,size:a.size,rating:a.rating}:{status:"failed",error:a.error,mynth_url:a.mynth_url??null}});return {taskId:t.id,status:t.status,images:n,...e.magic_prompt?{magic_prompt:e.magic_prompt}:{},...t.cost!==null?{cost:t.cost}:{},...e.model!==void 0?{model:e.model}:{}}},Un=async(t,e,n)=>{let a=((e.result??{}).images??[]).map(o=>o).filter(o=>o.status==="success").map(o=>o.url??o.mynth_url).filter(o=>typeof o=="string"&&o.length>0);return a.length===0?[]:t.downloadImages({urls:a,destinationDir:n,taskId:e.id})},Re=t=>{let e=new Command("image");e.command("upload").description("Upload local images to Mynth").argument("<files...>","Path to a local image file (.jpg, .jpeg, .png, .webp)").addOption(Se()).action(async(a,o)=>{if(a.length>M)throw new d(`too many files: ${a.length} (max ${M})`);let c=await t.images.upload(a);if(o.json){i(JSON.stringify({images:c},null,2));return}i(`${C} Uploaded ${c.length} image${c.length===1?"":"s"}`);for(let{path:h,url:y}of c)i(` ${h}`),i(` -> ${y}`);});let n=e.command("rate").description("Rate images by URL or local file").argument("<image...>","Image URL (http://, https://) or path to a local image file to upload first").addOption(Se());mt(n),n.action(async(a,o)=>{if(a.length>D)throw new d(`too many images: ${a.length} (max ${D})`);let c=await ut({levelPairs:o.level??[],levelsFile:o.levelsFile,levelsJson:o.levelsJson}),h=a.filter(ce),y=a.filter(u=>!ce(u)),f=y.length>0?await t.images.upload(y):[],le=new Map(f.map(u=>[u.path,u.url])),F=a.map(u=>ce(u)?u:le.get(u)??u),x=await t.images.rate({urls:F,...c?{levels:c}:{}});if(o.json){i(JSON.stringify(x,null,2));return}let J=x.results.filter(u=>u.status==="success"),z=x.results.filter(u=>u.status==="failed");f.length>0&&i(`${C} Uploaded ${f.length} image${f.length===1?"":"s"}`),i(`${C} Rated ${J.length}/${x.results.length} (task ${x.task.id})`);for(let u of J)i(` ${u.level} ${u.url}`);for(let u of z)i(` ERROR ${u.error.code} ${u.url}`);if(h.length===0&&f.length>0){i(""),i("Uploaded source files:");for(let u of f)i(` ${u.path} -> ${u.url}`);}});let s=e.command("generate").description("Generate images with Mynth").addHelpText("after",`
6
- Models: mynth models list`);return s.option("-p, --prompt <text>","Text prompt describing the image to generate").option("-n, --negative <text>","Negative prompt (elements to exclude)").addOption(new Option("--enhance <mode>",'Prompt enhancement mode: "prefer_magic" (Mynth) or "none". "prefer_native" is no longer supported by the API.').choices(["prefer_magic","prefer_native","none"])).option("-m, --model <id>",'Model ID (e.g. "black-forest-labs/flux.1-dev"). Default: "auto"').option("-s, --size <size>",'Size preset or aspect ratio: "square", "portrait", "landscape", "1:1", "16:9", "16:9_4k", "auto", etc.').option("-c, --count <number>","Number of images to generate (default: 1)",ht).addOption(new Option("-f, --format <format>","Output image format (default: webp)").choices(["png","jpg","webp"])).option("-q, --quality <number>","Output quality 1-100 (default: 80)",Nn).option("-i, --input <value>",`Input image as "[as:]path-or-url" (repeatable, up to ${Ae}). as is optional and must be one of: ${xe.join(", ")}. Examples: -i ./img.jpg, -i source:https://example.com/a.png, -i reference:./style.png`,gt).option("-o, --output-dir <dir>","Directory to save generated images to. Created if it doesn't exist. Ignored in --async mode since the task hasn't completed yet.").option("--destination <name>","Name (slug) of a user-configured destination to deliver the result to. Falls back to MYNTH_DESTINATION env var if not set.").option("--metadata <json>","Inline JSON object of custom metadata to attach to the task (max 2KB)").option("--content-rating","Enable content rating classification using default sfw/nsfw levels. For custom levels use --level / --levels-file / --levels-json.").option("--async","Return the task ID immediately instead of polling until completion").option("--detailed","Include full task data (all fields) in the output").addOption(Se()),mt(s),s.action(async a=>{let o=a.prompt??"";if(a.enhance==="prefer_native")throw new d('--enhance prefer_native is no longer supported by the API; use "prefer_magic" or "none"');let c=a.input??[];if(c.length>Ae)throw new d(`too many --input values: ${c.length} (max ${Ae})`);let h=c.map(Mn),y=a.metadata!==void 0?Dn(a.metadata):void 0,f=await ut({levelPairs:a.level??[],levelsFile:a.levelsFile,levelsJson:a.levelsJson}),le=h.filter(m=>m.isFile).map(m=>m.value),F=Array.from(new Set(le)),x=F.length>0?await t.images.upload(F):[],J=new Map(x.map(m=>[m.path,m.url])),z=h.map(m=>({type:"image",...m.as?{as:m.as}:{},source:{type:"url",url:m.isFile?J.get(m.value)??m.value:m.value}})),u=a.format!==void 0||a.quality!==void 0?{format:a.format??En,quality:a.quality??On}:void 0,Ie=f!==void 0?{mode:"custom",levels:f}:a.contentRating?true:void 0,k={prompt:o};if(a.model!==void 0&&(k.model=a.model),a.negative!==void 0&&(k.negative_prompt=a.negative),a.enhance==="prefer_magic"&&(k.magic_prompt=true),a.size!==void 0&&(k.size=a.size),a.count!==void 0&&(k.count=a.count),u!==void 0&&(k.output=u),z.length>0&&(k.inputs=z),a.destination!==void 0&&(k.destination=a.destination),Ie!==void 0&&(k.rating=Ie),y!==void 0&&(k.metadata=y),a.async){let m=await t.images.generate({request:k,requestPat:true}),de={taskId:m.taskId,...m.pat!==void 0?{access:{publicAccessToken:m.pat}}:{}};if(a.json){i(JSON.stringify(de,null,2));return}i(`${C} Task created: ${m.taskId}`),m.pat!==void 0&&i(` PAT: ${m.pat}`);return}let Pe=await t.images.generate({request:k,requestPat:true}),Ne=t.images.waitForTask(Pe.taskId,Pe.pat),K=a.json?await Ne:await oe(Ne),I=a.outputDir!==void 0?resolve(a.outputDir):void 0,P=I!==void 0?await Un(t.images,K,I):[];if(a.json){let m=a.detailed?K:be(K),de=I!==void 0?{...m,downloadedFiles:P}:m;i(JSON.stringify(de,null,2));return}if(Te(K,x.length),I!==void 0&&P.length>0){i(""),i(`${C} Saved ${P.length} image${P.length===1?"":"s"} to ${I}`);for(let m of P)i(` ${m}`);}}),e};var Ce=t=>t??"-",Jn=t=>{if(t.length===0){i("No models available.");return}let e=t.map(s=>({id:s.id,name:s.displayName??"-",base:Ce(s.pricing?.perImage.base),fourK:Ce(s.pricing?.perImage["4k"]),inputFee:Ce(s.pricing?.perInput)})),n={id:Math.max(2,...e.map(s=>s.id.length)),name:Math.max(4,...e.map(s=>s.name.length)),base:Math.max(4,...e.map(s=>s.base.length)),fourK:Math.max(2,...e.map(s=>s.fourK.length)),inputFee:Math.max(9,...e.map(s=>s.inputFee.length))};i(["ID".padEnd(n.id),"Name".padEnd(n.name),"Base".padEnd(n.base),"4K".padEnd(n.fourK),"Input fee".padEnd(n.inputFee)].join(" "));for(let s of e)i([s.id.padEnd(n.id),s.name.padEnd(n.name),s.base.padEnd(n.base),s.fourK.padEnd(n.fourK),s.inputFee.padEnd(n.inputFee)].join(" "));},$e=t=>{let e=new Command("models").description("Browse the public Mynth model catalog");return e.command("list").description("List available image generation models").option("--json","Output machine-readable JSON instead of a human-readable table").action(async n=>{let s=await t.models.list();if(n.json){i(JSON.stringify(s,null,2));return}Jn(s);}),e};var ft=300,yt=t=>e=>{let n=Number.parseInt(e,10);if(!Number.isInteger(n)||String(n)!==e||n<=0)throw new d(`invalid ${t}: "${e}" (expected a positive integer)`);return n},vt=t=>{switch(t){case "completed":return "\u2713";case "failed":return "\u2717";default:return "\u2026"}},Kn=(t,e)=>{let n=" ".repeat(e);return t.split(`
2
+ import {Command,Option,Help}from'commander';import {z}from'zod';import*as $ from'cross-keychain';import {readFile,mkdir,writeFile,chmod,rm,stat}from'fs/promises';import {homedir}from'os';import {resolve,join,extname,basename}from'path';import wt from'chalk';import Cn from'ora';var A=class extends Error{_tag="MynthCliError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},l=class extends Error{_tag="CliUsageError";constructor(e){super(e),this.name=this._tag;}},R=class extends Error{_tag="NotAuthenticatedError";constructor(e={}){super(e.reason??"not authenticated"),this.name=this._tag;}},S=class extends Error{_tag="CredentialsStoreError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},b=class extends Error{_tag="WorkOSError";code;status;cause;constructor(e){super(e.message),this.name=this._tag,this.code=e.code,this.status=e.status,this.cause=e.cause;}},C=class extends Error{_tag="AuthorizationPendingError";slowDown;constructor(e){super("authorization pending"),this.name=this._tag,this.slowDown=e.slowDown;}},E=class extends Error{_tag="AuthorizationExpiredError";constructor(){super("authorization expired"),this.name=this._tag;}},P=class extends Error{_tag="AuthorizationDeniedError";constructor(){super("authorization denied"),this.name=this._tag;}},k={error:1,usage:2,auth:3,insufficientCredits:4,moderation:5,rateLimited:6},$t={UNAUTHORIZED:k.auth,VALIDATION_ERROR:k.usage,INSUFFICIENT_BALANCE:k.insufficientCredits,RESTRICTED_CONTENT:k.moderation},De=t=>{if(t instanceof l)return k.usage;if(t instanceof R)return k.auth;if(t instanceof d){let n=t.code!==void 0?$t[t.code]:void 0;return n!==void 0?n:t.status===401||t.status===403?k.auth:t.status===429?k.rateLimited:k.error}let e=t.code;return typeof e=="string"&&e.startsWith("commander.")?k.usage:k.error},me=t=>{let e=t.result?.images,n=[...(t.errors??[]).map(s=>s.code),...(e??[]).map(s=>s.error?.code)].filter(s=>typeof s=="string");return n.find(s=>s==="RESTRICTED_CONTENT")??n[0]},Le=t=>me(t)==="RESTRICTED_CONTENT"?k.moderation:k.error,d=class extends Error{_tag="MynthApiError";status;code;cause;constructor(e){super(e.message),this.name=this._tag,this.status=e.status,this.code=e.code,this.cause=e.cause;}};var Ue=z.object({id:z.string(),email:z.string(),first_name:z.string().nullable().optional(),last_name:z.string().nullable().optional()}),Je=z.object({device_code:z.string(),user_code:z.string(),verification_uri:z.string(),verification_uri_complete:z.string().optional(),expires_in:z.number(),interval:z.number().optional()}),Fe=z.object({access_token:z.string(),refresh_token:z.string(),user:Ue.optional(),organization_id:z.string().optional()}),Ke=z.object({error:z.string().optional(),error_description:z.string().optional(),message:z.string().optional(),code:z.string().optional()}),Ct=z.object({kind:z.literal("oauth"),access_token:z.string(),refresh_token:z.string(),expires_at:z.number(),user:Ue.optional()}),Et=z.object({kind:z.literal("api_key"),api_key:z.string()}),ze=z.union([Ct,Et]),q=z.lazy(()=>z.union([z.string(),z.number(),z.boolean(),z.null(),z.array(q),z.record(q)])),We=z.object({data:z.object({urls:z.array(z.string())})}),Pt=z.union([z.object({status:z.literal("success"),url:z.string(),level:z.string()}),z.object({status:z.literal("failed"),url:z.string(),error:z.object({code:z.string()})})]),qe=z.object({data:z.object({task:z.object({id:z.string(),status:z.literal("completed"),cost:z.string()}),results:z.array(Pt)})}),Be=z.object({data:z.object({taskId:z.string(),estimatedCost:z.string().optional(),access:z.object({publicAccessToken:z.string()}).optional()})}),He=z.object({data:z.object({estimatedCost:z.string(),currency:z.string(),estimateKind:z.union([z.literal("exact"),z.literal("upper_bound")])})}),Ye=z.object({data:z.object({userId:z.string(),auth:z.object({method:z.string(),apiKey:z.object({id:z.string(),name:z.string().nullable(),keyPreview:z.string()}).optional()})})}),Ge=z.object({data:z.object({balance:z.string(),reserved:z.string(),available:z.string(),currency:z.string(),apiKey:z.object({spendingLimit:z.string(),spendingLimitPeriod:z.string(),usedInPeriod:z.string(),remainingInPeriod:z.string()}).optional()})}),B=z.object({data:z.object({status:z.union([z.literal("pending"),z.literal("completed"),z.literal("failed")])})}),Ot=z.object({id:z.string(),type:z.union([z.literal("image.generate"),z.literal("image.rate")]),status:z.union([z.literal("pending"),z.literal("completed"),z.literal("failed")]),userId:z.string(),apiKeyId:z.string().nullable(),cost:z.string().nullable(),request:q,result:q,errors:z.array(z.object({code:z.string()})).nullable().optional(),createdAt:z.string(),updatedAt:z.string()}),H=z.object({data:Ot}),It=z.object({id:z.string(),type:z.string(),status:z.string(),cost:z.string().nullable(),createdAt:z.string(),updatedAt:z.string()}),Ve=z.object({data:z.array(It)}),jt=z.object({perImage:z.object({base:z.string(),"4k":z.string().optional()}),perInput:z.string().optional()}),Mt=z.object({id:z.string(),displayName:z.string().nullable(),pricing:jt.nullable()}),Xe=z.object({data:z.array(Mt)});var Nt=t=>t.kind==="api_key"?t.apiKey:t.accessToken,x=async t=>{try{return await t.json()}catch(e){throw new d({message:`invalid JSON response: ${e.message}`,status:t.status,cause:e})}},N=async t=>{try{return await t.text()}catch{return ""}},w=async(t,e)=>{if(t.status>=200&&t.status<300)return;let n=await N(t),s;try{let a=JSON.parse(n);typeof a.code=="string"&&(s=a.code);}catch{}throw new d({message:`${e} failed (${t.status}): ${n||"no body"}`,status:t.status,...s!==void 0?{code:s}:{}})},Y=class{constructor(e,n){this.auth=n;this.baseUrl=e.mynthApiUrl;}baseUrl;cachedAuth;async execute(e,n={}){let s=await this.attempt(e,n,false);return s.status!==401?s:this.attempt(e,n,true)}async executePublic(e,n={}){try{return await fetch(`${this.baseUrl}${e}`,n)}catch(s){throw new d({message:`request failed: ${s.message}`,status:0,cause:s})}}async attempt(e,n,s){let a=await this.getAuth(s),i=new Headers(n.headers);i.set("Authorization",`Bearer ${Nt(a)}`);try{return await fetch(`${this.baseUrl}${e}`,{...n,headers:i})}catch(c){throw new d({message:`request failed: ${c.message}`,status:0,cause:c})}}async getAuth(e){return e&&(this.cachedAuth=void 0),this.cachedAuth!==void 0?this.cachedAuth:(this.cachedAuth=await this.auth.resolve(),this.cachedAuth)}};var G=class{constructor(e){this.api=e;}async me(){let e=await this.api.execute("/me");await w(e,"me");let n=Ye.safeParse(await x(e));if(!n.success)throw new d({message:"invalid me response",status:e.status,cause:n.error});return n.data.data}async balance(){let e=await this.api.execute("/balance");await w(e,"balance");let n=Ge.safeParse(await x(e));if(!n.success)throw new d({message:"invalid balance response",status:e.status,cause:n.error});return n.data.data}};var Dt=6e4,V=t=>t?{user:t}:{},pe=(t,e)=>new R({reason:e instanceof Error?`${t}: ${e.message}`:t}),X=class{constructor(e,n,s){this.config=e;this.store=n;this.workos=s;this.envApiKey=e.apiKeyEnvOverride,this.envApiKeySet=this.envApiKey!==void 0&&this.envApiKey.length>0;}envApiKeySet;envApiKey;async resolve(){if(this.envApiKeySet)return {kind:"api_key",apiKey:this.envApiKey,source:"env"};let e;try{e=await this.store.get();}catch(s){throw pe("could not read credentials",s)}if(e===void 0)throw new R({reason:"no credentials configured"});if(e.kind==="api_key")return {kind:"api_key",apiKey:e.api_key,source:"stored"};let n=await this.refreshIfNeeded(e);return {kind:"oauth",accessToken:n.access_token,...V(n.user)}}async status(){if(this.envApiKeySet)return {kind:"env",source:"MYNTH_API_KEY"};let e;try{e=await this.store.get();}catch{e=void 0;}return e===void 0?{kind:"none"}:e.kind==="api_key"?{kind:"api_key"}:{kind:"oauth",expiresAt:e.expires_at,...V(e.user)}}async setApiKey(e){await this.store.set({kind:"api_key",api_key:e});}async saveOAuth(e){await this.store.set({kind:"oauth",access_token:e.accessToken,refresh_token:e.refreshToken,expires_at:e.expiresAt,...V(e.user)});}async logout(){await this.store.clear();}async refreshIfNeeded(e){if(e.expires_at-Date.now()>Dt)return e;let n;try{n=await this.workos.refresh(e.refresh_token);}catch(a){throw pe("token refresh failed",a)}let s={kind:"oauth",access_token:n.token.access_token,refresh_token:n.token.refresh_token,expires_at:n.expiresAt,...V(n.token.user??e.user)};try{await this.store.set(s);}catch(a){throw pe("could not persist refreshed token",a)}return s}};var Ze=()=>{let t=process.env.MYNTH_API_KEY;return {mynthApiUrl:process.env.MYNTH_API_URL??"https://api.mynth.io",mynthDocsUrl:process.env.MYNTH_DOCS_URL??"https://docs.mynth.io",...t!==void 0?{apiKeyEnvOverride:t}:{}}};var ge="mynth-cli",he="default",qt="credentials.json",Bt=t=>{let e=t?.name;return e==="NoKeyringError"||e==="InitError"},Z=async(t,e)=>{try{return {available:true,value:await t()}}catch(n){if(Bt(n))return {available:false};throw new S({message:e,cause:n})}},Qe=t=>{let e;try{e=JSON.parse(t);}catch(s){throw new S({message:"credentials JSON parse failed",cause:s})}let n=ze.safeParse(e);if(!n.success)throw new S({message:"credentials shape invalid",cause:n.error});return n.data},et=t=>JSON.stringify(t),Ht=()=>{let t=process.env.XDG_CONFIG_HOME,e=t&&t.length>0?t:join(homedir(),".config");return join(e,"mynth")},tt=async t=>{try{return await stat(t),true}catch{return false}},Q=class{filePath;dir;constructor(){this.dir=Ht(),this.filePath=join(this.dir,qt);}async get(){let e=await Z(()=>$.getPassword(ge,he),"keychain get failed");if(e.available)return e.value===null?void 0:Qe(e.value);if(await tt(this.filePath))try{return Qe(await readFile(this.filePath,"utf8"))}catch(n){throw n instanceof S?n:new S({message:"read credentials file failed",cause:n})}}async set(e){if((await Z(()=>$.setPassword(ge,he,et(e)),"keychain set failed")).available){await this.deleteFileSilently();return}await mkdir(this.dir,{recursive:true}).catch(s=>{throw new S({message:"create config dir failed",cause:s})}),await writeFile(this.filePath,et(e),"utf8").catch(s=>{throw new S({message:"write credentials file failed",cause:s})}),await chmod(this.filePath,384).catch(()=>{});}async clear(){await Z(()=>$.deletePassword(ge,he),"keychain delete failed").catch(()=>{}),await this.deleteFileSilently();}async usingKeychain(){let e=await Z(()=>$.getKeyring(),"keychain probe failed");return e.available&&e.value!==null}async deleteFileSilently(){await tt(this.filePath)&&await rm(this.filePath).catch(e=>{throw new S({message:"delete credentials file failed",cause:e})});}};var Yt=t=>{if(t.length===0)return "no response body";try{let e=JSON.parse(t);if(typeof e.error=="string"&&e.error.length>0)return e.error}catch{}return t.length>500?`${t.slice(0,500)}\u2026`:t},nt=async(t,e)=>{if(t.ok)return;let n=Yt(await N(t));throw new d({message:`${e} failed (${t.status}): ${n}`,status:t.status})},st=async(t,e,n)=>{try{return await fetch(t,n)}catch(s){throw new d({message:`${e} failed: ${s.message}`,status:0,cause:s})}},at=async(t,e)=>{try{return await t.text()}catch(n){throw new d({message:`${e} failed while reading the response: ${n.message}`,status:t.status,cause:n})}},Gt=t=>{let e=t.trim();if(e.length===0)throw new l("documentation path must not be empty");if(e.length>2048)throw new l("documentation path is too long");if(e.startsWith("//")||e.includes("://"))throw new l("documentation path must be a path, not a URL");if(e.includes("?")||e.includes("#")||e.includes("\\"))throw new l("documentation path must not contain a query, fragment, or backslash");let n=e.startsWith("/")?e.slice(1):e;if(n.endsWith(".md"))throw new l("documentation path must not include the .md suffix");let s=n.split("/");if(s.some(a=>a.length===0||a==="."||a===".."||!/^[A-Za-z0-9._~%-]+$/.test(a)))throw new l("documentation path contains an invalid segment");return s.join("/")},ee=class{docsUrl;constructor(e){this.docsUrl=e.mynthDocsUrl.replace(/\/$/,"");}async get(e){let n=Gt(e),s=n.split("/").map(encodeURIComponent).join("/"),a=await st(`${this.docsUrl}/${s}.md`,`documentation page fetch for ${n}`);return await nt(a,`documentation page fetch for ${n}`),{path:n,content:await at(a,`documentation page fetch for ${n}`)}}async list(){let e=await st(`${this.docsUrl}/llms.txt`,"documentation index fetch");return await nt(e,"documentation index fetch"),at(e,"documentation index fetch")}};var D=10,L=10,U=2,J=7,rt=300*1e3,sn=12e3,an=2500,rn=5e3,on={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},cn=t=>new Promise(e=>setTimeout(e,t)),dn=(t,e,n)=>{try{let a=new URL(t).pathname.split("/").filter(Boolean).pop();if(a&&a.length>0)return decodeURIComponent(a)}catch{}return `${e}-${n}`},ln=async t=>{let e=extname(t).toLowerCase(),n=on[e];if(!n)throw new d({message:`unsupported image extension "${e}" for ${t} (allowed: .jpg, .jpeg, .png, .webp)`,status:0});let s;try{s=await readFile(t);}catch(i){throw new d({message:`could not read ${t}: ${i.message}`,status:0,cause:i})}let a=s.buffer.slice(s.byteOffset,s.byteOffset+s.byteLength);return new File([new Uint8Array(a)],basename(t),{type:n})},O=async(t,e,n)=>{let s=e.safeParse(await x(t));if(!s.success)throw new d({message:n,status:t.status,cause:s.error});return s.data},un=async(t,e,n)=>{let s=Array.from({length:t.length}),a=0,i=async()=>{for(;;){let c=a++,h=t[c];if(h===void 0)return;s[c]=await n(h,c);}};return await Promise.all(Array.from({length:Math.min(e,t.length)},i)),s},te=class{constructor(e){this.api=e;}async upload(e){if(e.length===0)throw new d({message:"no files to upload",status:0});if(e.length>D)throw new d({message:`too many files: ${e.length} (max ${D})`,status:0});let n=await Promise.all(e.map(ln)),s=new FormData;for(let c of n)s.append("images",c);let a=await this.api.execute("/image/upload",{method:"POST",body:s});await w(a,"upload");let i=await O(a,We,"invalid upload response");return e.map((c,h)=>({path:c,url:i.data.urls[h]}))}async rate(e){if(e.urls.length===0)throw new d({message:"no image URLs to rate",status:0});if(e.urls.length>L)throw new d({message:`too many images: ${e.urls.length} (max ${L})`,status:0});if(e.levels!==void 0&&(e.levels.length<U||e.levels.length>J))throw new d({message:`levels must have between ${U} and ${J} items (got ${e.levels.length})`,status:0});let n=e.levels!==void 0?{urls:e.urls,mode:"custom",levels:e.levels}:{urls:e.urls,mode:"nsfw_sfw"},s=await this.api.execute("/image/rate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});return await w(s,"rate"),(await O(s,qe,"invalid rate response")).data}async generate(e){let n=e.requestPat?{...e.request,access:{...e.request.access,pat:{enabled:true}}}:e.request,s=await this.api.execute("/image/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});await w(s,"generate");let a=await O(s,Be,"invalid generate response"),i=a.data.access?.publicAccessToken;return {taskId:a.data.taskId,...i!==void 0?{pat:i}:{}}}async estimate(e){let n=await this.api.execute("/image/generate/estimate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});return await w(n,"estimate"),(await O(n,He,"invalid estimate response")).data}async waitForTask(e,n){let s=Date.now();for(;;){let a=Date.now()-s;if(a>=rt)throw new d({message:`task ${e} polling timed out after ${rt}ms`,status:0});let i=await this.getTaskStatus(e,n);if(i==="completed")return this.getTaskDetails(e);if(i==="failed"){let h=await this.getTaskDetails(e).catch(()=>{}),y=h!==void 0?me(h):void 0;throw new d({message:`task ${e} failed during generation${y!==void 0?` (${y})`:""}`,status:0,...y!==void 0?{code:y}:{}})}let c=a<sn?an:rn;await cn(c+Math.floor(Math.random()*500));}}async getTaskDetails(e){let n=await this.api.execute(`/tasks/${e}`);return await w(n,"task details"),(await O(n,H,"invalid task details response")).data}async downloadImages(e){let n=resolve(e.destinationDir);try{await mkdir(n,{recursive:true});}catch(s){throw new d({message:`could not create output directory ${n}: ${s.message}`,status:0,cause:s})}return un(e.urls,4,async(s,a)=>{let i;try{i=await fetch(s);}catch(f){throw new d({message:`download failed for ${s}: ${f.message}`,status:0,cause:f})}if(i.status<200||i.status>=300)throw new d({message:`download failed for ${s} (${i.status})`,status:i.status});let c;try{c=await i.arrayBuffer();}catch(f){throw new d({message:`could not read body for ${s}: ${f.message}`,status:i.status,cause:f})}let h=dn(s,e.taskId,a),y=join(n,h);try{await writeFile(y,new Uint8Array(c));}catch(f){throw new d({message:`could not write ${y}: ${f.message}`,status:0,cause:f})}return y})}async getTaskStatus(e,n){let s=`/tasks/${e}/status`,a;if(n!==void 0)try{a=await fetch(`${this.api.baseUrl}${s}`,{headers:{Authorization:`Bearer ${n}`}});}catch(c){throw new d({message:`task status request failed: ${c.message}`,status:0,cause:c})}else a=await this.api.execute(s);return await w(a,"task status"),(await O(a,B,"invalid task status response")).data.status}};var ne=class{constructor(e){this.api=e;}async list(){let e=await this.api.executePublic("/models");if(e.status<200||e.status>=300){let s=await N(e);throw new d({message:`models fetch failed (${e.status}): ${s||"no body"}`,status:e.status})}let n=Xe.safeParse(await x(e));if(!n.success)throw new d({message:"invalid models response",status:e.status,cause:n.error});return n.data.data}};var mn=300*1e3,pn=12e3,gn=2500,hn=5e3,fn=t=>new Promise(e=>setTimeout(e,t)),se=class{constructor(e){this.api=e;}async getTask(e){let n=await this.api.execute(`/tasks/${e}`);await w(n,"task fetch");let s=H.safeParse(await x(n));if(!s.success)throw new d({message:"invalid task response",status:n.status,cause:s.error});return s.data.data}async listTasks(e={}){let n=new URLSearchParams;e.limit!==void 0&&n.set("limit",String(e.limit)),e.after!==void 0&&n.set("after",e.after);let s=n.size>0?`?${n}`:"",a=await this.api.execute(`/tasks${s}`);await w(a,"task list");let i=Ve.safeParse(await x(a));if(!i.success)throw new d({message:"invalid task list response",status:a.status,cause:i.error});return i.data.data}async getTaskStatus(e){let n=await this.api.execute(`/tasks/${e}/status`);await w(n,"task status");let s=B.safeParse(await x(n));if(!s.success)throw new d({message:"invalid task status response",status:n.status,cause:s.error});return s.data.data.status}async waitForTask(e,n=mn){let s=Date.now();for(;;){if(await this.getTaskStatus(e)!=="pending")return this.getTask(e);let i=Date.now()-s;if(i>=n)throw new d({message:`task ${e} did not complete within ${Math.round(n/1e3)}s`,status:0});let c=i<pn?gn:hn;await fn(c+Math.floor(Math.random()*500));}}};var ae="client_01KATK792RR5ZCHMF5YMNN1ZSE",ot="https://api.workos.com";var yn="urn:ietf:params:oauth:grant-type:device_code",wn="refresh_token",vn=t=>{try{let e=t.split(".");if(e.length<2)throw new Error("malformed jwt");let n=JSON.parse(Buffer.from(e[1],"base64url").toString("utf8"));if(typeof n.exp!="number")throw new Error("jwt missing exp claim");return n.exp*1e3}catch(e){throw new b({message:"could not decode access token",cause:e})}},ye=async t=>{try{return await t.json()}catch{return {}}},ct=async t=>Ke.catch({}).parse(await ye(t)),it=async(t,e)=>{let n=await ct(t),s=n.error??n.code;throw new b({message:n.error_description??n.message??e,status:t.status,...s!==void 0?{code:s}:{}})},dt=async(t,e)=>{let n=Fe.safeParse(await ye(t));if(!n.success)throw new b({message:`invalid ${e} response`,status:t.status,cause:n.error});return {token:n.data,expiresAt:vn(n.data.access_token)}},kn=async t=>{if(t.status===200)return dt(t,"token");let e=await ct(t),n=e.error??e.code;switch(n){case "authorization_pending":throw new C({slowDown:false});case "slow_down":throw new C({slowDown:true});case "expired_token":throw new E;case "access_denied":throw new P;default:throw new b({message:e.error_description??e.message??"WorkOS error",status:t.status,...n!==void 0?{code:n}:{}})}},re=class{baseUrl=ot;async requestDeviceAuthorization(){let e=await this.post("/user_management/authorize/device",new URLSearchParams({client_id:ae}),"device authorize request failed");if(e.status!==200)return it(e,"device authorize failed");let n=Je.safeParse(await ye(e));if(!n.success)throw new b({message:"invalid device authorize response",status:e.status,cause:n.error});return n.data}async exchangeDeviceCode(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:yn,client_id:ae,device_code:e}),"authenticate request failed",{"Content-Type":"application/json"});return kn(n)}async refresh(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:wn,client_id:ae,refresh_token:e}),"refresh request failed",{"Content-Type":"application/json"});return n.status===200?dt(n,"refresh"):it(n,"refresh failed")}async post(e,n,s,a={}){try{return await fetch(`${this.baseUrl}${e}`,{method:"POST",body:n,headers:{Accept:"application/json",...a}})}catch(i){throw new b({message:s,cause:i})}}};var lt=()=>{let t=Ze(),e=new Q,n=new re,s=new X(t,e,n),a=new Y(t,s);return {account:new G(a),auth:s,credentialsStore:e,docs:new ee(t),images:new te(a),models:new ne(a),tasks:new se(a),workos:n}};var o=(t="")=>{process.stdout.write(`${t}
3
+ `);},oe=(t="")=>{process.stderr.write(`${t}
4
+ `);};var Sn=t=>new Date(t).toISOString(),bn=t=>new Promise(e=>setTimeout(e,t)),ut=wt.green("\u2713"),mt=(t,e)=>new A({message:e instanceof Error?`${t}: ${e.message}`:t,cause:e}),xn=async(t,e,n,s)=>{let a=n;for(;;){if(Date.now()>=s)throw new A({message:"device code expired before approval"});try{return await t.workos.exchangeDeviceCode(e)}catch(i){if(i instanceof C){i.slowDown&&(a+=5e3),await bn(a);continue}throw i instanceof P?new A({message:"login denied by user"}):i instanceof E?new A({message:"device code expired"}):i instanceof b?new A({message:i.message,cause:i}):i}}},we=t=>{let e=new Command("auth");return e.command("login").description("Authenticate with Mynth using OAuth device login").action(async()=>{if(t.auth.envApiKeySet)throw o(`MYNTH_API_KEY is set in your environment; that takes precedence over login.
5
+ Unset it to use OAuth, or just continue using the env API key.`),new A({message:"env api key takes precedence"});let n;try{n=await t.workos.requestDeviceAuthorization();}catch(i){throw mt("device authorize",i)}o(""),o(` First copy your one-time code: ${n.user_code}`),o(` Then open: ${n.verification_uri_complete??n.verification_uri}`),o(""),o("Waiting for confirmation...");let s=await xn(t,n.device_code,(n.interval??5)*1e3,Date.now()+n.expires_in*1e3);try{await t.auth.saveOAuth({accessToken:s.token.access_token,refreshToken:s.token.refresh_token,expiresAt:s.expiresAt,...s.token.user?{user:s.token.user}:{}});}catch(i){throw mt("could not save credentials",i)}let a=s.token.user?.email??s.token.user?.id??"unknown user";o(`${ut} Logged in as ${a}`);}),e.command("logout").description("Clear local Mynth credentials").action(async()=>{await t.auth.logout(),o(`${ut} Local credentials cleared`),t.auth.envApiKeySet&&o("Note: MYNTH_API_KEY is still set in your environment and will be used.");}),e.command("status").description("Show current authentication status").action(async()=>{let n=await t.auth.status(),a=await t.credentialsStore.usingKeychain()?"system keychain":`file (${t.credentialsStore.filePath})`;switch(n.kind){case "env":o("Authenticated via env: MYNTH_API_KEY");return;case "none":o("Not authenticated. Run `mynth auth login` or set an API key.");return;case "api_key":o(`Authenticated via stored API key (${a})`);return;case "oauth":{let i=n.user?.email??n.user?.id??"unknown user";o(`Authenticated via OAuth as ${i} (${a})`),o(` access token expires: ${Sn(n.expiresAt)}`);}}}),e.addCommand(ie(t)),e},ie=t=>new Command("whoami").description("Print the active Mynth identity, verified against the API").action(async()=>{let e=await t.auth.status();if(e.kind==="none")throw o("not authenticated"),new R;let n=e.kind==="env"?"env:MYNTH_API_KEY":e.kind==="api_key"?"api-key":e.user?.email??e.user?.id??"oauth",s=await t.account.me();o(n),o(` user: ${s.userId}`),s.auth.apiKey&&o(` key: ${s.auth.apiKey.name??"unnamed"} (${s.auth.apiKey.keyPreview})`);});var ve=t=>new Command("balance").description("Show account balance and API key spending limit usage").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async e=>{let n=await t.account.balance();if(e.json){o(JSON.stringify(n,null,2));return}o(`Balance: $${n.balance}`),o(`Reserved: $${n.reserved}`),o(`Available: $${n.available}`),n.apiKey&&(o(""),o(`API key limit: $${n.apiKey.spendingLimit} / ${n.apiKey.spendingLimitPeriod}`),o(` used: $${n.apiKey.usedInPeriod}`),o(` remaining: $${n.apiKey.remainingInPeriod}`));});var gt=wt.green("\u2713"),Rn=async()=>{try{let t="";process.stdin.setEncoding("utf8");for await(let e of process.stdin)t+=e;return t.trim()}catch(t){throw new A({message:"could not read stdin",cause:t})}},Ae=t=>{let e=new Command("config"),n=new Command("set").description("Set local CLI configuration");n.command("api-key").description("Save a Mynth API key").argument("<value>","API key value, or `-` to read from stdin").action(async a=>{let i=a==="-"?await Rn():a;if(i.length===0)throw new A({message:"API key is empty"});try{await t.auth.setApiKey(i);}catch(c){throw new A({message:`could not save API key: ${c.message}`,cause:c})}o(`${gt} API key saved`),t.auth.envApiKeySet&&o("Note: MYNTH_API_KEY is also set in your environment and will take precedence.");});let s=new Command("unset").description("Unset local CLI configuration");return s.command("api-key").description("Clear stored Mynth credentials").action(async()=>{await t.auth.logout(),o(`${gt} Stored credentials cleared`);}),e.addCommand(n),e.addCommand(s),e};var Se=t=>{let e=new Command("docs").description("Read Mynth documentation");return e.command("get").description("Fetch a documentation page as Markdown").argument("<path>","Documentation path without the .md suffix").option("--json","Output machine-readable JSON").action(async(n,s)=>{let a=await t.docs.get(n);o(s.json?JSON.stringify(a,null,2):a.content);}),e.command("list").description("Fetch the complete documentation index").option("--json","Output machine-readable JSON").action(async n=>{let s=await t.docs.list();o(n.json?JSON.stringify({content:s},null,2):s);}),e};var En=["Priming the canvas","Summoning pixels","Mixing digital pigments","Whispering to the model","Dreaming up details","Sketching silhouettes","Arranging composition","Weaving light and shadow","Polishing reflections","Sculpting atmosphere","Tracing final strokes"],Pn=()=>typeof process<"u"&&process.stderr&&process.stderr.isTTY===true,On=t=>{let e=t.slice();for(let n=e.length-1;n>0;n--){let s=Math.floor(Math.random()*(n+1));[e[n],e[s]]=[e[s],e[n]];}return e},ce=async(t,e={})=>{if(!Pn())return t;let n=On(e.messages??En),s=0,a=Cn({text:n[0]??"Working",stream:process.stderr}).start(),i=setInterval(()=>{s++,a.text=n[s%n.length]??"Working";},2800);try{return await t}finally{clearInterval(i),a.stop();}};var be=20,Nn="webp",Dn=80,Te=["auto","person","garment","pose","source","reference"],Ln="https://dry-run.mynth.io/input",_=wt.green("\u2713"),Un=wt.red("\u2717"),xe=()=>new Option("--json","Output machine-readable JSON instead of a human-readable summary"),Jn=z.array(z.object({value:z.string(),description:z.string()})),le=t=>/^https?:\/\//i.test(t),vt=(t,e=[])=>[...e,t],kt=t=>{let e=Number.parseInt(t,10);if(!Number.isInteger(e)||String(e)!==t)throw new l(`invalid integer: "${t}"`);return e},Fn=t=>{let e=kt(t);if(e<1||e>100)throw new l(`invalid quality: "${t}" (expected 1-100)`);return e},Kn=t=>{let e=t.indexOf("=");if(e<=0)throw new l(`invalid --level "${t}": expected "value=description"`);let n={value:t.slice(0,e).trim(),description:t.slice(e+1).trim()};if(n.value.length===0||n.description.length===0)throw new l(`invalid --level "${t}": value and description must be non-empty`);return n},ht=(t,e)=>{let n;try{n=JSON.parse(t);}catch(a){throw new l(`invalid JSON in ${e}: ${a.message}`)}let s=Jn.safeParse(n);if(!s.success)throw new l(`invalid levels in ${e}: expected array of { value, description }`);return s.data},ft=async t=>{let e=[t.levelPairs.length>0?"--level":null,t.levelsFile!==void 0?"--levels-file":null,t.levelsJson!==void 0?"--levels-json":null].filter(a=>a!==null);if(e.length===0)return;if(e.length>1)throw new l(`conflicting level options: ${e.join(", ")} - use only one`);let n;if(t.levelPairs.length>0)n=t.levelPairs.map(Kn);else if(t.levelsFile!==void 0){let a;try{a=await readFile(t.levelsFile,"utf8");}catch(i){throw new l(`could not read ${t.levelsFile}: ${i.message}`)}n=ht(a,t.levelsFile);}else n=ht(t.levelsJson??"[]","--levels-json");if(n.length<U||n.length>J)throw new l(`levels must have between ${U} and ${J} items (got ${n.length})`);let s=new Set;for(let a of n){if(s.has(a.value))throw new l(`duplicate level value: "${a.value}"`);s.add(a.value);}return n},zn=t=>{let e=t.indexOf(":"),n=/^https?:/i.test(t),s,a=t;if(e>0&&!n){let i=t.slice(0,e);if(!Te.includes(i))throw new l(`invalid --input as "${i}". Expected one of: ${Te.join(", ")}`);s=i,a=t.slice(e+1);}if(a.length===0)throw new l(`invalid --input "${t}": missing path or URL`);return {...s!==void 0?{as:s}:{},value:a,isFile:!le(a)}},Wn=t=>{let e;try{e=JSON.parse(t);}catch(n){throw new l(`invalid --metadata JSON: ${n.message}`)}if(e===null||typeof e!="object"||Array.isArray(e))throw new l("--metadata must be a JSON object");return e},yt=t=>t.option("-l, --level <value>",'Custom rating level as "value=description" (repeatable, 2-7 items). Example: -l safe="No explicit content" -l nsfw="Contains nudity"',vt).option("--levels-file <path>",'Path to a JSON file containing an array of { "value": string, "description": string } (2-7 items). Alternative to --level when descriptions contain special characters.').option("--levels-json <json>",'Inline JSON array of { "value": string, "description": string } (2-7 items). Alternative to --level / --levels-file.'),Re=(t,e)=>{let n=t.result??{},s=n.images??[];e>0&&o(`${_} Uploaded ${e} input image${e===1?"":"s"}`);let a=s.filter(i=>i.status==="success");if(o(`${_} Generated ${a.length}/${s.length} image${s.length===1?"":"s"} (task ${t.id})`),n.model!==void 0&&o(` Model: ${n.model}`),t.cost!==null&&o(` Cost: ${t.cost}`),n.magic_prompt?.positive!==void 0&&(o(""),o("Enhanced prompt (mynth):"),o(` ${n.magic_prompt.positive}`),n.magic_prompt.negative!==void 0&&n.magic_prompt.negative.length>0&&o(` negative: ${n.magic_prompt.negative}`)),s.length>0){o("");for(let i of s){let c=i;if(c.status==="success"){let h=c.rating,y=h?.level!==void 0?` [${h.level}]`:"",f=c.url??c.mynth_url;o(` ${_} ${f}${y}`);}else o(` ${Un} ${qn(c.error)}`);}}},qn=t=>{if(typeof t=="string")return t;if(t!==null&&typeof t=="object"){let e=t,n=typeof e.code=="string"?e.code:"unknown error",s=typeof e.message=="string"?e.message:void 0;return s!==void 0?`${n}: ${s}`:n}return "unknown error"},$e=t=>{let e=t.result??{},n=(e.images??[]).map(s=>{let a=s;return a.status==="success"?{status:"success",url:a.url??null,mynth_url:a.mynth_url??null,size:a.size,rating:a.rating}:{status:"failed",error:a.error,mynth_url:a.mynth_url??null}});return {taskId:t.id,status:t.status,images:n,...e.magic_prompt?{magic_prompt:e.magic_prompt}:{},...t.cost!==null?{cost:t.cost}:{},...e.model!==void 0?{model:e.model}:{}}},Bn=async(t,e,n)=>{let a=((e.result??{}).images??[]).map(i=>i).filter(i=>i.status==="success").map(i=>i.url??i.mynth_url).filter(i=>typeof i=="string"&&i.length>0);return a.length===0?[]:t.downloadImages({urls:a,destinationDir:n,taskId:e.id})},Ce=t=>{let e=new Command("image");e.command("upload").description("Upload local images to Mynth").argument("<files...>","Path to a local image file (.jpg, .jpeg, .png, .webp)").addOption(xe()).action(async(a,i)=>{if(a.length>D)throw new l(`too many files: ${a.length} (max ${D})`);let c=await t.images.upload(a);if(i.json){o(JSON.stringify({images:c},null,2));return}o(`${_} Uploaded ${c.length} image${c.length===1?"":"s"}`);for(let{path:h,url:y}of c)o(` ${h}`),o(` -> ${y}`);});let n=e.command("rate").description("Rate images by URL or local file").argument("<image...>","Image URL (http://, https://) or path to a local image file to upload first").addOption(xe());yt(n),n.action(async(a,i)=>{if(a.length>L)throw new l(`too many images: ${a.length} (max ${L})`);let c=await ft({levelPairs:i.level??[],levelsFile:i.levelsFile,levelsJson:i.levelsJson}),h=a.filter(le),y=a.filter(m=>!le(m)),f=y.length>0?await t.images.upload(y):[],ue=new Map(f.map(m=>[m.path,m.url])),F=a.map(m=>le(m)?m:ue.get(m)??m),T=await t.images.rate({urls:F,...c?{levels:c}:{}});if(i.json){o(JSON.stringify(T,null,2));return}let K=T.results.filter(m=>m.status==="success"),z=T.results.filter(m=>m.status==="failed");f.length>0&&o(`${_} Uploaded ${f.length} image${f.length===1?"":"s"}`),o(`${_} Rated ${K.length}/${T.results.length} (task ${T.task.id})`);for(let m of K)o(` ${m.level} ${m.url}`);for(let m of z)o(` ERROR ${m.error.code} ${m.url}`);if(h.length===0&&f.length>0){o(""),o("Uploaded source files:");for(let m of f)o(` ${m.path} -> ${m.url}`);}});let s=e.command("generate").description("Generate images with Mynth").addHelpText("after",`
6
+ Models: mynth models list`);return s.option("-p, --prompt <text>","Text prompt describing the image to generate").option("-n, --negative <text>","Negative prompt (elements to exclude)").addOption(new Option("--enhance <mode>",'Prompt enhancement mode: "prefer_magic" (Mynth) or "none". "prefer_native" is no longer supported by the API.').choices(["prefer_magic","prefer_native","none"])).option("-m, --model <id>",'Model ID (e.g. "black-forest-labs/flux.1-dev"). Default: "auto"').option("-s, --size <size>",'Size preset or aspect ratio: "square", "portrait", "landscape", "1:1", "16:9", "16:9_4k", "auto", etc.').option("-c, --count <number>","Number of images to generate (default: 1)",kt).addOption(new Option("-f, --format <format>","Output image format (default: webp)").choices(["png","jpg","webp"])).option("-q, --quality <number>","Output quality 1-100 (default: 80)",Fn).option("-i, --input <value>",`Input image as "[as:]path-or-url" (repeatable, up to ${be}). as is optional and must be one of: ${Te.join(", ")}. Examples: -i ./img.jpg, -i source:https://example.com/a.png, -i reference:./style.png`,vt).option("-o, --output-dir <dir>","Directory to save generated images to. Created if it doesn't exist. Ignored in --async mode since the task hasn't completed yet.").option("--destination <name>","Name (slug) of a user-configured destination to deliver the result to. Falls back to MYNTH_DESTINATION env var if not set.").option("--metadata <json>","Inline JSON object of custom metadata to attach to the task (max 2KB)").option("--content-rating","Enable content rating classification using default sfw/nsfw levels. For custom levels use --level / --levels-file / --levels-json.").option("--dry-run","Validate the request and print the estimated cost without generating anything").option("--async","Return the task ID immediately instead of polling until completion").option("--detailed","Include full task data (all fields) in the output").addOption(xe()),yt(s),s.action(async a=>{let i=a.prompt??"";if(a.enhance==="prefer_native")throw new l('--enhance prefer_native is no longer supported by the API; use "prefer_magic" or "none"');let c=a.input??[];if(c.length>be)throw new l(`too many --input values: ${c.length} (max ${be})`);let h=c.map(zn),y=a.metadata!==void 0?Wn(a.metadata):void 0,f=await ft({levelPairs:a.level??[],levelsFile:a.levelsFile,levelsJson:a.levelsJson}),ue=h.filter(u=>u.isFile).map(u=>u.value),F=Array.from(new Set(ue)),T=F.length>0&&!a.dryRun?await t.images.upload(F):[],K=new Map(T.map(u=>[u.path,u.url])),z=h.map(u=>({type:"image",...u.as?{as:u.as}:{},source:{type:"url",url:u.isFile?K.get(u.value)??Ln:u.value}})),m=a.format!==void 0||a.quality!==void 0?{format:a.format??Nn,quality:a.quality??Dn}:void 0,je=f!==void 0?{mode:"custom",levels:f}:a.contentRating?true:void 0,v={prompt:i};if(a.model!==void 0&&(v.model=a.model),a.negative!==void 0&&(v.negative_prompt=a.negative),a.enhance==="prefer_magic"&&(v.magic_prompt=true),a.size!==void 0&&(v.size=a.size),a.count!==void 0&&(v.count=a.count),m!==void 0&&(v.output=m),z.length>0&&(v.inputs=z),a.destination!==void 0&&(v.destination=a.destination),je!==void 0&&(v.rating=je),y!==void 0&&(v.metadata=y),a.dryRun){let u=await t.images.estimate(v);if(a.json){o(JSON.stringify(u,null,2));return}let M=u.estimateKind==="upper_bound"?" (upper bound)":"";o(`${_} Estimated cost: $${u.estimatedCost}${M}`);return}if(a.async){let u=await t.images.generate({request:v,requestPat:true}),M={taskId:u.taskId,...u.pat!==void 0?{access:{publicAccessToken:u.pat}}:{}};if(a.json){o(JSON.stringify(M,null,2));return}o(`${_} Task created: ${u.taskId}`),u.pat!==void 0&&o(` PAT: ${u.pat}`);return}let Me=await t.images.generate({request:v,requestPat:true}),Ne=t.images.waitForTask(Me.taskId,Me.pat),W=a.json?await Ne:await ce(Ne),I=a.outputDir!==void 0?resolve(a.outputDir):void 0,j=I!==void 0?await Bn(t.images,W,I):[];if(a.json){let u=a.detailed?W:$e(W),M=I!==void 0?{...u,downloadedFiles:j}:u;o(JSON.stringify(M,null,2));return}if(Re(W,T.length),I!==void 0&&j.length>0){o(""),o(`${_} Saved ${j.length} image${j.length===1?"":"s"} to ${I}`);for(let u of j)o(` ${u}`);}}),e};var Ee=t=>t??"-",Yn=t=>{if(t.length===0){o("No models available.");return}let e=t.map(s=>({id:s.id,name:s.displayName??"-",base:Ee(s.pricing?.perImage.base),fourK:Ee(s.pricing?.perImage["4k"]),inputFee:Ee(s.pricing?.perInput)})),n={id:Math.max(2,...e.map(s=>s.id.length)),name:Math.max(4,...e.map(s=>s.name.length)),base:Math.max(4,...e.map(s=>s.base.length)),fourK:Math.max(2,...e.map(s=>s.fourK.length)),inputFee:Math.max(9,...e.map(s=>s.inputFee.length))};o(["ID".padEnd(n.id),"Name".padEnd(n.name),"Base".padEnd(n.base),"4K".padEnd(n.fourK),"Input fee".padEnd(n.inputFee)].join(" "));for(let s of e)o([s.id.padEnd(n.id),s.name.padEnd(n.name),s.base.padEnd(n.base),s.fourK.padEnd(n.fourK),s.inputFee.padEnd(n.inputFee)].join(" "));},Pe=t=>{let e=new Command("models").description("Browse the public Mynth model catalog");return e.command("list").description("List available image generation models").option("--json","Output machine-readable JSON instead of a human-readable table").action(async n=>{let s=await t.models.list();if(n.json){o(JSON.stringify(s,null,2));return}Yn(s);}),e};var At=300,St=t=>e=>{let n=Number.parseInt(e,10);if(!Number.isInteger(n)||String(n)!==e||n<=0)throw new l(`invalid ${t}: "${e}" (expected a positive integer)`);return n},xt=t=>{switch(t){case "completed":return "\u2713";case "failed":return "\u2717";default:return "\u2026"}},Vn=(t,e)=>{let n=" ".repeat(e);return t.split(`
7
7
  `).map(s=>`${n}${s}`).join(`
8
- `)},wt=t=>{i(`${vt(t.status)} Task ${t.id}`),i(` Type: ${t.type}`),i(` Status: ${t.status}`),t.cost!==null&&i(` Cost: ${t.cost}`),i(` Created: ${t.createdAt}`),i(` Updated: ${t.updatedAt}`),t.result!==null&&t.result!==void 0&&(i(""),i("Result:"),i(Kn(JSON.stringify(t.result,null,2),2)));},Wn=t=>{if(t.length===0){i("No tasks found.");return}for(let e of t){let n=e.cost!==null?` ${e.cost}`:"";i(`${vt(e.status)} ${e.id} ${e.type} ${e.status}${n} ${e.createdAt}`);}},Ee=t=>{let e=new Command("task");return e.command("get").description("Fetch a task by ID").argument("<id>","Task ID").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,s)=>{let a=await t.tasks.getTask(n);if(s.json){i(JSON.stringify(a,null,2));return}wt(a);}),e.command("wait").description("Block until a task completes (or fails), then print it").argument("<id>","Task ID").option("--timeout <seconds>",`Max seconds to wait before giving up (default: ${ft})`,yt("--timeout")).option("--detailed","Include full task data (all fields) in the output").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,s)=>{let a=(s.timeout??ft)*1e3,o=t.tasks.waitForTask(n,a),c=s.json?await o:await oe(o);if(c.status==="failed"&&(process.exitCode=Me(c)),s.json){let h=s.detailed||c.type!=="image.generate"?c:be(c);i(JSON.stringify(h,null,2));return}if(c.type==="image.generate"&&c.status==="completed"){Te(c,0);return}wt(c);}),e.command("list").description("List recent tasks, newest first").option("--limit <number>","Max tasks to return (1-100, default: 20)",yt("--limit")).option("--after <id>","Cursor: return tasks created before this task ID").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async n=>{let s=await t.tasks.listTasks({...n.limit!==void 0?{limit:n.limit}:{},...n.after!==void 0?{after:n.after}:{}});if(n.json){i(JSON.stringify({tasks:s},null,2));return}Wn(s);}),e};var Oe=class extends Help{optionTerm(e){return `(${e.flags.replaceAll("<","").replaceAll(">","")})`}subcommandTerm(e){let n=e.registeredArguments.map(s=>s.required?`${s.name()}${s.variadic?"...":""}`:`[${s.name()}]`).join(" ");return n.length>0?`${e.name()} ${n}`:e.name()}},kt=()=>{let t=rt(),e=new Command("mynth");return e.description("Official Mynth CLI").version("0.0.12"),e.addHelpText("after",`
8
+ `)},bt=t=>{o(`${xt(t.status)} Task ${t.id}`),o(` Type: ${t.type}`),o(` Status: ${t.status}`),t.cost!==null&&o(` Cost: ${t.cost}`),o(` Created: ${t.createdAt}`),o(` Updated: ${t.updatedAt}`),t.result!==null&&t.result!==void 0&&(o(""),o("Result:"),o(Vn(JSON.stringify(t.result,null,2),2)));},Xn=t=>{if(t.length===0){o("No tasks found.");return}for(let e of t){let n=e.cost!==null?` ${e.cost}`:"";o(`${xt(e.status)} ${e.id} ${e.type} ${e.status}${n} ${e.createdAt}`);}},Oe=t=>{let e=new Command("task");return e.command("get").description("Fetch a task by ID").argument("<id>","Task ID").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,s)=>{let a=await t.tasks.getTask(n);if(s.json){o(JSON.stringify(a,null,2));return}bt(a);}),e.command("wait").description("Block until a task completes (or fails), then print it").argument("<id>","Task ID").option("--timeout <seconds>",`Max seconds to wait before giving up (default: ${At})`,St("--timeout")).option("--detailed","Include full task data (all fields) in the output").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,s)=>{let a=(s.timeout??At)*1e3,i=t.tasks.waitForTask(n,a),c=s.json?await i:await ce(i);if(c.status==="failed"&&(process.exitCode=Le(c)),s.json){let h=s.detailed||c.type!=="image.generate"?c:$e(c);o(JSON.stringify(h,null,2));return}if(c.type==="image.generate"&&c.status==="completed"){Re(c,0);return}bt(c);}),e.command("list").description("List recent tasks, newest first").option("--limit <number>","Max tasks to return (1-100, default: 20)",St("--limit")).option("--after <id>","Cursor: return tasks created before this task ID").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async n=>{let s=await t.tasks.listTasks({...n.limit!==void 0?{limit:n.limit}:{},...n.after!==void 0?{after:n.after}:{}});if(n.json){o(JSON.stringify({tasks:s},null,2));return}Xn(s);}),e};var Ie=class extends Help{optionTerm(e){return `(${e.flags.replaceAll("<","").replaceAll(">","")})`}subcommandTerm(e){let n=e.registeredArguments.map(s=>s.required?`${s.name()}${s.variadic?"...":""}`:`[${s.name()}]`).join(" ");return n.length>0?`${e.name()} ${n}`:e.name()}},_t=()=>{let t=lt(),e=new Command("mynth");return e.description("Official Mynth CLI").version("0.0.12"),e.addHelpText("after",`
9
9
  Exit codes:
10
10
  0 success
11
11
  1 error (network, server, or unexpected failure)
@@ -13,4 +13,4 @@ Exit codes:
13
13
  3 authentication error (missing or invalid credentials)
14
14
  4 insufficient credits
15
15
  5 blocked by content moderation
16
- 6 rate limited`),e.addCommand(ye(t)),e.addCommand(ve(t)),e.addCommand(ke(t)),e.addCommand(Re(t)),e.addCommand($e(t)),e.addCommand(Ee(t)),e.addCommand(re(t)),At(e),e},At=t=>{t.configureHelp({helpWidth:100}),t.createHelp=()=>new Oe;for(let e of t.commands)At(e);};var Bn=process.env.MYNTH_DEBUG==="1"||process.env.MYNTH_DEBUG==="true",Yn=t=>t instanceof Error?t.message:String(t),St=t=>{t.exitOverride();for(let e of t.commands)St(e);},Gn=async()=>{let t=kt();St(t);try{await t.parseAsync(process.argv);}catch(e){let n=e.code;if(n==="commander.helpDisplayed"||n==="commander.version")return;(typeof n!="string"||!n.startsWith("commander."))&&(ae(Yn(e)),Bn&&e instanceof Error&&(ae("=== MYNTH_DEBUG cause ==="),ae(JSON.stringify(e.cause??e,null,2)))),process.exitCode=je(e);}};await Gn();
16
+ 6 rate limited`),e.addCommand(we(t)),e.addCommand(ve(t)),e.addCommand(Ae(t)),e.addCommand(Se(t)),e.addCommand(Ce(t)),e.addCommand(Pe(t)),e.addCommand(Oe(t)),e.addCommand(ie(t)),Tt(e),e},Tt=t=>{t.configureHelp({helpWidth:100}),t.createHelp=()=>new Ie;for(let e of t.commands)Tt(e);};var es=process.env.MYNTH_DEBUG==="1"||process.env.MYNTH_DEBUG==="true",ts=t=>t instanceof Error?t.message:String(t),Rt=t=>{t.exitOverride();for(let e of t.commands)Rt(e);},ns=async()=>{let t=_t();Rt(t);try{await t.parseAsync(process.argv);}catch(e){let n=e.code;if(n==="commander.helpDisplayed"||n==="commander.version")return;(typeof n!="string"||!n.startsWith("commander."))&&(oe(ts(e)),es&&e instanceof Error&&(oe("=== MYNTH_DEBUG cause ==="),oe(JSON.stringify(e.cause??e,null,2)))),process.exitCode=De(e);}};await ns();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mynthio/cli",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "Official Mynth CLI",
5
5
  "keywords": [
6
6
  "ai",