npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@three-ws/forge

v0.2.0

Published

Text/image/sketch to a textured, rig-ready 3D GLB in one call. The three.ws Forge generation SDK: free TRELLIS lane, pay-per-call USDC tiers over x402, and auto-rigging.

Downloads

249

Readme


@three-ws/forge is the official client for the three.ws Forge — the generation engine behind three.ws/forge. It turns a prompt, a photo, or a sketch into a watertight, textured GLB, and can auto-rig that GLB into an animation-ready humanoid. It wraps the public, auth-free /api/forge endpoint: a free TRELLIS lane on NVIDIA NIM, paid high-detail tiers billed in USDC over x402, and bring-your-own-key geometry backends (Meshy, Tripo, Rodin). It pairs with @three-ws/avatar — Forge makes the model, @three-ws/avatar renders it.

Why

Every "text-to-3D" you find is either a closed playground with no API, or a raw model endpoint that hands you an untextured mesh and leaves rigging, polygon budgets, provider fallback, job polling, and billing to you. Forge is the whole pipeline, done once:

  • One call, a real GLB. forge('a chrome robot') resolves to a hosted, durable .glb URL plus a three.ws viewer link.
  • Free first. Text prompts default to the free NVIDIA NIM / TRELLIS lane — no key, no wallet, no card.
  • Scales with the asset. Three quality tiers (draft → standard → high) map to real polygon budgets and PBR texturing. Pay per call in USDC only when you reach for the paid tiers.
  • Rig-ready. One flag chains generation into auto-rigging, so the output drops straight into the three.ws animation runtime — idle, walk, emotes.

This is the SDK twin of the 3D Studio MCP server, the same engine, exposed as plain functions instead of MCP tools.

Install

npm install @three-ws/forge

Zero runtime dependencies. Works in Node 18+ and the browser (uses fetch). For rendering the result, add @three-ws/avatar.

Quick start

The free lane needs no key:

import { forge } from '@three-ws/forge';

const model = await forge('a chrome robot with neon trim', { tier: 'draft' });

console.log(model.glbUrl);  // → durable https://…/forge/…​.glb
console.log(model.backend); // → which engine actually produced it

glbUrl is a durable CDN URL you can hand straight to a viewer. viewerUrl is the shareable three.ws link, and it is null unless the API attached a creation id (the anonymous free lane usually does not).

A fuller run — high tier, geometry-first, then auto-rig:

import { forge, rig } from '@three-ws/forge';

const model = await forge('a stylized fox, full body, T-pose', {
  tier: 'high',        // draft | standard | high
  path: 'geometry',    // image | geometry | sketch
  providerKey: process.env.MESHY_KEY, // BYOK for the geometry path
});

const rigged = await rig(model.glbUrl); // animation-ready humanoid GLB
console.log(rigged.glbUrl);

From an image or a sketch:

// Photo → 3D
await forge({ images: ['https://three.ws/avatars/thumbs/default.png'], prompt: 'a 3D character' });

// Drawing + a name → geometry (no textures)
await forge({ images: ['data:image/png;base64,…'], prompt: 'a sword', path: 'sketch' });

API

forge(promptOrInput, options?) → Promise<ForgeResult>

Generate a GLB from text, image(s), or a sketch. Accepts a bare prompt string, or an input object.

Input

| Field | Type | Notes | |---|---|---| | prompt | string | Text description. Required for text + sketch paths. | | images | string[] | One or more image URLs / data URIs. Switches to image→3D. | | aspectRatio | string | Reference-image aspect for the image path, e.g. "1:1". |

Options

| Option | Type | Default | Notes | |---|---|---|---| | path | 'image' \| 'geometry' \| 'sketch' | 'image' | How geometry is produced, see How it works. | | tier | 'draft' \| 'standard' \| 'high' | 'standard' | Polygon budget + texture richness. | | backend | string | auto | Force a generation backend. Read the live ids from catalog(). | | providerKey | string | n/a | BYOK key for the geometry path (Meshy/Tripo/Rodin). Overrides the client-level key, and is resent on every poll. | | payWith | 'credits' \| 'x402' | 'credits' | Billing lane, see Pricing. 'x402' switches endpoints. | | pollIntervalMs | number | 2500 | Gap between job polls. | | timeoutMs | number | 180000 | Give up on a job that never finishes. | | headers | object | n/a | Extra headers merged into every request for this call. | | signal | AbortSignal | — | Cancel an in-flight generation. | | onProgress | (job) => void | — | Called on each poll tick with the latest job state. |

