@garuhq/cli 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,73 @@
3
3
  All notable changes to `@garuhq/cli` are documented in this file. Format:
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
5
5
 
6
+ ## [0.6.0] — 2026-05-25
7
+
8
+ ### Added
9
+
10
+ - `garu scheduled-charges` — a new command group for future-dated
11
+ charges, wrapping the full `@garuhq/node` `scheduledCharges` surface:
12
+ `create`, `list`, `get`, `postpone`, `pause`, `resume`, `mark-paid`,
13
+ `cancel-recurrence`, `cancel-at-period-end`, `change-payment-method`,
14
+ `clear-payment-method`, and `attempts`.
15
+ - `garu scheduled-charges charge-now <id>` — dispatch a scheduled
16
+ charge immediately (the same charge + notification the daily cron
17
+ sends on the due date), instead of waiting for `dueDate`.
18
+ - **Idempotent**: a cycle whose d-day was already dispatched reports
19
+ `already_sent` and is **not** re-charged.
20
+ - The returned pt-BR `message` is always printed; in `--json` mode
21
+ the full `{ outcome, cycleNumber, reason?, message }` is emitted on
22
+ stdout.
23
+ - **Exits non-zero** on outcome `failed`/`not_sent` and on a 4xx
24
+ rejection (`400` for a non-billable status or a recurring series
25
+ with no open cycle; `404` if the charge isn't yours), so it
26
+ composes in shell pipelines. `dispatched` and `already_sent`
27
+ exit `0`.
28
+ - `garu scheduled-charges create --max-recovery-days <n>` — set how
29
+ many days past `dueDate` the daily recovery sweep keeps auto-billing
30
+ a missed charge (integer 1–365). Omit for the system default (14).
31
+
32
+ ### Changed
33
+
34
+ - `@garuhq/node` SDK bumped to 0.13.0 for the new
35
+ `scheduledCharges.chargeNow()` method and the `maxRecoveryDays`
36
+ field on create params / `ScheduledChargeRecord`.
37
+
38
+ ## [0.5.0] — 2026-05-19
39
+
40
+ ### Added
41
+
42
+ - `garu webhooks events resend <id>` — audit-trail-preserving replay
43
+ of a webhook event. Unlike `retry`, this does **not** mutate the
44
+ original row: the gateway inserts a fresh event with its own
45
+ numeric id that points back at the source via `manualResendOf`,
46
+ then dispatches that clone. The original failure record (response
47
+ status, response body, attempts, timestamps) stays intact.
48
+ - In pretty mode the CLI prints `✓ Resent event <src> → new event
49
+ <clone>` to stderr so the new id is impossible to miss; the
50
+ cloned event itself is rendered on stdout with a new `resendOf:`
51
+ line.
52
+ - In JSON mode the cloned event is the full stdout payload —
53
+ `.id` is the new event, `.manualResendOf` is the source.
54
+ - Recipient handlers will see this as a distinct delivery: the
55
+ gateway POSTs the clone with `Idempotency-Key:
56
+ resend_<originalId>`.
57
+
58
+ ### Deprecated
59
+
60
+ - `garu webhooks events retry <id>` — kept for backwards
61
+ compatibility but now marked `[deprecated: prefer resend]` in
62
+ `--help`. `retry` resets the original row in place, which means
63
+ once the replay succeeds the historical record of the prior
64
+ failure is gone. For incident response, support workflows, and
65
+ any backfill where the audit trail matters, use `resend` instead.
66
+
67
+ ### Changed
68
+
69
+ - `@garuhq/node` SDK bumped to 0.12.0 for the new
70
+ `webhookEvents.resend()` method and the `manualResendOf` field on
71
+ `WebhookEvent`.
72
+
6
73
  ## [0.4.2] — 2026-05-19
7
74
 
8
75
  ### Fixed
@@ -15,7 +82,7 @@ All notable changes to `@garuhq/cli` are documented in this file. Format:
15
82
  - **`garu --version`** correctly reports `0.4.2`. The `0.4.1` release
16
83
  shipped with `src/version.ts:CLI_VERSION` still pinned to `'0.4.0'`
17
84
  because that file was missed during the bump. A new `scripts/
18
- check-version-sync.mjs` runs as part of `prepublishOnly` and fails
85
+ check-version-sync.mjs` runs as part of `prepublishOnly` and fails
19
86
  the build if `package.json:version` and `src/version.ts:CLI_VERSION`
20
87
  drift apart.
21
88
 
@@ -36,7 +103,7 @@ All notable changes to `@garuhq/cli` are documented in this file. Format:
36
103
  - Bump `@garuhq/node` to `0.11.1` to pick up the empty-body POST fix.
37
104
  `garu webhooks events retry <id>` and `garu scheduled-charges resume <id>`
38
105
  were failing against production with `Body cannot be empty when content-type
39
- is set to 'application/json'`. The SDK now sends an explicit `{}` body on
106
+ is set to 'application/json'`. The SDK now sends an explicit `{}` body on
40
107
  every otherwise-empty mutation.
41
108
 
42
109
  ## [0.4.0] — 2026-05-19
@@ -46,15 +113,15 @@ All notable changes to `@garuhq/cli` are documented in this file. Format:
46
113
  - `garu webhooks events` command tree — inspect and replay webhook
47
114
  deliveries from the CLI. The dashboard "Reenviar" button is now
48
115
  available from the API key auth path as well, which means support
49
- + on-call workflows can resend events from a terminal instead of
50
- having to log into the dashboard.
51
- - `garu webhooks events list [--status <s>] [--event-type <t>] [--endpoint-id <n>] [--page <n>] [--limit <n>]`
116
+ - on-call workflows can resend events from a terminal instead of
117
+ having to log into the dashboard.
118
+ * `garu webhooks events list [--status <s>] [--event-type <t>] [--endpoint-id <n>] [--page <n>] [--limit <n>]`
52
119
  — paginated listing with status badges (green `success`, yellow
53
120
  `pending`, red `failed`) in TTY mode.
54
- - `garu webhooks events get <id>` — fetch one webhook event with
121
+ * `garu webhooks events get <id>` — fetch one webhook event with
55
122
  the full endpoint snapshot, response status, and (truncated)
56
123
  response body.
57
- - `garu webhooks events retry <id>` — re-deliver a webhook event
124
+ * `garu webhooks events retry <id>` — re-deliver a webhook event
58
125
  (resets to `pending` and triggers an immediate attempt). Works on
59
126
  any status; use this when a customer reports a missed event.
60
127
 
package/README.md CHANGED
@@ -59,11 +59,11 @@ garu doctor
59
59
 
60
60
  The CLI resolves your API key using the following priority chain:
61
61
 
62
- | Priority | Source | How to set |
63
- | ----------- | ------------------------ | --------------------------------------- |
64
- | 1 (highest) | `--api-key` flag | `garu --api-key sk_live_... charges list` |
65
- | 2 | `GARU_API_KEY` env var | `export GARU_API_KEY=sk_live_...` |
66
- | 3 (lowest) | Config file | `garu login` |
62
+ | Priority | Source | How to set |
63
+ | ----------- | ---------------------- | ----------------------------------------- |
64
+ | 1 (highest) | `--api-key` flag | `garu --api-key sk_live_... charges list` |
65
+ | 2 | `GARU_API_KEY` env var | `export GARU_API_KEY=sk_live_...` |
66
+ | 3 (lowest) | Config file | `garu login` |
67
67
 
68
68
  If no key is found from any source, the CLI errors with code `auth_error`.
69
69
 
@@ -112,9 +112,9 @@ garu login
112
112
  garu login --api-key sk_live_... --profile production
113
113
  ```
114
114
 
115
- | Flag | Description |
116
- | ---------------------- | ---------------------------------------- |
117
- | `--api-key <key>` | Pre-supply the key instead of prompting |
115
+ | Flag | Description |
116
+ | ---------------------- | ------------------------------------------------ |
117
+ | `--api-key <key>` | Pre-supply the key instead of prompting |
118
118
  | `-p, --profile <name>` | Profile name to store under (default: `default`) |
119
119
 
120
120
  On success, credentials are saved to `~/.config/garu/credentials.json` with `0600` permissions (owner read/write only).
@@ -130,9 +130,9 @@ garu logout
130
130
  garu logout --profile test
131
131
  ```
132
132
 
133
- | Flag | Description |
134
- | ---------------------- | ---------------------------------------------------- |
135
- | `-p, --profile <name>` | Only remove this profile instead of the whole file |
133
+ | Flag | Description |
134
+ | ---------------------- | -------------------------------------------------- |
135
+ | `-p, --profile <name>` | Only remove this profile instead of the whole file |
136
136
 
137
137
  ---
138
138
 
@@ -156,13 +156,13 @@ garu charges list --status paid --limit 50
156
156
  garu charges list --search "Maria" --payment-method pix
157
157
  ```
158
158
 
159
- | Flag | Description |
160
- | --------------------------- | ---------------------------------------------- |
161
- | `--page <n>` | Page number (1-based) |
162
- | `--limit <n>` | Items per page (1-100) |
163
- | `--status <status>` | Filter by status (e.g. `paid`, `pending`) |
164
- | `--search <query>` | Search by customer name, email, or document |
165
- | `--payment-method <method>` | Filter: `pix`, `creditcard`, `boleto` |
159
+ | Flag | Description |
160
+ | --------------------------- | ------------------------------------------- |
161
+ | `--page <n>` | Page number (1-based) |
162
+ | `--limit <n>` | Items per page (1-100) |
163
+ | `--status <status>` | Filter by status (e.g. `paid`, `pending`) |
164
+ | `--search <query>` | Search by customer name, email, or document |
165
+ | `--payment-method <method>` | Filter: `pix`, `creditcard`, `boleto` |
166
166
 
167
167
  ---
168
168
 
@@ -191,21 +191,21 @@ garu charges create --type credit_card --product-id prod-uuid \
191
191
  --installments 3
192
192
  ```
193
193
 
194
- | Flag | Required | Description |
195
- | -------------------------------- | -------- | ------------------------------------------ |
196
- | `--type <type>` | Yes | Payment method: `pix`, `credit_card`, `boleto` |
197
- | `--product-id <uuid>` | Yes | Product UUID |
198
- | `--customer-name <name>` | Yes | Customer full name |
199
- | `--customer-email <email>` | Yes | Customer email |
200
- | `--customer-document <doc>` | Yes | CPF (11 digits) or CNPJ (14 digits) |
201
- | `--customer-phone <phone>` | Yes | Phone with area code, digits only |
202
- | `--card-number <number>` | credit_card | Credit card number |
203
- | `--card-cvv <cvv>` | credit_card | Credit card CVV |
204
- | `--card-expiration <yyyy-mm>` | credit_card | Expiration date |
205
- | `--card-holder <name>` | credit_card | Cardholder name |
206
- | `--installments <n>` | No | Number of installments, 1-12 (default: 1) |
207
- | `--additional-info <text>` | No | Free-form metadata |
208
- | `--idempotency-key <key>` | No | Idempotency key (auto-generated if omitted) |
194
+ | Flag | Required | Description |
195
+ | ----------------------------- | ----------- | ---------------------------------------------- |
196
+ | `--type <type>` | Yes | Payment method: `pix`, `credit_card`, `boleto` |
197
+ | `--product-id <uuid>` | Yes | Product UUID |
198
+ | `--customer-name <name>` | Yes | Customer full name |
199
+ | `--customer-email <email>` | Yes | Customer email |
200
+ | `--customer-document <doc>` | Yes | CPF (11 digits) or CNPJ (14 digits) |
201
+ | `--customer-phone <phone>` | Yes | Phone with area code, digits only |
202
+ | `--card-number <number>` | credit_card | Credit card number |
203
+ | `--card-cvv <cvv>` | credit_card | Credit card CVV |
204
+ | `--card-expiration <yyyy-mm>` | credit_card | Expiration date |
205
+ | `--card-holder <name>` | credit_card | Cardholder name |
206
+ | `--installments <n>` | No | Number of installments, 1-12 (default: 1) |
207
+ | `--additional-info <text>` | No | Free-form metadata |
208
+ | `--idempotency-key <key>` | No | Idempotency key (auto-generated if omitted) |
209
209
 
210
210
  ---
211
211
 
@@ -232,11 +232,99 @@ garu charges refund 4472
232
232
  garu charges refund 4472 --amount 1000 --reason "customer_request"
233
233
  ```
234
234
 
235
- | Flag | Description |
236
- | ------------------------- | --------------------------------------------- |
235
+ | Flag | Description |
236
+ | ------------------------- | ------------------------------------------------- |
237
237
  | `--amount <centavos>` | Partial refund amount in centavos (omit for full) |
238
- | `--reason <text>` | Optional refund reason |
239
- | `--idempotency-key <key>` | Idempotency key (auto-generated if omitted) |
238
+ | `--reason <text>` | Optional refund reason |
239
+ | `--idempotency-key <key>` | Idempotency key (auto-generated if omitted) |
240
+
241
+ ---
242
+
243
+ ### `garu scheduled-charges create`
244
+
245
+ Schedule a future-dated charge — one-time or recurring (PIX, Boleto, or Card).
246
+
247
+ ```bash
248
+ # One-time PIX/Boleto charge
249
+ garu scheduled-charges create \
250
+ --customer-id 42 --amount 297.50 --type one_time \
251
+ --due-date 2026-06-15 --methods pix,boleto \
252
+ --description "Mensalidade Junho"
253
+
254
+ # Recurring card subscription, custom recovery window
255
+ garu scheduled-charges create \
256
+ --customer-id 42 --amount 99.00 --type recurring \
257
+ --due-date 2026-06-15 --methods card --product-id 5 \
258
+ --recurrence-interval monthly --recurrence-ends-after 12 \
259
+ --max-recovery-days 30
260
+ ```
261
+
262
+ | Flag | Description |
263
+ | --------------------------------- | ------------------------------------------------------------------------- |
264
+ | `--customer-id <n>` | Customer id (required) |
265
+ | `--amount <brl>` | Decimal BRL amount, e.g. `297.50` (required) |
266
+ | `--type <type>` | `one_time` or `recurring` (required) |
267
+ | `--due-date <yyyy-mm-dd>` | First due date in São Paulo time (required) |
268
+ | `--methods <list>` | Comma-separated: `pix,boleto,card` (required; `card` is recurring-only) |
269
+ | `--product-id <n>` | Product id (required when `methods` includes `card`) |
270
+ | `--description <text>` | Charge description |
271
+ | `--recurrence-interval <i>` | `weekly`/`biweekly`/`monthly`/`bimonthly`/`quarterly`/`biannual`/`yearly` |
272
+ | `--recurrence-interval-count <n>` | Multiplier for the interval |
273
+ | `--recurrence-ends-after <n>` | Stop after N successful cycles |
274
+ | `--recurrence-ends-on <date>` | Stop after this calendar date |
275
+ | `--trial-days <n>` | Free-trial days (1–365, recurring-only) |
276
+ | `--external-reference <ref>` | Your own reconciliation reference |
277
+ | `--metadata <json>` | JSON object of custom metadata |
278
+ | `--max-recovery-days <n>` | Days past due the recovery sweep keeps auto-billing (1–365; default 14) |
279
+ | `--idempotency-key <key>` | Idempotency key (auto-generated if omitted) |
280
+
281
+ ---
282
+
283
+ ### `garu scheduled-charges charge-now`
284
+
285
+ Dispatch a scheduled charge **now** — the same charge + notification the daily
286
+ cron would send on the due date — instead of waiting for `dueDate`.
287
+
288
+ ```bash
289
+ garu scheduled-charges charge-now sch_abc123
290
+
291
+ # Compose in scripts: non-zero exit on a negative outcome
292
+ if garu scheduled-charges charge-now sch_abc123 --json | jq -e '.outcome=="dispatched"'; then
293
+ echo "sent"
294
+ fi
295
+ ```
296
+
297
+ **Idempotent.** If this cycle's d-day was already dispatched, the command
298
+ reports `already_sent` and does **not** re-charge. The returned `message`
299
+ (pt-BR, ready to show) is always printed; in `--json` mode the full result —
300
+ `{ outcome, cycleNumber, reason?, message }` — is written to stdout.
301
+
302
+ The process **exits non-zero** when:
303
+
304
+ - `outcome` is `failed` (card charge failed — see `reason`, e.g. `card_expired`) or
305
+ `not_sent` (couldn't dispatch — e.g. `no_email`, `no_saved_payment_method`), and
306
+ - the gateway rejects the request with a 4xx — `400` if the charge isn't in a
307
+ billable status (`scheduled`/`due_today`) or a recurring series has no open
308
+ cycle, `404` if the charge isn't yours.
309
+
310
+ `dispatched` and `already_sent` exit `0`.
311
+
312
+ ---
313
+
314
+ ### `garu scheduled-charges` — other subcommands
315
+
316
+ | Command | Description |
317
+ | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
318
+ | `list [--page --limit --customer-id --status --type ...]` | List scheduled charges (`--status` repeatable; `--due-from/--due-to`, `--search`) |
319
+ | `get <id>` | Fetch a charge with its event timeline and linked transactions |
320
+ | `postpone <id> --new-due-date <date> [--reason]` | Move to a new due date |
321
+ | `pause <id> [--reason]` / `resume <id>` | Pause (no reminders fire) / resume |
322
+ | `mark-paid <id> --payment-date <date> [--external-reference --cycle-number]` | Mark paid out-of-band (`--cycle-number` required for recurring) |
323
+ | `cancel-recurrence <id> [--reason]` | Stop future cycles of a recurring series |
324
+ | `cancel-at-period-end <id> [--disable]` | Toggle Stripe-style soft cancel (omit `--disable` to enable) |
325
+ | `change-payment-method <id> --payment-method-id <n>` | Swap the saved card on a recurring series |
326
+ | `clear-payment-method <id>` | Clear the saved card (future cycles fall back to email-with-link) |
327
+ | `attempts <id> [--page --limit --cycle-number]` | Per-attempt billing log |
240
328
 
241
329
  ---
242
330
 
@@ -274,10 +362,10 @@ These flags work on every command:
274
362
  garu [global options] <command> [command options]
275
363
  ```
276
364
 
277
- | Flag | Description |
278
- | ---------------------- | ------------------------------------------------------------ |
365
+ | Flag | Description |
366
+ | ---------------------- | ------------------------------------------------------------- |
279
367
  | `--api-key <key>` | Override API key for this invocation (takes highest priority) |
280
- | `-p, --profile <name>` | Credentials profile to use |
368
+ | `-p, --profile <name>` | Credentials profile to use |
281
369
  | `--json` | Force JSON output even in interactive terminals |
282
370
  | `-q, --quiet` | Suppress status output; only print results and errors |
283
371
  | `-v, --version` | Print version and exit |
@@ -289,10 +377,10 @@ garu [global options] <command> [command options]
289
377
 
290
378
  The CLI has two output modes:
291
379
 
292
- | Mode | When | Stdout | Stderr |
293
- | --------------- | ---------------------- | -------------- | ---------------- |
294
- | **Interactive** | Terminal (TTY) | Formatted text | Status lines |
295
- | **Machine** | Piped, CI, or `--json` | JSON | Nothing |
380
+ | Mode | When | Stdout | Stderr |
381
+ | --------------- | ---------------------- | -------------- | ------------ |
382
+ | **Interactive** | Terminal (TTY) | Formatted text | Status lines |
383
+ | **Machine** | Piped, CI, or `--json` | JSON | Nothing |
296
384
 
297
385
  Switching is automatic -- pipe to another command and JSON output activates:
298
386
 
@@ -338,11 +426,11 @@ Agents calling the CLI as a subprocess automatically get JSON output (non-TTY de
338
426
 
339
427
  ## Configuration
340
428
 
341
- | Item | Path | Notes |
342
- | ----------------- | --------------------------------- | ----------------------------------------- |
343
- | Config directory | `~/.config/garu/` | Respects `$XDG_CONFIG_HOME` |
344
- | Credentials | `~/.config/garu/credentials.json` | `0600` permissions (owner read/write) |
345
- | Override path | `$GARU_CREDENTIALS_PATH` | Point to a custom credentials file |
429
+ | Item | Path | Notes |
430
+ | ---------------- | --------------------------------- | ------------------------------------- |
431
+ | Config directory | `~/.config/garu/` | Respects `$XDG_CONFIG_HOME` |
432
+ | Credentials | `~/.config/garu/credentials.json` | `0600` permissions (owner read/write) |
433
+ | Override path | `$GARU_CREDENTIALS_PATH` | Point to a custom credentials file |
346
434
 
347
435
  ---
348
436
 
package/dist/index.cjs CHANGED
@@ -15,7 +15,7 @@ var pc__default = /*#__PURE__*/_interopDefault(pc);
15
15
  // src/index.ts
16
16
 
17
17
  // src/version.ts
18
- var CLI_VERSION = "0.4.2";
18
+ var CLI_VERSION = "0.6.0";
19
19
  var CliError = class extends Error {
20
20
  code;
21
21
  exitCode;
@@ -475,6 +475,8 @@ async function logoutCommand(opts = {}) {
475
475
  printSuccess(`Profile '${opts.profile}' removed.`, opts);
476
476
  return { cleared: opts.profile };
477
477
  }
478
+
479
+ // src/commands/scheduled-charges.ts
478
480
  async function getClient2(opts) {
479
481
  if (opts.garu) return opts.garu;
480
482
  const auth = await resolveAuth({
@@ -486,8 +488,209 @@ async function getClient2(opts) {
486
488
  ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
487
489
  });
488
490
  }
489
- async function webhooksEventsListCommand(opts) {
491
+ function buildRecurrence(opts) {
492
+ if (opts.recurrenceInterval === void 0) return void 0;
493
+ const recurrence = { interval: opts.recurrenceInterval };
494
+ if (opts.recurrenceIntervalCount !== void 0)
495
+ recurrence.intervalCount = opts.recurrenceIntervalCount;
496
+ if (opts.recurrenceEndsAfter !== void 0) recurrence.endsAfter = opts.recurrenceEndsAfter;
497
+ if (opts.recurrenceEndsOn !== void 0) recurrence.endsOn = opts.recurrenceEndsOn;
498
+ return recurrence;
499
+ }
500
+ async function scheduledChargesCreateCommand(opts) {
501
+ const garu = await getClient2(opts);
502
+ const params = {
503
+ customerId: opts.customerId,
504
+ amount: opts.amount,
505
+ type: opts.type,
506
+ dueDate: opts.dueDate,
507
+ methods: opts.methods
508
+ };
509
+ if (opts.productId !== void 0) params.productId = opts.productId;
510
+ if (opts.description !== void 0) params.description = opts.description;
511
+ const recurrence = buildRecurrence(opts);
512
+ if (recurrence !== void 0) params.recurrence = recurrence;
513
+ if (opts.trialDays !== void 0) params.trialDays = opts.trialDays;
514
+ if (opts.externalReference !== void 0) params.externalReference = opts.externalReference;
515
+ if (opts.metadata !== void 0) params.metadata = opts.metadata;
516
+ if (opts.maxRecoveryDays !== void 0) params.maxRecoveryDays = opts.maxRecoveryDays;
517
+ if (opts.idempotencyKey !== void 0) params.idempotencyKey = opts.idempotencyKey;
518
+ const charge = await garu.scheduledCharges.create(params);
519
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
520
+ return charge;
521
+ }
522
+ async function scheduledChargesListCommand(opts) {
523
+ const garu = await getClient2(opts);
524
+ const params = {};
525
+ if (opts.page !== void 0) params.page = opts.page;
526
+ if (opts.limit !== void 0) params.limit = opts.limit;
527
+ if (opts.customerId !== void 0) params.customerId = opts.customerId;
528
+ if (opts.status !== void 0 && opts.status.length > 0) {
529
+ params.status = opts.status.length === 1 ? opts.status[0] : opts.status;
530
+ }
531
+ if (opts.type !== void 0) params.type = opts.type;
532
+ if (opts.dueFrom !== void 0) params.dueFrom = opts.dueFrom;
533
+ if (opts.dueTo !== void 0) params.dueTo = opts.dueTo;
534
+ if (opts.search !== void 0) params.search = opts.search;
535
+ const result = await garu.scheduledCharges.list(params);
536
+ printResult(result, { ...opts, prettyPrint: prettyScheduledChargeList });
537
+ return result;
538
+ }
539
+ async function scheduledChargesGetCommand(opts) {
540
+ const garu = await getClient2(opts);
541
+ const detail = await garu.scheduledCharges.get(opts.id);
542
+ printResult(detail, { ...opts, prettyPrint: prettyScheduledChargeDetail });
543
+ return detail;
544
+ }
545
+ async function scheduledChargesPostponeCommand(opts) {
546
+ const garu = await getClient2(opts);
547
+ const params = { newDueDate: opts.newDueDate };
548
+ if (opts.reason !== void 0) params.reason = opts.reason;
549
+ const charge = await garu.scheduledCharges.postpone(opts.id, params);
550
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
551
+ return charge;
552
+ }
553
+ async function scheduledChargesPauseCommand(opts) {
554
+ const garu = await getClient2(opts);
555
+ const params = {};
556
+ if (opts.reason !== void 0) params.reason = opts.reason;
557
+ const charge = await garu.scheduledCharges.pause(opts.id, params);
558
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
559
+ return charge;
560
+ }
561
+ async function scheduledChargesResumeCommand(opts) {
562
+ const garu = await getClient2(opts);
563
+ const charge = await garu.scheduledCharges.resume(opts.id);
564
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
565
+ return charge;
566
+ }
567
+ async function scheduledChargesMarkPaidCommand(opts) {
568
+ const garu = await getClient2(opts);
569
+ const params = { paymentDate: opts.paymentDate };
570
+ if (opts.externalReference !== void 0) params.externalReference = opts.externalReference;
571
+ if (opts.cycleNumber !== void 0) params.cycleNumber = opts.cycleNumber;
572
+ const charge = await garu.scheduledCharges.markPaid(opts.id, params);
573
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
574
+ return charge;
575
+ }
576
+ async function scheduledChargesCancelRecurrenceCommand(opts) {
577
+ const garu = await getClient2(opts);
578
+ const params = {};
579
+ if (opts.reason !== void 0) params.reason = opts.reason;
580
+ const charge = await garu.scheduledCharges.cancelRecurrence(opts.id, params);
581
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
582
+ return charge;
583
+ }
584
+ async function scheduledChargesCancelAtPeriodEndCommand(opts) {
585
+ const garu = await getClient2(opts);
586
+ const params = { enabled: opts.enabled };
587
+ const charge = await garu.scheduledCharges.setCancelAtPeriodEnd(opts.id, params);
588
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
589
+ return charge;
590
+ }
591
+ async function scheduledChargesChangePaymentMethodCommand(opts) {
592
+ const garu = await getClient2(opts);
593
+ const params = {
594
+ paymentMethodId: opts.paymentMethodId
595
+ };
596
+ const charge = await garu.scheduledCharges.changePaymentMethod(opts.id, params);
597
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
598
+ return charge;
599
+ }
600
+ async function scheduledChargesClearPaymentMethodCommand(opts) {
601
+ const garu = await getClient2(opts);
602
+ const charge = await garu.scheduledCharges.clearPaymentMethod(opts.id);
603
+ printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
604
+ return charge;
605
+ }
606
+ async function scheduledChargesAttemptsCommand(opts) {
607
+ const garu = await getClient2(opts);
608
+ const params = {};
609
+ if (opts.page !== void 0) params.page = opts.page;
610
+ if (opts.limit !== void 0) params.limit = opts.limit;
611
+ if (opts.cycleNumber !== void 0) params.cycleNumber = opts.cycleNumber;
612
+ const result = await garu.scheduledCharges.listAttempts(opts.id, params);
613
+ printResult(result, { ...opts, prettyPrint: prettyAttemptList });
614
+ return result;
615
+ }
616
+ async function scheduledChargesChargeNowCommand(opts) {
490
617
  const garu = await getClient2(opts);
618
+ const result = await garu.scheduledCharges.chargeNow(opts.id);
619
+ printResult(result, { ...opts, prettyPrint: prettyChargeNow });
620
+ if (result.outcome === "failed" || result.outcome === "not_sent") {
621
+ process.exitCode = 1;
622
+ }
623
+ return result;
624
+ }
625
+ function prettyScheduledCharge(c) {
626
+ const lines = [
627
+ `Scheduled charge ${c.id}`,
628
+ ` status: ${c.status}`,
629
+ ` type: ${c.type}`,
630
+ ` amount: ${c.amount}`,
631
+ ` dueDate: ${c.dueDate}`,
632
+ ` methods: ${c.methods.join(", ")}`
633
+ ];
634
+ if (c.description) lines.push(` description: ${c.description}`);
635
+ if (c.maxRecoveryDays !== null) lines.push(` maxRecoveryDays: ${c.maxRecoveryDays}`);
636
+ if (c.externalReference) lines.push(` externalRef: ${c.externalReference}`);
637
+ return lines.join("\n");
638
+ }
639
+ function prettyScheduledChargeList(list) {
640
+ if (list.data.length === 0) {
641
+ return `No scheduled charges found (page ${list.meta.page}/${list.meta.totalPages || 1})`;
642
+ }
643
+ const header = `Scheduled charges (page ${list.meta.page}/${list.meta.totalPages || "?"}, ${list.meta.total} total)`;
644
+ const rows = list.data.map(
645
+ (c) => ` ${c.id.padEnd(20)} ${String(c.status).padEnd(18)} ${String(c.type).padEnd(9)} ${String(c.amount).padStart(10)} ${c.dueDate}`
646
+ );
647
+ return [header, ...rows].join("\n");
648
+ }
649
+ function prettyScheduledChargeDetail(detail) {
650
+ const lines = [prettyScheduledCharge(detail.charge)];
651
+ if (detail.events.length > 0) {
652
+ lines.push(" events:");
653
+ for (const e of detail.events) {
654
+ lines.push(` [${e.id}] ${e.eventType} (${e.createdAt})`);
655
+ }
656
+ }
657
+ if (detail.transactions.length > 0) {
658
+ lines.push(" transactions:");
659
+ for (const t of detail.transactions) {
660
+ lines.push(` [${t.id}] ${t.paymentMethod} ${t.status} ${t.value} (${t.date})`);
661
+ }
662
+ }
663
+ return lines.join("\n");
664
+ }
665
+ function prettyAttemptList(list) {
666
+ if (list.data.length === 0) {
667
+ return `No billing attempts found (page ${list.meta.page}/${list.meta.totalPages || 1})`;
668
+ }
669
+ const header = `Billing attempts (page ${list.meta.page}/${list.meta.totalPages || "?"}, ${list.meta.total} total)`;
670
+ const rows = list.data.map(
671
+ (a) => ` cycle ${String(a.cycleNumber).padStart(3)} #${a.attemptNumber} ${a.status.padEnd(10)} ${a.paymentMethod.padEnd(7)} ${a.failureCode ?? ""} ${a.attemptedAt}`
672
+ );
673
+ return [header, ...rows].join("\n");
674
+ }
675
+ function prettyChargeNow(result) {
676
+ const lines = [result.message, ` outcome: ${result.outcome}`];
677
+ if (result.cycleNumber !== null) lines.push(` cycle: ${result.cycleNumber}`);
678
+ if (result.reason) lines.push(` reason: ${result.reason}`);
679
+ return lines.join("\n");
680
+ }
681
+ async function getClient3(opts) {
682
+ if (opts.garu) return opts.garu;
683
+ const auth = await resolveAuth({
684
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
685
+ ...opts.profile !== void 0 ? { profile: opts.profile } : {}
686
+ });
687
+ return createGaruClient({
688
+ auth,
689
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
690
+ });
691
+ }
692
+ async function webhooksEventsListCommand(opts) {
693
+ const garu = await getClient3(opts);
491
694
  const params = {};
492
695
  if (opts.page !== void 0) params.page = opts.page;
493
696
  if (opts.limit !== void 0) params.limit = opts.limit;
@@ -499,17 +702,24 @@ async function webhooksEventsListCommand(opts) {
499
702
  return result;
500
703
  }
501
704
  async function webhooksEventsGetCommand(opts) {
502
- const garu = await getClient2(opts);
705
+ const garu = await getClient3(opts);
503
706
  const event = await garu.webhookEvents.get(opts.id);
504
707
  printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
505
708
  return event;
506
709
  }
507
710
  async function webhooksEventsRetryCommand(opts) {
508
- const garu = await getClient2(opts);
711
+ const garu = await getClient3(opts);
509
712
  const event = await garu.webhookEvents.retry(opts.id);
510
713
  printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
511
714
  return event;
512
715
  }
716
+ async function webhooksEventsResendCommand(opts) {
717
+ const garu = await getClient3(opts);
718
+ const clone = await garu.webhookEvents.resend(opts.id);
719
+ printSuccess(`Resent event ${opts.id} \u2192 new event ${clone.id}`, opts);
720
+ printResult(clone, { ...opts, prettyPrint: prettyWebhookEvent });
721
+ return clone;
722
+ }
513
723
  function statusBadge(status) {
514
724
  const padded = status.padEnd(7);
515
725
  if (status === "success") return pc__default.default.green(padded);
@@ -535,6 +745,7 @@ function prettyWebhookEvent(event) {
535
745
  ` endpoint: [${event.webhookEndpoint.id}] ${event.webhookEndpoint.url}`,
536
746
  ` createdAt: ${event.createdAt}`
537
747
  ];
748
+ if (event.manualResendOf !== null) lines.push(` resendOf: ${event.manualResendOf}`);
538
749
  if (event.lastAttemptAt) lines.push(` lastAttemptAt: ${event.lastAttemptAt}`);
539
750
  if (event.nextRetryAt) lines.push(` nextRetryAt: ${event.nextRetryAt}`);
540
751
  if (event.responseStatus !== null) lines.push(` responseStatus: ${event.responseStatus}`);
@@ -567,6 +778,96 @@ function parseWebhookEventStatus(raw) {
567
778
  `--status must be 'pending', 'success', or 'failed' (got '${raw}')`
568
779
  );
569
780
  }
781
+ function parseScheduledChargeType(raw) {
782
+ if (raw === "one_time" || raw === "recurring") return raw;
783
+ throw new CliError("invalid_input", `--type must be 'one_time' or 'recurring' (got '${raw}')`);
784
+ }
785
+ var SCHEDULED_PAYMENT_METHODS = ["pix", "boleto", "card"];
786
+ function parseScheduledPaymentMethods(raw) {
787
+ const parts = raw.split(",").map((s) => s.trim()).filter(Boolean);
788
+ if (parts.length === 0) {
789
+ throw new CliError(
790
+ "invalid_input",
791
+ `--methods must list at least one of: ${SCHEDULED_PAYMENT_METHODS.join(", ")}`
792
+ );
793
+ }
794
+ for (const p of parts) {
795
+ if (!SCHEDULED_PAYMENT_METHODS.includes(p)) {
796
+ throw new CliError(
797
+ "invalid_input",
798
+ `--methods entries must be one of ${SCHEDULED_PAYMENT_METHODS.join(", ")} (got '${p}')`
799
+ );
800
+ }
801
+ }
802
+ return parts;
803
+ }
804
+ var RECURRENCE_INTERVALS = [
805
+ "weekly",
806
+ "biweekly",
807
+ "monthly",
808
+ "bimonthly",
809
+ "quarterly",
810
+ "biannual",
811
+ "yearly"
812
+ ];
813
+ function parseRecurrenceInterval(raw) {
814
+ if (RECURRENCE_INTERVALS.includes(raw)) return raw;
815
+ throw new CliError(
816
+ "invalid_input",
817
+ `--recurrence-interval must be one of ${RECURRENCE_INTERVALS.join(", ")} (got '${raw}')`
818
+ );
819
+ }
820
+ var SCHEDULED_CHARGE_STATUSES = [
821
+ "scheduled",
822
+ "due_today",
823
+ "overdue",
824
+ "paid",
825
+ "paused",
826
+ "canceled",
827
+ "trial",
828
+ "pending_tokenization",
829
+ "recurrence_canceled"
830
+ ];
831
+ function parseScheduledChargeStatus(raw) {
832
+ if (SCHEDULED_CHARGE_STATUSES.includes(raw)) return raw;
833
+ throw new CliError(
834
+ "invalid_input",
835
+ `--status must be one of ${SCHEDULED_CHARGE_STATUSES.join(", ")} (got '${raw}')`
836
+ );
837
+ }
838
+ function parseIntInRange(raw, label, min, max) {
839
+ const trimmed = raw.trim();
840
+ const n = Number.parseInt(trimmed, 10);
841
+ if (!Number.isFinite(n) || String(n) !== trimmed || n < min || n > max) {
842
+ throw new CliError(
843
+ "invalid_input",
844
+ `${label} must be an integer between ${min} and ${max} (got '${raw}')`
845
+ );
846
+ }
847
+ return n;
848
+ }
849
+ function parseAmountBrl(raw) {
850
+ const n = Number(raw);
851
+ if (!Number.isFinite(n) || n <= 0) {
852
+ throw new CliError(
853
+ "invalid_input",
854
+ `--amount must be a positive decimal in BRL, e.g. 297.50 (got '${raw}')`
855
+ );
856
+ }
857
+ return n;
858
+ }
859
+ function parseMetadata(raw) {
860
+ let parsed;
861
+ try {
862
+ parsed = JSON.parse(raw);
863
+ } catch {
864
+ throw new CliError("invalid_input", `--metadata must be valid JSON (got '${raw}')`);
865
+ }
866
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
867
+ throw new CliError("invalid_input", `--metadata must be a JSON object`);
868
+ }
869
+ return parsed;
870
+ }
570
871
 
571
872
  // src/index.ts
572
873
  function buildCli() {
@@ -597,17 +898,19 @@ function buildCli() {
597
898
  );
598
899
  });
599
900
  const charges = program.command("charges").description("Create, fetch, and refund charges");
600
- charges.command("list").description("List charges with pagination and filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option("--status <status>", "filter by status (e.g. paid, pending)").option("--search <query>", "search by customer name, email, or document").option("--payment-method <method>", "filter: pix, creditcard, boleto").action(async (cmdOpts) => {
601
- const base = toCommandOptions(program);
602
- await chargesListCommand({
603
- ...base,
604
- page: cmdOpts.page,
605
- limit: cmdOpts.limit,
606
- status: cmdOpts.status,
607
- search: cmdOpts.search,
608
- paymentMethod: cmdOpts.paymentMethod
609
- }).catch((err) => printErrorAndExit(err, base));
610
- });
901
+ charges.command("list").description("List charges with pagination and filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option("--status <status>", "filter by status (e.g. paid, pending)").option("--search <query>", "search by customer name, email, or document").option("--payment-method <method>", "filter: pix, creditcard, boleto").action(
902
+ async (cmdOpts) => {
903
+ const base = toCommandOptions(program);
904
+ await chargesListCommand({
905
+ ...base,
906
+ page: cmdOpts.page,
907
+ limit: cmdOpts.limit,
908
+ status: cmdOpts.status,
909
+ search: cmdOpts.search,
910
+ paymentMethod: cmdOpts.paymentMethod
911
+ }).catch((err) => printErrorAndExit(err, base));
912
+ }
913
+ );
611
914
  charges.command("create").description("Create a charge (PIX, credit card, or boleto)").requiredOption("--type <type>", "payment method: pix | credit_card | boleto").requiredOption("--product-id <uuid>", "product UUID").requiredOption("--customer-name <name>", "customer full name").requiredOption("--customer-email <email>", "customer email").requiredOption(
612
915
  "--customer-document <document>",
613
916
  "CPF (11 digits) or CNPJ (14 digits), digits only"
@@ -646,6 +949,176 @@ function buildCli() {
646
949
  idempotencyKey: cmdOpts.idempotencyKey
647
950
  }).catch((err) => printErrorAndExit(err, base));
648
951
  });
952
+ const scheduled = program.command("scheduled-charges").description("Schedule, inspect, and act on future-dated charges");
953
+ scheduled.command("create").description("Schedule a future-dated charge (one-time or recurring)").requiredOption(
954
+ "--customer-id <n>",
955
+ "customer id",
956
+ (v) => parsePositiveIntId(v, "--customer-id")
957
+ ).requiredOption("--amount <brl>", "decimal BRL amount, e.g. 297.50").requiredOption("--type <type>", "one_time | recurring").requiredOption("--due-date <yyyy-mm-dd>", "first due date in S\xE3o Paulo time").requiredOption("--methods <list>", "comma-separated: pix,boleto,card").option(
958
+ "--product-id <n>",
959
+ "product id (required when methods includes card)",
960
+ (v) => parsePositiveIntId(v, "--product-id")
961
+ ).option("--description <text>", "charge description").option("--recurrence-interval <interval>", "recurring cadence: weekly|monthly|yearly|\u2026").option(
962
+ "--recurrence-interval-count <n>",
963
+ "multiplier for the interval",
964
+ (v) => parsePositiveIntId(v, "--recurrence-interval-count")
965
+ ).option(
966
+ "--recurrence-ends-after <n>",
967
+ "stop after N successful cycles",
968
+ (v) => parsePositiveIntId(v, "--recurrence-ends-after")
969
+ ).option("--recurrence-ends-on <yyyy-mm-dd>", "stop after this calendar date").option("--trial-days <n>", "free-trial days (1-365, recurring-only)").option("--external-reference <ref>", "your own reconciliation reference").option("--metadata <json>", "JSON object of custom metadata").option(
970
+ "--max-recovery-days <n>",
971
+ "days past due the recovery sweep keeps auto-billing (1-365; default 14)"
972
+ ).option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (cmdOpts) => {
973
+ const base = toCommandOptions(program);
974
+ await scheduledChargesCreateCommand({
975
+ ...base,
976
+ customerId: cmdOpts.customerId,
977
+ amount: parseAmountBrl(cmdOpts.amount),
978
+ type: parseScheduledChargeType(cmdOpts.type),
979
+ dueDate: cmdOpts.dueDate,
980
+ methods: parseScheduledPaymentMethods(cmdOpts.methods),
981
+ productId: cmdOpts.productId,
982
+ description: cmdOpts.description,
983
+ recurrenceInterval: cmdOpts.recurrenceInterval ? parseRecurrenceInterval(cmdOpts.recurrenceInterval) : void 0,
984
+ recurrenceIntervalCount: cmdOpts.recurrenceIntervalCount,
985
+ recurrenceEndsAfter: cmdOpts.recurrenceEndsAfter,
986
+ recurrenceEndsOn: cmdOpts.recurrenceEndsOn,
987
+ trialDays: cmdOpts.trialDays !== void 0 ? parseIntInRange(cmdOpts.trialDays, "--trial-days", 1, 365) : void 0,
988
+ externalReference: cmdOpts.externalReference,
989
+ metadata: cmdOpts.metadata ? parseMetadata(cmdOpts.metadata) : void 0,
990
+ maxRecoveryDays: cmdOpts.maxRecoveryDays !== void 0 ? parseIntInRange(cmdOpts.maxRecoveryDays, "--max-recovery-days", 1, 365) : void 0,
991
+ idempotencyKey: cmdOpts.idempotencyKey
992
+ }).catch((err) => printErrorAndExit(err, base));
993
+ });
994
+ scheduled.command("list").description("List scheduled charges with pagination and filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option(
995
+ "--customer-id <n>",
996
+ "filter by customer id",
997
+ (v) => parsePositiveIntId(v, "--customer-id")
998
+ ).option(
999
+ "--status <status>",
1000
+ "filter by status (repeatable)",
1001
+ (val, acc) => acc.concat(val),
1002
+ []
1003
+ ).option("--type <type>", "filter by type: one_time | recurring").option("--due-from <yyyy-mm-dd>", "lower bound for due date").option("--due-to <yyyy-mm-dd>", "upper bound for due date").option("--search <query>", "search by customer name, email, or document").action(
1004
+ async (cmdOpts) => {
1005
+ const base = toCommandOptions(program);
1006
+ await scheduledChargesListCommand({
1007
+ ...base,
1008
+ page: cmdOpts.page,
1009
+ limit: cmdOpts.limit,
1010
+ customerId: cmdOpts.customerId,
1011
+ status: cmdOpts.status.length > 0 ? cmdOpts.status.map(parseScheduledChargeStatus) : void 0,
1012
+ type: cmdOpts.type ? parseScheduledChargeType(cmdOpts.type) : void 0,
1013
+ dueFrom: cmdOpts.dueFrom,
1014
+ dueTo: cmdOpts.dueTo,
1015
+ search: cmdOpts.search
1016
+ }).catch((err) => printErrorAndExit(err, base));
1017
+ }
1018
+ );
1019
+ scheduled.command("get <id>").description("Fetch a scheduled charge with its event timeline and linked transactions").action(async (id) => {
1020
+ const base = toCommandOptions(program);
1021
+ await scheduledChargesGetCommand({ ...base, id }).catch(
1022
+ (err) => printErrorAndExit(err, base)
1023
+ );
1024
+ });
1025
+ scheduled.command("charge-now <id>").description(
1026
+ "Dispatch a scheduled charge now (the charge + notification the daily cron would send on the due date), instead of waiting for dueDate. Idempotent: an already-dispatched cycle reports `already_sent` and is NOT re-charged. Prints the result message; exits non-zero on outcome failed/not_sent (and on 4xx)."
1027
+ ).action(async (id) => {
1028
+ const base = toCommandOptions(program);
1029
+ await scheduledChargesChargeNowCommand({ ...base, id }).catch(
1030
+ (err) => printErrorAndExit(err, base)
1031
+ );
1032
+ });
1033
+ scheduled.command("postpone <id>").description("Move a scheduled charge to a new due date").requiredOption("--new-due-date <yyyy-mm-dd>", "new due date (today or future)").option("--reason <text>", "optional reason recorded in the timeline").action(async (id, cmdOpts) => {
1034
+ const base = toCommandOptions(program);
1035
+ await scheduledChargesPostponeCommand({
1036
+ ...base,
1037
+ id,
1038
+ newDueDate: cmdOpts.newDueDate,
1039
+ reason: cmdOpts.reason
1040
+ }).catch((err) => printErrorAndExit(err, base));
1041
+ });
1042
+ scheduled.command("pause <id>").description("Pause a scheduled charge (no reminders fire while paused)").option("--reason <text>", "optional reason recorded in the timeline").action(async (id, cmdOpts) => {
1043
+ const base = toCommandOptions(program);
1044
+ await scheduledChargesPauseCommand({ ...base, id, reason: cmdOpts.reason }).catch(
1045
+ (err) => printErrorAndExit(err, base)
1046
+ );
1047
+ });
1048
+ scheduled.command("resume <id>").description("Resume a paused scheduled charge").action(async (id) => {
1049
+ const base = toCommandOptions(program);
1050
+ await scheduledChargesResumeCommand({ ...base, id }).catch(
1051
+ (err) => printErrorAndExit(err, base)
1052
+ );
1053
+ });
1054
+ scheduled.command("mark-paid <id>").description("Mark a scheduled charge as paid out-of-band (bank transfer, cash, etc.)").requiredOption("--payment-date <yyyy-mm-dd>", "date the payment was received (today or past)").option("--external-reference <ref>", "bank reference or internal id for reconciliation").option(
1055
+ "--cycle-number <n>",
1056
+ "cycle to mark paid (REQUIRED for recurring)",
1057
+ (v) => parsePositiveIntId(v, "--cycle-number")
1058
+ ).action(
1059
+ async (id, cmdOpts) => {
1060
+ const base = toCommandOptions(program);
1061
+ await scheduledChargesMarkPaidCommand({
1062
+ ...base,
1063
+ id,
1064
+ paymentDate: cmdOpts.paymentDate,
1065
+ externalReference: cmdOpts.externalReference,
1066
+ cycleNumber: cmdOpts.cycleNumber
1067
+ }).catch((err) => printErrorAndExit(err, base));
1068
+ }
1069
+ );
1070
+ scheduled.command("cancel-recurrence <id>").description("Stop future cycles of a recurring series (recurring-only)").option("--reason <text>", "optional reason recorded in the timeline").action(async (id, cmdOpts) => {
1071
+ const base = toCommandOptions(program);
1072
+ await scheduledChargesCancelRecurrenceCommand({
1073
+ ...base,
1074
+ id,
1075
+ reason: cmdOpts.reason
1076
+ }).catch((err) => printErrorAndExit(err, base));
1077
+ });
1078
+ scheduled.command("cancel-at-period-end <id>").description("Toggle Stripe-style soft cancel on a recurring series (recurring-only)").option("--disable", "clear the flag instead of setting it").action(async (id, cmdOpts) => {
1079
+ const base = toCommandOptions(program);
1080
+ await scheduledChargesCancelAtPeriodEndCommand({
1081
+ ...base,
1082
+ id,
1083
+ enabled: !cmdOpts.disable
1084
+ }).catch((err) => printErrorAndExit(err, base));
1085
+ });
1086
+ scheduled.command("change-payment-method <id>").description("Swap the saved card on a recurring series (recurring-only)").requiredOption(
1087
+ "--payment-method-id <n>",
1088
+ "PaymentMethod id (same customer)",
1089
+ (v) => parsePositiveIntId(v, "--payment-method-id")
1090
+ ).action(async (id, cmdOpts) => {
1091
+ const base = toCommandOptions(program);
1092
+ await scheduledChargesChangePaymentMethodCommand({
1093
+ ...base,
1094
+ id,
1095
+ paymentMethodId: cmdOpts.paymentMethodId
1096
+ }).catch((err) => printErrorAndExit(err, base));
1097
+ });
1098
+ scheduled.command("clear-payment-method <id>").description(
1099
+ "Clear the saved card; future cycles fall back to email-with-link (recurring-only)"
1100
+ ).action(async (id) => {
1101
+ const base = toCommandOptions(program);
1102
+ await scheduledChargesClearPaymentMethodCommand({ ...base, id }).catch(
1103
+ (err) => printErrorAndExit(err, base)
1104
+ );
1105
+ });
1106
+ scheduled.command("attempts <id>").description("List the per-attempt billing log for a scheduled charge").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option(
1107
+ "--cycle-number <n>",
1108
+ "filter to a single cycle",
1109
+ (v) => parsePositiveIntId(v, "--cycle-number")
1110
+ ).action(
1111
+ async (id, cmdOpts) => {
1112
+ const base = toCommandOptions(program);
1113
+ await scheduledChargesAttemptsCommand({
1114
+ ...base,
1115
+ id,
1116
+ page: cmdOpts.page,
1117
+ limit: cmdOpts.limit,
1118
+ cycleNumber: cmdOpts.cycleNumber
1119
+ }).catch((err) => printErrorAndExit(err, base));
1120
+ }
1121
+ );
649
1122
  const webhooks = program.command("webhooks").description("Inspect and replay webhook deliveries");
