@garuhq/cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,82 @@
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.3.0] — 2026-04-28
7
+
8
+ Rolls up the unpublished 0.2.0 work plus a new update notifier into a single
9
+ release.
10
+
11
+ ### Added
12
+
13
+ - `charges list` command — paginated listing of the authenticated seller's
14
+ charges with status, search, and payment-method filters. (Originally
15
+ shipped to git as 0.2.0 but never published to npm.)
16
+ - Startup version-update notifier. The CLI now checks the npm registry once
17
+ every 24h (in a detached background process) and prints a banner at process
18
+ exit when a newer `@garuhq/cli` is available, suggesting how to update.
19
+ Silent on offline / network failures.
20
+
21
+ ### Changed
22
+
23
+ - `@garuhq/node` SDK bumped to 0.2.0 (also from the unpublished 0.2.0 batch).
24
+
25
+ ## [0.1.2] — 2026-04-08
26
+
27
+ Post-review batch — addresses all findings from an internal code review.
28
+
29
+ ### Security
30
+
31
+ - **`install.sh` now verifies binaries against `SHA256SUMS.txt`.** The installer
32
+ downloads the checksum file from the same release, grep's the expected hash
33
+ for the target asset, and fails the install if the binary has been tampered
34
+ with. Supports both `sha256sum` (Linux) and `shasum -a 256` (macOS).
35
+ - **Credentials file path now uses `node:path`'s `dirname`** instead of a
36
+ `lastIndexOf('/')` slice, fixing `garu login` on Windows (`npm i -g @garuhq/cli`).
37
+ - Upgrade dev dependencies to clear `npm audit` findings:
38
+ - `vitest` 1.5.0 → 4.1.3 (closes critical vitest RCE advisory)
39
+ - `tsup` 8.0.2 → 8.5.1, `tsx` 4.7.2 → 4.21.0
40
+ - `@inquirer/prompts` 5.0.2 → 8.4.1 (clears the `tmp` symlink chain)
41
+
42
+ ### Fixed
43
+
44
+ - **`garu login` no longer claims to validate the API key.** The prompt now
45
+ says "Checking connectivity to Garu..." instead of "Validating key with
46
+ Garu..." — a key that passes the `sk_(live|test)_...` regex will still be
47
+ saved even if the backend would reject it. A real authenticated probe lands
48
+ once the backend exposes `GET /api/v1/me`.
49
+ - **`garu logout --profile <name>` now deletes the credentials file** when the
50
+ removed profile was the last one, instead of leaving an orphaned
51
+ `activeProfile` pointing at nothing.
52
+ - **SDK errors are now classified via `instanceof GaruError`** instead of
53
+ `.name.startsWith('Garu')` duck-typing.
54
+
55
+ ### Changed
56
+
57
+ - Removed non-null assertions on credit-card fields in `charges create`;
58
+ replaced with a type-guard assertion so the TypeScript narrowing is
59
+ compile-time enforced.
60
+ - Collapsed `globalsToOutput` + `globalFlagsToCommandOptions` into one
61
+ `toCommandOptions` helper. `src/index.ts` is ~25 lines shorter.
62
+ - Removed unused `cliUserAgent()` dead code from `src/lib/client.ts`.
63
+
64
+ ### Infrastructure
65
+
66
+ - **All third-party GitHub Actions are now SHA-pinned** in both `ci.yml` and
67
+ `release.yml`: `actions/checkout`, `actions/setup-node`,
68
+ `actions/upload-artifact`, `actions/download-artifact`, `oven-sh/setup-bun`,
69
+ `ludeeus/action-shellcheck`. Human-readable version as trailing comment.
70
+ - **Release workflow asserts `package.json .version == $TAG_NAME`** before
71
+ publishing to npm. Mismatched tags fail fast with a clear error instead of
72
+ hitting `npm publish` with a stale version.
73
+
74
+ ### Tests
75
+
76
+ - Added 6 new tests for `logoutCommand` covering: removing the last profile
77
+ deletes the file, removing a non-active profile preserves the active one,
78
+ removing the active profile elects a remaining profile, and the not-found
79
+ error path.
80
+ - Total: 42 tests across 6 files (previously 36 across 5).
81
+
6
82
  ## [0.1.1] — 2026-04-08
7
83
 
8
84
  ### Fixed
package/README.md CHANGED
@@ -1,57 +1,82 @@
1
- # garu-cli
1
+ # Garu CLI
2
2
 