Returns ForgeResult

| Field | Type | Notes | |---|---|---| | glbUrl | string | Durable hosted GLB URL. | | viewerUrl | string \| null | Shareable three.ws viewer link. null when the response carried no creation id. | | jobId | string \| null | null when the backend returned synchronously. | | creationId | string \| null | Gallery/creation id, when the lane records one. | | status | 'done' | Resolved jobs are always done; failures throw. | | path / tier / backend | string \| null | What actually produced the mesh. | | etaSeconds | number \| null | Backend ETA at submit time. | | estimatedCredits | number \| null | BYOK vendor spend estimate, where the backend reports one. | | durable | boolean | true once the GLB has been copied to three.ws storage. | | raw | object | The untouched API response, for anything not mapped above. |

forge() submits to POST /api/forge, then polls GET /api/forge?job=<id> until the job is done (the free NVIDIA lane often returns synchronously, with no polling). Failures reject with a typed ThreeWsError.

rig(glbUrl, options?) → Promise<ForgeResult>

Auto-rig an existing GLB into an animation-ready humanoid. Wraps POST /api/forge?action=rig { glb_url }. Returns the same ForgeResult shape with a rigged glbUrl.

catalog() → Promise<Catalog>

Fetch the live tier / backend / cost matrix (GET /api/forge?catalog=1), the single source of truth for prices, ETAs, which backends are configured, and which paths each serves. Use it to render a picker before the user commits.

getJob(jobId, options?) → Promise<ForgeResult>

Read one job's current state without waiting for it to finish, so you can drive your own progress UI, resume after a restart, or poll from a different process than the one that submitted. Wraps GET /api/forge?job=<id> and returns the same shape forge() resolves to, at whatever status the job is in right now. Pass providerKey if the job was submitted with a BYOK key: the API re-resolves the key on every poll and cannot report on the job without it.

const job = await getJob('job_abc', { providerKey: process.env.MESHY_KEY });
console.log(job.status); // 'queued' | 'running' | 'done' | 'failed'

createForge(clientOptions?) → ForgeClient

Bind a base URL, fetch, and credentials once and reuse them. The bare forge / rig / catalog / getJob exports are a shared default client with no options; reach for createForge when you need any of these:

| Client option | Type | Notes | |---|---|---| | baseUrl | string | API origin. Defaults to THREE_WS_BASE_URL, then https://three.ws. | | fetch | typeof fetch | Bring your own, e.g. a payment-aware fetch that settles 402s. | | apiKey | string | Sent as Authorization: Bearer …, for the credits lane. | | providerKey | string | Default BYOK key. A per-call providerKey wins. | | headers | object | Default headers on every request. |

It returns { forge, rig, catalog, getJob } with identical signatures:

import { createForge } from '@three-ws/forge';
import { x402Client } from '@x402/core/client';
import { ExactSvmScheme } from '@x402/svm/exact/client';
import { wrapFetchWithPayment } from '@x402/fetch';
import { createKeyPairSignerFromBytes } from '@solana/kit';
import bs58 from 'bs58';

const signer = await createKeyPairSignerFromBytes(bs58.decode(process.env.SOLANA_SECRET_KEY));
const payer = new x402Client();
payer.register('solana:*', new ExactSvmScheme(signer));

const client = createForge({ fetch: wrapFetchWithPayment(globalThis.fetch, payer) });
const paid = await client.forge('a marble bust', { tier: 'high', payWith: 'x402' });

How it works

Two orthogonal axes describe every request — path (how geometry is produced) and tier (how much budget to spend):

prompt / image / sketch
        │
        ▼
   ┌──────────┐   image     ┌───────────────────────────────┐
   │  path =  ├────────────▶ FLUX/Imagen → TRELLIS·Hunyuan3D │  fast default, free lane
   │          ├─ geometry ─▶ Meshy/Tripo/Rodin native text→mesh│  BYOK, higher detail ceiling
   │          ├─ sketch ───▶ TripoSG-scribble                │  drawing + name → raw geometry
   └──────────┘             └───────────────┬───────────────┘
                                            ▼
                                   textured / untextured GLB
                                            │ (action=rig)
                                            ▼
                                   rigged humanoid GLB
  • image (default) — text is painted into a reference image, then reconstructed to a mesh. Fast, and the free lane lives here.
  • geometry — a native 3D model emits mesh geometry directly, so detail isn't capped by a single synthesized view. Bring your own Meshy, Tripo, or Rodin key.
  • sketch — a drawing plus a prompt naming it drives TripoSG-scribble to raw geometry (no textures).

