Skip to content

Commit 5b00942

Browse files
katspaughclaude
andauthored
feat: add AI-powered command suggestions for unrecognized commands (#39)
* feat: add AI-powered command suggestions for unrecognized commands When a CLI command is not recognized, the CLI now asks an AI tool to suggest the correct command. Features: - Cascading fallback through AI tools: ollama → claude → copilot - Address masking: 0x addresses are replaced with placeholders (0xAAAA, 0xBBBB, etc.) before sending to AI and restored in the response - Auto-detection of ollama models (prefers 2-5GB models) - Dynamic extraction of available commands from Commander.js AI tool configurations: - ollama: auto-detects available model, uses --quiet flag - claude: uses haiku model with --print flag - copilot: uses claude-haiku-4.5 model with --prompt flag 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: address PR review comments - Add outer try-catch wrapper around async command handler to prevent unhandled promise rejections - Add clarifying comment for ollamaModel caching logic (empty string means no models available) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: address additional PR review comments - Fix comment "3B-8B models" to "2-5GB models" to match implementation - Change address regex to only match full 40-char Ethereum addresses (was matching any hex string like 0x1, 0xabc which could be tx data) - Escape placeholder string in RegExp constructor for safety - Update test to reflect new address matching behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: use raw output flags instead of stripping ANSI - Add `-o raw=true` to claude CLI args for clean output - Remove stripAnsi method (no longer needed with --quiet and raw flags) - Keep NO_COLOR env var as additional fallback 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: prioritize ollama over cloud AI tools Move ollama to the top of the AI tools fallback list since it: - Works offline without API keys - Has faster response times when running locally - Avoids network latency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: revert address regex to match all hex strings Keep masking any hex string starting with 0x (not just 40-char addresses) since we want to mask transaction hashes, calldata, and other hex values for privacy, not just Ethereum addresses. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b28b5da commit 5b00942

File tree

7 files changed

+826
-275
lines changed

7 files changed

+826
-275
lines changed

AGENTS.md

Lines changed: 1 addition & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -1,275 +1 @@
1-
# Safe CLI Development Guide
2-
3-
## 🚨 CRITICAL SAFETY WARNING 🚨
4-
5-
**NEVER run tests without isolated storage!** Integration tests were previously written in a dangerous way that could **DELETE YOUR ACTUAL WALLET DATA AND SAFE CONFIGURATIONS**.
6-
7-
### Mandatory Safety Rules:
8-
9-
1. **ALL integration tests MUST use `createTestStorage()`** from `src/tests/helpers/test-storage.ts`
10-
2. **NEVER instantiate storage classes without the `cwd` option** in test mode
11-
3. **ALWAYS verify tests are using `/tmp` directories** before running
12-
4. **Backup your config before running tests** if unsure
13-
14-
The storage classes now have built-in safety checks that will throw an error if you try to use non-temp directories in test mode.
15-
16-
### Safe Test Pattern (REQUIRED):
17-
18-
```typescript
19-
import { createTestStorage } from '../helpers/test-storage.js'
20-
import { WalletStorageService } from '../../storage/wallet-store.js'
21-
22-
describe('My Test', () => {
23-
let testStorage: ReturnType<typeof createTestStorage>
24-
let walletStorage: WalletStorageService
25-
26-
beforeEach(() => {
27-
// REQUIRED: Create isolated test storage
28-
testStorage = createTestStorage('my-test')
29-
walletStorage = new WalletStorageService({ cwd: testStorage.configDir })
30-
})
31-
32-
afterEach(() => {
33-
// REQUIRED: Cleanup test directories
34-
testStorage.cleanup()
35-
})
36-
})
37-
```
38-
39-
### Dangerous Pattern (FORBIDDEN):
40-
41-
```typescript
42-
// ❌ NEVER DO THIS IN TESTS - touches real user config!
43-
const walletStorage = new WalletStorageService()
44-
walletStorage.getAllWallets().forEach(w => walletStorage.removeWallet(w.id)) // DELETES REAL DATA!
45-
```
46-
47-
## Pre-Commit Checklist
48-
49-
Run the following commands before committing:
50-
51-
```bash
52-
npm run lint # Check code style and potential issues
53-
npm run format # Format code with Prettier
54-
npm run typecheck # Run TypeScript type checking
55-
npm run test # Run unit and integration tests
56-
```
57-
58-
If any errors pop up, fix them before committing.
59-
60-
## Development Workflow
61-
62-
### Testing
63-
64-
#### Unit Tests
65-
Unit tests are located in `src/tests/unit/` and cover:
66-
- Services (`src/services/*`)
67-
- Utilities (`src/utils/*`)
68-
- Storage (`src/storage/*`)
69-
70-
Run unit tests:
71-
```bash
72-
npm test # Run all tests (excluding integration/e2e)
73-
npm test -- --watch # Run tests in watch mode
74-
npm test -- --ui # Run tests with Vitest UI
75-
```
76-
77-
#### Integration Tests
78-
Integration tests are in `src/tests/integration/` and test:
79-
- Full workflows (wallet import, Safe creation, transaction lifecycle)
80-
- Service integration
81-
- Storage integration
82-
- Transaction building and parsing
83-
84-
Run integration tests explicitly (they require blockchain access):
85-
```bash
86-
npm test src/tests/integration/integration-*.test.ts
87-
```
88-
89-
#### E2E Tests
90-
E2E tests verify the CLI commands work correctly:
91-
- `e2e-cli.test.ts` - Basic CLI functionality
92-
- `e2e-wallet-commands.test.ts` - Wallet operations
93-
- `e2e-config-commands.test.ts` - Configuration management
94-
- `e2e-account-commands.test.ts` - Account operations
95-
- `e2e-tx-commands.test.ts` - Transaction commands
96-
- `integration-full-workflow.test.ts` - Complete end-to-end workflow
97-
98-
Run E2E tests:
99-
```bash
100-
# Build the CLI first
101-
npm run build
102-
103-
# Run E2E tests (requires TEST_WALLET_PK environment variable)
104-
TEST_WALLET_PK=0x... npm test src/tests/integration/e2e-*.test.ts
105-
```
106-
107-
#### Coverage
108-
Check test coverage:
109-
```bash
110-
npm test -- --coverage # Generate coverage report
111-
```
112-
113-
Coverage thresholds are configured in `vitest.config.ts`:
114-
- Lines: 30%
115-
- Functions: 69%
116-
- Branches: 85%
117-
- Statements: 30%
118-
119-
### Project Structure
120-
121-
```
122-
src/
123-
├── commands/ # CLI command implementations (0% coverage - tested via E2E)
124-
│ ├── account/ # Safe account management
125-
│ ├── config/ # Configuration management
126-
│ ├── tx/ # Transaction operations
127-
│ └── wallet/ # Wallet management
128-
├── services/ # Business logic (87% coverage)
129-
│ ├── abi-service.ts
130-
│ ├── api-service.ts
131-
│ ├── contract-service.ts
132-
│ ├── ledger-service.ts
133-
│ ├── safe-service.ts
134-
│ ├── transaction-builder.ts
135-
│ ├── transaction-service.ts
136-
│ └── validation-service.ts
137-
├── storage/ # Data persistence (81% coverage)
138-
│ ├── config-store.ts
139-
│ ├── safe-store.ts
140-
│ ├── transaction-store.ts
141-
│ └── wallet-store.ts
142-
├── ui/ # CLI interface (0% coverage - interactive components)
143-
│ ├── components/
144-
│ ├── hooks/
145-
│ └── screens/
146-
├── utils/ # Utilities (96% coverage)
147-
│ ├── balance.ts
148-
│ ├── eip3770.ts
149-
│ ├── errors.ts
150-
│ ├── ethereum.ts
151-
│ └── validation.ts
152-
└── tests/
153-
├── fixtures/ # Test data and mocks
154-
├── helpers/ # Test utilities
155-
├── integration/ # Integration and E2E tests
156-
└── unit/ # Unit tests
157-
```
158-
159-
### Configuration and Storage
160-
161-
If in the course of development or testing you need to clear or modify the local configs, back up the existing ones first, and restore them when finished.
162-
163-
Configuration is stored in:
164-
- Config: `~/.config/@safe-global/safe-cli/config.json`
165-
- Data: `~/.local/share/@safe-global/safe-cli/`
166-
167-
For testing with isolated directories, use `XDG_CONFIG_HOME` and `XDG_DATA_HOME`:
168-
```bash
169-
XDG_CONFIG_HOME=/tmp/test-config XDG_DATA_HOME=/tmp/test-data npm run dev
170-
```
171-
172-
### Adding New Features
173-
174-
1. **Create the service/utility** - Write the core logic with tests
175-
2. **Add storage layer** (if needed) - Implement data persistence
176-
3. **Create command** - Implement the CLI command in `src/commands/`
177-
4. **Add E2E test** - Verify the command works end-to-end
178-
5. **Update documentation** - Add to README if user-facing
179-
180-
### Debugging
181-
182-
Run CLI in development mode:
183-
```bash
184-
npm run dev -- <command> # Run with tsx (fast reload)
185-
DEBUG=* npm run dev -- <command> # Run with debug logging
186-
```
187-
188-
Build and run production version:
189-
```bash
190-
npm run build
191-
node dist/index.js <command>
192-
```
193-
194-
### Code Style
195-
196-
- TypeScript strict mode enabled
197-
- ESLint for linting
198-
- Prettier for formatting
199-
- Husky for pre-commit hooks
200-
- lint-staged for staged file checking
201-
202-
### Common Patterns
203-
204-
#### Error Handling
205-
Use custom error classes from `src/utils/errors.ts`:
206-
```typescript
207-
import { ValidationError, SafeError } from '../utils/errors.js'
208-
209-
throw new ValidationError('Invalid address format')
210-
throw new SafeError('Failed to create Safe')
211-
```
212-
213-
#### Address Validation
214-
Support both plain and EIP-3770 addresses:
215-
```typescript
216-
import { parseEIP3770Address } from '../utils/eip3770.js'
217-
import { validateAddress } from '../utils/validation.js'
218-
219-
const { chainId, address } = parseEIP3770Address('sep:0x...')
220-
validateAddress(address) // throws if invalid
221-
```
222-
223-
#### Storage
224-
All storage services follow the same pattern:
225-
```typescript
226-
import { ConfigStore } from '../storage/config-store.js'
227-
228-
const store = new ConfigStore()
229-
store.set('key', value)
230-
const value = store.get('key')
231-
```
232-
233-
### Testing Best Practices
234-
235-
1. **Isolate test data** - Use temporary directories for test configs/data
236-
2. **Mock external dependencies** - Mock API calls and blockchain interactions
237-
3. **Test error cases** - Verify error handling and edge cases
238-
4. **Use factories** - Use test helpers from `src/tests/helpers/factories.ts`
239-
5. **Clean up after tests** - Remove temporary files/directories in `afterEach`
240-
241-
### Environment Variables
242-
243-
- `TEST_WALLET_PK` - Private key for E2E tests (Sepolia testnet)
244-
- `XDG_CONFIG_HOME` - Custom config directory
245-
- `XDG_DATA_HOME` - Custom data directory
246-
- `NODE_ENV` - Set to 'test' during testing
247-
- `CI` - Set to 'true' for non-interactive mode
248-
249-
### Blockchain Testing
250-
251-
E2E tests that interact with blockchain require:
252-
- A funded Sepolia test wallet
253-
- `TEST_WALLET_PK` environment variable set
254-
- Network access to Sepolia RPC and Safe API
255-
256-
Get Sepolia ETH:
257-
- [Sepolia Faucet](https://sepoliafaucet.com/)
258-
- [Alchemy Sepolia Faucet](https://sepoliafaucet.com/)
259-
260-
### Troubleshooting
261-
262-
**Tests timing out:**
263-
- Increase timeout in test: `{ timeout: 60000 }`
264-
- Check network connectivity
265-
- Verify RPC endpoints are accessible
266-
267-
**Interactive prompts in tests:**
268-
- Use `CLITestHelper.execWithInput()` for tests with prompts
269-
- Set `CI=true` environment variable for non-interactive mode
270-
- Consider adding `--yes` flags to commands
271-
272-
**Storage conflicts:**
273-
- Use isolated directories with `XDG_CONFIG_HOME` and `XDG_DATA_HOME`
274-
- Clean up in `afterEach` hooks
275-
- Use `mkdtempSync()` for temporary directories
1+
Read @CLAUDE.md

0 commit comments

Comments
 (0)