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