@helix3/helix-cli 0.1.13-helix3.65 → 0.1.13-helix3.67
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 +49 -2
- package/dist/assets.d.ts +141 -9
- package/dist/assets.js +243 -14
- package/dist/assets.js.map +1 -1
- package/dist/index.js +80 -4
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js +7 -1
- package/dist/lib.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/voiceCommand.d.ts +11 -0
- package/dist/voiceCommand.js +101 -0
- package/dist/voiceCommand.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,9 +49,11 @@ The token is stored in `~/.helix/credentials.json` (mode `600`). It's sent as
|
|
|
49
49
|
| `helix thumbnail set <slug> <file>` | Upload a thumbnail or preview image through the CLI-owned media path. |
|
|
50
50
|
| `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. |
|
|
51
51
|
| `helix assets start\|status\|generate\|resume` | Start or poll generation without duplicates. Props/characters use the main Dreamer path; standalone assets use the shared broker and report the auto-published `vaultAssetId`. |
|
|
52
|
+
| `helix assets voices [--search <text>] [--category premade\|professional] [--page-size <n>] [--page-token <token>] [--json]` | Discover safe text-to-speech voices through the authenticated HELIX catalog, including language, labels, preview, default/recommended selection, and pagination. |
|
|
52
53
|
| `helix assets generate-image\|generate-material\|generate-audio\|generate-environment` | Run the supported standalone generation adapters. Material is a validated PBR bundle (1K default, explicit 2K); splats declare object/environment scope. Animation remains an explicit unavailable boundary. |
|
|
53
54
|
| `helix assets start-reference\|generate-reference` | Start or fully drive a character job from a local four-view PNG/JPEG/WebP. The backend validates and reuses the exact sheet; it skips image generation while preserving mesh, texture, rig, LOD, thumbnail, billing, and Vault publication. |
|
|
54
|
-
| `helix assets materials
|
|
55
|
+
| `helix assets materials [--resolution <res>] [--human]` | Discover the small built-in material palette. `--resolution` keeps only materials carrying that texture resolution. |
|
|
56
|
+
| `helix assets material <id> [--resolution <res>] [--human]` | Resolve one material to its immutable map URLs at the resolution you pick (default: the pack's own default). |
|
|
55
57
|
| `helix assets credits\|check-loaders` | Render asset credits from provenance and reject models requiring unsupported loaders. |
|
|
56
58
|
| `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. |
|
|
57
59
|
| `helix world audit\|source-audit\|perf-gate\|prove-live` | Run the blocking world QA, performance, and deployed-build identity gates. |
|
|
@@ -145,11 +147,15 @@ helix assets generate-audio "hopeful orchestral exploration theme" \
|
|
|
145
147
|
--mode music --duration-seconds 180 --force-instrumental
|
|
146
148
|
|
|
147
149
|
# Text to speech
|
|
150
|
+
helix assets voices --search narrator --page-size 10
|
|
148
151
|
helix assets generate-audio --mode text_to_speech \
|
|
149
|
-
--text "Welcome to HELIX." --voice-id
|
|
152
|
+
--text "Welcome to HELIX." --voice-id <id-from-catalog> \
|
|
150
153
|
--language-code en --stability 0.5 --speaker-boost
|
|
151
154
|
```
|
|
152
155
|
|
|
156
|
+
Use the opaque `--page-token` printed by a page to continue. Voice cloning and
|
|
157
|
+
voice administration are deliberately not exposed.
|
|
158
|
+
|
|
153
159
|
Use a deterministic four-view sheet when you already have approved FRONT, BACK,
|
|
154
160
|
LEFT, and RIGHT character art and do not want Dreamer to generate another image:
|
|
155
161
|
|
|
@@ -164,6 +170,47 @@ The reference endpoint is character-only. The file is image-guarded, normalized,
|
|
|
164
170
|
checksum-bound to the job, semantically validated, and mirrored before approval.
|
|
165
171
|
Retries reuse those same bytes and never invoke or charge the image provider.
|
|
166
172
|
|
|
173
|
+
## Material texture resolution
|
|
174
|
+
|
|
175
|
+
A platform material can ship more than one texture resolution. `--resolution` picks which one you
|
|
176
|
+
get; without it you get the pack's declared default, byte-for-byte what every earlier CLI returned.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
helix assets material brick-block # the pack's default resolution
|
|
180
|
+
helix assets material brick-block --resolution 2k # the 2K variant's map URLs
|
|
181
|
+
helix assets material brick-block --resolution 2048 # same thing — numeric spelling
|
|
182
|
+
helix assets materials --resolution 2k --human # only materials that carry 2K
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Accepted values are the keys the catalog itself declares (`1k`, `2k`, …), case-insensitively, plus
|
|
186
|
+
the numeric aliases `1024`/`2048`, which map onto the variant authored at that pixel edge.
|
|
187
|
+
|
|
188
|
+
Both commands print JSON by default (agents and the MCP delegation parse it); `--human` prints a
|
|
189
|
+
readable summary instead. Either way the output states **which resolution was resolved and which
|
|
190
|
+
ones the material offers**, under `resolution`:
|
|
191
|
+
|
|
192
|
+
```jsonc
|
|
193
|
+
"resolution": {
|
|
194
|
+
"requested": "2k", "resolved": "2k", "pixels": 2048, "default": "1k",
|
|
195
|
+
"available": [{ "key": "1k", "pixels": 1024 }, { "key": "2k", "pixels": 2048 }],
|
|
196
|
+
"applicable": true
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Three rules make this safe to rely on:
|
|
201
|
+
|
|
202
|
+
- **No silent fallback, ever.** Asking for a resolution the material does not carry is an error that
|
|
203
|
+
names the ones it does. You never receive a different resolution than the one you asked for.
|
|
204
|
+
- **Old packs keep working.** A catalog with no `resolutions` block (`schemaVersion: 1`) is treated
|
|
205
|
+
as having exactly one resolution, keyed `default`, derived from its existing `maps`. Such a pack
|
|
206
|
+
never claims to be "1k" — it does not say how big its textures are, so neither do we.
|
|
207
|
+
- **Procedural materials are not an error.** `glass` and `procedural_water` carry no texture maps;
|
|
208
|
+
a resolution request against them is ignored and the output says so in `resolution.note`.
|
|
209
|
+
|
|
210
|
+
`helix assets install` has no resolution flag: Vault related-artifact roles are semantic
|
|
211
|
+
(`source.albedo`, `runtime.ktx2.normal`, `sky.backdrop`), never resolution-tagged, so a filter there
|
|
212
|
+
would filter nothing.
|
|
213
|
+
|
|
167
214
|
## Item publish flow
|
|
168
215
|
|
|
169
216
|
`helix item publish` is a different shape from a world publish: one mesh, one round trip, an
|
package/dist/assets.d.ts
CHANGED
|
@@ -14,6 +14,48 @@ export type DreamerAudioVoiceSettings = {
|
|
|
14
14
|
speed?: number;
|
|
15
15
|
speakerBoost?: boolean;
|
|
16
16
|
};
|
|
17
|
+
export type DreamerVoiceCategory = 'premade' | 'professional';
|
|
18
|
+
export type DreamerVoiceLanguage = {
|
|
19
|
+
code: string;
|
|
20
|
+
locale: string | null;
|
|
21
|
+
accent: string | null;
|
|
22
|
+
previewUrl: string | null;
|
|
23
|
+
};
|
|
24
|
+
export type DreamerCatalogVoice = {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description: string | null;
|
|
28
|
+
category: DreamerVoiceCategory;
|
|
29
|
+
labels: {
|
|
30
|
+
accent: string | null;
|
|
31
|
+
age: string | null;
|
|
32
|
+
gender: string | null;
|
|
33
|
+
useCase: string | null;
|
|
34
|
+
};
|
|
35
|
+
languages: DreamerVoiceLanguage[];
|
|
36
|
+
previewUrl: string | null;
|
|
37
|
+
curated: true;
|
|
38
|
+
};
|
|
39
|
+
export type DreamerVoiceCatalogQuery = {
|
|
40
|
+
search?: string;
|
|
41
|
+
pageSize?: number;
|
|
42
|
+
pageToken?: string;
|
|
43
|
+
category?: DreamerVoiceCategory;
|
|
44
|
+
};
|
|
45
|
+
export type DreamerVoiceCatalogResponse = {
|
|
46
|
+
voices: DreamerCatalogVoice[];
|
|
47
|
+
selection: {
|
|
48
|
+
defaultVoiceId: string | null;
|
|
49
|
+
curatedVoiceIds: string[];
|
|
50
|
+
};
|
|
51
|
+
pageInfo: {
|
|
52
|
+
pageSize: number;
|
|
53
|
+
hasMore: boolean;
|
|
54
|
+
nextPageToken: string | null;
|
|
55
|
+
totalCount: number | null;
|
|
56
|
+
};
|
|
57
|
+
stale: boolean;
|
|
58
|
+
};
|
|
17
59
|
export type DreamerAssetJob = {
|
|
18
60
|
id: string;
|
|
19
61
|
status: string;
|
|
@@ -229,25 +271,108 @@ export declare class UnavailableCapabilityError extends Error {
|
|
|
229
271
|
readonly code = "CAPABILITY_UNAVAILABLE";
|
|
230
272
|
constructor(capability: string, message: string);
|
|
231
273
|
}
|
|
274
|
+
export type MaterialMaps = {
|
|
275
|
+
albedo: string;
|
|
276
|
+
normal: string;
|
|
277
|
+
orm: string;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* One texture-resolution variant a catalog entry declares (pack catalog
|
|
281
|
+
* schemaVersion 2+). `pixels` is the authored square edge, e.g. 2048 for "2k".
|
|
282
|
+
*/
|
|
283
|
+
export type MaterialResolutionVariant = {
|
|
284
|
+
pixels?: number;
|
|
285
|
+
maps: MaterialMaps;
|
|
286
|
+
};
|
|
232
287
|
export type MaterialCatalogEntry = {
|
|
233
288
|
id: string;
|
|
234
289
|
name: string;
|
|
235
290
|
category: string;
|
|
236
291
|
kind: 'texture' | 'glass' | 'procedural_water';
|
|
237
292
|
tags: string[];
|
|
238
|
-
maps
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
293
|
+
/** The DEFAULT resolution's maps. Unchanged across schema versions. */
|
|
294
|
+
maps?: MaterialMaps;
|
|
295
|
+
/** Catalog schemaVersion 2+: names which `resolutions` key `maps` mirrors. */
|
|
296
|
+
defaultResolution?: string;
|
|
297
|
+
/** Catalog schemaVersion 2+: every texture resolution this material carries. */
|
|
298
|
+
resolutions?: Record<string, MaterialResolutionVariant>;
|
|
243
299
|
[key: string]: unknown;
|
|
244
300
|
};
|
|
301
|
+
/** One resolvable resolution of one material, normalized across schema versions. */
|
|
302
|
+
export type MaterialResolutionOption = {
|
|
303
|
+
key: string;
|
|
304
|
+
pixels: number | null;
|
|
305
|
+
maps: MaterialMaps;
|
|
306
|
+
};
|
|
307
|
+
/**
|
|
308
|
+
* What a command actually resolved, and what else it could have. Present on
|
|
309
|
+
* every material this CLI returns so a caller can SEE the choice it has
|
|
310
|
+
* instead of guessing — and so a silent downgrade is impossible.
|
|
311
|
+
*/
|
|
312
|
+
export type MaterialResolutionReport = {
|
|
313
|
+
/** Exactly what the caller passed, or null when they did not ask. */
|
|
314
|
+
requested: string | null;
|
|
315
|
+
/** The catalog key whose maps were used; null when the material has none. */
|
|
316
|
+
resolved: string | null;
|
|
317
|
+
/** Authored square edge of the resolved variant when the pack declares it. */
|
|
318
|
+
pixels: number | null;
|
|
319
|
+
/** The key this material treats as its default. */
|
|
320
|
+
default: string | null;
|
|
321
|
+
available: Array<{
|
|
322
|
+
key: string;
|
|
323
|
+
pixels: number | null;
|
|
324
|
+
}>;
|
|
325
|
+
/** false for procedural materials — they carry no texture maps at all. */
|
|
326
|
+
applicable: boolean;
|
|
327
|
+
note?: string;
|
|
328
|
+
};
|
|
329
|
+
export type ResolvedMaterial = MaterialCatalogEntry & {
|
|
330
|
+
resolvedMaps?: Record<string, string>;
|
|
331
|
+
resolution: MaterialResolutionReport;
|
|
332
|
+
};
|
|
245
333
|
export type MaterialCatalogResult = {
|
|
246
334
|
slug: string;
|
|
247
335
|
version: string;
|
|
336
|
+
/** The catalog's own schemaVersion, verbatim (1 = no per-resolution variants). */
|
|
337
|
+
schemaVersion: string | number | null;
|
|
248
338
|
assetBaseUrl: string;
|
|
249
|
-
materials:
|
|
339
|
+
materials: ResolvedMaterial[];
|
|
340
|
+
resolution: {
|
|
341
|
+
requested: string | null;
|
|
342
|
+
/** Every resolution key offered by ANY material in this pack. */
|
|
343
|
+
packResolutions: Array<{
|
|
344
|
+
key: string;
|
|
345
|
+
pixels: number | null;
|
|
346
|
+
}>;
|
|
347
|
+
matched: number;
|
|
348
|
+
/** Texture materials dropped because they lack the requested resolution. */
|
|
349
|
+
skippedWithoutResolution: number;
|
|
350
|
+
/** Procedural materials dropped because they carry no texture maps. */
|
|
351
|
+
skippedProcedural: number;
|
|
352
|
+
};
|
|
250
353
|
};
|
|
354
|
+
/**
|
|
355
|
+
* The label a schemaVersion-1 entry's single resolution gets. Such a pack never
|
|
356
|
+
* says how big its maps are, so naming it "1k" would be a guess presented as a
|
|
357
|
+
* fact — and a creator asking for "1k" would then be silently served whatever
|
|
358
|
+
* the pack happens to hold. "default" is the only honest label.
|
|
359
|
+
*/
|
|
360
|
+
export declare const DEFAULT_RESOLUTION_KEY = "default";
|
|
361
|
+
/**
|
|
362
|
+
* Every resolution a material offers, in catalog-declared order, normalized so
|
|
363
|
+
* that an old single-resolution pack and a new multi-resolution pack are the
|
|
364
|
+
* same shape to every caller above this line.
|
|
365
|
+
*/
|
|
366
|
+
export declare function materialResolutionOptions(material: MaterialCatalogEntry): MaterialResolutionOption[];
|
|
367
|
+
/** The key whose maps the pack mirrors at the top level — the default download. */
|
|
368
|
+
export declare function materialDefaultResolutionKey(material: MaterialCatalogEntry, options?: MaterialResolutionOption[]): string | null;
|
|
369
|
+
/**
|
|
370
|
+
* Match a requested resolution against what a material declares. Exact keys win
|
|
371
|
+
* (case-insensitively); `1024`/`2048` and `1k`/`2k` are accepted as aliases of
|
|
372
|
+
* each other so a caller never has to know which spelling a pack chose.
|
|
373
|
+
* Returns null when nothing matches — the caller MUST then fail loudly.
|
|
374
|
+
*/
|
|
375
|
+
export declare function matchMaterialResolution(options: MaterialResolutionOption[], requested: string): MaterialResolutionOption | null;
|
|
251
376
|
export declare function generateAndPublishAsset(creds: CliCredentials, input: GenerateAssetInput, options?: GenerateAssetOptions): Promise<DreamerAssetJob>;
|
|
252
377
|
export declare function generateAndPublishAssetFromReference(creds: CliCredentials, input: GenerateAssetFromReferenceInput, options?: GenerateAssetOptions): Promise<DreamerAssetJob>;
|
|
253
378
|
export declare function startDreamerAssetGeneration(creds: CliCredentials, input: GenerateAssetInput): Promise<DreamerAssetJob>;
|
|
@@ -293,6 +418,12 @@ export declare function generateAudio(creds: CliCredentials, input: GenerateAudi
|
|
|
293
418
|
timeoutMs?: number;
|
|
294
419
|
onProgress?: (job: StandaloneGeneratedAsset) => void;
|
|
295
420
|
}): Promise<StandaloneGeneratedAsset>;
|
|
421
|
+
/**
|
|
422
|
+
* List/search the HELIX-safe text-to-speech voice catalog. The backend keeps
|
|
423
|
+
* vendor credentials and voice administration private; callers receive only
|
|
424
|
+
* the stable selection projection used by website, CLI, SDK, and MCP clients.
|
|
425
|
+
*/
|
|
426
|
+
export declare function listDreamerVoices(creds: CliCredentials, query?: DreamerVoiceCatalogQuery): Promise<DreamerVoiceCatalogResponse>;
|
|
296
427
|
export declare function searchVaultAssets(apiUrl: string, query?: VaultAssetSearch, creds?: CliCredentials): Promise<{
|
|
297
428
|
items: VaultAssetSummary[];
|
|
298
429
|
totalItems: number;
|
|
@@ -326,7 +457,8 @@ export declare function listMaterials(apiUrl: string, query?: {
|
|
|
326
457
|
q?: string;
|
|
327
458
|
category?: string;
|
|
328
459
|
limit?: number;
|
|
460
|
+
resolution?: string;
|
|
329
461
|
}, slug?: string): Promise<MaterialCatalogResult>;
|
|
330
|
-
export declare function resolveMaterial(apiUrl: string, id: string, slug?: string
|
|
331
|
-
|
|
332
|
-
}>;
|
|
462
|
+
export declare function resolveMaterial(apiUrl: string, id: string, slug?: string, options?: {
|
|
463
|
+
resolution?: string;
|
|
464
|
+
}): Promise<ResolvedMaterial>;
|
package/dist/assets.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UnavailableCapabilityError = exports.AUDIO_GENERATION_TIMEOUT_MS = exports.VAULT_ASSET_KINDS = void 0;
|
|
3
|
+
exports.DEFAULT_RESOLUTION_KEY = exports.UnavailableCapabilityError = exports.AUDIO_GENERATION_TIMEOUT_MS = exports.VAULT_ASSET_KINDS = void 0;
|
|
4
|
+
exports.materialResolutionOptions = materialResolutionOptions;
|
|
5
|
+
exports.materialDefaultResolutionKey = materialDefaultResolutionKey;
|
|
6
|
+
exports.matchMaterialResolution = matchMaterialResolution;
|
|
4
7
|
exports.generateAndPublishAsset = generateAndPublishAsset;
|
|
5
8
|
exports.generateAndPublishAssetFromReference = generateAndPublishAssetFromReference;
|
|
6
9
|
exports.startDreamerAssetGeneration = startDreamerAssetGeneration;
|
|
@@ -12,6 +15,7 @@ exports.startStandaloneAssetGeneration = startStandaloneAssetGeneration;
|
|
|
12
15
|
exports.getStandaloneAssetGenerationJob = getStandaloneAssetGenerationJob;
|
|
13
16
|
exports.generateImage = generateImage;
|
|
14
17
|
exports.generateAudio = generateAudio;
|
|
18
|
+
exports.listDreamerVoices = listDreamerVoices;
|
|
15
19
|
exports.searchVaultAssets = searchVaultAssets;
|
|
16
20
|
exports.getVaultAsset = getVaultAsset;
|
|
17
21
|
exports.getVaultAssetVersions = getVaultAssetVersions;
|
|
@@ -52,6 +56,184 @@ class UnavailableCapabilityError extends Error {
|
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
exports.UnavailableCapabilityError = UnavailableCapabilityError;
|
|
59
|
+
/**
|
|
60
|
+
* The label a schemaVersion-1 entry's single resolution gets. Such a pack never
|
|
61
|
+
* says how big its maps are, so naming it "1k" would be a guess presented as a
|
|
62
|
+
* fact — and a creator asking for "1k" would then be silently served whatever
|
|
63
|
+
* the pack happens to hold. "default" is the only honest label.
|
|
64
|
+
*/
|
|
65
|
+
exports.DEFAULT_RESOLUTION_KEY = 'default';
|
|
66
|
+
const isMapTable = (value) => !!value &&
|
|
67
|
+
typeof value === 'object' &&
|
|
68
|
+
!Array.isArray(value) &&
|
|
69
|
+
Object.values(value).every((entry) => typeof entry === 'string');
|
|
70
|
+
/**
|
|
71
|
+
* Every resolution a material offers, in catalog-declared order, normalized so
|
|
72
|
+
* that an old single-resolution pack and a new multi-resolution pack are the
|
|
73
|
+
* same shape to every caller above this line.
|
|
74
|
+
*/
|
|
75
|
+
function materialResolutionOptions(material) {
|
|
76
|
+
const declared = material.resolutions;
|
|
77
|
+
if (declared && typeof declared === 'object' && !Array.isArray(declared)) {
|
|
78
|
+
const options = Object.entries(declared)
|
|
79
|
+
.filter(([key, variant]) => key.trim() !== '' && isMapTable(variant?.maps))
|
|
80
|
+
.map(([key, variant]) => ({
|
|
81
|
+
key,
|
|
82
|
+
pixels: typeof variant.pixels === 'number' && Number.isFinite(variant.pixels) ? variant.pixels : null,
|
|
83
|
+
maps: variant.maps,
|
|
84
|
+
}));
|
|
85
|
+
if (options.length)
|
|
86
|
+
return options;
|
|
87
|
+
}
|
|
88
|
+
// schemaVersion 1 (or an entry with no `resolutions`): exactly one resolution,
|
|
89
|
+
// derived from the top-level maps that every existing client already reads.
|
|
90
|
+
if (isMapTable(material.maps)) {
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
key: (typeof material.defaultResolution === 'string' && material.defaultResolution.trim()) || exports.DEFAULT_RESOLUTION_KEY,
|
|
94
|
+
pixels: null,
|
|
95
|
+
maps: material.maps,
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const sameMaps = (a, b) => {
|
|
102
|
+
const left = Object.entries(a).sort(([x], [y]) => x.localeCompare(y));
|
|
103
|
+
const right = Object.entries(b).sort(([x], [y]) => x.localeCompare(y));
|
|
104
|
+
return (left.length === right.length &&
|
|
105
|
+
left.every(([key, value], index) => right[index][0] === key && right[index][1] === value));
|
|
106
|
+
};
|
|
107
|
+
/** The key whose maps the pack mirrors at the top level — the default download. */
|
|
108
|
+
function materialDefaultResolutionKey(material, options = materialResolutionOptions(material)) {
|
|
109
|
+
if (!options.length)
|
|
110
|
+
return null;
|
|
111
|
+
const declared = typeof material.defaultResolution === 'string' ? material.defaultResolution.trim().toLowerCase() : '';
|
|
112
|
+
if (declared) {
|
|
113
|
+
const named = options.find((option) => option.key.toLowerCase() === declared);
|
|
114
|
+
if (named)
|
|
115
|
+
return named.key;
|
|
116
|
+
}
|
|
117
|
+
if (isMapTable(material.maps)) {
|
|
118
|
+
const mirrored = options.find((option) => sameMaps(option.maps, material.maps));
|
|
119
|
+
if (mirrored)
|
|
120
|
+
return mirrored.key;
|
|
121
|
+
}
|
|
122
|
+
return options[0].key;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Match a requested resolution against what a material declares. Exact keys win
|
|
126
|
+
* (case-insensitively); `1024`/`2048` and `1k`/`2k` are accepted as aliases of
|
|
127
|
+
* each other so a caller never has to know which spelling a pack chose.
|
|
128
|
+
* Returns null when nothing matches — the caller MUST then fail loudly.
|
|
129
|
+
*/
|
|
130
|
+
function matchMaterialResolution(options, requested) {
|
|
131
|
+
const wanted = requested.trim().toLowerCase();
|
|
132
|
+
if (!wanted)
|
|
133
|
+
return null;
|
|
134
|
+
const exact = options.find((option) => option.key.toLowerCase() === wanted);
|
|
135
|
+
if (exact)
|
|
136
|
+
return exact;
|
|
137
|
+
// Numeric spelling ("2048", "2048px") → the variant authored at that edge, or
|
|
138
|
+
// the conventional "2k" key when the pack declares no pixel counts.
|
|
139
|
+
const numeric = /^(\d+)(?:px)?$/.exec(wanted);
|
|
140
|
+
if (numeric) {
|
|
141
|
+
const pixels = Number(numeric[1]);
|
|
142
|
+
const byPixels = options.find((option) => option.pixels === pixels);
|
|
143
|
+
if (byPixels)
|
|
144
|
+
return byPixels;
|
|
145
|
+
if (pixels > 0 && pixels % 1024 === 0) {
|
|
146
|
+
const derived = `${pixels / 1024}k`;
|
|
147
|
+
const byDerivedKey = options.find((option) => option.key.toLowerCase() === derived);
|
|
148
|
+
if (byDerivedKey)
|
|
149
|
+
return byDerivedKey;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// "k" spelling → the variant authored at that edge, for a pack whose keys are
|
|
154
|
+
// named something else entirely but which declares pixels.
|
|
155
|
+
const kilo = /^(\d+(?:\.\d+)?)k$/.exec(wanted);
|
|
156
|
+
if (kilo) {
|
|
157
|
+
const pixels = Math.round(Number(kilo[1]) * 1024);
|
|
158
|
+
const byPixels = options.find((option) => option.pixels === pixels);
|
|
159
|
+
if (byPixels)
|
|
160
|
+
return byPixels;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const renderResolutionList = (options) => options.length
|
|
165
|
+
? options.map((option) => (option.pixels ? `${option.key} (${option.pixels}px)` : option.key)).join(', ')
|
|
166
|
+
: '(none)';
|
|
167
|
+
const publicResolutions = (options) => options.map(({ key, pixels }) => ({ key, pixels }));
|
|
168
|
+
/** Union of every resolution key in the pack, in first-seen order. */
|
|
169
|
+
function packResolutionOptions(materials) {
|
|
170
|
+
const seen = new Map();
|
|
171
|
+
for (const material of materials) {
|
|
172
|
+
for (const option of materialResolutionOptions(material)) {
|
|
173
|
+
const id = option.key.toLowerCase();
|
|
174
|
+
const prior = seen.get(id);
|
|
175
|
+
if (!prior)
|
|
176
|
+
seen.set(id, { key: option.key, pixels: option.pixels });
|
|
177
|
+
else if (prior.pixels === null && option.pixels !== null)
|
|
178
|
+
prior.pixels = option.pixels;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return [...seen.values()];
|
|
182
|
+
}
|
|
183
|
+
function absoluteMaps(maps, assetBaseUrl) {
|
|
184
|
+
return Object.fromEntries(Object.entries(maps).map(([key, value]) => [
|
|
185
|
+
key,
|
|
186
|
+
/^https?:\/\//i.test(value) ? value : `${assetBaseUrl}/${value.replace(/^\/+/, '')}`,
|
|
187
|
+
]));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Attach the resolution report (and, when asked, the resolved absolute map
|
|
191
|
+
* URLs) to one catalog entry. Throws when the caller asked for a resolution
|
|
192
|
+
* this material does not carry — never falls back to a different one.
|
|
193
|
+
*/
|
|
194
|
+
function describeMaterialResolution(material, assetBaseUrl, requested) {
|
|
195
|
+
const options = materialResolutionOptions(material);
|
|
196
|
+
const available = publicResolutions(options);
|
|
197
|
+
const defaultKey = materialDefaultResolutionKey(material, options);
|
|
198
|
+
if (!options.length) {
|
|
199
|
+
// Procedural (glass / water): no maps exist, so a resolution is not an
|
|
200
|
+
// error — it simply does not apply, and we say so out loud.
|
|
201
|
+
return {
|
|
202
|
+
...material,
|
|
203
|
+
resolution: {
|
|
204
|
+
requested,
|
|
205
|
+
resolved: null,
|
|
206
|
+
pixels: null,
|
|
207
|
+
default: null,
|
|
208
|
+
available,
|
|
209
|
+
applicable: false,
|
|
210
|
+
...(requested
|
|
211
|
+
? {
|
|
212
|
+
note: `"${material.id}" is a procedural ${material.kind} material with no texture maps — resolution "${requested}" does not apply and was ignored.`,
|
|
213
|
+
}
|
|
214
|
+
: {}),
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const selected = requested
|
|
219
|
+
? matchMaterialResolution(options, requested)
|
|
220
|
+
: options.find((option) => option.key === defaultKey) ?? options[0];
|
|
221
|
+
if (!selected) {
|
|
222
|
+
throw new Error(`Material "${material.id}" does not offer resolution "${requested}" — available: ${renderResolutionList(available)}`);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
...material,
|
|
226
|
+
resolvedMaps: absoluteMaps(selected.maps, assetBaseUrl),
|
|
227
|
+
resolution: {
|
|
228
|
+
requested,
|
|
229
|
+
resolved: selected.key,
|
|
230
|
+
pixels: selected.pixels,
|
|
231
|
+
default: defaultKey,
|
|
232
|
+
available,
|
|
233
|
+
applicable: true,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
55
237
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
238
|
function isTerminalFailure(job) {
|
|
57
239
|
return ['failed', 'blocked_insufficient_spark', 'cancelled'].includes(job.status);
|
|
@@ -199,6 +381,24 @@ function generateImage(creds, input, options = {}) {
|
|
|
199
381
|
function generateAudio(creds, input, options = {}) {
|
|
200
382
|
return generateStandaloneAsset(creds, { kind: 'audio', ...input }, { timeoutMs: exports.AUDIO_GENERATION_TIMEOUT_MS, ...options });
|
|
201
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* List/search the HELIX-safe text-to-speech voice catalog. The backend keeps
|
|
386
|
+
* vendor credentials and voice administration private; callers receive only
|
|
387
|
+
* the stable selection projection used by website, CLI, SDK, and MCP clients.
|
|
388
|
+
*/
|
|
389
|
+
function listDreamerVoices(creds, query = {}) {
|
|
390
|
+
const params = new URLSearchParams();
|
|
391
|
+
if (query.search)
|
|
392
|
+
params.set('search', query.search);
|
|
393
|
+
if (query.pageSize !== undefined)
|
|
394
|
+
params.set('pageSize', String(query.pageSize));
|
|
395
|
+
if (query.pageToken)
|
|
396
|
+
params.set('pageToken', query.pageToken);
|
|
397
|
+
if (query.category)
|
|
398
|
+
params.set('category', query.category);
|
|
399
|
+
const suffix = params.size ? `?${params}` : '';
|
|
400
|
+
return api_1.HelixApi.forCredentials(creds).request('GET', `/api/v1/dreamer/voices${suffix}`);
|
|
401
|
+
}
|
|
202
402
|
async function searchVaultAssets(apiUrl, query = {}, creds) {
|
|
203
403
|
const api = creds ? api_1.HelixApi.forCredentials(creds) : new api_1.HelixApi(apiUrl);
|
|
204
404
|
const params = new URLSearchParams();
|
|
@@ -492,6 +692,13 @@ async function listMaterials(apiUrl, query = {}, slug = 'helix-materials') {
|
|
|
492
692
|
const catalog = (await response.json());
|
|
493
693
|
if (!Array.isArray(catalog.materials))
|
|
494
694
|
throw new Error('Material catalog is invalid');
|
|
695
|
+
const requested = query.resolution?.trim() ? query.resolution.trim() : null;
|
|
696
|
+
const packResolutions = packResolutionOptions(catalog.materials);
|
|
697
|
+
// A resolution this pack does not carry ANYWHERE is a loud failure, not an
|
|
698
|
+
// empty result — an empty list reads as "no materials matched your search".
|
|
699
|
+
if (requested && !catalog.materials.some((material) => matchMaterialResolution(materialResolutionOptions(material), requested))) {
|
|
700
|
+
throw new Error(`Material pack "${catalog.pack?.slug ?? slug}"@${catalog.pack?.version ?? 'latest'} does not offer resolution "${requested}" — available: ${renderResolutionList(packResolutions)}`);
|
|
701
|
+
}
|
|
495
702
|
const needle = query.q?.trim().toLowerCase() ?? '';
|
|
496
703
|
const matched = catalog.materials.filter((material) => {
|
|
497
704
|
if (query.category && material.category !== query.category)
|
|
@@ -502,29 +709,51 @@ async function listMaterials(apiUrl, query = {}, slug = 'helix-materials') {
|
|
|
502
709
|
.toLowerCase()
|
|
503
710
|
.includes(needle));
|
|
504
711
|
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
712
|
+
let skippedWithoutResolution = 0;
|
|
713
|
+
let skippedProcedural = 0;
|
|
714
|
+
const carried = requested
|
|
715
|
+
? matched.filter((material) => {
|
|
716
|
+
const options = materialResolutionOptions(material);
|
|
717
|
+
if (!options.length) {
|
|
718
|
+
skippedProcedural += 1;
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
if (!matchMaterialResolution(options, requested)) {
|
|
722
|
+
skippedWithoutResolution += 1;
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
return true;
|
|
726
|
+
})
|
|
727
|
+
: matched;
|
|
728
|
+
const limited = query.limit === undefined
|
|
729
|
+
? carried
|
|
730
|
+
: carried.slice(0, Math.max(0, Math.floor(query.limit)));
|
|
731
|
+
const materials = limited.map((material) => describeMaterialResolution(material, assetBaseUrl, requested));
|
|
508
732
|
return {
|
|
509
733
|
slug: catalog.pack?.slug ?? slug,
|
|
510
734
|
version: catalog.pack?.version ?? 'latest',
|
|
735
|
+
schemaVersion: catalog.schemaVersion ?? null,
|
|
511
736
|
assetBaseUrl,
|
|
512
737
|
materials,
|
|
738
|
+
resolution: {
|
|
739
|
+
requested,
|
|
740
|
+
packResolutions,
|
|
741
|
+
matched: materials.length,
|
|
742
|
+
skippedWithoutResolution,
|
|
743
|
+
skippedProcedural,
|
|
744
|
+
},
|
|
513
745
|
};
|
|
514
746
|
}
|
|
515
|
-
async function resolveMaterial(apiUrl, id, slug = 'helix-materials') {
|
|
747
|
+
async function resolveMaterial(apiUrl, id, slug = 'helix-materials', options = {}) {
|
|
748
|
+
// Resolve against the UNFILTERED catalog: the resolution check belongs to the
|
|
749
|
+
// named material, so `helix assets material <id> --resolution 4k` must say
|
|
750
|
+
// what THAT material offers, not "not found".
|
|
516
751
|
const catalog = await listMaterials(apiUrl, {}, slug);
|
|
517
752
|
const material = catalog.materials.find((candidate) => candidate.id === id);
|
|
518
753
|
if (!material)
|
|
519
754
|
throw new Error(`Material "${id}" was not found in ${slug}`);
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
/^https?:\/\//i.test(value)
|
|
524
|
-
? value
|
|
525
|
-
: `${catalog.assetBaseUrl}/${value.replace(/^\/+/, '')}`,
|
|
526
|
-
]))
|
|
527
|
-
: undefined;
|
|
528
|
-
return { ...material, ...(resolvedMaps ? { resolvedMaps } : {}) };
|
|
755
|
+
const requested = options.resolution?.trim() ? options.resolution.trim() : null;
|
|
756
|
+
const { resolvedMaps: _ignored, resolution: _prior, ...entry } = material;
|
|
757
|
+
return describeMaterialResolution(entry, catalog.assetBaseUrl, requested);
|
|
529
758
|
}
|
|
530
759
|
//# sourceMappingURL=assets.js.map
|