@assethub/cli 0.1.27 → 0.1.29

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 CHANGED
@@ -276,6 +276,78 @@ 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
+ top-level `partComposerAgentVersion: "part_composer_v6_auto_assemble"`
309
+ (`config.autoCompose` is left at its default of `true`). A server that has shipped this (assethub-web PR
310
+ #8179) does the V6 compose itself, in the same automation call, pairing
311
+ each part's mesh with its source image automatically. Against an older,
312
+ currently-deployed server (detected by the automation response lacking
313
+ `estimatedCostBreakdown`), the CLI instead reads the batch's own
314
+ `mesh.generate` executions itself, pairs each produced mesh with the part
315
+ image it was generated from, and runs an explicit `composer run` — same
316
+ end result, either way you never pair mesh and image assets by hand.
317
+
318
+ `--mesh-model <id>` and `--max-parts <n>` (default 24, matching the server's
319
+ own conservative constant for this math) only affect `--estimate`'s math (mesh
320
+ unit cost x an assumed worst-case part count — the CLI cannot know the real
321
+ part count before splitting runs); they do not cap or select anything on the
322
+ real run. `--max-cost <credits>` refuses to run at all, before anything is
323
+ submitted, once the best-effort estimate exceeds it, and is also sent to the
324
+ server as `maxCostCredits` on the real run as a backstop. `--estimate`'s numbers
325
+ come only from side-effect-free catalog reads (mesh model credit cost, compose
326
+ quotes) shaped like the server's own `split`/`meshGeneration`/`compose`
327
+ breakdown; part-extraction/split has no dry-run price today, so that line is
328
+ reported as unknown rather than guessed. On a real (non-`--estimate`) run
329
+ against a server that has shipped #8179, the printed output additionally
330
+ includes the server's own `estimatedTotalCredits`/`estimatedCostBreakdown`
331
+ in place of the CLI's own preflight guess.
332
+
333
+ This depends on assethub-web PR #8179 for the `partComposerAgentVersion` field
334
+ and the richer cost breakdown; it is not merged/deployed as of this writing.
335
+ The CLI degrades gracefully against the currently-deployed API (see above).
336
+
337
+ `runs get <run-id> --summary` prints a handful of plain-text lines (status, stage,
338
+ credits, output asset, error) instead of the full JSON execution. `runs wait
339
+ <run-id>` is `runs watch` under a name that matches a long-running wait; both
340
+ accept `--interval-ms`/`--timeout-ms` and retry rate limits the same way every
341
+ other poll loop in the CLI does.
342
+
343
+ The one-shot command's stdout is always a single JSON object (`batchId`,
344
+ `estimate`, `batchStatus`, `compose`, `execution`, and `downloads` when
345
+ requested) — the same convention every other command in this CLI already
346
+ follows. `--json` is accepted for compatibility with that convention but has
347
+ no effect, since there is no alternate plain-text mode to opt out of (`runs
348
+ get --summary` is the one deliberate exception, and it is opt-in by its own
349
+ flag).
350
+
279
351
  `evaluate list` discovers server evaluators. Server evaluation dispatch is currently
280
352
  unavailable. Graph exports are bounded to 100 runs/evaluations and 1,000 nodes;
281
353
  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.
