@helix3/helix-cli 0.1.18-helix3.353 → 0.1.18-helix3.355
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 +23 -0
- package/dist/index.js +247 -1
- package/dist/index.js.map +1 -1
- package/dist/item.d.ts +7 -0
- package/dist/item.js +22 -8
- package/dist/item.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/vehicle/addon.d.ts +178 -0
- package/dist/vehicle/addon.js +467 -0
- package/dist/vehicle/addon.js.map +1 -0
- package/dist/vehicle/audio.d.ts +104 -0
- package/dist/vehicle/audio.js +419 -0
- package/dist/vehicle/audio.js.map +1 -0
- package/dist/vehicle/platform.d.ts +71 -0
- package/dist/vehicle/platform.js +144 -0
- package/dist/vehicle/platform.js.map +1 -0
- package/dist/vehicle/publish.d.ts +160 -0
- package/dist/vehicle/publish.js +315 -0
- package/dist/vehicle/publish.js.map +1 -0
- package/dist/vehicle/validate.d.ts +10 -0
- package/dist/vehicle/validate.js +15 -0
- package/dist/vehicle/validate.js.map +1 -1
- package/dist/visualQa/commonAsserts.d.ts +1 -1
- package/dist/visualQa/commonAsserts.js +14 -2
- package/dist/visualQa/commonAsserts.js.map +1 -1
- package/dist/visualQa/viewerPage.d.ts +1 -1
- package/dist/visualQa/viewerPage.js +4 -2
- package/dist/visualQa/viewerPage.js.map +1 -1
- package/package.json +1 -1
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
package/README.md
CHANGED
|
@@ -46,6 +46,10 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
|
|
|
46
46
|
| `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**. |
|
|
47
47
|
| `helix list` | List your worlds. |
|
|
48
48
|
| `helix vehicle validate <vehicle-package.json> [--glb <model.glb>] [--json]` | One-second local gate for a hand-authored Universal Vehicle, run before `item publish`. Checks the package against the engine's own validator (vendored, pinned to an engine commit), the engine publish gate, the backend pin requirements, `groundToBodyOrigin` vs `wheelRadius + staticRideHeight`, and the `componentBuild` digest; with `--glb` also resolves the four wheel nodes, checks each wheel node's origin is at its hub (`WHEEL_PIVOT_NOT_AT_HUB`), the +X-left / +Z-forward frame, the vehicle publish budgets and the articulation nodes. Exit `0` no errors, `1` errors, `2` unreadable input. See [Vehicle validate](#vehicle-validate). |
|
|
49
|
+
| `helix vehicle publish <dir> [--dry-run] [--item <id> --package-version <v> --reason <text>] [--json]` | One command from a vehicle package directory to a sealed, pinned, read-back-verified vehicle item: validate → upload each local WAV and write its verified public URL → mint → seal + pin the `vehicle` Package → read back. Resumable (ledger in `<dir>/.helix-publish`). See [Vehicle publish](#vehicle-publish). |
|
|
50
|
+
| `helix vehicle audio-check <dir\|package.json\|item-id> [--json]` | Proves every sound the runtime will select resolves, is PCM WAV and is audible (pre-publish on local files, or read-back on a published item). |
|
|
51
|
+
| `helix vehicle addon check <dir> [--grant] [--json]` / `helix vehicle addon publish <dir> [--no-grant] [--dry-run]` | Add-on fit gate (contract, `helixMeshFrame` anchoring, host hubs, hide targets + grants, the backend's `validate-fit` dry run) and the end-to-end add-on publish (grant → upload → seal + pin the `addon` Package → read back). |
|
|
52
|
+
| `helix vehicle grant-hides <host-id> --hide <slot=glob,...>` | Read-modify-write of the host board's `hostGrantExtensions` (`PUT /universal-items/:id/slots`), so add-ons may hide its real nodes (`rim_*`, `exhaust`). |
|
|
49
53
|
| `helix item list-slots [--json]` | Print the Character-Creator vocabulary a wearable declares: the cosmetic slot tags, and the genders. |
|
|
50
54
|
| `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. |
|
|
51
55
|
| `helix item distribution create\|list\|update\|disable …` | Manage Marketplace/World Claim or Buy routes without republishing the item or paying another publish fee. |
|
|
@@ -567,6 +571,25 @@ The model frame is glTF: +X is the vehicle's left, +Y up, +Z forward, metres. Wh
|
|
|
567
571
|
the engine's own resolver (`wheel_lf` / `wheel_rf` / `wheel_lr` / `wheel_rr` and the usual aliases); all four must
|
|
568
572
|
resolve. The engine validator is vendored by `scripts/vendor-vehicle-package-v2.mjs` (see `vendor/README.md`).
|
|
569
573
|
|
|
574
|
+
## Vehicle publish
|
|
575
|
+
|
|
576
|
+
`helix vehicle publish <dir>` takes a directory holding `vehicle-package.json`, one `.glb` (`model.glb` when there
|
|
577
|
+
are several), an optional `thumbnail.{png,jpg,webp}`, WAVs beside the package or under `audio/`, and an optional
|
|
578
|
+
`publish.json` (`title`, `description`, `tags`, `slug`, `version`, `provenance`). Audio rows are authored as
|
|
579
|
+
`{ name, file }`; the command uploads each file as a public Continuum object, waits until its content URL serves the
|
|
580
|
+
exact bytes, and writes `url` / `checksumSha256` / `bytes` / `cid` before the mint (a public object is readable at
|
|
581
|
+
its content URL before any seal, so this is one pass). It then mints the item, seals the `vehicle` Package (root =
|
|
582
|
+
the item's canonical package bytes, the item's web GLB adopted, every WAV) with `universalItemPin`, and reads back
|
|
583
|
+
the pin, the package identity and every audio URL. `--item <id> --package-version <v> --reason <text>` seals a
|
|
584
|
+
staged version of an existing car and moves it (a listed car's move waits for a passing candidate visual QA; the
|
|
585
|
+
command says so). Exit `0` published and verified, `1` refused or read-back failed, `2` unreadable input.
|
|
586
|
+
`helix vehicle validate` judges local-WAV audio rows as if uploaded, so it passes on the same directory.
|
|
587
|
+
|
|
588
|
+
`helix vehicle addon publish <dir>` does the same for an add-on directory (`addon.json` { `title`, `fits`, `ops`, … }
|
|
589
|
+
+ `model.glb`): `addon check` first (opening missing hide grants on hosts you created), then publish with no
|
|
590
|
+
Marketplace route, seal + pin the `addon` Package (vehicle add-ons are not auto-sealed), and read back that every
|
|
591
|
+
host lists it as a fit.
|
|
592
|
+
|
|
570
593
|
## Item publish flow
|
|
571
594
|
|
|
572
595
|
`helix item publish` creates an item definition. It defaults to **Standard**: unlimited issuance,
|
package/dist/index.js
CHANGED
|
@@ -588,8 +588,14 @@ vehicleCommand
|
|
|
588
588
|
}
|
|
589
589
|
}
|
|
590
590
|
let result;
|
|
591
|
+
let provisional = 0;
|
|
591
592
|
try {
|
|
592
|
-
|
|
593
|
+
// Audio rows backed by a WAV beside the package get their public url from `helix vehicle publish`; judge the
|
|
594
|
+
// rest of the package as if that had happened (the engine validator requires a complete runtime closure).
|
|
595
|
+
const { provisionalAudio } = await Promise.resolve().then(() => __importStar(require('./vehicle/audio')));
|
|
596
|
+
const prepared = provisionalAudio(pkg, (0, node_path_1.resolve)(packageFile, '..'));
|
|
597
|
+
provisional = prepared.filled;
|
|
598
|
+
result = await (0, validate_1.validateVehicle)(prepared.pkg, glb);
|
|
593
599
|
}
|
|
594
600
|
catch (error) {
|
|
595
601
|
process.stderr.write(`✖ cannot validate ${opts.glb ?? packageFile}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -602,12 +608,252 @@ vehicleCommand
|
|
|
602
608
|
else {
|
|
603
609
|
if (result.issues.length > 0)
|
|
604
610
|
console.log((0, validate_1.formatVehicleIssues)(result.issues));
|
|
611
|
+
if (provisional > 0)
|
|
612
|
+
console.log(` ${provisional} audio row${provisional === 1 ? '' : 's'} backed by a local WAV: \`helix vehicle publish\` uploads ${provisional === 1 ? 'it' : 'them'} and writes the url; \`helix vehicle audio-check\` proves ${provisional === 1 ? 'it plays' : 'they play'}`);
|
|
605
613
|
console.log(`${result.ok ? '✔' : '✖'} ${result.ok ? 'Valid' : 'Invalid'} vehicle package: ${result.errors} error${result.errors === 1 ? '' : 's'}, ${result.warnings} warning${result.warnings === 1 ? '' : 's'}`
|
|
606
614
|
+ `${glb ? ' (package + GLB)' : ' (package only — add --glb <model.glb> to check wheels, pivots and budgets)'} [engine ${result.engineCommit.slice(0, 8)}]`);
|
|
607
615
|
}
|
|
608
616
|
if (!result.ok)
|
|
609
617
|
process.exitCode = 1;
|
|
610
618
|
});
|
|
619
|
+
// ── vehicle publish / audio / add-on tooling (src/vehicle/{publish,audio,addon}.ts) ───────────────────────────────
|
|
620
|
+
function vehicleContinuumDeps() {
|
|
621
|
+
const creds = requireAuth();
|
|
622
|
+
const api = lib_1.HelixApi.forCredentials(creds);
|
|
623
|
+
return Promise.resolve().then(() => __importStar(require('./continuum'))).then(({ ContinuumPublisher, HelixContinuumApi }) => {
|
|
624
|
+
const continuumApi = new HelixContinuumApi(api);
|
|
625
|
+
const publisher = new ContinuumPublisher(continuumApi);
|
|
626
|
+
return {
|
|
627
|
+
creds,
|
|
628
|
+
api: api,
|
|
629
|
+
objects: continuumApi,
|
|
630
|
+
publishPackage: async (intent) => (await publisher.publish(intent)),
|
|
631
|
+
};
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
function printVehicleJsonOr(json, value, human) {
|
|
635
|
+
if (json)
|
|
636
|
+
console.log(JSON.stringify(value, (_k, v) => (v === Number.NEGATIVE_INFINITY ? '-Infinity' : v), 2));
|
|
637
|
+
else
|
|
638
|
+
human();
|
|
639
|
+
}
|
|
640
|
+
vehicleCommand
|
|
641
|
+
.command('publish')
|
|
642
|
+
.description('Publish a vehicle package directory end to end: validate (package, GLB, every audio sample decoded and audible) → upload each WAV and write its '
|
|
643
|
+
+ 'public URL into the package → mint the vehicle item → seal and pin its `vehicle` Continuum Package (root, adopted web GLB, WAVs) → read it back '
|
|
644
|
+
+ '(pin, package identity, every audio URL fetched and decoded). Resumable: re-run the same command after any failure. '
|
|
645
|
+
+ 'Directory: vehicle-package.json, one .glb (model.glb), optional thumbnail.png, WAVs beside it or in audio/, optional publish.json. '
|
|
646
|
+
+ 'Exit 0 = published and verified, 1 = refused or read-back failed, 2 = unreadable input')
|
|
647
|
+
.argument('<dir>', 'the vehicle package directory')
|
|
648
|
+
.option('--glb <file>', 'the drawn model, when the directory holds more than one .glb')
|
|
649
|
+
.option('--thumbnail <file>', 'thumbnail image (png/jpg/webp); rendered by the server when omitted')
|
|
650
|
+
.option('--title <title>', 'display title (default: publish.json title, then the package displayName)')
|
|
651
|
+
.option('--description <text>', 'item description')
|
|
652
|
+
.option('--tags <a,b,c>', 'comma-separated search keywords')
|
|
653
|
+
.option('--slug <slug>', 'url slug')
|
|
654
|
+
.option('--package-version <v>', 'Package Version to seal (default 1.0.0; required with --item)')
|
|
655
|
+
.option('--item <item-id>', 'publish a NEW VERSION of this existing vehicle item (staged seal + version move) instead of minting')
|
|
656
|
+
.option('--reason <text>', 'with --item: why the version moves (1–500 chars)')
|
|
657
|
+
.option('--allow-oversize', 'relax the mesh size ceiling (the server has the final say)')
|
|
658
|
+
.option('--out <dir>', 'ledger / finished package / descriptor / receipt directory (default <dir>/.helix-publish)')
|
|
659
|
+
.option('--dry-run', 'validate and check audio only; no upload, no fee')
|
|
660
|
+
.option('--json', 'print the machine-readable result')
|
|
661
|
+
.action(async (dir, opts) => {
|
|
662
|
+
try {
|
|
663
|
+
const { publishVehicle } = await Promise.resolve().then(() => __importStar(require('./vehicle/publish')));
|
|
664
|
+
const { formatAudioReport } = await Promise.resolve().then(() => __importStar(require('./vehicle/audio')));
|
|
665
|
+
const say = (m) => { if (!opts.json)
|
|
666
|
+
console.log(`· ${m}`); };
|
|
667
|
+
const base = opts.dryRun ? null : await vehicleContinuumDeps();
|
|
668
|
+
const creds = base?.creds ?? (0, config_1.loadCredentials)() ?? { apiUrl: '', token: '', expiresAt: '', username: '' };
|
|
669
|
+
const result = await publishVehicle({
|
|
670
|
+
dir, glb: opts.glb, thumbnail: opts.thumbnail, title: opts.title, description: opts.description,
|
|
671
|
+
tags: opts.tags ? (0, lib_1.parseTags)(opts.tags) : undefined, slug: opts.slug, version: opts.packageVersion, itemId: opts.item,
|
|
672
|
+
reason: opts.reason, allowOversize: opts.allowOversize, out: opts.out, dryRun: opts.dryRun,
|
|
673
|
+
}, creds, {
|
|
674
|
+
api: base?.api ?? { request: async () => { throw new Error('dry run makes no requests'); } },
|
|
675
|
+
objects: base?.objects ?? { putObjectFromFile: async () => { throw new Error('dry run uploads nothing'); } },
|
|
676
|
+
publishPackage: base?.publishPackage ?? (async () => { throw new Error('dry run seals nothing'); }),
|
|
677
|
+
mintItem: async (input) => {
|
|
678
|
+
const r = await (0, lib_1.publishItem)({ kind: 'vehicle', title: input.title, mesh: input.mesh, thumbnail: input.thumbnail, vehiclePackage: input.vehiclePackage, description: input.description, tags: input.tags, slug: input.slug, allowOversize: input.allowOversize }, base.creds, { onProgress: say });
|
|
679
|
+
return { id: r.id, slug: r.slug };
|
|
680
|
+
},
|
|
681
|
+
setVersion: async (itemId, input) => (0, itemSetVersion_1.setItemVersion)(itemId, { packageVersion: input.packageVersion, reason: input.reason, vehiclePackage: input.vehiclePackage }, base.creds),
|
|
682
|
+
onProgress: say,
|
|
683
|
+
});
|
|
684
|
+
printVehicleJsonOr(opts.json, result, () => {
|
|
685
|
+
if (result.validation.issues.length)
|
|
686
|
+
console.log((0, validate_1.formatVehicleIssues)(result.validation.issues));
|
|
687
|
+
console.log(formatAudioReport(result.audio));
|
|
688
|
+
if (result.itemId)
|
|
689
|
+
console.log(` item: ${result.itemId}${result.slug ? ` (${result.slug})` : ''}`);
|
|
690
|
+
if (result.packagePin)
|
|
691
|
+
console.log(` package: ${result.packagePin.packageId}@${result.packagePin.version} ${result.packagePin.manifestCid}`);
|
|
692
|
+
if (result.readBack) {
|
|
693
|
+
console.log(` read-back: pin ${result.readBack.pinMatches ? 'matches' : 'MISMATCH'}, package identity ${result.readBack.packageIdentityMatches ? 'matches' : 'MISMATCH'}, status ${result.readBack.status}`);
|
|
694
|
+
console.log(formatAudioReport(result.readBack.audio));
|
|
695
|
+
}
|
|
696
|
+
console.log(` files: ${Object.values(result.files).join(' ')}`);
|
|
697
|
+
for (const step of result.next)
|
|
698
|
+
console.log(` next: ${step}`);
|
|
699
|
+
console.log(`${result.ok ? '✔' : '✖'} ${result.dryRun ? 'dry run' : result.versionMove ?? 'publish'}${result.ok ? '' : ' failed'}`);
|
|
700
|
+
});
|
|
701
|
+
if (!result.ok)
|
|
702
|
+
process.exitCode = 1;
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
process.stderr.write(`${error instanceof lib_1.ApiError ? error.render() : `✖ ${error instanceof Error ? error.message : String(error)}`}\n`);
|
|
706
|
+
process.exitCode = 2;
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
vehicleCommand
|
|
710
|
+
.command('audio-check')
|
|
711
|
+
.description('Prove every sound a vehicle declares will actually play: each engine-ladder rung and slot sample the runtime selects must resolve (a local WAV '
|
|
712
|
+
+ 'beside the package before publish, or HTTP 200 with the exact declared bytes and sha256 after), be PCM WAV, and be audible (not silence). '
|
|
713
|
+
+ 'Pass a package directory / package JSON for the pre-seal check, or a published vehicle item id for the read-back check. Exit 0 = every sample plays')
|
|
714
|
+
.argument('<dir|package.json|item-id>', 'package directory or file (pre-seal), or a published vehicle item UUID (read-back)')
|
|
715
|
+
.option('--json', 'print the machine-readable report')
|
|
716
|
+
.action(async (target, opts) => {
|
|
717
|
+
try {
|
|
718
|
+
const audio = await Promise.resolve().then(() => __importStar(require('./vehicle/audio')));
|
|
719
|
+
let report;
|
|
720
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target)) {
|
|
721
|
+
const creds = (0, config_1.loadCredentials)();
|
|
722
|
+
const apiUrl = creds?.apiUrl ?? process.env.HELIX_API_URL ?? 'https://helix-backend-api-helix3.up.railway.app';
|
|
723
|
+
const res = await fetch(`${apiUrl.replace(/\/$/, '')}/api/v1/universal-items/${target}`, { headers: { accept: 'application/json' } });
|
|
724
|
+
if (!res.ok)
|
|
725
|
+
throw new Error(`item ${target}: HTTP ${res.status}`);
|
|
726
|
+
const body = await res.json();
|
|
727
|
+
const pkg = (body.item ?? body).properties?.vehiclePackage;
|
|
728
|
+
if (!pkg)
|
|
729
|
+
throw new Error(`item ${target} carries no properties.vehiclePackage (is it a vehicle?)`);
|
|
730
|
+
report = await audio.checkAudioReadBack(pkg);
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
const path = (0, node_path_1.resolve)(target);
|
|
734
|
+
const { statSync } = await Promise.resolve().then(() => __importStar(require('node:fs')));
|
|
735
|
+
const isDir = statSync(path).isDirectory();
|
|
736
|
+
const { readVehicleDir } = await Promise.resolve().then(() => __importStar(require('./vehicle/publish')));
|
|
737
|
+
const pkg = isDir ? readVehicleDir(path).pkg : (0, validate_1.readJsonFile)(path);
|
|
738
|
+
report = await audio.checkAudioLocal(pkg, isDir ? path : (0, node_path_1.resolve)(path, '..'));
|
|
739
|
+
}
|
|
740
|
+
printVehicleJsonOr(opts.json, report, () => console.log(audio.formatAudioReport(report)));
|
|
741
|
+
if (!report.ok)
|
|
742
|
+
process.exitCode = 1;
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
process.stderr.write(`✖ ${error instanceof Error ? error.message : String(error)}\n`);
|
|
746
|
+
process.exitCode = 2;
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
vehicleCommand
|
|
750
|
+
.command('grant-hides')
|
|
751
|
+
.description('Let add-ons hide your vehicle\'s real nodes: add hostGrantExtensions to the host\'s slot board (PUT /universal-items/:id/slots, read-modify-write, '
|
|
752
|
+
+ 'existing grants kept). The standard board only grants `*_stock_*` globs, so a wheel add-on that hides `rim_*` fits zero hosts until this runs. '
|
|
753
|
+
+ 'You must be the host item\'s creator. Each glob must match at least one host node')
|
|
754
|
+
.argument('<host-item-id>', 'your vehicle item')
|
|
755
|
+
.requiredOption('--hide <slot=glob,...>', 'board slot id = node glob pairs, e.g. whl.wheel=rim_*,pwr.exhaust=exhaust')
|
|
756
|
+
.option('--json', 'print the board grants before and after')
|
|
757
|
+
.action(async (hostId, opts) => {
|
|
758
|
+
try {
|
|
759
|
+
const add = {};
|
|
760
|
+
for (const pair of opts.hide.split(',').map((p) => p.trim()).filter(Boolean)) {
|
|
761
|
+
const eq = pair.indexOf('=');
|
|
762
|
+
if (eq <= 0 || eq === pair.length - 1)
|
|
763
|
+
throw new Error(`--hide entry "${pair}" must be <slot id>=<glob>, e.g. whl.wheel=rim_*`);
|
|
764
|
+
(add[pair.slice(0, eq)] ??= []).push(pair.slice(eq + 1));
|
|
765
|
+
}
|
|
766
|
+
const { grantHostHides } = await Promise.resolve().then(() => __importStar(require('./vehicle/addon')));
|
|
767
|
+
const creds = requireAuth();
|
|
768
|
+
const result = await grantHostHides(lib_1.HelixApi.forCredentials(creds), hostId, add);
|
|
769
|
+
printVehicleJsonOr(opts.json, { before: result.before, after: result.after }, () => {
|
|
770
|
+
console.log(` before: ${JSON.stringify(result.before)}`);
|
|
771
|
+
console.log(` after: ${JSON.stringify(result.after)}`);
|
|
772
|
+
console.log(`✔ host ${hostId} grants updated`);
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
catch (error) {
|
|
776
|
+
process.stderr.write(`${error instanceof lib_1.ApiError ? error.render() : `✖ ${error instanceof Error ? error.message : String(error)}`}\n`);
|
|
777
|
+
process.exitCode = 1;
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
const vehicleAddOnCommand = vehicleCommand
|
|
781
|
+
.command('addon')
|
|
782
|
+
.description('Check and publish vehicle add-ons (wheels, kits, exhausts, tune parts)');
|
|
783
|
+
vehicleAddOnCommand
|
|
784
|
+
.command('check')
|
|
785
|
+
.description('Prove an add-on will fit and land where it should BEFORE paying for it: the local fits/ops contract, the model\'s helixMeshFrame anchoring '
|
|
786
|
+
+ '(a socket-frame part centred on its mount), per host — a vehicle, wheel origins at the hubs, hide targets present, hide grants on the board — '
|
|
787
|
+
+ 'and the backend\'s own fit dry run (POST /universal-items/validate-fit: how many hosts it actually fits). '
|
|
788
|
+
+ 'Directory: addon.json { title, fits, ops, ... } + model.glb. Exit 0 = fits every named host')
|
|
789
|
+
.argument('<dir>', 'the add-on directory')
|
|
790
|
+
.option('--glb <file>', 'the add-on model, when the directory holds more than one .glb')
|
|
791
|
+
.option('--grant', 'also open missing hide grants on hosts you own (helix vehicle grant-hides), then re-check')
|
|
792
|
+
.option('--json', 'print the machine-readable report')
|
|
793
|
+
.action(async (dir, opts) => {
|
|
794
|
+
try {
|
|
795
|
+
const addon = await Promise.resolve().then(() => __importStar(require('./vehicle/addon')));
|
|
796
|
+
const creds = requireAuth();
|
|
797
|
+
const report = await addon.checkAddOn(addon.readAddOnDir(dir, { glb: opts.glb }), creds, {
|
|
798
|
+
api: lib_1.HelixApi.forCredentials(creds), grant: opts.grant,
|
|
799
|
+
onProgress: (m) => { if (!opts.json)
|
|
800
|
+
console.log(`· ${m}`); },
|
|
801
|
+
});
|
|
802
|
+
printVehicleJsonOr(opts.json, report, () => console.log(addon.formatAddOnReport(report)));
|
|
803
|
+
if (!report.ok)
|
|
804
|
+
process.exitCode = 1;
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
process.stderr.write(`${error instanceof lib_1.ApiError ? error.render() : `✖ ${error instanceof Error ? error.message : String(error)}`}\n`);
|
|
808
|
+
process.exitCode = 2;
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
vehicleAddOnCommand
|
|
812
|
+
.command('publish')
|
|
813
|
+
.description('Publish a vehicle add-on end to end: `vehicle addon check` (opening missing hide grants on hosts you own) → upload it → seal and pin its `addon` '
|
|
814
|
+
+ 'Continuum Package (vehicle add-ons are not auto-sealed) → read back (pinned, the backend lists each host as a fit). Resumable. Published with no '
|
|
815
|
+
+ 'Marketplace route: sell it with a distribution once visual QA passes. Exit 0 = published, pinned and fitting every host')
|
|
816
|
+
.argument('<dir>', 'the add-on directory (addon.json + model.glb)')
|
|
817
|
+
.option('--glb <file>', 'the add-on model, when the directory holds more than one .glb')
|
|
818
|
+
.option('--no-grant', 'never touch host boards; refuse instead when a hide grant is missing')
|
|
819
|
+
.option('--out <dir>', 'ledger / descriptor / receipt directory (default <dir>/.helix-publish)')
|
|
820
|
+
.option('--dry-run', 'run the check only; nothing is written')
|
|
821
|
+
.option('--json', 'print the machine-readable result')
|
|
822
|
+
.action(async (dir, opts) => {
|
|
823
|
+
try {
|
|
824
|
+
const addon = await Promise.resolve().then(() => __importStar(require('./vehicle/addon')));
|
|
825
|
+
const base = await vehicleContinuumDeps();
|
|
826
|
+
const say = (m) => { if (!opts.json)
|
|
827
|
+
console.log(`· ${m}`); };
|
|
828
|
+
const result = await addon.publishAddOn({ dir, glb: opts.glb, out: opts.out, noGrant: opts.grant === false, dryRun: opts.dryRun }, base.creds, {
|
|
829
|
+
api: base.api,
|
|
830
|
+
publishPackage: base.publishPackage,
|
|
831
|
+
mintItem: async (input) => {
|
|
832
|
+
const r = await (0, lib_1.publishItem)({ kind: 'add_on', title: input.title, mesh: input.mesh, thumbnail: input.thumbnail, fits: input.fits, ops: input.ops, description: input.description, tags: input.tags, slug: input.slug, noMarketplace: true }, base.creds, { onProgress: say });
|
|
833
|
+
return { id: r.id, slug: r.slug };
|
|
834
|
+
},
|
|
835
|
+
onProgress: say,
|
|
836
|
+
});
|
|
837
|
+
printVehicleJsonOr(opts.json, result, () => {
|
|
838
|
+
console.log(addon.formatAddOnReport(result.check));
|
|
839
|
+
if (result.itemId)
|
|
840
|
+
console.log(` item: ${result.itemId}`);
|
|
841
|
+
if (result.packagePin)
|
|
842
|
+
console.log(` package: ${result.packagePin.packageId}@${result.packagePin.version} ${result.packagePin.manifestCid}`);
|
|
843
|
+
if (result.readBack)
|
|
844
|
+
console.log(` read-back: pinned ${result.readBack.pinned}, fits ${JSON.stringify(result.readBack.fitsHosts)}, on host rails ${JSON.stringify(result.readBack.listedOnHosts)}`);
|
|
845
|
+
for (const step of result.next)
|
|
846
|
+
console.log(` next: ${step}`);
|
|
847
|
+
console.log(`${result.ok ? '✔' : '✖'} add-on ${opts.dryRun ? 'check' : 'publish'}${result.ok ? '' : ' failed'}`);
|
|
848
|
+
});
|
|
849
|
+
if (!result.ok)
|
|
850
|
+
process.exitCode = 1;
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
process.stderr.write(`${error instanceof lib_1.ApiError ? error.render() : `✖ ${error instanceof Error ? error.message : String(error)}`}\n`);
|
|
854
|
+
process.exitCode = 2;
|
|
855
|
+
}
|
|
856
|
+
});
|
|
611
857
|
// Validate a token against the API, then persist it. Shared by the browser
|
|
612
858
|
// flow and the --token (CI/headless) path.
|
|
613
859
|
async function finishLogin(apiUrl, token) {
|