@helix3/helix-cli 0.1.11-helix3.50 → 0.1.13-helix3.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +264 -3
  2. package/assets/item-def-sounds/Common/A_DryShot.ogg +0 -0
  3. package/assets/item-def-sounds/Common/A_LMG_CloseCoverA.ogg +0 -0
  4. package/assets/item-def-sounds/Common/A_LMG_CloseCoverB.ogg +0 -0
  5. package/assets/item-def-sounds/Common/A_LMG_InsertMag.ogg +0 -0
  6. package/assets/item-def-sounds/Common/A_LMG_RemoveMag.ogg +0 -0
  7. package/assets/item-def-sounds/Common/A_Pistol_InsertMag_001.ogg +0 -0
  8. package/assets/item-def-sounds/Common/A_Pistol_InsertMag_002.ogg +0 -0
  9. package/assets/item-def-sounds/Common/A_Pistol_InsertMag_003.ogg +0 -0
  10. package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_001.ogg +0 -0
  11. package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_002.ogg +0 -0
  12. package/assets/item-def-sounds/Common/A_Pistol_RemoveMag_003.ogg +0 -0
  13. package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_001.ogg +0 -0
  14. package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_002.ogg +0 -0
  15. package/assets/item-def-sounds/Common/A_Rifle_ChargingHandle_003.ogg +0 -0
  16. package/assets/item-def-sounds/Common/A_Rifle_InsertMag_001.ogg +0 -0
  17. package/assets/item-def-sounds/Common/A_Rifle_InsertMag_002.ogg +0 -0
  18. package/assets/item-def-sounds/Common/A_Rifle_InsertMag_003.ogg +0 -0
  19. package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_001.ogg +0 -0
  20. package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_002.ogg +0 -0
  21. package/assets/item-def-sounds/Common/A_Rifle_RemoveMag_003.ogg +0 -0
  22. package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_001.ogg +0 -0
  23. package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_002.ogg +0 -0
  24. package/assets/item-def-sounds/Common/A_SMG_ChargingHandle_003.ogg +0 -0
  25. package/assets/item-def-sounds/Common/A_Sniper_ChargingHandle.ogg +0 -0
  26. package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_001.ogg +0 -0
  27. package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_002.ogg +0 -0
  28. package/assets/item-def-sounds/Common/A_Sniper_RemoveMag_003.ogg +0 -0
  29. package/assets/item-def-sounds/LMG/A_LWS-32_Shot_001.ogg +0 -0
  30. package/assets/item-def-sounds/LMG/A_LWS-32_Shot_002.ogg +0 -0
  31. package/assets/item-def-sounds/LMG/A_LWS-32_Shot_003.ogg +0 -0
  32. package/assets/item-def-sounds/Pistol/A_Gaston_Shot_001.ogg +0 -0
  33. package/assets/item-def-sounds/Pistol/A_Gaston_Shot_002.ogg +0 -0
  34. package/assets/item-def-sounds/Pistol/A_Gaston_Shot_003.ogg +0 -0
  35. package/assets/item-def-sounds/Rifle/A_KAL_Shot_001.ogg +0 -0
  36. package/assets/item-def-sounds/Rifle/A_KAL_Shot_002.ogg +0 -0
  37. package/assets/item-def-sounds/Rifle/A_KAL_Shot_003.ogg +0 -0
  38. package/assets/item-def-sounds/SMG/A_Bison_InsertMag.ogg +0 -0
  39. package/assets/item-def-sounds/SMG/A_Bison_RemoveMag.ogg +0 -0
  40. package/assets/item-def-sounds/SMG/A_Bison_Shot_001.ogg +0 -0
  41. package/assets/item-def-sounds/SMG/A_Bison_Shot_002.ogg +0 -0
  42. package/assets/item-def-sounds/SMG/A_Bison_Shot_003.ogg +0 -0
  43. package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_001.ogg +0 -0
  44. package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_002.ogg +0 -0
  45. package/assets/item-def-sounds/Shotgun/A_DB-12_Shot_003.ogg +0 -0
  46. package/assets/item-def-sounds/Sniper/A_CS-446_Shot_001.ogg +0 -0
  47. package/assets/item-def-sounds/Sniper/A_CS-446_Shot_002.ogg +0 -0
  48. package/assets/item-def-sounds/Sniper/A_CS-446_Shot_003.ogg +0 -0
  49. package/dist/achievement.d.ts +123 -0
  50. package/dist/achievement.js +400 -0
  51. package/dist/achievement.js.map +1 -0
  52. package/dist/api.d.ts +2 -0
  53. package/dist/api.js +25 -0
  54. package/dist/api.js.map +1 -1
  55. package/dist/assets.d.ts +556 -0
  56. package/dist/assets.js +1085 -0
  57. package/dist/assets.js.map +1 -0
  58. package/dist/audioCommand.d.ts +65 -0
  59. package/dist/audioCommand.js +181 -0
  60. package/dist/audioCommand.js.map +1 -0
  61. package/dist/bundle.d.ts +6 -0
  62. package/dist/bundle.js +68 -5
  63. package/dist/bundle.js.map +1 -1
  64. package/dist/character/fbx.d.ts +6 -0
  65. package/dist/character/fbx.js +50 -0
  66. package/dist/character/fbx.js.map +1 -0
  67. package/dist/character/importCharacter.d.ts +18 -0
  68. package/dist/character/importCharacter.js +85 -10
  69. package/dist/character/importCharacter.js.map +1 -1
  70. package/dist/character/meshyToHelixMap.d.ts +10 -0
  71. package/dist/character/meshyToHelixMap.js +92 -1
  72. package/dist/character/meshyToHelixMap.js.map +1 -1
  73. package/dist/character/stages/retargetClip.d.ts +6 -2
  74. package/dist/character/stages/retargetClip.js +53 -14
  75. package/dist/character/stages/retargetClip.js.map +1 -1
  76. package/dist/config.js +17 -3
  77. package/dist/config.js.map +1 -1
  78. package/dist/currency.js +4 -1
  79. package/dist/currency.js.map +1 -1
  80. package/dist/dev/devServer.d.ts +53 -0
  81. package/dist/dev/devServer.js +228 -0
  82. package/dist/dev/devServer.js.map +1 -0
  83. package/dist/dev/shellPage.d.ts +3 -0
  84. package/dist/dev/shellPage.js +297 -0
  85. package/dist/dev/shellPage.js.map +1 -0
  86. package/dist/dev/state.d.ts +84 -0
  87. package/dist/dev/state.js +265 -0
  88. package/dist/dev/state.js.map +1 -0
  89. package/dist/distribution.d.ts +51 -0
  90. package/dist/distribution.js +110 -0
  91. package/dist/distribution.js.map +1 -0
  92. package/dist/gesture/inspect.d.ts +4 -0
  93. package/dist/gesture/inspect.js +40 -12
  94. package/dist/gesture/inspect.js.map +1 -1
  95. package/dist/gesture/worldRuntime.d.ts +5 -0
  96. package/dist/gesture/worldRuntime.js +19 -1
  97. package/dist/gesture/worldRuntime.js.map +1 -1
  98. package/dist/glb/glb-summary.d.ts +1 -1
  99. package/dist/glb/glb-summary.js +84 -34
  100. package/dist/glb/glb-summary.js.map +1 -1
  101. package/dist/index.js +2447 -55
  102. package/dist/index.js.map +1 -1
  103. package/dist/init.d.ts +16 -1
  104. package/dist/init.js +492 -47
  105. package/dist/init.js.map +1 -1
  106. package/dist/item.d.ts +153 -0
  107. package/dist/item.js +523 -0
  108. package/dist/item.js.map +1 -0
  109. package/dist/itemDef/gunConfigRules.d.ts +24 -0
  110. package/dist/itemDef/gunConfigRules.js +244 -0
  111. package/dist/itemDef/gunConfigRules.js.map +1 -0
  112. package/dist/itemDef/inspect.d.ts +18 -0
  113. package/dist/itemDef/inspect.js +119 -0
  114. package/dist/itemDef/inspect.js.map +1 -0
  115. package/dist/itemDef/publish.d.ts +38 -0
  116. package/dist/itemDef/publish.js +178 -0
  117. package/dist/itemDef/publish.js.map +1 -0
  118. package/dist/itemDef/scaffold.d.ts +28 -0
  119. package/dist/itemDef/scaffold.js +165 -0
  120. package/dist/itemDef/scaffold.js.map +1 -0
  121. package/dist/itemDef/tables.d.ts +56 -0
  122. package/dist/itemDef/tables.js +169 -0
  123. package/dist/itemDef/tables.js.map +1 -0
  124. package/dist/itemDef/validate.d.ts +16 -0
  125. package/dist/itemDef/validate.js +93 -0
  126. package/dist/itemDef/validate.js.map +1 -0
  127. package/dist/itemQuote.d.ts +7 -0
  128. package/dist/itemQuote.js +38 -0
  129. package/dist/itemQuote.js.map +1 -0
  130. package/dist/lib.d.ts +534 -2
  131. package/dist/lib.js +819 -10
  132. package/dist/lib.js.map +1 -1
  133. package/dist/money.d.ts +183 -0
  134. package/dist/money.js +299 -0
  135. package/dist/money.js.map +1 -0
  136. package/dist/product.d.ts +84 -0
  137. package/dist/product.js +285 -0
  138. package/dist/product.js.map +1 -0
  139. package/dist/screenshot/staticServe.d.ts +8 -1
  140. package/dist/screenshot/staticServe.js +27 -6
  141. package/dist/screenshot/staticServe.js.map +1 -1
  142. package/dist/source-archive.d.ts +91 -0
  143. package/dist/source-archive.js +296 -0
  144. package/dist/source-archive.js.map +1 -0
  145. package/dist/tsconfig.build.tsbuildinfo +1 -1
  146. package/dist/verifySubPath.d.ts +32 -0
  147. package/dist/verifySubPath.js +214 -0
  148. package/dist/verifySubPath.js.map +1 -0
  149. package/dist/voiceCommand.d.ts +11 -0
  150. package/dist/voiceCommand.js +101 -0
  151. package/dist/voiceCommand.js.map +1 -0
  152. package/dist/world/analyze.d.ts +6 -1
  153. package/dist/world/analyze.js +94 -0
  154. package/dist/world/analyze.js.map +1 -1
  155. package/dist/world/baseline.js +7 -0
  156. package/dist/world/baseline.js.map +1 -1
  157. package/package.json +8 -3
  158. package/vendor/ability-manifest.mjs +4 -4
  159. package/vendor/audits/asset-credits.mjs +96 -0
  160. package/vendor/audits/check-loaders.mjs +80 -0
  161. package/vendor/audits/perf-gate.mjs +759 -0
  162. package/vendor/audits/prove-live.mjs +307 -0
  163. package/vendor/audits/source-audit.mjs +517 -0
  164. package/vendor/audits/world-audit.mjs +439 -0
  165. package/vendor/installed-index.mjs +1 -1
