@garuhq/cli 0.7.0 → 0.9.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,62 @@
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.9.0] — 2026-08-21
7
+
8
+ ### Added
9
+
10
+ - **`garu installment-plans` — boleto parcelado (carnê).** Sell a product as
11
+ 2–12 monthly bank slips: `create`, `list`, `get`, `reissue`, `postpone`,
12
+ `mark-paid`, `cancel`, `request-refund`.
13
+ - **`garu refund-requests` — refunds Garu cannot make for you.** A boleto
14
+ cannot be reversed and Celcoin exposes no Pix devolução, so this records the
15
+ request and waits for you to confirm the transfer: `list`, `get`, `confirm`,
16
+ `reject`.
17
+
18
+ ### Changed
19
+
20
+ - **`@garuhq/node` bumped to `1.1.0`.**
21
+
22
+ ### Breaking
23
+
24
+ - **`charges` commands now use the versioned `/api/v1/charges` API**, via
25
+ `@garuhq/node@1.0.0`+.
26
+ - `garu charges get <id>` and `garu charges refund <id>` now take the
27
+ charge's **uuid**, not the old numeric id.
28
+ - `garu charges refund --amount` is now **decimal BRL / reais** (e.g.
29
+ `10.00`), not centavos. The `--idempotency-key` flag on `refund` is
30
+ removed — the v1 refund route doesn't take one.
31
+ - `garu charges create --type credit_card` keeps its flag spelling; the
32
+ underlying SDK call now sends `creditCard` and a `card` object (was
33
+ `cardInfo`) — no CLI-facing change beyond the `--card-*` flags already in
34
+ use.
35
+ - `garu charges list --payment-method` now takes `credit_card` (was
36
+ `creditcard`, undocumented and unvalidated); `--status` is validated
37
+ against the new friendly status set (`pending`, `authorized`, `paid`,
38
+ `failed`, `expired`, `canceled`, `refund_pending`, `refunded`,
39
+ `chargeback`).
40
+ - `garu charges get`/`list` output: `charge.uuid` (was `.id`),
41
+ `charge.paymentMethod` (was `.paymentMethodId`), `charge.createdAt` /
42
+ `.expiresAt` (was `.date` / `.deadline`), and a new `charge.chargedTotal`
43
+ field (what was actually charged, vs. `.amount`, the product's base
44
+ price). `charges list`'s response shape is now `{ data, count, totalCount,
45
+ totalPages }` (was `{ data, meta }`).
46
+
47
+ ## [0.8.0] — 2026-07-18
48
+
49
+ ### Changed
50
+
51
+ - **Products now use the versioned public API.** Via `@garuhq/node@0.16.0`,
52
+ `garu products create` / `update` hit `/api/v1/products` (uuid-keyed). The
53
+ numeric product id is no longer returned; product output shows the `uuid`.
54
+
55
+ ### Fixed
56
+
57
+ - **`--value` is Reais, not centavos.** The flag was documented and parsed as
58
+ centavos; following the help (`--value 4990`) created a product priced 100×
59
+ (R$ 4.990,00 instead of R$ 49,90). `--value` now takes a decimal amount in
60
+ Reais (e.g. `49.90`). `charges` / refund `--amount` remain centavos.
61
+
6
62
  ## [0.7.0] — 2026-05-31
7
63
 
8
64
  ### Added
package/README.md CHANGED
@@ -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: `pending`, `authorized`, `paid`, `failed`, `expired`, `canceled`, `refund_pending`, `refunded`, `chargeback` |
164
+ | `--search <query>` | Search by customer name, email, or document |
165
+ | `--payment-method <method>` | Filter: `pix`, `credit_card`, `boleto` |
166
166
 
167
167
  ---
168
168
 
@@ -211,32 +211,31 @@ garu charges create --type credit_card --product-id prod-uuid \
211
211
 
212
212
  ### `garu charges get`
213
213
 
214
- Fetch a single charge by ID.
214
+ Fetch a single charge by its uuid.
215
215
 
216
216
  ```bash
217
- garu charges get 4472
218
- garu charges get 4472 --json | jq '.status'
217
+ garu charges get 6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f
218
+ garu charges get 6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f --json | jq '.status'
219
219
  ```
220
220
 
221
221
  ---
222
222
 
223
223
  ### `garu charges refund`
224
224
 
225
- Refund a charge (full or partial).
225
+ Refund a charge by its uuid (full or partial).
226
226
 
227
227
  ```bash
228
228
  # Full refund
229
- garu charges refund 4472
229
+ garu charges refund 6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f
230
230
 