650
1123
  const events = webhooks.command("events").description("List, inspect, and resend webhook events");
651
1124
  events.command("list").description("List webhook events with filters").option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10)).option("--limit <n>", "items per page (1-100)", (v) => parseInt(v, 10)).option("--status <status>", "filter: pending, success, or failed").option("--event-type <type>", "filter by Garu event type, e.g. transaction.payment.paid").option(
@@ -672,13 +1145,24 @@ function buildCli() {
672
1145
  id: parsePositiveIntId(id, "Webhook event ID")
673
1146
  }).catch((err) => printErrorAndExit(err, base));
674
1147
  });
675
- events.command("retry <id>").description("Re-deliver a webhook event (resets to pending and triggers an immediate attempt)").action(async (id) => {
1148
+ events.command("retry <id>").description(
1149
+ "[deprecated: prefer `resend`] Re-deliver a webhook event in place (resets the original row to pending and triggers an immediate attempt; destroys the prior failure record)"
1150
+ ).action(async (id) => {
676
1151
  const base = toCommandOptions(program);
677
1152
  await webhooksEventsRetryCommand({
678
1153
  ...base,
679
1154
  id: parsePositiveIntId(id, "Webhook event ID")
680
1155
  }).catch((err) => printErrorAndExit(err, base));
681
1156
  });
1157
+ events.command("resend <id>").description(
1158
+ "Re-deliver a webhook event by cloning it (audit-trail preserving: original row is untouched, clone gets a new id and points back via manualResendOf)"
1159
+ ).action(async (id) => {
1160
+ const base = toCommandOptions(program);
1161
+ await webhooksEventsResendCommand({
1162
+ ...base,
1163
+ id: parsePositiveIntId(id, "Webhook event ID")
1164
+ }).catch((err) => printErrorAndExit(err, base));
1165
+ });
682
1166
  program.command("doctor").description("Environment diagnostic").action(async () => {
683
1167
  const base = toCommandOptions(program);
684
1168
  await doctorCommand(base).catch((err) => printErrorAndExit(err, base));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/cli",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "Official command-line interface for the Garu payment gateway.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://garu.com.br",
@@ -46,7 +46,7 @@
46
46
  "prepublishOnly": "npm run check:version-sync && npm run typecheck && npm test && npm run build"
47
47
  },
48
48
  "dependencies": {
49
- "@garuhq/node": "0.11.1",
49
+ "@garuhq/node": "0.13.0",
50
50
  "@inquirer/prompts": "8.4.1",
51
51
  "commander": "12.0.0",
52
52
  "picocolors": "1.0.0",