package/README.md CHANGED
@@ -4,6 +4,12 @@
4
4
  backend. It shares bundle validation with the server via `@hypersoniclabs/helix-manifest`, so "validate passed but
5
5
  publish failed" can't happen for contract reasons.
6
6
 
7
+ ## Architecture boundary
8
+
9
+ All creator feature and operational logic, validation, file mutation, network behavior, and tests
10
+ live here. MCP packages may expose transport schemas, resources, instructions, and thin delegation
11
+ to these commands, but must not reimplement this logic.
12
+
7
13
  ## Install
8
14
 
9
15
  ```bash
@@ -35,8 +41,24 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
35
41
  | `helix init <dir>` | Scaffold a minimal Three.js world (`helix.json` + `index.html` + `main.js`). |
36
42
  | `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`. |
37
43
  | `helix validate [dir]` | Validate a bundle locally — the exact rules the server enforces. |
38
- | `helix publish [dir]` | Validate → resolve/create the world → upload files → finalize → print the play URL. |
44
+ | `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**. |
39
45
  | `helix list` | List your worlds. |
46
+ | `helix item list-slots [--json]` | Print the Character-Creator vocabulary a wearable declares: the cosmetic slot tags, and the genders. |
47
+ | `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. |
48
+ | `helix item distribution create\|list\|update\|disable …` | Manage Marketplace/World Claim or Buy routes without republishing the item or paying another publish fee. |
49
+ | `helix preview-video <slug> <youtube-url>` | Set or clear a published world's YouTube preview. |
50
+ | `helix thumbnail set <slug> <file>` | Upload a thumbnail or preview image through the CLI-owned media path. |
51
+ | `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. |
52
+ | `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). |
53
+ | `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. |
54
+ | `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. |
55
+ | `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). |
56
+ | `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`. |
57
+ | `helix assets materials [--resolution <res>] [--human]` | Discover the small built-in material palette. `--resolution` keeps only materials carrying that texture resolution. |
58
+ | `helix assets material <id> [--resolution <res>] [--human]` | Resolve one material to its immutable map URLs at the resolution you pick (default: the pack's own default). |
59
+ | `helix assets credits\|check-loaders` | Render asset credits from provenance and reject models requiring unsupported loaders. |
60
+ | `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. |
61
+ | `helix world audit\|source-audit\|perf-gate\|prove-live` | Run the blocking world QA, performance, and deployed-build identity gates. |
40
62
  | `helix doctor [--project <dir>]` | Print the environment + whether the @helix toolchain (CLI/MCP/SDK/manifest) is current. |
41
63
 
42
64
  ## Publish flow
@@ -51,8 +73,247 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
51
73
  5. `POST /api/v1/instant-worlds/:id/builds/:buildId/finalize` → byte-exact verify, activate, publish.
52
74
  6. `GET /api/v1/instant-worlds/:slug` → the play URL.
53
75
 
54
- The programmatic surface (`publishWorld`, `checkBundle`, `whoAmI`, …) is exported from the package
55
- main, so an MCP server can drive the exact same code paths an agent would.
76
+ ### Opt-in source upload
77
+
78
+ A published world ships only its **built bundle**, so there is nothing on the server to edit later.
79
+ `--upload-source` adds a second, separate upload that fixes that:
80
+
81
+ 7. `POST /api/v1/instant-worlds/:id/source` → a presigned PUT into a **private** bucket (this call
82
+ also adopts the world into a project, which is the key its future workspace hangs from).
83
+ 8. `helix-source.tar.gz` PUT with `cache-control: no-store`.
84
+ 9. `POST /api/v1/instant-worlds/:id/source/finalize` → the server verifies the stored byte count
85
+ equals the declared one, then records the version.
86
+
87
+ - **Off by default.** Source is never sent unless you pass the flag. Not a prompt — an interactive
88
+ confirm would break CI and agent-driven publishes.
89
+ - **What goes in**: the project directory, honouring `.gitignore`, always excluding `node_modules`,
90
+ `.git`, `dist` and `.vite` — the same exclude set the website's own workspace checkpoints use.
91
+ - **Which directory**: `[dir]` is the BUILT bundle, so the source defaults to its parent. Override
92
+ with `--source-dir <path>`.
93
+ - **Never part of the build.** The bundle's content-type allowlist rejects archives, raw `.ts` is
94
+ excluded from a bundle by design, the bundle size budget is the playable budget, and every build
95
+ file lands on a public CDN path with a year-long immutable cache. Source uses its own private
96
+ channel for exactly that reason.
97
+ - **Limit**: 128 MiB compressed, checked locally before anything is uploaded.
98
+
99
+ The programmatic surface (`publishWorld`, `checkBundle`, `whoAmI`, …) is exported for other CLI
100
+ modules. Agent integrations should instruct or delegate to the `helix` commands above rather than
101
+ reimplementing operational behavior in a transport package.
102
+
103
+ ## Vault lifecycle
104
+
105
+ Use the durable Vault UUID as the handle. Search results include independent lexical, hard-filter,
106
+ measured-performance, reuse, and availability explanations; they do not collapse those signals into
107
+ a made-up score. `helix assets install` resolves an immutable version, follows the `/download`
108
+ indirection, verifies its server checksum and byte count, installs related material maps, and writes
109
+ `public/helix.assets.json`. Material installs keep the descriptor and default to the runtime KTX2
110
+ map family only; choose source PNGs with `--material-renditions source`, or both families with
111
+ `--material-renditions all`. Reinstalling with a different selection removes the stale opposite
112
+ family. Non-material assets keep their existing related-artifact behavior. For a reproducible agent build, pass `--asset-version`,
113
+ `--checksum-sha256`, and `--size-bytes` together; a partial pin or any metadata/download mismatch
114
+ fails before a provenance receipt is written.
115
+
116
+ Generation is source-agnostic at the backend:
117
+
118
+ - `prop` and `character` start through the main Dreamer route because they use its verified
119
+ universal-item flow.
120
+ - `image`, `material`, `audio`, and `gaussian_splat` start through the shared asset route.
121
+ Gaussian splats must declare `object` or `environment` scope.
122
+ - `animation` fails locally with `CAPABILITY_UNAVAILABLE`; it never starts a chargeable job.
123
+
124
+ Run `helix assets status <job-id> --pipeline dreamer|asset` to poll an existing job. A successful
125
+ default-on generation reports `vaultAssetId` and `vaultAutoPublish: "published"`; an explicit
126
+ creator opt-out reports `"disabled"`.
127
+
128
+ For `.helix-scene.json`, the open Vault catalog is not the scene-local budget. Reuse a small
129
+ material palette and instance repeated assets. The canonical ceilings are:
130
+
131
+ | Profile | Unique materials | Draw calls | Decoded texture memory | Triangles | Particles |
132
+ | --- | ---: | ---: | ---: | ---: | ---: |
133
+ | mobile | 48 | 300 | 256 MiB | 750,000 | 5,000 |
134
+ | desktop | 128 | 1,200 | 1 GiB | 4,000,000 | 50,000 |
135
+ | cinematic | 256 | 4,000 | 4 GiB | 15,000,000 | 250,000 |
136
+
137
+ These are publish-contract ceilings, not frame-rate promises; world runtime QA still applies.
138
+
139
+ The lifecycle acceptance harness intentionally has no CLI-local demo. Point it
140
+ at a built world in `helix-web-demo-worlds` that consumes the published SDK and
141
+ humanoid runtime:
142
+
143
+ ```bash
144
+ npm run qa:vault-lifecycle -- \
145
+ --world-template /path/to/helix-web-demo-worlds/worlds/sdk-showcase
146
+ ```
147
+
148
+ Publishing is an explicit release-only action and additionally requires a new
149
+ approved slug:
150
+
151
+ ```bash
152
+ npm run qa:vault-lifecycle -- \
153
+ --world-template /path/to/helix-web-demo-worlds/worlds/sdk-showcase \
154
+ --publish --world-slug ch1146-vault-lifecycle-qa-<unique-suffix>
155
+ ```
156
+
157
+ The publish receipt requires one usage increment, one world association, and
158
+ an idempotent second publish with no additional increment.
159
+
160
+ ### Audio generation
161
+
162
+ All three audio modes use the same authenticated Dreamer route and automatically publish the
163
+ completed MP3 to Vault. The command waits up to 30 minutes so a ten-minute music request has enough
164
+ time to render, ingest, and publish. It prints the durable `vaultAssetId`, measured Spark charge,
165
+ provider cost, and server-stamped `billingEvidence`. It does not download another local copy unless
166
+ `--output` is supplied.
167
+
168
+ ```bash
169
+ # Sound effect
170
+ helix assets generate-audio "heavy metal door slamming shut" \
171
+ --mode sound_effect --duration-seconds 3 --prompt-influence 0.4 --output door.mp3
172
+
173
+ # Music
174
+ helix assets generate-audio "hopeful orchestral exploration theme" \
175
+ --mode music --duration-seconds 180 --force-instrumental
176
+
177
+ # Text to speech
178
+ helix assets voices --search narrator --page-size 10
179
+ helix assets generate-audio --mode text_to_speech \
180
+ --text "Welcome to HELIX." --voice-id <id-from-catalog> \
181
+ --language-code en --stability 0.5 --speaker-boost
182
+ ```
183
+
184
+ Use the opaque `--page-token` printed by a page to continue. Voice cloning and
185
+ voice administration are deliberately not exposed.
186
+
187
+ Use a deterministic four-view sheet when you already have approved FRONT, BACK,
188
+ LEFT, and RIGHT character art and do not want Dreamer to generate another image:
189
+
190
+ ```bash
191
+ helix assets generate-reference ./character-four-views.png \
192
+ "compact friendly service robot, game-ready humanoid proportions" \
193
+ --title "Service Robot" \
194
+ --target-polycount 12000
195
+ ```
196
+
197
+ The reference endpoint is character-only. The file is image-guarded, normalized,
198
+ checksum-bound to the job, semantically validated, and mirrored before approval.
199
+ Retries reuse those same bytes and never invoke or charge the image provider.
200
+
201
+ ## Material texture resolution
202
+
203
+ A platform material can ship more than one texture resolution. `--resolution` picks which one you
204
+ get; without it you get the pack's declared default, byte-for-byte what every earlier CLI returned.
205
+
206
+ ```bash
207
+ helix assets material brick-block # the pack's default resolution
208
+ helix assets material brick-block --resolution 2k # the 2K variant's map URLs
209
+ helix assets material brick-block --resolution 2048 # same thing — numeric spelling
210
+ helix assets materials --resolution 2k --human # only materials that carry 2K
211
+ ```
212
+
213
+ Accepted values are the keys the catalog itself declares (`1k`, `2k`, …), case-insensitively, plus
214
+ the numeric aliases `1024`/`2048`, which map onto the variant authored at that pixel edge.
215
+
216
+ Both commands print JSON by default (agents and the MCP delegation parse it); `--human` prints a
217
+ readable summary instead. Either way the output states **which resolution was resolved and which
218
+ ones the material offers**, under `resolution`:
219
+
220
+ ```jsonc
221
+ "resolution": {
222
+ "requested": "2k", "resolved": "2k", "pixels": 2048, "default": "1k",
223
+ "available": [{ "key": "1k", "pixels": 1024 }, { "key": "2k", "pixels": 2048 }],
224
+ "applicable": true
225
+ }
226
+ ```
227
+
228
+ Three rules make this safe to rely on:
229
+
230
+ - **No silent fallback, ever.** Asking for a resolution the material does not carry is an error that
231
+ names the ones it does. You never receive a different resolution than the one you asked for.
232
+ - **Old packs keep working.** A catalog with no `resolutions` block (`schemaVersion: 1`) is treated
233
+ as having exactly one resolution, keyed `default`, derived from its existing `maps`. Such a pack
234
+ never claims to be "1k" — it does not say how big its textures are, so neither do we.
235
+ - **Procedural materials are not an error.** `glass` and `procedural_water` carry no texture maps;
236
+ a resolution request against them is ignored and the output says so in `resolution.note`.
237
+
238
+ `helix assets install` has no resolution flag: Vault related-artifact roles are semantic
239
+ (`source.albedo`, `runtime.ktx2.normal`, `sky.backdrop`), never resolution-tagged, so a filter there
240
+ would filter nothing.
241
+
242
+ ## Item publish flow
243
+
244
+ `helix item publish` creates an item definition. It defaults to **Standard**: unlimited issuance,
245
+ no public serial, and no resale. `--collectible-supply N` is the only edition selector: it fixes a
246
+ positive supply, assigns public serials, makes instances Marketplace-resellable, and reserves
247
+ serial `#1` for the creator. There are no separate tradable, serial, giftable, personal, or
248
+ discoverability switches.
249
+
250
+ 1. Local validation, before anything leaves the machine: the `--kind`, the Character-Creator
251
+ `--slot` and `--gender` (**both required** for a wearable, exact-match), the title/slug/tag
252
+ limits, and the GLB container magic. `--dry-run` stops here and prints exactly what would be
253
+ sent.
254
+
255
+ `--kind` accepts `wearable`, `avatar`, `prop`, and `home` — `prop` and `home` are the current
256
+ product names (see helixgame.com) for what the API still calls `home_item` and `home_shell`; both
257
+ spellings work identically and `helix item publish --kind prop …` sends the exact same
258
+ `"kind":"home_item"` on the wire as `--kind home_item …` does. This is a CLI-vocabulary alias only —
259
+ it is unrelated to the backend's separate `prop` universal-item kind (art/collectible items).
260
+ 2. `--quote` asks the server for the definition publish fee, the creator tier's included free-
261
+ Collectible units, excess issuance authorization cost, creator serial `#1`, price floor and
262
+ resale policy. It uploads nothing and charges nothing.
263
+ 3. `POST /api/v1/universal-items/upload` — `multipart/form-data` with a `mesh` file part, an
264
+ optional `thumbnail` file part, and the metadata as ONE JSON string in `payload`.
265
+ 4. The server verifies the mesh **inline** (triangles, texture edges, materials, and — for a
266
+ wearable — whether it is a skinned garment or a rigid socketed accessory) and returns the
267
+ created item together with the verification verdict and warnings.
268
+
269
+ Price, world, schedule and limits live on a separate distribution. Price `0` means a shell-
270
+ confirmed **Claim**; a positive price means **Buy** and must meet the server-owned floor. A world
271
+ route also has a stable key, which world code passes to
272
+ `Helix.marketplace.purchaseDistributionKey(key)`. `--max-per-player unlimited` explicitly clears a
273
+ cap; free Collectibles default to one per player when omitted. Distribution changes never republish
274
+ the definition.
275
+
276
+ ```bash
277
+ # Standard, free Marketplace Claim
278
+ helix item publish ./postcard.glb --title "Harbor Postcard"
279
+ helix item distribution create <item-id> --channel marketplace --price-lix 0
280
+
281
+ # Standard, paid World Buy
282
+ helix item distribution create <item-id> --channel world --world fishing-cove \
283
+ --key fishing_rod --price-lix 250 --max-per-player unlimited
284
+
285
+ # Collectible, free World Claim (creator receives #1 from the 500 fixed units)
286
+ helix item publish ./trophy.glb --title "Season One Trophy" --collectible-supply 500 \
287
+ --distribution-channel world --distribution-price-lix 0 --world tournament \
288
+ --distribution-key season_one --max-per-player 1 --quote
289
+ # Review the quote, then run the same command without --quote to publish.
290
+
291
+ # Collectible, paid Marketplace Buy
292
+ helix item publish ./bluefin.glb --title "Legendary Bluefin" --collectible-supply 10000
293
+ helix item distribution create <item-id> --channel marketplace --price-lix 500 --max-claims 9000
294
+
295
+ helix item distribution update <distribution-id> --price-lix 600 --ends-at 2026-12-01T00:00:00Z
296
+ helix item distribution disable <distribution-id>
297
+ ```
298
+
299
+ Collectible resale uses Marketplace escrow. The creator royalty is fixed at 5%; the launch seller
300
+ platform fee is 10% for Free and 5% for Plus (future Gold 2.5%, Diamond 0%). The buyer pays no
301
+ surcharge, creator self-sale omits a redundant royalty leg, and self-purchase is prohibited.
302
+ Every acquisition has one 24-hour relisting cooldown. Basic requires email; Verified means a unique
303
+ verified mobile plus good standing and may resell immediately subject to that cooldown. Identity Verified
304
+ is reserved for later high-risk capability, and tooling accepts future Business Verified. Payout holds are
305
+ separate: 3 days base, risk-extendable up to 15 days.
306
+
307
+ The slot table in `src/item.ts` MIRRORS `helix-backend-api → src/universal-items/cc-wearable-slot.ts`,
308
+ and the gender list mirrors `src/universal-items/cc-gender.ts` the same way. They are a local fast
309
+ path so a typo costs no upload; the server remains the authority and its 400 lists every valid
310
+ value. Adding a slot or a gender means editing both, in the same PR wave.
311
+
312
+ `--gender` takes `male`, `female`, or `male,female`, and is **required** for `--kind wearable`.
313
+ There is no `both`/`unisex`/`all`: Unreal's cosmetics enum has exactly Male and Female, so a garment
314
+ that fits every body names both — one HELIX row with `{male, female}` is the web equivalent of
315
+ Unreal's two entries sharing one mesh. The field goes on the wire as `genders`; `ccGenders` is the
316
+ database column and the backend rejects it by name rather than dropping it in silence.
56
317
 
57
318
  ## Develop
58
319
 
@@ -0,0 +1,123 @@
1
+ /** 2–80 chars, lowercase letters/digits/`_`/`-`, starting alphanumeric. */
2
+ export declare const ACHIEVEMENT_KEY_PATTERN: RegExp;
3
+ /**
4
+ * WHO may award the badge — the trust marker a player and the platform UI read.
5
+ * `platform` is first-party code only; it is in the list because the backend
6
+ * accepts it, not because a world should use it (see `assertUnlockModeForWorld`).
7
+ */
8
+ export declare const ACHIEVEMENT_UNLOCK_MODES: readonly ["room", "criteria", "platform"];
9
+ export type AchievementUnlockMode = (typeof ACHIEVEMENT_UNLOCK_MODES)[number];
10
+ export declare const ACHIEVEMENT_CRITERIA_SIGNALS: readonly ["datastore", "iwp", "analytics", "playtime"];
11
+ export type AchievementCriteriaSignal = (typeof ACHIEVEMENT_CRITERIA_SIGNALS)[number];
12
+ export declare const ACHIEVEMENT_CRITERIA_OPS: readonly ["gte", "lte", "eq"];
13
+ export type AchievementCriteriaOp = (typeof ACHIEVEMENT_CRITERIA_OPS)[number];
14
+ export declare const ACHIEVEMENT_CRITERIA_METRICS: readonly ["count", "lix"];
15
+ export type AchievementCriteriaMetric = (typeof ACHIEVEMENT_CRITERIA_METRICS)[number];
16
+ /**
17
+ * The only two documents a `datastore` criteria may read, and WHICH ONE IS THE
18
+ * TRUST DECISION: `mp:player:{userId}` is the room's flushed persistent
19
+ * playerVars (room-only writable ⇒ server-authoritative), `player:{userId}` is
20
+ * the player's own client-written save (forgeable by its owner, accepted
21
+ * deliberately — it is how a single-player world unlocks anything).
22
+ */
23
+ export declare const DATA_STORE_CRITERIA_KEY_TEMPLATES: readonly ["player:{userId}", "mp:player:{userId}"];
24
+ export type DataStoreCriteriaKeyTemplate = (typeof DATA_STORE_CRITERIA_KEY_TEMPLATES)[number];
25
+ /** A `datastore` criteria's `field`: a dot-path of letters, digits and underscores. */
26
+ export declare const DATA_STORE_CRITERIA_FIELD_PATTERN: RegExp;
27
+ /** Backend caps (`ACHIEVEMENT_ICON_MAX_BYTES` / `ACHIEVEMENT_MESH_MAX_BYTES` defaults). */
28
+ export declare const ACHIEVEMENT_ICON_MAX_BYTES: number;
29
+ export declare const ACHIEVEMENT_TROPHY_MAX_BYTES: number;
30
+ /** Smallest square an icon can be and still read as a graphic on a plaque. */
31
+ export declare const MIN_ICON_EDGE = 16;
32
+ /**
33
+ * Per-channel standard deviation below which the backend treats an image as one
34
+ * flat colour. Not zero — a real icon that has been through lossy encoding
35
+ * carries a little noise.
36
+ */
37
+ export declare const MIN_ICON_STDDEV = 1;
38
+ export declare const isAchievementKey: (value: unknown) => value is string;
39
+ /** Content type for the required `image` part — the 2D badge graphic. */
40
+ export declare function iconContentTypeFromExtension(file: string): string;
41
+ /**
42
+ * What the backend's `assertValidAchievementIcon` checks, mirrored so a
43
+ * placeholder fails in a second instead of after an upload + a moderation pass.
44
+ * The two rejections are the two placeholders creators actually send: an image
45
+ * too small to read, and a single flat colour. Deliberately NOT a quality
46
+ * judgement — a two-tone glyph or a flat logo on a plate passes.
47
+ */
48
+ export declare function assertIconLooksReal(bytes: Uint8Array, file: string): Promise<void>;
49
+ export type AchievementCriteria = {
50
+ signal: AchievementCriteriaSignal;
51
+ op: AchievementCriteriaOp;
52
+ value: number;
53
+ eventName?: string;
54
+ key?: DataStoreCriteriaKeyTemplate;
55
+ field?: string;
56
+ metric?: AchievementCriteriaMetric;
57
+ };
58
+ export type AchievementCriteriaInput = {
59
+ signal?: string;
60
+ op?: string;
61
+ value?: number;
62
+ eventName?: string;
63
+ key?: string;
64
+ field?: string;
65
+ metric?: string;
66
+ };
67
+ /** True when any criteria field was supplied — i.e. the caller means `criteria`. */
68
+ export declare function hasCriteriaInput(input: AchievementCriteriaInput | undefined): boolean;
69
+ /**
70
+ * Validate + normalize the criteria the way the backend's `normalizeCriteria`
71
+ * does, and throw the same failures. Returns the exact object sent as the
72
+ * multipart `criteria` JSON string — no key the caller did not set.
73
+ */
74
+ export declare function buildAchievementCriteria(input: AchievementCriteriaInput): AchievementCriteria;
75
+ /** One-line human summary of a stored criteria — the `list` column and the register echo. */
76
+ export declare function formatAchievementCriteria(criteria: AchievementCriteria | null | undefined): string;
77
+ export type AchievementRegistrationInput = {
78
+ key: string;
79
+ name: string;
80
+ description?: string;
81
+ points?: number;
82
+ hidden?: boolean;
83
+ unlockMode?: string;
84
+ criteria?: AchievementCriteriaInput;
85
+ };
86
+ export type AchievementRegistrationFields = {
87
+ key: string;
88
+ name: string;
89
+ description?: string;
90
+ points?: number;
91
+ hidden?: boolean;
92
+ unlockMode: AchievementUnlockMode;
93
+ criteria?: AchievementCriteria;
94
+ };
95
+ export type AchievementRegistrationPlan = {
96
+ fields: AchievementRegistrationFields;
97
+ warnings: string[];
98
+ };
99
+ /**
100
+ * Resolve the trust marker. The backend's DTO defaults to `platform` because the
101
+ * ADMIN path (worldId omitted) is the one that needs it; for a WORLD
102
+ * registration `platform` is a mislabel — nothing in a world is first-party
103
+ * code. So this CLI always sends an explicit mode: `criteria` when a condition
104
+ * was declared, otherwise `room`, the only thing a world can actually award
105
+ * (the `awardAchievement` rule effect).
106
+ */
107
+ export declare function resolveUnlockMode(explicit: string | undefined, hasCriteria: boolean): AchievementUnlockMode;
108
+ /**
109
+ * Build the multipart text fields, applying every local rule first. Keys the
110
+ * caller did not set are OMITTED rather than sent empty — an empty description
111
+ * and no description are the same row, and sending "" would overwrite nothing
112
+ * while looking like intent.
113
+ */
114
+ export declare function buildAchievementRegistration(input: AchievementRegistrationInput): AchievementRegistrationPlan;
115
+ export type AchievementUpdateInput = {
116
+ name?: string;
117
+ description?: string;
118
+ points?: number;
119
+ hidden?: boolean;
120
+ active?: boolean;
121
+ };
122
+ /** Validate a PATCH body locally and refuse an empty one (a no-op round trip that reads as success). */
123
+ export declare function buildAchievementUpdate(input: AchievementUpdateInput): AchievementUpdateInput;