@mynthio/cli 0.0.6 → 0.0.8
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 +3 -1
- package/dist/bin.js +6 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -38,4 +38,6 @@ bun run test
|
|
|
38
38
|
bun run typecheck
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Built
|
|
41
|
+
Built with focused TypeScript CLI libraries: [`commander`](https://github.com/tj/commander.js),
|
|
42
|
+
[`chalk`](https://github.com/chalk/chalk), [`ora`](https://github.com/sindresorhus/ora), and
|
|
43
|
+
[`zod`](https://github.com/colinhacks/zod).
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import*as en from'@effect/platform-node/NodeContext';import*as tn from'@effect/platform-node/NodeRuntime';import*as g from'effect/Effect';import*as ie from'effect/Layer';import*as Ge from'effect/Logger';import*as nn from'effect/LogLevel';import*as z from'@effect/cli/Command';import*as O from'effect/Console';import*as R from'effect/Match';import*as G from'effect/Data';import*as A from'effect/Option';import*as bt from'effect/Redacted';import*as Z from'effect/Config';import*as st from'@effect/platform/FileSystem';import*as it from'@effect/platform/Path';import*as te from'cross-keychain';import*as p from'effect/Schema';import*as ke from'@effect/platform/HttpBody';import*as Ie from'@effect/platform/HttpClient';import*as M from'@effect/platform/HttpClientRequest';import*as Oe from'@effect/platform/HttpClientResponse';import*as J from'@effect/cli/Args';import*as i from'@effect/cli/Options';import*as Lt from'effect/Duration';import*as ce from'effect/Ref';import*as jt from'effect/Fiber';import*as Qt from'@effect/platform/FetchHttpClient';var P=class extends G.TaggedError("MynthCliError"){},Se=class extends G.TaggedError("NotAuthenticatedError"){},fe=class extends G.TaggedError("CredentialsStoreError"){},K=class extends G.TaggedError("WorkOSError"){},ve=class extends G.TaggedError("AuthorizationPendingError"){},be=class extends G.TaggedError("AuthorizationExpiredError"){},Te=class extends G.TaggedError("AuthorizationDeniedError"){},d=class extends G.TaggedError("MynthApiError"){};var ue=g.all({mynthApiUrl:Z.string("MYNTH_API_URL").pipe(Z.withDefault("https://api.mynth.io")),apiKeyEnvOverride:Z.redacted("MYNTH_API_KEY").pipe(Z.option)});var yt=p.Struct({id:p.String,email:p.String,first_name:p.NullOr(p.String).pipe(p.optional),last_name:p.NullOr(p.String).pipe(p.optional)}),ht=p.Struct({device_code:p.String,user_code:p.String,verification_uri:p.String,verification_uri_complete:p.String.pipe(p.optional),expires_in:p.Number,interval:p.Number.pipe(p.optional)}),Et=p.Struct({access_token:p.String,refresh_token:p.String,user:yt.pipe(p.optional),organization_id:p.String.pipe(p.optional)}),St=p.Struct({error:p.String.pipe(p.optional),error_description:p.String.pipe(p.optional),message:p.String.pipe(p.optional),code:p.String.pipe(p.optional)}),sn=p.Struct({kind:p.Literal("oauth"),access_token:p.String,refresh_token:p.String,expires_at:p.Number,user:yt.pipe(p.optional)}),rn=p.Struct({kind:p.Literal("api_key"),api_key:p.String}),vt=p.Union(sn,rn);var Xe="mynth-cli",Qe="default",an="credentials.json",me=e=>g.mapError(t=>new fe({message:e,cause:t})),De=(e,t)=>g.tryPromise({try:e,catch:n=>n}).pipe(g.map(A.some),g.catchAll(n=>{let o=n?.name;return o==="NoKeyringError"||o==="InitError"?g.succeed(A.none()):new fe({message:t,cause:n})})),kt=e=>g.try({try:()=>JSON.parse(e),catch:t=>new fe({message:"credentials JSON parse failed",cause:t})}).pipe(g.flatMap(p.decodeUnknown(vt)),me("credentials shape invalid")),Ot=e=>JSON.stringify(e),ln=e=>{let t=process.env.XDG_CONFIG_HOME,n=t&&t.length>0?t:e.join(process.env.HOME??".",".config");return e.join(n,"mynth")},ee=class extends g.Service()("CredentialsStore",{effect:g.gen(function*(){let t=yield*st.FileSystem,n=yield*it.Path,o=ln(n),s=n.join(o,an),c=g.gen(function*(){if(!(yield*t.exists(s).pipe(me("stat credentials file failed"))))return A.none();let m=yield*t.readFileString(s).pipe(me("read credentials file failed"));return A.some(yield*kt(m))}),y=g.fn("CredentialsStore.writeFile")(function*(r){yield*t.makeDirectory(o,{recursive:true}).pipe(me("create config dir failed")),yield*t.writeFileString(s,Ot(r)).pipe(me("write credentials file failed")),yield*t.chmod(s,384).pipe(g.ignore);}),v=g.gen(function*(){(yield*t.exists(s).pipe(g.orElseSucceed(()=>false)))&&(yield*t.remove(s).pipe(me("delete credentials file failed")));}),u=g.gen(function*(){let r=yield*De(()=>te.getPassword(Xe,Qe),"keychain get failed");if(A.isNone(r))return yield*c;let m=r.value;return m===null?A.none():A.some(yield*kt(m))}),h=g.fn("CredentialsStore.set")(function*(r){let m=yield*De(()=>te.setPassword(Xe,Qe,Ot(r)),"keychain set failed");if(A.isNone(m)){yield*y(r);return}yield*v.pipe(g.ignore);}),_=g.gen(function*(){yield*De(()=>te.deletePassword(Xe,Qe),"keychain delete failed").pipe(g.ignore),yield*v.pipe(g.ignore);}),l=De(()=>te.getKeyring(),"keychain probe failed").pipe(g.map(r=>A.isSome(r)&&r.value!==null));return {get:u,set:h,clear:_,usingKeychain:l,filePath:s}})}){};var Me="client_01KATK792RR5ZCHMF5YMNN1ZSE",Rt="https://api.workos.com";var cn="urn:ietf:params:oauth:grant-type:device_code",pn="refresh_token",dn=e=>g.try({try:()=>{let t=e.split(".");if(t.length<2)throw new Error("malformed jwt");let n=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8"));if(typeof n.exp!="number")throw new Error("jwt missing exp claim");return n.exp*1e3},catch:t=>new K({message:"could not decode access token",cause:t})}),fn=e=>ke.urlParams(Object.entries(e)),_t=e=>Oe.schemaBodyJson(St)(e).pipe(g.orElseSucceed(()=>({}))),xt=(e,t)=>_t(e).pipe(g.flatMap(n=>{let o=n.error??n.code;return new K({message:n.error_description??n.message??t,status:e.status,...o!==void 0?{code:o}:{}})})),$t=(e,t)=>g.gen(function*(){let n=yield*Oe.schemaBodyJson(Et)(e).pipe(g.mapError(s=>new K({message:`invalid ${t} response`,status:e.status,cause:s}))),o=yield*dn(n.access_token);return {token:n,expiresAt:o}}),un=g.fn("WorkOS.parseAuthenticate")(function*(e){if(e.status===200)return yield*$t(e,"token");let t=yield*_t(e),n=t.error??t.code;return yield*R.value(n).pipe(R.when("authorization_pending",()=>new ve({slowDown:false})),R.when("slow_down",()=>new ve({slowDown:true})),R.when("expired_token",()=>new be),R.when("access_denied",()=>new Te),R.orElse(()=>new K({message:t.error_description??t.message??"WorkOS error",status:e.status,...n!==void 0?{code:n}:{}})))}),ne=class extends g.Service()("WorkOS",{effect:g.gen(function*(){let n=(yield*Ie.HttpClient).pipe(Ie.mapRequest(M.prependUrl(Rt))),o=(u,h)=>M.post(u).pipe(M.setBody(h),M.setHeader("Accept","application/json")),s=(u,h)=>n.execute(o("/user_management/authenticate",u)).pipe(g.mapError(_=>new K({message:`${h} request failed`,cause:_}))),c=g.fn("WorkOS.deviceAuthorize")(function*(){let u=yield*n.execute(o("/user_management/authorize/device",fn({client_id:Me}))).pipe(g.mapError(h=>new K({message:"device authorize request failed",cause:h})));return u.status!==200?yield*xt(u,"device authorize failed"):yield*Oe.schemaBodyJson(ht)(u).pipe(g.mapError(h=>new K({message:"invalid device authorize response",cause:h})))}),y=g.fn("WorkOS.exchangeDeviceCode")(function*(u){let h=yield*s(ke.unsafeJson({grant_type:cn,client_id:Me,device_code:u}),"authenticate");return yield*un(h)}),v=g.fn("WorkOS.refresh")(function*(u){let h=yield*s(ke.unsafeJson({grant_type:pn,client_id:Me,refresh_token:u}),"refresh");return h.status===200?yield*$t(h,"refresh"):yield*xt(h,"refresh failed")});return {requestDeviceAuthorization:c,exchangeDeviceCode:y,refresh:v}})}){};var mn=6e4,Ze=e=>g.mapError(t=>new Se({reason:`${e}: ${t.message}`})),Le=e=>e?{user:e}:{},L=class extends g.Service()("Auth",{effect:g.gen(function*(){let t=yield*ue,n=yield*ee,o=yield*ne,s=A.map(t.apiKeyEnvOverride,bt.value),c=A.isSome(s),y=r=>g.gen(function*(){if(r.expires_at-Date.now()>mn)return r;let m=yield*o.refresh(r.refresh_token).pipe(Ze("token refresh failed")),a={kind:"oauth",access_token:m.token.access_token,refresh_token:m.token.refresh_token,expires_at:m.expiresAt,...Le(m.token.user??r.user)};return yield*n.set(a).pipe(Ze("could not persist refreshed token")),a}),v=g.gen(function*(){if(A.isSome(s))return {kind:"api_key",apiKey:s.value,source:"env"};let r=yield*n.get.pipe(Ze("could not read credentials"));if(A.isNone(r))return yield*new Se({reason:"no credentials configured"});let m=r.value;if(m.kind==="api_key")return {kind:"api_key",apiKey:m.api_key,source:"stored"};let a=yield*y(m);return {kind:"oauth",accessToken:a.access_token,...Le(a.user)}}),u=g.gen(function*(){if(A.isSome(s))return {kind:"env",source:"MYNTH_API_KEY"};let r=yield*n.get.pipe(g.orElseSucceed(()=>A.none()));if(A.isNone(r))return {kind:"none"};let m=r.value;return m.kind==="api_key"?{kind:"api_key"}:{kind:"oauth",expiresAt:m.expires_at,...Le(m.user)}}),h=r=>n.set({kind:"api_key",api_key:r}),_=r=>n.set({kind:"oauth",access_token:r.accessToken,refresh_token:r.refreshToken,expires_at:r.expiresAt,...Le(r.user)}),l=n.clear;return {resolve:v,status:u,setApiKey:h,saveOAuth:_,logout:l,envApiKeySet:c}})}){};var gn=e=>new Date(e).toISOString(),Tt=e=>g.mapError(t=>new P({message:`${e}: ${t.message}`,cause:t})),yn=g.fn("auth.pollForToken")(function*(e,t,n,o){let s=n;for(;;){if(Date.now()>=o)return yield*new P({message:"device code expired before approval"});let c=yield*g.either(e.exchangeDeviceCode(t));if(c._tag==="Right")return c.right;let y=c.left,v=R.value(y).pipe(R.tag("AuthorizationPendingError",u=>({kind:"retry",slowDown:u.slowDown})),R.tag("AuthorizationDeniedError",()=>({kind:"fail",message:"login denied by user"})),R.tag("AuthorizationExpiredError",()=>({kind:"fail",message:"device code expired"})),R.tag("WorkOSError",u=>({kind:"fail",message:u.message,cause:u})),R.exhaustive);if(v.kind==="fail")return yield*new P({message:v.message,..."cause"in v?{cause:v.cause}:{}});v.slowDown&&(s+=5e3),yield*g.sleep(`${s} millis`);}}),hn=z.make("login",{},()=>g.gen(function*(){let e=yield*L,t=yield*ne;if(e.envApiKeySet)return yield*O.log(`MYNTH_API_KEY is set in your environment; that takes precedence over login.
|
|
3
|
-
Unset it to use OAuth, or just continue using the env API key.`),yield*new P({message:"env api key takes precedence"});let n=yield*t.requestDeviceAuthorization().pipe(Tt("device authorize"));yield*O.log(""),yield*O.log(` First copy your one-time code: ${n.user_code}`),yield*O.log(` Then open: ${n.verification_uri_complete??n.verification_uri}`),yield*O.log(""),yield*O.log("Waiting for confirmation...");let o=yield*yn(t,n.device_code,(n.interval??5)*1e3,Date.now()+n.expires_in*1e3);yield*e.saveOAuth({accessToken:o.token.access_token,refreshToken:o.token.refresh_token,expiresAt:o.expiresAt,...o.token.user?{user:o.token.user}:{}}).pipe(Tt("could not save credentials"));let s=o.token.user?.email??o.token.user?.id??"unknown user";yield*O.log(`\u2713 Logged in as ${s}`);})),En=z.make("logout",{},()=>g.gen(function*(){let e=yield*L;yield*e.logout,yield*O.log("\u2713 Local credentials cleared"),e.envApiKeySet&&(yield*O.log("Note: MYNTH_API_KEY is still set in your environment and will be used."));})),Sn=z.make("status",{},()=>g.gen(function*(){let e=yield*L,t=yield*ee,n=yield*e.status,s=(yield*t.usingKeychain)?"system keychain":`file (${t.filePath})`,c=R.value(n).pipe(R.when({kind:"env"},()=>["Authenticated via env: MYNTH_API_KEY"]),R.when({kind:"none"},()=>["Not authenticated. Run `mynth auth login` or set an API key."]),R.when({kind:"api_key"},()=>[`Authenticated via stored API key (${s})`]),R.when({kind:"oauth"},y=>[`Authenticated via OAuth as ${y.user?.email??y.user?.id??"unknown user"} (${s})`,` access token expires: ${gn(y.expiresAt)}`]),R.exhaustive);yield*g.forEach(c,O.log);})),Dt=z.make("whoami",{},()=>g.gen(function*(){let t=yield*(yield*L).status;return yield*R.value(t).pipe(R.when({kind:"none"},()=>O.log("not authenticated").pipe(g.zipRight(new P({message:"not authenticated"})))),R.when({kind:"env"},()=>O.log("env:MYNTH_API_KEY")),R.when({kind:"api_key"},()=>O.log("api-key")),R.when({kind:"oauth"},n=>O.log(n.user?.email??n.user?.id??"oauth")),R.exhaustive)})),et=z.make("auth").pipe(z.withSubcommands([hn,En,Sn,Dt])),tt=Dt;var vn=g.tryPromise({try:async()=>{let e="";process.stdin.setEncoding("utf8");for await(let t of process.stdin)e+=t;return e.trim()},catch:e=>new P({message:"could not read stdin",cause:e})}),kn=J.text({name:"value"}).pipe(J.withDescription("API key value, or `-` to read from stdin")),On=z.make("api-key",{value:kn},({value:e})=>g.gen(function*(){let t=yield*L,n=e==="-"?yield*vn:e;if(n.length===0)return yield*new P({message:"API key is empty"});yield*t.setApiKey(n).pipe(g.mapError(o=>new P({message:`could not save API key: ${o.message}`,cause:o}))),yield*O.log("\u2713 API key saved"),t.envApiKeySet&&(yield*O.log("Note: MYNTH_API_KEY is also set in your environment and will take precedence."));})),Cn=z.make("set").pipe(z.withSubcommands([On])),wn=z.make("api-key",{},()=>g.gen(function*(){yield*(yield*L).logout,yield*O.log("\u2713 Stored credentials cleared");})),An=z.make("unset").pipe(z.withSubcommands([wn])),nt=z.make("config").pipe(z.withSubcommands([Cn,An]));var Rn=e=>e.kind==="api_key"?e.apiKey:e.accessToken,B=class extends g.Service()("MynthApi",{effect:g.gen(function*(){let t=yield*ue,n=yield*L,o=yield*Ie.HttpClient,s=yield*ce.make(A.none()),c=u=>g.gen(function*(){u&&(yield*ce.set(s,A.none()));let h=yield*ce.get(s);if(A.isSome(h))return h.value;let _=yield*n.resolve;return yield*ce.set(s,A.some(_)),_}),y=g.fn("MynthApi.attempt")(function*(u,h){let _=yield*c(h),l=u.pipe(M.prependUrl(t.mynthApiUrl),M.setHeader("Authorization",`Bearer ${Rn(_)}`));yield*g.logDebug("MynthApi request",{method:l.method,url:l.url,authKind:_.kind,forceRefresh:h});let r=yield*o.execute(l).pipe(g.tapErrorCause(m=>g.logDebug("MynthApi transport error",m)),g.mapError(m=>new d({message:`request failed: ${m.message}`,status:0,cause:m})));return yield*g.logDebug("MynthApi response",{status:r.status}),r});return {execute:u=>g.gen(function*(){let h=yield*y(u,false);return h.status!==401?h:yield*y(u,true)}),baseUrl:t.mynthApiUrl}})}){};var Ue=10,je=10,Ce=2,we=7,It=300*1e3,xn=12e3,_n=2500,$n=5e3,bn=(e,t,n)=>{try{let s=new URL(e).pathname.split("/").filter(Boolean).pop();if(s&&s.length>0)return decodeURIComponent(s)}catch{}return `${t}-${n}`},Tn={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},Dn=g.fn("ImageService.readImageFile")(function*(e){let t=yield*st.FileSystem,n=yield*it.Path,o=n.extname(e).toLowerCase(),s=Tn[o];if(!s)return yield*new d({message:`unsupported image extension "${o}" for ${e} (allowed: .jpg, .jpeg, .png, .webp)`,status:0});let c=yield*t.readFile(e).pipe(g.mapError(u=>new d({message:`could not read ${e}: ${u.message}`,status:0}))),y=n.basename(e),v=new ArrayBuffer(c.byteLength);return new Uint8Array(v).set(c),new File([v],y,{type:s})}),X=class extends g.Service()("ImageService",{effect:g.gen(function*(){let t=yield*B,n=yield*Ie.HttpClient,o=yield*ue,s=g.fn("ImageService.upload")(function*(l){if(l.length===0)return yield*new d({message:"no files to upload",status:0});if(l.length>Ue)return yield*new d({message:`too many files: ${l.length} (max ${Ue})`,status:0});yield*g.logDebug("ImageService.upload starting",{count:l.length,filePaths:l});let r=yield*g.forEach(l,Dn);yield*g.logDebug("ImageService.upload read files",r.map(f=>({name:f.name,size:f.size,type:f.type})));let m=new FormData;for(let f of r)m.append("images",f);let a=yield*t.execute(M.post("/image/upload").pipe(M.bodyFormData(m)));if(yield*g.logDebug("ImageService.upload response",{status:a.status}),a.status<200||a.status>=300){let f=yield*a.text.pipe(g.orElseSucceed(()=>""));return yield*new d({message:`upload failed (${a.status}): ${f||"no body"}`,status:a.status})}let E=yield*Oe.schemaBodyJson(Mn)(a).pipe(g.mapError(f=>new d({message:`invalid upload response: ${f.message}`,status:a.status})));return l.map((f,H)=>({path:f,url:E.urls[H]}))}),c=g.fn("ImageService.rate")(function*(l){if(l.urls.length===0)return yield*new d({message:"no image URLs to rate",status:0});if(l.urls.length>je)return yield*new d({message:`too many images: ${l.urls.length} (max ${je})`,status:0});if(l.levels!==void 0&&(l.levels.length<Ce||l.levels.length>we))return yield*new d({message:`levels must have between ${Ce} and ${we} items (got ${l.levels.length})`,status:0});yield*g.logDebug("ImageService.rate starting",{count:l.urls.length,hasLevels:l.levels!==void 0});let r=l.levels!==void 0?{urls:l.urls,mode:"custom",levels:l.levels}:{urls:l.urls,mode:"nsfw_sfw"},m=yield*ke.json(r).pipe(g.mapError(f=>new d({message:`could not encode rate body: ${f.reason}`,status:0,cause:f}))),a=yield*t.execute(M.post("/image/rate").pipe(M.setBody(m)));if(yield*g.logDebug("ImageService.rate response",{status:a.status}),a.status<200||a.status>=300){let f=yield*a.text.pipe(g.orElseSucceed(()=>""));return yield*new d({message:`rate failed (${a.status}): ${f||"no body"}`,status:a.status})}return yield*Oe.schemaBodyJson(Hn)(a).pipe(g.mapError(f=>new d({message:`invalid rate response: ${f.message}`,status:a.status})))}),y=g.fn("ImageService.generate")(function*(l){let r=l.requestPat?{...l.request,access:{...l.request.access,pat:{enabled:true}}}:l.request,m=yield*ke.json(r).pipe(g.mapError(f=>new d({message:`could not encode generate body: ${f.reason}`,status:0,cause:f}))),a=yield*t.execute(M.post("/image/generate").pipe(M.setBody(m)));if(a.status<200||a.status>=300){let f=yield*a.text.pipe(g.orElseSucceed(()=>""));return yield*new d({message:`generate failed (${a.status}): ${f||"no body"}`,status:a.status})}let E=yield*Oe.schemaBodyJson(Ln)(a).pipe(g.mapError(f=>new d({message:`invalid generate response: ${f.message}`,status:a.status})));return {taskId:E.taskId,pat:A.fromNullable(E.access?.publicAccessToken)}}),v=g.fn("ImageService.getTaskStatus")(function*(l,r){let m=`/tasks/${l}/status`,a=yield*A.match(r,{onSome:f=>n.execute(M.get(`${o.mynthApiUrl}${m}`).pipe(M.setHeader("Authorization",`Bearer ${f}`))).pipe(g.mapError(H=>new d({message:`task status request failed: ${H.message}`,status:0,cause:H}))),onNone:()=>t.execute(M.get(m))});if(a.status<200||a.status>=300){let f=yield*a.text.pipe(g.orElseSucceed(()=>""));return yield*new d({message:`task status failed (${a.status}): ${f||"no body"}`,status:a.status})}return (yield*Oe.schemaBodyJson(Nn)(a).pipe(g.mapError(f=>new d({message:`invalid task status response: ${f.message}`,status:a.status})))).status}),u=g.fn("ImageService.getTaskDetails")(function*(l){let r=yield*t.execute(M.get(`/tasks/${l}`));if(r.status<200||r.status>=300){let a=yield*r.text.pipe(g.orElseSucceed(()=>""));return yield*new d({message:`task details failed (${r.status}): ${a||"no body"}`,status:r.status})}return yield*r.json.pipe(g.mapError(a=>new d({message:`invalid task details response: ${a.message}`,status:r.status})))}),h=g.fn("ImageService.waitForTask")(function*(l,r){let m=Date.now();for(;;){let a=Date.now()-m;if(a>=It)return yield*new d({message:`task ${l} polling timed out after ${It}ms`,status:0});let E=yield*v(l,r);if(E==="completed")return yield*u(l);if(E==="failed")return yield*new d({message:`task ${l} failed during generation`,status:0});let f=a<xn?_n:$n;yield*g.sleep(Lt.millis(f+Math.floor(Math.random()*500)));}}),_=g.fn("ImageService.downloadImages")(function*(l){let r=yield*st.FileSystem,m=yield*it.Path,a=m.resolve(l.destinationDir);return yield*r.makeDirectory(a,{recursive:true}).pipe(g.mapError(E=>new d({message:`could not create output directory ${a}: ${E.message}`,status:0}))),yield*g.forEach(l.urls,(E,f)=>g.gen(function*(){let H=yield*n.execute(M.get(E)).pipe(g.mapError(W=>new d({message:`download failed for ${E}: ${W.message}`,status:0,cause:W})));if(H.status<200||H.status>=300)return yield*new d({message:`download failed for ${E} (${H.status})`,status:H.status});let xe=yield*H.arrayBuffer.pipe(g.mapError(W=>new d({message:`could not read body for ${E}: ${W.message}`,status:H.status,cause:W}))),_e=bn(E,l.taskId,f),de=m.join(a,_e);return yield*r.writeFile(de,new Uint8Array(xe)).pipe(g.mapError(W=>new d({message:`could not write ${de}: ${W.message}`,status:0}))),de}),{concurrency:4})});return {upload:s,rate:c,generate:y,waitForTask:h,getTaskDetails:u,downloadImages:_}}),dependencies:[B.Default]}){},Mn=p.Struct({urls:p.Array(p.String)}),In=p.Union(p.Struct({status:p.Literal("success"),url:p.String,level:p.String}),p.Struct({status:p.Literal("failed"),url:p.String,error:p.Struct({code:p.String})})),Hn=p.Struct({task:p.Struct({id:p.String,cost:p.String}),results:p.Array(In)}),Ln=p.Struct({taskId:p.String,access:p.optional(p.Struct({publicAccessToken:p.optional(p.String)}))}),Nn=p.Struct({status:p.Literal("pending","completed","failed")});var Pn=Lt.millis(60),Fn=2800,Nt=20,Ae=[22,28,34,70,76,112,120,156,194,230],Pt=["\u2726","\u2727","\u2736","\xB7","\u2022"],Un="\x1B[38;5;230m",jn="\x1B[38;5;156m",qn="\x1B[38;5;108m",We="\x1B[0m",Wn="\x1B[1m",Ft="\r\x1B[K",Kn="\x1B[?25l",zn="\x1B[?25h",Bn=["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","Teaching photons to behave","Taming entropy","Folding noise into form","Breathing life into latents","Tracing final strokes"],qt=()=>typeof process<"u"&&process.stderr&&process.stderr.isTTY===true,rt=e=>g.sync(()=>{qt()&&process.stderr.write(e);}),Jn=(e,t)=>{let n=Math.sin(e*.55+t*.18),o=Math.sin(e*.23-t*.11);return (n+o*.6)/1.6},Yn=e=>{let t=e%(Nt+6),n="";for(let o=0;o<Nt;o++){let c=(Jn(o,e)+1)/2,y=Math.min(Ae.length-1,Math.floor(c*Ae.length)),v=Math.abs(o-t);v<=1?y=Ae.length-1:v===2&&(y=Math.max(y,Ae.length-2));let u=Ae[y];if(v===0&&e%3===0){let h=Pt[e%Pt.length];n+=`\x1B[38;5;${u}m${h}`;}else n+=`\x1B[38;5;${u}m\u2588`;}return n+We},Gn=e=>{let t=e.slice();for(let n=t.length-1;n>0;n--){let o=Math.floor(Math.random()*(n+1));[t[n],t[o]]=[t[o],t[n]];}return t},Vn=(e,t)=>t[e%t.length]??"Working",Xn=(e,t,n)=>{let o=Yn(e),s=`${Wn}${jn}${t}\u2026${We}`,c=`${qn}${n.toFixed(1)}s${We}`;return `${`${Un}\u258D${We}`} ${o} ${s} ${c}`},Wt=(e,t={})=>{if(!qt())return e;let n=Gn(t.messages??Bn),o=g.gen(function*(){let s=Date.now(),c=0,y=0,v=Date.now();for(yield*rt(Kn);;){let u=Date.now();u-v>Fn&&(y++,v=u);let h=(u-s)/1e3,_=Xn(c,Vn(y,n),h);yield*rt(`${Ft}${_}`),c++,yield*g.sleep(Pn);}});return g.gen(function*(){let s=yield*g.fork(o);return yield*e.pipe(g.ensuring(g.gen(function*(){yield*jt.interrupt(s),yield*rt(`${Ft}${zn}`);})))})};var lt=i.boolean("json").pipe(i.withDescription("Output machine-readable JSON instead of a human-readable summary")),Qn=p.Array(p.Struct({value:p.String,description:p.String})),Zn=e=>g.sync(()=>e.indexOf("=")).pipe(g.flatMap(t=>t<=0?g.fail(new d({message:`invalid --level "${e}": expected "value=description"`,status:0})):g.succeed({value:e.slice(0,t).trim(),description:e.slice(t+1).trim()})),g.flatMap(t=>t.value.length===0||t.description.length===0?g.fail(new d({message:`invalid --level "${e}": value and description must be non-empty`,status:0})):g.succeed(t))),Kt=(e,t)=>g.try({try:()=>JSON.parse(e),catch:n=>new d({message:`invalid JSON in ${t}: ${n.message}`,status:0,cause:n})}).pipe(g.flatMap(n=>p.decodeUnknown(Qn)(n).pipe(g.mapError(o=>new d({message:`invalid levels in ${t}: expected array of { value, description }`,status:0,cause:o}))))),Jt=g.fn("image.resolveLevels")(function*(e){let t=[e.levelPairs.length>0?"--level":null,A.isSome(e.levelsFile)?"--levels-file":null,A.isSome(e.levelsJson)?"--levels-json":null].filter(s=>s!==null);if(t.length===0)return;if(t.length>1)return yield*new d({message:`conflicting level options: ${t.join(", ")} \u2014 use only one`,status:0});let n=yield*e.levelPairs.length>0?g.forEach(e.levelPairs,Zn):A.match(e.levelsFile,{onSome:s=>g.gen(function*(){let y=yield*(yield*st.FileSystem).readFileString(s).pipe(g.mapError(v=>new d({message:`could not read ${s}: ${v.message}`,status:0})));return yield*Kt(y,s)}),onNone:()=>Kt(A.getOrElse(e.levelsJson,()=>"[]"),"--levels-json")});if(n.length<Ce||n.length>we)return yield*new d({message:`levels must have between ${Ce} and ${we} items (got ${n.length})`,status:0});let o=new Set;for(let s of n){if(o.has(s.value))return yield*new d({message:`duplicate level value: "${s.value}"`,status:0});o.add(s.value);}return n}),Ke=e=>/^https?:\/\//i.test(e),eo=J.file({exists:"yes"}).pipe(J.withDescription("Path to a local image file (.jpg, .jpeg, .png, .webp)"),J.between(1,Ue)),to=z.make("upload",{files:eo,json:lt},({files:e,json:t})=>g.gen(function*(){let o=yield*(yield*X).upload(e);if(t){yield*O.log(JSON.stringify({images:o},null,2));return}yield*O.log(`\u2713 Uploaded ${o.length} image${o.length===1?"":"s"}`);for(let{path:s,url:c}of o)yield*O.log(` ${s}`),yield*O.log(` \u2192 ${c}`);})),no=J.text({name:"image"}).pipe(J.withDescription("Image URL (http://, https://) or path to a local image file to upload first"),J.between(1,je)),Yt=i.text("level").pipe(i.withAlias("l"),i.withDescription('Custom rating level as "value=description" (repeatable, 2\u20137 items). Example: -l safe="No explicit content" -l nsfw="Contains nudity"'),i.repeated),Gt=i.file("levels-file",{exists:"yes"}).pipe(i.withDescription('Path to a JSON file containing an array of { "value": string, "description": string } (2\u20137 items). Alternative to --level when descriptions contain special characters.'),i.optional),Vt=i.text("levels-json").pipe(i.withDescription('Inline JSON array of { "value": string, "description": string } (2\u20137 items). Alternative to --level / --levels-file.'),i.optional),oo=z.make("rate",{inputs:no,level:Yt,levelsFile:Gt,levelsJson:Vt,json:lt},({inputs:e,level:t,levelsFile:n,levelsJson:o,json:s})=>g.gen(function*(){let c=yield*X,y=yield*Jt({levelPairs:t,levelsFile:n,levelsJson:o}),v=e.filter(Ke),u=e.filter(E=>!Ke(E)),h=u.length>0?yield*c.upload(u):[],_=new Map(h.map(E=>[E.path,E.url])),l=e.map(E=>Ke(E)?E:_.get(E)??E),r=yield*c.rate({urls:l,...y?{levels:y}:{}});if(s){yield*O.log(JSON.stringify(r,null,2));return}let m=r.results.filter(E=>E.status==="success"),a=r.results.filter(E=>E.status==="failed");h.length>0&&(yield*O.log(`\u2713 Uploaded ${h.length} image${h.length===1?"":"s"}`)),yield*O.log(`\u2713 Rated ${m.length}/${r.results.length} (task ${r.task.id})`);for(let E of m)yield*O.log(` ${E.level} ${E.url}`);for(let E of a)yield*O.log(` ERROR ${E.error.code} ${E.url}`);if(v.length===0&&h.length>0){yield*O.log(""),yield*O.log("Uploaded source files:");for(let E of h)yield*O.log(` ${E.path} \u2192 ${E.url}`);}})),at=8,so=["auto","init","reference"],io=e=>g.sync(()=>{let t=e.indexOf(":"),n=/^https?:/i.test(e);if(t>0&&!n){let o=e.slice(0,t);if(so.includes(o))return {role:o,rest:e.slice(t+1)}}return {role:"reference",rest:e}}).pipe(g.flatMap(({role:t,rest:n})=>n.length===0?g.fail(new d({message:`invalid --input "${e}": missing path or URL`,status:0})):g.succeed({role:t,value:n,isFile:!Ke(n)}))),ro=i.text("prompt").pipe(i.withAlias("p"),i.withDescription("Text prompt describing the image to generate")),ao=i.text("negative").pipe(i.withAlias("n"),i.withDescription("Negative prompt (elements to exclude)"),i.optional),lo=i.choice("enhance",["prefer_magic","prefer_native","none"]).pipe(i.withDescription('Prompt enhancement mode: "prefer_magic" (Mynth) or "none". "prefer_native" is no longer supported by the API.'),i.optional),co=i.text("model").pipe(i.withAlias("m"),i.withDescription('Model ID (e.g. "black-forest-labs/flux.1-dev"). Default: "auto"'),i.optional),po=i.text("size").pipe(i.withAlias("s"),i.withDescription('Size preset or aspect ratio: "square", "portrait", "landscape", "1:1", "16:9", "16:9_4k", "auto", etc.'),i.optional),fo=i.integer("count").pipe(i.withAlias("c"),i.withDescription("Number of images to generate (default: 1)"),i.optional),uo=i.choice("format",["png","jpg","webp"]).pipe(i.withAlias("f"),i.withDescription("Output image format (default: webp)"),i.optional),mo=i.integer("quality").pipe(i.withAlias("q"),i.withDescription("Output quality 0\u2013100 (default: 80)"),i.optional),go=i.text("input").pipe(i.withAlias("i"),i.withDescription(`Input image as "[role:]path-or-url" (repeatable, up to ${at}). Role is one of: auto, init, reference (default: reference). Examples: -i ./img.jpg, -i reference:https://example.com/a.png, -i init:./seed.png`),i.repeated),yo=i.directory("output-dir",{exists:"either"}).pipe(i.withAlias("o"),i.withDescription("Directory to save generated images to. Created if it doesn't exist. Ignored in --async mode since the task hasn't completed yet."),i.optional),ho=i.text("destination").pipe(i.withDescription("Name (slug) of a user-configured destination to deliver the result to. Falls back to MYNTH_DESTINATION env var if not set."),i.optional),Eo=i.text("metadata").pipe(i.withDescription("Inline JSON object of custom metadata to attach to the task (max 2KB)"),i.optional),So=i.boolean("content-rating").pipe(i.withDescription("Enable content rating classification using default sfw/nsfw levels. For custom levels use --level / --levels-file / --levels-json.")),vo=i.boolean("async").pipe(i.withDescription("Return the task ID immediately instead of polling until completion")),ko=i.boolean("detailed").pipe(i.withDescription("Include full task data (all fields) in the output")),Oo=e=>g.try({try:()=>JSON.parse(e),catch:t=>new d({message:`invalid --metadata JSON: ${t.message}`,status:0,cause:t})}).pipe(g.flatMap(t=>t!==null&&typeof t=="object"&&!Array.isArray(t)?g.succeed(t):g.fail(new d({message:"--metadata must be a JSON object",status:0})))),Co=z.make("generate",{prompt:ro,negative:ao,enhance:lo,model:co,size:po,count:fo,format:uo,quality:mo,input:go,outputDir:yo,destination:ho,metadata:Eo,contentRating:So,level:Yt,levelsFile:Gt,levelsJson:Vt,async:vo,detailed:ko,json:lt},e=>g.gen(function*(){let t=yield*X,n=A.getOrUndefined(e.enhance);if(n==="prefer_native")return yield*new d({message:'--enhance prefer_native is no longer supported by the API; use "prefer_magic" or "none"',status:0});if(e.input.length>at)return yield*new d({message:`too many --input values: ${e.input.length} (max ${at})`,status:0});let o=yield*g.forEach(e.input,io),s=yield*A.match(e.metadata,{onSome:w=>Oo(w).pipe(g.map(A.some)),onNone:()=>g.succeed(A.none())}),c=yield*Jt({levelPairs:e.level,levelsFile:e.levelsFile,levelsJson:e.levelsJson});let y=o.filter(w=>w.isFile).map(w=>w.value),v=Array.from(new Set(y)),u=v.length>0?yield*t.upload(v):[],h=new Map(u.map(w=>[w.path,w.url])),_=o.map(w=>({type:"image",role:w.role,source:{type:"url",url:w.isFile?h.get(w.value)??w.value:w.value}})),l=A.getOrUndefined(e.negative),r={},m=A.getOrUndefined(e.format),a=A.getOrUndefined(e.quality);m!==void 0&&(r.format=m),a!==void 0&&(r.quality=a);let E=c!==void 0?{mode:"custom",levels:c}:e.contentRating?true:void 0,f={prompt:e.prompt},H=A.getOrUndefined(e.model),xe=A.getOrUndefined(e.size),_e=A.getOrUndefined(e.count),de=A.getOrUndefined(e.destination);if(H!==void 0&&(f.model=H),l!==void 0&&(f.negative_prompt=l),n==="prefer_magic"&&(f.magic_prompt=true),xe!==void 0&&(f.size=xe),_e!==void 0&&(f.count=_e),Object.keys(r).length>0&&(f.output=r),_.length>0&&(f.inputs=_),de!==void 0&&(f.destination=de),E!==void 0&&(f.rating=E),A.isSome(s)&&(f.metadata=s.value),e.async){let w=yield*t.generate({request:f,requestPat:true}),Ve={taskId:w.taskId,access:A.match(w.pat,{onSome:on=>({publicAccessToken:on}),onNone:()=>{}})};if(e.json){yield*O.log(JSON.stringify(Ve,null,2));return}yield*O.log(`\u2713 Task created: ${w.taskId}`),A.isSome(w.pat)&&(yield*O.log(` PAT: ${w.pat.value}`));return}let W=yield*t.generate({request:f,requestPat:true}),ut=t.waitForTask(W.taskId,W.pat),$e=yield*e.json?ut:Wt(ut),mt=A.getOrUndefined(e.outputDir),he=mt!==void 0?(yield*it.Path).resolve(mt):void 0,Ee=he!==void 0?yield*wo($e,he):[];if(e.json){let w=e.detailed?$e:Ao($e),Ve=he!==void 0?{...w,downloadedFiles:Ee}:w;yield*O.log(JSON.stringify(Ve,null,2));return}if(yield*Ro($e,u.length),he!==void 0&&Ee.length>0){yield*O.log(""),yield*O.log(`\u2713 Saved ${Ee.length} image${Ee.length===1?"":"s"} to ${he}`);for(let w of Ee)yield*O.log(` ${w}`);}})),wo=g.fn("image.downloadSucceededImages")(function*(e,t){let n=yield*X,s=((e.result??{}).images??[]).map(c=>c).filter(c=>c.status==="success").map(c=>c.url??c.mynth_url).filter(c=>typeof c=="string"&&c.length>0);return s.length===0?[]:yield*n.downloadImages({urls:s,destinationDir:t,taskId:e.id})}),Ao=e=>{let t=e.result??{},n=(t.images??[]).map(o=>{let s=o;return s.status==="success"?{status:"success",url:s.url??null,mynth_url:s.mynth_url??null,size:s.size,rating:s.rating}:{status:"failed",error:s.error,mynth_url:s.mynth_url??null}});return {taskId:e.id,status:e.status,images:n,...t.magic_prompt?{magic_prompt:t.magic_prompt}:{},...t.cost?.total!==void 0?{cost:t.cost.total}:{},...t.model!==void 0?{model:t.model}:{}}},Ro=g.fn("image.renderTaskHuman")(function*(e,t){let n=e.result??{},o=n.images??[];t>0&&(yield*O.log(`\u2713 Uploaded ${t} input image${t===1?"":"s"}`));let s=o.filter(c=>c.status==="success");if(yield*O.log(`\u2713 Generated ${s.length}/${o.length} image${o.length===1?"":"s"} (task ${e.id})`),n.model!==void 0&&(yield*O.log(` Model: ${n.model}`)),n.size_auto?.value!==void 0&&(yield*O.log(` Size: ${n.size_auto.value} (auto, ${n.size_auto.source})`)),n.cost?.total!==void 0&&(yield*O.log(` Cost: ${n.cost.total}`)),n.destination?.name!==void 0&&(yield*O.log(` Destination: ${n.destination.name}`)),n.magic_prompt?.positive!==void 0&&(yield*O.log(""),yield*O.log("Enhanced prompt (mynth):"),yield*O.log(` ${n.magic_prompt.positive}`),n.magic_prompt.negative!==void 0&&n.magic_prompt.negative.length>0&&(yield*O.log(` negative: ${n.magic_prompt.negative}`))),o.length>0){yield*O.log("");for(let c of o){let y=c;if(y.status==="success"){let v=y.rating,u=v?.level!==void 0?` [${v.level}]`:"",h=y.url??y.mynth_url;yield*O.log(` \u2713 ${h}${u}`);}else yield*O.log(` \u2717 ${y.error??"unknown error"}`);}}}),ct=z.make("image").pipe(z.withSubcommands([Co,to,oo]));var ge=class extends g.Service()("TaskService",{effect:g.gen(function*(){let t=yield*B;return {getTask:g.fn("TaskService.getTask")(function*(o){let s=yield*t.execute(M.get(`/tasks/${o}`));if(s.status<200||s.status>=300){let y=yield*s.text.pipe(g.orElseSucceed(()=>""));return yield*new d({message:`task fetch failed (${s.status}): ${y||"no body"}`,status:s.status})}return yield*s.json.pipe(g.mapError(y=>new d({message:`invalid task response: ${y.message}`,status:s.status})))})}}),dependencies:[B.Default]}){};var xo=i.boolean("json").pipe(i.withDescription("Output machine-readable JSON instead of a human-readable summary")),_o=J.text({name:"id"}).pipe(J.withDescription("Task ID")),$o=e=>{switch(e){case "completed":return "\u2713";case "failed":return "\u2717";default:return "\u2026"}},bo=g.fn("task.renderHuman")(function*(e){yield*O.log(`${$o(e.status)} Task ${e.id}`),yield*O.log(` Type: ${e.type}`),yield*O.log(` Status: ${e.status}`),e.cost!==null&&(yield*O.log(` Cost: ${e.cost}`)),yield*O.log(` Created: ${e.createdAt}`),yield*O.log(` Updated: ${e.updatedAt}`),e.result!==null&&e.result!==void 0&&(yield*O.log(""),yield*O.log("Result:"),yield*O.log(To(JSON.stringify(e.result,null,2),2)));}),To=(e,t)=>{let n=" ".repeat(t);return e.split(`
|
|
4
|
-
`).
|
|
5
|
-
`)},Do=z.make("get",{id:_o,json:xo},({id:e,json:t})=>g.gen(function*(){let o=yield*(yield*ge).getTask(e);if(t){yield*O.log(JSON.stringify(o,null,2));return}yield*bo(o);})),pt=z.make("task").pipe(z.withSubcommands([Do]));var Mo=z.make("mynth").pipe(z.withSubcommands([et,nt,ct,pt,tt])),dt=z.run(Mo,{name:"Mynth CLI",version:"0.0.6"});var Io=Qt.layer,Ho=ie.mergeAll(ee.Default,Io),Lo=ne.Default.pipe(ie.provideMerge(Ho)),No=L.Default.pipe(ie.provideMerge(Lo)),Po=B.Default.pipe(ie.provideMerge(No)),Fo=X.DefaultWithoutDependencies.pipe(ie.provideMerge(Po)),Zt=ge.DefaultWithoutDependencies.pipe(ie.provideMerge(Fo));var ft=process.env.MYNTH_DEBUG==="1"||process.env.MYNTH_DEBUG==="true",Uo=ft?Ge.pretty.pipe(ie.provide(Ge.minimumLogLevel(nn.Debug))):ie.empty,jo=ft?dt(process.argv).pipe(g.tapErrorCause(e=>g.sync(()=>{console.error("=== MYNTH_DEBUG cause ==="),console.error(JSON.stringify(e,null,2));}))):dt(process.argv);jo.pipe(g.provide(Zt),g.provide(en.layer),g.provide(Uo),tn.runMain({disableErrorReporting:!ft}));
|
|
2
|
+
import {Command,Option,Help,InvalidArgumentError}from'commander';import*as A 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 Be from'chalk';import Lt from'ora';var f=class extends Error{_tag="MynthCliError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},R=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;}},v=class extends Error{_tag="CredentialsStoreError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},k=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;}},x=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;}},l=class extends Error{_tag="MynthApiError";status;code;cause;constructor(e){super(e.message),this.name=this._tag,this.status=e.status,this.code=e.code,this.cause=e.cause;}};var Ze=6e4,F=t=>t?{user:t}:{},re=(t,e)=>new O({reason:e instanceof Error?`${t}: ${e.message}`:t}),J=class{constructor(e,n,r){this.config=e;this.store=n;this.workos=r;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(r){throw re("could not read credentials",r)}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,...F(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,...F(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,...F(e.user)});}async logout(){await this.store.clear();}async refreshIfNeeded(e){if(e.expires_at-Date.now()>Ze)return e;let n;try{n=await this.workos.refresh(e.refresh_token);}catch(s){throw re("token refresh failed",s)}let r={kind:"oauth",access_token:n.token.access_token,refresh_token:n.token.refresh_token,expires_at:n.expiresAt,...F(n.token.user??e.user)};try{await this.store.set(r);}catch(s){throw re("could not persist refreshed token",s)}return r}};var ke=()=>{let t=process.env.MYNTH_API_KEY;return {mynthApiUrl:process.env.MYNTH_API_URL??"https://api.mynth.io",...t!==void 0?{apiKeyEnvOverride:t}:{}}};var Ae=z.object({id:z.string(),email:z.string(),first_name:z.string().nullable().optional(),last_name:z.string().nullable().optional()}),_e=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()}),Se=z.object({access_token:z.string(),refresh_token:z.string(),user:Ae.optional(),organization_id:z.string().optional()}),Re=z.object({error:z.string().optional(),error_description:z.string().optional(),message:z.string().optional(),code:z.string().optional()}),Qe=z.object({kind:z.literal("oauth"),access_token:z.string(),refresh_token:z.string(),expires_at:z.number(),user:Ae.optional()}),et=z.object({kind:z.literal("api_key"),api_key:z.string()}),xe=z.union([Qe,et]),be=z.object({data:z.object({urls:z.array(z.string())})}),tt=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()})})]),$e=z.object({data:z.object({task:z.object({id:z.string(),cost:z.string()}),results:z.array(tt)})}),Ce=z.object({data:z.object({taskId:z.string(),access:z.object({publicAccessToken:z.string().optional()}).optional()})}),Te=z.object({data:z.object({status:z.union([z.literal("pending"),z.literal("completed"),z.literal("failed")])})});var ae="mynth-cli",oe="default",ct="credentials.json",dt=t=>{let e=t?.name;return e==="NoKeyringError"||e==="InitError"},W=async(t,e)=>{try{return {available:true,value:await t()}}catch(n){if(dt(n))return {available:false};throw new v({message:e,cause:n})}},Ee=t=>{let e;try{e=JSON.parse(t);}catch(r){throw new v({message:"credentials JSON parse failed",cause:r})}let n=xe.safeParse(e);if(!n.success)throw new v({message:"credentials shape invalid",cause:n.error});return n.data},Pe=t=>JSON.stringify(t),ut=()=>{let t=process.env.XDG_CONFIG_HOME,e=t&&t.length>0?t:join(homedir(),".config");return join(e,"mynth")},Oe=async t=>{try{return await stat(t),true}catch{return false}},q=class{filePath;dir;constructor(){this.dir=ut(),this.filePath=join(this.dir,ct);}async get(){let e=await W(()=>A.getPassword(ae,oe),"keychain get failed");if(e.available)return e.value===null?void 0:Ee(e.value);if(await Oe(this.filePath))try{return Ee(await readFile(this.filePath,"utf8"))}catch(n){throw n instanceof v?n:new v({message:"read credentials file failed",cause:n})}}async set(e){if((await W(()=>A.setPassword(ae,oe,Pe(e)),"keychain set failed")).available){await this.deleteFileSilently();return}await mkdir(this.dir,{recursive:true}).catch(r=>{throw new v({message:"create config dir failed",cause:r})}),await writeFile(this.filePath,Pe(e),"utf8").catch(r=>{throw new v({message:"write credentials file failed",cause:r})}),await chmod(this.filePath,384).catch(()=>{});}async clear(){await W(()=>A.deletePassword(ae,oe),"keychain delete failed").catch(()=>{}),await this.deleteFileSilently();}async usingKeychain(){let e=await W(()=>A.getKeyring(),"keychain probe failed");return e.available&&e.value!==null}async deleteFileSilently(){await Oe(this.filePath)&&await rm(this.filePath).catch(e=>{throw new v({message:"delete credentials file failed",cause:e})});}};var mt=t=>t.kind==="api_key"?t.apiKey:t.accessToken,I=async t=>{try{return await t.json()}catch(e){throw new l({message:`invalid JSON response: ${e.message}`,status:t.status,cause:e})}},Y=async t=>{try{return await t.text()}catch{return ""}},H=class{constructor(e,n){this.auth=n;this.baseUrl=e.mynthApiUrl;}baseUrl;cachedAuth;async execute(e,n={}){let r=await this.attempt(e,n,false);return r.status!==401?r:this.attempt(e,n,true)}async attempt(e,n,r){let s=await this.getAuth(r),a=new Headers(n.headers);a.set("Authorization",`Bearer ${mt(s)}`);try{return await fetch(`${this.baseUrl}${e}`,{...n,headers:a})}catch(c){throw new l({message:`request failed: ${c.message}`,status:0,cause:c})}}async getAuth(e){return e&&(this.cachedAuth=void 0),this.cachedAuth!==void 0?this.cachedAuth:(this.cachedAuth=await this.auth.resolve(),this.cachedAuth)}};var N=10,D=10,M=2,L=7,Ie=300*1e3,kt=12e3,At=2500,_t=5e3,St={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},Rt=t=>new Promise(e=>setTimeout(e,t)),xt=(t,e,n)=>{try{let s=new URL(t).pathname.split("/").filter(Boolean).pop();if(s&&s.length>0)return decodeURIComponent(s)}catch{}return `${e}-${n}`},bt=async t=>{let e=extname(t).toLowerCase(),n=St[e];if(!n)throw new l({message:`unsupported image extension "${e}" for ${t} (allowed: .jpg, .jpeg, .png, .webp)`,status:0});let r;try{r=await readFile(t);}catch(a){throw new l({message:`could not read ${t}: ${a.message}`,status:0,cause:a})}let s=r.buffer.slice(r.byteOffset,r.byteOffset+r.byteLength);return new File([new Uint8Array(s)],basename(t),{type:n})},B=async(t,e,n)=>{let r=e.safeParse(await I(t));if(!r.success)throw new l({message:n,status:t.status,cause:r.error});return r.data},j=async(t,e,n)=>{if(t.status>=200&&t.status<300)return;let r=await Y(t);throw new l({message:`${e} failed (${t.status}): ${r||"no body"}`,status:t.status})},$t=async(t,e,n)=>{let r=Array.from({length:t.length}),s=0,a=async()=>{for(;;){let c=s++,h=t[c];if(h===void 0)return;r[c]=await n(h,c);}};return await Promise.all(Array.from({length:Math.min(e,t.length)},a)),r},G=class{constructor(e){this.api=e;}async upload(e){if(e.length===0)throw new l({message:"no files to upload",status:0});if(e.length>N)throw new l({message:`too many files: ${e.length} (max ${N})`,status:0});let n=await Promise.all(e.map(bt)),r=new FormData;for(let c of n)r.append("images",c);let s=await this.api.execute("/image/upload",{method:"POST",body:r});await j(s,"upload");let a=await B(s,be,"invalid upload response");return e.map((c,h)=>({path:c,url:a.data.urls[h]}))}async rate(e){if(e.urls.length===0)throw new l({message:"no image URLs to rate",status:0});if(e.urls.length>D)throw new l({message:`too many images: ${e.urls.length} (max ${D})`,status:0});if(e.levels!==void 0&&(e.levels.length<M||e.levels.length>L))throw new l({message:`levels must have between ${M} 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"},r=await this.api.execute("/image/rate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});return await j(r,"rate"),(await B(r,$e,"invalid rate response")).data}async generate(e){let n=e.requestPat?{...e.request,access:{...e.request.access,pat:{enabled:true}}}:e.request,r=await this.api.execute("/image/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});await j(r,"generate");let s=await B(r,Ce,"invalid generate response"),a=s.data.access?.publicAccessToken;return {taskId:s.data.taskId,...a!==void 0?{pat:a}:{}}}async waitForTask(e,n){let r=Date.now();for(;;){let s=Date.now()-r;if(s>=Ie)throw new l({message:`task ${e} polling timed out after ${Ie}ms`,status:0});let a=await this.getTaskStatus(e,n);if(a==="completed")return this.getTaskDetails(e);if(a==="failed")throw new l({message:`task ${e} failed during generation`,status:0});let c=s<kt?At:_t;await Rt(c+Math.floor(Math.random()*500));}}async getTaskDetails(e){let n=await this.api.execute(`/tasks/${e}`);await j(n,"task details");let r=await I(n);if(r.data===void 0)throw new l({message:"invalid task details response: missing data",status:n.status});return r.data}async downloadImages(e){let n=resolve(e.destinationDir);try{await mkdir(n,{recursive:true});}catch(r){throw new l({message:`could not create output directory ${n}: ${r.message}`,status:0,cause:r})}return $t(e.urls,4,async(r,s)=>{let a;try{a=await fetch(r);}catch(m){throw new l({message:`download failed for ${r}: ${m.message}`,status:0,cause:m})}if(a.status<200||a.status>=300)throw new l({message:`download failed for ${r} (${a.status})`,status:a.status});let c;try{c=await a.arrayBuffer();}catch(m){throw new l({message:`could not read body for ${r}: ${m.message}`,status:a.status,cause:m})}let h=xt(r,e.taskId,s),y=join(n,h);try{await writeFile(y,new Uint8Array(c));}catch(m){throw new l({message:`could not write ${y}: ${m.message}`,status:0,cause:m})}return y})}async getTaskStatus(e,n){let r=`/tasks/${e}/status`,s;if(n!==void 0)try{s=await fetch(`${this.api.baseUrl}${r}`,{headers:{Authorization:`Bearer ${n}`}});}catch(c){throw new l({message:`task status request failed: ${c.message}`,status:0,cause:c})}else s=await this.api.execute(r);return await j(s,"task status"),(await B(s,Te,"invalid task status response")).data.status}};var X=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 s=await Y(n);throw new l({message:`task fetch failed (${n.status}): ${s||"no body"}`,status:n.status})}let r=await I(n);if(r.data===void 0)throw new l({message:"invalid task response: missing data",status:n.status});return r.data}};var V="client_01KATK792RR5ZCHMF5YMNN1ZSE",je="https://api.workos.com";var Ct="urn:ietf:params:oauth:grant-type:device_code",Tt="refresh_token",Et=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 k({message:"could not decode access token",cause:e})}},le=async t=>{try{return await t.json()}catch{return {}}},De=async t=>Re.catch({}).parse(await le(t)),Ne=async(t,e)=>{let n=await De(t),r=n.error??n.code;throw new k({message:n.error_description??n.message??e,status:t.status,...r!==void 0?{code:r}:{}})},Me=async(t,e)=>{let n=Se.safeParse(await le(t));if(!n.success)throw new k({message:`invalid ${e} response`,status:t.status,cause:n.error});return {token:n.data,expiresAt:Et(n.data.access_token)}},Pt=async t=>{if(t.status===200)return Me(t,"token");let e=await De(t),n=e.error??e.code;switch(n){case "authorization_pending":throw new x({slowDown:false});case "slow_down":throw new x({slowDown:true});case "expired_token":throw new $;case "access_denied":throw new C;default:throw new k({message:e.error_description??e.message??"WorkOS error",status:t.status,...n!==void 0?{code:n}:{}})}},Z=class{baseUrl=je;async requestDeviceAuthorization(){let e=await this.post("/user_management/authorize/device",new URLSearchParams({client_id:V}),"device authorize request failed");if(e.status!==200)return Ne(e,"device authorize failed");let n=_e.safeParse(await le(e));if(!n.success)throw new k({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:Ct,client_id:V,device_code:e}),"authenticate request failed",{"Content-Type":"application/json"});return Pt(n)}async refresh(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:Tt,client_id:V,refresh_token:e}),"refresh request failed",{"Content-Type":"application/json"});return n.status===200?Me(n,"refresh"):Ne(n,"refresh failed")}async post(e,n,r,s={}){try{return await fetch(`${this.baseUrl}${e}`,{method:"POST",body:n,headers:{Accept:"application/json",...s}})}catch(a){throw new k({message:r,cause:a})}}};var Le=()=>{let t=ke(),e=new q,n=new Z,r=new J(t,e,n),s=new H(t,r);return {auth:r,credentialsStore:e,images:new G(s),tasks:new X(s),workos:n}};var o=(t="")=>{process.stdout.write(`${t}
|
|
3
|
+
`);},Q=(t="")=>{process.stderr.write(`${t}
|
|
4
|
+
`);};var It=t=>new Date(t).toISOString(),jt=t=>new Promise(e=>setTimeout(e,t)),Ue=Be.green("\u2713"),ze=(t,e)=>new f({message:e instanceof Error?`${t}: ${e.message}`:t,cause:e}),Nt=async(t,e,n,r)=>{let s=n;for(;;){if(Date.now()>=r)throw new f({message:"device code expired before approval"});try{return await t.workos.exchangeDeviceCode(e)}catch(a){if(a instanceof x){a.slowDown&&(s+=5e3),await jt(s);continue}throw a instanceof C?new f({message:"login denied by user"}):a instanceof $?new f({message:"device code expired"}):a instanceof k?new f({message:a.message,cause:a}):a}}},ce=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 f({message:"env api key takes precedence"});let n;try{n=await t.workos.requestDeviceAuthorization();}catch(a){throw ze("device authorize",a)}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 r=await Nt(t,n.device_code,(n.interval??5)*1e3,Date.now()+n.expires_in*1e3);try{await t.auth.saveOAuth({accessToken:r.token.access_token,refreshToken:r.token.refresh_token,expiresAt:r.expiresAt,...r.token.user?{user:r.token.user}:{}});}catch(a){throw ze("could not save credentials",a)}let s=r.token.user?.email??r.token.user?.id??"unknown user";o(`${Ue} Logged in as ${s}`);}),e.command("logout").description("Clear local Mynth credentials").action(async()=>{await t.auth.logout(),o(`${Ue} 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(),s=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 (${s})`);return;case "oauth":{let a=n.user?.email??n.user?.id??"unknown user";o(`Authenticated via OAuth as ${a} (${s})`),o(` access token expires: ${It(n.expiresAt)}`);}}}),e.addCommand(ee(t)),e},ee=t=>new Command("whoami").description("Print the active Mynth identity").action(async()=>{let e=await t.auth.status();switch(e.kind){case "none":throw o("not authenticated"),new f({message:"not authenticated"});case "env":o("env:MYNTH_API_KEY");return;case "api_key":o("api-key");return;case "oauth":o(e.user?.email??e.user?.id??"oauth");}});var Fe=Be.green("\u2713"),Mt=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})}},ue=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 s=>{let a=s==="-"?await Mt():s;if(a.length===0)throw new f({message:"API key is empty"});try{await t.auth.setApiKey(a);}catch(c){throw new f({message:`could not save API key: ${c.message}`,cause:c})}o(`${Fe} API key saved`),t.auth.envApiKeySet&&o("Note: MYNTH_API_KEY is also set in your environment and will take precedence.");});let r=new Command("unset").description("Unset local CLI configuration");return r.command("api-key").description("Clear stored Mynth credentials").action(async()=>{await t.auth.logout(),o(`${Fe} Stored credentials cleared`);}),e.addCommand(n),e.addCommand(r),e};var Ut=["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"],zt=()=>typeof process<"u"&&process.stderr&&process.stderr.isTTY===true,Kt=t=>{let e=t.slice();for(let n=e.length-1;n>0;n--){let r=Math.floor(Math.random()*(n+1));[e[n],e[r]]=[e[r],e[n]];}return e},Je=async(t,e={})=>{if(!zt())return t;let n=Kt(e.messages??Ut),r=0,s=Lt({text:n[0]??"Working",stream:process.stderr}).start(),a=setInterval(()=>{r++,s.text=n[r%n.length]??"Working";},2800);try{return await t}finally{clearInterval(a),s.stop();}};var me=8,qt=["auto","init","reference"],_=Be.green("\u2713"),Ht=Be.red("\u2717"),pe=()=>new Option("--json","Output machine-readable JSON instead of a human-readable summary"),Yt=z.array(z.object({value:z.string(),description:z.string()})),ne=t=>/^https?:\/\//i.test(t),Ge=(t,e=[])=>[...e,t],We=t=>{let e=Number.parseInt(t,10);if(!Number.isInteger(e)||String(e)!==t)throw new R(`invalid integer: "${t}"`);return e},Bt=t=>{let e=t.indexOf("=");if(e<=0)throw new l({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 l({message:`invalid --level "${t}": value and description must be non-empty`,status:0});return n},qe=(t,e)=>{let n;try{n=JSON.parse(t);}catch(s){throw new l({message:`invalid JSON in ${e}: ${s.message}`,status:0,cause:s})}let r=Yt.safeParse(n);if(!r.success)throw new l({message:`invalid levels in ${e}: expected array of { value, description }`,status:0,cause:r.error});return r.data},He=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(s=>s!==null);if(e.length===0)return;if(e.length>1)throw new l({message:`conflicting level options: ${e.join(", ")} - use only one`,status:0});let n;if(t.levelPairs.length>0)n=t.levelPairs.map(Bt);else if(t.levelsFile!==void 0){let s;try{s=await readFile(t.levelsFile,"utf8");}catch(a){throw new l({message:`could not read ${t.levelsFile}: ${a.message}`,status:0,cause:a})}n=qe(s,t.levelsFile);}else n=qe(t.levelsJson??"[]","--levels-json");if(n.length<M||n.length>L)throw new l({message:`levels must have between ${M} and ${L} items (got ${n.length})`,status:0});let r=new Set;for(let s of n){if(r.has(s.value))throw new l({message:`duplicate level value: "${s.value}"`,status:0});r.add(s.value);}return n},Gt=t=>{let e=t.indexOf(":"),n=/^https?:/i.test(t),r="reference",s=t;if(e>0&&!n){let a=t.slice(0,e);qt.includes(a)&&(r=a,s=t.slice(e+1));}if(s.length===0)throw new l({message:`invalid --input "${t}": missing path or URL`,status:0});return {role:r,value:s,isFile:!ne(s)}},Xt=t=>{let e;try{e=JSON.parse(t);}catch(n){throw new l({message:`invalid --metadata JSON: ${n.message}`,status:0,cause:n})}if(e===null||typeof e!="object"||Array.isArray(e))throw new l({message:"--metadata must be a JSON object",status:0});return e},Ye=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"',Ge).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.'),Vt=(t,e)=>{let n=t.result??{},r=n.images??[];e>0&&o(`${_} Uploaded ${e} input image${e===1?"":"s"}`);let s=r.filter(a=>a.status==="success");if(o(`${_} Generated ${s.length}/${r.length} image${r.length===1?"":"s"} (task ${t.id})`),n.model!==void 0&&o(` Model: ${n.model}`),n.size_auto?.value!==void 0&&o(` Size: ${n.size_auto.value} (auto, ${n.size_auto.source})`),n.cost?.total!==void 0&&o(` Cost: ${n.cost.total}`),n.destination?.name!==void 0&&o(` Destination: ${n.destination.name}`),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}`)),r.length>0){o("");for(let a of r){let c=a;if(c.status==="success"){let h=c.rating,y=h?.level!==void 0?` [${h.level}]`:"",m=c.url??c.mynth_url;o(` ${_} ${m}${y}`);}else o(` ${Ht} ${c.error??"unknown error"}`);}}},Zt=t=>{let e=t.result??{},n=(e.images??[]).map(r=>{let s=r;return s.status==="success"?{status:"success",url:s.url??null,mynth_url:s.mynth_url??null,size:s.size,rating:s.rating}:{status:"failed",error:s.error,mynth_url:s.mynth_url??null}});return {taskId:t.id,status:t.status,images:n,...e.magic_prompt?{magic_prompt:e.magic_prompt}:{},...e.cost?.total!==void 0?{cost:e.cost.total}:{},...e.model!==void 0?{model:e.model}:{}}},Qt=async(t,e,n)=>{let s=((e.result??{}).images??[]).map(a=>a).filter(a=>a.status==="success").map(a=>a.url??a.mynth_url).filter(a=>typeof a=="string"&&a.length>0);return s.length===0?[]:t.downloadImages({urls:s,destinationDir:n,taskId:e.id})},he=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(pe()).action(async(s,a)=>{if(s.length>N)throw new l({message:`too many files: ${s.length} (max ${N})`,status:0});let c=await t.images.upload(s);if(a.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(pe());Ye(n),n.action(async(s,a)=>{if(s.length>D)throw new l({message:`too many images: ${s.length} (max ${D})`,status:0});let c=await He({levelPairs:a.level??[],levelsFile:a.levelsFile,levelsJson:a.levelsJson}),h=s.filter(ne),y=s.filter(d=>!ne(d)),m=y.length>0?await t.images.upload(y):[],U=new Map(m.map(d=>[d.path,d.url])),z=s.map(d=>ne(d)?d:U.get(d)??d),S=await t.images.rate({urls:z,...c?{levels:c}:{}});if(a.json){o(JSON.stringify(S,null,2));return}let T=S.results.filter(d=>d.status==="success"),b=S.results.filter(d=>d.status==="failed");m.length>0&&o(`${_} Uploaded ${m.length} image${m.length===1?"":"s"}`),o(`${_} Rated ${T.length}/${S.results.length} (task ${S.task.id})`);for(let d of T)o(` ${d.level} ${d.url}`);for(let d of b)o(` ERROR ${d.error.code} ${d.url}`);if(h.length===0&&m.length>0){o(""),o("Uploaded source files:");for(let d of m)o(` ${d.path} -> ${d.url}`);}});let r=e.command("generate").description("Generate images with Mynth");return r.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)",We).addOption(new Option("-f, --format <format>","Output image format (default: webp)").choices(["png","jpg","webp"])).option("-q, --quality <number>","Output quality 0-100 (default: 80)",We).option("-i, --input <value>",`Input image as "[role:]path-or-url" (repeatable, up to ${me}). Role is one of: auto, init, reference (default: reference). Examples: -i ./img.jpg, -i reference:https://example.com/a.png, -i init:./seed.png`,Ge).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(pe()),Ye(r),r.action(async s=>{if(s.prompt===void 0||s.prompt.length===0)throw new R("Expected to find option: '--prompt'");if(s.enhance==="prefer_native")throw new l({message:'--enhance prefer_native is no longer supported by the API; use "prefer_magic" or "none"',status:0});let a=s.input??[];if(a.length>me)throw new l({message:`too many --input values: ${a.length} (max ${me})`,status:0});let c=a.map(Gt),h=s.metadata!==void 0?Xt(s.metadata):void 0,y=await He({levelPairs:s.level??[],levelsFile:s.levelsFile,levelsJson:s.levelsJson}),m=c.filter(u=>u.isFile).map(u=>u.value),U=Array.from(new Set(m)),z=U.length>0?await t.images.upload(U):[],S=new Map(z.map(u=>[u.path,u.url])),T=c.map(u=>({type:"image",role:u.role,source:{type:"url",url:u.isFile?S.get(u.value)??u.value:u.value}})),b={};s.format!==void 0&&(b.format=s.format),s.quality!==void 0&&(b.quality=s.quality);let d=y!==void 0?{mode:"custom",levels:y}:s.contentRating?true:void 0,w={prompt:s.prompt};if(s.model!==void 0&&(w.model=s.model),s.negative!==void 0&&(w.negative_prompt=s.negative),s.enhance==="prefer_magic"&&(w.magic_prompt=true),s.size!==void 0&&(w.size=s.size),s.count!==void 0&&(w.count=s.count),Object.keys(b).length>0&&(w.output=b),T.length>0&&(w.inputs=T),s.destination!==void 0&&(w.destination=s.destination),d!==void 0&&(w.rating=d),h!==void 0&&(w.metadata=h),s.async){let u=await t.images.generate({request:w,requestPat:true}),se={taskId:u.taskId,...u.pat!==void 0?{access:{publicAccessToken:u.pat}}:{}};if(s.json){o(JSON.stringify(se,null,2));return}o(`${_} Task created: ${u.taskId}`),u.pat!==void 0&&o(` PAT: ${u.pat}`);return}let we=await t.images.generate({request:w,requestPat:true}),ve=t.images.waitForTask(we.taskId,we.pat),K=s.json?await ve:await Je(ve),E=s.outputDir!==void 0?resolve(s.outputDir):void 0,P=E!==void 0?await Qt(t.images,K,E):[];if(s.json){let u=s.detailed?K:Zt(K),se=E!==void 0?{...u,downloadedFiles:P}:u;o(JSON.stringify(se,null,2));return}if(Vt(K,z.length),E!==void 0&&P.length>0){o(""),o(`${_} Saved ${P.length} image${P.length===1?"":"s"} to ${E}`);for(let u of P)o(` ${u}`);}}),e};var tn=t=>{switch(t){case "completed":return "\u2713";case "failed":return "\u2717";default:return "\u2026"}},nn=(t,e)=>{let n=" ".repeat(e);return t.split(`
|
|
6
|
+
`).map(r=>`${n}${r}`).join(`
|
|
7
|
+
`)},sn=t=>{o(`${tn(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(nn(JSON.stringify(t.result,null,2),2)));},fe=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,r)=>{let s=await t.tasks.getTask(n);if(r.json){o(JSON.stringify(s,null,2));return}sn(s);}),e};var ye=class extends Help{optionTerm(e){return `(${e.flags.replaceAll("<","").replaceAll(">","")})`}subcommandTerm(e){let n=e.registeredArguments.map(r=>r.required?`${r.name()}${r.variadic?"...":""}`:`[${r.name()}]`).join(" ");return n.length>0?`${e.name()} ${n}`:e.name()}},Xe=()=>{let t=Le(),e=new Command("mynth");return e.description("Official Mynth CLI").version("0.0.7"),e.addCommand(ce(t)),e.addCommand(ue(t)),e.addCommand(he(t)),e.addCommand(fe(t)),e.addCommand(ee(t)),Ve(e),e},Ve=t=>{t.configureHelp({helpWidth:100}),t.createHelp=()=>new ye;for(let e of t.commands)Ve(e);};var ln=process.env.MYNTH_DEBUG==="1"||process.env.MYNTH_DEBUG==="true",cn=t=>t instanceof InvalidArgumentError||t instanceof Error?t.message:String(t),dn=async()=>{let t=Xe();t.exitOverride();try{await t.parseAsync(process.argv);}catch(e){if(e.code==="commander.helpDisplayed"||e.code==="commander.version")return;let n=cn(e);Q(n),ln&&e instanceof Error&&(Q("=== MYNTH_DEBUG cause ==="),Q(JSON.stringify(e.cause??e,null,2))),process.exitCode=(1);}};await dn();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mynthio/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Official Mynth CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"@effect/platform-node": "^0.96.0",
|
|
36
|
+
"chalk": "^5.6.2",
|
|
37
|
+
"commander": "^14.0.3",
|
|
39
38
|
"cross-keychain": "^1.1.0",
|
|
40
|
-
"
|
|
39
|
+
"ora": "^8.2.0",
|
|
40
|
+
"zod": "^3.25.76"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^25.5.0",
|