@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.
@@ -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 {};
@@ -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
- const AUDIO_MODES = [
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
- .option('--mode <mode>', 'sound_effect, music, or text_to_speech', 'sound_effect')
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
- if (!AUDIO_MODES.includes(opts.mode)) {
56
- dependencies.fail(`✖ --mode must be ${AUDIO_MODES.join(', ')}; got '${opts.mode}'`);
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,
@@ -1 +1 @@
1
- {"version":3,"file":"audioCommand.js","sourceRoot":"","sources":["../src/audioCommand.ts"],"names":[],"mappings":"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"}
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), { thumbnail: opts.thumbnail ? (0, node_path_1.resolve)(opts.thumbnail) : undefined });
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, kind, prompt, opts) {
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, buildQuoteInput(kind, prompt, opts));
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.totalLix > maxLix) {
828
- fail(`✖ Estimated ${quote.totalLix} LIX exceeds --max-lix ${maxLix}. Nothing was charged.`);
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.totalLix > balance.available) {
831
- fail(`✖ Not enough LIX: this job needs ${quote.totalLix} and you have ${balance.available} available. Nothing was charged.`);
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
- .argument('<prompt>', 'asset prompt')
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
- await showEstimateBeforeSpending(creds, kind, prompt, opts);
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
- printJson(await (0, lib_1.startStandaloneAssetGeneration)(creds, {
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,