@clickraft/cli 0.1.8 → 0.3.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/cli.js CHANGED
@@ -170,6 +170,12 @@ var EXIT_CODE_MAP = {
170
170
  retryable: false,
171
171
  hint: "Resource not found."
172
172
  },
173
+ FEATURE_DISABLED: {
174
+ exitCode: 2,
175
+ category: "usage",
176
+ retryable: false,
177
+ hint: "This endpoint is not enabled. Contact support if you believe this is in error."
178
+ },
173
179
  FORBIDDEN: {
174
180
  exitCode: 4,
175
181
  category: "permission",
@@ -715,6 +721,10 @@ function buildUrl(baseUrl, path, query) {
715
721
  if (query) {
716
722
  for (const [key, value] of Object.entries(query)) {
717
723
  if (value === void 0) continue;
724
+ if (Array.isArray(value)) {
725
+ for (const item of value) url.searchParams.append(key, item);
726
+ continue;
727
+ }
718
728
  url.searchParams.append(key, String(value));
719
729
  }
720
730
  }
@@ -848,65 +858,8 @@ function buildPayload(args) {
848
858
  return payload;
849
859
  }
850
860
 
851
- // src/schemas/jobs.ts
852
- import { z as z3 } from "zod";
853
- var GenerateCreateRequestSchema = z3.object({
854
- modelSlug: z3.string().min(1).max(100),
855
- input: z3.object({
856
- prompt: z3.string().max(1e4).optional(),
857
- referenceImageUrl: z3.string().url().optional(),
858
- referenceImages: z3.array(z3.string().url()).max(8).optional(),
859
- aspectRatio: z3.string().regex(/^\d+:\d+$/).optional(),
860
- resolution: z3.string().regex(/^\d+x\d+$/).optional(),
861
- durationSeconds: z3.number().int().min(1).max(60).optional(),
862
- providerParams: z3.record(z3.string(), z3.unknown()).optional()
863
- }).strict(),
864
- options: z3.object({
865
- workflowId: z3.string().uuid().optional(),
866
- nodeId: z3.string().optional()
867
- }).strict().optional()
868
- }).strict();
869
- var GenerateCreateResponseSchema = z3.object({
870
- jobId: z3.string().uuid(),
871
- status: z3.enum(["queued", "processing", "completed", "failed"]),
872
- estimatedSeconds: z3.number().int().nonnegative().nullable(),
873
- creditsCharged: z3.number().int().nonnegative()
874
- }).strict();
875
- var GenerationStatusSchema = z3.enum([
876
- "queued",
877
- "processing",
878
- "uploading",
879
- "completed",
880
- "failed",
881
- "cancelled"
882
- ]);
883
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
884
- "completed",
885
- "failed",
886
- "cancelled"
887
- ]);
888
- function isTerminalStatus(status) {
889
- return TERMINAL_STATUSES.has(status);
890
- }
891
- var GenerationResultSchema = z3.object({
892
- jobId: z3.string().uuid(),
893
- status: GenerationStatusSchema,
894
- modelSlug: z3.string(),
895
- resultUrl: z3.string().url().nullable(),
896
- thumbnailUrl: z3.string().url().nullable(),
897
- errorCode: z3.string().nullable(),
898
- errorMessage: z3.string().nullable(),
899
- creditsCharged: z3.number().int().nonnegative(),
900
- creditsRefunded: z3.boolean(),
901
- startedAt: z3.string().nullable(),
902
- completedAt: z3.string().nullable()
903
- }).strict();
904
-
905
- // src/commands/upload.ts
906
- import { createHash as createHash2 } from "crypto";
907
- import { readFile as readFile2, stat as stat2 } from "fs/promises";
908
- import { basename } from "path";
909
- import { request as undiciRequest2 } from "undici";
861
+ // src/commands/balance.ts
862
+ import { z as z4 } from "zod";
910
863
 
911
864
  // src/auth/credentials.ts
