@assethub/cli 0.1.27 → 0.1.28
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 +73 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +176 -3
- package/dist/productionOneShot.d.ts +195 -0
- package/dist/productionOneShot.d.ts.map +1 -0
- package/dist/productionOneShot.js +307 -0
- package/package.json +1 -1
- package/skills/assethub/SKILL.md +20 -0
package/README.md
CHANGED
|
@@ -276,6 +276,79 @@ or an `imageUrl` to import), `config`, and optional `maxCostCredits`.
|
|
|
276
276
|
An uncertain Trigger acknowledgement remains recoverable as `needs_review` with
|
|
277
277
|
pending history; retrying the same operation never silently launches another run.
|
|
278
278
|
|
|
279
|
+
### One-shot: image to finished asset
|
|
280
|
+
|
|
281
|
+
`production run --image <file|asset-id>` is the fastest path from a single image to
|
|
282
|
+
a finished, composed asset. It is a thin wrapper around `production automation`
|
|
283
|
+
(split, mesh generation per part, and — by default — server-side compose, all in
|
|
284
|
+
one call) plus waiting, cost estimation and downloads. It never re-runs the manual
|
|
285
|
+
split/mesh/compose sequence itself.
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
# Estimate cost first; nothing is submitted, nothing is billed.
|
|
289
|
+
assethub production run --image ./character.png --estimate
|
|
290
|
+
|
|
291
|
+
# Run it, wait for every part and the compose stage, download the result.
|
|
292
|
+
assethub production run --image ./character.png --wait --download --out-dir ./out/asset
|
|
293
|
+
|
|
294
|
+
# An existing asset id works the same way as a local file.
|
|
295
|
+
assethub production run --image "$IMAGE_ASSET_ID" --canvas "$CANVAS_ID" --wait
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`--image` auto-detects its argument: an existing local path is uploaded like
|
|
299
|
+
`--file`; anything else is treated like `--source-id`. Do not combine `--image`
|
|
300
|
+
with `--file`/`--source-id`/other source flags — it already resolves one.
|
|
301
|
+
|
|
302
|
+
`--compose` controls the compose stage:
|
|
303
|
+
|
|
304
|
+
- Omitted: automation's own server-side default applies, untouched.
|
|
305
|
+
- `--compose none`: disables automation's compose stage (`config.autoCompose:
|
|
306
|
+
false`); the run stops once every part's mesh is generated.
|
|
307
|
+
- `--compose v6`: requests an explicit Composer V6 pass via
|
|
308
|
+
`config.partComposerAgentVersion: "part_composer_v6_auto_assemble"`
|
|
309
|
+
(`autoCompose` is left at its default of `true` — sending both together is
|
|
310
|
+
rejected by the server). A server that has shipped this (assethub-web PR
|
|
311
|
+
#8179) does the V6 compose itself, in the same automation call, pairing
|
|
312
|
+
each part's mesh with its source image automatically. Against an older,
|
|
313
|
+
currently-deployed server (detected by the automation response lacking
|
|
314
|
+
`estimatedCostBreakdown`), the CLI instead reads the batch's own
|
|
315
|
+
`mesh.generate` executions itself, pairs each produced mesh with the part
|
|
316
|
+
image it was generated from, and runs an explicit `composer run` — same
|
|
317
|
+
end result, either way you never pair mesh and image assets by hand.
|
|
318
|
+
|
|
319
|
+
`--mesh-model <id>` and `--max-parts <n>` (default 24, matching the server's
|
|
320
|
+
own conservative constant for this math) only affect `--estimate`'s math (mesh
|
|
321
|
+
unit cost x an assumed worst-case part count — the CLI cannot know the real
|
|
322
|
+
part count before splitting runs); they do not cap or select anything on the
|
|
323
|
+
real run. `--max-cost <credits>` refuses to run at all, before anything is
|
|
324
|
+
submitted, once the best-effort estimate exceeds it, and is also sent to the
|
|
325
|
+
server as `maxCostCredits` on the real run as a backstop. `--estimate`'s numbers
|
|
326
|
+
come only from side-effect-free catalog reads (mesh model credit cost, compose
|
|
327
|
+
quotes) shaped like the server's own `split`/`meshGeneration`/`compose`
|
|
328
|
+
breakdown; part-extraction/split has no dry-run price today, so that line is
|
|
329
|
+
reported as unknown rather than guessed. On a real (non-`--estimate`) run
|
|
330
|
+
against a server that has shipped #8179, the printed output additionally
|
|
331
|
+
includes the server's own `estimatedTotalCredits`/`estimatedCostBreakdown`
|
|
332
|
+
in place of the CLI's own preflight guess.
|
|
333
|
+
|
|
334
|
+
This depends on assethub-web PR #8179 for the `partComposerAgentVersion` field
|
|
335
|
+
and the richer cost breakdown; it is not merged/deployed as of this writing.
|
|
336
|
+
The CLI degrades gracefully against the currently-deployed API (see above).
|
|
337
|
+
|
|
338
|
+
`runs get <run-id> --summary` prints a handful of plain-text lines (status, stage,
|
|
339
|
+
credits, output asset, error) instead of the full JSON execution. `runs wait
|
|
340
|
+
<run-id>` is `runs watch` under a name that matches a long-running wait; both
|
|
341
|
+
accept `--interval-ms`/`--timeout-ms` and retry rate limits the same way every
|
|
342
|
+
other poll loop in the CLI does.
|
|
343
|
+
|
|
344
|
+
The one-shot command's stdout is always a single JSON object (`batchId`,
|
|
345
|
+
`estimate`, `batchStatus`, `compose`, `execution`, and `downloads` when
|
|
346
|
+
requested) — the same convention every other command in this CLI already
|
|
347
|
+
follows. `--json` is accepted for compatibility with that convention but has
|
|
348
|
+
no effect, since there is no alternate plain-text mode to opt out of (`runs
|
|
349
|
+
get --summary` is the one deliberate exception, and it is opt-in by its own
|
|
350
|
+
flag).
|
|
351
|
+
|
|
279
352
|
`evaluate list` discovers server evaluators. Server evaluation dispatch is currently
|
|
280
353
|
unavailable. Graph exports are bounded to 100 runs/evaluations and 1,000 nodes;
|
|
281
354
|
truncated exports require `--allow-truncated`. Other commands, including production run/intervene, rig and retopology,
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ type CommandContext = {
|
|
|
22
22
|
*/
|
|
23
23
|
args: string[];
|
|
24
24
|
};
|
|
25
|
-
export declare const usage = "AssetHub CLI\n\nCLI and MCP are available to all AssetHub users. Workspace permissions and feature availability apply.\n\nUsage:\n assethub --version\n assethub doctor [--mcp] [--profile <name>] [--timeout-ms <n>]\n assethub init [--agent claude-code|codex|cursor...] [--project] [--dry-run] [--base-url <url>] [--workspace <id>] [--profile <name>]\n assethub mcp tools [tool-name] [--account] [--profile <name>] [--timeout-ms <n>]\n assethub mcp config --client cursor|codex [--account] [--base-url <url>]\n assethub auth login --api-key <key> [--base-url <url>] [--profile <name>]\n assethub auth login --api-key-stdin [--base-url <url>] [--profile <name>]\n assethub auth login --access-token-stdin [--base-url <url>] [--profile <name>]\n assethub auth status [--profile <name>]\n assethub auth logout [--profile <name>]\n assethub workspace list [--query <text>] [--limit <1-100>] [--cursor <cursor>]\n assethub workspace get\n assethub workspace create --name <name> [--operation-id <uuid>]\n assethub workspace use <workspace-id>\n assethub workspace members [--workspace <id>]\n assethub workspace invite --email <email> [--role user|admin] [--resend] [--workspace <id>]\n assethub workspace set-role <user-id> --role user|admin [--workspace <id>]\n assethub workspace remove-member <user-id> [--workspace <id>]\n assethub workspace prepare|sync|analyze|verify --internal --manifest <json> --state <json>\n assethub workspace self-check --internal\n assethub workspace context put --internal --org <uuid> --actor <uuid> --canvas <id> --file <json> [--if-version <n>] [--order <uuid>]\n assethub workspace context get --internal --org <uuid> --actor <uuid> --canvas <id> [--out <json>] [--order <uuid>]\n assethub project ingest <source-dir> --out-dir <directory> [--exclude-dir <relative-path>...]\n assethub language text|vision --input-json @<json> --operation-id <uuid> [--out <json>]\n assethub canvas download --canvas <id> --out-dir <directory> [--mesh <mesh_asset_id>]\n assethub canvas nodes --canvas <id>\n assethub canvas import --canvas <id> --file <image> [--name <name>]\n assethub canvas context get --canvas <id> [--out <json>]\n assethub canvas context put --canvas <id> --file <json> [--if-version <n>]\n assethub canvas layout --canvas <id> --input-json @<json>\n assethub canvas compare --canvas <id> --source-id <asset-id> --asset-id <asset-id> --out <html>\n assethub moodboard create --input-json @<json> [--operation-id <uuid>]\n assethub moodboard list|get|update|archive [<board-id>] [--input-json @<json>]\n assethub moodboard analyze <board-id> [--revision <uuid>] [--wait]\n assethub image generate --canvas <id> --context <id> [--context-version <n>] [--context-source <key>] [--moodboard-revision <uuid>] --prompt <text> --wait\n assethub models list [--domain <domain>] [--ids-only] [--limit <n>]\n assethub models get <model-id>\n assethub files import <url> --media-type image|mesh\n assethub files upload <file> --media-type image|mesh\n 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>]\n assethub capabilities\n assethub api search [query]\n assethub api describe \"<METHOD /path>\"\n assethub api call \"<METHOD /path>\" [--path-json <json|@file>] [--query-json <json|@file>] [--input-json <json|@file|@->] [--operation-id <uuid>]\n assethub skills list [--cursor <cursor>]\n assethub skills get <skill-id> [--revision <n>]\n assethub skills learn --input-json <json|@file|@-> --operation-id <uuid>\n assethub skills update <skill-id> --input-json <json|@file|@->\n assethub skills controls <skill-id> --revision <n> --grant-revision <n> --mode automatic|manual|off [--share]\n assethub skills prepare --input-json <json|@file|@->\n assethub skills proposal <proposal-id>\n assethub skills accept <proposal-id> --input-json <json|@file|@->\n assethub composer models\n assethub composer refine --list-modes\n assethub composer refine --from-run <run-id> --instruction <text> [--mode standard|thorough|placement|workshop|blender|codex] [--max-rounds <n>] [--transforms-json <json|@file>] [--wait] [--download --out-dir <dir>]\n assethub composer refine --input-json <json|@file> [--canvas <id>] [--operation-id <uuid>] [--wait] [--download --out-dir <dir>]\n assethub composer run --part <mesh-asset-id>[:<part-image-asset-id>] [--part <mesh-asset-id>[:<part-image-asset-id>]...] --reference <image-asset-id> [--model <id>] [--mode quick|quality] [--transforms-json <json|@file>] [--canvas <id>] [--wait]\n assethub composer run --input-json <json|@file> [--canvas <id>] [--operation-id <uuid>] [--wait] [--download --out-dir <dir>]\n assethub composer run --from-run <run-id> [--transforms-json <json|@file>] [--mode quick|quality] [--wait]\n assethub composer run --node <shape:id> --canvas <id> [--model <id>] [--mode quick|quality] [--operation-id <uuid>] [--wait]\n assethub canvas create [--name <name>] [--operation-id <uuid>]\n assethub canvas list [--cursor <cursor>] [--limit <n>]\n assethub canvas get|use|open [<id>]\n assethub runs list [--canvas <id>] [--cursor <cursor>] [--limit <n>]\n assethub runs get|watch <run-id> [--timeout-ms <n>]\n assethub runs resume <operation-id> [--wait]\n assethub graph list [--cursor <cursor>] [--limit <n>]\n assethub graph show|export [--canvas <id> | --graph <id>] [--source generated|upload] [--artifact <node-id>] [--out <path>] [--allow-truncated]\n assethub graph lineage --artifact <node-id> [--canvas <id> | --graph <id>] [--source generated|upload] [--direction ancestors|descendants|both] [--depth <n>] [--out <path>]\n assethub evaluate list\n assethub evaluations submit --artifact <asset-id> --report <json|file|@file|@-> [--agent <name>] [--canvas <id>] [--reference <asset-id>...] [--run <run-id>] [--operation-id <uuid>] [--require-pass]\n assethub evaluations list [--canvas <id>] [--artifact <asset-id>]\n assethub evaluations get <evaluation-id>\n assethub image generate --input-json <json|@file|@-> [--canvas <id>] [--operation-id <uuid>] [--agent <name>] [--derived-from-run <run-id>] [--from-node <graphId/nodeId> --graph-revision <rev>] [--wait]\n assethub mesh generate --input-json <json|@file|@-> [--canvas <id>] [--operation-id <uuid>] [--agent <name>] [--derived-from-run <run-id>] [--from-node <graphId/nodeId> --graph-revision <rev>] [--wait]\n assethub mesh generate --node <shape:id> --canvas <id> [--model-id <id>] [--params-json <json|@file>] [--operation-id <uuid>] [--wait]\n assethub mesh list [--query <text>] [--cursor <cursor>] [--limit <n>]\n assethub mesh get <mesh-asset-id>\n assethub mesh download <mesh-asset-id> --out-dir <directory>\n 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>] [--canvas <id>] [--operation-id <uuid>] [--api-version v2]\n assethub image generate --prompt <text> --source-resource-id <id> --api-version v2 [--model-id <id>] [--batch-size <n>] [--wait]\n 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>]\n 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>]\n assethub rig check (--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>) [--name <name>] [--wait] [--download --out-dir <dir>]\n assethub rig create (--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>) [--model v1.0-20240301|v2.5-20260210] [--rig-type biped|quadruped|hexapod|octopod|avian|serpentine|aquatic] [--spec tripo|mixamo] [--out-format glb|fbx] [--name <name>] [--wait] [--download --out-dir <dir>]\n assethub animate presets [--model v1.0-20240301|v2.5-20260210] [--rig-type <type>]\n assethub animate retarget --resource-id <id> --animation <preset-id> [--animation <preset-id>... up to 5 total] [--out-format glb|fbx] [--bake-animation true|false] [--export-with-geometry true|false] [--animate-in-place true|false] [--name <name>] [--wait] [--download --out-dir <dir>]\n assethub jobs get <job-id> [--download --out-dir <dir>]\n assethub jobs watch <job-id> [--interval-ms <ms>] [--timeout-ms <ms>] [--download --out-dir <dir>]\n 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>] [--skill-mode auto|manual|off] [--skill-id <id>...] [--context <canvas-id> --context-version <n>] [--wait] [--download --out-dir <dir>]\n assethub production agents\n assethub production automation --input-json <json|@file|@-> [--canvas <id>] [--operation-id <uuid>] [--wait]\n assethub production status <order-id> [--wait] [--download --out-dir <dir>]\n 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>]\n assethub production run --order-id <id> --mission-id <id> [--run-mode full_auto|approval] [--task-id <id>...] [--part-extractor <name>] [--part-extraction-mode fast|high_quality] [--allowed-model-id <id>...] [--max-iterations <n>] [--max-cost-credits <n>] [--wait] [--download --out-dir <dir>]\n assethub production watch <order-id> [--interval-ms <ms>] [--timeout-ms <ms>] [--download --out-dir <dir>]\n assethub production intervene <order-id> (--exclude <target-id> | --include <target-id> | --add-part <name> | --rename <target-id>=<name> | --reject <target-id>[=<reason>] | --regenerate <target-id>=<mode> | --set-param <key>=<value> | --ops-json <json|@file|@->)... [--idempotency-key <key>]\n assethub production interventions <order-id>\n assethub runs upload <path> [--graph-id <id>] [--stream-id <id>] [--rev <n>] [--description <text>] [--tag <tag>...] [--skip-register] [--dry-run]\n 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>] [--skill-mode auto|manual|off] [--skill-id <id>...] [--wait] [--task-id <id>...] [--mission-id <id>] [--all-ready] [--download --out-dir <dir>]\n 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>]\n assethub autopilot [<image>] --demo [--yolo] [--json] [--max-regen <n>] [--stall-rounds <n>] [--tie-epsilon <x>] [--multiview 2-view|4-view|6-view] [--ab-mode serial|cross] [--credit-budget <n>]\n\nGlobal options:\n --api-key <key> Overrides saved auth and ASSETHUB_API_KEY.\n --base-url <url> Override the selected API origin. Default: https://app.assethub.io.\n --profile <name> Select this saved profile ahead of environment credentials and origin.\n --workspace <id> Select workspace scope for this command without changing saved selection.\n --config <path> Auth config path. Defaults to ~/.assethub/config.json.\n --version Show installed package version.\n --help Show help.\n\nExamples:\n ASSETHUB_API_KEY=ah_live_xxx assethub models list\n assethub auth login --api-key-stdin\n assethub init --dry-run\n assethub models list --ids-only\n assethub files upload ./input.png --media-type image\n assethub source create --file ./input.png --media-type image\n pbpaste | assethub source create --stdin-data-uri --media-type image\n assethub source create --clipboard --media-type image --file-name clipboard.png\n printf '{\"image_url\":{\"url\":\"data:image/png;base64,...\"}}' | assethub parts split --stdin-json --part-extractor \"V1.5\" --wait\n base64 -i ./input.png | assethub parts split --stdin-base64 --file-name input.png --content-type image/png --part-extractor \"V1.5\" --wait\n cat ./input.png | assethub parts split --stdin --file-name input.png --part-extractor \"V1.5\" --wait\n assethub image generate --prompt \"stylized prop concept\" --wait\n assethub mesh generate --file ./input.png --wait\n assethub animate presets --rig-type biped\n assethub rig create --source-resource-id mesh_x --rig-type biped --wait\n assethub animate retarget --resource-id rig_x --animation preset:idle --wait\n assethub production analyze --file ./input.png --part-extractor \"V1.5\" --wait\n assethub production run --order-id ord_x --mission-id ms_x --run-mode full_auto --max-cost-credits 200 --wait\n assethub production watch ord_x\n assethub production intervene ord_x --exclude 7 --rename 3=\"left wing\" --idempotency-key review-2026-07-29\n assethub production interventions ord_x\n assethub runs upload ./out/pluffy-run-2026-07-30 --dry-run\n assethub runs upload ./out/pluffy-run-2026-07-30 --tag pluffy --description \"V3 humanoid, 18 parts\"\n assethub parts split --file ./input.png --part-extractor \"V1.5\" --wait --all-ready\n assethub parts compare --file ./input.png --preprocess-prompt \"clean white background, centered product photo\" --part-extractor \"V1.5\" --wait --all-ready\n";
|
|
25
|
+
export declare const usage = "AssetHub CLI\n\nCLI and MCP are available to all AssetHub users. Workspace permissions and feature availability apply.\n\nUsage:\n assethub --version\n assethub doctor [--mcp] [--profile <name>] [--timeout-ms <n>]\n assethub init [--agent claude-code|codex|cursor...] [--project] [--dry-run] [--base-url <url>] [--workspace <id>] [--profile <name>]\n assethub mcp tools [tool-name] [--account] [--profile <name>] [--timeout-ms <n>]\n assethub mcp config --client cursor|codex [--account] [--base-url <url>]\n assethub auth login --api-key <key> [--base-url <url>] [--profile <name>]\n assethub auth login --api-key-stdin [--base-url <url>] [--profile <name>]\n assethub auth login --access-token-stdin [--base-url <url>] [--profile <name>]\n assethub auth status [--profile <name>]\n assethub auth logout [--profile <name>]\n assethub workspace list [--query <text>] [--limit <1-100>] [--cursor <cursor>]\n assethub workspace get\n assethub workspace create --name <name> [--operation-id <uuid>]\n assethub workspace use <workspace-id>\n assethub workspace members [--workspace <id>]\n assethub workspace invite --email <email> [--role user|admin] [--resend] [--workspace <id>]\n assethub workspace set-role <user-id> --role user|admin [--workspace <id>]\n assethub workspace remove-member <user-id> [--workspace <id>]\n assethub workspace prepare|sync|analyze|verify --internal --manifest <json> --state <json>\n assethub workspace self-check --internal\n assethub workspace context put --internal --org <uuid> --actor <uuid> --canvas <id> --file <json> [--if-version <n>] [--order <uuid>]\n assethub workspace context get --internal --org <uuid> --actor <uuid> --canvas <id> [--out <json>] [--order <uuid>]\n assethub project ingest <source-dir> --out-dir <directory> [--exclude-dir <relative-path>...]\n assethub language text|vision --input-json @<json> --operation-id <uuid> [--out <json>]\n assethub canvas download --canvas <id> --out-dir <directory> [--mesh <mesh_asset_id>]\n assethub canvas nodes --canvas <id>\n assethub canvas import --canvas <id> --file <image> [--name <name>]\n assethub canvas context get --canvas <id> [--out <json>]\n assethub canvas context put --canvas <id> --file <json> [--if-version <n>]\n assethub canvas layout --canvas <id> --input-json @<json>\n assethub canvas compare --canvas <id> --source-id <asset-id> --asset-id <asset-id> --out <html>\n assethub moodboard create --input-json @<json> [--operation-id <uuid>]\n assethub moodboard list|get|update|archive [<board-id>] [--input-json @<json>]\n assethub moodboard analyze <board-id> [--revision <uuid>] [--wait]\n assethub image generate --canvas <id> --context <id> [--context-version <n>] [--context-source <key>] [--moodboard-revision <uuid>] --prompt <text> --wait\n assethub models list [--domain <domain>] [--ids-only] [--limit <n>]\n assethub models get <model-id>\n assethub files import <url> --media-type image|mesh\n assethub files upload <file> --media-type image|mesh\n 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>]\n assethub capabilities\n assethub api search [query]\n assethub api describe \"<METHOD /path>\"\n assethub api call \"<METHOD /path>\" [--path-json <json|@file>] [--query-json <json|@file>] [--input-json <json|@file|@->] [--operation-id <uuid>]\n assethub skills list [--cursor <cursor>]\n assethub skills get <skill-id> [--revision <n>]\n assethub skills learn --input-json <json|@file|@-> --operation-id <uuid>\n assethub skills update <skill-id> --input-json <json|@file|@->\n assethub skills controls <skill-id> --revision <n> --grant-revision <n> --mode automatic|manual|off [--share]\n assethub skills prepare --input-json <json|@file|@->\n assethub skills proposal <proposal-id>\n assethub skills accept <proposal-id> --input-json <json|@file|@->\n assethub composer models\n assethub composer refine --list-modes\n assethub composer refine --from-run <run-id> --instruction <text> [--mode standard|thorough|placement|workshop|blender|codex] [--max-rounds <n>] [--transforms-json <json|@file>] [--wait] [--download --out-dir <dir>]\n assethub composer refine --input-json <json|@file> [--canvas <id>] [--operation-id <uuid>] [--wait] [--download --out-dir <dir>]\n assethub composer run --part <mesh-asset-id>[:<part-image-asset-id>] [--part <mesh-asset-id>[:<part-image-asset-id>]...] --reference <image-asset-id> [--model <id>] [--mode quick|quality] [--transforms-json <json|@file>] [--canvas <id>] [--wait]\n assethub composer run --input-json <json|@file> [--canvas <id>] [--operation-id <uuid>] [--wait] [--download --out-dir <dir>]\n assethub composer run --from-run <run-id> [--transforms-json <json|@file>] [--mode quick|quality] [--wait]\n assethub composer run --node <shape:id> --canvas <id> [--model <id>] [--mode quick|quality] [--operation-id <uuid>] [--wait]\n assethub canvas create [--name <name>] [--operation-id <uuid>]\n assethub canvas list [--cursor <cursor>] [--limit <n>]\n assethub canvas get|use|open [<id>]\n assethub runs list [--canvas <id>] [--cursor <cursor>] [--limit <n>]\n assethub runs get|watch|wait <run-id> [--interval-ms <ms>] [--timeout-ms <n>] [--summary]\n assethub runs resume <operation-id> [--wait]\n assethub graph list [--cursor <cursor>] [--limit <n>]\n assethub graph show|export [--canvas <id> | --graph <id>] [--source generated|upload] [--artifact <node-id>] [--out <path>] [--allow-truncated]\n assethub graph lineage --artifact <node-id> [--canvas <id> | --graph <id>] [--source generated|upload] [--direction ancestors|descendants|both] [--depth <n>] [--out <path>]\n assethub evaluate list\n assethub evaluations submit --artifact <asset-id> --report <json|file|@file|@-> [--agent <name>] [--canvas <id>] [--reference <asset-id>...] [--run <run-id>] [--operation-id <uuid>] [--require-pass]\n assethub evaluations list [--canvas <id>] [--artifact <asset-id>]\n assethub evaluations get <evaluation-id>\n assethub image generate --input-json <json|@file|@-> [--canvas <id>] [--operation-id <uuid>] [--agent <name>] [--derived-from-run <run-id>] [--from-node <graphId/nodeId> --graph-revision <rev>] [--wait]\n assethub mesh generate --input-json <json|@file|@-> [--canvas <id>] [--operation-id <uuid>] [--agent <name>] [--derived-from-run <run-id>] [--from-node <graphId/nodeId> --graph-revision <rev>] [--wait]\n assethub mesh generate --node <shape:id> --canvas <id> [--model-id <id>] [--params-json <json|@file>] [--operation-id <uuid>] [--wait]\n assethub mesh list [--query <text>] [--cursor <cursor>] [--limit <n>]\n assethub mesh get <mesh-asset-id>\n assethub mesh download <mesh-asset-id> --out-dir <directory>\n 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>] [--canvas <id>] [--operation-id <uuid>] [--api-version v2]\n assethub image generate --prompt <text> --source-resource-id <id> --api-version v2 [--model-id <id>] [--batch-size <n>] [--wait]\n 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>]\n 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>]\n assethub rig check (--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>) [--name <name>] [--wait] [--download --out-dir <dir>]\n assethub rig create (--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>) [--model v1.0-20240301|v2.5-20260210] [--rig-type biped|quadruped|hexapod|octopod|avian|serpentine|aquatic] [--spec tripo|mixamo] [--out-format glb|fbx] [--name <name>] [--wait] [--download --out-dir <dir>]\n assethub animate presets [--model v1.0-20240301|v2.5-20260210] [--rig-type <type>]\n assethub animate retarget --resource-id <id> --animation <preset-id> [--animation <preset-id>... up to 5 total] [--out-format glb|fbx] [--bake-animation true|false] [--export-with-geometry true|false] [--animate-in-place true|false] [--name <name>] [--wait] [--download --out-dir <dir>]\n assethub jobs get <job-id> [--download --out-dir <dir>]\n assethub jobs watch <job-id> [--interval-ms <ms>] [--timeout-ms <ms>] [--download --out-dir <dir>]\n 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>] [--skill-mode auto|manual|off] [--skill-id <id>...] [--context <canvas-id> --context-version <n>] [--wait] [--download --out-dir <dir>]\n assethub production agents\n assethub production automation --input-json <json|@file|@-> [--canvas <id>] [--operation-id <uuid>] [--wait]\n assethub production status <order-id> [--wait] [--download --out-dir <dir>]\n 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>]\n assethub production run --order-id <id> --mission-id <id> [--run-mode full_auto|approval] [--task-id <id>...] [--part-extractor <name>] [--part-extraction-mode fast|high_quality] [--allowed-model-id <id>...] [--max-iterations <n>] [--max-cost-credits <n>] [--wait] [--download --out-dir <dir>]\n assethub production run --image <file|asset-id> [--canvas <id>] [--part-extractor <name>] [--compose v6|none] [--mesh-model <id>] [--max-parts <n>] [--max-cost <credits>] [--estimate] [--wait] [--download --out-dir <dir>] [--json]\n assethub production watch <order-id> [--interval-ms <ms>] [--timeout-ms <ms>] [--download --out-dir <dir>]\n assethub production intervene <order-id> (--exclude <target-id> | --include <target-id> | --add-part <name> | --rename <target-id>=<name> | --reject <target-id>[=<reason>] | --regenerate <target-id>=<mode> | --set-param <key>=<value> | --ops-json <json|@file|@->)... [--idempotency-key <key>]\n assethub production interventions <order-id>\n assethub runs upload <path> [--graph-id <id>] [--stream-id <id>] [--rev <n>] [--description <text>] [--tag <tag>...] [--skip-register] [--dry-run]\n 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>] [--skill-mode auto|manual|off] [--skill-id <id>...] [--wait] [--task-id <id>...] [--mission-id <id>] [--all-ready] [--download --out-dir <dir>]\n 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>]\n assethub autopilot [<image>] --demo [--yolo] [--json] [--max-regen <n>] [--stall-rounds <n>] [--tie-epsilon <x>] [--multiview 2-view|4-view|6-view] [--ab-mode serial|cross] [--credit-budget <n>]\n\nGlobal options:\n --api-key <key> Overrides saved auth and ASSETHUB_API_KEY.\n --base-url <url> Override the selected API origin. Default: https://app.assethub.io.\n --profile <name> Select this saved profile ahead of environment credentials and origin.\n --workspace <id> Select workspace scope for this command without changing saved selection.\n --config <path> Auth config path. Defaults to ~/.assethub/config.json.\n --version Show installed package version.\n --help Show help.\n\nExamples:\n ASSETHUB_API_KEY=ah_live_xxx assethub models list\n assethub auth login --api-key-stdin\n assethub init --dry-run\n assethub models list --ids-only\n assethub files upload ./input.png --media-type image\n assethub source create --file ./input.png --media-type image\n pbpaste | assethub source create --stdin-data-uri --media-type image\n assethub source create --clipboard --media-type image --file-name clipboard.png\n printf '{\"image_url\":{\"url\":\"data:image/png;base64,...\"}}' | assethub parts split --stdin-json --part-extractor \"V1.5\" --wait\n base64 -i ./input.png | assethub parts split --stdin-base64 --file-name input.png --content-type image/png --part-extractor \"V1.5\" --wait\n cat ./input.png | assethub parts split --stdin --file-name input.png --part-extractor \"V1.5\" --wait\n assethub image generate --prompt \"stylized prop concept\" --wait\n assethub mesh generate --file ./input.png --wait\n assethub animate presets --rig-type biped\n assethub rig create --source-resource-id mesh_x --rig-type biped --wait\n assethub animate retarget --resource-id rig_x --animation preset:idle --wait\n assethub production analyze --file ./input.png --part-extractor \"V1.5\" --wait\n assethub production run --order-id ord_x --mission-id ms_x --run-mode full_auto --max-cost-credits 200 --wait\n assethub production run --image ./character.png --estimate\n assethub production run --image ./character.png --compose v6 --max-cost 500 --wait --download --out-dir ./out/asset\n assethub production watch ord_x\n assethub runs get run_x --summary\n assethub runs wait run_x --timeout-ms 3600000\n assethub production intervene ord_x --exclude 7 --rename 3=\"left wing\" --idempotency-key review-2026-07-29\n assethub production interventions ord_x\n assethub runs upload ./out/pluffy-run-2026-07-30 --dry-run\n assethub runs upload ./out/pluffy-run-2026-07-30 --tag pluffy --description \"V3 humanoid, 18 parts\"\n assethub parts split --file ./input.png --part-extractor \"V1.5\" --wait --all-ready\n assethub parts compare --file ./input.png --preprocess-prompt \"clean white background, centered product photo\" --part-extractor \"V1.5\" --wait --all-ready\n";
|
|
26
26
|
export declare const commandSkills: (subcommand: string | undefined, positionals: string[], ctx: CommandContext) => Promise<void>;
|
|
27
27
|
/**
|
|
28
28
|
* The `animate retarget` request, built from flags alone.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA6BA,OAAO,EAQL,KAAK,wBAAwB,EAO7B,KAAK,YAAY,EAEjB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA6BA,OAAO,EAQL,KAAK,wBAAwB,EAO7B,KAAK,YAAY,EAEjB,KAAK,UAAU,EAkBf,oBAAoB,EACrB,MAAM,sBAAsB,CAAA;AAc7B,OAAO,EAAC,kBAAkB,EAAc,MAAM,gCAAgC,CAAA;AAoB9E,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAA;AAuBxD,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,UAAU,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;CACnC,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAA;IAC/C,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,YAAY,CAAA;IAClB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,EAAE,CAAA;CACf,CAAA;AA4ID,eAAO,MAAM,KAAK,s+dAoJjB,CAAA;AA2gDD,eAAO,MAAM,aAAa,GACxB,YAAY,MAAM,GAAG,SAAS,EAC9B,aAAa,MAAM,EAAE,EACrB,KAAK,cAAc,KAClB,OAAO,CAAC,IAAI,CA6Hd,CAAA;AA61CD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GACxC,OAAO,KAAK,KACX,wBA0BF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,KAAK,KACX,IAAI,CAAC,UAAU,EAAE,QAAQ,CAM1B,CAAA;AA6fF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,uBAAuB,GAClC,MAAM,SAAS,MAAM,EAAE,KACtB,YAAY,EAuGd,CAAA;AAy7CD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,EAAE,OAAO,CAAA;CACtB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,YAAY,MAAM,GAAG,SAAS,EAC9B,aAAa,MAAM,EAAE,EACrB,OAAO,KAAK,KACX,cAoBF,CAAA;AAED,KAAK,iBAAiB,GAAG,IAAI,CAC3B,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,EAC5C,SAAS,GACT,UAAU,GACV,KAAK,GACL,WAAW,GACX,QAAQ,GACR,OAAO,GACP,OAAO,GACP,UAAU,CACb,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,WAAW,MAAM,EACjB,MAAM,iBAAiB;;;;;;;;;;;;;;CAUvB,CAAA;AAEF,eAAO,MAAM,sBAAsB,GACjC,SAAS,UAAU,CAAC,OAAO,iBAAiB,CAAC,EAC7C,MAAM,iBAAiB,EACvB,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;CAYf,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,8 @@ import { activeExecution, childOperationId, CliExecutionError, executeRecorded,
|
|
|
22
22
|
import { buildRunUploadPlan, formatBytes } from './runsUpload/buildRunUpload.js';
|
|
23
23
|
import { readGraphFolder } from './runsUpload/graphFolder.js';
|
|
24
24
|
import { uploadRun } from './runsUpload/uploadRun.js';
|
|
25
|
+
import { existsSync } from 'node:fs';
|
|
26
|
+
import { DEFAULT_MAX_PARTS, buildAutomationConfig, buildComposerPartsFromMeshExecutions, detectOneShotImageInput, estimateOneShotCost, exceedsMaxCost, formatExecutionSummaryLines, parseComposeFlag, pollAutomationBatch, readServerCostBreakdown, resolveFullBodyImageAssetId, serverSupportsV6Compose, withErrorHint, } from './productionOneShot.js';
|
|
25
27
|
const defaultBaseUrl = 'https://app.assethub.io';
|
|
26
28
|
// Mirrors PUBLIC_IMAGE_GENERATE_DEFAULT_MODEL_ID in apps/frontend
|
|
27
29
|
// (src/feature/imageGen/util/publicImageGenDefaultModel.ts). This package does
|
|
@@ -185,7 +187,7 @@ Usage:
|
|
|
185
187
|
assethub canvas list [--cursor <cursor>] [--limit <n>]
|
|
186
188
|
assethub canvas get|use|open [<id>]
|
|
187
189
|
assethub runs list [--canvas <id>] [--cursor <cursor>] [--limit <n>]
|
|
188
|
-
assethub runs get|watch <run-id> [--timeout-ms <n>]
|
|
190
|
+
assethub runs get|watch|wait <run-id> [--interval-ms <ms>] [--timeout-ms <n>] [--summary]
|
|
189
191
|
assethub runs resume <operation-id> [--wait]
|
|
190
192
|
assethub graph list [--cursor <cursor>] [--limit <n>]
|
|
191
193
|
assethub graph show|export [--canvas <id> | --graph <id>] [--source generated|upload] [--artifact <node-id>] [--out <path>] [--allow-truncated]
|
|
@@ -216,6 +218,7 @@ Usage:
|
|
|
216
218
|
assethub production status <order-id> [--wait] [--download --out-dir <dir>]
|
|
217
219
|
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>]
|
|
218
220
|
assethub production run --order-id <id> --mission-id <id> [--run-mode full_auto|approval] [--task-id <id>...] [--part-extractor <name>] [--part-extraction-mode fast|high_quality] [--allowed-model-id <id>...] [--max-iterations <n>] [--max-cost-credits <n>] [--wait] [--download --out-dir <dir>]
|
|
221
|
+
assethub production run --image <file|asset-id> [--canvas <id>] [--part-extractor <name>] [--compose v6|none] [--mesh-model <id>] [--max-parts <n>] [--max-cost <credits>] [--estimate] [--wait] [--download --out-dir <dir>] [--json]
|
|
219
222
|
assethub production watch <order-id> [--interval-ms <ms>] [--timeout-ms <ms>] [--download --out-dir <dir>]
|
|
220
223
|
assethub production intervene <order-id> (--exclude <target-id> | --include <target-id> | --add-part <name> | --rename <target-id>=<name> | --reject <target-id>[=<reason>] | --regenerate <target-id>=<mode> | --set-param <key>=<value> | --ops-json <json|@file|@->)... [--idempotency-key <key>]
|
|
221
224
|
assethub production interventions <order-id>
|
|
@@ -252,7 +255,11 @@ Examples:
|
|
|
252
255
|
assethub animate retarget --resource-id rig_x --animation preset:idle --wait
|
|
253
256
|
assethub production analyze --file ./input.png --part-extractor "V1.5" --wait
|
|
254
257
|
assethub production run --order-id ord_x --mission-id ms_x --run-mode full_auto --max-cost-credits 200 --wait
|
|
258
|
+
assethub production run --image ./character.png --estimate
|
|
259
|
+
assethub production run --image ./character.png --compose v6 --max-cost 500 --wait --download --out-dir ./out/asset
|
|
255
260
|
assethub production watch ord_x
|
|
261
|
+
assethub runs get run_x --summary
|
|
262
|
+
assethub runs wait run_x --timeout-ms 3600000
|
|
256
263
|
assethub production intervene ord_x --exclude 7 --rename 3="left wing" --idempotency-key review-2026-07-29
|
|
257
264
|
assethub production interventions ord_x
|
|
258
265
|
assethub runs upload ./out/pluffy-run-2026-07-30 --dry-run
|
|
@@ -3110,6 +3117,155 @@ const productionSession = (ctx, operation, canvasId = selectedCanvasId(ctx.flags
|
|
|
3110
3117
|
canvasId,
|
|
3111
3118
|
operation,
|
|
3112
3119
|
});
|
|
3120
|
+
/**
|
|
3121
|
+
* `production run --image <file|assetId>`: the one-shot "image -> finished
|
|
3122
|
+
* asset" command. A thin wrapper around `production automation` (which
|
|
3123
|
+
* already does split -> mesh x N -> compose server-side) plus waiting,
|
|
3124
|
+
* cost estimation and downloads — it never re-implements the manual
|
|
3125
|
+
* split/mesh/compose dance. See productionOneShot.ts for the pure,
|
|
3126
|
+
* independently-tested pieces (image-flag resolution, cost estimate math,
|
|
3127
|
+
* batch-status polling, the V6 compose fallback's mesh<->image pairing, and
|
|
3128
|
+
* error hints); this function only wires them to the client and to flags.
|
|
3129
|
+
*/
|
|
3130
|
+
const commandProductionRunOneShot = async (ctx) => {
|
|
3131
|
+
if (sourceInputCount(ctx.flags) > 0) {
|
|
3132
|
+
throw new Error('--image already resolves a source; do not combine it with --file/--source-id/--source-url/other source flags.');
|
|
3133
|
+
}
|
|
3134
|
+
const imageValue = requireFlag(ctx.flags, 'image');
|
|
3135
|
+
const detected = detectOneShotImageInput(imageValue, existsSync);
|
|
3136
|
+
const sourceFlags = {
|
|
3137
|
+
...ctx.flags,
|
|
3138
|
+
...(detected.kind === 'file'
|
|
3139
|
+
? { file: detected.path }
|
|
3140
|
+
: { 'source-id': detected.resourceId }),
|
|
3141
|
+
};
|
|
3142
|
+
const compose = parseComposeFlag(getFlag(ctx.flags, 'compose'));
|
|
3143
|
+
const meshModelId = getFlag(ctx.flags, 'mesh-model') ?? defaultMeshModelId;
|
|
3144
|
+
const maxParts = getFlag(ctx.flags, 'max-parts') == null
|
|
3145
|
+
? DEFAULT_MAX_PARTS
|
|
3146
|
+
: parsePositiveIntegerFlag(ctx.flags, 'max-parts', 1);
|
|
3147
|
+
const maxCostCredits = getFlag(ctx.flags, 'max-cost') == null
|
|
3148
|
+
? undefined
|
|
3149
|
+
: parsePositiveIntegerFlag(ctx.flags, 'max-cost', 1);
|
|
3150
|
+
const agentVersion = resolvePartExtractorForAnalyze(ctx.flags);
|
|
3151
|
+
// Every number below comes from a side-effect-free GET; nothing here
|
|
3152
|
+
// starts real (billable) work, so this runs whether or not --estimate was
|
|
3153
|
+
// passed and gates --max-cost before the real automation call below.
|
|
3154
|
+
const [models, composerCapabilities] = await Promise.all([
|
|
3155
|
+
ctx.client.v2.listModels({ domain: 'meshGen' }),
|
|
3156
|
+
ctx.client.v2.getMeshComposers(),
|
|
3157
|
+
]);
|
|
3158
|
+
const meshModel = models.find(model => model.id === meshModelId);
|
|
3159
|
+
const composeCredits = compose === 'v6'
|
|
3160
|
+
? (composerCapabilities.quotes?.find(quote => quote.agentVersion === 'v6')
|
|
3161
|
+
?.credits ?? null)
|
|
3162
|
+
: undefined;
|
|
3163
|
+
const estimate = estimateOneShotCost({
|
|
3164
|
+
meshModel,
|
|
3165
|
+
maxParts,
|
|
3166
|
+
composeRequested: compose === 'v6',
|
|
3167
|
+
composeCredits,
|
|
3168
|
+
});
|
|
3169
|
+
if (exceedsMaxCost(estimate.totalCredits, maxCostCredits)) {
|
|
3170
|
+
throw new Error(`Estimated cost ${estimate.totalCredits} credits exceeds --max-cost ${maxCostCredits}; refusing to run before spending anything. ${estimate.notes.join(' ')}`);
|
|
3171
|
+
}
|
|
3172
|
+
if (hasFlag(ctx.flags, 'estimate')) {
|
|
3173
|
+
print({ estimate });
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
const session = await productionSession(ctx, 'production.automation');
|
|
3177
|
+
const source = await recordedSource(ctx, await requireSource({
|
|
3178
|
+
flags: sourceFlags,
|
|
3179
|
+
client: ctx.client,
|
|
3180
|
+
fileMediaType: 'image',
|
|
3181
|
+
preferUploadId: true,
|
|
3182
|
+
}));
|
|
3183
|
+
const analyzeImage = productionAnalyzeSourceFromSource(source);
|
|
3184
|
+
const config = buildAutomationConfig(compose);
|
|
3185
|
+
const body = {
|
|
3186
|
+
images: [analyzeImage],
|
|
3187
|
+
agentVersion,
|
|
3188
|
+
...(config ? { config } : {}),
|
|
3189
|
+
...(maxCostCredits != null ? { maxCostCredits } : {}),
|
|
3190
|
+
};
|
|
3191
|
+
const queued = (await executeRecorded(session, 'production.automation', body, executionOptions(ctx.flags)));
|
|
3192
|
+
stderr.write(`[run] batch=${queued.batchId}\n`);
|
|
3193
|
+
// `estimatedCostBreakdown`'s presence on the automation response itself
|
|
3194
|
+
// (assethub-web #8179) is the proof that this server understood
|
|
3195
|
+
// `partComposerAgentVersion` and already ran Composer V6 server-side, in
|
|
3196
|
+
// the same call — no client-side fallback compose is needed. Its absence
|
|
3197
|
+
// means an older, currently-deployed server silently ignored that field,
|
|
3198
|
+
// so the CLI still has to pair parts and run compose itself below.
|
|
3199
|
+
const serverHandledV6Compose = compose === 'v6' &&
|
|
3200
|
+
serverSupportsV6Compose(queued);
|
|
3201
|
+
const needsFallbackCompose = compose === 'v6' && !serverHandledV6Compose;
|
|
3202
|
+
let batchStatus;
|
|
3203
|
+
if (hasFlag(ctx.flags, 'wait') || needsFallbackCompose) {
|
|
3204
|
+
batchStatus = await pollAutomationBatch({
|
|
3205
|
+
getStatus: () => ctx.client.v1.getProductionAutomationStatus(queued.batchId),
|
|
3206
|
+
sleep: ms => delay(ms),
|
|
3207
|
+
intervalMs: parsePositiveIntegerFlag(ctx.flags, 'interval-ms', 5000),
|
|
3208
|
+
timeoutMs: parsePositiveIntegerFlag(ctx.flags, 'timeout-ms', 1800000),
|
|
3209
|
+
onProgress: lines => lines.forEach(line => stderr.write(`${line}\n`)),
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
let composeExecution;
|
|
3213
|
+
if (needsFallbackCompose) {
|
|
3214
|
+
const order = queued.images[0];
|
|
3215
|
+
if (!order)
|
|
3216
|
+
throw new Error('Automation did not return an order for the requested image.');
|
|
3217
|
+
if (batchStatus?.images.find(image => image.orderId === order.orderId)
|
|
3218
|
+
?.status === 'failed')
|
|
3219
|
+
throw new Error(`Automation failed for order ${order.orderId}; there is nothing to compose.`);
|
|
3220
|
+
const runs = await ctx.client.v2.listCanvasRuns(session.canvas.id, { limit: 200 });
|
|
3221
|
+
const parts = buildComposerPartsFromMeshExecutions(runs.items, order.orderId);
|
|
3222
|
+
const fullBodyImageAssetId = resolveFullBodyImageAssetId(runs.items, order.orderId) ??
|
|
3223
|
+
(detected.kind === 'resourceId' ? detected.resourceId : undefined);
|
|
3224
|
+
if (parts.length === 0 || fullBodyImageAssetId == null) {
|
|
3225
|
+
throw withErrorHint(new Error('Could not find mesh part outputs (and the source image they were generated from) to compose for this order.'));
|
|
3226
|
+
}
|
|
3227
|
+
try {
|
|
3228
|
+
const composeQueued = await executeRecorded(session, 'mesh.compose', {
|
|
3229
|
+
parts,
|
|
3230
|
+
fullBodyImageAssetId,
|
|
3231
|
+
agentVersion: 'v6',
|
|
3232
|
+
}, executionOptions(ctx.flags));
|
|
3233
|
+
composeExecution = composeQueued.execution;
|
|
3234
|
+
}
|
|
3235
|
+
catch (error) {
|
|
3236
|
+
throw error instanceof Error ? withErrorHint(error) : error;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
// The automation receipt (`queued.execution`) is only the split stage's own
|
|
3240
|
+
// execution row; the real completion signal for the whole order (mesh gen
|
|
3241
|
+
// across every part, plus compose when the server ran it) is the batch
|
|
3242
|
+
// status polled above. Once that says the order is done, re-fetch the
|
|
3243
|
+
// execution so `--download`/the printed summary reflect the final state
|
|
3244
|
+
// rather than the split-stage snapshot from right after submission.
|
|
3245
|
+
const finalExecution = composeExecution ??
|
|
3246
|
+
(batchStatus != null
|
|
3247
|
+
? await ctx.client.v2.getRun(queued.execution.runId)
|
|
3248
|
+
: queued.execution);
|
|
3249
|
+
// Prefer the server's own reported cost (#8179) over our preflight
|
|
3250
|
+
// best-effort `estimate` once we actually have it; absent on a server
|
|
3251
|
+
// that has not deployed #8179 yet, in which case only `estimate` (used
|
|
3252
|
+
// for the --max-cost gate above) is available.
|
|
3253
|
+
const serverCost = readServerCostBreakdown(queued);
|
|
3254
|
+
// The machine-readable summary this prints is always JSON, matching every
|
|
3255
|
+
// other command in this CLI; --json is accepted (it is a no-op) rather than
|
|
3256
|
+
// rejected, since there is no plain-text mode here to opt out of.
|
|
3257
|
+
print(await withOptionalDownloads(ctx.flags, sanitizeProductionOutput({
|
|
3258
|
+
batchId: queued.batchId,
|
|
3259
|
+
estimatedCostCredits: queued.estimatedCostCredits,
|
|
3260
|
+
...(serverCost ?? {}),
|
|
3261
|
+
images: queued.images,
|
|
3262
|
+
estimate,
|
|
3263
|
+
batchStatus,
|
|
3264
|
+
compose: composeExecution,
|
|
3265
|
+
execution: finalExecution,
|
|
3266
|
+
})));
|
|
3267
|
+
process.exitCode = executionExitCode(finalExecution);
|
|
3268
|
+
};
|
|
3113
3269
|
const commandProduction = async (subcommand, positionals, ctx) => {
|
|
3114
3270
|
if (subcommand === 'agents') {
|
|
3115
3271
|
print(sanitizeProductionOutput(await ctx.client.v1.getProductionAgents()));
|
|
@@ -3216,6 +3372,14 @@ const commandProduction = async (subcommand, positionals, ctx) => {
|
|
|
3216
3372
|
return;
|
|
3217
3373
|
}
|
|
3218
3374
|
if (subcommand === 'run') {
|
|
3375
|
+
// `production run --image <file|assetId>` is a different command in
|
|
3376
|
+
// everything but name: the one-shot image -> finished-asset flow, not
|
|
3377
|
+
// confirmed-task mission execution. Intercept before `--order-id` is
|
|
3378
|
+
// required so the mission flow below is completely untouched when
|
|
3379
|
+
// --image is absent.
|
|
3380
|
+
if (getFlag(ctx.flags, 'image') != null) {
|
|
3381
|
+
return commandProductionRunOneShot(ctx);
|
|
3382
|
+
}
|
|
3219
3383
|
const orderId = requireFlag(ctx.flags, 'order-id');
|
|
3220
3384
|
const missionId = requireFlag(ctx.flags, 'mission-id');
|
|
3221
3385
|
const runMode = parseRunModeFlag(ctx.flags);
|
|
@@ -3910,12 +4074,21 @@ const commandRuns = async (subcommand, positionals, ctx) => {
|
|
|
3910
4074
|
print(await ctx.client.v2.listCanvasRuns(canvas.id, pageOptions(ctx.flags)));
|
|
3911
4075
|
return;
|
|
3912
4076
|
}
|
|
3913
|
-
if (subcommand === 'get' || subcommand === 'watch') {
|
|
4077
|
+
if (subcommand === 'get' || subcommand === 'watch' || subcommand === 'wait') {
|
|
3914
4078
|
const id = requirePositional(positionals, 2, 'run-id');
|
|
4079
|
+
// `wait` is `watch` under a name that matches how long-running waits are
|
|
4080
|
+
// asked for elsewhere in the CLI; both poll the same way (429s are
|
|
4081
|
+
// retried inside the client's own request layer, same as every other
|
|
4082
|
+
// poll loop in this file) and accept the same --interval-ms/--timeout-ms.
|
|
3915
4083
|
const execution = subcommand === 'get'
|
|
3916
4084
|
? await ctx.client.v2.getRun(id)
|
|
3917
4085
|
: await waitForExecution(ctx.client, id, watchOptions(ctx.flags));
|
|
3918
|
-
|
|
4086
|
+
if (subcommand === 'get' && hasFlag(ctx.flags, 'summary')) {
|
|
4087
|
+
stdout.write(`${formatExecutionSummaryLines(execution).join('\n')}\n`);
|
|
4088
|
+
}
|
|
4089
|
+
else {
|
|
4090
|
+
print({ execution });
|
|
4091
|
+
}
|
|
3919
4092
|
process.exitCode = executionExitCode(execution);
|
|
3920
4093
|
return;
|
|
3921
4094
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, independently-testable pieces of `production run --image ...`
|
|
3
|
+
* (the image -> finished-asset one-shot command). Kept separate from
|
|
4
|
+
* index.ts so the tricky bits — cost estimation, batch-status polling, the
|
|
5
|
+
* V6 compose fallback's mesh<->image pairing, and error hints — can be unit
|
|
6
|
+
* tested without spinning up an HTTP server or spawning the built CLI.
|
|
7
|
+
*
|
|
8
|
+
* This module intentionally does not import from `./index.js`: the CLI
|
|
9
|
+
* command wiring (in index.ts) calls these functions and supplies whatever
|
|
10
|
+
* they need (flags already parsed, client calls already made) rather than
|
|
11
|
+
* this module reaching back into index.ts's private helpers, which would
|
|
12
|
+
* create a circular import between the two files.
|
|
13
|
+
*/
|
|
14
|
+
import type { CanvasExecution, MeshComposerPart, ModelSummary, ProductionAutomationBatchStatusResult, ProductionOrderStatus } from '@assethub/api-client';
|
|
15
|
+
export type OneShotImageInput = {
|
|
16
|
+
kind: 'file';
|
|
17
|
+
path: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'resourceId';
|
|
20
|
+
resourceId: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* `--image <file|assetId>` auto-detection: an existing local path is treated
|
|
24
|
+
* as `--file`; anything else (an asset/resource id) is treated as
|
|
25
|
+
* `--source-id`. `exists` is injectable so this stays a pure function in
|
|
26
|
+
* tests instead of touching the real filesystem.
|
|
27
|
+
*/
|
|
28
|
+
export declare const detectOneShotImageInput: (value: string, exists: (path: string) => boolean) => OneShotImageInput;
|
|
29
|
+
export type ComposeMode = 'v6' | 'none';
|
|
30
|
+
/** Parses `--compose`. Absent stays absent (server default applies untouched). */
|
|
31
|
+
export declare const parseComposeFlag: (value: string | undefined) => ComposeMode | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The confirmed field name (assethub-web PR #8179, `web-cli-ux`) for
|
|
34
|
+
* requesting an explicit Composer V6 pass from `production automation`
|
|
35
|
+
* itself. Not `composeModel` — that was this module's pre-#8179 best guess.
|
|
36
|
+
*/
|
|
37
|
+
export declare const PART_COMPOSER_V6_AGENT_VERSION = "part_composer_v6_auto_assemble";
|
|
38
|
+
/**
|
|
39
|
+
* Builds the `config` object for the `production automation` request.
|
|
40
|
+
*
|
|
41
|
+
* - `--compose` omitted: returns `undefined` so the request carries no
|
|
42
|
+
* `config.autoCompose` at all, leaving the server default in effect.
|
|
43
|
+
* - `--compose none`: disables automation's own compose stage entirely.
|
|
44
|
+
* - `--compose v6`: asks the server to run Composer V6 itself, in the same
|
|
45
|
+
* call, via `partComposerAgentVersion`. `autoCompose` is deliberately left
|
|
46
|
+
* unset (defaults to `true`) — the server 400s with
|
|
47
|
+
* `PART_COMPOSER_VERSION_REQUIRES_AUTO_COMPOSE` if both are sent together,
|
|
48
|
+
* so this function must never combine them. A server that predates #8179
|
|
49
|
+
* ignores the unrecognized field and runs its own default compose instead;
|
|
50
|
+
* `serverSupportsV6Compose` inspects the response afterward so the caller
|
|
51
|
+
* can still fall back to the client-side lineage-pairing compose
|
|
52
|
+
* (`buildComposerPartsFromMeshExecutions`) on that older server.
|
|
53
|
+
*/
|
|
54
|
+
export declare const buildAutomationConfig: (compose: ComposeMode | undefined) => Record<string, unknown> | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* `estimatedCostBreakdown` (added by #8179) only appears on a server new
|
|
57
|
+
* enough to also honor `partComposerAgentVersion` — its presence in the
|
|
58
|
+
* automation response is the signal that the server already did the V6
|
|
59
|
+
* compose server-side in that same call, so no client-side fallback compose
|
|
60
|
+
* is needed. Its absence means an older, currently-deployed server: it
|
|
61
|
+
* silently ignored `partComposerAgentVersion` and ran its own default
|
|
62
|
+
* compose (if any), so the caller must still run the explicit fallback.
|
|
63
|
+
*/
|
|
64
|
+
export declare const serverSupportsV6Compose: (automationResult: Record<string, unknown>) => boolean;
|
|
65
|
+
export declare const DEFAULT_MAX_PARTS = 24;
|
|
66
|
+
export type CostLineItem = {
|
|
67
|
+
total: number | null;
|
|
68
|
+
note?: string;
|
|
69
|
+
};
|
|
70
|
+
export type OneShotCostBreakdown = {
|
|
71
|
+
split: CostLineItem & {
|
|
72
|
+
perImage: number | null;
|
|
73
|
+
};
|
|
74
|
+
meshGeneration: CostLineItem & {
|
|
75
|
+
perPart: number | null;
|
|
76
|
+
maxParts: number;
|
|
77
|
+
};
|
|
78
|
+
compose: CostLineItem & {
|
|
79
|
+
perLane: number | null;
|
|
80
|
+
lanesPerImage: number;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
export type OneShotCostEstimate = {
|
|
84
|
+
breakdown: OneShotCostBreakdown;
|
|
85
|
+
totalCredits: number;
|
|
86
|
+
notes: string[];
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Best-effort cost breakdown for `--estimate`, shaped like the server's own
|
|
90
|
+
* `estimatedCostBreakdown` (#8179: `split`/`meshGeneration`/`compose`, each
|
|
91
|
+
* with a per-unit price and a total). Nothing here calls `production
|
|
92
|
+
* automation`/`analyze` (that would start real, billable work); every
|
|
93
|
+
* number comes from a side-effect-free GET (`models` catalog, `mesh/compose`
|
|
94
|
+
* capabilities). Today's API has no dry-run cost for part extraction/split,
|
|
95
|
+
* so that line is reported as unknown rather than guessed. `maxParts`
|
|
96
|
+
* mirrors the server's own conservative constant for this math (24, per
|
|
97
|
+
* #8179) rather than a locally invented cap; `--max-parts` overrides it.
|
|
98
|
+
*/
|
|
99
|
+
export declare const estimateOneShotCost: (options: {
|
|
100
|
+
meshModel: ModelSummary | undefined;
|
|
101
|
+
maxParts: number;
|
|
102
|
+
composeRequested: boolean;
|
|
103
|
+
composeCredits: number | null | undefined;
|
|
104
|
+
}) => OneShotCostEstimate;
|
|
105
|
+
/**
|
|
106
|
+
* Reads the server's own `estimatedTotalCredits`/`estimatedCostBreakdown`
|
|
107
|
+
* off a real (non-estimate) automation response, when present (#8179).
|
|
108
|
+
* These fields are not yet in `ProductionAutomationResult`'s shipped type,
|
|
109
|
+
* so this reads them optionally/untyped rather than guessing a schema;
|
|
110
|
+
* absent on a server that has not deployed #8179 yet.
|
|
111
|
+
*/
|
|
112
|
+
export declare const readServerCostBreakdown: (automationResult: Record<string, unknown>) => {
|
|
113
|
+
estimatedTotalCredits: number;
|
|
114
|
+
estimatedCostBreakdown: unknown;
|
|
115
|
+
} | undefined;
|
|
116
|
+
/** Whether the estimated (or actual, server-reported) total would exceed `--max-cost`. */
|
|
117
|
+
export declare const exceedsMaxCost: (totalCredits: number, maxCostCredits: number | undefined) => boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Human stage label for one image's `ProductionOrderStatus`. The API has no
|
|
120
|
+
* distinct "compose" status, so `ready`/`in_progress` are both reported as
|
|
121
|
+
* `mesh` (parts are being generated or are ready to be); there is no way to
|
|
122
|
+
* tell "meshing" apart from "composing" from this field alone.
|
|
123
|
+
*/
|
|
124
|
+
export declare const stageLabelForOrderStatus: (status: ProductionOrderStatus) => string;
|
|
125
|
+
/**
|
|
126
|
+
* One line per per-image stage transition, plus a batch-level completion
|
|
127
|
+
* line when the completed/failed counts change. "mesh k/N" here means
|
|
128
|
+
* images processed out of the batch, not parts within one image — the batch
|
|
129
|
+
* status endpoint does not expose per-part progress.
|
|
130
|
+
*/
|
|
131
|
+
export declare const batchProgressLines: (previous: ProductionAutomationBatchStatusResult | undefined, next: ProductionAutomationBatchStatusResult) => string[];
|
|
132
|
+
/**
|
|
133
|
+
* Polls `getStatus` (expected to be `client.v1.getProductionAutomationStatus`,
|
|
134
|
+
* which already retries 429s in its own request layer) until every image in
|
|
135
|
+
* the batch reaches a terminal outcome, printing one line per stage
|
|
136
|
+
* transition via `onProgress`. Mirrors the deadline/backoff shape of
|
|
137
|
+
* `pollProductionStatus`/`watchProductionRun` in index.ts.
|
|
138
|
+
*/
|
|
139
|
+
export declare const pollAutomationBatch: (options: {
|
|
140
|
+
getStatus: () => Promise<ProductionAutomationBatchStatusResult>;
|
|
141
|
+
sleep: (ms: number) => Promise<void>;
|
|
142
|
+
intervalMs: number;
|
|
143
|
+
timeoutMs: number;
|
|
144
|
+
onProgress?: (lines: string[]) => void;
|
|
145
|
+
}) => Promise<ProductionAutomationBatchStatusResult>;
|
|
146
|
+
/**
|
|
147
|
+
* The V6 compose fallback's mesh<->image pairing. Reads the automation
|
|
148
|
+
* batch's own `mesh.generate` executions (fetched via `listCanvasRuns` and
|
|
149
|
+
* filtered to the target order) for each part's produced mesh asset
|
|
150
|
+
* (`execution.outputs`, `mediaType === 'mesh'`) and the part image it was
|
|
151
|
+
* generated from (`execution.inputAssets`, `mediaType === 'image'`) — the
|
|
152
|
+
* same `{assetId, partImageAssetId}` shape `composer run --part
|
|
153
|
+
* <mesh>:<image>` accepts, so no manual pairing is needed.
|
|
154
|
+
*/
|
|
155
|
+
export declare const buildComposerPartsFromMeshExecutions: (executions: CanvasExecution[], orderId: string) => MeshComposerPart[];
|
|
156
|
+
/**
|
|
157
|
+
* The V6 compose fallback's `fullBodyImageAssetId`. Automation's own result
|
|
158
|
+
* does not return a durable asset id for the original image (only
|
|
159
|
+
* `orderId`/`projectId`/`url`/`runId`), so this reads it back off the part
|
|
160
|
+
* images' own `sourceAssetId` (each part image's own upstream source is the
|
|
161
|
+
* original full-body photo it was cropped from) — the same field
|
|
162
|
+
* `composerInputFromRun`'s mesh.generate part-field allowlist tracks.
|
|
163
|
+
*/
|
|
164
|
+
export declare const resolveFullBodyImageAssetId: (executions: CanvasExecution[], orderId: string) => string | undefined;
|
|
165
|
+
export type KnownErrorLike = {
|
|
166
|
+
status?: number;
|
|
167
|
+
code?: string;
|
|
168
|
+
message: string;
|
|
169
|
+
details?: {
|
|
170
|
+
meshAssetId?: string;
|
|
171
|
+
assetId?: string;
|
|
172
|
+
actualType?: string;
|
|
173
|
+
expectedType?: string;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Actionable next steps appended to (never replacing) a known
|
|
178
|
+
* `AssetHubApiError`'s own message. Matches on `error.code`/`error.details`
|
|
179
|
+
* first — `PART_IMAGE_REQUIRED` and `ASSET_WRONG_MEDIA_TYPE` are confirmed
|
|
180
|
+
* `/mesh/compose` error codes (assethub-web PR #8179) with a structured
|
|
181
|
+
* `details` payload. Falls back to matching on message content for a server
|
|
182
|
+
* that has not shipped those codes yet (e.g. today's deployed API, where
|
|
183
|
+
* the same failures still surface as a plain `404 ASSET_NOT_FOUND` or a
|
|
184
|
+
* generic validation message — "Asset not found" is quoted directly from
|
|
185
|
+
* the originally reported failure). `ASSET_NOT_FOUND` itself (asset does
|
|
186
|
+
* not exist / isn't yours) is left alone; it is not one of these cases.
|
|
187
|
+
*/
|
|
188
|
+
export declare const errorHintFor: (error: KnownErrorLike) => string | undefined;
|
|
189
|
+
/** Appends an error hint to `error.message` in place, if one applies. */
|
|
190
|
+
export declare const withErrorHint: <T extends Error & {
|
|
191
|
+
message: string;
|
|
192
|
+
}>(error: T) => T;
|
|
193
|
+
/** Condensed, human-readable lines for `runs get --summary`. */
|
|
194
|
+
export declare const formatExecutionSummaryLines: (execution: CanvasExecution) => string[];
|
|
195
|
+
//# sourceMappingURL=productionOneShot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"productionOneShot.d.ts","sourceRoot":"","sources":["../src/productionOneShot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,qCAAqC,EACrC,qBAAqB,EACtB,MAAM,sBAAsB,CAAA;AAE7B,MAAM,MAAM,iBAAiB,GACzB;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,GAC5B;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,CAAA;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,MAAM,EACb,QAAQ,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAChC,iBACoF,CAAA;AAEvF,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,CAAA;AAEvC,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,WAAW,GAAG,SAI1E,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,mCAAmC,CAAA;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,WAAW,GAAG,SAAS,KAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAI5B,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,GAClC,kBAAkB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACxC,OAA0D,CAAA;AAE7D,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC,MAAM,MAAM,YAAY,GAAG;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAC,CAAA;AAEhE,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,YAAY,GAAG;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,CAAA;IAC/C,cAAc,EAAE,YAAY,GAAG;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAA;IACzE,OAAO,EAAE,YAAY,GAAG;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAC,CAAA;CACxE,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,oBAAoB,CAAA;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS;IAC3C,SAAS,EAAE,YAAY,GAAG,SAAS,CAAA;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,OAAO,CAAA;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAC1C,KAAG,mBAmDH,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAClC,kBAAkB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACxC;IAAC,qBAAqB,EAAE,MAAM,CAAC;IAAC,sBAAsB,EAAE,OAAO,CAAA;CAAC,GAAG,SAKrE,CAAA;AAED,0FAA0F;AAC1F,eAAO,MAAM,cAAc,GACzB,cAAc,MAAM,EACpB,gBAAgB,MAAM,GAAG,SAAS,KACjC,OAAkE,CAAA;AAErE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GAAI,QAAQ,qBAAqB,KAAG,MAgBxE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAU,qCAAqC,GAAG,SAAS,EAC3D,MAAM,qCAAqC,KAC1C,MAAM,EAuBR,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAU,SAAS;IACjD,SAAS,EAAE,MAAM,OAAO,CAAC,qCAAqC,CAAC,CAAA;IAC/D,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACvC,KAAG,OAAO,CAAC,qCAAqC,CAkBhD,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,GAC/C,YAAY,eAAe,EAAE,EAC7B,SAAS,MAAM,KACd,gBAAgB,EAclB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACtC,YAAY,eAAe,EAAE,EAC7B,SAAS,MAAM,KACd,MAAM,GAAG,SAQX,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,cAAc,KAAG,MAAM,GAAG,SA0B7D,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,KAAK,GAAG;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,EAAE,OAAO,CAAC,KAAG,CAI7E,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,2BAA2B,GAAI,WAAW,eAAe,KAAG,MAAM,EAe9E,CAAA"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--image <file|assetId>` auto-detection: an existing local path is treated
|
|
3
|
+
* as `--file`; anything else (an asset/resource id) is treated as
|
|
4
|
+
* `--source-id`. `exists` is injectable so this stays a pure function in
|
|
5
|
+
* tests instead of touching the real filesystem.
|
|
6
|
+
*/
|
|
7
|
+
export const detectOneShotImageInput = (value, exists) => exists(value) ? { kind: 'file', path: value } : { kind: 'resourceId', resourceId: value };
|
|
8
|
+
/** Parses `--compose`. Absent stays absent (server default applies untouched). */
|
|
9
|
+
export const parseComposeFlag = (value) => {
|
|
10
|
+
if (value == null)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (value === 'v6' || value === 'none')
|
|
13
|
+
return value;
|
|
14
|
+
throw new Error('--compose must be "v6" or "none"');
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The confirmed field name (assethub-web PR #8179, `web-cli-ux`) for
|
|
18
|
+
* requesting an explicit Composer V6 pass from `production automation`
|
|
19
|
+
* itself. Not `composeModel` — that was this module's pre-#8179 best guess.
|
|
20
|
+
*/
|
|
21
|
+
export const PART_COMPOSER_V6_AGENT_VERSION = 'part_composer_v6_auto_assemble';
|
|
22
|
+
/**
|
|
23
|
+
* Builds the `config` object for the `production automation` request.
|
|
24
|
+
*
|
|
25
|
+
* - `--compose` omitted: returns `undefined` so the request carries no
|
|
26
|
+
* `config.autoCompose` at all, leaving the server default in effect.
|
|
27
|
+
* - `--compose none`: disables automation's own compose stage entirely.
|
|
28
|
+
* - `--compose v6`: asks the server to run Composer V6 itself, in the same
|
|
29
|
+
* call, via `partComposerAgentVersion`. `autoCompose` is deliberately left
|
|
30
|
+
* unset (defaults to `true`) — the server 400s with
|
|
31
|
+
* `PART_COMPOSER_VERSION_REQUIRES_AUTO_COMPOSE` if both are sent together,
|
|
32
|
+
* so this function must never combine them. A server that predates #8179
|
|
33
|
+
* ignores the unrecognized field and runs its own default compose instead;
|
|
34
|
+
* `serverSupportsV6Compose` inspects the response afterward so the caller
|
|
35
|
+
* can still fall back to the client-side lineage-pairing compose
|
|
36
|
+
* (`buildComposerPartsFromMeshExecutions`) on that older server.
|
|
37
|
+
*/
|
|
38
|
+
export const buildAutomationConfig = (compose) => {
|
|
39
|
+
if (compose == null)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (compose === 'none')
|
|
42
|
+
return { autoCompose: false };
|
|
43
|
+
return { partComposerAgentVersion: PART_COMPOSER_V6_AGENT_VERSION };
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* `estimatedCostBreakdown` (added by #8179) only appears on a server new
|
|
47
|
+
* enough to also honor `partComposerAgentVersion` — its presence in the
|
|
48
|
+
* automation response is the signal that the server already did the V6
|
|
49
|
+
* compose server-side in that same call, so no client-side fallback compose
|
|
50
|
+
* is needed. Its absence means an older, currently-deployed server: it
|
|
51
|
+
* silently ignored `partComposerAgentVersion` and ran its own default
|
|
52
|
+
* compose (if any), so the caller must still run the explicit fallback.
|
|
53
|
+
*/
|
|
54
|
+
export const serverSupportsV6Compose = (automationResult) => automationResult.estimatedCostBreakdown != null;
|
|
55
|
+
export const DEFAULT_MAX_PARTS = 24;
|
|
56
|
+
/**
|
|
57
|
+
* Best-effort cost breakdown for `--estimate`, shaped like the server's own
|
|
58
|
+
* `estimatedCostBreakdown` (#8179: `split`/`meshGeneration`/`compose`, each
|
|
59
|
+
* with a per-unit price and a total). Nothing here calls `production
|
|
60
|
+
* automation`/`analyze` (that would start real, billable work); every
|
|
61
|
+
* number comes from a side-effect-free GET (`models` catalog, `mesh/compose`
|
|
62
|
+
* capabilities). Today's API has no dry-run cost for part extraction/split,
|
|
63
|
+
* so that line is reported as unknown rather than guessed. `maxParts`
|
|
64
|
+
* mirrors the server's own conservative constant for this math (24, per
|
|
65
|
+
* #8179) rather than a locally invented cap; `--max-parts` overrides it.
|
|
66
|
+
*/
|
|
67
|
+
export const estimateOneShotCost = (options) => {
|
|
68
|
+
const notes = [];
|
|
69
|
+
const split = {
|
|
70
|
+
perImage: null,
|
|
71
|
+
total: null,
|
|
72
|
+
note: 'part extraction/split cost has no dry-run price source today; the actual cost is reported after the run (see `production status` or `runs get --summary`)',
|
|
73
|
+
};
|
|
74
|
+
notes.push(split.note);
|
|
75
|
+
const perPart = options.meshModel?.creditCost ?? options.meshModel?.defaultCreditCost ?? null;
|
|
76
|
+
const meshTotal = perPart == null ? null : perPart * options.maxParts;
|
|
77
|
+
const meshGeneration = {
|
|
78
|
+
perPart,
|
|
79
|
+
maxParts: options.maxParts,
|
|
80
|
+
total: meshTotal,
|
|
81
|
+
...(perPart == null
|
|
82
|
+
? {
|
|
83
|
+
note: options.meshModel
|
|
84
|
+
? `mesh generation cost not included: no credit price found for model "${options.meshModel.id}"`
|
|
85
|
+
: 'mesh generation cost not included: mesh model was not found in the catalog',
|
|
86
|
+
}
|
|
87
|
+
: {
|
|
88
|
+
note: `assumes up to ${options.maxParts} parts at ${perPart} credits each; the CLI has no way to know the actual part count before splitting runs`,
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
notes.push(meshGeneration.note);
|
|
92
|
+
const lanesPerImage = 1;
|
|
93
|
+
const compose = options.composeRequested
|
|
94
|
+
? options.composeCredits == null
|
|
95
|
+
? {
|
|
96
|
+
perLane: null,
|
|
97
|
+
lanesPerImage,
|
|
98
|
+
total: null,
|
|
99
|
+
note: 'compose cost not included: no quote found for the requested compose model',
|
|
100
|
+
}
|
|
101
|
+
: { perLane: options.composeCredits, lanesPerImage, total: options.composeCredits * lanesPerImage }
|
|
102
|
+
: {
|
|
103
|
+
perLane: null,
|
|
104
|
+
lanesPerImage,
|
|
105
|
+
total: 0,
|
|
106
|
+
note: 'compose disabled by --compose none',
|
|
107
|
+
};
|
|
108
|
+
if (compose.note)
|
|
109
|
+
notes.push(compose.note);
|
|
110
|
+
const totalCredits = (meshGeneration.total ?? 0) + (compose.total ?? 0);
|
|
111
|
+
return { breakdown: { split, meshGeneration, compose }, totalCredits, notes };
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Reads the server's own `estimatedTotalCredits`/`estimatedCostBreakdown`
|
|
115
|
+
* off a real (non-estimate) automation response, when present (#8179).
|
|
116
|
+
* These fields are not yet in `ProductionAutomationResult`'s shipped type,
|
|
117
|
+
* so this reads them optionally/untyped rather than guessing a schema;
|
|
118
|
+
* absent on a server that has not deployed #8179 yet.
|
|
119
|
+
*/
|
|
120
|
+
export const readServerCostBreakdown = (automationResult) => {
|
|
121
|
+
const total = automationResult.estimatedTotalCredits;
|
|
122
|
+
const breakdown = automationResult.estimatedCostBreakdown;
|
|
123
|
+
if (typeof total !== 'number' || breakdown == null)
|
|
124
|
+
return undefined;
|
|
125
|
+
return { estimatedTotalCredits: total, estimatedCostBreakdown: breakdown };
|
|
126
|
+
};
|
|
127
|
+
/** Whether the estimated (or actual, server-reported) total would exceed `--max-cost`. */
|
|
128
|
+
export const exceedsMaxCost = (totalCredits, maxCostCredits) => maxCostCredits != null && totalCredits > maxCostCredits;
|
|
129
|
+
/**
|
|
130
|
+
* Human stage label for one image's `ProductionOrderStatus`. The API has no
|
|
131
|
+
* distinct "compose" status, so `ready`/`in_progress` are both reported as
|
|
132
|
+
* `mesh` (parts are being generated or are ready to be); there is no way to
|
|
133
|
+
* tell "meshing" apart from "composing" from this field alone.
|
|
134
|
+
*/
|
|
135
|
+
export const stageLabelForOrderStatus = (status) => {
|
|
136
|
+
switch (status) {
|
|
137
|
+
case 'draft':
|
|
138
|
+
return 'queued';
|
|
139
|
+
case 'analyzing':
|
|
140
|
+
return 'split';
|
|
141
|
+
case 'ready':
|
|
142
|
+
case 'in_progress':
|
|
143
|
+
return 'mesh';
|
|
144
|
+
case 'completed':
|
|
145
|
+
return 'done';
|
|
146
|
+
case 'failed':
|
|
147
|
+
return 'failed';
|
|
148
|
+
default:
|
|
149
|
+
return status;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* One line per per-image stage transition, plus a batch-level completion
|
|
154
|
+
* line when the completed/failed counts change. "mesh k/N" here means
|
|
155
|
+
* images processed out of the batch, not parts within one image — the batch
|
|
156
|
+
* status endpoint does not expose per-part progress.
|
|
157
|
+
*/
|
|
158
|
+
export const batchProgressLines = (previous, next) => {
|
|
159
|
+
const lines = [];
|
|
160
|
+
const previousStatusByOrder = new Map((previous?.images ?? []).map(image => [image.orderId, image.status]));
|
|
161
|
+
for (const image of next.images) {
|
|
162
|
+
const before = previousStatusByOrder.get(image.orderId);
|
|
163
|
+
if (before !== image.status) {
|
|
164
|
+
lines.push(`[batch] order=${image.orderId} stage=${stageLabelForOrderStatus(image.status)} (${image.status})`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const previousSummary = previous?.summary;
|
|
168
|
+
const done = next.summary.completed + next.summary.failed;
|
|
169
|
+
if (previousSummary == null ||
|
|
170
|
+
previousSummary.completed !== next.summary.completed ||
|
|
171
|
+
previousSummary.failed !== next.summary.failed) {
|
|
172
|
+
lines.push(`[batch] mesh ${done}/${next.summary.total} done`);
|
|
173
|
+
}
|
|
174
|
+
return lines;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Polls `getStatus` (expected to be `client.v1.getProductionAutomationStatus`,
|
|
178
|
+
* which already retries 429s in its own request layer) until every image in
|
|
179
|
+
* the batch reaches a terminal outcome, printing one line per stage
|
|
180
|
+
* transition via `onProgress`. Mirrors the deadline/backoff shape of
|
|
181
|
+
* `pollProductionStatus`/`watchProductionRun` in index.ts.
|
|
182
|
+
*/
|
|
183
|
+
export const pollAutomationBatch = async (options) => {
|
|
184
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
185
|
+
let previous;
|
|
186
|
+
for (;;) {
|
|
187
|
+
const status = await options.getStatus();
|
|
188
|
+
const lines = batchProgressLines(previous, status);
|
|
189
|
+
if (lines.length && options.onProgress)
|
|
190
|
+
options.onProgress(lines);
|
|
191
|
+
previous = status;
|
|
192
|
+
if (status.summary.completed + status.summary.failed >= status.summary.total) {
|
|
193
|
+
return status;
|
|
194
|
+
}
|
|
195
|
+
if (Date.now() >= deadline) {
|
|
196
|
+
throw new Error(`production run --wait timed out after ${options.timeoutMs}ms for batch=${status.batchId}`);
|
|
197
|
+
}
|
|
198
|
+
await options.sleep(Math.min(options.intervalMs, Math.max(0, deadline - Date.now())));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* The V6 compose fallback's mesh<->image pairing. Reads the automation
|
|
203
|
+
* batch's own `mesh.generate` executions (fetched via `listCanvasRuns` and
|
|
204
|
+
* filtered to the target order) for each part's produced mesh asset
|
|
205
|
+
* (`execution.outputs`, `mediaType === 'mesh'`) and the part image it was
|
|
206
|
+
* generated from (`execution.inputAssets`, `mediaType === 'image'`) — the
|
|
207
|
+
* same `{assetId, partImageAssetId}` shape `composer run --part
|
|
208
|
+
* <mesh>:<image>` accepts, so no manual pairing is needed.
|
|
209
|
+
*/
|
|
210
|
+
export const buildComposerPartsFromMeshExecutions = (executions, orderId) => {
|
|
211
|
+
const parts = [];
|
|
212
|
+
for (const execution of executions) {
|
|
213
|
+
if (execution.operation !== 'mesh.generate')
|
|
214
|
+
continue;
|
|
215
|
+
if (!execution.orderIds.includes(orderId))
|
|
216
|
+
continue;
|
|
217
|
+
const meshOutput = execution.outputs.find(output => output.mediaType === 'mesh');
|
|
218
|
+
if (!meshOutput)
|
|
219
|
+
continue;
|
|
220
|
+
const partImage = execution.inputAssets?.find(asset => asset.mediaType === 'image');
|
|
221
|
+
parts.push({
|
|
222
|
+
assetId: meshOutput.assetId,
|
|
223
|
+
...(partImage ? { partImageAssetId: partImage.assetId } : {}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return parts;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* The V6 compose fallback's `fullBodyImageAssetId`. Automation's own result
|
|
230
|
+
* does not return a durable asset id for the original image (only
|
|
231
|
+
* `orderId`/`projectId`/`url`/`runId`), so this reads it back off the part
|
|
232
|
+
* images' own `sourceAssetId` (each part image's own upstream source is the
|
|
233
|
+
* original full-body photo it was cropped from) — the same field
|
|
234
|
+
* `composerInputFromRun`'s mesh.generate part-field allowlist tracks.
|
|
235
|
+
*/
|
|
236
|
+
export const resolveFullBodyImageAssetId = (executions, orderId) => {
|
|
237
|
+
for (const execution of executions) {
|
|
238
|
+
if (execution.operation !== 'mesh.generate')
|
|
239
|
+
continue;
|
|
240
|
+
if (!execution.orderIds.includes(orderId))
|
|
241
|
+
continue;
|
|
242
|
+
const partImage = execution.inputAssets?.find(asset => asset.mediaType === 'image');
|
|
243
|
+
if (partImage?.sourceAssetId)
|
|
244
|
+
return partImage.sourceAssetId;
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Actionable next steps appended to (never replacing) a known
|
|
250
|
+
* `AssetHubApiError`'s own message. Matches on `error.code`/`error.details`
|
|
251
|
+
* first — `PART_IMAGE_REQUIRED` and `ASSET_WRONG_MEDIA_TYPE` are confirmed
|
|
252
|
+
* `/mesh/compose` error codes (assethub-web PR #8179) with a structured
|
|
253
|
+
* `details` payload. Falls back to matching on message content for a server
|
|
254
|
+
* that has not shipped those codes yet (e.g. today's deployed API, where
|
|
255
|
+
* the same failures still surface as a plain `404 ASSET_NOT_FOUND` or a
|
|
256
|
+
* generic validation message — "Asset not found" is quoted directly from
|
|
257
|
+
* the originally reported failure). `ASSET_NOT_FOUND` itself (asset does
|
|
258
|
+
* not exist / isn't yours) is left alone; it is not one of these cases.
|
|
259
|
+
*/
|
|
260
|
+
export const errorHintFor = (error) => {
|
|
261
|
+
if (error.code === 'PART_IMAGE_REQUIRED') {
|
|
262
|
+
const meshAssetId = error.details?.meshAssetId ?? '<mesh-asset-id>';
|
|
263
|
+
return `pass --part ${meshAssetId}:<part-image-asset-id>, or regenerate with mesh generate --derived-from-run <split-run-id>`;
|
|
264
|
+
}
|
|
265
|
+
if (error.code === 'ASSET_WRONG_MEDIA_TYPE') {
|
|
266
|
+
const { assetId, actualType, expectedType } = error.details ?? {};
|
|
267
|
+
if (expectedType === 'mesh' && actualType === 'image') {
|
|
268
|
+
return `asset ${assetId ?? '<asset-id>'} is an image; run mesh generate (or production run --image) first`;
|
|
269
|
+
}
|
|
270
|
+
return `asset ${assetId ?? '<asset-id>'} has the wrong type: expected ${expectedType ?? 'unknown'}, got ${actualType ?? 'unknown'}`;
|
|
271
|
+
}
|
|
272
|
+
const message = error.message.toLowerCase();
|
|
273
|
+
if (message.includes('asset not found') || message.includes('not found')) {
|
|
274
|
+
return 'compose expects a mesh part asset id, not an image asset — run `mesh generate` (or `production run --image <file|assetId>`) first, then compose the resulting mesh asset ids';
|
|
275
|
+
}
|
|
276
|
+
if (message.includes('part image') ||
|
|
277
|
+
message.includes('partimageassetid') ||
|
|
278
|
+
message.includes('full body image') ||
|
|
279
|
+
message.includes('fullbodyimageassetid')) {
|
|
280
|
+
return "pass `--part <mesh-asset-id>:<part-image-asset-id>` on `composer run`/`composer refine` to attach each part's source image (or use `production run --image ... --compose v6`, which pairs them automatically)";
|
|
281
|
+
}
|
|
282
|
+
return undefined;
|
|
283
|
+
};
|
|
284
|
+
/** Appends an error hint to `error.message` in place, if one applies. */
|
|
285
|
+
export const withErrorHint = (error) => {
|
|
286
|
+
const hint = errorHintFor(error);
|
|
287
|
+
if (hint)
|
|
288
|
+
error.message = `${error.message} (${hint})`;
|
|
289
|
+
return error;
|
|
290
|
+
};
|
|
291
|
+
/** Condensed, human-readable lines for `runs get --summary`. */
|
|
292
|
+
export const formatExecutionSummaryLines = (execution) => {
|
|
293
|
+
const lines = [
|
|
294
|
+
`runId: ${execution.runId}`,
|
|
295
|
+
`operation: ${execution.operation}`,
|
|
296
|
+
`status: ${execution.status}`,
|
|
297
|
+
`canvas: ${execution.canvas.id}`,
|
|
298
|
+
];
|
|
299
|
+
if (execution.orderIds.length)
|
|
300
|
+
lines.push(`orders: ${execution.orderIds.join(', ')}`);
|
|
301
|
+
lines.push(`credits: reserved=${execution.usage.reservedCredits ?? '-'} charged=${execution.usage.chargedCredits ?? '-'}`);
|
|
302
|
+
const output = execution.outputs[0];
|
|
303
|
+
lines.push(`output: ${output ? output.assetId : '-'}`);
|
|
304
|
+
if (execution.error)
|
|
305
|
+
lines.push(`error: ${execution.error.code}: ${execution.error.message}`);
|
|
306
|
+
return lines;
|
|
307
|
+
};
|
package/package.json
CHANGED
package/skills/assethub/SKILL.md
CHANGED
|
@@ -72,6 +72,26 @@ assethub api call "POST /mesh/compose" --input-json @request.json --operation-id
|
|
|
72
72
|
- `--operation-id <uuid>` makes a paid request idempotent. Reuse the **same** ID with the **same** input to retry; a new candidate needs a new ID.
|
|
73
73
|
- Generation returns `execution` with the canvas URL, run and job IDs, and the output asset IDs. Chain those IDs into the next step with `--source-id`.
|
|
74
74
|
|
|
75
|
+
### Fast path: image to a finished, composed asset
|
|
76
|
+
|
|
77
|
+
Turning one image into a finished 3D asset does **not** need manual `parts split`
|
|
78
|
+
→ `mesh generate` (once per part) → `composer run`. Use the one-shot command instead:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
assethub production run --image ./character.png --estimate
|
|
82
|
+
assethub production run --image ./character.png --wait --download --out-dir ./out/asset
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`--image` accepts a local file path or an existing asset id — it auto-detects
|
|
86
|
+
which. This wraps `production automation` (split, mesh generation per part, and
|
|
87
|
+
compose all happen server-side) plus waiting and downloads; it never leaves you
|
|
88
|
+
to pair mesh outputs with their source part images by hand. `--compose v6`
|
|
89
|
+
requests a Composer V6 pass explicitly (still pairs parts and images
|
|
90
|
+
automatically); `--compose none` stops after mesh generation. `--max-cost
|
|
91
|
+
<credits>` refuses to run at all once the estimate exceeds it. `runs get <run-id>
|
|
92
|
+
--summary` is the readable alternative to the full JSON dump for checking on a
|
|
93
|
+
long run.
|
|
94
|
+
|
|
75
95
|
### 5. Wait or recover — never resubmit blindly
|
|
76
96
|
|
|
77
97
|
```bash
|