@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 +3 -3
- package/dist/cli.js +336 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.js +336 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
## [0.
|
|
1
|
+
## [0.6.1](https://github.com/clickraft/cli/compare/v0.6.0...v0.6.1) (2026-05-25)
|
|
2
2
|
|
|
3
|
-
###
|
|
3
|
+
### Bug Fixes
|
|
4
4
|
|
|
5
|
-
* **
|
|
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
|
-
|
|
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
|
|
2606
|
-
var DeclaredParamSchema =
|
|
2607
|
-
name:
|
|
2608
|
-
type:
|
|
2609
|
-
required:
|
|
2610
|
-
defaultValue:
|
|
2611
|
-
enumValues:
|
|
2612
|
-
description:
|
|
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 =
|
|
2615
|
-
id:
|
|
2616
|
-
source:
|
|
2617
|
-
name:
|
|
2618
|
-
description:
|
|
2619
|
-
category:
|
|
2620
|
-
thumbnailUrl:
|
|
2621
|
-
tags:
|
|
2622
|
-
createdAt:
|
|
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:
|
|
2626
|
-
timesUsed:
|
|
2790
|
+
declaredParams: z10.array(DeclaredParamSchema),
|
|
2791
|
+
timesUsed: z10.number()
|
|
2627
2792
|
}).strict();
|
|
2628
|
-
var TemplatesListResponseSchema =
|
|
2629
|
-
templates:
|
|
2630
|
-
cursor:
|
|
2631
|
-
hasMore:
|
|
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
|
|
2682
|
-
var TokenRowSchema =
|
|
2683
|
-
id:
|
|
2684
|
-
organizationId:
|
|
2685
|
-
userId:
|
|
2686
|
-
clientId:
|
|
2687
|
-
tokenPrefix:
|
|
2688
|
-
name:
|
|
2689
|
-
scopes:
|
|
2690
|
-
lastUsedAt:
|
|
2691
|
-
expiresAt:
|
|
2692
|
-
createdAt:
|
|
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 =
|
|
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
|
-
|
|
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"],
|