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

@drawcall/market

v2.1.3

Published

Typed client, dependency resolver, and CLI for the [Drawcall Market](https://market.drawcall.ai) — an asset marketplace for 3D models, textures, animations, audio, environments, flipbooks, and templates.

Readme

@drawcall/market

Typed client, dependency resolver, and CLI for the Drawcall Market — an asset marketplace for 3D models, textures, animations, audio, environments, flipbooks, and templates.

This package is the single source of truth for the Market API surface: the oRPC contract, its Zod schemas, and the asset-type table. It ships a browser-safe programmatic API, Node-only install helpers, and the market CLI.

Install

npm install @drawcall/market

Entry points

| Import | Contents | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | @drawcall/market | Browser-safe: createClient, the contract and its output types, assetTypes, the input schemas, findAsset, findVersion, resolve, assetFileUrl (fileUrl is its deprecated alias), isOpen and registerMarketTools. | | @drawcall/market/node | Node-only: install, upload, getCliClient, listInstalledAssets and createMarketCommand. | | market (bin, npx drawcall market) | The CLI. |

Each name is exported from exactly one entry point.

Usage

createClient returns a fully typed oRPC client for the /api/v1 REST surface. Every output is a Zod schema inside contract (its types are exported), so responses, the OpenAPI document (/api/v1/openapi.json), and MCP output schemas share one definition.

import { assetFileUrl, createClient, findVersion } from '@drawcall/market'

const client = createClient()
// createClient({ baseUrl, fetch, authToken }) overrides the API host, supplies a custom fetch,
// or authenticates reads and uploads.

const list = await client.asset.search({ query: 'robot', type: 'model', limit: 12 })
// `{ items, total }`: page with the page and limit you sent.
const next =
  12 < list.total
    ? await client.asset.search({ query: 'robot', type: 'model', limit: 12, page: 2 })
    : null

// The asset with its version history (oldest first) and whether the Viewer owns it (`viewerOwns`):
const asset = await client.asset.get({ name: 'my-model' })

// One exact version; `findVersion` also reads a bare name's latest version.
const version = await client.assetVersion.get({ name: 'my-model', version: asset.latestVersion })
const url = assetFileUrl(version, version.files[0]) // never build file URLs by hand
const latest = await findVersion(client, { name: 'my-model' }) // null when not fetchable

| Route | HTTP | Returns | | --------------------- | ------------------------------------------------------ | -------------------------------------------------------------- | | asset.search | GET /assets?query&type&page&limit&includeUnapproved | AssetList { items: AssetSummary[], total } | | asset.get | GET /assets/{name} | Asset: an AssetSummary plus versions and viewerOwns | | assetVersion.get | GET /assets/{name}/{version} | AssetVersion: the version, its files and URLs | | assetVersion.upload | PUT /assets/{name}/{version} (multipart, zip ≤ 1 GB) | 201 with the created AssetVersion; requires an authToken |

total is exact for browse; for a text search it counts the ranked candidates, a bounded window that widens as deeper pages are requested.

Search lists public, approved assets; owners and admins asking for includeUnapproved also see their own unapproved and private ones. Approval gates listing only: asset.get and assetVersion.get read every version that is not private — public (approved or not) and unlisted — for anyone. Private assets are fetchable by their owner and admins only. Reads of a missing or unfetchable asset fail with the typed NOT_FOUND error, checkable with isDefinedError from @orpc/client; upload failures are typed too: ASSET_NAME_TAKEN, VERSION_EXISTS and TYPE_MISMATCH (409), INVALID_ASSET_ZIP (400), PRIVATE_NOT_ENTITLED (403, private without the market:private entitlement), FORBIDDEN (403, a delegated token instead of a session) and UNAUTHORIZED (401). Manifests of open versions are shared by every caller for five minutes (public, max-age=300); private ones are never stored.

The server still answers the routes older clients call — GET /assets?refs=…, POST /assets, GET /types, POST /installs, and GET /me — with Deprecation and Sunset headers. They are not part of this contract.

Output type fields are open strings, so a client keeps working when the Market adds a type. assetTypes holds what this release knows about each type: label, Type Page title (typePageTitle; catalogTitle is its deprecated alias), Type Slug, Type Folder, description, search and install guidance, whether it ships a whole project (a template: pack reads its package.json and omits installed dependency files, install writes its README and does not save it), and whether uploads store a preview. isAssetType narrows a server-reported type.

Previews

previewUrl is an image URL, or null when the asset has no preview (see assetTypes[type].hasPreview). For an open version — isOpen(version): every version that is not private — drop it straight into an <img src>. Files of private versions download only with their owner's Authorization: Bearer token.

resolve — dependency resolution

Resolve assets and their transitive asset, npm, and skill dependencies to an installable plan:

import { createClient, resolve } from '@drawcall/market'

const plan = await resolve(createClient(), [{ name: 'my-model', range: '^1.0.0' }])

plan.assets // one AssetVersion per asset: the highest fetchable version in every range on it
plan.npmDependencies // merged npm ranges
plan.skillDependencies // merged skill sources

The Node-only filesystem APIs are available from @drawcall/market/node. install downloads and writes resolved assets (pass authToken to install private ones); listInstalledAssets reads the declared assetDependencies (name, range, aliases) from the nearest package.json; upload(client, source, { name, type, description }) is what market upload runs: it packs a directory, a .zip path or zip bytes, refuses a name owned by someone else, answers { version, unchanged: true } when the latest version holds the same files, description and dependencies, and otherwise publishes the next patch version once; getCliClient uses DRAWCALL_AUTH_TOKEN or the saved Drawcall login when private assets must resolve.

import { resolve } from '@drawcall/market'
import { getCliClient, install, listInstalledAssets } from '@drawcall/market/node'

const { assets } = await listInstalledAssets(process.cwd())
const { client } = await getCliClient()
const plan = await resolve(
  client,
  assets.map(({ name, range }) => ({ name, range })),
)
await install(client, plan)

CLI

npx drawcall market install my-model        # resolve + install by name
npx drawcall market types                   # asset types and search guidance (offline)
npx drawcall market list                    # list locally installed assets
npx drawcall market search robot --type model
npx drawcall market preview [email protected]  # save that version's preview image
npx drawcall market urls [email protected]     # remote file base URL, subpaths, and preview URL
npx drawcall market pack ./my-model --type model --out ./my-model.zip
npx drawcall market upload my-model ./my-model "A robot" --type model
npx drawcall auth login                     # device-authorization sign-in

GLTS assets are models and use --type model. Include the .glts source and all companion files in the ZIP at their intended install paths (for example public/model/tree.glts and public/model/parts/branch.glts). For composed assets with multiple GLTS files, name exactly one entry point index.glts. Declare runtime packages with --npm as needed. Market renders a preview with GLTSLoader and GLTSRenderer in Cloudflare Browser Rendering. Authored preview staging and default cameras are honored; scenes without a camera are framed automatically. GLTS source is displayed as text in the file browser.

npx drawcall market upload procedural-tree ./tree.zip "A procedural tree" --type model --npm @drawcall/glts --npm three

Drawcall Auth Tokens are managed by @drawcall/auth and shared with other Drawcall CLIs. --api <url> or MARKET_API_URL selects another API host for one call.

types, list, and pack of non-template assets work offline; install, search, preview, and urls work without auth; upload requires npx drawcall auth login. After an install, the CLI prints each installed type's usage from the asset-type table. search shows 5 results unless --limit (1-100) asks for more, prints More results: --page <n> when another page exists, and --json prints the API's { items, total }.

urls performs no filesystem writes. Its output follows the same compact asset-and-files layout as install; the base plus each indented file subpath forms a fetch-ready asset file URL:

URLs:
- [email protected]
  base: https://market.drawcallcontent.com/[email protected]/
  files:
    public/models/my-model.glb
  preview: https://market.drawcallcontent.com/previews/[email protected]

Every asset that is not private installs by name for anyone, approved or not; a private asset installs for its owner after npx drawcall auth login, and install --redirect refuses it because a browser cannot send the sign-in its files need. An explicit name@range is saved to assetDependencies as given, a bare name as ^ the installed version.

upload --access public|unlisted|private sets the asset's access. Without it, a new asset is unlisted and an existing one keeps its access; private needs the market:private entitlement.

pack creates the same asset zip that upload sends from a project directory or a .zip file, filtered by its .gitignore files; --type decides whether it packs a whole project (templates), and only a project's pack reads the API (--api) for its dependencies' file hashes. Pack never writes to the source: the zip's package.json records the aliases of renamed dependency files, and pack says when sync would record them in the project. upload calls the shared pack step internally, then publishes; it refuses a name owned by someone else and skips publishing when the packed files (path and md5), description and dependencies match the latest version.

list is an offline local inventory command. It reads assetDependencies from the nearest package.json and prints each declared asset with its range and file aliases.

Run npx drawcall market skill to print the agent workflow guidance, or npx drawcall market --help for the full command list.

The Commander implementation is available for aggregate CLIs:

import { createMarketCommand } from '@drawcall/market/node'

program.addCommand(createMarketCommand())

MCP

The MCP tools follow the same concepts as types, search, and preview, without exposing CLI-only filesystem operations such as install or pack. Their outputs are the contract's schemas, parsed before they are returned: search_market returns the AssetList, get_market_asset returns { asset, version }. Pass authToken in the options to read previews of the caller's private assets:

import { createClient, registerMarketTools } from '@drawcall/market'
import { McpServer } from '@modelcontextprotocol/server'

const client = createClient({ authToken: process.env.DRAWCALL_AUTH_TOKEN })
const server = new McpServer({ name: 'drawcall-mcp', version: '1.0.0' })

registerMarketTools(server, client, { authToken: process.env.DRAWCALL_AUTH_TOKEN })

The host owns server identity, authentication, and transport. The MCP SDK is an optional peer dependency and is required only when mounting these tools.