@helix3/helix-cli
v0.1.14-helix3.213
Published
helix — the HELIX creator CLI: scaffold, validate, and publish Instant Worlds
Downloads
20,134
Readme
@hypersoniclabs/helix-cli
helix — the HELIX creator CLI: scaffold, validate, and publish Instant Worlds to the HELIX
backend. It shares bundle validation with the server via @hypersoniclabs/helix-manifest, so "validate passed but
publish failed" can't happen for contract reasons.
Architecture boundary
All creator feature and operational logic, validation, file mutation, network behavior, and tests live here. MCP packages may expose transport schemas, resources, instructions, and thin delegation to these commands, but must not reimplement this logic.
Install
npm install -g @hypersoniclabs/helix-cliAuthentication
helix login opens your browser to the website's /cli-login page; you sign in there and the CLI
captures a client token via a one-shot loopback redirect. For CI/headless, pass a token directly:
helix login --token <token> (mint one in the Creator Hub → Settings → Client Tokens).
The token is stored in ~/.helix/credentials.json (mode 600). It's sent as
Authorization: Bearer <token> on publish calls. helix login validates it against
GET /api/v1/users/me before saving and reads the token's exp so the CLI knows when it lapses.
The backend & website default to the build's environment: the published
@hypersoniclabs/*CLI targets production (api.helixgame.com/helixgame.com); the staging@helixdev/*CLI targets the staging backend (helix-backend-staging.up.railway.app) + staging website (next.helixgame.com). Override per run with--api-url/HELIX_API_URLand--web-url/HELIX_WEB_URL(e.g. a local/dev backend).
Commands
| Command | What it does |
|---|---|
| helix bridge detect\|inspect\|plan\|import\|validate\|doctor | Universal plugin-driven intake for assets, characters, animations, scenes, and engine projects. Produces deterministic plans, HELIX-native outputs, and checksummed provenance. See docs/bridge.md. |
| helix bridge plugins create\|test\|list\|inspect\|install\|remove | Build and manage local/community Bridge Plugins using the versioned capability and permission contract. |
| helix login [--api-url <url>] [--web-url <url>] [--token <t>] [--no-browser] | Sign in via the browser (/cli-login), or --token for CI/headless. Stores the client token. |
| helix logout | Remove the stored token. |
| helix whoami | Show current login status. |
| helix init <dir> [--kind character\|multiplayer\|scene] | Scaffold a complete, build-ready Vite + TypeScript world. --kind scene emits a multiplayer world whose ENVIRONMENT is a sealed HELIX Scene v2 document — the starter document is compiled offline into public/scene/mall/, pinned in public/helix.json's scene field, kept out of the bundle by public/.helixignore, and lit by public/helix.visuals.json through the scene + visual system pins. |
| helix install [--update] | Resolve a world's systems/abilities pins (a v0.2 or v0.3 manifest) → materialize the modules, the three import map, and helix.runtime.ts. |
| helix validate [dir] | Validate a bundle locally — the exact rules the server enforces. |
| helix publish [dir] [--thumbnail <file>] [--upload-source] [--source-dir <path>] | Validate → resolve/create the world → upload files → finalize → set the cover image → print the play URL. --upload-source additionally sends the project's source through a separate private channel so the world can be edited on the website later — off by default. |
| helix list | List your worlds. |
| helix item list-slots [--json] | Print the Character-Creator vocabulary a wearable declares: the cosmetic slot tags, and the genders. |
| helix item publish <mesh.glb> --title <t> [--collectible-supply <n>] [initial distribution flags] [--dry-run\|--quote] | Publish a Standard item by default, or one fixed-supply Collectible. The item definition is separate from acquisition routes. |
| helix item distribution create\|list\|update\|disable … | Manage Marketplace/World Claim or Buy routes without republishing the item or paying another publish fee. |
| helix device create <dir> --id <slug> --title <t> --model <mesh.glb> --template standing-desk\|clock\|television | Scaffold a GLB-backed helix.device/1 definition, including the interactive TV/media-player template. There is intentionally no website Device editor. |
| helix device validate [dir] [--json] | Validate the definition, GLB bytes and named-node bindings, typed state/controls, capabilities, and Home+World compatibility. |
| helix device test [dir] [--mode single\|multiplayer\|both] [--json] | Exercise every interaction, persistence remount, and deterministic one-/two-client authority convergence. |
| helix preview-video <slug> <youtube-url> | Set or clear a published world's YouTube preview. |
| helix thumbnail set <slug> <file> | Upload a thumbnail or preview image through the CLI-owned media path. |
| helix assets search\|get\|versions\|track\|install\|update | Search the typed Vault with explainable ranking; inspect/manage annotations and immutable versions; install by durable ID with verified SHA-256 receipts. Material installs default to lean runtime KTX2 maps; use --material-renditions source for source PNGs or all for both families. |
| helix assets start\|status\|generate\|resume | Start or poll generation without duplicates. Props/characters use the main Dreamer path; standalone assets use the shared broker and report the auto-published vaultAssetId. generate and resume block waiting for the job (see --timeout/--poll-interval below). |
| helix assets voices [--search <text>] [--category premade\|professional] [--page-size <n>] [--page-token <token>] [--json] | Discover safe text-to-speech voices through the authenticated HELIX catalog, including language, labels, preview, default/recommended selection, and pagination. |
| helix assets generate-image\|generate-material\|generate-audio\|generate-environment | Run the supported standalone generation adapters. Material is a validated PBR bundle (1K default, explicit 2K); splats declare object/environment scope. Animation remains an explicit unavailable boundary. |
| helix assets start-reference\|generate-reference | Start or fully drive a character job from a local four-view PNG/JPEG/WebP. The backend validates and reuses the exact sheet; it skips image generation while preserving mesh, texture, rig, LOD, thumbnail, billing, and Vault publication. generate-reference blocks waiting for the job (see --timeout/--poll-interval below). |
| helix assets generate\|generate-reference\|resume [--timeout <ms>] [--poll-interval <ms>] | These three drive a Dreamer prop/character job to completion (concept → mesh → texture → rig → publish) and block until it finishes. --timeout bounds how long the CLI process waits before giving up (default 1200000ms / 20min, unchanged if omitted); --poll-interval sets how often it checks job status while waiting (default 2000ms). A timeout is a CLIENT-SIDE giveup, not a job failure — the Dreamer job keeps running server-side. The error prints the exact helix assets resume <job-id> command to check on or continue it; on a genuinely slow stage (e.g. mesh generation), rerun with a larger --timeout, or just poll separately with helix assets status <job-id> --pipeline dreamer. |
| helix assets materials [--resolution <res>] [--no-resolve] [--human] | Discover the small built-in material palette. --resolution keeps only materials carrying that texture resolution. Each entry also carries the Scene v2 vault pin and the continuum package ref; --no-resolve skips those lookups. |
| helix assets material <id> [--resolution <res>] [--no-resolve] [--human] | Resolve one material to its immutable map URLs at the resolution you pick (default: the pack's own default), plus its vault pin and continuum ref. |
| helix assets credits\|check-loaders | Render asset credits from provenance and reject models requiring unsupported loaders. |
| helix character import <body.glb> [--map <json>] [--fit source\|canonical] [--provenance <json>] | Convert one self-contained third-party humanoid through the public helix-humanoid@1 pipeline. The command rejects external/remote resources and resource bombs before parsing, generates bounded LODs, and emits a source/license/transformation/checksum receipt beside the output. --fit canonical aligns the rest skeleton/proportions; it does not prove that Base Male-targeted garments fit a different body surface. |
| helix character retarget-animation <clip.fbx\|clip.glb> | Retarget a raw Mixamo/Meshy clip directly to helix-humanoid@1. FBX conversion is deterministic and runs inside the CLI; no hidden Blender/DCC pre-export is required. |
| helix world audit\|source-audit\|perf-gate\|prove-live | Run the blocking world QA, performance, and deployed-build identity gates. |
| helix world scene-source inventory\|compile <helsinki\|bistro> <archive> | Verify and safely inventory a known CC BY source archive, then compile deterministic local proxy/detail GLBs and an existing-schema Scene v2 package. |
| helix world scene-source publish <world-id> <package-dir> [--publish] | Validate the Scene v2 package and print its multipart build receipt. Execution remains dry-run-only until backend private-pin entitlement lands; --publish currently fails closed, and access control never changes CC BY rights. |
| helix continuum publish-source-map\|publish-scratch\|publish-package | Resume exact-CID object adoption/ingestion, Package lease/seal, optional explicitly authorized Listing, and WorldRelease publication through one creator-owned pipeline. |
| helix continuum receipt <job-id> | Read the sanitized durable receipt for a completed Continuum publication. |
| helix unreal init\|validate\|doctor\|package [project] | Create and gate a shared-runtime or native-game publish manifest, check optional editor MCP, and assemble a local upload-shaped package. |
| helix unreal publish <candidate> --world <slug> | Upload a cooked Unreal world to HELIX: verify the candidate, presign, upload every artifact, finalize, and promote it as the world's active build. |
| helix doctor [--project <dir>] | Print the environment + whether the @helix toolchain (CLI/MCP/SDK/manifest) is current. |
Unreal runtime projects
helix unreal init <project> --mode shared-runtime|native-game creates helix.unreal.json.
Shared-runtime projects ship cooked content/Blueprints against a pinned HELIX runtime; native-game
projects ship separate signed client/server builds and may use custom C++ and plugins. Both require
Unreal 5.8, the HelixSDK plugin, and HelixPlatform identity/session integration.
Networking is explicit: unreal-native, helixnet, custom, or none. Only helixnet requires
contract v21; blank Unreal projects using arbitrary GameMode/Pawn classes and native networking do
not need Lyra Experiences, Colyseus, or the HelixNet contract.
helix unreal validate is the publish gate and does not require MCP. helix unreal doctor adds the
loopback/editor-only Unreal MCP authoring checks. helix unreal package copies validated cooked or
native artifacts into a local checksummed staging directory — it is a local assembly step, not a
publish.
Publishing a cooked world
helix unreal publish <candidate> --world <slug-or-id> takes the directory the Unreal
HelixWorldPublish commandlet writes — world-build-manifest.json, its .sha256 receipt,
cook-plan.json, and artifacts/<role>/… — and gets it live in one command:
helix unreal publish Saved/HelixPublish --world my-world --max-players 32 --visibility unlistedIt verifies the candidate against the bytes on disk (every digest recomputed — a manifest is a
claim, not evidence), registers the Build and presigns each artifact, streams the uploads,
finalizes so the server re-verifies every byte by size and SHA-256 and countersigns the manifest,
then promotes the Build as the world's active release. --no-promote stops after verification;
--verify-delivery additionally checks what a client would actually download.
The idempotency key defaults to the cooked manifest's own digest, so re-running after a dropped connection resumes the same Build rather than minting a second one.
Only shared-runtime worlds publish this way. A World Build may not contain creator
executables, so native-game builds are a separate distribution path.
Working in a git worktree? Run this first
npm run check:dev-linkspackage.json links its sibling HELIX packages by relative path
(file:../helix-manifest, file:../helix-web-sdk). That is right in the canonical
layout, where the siblings sit beside this clone — but in a git worktree ../ is
the worktree parent, so the links resolve to whatever happens to be there: a checkout
parked on an older branch, or nothing at all.
The symptom is not "broken dev link". It is a branch that looks broken: tsc
reporting has no exported member … in files your change never touched, and whole
suites failing at import (could not find the dev shell in @hypersoniclabs/helix-sdk).
People have concluded a PR was broken on the strength of exactly this.
check:dev-links names which link is wrong, which imported symbols the linked sibling
does not export, and how to fix it without repointing a shared directory other
worktrees resolve through.
Publish flow
Platform system updates
helix install records platform-owned systems in public/helix.runtime.json.
Compatible ranges such as ^0.2 are externalized and resolved to an active,
immutable catalog module when a world launches, so a compatible platform fix can
reach an already-published world without creator edits or republishing. A new
runtime version reaches no world until the platform deliberately publishes it to
the catalog and it goes active there — building or merging one changes nothing on
its own, and there is no rolling "latest". Exact
versions such as 0.2.43 map directly to the immutable catalog URL and remain
pinned. A custom Vite config is never rewritten: install reports
legacy-bundled until the creator explicitly externalizes @helix/*.
helix validate, helix publish, and the publish API refuse that legacy mode,
so every newly accepted system-bearing web World is either safely compatible or
explicitly pinned. latest and * are not accepted publish ranges because they
can cross a compatibility boundary.
The platform runtime is on three 0.185.1. The version is a property of the
pinned system — humanoid-character@^0.3 resolves to an envelope declaring it —
and helix install bakes it into the import map immutably, so an already-published
world keeps running whatever it baked. A world behind that line still publishes:
helix validate and helix publish only warn, naming the upgrade path (re-pin
^0.3, helix install --update, rebuild, republish), and helix doctor reports it
(and a local devDep three that has drifted off the platform runtime) before you build.
That update contract is deliberately narrower than "the SDK updates itself":
| World dependency | What the built bytes contain | What can advance without republishing |
| --- | --- | --- |
| Platform systems (@helix/humanoid-character, @helix/engine-core and declared subpaths) with a compatible range | Bare imports plus a generated import-map entry | The compatible catalog target on the next fresh launch |
| Platform systems with an exact version | Bare imports plus the exact immutable catalog URL | Nothing; the pin stays byte-identical |
| Creator SDK (@helix3/helix-sdk) | The SDK module graph bundled into the world's own JavaScript | Nothing; SDK changes require rebuilding and republishing the world |
| Installed abilities (public/helix_modules/*) | Verified ability code/assets copied into the world build and indexed by installed.json | Nothing; update/install and republish are explicit creator actions |
| Creator game code and assets | The world's own immutable build files | Nothing; the platform never rewrites a published creator bundle |
The generated import map, helix.runtime.json, and
helix_modules/installed.json are the authoritative way to inspect which side
of this boundary a particular build uses. Browser or proxy caching may serve a
previous compatible resolver response during a transient outage; the descriptor's
lastKnownGood field is provenance metadata, not an executable in-world fallback.
Without a cached resolver response, a cold resolver failure fails closed.
Native features remain individually overridable without pinning the whole
character runtime. The character system's native avatar features — carried
emotes and the radial wheel (including its Customize door), x-ray, pointing,
sitting, the avatar camera, the two camera legs, and rendering each player's
universal avatar — are constructed by the character chassis itself, so every
character World has them whether it was built on Character or on
CharacterMultiplayer, and a World published today receives ones added later on
its next fresh launch.
A World refuses what it does not want by NAME, in the character config it passes
at create time — not in helix.json, and nothing here is a publish-time concern:
config: { character: { native: { disabled: ['xray', 'pointing'] } } }Ids: emotes, emote-wheel, xray, pointing, sitting, avatar-camera,
camera-first-person, camera-third-person, universal-avatar. It is a
blocklist, never an allow-list, for the same reason this whole delivery
contract exists: an allow-list would freeze every already-published World out of
every feature added afterwards. A refusal also removes the feature's key,
controller button, touch pad and prompt; universal-avatar is the top switch and
takes emotes, the wheel, x-ray, pointing, sitting and the avatar camera with it
(the camera legs survive — a World still needs a camera); refusing BOTH camera
legs throws at create; and an unrecognised id is kept with a warning rather than
rejected, because it may belong to a newer runtime. The older switches still
work and route into the same resolver: CharacterMultiplayer.create's
emotes: false, emoteWheel: false and universalAvatars: false, and the
config keys character.emote.enabled, character.emote.wheel,
character.xray.enabled, character.universalAvatar.enabled. (localControl:
false is not one of these — it declares a spectator body the World drives its
own camera and input for.) Every other compatible native fix continues to update
normally.
This surface needs @helix/humanoid-character 0.2.54 or newer; an older
runtime ignores character.native.disabled silently, so confirm against the
resolved system's config schema rather than assuming. The full creator reference
is the character world recipe in the HELIX MCP docs
(read_doc({ name: "character-world" }), section 8g); the authoritative id list
and defaults are that config schema, readable from the installed system
manifest.
The runtime descriptor is mandatory for bundles that declare systems. It makes the delivery boundary inspectable by the CLI, MCP, backend, and fleet census; it does not execute code or silently rewrite an old minified bundle.
helix publish maps directly onto the backend's Instant World API:
checkBundlevalidateshelix.json+ files via@hypersoniclabs/helix-manifest.GET /api/v1/instant-worlds/me→ reuse the world with the manifest's slug, elsePOST /api/v1/instant-worlds.POST /api/v1/instant-worlds/:id/builds→ presigned upload tickets.- Files PUT directly to storage (6 concurrent), content-type/length bound to each ticket.
POST /api/v1/instant-worlds/:id/builds/:buildId/finalize→ byte-exact verify, activate, publish.GET /api/v1/instant-worlds/:slug→ the play URL.
helix.json's fields are the shared manifest's, not the CLI's — including the optional v0.3 scene
pin ({kind, id, revision, integrity:{sha256}}, or a continuum-scene package triple) naming the
sealed Scene document a world streams as its environment; integrity.sha256 is the authority and
id/revision are display text. Reference: docs/helix-scene-v2.md in @hypersoniclabs/helix-manifest.
.helixignore
An optional gitignore-style exclude list read at the bundle root. Matching paths are left out of
the upload and out of the 50 MB / 200-file budgets, and the file itself is never uploaded.
helix validate and helix publish print excluded N path(s) via .helixignore when it drops anything.
- Blank lines and
#comments are ignored. scene/— a trailing slash matches a directory and everything under it.scene/pin.json— a plain path, relative to the bundle root, forward slashes.*.mapand**/notes.txt—*stays inside one path segment,**/crosses segments.!keep.jsonre-includes: rules apply in order and the last match wins. As in git, nothing under an excluded directory can be re-included.
It must land at the bundle root, so a Vite world keeps it at public/.helixignore and the build copies
it into dist/. node_modules, .git, .vscode, .idea and screenshots are dropped regardless and
need no rule.
The case it exists for: a @helix/scene world keeps its dev-only Scene document and source GLBs under
public/scene/, which Vite copies verbatim. One scene/ line keeps megabytes of authoring bytes out of
every player's download.
Opt-in source upload
A published world ships only its built bundle, so there is nothing on the server to edit later.
--upload-source adds a second, separate upload that fixes that:
POST /api/v1/instant-worlds/:id/source→ a presigned PUT into a private bucket (this call also adopts the world into a project, which is the key its future workspace hangs from).helix-source.tar.gzPUT withcache-control: no-store.POST /api/v1/instant-worlds/:id/source/finalize→ the server verifies the stored byte count equals the declared one, then records the version.
- Off by default. Source is never sent unless you pass the flag. Not a prompt — an interactive confirm would break CI and agent-driven publishes.
- What goes in: the project directory, honouring
.gitignore, always excludingnode_modules,.git,distand.vite— the same exclude set the website's own workspace checkpoints use. - Which directory:
[dir]is the BUILT bundle, so the source defaults to its parent. Override with--source-dir <path>. - Never part of the build. The bundle's content-type allowlist rejects archives, raw
.tsis excluded from a bundle by design, the bundle size budget is the playable budget, and every build file lands on a public CDN path with a year-long immutable cache. Source uses its own private channel for exactly that reason. - Limit: 128 MiB compressed, checked locally before anything is uploaded.
Before upload, module-based multiplayer worlds are checked for server realizability: every bank must
be published with the declared checksum and the shared manifest validator must accept all module
budgets under the 100,000 tick-node ceiling. Fix the named module/bank when this gate refuses a
publish. For exceptional recovery only, --skip-realizability-check bypasses this check; flat-v0
worlds are unaffected.
The programmatic surface (publishWorld, checkBundle, whoAmI, …) is exported for other CLI
modules. Agent integrations should instruct or delegate to the helix commands above rather than
reimplementing operational behavior in a transport package.
Vault lifecycle
Use the durable Vault UUID as the handle. Search results include independent lexical, hard-filter,
measured-performance, reuse, and availability explanations; they do not collapse those signals into
a made-up score. helix assets install resolves an immutable version, follows the /download
indirection, verifies its server checksum and byte count, installs related material maps, and writes
public/helix.assets.json. Material installs keep the descriptor and default to the runtime KTX2
map family only; choose source PNGs with --material-renditions source, or both families with
--material-renditions all. Reinstalling with a different selection removes the stale opposite
family. Non-material assets keep their existing related-artifact behavior. For a reproducible agent build, pass --asset-version,
--checksum-sha256, and --size-bytes together; a partial pin or any metadata/download mismatch
fails before a provenance receipt is written.
Generation is source-agnostic at the backend:
propandcharacterstart through the main Dreamer route because they use its verified universal-item flow.image,material,audio, andgaussian_splatstart through the shared asset route. Gaussian splats must declareobjectorenvironmentscope.animationfails locally withCAPABILITY_UNAVAILABLE; it never starts a chargeable job.
Run helix assets status <job-id> --pipeline dreamer|asset to poll an existing job. A successful
default-on generation reports vaultAssetId and vaultAutoPublish: "published"; an explicit
creator opt-out reports "disabled".
For .helix-scene.json, the open Vault catalog is not the scene-local budget. Reuse a small
material palette and instance repeated assets. The canonical ceilings are:
| Profile | Unique materials | Draw calls | Decoded texture memory | Triangles | Particles | | --- | ---: | ---: | ---: | ---: | ---: | | mobile | 48 | 300 | 256 MiB | 750,000 | 5,000 | | desktop | 128 | 1,200 | 1 GiB | 4,000,000 | 50,000 | | cinematic | 256 | 4,000 | 4 GiB | 15,000,000 | 250,000 |
These are publish-contract ceilings, not frame-rate promises; world runtime QA still applies.
The lifecycle acceptance harness intentionally has no CLI-local demo. Point it
at a built world in helix-web-demo-worlds that consumes the published SDK and
humanoid runtime:
npm run qa:vault-lifecycle -- \
--world-template /path/to/helix-web-demo-worlds/worlds/sdk-showcasePublishing is an explicit release-only action and additionally requires a new approved slug:
npm run qa:vault-lifecycle -- \
--world-template /path/to/helix-web-demo-worlds/worlds/sdk-showcase \
--publish --world-slug ch1146-vault-lifecycle-qa-<unique-suffix>The publish receipt requires one usage increment, one world association, and an idempotent second publish with no additional increment.
Audio generation
All three audio modes use the same authenticated Dreamer route and automatically publish the
completed MP3 to Vault. The command waits up to 30 minutes so a ten-minute music request has enough
time to render, ingest, and publish. It prints the durable vaultAssetId, measured Spark charge,
provider cost, and server-stamped billingEvidence. It does not download another local copy unless
--output is supplied.
# Sound effect
helix assets generate-audio "heavy metal door slamming shut" \
--mode sound_effect --duration-seconds 3 --prompt-influence 0.4 --output door.mp3
# Music
helix assets generate-audio "hopeful orchestral exploration theme" \
--mode music --duration-seconds 180 --force-instrumental
# Text to speech
helix assets voices --search narrator --page-size 10
helix assets generate-audio --mode text_to_speech \
--text "Welcome to HELIX." --voice-id <id-from-catalog> \
--language-code en --stability 0.5 --speaker-boostUse the opaque --page-token printed by a page to continue. Voice cloning and
voice administration are deliberately not exposed.
Use a deterministic four-view sheet when you already have approved FRONT, BACK, LEFT, and RIGHT character art and do not want Dreamer to generate another image:
helix assets generate-reference ./character-four-views.png \
"compact friendly service robot, game-ready humanoid proportions" \
--title "Service Robot" \
--target-polycount 12000The reference endpoint is character-only. The file is image-guarded, normalized, checksum-bound to the job, semantically validated, and mirrored before approval. Retries reuse those same bytes and never invoke or charge the image provider.
Material texture resolution
A platform material can ship more than one texture resolution. --resolution picks which one you
get; without it you get the pack's declared default, byte-for-byte what every earlier CLI returned.
helix assets material brick-block # the pack's default resolution
helix assets material brick-block --resolution 2k # the 2K variant's map URLs
helix assets material brick-block --resolution 2048 # same thing — numeric spelling
helix assets materials --resolution 2k --human # only materials that carry 2KAccepted values are the keys the catalog itself declares (1k, 2k, …), case-insensitively, plus
the numeric aliases 1024/2048, which map onto the variant authored at that pixel edge.
Both commands print JSON by default (agents and the MCP delegation parse it); --human prints a
readable summary instead. Either way the output states which resolution was resolved and which
ones the material offers, under resolution:
"resolution": {
"requested": "2k", "resolved": "2k", "pixels": 2048, "default": "1k",
"available": [{ "key": "1k", "pixels": 1024 }, { "key": "2k", "pixels": 2048 }],
"applicable": true
}Three rules make this safe to rely on:
- No silent fallback, ever. Asking for a resolution the material does not carry is an error that names the ones it does. You never receive a different resolution than the one you asked for.
- Old packs keep working. A catalog with no
resolutionsblock (schemaVersion: 1) is treated as having exactly one resolution, keyeddefault, derived from its existingmaps. Such a pack never claims to be "1k" — it does not say how big its textures are, so neither do we. - Procedural materials are not an error.
glassandprocedural_watercarry no texture maps; a resolution request against them is ignored and the output says so inresolution.note.
helix assets install has no resolution flag: Vault related-artifact roles are semantic
(source.albedo, runtime.ktx2.normal, sky.backdrop), never resolution-tagged, so a filter there
would filter nothing.
Pinning a platform material — the Vault pin and the Continuum ref
A catalog id such as white-marble is a pack-local name. No scene format accepts it, so both
material commands also return the two identities an author actually writes down:
"vault": { "assetId": "6d7ac215-…", "revision": 2 },
"continuum": {
"packageId": "3970d36f-9374-56f9-9be8-576bfa70fa8d",
"listingId": "33a5a6bc-…",
"version": "1.0.1",
"manifestCid": "cid:sha256:b7a3206f…"
}vault is the Scene v2 identity. A materials[] entry must resolve to a material resource
whose source is an immutable Vault pin (helix-manifest/src/scene-v2.ts:1627-1628), so the pin is
written on the resource and the material points at it:
"resources": [{
"id": "res-white-marble",
"kind": "material",
"source": { "kind": "vault", "assetId": "6d7ac215-…", "revision": 2 },
"integrity": { "sha256": "…" },
"mimeType": "application/vnd.helix.material+json"
}],
"materials": [{
"id": "mat-white-marble",
"resource": "res-white-marble",
"defaults": { "tint": "#ffffff", "repeatPerMeter": 1, "roughnessScale": 1, "metalnessScale": 1 }
}]continuum is the semantic-scene identity. A sceneDocument.materialBindings[].binding
(helix-manifest/src/continuum/material.ts:111-136) references a Package Version and never owns
maps:
{
"id": "white-marble",
"binding": {
"schema": "helix.continuum.material-binding/1",
"material": { "packageId": "3970d36f-…", "version": "1.0.1", "manifestCid": "cid:sha256:…" },
"slot": "white-marble",
"tint": [1, 1, 1, 1],
"opacity": 1,
"roughnessScale": 1, "metallicScale": 1, "normalScale": 1, "emissiveScale": 1,
"uv": { "set": 0, "offset": [0, 0], "scale": [1, 1], "rotationDeg": 0, "tiling": [1, 1] }
}
}The same { packageId, version, manifestCid } triple is what a scratch descriptor's
source.materials[].sourcePackage and every entry of compilation.dependencyClosures[] require.
How each field is resolved, and what a null means:
| Field | Route | Login |
| --- | --- | --- |
| vault | GET /api/v1/vault/assets?q=platform-material:<id>&kind=material&match=narrow | not needed — the route is public and the CLI sends no token |
| continuum.listingId / version / manifestCid | GET /api/v1/vault/catalog?packageType=material&q=<id, hyphens as spaces> | not needed — public |
continuum.packageIdis a pure function of the catalog id — no lookup, always present, stable forever:deterministicContinuumUuid("helix.material.catalog/1\0" + id). The catalog page is filtered on that derived id, never on a title, so a same-named Listing cannot become a wrong pin.vault: nullmeans the platform tag matched zero or several assets — exactly one match is the only pinnable state (the same rule the Home scene loader applies). Several also setsvaultWarning: "ambiguous: N matches".version: nullmeans no sealed public Package Version for that id on this lane.- A failed lookup never masquerades as an absence: it sets
vaultWarning/continuumWarningtolookup failed: …and leaves the rest of the catalog intact. --no-resolvemakes no request at all. Entries then carry only the derivedcontinuum.packageIdand novaultkey — absent, notnull, so "not looked up" cannot be read as "not there".- Lookups run at most 8 in flight and are cached for the life of the process.
Item publish flow
helix item publish creates an item definition. It defaults to Standard: unlimited issuance,
no public serial, and no resale. --collectible-supply N is the only edition selector: it fixes a
positive supply, assigns public serials, makes instances Marketplace-resellable, and reserves
serial #1 for the creator. There are no separate tradable, serial, giftable, personal, or
discoverability switches.
- Local validation, before anything leaves the machine: the
--kind, the Character-Creator--slotand--gender(both required for a wearable, exact-match), the title/slug/tag limits, and the GLB container magic.--dry-runstops here and prints exactly what would be sent.
--kind accepts wearable, avatar, add_on, vehicle, prop, and home — prop and home are the current
product names (see helixgame.com) for what the API still calls home_item and home_shell; both
spellings work identically and helix item publish --kind prop … sends the exact same
"kind":"home_item" on the wire as --kind home_item … does. This is a CLI-vocabulary alias only —
it is unrelated to the backend's separate prop universal-item kind (art/collectible items).
An add_on declares --fits and optional --ops; each flag accepts inline JSON or a JSON file.
A base that hosts add-ons declares --host-kind and a measured --host-surface the same way.
The JSON keys are sent unchanged as the backend's fits, ops, hostKind, and hostSurface
payload fields. --dry-run --json is the offline contract check before uploading anything.
The published host packs are vehicle and character; a character host must be an avatar and
its measured surface must declare "skeleton":"helix-humanoid@1".
helix item publish ./tunic.glb --kind add_on --title "Fantasy Tunic" \
--fits ./tunic-fits.json --ops ./tunic-ops.json --dry-run --json
helix item publish ./base.web.glb --kind avatar --title "Universal Base" \
--thumbnail ./base.webp --host-kind character \
--host-surface ./character-surface.json --dry-run --json--quoteasks the server for the definition publish fee, the creator tier's included free- Collectible units, excess issuance authorization cost, creator serial#1, price floor and resale policy. It uploads nothing and charges nothing.POST /api/v1/universal-items/upload—multipart/form-datawith ameshfile part, an optionalthumbnailfile part, and the metadata as ONE JSON string inpayload.- The server verifies the mesh inline (triangles, texture edges, materials, and — for a wearable — whether it is a skinned garment or a rigid socketed accessory) and returns the created item together with the verification verdict and warnings.
Price, world, schedule and limits live on a separate distribution. Price 0 means a shell-
confirmed Claim; a positive price means Buy and must meet the server-owned floor. A world
route also has a stable key, which world code passes to
Helix.marketplace.purchaseDistributionKey(key). --max-per-player unlimited explicitly clears a
cap; free Collectibles default to one per player when omitted. Distribution changes never republish
the definition.
# Standard, free Marketplace Claim
helix item publish ./postcard.glb --title "Harbor Postcard"
helix item distribution create <item-id> --channel marketplace --price-lix 0
# Standard, paid World Buy
helix item distribution create <item-id> --channel world --world fishing-cove \
--key fishing_rod --price-lix 250 --max-per-player unlimited
# Collectible, free World Claim (creator receives #1 from the 500 fixed units)
helix item publish ./trophy.glb --title "Season One Trophy" --collectible-supply 500 \
--distribution-channel world --distribution-price-lix 0 --world tournament \
--distribution-key season_one --max-per-player 1 --quote
# Review the quote, then run the same command without --quote to publish.
# Collectible, paid Marketplace Buy
helix item publish ./bluefin.glb --title "Legendary Bluefin" --collectible-supply 10000
helix item distribution create <item-id> --channel marketplace --price-lix 500 --max-claims 9000
helix item distribution update <distribution-id> --price-lix 600 --ends-at 2026-12-01T00:00:00Z
helix item distribution disable <distribution-id>Collectible resale uses Marketplace escrow. The creator royalty is fixed at 5%; the launch seller platform fee is 10% for Free and 5% for Plus (future Gold 2.5%, Diamond 0%). The buyer pays no surcharge, creator self-sale omits a redundant royalty leg, and self-purchase is prohibited. Every acquisition has one 24-hour relisting cooldown. Basic requires email; Verified means a unique verified mobile plus good standing and may resell immediately subject to that cooldown. Identity Verified is reserved for later high-risk capability, and tooling accepts future Business Verified. Payout holds are separate: 3 days base, risk-extendable up to 15 days.
The slot table in src/item.ts MIRRORS helix-backend-api → src/universal-items/cc-wearable-slot.ts,
and the gender list mirrors src/universal-items/cc-gender.ts the same way. They are a local fast
path so a typo costs no upload; the server remains the authority and its 400 lists every valid
value. Adding a slot or a gender means editing both, in the same PR wave.
--gender takes male, female, or male,female, and is required for --kind wearable.
There is no both/unisex/all: Unreal's cosmetics enum has exactly Male and Female, so a garment
that fits every body names both — one HELIX row with {male, female} is the web equivalent of
Unreal's two entries sharing one mesh. The field goes on the wire as genders; ccGenders is the
database column and the backend rejects it by name rather than dropping it in silence.
Develop
npm install
npm test # build current source, then Jest
npm run build # tsc → dist/ (bin: dist/index.js)
npm run lint