@helix3/helix-cli 0.1.13-helix3.84 → 0.1.13-helix3.86

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.
package/README.md CHANGED
@@ -44,7 +44,8 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
44
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**. |
45
45
  | `helix list` | List your worlds. |
46
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> [--kind <k>] [--slot <tag>] [--gender <g>] [--price-lix <n> \| --personal] [--thumbnail <f>] [--dry-run]` | Publish a universal item (wearable / avatar / home item) from one `.glb`; the server verifies the mesh inline. |
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. |
48
49
  | `helix preview-video <slug> <youtube-url>` | Set or clear a published world's YouTube preview. |
49
50
  | `helix thumbnail set <slug> <file>` | Upload a thumbnail or preview image through the CLI-owned media path. |
50
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. |
@@ -239,19 +240,63 @@ would filter nothing.
239
240
 
240
241
  ## Item publish flow
241
242
 
242
- `helix item publish` is a different shape from a world publish: one mesh, one round trip, an
243
- inline verdict — which is what an editor (Helix Studio) needs when an artist clicks Publish.
243
+ `helix item publish` creates an item definition. It defaults to **Standard**: unlimited issuance,
244
+ no public serial, and no resale. `--collectible-supply N` is the only edition selector: it fixes a
245
+ positive supply, assigns public serials, makes instances Marketplace-resellable, and reserves
246
+ serial `#1` for the creator. There are no separate tradable, serial, giftable, personal, or
247
+ discoverability switches.
244
248
 
245
249
  1. Local validation, before anything leaves the machine: the `--kind`, the Character-Creator
246
250
  `--slot` and `--gender` (**both required** for a wearable, exact-match), the title/slug/tag
247
251
  limits, and the GLB container magic. `--dry-run` stops here and prints exactly what would be
248
252
  sent.
249
- 2. `POST /api/v1/universal-items/upload` — `multipart/form-data` with a `mesh` file part, an
253
+ 2. `--quote` asks the server for the definition publish fee, the creator tier's included free-
254
+ Collectible units, excess issuance authorization cost, creator serial `#1`, price floor and
255
+ resale policy. It uploads nothing and charges nothing.
256
+ 3. `POST /api/v1/universal-items/upload` — `multipart/form-data` with a `mesh` file part, an
250
257
  optional `thumbnail` file part, and the metadata as ONE JSON string in `payload`.
