@helix3/helix-cli 0.1.4-helix3.24
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/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/ability.d.ts +73 -0
- package/dist/ability.js +120 -0
- package/dist/ability.js.map +1 -0
- package/dist/api.d.ts +24 -0
- package/dist/api.js +83 -0
- package/dist/api.js.map +1 -0
- package/dist/browserLogin.d.ts +22 -0
- package/dist/browserLogin.js +148 -0
- package/dist/browserLogin.js.map +1 -0
- package/dist/bundle.d.ts +15 -0
- package/dist/bundle.js +98 -0
- package/dist/bundle.js.map +1 -0
- package/dist/character/canonicalSkeleton.d.ts +3 -0
- package/dist/character/canonicalSkeleton.js +23 -0
- package/dist/character/canonicalSkeleton.js.map +1 -0
- package/dist/character/canonicalSkeletonData.d.ts +6 -0
- package/dist/character/canonicalSkeletonData.js +92 -0
- package/dist/character/canonicalSkeletonData.js.map +1 -0
- package/dist/character/esm.d.ts +1 -0
- package/dist/character/esm.js +6 -0
- package/dist/character/esm.js.map +1 -0
- package/dist/character/importCharacter.d.ts +88 -0
- package/dist/character/importCharacter.js +293 -0
- package/dist/character/importCharacter.js.map +1 -0
- package/dist/character/mat4.d.ts +29 -0
- package/dist/character/mat4.js +103 -0
- package/dist/character/mat4.js.map +1 -0
- package/dist/character/meshyToHelixMap.d.ts +2 -0
- package/dist/character/meshyToHelixMap.js +40 -0
- package/dist/character/meshyToHelixMap.js.map +1 -0
- package/dist/character/quat.d.ts +15 -0
- package/dist/character/quat.js +137 -0
- package/dist/character/quat.js.map +1 -0
- package/dist/character/stages/bakeTexture.d.ts +20 -0
- package/dist/character/stages/bakeTexture.js +396 -0
- package/dist/character/stages/bakeTexture.js.map +1 -0
- package/dist/character/stages/conform.d.ts +56 -0
- package/dist/character/stages/conform.js +292 -0
- package/dist/character/stages/conform.js.map +1 -0
- package/dist/character/stages/ktx2.d.ts +22 -0
- package/dist/character/stages/ktx2.js +97 -0
- package/dist/character/stages/ktx2.js.map +1 -0
- package/dist/character/stages/mergeLods.d.ts +15 -0
- package/dist/character/stages/mergeLods.js +42 -0
- package/dist/character/stages/mergeLods.js.map +1 -0
- package/dist/character/stages/report.d.ts +2 -0
- package/dist/character/stages/report.js +19 -0
- package/dist/character/stages/report.js.map +1 -0
- package/dist/character/stages/resizeTextures.d.ts +11 -0
- package/dist/character/stages/resizeTextures.js +62 -0
- package/dist/character/stages/resizeTextures.js.map +1 -0
- package/dist/character/stages/retarget.d.ts +49 -0
- package/dist/character/stages/retarget.js +105 -0
- package/dist/character/stages/retarget.js.map +1 -0
- package/dist/character/stages/retargetClip.d.ts +27 -0
- package/dist/character/stages/retargetClip.js +237 -0
- package/dist/character/stages/retargetClip.js.map +1 -0
- package/dist/character/stages/simplifyMesh.d.ts +15 -0
- package/dist/character/stages/simplifyMesh.js +37 -0
- package/dist/character/stages/simplifyMesh.js.map +1 -0
- package/dist/character/stages/splitFacePrimitive.d.ts +30 -0
- package/dist/character/stages/splitFacePrimitive.js +115 -0
- package/dist/character/stages/splitFacePrimitive.js.map +1 -0
- package/dist/character/stages/weldedSimplify.d.ts +32 -0
- package/dist/character/stages/weldedSimplify.js +202 -0
- package/dist/character/stages/weldedSimplify.js.map +1 -0
- package/dist/character/testutil/syntheticRig.d.ts +9 -0
- package/dist/character/testutil/syntheticRig.js +84 -0
- package/dist/character/testutil/syntheticRig.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.js +103 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +378 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +87 -0
- package/dist/init.js.map +1 -0
- package/dist/initAbility.d.ts +1 -0
- package/dist/initAbility.js +71 -0
- package/dist/initAbility.js.map +1 -0
- package/dist/install.d.ts +51 -0
- package/dist/install.js +201 -0
- package/dist/install.js.map +1 -0
- package/dist/lib.d.ts +88 -0
- package/dist/lib.js +347 -0
- package/dist/lib.js.map +1 -0
- package/dist/package.d.ts +11 -0
- package/dist/package.js +136 -0
- package/dist/package.js.map +1 -0
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.js +55 -0
- package/dist/prompt.js.map +1 -0
- package/dist/resolve.d.ts +57 -0
- package/dist/resolve.js +103 -0
- package/dist/resolve.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +58 -0
- package/vendor/ability-manifest.mjs +558 -0
- package/vendor/installed-index.mjs +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hypersonic Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @hypersoniclabs/helix-cli
|
|
2
|
+
|
|
3
|
+
`helix` — the **HELIX creator CLI**: scaffold, validate, and publish Instant Worlds to the HELIX
|
|
4
|
+
backend. It shares bundle validation with the server via `@hypersoniclabs/helix-manifest`, so "validate passed but
|
|
5
|
+
publish failed" can't happen for contract reasons.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @hypersoniclabs/helix-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Authentication
|
|
14
|
+
|
|
15
|
+
`helix login` opens your browser to the website's `/cli-login` page; you sign in there and the CLI
|
|
16
|
+
captures a **client token** via a one-shot loopback redirect. For CI/headless, pass a token directly:
|
|
17
|
+
`helix login --token <token>` (mint one in the **Creator Hub** → *Settings → Client Tokens*).
|
|
18
|
+
|
|
19
|
+
The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
|
|
20
|
+
`Authorization: Bearer <token>` on publish calls. `helix login` validates it against
|
|
21
|
+
`GET /api/v1/users/me` before saving and reads the token's `exp` so the CLI knows when it lapses.
|
|
22
|
+
|
|
23
|
+
> **The backend & website default to the build's environment:** the published `@hypersoniclabs/*`
|
|
24
|
+
> CLI targets production (`api.helixgame.com` / `helixgame.com`); the staging `@helixdev/*` CLI targets
|
|
25
|
+
> the staging backend (`helix-backend-staging.up.railway.app`) + staging website (`next.helixgame.com`).
|
|
26
|
+
> Override per run with `--api-url`/`HELIX_API_URL` and `--web-url`/`HELIX_WEB_URL` (e.g. a local/dev backend).
|
|
27
|
+
|
|
28
|
+
## Commands
|
|
29
|
+
|
|
30
|
+
| Command | What it does |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `helix login [--api-url <url>] [--web-url <url>] [--token <t>] [--no-browser]` | Sign in via the browser (`/cli-login`), or `--token` for CI/headless. Stores the client token. |
|
|
33
|
+
| `helix logout` | Remove the stored token. |
|
|
34
|
+
| `helix whoami` | Show current login status. |
|
|
35
|
+
| `helix init <dir>` | Scaffold a minimal Three.js world (`helix.json` + `index.html` + `main.js`). |
|
|
36
|
+
| `helix install [--update]` | Resolve a world's `systems`/`abilities` pins (a v0.2 **or v0.3** manifest) → materialize the modules, the `three` import map, and `helix.runtime.ts`. |
|
|
37
|
+
| `helix validate [dir]` | Validate a bundle locally — the exact rules the server enforces. |
|
|
38
|
+
| `helix publish [dir]` | Validate → resolve/create the world → upload files → finalize → print the play URL. |
|
|
39
|
+
| `helix list` | List your worlds. |
|
|
40
|
+
| `helix doctor [--project <dir>]` | Print the environment + whether the @helix toolchain (CLI/MCP/SDK/manifest) is current. |
|
|
41
|
+
|
|
42
|
+
## Publish flow
|
|
43
|
+
|
|
44
|
+
`helix publish` maps directly onto the backend's Instant World API:
|
|
45
|
+
|
|
46
|
+
1. `checkBundle` validates `helix.json` + files via `@hypersoniclabs/helix-manifest`.
|
|
47
|
+
2. `GET /api/v1/instant-worlds/me` → reuse the world with the manifest's slug, else
|
|
48
|
+
`POST /api/v1/instant-worlds`.
|
|
49
|
+
3. `POST /api/v1/instant-worlds/:id/builds` → presigned upload tickets.
|
|
50
|
+
4. Files PUT directly to storage (6 concurrent), content-type/length bound to each ticket.
|
|
51
|
+
5. `POST /api/v1/instant-worlds/:id/builds/:buildId/finalize` → byte-exact verify, activate, publish.
|
|
52
|
+
6. `GET /api/v1/instant-worlds/:slug` → the play URL.
|
|
53
|
+
|
|
54
|
+
The programmatic surface (`publishWorld`, `checkBundle`, `whoAmI`, …) is exported from the package
|
|
55
|
+
main, so an MCP server can drive the exact same code paths an agent would.
|
|
56
|
+
|
|
57
|
+
## Develop
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install
|
|
61
|
+
npm test # jest
|
|
62
|
+
npm run build # tsc → dist/ (bin: dist/index.js)
|
|
63
|
+
npm run lint
|
|
64
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type ManifestIssue = {
|
|
2
|
+
path: string;
|
|
3
|
+
message: string;
|
|
4
|
+
};
|
|
5
|
+
export type ParsedSemver = {
|
|
6
|
+
major: number;
|
|
7
|
+
minor: number;
|
|
8
|
+
patch: number;
|
|
9
|
+
prerelease: string[];
|
|
10
|
+
};
|
|
11
|
+
export type AbilityValidator = {
|
|
12
|
+
validateAbilityManifest(value: unknown): ManifestIssue[];
|
|
13
|
+
/** Present on the real vendored artifact — enables the install-time system-pin check (gate 2). */
|
|
14
|
+
satisfiesSystemPin?(systemVersion: string, range: string): boolean;
|
|
15
|
+
/** Frozen semver primitives — the single source of `^`/`~` + prerelease ordering (reused by resolvePins). */
|
|
16
|
+
parseSemver?(version: string): ParsedSemver | null;
|
|
17
|
+
compareSemver?(a: ParsedSemver, b: ParsedSemver): number;
|
|
18
|
+
SYSTEM_IDENTITY?: {
|
|
19
|
+
id: string;
|
|
20
|
+
version: string;
|
|
21
|
+
skeleton: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export type InstalledIndexValidator = {
|
|
25
|
+
validateInstalledIndex(value: unknown): ManifestIssue[];
|
|
26
|
+
isResolvedEntry(entry: Record<string, unknown>): boolean;
|
|
27
|
+
};
|
|
28
|
+
export type AbilityManifestLike = {
|
|
29
|
+
id: string;
|
|
30
|
+
version: string;
|
|
31
|
+
title: string;
|
|
32
|
+
system: {
|
|
33
|
+
id: string;
|
|
34
|
+
version: string;
|
|
35
|
+
skeleton: string;
|
|
36
|
+
};
|
|
37
|
+
activation: {
|
|
38
|
+
mode: string;
|
|
39
|
+
};
|
|
40
|
+
entry: string;
|
|
41
|
+
configSchema: string;
|
|
42
|
+
clips?: {
|
|
43
|
+
name: string;
|
|
44
|
+
src: string;
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
47
|
+
export declare function loadVendoredValidator(): Promise<AbilityValidator>;
|
|
48
|
+
export declare function loadInstalledIndexValidator(): Promise<InstalledIndexValidator>;
|
|
49
|
+
export type AbilityBundleCheck = {
|
|
50
|
+
manifest: AbilityManifestLike | null;
|
|
51
|
+
errors: string[];
|
|
52
|
+
};
|
|
53
|
+
/** True when an ability's built entry has three.js bundled in rather than left external. */
|
|
54
|
+
export declare function bundlesThree(entrySource: string): boolean;
|
|
55
|
+
/** Validates an ability bundle directory: ability.json against the frozen contract + referenced files exist. */
|
|
56
|
+
export declare function checkAbilityBundle(root: string, validator: AbilityValidator): AbilityBundleCheck;
|
|
57
|
+
export declare const MODULES_DIR = "helix_modules";
|
|
58
|
+
export declare const INSTALLED_INDEX = "installed.json";
|
|
59
|
+
export type InstallResult = {
|
|
60
|
+
id: string;
|
|
61
|
+
version: string;
|
|
62
|
+
/** Bundle path relative to the index (== the ability id). */
|
|
63
|
+
path: string;
|
|
64
|
+
/** Total entries in installed.json after this install. */
|
|
65
|
+
installedCount: number;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* `helix install <bundle> --first-party` (a.k.a. `--local`): the FIRST-PARTY whole-bundle copy —
|
|
69
|
+
* validates, copies the bundle (code AND clips) into `<worldDir>/helix_modules/<id>/`, and upserts a
|
|
70
|
+
* v1 local `installed.json` entry. Used by the engine devs to test co-located assets; consumer
|
|
71
|
+
* worlds use `helix install` (no path) which is code-only with assets streaming from the CDN.
|
|
72
|
+
*/
|
|
73
|
+
export declare function installLocalAbility(bundleDir: string, worldDir: string, validator: AbilityValidator, indexValidator: InstalledIndexValidator): InstallResult;
|
package/dist/ability.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.INSTALLED_INDEX = exports.MODULES_DIR = void 0;
|
|
4
|
+
exports.loadVendoredValidator = loadVendoredValidator;
|
|
5
|
+
exports.loadInstalledIndexValidator = loadInstalledIndexValidator;
|
|
6
|
+
exports.bundlesThree = bundlesThree;
|
|
7
|
+
exports.checkAbilityBundle = checkAbilityBundle;
|
|
8
|
+
exports.installLocalAbility = installLocalAbility;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_url_1 = require("node:url");
|
|
12
|
+
// The vendor file is ESM; this package compiles to CommonJS, where tsc downlevels `import()` to
|
|
13
|
+
// `require()` — which cannot load ESM. The Function indirection keeps the native dynamic import.
|
|
14
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
15
|
+
async function loadVendoredValidator() {
|
|
16
|
+
const url = (0, node_url_1.pathToFileURL)((0, node_path_1.join)(__dirname, '..', 'vendor', 'ability-manifest.mjs')).href;
|
|
17
|
+
return (await dynamicImport(url));
|
|
18
|
+
}
|
|
19
|
+
async function loadInstalledIndexValidator() {
|
|
20
|
+
const url = (0, node_url_1.pathToFileURL)((0, node_path_1.join)(__dirname, '..', 'vendor', 'installed-index.mjs')).href;
|
|
21
|
+
return (await dynamicImport(url));
|
|
22
|
+
}
|
|
23
|
+
// three's bundled source carries its REVISION constant (`const REVISION = '172'`) and core class
|
|
24
|
+
// declarations; an external bare import (`import ... from 'three'`) produces only property access
|
|
25
|
+
// against the namespace (`THREE.Vector3`), never these definition forms. Either signal therefore
|
|
26
|
+
// means three got inlined — which forks the instance and silently breaks instanceof across the host.
|
|
27
|
+
const THREE_REVISION = /\bREVISION\s*=\s*['"]\d+['"]/;
|
|
28
|
+
const THREE_CORE_CLASS = /\bclass\s+(Object3D|BufferGeometry|Matrix4|Vector3|Quaternion|Euler|PerspectiveCamera|WebGLRenderer|Material)\b/g;
|
|
29
|
+
/** True when an ability's built entry has three.js bundled in rather than left external. */
|
|
30
|
+
function bundlesThree(entrySource) {
|
|
31
|
+
if (THREE_REVISION.test(entrySource))
|
|
32
|
+
return true;
|
|
33
|
+
const classes = new Set(entrySource.match(THREE_CORE_CLASS) ?? []);
|
|
34
|
+
return classes.size >= 2;
|
|
35
|
+
}
|
|
36
|
+
/** Validates an ability bundle directory: ability.json against the frozen contract + referenced files exist. */
|
|
37
|
+
function checkAbilityBundle(root, validator) {
|
|
38
|
+
const manifestPath = (0, node_path_1.join)(root, 'ability.json');
|
|
39
|
+
if (!(0, node_fs_1.existsSync)(manifestPath)) {
|
|
40
|
+
const hint = (0, node_fs_1.existsSync)((0, node_path_1.join)(root, 'helix.json'))
|
|
41
|
+
? 'this looks like a WORLD bundle (helix.json) — use `helix validate`'
|
|
42
|
+
: 'is this the built bundle directory (dist/)?';
|
|
43
|
+
return { manifest: null, errors: [`no ability.json at the bundle root — ${hint}`] };
|
|
44
|
+
}
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return { manifest: null, errors: [`ability.json is not valid JSON: ${err instanceof Error ? err.message : err}`] };
|
|
51
|
+
}
|
|
52
|
+
const issues = validator.validateAbilityManifest(parsed);
|
|
53
|
+
if (issues.length) {
|
|
54
|
+
return { manifest: null, errors: issues.map((i) => `ability.json ${i.path === '' ? '(root)' : i.path}: ${i.message}`) };
|
|
55
|
+
}
|
|
56
|
+
const manifest = parsed;
|
|
57
|
+
const errors = [];
|
|
58
|
+
// System-pin check against the vendored validator's own system identity (gate 2: refused at
|
|
59
|
+
// install, not just at load) — the artifact banner records which system version it came from.
|
|
60
|
+
const system = validator.SYSTEM_IDENTITY;
|
|
61
|
+
if (system !== undefined && validator.satisfiesSystemPin !== undefined) {
|
|
62
|
+
if (manifest.system.id !== system.id) {
|
|
63
|
+
errors.push(`system.id '${manifest.system.id}' does not match the running system '${system.id}'`);
|
|
64
|
+
}
|
|
65
|
+
else if (!validator.satisfiesSystemPin(system.version, manifest.system.version)) {
|
|
66
|
+
errors.push(`system.version '${manifest.system.version}' is not satisfied by the system version ${system.version}`);
|
|
67
|
+
}
|
|
68
|
+
else if (manifest.system.skeleton !== system.skeleton) {
|
|
69
|
+
errors.push(`system.skeleton '${manifest.system.skeleton}' does not match the running skeleton standard '${system.skeleton}'`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const referenced = [manifest.entry, manifest.configSchema, ...(manifest.clips ?? []).map((c) => c.src)];
|
|
73
|
+
for (const rel of referenced) {
|
|
74
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(root, rel)))
|
|
75
|
+
errors.push(`referenced file missing from the bundle: ${rel}`);
|
|
76
|
+
}
|
|
77
|
+
// Single-instance invariant: the world supplies the one shared three; the entry must import it,
|
|
78
|
+
// not ship its own copy. Build the ability with `--external three`.
|
|
79
|
+
const entryPath = (0, node_path_1.join)(root, manifest.entry);
|
|
80
|
+
if ((0, node_fs_1.existsSync)(entryPath) && bundlesThree((0, node_fs_1.readFileSync)(entryPath, 'utf8'))) {
|
|
81
|
+
errors.push(`${manifest.entry} inlines three.js — rebuild the ability with \`--external three\` so the world supplies the single shared instance`);
|
|
82
|
+
}
|
|
83
|
+
return { manifest: errors.length ? null : manifest, errors };
|
|
84
|
+
}
|
|
85
|
+
exports.MODULES_DIR = 'helix_modules';
|
|
86
|
+
exports.INSTALLED_INDEX = 'installed.json';
|
|
87
|
+
/**
|
|
88
|
+
* `helix install <bundle> --first-party` (a.k.a. `--local`): the FIRST-PARTY whole-bundle copy —
|
|
89
|
+
* validates, copies the bundle (code AND clips) into `<worldDir>/helix_modules/<id>/`, and upserts a
|
|
90
|
+
* v1 local `installed.json` entry. Used by the engine devs to test co-located assets; consumer
|
|
91
|
+
* worlds use `helix install` (no path) which is code-only with assets streaming from the CDN.
|
|
92
|
+
*/
|
|
93
|
+
function installLocalAbility(bundleDir, worldDir, validator, indexValidator) {
|
|
94
|
+
const check = checkAbilityBundle(bundleDir, validator);
|
|
95
|
+
if (check.errors.length || check.manifest === null) {
|
|
96
|
+
throw new Error(`bundle refused:\n${check.errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
97
|
+
}
|
|
98
|
+
const { id, version } = check.manifest;
|
|
99
|
+
const modulesDir = (0, node_path_1.join)(worldDir, exports.MODULES_DIR);
|
|
100
|
+
const indexPath = (0, node_path_1.join)(modulesDir, exports.INSTALLED_INDEX);
|
|
101
|
+
let index = [];
|
|
102
|
+
if ((0, node_fs_1.existsSync)(indexPath)) {
|
|
103
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(indexPath, 'utf8'));
|
|
104
|
+
const issues = indexValidator.validateInstalledIndex(parsed);
|
|
105
|
+
if (issues.length) {
|
|
106
|
+
throw new Error(`existing ${exports.INSTALLED_INDEX} is invalid (delete it to regenerate):\n${issues.map((i) => ` ${i.path}: ${i.message}`).join('\n')}`);
|
|
107
|
+
}
|
|
108
|
+
index = parsed;
|
|
109
|
+
}
|
|
110
|
+
const target = (0, node_path_1.join)(modulesDir, id);
|
|
111
|
+
(0, node_fs_1.rmSync)(target, { recursive: true, force: true });
|
|
112
|
+
(0, node_fs_1.mkdirSync)(target, { recursive: true });
|
|
113
|
+
(0, node_fs_1.cpSync)(bundleDir, target, { recursive: true });
|
|
114
|
+
index = index.filter((entry) => entry.id !== id);
|
|
115
|
+
index.push({ id, version, path: id });
|
|
116
|
+
index.sort((a, b) => a.id.localeCompare(b.id));
|
|
117
|
+
(0, node_fs_1.writeFileSync)(indexPath, `${JSON.stringify(index, null, 2)}\n`);
|
|
118
|
+
return { id, version, path: id, installedCount: index.length };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=ability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ability.js","sourceRoot":"","sources":["../src/ability.ts"],"names":[],"mappings":"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"}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CliCredentials } from './config';
|
|
2
|
+
type NestErrorBody = {
|
|
3
|
+
statusCode?: number;
|
|
4
|
+
message?: string | string[];
|
|
5
|
+
error?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare class ApiError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
readonly body: NestErrorBody;
|
|
10
|
+
constructor(status: number, body: NestErrorBody);
|
|
11
|
+
private static messages;
|
|
12
|
+
render(): string;
|
|
13
|
+
}
|
|
14
|
+
export declare class HelixApi {
|
|
15
|
+
private readonly baseUrl;
|
|
16
|
+
private token;
|
|
17
|
+
constructor(baseUrl: string, token?: string | null);
|
|
18
|
+
setToken(token: string): void;
|
|
19
|
+
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
20
|
+
requestItem<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
21
|
+
static forCredentials(creds: CliCredentials): HelixApi;
|
|
22
|
+
}
|
|
23
|
+
export declare function uploadToTicket(url: string, body: Buffer, contentType: string, cacheControl?: string): Promise<void>;
|
|
24
|
+
export {};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HelixApi = exports.ApiError = void 0;
|
|
4
|
+
exports.uploadToTicket = uploadToTicket;
|
|
5
|
+
class ApiError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
body;
|
|
8
|
+
constructor(status, body) {
|
|
9
|
+
super(ApiError.messages(body).join('; ') || `HTTP ${status}`);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.body = body;
|
|
12
|
+
}
|
|
13
|
+
static messages(body) {
|
|
14
|
+
if (Array.isArray(body.message))
|
|
15
|
+
return body.message;
|
|
16
|
+
if (typeof body.message === 'string')
|
|
17
|
+
return [body.message];
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
// Rendered for terminal humans.
|
|
21
|
+
render() {
|
|
22
|
+
const heading = this.body.error ?? 'Error';
|
|
23
|
+
const lines = [`✖ ${heading} (${this.status})`];
|
|
24
|
+
for (const msg of ApiError.messages(this.body))
|
|
25
|
+
lines.push(` - ${msg}`);
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.ApiError = ApiError;
|
|
30
|
+
class HelixApi {
|
|
31
|
+
baseUrl;
|
|
32
|
+
token;
|
|
33
|
+
constructor(baseUrl, token = null) {
|
|
34
|
+
this.baseUrl = baseUrl;
|
|
35
|
+
this.token = token;
|
|
36
|
+
}
|
|
37
|
+
setToken(token) {
|
|
38
|
+
this.token = token;
|
|
39
|
+
}
|
|
40
|
+
async request(method, path, body) {
|
|
41
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
42
|
+
method,
|
|
43
|
+
headers: {
|
|
44
|
+
'content-type': 'application/json',
|
|
45
|
+
...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
|
|
46
|
+
},
|
|
47
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const problem = (await res.json().catch(() => ({})));
|
|
51
|
+
throw new ApiError(res.status, problem);
|
|
52
|
+
}
|
|
53
|
+
if (res.status === 204)
|
|
54
|
+
return undefined;
|
|
55
|
+
return (await res.json());
|
|
56
|
+
}
|
|
57
|
+
// Convenience for endpoints that return a SingleResponseDto ({ item: T }).
|
|
58
|
+
async requestItem(method, path, body) {
|
|
59
|
+
const envelope = await this.request(method, path, body);
|
|
60
|
+
return envelope.item;
|
|
61
|
+
}
|
|
62
|
+
static forCredentials(creds) {
|
|
63
|
+
return new HelixApi(creds.apiUrl, creds.token);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.HelixApi = HelixApi;
|
|
67
|
+
// Uploads a file body to a presigned URL. Content-Type (and, for package
|
|
68
|
+
// uploads, Cache-Control) are part of the signature, so the client must echo
|
|
69
|
+
// them exactly or R2 rejects the PUT with a signature mismatch.
|
|
70
|
+
async function uploadToTicket(url, body, contentType, cacheControl) {
|
|
71
|
+
const res = await fetch(url, {
|
|
72
|
+
method: 'PUT',
|
|
73
|
+
body: new Uint8Array(body),
|
|
74
|
+
headers: {
|
|
75
|
+
'content-type': contentType,
|
|
76
|
+
...(cacheControl ? { 'cache-control': cacheControl } : {}),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
throw new Error(`upload failed (${res.status}): ${await res.text().catch(() => '')}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type BrowserLoginResult = {
|
|
2
|
+
token: string;
|
|
3
|
+
};
|
|
4
|
+
/** A human-readable default token nickname, e.g. "HELIX CLI (my-laptop)". */
|
|
5
|
+
export declare function defaultClientTokenName(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Derive the website URL from the API URL: `https://api.helixgame.com` →
|
|
8
|
+
* `https://helixgame.com`. For local dev (e.g. backend on localhost) the host
|
|
9
|
+
* isn't an `api.` subdomain of the site, so pass `--web-url` explicitly.
|
|
10
|
+
*/
|
|
11
|
+
export declare function deriveWebUrl(apiUrl: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Run the loopback login flow. Resolves with the captured client token, or
|
|
14
|
+
* rejects on timeout / state mismatch / a site-reported error.
|
|
15
|
+
*/
|
|
16
|
+
export declare function browserLogin(opts: {
|
|
17
|
+
webUrl: string;
|
|
18
|
+
name: string;
|
|
19
|
+
openBrowser?: boolean;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
onUrl?: (url: string) => void;
|
|
22
|
+
}): Promise<BrowserLoginResult>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultClientTokenName = defaultClientTokenName;
|
|
4
|
+
exports.deriveWebUrl = deriveWebUrl;
|
|
5
|
+
exports.browserLogin = browserLogin;
|
|
6
|
+
// Browser-based login (OAuth-style loopback flow), mirroring how the HELIX
|
|
7
|
+
// desktop launcher authenticates via the website. Instead of pasting a token,
|
|
8
|
+
// `helix login` opens the browser to the HELIX website's /cli-login page; once
|
|
9
|
+
// the user is signed in there, the site mints a client token and redirects the
|
|
10
|
+
// browser to a one-shot loopback server this process runs on 127.0.0.1, which
|
|
11
|
+
// captures the token. A random `state` ties the redirect to this process.
|
|
12
|
+
//
|
|
13
|
+
// Headless/CI keeps using `helix login --token <client-token>` (no browser).
|
|
14
|
+
const node_http_1 = require("node:http");
|
|
15
|
+
const node_crypto_1 = require("node:crypto");
|
|
16
|
+
const node_child_process_1 = require("node:child_process");
|
|
17
|
+
const node_os_1 = require("node:os");
|
|
18
|
+
const page = (title, body) => `<!doctype html><html lang="en"><head><meta charset="utf-8">` +
|
|
19
|
+
`<meta name="viewport" content="width=device-width,initial-scale=1">` +
|
|
20
|
+
`<title>${title}</title><style>` +
|
|
21
|
+
`body{font-family:system-ui,-apple-system,Segoe UI,sans-serif;background:#0a0a0a;color:#ededed;` +
|
|
22
|
+
`display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0}` +
|
|
23
|
+
`.card{max-width:420px;text-align:center;padding:40px;border:1px solid #222;border-radius:12px;background:#111}` +
|
|
24
|
+
`h1{font-size:20px;text-transform:uppercase;letter-spacing:.04em;margin:0 0 12px}` +
|
|
25
|
+
`p{color:#9a9a9a;font-size:14px;line-height:1.6;margin:0}` +
|
|
26
|
+
`</style></head><body><div class="card"><h1>${title}</h1><p>${body}</p></div></body></html>`;
|
|
27
|
+
const SUCCESS_HTML = page('Signed in to HELIX', "You're logged in to the HELIX CLI. You can close this tab and return to your terminal.");
|
|
28
|
+
const errorHtml = (msg) => page('Sign-in failed', msg);
|
|
29
|
+
/** A human-readable default token nickname, e.g. "HELIX CLI (my-laptop)". */
|
|
30
|
+
function defaultClientTokenName() {
|
|
31
|
+
let host = 'cli';
|
|
32
|
+
try {
|
|
33
|
+
host = (0, node_os_1.hostname)() || 'cli';
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// hostname() can throw on some sandboxes — fall back to a generic name.
|
|
37
|
+
}
|
|
38
|
+
return `HELIX CLI (${host})`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Derive the website URL from the API URL: `https://api.helixgame.com` →
|
|
42
|
+
* `https://helixgame.com`. For local dev (e.g. backend on localhost) the host
|
|
43
|
+
* isn't an `api.` subdomain of the site, so pass `--web-url` explicitly.
|
|
44
|
+
*/
|
|
45
|
+
function deriveWebUrl(apiUrl) {
|
|
46
|
+
try {
|
|
47
|
+
const u = new URL(apiUrl);
|
|
48
|
+
if (u.hostname.startsWith('api.'))
|
|
49
|
+
u.hostname = u.hostname.slice('api.'.length);
|
|
50
|
+
return u.origin;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return apiUrl.replace(/\/$/, '');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function openInBrowser(url) {
|
|
57
|
+
try {
|
|
58
|
+
if (process.platform === 'win32') {
|
|
59
|
+
// NOT `cmd /c start` — cmd treats `&` (and `(`/`)`) in the URL's query
|
|
60
|
+
// string as command separators and truncates it. rundll32 hands the full
|
|
61
|
+
// URL to the shell's protocol handler with no cmd re-parsing.
|
|
62
|
+
(0, node_child_process_1.spawn)('rundll32', ['url.dll,FileProtocolHandler', url], {
|
|
63
|
+
stdio: 'ignore',
|
|
64
|
+
detached: true,
|
|
65
|
+
}).unref();
|
|
66
|
+
}
|
|
67
|
+
else if (process.platform === 'darwin') {
|
|
68
|
+
(0, node_child_process_1.spawn)('open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
(0, node_child_process_1.spawn)('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Best-effort — the caller also prints the URL for manual copy.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Run the loopback login flow. Resolves with the captured client token, or
|
|
80
|
+
* rejects on timeout / state mismatch / a site-reported error.
|
|
81
|
+
*/
|
|
82
|
+
function browserLogin(opts) {
|
|
83
|
+
const state = (0, node_crypto_1.randomBytes)(16).toString('hex');
|
|
84
|
+
const timeoutMs = opts.timeoutMs ?? 5 * 60 * 1000;
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
let timer;
|
|
87
|
+
const server = (0, node_http_1.createServer)((req, res) => {
|
|
88
|
+
const reqUrl = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
89
|
+
if (reqUrl.pathname !== '/callback') {
|
|
90
|
+
res.statusCode = 404;
|
|
91
|
+
res.end('Not found');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const settle = (status, html) => {
|
|
95
|
+
res.statusCode = status;
|
|
96
|
+
res.setHeader('content-type', 'text/html; charset=utf-8');
|
|
97
|
+
res.end(html);
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
server.close();
|
|
100
|
+
};
|
|
101
|
+
const error = reqUrl.searchParams.get('error');
|
|
102
|
+
const token = reqUrl.searchParams.get('token');
|
|
103
|
+
const gotState = reqUrl.searchParams.get('state');
|
|
104
|
+
if (error) {
|
|
105
|
+
settle(400, errorHtml(`The website reported an error: ${error}.`));
|
|
106
|
+
reject(new Error(`login failed: ${error}`));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!gotState || gotState !== state) {
|
|
110
|
+
settle(400, errorHtml('State mismatch — the response did not match this login attempt. Aborted.'));
|
|
111
|
+
reject(new Error('state mismatch (possible CSRF) — aborted'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!token) {
|
|
115
|
+
settle(400, errorHtml('No token was returned by the website.'));
|
|
116
|
+
reject(new Error('no token in callback'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
settle(200, SUCCESS_HTML);
|
|
120
|
+
resolve({ token });
|
|
121
|
+
});
|
|
122
|
+
timer = setTimeout(() => {
|
|
123
|
+
server.close();
|
|
124
|
+
reject(new Error('timed out waiting for the browser login (5 minutes)'));
|
|
125
|
+
}, timeoutMs);
|
|
126
|
+
server.on('error', (err) => {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
reject(err);
|
|
129
|
+
});
|
|
130
|
+
// Bind loopback-only on an ephemeral port, then build the URL with the port.
|
|
131
|
+
server.listen(0, '127.0.0.1', () => {
|
|
132
|
+
const addr = server.address();
|
|
133
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
134
|
+
if (!port) {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
server.close();
|
|
137
|
+
reject(new Error('could not bind a loopback port for login'));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const loginUrl = `${opts.webUrl.replace(/\/$/, '')}/cli-login` +
|
|
141
|
+
`?port=${port}&state=${state}&name=${encodeURIComponent(opts.name)}`;
|
|
142
|
+
opts.onUrl?.(loginUrl);
|
|
143
|
+
if (opts.openBrowser !== false)
|
|
144
|
+
openInBrowser(loginUrl);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=browserLogin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserLogin.js","sourceRoot":"","sources":["../src/browserLogin.ts"],"names":[],"mappings":"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"}
|
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NormalizedHelixManifest } from '@hypersoniclabs/helix-manifest';
|
|
2
|
+
export type BundleFile = {
|
|
3
|
+
path: string;
|
|
4
|
+
size: number;
|
|
5
|
+
absolutePath: string;
|
|
6
|
+
content?: Buffer;
|
|
7
|
+
};
|
|
8
|
+
export type BundleCheck = {
|
|
9
|
+
manifest: NormalizedHelixManifest | null;
|
|
10
|
+
files: BundleFile[];
|
|
11
|
+
errors: string[];
|
|
12
|
+
};
|
|
13
|
+
export declare function checkBundle(root: string): BundleCheck;
|
|
14
|
+
export declare const MULTIPLAYER_CONFIG_FILENAME = "multiplayer.json";
|
|
15
|
+
export declare function multiplayerConfigBundleFile(root: string, manifest: NormalizedHelixManifest): BundleFile | null;
|