@mynthio/cli 0.0.19 → 0.0.21

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 +222 -58
  2. package/dist/bin.js +17 -10
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,128 +1,292 @@
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.
37
+
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).
29
41
 
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:
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
36
49
  ```
37
50
 
38
- ### Image review
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.
39
56
 
40
- Quality review with a multi-model panel (score 1–4, findings, and strengths):
57
+ ## Generating images
41
58
 
42
59
  ```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
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
46
63
  ```
47
64
 
48
- ### Cost and balance
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
49
85
 
50
- Check spend before a batch run:
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
89
+ ```
90
+
91
+ Estimates for `--model auto` are an upper bound. Add `--json` to either command for machine-readable
92
+ output.
93
+
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
+ | `--type` | Only one media type: `image` or `video`. |
103
+ | `--max-price` | Price at or below this, in USD. |
104
+ | `--min-price` | Price at or above this, in USD. |
105
+ | `--4k` | Only models that publish a 4K price. |
106
+ | `--capability` | One generation mode: `txt2img`, `img2img`, `txt2vid`, or `img2vid`. |
51
107
 
52
108
  ```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
109
+ mynth models list -s "gemini flash" # fuzzy: tolerates typos and word order
110
+ mynth models list --capability img2img --max-price 0.03
111
+ mynth models list --type video --json
112
+ mynth models list --org bfl --json
55
113
  ```
56
114
 
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.
115
+ Search is fuzzy, not substring: `sedream` finds Seedream, and results come back ranked by relevance.
116
+ A query that matches nothing prints `No models matched the filters.` and still exits `0`.
117
+
118
+ The catalog carries image and video models. Image models are priced per image, video models per
119
+ second of output, so the `Price` column reports the cheapest rate a model charges and marks the
120
+ per-second ones with `/s`. `--max-price` and `--min-price` compare against that same figure, which
121
+ is why they are most useful alongside `--type`.
60
122
 
61
- ### Tasks
123
+ `--capability` matches the modes a model actually serves, so a model that both takes a prompt and
124
+ accepts images matches `txt2img` and `img2img` alike.
62
125
 
63
- Async workflows: fire a generation with `--async`, do other work, then wait for the result.
126
+ ## Analyzing images
127
+
128
+ Every analysis command takes one URL or one local file, and waits for the result:
129
+
130
+ ```bash
131
+ mynth image rate https://cdn.example.com/product.webp
132
+ mynth image rate ./shot.png -l kids="Safe for children" -l adults="Adults only"
133
+ mynth image alt ./product.webp --json
134
+ mynth image review ./shot.png # score 1-4, findings, strengths
135
+ mynth image review ./shot.png --effort low # faster, cheaper triage panel
136
+ ```
137
+
138
+ Custom rating levels come from repeated `--level value=description`, `--levels-file`, or
139
+ `--levels-json` — one source at a time, 2 to 7 levels.
140
+
141
+ ## Tasks
142
+
143
+ Fire a generation, do other work, then collect the result:
64
144
 
65
145
  ```bash
66
146
  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
147
+ mynth task wait "$task_id" --json # blocks; prints the same shape as a sync generate
148
+ mynth task wait "$task_id" --timeout 600
149
+ mynth task get "$task_id" # fetch once
150
+ mynth task result "$task_id" # just the result payload
151
+ mynth task list --limit 10 # newest first
152
+ mynth task list --after tsk_... # next page
72
153
  ```
73
154
 
74
- `task wait` exits non-zero if the task fails or the timeout is reached.
155
+ `--async --json` also returns a short-lived public access token, so browser or CI code can poll the
156
+ task without your API key.
75
157
 
76
- ### Documentation
158
+ `task wait` exits non-zero when the task fails or the timeout is hit. Transient API failures
159
+ (404, 429, 5xx, dropped connections) are retried while polling — the wait only gives up on them
160
+ after ~40s of consecutive failures, or immediately on an error that cannot self-heal (401, 403).
77
161
 
78
- Fetch one page as Markdown or retrieve the complete documentation index:
162
+ ## API keys
163
+
164
+ `auth login` creates a key for the machine you're on. For an app or a deploy target, create one
165
+ explicitly:
79
166
 
80
167
  ```bash
81
- mynth docs get guides/async-and-polling
82
- mynth docs list
168
+ mynth api-key create my-app # generate scope
169
+ mynth api-key create my-app --json | jq -r .key # capture it for a .env
170
+ mynth api-key list
171
+ mynth api-key delete key_... --yes
83
172
  ```
84
173
 
