@helix3/helix-cli 0.1.13-helix3.74 → 0.1.13-helix3.76
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/dist/audioCommand.d.ts +50 -0
- package/dist/audioCommand.js +132 -61
- package/dist/audioCommand.js.map +1 -1
- package/dist/index.js +139 -16
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +10 -1
- package/dist/lib.js +14 -2
- package/dist/lib.js.map +1 -1
- package/dist/money.d.ts +54 -4
- package/dist/money.js +86 -8
- package/dist/money.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/world/analyze.d.ts +6 -1
- package/dist/world/analyze.js +94 -0
- package/dist/world/analyze.js.map +1 -1
- package/package.json +1 -1
package/dist/audioCommand.d.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { type GenerateAudioInput, type StandaloneGeneratedAsset } from './assets';
|
|
3
3
|
import type { CliCredentials } from './config';
|
|
4
|
+
type GenerateAudioOptions = {
|
|
5
|
+
mode: string;
|
|
6
|
+
additionalPrompt?: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
outputFormat?: string;
|
|
9
|
+
projectId?: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
durationSeconds?: string;
|
|
13
|
+
loop?: boolean;
|
|
14
|
+
promptInfluence?: string;
|
|
15
|
+
forceInstrumental?: boolean;
|
|
16
|
+
text?: string;
|
|
17
|
+
voiceId?: string;
|
|
18
|
+
languageCode?: string;
|
|
19
|
+
seed?: string;
|
|
20
|
+
stability?: string;
|
|
21
|
+
similarityBoost?: string;
|
|
22
|
+
style?: string;
|
|
23
|
+
speed?: string;
|
|
24
|
+
speakerBoost?: boolean;
|
|
25
|
+
pronunciationDictionaryIds?: string;
|
|
26
|
+
output?: string;
|
|
27
|
+
};
|
|
4
28
|
export type GenerateAudioCommandDependencies = {
|
|
5
29
|
requireAuth: () => CliCredentials;
|
|
6
30
|
generateAudio: (creds: CliCredentials, input: GenerateAudioInput, options?: {
|
|
@@ -12,4 +36,30 @@ export type GenerateAudioCommandDependencies = {
|
|
|
12
36
|
printJson: (value: unknown) => void;
|
|
13
37
|
fail: (message: string) => never;
|
|
14
38
|
};
|
|
39
|
+
export declare const AUDIO_MODES: readonly ["sound_effect", "music", "text_to_speech"];
|
|
40
|
+
export declare const AUDIO_OPTIONS: {
|
|
41
|
+
flags: string;
|
|
42
|
+
description: string;
|
|
43
|
+
default?: string;
|
|
44
|
+
shared?: boolean;
|
|
45
|
+
}[];
|
|
46
|
+
/** Attach the audio flags. `skipShared` omits the ones the target command already declares. */
|
|
47
|
+
export declare function addAudioOptions(command: Command, opts?: {
|
|
48
|
+
skipShared?: boolean;
|
|
49
|
+
}): Command;
|
|
50
|
+
export type AudioFlagValues = Omit<GenerateAudioOptions, 'output'>;
|
|
51
|
+
/**
|
|
52
|
+
* Parse the audio flags into the library's input shape. Shared by both routes so a flag can never
|
|
53
|
+
* mean one thing synchronously and another durably.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildAudioInput(prompt: string | undefined, opts: AudioFlagValues, fail: (message: string) => never): GenerateAudioInput;
|
|
56
|
+
/**
|
|
57
|
+
* Everything that can be known to be wrong BEFORE any LIX moves. Returns the problems rather than
|
|
58
|
+
* throwing, so callers can render them all at once with the fix named.
|
|
59
|
+
*
|
|
60
|
+
* The voice check is deliberately NOT done here — it needs the catalog, and a network lookup that
|
|
61
|
+
* fails must not become a new way to block a valid job.
|
|
62
|
+
*/
|
|
63
|
+
export declare function validateAudioRequest(prompt: string | undefined, opts: AudioFlagValues): string[];
|
|
15
64
|
export declare function registerGenerateAudioCommand(assets: Command, dependencies: GenerateAudioCommandDependencies): void;
|
|
65
|
+
export {};
|
package/dist/audioCommand.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUDIO_OPTIONS = exports.AUDIO_MODES = void 0;
|
|
4
|
+
exports.addAudioOptions = addAudioOptions;
|
|
5
|
+
exports.buildAudioInput = buildAudioInput;
|
|
6
|
+
exports.validateAudioRequest = validateAudioRequest;
|
|
3
7
|
exports.registerGenerateAudioCommand = registerGenerateAudioCommand;
|
|
4
8
|
const api_1 = require("./api");
|
|
5
9
|
const assets_1 = require("./assets");
|
|
6
|
-
|
|
10
|
+
exports.AUDIO_MODES = [
|
|
7
11
|
'sound_effect',
|
|
8
12
|
'music',
|
|
9
13
|
'text_to_speech',
|
|
@@ -24,73 +28,140 @@ function commaList(value) {
|
|
|
24
28
|
.filter(Boolean);
|
|
25
29
|
return values?.length ? values : undefined;
|
|
26
30
|
}
|
|
31
|
+
// The audio flags, as DATA, so the synchronous route (`generate-audio`) and the durable route
|
|
32
|
+
// (`assets start audio`) attach the SAME names from one definition. They drifted once already:
|
|
33
|
+
// `start` accepted no mode/text/voice flags at all, so the async path agents are told to prefer
|
|
34
|
+
// could only ever produce a sound effect, and music and speech were reachable only by blocking the
|
|
35
|
+
// call with no job id to poll.
|
|
36
|
+
//
|
|
37
|
+
// `start` already declares --title/--additional-prompt/--project-id/--session-id/--duration-seconds
|
|
38
|
+
// /--loop for every kind, so those are marked shared and skipped when attaching there.
|
|
39
|
+
exports.AUDIO_OPTIONS = [
|
|
40
|
+
{ flags: '--mode <mode>', description: 'sound_effect, music, or text_to_speech', default: 'sound_effect' },
|
|
41
|
+
{ flags: '--additional-prompt <text>', description: 'additional generation guidance', shared: true },
|
|
42
|
+
{ flags: '--model <model>', description: 'an allowlisted ElevenLabs model id for the selected --mode' },
|
|
43
|
+
{ flags: '--output-format <format>', description: 'portable MP3 output format, e.g. mp3_44100_128' },
|
|
44
|
+
{ flags: '--project-id <id>', description: 'Dreamer project id', shared: true },
|
|
45
|
+
{ flags: '--session-id <id>', description: 'Dreamer session id', shared: true },
|
|
46
|
+
{ flags: '--title <title>', description: 'asset title', shared: true },
|
|
47
|
+
{ flags: '--duration-seconds <n>', description: 'requested duration — sound_effect: 0.5-30s; music: 3-600s', shared: true },
|
|
48
|
+
{ flags: '--loop', description: 'sound_effect only: request a seamless loop', shared: true },
|
|
49
|
+
{ flags: '--prompt-influence <n>', description: 'sound_effect only: how closely to follow the prompt (0-1)' },
|
|
50
|
+
{ flags: '--force-instrumental', description: 'music only: request an instrumental-only composition' },
|
|
51
|
+
{ flags: '--text <text>', description: 'text_to_speech only: the text to convert to speech' },
|
|
52
|
+
{ flags: '--voice-id <id>', description: 'text_to_speech only: the voice id to speak with (see `helix assets voices`)' },
|
|
53
|
+
{ flags: '--language-code <code>', description: 'text_to_speech only: ISO 639-1 language code' },
|
|
54
|
+
{ flags: '--seed <n>', description: 'text_to_speech only: a seed for deterministic output' },
|
|
55
|
+
{ flags: '--stability <n>', description: 'text_to_speech only: voice stability (0-1)' },
|
|
56
|
+
{ flags: '--similarity-boost <n>', description: 'text_to_speech only: voice similarity boost (0-1)' },
|
|
57
|
+
{ flags: '--style <n>', description: 'text_to_speech only: voice style (0-1)' },
|
|
58
|
+
{ flags: '--speed <n>', description: 'text_to_speech only: voice speed (0.7-1.2)' },
|
|
59
|
+
{ flags: '--speaker-boost', description: 'text_to_speech only: enable speaker boost' },
|
|
60
|
+
{
|
|
61
|
+
flags: '--pronunciation-dictionary-ids <a,b,c>',
|
|
62
|
+
description: 'text_to_speech only: up to 3 comma-separated pronunciation dictionary ids',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
/** Attach the audio flags. `skipShared` omits the ones the target command already declares. */
|
|
66
|
+
function addAudioOptions(command, opts = {}) {
|
|
67
|
+
for (const option of exports.AUDIO_OPTIONS) {
|
|
68
|
+
if (opts.skipShared && option.shared)
|
|
69
|
+
continue;
|
|
70
|
+
if (option.default === undefined)
|
|
71
|
+
command.option(option.flags, option.description);
|
|
72
|
+
else
|
|
73
|
+
command.option(option.flags, option.description, option.default);
|
|
74
|
+
}
|
|
75
|
+
return command;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse the audio flags into the library's input shape. Shared by both routes so a flag can never
|
|
79
|
+
* mean one thing synchronously and another durably.
|
|
80
|
+
*/
|
|
81
|
+
function buildAudioInput(prompt, opts, fail) {
|
|
82
|
+
const stability = optionalNumber(opts.stability, '--stability', fail);
|
|
83
|
+
const similarityBoost = optionalNumber(opts.similarityBoost, '--similarity-boost', fail);
|
|
84
|
+
const style = optionalNumber(opts.style, '--style', fail);
|
|
85
|
+
const speed = optionalNumber(opts.speed, '--speed', fail);
|
|
86
|
+
const voiceSettings = stability === undefined &&
|
|
87
|
+
similarityBoost === undefined &&
|
|
88
|
+
style === undefined &&
|
|
89
|
+
speed === undefined &&
|
|
90
|
+
opts.speakerBoost === undefined
|
|
91
|
+
? undefined
|
|
92
|
+
: { stability, similarityBoost, style, speed, speakerBoost: opts.speakerBoost };
|
|
93
|
+
return {
|
|
94
|
+
prompt,
|
|
95
|
+
audioMode: opts.mode,
|
|
96
|
+
additionalPrompt: opts.additionalPrompt,
|
|
97
|
+
model: opts.model,
|
|
98
|
+
outputFormat: opts.outputFormat,
|
|
99
|
+
projectId: opts.projectId,
|
|
100
|
+
sessionId: opts.sessionId,
|
|
101
|
+
title: opts.title,
|
|
102
|
+
durationSeconds: optionalNumber(opts.durationSeconds, '--duration-seconds', fail),
|
|
103
|
+
loop: opts.loop,
|
|
104
|
+
promptInfluence: optionalNumber(opts.promptInfluence, '--prompt-influence', fail),
|
|
105
|
+
forceInstrumental: opts.forceInstrumental,
|
|
106
|
+
text: opts.text,
|
|
107
|
+
voiceId: opts.voiceId,
|
|
108
|
+
languageCode: opts.languageCode,
|
|
109
|
+
seed: optionalNumber(opts.seed, '--seed', fail),
|
|
110
|
+
voiceSettings,
|
|
111
|
+
pronunciationDictionaryIds: commaList(opts.pronunciationDictionaryIds),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Everything that can be known to be wrong BEFORE any LIX moves. Returns the problems rather than
|
|
116
|
+
* throwing, so callers can render them all at once with the fix named.
|
|
117
|
+
*
|
|
118
|
+
* The voice check is deliberately NOT done here — it needs the catalog, and a network lookup that
|
|
119
|
+
* fails must not become a new way to block a valid job.
|
|
120
|
+
*/
|
|
121
|
+
function validateAudioRequest(prompt, opts) {
|
|
122
|
+
const problems = [];
|
|
123
|
+
const mode = opts.mode;
|
|
124
|
+
if (!exports.AUDIO_MODES.includes(mode)) {
|
|
125
|
+
problems.push(`--mode must be ${exports.AUDIO_MODES.join(', ')}; got '${opts.mode}'`);
|
|
126
|
+
return problems; // every other rule is mode-specific
|
|
127
|
+
}
|
|
128
|
+
if (mode === 'text_to_speech') {
|
|
129
|
+
if (!opts.text?.trim()) {
|
|
130
|
+
problems.push('text_to_speech needs --text "<what to say>" (the prompt argument is not used for speech)');
|
|
131
|
+
}
|
|
132
|
+
if (!opts.voiceId?.trim()) {
|
|
133
|
+
problems.push('text_to_speech needs --voice-id <id> — list them with `helix assets voices`');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (!prompt?.trim()) {
|
|
137
|
+
problems.push(`${mode} needs a prompt argument describing the audio to generate`);
|
|
138
|
+
}
|
|
139
|
+
if (mode !== 'music' && opts.forceInstrumental) {
|
|
140
|
+
problems.push('--force-instrumental applies to music only');
|
|
141
|
+
}
|
|
142
|
+
if (mode !== 'sound_effect' && opts.loop) {
|
|
143
|
+
problems.push('--loop applies to sound_effect only');
|
|
144
|
+
}
|
|
145
|
+
if (mode !== 'sound_effect' && opts.promptInfluence !== undefined) {
|
|
146
|
+
problems.push('--prompt-influence applies to sound_effect only');
|
|
147
|
+
}
|
|
148
|
+
return problems;
|
|
149
|
+
}
|
|
27
150
|
function registerGenerateAudioCommand(assets, dependencies) {
|
|
28
|
-
assets
|
|
151
|
+
const command = assets
|
|
29
152
|
.command('generate-audio')
|
|
30
|
-
.description('Generate a sound effect, music track, or spoken line via ElevenLabs (through Dreamer), auto-publish it to Vault, and optionally save it locally')
|
|
31
|
-
.argument('[prompt]', 'generation prompt — required for sound_effect/music; text_to_speech uses --text instead')
|
|
32
|
-
|
|
33
|
-
.option('--additional-prompt <text>', 'additional generation guidance')
|
|
34
|
-
.option('--model <model>', 'an allowlisted ElevenLabs model id for the selected --mode')
|
|
35
|
-
.option('--output-format <format>', 'portable MP3 output format, e.g. mp3_44100_128')
|
|
36
|
-
.option('--project-id <id>', 'Dreamer project id')
|
|
37
|
-
.option('--session-id <id>', 'Dreamer session id')
|
|
38
|
-
.option('--title <title>', 'asset title')
|
|
39
|
-
.option('--duration-seconds <n>', 'requested duration — sound_effect: 0.5-30s; music: 3-600s')
|
|
40
|
-
.option('--loop', 'sound_effect only: request a seamless loop')
|
|
41
|
-
.option('--prompt-influence <n>', 'sound_effect only: how closely to follow the prompt (0-1)')
|
|
42
|
-
.option('--force-instrumental', 'music only: request an instrumental-only composition')
|
|
43
|
-
.option('--text <text>', 'text_to_speech only: the text to convert to speech')
|
|
44
|
-
.option('--voice-id <id>', 'text_to_speech only: the ElevenLabs voice id to speak with')
|
|
45
|
-
.option('--language-code <code>', 'text_to_speech only: ISO 639-1 language code')
|
|
46
|
-
.option('--seed <n>', 'text_to_speech only: a seed for deterministic output')
|
|
47
|
-
.option('--stability <n>', 'text_to_speech only: voice stability (0-1)')
|
|
48
|
-
.option('--similarity-boost <n>', 'text_to_speech only: voice similarity boost (0-1)')
|
|
49
|
-
.option('--style <n>', 'text_to_speech only: voice style (0-1)')
|
|
50
|
-
.option('--speed <n>', 'text_to_speech only: voice speed (0.7-1.2)')
|
|
51
|
-
.option('--speaker-boost', 'text_to_speech only: enable speaker boost')
|
|
52
|
-
.option('--pronunciation-dictionary-ids <a,b,c>', 'text_to_speech only: up to 3 comma-separated pronunciation dictionary ids')
|
|
153
|
+
.description('Generate a sound effect, music track, or spoken line via ElevenLabs (through Dreamer), auto-publish it to Vault, and optionally save it locally. Blocks until done — use `helix assets start audio` for a pollable job.')
|
|
154
|
+
.argument('[prompt]', 'generation prompt — required for sound_effect/music; text_to_speech uses --text instead');
|
|
155
|
+
addAudioOptions(command)
|
|
53
156
|
.option('--output <path>', 'save the generated audio file to this path')
|
|
54
157
|
.action(async (prompt, opts) => {
|
|
55
|
-
|
|
56
|
-
|
|
158
|
+
const problems = validateAudioRequest(prompt, opts);
|
|
159
|
+
if (problems.length) {
|
|
160
|
+
dependencies.fail(`✖ ${problems.join('\n ✖ ')}`);
|
|
57
161
|
}
|
|
58
|
-
const durationSeconds = optionalNumber(opts.durationSeconds, '--duration-seconds', dependencies.fail);
|
|
59
|
-
const promptInfluence = optionalNumber(opts.promptInfluence, '--prompt-influence', dependencies.fail);
|
|
60
|
-
const seed = optionalNumber(opts.seed, '--seed', dependencies.fail);
|
|
61
|
-
const stability = optionalNumber(opts.stability, '--stability', dependencies.fail);
|
|
62
|
-
const similarityBoost = optionalNumber(opts.similarityBoost, '--similarity-boost', dependencies.fail);
|
|
63
|
-
const style = optionalNumber(opts.style, '--style', dependencies.fail);
|
|
64
|
-
const speed = optionalNumber(opts.speed, '--speed', dependencies.fail);
|
|
65
|
-
const voiceSettings = stability === undefined &&
|
|
66
|
-
similarityBoost === undefined &&
|
|
67
|
-
style === undefined &&
|
|
68
|
-
speed === undefined &&
|
|
69
|
-
opts.speakerBoost === undefined
|
|
70
|
-
? undefined
|
|
71
|
-
: { stability, similarityBoost, style, speed, speakerBoost: opts.speakerBoost };
|
|
72
162
|
const creds = dependencies.requireAuth();
|
|
73
163
|
try {
|
|
74
|
-
const job = await dependencies.generateAudio(creds, {
|
|
75
|
-
prompt,
|
|
76
|
-
audioMode: opts.mode,
|
|
77
|
-
additionalPrompt: opts.additionalPrompt,
|
|
78
|
-
model: opts.model,
|
|
79
|
-
outputFormat: opts.outputFormat,
|
|
80
|
-
projectId: opts.projectId,
|
|
81
|
-
sessionId: opts.sessionId,
|
|
82
|
-
title: opts.title,
|
|
83
|
-
durationSeconds,
|
|
84
|
-
loop: opts.loop,
|
|
85
|
-
promptInfluence,
|
|
86
|
-
forceInstrumental: opts.forceInstrumental,
|
|
87
|
-
text: opts.text,
|
|
88
|
-
voiceId: opts.voiceId,
|
|
89
|
-
languageCode: opts.languageCode,
|
|
90
|
-
seed,
|
|
91
|
-
voiceSettings,
|
|
92
|
-
pronunciationDictionaryIds: commaList(opts.pronunciationDictionaryIds),
|
|
93
|
-
}, { timeoutMs: assets_1.AUDIO_GENERATION_TIMEOUT_MS });
|
|
164
|
+
const job = await dependencies.generateAudio(creds, buildAudioInput(prompt, opts, dependencies.fail), { timeoutMs: assets_1.AUDIO_GENERATION_TIMEOUT_MS });
|
|
94
165
|
if (opts.output && job.status === 'succeeded' && job.asset) {
|
|
95
166
|
await dependencies.saveGeneratedAsset(job.asset, opts.output, {
|
|
96
167
|
apiUrl: creds.apiUrl,
|
package/dist/audioCommand.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audioCommand.js","sourceRoot":"","sources":["../src/audioCommand.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audioCommand.js","sourceRoot":"","sources":["../src/audioCommand.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -299,7 +299,25 @@ program
|
|
|
299
299
|
.argument('[directory]', 'bundle directory', '.')
|
|
300
300
|
.option('--thumbnail <file>', 'cover image (png, jpg/jpeg, webp, avif, or gif); otherwise uses thumbnail.<ext> at the bundle root')
|
|
301
301
|
.option('--skip-subpath-check', 'skip the pre-publish sub-path load check (not recommended: it is the only gate that serves the bundle the way production does)')
|
|
302
|
+
.option('--description <text>', `catalog description, max ${lib_1.WORLD_DESCRIPTION_MAX} chars (pass "" to clear). helix.json cannot carry this — the manifest schema forbids unknown fields`)
|
|
303
|
+
.option('--description-file <path>', 'read the catalog description from a file')
|
|
302
304
|
.action(async (directory, opts) => {
|
|
305
|
+
// Pure input checks first — bad flags should fail before auth, the network, or any upload.
|
|
306
|
+
if (opts.description !== undefined && opts.descriptionFile) {
|
|
307
|
+
fail('✖ Pass --description or --description-file, not both');
|
|
308
|
+
}
|
|
309
|
+
let description = opts.description;
|
|
310
|
+
if (opts.descriptionFile) {
|
|
311
|
+
try {
|
|
312
|
+
description = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(opts.descriptionFile), 'utf8').trim();
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
fail(`✖ could not read --description-file: ${err instanceof Error ? err.message : String(err)}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (description !== undefined && description.length > lib_1.WORLD_DESCRIPTION_MAX) {
|
|
319
|
+
fail(`✖ description is ${description.length} chars; the limit is ${lib_1.WORLD_DESCRIPTION_MAX}. Nothing was published.`);
|
|
320
|
+
}
|
|
303
321
|
const creds = requireAuth();
|
|
304
322
|
const mismatch = (0, config_1.credsEnvMismatchWarning)(creds.apiUrl);
|
|
305
323
|
if (mismatch)
|
|
@@ -325,7 +343,10 @@ program
|
|
|
325
343
|
}
|
|
326
344
|
}
|
|
327
345
|
try {
|
|
328
|
-
const result = await (0, lib_1.publishWorld)((0, node_path_1.resolve)(directory), creds, (msg) => console.log(msg), {
|
|
346
|
+
const result = await (0, lib_1.publishWorld)((0, node_path_1.resolve)(directory), creds, (msg) => console.log(msg), {
|
|
347
|
+
thumbnail: opts.thumbnail ? (0, node_path_1.resolve)(opts.thumbnail) : undefined,
|
|
348
|
+
description,
|
|
349
|
+
});
|
|
329
350
|
console.log(`✔ Published "${result.title}" build ${result.buildNumber}`);
|
|
330
351
|
if (result.playUrl)
|
|
331
352
|
console.log(` play: ${result.playUrl}`);
|
|
@@ -816,19 +837,29 @@ function buildQuoteInput(kind, prompt, opts) {
|
|
|
816
837
|
* A quote failure never blocks the generation (the server prices it anyway); it warns, because a
|
|
817
838
|
* hard dependency on an advisory call would make the estimate a new way to fail.
|
|
818
839
|
*/
|
|
819
|
-
async function showEstimateBeforeSpending(creds,
|
|
840
|
+
async function showEstimateBeforeSpending(creds,
|
|
841
|
+
// The EXACT body the generation call will send. Passing the real request (rather than rebuilding
|
|
842
|
+
// an approximation of it) is what stops the quoted price and the charged price drifting apart.
|
|
843
|
+
input, opts) {
|
|
820
844
|
if (opts.estimate === false)
|
|
821
845
|
return;
|
|
822
846
|
try {
|
|
823
|
-
const quote = await (0, lib_1.quoteGeneration)(creds,
|
|
847
|
+
const quote = await (0, lib_1.quoteGeneration)(creds, input);
|
|
824
848
|
const balance = await (0, lib_1.getLixBalance)(creds).catch(() => null);
|
|
825
849
|
console.log((0, lib_1.formatQuote)(quote, balance));
|
|
850
|
+
// Guard the CAP, not the estimate. A metered music job is billed on the duration the provider
|
|
851
|
+
// ACCEPTS, which can exceed the one requested — so its authorized maximum sits above the quote.
|
|
852
|
+
// Comparing --max-lix against the estimate lets someone approve a bound the charge is then
|
|
853
|
+
// permitted to exceed, which is the whole point of the flag defeated.
|
|
826
854
|
const maxLix = optionalNumber(typeof opts.maxLix === 'string' ? opts.maxLix : undefined, '--max-lix');
|
|
827
|
-
if (maxLix !== undefined && quote.
|
|
828
|
-
|
|
855
|
+
if (maxLix !== undefined && quote.maxLix > maxLix) {
|
|
856
|
+
const overshoot = quote.maxLix > quote.totalLix
|
|
857
|
+
? ` (estimated ${quote.totalLix}, but this job may be charged up to ${quote.maxLix})`
|
|
858
|
+
: '';
|
|
859
|
+
fail(`✖ This job can cost ${quote.maxLix} LIX, which exceeds --max-lix ${maxLix}${overshoot}. Nothing was charged.`);
|
|
829
860
|
}
|
|
830
|
-
if (balance && balance.source === 'lix-billing' && quote.
|
|
831
|
-
fail(`✖ Not enough LIX: this job
|
|
861
|
+
if (balance && balance.source === 'lix-billing' && quote.maxLix > balance.available) {
|
|
862
|
+
fail(`✖ Not enough LIX: this job can cost up to ${quote.maxLix} and you have ${balance.available} available. Nothing was charged.`);
|
|
832
863
|
}
|
|
833
864
|
console.log('');
|
|
834
865
|
}
|
|
@@ -1140,11 +1171,14 @@ assets
|
|
|
1140
1171
|
fail(`✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
1141
1172
|
}
|
|
1142
1173
|
});
|
|
1143
|
-
assets
|
|
1174
|
+
const assetsStart = assets
|
|
1144
1175
|
.command('start')
|
|
1145
|
-
.description('Start one generation job without waiting; prop/character use Dreamer, other supported kinds use the shared asset pipeline')
|
|
1176
|
+
.description('Start one generation job without waiting; prop/character use Dreamer, other supported kinds use the shared asset pipeline. For audio, --mode selects sound_effect (default), music, or text_to_speech.')
|
|
1146
1177
|
.argument('<kind>', 'prop, character, image, material, audio, gaussian_splat, or animation (explicit unavailable stub)')
|
|
1147
|
-
|
|
1178
|
+
// Optional at the parser level ON PURPOSE: text_to_speech takes its content from --text, and a
|
|
1179
|
+
// commander-level "missing required argument 'prompt'" would be actively misleading there.
|
|
1180
|
+
// Required-ness is enforced below, per kind, with a message that names the right flag.
|
|
1181
|
+
.argument('[prompt]', 'asset prompt (sound_effect/music/other kinds; speech uses --text)')
|
|
1148
1182
|
.option('--title <title>', 'asset title')
|
|
1149
1183
|
.option('--additional-prompt <text>', 'additional generation guidance')
|
|
1150
1184
|
.option('--target-polycount <n>', 'prop/character triangle target')
|
|
@@ -1156,7 +1190,11 @@ assets
|
|
|
1156
1190
|
.option('--repeat-per-meter <n>', 'material physical scale: tile repeats per metre')
|
|
1157
1191
|
.option('--splat-scope <scope>', 'Gaussian-splat scope: object or environment', 'environment')
|
|
1158
1192
|
.option('--max-lix <n>', 'refuse to start if the estimate exceeds this many LIX')
|
|
1159
|
-
.option('--no-estimate', 'skip the up-front cost estimate')
|
|
1193
|
+
.option('--no-estimate', 'skip the up-front cost estimate');
|
|
1194
|
+
// The audio surface, attached from the SAME definition `generate-audio` uses (minus the flags
|
|
1195
|
+
// `start` already declares for every kind), so the durable route reaches music and speech with
|
|
1196
|
+
// identical flag names instead of only ever producing a sound effect.
|
|
1197
|
+
(0, audioCommand_1.addAudioOptions)(assetsStart, { skipShared: true })
|
|
1160
1198
|
.action(async (kind, prompt, opts) => {
|
|
1161
1199
|
const supported = [
|
|
1162
1200
|
'prop',
|
|
@@ -1170,6 +1208,13 @@ assets
|
|
|
1170
1208
|
if (!supported.includes(kind)) {
|
|
1171
1209
|
fail(`✖ kind must be ${supported.join(', ')}; got '${kind}'`);
|
|
1172
1210
|
}
|
|
1211
|
+
// Every kind except audio takes its subject from the prompt argument.
|
|
1212
|
+
if (kind !== 'audio' && !prompt?.trim()) {
|
|
1213
|
+
fail(`✖ ${kind} needs a prompt argument describing what to generate`);
|
|
1214
|
+
}
|
|
1215
|
+
// Past the guard above, only the audio path can still have an absent prompt, and that path
|
|
1216
|
+
// never reads this binding (speech uses --text; sound_effect/music are checked separately).
|
|
1217
|
+
const promptText = prompt ?? '';
|
|
1173
1218
|
const resolution = optionalNumber(opts.resolution, '--resolution');
|
|
1174
1219
|
if (resolution !== 1024 && resolution !== 2048) {
|
|
1175
1220
|
fail('✖ --resolution must be 1024 or 2048');
|
|
@@ -1178,22 +1223,70 @@ assets
|
|
|
1178
1223
|
!['object', 'environment'].includes(opts.splatScope)) {
|
|
1179
1224
|
fail('✖ --splat-scope must be object or environment');
|
|
1180
1225
|
}
|
|
1226
|
+
// Audio is the one kind whose request shape is mode-dependent, so build and check it before
|
|
1227
|
+
// anything else. Until now `start` accepted no audio flags at all, which meant the durable
|
|
1228
|
+
// route agents are told to prefer could only ever emit a sound effect: music and speech were
|
|
1229
|
+
// reachable only by blocking the call, with no job id to poll.
|
|
1230
|
+
let audioInput = null;
|
|
1231
|
+
if (kind === 'audio') {
|
|
1232
|
+
const problems = (0, audioCommand_1.validateAudioRequest)(prompt, opts);
|
|
1233
|
+
if (problems.length) {
|
|
1234
|
+
fail(`✖ ${problems.join('\n ✖ ')}`);
|
|
1235
|
+
}
|
|
1236
|
+
audioInput = {
|
|
1237
|
+
kind,
|
|
1238
|
+
...(0, audioCommand_1.buildAudioInput)(prompt, opts, fail),
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1181
1241
|
const creds = requireAuth();
|
|
1182
|
-
|
|
1242
|
+
// A voice id that is not in the catalog fails HERE, before the quote and before any LIX
|
|
1243
|
+
// moves. A lookup that cannot run only warns — an advisory check must not become a new way
|
|
1244
|
+
// to block a valid job.
|
|
1245
|
+
if (audioInput && typeof audioInput.voiceId === 'string') {
|
|
1246
|
+
const voiceId = audioInput.voiceId;
|
|
1247
|
+
try {
|
|
1248
|
+
const known = await (0, lib_1.listDreamerVoices)(creds, { search: voiceId, pageSize: 100 });
|
|
1249
|
+
const match = known.voices.find((v) => v.id === voiceId);
|
|
1250
|
+
if (!match) {
|
|
1251
|
+
const nearby = known.voices.slice(0, 5).map((v) => `${v.id} (${v.name})`);
|
|
1252
|
+
fail(`✖ No voice "${voiceId}" in the catalog. Nothing was charged.\n` +
|
|
1253
|
+
` List them with: helix assets voices\n` +
|
|
1254
|
+
(nearby.length ? ` Close matches: ${nearby.join(', ')}` : ''));
|
|
1255
|
+
}
|
|
1256
|
+
const languageCode = audioInput.languageCode;
|
|
1257
|
+
if (typeof languageCode === 'string' &&
|
|
1258
|
+
match.languages.length &&
|
|
1259
|
+
!match.languages.some((l) => l.code === languageCode)) {
|
|
1260
|
+
fail(`✖ Voice "${match.name}" does not support language "${languageCode}". Nothing was charged.\n` +
|
|
1261
|
+
` It supports: ${match.languages.map((l) => l.code).join(', ')}`);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
catch (err) {
|
|
1265
|
+
if (err instanceof lib_1.ApiError) {
|
|
1266
|
+
console.warn(` • could not verify the voice id against the catalog: ${err.message}`);
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
throw err;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
const generationInput = audioInput ?? buildQuoteInput(kind, promptText, opts);
|
|
1274
|
+
await showEstimateBeforeSpending(creds, generationInput, opts);
|
|
1183
1275
|
try {
|
|
1184
1276
|
if (kind === 'prop' || kind === 'character') {
|
|
1185
1277
|
printJson(await (0, lib_1.startDreamerAssetGeneration)(creds, {
|
|
1186
1278
|
kind,
|
|
1187
|
-
prompt,
|
|
1279
|
+
prompt: promptText,
|
|
1188
1280
|
title: opts.title,
|
|
1189
1281
|
additionalPrompt: opts.additionalPrompt,
|
|
1190
1282
|
targetPolycount: optionalNumber(opts.targetPolycount, '--target-polycount'),
|
|
1191
1283
|
}));
|
|
1192
1284
|
return;
|
|
1193
1285
|
}
|
|
1194
|
-
|
|
1286
|
+
// For audio, send the SAME object that was just quoted — not a re-derived one.
|
|
1287
|
+
printJson(await (0, lib_1.startStandaloneAssetGeneration)(creds, audioInput ?? {
|
|
1195
1288
|
kind: kind,
|
|
1196
|
-
prompt,
|
|
1289
|
+
prompt: promptText,
|
|
1197
1290
|
title: opts.title,
|
|
1198
1291
|
additionalPrompt: opts.additionalPrompt,
|
|
1199
1292
|
projectId: opts.projectId,
|
|
@@ -1210,6 +1303,36 @@ assets
|
|
|
1210
1303
|
}
|
|
1211
1304
|
});
|
|
1212
1305
|
// ---- Money: what you have, and what a job will cost BEFORE it runs ---------
|
|
1306
|
+
program
|
|
1307
|
+
.command('jobs')
|
|
1308
|
+
.description('List your generation jobs with what each one cost — the answer to "what was I charged for?" after a failure')
|
|
1309
|
+
.option('--state <state>', 'all, unfinished, failed, published, or awaiting_approval', 'all')
|
|
1310
|
+
.option('--limit <n>', 'how many to show', '20')
|
|
1311
|
+
.option('--offset <n>', 'skip this many')
|
|
1312
|
+
.option('--json', 'emit the raw rows')
|
|
1313
|
+
.action(async (opts) => {
|
|
1314
|
+
const states = ['all', 'unfinished', 'failed', 'published', 'awaiting_approval'];
|
|
1315
|
+
if (opts.state && !states.includes(opts.state)) {
|
|
1316
|
+
fail(`✖ --state must be ${states.join(', ')}; got '${opts.state}'`);
|
|
1317
|
+
}
|
|
1318
|
+
const creds = requireAuth();
|
|
1319
|
+
try {
|
|
1320
|
+
const result = await (0, lib_1.listJobs)(creds, {
|
|
1321
|
+
state: opts.state,
|
|
1322
|
+
limit: optionalNumber(opts.limit, '--limit'),
|
|
1323
|
+
offset: optionalNumber(opts.offset, '--offset'),
|
|
1324
|
+
});
|
|
1325
|
+
if (opts.json)
|
|
1326
|
+
printJson(result);
|
|
1327
|
+
else
|
|
1328
|
+
console.log((0, lib_1.formatJobs)(result));
|
|
1329
|
+
}
|
|
1330
|
+
catch (err) {
|
|
1331
|
+
fail(err instanceof lib_1.ApiError
|
|
1332
|
+
? err.render()
|
|
1333
|
+
: `✖ ${err instanceof Error ? err.message : String(err)}`);
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1213
1336
|
program
|
|
1214
1337
|
.command('balance')
|
|
1215
1338
|
.description('Show your LIX balance: available, held by open jobs, and total')
|
|
@@ -1412,7 +1535,7 @@ assets
|
|
|
1412
1535
|
fail(`✖ --visibility must be public, unlisted, or private`);
|
|
1413
1536
|
}
|
|
1414
1537
|
const creds = requireAuth();
|
|
1415
|
-
await showEstimateBeforeSpending(creds, kind, prompt, opts);
|
|
1538
|
+
await showEstimateBeforeSpending(creds, buildQuoteInput(kind, prompt, opts), opts);
|
|
1416
1539
|
try {
|
|
1417
1540
|
printJson(await (0, lib_1.generateAndPublishAsset)(creds, {
|
|
1418
1541
|
kind,
|