@clickraft/cli 0.5.0 → 0.6.1

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,8 @@
1
- ## [0.5.0](https://github.com/clickraft/cli/compare/v0.4.0...v0.5.0) (2026-05-24)
1
+ ## [0.6.1](https://github.com/clickraft/cli/compare/v0.6.0...v0.6.1) (2026-05-25)
2
2
 
3
- ### Features
3
+ ### Bug Fixes
4
4
 
5
- * **models:** add models list command consuming agents/v1/models ([83761d1](https://github.com/clickraft/cli/commit/83761d11d136758e23c40cf80cd87c8f08dbaad6))
5
+ * **auth:** include models:read in DEFAULT_SCOPES ([284764b](https://github.com/clickraft/cli/commit/284764bfff8b132a224c6f3634fa230309d42ce5))
6
6
 
7
7
  # Changelog
8
8
 
package/dist/cli.js CHANGED
@@ -334,6 +334,55 @@ var EXIT_CODE_MAP = {
334
334
  retryable: false,
335
335
  hint: "Too many reference images. Check model limits."
336
336
  },
337
+ // --- 8 product reference resolver codes -------------------------------------
338
+ PRODUCT_NOT_FOUND: {
339
+ exitCode: 1,
340
+ category: "runtime",
341
+ retryable: false,
342
+ hint: "No product with that ID in your org. Run `clickraft product list`."
343
+ },
344
+ PRODUCT_DUPLICATE_REFERENCE: {
345
+ exitCode: 2,
346
+ category: "usage",
347
+ retryable: false,
348
+ hint: "Same (productId, imageId) tuple appears twice in the request."
349
+ },
350
+ PRODUCT_IMAGE_NOT_FOUND: {
351
+ exitCode: 1,
352
+ category: "runtime",
353
+ retryable: false,
354
+ hint: "imageId does not exist in the product images."
355
+ },
356
+ PRODUCT_IMAGE_MISMATCH: {
357
+ exitCode: 2,
358
+ category: "usage",
359
+ retryable: false,
360
+ hint: "imageId belongs to a different product."
361
+ },
362
+ PRODUCT_SYNC_ERROR: {
363
+ exitCode: 1,
364
+ category: "runtime",
365
+ retryable: false,
366
+ hint: "Product sync status is 'error'. Re-sync or use a different product."
367
+ },
368
+ PRODUCT_DELETED_UPSTREAM: {
369
+ exitCode: 1,
370
+ category: "runtime",
371
+ retryable: false,
372
+ hint: "Product was deleted upstream. Remove it or re-sync."
373
+ },
374
+ PRODUCT_NO_PRIMARY_IMAGE: {
375
+ exitCode: 1,
376
+ category: "runtime",
377
+ retryable: false,
378
+ hint: "Product has no primary image. Specify --product <id>:<imageId> explicitly."
379
+ },
380
+ PRODUCT_SOURCE_MANAGED: {
381
+ exitCode: 4,
382
+ category: "permission",
383
+ retryable: false,
384
+ hint: "Product is managed by an external integration."
385
+ },
337
386
  // --- 4 CLI-synthetic codes --------------------------------------------------
338
387
  NETWORK_ERROR: {
339
388
  exitCode: 6,
@@ -877,7 +926,8 @@ function errorToEnvelopePayload(err) {
877
926
  retryable: err.meta.retryable,
878
927
  retryAfterMs: err.meta.retryAfterMs,
879
928
  requestId: err.meta.requestId,
880
- details: err.meta.details
929
+ details: err.meta.details,
930
+ hint: err.meta.hint
881
931
  });
882
932
  }
883
933
  if (err instanceof AuthError || err instanceof CredentialsError) {
@@ -905,7 +955,8 @@ function buildPayload(args) {
905
955
  };
906
956
  if (typeof args.retryAfterMs === "number") payload.retry_after_ms = args.retryAfterMs;
907
957
  if (args.requestId) payload.request_id = args.requestId;
908
- if (mapping?.hint) payload.hint = mapping.hint;
958
+ const resolvedHint = args.hint ?? mapping?.hint;
959
+ if (resolvedHint) payload.hint = resolvedHint;
909
960
  if (args.details != null) payload.details = args.details;
910
961
  return payload;
911
962
  }
@@ -1322,6 +1373,10 @@ async function listBrandModels(options) {
1322
1373
 
1323
1374
  // src/schemas/jobs.ts
1324
1375
  import { z as z6 } from "zod";
1376
+ var ProductReferenceSchema = z6.object({
1377
+ id: z6.string().uuid(),
1378
+ imageId: z6.string().uuid().optional()
1379
+ }).strict();
1325
1380
  var GenerateCreateRequestSchema = z6.object({
1326
1381
  modelSlug: z6.string().min(1).max(100),
1327
1382
  input: z6.object({
@@ -1336,7 +1391,8 @@ var GenerateCreateRequestSchema = z6.object({
1336
1391
  options: z6.object({
1337
1392
  workflowId: z6.string().uuid().optional(),
1338
1393
  nodeId: z6.string().optional()
1339
- }).strict().optional()
1394
+ }).strict().optional(),
1395
+ products: z6.array(ProductReferenceSchema).min(1).max(10).optional()
1340
1396
  }).strict();
1341
1397
  var GenerateCreateResponseSchema = z6.object({
1342
1398
  jobId: z6.string().uuid(),
@@ -1807,6 +1863,13 @@ function buildRequestBody(args) {
1807
1863
  if (args.nodeId !== void 0) options.nodeId = args.nodeId;
1808
1864
  body.options = options;
1809
1865
  }
1866
+ if (args.products !== void 0 && args.products.length > 0) {
1867
+ body.products = args.products.map((spec) => {
1868
+ const entry = { id: spec.id };
1869
+ if (spec.imageId) entry.imageId = spec.imageId;
1870
+ return entry;
1871
+ });
1872
+ }
1810
1873
  const parsed = GenerateCreateRequestSchema.safeParse(body);
1811
1874
  if (!parsed.success) {
1812
1875
  throw new ApiError(
@@ -1979,6 +2042,44 @@ function validateBrandModelSpecs(specs) {
1979
2042
  }
1980
2043
  }
1981
2044
 
2045
+ // src/commands/generate/parse-product-spec.ts
2046
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2047
+ function parseProductSpec(raw) {
2048
+ if (!raw) throw new ArgError("--product value cannot be empty.");
2049
+ const colonIdx = raw.indexOf(":");
2050
+ const productId = colonIdx >= 0 ? raw.slice(0, colonIdx) : raw;
2051
+ const imageIdRaw = colonIdx >= 0 ? raw.slice(colonIdx + 1) : void 0;
2052
+ if (!UUID_RE2.test(productId)) {
2053
+ throw new ArgError(
2054
+ `Invalid product ID: '${productId}'. Expected a UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).`
2055
+ );
2056
+ }
2057
+ if (imageIdRaw !== void 0) {
2058
+ if (!imageIdRaw) {
2059
+ throw new ArgError(
2060
+ `Empty image ID for product ${productId.toLowerCase()}. Omit the colon or provide a valid UUID.`
2061
+ );
2062
+ }
2063
+ if (!UUID_RE2.test(imageIdRaw)) {
2064
+ throw new ArgError(
2065
+ `Invalid image ID: '${imageIdRaw}' for product ${productId.toLowerCase()}. Expected a UUID.`
2066
+ );
2067
+ }
2068
+ return { id: productId.toLowerCase(), imageId: imageIdRaw.toLowerCase() };
2069
+ }
2070
+ return { id: productId.toLowerCase() };
2071
+ }
2072
+ function validateProductSpecs(specs) {
2073
+ const seen = /* @__PURE__ */ new Set();
2074
+ for (const spec of specs) {
2075
+ const key = spec.imageId ? `${spec.id}:${spec.imageId}` : spec.id;
2076
+ if (seen.has(key)) {
2077
+ throw new ArgError(`Duplicate product reference: ${key}. Each --product must be unique.`);
2078
+ }
2079
+ seen.add(key);
2080
+ }
2081
+ }
2082
+
1982
2083
  // src/auth/device-flow.ts
1983
2084
  import { setTimeout as delay } from "timers/promises";
1984
2085
  import { request } from "undici";
@@ -2315,6 +2416,8 @@ var DEFAULT_SCOPES = [
2315
2416
  "workflows:write",
2316
2417
  "workflows:read",
2317
2418
  "brand-models:read",
2419
+ "products:read",
2420
+ "models:read",
2318
2421
  "assets:write",
2319
2422
  "balance:read"
2320
2423
  ];
@@ -2512,6 +2615,68 @@ async function listModels(opts) {
2512
2615
  return data;
2513
2616
  }
2514
2617
 
2618
+ // src/commands/product/list.ts
2619
+ import { z as z9 } from "zod";
2620
+ var ProductImageSummarySchema = z9.object({
2621
+ id: z9.string().uuid(),
2622
+ url: z9.string(),
2623
+ altText: z9.string().nullable(),
2624
+ isPrimary: z9.boolean(),
2625
+ sortOrder: z9.number().int()
2626
+ });
2627
+ var ProductSummarySchema = z9.object({
2628
+ id: z9.string().uuid(),
2629
+ title: z9.string(),
2630
+ sourceType: z9.string(),
2631
+ syncStatus: z9.enum(["synced", "pending", "stale", "error", "deleted_upstream"]).nullable(),
2632
+ vendor: z9.string().nullable(),
2633
+ productType: z9.string().nullable(),
2634
+ tags: z9.array(z9.string()),
2635
+ images: z9.array(ProductImageSummarySchema)
2636
+ });
2637
+ var ProductsListResponseSchema = z9.object({
2638
+ items: z9.array(ProductSummarySchema),
2639
+ nextCursor: z9.string().nullable()
2640
+ });
2641
+ var DEFAULT_LIMIT = 50;
2642
+ async function listProducts(options) {
2643
+ const config = await loadConfig({
2644
+ flagProfile: options.profile,
2645
+ flagApiBaseUrl: options.apiBaseUrl,
2646
+ flagToken: options.token,
2647
+ credentialsPath: options.credentialsPath
2648
+ });
2649
+ if (!config.accessToken) {
2650
+ throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
2651
+ }
2652
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
2653
+ apiBaseUrl,
2654
+ accessToken,
2655
+ signal: options.signal,
2656
+ cloudflareAccess: config.cloudflareAccess
2657
+ }));
2658
+ const client = factory(config.apiBaseUrl, config.accessToken);
2659
+ const query = {
2660
+ limit: options.limit ?? DEFAULT_LIMIT
2661
+ };
2662
+ if (options.cursor) query.cursor = options.cursor;
2663
+ if (options.syncStatus) query.syncStatus = options.syncStatus;
2664
+ if (options.sourceType) query.sourceType = options.sourceType;
2665
+ if (options.search) query.search = options.search;
2666
+ const data = await client.request({
2667
+ method: "GET",
2668
+ path: "/products",
2669
+ query,
2670
+ responseSchema: ProductsListResponseSchema,
2671
+ signal: options.signal
2672
+ });
2673
+ return {
2674
+ products: data.items,
2675
+ nextCursor: data.nextCursor,
2676
+ hasMore: data.nextCursor !== null
2677
+ };
2678
+ }
2679
+
2515
2680
  // src/telemetry/detect.ts
2516
2681
  var AGENT_SIGNALS = [
2517
2682
  { envVar: "CLAUDE_CODE_SESSION_ID", agent: "claude-code" },
@@ -2602,33 +2767,33 @@ async function buildTemplateClient(opts) {
2602
2767
  }
2603
2768
 
2604
2769
  // src/schemas/templates.ts
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()
2770
+ import { z as z10 } from "zod";
2771
+ var DeclaredParamSchema = z10.object({
2772
+ name: z10.string(),
2773
+ type: z10.string(),
2774
+ required: z10.boolean(),
2775
+ defaultValue: z10.string().nullable().optional(),
2776
+ enumValues: z10.array(z10.string()).nullable().optional(),
2777
+ description: z10.string().nullable().optional()
2613
2778
  }).strict();
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()
2779
+ var TemplateSummarySchema = z10.object({
2780
+ id: z10.string().uuid(),
2781
+ source: z10.enum(["own", "system"]),
2782
+ name: z10.string(),
2783
+ description: z10.string().nullable(),
2784
+ category: z10.string().nullable(),
2785
+ thumbnailUrl: z10.string().nullable(),
2786
+ tags: z10.array(z10.string()),
2787
+ createdAt: z10.string()
2623
2788
  }).strict();
2624
2789
  var TemplateDetailSchema = TemplateSummarySchema.extend({
2625
- declaredParams: z9.array(DeclaredParamSchema),
2626
- timesUsed: z9.number()
2790
+ declaredParams: z10.array(DeclaredParamSchema),
2791
+ timesUsed: z10.number()
2627
2792
  }).strict();
2628
- var TemplatesListResponseSchema = z9.object({
2629
- templates: z9.array(TemplateSummarySchema),
2630
- cursor: z9.string().nullable(),
2631
- hasMore: z9.boolean()
2793
+ var TemplatesListResponseSchema = z10.object({
2794
+ templates: z10.array(TemplateSummarySchema),
2795
+ cursor: z10.string().nullable(),
2796
+ hasMore: z10.boolean()
2632
2797
  }).strict();
2633
2798
 
2634
2799
  // src/commands/template/list.ts
@@ -2678,20 +2843,20 @@ async function getTemplate(opts) {
2678
2843
  }
2679
2844
 
2680
2845
  // src/commands/tokens/list.ts
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()
2846
+ import { z as z11 } from "zod";
2847
+ var TokenRowSchema = z11.object({
2848
+ id: z11.string().uuid(),
2849
+ organizationId: z11.string().uuid(),
2850
+ userId: z11.string().uuid(),
2851
+ clientId: z11.string().nullable(),
2852
+ tokenPrefix: z11.string(),
2853
+ name: z11.string().nullable(),
2854
+ scopes: z11.array(z11.string()),
2855
+ lastUsedAt: z11.string().nullable(),
2856
+ expiresAt: z11.string(),
2857
+ createdAt: z11.string()
2693
2858
  }).strict();
2694
- var TokensListResponseSchema = z10.array(TokenRowSchema);
2859
+ var TokensListResponseSchema = z11.array(TokenRowSchema);
2695
2860
  async function listTokens(options) {
2696
2861
  const config = await loadConfig({
2697
2862
  flagProfile: options.profile,
@@ -2744,7 +2909,7 @@ function errorToView(err) {
2744
2909
  return {
2745
2910
  code: toEnvelopeCode(err.code),
2746
2911
  message: err.message,
2747
- hint: EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
2912
+ hint: err.meta.hint ?? EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
2748
2913
  requestId: err.meta.requestId,
2749
2914
  details: formatDetails(err.meta.details)
2750
2915
  };
@@ -3004,6 +3169,39 @@ function renderBrandModelsList(models, out) {
3004
3169
  out.write(padRow(headers) + "\n");
3005
3170
  for (const cells of data) out.write(padRow(cells) + "\n");
3006
3171
  }
3172
+ function renderProductsList(result, out) {
3173
+ const { products } = result;
3174
+ if (products.length === 0) {
3175
+ out.write("No products.\n");
3176
+ return;
3177
+ }
3178
+ const MAX_TAGS = 3;
3179
+ const headers = ["ID", "TITLE", "SOURCE", "SYNC", "IMAGES", "PRIMARY IMAGE", "VENDOR", "TAGS"];
3180
+ const data = products.map((p) => {
3181
+ const shown = p.tags.slice(0, MAX_TAGS).join(", ");
3182
+ const extra = p.tags.length > MAX_TAGS ? ` +${p.tags.length - MAX_TAGS} more` : "";
3183
+ const primary = p.images.find((img) => img.isPrimary) ?? p.images.find((img) => img.sortOrder === 0);
3184
+ return [
3185
+ p.id,
3186
+ p.title,
3187
+ p.sourceType,
3188
+ p.syncStatus ?? "\u2014",
3189
+ String(p.images.length),
3190
+ primary?.id ?? "\u2014",
3191
+ p.vendor ?? "\u2014",
3192
+ p.tags.length > 0 ? shown + extra : "\u2014"
3193
+ ];
3194
+ });
3195
+ const widths = headers.map(
3196
+ (header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
3197
+ );
3198
+ const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
3199
+ out.write(padRow(headers) + "\n");
3200
+ for (const cells of data) out.write(padRow(cells) + "\n");
3201
+ if (result.hasMore) {
3202
+ out.write("\nMore results available. Use --cursor to fetch the next page.\n");
3203
+ }
3204
+ }
3007
3205
  function renderTemplatesList(result, out) {
3008
3206
  const { templates } = result;
3009
3207
  if (templates.length === 0) {
@@ -3351,6 +3549,7 @@ function renderRootHelp(version) {
3351
3549
  " tokens list List the caller's API tokens",
3352
3550
  " balance Show credit balance and rate limits",
3353
3551
  " brand-model list List available brand models",
3552
+ " product list List products in your organization",
3354
3553
  " models list List available AI models",
3355
3554
  " telemetry inspect Show current telemetry state and payload shape",
3356
3555
  " template list List available templates",
@@ -3379,7 +3578,7 @@ function renderCommandHelp(command) {
3379
3578
  "Options:",
3380
3579
  ' --profile <name> Write to a specific profile (default: "default")',
3381
3580
  " --api-base-url <url> Override the API base URL",
3382
- " --scope <s> Space-separated scope list (default: all 8)",
3581
+ ` --scope <s> Space-separated scope list (default: all ${DEFAULT_SCOPES.length})`,
3383
3582
  " --force-reauth Re-authenticate even if a valid token exists",
3384
3583
  " --no-open-browser Do not auto-open the verification URL",
3385
3584
  " --json Emit JSON envelope instead of human output",
@@ -3431,6 +3630,32 @@ function renderCommandHelp(command) {
3431
3630
  " --json Emit JSON envelope instead of human output",
3432
3631
  ""
3433
3632
  ].join("\n");
3633
+ case "product":
3634
+ return [
3635
+ "Usage: clickraft product <subcommand> [options]",
3636
+ "",
3637
+ "Subcommands:",
3638
+ " list List products in your organization",
3639
+ ""
3640
+ ].join("\n");
3641
+ case "product list":
3642
+ return [
3643
+ "Usage: clickraft product list [options]",
3644
+ "",
3645
+ "List products in your organization. Supports cursor-based pagination.",
3646
+ "",
3647
+ "Options:",
3648
+ " --cursor <token> Pagination cursor from a previous response",
3649
+ " --limit <n> Max results per page (1-100, default: 50)",
3650
+ " --sync-status <s> Filter by sync status (synced, pending, stale, error, deleted_upstream)",
3651
+ " --source-type <s> Filter by source type (manual, csv, shopify, etsy, etc.)",
3652
+ " --search <q> Free-text search query",
3653
+ " --profile <name>",
3654
+ " --api-base-url <url>",
3655
+ " --token <token>",
3656
+ " --json",
3657
+ ""
3658
+ ].join("\n");
3434
3659
  case "models":
3435
3660
  return [
3436
3661
  "Usage: clickraft models <subcommand> [options]",
@@ -3550,6 +3775,9 @@ function renderCommandHelp(command) {
3550
3775
  " --brand-model <spec> Brand Model reference (repeatable, max 3)",
3551
3776
  " Format: <uuid> or <uuid>:<pose>",
3552
3777
  " Poses: front, 3/4-right, right, left, 3/4-left, back, approved",
3778
+ " --product <spec> Product reference (repeatable)",
3779
+ " Format: <uuid> or <uuid>:<imageId>",
3780
+ " Server enforces reference cap per model",
3553
3781
  " --reference-image <v> URL or local path (repeatable, max 8)",
3554
3782
  " --aspect-ratio <a:b> e.g. 16:9",
3555
3783
  " --resolution <WxH> e.g. 1024x768",
@@ -3658,6 +3886,12 @@ function matchNewCommand(argv) {
3658
3886
  if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "template" };
3659
3887
  return { kind: "unknown-sub", verb: "template", sub };
3660
3888
  }
3889
+ if (verb === "product") {
3890
+ const sub = argv[1];
3891
+ if (sub === "list") return { kind: "cmd", path: "product.list", verbTokenCount: 2 };
3892
+ if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "product" };
3893
+ return { kind: "unknown-sub", verb: "product", sub };
3894
+ }
3661
3895
  if (verb === "models") {
3662
3896
  const sub = argv[1];
3663
3897
  if (sub === "list") return { kind: "cmd", path: "models.list", verbTokenCount: 2 };
@@ -3720,6 +3954,8 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
3720
3954
  return await runTemplateGet(argvAfterVerb, ctx, rc);
3721
3955
  case "template.find":
3722
3956
  return await runTemplateFind(argvAfterVerb, ctx, rc);
3957
+ case "product.list":
3958
+ return await runProductList(argvAfterVerb, ctx, rc);
3723
3959
  case "models.list":
3724
3960
  return await runModelsList(argvAfterVerb, ctx, rc);
3725
3961
  }
@@ -3760,13 +3996,16 @@ async function runGenerateCreate(argv, ctx, rc) {
3760
3996
  "timeout"
3761
3997
  ],
3762
3998
  boolean: [...COMMON_RICH_BOOL_FLAGS, "wait", "async"],
3763
- array: ["reference-image", "brand-model"]
3999
+ array: ["reference-image", "brand-model", "product"]
3764
4000
  });
3765
4001
  const modelSlug = parsed.string["model-slug"];
3766
4002
  if (!modelSlug) throw new ArgError("--model-slug is required.");
3767
4003
  const rawBrandModels = parsed.array["brand-model"] ?? [];
3768
4004
  const brandModels = rawBrandModels.length > 0 ? rawBrandModels.map(parseBrandModelSpec) : void 0;
3769
4005
  if (brandModels) validateBrandModelSpecs(brandModels);
4006
+ const rawProducts = parsed.array["product"] ?? [];
4007
+ const products = rawProducts.length > 0 ? rawProducts.map(parseProductSpec) : void 0;
4008
+ if (products) validateProductSpecs(products);
3770
4009
  const result = await generateCreate({
3771
4010
  prompt: parsed.string.prompt ?? parsed.positional[0],
3772
4011
  modelSlug,
@@ -3775,6 +4014,7 @@ async function runGenerateCreate(argv, ctx, rc) {
3775
4014
  resolution: parsed.string.resolution,
3776
4015
  durationSeconds: optionalInt(parsed.string["duration-seconds"], "duration-seconds"),
3777
4016
  brandModels,
4017
+ products,
3778
4018
  workflowId: parsed.string["workflow-id"],
3779
4019
  nodeId: parsed.string["node-id"],
3780
4020
  wait: computeWaitFlag(parsed),
@@ -3999,6 +4239,60 @@ function emitTemplateDetail(detail, ctx, rc) {
3999
4239
  }
4000
4240
  renderTemplateDetail(detail, rc.stdout);
4001
4241
  }
4242
+ var PRODUCTS_SCOPE_HINT = "Your CLI token doesn't include products:read. Run `clickraft login` to re-authenticate with the new scope.";
4243
+ async function runProductList(argv, ctx, rc) {
4244
+ const parsed = parseArgs(argv, {
4245
+ string: [
4246
+ ...COMMON_RICH_STRING_FLAGS,
4247
+ "cursor",
4248
+ "limit",
4249
+ "sync-status",
4250
+ "source-type",
4251
+ "search"
4252
+ ],
4253
+ boolean: [...COMMON_RICH_BOOL_FLAGS]
4254
+ });
4255
+ const limit = optionalInt(parsed.string.limit, "limit");
4256
+ if (limit !== void 0 && (limit < 1 || limit > 100)) {
4257
+ throw new ArgError("--limit must be between 1 and 100.");
4258
+ }
4259
+ try {
4260
+ const result = await listProducts({
4261
+ cursor: parsed.string.cursor,
4262
+ limit,
4263
+ syncStatus: parsed.string["sync-status"],
4264
+ sourceType: parsed.string["source-type"],
4265
+ search: parsed.string.search,
4266
+ profile: parsed.string.profile,
4267
+ apiBaseUrl: parsed.string["api-base-url"],
4268
+ token: parsed.string.token,
4269
+ signal: rc.signal
4270
+ });
4271
+ ctx.nextCursor = result.nextCursor ?? void 0;
4272
+ emitProductList(result, ctx, rc);
4273
+ return 0;
4274
+ } catch (err) {
4275
+ if (err instanceof ApiError && err.code === "AUTH_TOKEN_SCOPE_INSUFFICIENT") {
4276
+ throw new ApiError(
4277
+ err.code,
4278
+ PRODUCTS_SCOPE_HINT,
4279
+ { ...err.meta, hint: PRODUCTS_SCOPE_HINT },
4280
+ err.cause
4281
+ );
4282
+ }
4283
+ throw err;
4284
+ }
4285
+ }
4286
+ function emitProductList(result, ctx, rc) {
4287
+ if (rc.signal?.aborted) {
4288
+ throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4289
+ }
4290
+ if (rc.jsonMode) {
4291
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
4292
+ return;
4293
+ }
4294
+ renderProductsList(result, rc.stdout);
4295
+ }
4002
4296
  async function runModelsList(argv, ctx, rc) {
4003
4297
  const parsed = parseArgs(argv, {
4004
4298
  string: [...COMMON_RICH_STRING_FLAGS, "category", "tier", "type"],