Skip to content

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

Closed
Fl0p wants to merge 8 commits into
mainfrom
add-meshy-plugin
Closed

✨ Add meshy plugin — Meshy AI 3D generation via zero-dependency Node client#8
Fl0p wants to merge 8 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

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 76ccba2b-1278-43c9-949e-3b87dece61e2

📝 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
@Fl0p Fl0p self-assigned this Jul 10, 2026
Resolve the plugin-catalog sidebar collision: keep the KSeF entry added on
main alongside the Meshy entry from this branch, matching the ordering
already used in the catalog index and marketplace manifest.

Co-Authored-By: Daedalus <daedalus@agents.flopbut.local>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Fl0p

Fl0p commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main — conflicts resolved, PR is mergeable again

This PR had gone CONFLICTING against main and was unmergeable by anyone. Merged main in (8cdb4d1) and resolved the single conflict.

The conflict: docs/.vitepress/config.mts, the plugin-catalog sidebar. This branch appended a Meshy entry; main had meanwhile inserted the KSeF entry. Kept both, ordered to match docs/plugin-catalog/index.md and .claude-plugin/marketplace.json, which both already list Meshy last. Everything else (marketplace.json, .gitignore, docs/plugin-catalog/index.md) auto-merged.

Verified on the merged tree:

  • npm run docs:build — VitePress build clean
  • .claude-plugin/marketplace.json parses, all 5 plugins present (docs, git, ksef, resolve-coderabbit, meshy)
  • node plugins/meshy/skills/meshy/scripts/meshy.mjs version → single JSON object on STDOUT, honouring the documented output contract
  • no conflict markers anywhere in the tree

About the red claude-review check — it is pre-existing and unrelated to this change. That workflow has failed on every run since 2026-07-21 across three unrelated branches (last success 2026-06-28), erroring ~2s after the SDK starts, before it reviews anything. Tracked and being diagnosed separately. CodeRabbit passes.

No functional code in this PR was modified — the merge only touched the docs sidebar. Ship/no-ship is still an open product call, not an engineering blocker.

Fl0p pushed a commit that referenced this pull request Aug 22, 2026
Adds the `meshy` plugin to the marketplace: a zero-dependency Node CLI
(`meshy.mjs`) wrapped in a skill, covering text/image-to-3D preview and
refine, retexture, remesh, rig, animate and format conversion against the
Meshy AI API. Every command emits exactly one JSON object on STDOUT, with
distinct exit codes for auth, credit, rate-limit, task-failure, timeout and
network errors.

Registers the plugin in `.claude-plugin/marketplace.json` and adds its
catalog page to both the plugin-catalog index and the VitePress sidebar.

Squash-merge of PR #8 (branch `add-meshy-plugin`), shipped on board
approval. Verified before merge: `npm run docs:build` clean, all 5 plugins
parse from `marketplace.json`, and `meshy.mjs version` honours the
one-JSON-object STDOUT contract.

Co-Authored-By: Daedalus <daedalus@agents.flopbut.local>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Fl0p

Fl0p commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Shipped. Squash-merged to main as bd9080c — merged locally rather than via the GitHub button so the commit keeps an agent identity (author and committer Daedalus), which is why GitHub cannot auto-detect this PR as merged and I am closing it by hand.

The merged tree is byte-identical to this PR's head tree (65152e9). Pre-merge verification on the exact merged content: npm run docs:build clean, all 5 plugins parse from marketplace.json, meshy.mjs version honours the one-JSON-object-on-STDOUT contract, no conflict markers, no secrets in the diff.

The claude-review check failure on this PR is a known repo-wide CI outage (revoked CLAUDE_CODE_OAUTH_TOKEN), not a signal about this change.

One non-blocking nit filed as a fast-follow: extFromUrl derives the extension from the raw URL string instead of the path basename.

@Fl0p Fl0p closed this Aug 22, 2026
@Fl0p Fl0p reopened this Aug 27, 2026
@Fl0p

Fl0p commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Закрываю обратно: работа уже в main squash-коммитом bd9080c («feat(meshy): add Meshy AI 3D generation plugin»). PR был переоткрыт по ошибке — локальный main отставал на 10 коммитов.

@Fl0p Fl0p closed this Aug 27, 2026
@Fl0p
Fl0p deleted the add-meshy-plugin branch August 27, 2026 12:17
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