251
- 3. The server verifies the mesh **inline** (triangles, texture edges, materials, and — for a
258
+ 4. The server verifies the mesh **inline** (triangles, texture edges, materials, and — for a
252
259
  wearable — whether it is a skinned garment or a rigid socketed accessory) and returns the
253
260
  created item together with the verification verdict and warnings.
254
261
 
262
+ Price, world, schedule and limits live on a separate distribution. Price `0` means a shell-
263
+ confirmed **Claim**; a positive price means **Buy** and must meet the server-owned floor. A world
264
+ route also has a stable key, which world code passes to
265
+ `Helix.marketplace.purchaseDistributionKey(key)`. `--max-per-player unlimited` explicitly clears a
266
+ cap; free Collectibles default to one per player when omitted. Distribution changes never republish
267
+ the definition.
268
+
269
+ ```bash
270
+ # Standard, free Marketplace Claim
271
+ helix item publish ./postcard.glb --title "Harbor Postcard"
272
+ helix item distribution create <item-id> --channel marketplace --price-lix 0
273
+
274
+ # Standard, paid World Buy
275
+ helix item distribution create <item-id> --channel world --world fishing-cove \
276
+ --key fishing_rod --price-lix 250 --max-per-player unlimited
277
+
278
+ # Collectible, free World Claim (creator receives #1 from the 500 fixed units)
279
+ helix item publish ./trophy.glb --title "Season One Trophy" --collectible-supply 500 \
280
+ --distribution-channel world --distribution-price-lix 0 --world tournament \
281
+ --distribution-key season_one --max-per-player 1 --quote
282
+ # Review the quote, then run the same command without --quote to publish.
283
+
284
+ # Collectible, paid Marketplace Buy
285
+ helix item publish ./bluefin.glb --title "Legendary Bluefin" --collectible-supply 10000
286
+ helix item distribution create <item-id> --channel marketplace --price-lix 500 --max-claims 9000
287
+
288
+ helix item distribution update <distribution-id> --price-lix 600 --ends-at 2026-12-01T00:00:00Z
289
+ helix item distribution disable <distribution-id>
290
+ ```
291
+
292
+ Collectible resale uses Marketplace escrow. The creator royalty is fixed at 5%; the launch seller
293
+ platform fee is 10% for Free and 5% for Plus (future Gold 2.5%, Diamond 0%). The buyer pays no
294
+ surcharge, creator self-sale omits a redundant royalty leg, and self-purchase is prohibited.
295
+ Every acquisition has one 24-hour relisting cooldown. Basic requires email; Verified means a unique
296
+ verified mobile plus good standing and may resell immediately subject to that cooldown. Identity Verified
297
+ is reserved for later high-risk capability, and tooling accepts future Business Verified. Payout holds are
298
+ separate: 3 days base, risk-extendable up to 15 days.
299
+
255
300
  The slot table in `src/item.ts` MIRRORS `helix-backend-api → src/universal-items/cc-wearable-slot.ts`,
256
301
  and the gender list mirrors `src/universal-items/cc-gender.ts` the same way. They are a local fast
257
302
  path so a typo costs no upload; the server remains the authority and its 400 lists every valid
@@ -1,3 +1,3 @@
1
1
  export declare const DEV_SHELL_BANNER: string;
2
- export declare const DEV_SHELL_PAGE_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>helix dev \u2014 SIMULATED shell</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body { margin: 0; height: 100vh; display: flex; flex-direction: column;\n font: 13px/1.5 ui-sans-serif, system-ui, sans-serif; background: #0d0f13; color: #e6e8ec; }\n .banner { padding: 8px 14px; background: #7a4b00; color: #fff5e0; font-weight: 600; }\n .banner code { background: rgba(0,0,0,.3); padding: 1px 5px; border-radius: 3px; }\n main { flex: 1; display: flex; min-height: 0; }\n .stage { flex: 1; min-width: 0; background: #000; position: relative; }\n iframe { width: 100%; height: 100%; border: 0; display: block; }\n #toast { position: absolute; left: 50%; top: 18px; transform: translateX(-50%); max-width: 80%;\n background: #16241a; color: #c9f0c9; border: 1px solid #3f7a3f; border-radius: 6px;\n padding: 8px 14px; font-weight: 600; opacity: 0; pointer-events: none; transition: opacity .25s; }\n #toast.show { opacity: 1; }\n .panel { width: 320px; border-left: 1px solid #23262d; display: flex; flex-direction: column; min-height: 0; }\n .panel h2 { margin: 0; padding: 8px 12px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;\n color: #8b93a1; border-bottom: 1px solid #23262d; }\n .meta { padding: 8px 12px; color: #8b93a1; border-bottom: 1px solid #23262d; }\n .meta b { color: #e6e8ec; font-weight: 600; }\n .menu { padding: 8px; display: grid; gap: 6px; border-bottom: 1px solid #23262d; }\n button { font: inherit; text-align: left; padding: 6px 9px; border-radius: 5px; cursor: pointer;\n background: #1a1d24; color: #e6e8ec; border: 1px solid #2c3039; }\n button:hover { background: #232732; }\n button.armed { border-color: #b4741a; color: #ffcf8a; }\n #log { flex: 1; margin: 0; padding: 8px 12px; overflow: auto; white-space: pre-wrap; word-break: break-word;\n font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; color: #b7bdc8; }\n #log .sim { color: #9fd39f; }\n #log .err { color: #ff9b9b; }\n</style>\n</head>\n<body>\n<div class=\"banner\">SIMULATED shell \u2014 helix dev. Purchases, saves and achievements are simulated locally; nothing here proves a product is registered or that settlement works.</div>\n<main>\n <div class=\"stage\"><iframe id=\"world\" title=\"world\" allow=\"autoplay; microphone; gamepad; fullscreen\"></iframe><div id=\"toast\"></div></div>\n <aside class=\"panel\">\n <h2>helix dev \u2014 simulated</h2>\n <div class=\"meta\" id=\"meta\">loading\u2026</div>\n <h2>Debug menu</h2>\n <div class=\"menu\" id=\"menu\"></div>\n <h2>Log</h2>\n <pre id=\"log\"></pre>\n </aside>\n</main>\n<script type=\"module\" src=\"/dev/shell.js\"></script>\n</body>\n</html>\n";
3
- export declare const DEV_SHELL_PAGE_JS = "// Served by `helix dev`. Everything here is SIMULATED \u2014 see the banner.\nconst logEl = document.getElementById('log');\nconst menuEl = document.getElementById('menu');\nconst metaEl = document.getElementById('meta');\nconst frame = document.getElementById('world');\nconst toastEl = document.getElementById('toast');\n\nlet toastTimer = null;\nfunction toast(text) {\n toastEl.textContent = text;\n toastEl.className = 'show';\n if (toastTimer) clearTimeout(toastTimer);\n toastTimer = setTimeout(function () { toastEl.className = ''; }, 4000);\n}\n\nfunction append(text, kind) {\n const line = document.createElement('div');\n if (kind) line.className = kind;\n line.textContent = text;\n logEl.appendChild(line);\n while (logEl.childElementCount > 500) logEl.removeChild(logEl.firstChild);\n logEl.scrollTop = logEl.scrollHeight;\n}\n\nfunction show(value) {\n if (value === undefined) return '';\n try {\n return typeof value === 'string' ? value : JSON.stringify(value);\n } catch (err) {\n return String(value);\n }\n}\n\nfunction button(label, onClick) {\n const el = document.createElement('button');\n el.textContent = label;\n el.addEventListener('click', function () { onClick(el); });\n menuEl.appendChild(el);\n return el;\n}\n\nasync function main() {\n const config = await (await fetch('/dev/config')).json();\n frame.src = config.worldOrigin + '/';\n\n const devShell = await import('/sdk/dev-shell/index.js');\n const prefix = devShell.DEV_SHELL_LOG_PREFIX;\n\n let pending = null;\n let sending = false;\n async function flush() {\n if (sending || pending === null) return;\n sending = true;\n const body = pending;\n pending = null;\n try {\n await fetch('/dev/state', { method: 'POST', headers: { 'content-type': 'application/json' }, body: body });\n } catch (err) {\n append('state was not saved: ' + String(err), 'err');\n }\n sending = false;\n flush();\n }\n\n // A fresh player has no stored state, so the seeded achievement registry is minted into a new one here.\n const freshState = !config.state && config.achievements\n ? devShell.createDevShellState({ identity: config.user, achievements: config.achievements })\n : undefined;\n\n const shell = devShell.createDevShell({\n worldWindow: frame,\n worldOrigin: config.worldOrigin,\n world: config.world,\n user: config.user,\n state: config.state || freshState,\n products: config.products || undefined,\n debug: true,\n onAchievementEarned: function (a) {\n toast('Achievement unlocked (SIMULATED): ' + a.name + (a.points ? ' (+' + a.points + ')' : ''));\n },\n log: function (message, detail) {\n append(detail === undefined ? message : message + ' ' + show(detail), message.indexOf(prefix) === 0 ? 'sim' : null);\n if (detail === undefined) console.log(message);\n else console.log(message, detail);\n },\n onStateChange: function (state) {\n pending = JSON.stringify(state);\n flush();\n },\n });\n window.helixDevShell = shell;\n\n metaEl.innerHTML =\n '<div>world <b>' + config.world.title + '</b> (' + config.world.slug + ')</div>' +\n '<div>player <b>' + config.user.id + '</b></div>' +\n '<div>served from ' + config.worldOrigin + '</div>';\n\n let failMode = 'off';\n let saveFailure = false;\n let signedIn = true;\n\n button('Grant all entitlements', function () { shell.grantAllEntitlements(); });\n button('Grant one entitlement\u2026', function () {\n const ref = window.prompt('Product key or ref to grant');\n if (ref) shell.grantEntitlement(ref);\n });\n button('Remove one entitlement\u2026', function () {\n const ref = window.prompt('Product key or ref to remove');\n if (ref) shell.removeEntitlement(ref);\n });\n button('Print owned entitlements', function () {\n append(prefix + ' entitlements ' + show(shell.getEntitlementsSnapshot()), 'sim');\n });\n const failButton = button('Purchases always fail: off', function (el) {\n failMode = shell.cyclePurchasesAlwaysFail();\n el.textContent = 'Purchases always fail: ' + failMode;\n el.className = failMode === 'off' ? '' : 'armed';\n });\n failButton.textContent = 'Purchases always fail: ' + failMode;\n button('Reset player document', function () { shell.resetPlayerDocument(); });\n button('Force save failure: off', function (el) {\n saveFailure = !saveFailure;\n shell.setForceSaveFailure(saveFailure);\n el.textContent = 'Force save failure: ' + (saveFailure ? 'on' : 'off');\n el.className = saveFailure ? 'armed' : '';\n });\n button('Exhaust consume fence', function () { shell.exhaustConsumeFence(); });\n button('Set wallet\u2026', function () {\n const lix = window.prompt('Simulated LIX balance', '500');\n if (lix !== null && lix !== '') shell.setWallet({ lix: Number(lix) });\n });\n button('Award achievement\u2026', function () {\n const key = window.prompt('Achievement key to award (must be in the local registry)');\n if (key) shell.awardAchievement(key);\n });\n button('Print achievement progress', function () { shell.getAchievementsProgress(); });\n button('Add 5 min playtime', function () {\n shell.addPlaytime(300);\n append(prefix + ' +300s playtime added', 'sim');\n });\n button('Bump analytics event\u2026', function () {\n const name = window.prompt('Analytics event name (e.g. world_entered) \u2014 real events never reach a shell');\n if (name) shell.bumpAnalyticsEvent(name);\n });\n button('Set MP player var\u2026', function () {\n const field = window.prompt('Persistent playerVar (dot-path, e.g. wins)');\n if (!field) return;\n const value = window.prompt('Numeric value for \"' + field + '\"', '1');\n if (value !== null && value !== '') shell.setMpPlayerVar(field, Number(value));\n });\n button('Reset earned achievements', function () { shell.resetAchievements(); });\n button('Signed in: yes', function (el) {\n signedIn = !signedIn;\n shell.setSignedIn(signedIn);\n el.textContent = 'Signed in: ' + (signedIn ? 'yes' : 'no');\n el.className = signedIn ? '' : 'armed';\n });\n button('Print state', function () { append(prefix + ' state ' + show(shell.getState()), 'sim'); });\n button('Reload world', function () { frame.src = config.worldOrigin + '/?t=' + Date.now(); });\n button('Clear log', function () { logEl.textContent = ''; });\n\n // Playtime evidence accrues while the shell is open \u2014 the local stand-in for the server's rollup.\n setInterval(function () { shell.addPlaytime(15); }, 15000);\n\n append(prefix + ' shell ready \u2014 the room lane (DSL rules, consume/save/saveRoom) is NOT simulated; ' +\n 'the debug menu stands in for awardAchievement and the persistent-var flush.', 'sim');\n}\n\nmain().catch(function (err) {\n append('dev shell failed to start: ' + String(err && err.stack ? err.stack : err), 'err');\n});\n";
2
+ export declare const DEV_SHELL_PAGE_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>helix dev \u2014 SIMULATED shell</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body { margin: 0; height: 100vh; display: flex; flex-direction: column;\n font: 13px/1.5 ui-sans-serif, system-ui, sans-serif; background: #0d0f13; color: #e6e8ec; }\n .banner { padding: 8px 14px; background: #7a4b00; color: #fff5e0; font-weight: 600; }\n .banner code { background: rgba(0,0,0,.3); padding: 1px 5px; border-radius: 3px; }\n main { flex: 1; display: flex; min-height: 0; }\n .stage { flex: 1; min-width: 0; background: #000; position: relative; }\n iframe { width: 100%; height: 100%; border: 0; display: block; }\n #toast { position: absolute; left: 50%; top: 18px; transform: translateX(-50%); max-width: 80%;\n background: #16241a; color: #c9f0c9; border: 1px solid #3f7a3f; border-radius: 6px;\n padding: 8px 14px; font-weight: 600; opacity: 0; pointer-events: none; transition: opacity .25s; }\n #toast.show { opacity: 1; }\n #confirm { position: absolute; inset: 0; display: none; align-items: center; justify-content: center;\n background: rgba(0,0,0,.55); }\n #confirm.show { display: flex; }\n .confirm-card { background: #161a21; border: 1px solid #3a4152; border-radius: 10px; padding: 18px 20px;\n width: 320px; box-shadow: 0 12px 40px rgba(0,0,0,.5); }\n .confirm-card .tag { color: #ffcf8a; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }\n .confirm-card h3 { margin: 4px 0 10px; font-size: 15px; }\n .confirm-card .price { color: #b7bdc8; }\n .confirm-card .price b { color: #e6e8ec; }\n .confirm-card .row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }\n .confirm-card button.buy { border-color: #3f7a3f; color: #c9f0c9; }\n .panel { width: 320px; border-left: 1px solid #23262d; display: flex; flex-direction: column; min-height: 0; }\n .panel h2 { margin: 0; padding: 8px 12px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;\n color: #8b93a1; border-bottom: 1px solid #23262d; }\n .meta { padding: 8px 12px; color: #8b93a1; border-bottom: 1px solid #23262d; }\n .meta b { color: #e6e8ec; font-weight: 600; }\n .menu { padding: 8px; display: grid; gap: 6px; border-bottom: 1px solid #23262d; }\n button { font: inherit; text-align: left; padding: 6px 9px; border-radius: 5px; cursor: pointer;\n background: #1a1d24; color: #e6e8ec; border: 1px solid #2c3039; }\n button:hover { background: #232732; }\n button.armed { border-color: #b4741a; color: #ffcf8a; }\n #log { flex: 1; margin: 0; padding: 8px 12px; overflow: auto; white-space: pre-wrap; word-break: break-word;\n font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; color: #b7bdc8; }\n #log .sim { color: #9fd39f; }\n #log .err { color: #ff9b9b; }\n</style>\n</head>\n<body>\n<div class=\"banner\">SIMULATED shell \u2014 helix dev. Purchases, saves and achievements are simulated locally; nothing here proves a product is registered or that settlement works.</div>\n<main>\n <div class=\"stage\"><iframe id=\"world\" title=\"world\" allow=\"autoplay; microphone; gamepad; fullscreen\"></iframe><div id=\"toast\"></div><div id=\"confirm\"><div class=\"confirm-card\"><div class=\"tag\">Simulated purchase</div><h3 id=\"confirmTitle\"></h3><div class=\"price\" id=\"confirmPrice\"></div><div class=\"row\"><button id=\"confirmCancel\">Cancel</button><button id=\"confirmBuy\" class=\"buy\">Buy</button></div></div></div></div>\n <aside class=\"panel\">\n <h2>helix dev \u2014 simulated</h2>\n <div class=\"meta\" id=\"meta\">loading\u2026</div>\n <h2>Debug menu</h2>\n <div class=\"menu\" id=\"menu\"></div>\n <h2>Log</h2>\n <pre id=\"log\"></pre>\n </aside>\n</main>\n<script type=\"module\" src=\"/dev/shell.js\"></script>\n</body>\n</html>\n";
3
+ export declare const DEV_SHELL_PAGE_JS = "// Served by `helix dev`. Everything here is SIMULATED \u2014 see the banner.\nconst logEl = document.getElementById('log');\nconst menuEl = document.getElementById('menu');\nconst metaEl = document.getElementById('meta');\nconst frame = document.getElementById('world');\nconst toastEl = document.getElementById('toast');\n\nlet toastTimer = null;\nfunction toast(text) {\n toastEl.textContent = text;\n toastEl.className = 'show';\n if (toastTimer) clearTimeout(toastTimer);\n toastTimer = setTimeout(function () { toastEl.className = ''; }, 4000);\n}\n\n// The real shell's purchase confirm, simulated: one dialog at a time, queued in arrival order.\nconst confirmEl = document.getElementById('confirm');\nlet confirmChain = Promise.resolve();\nfunction confirmPurchase(req) {\n const ask = function () {\n return new Promise(function (done) {\n document.getElementById('confirmTitle').textContent = req.title;\n document.getElementById('confirmPrice').innerHTML = req.isFree\n ? 'Free claim &middot; balance stays <b>' + req.balanceLix + ' LIX</b>'\n : '<b>' + req.priceLix + ' LIX</b> &middot; balance ' + req.balanceLix + ' &rarr; <b>' + req.balanceAfterLix + '</b>';\n confirmEl.className = 'show';\n const buy = document.getElementById('confirmBuy');\n const cancel = document.getElementById('confirmCancel');\n const settle = function (verdict) {\n confirmEl.className = '';\n buy.onclick = null;\n cancel.onclick = null;\n done(verdict);\n };\n buy.onclick = function () { settle(true); };\n cancel.onclick = function () { settle(false); };\n });\n };\n const result = confirmChain.then(ask);\n confirmChain = result.then(function () {}, function () {});\n return result;\n}\n\nfunction append(text, kind) {\n const line = document.createElement('div');\n if (kind) line.className = kind;\n line.textContent = text;\n logEl.appendChild(line);\n while (logEl.childElementCount > 500) logEl.removeChild(logEl.firstChild);\n logEl.scrollTop = logEl.scrollHeight;\n}\n\nfunction show(value) {\n if (value === undefined) return '';\n try {\n return typeof value === 'string' ? value : JSON.stringify(value);\n } catch (err) {\n return String(value);\n }\n}\n\nfunction button(label, onClick) {\n const el = document.createElement('button');\n el.textContent = label;\n el.addEventListener('click', function () { onClick(el); });\n menuEl.appendChild(el);\n return el;\n}\n\nasync function main() {\n const config = await (await fetch('/dev/config')).json();\n frame.src = config.worldOrigin + '/';\n\n const devShell = await import('/sdk/dev-shell/index.js');\n const prefix = devShell.DEV_SHELL_LOG_PREFIX;\n\n let pending = null;\n let sending = false;\n async function flush() {\n if (sending || pending === null) return;\n sending = true;\n const body = pending;\n pending = null;\n try {\n await fetch('/dev/state', { method: 'POST', headers: { 'content-type': 'application/json' }, body: body });\n } catch (err) {\n append('state was not saved: ' + String(err), 'err');\n }\n sending = false;\n flush();\n }\n\n // A fresh player has no stored state, so the seeded achievement registry is minted into a new one here.\n const freshState = !config.state && config.achievements\n ? devShell.createDevShellState({ identity: config.user, achievements: config.achievements })\n : undefined;\n\n const shell = devShell.createDevShell({\n worldWindow: frame,\n worldOrigin: config.worldOrigin,\n world: config.world,\n user: config.user,\n state: config.state || freshState,\n products: config.products || undefined,\n debug: true,\n onAchievementEarned: function (a) {\n toast('Achievement unlocked (SIMULATED): ' + a.name + (a.points ? ' (+' + a.points + ')' : ''));\n },\n confirmPurchase: confirmPurchase,\n log: function (message, detail) {\n append(detail === undefined ? message : message + ' ' + show(detail), message.indexOf(prefix) === 0 ? 'sim' : null);\n if (detail === undefined) console.log(message);\n else console.log(message, detail);\n },\n onStateChange: function (state) {\n pending = JSON.stringify(state);\n flush();\n },\n });\n window.helixDevShell = shell;\n\n metaEl.innerHTML =\n '<div>world <b>' + config.world.title + '</b> (' + config.world.slug + ')</div>' +\n '<div>player <b>' + config.user.id + '</b></div>' +\n '<div>served from ' + config.worldOrigin + '</div>';\n\n let failMode = 'off';\n let saveFailure = false;\n let signedIn = true;\n\n button('Grant all entitlements', function () { shell.grantAllEntitlements(); });\n button('Grant one entitlement\u2026', function () {\n const ref = window.prompt('Product key or ref to grant');\n if (ref) shell.grantEntitlement(ref);\n });\n button('Remove one entitlement\u2026', function () {\n const ref = window.prompt('Product key or ref to remove');\n if (ref) shell.removeEntitlement(ref);\n });\n button('Print owned entitlements', function () {\n append(prefix + ' entitlements ' + show(shell.getEntitlementsSnapshot()), 'sim');\n });\n const failButton = button('Purchases always fail: off', function (el) {\n failMode = shell.cyclePurchasesAlwaysFail();\n el.textContent = 'Purchases always fail: ' + failMode;\n el.className = failMode === 'off' ? '' : 'armed';\n });\n failButton.textContent = 'Purchases always fail: ' + failMode;\n button('Reset player document', function () { shell.resetPlayerDocument(); });\n button('Force save failure: off', function (el) {\n saveFailure = !saveFailure;\n shell.setForceSaveFailure(saveFailure);\n el.textContent = 'Force save failure: ' + (saveFailure ? 'on' : 'off');\n el.className = saveFailure ? 'armed' : '';\n });\n button('Exhaust consume fence', function () { shell.exhaustConsumeFence(); });\n button('Set wallet\u2026', function () {\n const lix = window.prompt('Simulated LIX balance', '500');\n if (lix !== null && lix !== '') shell.setWallet({ lix: Number(lix) });\n });\n button('Award achievement\u2026', function () {\n const key = window.prompt('Achievement key to award (must be in the local registry)');\n if (key) shell.awardAchievement(key);\n });\n button('Print achievement progress', function () { shell.getAchievementsProgress(); });\n button('Add 5 min playtime', function () {\n shell.addPlaytime(300);\n append(prefix + ' +300s playtime added', 'sim');\n });\n button('Bump analytics event\u2026', function () {\n const name = window.prompt('Analytics event name (e.g. world_entered) \u2014 real events never reach a shell');\n if (name) shell.bumpAnalyticsEvent(name);\n });\n button('Set MP player var\u2026', function () {\n const field = window.prompt('Persistent playerVar (dot-path, e.g. wins)');\n if (!field) return;\n const value = window.prompt('Numeric value for \"' + field + '\"', '1');\n if (value !== null && value !== '') shell.setMpPlayerVar(field, Number(value));\n });\n button('Reset earned achievements', function () { shell.resetAchievements(); });\n button('Seed leaderboard score\u2026', function () {\n const board = window.prompt('Board name (declared boards are room-written live \u2014 this is the room, by hand)', 'main');\n if (!board) return;\n const player = window.prompt('Player name (empty = the simulated player)');\n const score = window.prompt('Score', '100');\n if (score === null || score === '') return;\n shell.seedLeaderboardScore(board, Number(score), player ? { subject: player, displayName: player } : {});\n });\n button('Set data-store doc\u2026', function () {\n const key = window.prompt('Document key (world-global / mp:world:* / reserved keys allowed \u2014 this is the server, by hand)');\n if (!key) return;\n const raw = window.prompt('JSON value for \"' + key + '\"', '{}');\n if (raw === null) return;\n try { shell.seedDocument(key, JSON.parse(raw)); }\n catch (err) { append('not valid JSON: ' + String(err), 'err'); }\n });\n button('Push shell event\u2026', function () {\n const event = window.prompt('Shell event (avatar-changed, equipment-changed, overlay-open, overlay-closed, \u2026)');\n if (event) shell.pushEvent(event);\n });\n button('Signed in: yes', function (el) {\n signedIn = !signedIn;\n shell.setSignedIn(signedIn);\n el.textContent = 'Signed in: ' + (signedIn ? 'yes' : 'no');\n el.className = signedIn ? '' : 'armed';\n });\n button('Print state', function () { append(prefix + ' state ' + show(shell.getState()), 'sim'); });\n button('Reload world', function () { frame.src = config.worldOrigin + '/?t=' + Date.now(); });\n button('Clear log', function () { logEl.textContent = ''; });\n\n // Playtime evidence accrues while the shell is open \u2014 the local stand-in for the server's rollup.\n setInterval(function () { shell.addPlaytime(15); }, 15000);\n\n append(prefix + ' shell ready \u2014 the room lane (DSL rules, consume/save/saveRoom) is NOT simulated; ' +\n 'the debug menu stands in for awardAchievement and the persistent-var flush.', 'sim');\n}\n\nmain().catch(function (err) {\n append('dev shell failed to start: ' + String(err && err.stack ? err.stack : err), 'err');\n});\n";
@@ -29,6 +29,17 @@ exports.DEV_SHELL_PAGE_HTML = `<!doctype html>
29
29
  background: #16241a; color: #c9f0c9; border: 1px solid #3f7a3f; border-radius: 6px;
30
30
  padding: 8px 14px; font-weight: 600; opacity: 0; pointer-events: none; transition: opacity .25s; }
31
31
  #toast.show { opacity: 1; }
32
+ #confirm { position: absolute; inset: 0; display: none; align-items: center; justify-content: center;
33
+ background: rgba(0,0,0,.55); }
34
+ #confirm.show { display: flex; }
35
+ .confirm-card { background: #161a21; border: 1px solid #3a4152; border-radius: 10px; padding: 18px 20px;
36
+ width: 320px; box-shadow: 0 12px 40px rgba(0,0,0,.5); }
37
+ .confirm-card .tag { color: #ffcf8a; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
38
+ .confirm-card h3 { margin: 4px 0 10px; font-size: 15px; }
39
+ .confirm-card .price { color: #b7bdc8; }
40
+ .confirm-card .price b { color: #e6e8ec; }
41
+ .confirm-card .row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
42
+ .confirm-card button.buy { border-color: #3f7a3f; color: #c9f0c9; }
32
43
  .panel { width: 320px; border-left: 1px solid #23262d; display: flex; flex-direction: column; min-height: 0; }
33
44
  .panel h2 { margin: 0; padding: 8px 12px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
34
45
  color: #8b93a1; border-bottom: 1px solid #23262d; }
@@ -48,7 +59,7 @@ exports.DEV_SHELL_PAGE_HTML = `<!doctype html>
48
59
  <body>
49
60
  <div class="banner">${exports.DEV_SHELL_BANNER}</div>
50
61
  <main>
51
- <div class="stage"><iframe id="world" title="world" allow="autoplay; microphone; gamepad; fullscreen"></iframe><div id="toast"></div></div>
62
+ <div class="stage"><iframe id="world" title="world" allow="autoplay; microphone; gamepad; fullscreen"></iframe><div id="toast"></div><div id="confirm"><div class="confirm-card"><div class="tag">Simulated purchase</div><h3 id="confirmTitle"></h3><div class="price" id="confirmPrice"></div><div class="row"><button id="confirmCancel">Cancel</button><button id="confirmBuy" class="buy">Buy</button></div></div></div></div>
52
63
  <aside class="panel">
53
64
  <h2>helix dev — simulated</h2>
54
65
  <div class="meta" id="meta">loading…</div>
@@ -77,6 +88,34 @@ function toast(text) {
77
88
  toastTimer = setTimeout(function () { toastEl.className = ''; }, 4000);
78
89
  }
79
90
 
91
+ // The real shell's purchase confirm, simulated: one dialog at a time, queued in arrival order.
92
+ const confirmEl = document.getElementById('confirm');
93
+ let confirmChain = Promise.resolve();
94
+ function confirmPurchase(req) {
95
+ const ask = function () {
96
+ return new Promise(function (done) {
97
+ document.getElementById('confirmTitle').textContent = req.title;
98
+ document.getElementById('confirmPrice').innerHTML = req.isFree
99
+ ? 'Free claim &middot; balance stays <b>' + req.balanceLix + ' LIX</b>'
100
+ : '<b>' + req.priceLix + ' LIX</b> &middot; balance ' + req.balanceLix + ' &rarr; <b>' + req.balanceAfterLix + '</b>';
101
+ confirmEl.className = 'show';
102
+ const buy = document.getElementById('confirmBuy');
103
+ const cancel = document.getElementById('confirmCancel');
104
+ const settle = function (verdict) {
105
+ confirmEl.className = '';
106
+ buy.onclick = null;
107
+ cancel.onclick = null;
108
+ done(verdict);
109
+ };
110
+ buy.onclick = function () { settle(true); };
111
+ cancel.onclick = function () { settle(false); };
112
+ });
113
+ };
114
+ const result = confirmChain.then(ask);
115
+ confirmChain = result.then(function () {}, function () {});
116
+ return result;
117
+ }
118
+
80
119
  function append(text, kind) {
81
120
  const line = document.createElement('div');
82
121
  if (kind) line.className = kind;
@@ -142,6 +181,7 @@ async function main() {
142
181
  onAchievementEarned: function (a) {
143
182
  toast('Achievement unlocked (SIMULATED): ' + a.name + (a.points ? ' (+' + a.points + ')' : ''));
144
183
  },
184
+ confirmPurchase: confirmPurchase,
145
185
  log: function (message, detail) {
146
186
  append(detail === undefined ? message : message + ' ' + show(detail), message.indexOf(prefix) === 0 ? 'sim' : null);
147
187
  if (detail === undefined) console.log(message);
@@ -213,6 +253,26 @@ async function main() {
213
253
  if (value !== null && value !== '') shell.setMpPlayerVar(field, Number(value));
214
254
  });
215
255
  button('Reset earned achievements', function () { shell.resetAchievements(); });
256
+ button('Seed leaderboard score…', function () {
257
+ const board = window.prompt('Board name (declared boards are room-written live — this is the room, by hand)', 'main');
258
+ if (!board) return;
259
+ const player = window.prompt('Player name (empty = the simulated player)');
260
+ const score = window.prompt('Score', '100');
261
+ if (score === null || score === '') return;
262
+ shell.seedLeaderboardScore(board, Number(score), player ? { subject: player, displayName: player } : {});
263
+ });
264
+ button('Set data-store doc…', function () {
265
+ const key = window.prompt('Document key (world-global / mp:world:* / reserved keys allowed — this is the server, by hand)');
266
+ if (!key) return;
267
+ const raw = window.prompt('JSON value for "' + key + '"', '{}');
268
+ if (raw === null) return;
269
+ try { shell.seedDocument(key, JSON.parse(raw)); }
270
+ catch (err) { append('not valid JSON: ' + String(err), 'err'); }
271
+ });
272
+ button('Push shell event…', function () {
273
+ const event = window.prompt('Shell event (avatar-changed, equipment-changed, overlay-open, overlay-closed, …)');
274
+ if (event) shell.pushEvent(event);
275
+ });
216
276
  button('Signed in: yes', function (el) {
217
277
  signedIn = !signedIn;
218
278
  shell.setSignedIn(signedIn);
@@ -1 +1 @@
1
- {"version":3,"file":"shellPage.js","sourceRoot":"","sources":["../../src/dev/shellPage.ts"],"names":[],"mappings":";AAAA,yGAAyG;AACzG,sGAAsG;AACtG,6FAA6F;AAC7F,EAAE;AACF,yGAAyG;AACzG,eAAe;;;AAEF,QAAA,gBAAgB,GAC3B,wFAAwF;IACxF,uEAAuE,CAAC;AAE7D,QAAA,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAqCb,wBAAgB;;;;;;;;;;;;;;;CAerC,CAAC;AAEW,QAAA,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2KhC,CAAC"}
1
+ {"version":3,"file":"shellPage.js","sourceRoot":"","sources":["../../src/dev/shellPage.ts"],"names":[],"mappings":";AAAA,yGAAyG;AACzG,sGAAsG;AACtG,6FAA6F;AAC7F,EAAE;AACF,yGAAyG;AACzG,eAAe;;;AAEF,QAAA,gBAAgB,GAC3B,wFAAwF;IACxF,uEAAuE,CAAC;AAE7D,QAAA,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAgDb,wBAAgB;;;;;;;;;;;;;;;CAerC,CAAC;AAEW,QAAA,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4NhC,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Canonical universal-item distribution vocabulary.
3
+ *
4
+ * An item definition says what the item is. A distribution is one creator-
5
+ * authorized primary acquisition route. Keeping this builder network-free
6
+ * lets `--dry-run` exercise exactly the payload the real command sends.
7
+ */
8
+ export declare const ITEM_EDITION_MODES: readonly ["standard", "collectible"];
9
+ export type ItemEditionMode = (typeof ITEM_EDITION_MODES)[number];
10
+ export declare const ITEM_DISTRIBUTION_CHANNELS: readonly ["marketplace", "world"];
11
+ export type ItemDistributionChannel = (typeof ITEM_DISTRIBUTION_CHANNELS)[number];
12
+ export declare const DISTRIBUTION_KEY_PATTERN: RegExp;
13
+ export type ItemDistributionInput = {
14
+ itemId?: string;
15
+ channel: ItemDistributionChannel;
16
+ priceLix: number;
17
+ worldId?: string;
18
+ worldSlug?: string;
19
+ key?: string;
20
+ maxPerPlayer?: number | null;
21
+ maxClaims?: number | null;
22
+ startsAt?: string | null;
23
+ endsAt?: string | null;
24
+ };
25
+ export type ItemDistributionFields = {
26
+ itemId?: string;
27
+ channel: ItemDistributionChannel;
28
+ priceLix: number;
29
+ worldId?: string;
30
+ /** Offline plan only; the CLI resolves it to worldId before a network write. */
31
+ worldSlug?: string;
32
+ key?: string;
33
+ maxPerPlayer?: number | null;
34
+ maxClaims?: number | null;
35
+ startsAt?: string | null;
36
+ endsAt?: string | null;
37
+ };
38
+ export type ItemDistributionUpdateInput = {
39
+ priceLix?: number;
40
+ maxPerPlayer?: number | null;
41
+ maxClaims?: number | null;
42
+ startsAt?: string | null;
43
+ endsAt?: string | null;
44
+ };
45
+ export type ItemDistributionPatch = ItemDistributionUpdateInput;
46
+ export declare function buildItemDistribution(input: ItemDistributionInput): ItemDistributionFields;
47
+ export declare function buildItemDistributionUpdate(input: ItemDistributionUpdateInput): ItemDistributionPatch;
48
+ export declare function editionFields(collectibleSupply?: number): {
49
+ editionMode: ItemEditionMode;
50
+ collectibleSupply?: number;
51
+ };
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ /**
3
+ * Canonical universal-item distribution vocabulary.
4
+ *
5
+ * An item definition says what the item is. A distribution is one creator-
6
+ * authorized primary acquisition route. Keeping this builder network-free
7
+ * lets `--dry-run` exercise exactly the payload the real command sends.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.DISTRIBUTION_KEY_PATTERN = exports.ITEM_DISTRIBUTION_CHANNELS = exports.ITEM_EDITION_MODES = void 0;
11
+ exports.buildItemDistribution = buildItemDistribution;
12
+ exports.buildItemDistributionUpdate = buildItemDistributionUpdate;
13
+ exports.editionFields = editionFields;
14
+ exports.ITEM_EDITION_MODES = ['standard', 'collectible'];
15
+ exports.ITEM_DISTRIBUTION_CHANNELS = ['marketplace', 'world'];
16
+ exports.DISTRIBUTION_KEY_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?$/;
17
+ const positiveInt = (name, value) => {
18
+ if (value === undefined || value === null)
19
+ return;
20
+ if (!Number.isInteger(value) || value < 1) {
21
+ throw new Error(`${name} must be a whole number >= 1, or unlimited`);
22
+ }
23
+ };
24
+ const price = (value) => {
25
+ if (!Number.isFinite(value) || value < 0) {
26
+ throw new Error(`--price-lix must be a number >= 0 LIX (got ${value})`);
27
+ }
28
+ return value;
29
+ };
30
+ const timestamp = (name, value) => {
31
+ if (value === undefined || value === null)
32
+ return value;
33
+ const parsed = new Date(value);
34
+ if (!Number.isFinite(parsed.valueOf())) {
35
+ throw new Error(`${name} must be an ISO-8601 timestamp (got "${value}")`);
36
+ }
37
+ return parsed.toISOString();
38
+ };
39
+ function buildItemDistribution(input) {
40
+ if (!exports.ITEM_DISTRIBUTION_CHANNELS.includes(input.channel)) {
41
+ throw new Error(`--channel must be marketplace or world (got "${input.channel}")`);
42
+ }
43
+ const priceLix = price(input.priceLix);
44
+ positiveInt('--max-per-player', input.maxPerPlayer);
45
+ positiveInt('--max-claims', input.maxClaims);
46
+ const startsAt = timestamp('--starts-at', input.startsAt);
47
+ const endsAt = timestamp('--ends-at', input.endsAt);
48
+ if (startsAt && endsAt && new Date(startsAt) >= new Date(endsAt)) {
49
+ throw new Error('--ends-at must be later than --starts-at');
50
+ }
51
+ const worldId = input.worldId?.trim();
52
+ const worldSlug = input.worldSlug?.trim();
53
+ const key = input.key?.trim();
54
+ if (input.channel === 'world') {
55
+ if (!worldId && !worldSlug) {
56
+ throw new Error('a world distribution requires --world <slug>');
57
+ }
58
+ if (!key)
59
+ throw new Error('a world distribution requires --distribution-key <key>');
60
+ if (!exports.DISTRIBUTION_KEY_PATTERN.test(key)) {
61
+ throw new Error(`--distribution-key "${key}" must use lowercase letters, digits, _ or - (1-64 characters)`);
62
+ }
63
+ }
64
+ else if (worldId || input.worldSlug || key) {
65
+ throw new Error('a marketplace distribution cannot carry --world or --distribution-key');
66
+ }
67
+ return {
68
+ ...(input.itemId ? { itemId: input.itemId } : {}),
69
+ channel: input.channel,
70
+ priceLix,
71
+ ...(worldId ? { worldId } : {}),
72
+ ...(!worldId && worldSlug ? { worldSlug } : {}),
73
+ ...(key ? { key } : {}),
74
+ ...(input.maxPerPlayer !== undefined ? { maxPerPlayer: input.maxPerPlayer } : {}),
75
+ ...(input.maxClaims !== undefined ? { maxClaims: input.maxClaims } : {}),
76
+ ...(startsAt !== undefined ? { startsAt } : {}),
77
+ ...(endsAt !== undefined ? { endsAt } : {}),
78
+ };
79
+ }
80
+ function buildItemDistributionUpdate(input) {
81
+ if (input.priceLix === undefined && input.maxPerPlayer === undefined &&
82
+ input.maxClaims === undefined && input.startsAt === undefined && input.endsAt === undefined) {
83
+ throw new Error('no distribution changes supplied');
84
+ }
85
+ if (input.priceLix !== undefined)
86
+ price(input.priceLix);
87
+ positiveInt('--max-per-player', input.maxPerPlayer);
88
+ positiveInt('--max-claims', input.maxClaims);
89
+ const startsAt = timestamp('--starts-at', input.startsAt);
90
+ const endsAt = timestamp('--ends-at', input.endsAt);
91
+ if (startsAt && endsAt && new Date(startsAt) >= new Date(endsAt)) {
92
+ throw new Error('--ends-at must be later than --starts-at');
93
+ }
94
+ return {
95
+ ...(input.priceLix !== undefined ? { priceLix: input.priceLix } : {}),
96
+ ...(input.maxPerPlayer !== undefined ? { maxPerPlayer: input.maxPerPlayer } : {}),
97
+ ...(input.maxClaims !== undefined ? { maxClaims: input.maxClaims } : {}),
98
+ ...(startsAt !== undefined ? { startsAt } : {}),
99
+ ...(endsAt !== undefined ? { endsAt } : {}),
100
+ };
101
+ }
102
+ function editionFields(collectibleSupply) {
103
+ if (collectibleSupply === undefined)
104
+ return { editionMode: 'standard' };
105
+ if (!Number.isInteger(collectibleSupply) || collectibleSupply < 1) {
106
+ throw new Error('--collectible-supply must be a whole number >= 1');
107
+ }
108
+ return { editionMode: 'collectible', collectibleSupply };
109
+ }
110
+ //# sourceMappingURL=distribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distribution.js","sourceRoot":"","sources":["../src/distribution.ts"],"names":[],"mappings":"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"}