@assethub/cli 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -5
- package/dist/index.js +455 -102
- 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 --
|
|
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 --
|
|
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
|
|
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 {
|
|
3
|
-
import {
|
|
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';
|
|
@@ -47,6 +49,7 @@ const partExtractorPublicNameByInput = new Map(partExtractorOptions.flatMap(opti
|
|
|
47
49
|
]),
|
|
48
50
|
]));
|
|
49
51
|
const isInternalPartExtractorId = (value) => value.trim().includes('_');
|
|
52
|
+
const execFileAsync = promisify(execFile);
|
|
50
53
|
const usage = `AssetHub CLI
|
|
51
54
|
|
|
52
55
|
Usage:
|
|
@@ -58,18 +61,18 @@ Usage:
|
|
|
58
61
|
assethub models get <model-id>
|
|
59
62
|
assethub files import <url> --media-type image|mesh
|
|
60
63
|
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]
|
|
64
|
+
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>]
|
|
65
|
+
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
66
|
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>]
|
|
67
|
+
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>]
|
|
68
|
+
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
69
|
assethub jobs get <job-id> [--download --out-dir <dir>]
|
|
67
70
|
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>]
|
|
71
|
+
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
72
|
assethub production status <order-id> [--wait] [--download --out-dir <dir>]
|
|
70
73
|
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>]
|
|
74
|
+
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>]
|
|
75
|
+
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
76
|
|
|
74
77
|
Global options:
|
|
75
78
|
--api-key <key> Overrides saved auth and ASSETHUB_API_KEY.
|
|
@@ -81,12 +84,16 @@ Global options:
|
|
|
81
84
|
Examples:
|
|
82
85
|
ASSETHUB_API_KEY=ah_live_xxx assethub models list
|
|
83
86
|
assethub auth login --api-key-stdin
|
|
84
|
-
assethub models list --
|
|
87
|
+
assethub models list --ids-only
|
|
85
88
|
assethub files upload ./input.png --media-type image
|
|
86
89
|
assethub source create --file ./input.png --media-type image
|
|
90
|
+
pbpaste | assethub source create --stdin-data-uri --media-type image
|
|
91
|
+
assethub source create --clipboard --media-type image --file-name clipboard.png
|
|
92
|
+
printf '{"image_url":{"url":"data:image/png;base64,..."}}' | assethub parts split --stdin-json --part-extractor "V1.5" --wait
|
|
93
|
+
base64 -i ./input.png | assethub parts split --stdin-base64 --file-name input.png --content-type image/png --part-extractor "V1.5" --wait
|
|
87
94
|
cat ./input.png | assethub parts split --stdin --file-name input.png --part-extractor "V1.5" --wait
|
|
88
95
|
assethub image generate --prompt "stylized prop concept" --wait
|
|
89
|
-
assethub mesh generate --file ./input.png --
|
|
96
|
+
assethub mesh generate --file ./input.png --wait
|
|
90
97
|
assethub production analyze --file ./input.png --part-extractor "V1.5" --wait
|
|
91
98
|
assethub parts split --file ./input.png --part-extractor "V1.5" --wait --all-ready
|
|
92
99
|
assethub parts compare --file ./input.png --preprocess-prompt "clean white background, centered product photo" --part-extractor "V1.5" --wait --all-ready
|
|
@@ -327,31 +334,73 @@ const createContext = async (flags) => {
|
|
|
327
334
|
}),
|
|
328
335
|
};
|
|
329
336
|
};
|
|
337
|
+
const isRecord = (value) => value != null && typeof value === 'object' && !Array.isArray(value);
|
|
338
|
+
const isBlobLocation = (value) => {
|
|
339
|
+
if (!isRecord(value) || typeof value.type !== 'string') {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
if (value.type === 'supabase' &&
|
|
343
|
+
typeof value.bucket === 'string' &&
|
|
344
|
+
typeof value.path === 'string') {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
if (value.type === 'signedSupabase' &&
|
|
348
|
+
typeof value.signedUrl === 'string') {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
if (value.type === 'asset' && typeof value.assetId === 'string') {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
};
|
|
330
356
|
const parseFileRef = (value) => {
|
|
331
357
|
const parsed = JSON.parse(value);
|
|
332
|
-
if (
|
|
358
|
+
if (!isBlobLocation(parsed)) {
|
|
333
359
|
throw new Error('--file-ref-json must be a BlobLocation JSON object');
|
|
334
360
|
}
|
|
335
361
|
return parsed;
|
|
336
362
|
};
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
return new Blob([
|
|
363
|
+
const blobFromBuffer = (buffer, type) => {
|
|
364
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
365
|
+
return new Blob([arrayBuffer], type == null ? undefined : { type });
|
|
340
366
|
};
|
|
341
|
-
const
|
|
342
|
-
|
|
367
|
+
const readBlob = async (filePath, type) => blobFromBuffer(await readFile(filePath), type);
|
|
368
|
+
const getContentTypeFlag = (flags) => getFlag(flags, 'content-type') ?? getFlag(flags, 'mime-type');
|
|
369
|
+
const blobFromDataUri = (value, flagName = '--data-uri') => {
|
|
370
|
+
const trimmed = value.trim();
|
|
371
|
+
const match = trimmed.match(/^data:([^;,]+)?(;base64)?,(.*)$/s);
|
|
343
372
|
if (match == null) {
|
|
344
|
-
throw new Error(
|
|
373
|
+
throw new Error(`${flagName} must be a valid data URI.`);
|
|
345
374
|
}
|
|
346
375
|
const contentType = match[1] === '' ? undefined : match[1];
|
|
347
376
|
const isBase64 = match[2] != null;
|
|
348
|
-
const payload = match[3] ?? '';
|
|
377
|
+
const payload = (match[3] ?? '').replace(/\s+/g, '');
|
|
349
378
|
const buffer = isBase64
|
|
350
379
|
? Buffer.from(payload, 'base64')
|
|
351
380
|
: Buffer.from(decodeURIComponent(payload), 'utf8');
|
|
352
|
-
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
353
381
|
return {
|
|
354
|
-
blob:
|
|
382
|
+
blob: blobFromBuffer(buffer, contentType),
|
|
383
|
+
contentType,
|
|
384
|
+
};
|
|
385
|
+
};
|
|
386
|
+
const blobFromBase64 = (value, contentType, flagName) => {
|
|
387
|
+
const trimmed = value.trim();
|
|
388
|
+
if (trimmed.startsWith('data:')) {
|
|
389
|
+
return blobFromDataUri(trimmed, flagName);
|
|
390
|
+
}
|
|
391
|
+
const normalized = trimmed.replace(/\s+/g, '').replace(/-/g, '+').replace(/_/g, '/');
|
|
392
|
+
if (normalized === '') {
|
|
393
|
+
throw new Error(`${flagName} input is empty.`);
|
|
394
|
+
}
|
|
395
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
|
|
396
|
+
throw new Error(`${flagName} must contain base64 data or a data URI.`);
|
|
397
|
+
}
|
|
398
|
+
const buffer = Buffer.from(normalized, 'base64');
|
|
399
|
+
if (buffer.byteLength === 0) {
|
|
400
|
+
throw new Error(`${flagName} did not decode to any bytes.`);
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
blob: blobFromBuffer(buffer, contentType),
|
|
355
404
|
contentType,
|
|
356
405
|
};
|
|
357
406
|
};
|
|
@@ -376,54 +425,392 @@ const ensureFileNameExtension = (fileName, mediaType, mimeType) => {
|
|
|
376
425
|
}
|
|
377
426
|
return `${fileName}.${extensionFromMimeType(mimeType, mediaType)}`;
|
|
378
427
|
};
|
|
428
|
+
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.';
|
|
429
|
+
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.';
|
|
430
|
+
const getSourceUrlFlag = (flags) => getFlag(flags, 'source-url') ?? getFlag(flags, 'image-url') ?? getFlag(flags, 'url');
|
|
431
|
+
const assertFlagHasValue = (value, flagDescription) => {
|
|
432
|
+
if (value === 'true' || value.trim() === '') {
|
|
433
|
+
throw new Error(`Missing required flag value: ${flagDescription}`);
|
|
434
|
+
}
|
|
435
|
+
return value;
|
|
436
|
+
};
|
|
437
|
+
const sourceInputCount = (flags) => [
|
|
438
|
+
getSourceResourceIdFlag(flags) != null,
|
|
439
|
+
getSourceUrlFlag(flags) != null,
|
|
440
|
+
getFlag(flags, 'file-ref-json') != null,
|
|
441
|
+
getFlag(flags, 'file') != null,
|
|
442
|
+
getFlag(flags, 'data-uri') != null,
|
|
443
|
+
hasFlag(flags, 'stdin'),
|
|
444
|
+
hasFlag(flags, 'stdin-base64'),
|
|
445
|
+
hasFlag(flags, 'stdin-data-uri'),
|
|
446
|
+
hasFlag(flags, 'stdin-json'),
|
|
447
|
+
getFlag(flags, 'source-json') != null,
|
|
448
|
+
hasFlag(flags, 'clipboard'),
|
|
449
|
+
].filter(Boolean).length;
|
|
450
|
+
const sourceFromString = (value) => {
|
|
451
|
+
const trimmed = value.trim();
|
|
452
|
+
if (trimmed === '' || trimmed.startsWith('data:')) {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
const url = new URL(trimmed);
|
|
457
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
458
|
+
return { url: trimmed };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// Ignore non-URL strings.
|
|
463
|
+
}
|
|
464
|
+
return undefined;
|
|
465
|
+
};
|
|
466
|
+
const getFirstString = (record, keys) => {
|
|
467
|
+
for (const key of keys) {
|
|
468
|
+
const value = record[key];
|
|
469
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
470
|
+
return value;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return undefined;
|
|
474
|
+
};
|
|
475
|
+
const stringFromJsonImageUrl = (value) => {
|
|
476
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
if (isRecord(value)) {
|
|
480
|
+
return getFirstString(value, ['url']);
|
|
481
|
+
}
|
|
482
|
+
return undefined;
|
|
483
|
+
};
|
|
484
|
+
const descriptorFromDataUri = (value, mediaType, fileName, flagName) => {
|
|
485
|
+
const { blob, contentType } = blobFromDataUri(value, flagName);
|
|
486
|
+
return {
|
|
487
|
+
kind: 'blob',
|
|
488
|
+
blob,
|
|
489
|
+
fileName: ensureFileNameExtension(fileName, mediaType, contentType),
|
|
490
|
+
contentType,
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
const descriptorFromBase64 = ({ value, contentType, fileName, mediaType, flagName, }) => {
|
|
494
|
+
const decoded = blobFromBase64(value, contentType, flagName);
|
|
495
|
+
return {
|
|
496
|
+
kind: 'blob',
|
|
497
|
+
blob: decoded.blob,
|
|
498
|
+
fileName: ensureFileNameExtension(fileName, mediaType, decoded.contentType),
|
|
499
|
+
contentType: decoded.contentType,
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
const sourceDescriptorFromJson = (value, options) => {
|
|
503
|
+
if (typeof value === 'string') {
|
|
504
|
+
const trimmed = value.trim();
|
|
505
|
+
if (trimmed === '') {
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
if (trimmed.startsWith('data:')) {
|
|
509
|
+
return descriptorFromDataUri(trimmed, options.mediaType, options.fallbackFileName, '--source-json');
|
|
510
|
+
}
|
|
511
|
+
const source = sourceFromString(trimmed);
|
|
512
|
+
return source == null ? undefined : { kind: 'source', source };
|
|
513
|
+
}
|
|
514
|
+
if (Array.isArray(value)) {
|
|
515
|
+
for (const item of value) {
|
|
516
|
+
const found = sourceDescriptorFromJson(item, options);
|
|
517
|
+
if (found != null) {
|
|
518
|
+
return found;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
if (!isRecord(value)) {
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
const nestedSource = value.source;
|
|
527
|
+
if (nestedSource != null) {
|
|
528
|
+
const found = sourceDescriptorFromJson(nestedSource, options);
|
|
529
|
+
if (found != null) {
|
|
530
|
+
return found;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
const imageUrl = stringFromJsonImageUrl(value.image_url ?? value.imageUrl);
|
|
534
|
+
if (imageUrl != null) {
|
|
535
|
+
if (imageUrl.trim().startsWith('data:')) {
|
|
536
|
+
return descriptorFromDataUri(imageUrl, options.mediaType, options.fallbackFileName, '--source-json');
|
|
537
|
+
}
|
|
538
|
+
return { kind: 'source', source: { url: imageUrl } };
|
|
539
|
+
}
|
|
540
|
+
const resourceId = getFirstString(value, [
|
|
541
|
+
'resourceId',
|
|
542
|
+
'resource_id',
|
|
543
|
+
'sourceResourceId',
|
|
544
|
+
'source_resource_id',
|
|
545
|
+
'sourceId',
|
|
546
|
+
'source_id',
|
|
547
|
+
'imageAssetId',
|
|
548
|
+
'image_asset_id',
|
|
549
|
+
'assetId',
|
|
550
|
+
'asset_id',
|
|
551
|
+
]);
|
|
552
|
+
if (resourceId != null) {
|
|
553
|
+
return { kind: 'source', source: { resourceId } };
|
|
554
|
+
}
|
|
555
|
+
const fileRefValue = value.fileRef ?? value.file_ref ?? value.imageBlobLocation ?? value.image_blob_location ?? value.blobLocation ?? value.blob_location;
|
|
556
|
+
if (isBlobLocation(fileRefValue)) {
|
|
557
|
+
return { kind: 'source', source: { fileRef: fileRefValue } };
|
|
558
|
+
}
|
|
559
|
+
const dataUri = getFirstString(value, ['dataUri', 'data_uri']);
|
|
560
|
+
if (dataUri != null) {
|
|
561
|
+
return descriptorFromDataUri(dataUri, options.mediaType, getFirstString(value, ['fileName', 'file_name', 'name']) ??
|
|
562
|
+
options.fallbackFileName, '--source-json');
|
|
563
|
+
}
|
|
564
|
+
const contentType = getFirstString(value, [
|
|
565
|
+
'contentType',
|
|
566
|
+
'content_type',
|
|
567
|
+
'mimeType',
|
|
568
|
+
'mime_type',
|
|
569
|
+
'media_type',
|
|
570
|
+
]);
|
|
571
|
+
const base64Value = getFirstString(value, ['base64', 'data', 'content']);
|
|
572
|
+
if (base64Value != null && (value.type === 'base64' || contentType != null)) {
|
|
573
|
+
return descriptorFromBase64({
|
|
574
|
+
value: base64Value,
|
|
575
|
+
contentType,
|
|
576
|
+
fileName: getFirstString(value, ['fileName', 'file_name', 'name']) ??
|
|
577
|
+
options.fallbackFileName,
|
|
578
|
+
mediaType: options.mediaType,
|
|
579
|
+
flagName: '--source-json',
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
const url = getFirstString(value, ['sourceUrl', 'source_url', 'url', 'publicUrl', 'public_url']);
|
|
583
|
+
if (url != null) {
|
|
584
|
+
const source = sourceFromString(url);
|
|
585
|
+
if (source != null) {
|
|
586
|
+
return { kind: 'source', source };
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const path = getFirstString(value, ['path', 'file', 'filePath', 'file_path']);
|
|
590
|
+
if (path != null) {
|
|
591
|
+
return { kind: 'file', path };
|
|
592
|
+
}
|
|
593
|
+
const preferredKeys = [
|
|
594
|
+
'input',
|
|
595
|
+
'image',
|
|
596
|
+
'asset',
|
|
597
|
+
'file',
|
|
598
|
+
'message',
|
|
599
|
+
'messages',
|
|
600
|
+
'content',
|
|
601
|
+
'attachments',
|
|
602
|
+
];
|
|
603
|
+
for (const key of preferredKeys) {
|
|
604
|
+
const found = sourceDescriptorFromJson(value[key], options);
|
|
605
|
+
if (found != null) {
|
|
606
|
+
return found;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
for (const entry of Object.values(value)) {
|
|
610
|
+
const found = sourceDescriptorFromJson(entry, options);
|
|
611
|
+
if (found != null) {
|
|
612
|
+
return found;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return undefined;
|
|
616
|
+
};
|
|
617
|
+
const parseJsonValue = (raw, sourceName) => {
|
|
618
|
+
try {
|
|
619
|
+
return JSON.parse(raw);
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
throw new Error(`${sourceName} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
const readSourceJsonFlag = async (flags) => {
|
|
626
|
+
const value = getFlag(flags, 'source-json');
|
|
627
|
+
if (value == null || value === 'true' || value.trim() === '') {
|
|
628
|
+
throw new Error('Missing required flag value: --source-json <json|@file|@->');
|
|
629
|
+
}
|
|
630
|
+
if (value === '@-' || value === '-') {
|
|
631
|
+
return await readStdin();
|
|
632
|
+
}
|
|
633
|
+
if (value.startsWith('@')) {
|
|
634
|
+
return await readFile(value.slice(1), 'utf8');
|
|
635
|
+
}
|
|
636
|
+
return value;
|
|
637
|
+
};
|
|
638
|
+
const readClipboardImage = async () => {
|
|
639
|
+
const filePath = join(tmpdir(), `assethub-clipboard-${process.pid}-${Date.now()}.png`);
|
|
640
|
+
const readTempFile = async () => await readBlob(filePath, 'image/png');
|
|
641
|
+
try {
|
|
642
|
+
await execFileAsync('pngpaste', [filePath], { timeout: 10000 });
|
|
643
|
+
return await readTempFile();
|
|
644
|
+
}
|
|
645
|
+
catch (pngpasteError) {
|
|
646
|
+
const quoteAppleScriptString = (value) => `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
647
|
+
const pngClass = `${String.fromCharCode(0x00ab)}class PNGf${String.fromCharCode(0x00bb)}`;
|
|
648
|
+
const script = [
|
|
649
|
+
`set outPath to ${quoteAppleScriptString(filePath)}`,
|
|
650
|
+
`set pngData to the clipboard as ${pngClass}`,
|
|
651
|
+
'set fileRef to open for access (POSIX file outPath) with write permission',
|
|
652
|
+
'try',
|
|
653
|
+
'set eof fileRef to 0',
|
|
654
|
+
'write pngData to fileRef',
|
|
655
|
+
'close access fileRef',
|
|
656
|
+
'on error errMsg',
|
|
657
|
+
'try',
|
|
658
|
+
'close access fileRef',
|
|
659
|
+
'end try',
|
|
660
|
+
'error errMsg',
|
|
661
|
+
'end try',
|
|
662
|
+
].join('\n');
|
|
663
|
+
try {
|
|
664
|
+
await execFileAsync('osascript', ['-e', script], { timeout: 10000 });
|
|
665
|
+
return await readTempFile();
|
|
666
|
+
}
|
|
667
|
+
catch (osascriptError) {
|
|
668
|
+
const details = osascriptError instanceof Error
|
|
669
|
+
? osascriptError.message
|
|
670
|
+
: pngpasteError instanceof Error
|
|
671
|
+
? pngpasteError.message
|
|
672
|
+
: 'unknown error';
|
|
673
|
+
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}`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
finally {
|
|
677
|
+
await unlink(filePath).catch(() => undefined);
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const uploadSourceDescriptor = async ({ descriptor, client, mediaType, }) => {
|
|
681
|
+
if (descriptor.kind === 'source') {
|
|
682
|
+
return descriptor.source;
|
|
683
|
+
}
|
|
684
|
+
const file = descriptor.kind === 'file'
|
|
685
|
+
? await readBlob(descriptor.path, descriptor.contentType)
|
|
686
|
+
: descriptor.blob;
|
|
687
|
+
const fileName = descriptor.kind === 'file'
|
|
688
|
+
? basename(descriptor.path)
|
|
689
|
+
: descriptor.fileName;
|
|
690
|
+
const upload = await client.v2.uploadFile({
|
|
691
|
+
file,
|
|
692
|
+
fileName: ensureFileNameExtension(fileName, mediaType, descriptor.contentType),
|
|
693
|
+
mediaType,
|
|
694
|
+
});
|
|
695
|
+
return { fileRef: upload.fileRef };
|
|
696
|
+
};
|
|
379
697
|
const resolveSource = async ({ flags, client, fileMediaType, }) => {
|
|
698
|
+
const providedCount = sourceInputCount(flags);
|
|
699
|
+
if (providedCount === 0) {
|
|
700
|
+
return undefined;
|
|
701
|
+
}
|
|
702
|
+
if (providedCount > 1) {
|
|
703
|
+
throw new Error(exactlyOneSourceMessage);
|
|
704
|
+
}
|
|
380
705
|
const sourceResourceId = getSourceResourceIdFlag(flags);
|
|
381
706
|
if (sourceResourceId != null) {
|
|
382
|
-
return {
|
|
707
|
+
return {
|
|
708
|
+
resourceId: assertFlagHasValue(sourceResourceId, '--source-id/--source-resource-id <id>'),
|
|
709
|
+
};
|
|
383
710
|
}
|
|
384
|
-
const sourceUrl =
|
|
711
|
+
const sourceUrl = getSourceUrlFlag(flags);
|
|
385
712
|
if (sourceUrl != null) {
|
|
386
|
-
return {
|
|
713
|
+
return {
|
|
714
|
+
url: assertFlagHasValue(sourceUrl, '--source-url/--image-url <url>'),
|
|
715
|
+
};
|
|
387
716
|
}
|
|
388
717
|
const fileRefJson = getFlag(flags, 'file-ref-json');
|
|
389
718
|
if (fileRefJson != null) {
|
|
390
|
-
return {
|
|
719
|
+
return {
|
|
720
|
+
fileRef: parseFileRef(assertFlagHasValue(fileRefJson, '--file-ref-json <json>')),
|
|
721
|
+
};
|
|
391
722
|
}
|
|
392
723
|
const filePath = getFlag(flags, 'file');
|
|
393
724
|
if (filePath != null) {
|
|
394
|
-
|
|
395
|
-
file:
|
|
396
|
-
|
|
725
|
+
return await uploadSourceDescriptor({
|
|
726
|
+
descriptor: { kind: 'file', path: assertFlagHasValue(filePath, '--file <path>') },
|
|
727
|
+
client,
|
|
397
728
|
mediaType: fileMediaType,
|
|
398
729
|
});
|
|
399
|
-
return { fileRef: fileRef.fileRef };
|
|
400
730
|
}
|
|
401
731
|
const dataUri = getFlag(flags, 'data-uri');
|
|
402
732
|
if (dataUri != null) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
733
|
+
return await uploadSourceDescriptor({
|
|
734
|
+
descriptor: descriptorFromDataUri(assertFlagHasValue(dataUri, '--data-uri <uri>'), fileMediaType, getFlag(flags, 'file-name') ?? 'data-uri', '--data-uri'),
|
|
735
|
+
client,
|
|
736
|
+
mediaType: fileMediaType,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
if (getFlag(flags, 'source-json') != null) {
|
|
740
|
+
const parsed = parseJsonValue(await readSourceJsonFlag(flags), '--source-json');
|
|
741
|
+
const descriptor = sourceDescriptorFromJson(parsed, {
|
|
742
|
+
mediaType: fileMediaType,
|
|
743
|
+
fallbackFileName: getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType),
|
|
744
|
+
});
|
|
745
|
+
if (descriptor == null) {
|
|
746
|
+
throw new Error('--source-json did not contain a usable source. Expected url, image_url.url, resourceId, fileRef, data URI, base64 data, or path.');
|
|
747
|
+
}
|
|
748
|
+
return await uploadSourceDescriptor({ descriptor, client, mediaType: fileMediaType });
|
|
749
|
+
}
|
|
750
|
+
if (hasFlag(flags, 'stdin-json')) {
|
|
751
|
+
const parsed = parseJsonValue(await readStdin(), '--stdin-json');
|
|
752
|
+
const descriptor = sourceDescriptorFromJson(parsed, {
|
|
753
|
+
mediaType: fileMediaType,
|
|
754
|
+
fallbackFileName: getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType),
|
|
755
|
+
});
|
|
756
|
+
if (descriptor == null) {
|
|
757
|
+
throw new Error('--stdin-json did not contain a usable source. Expected url, image_url.url, resourceId, fileRef, data URI, base64 data, or path.');
|
|
758
|
+
}
|
|
759
|
+
return await uploadSourceDescriptor({ descriptor, client, mediaType: fileMediaType });
|
|
760
|
+
}
|
|
761
|
+
if (hasFlag(flags, 'stdin-data-uri')) {
|
|
762
|
+
return await uploadSourceDescriptor({
|
|
763
|
+
descriptor: descriptorFromDataUri(await readStdin(), fileMediaType, getFlag(flags, 'file-name') ?? 'stdin-data-uri', '--stdin-data-uri'),
|
|
764
|
+
client,
|
|
765
|
+
mediaType: fileMediaType,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
if (hasFlag(flags, 'stdin-base64')) {
|
|
769
|
+
return await uploadSourceDescriptor({
|
|
770
|
+
descriptor: descriptorFromBase64({
|
|
771
|
+
value: await readStdin(),
|
|
772
|
+
contentType: getContentTypeFlag(flags),
|
|
773
|
+
fileName: getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType),
|
|
774
|
+
mediaType: fileMediaType,
|
|
775
|
+
flagName: '--stdin-base64',
|
|
776
|
+
}),
|
|
777
|
+
client,
|
|
778
|
+
mediaType: fileMediaType,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
if (hasFlag(flags, 'clipboard')) {
|
|
782
|
+
if (fileMediaType !== 'image') {
|
|
783
|
+
throw new Error('--clipboard is only supported for image inputs.');
|
|
784
|
+
}
|
|
785
|
+
return await uploadSourceDescriptor({
|
|
786
|
+
descriptor: {
|
|
787
|
+
kind: 'blob',
|
|
788
|
+
blob: await readClipboardImage(),
|
|
789
|
+
fileName: ensureFileNameExtension(getFlag(flags, 'file-name') ?? 'clipboard.png', fileMediaType, 'image/png'),
|
|
790
|
+
contentType: 'image/png',
|
|
791
|
+
},
|
|
792
|
+
client,
|
|
408
793
|
mediaType: fileMediaType,
|
|
409
794
|
});
|
|
410
|
-
return { fileRef: fileRef.fileRef };
|
|
411
795
|
}
|
|
412
796
|
if (hasFlag(flags, 'stdin')) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
797
|
+
return await uploadSourceDescriptor({
|
|
798
|
+
descriptor: {
|
|
799
|
+
kind: 'blob',
|
|
800
|
+
blob: await readStdinBlob(getContentTypeFlag(flags)),
|
|
801
|
+
fileName: ensureFileNameExtension(getFlag(flags, 'file-name') ?? defaultFileNameForMediaType(fileMediaType), fileMediaType, getContentTypeFlag(flags)),
|
|
802
|
+
contentType: getContentTypeFlag(flags),
|
|
803
|
+
},
|
|
804
|
+
client,
|
|
417
805
|
mediaType: fileMediaType,
|
|
418
806
|
});
|
|
419
|
-
return { fileRef: fileRef.fileRef };
|
|
420
807
|
}
|
|
421
808
|
return undefined;
|
|
422
809
|
};
|
|
423
810
|
const requireSource = async (options) => {
|
|
424
811
|
const source = await resolveSource(options);
|
|
425
812
|
if (source == null) {
|
|
426
|
-
throw new Error(
|
|
813
|
+
throw new Error(missingSourceMessage);
|
|
427
814
|
}
|
|
428
815
|
return source;
|
|
429
816
|
};
|
|
@@ -440,10 +827,6 @@ const sourceToV1ImageInput = (source) => {
|
|
|
440
827
|
throw new Error('v1 image generation does not accept source-resource-id. Use --source-url, --file, or --api-version v2.');
|
|
441
828
|
};
|
|
442
829
|
const resolveProductionAnalyzeSource = async (ctx) => {
|
|
443
|
-
const imageUrl = getFlag(ctx.flags, 'image-url');
|
|
444
|
-
if (imageUrl != null) {
|
|
445
|
-
return { imageUrl };
|
|
446
|
-
}
|
|
447
830
|
const source = await requireSource({
|
|
448
831
|
flags: ctx.flags,
|
|
449
832
|
client: ctx.client,
|
|
@@ -456,7 +839,7 @@ const resolveProductionAnalyzeSource = async (ctx) => {
|
|
|
456
839
|
return { imageUrl: source.url };
|
|
457
840
|
}
|
|
458
841
|
if (source.fileRef == null) {
|
|
459
|
-
throw new Error(
|
|
842
|
+
throw new Error(missingSourceMessage);
|
|
460
843
|
}
|
|
461
844
|
return { imageBlobLocation: source.fileRef };
|
|
462
845
|
};
|
|
@@ -522,54 +905,20 @@ const commandSource = async (subcommand, ctx) => {
|
|
|
522
905
|
throw new Error('Unknown source command. Use "source create".');
|
|
523
906
|
}
|
|
524
907
|
const mediaType = parseMediaType(requireFlag(ctx.flags, 'media-type'));
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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 });
|
|
908
|
+
const source = await requireSource({
|
|
909
|
+
flags: ctx.flags,
|
|
910
|
+
client: ctx.client,
|
|
911
|
+
fileMediaType: mediaType,
|
|
912
|
+
});
|
|
913
|
+
if (source.url != null) {
|
|
914
|
+
print({ source, url: source.url, mediaType });
|
|
542
915
|
return;
|
|
543
916
|
}
|
|
544
|
-
if (
|
|
545
|
-
print({
|
|
546
|
-
source: { resourceId: sourceResourceId },
|
|
547
|
-
resourceId: sourceResourceId,
|
|
548
|
-
mediaType,
|
|
549
|
-
});
|
|
917
|
+
if (source.resourceId != null) {
|
|
918
|
+
print({ source, resourceId: source.resourceId, mediaType });
|
|
550
919
|
return;
|
|
551
920
|
}
|
|
552
|
-
|
|
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 });
|
|
921
|
+
print({ source, fileRef: source.fileRef, mediaType });
|
|
573
922
|
};
|
|
574
923
|
const commandModels = async (subcommand, positionals, ctx) => {
|
|
575
924
|
if (subcommand === 'list') {
|
|
@@ -1118,6 +1467,9 @@ const commandProduction = async (subcommand, positionals, ctx) => {
|
|
|
1118
1467
|
const commandParts = async (subcommand, ctx) => {
|
|
1119
1468
|
const runPartSplit = async (source) => {
|
|
1120
1469
|
let orderId = source == null ? getFlag(ctx.flags, 'order-id') : undefined;
|
|
1470
|
+
if (orderId != null) {
|
|
1471
|
+
orderId = assertFlagHasValue(orderId, '--order-id <id>');
|
|
1472
|
+
}
|
|
1121
1473
|
let analyzed;
|
|
1122
1474
|
let status;
|
|
1123
1475
|
if (orderId == null) {
|
|
@@ -1193,12 +1545,15 @@ const commandParts = async (subcommand, ctx) => {
|
|
|
1193
1545
|
});
|
|
1194
1546
|
};
|
|
1195
1547
|
if (subcommand === 'split') {
|
|
1548
|
+
if (getFlag(ctx.flags, 'order-id') != null && sourceInputCount(ctx.flags) > 0) {
|
|
1549
|
+
throw new Error('Pass either --order-id or one image source, not both.');
|
|
1550
|
+
}
|
|
1196
1551
|
print(await withOptionalDownloads(ctx.flags, await runPartSplit()));
|
|
1197
1552
|
return;
|
|
1198
1553
|
}
|
|
1199
1554
|
if (subcommand === 'compare') {
|
|
1200
1555
|
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,
|
|
1556
|
+
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
1557
|
}
|
|
1203
1558
|
const preprocessPrompt = getFlag(ctx.flags, 'preprocess-prompt');
|
|
1204
1559
|
if (preprocessPrompt === 'true') {
|
|
@@ -1210,17 +1565,15 @@ const commandParts = async (subcommand, ctx) => {
|
|
|
1210
1565
|
if (preprocessPrompt != null && getFlag(ctx.flags, 'mission-id') != null) {
|
|
1211
1566
|
throw new Error('parts compare preprocessing creates a second order. Omit --mission-id and let the CLI infer it.');
|
|
1212
1567
|
}
|
|
1213
|
-
const
|
|
1214
|
-
if (
|
|
1215
|
-
throw new Error('Missing required flag value: --
|
|
1568
|
+
const preprocessModelId = getFlag(ctx.flags, 'preprocess-model-id');
|
|
1569
|
+
if (preprocessModelId === 'true') {
|
|
1570
|
+
throw new Error('Missing required flag value: --preprocess-model-id <id>');
|
|
1216
1571
|
}
|
|
1217
|
-
const inputSource =
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
}))
|
|
1223
|
-
: { imageUrl: imageUrlFlag };
|
|
1572
|
+
const inputSource = productionAnalyzeSourceFromSource(await requireSource({
|
|
1573
|
+
flags: ctx.flags,
|
|
1574
|
+
client: ctx.client,
|
|
1575
|
+
fileMediaType: 'image',
|
|
1576
|
+
}));
|
|
1224
1577
|
const direct = await runPartSplit(inputSource);
|
|
1225
1578
|
let preprocessed = null;
|
|
1226
1579
|
if (preprocessPrompt != null && preprocessPrompt !== 'true') {
|
|
@@ -1228,7 +1581,7 @@ const commandParts = async (subcommand, ctx) => {
|
|
|
1228
1581
|
const queued = await ctx.client.v2.generateImage({
|
|
1229
1582
|
source: sourceFromProductionAnalyzeSource(inputSource),
|
|
1230
1583
|
prompt: preprocessPrompt,
|
|
1231
|
-
modelId:
|
|
1584
|
+
modelId: preprocessModelId ?? defaultImageModelId,
|
|
1232
1585
|
batchSize: 1,
|
|
1233
1586
|
negativePrompt: getFlag(ctx.flags, 'preprocess-negative-prompt'),
|
|
1234
1587
|
seed: getFlag(ctx.flags, 'preprocess-seed') == null
|