@clickraft/cli 0.3.0 → 0.5.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/dist/index.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";
@@ -2235,6 +2441,77 @@ function formatRevokeFailure(err, token) {
2235
2441
  return `warning: server-side revoke failed (${reason}). Local profile removed; token ${redactTokenLikeValue(token)} remains server-valid until expiry.`;
2236
2442
  }
2237
2443
 
2444
+ // src/schemas/models.ts
2445
+ import { z as z8 } from "zod";
2446
+ var ModelSummarySchema = z8.object({
2447
+ id: z8.string().uuid(),
2448
+ slug: z8.string(),
2449
+ displayName: z8.string(),
2450
+ description: z8.string().nullable(),
2451
+ shortDescription: z8.string().nullable(),
2452
+ providerSlug: z8.string(),
2453
+ providerName: z8.string(),
2454
+ modelType: z8.string(),
2455
+ category: z8.string(),
2456
+ costTier: z8.string(),
2457
+ creditCost: z8.number(),
2458
+ pricingStrategy: z8.string(),
2459
+ estimatedSeconds: z8.object({
2460
+ typical: z8.number(),
2461
+ range: z8.array(z8.number())
2462
+ }).passthrough().nullable(),
2463
+ status: z8.string(),
2464
+ modalities: z8.object({
2465
+ input: z8.array(z8.string()),
2466
+ output: z8.array(z8.string())
2467
+ }).passthrough().nullable(),
2468
+ bestFor: z8.array(z8.string()),
2469
+ badges: z8.array(z8.string()),
2470
+ isFeatured: z8.boolean(),
2471
+ isDefault: z8.boolean(),
2472
+ docsUrl: z8.string().nullable(),
2473
+ llmContext: z8.string().nullable(),
2474
+ constraints: z8.record(z8.unknown()).nullable(),
2475
+ modelFamily: z8.string().nullable(),
2476
+ isFamilyPrimary: z8.boolean().nullable()
2477
+ }).strict();
2478
+ var ModelsListResponseSchema = z8.object({
2479
+ models: z8.array(ModelSummarySchema)
2480
+ }).strict();
2481
+
2482
+ // src/commands/models/internal.ts
2483
+ async function buildModelClient(opts) {
2484
+ const cfg = await loadConfig({
2485
+ flagProfile: opts.profile,
2486
+ flagApiBaseUrl: opts.apiBaseUrl,
2487
+ flagToken: opts.token
2488
+ });
2489
+ return new ApiClient({
2490
+ apiBaseUrl: cfg.apiBaseUrl,
2491
+ accessToken: cfg.accessToken,
2492
+ signal: opts.signal,
2493
+ cloudflareAccess: cfg.cloudflareAccess
2494
+ });
2495
+ }
2496
+
2497
+ // src/commands/models/list.ts
2498
+ async function listModels(opts) {
2499
+ const client = opts.client ?? await buildModelClient(opts);
2500
+ const data = await client.request({
2501
+ method: "GET",
2502
+ path: "/models",
2503
+ query: {
2504
+ category: opts.category,
2505
+ tier: opts.tier,
2506
+ type: opts.type,
2507
+ featured: opts.featured !== void 0 ? String(opts.featured) : void 0
2508
+ },
2509
+ responseSchema: ModelsListResponseSchema,
2510
+ signal: opts.signal
2511
+ });
2512
+ return data;
2513
+ }
2514
+
2238
2515
  // src/telemetry/detect.ts
2239
2516
  var AGENT_SIGNALS = [
2240
2517
  { envVar: "CLAUDE_CODE_SESSION_ID", agent: "claude-code" },
@@ -2325,33 +2602,33 @@ async function buildTemplateClient(opts) {
2325
2602
  }
2326
2603
 
2327
2604
  // src/schemas/templates.ts
2328
- import { z as z8 } from "zod";
2329
- var DeclaredParamSchema = z8.object({
2330
- name: z8.string(),
2331
- type: z8.string(),
2332
- required: z8.boolean(),
2333
- defaultValue: z8.string().nullable().optional(),
2334
- enumValues: z8.array(z8.string()).nullable().optional(),
2335
- description: z8.string().nullable().optional()
2605
+ import { z as z9 } from "zod";
2606
+ var DeclaredParamSchema = z9.object({
2607
+ name: z9.string(),
2608
+ type: z9.string(),
2609
+ required: z9.boolean(),
2610
+ defaultValue: z9.string().nullable().optional(),
2611
+ enumValues: z9.array(z9.string()).nullable().optional(),
2612
+ description: z9.string().nullable().optional()
2336
2613
  }).strict();
2337
- var TemplateSummarySchema = z8.object({
2338
- id: z8.string().uuid(),
2339
- source: z8.enum(["own", "system"]),
2340
- name: z8.string(),
2341
- description: z8.string().nullable(),
2342
- category: z8.string().nullable(),
2343
- thumbnailUrl: z8.string().nullable(),
2344
- tags: z8.array(z8.string()),
2345
- createdAt: z8.string()
2614
+ var TemplateSummarySchema = z9.object({
2615
+ id: z9.string().uuid(),
2616
+ source: z9.enum(["own", "system"]),
2617
+ name: z9.string(),
2618
+ description: z9.string().nullable(),
2619
+ category: z9.string().nullable(),
2620
+ thumbnailUrl: z9.string().nullable(),
2621
+ tags: z9.array(z9.string()),
2622
+ createdAt: z9.string()
2346
2623
  }).strict();
2347
2624
  var TemplateDetailSchema = TemplateSummarySchema.extend({
2348
- declaredParams: z8.array(DeclaredParamSchema),
2349
- timesUsed: z8.number()
2625
+ declaredParams: z9.array(DeclaredParamSchema),
2626
+ timesUsed: z9.number()
2350
2627
  }).strict();
2351
- var TemplatesListResponseSchema = z8.object({
2352
- templates: z8.array(TemplateSummarySchema),
2353
- cursor: z8.string().nullable(),
2354
- hasMore: z8.boolean()
2628
+ var TemplatesListResponseSchema = z9.object({
2629
+ templates: z9.array(TemplateSummarySchema),
2630
+ cursor: z9.string().nullable(),
2631
+ hasMore: z9.boolean()
2355
2632
  }).strict();
2356
2633
 
2357
2634
  // src/commands/template/list.ts
@@ -2401,20 +2678,20 @@ async function getTemplate(opts) {
2401
2678
  }
2402
2679
 
2403
2680
  // src/commands/tokens/list.ts
2404
- import { z as z9 } from "zod";
2405
- var TokenRowSchema = z9.object({
2406
- id: z9.string().uuid(),
2407
- organizationId: z9.string().uuid(),
2408
- userId: z9.string().uuid(),
2409
- clientId: z9.string().nullable(),
2410
- tokenPrefix: z9.string(),
2411
- name: z9.string().nullable(),
2412
- scopes: z9.array(z9.string()),
2413
- lastUsedAt: z9.string().nullable(),
2414
- expiresAt: z9.string(),
2415
- createdAt: z9.string()
2681
+ import { z as z10 } from "zod";
2682
+ var TokenRowSchema = z10.object({
2683
+ id: z10.string().uuid(),
2684
+ organizationId: z10.string().uuid(),
2685
+ userId: z10.string().uuid(),
2686
+ clientId: z10.string().nullable(),
2687
+ tokenPrefix: z10.string(),
2688
+ name: z10.string().nullable(),
2689
+ scopes: z10.array(z10.string()),
2690
+ lastUsedAt: z10.string().nullable(),
2691
+ expiresAt: z10.string(),
2692
+ createdAt: z10.string()
2416
2693
  }).strict();
2417
- var TokensListResponseSchema = z9.array(TokenRowSchema);
2694
+ var TokensListResponseSchema = z10.array(TokenRowSchema);
2418
2695
  async function listTokens(options) {
2419
2696
  const config = await loadConfig({
2420
2697
  flagProfile: options.profile,
@@ -2443,23 +2720,33 @@ async function listTokens(options) {
2443
2720
  var FALLBACK_HINT = "See `clickraft --help` or contact support.";
2444
2721
  function formatHumanError(err) {
2445
2722
  const view = errorToView(err);
2446
- return [
2723
+ const lines = [
2447
2724
  `Error: ${view.message}`,
2448
2725
  `Code: ${view.code}`,
2449
2726
  `Try: ${view.hint}`,
2450
2727
  `Request ID: ${view.requestId ?? "n/a"}`
2451
- ].join("\n");
2728
+ ];
2729
+ if (view.details) lines.push(`Details: ${view.details}`);
2730
+ return lines.join("\n");
2452
2731
  }
2453
2732
  function formatJsonError(err, ctx) {
2454
2733
  return JSON.stringify(wrapError(err, ctx));
2455
2734
  }
2735
+ function formatDetails(details) {
2736
+ if (details == null) return void 0;
2737
+ if (typeof details !== "object") return String(details);
2738
+ const entries = Object.entries(details);
2739
+ if (entries.length === 0) return void 0;
2740
+ return entries.map(([k, v]) => `${k}=${v}`).join(", ");
2741
+ }
2456
2742
  function errorToView(err) {
2457
2743
  if (err instanceof ApiError) {
2458
2744
  return {
2459
2745
  code: toEnvelopeCode(err.code),
2460
2746
  message: err.message,
2461
2747
  hint: EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
2462
- requestId: err.meta.requestId
2748
+ requestId: err.meta.requestId,
2749
+ details: formatDetails(err.meta.details)
2463
2750
  };
2464
2751
  }
2465
2752
  if (err instanceof AuthError || err instanceof CredentialsError) {
@@ -2477,101 +2764,6 @@ function errorToView(err) {
2477
2764
  };
2478
2765
  }
2479
2766
 
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
2767
  // src/cli/parse-args.ts
2576
2768
  var SHORT_ALIASES = {
2577
2769
  h: "help",
@@ -2867,6 +3059,72 @@ function renderTemplateDetail(detail, out) {
2867
3059
  }
2868
3060
  out.write(lines.join("\n") + "\n");
2869
3061
  }
3062
+ var TIER_ORDER = { premium: 0, pro: 1, standard: 2 };
3063
+ function renderModelsList(result, out, options) {
3064
+ const { models } = result;
3065
+ if (models.length === 0) {
3066
+ out.write("No models match your filters.\n");
3067
+ return;
3068
+ }
3069
+ const sorted = [...models].sort((a, b) => {
3070
+ const catCmp = a.category.localeCompare(b.category);
3071
+ if (catCmp !== 0) return catCmp;
3072
+ const tierA = TIER_ORDER[a.costTier] ?? 99;
3073
+ const tierB = TIER_ORDER[b.costTier] ?? 99;
3074
+ if (tierA !== tierB) return tierA - tierB;
3075
+ return a.slug.localeCompare(b.slug);
3076
+ });
3077
+ const filterParts = [];
3078
+ if (options?.filters) {
3079
+ for (const [k, v] of Object.entries(options.filters)) {
3080
+ if (v) filterParts.push(`${k}=${v}`);
3081
+ }
3082
+ }
3083
+ const filterSuffix = filterParts.length > 0 ? ` (filters: ${filterParts.join(", ")})` : "";
3084
+ out.write(`${sorted.length} models${filterSuffix}
3085
+
3086
+ `);
3087
+ const headers = ["SLUG", "NAME", "CATEGORY", "TIER", "CREDITS", "DEFAULT"];
3088
+ const data = sorted.map((m) => [
3089
+ m.slug,
3090
+ m.displayName,
3091
+ m.category,
3092
+ m.costTier,
3093
+ String(m.creditCost),
3094
+ m.isDefault ? "\u2713" : ""
3095
+ ]);
3096
+ const widths = headers.map(
3097
+ (header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
3098
+ );
3099
+ const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
3100
+ out.write(padRow(headers) + "\n");
3101
+ for (const cells of data) out.write(padRow(cells) + "\n");
3102
+ out.write(
3103
+ `
3104
+ Showing all ${sorted.length} models. Use --json for full details including constraints.
3105
+ `
3106
+ );
3107
+ if (options?.verbose) {
3108
+ out.write("\n");
3109
+ for (const m of sorted) {
3110
+ const pad = 16;
3111
+ out.write(`${m.slug} (${m.displayName})
3112
+ `);
3113
+ out.write(`${" Provider:".padEnd(pad)}${m.providerName}
3114
+ `);
3115
+ out.write(`${" Category:".padEnd(pad)}${m.category} / ${m.modelType}
3116
+ `);
3117
+ out.write(`${" Tier:".padEnd(pad)}${m.costTier} (${m.creditCost} credits)
3118
+ `);
3119
+ if (m.llmContext) {
3120
+ const ctx = m.llmContext.length > 100 ? m.llmContext.slice(0, 100) + "..." : m.llmContext;
3121
+ out.write(`${" Context:".padEnd(pad)}${ctx}
3122
+ `);
3123
+ }
3124
+ out.write("\n");
3125
+ }
3126
+ }
3127
+ }
2870
3128
  function formatExpiresAt(iso) {
2871
3129
  const ms = Date.parse(iso);
2872
3130
  if (Number.isNaN(ms)) return iso;
@@ -3093,6 +3351,7 @@ function renderRootHelp(version) {
3093
3351
  " tokens list List the caller's API tokens",
3094
3352
  " balance Show credit balance and rate limits",
3095
3353
  " brand-model list List available brand models",
3354
+ " models list List available AI models",
3096
3355
  " telemetry inspect Show current telemetry state and payload shape",
3097
3356
  " template list List available templates",
3098
3357
  " template get Fetch a template by ID",
@@ -3172,6 +3431,38 @@ function renderCommandHelp(command) {
3172
3431
  " --json Emit JSON envelope instead of human output",
3173
3432
  ""
3174
3433
  ].join("\n");
3434
+ case "models":
3435
+ return [
3436
+ "Usage: clickraft models <subcommand> [options]",
3437
+ "",
3438
+ "Subcommands:",
3439
+ " list List available AI models",
3440
+ ""
3441
+ ].join("\n");
3442
+ case "models list":
3443
+ return [
3444
+ "Usage: clickraft models list [options]",
3445
+ "",
3446
+ "List AI models available for generation. By default shows a summary table;",
3447
+ "use --json for full details including constraints and llmContext.",
3448
+ "",
3449
+ "Options:",
3450
+ " --category <name> Filter by category (image, video, audio, chat, edit)",
3451
+ " --tier <name> Filter by cost tier (standard, pro, premium)",
3452
+ " --type <name> Filter by model type (e.g. text-to-image)",
3453
+ " --featured Show only featured models",
3454
+ " --verbose Show expanded per-model details after the table",
3455
+ " --profile <name>",
3456
+ " --api-base-url <url>",
3457
+ " --token <token>",
3458
+ " --json",
3459
+ "",
3460
+ "Examples:",
3461
+ " clickraft models list",
3462
+ " clickraft models list --category image --tier pro",
3463
+ " clickraft models list --featured --json",
3464
+ ""
3465
+ ].join("\n");
3175
3466
  case "telemetry inspect":
3176
3467
  return [
3177
3468
  "Usage: clickraft telemetry inspect [options]",
@@ -3256,7 +3547,9 @@ function renderCommandHelp(command) {
3256
3547
  "Options:",
3257
3548
  " --prompt <text> Text prompt (or pass as the positional argument)",
3258
3549
  " --model-slug <slug> Required model identifier",
3259
- " --brand-model <id> Optional Brand Model reference",
3550
+ " --brand-model <spec> Brand Model reference (repeatable, max 3)",
3551
+ " Format: <uuid> or <uuid>:<pose>",
3552
+ " Poses: front, 3/4-right, right, left, 3/4-left, back, approved",
3260
3553
  " --reference-image <v> URL or local path (repeatable, max 8)",
3261
3554
  " --aspect-ratio <a:b> e.g. 16:9",
3262
3555
  " --resolution <WxH> e.g. 1024x768",
@@ -3365,6 +3658,12 @@ function matchNewCommand(argv) {
3365
3658
  if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "template" };
3366
3659
  return { kind: "unknown-sub", verb: "template", sub };
3367
3660
  }
3661
+ if (verb === "models") {
3662
+ const sub = argv[1];
3663
+ if (sub === "list") return { kind: "cmd", path: "models.list", verbTokenCount: 2 };
3664
+ if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "models" };
3665
+ return { kind: "unknown-sub", verb: "models", sub };
3666
+ }
3368
3667
  return null;
3369
3668
  }
3370
3669
  async function runNewCommand(newCmd, options, stdout, stderr) {
@@ -3421,6 +3720,8 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
3421
3720
  return await runTemplateGet(argvAfterVerb, ctx, rc);
3422
3721
  case "template.find":
3423
3722
  return await runTemplateFind(argvAfterVerb, ctx, rc);
3723
+ case "models.list":
3724
+ return await runModelsList(argvAfterVerb, ctx, rc);
3424
3725
  }
3425
3726
  } catch (err) {
3426
3727
  if (isAbortedError(err, options.signal)) {
@@ -3451,7 +3752,6 @@ async function runGenerateCreate(argv, ctx, rc) {
3451
3752
  ...COMMON_RICH_STRING_FLAGS,
3452
3753
  "prompt",
3453
3754
  "model-slug",
3454
- "brand-model",
3455
3755
  "aspect-ratio",
3456
3756
  "resolution",
3457
3757
  "duration-seconds",
@@ -3460,10 +3760,13 @@ async function runGenerateCreate(argv, ctx, rc) {
3460
3760
  "timeout"
3461
3761
  ],
3462
3762
  boolean: [...COMMON_RICH_BOOL_FLAGS, "wait", "async"],
3463
- array: ["reference-image"]
3763
+ array: ["reference-image", "brand-model"]
3464
3764
  });
3465
3765
  const modelSlug = parsed.string["model-slug"];
3466
3766
  if (!modelSlug) throw new ArgError("--model-slug is required.");
3767
+ const rawBrandModels = parsed.array["brand-model"] ?? [];
3768
+ const brandModels = rawBrandModels.length > 0 ? rawBrandModels.map(parseBrandModelSpec) : void 0;
3769
+ if (brandModels) validateBrandModelSpecs(brandModels);
3467
3770
  const result = await generateCreate({
3468
3771
  prompt: parsed.string.prompt ?? parsed.positional[0],
3469
3772
  modelSlug,
@@ -3471,7 +3774,7 @@ async function runGenerateCreate(argv, ctx, rc) {
3471
3774
  aspectRatio: parsed.string["aspect-ratio"],
3472
3775
  resolution: parsed.string.resolution,
3473
3776
  durationSeconds: optionalInt(parsed.string["duration-seconds"], "duration-seconds"),
3474
- brandModel: parsed.string["brand-model"],
3777
+ brandModels,
3475
3778
  workflowId: parsed.string["workflow-id"],
3476
3779
  nodeId: parsed.string["node-id"],
3477
3780
  wait: computeWaitFlag(parsed),
@@ -3696,6 +3999,42 @@ function emitTemplateDetail(detail, ctx, rc) {
3696
3999
  }
3697
4000
  renderTemplateDetail(detail, rc.stdout);
3698
4001
  }
4002
+ async function runModelsList(argv, ctx, rc) {
4003
+ const parsed = parseArgs(argv, {
4004
+ string: [...COMMON_RICH_STRING_FLAGS, "category", "tier", "type"],
4005
+ boolean: [...COMMON_RICH_BOOL_FLAGS, "featured", "verbose"]
4006
+ });
4007
+ const result = await listModels({
4008
+ category: parsed.string.category,
4009
+ tier: parsed.string.tier,
4010
+ type: parsed.string.type,
4011
+ featured: parsed.boolean.featured,
4012
+ profile: parsed.string.profile,
4013
+ apiBaseUrl: parsed.string["api-base-url"],
4014
+ token: parsed.string.token,
4015
+ signal: rc.signal
4016
+ });
4017
+ emitModelsList(result, ctx, rc, parsed);
4018
+ return 0;
4019
+ }
4020
+ function emitModelsList(result, ctx, rc, parsed) {
4021
+ if (rc.signal?.aborted) {
4022
+ throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4023
+ }
4024
+ if (rc.jsonMode) {
4025
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
4026
+ return;
4027
+ }
4028
+ renderModelsList(result, rc.stdout, {
4029
+ verbose: parsed.boolean.verbose === true,
4030
+ filters: {
4031
+ category: parsed.string.category ?? "",
4032
+ tier: parsed.string.tier ?? "",
4033
+ type: parsed.string.type ?? "",
4034
+ featured: parsed.boolean.featured !== void 0 ? String(parsed.boolean.featured) : ""
4035
+ }
4036
+ });
4037
+ }
3699
4038
 
3700
4039
  // src/index.ts
3701
4040
  var __filename2 = fileURLToPath(import.meta.url);