@clickraft/cli 0.3.0 → 0.4.0

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/CHANGELOG.md CHANGED
@@ -1,8 +1,9 @@
1
- ## [0.3.0](https://github.com/clickraft/cli/compare/v0.2.0...v0.3.0) (2026-05-24)
1
+ ## [0.4.0](https://github.com/clickraft/cli/compare/v0.3.0...v0.4.0) (2026-05-24)
2
2
 
3
3
  ### Features
4
4
 
5
- * **commands:** add template list, get, and find commands ([b2f5c5c](https://github.com/clickraft/cli/commit/b2f5c5c2e501f8ea5c6586476d121b97ce97f4ba))
5
+ * **errors:** add brand model error codes and surface error details ([98ee9f4](https://github.com/clickraft/cli/commit/98ee9f415a85cf31e38152092655accbe9acef1c))
6
+ * **generate:** make --brand-model repeatable with pose support ([872209c](https://github.com/clickraft/cli/commit/872209cfab22f7cd31e2a7655fa072c437a78351))
6
7
 
7
8
  # Changelog
8
9
 
package/README.md CHANGED
@@ -24,6 +24,25 @@ clickraft template get <template-id>
24
24
  clickraft template find "coffee brand"
25
25
  ```
26
26
 
27
+ ### Brand Model references
28
+
29
+ Use `--brand-model` to attach trained brand models to a generation. The flag is repeatable (max 3) and accepts an optional pose:
30
+
31
+ ```bash
32
+ # Single brand model, primary image
33
+ clickraft generate create --model-slug nano-banana-2 \
34
+ --prompt "product on marble" \
35
+ --brand-model a2438521-abcd-4000-8000-000000000001
36
+
37
+ # Two brand models with pose selection
38
+ clickraft generate create --model-slug nano-banana-2 \
39
+ --prompt "lifestyle shot" \
40
+ --brand-model a2438521-abcd-4000-8000-000000000001:front \
41
+ --brand-model b3549632-bcde-4000-8000-000000000002:3/4-right
42
+ ```
43
+
44
+ > **Note:** `--brand-model` only works with reference-supporting models (nano-banana-2 recommended).
45
+
27
46
  Full documentation: [docs.clickraft.ai/cli](https://docs.clickraft.ai/cli) _(coming soon)_
28
47
 
29
48
  ## Connecting to staging
package/dist/cli.js CHANGED
@@ -285,6 +285,55 @@ var EXIT_CODE_MAP = {
285
285
  retryable: false,
286
286
  hint: "No such profile. Use `clickraft auth profiles` to list available profiles."
287
287
  },
288
+ // --- 8 brand model resolution codes -----------------------------------------
289
+ BRAND_MODEL_NOT_FOUND: {
290
+ exitCode: 1,
291
+ category: "runtime",
292
+ retryable: false,
293
+ hint: "No brand model with that ID. Run `clickraft brand-model list`."
294
+ },
295
+ BRAND_MODEL_NOT_A_MODEL: {
296
+ exitCode: 1,
297
+ category: "runtime",
298
+ retryable: false,
299
+ hint: "The specified asset is not a brand model."
300
+ },
301
+ BRAND_MODEL_NOT_ACTIVE: {
302
+ exitCode: 1,
303
+ category: "runtime",
304
+ retryable: false,
305
+ hint: "Brand model is not active. Check its status in the dashboard."
306
+ },
307
+ BRAND_MODEL_NO_REFERENCES: {
308
+ exitCode: 1,
309
+ category: "runtime",
310
+ retryable: false,
311
+ hint: "Brand model has no reference images. Upload references first."
312
+ },
313
+ BRAND_MODEL_POSE_NOT_FOUND: {
314
+ exitCode: 1,
315
+ category: "runtime",
316
+ retryable: false,
317
+ hint: "Requested pose not found. Valid: front, 3/4-right, right, left, 3/4-left, back, approved."
318
+ },
319
+ BRAND_MODEL_DUPLICATE_ID: {
320
+ exitCode: 2,
321
+ category: "usage",
322
+ retryable: false,
323
+ hint: "Same brand model ID appears more than once."
324
+ },
325
+ MODEL_DOES_NOT_SUPPORT_REFERENCES: {
326
+ exitCode: 2,
327
+ category: "usage",
328
+ retryable: false,
329
+ hint: "This model does not support brand model references."
330
+ },
331
+ REFERENCE_LIMIT_EXCEEDED: {
332
+ exitCode: 2,
333
+ category: "usage",
334
+ retryable: false,
335
+ hint: "Too many reference images. Check model limits."
336
+ },
288
337
  // --- 4 CLI-synthetic codes --------------------------------------------------
289
338
  NETWORK_ERROR: {
290
339
  exitCode: 6,
@@ -697,7 +746,8 @@ function apiErrorFromServerBody(args) {
697
746
  requestId: error.correlationId ?? args.requestId,
698
747
  retryable,
699
748
  retryAfterMs,
700
- redactedBody: args.redactedBody
749
+ redactedBody: args.redactedBody,
750
+ details: error.details
701
751
  });
702
752
  }
703
753
  function parseRetryAfterHeader(headerValue) {
@@ -826,7 +876,8 @@ function errorToEnvelopePayload(err) {
826
876
  message: err.message,
827
877
  retryable: err.meta.retryable,
828
878
  retryAfterMs: err.meta.retryAfterMs,
829
- requestId: err.meta.requestId
879
+ requestId: err.meta.requestId,
880
+ details: err.meta.details
830
881
  });
831
882
  }
832
883
  if (err instanceof AuthError || err instanceof CredentialsError) {
@@ -855,6 +906,7 @@ function buildPayload(args) {
855
906
  if (typeof args.retryAfterMs === "number") payload.retry_after_ms = args.retryAfterMs;
856
907
  if (args.requestId) payload.request_id = args.requestId;
857
908
  if (mapping?.hint) payload.hint = mapping.hint;
909
+ if (args.details != null) payload.details = args.details;
858
910
  return payload;
859
911
  }
860
912
 
@@ -1736,7 +1788,15 @@ function buildRequestBody(args) {
1736
1788
  if (args.aspectRatio !== void 0) input.aspectRatio = args.aspectRatio;
1737
1789
  if (args.resolution !== void 0) input.resolution = args.resolution;
1738
1790
  if (args.durationSeconds !== void 0) input.durationSeconds = args.durationSeconds;
1739
- if (args.brandModel !== void 0) input.providerParams = { brandModelId: args.brandModel };
1791
+ if (args.brandModels !== void 0 && args.brandModels.length > 0) {
1792
+ input.providerParams = {
1793
+ brandModels: args.brandModels.map((spec) => {
1794
+ const entry = { id: spec.id };
1795
+ if (spec.imageType) entry.imageType = spec.imageType;
1796
+ return entry;
1797
+ })
1798
+ };
1799
+ }
1740
1800
  const body = {
1741
1801
  modelSlug: args.modelSlug,
1742
1802
  input
@@ -1773,6 +1833,152 @@ function projectCreateToResult(response, modelSlug) {
1773
1833
  };
1774
1834
  }
1775
1835
 
1836
+ // src/cli/args.ts
1837
+ var ArgError = class extends Error {
1838
+ constructor(message) {
1839
+ super(message);
1840
+ this.name = "ArgError";
1841
+ }
1842
+ };
1843
+ function parseArgs(argv, spec) {
1844
+ const result = {
1845
+ positional: [],
1846
+ string: {},
1847
+ boolean: {},
1848
+ array: {}
1849
+ };
1850
+ const stringFlags = new Set(spec.string ?? []);
1851
+ const booleanFlags = new Set(spec.boolean ?? []);
1852
+ const arrayFlags = new Set(spec.array ?? []);
1853
+ const aliases = spec.aliases ?? {};
1854
+ let i = 0;
1855
+ let stopFlags = false;
1856
+ while (i < argv.length) {
1857
+ const raw = argv[i];
1858
+ if (raw === void 0) {
1859
+ i += 1;
1860
+ continue;
1861
+ }
1862
+ if (stopFlags) {
1863
+ result.positional.push(raw);
1864
+ i += 1;
1865
+ continue;
1866
+ }
1867
+ if (raw === "--") {
1868
+ stopFlags = true;
1869
+ i += 1;
1870
+ continue;
1871
+ }
1872
+ if (!raw.startsWith("--")) {
1873
+ result.positional.push(raw);
1874
+ i += 1;
1875
+ continue;
1876
+ }
1877
+ const eqIdx = raw.indexOf("=");
1878
+ const flagToken = eqIdx >= 0 ? raw.slice(2, eqIdx) : raw.slice(2);
1879
+ const inlineValue = eqIdx >= 0 ? raw.slice(eqIdx + 1) : void 0;
1880
+ const canonical = resolveCanonical(flagToken, { booleanFlags, aliases });
1881
+ if (booleanFlags.has(canonical)) {
1882
+ if (inlineValue !== void 0) {
1883
+ result.boolean[canonical] = parseBool(inlineValue);
1884
+ } else if (flagToken.startsWith("no-") && booleanFlags.has(flagToken.slice(3))) {
1885
+ result.boolean[flagToken.slice(3)] = false;
1886
+ } else {
1887
+ result.boolean[canonical] = true;
1888
+ }
1889
+ i += 1;
1890
+ continue;
1891
+ }
1892
+ if (stringFlags.has(canonical) || arrayFlags.has(canonical)) {
1893
+ let value;
1894
+ if (inlineValue !== void 0) {
1895
+ value = inlineValue;
1896
+ } else {
1897
+ const next = argv[i + 1];
1898
+ if (next === void 0 || next.startsWith("--")) {
1899
+ throw new ArgError(`Flag --${canonical} requires a value.`);
1900
+ }
1901
+ value = next;
1902
+ i += 1;
1903
+ }
1904
+ if (arrayFlags.has(canonical)) {
1905
+ const existing = result.array[canonical] ?? [];
1906
+ existing.push(value);
1907
+ result.array[canonical] = existing;
1908
+ } else {
1909
+ result.string[canonical] = value;
1910
+ }
1911
+ i += 1;
1912
+ continue;
1913
+ }
1914
+ throw new ArgError(`Unknown flag --${flagToken}`);
1915
+ }
1916
+ return result;
1917
+ }
1918
+ function resolveCanonical(flagToken, ctx) {
1919
+ if (ctx.aliases[flagToken]) return ctx.aliases[flagToken];
1920
+ if (flagToken.startsWith("no-") && ctx.booleanFlags.has(flagToken.slice(3))) {
1921
+ return flagToken.slice(3);
1922
+ }
1923
+ return flagToken;
1924
+ }
1925
+ function parseBool(value) {
1926
+ if (value === "true" || value === "1") return true;
1927
+ if (value === "false" || value === "0") return false;
1928
+ throw new ArgError(`Expected boolean value (true/false), got "${value}".`);
1929
+ }
1930
+
1931
+ // src/commands/generate/parse-brand-model-spec.ts
1932
+ var VALID_POSES = [
1933
+ "front",
1934
+ "3/4-right",
1935
+ "right",
1936
+ "left",
1937
+ "3/4-left",
1938
+ "back",
1939
+ "approved"
1940
+ ];
1941
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1942
+ function parseBrandModelSpec(raw) {
1943
+ if (!raw) throw new ArgError("--brand-model value cannot be empty.");
1944
+ const colonIdx = raw.indexOf(":");
1945
+ const uuid = colonIdx >= 0 ? raw.slice(0, colonIdx) : raw;
1946
+ const poseRaw = colonIdx >= 0 ? raw.slice(colonIdx + 1) : void 0;
1947
+ if (!UUID_RE.test(uuid)) {
1948
+ throw new ArgError(
1949
+ `Invalid brand model ID: '${uuid}'. Expected a UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).`
1950
+ );
1951
+ }
1952
+ if (poseRaw !== void 0) {
1953
+ if (!poseRaw) {
1954
+ throw new ArgError(
1955
+ `Empty pose for brand model ${uuid}. Valid poses: ${VALID_POSES.join(", ")}.`
1956
+ );
1957
+ }
1958
+ if (!VALID_POSES.includes(poseRaw)) {
1959
+ throw new ArgError(
1960
+ `Unknown pose '${poseRaw}' for brand model ${uuid}. Valid poses: ${VALID_POSES.join(", ")}.`
1961
+ );
1962
+ }
1963
+ return { id: uuid.toLowerCase(), imageType: poseRaw };
1964
+ }
1965
+ return { id: uuid.toLowerCase() };
1966
+ }
1967
+ function validateBrandModelSpecs(specs) {
1968
+ if (specs.length > 3) {
1969
+ throw new ArgError(`Too many --brand-model flags (${specs.length}). Maximum is 3.`);
1970
+ }
1971
+ const seen = /* @__PURE__ */ new Set();
1972
+ for (const spec of specs) {
1973
+ if (seen.has(spec.id)) {
1974
+ throw new ArgError(
1975
+ `Duplicate brand model ID: ${spec.id}. Each --brand-model must reference a different model.`
1976
+ );
1977
+ }
1978
+ seen.add(spec.id);
1979
+ }
1980
+ }
1981
+
1776
1982
  // src/auth/device-flow.ts
1777
1983
  import { setTimeout as delay } from "timers/promises";
1778
1984
  import { request } from "undici";
@@ -2443,23 +2649,33 @@ async function listTokens(options) {
2443
2649
  var FALLBACK_HINT = "See `clickraft --help` or contact support.";
2444
2650
  function formatHumanError(err) {
2445
2651
  const view = errorToView(err);
2446
- return [
2652
+ const lines = [
2447
2653
  `Error: ${view.message}`,
2448
2654
  `Code: ${view.code}`,
2449
2655
  `Try: ${view.hint}`,
2450
2656
  `Request ID: ${view.requestId ?? "n/a"}`
2451
- ].join("\n");
2657
+ ];
2658
+ if (view.details) lines.push(`Details: ${view.details}`);
2659
+ return lines.join("\n");
2452
2660
  }
2453
2661
  function formatJsonError(err, ctx) {
2454
2662
  return JSON.stringify(wrapError(err, ctx));
2455
2663
  }
2664
+ function formatDetails(details) {
2665
+ if (details == null) return void 0;
2666
+ if (typeof details !== "object") return String(details);
2667
+ const entries = Object.entries(details);
2668
+ if (entries.length === 0) return void 0;
2669
+ return entries.map(([k, v]) => `${k}=${v}`).join(", ");
2670
+ }
2456
2671
  function errorToView(err) {
2457
2672
  if (err instanceof ApiError) {
2458
2673
  return {
2459
2674
  code: toEnvelopeCode(err.code),
2460
2675
  message: err.message,
2461
2676
  hint: EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
2462
- requestId: err.meta.requestId
2677
+ requestId: err.meta.requestId,
2678
+ details: formatDetails(err.meta.details)
2463
2679
  };
2464
2680
  }
2465
2681
  if (err instanceof AuthError || err instanceof CredentialsError) {
@@ -2477,101 +2693,6 @@ function errorToView(err) {
2477
2693
  };
2478
2694
  }
2479
2695
 
2480
- // src/cli/args.ts
2481
- var ArgError = class extends Error {
2482
- constructor(message) {
2483
- super(message);
2484
- this.name = "ArgError";
2485
- }
2486
- };
2487
- function parseArgs(argv, spec) {
2488
- const result = {
2489
- positional: [],
2490
- string: {},
2491
- boolean: {},
2492
- array: {}
2493
- };
2494
- const stringFlags = new Set(spec.string ?? []);
2495
- const booleanFlags = new Set(spec.boolean ?? []);
2496
- const arrayFlags = new Set(spec.array ?? []);
2497
- const aliases = spec.aliases ?? {};
2498
- let i = 0;
2499
- let stopFlags = false;
2500
- while (i < argv.length) {
2501
- const raw = argv[i];
2502
- if (raw === void 0) {
2503
- i += 1;
2504
- continue;
2505
- }
2506
- if (stopFlags) {
2507
- result.positional.push(raw);
2508
- i += 1;
2509
- continue;
2510
- }
2511
- if (raw === "--") {
2512
- stopFlags = true;
2513
- i += 1;
2514
- continue;
2515
- }
2516
- if (!raw.startsWith("--")) {
2517
- result.positional.push(raw);
2518
- i += 1;
2519
- continue;
2520
- }
2521
- const eqIdx = raw.indexOf("=");
2522
- const flagToken = eqIdx >= 0 ? raw.slice(2, eqIdx) : raw.slice(2);
2523
- const inlineValue = eqIdx >= 0 ? raw.slice(eqIdx + 1) : void 0;
2524
- const canonical = resolveCanonical(flagToken, { booleanFlags, aliases });
2525
- if (booleanFlags.has(canonical)) {
2526
- if (inlineValue !== void 0) {
2527
- result.boolean[canonical] = parseBool(inlineValue);
2528
- } else if (flagToken.startsWith("no-") && booleanFlags.has(flagToken.slice(3))) {
2529
- result.boolean[flagToken.slice(3)] = false;
2530
- } else {
2531
- result.boolean[canonical] = true;
2532
- }
2533
- i += 1;
2534
- continue;
2535
- }
2536
- if (stringFlags.has(canonical) || arrayFlags.has(canonical)) {
2537
- let value;
2538
- if (inlineValue !== void 0) {
2539
- value = inlineValue;
2540
- } else {
2541
- const next = argv[i + 1];
2542
- if (next === void 0 || next.startsWith("--")) {
2543
- throw new ArgError(`Flag --${canonical} requires a value.`);
2544
- }
2545
- value = next;
2546
- i += 1;
2547
- }
2548
- if (arrayFlags.has(canonical)) {
2549
- const existing = result.array[canonical] ?? [];
2550
- existing.push(value);
2551
- result.array[canonical] = existing;
2552
- } else {
2553
- result.string[canonical] = value;
2554
- }
2555
- i += 1;
2556
- continue;
2557
- }
2558
- throw new ArgError(`Unknown flag --${flagToken}`);
2559
- }
2560
- return result;
2561
- }
2562
- function resolveCanonical(flagToken, ctx) {
2563
- if (ctx.aliases[flagToken]) return ctx.aliases[flagToken];
2564
- if (flagToken.startsWith("no-") && ctx.booleanFlags.has(flagToken.slice(3))) {
2565
- return flagToken.slice(3);
2566
- }
2567
- return flagToken;
2568
- }
2569
- function parseBool(value) {
2570
- if (value === "true" || value === "1") return true;
2571
- if (value === "false" || value === "0") return false;
2572
- throw new ArgError(`Expected boolean value (true/false), got "${value}".`);
2573
- }
2574
-
2575
2696
  // src/cli/parse-args.ts
2576
2697
  var SHORT_ALIASES = {
2577
2698
  h: "help",
@@ -3256,7 +3377,9 @@ function renderCommandHelp(command) {
3256
3377
  "Options:",
3257
3378
  " --prompt <text> Text prompt (or pass as the positional argument)",
3258
3379
  " --model-slug <slug> Required model identifier",
3259
- " --brand-model <id> Optional Brand Model reference",
3380
+ " --brand-model <spec> Brand Model reference (repeatable, max 3)",
3381
+ " Format: <uuid> or <uuid>:<pose>",
3382
+ " Poses: front, 3/4-right, right, left, 3/4-left, back, approved",
3260
3383
  " --reference-image <v> URL or local path (repeatable, max 8)",
3261
3384
  " --aspect-ratio <a:b> e.g. 16:9",
3262
3385
  " --resolution <WxH> e.g. 1024x768",
@@ -3451,7 +3574,6 @@ async function runGenerateCreate(argv, ctx, rc) {
3451
3574
  ...COMMON_RICH_STRING_FLAGS,
3452
3575
  "prompt",
3453
3576
  "model-slug",
3454
- "brand-model",
3455
3577
  "aspect-ratio",
3456
3578
  "resolution",
3457
3579
  "duration-seconds",
@@ -3460,10 +3582,13 @@ async function runGenerateCreate(argv, ctx, rc) {
3460
3582
  "timeout"
3461
3583
  ],
3462
3584
  boolean: [...COMMON_RICH_BOOL_FLAGS, "wait", "async"],
3463
- array: ["reference-image"]
3585
+ array: ["reference-image", "brand-model"]
3464
3586
  });
3465
3587
  const modelSlug = parsed.string["model-slug"];
3466
3588
  if (!modelSlug) throw new ArgError("--model-slug is required.");
3589
+ const rawBrandModels = parsed.array["brand-model"] ?? [];
3590
+ const brandModels = rawBrandModels.length > 0 ? rawBrandModels.map(parseBrandModelSpec) : void 0;
3591
+ if (brandModels) validateBrandModelSpecs(brandModels);
3467
3592
  const result = await generateCreate({
3468
3593
  prompt: parsed.string.prompt ?? parsed.positional[0],
3469
3594
  modelSlug,
@@ -3471,7 +3596,7 @@ async function runGenerateCreate(argv, ctx, rc) {
3471
3596
  aspectRatio: parsed.string["aspect-ratio"],
3472
3597
  resolution: parsed.string.resolution,
3473
3598
  durationSeconds: optionalInt(parsed.string["duration-seconds"], "duration-seconds"),
3474
- brandModel: parsed.string["brand-model"],
3599
+ brandModels,
3475
3600
  workflowId: parsed.string["workflow-id"],
3476
3601
  nodeId: parsed.string["node-id"],
3477
3602
  wait: computeWaitFlag(parsed),