@@ -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,EAef,oBAAoB,EACrB,MAAM,sBAAsB,CAAA;AAc7B,OAAO,EAAC,kBAAkB,EAAc,MAAM,gCAAgC,CAAA;AAI9E,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,28cA+IjB,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;AA+tCD,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"}
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;AAqB9E,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;AA07CD,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, PART_COMPOSER_V6_AGENT_VERSION, 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,156 @@ 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
+ ...(compose === 'v6' ? { partComposerAgentVersion: PART_COMPOSER_V6_AGENT_VERSION } : {}),
3190
+ ...(maxCostCredits != null ? { maxCostCredits } : {}),
3191
+ };
3192
+ const queued = (await executeRecorded(session, 'production.automation', body, executionOptions(ctx.flags)));
3193
+ stderr.write(`[run] batch=${queued.batchId}\n`);
3194
+ // `estimatedCostBreakdown`'s presence on the automation response itself
3195
+ // (assethub-web #8179) is the proof that this server understood
3196
+ // `partComposerAgentVersion` and already ran Composer V6 server-side, in
3197
+ // the same call — no client-side fallback compose is needed. Its absence
3198
+ // means an older, currently-deployed server silently ignored that field,
3199
+ // so the CLI still has to pair parts and run compose itself below.
3200
+ const serverHandledV6Compose = compose === 'v6' &&
3201
+ serverSupportsV6Compose(queued);
3202
+ const needsFallbackCompose = compose === 'v6' && !serverHandledV6Compose;
3203
+ let batchStatus;
3204
+ if (hasFlag(ctx.flags, 'wait') || needsFallbackCompose) {
3205
+ batchStatus = await pollAutomationBatch({
3206
+ getStatus: () => ctx.client.v1.getProductionAutomationStatus(queued.batchId),
3207
+ sleep: ms => delay(ms),
3208
+ intervalMs: parsePositiveIntegerFlag(ctx.flags, 'interval-ms', 5000),
3209
+ timeoutMs: parsePositiveIntegerFlag(ctx.flags, 'timeout-ms', 1800000),
3210
+ onProgress: lines => lines.forEach(line => stderr.write(`${line}\n`)),
3211
+ });
3212
+ }
3213
+ let composeExecution;
3214
+ if (needsFallbackCompose) {
3215
+ const order = queued.images[0];
3216
+ if (!order)
3217
+ throw new Error('Automation did not return an order for the requested image.');
3218
+ if (batchStatus?.images.find(image => image.orderId === order.orderId)
3219
+ ?.status === 'failed')
3220
+ throw new Error(`Automation failed for order ${order.orderId}; there is nothing to compose.`);
3221
+ const runs = await ctx.client.v2.listCanvasRuns(session.canvas.id, { limit: 200 });
3222
+ const parts = buildComposerPartsFromMeshExecutions(runs.items, order.orderId);
3223
+ const fullBodyImageAssetId = resolveFullBodyImageAssetId(runs.items, order.orderId) ??
3224
+ (detected.kind === 'resourceId' ? detected.resourceId : undefined);
3225
+ if (parts.length === 0 || fullBodyImageAssetId == null) {
3226
+ throw withErrorHint(new Error('Could not find mesh part outputs (and the source image they were generated from) to compose for this order.'));
3227
+ }
3228
+ try {
3229
+ const composeQueued = await executeRecorded(session, 'mesh.compose', {
3230
+ parts,
3231
+ fullBodyImageAssetId,
3232
+ agentVersion: 'v6',
3233
+ }, executionOptions(ctx.flags));
3234
+ composeExecution = composeQueued.execution;
3235
+ }
3236
+ catch (error) {
3237
+ throw error instanceof Error ? withErrorHint(error) : error;
3238
+ }
3239
+ }
3240
+ // The automation receipt (`queued.execution`) is only the split stage's own
3241
+ // execution row; the real completion signal for the whole order (mesh gen
3242
+ // across every part, plus compose when the server ran it) is the batch
3243
+ // status polled above. Once that says the order is done, re-fetch the
3244
+ // execution so `--download`/the printed summary reflect the final state
3245
+ // rather than the split-stage snapshot from right after submission.
3246
+ const finalExecution = composeExecution ??
3247
+ (batchStatus != null
3248
+ ? await ctx.client.v2.getRun(queued.execution.runId)
3249
+ : queued.execution);
3250
+ // Prefer the server's own reported cost (#8179) over our preflight
3251
+ // best-effort `estimate` once we actually have it; absent on a server
3252
+ // that has not deployed #8179 yet, in which case only `estimate` (used
3253
+ // for the --max-cost gate above) is available.
3254
+ const serverCost = readServerCostBreakdown(queued);
3255
+ // The machine-readable summary this prints is always JSON, matching every
3256
+ // other command in this CLI; --json is accepted (it is a no-op) rather than
3257
+ // rejected, since there is no plain-text mode here to opt out of.
3258
+ print(await withOptionalDownloads(ctx.flags, sanitizeProductionOutput({
3259
+ batchId: queued.batchId,
3260
+ estimatedCostCredits: queued.estimatedCostCredits,
3261
+ ...(serverCost ?? {}),
3262
+ images: queued.images,
3263
+ estimate,
3264
+ batchStatus,
3265
+ compose: composeExecution,
3266
+ execution: finalExecution,
3267
+ })));
3268
+ process.exitCode = executionExitCode(finalExecution);
3269
+ };
3113
3270
  const commandProduction = async (subcommand, positionals, ctx) => {
3114
3271
  if (subcommand === 'agents') {
3115
3272
  print(sanitizeProductionOutput(await ctx.client.v1.getProductionAgents()));
@@ -3216,6 +3373,14 @@ const commandProduction = async (subcommand, positionals, ctx) => {
3216
3373
  return;
3217
3374
  }
3218
3375
  if (subcommand === 'run') {
3376
+ // `production run --image <file|assetId>` is a different command in
3377
+ // everything but name: the one-shot image -> finished-asset flow, not
3378
+ // confirmed-task mission execution. Intercept before `--order-id` is
3379
+ // required so the mission flow below is completely untouched when
3380
+ // --image is absent.
3381
+ if (getFlag(ctx.flags, 'image') != null) {
3382
+ return commandProductionRunOneShot(ctx);
3383
+ }
3219
3384
  const orderId = requireFlag(ctx.flags, 'order-id');
3220
3385
  const missionId = requireFlag(ctx.flags, 'mission-id');
3221
3386
  const runMode = parseRunModeFlag(ctx.flags);
@@ -3910,12 +4075,21 @@ const commandRuns = async (subcommand, positionals, ctx) => {
3910
4075
  print(await ctx.client.v2.listCanvasRuns(canvas.id, pageOptions(ctx.flags)));
3911
4076
  return;
3912
4077
  }
3913
- if (subcommand === 'get' || subcommand === 'watch') {
4078
+ if (subcommand === 'get' || subcommand === 'watch' || subcommand === 'wait') {
3914
4079
  const id = requirePositional(positionals, 2, 'run-id');
4080
+ // `wait` is `watch` under a name that matches how long-running waits are
4081
+ // asked for elsewhere in the CLI; both poll the same way (429s are
4082
+ // retried inside the client's own request layer, same as every other
4083
+ // poll loop in this file) and accept the same --interval-ms/--timeout-ms.
3915
4084
  const execution = subcommand === 'get'
3916
4085
  ? await ctx.client.v2.getRun(id)
3917
4086
  : await waitForExecution(ctx.client, id, watchOptions(ctx.flags));
3918
- print({ execution });
4087
+ if (subcommand === 'get' && hasFlag(ctx.flags, 'summary')) {
4088
+ stdout.write(`${formatExecutionSummaryLines(execution).join('\n')}\n`);
4089
+ }
4090
+ else {
4091
+ print({ execution });
4092
+ }
3919
4093
  process.exitCode = executionExitCode(execution);
3920
4094
  return;
3921
4095
  }
@@ -0,0 +1,188 @@
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`: leaves config unset. The version is a top-level automation
45
+ * request field, added by the caller; `autoCompose` defaults to `true`.
46
+ */
47
+ export declare const buildAutomationConfig: (compose: ComposeMode | undefined) => Record<string, unknown> | undefined;
48
+ /**
49
+ * `estimatedCostBreakdown` (added by #8179) only appears on a server new
50
+ * enough to also honor `partComposerAgentVersion` — its presence in the
51
+ * automation response is the signal that the server already did the V6
52
+ * compose server-side in that same call, so no client-side fallback compose
53
+ * is needed. Its absence means an older, currently-deployed server: it
54
+ * silently ignored `partComposerAgentVersion` and ran its own default
55
+ * compose (if any), so the caller must still run the explicit fallback.
56
+ */
57
+ export declare const serverSupportsV6Compose: (automationResult: Record<string, unknown>) => boolean;
58
+ export declare const DEFAULT_MAX_PARTS = 24;
59
+ export type CostLineItem = {
60
+ total: number | null;
61
+ note?: string;
62
+ };
63
+ export type OneShotCostBreakdown = {
64
+ split: CostLineItem & {
65
+ perImage: number | null;
66
+ };
67
+ meshGeneration: CostLineItem & {
68
+ perPart: number | null;
69
+ maxParts: number;
70
+ };
71
+ compose: CostLineItem & {
72
+ perLane: number | null;
73
+ lanesPerImage: number;
74
+ };
75
+ };
76
+ export type OneShotCostEstimate = {
77
+ breakdown: OneShotCostBreakdown;
78
+ totalCredits: number;
79
+ notes: string[];
80
+ };
81
+ /**
82
+ * Best-effort cost breakdown for `--estimate`, shaped like the server's own
83
+ * `estimatedCostBreakdown` (#8179: `split`/`meshGeneration`/`compose`, each
84
+ * with a per-unit price and a total). Nothing here calls `production
85
+ * automation`/`analyze` (that would start real, billable work); every
86
+ * number comes from a side-effect-free GET (`models` catalog, `mesh/compose`
87
+ * capabilities). Today's API has no dry-run cost for part extraction/split,
88
+ * so that line is reported as unknown rather than guessed. `maxParts`
89
+ * mirrors the server's own conservative constant for this math (24, per
90
+ * #8179) rather than a locally invented cap; `--max-parts` overrides it.
91
+ */
92
+ export declare const estimateOneShotCost: (options: {
93
+ meshModel: ModelSummary | undefined;
94
+ maxParts: number;
95
+ composeRequested: boolean;
96
+ composeCredits: number | null | undefined;
97
+ }) => OneShotCostEstimate;
98
+ /**
99
+ * Reads the server's own `estimatedTotalCredits`/`estimatedCostBreakdown`
100
+ * off a real (non-estimate) automation response, when present (#8179).
101
+ * These fields are not yet in `ProductionAutomationResult`'s shipped type,
102
+ * so this reads them optionally/untyped rather than guessing a schema;
103
+ * absent on a server that has not deployed #8179 yet.
104
+ */
105
+ export declare const readServerCostBreakdown: (automationResult: Record<string, unknown>) => {
106
+ estimatedTotalCredits: number;
107
+ estimatedCostBreakdown: unknown;
108
+ } | undefined;
109
+ /** Whether the estimated (or actual, server-reported) total would exceed `--max-cost`. */
110
+ export declare const exceedsMaxCost: (totalCredits: number, maxCostCredits: number | undefined) => boolean;
111
+ /**
112
+ * Human stage label for one image's `ProductionOrderStatus`. The API has no
113
+ * distinct "compose" status, so `ready`/`in_progress` are both reported as
114
+ * `mesh` (parts are being generated or are ready to be); there is no way to
115
+ * tell "meshing" apart from "composing" from this field alone.
116
+ */
117
+ export declare const stageLabelForOrderStatus: (status: ProductionOrderStatus) => string;
118
+ /**
119
+ * One line per per-image stage transition, plus a batch-level completion
120
+ * line when the completed/failed counts change. "mesh k/N" here means
121
+ * images processed out of the batch, not parts within one image — the batch
122
+ * status endpoint does not expose per-part progress.
123
+ */
124
+ export declare const batchProgressLines: (previous: ProductionAutomationBatchStatusResult | undefined, next: ProductionAutomationBatchStatusResult) => string[];
125
+ /**
126
+ * Polls `getStatus` (expected to be `client.v1.getProductionAutomationStatus`,
127
+ * which already retries 429s in its own request layer) until every image in
128
+ * the batch reaches a terminal outcome, printing one line per stage
129
+ * transition via `onProgress`. Mirrors the deadline/backoff shape of
130
+ * `pollProductionStatus`/`watchProductionRun` in index.ts.
131
+ */
132
+ export declare const pollAutomationBatch: (options: {
133
+ getStatus: () => Promise<ProductionAutomationBatchStatusResult>;
134
+ sleep: (ms: number) => Promise<void>;
135
+ intervalMs: number;
136
+ timeoutMs: number;
137
+ onProgress?: (lines: string[]) => void;
138
+ }) => Promise<ProductionAutomationBatchStatusResult>;
139
+ /**
140
+ * The V6 compose fallback's mesh<->image pairing. Reads the automation
141
+ * batch's own `mesh.generate` executions (fetched via `listCanvasRuns` and
142
+ * filtered to the target order) for each part's produced mesh asset
143
+ * (`execution.outputs`, `mediaType === 'mesh'`) and the part image it was
144
+ * generated from (`execution.inputAssets`, `mediaType === 'image'`) — the
145
+ * same `{assetId, partImageAssetId}` shape `composer run --part
146
+ * <mesh>:<image>` accepts, so no manual pairing is needed.
147
+ */
148
+ export declare const buildComposerPartsFromMeshExecutions: (executions: CanvasExecution[], orderId: string) => MeshComposerPart[];
149
+ /**
150
+ * The V6 compose fallback's `fullBodyImageAssetId`. Automation's own result
151
+ * does not return a durable asset id for the original image (only
152
+ * `orderId`/`projectId`/`url`/`runId`), so this reads it back off the part
153
+ * images' own `sourceAssetId` (each part image's own upstream source is the
154
+ * original full-body photo it was cropped from) — the same field
155
+ * `composerInputFromRun`'s mesh.generate part-field allowlist tracks.
156
+ */
157
+ export declare const resolveFullBodyImageAssetId: (executions: CanvasExecution[], orderId: string) => string | undefined;
158
+ export type KnownErrorLike = {
159
+ status?: number;
160
+ code?: string;
161
+ message: string;
162
+ details?: {
163
+ meshAssetId?: string;
164
+ assetId?: string;
165
+ actualType?: string;
166
+ expectedType?: string;
167
+ };
168
+ };
169
+ /**
170
+ * Actionable next steps appended to (never replacing) a known
171
+ * `AssetHubApiError`'s own message. Matches on `error.code`/`error.details`
172
+ * first — `PART_IMAGE_REQUIRED` and `ASSET_WRONG_MEDIA_TYPE` are confirmed
173
+ * `/mesh/compose` error codes (assethub-web PR #8179) with a structured
174
+ * `details` payload. Falls back to matching on message content for a server
175
+ * that has not shipped those codes yet (e.g. today's deployed API, where
176
+ * the same failures still surface as a plain `404 ASSET_NOT_FOUND` or a
177
+ * generic validation message — "Asset not found" is quoted directly from
178
+ * the originally reported failure). `ASSET_NOT_FOUND` itself (asset does
179
+ * not exist / isn't yours) is left alone; it is not one of these cases.
180
+ */
181
+ export declare const errorHintFor: (error: KnownErrorLike) => string | undefined;
182
+ /** Appends an error hint to `error.message` in place, if one applies. */
183
+ export declare const withErrorHint: <T extends Error & {
184
+ message: string;
185
+ }>(error: T) => T;
186
+ /** Condensed, human-readable lines for `runs get --summary`. */
187
+ export declare const formatExecutionSummaryLines: (execution: CanvasExecution) => string[];
188
+ //# 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;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,WAAW,GAAG,SAAS,KAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAE5B,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,296 @@
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`: leaves config unset. The version is a top-level automation
29
+ * request field, added by the caller; `autoCompose` defaults to `true`.
30
+ */
31
+ export const buildAutomationConfig = (compose) => {
32
+ return compose === 'none' ? { autoCompose: false } : undefined;
33
+ };
34
+ /**
35
+ * `estimatedCostBreakdown` (added by #8179) only appears on a server new
36
+ * enough to also honor `partComposerAgentVersion` — its presence in the
37
+ * automation response is the signal that the server already did the V6
38
+ * compose server-side in that same call, so no client-side fallback compose
39
+ * is needed. Its absence means an older, currently-deployed server: it
40
+ * silently ignored `partComposerAgentVersion` and ran its own default
41
+ * compose (if any), so the caller must still run the explicit fallback.
42
+ */
43
+ export const serverSupportsV6Compose = (automationResult) => automationResult.estimatedCostBreakdown != null;
44
+ export const DEFAULT_MAX_PARTS = 24;
45
+ /**
46
+ * Best-effort cost breakdown for `--estimate`, shaped like the server's own
47
+ * `estimatedCostBreakdown` (#8179: `split`/`meshGeneration`/`compose`, each
48
+ * with a per-unit price and a total). Nothing here calls `production
49
+ * automation`/`analyze` (that would start real, billable work); every
50
+ * number comes from a side-effect-free GET (`models` catalog, `mesh/compose`
51
+ * capabilities). Today's API has no dry-run cost for part extraction/split,
52
+ * so that line is reported as unknown rather than guessed. `maxParts`
53
+ * mirrors the server's own conservative constant for this math (24, per
54
+ * #8179) rather than a locally invented cap; `--max-parts` overrides it.
55
+ */
56
+ export const estimateOneShotCost = (options) => {
57
+ const notes = [];
58
+ const split = {
59
+ perImage: null,
60
+ total: null,
61
+ 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`)',
62
+ };
63
+ notes.push(split.note);
64
+ const perPart = options.meshModel?.creditCost ?? options.meshModel?.defaultCreditCost ?? null;
65
+ const meshTotal = perPart == null ? null : perPart * options.maxParts;
66
+ const meshGeneration = {
67
+ perPart,
68
+ maxParts: options.maxParts,
69
+ total: meshTotal,
70
+ ...(perPart == null
71
+ ? {
72
+ note: options.meshModel
73
+ ? `mesh generation cost not included: no credit price found for model "${options.meshModel.id}"`
74
+ : 'mesh generation cost not included: mesh model was not found in the catalog',
75
+ }
76
+ : {
77
+ 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`,
78
+ }),
79
+ };
80
+ notes.push(meshGeneration.note);
81
+ const lanesPerImage = 1;
82
+ const compose = options.composeRequested
83
+ ? options.composeCredits == null
84
+ ? {
85
+ perLane: null,
86
+ lanesPerImage,
87
+ total: null,
88
+ note: 'compose cost not included: no quote found for the requested compose model',
89
+ }
90
+ : { perLane: options.composeCredits, lanesPerImage, total: options.composeCredits * lanesPerImage }
91
+ : {
92
+ perLane: null,
93
+ lanesPerImage,
94
+ total: 0,
95
+ note: 'compose disabled by --compose none',
96
+ };
97
+ if (compose.note)
98
+ notes.push(compose.note);
99
+ const totalCredits = (meshGeneration.total ?? 0) + (compose.total ?? 0);
100
+ return { breakdown: { split, meshGeneration, compose }, totalCredits, notes };
101
+ };
102
+ /**
103
+ * Reads the server's own `estimatedTotalCredits`/`estimatedCostBreakdown`
104
+ * off a real (non-estimate) automation response, when present (#8179).
105
+ * These fields are not yet in `ProductionAutomationResult`'s shipped type,
106
+ * so this reads them optionally/untyped rather than guessing a schema;
107
+ * absent on a server that has not deployed #8179 yet.
108
+ */
109
+ export const readServerCostBreakdown = (automationResult) => {
110
+ const total = automationResult.estimatedTotalCredits;
111
+ const breakdown = automationResult.estimatedCostBreakdown;
112
+ if (typeof total !== 'number' || breakdown == null)
113
+ return undefined;
114
+ return { estimatedTotalCredits: total, estimatedCostBreakdown: breakdown };
115
+ };
116
+ /** Whether the estimated (or actual, server-reported) total would exceed `--max-cost`. */
117
+ export const exceedsMaxCost = (totalCredits, maxCostCredits) => maxCostCredits != null && totalCredits > maxCostCredits;
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 const stageLabelForOrderStatus = (status) => {
125
+ switch (status) {
126
+ case 'draft':
127
+ return 'queued';
128
+ case 'analyzing':
129
+ return 'split';
130
+ case 'ready':
131
+ case 'in_progress':
132
+ return 'mesh';
133
+ case 'completed':
134
+ return 'done';
135
+ case 'failed':
136
+ return 'failed';
137
+ default:
138
+ return status;
139
+ }
140
+ };
141
+ /**
142
+ * One line per per-image stage transition, plus a batch-level completion
143
+ * line when the completed/failed counts change. "mesh k/N" here means
144
+ * images processed out of the batch, not parts within one image — the batch
145
+ * status endpoint does not expose per-part progress.
146
+ */
147
+ export const batchProgressLines = (previous, next) => {
148
+ const lines = [];
149
+ const previousStatusByOrder = new Map((previous?.images ?? []).map(image => [image.orderId, image.status]));
150
+ for (const image of next.images) {
151
+ const before = previousStatusByOrder.get(image.orderId);
152
+ if (before !== image.status) {
153
+ lines.push(`[batch] order=${image.orderId} stage=${stageLabelForOrderStatus(image.status)} (${image.status})`);
154
+ }
155
+ }
156
+ const previousSummary = previous?.summary;
157
+ const done = next.summary.completed + next.summary.failed;
158
+ if (previousSummary == null ||
159
+ previousSummary.completed !== next.summary.completed ||
160
+ previousSummary.failed !== next.summary.failed) {
161
+ lines.push(`[batch] mesh ${done}/${next.summary.total} done`);
162
+ }
163
+ return lines;
164
+ };
165
+ /**
166
+ * Polls `getStatus` (expected to be `client.v1.getProductionAutomationStatus`,
167
+ * which already retries 429s in its own request layer) until every image in
168
+ * the batch reaches a terminal outcome, printing one line per stage
169
+ * transition via `onProgress`. Mirrors the deadline/backoff shape of
170
+ * `pollProductionStatus`/`watchProductionRun` in index.ts.
171
+ */
172
+ export const pollAutomationBatch = async (options) => {
173
+ const deadline = Date.now() + options.timeoutMs;
174
+ let previous;
175
+ for (;;) {
176
+ const status = await options.getStatus();
177
+ const lines = batchProgressLines(previous, status);
178
+ if (lines.length && options.onProgress)
179
+ options.onProgress(lines);
180
+ previous = status;
181
+ if (status.summary.completed + status.summary.failed >= status.summary.total) {
182
+ return status;
183
+ }
184
+ if (Date.now() >= deadline) {
185
+ throw new Error(`production run --wait timed out after ${options.timeoutMs}ms for batch=${status.batchId}`);
186
+ }
187
+ await options.sleep(Math.min(options.intervalMs, Math.max(0, deadline - Date.now())));
188
+ }
189
+ };
190
+ /**
191
+ * The V6 compose fallback's mesh<->image pairing. Reads the automation
192
+ * batch's own `mesh.generate` executions (fetched via `listCanvasRuns` and
193
+ * filtered to the target order) for each part's produced mesh asset
194
+ * (`execution.outputs`, `mediaType === 'mesh'`) and the part image it was
195
+ * generated from (`execution.inputAssets`, `mediaType === 'image'`) — the
196
+ * same `{assetId, partImageAssetId}` shape `composer run --part
197
+ * <mesh>:<image>` accepts, so no manual pairing is needed.
198
+ */
199
+ export const buildComposerPartsFromMeshExecutions = (executions, orderId) => {
200
+ const parts = [];
201
+ for (const execution of executions) {
202
+ if (execution.operation !== 'mesh.generate')
203
+ continue;
204
+ if (!execution.orderIds.includes(orderId))
205
+ continue;
206
+ const meshOutput = execution.outputs.find(output => output.mediaType === 'mesh');
207
+ if (!meshOutput)
208
+ continue;
209
+ const partImage = execution.inputAssets?.find(asset => asset.mediaType === 'image');
210
+ parts.push({
211
+ assetId: meshOutput.assetId,
212
+ ...(partImage ? { partImageAssetId: partImage.assetId } : {}),
213
+ });
214
+ }
215
+ return parts;
216
+ };
217
+ /**
218
+ * The V6 compose fallback's `fullBodyImageAssetId`. Automation's own result
219
+ * does not return a durable asset id for the original image (only
220
+ * `orderId`/`projectId`/`url`/`runId`), so this reads it back off the part
221
+ * images' own `sourceAssetId` (each part image's own upstream source is the
222
+ * original full-body photo it was cropped from) — the same field
223
+ * `composerInputFromRun`'s mesh.generate part-field allowlist tracks.
224
+ */
225
+ export const resolveFullBodyImageAssetId = (executions, orderId) => {
226
+ for (const execution of executions) {
227
+ if (execution.operation !== 'mesh.generate')
228
+ continue;
229
+ if (!execution.orderIds.includes(orderId))
230
+ continue;
231
+ const partImage = execution.inputAssets?.find(asset => asset.mediaType === 'image');
232
+ if (partImage?.sourceAssetId)
233
+ return partImage.sourceAssetId;
234
+ }
235
+ return undefined;
236
+ };
237
+ /**
238
+ * Actionable next steps appended to (never replacing) a known
239
+ * `AssetHubApiError`'s own message. Matches on `error.code`/`error.details`
240
+ * first — `PART_IMAGE_REQUIRED` and `ASSET_WRONG_MEDIA_TYPE` are confirmed
241
+ * `/mesh/compose` error codes (assethub-web PR #8179) with a structured
242
+ * `details` payload. Falls back to matching on message content for a server
243
+ * that has not shipped those codes yet (e.g. today's deployed API, where
244
+ * the same failures still surface as a plain `404 ASSET_NOT_FOUND` or a
245
+ * generic validation message — "Asset not found" is quoted directly from
246
+ * the originally reported failure). `ASSET_NOT_FOUND` itself (asset does
247
+ * not exist / isn't yours) is left alone; it is not one of these cases.
248
+ */
249
+ export const errorHintFor = (error) => {
250
+ if (error.code === 'PART_IMAGE_REQUIRED') {
251
+ const meshAssetId = error.details?.meshAssetId ?? '<mesh-asset-id>';
252
+ return `pass --part ${meshAssetId}:<part-image-asset-id>, or regenerate with mesh generate --derived-from-run <split-run-id>`;
253
+ }
254
+ if (error.code === 'ASSET_WRONG_MEDIA_TYPE') {
255
+ const { assetId, actualType, expectedType } = error.details ?? {};
256
+ if (expectedType === 'mesh' && actualType === 'image') {
257
+ return `asset ${assetId ?? '<asset-id>'} is an image; run mesh generate (or production run --image) first`;
258
+ }
259
+ return `asset ${assetId ?? '<asset-id>'} has the wrong type: expected ${expectedType ?? 'unknown'}, got ${actualType ?? 'unknown'}`;
260
+ }
261
+ const message = error.message.toLowerCase();
262
+ if (message.includes('asset not found') || message.includes('not found')) {
263
+ 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';
264
+ }
265
+ if (message.includes('part image') ||
266
+ message.includes('partimageassetid') ||
267
+ message.includes('full body image') ||
268
+ message.includes('fullbodyimageassetid')) {
269
+ 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)";
270
+ }
271
+ return undefined;
272
+ };
273
+ /** Appends an error hint to `error.message` in place, if one applies. */
274
+ export const withErrorHint = (error) => {
275
+ const hint = errorHintFor(error);
276
+ if (hint)
277
+ error.message = `${error.message} (${hint})`;
278
+ return error;
279
+ };
280
+ /** Condensed, human-readable lines for `runs get --summary`. */
281
+ export const formatExecutionSummaryLines = (execution) => {
282
+ const lines = [
283
+ `runId: ${execution.runId}`,
284
+ `operation: ${execution.operation}`,
285
+ `status: ${execution.status}`,
286
+ `canvas: ${execution.canvas.id}`,
287
+ ];
288
+ if (execution.orderIds.length)
289
+ lines.push(`orders: ${execution.orderIds.join(', ')}`);
290
+ lines.push(`credits: reserved=${execution.usage.reservedCredits ?? '-'} charged=${execution.usage.chargedCredits ?? '-'}`);
291
+ const output = execution.outputs[0];
292
+ lines.push(`output: ${output ? output.assetId : '-'}`);
293
+ if (execution.error)
294
+ lines.push(`error: ${execution.error.code}: ${execution.error.message}`);
295
+ return lines;
296
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assethub/cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Command line interface for AssetHub.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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