@helix3/helix-cli 0.1.13-helix3.85 → 0.1.13-helix3.87
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 +50 -5
- package/dist/distribution.d.ts +51 -0
- package/dist/distribution.js +110 -0
- package/dist/distribution.js.map +1 -0
- package/dist/index.js +236 -12
- package/dist/index.js.map +1 -1
- package/dist/item.d.ts +5 -4
- package/dist/item.js +7 -17
- package/dist/item.js.map +1 -1
- package/dist/lib.d.ts +90 -1
- package/dist/lib.js +94 -6
- package/dist/lib.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
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> [--
|
|
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`
|
|
243
|
-
|
|
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. `
|
|
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
|
-
|
|
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
|
|
@@ -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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"}
|
package/dist/index.js
CHANGED
|
@@ -524,6 +524,56 @@ function parsePriceLix(value) {
|
|
|
524
524
|
fail(`✖ --price-lix must be a number, got '${value}'`);
|
|
525
525
|
return price;
|
|
526
526
|
}
|
|
527
|
+
function parsePositiveInt(value, flag) {
|
|
528
|
+
const parsed = Number(value);
|
|
529
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
530
|
+
fail(`✖ ${flag} must be a whole number >= 1`);
|
|
531
|
+
return parsed;
|
|
532
|
+
}
|
|
533
|
+
function parseLimit(value, flag) {
|
|
534
|
+
if (value.trim().toLowerCase() === 'unlimited')
|
|
535
|
+
return null;
|
|
536
|
+
return parsePositiveInt(value, flag);
|
|
537
|
+
}
|
|
538
|
+
function renderItemQuote(q) {
|
|
539
|
+
const lines = [
|
|
540
|
+
` edition: ${q.editionMode}${q.collectibleSupply !== null ? ` · fixed supply ${q.collectibleSupply} · creator serial #${q.creatorSerial ?? 1}` : ' · unlimited · no serials'}`,
|
|
541
|
+
` publish total: ${q.publishFeeLix} LIX`,
|
|
542
|
+
` review fee: ${q.reviewFeeLix} LIX · normally non-refundable`,
|
|
543
|
+
` compliance bond: ${q.complianceBondLix} LIX · returned after approval + ${q.complianceBondReleaseDays} days in good standing`,
|
|
544
|
+
];
|
|
545
|
+
if (q.editionMode === 'collectible') {
|
|
546
|
+
lines.push(` free Collectible allowance: ${q.includedCollectibleUnits} included · ${q.chargeableCollectibleUnits} additionally authorized`, ` issuance authorization: ${q.issuanceAuthorizationFeeLix} LIX`, ` resale: ${q.creatorRoyaltyPercent}% creator royalty · seller fee ${q.sellerPlatformFeePercentByTier.free}% Free / ${q.sellerPlatformFeePercentByTier.plus}% Plus (future ${q.sellerPlatformFeePercentByTier.gold}% Gold / ${q.sellerPlatformFeePercentByTier.diamond}% Diamond)`, ` buyer surcharge: ${q.buyerSurchargePercent}% · self-purchase prohibited · ${q.resaleCooldownHours}h relisting cooldown`, ` resale eligibility: Basic email; Verified unique mobile + good standing (immediate); Identity Verified reserved for later high-risk capability; Business Verified supported`, ` payout hold: ${q.payoutHoldBaseDays} days base · risk-extension up to ${q.payoutHoldMaxDays} days`);
|
|
547
|
+
}
|
|
548
|
+
if (q.distribution) {
|
|
549
|
+
lines.push(` initial ${q.distribution.channel}: ${q.distribution.priceLix === 0 ? 'Claim (free)' : `Buy at ${q.distribution.priceLix} LIX`} · floor ${q.distribution.priceFloorLix} LIX`);
|
|
550
|
+
}
|
|
551
|
+
lines.push(` total due now: ${q.totalDueLix} LIX`);
|
|
552
|
+
return lines;
|
|
553
|
+
}
|
|
554
|
+
function initialDistributionFromFlags(opts) {
|
|
555
|
+
const any = opts.distributionChannel !== undefined || opts.distributionPriceLix !== undefined ||
|
|
556
|
+
opts.world !== undefined || opts.distributionKey !== undefined || opts.maxPerPlayer !== undefined ||
|
|
557
|
+
opts.maxClaims !== undefined || opts.startsAt !== undefined || opts.endsAt !== undefined;
|
|
558
|
+
if (!any)
|
|
559
|
+
return undefined;
|
|
560
|
+
if (!opts.distributionChannel)
|
|
561
|
+
fail('✖ initial distribution flags require --distribution-channel');
|
|
562
|
+
if (opts.distributionPriceLix === undefined)
|
|
563
|
+
fail('✖ an initial distribution requires --distribution-price-lix');
|
|
564
|
+
const input = {
|
|
565
|
+
channel: opts.distributionChannel,
|
|
566
|
+
priceLix: parsePriceLix(opts.distributionPriceLix),
|
|
567
|
+
worldSlug: opts.world,
|
|
568
|
+
key: opts.distributionKey,
|
|
569
|
+
maxPerPlayer: opts.maxPerPlayer !== undefined ? parseLimit(opts.maxPerPlayer, '--max-per-player') : undefined,
|
|
570
|
+
maxClaims: opts.maxClaims !== undefined ? parseLimit(opts.maxClaims, '--max-claims') : undefined,
|
|
571
|
+
startsAt: opts.startsAt,
|
|
572
|
+
endsAt: opts.endsAt,
|
|
573
|
+
};
|
|
574
|
+
(0, lib_1.buildItemDistribution)(input);
|
|
575
|
+
return input;
|
|
576
|
+
}
|
|
527
577
|
item
|
|
528
578
|
.command('publish')
|
|
529
579
|
.description('Publish a universal item from a local .glb — the server verifies the mesh inline and returns the verdict in the same round trip')
|
|
@@ -541,11 +591,19 @@ item
|
|
|
541
591
|
.option('--slug <slug>', 'url slug (default: derived from the title)')
|
|
542
592
|
.option('--description <text>', 'item description')
|
|
543
593
|
.option('--thumbnail <file>', 'thumbnail image (png/jpg/webp); rendered from the mesh when omitted, required for an avatar')
|
|
544
|
-
.option('--
|
|
545
|
-
.option('--
|
|
594
|
+
.option('--collectible-supply <n>', 'select Collectible edition with this fixed positive supply; omitted means Standard')
|
|
595
|
+
.option('--distribution-channel <channel>', 'create one initial route: marketplace or world')
|
|
596
|
+
.option('--distribution-price-lix <n>', 'initial route price; 0 is Claim, positive is Buy')
|
|
597
|
+
.option('--world <slug>', 'world that owns an initial world route')
|
|
598
|
+
.option('--distribution-key <key>', 'stable key world code passes to purchaseDistributionKey')
|
|
599
|
+
.option('--max-per-player <n|unlimited>', 'initial route per-player cap; free Collectibles default to 1 server-side')
|
|
600
|
+
.option('--max-claims <n|unlimited>', 'initial route-wide issuance cap')
|
|
601
|
+
.option('--starts-at <iso>', 'initial route activation time (ISO-8601)')
|
|
602
|
+
.option('--ends-at <iso>', 'initial route end time (ISO-8601)')
|
|
546
603
|
.option('--tags <a,b,c>', 'comma-separated search keywords')
|
|
547
604
|
.option('--allow-oversize', 'relax the mesh size/polycount ceilings for a deliberately heavy asset')
|
|
548
605
|
.option('--dry-run', 'validate everything locally and print what WOULD be sent; no upload, no fee')
|
|
606
|
+
.option('--quote', 'request the server-owned publish/issuance quote; no upload or fee')
|
|
549
607
|
.option('--json', 'machine-readable output')
|
|
550
608
|
.action(async (mesh, opts) => {
|
|
551
609
|
const publishOptions = {
|
|
@@ -557,11 +615,15 @@ item
|
|
|
557
615
|
genders: opts.gender !== undefined ? (0, lib_1.parseGenders)(opts.gender) : undefined,
|
|
558
616
|
slug: opts.slug,
|
|
559
617
|
description: opts.description,
|
|
560
|
-
|
|
561
|
-
|
|
618
|
+
collectibleSupply: opts.collectibleSupply !== undefined
|
|
619
|
+
? parsePositiveInt(opts.collectibleSupply, '--collectible-supply')
|
|
620
|
+
: undefined,
|
|
621
|
+
initialDistribution: initialDistributionFromFlags(opts),
|
|
562
622
|
tags: opts.tags !== undefined ? (0, lib_1.parseTags)(opts.tags) : undefined,
|
|
563
623
|
allowOversize: opts.allowOversize === true,
|
|
564
624
|
};
|
|
625
|
+
if (opts.dryRun && opts.quote)
|
|
626
|
+
fail('✖ choose --dry-run (offline) or --quote (server-owned fees), not both');
|
|
565
627
|
// --dry-run stops before ANY network call, so it works logged out — the
|
|
566
628
|
// point is to check the mesh and the metadata, not the account.
|
|
567
629
|
if (opts.dryRun) {
|
|
@@ -594,6 +656,23 @@ item
|
|
|
594
656
|
}
|
|
595
657
|
return;
|
|
596
658
|
}
|
|
659
|
+
if (opts.quote) {
|
|
660
|
+
const creds = requireAuth();
|
|
661
|
+
try {
|
|
662
|
+
const quote = await (0, lib_1.quoteItemPublish)(publishOptions, creds);
|
|
663
|
+
if (opts.json)
|
|
664
|
+
console.log(JSON.stringify({ quote }, null, 2));
|
|
665
|
+
else {
|
|
666
|
+
console.log('✔ Publish quote — nothing uploaded or charged.');
|
|
667
|
+
for (const line of renderItemQuote(quote))
|
|
668
|
+
console.log(line);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
catch (err) {
|
|
672
|
+
fail(err instanceof lib_1.ApiError ? err.render() : `✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
597
676
|
const creds = requireAuth();
|
|
598
677
|
const mismatch = (0, config_1.credsEnvMismatchWarning)(creds.apiUrl);
|
|
599
678
|
if (mismatch)
|
|
@@ -614,9 +693,11 @@ item
|
|
|
614
693
|
if (result.genders.length)
|
|
615
694
|
console.log(` gender: ${result.genders.join(', ')}`);
|
|
616
695
|
console.log(` status: ${result.status}`);
|
|
617
|
-
console.log(result.
|
|
618
|
-
|
|
619
|
-
|
|
696
|
+
console.log(` edition: ${result.editionMode}${result.collectibleSupply !== null ? ` · supply ${result.collectibleSupply} · creator serial #${result.creatorSerial ?? 1}` : ''}`);
|
|
697
|
+
console.log(` publish fee: ${result.publishFeeLix} LIX`);
|
|
698
|
+
if (result.initialDistribution) {
|
|
699
|
+
console.log(` route: ${result.initialDistribution.channel} · ${result.initialDistribution.priceLix === 0 ? 'Claim' : `Buy ${result.initialDistribution.priceLix} LIX`}`);
|
|
700
|
+
}
|
|
620
701
|
console.log(` id: ${result.id}`);
|
|
621
702
|
for (const warning of result.verification.warnings)
|
|
622
703
|
console.log(` ⚠ ${warning}`);
|
|
@@ -627,6 +708,145 @@ item
|
|
|
627
708
|
: `✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
628
709
|
}
|
|
629
710
|
});
|
|
711
|
+
const itemDistribution = item
|
|
712
|
+
.command('distribution')
|
|
713
|
+
.description('Create and manage primary Claim/Buy routes for one published item definition');
|
|
714
|
+
const renderDistribution = (d) => [
|
|
715
|
+
`${d.id} · ${d.channel}${d.worldSlug ? `:${d.worldSlug}/${d.key}` : ''}`,
|
|
716
|
+
`${d.priceLix === 0 ? 'Claim' : `Buy ${d.priceLix} LIX`}${d.active ? '' : ' · DISABLED'}`,
|
|
717
|
+
`max/player ${d.maxPerPlayer ?? 'unlimited'} · max claims ${d.maxClaims ?? 'unlimited'}`,
|
|
718
|
+
].join(' · ');
|
|
719
|
+
itemDistribution
|
|
720
|
+
.command('create')
|
|
721
|
+
.description('Create one creator-authorized primary acquisition route; no second publish fee')
|
|
722
|
+
.argument('<item-id>', 'published universal item definition id')
|
|
723
|
+
.requiredOption('--channel <marketplace|world>', 'route channel')
|
|
724
|
+
.requiredOption('--price-lix <n>', '0 is Claim; positive is Buy and must meet the server-owned floor')
|
|
725
|
+
.option('--world <slug>', 'required for a world route')
|
|
726
|
+
.option('--key <key>', 'stable world route key')
|
|
727
|
+
.option('--max-per-player <n|unlimited>', 'per-player cap')
|
|
728
|
+
.option('--max-claims <n|unlimited>', 'distribution-wide cap')
|
|
729
|
+
.option('--starts-at <iso>', 'activation time')
|
|
730
|
+
.option('--ends-at <iso>', 'end time')
|
|
731
|
+
.option('--dry-run', 'validate and print the request; no network')
|
|
732
|
+
.option('--json', 'machine-readable output')
|
|
733
|
+
.action(async (itemId, opts) => {
|
|
734
|
+
const input = {
|
|
735
|
+
itemId,
|
|
736
|
+
channel: opts.channel,
|
|
737
|
+
priceLix: parsePriceLix(opts.priceLix),
|
|
738
|
+
worldSlug: opts.world,
|
|
739
|
+
key: opts.key,
|
|
740
|
+
maxPerPlayer: opts.maxPerPlayer !== undefined ? parseLimit(opts.maxPerPlayer, '--max-per-player') : undefined,
|
|
741
|
+
maxClaims: opts.maxClaims !== undefined ? parseLimit(opts.maxClaims, '--max-claims') : undefined,
|
|
742
|
+
startsAt: opts.startsAt,
|
|
743
|
+
endsAt: opts.endsAt,
|
|
744
|
+
};
|
|
745
|
+
try {
|
|
746
|
+
const fields = (0, lib_1.buildItemDistribution)(input);
|
|
747
|
+
if (opts.dryRun) {
|
|
748
|
+
const out = { dryRun: true, endpoint: '/api/v1/item-distributions', fields };
|
|
749
|
+
if (opts.json)
|
|
750
|
+
console.log(JSON.stringify(out, null, 2));
|
|
751
|
+
else
|
|
752
|
+
console.log(`✔ Dry run — nothing sent.\n POST ${out.endpoint}\n ${JSON.stringify(fields)}`);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const created = await (0, lib_1.createItemDistribution)(input, requireAuth());
|
|
756
|
+
if (opts.json)
|
|
757
|
+
console.log(JSON.stringify(created, null, 2));
|
|
758
|
+
else
|
|
759
|
+
console.log(`✔ Created ${created.priceLix === 0 ? 'Claim' : 'Buy'} route.\n ${renderDistribution(created)}`);
|
|
760
|
+
}
|
|
761
|
+
catch (err) {
|
|
762
|
+
fail(err instanceof lib_1.ApiError ? err.render() : `✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
itemDistribution
|
|
766
|
+
.command('list')
|
|
767
|
+
.description('List every active or disabled primary route drawing from an item')
|
|
768
|
+
.argument('<item-id>')
|
|
769
|
+
.option('--json', 'machine-readable output')
|
|
770
|
+
.action(async (itemId, opts) => {
|
|
771
|
+
try {
|
|
772
|
+
const rows = await (0, lib_1.listItemDistributions)(itemId, requireAuth());
|
|
773
|
+
if (opts.json)
|
|
774
|
+
console.log(JSON.stringify({ distributions: rows }, null, 2));
|
|
775
|
+
else if (!rows.length)
|
|
776
|
+
console.log(`No distributions exist for ${itemId}.`);
|
|
777
|
+
else
|
|
778
|
+
console.log(rows.map(renderDistribution).join('\n'));
|
|
779
|
+
}
|
|
780
|
+
catch (err) {
|
|
781
|
+
fail(err instanceof lib_1.ApiError ? err.render() : `✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
const clearableTimestamp = (value) => value?.toLowerCase() === 'none' ? null : value;
|
|
785
|
+
itemDistribution
|
|
786
|
+
.command('update')
|
|
787
|
+
.description('Reprice or change schedule/caps. Item, channel, world and key remain fixed')
|
|
788
|
+
.argument('<distribution-id>')
|
|
789
|
+
.option('--price-lix <n>')
|
|
790
|
+
.option('--max-per-player <n|unlimited>')
|
|
791
|
+
.option('--max-claims <n|unlimited>')
|
|
792
|
+
.option('--starts-at <iso|none>', 'replace or clear the activation time')
|
|
793
|
+
.option('--ends-at <iso|none>', 'replace or clear the end time')
|
|
794
|
+
.option('--dry-run', 'validate and print the patch; no network')
|
|
795
|
+
.option('--json', 'machine-readable output')
|
|
796
|
+
.action(async (id, opts) => {
|
|
797
|
+
const input = {
|
|
798
|
+
priceLix: opts.priceLix !== undefined ? parsePriceLix(opts.priceLix) : undefined,
|
|
799
|
+
maxPerPlayer: opts.maxPerPlayer !== undefined ? parseLimit(opts.maxPerPlayer, '--max-per-player') : undefined,
|
|
800
|
+
maxClaims: opts.maxClaims !== undefined ? parseLimit(opts.maxClaims, '--max-claims') : undefined,
|
|
801
|
+
startsAt: clearableTimestamp(opts.startsAt),
|
|
802
|
+
endsAt: clearableTimestamp(opts.endsAt),
|
|
803
|
+
};
|
|
804
|
+
try {
|
|
805
|
+
const patch = (0, lib_1.buildItemDistributionUpdate)(input);
|
|
806
|
+
if (opts.dryRun) {
|
|
807
|
+
const out = { dryRun: true, endpoint: `/api/v1/item-distributions/${id}`, patch };
|
|
808
|
+
if (opts.json)
|
|
809
|
+
console.log(JSON.stringify(out, null, 2));
|
|
810
|
+
else
|
|
811
|
+
console.log(`✔ Dry run — nothing sent.\n PATCH ${out.endpoint}\n ${JSON.stringify(patch)}`);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const updated = await (0, lib_1.updateItemDistribution)(id, input, requireAuth());
|
|
815
|
+
if (opts.json)
|
|
816
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
817
|
+
else
|
|
818
|
+
console.log(`✔ Updated distribution.\n ${renderDistribution(updated)}`);
|
|
819
|
+
}
|
|
820
|
+
catch (err) {
|
|
821
|
+
fail(err instanceof lib_1.ApiError ? err.render() : `✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
itemDistribution
|
|
825
|
+
.command('disable')
|
|
826
|
+
.description('Disable an acquisition route without deleting its history or republishing the item')
|
|
827
|
+
.argument('<distribution-id>')
|
|
828
|
+
.option('--dry-run', 'print the request; no network')
|
|
829
|
+
.option('--json', 'machine-readable output')
|
|
830
|
+
.action(async (id, opts) => {
|
|
831
|
+
if (opts.dryRun) {
|
|
832
|
+
const out = { dryRun: true, endpoint: `/api/v1/item-distributions/${id}/disable` };
|
|
833
|
+
if (opts.json)
|
|
834
|
+
console.log(JSON.stringify(out, null, 2));
|
|
835
|
+
else
|
|
836
|
+
console.log(`✔ Dry run — nothing sent.\n POST ${out.endpoint}`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
const disabled = await (0, lib_1.disableItemDistribution)(id, requireAuth());
|
|
841
|
+
if (opts.json)
|
|
842
|
+
console.log(JSON.stringify(disabled, null, 2));
|
|
843
|
+
else
|
|
844
|
+
console.log(`✔ Disabled distribution.\n ${renderDistribution(disabled)}`);
|
|
845
|
+
}
|
|
846
|
+
catch (err) {
|
|
847
|
+
fail(err instanceof lib_1.ApiError ? err.render() : `✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
630
850
|
// --- helix achievement -----------------------------------------------------
|
|
631
851
|
// A world's achievement registry lives OUTSIDE its bundle — helix.json cannot
|
|
632
852
|
// declare one and publish cannot check that a key exists, so a rule awarding an
|
|
@@ -847,7 +1067,7 @@ achievement
|
|
|
847
1067
|
// they cannot enter. Worlds are named by SLUG, products by their key.
|
|
848
1068
|
const product = program
|
|
849
1069
|
.command('product')
|
|
850
|
-
.description("Register
|
|
1070
|
+
.description("Register world-local consumables, passes and currencies. Universal items use `helix item distribution` instead");
|
|
851
1071
|
function renderProduct(p, indent = ' ') {
|
|
852
1072
|
// An authored-grants product's stored type is server-DERIVED — the effect list is the truth,
|
|
853
1073
|
// so the "your world applies it" hint only fits legacy consumables.
|
|
@@ -897,12 +1117,11 @@ product
|
|
|
897
1117
|
.argument('<world-slug>', 'the world that sells it')
|
|
898
1118
|
.requiredOption('--title <title>', 'name the purchase popup shows the player (max 120 chars)')
|
|
899
1119
|
.requiredOption('--price <lix>', 'price in LIX; 0 registers a free "Claim" (a confirmation, no spend)')
|
|
900
|
-
.option('--type <type>',
|
|
901
|
-
.option('--grants <json>', `JSON array of
|
|
1120
|
+
.option('--type <type>', 'legacy world-local effect type: consumable (required unless you author --grants)')
|
|
1121
|
+
.option('--grants <json>', `JSON array of world-local entitlements instead of --type: pass | currency, max ${lib_1.MAX_GRANT_EFFECTS} ` +
|
|
902
1122
|
"(e.g. '[{\"kind\":\"currency\",\"code\":\"coin\",\"amount\":500}]'; [] = a tip jar). Fixed at registration")
|
|
903
1123
|
.option('--key <key>', 'stable slug world code buys as `pkey:<key>` (max 64 chars, lowercase a-z0-9_-); it cannot be renamed later')
|
|
904
1124
|
.option('--description <text>', 'what the player is buying')
|
|
905
|
-
.option('--item-id <uuid>', 'the universal item handed over — REQUIRED for item_grant and non_consumable, refused for consumable')
|
|
906
1125
|
.option('--max-per-user <n>', 'cap on how many one player may own (whole number >= 1)')
|
|
907
1126
|
.option('--dry-run', 'validate everything locally and print what WOULD be sent; no network, works logged out')
|
|
908
1127
|
.option('--json', 'machine-readable output')
|
|
@@ -914,10 +1133,15 @@ product
|
|
|
914
1133
|
description: opts.description,
|
|
915
1134
|
priceLix: Number(opts.price),
|
|
916
1135
|
type: opts.type,
|
|
917
|
-
itemId: opts.itemId,
|
|
918
1136
|
grants: opts.grants !== undefined ? parseGrantsFlag(opts.grants) : undefined,
|
|
919
1137
|
maxPerUser: opts.maxPerUser !== undefined ? parseWholeNumber(opts.maxPerUser, '--max-per-user') : undefined,
|
|
920
1138
|
};
|
|
1139
|
+
if (registration.type !== undefined && registration.type !== 'consumable') {
|
|
1140
|
+
fail('✖ World Products do not mint universal items. Use `helix item distribution create`; legacy --type accepts only consumable.');
|
|
1141
|
+
}
|
|
1142
|
+
if (registration.grants?.some((grant) => grant.kind === 'item')) {
|
|
1143
|
+
fail('✖ World Product grants accept pass/currency only. Publish the item and create an ItemDistribution instead.');
|
|
1144
|
+
}
|
|
921
1145
|
// Stops before ANY network call, so it works logged out — the point is to
|
|
922
1146
|
// check the vocabulary and the type/item coherence, not the account.
|
|
923
1147
|
if (opts.dryRun) {
|