@meshy-ai/cli 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.env.example +1 -1
  2. package/README.md +69 -581
  3. package/dist/cmd/auth.d.ts.map +1 -1
  4. package/dist/cmd/auth.js +14 -12
  5. package/dist/cmd/auth.js.map +1 -1
  6. package/dist/cmd/download.d.ts.map +1 -1
  7. package/dist/cmd/download.js +3 -2
  8. package/dist/cmd/download.js.map +1 -1
  9. package/dist/cmd/make.d.ts.map +1 -1
  10. package/dist/cmd/make.js +26 -10
  11. package/dist/cmd/make.js.map +1 -1
  12. package/dist/cmd/multi-image-to-3d.d.ts +10 -3
  13. package/dist/cmd/multi-image-to-3d.d.ts.map +1 -1
  14. package/dist/cmd/multi-image-to-3d.js +17 -6
  15. package/dist/cmd/multi-image-to-3d.js.map +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +29 -8
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/color.d.ts +1 -1
  20. package/dist/internal/color.d.ts.map +1 -1
  21. package/dist/internal/color.js +13 -1
  22. package/dist/internal/color.js.map +1 -1
  23. package/dist/internal/context.d.ts +5 -0
  24. package/dist/internal/context.d.ts.map +1 -1
  25. package/dist/internal/context.js +5 -0
  26. package/dist/internal/context.js.map +1 -1
  27. package/dist/internal/errors.d.ts.map +1 -1
  28. package/dist/internal/errors.js +8 -3
  29. package/dist/internal/errors.js.map +1 -1
  30. package/dist/internal/logger.d.ts.map +1 -1
  31. package/dist/internal/logger.js +2 -0
  32. package/dist/internal/logger.js.map +1 -1
  33. package/dist/internal/output.d.ts +7 -0
  34. package/dist/internal/output.d.ts.map +1 -1
  35. package/dist/internal/output.js +23 -2
  36. package/dist/internal/output.js.map +1 -1
  37. package/dist/internal/progress.d.ts +62 -0
  38. package/dist/internal/progress.d.ts.map +1 -0
  39. package/dist/internal/progress.js +198 -0
  40. package/dist/internal/progress.js.map +1 -0
  41. package/dist/internal/runtime.d.ts +13 -0
  42. package/dist/internal/runtime.d.ts.map +1 -1
  43. package/dist/internal/runtime.js +17 -2
  44. package/dist/internal/runtime.js.map +1 -1
  45. package/dist/internal/task-command.d.ts +0 -1
  46. package/dist/internal/task-command.d.ts.map +1 -1
  47. package/dist/internal/task-command.js +40 -10
  48. package/dist/internal/task-command.js.map +1 -1
  49. package/dist/internal/update-notifier.d.ts +2 -0
  50. package/dist/internal/update-notifier.d.ts.map +1 -1
  51. package/dist/internal/update-notifier.js +1 -1
  52. package/dist/internal/update-notifier.js.map +1 -1
  53. package/dist/internal/views.d.ts +57 -0
  54. package/dist/internal/views.d.ts.map +1 -0
  55. package/dist/internal/views.js +570 -0
  56. package/dist/internal/views.js.map +1 -0
  57. package/dist/root.d.ts.map +1 -1
  58. package/dist/root.js +3 -0
  59. package/dist/root.js.map +1 -1
  60. package/package.json +1 -1
  61. package/skills/meshy-cli/SKILL.md +12 -5
package/README.md CHANGED
@@ -1,616 +1,104 @@
1
1
  # meshy-cli
2
2
 
