@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 +76 -0
- package/README.md +284 -53
- package/dist/index.cjs +147 -143
- package/dist/index.d.cts +1 -1
- package/package.json +11 -8
- package/dist/index.cjs.map +0 -1
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
|
-
#
|
|
1
|
+
# Garu CLI
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
75
|
+
-> Validating key with Garu...
|
|
76
|
+
Saved profile 'default' to ~/.config/garu/credentials.json
|
|
52
77
|
```
|
|
53
78
|
|
|
54
|
-
###
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
|
81
|
-
|
|
|
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
|
-
|
|
120
|
+
On success, credentials are saved to `~/.config/garu/credentials.json` with `0600` permissions (owner read/write only).
|
|
84
121
|
|
|
85
|
-
|
|
122
|
+
---
|
|
86
123
|
|
|
87
|
-
|
|
124
|
+
### `garu logout`
|
|
88
125
|
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
garu
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
98
|
-
await promises.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
250
|
+
const base = {
|
|
261
251
|
productId: opts.productId,
|
|
262
252
|
paymentMethod: opts.type,
|
|
263
253
|
customer,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
290
|
-
if (opts.
|
|
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
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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("
|
|
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
|
-
|
|
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(
|
|
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
|
|
468
|
+
const base = toCommandOptions(program);
|
|
467
469
|
await loginCommand({
|
|
468
|
-
|
|
470
|
+
apiKey: cmdOpts.apiKey,
|
|
469
471
|
profile: cmdOpts.profile,
|
|
470
|
-
|
|
471
|
-
|
|
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
|
|
477
|
+
const base = toCommandOptions(program);
|
|
475
478
|
await logoutCommand({
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
|
483
|
-
await authSwitchCommand({ profile,
|
|
484
|
-
(err) => printErrorAndExit(err,
|
|
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
|
|
493
|
-
const type = parsePaymentMethod(cmdOpts.type);
|
|
507
|
+
const base = toCommandOptions(program);
|
|
494
508
|
await chargesCreateCommand({
|
|
495
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
|
513
|
-
await chargesGetCommand({
|
|
514
|
-
|
|
515
|
-
|
|
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
|
|
532
|
+
const base = toCommandOptions(program);
|
|
520
533
|
await chargesRefundCommand({
|
|
534
|
+
...base,
|
|
521
535
|
id: parseId(id),
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
|
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
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
function globalsToOutput(globals) {
|
|
547
|
+
function toCommandOptions(program) {
|
|
548
|
+
const g = program.opts();
|
|
540
549
|
const out = {};
|
|
541
|
-
if (
|
|
542
|
-
|
|
543
|
-
if (
|
|
544
|
-
|
|
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
|
|
575
|
-
|
|
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=
|
|
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.
|
|
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.
|
|
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.
|
|
48
|
-
"@inquirer/prompts": "
|
|
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
|
-
"
|
|
55
|
-
"
|
|
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": "
|
|
60
|
+
"vitest": "3.2.4"
|
|
58
61
|
}
|
|
59
62
|
}
|
package/dist/index.cjs.map
DELETED
|
@@ -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"]}
|