@garuhq/cli 0.1.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 ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@garuhq/cli` are documented in this file. Format:
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
5
+
6
+ ## [0.1.0] — 2026-04-08
7
+
8
+ ### Added
9
+
10
+ - Initial public beta. Single-binary CLI for the Garu payment gateway.
11
+ - **`garu login`** — interactive paste-and-validate flow. Hits `/api/meta` to confirm reachability, stores the key at `~/.config/garu/credentials.json` with mode `0600` (directory `0700`). Honors `$XDG_CONFIG_HOME`.
12
+ - **`garu logout`** — remove one profile or the entire credentials file.
13
+ - **`garu auth switch <profile>`** — toggle the active credentials profile.
14
+ - **`garu charges create`** — PIX, credit card, or boleto. Full flag set including `--idempotency-key`.
15
+ - **`garu charges get <id>`** — fetch a single charge.
16
+ - **`garu charges refund <id>`** — full or partial (`--amount`, `--reason`).
17
+ - **`garu doctor`** — environment diagnostic: CLI version, API reachability, credentials source and file mode, installed agents (Claude Code, Cursor, Codex, Windsurf, Claude Desktop, VS Code MCP).
18
+ - **Auth priority chain** per SPEC §7: `--api-key` > `GARU_API_KEY` > credentials file active profile.
19
+ - **Output modes:** TTY-aware pretty output vs strict JSON on pipe or `--json`. Errors as `{"error":{"code","message"}}` on stdout + exit 1 in JSON mode; red `Error:` line on stderr in pretty mode.
20
+ - **Global flags:** `--api-key`, `-p/--profile`, `--json`, `-q/--quiet`.
21
+ - Dogfoods the official SDK: every HTTP call flows through `@garuhq/node@0.1.0`.
22
+ - Two install channels at launch: **npm** (`npm install -g @garuhq/cli`) and **`curl | bash`** (`install.sh` served from GitHub raw).
23
+ - Prebuilt binaries for `linux-x64`, `darwin-arm64`, `darwin-x64` via `bun build --compile` in the release workflow.
24
+ - GitHub Actions CI runs typecheck, tests, and build on Node 18 / 20 / 22, plus shellcheck on `install.sh`.
25
+
26
+ ### Known limitations
27
+
28
+ - **`garu charges list`** intentionally omitted — the backend does not yet expose `GET /api/transactions`. Will land when the backend grows a seller-scoped list endpoint.
29
+ - **Windows binary** not yet shipped — `bun --target=windows-x64` is still beta. Windows users can `npm install -g @garuhq/cli` in the meantime.
30
+ - **Homebrew tap** not yet published — use `curl | bash` or `npm` in v0.1.
31
+ - **`garu.com.br/install.sh`** pretty URL not yet live — README advertises the GitHub raw URL until the marketing site (SPEC W1) ships.
32
+ - **`garu mcp install <tool>`** depends on the MCP server (SPEC W4) which has not shipped yet.
33
+ - **`customers`, `products`, `subscriptions`, `webhooks`, `keys`** command groups not included in v0.1 per SPEC Phase 1 scope.
34
+ - **Browser-based OAuth** deferred; v0.1 uses paste-and-validate like Stripe CLI.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Garu Pagamentos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # garu-cli
2
+
3
+ Official command-line interface for the [Garu](https://garu.com.br) payment gateway.
4
+
5
+ ```
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
16
+ ```
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ # curl | bash (Linux, macOS)
22
+ curl -fsSL https://raw.githubusercontent.com/Garu-Pagamentos/garu-cli/main/install.sh | bash
23
+
24
+ # npm (all platforms, including Windows)
25
+ npm install -g @garuhq/cli
26
+ ```
27
+
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
+ Verify the install:
32
+
33
+ ```bash
34
+ garu doctor
35
+ ```
36
+
37
+ ## Authentication
38
+
39
+ The CLI resolves your API key from the first of:
40
+
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`)
44
+
45
+ ### Interactive
46
+
47
+ ```bash
48
+ $ garu login
49
+ ? Paste your Garu API key (sk_live_... or sk_test_...) ****************
50
+ → Validating key with Garu...
51
+ ✓ Saved profile 'default' to ~/.config/garu/credentials.json
52
+ ```
53
+
54
+ ### Scripted (CI)
55
+
56
+ ```bash
57
+ export GARU_API_KEY=sk_live_...
58
+ garu charges create --type pix ...
59
+ ```
60
+
61
+ ### Multi-profile
62
+
63
+ ```bash
64
+ garu login --profile test --api-key sk_test_...
65
+ garu login --profile live --api-key sk_live_...
66
+ garu auth switch live
67
+ ```
68
+
69
+ ## Commands
70
+
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 |
82
+
83
+ See `garu <command> --help` for per-command flags.
84
+
85
+ ## Output modes
86
+
87
+ The CLI auto-detects whether it's running interactively:
88
+
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.
91
+
92
+ ```bash
93
+ # Pipe-safe
94
+ garu charges get 4472 | jq '.status'
95
+ ```
96
+
97
+ Global flags:
98
+
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
103
+
104
+ ## `garu doctor`
105
+
106
+ Prints a structured report of your environment:
107
+
108
+ ```json
109
+ {
110
+ "cli": { "version": "0.1.0" },
111
+ "api": { "reachable": true, "url": "https://garu.com.br", "version": "1.3.2" },
112
+ "credentials": { "path": "...", "source": "file", "profile": "default", "fileMode": "0600" },
113
+ "agents": {
114
+ "claudeCode": true,
115
+ "cursor": true,
116
+ "codex": false,
117
+ "windsurf": false,
118
+ "claudeDesktop": true,
119
+ "vscodeMcpInCwd": false
120
+ }
121
+ }
122
+ ```
123
+
124
+ ## License
125
+
126
+ MIT.
package/bin/garu.cjs ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim for `npm i -g @garuhq/cli` installs.
3
+ // The real CLI lives in `../dist/index.cjs`, produced by `npm run build`.
4
+ require('../dist/index.cjs');
package/dist/index.cjs ADDED
@@ -0,0 +1,591 @@
1
+ 'use strict';
2
+
3
+ var commander = require('commander');
4
+ var os = require('os');
5
+ var path = require('path');
6
+ var promises = require('fs/promises');
7
+ var pc = require('picocolors');
8
+ var node = require('@garuhq/node');
9
+ var fs = require('fs');
10
+ var prompts = require('@inquirer/prompts');
11
+
12
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
+
14
+ var pc__default = /*#__PURE__*/_interopDefault(pc);
15
+
16
+ // src/index.ts
17
+
18
+ // src/lib/errors.ts
19
+ var CliError = class extends Error {
20
+ code;
21
+ exitCode;
22
+ constructor(code, message, exitCode = 1) {
23
+ super(message);
24
+ this.name = "CliError";
25
+ this.code = code;
26
+ this.exitCode = exitCode;
27
+ }
28
+ };
29
+ function toCliError(err) {
30
+ if (err instanceof CliError)
31
+ return err;
32
+ if (err && typeof err === "object" && "name" in err && "code" in err) {
33
+ const e = err;
34
+ if (typeof e.name === "string" && e.name.startsWith("Garu")) {
35
+ return new CliError(mapSdkCodeToCliCode(e.code), e.message ?? "Request failed", 1);
36
+ }
37
+ }
38
+ if (err instanceof Error)
39
+ return new CliError("unknown_error", err.message);
40
+ return new CliError("unknown_error", String(err));
41
+ }
42
+ function mapSdkCodeToCliCode(sdkCode) {
43
+ switch (sdkCode) {
44
+ case "authentication_error":
45
+ case "permission_error":
46
+ return "auth_error";
47
+ case "not_found":
48
+ return "not_found";
49
+ case "validation_error":
50
+ return "validation_error";
51
+ case "rate_limited":
52
+ return "rate_limited";
53
+ case "server_error":
54
+ return "server_error";
55
+ case "connection_error":
56
+ return "connection_error";
57
+ case "signature_verification_failed":
58
+ return "signature_verification_failed";
59
+ default:
60
+ return "unknown_error";
61
+ }
62
+ }
63
+
64
+ // src/lib/credentials.ts
65
+ var DEFAULT_FILE = {
66
+ version: 1,
67
+ activeProfile: "default",
68
+ profiles: {}
69
+ };
70
+ function credentialsPath(env = process.env) {
71
+ if (env.GARU_CREDENTIALS_PATH)
72
+ return env.GARU_CREDENTIALS_PATH;
73
+ const base = env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
74
+ return path.join(base, "garu", "credentials.json");
75
+ }
76
+ async function loadCredentials(env = process.env) {
77
+ const path = credentialsPath(env);
78
+ try {
79
+ const raw = await promises.readFile(path, "utf8");
80
+ const parsed = JSON.parse(raw);
81
+ return normalize(parsed);
82
+ } catch (err) {
83
+ if (err.code === "ENOENT") {
84
+ return { ...DEFAULT_FILE };
85
+ }
86
+ throw new CliError(
87
+ "invalid_input",
88
+ `Failed to read credentials at ${path}: ${err.message}`
89
+ );
90
+ }
91
+ }
92
+ async function saveCredentials(file, env = process.env) {
93
+ const path = credentialsPath(env);
94
+ const dir = path.slice(0, path.lastIndexOf("/"));
95
+ await promises.mkdir(dir, { recursive: true, mode: 448 });
96
+ await promises.writeFile(path, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
97
+ }
98
+ async function deleteCredentials(env = process.env) {
99
+ const path = credentialsPath(env);
100
+ try {
101
+ await promises.rm(path);
102
+ } catch (err) {
103
+ if (err.code !== "ENOENT")
104
+ throw err;
105
+ }
106
+ }
107
+ function upsertProfile(file, name, profile) {
108
+ return {
109
+ ...file,
110
+ activeProfile: name,
111
+ profiles: { ...file.profiles, [name]: profile }
112
+ };
113
+ }
114
+ async function credentialsFileMode(env = process.env) {
115
+ try {
116
+ const s = await promises.stat(credentialsPath(env));
117
+ return s.mode & 511;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ function normalize(raw) {
123
+ if (!raw || typeof raw !== "object")
124
+ return { ...DEFAULT_FILE };
125
+ const obj = raw;
126
+ return {
127
+ version: 1,
128
+ activeProfile: obj.activeProfile ?? "default",
129
+ profiles: obj.profiles && typeof obj.profiles === "object" ? obj.profiles : {}
130
+ };
131
+ }
132
+ function resolveMode(opts = {}, stdout = process.stdout) {
133
+ if (opts.mode)
134
+ return opts.mode;
135
+ return stdout.isTTY ? "pretty" : "json";
136
+ }
137
+ function printResult(value, opts = {}) {
138
+ const mode = resolveMode(opts);
139
+ if (mode === "json") {
140
+ process.stdout.write(`${JSON.stringify(value)}
141
+ `);
142
+ return;
143
+ }
144
+ if (opts.prettyPrint) {
145
+ process.stdout.write(`${opts.prettyPrint(value)}
146
+ `);
147
+ return;
148
+ }
149
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
150
+ `);
151
+ }
152
+ function printStatus(message, opts = {}) {
153
+ if (opts.quiet)
154
+ return;
155
+ if (resolveMode(opts) === "json")
156
+ return;
157
+ process.stderr.write(`${pc__default.default.dim("\u2192")} ${message}
158
+ `);
159
+ }
160
+ function printSuccess(message, opts = {}) {
161
+ if (opts.quiet)
162
+ return;
163
+ if (resolveMode(opts) === "json")
164
+ return;
165
+ process.stderr.write(`${pc__default.default.green("\u2713")} ${message}
166
+ `);
167
+ }
168
+ function printErrorAndExit(err, opts = {}) {
169
+ const cliErr = toCliError(err);
170
+ const payload = { error: { code: cliErr.code, message: cliErr.message } };
171
+ if (resolveMode(opts) === "json") {
172
+ process.stdout.write(`${JSON.stringify(payload)}
173
+ `);
174
+ } else {
175
+ process.stderr.write(`${pc__default.default.red("Error:")} ${cliErr.message}
176
+ `);
177
+ if (cliErr.code !== "unknown_error") {
178
+ process.stderr.write(`${pc__default.default.dim(` code: ${cliErr.code}`)}
179
+ `);
180
+ }
181
+ }
182
+ process.exit(cliErr.exitCode);
183
+ }
184
+
185
+ // src/commands/auth-switch.ts
186
+ async function authSwitchCommand(opts) {
187
+ const file = await loadCredentials();
188
+ if (!file.profiles[opts.profile]) {
189
+ const available = Object.keys(file.profiles).join(", ") || "(none)";
190
+ throw new CliError("not_found", `Profile '${opts.profile}' not found. Available: ${available}`);
191
+ }
192
+ await saveCredentials({ ...file, activeProfile: opts.profile });
193
+ printSuccess(`Active profile set to '${opts.profile}'.`, opts);
194
+ return { activeProfile: opts.profile };
195
+ }
196
+
197
+ // src/lib/auth.ts
198
+ async function resolveAuth(opts = {}) {
199
+ const env = opts.env ?? process.env;
200
+ if (opts.apiKey) {
201
+ return { apiKey: opts.apiKey, source: "flag" };
202
+ }
203
+ if (env.GARU_API_KEY) {
204
+ return { apiKey: env.GARU_API_KEY, source: "env" };
205
+ }
206
+ const file = await loadCredentials(env);
207
+ const profileName = opts.profile ?? file.activeProfile;
208
+ const profile = file.profiles[profileName];
209
+ if (profile && profile.apiKey) {
210
+ return {
211
+ apiKey: profile.apiKey,
212
+ source: "file",
213
+ profile: profileName,
214
+ fileProfile: profile
215
+ };
216
+ }
217
+ throw new CliError("auth_error", "No API key found. Run `garu login` or set GARU_API_KEY.");
218
+ }
219
+ async function resolveAuthOptional(opts = {}) {
220
+ try {
221
+ return await resolveAuth(opts);
222
+ } catch (err) {
223
+ if (err instanceof CliError && err.code === "auth_error")
224
+ return null;
225
+ throw err;
226
+ }
227
+ }
228
+
229
+ // src/version.ts
230
+ var CLI_VERSION = "0.1.0";
231
+
232
+ // src/lib/client.ts
233
+ function createGaruClient(opts) {
234
+ return new node.Garu({
235
+ apiKey: opts.auth.apiKey,
236
+ baseUrl: opts.baseUrl
237
+ });
238
+ }
239
+
240
+ // src/commands/charges.ts
241
+ async function getClient(opts) {
242
+ if (opts.garu)
243
+ return opts.garu;
244
+ const auth = await resolveAuth({
245
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
246
+ ...opts.profile !== void 0 ? { profile: opts.profile } : {}
247
+ });
248
+ return createGaruClient({
249
+ auth,
250
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
251
+ });
252
+ }
253
+ async function chargesCreateCommand(opts) {
254
+ validateCreate(opts);
255
+ const garu = await getClient(opts);
256
+ const customer = {
257
+ name: opts.customerName,
258
+ email: opts.customerEmail,
259
+ document: opts.customerDocument,
260
+ phone: opts.customerPhone
261
+ };
262
+ const charge = await garu.charges.create({
263
+ productId: opts.productId,
264
+ paymentMethod: opts.type,
265
+ customer,
266
+ ...opts.additionalInfo !== void 0 ? { additionalInfo: opts.additionalInfo } : {},
267
+ ...opts.idempotencyKey !== void 0 ? { idempotencyKey: opts.idempotencyKey } : {},
268
+ ...opts.type === "credit_card" && opts.cardNumber ? {
269
+ cardInfo: {
270
+ cardNumber: opts.cardNumber,
271
+ cvv: opts.cardCvv,
272
+ expirationDate: opts.cardExpiration,
273
+ holderName: opts.cardHolder,
274
+ installments: opts.installments ?? 1
275
+ }
276
+ } : {}
277
+ });
278
+ printResult(charge, { ...opts, prettyPrint: prettyCharge });
279
+ return charge;
280
+ }
281
+ async function chargesGetCommand(opts) {
282
+ const garu = await getClient(opts);
283
+ const charge = await garu.charges.get(opts.id);
284
+ printResult(charge, { ...opts, prettyPrint: prettyCharge });
285
+ return charge;
286
+ }
287
+ async function chargesRefundCommand(opts) {
288
+ const garu = await getClient(opts);
289
+ const params = {};
290
+ if (opts.amount !== void 0)
291
+ params.amount = opts.amount;
292
+ if (opts.reason !== void 0)
293
+ params.reason = opts.reason;
294
+ if (opts.idempotencyKey !== void 0)
295
+ params.idempotencyKey = opts.idempotencyKey;
296
+ const charge = await garu.charges.refund(opts.id, params);
297
+ printResult(charge, { ...opts, prettyPrint: prettyCharge });
298
+ return charge;
299
+ }
300
+ function validateCreate(opts) {
301
+ if (opts.type === "credit_card") {
302
+ const missing = [];
303
+ if (!opts.cardNumber)
304
+ missing.push("--card-number");
305
+ if (!opts.cardCvv)
306
+ missing.push("--card-cvv");
307
+ if (!opts.cardExpiration)
308
+ missing.push("--card-expiration");
309
+ if (!opts.cardHolder)
310
+ missing.push("--card-holder");
311
+ if (missing.length) {
312
+ throw new CliError("invalid_input", `Credit-card charges require: ${missing.join(", ")}`);
313
+ }
314
+ }
315
+ }
316
+ function prettyCharge(charge) {
317
+ const lines = [
318
+ `Charge ${charge.id}`,
319
+ ` status: ${charge.status}`,
320
+ ` amount: ${charge.amount}`,
321
+ ` method: ${charge.paymentMethodId}`,
322
+ ` date: ${charge.date}`
323
+ ];
324
+ if (charge.deadline)
325
+ lines.push(` deadline: ${charge.deadline}`);
326
+ return lines.join("\n");
327
+ }
328
+ async function doctorCommand(opts = {}) {
329
+ const home = opts.home ?? os.homedir();
330
+ const cwd = opts.cwd ?? process.cwd();
331
+ const plat = opts.platform ?? os.platform();
332
+ const credentials = await reportCredentials(opts);
333
+ const api = await reportApi(opts);
334
+ const agents = detectAgents(home, cwd, plat);
335
+ const report = {
336
+ cli: { version: CLI_VERSION },
337
+ api,
338
+ credentials,
339
+ agents
340
+ };
341
+ printResult(report, { ...opts, prettyPrint: prettyDoctor });
342
+ return report;
343
+ }
344
+ async function reportCredentials(opts) {
345
+ const path = credentialsPath();
346
+ const resolved = await resolveAuthOptional({
347
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
348
+ ...opts.profile !== void 0 ? { profile: opts.profile } : {}
349
+ });
350
+ if (!resolved) {
351
+ return { path, source: "none" };
352
+ }
353
+ const out = { path, source: resolved.source };
354
+ if (resolved.profile)
355
+ out.profile = resolved.profile;
356
+ if (resolved.source === "file") {
357
+ const mode = await credentialsFileMode();
358
+ if (mode !== null) {
359
+ out.fileMode = `0${mode.toString(8)}`;
360
+ if (mode !== 384) {
361
+ out.warning = `credentials file mode is 0${mode.toString(8)}; expected 0600`;
362
+ }
363
+ }
364
+ }
365
+ return out;
366
+ }
367
+ async function reportApi(opts) {
368
+ const url = opts.baseUrl ?? "https://garu.com.br";
369
+ const garu = opts.garu ?? new node.Garu({
370
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
371
+ });
372
+ try {
373
+ const meta = await garu.meta.get();
374
+ return { reachable: true, url, version: meta.version };
375
+ } catch (err) {
376
+ return {
377
+ reachable: false,
378
+ url,
379
+ error: err instanceof Error ? err.message : String(err)
380
+ };
381
+ }
382
+ }
383
+ function detectAgents(home, cwd, plat) {
384
+ return {
385
+ claudeCode: fs.existsSync(path.join(home, ".claude")),
386
+ cursor: fs.existsSync(path.join(home, ".cursor")),
387
+ codex: fs.existsSync(path.join(home, ".codex")),
388
+ windsurf: fs.existsSync(path.join(home, ".windsurf")),
389
+ claudeDesktop: fs.existsSync(claudeDesktopConfigPath(home, plat)),
390
+ vscodeMcpInCwd: fs.existsSync(path.join(cwd, ".vscode", "mcp.json"))
391
+ };
392
+ }
393
+ function claudeDesktopConfigPath(home, plat) {
394
+ if (plat === "darwin") {
395
+ return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
396
+ }
397
+ if (plat === "win32") {
398
+ return path.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json");
399
+ }
400
+ return path.join(home, ".config", "Claude", "claude_desktop_config.json");
401
+ }
402
+ function prettyDoctor(r) {
403
+ const check = (ok) => ok ? "yes" : "no ";
404
+ const lines = [
405
+ `CLI: garu ${r.cli.version}`,
406
+ `API: ${r.api.reachable ? `reachable (${r.api.url}, backend ${r.api.version ?? "?"})` : `unreachable (${r.api.url})`}`,
407
+ r.api.error ? ` ${r.api.error}` : void 0,
408
+ `Credentials: source=${r.credentials.source}${r.credentials.profile ? ` profile=${r.credentials.profile}` : ""}${r.credentials.fileMode ? ` mode=${r.credentials.fileMode}` : ""}`,
409
+ r.credentials.warning ? ` \u26A0 ${r.credentials.warning}` : void 0,
410
+ "",
411
+ "Detected agents:",
412
+ ` Claude Code: ${check(r.agents.claudeCode)}`,
413
+ ` Cursor: ${check(r.agents.cursor)}`,
414
+ ` Codex: ${check(r.agents.codex)}`,
415
+ ` Windsurf: ${check(r.agents.windsurf)}`,
416
+ ` Claude Desktop: ${check(r.agents.claudeDesktop)}`,
417
+ ` VS Code MCP: ${check(r.agents.vscodeMcpInCwd)} (.vscode/mcp.json in cwd)`
418
+ ].filter((l) => l !== void 0);
419
+ return lines.join("\n");
420
+ }
421
+ async function loginCommand(opts = {}) {
422
+ const profile = opts.profile ?? "default";
423
+ const apiKey = opts.apiKey ?? await prompts.password({
424
+ message: "Paste your Garu API key (sk_live_... or sk_test_...)",
425
+ mask: "*"
426
+ }).catch(() => {
427
+ throw new CliError("user_cancelled", "Login cancelled.");
428
+ });
429
+ if (!apiKey || !/^sk_(live|test)_[A-Za-z0-9_]+$/.test(apiKey)) {
430
+ throw new CliError("invalid_input", "API key must look like `sk_live_...` or `sk_test_...`.");
431
+ }
432
+ printStatus("Validating key with Garu...", opts);
433
+ const garu = new node.Garu({
434
+ apiKey,
435
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
436
+ });
437
+ await garu.meta.get();
438
+ const file = await loadCredentials();
439
+ const updated = upsertProfile(file, profile, { apiKey });
440
+ await saveCredentials(updated);
441
+ printSuccess(`Saved profile '${profile}' to ~/.config/garu/credentials.json`, opts);
442
+ return { profile };
443
+ }
444
+
445
+ // src/commands/logout.ts
446
+ async function logoutCommand(opts = {}) {
447
+ if (!opts.profile) {
448
+ await deleteCredentials();
449
+ printSuccess("All saved credentials deleted.", opts);
450
+ return { cleared: "all" };
451
+ }
452
+ const file = await loadCredentials();
453
+ if (!file.profiles[opts.profile]) {
454
+ throw new CliError("not_found", `Profile '${opts.profile}' not found.`);
455
+ }
456
+ const { [opts.profile]: _removed, ...rest } = file.profiles;
457
+ const nextActive = file.activeProfile === opts.profile ? Object.keys(rest)[0] ?? "default" : file.activeProfile;
458
+ await saveCredentials({ ...file, activeProfile: nextActive, profiles: rest });
459
+ printSuccess(`Profile '${opts.profile}' removed.`, opts);
460
+ return { cleared: opts.profile };
461
+ }
462
+
463
+ // src/index.ts
464
+ function buildCli() {
465
+ const program = new commander.Command();
466
+ 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();
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) => {
468
+ const globals = getGlobals(program);
469
+ await loginCommand({
470
+ ...cmdOpts.apiKey !== void 0 ? { apiKey: cmdOpts.apiKey } : {},
471
+ profile: cmdOpts.profile,
472
+ ...globalsToOutput(globals)
473
+ }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
474
+ });
475
+ program.command("logout").description("Remove saved credentials").option("-p, --profile <name>", "only remove this profile instead of the whole file").action(async (cmdOpts) => {
476
+ const globals = getGlobals(program);
477
+ await logoutCommand({
478
+ ...cmdOpts.profile !== void 0 ? { profile: cmdOpts.profile } : {},
479
+ ...globalsToOutput(globals)
480
+ }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
481
+ });
482
+ const auth = program.command("auth").description("Manage credentials profiles");
483
+ auth.command("switch <profile>").description("Set the active credentials profile").action(async (profile) => {
484
+ const globals = getGlobals(program);
485
+ await authSwitchCommand({ profile, ...globalsToOutput(globals) }).catch(
486
+ (err) => printErrorAndExit(err, globalsToOutput(globals))
487
+ );
488
+ });
489
+ const charges = program.command("charges").description("Create, fetch, and refund charges");
490
+ 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(
491
+ "--customer-document <document>",
492
+ "CPF (11 digits) or CNPJ (14 digits), digits only"
493
+ ).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) => {
494
+ const globals = getGlobals(program);
495
+ const type = parsePaymentMethod(cmdOpts.type);
496
+ await chargesCreateCommand({
497
+ type,
498
+ productId: cmdOpts.productId,
499
+ customerName: cmdOpts.customerName,
500
+ customerEmail: cmdOpts.customerEmail,
501
+ customerDocument: cmdOpts.customerDocument,
502
+ customerPhone: cmdOpts.customerPhone,
503
+ ...cmdOpts.cardNumber !== void 0 ? { cardNumber: cmdOpts.cardNumber } : {},
504
+ ...cmdOpts.cardCvv !== void 0 ? { cardCvv: cmdOpts.cardCvv } : {},
505
+ ...cmdOpts.cardExpiration !== void 0 ? { cardExpiration: cmdOpts.cardExpiration } : {},
506
+ ...cmdOpts.cardHolder !== void 0 ? { cardHolder: cmdOpts.cardHolder } : {},
507
+ ...cmdOpts.installments !== void 0 ? { installments: cmdOpts.installments } : {},
508
+ ...cmdOpts.additionalInfo !== void 0 ? { additionalInfo: cmdOpts.additionalInfo } : {},
509
+ ...cmdOpts.idempotencyKey !== void 0 ? { idempotencyKey: cmdOpts.idempotencyKey } : {},
510
+ ...globalFlagsToCommandOptions(globals)
511
+ }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
512
+ });
513
+ charges.command("get <id>").description("Fetch a single charge by ID").action(async (id) => {
514
+ const globals = getGlobals(program);
515
+ await chargesGetCommand({
516
+ id: parseId(id),
517
+ ...globalFlagsToCommandOptions(globals)
518
+ }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
519
+ });
520
+ 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) => {
521
+ const globals = getGlobals(program);
522
+ await chargesRefundCommand({
523
+ id: parseId(id),
524
+ ...cmdOpts.amount !== void 0 ? { amount: cmdOpts.amount } : {},
525
+ ...cmdOpts.reason !== void 0 ? { reason: cmdOpts.reason } : {},
526
+ ...cmdOpts.idempotencyKey !== void 0 ? { idempotencyKey: cmdOpts.idempotencyKey } : {},
527
+ ...globalFlagsToCommandOptions(globals)
528
+ }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
529
+ });
530
+ program.command("doctor").description("Environment diagnostic").action(async () => {
531
+ const globals = getGlobals(program);
532
+ await doctorCommand({
533
+ ...globalFlagsToCommandOptions(globals)
534
+ }).catch((err) => printErrorAndExit(err, globalsToOutput(globals)));
535
+ });
536
+ return program;
537
+ }
538
+ function getGlobals(program) {
539
+ return program.opts();
540
+ }
541
+ function globalsToOutput(globals) {
542
+ const out = {};
543
+ if (globals.json)
544
+ out.mode = "json";
545
+ if (globals.quiet)
546
+ out.quiet = true;
547
+ return out;
548
+ }
549
+ function globalFlagsToCommandOptions(globals) {
550
+ const out = {};
551
+ if (globals.apiKey)
552
+ out.apiKey = globals.apiKey;
553
+ if (globals.profile)
554
+ out.profile = globals.profile;
555
+ if (globals.json)
556
+ out.mode = "json";
557
+ if (globals.quiet)
558
+ out.quiet = true;
559
+ return out;
560
+ }
561
+ function parsePaymentMethod(raw) {
562
+ if (raw === "pix" || raw === "credit_card" || raw === "boleto")
563
+ return raw;
564
+ throw new CliError(
565
+ "invalid_input",
566
+ `--type must be 'pix', 'credit_card', or 'boleto' (got '${raw}')`
567
+ );
568
+ }
569
+ function parseId(raw) {
570
+ const id = Number.parseInt(raw, 10);
571
+ if (!Number.isFinite(id) || id <= 0) {
572
+ throw new CliError("invalid_input", `Charge ID must be a positive integer (got '${raw}')`);
573
+ }
574
+ return id;
575
+ }
576
+ function getVersion() {
577
+ return "0.1.0";
578
+ }
579
+ async function main(argv = process.argv) {
580
+ const program = buildCli();
581
+ await program.parseAsync(argv);
582
+ }
583
+ var invokedDirectly = typeof process !== "undefined" && Array.isArray(process.argv) && process.argv[1] !== void 0 && /garu(-cli)?(\.(cjs|js|ts))?$/.test(process.argv[1] ?? "");
584
+ if (invokedDirectly) {
585
+ main().catch((err) => printErrorAndExit(err));
586
+ }
587
+
588
+ exports.buildCli = buildCli;
589
+ exports.main = main;
590
+ //# sourceMappingURL=out.js.map
591
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.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/version.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 { 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 // Statically imported so `bun build --compile` can inline it.\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n return '0.1.0';\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// Auto-run when invoked directly (not imported).\nconst invokedDirectly =\n typeof process !== 'undefined' &&\n Array.isArray(process.argv) &&\n process.argv[1] !== undefined &&\n /garu(-cli)?(\\.(cjs|js|ts))?$/.test(process.argv[1] ?? '');\n\nif (invokedDirectly) {\n main().catch((err) => printErrorAndExit(err));\n}\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","/**\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.0';\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"]}
@@ -0,0 +1,8 @@
1
+ import { Command } from 'commander';
2
+
3
+ /** Build the top-level command tree. Exposed so tests can exercise the router. */
4
+ declare function buildCli(): Command;
5
+ /** Main entry invoked by `bin/garu.js` and `src/index.ts` when run directly. */
6
+ declare function main(argv?: string[]): Promise<void>;
7
+
8
+ export { buildCli, main };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@garuhq/cli",
3
+ "version": "0.1.0",
4
+ "description": "Official command-line interface for the Garu payment gateway.",
5
+ "license": "MIT",
6
+ "homepage": "https://garu.com.br",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Garu-Pagamentos/garu-cli.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/Garu-Pagamentos/garu-cli/issues"
13
+ },
14
+ "keywords": [
15
+ "garu",
16
+ "cli",
17
+ "payments",
18
+ "pix",
19
+ "brazil",
20
+ "fintech"
21
+ ],
22
+ "type": "module",
23
+ "bin": {
24
+ "garu": "./bin/garu.cjs"
25
+ },
26
+ "main": "./dist/index.cjs",
27
+ "types": "./dist/index.d.ts",
28
+ "files": [
29
+ "bin",
30
+ "dist",
31
+ "README.md",
32
+ "CHANGELOG.md",
33
+ "LICENSE"
34
+ ],
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsx src/index.ts",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
45
+ },
46
+ "dependencies": {
47
+ "@garuhq/node": "0.1.0",
48
+ "@inquirer/prompts": "5.0.2",
49
+ "commander": "12.0.0",
50
+ "picocolors": "1.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "20.12.7",
54
+ "tsup": "8.0.2",
55
+ "tsx": "4.7.2",
56
+ "typescript": "5.4.5",
57
+ "vitest": "1.5.0"
58
+ }
59
+ }