231
- # Partial refund (1000 centavos = R$10.00)
232
- garu charges refund 4472 --amount 1000 --reason "customer_request"
231
+ # Partial refund, in decimal BRL / reais
232
+ garu charges refund 6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f --amount 10.00 --reason "customer_request"
233
233
  ```
234
234
 
235
- | Flag | Description |
236
- | ------------------------- | ------------------------------------------------- |
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) |
235
+ | Flag | Description |
236
+ | ------------------ | ------------------------------------------------------------------ |
237
+ | `--amount <reais>` | Partial refund amount in decimal BRL, e.g. `10.00` (omit for full) |
238
+ | `--reason <text>` | Optional refund reason |
240
239
 
241
240
  ---
242
241
 
@@ -328,6 +327,81 @@ The process **exits non-zero** when:
328
327
 
329
328
  ---
330
329
 
330
+ ### `garu installment-plans create`
331
+
332
+ Sell a product as **boleto parcelado (carnê)** — a purchase split into 2–12
333
+ monthly bank slips. This is seller-financed consumer credit: nobody guarantees
334
+ a boleto, so Garu never advances funds and carries none of the default risk.
335
+ Only the first slip is registered at creation; the rest are emitted month by
336
+ month once parcela 1 compensates.
337
+
338
+ ```bash
339
+ garu installment-plans create \
340
+ --product-id 40381e8e-6ee7-4b8e-9393-766a6e2109d2 \
341
+ --customer-id 4821 --installments 12
342
+
343
+ # Attribute the sale to an affiliate (fixed for the whole carnê) and pick
344
+ # the first due date
345
+ garu installment-plans create \
346
+ --product-id 40381e8e-6ee7-4b8e-9393-766a6e2109d2 \
347
+ --customer-id 4821 --installments 6 \
348
+ --first-due-date 2026-10-05 --affiliate-id 5
349
+ ```
350
+
351
+ | Flag | Description |
352
+ | ------------------------- | --------------------------------------------------------------- |
353
+ | `--product-id <uuid>` | Product uuid (must have boleto parcelado enabled) — required |
354
+ | `--customer-id <n>` | Customer id — required |
355
+ | `--installments <n>` | 2–12 installments — required |
356
+ | `--first-due-date <date>` | First installment due date (default: today) |
357
+ | `--affiliate-id <n>` | Attribute the sale to this affiliate (fixed for the whole plan) |
358
+ | `--idempotency-key <key>` | Idempotency key (auto-generated if omitted) |
359
+
360
+ ---
361
+
362
+ ### `garu installment-plans` — other subcommands
363
+
364
+ | Command | Description |
365
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------ |
366
+ | `list [--page --limit --customer-id --product-id --status ...]` | List carnês (`--status` repeatable; `--due-from/--due-to`) |
367
+ | `get <uuid>` | Fetch a carnê with every installment: due date, status, barcode and PDF |
368
+ | `reissue <uuid> <number>` | Issue a segunda via for one installment once its slip has expired |
369
+ | `postpone <uuid> <number> --new-due-date <date>` | Move one installment to a later date (its siblings keep theirs) |
370
+ | `mark-paid <uuid> <number>` | Record an installment as paid when the webhook never arrived |
371
+ | `cancel <uuid> [--note]` | Cancel the carnê — stops emission/reminders, cancels open provider slips |
372
+ | `request-refund <uuid> [--amount --reason]` | Ask for the carnê to be refunded (see `garu refund-requests` below) |
373
+
374
+ ---
375
+
376
+ ### `garu refund-requests`
377
+
378
+ Garu cannot reverse a boleto, and Celcoin exposes no Pix devolução, so a
379
+ refund on either rail is a request-and-notify flow: you transfer the money
380
+ back yourself, then tell Garu it happened. Card charges keep their automated
381
+ reversal via `garu charges refund`.
382
+
383
+ ```bash
384
+ # See everything you still owe a buyer
385
+ garu refund-requests list --status pending
386
+
387
+ # After you've transferred the money back
388
+ garu refund-requests confirm a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
389
+ --note "Pix devolvido em 14/08, e2e E12345678"
390
+
391
+ # Decline instead — the carnê or charge is left untouched
392
+ garu refund-requests reject a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
393
+ --note "Produto entregue e retirado na loja"
394
+ ```
395
+
396
+ | Command | Description |
397
+ | ------------------------------------------------------ | ------------------------------------------------------------ |
398
+ | `list [--page --limit --status --plan-id --charge-id]` | List refund requests (`--status` repeatable) |
399
+ | `get <uuid>` | Fetch a single refund request |
400
+ | `confirm <uuid> [--note]` | Record that you returned the money — call AFTER the transfer |
401
+ | `reject <uuid> [--note]` | Decline the request — the carnê or charge is untouched |
402
+
403
+ ---
404
+
331
405
  ### `garu doctor`
332
406
 
333
407
  Run environment diagnostics. Verifies your CLI version, API connectivity, credentials, and detects AI agent integrations.
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.7.0";
18
+ var CLI_VERSION = "0.9.0";
19
19
  var CliError = class extends Error {
20
20
  code;
21
21
  exitCode;
@@ -233,6 +233,185 @@ function createGaruClient(opts) {
233
233
  });
234
234
  }
235
235
 
236
+ // src/lib/parse.ts
237
+ function parsePositiveIntId(raw, label) {
238
+ const id = Number.parseInt(raw, 10);
239
+ if (!Number.isFinite(id) || id <= 0) {
240
+ throw new CliError("invalid_input", `${label} must be a positive integer (got '${raw}')`);
241
+ }
242
+ return id;
243
+ }
244
+ function parsePaymentMethod(raw) {
245
+ if (raw === "pix" || raw === "credit_card" || raw === "boleto") return raw;
246
+ throw new CliError(
247
+ "invalid_input",
248
+ `--type must be 'pix', 'credit_card', or 'boleto' (got '${raw}')`
249
+ );
250
+ }
251
+ function toChargePaymentMethod(type) {
252
+ return type === "credit_card" ? "creditCard" : type;
253
+ }
254
+ function parseChargePaymentMethodFilter(raw) {
255
+ return toChargePaymentMethod(parsePaymentMethod(raw));
256
+ }
257
+ var CHARGE_STATUSES = [
258
+ "pending",
259
+ "authorized",
260
+ "paid",
261
+ "failed",
262
+ "expired",
263
+ "canceled",
264
+ "refund_pending",
265
+ "refunded",
266
+ "chargeback"
267
+ ];
268
+ function parseChargeStatus(raw) {
269
+ if (CHARGE_STATUSES.includes(raw)) return raw;
270
+ throw new CliError(
271
+ "invalid_input",
272
+ `--status must be one of ${CHARGE_STATUSES.join(", ")} (got '${raw}')`
273
+ );
274
+ }
275
+ var INSTALLMENT_PLAN_STATUSES = [
276
+ "pending_activation",
277
+ "active",
278
+ "completed",
279
+ "defaulted",
280
+ "canceled",
281
+ "refunded"
282
+ ];
283
+ function parseInstallmentPlanStatus(raw) {
284
+ if (INSTALLMENT_PLAN_STATUSES.includes(raw)) return raw;
285
+ throw new CliError(
286
+ "invalid_input",
287
+ `--status must be one of ${INSTALLMENT_PLAN_STATUSES.join(", ")} (got '${raw}')`
288
+ );
289
+ }
290
+ var REFUND_REQUEST_STATUSES = ["pending", "confirmed", "rejected"];
291
+ function parseRefundRequestStatus(raw) {
292
+ if (REFUND_REQUEST_STATUSES.includes(raw)) return raw;
293
+ throw new CliError(
294
+ "invalid_input",
295
+ `--status must be one of ${REFUND_REQUEST_STATUSES.join(", ")} (got '${raw}')`
296
+ );
297
+ }
298
+ function parseWebhookEventStatus(raw) {
299
+ if (raw === "pending" || raw === "success" || raw === "failed") return raw;
300
+ throw new CliError(
301
+ "invalid_input",
302
+ `--status must be 'pending', 'success', or 'failed' (got '${raw}')`
303
+ );
304
+ }
305
+ function parseScheduledChargeType(raw) {
306
+ if (raw === "one_time" || raw === "recurring") return raw;
307
+ throw new CliError("invalid_input", `--type must be 'one_time' or 'recurring' (got '${raw}')`);
308
+ }
309
+ var SCHEDULED_PAYMENT_METHODS = [
310
+ "pix",
311
+ "boleto",
312
+ "card",
313
+ "pix_automatic"
314
+ ];
315
+ function parseCsvList(raw) {
316
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
317
+ }
318
+ function parseScheduledPaymentMethods(raw) {
319
+ const parts = parseCsvList(raw);
320
+ if (parts.length === 0) {
321
+ throw new CliError(
322
+ "invalid_input",
323
+ `--methods must list at least one of: ${SCHEDULED_PAYMENT_METHODS.join(", ")}`
324
+ );
325
+ }
326
+ for (const p of parts) {
327
+ if (!SCHEDULED_PAYMENT_METHODS.includes(p)) {
328
+ throw new CliError(
329
+ "invalid_input",
330
+ `--methods entries must be one of ${SCHEDULED_PAYMENT_METHODS.join(", ")} (got '${p}')`
331
+ );
332
+ }
333
+ }
334
+ return parts;
335
+ }
336
+ var RECURRENCE_INTERVALS = [
337
+ "weekly",
338
+ "biweekly",
339
+ "monthly",
340
+ "bimonthly",
341
+ "quarterly",
342
+ "biannual",
343
+ "yearly"
344
+ ];
345
+ function parseRecurrenceInterval(raw) {
346
+ if (RECURRENCE_INTERVALS.includes(raw)) return raw;
347
+ throw new CliError(
348
+ "invalid_input",
349
+ `--recurrence-interval must be one of ${RECURRENCE_INTERVALS.join(", ")} (got '${raw}')`
350
+ );
351
+ }
352
+ var SCHEDULED_CHARGE_STATUSES = [
353
+ "scheduled",
354
+ "due_today",
355
+ "overdue",
356
+ "paid",
357
+ "paused",
358
+ "canceled",
359
+ "trial",
360
+ "pending_tokenization",
361
+ "recurrence_canceled"
362
+ ];
363
+ function parseScheduledChargeStatus(raw) {
364
+ if (SCHEDULED_CHARGE_STATUSES.includes(raw)) return raw;
365
+ throw new CliError(
366
+ "invalid_input",
367
+ `--status must be one of ${SCHEDULED_CHARGE_STATUSES.join(", ")} (got '${raw}')`
368
+ );
369
+ }
370
+ function parseIntInRange(raw, label, min, max) {
371
+ const trimmed = raw.trim();
372
+ const n = Number.parseInt(trimmed, 10);
373
+ if (!Number.isFinite(n) || String(n) !== trimmed || n < min || n > max) {
374
+ throw new CliError(
375
+ "invalid_input",
376
+ `${label} must be an integer between ${min} and ${max} (got '${raw}')`
377
+ );
378
+ }
379
+ return n;
380
+ }
381
+ function parseNonNegativeBrl(raw, label) {
382
+ const trimmed = raw.trim();
383
+ const n = Number(trimmed);
384
+ if (trimmed === "" || !Number.isFinite(n) || n < 0) {
385
+ throw new CliError(
386
+ "invalid_input",
387
+ `${label} must be a non-negative amount in BRL/reais, e.g. 49.90 (got '${raw}')`
388
+ );
389
+ }
390
+ return n;
391
+ }
392
+ function parseAmountBrl(raw) {
393
+ const n = Number(raw);
394
+ if (!Number.isFinite(n) || n <= 0) {
395
+ throw new CliError(
396
+ "invalid_input",
397
+ `--amount must be a positive decimal in BRL, e.g. 297.50 (got '${raw}')`
398
+ );
399
+ }
400
+ return n;
401
+ }
402
+ function parseMetadata(raw) {
403
+ let parsed;
404
+ try {
405
+ parsed = JSON.parse(raw);
406
+ } catch {
407
+ throw new CliError("invalid_input", `--metadata must be valid JSON (got '${raw}')`);
408
+ }
409
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
410
+ throw new CliError("invalid_input", `--metadata must be a JSON object`);
411
+ }
412
+ return parsed;
413
+ }
414
+
236
415
  // src/commands/charges.ts
237
416
  async function getClient(opts) {
238
417
  if (opts.garu) return opts.garu;
@@ -275,8 +454,9 @@ async function chargesCreateCommand(opts) {
275
454
  assertCardFieldsPresent(opts);
276
455
  charge = await garu.charges.create({
277
456
  ...base,
278
- cardInfo: {
279
- cardNumber: opts.cardNumber,
457
+ paymentMethod: toChargePaymentMethod(opts.type),
458
+ card: {
459
+ number: opts.cardNumber,
280
460
  cvv: opts.cardCvv,
281
461
  expirationDate: opts.cardExpiration,
282
462
  holderName: opts.cardHolder,
@@ -284,14 +464,17 @@ async function chargesCreateCommand(opts) {
284
464
  }
285
465
  });
286
466
  } else {
287
- charge = await garu.charges.create(base);
467
+ charge = await garu.charges.create({
468
+ ...base,
469
+ paymentMethod: toChargePaymentMethod(opts.type)
470
+ });
288
471
  }
289
472
  printResult(charge, { ...opts, prettyPrint: prettyCharge });
290
473
  return charge;
291
474
  }
292
475
  async function chargesGetCommand(opts) {
293
476
  const garu = await getClient(opts);
294
- const charge = await garu.charges.get(opts.id);
477
+ const charge = await garu.charges.retrieve(opts.id);
295
478
  printResult(charge, { ...opts, prettyPrint: prettyCharge });
296
479
  return charge;
297
480
  }
@@ -300,7 +483,6 @@ async function chargesRefundCommand(opts) {
300
483
  const params = {};
301
484
  if (opts.amount !== void 0) params.amount = opts.amount;
302
485
  if (opts.reason !== void 0) params.reason = opts.reason;
303
- if (opts.idempotencyKey !== void 0) params.idempotencyKey = opts.idempotencyKey;
304
486
  const charge = await garu.charges.refund(opts.id, params);
305
487
  printResult(charge, { ...opts, prettyPrint: prettyCharge });
306
488
  return charge;
@@ -319,23 +501,26 @@ async function chargesListCommand(opts) {
319
501
  }
320
502
  function prettyChargeList(list) {
321
503
  if (list.data.length === 0) {
322
- return `No charges found (page ${list.meta.page}/${list.meta.totalPages || 1})`;
504
+ return "No charges found";
323
505
  }
324
- const header = `Charges (page ${list.meta.page}/${list.meta.totalPages || "?"}, ${list.meta.total} total)`;
506
+ const header = `Charges (${list.count} of ${list.totalCount} total, ${list.totalPages} page(s))`;
325
507
  const rows = list.data.map(
326
- (c) => ` ${String(c.id).padStart(6)} ${String(c.status).padEnd(14)} ${c.paymentMethodId?.padEnd(10) ?? ""} ${c.date}`
508
+ (c) => ` ${c.uuid} ${String(c.status).padEnd(14)} ${c.paymentMethod.padEnd(10)} ${c.createdAt}`
327
509
  );
328
510
  return [header, ...rows].join("\n");
329
511
  }
330
512
  function prettyCharge(charge) {
331
513
  const lines = [
332
- `Charge ${charge.id}`,
333
- ` status: ${charge.status}`,
334
- ` amount: ${charge.amount}`,
335
- ` method: ${charge.paymentMethodId}`,
336
- ` date: ${charge.date}`
514
+ `Charge ${charge.uuid}`,
515
+ ` status: ${charge.status}`,
516
+ ` amount: ${charge.amount}`,
517
+ ` chargedTotal: ${charge.chargedTotal}`,
518
+ ` method: ${charge.paymentMethod}`,
519
+ ` createdAt: ${charge.createdAt}`
337
520
  ];
338
- if (charge.deadline) lines.push(` deadline: ${charge.deadline}`);
521
+ if (charge.expiresAt) lines.push(` expiresAt: ${charge.expiresAt}`);
522
+ if (charge.refund)
523
+ lines.push(` refund: ${charge.refund.amount} (${charge.refund.reason ?? "no reason"})`);
339
524
  return lines.join("\n");
340
525
  }
341
526
  async function doctorCommand(opts = {}) {
@@ -430,6 +615,152 @@ function prettyDoctor(r) {
430
615
  ].filter((l) => l !== void 0);
431
616
  return lines.join("\n");
432
617
  }
618
+
619
+ // src/commands/installment-plans.ts
620
+ async function getClient2(opts) {
621
+ if (opts.garu) return opts.garu;
622
+ const auth = await resolveAuth({
623
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
624
+ ...opts.profile !== void 0 ? { profile: opts.profile } : {}
625
+ });
626
+ return createGaruClient({
627
+ auth,
628
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
629
+ });
630
+ }
631
+ async function installmentPlansCreateCommand(opts) {
632
+ const garu = await getClient2(opts);
633
+ const params = {
634
+ productId: opts.productId,
635
+ customerId: opts.customerId,
636
+ installments: opts.installments
637
+ };
638
+ if (opts.firstDueDate !== void 0) params.firstDueDate = opts.firstDueDate;
639
+ if (opts.affiliateId !== void 0) params.affiliateId = opts.affiliateId;
640
+ if (opts.idempotencyKey !== void 0) params.idempotencyKey = opts.idempotencyKey;
641
+ const plan = await garu.installmentPlans.create(params);
642
+ printResult(plan, { ...opts, prettyPrint: prettyInstallmentPlan });
643
+ return plan;
644
+ }
645
+ async function installmentPlansListCommand(opts) {
646
+ const garu = await getClient2(opts);
647
+ const params = {};
648
+ if (opts.page !== void 0) params.page = opts.page;
649
+ if (opts.limit !== void 0) params.limit = opts.limit;
650
+ if (opts.customerId !== void 0) params.customerId = opts.customerId;
651
+ if (opts.productId !== void 0) params.productId = opts.productId;
652
+ if (opts.dueFrom !== void 0) params.dueFrom = opts.dueFrom;
653
+ if (opts.dueTo !== void 0) params.dueTo = opts.dueTo;
654
+ if (opts.status !== void 0 && opts.status.length > 0) {
655
+ params.status = opts.status.length === 1 ? opts.status[0] : opts.status;
656
+ }
657
+ const result = await garu.installmentPlans.list(params);
658
+ printResult(result, { ...opts, prettyPrint: prettyInstallmentPlanList });
659
+ return result;
660
+ }
661
+ async function installmentPlansGetCommand(opts) {
662
+ const garu = await getClient2(opts);
663
+ const plan = await garu.installmentPlans.get(opts.uuid);
664
+ printResult(plan, { ...opts, prettyPrint: prettyInstallmentPlanDetail });
665
+ return plan;
666
+ }
667
+ async function installmentPlansReissueCommand(opts) {
668
+ const garu = await getClient2(opts);
669
+ const result = await garu.installmentPlans.reissueInstallment(opts.uuid, opts.number);
670
+ printResult(result, { ...opts, prettyPrint: prettyReissueResult });
671
+ return result;
672
+ }
673
+ async function installmentPlansPostponeCommand(opts) {
674
+ const garu = await getClient2(opts);
675
+ const params = { newDueDate: opts.newDueDate };
676
+ const installment = await garu.installmentPlans.postponeInstallment(
677
+ opts.uuid,
678
+ opts.number,
679
+ params
680
+ );
681
+ printResult(installment, { ...opts, prettyPrint: prettyInstallment });
682
+ return installment;
683
+ }
684
+ async function installmentPlansMarkPaidCommand(opts) {
685
+ const garu = await getClient2(opts);
686
+ const installment = await garu.installmentPlans.markInstallmentPaid(opts.uuid, opts.number);
687
+ printResult(installment, { ...opts, prettyPrint: prettyInstallment });
688
+ return installment;
689
+ }
690
+ async function installmentPlansCancelCommand(opts) {
691
+ const garu = await getClient2(opts);
692
+ const params = {};
693
+ if (opts.note !== void 0) params.note = opts.note;
694
+ const plan = await garu.installmentPlans.cancel(opts.uuid, params);
695
+ printResult(plan, { ...opts, prettyPrint: prettyInstallmentPlan });
696
+ return plan;
697
+ }
698
+ async function installmentPlansRequestRefundCommand(opts) {
699
+ const garu = await getClient2(opts);
700
+ const params = {};
701
+ if (opts.amount !== void 0) params.amount = opts.amount;
702
+ if (opts.reason !== void 0) params.reason = opts.reason;
703
+ const request = await garu.installmentPlans.requestRefund(opts.uuid, params);
704
+ printResult(request, { ...opts, prettyPrint: prettyRefundRequest });
705
+ return request;
706
+ }
707
+ function prettyInstallmentPlan(p) {
708
+ const lines = [
709
+ `Installment plan ${p.uuid}`,
710
+ ` status: ${p.status}`,
711
+ ` installments: ${p.installmentsPaid}/${p.installments} paid`,
712
+ ` baseValue: ${p.baseValue}`,
713
+ ` installmentAmount: ${p.installmentAmount}`,
714
+ ` totalScheduled: ${p.totalScheduled}`,
715
+ ` totalCollected: ${p.totalCollected}`,
716
+ ` firstDueDate: ${p.firstDueDate}`
717
+ ];
718
+ if (p.product) lines.push(` product: ${p.product.name} (${p.product.uuid})`);
719
+ if (p.customer) lines.push(` customer: ${p.customer.name} <${p.customer.email}>`);
720
+ return lines.join("\n");
721
+ }
722
+ function prettyInstallmentPlanDetail(p) {
723
+ const lines = [prettyInstallmentPlan(p)];
724
+ if (p.installmentsDetail && p.installmentsDetail.length > 0) {
725
+ lines.push(" installments:");
726
+ for (const i of p.installmentsDetail) {
727
+ lines.push(
728
+ ` #${i.number} ${i.status.padEnd(10)} ${String(i.amount).padStart(10)} due ${i.dueDate}${i.paidAt ? ` paid ${i.paidAt}` : ""}`
729
+ );
730
+ }
731
+ }
732
+ return lines.join("\n");
733
+ }
734
+ function prettyInstallmentPlanList(list) {
735
+ if (list.data.length === 0) return "No installment plans found";
736
+ const header = `Installment plans (${list.count} of ${list.totalCount} total, ${list.totalPages} page(s))`;
737
+ const rows = list.data.map(
738
+ (p) => ` ${p.uuid} ${p.status.padEnd(18)} ${p.installmentsPaid}/${p.installments} paid ${p.firstDueDate}`
739
+ );
740
+ return [header, ...rows].join("\n");
741
+ }
742
+ function prettyInstallment(i) {
743
+ const lines = [
744
+ `Installment #${i.number}`,
745
+ ` status: ${i.status}`,
746
+ ` amount: ${i.amount}`,
747
+ ` dueDate: ${i.dueDate}`
748
+ ];
749
+ if (i.paidAt) lines.push(` paidAt: ${i.paidAt}`);
750
+ if (i.boleto) lines.push(` boleto: ${i.boleto.barcodeLine}`);
751
+ return lines.join("\n");
752
+ }
753
+ function prettyReissueResult(result) {
754
+ const lines = [`Reissue: ${result.status}`];
755
+ if (result.reason) lines.push(` reason: ${result.reason}`);
756
+ if (result.installment) lines.push(prettyInstallment(result.installment));
757
+ return lines.join("\n");
758
+ }
759
+ function prettyRefundRequest(r) {
760
+ const lines = [`Refund request ${r.uuid}`, ` status: ${r.status}`, ` amount: ${r.amount}`];
761
+ if (r.reason) lines.push(` reason: ${r.reason}`);
762
+ return lines.join("\n");
763
+ }
433
764
  async function loginCommand(opts = {}) {
434
765
  const profile = opts.profile ?? "default";
435
766
  const apiKey = opts.apiKey ?? await import('@inquirer/prompts').then(
@@ -477,7 +808,7 @@ async function logoutCommand(opts = {}) {
477
808
  }
478
809
 
479
810
  // src/commands/products.ts
480
- async function getClient2(opts) {
811
+ async function getClient3(opts) {
481
812
  if (opts.garu) return opts.garu;
482
813
  const auth = await resolveAuth({
483
814
  ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
@@ -508,7 +839,7 @@ function buildProductBody(opts) {
508
839
  return body;
509
840
  }
510
841
  async function productsCreateCommand(opts) {
511
- const garu = await getClient2(opts);
842
+ const garu = await getClient3(opts);
512
843
  const params = { ...buildProductBody(opts), name: opts.name };
513
844
  const product = await garu.products.create(params);
514
845
  printSuccess(`Created product ${product.uuid ?? product.id}`, opts);
@@ -520,7 +851,7 @@ async function productsUpdateCommand(opts) {
520
851
  if (Object.keys(body).length === 0) {
521
852
  throw new CliError("invalid_input", "Nothing to update \u2014 pass at least one field to change.");
522
853
  }
523
- const garu = await getClient2(opts);
854
+ const garu = await getClient3(opts);
524
855
  const product = await garu.products.update(opts.id, body);
525
856
  printSuccess(`Updated product ${product.uuid ?? product.id}`, opts);
526
857
  printResult(product, { ...opts, prettyPrint: prettyProduct });
@@ -536,15 +867,81 @@ function prettyProduct(p) {
536
867
  const lines = [
537
868
  `Product ${p.uuid ?? p.id}`,
538
869
  ` name: ${p.name}`,
539
- ` value: ${p.value} (centavos)`,
870
+ ` value: ${p.value} (reais)`,
540
871
  ` methods: ${methods || "(none)"}`
541
872
  ];
542
873
  if (p.description) lines.push(` description: ${p.description}`);
543
874
  return lines.join("\n");
544
875
  }
545
876
 
877
+ // src/commands/refund-requests.ts
878
+ async function getClient4(opts) {
879
+ if (opts.garu) return opts.garu;
880
+ const auth = await resolveAuth({
881
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
882
+ ...opts.profile !== void 0 ? { profile: opts.profile } : {}
883
+ });
884
+ return createGaruClient({
885
+ auth,
886
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
887
+ });
888
+ }
889
+ async function refundRequestsListCommand(opts) {
890
+ const garu = await getClient4(opts);
891
+ const params = {};
892
+ if (opts.page !== void 0) params.page = opts.page;
893
+ if (opts.limit !== void 0) params.limit = opts.limit;
894
+ if (opts.planId !== void 0) params.planId = opts.planId;
895
+ if (opts.chargeId !== void 0) params.chargeId = opts.chargeId;
896
+ if (opts.status !== void 0 && opts.status.length > 0) {
897
+ params.status = opts.status.length === 1 ? opts.status[0] : opts.status;
898
+ }
899
+ const result = await garu.refundRequests.list(params);
900
+ printResult(result, { ...opts, prettyPrint: prettyRefundRequestList });
901
+ return result;
902
+ }
903
+ async function refundRequestsGetCommand(opts) {
904
+ const garu = await getClient4(opts);
905
+ const request = await garu.refundRequests.get(opts.uuid);
906
+ printResult(request, { ...opts, prettyPrint: prettyRefundRequest2 });
907
+ return request;
908
+ }
909
+ async function refundRequestsConfirmCommand(opts) {
910
+ const garu = await getClient4(opts);
911
+ const params = {};
912
+ if (opts.note !== void 0) params.note = opts.note;
913
+ const request = await garu.refundRequests.confirm(opts.uuid, params);
914
+ printResult(request, { ...opts, prettyPrint: prettyRefundRequest2 });
915
+ return request;
916
+ }
917
+ async function refundRequestsRejectCommand(opts) {
918
+ const garu = await getClient4(opts);
919
+ const params = {};
920
+ if (opts.note !== void 0) params.note = opts.note;
921
+ const request = await garu.refundRequests.reject(opts.uuid, params);
922
+ printResult(request, { ...opts, prettyPrint: prettyRefundRequest2 });
923
+ return request;
924
+ }
925
+ function prettyRefundRequestList(list) {
926
+ if (list.data.length === 0) return "No refund requests found";
927
+ const header = `Refund requests (${list.count} of ${list.totalCount} total, ${list.totalPages} page(s))`;
928
+ const rows = list.data.map(
929
+ (r) => ` ${r.uuid} ${r.status.padEnd(10)} ${String(r.amount).padStart(10)} ${r.installmentPlanId ?? r.chargeId ?? ""}`
930
+ );
931
+ return [header, ...rows].join("\n");
932
+ }
933
+ function prettyRefundRequest2(r) {
934
+ const lines = [`Refund request ${r.uuid}`, ` status: ${r.status}`, ` amount: ${r.amount}`];
935
+ if (r.reason) lines.push(` reason: ${r.reason}`);
936
+ if (r.installmentPlanId) lines.push(` installmentPlan: ${r.installmentPlanId}`);
937
+ if (r.chargeId) lines.push(` charge: ${r.chargeId}`);
938
+ if (r.resolvedAt) lines.push(` resolvedAt: ${r.resolvedAt}`);
939
+ if (r.sellerNote) lines.push(` sellerNote: ${r.sellerNote}`);
940
+ return lines.join("\n");
941
+ }
942
+
546
943
  // src/commands/scheduled-charges.ts
547
- async function getClient3(opts) {
944
+ async function getClient5(opts) {
548
945
  if (opts.garu) return opts.garu;
549
946
  const auth = await resolveAuth({
550
947
  ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
@@ -581,7 +978,7 @@ function assertPixAutomaticRequirements(opts) {
581
978
  }
582
979
  async function scheduledChargesCreateCommand(opts) {
583
980
  assertPixAutomaticRequirements(opts);
584
- const garu = await getClient3(opts);
981
+ const garu = await getClient5(opts);
585
982
  const params = {
586
983
  customerId: opts.customerId,
587
984
  amount: opts.amount,
@@ -603,7 +1000,7 @@ async function scheduledChargesCreateCommand(opts) {
603
1000
  return charge;
604
1001
  }
605
1002
  async function scheduledChargesListCommand(opts) {
606
- const garu = await getClient3(opts);
1003
+ const garu = await getClient5(opts);
607
1004
  const params = {};
608
1005
  if (opts.page !== void 0) params.page = opts.page;
609
1006
  if (opts.limit !== void 0) params.limit = opts.limit;
@@ -620,13 +1017,13 @@ async function scheduledChargesListCommand(opts) {
620
1017
  return result;
621
1018
  }
622
1019
  async function scheduledChargesGetCommand(opts) {
623
- const garu = await getClient3(opts);
1020
+ const garu = await getClient5(opts);
624
1021
  const detail = await garu.scheduledCharges.get(opts.id);
625
1022
  printResult(detail, { ...opts, prettyPrint: prettyScheduledChargeDetail });
626
1023
  return detail;
627
1024
  }
628
1025
  async function scheduledChargesPostponeCommand(opts) {
629
- const garu = await getClient3(opts);
1026
+ const garu = await getClient5(opts);
630
1027
  const params = { newDueDate: opts.newDueDate };
631
1028
  if (opts.reason !== void 0) params.reason = opts.reason;
632
1029
  const charge = await garu.scheduledCharges.postpone(opts.id, params);
@@ -634,7 +1031,7 @@ async function scheduledChargesPostponeCommand(opts) {
634
1031
  return charge;
635
1032
  }
636
1033
  async function scheduledChargesPauseCommand(opts) {
637
- const garu = await getClient3(opts);
1034
+ const garu = await getClient5(opts);
638
1035
  const params = {};
639
1036
  if (opts.reason !== void 0) params.reason = opts.reason;
640
1037
  const charge = await garu.scheduledCharges.pause(opts.id, params);
@@ -642,13 +1039,13 @@ async function scheduledChargesPauseCommand(opts) {
642
1039
  return charge;
643
1040
  }
644
1041
  async function scheduledChargesResumeCommand(opts) {
645
- const garu = await getClient3(opts);
1042
+ const garu = await getClient5(opts);
646
1043
  const charge = await garu.scheduledCharges.resume(opts.id);
647
1044
  printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
648
1045
  return charge;
649
1046
  }
650
1047
  async function scheduledChargesMarkPaidCommand(opts) {
651
- const garu = await getClient3(opts);
1048
+ const garu = await getClient5(opts);
652
1049
  const params = { paymentDate: opts.paymentDate };
653
1050
  if (opts.externalReference !== void 0) params.externalReference = opts.externalReference;
654
1051
  if (opts.cycleNumber !== void 0) params.cycleNumber = opts.cycleNumber;
@@ -657,7 +1054,7 @@ async function scheduledChargesMarkPaidCommand(opts) {
657
1054
  return charge;
658
1055
  }
659
1056
  async function scheduledChargesCancelRecurrenceCommand(opts) {
660
- const garu = await getClient3(opts);
1057
+ const garu = await getClient5(opts);
661
1058
  const params = {};
662
1059
  if (opts.reason !== void 0) params.reason = opts.reason;
663
1060
  const charge = await garu.scheduledCharges.cancelRecurrence(opts.id, params);
@@ -665,14 +1062,14 @@ async function scheduledChargesCancelRecurrenceCommand(opts) {
665
1062
  return charge;
666
1063
  }
667
1064
  async function scheduledChargesCancelAtPeriodEndCommand(opts) {
668
- const garu = await getClient3(opts);
1065
+ const garu = await getClient5(opts);
669
1066
  const params = { enabled: opts.enabled };
670
1067
  const charge = await garu.scheduledCharges.setCancelAtPeriodEnd(opts.id, params);
671
1068
  printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
672
1069
  return charge;
673
1070
  }
674
1071
  async function scheduledChargesChangePaymentMethodCommand(opts) {
675
- const garu = await getClient3(opts);
1072
+ const garu = await getClient5(opts);
676
1073
  const params = {
677
1074
  paymentMethodId: opts.paymentMethodId
678
1075
  };
@@ -681,13 +1078,13 @@ async function scheduledChargesChangePaymentMethodCommand(opts) {
681
1078
  return charge;
682
1079
  }
683
1080
  async function scheduledChargesClearPaymentMethodCommand(opts) {
684
- const garu = await getClient3(opts);
1081
+ const garu = await getClient5(opts);
685
1082
  const charge = await garu.scheduledCharges.clearPaymentMethod(opts.id);
686
1083
  printResult(charge, { ...opts, prettyPrint: prettyScheduledCharge });
687
1084
  return charge;
688
1085
  }
689
1086
  async function scheduledChargesAttemptsCommand(opts) {
690
- const garu = await getClient3(opts);
1087
+ const garu = await getClient5(opts);
691
1088
  const params = {};
692
1089
  if (opts.page !== void 0) params.page = opts.page;
693
1090
  if (opts.limit !== void 0) params.limit = opts.limit;
@@ -697,7 +1094,7 @@ async function scheduledChargesAttemptsCommand(opts) {
697
1094
  return result;
698
1095
  }
699
1096
  async function scheduledChargesChargeNowCommand(opts) {
700
- const garu = await getClient3(opts);
1097
+ const garu = await getClient5(opts);
701
1098
  const result = await garu.scheduledCharges.chargeNow(opts.id);
702
1099
  printResult(result, { ...opts, prettyPrint: prettyChargeNow });
703
1100
  if (result.outcome === "failed" || result.outcome === "not_sent") {
@@ -761,7 +1158,7 @@ function prettyChargeNow(result) {
761
1158
  if (result.reason) lines.push(` reason: ${result.reason}`);
762
1159
  return lines.join("\n");
763
1160
  }
764
- async function getClient4(opts) {
1161
+ async function getClient6(opts) {
765
1162
  if (opts.garu) return opts.garu;
766
1163
  const auth = await resolveAuth({
767
1164
  ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
@@ -773,7 +1170,7 @@ async function getClient4(opts) {
773
1170
  });
774
1171
  }
775
1172
  async function webhooksEventsListCommand(opts) {
776
- const garu = await getClient4(opts);
1173
+ const garu = await getClient6(opts);
777
1174
  const params = {};
778
1175
  if (opts.page !== void 0) params.page = opts.page;
779
1176
  if (opts.limit !== void 0) params.limit = opts.limit;
@@ -785,19 +1182,19 @@ async function webhooksEventsListCommand(opts) {
785
1182
  return result;
786
1183
  }
787
1184
  async function webhooksEventsGetCommand(opts) {
788
- const garu = await getClient4(opts);
1185
+ const garu = await getClient6(opts);
789
1186
  const event = await garu.webhookEvents.get(opts.id);
790
1187
  printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
791
1188
  return event;
792
1189
  }
793
1190
  async function webhooksEventsRetryCommand(opts) {
794
- const garu = await getClient4(opts);
1191
+ const garu = await getClient6(opts);
795
1192
  const event = await garu.webhookEvents.retry(opts.id);
796
1193
  printResult(event, { ...opts, prettyPrint: prettyWebhookEvent });
797
1194
  return event;
798
1195
  }
799
1196
  async function webhooksEventsResendCommand(opts) {
800
- const garu = await getClient4(opts);
1197
+ const garu = await getClient6(opts);
801
1198
  const clone = await garu.webhookEvents.resend(opts.id);
802
1199
  printSuccess(`Resent event ${opts.id} \u2192 new event ${clone.id}`, opts);
803
1200
  printResult(clone, { ...opts, prettyPrint: prettyWebhookEvent });
@@ -839,135 +1236,6 @@ function prettyWebhookEvent(event) {
839
1236
  return lines.join("\n");
840
1237
  }
841
1238
 
842
- // src/lib/parse.ts
843
- function parsePositiveIntId(raw, label) {
844
- const id = Number.parseInt(raw, 10);
845
- if (!Number.isFinite(id) || id <= 0) {
846
- throw new CliError("invalid_input", `${label} must be a positive integer (got '${raw}')`);
847
- }
848
- return id;
849
- }
850
- function parsePaymentMethod(raw) {
851
- if (raw === "pix" || raw === "credit_card" || raw === "boleto") return raw;
852
- throw new CliError(
853
- "invalid_input",
854
- `--type must be 'pix', 'credit_card', or 'boleto' (got '${raw}')`
855
- );
856
- }
857
- function parseWebhookEventStatus(raw) {
858
- if (raw === "pending" || raw === "success" || raw === "failed") return raw;
859
- throw new CliError(
860
- "invalid_input",
861
- `--status must be 'pending', 'success', or 'failed' (got '${raw}')`
862
- );
863
- }
864
- function parseScheduledChargeType(raw) {
865
- if (raw === "one_time" || raw === "recurring") return raw;
866
- throw new CliError("invalid_input", `--type must be 'one_time' or 'recurring' (got '${raw}')`);
867
- }
868
- var SCHEDULED_PAYMENT_METHODS = [
869
- "pix",
870
- "boleto",
871
- "card",
872
- "pix_automatic"
873
- ];
874
- function parseCsvList(raw) {
875
- return raw.split(",").map((s) => s.trim()).filter(Boolean);
876
- }
877
- function parseScheduledPaymentMethods(raw) {
878
- const parts = parseCsvList(raw);
879
- if (parts.length === 0) {
880
- throw new CliError(
881
- "invalid_input",
882
- `--methods must list at least one of: ${SCHEDULED_PAYMENT_METHODS.join(", ")}`
883
- );
884
- }
885
- for (const p of parts) {
886
- if (!SCHEDULED_PAYMENT_METHODS.includes(p)) {
887
- throw new CliError(
888
- "invalid_input",
889
- `--methods entries must be one of ${SCHEDULED_PAYMENT_METHODS.join(", ")} (got '${p}')`
890
- );
891
- }
892
- }
893
- return parts;
894
- }
895
- var RECURRENCE_INTERVALS = [
896
- "weekly",
897
- "biweekly",
898
- "monthly",
899
- "bimonthly",
900
- "quarterly",
901
- "biannual",
902
- "yearly"
903
- ];
904
- function parseRecurrenceInterval(raw) {
905
- if (RECURRENCE_INTERVALS.includes(raw)) return raw;
906
- throw new CliError(
907
- "invalid_input",
908
- `--recurrence-interval must be one of ${RECURRENCE_INTERVALS.join(", ")} (got '${raw}')`
909
- );
910
- }
911
- var SCHEDULED_CHARGE_STATUSES = [
912
- "scheduled",
913
- "due_today",
914
- "overdue",
915
- "paid",
916
- "paused",
917
- "canceled",
918
- "trial",
919
- "pending_tokenization",
920
- "recurrence_canceled"
921
- ];
922
- function parseScheduledChargeStatus(raw) {
923
- if (SCHEDULED_CHARGE_STATUSES.includes(raw)) return raw;
924
- throw new CliError(
925
- "invalid_input",
926
- `--status must be one of ${SCHEDULED_CHARGE_STATUSES.join(", ")} (got '${raw}')`
927
- );
928
- }
929
- function parseIntInRange(raw, label, min, max) {
930
- const trimmed = raw.trim();
931
- const n = Number.parseInt(trimmed, 10);
932
- if (!Number.isFinite(n) || String(n) !== trimmed || n < min || n > max) {
933
- throw new CliError(
934
- "invalid_input",
935
- `${label} must be an integer between ${min} and ${max} (got '${raw}')`
936
- );
937
- }
938
- return n;
939
- }
940
- function parseNonNegativeInt(raw, label) {
941
- const trimmed = raw.trim();
942
- const n = Number.parseInt(trimmed, 10);
943
- if (!Number.isFinite(n) || String(n) !== trimmed || n < 0) {
944
- throw new CliError("invalid_input", `${label} must be a non-negative integer (got '${raw}')`);
945
- }
946
- return n;
947
- }
948
- function parseAmountBrl(raw) {
949
- const n = Number(raw);
950
- if (!Number.isFinite(n) || n <= 0) {
951
- throw new CliError(
952
- "invalid_input",
953
- `--amount must be a positive decimal in BRL, e.g. 297.50 (got '${raw}')`
954
- );
955
- }
956
- return n;
957
- }
958
- function parseMetadata(raw) {
959
- let parsed;
960
- try {
961
- parsed = JSON.parse(raw);
962
- } catch {
963
- throw new CliError("invalid_input", `--metadata must be valid JSON (got '${raw}')`);
964
- }
965
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
966
- throw new CliError("invalid_input", `--metadata must be a JSON object`);
967
- }
968
- return parsed;
969
- }
970
-
971
1239
  // src/index.ts
972
1240
  function buildCli() {
973
1241
  const program = new commander.Command();
@@ -979,7 +1247,7 @@ Recipes:
979
1247
 
980
1248
  # 1. Create a product with Pix Autom\xE1tico enabled
981
1249
  garu products create \\
982
- --name "Plano Mensal" --value 4990 \\
1250
+ --name "Plano Mensal" --value 49.90 \\
983
1251
  --pix --credit-card --pix-automatic \\
984
1252
  --subscription --subscription-type monthly
985
1253
 
@@ -1018,16 +1286,19 @@ Recipes:
1018
1286
  );
1019
1287
  });
1020
1288
  const charges = program.command("charges").description("Create, fetch, and refund charges");
1021
- 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(
1289
+ 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(
1290
+ "--status <status>",
1291
+ "filter by status: pending, authorized, paid, failed, expired, canceled, refund_pending, refunded, chargeback"
1292
+ ).option("--search <query>", "search by customer name, email, or document").option("--payment-method <method>", "filter: pix, credit_card, boleto").action(
1022
1293
  async (cmdOpts) => {
1023
1294
  const base = toCommandOptions(program);
1024
1295
  await chargesListCommand({
1025
1296
  ...base,
1026
1297
  page: cmdOpts.page,
1027
1298
  limit: cmdOpts.limit,
1028
- status: cmdOpts.status,
1299
+ status: cmdOpts.status ? parseChargeStatus(cmdOpts.status) : void 0,
1029
1300
  search: cmdOpts.search,
1030
- paymentMethod: cmdOpts.paymentMethod
1301
+ paymentMethod: cmdOpts.paymentMethod ? parseChargePaymentMethodFilter(cmdOpts.paymentMethod) : void 0
1031
1302
  }).catch((err) => printErrorAndExit(err, base));
1032
1303
  }
1033
1304
  );
@@ -1053,20 +1324,21 @@ Recipes:
1053
1324
  idempotencyKey: cmdOpts.idempotencyKey
1054
1325
  }).catch((err) => printErrorAndExit(err, base));
1055
1326
  });
1056
- charges.command("get <id>").description("Fetch a single charge by ID").action(async (id) => {
1327
+ charges.command("get <id>").description("Fetch a single charge by its uuid").action(async (id) => {
1057
1328
  const base = toCommandOptions(program);
1058
- await chargesGetCommand({ ...base, id: parsePositiveIntId(id, "Charge ID") }).catch(
1059
- (err) => printErrorAndExit(err, base)
1060
- );
1329
+ await chargesGetCommand({ ...base, id }).catch((err) => printErrorAndExit(err, base));
1061
1330
  });
1062
- charges.command("refund <id>").description("Refund a charge (full or partial)").option("--amount <centavos>", "partial refund amount in centavos", (v) => parseInt(v, 10)).option("--reason <text>", "optional refund reason").option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (id, cmdOpts) => {
1331
+ charges.command("refund <id>").description("Refund a charge by its uuid (full or partial)").option(
1332
+ "--amount <reais>",
1333
+ "partial refund amount in decimal BRL, e.g. 10.00",
1334
+ (v) => parseNonNegativeBrl(v, "--amount")
1335
+ ).option("--reason <text>", "optional refund reason").action(async (id, cmdOpts) => {
1063
1336
  const base = toCommandOptions(program);
1064
1337
  await chargesRefundCommand({
1065
1338
  ...base,
1066
- id: parsePositiveIntId(id, "Charge ID"),
1339
+ id,
1067
1340
  amount: cmdOpts.amount,
1068
- reason: cmdOpts.reason,
1069
- idempotencyKey: cmdOpts.idempotencyKey
1341
+ reason: cmdOpts.reason
1070
1342
  }).catch((err) => printErrorAndExit(err, base));
1071
1343
  });
1072
1344
  const scheduled = program.command("scheduled-charges").description("Schedule, inspect, and act on future-dated charges");
@@ -1283,11 +1555,149 @@ Recipes:
1283
1555
  id: parsePositiveIntId(id, "Webhook event ID")
1284
1556
  }).catch((err) => printErrorAndExit(err, base));
1285
1557
  });
1558
+ const installmentPlans = program.command("installment-plans").description("Sell a product as boleto parcelado (carn\xEA) and manage its installments");
1559
+ installmentPlans.command("create").description("Sell a product as a carn\xEA (installments of boleto slips)").requiredOption("--product-id <uuid>", "product uuid (must have boleto parcelado enabled)").requiredOption(
1560
+ "--customer-id <n>",
1561
+ "customer id",
1562
+ (v) => parsePositiveIntId(v, "--customer-id")
1563
+ ).requiredOption(
1564
+ "--installments <n>",
1565
+ "2-12 installments",
1566
+ (v) => parseIntInRange(v, "--installments", 2, 12)
1567
+ ).option("--first-due-date <yyyy-mm-dd>", "first installment due date (default: today)").option(
1568
+ "--affiliate-id <n>",
1569
+ "attribute the sale to this affiliate (fixed for the whole plan)",
1570
+ (v) => parsePositiveIntId(v, "--affiliate-id")
1571
+ ).option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (cmdOpts) => {
1572
+ const base = toCommandOptions(program);
1573
+ await installmentPlansCreateCommand({
1574
+ ...base,
1575
+ productId: cmdOpts.productId,
1576
+ customerId: cmdOpts.customerId,
1577
+ installments: cmdOpts.installments,
1578
+ firstDueDate: cmdOpts.firstDueDate,
1579
+ affiliateId: cmdOpts.affiliateId,
1580
+ idempotencyKey: cmdOpts.idempotencyKey
1581
+ }).catch((err) => printErrorAndExit(err, base));
1582
+ });
1583
+ installmentPlans.command("list").description("List installment plans (carn\xEAs) 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(
1584
+ "--customer-id <n>",
1585
+ "filter by customer id",
1586
+ (v) => parsePositiveIntId(v, "--customer-id")
1587
+ ).option("--product-id <uuid>", "filter by product uuid").option(
1588
+ "--status <status>",
1589
+ "filter by status (repeatable)",
1590
+ (val, acc) => acc.concat(val),
1591
+ []
1592
+ ).option("--due-from <yyyy-mm-dd>", "lower bound for the first installment due date").option("--due-to <yyyy-mm-dd>", "upper bound for the first installment due date").action(
1593
+ async (cmdOpts) => {
1594
+ const base = toCommandOptions(program);
1595
+ await installmentPlansListCommand({
1596
+ ...base,
1597
+ page: cmdOpts.page,
1598
+ limit: cmdOpts.limit,
1599
+ customerId: cmdOpts.customerId,
1600
+ productId: cmdOpts.productId,
1601
+ status: cmdOpts.status.length > 0 ? cmdOpts.status.map(parseInstallmentPlanStatus) : void 0,
1602
+ dueFrom: cmdOpts.dueFrom,
1603
+ dueTo: cmdOpts.dueTo
1604
+ }).catch((err) => printErrorAndExit(err, base));
1605
+ }
1606
+ );
1607
+ installmentPlans.command("get <uuid>").description("Fetch a carn\xEA with every installment: due date, status, barcode and PDF").action(async (uuid) => {
1608
+ const base = toCommandOptions(program);
1609
+ await installmentPlansGetCommand({ ...base, uuid }).catch(
1610
+ (err) => printErrorAndExit(err, base)
1611
+ );
1612
+ });
1613
+ installmentPlans.command("reissue <uuid> <number>").description("Issue a segunda via for one installment once its slip has expired").action(async (uuid, number) => {
1614
+ const base = toCommandOptions(program);
1615
+ await installmentPlansReissueCommand({
1616
+ ...base,
1617
+ uuid,
1618
+ number: parsePositiveIntId(number, "installment number")
1619
+ }).catch((err) => printErrorAndExit(err, base));
1620
+ });
1621
+ installmentPlans.command("postpone <uuid> <number>").description("Move one installment to a later due date (its siblings keep theirs)").requiredOption("--new-due-date <yyyy-mm-dd>", "new due date for this installment").action(async (uuid, number, cmdOpts) => {
1622
+ const base = toCommandOptions(program);
1623
+ await installmentPlansPostponeCommand({
1624
+ ...base,
1625
+ uuid,
1626
+ number: parsePositiveIntId(number, "installment number"),
1627
+ newDueDate: cmdOpts.newDueDate
1628
+ }).catch((err) => printErrorAndExit(err, base));
1629
+ });
1630
+ installmentPlans.command("mark-paid <uuid> <number>").description("Record an installment as paid when the buyer paid but the webhook never arrived").action(async (uuid, number) => {
1631
+ const base = toCommandOptions(program);
1632
+ await installmentPlansMarkPaidCommand({
1633
+ ...base,
1634
+ uuid,
1635
+ number: parsePositiveIntId(number, "installment number")
1636
+ }).catch((err) => printErrorAndExit(err, base));
1637
+ });
1638
+ installmentPlans.command("cancel <uuid>").description("Cancel a carn\xEA \u2014 stops emission/reminders and cancels open provider slips").option("--note <text>", "optional note recorded on the plan").action(async (uuid, cmdOpts) => {
1639
+ const base = toCommandOptions(program);
1640
+ await installmentPlansCancelCommand({ ...base, uuid, note: cmdOpts.note }).catch(
1641
+ (err) => printErrorAndExit(err, base)
1642
+ );
1643
+ });
1644
+ installmentPlans.command("request-refund <uuid>").description(
1645
+ "Ask for a carn\xEA to be refunded (Garu records the request; transfer the money yourself, then confirm with `garu refund-requests confirm`)"
1646
+ ).option(
1647
+ "--amount <reais>",
1648
+ "defaults to everything the carn\xEA has collected",
1649
+ (v) => parseNonNegativeBrl(v, "--amount")
1650
+ ).option("--reason <text>", "optional reason").action(async (uuid, cmdOpts) => {
1651
+ const base = toCommandOptions(program);
1652
+ await installmentPlansRequestRefundCommand({
1653
+ ...base,
1654
+ uuid,
1655
+ amount: cmdOpts.amount,
1656
+ reason: cmdOpts.reason
1657
+ }).catch((err) => printErrorAndExit(err, base));
1658
+ });
1659
+ const refundRequests = program.command("refund-requests").description("Inspect and resolve refund requests (carn\xEA and Pix/boleto)");
1660
+ refundRequests.command("list").description("List refund requests 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("--plan-id <uuid>", "filter by carn\xEA uuid").option("--charge-id <uuid>", "filter by charge uuid (Pix/boleto)").option(
1661
+ "--status <status>",
1662
+ "filter by status (repeatable): pending, confirmed, rejected",
1663
+ (val, acc) => acc.concat(val),
1664
+ []
1665
+ ).action(
1666
+ async (cmdOpts) => {
1667
+ const base = toCommandOptions(program);
1668
+ await refundRequestsListCommand({
1669
+ ...base,
1670
+ page: cmdOpts.page,
1671
+ limit: cmdOpts.limit,
1672
+ planId: cmdOpts.planId,
1673
+ chargeId: cmdOpts.chargeId,
1674
+ status: cmdOpts.status.length > 0 ? cmdOpts.status.map(parseRefundRequestStatus) : void 0
1675
+ }).catch((err) => printErrorAndExit(err, base));
1676
+ }
1677
+ );
1678
+ refundRequests.command("get <uuid>").description("Fetch a single refund request").action(async (uuid) => {
1679
+ const base = toCommandOptions(program);
1680
+ await refundRequestsGetCommand({ ...base, uuid }).catch(
1681
+ (err) => printErrorAndExit(err, base)
1682
+ );
1683
+ });
1684
+ refundRequests.command("confirm <uuid>").description("Record that you transferred the money back \u2014 call this AFTER the transfer").option("--note <text>", "optional note, e.g. a bank reference").action(async (uuid, cmdOpts) => {
1685
+ const base = toCommandOptions(program);
1686
+ await refundRequestsConfirmCommand({ ...base, uuid, note: cmdOpts.note }).catch(
1687
+ (err) => printErrorAndExit(err, base)
1688
+ );
1689
+ });
1690
+ refundRequests.command("reject <uuid>").description("Decline the request \u2014 the carn\xEA or charge is left untouched").option("--note <text>", "optional note explaining the rejection").action(async (uuid, cmdOpts) => {
1691
+ const base = toCommandOptions(program);
1692
+ await refundRequestsRejectCommand({ ...base, uuid, note: cmdOpts.note }).catch(
1693
+ (err) => printErrorAndExit(err, base)
1694
+ );
1695
+ });
1286
1696
  const products = program.command("products").description("Create and update products");
1287
1697
  products.command("create").description("Create a product").requiredOption("--name <name>", "product name").option(
1288
- "--value <centavos>",
1289
- "price in centavos (BRL \xD7 100)",
1290
- (v) => parseNonNegativeInt(v, "--value")
1698
+ "--value <reais>",
1699
+ "price in reais / decimal BRL (e.g. 49.90)",
1700
+ (v) => parseNonNegativeBrl(v, "--value")
1291
1701
  ).option("--description <text>", "product description").option("--image <url>", "HTTPS URL of the product cover image").option("--tags <list>", "comma-separated tags", parseCsvList).option("--pix", "accept PIX").option("--no-pix", "do not accept PIX").option("--boleto", "accept boleto").option("--no-boleto", "do not accept boleto").option("--credit-card", "accept credit card").option("--no-credit-card", "do not accept credit card").option("--pix-automatic", "expose Pix Autom\xE1tico on the subscription checkout").option("--no-pix-automatic", "do not expose Pix Autom\xE1tico").option(
1292
1702
  "--installments <n>",
1293
1703
  "max credit-card installments",
@@ -1316,9 +1726,9 @@ Recipes:
1316
1726
  products.command("update <id>").description(
1317
1727
  "Update a product (partial \u2014 only the flags you pass change). <id> is the numeric id or UUID"
1318
1728
  ).option("--name <name>", "product name").option(
1319
- "--value <centavos>",
1320
- "price in centavos (BRL \xD7 100)",
1321
- (v) => parseNonNegativeInt(v, "--value")
1729
+ "--value <reais>",
1730
+ "price in reais / decimal BRL (e.g. 49.90)",
1731
+ (v) => parseNonNegativeBrl(v, "--value")
1322
1732
  ).option("--description <text>", "product description").option("--image <url>", "HTTPS URL of the product cover image").option("--tags <list>", "comma-separated tags", parseCsvList).option("--pix", "accept PIX").option("--no-pix", "do not accept PIX").option("--boleto", "accept boleto").option("--no-boleto", "do not accept boleto").option("--credit-card", "accept credit card").option("--no-credit-card", "do not accept credit card").option("--pix-automatic", "expose Pix Autom\xE1tico on the subscription checkout").option("--no-pix-automatic", "do not expose Pix Autom\xE1tico").option(
1323
1733
  "--installments <n>",
1324
1734
  "max credit-card installments",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.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.15.0",
49
+ "@garuhq/node": "1.1.0",
50
50
  "@inquirer/prompts": "8.4.1",
51
51
  "commander": "12.0.0",
52
52
  "picocolors": "1.0.0",