912
865
  import {
@@ -950,31 +903,31 @@ function getCredentialsPath(ctx = {}) {
950
903
  }
951
904
 
952
905
  // src/auth/types.ts
953
- import { z as z4 } from "zod";
954
- var DeviceAuthorizationRequestSchema = z4.object({
955
- client_id: z4.string(),
956
- scope: z4.string()
906
+ import { z as z3 } from "zod";
907
+ var DeviceAuthorizationRequestSchema = z3.object({
908
+ client_id: z3.string(),
909
+ scope: z3.string()
957
910
  });
958
- var DeviceAuthorizationResponseSchema = z4.object({
959
- device_code: z4.string(),
960
- user_code: z4.string().min(1),
961
- verification_uri: z4.string().url(),
962
- verification_uri_complete: z4.string().url().optional(),
963
- expires_in: z4.number().int().positive(),
964
- interval: z4.number().int().positive().default(5)
911
+ var DeviceAuthorizationResponseSchema = z3.object({
912
+ device_code: z3.string(),
913
+ user_code: z3.string().min(1),
914
+ verification_uri: z3.string().url(),
915
+ verification_uri_complete: z3.string().url().optional(),
916
+ expires_in: z3.number().int().positive(),
917
+ interval: z3.number().int().positive().default(5)
965
918
  });
966
- var DeviceTokenPollRequestSchema = z4.object({
967
- client_id: z4.string(),
968
- device_code: z4.string(),
969
- grant_type: z4.literal("urn:ietf:params:oauth:grant-type:device_code")
919
+ var DeviceTokenPollRequestSchema = z3.object({
920
+ client_id: z3.string(),
921
+ device_code: z3.string(),
922
+ grant_type: z3.literal("urn:ietf:params:oauth:grant-type:device_code")
970
923
  });
971
- var TokenResponseSchema = z4.object({
972
- access_token: z4.string(),
973
- token_type: z4.literal("Bearer"),
974
- expires_in: z4.number().int().positive(),
975
- scope: z4.string()
924
+ var TokenResponseSchema = z3.object({
925
+ access_token: z3.string(),
926
+ token_type: z3.literal("Bearer"),
927
+ expires_in: z3.number().int().positive(),
928
+ scope: z3.string()
976
929
  });
977
- var DeviceTokenPollErrorCodeSchema = z4.enum([
930
+ var DeviceTokenPollErrorCodeSchema = z3.enum([
978
931
  "authorization_pending",
979
932
  "slow_down",
980
933
  "access_denied",
@@ -985,27 +938,27 @@ var DeviceTokenPollErrorCodeSchema = z4.enum([
985
938
  "unsupported_grant_type",
986
939
  "invalid_scope"
987
940
  ]);
988
- var DeviceTokenPollErrorSchema = z4.object({
941
+ var DeviceTokenPollErrorSchema = z3.object({
989
942
  error: DeviceTokenPollErrorCodeSchema,
990
- error_description: z4.string().optional()
943
+ error_description: z3.string().optional()
991
944
  });
992
- var CredentialsProfileSchema = z4.object({
993
- apiBaseUrl: z4.string().url(),
994
- accessToken: z4.string(),
995
- tokenType: z4.literal("Bearer"),
996
- tokenPrefix: z4.string(),
997
- organizationId: z4.string().uuid(),
998
- organizationSlug: z4.string(),
999
- userId: z4.string().uuid(),
1000
- scopes: z4.array(z4.string()),
1001
- expiresAt: z4.string().datetime(),
1002
- issuedAt: z4.string().datetime(),
1003
- clientId: z4.string()
945
+ var CredentialsProfileSchema = z3.object({
946
+ apiBaseUrl: z3.string().url(),
947
+ accessToken: z3.string(),
948
+ tokenType: z3.literal("Bearer"),
949
+ tokenPrefix: z3.string(),
950
+ organizationId: z3.string().uuid(),
951
+ organizationSlug: z3.string(),
952
+ userId: z3.string().uuid(),
953
+ scopes: z3.array(z3.string()),
954
+ expiresAt: z3.string().datetime(),
955
+ issuedAt: z3.string().datetime(),
956
+ clientId: z3.string()
1004
957
  }).strict();
1005
- var CredentialsFileSchema = z4.object({
1006
- version: z4.literal(1),
1007
- defaultProfile: z4.string(),
1008
- profiles: z4.record(z4.string(), CredentialsProfileSchema)
958
+ var CredentialsFileSchema = z3.object({
959
+ version: z3.literal(1),
960
+ defaultProfile: z3.string(),
961
+ profiles: z3.record(z3.string(), CredentialsProfileSchema)
1009
962
  }).strict();
1010
963
 
1011
964
  // src/auth/credentials.ts
@@ -1237,20 +1190,158 @@ function trim(value) {
1237
1190
  return t.length === 0 ? void 0 : t;
1238
1191
  }
1239
1192
 
1240
- // src/schemas/assets.ts
1193
+ // src/commands/balance.ts
1194
+ var BalanceResponseSchema = z4.object({
1195
+ totalCredits: z4.number(),
1196
+ subscriptionCredits: z4.number(),
1197
+ purchasedCredits: z4.number(),
1198
+ promotionalCredits: z4.number(),
1199
+ rateLimit: z4.object({
1200
+ apiBurstPerMinute: z4.number()
1201
+ }).strict()
1202
+ }).strict();
1203
+ async function getBalance(options) {
1204
+ const config = await loadConfig({
1205
+ flagProfile: options.profile,
1206
+ credentialsPath: options.credentialsPath
1207
+ });
1208
+ if (!config.accessToken) {
1209
+ throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
1210
+ }
1211
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
1212
+ apiBaseUrl,
1213
+ accessToken,
1214
+ signal: options.signal,
1215
+ cloudflareAccess: config.cloudflareAccess
1216
+ }));
1217
+ const client = factory(config.apiBaseUrl, config.accessToken);
1218
+ const data = await client.request({
1219
+ method: "GET",
1220
+ path: "/balance",
1221
+ responseSchema: BalanceResponseSchema,
1222
+ signal: options.signal
1223
+ });
1224
+ return data;
1225
+ }
1226
+
1227
+ // src/commands/brand-model/list.ts
1241
1228
  import { z as z5 } from "zod";
1229
+ var BrandModelSummarySchema = z5.object({
1230
+ id: z5.string().uuid(),
1231
+ source: z5.enum(["system", "user"]),
1232
+ slug: z5.string().nullable(),
1233
+ name: z5.string(),
1234
+ description: z5.string().nullable(),
1235
+ thumbnailUrl: z5.string().nullable(),
1236
+ tags: z5.array(z5.string()),
1237
+ category: z5.string().nullable(),
1238
+ isPremium: z5.boolean().nullable(),
1239
+ isFeatured: z5.boolean().nullable(),
1240
+ badgeText: z5.string().nullable(),
1241
+ usageCount: z5.number().nullable(),
1242
+ createdAt: z5.string()
1243
+ }).strict();
1244
+ var BrandModelsListResponseSchema = z5.object({
1245
+ brandModels: z5.array(BrandModelSummarySchema)
1246
+ }).strict();
1247
+ async function listBrandModels(options) {
1248
+ const config = await loadConfig({
1249
+ flagProfile: options.profile,
1250
+ credentialsPath: options.credentialsPath
1251
+ });
1252
+ if (!config.accessToken) {
1253
+ throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
1254
+ }
1255
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
1256
+ apiBaseUrl,
1257
+ accessToken,
1258
+ signal: options.signal,
1259
+ cloudflareAccess: config.cloudflareAccess
1260
+ }));
1261
+ const client = factory(config.apiBaseUrl, config.accessToken);
1262
+ const data = await client.request({
1263
+ method: "GET",
1264
+ path: "/brand-models",
1265
+ responseSchema: BrandModelsListResponseSchema,
1266
+ signal: options.signal
1267
+ });
1268
+ return data.brandModels;
1269
+ }
1270
+
1271
+ // src/schemas/jobs.ts
1272
+ import { z as z6 } from "zod";
1273
+ var GenerateCreateRequestSchema = z6.object({
1274
+ modelSlug: z6.string().min(1).max(100),
1275
+ input: z6.object({
1276
+ prompt: z6.string().max(1e4).optional(),
1277
+ referenceImageUrl: z6.string().url().optional(),
1278
+ referenceImages: z6.array(z6.string().url()).max(8).optional(),
1279
+ aspectRatio: z6.string().regex(/^\d+:\d+$/).optional(),
1280
+ resolution: z6.string().regex(/^\d+x\d+$/).optional(),
1281
+ durationSeconds: z6.number().int().min(1).max(60).optional(),
1282
+ providerParams: z6.record(z6.string(), z6.unknown()).optional()
1283
+ }).strict(),
1284
+ options: z6.object({
1285
+ workflowId: z6.string().uuid().optional(),
1286
+ nodeId: z6.string().optional()
1287
+ }).strict().optional()
1288
+ }).strict();
1289
+ var GenerateCreateResponseSchema = z6.object({
1290
+ jobId: z6.string().uuid(),
1291
+ status: z6.enum(["queued", "processing", "completed", "failed"]),
1292
+ estimatedSeconds: z6.number().int().nonnegative().nullable(),
1293
+ creditsCharged: z6.number().int().nonnegative()
1294
+ }).strict();
1295
+ var GenerationStatusSchema = z6.enum([
1296
+ "queued",
1297
+ "processing",
1298
+ "uploading",
1299
+ "completed",
1300
+ "failed",
1301
+ "cancelled"
1302
+ ]);
1303
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
1304
+ "completed",
1305
+ "failed",
1306
+ "cancelled"
1307
+ ]);
1308
+ function isTerminalStatus(status) {
1309
+ return TERMINAL_STATUSES.has(status);
1310
+ }
1311
+ var GenerationResultSchema = z6.object({
1312
+ jobId: z6.string().uuid(),
1313
+ status: GenerationStatusSchema,
1314
+ modelSlug: z6.string(),
1315
+ resultUrl: z6.string().url().nullable(),
1316
+ thumbnailUrl: z6.string().url().nullable(),
1317
+ errorCode: z6.string().nullable(),
1318
+ errorMessage: z6.string().nullable(),
1319
+ creditsCharged: z6.number().int().nonnegative(),
1320
+ creditsRefunded: z6.boolean(),
1321
+ startedAt: z6.string().nullable(),
1322
+ completedAt: z6.string().nullable()
1323
+ }).strict();
1324
+
1325
+ // src/commands/upload.ts
1326
+ import { createHash as createHash2 } from "crypto";
1327
+ import { readFile as readFile2, stat as stat2 } from "fs/promises";
1328
+ import { basename } from "path";
1329
+ import { request as undiciRequest2 } from "undici";
1330
+
1331
+ // src/schemas/assets.ts
1332
+ import { z as z7 } from "zod";
1242
1333
  var MAX_UPLOAD_SIZE_BYTES = 15 * 1024 * 1024;
1243
- var UploadRequestSchema = z5.object({
1244
- filename: z5.string().min(1).max(255),
1245
- contentType: z5.string().regex(/^[\w-]+\/[\w+.-]+$/),
1246
- sizeBytes: z5.number().int().positive().max(MAX_UPLOAD_SIZE_BYTES),
1247
- contentHash: z5.string().regex(/^sha256:[0-9a-f]{64}$/)
1334
+ var UploadRequestSchema = z7.object({
1335
+ filename: z7.string().min(1).max(255),
1336
+ contentType: z7.string().regex(/^[\w-]+\/[\w+.-]+$/),
1337
+ sizeBytes: z7.number().int().positive().max(MAX_UPLOAD_SIZE_BYTES),
1338
+ contentHash: z7.string().regex(/^sha256:[0-9a-f]{64}$/)
1248
1339
  }).strict();
1249
- var UploadResponseSchema = z5.object({
1250
- assetId: z5.string().uuid(),
1251
- uploadUrl: z5.string().url(),
1252
- publicUrl: z5.string().url(),
1253
- deduplicated: z5.boolean()
1340
+ var UploadResponseSchema = z7.object({
1341
+ assetId: z7.string().uuid(),
1342
+ uploadUrl: z7.string().url(),
1343
+ publicUrl: z7.string().url(),
1344
+ deduplicated: z7.boolean()
1254
1345
  }).strict();
1255
1346
 
1256
1347
  // src/commands/upload.ts
@@ -2218,21 +2309,112 @@ function inspectTelemetry(options = {}) {
2218
2309
  };
2219
2310
  }
2220
2311
 
2312
+ // src/commands/template/internal.ts
2313
+ async function buildTemplateClient(opts) {
2314
+ const cfg = await loadConfig({
2315
+ flagProfile: opts.profile,
2316
+ flagApiBaseUrl: opts.apiBaseUrl,
2317
+ flagToken: opts.token
2318
+ });
2319
+ return new ApiClient({
2320
+ apiBaseUrl: cfg.apiBaseUrl,
2321
+ accessToken: cfg.accessToken,
2322
+ signal: opts.signal,
2323
+ cloudflareAccess: cfg.cloudflareAccess
2324
+ });
2325
+ }
2326
+
2327
+ // src/schemas/templates.ts
2328
+ import { z as z8 } from "zod";
2329
+ var DeclaredParamSchema = z8.object({
2330
+ name: z8.string(),
2331
+ type: z8.string(),
2332
+ required: z8.boolean(),
2333
+ defaultValue: z8.string().nullable().optional(),
2334
+ enumValues: z8.array(z8.string()).nullable().optional(),
2335
+ description: z8.string().nullable().optional()
2336
+ }).strict();
2337
+ var TemplateSummarySchema = z8.object({
2338
+ id: z8.string().uuid(),
2339
+ source: z8.enum(["own", "system"]),
2340
+ name: z8.string(),
2341
+ description: z8.string().nullable(),
2342
+ category: z8.string().nullable(),
2343
+ thumbnailUrl: z8.string().nullable(),
2344
+ tags: z8.array(z8.string()),
2345
+ createdAt: z8.string()
2346
+ }).strict();
2347
+ var TemplateDetailSchema = TemplateSummarySchema.extend({
2348
+ declaredParams: z8.array(DeclaredParamSchema),
2349
+ timesUsed: z8.number()
2350
+ }).strict();
2351
+ var TemplatesListResponseSchema = z8.object({
2352
+ templates: z8.array(TemplateSummarySchema),
2353
+ cursor: z8.string().nullable(),
2354
+ hasMore: z8.boolean()
2355
+ }).strict();
2356
+
2357
+ // src/commands/template/list.ts
2358
+ async function listTemplates(opts) {
2359
+ const client = opts.client ?? await buildTemplateClient(opts);
2360
+ const data = await client.request({
2361
+ method: "GET",
2362
+ path: "/templates",
2363
+ query: {
2364
+ q: opts.q,
2365
+ tags: opts.tags,
2366
+ scope: opts.scope,
2367
+ cursor: opts.cursor,
2368
+ limit: opts.limit
2369
+ },
2370
+ responseSchema: TemplatesListResponseSchema,
2371
+ signal: opts.signal
2372
+ });
2373
+ return data;
2374
+ }
2375
+
2376
+ // src/commands/template/find.ts
2377
+ async function findTemplates(opts) {
2378
+ return listTemplates({
2379
+ q: opts.query,
2380
+ tags: opts.tags,
2381
+ scope: opts.scope,
2382
+ limit: opts.limit,
2383
+ cursor: opts.cursor,
2384
+ profile: opts.profile,
2385
+ apiBaseUrl: opts.apiBaseUrl,
2386
+ token: opts.token,
2387
+ signal: opts.signal,
2388
+ client: opts.client
2389
+ });
2390
+ }
2391
+
2392
+ // src/commands/template/get.ts
2393
+ async function getTemplate(opts) {
2394
+ const client = opts.client ?? await buildTemplateClient(opts);
2395
+ return client.request({
2396
+ method: "GET",
2397
+ path: `/templates/${encodeURIComponent(opts.id)}`,
2398
+ responseSchema: TemplateDetailSchema,
2399
+ signal: opts.signal
2400
+ });
2401
+ }
2402
+
2221
2403
  // src/commands/tokens/list.ts
2222
- import { z as z6 } from "zod";
2223
- var TokenRowSchema = z6.object({
2224
- id: z6.string().uuid(),
2225
- organizationId: z6.string().uuid(),
2226
- userId: z6.string().uuid(),
2227
- clientId: z6.string().nullable(),
2228
- tokenPrefix: z6.string(),
2229
- name: z6.string().nullable(),
2230
- scopes: z6.array(z6.string()),
2231
- lastUsedAt: z6.string().nullable(),
2232
- expiresAt: z6.string(),
2233
- createdAt: z6.string()
2404
+ import { z as z9 } from "zod";
2405
+ var TokenRowSchema = z9.object({
2406
+ id: z9.string().uuid(),
2407
+ organizationId: z9.string().uuid(),
2408
+ userId: z9.string().uuid(),
2409
+ clientId: z9.string().nullable(),
2410
+ tokenPrefix: z9.string(),
2411
+ name: z9.string().nullable(),
2412
+ scopes: z9.array(z9.string()),
2413
+ lastUsedAt: z9.string().nullable(),
2414
+ expiresAt: z9.string(),
2415
+ createdAt: z9.string()
2234
2416
  }).strict();
2235
- var TokensListResponseSchema = z6.array(TokenRowSchema);
2417
+ var TokensListResponseSchema = z9.array(TokenRowSchema);
2236
2418
  async function listTokens(options) {
2237
2419
  const config = await loadConfig({
2238
2420
  flagProfile: options.profile,
@@ -2592,6 +2774,99 @@ function renderTelemetryState(state, out) {
2592
2774
  ];
2593
2775
  out.write(lines.join("\n") + "\n");
2594
2776
  }
2777
+ var fmtNum = new Intl.NumberFormat("en-US");
2778
+ function renderBalance(data, out) {
2779
+ const pad = 22;
2780
+ const lines = [
2781
+ `${"Total credits:".padEnd(pad)}${fmtNum.format(data.totalCredits).padStart(12)}`,
2782
+ `${"\u251C\u2500 Subscription:".padEnd(pad)}${fmtNum.format(data.subscriptionCredits).padStart(12)}`,
2783
+ `${"\u251C\u2500 Purchased:".padEnd(pad)}${fmtNum.format(data.purchasedCredits).padStart(12)}`,
2784
+ `${"\u2514\u2500 Promotional:".padEnd(pad)}${fmtNum.format(data.promotionalCredits).padStart(12)}`,
2785
+ "",
2786
+ `${"Rate limit:".padEnd(pad)}${fmtNum.format(data.rateLimit.apiBurstPerMinute).padStart(12)} req/min`
2787
+ ];
2788
+ out.write(lines.join("\n") + "\n");
2789
+ }
2790
+ function renderBrandModelsList(models, out) {
2791
+ if (models.length === 0) {
2792
+ out.write("No brand models.\n");
2793
+ return;
2794
+ }
2795
+ const MAX_TAGS = 3;
2796
+ const headers = ["SOURCE", "NAME", "SLUG", "CATEGORY", "TAGS"];
2797
+ const data = models.map((m) => {
2798
+ const shown = m.tags.slice(0, MAX_TAGS).join(", ");
2799
+ const extra = m.tags.length > MAX_TAGS ? ` +${m.tags.length - MAX_TAGS} more` : "";
2800
+ return [
2801
+ m.source,
2802
+ m.name,
2803
+ m.slug ?? "\u2014",
2804
+ m.category ?? "\u2014",
2805
+ m.tags.length > 0 ? shown + extra : "\u2014"
2806
+ ];
2807
+ });
2808
+ const widths = headers.map(
2809
+ (header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
2810
+ );
2811
+ const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
2812
+ out.write(padRow(headers) + "\n");
2813
+ for (const cells of data) out.write(padRow(cells) + "\n");
2814
+ }
2815
+ function renderTemplatesList(result, out) {
2816
+ const { templates } = result;
2817
+ if (templates.length === 0) {
2818
+ out.write("No templates.\n");
2819
+ return;
2820
+ }
2821
+ const MAX_TAGS = 3;
2822
+ const headers = ["SOURCE", "NAME", "CATEGORY", "TAGS", "CREATED"];
2823
+ const data = templates.map((t) => {
2824
+ const shown = t.tags.slice(0, MAX_TAGS).join(", ");
2825
+ const extra = t.tags.length > MAX_TAGS ? ` +${t.tags.length - MAX_TAGS} more` : "";
2826
+ return [
2827
+ t.source,
2828
+ t.name,
2829
+ t.category ?? "\u2014",
2830
+ t.tags.length > 0 ? shown + extra : "\u2014",
2831
+ t.createdAt.slice(0, 10)
2832
+ ];
2833
+ });
2834
+ const widths = headers.map(
2835
+ (header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
2836
+ );
2837
+ const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
2838
+ out.write(padRow(headers) + "\n");
2839
+ for (const cells of data) out.write(padRow(cells) + "\n");
2840
+ if (result.hasMore) {
2841
+ out.write("\nMore results available. Use --cursor to fetch the next page.\n");
2842
+ }
2843
+ }
2844
+ function renderTemplateDetail(detail, out) {
2845
+ const pad = 17;
2846
+ const lines = [
2847
+ detail.name,
2848
+ `${"ID:".padEnd(pad)}${detail.id}`,
2849
+ `${"Source:".padEnd(pad)}${detail.source}`,
2850
+ `${"Category:".padEnd(pad)}${detail.category ?? "\u2014"}`,
2851
+ `${"Tags:".padEnd(pad)}${detail.tags.length > 0 ? detail.tags.join(", ") : "\u2014"}`,
2852
+ `${"Created:".padEnd(pad)}${detail.createdAt.slice(0, 10)}`,
2853
+ `${"Times used:".padEnd(pad)}${detail.timesUsed}`
2854
+ ];
2855
+ if (detail.description) {
2856
+ lines.push("", detail.description);
2857
+ }
2858
+ lines.push("", "Declared parameters:");
2859
+ if (detail.declaredParams.length === 0) {
2860
+ lines.push(" (none)");
2861
+ } else {
2862
+ for (const p of detail.declaredParams) {
2863
+ const req = p.required ? "required" : "optional";
2864
+ const desc = p.description ? `: ${p.description}` : "";
2865
+ lines.push(` - ${p.name} (${p.type}, ${req})${desc}`);
2866
+ }
2867
+ }
2868
+ out.write(lines.join("\n") + "\n");
2869
+ }
2595
2870
  function formatExpiresAt(iso) {
2596
2871
  const ms = Date.parse(iso);
2597
2872
  if (Number.isNaN(ms)) return iso;
@@ -2685,6 +2960,22 @@ async function runCommand(parsed, ctx, rc) {
2685
2960
  emit(state, renderTelemetryState, ctx, rc);
2686
2961
  return 0;
2687
2962
  }
2963
+ case "balance": {
2964
+ const data = await getBalance({
2965
+ profile: stringFlag(parsed.flags.profile),
2966
+ signal: rc.signal
2967
+ });
2968
+ emit(data, renderBalance, ctx, rc);
2969
+ return 0;
2970
+ }
2971
+ case "brand-model.list": {
2972
+ const models = await listBrandModels({
2973
+ profile: stringFlag(parsed.flags.profile),
2974
+ signal: rc.signal
2975
+ });
2976
+ emit(models, renderBrandModelsList, ctx, rc);
2977
+ return 0;
2978
+ }
2688
2979
  default:
2689
2980
  throw new ApiError(
2690
2981
  "INPUT_INVALID_FORMAT",
@@ -2757,7 +3048,9 @@ var COMMAND_FLAGS = {
2757
3048
  },
2758
3049
  logout: { profile: "string", all: "boolean", revoke: "boolean", json: "boolean" },
2759
3050
  "tokens.list": { profile: "string", json: "boolean" },
2760
- "telemetry.inspect": { json: "boolean" }
3051
+ "telemetry.inspect": { json: "boolean" },
3052
+ balance: { profile: "string", json: "boolean" },
3053
+ "brand-model.list": { profile: "string", json: "boolean" }
2761
3054
  };
2762
3055
  function validateFlags(parsed) {
2763
3056
  const allowed = COMMAND_FLAGS[parsed.command.join(".")];
@@ -2798,7 +3091,12 @@ function renderRootHelp(version) {
2798
3091
  " login Authenticate via OAuth device flow",
2799
3092
  " logout Remove credentials (optional server-side revoke)",
2800
3093
  " tokens list List the caller's API tokens",
3094
+ " balance Show credit balance and rate limits",
3095
+ " brand-model list List available brand models",
2801
3096
  " telemetry inspect Show current telemetry state and payload shape",
3097
+ " template list List available templates",
3098
+ " template get Fetch a template by ID",
3099
+ " template find Search templates by keyword",
2802
3100
  " generate create Submit a generation job and wait for completion",
2803
3101
  " generate wait Wait for an existing job to reach a terminal state",
2804
3102
  " generate get Fetch the current state of a job",
@@ -2848,6 +3146,32 @@ function renderCommandHelp(command) {
2848
3146
  " --json Emit JSON envelope instead of human output",
2849
3147
  ""
2850
3148
  ].join("\n");
3149
+ case "balance":
3150
+ return [
3151
+ "Usage: clickraft balance [options]",
3152
+ "",
3153
+ "Options:",
3154
+ " --profile <name> Read credentials from a specific profile",
3155
+ " --json Emit JSON envelope instead of human output",
3156
+ ""
3157
+ ].join("\n");
3158
+ case "brand-model":
3159
+ return [
3160
+ "Usage: clickraft brand-model <subcommand> [options]",
3161
+ "",
3162
+ "Subcommands:",
3163
+ " list List available brand models",
3164
+ ""
3165
+ ].join("\n");
3166
+ case "brand-model list":
3167
+ return [
3168
+ "Usage: clickraft brand-model list [options]",
3169
+ "",
3170
+ "Options:",
3171
+ " --profile <name> Read credentials from a specific profile",
3172
+ " --json Emit JSON envelope instead of human output",
3173
+ ""
3174
+ ].join("\n");
2851
3175
  case "telemetry inspect":
2852
3176
  return [
2853
3177
  "Usage: clickraft telemetry inspect [options]",
@@ -2856,6 +3180,62 @@ function renderCommandHelp(command) {
2856
3180
  " --json Emit JSON envelope instead of human output",
2857
3181
  ""
2858
3182
  ].join("\n");
3183
+ case "template":
3184
+ return [
3185
+ "Usage: clickraft template <subcommand> [options]",
3186
+ "",
3187
+ "Subcommands:",
3188
+ " list List available templates",
3189
+ " get Fetch a template by ID",
3190
+ " find Search templates by keyword",
3191
+ ""
3192
+ ].join("\n");
3193
+ case "template list":
3194
+ return [
3195
+ "Usage: clickraft template list [options]",
3196
+ "",
3197
+ "Options:",
3198
+ " --scope <scope> Filter by scope: own, system, or all (default: all)",
3199
+ " --q <query> Free-text search query",
3200
+ " --tags <tag> Filter by tag (repeatable)",
3201
+ " --cursor <token> Pagination cursor from a previous response",
3202
+ " --limit <n> Max results per page (1-50, default: 20)",
3203
+ " --profile <name>",
3204
+ " --api-base-url <url>",
3205
+ " --token <token>",
3206
+ " --json",
3207
+ ""
3208
+ ].join("\n");
3209
+ case "template get":
3210
+ return [
3211
+ "Usage: clickraft template get <id> [options]",
3212
+ "",
3213
+ "Fetches a single template by its ID, including declared parameters.",
3214
+ "",
3215
+ "Options:",
3216
+ " --profile <name>",
3217
+ " --api-base-url <url>",
3218
+ " --token <token>",
3219
+ " --json",
3220
+ ""
3221
+ ].join("\n");
3222
+ case "template find":
3223
+ return [
3224
+ "Usage: clickraft template find <query> [options]",
3225
+ "",
3226
+ "Searches templates by keyword. Shorthand for `template list --q <query>`.",
3227
+ "",
3228
+ "Options:",
3229
+ " --tags <tag> Filter by tag (repeatable)",
3230
+ " --scope <scope> Filter by scope: own, system, or all (default: all)",
3231
+ " --limit <n> Max results per page (1-50, default: 20)",
3232
+ " --cursor <token> Pagination cursor from a previous response",
3233
+ " --profile <name>",
3234
+ " --api-base-url <url>",
3235
+ " --token <token>",
3236
+ " --json",
3237
+ ""
3238
+ ].join("\n");
2859
3239
  case "generate":
2860
3240
  return [
2861
3241
  "Usage: clickraft generate <subcommand> [options]",
@@ -2977,6 +3357,14 @@ function matchNewCommand(argv) {
2977
3357
  if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "jobs" };
2978
3358
  return { kind: "unknown-sub", verb: "jobs", sub };
2979
3359
  }
3360
+ if (verb === "template") {
3361
+ const sub = argv[1];
3362
+ if (sub === "list") return { kind: "cmd", path: "template.list", verbTokenCount: 2 };
3363
+ if (sub === "get") return { kind: "cmd", path: "template.get", verbTokenCount: 2 };
3364
+ if (sub === "find") return { kind: "cmd", path: "template.find", verbTokenCount: 2 };
3365
+ if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "template" };
3366
+ return { kind: "unknown-sub", verb: "template", sub };
3367
+ }
2980
3368
  return null;
2981
3369
  }
2982
3370
  async function runNewCommand(newCmd, options, stdout, stderr) {
@@ -3027,6 +3415,12 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
3027
3415
  return await runJobsWait(argvAfterVerb, ctx, rc);
3028
3416
  case "upload":
3029
3417
  return await runUploadCmd(argvAfterVerb, ctx, rc);
3418
+ case "template.list":
3419
+ return await runTemplateList(argvAfterVerb, ctx, rc);
3420
+ case "template.get":
3421
+ return await runTemplateGet(argvAfterVerb, ctx, rc);
3422
+ case "template.find":
3423
+ return await runTemplateFind(argvAfterVerb, ctx, rc);
3030
3424
  }
3031
3425
  } catch (err) {
3032
3426
  if (isAbortedError(err, options.signal)) {
@@ -3221,6 +3615,87 @@ function exitCodeForResult(result) {
3221
3615
  }
3222
3616
  return 0;
3223
3617
  }
3618
+ async function runTemplateList(argv, ctx, rc) {
3619
+ const parsed = parseArgs(argv, {
3620
+ string: [...COMMON_RICH_STRING_FLAGS, "scope", "q", "cursor", "limit"],
3621
+ boolean: [...COMMON_RICH_BOOL_FLAGS],
3622
+ array: ["tags"]
3623
+ });
3624
+ const result = await listTemplates({
3625
+ q: parsed.string.q,
3626
+ tags: parsed.array.tags,
3627
+ scope: parsed.string.scope,
3628
+ cursor: parsed.string.cursor,
3629
+ limit: optionalInt(parsed.string.limit, "limit"),
3630
+ profile: parsed.string.profile,
3631
+ apiBaseUrl: parsed.string["api-base-url"],
3632
+ token: parsed.string.token,
3633
+ signal: rc.signal
3634
+ });
3635
+ ctx.nextCursor = result.cursor ?? void 0;
3636
+ emitTemplateList(result, ctx, rc);
3637
+ return 0;
3638
+ }
3639
+ async function runTemplateGet(argv, ctx, rc) {
3640
+ const parsed = parseArgs(argv, {
3641
+ string: [...COMMON_RICH_STRING_FLAGS],
3642
+ boolean: [...COMMON_RICH_BOOL_FLAGS]
3643
+ });
3644
+ const id = parsed.positional[0];
3645
+ if (!id) throw new ArgError("Missing required <id> argument.");
3646
+ const detail = await getTemplate({
3647
+ id,
3648
+ profile: parsed.string.profile,
3649
+ apiBaseUrl: parsed.string["api-base-url"],
3650
+ token: parsed.string.token,
3651
+ signal: rc.signal
3652
+ });
3653
+ emitTemplateDetail(detail, ctx, rc);
3654
+ return 0;
3655
+ }
3656
+ async function runTemplateFind(argv, ctx, rc) {
3657
+ const parsed = parseArgs(argv, {
3658
+ string: [...COMMON_RICH_STRING_FLAGS, "scope", "cursor", "limit"],
3659
+ boolean: [...COMMON_RICH_BOOL_FLAGS],
3660
+ array: ["tags"]
3661
+ });
3662
+ const query = parsed.positional[0];
3663
+ if (!query) throw new ArgError("Missing required <query> argument.");
3664
+ const result = await findTemplates({
3665
+ query,
3666
+ tags: parsed.array.tags,
3667
+ scope: parsed.string.scope,
3668
+ cursor: parsed.string.cursor,
3669
+ limit: optionalInt(parsed.string.limit, "limit"),
3670
+ profile: parsed.string.profile,
3671
+ apiBaseUrl: parsed.string["api-base-url"],
3672
+ token: parsed.string.token,
3673
+ signal: rc.signal
3674
+ });
3675
+ ctx.nextCursor = result.cursor ?? void 0;
3676
+ emitTemplateList(result, ctx, rc);
3677
+ return 0;
3678
+ }
3679
+ function emitTemplateList(result, ctx, rc) {
3680
+ if (rc.signal?.aborted) {
3681
+ throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
3682
+ }
3683
+ if (rc.jsonMode) {
3684
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
3685
+ return;
3686
+ }
3687
+ renderTemplatesList(result, rc.stdout);
3688
+ }
3689
+ function emitTemplateDetail(detail, ctx, rc) {
3690
+ if (rc.signal?.aborted) {
3691
+ throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
3692
+ }
3693
+ if (rc.jsonMode) {
3694
+ rc.stdout.write(JSON.stringify(wrapSuccess(detail, ctx)) + "\n");
3695
+ return;
3696
+ }
3697
+ renderTemplateDetail(detail, rc.stdout);
3698
+ }
3224
3699
 
3225
3700
  // src/index.ts
3226
3701
  var __filename2 = fileURLToPath(import.meta.url);