@agenzo/token-cli 0.4.0 → 0.5.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/index.js CHANGED
@@ -18,6 +18,9 @@ var CODE_NUM = {
18
18
  AUTH_INVITE_CODE_INVALID: 1004,
19
19
  KEY_INVALID: 1101,
20
20
  KEY_SCOPE_DENIED: 1102,
21
+ PAYMENT_METHOD_NOT_FOUND: 1401,
22
+ PAYMENT_METHOD_DISABLED: 1402,
23
+ INVALID_PAYMENT_METHOD: 1403,
21
24
  RESOURCE_NOT_FOUND: 2001,
22
25
  RESOURCE_CONFLICT: 2002,
23
26
  RESOURCE_STATE_INVALID: 2003,
@@ -38,6 +41,23 @@ var CODE_NUM = {
38
41
  QUOTE_EXPIRED: 4202,
39
42
  BOOKING_FAILED: 4203,
40
43
  CANCELLATION_NOT_ALLOWED: 4204,
44
+ PRICE_MISMATCH: 4205,
45
+ VEHICLE_CLASS_MISMATCH: 4206,
46
+ PAYMENT_METHOD_REQUIRED: 4207,
47
+ QUOTE_CACHE_UNAVAILABLE: 4208,
48
+ ARREARS_OUTSTANDING: 4209,
49
+ CONTACT_REQUIRED: 4210,
50
+ INVALID_PHONE: 4211,
51
+ SEAT_LIMIT_EXCEEDED: 4213,
52
+ RIDE_NOT_FOUND: 4212,
53
+ NO_AVAILABILITY: 4301,
54
+ PRICE_CHANGED: 4302,
55
+ NAME_FORMAT_INVALID: 4303,
56
+ HOTEL_ORDER_NOT_FOUND: 4304,
57
+ ALREADY_CANCELLED: 4305,
58
+ CHECKOUT_NOT_ALLOWED: 4306,
59
+ CHECKOUT_TASK_NOT_FOUND: 4307,
60
+ PAY_PER_CALL_NOT_AVAILABLE: 4308,
41
61
  RATE_LIMITED: 5001,
42
62
  UPSTREAM_ERROR: 5101,
43
63
  INTERNAL_ERROR: 5201,
@@ -55,23 +75,29 @@ function codeNum(code) {
55
75
  return CODE_NUM[code];
56
76
  }
57
77
  var CliError = class _CliError extends Error {
58
- constructor(code, message, statusCode, requestId, upstream) {
78
+ constructor(code, message, statusCode, requestId, upstream, backendMessage) {
59
79
  super(message);
60
80
  this.code = code;
61
81
  this.statusCode = statusCode;
62
82
  this.requestId = requestId;
63
83
  this.upstream = upstream;
84
+ this.backendMessage = backendMessage;
64
85
  this.name = "CliError";
65
86
  }
66
87
  code;
67
88
  statusCode;
68
89
  requestId;
69
90
  upstream;
91
+ backendMessage;
70
92
  /**
71
93
  * Map a backend ApiError to a CLI-facing code (§8.5 transitional mapping).
72
- * Backend numeric codes / HTTP status are translated to §8.4 string codes;
73
- * the raw backend message is NOT passed through (§8.1 principle 4) — a
74
- * stable, fixed message per code is used instead.
94
+ * Backend numeric codes / HTTP status are translated to §8.4 string codes.
95
+ * The top-level `message` is always the stable, catalog-owned text for the
96
+ * resolved code (§8.1 principle 4 never let raw backend text become the
97
+ * primary/stable message). The backend's own message is NOT discarded,
98
+ * though: it is preserved on `backendMessage` and surfaced in the envelope
99
+ * as `backend_message` so the caller can see the real reason (e.g. which
100
+ * field failed validation) alongside the stable code/message.
75
101
  *
76
102
  * The optional `opts.auth` selects the auth semantics for the 401 mapping:
77
103
  * - `'bearer'` (default, admin-cli Bearer Token): 401 → `AUTH_SESSION_EXPIRED`.
@@ -95,7 +121,8 @@ var CliError = class _CliError extends Error {
95
121
  STABLE_MESSAGE[error.code] ?? "Request failed. Please check your input and retry.",
96
122
  error.statusCode,
97
123
  error.requestId,
98
- error.upstream
124
+ error.upstream,
125
+ error.errorMessage
99
126
  );
100
127
  }
101
128
  const status = error.statusCode;
@@ -112,7 +139,8 @@ var CliError = class _CliError extends Error {
112
139
  STABLE_MESSAGE[code] ?? "Request failed. Please check your input and retry.",
113
140
  status,
114
141
  error.requestId,
115
- error.upstream
142
+ error.upstream,
143
+ error.errorMessage
116
144
  );
117
145
  }
118
146
  };
@@ -167,6 +195,9 @@ var STABLE_MESSAGE = {
167
195
  AUTH_INVITE_CODE_INVALID: "The invitation code is invalid.",
168
196
  KEY_INVALID: "The API key is invalid or has been revoked. Please check your --api-key and retry.",
169
197
  KEY_SCOPE_DENIED: "This API key does not have the required scope for this command.",
198
+ PAYMENT_METHOD_NOT_FOUND: "Payment method not found.",
199
+ PAYMENT_METHOD_DISABLED: "This payment method has been disabled.",
200
+ INVALID_PAYMENT_METHOD: "The payment method is not available for this operation.",
170
201
  RESOURCE_NOT_FOUND: "The resource was not found or does not belong to the current organization.",
171
202
  RESOURCE_CONFLICT: "A resource with the same unique value already exists.",
172
203
  RESOURCE_STATE_INVALID: "The resource is in a state that does not permit this operation.",
@@ -188,18 +219,38 @@ var STABLE_MESSAGE = {
188
219
  VEHICLE_UNAVAILABLE: "No vehicle is available for the requested trip. Please try a different time or class.",
189
220
  QUOTE_EXPIRED: "The quote has expired. Please request a new quote and retry.",
190
221
  BOOKING_FAILED: "The booking could not be completed. Please retry.",
222
+ PRICE_MISMATCH: "The price does not match the quote. Re-quote and use the exact amount from the quote response.",
223
+ VEHICLE_CLASS_MISMATCH: "The vehicle class does not match the quote. Use the vehicle class from the quote response.",
224
+ PAYMENT_METHOD_REQUIRED: "Pay-per-call billing requires an active payment method. Please add a card first.",
225
+ QUOTE_CACHE_UNAVAILABLE: "Quote cache is temporarily unavailable. Please request a new quote and retry.",
226
+ ARREARS_OUTSTANDING: "You have outstanding arrears. Please settle your balance before booking a new ride.",
227
+ CONTACT_REQUIRED: "Passenger contact information is required. Please provide a passenger name and phone number.",
228
+ INVALID_PHONE: "The passenger phone number is not valid. Please provide a valid international number.",
229
+ SEAT_LIMIT_EXCEEDED: "The number of child/infant/toddler seats exceeds the vehicle capacity. Reduce seats or choose a larger vehicle.",
230
+ RIDE_NOT_FOUND: "The ride order was not found or does not belong to you.",
191
231
  CANCELLATION_NOT_ALLOWED: "This order cannot be cancelled in its current state.",
232
+ NO_AVAILABILITY: "No rooms available for the selected hotel and dates.",
233
+ PRICE_CHANGED: "The price changed since the quote. Re-quote and confirm.",
234
+ NAME_FORMAT_INVALID: "The guest name format is not accepted; use Latin letters.",
235
+ HOTEL_ORDER_NOT_FOUND: "Hotel order not found.",
236
+ ALREADY_CANCELLED: "This hotel order is already cancelled.",
237
+ CHECKOUT_NOT_ALLOWED: "A partial check-out is not allowed in the current state.",
238
+ CHECKOUT_TASK_NOT_FOUND: "Check-out application not found.",
239
+ PAY_PER_CALL_NOT_AVAILABLE: "Pay-per-call billing is not available; use monthly_settlement.",
192
240
  NOT_IMPLEMENTED: "This operation is not implemented yet."
193
241
  };
194
242
  function toErrorEnvelope(error) {
195
243
  if (error instanceof CliError) {
244
+ const stableMessage = error.message || "Unknown error";
245
+ const backendMessage = error.backendMessage && error.backendMessage !== stableMessage ? error.backendMessage : void 0;
196
246
  return {
197
247
  error: {
198
248
  code: error.code,
199
249
  code_num: codeNum(error.code),
200
- message: error.message || "Unknown error",
250
+ message: stableMessage,
201
251
  ...error.requestId ? { request_id: error.requestId } : {},
202
- ...error.upstream ? { upstream: error.upstream } : {}
252
+ ...error.upstream ? { upstream: error.upstream } : {},
253
+ ...backendMessage ? { backend_message: backendMessage } : {}
203
254
  }
204
255
  };
205
256
  }
@@ -262,13 +313,27 @@ function isBelow(current, minimum) {
262
313
  var ApiClient = class {
263
314
  baseUrl;
264
315
  timeout;
316
+ /**
317
+ * Correlation id for this CLI invocation. Generated once per `ApiClient`
318
+ * instance (i.e. once per command execution — each `apps/*` entrypoint
319
+ * constructs exactly one `ApiClient` per process run) and sent as
320
+ * `X-Request-Id` on every request this client makes. The backend's
321
+ * `RequestIdMiddleware` adopts a client-supplied `X-Request-Id` verbatim as
322
+ * both `trace_id` and `request_id` (only generating its own when the header
323
+ * is absent), so every HTTP call belonging to one CLI operation — including
324
+ * multi-step flows like `payment-methods add`'s create + poll + get — shares
325
+ * a single trace_id server-side and can be followed as one chain in logs.
326
+ */
327
+ requestId;
265
328
  constructor(config) {
266
329
  this.baseUrl = config.baseUrl.replace(/\/+$/, "");
267
330
  this.timeout = config.timeout ?? 6e4;
331
+ this.requestId = crypto.randomUUID();
268
332
  }
269
333
  buildHeaders(auth) {
270
334
  const headers = {
271
- "User-Agent": `agenzo-admin-cli/${getCurrentVersion()}`
335
+ "User-Agent": `agenzo-admin-cli/${getCurrentVersion()}`,
336
+ "X-Request-Id": this.requestId
272
337
  };
273
338
  if (auth.type === "bearer") {
274
339
  headers["Authorization"] = `Bearer ${auth.token}`;
@@ -560,29 +625,6 @@ var STATUS_ICONS = {
560
625
  warning: "\u26A0",
561
626
  loading: "\u280B"
562
627
  };
563
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
564
- function createSpinner(message, intervalMs = 60) {
565
- let frameIdx = 0;
566
- let currentMessage = message;
567
- const render2 = () => {
568
- process.stdout.write(`\r\x1B[K${SPINNER_FRAMES[frameIdx]} ${currentMessage}`);
569
- frameIdx = (frameIdx + 1) % SPINNER_FRAMES.length;
570
- };
571
- render2();
572
- const timer = setInterval(render2, intervalMs);
573
- return {
574
- update(msg) {
575
- currentMessage = msg;
576
- },
577
- stop(type, finalMessage) {
578
- clearInterval(timer);
579
- process.stdout.write("\r\x1B[K");
580
- if (type && finalMessage) {
581
- console.log(Formatter.status(type, finalMessage));
582
- }
583
- }
584
- };
585
- }
586
628
  var Formatter = class _Formatter {
587
629
  /** Get display width of a string (CJK characters count as 2) */
588
630
  static displayWidth(str) {
@@ -768,6 +810,8 @@ async function collectPaymentMethodParams(type, flags) {
768
810
 
769
811
  // src/verb-schema.ts
770
812
  var CLI_NAME = "agenzo-token-cli";
813
+ var PAYMENT_METHODS_NOUN = "payment-methods";
814
+ var PAYMENT_TOKENS_NOUN = "payment-tokens";
771
815
  function wantsJsonSchema(argv = process.argv) {
772
816
  for (let i = 0; i < argv.length; i++) {
773
817
  const a = argv[i];
@@ -790,140 +834,236 @@ function attachSchemaHelp(cmd, schema) {
790
834
  }
791
835
  var pmAddSchema = {
792
836
  cli: CLI_NAME,
793
- noun: "payment-methods",
837
+ noun: PAYMENT_METHODS_NOUN,
794
838
  verb: "add",
795
- description: "Add a payment method. --payment-brand evo (default): card binding + 3DS via Drop-in session. --payment-brand unionpay: UPI Agent Pay enrollment (requires --member); returns enroll_url, result arrives asynchronously via webhook.",
839
+ description: "Add a payment method (manual 3DS or Drop-in session)",
796
840
  flags: {
797
- "payment-brand": {
798
- type: "string",
799
- required: false,
800
- default: "evo",
801
- description: 'Payment brand: "evo" (default; 3DS/Drop-in binding) or "unionpay" (UPI Agent Pay enrollment)',
802
- constraints: "evo | unionpay"
803
- },
804
- member: {
805
- type: "string",
806
- required: "conditional",
807
- description: "End-user member id (required when --payment-brand unionpay; identifies which user this card belongs to). Ignored for evo payment brand.",
808
- source: "from_previous_step",
809
- from: "user.member_id or auth context"
810
- },
841
+ type: { type: "string", required: false, default: "card", description: "Payment method type" },
811
842
  mode: {
812
843
  type: "string",
813
844
  required: false,
814
- default: "dropin",
815
- description: 'Add mode (evo payment brand only): "manual" (CLI collects card details) or "dropin" (mint a Drop-in session)',
845
+ default: "manual",
846
+ description: 'Add mode: "manual" (CLI collects card details and polls 3DS) or "dropin" (mint a Drop-in session; the app opens the Drop-in SDK for the user to enter card details securely \u2014 use this mode when the user wants to bind/add a card from chat)',
816
847
  constraints: "manual | dropin"
817
848
  },
818
849
  email: {
850
+ type: "string",
851
+ required: "conditional",
852
+ description: "Manual mode: email for 3DS verification. Dropin mode: email used as the Drop-in session reference. Use the user's login profile email \u2014 never ask in chat."
853
+ },
854
+ "idempotency-key": {
819
855
  type: "string",
820
856
  required: false,
821
- description: "Email for 3DS verification (evo/manual) or Drop-in session reference (evo/dropin)"
857
+ description: "Idempotency key forwarded verbatim as the Idempotency-Key header (manual mode only)"
822
858
  },
823
859
  "no-poll": {
824
860
  type: "bool",
825
861
  required: false,
826
862
  default: false,
827
- description: "Dropin mode: mint session and exit immediately without polling verification status"
863
+ description: "Dropin mode: mint the session, print it, and exit immediately without polling verification status (for server/SDK-driven flows where the front-end completes the binding). Agents integrating with a UI card flow should set this."
828
864
  }
829
865
  },
830
866
  response: {
831
867
  id: { type: "string", description: "Payment method id" },
832
- status: {
833
- type: "string",
834
- description: "PENDING (awaiting enrollment/3DS) / ACTIVE / FAILED / DISABLED"
835
- },
836
- "payment-brand": { type: "string", description: "evo or unionpay" },
837
- session_id: {
838
- type: "string|absent",
839
- description: "Drop-in session id (evo/dropin mode only)"
840
- },
841
- enroll_url: {
842
- type: "string|absent",
843
- description: "UnionPay enrollment URL (unionpay payment brand only) \u2014 user opens this to bind card"
844
- },
845
- correlation_id: {
846
- type: "string|absent",
847
- description: "src_correlation_id for tracking enrollment result (unionpay payment brand only)"
848
- }
868
+ session_id: { type: "string|absent", description: "Drop-in session id (dropin mode only) \u2014 pass to the front-end Drop-in SDK, never read aloud to the user" },
869
+ status: { type: "string", description: "PENDING | ACTIVE | FAILED | EXPIRED" },
870
+ brand: { type: "string|absent", description: "Card brand, once known" },
871
+ last4: { type: "string|absent", description: "Card last 4 digits, once known" }
849
872
  },
850
873
  example: {
851
- command: "agenzo-token-cli payment-methods add --payment-brand unionpay --member usr_abc123 --format json",
852
- output_summary: "Returns enroll_url. User opens the URL in a browser to complete card binding. Result arrives async via webhook; poll with `payment-methods list` or `payment-methods get`."
874
+ command: "agenzo-token-cli payment-methods add --mode dropin --email user@example.com --no-poll",
875
+ output_summary: "Dropin mode returns { id, session_id }. Never read card numbers/CVV/expiry to or from the user (PCI) \u2014 the Drop-in SDK collects them securely."
853
876
  },
854
877
  error_recovery: {
855
- PARAM_INVALID: "Fix the offending flag (--payment-brand must be evo|unionpay; --member required for unionpay).",
856
- UPSTREAM_ERROR: "UPI enrollment initiation failed. Retry after a short delay (may be transient network).",
857
- MEMBER_REQUIRED: "UnionPay payment brand requires --member <id>. Supply the end-user member identifier."
878
+ PARAM_INVALID: 'Fix the offending flag (mode must be "manual" or "dropin"), then retry.'
858
879
  }
859
880
  };
860
881
  var pmListSchema = {
861
882
  cli: CLI_NAME,
862
- noun: "payment-methods",
883
+ noun: PAYMENT_METHODS_NOUN,
863
884
  verb: "list",
864
- description: "List all payment methods for the authenticated developer",
865
- flags: {},
885
+ description: "List payment methods",
886
+ flags: {
887
+ member: { type: "string", required: false, description: "Filter by member ID" }
888
+ },
866
889
  response: {
867
890
  payment_methods: {
868
891
  type: "array",
869
- description: "List of payment methods",
892
+ description: "Payment methods for the developer (optionally scoped to --member)",
870
893
  items: {
871
894
  id: { type: "string", description: "Payment method id" },
872
- status: { type: "string", description: "PENDING / ACTIVE / FAILED / DISABLED" },
873
- payment_brand: { type: "string", description: "evo or unionpay" },
874
- brand: { type: "string|null", description: "Card brand (Visa, Mastercard, UnionPay, etc.)" },
875
- last4: { type: "string|null", description: "Last 4 digits of card" },
876
- exp_month: { type: "int|null", description: "Expiry month" },
877
- exp_year: { type: "int|null", description: "Expiry year" }
895
+ type: { type: "string", description: "Payment method type (e.g. card)" },
896
+ status: { type: "string", description: "PENDING | ACTIVE | FAILED | DISABLED | EXPIRED" },
897
+ brand: { type: "string|null", description: "Card brand" },
898
+ first6: { type: "string|null", description: "Card first 6 digits" },
899
+ last4: { type: "string|null", description: "Card last 4 digits" }
878
900
  }
879
901
  }
880
902
  },
881
903
  example: {
882
- command: "agenzo-token-cli payment-methods list --format json",
883
- output_summary: "Returns array of payment methods with status and card info."
904
+ command: "agenzo-token-cli payment-methods list",
905
+ output_summary: "Returns payment_methods[]. Check for status=ACTIVE before assuming the user has a usable card."
884
906
  }
885
907
  };
886
908
  var pmGetSchema = {
887
909
  cli: CLI_NAME,
888
- noun: "payment-methods",
910
+ noun: PAYMENT_METHODS_NOUN,
889
911
  verb: "get",
890
912
  description: "Get a payment method by ID",
891
913
  flags: {
892
- id: { type: "string", required: true, description: "Payment method id", source: "from_previous_step", from: "payment_methods[].id" }
914
+ pm_id: { type: "string", required: true, description: "Payment method id (positional argument)" }
893
915
  },
894
916
  response: {
895
917
  id: { type: "string", description: "Payment method id" },
896
- status: { type: "string", description: "PENDING / ACTIVE / FAILED / DISABLED" },
897
- payment_brand: { type: "string", description: "evo or unionpay" },
918
+ type: { type: "string", description: "Payment method type" },
919
+ status: { type: "string", description: "PENDING | ACTIVE | FAILED | DISABLED | EXPIRED" },
898
920
  brand: { type: "string|null", description: "Card brand" },
899
- last4: { type: "string|null", description: "Last 4 digits" },
900
- exp_month: { type: "int|null", description: "Expiry month" },
901
- exp_year: { type: "int|null", description: "Expiry year" }
921
+ first6: { type: "string|null", description: "Card first 6 digits" },
922
+ last4: { type: "string|null", description: "Card last 4 digits" },
923
+ created_at: { type: "string", description: "Creation time" }
902
924
  },
903
925
  example: {
904
- command: "agenzo-token-cli payment-methods get --id pm_abc123 --format json",
905
- output_summary: "Returns full payment method details including card info and status."
926
+ command: "agenzo-token-cli payment-methods get pm_01H...",
927
+ output_summary: "Returns the payment method detail including status."
906
928
  }
907
929
  };
908
930
  var pmDisableSchema = {
909
931
  cli: CLI_NAME,
910
- noun: "payment-methods",
932
+ noun: PAYMENT_METHODS_NOUN,
911
933
  verb: "disable",
912
- description: "Disable a payment method (cascades revoke on active tokens)",
934
+ description: "Disable a payment method",
913
935
  flags: {
914
- id: { type: "string", required: true, description: "Payment method id to disable", source: "from_previous_step", from: "payment_methods[].id" }
936
+ pm_id: { type: "string", required: true, description: "Payment method id (positional argument)" },
937
+ "idempotency-key": {
938
+ type: "string",
939
+ required: true,
940
+ description: "Idempotency key forwarded verbatim as the Idempotency-Key header"
941
+ }
915
942
  },
916
943
  response: {
917
- id: { type: "string", description: "Payment method id" },
918
- status: { type: "string", description: "DISABLED" }
944
+ status: { type: "string", description: "Status after disabling (typically DISABLED)" },
945
+ revoked_tokens_count: { type: "int", description: "Number of payment tokens revoked as a side effect" }
946
+ },
947
+ example: {
948
+ command: "agenzo-token-cli payment-methods disable pm_01H... --idempotency-key disable-123",
949
+ output_summary: "Disables the card and revokes any payment tokens issued against it. Only use when the user explicitly requests it."
950
+ },
951
+ error_recovery: {
952
+ PARAM_IDEMPOTENCY_KEY_REQUIRED: "Supply --idempotency-key (1-128 chars [A-Za-z0-9_-]); the CLI never generates one under --yes."
953
+ }
954
+ };
955
+ var ptCreateSchema = {
956
+ cli: CLI_NAME,
957
+ noun: PAYMENT_TOKENS_NOUN,
958
+ verb: "create",
959
+ description: "Create a payment token (VCN / Network Token / X402)",
960
+ flags: {
961
+ type: { type: "string", required: true, description: "Token type", constraints: "vcn | network-token | x402" },
962
+ "payment-method-id": { type: "string", required: "conditional", description: "Payment method ID to use (or resolved via --card / auto-select from ACTIVE cards)" },
963
+ card: { type: "string", required: false, description: "Match payment method by last 4 digits" },
964
+ member: { type: "string", required: false, description: "Member ID" },
965
+ amount: { type: "string", required: "conditional", description: "Amount in USD (vcn) or USDC (x402)" },
966
+ currency: { type: "string", required: false, description: "Currency (vcn only; server default applies if omitted)" },
967
+ "pay-to": { type: "string", required: "conditional", description: "Pay-to address (x402 only)" },
968
+ nonce: { type: "string", required: "conditional", description: "Nonce (x402 only)" },
969
+ network: { type: "string", required: "conditional", description: "Network (x402 only)" },
970
+ deadline: { type: "string", required: "conditional", description: "Deadline as a Unix timestamp in seconds (x402 only)" },
971
+ "external-tx-id": { type: "string", required: false, description: "External transaction ID" },
972
+ "idempotency-key": {
973
+ type: "string",
974
+ required: true,
975
+ description: "Idempotency key forwarded verbatim as the Idempotency-Key header"
976
+ }
977
+ },
978
+ response: {
979
+ id: { type: "string", description: "Payment token id" },
980
+ type: { type: "string", description: "vcn | network_token | x402" },
981
+ status: { type: "string", description: "Token status" }
982
+ },
983
+ example: {
984
+ command: "agenzo-token-cli payment-tokens create --type vcn --card 1234 --amount 25.00 --idempotency-key tok-123",
985
+ output_summary: "Creates a token; response shape varies by type (nested under vcn / network_token / x402)."
986
+ },
987
+ error_recovery: {
988
+ TOKEN_FEATURE_DISABLED: "This token type is not enabled yet. Tell the user and suggest an alternative type if applicable.",
989
+ CLIENT_NO_PAYMENT_METHOD: "The user has no ACTIVE payment method. Guide them to add one first (payment-methods add --mode dropin).",
990
+ CLIENT_CARD_NOT_MATCHED: "The --card last-4 did not match any ACTIVE card. Call payment-methods list to see available cards."
991
+ }
992
+ };
993
+ var ptListSchema = {
994
+ cli: CLI_NAME,
995
+ noun: PAYMENT_TOKENS_NOUN,
996
+ verb: "list",
997
+ description: "List payment tokens",
998
+ flags: {
999
+ type: { type: "string", required: false, description: "Filter by token type" },
1000
+ member: { type: "string", required: false, description: "Filter by member ID" }
1001
+ },
1002
+ response: {
1003
+ payment_tokens: {
1004
+ type: "array",
1005
+ description: "Payment tokens for the developer (optionally filtered)",
1006
+ items: {
1007
+ id: { type: "string", description: "Token id" },
1008
+ type: { type: "string", description: "vcn | network_token | x402" },
1009
+ status: { type: "string", description: "Token status" }
1010
+ }
1011
+ }
919
1012
  },
920
1013
  example: {
921
- command: "agenzo-token-cli payment-methods disable --id pm_abc123 --format json",
922
- output_summary: "Disables the payment method and revokes any active tokens."
1014
+ command: "agenzo-token-cli payment-tokens list --type vcn",
1015
+ output_summary: "Returns payment_tokens[]."
1016
+ }
1017
+ };
1018
+ var ptGetSchema = {
1019
+ cli: CLI_NAME,
1020
+ noun: PAYMENT_TOKENS_NOUN,
1021
+ verb: "get",
1022
+ description: "Get a payment token by ID",
1023
+ flags: {
1024
+ payment_token_id: { type: "string", required: true, description: "Payment token id (positional argument)" },
1025
+ reveal: {
1026
+ type: "bool",
1027
+ required: false,
1028
+ default: false,
1029
+ description: "Reveal full VCN card number and CVC in the output. NEVER pass this or read revealed card data aloud to the user (PCI) unless explicitly required by a payment flow."
1030
+ }
1031
+ },
1032
+ response: {
1033
+ id: { type: "string", description: "Token id" },
1034
+ type: { type: "string", description: "vcn | network_token | x402" },
1035
+ status: { type: "string", description: "Token status" }
1036
+ },
1037
+ example: {
1038
+ command: "agenzo-token-cli payment-tokens get pt_01H...",
1039
+ output_summary: "Returns the token detail. VCN PAN/CVC are masked unless --reveal is set."
1040
+ }
1041
+ };
1042
+ var ptRevokeSchema = {
1043
+ cli: CLI_NAME,
1044
+ noun: PAYMENT_TOKENS_NOUN,
1045
+ verb: "revoke",
1046
+ description: "Revoke a payment token",
1047
+ flags: {
1048
+ payment_token_id: { type: "string", required: true, description: "Payment token id (positional argument)" },
1049
+ "idempotency-key": {
1050
+ type: "string",
1051
+ required: true,
1052
+ description: "Idempotency key forwarded verbatim as the Idempotency-Key header"
1053
+ }
1054
+ },
1055
+ response: {
1056
+ id: { type: "string", description: "Token id" },
1057
+ status: { type: "string", description: "Status after revocation" },
1058
+ revoked_at: { type: "string|absent", description: "Revocation time (immediate revoke)" },
1059
+ expires_at: { type: "string|absent", description: "Expiry time (delayed revoke \u2014 x402 cryptogram auto-expires)" }
1060
+ },
1061
+ example: {
1062
+ command: "agenzo-token-cli payment-tokens revoke pt_01H... --idempotency-key revoke-123",
1063
+ output_summary: "Revokes the token immediately, or schedules a delayed revoke for x402 (status stays ACTIVE until expires_at)."
923
1064
  },
924
1065
  error_recovery: {
925
- NOT_FOUND: "Payment method id does not exist. Check the id with `payment-methods list`.",
926
- ALREADY_DISABLED: "Card is already disabled. No action needed."
1066
+ PARAM_IDEMPOTENCY_KEY_REQUIRED: "Supply --idempotency-key (1-128 chars [A-Za-z0-9_-]); the CLI never generates one under --yes."
927
1067
  }
928
1068
  };
929
1069
 
@@ -935,13 +1075,6 @@ var DROPIN_POLL_TIMEOUT_MS = 30 * 60 * 1e3;
935
1075
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["ACTIVE", "FAILED", "EXPIRED"]);
936
1076
  function registerAddCommand(parent, deps) {
937
1077
  const cmd = parent.command("add").description("Add a payment method (manual 3DS or Drop-in session)").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Payment method type (default: card)", "card").option(
938
- "--payment-brand <brand>",
939
- 'Payment brand: "evo" (default; existing 3DS/Drop-in binding) or "unionpay" (UPI Agent Pay enrollment)',
940
- "evo"
941
- ).option(
942
- "--member <id>",
943
- "End-user member id (required when --payment-brand unionpay; identifies which end-user this card belongs to)"
944
- ).option(
945
1078
  "--mode <mode>",
946
1079
  'Add mode: "manual" (default; CLI collects card details and polls 3DS) or "dropin" (mint a Drop-in session and poll until the user finishes adding the payment method in the browser)',
947
1080
  "manual"
@@ -960,17 +1093,6 @@ function registerAddCommand(parent, deps) {
960
1093
  const opts = cmd.optsWithGlobals();
961
1094
  const format = resolveFormat(opts.format);
962
1095
  const isYes = Boolean(opts.yes);
963
- const paymentBrand = String(opts.paymentBrand ?? "evo").toLowerCase();
964
- if (paymentBrand !== "evo" && paymentBrand !== "unionpay") {
965
- throw new CliError(
966
- "PARAM_INVALID",
967
- `Unknown --payment-brand "${opts.paymentBrand}". Expected "evo" or "unionpay".`
968
- );
969
- }
970
- if (paymentBrand === "unionpay") {
971
- await handleUnionpayPaymentBrand(deps, opts, format);
972
- return;
973
- }
974
1096
  const mode = String(opts.mode ?? "manual").toLowerCase();
975
1097
  if (mode !== "manual" && mode !== "dropin") {
976
1098
  throw new CliError(
@@ -1094,96 +1216,6 @@ async function handleManualMode(deps, opts, format, isYes) {
1094
1216
  }
1095
1217
  }
1096
1218
  }
1097
- async function handleUnionpayPaymentBrand(deps, opts, format) {
1098
- const isYes = Boolean(opts.yes);
1099
- let member;
1100
- if (opts.member) {
1101
- member = String(opts.member);
1102
- } else if (isYes) {
1103
- throw new CliError(
1104
- "PARAM_INVALID",
1105
- "Missing required --member <id> for --payment-brand unionpay (required in --yes mode)"
1106
- );
1107
- } else {
1108
- member = await PromptEngine.resolveInput(void 0, {
1109
- message: "Member ID (end-user identity this card belongs to):",
1110
- validate: (v) => v.trim().length > 0 || "Member ID is required for --payment-brand unionpay"
1111
- });
1112
- }
1113
- const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
1114
- message: "API Key:",
1115
- type: "password"
1116
- });
1117
- const email = await PromptEngine.resolveInput(opts.email, {
1118
- message: "Email:"
1119
- });
1120
- const result = await deps.apiClient.post(
1121
- "/payment-methods/create",
1122
- { type: "api-key", key: apiKey },
1123
- { type: "card", payment_brand: "unionpay", member_id: member, email }
1124
- );
1125
- if (!result.success) {
1126
- throw CliError.fromApi(result, { auth: "api-key" });
1127
- }
1128
- const pm = result.data;
1129
- notify(format, "success", "Card binding initiated");
1130
- const createdResult = {
1131
- data: pm,
1132
- text: () => Formatter.keyValue([
1133
- ["ID", pm.id],
1134
- ["Status", pm.status],
1135
- ["Enroll URL", pm.enroll_url ?? "-"],
1136
- ["Correlation ID", pm.correlation_id ?? "-"]
1137
- ])
1138
- };
1139
- const configManager = new ConfigManager();
1140
- await renderWithContext(createdResult, { format }, configManager);
1141
- notify(
1142
- format,
1143
- "info",
1144
- "Open the Enroll URL in a browser to complete card binding. Waiting for result..."
1145
- );
1146
- const UNIONPAY_POLL_INTERVAL_MS = 5e3;
1147
- const UNIONPAY_POLL_TIMEOUT_MS = 6e4;
1148
- const startTime = Date.now();
1149
- const spinner = format !== "json" ? createSpinner("Waiting for card binding result...") : null;
1150
- while (Date.now() - startTime < UNIONPAY_POLL_TIMEOUT_MS) {
1151
- await sleep(UNIONPAY_POLL_INTERVAL_MS);
1152
- const pollResult = await deps.apiClient.get(
1153
- `/payment-methods/${pm.id}`,
1154
- { type: "api-key", key: apiKey }
1155
- );
1156
- if (pollResult.success) {
1157
- const status = pollResult.data.status;
1158
- if (status === "ACTIVE") {
1159
- spinner?.stop();
1160
- notify(format, "success", "Payment method activated");
1161
- const activatedPm = pollResult.data;
1162
- const activatedResult = {
1163
- data: activatedPm,
1164
- text: () => {
1165
- const lines = [
1166
- ["ID", activatedPm.id],
1167
- ["Type", activatedPm.type ?? "card"],
1168
- ["Status", activatedPm.status]
1169
- ];
1170
- if (activatedPm.brand) lines.push(["Brand", activatedPm.brand]);
1171
- if (activatedPm.first6) lines.push(["First 6", activatedPm.first6]);
1172
- if (activatedPm.last4) lines.push(["Last 4", activatedPm.last4]);
1173
- return Formatter.keyValue(lines);
1174
- }
1175
- };
1176
- await renderWithContext(activatedResult, { format }, configManager);
1177
- return;
1178
- }
1179
- if (status === "FAILED") {
1180
- spinner?.stop("error", "Card binding failed.");
1181
- return;
1182
- }
1183
- }
1184
- }
1185
- spinner?.stop("info", "Timed out waiting for card binding result. Check status later with: payment-methods get " + pm.id);
1186
- }
1187
1219
  async function handleDropinMode(deps, opts, format) {
1188
1220
  const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
1189
1221
  message: "API Key:",
@@ -1528,9 +1560,8 @@ function registerDropinStatusCommand(parent, deps) {
1528
1560
 
1529
1561
  // src/payment-tokens/create.ts
1530
1562
  import { confirm, select as select2 } from "@inquirer/prompts";
1531
- var DEFAULT_NT_FEE_CENTS = 500;
1563
+ var DEFAULT_NT_FEE_CENTS = 50;
1532
1564
  var USDC_UNIT = 1e6;
1533
- var DECIMAL_AMOUNT_RE = /^\d+(\.\d+)?$/;
1534
1565
  function mapTokenType(cliType) {
1535
1566
  if (cliType === "network-token") return "network_token";
1536
1567
  return cliType;
@@ -1608,23 +1639,14 @@ function formatPaymentToken(data) {
1608
1639
  lines.push(["Currency", String(vcn.currency || "USD")]);
1609
1640
  lines.push(["Status", String(vcn.status || data.status || "")]);
1610
1641
  } else if (type === "network_token") {
1611
- if (data.status === "PENDING" && data.checkout_url) {
1612
- lines.push(["Payment Token ID", String(data.id || "")]);
1613
- lines.push(["Type", "Network Token"]);
1614
- lines.push(["Status", "PENDING"]);
1615
- lines.push(["Checkout URL", String(data.checkout_url || "")]);
1616
- lines.push(["Correlation ID", String(data.correlation_id || "")]);
1617
- } else {
1618
- const nt = data.network_token ?? data;
1619
- lines.push(["Payment Token ID", String(data.id || nt.id || "")]);
1620
- lines.push(["Type", "Network Token"]);
1621
- lines.push(["Brand", String(nt.payment_brand || nt.brand || "")]);
1622
- const eci = nt.eci || "";
1623
- if (eci) lines.push(["ECI", String(eci)]);
1624
- lines.push(["Cryptogram", String(nt.token_cryptogram || nt.cryptogram || "")]);
1625
- lines.push(["Expiry", String(nt.expiry_date || nt.expiry || "")]);
1626
- lines.push(["Token Number", String(nt.value || "")]);
1627
- }
1642
+ const nt = data.network_token ?? data;
1643
+ lines.push(["Payment Token ID", String(data.id || nt.id || "")]);
1644
+ lines.push(["Type", "Network Token"]);
1645
+ lines.push(["Brand", String(nt.payment_brand || nt.brand || "")]);
1646
+ lines.push(["ECI", String(nt.eci || "")]);
1647
+ lines.push(["Cryptogram", String(nt.token_cryptogram || nt.cryptogram || "")]);
1648
+ lines.push(["Expiry", String(nt.expiry_date || nt.expiry || "")]);
1649
+ lines.push(["Value", String(nt.value || "")]);
1628
1650
  } else if (type === "x402") {
1629
1651
  const x402 = data.x402 ?? data;
1630
1652
  lines.push(["Payment Token ID", String(data.id || x402.id || "")]);
@@ -1644,16 +1666,12 @@ function formatCentsToUsd(cents) {
1644
1666
  const remainder = cents % 100;
1645
1667
  return `${dollars}.${String(remainder).padStart(2, "0")}`;
1646
1668
  }
1647
- function isValidUnionpayAmount(amountStr) {
1648
- const trimmed = amountStr.trim();
1649
- if (!DECIMAL_AMOUNT_RE.test(trimmed)) return false;
1650
- return parseFloat(trimmed) > 0;
1651
- }
1652
1669
  function registerCreateCommand(parent, deps) {
1653
- const cmd = parent.command("create").description("Create a payment token (VCN / Network Token / X402)").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Token type: vcn | network-token | x402").option("--payment-method-id <id>", "Payment method ID to use").option("--card <last4>", "Match payment method by last 4 digits").option("--member <member_id>", "Member ID").option("--amount <amount>", "Amount in USD (VCN / X402)").option("--currency <currency>", "Currency (default: USD)").option("--pay-to <address>", "Pay-to address (X402)").option("--nonce <nonce>", "Nonce (X402)").option("--network <network>", "Network (X402)").option("--deadline <deadline>", "Deadline (X402)").option("--external-tx-id <id>", "External transaction ID").option("--recipient-first-name <name>", "UnionPay network token: recipient first name (order delivery details)").option("--recipient-last-name <name>", "UnionPay network token: recipient last name (order delivery details)").option("--recipient-email <email>", "UnionPay network token: recipient email (recipient-email or recipient-phone required)").option("--recipient-phone <phone>", "UnionPay network token: recipient phone (recipient-email or recipient-phone required)").option("--unionpay-amount <amount>", 'UnionPay network token: intent amount as a decimal string, e.g. "174.58"').option(
1670
+ const cmd = parent.command("create").description("Create a payment token (VCN / Network Token / X402)").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Token type: vcn | network-token | x402").option("--payment-method-id <id>", "Payment method ID to use").option("--card <last4>", "Match payment method by last 4 digits").option("--member <member_id>", "Member ID").option("--amount <amount>", "Amount in USD (VCN / X402)").option("--currency <currency>", "Currency (default: USD)").option("--pay-to <address>", "Pay-to address (X402)").option("--nonce <nonce>", "Nonce (X402)").option("--network <network>", "Network (X402)").option("--deadline <deadline>", "Deadline (X402)").option("--external-tx-id <id>", "External transaction ID").option(
1654
1671
  "--idempotency-key <key>",
1655
1672
  "Idempotency key forwarded verbatim as the Idempotency-Key header"
1656
1673
  );
1674
+ attachSchemaHelp(cmd, ptCreateSchema);
1657
1675
  cmd.action(async () => {
1658
1676
  const opts = cmd.optsWithGlobals();
1659
1677
  const format = resolveFormat(opts.format);
@@ -1677,24 +1695,6 @@ function registerCreateCommand(parent, deps) {
1677
1695
  card: opts.card,
1678
1696
  yes: isYes
1679
1697
  });
1680
- let selectedPmPaymentBrand;
1681
- if (serverType === "network_token") {
1682
- const pmResult = await deps.apiClient.get(
1683
- `/payment-methods/${paymentMethodId}`,
1684
- { type: "api-key", key: apiKey }
1685
- );
1686
- if (!pmResult.success) {
1687
- throw CliError.fromApi(pmResult, { auth: "api-key" });
1688
- }
1689
- selectedPmPaymentBrand = pmResult.data.payment_brand;
1690
- if (selectedPmPaymentBrand === "unionpay" && opts.card && !opts.paymentMethodId) {
1691
- throw new CliError(
1692
- "PARAM_INVALID",
1693
- "UnionPay cards must be selected via --payment-method-id; --card (last4 matching) is not supported for unionpay payment brand."
1694
- );
1695
- }
1696
- }
1697
- const isUnionpayNetworkToken = serverType === "network_token" && selectedPmPaymentBrand === "unionpay";
1698
1698
  let member = opts.member;
1699
1699
  if (!member && !isYes) {
1700
1700
  const memberInput = await PromptEngine.resolveInput(void 0, {
@@ -1743,66 +1743,10 @@ function registerCreateCommand(parent, deps) {
1743
1743
  typeBody.currency = opts.currency;
1744
1744
  }
1745
1745
  } else if (serverType === "network_token") {
1746
- if (isUnionpayNetworkToken) {
1747
- const unionpayAmountStr = await PromptEngine.resolveInput(
1748
- opts.unionpayAmount,
1749
- {
1750
- message: "UnionPay intent amount (USD, e.g. 174.58):",
1751
- validate: (v) => isValidUnionpayAmount(v) || 'Amount must be a positive decimal, e.g. "174.58"'
1752
- }
1753
- );
1754
- if (!isValidUnionpayAmount(unionpayAmountStr)) {
1755
- throw new CliError(
1756
- "PARAM_INVALID",
1757
- `Invalid --unionpay-amount "${unionpayAmountStr}". Expected a positive decimal string, e.g. "174.58".`
1758
- );
1759
- }
1760
- typeBody.unionpay_amount = unionpayAmountStr.trim();
1761
- typeBody.recipient_first_name = await PromptEngine.resolveInput(
1762
- opts.recipientFirstName,
1763
- {
1764
- message: "Recipient first name:",
1765
- validate: (v) => v.trim().length > 0 || "Recipient first name is required"
1766
- }
1767
- );
1768
- typeBody.recipient_last_name = await PromptEngine.resolveInput(
1769
- opts.recipientLastName,
1770
- {
1771
- message: "Recipient last name:",
1772
- validate: (v) => v.trim().length > 0 || "Recipient last name is required"
1773
- }
1774
- );
1775
- let recipientEmail = opts.recipientEmail;
1776
- let recipientPhone = opts.recipientPhone;
1777
- if (!recipientEmail && !recipientPhone) {
1778
- if (isYes) {
1779
- throw new CliError(
1780
- "PARAM_INVALID",
1781
- "--recipient-email or --recipient-phone is required for unionpay network tokens."
1782
- );
1783
- }
1784
- const emailInput = await PromptEngine.resolveInput(void 0, {
1785
- message: "Recipient email (optional, press Enter to skip and enter phone instead):",
1786
- validate: () => true
1787
- });
1788
- if (emailInput.trim()) {
1789
- recipientEmail = emailInput.trim();
1790
- } else {
1791
- const phoneInput = await PromptEngine.resolveInput(void 0, {
1792
- message: "Recipient phone (required, since no email was given):",
1793
- validate: (v) => v.trim().length > 0 || "Recipient email or phone is required"
1794
- });
1795
- recipientPhone = phoneInput.trim();
1796
- }
1797
- }
1798
- if (recipientEmail) typeBody.recipient_email = recipientEmail;
1799
- if (recipientPhone) typeBody.recipient_phone = recipientPhone;
1800
- } else {
1801
- feeCents = await fetchNetworkTokenFee(deps.apiClient, apiKey);
1802
- freezeAmountCents = feeCents;
1803
- feeDisplay = `$${formatCentsToUsd(feeCents)}`;
1804
- freezeDisplay = feeDisplay;
1805
- }
1746
+ feeCents = await fetchNetworkTokenFee(deps.apiClient, apiKey);
1747
+ freezeAmountCents = feeCents;
1748
+ feeDisplay = `$${formatCentsToUsd(feeCents)}`;
1749
+ freezeDisplay = feeDisplay;
1806
1750
  } else if (serverType === "x402") {
1807
1751
  const amountStr = await PromptEngine.resolveInput(opts.amount, {
1808
1752
  message: "Amount (USDC):",
@@ -1844,15 +1788,10 @@ function registerCreateCommand(parent, deps) {
1844
1788
  typeBody.deadline = deadlineNum;
1845
1789
  }
1846
1790
  if (!isYes) {
1847
- if (freezeDisplay !== void 0 && feeDisplay !== void 0) {
1848
- const warningLines = [`Freeze: ${freezeDisplay}`, `Fee: ${feeDisplay}`];
1849
- notify(format, "warning", warningLines.join(" | "));
1850
- } else if (isUnionpayNetworkToken) {
1851
- notify(format, "info", "UnionPay: no fee (clearing network not yet enabled)");
1852
- }
1853
- const confirmMessage = isUnionpayNetworkToken ? "Proceed with UnionPay network token request?" : "Proceed with token creation?";
1791
+ const warningLines = [`Freeze: ${freezeDisplay}`, `Fee: ${feeDisplay}`];
1792
+ notify(format, "warning", warningLines.join(" | "));
1854
1793
  const confirmed = await confirm({
1855
- message: confirmMessage,
1794
+ message: "Proceed with token creation?",
1856
1795
  default: true
1857
1796
  });
1858
1797
  if (!confirmed) {
@@ -1909,42 +1848,6 @@ function registerCreateCommand(parent, deps) {
1909
1848
  }
1910
1849
  };
1911
1850
  await renderWithContext(commandResult, { format }, configManager);
1912
- if (isUnionpayNetworkToken) {
1913
- notify(
1914
- format,
1915
- "info",
1916
- "Open the Checkout URL to complete the UnionPay payment verification. Waiting for result..."
1917
- );
1918
- const UNIONPAY_TOKEN_POLL_INTERVAL_MS = 5e3;
1919
- const UNIONPAY_TOKEN_POLL_TIMEOUT_MS = 6e4;
1920
- const pollStart = Date.now();
1921
- const tokenId = tokenData.id;
1922
- while (Date.now() - pollStart < UNIONPAY_TOKEN_POLL_TIMEOUT_MS) {
1923
- await new Promise((resolve) => setTimeout(resolve, UNIONPAY_TOKEN_POLL_INTERVAL_MS));
1924
- const pollResult = await deps.apiClient.get(
1925
- `/payment-tokens/${tokenId}`,
1926
- { type: "api-key", key: apiKey }
1927
- );
1928
- if (pollResult.success) {
1929
- const status = pollResult.data.status;
1930
- if (status === "ACTIVE") {
1931
- notify(format, "success", "UnionPay network token activated!");
1932
- if (!pollResult.data.type) pollResult.data.type = serverType;
1933
- const activatedResult = {
1934
- data: pollResult.data,
1935
- text: () => formatPaymentToken(pollResult.data)
1936
- };
1937
- await renderWithContext(activatedResult, { format }, configManager);
1938
- return;
1939
- }
1940
- if (status === "FAILED") {
1941
- notify(format, "error", "UnionPay network token failed.");
1942
- return;
1943
- }
1944
- }
1945
- }
1946
- notify(format, "info", `Timed out waiting for token activation. Check status later with: payment-tokens get ${tokenId}`);
1947
- }
1948
1851
  });
1949
1852
  }
1950
1853
  async function checkVcnFeatureEnabled(apiClient, apiKey) {
@@ -2000,6 +1903,7 @@ function getSummary(token) {
2000
1903
  }
2001
1904
  function registerListCommand2(parent, deps) {
2002
1905
  const cmd = parent.command("list").description("List payment tokens").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Filter by token type").option("--member <member_id>", "Filter by member ID");
1906
+ attachSchemaHelp(cmd, ptListSchema);
2003
1907
  cmd.action(async () => {
2004
1908
  const opts = cmd.optsWithGlobals();
2005
1909
  const format = resolveFormat(opts.format);
@@ -2136,6 +2040,7 @@ function formatCentsPlain(cents) {
2136
2040
  }
2137
2041
  function registerGetCommand2(parent, deps) {
2138
2042
  const cmd = parent.command("get <payment_token_id>").description("Get a payment token by ID").option("--api-key <key>", "API key for authentication").option("--reveal", "Reveal full VCN card number and CVC in the output");
2043
+ attachSchemaHelp(cmd, ptGetSchema);
2139
2044
  cmd.action(async (paymentTokenId) => {
2140
2045
  const opts = cmd.optsWithGlobals();
2141
2046
  const format = resolveFormat(opts.format);
@@ -2167,6 +2072,7 @@ function registerRevokeCommand(parent, deps) {
2167
2072
  "--idempotency-key <key>",
2168
2073
  "Idempotency key forwarded verbatim as the Idempotency-Key header"
2169
2074
  );
2075
+ attachSchemaHelp(cmd, ptRevokeSchema);
2170
2076
  cmd.action(async (paymentTokenId) => {
2171
2077
  const opts = cmd.optsWithGlobals();
2172
2078
  const format = resolveFormat(opts.format);
@@ -2277,6 +2183,9 @@ function reportError(error) {
2277
2183
  console.error(
2278
2184
  Formatter.status("error", `[${envelope.error.code_num}] ${envelope.error.message}`)
2279
2185
  );
2186
+ if (envelope.error.upstream) {
2187
+ console.error(` \u21B3 [${envelope.error.upstream.code}] ${envelope.error.upstream.message}`);
2188
+ }
2280
2189
  if (!(error instanceof CliError) && process.argv.includes("--verbose")) {
2281
2190
  console.error(error);
2282
2191
  }