@assethub/cli 0.1.3 → 0.1.5

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.
Files changed (3) hide show
  1. package/README.md +23 -5
  2. package/dist/index.js +486 -114
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,11 +6,10 @@ Command line interface for AssetHub.
6
6
  export ASSETHUB_API_KEY=ah_live_xxx
7
7
 
8
8
  assethub models list
9
- assethub models list --domain meshGen --ids-only
10
9
  assethub files import https://example.com/input.png --media-type image
11
10
  assethub source create --file ./input.png --media-type image
12
11
  assethub image generate --prompt "stylized prop concept" --wait
13
- assethub mesh generate --file ./input.png --model-id meshGen.hunyuan31 --wait
12
+ assethub mesh generate --file ./input.png --wait
14
13
  assethub jobs watch job_123 --download --out-dir ./out/job
15
14
  assethub parts split --file ./input.png --part-extractor "V1.5" --wait --all-ready --download --out-dir ./out/parts
16
15
  assethub parts compare --file ./input.png --preprocess-prompt "clean white background, centered product photo" --part-extractor "V1.5" --wait --all-ready
@@ -27,22 +26,41 @@ Agent-friendly login:
27
26
  ```sh
28
27
  printf '%s' "$ASSETHUB_API_KEY" | assethub auth login --api-key-stdin
29
28
  assethub image generate --prompt "small stylized game asset chest" --wait --download --out-dir ./out/image
30
- assethub mesh generate --source-url https://example.com/input.png --model-id meshGen.hunyuan31 --wait --download --out-dir ./out/mesh
29
+ assethub mesh generate --source-url https://example.com/input.png --wait --download --out-dir ./out/mesh
31
30
  cat ./character.png | assethub parts split --stdin --file-name character.png --part-extractor "V1.5" --wait --all-ready --download --out-dir ./out/parts
31
+ base64 -i ./character.png | assethub parts split --stdin-base64 --file-name character.png --content-type image/png --part-extractor "V1.5" --wait --all-ready
32
+ printf '{"image_url":{"url":"data:image/png;base64,..."}}' | assethub parts split --stdin-json --part-extractor "V1.5" --wait --all-ready
33
+ assethub parts split --clipboard --file-name character.png --part-extractor "V1.5" --wait --all-ready
32
34
  cat ./character.png | assethub parts compare --stdin --file-name character.png --preprocess-prompt "clean white background" --part-extractor "V1.5" --wait --all-ready
33
35
  ```
34
36
 
35
37
  Agent-oriented input:
36
38
 
37
39
  - `--file <path>` uploads a local image or mesh through the public file API.
38
- - `--stdin --file-name <name>` accepts piped bytes. Give a file name with an
39
- extension when possible.
40
+ - `--stdin --file-name <name>` accepts raw piped bytes. Give a file name with
41
+ an extension when possible.
42
+ - `--stdin-base64 --file-name <name> --content-type <type>` accepts raw base64
43
+ from stdin. It also accepts a full `data:*;base64,...` value.
44
+ - `--stdin-data-uri` accepts a data URI from stdin.
45
+ - `--stdin-json` accepts common agent payload shapes from stdin, including
46
+ OpenAI-style `{"image_url":{"url":"..."}}`, Anthropic-style
47
+ `{"source":{"type":"base64","media_type":"image/png","data":"..."}}`,
48
+ `resourceId`, `fileRef`, `dataUri`, `base64`, `url`, and local `path`.
49
+ - `--source-json <json|@file|@->` parses the same JSON shapes from an argument,
50
+ a file, or stdin.
40
51
  - `--data-uri <uri>` accepts `data:image/png;base64,...` style inputs and uploads
41
52
  them before running the target command.
53
+ - `--clipboard` reads an image from the macOS clipboard for image inputs. It
54
+ uses `pngpaste` when available and falls back to the system clipboard bridge.
42
55
  - `--source-url`, `--source-id`/`--source-resource-id`, and `--file-ref-json`
43
56
  can be reused across image, mesh, and part extraction commands where the API
44
57
  supports them.
45
58
 
59
+ For Codex or other AI agents, the important contract is: if the runtime can
60
+ expose the attachment as bytes, base64, a data URI, a local path, an
61
+ OpenAI/Anthropic-style JSON object, or a URL, the CLI can consume it directly.
62
+ It does not require writing a temporary file first.
63
+
46
64
  Agent-oriented output:
47
65
 
48
66
  - stdout is one final JSON object.
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
3
- import { homedir } from 'node:os';
2
+ import { execFile } from 'node:child_process';
3
+ import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
4
+ import { homedir, tmpdir } from 'node:os';
4
5
  import { basename, dirname, extname, join } from 'node:path';
5
6
  import { argv, env, exit, stdin, stderr, stdout } from 'node:process';
7
+ import { promisify } from 'node:util';
6
8
  import { AssetHubApiError, createAssetHubClient, } from '@assethub/api-client';
7
9
  const defaultBaseUrl = 'https://app.assethub.io';
8
10
  const defaultImageModelId = 'imageGen.nanoBanana.pro.openrouter';
@@ -12,26 +14,32 @@ const defaultPartExtractorName = 'V1.5';
12
14
  const partExtractorOptions = [
13
15
  {
14
16
  publicName: 'Part Extractor V3',
17
+ apiValue: 'ah_part_extractor_v3',
15
18
  aliases: ['part extractor v3', 'v3'],
16
19
  },
17
20
  {
18
21
  publicName: 'V1',
22
+ apiValue: 'ah_part_extractor_v5',
19
23
  aliases: ['v1', '1'],
20
24
  },
21
25
  {
22
26
  publicName: 'V1.5 alpha',
27
+ apiValue: 'ah_part_extractor_v55',
23
28
  aliases: ['v1.5 alpha', 'v1.5-alpha', '1.5 alpha', '1.5-alpha'],
24
29
  },
25
30
  {
26
31
  publicName: 'V1.5',
32
+ apiValue: 'ah_part_extractor_v65',
27
33
  aliases: ['v1.5', '1.5', 'latest', 'default'],
28
34
  },
29
35
  {
30
36
  publicName: 'V2.0 alpha',
37
+ apiValue: 'ah_part_extractor_v20_alpha',
31
38
  aliases: ['v2.0 alpha', 'v2 alpha', 'v2.0-alpha', 'v2-alpha', '2.0 alpha'],
32
39
  },
33
40
  {
34
41
  publicName: 'V2.1 alpha',
42
+ apiValue: 'ah_part_extractor_v21_alpha',
35
43
  aliases: ['v2.1 alpha', 'v2.1-alpha', '2.1 alpha'],
36
44
  },
37
45
  ];
