@mynthio/cli 0.0.14 → 0.0.15
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 +15 -0
- package/dist/bin.js +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,21 @@ 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
|
+
### Tasks
|
|
29
|
+
|
|
30
|
+
Async workflows: fire a generation with `--async`, do other work, then wait for the result.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
task_id=$(mynth image generate -p "A neon koi pond" --async --json | jq -r .taskId)
|
|
34
|
+
mynth task wait "$task_id" --json # blocks until completed/failed, prints like sync generate
|
|
35
|
+
mynth task wait "$task_id" --timeout 600 # wait up to 10 minutes (default: 300s)
|
|
36
|
+
mynth task get "$task_id" # fetch once, no waiting
|
|
37
|
+
mynth task list --limit 10 # recent tasks, newest first
|
|
38
|
+
mynth task list --after tsk_... # next page: tasks created before that ID
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`task wait` exits non-zero if the task fails or the timeout is reached.
|
|
42
|
+
|
|
28
43
|
### Documentation
|
|
29
44
|
|
|
30
45
|
Fetch one page as Markdown or retrieve the complete documentation index:
|
package/dist/bin.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command,Option,Help,InvalidArgumentError}from'commander';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 {z}from'zod';import ot from'chalk';import sn from'ora';var f=class extends Error{_tag="MynthCliError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},y=class extends Error{_tag="CliUsageError";constructor(e){super(e),this.name=this._tag;}},O=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;}},R=class extends Error{_tag="AuthorizationPendingError";slowDown;constructor(e){super("authorization pending"),this.name=this._tag,this.slowDown=e.slowDown;}},$=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 ut=6e4,K=t=>t?{user:t}:{},ce=(t,e)=>new O({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 ce("could not read credentials",s)}if(e===void 0)throw new O({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()>ut)return e;let n;try{n=await this.workos.refresh(e.refresh_token);}catch(a){throw ce("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 ce("could not persist refreshed token",a)}return s}};var Te=()=>{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 Ee=z.object({id:z.string(),email:z.string(),first_name:z.string().nullable().optional(),last_name:z.string().nullable().optional()}),Pe=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()}),Oe=z.object({access_token:z.string(),refresh_token:z.string(),user:Ee.optional(),organization_id:z.string().optional()}),Ie=z.object({error:z.string().optional(),error_description:z.string().optional(),message:z.string().optional(),code:z.string().optional()}),mt=z.object({kind:z.literal("oauth"),access_token:z.string(),refresh_token:z.string(),expires_at:z.number(),user:Ee.optional()}),pt=z.object({kind:z.literal("api_key"),api_key:z.string()}),je=z.union([mt,pt]),q=z.lazy(()=>z.union([z.string(),z.number(),z.boolean(),z.null(),z.array(q),z.record(q)])),Me=z.object({data:z.object({urls:z.array(z.string())})}),gt=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()})})]),De=z.object({data:z.object({task:z.object({id:z.string(),status:z.literal("completed"),cost:z.string()}),results:z.array(gt)})}),Ne=z.object({data:z.object({taskId:z.string(),access:z.object({publicAccessToken:z.string()}).optional()})}),Ue=z.object({data:z.object({status:z.union([z.literal("pending"),z.literal("completed"),z.literal("failed")])})}),ht=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:ht}),ft=z.object({perImage:z.object({base:z.string(),"4k":z.string().optional()}),perInput:z.string().optional()}),yt=z.object({id:z.string(),displayName:z.string().nullable(),pricing:ft.nullable()}),Le=z.object({data:z.array(yt)});var le="mynth-cli",de="default",bt="credentials.json",Rt=t=>{let e=t?.name;return e==="NoKeyringError"||e==="InitError"},B=async(t,e)=>{try{return {available:true,value:await t()}}catch(n){if(Rt(n))return {available:false};throw new k({message:e,cause:n})}},Je=t=>{let e;try{e=JSON.parse(t);}catch(s){throw new k({message:"credentials JSON parse failed",cause:s})}let n=je.safeParse(e);if(!n.success)throw new k({message:"credentials shape invalid",cause:n.error});return n.data},Fe=t=>JSON.stringify(t),$t=()=>{let t=process.env.XDG_CONFIG_HOME,e=t&&t.length>0?t:join(homedir(),".config");return join(e,"mynth")},ze=async t=>{try{return await stat(t),true}catch{return false}},Y=class{filePath;dir;constructor(){this.dir=$t(),this.filePath=join(this.dir,bt);}async get(){let e=await B(()=>_.getPassword(le,de),"keychain get failed");if(e.available)return e.value===null?void 0:Je(e.value);if(await ze(this.filePath))try{return Je(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 B(()=>_.setPassword(le,de,Fe(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,Fe(e),"utf8").catch(s=>{throw new k({message:"write credentials file failed",cause:s})}),await chmod(this.filePath,384).catch(()=>{});}async clear(){await B(()=>_.deletePassword(le,de),"keychain delete failed").catch(()=>{}),await this.deleteFileSilently();}async usingKeychain(){let e=await B(()=>_.getKeyring(),"keychain probe failed");return e.available&&e.value!==null}async deleteFileSilently(){await ze(this.filePath)&&await rm(this.filePath).catch(e=>{throw new k({message:"delete credentials file failed",cause:e})});}};var Ct=t=>t.kind==="api_key"?t.apiKey:t.accessToken,T=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 ""}},G=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 ${Ct(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 Tt=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},Ke=async(t,e)=>{if(t.ok)return;let n=Tt(await S(t));throw new c({message:`${e} failed (${t.status}): ${n}`,status:t.status})},We=async(t,e,n)=>{try{return await fetch(t,n)}catch(s){throw new c({message:`${e} failed: ${s.message}`,status:0,cause:s})}},qe=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})}},Et=t=>{let e=t.trim();if(e.length===0)throw new y("documentation path must not be empty");if(e.length>2048)throw new y("documentation path is too long");if(e.startsWith("//")||e.includes("://"))throw new y("documentation path must be a path, not a URL");if(e.includes("?")||e.includes("#")||e.includes("\\"))throw new y("documentation path must not contain a query, fragment, or backslash");let n=e.startsWith("/")?e.slice(1):e;if(n.endsWith(".md"))throw new y("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 y("documentation path contains an invalid segment");return s.join("/")},V=class{docsUrl;constructor(e){this.docsUrl=e.mynthDocsUrl.replace(/\/$/,"");}async get(e){let n=Et(e),s=n.split("/").map(encodeURIComponent).join("/"),a=await We(`${this.docsUrl}/${s}.md`,`documentation page fetch for ${n}`);return await Ke(a,`documentation page fetch for ${n}`),{path:n,content:await qe(a,`documentation page fetch for ${n}`)}}async list(){let e=await We(`${this.docsUrl}/llms.txt`,"documentation index fetch");return await Ke(e,"documentation index fetch"),qe(e,"documentation index fetch")}};var M=10,D=10,N=2,U=7,He=300*1e3,Ut=12e3,Lt=2500,Jt=5e3,Ft={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},zt=t=>new Promise(e=>setTimeout(e,t)),Kt=(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}`},Wt=async t=>{let e=extname(t).toLowerCase(),n=Ft[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 T(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},X=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(Wt)),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,Me,"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>D)throw new c({message:`too many images: ${e.urls.length} (max ${D})`,status:0});if(e.levels!==void 0&&(e.levels.length<N||e.levels.length>U))throw new c({message:`levels must have between ${N} 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 j(s,"rate"),(await I(s,De,"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,Ne,"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>=He)throw new c({message:`task ${e} polling timed out after ${He}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<Ut?Lt:Jt;await zt(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,H,"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=Kt(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,Ue,"invalid task status response")).data.status}};var Z=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=Le.safeParse(await T(e));if(!n.success)throw new c({message:"invalid models response",status:e.status,cause:n.error});return n.data.data}};var Q=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=H.safeParse(await T(n));if(!s.success)throw new c({message:"invalid task response",status:n.status,cause:s.error});return s.data.data}};var ee="client_01KATK792RR5ZCHMF5YMNN1ZSE",Be="https://api.workos.com";var Ht="urn:ietf:params:oauth:grant-type:device_code",Bt="refresh_token",Yt=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})}},me=async t=>{try{return await t.json()}catch{return {}}},Ge=async t=>Ie.catch({}).parse(await me(t)),Ye=async(t,e)=>{let n=await Ge(t),s=n.error??n.code;throw new A({message:n.error_description??n.message??e,status:t.status,...s!==void 0?{code:s}:{}})},Ve=async(t,e)=>{let n=Oe.safeParse(await me(t));if(!n.success)throw new A({message:`invalid ${e} response`,status:t.status,cause:n.error});return {token:n.data,expiresAt:Yt(n.data.access_token)}},Gt=async t=>{if(t.status===200)return Ve(t,"token");let e=await Ge(t),n=e.error??e.code;switch(n){case "authorization_pending":throw new R({slowDown:false});case "slow_down":throw new R({slowDown:true});case "expired_token":throw new $;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}:{}})}},te=class{baseUrl=Be;async requestDeviceAuthorization(){let e=await this.post("/user_management/authorize/device",new URLSearchParams({client_id:ee}),"device authorize request failed");if(e.status!==200)return Ye(e,"device authorize failed");let n=Pe.safeParse(await me(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:Ht,client_id:ee,device_code:e}),"authenticate request failed",{"Content-Type":"application/json"});return Gt(n)}async refresh(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:Bt,client_id:ee,refresh_token:e}),"refresh request failed",{"Content-Type":"application/json"});return n.status===200?Ve(n,"refresh"):Ye(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 Xe=()=>{let t=Te(),e=new Y,n=new te,s=new W(t,e,n),a=new G(t,s);return {auth:s,credentialsStore:e,docs:new V(t),images:new X(a),models:new Z(a),tasks:new Q(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 f({message:"env api key takes precedence"});let n;try{n=await t.workos.requestDeviceAuthorization();}catch(o){throw Qe("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 Qt(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 Qe("could not save credentials",o)}let a=s.token.user?.email??s.token.user?.id??"unknown user";i(`${Ze} Logged in as ${a}`);}),e.command("logout").description("Clear local Mynth credentials").action(async()=>{await t.auth.logout(),i(`${Ze} 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: ${Xt(n.expiresAt)}`);}}}),e.addCommand(se(t)),e},se=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 f({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 tt=ot.green("\u2713"),tn=async()=>{try{let t="";process.stdin.setEncoding("utf8");for await(let e of process.stdin)t+=e;return t.trim()}catch(t){throw new f({message:"could not read stdin",cause:t})}},he=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 tn():a;if(o.length===0)throw new f({message:"API key is empty"});try{await t.auth.setApiKey(o);}catch(l){throw new f({message:`could not save API key: ${l.message}`,cause:l})}i(`${tt} 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(`${tt} Stored credentials cleared`);}),e.addCommand(n),e.addCommand(s),e};var fe=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 an=["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"],rn=()=>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},nt=async(t,e={})=>{if(!rn())return t;let n=on(e.messages??an),s=0,a=sn({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 ye=20,un="webp",mn=80,ke=["auto","person","garment","pose","source","reference"],b=ot.green("\u2713"),pn=ot.red("\u2717"),we=()=>new Option("--json","Output machine-readable JSON instead of a human-readable summary"),gn=z.array(z.object({value:z.string(),description:z.string()})),re=t=>/^https?:\/\//i.test(t),it=(t,e=[])=>[...e,t],ct=t=>{let e=Number.parseInt(t,10);if(!Number.isInteger(e)||String(e)!==t)throw new y(`invalid integer: "${t}"`);return e},hn=t=>{let e=ct(t);if(e<1||e>100)throw new y(`invalid quality: "${t}" (expected 1-100)`);return e},fn=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},st=(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=gn.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},at=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(fn);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=st(a,t.levelsFile);}else n=st(t.levelsJson??"[]","--levels-json");if(n.length<N||n.length>U)throw new c({message:`levels must have between ${N} and ${U} 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},yn=t=>{let e=t.indexOf(":"),n=/^https?:/i.test(t),s,a=t;if(e>0&&!n){let o=t.slice(0,e);if(!ke.includes(o))throw new c({message:`invalid --input as "${o}". Expected one of: ${ke.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:!re(a)}},wn=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},rt=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"',it).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.'),vn=(t,e)=>{let n=t.result??{},s=n.images??[];e>0&&i(`${b} Uploaded ${e} input image${e===1?"":"s"}`);let a=s.filter(o=>o.status==="success");if(i(`${b} 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(` ${b} ${g}${w}`);}else i(` ${pn} ${kn(l.error)}`);}}},kn=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"},An=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}:{}}},xn=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})},Ae=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(we()).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(`${b} 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(we());rt(n),n.action(async(a,o)=>{if(a.length>D)throw new c({message:`too many images: ${a.length} (max ${D})`,status:0});let l=await at({levelPairs:o.level??[],levelsFile:o.levelsFile,levelsJson:o.levelsJson}),h=a.filter(re),w=a.filter(d=>!re(d)),g=w.length>0?await t.images.upload(w):[],oe=new Map(g.map(d=>[d.path,d.url])),L=a.map(d=>re(d)?d:oe.get(d)??d),x=await t.images.rate({urls:L,...l?{levels:l}:{}});if(o.json){i(JSON.stringify(x,null,2));return}let J=x.results.filter(d=>d.status==="success"),F=x.results.filter(d=>d.status==="failed");g.length>0&&i(`${b} Uploaded ${g.length} image${g.length===1?"":"s"}`),i(`${b} Rated ${J.length}/${x.results.length} (task ${x.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,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
|
+
`);},se=(t="")=>{process.stderr.write(`${t}
|
|
4
|
+
`);};var un=t=>new Date(t).toISOString(),mn=t=>new Promise(e=>setTimeout(e,t)),nt=lt.green("\u2713"),st=(t,e)=>new y({message:e instanceof Error?`${t}: ${e.message}`:t,cause:e}),pn=async(t,e,n,s)=>{let a=n;for(;;){if(Date.now()>=s)throw new y({message:"device code expired before approval"});try{return await t.workos.exchangeDeviceCode(e)}catch(o){if(o instanceof $){o.slowDown&&(a+=5e3),await mn(a);continue}throw o instanceof C?new y({message:"login denied by user"}):o instanceof R?new y({message:"device code expired"}):o instanceof A?new y({message:o.message,cause:o}):o}}},he=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 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)",ut).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)",Rn).option("-i, --input <value>",`Input image as "[as:]path-or-url" (repeatable, up to ${ve}). as is optional and must be one of: ${Se.join(", ")}. Examples: -i ./img.jpg, -i source:https://example.com/a.png, -i reference:./style.png`,dt).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(ke()),ct(s),s.action(async a=>{let o=a.prompt??"";if(a.enhance==="prefer_native")throw new c({message:'--enhance prefer_native is no longer supported by the API; use "prefer_magic" or "none"',status:0});let l=a.input??[];if(l.length>ve)throw new c({message:`too many --input values: ${l.length} (max ${ve})`,status:0});let h=l.map(En),w=a.metadata!==void 0?On(a.metadata):void 0,g=await it({levelPairs:a.level??[],levelsFile:a.levelsFile,levelsJson:a.levelsJson}),ce=h.filter(u=>u.isFile).map(u=>u.value),U=Array.from(new Set(ce)),_=U.length>0?await t.images.upload(U):[],J=new Map(_.map(u=>[u.path,u.url])),F=h.map(u=>({type:"image",...u.as?{as:u.as}:{},source:{type:"url",url:u.isFile?J.get(u.value)??u.value:u.value}})),d=a.format!==void 0||a.quality!==void 0?{format:a.format??bn,quality:a.quality??xn}:void 0,Ee=g!==void 0?{mode:"custom",levels:g}:a.contentRating?true:void 0,v={prompt:o};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),d!==void 0&&(v.output=d),F.length>0&&(v.inputs=F),a.destination!==void 0&&(v.destination=a.destination),Ee!==void 0&&(v.rating=Ee),w!==void 0&&(v.metadata=w),a.async){let u=await t.images.generate({request:v,requestPat:true}),le={taskId:u.taskId,...u.pat!==void 0?{access:{publicAccessToken:u.pat}}:{}};if(a.json){i(JSON.stringify(le,null,2));return}i(`${T} Task created: ${u.taskId}`),u.pat!==void 0&&i(` PAT: ${u.pat}`);return}let Oe=await t.images.generate({request:v,requestPat:true}),Pe=t.images.waitForTask(Oe.taskId,Oe.pat),z=a.json?await Pe:await re(Pe),E=a.outputDir!==void 0?resolve(a.outputDir):void 0,O=E!==void 0?await In(t.images,z,E):[];if(a.json){let u=a.detailed?z:be(z),le=E!==void 0?{...u,downloadedFiles:O}:u;i(JSON.stringify(le,null,2));return}if(_e(z,_.length),E!==void 0&&O.length>0){i(""),i(`${T} Saved ${O.length} image${O.length===1?"":"s"} to ${E}`);for(let u of O)i(` ${u}`);}}),e};var Te=t=>t??"-",Mn=t=>{if(t.length===0){i("No models available.");return}let e=t.map(s=>({id:s.id,name:s.displayName??"-",base:Te(s.pricing?.perImage.base),fourK:Te(s.pricing?.perImage["4k"]),inputFee:Te(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}Mn(s);}),e};var mt=300,pt=t=>e=>{let n=Number.parseInt(e,10);if(!Number.isInteger(n)||String(n)!==e||n<=0)throw new f(`invalid ${t}: "${e}" (expected a positive integer)`);return n},ht=t=>{switch(t){case "completed":return "\u2713";case "failed":return "\u2717";default:return "\u2026"}},Dn=(t,e)=>{let n=" ".repeat(e);return t.split(`
|
|
7
7
|
`).map(s=>`${n}${s}`).join(`
|
|
8
|
-
`)},
|
|
8
|
+
`)},gt=t=>{i(`${ht(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(Dn(JSON.stringify(t.result,null,2),2)));},Ln=t=>{if(t.length===0){i("No tasks found.");return}for(let e of t){let n=e.cost!==null?` ${e.cost}`:"";i(`${ht(e.status)} ${e.id} ${e.type} ${e.status}${n} ${e.createdAt}`);}},Re=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}gt(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: ${mt})`,pt("--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??mt)*1e3,o=t.tasks.waitForTask(n,a),l=s.json?await o:await re(o);if(l.status==="failed"&&(process.exitCode=1),s.json){let h=s.detailed||l.type!=="image.generate"?l:be(l);i(JSON.stringify(h,null,2));return}if(l.type==="image.generate"&&l.status==="completed"){_e(l,0);return}gt(l);}),e.command("list").description("List recent tasks, newest first").option("--limit <number>","Max tasks to return (1-100, default: 20)",pt("--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}Ln(s);}),e};var Ce=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()}},ft=()=>{let t=tt(),e=new Command("mynth");return e.description("Official Mynth CLI").version("0.0.12"),e.addCommand(he(t)),e.addCommand(ye(t)),e.addCommand(we(t)),e.addCommand(xe(t)),e.addCommand($e(t)),e.addCommand(Re(t)),e.addCommand(ae(t)),yt(e),e},yt=t=>{t.configureHelp({helpWidth:100}),t.createHelp=()=>new Ce;for(let e of t.commands)yt(e);};var zn=process.env.MYNTH_DEBUG==="1"||process.env.MYNTH_DEBUG==="true",Kn=t=>t instanceof InvalidArgumentError||t instanceof Error?t.message:String(t),Wn=async()=>{let t=ft();t.exitOverride();try{await t.parseAsync(process.argv);}catch(e){if(e.code==="commander.helpDisplayed"||e.code==="commander.version")return;let n=Kn(e);se(n),zn&&e instanceof Error&&(se("=== MYNTH_DEBUG cause ==="),se(JSON.stringify(e.cause??e,null,2))),process.exitCode=(1);}};await Wn();
|