@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 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')