@clickraft/cli 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,6 +10,10 @@ import { z } from "zod";
10
10
  import { z as z2 } from "zod";
11
11
  import { z as z3 } from "zod";
12
12
  import { z as z4 } from "zod";
13
+ import { request as undiciRequest } from "undici";
14
+ import { z as z6 } from "zod";
15
+ import { createHash, randomUUID } from "crypto";
16
+ import { z as z5 } from "zod";
13
17
  var TOKEN_FIELD_PATTERN = /"(access_token|refresh_token|device_code|user_code|code_verifier|client_secret)"\s*:\s*"[^"]*"/gi;
14
18
  var BARE_TOKEN_PATTERN = /\bck_(?:live|dev|test)_[A-Za-z0-9_-]+/gi;
15
19
  function redactTokenLikeFields(input) {
@@ -202,432 +206,6 @@ function composeIterationSignal(userSignal, remainingMs) {
202
206
  const deadlineSignal = AbortSignal.timeout(Math.max(remainingMs, 0));
203
207
  return userSignal ? AbortSignal.any([userSignal, deadlineSignal]) : deadlineSignal;
204
208
  }
205
-
206
- // src/api/client.ts
207
- import { request as undiciRequest } from "undici";
208
- import { z as z6 } from "zod";
209
-
210
- // src/errors/codes.ts
211
- var LOGIN_HINT = "Run `clickraft login` to authenticate.";
212
- var RELOGIN_HINT = "Run `clickraft login` to refresh.";
213
- var EXIT_CODE_MAP = {
214
- // --- 37 Phase 8 Q7 codes ----------------------------------------------------
215
- AUTH_TOKEN_MISSING: {
216
- exitCode: 3,
217
- category: "auth",
218
- retryable: false,
219
- hint: "Set `CLICKRAFT_TOKEN` or run `clickraft login`."
220
- },
221
- AUTH_TOKEN_INVALID: {
222
- exitCode: 3,
223
- category: "auth",
224
- retryable: false,
225
- hint: "The token is malformed or unknown. Run `clickraft login`."
226
- },
227
- AUTH_TOKEN_EXPIRED: {
228
- exitCode: 3,
229
- category: "auth",
230
- retryable: false,
231
- hint: RELOGIN_HINT
232
- },
233
- AUTH_TOKEN_REVOKED: {
234
- exitCode: 3,
235
- category: "auth",
236
- retryable: false,
237
- hint: "Token was revoked. Run `clickraft login` to mint a new one."
238
- },
239
- AUTH_TOKEN_SCOPE_INSUFFICIENT: {
240
- exitCode: 4,
241
- category: "permission",
242
- retryable: false,
243
- hint: "This token does not have the required scope. Re-run `clickraft login` selecting the needed scopes."
244
- },
245
- AUTH_ORG_MISMATCH: {
246
- exitCode: 4,
247
- category: "permission",
248
- retryable: false,
249
- hint: "This token's org no longer exists or you have been removed."
250
- },
251
- AUTH_DEVICE_CODE_PENDING: {
252
- exitCode: 0,
253
- category: "success",
254
- retryable: false,
255
- hint: "Continue polling."
256
- },
257
- AUTH_DEVICE_CODE_EXPIRED: {
258
- exitCode: 3,
259
- category: "auth",
260
- retryable: false,
261
- hint: "Device code expired. Re-run `clickraft login`."
262
- },
263
- AUTH_DEVICE_CODE_SLOW_DOWN: {
264
- exitCode: 0,
265
- category: "success",
266
- retryable: false,
267
- hint: "Increase polling interval (Retry-After)."
268
- },
269
- IDEMPOTENCY_KEY_MISSING: {
270
- exitCode: 2,
271
- category: "usage",
272
- retryable: false,
273
- hint: "Add `X-Idempotency-Key` header (or `--idempotency-key`)."
274
- },
275
- IDEMPOTENCY_KEY_CONFLICT: {
276
- exitCode: 7,
277
- category: "conflict",
278
- retryable: false,
279
- hint: "Same key was used with a different request body. Use a new key."
280
- },
281
- PLAN_FEATURE_LOCKED: {
282
- exitCode: 4,
283
- category: "permission",
284
- retryable: false,
285
- hint: "API access is not enabled on your plan. Visit billing settings."
286
- },
287
- PLAN_QUOTA_EXCEEDED: {
288
- exitCode: 5,
289
- category: "quota",
290
- retryable: false,
291
- hint: "Daily API quota exhausted. Wait, or upgrade your plan."
292
- },
293
- INSUFFICIENT_CREDITS: {
294
- exitCode: 5,
295
- category: "quota",
296
- retryable: false,
297
- hint: "Top up credits to retry."
298
- },
299
- CREDIT_DEDUCTION_FAILED: {
300
- exitCode: 1,
301
- category: "runtime",
302
- retryable: true,
303
- hint: "Could not charge credits. Retry; no charge was made."
304
- },
305
- RATE_LIMITED: {
306
- exitCode: 5,
307
- category: "quota",
308
- retryable: true,
309
- hint: "Slow down. Honor Retry-After."
310
- },
311
- INPUT_INVALID_FORMAT: {
312
- exitCode: 2,
313
- category: "usage",
314
- retryable: false,
315
- hint: "Check input format and try again."
316
- },
317
- INPUT_TOO_LONG: {
318
- exitCode: 2,
319
- category: "usage",
320
- retryable: false,
321
- hint: "Field exceeded max length."
322
- },
323
- WORKFLOW_VALIDATION_FAILED: {
324
- exitCode: 2,
325
- category: "usage",
326
- retryable: false,
327
- hint: "Run `workflow validate` for the full list of errors."
328
- },
329
- WORKFLOW_NODE_TYPE_UNKNOWN: {
330
- exitCode: 2,
331
- category: "usage",
332
- retryable: false,
333
- hint: "Run `clickraft nodes list` for the current set of node types."
334
- },
335
- CANVAS_REV_MISMATCH: {
336
- exitCode: 7,
337
- category: "conflict",
338
- retryable: false,
339
- hint: "The workflow was updated elsewhere. Re-fetch and retry with the current canvas_rev."
340
- },
341
- CANVAS_REV_REQUIRED: {
342
- exitCode: 2,
343
- category: "usage",
344
- retryable: false,
345
- hint: "Include --canvas-rev or use --auto-rev."
346
- },
347
- WORKER_UNREACHABLE: {
348
- exitCode: 6,
349
- category: "network",
350
- retryable: true,
351
- hint: "Collaboration service temporarily unavailable. Retry."
352
- },
353
- IDEMPOTENCY_IN_PROGRESS: {
354
- exitCode: 7,
355
- category: "conflict",
356
- retryable: false,
357
- hint: "A request with this idempotency key is currently in-flight. Retry in a moment."
358
- },
359
- TOKEN_NAME_INVALID: {
360
- exitCode: 2,
361
- category: "usage",
362
- retryable: false,
363
- hint: "Agent token name is malformed."
364
- },
365
- WORKFLOW_NOT_FOUND: {
366
- exitCode: 2,
367
- category: "usage",
368
- retryable: false,
369
- hint: "No workflow with that ID in your org."
370
- },
371
- NOT_FOUND: {
372
- exitCode: 2,
373
- category: "usage",
374
- retryable: false,
375
- hint: "Resource not found."
376
- },
377
- NODE_TYPE_NOT_FOUND: {
378
- exitCode: 2,
379
- category: "usage",
380
- retryable: false,
381
- hint: "Run `clickraft nodes list` for the current set of node types."
382
- },
383
- FEATURE_DISABLED: {
384
- exitCode: 2,
385
- category: "usage",
386
- retryable: false,
387
- hint: "This endpoint is not enabled. Contact support if you believe this is in error."
388
- },
389
- FORBIDDEN: {
390
- exitCode: 4,
391
- category: "permission",
392
- retryable: false,
393
- hint: "Your role does not permit this action."
394
- },
395
- MODEL_NOT_FOUND: {
396
- exitCode: 2,
397
- category: "usage",
398
- retryable: false,
399
- hint: "Unknown `modelSlug`. Run `brand-model list` for the current set."
400
- },
401
- PROVIDER_UNAVAILABLE: {
402
- exitCode: 6,
403
- category: "network",
404
- retryable: true,
405
- hint: "Provider is temporarily unavailable. Retry shortly."
406
- },
407
- STORAGE_UPLOAD_FAILED: {
408
- exitCode: 1,
409
- category: "runtime",
410
- retryable: true,
411
- hint: "Upload failed mid-stream. Retry."
412
- },
413
- STORAGE_QUOTA_EXCEEDED: {
414
- exitCode: 5,
415
- category: "quota",
416
- retryable: false,
417
- hint: "Org storage quota exceeded. Free space or upgrade."
418
- },
419
- GEN_CONTENT_REFUSAL: {
420
- exitCode: 1,
421
- category: "runtime",
422
- retryable: false,
423
- hint: "The model declined the request via safety policy."
424
- },
425
- GEN_OUTPUT_SCHEMA_MISMATCH: {
426
- exitCode: 1,
427
- category: "runtime",
428
- retryable: true,
429
- hint: "Provider returned malformed output. Full refund issued. Retry."
430
- },
431
- GEN_EMPTY_OUTPUT: {
432
- exitCode: 1,
433
- category: "runtime",
434
- retryable: true,
435
- hint: "Provider returned no usable output. Full refund issued. Retry."
436
- },
437
- GEN_ITEM_COUNT_MISMATCH: {
438
- exitCode: 0,
439
- category: "success",
440
- retryable: false,
441
- hint: "Partial output kept; no refund."
442
- },
443
- AGENT_FINGERPRINT_REJECTED: {
444
- exitCode: 4,
445
- category: "permission",
446
- retryable: false,
447
- hint: "This client identification is not permitted. Contact support."
448
- },
449
- INTERNAL_ERROR: {
450
- exitCode: 1,
451
- category: "runtime",
452
- retryable: true,
453
- hint: "Server error. Retry; if it persists, contact support with the request ID."
454
- },
455
- CREDENTIALS_LOCKED: {
456
- exitCode: 1,
457
- category: "runtime",
458
- retryable: true,
459
- hint: "Another CLI invocation holds the credentials lock. Retry shortly."
460
- },
461
- CREDENTIALS_PERMISSIONS_LOOSE: {
462
- exitCode: 0,
463
- category: "success",
464
- retryable: false,
465
- hint: "Credentials file mode is wider than 0600; continuing."
466
- },
467
- // --- 5 additional codes introduced by 1A.1 ---------------------------------
468
- AUTH_ACCESS_DENIED: {
469
- exitCode: 3,
470
- category: "auth",
471
- retryable: false,
472
- hint: "You denied the OAuth grant. Re-run `clickraft login` to retry."
473
- },
474
- AUTH_FLOW_CANCELLED: {
475
- exitCode: 3,
476
- category: "auth",
477
- retryable: false,
478
- hint: "Authentication was cancelled. Re-run `clickraft login`."
479
- },
480
- CREDENTIALS_CORRUPT: {
481
- exitCode: 1,
482
- category: "runtime",
483
- retryable: false,
484
- hint: "Credentials file is unreadable. Remove it and run `clickraft login`."
485
- },
486
- CREDENTIALS_NOT_FOUND: {
487
- exitCode: 3,
488
- category: "auth",
489
- retryable: false,
490
- hint: LOGIN_HINT
491
- },
492
- CREDENTIALS_PROFILE_NOT_FOUND: {
493
- exitCode: 2,
494
- category: "usage",
495
- retryable: false,
496
- hint: "No such profile. Use `clickraft auth profiles` to list available profiles."
497
- },
498
- // --- 8 brand model resolution codes -----------------------------------------
499
- BRAND_MODEL_NOT_FOUND: {
500
- exitCode: 1,
501
- category: "runtime",
502
- retryable: false,
503
- hint: "No brand model with that ID. Run `clickraft brand-model list`."
504
- },
505
- BRAND_MODEL_NOT_A_MODEL: {
506
- exitCode: 1,
507
- category: "runtime",
508
- retryable: false,
509
- hint: "The specified asset is not a brand model."
510
- },
511
- BRAND_MODEL_NOT_ACTIVE: {
512
- exitCode: 1,
513
- category: "runtime",
514
- retryable: false,
515
- hint: "Brand model is not active. Check its status in the dashboard."
516
- },
517
- BRAND_MODEL_NO_REFERENCES: {
518
- exitCode: 1,
519
- category: "runtime",
520
- retryable: false,
521
- hint: "Brand model has no reference images. Upload references first."
522
- },
523
- BRAND_MODEL_POSE_NOT_FOUND: {
524
- exitCode: 1,
525
- category: "runtime",
526
- retryable: false,
527
- hint: "Requested pose not found. Valid: front, 3/4-right, right, left, 3/4-left, back, approved."
528
- },
529
- BRAND_MODEL_DUPLICATE_ID: {
530
- exitCode: 2,
531
- category: "usage",
532
- retryable: false,
533
- hint: "Same brand model ID appears more than once."
534
- },
535
- MODEL_DOES_NOT_SUPPORT_REFERENCES: {
536
- exitCode: 2,
537
- category: "usage",
538
- retryable: false,
539
- hint: "This model does not support brand model references."
540
- },
541
- REFERENCE_LIMIT_EXCEEDED: {
542
- exitCode: 2,
543
- category: "usage",
544
- retryable: false,
545
- hint: "Too many reference images. Check model limits."
546
- },
547
- // --- 8 product reference resolver codes -------------------------------------
548
- PRODUCT_NOT_FOUND: {
549
- exitCode: 1,
550
- category: "runtime",
551
- retryable: false,
552
- hint: "No product with that ID in your org. Run `clickraft product list`."
553
- },
554
- PRODUCT_DUPLICATE_REFERENCE: {
555
- exitCode: 2,
556
- category: "usage",
557
- retryable: false,
558
- hint: "Same (productId, imageId) tuple appears twice in the request."
559
- },
560
- PRODUCT_IMAGE_NOT_FOUND: {
561
- exitCode: 1,
562
- category: "runtime",
563
- retryable: false,
564
- hint: "imageId does not exist in the product images."
565
- },
566
- PRODUCT_IMAGE_MISMATCH: {
567
- exitCode: 2,
568
- category: "usage",
569
- retryable: false,
570
- hint: "imageId belongs to a different product."
571
- },
572
- PRODUCT_SYNC_ERROR: {
573
- exitCode: 1,
574
- category: "runtime",
575
- retryable: false,
576
- hint: "Product sync status is 'error'. Re-sync or use a different product."
577
- },
578
- PRODUCT_DELETED_UPSTREAM: {
579
- exitCode: 1,
580
- category: "runtime",
581
- retryable: false,
582
- hint: "Product was deleted upstream. Remove it or re-sync."
583
- },
584
- PRODUCT_NO_PRIMARY_IMAGE: {
585
- exitCode: 1,
586
- category: "runtime",
587
- retryable: false,
588
- hint: "Product has no primary image. Specify --product <id>:<imageId> explicitly."
589
- },
590
- PRODUCT_SOURCE_MANAGED: {
591
- exitCode: 4,
592
- category: "permission",
593
- retryable: false,
594
- hint: "Product is managed by an external integration."
595
- },
596
- // --- 4 CLI-synthetic codes --------------------------------------------------
597
- NETWORK_ERROR: {
598
- exitCode: 6,
599
- category: "network",
600
- retryable: true,
601
- hint: "Network connection failed. Check connectivity and retry."
602
- },
603
- TIMEOUT: {
604
- exitCode: 6,
605
- category: "network",
606
- retryable: true,
607
- hint: "Request timed out. Retry; consider increasing the timeout."
608
- },
609
- PARSE_ERROR: {
610
- exitCode: 1,
611
- category: "runtime",
612
- retryable: false,
613
- hint: "Server returned an unexpected response. Contact support with the request ID."
614
- },
615
- SCHEMA_MISMATCH: {
616
- exitCode: 1,
617
- category: "runtime",
618
- retryable: false,
619
- hint: "Server response did not match the expected schema. Update the CLI."
620
- }
621
- };
622
- function getExitCode(code) {
623
- return EXIT_CODE_MAP[code]?.exitCode ?? 1;
624
- }
625
- function toEnvelopeCode(code) {
626
- return code.startsWith("E_") ? code : `E_${code}`;
627
- }
628
- var SIGINT_EXIT_CODE = 130;
629
-
630
- // src/errors/schema-mismatch.ts
631
209
  var MAX_ISSUES = 5;
632
210
  function formatSchemaIssues(error) {
633
211
  const issues = error.issues;
@@ -651,9 +229,6 @@ function formatPath(path) {
651
229
  if (path.length === 0) return "(root)";
652
230
  return path.map(String).join(".");
653
231
  }
654
-
655
- // src/api/idempotency.ts
656
- import { createHash, randomUUID } from "crypto";
657
232
  function generateIdempotencyKey() {
658
233
  return randomUUID();
659
234
  }
@@ -661,8 +236,6 @@ function generateUploadIdempotencyKey(content, scope) {
661
236
  const hash = createHash("sha256").update(content).digest("hex");
662
237
  return `upload-${scope}-${hash}`;
663
238
  }
664
-
665
- // src/api/retry.ts
666
239
  var DEFAULT_RETRY_OPTIONS = {
667
240
  maxAttempts: 3,
668
241
  baseDelayMs: 200,
@@ -676,7 +249,7 @@ function computeBackoffMs(attempt, baseDelayMs, maxDelayMs, jitterFraction) {
676
249
  return Math.max(0, Math.round(exp + jitter));
677
250
  }
678
251
  function decideRetry(params) {
679
- const { attempt, options, serverRetryAfterMs, isNetworkError, isRetryable } = params;
252
+ const { attempt, options, serverRetryAfterMs, isNetworkError, isRetryable, onWarning: onWarning2 } = params;
680
253
  if (options.signal?.aborted) {
681
254
  return { shouldRetry: false, delayMs: 0, reason: "aborted" };
682
255
  }
@@ -689,7 +262,7 @@ function decideRetry(params) {
689
262
  if (typeof serverRetryAfterMs === "number" && serverRetryAfterMs >= 0) {
690
263
  let delayMs2 = serverRetryAfterMs;
691
264
  if (delayMs2 > ABSOLUTE_MAX_RETRY_AFTER_MS) {
692
- console.warn(`Server Retry-After exceeded ${ABSOLUTE_MAX_RETRY_AFTER_MS}ms; clipping.`);
265
+ onWarning2?.(`Server Retry-After exceeded ${ABSOLUTE_MAX_RETRY_AFTER_MS}ms; clipping.`);
693
266
  delayMs2 = ABSOLUTE_MAX_RETRY_AFTER_MS;
694
267
  }
695
268
  return { shouldRetry: true, delayMs: delayMs2, reason: "rate_limited" };
@@ -729,9 +302,6 @@ function signalToAbortError(signal) {
729
302
  err.name = "AbortError";
730
303
  return err;
731
304
  }
732
-
733
- // src/api/types.ts
734
- import { z as z5 } from "zod";
735
305
  var ServerSuccessSchema = (payload) => z5.object({ data: payload }).strict();
736
306
  var ServerErrorBodySchema = z5.object({
737
307
  error: z5.object({
@@ -742,8 +312,6 @@ var ServerErrorBodySchema = z5.object({
742
312
  details: z5.unknown().optional()
743
313
  }).strict()
744
314
  }).strict();
745
-
746
- // src/api/client.ts
747
315
  var REDACT_BODY_LIMIT = 1024;
748
316
  var REDACT_INPUT_CAP = 100 * 1024;
749
317
  var DEFAULT_USER_AGENT = "clickraft-cli";
@@ -800,7 +368,8 @@ var ApiClient = class {
800
368
  options: { ...retry, signal },
801
369
  serverRetryAfterMs: err.meta.retryAfterMs,
802
370
  isNetworkError,
803
- isRetryable
371
+ isRetryable,
372
+ onWarning: this.options.onWarning
804
373
  });
805
374
  if (!decision.shouldRetry) throw err;
806
375
  try {
@@ -888,10 +457,15 @@ var ApiClient = class {
888
457
  bodyText,
889
458
  statusCode,
890
459
  requestId,
891
- retryAfterHeader: pickHeader(responseHeaders, "retry-after")
460
+ retryAfterHeader: pickHeader(responseHeaders, "retry-after"),
461
+ onWarning: this.options.onWarning,
462
+ isRetryableServerCode: this.options.isRetryableServerCode
892
463
  });
893
464
  }
894
465
  };
466
+ function createClient(options) {
467
+ return new ApiClient(options);
468
+ }
895
469
  function parseSuccessBody(args) {
896
470
  let parsed;
897
471
  try {
@@ -960,7 +534,9 @@ function parseErrorBody(args) {
960
534
  statusCode: args.statusCode,
961
535
  requestId: args.requestId,
962
536
  retryAfterHeader: args.retryAfterHeader,
963
- redactedBody
537
+ redactedBody,
538
+ onWarning: args.onWarning,
539
+ isRetryableServerCode: args.isRetryableServerCode
964
540
  });
965
541
  }
966
542
  function apiErrorFromServerBody(args) {
@@ -972,12 +548,11 @@ function apiErrorFromServerBody(args) {
972
548
  safeMessage = redactTokenLikeFields(error.message);
973
549
  } else {
974
550
  const redactedRawCode = redactTokenLikeFields(error.code);
975
- console.warn(`Unknown server error code: ${redactedRawCode}. Treating as INTERNAL_ERROR.`);
551
+ args.onWarning?.(`Unknown server error code: ${redactedRawCode}. Treating as INTERNAL_ERROR.`);
976
552
  code = "INTERNAL_ERROR";
977
553
  safeMessage = `Unknown server error code: ${redactedRawCode}.`;
978
554
  }
979
- const mapping = EXIT_CODE_MAP[code];
980
- const retryable = mapping?.retryable ?? (args.statusCode >= 500 || args.statusCode === 429);
555
+ const retryable = args.isRetryableServerCode?.(code) ?? (args.statusCode >= 500 || args.statusCode === 429);
981
556
  const retryAfterMs = typeof error.retryAfter === "number" ? error.retryAfter * 1e3 : parseRetryAfterHeader(args.retryAfterHeader);
982
557
  return new ApiError(code, safeMessage, {
983
558
  status: args.statusCode,
@@ -1062,6 +637,435 @@ function errorMessage(err) {
1062
637
  return String(err);
1063
638
  }
1064
639
 
640
+ // src/errors/codes.ts
641
+ var LOGIN_HINT = "Run `clickraft login` to authenticate.";
642
+ var RELOGIN_HINT = "Run `clickraft login` to refresh.";
643
+ var EXIT_CODE_MAP = {
644
+ // --- 37 Phase 8 Q7 codes ----------------------------------------------------
645
+ AUTH_TOKEN_MISSING: {
646
+ exitCode: 3,
647
+ category: "auth",
648
+ retryable: false,
649
+ hint: "Set `CLICKRAFT_TOKEN` or run `clickraft login`."
650
+ },
651
+ AUTH_TOKEN_INVALID: {
652
+ exitCode: 3,
653
+ category: "auth",
654
+ retryable: false,
655
+ hint: "The token is malformed or unknown. Run `clickraft login`."
656
+ },
657
+ AUTH_TOKEN_EXPIRED: {
658
+ exitCode: 3,
659
+ category: "auth",
660
+ retryable: false,
661
+ hint: RELOGIN_HINT
662
+ },
663
+ AUTH_TOKEN_REVOKED: {
664
+ exitCode: 3,
665
+ category: "auth",
666
+ retryable: false,
667
+ hint: "Token was revoked. Run `clickraft login` to mint a new one."
668
+ },
669
+ AUTH_TOKEN_SCOPE_INSUFFICIENT: {
670
+ exitCode: 4,
671
+ category: "permission",
672
+ retryable: false,
673
+ hint: "This token does not have the required scope. Re-run `clickraft login` selecting the needed scopes."
674
+ },
675
+ AUTH_ORG_MISMATCH: {
676
+ exitCode: 4,
677
+ category: "permission",
678
+ retryable: false,
679
+ hint: "This token's org no longer exists or you have been removed."
680
+ },
681
+ AUTH_DEVICE_CODE_PENDING: {
682
+ exitCode: 0,
683
+ category: "success",
684
+ retryable: false,
685
+ hint: "Continue polling."
686
+ },
687
+ AUTH_DEVICE_CODE_EXPIRED: {
688
+ exitCode: 3,
689
+ category: "auth",
690
+ retryable: false,
691
+ hint: "Device code expired. Re-run `clickraft login`."
692
+ },
693
+ AUTH_DEVICE_CODE_SLOW_DOWN: {
694
+ exitCode: 0,
695
+ category: "success",
696
+ retryable: false,
697
+ hint: "Increase polling interval (Retry-After)."
698
+ },
699
+ IDEMPOTENCY_KEY_MISSING: {
700
+ exitCode: 2,
701
+ category: "usage",
702
+ retryable: false,
703
+ hint: "Add `X-Idempotency-Key` header (or `--idempotency-key`)."
704
+ },
705
+ IDEMPOTENCY_KEY_CONFLICT: {
706
+ exitCode: 7,
707
+ category: "conflict",
708
+ retryable: false,
709
+ hint: "Same key was used with a different request body. Use a new key."
710
+ },
711
+ PLAN_FEATURE_LOCKED: {
712
+ exitCode: 4,
713
+ category: "permission",
714
+ retryable: false,
715
+ hint: "API access is not enabled on your plan. Visit billing settings."
716
+ },
717
+ PLAN_QUOTA_EXCEEDED: {
718
+ exitCode: 5,
719
+ category: "quota",
720
+ retryable: false,
721
+ hint: "Daily API quota exhausted. Wait, or upgrade your plan."
722
+ },
723
+ INSUFFICIENT_CREDITS: {
724
+ exitCode: 5,
725
+ category: "quota",
726
+ retryable: false,
727
+ hint: "Top up credits to retry."
728
+ },
729
+ CREDIT_DEDUCTION_FAILED: {
730
+ exitCode: 1,
731
+ category: "runtime",
732
+ retryable: true,
733
+ hint: "Could not charge credits. Retry; no charge was made."
734
+ },
735
+ RATE_LIMITED: {
736
+ exitCode: 5,
737
+ category: "quota",
738
+ retryable: true,
739
+ hint: "Slow down. Honor Retry-After."
740
+ },
741
+ INPUT_INVALID_FORMAT: {
742
+ exitCode: 2,
743
+ category: "usage",
744
+ retryable: false,
745
+ hint: "Check input format and try again."
746
+ },
747
+ INPUT_TOO_LONG: {
748
+ exitCode: 2,
749
+ category: "usage",
750
+ retryable: false,
751
+ hint: "Field exceeded max length."
752
+ },
753
+ WORKFLOW_VALIDATION_FAILED: {
754
+ exitCode: 2,
755
+ category: "usage",
756
+ retryable: false,
757
+ hint: "Run `workflow validate` for the full list of errors."
758
+ },
759
+ WORKFLOW_NODE_TYPE_UNKNOWN: {
760
+ exitCode: 2,
761
+ category: "usage",
762
+ retryable: false,
763
+ hint: "Run `clickraft nodes list` for the current set of node types."
764
+ },
765
+ CANVAS_REV_MISMATCH: {
766
+ exitCode: 7,
767
+ category: "conflict",
768
+ retryable: false,
769
+ hint: "The workflow was updated elsewhere. Re-fetch and retry with the current canvas_rev."
770
+ },
771
+ CANVAS_REV_REQUIRED: {
772
+ exitCode: 2,
773
+ category: "usage",
774
+ retryable: false,
775
+ hint: "Include --canvas-rev or use --auto-rev."
776
+ },
777
+ WORKER_UNREACHABLE: {
778
+ exitCode: 6,
779
+ category: "network",
780
+ retryable: true,
781
+ hint: "Collaboration service temporarily unavailable. Retry."
782
+ },
783
+ IDEMPOTENCY_IN_PROGRESS: {
784
+ exitCode: 7,
785
+ category: "conflict",
786
+ retryable: false,
787
+ hint: "A request with this idempotency key is currently in-flight. Retry in a moment."
788
+ },
789
+ TOKEN_NAME_INVALID: {
790
+ exitCode: 2,
791
+ category: "usage",
792
+ retryable: false,
793
+ hint: "Agent token name is malformed."
794
+ },
795
+ WORKFLOW_NOT_FOUND: {
796
+ exitCode: 2,
797
+ category: "usage",
798
+ retryable: false,
799
+ hint: "No workflow with that ID in your org."
800
+ },
801
+ NOT_FOUND: {
802
+ exitCode: 2,
803
+ category: "usage",
804
+ retryable: false,
805
+ hint: "Resource not found."
806
+ },
807
+ NODE_TYPE_NOT_FOUND: {
808
+ exitCode: 2,
809
+ category: "usage",
810
+ retryable: false,
811
+ hint: "Run `clickraft nodes list` for the current set of node types."
812
+ },
813
+ FEATURE_DISABLED: {
814
+ exitCode: 2,
815
+ category: "usage",
816
+ retryable: false,
817
+ hint: "This endpoint is not enabled. Contact support if you believe this is in error."
818
+ },
819
+ FORBIDDEN: {
820
+ exitCode: 4,
821
+ category: "permission",
822
+ retryable: false,
823
+ hint: "Your role does not permit this action."
824
+ },
825
+ MODEL_NOT_FOUND: {
826
+ exitCode: 2,
827
+ category: "usage",
828
+ retryable: false,
829
+ hint: "Unknown `modelSlug`. Run `brand-model list` for the current set."
830
+ },
831
+ PROVIDER_UNAVAILABLE: {
832
+ exitCode: 6,
833
+ category: "network",
834
+ retryable: true,
835
+ hint: "Provider is temporarily unavailable. Retry shortly."
836
+ },
837
+ STORAGE_UPLOAD_FAILED: {
838
+ exitCode: 1,
839
+ category: "runtime",
840
+ retryable: true,
841
+ hint: "Upload failed mid-stream. Retry."
842
+ },
843
+ STORAGE_QUOTA_EXCEEDED: {
844
+ exitCode: 5,
845
+ category: "quota",
846
+ retryable: false,
847
+ hint: "Org storage quota exceeded. Free space or upgrade."
848
+ },
849
+ GEN_CONTENT_REFUSAL: {
850
+ exitCode: 1,
851
+ category: "runtime",
852
+ retryable: false,
853
+ hint: "The model declined the request via safety policy."
854
+ },
855
+ GEN_OUTPUT_SCHEMA_MISMATCH: {
856
+ exitCode: 1,
857
+ category: "runtime",
858
+ retryable: true,
859
+ hint: "Provider returned malformed output. Full refund issued. Retry."
860
+ },
861
+ GEN_EMPTY_OUTPUT: {
862
+ exitCode: 1,
863
+ category: "runtime",
864
+ retryable: true,
865
+ hint: "Provider returned no usable output. Full refund issued. Retry."
866
+ },
867
+ GEN_ITEM_COUNT_MISMATCH: {
868
+ exitCode: 0,
869
+ category: "success",
870
+ retryable: false,
871
+ hint: "Partial output kept; no refund."
872
+ },
873
+ AGENT_FINGERPRINT_REJECTED: {
874
+ exitCode: 4,
875
+ category: "permission",
876
+ retryable: false,
877
+ hint: "This client identification is not permitted. Contact support."
878
+ },
879
+ INTERNAL_ERROR: {
880
+ exitCode: 1,
881
+ category: "runtime",
882
+ retryable: true,
883
+ hint: "Server error. Retry; if it persists, contact support with the request ID."
884
+ },
885
+ CREDENTIALS_LOCKED: {
886
+ exitCode: 1,
887
+ category: "runtime",
888
+ retryable: true,
889
+ hint: "Another CLI invocation holds the credentials lock. Retry shortly."
890
+ },
891
+ CREDENTIALS_PERMISSIONS_LOOSE: {
892
+ exitCode: 0,
893
+ category: "success",
894
+ retryable: false,
895
+ hint: "Credentials file mode is wider than 0600; continuing."
896
+ },
897
+ // --- 5 additional codes introduced by 1A.1 ---------------------------------
898
+ AUTH_ACCESS_DENIED: {
899
+ exitCode: 3,
900
+ category: "auth",
901
+ retryable: false,
902
+ hint: "You denied the OAuth grant. Re-run `clickraft login` to retry."
903
+ },
904
+ AUTH_FLOW_CANCELLED: {
905
+ exitCode: 3,
906
+ category: "auth",
907
+ retryable: false,
908
+ hint: "Authentication was cancelled. Re-run `clickraft login`."
909
+ },
910
+ CREDENTIALS_CORRUPT: {
911
+ exitCode: 1,
912
+ category: "runtime",
913
+ retryable: false,
914
+ hint: "Credentials file is unreadable. Remove it and run `clickraft login`."
915
+ },
916
+ CREDENTIALS_NOT_FOUND: {
917
+ exitCode: 3,
918
+ category: "auth",
919
+ retryable: false,
920
+ hint: LOGIN_HINT
921
+ },
922
+ CREDENTIALS_PROFILE_NOT_FOUND: {
923
+ exitCode: 2,
924
+ category: "usage",
925
+ retryable: false,
926
+ hint: "No such profile. Use `clickraft auth profiles` to list available profiles."
927
+ },
928
+ // --- 8 brand model resolution codes -----------------------------------------
929
+ BRAND_MODEL_NOT_FOUND: {
930
+ exitCode: 1,
931
+ category: "runtime",
932
+ retryable: false,
933
+ hint: "No brand model with that ID. Run `clickraft brand-model list`."
934
+ },
935
+ BRAND_MODEL_NOT_A_MODEL: {
936
+ exitCode: 1,
937
+ category: "runtime",
938
+ retryable: false,
939
+ hint: "The specified asset is not a brand model."
940
+ },
941
+ BRAND_MODEL_NOT_ACTIVE: {
942
+ exitCode: 1,
943
+ category: "runtime",
944
+ retryable: false,
945
+ hint: "Brand model is not active. Check its status in the dashboard."
946
+ },
947
+ BRAND_MODEL_NO_REFERENCES: {
948
+ exitCode: 1,
949
+ category: "runtime",
950
+ retryable: false,
951
+ hint: "Brand model has no reference images. Upload references first."
952
+ },
953
+ BRAND_MODEL_POSE_NOT_FOUND: {
954
+ exitCode: 1,
955
+ category: "runtime",
956
+ retryable: false,
957
+ hint: "Requested pose not found. Valid: front, 3/4-right, right, left, 3/4-left, back, approved."
958
+ },
959
+ BRAND_MODEL_DUPLICATE_ID: {
960
+ exitCode: 2,
961
+ category: "usage",
962
+ retryable: false,
963
+ hint: "Same brand model ID appears more than once."
964
+ },
965
+ MODEL_DOES_NOT_SUPPORT_REFERENCES: {
966
+ exitCode: 2,
967
+ category: "usage",
968
+ retryable: false,
969
+ hint: "This model does not support brand model references."
970
+ },
971
+ REFERENCE_LIMIT_EXCEEDED: {
972
+ exitCode: 2,
973
+ category: "usage",
974
+ retryable: false,
975
+ hint: "Too many reference images. Check model limits."
976
+ },
977
+ // --- 8 product reference resolver codes -------------------------------------
978
+ PRODUCT_NOT_FOUND: {
979
+ exitCode: 1,
980
+ category: "runtime",
981
+ retryable: false,
982
+ hint: "No product with that ID in your org. Run `clickraft product list`."
983
+ },
984
+ PRODUCT_DUPLICATE_REFERENCE: {
985
+ exitCode: 2,
986
+ category: "usage",
987
+ retryable: false,
988
+ hint: "Same (productId, imageId) tuple appears twice in the request."
989
+ },
990
+ PRODUCT_IMAGE_NOT_FOUND: {
991
+ exitCode: 1,
992
+ category: "runtime",
993
+ retryable: false,
994
+ hint: "imageId does not exist in the product images."
995
+ },
996
+ PRODUCT_IMAGE_MISMATCH: {
997
+ exitCode: 2,
998
+ category: "usage",
999
+ retryable: false,
1000
+ hint: "imageId belongs to a different product."
1001
+ },
1002
+ PRODUCT_SYNC_ERROR: {
1003
+ exitCode: 1,
1004
+ category: "runtime",
1005
+ retryable: false,
1006
+ hint: "Product sync status is 'error'. Re-sync or use a different product."
1007
+ },
1008
+ PRODUCT_DELETED_UPSTREAM: {
1009
+ exitCode: 1,
1010
+ category: "runtime",
1011
+ retryable: false,
1012
+ hint: "Product was deleted upstream. Remove it or re-sync."
1013
+ },
1014
+ PRODUCT_NO_PRIMARY_IMAGE: {
1015
+ exitCode: 1,
1016
+ category: "runtime",
1017
+ retryable: false,
1018
+ hint: "Product has no primary image. Specify --product <id>:<imageId> explicitly."
1019
+ },
1020
+ PRODUCT_SOURCE_MANAGED: {
1021
+ exitCode: 4,
1022
+ category: "permission",
1023
+ retryable: false,
1024
+ hint: "Product is managed by an external integration."
1025
+ },
1026
+ // --- 4 CLI-synthetic codes --------------------------------------------------
1027
+ NETWORK_ERROR: {
1028
+ exitCode: 6,
1029
+ category: "network",
1030
+ retryable: true,
1031
+ hint: "Network connection failed. Check connectivity and retry."
1032
+ },
1033
+ TIMEOUT: {
1034
+ exitCode: 6,
1035
+ category: "network",
1036
+ retryable: true,
1037
+ hint: "Request timed out. Retry; consider increasing the timeout."
1038
+ },
1039
+ PARSE_ERROR: {
1040
+ exitCode: 1,
1041
+ category: "runtime",
1042
+ retryable: false,
1043
+ hint: "Server returned an unexpected response. Contact support with the request ID."
1044
+ },
1045
+ SCHEMA_MISMATCH: {
1046
+ exitCode: 1,
1047
+ category: "runtime",
1048
+ retryable: false,
1049
+ hint: "Server response did not match the expected schema. Update the CLI."
1050
+ }
1051
+ };
1052
+ function getExitCode(code) {
1053
+ return EXIT_CODE_MAP[code]?.exitCode ?? 1;
1054
+ }
1055
+ function toEnvelopeCode(code) {
1056
+ return code.startsWith("E_") ? code : `E_${code}`;
1057
+ }
1058
+ var SIGINT_EXIT_CODE = 130;
1059
+
1060
+ // src/api/client.ts
1061
+ var isRetryableServerCode = (code) => EXIT_CODE_MAP[code].retryable;
1062
+ var onWarning = (msg) => {
1063
+ console.warn(msg);
1064
+ };
1065
+ function createApiClient(opts) {
1066
+ return createClient({ ...opts, onWarning, isRetryableServerCode });
1067
+ }
1068
+
1065
1069
  // src/auth/errors.ts
1066
1070
  var AuthError = class extends Error {
1067
1071
  code;
@@ -1488,7 +1492,7 @@ async function getBalance(options) {
1488
1492
  if (!config.accessToken) {
1489
1493
  throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
1490
1494
  }
1491
- const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
1495
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => createApiClient({
1492
1496
  apiBaseUrl,
1493
1497
  accessToken,
1494
1498
  signal: options.signal,
@@ -1514,7 +1518,7 @@ async function listBrandModels(options) {
1514
1518
  if (!config.accessToken) {
1515
1519
  throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
1516
1520
  }
1517
- const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
1521
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => createApiClient({
1518
1522
  apiBaseUrl,
1519
1523
  accessToken,
1520
1524
  signal: options.signal,
@@ -1679,7 +1683,7 @@ async function buildClient(opts) {
1679
1683
  flagApiBaseUrl: opts.apiBaseUrl,
1680
1684
  flagToken: opts.token
1681
1685
  });
1682
- return new ApiClient({
1686
+ return createApiClient({
1683
1687
  apiBaseUrl: cfg.apiBaseUrl,
1684
1688
  accessToken: cfg.accessToken,
1685
1689
  signal: opts.signal,
@@ -1846,7 +1850,7 @@ async function buildGenerateClient(opts) {
1846
1850
  flagApiBaseUrl: opts.apiBaseUrl,
1847
1851
  flagToken: opts.token
1848
1852
  });
1849
- return new ApiClient({
1853
+ return createApiClient({
1850
1854
  apiBaseUrl: cfg.apiBaseUrl,
1851
1855
  accessToken: cfg.accessToken,
1852
1856
  signal: opts.signal,
@@ -2618,7 +2622,7 @@ async function logout(options) {
2618
2622
  let serverRevoked = false;
2619
2623
  let revokeFailureMessage;
2620
2624
  if (options.revoke) {
2621
- const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
2625
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => createApiClient({
2622
2626
  apiBaseUrl,
2623
2627
  accessToken,
2624
2628
  signal: options.signal,
@@ -2703,7 +2707,7 @@ async function buildModelClient(opts) {
2703
2707
  flagApiBaseUrl: opts.apiBaseUrl,
2704
2708
  flagToken: opts.token
2705
2709
  });
2706
- return new ApiClient({
2710
+ return createApiClient({
2707
2711
  apiBaseUrl: cfg.apiBaseUrl,
2708
2712
  accessToken: cfg.accessToken,
2709
2713
  signal: opts.signal,
@@ -2756,7 +2760,7 @@ async function buildNodesClient(opts) {
2756
2760
  flagApiBaseUrl: opts.apiBaseUrl,
2757
2761
  flagToken: opts.token
2758
2762
  });
2759
- return new ApiClient({
2763
+ return createApiClient({
2760
2764
  apiBaseUrl: cfg.apiBaseUrl,
2761
2765
  accessToken: cfg.accessToken,
2762
2766
  signal: opts.signal,
@@ -2800,7 +2804,7 @@ async function listProducts(options) {
2800
2804
  if (!config.accessToken) {
2801
2805
  throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
2802
2806
  }
2803
- const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
2807
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => createApiClient({
2804
2808
  apiBaseUrl,
2805
2809
  accessToken,
2806
2810
  signal: options.signal,
@@ -2909,7 +2913,7 @@ async function buildTemplateClient(opts) {
2909
2913
  flagApiBaseUrl: opts.apiBaseUrl,
2910
2914
  flagToken: opts.token
2911
2915
  });
2912
- return new ApiClient({
2916
+ return createApiClient({
2913
2917
  apiBaseUrl: cfg.apiBaseUrl,
2914
2918
  accessToken: cfg.accessToken,
2915
2919
  signal: opts.signal,
@@ -3003,7 +3007,7 @@ async function listTokens(options) {
3003
3007
  if (!config.accessToken) {
3004
3008
  throw new AuthError("AUTH_TOKEN_MISSING", `Not authenticated. Run \`clickraft login\` first.`);
3005
3009
  }
3006
- const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => new ApiClient({
3010
+ const factory = options.apiClientFactory ?? ((apiBaseUrl, accessToken) => createApiClient({
3007
3011
  apiBaseUrl,
3008
3012
  accessToken,
3009
3013
  signal: options.signal,
@@ -3075,7 +3079,7 @@ async function buildWorkflowClient(opts) {
3075
3079
  flagApiBaseUrl: opts.apiBaseUrl,
3076
3080
  flagToken: opts.token
3077
3081
  });
3078
- return new ApiClient({
3082
+ return createApiClient({
3079
3083
  apiBaseUrl: cfg.apiBaseUrl,
3080
3084
  accessToken: cfg.accessToken,
3081
3085
  signal: opts.signal,