@clickraft/cli 0.5.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/CHANGELOG.md +14 -2
- package/dist/cli.js +335 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.js +335 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
## [0.
|
|
1
|
+
## [0.6.0](https://github.com/clickraft/cli/compare/v0.5.0...v0.6.0) (2026-05-25)
|
|
2
2
|
|
|
3
3
|
### Features
|
|
4
4
|
|
|
5
|
-
* **
|
|
5
|
+
* **errors:** add PRODUCT_* server error codes ([b60a018](https://github.com/clickraft/cli/commit/b60a0184f57d52d8a423631adeea69d47e8f28fc))
|
|
6
|
+
* **products:** add --product flag to generate create ([41a8948](https://github.com/clickraft/cli/commit/41a8948ee79c63abffe891411c0a36c67059d2d5))
|
|
7
|
+
* **products:** add product list command with cursor pagination ([ca4c343](https://github.com/clickraft/cli/commit/ca4c3433e5e3aa7c53ccdd5ab26bac3775d37608))
|
|
8
|
+
* **products:** show product and primary image IDs in list render ([2dc1d65](https://github.com/clickraft/cli/commit/2dc1d653ada365ceb0f1fa68697df71e24045633))
|
|
9
|
+
* **products:** wire product commands into CLI dispatch ([c458355](https://github.com/clickraft/cli/commit/c45835599fe546f8a19ad6b08eb35ee772299d49))
|
|
10
|
+
* **scopes:** request products:read in OAuth login ([ad67816](https://github.com/clickraft/cli/commit/ad67816e359de8198f2c5423de9f6ab9671f7418))
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **products:** align scope-insufficient hint in error message and Try line ([6f6cb1e](https://github.com/clickraft/cli/commit/6f6cb1e935e9e65b7039f4423acdb0569772892d))
|
|
15
|
+
* **products:** honor meta.hint in JSON error envelope ([f39b516](https://github.com/clickraft/cli/commit/f39b51668b29d5d78166933d56bd4f3954039ebe))
|
|
16
|
+
* **products:** tolerate forward-compat additive fields in list response ([cfe7bf2](https://github.com/clickraft/cli/commit/cfe7bf25dc18710583408dea9dc1e975449ce13a))
|
|
17
|
+
* **products:** validate --limit range and correct login help scope count ([d1341d9](https://github.com/clickraft/cli/commit/d1341d929f80f87407ec89c9abff5c214cd3b52a))
|
|
6
18
|
|
|
7
19
|
# Changelog
|
|
8
20
|
|
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,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
|
];
|
|
@@ -2512,6 +2614,68 @@ async function listModels(opts) {
|
|
|
2512
2614
|
return data;
|
|
2513
2615
|
}
|
|
2514
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
|
+
|
|
2515
2679
|
// src/telemetry/detect.ts
|
|
2516
2680
|
var AGENT_SIGNALS = [
|
|
2517
2681
|
{ envVar: "CLAUDE_CODE_SESSION_ID", agent: "claude-code" },
|
|
@@ -2602,33 +2766,33 @@ async function buildTemplateClient(opts) {
|
|
|
2602
2766
|
}
|
|
2603
2767
|
|
|
2604
2768
|
// src/schemas/templates.ts
|
|
2605
|
-
import { z as
|
|
2606
|
-
var DeclaredParamSchema =
|
|
2607
|
-
name:
|
|
2608
|
-
type:
|
|
2609
|
-
required:
|
|
2610
|
-
defaultValue:
|
|
2611
|
-
enumValues:
|
|
2612
|
-
description:
|
|
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()
|
|
2613
2777
|
}).strict();
|
|
2614
|
-
var TemplateSummarySchema =
|
|
2615
|
-
id:
|
|
2616
|
-
source:
|
|
2617
|
-
name:
|
|
2618
|
-
description:
|
|
2619
|
-
category:
|
|
2620
|
-
thumbnailUrl:
|
|
2621
|
-
tags:
|
|
2622
|
-
createdAt:
|
|
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()
|
|
2623
2787
|
}).strict();
|
|
2624
2788
|
var TemplateDetailSchema = TemplateSummarySchema.extend({
|
|
2625
|
-
declaredParams:
|
|
2626
|
-
timesUsed:
|
|
2789
|
+
declaredParams: z10.array(DeclaredParamSchema),
|
|
2790
|
+
timesUsed: z10.number()
|
|
2627
2791
|
}).strict();
|
|
2628
|
-
var TemplatesListResponseSchema =
|
|
2629
|
-
templates:
|
|
2630
|
-
cursor:
|
|
2631
|
-
hasMore:
|
|
2792
|
+
var TemplatesListResponseSchema = z10.object({
|
|
2793
|
+
templates: z10.array(TemplateSummarySchema),
|
|
2794
|
+
cursor: z10.string().nullable(),
|
|
2795
|
+
hasMore: z10.boolean()
|
|
2632
2796
|
}).strict();
|
|
2633
2797
|
|
|
2634
2798
|
// src/commands/template/list.ts
|
|
@@ -2678,20 +2842,20 @@ async function getTemplate(opts) {
|
|
|
2678
2842
|
}
|
|
2679
2843
|
|
|
2680
2844
|
// 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:
|
|
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()
|
|
2693
2857
|
}).strict();
|
|
2694
|
-
var TokensListResponseSchema =
|
|
2858
|
+
var TokensListResponseSchema = z11.array(TokenRowSchema);
|
|
2695
2859
|
async function listTokens(options) {
|
|
2696
2860
|
const config = await loadConfig({
|
|
2697
2861
|
flagProfile: options.profile,
|
|
@@ -2744,7 +2908,7 @@ function errorToView(err) {
|
|
|
2744
2908
|
return {
|
|
2745
2909
|
code: toEnvelopeCode(err.code),
|
|
2746
2910
|
message: err.message,
|
|
2747
|
-
hint: EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
|
|
2911
|
+
hint: err.meta.hint ?? EXIT_CODE_MAP[err.code]?.hint ?? FALLBACK_HINT,
|
|
2748
2912
|
requestId: err.meta.requestId,
|
|
2749
2913
|
details: formatDetails(err.meta.details)
|
|
2750
2914
|
};
|
|
@@ -3004,6 +3168,39 @@ function renderBrandModelsList(models, out) {
|
|
|
3004
3168
|
out.write(padRow(headers) + "\n");
|
|
3005
3169
|
for (const cells of data) out.write(padRow(cells) + "\n");
|
|
3006
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
|
+
}
|
|
3007
3204
|
function renderTemplatesList(result, out) {
|
|
3008
3205
|
const { templates } = result;
|
|
3009
3206
|
if (templates.length === 0) {
|
|
@@ -3351,6 +3548,7 @@ function renderRootHelp(version) {
|
|
|
3351
3548
|
" tokens list List the caller's API tokens",
|
|
3352
3549
|
" balance Show credit balance and rate limits",
|
|
3353
3550
|
" brand-model list List available brand models",
|
|
3551
|
+
" product list List products in your organization",
|
|
3354
3552
|
" models list List available AI models",
|
|
3355
3553
|
" telemetry inspect Show current telemetry state and payload shape",
|
|
3356
3554
|
" template list List available templates",
|
|
@@ -3379,7 +3577,7 @@ function renderCommandHelp(command) {
|
|
|
3379
3577
|
"Options:",
|
|
3380
3578
|
' --profile <name> Write to a specific profile (default: "default")',
|
|
3381
3579
|
" --api-base-url <url> Override the API base URL",
|
|
3382
|
-
|
|
3580
|
+
` --scope <s> Space-separated scope list (default: all ${DEFAULT_SCOPES.length})`,
|
|
3383
3581
|
" --force-reauth Re-authenticate even if a valid token exists",
|
|
3384
3582
|
" --no-open-browser Do not auto-open the verification URL",
|
|
3385
3583
|
" --json Emit JSON envelope instead of human output",
|
|
@@ -3431,6 +3629,32 @@ function renderCommandHelp(command) {
|
|
|
3431
3629
|
" --json Emit JSON envelope instead of human output",
|
|
3432
3630
|
""
|
|
3433
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");
|
|
3434
3658
|
case "models":
|
|
3435
3659
|
return [
|
|
3436
3660
|
"Usage: clickraft models <subcommand> [options]",
|
|
@@ -3550,6 +3774,9 @@ function renderCommandHelp(command) {
|
|
|
3550
3774
|
" --brand-model <spec> Brand Model reference (repeatable, max 3)",
|
|
3551
3775
|
" Format: <uuid> or <uuid>:<pose>",
|
|
3552
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",
|
|
3553
3780
|
" --reference-image <v> URL or local path (repeatable, max 8)",
|
|
3554
3781
|
" --aspect-ratio <a:b> e.g. 16:9",
|
|
3555
3782
|
" --resolution <WxH> e.g. 1024x768",
|
|
@@ -3658,6 +3885,12 @@ function matchNewCommand(argv) {
|
|
|
3658
3885
|
if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "template" };
|
|
3659
3886
|
return { kind: "unknown-sub", verb: "template", sub };
|
|
3660
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
|
+
}
|
|
3661
3894
|
if (verb === "models") {
|
|
3662
3895
|
const sub = argv[1];
|
|
3663
3896
|
if (sub === "list") return { kind: "cmd", path: "models.list", verbTokenCount: 2 };
|
|
@@ -3720,6 +3953,8 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
|
|
|
3720
3953
|
return await runTemplateGet(argvAfterVerb, ctx, rc);
|
|
3721
3954
|
case "template.find":
|
|
3722
3955
|
return await runTemplateFind(argvAfterVerb, ctx, rc);
|
|
3956
|
+
case "product.list":
|
|
3957
|
+
return await runProductList(argvAfterVerb, ctx, rc);
|
|
3723
3958
|
case "models.list":
|
|
3724
3959
|
return await runModelsList(argvAfterVerb, ctx, rc);
|
|
3725
3960
|
}
|
|
@@ -3760,13 +3995,16 @@ async function runGenerateCreate(argv, ctx, rc) {
|
|
|
3760
3995
|
"timeout"
|
|
3761
3996
|
],
|
|
3762
3997
|
boolean: [...COMMON_RICH_BOOL_FLAGS, "wait", "async"],
|
|
3763
|
-
array: ["reference-image", "brand-model"]
|
|
3998
|
+
array: ["reference-image", "brand-model", "product"]
|
|
3764
3999
|
});
|
|
3765
4000
|
const modelSlug = parsed.string["model-slug"];
|
|
3766
4001
|
if (!modelSlug) throw new ArgError("--model-slug is required.");
|
|
3767
4002
|
const rawBrandModels = parsed.array["brand-model"] ?? [];
|
|
3768
4003
|
const brandModels = rawBrandModels.length > 0 ? rawBrandModels.map(parseBrandModelSpec) : void 0;
|
|
3769
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);
|
|
3770
4008
|
const result = await generateCreate({
|
|
3771
4009
|
prompt: parsed.string.prompt ?? parsed.positional[0],
|
|
3772
4010
|
modelSlug,
|
|
@@ -3775,6 +4013,7 @@ async function runGenerateCreate(argv, ctx, rc) {
|
|
|
3775
4013
|
resolution: parsed.string.resolution,
|
|
3776
4014
|
durationSeconds: optionalInt(parsed.string["duration-seconds"], "duration-seconds"),
|
|
3777
4015
|
brandModels,
|
|
4016
|
+
products,
|
|
3778
4017
|
workflowId: parsed.string["workflow-id"],
|
|
3779
4018
|
nodeId: parsed.string["node-id"],
|
|
3780
4019
|
wait: computeWaitFlag(parsed),
|
|
@@ -3999,6 +4238,60 @@ function emitTemplateDetail(detail, ctx, rc) {
|
|
|
3999
4238
|
}
|
|
4000
4239
|
renderTemplateDetail(detail, rc.stdout);
|
|
4001
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
|
+
}
|
|
4002
4295
|
async function runModelsList(argv, ctx, rc) {
|
|
4003
4296
|
const parsed = parseArgs(argv, {
|
|
4004
4297
|
string: [...COMMON_RICH_STRING_FLAGS, "category", "tier", "type"],
|