3
- Official command-line interface for the [Garu](https://garu.com.br) payment gateway.
3
+ The official CLI for [Garu](https://garu.com.br), the Brazilian payment gateway.
4
+
5
+ Built for developers, AI agents, and CI/CD pipelines.
4
6
 
5
7
  ```
6
- $ garu charges create --type pix --product-id prod-uuid \
7
- --customer-name "Maria Silva" \
8
- --customer-email maria@exemplo.com.br \
9
- --customer-document 12345678909 \
10
- --customer-phone 11987654321
11
- Charge 4472
12
- status: pending
13
- amount: 4990
14
- method: pix
15
- date: 2026-04-08T18:30:00.000Z
8
+ ██████╗ █████╗ ██████╗ ██╗ ██╗
9
+ ██╔════╝ ██╔══██╗██╔══██╗██║ ██║
10
+ ██║ ███╗███████║██████╔╝██║ ██║
11
+ ██║ ██║██╔══██║██╔══██╗██║ ██║
12
+ ╚██████╔╝██║ ██║██║ ██║╚██████╔╝
13
+ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
16
14
  ```
17
15
 
18
16
  ## Install
19
17
 
20
- ```bash
21
- # curl | bash (Linux, macOS)
18
+ ### cURL (Linux, macOS)
19
+
20
+ ```sh
22
21
  curl -fsSL https://raw.githubusercontent.com/Garu-Pagamentos/garu-cli/main/install.sh | bash
22
+ ```
23
23
 
24
- # npm (all platforms, including Windows)
24
+ ### Node.js (all platforms)
25
+
26
+ ```sh
25
27
  npm install -g @garuhq/cli
26
28
  ```
27
29
 
28
- > The pretty URL `curl -fsSL https://garu.com.br/install.sh | bash` is coming —
29
- > waiting on the marketing site rebuild (SPEC W1). Use the GitHub raw URL for now.
30
-
31
30
  Verify the install:
32
31
 
33
32
  ```bash
34
33
  garu doctor
35
34
  ```
36
35
 
36
+ ## Quickstart
37
+
38
+ ```bash
39
+ # Authenticate
40
+ garu login
41
+
42
+ # Create a PIX charge
43
+ garu charges create --type pix --product-id prod-uuid \
44
+ --customer-name "Maria Silva" \
45
+ --customer-email maria@exemplo.com.br \
46
+ --customer-document 12345678909 \
47
+ --customer-phone 11987654321
48
+
49
+ # List recent charges
50
+ garu charges list
51
+
52
+ # Check your environment
53
+ garu doctor
54
+ ```
55
+
56
+ ---
57
+
37
58
  ## Authentication
38
59
 
39
- The CLI resolves your API key from the first of:
60
+ The CLI resolves your API key using the following priority chain:
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` |
40
67
 
41
- 1. `--api-key <key>` flag
42
- 2. `GARU_API_KEY` environment variable
43
- 3. `~/.config/garu/credentials.json` (created by `garu login`, mode `0600`)
68
+ If no key is found from any source, the CLI errors with code `auth_error`.
44
69
 
45
- ### Interactive
70
+ ### Interactive mode (default in terminals)
46
71
 
47
72
  ```bash
48
73
  $ garu login
49
74
  ? Paste your Garu API key (sk_live_... or sk_test_...) ****************
50
- Validating key with Garu...
51
- Saved profile 'default' to ~/.config/garu/credentials.json
75
+ -> Validating key with Garu...
76
+ Saved profile 'default' to ~/.config/garu/credentials.json
52
77
  ```
53
78
 
54
- ### Scripted (CI)
79
+ ### Non-interactive mode (CI, pipes, scripts)
55
80
 
56
81
  ```bash
57
82
  export GARU_API_KEY=sk_live_...
@@ -60,54 +85,172 @@ garu charges create --type pix ...
60
85
 
61
86
  ### Multi-profile
62
87
 
88
+ Switch between test and production without logging in and out:
89
+
63
90
  ```bash
64
91
  garu login --profile test --api-key sk_test_...
65
92
  garu login --profile live --api-key sk_live_...
66
93
  garu auth switch live
67
94
  ```
68
95
 
96
+ You can also use the global `--profile` (or `-p`) flag on any command:
97
+
98
+ ```bash
99
+ garu charges list --profile production
100
+ ```
101
+
102
+ ---
103
+
69
104
  ## Commands
70
105
 
71
- | Command | Description |
72
- | -------------------------------- | ------------------------------------------- |
73
- | `garu login` | Paste an API key, validate, save |
74
- | `garu logout [--profile <name>]` | Remove saved credentials |
75
- | `garu auth switch <profile>` | Set the active credentials profile |
76
- | `garu charges create` | Create a PIX, credit-card, or boleto charge |
77
- | `garu charges get <id>` | Fetch a single charge |
78
- | `garu charges refund <id>` | Refund a charge (full or partial) |
79
- | `garu doctor` | Environment diagnostic |
80
- | `garu --version` | Print the CLI version |
81
- | `garu --help` | Top-level help |
106
+ ### `garu login`
107
+
108
+ Authenticate by storing your API key locally. The key is validated against the Garu API before being saved.
109
+
110
+ ```bash
111
+ garu login
112
+ garu login --api-key sk_live_... --profile production
113
+ ```
114
+
115
+ | Flag | Description |
116
+ | ---------------------- | ---------------------------------------- |
117
+ | `--api-key <key>` | Pre-supply the key instead of prompting |
118
+ | `-p, --profile <name>` | Profile name to store under (default: `default`) |
82
119
 
83
- See `garu <command> --help` for per-command flags.
120
+ On success, credentials are saved to `~/.config/garu/credentials.json` with `0600` permissions (owner read/write only).
84
121
 
85
- ## Output modes
122
+ ---
86
123
 
87
- The CLI auto-detects whether it's running interactively:
124
+ ### `garu logout`
88
125
 
89
- - **TTY (pretty):** colored output, status lines on stderr, formatted result on stdout.
90
- - **Pipe / CI / `--json`:** strict JSON on stdout, errors as `{"error":{"code","message"}}` on stdout with exit code 1. Nothing on stderr in this mode.
126
+ Remove saved credentials.
91
127
 
92
128
  ```bash
93
- # Pipe-safe
94
- garu charges get 4472 | jq '.status'
129
+ garu logout
130
+ garu logout --profile test
131
+ ```
132
+
133
+ | Flag | Description |
134
+ | ---------------------- | ---------------------------------------------------- |
135
+ | `-p, --profile <name>` | Only remove this profile instead of the whole file |
136
+
137
+ ---
138
+
139
+ ### `garu auth switch`
140
+
141
+ Set the active credentials profile.
142
+
143
+ ```bash
144
+ garu auth switch live
145
+ ```
146
+
147
+ ---
148
+
149
+ ### `garu charges list`
150
+
151
+ List charges with pagination and filters.
152
+
153
+ ```bash
154
+ garu charges list
155
+ garu charges list --status paid --limit 50
156
+ garu charges list --search "Maria" --payment-method pix
95
157
  ```
96
158
 
97
- Global flags:
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
+
167
+ ---
168
+
169
+ ### `garu charges create`
98
170
 
99
- - `--api-key <key>` override auth chain
100
- - `-p, --profile <name>` — use a specific credentials profile
101
- - `--json` — force JSON output
102
- - `-q, --quiet` — suppress status lines
171
+ Create a PIX, credit card, or boleto charge.
103
172
 
104
- ## `garu doctor`
173
+ ```bash
174
+ # PIX charge
175
+ garu charges create --type pix --product-id prod-uuid \
176
+ --customer-name "Maria Silva" \
177
+ --customer-email maria@exemplo.com.br \
178
+ --customer-document 12345678909 \
179
+ --customer-phone 11987654321
180
+
181
+ # Credit card charge
182
+ garu charges create --type credit_card --product-id prod-uuid \
183
+ --customer-name "Maria Silva" \
184
+ --customer-email maria@exemplo.com.br \
185
+ --customer-document 12345678909 \
186
+ --customer-phone 11987654321 \
187
+ --card-number 4111111111111111 \
188
+ --card-cvv 123 \
189
+ --card-expiration 2030-12 \
190
+ --card-holder "MARIA SILVA" \
191
+ --installments 3
192
+ ```
105
193
 
106
- Prints a structured report of your environment:
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
+
210
+ ---
211
+
212
+ ### `garu charges get`
213
+
214
+ Fetch a single charge by ID.
215
+
216
+ ```bash
217
+ garu charges get 4472
218
+ garu charges get 4472 --json | jq '.status'
219
+ ```
220
+
221
+ ---
222
+
223
+ ### `garu charges refund`
224
+
225
+ Refund a charge (full or partial).
226
+
227
+ ```bash
228
+ # Full refund
229
+ garu charges refund 4472
230
+
231
+ # Partial refund (1000 centavos = R$10.00)
232
+ garu charges refund 4472 --amount 1000 --reason "customer_request"
233
+ ```
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) |
240
+
241
+ ---
242
+
243
+ ### `garu doctor`
244
+
245
+ Run environment diagnostics. Verifies your CLI version, API connectivity, credentials, and detects AI agent integrations.
246
+
247
+ ```bash
248
+ garu doctor
249
+ ```
107
250
 
108
251
  ```json
109
252
  {
110
- "cli": { "version": "0.1.0" },
253
+ "cli": { "version": "0.2.0" },
111
254
  "api": { "reachable": true, "url": "https://garu.com.br", "version": "1.3.2" },
112
255
  "credentials": { "path": "...", "source": "file", "profile": "default", "fileMode": "0600" },
113
256
  "agents": {
@@ -121,6 +264,94 @@ Prints a structured report of your environment:
121
264
  }
122
265
  ```
123
266
 
267
+ ---
268
+
269
+ ## Global options
270
+
271
+ These flags work on every command:
272
+
273
+ ```bash
274
+ garu [global options] <command> [command options]
275
+ ```
276
+
277
+ | Flag | Description |
278
+ | ---------------------- | ------------------------------------------------------------ |
279
+ | `--api-key <key>` | Override API key for this invocation (takes highest priority) |
280
+ | `-p, --profile <name>` | Credentials profile to use |
281
+ | `--json` | Force JSON output even in interactive terminals |
282
+ | `-q, --quiet` | Suppress status output; only print results and errors |
283
+ | `-v, --version` | Print version and exit |
284
+ | `--help` | Show help text |
285
+
286
+ ---
287
+
288
+ ## Output behavior
289
+
290
+ The CLI has two output modes:
291
+
292
+ | Mode | When | Stdout | Stderr |
293
+ | --------------- | ---------------------- | -------------- | ---------------- |
294
+ | **Interactive** | Terminal (TTY) | Formatted text | Status lines |
295
+ | **Machine** | Piped, CI, or `--json` | JSON | Nothing |
296
+
297
+ Switching is automatic -- pipe to another command and JSON output activates:
298
+
299
+ ```bash
300
+ garu charges get 4472 | jq '.status'
301
+ garu charges list --status paid | jq '.data[].id'
302
+ ```
303
+
304
+ ### Error output
305
+
306
+ Errors exit with code `1` and output structured JSON to stdout:
307
+
308
+ ```json
309
+ { "error": { "code": "auth_error", "message": "No API key found" } }
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Agent & CI/CD usage
315
+
316
+ ### CI/CD
317
+
318
+ Set `GARU_API_KEY` as an environment variable -- no `garu login` needed:
319
+
320
+ ```yaml
321
+ # GitHub Actions
322
+ env:
323
+ GARU_API_KEY: ${{ secrets.GARU_API_KEY }}
324
+ steps:
325
+ - run: garu charges list --status pending --json
326
+ ```
327
+
328
+ ### AI agents
329
+
330
+ Agents calling the CLI as a subprocess automatically get JSON output (non-TTY detection). The contract:
331
+
332
+ - **Input:** All required flags must be provided (no interactive prompts)
333
+ - **Output:** JSON to stdout, nothing to stderr
334
+ - **Exit code:** `0` success, `1` error
335
+ - **Errors:** Always include `code` and `message` fields
336
+
337
+ ---
338
+
339
+ ## Configuration
340
+
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 |
346
+
347
+ ---
348
+
349
+ ## Security
350
+
351
+ See [SECURITY.md](SECURITY.md) for our vulnerability disclosure policy.
352
+
353
+ > **Note on credit card flags:** The `--card-number`, `--card-cvv`, and `--card-expiration` flags pass data via command-line arguments, which may be visible in shell history and process listings. For production card processing, use the [`@garuhq/node`](https://www.npmjs.com/package/@garuhq/node) SDK directly.
354
+
124
355
  ## License
125
356
 
126
- MIT.
357
+ MIT
package/dist/index.cjs CHANGED
@@ -4,10 +4,9 @@ var commander = require('commander');
4
4
  var os = require('os');
5
5
  var path = require('path');
6
6
  var promises = require('fs/promises');
7
- var pc = require('picocolors');
8
7
  var node = require('@garuhq/node');
8
+ var pc = require('picocolors');
9
9
  var fs = require('fs');
10
- var prompts = require('@inquirer/prompts');
11
10
 
12
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
12
 
@@ -16,9 +15,7 @@ var pc__default = /*#__PURE__*/_interopDefault(pc);
16
15
  // src/index.ts
17
16
 
18
17
  // src/version.ts
19
- var CLI_VERSION = "0.1.1";
20
-
21
- // src/lib/errors.ts
18
+ var CLI_VERSION = "0.3.0";
22
19
  var CliError = class extends Error {
23
20
  code;
24
21
  exitCode;
@@ -30,16 +27,11 @@ var CliError = class extends Error {
30
27
  }
31
28
  };
32
29
  function toCliError(err) {
33
- if (err instanceof CliError)
34
- return err;
35
- if (err && typeof err === "object" && "name" in err && "code" in err) {
36
- const e = err;
37
- if (typeof e.name === "string" && e.name.startsWith("Garu")) {
38
- return new CliError(mapSdkCodeToCliCode(e.code), e.message ?? "Request failed", 1);
39
- }
30
+ if (err instanceof CliError) return err;
31
+ if (err instanceof node.GaruError) {
32
+ return new CliError(mapSdkCodeToCliCode(err.code), err.message, 1);
40
33
  }
41
- if (err instanceof Error)
42
- return new CliError("unknown_error", err.message);
34
+ if (err instanceof Error) return new CliError("unknown_error", err.message);
43
35
  return new CliError("unknown_error", String(err));
44
36
  }
45
37
  function mapSdkCodeToCliCode(sdkCode) {
@@ -71,8 +63,7 @@ var DEFAULT_FILE = {
71
63
  profiles: {}
72
64
  };
73
65
  function credentialsPath(env = process.env) {
74
- if (env.GARU_CREDENTIALS_PATH)
75
- return env.GARU_CREDENTIALS_PATH;
66
+ if (env.GARU_CREDENTIALS_PATH) return env.GARU_CREDENTIALS_PATH;
76
67
  const base = env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
77
68
  return path.join(base, "garu", "credentials.json");
78
69
  }
@@ -93,18 +84,16 @@ async function loadCredentials(env = process.env) {
93
84
  }
94
85
  }
95
86
  async function saveCredentials(file, env = process.env) {
96
- const path = credentialsPath(env);
97
- const dir = path.slice(0, path.lastIndexOf("/"));
98
- await promises.mkdir(dir, { recursive: true, mode: 448 });
99
- await promises.writeFile(path, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
87
+ const path$1 = credentialsPath(env);
88
+ await promises.mkdir(path.dirname(path$1), { recursive: true, mode: 448 });
89
+ await promises.writeFile(path$1, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
100
90
  }
101
91
  async function deleteCredentials(env = process.env) {
102
92
  const path = credentialsPath(env);
103
93
  try {
104
94
  await promises.rm(path);
105
95
  } catch (err) {
106
- if (err.code !== "ENOENT")
107
- throw err;
96
+ if (err.code !== "ENOENT") throw err;
108
97
  }
109
98
  }
110
99
  function upsertProfile(file, name, profile) {
@@ -123,8 +112,7 @@ async function credentialsFileMode(env = process.env) {
123
112
  }
124
113
  }
125
114
  function normalize(raw) {
126
- if (!raw || typeof raw !== "object")
127
- return { ...DEFAULT_FILE };
115
+ if (!raw || typeof raw !== "object") return { ...DEFAULT_FILE };
128
116
  const obj = raw;
129
117
  return {
130
118
  version: 1,
@@ -133,8 +121,7 @@ function normalize(raw) {
133
121
  };
134
122
  }
135
123
  function resolveMode(opts = {}, stdout = process.stdout) {
136
- if (opts.mode)
137
- return opts.mode;
124
+ if (opts.mode) return opts.mode;
138
125
  return stdout.isTTY ? "pretty" : "json";
139
126
  }
140
127
  function printResult(value, opts = {}) {
@@ -153,18 +140,14 @@ function printResult(value, opts = {}) {
153
140
  `);