85
- Add `--json` to any documentation command for machine-readable output:
174
+ The key is printed once and cannot be retrieved again.
175
+
176
+ Keys created from the CLI only get the `generate` scope. That's what an app needs to call the image
177
+ API; `manage` and `keys` have to come from the
178
+ [dashboard](https://mynth.io/dashboard), because the API refuses scope escalation from a CLI
179
+ session. Registering webhooks and destinations for that app is done with _your_ credentials, so the
180
+ app's key doesn't need `manage`.
181
+
182
+ Set a spending limit on app keys in the dashboard — it's the cheapest way to bound a leaked key.
183
+
184
+ ## Destinations
185
+
186
+ Deliver generated images straight to your own storage. Secrets are read from a file or stdin, never
187
+ from the command line, so they stay out of shell history and `ps`.
86
188
 
87
189
  ```bash
88
- mynth docs get reference/webhooks --json
89
- mynth docs list --json
190
+ # Bunny — a single-field secret may be passed bare
191
+ printf 'my-storage-password' | mynth destination create bunny-prod \
192
+ --provider bunny --storage-zone my-zone --region de \
193
+ --path-template 'images/{id}' --url-template 'https://cdn.example.com/{path}' \
194
+ --secret -
195
+
196
+ # S3 or R2 — JSON secret
197
+ mynth destination create s3-prod \
198
+ --provider s3 --bucket my-bucket --region us-east-1 \
199
+ --path-template 'images/{id}' --secret ./s3-secret.json
200
+
201
+ mynth destination test dst_... # verify credentials with a probe upload
202
+ mynth destination list
203
+ mynth destination delete dst_... --yes
204
+ ```
205
+
206
+ `--file <path|->` still accepts a complete JSON body instead of the typed flags.
207
+ Then use it: `mynth image generate -p "..." --destination bunny-prod`.
208
+
209
+ ## Webhooks
210
+
211
+ ```bash
212
+ mynth webhook create --url https://example.com/hooks/mynth -e task.completed -e task.failed
213
+ mynth webhook create --url https://example.com/hooks/mynth -e all --api-key-id key_...
214
+ mynth webhook delete whk_... --yes
90
215
  ```
91
216
 
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.
217
+ The signing secret is printed once, on create, and cannot be retrieved again.
218
+
219
+ By default a webhook only receives tasks created with an **API key** — that matches where webhooks
220
+ are actually consumed, on a server. Pass `--oauth-events` to also receive tasks created by OAuth
221
+ sessions (this CLI, the playground).
222
+
223
+ ## Documentation
94
224
 
95
- Run `mynth --help` for the full command list.
225
+ ```bash
226
+ mynth docs get guides/async-and-polling
227
+ mynth docs list
228
+ mynth docs get reference/webhooks --json
229
+ ```
230
+
231
+ Paths take an optional leading slash and must not include the `.md` suffix. Documentation commands
232
+ need no authentication.
96
233
 
97
234
  ## Exit codes
98
235
 
99
- The CLI uses distinct exit codes so scripts and AI agents can branch without parsing error
100
- messages:
236
+ Distinct exit codes so scripts and agents can branch without parsing error messages:
101
237
 
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 |
238
+ | Code | Meaning |
239
+ | ---- | -------------------------------------------------------------------- |
240
+ | 0 | Success |
241
+ | 1 | Error (network, server, or unexpected failure) |
242
+ | 2 | Usage error (invalid arguments, flags, or request) |
243
+ | 3 | Authentication error (missing, invalid, or under-scoped credentials) |
244
+ | 4 | Insufficient credits (account balance or API key spending limit) |
245
+ | 5 | Blocked by content moderation |
246
+ | 6 | Rate limited |
111
247
 
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.
248
+ `task wait` reports the awaited task's outcome the same way: a moderation block exits 5, any other
249
+ failure exits 1.
250
+
251
+ ## Environment
252
+
253
+ | Variable | Effect |
254
+ | --------------------- | ---------------------------------------------------------- |
255
+ | `MYNTH_API_KEY` | API key; takes precedence over stored credentials |
256
+ | `MYNTH_DESTINATION` | Default `--destination` for image generation |
257
+ | `MYNTH_DEBUG=1` | Print stack traces and error causes to stderr |
258
+ | `MYNTH_NO_KEYCHAIN=1` | Store credentials in a file instead of the system keychain |
259
+ | `MYNTH_API_URL` | Override the API base URL |
260
+ | `MYNTH_DOCS_URL` | Override the documentation base URL |
114
261
 
115
262
  ## Development
116
263
 
117
264
  ```bash
118
265
  cd packages/cli
119
266
  bun install
120
- bun run dev -- --help # run from sources
267
+ bun run dev -- --help # run from source
121
268
  bun run build # bundle to dist/bin.js
122
269
  bun run test
123
270
  bun run typecheck
124
271
  ```
125
272
 
126
- Built with focused TypeScript CLI libraries: [`commander`](https://github.com/tj/commander.js),
273
+ ### Layout
274
+
275
+ ```
276
+ src/
277
+ bin.ts entry point: parse argv, map errors to exit codes
278
+ program.ts command tree and help formatting
279
+ app.ts the config/session/api/docs bundle every command receives
280
+ config.ts environment and build-time constants
281
+ errors.ts error types and the exit-code contract
282
+ api/ one module per API resource, over a shared fetch client
283
+ auth/ credential file, device login, API key minting
284
+ commands/ one module per command; they only orchestrate
285
+ output/ printing, tables, spinners, and shared result renderers
286
+ utils/ parsing, file, download, and concurrency helpers
287
+ ```
288
+
289
+ Commands hold argument parsing and rendering; `api/` holds the wire format; nothing in `api/` knows
290
+ about Commander. Built with [`commander`](https://github.com/tj/commander.js),
127
291
  [`chalk`](https://github.com/chalk/chalk), [`ora`](https://github.com/sindresorhus/ora), and
128
292
  [`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 U 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 er from'ora';import kn from'fuzzysort';var ge="client_01KATK792RR5ZCHMF5YMNN1ZSE",Ye=process.env.MYNTH_WORKOS_API_URL??"https://api.workos.com",He="0.0.21",We=e=>e.replace(/\/+$/,""),Be=e=>e!==void 0&&e.length>0?e:void 0,G=()=>({apiUrl:We(process.env.MYNTH_API_URL??"https://api.mynth.io"),docsUrl:We(process.env.MYNTH_DOCS_URL??"https://docs.mynth.io"),envApiKey:Be(process.env.MYNTH_API_KEY),envDestination:Be(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,i){super(n,i),this.code=t;}},Fn={UNAUTHORIZED:v.auth,INSUFFICIENT_SCOPE:v.auth,VALIDATION_ERROR:v.usage,INSUFFICIENT_BALANCE:v.insufficientCredits,SPENDING_LIMIT_EXCEEDED:v.insufficientCredits,RESTRICTED_CONTENT:v.moderation},Ge=e=>{if(e instanceof l)return v.usage;if(e instanceof $)return v.auth;if(e instanceof k){let n=e.code!==void 0?Fn[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(i=>i.code),...(t?.images??[]).map(i=>i.error?.code)].filter(i=>typeof i=="string");return n.find(i=>i==="RESTRICTED_CONTENT")??n[0]},qe=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:U.green("\u2713"),fail:U.red("\u2717"),pending:U.yellow("\u2026")},q=e=>e==="completed"?g.ok:e==="failed"?g.fail:g.pending,O=(e,t,n="s")=>`${e} ${t}${e===1?"":n}`,Ve=(e,t=2)=>{let n=" ".repeat(t);return e.split(`
5
+ `).map(i=>`${n}${i}`).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)])),Xe=e=>z$1.object({data:e}),Un=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()}),Ze=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()}),Qe=z$1.object({access_token:z$1.string(),refresh_token:z$1.string(),user:Un.optional()}),et=z$1.object({error:z$1.string().optional(),error_description:z$1.string().optional(),message:z$1.string().optional(),code:z$1.string().optional()}),tt=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())})}),Jn=z$1.union([z$1.string(),z$1.number()]).nullable().optional(),nt=z$1.object({id:z$1.string(),name:z$1.string().nullable(),keyPreview:z$1.string(),scopes:z$1.array(z$1.string()),spendingLimit:Jn,spendingLimitPeriod:z$1.string().nullable().optional(),createdAt:z$1.string()}),Kn=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()})]),ot=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:Kn.optional()}).optional()})}),rt=z$1.object({balance:z$1.string(),reserved:z$1.string(),available:z$1.string(),currency:z$1.string()}),zn=z$1.object({type:z$1.string(),kind:z$1.string().optional(),min:z$1.number().optional(),max:z$1.number()}),Wn=z$1.object({inputs:z$1.object({rules:z$1.array(zn),maxTotal:z$1.number().optional()}).optional()}),Bn=z$1.object({perImage:z$1.object({base:z$1.string(),"4k":z$1.string().optional()}),perInput:z$1.string().optional()}),Yn=z$1.object({perSecond:z$1.record(z$1.string(),z$1.string()),audio:z$1.object({perSecond:z$1.string()}).optional()}),it=z$1.object({id:z$1.string(),displayName:z$1.string().nullable(),type:z$1.string(),modes:z$1.record(z$1.string(),Wn),pricing:z$1.union([Bn,Yn]).nullable()}),V=z$1.enum(["pending","completed","failed"]),st=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()}),at=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()}),dt=z$1.object({id:z$1.string(),type:z$1.string(),status:V,result:S.nullable()}),lt=z$1.object({urls:z$1.array(z$1.string())}),ct=z$1.object({taskId:z$1.string(),estimatedCost:z$1.string().optional(),access:z$1.object({publicAccessToken:z$1.string()}).optional()}),pt=z$1.object({estimatedCost:z$1.string(),currency:z$1.string(),estimateKind:z$1.enum(["exact","upper_bound"])}),mt=z$1.object({url:z$1.string(),level:z$1.string()}),ut=z$1.object({url:z$1.string(),alt:z$1.string()}),gt=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()}),Hn=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})]),ft=z$1.object({model:z$1.string().optional(),images:z$1.array(Hn).optional(),magic_prompt:z$1.object({positive:z$1.string(),negative:z$1.string().optional()}).optional()}),F=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()}),yt=z$1.union([z$1.literal("all"),z$1.array(z$1.string())]),ht=z$1.object({id:z$1.string(),enabled:z$1.boolean(),url:z$1.string(),secret:z$1.string(),events:yt,apiKeyIds:z$1.array(z$1.string()).nullish(),oauthEnabled:z$1.boolean().optional(),createdAt:z$1.string().optional()}),wt=z$1.object({id:z$1.string(),enabled:z$1.boolean().optional(),url:z$1.string(),events:yt,apiKeyIds:z$1.array(z$1.string()).nullish(),oauthEnabled:z$1.boolean().optional()});var Gn=e=>{if(e===void 0)return "";let t=new URLSearchParams;for(let[n,i]of Object.entries(e))i!==void 0&&t.set(n,String(i));return t.size>0?`?${t}`:""},qn=e=>e===void 0?{headers:{}}:e instanceof FormData?{body:e,headers:{}}:{body:JSON.stringify(e),headers:{"Content-Type":"application/json"}},Vn=async e=>{try{return await e.text()}catch{return ""}},Xn=async(e,t)=>{let n=await Vn(e),i,o;try{let a=JSON.parse(n);typeof a.code=="string"&&(i=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,...i!==void 0?{code:i}:{}})},X=class{constructor(t,n){this.tokens=n;this.baseUrl=t.apiUrl;}baseUrl;async send(t,n,i={}){let o=await this.attempt(n,i);if(o.ok)return o;throw await Xn(o,t)}async fetch(t,n,i,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=Xe(i).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,i={}){await this.send(t,n,i);}async attempt(t,n){let{body:i,headers:o}=qn(n.body),a=n.auth===false?void 0:n.token??await this.tokens.token();try{return await fetch(`${this.baseUrl}${t}${Gn(n.query)}`,{method:n.method??(n.body!==void 0?"POST":"GET"),headers:{...o,...a!==void 0?{Authorization:`Bearer ${a}`}:{}},...i!==void 0?{body:i}:{}})}catch(d){throw new k(`request to ${t} failed: ${d.message}`,{status:0,cause:d})}}};var Zn=2048,Qn=/^[A-Za-z0-9._~%-]+$/,eo=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 i=n.split("/");if(i.some(o=>!Qn.test(o)||o==="."||o===".."))throw new l("documentation path contains an invalid segment");return i.join("/")},to=e=>e.length>500?`${e.slice(0,500)}\u2026`:e,kt=async(e,t)=>{let n;try{n=await fetch(e);}catch(o){throw new k(`${t} failed: ${o.message}`,{status:0,cause:o})}let i=await n.text().catch(()=>"");if(!n.ok)throw new k(`${t} failed (${n.status}): ${to(i)||"no body"}`,{status:n.status});return i},Z=class{constructor(t){this.docsUrl=t;}async get(t){let n=eo(t),i=n.split("/").map(encodeURIComponent).join("/");return {path:n,content:await kt(`${this.docsUrl}/${i}.md`,`docs fetch for ${n}`)}}list(){return kt(`${this.docsUrl}/llms.txt`,"docs index fetch")}};var po="credentials.json",mo=384,uo=()=>{let e=process.env.XDG_CONFIG_HOME;return join(e!==void 0&&e.length>0?e:join(homedir(),".config"),"mynth")},go=async e=>{try{return await stat(e),true}catch{return false}},Q=class{filePath;constructor(){this.filePath=join(uo(),po);}async get(){if(!await go(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 i=tt.safeParse(n);if(!i.success)throw new y(`${this.filePath} has an unexpected shape; run \`mynth auth login\``,{cause:i.error});return i.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:mo}),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 bt=()=>{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"},fo=(e,t)=>{let n=i=>i!==".";return !n(e)&&!n(t)?" ":U.level===0?n(e)&&n(t)?"\u2588":n(e)?"\u2580":"\u2584":n(t)?n(e)?e===t?U.hex(J[e])("\u2588"):U.hex(J[e]).bgHex(J[t])("\u2580"):U.hex(J[t])("\u2584"):U.hex(J[e])("\u2580")},vt=()=>{let e=[];for(let t=0;t<be.length;t+=2){let n=be[t],i=be[t+1]??"",o="";for(let a=0;a<n.length;a+=1)o+=fo(n[a],i[a]??".");e.push(o.trimEnd());}return e.filter(t=>t.length>0).join(`
7
+ `)};var At=(e,t)=>e.fetch("api key create","/api-key",we,{body:{name:t.name,scopes:t.scopes},token:t.token}),$t=(e,t)=>e.fetch("api key create","/api-key",we,{body:{name:t.name,scopes:t.scopes}}),xt=e=>e.fetch("api key list","/api-key",z$1.array(nt)),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 i=e.map(d=>t.map(m=>m.value(d))),o=t.map((d,m)=>Math.max(d.header.length,...i.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 i)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 Tt=["generate"],wo=e=>{if(e===void 0)return Tt;let t=e.split(",").map(i=>i.trim()).filter(Boolean),n=t.filter(i=>!R.includes(i));if(t.length===0||n.length>0)throw new l(`invalid --scopes: ${n.join(", ")||"empty"}. Valid scopes: ${R.join(", ")}`);return t},Rt=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: ${Tt.join(",")}). Widening beyond \`generate\` requires the dashboard: the API refuses scope escalation from a CLI session.`).addOption(f()).action(async(n,i)=>{let o=await $t(e.api,{name:n,scopes:wo(i.scopes)});if(i.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 i=await xt(e.api);if(n.json){u(i);return}E(i,[{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,i)=>{if(i.yes!==true)throw new l("refusing to delete without --yes");if(await te(e.api,n),i.json){u({deleted:n});return}s(`${g.ok} Revoked API key ${n}`);}),t};var ne=e=>e.fetch("me","/me",ot),Et=e=>e.fetch("balance","/balance",rt);var ko="urn:ietf:params:oauth:grant-type:device_code",Ct=async e=>{try{return await e.json()}catch{return {}}},Pt=async(e,t,n)=>{try{return await fetch(`${Ye}${e}`,{method:"POST",body:t,headers:{Accept:"application/json",...typeof t=="string"?{"Content-Type":"application/json"}:{}}})}catch(i){throw new y(`${n} failed: ${i.message}`,{cause:i})}},_t=async(e,t)=>{let n=et.catch({}).parse(await Ct(e)),i=n.error??n.code??"workos_error";return new _(i,n.error_description??n.message??`${t} failed`)},Ot=async(e,t,n)=>{let i=t.safeParse(await Ct(e));if(!i.success)throw new y(`${n} returned an unexpected response`,{cause:i.error});return i.data},St=async()=>{let e=await Pt("/user_management/authorize/device",new URLSearchParams({client_id:ge}),"device authorization");if(!e.ok)throw await _t(e,"device authorization");return Ot(e,Ze,"device authorization")},jt=async e=>{let t=await Pt("/user_management/authenticate",JSON.stringify({grant_type:ko,client_id:ge,device_code:e}),"device token exchange");if(!t.ok)throw await _t(t,"device token exchange");return Ot(t,Qe,"device token exchange")};var oe=e=>new Promise(t=>setTimeout(t,e)),Nt=async(e,t,n)=>{let i=Array.from({length:e.length}),o=0,a=async()=>{for(;;){let d=o++;if(d>=e.length)return;i[d]=await n(e[d],d);}};return await Promise.all(Array.from({length:Math.min(t,e.length)},a)),i};var Dt=()=>{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")))},Ao=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]}}},Lt=async e=>{try{let{protocol:i}=new URL(e);if(i!=="https:"&&i!=="http:")return false}catch{return false}let{command:t,args:n}=Ao(e);return new Promise(i=>{try{let o=spawn(t,[...n],{detached:true,stdio:"ignore"});o.on("error",()=>i(false)),o.unref(),setTimeout(()=>i(true),150).unref();}catch{i(false);}})};var xo="https://mynth.io/dashboard",Mt=e=>`${xo}/keys/${e}`,Io=5e3,To=5,Ro=()=>`mynth-cli (${hostname()})`,Eo=async(e,t,n)=>{let i=t;for(;;){if(Date.now()>=n)throw new y("device code expired before approval");try{return await jt(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"&&(i+=Io),await oe(i);}}},Co=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(I=>I.trim()).filter(Boolean):[...R],i=n.filter(I=>!R.includes(I));if(n.length===0||i.length>0)throw new $(`invalid --scopes: ${i.join(", ")||"empty"}. Valid scopes: ${R.join(", ")}`);let o=await St(),a=o.verification_uri_complete??o.verification_uri,d=o.verification_uri_complete!==void 0,m=t.browser!==false&&Dt()?await Lt(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 Eo(o.device_code,(o.interval??To)*1e3,Date.now()+o.expires_in*1e3),b=Ro(),A=await At(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: ${Mt(A.apiKey.id)}`);}),Po=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,i=false;if(n!==void 0)try{await te(e.api,n),i=true;}catch(o){T(`Warning: could not revoke API key ${n}: ${o.message}`),T(`Revoke it manually at ${Mt(n)}`);}await e.session.clear(),s(`${g.ok} Local credentials cleared${i?" 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.");}),_o=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:i}=n.credentials;s("Authenticated via stored API key"),i!==void 0&&s(` key: ${i}`),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 i=await ne(e.api);if(t.json){u({source:n.kind,...i});return}s(n.kind==="env"?"env:MYNTH_API_KEY":"api-key"),s(` user: ${i.userId}`),s(` method: ${i.auth.method}`);let o=i.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(Co(e)).addCommand(Po(e)).addCommand(_o(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 Et(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 Kt={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".webp":"image/webp"},Ae=Object.keys(Kt),$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}`)}},zt=async e=>{let t=await z(e);try{return JSON.parse(t)}catch(n){throw new l(`invalid JSON in ${e}: ${n.message}`)}},Wt=async e=>{let t=extname(e).toLowerCase(),n=Kt[t];if(n===void 0)throw new l(`unsupported image extension "${t}" for ${e} (allowed: ${Ae.join(", ")})`);let i;try{i=await readFile(e);}catch(o){throw new l(`could not read ${e}: ${o.message}`)}return new File([new Uint8Array(i)],basename(e),{type:n})};var Bt=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 i=>{let o=(i==="-"?await $e():i).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 Yt=e=>e.fetch("destination list","/destinations",z$1.array(F)),Ht=(e,t)=>e.fetch("destination fetch",`/destinations/${t}`,F),Gt=(e,t)=>e.fetch("destination create","/destinations",F,{body:t}),qt=(e,t,n)=>e.fetch("destination update",`/destinations/${t}`,F,{method:"PUT",body:n}),Vt=(e,t,n)=>e.call("destination test",`/destinations/${t}/test`,{body:{path:n}}),Xt=(e,t)=>e.call("destination delete",`/destinations/${t}`,{method:"DELETE"});var Ie=/^[a-z0-9-]+$/,Te=64,Lo=["bunny","r2","s3"],Ee=["de","uk","ny","la","sg","se","br","jh","syd"],Mo=["default","eu","fedramp"],Fo=(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}:{},Uo=(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)}}},Jo=async(e,t)=>{let n=(await z(e)).trim();if(n.startsWith("{"))try{return JSON.parse(n)}catch(i){throw new l(`invalid JSON secret from ${e}: ${i.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}},Ko=e=>({path_template:Fo(e.pathTemplate,"--path-template"),...B("url_template",e.urlTemplate)}),Qt=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 zt(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 i=e.provider;if(i===void 0)throw new l("--provider or --file is required");let o=e.secret!==void 0?await Jo(e.secret,i):void 0;if(o===void 0&&n)throw new l("--secret <path|-> is required (use `-` to read the secret from stdin)");return {...t,provider:Uo(e,i),config:Ko(e),...o!==void 0?{secret:o}:{}}},en=e=>e.addOption(new Option("--provider <id>","Storage provider. Required unless --file is used.").choices([...Lo])).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([...Mo])).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}`);},tn=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 Yt(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, ${Ie.source}. Immutable.`);en(n).action(async(o,a)=>{if(o.length>Te||!Ie.test(o))throw new l(`invalid destination name "${o}": expected 1-${Te} chars matching ${Ie.source}`);let d=await Gt(e.api,await Qt(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 i=t.command("update").description("Replace a destination's provider and config. The slug is immutable.").argument("<id>","Destination ID");return en(i).action(async(o,a)=>{let d=await Qt(a,{},false);delete d.name;let m=await qt(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 Vt(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 Xt(e.api,o),a.json){u({deleted:o});return}s(`${g.ok} Deleted destination ${o}`);}),t};var nn=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,i)=>{let o=await e.docs.get(n);if(i.json){u(o);return}s(o.content);}),t.command("list").description("Fetch the complete documentation index").addOption(f()).action(async n=>{let i=await e.docs.list();if(n.json){u({content:i});return}s(i);}),t};var Wo={4:"Production-ready",3:"Usable, with fixes",2:"Not fit for purpose",1:"Discard"},Pe=e=>e.type==="image.generate",re=e=>{let t=ft.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}`);}},Bo=e=>{let t=re(e.result),n=t.images,i=n.filter(o=>o.status==="success").length;if(s(`${g.ok} Generated ${i}/${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(Ve(JSON.stringify(e.result,null,2))));},ie=e=>{if(Pe(e)&&e.status==="completed"){Bo(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}:{}}},on=e=>{if(s(`${g.ok} Reviewed (task ${e.taskId})`),s(` Score: ${e.score}/4 \u2014 ${Wo[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 x=(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},rn=(e,t)=>{let n;try{n=JSON.parse(e);}catch(i){throw new l(`invalid JSON in ${t}: ${i.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 Y=10,D=async(e,t)=>{if(t.length===0)throw new l("no files to upload");if(t.length>Y)throw new l(`too many files: ${t.length} (max ${Y})`);let n=new FormData;for(let o of await Promise.all(t.map(Wt)))n.append("images",o);let{urls:i}=await e.fetch("upload","/image/upload",lt,{body:n});if(i.length!==t.length)throw new l(`upload returned ${i.length} URLs for ${t.length} files`);return t.map((o,a)=>({path:o,url:i[a]}))},H=(e,t,n)=>e.fetch(`image ${t}`,`/image/${t}`,ct,{body:n}),sn=(e,t)=>e.fetch("estimate","/image/generate/estimate",pt,{body:t});var Ne=1800*1e3,Yo=12e3,Ho=2500,Go=5e3,qo=500,Vo=new Set([0,404,408,429]),Xo=e=>e instanceof k&&(Vo.has(e.status)||e.status>=500),Zo=10,De=(e,t)=>e.fetch("task fetch",`/tasks/${t}`,st),dn=(e,t={})=>e.fetch("task list","/tasks",z$1.array(at),{query:t}),Qo=(e,t)=>e.fetch("task status",`/tasks/${t}/status`,z$1.object({status:V})).then(n=>n.status),ln=(e,t)=>e.fetch("task result",`/tasks/${t}/result`,dt),L=async(e,t,n=Ne)=>{let i=Date.now(),o=0;for(;;){try{let m=await Qo(e,t);if(o=0,m!=="pending")return await De(e,t)}catch(m){if(!Xo(m)||++o>Zo)throw m}let a=Date.now()-i;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<Yo?Ho:Go;await oe(d+Math.floor(Math.random()*qo));}};var tr=["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"],nr=2800,or=e=>{let t=e.slice();for(let n=t.length-1;n>0;n--){let i=Math.floor(Math.random()*(n+1));[t[n],t[i]]=[t[i],t[n]];}return t},M=async(e,t={})=>{if(process.stderr.isTTY!==true)return e;let n=or(t.messages??tr),i=0,o=er({text:n[0]??"Working",stream:process.stderr}).start(),a=setInterval(()=>{i++,o.text=n[i%n.length]??"Working";},nr);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}},rr=z$1.array(z$1.object({value:z$1.string(),description:z$1.string()})),ir=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},cn=(e,t)=>{let n;try{n=JSON.parse(e);}catch(o){throw new l(`invalid JSON in ${t}: ${o.message}`)}let i=rr.safeParse(n);if(!i.success)throw new l(`invalid levels in ${t}: expected an array of { value, description }`);return i.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 i=t.length>0?t.map(ir):e.levelsFile!==void 0?cn(await z(e.levelsFile),e.levelsFile):cn(e.levelsJson??"[]","--levels-json");if(i.length<le||i.length>ce)throw new l(`levels must have between ${le} and ${ce} items (got ${i.length})`);let o=new Set;for(let a of i){if(o.has(a.value))throw new l(`duplicate level value: "${a.value}"`);o.add(a.value);}return i},sr=(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 H(e.api,t.endpoint,t.body),i=L(e.api,n.taskId),o=t.quiet?await i:await M(i);if(o.status!=="completed")throw sr(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 dr=["low","high"],Me="Image URL (http/https), or a local image file to upload first",lr=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"`,x).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 }."),cr=e=>{let t=new Command("rate").description("Classify an image against the default sfw/nsfw levels, or custom ones").argument("<image>",Me).addOption(f());return lr(t).action(async(n,i)=>{let o=await me(i),{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:mt,quiet:i.json===true});if(i.json){u({taskId:m,cost:w,...b});return}N(d),s(`${g.ok} Rated (task ${m})`),s(` ${b.level} ${b.url}`);}),t},pr=e=>new Command("alt").description("Generate accessibility alt text for an image").argument("<image>",Me).addOption(f()).action(async(t,n)=>{let{url:i,uploads:o}=await pe(e,t),{taskId:a,cost:d,result:m}=await ue(e,{endpoint:"alt",body:{url:i},schema:ut,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}`);}),mr=e=>new Command("review").description("Review image quality with a multi-model panel (score, findings, strengths)").argument("<image>",Me).addOption(new Option("--effort <level>",'"high" (default) runs five strong vision models; "low" runs three smaller ones for faster, cheaper triage').choices([...dr])).addOption(f()).action(async(t,n)=>{let{url:i,uploads:o}=await pe(e,t),{taskId:a,cost:d,result:m}=await ue(e,{endpoint:"review",body:{url:i,...n.effort!==void 0?{effort:n.effort}:{}},schema:gt,quiet:n.json===true});if(n.json){u({taskId:a,cost:d,...m});return}N(o),on({taskId:a,cost:d,...m,findings:m.findings??[],strengths:m.strengths??[]});}),pn=e=>[cr(e),pr(e),mr(e)];var hr=4,wr=(e,t,n)=>{try{let i=new URL(e).pathname.split("/").filter(Boolean).pop();if(i!==void 0&&i.length>0)return decodeURIComponent(i)}catch{}return `${t}-${n}`},mn=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 Nt(e.urls,hr,async(n,i)=>{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,wr(n,e.fallbackPrefix,i));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 Fe=20,Ar=["png","jpg","webp"],Ue=["auto","person","garment","pose","source","reference"],$r="https://dry-run.mynth.io/input",xr=e=>{let t=e.indexOf(":"),n,i=e;if(t>0&&!/^https?:/i.test(e)){let o=e.slice(0,t);if(!Ue.includes(o))throw new l(`invalid --input role "${o}". Expected one of: ${Ue.join(", ")}`);n=o,i=e.slice(t+1);}if(i.length===0)throw new l(`invalid --input "${e}": missing path or URL`);return {...n!==void 0?{role:n}:{},value:i,isLocalFile:!ae(i)}},Ir=e=>{let t=e.webhookUrl??[],n=e.dashboardWebhooks===false;if(!(t.length===0&&!n))return {...n?{dashboard:false}:{},...t.length>0?{custom:t.map(i=>({url:i}))}:{}}},Tr=async(e,t,n,i)=>{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=Ir(t),w=n.map(b=>({type:"image",...b.role!==void 0?{as:b.role}:{},source:{type:"url",url:b.isLocalFile?i.get(b.value)??$r: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:rn(t.metadata,"--metadata")}:{}}},Rr=(e,t)=>{let n=re(e.result).images.map(_e).filter(i=>i!==void 0);return n.length===0?Promise.resolve([]):mn({urls:n,directory:t,fallbackPrefix:e.id})},un=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([...Ar])).option("-i, --input <value>",`Input image as "[role:]path-or-url" (repeatable, up to ${Fe}). Roles: ${Ue.join(", ")}. Examples: -i ./img.jpg, -i source:https://example.com/a.png`,x).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)',x).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)",x).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 i=n.input??[];if(i.length>Fe)throw new l(`too many --input values: ${i.length} (max ${Fe})`);let o=i.map(xr),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 Tr(e,n,o,new Map(d.map(h=>[h.path,h.url])));if(n.dryRun===true){let h=await sn(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 H(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 H(e.api,"generate",m),b=L(e.api,w.taskId),A=n.json?await b:await M(b),C=n.outputDir!==void 0?resolve(n.outputDir):void 0,I=C!==void 0?await Rr(A,C):[];if(n.json){let h=n.detailed===true?A:se(A);u(C!==void 0?{...h,downloadedFiles:I}:h);return}if(N(d),ie(A),I.length>0){s(""),s(`${g.ok} Saved ${O(I.length,"image")} to ${C}`);for(let h of I)s(` ${h}`);}}),t};var gn=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>Y)throw new l(`too many files: ${t.length} (max ${Y})`);let i=await D(e.api,t);if(n.json){u({images:i});return}s(`${g.ok} Uploaded ${O(i.length,"image")}`);for(let{path:o,url:a}of i)s(` ${o}`),s(` -> ${a}`);});var fn=e=>{let t=new Command("image").description("Generate, upload, and analyze images");t.addCommand(un(e)),t.addCommand(gn(e));for(let n of pn(e))t.addCommand(n);return t};var yn=e=>e.fetch("models list","/models",z$1.array(it),{auth:false});var bn={txt2img:"txt->img",img2img:"img->img",txt2vid:"txt->vid",img2vid:"img->vid"},vn=e=>e.id.split("/")[0],Je=e=>"perImage"in e,An=e=>e.type==="video"?"/s":"",Sr=e=>{let t=e.pricing;return t===null?[]:Je(t)?[t.perImage.base]:Object.values(t.perSecond)},$n=e=>{let t;for(let n of Sr(e)){let i=Number.parseFloat(n);Number.isFinite(i)&&(t===void 0||i<t.value)&&(t={raw:n,value:i});}return t},hn=e=>$n(e)?.value,jr=e=>{let t=$n(e);return t===void 0?"-":`${t.raw}${An(e)}`},xn=e=>{let t=e.pricing;if(t!==null)return Je(t)?t.perImage["4k"]:t.perSecond["4k"]},Nr=e=>{let t=xn(e);return t===void 0?"-":`${t}${An(e)}`},Dr=e=>{let t=e.pricing;if(!(t===null||!Je(t)))return t.perInput},wn=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},In=.3,Lr=(e,t)=>kn.go(t,e,{keys:["id","displayName"],threshold:In}).map(n=>n.obj),Mr=(e,t)=>{let n=[...new Set(e.map(vn))];return n.includes(t)?new Set([t]):new Set(kn.go(t,n,{threshold:In}).map(i=>i.target))},Fr=(e,t)=>{let n=e;if(t.org!==void 0){let i=Mr(n,t.org);n=n.filter(o=>i.has(vn(o)));}if(t.type!==void 0){let i=t.type;n=n.filter(o=>o.type===i);}if(t["4k"]&&(n=n.filter(i=>xn(i)!==void 0)),t.capability!==void 0){let i=bn[t.capability];n=n.filter(o=>i in o.modes);}if(t.maxPrice!==void 0){let i=t.maxPrice;n=n.filter(o=>(hn(o)??1/0)<=i);}if(t.minPrice!==void 0){let i=t.minPrice;n=n.filter(o=>(hn(o)??-1/0)>=i);}return t.search!==void 0&&(n=Lr(n,t.search)),n},Tn=e=>{let t=new Command("models").description("Browse the public Mynth model catalog");return t.command("list").description("List available generation models and their 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("--type <type>","Only models of this media type (image, video)").option("--max-price <usd>","Only models at or below this price (per image, or per second for video)",wn("--max-price")).option("--min-price <usd>","Only models at or above this price (per image, or per second for video)",wn("--min-price")).option("--4k","Only models with 4K pricing").addOption(new Option("--capability <capability>","Only models serving this generation mode").choices(Object.keys(bn))).addOption(f()).action(async n=>{let i=Fr(await yn(e.api),n);if(n.json){u(i);return}E(i,[{header:"ID",value:o=>o.id},{header:"Name",value:o=>o.displayName??"-"},{header:"Type",value:o=>o.type},{header:"Modes",value:o=>Object.keys(o.modes).join(",")||"-"},{header:"Price",value:o=>jr(o)},{header:"4K",value:o=>Nr(o)},{header:"Input fee",value:o=>Dr(o)??"-"}],"No models matched the filters.");}),t};var Rn=Ne/1e3,Jr=e=>e.option("--detailed","Include the full task record instead of a compact summary"),En=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(i,o)=>{let a=await De(e.api,i);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 i=>{u((await ln(e.api,i)).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: ${Rn})`,je("--timeout")).addOption(f());return Jr(n).action(async(i,o)=>{let a=L(e.api,i,(o.timeout??Rn)*1e3),d=o.json?await a:await M(a);if(d.status==="failed"&&(process.exitCode=qe(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 i=>{let o=await dn(e.api,{...i.limit!==void 0?{limit:i.limit}:{},...i.after!==void 0?{after:i.after}:{}});if(i.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 Cn=(e,t)=>e.fetch("webhook create","/webhook",ht,{body:t}),Pn=(e,t,n)=>e.fetch("webhook update",`/webhook/${t}`,wt,{method:"PUT",body:n}),_n=(e,t)=>e.call("webhook delete",`/webhook/${t}`,{method:"DELETE"});var Ke=["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"],zr=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(!Ke.includes(t))throw new l(`unknown event "${t}". Valid events: all, ${Ke.join(", ")}`);return e},Wr=e=>{if(e.enabled===true&&e.disabled===true)throw new l("--enabled and --disabled are mutually exclusive");return e.disabled!==true},On=e=>({enabled:Wr(e),url:e.url,events:zr(e.event),...e.apiKeyId!==void 0?{apiKeyIds:e.apiKeyId}:{},...e.oauthEvents===true?{oauthEnabled:true}:{}}),Sn=e=>Array.isArray(e)?e.join(", "):String(e),jn=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: ${Ke.join(", ")}`,x).option("--api-key-id <id>","Only deliver tasks created by this API key (repeatable). Omit to deliver for every key.",x).option("--oauth-events","Also deliver tasks created by OAuth sessions (this CLI, the playground). Off by default.").addOption(f()),Nn=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)");jn(n).action(async o=>{let a=await Cn(e.api,On(o));if(o.json){u(a);return}s(`${g.ok} Webhook ${a.id} created`),s(` URL: ${a.url}`),s(` Enabled: ${a.enabled}`),s(` Events: ${Sn(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 i=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 jn(i).action(async(o,a)=>{let d=await Pn(e.api,o,On(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: ${Sn(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 _n(e.api,o),a.json){u({deleted:o});return}s(`${g.ok} Deleted webhook ${o}`);}),t};var Hr=100,Gr=`
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)`,ze=class extends Help{optionTerm(t){return `(${t.flags.replaceAll("<","").replaceAll(">","")})`}subcommandTerm(t){let n=t.registeredArguments.map(i=>i.required?`${i.name()}${i.variadic?"...":""}`:`[${i.name()}]`).join(" ");return n.length>0?`${t.name()} ${n}`:t.name()}},Dn=e=>{e.configureHelp({helpWidth:Hr}),e.createHelp=()=>new ze,e.exitOverride();for(let t of e.commands)Dn(t);},Ln=()=>{let e=bt(),t=new Command("mynth").description("Official Mynth CLI").version(He).addHelpText("before",({error:n})=>(n?process.stderr.isTTY:process.stdout.isTTY)?`${vt()}
23
+ `:"").addHelpText("after",Gr);for(let n of [Rt(e),Ft(e),Ut(e),Bt(e),tn(e),nn(e),fn(e),Tn(e),En(e),Nn(e),ve(e)])t.addCommand(n);return Dn(t),t};var Mn=e=>e instanceof Error?e.message:String(e);try{await Ln().parseAsync(process.argv);}catch(e){let t=e?.code;t==="commander.helpDisplayed"||t==="commander.version"||((typeof t!="string"||!t.startsWith("commander."))&&(T(Mn(e)),G().debug&&e instanceof Error&&(T("=== MYNTH_DEBUG ==="),T(e.stack??e.message),e.cause!==void 0&&T(`cause: ${Mn(e.cause)}`))),process.exitCode=Ge(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.21",
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
  },