✨ Add meshy plugin — Meshy AI 3D generation via zero-dependency Node client#8
✨ Add meshy plugin — Meshy AI 3D generation via zero-dependency Node client#8Fl0p wants to merge 7 commits into
Conversation
|
Warning Review limit reached
More reviews will be available in 49 minutes and 56 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughAdds a new ChangesMeshy Plugin
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/meshy/skills/meshy/scripts/meshy.mjs`:
- Around line 742-757: Update cmdGenerate so the interrupt state is assigned the
newly created task id after createTask succeeds, before any early return or
waiting logic. Use the existing state object and its taskId field in meshy.mjs
so Ctrl-C can emit the current task_id instead of null, and make sure the same
update applies to the no_wait path and the later wait/resume flow.
- Around line 872-900: The help/version paths are violating the JSON STDOUT
contract by writing plain text instead of a single parseable JSON object. Update
the CLI handling in `printHelp` and the version output path to emit JSON via
`emit(...)` on STDOUT, keeping human-readable text only on STDERR if needed.
Make sure both `help` and `version` follow the same structured output shape as
the other commands so callers can always parse STDOUT consistently.
- Around line 495-529: The retry logic in request() currently retries transient
failures for all methods, which can duplicate non-idempotent POST creates like
createTask(). Update request() to avoid retrying POST requests unless an
idempotency key is explicitly provided, and ensure createTask() either disables
retries or passes a unique idempotency key when calling request(). Keep the
retry behavior for safe/idempotent methods only.
- Around line 903-913: Validate the numeric CLI flags before `buildContext()`
returns by checking `request_timeout`, `poll_timeout`, `interval`, and `retries`
for valid finite numbers instead of blindly using `Number(...)`; if any value is
invalid, return a usage error rather than constructing the context. Update
`buildContext(opts)` in `meshy.mjs` so it rejects `NaN`/non-numeric inputs and
only populates `requestTimeoutMs`, `pollTimeoutMs`, `pollIntervalMs`, and
`maxRetries` with safe values.
- Around line 689-708: The expired-URL recovery in downloadAssets only refreshes
once and then keeps using stale URLs for later assets because the refreshed flag
blocks additional task re-fetches. Update the download loop so each asset that
fails with 401/403/410 can trigger a fresh getTask() lookup for its own URL, or
otherwise reuse a refreshed URL map for the remaining items, rather than
limiting recovery to the first failure. Keep the fix localized to
downloadAssets, collectUrls, and the match lookup logic so all remaining assets
can recover after signed URLs expire together.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c08b9709-2546-49e5-a91b-a9627a947163
📒 Files selected for processing (8)
.claude-plugin/marketplace.json.gitignoredocs/plugin-catalog/index.mddocs/plugin-catalog/meshy-plugin.mdplugins/meshy/.claude-plugin/plugin.jsonplugins/meshy/skills/meshy/SKILL.mdplugins/meshy/skills/meshy/docs/api-reference.mdplugins/meshy/skills/meshy/scripts/meshy.mjs
Fl0p
left a comment
There was a problem hiding this comment.
Review — meshy plugin (Daedalus / CTO)
Verdict: solid work, mergeable. Architecture, structure, docs, and repo conventions are all correct, and the offline paths verify cleanly (node --check OK; help --json valid JSON; no-key → exit 3; missing prompt / unknown flag → exit 2). I'd merge after one small fix below; the rest are optional.
What's good
- Clean generic
create → poll → downloadengine + data registry; commands stay thin. Easy to extend, easy to debug at 2am (boring-tech lens ✅). - Zero-dependency, no build step, runs identically across OSes — trivial deployment story ✅.
- Strong, consistent output contract: one JSON object on STDOUT, progress on STDERR, classified exit codes mirrored in
error.kind. - No secret leakage — API key only ever goes into the
Authorizationheader, never logged. - Correct repo layout: scripts live inside the skill dir and are invoked via
${CLAUDE_SKILL_DIR}per our CLAUDE.md; marketplace entry,plugin.json, catalog page + index link all present and consistent.
Recommend fixing before merge (1)
request()retries non-idempotent POSTs on transient network errors (lines ~522-531). If acreateTaskPOST is received and processed by Meshy but the response is lost (ECONNRESET / abort-timeout), the retry creates a second task and double-spends credits. Low probability (create returns fast), but it's silent money on a paid API ("budgets are real" lens). Fix: only retry transient network failures for idempotent methods (GET); leave 429 retries on POST as-is (429 = not processed, safe). This is CodeRabbit finding #3 — I agree with it.
Minor / optional (can be a follow-up)
- NaN numeric flags (
buildContext, line ~903).--poll-timeout xyz/--interval xyzpassNumber()unguarded →pollIntervalMs = NaNmakessetTimeout(NaN)a 0ms tight loop, andDate.now() > NaNnever trips the deadline → hammers the API. Validate finite numbers and reject with a usage error. SameNumber()pattern incmdListpage args. - Interrupt task-id is always
null(lines ~742-757 / 944).state.taskIdinmain()is never assigned aftercreateTask, so Ctrl-C can't emit the id for resume. Thestateobject isn't passed intocmdGenerate. Harmless, but the resumable-abort nicety doesn't actually deliver the id. - Expired-URL recovery refreshes only once (
downloadAssets, line ~693). The singlerefreshedboolean means if signed URLs expire together, only the first asset recovers; the rest fall through to error with stale URLs. Cache the re-fetched task and reuse its fresh URL map for all remaining items. - Nit: plain
help/versionwrite human text to STDOUT, technically against the "STDOUT is one JSON object" contract.help --jsoncovers machine callers, so this is fine as-is — just calling it out.
None of the minors block the happy path. Nice plugin.
|
Applied the one fix I flagged in review — pushed 🐛 Don't retry non-idempotent POST on network errors. Verified after the change: With this in, PR is merge-ready from my side. The remaining items (NaN poll-timeout validation, |
Per CodeRabbit PR review on #8: downloadAssets() only re-fetched fresh signed URLs for the first asset that hit 401/403/410; a one-shot 'refreshed' flag then forced every later asset to keep using its stale URL, so they failed even though signed URLs expire together. Build a name->fresh-URL map once on first expiry and reuse it for all remaining assets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per CodeRabbit PR review on #8: state.taskId was initialized but never assigned, so a Ctrl-C during generation emitted task_id:null and broke the documented resumable-abort path. Thread the state object into cmdGenerate and set state.taskId right after createTask; also set it from the id argument for status/download so an interrupt there is resumable too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per CodeRabbit PR review on #8: the version path wrote a bare string to STDOUT, breaking the 'STDOUT is one parseable JSON object' contract. Emit version via emit() like every other command. help is intentionally left human-readable (a documented exception for the interactive entry point); 'help --json' already provides the machine-readable catalog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per CodeRabbit PR review on #8: buildContext() ran --poll-timeout/--interval/--request-timeout/--retries through a bare Number(), so a non-numeric value became NaN and silently broke behavior (a NaN poll deadline never times out; a NaN interval busy-loops). Reject non-finite or below-minimum values with a usage error (exit 2) before the context is built. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zero-dependency Node (ESM, native fetch) CLI client wrapped in a model-invoked skill, covering the full Meshy REST API (text/image-to-3D, multi-image, refine, retexture, remesh, rig, animate, image generation, convert/resize/uv-unwrap, 3D-printing prep, plus balance/status/list/download) through one generic create→poll→download engine. Chosen over the official MCP server to avoid loading its tool schemas into context every session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
request() retried any transient network error or timeout regardless of HTTP method. A createTask POST that reached the server but lost its response would be re-sent, creating a second task and double-charging credits on Meshy's paid API. Gate network/timeout retries to idempotent methods (GET/HEAD); 429 retries stay enabled for all methods since a 429 means the request was not processed. Co-Authored-By: Daedalus <daedalus@agents.flopbut.local> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per CodeRabbit PR review on #8: downloadAssets() only re-fetched fresh signed URLs for the first asset that hit 401/403/410; a one-shot 'refreshed' flag then forced every later asset to keep using its stale URL, so they failed even though signed URLs expire together. Build a name->fresh-URL map once on first expiry and reuse it for all remaining assets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per CodeRabbit PR review on #8: state.taskId was initialized but never assigned, so a Ctrl-C during generation emitted task_id:null and broke the documented resumable-abort path. Thread the state object into cmdGenerate and set state.taskId right after createTask; also set it from the id argument for status/download so an interrupt there is resumable too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per CodeRabbit PR review on #8: the version path wrote a bare string to STDOUT, breaking the 'STDOUT is one parseable JSON object' contract. Emit version via emit() like every other command. help is intentionally left human-readable (a documented exception for the interactive entry point); 'help --json' already provides the machine-readable catalog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per CodeRabbit PR review on #8: buildContext() ran --poll-timeout/--interval/--request-timeout/--retries through a bare Number(), so a non-numeric value became NaN and silently broke behavior (a NaN poll deadline never times out; a NaN interval busy-loops). Reject non-finite or below-minimum values with a usage error (exit 2) before the context is built. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Register /plugin-catalog/meshy-plugin in the VitePress sidebar (config.mts) so the Meshy page appears in the Plugin Catalog nav. config.mts only exists on the VitePress main, so this lands now that the branch is rebased onto it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds a new
meshyplugin: a model-invoked skill that drives the full Meshy AI 3D-generation REST API through a single zero-dependency Node (ESM) CLI (meshy.mjs).Deliberately not an MCP server — an MCP server loads all of its tool schemas into context on every session, while a skill loads only its one-line description until needed. Node + plain JS was chosen over bash/jq (broken on Windows) and Python (absent on Windows) so it runs identically everywhere with no
npm installand no build step.Architecture
create → poll → downloadengine + a data registry of endpoints; commands are thin.text-to-3d(+refine),image-to-3d,multi-image-to-3d,retexture,remesh,rig,animate,text-to-image,image-to-image,convert,resize,uv-unwrap, printability analyze/repair,multi-color-print, experimentalcreative-lab, plusbalance/status/list/download.task_id(--no-wait→ laterstatus/download), local-image auto-base64, expired-URL re-fetch on download,--param/--jsonescape hatch for any field.--api-key→$MESHY_AI_API_KEY→$MESHY_API_KEY.Files
plugins/meshy/skills/meshy/scripts/meshy.mjs— the clientplugins/meshy/skills/meshy/SKILL.md— lean, trigger-optimized; full param tables indocs/api-reference.md(loaded on demand)plugins/meshy/.claude-plugin/plugin.json, marketplace entry,docs/plugin-catalog/meshy-plugin.md+ index linkTesting (against the live API)
help --json, all validation paths → exit 2, no key → exit 3.balance✓, invalid key → 401/exit 3 ✓, free test-key full create→poll→download (glb/fbx/usdz/obj/stl + thumbnail),status,list,downloadresume ✓.refine --enable-pbr(got base_color/metallic/roughness/normal maps), downloaded glb/obj + textures + thumbnails. All.glbverified asglTF binary v2; credit accounting exact (760 → 740 = −20).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores