Skip to content

✨ Add meshy plugin — Meshy AI 3D generation via zero-dependency Node client#8

Open
Fl0p wants to merge 7 commits into
mainfrom
add-meshy-plugin
Open

✨ Add meshy plugin — Meshy AI 3D generation via zero-dependency Node client#8
Fl0p wants to merge 7 commits into
mainfrom
add-meshy-plugin

Conversation

@Fl0p

@Fl0p Fl0p commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

What

Adds a new meshy plugin: 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 install and no build step.

Architecture

  • One generic create → poll → download engine + a data registry of endpoints; commands are thin.
  • Covers everything: 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, experimental creative-lab, plus balance / status / list / download.
  • Output contract: exactly one JSON object on STDOUT (progress on STDERR); classified exit codes (auth/credits/rate-limit/task-failed/timeout/network).
  • 429 + transient-network retry with backoff, internal polling, resumable by task_id (--no-wait → later status/download), local-image auto-base64, expired-URL re-fetch on download, --param/--json escape hatch for any field.
  • API key from --api-key$MESHY_AI_API_KEY$MESHY_API_KEY.

Files

  • plugins/meshy/skills/meshy/scripts/meshy.mjs — the client
  • plugins/meshy/skills/meshy/SKILL.md — lean, trigger-optimized; full param tables in docs/api-reference.md (loaded on demand)
  • plugins/meshy/.claude-plugin/plugin.json, marketplace entry, docs/plugin-catalog/meshy-plugin.md + index link

Testing (against the live API)

  • Offline: help/help --json, all validation paths → exit 2, no key → exit 3.
  • Live: balance ✓, invalid key → 401/exit 3 ✓, free test-key full create→poll→download (glb/fbx/usdz/obj/stl + thumbnail), status, list, download resume ✓.
  • Real end-to-end: generated a cube and a donut (text-to-3d preview), textured the donut via refine --enable-pbr (got base_color/metallic/roughness/normal maps), downloaded glb/obj + textures + thumbnails. All .glb verified as glTF binary v2; credit accounting exact (760 → 740 = −20).

Note: Meshy's public API only lists tasks created via the API — models created in the web app are not reachable through any public endpoint (confirmed; the official MCP's "list models" is the same task list).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new Meshy plugin for 3D generation workflows, including command support for creating, checking, downloading, and managing tasks.
    • Added plugin metadata so it appears in the plugin catalog and can be discovered more easily.
  • Documentation

    • Added detailed setup and usage docs for the Meshy plugin, including installation, configuration, supported commands, and common troubleshooting.
    • Updated the plugin catalog to list the new Meshy plugin.
  • Chores

    • Ignored local test artifacts related to the Meshy plugin.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Fl0p, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30b14fd8-a426-4121-825f-8c4ac66bfd34

📥 Commits

Reviewing files that changed from the base of the PR and between 4323034 and 8bdd272.

📒 Files selected for processing (9)
  • .claude-plugin/marketplace.json
  • .gitignore
  • docs/.vitepress/config.mts
  • docs/plugin-catalog/index.md
  • docs/plugin-catalog/meshy-plugin.md
  • plugins/meshy/.claude-plugin/plugin.json
  • plugins/meshy/skills/meshy/SKILL.md
  • plugins/meshy/skills/meshy/docs/api-reference.md
  • plugins/meshy/skills/meshy/scripts/meshy.mjs
📝 Walkthrough

Walkthrough

Adds a new meshy plugin that gives Claude Code access to the Meshy 3D generation REST API via a single zero-dependency Node.js ESM CLI script (meshy.mjs). Includes plugin manifest, marketplace and catalog registration, skill configuration with tool wiring, a bundled API reference, and user-facing documentation.

Changes

Meshy Plugin

Layer / File(s) Summary
Plugin manifest and marketplace registration
plugins/meshy/.claude-plugin/plugin.json, .claude-plugin/marketplace.json, .gitignore, docs/plugin-catalog/index.md
Adds the plugin identity manifest, registers the plugin in the marketplace JSON, excludes local test artifacts from git, and adds the catalog index entry.
Skill configuration and I/O contract
plugins/meshy/skills/meshy/SKILL.md
Defines frontmatter metadata, allowed-tools wiring for Node script execution, CLI STDOUT/STDERR contract, exit codes, end-to-end workflow, command/option reference table, and operational notes.
CLI entrypoint, command registry, and error handling
plugins/meshy/skills/meshy/scripts/meshy.mjs (lines 1–468)
Sets up the ESM entrypoint and imports, defines the typed parameter/command registry (TYPES) with validation rules, exit-code constants, command alias map, custom ApiError/CliError classes, error classification, argument parser with --no-<bool> and --json support, type coercion, request-body builder, and output emitters.
HTTP engine, image normalization, and task orchestration
plugins/meshy/skills/meshy/scripts/meshy.mjs (lines 470–617)
Implements the HTTP request engine with AbortController timeout, 429 retry with retry-after backoff, and transient-network retry; adds image-field normalization to data URIs; implements basePath resolution and task lifecycle helpers including create, poll-to-terminal, and list.
Download, command handlers, and main dispatcher
plugins/meshy/skills/meshy/scripts/meshy.mjs (lines 618–966)
Implements asset download with expired-URL re-fetch retry, command handlers (cmdGenerate, status, list, download, balance), help/JSON-help output, SIGINT handling that emits an interrupted JSON error with current task_id, and top-level command routing with exit-code wiring.
Plugin docs and API reference
docs/plugin-catalog/meshy-plugin.md, plugins/meshy/skills/meshy/docs/api-reference.md
Adds user-facing plugin overview (installation, requirements, features, runtime behaviors, credit ranges, limitations) and the bundled CLI API reference (endpoints, parameters, output shapes, polling semantics, retry rules).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 Hop hop, a new dimension appears,
From text to mesh in seconds, not years!
A script that polls and downloads with care,
Zero deps, just Node floating in air.
The rabbit cheers — now models in 3D,
Meshy magic, plain as can be! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the meshy plugin with a zero-dependency Node client for Meshy AI 3D generation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-meshy-plugin

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e848a0e and 4323034.

📒 Files selected for processing (8)
  • .claude-plugin/marketplace.json
  • .gitignore
  • docs/plugin-catalog/index.md
  • docs/plugin-catalog/meshy-plugin.md
  • plugins/meshy/.claude-plugin/plugin.json
  • plugins/meshy/skills/meshy/SKILL.md
  • plugins/meshy/skills/meshy/docs/api-reference.md
  • plugins/meshy/skills/meshy/scripts/meshy.mjs

Comment thread plugins/meshy/skills/meshy/scripts/meshy.mjs
Comment thread plugins/meshy/skills/meshy/scripts/meshy.mjs Outdated
Comment thread plugins/meshy/skills/meshy/scripts/meshy.mjs Outdated
Comment thread plugins/meshy/skills/meshy/scripts/meshy.mjs
Comment thread plugins/meshy/skills/meshy/scripts/meshy.mjs

@Fl0p Fl0p left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 → download engine + 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 Authorization header, 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 a createTask POST 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 xyz pass Number() unguarded → pollIntervalMs = NaN makes setTimeout(NaN) a 0ms tight loop, and Date.now() > NaN never trips the deadline → hammers the API. Validate finite numbers and reject with a usage error. Same Number() pattern in cmdList page args.
  • Interrupt task-id is always null (lines ~742-757 / 944). state.taskId in main() is never assigned after createTask, so Ctrl-C can't emit the id for resume. The state object isn't passed into cmdGenerate. Harmless, but the resumable-abort nicety doesn't actually deliver the id.
  • Expired-URL recovery refreshes only once (downloadAssets, line ~693). The single refreshed boolean 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 / version write human text to STDOUT, technically against the "STDOUT is one JSON object" contract. help --json covers machine callers, so this is fine as-is — just calling it out.

None of the minors block the happy path. Nice plugin.

@Fl0p

Fl0p commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

Applied the one fix I flagged in review — pushed bbd2dc4 to this branch.

🐛 Don't retry non-idempotent POST on network errors. request() retried any transient network error/timeout for all methods, so a createTask POST that reached the server but lost its response would be re-sent → second task, credits charged twice. Now network/timeout retries are gated to idempotent methods (GET/HEAD); 429 retries stay on for all methods (429 = not processed, safe to repeat).

Verified after the change: node --check ok, help --json valid, no key → exit 3, no --prompt → exit 2.

With this in, PR is merge-ready from my side. The remaining items (NaN poll-timeout validation, state.taskId on Ctrl-C, one-shot expired-URL recovery) stay as optional follow-ups, not blockers. Merge decision is yours.

Fl0p pushed a commit that referenced this pull request Jun 28, 2026
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>
Fl0p pushed a commit that referenced this pull request Jun 28, 2026
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>
Fl0p pushed a commit that referenced this pull request Jun 28, 2026
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>
Fl0p pushed a commit that referenced this pull request Jun 28, 2026
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>
FlopBut and others added 7 commits June 28, 2026 20:44
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>
@Fl0p Fl0p force-pushed the add-meshy-plugin branch from 42d20db to 8bdd272 Compare June 28, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant