@clickraft/cli 0.4.0 → 0.6.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
@@ -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,7 @@ var DEFAULT_SCOPES = [
2315
2416
  "workflows:write",
2316
2417
  "workflows:read",
2317
2418
  "brand-models:read",
2419
+ "products:read",
2318
2420
  "assets:write",
2319
2421
  "balance:read"
2320
2422
  ];
@@ -2441,6 +2543,139 @@ function formatRevokeFailure(err, token) {
2441
2543
  return `warning: server-side revoke failed (${reason}). Local profile removed; token ${redactTokenLikeValue(token)} remains server-valid until expiry.`;
2442
2544
  }
2443
2545
 
2546
+ // src/schemas/models.ts
2547
+ import { z as z8 } from "zod";
2548
+ var ModelSummarySchema = z8.object({
2549
+ id: z8.string().uuid(),
2550
+ slug: z8.string(),
2551
+ displayName: z8.string(),
2552
+ description: z8.string().nullable(),
2553
+ shortDescription: z8.string().nullable(),
2554
+ providerSlug: z8.string(),
2555
+ providerName: z8.string(),
2556
+ modelType: z8.string(),
2557
+ category: z8.string(),
2558
+ costTier: z8.string(),
2559
+ creditCost: z8.number(),
2560
+ pricingStrategy: z8.string(),
2561
+ estimatedSeconds: z8.object({
2562
+ typical: z8.number(),
2563
+ range: z8.array(z8.number())
2564
+ }).passthrough().nullable(),
2565
+ status: z8.string(),
2566
+ modalities: z8.object({
2567
+ input: z8.array(z8.string()),
2568
+ output: z8.array(z8.string())
2569
+ }).passthrough().nullable(),
2570
+ bestFor: z8.array(z8.string()),
2571
+ badges: z8.array(z8.string()),
2572
+ isFeatured: z8.boolean(),
2573
+ isDefault: z8.boolean(),
2574
+ docsUrl: z8.string().nullable(),
2575
+ llmContext: z8.string().nullable(),
2576
+ constraints: z8.record(z8.unknown()).nullable(),
2577
+ modelFamily: z8.string().nullable(),
2578
+ isFamilyPrimary: z8.boolean().nullable()
2579
+ }).strict();
2580
+ var ModelsListResponseSchema = z8.object({
2581
+ models: z8.array(ModelSummarySchema)
2582
+ }).strict();
2583
+
2584
+ // src/commands/models/internal.ts
2585
+ async function buildModelClient(opts) {
2586
+ const cfg = await loadConfig({
2587
+ flagProfile: opts.profile,
2588
+ flagApiBaseUrl: opts.apiBaseUrl,
2589
+ flagToken: opts.token
2590
+ });
2591
+ return new ApiClient({
2592
+ apiBaseUrl: cfg.apiBaseUrl,
2593
+ accessToken: cfg.accessToken,
2594
+ signal: opts.signal,
2595
+ cloudflareAccess: cfg.cloudflareAccess
2596
+ });
2597
+ }
2598
+
2599
+ // src/commands/models/list.ts
2600
+ async function listModels(opts) {
2601
+ const client = opts.client ?? await buildModelClient(opts);
2602
+ const data = await client.request({
2603
+ method: "GET",
2604
+ path: "/models",
2605
+ query: {
2606
+ category: opts.category,
2607
+ tier: opts.tier,
2608
+ type: opts.type,
2609
+ featured: opts.featured !== void 0 ? String(opts.featured) : void 0
2610
+ },
2611
+ responseSchema: ModelsListResponseSchema,
2612
+ signal: opts.signal
2613
+ });
2614
+ return data;
2615
+ }
2616
+
2617
+ // src/commands/product/list.ts
2618
+ import { z as z9 } from "zod";
2619
+ var ProductImageSummarySchema = z9.object({
2620
+ id: z9.string().uuid(),
2621
+ url: z9.string(),
2622
+ altText: z9.string().nullable(),
2623
+ isPrimary: z9.boolean(),
2624
+ sortOrder: z9.number().int()
2625
+ });
2626
+ var ProductSummarySchema = z9.object({
2627
+ id: z9.string().uuid(),
2628
+ title: z9.string(),
2629
+ sourceType: z9.string(),
2630
+ syncStatus: z9.enum(["synced", "pending", "stale", "error", "deleted_upstream"]).nullable(),
2631
+ vendor: z9.string().nullable(),
2632
+ productType: z9.string().nullable(),
2633
+ tags: z9.array(z9.string()),
2634
+ images: z9.array(ProductImageSummarySchema)
2635
+ });
2636
+ var ProductsListResponseSchema = z9.object({
2637
+ items: z9.array(ProductSummarySchema),
2638
+ nextCursor: z9.string().nullable()
2639
+ });
2640
+ var DEFAULT_LIMIT = 50;
2641
+ async function listProducts(options) {
2642
+ const config = await loadConfig({
2643
+ flagProfile: options.profile,
2644
+ flagApiBaseUrl: options.apiBaseUrl,
2645
+ flagToken: options.token,
2646
+ credentialsPath: options.credentialsPath
2647
+ });
2648
+ if (!config.accessToken) {
2649
+ throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
2650
+ }
2651
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
2652
+ apiBaseUrl,
2653
+ accessToken,
2654
+ signal: options.signal,
2655
+ cloudflareAccess: config.cloudflareAccess
2656
+ }));
2657
+ const client = factory(config.apiBaseUrl, config.accessToken);
2658
+ const query = {
2659
+ limit: options.limit ?? DEFAULT_LIMIT
2660
+ };
2661
+ if (options.cursor) query.cursor = options.cursor;
2662
+ if (options.syncStatus) query.syncStatus = options.syncStatus;
2663
+ if (options.sourceType) query.sourceType = options.sourceType;
2664
+ if (options.search) query.search = options.search;
2665
+ const data = await client.request({
2666
+ method: "GET",
2667
+ path: "/products",
2668
+ query,
2669
+ responseSchema: ProductsListResponseSchema,
2670
+ signal: options.signal
2671
+ });
2672
+ return {
2673
+ products: data.items,
2674
+ nextCursor: data.nextCursor,
2675
+ hasMore: data.nextCursor !== null
2676
+ };
2677
+ }
2678
+
2444
2679
  // src/telemetry/detect.ts
2445
2680
  var AGENT_SIGNALS = [
2446
2681
  { envVar: "CLAUDE_CODE_SESSION_ID", agent: "claude-code" },
@@ -2531,33 +2766,33 @@ async function buildTemplateClient(opts) {
2531
2766
  }
2532
2767
 
2533
2768
  // src/schemas/templates.ts
2534
- import { z as z8 } from "zod";
2535
- var DeclaredParamSchema = z8.object({
2536
- name: z8.string(),
2537
- type: z8.string(),
2538
- required: z8.boolean(),
2539
- defaultValue: z8.string().nullable().optional(),
2540
- enumValues: z8.array(z8.string()).nullable().optional(),
2541
- description: z8.string().nullable().optional()
2769
+ import { z as z10 } from "zod";
2770
+ var DeclaredParamSchema = z10.object({
2771
+ name: z10.string(),
2772
+ type: z10.string(),
2773
+ required: z10.boolean(),
2774
+ defaultValue: z10.string().nullable().optional(),
2775
+ enumValues: z10.array(z10.string()).nullable().optional(),
2776
+ description: z10.string().nullable().optional()
2542
2777
  }).strict();
2543
- var TemplateSummarySchema = z8.object({
2544
- id: z8.string().uuid(),
2545
- source: z8.enum(["own", "system"]),
2546
- name: z8.string(),
2547
- description: z8.string().nullable(),
2548
- category: z8.string().nullable(),
2549
- thumbnailUrl: z8.string().nullable(),
2550
- tags: z8.array(z8.string()),
2551
- createdAt: z8.string()
2778
+ var TemplateSummarySchema = z10.object({
2779
+ id: z10.string().uuid(),
2780
+ source: z10.enum(["own", "system"]),
2781
+ name: z10.string(),
2782
+ description: z10.string().nullable(),
2783
+ category: z10.string().nullable(),
2784
+ thumbnailUrl: z10.string().nullable(),
2785
+ tags: z10.array(z10.string()),
2786
+ createdAt: z10.string()
2552
2787
  }).strict();
2553
2788
  var TemplateDetailSchema = TemplateSummarySchema.extend({
2554
- declaredParams: z8.array(DeclaredParamSchema),
2555
- timesUsed: z8.number()
2789
+ declaredParams: z10.array(DeclaredParamSchema),
2790
+ timesUsed: z10.number()
2556
2791
  }).strict();
2557
- var TemplatesListResponseSchema = z8.object({
2558
- templates: z8.array(TemplateSummarySchema),
2559
- cursor: z8.string().nullable(),
2560
- hasMore: z8.boolean()
2792
+ var TemplatesListResponseSchema = z10.object({
2793
+ templates: z10.array(TemplateSummarySchema),
2794
+ cursor: z10.string().nullable(),
2795
+ hasMore: z10.boolean()
2561
2796
  }).strict();
2562
2797
 
2563
2798
  // src/commands/template/list.ts
@@ -2607,20 +2842,20 @@ async function getTemplate(opts) {
2607
2842
  }
2608
2843
 
2609
2844
  // src/commands/tokens/list.ts
2610
- import { z as z9 } from "zod";
2611
- var TokenRowSchema = z9.object({
2612
- id: z9.string().uuid(),
2613
- organizationId: z9.string().uuid(),
2614
- userId: z9.string().uuid(),
2615
- clientId: z9.string().nullable(),
2616
- tokenPrefix: z9.string(),
2617
- name: z9.string().nullable(),
2618
- scopes: z9.array(z9.string()),
2619
- lastUsedAt: z9.string().nullable(),
2620
- expiresAt: z9.string(),
2621
- createdAt: z9.string()
2845
+ import { z as z11 } from "zod";
2846
+ var TokenRowSchema = z11.object({
2847
+ id: z11.string().uuid(),
2848
+ organizationId: z11.string().uuid(),
2849
+ userId: z11.string().uuid(),
2850
+ clientId: z11.string().nullable(),
2851
+ tokenPrefix: z11.string(),
2852
+ name: z11.string().nullable(),
2853
+ scopes: z11.array(z11.string()),
2854
+ lastUsedAt: z11.string().nullable(),
2855
+ expiresAt: z11.string(),
2856
+ createdAt: z11.string()
2622
2857
  }).strict();
2623
- var TokensListResponseSchema = z9.array(TokenRowSchema);
2858
+ var TokensListResponseSchema = z11.array(TokenRowSchema);
2624
2859
  async function listTokens(options) {
2625
2860
  const config = await loadConfig({
2626
2861
  flagProfile: options.profile,
@@ -2673,7 +2908,7 @@ function errorToView(err) {
2673
2908
  return {
2674
2909
  code: toEnvelopeCode(err.code),
2675
2910
  message: err.message,
2676
- hint: EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
2911
+ hint: err.meta.hint ?? EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
2677
2912
  requestId: err.meta.requestId,
2678
2913
  details: formatDetails(err.meta.details)
2679
2914
  };
@@ -2933,6 +3168,39 @@ function renderBrandModelsList(models, out) {
2933
3168
  out.write(padRow(headers) + "\n");
2934
3169
  for (const cells of data) out.write(padRow(cells) + "\n");
2935
3170
  }
3171
+ function renderProductsList(result, out) {
3172
+ const { products } = result;
3173
+ if (products.length === 0) {
3174
+ out.write("No products.\n");
3175
+ return;
3176
+ }
3177
+ const MAX_TAGS = 3;
3178
+ const headers = ["ID", "TITLE", "SOURCE", "SYNC", "IMAGES", "PRIMARY IMAGE", "VENDOR", "TAGS"];
3179
+ const data = products.map((p) => {
3180
+ const shown = p.tags.slice(0, MAX_TAGS).join(", ");
3181
+ const extra = p.tags.length > MAX_TAGS ? ` +${p.tags.length - MAX_TAGS} more` : "";
3182
+ const primary = p.images.find((img) => img.isPrimary) ?? p.images.find((img) => img.sortOrder === 0);
3183
+ return [
3184
+ p.id,
3185
+ p.title,
3186
+ p.sourceType,
3187
+ p.syncStatus ?? "\u2014",
3188
+ String(p.images.length),
3189
+ primary?.id ?? "\u2014",
3190
+ p.vendor ?? "\u2014",
3191
+ p.tags.length > 0 ? shown + extra : "\u2014"
3192
+ ];
3193
+ });
3194
+ const widths = headers.map(
3195
+ (header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
3196
+ );
3197
+ const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
3198
+ out.write(padRow(headers) + "\n");
3199
+ for (const cells of data) out.write(padRow(cells) + "\n");
3200
+ if (result.hasMore) {
3201
+ out.write("\nMore results available. Use --cursor to fetch the next page.\n");
3202
+ }
3203
+ }
2936
3204
  function renderTemplatesList(result, out) {
2937
3205
  const { templates } = result;
2938
3206
  if (templates.length === 0) {
@@ -2988,6 +3256,72 @@ function renderTemplateDetail(detail, out) {
2988
3256
  }
2989
3257
  out.write(lines.join("\n") + "\n");
2990
3258
  }
3259
+ var TIER_ORDER = { premium: 0, pro: 1, standard: 2 };
3260
+ function renderModelsList(result, out, options) {
3261
+ const { models } = result;
3262
+ if (models.length === 0) {
3263
+ out.write("No models match your filters.\n");
3264
+ return;
3265
+ }
3266
+ const sorted = [...models].sort((a, b) => {
3267
+ const catCmp = a.category.localeCompare(b.category);
3268
+ if (catCmp !== 0) return catCmp;
3269
+ const tierA = TIER_ORDER[a.costTier] ?? 99;
3270
+ const tierB = TIER_ORDER[b.costTier] ?? 99;
3271
+ if (tierA !== tierB) return tierA - tierB;
3272
+ return a.slug.localeCompare(b.slug);
3273
+ });
3274
+ const filterParts = [];
3275
+ if (options?.filters) {
3276
+ for (const [k, v] of Object.entries(options.filters)) {
3277
+ if (v) filterParts.push(`${k}=${v}`);
3278
+ }
3279
+ }
3280
+ const filterSuffix = filterParts.length > 0 ? ` (filters: ${filterParts.join(", ")})` : "";
3281
+ out.write(`${sorted.length} models${filterSuffix}
3282
+
3283
+ `);
3284
+ const headers = ["SLUG", "NAME", "CATEGORY", "TIER", "CREDITS", "DEFAULT"];
3285
+ const data = sorted.map((m) => [
3286
+ m.slug,
3287
+ m.displayName,
3288
+ m.category,
3289
+ m.costTier,
3290
+ String(m.creditCost),
3291
+ m.isDefault ? "\u2713" : ""
3292
+ ]);
3293
+ const widths = headers.map(
3294
+ (header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
3295
+ );
3296
+ const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
3297
+ out.write(padRow(headers) + "\n");
3298
+ for (const cells of data) out.write(padRow(cells) + "\n");
3299
+ out.write(
3300
+ `
3301
+ Showing all ${sorted.length} models. Use --json for full details including constraints.
3302
+ `
3303
+ );
3304
+ if (options?.verbose) {
3305
+ out.write("\n");
3306
+ for (const m of sorted) {
3307
+ const pad = 16;
3308
+ out.write(`${m.slug} (${m.displayName})
3309
+ `);
3310
+ out.write(`${" Provider:".padEnd(pad)}${m.providerName}
3311
+ `);
3312
+ out.write(`${" Category:".padEnd(pad)}${m.category} / ${m.modelType}
3313
+ `);
3314
+ out.write(`${" Tier:".padEnd(pad)}${m.costTier} (${m.creditCost} credits)
3315
+ `);
3316
+ if (m.llmContext) {
3317
+ const ctx = m.llmContext.length > 100 ? m.llmContext.slice(0, 100) + "..." : m.llmContext;
3318
+ out.write(`${" Context:".padEnd(pad)}${ctx}
3319
+ `);
3320
+ }
3321
+ out.write("\n");
3322
+ }
3323
+ }
3324
+ }
2991
3325
  function formatExpiresAt(iso) {
2992
3326
  const ms = Date.parse(iso);
2993
3327
  if (Number.isNaN(ms)) return iso;
@@ -3214,6 +3548,8 @@ function renderRootHelp(version) {
3214
3548
  " tokens list List the caller's API tokens",
3215
3549
  " balance Show credit balance and rate limits",
3216
3550
  " brand-model list List available brand models",
3551
+ " product list List products in your organization",
3552
+ " models list List available AI models",
3217
3553
  " telemetry inspect Show current telemetry state and payload shape",
3218
3554
  " template list List available templates",
3219
3555
  " template get Fetch a template by ID",
@@ -3241,7 +3577,7 @@ function renderCommandHelp(command) {
3241
3577
  "Options:",
3242
3578
  ' --profile <name> Write to a specific profile (default: "default")',
3243
3579
  " --api-base-url <url> Override the API base URL",
3244
- " --scope <s> Space-separated scope list (default: all 8)",
3580
+ ` --scope <s> Space-separated scope list (default: all ${DEFAULT_SCOPES.length})`,
3245
3581
  " --force-reauth Re-authenticate even if a valid token exists",
3246
3582
  " --no-open-browser Do not auto-open the verification URL",
3247
3583
  " --json Emit JSON envelope instead of human output",
@@ -3293,6 +3629,64 @@ function renderCommandHelp(command) {
3293
3629
  " --json Emit JSON envelope instead of human output",
3294
3630
  ""
3295
3631
  ].join("\n");
3632
+ case "product":
3633
+ return [
3634
+ "Usage: clickraft product <subcommand> [options]",
3635
+ "",
3636
+ "Subcommands:",
3637
+ " list List products in your organization",
3638
+ ""
3639
+ ].join("\n");
3640
+ case "product list":
3641
+ return [
3642
+ "Usage: clickraft product list [options]",
3643
+ "",
3644
+ "List products in your organization. Supports cursor-based pagination.",
3645
+ "",
3646
+ "Options:",
3647
+ " --cursor <token> Pagination cursor from a previous response",
3648
+ " --limit <n> Max results per page (1-100, default: 50)",
3649
+ " --sync-status <s> Filter by sync status (synced, pending, stale, error, deleted_upstream)",
3650
+ " --source-type <s> Filter by source type (manual, csv, shopify, etsy, etc.)",
3651
+ " --search <q> Free-text search query",
3652
+ " --profile <name>",
3653
+ " --api-base-url <url>",
3654
+ " --token <token>",
3655
+ " --json",
3656
+ ""
3657
+ ].join("\n");
3658
+ case "models":
3659
+ return [
3660
+ "Usage: clickraft models <subcommand> [options]",
3661
+ "",
3662
+ "Subcommands:",
3663
+ " list List available AI models",
3664
+ ""
3665
+ ].join("\n");
3666
+ case "models list":
3667
+ return [
3668
+ "Usage: clickraft models list [options]",
3669
+ "",
3670
+ "List AI models available for generation. By default shows a summary table;",
3671
+ "use --json for full details including constraints and llmContext.",
3672
+ "",
3673
+ "Options:",
3674
+ " --category <name> Filter by category (image, video, audio, chat, edit)",
3675
+ " --tier <name> Filter by cost tier (standard, pro, premium)",
3676
+ " --type <name> Filter by model type (e.g. text-to-image)",
3677
+ " --featured Show only featured models",
3678
+ " --verbose Show expanded per-model details after the table",
3679
+ " --profile <name>",
3680
+ " --api-base-url <url>",
3681
+ " --token <token>",
3682
+ " --json",
3683
+ "",
3684
+ "Examples:",
3685
+ " clickraft models list",
3686
+ " clickraft models list --category image --tier pro",
3687
+ " clickraft models list --featured --json",
3688
+ ""
3689
+ ].join("\n");
3296
3690
  case "telemetry inspect":
3297
3691
  return [
3298
3692
  "Usage: clickraft telemetry inspect [options]",
@@ -3380,6 +3774,9 @@ function renderCommandHelp(command) {
3380
3774
  " --brand-model <spec> Brand Model reference (repeatable, max 3)",
3381
3775
  " Format: <uuid> or <uuid>:<pose>",
3382
3776
  " Poses: front, 3/4-right, right, left, 3/4-left, back, approved",
3777
+ " --product <spec> Product reference (repeatable)",
3778
+ " Format: <uuid> or <uuid>:<imageId>",
3779
+ " Server enforces reference cap per model",
3383
3780
  " --reference-image <v> URL or local path (repeatable, max 8)",
3384
3781
  " --aspect-ratio <a:b> e.g. 16:9",
3385
3782
  " --resolution <WxH> e.g. 1024x768",
@@ -3488,6 +3885,18 @@ function matchNewCommand(argv) {
3488
3885
  if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "template" };
3489
3886
  return { kind: "unknown-sub", verb: "template", sub };
3490
3887
  }
3888
+ if (verb === "product") {
3889
+ const sub = argv[1];
3890
+ if (sub === "list") return { kind: "cmd", path: "product.list", verbTokenCount: 2 };
3891
+ if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "product" };
3892
+ return { kind: "unknown-sub", verb: "product", sub };
3893
+ }
3894
+ if (verb === "models") {
3895
+ const sub = argv[1];
3896
+ if (sub === "list") return { kind: "cmd", path: "models.list", verbTokenCount: 2 };
3897
+ if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "models" };
3898
+ return { kind: "unknown-sub", verb: "models", sub };
3899
+ }
3491
3900
  return null;
3492
3901
  }
3493
3902
  async function runNewCommand(newCmd, options, stdout, stderr) {
@@ -3544,6 +3953,10 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
3544
3953
  return await runTemplateGet(argvAfterVerb, ctx, rc);
3545
3954
  case "template.find":
3546
3955
  return await runTemplateFind(argvAfterVerb, ctx, rc);
3956
+ case "product.list":
3957
+ return await runProductList(argvAfterVerb, ctx, rc);
3958
+ case "models.list":
3959
+ return await runModelsList(argvAfterVerb, ctx, rc);
3547
3960
  }
3548
3961
  } catch (err) {
3549
3962
  if (isAbortedError(err, options.signal)) {
@@ -3582,13 +3995,16 @@ async function runGenerateCreate(argv, ctx, rc) {
3582
3995
  "timeout"
3583
3996
  ],
3584
3997
  boolean: [...COMMON_RICH_BOOL_FLAGS, "wait", "async"],
3585
- array: ["reference-image", "brand-model"]
3998
+ array: ["reference-image", "brand-model", "product"]
3586
3999
  });
3587
4000
  const modelSlug = parsed.string["model-slug"];
3588
4001
  if (!modelSlug) throw new ArgError("--model-slug is required.");
3589
4002
  const rawBrandModels = parsed.array["brand-model"] ?? [];
3590
4003
  const brandModels = rawBrandModels.length > 0 ? rawBrandModels.map(parseBrandModelSpec) : void 0;
3591
4004
  if (brandModels) validateBrandModelSpecs(brandModels);
4005
+ const rawProducts = parsed.array["product"] ?? [];
4006
+ const products = rawProducts.length > 0 ? rawProducts.map(parseProductSpec) : void 0;
4007
+ if (products) validateProductSpecs(products);
3592
4008
  const result = await generateCreate({
3593
4009
  prompt: parsed.string.prompt ?? parsed.positional[0],
3594
4010
  modelSlug,
@@ -3597,6 +4013,7 @@ async function runGenerateCreate(argv, ctx, rc) {
3597
4013
  resolution: parsed.string.resolution,
3598
4014
  durationSeconds: optionalInt(parsed.string["duration-seconds"], "duration-seconds"),
3599
4015
  brandModels,
4016
+ products,
3600
4017
  workflowId: parsed.string["workflow-id"],
3601
4018
  nodeId: parsed.string["node-id"],
3602
4019
  wait: computeWaitFlag(parsed),
@@ -3821,6 +4238,96 @@ function emitTemplateDetail(detail, ctx, rc) {
3821
4238
  }
3822
4239
  renderTemplateDetail(detail, rc.stdout);
3823
4240
  }
4241
+ var PRODUCTS_SCOPE_HINT = "Your CLI token doesn't include products:read. Run `clickraft login` to re-authenticate with the new scope.";
4242
+ async function runProductList(argv, ctx, rc) {
4243
+ const parsed = parseArgs(argv, {
4244
+ string: [
4245
+ ...COMMON_RICH_STRING_FLAGS,
4246
+ "cursor",
4247
+ "limit",
4248
+ "sync-status",
4249
+ "source-type",
4250
+ "search"
4251
+ ],
4252
+ boolean: [...COMMON_RICH_BOOL_FLAGS]
4253
+ });
4254
+ const limit = optionalInt(parsed.string.limit, "limit");
4255
+ if (limit !== void 0 && (limit < 1 || limit > 100)) {
4256
+ throw new ArgError("--limit must be between 1 and 100.");
4257
+ }
4258
+ try {
4259
+ const result = await listProducts({
4260
+ cursor: parsed.string.cursor,
4261
+ limit,
4262
+ syncStatus: parsed.string["sync-status"],
4263
+ sourceType: parsed.string["source-type"],
4264
+ search: parsed.string.search,
4265
+ profile: parsed.string.profile,
4266
+ apiBaseUrl: parsed.string["api-base-url"],
4267
+ token: parsed.string.token,
4268
+ signal: rc.signal
4269
+ });
4270
+ ctx.nextCursor = result.nextCursor ?? void 0;
4271
+ emitProductList(result, ctx, rc);
4272
+ return 0;
4273
+ } catch (err) {
4274
+ if (err instanceof ApiError && err.code === "AUTH_TOKEN_SCOPE_INSUFFICIENT") {
4275
+ throw new ApiError(
4276
+ err.code,
4277
+ PRODUCTS_SCOPE_HINT,
4278
+ { ...err.meta, hint: PRODUCTS_SCOPE_HINT },
4279
+ err.cause
4280
+ );
4281
+ }
4282
+ throw err;
4283
+ }
4284
+ }
4285
+ function emitProductList(result, ctx, rc) {
4286
+ if (rc.signal?.aborted) {
4287
+ throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4288
+ }
4289
+ if (rc.jsonMode) {
4290
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
4291
+ return;
4292
+ }
4293
+ renderProductsList(result, rc.stdout);
4294
+ }
4295
+ async function runModelsList(argv, ctx, rc) {
4296
+ const parsed = parseArgs(argv, {
4297
+ string: [...COMMON_RICH_STRING_FLAGS, "category", "tier", "type"],
4298
+ boolean: [...COMMON_RICH_BOOL_FLAGS, "featured", "verbose"]
4299
+ });
4300
+ const result = await listModels({
4301
+ category: parsed.string.category,
4302
+ tier: parsed.string.tier,
4303
+ type: parsed.string.type,
4304
+ featured: parsed.boolean.featured,
4305
+ profile: parsed.string.profile,
4306
+ apiBaseUrl: parsed.string["api-base-url"],
4307
+ token: parsed.string.token,
4308
+ signal: rc.signal
4309
+ });
4310
+ emitModelsList(result, ctx, rc, parsed);
4311
+ return 0;
4312
+ }
4313
+ function emitModelsList(result, ctx, rc, parsed) {
4314
+ if (rc.signal?.aborted) {
4315
+ throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4316
+ }
4317
+ if (rc.jsonMode) {
4318
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
4319
+ return;
4320
+ }
4321
+ renderModelsList(result, rc.stdout, {
4322
+ verbose: parsed.boolean.verbose === true,
4323
+ filters: {
4324
+ category: parsed.string.category ?? "",
4325
+ tier: parsed.string.tier ?? "",
4326
+ type: parsed.string.type ?? "",
4327
+ featured: parsed.boolean.featured !== void 0 ? String(parsed.boolean.featured) : ""
4328
+ }
4329
+ });
4330
+ }
3824
4331
 
3825
4332
  // src/index.ts
3826
4333
  var __filename2 = fileURLToPath(import.meta.url);