@mynthio/cli 0.0.15 → 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.
- package/README.md +31 -0
- package/dist/bin.js +14 -6
- 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.
|
|
@@ -61,6 +74,24 @@ suffix. Documentation commands do not require Mynth authentication.
|
|
|
61
74
|
|
|
62
75
|
Run `mynth --help` for the full command list.
|
|
63
76
|
|
|
77
|
+
## Exit codes
|
|
78
|
+
|
|
79
|
+
The CLI uses distinct exit codes so scripts and AI agents can branch without parsing error
|
|
80
|
+
messages:
|
|
81
|
+
|
|
82
|
+
| Code | Meaning |
|
|
83
|
+
| ---- | ----------------------------------------------------- |
|
|
84
|
+
| 0 | Success |
|
|
85
|
+
| 1 | Error (network, server, or unexpected failure) |
|
|
86
|
+
| 2 | Usage error (invalid arguments, flags, or request) |
|
|
87
|
+
| 3 | Authentication error (missing or invalid credentials) |
|
|
88
|
+
| 4 | Insufficient credits |
|
|
89
|
+
| 5 | Blocked by content moderation |
|
|
90
|
+
| 6 | Rate limited |
|
|
91
|
+
|
|
92
|
+
`task wait` also uses these for the awaited task's outcome: a task that failed due to content
|
|
93
|
+
moderation exits 5, any other failure exits 1.
|
|
94
|
+
|
|
64
95
|
## Development
|
|
65
96
|
|
|
66
97
|
```bash
|
package/dist/bin.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command,Option,Help,InvalidArgumentError}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 lt from'chalk';import yn from'ora';var y=class extends Error{_tag="MynthCliError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},f=class extends Error{_tag="CliUsageError";constructor(e){super(e),this.name=this._tag;}},P=class extends Error{_tag="NotAuthenticatedError";constructor(e={}){super(e.reason??"not authenticated"),this.name=this._tag;}},k=class extends Error{_tag="CredentialsStoreError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},A=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;}},R=class extends Error{_tag="AuthorizationExpiredError";constructor(){super("authorization expired"),this.name=this._tag;}},C=class extends Error{_tag="AuthorizationDeniedError";constructor(){super("authorization denied"),this.name=this._tag;}},c=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 wt=6e4,K=t=>t?{user:t}:{},de=(t,e)=>new P({reason:e instanceof Error?`${t}: ${e.message}`:t}),W=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 de("could not read credentials",s)}if(e===void 0)throw new P({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,...K(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,...K(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,...K(e.user)});}async logout(){await this.store.clear();}async refreshIfNeeded(e){if(e.expires_at-Date.now()>wt)return e;let n;try{n=await this.workos.refresh(e.refresh_token);}catch(a){throw de("token refresh failed",a)}let s={kind:"oauth",access_token:n.token.access_token,refresh_token:n.token.refresh_token,expires_at:n.expiresAt,...K(n.token.user??e.user)};try{await this.store.set(s);}catch(a){throw de("could not persist refreshed token",a)}return s}};var Ie=()=>{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 je=z.object({id:z.string(),email:z.string(),first_name:z.string().nullable().optional(),last_name:z.string().nullable().optional()}),Me=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()}),Ne=z.object({access_token:z.string(),refresh_token:z.string(),user:je.optional(),organization_id:z.string().optional()}),De=z.object({error:z.string().optional(),error_description:z.string().optional(),message:z.string().optional(),code:z.string().optional()}),vt=z.object({kind:z.literal("oauth"),access_token:z.string(),refresh_token:z.string(),expires_at:z.number(),user:je.optional()}),kt=z.object({kind:z.literal("api_key"),api_key:z.string()}),Le=z.union([vt,kt]),q=z.lazy(()=>z.union([z.string(),z.number(),z.boolean(),z.null(),z.array(q),z.record(q)])),Ue=z.object({data:z.object({urls:z.array(z.string())})}),At=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()})})]),Je=z.object({data:z.object({task:z.object({id:z.string(),status:z.literal("completed"),cost:z.string()}),results:z.array(At)})}),Fe=z.object({data:z.object({taskId:z.string(),access:z.object({publicAccessToken:z.string()}).optional()})}),H=z.object({data:z.object({status:z.union([z.literal("pending"),z.literal("completed"),z.literal("failed")])})}),St=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()}),B=z.object({data:St}),_t=z.object({id:z.string(),type:z.string(),status:z.string(),cost:z.string().nullable(),createdAt:z.string(),updatedAt:z.string()}),ze=z.object({data:z.array(_t)}),bt=z.object({perImage:z.object({base:z.string(),"4k":z.string().optional()}),perInput:z.string().optional()}),xt=z.object({id:z.string(),displayName:z.string().nullable(),pricing:bt.nullable()}),Ke=z.object({data:z.array(xt)});var ue="mynth-cli",me="default",It="credentials.json",jt=t=>{let e=t?.name;return e==="NoKeyringError"||e==="InitError"},Y=async(t,e)=>{try{return {available:true,value:await t()}}catch(n){if(jt(n))return {available:false};throw new k({message:e,cause:n})}},We=t=>{let e;try{e=JSON.parse(t);}catch(s){throw new k({message:"credentials JSON parse failed",cause:s})}let n=Le.safeParse(e);if(!n.success)throw new k({message:"credentials shape invalid",cause:n.error});return n.data},qe=t=>JSON.stringify(t),Mt=()=>{let t=process.env.XDG_CONFIG_HOME,e=t&&t.length>0?t:join(homedir(),".config");return join(e,"mynth")},He=async t=>{try{return await stat(t),true}catch{return false}},G=class{filePath;dir;constructor(){this.dir=Mt(),this.filePath=join(this.dir,It);}async get(){let e=await Y(()=>b.getPassword(ue,me),"keychain get failed");if(e.available)return e.value===null?void 0:We(e.value);if(await He(this.filePath))try{return We(await readFile(this.filePath,"utf8"))}catch(n){throw n instanceof k?n:new k({message:"read credentials file failed",cause:n})}}async set(e){if((await Y(()=>b.setPassword(ue,me,qe(e)),"keychain set failed")).available){await this.deleteFileSilently();return}await mkdir(this.dir,{recursive:true}).catch(s=>{throw new k({message:"create config dir failed",cause:s})}),await writeFile(this.filePath,qe(e),"utf8").catch(s=>{throw new k({message:"write credentials file failed",cause:s})}),await chmod(this.filePath,384).catch(()=>{});}async clear(){await Y(()=>b.deletePassword(ue,me),"keychain delete failed").catch(()=>{}),await this.deleteFileSilently();}async usingKeychain(){let e=await Y(()=>b.getKeyring(),"keychain probe failed");return e.available&&e.value!==null}async deleteFileSilently(){await He(this.filePath)&&await rm(this.filePath).catch(e=>{throw new k({message:"delete credentials file failed",cause:e})});}};var Nt=t=>t.kind==="api_key"?t.apiKey:t.accessToken,x=async t=>{try{return await t.json()}catch(e){throw new c({message:`invalid JSON response: ${e.message}`,status:t.status,cause:e})}},S=async t=>{try{return await t.text()}catch{return ""}},V=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 c({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 ${Nt(a)}`);try{return await fetch(`${this.baseUrl}${e}`,{...n,headers:o})}catch(l){throw new c({message:`request failed: ${l.message}`,status:0,cause:l})}}async getAuth(e){return e&&(this.cachedAuth=void 0),this.cachedAuth!==void 0?this.cachedAuth:(this.cachedAuth=await this.auth.resolve(),this.cachedAuth)}};var Dt=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},Be=async(t,e)=>{if(t.ok)return;let n=Dt(await S(t));throw new c({message:`${e} failed (${t.status}): ${n}`,status:t.status})},Ye=async(t,e,n)=>{try{return await fetch(t,n)}catch(s){throw new c({message:`${e} failed: ${s.message}`,status:0,cause:s})}},Ge=async(t,e)=>{try{return await t.text()}catch(n){throw new c({message:`${e} failed while reading the response: ${n.message}`,status:t.status,cause:n})}},Lt=t=>{let e=t.trim();if(e.length===0)throw new f("documentation path must not be empty");if(e.length>2048)throw new f("documentation path is too long");if(e.startsWith("//")||e.includes("://"))throw new f("documentation path must be a path, not a URL");if(e.includes("?")||e.includes("#")||e.includes("\\"))throw new f("documentation path must not contain a query, fragment, or backslash");let n=e.startsWith("/")?e.slice(1):e;if(n.endsWith(".md"))throw new f("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 f("documentation path contains an invalid segment");return s.join("/")},X=class{docsUrl;constructor(e){this.docsUrl=e.mynthDocsUrl.replace(/\/$/,"");}async get(e){let n=Lt(e),s=n.split("/").map(encodeURIComponent).join("/"),a=await Ye(`${this.docsUrl}/${s}.md`,`documentation page fetch for ${n}`);return await Be(a,`documentation page fetch for ${n}`),{path:n,content:await Ge(a,`documentation page fetch for ${n}`)}}async list(){let e=await Ye(`${this.docsUrl}/llms.txt`,"documentation index fetch");return await Be(e,"documentation index fetch"),Ge(e,"documentation index fetch")}};var M=10,N=10,D=2,L=7,Ve=300*1e3,Ht=12e3,Bt=2500,Yt=5e3,Gt={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},Vt=t=>new Promise(e=>setTimeout(e,t)),Xt=(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}`},Zt=async t=>{let e=extname(t).toLowerCase(),n=Gt[e];if(!n)throw new c({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 c({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})},I=async(t,e,n)=>{let s=e.safeParse(await x(t));if(!s.success)throw new c({message:n,status:t.status,cause:s.error});return s.data},j=async(t,e,n)=>{if(t.status>=200&&t.status<300)return;let s=await S(t);throw new c({message:`${e} failed (${t.status}): ${s||"no body"}`,status:t.status})},Qt=async(t,e,n)=>{let s=Array.from({length:t.length}),a=0,o=async()=>{for(;;){let l=a++,h=t[l];if(h===void 0)return;s[l]=await n(h,l);}};return await Promise.all(Array.from({length:Math.min(e,t.length)},o)),s},Z=class{constructor(e){this.api=e;}async upload(e){if(e.length===0)throw new c({message:"no files to upload",status:0});if(e.length>M)throw new c({message:`too many files: ${e.length} (max ${M})`,status:0});let n=await Promise.all(e.map(Zt)),s=new FormData;for(let l of n)s.append("images",l);let a=await this.api.execute("/image/upload",{method:"POST",body:s});await j(a,"upload");let o=await I(a,Ue,"invalid upload response");return e.map((l,h)=>({path:l,url:o.data.urls[h]}))}async rate(e){if(e.urls.length===0)throw new c({message:"no image URLs to rate",status:0});if(e.urls.length>N)throw new c({message:`too many images: ${e.urls.length} (max ${N})`,status:0});if(e.levels!==void 0&&(e.levels.length<D||e.levels.length>L))throw new c({message:`levels must have between ${D} and ${L} 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 j(s,"rate"),(await I(s,Je,"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 j(s,"generate");let a=await I(s,Fe,"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>=Ve)throw new c({message:`task ${e} polling timed out after ${Ve}ms`,status:0});let o=await this.getTaskStatus(e,n);if(o==="completed")return this.getTaskDetails(e);if(o==="failed")throw new c({message:`task ${e} failed during generation`,status:0});let l=a<Ht?Bt:Yt;await Vt(l+Math.floor(Math.random()*500));}}async getTaskDetails(e){let n=await this.api.execute(`/tasks/${e}`);return await j(n,"task details"),(await I(n,B,"invalid task details response")).data}async downloadImages(e){let n=resolve(e.destinationDir);try{await mkdir(n,{recursive:true});}catch(s){throw new c({message:`could not create output directory ${n}: ${s.message}`,status:0,cause:s})}return Qt(e.urls,4,async(s,a)=>{let o;try{o=await fetch(s);}catch(g){throw new c({message:`download failed for ${s}: ${g.message}`,status:0,cause:g})}if(o.status<200||o.status>=300)throw new c({message:`download failed for ${s} (${o.status})`,status:o.status});let l;try{l=await o.arrayBuffer();}catch(g){throw new c({message:`could not read body for ${s}: ${g.message}`,status:o.status,cause:g})}let h=Xt(s,e.taskId,a),w=join(n,h);try{await writeFile(w,new Uint8Array(l));}catch(g){throw new c({message:`could not write ${w}: ${g.message}`,status:0,cause:g})}return w})}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(l){throw new c({message:`task status request failed: ${l.message}`,status:0,cause:l})}else a=await this.api.execute(s);return await j(a,"task status"),(await I(a,H,"invalid task status response")).data.status}};var Q=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 S(e);throw new c({message:`models fetch failed (${e.status}): ${s||"no body"}`,status:e.status})}let n=Ke.safeParse(await x(e));if(!n.success)throw new c({message:"invalid models response",status:e.status,cause:n.error});return n.data.data}};var en=300*1e3,tn=12e3,nn=2500,sn=5e3,an=t=>new Promise(e=>setTimeout(e,t)),ee=class{constructor(e){this.api=e;}async getTask(e){let n=await this.api.execute(`/tasks/${e}`);if(n.status<200||n.status>=300){let a=await S(n);throw new c({message:`task fetch failed (${n.status}): ${a||"no body"}`,status:n.status})}let s=B.safeParse(await x(n));if(!s.success)throw new c({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}`);if(a.status<200||a.status>=300){let l=await S(a);throw new c({message:`task list failed (${a.status}): ${l||"no body"}`,status:a.status})}let o=ze.safeParse(await x(a));if(!o.success)throw new c({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`);if(n.status<200||n.status>=300){let a=await S(n);throw new c({message:`task status failed (${n.status}): ${a||"no body"}`,status:n.status})}let s=H.safeParse(await x(n));if(!s.success)throw new c({message:"invalid task status response",status:n.status,cause:s.error});return s.data.data.status}async waitForTask(e,n=en){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 c({message:`task ${e} did not complete within ${Math.round(n/1e3)}s`,status:0});let l=o<tn?nn:sn;await an(l+Math.floor(Math.random()*500));}}};var te="client_01KATK792RR5ZCHMF5YMNN1ZSE",Xe="https://api.workos.com";var rn="urn:ietf:params:oauth:grant-type:device_code",on="refresh_token",cn=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 A({message:"could not decode access token",cause:e})}},ge=async t=>{try{return await t.json()}catch{return {}}},Qe=async t=>De.catch({}).parse(await ge(t)),Ze=async(t,e)=>{let n=await Qe(t),s=n.error??n.code;throw new A({message:n.error_description??n.message??e,status:t.status,...s!==void 0?{code:s}:{}})},et=async(t,e)=>{let n=Ne.safeParse(await ge(t));if(!n.success)throw new A({message:`invalid ${e} response`,status:t.status,cause:n.error});return {token:n.data,expiresAt:cn(n.data.access_token)}},ln=async t=>{if(t.status===200)return et(t,"token");let e=await Qe(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 R;case "access_denied":throw new C;default:throw new A({message:e.error_description??e.message??"WorkOS error",status:t.status,...n!==void 0?{code:n}:{}})}},ne=class{baseUrl=Xe;async requestDeviceAuthorization(){let e=await this.post("/user_management/authorize/device",new URLSearchParams({client_id:te}),"device authorize request failed");if(e.status!==200)return Ze(e,"device authorize failed");let n=Me.safeParse(await ge(e));if(!n.success)throw new A({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:rn,client_id:te,device_code:e}),"authenticate request failed",{"Content-Type":"application/json"});return ln(n)}async refresh(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:on,client_id:te,refresh_token:e}),"refresh request failed",{"Content-Type":"application/json"});return n.status===200?et(n,"refresh"):Ze(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 A({message:s,cause:o})}}};var tt=()=>{let t=Ie(),e=new G,n=new ne,s=new W(t,e,n),a=new V(t,s);return {auth:s,credentialsStore:e,docs:new X(t),images:new Z(a),models:new Q(a),tasks:new ee(a),workos:n}};var i=(t="")=>{process.stdout.write(`${t}
|
|
3
|
-
`);},
|
|
4
|
-
`);};var
|
|
5
|
-
Unset it to use OAuth, or just continue using the env API key.`),new y({message:"env api key takes precedence"});let n;try{n=await t.workos.requestDeviceAuthorization();}catch(o){throw st("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 pn(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 st("could not save credentials",o)}let a=s.token.user?.email??s.token.user?.id??"unknown user";i(`${nt} Logged in as ${a}`);}),e.command("logout").description("Clear local Mynth credentials").action(async()=>{await t.auth.logout(),i(`${nt} 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: ${un(n.expiresAt)}`);}}}),e.addCommand(ae(t)),e},ae=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 y({message:"not authenticated"});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 rt=lt.green("\u2713"),hn=async()=>{try{let t="";process.stdin.setEncoding("utf8");for await(let e of process.stdin)t+=e;return t.trim()}catch(t){throw new y({message:"could not read stdin",cause:t})}},ye=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 hn():a;if(o.length===0)throw new y({message:"API key is empty"});try{await t.auth.setApiKey(o);}catch(l){throw new y({message:`could not save API key: ${l.message}`,cause:l})}i(`${rt} 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(`${rt} Stored credentials cleared`);}),e.addCommand(n),e.addCommand(s),e};var we=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 wn=["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"],vn=()=>typeof process<"u"&&process.stderr&&process.stderr.isTTY===true,kn=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},re=async(t,e={})=>{if(!vn())return t;let n=kn(e.messages??wn),s=0,a=yn({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 ve=20,bn="webp",xn=80,Se=["auto","person","garment","pose","source","reference"],T=lt.green("\u2713"),Tn=lt.red("\u2717"),ke=()=>new Option("--json","Output machine-readable JSON instead of a human-readable summary"),$n=z.array(z.object({value:z.string(),description:z.string()})),ie=t=>/^https?:\/\//i.test(t),dt=(t,e=[])=>[...e,t],ut=t=>{let e=Number.parseInt(t,10);if(!Number.isInteger(e)||String(e)!==t)throw new f(`invalid integer: "${t}"`);return e},Rn=t=>{let e=ut(t);if(e<1||e>100)throw new f(`invalid quality: "${t}" (expected 1-100)`);return e},Cn=t=>{let e=t.indexOf("=");if(e<=0)throw new c({message:`invalid --level "${t}": expected "value=description"`,status:0});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 c({message:`invalid --level "${t}": value and description must be non-empty`,status:0});return n},ot=(t,e)=>{let n;try{n=JSON.parse(t);}catch(a){throw new c({message:`invalid JSON in ${e}: ${a.message}`,status:0,cause:a})}let s=$n.safeParse(n);if(!s.success)throw new c({message:`invalid levels in ${e}: expected array of { value, description }`,status:0,cause:s.error});return s.data},it=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 c({message:`conflicting level options: ${e.join(", ")} - use only one`,status:0});let n;if(t.levelPairs.length>0)n=t.levelPairs.map(Cn);else if(t.levelsFile!==void 0){let a;try{a=await readFile(t.levelsFile,"utf8");}catch(o){throw new c({message:`could not read ${t.levelsFile}: ${o.message}`,status:0,cause:o})}n=ot(a,t.levelsFile);}else n=ot(t.levelsJson??"[]","--levels-json");if(n.length<D||n.length>L)throw new c({message:`levels must have between ${D} and ${L} items (got ${n.length})`,status:0});let s=new Set;for(let a of n){if(s.has(a.value))throw new c({message:`duplicate level value: "${a.value}"`,status:0});s.add(a.value);}return n},En=t=>{let e=t.indexOf(":"),n=/^https?:/i.test(t),s,a=t;if(e>0&&!n){let o=t.slice(0,e);if(!Se.includes(o))throw new c({message:`invalid --input as "${o}". Expected one of: ${Se.join(", ")}`,status:0});s=o,a=t.slice(e+1);}if(a.length===0)throw new c({message:`invalid --input "${t}": missing path or URL`,status:0});return {...s!==void 0?{as:s}:{},value:a,isFile:!ie(a)}},On=t=>{let e;try{e=JSON.parse(t);}catch(n){throw new c({message:`invalid --metadata JSON: ${n.message}`,status:0,cause:n})}if(e===null||typeof e!="object"||Array.isArray(e))throw new c({message:"--metadata must be a JSON object",status:0});return e},ct=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"',dt).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.'),_e=(t,e)=>{let n=t.result??{},s=n.images??[];e>0&&i(`${T} Uploaded ${e} input image${e===1?"":"s"}`);let a=s.filter(o=>o.status==="success");if(i(`${T} 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 l=o;if(l.status==="success"){let h=l.rating,w=h?.level!==void 0?` [${h.level}]`:"",g=l.url??l.mynth_url;i(` ${T} ${g}${w}`);}else i(` ${Tn} ${Pn(l.error)}`);}}},Pn=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}:{}}},In=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})},xe=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(ke()).action(async(a,o)=>{if(a.length>M)throw new c({message:`too many files: ${a.length} (max ${M})`,status:0});let l=await t.images.upload(a);if(o.json){i(JSON.stringify({images:l},null,2));return}i(`${T} Uploaded ${l.length} image${l.length===1?"":"s"}`);for(let{path:h,url:w}of l)i(` ${h}`),i(` -> ${w}`);});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(ke());ct(n),n.action(async(a,o)=>{if(a.length>N)throw new c({message:`too many images: ${a.length} (max ${N})`,status:0});let l=await it({levelPairs:o.level??[],levelsFile:o.levelsFile,levelsJson:o.levelsJson}),h=a.filter(ie),w=a.filter(d=>!ie(d)),g=w.length>0?await t.images.upload(w):[],ce=new Map(g.map(d=>[d.path,d.url])),U=a.map(d=>ie(d)?d:ce.get(d)??d),_=await t.images.rate({urls:U,...l?{levels:l}:{}});if(o.json){i(JSON.stringify(_,null,2));return}let J=_.results.filter(d=>d.status==="success"),F=_.results.filter(d=>d.status==="failed");g.length>0&&i(`${T} Uploaded ${g.length} image${g.length===1?"":"s"}`),i(`${T} Rated ${J.length}/${_.results.length} (task ${_.task.id})`);for(let d of J)i(` ${d.level} ${d.url}`);for(let d of F)i(` ERROR ${d.error.code} ${d.url}`);if(h.length===0&&g.length>0){i(""),i("Uploaded source files:");for(let d of g)i(` ${d.path} -> ${d.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)",
|
|
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
|
-
`)},
|
|
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
|
+
Exit codes:
|
|
10
|
+
0 success
|
|
11
|
+
1 error (network, server, or unexpected failure)
|
|
12
|
+
2 usage error (invalid arguments, flags, or request)
|
|
13
|
+
3 authentication error (missing or invalid credentials)
|
|
14
|
+
4 insufficient credits
|
|
15
|
+
5 blocked by content moderation
|
|
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();
|