@meshy-ai/cli 0.3.1 → 0.4.0

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