@mynthio/cli 0.0.19 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +216 -58
  2. package/dist/bin.js +17 -10
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,128 +1,286 @@
1
1
  # @mynthio/cli
2
2
 
3
- Official [Mynth](https://mynth.io) CLI.
3
+ Official [Mynth](https://mynth.io) CLI: generate, analyze, and deliver images from your terminal,
4
+ your CI, or an agent.
4
5
 
5
6
  ## Install
6
7
 
7
- Globally:
8
-
9
8
  ```bash
10
9
  npm install -g @mynthio/cli
11
- # then
12
10
  mynth --help
13
11
  ```
14
12
 
15
- Or one-off via `npx`:
13
+ Or run it once without installing:
16
14
 
17
15
  ```bash
18
16
  npx @mynthio/cli --help
19
17
  ```
20
18
 
21
- ## Usage
19
+ ## Authentication
20
+
21
+ The CLI always authenticates with an API key. `auth login` is a convenience that creates one for
22
+ you:
22
23
 
23
24
  ```bash
24
- mynth image generate --prompt "A cinematic product photo of a glass keyboard"
25
- mynth image generate -p "A watercolor city skyline" --size 16:9 --count 2
25
+ mynth auth login # browser login, then creates and stores an API key
26
+ mynth auth login --scopes generate,manage # narrow the created key
27
+ mynth config set api-key - # or store a key you already have, from stdin
28
+ export MYNTH_API_KEY=mak_... # or supply one per-process; wins over a stored key
26
29
  ```
27
30
 
28
- ### Image analysis
31
+ `auth login` opens a device login, then exchanges that short-lived session for a long-lived API key
32
+ named `mynth-cli (hostname)`. **Only the API key is stored** — no OAuth tokens ever reach disk.
33
+
34
+ Your browser is opened automatically when the CLI is running interactively on a desktop. It is not
35
+ opened over SSH, in CI, in a container, or when output is piped — the login URL is always printed as
36
+ well, so those cases still work. `--no-browser` disables it outright.
29
37
 
30
- Rate an image or generate accessibility alt text. Both commands accept one URL or local image file,
31
- create an asynchronous API task, and wait for its result:
38
+ This matters for unattended use. A browser session expires (7 days, or 2 days idle) and its refresh
39
+ token rotates on every use, which races when several commands run at once. An API key does neither,
40
+ and it can be inspected, limited, or revoked from the [dashboard](https://mynth.io/dashboard).
41
+
42
+ Since the key does not expire, set a spending limit on it in the dashboard if it will live on a
43
+ shared or long-lived machine.
32
44
 
33
45
  ```bash
34
- mynth image rate https://cdn.example.com/product.webp
35
- mynth image alt ./product.webp --json
46
+ mynth auth status # how this machine is authenticated; no API call
47
+ mynth whoami # verified against the API, so a revoked key fails here not mid-run
48
+ mynth auth logout # revokes the key it created, then clears the file
49
+ ```
50
+
51
+ Credentials live in `$XDG_CONFIG_HOME/mynth/credentials.json` (default `~/.config`), written `0600`.
52
+ The file holds the key and its id, nothing else — a key's name, scopes and spending limit can be
53
+ changed in the dashboard at any time, so `whoami` reads them live rather than caching a copy that
54
+ could disagree. `auth logout` revokes keys the CLI created; a key you supplied yourself is only
55
+ removed locally.
56
+
57
+ ## Generating images
58
+
59
+ ```bash
60
+ mynth image generate -p "A cinematic product photo of a glass keyboard"
61
+ mynth image generate -p "A watercolor city skyline" --size 16:9 --count 2 -o ./out
62
+ mynth image generate -p "A neon koi pond" --magic-prompt --format png
63
+ ```
64
+
65
+ | Flag | Purpose |
66
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------- |
67
+ | `-p, --prompt` | Prompt. Optional: some models (virtual try-on) work best from inputs alone. |
68
+ | `-n, --negative` | Negative prompt. |
69
+ | `--magic-prompt` | Let Mynth expand the prompt before generating. Off by default. |
70
+ | `-m, --model` | Model ID. Defaults to `auto`. See `mynth models list`. |
71
+ | `-s, --size` | Preset or aspect ratio: `square`, `landscape`, `16:9`, `16:9_4k`, `auto`, … |
72
+ | `-c, --count` | Images per request. |
73
+ | `-f, --format` | `png`, `jpg`, or `webp`. |
74
+ | `-i, --input` | Input image as `[role:]path-or-url`, repeatable. Roles: `auto`, `person`, `garment`, `pose`, `source`, `reference`. |
75
+ | `-o, --output-dir` | Download the results into this directory. |
76
+ | `--destination` | Deliver to a configured storage destination. Defaults to `MYNTH_DESTINATION`. |
77
+ | `--content-rating` | Classify each image `sfw`/`nsfw`. Use `--level` for custom levels. |
78
+ | `--webhook-url` | Deliver this task's events to a URL (repeatable). |
79
+ | `--no-dashboard-webhooks` | Skip dashboard-configured webhooks for this task. |
80
+ | `--metadata` | Inline JSON attached to the task. |
81
+
82
+ Local paths passed to `-i` are uploaded first; `https://` inputs are used as-is.
83
+
84
+ ### Cost before you spend it
85
+
86
+ ```bash
87
+ mynth balance # balance, reserved, available, key limit
88
+ mynth image generate -p "A neon koi pond" -c 10 --dry-run # validate + price, generate nothing
36
89
  ```
37
90
 
38
- ### Image review
91
+ Estimates for `--model auto` are an upper bound. Add `--json` to either command for machine-readable
92
+ output.
39
93
 
40
- Quality review with a multi-model panel (score 1–4, findings, and strengths):
94
+ ### Finding a model
95
+
96
+ `mynth models list` prints the catalog. Filters are applied locally, so they compose freely:
97
+
98
+ | Flag | Purpose |
99
+ | -------------- | -------------------------------------------------------------------- |
100
+ | `-s, --search` | Fuzzy match on model ID (which carries the org) and display name. |
101
+ | `--org` | Only one org, fuzzy matched — `--org bfl` finds `black-forest-labs`. |
102
+ | `--max-price` | Base per-image price at or below this, in USD. |
103
+ | `--min-price` | Base per-image price at or above this, in USD. |
104
+ | `--4k` | Only models that publish a 4K price. |
105
+ | `--capability` | `img2img` (bills for image inputs) or `txt2img` (prompt only). |
41
106
 
42
107
  ```bash
43
- mynth image review https://cdn.example.com/product.webp
44
- mynth image review ./shot.png --effort low # faster, cheaper triage panel
45
- mynth image review ./shot.png --json
108
+ mynth models list -s "gemini flash" # fuzzy: tolerates typos and word order
109
+ mynth models list --capability img2img --max-price 0.03
110
+ mynth models list --org bfl --json
46
111
  ```
47
112
 
48
- ### Cost and balance
113
+ Search is fuzzy, not substring: `sedream` finds Seedream, and results come back ranked by relevance.
114
+ A query that matches nothing prints `No models matched the filters.` and still exits `0`.
115
+
116
+ `--capability` is derived from pricing, because the catalog exposes no capability field: a model that
117
+ publishes `perInput` pricing bills for image inputs, so it accepts them. Models that both take a
118
+ prompt and accept images are therefore reported as `img2img`.
49
119
 
50
- Check spend before a batch run:
120
+ ## Analyzing images
121
+
122
+ Every analysis command takes one URL or one local file, and waits for the result:
51
123
 
52
124
  ```bash
53
- mynth balance # balance, reserved, available (+ key spending limit)
54
- mynth image generate -p "A neon koi pond" -m black-forest-labs/flux.1-dev -c 10 --dry-run
125
+ mynth image rate https://cdn.example.com/product.webp
126
+ mynth image rate ./shot.png -l kids="Safe for children" -l adults="Adults only"
127
+ mynth image alt ./product.webp --json
128
+ mynth image review ./shot.png # score 1-4, findings, strengths
129
+ mynth image review ./shot.png --effort low # faster, cheaper triage panel
55
130
  ```
56
131
 
57
- `--dry-run` validates the request server-side and prints the estimated cost without generating
58
- anything. Estimates for `--model auto` are an upper bound. Add `--json` to either command for
59
- machine-readable output.
132
+ Custom rating levels come from repeated `--level value=description`, `--levels-file`, or
133
+ `--levels-json` — one source at a time, 2 to 7 levels.
60
134
 
61
- ### Tasks
135
+ ## Tasks
62
136
 
63
- Async workflows: fire a generation with `--async`, do other work, then wait for the result.
137
+ Fire a generation, do other work, then collect the result:
64
138
 
65
139
  ```bash
66
140
  task_id=$(mynth image generate -p "A neon koi pond" --async --json | jq -r .taskId)
67
- mynth task wait "$task_id" --json # blocks until completed/failed, prints like sync generate
68
- mynth task wait "$task_id" --timeout 600 # wait up to 10 minutes (default: 300s)
69
- mynth task get "$task_id" # fetch once, no waiting
70
- mynth task list --limit 10 # recent tasks, newest first
71
- mynth task list --after tsk_... # next page: tasks created before that ID
141
+ mynth task wait "$task_id" --json # blocks; prints the same shape as a sync generate
142
+ mynth task wait "$task_id" --timeout 600
143
+ mynth task get "$task_id" # fetch once
144
+ mynth task result "$task_id" # just the result payload
145
+ mynth task list --limit 10 # newest first
146
+ mynth task list --after tsk_... # next page
72
147
  ```
73
148
 
74
- `task wait` exits non-zero if the task fails or the timeout is reached.
149
+ `--async --json` also returns a short-lived public access token, so browser or CI code can poll the
150
+ task without your API key.
75
151
 
76
- ### Documentation
152
+ `task wait` exits non-zero when the task fails or the timeout is hit. Transient API failures
153
+ (404, 429, 5xx, dropped connections) are retried while polling — the wait only gives up on them
154
+ after ~40s of consecutive failures, or immediately on an error that cannot self-heal (401, 403).
77
155
 
78
- Fetch one page as Markdown or retrieve the complete documentation index:
156
+ ## API keys
157
+
158
+ `auth login` creates a key for the machine you're on. For an app or a deploy target, create one
159
+ explicitly:
79
160
 
80
161
  ```bash
81
- mynth docs get guides/async-and-polling
82
- mynth docs list
162
+ mynth api-key create my-app # generate scope
163
+ mynth api-key create my-app --json | jq -r .key # capture it for a .env
164
+ mynth api-key list
165
+ mynth api-key delete key_... --yes
83
166
  ```
84
167
 
85
- Add `--json` to any documentation command for machine-readable output:
168
+ The key is printed once and cannot be retrieved again.
169
+
170
+ Keys created from the CLI only get the `generate` scope. That's what an app needs to call the image
171
+ API; `manage` and `keys` have to come from the
172
+ [dashboard](https://mynth.io/dashboard), because the API refuses scope escalation from a CLI
173
+ session. Registering webhooks and destinations for that app is done with _your_ credentials, so the
174
+ app's key doesn't need `manage`.
175
+
176
+ Set a spending limit on app keys in the dashboard — it's the cheapest way to bound a leaked key.
177
+
178
+ ## Destinations
179
+
180
+ Deliver generated images straight to your own storage. Secrets are read from a file or stdin, never
181
+ from the command line, so they stay out of shell history and `ps`.
86
182
 
87
183
  ```bash
88
- mynth docs get reference/webhooks --json
89
- mynth docs list --json
184
+ # Bunny — a single-field secret may be passed bare
185
+ printf 'my-storage-password' | mynth destination create bunny-prod \
186
+ --provider bunny --storage-zone my-zone --region de \
187
+ --path-template 'images/{id}' --url-template 'https://cdn.example.com/{path}' \
188
+ --secret -
189
+
190
+ # S3 or R2 — JSON secret
191
+ mynth destination create s3-prod \
192
+ --provider s3 --bucket my-bucket --region us-east-1 \
193
+ --path-template 'images/{id}' --secret ./s3-secret.json
194
+
195
+ mynth destination test dst_... # verify credentials with a probe upload
196
+ mynth destination list
197
+ mynth destination delete dst_... --yes
198
+ ```
199
+
200
+ `--file <path|->` still accepts a complete JSON body instead of the typed flags.
201
+ Then use it: `mynth image generate -p "..." --destination bunny-prod`.
202
+
203
+ ## Webhooks
204
+
205
+ ```bash
206
+ mynth webhook create --url https://example.com/hooks/mynth -e task.completed -e task.failed
207
+ mynth webhook create --url https://example.com/hooks/mynth -e all --api-key-id key_...
208
+ mynth webhook delete whk_... --yes
90
209
  ```
91
210
 
92
- `docs get` accepts a documentation path with an optional leading slash. Do not include the `.md`
93
- suffix. Documentation commands do not require Mynth authentication.
211
+ The signing secret is printed once, on create, and cannot be retrieved again.
212
+
213
+ By default a webhook only receives tasks created with an **API key** — that matches where webhooks
214
+ are actually consumed, on a server. Pass `--oauth-events` to also receive tasks created by OAuth
215
+ sessions (this CLI, the playground).
216
+
217
+ ## Documentation
94
218
 
95
- Run `mynth --help` for the full command list.
219
+ ```bash
220
+ mynth docs get guides/async-and-polling
221
+ mynth docs list
222
+ mynth docs get reference/webhooks --json
223
+ ```
224
+
225
+ Paths take an optional leading slash and must not include the `.md` suffix. Documentation commands
226
+ need no authentication.
96
227
 
97
228
  ## Exit codes
98
229
 
99
- The CLI uses distinct exit codes so scripts and AI agents can branch without parsing error
100
- messages:
230
+ Distinct exit codes so scripts and agents can branch without parsing error messages:
101
231
 
102
- | Code | Meaning |
103
- | ---- | ----------------------------------------------------- |
104
- | 0 | Success |
105
- | 1 | Error (network, server, or unexpected failure) |
106
- | 2 | Usage error (invalid arguments, flags, or request) |
107
- | 3 | Authentication error (missing or invalid credentials) |
108
- | 4 | Insufficient credits |
109
- | 5 | Blocked by content moderation |
110
- | 6 | Rate limited |
232
+ | Code | Meaning |
233
+ | ---- | -------------------------------------------------------------------- |
234
+ | 0 | Success |
235
+ | 1 | Error (network, server, or unexpected failure) |
236
+ | 2 | Usage error (invalid arguments, flags, or request) |
237
+ | 3 | Authentication error (missing, invalid, or under-scoped credentials) |
238
+ | 4 | Insufficient credits (account balance or API key spending limit) |
239
+ | 5 | Blocked by content moderation |
240
+ | 6 | Rate limited |
111
241
 
112
- `task wait` also uses these for the awaited task's outcome: a task that failed due to content
113
- moderation exits 5, any other failure exits 1.
242
+ `task wait` reports the awaited task's outcome the same way: a moderation block exits 5, any other
243
+ failure exits 1.
244
+
245
+ ## Environment
246
+
247
+ | Variable | Effect |
248
+ | --------------------- | ---------------------------------------------------------- |
249
+ | `MYNTH_API_KEY` | API key; takes precedence over stored credentials |
250
+ | `MYNTH_DESTINATION` | Default `--destination` for image generation |
251
+ | `MYNTH_DEBUG=1` | Print stack traces and error causes to stderr |
252
+ | `MYNTH_NO_KEYCHAIN=1` | Store credentials in a file instead of the system keychain |
253
+ | `MYNTH_API_URL` | Override the API base URL |
254
+ | `MYNTH_DOCS_URL` | Override the documentation base URL |
114
255
 
115
256
  ## Development
116
257
 
117
258
  ```bash
118
259
  cd packages/cli
119
260
  bun install
120
- bun run dev -- --help # run from sources
261
+ bun run dev -- --help # run from source
121
262
  bun run build # bundle to dist/bin.js
122
263
  bun run test
123
264
  bun run typecheck
124
265
  ```
125
266
 
126
- Built with focused TypeScript CLI libraries: [`commander`](https://github.com/tj/commander.js),
267
+ ### Layout
268
+
269
+ ```
270
+ src/
271
+ bin.ts entry point: parse argv, map errors to exit codes
272
+ program.ts command tree and help formatting
273
+ app.ts the config/session/api/docs bundle every command receives
274
+ config.ts environment and build-time constants
275
+ errors.ts error types and the exit-code contract
276
+ api/ one module per API resource, over a shared fetch client
277
+ auth/ credential file, device login, API key minting
278
+ commands/ one module per command; they only orchestrate
279
+ output/ printing, tables, spinners, and shared result renderers
280
+ utils/ parsing, file, download, and concurrency helpers
281
+ ```
282
+
283
+ Commands hold argument parsing and rendering; `api/` holds the wire format; nothing in `api/` knows
284
+ about Commander. Built with [`commander`](https://github.com/tj/commander.js),
127
285
  [`chalk`](https://github.com/chalk/chalk), [`ora`](https://github.com/sindresorhus/ora), and
128
286
  [`zod`](https://github.com/colinhacks/zod).
package/dist/bin.js CHANGED
@@ -1,16 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import {Command,Option,Help}from'commander';import {z as z$1}from'zod';import*as C 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 Jt from'chalk';import na from'ora';var b=class extends Error{_tag="MynthCliError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},l=class extends Error{_tag="CliUsageError";constructor(e){super(e),this.name=this._tag;}},$=class extends Error{_tag="NotAuthenticatedError";constructor(e={}){super(e.reason??"not authenticated"),this.name=this._tag;}},A=class extends Error{_tag="CredentialsStoreError";cause;constructor(e){super(e.message),this.name=this._tag,this.cause=e.cause;}},x=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;}},T=class extends Error{_tag="AuthorizationPendingError";slowDown;constructor(e){super("authorization pending"),this.name=this._tag,this.slowDown=e.slowDown;}},E=class extends Error{_tag="AuthorizationExpiredError";constructor(){super("authorization expired"),this.name=this._tag;}},P=class extends Error{_tag="AuthorizationDeniedError";constructor(){super("authorization denied"),this.name=this._tag;}},k={error:1,usage:2,auth:3,insufficientCredits:4,moderation:5,rateLimited:6},Zt={UNAUTHORIZED:k.auth,VALIDATION_ERROR:k.usage,INSUFFICIENT_BALANCE:k.insufficientCredits,RESTRICTED_CONTENT:k.moderation},Ke=t=>{if(t instanceof l)return k.usage;if(t instanceof $)return k.auth;if(t instanceof c){let n=t.code!==void 0?Zt[t.code]:void 0;return n!==void 0?n:t.status===401||t.status===403?k.auth:t.status===429?k.rateLimited:k.error}let e=t.code;return typeof e=="string"&&e.startsWith("commander.")?k.usage:k.error},ue=t=>{let e=t.result?.images,n=[...(t.errors??[]).map(a=>a.code),...(e??[]).map(a=>a.error?.code)].filter(a=>typeof a=="string");return n.find(a=>a==="RESTRICTED_CONTENT")??n[0]},ze=t=>ue(t)==="RESTRICTED_CONTENT"?k.moderation:k.error,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 qe=z$1.object({id:z$1.string(),email:z$1.string(),first_name:z$1.string().nullable().optional(),last_name:z$1.string().nullable().optional()}),Be=z$1.object({device_code:z$1.string(),user_code:z$1.string(),verification_uri:z$1.string(),verification_uri_complete:z$1.string().optional(),expires_in:z$1.number(),interval:z$1.number().optional()}),He=z$1.object({access_token:z$1.string(),refresh_token:z$1.string(),user:qe.optional(),organization_id:z$1.string().optional()}),Ye=z$1.object({error:z$1.string().optional(),error_description:z$1.string().optional(),message:z$1.string().optional(),code:z$1.string().optional()}),Qt=z$1.object({kind:z$1.literal("oauth"),access_token:z$1.string(),refresh_token:z$1.string(),expires_at:z$1.number(),user:qe.optional()}),en=z$1.object({kind:z$1.literal("api_key"),api_key:z$1.string()}),Ve=z$1.union([Qt,en]),I=z$1.lazy(()=>z$1.union([z$1.string(),z$1.number(),z$1.boolean(),z$1.null(),z$1.array(I),z$1.record(I)])),Ge=z$1.object({data:z$1.object({urls:z$1.array(z$1.string())})}),K=z$1.object({data:z$1.object({taskId:z$1.string(),estimatedCost:z$1.string()})}),Xe=K,Ze=K,Qe=z$1.object({url:z$1.string(),level:z$1.string()}),et=z$1.object({url:z$1.string(),alt:z$1.string()}),tt=z$1.object({data:z$1.object({taskId:z$1.string(),estimatedCost:z$1.string().optional(),access:z$1.object({publicAccessToken:z$1.string()}).optional()})}),nt=z$1.object({data:z$1.object({estimatedCost:z$1.string(),currency:z$1.string(),estimateKind:z$1.union([z$1.literal("exact"),z$1.literal("upper_bound")])})}),at=z$1.object({data:z$1.object({userId:z$1.string(),auth:z$1.object({method:z$1.string(),apiKey:z$1.object({id:z$1.string(),name:z$1.string().nullable(),keyPreview:z$1.string()}).optional()})})}),st=z$1.object({data:z$1.object({balance:z$1.string(),reserved:z$1.string(),available:z$1.string(),currency:z$1.string(),apiKey:z$1.object({spendingLimit:z$1.string(),spendingLimitPeriod:z$1.string(),usedInPeriod:z$1.string(),remainingInPeriod:z$1.string()}).optional()})}),z=z$1.object({data:z$1.object({status:z$1.union([z$1.literal("pending"),z$1.literal("completed"),z$1.literal("failed")])})}),tn=z$1.object({id:z$1.string(),type:z$1.union([z$1.literal("image.generate"),z$1.literal("image.rate"),z$1.literal("image.alt"),z$1.literal("image.review")]),status:z$1.union([z$1.literal("pending"),z$1.literal("completed"),z$1.literal("failed")]),userId:z$1.string(),apiKeyId:z$1.string().nullable(),cost:z$1.string().nullable(),request:I,result:I,errors:z$1.array(z$1.object({code:z$1.string()})).nullable().optional(),createdAt:z$1.string(),updatedAt:z$1.string()}),q=z$1.object({data:tn}),nn=z$1.object({id:z$1.string(),type:z$1.string(),status:z$1.string(),cost:z$1.string().nullable(),createdAt:z$1.string(),updatedAt:z$1.string()}),rt=z$1.object({data:z$1.array(nn)}),an=z$1.object({perImage:z$1.object({base:z$1.string(),"4k":z$1.string().optional()}),perInput:z$1.string().optional()}),sn=z$1.object({id:z$1.string(),displayName:z$1.string().nullable(),pricing:an.nullable()}),ot=z$1.object({data:z$1.array(sn)}),it=z$1.object({id:z$1.string(),name:z$1.string(),provider:I,config:I,createdAt:z$1.string(),updatedAt:z$1.string()}),dt=z$1.object({data:it}),ct=z$1.object({data:z$1.array(it)}),lt=z$1.union([z$1.literal("all"),z$1.array(z$1.string())]),ut=z$1.object({data:z$1.object({id:z$1.string(),userId:z$1.string().optional(),enabled:z$1.boolean(),url:z$1.string(),secret:z$1.string(),events:lt,createdAt:z$1.string().optional(),updatedAt:z$1.string().optional()})}),mt=z$1.object({data:z$1.object({id:z$1.string(),enabled:z$1.boolean().optional(),url:z$1.string(),events:lt})});var rn=t=>t.kind==="api_key"?t.apiKey:t.accessToken,w=async t=>{try{return await t.json()}catch(e){throw new c({message:`invalid JSON response: ${e.message}`,status:t.status,cause:e})}},J=async t=>{try{return await t.text()}catch{return ""}},h=async(t,e)=>{if(t.status>=200&&t.status<300)return;let n=await J(t),a;try{let s=JSON.parse(n);typeof s.code=="string"&&(a=s.code);}catch{}throw new c({message:`${e} failed (${t.status}): ${n||"no body"}`,status:t.status,...a!==void 0?{code:a}:{}})},B=class{constructor(e,n){this.auth=n;this.baseUrl=e.mynthApiUrl;}baseUrl;cachedAuth;async execute(e,n={}){let a=await this.attempt(e,n,false);return a.status!==401?a:this.attempt(e,n,true)}async executePublic(e,n={}){try{return await fetch(`${this.baseUrl}${e}`,n)}catch(a){throw new c({message:`request failed: ${a.message}`,status:0,cause:a})}}async attempt(e,n,a){let s=await this.getAuth(a),i=new Headers(n.headers);i.set("Authorization",`Bearer ${rn(s)}`);try{return await fetch(`${this.baseUrl}${e}`,{...n,headers:i})}catch(d){throw new c({message:`request failed: ${d.message}`,status:0,cause:d})}}async getAuth(e){return e&&(this.cachedAuth=void 0),this.cachedAuth!==void 0?this.cachedAuth:(this.cachedAuth=await this.auth.resolve(),this.cachedAuth)}};var H=class{constructor(e){this.api=e;}async me(){let e=await this.api.execute("/me");await h(e,"me");let n=at.safeParse(await w(e));if(!n.success)throw new c({message:"invalid me response",status:e.status,cause:n.error});return n.data.data}async balance(){let e=await this.api.execute("/balance");await h(e,"balance");let n=st.safeParse(await w(e));if(!n.success)throw new c({message:"invalid balance response",status:e.status,cause:n.error});return n.data.data}};var on=6e4,Y=t=>t?{user:t}:{},me=(t,e)=>new $({reason:e instanceof Error?`${t}: ${e.message}`:t}),V=class{constructor(e,n,a){this.config=e;this.store=n;this.workos=a;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(a){throw me("could not read credentials",a)}if(e===void 0)throw new $({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,...Y(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,...Y(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,...Y(e.user)});}async logout(){await this.store.clear();}async refreshIfNeeded(e){if(e.expires_at-Date.now()>on)return e;let n;try{n=await this.workos.refresh(e.refresh_token);}catch(s){throw me("token refresh failed",s)}let a={kind:"oauth",access_token:n.token.access_token,refresh_token:n.token.refresh_token,expires_at:n.expiresAt,...Y(n.token.user??e.user)};try{await this.store.set(a);}catch(s){throw me("could not persist refreshed token",s)}return a}};var pt=()=>{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 pe="mynth-cli",ge="default",hn="credentials.json",fn=t=>{let e=t?.name;return e==="NoKeyringError"||e==="InitError"},G=async(t,e)=>{try{return {available:true,value:await t()}}catch(n){if(fn(n))return {available:false};throw new A({message:e,cause:n})}},gt=t=>{let e;try{e=JSON.parse(t);}catch(a){throw new A({message:"credentials JSON parse failed",cause:a})}let n=Ve.safeParse(e);if(!n.success)throw new A({message:"credentials shape invalid",cause:n.error});return n.data},ht=t=>JSON.stringify(t),yn=()=>{let t=process.env.XDG_CONFIG_HOME,e=t&&t.length>0?t:join(homedir(),".config");return join(e,"mynth")},ft=async t=>{try{return await stat(t),true}catch{return false}},X=class{filePath;dir;constructor(){this.dir=yn(),this.filePath=join(this.dir,hn);}async get(){let e=await G(()=>C.getPassword(pe,ge),"keychain get failed");if(e.available)return e.value===null?void 0:gt(e.value);if(await ft(this.filePath))try{return gt(await readFile(this.filePath,"utf8"))}catch(n){throw n instanceof A?n:new A({message:"read credentials file failed",cause:n})}}async set(e){if((await G(()=>C.setPassword(pe,ge,ht(e)),"keychain set failed")).available){await this.deleteFileSilently();return}await mkdir(this.dir,{recursive:true}).catch(a=>{throw new A({message:"create config dir failed",cause:a})}),await writeFile(this.filePath,ht(e),"utf8").catch(a=>{throw new A({message:"write credentials file failed",cause:a})}),await chmod(this.filePath,384).catch(()=>{});}async clear(){await G(()=>C.deletePassword(pe,ge),"keychain delete failed").catch(()=>{}),await this.deleteFileSilently();}async usingKeychain(){let e=await G(()=>C.getKeyring(),"keychain probe failed");return e.available&&e.value!==null}async deleteFileSilently(){await ft(this.filePath)&&await rm(this.filePath).catch(e=>{throw new A({message:"delete credentials file failed",cause:e})});}};var fe=(t,e)=>({method:t,headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),Z=class{constructor(e){this.api=e;}async list(){let e=await this.api.execute("/destinations");await h(e,"destination list");let n=ct.safeParse(await w(e));if(!n.success)throw new c({message:"invalid destination list response",status:e.status,cause:n.error});return n.data.data}async get(e){let n=await this.api.execute(`/destinations/${e}`);return await h(n,"destination fetch"),this.parseOne(n)}async create(e){let n=await this.api.execute("/destinations",fe("POST",e));return await h(n,"destination create"),this.parseOne(n)}async update(e,n){let a=await this.api.execute(`/destinations/${e}`,fe("PUT",n));return await h(a,"destination update"),this.parseOne(a)}async test(e,n){let a=await this.api.execute(`/destinations/${e}/test`,fe("POST",{path:n}));await h(a,"destination test");}async delete(e){let n=await this.api.execute(`/destinations/${e}`,{method:"DELETE"});await h(n,"destination delete");}async parseOne(e){let n=dt.safeParse(await w(e));if(!n.success)throw new c({message:"invalid destination response",status:e.status,cause:n.error});return n.data.data}};var wn=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},yt=async(t,e)=>{if(t.ok)return;let n=wn(await J(t));throw new c({message:`${e} failed (${t.status}): ${n}`,status:t.status})},wt=async(t,e,n)=>{try{return await fetch(t,n)}catch(a){throw new c({message:`${e} failed: ${a.message}`,status:0,cause:a})}},vt=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})}},vn=t=>{let e=t.trim();if(e.length===0)throw new l("documentation path must not be empty");if(e.length>2048)throw new l("documentation path is too long");if(e.startsWith("//")||e.includes("://"))throw new l("documentation path must be a path, not a URL");if(e.includes("?")||e.includes("#")||e.includes("\\"))throw new l("documentation path must not contain a query, fragment, or backslash");let n=e.startsWith("/")?e.slice(1):e;if(n.endsWith(".md"))throw new l("documentation path must not include the .md suffix");let a=n.split("/");if(a.some(s=>s.length===0||s==="."||s===".."||!/^[A-Za-z0-9._~%-]+$/.test(s)))throw new l("documentation path contains an invalid segment");return a.join("/")},Q=class{docsUrl;constructor(e){this.docsUrl=e.mynthDocsUrl.replace(/\/$/,"");}async get(e){let n=vn(e),a=n.split("/").map(encodeURIComponent).join("/"),s=await wt(`${this.docsUrl}/${a}.md`,`documentation page fetch for ${n}`);return await yt(s,`documentation page fetch for ${n}`),{path:n,content:await vt(s,`documentation page fetch for ${n}`)}}async list(){let e=await wt(`${this.docsUrl}/llms.txt`,"documentation index fetch");return await yt(e,"documentation index fetch"),vt(e,"documentation index fetch")}};var M=10,L=2,U=7,bt=["low","high"],kt=300*1e3,Cn=12e3,On=2500,Tn=5e3,_n={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},En=t=>new Promise(e=>setTimeout(e,t)),Pn=(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}`},In=async t=>{let e=extname(t).toLowerCase(),n=_n[e];if(!n)throw new c({message:`unsupported image extension "${e}" for ${t} (allowed: .jpg, .jpeg, .png, .webp)`,status:0});let a;try{a=await readFile(t);}catch(i){throw new c({message:`could not read ${t}: ${i.message}`,status:0,cause:i})}let s=a.buffer.slice(a.byteOffset,a.byteOffset+a.byteLength);return new File([new Uint8Array(s)],basename(t),{type:n})},O=async(t,e,n)=>{let a=e.safeParse(await w(t));if(!a.success)throw new c({message:n,status:t.status,cause:a.error});return a.data},jn=async(t,e,n)=>{let a=Array.from({length:t.length}),s=0,i=async()=>{for(;;){let d=s++,u=t[d];if(u===void 0)return;a[d]=await n(u,d);}};return await Promise.all(Array.from({length:Math.min(e,t.length)},i)),a},ee=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(In)),a=new FormData;for(let d of n)a.append("images",d);let s=await this.api.execute("/image/upload",{method:"POST",body:a});await h(s,"upload");let i=await O(s,Ge,"invalid upload response");return e.map((d,u)=>({path:d,url:i.data.urls[u]}))}async rate(e){if(e.levels!==void 0&&(e.levels.length<L||e.levels.length>U))throw new c({message:`levels must have between ${L} and ${U} items (got ${e.levels.length})`,status:0});let n=e.levels!==void 0?{url:e.url,mode:"custom",levels:e.levels}:{url:e.url},a=await this.api.execute("/image/rate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});await h(a,"rate");let s=await O(a,K,"invalid rate create response"),i=await this.waitForTask(s.data.taskId);if(i.type!=="image.rate"||i.status!=="completed"||i.result===null)throw new c({message:`rate task ${s.data.taskId} did not complete successfully`,status:0});if(i.cost===null)throw new c({message:`rate task ${s.data.taskId} is missing cost`,status:0});let d=Qe.safeParse(i.result);if(!d.success)throw new c({message:`rate task ${s.data.taskId} returned an invalid result`,status:0,cause:d.error});return {taskId:i.id,cost:i.cost,url:d.data.url,level:d.data.level}}async alt(e){let n=await this.api.execute("/image/alt",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e.url})});await h(n,"alt text");let a=await O(n,Xe,"invalid alt text create response"),s=await this.waitForTask(a.data.taskId);if(s.type!=="image.alt"||s.status!=="completed"||s.result===null)throw new c({message:`alt text task ${a.data.taskId} did not complete successfully`,status:0});if(s.cost===null)throw new c({message:`alt text task ${a.data.taskId} is missing cost`,status:0});let i=et.safeParse(s.result);if(!i.success)throw new c({message:`alt text task ${a.data.taskId} returned an invalid result`,status:0,cause:i.error});return {taskId:s.id,cost:s.cost,url:i.data.url,alt:i.data.alt}}async review(e){let n=e.effort!==void 0?{url:e.url,effort:e.effort}:{url:e.url},a=await this.api.execute("/image/review",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});await h(a,"review");let s=await O(a,Ze,"invalid review create response"),i=await this.waitForTask(s.data.taskId);if(i.type!=="image.review"||i.status!=="completed"||i.result===null)throw new c({message:`review task ${s.data.taskId} did not complete successfully`,status:0});if(i.cost===null)throw new c({message:`review task ${s.data.taskId} is missing cost`,status:0});let d=i.result;return {taskId:i.id,cost:i.cost,url:d.url,score:d.score,summary:d.summary,findings:d.findings??[],strengths:d.strengths??[]}}async generate(e){let n=e.requestPat?{...e.request,access:{...e.request.access,pat:{enabled:true}}}:e.request,a=await this.api.execute("/image/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});await h(a,"generate");let s=await O(a,tt,"invalid generate response"),i=s.data.access?.publicAccessToken;return {taskId:s.data.taskId,...i!==void 0?{pat:i}:{}}}async estimate(e){let n=await this.api.execute("/image/generate/estimate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});return await h(n,"estimate"),(await O(n,nt,"invalid estimate response")).data}async waitForTask(e,n){let a=Date.now();for(;;){let s=Date.now()-a;if(s>=kt)throw new c({message:`task ${e} polling timed out after ${kt}ms`,status:0});let i=await this.getTaskStatus(e,n);if(i==="completed")return this.getTaskDetails(e);if(i==="failed"){let u=await this.getTaskDetails(e).catch(()=>{}),f=u!==void 0?ue(u):void 0;throw new c({message:`task ${e} failed${f!==void 0?` (${f})`:""}`,status:0,...f!==void 0?{code:f}:{}})}let d=s<Cn?On:Tn;await En(d+Math.floor(Math.random()*500));}}async getTaskDetails(e){let n=await this.api.execute(`/tasks/${e}`);return await h(n,"task details"),(await O(n,q,"invalid task details response")).data}async downloadImages(e){let n=resolve(e.destinationDir);try{await mkdir(n,{recursive:true});}catch(a){throw new c({message:`could not create output directory ${n}: ${a.message}`,status:0,cause:a})}return jn(e.urls,4,async(a,s)=>{let i;try{i=await fetch(a);}catch(y){throw new c({message:`download failed for ${a}: ${y.message}`,status:0,cause:y})}if(i.status<200||i.status>=300)throw new c({message:`download failed for ${a} (${i.status})`,status:i.status});let d;try{d=await i.arrayBuffer();}catch(y){throw new c({message:`could not read body for ${a}: ${y.message}`,status:i.status,cause:y})}let u=Pn(a,e.taskId,s),f=join(n,u);try{await writeFile(f,new Uint8Array(d));}catch(y){throw new c({message:`could not write ${f}: ${y.message}`,status:0,cause:y})}return f})}async getTaskStatus(e,n){let a=`/tasks/${e}/status`,s;if(n!==void 0)try{s=await fetch(`${this.api.baseUrl}${a}`,{headers:{Authorization:`Bearer ${n}`}});}catch(d){throw new c({message:`task status request failed: ${d.message}`,status:0,cause:d})}else s=await this.api.execute(a);return await h(s,"task status"),(await O(s,z,"invalid task status response")).data.status}};var te=class{constructor(e){this.api=e;}async list(){let e=await this.api.executePublic("/models");if(e.status<200||e.status>=300){let a=await J(e);throw new c({message:`models fetch failed (${e.status}): ${a||"no body"}`,status:e.status})}let n=ot.safeParse(await w(e));if(!n.success)throw new c({message:"invalid models response",status:e.status,cause:n.error});return n.data.data}};var Nn=300*1e3,Dn=12e3,Jn=2500,Mn=5e3,Ln=t=>new Promise(e=>setTimeout(e,t)),ne=class{constructor(e){this.api=e;}async getTask(e){let n=await this.api.execute(`/tasks/${e}`);await h(n,"task fetch");let a=q.safeParse(await w(n));if(!a.success)throw new c({message:"invalid task response",status:n.status,cause:a.error});return a.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 a=n.size>0?`?${n}`:"",s=await this.api.execute(`/tasks${a}`);await h(s,"task list");let i=rt.safeParse(await w(s));if(!i.success)throw new c({message:"invalid task list response",status:s.status,cause:i.error});return i.data.data}async getTaskStatus(e){let n=await this.api.execute(`/tasks/${e}/status`);await h(n,"task status");let a=z.safeParse(await w(n));if(!a.success)throw new c({message:"invalid task status response",status:n.status,cause:a.error});return a.data.data.status}async waitForTask(e,n=Nn){let a=Date.now();for(;;){if(await this.getTaskStatus(e)!=="pending")return this.getTask(e);let i=Date.now()-a;if(i>=n)throw new c({message:`task ${e} did not complete within ${Math.round(n/1e3)}s`,status:0});let d=i<Dn?Jn:Mn;await Ln(d+Math.floor(Math.random()*500));}}};var St=(t,e)=>({method:t,headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),ae=class{constructor(e){this.api=e;}async create(e){let n=await this.api.execute("/webhook",St("POST",e));await h(n,"webhook create");let a=ut.safeParse(await w(n));if(!a.success)throw new c({message:"invalid webhook create response",status:n.status,cause:a.error});return a.data.data}async update(e,n){let a=await this.api.execute(`/webhook/${e}`,St("PUT",n));await h(a,"webhook update");let s=mt.safeParse(await w(a));if(!s.success)throw new c({message:"invalid webhook update response",status:a.status,cause:s.error});return s.data.data}async delete(e){let n=await this.api.execute(`/webhook/${e}`,{method:"DELETE"});await h(n,"webhook delete");}};var se="client_01KATK792RR5ZCHMF5YMNN1ZSE",At="https://api.workos.com";var Un="urn:ietf:params:oauth:grant-type:device_code",Fn="refresh_token",Wn=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 x({message:"could not decode access token",cause:e})}},ye=async t=>{try{return await t.json()}catch{return {}}},Rt=async t=>Ye.catch({}).parse(await ye(t)),xt=async(t,e)=>{let n=await Rt(t),a=n.error??n.code;throw new x({message:n.error_description??n.message??e,status:t.status,...a!==void 0?{code:a}:{}})},$t=async(t,e)=>{let n=He.safeParse(await ye(t));if(!n.success)throw new x({message:`invalid ${e} response`,status:t.status,cause:n.error});return {token:n.data,expiresAt:Wn(n.data.access_token)}},Kn=async t=>{if(t.status===200)return $t(t,"token");let e=await Rt(t),n=e.error??e.code;switch(n){case "authorization_pending":throw new T({slowDown:false});case "slow_down":throw new T({slowDown:true});case "expired_token":throw new E;case "access_denied":throw new P;default:throw new x({message:e.error_description??e.message??"WorkOS error",status:t.status,...n!==void 0?{code:n}:{}})}},re=class{baseUrl=At;async requestDeviceAuthorization(){let e=await this.post("/user_management/authorize/device",new URLSearchParams({client_id:se}),"device authorize request failed");if(e.status!==200)return xt(e,"device authorize failed");let n=Be.safeParse(await ye(e));if(!n.success)throw new x({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:Un,client_id:se,device_code:e}),"authenticate request failed",{"Content-Type":"application/json"});return Kn(n)}async refresh(e){let n=await this.post("/user_management/authenticate",JSON.stringify({grant_type:Fn,client_id:se,refresh_token:e}),"refresh request failed",{"Content-Type":"application/json"});return n.status===200?$t(n,"refresh"):xt(n,"refresh failed")}async post(e,n,a,s={}){try{return await fetch(`${this.baseUrl}${e}`,{method:"POST",body:n,headers:{Accept:"application/json",...s}})}catch(i){throw new x({message:a,cause:i})}}};var Ct=()=>{let t=pt(),e=new X,n=new re,a=new V(t,e,n),s=new B(t,a);return {account:new H(s),auth:a,credentialsStore:e,destinations:new Z(s),docs:new Q(t),images:new ee(s),models:new te(s),tasks:new ne(s),webhooks:new ae(s),workos:n}};var o=(t="")=>{process.stdout.write(`${t}
3
- `);},oe=(t="")=>{process.stderr.write(`${t}
4
- `);};var qn=t=>new Date(t).toISOString(),Bn=t=>new Promise(e=>setTimeout(e,t)),Ot=Jt.green("\u2713"),Tt=(t,e)=>new b({message:e instanceof Error?`${t}: ${e.message}`:t,cause:e}),Hn=async(t,e,n,a)=>{let s=n;for(;;){if(Date.now()>=a)throw new b({message:"device code expired before approval"});try{return await t.workos.exchangeDeviceCode(e)}catch(i){if(i instanceof T){i.slowDown&&(s+=5e3),await Bn(s);continue}throw i instanceof P?new b({message:"login denied by user"}):i instanceof E?new b({message:"device code expired"}):i instanceof x?new b({message:i.message,cause:i}):i}}},we=t=>{let e=new Command("auth");return e.command("login").description("Authenticate with Mynth using OAuth device login").action(async()=>{if(t.auth.envApiKeySet)throw o(`MYNTH_API_KEY is set in your environment; that takes precedence over login.
5
- Unset it to use OAuth, or just continue using the env API key.`),new b({message:"env api key takes precedence"});let n;try{n=await t.workos.requestDeviceAuthorization();}catch(i){throw Tt("device authorize",i)}o(""),o(` First copy your one-time code: ${n.user_code}`),o(` Then open: ${n.verification_uri_complete??n.verification_uri}`),o(""),o("Waiting for confirmation...");let a=await Hn(t,n.device_code,(n.interval??5)*1e3,Date.now()+n.expires_in*1e3);try{await t.auth.saveOAuth({accessToken:a.token.access_token,refreshToken:a.token.refresh_token,expiresAt:a.expiresAt,...a.token.user?{user:a.token.user}:{}});}catch(i){throw Tt("could not save credentials",i)}let s=a.token.user?.email??a.token.user?.id??"unknown user";o(`${Ot} Logged in as ${s}`);}),e.command("logout").description("Clear local Mynth credentials").action(async()=>{await t.auth.logout(),o(`${Ot} 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 i=n.user?.email??n.user?.id??"unknown user";o(`Authenticated via OAuth as ${i} (${s})`),o(` access token expires: ${qn(n.expiresAt)}`);}}}),e.addCommand(ie(t)),e},ie=t=>new Command("whoami").description("Print the active Mynth identity, verified against the API").action(async()=>{let e=await t.auth.status();if(e.kind==="none")throw o("not authenticated"),new $;let n=e.kind==="env"?"env:MYNTH_API_KEY":e.kind==="api_key"?"api-key":e.user?.email??e.user?.id??"oauth",a=await t.account.me();o(n),o(` user: ${a.userId}`),a.auth.apiKey&&o(` key: ${a.auth.apiKey.name??"unnamed"} (${a.auth.apiKey.keyPreview})`);});var ve=t=>new Command("balance").description("Show account balance and API key spending limit usage").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async e=>{let n=await t.account.balance();if(e.json){o(JSON.stringify(n,null,2));return}o(`Balance: $${n.balance}`),o(`Reserved: $${n.reserved}`),o(`Available: $${n.available}`),n.apiKey&&(o(""),o(`API key limit: $${n.apiKey.spendingLimit} / ${n.apiKey.spendingLimitPeriod}`),o(` used: $${n.apiKey.usedInPeriod}`),o(` remaining: $${n.apiKey.remainingInPeriod}`));});var Et=Jt.green("\u2713"),Gn=async()=>{try{let t="";process.stdin.setEncoding("utf8");for await(let e of process.stdin)t+=e;return t.trim()}catch(t){throw new b({message:"could not read stdin",cause:t})}},be=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 i=s==="-"?await Gn():s;if(i.length===0)throw new b({message:"API key is empty"});try{await t.auth.setApiKey(i);}catch(d){throw new b({message:`could not save API key: ${d.message}`,cause:d})}o(`${Et} API key saved`),t.auth.envApiKeySet&&o("Note: MYNTH_API_KEY is also set in your environment and will take precedence.");});let a=new Command("unset").description("Unset local CLI configuration");return a.command("api-key").description("Clear stored Mynth credentials").action(async()=>{await t.auth.logout(),o(`${Et} Stored credentials cleared`);}),e.addCommand(n),e.addCommand(a),e};var R=async t=>{if((await t.auth.status()).kind!=="oauth")throw new $({reason:"destination/webhook commands require OAuth login (run 'mynth auth login'); API key auth is not supported for this resource yet"})},Zn=async()=>{let t="";process.stdin.setEncoding("utf8");for await(let e of process.stdin)t+=e;return t},Se=async t=>{let e;try{e=t==="-"?await Zn():await readFile(t,"utf8");}catch(n){throw new l(`could not read ${t}: ${n.message}`)}try{return JSON.parse(e)}catch(n){throw new l(`invalid JSON in ${t}: ${n.message}`)}};var Pt=/^[a-z0-9-]+$/,It=t=>{let e=t.provider;return typeof e?.id=="string"?e.id:"-"},ea=t=>{if(t.length===0){o("No destinations found.");return}let e=t.map(s=>({id:s.id,name:s.name,provider:It(s),created:s.createdAt})),n=(s,i)=>Math.max(i.length,...e.map(d=>d[s].length)),a={id:n("id","ID"),name:n("name","Name"),provider:n("provider","Provider")};o(["ID".padEnd(a.id),"Name".padEnd(a.name),"Provider".padEnd(a.provider),"Created"].join(" "));for(let s of e)o([s.id.padEnd(a.id),s.name.padEnd(a.name),s.provider.padEnd(a.provider),s.created].join(" "));},Ae=t=>{o(`Destination ${t.id}`),o(` Name: ${t.name}`),o(` Provider: ${It(t)}`),o(` Created: ${t.createdAt}`),o(` Updated: ${t.updatedAt}`);},xe=t=>{let e=new Command("destination").description("Manage storage destinations (OAuth login required)");return e.command("list").description("List storage destinations").option("--json","Output machine-readable JSON instead of a human-readable table").action(async n=>{await R(t);let a=await t.destinations.list();if(n.json){o(JSON.stringify(a,null,2));return}ea(a);}),e.command("get").description("Fetch a destination by ID").argument("<id>","Destination ID").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,a)=>{await R(t);let s=await t.destinations.get(n);if(a.json){o(JSON.stringify(s,null,2));return}Ae(s);}),e.command("create").description("Create a destination from a JSON file ({ name, provider, config, secret })").requiredOption("--file <path>","Path to a JSON file, or `-` to read from stdin").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async n=>{await R(t);let a=await Se(n.file),s=a?.name;if(typeof s!="string"||s.length<1||s.length>64||!Pt.test(s))throw new l(`invalid destination name: expected 1-64 chars matching ${Pt.source}`);let i=await t.destinations.create(a);if(n.json){o(JSON.stringify(i,null,2));return}Ae(i),o(""),o(`Next: verify credentials with 'mynth destination test ${i.id}'`);}),e.command("update").description("Update a destination from a JSON file ({ provider, config, secret? })").argument("<id>","Destination ID").requiredOption("--file <path>","Path to a JSON file, or `-` to read from stdin").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,a)=>{await R(t);let s=await Se(a.file),{name:i,...d}=s??{},u=await t.destinations.update(n,d);if(a.json){o(JSON.stringify(u,null,2));return}Ae(u);}),e.command("test").description("Test a destination's credentials by uploading a probe file").argument("<id>","Destination ID").option("--path <path>","Object path to write (defaults to a unique probe path)").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,a)=>{await R(t);let s=a.path??`mynth-cli-test/${Date.now()}.txt`;if(await t.destinations.test(n,s),a.json){o(JSON.stringify({id:n,path:s,ok:true},null,2));return}o(`\u2713 credentials valid (wrote ${s})`);}),e.command("delete").description("Delete a destination (requires --yes)").argument("<id>","Destination ID").option("--yes","Confirm deletion (required; no interactive prompt)").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,a)=>{if(a.yes!==true)throw new l("refusing to delete without --yes");if(await R(t),await t.destinations.delete(n),a.json){o(JSON.stringify({deleted:n},null,2));return}o(`\u2713 deleted destination ${n}`);}),e};var Re=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,a)=>{let s=await t.docs.get(n);o(a.json?JSON.stringify(s,null,2):s.content);}),e.command("list").description("Fetch the complete documentation index").option("--json","Output machine-readable JSON").action(async n=>{let a=await t.docs.list();o(n.json?JSON.stringify({content:a},null,2):a);}),e};var aa=["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"],sa=()=>typeof process<"u"&&process.stderr&&process.stderr.isTTY===true,ra=t=>{let e=t.slice();for(let n=e.length-1;n>0;n--){let a=Math.floor(Math.random()*(n+1));[e[n],e[a]]=[e[a],e[n]];}return e},de=async(t,e={})=>{if(!sa())return t;let n=ra(e.messages??aa),a=0,s=na({text:n[0]??"Working",stream:process.stderr}).start(),i=setInterval(()=>{a++,s.text=n[a%n.length]??"Working";},2800);try{return await t}finally{clearInterval(i),s.stop();}};var $e=20,ca="webp",la=80,Ce=["auto","person","garment","pose","source","reference"],ua={4:"Production-ready",3:"Usable, with fixes",2:"Not fit for purpose",1:"Discard"},ma="https://dry-run.mynth.io/input",S=Jt.green("\u2713"),pa=Jt.red("\u2717"),F=()=>new Option("--json","Output machine-readable JSON instead of a human-readable summary"),ga=z$1.array(z$1.object({value:z$1.string(),description:z$1.string()})),_=t=>/^https?:\/\//i.test(t),Mt=(t,e=[])=>[...e,t],Lt=t=>{let e=Number.parseInt(t,10);if(!Number.isInteger(e)||String(e)!==t)throw new l(`invalid integer: "${t}"`);return e},ha=t=>{let e=Lt(t);if(e<1||e>100)throw new l(`invalid quality: "${t}" (expected 1-100)`);return e},fa=t=>{let e=t.indexOf("=");if(e<=0)throw new l(`invalid --level "${t}": expected "value=description"`);let n={value:t.slice(0,e).trim(),description:t.slice(e+1).trim()};if(n.value.length===0||n.description.length===0)throw new l(`invalid --level "${t}": value and description must be non-empty`);return n},jt=(t,e)=>{let n;try{n=JSON.parse(t);}catch(s){throw new l(`invalid JSON in ${e}: ${s.message}`)}let a=ga.safeParse(n);if(!a.success)throw new l(`invalid levels in ${e}: expected array of { value, description }`);return a.data},Nt=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(`conflicting level options: ${e.join(", ")} - use only one`);let n;if(t.levelPairs.length>0)n=t.levelPairs.map(fa);else if(t.levelsFile!==void 0){let s;try{s=await readFile(t.levelsFile,"utf8");}catch(i){throw new l(`could not read ${t.levelsFile}: ${i.message}`)}n=jt(s,t.levelsFile);}else n=jt(t.levelsJson??"[]","--levels-json");if(n.length<L||n.length>U)throw new l(`levels must have between ${L} and ${U} items (got ${n.length})`);let a=new Set;for(let s of n){if(a.has(s.value))throw new l(`duplicate level value: "${s.value}"`);a.add(s.value);}return n},ya=t=>{let e=t.indexOf(":"),n=/^https?:/i.test(t),a,s=t;if(e>0&&!n){let i=t.slice(0,e);if(!Ce.includes(i))throw new l(`invalid --input as "${i}". Expected one of: ${Ce.join(", ")}`);a=i,s=t.slice(e+1);}if(s.length===0)throw new l(`invalid --input "${t}": missing path or URL`);return {...a!==void 0?{as:a}:{},value:s,isFile:!_(s)}},wa=t=>{let e;try{e=JSON.parse(t);}catch(n){throw new l(`invalid --metadata JSON: ${n.message}`)}if(e===null||typeof e!="object"||Array.isArray(e))throw new l("--metadata must be a JSON object");return e},Dt=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"',Mt).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.'),Oe=(t,e)=>{let n=t.result??{},a=n.images??[];e>0&&o(`${S} Uploaded ${e} input image${e===1?"":"s"}`);let s=a.filter(i=>i.status==="success");if(o(`${S} Generated ${s.length}/${a.length} image${a.length===1?"":"s"} (task ${t.id})`),n.model!==void 0&&o(` Model: ${n.model}`),t.cost!==null&&o(` Cost: ${t.cost}`),n.magic_prompt?.positive!==void 0&&(o(""),o("Enhanced prompt (mynth):"),o(` ${n.magic_prompt.positive}`),n.magic_prompt.negative!==void 0&&n.magic_prompt.negative.length>0&&o(` negative: ${n.magic_prompt.negative}`)),a.length>0){o("");for(let i of a){let d=i;if(d.status==="success"){let u=d.rating,f=u?.level!==void 0?` [${u.level}]`:"",y=d.url??d.mynth_url;o(` ${S} ${y}${f}`);}else o(` ${pa} ${va(d.error)}`);}}},va=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",a=typeof e.message=="string"?e.message:void 0;return a!==void 0?`${n}: ${a}`:n}return "unknown error"},ka=t=>{let e=ua[t.score]??`${t.score}/4`;if(o(`${S} Reviewed (task ${t.taskId})`),o(` Score: ${t.score}/4 \u2014 ${e}`),o(` Cost: ${t.cost}`),o(` ${t.url}`),t.summary.length>0&&(o(""),o("Summary"),o(` ${t.summary}`)),t.findings.length>0){o(""),o(`Findings (${t.findings.length})`);for(let n of t.findings)o(` \u2022 [${n.severity}] ${n.category} \u2014 ${n.finding}`),o(` where: ${n.where} (${n.confidence} confidence)`);}if(t.strengths.length>0){o(""),o(`Strengths (${t.strengths.length})`);for(let n of t.strengths)o(` \u2022 ${n.strength} (${n.confidence} confidence)`);}},Te=t=>{let e=t.result??{},n=(e.images??[]).map(a=>{let s=a;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}:{},...t.cost!==null?{cost:t.cost}:{},...e.model!==void 0?{model:e.model}:{}}},ba=async(t,e,n)=>{let s=((e.result??{}).images??[]).map(i=>i).filter(i=>i.status==="success").map(i=>i.url??i.mynth_url).filter(i=>typeof i=="string"&&i.length>0);return s.length===0?[]:t.downloadImages({urls:s,destinationDir:n,taskId:e.id})},_e=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(F()).action(async(s,i)=>{if(s.length>M)throw new l(`too many files: ${s.length} (max ${M})`);let d=await t.images.upload(s);if(i.json){o(JSON.stringify({images:d},null,2));return}o(`${S} Uploaded ${d.length} image${d.length===1?"":"s"}`);for(let{path:u,url:f}of d)o(` ${u}`),o(` -> ${f}`);});let n=e.command("rate").description("Rate an image by URL or local file").argument("<image>","Image URL (http://, https://) or path to a local image file to upload first").addOption(F());Dt(n),n.action(async(s,i)=>{let d=await Nt({levelPairs:i.level??[],levelsFile:i.levelsFile,levelsJson:i.levelsJson}),u=_(s)?[]:await t.images.upload([s]),f=_(s)?s:u[0].url,y=await t.images.rate({url:f,...d?{levels:d}:{}});if(i.json){o(JSON.stringify(y,null,2));return}u.length>0&&(o(`${S} Uploaded 1 image`),o(` ${u[0].path} -> ${u[0].url}`)),o(`${S} Rated (task ${y.taskId})`),o(` ${y.level} ${y.url}`);}),e.command("alt").description("Generate alt text for an image by URL or local file").argument("<image>","Image URL (http://, https://) or path to a local image file to upload first").addOption(F()).action(async(s,i)=>{let d=_(s)?[]:await t.images.upload([s]),u=_(s)?s:d[0].url,f=await t.images.alt({url:u});if(i.json){o(JSON.stringify(f,null,2));return}d.length>0&&(o(`${S} Uploaded 1 image`),o(` ${d[0].path} -> ${d[0].url}`)),o(`${S} Generated alt text (task ${f.taskId})`),o(` ${f.alt}`),o(` ${f.url}`);}),e.command("review").description("Review image quality with a multi-model panel (score, findings, strengths)").argument("<image>","Image URL (http://, https://) or path to a local image file to upload first").addOption(new Option("--effort <level>",'Reviewer panel size: "high" (default, five strong vision models) or "low" (three smaller models, faster/cheaper triage)').choices([...bt])).addOption(F()).action(async(s,i)=>{let d=_(s)?[]:await t.images.upload([s]),u=_(s)?s:d[0].url,f=await t.images.review({url:u,...i.effort!==void 0?{effort:i.effort}:{}});if(i.json){o(JSON.stringify(f,null,2));return}d.length>0&&(o(`${S} Uploaded 1 image`),o(` ${d[0].path} -> ${d[0].url}`)),ka(f);});let a=e.command("generate").description("Generate images with Mynth").addHelpText("after",`
6
- Models: mynth models list`);return a.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)",Lt).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)",ha).option("-i, --input <value>",`Input image as "[as:]path-or-url" (repeatable, up to ${$e}). as is optional and must be one of: ${Ce.join(", ")}. Examples: -i ./img.jpg, -i source:https://example.com/a.png, -i reference:./style.png`,Mt).option("-o, --output-dir <dir>","Directory to save generated images to. Created if it doesn't exist. Ignored in --async mode since the task hasn't completed yet.").option("--destination <name>","Name (slug) of a user-configured destination to deliver the result to. Falls back to MYNTH_DESTINATION env var if not set.").option("--metadata <json>","Inline JSON object of custom metadata to attach to the task (max 2KB)").option("--content-rating","Enable content rating classification using default sfw/nsfw levels. For custom levels use --level / --levels-file / --levels-json.").option("--dry-run","Validate the request and print the estimated cost without generating anything").option("--async","Return the task ID immediately instead of polling until completion").option("--detailed","Include full task data (all fields) in the output").addOption(F()),Dt(a),a.action(async s=>{let i=s.prompt??"";if(s.enhance==="prefer_native")throw new l('--enhance prefer_native is no longer supported by the API; use "prefer_magic" or "none"');let d=s.input??[];if(d.length>$e)throw new l(`too many --input values: ${d.length} (max ${$e})`);let u=d.map(ya),f=s.metadata!==void 0?wa(s.metadata):void 0,y=await Nt({levelPairs:s.level??[],levelsFile:s.levelsFile,levelsJson:s.levelsJson}),Gt=u.filter(g=>g.isFile).map(g=>g.value),De=Array.from(new Set(Gt)),Je=De.length>0&&!s.dryRun?await t.images.upload(De):[],Xt=new Map(Je.map(g=>[g.path,g.url])),Me=u.map(g=>({type:"image",...g.as?{as:g.as}:{},source:{type:"url",url:g.isFile?Xt.get(g.value)??ma:g.value}})),Le=s.format!==void 0||s.quality!==void 0?{format:s.format??ca,quality:s.quality??la}:void 0,Ue=y!==void 0?{mode:"custom",levels:y}:s.contentRating?true:void 0,v={prompt:i};if(s.model!==void 0&&(v.model=s.model),s.negative!==void 0&&(v.negative_prompt=s.negative),s.enhance==="prefer_magic"&&(v.magic_prompt=true),s.size!==void 0&&(v.size=s.size),s.count!==void 0&&(v.count=s.count),Le!==void 0&&(v.output=Le),Me.length>0&&(v.inputs=Me),s.destination!==void 0&&(v.destination=s.destination),Ue!==void 0&&(v.rating=Ue),f!==void 0&&(v.metadata=f),s.dryRun){let g=await t.images.estimate(v);if(s.json){o(JSON.stringify(g,null,2));return}let D=g.estimateKind==="upper_bound"?" (upper bound)":"";o(`${S} Estimated cost: $${g.estimatedCost}${D}`);return}if(s.async){let g=await t.images.generate({request:v,requestPat:true}),D={taskId:g.taskId,...g.pat!==void 0?{access:{publicAccessToken:g.pat}}:{}};if(s.json){o(JSON.stringify(D,null,2));return}o(`${S} Task created: ${g.taskId}`),g.pat!==void 0&&o(` PAT: ${g.pat}`);return}let Fe=await t.images.generate({request:v,requestPat:true}),We=t.images.waitForTask(Fe.taskId,Fe.pat),W=s.json?await We:await de(We),j=s.outputDir!==void 0?resolve(s.outputDir):void 0,N=j!==void 0?await ba(t.images,W,j):[];if(s.json){let g=s.detailed?W:Te(W),D=j!==void 0?{...g,downloadedFiles:N}:g;o(JSON.stringify(D,null,2));return}if(Oe(W,Je.length),j!==void 0&&N.length>0){o(""),o(`${S} Saved ${N.length} image${N.length===1?"":"s"} to ${j}`);for(let g of N)o(` ${g}`);}}),e};var Ee=t=>t??"-",Aa=t=>{if(t.length===0){o("No models available.");return}let e=t.map(a=>({id:a.id,name:a.displayName??"-",base:Ee(a.pricing?.perImage.base),fourK:Ee(a.pricing?.perImage["4k"]),inputFee:Ee(a.pricing?.perInput)})),n={id:Math.max(2,...e.map(a=>a.id.length)),name:Math.max(4,...e.map(a=>a.name.length)),base:Math.max(4,...e.map(a=>a.base.length)),fourK:Math.max(2,...e.map(a=>a.fourK.length)),inputFee:Math.max(9,...e.map(a=>a.inputFee.length))};o(["ID".padEnd(n.id),"Name".padEnd(n.name),"Base".padEnd(n.base),"4K".padEnd(n.fourK),"Input fee".padEnd(n.inputFee)].join(" "));for(let a of e)o([a.id.padEnd(n.id),a.name.padEnd(n.name),a.base.padEnd(n.base),a.fourK.padEnd(n.fourK),a.inputFee.padEnd(n.inputFee)].join(" "));},Pe=t=>{let e=new Command("models").description("Browse the public Mynth model catalog");return e.command("list").description("List available image generation models").option("--json","Output machine-readable JSON instead of a human-readable table").action(async n=>{let a=await t.models.list();if(n.json){o(JSON.stringify(a,null,2));return}Aa(a);}),e};var Ut=300,Ft=t=>e=>{let n=Number.parseInt(e,10);if(!Number.isInteger(n)||String(n)!==e||n<=0)throw new l(`invalid ${t}: "${e}" (expected a positive integer)`);return n},Kt=t=>{switch(t){case "completed":return "\u2713";case "failed":return "\u2717";default:return "\u2026"}},Ra=(t,e)=>{let n=" ".repeat(e);return t.split(`
7
- `).map(a=>`${n}${a}`).join(`
8
- `)},Wt=t=>{o(`${Kt(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(Ra(JSON.stringify(t.result,null,2),2)));},$a=t=>{if(t.length===0){o("No tasks found.");return}for(let e of t){let n=e.cost!==null?` ${e.cost}`:"";o(`${Kt(e.status)} ${e.id} ${e.type} ${e.status}${n} ${e.createdAt}`);}},Ie=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,a)=>{let s=await t.tasks.getTask(n);if(a.json){o(JSON.stringify(s,null,2));return}Wt(s);}),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: ${Ut})`,Ft("--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,a)=>{let s=(a.timeout??Ut)*1e3,i=t.tasks.waitForTask(n,s),d=a.json?await i:await de(i);if(d.status==="failed"&&(process.exitCode=ze(d)),a.json){let u=a.detailed||d.type!=="image.generate"?d:Te(d);o(JSON.stringify(u,null,2));return}if(d.type==="image.generate"&&d.status==="completed"){Oe(d,0);return}Wt(d);}),e.command("list").description("List recent tasks, newest first").option("--limit <number>","Max tasks to return (1-100, default: 20)",Ft("--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 a=await t.tasks.listTasks({...n.limit!==void 0?{limit:n.limit}:{},...n.after!==void 0?{after:n.after}:{}});if(n.json){o(JSON.stringify({tasks:a},null,2));return}$a(a);}),e};var zt=["task.completed","task.failed","task.image.generate.completed","task.image.generate.failed","task.image.rate.completed","task.image.rate.failed","task.image.alt.completed","task.image.alt.failed","task.image.review.completed","task.image.review.failed"],qt=(t,e=[])=>[...e,t],Bt=t=>{if(t===void 0||t.length===0)throw new l("at least one --event is required");if(t.includes("all"))return "all";for(let e of t)if(!zt.includes(e))throw new l(`unknown event "${e}". Valid events: all, ${zt.join(", ")}`);return t},Oa=t=>{if(t.enabled===true&&t.disabled===true)throw new l("--enabled and --disabled are mutually exclusive");return t.disabled!==true},Ta=t=>{o(`\u2713 Webhook ${t.id} created`),o(` URL: ${t.url}`),o(` Enabled: ${t.enabled}`),o(` Events: ${Array.isArray(t.events)?t.events.join(", "):t.events}`),o(""),o(` Signing secret: ${t.secret}`),o(" Save this now \u2014 it is shown only once and cannot be retrieved again.");},_a=t=>{o(`\u2713 Webhook ${t.id} updated`),o(` URL: ${t.url}`),t.enabled!==void 0&&o(` Enabled: ${t.enabled}`),o(` Events: ${Array.isArray(t.events)?t.events.join(", "):t.events}`);},je=t=>{let e=new Command("webhook").description("Manage registered webhooks (OAuth login required)");return e.command("create").description("Register a webhook; the signing secret is shown once on success").requiredOption("--url <url>","Destination URL for webhook deliveries").option("-e, --event <name>","Event to subscribe to (repeatable, or `all`)",qt).option("--disabled","Create the webhook disabled (default: enabled)").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async n=>{await R(t);let a={enabled:n.disabled!==true,url:n.url,events:Bt(n.event)},s=await t.webhooks.create(a);if(n.json){o(JSON.stringify(s,null,2));return}Ta(s);}),e.command("update").description("Replace a webhook's configuration (all fields required)").argument("<id>","Webhook ID").requiredOption("--url <url>","Destination URL for webhook deliveries").option("-e, --event <name>","Event to subscribe to (repeatable, or `all`)",qt).option("--enabled","Enable the webhook").option("--disabled","Disable the webhook").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,a)=>{await R(t);let s={enabled:Oa(a),url:a.url,events:Bt(a.event)},i=await t.webhooks.update(n,s);if(a.json){o(JSON.stringify(i,null,2));return}_a(i);}),e.command("delete").description("Delete a webhook (requires --yes)").argument("<id>","Webhook ID").option("--yes","Confirm deletion (required; no interactive prompt)").option("--json","Output machine-readable JSON instead of a human-readable summary").action(async(n,a)=>{if(a.yes!==true)throw new l("refusing to delete without --yes");if(await R(t),await t.webhooks.delete(n),a.json){o(JSON.stringify({deleted:n},null,2));return}o(`\u2713 deleted webhook ${n}`);}),e};var Ne=class extends Help{optionTerm(e){return `(${e.flags.replaceAll("<","").replaceAll(">","")})`}subcommandTerm(e){let n=e.registeredArguments.map(a=>a.required?`${a.name()}${a.variadic?"...":""}`:`[${a.name()}]`).join(" ");return n.length>0?`${e.name()} ${n}`:e.name()}},Ht=()=>{let t=Ct(),e=new Command("mynth");return e.description("Official Mynth CLI").version("0.0.12"),e.addHelpText("after",`
2
+ import M from'chalk';import {Command,Option,Help}from'commander';import {z as z$1}from'zod';import {readFile,mkdir,writeFile,rename,rm,stat}from'fs/promises';import {hostname,homedir}from'os';import {resolve,join,dirname,extname,basename}from'path';import {spawn}from'child_process';import {existsSync}from'fs';import Bo from'ora';import wn from'fuzzysort';var ge="client_01KATK792RR5ZCHMF5YMNN1ZSE",Be=process.env.MYNTH_WORKOS_API_URL??"https://api.workos.com",He="0.0.20",ze=e=>e.replace(/\/+$/,""),We=e=>e!==void 0&&e.length>0?e:void 0,G=()=>({apiUrl:ze(process.env.MYNTH_API_URL??"https://api.mynth.io"),docsUrl:ze(process.env.MYNTH_DOCS_URL??"https://docs.mynth.io"),envApiKey:We(process.env.MYNTH_API_KEY),envDestination:We(process.env.MYNTH_DESTINATION),debug:process.env.MYNTH_DEBUG==="1"||process.env.MYNTH_DEBUG==="true"});var v={error:1,usage:2,auth:3,insufficientCredits:4,moderation:5,rateLimited:6},y=class extends Error{constructor(t,n){super(t,n),this.name=new.target.name;}},l=class extends y{},$=class extends y{},k=class extends y{status;code;constructor(t,n){super(t,n),this.status=n.status,this.code=n.code;}},_=class extends y{code;constructor(t,n,r){super(n,r),this.code=t;}},jn={UNAUTHORIZED:v.auth,INSUFFICIENT_SCOPE:v.auth,VALIDATION_ERROR:v.usage,INSUFFICIENT_BALANCE:v.insufficientCredits,SPENDING_LIMIT_EXCEEDED:v.insufficientCredits,RESTRICTED_CONTENT:v.moderation},Ye=e=>{if(e instanceof l)return v.usage;if(e instanceof $)return v.auth;if(e instanceof k){let n=e.code!==void 0?jn[e.code]:void 0;return n!==void 0?n:e.status===401||e.status===403?v.auth:e.status===429?v.rateLimited:v.error}let t=e?.code;return typeof t=="string"&&t.startsWith("commander.")?v.usage:v.error},fe=e=>{let t=e.result,n=[...(e.errors??[]).map(r=>r.code),...(t?.images??[]).map(r=>r.error?.code)].filter(r=>typeof r=="string");return n.find(r=>r==="RESTRICTED_CONTENT")??n[0]},Ge=e=>fe(e)==="RESTRICTED_CONTENT"?v.moderation:v.error;var s=(e="")=>{process.stdout.write(`${e}
3
+ `);},T=(e="")=>{process.stderr.write(`${e}
4
+ `);},u=e=>{s(JSON.stringify(e,null,2));},g={ok:M.green("\u2713"),fail:M.red("\u2717"),pending:M.yellow("\u2026")},q=e=>e==="completed"?g.ok:e==="failed"?g.fail:g.pending,O=(e,t,n="s")=>`${e} ${t}${e===1?"":n}`,qe=(e,t=2)=>{let n=" ".repeat(t);return e.split(`
5
+ `).map(r=>`${n}${r}`).join(`
6
+ `)};var S=z$1.lazy(()=>z$1.union([z$1.string(),z$1.number(),z$1.boolean(),z$1.null(),z$1.array(S),z$1.record(S)])),Ve=e=>z$1.object({data:e}),Nn=z$1.object({id:z$1.string(),email:z$1.string(),first_name:z$1.string().nullable().optional(),last_name:z$1.string().nullable().optional()}),Xe=z$1.object({device_code:z$1.string(),user_code:z$1.string(),verification_uri:z$1.string(),verification_uri_complete:z$1.string().optional(),expires_in:z$1.number(),interval:z$1.number().optional()}),Ze=z$1.object({access_token:z$1.string(),refresh_token:z$1.string(),user:Nn.optional()}),Qe=z$1.object({error:z$1.string().optional(),error_description:z$1.string().optional(),message:z$1.string().optional(),code:z$1.string().optional()}),et=z$1.object({kind:z$1.literal("api_key"),api_key:z$1.string(),id:z$1.string().optional()}),R=["generate","manage","keys"],we=z$1.object({raw:z$1.string(),apiKey:z$1.object({id:z$1.string(),name:z$1.string().optional(),keyPreview:z$1.string(),scopes:z$1.array(z$1.string())})}),Dn=z$1.union([z$1.string(),z$1.number()]).nullable().optional(),tt=z$1.object({id:z$1.string(),name:z$1.string().nullable(),keyPreview:z$1.string(),scopes:z$1.array(z$1.string()),spendingLimit:Dn,spendingLimitPeriod:z$1.string().nullable().optional(),createdAt:z$1.string()}),Ln=z$1.union([z$1.object({mode:z$1.literal("unlimited")}),z$1.object({mode:z$1.literal("limited"),limit:z$1.string(),period:z$1.string(),used:z$1.string(),remaining:z$1.string()})]),nt=z$1.object({userId:z$1.string(),auth:z$1.object({method:z$1.string(),apiKey:z$1.object({id:z$1.string(),name:z$1.string().nullable(),keyPreview:z$1.string(),scopes:z$1.array(z$1.string()).optional(),spending:Ln.optional()}).optional()})}),ot=z$1.object({balance:z$1.string(),reserved:z$1.string(),available:z$1.string(),currency:z$1.string()}),rt=z$1.object({id:z$1.string(),displayName:z$1.string().nullable(),pricing:z$1.object({perImage:z$1.object({base:z$1.string(),"4k":z$1.string().optional()}),perInput:z$1.string().optional()}).nullable()}),V=z$1.enum(["pending","completed","failed"]),it=z$1.object({id:z$1.string(),type:z$1.string(),status:V,userId:z$1.string().optional(),apiKeyId:z$1.string().nullable().optional(),cost:z$1.string().nullable(),request:S.optional(),result:S.nullable(),errors:z$1.array(z$1.object({code:z$1.string(),message:z$1.string().optional()})).nullish(),createdAt:z$1.string(),updatedAt:z$1.string()}),st=z$1.object({id:z$1.string(),type:z$1.string(),status:z$1.string(),cost:z$1.string().nullable(),createdAt:z$1.string(),updatedAt:z$1.string()}),at=z$1.object({id:z$1.string(),type:z$1.string(),status:V,result:S.nullable()}),dt=z$1.object({urls:z$1.array(z$1.string())}),lt=z$1.object({taskId:z$1.string(),estimatedCost:z$1.string().optional(),access:z$1.object({publicAccessToken:z$1.string()}).optional()}),ct=z$1.object({estimatedCost:z$1.string(),currency:z$1.string(),estimateKind:z$1.enum(["exact","upper_bound"])}),pt=z$1.object({url:z$1.string(),level:z$1.string()}),mt=z$1.object({url:z$1.string(),alt:z$1.string()}),ut=z$1.object({url:z$1.string(),score:z$1.number(),summary:z$1.string(),findings:z$1.array(z$1.object({finding:z$1.string(),category:z$1.string(),severity:z$1.string(),where:z$1.string(),confidence:z$1.string()})).optional(),strengths:z$1.array(z$1.object({strength:z$1.string(),confidence:z$1.string()})).optional()}),he=z$1.object({code:z$1.string(),message:z$1.string().optional()}),Fn=z$1.union([z$1.object({status:z$1.literal("success"),id:z$1.string().optional(),url:z$1.string().nullable().optional(),mynth_url:z$1.string().optional(),size:z$1.string().optional(),format:z$1.string().optional(),rating:z$1.union([z$1.object({status:z$1.literal("success"),level:z$1.string()}),z$1.object({status:z$1.literal("failed"),error:he})]).optional(),destination:z$1.union([z$1.object({status:z$1.literal("success"),name:z$1.string()}),z$1.object({status:z$1.literal("failed"),name:z$1.string(),error:he})]).optional()}),z$1.object({status:z$1.literal("failed"),error:he})]),gt=z$1.object({model:z$1.string().optional(),images:z$1.array(Fn).optional(),magic_prompt:z$1.object({positive:z$1.string(),negative:z$1.string().optional()}).optional()}),U=z$1.object({id:z$1.string(),name:z$1.string(),provider:z$1.object({id:z$1.string()}).catchall(S),config:z$1.object({path_template:z$1.string(),url_template:z$1.string().optional()}).partial(),createdAt:z$1.string(),updatedAt:z$1.string()}),ft=z$1.union([z$1.literal("all"),z$1.array(z$1.string())]),yt=z$1.object({id:z$1.string(),enabled:z$1.boolean(),url:z$1.string(),secret:z$1.string(),events:ft,apiKeyIds:z$1.array(z$1.string()).nullish(),oauthEnabled:z$1.boolean().optional(),createdAt:z$1.string().optional()}),ht=z$1.object({id:z$1.string(),enabled:z$1.boolean().optional(),url:z$1.string(),events:ft,apiKeyIds:z$1.array(z$1.string()).nullish(),oauthEnabled:z$1.boolean().optional()});var Un=e=>{if(e===void 0)return "";let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!==void 0&&t.set(n,String(r));return t.size>0?`?${t}`:""},Mn=e=>e===void 0?{headers:{}}:e instanceof FormData?{body:e,headers:{}}:{body:JSON.stringify(e),headers:{"Content-Type":"application/json"}},Jn=async e=>{try{return await e.text()}catch{return ""}},Kn=async(e,t)=>{let n=await Jn(e),r,o;try{let a=JSON.parse(n);typeof a.code=="string"&&(r=a.code),typeof a.message=="string"?o=a.message:typeof a.error=="string"&&(o=a.error);}catch{}return new k(`${t} failed (${e.status}): ${o??n??"no body"}`,{status:e.status,...r!==void 0?{code:r}:{}})},X=class{constructor(t,n){this.tokens=n;this.baseUrl=t.apiUrl;}baseUrl;async send(t,n,r={}){let o=await this.attempt(n,r);if(o.ok)return o;throw await Kn(o,t)}async fetch(t,n,r,o={}){let a=await this.send(t,n,o),d;try{d=await a.json();}catch(w){throw new k(`${t} returned invalid JSON: ${w.message}`,{status:a.status,cause:w})}let m=Ve(r).safeParse(d);if(!m.success)throw new k(`${t} returned an unexpected response shape`,{status:a.status,cause:m.error});return m.data.data}async call(t,n,r={}){await this.send(t,n,r);}async attempt(t,n){let{body:r,headers:o}=Mn(n.body),a=n.auth===false?void 0:n.token??await this.tokens.token();try{return await fetch(`${this.baseUrl}${t}${Un(n.query)}`,{method:n.method??(n.body!==void 0?"POST":"GET"),headers:{...o,...a!==void 0?{Authorization:`Bearer ${a}`}:{}},...r!==void 0?{body:r}:{}})}catch(d){throw new k(`request to ${t} failed: ${d.message}`,{status:0,cause:d})}}};var zn=2048,Wn=/^[A-Za-z0-9._~%-]+$/,Bn=e=>{let t=e.trim();if(t.length===0)throw new l("documentation path must not be empty");if(t.length>zn)throw new l("documentation path is too long");if(t.startsWith("//")||t.includes("://"))throw new l("documentation path must be a path, not a URL");if(/[?#\\]/.test(t))throw new l("documentation path must not contain a query, fragment, or backslash");let n=t.startsWith("/")?t.slice(1):t;if(n.endsWith(".md"))throw new l("documentation path must not include the .md suffix");let r=n.split("/");if(r.some(o=>!Wn.test(o)||o==="."||o===".."))throw new l("documentation path contains an invalid segment");return r.join("/")},Hn=e=>e.length>500?`${e.slice(0,500)}\u2026`:e,wt=async(e,t)=>{let n;try{n=await fetch(e);}catch(o){throw new k(`${t} failed: ${o.message}`,{status:0,cause:o})}let r=await n.text().catch(()=>"");if(!n.ok)throw new k(`${t} failed (${n.status}): ${Hn(r)||"no body"}`,{status:n.status});return r},Z=class{constructor(t){this.docsUrl=t;}async get(t){let n=Bn(t),r=n.split("/").map(encodeURIComponent).join("/");return {path:n,content:await wt(`${this.docsUrl}/${r}.md`,`docs fetch for ${n}`)}}list(){return wt(`${this.docsUrl}/llms.txt`,"docs index fetch")}};var to="credentials.json",no=384,oo=()=>{let e=process.env.XDG_CONFIG_HOME;return join(e!==void 0&&e.length>0?e:join(homedir(),".config"),"mynth")},ro=async e=>{try{return await stat(e),true}catch{return false}},Q=class{filePath;constructor(){this.filePath=join(oo(),to);}async get(){if(!await ro(this.filePath))return;let t;try{t=await readFile(this.filePath,"utf8");}catch(o){throw new y(`could not read ${this.filePath}: ${o.message}`,{cause:o})}let n;try{n=JSON.parse(t);}catch(o){throw new y(`${this.filePath} is not valid JSON`,{cause:o})}let r=et.safeParse(n);if(!r.success)throw new y(`${this.filePath} has an unexpected shape; run \`mynth auth login\``,{cause:r.error});return r.data}async set(t){try{await mkdir(dirname(this.filePath),{recursive:true,mode:448});let n=`${this.filePath}.${process.pid}.tmp`;await writeFile(n,JSON.stringify(t),{encoding:"utf8",mode:no}),await rename(n,this.filePath);}catch(n){throw new y(`could not write ${this.filePath}: ${n.message}`,{cause:n})}}async clear(){try{await rm(this.filePath,{force:true});}catch(t){throw new y(`could not delete ${this.filePath}: ${t.message}`,{cause:t})}}};var ee=class{constructor(t){this.config=t;this.store=new Q,this.envApiKeySet=t.envApiKey!==void 0;}store;envApiKeySet;cached;async token(){if(this.config.envApiKey!==void 0)return this.config.envApiKey;if(this.cached!==void 0)return this.cached;let t=await this.read();if(t===void 0)throw new $("not authenticated: run `mynth auth login` or set MYNTH_API_KEY");return this.cached=t.api_key,this.cached}async status(){if(this.envApiKeySet)return {kind:"env"};let t=await this.read().catch(()=>{});return t===void 0?{kind:"none"}:{kind:"stored",credentials:t}}async save(t){await this.store.set(t),this.cached=void 0;}async clear(){await this.store.clear(),this.cached=void 0;}async read(){try{return await this.store.get()}catch(t){throw new $(`could not read stored credentials: ${t.message}`,{cause:t})}}};var kt=()=>{let e=G(),t=new ee(e);return {config:e,session:t,api:new X(e,t),docs:new Z(e.docsUrl)}};var be=["...........",".....4.....","....343....",".....2.....",".344.2.443.","..4432344..",".....2.....",".233.2.332.","..3322233..",".....1.....","..........."],J={1:"#318267",2:"#00a27a",3:"#33bb92",4:"#6bd1ad"},io=(e,t)=>{let n=r=>r!==".";return !n(e)&&!n(t)?" ":M.level===0?n(e)&&n(t)?"\u2588":n(e)?"\u2580":"\u2584":n(t)?n(e)?e===t?M.hex(J[e])("\u2588"):M.hex(J[e]).bgHex(J[t])("\u2580"):M.hex(J[t])("\u2584"):M.hex(J[e])("\u2580")},bt=()=>{let e=[];for(let t=0;t<be.length;t+=2){let n=be[t],r=be[t+1]??"",o="";for(let a=0;a<n.length;a+=1)o+=io(n[a],r[a]??".");e.push(o.trimEnd());}return e.filter(t=>t.length>0).join(`
7
+ `)};var vt=(e,t)=>e.fetch("api key create","/api-key",we,{body:{name:t.name,scopes:t.scopes},token:t.token}),At=(e,t)=>e.fetch("api key create","/api-key",we,{body:{name:t.name,scopes:t.scopes}}),$t=e=>e.fetch("api key list","/api-key",z$1.array(tt)),te=(e,t)=>e.call("api key delete",`/api-key/${t}`,{method:"DELETE"});var E=(e,t,n)=>{if(e.length===0){s(n);return}let r=e.map(d=>t.map(m=>m.value(d))),o=t.map((d,m)=>Math.max(d.header.length,...r.map(w=>w[m].length))),a=d=>d.map((m,w)=>w===d.length-1?m:m.padEnd(o[w])).join(" ");s(a(t.map(d=>d.header)));for(let d of r)s(a(d));};var f=()=>new Option("--json","Output machine-readable JSON instead of a human-readable summary"),j=()=>new Option("--yes","Confirm the destructive action (required; there is no interactive prompt)");var xt=["generate"],lo=e=>{if(e===void 0)return xt;let t=e.split(",").map(r=>r.trim()).filter(Boolean),n=t.filter(r=>!R.includes(r));if(t.length===0||n.length>0)throw new l(`invalid --scopes: ${n.join(", ")||"empty"}. Valid scopes: ${R.join(", ")}`);return t},Tt=e=>{let t=new Command("api-key").description("Manage Mynth API keys");return t.command("create").description("Create an API key. The key itself is shown once, on success.").argument("<name>","Name for the key, e.g. the app or environment it belongs to").option("--scopes <list>",`Comma-separated scopes (default: ${xt.join(",")}). Widening beyond \`generate\` requires the dashboard: the API refuses scope escalation from a CLI session.`).addOption(f()).action(async(n,r)=>{let o=await At(e.api,{name:n,scopes:lo(r.scopes)});if(r.json){u({key:o.raw,...o.apiKey});return}s(`${g.ok} Created API key "${o.apiKey.name??n}"`),s(` ID: ${o.apiKey.id}`),s(` Scopes: ${o.apiKey.scopes.join(", ")}`),s(""),s(` ${o.raw}`),s(" Save this now \u2014 it is shown only once and cannot be retrieved again.");}),t.command("list").description("List active API keys").addOption(f()).action(async n=>{let r=await $t(e.api);if(n.json){u(r);return}E(r,[{header:"ID",value:o=>o.id},{header:"Name",value:o=>o.name??"-"},{header:"Preview",value:o=>o.keyPreview},{header:"Scopes",value:o=>o.scopes.join(",")},{header:"Limit",value:o=>o.spendingLimit===null||o.spendingLimit===void 0?"-":`$${o.spendingLimit}/${o.spendingLimitPeriod??"period"}`},{header:"Created",value:o=>o.createdAt}],"No API keys found.");}),t.command("delete").description("Revoke an API key").argument("<id>","API key ID").addOption(j()).addOption(f()).action(async(n,r)=>{if(r.yes!==true)throw new l("refusing to delete without --yes");if(await te(e.api,n),r.json){u({deleted:n});return}s(`${g.ok} Revoked API key ${n}`);}),t};var ne=e=>e.fetch("me","/me",nt),Rt=e=>e.fetch("balance","/balance",ot);var co="urn:ietf:params:oauth:grant-type:device_code",Et=async e=>{try{return await e.json()}catch{return {}}},Ct=async(e,t,n)=>{try{return await fetch(`${Be}${e}`,{method:"POST",body:t,headers:{Accept:"application/json",...typeof t=="string"?{"Content-Type":"application/json"}:{}}})}catch(r){throw new y(`${n} failed: ${r.message}`,{cause:r})}},Pt=async(e,t)=>{let n=Qe.catch({}).parse(await Et(e)),r=n.error??n.code??"workos_error";return new _(r,n.error_description??n.message??`${t} failed`)},_t=async(e,t,n)=>{let r=t.safeParse(await Et(e));if(!r.success)throw new y(`${n} returned an unexpected response`,{cause:r.error});return r.data},Ot=async()=>{let e=await Ct("/user_management/authorize/device",new URLSearchParams({client_id:ge}),"device authorization");if(!e.ok)throw await Pt(e,"device authorization");return _t(e,Xe,"device authorization")},St=async e=>{let t=await Ct("/user_management/authenticate",JSON.stringify({grant_type:co,client_id:ge,device_code:e}),"device token exchange");if(!t.ok)throw await Pt(t,"device token exchange");return _t(t,Ze,"device token exchange")};var oe=e=>new Promise(t=>setTimeout(t,e)),jt=async(e,t,n)=>{let r=Array.from({length:e.length}),o=0,a=async()=>{for(;;){let d=o++;if(d>=e.length)return;r[d]=await n(e[d],d);}};return await Promise.all(Array.from({length:Math.min(t,e.length)},a)),r};var Nt=()=>{let e=process.env;return !(e.CI!==void 0||e.SSH_CONNECTION!==void 0||e.SSH_TTY!==void 0||process.stdout.isTTY!==true||process.platform==="linux"&&(e.DISPLAY===void 0&&e.WAYLAND_DISPLAY===void 0||existsSync("/.dockerenv")))},uo=e=>{switch(process.platform){case "darwin":return {command:"open",args:[e]};case "win32":return {command:"cmd",args:["/c","start","",e]};default:return {command:"xdg-open",args:[e]}}},Dt=async e=>{try{let{protocol:r}=new URL(e);if(r!=="https:"&&r!=="http:")return false}catch{return false}let{command:t,args:n}=uo(e);return new Promise(r=>{try{let o=spawn(t,[...n],{detached:true,stdio:"ignore"});o.on("error",()=>r(false)),o.unref(),setTimeout(()=>r(true),150).unref();}catch{r(false);}})};var fo="https://mynth.io/dashboard",Lt=e=>`${fo}/keys/${e}`,yo=5e3,ho=5,wo=()=>`mynth-cli (${hostname()})`,ko=async(e,t,n)=>{let r=t;for(;;){if(Date.now()>=n)throw new y("device code expired before approval");try{return await St(e)}catch(o){if(!(o instanceof _))throw o;if(o.code==="access_denied")throw new $("login was denied");if(o.code==="expired_token")throw new $("device code expired; run `mynth auth login` again");if(o.code!=="authorization_pending"&&o.code!=="slow_down")throw o;o.code==="slow_down"&&(r+=yo),await oe(r);}}},bo=e=>new Command("login").description("Authenticate with Mynth and store a long-lived API key").option("--scopes <list>",`Comma-separated scopes for the created key (default: ${R.join(",")})`).option("--no-browser","Print the login URL instead of opening a browser").addOption(f()).action(async t=>{if(e.session.envApiKeySet)throw new $("MYNTH_API_KEY is set and takes precedence over login. Unset it to log in, or keep using the env API key.");let n=t.scopes!==void 0?t.scopes.split(",").map(x=>x.trim()).filter(Boolean):[...R],r=n.filter(x=>!R.includes(x));if(n.length===0||r.length>0)throw new $(`invalid --scopes: ${r.join(", ")||"empty"}. Valid scopes: ${R.join(", ")}`);let o=await Ot(),a=o.verification_uri_complete??o.verification_uri,d=o.verification_uri_complete!==void 0,m=t.browser!==false&&Nt()?await Dt(a):false;s(""),s(` ${m?"Opened":"Open"}: ${a}`),s(d?` Check the page shows this code: ${o.user_code}`:` Enter this code: ${o.user_code}`),s(""),s("Waiting for confirmation...");let w=await ko(o.device_code,(o.interval??ho)*1e3,Date.now()+o.expires_in*1e3),b=wo(),A=await vt(e.api,{name:b,scopes:n,token:w.access_token});await e.session.save({kind:"api_key",api_key:A.raw,id:A.apiKey.id});let C=w.user?.email??w.user?.id??"unknown user";if(t.json){u({user:w.user?.id??null,apiKey:{id:A.apiKey.id,name:A.apiKey.name??b,keyPreview:A.apiKey.keyPreview,scopes:A.apiKey.scopes},storedAt:e.session.store.filePath});return}s(`${g.ok} Logged in as ${C}`),s(` Created API key "${b}" with scopes: ${A.apiKey.scopes.join(", ")}`),s(` Stored in ${e.session.store.filePath}`),s(` Set a spending limit or change scopes: ${Lt(A.apiKey.id)}`);}),vo=e=>new Command("logout").description("Revoke this machine's API key and clear local credentials").action(async()=>{let t=await e.session.status(),n=t.kind==="stored"?t.credentials.id:void 0,r=false;if(n!==void 0)try{await te(e.api,n),r=true;}catch(o){T(`Warning: could not revoke API key ${n}: ${o.message}`),T(`Revoke it manually at ${Lt(n)}`);}await e.session.clear(),s(`${g.ok} Local credentials cleared${r?" and API key revoked":""}`),n===void 0&&t.kind==="stored"&&s(" The stored key was not created by `auth login`, so it was left active."),e.session.envApiKeySet&&s("Note: MYNTH_API_KEY is still set in your environment and will still be used.");}),Ao=e=>new Command("status").description("Show how this machine is authenticated, without calling the API").addOption(f()).action(async t=>{let n=await e.session.status();if(t.json){u({source:n.kind,...n.kind==="stored"?{apiKeyId:n.credentials.id??null,path:e.session.store.filePath}:{}});return}switch(n.kind){case "env":s("Authenticated via env: MYNTH_API_KEY");return;case "none":s("Not authenticated. Run `mynth auth login`, or set an API key.");return;case "stored":{let{id:r}=n.credentials;s("Authenticated via stored API key"),r!==void 0&&s(` key: ${r}`),s(` stored: ${e.session.store.filePath}`),s(" Run `mynth whoami` for its current scopes and spending limit.");}}}),ve=e=>new Command("whoami").description("Print the active Mynth identity, verified against the API").addOption(f()).action(async t=>{let n=await e.session.status();if(n.kind==="none")throw new $("not authenticated: run `mynth auth login` or set MYNTH_API_KEY");let r=await ne(e.api);if(t.json){u({source:n.kind,...r});return}s(n.kind==="env"?"env:MYNTH_API_KEY":"api-key"),s(` user: ${r.userId}`),s(` method: ${r.auth.method}`);let o=r.auth.apiKey;o!==void 0&&(s(` key: ${o.name??"unnamed"} (${o.keyPreview})`),o.scopes!==void 0&&o.scopes.length>0&&s(` scopes: ${o.scopes.join(", ")}`),o.spending?.mode==="limited"&&s(` spend: $${o.spending.used} of $${o.spending.limit} per ${o.spending.period} ($${o.spending.remaining} left)`));}),Ft=e=>new Command("auth").description("Manage Mynth authentication").addCommand(bo(e)).addCommand(vo(e)).addCommand(Ao(e)).addCommand(ve(e));var Ut=e=>new Command("balance").description("Show account balance, and the active API key's spending limit").addOption(f()).action(async t=>{let n=await Rt(e.api),o=(await ne(e.api).catch(()=>{}))?.auth.apiKey?.spending;if(t.json){u({...n,...o!==void 0?{spending:o}:{}});return}if(s(`Balance: $${n.balance}`),s(`Reserved: $${n.reserved}`),s(`Available: $${n.available}`),o!==void 0){if(s(""),o.mode==="unlimited"){s("API key spending: unlimited");return}s(`API key limit: $${o.limit} / ${o.period}`),s(` used: $${o.used}`),s(` remaining: $${o.remaining}`);}});var Jt={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},Ae=Object.keys(Jt),$e=async()=>{try{let e="";process.stdin.setEncoding("utf8");for await(let t of process.stdin)e+=t;return e}catch(e){throw new y("could not read stdin",{cause:e})}},z=async e=>{if(e==="-")return $e();try{return await readFile(e,"utf8")}catch(t){throw new l(`could not read ${e}: ${t.message}`)}},Kt=async e=>{let t=await z(e);try{return JSON.parse(t)}catch(n){throw new l(`invalid JSON in ${e}: ${n.message}`)}},zt=async e=>{let t=extname(e).toLowerCase(),n=Jt[t];if(n===void 0)throw new l(`unsupported image extension "${t}" for ${e} (allowed: ${Ae.join(", ")})`);let r;try{r=await readFile(e);}catch(o){throw new l(`could not read ${e}: ${o.message}`)}return new File([new Uint8Array(r)],basename(e),{type:n})};var Wt=e=>{let t=new Command("set").description("Set local CLI configuration");t.command("api-key").description("Save an existing Mynth API key to the credentials file").argument("<value>","API key value, or `-` to read it from stdin").action(async r=>{let o=(r==="-"?await $e():r).trim();if(o.length===0)throw new l("API key is empty");await e.session.save({kind:"api_key",api_key:o}),s(`${g.ok} API key saved to ${e.session.store.filePath}`),e.session.envApiKeySet&&s("Note: MYNTH_API_KEY is also set in your environment and takes precedence.");});let n=new Command("unset").description("Unset local CLI configuration");return n.command("api-key").description("Clear stored Mynth credentials").action(async()=>{await e.session.clear(),s(`${g.ok} Stored credentials cleared`),s("This does not revoke the key; use `mynth auth logout` for a CLI-created key.");}),new Command("config").description("Manage local CLI configuration").addCommand(t).addCommand(n)};var Bt=e=>e.fetch("destination list","/destinations",z$1.array(U)),Ht=(e,t)=>e.fetch("destination fetch",`/destinations/${t}`,U),Yt=(e,t)=>e.fetch("destination create","/destinations",U,{body:t}),Gt=(e,t,n)=>e.fetch("destination update",`/destinations/${t}`,U,{method:"PUT",body:n}),qt=(e,t,n)=>e.call("destination test",`/destinations/${t}/test`,{body:{path:n}}),Vt=(e,t)=>e.call("destination delete",`/destinations/${t}`,{method:"DELETE"});var xe=/^[a-z0-9-]+$/,Te=64,Eo=["bunny","r2","s3"],Ee=["de","uk","ny","la","sg","se","br","jh","syd"],Co=["default","eu","fedramp"],Po=(e,t)=>{if(e===void 0)throw new l(`${t} is required`);return e},W=(e,t,n)=>{if(t===void 0)throw new l(`--provider ${e} requires ${n}`);return t},B=(e,t)=>t!==void 0?{[e]:t}:{},_o=(e,t)=>{switch(t){case "bunny":{let n=e.region;if(n!==void 0&&!Ee.includes(n))throw new l(`invalid bunny --region "${n}". One of: ${Ee.join(", ")}`);return {id:"bunny",storage_zone:W(t,e.storageZone,"--storage-zone"),...B("region",n)}}case "r2":return {id:"r2",account_id:W(t,e.accountId,"--account-id"),bucket:W(t,e.bucket,"--bucket"),...B("jurisdiction",e.jurisdiction)};case "s3":return {id:"s3",bucket:W(t,e.bucket,"--bucket"),region:W(t,e.region,"--region"),...B("endpoint",e.endpoint),...B("force_path_style",e.forcePathStyle)}}},Oo=async(e,t)=>{let n=(await z(e)).trim();if(n.startsWith("{"))try{return JSON.parse(n)}catch(r){throw new l(`invalid JSON secret from ${e}: ${r.message}`)}if(t!=="bunny")throw new l(`--provider ${t} needs a JSON secret: { "access_key_id": "...", "secret_access_key": "..." }`);if(n.length===0)throw new l(`secret from ${e} is empty`);return {password:n}},So=e=>({path_template:Po(e.pathTemplate,"--path-template"),...B("url_template",e.urlTemplate)}),Zt=async(e,t,n)=>{if(e.file!==void 0){if(e.provider!==void 0)throw new l("use either --file or the typed --provider flags, not both");let a=await Kt(e.file);if(a===null||typeof a!="object"||Array.isArray(a))throw new l(`${e.file} must contain a JSON object`);return {...a,...t}}let r=e.provider;if(r===void 0)throw new l("--provider or --file is required");let o=e.secret!==void 0?await Oo(e.secret,r):void 0;if(o===void 0&&n)throw new l("--secret <path|-> is required (use `-` to read the secret from stdin)");return {...t,provider:_o(e,r),config:So(e),...o!==void 0?{secret:o}:{}}},Qt=e=>e.addOption(new Option("--provider <id>","Storage provider. Required unless --file is used.").choices([...Eo])).option("--path-template <template>",'Object path template, e.g. "images/{id}"').option("--url-template <template>",'Public URL template; must contain {path}, e.g. "https://cdn.example.com/{path}"').option("--storage-zone <name>","bunny: storage zone name").option("--region <region>",`bunny: one of ${Ee.join(", ")}. s3: the bucket's region, e.g. us-east-1.`).option("--account-id <id>","r2: Cloudflare account ID").option("--bucket <name>","r2/s3: bucket name").addOption(new Option("--jurisdiction <name>","r2: data jurisdiction").choices([...Co])).option("--endpoint <url>","s3: custom endpoint for S3-compatible storage").option("--force-path-style","s3: use path-style addressing instead of virtual-hosted").option("--secret <path>","Path to a JSON secret file, or `-` to read it from stdin. bunny also accepts a bare password.").option("--file <path>","Full destination JSON body, or `-` for stdin. Alternative to the flags above.").addOption(f()),Re=e=>{s(`Destination ${e.id}`),s(` Name: ${e.name}`),s(` Provider: ${e.provider.id}`),e.config.path_template!==void 0&&s(` Path: ${e.config.path_template}`),e.config.url_template!==void 0&&s(` URL: ${e.config.url_template}`),s(` Created: ${e.createdAt}`),s(` Updated: ${e.updatedAt}`);},en=e=>{let t=new Command("destination").description("Manage storage destinations that generated images are delivered to");t.command("list").description("List storage destinations").addOption(f()).action(async o=>{let a=await Bt(e.api);if(o.json){u(a);return}E(a,[{header:"ID",value:d=>d.id},{header:"Name",value:d=>d.name},{header:"Provider",value:d=>d.provider.id},{header:"Created",value:d=>d.createdAt}],"No destinations found.");}),t.command("get").description("Fetch a destination by ID").argument("<id>","Destination ID").addOption(f()).action(async(o,a)=>{let d=await Ht(e.api,o);if(a.json){u(d);return}Re(d);});let n=t.command("create").description("Create a storage destination").argument("<name>",`Destination slug: 1-${Te} chars, ${xe.source}. Immutable.`);Qt(n).action(async(o,a)=>{if(o.length>Te||!xe.test(o))throw new l(`invalid destination name "${o}": expected 1-${Te} chars matching ${xe.source}`);let d=await Yt(e.api,await Zt(a,{name:o},true));if(a.json){u(d);return}Re(d),s(""),s(`Next: verify the credentials with \`mynth destination test ${d.id}\``);});let r=t.command("update").description("Replace a destination's provider and config. The slug is immutable.").argument("<id>","Destination ID");return Qt(r).action(async(o,a)=>{let d=await Zt(a,{},false);delete d.name;let m=await Gt(e.api,o,d);if(a.json){u(m);return}Re(m);}),t.command("test").description("Verify a destination's credentials by uploading a probe file").argument("<id>","Destination ID").option("--path <path>","Object path to write (defaults to a unique probe path)").addOption(f()).action(async(o,a)=>{let d=a.path??`mynth-cli-test/${Date.now()}.txt`;if(await qt(e.api,o,d),a.json){u({id:o,path:d,ok:true});return}s(`${g.ok} Credentials valid (wrote ${d})`);}),t.command("delete").description("Delete a destination").argument("<id>","Destination ID").addOption(j()).addOption(f()).action(async(o,a)=>{if(a.yes!==true)throw new l("refusing to delete without --yes");if(await Vt(e.api,o),a.json){u({deleted:o});return}s(`${g.ok} Deleted destination ${o}`);}),t};var tn=e=>{let t=new Command("docs").description("Read Mynth documentation (no authentication)");return t.command("get").description("Fetch a documentation page as Markdown").argument("<path>","Documentation path, without the .md suffix (e.g. guides/async-and-polling)").addOption(f()).action(async(n,r)=>{let o=await e.docs.get(n);if(r.json){u(o);return}s(o.content);}),t.command("list").description("Fetch the complete documentation index").addOption(f()).action(async n=>{let r=await e.docs.list();if(n.json){u({content:r});return}s(r);}),t};var No={4:"Production-ready",3:"Usable, with fixes",2:"Not fit for purpose",1:"Discard"},Pe=e=>e.type==="image.generate",re=e=>{let t=gt.safeParse(e??{});return t.success?{...t.data,images:t.data.images??[]}:{images:[]}},_e=e=>e.status==="success"?e.url??e.mynth_url??void 0:void 0,Ce=e=>e.message!==void 0?`${e.code}: ${e.message}`:e.code,N=e=>{if(e.length!==0){s(`${g.ok} Uploaded ${O(e.length,"image")}`);for(let t of e)s(` ${t.path} -> ${t.url}`);}},Do=e=>{let t=re(e.result),n=t.images,r=n.filter(o=>o.status==="success").length;if(s(`${g.ok} Generated ${r}/${n.length} ${n.length===1?"image":"images"} (task ${e.id})`),t.model!==void 0&&s(` Model: ${t.model}`),e.cost!==null&&s(` Cost: ${e.cost}`),t.magic_prompt!==void 0&&(s(""),s("Enhanced prompt (mynth):"),s(` ${t.magic_prompt.positive}`),t.magic_prompt.negative&&s(` negative: ${t.magic_prompt.negative}`)),n.length!==0){s("");for(let o of n){if(o.status==="failed"){s(` ${g.fail} ${Ce(o.error)}`);continue}let a=o.rating?.status==="success"?` [${o.rating.level}]`:"";s(` ${g.ok} ${_e(o)??"(no url)"}${a}`),o.destination?.status==="failed"&&s(` ${g.fail} destination ${o.destination.name}: ${Ce(o.destination.error)}`);}}},Oe=e=>{if(s(`${q(e.status)} Task ${e.id}`),s(` Type: ${e.type}`),s(` Status: ${e.status}`),e.cost!==null&&s(` Cost: ${e.cost}`),s(` Created: ${e.createdAt}`),s(` Updated: ${e.updatedAt}`),e.errors!==null&&e.errors!==void 0&&e.errors.length>0){s(""),s("Errors:");for(let t of e.errors)s(` ${g.fail} ${Ce(t)}`);}e.result!==null&&e.result!==void 0&&(s(""),s("Result:"),s(qe(JSON.stringify(e.result,null,2))));},ie=e=>{if(Pe(e)&&e.status==="completed"){Do(e);return}Oe(e);},se=e=>{let t=re(e.result);return {taskId:e.id,status:e.status,images:t.images.map(n=>n.status==="success"?{status:"success",url:n.url??null,mynth_url:n.mynth_url??null,size:n.size,format:n.format,rating:n.rating,destination:n.destination}:{status:"failed",error:n.error}),...t.magic_prompt!==void 0?{magic_prompt:t.magic_prompt}:{},...e.cost!==null?{cost:e.cost}:{},...t.model!==void 0?{model:t.model}:{}}},nn=e=>{if(s(`${g.ok} Reviewed (task ${e.taskId})`),s(` Score: ${e.score}/4 \u2014 ${No[e.score]??"unknown"}`),e.cost!==null&&s(` Cost: ${e.cost}`),s(` ${e.url}`),e.summary.length>0&&(s(""),s("Summary"),s(` ${e.summary}`)),e.findings.length>0){s(""),s(`Findings (${e.findings.length})`);for(let t of e.findings)s(` \u2022 [${t.severity}] ${t.category} \u2014 ${t.finding}`),s(` where: ${t.where} (${t.confidence} confidence)`);}if(e.strengths.length>0){s(""),s(`Strengths (${e.strengths.length})`);for(let t of e.strengths)s(` \u2022 ${t.strength} (${t.confidence} confidence)`);}};var I=(e,t=[])=>[...t,e],Se=e=>t=>{let n=Number.parseInt(t,10);if(!Number.isInteger(n)||String(n)!==t)throw new l(`invalid ${e}: "${t}" (expected an integer)`);return n},je=e=>t=>{let n=Se(e)(t);if(n<=0)throw new l(`invalid ${e}: "${t}" (expected a positive integer)`);return n},on=(e,t)=>{let n;try{n=JSON.parse(e);}catch(r){throw new l(`invalid JSON in ${t}: ${r.message}`)}if(n===null||typeof n!="object"||Array.isArray(n))throw new l(`${t} must be a JSON object`);return n},ae=e=>/^https?:\/\//i.test(e);var H=10,D=async(e,t)=>{if(t.length===0)throw new l("no files to upload");if(t.length>H)throw new l(`too many files: ${t.length} (max ${H})`);let n=new FormData;for(let o of await Promise.all(t.map(zt)))n.append("images",o);let{urls:r}=await e.fetch("upload","/image/upload",dt,{body:n});if(r.length!==t.length)throw new l(`upload returned ${r.length} URLs for ${t.length} files`);return t.map((o,a)=>({path:o,url:r[a]}))},Y=(e,t,n)=>e.fetch(`image ${t}`,`/image/${t}`,lt,{body:n}),rn=(e,t)=>e.fetch("estimate","/image/generate/estimate",ct,{body:t});var Ne=1800*1e3,Lo=12e3,Fo=2500,Uo=5e3,Mo=500,Jo=new Set([0,404,408,429]),Ko=e=>e instanceof k&&(Jo.has(e.status)||e.status>=500),zo=10,De=(e,t)=>e.fetch("task fetch",`/tasks/${t}`,it),an=(e,t={})=>e.fetch("task list","/tasks",z$1.array(st),{query:t}),Wo=(e,t)=>e.fetch("task status",`/tasks/${t}/status`,z$1.object({status:V})).then(n=>n.status),dn=(e,t)=>e.fetch("task result",`/tasks/${t}/result`,at),L=async(e,t,n=Ne)=>{let r=Date.now(),o=0;for(;;){try{let m=await Wo(e,t);if(o=0,m!=="pending")return await De(e,t)}catch(m){if(!Ko(m)||++o>zo)throw m}let a=Date.now()-r;if(a>=n)throw new k(`task ${t} did not complete within ${Math.round(n/1e3)}s; it may still finish, check \`mynth task get ${t}\``,{status:0});let d=a<Lo?Fo:Uo;await oe(d+Math.floor(Math.random()*Mo));}};var Ho=["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"],Yo=2800,Go=e=>{let t=e.slice();for(let n=t.length-1;n>0;n--){let r=Math.floor(Math.random()*(n+1));[t[n],t[r]]=[t[r],t[n]];}return t},F=async(e,t={})=>{if(process.stderr.isTTY!==true)return e;let n=Go(t.messages??Ho),r=0,o=Bo({text:n[0]??"Working",stream:process.stderr}).start(),a=setInterval(()=>{r++,o.text=n[r%n.length]??"Working";},Yo);try{return await e}finally{clearInterval(a),o.stop();}};var le=2,ce=7,pe=async(e,t)=>{if(ae(t))return {url:t,uploads:[]};let n=await D(e.api,[t]);return {url:n[0].url,uploads:n}},qo=z$1.array(z$1.object({value:z$1.string(),description:z$1.string()})),Vo=e=>{let t=e.indexOf("=");if(t<=0)throw new l(`invalid --level "${e}": expected "value=description"`);let n={value:e.slice(0,t).trim(),description:e.slice(t+1).trim()};if(n.value.length===0||n.description.length===0)throw new l(`invalid --level "${e}": value and description must both be non-empty`);return n},ln=(e,t)=>{let n;try{n=JSON.parse(e);}catch(o){throw new l(`invalid JSON in ${t}: ${o.message}`)}let r=qo.safeParse(n);if(!r.success)throw new l(`invalid levels in ${t}: expected an array of { value, description }`);return r.data},me=async e=>{let t=e.level??[],n=[t.length>0?"--level":void 0,e.levelsFile!==void 0?"--levels-file":void 0,e.levelsJson!==void 0?"--levels-json":void 0].filter(a=>a!==void 0);if(n.length===0)return;if(n.length>1)throw new l(`conflicting level options: ${n.join(", ")} \u2014 use only one`);let r=t.length>0?t.map(Vo):e.levelsFile!==void 0?ln(await z(e.levelsFile),e.levelsFile):ln(e.levelsJson??"[]","--levels-json");if(r.length<le||r.length>ce)throw new l(`levels must have between ${le} and ${ce} items (got ${r.length})`);let o=new Set;for(let a of r){if(o.has(a.value))throw new l(`duplicate level value: "${a.value}"`);o.add(a.value);}return r},Xo=(e,t)=>{let n=fe(e);return new k(`${t} task ${e.id} failed${n!==void 0?` (${n})`:""}`,{status:0,...n!==void 0?{code:n}:{}})},ue=async(e,t)=>{let n=await Y(e.api,t.endpoint,t.body),r=L(e.api,n.taskId),o=t.quiet?await r:await F(r);if(o.status!=="completed")throw Xo(o,t.endpoint);let a=t.schema.safeParse(o.result);if(!a.success)throw new k(`${t.endpoint} task ${o.id} returned an unexpected result`,{status:0,cause:a.error});return {taskId:o.id,cost:o.cost,result:a.data}};var Qo=["low","high"],Fe="Image URL (http/https), or a local image file to upload first",er=e=>e.option("-l, --level <value>",`Custom rating level as "value=description" (repeatable, ${le}-${ce} items). Example: -l safe="No explicit content" -l nsfw="Contains nudity"`,I).option("--levels-file <path>",'JSON file holding an array of { "value", "description" }, or `-` for stdin. Use when descriptions contain shell metacharacters.').option("--levels-json <json>","Inline JSON array of { value, description }."),tr=e=>{let t=new Command("rate").description("Classify an image against the default sfw/nsfw levels, or custom ones").argument("<image>",Fe).addOption(f());return er(t).action(async(n,r)=>{let o=await me(r),{url:a,uploads:d}=await pe(e,n),{taskId:m,cost:w,result:b}=await ue(e,{endpoint:"rate",body:o!==void 0?{url:a,mode:"custom",levels:o}:{url:a},schema:pt,quiet:r.json===true});if(r.json){u({taskId:m,cost:w,...b});return}N(d),s(`${g.ok} Rated (task ${m})`),s(` ${b.level} ${b.url}`);}),t},nr=e=>new Command("alt").description("Generate accessibility alt text for an image").argument("<image>",Fe).addOption(f()).action(async(t,n)=>{let{url:r,uploads:o}=await pe(e,t),{taskId:a,cost:d,result:m}=await ue(e,{endpoint:"alt",body:{url:r},schema:mt,quiet:n.json===true});if(n.json){u({taskId:a,cost:d,...m});return}N(o),s(`${g.ok} Generated alt text (task ${a})`),s(` ${m.alt}`),s(` ${m.url}`);}),or=e=>new Command("review").description("Review image quality with a multi-model panel (score, findings, strengths)").argument("<image>",Fe).addOption(new Option("--effort <level>",'"high" (default) runs five strong vision models; "low" runs three smaller ones for faster, cheaper triage').choices([...Qo])).addOption(f()).action(async(t,n)=>{let{url:r,uploads:o}=await pe(e,t),{taskId:a,cost:d,result:m}=await ue(e,{endpoint:"review",body:{url:r,...n.effort!==void 0?{effort:n.effort}:{}},schema:ut,quiet:n.json===true});if(n.json){u({taskId:a,cost:d,...m});return}N(o),nn({taskId:a,cost:d,...m,findings:m.findings??[],strengths:m.strengths??[]});}),cn=e=>[tr(e),nr(e),or(e)];var dr=4,lr=(e,t,n)=>{try{let r=new URL(e).pathname.split("/").filter(Boolean).pop();if(r!==void 0&&r.length>0)return decodeURIComponent(r)}catch{}return `${t}-${n}`},pn=async e=>{let t=resolve(e.directory);try{await mkdir(t,{recursive:true});}catch(n){throw new y(`could not create ${t}: ${n.message}`,{cause:n})}return jt(e.urls,dr,async(n,r)=>{let o;try{o=await fetch(n);}catch(d){throw new y(`download failed for ${n}: ${d.message}`,{cause:d})}if(!o.ok)throw new y(`download failed for ${n} (${o.status})`);let a=join(t,lr(n,e.fallbackPrefix,r));try{await writeFile(a,new Uint8Array(await o.arrayBuffer()));}catch(d){throw new y(`could not write ${a}: ${d.message}`,{cause:d})}return a})};var Ue=20,ur=["png","jpg","webp"],Me=["auto","person","garment","pose","source","reference"],gr="https://dry-run.mynth.io/input",fr=e=>{let t=e.indexOf(":"),n,r=e;if(t>0&&!/^https?:/i.test(e)){let o=e.slice(0,t);if(!Me.includes(o))throw new l(`invalid --input role "${o}". Expected one of: ${Me.join(", ")}`);n=o,r=e.slice(t+1);}if(r.length===0)throw new l(`invalid --input "${e}": missing path or URL`);return {...n!==void 0?{role:n}:{},value:r,isLocalFile:!ae(r)}},yr=e=>{let t=e.webhookUrl??[],n=e.dashboardWebhooks===false;if(!(t.length===0&&!n))return {...n?{dashboard:false}:{},...t.length>0?{custom:t.map(r=>({url:r}))}:{}}},hr=async(e,t,n,r)=>{let o=await me(t),a=o!==void 0?{mode:"custom",levels:o}:t.contentRating===true?true:void 0,d=t.destination??e.config.envDestination,m=yr(t),w=n.map(b=>({type:"image",...b.role!==void 0?{as:b.role}:{},source:{type:"url",url:b.isLocalFile?r.get(b.value)??gr:b.value}}));return {prompt:t.prompt??"",...t.model!==void 0?{model:t.model}:{},...t.negative!==void 0?{negative_prompt:t.negative}:{},...t.magicPrompt===true?{magic_prompt:true}:{},...t.size!==void 0?{size:t.size}:{},...t.count!==void 0?{count:t.count}:{},...t.format!==void 0?{output:{format:t.format}}:{},...w.length>0?{inputs:w}:{},...d!==void 0?{destination:d}:{},...a!==void 0?{rating:a}:{},...m!==void 0?{webhook:m}:{},...t.metadata!==void 0?{metadata:on(t.metadata,"--metadata")}:{}}},wr=(e,t)=>{let n=re(e.result).images.map(_e).filter(r=>r!==void 0);return n.length===0?Promise.resolve([]):pn({urls:n,directory:t,fallbackPrefix:e.id})},mn=e=>{let t=new Command("generate").description("Generate images with Mynth").addHelpText("after",`
8
+ Browse models with: mynth models list`).option("-p, --prompt <text>","Text prompt describing the image to generate").option("-n, --negative <text>","Negative prompt: elements to exclude").option("--magic-prompt","Let Mynth expand the prompt before generating").option("-m, --model <id>",'Model ID (e.g. "black-forest-labs/flux.2-pro"). Default: auto').option("-s, --size <size>",'Size preset or aspect ratio: "square", "portrait", "landscape", "1:1", "16:9", "16:9_4k", "auto", ...').option("-c, --count <number>","Number of images to generate (default: 1)",Se("--count")).addOption(new Option("-f, --format <format>","Output image format").choices([...ur])).option("-i, --input <value>",`Input image as "[role:]path-or-url" (repeatable, up to ${Ue}). Roles: ${Me.join(", ")}. Examples: -i ./img.jpg, -i source:https://example.com/a.png`,I).option("-o, --output-dir <dir>","Directory to save generated images into. Created if missing. Ignored with --async.").option("--destination <name>","Slug of a configured storage destination to deliver results to. Defaults to MYNTH_DESTINATION.").option("--metadata <json>","Inline JSON object attached to the task (max 2KB)").option("--content-rating","Classify each image with the default sfw/nsfw levels. For custom levels use --level.").option("-l, --level <value>",'Custom rating level as "value=description" (repeatable, 2-7)',I).option("--levels-file <path>","JSON file of custom rating levels, or `-` for stdin").option("--levels-json <json>","Inline JSON array of custom rating levels").option("--webhook-url <url>","Deliver this task's events to this URL (repeatable, max 5)",I).option("--no-dashboard-webhooks","Skip dashboard-configured webhooks for this task").option("--dry-run","Validate the request and print the estimated cost without generating").option("--async","Print the task ID immediately instead of waiting for the result").option("--detailed","Include the full task record in --json output").addOption(f());return t.action(async n=>{let r=n.input??[];if(r.length>Ue)throw new l(`too many --input values: ${r.length} (max ${Ue})`);let o=r.map(fr),a=[...new Set(o.filter(h=>h.isLocalFile).map(h=>h.value))],d=a.length>0&&n.dryRun!==true?await D(e.api,a):[],m=await hr(e,n,o,new Map(d.map(h=>[h.path,h.url])));if(n.dryRun===true){let h=await rn(e.api,m);if(n.json){u(h);return}let P=h.estimateKind==="upper_bound"?" (upper bound)":"";s(`${g.ok} Estimated cost: $${h.estimatedCost}${P}`);return}if(n.async===true){let h=await Y(e.api,"generate",{...m,access:{pat:{enabled:true}}}),P=h.access?.publicAccessToken;if(n.json){u({taskId:h.taskId,...h.estimatedCost!==void 0?{estimatedCost:h.estimatedCost}:{},...P!==void 0?{access:{publicAccessToken:P}}:{}});return}s(`${g.ok} Task created: ${h.taskId}`),P!==void 0&&s(` Public access token: ${P}`),s(` Await it with: mynth task wait ${h.taskId}`);return}let w=await Y(e.api,"generate",m),b=L(e.api,w.taskId),A=n.json?await b:await F(b),C=n.outputDir!==void 0?resolve(n.outputDir):void 0,x=C!==void 0?await wr(A,C):[];if(n.json){let h=n.detailed===true?A:se(A);u(C!==void 0?{...h,downloadedFiles:x}:h);return}if(N(d),ie(A),x.length>0){s(""),s(`${g.ok} Saved ${O(x.length,"image")} to ${C}`);for(let h of x)s(` ${h}`);}}),t};var un=e=>new Command("upload").description("Upload local images to Mynth's temporary input storage").argument("<files...>",`Local image files (${Ae.join(", ")})`).addOption(f()).action(async(t,n)=>{if(t.length>H)throw new l(`too many files: ${t.length} (max ${H})`);let r=await D(e.api,t);if(n.json){u({images:r});return}s(`${g.ok} Uploaded ${O(r.length,"image")}`);for(let{path:o,url:a}of r)s(` ${o}`),s(` -> ${a}`);});var gn=e=>{let t=new Command("image").description("Generate, upload, and analyze images");t.addCommand(mn(e)),t.addCommand(un(e));for(let n of cn(e))t.addCommand(n);return t};var fn=e=>e.fetch("models list","/models",z$1.array(rt),{auth:false});var kn=e=>e.id.split("/")[0],yn=e=>{let t=e.pricing?.perImage.base;if(t===void 0)return;let n=Number.parseFloat(t);return Number.isFinite(n)?n:void 0},Ir=e=>e.pricing?.perInput!==void 0,hn=e=>t=>{let n=Number.parseFloat(t);if(!Number.isFinite(n)||n<0)throw new l(`invalid ${e}: "${t}" (expected a non-negative number)`);return n},bn=.3,xr=(e,t)=>wn.go(t,e,{keys:["id","displayName"],threshold:bn}).map(n=>n.obj),Tr=(e,t)=>{let n=[...new Set(e.map(kn))];return n.includes(t)?new Set([t]):new Set(wn.go(t,n,{threshold:bn}).map(r=>r.target))},Rr=(e,t)=>{let n=e;if(t.org!==void 0){let r=Tr(n,t.org);n=n.filter(o=>r.has(kn(o)));}if(t["4k"]&&(n=n.filter(r=>r.pricing?.perImage["4k"]!==void 0)),t.capability!==void 0){let r=t.capability==="img2img";n=n.filter(o=>Ir(o)===r);}if(t.maxPrice!==void 0){let r=t.maxPrice;n=n.filter(o=>(yn(o)??1/0)<=r);}if(t.minPrice!==void 0){let r=t.minPrice;n=n.filter(o=>(yn(o)??-1/0)>=r);}return t.search!==void 0&&(n=xr(n,t.search)),n},vn=e=>{let t=new Command("models").description("Browse the public Mynth model catalog");return t.command("list").description("List available image generation models and their per-image pricing").option("-s, --search <query>","Fuzzy match against model ID (which includes the org) and name").option("--org <org>","Only models from this org, fuzzy matched (e.g. bfl, google)").option("--max-price <usd>","Only models at or below this base per-image price",hn("--max-price")).option("--min-price <usd>","Only models at or above this base per-image price",hn("--min-price")).option("--4k","Only models with 4K pricing").addOption(new Option("--capability <capability>","img2img: bills for image inputs. txt2img: prompt-only, no image inputs").choices(["img2img","txt2img"])).addOption(f()).action(async n=>{let r=Rr(await fn(e.api),n);if(n.json){u(r);return}E(r,[{header:"ID",value:o=>o.id},{header:"Name",value:o=>o.displayName??"-"},{header:"Base",value:o=>o.pricing?.perImage.base??"-"},{header:"4K",value:o=>o.pricing?.perImage["4k"]??"-"},{header:"Input fee",value:o=>o.pricing?.perInput??"-"}],"No models matched the filters.");}),t};var An=Ne/1e3,Cr=e=>e.option("--detailed","Include the full task record instead of a compact summary"),$n=e=>{let t=new Command("task").description("Inspect and await Mynth tasks");t.command("get").description("Fetch a task by ID").argument("<id>","Task ID").addOption(f()).action(async(r,o)=>{let a=await De(e.api,r);if(o.json){u(a);return}Oe(a);}),t.command("result").description("Print a task's result payload as JSON").argument("<id>","Task ID").action(async r=>{u((await dn(e.api,r)).result);});let n=t.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: ${An})`,je("--timeout")).addOption(f());return Cr(n).action(async(r,o)=>{let a=L(e.api,r,(o.timeout??An)*1e3),d=o.json?await a:await F(a);if(d.status==="failed"&&(process.exitCode=Ge(d)),o.json){let m=!o.detailed&&Pe(d);u(m?se(d):d);return}ie(d);}),t.command("list").description("List recent tasks, newest first").option("--limit <number>","Max tasks to return (1-100, default: 20)",je("--limit")).option("--after <id>","Cursor: return tasks created before this task ID").addOption(f()).action(async r=>{let o=await an(e.api,{...r.limit!==void 0?{limit:r.limit}:{},...r.after!==void 0?{after:r.after}:{}});if(r.json){u({tasks:o});return}E(o,[{header:"",value:a=>q(a.status)},{header:"ID",value:a=>a.id},{header:"Type",value:a=>a.type},{header:"Status",value:a=>a.status},{header:"Cost",value:a=>a.cost??"-"},{header:"Created",value:a=>a.createdAt}],"No tasks found.");}),t};var In=(e,t)=>e.fetch("webhook create","/webhook",yt,{body:t}),xn=(e,t,n)=>e.fetch("webhook update",`/webhook/${t}`,ht,{method:"PUT",body:n}),Tn=(e,t)=>e.call("webhook delete",`/webhook/${t}`,{method:"DELETE"});var Je=["task.completed","task.failed","task.image.generate.completed","task.image.generate.failed","task.image.rate.completed","task.image.rate.failed","task.image.alt.completed","task.image.alt.failed","task.image.review.completed","task.image.review.failed","task.video.generate.completed","task.video.generate.failed"],_r=e=>{if(e===void 0||e.length===0)throw new l("at least one --event is required");if(e.includes("all"))return "all";for(let t of e)if(!Je.includes(t))throw new l(`unknown event "${t}". Valid events: all, ${Je.join(", ")}`);return e},Or=e=>{if(e.enabled===true&&e.disabled===true)throw new l("--enabled and --disabled are mutually exclusive");return e.disabled!==true},Rn=e=>({enabled:Or(e),url:e.url,events:_r(e.event),...e.apiKeyId!==void 0?{apiKeyIds:e.apiKeyId}:{},...e.oauthEvents===true?{oauthEnabled:true}:{}}),En=e=>Array.isArray(e)?e.join(", "):String(e),Cn=e=>e.requiredOption("--url <url>","Destination URL for webhook deliveries").option("-e, --event <name>",`Event to subscribe to (repeatable). Use \`all\` for every event. One of: ${Je.join(", ")}`,I).option("--api-key-id <id>","Only deliver tasks created by this API key (repeatable). Omit to deliver for every key.",I).option("--oauth-events","Also deliver tasks created by OAuth sessions (this CLI, the playground). Off by default.").addOption(f()),Pn=e=>{let t=new Command("webhook").description("Manage registered webhooks"),n=t.command("create").description("Register a webhook. The signing secret is shown once, on success.").option("--disabled","Create the webhook disabled (default: enabled)");Cn(n).action(async o=>{let a=await In(e.api,Rn(o));if(o.json){u(a);return}s(`${g.ok} Webhook ${a.id} created`),s(` URL: ${a.url}`),s(` Enabled: ${a.enabled}`),s(` Events: ${En(a.events)}`),a.oauthEnabled!==void 0&&s(` OAuth: ${a.oauthEnabled}`),s(""),s(` Signing secret: ${a.secret}`),s(" Save this now \u2014 it is shown only once and cannot be retrieved again.");});let r=t.command("update").description("Replace a webhook's configuration. Every field is sent, so pass them all.").argument("<id>","Webhook ID").option("--enabled","Enable the webhook").option("--disabled","Disable the webhook");return Cn(r).action(async(o,a)=>{let d=await xn(e.api,o,Rn(a));if(a.json){u(d);return}s(`${g.ok} Webhook ${d.id} updated`),s(` URL: ${d.url}`),d.enabled!==void 0&&s(` Enabled: ${d.enabled}`),s(` Events: ${En(d.events)}`),d.oauthEnabled!==void 0&&s(` OAuth: ${d.oauthEnabled}`);}),t.command("delete").description("Delete a webhook").argument("<id>","Webhook ID").addOption(j()).addOption(f()).action(async(o,a)=>{if(a.yes!==true)throw new l("refusing to delete without --yes");if(await Tn(e.api,o),a.json){u({deleted:o});return}s(`${g.ok} Deleted webhook ${o}`);}),t};var Nr=100,Dr=`
9
9
  Exit codes:
10
10
  0 success
11
11
  1 error (network, server, or unexpected failure)
12
12
  2 usage error (invalid arguments, flags, or request)
13
- 3 authentication error (missing or invalid credentials)
14
- 4 insufficient credits
13
+ 3 authentication error (missing, invalid, or under-scoped credentials)
14
+ 4 insufficient credits (account balance or API key spending limit)
15
15
  5 blocked by content moderation
16
- 6 rate limited`),e.addCommand(we(t)),e.addCommand(ve(t)),e.addCommand(be(t)),e.addCommand(xe(t)),e.addCommand(Re(t)),e.addCommand(_e(t)),e.addCommand(Pe(t)),e.addCommand(Ie(t)),e.addCommand(je(t)),e.addCommand(ie(t)),Yt(e),e},Yt=t=>{t.configureHelp({helpWidth:100}),t.createHelp=()=>new Ne;for(let e of t.commands)Yt(e);};var Ia=process.env.MYNTH_DEBUG==="1"||process.env.MYNTH_DEBUG==="true",ja=t=>t instanceof Error?t.message:String(t),Vt=t=>{t.exitOverride();for(let e of t.commands)Vt(e);},Na=async()=>{let t=Ht();Vt(t);try{await t.parseAsync(process.argv);}catch(e){let n=e.code;if(n==="commander.helpDisplayed"||n==="commander.version")return;(typeof n!="string"||!n.startsWith("commander."))&&(oe(ja(e)),Ia&&e instanceof Error&&(oe("=== MYNTH_DEBUG cause ==="),oe(JSON.stringify(e.cause??e,null,2)))),process.exitCode=Ke(e);}};await Na();
16
+ 6 rate limited
17
+
18
+ Environment:
19
+ MYNTH_API_KEY API key; takes precedence over stored credentials
20
+ MYNTH_DESTINATION default --destination for image generation
21
+ MYNTH_DEBUG=1 print error causes and stack details to stderr
22
+ XDG_CONFIG_HOME where credentials are stored (default: ~/.config)`,Ke=class extends Help{optionTerm(t){return `(${t.flags.replaceAll("<","").replaceAll(">","")})`}subcommandTerm(t){let n=t.registeredArguments.map(r=>r.required?`${r.name()}${r.variadic?"...":""}`:`[${r.name()}]`).join(" ");return n.length>0?`${t.name()} ${n}`:t.name()}},_n=e=>{e.configureHelp({helpWidth:Nr}),e.createHelp=()=>new Ke,e.exitOverride();for(let t of e.commands)_n(t);},On=()=>{let e=kt(),t=new Command("mynth").description("Official Mynth CLI").version(He).addHelpText("before",({error:n})=>(n?process.stderr.isTTY:process.stdout.isTTY)?`${bt()}
23
+ `:"").addHelpText("after",Dr);for(let n of [Tt(e),Ft(e),Ut(e),Wt(e),en(e),tn(e),gn(e),vn(e),$n(e),Pn(e),ve(e)])t.addCommand(n);return _n(t),t};var Sn=e=>e instanceof Error?e.message:String(e);try{await On().parseAsync(process.argv);}catch(e){let t=e?.code;t==="commander.helpDisplayed"||t==="commander.version"||((typeof t!="string"||!t.startsWith("commander."))&&(T(Sn(e)),G().debug&&e instanceof Error&&(T("=== MYNTH_DEBUG ==="),T(e.stack??e.message),e.cause!==void 0&&T(`cause: ${Sn(e.cause)}`))),process.exitCode=Ye(e));}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mynthio/cli",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "Official Mynth CLI",
5
5
  "keywords": [
6
6
  "ai",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "chalk": "^5.6.2",
37
37
  "commander": "^14.0.3",
38
- "cross-keychain": "^1.1.0",
38
+ "fuzzysort": "^4.0.2",
39
39
  "ora": "^8.2.0",
40
40
  "zod": "^3.25.76"
41
41
  },