@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/CHANGELOG.md +14 -3
- package/README.md +3 -0
- package/dist/cli.js +549 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.js +549 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
2535
|
-
var DeclaredParamSchema =
|
|
2536
|
-
name:
|
|
2537
|
-
type:
|
|
2538
|
-
required:
|
|
2539
|
-
defaultValue:
|
|
2540
|
-
enumValues:
|
|
2541
|
-
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()
|
|
2542
2777
|
}).strict();
|
|
2543
|
-
var TemplateSummarySchema =
|
|
2544
|
-
id:
|
|
2545
|
-
source:
|
|
2546
|
-
name:
|
|
2547
|
-
description:
|
|
2548
|
-
category:
|
|
2549
|
-
thumbnailUrl:
|
|
2550
|
-
tags:
|
|
2551
|
-
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()
|
|
2552
2787
|
}).strict();
|
|
2553
2788
|
var TemplateDetailSchema = TemplateSummarySchema.extend({
|
|
2554
|
-
declaredParams:
|
|
2555
|
-
timesUsed:
|
|
2789
|
+
declaredParams: z10.array(DeclaredParamSchema),
|
|
2790
|
+
timesUsed: z10.number()
|
|
2556
2791
|
}).strict();
|
|
2557
|
-
var TemplatesListResponseSchema =
|
|
2558
|
-
templates:
|
|
2559
|
-
cursor:
|
|
2560
|
-
hasMore:
|
|
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
|
|
2611
|
-
var TokenRowSchema =
|
|
2612
|
-
id:
|
|
2613
|
-
organizationId:
|
|
2614
|
-
userId:
|
|
2615
|
-
clientId:
|
|
2616
|
-
tokenPrefix:
|
|
2617
|
-
name:
|
|
2618
|
-
scopes:
|
|
2619
|
-
lastUsedAt:
|
|
2620
|
-
expiresAt:
|
|
2621
|
-
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()
|
|
2622
2857
|
}).strict();
|
|
2623
|
-
var TokensListResponseSchema =
|
|
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
|
-
|
|
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);
|