Backends declare which paths they serve and whether they need a key. If a selected backend isn't configured, Forge returns a clean error state — it never fabricates a model.

Pricing

The browser-facing lane on /api/forge is free where the free engines can serve the request, and asks for an account (credits) where they cannot. There is also a pay-per-call twin at POST /api/x402/forge for agents with a wallet and no account: one USDC payment, one generation, no signup. payWith picks between them.

| Tier | Polygons (target) | PBR | Pay-per-call price | |---|---|---|---| | draft | ~12k | no | $0.05 | | standard | ~30k | no | $0.15 | | high | ~200k | yes | $0.50 |

Prices are flat per call, quoted in USDC (6-decimal atomics) and settled on Solana. They are authoritative in catalog(), so read them at runtime rather than hardcoding.

// Pay-per-call: no account, no key, just a wallet.
const model = await forge('a marble bust', { tier: 'high', payWith: 'x402' });

Because the x402 endpoint picks its own generation lane, it does not accept path, backend, or providerKey; passing one throws invalid_input before any request is sent. Without a payment-aware fetch the first call rejects with PaymentRequiredError, whose accepts carries the challenge (asset, amount, network, payTo) so you can settle it yourself. The live route quotes Solana USDC, so the payment-aware fetch shown under createForge is the one to reach for. Once paid, the job token is polled for free on the shared GET /api/forge?job=<id> endpoint, which is what this SDK does for you.

Errors & edge cases

forge(), rig(), and getJob() reject with a typed ThreeWsError carrying a code, an HTTP status, and the parsed body. HTTP 402 rejects with the PaymentRequiredError subclass, which adds accepts. Both are exported.

| code | HTTP | Meaning | Recovery | |---|---|---|---| | invalid_input | n/a | Rejected client-side before any request: unknown tier/path/payWith, no prompt and no image, or an x402 call carrying path/backend/providerKey. | Fix the call. | | needs_key | 501 | The selected BYOK backend needs your own provider key. | Pass providerKey. | | backend_unconfigured | 501 | That backend has no credential on the server. | Omit backend to auto-route, or pick another. | | unconfigured | 503 | No generation backend is configured at all. | Retry later, or self-host. | | generation_unavailable | 503 | Every eligible backend failed or is down. | Retry, or change path/tier. | | payment_required | 402 | The x402 lane wants payment. accepts carries the challenge. | Settle it, or use a payment-aware fetch. | | three_hold_required | 402 | The paid gate wants a $THREE hold or a per-use payment. | Hold $THREE, or pay the quoted amount. | | insufficient_credits | 402 | The account's credit balance is too low. | Top up, or use payWith: 'x402'. | | unauthorized | 401 | The credits lane needs a signed-in account. | Authenticate, or use payWith: 'x402'. | | rate_limited | 429 | Too many submissions from this IP. | Honour retryAfter on the error. | | timeout | n/a | The job did not finish within timeoutMs. | Raise timeoutMs, or resume with getJob(jobId). | | network_error | n/a | The request never reached the API. | Check connectivity / baseUrl. | | generation_failed | — | The backend produced no usable mesh. | Retry, or change path/backend. |

Every state is designed: a missing key returns needs_key (not a crash), an unconfigured backend returns a 501/503 state (not a fake model). Mirror that in your UI.

import { forge, ThreeWsError, PaymentRequiredError } from '@three-ws/forge';

try {
  await forge('a marble bust', { tier: 'high', payWith: 'x402' });
} catch (err) {
  if (err instanceof PaymentRequiredError) console.log(err.accepts);
  else if (err instanceof ThreeWsError) console.log(err.code, err.status);
  else throw err;
}

Examples

Agent tool (free, zero-config) — the same capability is exposed as the forge_free MCP tool, so an agent can generate 3D with no wallet:

const { glbUrl } = await forge('a low-poly treasure chest', { tier: 'draft' });

Browser → render inline with the sibling viewer:

<script type="module">
  import { forge } from '@three-ws/forge';
  import '@three-ws/avatar/viewer';

  const { glbUrl } = await forge('a friendly robot');
  const el = document.createElement('three-ws-viewer');
  el.setAttribute('src', glbUrl);
  document.body.append(el);
</script>

Generate → rig → animate in one chain, ready for the walk companion:

const base = await forge('a cartoon astronaut, full body', { tier: 'standard' });
const rigged = await rig(base.glbUrl);
// drop rigged.glbUrl into @three-ws/walk or @three-ws/avatar

Related