@@ -39,14 +47,16 @@ const partExtractorPublicNames = partExtractorOptions
39
47
  .map(option => option.publicName)
40
48
  .join(', ');
41
49
  const normalizePartExtractorName = (value) => value.trim().toLowerCase().replace(/\s+/g, ' ');
42
- const partExtractorPublicNameByInput = new Map(partExtractorOptions.flatMap(option => [
43
- [normalizePartExtractorName(option.publicName), option.publicName],
50
+ const partExtractorByInput = new Map(partExtractorOptions.flatMap(option => [
51
+ [normalizePartExtractorName(option.publicName), option],
44
52
  ...option.aliases.map(alias => [
45
53
  normalizePartExtractorName(alias),
46
- option.publicName,
54
+ option,
47
55
  ]),
48
56
  ]));
57
+ const partExtractorPublicNameByApiValue = new Map(partExtractorOptions.map(option => [option.apiValue, option.publicName]));
49
58
  const isInternalPartExtractorId = (value) => value.trim().includes('_');
59
+ const execFileAsync = promisify(execFile);
50
60
  const usage = `AssetHub CLI
51
61
 
52
62
  Usage:
@@ -58,18 +68,18 @@ Usage:
58
68
  assethub models get <model-id>
59
69
  assethub files import <url> --media-type image|mesh
60
70
  assethub files upload <file> --media-type image|mesh
61
- assethub source create (--file <path> | --source-url <url> | --source-id <id> | --stdin | --data-uri <uri>) --media-type image|mesh
62
- assethub image generate --prompt <text> [--file <path> | --source-url <url> | --stdin | --data-uri <uri>] [--model-id <id>] [--batch-size <n>] [--wait] [--download --out-dir <dir>] [--api-version v1|v2]
71
+ assethub source create (--file <path> | --source-url <url> | --source-id <id> | --stdin | --stdin-base64 | --stdin-data-uri | --stdin-json | --source-json <json|@file|@-> | --data-uri <uri> | --clipboard) --media-type image|mesh [--file-name <name>] [--content-type <type>]
72
+ assethub image generate --prompt <text> [--file <path> | --source-url <url> | --stdin | --stdin-base64 | --stdin-data-uri | --stdin-json | --source-json <json|@file|@-> | --data-uri <uri> | --clipboard] [--file-name <name>] [--content-type <type>] [--model-id <id>] [--batch-size <n>] [--wait] [--download --out-dir <dir>] [--api-version v1|v2]
63
73
  assethub image generate --prompt <text> --source-resource-id <id> --api-version v2 [--model-id <id>] [--batch-size <n>] [--wait]
64
- assethub mesh generate (--file <path> | --source-url <url> | --source-resource-id <id> | --file-ref-json <json> | --stdin | --data-uri <uri>) [--model-id <id>] [--name <name>] [--wait] [--download --out-dir <dir>]
65
- assethub mesh retopo (--source-resource-id <id> | --source-url <url> | --file-ref-json <json> | --stdin | --data-uri <uri>) --model-id <id> [--wait] [--download --out-dir <dir>]
74
+ assethub mesh generate (--file <path> | --source-url <url> | --source-resource-id <id> | --file-ref-json <json> | --stdin | --stdin-base64 | --stdin-data-uri | --stdin-json | --source-json <json|@file|@-> | --data-uri <uri>) [--file-name <name>] [--content-type <type>] [--model-id <id>] [--name <name>] [--wait] [--download --out-dir <dir>]
75
+ assethub mesh retopo (--source-resource-id <id> | --source-url <url> | --file-ref-json <json> | --stdin | --stdin-base64 | --stdin-data-uri | --stdin-json | --source-json <json|@file|@-> | --data-uri <uri>) --model-id <id> [--file-name <name>] [--content-type <type>] [--wait] [--download --out-dir <dir>]
66
76
  assethub jobs get <job-id> [--download --out-dir <dir>]
67
77
  assethub jobs watch <job-id> [--interval-ms <ms>] [--timeout-ms <ms>] [--download --out-dir <dir>]
68
- assethub production analyze (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --data-uri <uri>) [--part-extractor <name>] [--name <name>] [--wait] [--download --out-dir <dir>]
78
+ assethub production analyze (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --stdin-base64 | --stdin-data-uri | --stdin-json | --source-json <json|@file|@-> | --data-uri <uri> | --clipboard) [--file-name <name>] [--content-type <type>] [--part-extractor <name>] [--name <name>] [--wait] [--download --out-dir <dir>]
69
79
  assethub production status <order-id> [--wait] [--download --out-dir <dir>]
70
80
  assethub production execute --order-id <id> --mission-id <id> --task-id <id> [--task-id <id>...] [--part-extractor <name>] [--part-extraction-mode fast|high_quality] [--wait] [--download --out-dir <dir>]
71
- assethub parts split (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --data-uri <uri> | --order-id <id>) [--part-extractor <name>] [--wait] [--task-id <id>...] [--mission-id <id>] [--all-ready] [--download --out-dir <dir>]
72
- assethub parts compare (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --data-uri <uri>) [--preprocess-prompt <text>] [--preprocess-model-id <id>] [--fail-on-preprocess-error] [--part-extractor <name>] [--wait] [--task-id <id>...] [--mission-id <id>] [--all-ready] [--download --out-dir <dir>]
81
+ assethub parts split (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --stdin-base64 | --stdin-data-uri | --stdin-json | --source-json <json|@file|@-> | --data-uri <uri> | --clipboard | --order-id <id>) [--file-name <name>] [--content-type <type>] [--part-extractor <name>] [--wait] [--task-id <id>...] [--mission-id <id>] [--all-ready] [--download --out-dir <dir>]
82
+ assethub parts compare (--file <path> | --source-url <url> | --source-id <id> | --image-url <url> | --file-ref-json <json> | --stdin | --stdin-base64 | --stdin-data-uri | --stdin-json | --source-json <json|@file|@-> | --data-uri <uri> | --clipboard) [--file-name <name>] [--content-type <type>] [--preprocess-prompt <text>] [--preprocess-model-id <id>] [--fail-on-preprocess-error] [--part-extractor <name>] [--wait] [--task-id <id>...] [--mission-id <id>] [--all-ready] [--download --out-dir <dir>]
73
83
 
74
84
  Global options:
75
85
  --api-key <key> Overrides saved auth and ASSETHUB_API_KEY.
@@ -81,12 +91,16 @@ Global options:
81
91
  Examples:
82
92
  ASSETHUB_API_KEY=ah_live_xxx assethub models list
83
93
  assethub auth login --api-key-stdin
84
- assethub models list --domain meshGen --ids-only
94
+ assethub models list --ids-only
85
95
  assethub files upload ./input.png --media-type image
86
96
  assethub source create --file ./input.png --media-type image
97
+ pbpaste | assethub source create --stdin-data-uri --media-type image
98
+ assethub source create --clipboard --media-type image --file-name clipboard.png
99
+ printf '{"image_url":{"url":"data:image/png;base64,..."}}' | assethub parts split --stdin-json --part-extractor "V1.5" --wait
100
+ base64 -i ./input.png | assethub parts split --stdin-base64 --file-name input.png --content-type image/png --part-extractor "V1.5" --wait
87
101
  cat ./input.png | assethub parts split --stdin --file-name input.png --part-extractor "V1.5" --wait
88
102
  assethub image generate --prompt "stylized prop concept" --wait
89
- assethub mesh generate --file ./input.png --model-id meshGen.hunyuan31 --wait
103
+ assethub mesh generate --file ./input.png --wait
90
104
  assethub production analyze --file ./input.png --part-extractor "V1.5" --wait
91
105
  assethub parts split --file ./input.png --part-extractor "V1.5" --wait --all-ready
92
106
  assethub parts compare --file ./input.png --preprocess-prompt "clean white background, centered product photo" --part-extractor "V1.5" --wait --all-ready
@@ -166,18 +180,29 @@ const resolvePartExtractorPublicName = (value) => {
166
180
  if (isInternalPartExtractorId(value)) {
167
181
  throw new Error(`Internal part extractor IDs are not accepted by the public CLI. Use one of: ${partExtractorPublicNames}.`);
168
182
  }
169
- const publicName = partExtractorPublicNameByInput.get(normalizePartExtractorName(value));
170
- if (publicName == null) {
183
+ const option = partExtractorByInput.get(normalizePartExtractorName(value));
184
+ if (option == null) {
171
185
  throw new Error(`Unknown part extractor: ${value}. Use one of: ${partExtractorPublicNames}.`);
172
186
  }
173
- return publicName;
187
+ return option.publicName;
174
188
  };
175
- const resolvePartExtractorForAnalyze = (flags) => resolvePartExtractorPublicName(getPartExtractorFlag(flags) ?? defaultPartExtractorName);
189
+ const resolvePartExtractorApiValue = (value) => {
190
+ if (isInternalPartExtractorId(value)) {
191
+ throw new Error(`Internal part extractor IDs are not accepted by the public CLI. Use one of: ${partExtractorPublicNames}.`);
192
+ }
193
+ const option = partExtractorByInput.get(normalizePartExtractorName(value));
194
+ if (option == null) {
195
+ throw new Error(`Unknown part extractor: ${value}. Use one of: ${partExtractorPublicNames}.`);
196
+ }
197
+ return option.apiValue;
198
+ };
199
+ const resolvePartExtractorForAnalyze = (flags) => resolvePartExtractorApiValue(getPartExtractorFlag(flags) ?? defaultPartExtractorName);
176
200
  const publicPartExtractorName = (value) => {
177
201
  if (value == null) {
178
202
  return null;
179
203
  }
180
- return (isInternalPartExtractorId(value) ? 'Unknown' : value);
204
+ return (partExtractorPublicNameByApiValue.get(value) ??
205
+ (isInternalPartExtractorId(value) ? 'Unknown' : value));
181
206
  };
182
207
  const sanitizeProductionOutput = (value) => {
183
208
  if (Array.isArray(value)) {
@@ -327,31 +352,73 @@ const createContext = async (flags) => {
327
352
  }),
328
353
  };
329
354
  };
355
+ const isRecord = (value) => value != null && typeof value === 'object' && !Array.isArray(value);
356
+ const isBlobLocation = (value) => {
357
+ if (!isRecord(value) || typeof value.type !== 'string') {
358
+ return false;
359
+ }
360
+ if (value.type === 'supabase' &&
361
+ typeof value.bucket === 'string' &&
362
+ typeof value.path === 'string') {
363
+ return true;
364
+ }
365
+ if (value.type === 'signedSupabase' &&
366
+ typeof value.signedUrl === 'string') {
367
+ return true;
368
+ }
369
+ if (value.type === 'asset' && typeof value.assetId === 'string') {
370
+ return true;
371
+ }
372
+ return false;
373
+ };
330
374
  const parseFileRef = (value) => {
331
375
  const parsed = JSON.parse(value);
332
- if (parsed == null || typeof parsed !== 'object' || !('type' in parsed)) {
376
+ if (!isBlobLocation(parsed)) {
333
377
  throw new Error('--file-ref-json must be a BlobLocation JSON object');
334
378
  }
335
379
  return parsed;
336
380
  };
337
- const readBlob = async (filePath) => {
338
- const buffer = await readFile(filePath);
339
- return new Blob([new Uint8Array(buffer)]);
381
+ const blobFromBuffer = (buffer, type) => {
382
+ const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
383
+ return new Blob([arrayBuffer], type == null ? undefined : { type });
340
384
  };
341
- const blobFromDataUri = (value) => {
342
- const match = value.match(/^data:([^;,]+)?(;base64)?,(.*)$/s);
385
+ const readBlob = async (filePath, type) => blobFromBuffer(await readFile(filePath), type);
386
+ const getContentTypeFlag = (flags) => getFlag(flags, 'content-type') ?? getFlag(flags, 'mime-type');
387
+ const blobFromDataUri = (value, flagName = '--data-uri') => {
388
+ const trimmed = value.trim();
389
+ const match = trimmed.match(/^data:([^;,]+)?(;base64)?,(.*)$/s);
343
390
  if (match == null) {
344
- throw new Error('--data-uri must be a valid data URI.');
391
+ throw new Error(`${flagName} must be a valid data URI.`);
345
392
  }
346
393
  const contentType = match[1] === '' ? undefined : match[1];
347
394
  const isBase64 = match[2] != null;
348
- const payload = match[3] ?? '';
395
+ const payload = (match[3] ?? '').replace(/\s+/g, '');
349
396
  const buffer = isBase64
350
397
  ? Buffer.from(payload, 'base64')
351
398
  : Buffer.from(decodeURIComponent(payload), 'utf8');
352
- const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
353
399
  return {
354
- blob: new Blob([arrayBuffer], contentType == null ? undefined : { type: contentType }),
400
+ blob: blobFromBuffer(buffer, contentType),
401
+ contentType,
402
+ };
403
+ };
404
+ const blobFromBase64 = (value, contentType, flagName) => {
405
+ const trimmed = value.trim();
406
+ if (trimmed.startsWith('data:')) {
407
+ return blobFromDataUri(trimmed, flagName);
408
+ }
409
+ const normalized = trimmed.replace(/\s+/g, '').replace(/-/g, '+').replace(/_/g, '/');
410
+ if (normalized === '') {
411
+ throw new Error(`${flagName} input is empty.`);
412
+ }
413
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
414
+ throw new Error(`${flagName} must contain base64 data or a data URI.`);
415
+ }
416
+ const buffer = Buffer.from(normalized, 'base64');
417
+ if (buffer.byteLength === 0) {
418
+ throw new Error(`${flagName} did not decode to any bytes.`);
419
+ }
420
+ return {
421
+ blob: blobFromBuffer(buffer, contentType),
355
422
  contentType,
356
423
  };
357
424
  };
@@ -376,54 +443,392 @@ const ensureFileNameExtension = (fileName, mediaType, mimeType) => {
376
443
  }
377
444
  return `${fileName}.${extensionFromMimeType(mimeType, mediaType)}`;
378
445
  };
446
+ const missingSourceMessage = 'Missing source. Pass --file, --source-url, --source-resource-id, --source-id, --file-ref-json, --stdin, --stdin-base64, --stdin-data-uri, --stdin-json, --source-json, --data-uri, or --clipboard.';
447
+ const exactlyOneSourceMessage = 'Pass exactly one source input: --file, --source-url/--image-url, --source-id/--source-resource-id, --file-ref-json, --stdin, --stdin-base64, --stdin-data-uri, --stdin-json, --source-json, --data-uri, or --clipboard.';
448
+ const getSourceUrlFlag = (flags) => getFlag(flags, 'source-url') ?? getFlag(flags, 'image-url') ?? getFlag(flags, 'url');
449
+ const assertFlagHasValue = (value, flagDescription) => {
450
+ if (value === 'true' || value.trim() === '') {
451
+ throw new Error(`Missing required flag value: ${flagDescription}`);
452
+ }
453
+ return value;
454
+ };
455
+ const sourceInputCount = (flags) => [
456
+ getSourceResourceIdFlag(flags) != null,
457
+ getSourceUrlFlag(flags) != null,
458
+ getFlag(flags, 'file-ref-json') != null,
459
+ getFlag(flags, 'file') != null,
460
+ getFlag(flags, 'data-uri') != null,
461
+ hasFlag(flags, 'stdin'),
462
+ hasFlag(flags, 'stdin-base64'),
463
+ hasFlag(flags, 'stdin-data-uri'),
464
+ hasFlag(flags, 'stdin-json'),
465
+ getFlag(flags, 'source-json') != null,
466
+ hasFlag(flags, 'clipboard'),
467
+ ].filter(Boolean).length;
468
+ const sourceFromString = (value) => {
469
+ const trimmed = value.trim();
470
+ if (trimmed === '' || trimmed.startsWith('data:')) {
471
+ return undefined;
472
+ }
473
+ try {
474
+ const url = new URL(trimmed);
475
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
476
+ return { url: trimmed };
477
+ }
478
+ }
479
+ catch {
480
+ // Ignore non-URL strings.
481
+ }
482
+ return undefined;
483
+ };
484
+ const getFirstString = (record, keys) => {
485
+ for (const key of keys) {
486
+ const value = record[key];
487
+ if (typeof value === 'string' && value.trim() !== '') {
488
+ return value;
489
+ }
490
+ }
491
+ return undefined;
492
+ };
493
+ const stringFromJsonImageUrl = (value) => {
494
+ if (typeof value === 'string' && value.trim() !== '') {
495
+ return value;
496
+ }
497
+ if (isRecord(value)) {
498
+ return getFirstString(value, ['url']);
499
+ }
500
+ return undefined;
501
+ };
502
+ const descriptorFromDataUri = (value, mediaType, fileName, flagName) => {
503
+ const { blob, contentType } = blobFromDataUri(value, flagName);
504
+ return {
505
+ kind: 'blob',
506
+ blob,
507
+ fileName: ensureFileNameExtension(fileName, mediaType, contentType),
508
+ contentType,
509
+ };
510
+ };
511
+ const descriptorFromBase64 = ({ value, contentType, fileName, mediaType, flagName, }) => {
512
+ const decoded = blobFromBase64(value, contentType, flagName);
513
+ return {
514
+ kind: 'blob',
515
+ blob: decoded.blob,
516
+ fileName: ensureFileNameExtension(fileName, mediaType, decoded.contentType),
517
+ contentType: decoded.contentType,
518
+ };
519
+ };
520
+ const sourceDescriptorFromJson = (value, options) => {
521
+ if (typeof value === 'string') {
522
+ const trimmed = value.trim();
523
+ if (trimmed === '') {
524
+ return undefined;
525
+ }
526
+ if (trimmed.startsWith('data:')) {
527
+ return descriptorFromDataUri(trimmed, options.mediaType, options.fallbackFileName, '--source-json');
528
+ }
529
+ const source = sourceFromString(trimmed);
530
+ return source == null ? undefined : { kind: 'source', source };
531
+ }
532
+ if (Array.isArray(value)) {
533
+ for (const item of value) {
534
+ const found = sourceDescriptorFromJson(item, options);
535
+ if (found != null) {
536
+ return found;
537
+ }
538
+ }
539
+ return undefined;
540
+ }
541
+ if (!isRecord(value)) {
542
+ return undefined;
543
+ }
544
+ const nestedSource = value.source;
545
+ if (nestedSource != null) {
546
+ const found = sourceDescriptorFromJson(nestedSource, options);
547
+ if (found != null) {
548
+ return found;
549
+ }
550
+ }
551
+ const imageUrl = stringFromJsonImageUrl(value.image_url ?? value.imageUrl);
552
+ if (imageUrl != null) {
553
+ if (imageUrl.trim().startsWith('data:')) {
554
+ return descriptorFromDataUri(imageUrl, options.mediaType, options.fallbackFileName, '--source-json');
555
+ }
556
+ return { kind: 'source', source: { url: imageUrl } };
557
+ }
558
+ const resourceId = getFirstString(value, [
559
+ 'resourceId',
560
+ 'resource_id',
561
+ 'sourceResourceId',
562
+ 'source_resource_id',
563
+ 'sourceId',
564
+ 'source_id',
565
+ 'imageAssetId',
566
+ 'image_asset_id',
567
+ 'assetId',
568
+ 'asset_id',
569
+ ]);
570
+ if (resourceId != null) {
571
+ return { kind: 'source', source: { resourceId } };
572
+ }
573
+ const fileRefValue = value.fileRef ?? value.file_ref ?? value.imageBlobLocation ?? value.image_blob_location ?? value.blobLocation ?? value.blob_location;
574
+ if (isBlobLocation(fileRefValue)) {
575
+ return { kind: 'source', source: { fileRef: fileRefValue } };
576
+ }
577
+ const dataUri = getFirstString(value, ['dataUri', 'data_uri']);
578
+ if (dataUri != null) {
579
+ return descriptorFromDataUri(dataUri, options.mediaType, getFirstString(value, ['fileName', 'file_name', 'name']) ??
580
+ options.fallbackFileName, '--source-json');
581
+ }
582
+ const contentType = getFirstString(value, [
583
+ 'contentType',
584
+ 'content_type',
585
+ 'mimeType',
586
+ 'mime_type',
587
+ 'media_type',
588
+ ]);
589
+ const base64Value = getFirstString(value, ['base64', 'data', 'content']);
590
+ if (base64Value != null && (value.type === 'base64' || contentType != null)) {
591
+ return descriptorFromBase64({
592
+ value: base64Value,
593
+ contentType,
594
+ fileName: getFirstString(value, ['fileName', 'file_name', 'name']) ??
595
+ options.fallbackFileName,
596
+ mediaType: options.mediaType,
597
+ flagName: '--source-json',
598
+ });
599
+ }
600
+ const url = getFirstString(value, ['sourceUrl', 'source_url', 'url', 'publicUrl', 'public_url']);
601
+ if (url != null) {
602
+ const source = sourceFromString(url);
603
+ if (source != null) {
604
+ return { kind: 'source', source };
605
+ }
606
+ }
607
+ const path = getFirstString(value, ['path', 'file', 'filePath', 'file_path']);
608
+ if (path != null) {
609
+ return { kind: 'file', path };
610
+ }
611
+ const preferredKeys = [
612
+ 'input',
613
+ 'image',
614
+ 'asset',
615
+ 'file',
616
+ 'message',
617
+ 'messages',
618
+ 'content',
619
+ 'attachments',
620
+ ];
621
+ for (const key of preferredKeys) {
622
+ const found = sourceDescriptorFromJson(value[key], options);
623
+ if (found != null) {
624
+ return found;
625
+ }
626
+ }
627
+ for (const entry of Object.values(value)) {
628
+ const found = sourceDescriptorFromJson(entry, options);
629
+ if (found != null) {
630
+ return found;
631
+ }
632
+ }
633
+ return undefined;
634
+ };
635
+ const parseJsonValue = (raw, sourceName) => {
636
+ try {
637
+ return JSON.parse(raw);
638
+ }
639
+ catch (error) {
640
+ throw new Error(`${sourceName} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
641
+ }
642
+ };
643
+ const readSourceJsonFlag = async (flags) => {
644
+ const value = getFlag(flags, 'source-json');
645
+ if (value == null || value === 'true' || value.trim() === '') {
646
+ throw new Error('Missing required flag value: --source-json <json|@file|@->');
647
+ }
648
+ if (value === '@-' || value === '-') {
649
+ return await readStdin();
650
+ }
651
+ if (value.startsWith('@')) {
652
+ return await readFile(value.slice(1), 'utf8');
653
+ }
654
+ return value;
655
+ };
656
+ const readClipboardImage = async () => {
657
+ const filePath = join(tmpdir(), `assethub-clipboard-${process.pid}-${Date.now()}.png`);
658
+ const readTempFile = async () => await readBlob(filePath, 'image/png');
659
+ try {
660
+ await execFileAsync('pngpaste', [filePath], { timeout: 10000 });
661
+ return await readTempFile();
662
+ }
663
+ catch (pngpasteError) {
664
+ const quoteAppleScriptString = (value) => `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
665
+ const pngClass = `${String.fromCharCode(0x00ab)}class PNGf${String.fromCharCode(0x00bb)}`;
666
+ const script = [
667
+ `set outPath to ${quoteAppleScriptString(filePath)}`,
668
+ `set pngData to the clipboard as ${pngClass}`,
669
+ 'set fileRef to open for access (POSIX file outPath) with write permission',
670
+ 'try',
671
+ 'set eof fileRef to 0',
672
+ 'write pngData to fileRef',
673
+ 'close access fileRef',
674
+ 'on error errMsg',
675
+ 'try',
676
+ 'close access fileRef',
677
+ 'end try',
678
+ 'error errMsg',
679
+ 'end try',
680
+ ].join('\n');
681
+ try {
682
+ await execFileAsync('osascript', ['-e', script], { timeout: 10000 });
683
+ return await readTempFile();
684
+ }
685
+ catch (osascriptError) {
686
+ const details = osascriptError instanceof Error
687
+ ? osascriptError.message
688
+ : pngpasteError instanceof Error
689
+ ? pngpasteError.message
690
+ : 'unknown error';
691
+ throw new Error(`Could not read an image from the clipboard. Copy an image, install pngpaste, or pass bytes through --stdin/--stdin-base64/--stdin-json. Details: ${details}`);
692
+ }
693
+ }
694
+ finally {
695
+ await unlink(filePath).catch(() => undefined);
696
+ }
697
+ };
698
+ const uploadSourceDescriptor = async ({ descriptor, client, mediaType, }) => {
699
+ if (descriptor.kind === 'source') {
700
+ return descriptor.source;
701
+ }
702
+ const file = descriptor.kind === 'file'
703
+ ? await readBlob(descriptor.path, descriptor.contentType)
704
+ : descriptor.blob;
705
+ const fileName = descriptor.kind === 'file'
706
+ ? basename(descriptor.path)
707
+ : descriptor.fileName;
708
+ const upload = await client.v2.uploadFile({
709
+ file,
710
+ fileName: ensureFileNameExtension(fileName, mediaType, descriptor.contentType),
711
+ mediaType,
712
+ });
713
+ return { fileRef: upload.fileRef };
714
+ };
379
715
  const resolveSource = async ({ flags, client, fileMediaType, }) => {
716
+ const providedCount = sourceInputCount(flags);
717
+ if (providedCount === 0) {
718
+ return undefined;
719
+ }
720
+ if (providedCount > 1) {
721
+ throw new Error(exactlyOneSourceMessage);
722
+ }
380
723
  const sourceResourceId = getSourceResourceIdFlag(flags);
381
724
  if (sourceResourceId != null) {
382
- return { resourceId: sourceResourceId };
725
+ return {
726
+ resourceId: assertFlagHasValue(sourceResourceId, '--source-id/--source-resource-id <id>'),
727
+ };
383
728
  }
384
- const sourceUrl = getFlag(flags, 'source-url');
729
+ const sourceUrl = getSourceUrlFlag(flags);
385
730
  if (sourceUrl != null) {
386
- return { url: sourceUrl };
731
+ return {
732
+ url: assertFlagHasValue(sourceUrl, '--source-url/--image-url <url>'),
733
+ };
387
734
  }
388
735
  const fileRefJson = getFlag(flags, 'file-ref-json');
389
736
  if (fileRefJson != null) {
390
- return { fileRef: parseFileRef(fileRefJson) };
737
+ return {
738
+ fileRef: parseFileRef(assertFlagHasValue(fileRefJson, '--file-ref-json <json>')),
739
+ };
391
740
  }
392
741
  const filePath = getFlag(flags, 'file');
393
742
  if (filePath != null) {
394
- const fileRef = await client.v2.uploadFile({
395
- file: await readBlob(filePath),
396
- fileName: basename(filePath),
743
+ return await uploadSourceDescriptor({
744
+ descriptor: { kind: 'file', path: assertFlagHasValue(filePath, '--file <path>') },
745
+ client,
397
746
  mediaType: fileMediaType,
398
747
  });
399
- return { fileRef: fileRef.fileRef };
400
748
  }
401
749
  const dataUri = getFlag(flags, 'data-uri');
402
750
  if (dataUri != null) {
403
- const { blob, contentType } = blobFromDataUri(dataUri);
404
- const fileName = ensureFileNameExtension(getFlag(flags, 'file-name') ?? 'data-uri', fileMediaType, contentType);
405
- const fileRef = await client.v2.uploadFile({
406
- file: blob,
407
- fileName,
751
+ return await uploadSourceDescriptor({
752
+ descriptor: descriptorFromDataUri(assertFlagHasValue(dataUri, '--data-uri <uri>'), fileMediaType, getFlag(flags, 'file-name') ?? 'data-uri', '--data-uri'),
753
+ client,
754
+ mediaType: fileMediaType,
755
+ });
756
+ }
757
+ if (getFlag(flags, 'source-json') != null) {
758
+ const parsed = parseJsonValue(await readSourceJsonFlag(flags), '--source-json');
759
+ const descriptor = sourceDescriptorFromJson(parsed, {
760
+ mediaType: fileMediaType,
761
+ fallbackFileName: getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType),
762
+ });
763
+ if (descriptor == null) {
764
+ throw new Error('--source-json did not contain a usable source. Expected url, image_url.url, resourceId, fileRef, data URI, base64 data, or path.');
765
+ }
766
+ return await uploadSourceDescriptor({ descriptor, client, mediaType: fileMediaType });
767
+ }
768
+ if (hasFlag(flags, 'stdin-json')) {
769
+ const parsed = parseJsonValue(await readStdin(), '--stdin-json');
770
+ const descriptor = sourceDescriptorFromJson(parsed, {
771
+ mediaType: fileMediaType,
772
+ fallbackFileName: getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType),
773
+ });
774
+ if (descriptor == null) {
775
+ throw new Error('--stdin-json did not contain a usable source. Expected url, image_url.url, resourceId, fileRef, data URI, base64 data, or path.');
776
+ }
777
+ return await uploadSourceDescriptor({ descriptor, client, mediaType: fileMediaType });
778
+ }
779
+ if (hasFlag(flags, 'stdin-data-uri')) {
780
+ return await uploadSourceDescriptor({
781
+ descriptor: descriptorFromDataUri(await readStdin(), fileMediaType, getFlag(flags, 'file-name') ?? 'stdin-data-uri', '--stdin-data-uri'),
782
+ client,
783
+ mediaType: fileMediaType,
784
+ });
785
+ }
786
+ if (hasFlag(flags, 'stdin-base64')) {
787
+ return await uploadSourceDescriptor({
788
+ descriptor: descriptorFromBase64({
789
+ value: await readStdin(),
790
+ contentType: getContentTypeFlag(flags),
791
+ fileName: getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType),
792
+ mediaType: fileMediaType,
793
+ flagName: '--stdin-base64',
794
+ }),
795
+ client,
796
+ mediaType: fileMediaType,
797
+ });
798
+ }
799
+ if (hasFlag(flags, 'clipboard')) {
800
+ if (fileMediaType !== 'image') {
801
+ throw new Error('--clipboard is only supported for image inputs.');
802
+ }
803
+ return await uploadSourceDescriptor({
804
+ descriptor: {
805
+ kind: 'blob',
806
+ blob: await readClipboardImage(),
807
+ fileName: ensureFileNameExtension(getFlag(flags, 'file-name') ?? 'clipboard.png', fileMediaType, 'image/png'),
808
+ contentType: 'image/png',
809
+ },
810
+ client,
408
811
  mediaType: fileMediaType,
409
812
  });
410
- return { fileRef: fileRef.fileRef };
411
813
  }
412
814
  if (hasFlag(flags, 'stdin')) {
413
- const fileName = ensureFileNameExtension(getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType), fileMediaType);
414
- const fileRef = await client.v2.uploadFile({
415
- file: await readStdinBlob(),
416
- fileName,
815
+ return await uploadSourceDescriptor({
816
+ descriptor: {
817
+ kind: 'blob',
818
+ blob: await readStdinBlob(getContentTypeFlag(flags)),
819
+ fileName: ensureFileNameExtension(getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType), fileMediaType, getContentTypeFlag(flags)),
820
+ contentType: getContentTypeFlag(flags),
821
+ },
822
+ client,
417
823
  mediaType: fileMediaType,
418
824
  });
419
- return { fileRef: fileRef.fileRef };
420
825
  }
421
826
  return undefined;
422
827
  };
423
828
  const requireSource = async (options) => {
424
829
  const source = await resolveSource(options);
425
830
  if (source == null) {
426
- throw new Error('Missing source. Pass --file, --source-url, --source-resource-id, --source-id, --file-ref-json, --stdin, or --data-uri.');
831
+ throw new Error(missingSourceMessage);
427
832
  }
428
833
  return source;
429
834
  };
@@ -440,10 +845,6 @@ const sourceToV1ImageInput = (source) => {
440
845
  throw new Error('v1 image generation does not accept source-resource-id. Use --source-url, --file, or --api-version v2.');
441
846
  };
442
847
  const resolveProductionAnalyzeSource = async (ctx) => {
443
- const imageUrl = getFlag(ctx.flags, 'image-url');
444
- if (imageUrl != null) {
445
- return { imageUrl };
446
- }
447
848
  const source = await requireSource({
448
849
  flags: ctx.flags,
449
850
  client: ctx.client,
@@ -456,7 +857,7 @@ const resolveProductionAnalyzeSource = async (ctx) => {
456
857
  return { imageUrl: source.url };
457
858
  }
458
859
  if (source.fileRef == null) {
459
- throw new Error('Missing source. Pass --file, --source-url, --source-id, --file-ref-json, --stdin, or --data-uri.');
860
+ throw new Error(missingSourceMessage);
460
861
  }
461
862
  return { imageBlobLocation: source.fileRef };
462
863
  };
@@ -522,54 +923,20 @@ const commandSource = async (subcommand, ctx) => {
522
923
  throw new Error('Unknown source command. Use "source create".');
523
924
  }
524
925
  const mediaType = parseMediaType(requireFlag(ctx.flags, 'media-type'));
525
- const sourceUrl = getFlag(ctx.flags, 'source-url') ?? getFlag(ctx.flags, 'image-url');
526
- const sourceResourceId = getSourceResourceIdFlag(ctx.flags);
527
- const filePath = getFlag(ctx.flags, 'file');
528
- const useStdin = hasFlag(ctx.flags, 'stdin');
529
- const dataUri = getFlag(ctx.flags, 'data-uri');
530
- const provided = [
531
- sourceUrl != null,
532
- sourceResourceId != null,
533
- filePath != null,
534
- useStdin,
535
- dataUri != null,
536
- ].filter(Boolean);
537
- if (provided.length !== 1) {
538
- throw new Error('Pass exactly one source input: --file, --source-url, --source-id, --source-resource-id, --stdin, or --data-uri.');
539
- }
540
- if (sourceUrl != null) {
541
- print({ source: { url: sourceUrl }, url: sourceUrl, mediaType });
926
+ const source = await requireSource({
927
+ flags: ctx.flags,
928
+ client: ctx.client,
929
+ fileMediaType: mediaType,
930
+ });
931
+ if (source.url != null) {
932
+ print({ source, url: source.url, mediaType });
542
933
  return;
543
934
  }
544
- if (sourceResourceId != null) {
545
- print({
546
- source: { resourceId: sourceResourceId },
547
- resourceId: sourceResourceId,
548
- mediaType,
549
- });
935
+ if (source.resourceId != null) {
936
+ print({ source, resourceId: source.resourceId, mediaType });
550
937
  return;
551
938
  }
552
- const upload = filePath != null
553
- ? await ctx.client.v2.uploadFile({
554
- file: await readBlob(filePath),
555
- fileName: basename(filePath),
556
- mediaType,
557
- })
558
- : dataUri != null
559
- ? await (async () => {
560
- const { blob, contentType } = blobFromDataUri(dataUri);
561
- return await ctx.client.v2.uploadFile({
562
- file: blob,
563
- fileName: ensureFileNameExtension(getFlag(ctx.flags, 'file-name') ?? 'data-uri', mediaType, contentType),
564
- mediaType,
565
- });
566
- })()
567
- : await ctx.client.v2.uploadFile({
568
- file: await readStdinBlob(),
569
- fileName: ensureFileNameExtension(getFlag(ctx.flags, 'file-name') ?? defaultFileNameForMediaType(mediaType), mediaType),
570
- mediaType,
571
- });
572
- print({ source: { fileRef: upload.fileRef }, fileRef: upload.fileRef, mediaType });
939
+ print({ source, fileRef: source.fileRef, mediaType });
573
940
  };
574
941
  const commandModels = async (subcommand, positionals, ctx) => {
575
942
  if (subcommand === 'list') {
@@ -782,11 +1149,12 @@ const resolvePartExtractorFromMission = async (ctx, orderId, missionId) => {
782
1149
  throw new Error('Could not infer the part extractor from the mission. Pass --part-extractor with one of: ' +
783
1150
  partExtractorPublicNames);
784
1151
  }
785
- if (isInternalPartExtractorId(mission.agentVersion)) {
1152
+ const publicName = publicPartExtractorName(mission.agentVersion);
1153
+ if (publicName == null || publicName === 'Unknown') {
786
1154
  throw new Error('Could not infer the public part extractor from the mission. Pass --part-extractor with one of: ' +
787
1155
  partExtractorPublicNames);
788
1156
  }
789
- return resolvePartExtractorPublicName(mission.agentVersion);
1157
+ return resolvePartExtractorApiValue(publicName);
790
1158
  };
791
1159
  const redactUrl = (value) => {
792
1160
  try {
@@ -1095,7 +1463,7 @@ const commandProduction = async (subcommand, positionals, ctx) => {
1095
1463
  const partExtractorFlag = getPartExtractorFlag(ctx.flags);
1096
1464
  const agentVersion = partExtractorFlag == null
1097
1465
  ? await resolvePartExtractorFromMission(ctx, orderId, missionId)
1098
- : resolvePartExtractorPublicName(partExtractorFlag);
1466
+ : resolvePartExtractorApiValue(partExtractorFlag);
1099
1467
  const executed = await ctx.client.v1.executeProduction({
1100
1468
  orderId,
1101
1469
  missionId,
@@ -1118,6 +1486,9 @@ const commandProduction = async (subcommand, positionals, ctx) => {
1118
1486
  const commandParts = async (subcommand, ctx) => {
1119
1487
  const runPartSplit = async (source) => {
1120
1488
  let orderId = source == null ? getFlag(ctx.flags, 'order-id') : undefined;
1489
+ if (orderId != null) {
1490
+ orderId = assertFlagHasValue(orderId, '--order-id <id>');
1491
+ }
1121
1492
  let analyzed;
1122
1493
  let status;
1123
1494
  if (orderId == null) {
@@ -1163,7 +1534,7 @@ const commandParts = async (subcommand, ctx) => {
1163
1534
  const partExtractorFlag = getPartExtractorFlag(ctx.flags);
1164
1535
  const agentVersion = partExtractorFlag == null
1165
1536
  ? await resolvePartExtractorFromMission(ctx, orderId, missionId)
1166
- : resolvePartExtractorPublicName(partExtractorFlag);
1537
+ : resolvePartExtractorApiValue(partExtractorFlag);
1167
1538
  const partExtractionMode = getFlag(ctx.flags, 'part-extraction-mode');
1168
1539
  if (partExtractionMode != null &&
1169
1540
  partExtractionMode !== 'fast' &&
@@ -1193,12 +1564,15 @@ const commandParts = async (subcommand, ctx) => {
1193
1564
  });
1194
1565
  };
1195
1566
  if (subcommand === 'split') {
1567
+ if (getFlag(ctx.flags, 'order-id') != null && sourceInputCount(ctx.flags) > 0) {
1568
+ throw new Error('Pass either --order-id or one image source, not both.');
1569
+ }
1196
1570
  print(await withOptionalDownloads(ctx.flags, await runPartSplit()));
1197
1571
  return;
1198
1572
  }
1199
1573
  if (subcommand === 'compare') {
1200
1574
  if (getFlag(ctx.flags, 'order-id') != null) {
1201
- throw new Error('parts compare requires an image source. Use --file, --source-url, --source-id, --image-url, --file-ref-json, --stdin, or --data-uri.');
1575
+ throw new Error('parts compare requires an image source. Use --file, --source-url, --source-id, --image-url, --file-ref-json, --stdin, --stdin-base64, --stdin-data-uri, --stdin-json, --source-json, --data-uri, or --clipboard.');
1202
1576
  }
1203
1577
  const preprocessPrompt = getFlag(ctx.flags, 'preprocess-prompt');
1204
1578
  if (preprocessPrompt === 'true') {
@@ -1210,17 +1584,15 @@ const commandParts = async (subcommand, ctx) => {
1210
1584
  if (preprocessPrompt != null && getFlag(ctx.flags, 'mission-id') != null) {
1211
1585
  throw new Error('parts compare preprocessing creates a second order. Omit --mission-id and let the CLI infer it.');
1212
1586
  }
1213
- const imageUrlFlag = getFlag(ctx.flags, 'image-url');
1214
- if (imageUrlFlag === 'true') {
1215
- throw new Error('Missing required flag value: --image-url <url>');
1587
+ const preprocessModelId = getFlag(ctx.flags, 'preprocess-model-id');
1588
+ if (preprocessModelId === 'true') {
1589
+ throw new Error('Missing required flag value: --preprocess-model-id <id>');
1216
1590
  }
1217
- const inputSource = imageUrlFlag == null
1218
- ? productionAnalyzeSourceFromSource(await requireSource({
1219
- flags: ctx.flags,
1220
- client: ctx.client,
1221
- fileMediaType: 'image',
1222
- }))
1223
- : { imageUrl: imageUrlFlag };
1591
+ const inputSource = productionAnalyzeSourceFromSource(await requireSource({
1592
+ flags: ctx.flags,
1593
+ client: ctx.client,
1594
+ fileMediaType: 'image',
1595
+ }));
1224
1596
  const direct = await runPartSplit(inputSource);
1225
1597
  let preprocessed = null;
1226
1598
  if (preprocessPrompt != null && preprocessPrompt !== 'true') {
@@ -1228,7 +1600,7 @@ const commandParts = async (subcommand, ctx) => {
1228
1600
  const queued = await ctx.client.v2.generateImage({
1229
1601
  source: sourceFromProductionAnalyzeSource(inputSource),
1230
1602
  prompt: preprocessPrompt,
1231
- modelId: getFlag(ctx.flags, 'preprocess-model-id') ?? defaultImageModelId,
1603
+ modelId: preprocessModelId ?? defaultImageModelId,
1232
1604
  batchSize: 1,
1233
1605
  negativePrompt: getFlag(ctx.flags, 'preprocess-negative-prompt'),
1234
1606
  seed: getFlag(ctx.flags, 'preprocess-seed') == null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assethub/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Command line interface for AssetHub.",
5
5
  "type": "module",
6
6
  "bin": {