Skip to content

Commit 6bef7b0

Browse files
authored
Improvements (#35)
* updated errors with error codes * made improvements to allow the disabling of the spinner animation * implemented changes to improve the codebase and upgrade packages * improved file reading and writing * updated jsdocs * removed unused code * improved the cardKey handling * removed duplicate logic * added error codes to the readme * Cleaned up error handling * added tests and other small improvements * added testing to readme * improved command help text * improved the output formats * added yaml outtput * improved error handling on required values * added file validation * further improvements * allowed normalised filenames * piping * added history * added autocomplete * added unix error codes * added command categories * Added improved command descriptions * added validation * added aliases * added standard confirmations for all destructive commands * added config profiles and atomic writes * Added rate limiting support * added support for large file uploads * corrected tests after the large upload changes * added generate docs command * updated the dependencies * removed unused md files * Added initial improvements identified from CLIG * added suport for temp dir * added the ability to customize the editor used * improved handling of terminals not supporting emojis * improved the security posture on keys * corrected lit errors * improved the AI instruction sets * added an example CI_CD file * added coderabbit improvements * copilot suggestions * coderabbit improvements * further improvements from coderabbit * corrected errors * Improve spinner lifecycle and CLI error handling. Ensure spinners always clear cleanly across commands, normalize Investec API failures into CliError messages, and tighten table rendering/verbose behavior for cleaner terminal output. Made-with: Cursor * Add phased CLI improvement plan document. Capture prioritized workstreams for spinner/error hardening, command refactoring, utils modularization, and integration testing. Made-with: Cursor * Add utility baseline tests and PB auth error normalization. Cover debug/spinner/error helper behavior in unit tests and normalize Programmable Banking auth failures to consistent CliError output. Made-with: Cursor * Add shared spinner wrapper and migrate core list commands. Introduce withSpinner() for standardized spinner lifecycle handling and refactor accounts/cards commands to use it for cleaner control flow and guaranteed cleanup. Made-with: Cursor * Migrate balances and transactions to shared spinner wrapper. Use withSpinner() for consistent spinner lifecycle handling and cleaner command flow in balance and transaction fetch commands. Made-with: Cursor * Migrate remaining list commands to withSpinner wrapper. Apply shared spinner lifecycle handling to beneficiaries, countries, currencies, and merchants commands for consistent cleanup and reduced boilerplate. Made-with: Cursor * Migrate write commands to withSpinner lifecycle wrapper. Apply shared spinner handling to fetch/logs/upload/env/published/publish commands and tighten guarded writes for strict type safety. Made-with: Cursor * Migrate remaining spinner-heavy commands to withSpinner. Refactor deploy, disable, toggle, run, and ai commands to use shared spinner lifecycle management for consistent cleanup and simpler control flow. Made-with: Cursor * Add outcome-aware spinner helper for command status flows. Introduce withSpinnerOutcome() and migrate transfer/upload-env to shared success-fail spinner handling instead of manual start/succeed/fail blocks. Made-with: Cursor * Add shared list-output runner and migrate accounts/cards. Introduce runListCommand() to centralize empty/list output behavior and refactor accounts/cards commands plus tests to use the shared helper. Made-with: Cursor * Migrate remaining list commands to shared list runner. Refactor beneficiaries, countries, currencies, and merchants commands to use runListCommand for consistent list output behavior and reduced duplication. Made-with: Cursor * Add shared write runner and migrate fetch/logs commands. Introduce runWriteCommand() for common file write progress/success output and refactor fetch/logs plus tests to use the new abstraction. Made-with: Cursor * Migrate env and published commands to shared write runner. Use runWriteCommand() for consistent write progress and final size output when saving env variables and published code. Made-with: Cursor * Add shared read-upload runner and migrate upload/publish. Introduce runReadUploadCommand() to unify read-and-upload spinner flow and refactor upload and publish commands plus tests to use the helper. Made-with: Cursor * Migrate balances and transactions to shared list runner. Use runListCommand() for transaction and balance output flow and update tests to assert list-runner integration. Made-with: Cursor * Standardize env-list output and expand shared-runner tests. Route env-list structured output through runListCommand and add direct utility coverage for list/read-upload/outcome runners plus command coverage for env-list. Made-with: Cursor * Begin Phase 3 utils extraction with runtime and API error modules. Move runtime flag helpers and Investec error normalization into dedicated utility modules while preserving existing imports through utils.ts re-exports. Made-with: Cursor * Extract terminal, spinner, output, and runner helpers from utils. Split terminal detection, spinner lifecycle, output formatting, and command runner utilities into dedicated modules while keeping utils.ts as a compatibility re-export surface. Made-with: Cursor * Extract API initialization and credential validation utilities. Move Card/PB API initialization and credential validation logic into dedicated modules and keep utils.ts as a compatibility export surface. Made-with: Cursor * Extract command history utilities into dedicated module. Move history path/read/log logic and entry typing into utils/history.ts while preserving existing imports through utils.ts re-exports. Made-with: Cursor * Extract update-check and version notification utilities. Move npm version lookup, update cache handling, and notification rendering into utils/update.ts while preserving public access via utils.ts re-exports. Made-with: Cursor * Extract credentials and profile storage into dedicated module. Move credentials IO, atomic writes, and profile management into utils/credentials-store.ts and update history/update modules to import atomic writes directly from the new module. Made-with: Cursor * Extract retry and rate-limit handling into dedicated module. Move rate-limit detection, formatting, and exponential-backoff retry logic into utils/retry.ts while preserving existing imports through utils.ts re-exports. Made-with: Cursor * Extract file path and permission validation utilities. Move path normalization, extension validation, and read/write permission checks into utils/file-validation.ts while preserving existing imports via utils.ts re-exports. Made-with: Cursor * Extract core input validation helpers into dedicated module. Move account and amount validation logic into utils/input-validation.ts while preserving existing usage via utils.ts re-exports. Made-with: Cursor * Extract security and CLI error orchestration utilities. Move environment secret warning logic and CLI error handling/context wrappers into dedicated modules while keeping utils.ts as the compatibility export surface. Made-with: Cursor * Extract interaction and editor helper utilities. Move paging, temp/module path helpers, editor launching, and destructive-operation confirmation into utils/interaction.ts while keeping utils.ts compatibility re-exports. Made-with: Cursor * Add --no-spinner compatibility and deprecate legacy spinner flag. Support --no-spinner by normalizing it to existing spinner-disable behavior, emit a deprecation warning for --spinner/-s, and include the new flag in generated command docs/completion metadata. Made-with: Cursor * Harden spinner flag compatibility with targeted normalization tests. Extract spinner flag normalization into a dedicated utility for testability and add tests covering --no-spinner mapping plus deprecation detection for legacy --spinner/-s usage. Made-with: Cursor * Add integration safety-net tests for core command flows. Introduce command-level integration coverage for accounts/cards piped output and deploy/fetch spinner cleanup on errors to catch regressions in terminal-mode behavior. Made-with: Cursor * Apply lint-driven cleanup and stabilize recent refactor outputs. Run project-wide Biome fixes, resolve remaining lint blockers in publish/upload/output helpers, and align formatting/import order updates across affected command, utility, config, and template files. Made-with: Cursor * Expand utility test coverage for extracted modules. Add focused tests for security, file validation, interaction, and retry utilities to improve regression detection after the utils modularization work. Made-with: Cursor * Harden credentials loading, output safety, and history redaction. Fix JSON credentials-file import handling for ESM and relative paths, tighten write-permission validation behavior, route update notices to stderr, and add focused regression tests for credentials loading, file permissions, update output, and history sanitization. Made-with: Cursor * Improve CI stability and extend destructive command test coverage. Suppress update notifications for machine-readable output modes, replace environment-specific file paths in utility tests with portable temp files, and add focused pay/transfer command tests for confirmation and prompt-driven flows. Made-with: Cursor * Harden release automation and add snap distribution pipeline. This improves CI/release safety with tighter workflow permissions and concurrency, adds packaged binary smoke testing, and automates Snap build/publish with documented credential setup for GitHub Actions. Made-with: Cursor * Apply lint-driven formatting cleanup across utils and tests. This normalizes import ordering and line wrapping in utility and test modules to keep style output consistent after recent refactors. Made-with: Cursor * Expand command coverage for run and upload-env flows. Add focused tests for run command environment parsing and missing-env handling, plus upload-env success/error scenarios and default card key behavior to reduce regression risk in high-impact command paths. Made-with: Cursor * Broaden command test coverage across reference data, writes, and tooling. Add tests for simulate, register, config/profile flows, enable/disable, env/published/logs output paths, countries/currencies/merchants/beneficiaries, new project scaffolding, and docs generation to reduce regression risk in high-impact CLI paths. Made-with: Cursor * Fix CI TypeScript errors and add Husky pre-push verification. Harden loadCredentialsFile JSON import typing for tsc, use NodeJS.ErrnoException for write permission checks, add husky with pre-push running verify:ci, and wire prepare script so installs configure git hooks. Made-with: Cursor * Load credentials JSON without dynamic import; add PERMISSION_DENIED error code. Read credentials files with readFile and JSON.parse to avoid Vitest/Vite dynamic-import URL warnings, support optional default-wrapped JSON, and map EACCES/EPERM to E4017 with correct exit handling and CLI tips. Made-with: Cursor * chore: align Node.js to 22+ (engines, CI, docs) - Set engines.node to >=22.0.0 in package.json and lockfile - Run CI and release workflows on Node 22.x - Update README, .cursorrules, and Copilot instructions Made-with: Cursor * test: Phase 1 output and Investec error coverage - Add printTable/formatOutput tests (narrow terminal, NO_COLOR, piped JSON) - Add normalizeInvestecError unit tests at utils/investec-errors - Extend resolveSpinnerState cases (TTY on, DEBUG disables spinner) - Biome import order fixes in existing command tests Made-with: Cursor * Fix pkg smoke test by bundling cli-table3 without createRequire. createRequire(import.meta.url) fails in the esbuild CJS bundle inside pkg because import.meta.url is undefined there, which broke CI packaging-smoke when running --version. Made-with: Cursor * Target Node 24 for standalone binaries and esbuild bundle. pkg scripts, package.json pkg.targets, pkg.config.json, and release/snap/CI packaging workflows now use node24-* targets; esbuild uses node24. Release and snap jobs use Node 24.x when building binaries. Made-with: Cursor * Require Node 24 and drop Node 22 from CI. CI matrix and npm publish use Node 24.x only; engines and @types/node align with >=24. Documentation and Cursor rules updated accordingly. Made-with: Cursor * Use snap base core24 for the ipb snap. Moves the snap runtime from Ubuntu 22.04 (core22) to Ubuntu 24.04 (core24). Made-with: Cursor * refactor: shared spinner lifecycle for pay/simulate/auth flows - pay: isStdoutPiped-aware title, withSpinner around payMultiple + withRetry - simulate: addApiCredentialOptions, SimulateCommandOptions, withSpinner for cloud execute - register/login/bank: addSpinnerVerboseOptions and withSpinner for HTTP calls - login: getSafeText for token saved message - test: integration-safety mocks terminal isStdoutPiped; piped/TTY and spinner cases - test: pay stubs isStdoutPiped and createSpinner for stable output Made-with: Cursor * refactor: run command uses resolveSpinnerState and addSpinnerVerboseOptions - Respect --spinner/--no-spinner, verbose, and piped stdout for local emulator - Skip title box when stdout is piped; getSafeText on spinner labels - Shell completion lists --spinner for run/r Made-with: Cursor * refactor: ai and new commands use shared spinner lifecycle - ai: resolveSpinnerState, withSpinner around generateCode and file writes - ai: skip title when piped; static utils imports; getSafeText on success lines - new: addSpinnerVerboseOptions, withSpinner for rm+cp when forcing overwrite - index: wrap ai/new with addSpinnerVerboseOptions; completion adds --spinner - test: new stubs isStdoutPiped, createSpinner, withSpinner Made-with: Cursor * refactor: config and docs use shared spinner lifecycle - config: runWithConfigSpinner for deleteProfile, writeProfile, writeCredentialsFile - docs: addSpinnerVerboseOptions, piped-aware title, withSpinner for gen+write - docsCommand accepts DocsCommandOptions (verbose, spinner) - test: config/docs utils mocks for isStdoutPiped, createSpinner, withSpinner Made-with: Cursor * fix(completion): include docs command in bash/zsh generators - Add docs to completion command list (was missing) - Add docs-specific --output hint (globals still append shared flags) Made-with: Cursor * feat(completion): bash tab completion for config profile subcommands - After config|cfg, complete profile, edit, and credential/global flags - After "config profile", complete list, ls, set, show, delete, rm - Zsh script unchanged (still offers config flags only) Made-with: Cursor * feat(cli): offline tapes, update-skip env, and Phase A tests - Add IPB_MOCK_APIS so mock PB/Card APIs work without DEBUG verbose output - Add IPB_NO_UPDATE_CHECK to skip npm version checks (tapes and offline runs) - Wire npm run tapes to fixture creds, strict shell, mock APIs, no update fetch - Document env vars in env list; export isMockApisEnabled and isUpdateCheckDisabled Tests: credentials file ESM-style default unwrap; update banner gated for piped/json/yaml/--output; stderr-only showUpdateNotification; mock/spinner cases. Refresh VHS-generated assets under assets/ for the new tape environment. Made-with: Cursor * feat(cli): disable AI/auth commands; unify config profile helpers - Hide ai, bank, register, login; they throw E4018 (COMMAND_DISABLED) for scripts - Add config-subcommands module for profile list/set/show/delete and config edit - Wire ipb config profile * and config edit to shared helpers; set.ts uses same paths - Drop generateCommand/bankCommand from cmds barrel export; update completion and help - Document OPENAI_API_KEY/SANDBOX_KEY as reserved while AI/login are disabled Tests: config-subcommands suite; extend config tests; add logs/published tests; remove register command tests. Regenerate GENERATED_README.md. Made-with: Cursor * docs: align README and generated docs with disabled AI/auth commands - README: replace AI/Bank how-tos with disabled-command note; fix TOC; refresh testing/env sections - Remove stale TEST_SUGGESTIONS.md; add IMPROVEMENT_PLAN maintenance note - Skip hidden commands in docs generator (isCommandHidden); test coverage - Regenerate GENERATED_README.md via filtered command list Made-with: Cursor * docs: replace improvement plan with current status summary Archive original phases as completed; add summary table, definition-of-done notes, and optional follow-ups instead of an outdated task backlog. Made-with: Cursor * revert(tapes): restore DEBUG-only script; drop fixtures; refresh assets - scripts/tapes.sh: export DEBUG=true again (no fixtures, IPB_MOCK_APIS, or strict bash) - Remove tapes/fixtures credentials; adjust IPB_MOCK_APIS env-list wording - Update VHS-generated GIFs under assets/ after re-recording with system ipb Made-with: Cursor * chore(scripts): remove empty placeholder scripts Drop unused zero-byte advanced-fix-tests, fix-spinner-mock, fix-tests, test-accounts, and verify-process-emitter files. Made-with: Cursor * docs(readme): remove AI, register/login, and sandbox key references Made-with: Cursor
1 parent 53132d3 commit 6bef7b0

153 files changed

Lines changed: 17853 additions & 2796 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.biomeignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
bin/
2+
ai-generated.js
3+

.cursor/rules/code-review.mdc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
alwaysApply: false
3+
---
4+
- name: Senior Professional Reviewer
5+
description: |
6+
Act as a seasoned staff-level engineer delivering concise, high-signal code reviews.
7+
Emphasize security, reliability, performance, and maintainability while aligning feedback
8+
with team standards and product goals.
9+
guidelines:
10+
- Start with a prioritized list of critical findings (blocking issues, regressions, security risks).
11+
- Provide actionable remediation steps referencing relevant standards, documentation, or patterns.
12+
- Highlight tests to add or adjust, focusing on coverage for edge cases and non-regression.
13+
- Summarize low-severity observations separately, framing them as suggestions.
14+
- Encourage best practices in collaboration, including clear ownership, reviewer checklists,
15+
and follow-up tracking.
16+
- Leverage static analysis insights, architectural context, and prior review history to validate
17+
the change holistically.

.cursorrules

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
# Investec Programmable Banking CLI - Cursor Rules
2+
3+
## Project Technology Stack
4+
5+
### Core Technologies
6+
- **Node.js**: >=24.0.0 (ESM modules only)
7+
- **TypeScript**: 5.9.3 with strict mode
8+
- **Module System**: ESM (ES Modules) - all imports use `.js` extension
9+
- **CLI Framework**: Commander.js 14.x
10+
- **Testing**: Vitest 3.x with ESM support
11+
- **Linting/Formatting**: Biome 2.3.x (replaces ESLint/Prettier)
12+
- **Build Tool**: TypeScript compiler (tsc)
13+
14+
### Key Dependencies
15+
- `commander`: CLI framework for command definitions
16+
- `chalk`: Terminal colors (v5, respects NO_COLOR/FORCE_COLOR)
17+
- `ora`: Spinner/progress indicators
18+
- `cli-table3`: Enhanced table formatting
19+
- `js-yaml`: YAML serialization
20+
- `@inquirer/prompts`: Interactive prompts (confirm, input, password)
21+
- `investec-pb-api`: Programmable Banking API client
22+
- `investec-card-api`: Card API client
23+
- `programmable-card-code-emulator`: Local code execution emulator
24+
25+
## Project Setup
26+
27+
### Directory Structure
28+
```
29+
src/
30+
├── index.ts # Entry point, command registration, global options
31+
├── utils.ts # Shared utilities (2500+ lines of helper functions)
32+
├── errors.ts # CliError class, ExitCode enum, ERROR_CODES
33+
├── cmds/ # Command implementations (30+ commands)
34+
│ ├── index.ts # Command exports
35+
│ ├── types.ts # Shared interfaces (CommonOptions, Credentials, etc.)
36+
│ └── *.ts # Individual command files
37+
├── mock-pb.ts # Mock Programmable Banking API for testing
38+
└── mock-card.ts # Mock Card API for testing
39+
40+
test/
41+
├── __mocks__/ # Mock implementations (ora, utils, external-editor, etc.)
42+
├── cmds/ # Command tests
43+
├── helpers.ts # Test helper utilities
44+
└── setup.js # Vitest setup
45+
46+
bin/ # Compiled output (gitignored, generated)
47+
templates/ # Project templates (default, petro)
48+
```
49+
50+
### Build & Development
51+
- **Build**: `npm run build` → TypeScript compiles `src/` to `bin/`
52+
- **Source Maps**: Enabled for debugging
53+
- **Declaration Files**: Generated for library consumers
54+
- **File Copying**: Templates and assets copied to `bin/` during build
55+
56+
### TypeScript Configuration
57+
- **Target**: ES2022
58+
- **Module**: NodeNext (ESM)
59+
- **Strict Mode**: Enabled with `noUncheckedIndexedAccess` and `noImplicitOverride`
60+
- **Module Resolution**: NodeNext
61+
- **Import Extensions**: Must use `.js` for ESM imports (TypeScript requirement)
62+
63+
## Code Patterns & Conventions
64+
65+
### Error Handling Pattern
66+
```typescript
67+
// 1. Use CliError for user-facing errors
68+
throw new CliError(ERROR_CODES.MISSING_CARD_KEY, 'Card key is required');
69+
70+
// 2. Wrap commands with context
71+
export const myCommand = withCommandContext('my-command', async (options) => {
72+
// Command implementation
73+
});
74+
75+
// 3. Centralized error handling in main()
76+
catch (error) {
77+
handleCliError(error, { verbose: getVerboseMode(true) }, 'command name');
78+
}
79+
```
80+
81+
### Command Structure Pattern
82+
```typescript
83+
export async function commandName(options: CommonOptions) {
84+
// 1. Validate inputs
85+
const normalizedPath = await validateFilePath(options.filename, ['.js']);
86+
87+
// 2. Initialize API with retry support
88+
const api = await initializePbApi(credentials, options);
89+
const verbose = getVerboseMode(options.verbose);
90+
91+
// 3. Show progress (if not piped)
92+
const spinner = createSpinner(!isStdoutPiped(), getSafeText('💳 Processing...'));
93+
94+
// 4. Make API calls with retry
95+
const result = await withRetry(() => api.someMethod(), { verbose });
96+
97+
// 5. Format output
98+
formatOutput(result.data, options);
99+
}
100+
```
101+
102+
### Utility Functions Usage
103+
- **Credentials**: `loadCredentialsFile()`, `readCredentialsFile()`, `writeCredentialsFile()`
104+
- **Profiles**: `getActiveProfile()`, `setActiveProfile()`, `readProfile()`, `deleteProfile()`
105+
- **Terminal**: `getSafeText()`, `detectTerminalCapabilities()`, `getTerminalDimensions()`
106+
- **Output**: `formatOutput()`, `printTable()`, `formatFileSize()`
107+
- **Validation**: `validateAmount()`, `validateAccountId()`, `validateFilePath()`
108+
- **API**: `initializeApi()`, `initializePbApi()`, `withRetry()`
109+
- **Security**: `warnAboutSecretUsage()`, `detectSecretUsageFromEnv()`
110+
- **History**: `logCommandHistory()`, `readCommandHistory()`
111+
112+
### Import Patterns
113+
```typescript
114+
// Node.js built-ins (use node: prefix)
115+
import { promises as fsPromises } from 'node:fs';
116+
import { homedir } from 'node:os';
117+
118+
// External packages
119+
import chalk from 'chalk';
120+
import { Command } from 'commander';
121+
122+
// Internal modules (use .js extension)
123+
import { CliError, ERROR_CODES } from '../errors.js';
124+
import { formatOutput, getVerboseMode } from '../utils.js';
125+
126+
// Type-only imports
127+
import type { CommonOptions, Credentials } from './types.js';
128+
```
129+
130+
## Testing Patterns
131+
132+
### Mock Setup
133+
```typescript
134+
vi.mock('../../src/utils.ts', async () => {
135+
const actual = await vi.importActual<typeof import('../../src/utils.ts')>();
136+
return {
137+
...actual,
138+
initializeApi: vi.fn(),
139+
createSpinner: vi.fn(() => ({
140+
start: vi.fn(function() { return this; }),
141+
stop: vi.fn(),
142+
text: '',
143+
})),
144+
};
145+
});
146+
```
147+
148+
### Test Structure
149+
- Use `describe()` blocks for grouping
150+
- Use `it()` for individual test cases
151+
- Mock external dependencies
152+
- Test both success and error paths
153+
- Use `expect().toThrow(CliError)` for error testing
154+
155+
## Code Quality Standards
156+
157+
### Biome Configuration
158+
- **Quote Style**: Single quotes
159+
- **Semicolons**: Always
160+
- **Indentation**: 2 spaces
161+
- **Line Width**: 100 characters
162+
- **Trailing Commas**: ES5 style
163+
- **Arrow Parentheses**: Always
164+
165+
### Linting Rules
166+
- `noExplicitAny`: Error (use `biome-ignore` comments when necessary, e.g., generic function wrappers)
167+
- `noUnusedVariables`: Error
168+
- `noUnusedImports`: Auto-fixed
169+
- Recommended rules enabled
170+
171+
### Code Style Requirements
172+
1. **JSDoc Comments**: Required for exported functions
173+
2. **Type Safety**: Avoid `any`, use proper types or `unknown`
174+
3. **Error Handling**: Always use `CliError` for user-facing errors
175+
4. **Async/Await**: Prefer over promises
176+
5. **Terminal Safety**: Use `getSafeText()` for emoji/Unicode text
177+
6. **Progress Indicators**: Use `createSpinner()` for operations
178+
7. **Output Formatting**: Use `formatOutput()` for structured data
179+
180+
## Environment-Specific Behavior
181+
182+
### Terminal Detection
183+
- Automatically detects terminal capabilities (Unicode/emoji support)
184+
- Falls back to ASCII when terminal doesn't support emojis
185+
- Respects `TERM`, `NO_COLOR`, `FORCE_COLOR` environment variables
186+
- Detects piped output (`isStdoutPiped()`) to suppress interactive elements
187+
188+
### Non-Interactive Mode
189+
- Detects CI/CD environments (GitHub Actions, GitLab CI, etc.)
190+
- Shows security warnings when secrets are in environment variables
191+
- Suppresses interactive prompts when appropriate
192+
193+
### Verbose/Debug Mode
194+
- `--verbose` flag or `DEBUG=1` environment variable
195+
- Shows detailed API calls, rate limit info, retry attempts
196+
- Uses `getVerboseMode()` utility to check both sources
197+
198+
## Security Considerations
199+
200+
### Secret Management
201+
- **Preferred**: Store in credential files (`~/.ipb/.credentials.json` or profiles)
202+
- **Warning**: Environment variables are detected and warned about
203+
- **File Permissions**: Credential files use `0o600` (read/write owner only)
204+
- **Atomic Writes**: `writeFileAtomic()` ensures data integrity
205+
206+
### Input Validation
207+
- Always validate user inputs (amounts, account IDs, file paths)
208+
- Use validation utilities: `validateAmount()`, `validateAccountId()`, `validateFilePath()`
209+
- Provide clear error messages with suggestions
210+
211+
## Command Development Guidelines
212+
213+
### Adding New Commands
214+
1. Create file in `src/cmds/` following naming convention (`kebab-case.ts`)
215+
2. Export function following pattern: `export async function commandName(options: Options)`
216+
3. Add to `src/cmds/index.ts` exports
217+
4. Register in `src/index.ts` with `.command()`, `.description()`, `.action()`
218+
5. Add options using `.option()` or `.requiredOption()`
219+
6. Add tests in `test/cmds/`
220+
7. Update shell completion scripts if needed
221+
8. Add to `GENERATED_README.md` (auto-generated via `npm run docs`)
222+
223+
### Command Options
224+
- Use `CommonOptions` interface for shared options (verbose, json, yaml, output, profile, etc.)
225+
- Add command-specific options to command's `Options` interface
226+
- Use `.requiredOption()` for required inputs
227+
- Provide helpful descriptions and examples
228+
229+
### Output Formatting
230+
- Support `--json`, `--yaml`, `--output` flags via `formatOutput()`
231+
- Automatically detect piped output and use JSON format
232+
- Use `printTable()` for tabular data (with `cli-table3`)
233+
- Show file sizes in progress indicators using `formatFileSize()`
234+
235+
### Destructive Operations
236+
- Require confirmation using `confirmDestructiveOperation()`
237+
- Support `--yes` flag to bypass confirmation
238+
- Check for non-interactive mode to auto-confirm when appropriate
239+
240+
## File Operations
241+
242+
### File Path Handling
243+
- Use `validateFilePath()` for reading files
244+
- Use `validateFilePathForWrite()` for writing files
245+
- Use `normalizeFilePath()` to expand `~` and resolve paths
246+
- Check file extensions and permissions
247+
248+
### Atomic Operations
249+
- Use `writeFileAtomic()` for credential files and critical data
250+
- Ensures data integrity with temp file + rename pattern
251+
- Handles permissions and cleanup automatically
252+
253+
## API Integration
254+
255+
### API Client Initialization
256+
- Use `initializeApi()` for Card API
257+
- Use `initializePbApi()` for Programmable Banking API
258+
- Both support credential overrides via options
259+
- Both validate credentials before initialization
260+
261+
### Rate Limiting
262+
- All API calls wrapped in `withRetry()` for automatic retry
263+
- Exponential backoff with jitter
264+
- Detects rate limits from error responses
265+
- Shows rate limit info in verbose mode
266+
267+
### Error Handling
268+
- API errors caught and converted to `CliError` when appropriate
269+
- Rate limit errors automatically retried
270+
- Network errors provide actionable suggestions
271+
- Validation errors show specific field requirements
272+
273+
## Testing Guidelines
274+
275+
### Test File Structure
276+
- One test file per command in `test/cmds/`
277+
- Mock external dependencies (API clients, file system, etc.)
278+
- Test both success and error cases
279+
- Use `vi.hoisted()` for shared mocks
280+
281+
### Mock Patterns
282+
- Mock `../../src/utils.ts` with actual implementations + overrides
283+
- Mock `../../src/index.ts` for credentials and shared exports
284+
- Mock file system operations
285+
- Mock terminal capabilities for consistent output
286+
287+
## Common Utilities Reference
288+
289+
### Credential Management
290+
- `loadCredentialsFile()`: Load credentials with profile support
291+
- `readCredentialsFile()`: Read credentials file async
292+
- `readCredentialsFileSync()`: Read credentials file sync (module init)
293+
- `writeCredentialsFile()`: Write credentials with atomic operation
294+
- `validateCredentialsFile()`: Validate required fields
295+
296+
### Profile Management
297+
- `getActiveProfile()`: Get currently active profile name
298+
- `setActiveProfile()`: Set active profile (atomic write)
299+
- `readProfile()`: Read profile credentials
300+
- `deleteProfile()`: Delete profile and credentials
301+
- `listProfiles()`: List all available profiles
302+
303+
### Terminal & Output
304+
- `getSafeText()`: Get terminal-safe text (emoji fallbacks)
305+
- `formatOutput()`: Format data as JSON/YAML/table
306+
- `printTable()`: Print formatted table using cli-table3
307+
- `createSpinner()`: Create progress spinner (auto-handles pipe mode)
308+
- `formatFileSize()`: Format bytes as human-readable size
309+
310+
### Validation
311+
- `validateAmount()`: Validate monetary amounts
312+
- `validateAccountId()`: Validate account ID format
313+
- `validateFilePath()`: Validate file path for reading
314+
- `validateFilePathForWrite()`: Validate file path for writing
315+
316+
### API & Retry
317+
- `initializeApi()`: Initialize Card API client
318+
- `initializePbApi()`: Initialize Programmable Banking API client
319+
- `withRetry()`: Wrap function with retry logic (rate limit handling)
320+
- `detectRateLimit()`: Detect rate limit from error
321+
- `formatRateLimitInfo()`: Format rate limit info for display
322+
323+
### Security & Environment
324+
- `warnAboutSecretUsage()`: Warn about secrets in environment variables
325+
- `detectSecretUsageFromEnv()`: Detect secrets loaded from env vars
326+
- `isNonInteractiveEnvironment()`: Detect CI/CD/script environments
327+
- `getVerboseMode()`: Get effective verbose mode (flag or DEBUG env)
328+
329+
## Important Notes
330+
331+
1. **ESM Only**: All imports must use `.js` extension (TypeScript requirement for ESM)
332+
2. **Type Safety**: Strict TypeScript with `noUncheckedIndexedAccess`
333+
3. **Terminal Safety**: Always use `getSafeText()` for emoji/Unicode output
334+
4. **Error Context**: Use `withCommandContext()` to attach command names to errors
335+
5. **Rate Limiting**: Always use `withRetry()` for API calls
336+
6. **Security**: Prefer credential files over environment variables for secrets
337+
7. **Testing**: Mock terminal capabilities for consistent test output
338+
8. **Documentation**: Generate docs with `npm run docs` (updates GENERATED_README.md)
339+
340+
## When Writing Code
341+
342+
- Follow existing patterns in the codebase
343+
- Use provided utility functions instead of reinventing
344+
- Check for terminal capabilities before using advanced features
345+
- Support both interactive and non-interactive (CI/CD) environments
346+
- Provide clear, actionable error messages
347+
- Include JSDoc comments for exported functions
348+
- Write tests for new commands
349+
- Run `npm run lint:fix` before committing
350+

0 commit comments

Comments
 (0)