154
141
  }
155
142
  function printStatus(message, opts = {}) {
156
- if (opts.quiet)
157
- return;
158
- if (resolveMode(opts) === "json")
159
- return;
143
+ if (opts.quiet) return;
144
+ if (resolveMode(opts) === "json") return;
160
145
  process.stderr.write(`${pc__default.default.dim("\u2192")} ${message}
161
146
  `);
162
147
  }
163
148
  function printSuccess(message, opts = {}) {
164
- if (opts.quiet)
165
- return;
166
- if (resolveMode(opts) === "json")
167
- return;
149
+ if (opts.quiet) return;
150
+ if (resolveMode(opts) === "json") return;
168
151
  process.stderr.write(`${pc__default.default.green("\u2713")} ${message}
169
152
  `);
170
153
  }
@@ -223,8 +206,7 @@ async function resolveAuthOptional(opts = {}) {
223
206
  try {
224
207
  return await resolveAuth(opts);
225
208
  } catch (err) {
226
- if (err instanceof CliError && err.code === "auth_error")
227
- return null;
209
+ if (err instanceof CliError && err.code === "auth_error") return null;
228
210
  throw err;
229
211
  }
230
212
  }
@@ -237,8 +219,7 @@ function createGaruClient(opts) {
237
219
 
238
220
  // src/commands/charges.ts
239
221
  async function getClient(opts) {
240
- if (opts.garu)
241
- return opts.garu;
222
+ if (opts.garu) return opts.garu;
242
223
  const auth = await resolveAuth({
243
224
  ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
244
225
  ...opts.profile !== void 0 ? { profile: opts.profile } : {}
@@ -248,8 +229,17 @@ async function getClient(opts) {
248
229
  ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
249
230
  });
250
231
  }
232
+ function assertCardFieldsPresent(opts) {
233
+ const missing = [];
234
+ if (!opts.cardNumber) missing.push("--card-number");
235
+ if (!opts.cardCvv) missing.push("--card-cvv");
236
+ if (!opts.cardExpiration) missing.push("--card-expiration");
237
+ if (!opts.cardHolder) missing.push("--card-holder");
238
+ if (missing.length) {
239
+ throw new CliError("invalid_input", `Credit-card charges require: ${missing.join(", ")}`);
240
+ }
241
+ }
251
242
  async function chargesCreateCommand(opts) {
252
- validateCreate(opts);
253
243
  const garu = await getClient(opts);
254
244
  const customer = {
255
245
  name: opts.customerName,
@@ -257,13 +247,18 @@ async function chargesCreateCommand(opts) {
257
247
  document: opts.customerDocument,
258
248
  phone: opts.customerPhone
259
249
  };
260
- const charge = await garu.charges.create({
250
+ const base = {
261
251
  productId: opts.productId,
262
252
  paymentMethod: opts.type,
263
253
  customer,
264
- ...opts.additionalInfo !== void 0 ? { additionalInfo: opts.additionalInfo } : {},
265
- ...opts.idempotencyKey !== void 0 ? { idempotencyKey: opts.idempotencyKey } : {},
266
- ...opts.type === "credit_card" && opts.cardNumber ? {
254
+ additionalInfo: opts.additionalInfo,
255
+ idempotencyKey: opts.idempotencyKey
256
+ };
257
+ let charge;
258
+ if (opts.type === "credit_card") {
259
+ assertCardFieldsPresent(opts);
260
+ charge = await garu.charges.create({
261
+ ...base,
267
262
  cardInfo: {
268
263
  cardNumber: opts.cardNumber,
269
264
  cvv: opts.cardCvv,
@@ -271,8 +266,10 @@ async function chargesCreateCommand(opts) {
271
266
  holderName: opts.cardHolder,
272
267
  installments: opts.installments ?? 1
273
268
  }
274
- } : {}
275
- });
269
+ });
270
+ } else {
271
+ charge = await garu.charges.create(base);
272
+ }
276
273
  printResult(charge, { ...opts, prettyPrint: prettyCharge });
277
274
  return charge;
278
275
  }
@@ -285,31 +282,34 @@ async function chargesGetCommand(opts) {
285
282
  async function chargesRefundCommand(opts) {
286
283
  const garu = await getClient(opts);
287
284
  const params = {};
288
- if (opts.amount !== void 0)
289
- params.amount = opts.amount;
290
- if (opts.reason !== void 0)
291
- params.reason = opts.reason;
292
- if (opts.idempotencyKey !== void 0)
293
- params.idempotencyKey = opts.idempotencyKey;
285
+ if (opts.amount !== void 0) params.amount = opts.amount;
286
+ if (opts.reason !== void 0) params.reason = opts.reason;
287
+ if (opts.idempotencyKey !== void 0) params.idempotencyKey = opts.idempotencyKey;
294
288
  const charge = await garu.charges.refund(opts.id, params);
295
289
  printResult(charge, { ...opts, prettyPrint: prettyCharge });
296
290
  return charge;
297
291
  }
298
- function validateCreate(opts) {
299
- if (opts.type === "credit_card") {
300
- const missing = [];
301
- if (!opts.cardNumber)
302
- missing.push("--card-number");
303
- if (!opts.cardCvv)
304
- missing.push("--card-cvv");
305
- if (!opts.cardExpiration)
306
- missing.push("--card-expiration");
307
- if (!opts.cardHolder)
308
- missing.push("--card-holder");
309
- if (missing.length) {
310
- throw new CliError("invalid_input", `Credit-card charges require: ${missing.join(", ")}`);
311
- }
292
+ async function chargesListCommand(opts) {
293
+ const garu = await getClient(opts);
294
+ const params = {};
295
+ if (opts.page !== void 0) params.page = opts.page;
296
+ if (opts.limit !== void 0) params.limit = opts.limit;
297
+ if (opts.status !== void 0) params.status = opts.status;
298
+ if (opts.search !== void 0) params.search = opts.search;
299
+ if (opts.paymentMethod !== void 0) params.paymentMethod = opts.paymentMethod;
300
+ const result = await garu.charges.list(params);
301
+ printResult(result, { ...opts, prettyPrint: prettyChargeList });
302
+ return result;
303
+ }
304
+ function prettyChargeList(list) {
305
+ if (list.data.length === 0) {
306
+ return `No charges found (page ${list.meta.page}/${list.meta.totalPages || 1})`;
312
307
  }
308
+ const header = `Charges (page ${list.meta.page}/${list.meta.totalPages || "?"}, ${list.meta.total} total)`;
309
+ const rows = list.data.map(
310
+ (c) => ` ${String(c.id).padStart(6)} ${String(c.status).padEnd(14)} ${c.paymentMethodId?.padEnd(10) ?? ""} ${c.date}`
311
+ );
312
+ return [header, ...rows].join("\n");
313
313
  }
314
314
  function prettyCharge(charge) {
315
315
  const lines = [
@@ -319,8 +319,7 @@ function prettyCharge(charge) {
319
319
  ` method: ${charge.paymentMethodId}`,
320
320
  ` date: ${charge.date}`
321
321
  ];
322
- if (charge.deadline)
323
- lines.push(` deadline: ${charge.deadline}`);
322
+ if (charge.deadline) lines.push(` deadline: ${charge.deadline}`);
324
323
  return lines.join("\n");
325
324
  }
326
325
  async function doctorCommand(opts = {}) {
@@ -349,8 +348,7 @@ async function reportCredentials(opts) {
349
348
  return { path, source: "none" };
350
349
  }
351
350
  const out = { path, source: resolved.source };
352
- if (resolved.profile)
353
- out.profile = resolved.profile;
351
+ if (resolved.profile) out.profile = resolved.profile;
354
352
  if (resolved.source === "file") {
355
353
  const mode = await credentialsFileMode();
356
354
  if (mode !== null) {
@@ -418,20 +416,19 @@ function prettyDoctor(r) {
418
416
  }
419
417
  async function loginCommand(opts = {}) {
420
418
  const profile = opts.profile ?? "default";
421
- const apiKey = opts.apiKey ?? await prompts.password({
422
- message: "Paste your Garu API key (sk_live_... or sk_test_...)",
423
- mask: "*"
424
- }).catch(() => {
419
+ const apiKey = opts.apiKey ?? await import('@inquirer/prompts').then(
420
+ ({ password }) => password({
421
+ message: "Paste your Garu API key (sk_live_... or sk_test_...)",
422
+ mask: "*"
423
+ })
424
+ ).catch(() => {
425
425
  throw new CliError("user_cancelled", "Login cancelled.");
426
426
  });
427
427
  if (!apiKey || !/^sk_(live|test)_[A-Za-z0-9_]+$/.test(apiKey)) {
428
428
  throw new CliError("invalid_input", "API key must look like `sk_live_...` or `sk_test_...`.");
429
429
  }
430
- printStatus("Validating key with Garu...", opts);
431
- const garu = new node.Garu({
432
- apiKey,
433
- ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
434
- });
430
+ printStatus("Checking connectivity to Garu...", opts);
431
+ const garu = new node.Garu({ apiKey, baseUrl: opts.baseUrl });
435
432
  await garu.meta.get();
436
433
  const file = await loadCredentials();
437
434
  const updated = upsertProfile(file, profile, { apiKey });
@@ -452,7 +449,12 @@ async function logoutCommand(opts = {}) {
452
449
  throw new CliError("not_found", `Profile '${opts.profile}' not found.`);
453
450
  }
454
451
  const { [opts.profile]: _removed, ...rest } = file.profiles;
455
- const nextActive = file.activeProfile === opts.profile ? Object.keys(rest)[0] ?? "default" : file.activeProfile;
452
+ if (Object.keys(rest).length === 0) {
453
+ await deleteCredentials();
454
+ printSuccess(`Profile '${opts.profile}' removed (credentials file deleted).`, opts);
455
+ return { cleared: opts.profile };
456
+ }
457
+ const nextActive = file.activeProfile === opts.profile ? Object.keys(rest)[0] : file.activeProfile;
456
458
  await saveCredentials({ ...file, activeProfile: nextActive, profiles: rest });
457
459
  printSuccess(`Profile '${opts.profile}' removed.`, opts);
458
460
  return { cleared: opts.profile };
@@ -461,104 +463,98 @@ async function logoutCommand(opts = {}) {
461
463
  // src/index.ts
462
464
  function buildCli() {
463
465
  const program = new commander.Command();
464
- program.name("garu").description("Command-line interface for the Garu payment gateway.").version(getVersion(), "-v, --version").addOption(new commander.Option("--api-key <key>", "Garu API key (overrides env and credentials file)")).addOption(new commander.Option("-p, --profile <name>", "credentials profile name")).addOption(new commander.Option("--json", "emit strict JSON on stdout (forced in pipes)")).addOption(new commander.Option("-q, --quiet", "suppress status output; only print results and errors")).showHelpAfterError();
466
+ program.name("garu").description("Command-line interface for the Garu payment gateway.").version(CLI_VERSION, "-v, --version").addOption(new commander.Option("--api-key <key>", "Garu API key (overrides env and credentials file)")).addOption(new commander.Option("-p, --profile <name>", "credentials profile name")).addOption(new commander.Option("--json", "emit strict JSON on stdout (forced in pipes)")).addOption(new commander.Option("-q, --quiet", "suppress status output; only print results and errors")).showHelpAfterError();
465
467
  program.command("login").description("Save a Garu API key to the credentials file").option("--api-key <key>", "pre-supply the key instead of prompting").option("-p, --profile <name>", "profile name to store under", "default").action(async (cmdOpts) => {
466
- const globals = getGlobals(program);
468
+ const base = toCommandOptions(program);
467
469
  await loginCommand({
468
- ...cmdOpts.apiKey !== void 0 ? { apiKey: cmdOpts.apiKey } : {},
470
+ apiKey: cmdOpts.apiKey,
469
471
  profile: cmdOpts.profile,
470
- ...globalsToOutput(globals)
471
- }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
472
+ mode: base.mode,
473
+ quiet: base.quiet
474
+ }).catch((err) => printErrorAndExit(err, base));
472
475
  });
473
476
  program.command("logout").description("Remove saved credentials").option("-p, --profile <name>", "only remove this profile instead of the whole file").action(async (cmdOpts) => {
474
- const globals = getGlobals(program);
477
+ const base = toCommandOptions(program);
475
478
  await logoutCommand({
476
- ...cmdOpts.profile !== void 0 ? { profile: cmdOpts.profile } : {},
477
- ...globalsToOutput(globals)
478
- }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
479
+ profile: cmdOpts.profile,
480
+ mode: base.mode,
481
+ quiet: base.quiet
482
+ }).catch((err) => printErrorAndExit(err, base));
479
483
  });
480
484
  const auth = program.command("auth").description("Manage credentials profiles");
481
485
  auth.command("switch <profile>").description("Set the active credentials profile").action(async (profile) => {
482
- const globals = getGlobals(program);
483
- await authSwitchCommand({ profile, ...globalsToOutput(globals) }).catch(
484
- (err) => printErrorAndExit(err, globalsToOutput(globals))
486
+ const base = toCommandOptions(program);
487
+ await authSwitchCommand({ profile, mode: base.mode, quiet: base.quiet }).catch(
488
+ (err) => printErrorAndExit(err, base)
485
489
  );
486
490
  });
487
491
  const charges = program.command("charges").description("Create, fetch, and refund charges");
492
+ 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) => {
493
+ const base = toCommandOptions(program);
494
+ await chargesListCommand({
495
+ ...base,
496
+ page: cmdOpts.page,
497
+ limit: cmdOpts.limit,
498
+ status: cmdOpts.status,
499
+ search: cmdOpts.search,
500
+ paymentMethod: cmdOpts.paymentMethod
501
+ }).catch((err) => printErrorAndExit(err, base));
502
+ });
488
503
  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(
489
504
  "--customer-document <document>",
490
505
  "CPF (11 digits) or CNPJ (14 digits), digits only"
491
506
  ).requiredOption("--customer-phone <phone>", "customer phone with area code, digits only").option("--card-number <number>", "credit-card number (required for credit_card)").option("--card-cvv <cvv>", "credit-card CVV").option("--card-expiration <yyyy-mm>", "credit-card expiration date (YYYY-MM)").option("--card-holder <name>", "credit-card holder name").option("--installments <n>", "number of installments (1-12)", (v) => parseInt(v, 10), 1).option("--additional-info <text>", "free-form metadata attached to the charge").option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (cmdOpts) => {
492
- const globals = getGlobals(program);
493
- const type = parsePaymentMethod(cmdOpts.type);
507
+ const base = toCommandOptions(program);
494
508
  await chargesCreateCommand({
495
- type,
509
+ ...base,
510
+ type: parsePaymentMethod(cmdOpts.type),
496
511
  productId: cmdOpts.productId,
497
512
  customerName: cmdOpts.customerName,
498
513
  customerEmail: cmdOpts.customerEmail,
499
514
  customerDocument: cmdOpts.customerDocument,
500
515
  customerPhone: cmdOpts.customerPhone,
501
- ...cmdOpts.cardNumber !== void 0 ? { cardNumber: cmdOpts.cardNumber } : {},
502
- ...cmdOpts.cardCvv !== void 0 ? { cardCvv: cmdOpts.cardCvv } : {},
503
- ...cmdOpts.cardExpiration !== void 0 ? { cardExpiration: cmdOpts.cardExpiration } : {},
504
- ...cmdOpts.cardHolder !== void 0 ? { cardHolder: cmdOpts.cardHolder } : {},
505
- ...cmdOpts.installments !== void 0 ? { installments: cmdOpts.installments } : {},
506
- ...cmdOpts.additionalInfo !== void 0 ? { additionalInfo: cmdOpts.additionalInfo } : {},
507
- ...cmdOpts.idempotencyKey !== void 0 ? { idempotencyKey: cmdOpts.idempotencyKey } : {},
508
- ...globalFlagsToCommandOptions(globals)
509
- }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
516
+ cardNumber: cmdOpts.cardNumber,
517
+ cardCvv: cmdOpts.cardCvv,
518
+ cardExpiration: cmdOpts.cardExpiration,
519
+ cardHolder: cmdOpts.cardHolder,
520
+ installments: cmdOpts.installments,
521
+ additionalInfo: cmdOpts.additionalInfo,
522
+ idempotencyKey: cmdOpts.idempotencyKey
523
+ }).catch((err) => printErrorAndExit(err, base));
510
524
  });
511
525
  charges.command("get <id>").description("Fetch a single charge by ID").action(async (id) => {
512
- const globals = getGlobals(program);
513
- await chargesGetCommand({
514
- id: parseId(id),
515
- ...globalFlagsToCommandOptions(globals)
516
- }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
526
+ const base = toCommandOptions(program);
527
+ await chargesGetCommand({ ...base, id: parseId(id) }).catch(
528
+ (err) => printErrorAndExit(err, base)
529
+ );
517
530
  });
518
531
  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) => {
519
- const globals = getGlobals(program);
532
+ const base = toCommandOptions(program);
520
533
  await chargesRefundCommand({
534
+ ...base,
521
535
  id: parseId(id),
522
- ...cmdOpts.amount !== void 0 ? { amount: cmdOpts.amount } : {},
523
- ...cmdOpts.reason !== void 0 ? { reason: cmdOpts.reason } : {},
524
- ...cmdOpts.idempotencyKey !== void 0 ? { idempotencyKey: cmdOpts.idempotencyKey } : {},
525
- ...globalFlagsToCommandOptions(globals)
526
- }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
536
+ amount: cmdOpts.amount,
537
+ reason: cmdOpts.reason,
538
+ idempotencyKey: cmdOpts.idempotencyKey
539
+ }).catch((err) => printErrorAndExit(err, base));
527
540
  });
528
541
  program.command("doctor").description("Environment diagnostic").action(async () => {
529
- const globals = getGlobals(program);
530
- await doctorCommand({
531
- ...globalFlagsToCommandOptions(globals)
532
- }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
542
+ const base = toCommandOptions(program);
543
+ await doctorCommand(base).catch((err) => printErrorAndExit(err, base));
533
544
  });
534
545
  return program;
535
546
  }
536
- function getGlobals(program) {
537
- return program.opts();
538
- }
539
- function globalsToOutput(globals) {
547
+ function toCommandOptions(program) {
548
+ const g = program.opts();
540
549
  const out = {};
541
- if (globals.json)
542
- out.mode = "json";
543
- if (globals.quiet)
544
- out.quiet = true;
545
- return out;
546
- }
547
- function globalFlagsToCommandOptions(globals) {
548
- const out = {};
549
- if (globals.apiKey)
550
- out.apiKey = globals.apiKey;
551
- if (globals.profile)
552
- out.profile = globals.profile;
553
- if (globals.json)
554
- out.mode = "json";
555
- if (globals.quiet)
556
- out.quiet = true;
550
+ if (g.apiKey) out.apiKey = g.apiKey;
551
+ if (g.profile) out.profile = g.profile;
552
+ if (g.json) out.mode = "json";
553
+ if (g.quiet) out.quiet = true;
557
554
  return out;
558
555
  }
559
556
  function parsePaymentMethod(raw) {
560
- if (raw === "pix" || raw === "credit_card" || raw === "boleto")
561
- return raw;
557
+ if (raw === "pix" || raw === "credit_card" || raw === "boleto") return raw;
562
558
  throw new CliError(
563
559
  "invalid_input",
564
560
  `--type must be 'pix', 'credit_card', or 'boleto' (got '${raw}')`
@@ -571,10 +567,18 @@ function parseId(raw) {
571
567
  }
572
568
  return id;
573
569
  }
574
- function getVersion() {
575
- return CLI_VERSION;
570
+ async function setupUpdateNotifier() {
571
+ try {
572
+ const { default: updateNotifier } = await import('update-notifier');
573
+ updateNotifier({
574
+ pkg: { name: "@garuhq/cli", version: CLI_VERSION },
575
+ updateCheckInterval: 1e3 * 60 * 60 * 24
576
+ }).notify();
577
+ } catch {
578
+ }
576
579
  }
577
580
  async function main(argv = process.argv) {
581
+ await setupUpdateNotifier();
578
582
  const program = buildCli();
579
583
  await program.parseAsync(argv);
580
584
  }
@@ -582,5 +586,5 @@ main().catch((err) => printErrorAndExit(err));
582
586
 
583
587
  exports.buildCli = buildCli;
584
588
  exports.main = main;
585
- //# sourceMappingURL=out.js.map
589
+ //# sourceMappingURL=index.cjs.map
586
590
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
 
3
3
  /** Build the top-level command tree. Exposed so tests can exercise the router. */
4
4
  declare function buildCli(): Command;
5
- /** Main entry invoked by `bin/garu.js` and `src/index.ts` when run directly. */
5
+ /** Main entry invoked by `bin/garu.cjs` and the compiled binary. */
6
6
  declare function main(argv?: string[]): Promise<void>;
7
7
 
8
8
  export { buildCli, main };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garuhq/cli",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Official command-line interface for the Garu payment gateway.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://garu.com.br",
@@ -27,7 +27,8 @@
27
27
  "types": "./dist/index.d.ts",
28
28
  "files": [
29
29
  "bin",
30
- "dist",
30
+ "dist/index.cjs",
31
+ "dist/index.d.cts",
31
32
  "README.md",
32
33
  "CHANGELOG.md",
33
34
  "LICENSE"
@@ -44,16 +45,18 @@
44
45
  "prepublishOnly": "npm run typecheck && npm test && npm run build"
45
46
  },
46
47
  "dependencies": {
47
- "@garuhq/node": "0.1.0",
48
- "@inquirer/prompts": "5.0.2",
48
+ "@garuhq/node": "0.2.0",
49
+ "@inquirer/prompts": "8.4.1",
49
50
  "commander": "12.0.0",
50
- "picocolors": "1.0.0"
51
+ "picocolors": "1.0.0",
52
+ "update-notifier": "7.3.1"
51
53
  },
52
54
  "devDependencies": {
53
55
  "@types/node": "20.12.7",
54
- "tsup": "8.0.2",
55
- "tsx": "4.7.2",
56
+ "@types/update-notifier": "6.0.8",
57
+ "tsup": "8.5.1",
58
+ "tsx": "4.21.0",
56
59
  "typescript": "5.4.5",
57
- "vitest": "1.5.0"
60
+ "vitest": "3.2.4"
58
61
  }
59
62
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/version.ts","../src/lib/credentials.ts","../src/lib/errors.ts","../src/lib/output.ts","../src/commands/auth-switch.ts","../src/lib/auth.ts","../src/lib/client.ts","../src/commands/charges.ts","../src/commands/doctor.ts","../src/commands/login.ts","../src/commands/logout.ts"],"names":["homedir","join","Garu"],"mappings":"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","sourcesContent":["import { Command, Option } from 'commander';\n\nimport { CLI_VERSION } from './version.js';\nimport { authSwitchCommand } from './commands/auth-switch.js';\nimport {\n chargesCreateCommand,\n chargesGetCommand,\n chargesRefundCommand\n} from './commands/charges.js';\nimport { doctorCommand } from './commands/doctor.js';\nimport { loginCommand } from './commands/login.js';\nimport { logoutCommand } from './commands/logout.js';\nimport type { PaymentMethod } from '@garuhq/node';\nimport { CliError } from './lib/errors.js';\nimport { printErrorAndExit, type OutputMode } from './lib/output.js';\n\ninterface GlobalFlags {\n apiKey?: string;\n profile?: string;\n json?: boolean;\n quiet?: boolean;\n}\n\n/** Build the top-level command tree. Exposed so tests can exercise the router. */\nexport function buildCli(): Command {\n const program = new Command();\n\n program\n .name('garu')\n .description('Command-line interface for the Garu payment gateway.')\n .version(getVersion(), '-v, --version')\n .addOption(new Option('--api-key <key>', 'Garu API key (overrides env and credentials file)'))\n .addOption(new Option('-p, --profile <name>', 'credentials profile name'))\n .addOption(new Option('--json', 'emit strict JSON on stdout (forced in pipes)'))\n .addOption(new Option('-q, --quiet', 'suppress status output; only print results and errors'))\n .showHelpAfterError();\n\n // login\n program\n .command('login')\n .description('Save a Garu API key to the credentials file')\n .option('--api-key <key>', 'pre-supply the key instead of prompting')\n .option('-p, --profile <name>', 'profile name to store under', 'default')\n .action(async (cmdOpts: { apiKey?: string; profile: string }) => {\n const globals = getGlobals(program);\n await loginCommand({\n ...(cmdOpts.apiKey !== undefined ? { apiKey: cmdOpts.apiKey } : {}),\n profile: cmdOpts.profile,\n ...globalsToOutput(globals)\n }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));\n });\n\n // logout\n program\n .command('logout')\n .description('Remove saved credentials')\n .option('-p, --profile <name>', 'only remove this profile instead of the whole file')\n .action(async (cmdOpts: { profile?: string }) => {\n const globals = getGlobals(program);\n await logoutCommand({\n ...(cmdOpts.profile !== undefined ? { profile: cmdOpts.profile } : {}),\n ...globalsToOutput(globals)\n }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));\n });\n\n // auth switch\n const auth = program.command('auth').description('Manage credentials profiles');\n auth\n .command('switch <profile>')\n .description('Set the active credentials profile')\n .action(async (profile: string) => {\n const globals = getGlobals(program);\n await authSwitchCommand({ profile, ...globalsToOutput(globals) }).catch((err) =>\n printErrorAndExit(err, globalsToOutput(globals))\n );\n });\n\n // charges\n const charges = program.command('charges').description('Create, fetch, and refund charges');\n\n charges\n .command('create')\n .description('Create a charge (PIX, credit card, or boleto)')\n .requiredOption('--type <type>', 'payment method: pix | credit_card | boleto')\n .requiredOption('--product-id <uuid>', 'product UUID')\n .requiredOption('--customer-name <name>', 'customer full name')\n .requiredOption('--customer-email <email>', 'customer email')\n .requiredOption(\n '--customer-document <document>',\n 'CPF (11 digits) or CNPJ (14 digits), digits only'\n )\n .requiredOption('--customer-phone <phone>', 'customer phone with area code, digits only')\n .option('--card-number <number>', 'credit-card number (required for credit_card)')\n .option('--card-cvv <cvv>', 'credit-card CVV')\n .option('--card-expiration <yyyy-mm>', 'credit-card expiration date (YYYY-MM)')\n .option('--card-holder <name>', 'credit-card holder name')\n .option('--installments <n>', 'number of installments (1-12)', (v) => parseInt(v, 10), 1)\n .option('--additional-info <text>', 'free-form metadata attached to the charge')\n .option('--idempotency-key <key>', 'idempotency key (auto-generated if omitted)')\n .action(async (cmdOpts) => {\n const globals = getGlobals(program);\n const type = parsePaymentMethod(cmdOpts.type);\n await chargesCreateCommand({\n type,\n productId: cmdOpts.productId,\n customerName: cmdOpts.customerName,\n customerEmail: cmdOpts.customerEmail,\n customerDocument: cmdOpts.customerDocument,\n customerPhone: cmdOpts.customerPhone,\n ...(cmdOpts.cardNumber !== undefined ? { cardNumber: cmdOpts.cardNumber } : {}),\n ...(cmdOpts.cardCvv !== undefined ? { cardCvv: cmdOpts.cardCvv } : {}),\n ...(cmdOpts.cardExpiration !== undefined ? { cardExpiration: cmdOpts.cardExpiration } : {}),\n ...(cmdOpts.cardHolder !== undefined ? { cardHolder: cmdOpts.cardHolder } : {}),\n ...(cmdOpts.installments !== undefined ? { installments: cmdOpts.installments } : {}),\n ...(cmdOpts.additionalInfo !== undefined ? { additionalInfo: cmdOpts.additionalInfo } : {}),\n ...(cmdOpts.idempotencyKey !== undefined ? { idempotencyKey: cmdOpts.idempotencyKey } : {}),\n ...globalFlagsToCommandOptions(globals)\n }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));\n });\n\n charges\n .command('get <id>')\n .description('Fetch a single charge by ID')\n .action(async (id: string) => {\n const globals = getGlobals(program);\n await chargesGetCommand({\n id: parseId(id),\n ...globalFlagsToCommandOptions(globals)\n }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));\n });\n\n charges\n .command('refund <id>')\n .description('Refund a charge (full or partial)')\n .option('--amount <centavos>', 'partial refund amount in centavos', (v) => parseInt(v, 10))\n .option('--reason <text>', 'optional refund reason')\n .option('--idempotency-key <key>', 'idempotency key (auto-generated if omitted)')\n .action(async (id: string, cmdOpts) => {\n const globals = getGlobals(program);\n await chargesRefundCommand({\n id: parseId(id),\n ...(cmdOpts.amount !== undefined ? { amount: cmdOpts.amount } : {}),\n ...(cmdOpts.reason !== undefined ? { reason: cmdOpts.reason } : {}),\n ...(cmdOpts.idempotencyKey !== undefined ? { idempotencyKey: cmdOpts.idempotencyKey } : {}),\n ...globalFlagsToCommandOptions(globals)\n }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));\n });\n\n // doctor\n program\n .command('doctor')\n .description('Environment diagnostic')\n .action(async () => {\n const globals = getGlobals(program);\n await doctorCommand({\n ...globalFlagsToCommandOptions(globals)\n }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));\n });\n\n return program;\n}\n\nfunction getGlobals(program: Command): GlobalFlags {\n return program.opts<GlobalFlags>();\n}\n\nfunction globalsToOutput(globals: GlobalFlags): { mode?: OutputMode; quiet?: boolean } {\n const out: { mode?: OutputMode; quiet?: boolean } = {};\n if (globals.json) out.mode = 'json';\n if (globals.quiet) out.quiet = true;\n return out;\n}\n\nfunction globalFlagsToCommandOptions(globals: GlobalFlags): {\n apiKey?: string;\n profile?: string;\n mode?: OutputMode;\n quiet?: boolean;\n} {\n const out: { apiKey?: string; profile?: string; mode?: OutputMode; quiet?: boolean } = {};\n if (globals.apiKey) out.apiKey = globals.apiKey;\n if (globals.profile) out.profile = globals.profile;\n if (globals.json) out.mode = 'json';\n if (globals.quiet) out.quiet = true;\n return out;\n}\n\nfunction parsePaymentMethod(raw: string): PaymentMethod {\n if (raw === 'pix' || raw === 'credit_card' || raw === 'boleto') return raw;\n throw new CliError(\n 'invalid_input',\n `--type must be 'pix', 'credit_card', or 'boleto' (got '${raw}')`\n );\n}\n\nfunction parseId(raw: string): number {\n const id = Number.parseInt(raw, 10);\n if (!Number.isFinite(id) || id <= 0) {\n throw new CliError('invalid_input', `Charge ID must be a positive integer (got '${raw}')`);\n }\n return id;\n}\n\nfunction getVersion(): string {\n return CLI_VERSION;\n}\n\n/** Main entry invoked by `bin/garu.js` and `src/index.ts` when run directly. */\nexport async function main(argv: string[] = process.argv): Promise<void> {\n const program = buildCli();\n await program.parseAsync(argv);\n}\n\n// This module is only ever loaded as the CLI entry point — either through the\n// `bin/garu.cjs` shim for npm installs or as the compiled binary produced by\n// `bun build --compile`. No test imports `src/index.ts`, so it's safe to call\n// `main()` unconditionally at module load time.\nmain().catch((err) => printErrorAndExit(err));\n","/**\n * CLI version.\n *\n * Kept as a plain string so `bun build --compile` can inline it at compile time\n * and the resulting binary has no runtime dependency on `package.json`.\n * The release workflow bumps this in lockstep with `package.json` on `v*` tags.\n */\nexport const CLI_VERSION = '0.1.1';\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { mkdir, readFile, writeFile, rm, stat } from 'node:fs/promises';\n\nimport { CliError } from './errors.js';\n\nexport interface CredentialProfile {\n apiKey: string;\n /** Optional seller ID to render in `garu doctor` output. */\n sellerId?: number;\n /** Human-readable label for multi-profile setups. */\n label?: string;\n}\n\nexport interface CredentialsFile {\n version: 1;\n activeProfile: string;\n profiles: Record<string, CredentialProfile>;\n}\n\nconst DEFAULT_FILE: CredentialsFile = {\n version: 1,\n activeProfile: 'default',\n profiles: {}\n};\n\n/**\n * Resolve the credentials file path.\n *\n * Honors `$XDG_CONFIG_HOME` per the XDG base-dir spec, falling back to\n * `~/.config/garu/credentials.json`. Override with `$GARU_CREDENTIALS_PATH`\n * (primarily for tests).\n */\nexport function credentialsPath(env: NodeJS.ProcessEnv = process.env): string {\n if (env.GARU_CREDENTIALS_PATH) return env.GARU_CREDENTIALS_PATH;\n const base = env.XDG_CONFIG_HOME || join(homedir(), '.config');\n return join(base, 'garu', 'credentials.json');\n}\n\n/** Read the credentials file. Returns the default (empty) file if it doesn't exist. */\nexport async function loadCredentials(\n env: NodeJS.ProcessEnv = process.env\n): Promise<CredentialsFile> {\n const path = credentialsPath(env);\n try {\n const raw = await readFile(path, 'utf8');\n const parsed = JSON.parse(raw);\n return normalize(parsed);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { ...DEFAULT_FILE };\n }\n throw new CliError(\n 'invalid_input',\n `Failed to read credentials at ${path}: ${(err as Error).message}`\n );\n }\n}\n\n/**\n * Write the credentials file atomically with tight file-mode (0600) and\n * directory-mode (0700).\n */\nexport async function saveCredentials(\n file: CredentialsFile,\n env: NodeJS.ProcessEnv = process.env\n): Promise<void> {\n const path = credentialsPath(env);\n const dir = path.slice(0, path.lastIndexOf('/'));\n await mkdir(dir, { recursive: true, mode: 0o700 });\n await writeFile(path, JSON.stringify(file, null, 2) + '\\n', { mode: 0o600 });\n}\n\n/** Delete the credentials file. No-op if it doesn't exist. */\nexport async function deleteCredentials(env: NodeJS.ProcessEnv = process.env): Promise<void> {\n const path = credentialsPath(env);\n try {\n await rm(path);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;\n }\n}\n\n/** Add or replace a profile and set it active. */\nexport function upsertProfile(\n file: CredentialsFile,\n name: string,\n profile: CredentialProfile\n): CredentialsFile {\n return {\n ...file,\n activeProfile: name,\n profiles: { ...file.profiles, [name]: profile }\n };\n}\n\n/** Check whether the credentials file has the expected 0600 mode. Returns null on any error. */\nexport async function credentialsFileMode(\n env: NodeJS.ProcessEnv = process.env\n): Promise<number | null> {\n try {\n const s = await stat(credentialsPath(env));\n return s.mode & 0o777;\n } catch {\n return null;\n }\n}\n\nfunction normalize(raw: unknown): CredentialsFile {\n if (!raw || typeof raw !== 'object') return { ...DEFAULT_FILE };\n const obj = raw as Partial<CredentialsFile>;\n return {\n version: 1,\n activeProfile: obj.activeProfile ?? 'default',\n profiles: obj.profiles && typeof obj.profiles === 'object' ? obj.profiles : {}\n };\n}\n","/**\n * CLI error model. All user-visible failures flow through these types so that\n * the output layer can render them consistently — pretty with a red prefix in\n * TTY mode, strict `{\"error\":{\"code\",\"message\"}}` on stdout + exit 1 in JSON mode.\n */\n\nexport type CliErrorCode =\n | 'auth_error'\n | 'not_found'\n | 'validation_error'\n | 'rate_limited'\n | 'server_error'\n | 'connection_error'\n | 'signature_verification_failed'\n | 'user_cancelled'\n | 'invalid_input'\n | 'unknown_error';\n\nexport class CliError extends Error {\n public readonly code: CliErrorCode;\n public readonly exitCode: number;\n\n constructor(code: CliErrorCode, message: string, exitCode = 1) {\n super(message);\n this.name = 'CliError';\n this.code = code;\n this.exitCode = exitCode;\n }\n}\n\n/** Wrap an unknown thrown value as a {@link CliError}. */\nexport function toCliError(err: unknown): CliError {\n if (err instanceof CliError) return err;\n // Duck-type the SDK's GaruError — avoids importing the SDK class hierarchy\n // just for instanceof checks, and keeps this module dependency-free.\n if (err && typeof err === 'object' && 'name' in err && 'code' in err) {\n const e = err as { name: string; code: string; message?: string };\n if (typeof e.name === 'string' && e.name.startsWith('Garu')) {\n return new CliError(mapSdkCodeToCliCode(e.code), e.message ?? 'Request failed', 1);\n }\n }\n if (err instanceof Error) return new CliError('unknown_error', err.message);\n return new CliError('unknown_error', String(err));\n}\n\nfunction mapSdkCodeToCliCode(sdkCode: string): CliErrorCode {\n switch (sdkCode) {\n case 'authentication_error':\n case 'permission_error':\n return 'auth_error';\n case 'not_found':\n return 'not_found';\n case 'validation_error':\n return 'validation_error';\n case 'rate_limited':\n return 'rate_limited';\n case 'server_error':\n return 'server_error';\n case 'connection_error':\n return 'connection_error';\n case 'signature_verification_failed':\n return 'signature_verification_failed';\n default:\n return 'unknown_error';\n }\n}\n","import pc from 'picocolors';\n\nimport { CliError, toCliError } from './errors.js';\n\nexport type OutputMode = 'pretty' | 'json';\n\nexport interface OutputOptions {\n /** Force a mode. If omitted, auto-detect from TTY. */\n mode?: OutputMode;\n /** Passed by `--quiet`; suppresses everything except errors and the final value. */\n quiet?: boolean;\n}\n\n/**\n * Resolve the output mode. Rules:\n * - Explicit `--json` (or `opts.mode === 'json'`) wins.\n * - Non-TTY stdout (pipe, CI) → `json`.\n * - Everything else → `pretty`.\n */\nexport function resolveMode(\n opts: OutputOptions = {},\n stdout: NodeJS.WriteStream = process.stdout\n): OutputMode {\n if (opts.mode) return opts.mode;\n return stdout.isTTY ? 'pretty' : 'json';\n}\n\n/**\n * Print a successful command result. In JSON mode the full object is written\n * to stdout with a trailing newline. In pretty mode the caller's `prettyPrint`\n * fallback (if provided) is used; otherwise the JSON is indented.\n */\nexport function printResult<T>(\n value: T,\n opts: OutputOptions & { prettyPrint?: (value: T) => string } = {}\n): void {\n const mode = resolveMode(opts);\n if (mode === 'json') {\n process.stdout.write(`${JSON.stringify(value)}\\n`);\n return;\n }\n if (opts.prettyPrint) {\n process.stdout.write(`${opts.prettyPrint(value)}\\n`);\n return;\n }\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\n}\n\n/** Print a short status line — suppressed in JSON mode and when `--quiet`. */\nexport function printStatus(message: string, opts: OutputOptions = {}): void {\n if (opts.quiet) return;\n if (resolveMode(opts) === 'json') return;\n process.stderr.write(`${pc.dim('→')} ${message}\\n`);\n}\n\n/** Print a success line — suppressed in JSON mode and when `--quiet`. */\nexport function printSuccess(message: string, opts: OutputOptions = {}): void {\n if (opts.quiet) return;\n if (resolveMode(opts) === 'json') return;\n process.stderr.write(`${pc.green('✓')} ${message}\\n`);\n}\n\n/**\n * Print an error and exit with the appropriate code. Always uses the\n * CLI-standard shape `{\"error\":{\"code\",\"message\"}}` on stdout in JSON mode,\n * or a red `Error:` line on stderr in pretty mode.\n */\nexport function printErrorAndExit(err: unknown, opts: OutputOptions = {}): never {\n const cliErr: CliError = toCliError(err);\n const payload = { error: { code: cliErr.code, message: cliErr.message } };\n\n if (resolveMode(opts) === 'json') {\n process.stdout.write(`${JSON.stringify(payload)}\\n`);\n } else {\n process.stderr.write(`${pc.red('Error:')} ${cliErr.message}\\n`);\n if (cliErr.code !== 'unknown_error') {\n process.stderr.write(`${pc.dim(` code: ${cliErr.code}`)}\\n`);\n }\n }\n process.exit(cliErr.exitCode);\n}\n","import { loadCredentials, saveCredentials } from '../lib/credentials.js';\nimport { CliError } from '../lib/errors.js';\nimport { printSuccess, type OutputOptions } from '../lib/output.js';\n\nexport interface AuthSwitchOptions extends OutputOptions {\n profile: string;\n}\n\nexport async function authSwitchCommand(\n opts: AuthSwitchOptions\n): Promise<{ activeProfile: string }> {\n const file = await loadCredentials();\n if (!file.profiles[opts.profile]) {\n const available = Object.keys(file.profiles).join(', ') || '(none)';\n throw new CliError('not_found', `Profile '${opts.profile}' not found. Available: ${available}`);\n }\n\n await saveCredentials({ ...file, activeProfile: opts.profile });\n printSuccess(`Active profile set to '${opts.profile}'.`, opts);\n return { activeProfile: opts.profile };\n}\n","import { loadCredentials, type CredentialProfile } from './credentials.js';\nimport { CliError } from './errors.js';\n\nexport interface ResolvedAuth {\n apiKey: string;\n /** Where the key came from, for `doctor` and debug output. */\n source: 'flag' | 'env' | 'file';\n /** Profile name if resolved from file; undefined otherwise. */\n profile?: string;\n fileProfile?: CredentialProfile;\n}\n\nexport interface ResolveAuthOptions {\n /** `--api-key` flag value. Highest precedence. */\n apiKey?: string;\n /** `--profile` flag value. When set, reads that profile instead of the active one. */\n profile?: string;\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve the API key following SPEC §7 auth priority:\n *\n * 1. `--api-key` flag\n * 2. `GARU_API_KEY` environment variable\n * 3. credentials file active profile (or profile selected with `--profile`)\n *\n * Throws {@link CliError} with code `auth_error` if no key is found. Never logs.\n */\nexport async function resolveAuth(opts: ResolveAuthOptions = {}): Promise<ResolvedAuth> {\n const env = opts.env ?? process.env;\n\n if (opts.apiKey) {\n return { apiKey: opts.apiKey, source: 'flag' };\n }\n\n if (env.GARU_API_KEY) {\n return { apiKey: env.GARU_API_KEY, source: 'env' };\n }\n\n const file = await loadCredentials(env);\n const profileName = opts.profile ?? file.activeProfile;\n const profile = file.profiles[profileName];\n if (profile && profile.apiKey) {\n return {\n apiKey: profile.apiKey,\n source: 'file',\n profile: profileName,\n fileProfile: profile\n };\n }\n\n throw new CliError('auth_error', 'No API key found. Run `garu login` or set GARU_API_KEY.');\n}\n\n/**\n * Same as {@link resolveAuth} but returns `null` instead of throwing when no\n * credentials are available. Used by `garu doctor`.\n */\nexport async function resolveAuthOptional(\n opts: ResolveAuthOptions = {}\n): Promise<ResolvedAuth | null> {\n try {\n return await resolveAuth(opts);\n } catch (err) {\n if (err instanceof CliError && err.code === 'auth_error') return null;\n throw err;\n }\n}\n","import { Garu } from '@garuhq/node';\n\nimport type { ResolvedAuth } from './auth.js';\nimport { CLI_VERSION } from '../version.js';\n\nexport interface GaruClientOptions {\n auth: ResolvedAuth;\n baseUrl?: string;\n}\n\n/**\n * Construct a `Garu` SDK instance from resolved auth.\n *\n * The CLI dogfoods `@garuhq/node` — every HTTP call flows through the SDK we\n * shipped in Chunk 3. This keeps retries, idempotency, and error mapping\n * consistent between the CLI, MCP server, and any user-written integration.\n */\nexport function createGaruClient(opts: GaruClientOptions): Garu {\n return new Garu({\n apiKey: opts.auth.apiKey,\n baseUrl: opts.baseUrl\n });\n}\n\n/** User-Agent used for CLI requests. Surfaces the CLI version in backend logs. */\nexport function cliUserAgent(): string {\n return `garu-cli/${CLI_VERSION}`;\n}\n","import type { Charge, Customer, Garu, PaymentMethod, RefundChargeParams } from '@garuhq/node';\n\nimport { resolveAuth } from '../lib/auth.js';\nimport { createGaruClient } from '../lib/client.js';\nimport { CliError } from '../lib/errors.js';\nimport { printResult, type OutputOptions } from '../lib/output.js';\n\nexport interface ChargesGlobalOptions extends OutputOptions {\n apiKey?: string;\n profile?: string;\n baseUrl?: string;\n /** Injectable for tests — bypass auth resolution + SDK construction. */\n garu?: Garu;\n}\n\nexport interface ChargesCreateOptions extends ChargesGlobalOptions {\n type: PaymentMethod;\n productId: string;\n customerName: string;\n customerEmail: string;\n customerDocument: string;\n customerPhone: string;\n cardNumber?: string;\n cardCvv?: string;\n cardExpiration?: string;\n cardHolder?: string;\n installments?: number;\n idempotencyKey?: string;\n additionalInfo?: string;\n}\n\nexport interface ChargesByIdOptions extends ChargesGlobalOptions {\n id: number;\n}\n\nexport interface ChargesRefundOptions extends ChargesByIdOptions {\n /** Amount in centavos. Omit for full refund. */\n amount?: number;\n reason?: string;\n idempotencyKey?: string;\n}\n\nasync function getClient(opts: ChargesGlobalOptions): Promise<Garu> {\n if (opts.garu) return opts.garu;\n const auth = await resolveAuth({\n ...(opts.apiKey !== undefined ? { apiKey: opts.apiKey } : {}),\n ...(opts.profile !== undefined ? { profile: opts.profile } : {})\n });\n return createGaruClient({\n auth,\n ...(opts.baseUrl !== undefined ? { baseUrl: opts.baseUrl } : {})\n });\n}\n\nexport async function chargesCreateCommand(opts: ChargesCreateOptions): Promise<Charge> {\n validateCreate(opts);\n const garu = await getClient(opts);\n\n const customer: Customer = {\n name: opts.customerName,\n email: opts.customerEmail,\n document: opts.customerDocument,\n phone: opts.customerPhone\n };\n\n const charge = await garu.charges.create({\n productId: opts.productId,\n paymentMethod: opts.type,\n customer,\n ...(opts.additionalInfo !== undefined ? { additionalInfo: opts.additionalInfo } : {}),\n ...(opts.idempotencyKey !== undefined ? { idempotencyKey: opts.idempotencyKey } : {}),\n ...(opts.type === 'credit_card' && opts.cardNumber\n ? {\n cardInfo: {\n cardNumber: opts.cardNumber,\n cvv: opts.cardCvv!,\n expirationDate: opts.cardExpiration!,\n holderName: opts.cardHolder!,\n installments: opts.installments ?? 1\n }\n }\n : {})\n });\n\n printResult(charge, { ...opts, prettyPrint: prettyCharge });\n return charge;\n}\n\nexport async function chargesGetCommand(opts: ChargesByIdOptions): Promise<Charge> {\n const garu = await getClient(opts);\n const charge = await garu.charges.get(opts.id);\n printResult(charge, { ...opts, prettyPrint: prettyCharge });\n return charge;\n}\n\nexport async function chargesRefundCommand(opts: ChargesRefundOptions): Promise<Charge> {\n const garu = await getClient(opts);\n const params: RefundChargeParams = {};\n if (opts.amount !== undefined) params.amount = opts.amount;\n if (opts.reason !== undefined) params.reason = opts.reason;\n if (opts.idempotencyKey !== undefined) params.idempotencyKey = opts.idempotencyKey;\n const charge = await garu.charges.refund(opts.id, params);\n printResult(charge, { ...opts, prettyPrint: prettyCharge });\n return charge;\n}\n\nfunction validateCreate(opts: ChargesCreateOptions): void {\n if (opts.type === 'credit_card') {\n const missing: string[] = [];\n if (!opts.cardNumber) missing.push('--card-number');\n if (!opts.cardCvv) missing.push('--card-cvv');\n if (!opts.cardExpiration) missing.push('--card-expiration');\n if (!opts.cardHolder) missing.push('--card-holder');\n if (missing.length) {\n throw new CliError('invalid_input', `Credit-card charges require: ${missing.join(', ')}`);\n }\n }\n}\n\nfunction prettyCharge(charge: Charge): string {\n const lines = [\n `Charge ${charge.id}`,\n ` status: ${charge.status}`,\n ` amount: ${charge.amount}`,\n ` method: ${charge.paymentMethodId}`,\n ` date: ${charge.date}`\n ];\n if (charge.deadline) lines.push(` deadline: ${charge.deadline}`);\n return lines.join('\\n');\n}\n","import { existsSync } from 'node:fs';\nimport { homedir, platform } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Garu } from '@garuhq/node';\n\nimport { resolveAuthOptional } from '../lib/auth.js';\nimport { credentialsFileMode, credentialsPath } from '../lib/credentials.js';\nimport { printResult, type OutputOptions } from '../lib/output.js';\nimport { CLI_VERSION } from '../version.js';\n\nexport interface DoctorReport {\n cli: { version: string };\n api: { reachable: boolean; url: string; version?: string; error?: string };\n credentials: {\n path: string;\n source: 'flag' | 'env' | 'file' | 'none';\n profile?: string;\n fileMode?: string;\n warning?: string;\n };\n agents: {\n claudeCode: boolean;\n cursor: boolean;\n codex: boolean;\n windsurf: boolean;\n claudeDesktop: boolean;\n vscodeMcpInCwd: boolean;\n };\n}\n\nexport interface DoctorOptions extends OutputOptions {\n apiKey?: string;\n profile?: string;\n baseUrl?: string;\n /** Injectable for tests — bypass the real HTTP call. */\n garu?: Garu;\n /** Injectable HOME, for tests. */\n home?: string;\n /** Injectable cwd, for tests. */\n cwd?: string;\n /** Injectable platform, for tests. */\n platform?: NodeJS.Platform;\n}\n\n/**\n * Run a full environment diagnostic.\n *\n * Never throws on expected failures — bad auth, unreachable API, missing\n * credentials all become structured report fields. Only genuinely unexpected\n * errors (programming bugs) bubble up.\n */\nexport async function doctorCommand(opts: DoctorOptions = {}): Promise<DoctorReport> {\n const home = opts.home ?? homedir();\n const cwd = opts.cwd ?? process.cwd();\n const plat = opts.platform ?? platform();\n\n const credentials = await reportCredentials(opts);\n const api = await reportApi(opts);\n const agents = detectAgents(home, cwd, plat);\n\n const report: DoctorReport = {\n cli: { version: CLI_VERSION },\n api,\n credentials,\n agents\n };\n\n printResult(report, { ...opts, prettyPrint: prettyDoctor });\n return report;\n}\n\nasync function reportCredentials(opts: DoctorOptions): Promise<DoctorReport['credentials']> {\n const path = credentialsPath();\n const resolved = await resolveAuthOptional({\n ...(opts.apiKey !== undefined ? { apiKey: opts.apiKey } : {}),\n ...(opts.profile !== undefined ? { profile: opts.profile } : {})\n });\n\n if (!resolved) {\n return { path, source: 'none' };\n }\n\n const out: DoctorReport['credentials'] = { path, source: resolved.source };\n if (resolved.profile) out.profile = resolved.profile;\n\n if (resolved.source === 'file') {\n const mode = await credentialsFileMode();\n if (mode !== null) {\n out.fileMode = `0${mode.toString(8)}`;\n if (mode !== 0o600) {\n out.warning = `credentials file mode is 0${mode.toString(8)}; expected 0600`;\n }\n }\n }\n\n return out;\n}\n\nasync function reportApi(opts: DoctorOptions): Promise<DoctorReport['api']> {\n const url = opts.baseUrl ?? 'https://garu.com.br';\n const garu =\n opts.garu ??\n new Garu({\n ...(opts.baseUrl !== undefined ? { baseUrl: opts.baseUrl } : {})\n });\n\n try {\n const meta = await garu.meta.get();\n return { reachable: true, url, version: meta.version };\n } catch (err) {\n return {\n reachable: false,\n url,\n error: err instanceof Error ? err.message : String(err)\n };\n }\n}\n\nfunction detectAgents(home: string, cwd: string, plat: NodeJS.Platform): DoctorReport['agents'] {\n return {\n claudeCode: existsSync(join(home, '.claude')),\n cursor: existsSync(join(home, '.cursor')),\n codex: existsSync(join(home, '.codex')),\n windsurf: existsSync(join(home, '.windsurf')),\n claudeDesktop: existsSync(claudeDesktopConfigPath(home, plat)),\n vscodeMcpInCwd: existsSync(join(cwd, '.vscode', 'mcp.json'))\n };\n}\n\nfunction claudeDesktopConfigPath(home: string, plat: NodeJS.Platform): string {\n if (plat === 'darwin') {\n return join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');\n }\n if (plat === 'win32') {\n return join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');\n }\n return join(home, '.config', 'Claude', 'claude_desktop_config.json');\n}\n\nfunction prettyDoctor(r: DoctorReport): string {\n const check = (ok: boolean) => (ok ? 'yes' : 'no ');\n const lines = [\n `CLI: garu ${r.cli.version}`,\n `API: ${r.api.reachable ? `reachable (${r.api.url}, backend ${r.api.version ?? '?'})` : `unreachable (${r.api.url})`}`,\n r.api.error ? ` ${r.api.error}` : undefined,\n `Credentials: source=${r.credentials.source}${r.credentials.profile ? ` profile=${r.credentials.profile}` : ''}${r.credentials.fileMode ? ` mode=${r.credentials.fileMode}` : ''}`,\n r.credentials.warning ? ` ⚠ ${r.credentials.warning}` : undefined,\n '',\n 'Detected agents:',\n ` Claude Code: ${check(r.agents.claudeCode)}`,\n ` Cursor: ${check(r.agents.cursor)}`,\n ` Codex: ${check(r.agents.codex)}`,\n ` Windsurf: ${check(r.agents.windsurf)}`,\n ` Claude Desktop: ${check(r.agents.claudeDesktop)}`,\n ` VS Code MCP: ${check(r.agents.vscodeMcpInCwd)} (.vscode/mcp.json in cwd)`\n ].filter((l): l is string => l !== undefined);\n return lines.join('\\n');\n}\n","import { password as promptPassword } from '@inquirer/prompts';\n\nimport { Garu } from '@garuhq/node';\n\nimport { loadCredentials, saveCredentials, upsertProfile } from '../lib/credentials.js';\nimport { CliError } from '../lib/errors.js';\nimport { printStatus, printSuccess, type OutputOptions } from '../lib/output.js';\n\nexport interface LoginOptions extends OutputOptions {\n /** Profile name to create/update. Default: `default`. */\n profile?: string;\n /** Pre-supplied API key (for scripting). When set, no interactive prompt. */\n apiKey?: string;\n /** Override base URL (tests). */\n baseUrl?: string;\n}\n\nexport async function loginCommand(opts: LoginOptions = {}): Promise<{ profile: string }> {\n const profile = opts.profile ?? 'default';\n\n const apiKey =\n opts.apiKey ??\n (await promptPassword({\n message: 'Paste your Garu API key (sk_live_... or sk_test_...)',\n mask: '*'\n }).catch(() => {\n throw new CliError('user_cancelled', 'Login cancelled.');\n }));\n\n if (!apiKey || !/^sk_(live|test)_[A-Za-z0-9_]+$/.test(apiKey)) {\n throw new CliError('invalid_input', 'API key must look like `sk_live_...` or `sk_test_...`.');\n }\n\n printStatus('Validating key with Garu...', opts);\n const garu = new Garu({\n apiKey,\n ...(opts.baseUrl !== undefined ? { baseUrl: opts.baseUrl } : {})\n });\n\n // Hit an unauthenticated endpoint first to confirm connectivity, then use\n // the authenticated path to prove the key works. `meta.get` tests reachability;\n // we don't yet have a light authed endpoint to verify the key, so the key is\n // stored after basic format + reachability checks. A real auth probe will\n // land once the backend exposes `GET /api/v1/me`.\n await garu.meta.get();\n\n const file = await loadCredentials();\n const updated = upsertProfile(file, profile, { apiKey });\n await saveCredentials(updated);\n\n printSuccess(`Saved profile '${profile}' to ~/.config/garu/credentials.json`, opts);\n return { profile };\n}\n","import { deleteCredentials, loadCredentials, saveCredentials } from '../lib/credentials.js';\nimport { CliError } from '../lib/errors.js';\nimport { printSuccess, type OutputOptions } from '../lib/output.js';\n\nexport interface LogoutOptions extends OutputOptions {\n /**\n * Profile to remove. If omitted, the entire credentials file is deleted.\n */\n profile?: string;\n}\n\nexport async function logoutCommand(opts: LogoutOptions = {}): Promise<{ cleared: string }> {\n if (!opts.profile) {\n await deleteCredentials();\n printSuccess('All saved credentials deleted.', opts);\n return { cleared: 'all' };\n }\n\n const file = await loadCredentials();\n if (!file.profiles[opts.profile]) {\n throw new CliError('not_found', `Profile '${opts.profile}' not found.`);\n }\n\n const { [opts.profile]: _removed, ...rest } = file.profiles;\n void _removed;\n\n const nextActive =\n file.activeProfile === opts.profile ? (Object.keys(rest)[0] ?? 'default') : file.activeProfile;\n\n await saveCredentials({ ...file, activeProfile: nextActive, profiles: rest });\n printSuccess(`Profile '${opts.profile}' removed.`, opts);\n return { cleared: opts.profile };\n}\n"]}