@helix3/helix-cli 0.1.13-helix3.101 → 0.1.13-helix3.103
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 +61 -0
- package/dist/index.js +146 -0
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +3 -0
- package/dist/lib.js +23 -1
- package/dist/lib.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/unreal.d.ts +29 -0
- package/dist/unreal.js +359 -0
- package/dist/unreal.js.map +1 -0
- package/dist/unrealCandidate.d.ts +186 -0
- package/dist/unrealCandidate.js +626 -0
- package/dist/unrealCandidate.js.map +1 -0
- package/dist/unrealManifest.d.ts +74 -0
- package/dist/unrealManifest.js +354 -0
- package/dist/unrealManifest.js.map +1 -0
- package/dist/unrealPublish.d.ts +111 -0
- package/dist/unrealPublish.js +0 -0
- package/dist/unrealPublish.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -59,8 +59,69 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
|
|
|
59
59
|
| `helix assets credits\|check-loaders` | Render asset credits from provenance and reject models requiring unsupported loaders. |
|
|
60
60
|
| `helix character retarget-animation <clip.fbx\|clip.glb>` | Retarget a raw Mixamo/Meshy clip directly to `helix-humanoid@1`. FBX conversion is deterministic and runs inside the CLI; no hidden Blender/DCC pre-export is required. |
|
|
61
61
|
| `helix world audit\|source-audit\|perf-gate\|prove-live` | Run the blocking world QA, performance, and deployed-build identity gates. |
|
|
62
|
+
| `helix unreal init\|validate\|doctor\|package [project]` | Create and gate a shared-runtime or native-game publish manifest, check optional editor MCP, and assemble a local upload-shaped package. |
|
|
63
|
+
| `helix unreal publish <candidate> --world <slug>` | Upload a **cooked** Unreal world to HELIX: verify the candidate, presign, upload every artifact, finalize, and promote it as the world's active build. |
|
|
62
64
|
| `helix doctor [--project <dir>]` | Print the environment + whether the @helix toolchain (CLI/MCP/SDK/manifest) is current. |
|
|
63
65
|
|
|
66
|
+
## Unreal runtime projects
|
|
67
|
+
|
|
68
|
+
`helix unreal init <project> --mode shared-runtime|native-game` creates `helix.unreal.json`.
|
|
69
|
+
Shared-runtime projects ship cooked content/Blueprints against a pinned HELIX runtime; native-game
|
|
70
|
+
projects ship separate signed client/server builds and may use custom C++ and plugins. Both require
|
|
71
|
+
Unreal 5.8, the HelixSDK plugin, and HelixPlatform identity/session integration.
|
|
72
|
+
|
|
73
|
+
Networking is explicit: `unreal-native`, `helixnet`, `custom`, or `none`. Only `helixnet` requires
|
|
74
|
+
contract v21; blank Unreal projects using arbitrary GameMode/Pawn classes and native networking do
|
|
75
|
+
not need Lyra Experiences, Colyseus, or the HelixNet contract.
|
|
76
|
+
|
|
77
|
+
`helix unreal validate` is the publish gate and does not require MCP. `helix unreal doctor` adds the
|
|
78
|
+
loopback/editor-only Unreal MCP authoring checks. `helix unreal package` copies validated cooked or
|
|
79
|
+
native artifacts into a local checksummed staging directory — it is a local assembly step, not a
|
|
80
|
+
publish.
|
|
81
|
+
|
|
82
|
+
### Publishing a cooked world
|
|
83
|
+
|
|
84
|
+
`helix unreal publish <candidate> --world <slug-or-id>` takes the directory the Unreal
|
|
85
|
+
`HelixWorldPublish` commandlet writes — `world-build-manifest.json`, its `.sha256` receipt,
|
|
86
|
+
`cook-plan.json`, and `artifacts/<role>/…` — and gets it live in one command:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
helix unreal publish Saved/HelixPublish --world my-world --max-players 32 --visibility unlisted
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
It verifies the candidate against the bytes on disk (every digest recomputed — a manifest is a
|
|
93
|
+
claim, not evidence), registers the Build and presigns each artifact, streams the uploads,
|
|
94
|
+
finalizes so the server re-verifies every byte by size and SHA-256 and countersigns the manifest,
|
|
95
|
+
then promotes the Build as the world's active release. `--no-promote` stops after verification;
|
|
96
|
+
`--verify-delivery` additionally checks what a client would actually download.
|
|
97
|
+
|
|
98
|
+
The idempotency key defaults to the cooked manifest's own digest, so re-running after a dropped
|
|
99
|
+
connection resumes the same Build rather than minting a second one.
|
|
100
|
+
|
|
101
|
+
Only **shared-runtime** worlds publish this way. A World Build may not contain creator
|
|
102
|
+
executables, so `native-game` builds are a separate distribution path.
|
|
103
|
+
|
|
104
|
+
## Working in a git worktree? Run this first
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
npm run check:dev-links
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`package.json` links its sibling HELIX packages by relative path
|
|
111
|
+
(`file:../helix-manifest`, `file:../helix-web-sdk`). That is right in the canonical
|
|
112
|
+
layout, where the siblings sit beside this clone — but in a **git worktree** `../` is
|
|
113
|
+
the worktree parent, so the links resolve to whatever happens to be there: a checkout
|
|
114
|
+
parked on an older branch, or nothing at all.
|
|
115
|
+
|
|
116
|
+
The symptom is not "broken dev link". It is **a branch that looks broken**: `tsc`
|
|
117
|
+
reporting `has no exported member …` in files your change never touched, and whole
|
|
118
|
+
suites failing at import (`could not find the dev shell in @hypersoniclabs/helix-sdk`).
|
|
119
|
+
People have concluded a PR was broken on the strength of exactly this.
|
|
120
|
+
|
|
121
|
+
`check:dev-links` names which link is wrong, which imported symbols the linked sibling
|
|
122
|
+
does not export, and how to fix it without repointing a shared directory other
|
|
123
|
+
worktrees resolve through.
|
|
124
|
+
|
|
64
125
|
## Publish flow
|
|
65
126
|
|
|
66
127
|
`helix publish` maps directly onto the backend's Instant World API:
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,8 @@ const config_1 = require("./config");
|
|
|
53
53
|
const init_1 = require("./init");
|
|
54
54
|
const prompt_1 = require("./prompt");
|
|
55
55
|
const browserLogin_1 = require("./browserLogin");
|
|
56
|
+
const unreal_1 = require("./unreal");
|
|
57
|
+
const unrealPublish_1 = require("./unrealPublish");
|
|
56
58
|
const program = new commander_1.Command();
|
|
57
59
|
program
|
|
58
60
|
.name('helix')
|
|
@@ -3171,6 +3173,150 @@ function formatWorldSystemRow(row) {
|
|
|
3171
3173
|
return `? ${row.slug}: catalog query failed — currency unknown (${row.note.slice('catalog query failed ('.length, -1)})`;
|
|
3172
3174
|
}
|
|
3173
3175
|
}
|
|
3176
|
+
const unreal = program
|
|
3177
|
+
.command('unreal')
|
|
3178
|
+
.description('Prepare Unreal 5.8 projects for publishing to HELIX');
|
|
3179
|
+
function unrealArtifactMode(value) {
|
|
3180
|
+
if (!unreal_1.UNREAL_ARTIFACT_MODES.includes(value)) {
|
|
3181
|
+
fail(`✖ --mode must be one of: ${unreal_1.UNREAL_ARTIFACT_MODES.join(', ')}`);
|
|
3182
|
+
}
|
|
3183
|
+
return value;
|
|
3184
|
+
}
|
|
3185
|
+
function unrealNetworkingMode(value) {
|
|
3186
|
+
if (!unreal_1.UNREAL_NETWORKING_MODES.includes(value)) {
|
|
3187
|
+
fail(`✖ --networking must be one of: ${unreal_1.UNREAL_NETWORKING_MODES.join(', ')}`);
|
|
3188
|
+
}
|
|
3189
|
+
return value;
|
|
3190
|
+
}
|
|
3191
|
+
unreal
|
|
3192
|
+
.command('init')
|
|
3193
|
+
.description('Create the publish manifest for a shared-runtime or native-game project')
|
|
3194
|
+
.argument('[project]', 'project directory', '.')
|
|
3195
|
+
.requiredOption('--mode <mode>', 'shared-runtime or native-game')
|
|
3196
|
+
.option('--networking <mode>', 'unreal-native, helixnet, custom, or none', 'none')
|
|
3197
|
+
.option('--runtime-version <version>', 'pinned HELIX Unreal runtime version (shared-runtime only)')
|
|
3198
|
+
.option('--force', `replace an existing helix.unreal.json`)
|
|
3199
|
+
.option('--json', 'machine-readable result')
|
|
3200
|
+
.action((project, opts) => {
|
|
3201
|
+
try {
|
|
3202
|
+
const result = (0, unreal_1.initUnrealManifest)(project, {
|
|
3203
|
+
artifactMode: unrealArtifactMode(opts.mode),
|
|
3204
|
+
networking: unrealNetworkingMode(opts.networking),
|
|
3205
|
+
runtimeVersion: opts.runtimeVersion,
|
|
3206
|
+
force: opts.force,
|
|
3207
|
+
});
|
|
3208
|
+
console.log(opts.json
|
|
3209
|
+
? JSON.stringify(result, null, 2)
|
|
3210
|
+
: `✔ Created ${result.path}\n mode: ${result.manifest.artifactMode}\n networking: ${result.manifest.networking.mode}`);
|
|
3211
|
+
}
|
|
3212
|
+
catch (error) {
|
|
3213
|
+
fail(`✖ ${error instanceof Error ? error.message : String(error)}`);
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
3216
|
+
function registerUnrealValidationCommand(name, description) {
|
|
3217
|
+
unreal
|
|
3218
|
+
.command(name)
|
|
3219
|
+
.description(description)
|
|
3220
|
+
.argument('[project]', 'project directory or .uproject file', '.')
|
|
3221
|
+
.option('--json', 'machine-readable validation report')
|
|
3222
|
+
.action((project, opts) => {
|
|
3223
|
+
const report = (0, unreal_1.validateUnrealProject)(project, { includeMcp: name === 'doctor' });
|
|
3224
|
+
console.log(opts.json
|
|
3225
|
+
? JSON.stringify(report, null, 2)
|
|
3226
|
+
: (0, unreal_1.formatUnrealValidationReport)(report));
|
|
3227
|
+
if (!report.ok)
|
|
3228
|
+
process.exitCode = 1;
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
registerUnrealValidationCommand('doctor', 'Check publish readiness plus safe editor-only Unreal MCP authoring setup');
|
|
3232
|
+
registerUnrealValidationCommand('validate', 'Validate the project and HELIX publish manifest without requiring editor MCP');
|
|
3233
|
+
unreal
|
|
3234
|
+
.command('package')
|
|
3235
|
+
.description('Assemble validated cooked/native artifacts into a local upload-shaped directory')
|
|
3236
|
+
.argument('[project]', 'project directory', '.')
|
|
3237
|
+
.option('--out <directory>', 'output directory (default: <project>/dist/helix-unreal-package)')
|
|
3238
|
+
.option('--force', 'replace the exact output directory if it already exists')
|
|
3239
|
+
.option('--json', 'machine-readable package result')
|
|
3240
|
+
.action((project, opts) => {
|
|
3241
|
+
const validation = (0, unreal_1.validateUnrealProject)(project);
|
|
3242
|
+
if (!validation.ok) {
|
|
3243
|
+
if (opts.json)
|
|
3244
|
+
console.log(JSON.stringify({ validation, packaged: false }, null, 2));
|
|
3245
|
+
else
|
|
3246
|
+
console.log((0, unreal_1.formatUnrealValidationReport)(validation));
|
|
3247
|
+
process.exitCode = 1;
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
const output = opts.out ?? (0, node_path_1.resolve)(project, 'dist', 'helix-unreal-package');
|
|
3251
|
+
const result = (0, unreal_1.packageUnrealProject)(project, output, { force: opts.force });
|
|
3252
|
+
if (opts.json)
|
|
3253
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3254
|
+
else if (result.ok) {
|
|
3255
|
+
console.log(`✔ Prepared local HELIX package: ${result.outputDir}`);
|
|
3256
|
+
console.log(` ${result.index.files.length} files · ${result.index.totalBytes} bytes`);
|
|
3257
|
+
console.log(' This is a local staging directory, not a publish. To publish a world, cook it with');
|
|
3258
|
+
console.log(' the HelixWorldPublish commandlet and run: helix unreal publish <candidate> --world <slug>');
|
|
3259
|
+
}
|
|
3260
|
+
else {
|
|
3261
|
+
console.log(`✖ ${result.errors.length} packaging problem(s):\n${result.errors.map((error) => ` - ${error}`).join('\n')}`);
|
|
3262
|
+
}
|
|
3263
|
+
if (!result.ok)
|
|
3264
|
+
process.exitCode = 1;
|
|
3265
|
+
});
|
|
3266
|
+
unreal
|
|
3267
|
+
.command('publish')
|
|
3268
|
+
.description('Upload a cooked Unreal world to HELIX and promote it as the world’s active build')
|
|
3269
|
+
.argument('[candidate]', 'the cook output directory containing world-build-manifest.json', '.')
|
|
3270
|
+
.requiredOption('--world <slug-or-id>', 'the HELIX world to publish this build to')
|
|
3271
|
+
.option('--max-players <count>', 'players per instance (1-1000)', '32')
|
|
3272
|
+
.option('--activity <id>', 'activity id for the entry map', 'default')
|
|
3273
|
+
.option('--idempotency-key <key>', 'override the key derived from the cooked manifest')
|
|
3274
|
+
.option('--visibility <visibility>', 'private, unlisted, or public — sets the world’s catalog visibility on promote')
|
|
3275
|
+
.option('--no-promote', 'upload and verify the build but leave the active release unchanged')
|
|
3276
|
+
.option('--verify-delivery', 'after promoting, confirm what a client would download')
|
|
3277
|
+
.option('--json', 'machine-readable result')
|
|
3278
|
+
.action(async (candidate, opts) => {
|
|
3279
|
+
const creds = requireAuth();
|
|
3280
|
+
const maxPlayers = Number(opts.maxPlayers);
|
|
3281
|
+
if (!Number.isInteger(maxPlayers) || maxPlayers < 1 || maxPlayers > 1000) {
|
|
3282
|
+
fail('✖ --max-players must be a whole number between 1 and 1000');
|
|
3283
|
+
}
|
|
3284
|
+
if (opts.visibility && !['private', 'unlisted', 'public'].includes(opts.visibility)) {
|
|
3285
|
+
fail('✖ --visibility must be private, unlisted, or public');
|
|
3286
|
+
}
|
|
3287
|
+
try {
|
|
3288
|
+
const result = await (0, unrealPublish_1.publishUnrealBuild)(candidate, lib_1.HelixApi.forCredentials(creds), {
|
|
3289
|
+
world: opts.world,
|
|
3290
|
+
maxPlayers,
|
|
3291
|
+
activityId: opts.activity,
|
|
3292
|
+
idempotencyKey: opts.idempotencyKey,
|
|
3293
|
+
promote: opts.promote,
|
|
3294
|
+
visibility: opts.visibility,
|
|
3295
|
+
engineVersion: unreal_1.HELIX_UNREAL_ENGINE_VERSION,
|
|
3296
|
+
verifyDelivery: opts.verifyDelivery,
|
|
3297
|
+
}, (message) => {
|
|
3298
|
+
if (!opts.json)
|
|
3299
|
+
console.log(` ${message}`);
|
|
3300
|
+
});
|
|
3301
|
+
if (opts.json) {
|
|
3302
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3305
|
+
console.log(result.promoted
|
|
3306
|
+
? `✔ Published build #${result.buildNumber} — it is now the active release for this world.`
|
|
3307
|
+
: `✔ Verified build #${result.buildNumber}. It is not live: re-run without --no-promote to promote it.`);
|
|
3308
|
+
console.log(` build ${result.buildId}`);
|
|
3309
|
+
console.log(` networking ${result.networkMode}`);
|
|
3310
|
+
console.log(` artifacts ${result.artifactCount} · ${result.bundleSizeBytes.toLocaleString()} bytes${result.replayed ? ' (already uploaded — replayed)' : ''}`);
|
|
3311
|
+
console.log(` signed by ${result.manifestSigningKeyId} over ${result.manifestDigest.slice(0, 16)}…`);
|
|
3312
|
+
if (result.deliveryVerified) {
|
|
3313
|
+
console.log(' delivery verified against what a client would download');
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
catch (error) {
|
|
3317
|
+
fail((0, unrealPublish_1.renderPublishFailure)(error));
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3174
3320
|
program
|
|
3175
3321
|
.command('doctor')
|
|
3176
3322
|
.description('Health check: login state + whether the @helix toolchain (CLI/MCP/SDK/manifest) is up to date')
|