3
- A small, well-structured command-line interface for the [Meshy AI API](https://docs.meshy.ai/) — text-to-3D, image-to-3D (standard and smart-topology), text-to-motion, remesh, convert, resize, UV unwrap, rigging, animation, retexture, 2D image generation, multi-color print output, Creative Lab products, the public animation catalog, Enterprise showcases, and the `balance` endpoint — plus the local helpers a 3D-printing or agent workflow needs (selective downloads, project folders, face-count checks, OBJ print preparation, slicer launch, environment diagnosis). Built for humans and AI agents.
4
-
5
- Two layers. `meshy make` chains the documented flows so that one command
6
- produces one model. Underneath, a per-endpoint command for every resource
7
- shares a uniform `create / get / list / wait / stream / delete` verb surface, with a raw
8
- `api` passthrough for endpoints the CLI doesn't model yet and a `skills/`
9
- directory of agent-facing documentation.
10
-
11
- The flag surface is deliberately curated rather than a 1:1 mirror of the API: deprecated parameters (`symmetry_mode`, `hd_texture`, `is_a_t_pose`) are not exposed, geometry knobs live on `remesh` and sizing on `resize` instead of every generation command, and defaults are pinned to game-ready values (PBR maps on, 4k textures — same credit cost as the bare defaults). Everything the API accepts remains reachable through `--data`.
3
+ The command line for the [Meshy AI API](https://docs.meshy.ai/): text-to-3D,
4
+ image-to-3D, text-to-motion, remesh, convert, resize, UV unwrap, rigging,
5
+ animation, retexture, 2D images, multi-color print, Creative Lab products, plus
6
+ the local helpers a 3D-printing or agent workflow needs. Built for people and
7
+ AI agents: a terminal gets a readable view, a pipe or an agent gets JSON.
12
8
 
13
9
  ## Install
14
10
 
15
- Requires Node 22.12+ (the active LTS line). No Python, no other runtime.
11
+ Requires Node 22.12+. No Python, no other runtime.
16
12
 
17
13
  ```bash
18
- npm i -g meshy-cli # installs `meshy-cli` and `meshy`
19
- meshy --help
20
- meshy doctor # local diagnosis: versions, credential sources, base URLs (no network)
14
+ npm i -g meshy-cli # installs `meshy` and `meshy-cli`
15
+ meshy doctor # checks the environment, no network
21
16
  ```
22
17
 
23
- The same build is also published under the scoped alias
24
- [`@meshy-ai/cli`](https://www.npmjs.com/package/@meshy-ai/cli)
25
- (`npm i -g @meshy-ai/cli`) — identical contents, pick whichever name you
26
- remember.
18
+ The same build is published as [`@meshy-ai/cli`](https://www.npmjs.com/package/@meshy-ai/cli).
19
+ **Install one, not both**: they share the `meshy` binary and npm refuses the
20
+ second with `EEXIST` (`npm uninstall -g @meshy-ai/cli && npm i -g meshy-cli` to
21
+ switch). If `meshy --version` reports 0.1.x on Node 22, reinstall: 0.2.0–0.3.1
22
+ declared `node >=24` by mistake, and npm silently picked 0.1.3 instead.
27
23
 
28
- **Install one, not both.** The two packages declare the same `meshy` and
29
- `meshy-cli` binaries, and npm refuses to relink a binary owned by the other
30
- package, so installing the second one fails with `EEXIST: file already exists`.
31
- To switch, uninstall the one you have first:
24
+ ## Log in
32
25
 
33
26
  ```bash
34
- npm uninstall -g @meshy-ai/cli && npm i -g meshy-cli
27
+ meshy auth login # browser sign-in, stored for you
28
+ meshy auth login --with-key msy_your_key_here # or paste an API key
29
+ meshy auth status
35
30
  ```
36
31
 
37
- Versions 0.2.0–0.3.1 declared `engines.node: >=24` by mistake. npm silently
38
- resolves an install to the newest version whose `engines` your runtime
39
- satisfies, so `npm i -g meshy-cli` on Node 22 quietly installed **0.1.3** with
40
- no warning. If `meshy --version` reports 0.1.x, that is why — reinstall now
41
- that the floor is correct.
42
-
43
- ### Development
44
-
45
- ```bash
46
- # straight from git (`prepare` builds dist for you)
47
- npm i -g git+https://github.com/meshy-dev/meshy-cli.git
48
-
49
- # or clone for local development
50
- pnpm install
51
- pnpm build
52
- node dist/index.js --help
53
- pnpm link --global # exposes `meshy` / `meshy-cli` on $PATH
54
- pnpm typecheck && pnpm test # `pnpm test` rebuilds dist first (subprocess tests run it)
55
- ```
32
+ In CI, set `MESHY_API_KEY` instead; it wins over a stored login. Get a key at
33
+ <https://www.meshy.ai/developers/keys>.
56
34
 
57
- ## Auth
35
+ ## Quick start
58
36
 
59
- Log in once and the credential is stored for you:
37
+ One command, one model. `make` chains the documented flow — a prompt runs
38
+ text-to-3D preview then refine; an image runs one textured image-to-3D task:
60
39
 
61
40
  ```bash
62
- meshy auth login # opens the browser (OAuth, loopback + PKCE)
63
- meshy auth login --with-key msy_your_key_here # paste an existing API key instead
64
- meshy auth status # what's in effect, and does it work
41
+ meshy make "a lovely baby husky"
42
+ meshy make ./cat.png -o out/cat/ # -o downloads the result
43
+ meshy make "a red sports car" --dry-run # steps and credit estimate, no spend
65
44
  ```
66
45
 
67
- The default `auth login` opens `https://www.meshy.ai/oauth/authorize` in your
68
- browser, starts a loopback server on port 8765 (override with `--port`), and
69
- waits for the callback. The authorize URL is always printed to **stderr** so you
70
- can copy-paste it if the browser doesn't open automatically.
46
+ ```text
47
+ ✓ [1/2] text-to-3d preview (geometry) 01a0c94c-… 1m 27s
48
+ ✓ [2/2] text-to-3d refine (textures) 01a0c94d-… 5m 36s
71
49
 
72
- In headless or agent contexts where a browser is not available, use
73
- `--with-key` or set `MESHY_API_KEY` instead. Set `MESHY_CLI_NO_BROWSER=1` to
74
- suppress the browser-open attempt (the URL is still printed to stderr).
50
+ ✓ SUCCEEDED text-to-3d-refine
51
+ Task 01a0c94d-eaba-710e-b5b7-da5b2f620c95
52
+ Took 7m 03s
53
+ Credits ~30 (estimate for the whole chain)
54
+ Assets glb, fbx · textures: base_color, metallic, normal, roughness · thumbnail
75
55
 
76
- Or keep it in the environment — unchanged, and still the right choice for CI:
77
-
78
- ```bash
79
- export MESHY_API_KEY=msy_your_key_here
80
- meshy-cli --api-key msy_... balance # or per-call
81
- meshy balance --api-key-file ./keys.env # dotenv-style file; only MESHY_API_KEY is read
56
+ tip: download it now: meshy download --resource text-to-3d --task-id 01a0c94d-… --all --output-dir ./a-lovely-baby-husky
82
57
  ```
83
58
 
84
- Get a key at <https://www.meshy.ai/settings/api>.
85
-
86
- **Resolution order:** `--api-key` › `MESHY_API_KEY` › `--api-key-file` › the active stored profile.
87
- The environment variable stays ahead of the stored credential on purpose, so a
88
- CI runner is never overridden by whatever a developer once logged into on that
89
- machine. With none of the four, commands exit `3` and print the command that
90
- fixes it. An empty or placeholder `--api-key` / `MESHY_API_KEY` counts as unset.
91
-
92
- **`--api-key-file`** reads exactly one variable, `MESHY_API_KEY`, from a
93
- dotenv-style file (`export` prefix, quotes and `#` comments accepted; nothing is
94
- expanded or executed, other keys are ignored). A file you name but that is
95
- missing, malformed or key-less is an error — never a silent fall-through to
96
- another account. There is no automatic `.env` discovery. The flag is not called
97
- `--env-file` because Node.js itself intercepts that name anywhere in argv and
98
- loads the whole file into the environment before the CLI starts.
99
-
100
- **Where it lives:** `~/.config/meshy/credentials.json`, mode `0600`, on every
101
- platform (`MESHY_CONFIG_DIR` or `MESHY_CREDENTIALS_PATH` move it). Writes go
102
- through a cross-process lock and a temp-file rename, because several agents
103
- driving this CLI at once is the normal case. A non-production `--base-url-v1`
104
- reads and writes `credentials.dev.json` instead, so staging cannot clobber a
105
- production login. Stored profiles are only ever sent to the v1/v2 origins they
106
- were resolved for (and to a Creative Lab base on the same origin).
107
-
108
- **OAuth token refresh:** when the stored OAuth access token is within 60 seconds
109
- of expiry (or already expired), the CLI silently refreshes it using the stored
110
- refresh token before running the command. A refresh failure with an unexpired
111
- token is swallowed (the existing token is used); a failure with an expired token
112
- exits `3` with a hint to run `meshy auth login`.
113
-
114
- **Profiles:** `auth login --profile work`, `auth list`, `auth use work`,
115
- `auth logout [--all]`.
116
-
117
- ## One model, one command
118
-
119
- `make` chains the documented flows so a caller who wants a model does not have
120
- to pick an endpoint and carry task ids between steps:
59
+ Every Meshy endpoint is also a command of its own, with the same verbs:
121
60
 
122
61
  ```bash
123
- meshy make "a red sports car" -o car.glb # text-to-3d preview → refine
124
- meshy make ./cat.png -o out/cat/ # image-to-3d, textured
62
+ meshy text-to-3d create --mode preview --prompt "a red sports car"
63
+ meshy image-to-3d create --image-url ./cat.png --async # returns the task id at once
64
+ meshy image-to-3d wait <task-id> -o out/cat/
65
+ meshy text-to-3d list
66
+ meshy download --resource text-to-3d --task-id <task-id> --list
67
+ meshy balance
68
+ meshy resources # every command
125
69
  ```
126
70
 
127
- The input decides the chain: a prompt runs the two-stage text flow, an image
128
- runs the single textured `image-to-3d` task. There is no third judgement — no
129
- route picked by inspecting the input, no image step inserted ahead of a prompt,
130
- no pause between stages. Those are opinions, and a CLI that acts on its own
131
- opinions spends someone else's credits. Compose anything else from the resource
132
- commands below.
133
-
134
- ```bash
135
- meshy make "a red sports car" --dry-run # the steps and the estimate, no spend, no network
136
- meshy make "a red sports car" --max-credits 25 # refuse to start when over budget
137
- meshy make "a red sports car" --async # submit step 1 (one POST), return its id + pending_steps
138
- meshy make "a red sports car" --stop-after-first # wait for step 1, then return the resume command
139
- ```
71
+ `create / get / list / wait / stream / delete` work the same way on every
72
+ resource; `meshy <resource> --help` documents each one, and
73
+ `meshy api GET /balance` reaches any endpoint the CLI does not model yet.
140
74
 
141
- All guards run before the first task is created. If a later step fails, the
142
- error carries the finished step's task id and the command that resumes from it —
143
- running that beats starting over, which would pay for the finished step twice.
144
- `--async` and `--stop-after-first` are mutually exclusive.
75
+ ## For scripts and agents
145
76
 
146
- ## At a glance
77
+ stdout is the result and nothing else; progress and messages go to stderr.
78
+ Piped, redirected or run by an agent, the output is JSON automatically. Ask for
79
+ the stable envelope explicitly and the shape never depends on how you are run:
147
80
 
148
81
  ```bash
149
- # account
150
- meshy-cli balance
151
-
152
- # text → 3D (sync by default: blocks until the task finishes)
153
- meshy-cli text-to-3d create --mode preview --prompt "a red sports car"
154
- meshy-cli text-to-3d create --mode refine --preview-task-id <id>
155
-
156
- # image → 3D
157
- meshy-cli image-to-3d create --image-url https://example.com/cat.png
158
-
159
- # text → standalone motion clip (Prime/FBX by default; Swift produces BVH)
160
- meshy-cli text-to-motion create --prompt "a character waving" --duration 3
161
-
162
- # smart topology: component-aware low-poly with a native polycount
163
- meshy-cli image-to-3d create --image-url cat.png --model-type smart-topology --target-polycount 10000
164
-
165
- # fire-and-forget (--async): exactly one POST, returns the task_id, query later
166
- TASK=$(meshy-cli text-to-image create --prompt "mountain landscape" --async --output-schema v1 | jq -r .result.submission.task_id)
167
- meshy-cli text-to-image get "$TASK" --output-schema v1
168
- meshy-cli text-to-image wait "$TASK" --output-schema v1 --save-json ./task.json
169
- meshy-cli text-to-image stream "$TASK" --format ndjson --output-schema v1 # Server-Sent Events
170
-
171
- # UV unwrap and Creative Lab (photo → printable product, two stages)
172
- meshy uv-unwrap create --input-task-id <id> --async
173
- meshy creative-lab figure prototype create --image-url ./photo.png --name demo --async
174
- meshy creative-lab figure build create --input-task-id <prototype-id> --async
175
- meshy creative-lab lamp build create --input-task-id <id> --model-format zip --options '{"diameter_mm":180}'
176
-
177
- # public animation catalog (no key) and Enterprise showcases (billed per request)
178
- meshy animation-catalog list --category DailyActions --search wave
179
- meshy showcases list --search car --page-size 3 --model-format glb
180
-
181
- # local helpers (no key, no network)
182
- meshy download --task-json ./task.json --asset result.basic_animations.walking_glb_url --output walking.glb
183
- meshy project init --root ./meshy_output --name demo
184
- meshy inspect faces --task-json ./task.json --max-faces 300000
185
- meshy mesh prepare-print ./model.obj --height-mm 75
186
- meshy slicer detect
187
- meshy doctor
188
-
189
- # raw passthrough for any endpoint
190
- meshy-cli api GET /balance
191
- meshy-cli api POST /text-to-3d --data '{"mode":"preview","prompt":"a cactus"}'
192
- ```
193
-
194
- ## Stable machine output: `--output-schema v1`
195
-
196
- Existing commands keep their 0.2.0 output (`legacy`) unless told otherwise; the
197
- commands added in this release always speak `v1`. Pass `--output-schema v1` to
198
- get one envelope with six fixed keys on stdout, in `json` (default), `pretty`
199
- or `ndjson`:
200
-
201
- ```json
202
- {
203
- "schema_version": "meshy.cli/v1",
204
- "command": "image-to-3d.get",
205
- "ok": true,
206
- "result": {
207
- "task": { "task_id": "…", "resource": "image-to-3d", "status": "IN_PROGRESS", "progress": 52,
208
- "face_count": null, "consumed_credits": null, "model_urls": {}, "task_error": null, "…": "…" },
209
- "submission": { "state": "accepted", "operation_id": null },
210
- "downloads": { "state": "not_requested", "files": [], "metadata_path": null },
211
- "saved_json": null
212
- },
213
- "error": null,
214
- "warnings": []
215
- }
216
- ```
217
-
218
- `ok` says whether the CLI operation completed; `result.task.status` is the
219
- server's task state. A `get` of a FAILED task is `ok:true` (the query worked);
220
- a `wait` that ends on FAILED is `ok:false` with the whole task kept in `result`.
221
- Fields the server did not send are `null` — a missing `face_count` is never `0`.
222
- `--include-raw` adds the untouched response under `result.task.raw`;
223
- `--save-json <file>` writes the raw API JSON (never the envelope) and refuses to
224
- overwrite. Progress and update notices go to stderr, so stdout is always exactly
225
- one JSON document (`ndjson` streams emit one line per event plus a final
226
- `outcome` line). Errors are envelopes too:
227
-
228
- ```json
229
- { "schema_version": "meshy.cli/v1", "command": "text-to-3d.create", "ok": false,
230
- "result": { "submission": { "state": "unknown", "operation_id": "…" }, "task": null },
231
- "error": { "code": "submission_unknown", "message": "…", "http_status": null, "retryable": false,
232
- "recovery": { "action": "reconcile", "automatic": false, "command": "meshy text-to-3d list …" } },
233
- "warnings": [] }
234
- ```
235
-
236
- ### Create, wait, stream and money
237
-
238
- - `create` sends **exactly one POST**, journaled locally before it leaves
239
- (`~/.config/meshy/operations/<operation-id>.json`, no key material). A lost
240
- response, a 5xx or a malformed success is `submission_unknown` (exit 10): the
241
- server may have created the task, so the CLI never retries and never suggests
242
- re-running the create. Reconcile with `list`, then decide.
243
- - `--operation-id <id>` replays the recorded outcome of an identical earlier
244
- request instead of submitting again; a different request under the same id is
245
- refused (`operation_conflict`, exit 2, naming what differs). "Identical" means
246
- the same resource, API origin, credential — a one-way digest of the API key,
247
- or the OAuth account (its user id, else the login id `meshy auth login` mints
248
- for the profile) — and payload, with every inline image or model hashed by
249
- content. An OAuth profile saved before login ids existed carries no verifiable
250
- identity: it can start operations but is refused a replay (exit 2,
251
- `credential_unverified`) until you log in again. This is a local record, not a
252
- server-side idempotency key.
253
- - Local targets that would fail after the POST are checked before it: an
254
- existing `--save-json` file, an `-o` path outside `--workspace`, a missing
255
- `--project` all exit 11 with "nothing was submitted" and cost no request.
256
- - Once the server has accepted a task, every later failure — saving JSON,
257
- polling (a 503), downloading, recording, Ctrl-C — still reports
258
- `result.task_id`, `result.submission` and `result.next` (the `get`/`wait`/
259
- `stream` commands that pick the task up). A bookkeeping problem never reads
260
- as "no task was created".
261
- - `--async` returns after the POST (no polling). `get` is a query: any status
262
- exits 0. `wait --timeout N` polls with a monotonic deadline (`0` = one query)
263
- that also bounds every in-flight GET: a response arriving after the deadline
264
- is a timeout (exit 8, last status kept, `result.task` null when none arrived
265
- in time), never a late success, and no request starts once the budget is
266
- spent. `stream` follows Server-Sent Events with `--timeout` (total) and
267
- `--idle-timeout` (silence, keep-alives reset it); `-o` downloads the assets
268
- in every output format, and in `ndjson` the final `outcome` line carries the
269
- download manifest.
270
- - `-o` on `get`/`wait`/`stream`/`make` downloads every artifact of a SUCCEEDED
271
- task; `result.downloads.files` is a per-file manifest (key, path, bytes,
272
- sha256, status). When the second asset fails, the state is `partial`, the files
273
- already written stay listed and on disk, and the error keeps the asset host's
274
- class and HTTP status (a 503 is `network`, exit 7, not a local I/O error). The
275
- same holds for a failure *after* the transfers — relinking, digesting or
276
- publishing the `meta.json` sidecar: `downloads.failed_step` names the step, the
277
- manifest carries the digests actually on disk. The sidecar itself is published
278
- like an asset (exclusive, no symlink, inside the root), so a file that appears
279
- at its path during the download is never overwritten. The legacy schema
280
- reports the same failures with additive `task_id`/`operation_id` fields and the
281
- resume command as `hint`, so the accepted task is never lost from a default
282
- `create -o` error.
283
- - Ctrl-C stops waiting, streaming or downloading (exit 130) and sends no DELETE;
284
- the envelope carries the task id, the command that resumes and the files that
285
- had already landed. An interrupted transfer leaves no temp file behind.
286
-
287
- ## Resources
288
-
289
- One command per endpoint. They are all registered and all supported, but they
290
- are indexed by `meshy resources` rather than listed in `meshy --help` — that
291
- help text is read on every invocation (an agent pays for the whole surface each
292
- time), so it should not grow with the API. `meshy <resource> --help` documents
293
- each one in full; `meshy resources --output-schema v1` also lists the query and
294
- local commands with their kind.
295
-
296
- | Command | Meshy endpoint | Docs |
297
- |---|---|---|
298
- | `balance` | `GET /balance` | [docs](https://docs.meshy.ai/en/api/balance) |
299
- | `text-to-3d` | `/text-to-3d` (v2) | [docs](https://docs.meshy.ai/en/api/text-to-3d) |
300
- | `image-to-3d` | `/image-to-3d` | [docs](https://docs.meshy.ai/en/api/image-to-3d) |
301
- | `multi-image-to-3d` | `/multi-image-to-3d` | [docs](https://docs.meshy.ai/en/api/multi-image-to-3d) |
302
- | `remesh` | `/remesh` | [docs](https://docs.meshy.ai/en/api/remesh) |
303
- | `convert` | `/convert` | [docs](https://docs.meshy.ai/en/api/convert) |
304
- | `resize` | `/resize` | [docs](https://docs.meshy.ai/en/api/resize) |
305
- | `uv-unwrap` | `/uv-unwrap` | [docs](https://docs.meshy.ai/en/api/uv-unwrap) |
306
- | `rigging` | `/rigging` | [docs](https://docs.meshy.ai/en/api/rigging) |
307
- | `animate` | `/animations` | [docs](https://docs.meshy.ai/en/api/animations) |
308
- | `text-to-motion` | `/text-to-motion` | [docs](https://docs.meshy.ai/en/api/text-to-motion) |
309
- | `retexture` | `/retexture` | [docs](https://docs.meshy.ai/en/api/retexture) |
310
- | `text-to-image` | `/text-to-image` | [docs](https://docs.meshy.ai/en/api/text-to-image) |
311
- | `image-to-image` | `/image-to-image` | [docs](https://docs.meshy.ai/en/api/image-to-image) |
312
- | `multi-color-print` | `/print/multi-color` | [docs](https://docs.meshy.ai/en/api/multi-color-print) |
313
- | `analyze-printability` | `/print/analyze` | [docs](https://docs.meshy.ai/en/api/analyze-printability) |
314
- | `repair-printability` | `/print/repair` | [docs](https://docs.meshy.ai/en/api/repair-printability) |
315
- | `creative-lab <product> prototype\|build` | `/openapi/creative-lab/<product>/v1/<stage>` | [figure](https://docs.meshy.ai/en/api/creative-lab-figure) · [lamp](https://docs.meshy.ai/en/api/creative-lab-lamp) · [keychain](https://docs.meshy.ai/en/api/creative-lab-keychain) · [fridge-magnet](https://docs.meshy.ai/en/api/creative-lab-fridge-magnet) |
316
- | `animation-catalog list` | `GET /web/public/animations/resources` (no key) | [docs](https://docs.meshy.ai/en/api/animations) |
317
- | `showcases list` | `GET /showcases` (Enterprise; every request is billed) | [docs](https://docs.meshy.ai/en/api/enterprise-api) |
318
-
319
- Per-resource actions (all single-HTTP-call except `wait`/`stream`):
320
-
321
- ```
322
- meshy-cli <resource> create [flags] [--data <json>] [--async] [--timeout <s>] [--operation-id <id>]
323
- meshy-cli <resource> get <task-id> [--save-json <file>] [--include-raw] [--project <dir>]
324
- meshy-cli <resource> list [--page <n>] [--page-size <n>] [--sort-by <field>]
325
- meshy-cli <resource> wait <task-id> [--timeout <s>]
326
- meshy-cli <resource> stream <task-id> [--timeout <s>] [--idle-timeout <s>]
327
- meshy-cli <resource> delete <task-id>
328
- ```
329
-
330
- Top-level shortcut:
331
-
332
- ```
333
- meshy-cli delete <task-id>
334
- # Meshy's DELETE is unified across resources, but GET is not, so
335
- # `get`/`wait`/`stream` live only on their resource.
336
- ```
337
-
338
- `create` is **synchronous by default** — it polls until the task reaches a
339
- terminal status (`SUCCEEDED` / `FAILED` / `CANCELED`) or `--timeout` hits.
340
- Pass `--async` to return the `task_id` immediately; then call
341
- `<resource> get <id>`, `wait <id>` or `stream <id>` when you need the result.
342
-
343
- ### Creative Lab
344
-
345
- Four products (`figure`, `lamp`, `keychain`, `fridge-magnet`), each with a
346
- `prototype` stage (photo → styled concept image; the lamp prototype also yields
347
- a lampshade GLB) and a `build` stage that consumes a SUCCEEDED prototype created
348
- through this API with the same key. Build options are validated per product
349
- before anything is sent: lamp `--options` (diameter, thickness, light-source
350
- preset, rotations …) with `--model-format stl|zip`; keychain and fridge-magnet
351
- relief options with `--model-format glb|obj|zip` — their `obj` output is a ZIP
352
- bundle and is saved as `.zip`; figure has no options. Prototypes made in the
353
- web app are rejected by the server (404).
354
-
355
- ## Saving artifacts with `-o` (legacy) and `meshy download` (selective)
356
-
357
- When `-o` is set on a `create`/`wait`/`get`, the CLI downloads every artifact
358
- the task produced, writes a sidecar metadata file, and (legacy schema) prints a
359
- status report instead of JSON. Single-file outputs get a per-file
360
- `<stem>_meta.json`; directory-mode outputs share a single `meta.json`. Under
361
- `--output-schema v1` the same download is reported in `result.downloads`, and a
362
- task that is not yet SUCCEEDED yields `downloads.state: "not_ready"` with exit 0.
363
-
364
- ```bash
365
- meshy-cli text-to-image create --ai-model nano-banana-2-lite --prompt "a leaf" -o assets/leaf.jpeg
366
- meshy-cli text-to-image create --ai-model gpt-image-2-5-sunburst --aspect-ratio 3:2 --prompt "a leaf" -o assets/leaf-wide.png
367
- meshy-cli image-to-3d wait <id> -o out/robot/
368
- ```
369
-
370
- `meshy download` is the selective, scriptable counterpart:
371
-
372
- ```bash
373
- meshy download --task-json ./task.json --list # what is there?
374
- meshy download --task-json ./task.json --model-format glb --output ./model.glb
375
- meshy download --task-json ./rig.json --asset result.basic_animations.walking_glb_url --output ./walking.glb
376
- meshy download --task-json ./task.json --kind thumbnail --output-dir ./previews/
377
- meshy download --resource image-to-3d --task-id <id> --all --output-dir ./out/ # one GET, then the assets
378
- meshy download --url https://assets.meshy.ai/... --output ./file.glb
379
- ```
380
-
381
- Sources are `--task-json` (an API task, a legacy `meta.json`, or a v1 envelope),
382
- `--url`, or `--resource` + `--task-id`; selectors are `--asset <key>` (repeatable),
383
- `--model-format`, `--kind`, `--all`. With several assets and no selector the
384
- command lists the candidates and exits 2 instead of guessing. Selecting an OBJ
385
- pulls its MTL and textures (`--geometry-only` to skip); once the set has landed
386
- the OBJ's `mtllib` and the MTL's `map_*` references are rewritten to the names
387
- actually saved (`model.mtl`, `texture_0_base_color.png`, …) so the model loads
388
- from that directory. Textures are matched by the name the server served them
389
- under (then by channel), one candidate only: with several material groups a
390
- reference that could mean two files is left as written and reported as
391
- ambiguous, and a reference that merely equals one of the CLI's generated names
392
- (`texture_0_base_color.png`) while the server called that image something else
393
- is ambiguous too — identity follows the source, never the file name on disk.
394
- Channel fallbacks (a channel word in the reference, the MTL key's channel, the
395
- only texture there is) are heuristics and compete on the texture they actually
396
- reach: when two different references would both fall back to the same image, or
397
- one reference would go to different images under different keys, they all stay
398
- as written and are reported as ambiguous — the CLI never merges material groups
399
- without evidence that they name the same file. Every link is listed under `result.downloads.material_links`
400
- (`status: complete | incomplete`, the `newmtl` group of each map), rewritten
401
- files carry `relinked: true` with their final sha256, and a reference that
402
- matches no or several downloaded files stays as written and is warned
403
- (`material_reference_unresolved` / `material_reference_ambiguous`). Files are published
404
- exclusively (never overwritten without `--overwrite`), checked against the
405
- content type and magic bytes, kept inside the output directory (or `--workspace`),
406
- and listed with size and sha256 in `result.downloads.files`. With `--project <dir>`
407
- the files that landed inside the project are recorded in its `metadata.json`;
408
- when that record fails after the transfer (metadata.json replaced by a symlink,
409
- damaged, or locked) the command exits 11 with the complete `result` — manifest,
410
- `saved_json`, `project.action: "failed"` — and `error.recovery.command` is the
411
- one `meshy project record …` invocation that redoes the record — carrying the
412
- original `--workspace`, so a recovery never writes further than the command that
413
- failed; the assets stay where they landed. Problems visible before the transfer (no metadata.json, a
414
- symlink or invalid JSON in its place, a blank `--stage`) are refused with no
415
- request made. Asset hosts never
416
- receive the API credential; an expired signed URL is refreshed once when the
417
- task came from the API and reported as unrefreshable when it came from a file.
418
-
419
- ## Projects (`meshy_output/`)
420
-
421
- The Skills' project layout, without Python:
422
-
423
- ```bash
424
- meshy project init --root ./meshy_output --name "demo" --task-id <id>
425
- meshy text-to-3d wait <id> --project ./meshy_output/<folder> # saves task_<id>.json, records the stage
426
- meshy project record --project ./meshy_output/<folder> --task-id <id> --resource text-to-3d --stage preview --file preview.glb
427
- meshy project show --project ./meshy_output/<folder>
428
- meshy project list --root ./meshy_output
429
- meshy project rebuild-index --root ./meshy_output
430
- ```
431
-
432
- `metadata.json` (schema 2; legacy files are read as-is and migrated on the first
433
- write with a backup) is the source of truth; `history.json` is a rebuildable
434
- index. A repeat `(task_id, stage)` merges instead of duplicating.
435
-
436
- ## Printing helpers
437
-
438
- ```bash
439
- meshy inspect faces --task-json ./task.json --max-faces 300000 # pass (0) | fail (12) | unknown (13)
440
- meshy mesh prepare-print ./model.obj --height-mm 75 # writes ./model.print.obj
441
- meshy mesh prepare-print ./model.obj --height-mm 80 --in-place
442
- meshy slicer detect
443
- meshy slicer open --slicer OrcaSlicer --file ./model.print.obj
444
- ```
445
-
446
- `inspect faces` answers only whether the task's `face_count` is within the
447
- limit you pass (`--max-faces` is required); a missing count is `unknown`, never
448
- 0, and a failing verdict only *describes* a remesh. `prepare-print` rotates a
449
- Y-up OBJ to Z-up, scales it to the target height, centres it on XY and rests it
450
- on Z=0, preserving faces, UVs, normals (rotated only) and material references;
451
- it never overwrites without `--in-place`, and the MTL/texture copies it makes
452
- beside the output are proven — on real paths, before any directory is created —
453
- to lie inside the output directory (or `--workspace`), so a symlinked
454
- `materials/` cannot redirect them. `slicer open` launches only a
455
- registered slicer at its detected path with the file as a single argument — no
456
- shell, no default-application fallback; `launch_requested` is not proof that
457
- the import succeeded.
458
-
459
- ## Image and 3D-model inputs
460
-
461
- Flags that take a media source (`--image-url`, `--image-urls`,
462
- `--reference-image-urls`, `--texture-image-url`, `--image-style-url`,
463
- `--multiview-image-urls`, `--model-url`) — and the same fields inside `--data` —
464
- accept:
465
-
466
- - **http(s) URLs** — preflighted with an unauthenticated HEAD so unreachable sources fail fast.
467
- - **Local file paths** — absolute or relative to cwd. MIME-sniffed via magic
468
- bytes (with extension fallback), size-capped, and inlined as `data:` URIs on the wire.
469
- - **`data:` URIs** — validated (base64, MIME kind, size) and passed through.
470
-
471
- Missing files and 4xx/5xx preflights exit with code `2` and a flag-prefixed
472
- message before any task is created. GLB-only fields (`uv-unwrap`, `rigging`
473
- `--model-url`) reject other formats locally.
474
-
475
- ## Global flags
476
-
477
- | Flag | Purpose |
478
- |------|---------|
479
- | `--api-key <key>` | Override `MESHY_API_KEY` |
480
- | `--api-key-file <path>` | Read `MESHY_API_KEY` from an explicit dotenv-style file (only that key) |
481
- | `--base-url-v1 <url>` / `--base-url-v2 <url>` | Override endpoints (staging/proxy) |
482
- | `--base-url-creative-lab <url>` | Override the Creative Lab base (default: `<v1 origin>/openapi/creative-lab`) |
483
- | `--output-schema legacy\|v1` | Stdout data model (existing commands default to `legacy`; new commands are `v1`) |
484
- | `--format json\|pretty\|ndjson` | Stdout rendering. Defaults to `pretty` when stdout is a terminal and `json` everywhere else — piped, redirected, or spawned as a subprocess, which is every agent, script and CI run. `--json` is shorthand for `--format json`. `-o <file>` keeps writing JSON unless `--format` is explicit |
485
- | `-o, --output <path>` | Download artifacts to a file/directory (task commands); output file for `mesh prepare-print` |
486
- | `--workspace <dir>` | Confine every written file to this directory: `download`, `-o` on task verbs and `make` (report-only tasks included), `--save-json`, `--project`/project folders and the history index (skipped with `index_dirty` when its root would fall outside), `mesh prepare-print` outputs and their copied materials — checked on real paths before anything, even a directory, is created. The boundary is frozen when the command starts (real path and directory identity): a workspace or project replaced by a symlink while a request is in flight is refused, never followed |
487
- | `--no-update-check` | Skip the background npm version check in this process |
488
- | `NO_COLOR` / `FORCE_COLOR` (env) | Colour is on only when the stream is a terminal. `NO_COLOR` turns it off, `FORCE_COLOR` forces it on (`FORCE_COLOR=0` off), `TERM=dumb` disables it. `json` and `ndjson` are never coloured, and neither is anything written to a file |
489
- | `-v, --verbose` | Debug logging to stderr |
490
- | `--log-level <level>` | `debug \| info \| warn \| error \| silent` |
491
-
492
- ## Exit codes
493
-
494
- | Code | Meaning |
495
- |------|---------|
496
- | 0 | success (including `get` of any task status, empty lists, local checks that pass) |
497
- | 1 | task ended FAILED/CANCELED while waiting, or an unclassified error |
498
- | 2 | usage (flag parse error, conflicting or missing arguments) |
499
- | 3 | auth (`401`, no usable credential) |
500
- | 4 | validation (`400`, `422`, locally rejected payload) |
501
- | 5 | not found (`404`; the cause is not guessed) |
502
- | 6 | rate limit (`429`) |
503
- | 7 | network (read failures, stream disconnects) |
504
- | 8 | timed out waiting for or streaming a task (the task keeps running) |
505
- | 9 | credit exhausted (`402`) |
506
- | 10 | submission unknown — a create was sent but its outcome could not be confirmed |
507
- | 11 | local I/O — refused overwrite, path outside the authorised root, journal/project write failure |
508
- | 12 | check failed (`inspect faces` over the limit) |
509
- | 13 | check unknown (`inspect faces` without a usable face count) |
510
- | 130 | interrupted (Ctrl-C); nothing was deleted server-side |
511
-
512
- ## Environment variables
513
-
514
- | Variable | Default |
515
- |---|---|
516
- | `MESHY_API_KEY` | — (required unless a profile is stored or `--api-key-file` is given) |
517
- | `MESHY_BASE_URL_V1` | `https://api.meshy.ai/openapi/v1` |
518
- | `MESHY_BASE_URL_V2` | `https://api.meshy.ai/openapi/v2` |
519
- | `MESHY_BASE_URL_CREATIVE_LAB` | derived from the v1 origin (`/openapi/creative-lab`) |
520
- | `MESHY_OAUTH_AUTHORIZE_URL` | `https://www.meshy.ai/oauth/authorize` — override for staging/testing |
521
- | `MESHY_CLI_NO_BROWSER` | unset — set to `1` to suppress browser open (URL still printed to stderr) |
522
- | `MESHY_CONFIG_DIR` | `~/.config/meshy` (credentials, operation journal, update cache) |
523
- | `MESHY_CONNECT_TIMEOUT_MS` | `10000` (read for compatibility; fetch has no separate connect timeout) |
524
- | `MESHY_READ_TIMEOUT_MS` | `120000` — covers headers *and* body |
525
- | `MESHY_POLL_INTERVAL_MS` | `3000` |
526
- | `MESHY_LOG_LEVEL` | `warn` |
527
- | `MESHY_CLI_NO_UPDATE_NOTIFIER` | unset — any non-empty value disables update checks; CI envs (`CI`, `GITHUB_ACTIONS`, `BUILD_NUMBER`, `RUN_ID`) auto-skip |
528
-
529
- ## Update notifications
530
-
531
- meshy-cli checks the npm registry for a newer version at most once per 24 hours. The result is cached at `<MESHY_CONFIG_DIR>/update-state.json`. The refresh runs in a detached background process so it can never slow down or fail a command, and it never runs for local commands (`resources`, `project`, `inspect`, `mesh`, `slicer`, `doctor`, `download`, `animation-catalog`), for `make --dry-run`, or with `--no-update-check`.
532
-
533
- When a newer version is available:
534
-
535
- - **Legacy JSON object outputs** carry a top-level `_notice.update = { current, latest, message, command }` so agents can relay it to the user.
536
- - **v1 envelopes are never decorated** — the six keys are the contract; humans get the hint on stderr.
537
- - **ndjson arrays** (legacy) carry `_notice.update` on the **first line only**.
538
- - **Humans on an interactive terminal** get a single line on stderr after the command output.
539
- - **stdout is never polluted** — the notice never appears on stdout.
540
-
541
- ## Agent skills
542
-
543
- The `skills/` directory contains markdown-based skills for AI coding agents
544
- (Claude Code, etc.):
545
-
546
- - [`skills/meshy-cli`](skills/meshy-cli/SKILL.md) — a single skill covering
547
- setup, `make`, the shared verb contract, the v1 envelope, the API constraints
548
- that produce failed tasks when ignored, the local helpers, and the exit-code table.
549
-
550
- It is deliberately short. A skill is loaded into an agent's context on every
551
- invocation, so length is a running cost; anything an agent can look up on
552
- demand (`meshy resources`, `meshy <resource> --help`,
553
- <https://docs.meshy.ai/en/api/>) is linked rather than copied.
554
-
555
- ## Skill-parity documentation
556
-
557
- `docs/skill-parity/` records how this release covers the execution capabilities
558
- of the Meshy Skills without Python: the frozen baseline, the endpoint contracts,
559
- the capability matrix, design decisions, migration notes with every intentional
560
- difference, and the verification record.
561
-
562
- ## Project layout
563
-
564
- ```
565
- meshy-cli/
566
- ├── src/
567
- │ ├── index.ts # CLI entry: update-check policy, SIGINT, unified error exit
568
- │ ├── root.ts # root command + global flag wiring
569
- │ ├── cmd/ # make, auth, balance, resources, api, one file per endpoint,
570
- │ │ # uv-unwrap, creative-lab, animation-catalog, showcases,
571
- │ │ # download, project, inspect, mesh, slicer, doctor
572
- │ ├── client/
573
- │ │ ├── resource-registry.ts # every resource: path, family, verbs, billing, media fields
574
- │ │ ├── transport.ts # per-family HTTP transport (auth scope, deadlines, redirects)
575
- │ │ ├── endpoints/ # TaskEndpoint + balance / catalog / showcases
576
- │ │ └── types.ts # Zod task schemas
577
- │ └── internal/
578
- │ ├── result.ts / errors.ts # v1 envelope, error codes, exit codes
579
- │ ├── task-command.ts # create/get/list/wait/stream/delete factory
580
- │ ├── operation-store.ts # submission journal (single POST, unknown outcomes)
581
- │ ├── stream.ts / poll.ts # SSE parser + cancellable polling
582
- │ ├── artifacts.ts / download.ts # asset keys, safe downloads, legacy -o
583
- │ ├── project-store.ts # meshy_output metadata + history
584
- │ ├── inspect.ts / obj-transform.ts / slicers.ts / doctor.ts
585
- │ ├── config.ts / runtime.ts / env-file.ts / credentials.ts / oauth.ts
586
- │ └── atomic-file.ts / paths.ts / lock.ts
587
- ├── tests/ # node:test unit, contract and black-box tests
588
- ├── skills/ # agent-facing skill (published)
589
- ├── docs/skill-parity/ # baseline, contracts, matrix, decisions, verification
590
- ├── package.json
591
- └── README.md
592
- ```
593
-
594
- ## Design notes
595
-
596
- - **Two layers, one of them opinion-free.** `make` chains endpoints; the
597
- resource commands expose them one at a time. `make` picks its chain from the
598
- input type alone and stops there.
599
- - **Plan first, then spend.** `make` computes the whole chain before creating
600
- anything, so `--dry-run` and a real run share one code path and one estimate.
601
- - **One POST, journaled.** Every billable create is recorded before it is sent;
602
- a lost answer is reported as unknown, never re-sent.
603
- - **One registry.** `src/client/resource-registry.ts` is the only place a path,
604
- API family, verb set or media field is declared; commands, the `resources`
605
- index and the transport all read from it.
606
- - **Credentials have a scope.** The API credential goes to the API origins it
607
- was resolved for and nowhere else — not to asset hosts, not to the public
608
- catalog, not across redirects.
609
- - **`--data` escape hatch.** Every `create` accepts a raw JSON object (or
610
- `@file.json`) that merges with structured flags (flags win, explicit `false`
611
- and `0` survive).
612
- - **Stdout is reserved for command output.** Logs, progress and errors' prose go
613
- to stderr so pipes (`| jq`, `-o file`) stay clean.
82
+ meshy text-to-3d create --mode preview --prompt "a cactus" --async --output-schema v1 --format json \
83
+ | jq -r .result.submission.task_id
84
+ ```
85
+
86
+ Exit codes are stable (`3` auth, `8` timed out, `10` submission unknown, …), and
87
+ a create is sent exactly once, never retried behind your back. The envelope,
88
+ the create/wait/stream semantics and the full exit-code table are in
89
+ [docs/scripting.md](https://github.com/meshy-dev/meshy-cli/blob/main/docs/scripting.md).
90
+
91
+ ## Documentation
92
+
93
+ - [Using meshy-cli](https://github.com/meshy-dev/meshy-cli/blob/main/docs/usage.md)
94
+ — auth in depth, `make`, every resource, downloads, projects, printing
95
+ helpers, media inputs, what the terminal shows, global flags, environment
96
+ variables
97
+ - [Scripts and agents](https://github.com/meshy-dev/meshy-cli/blob/main/docs/scripting.md)
98
+ — the v1 envelope, create / wait / stream guarantees, exit codes, the agent skill
99
+ - [Developing](https://github.com/meshy-dev/meshy-cli/blob/main/docs/development.md)
100
+ — building from source, project layout, design notes
101
+ - [Meshy API reference](https://docs.meshy.ai/en/api/) — parameters and credit costs
614
102
 
615
103
  ## License
616
104