-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add argument support to wallet commands (Phase 2) #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add command-line argument support for wallet import to enable non-interactive usage: Changes: - Added --private-key and --name options to wallet import command - Updated importWallet() to accept WalletImportOptions - Integrated password-handler for secure password input (env > file > CLI > prompt) - Added JSON output mode support via outputSuccess() - Added proper exit codes for different failure scenarios - Security warnings when password provided via CLI args Usage examples: # Interactive (existing behavior) safe wallet import # Non-interactive with env password SAFE_WALLET_PASSWORD="pass" safe wallet import --private-key="0x..." --name="My Wallet" # JSON output for automation safe wallet import --json --private-key="0x..." --name="Bot" --password="pass" Benefits: - Enables automation and CI/CD workflows - Maintains backward compatibility (all args optional) - Secure password handling with priority: env > file > CLI > prompt - Machine-readable JSON output when --json flag used 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add command-line argument support for wallet create:
Changes:
- Added --name and --skip-backup-warning options
- Integrated password-handler for secure password input
- Skip backup verification in non-interactive mode or with flag
- Include private key in JSON output for automation
- Added proper exit codes for failures
Usage:
# Non-interactive with auto-generated wallet
SAFE_WALLET_PASSWORD="pass" safe wallet create \
--name="Bot Wallet" \
--skip-backup-warning
# JSON output for automation
safe wallet create --json \
--name="CI Wallet" \
--skip-backup-warning \
--password="pass"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add CLI argument support for wallet use and remove commands to enable non-interactive automation: - wallet use: Add --address and --name options to select wallet - wallet remove: Add --address, --name, and --force options - Both commands support JSON output mode with --json flag - Graceful fallback to interactive mode when no arguments provided This completes Phase 2 of the wallet commands with full argument support for automation and CI/CD scenarios. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request adds non-interactive mode support to wallet management commands, enabling programmatic usage and CI/CD automation. The changes introduce CLI options for specifying wallet parameters directly (--address, --name, --private-key, etc.) and add support for JSON output mode.
Key changes:
- Added option interfaces and parameters to wallet commands (import, create, use, remove) for non-interactive operation
- Implemented conditional UI rendering based on interactive vs non-interactive mode
- Updated error handling to use standardized
outputErrorandoutputSuccesshelpers with exit codes
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| src/commands/wallet/use.ts | Added options for selecting wallets by address/name, non-interactive mode support |
| src/commands/wallet/remove.ts | Added options for wallet selection and force flag to skip confirmations |
| src/commands/wallet/import.ts | Added options for providing private key and name via CLI, integrated password handling utilities |
| src/commands/wallet/create.ts | Added options for wallet name and backup warning skip, non-interactive mode support |
| src/cli.ts | Added CLI option flags for all wallet commands to support programmatic usage |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Interactive mode - prompt user to select | ||
| const selected = await p.select({ | ||
| message: 'Select wallet to remove:', | ||
| options: wallets.map((w) => ({ | ||
| value: w.id, | ||
| label: `${w.name} (${shortenAddress(w.address)})`, | ||
| })), | ||
| }) | ||
|
|
||
| if (p.isCancel(confirm) || !confirm) { | ||
| p.cancel('Operation cancelled') | ||
| return | ||
| if (p.isCancel(selected)) { | ||
| p.cancel('Operation cancelled') | ||
| return | ||
| } |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In non-interactive mode (JSON or quiet mode), if neither --address nor --name is provided, the code will attempt to show an interactive select prompt (line 47). This will likely fail or hang in non-interactive environments (CI/CD, scripts, etc.).
Consider adding validation to require either --address or --name when in non-interactive mode, and show an error if neither is provided. For example:
if (isNonInteractiveMode() && !options.address && !options.name) {
outputError('Either --address or --name must be provided in non-interactive mode', ExitCode.INVALID_ARGS)
}| const wallet = wallets.find((w) => { | ||
| if (options.address) return w.address.toLowerCase() === options.address.toLowerCase() | ||
| if (options.name) return w.name === options.name | ||
| return false |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wallet lookup logic gives priority to address over name when both are provided (lines 34-37). If a user provides both --address and --name, only the address will be checked, and the name will be silently ignored. This could lead to unexpected behavior if the user expects both to be validated.
Consider either:
- Validating that only one option is provided and showing an error if both are given
- Validating that both match the same wallet if both are provided
- Documenting this priority behavior clearly in the CLI help text
| let name = options.name | ||
| if (!name) { | ||
| const nameInput = await p.text({ | ||
| message: 'Give this wallet a name:', | ||
| placeholder: 'my-wallet', | ||
| validate: (value) => validator.validateRequired(value, 'Wallet name'), | ||
| }) | ||
|
|
||
| if (p.isCancel(name)) { | ||
| p.cancel('Operation cancelled') | ||
| return | ||
| if (p.isCancel(nameInput)) { | ||
| p.cancel('Operation cancelled') | ||
| return | ||
| } | ||
| name = nameInput as string |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In non-interactive mode (JSON or quiet mode), if --name is not provided, the code will attempt to show an interactive text prompt (line 137). This will likely fail or hang in non-interactive environments.
Consider adding validation to require --name when in non-interactive mode, and show an error if it's not provided.
| let name = options.name | ||
| if (!name) { | ||
| const nameInput = await p.text({ | ||
| message: 'Give this wallet a name:', | ||
| placeholder: 'my-wallet', | ||
| validate: (value) => validator.validateRequired(value, 'Wallet name'), | ||
| }) | ||
|
|
||
| if (p.isCancel(name)) { | ||
| p.cancel('Operation cancelled') | ||
| return | ||
| if (p.isCancel(nameInput)) { | ||
| p.cancel('Operation cancelled') | ||
| return | ||
| } | ||
| name = nameInput as string |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In non-interactive mode (JSON or quiet mode), if --name is not provided, the code will attempt to show an interactive text prompt (line 74). This will likely fail or hang in non-interactive environments.
Consider adding validation to require --name when in non-interactive mode, and show an error if it's not provided.
|
|
||
| if (!wallet) { | ||
| const criteria = options.address ? `address: ${options.address}` : `name: ${options.name}` | ||
| outputError(`Wallet not found with ${criteria}`, ExitCode.WALLET_ERROR) |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wallet variable is accessed before it's guaranteed to be assigned. If wallet is not found (line 40-43), outputError is called which exits the process, but TypeScript doesn't know this function never returns. The code at line 45 will try to access wallet.id when wallet is still undefined.
To fix this, ensure that after calling outputError on line 42, the code does not continue. Since outputError has a return type of never, add an explicit return statement or restructure to avoid the access.
| outputError(`Wallet not found with ${criteria}`, ExitCode.WALLET_ERROR) | |
| outputError(`Wallet not found with ${criteria}`, ExitCode.WALLET_ERROR) | |
| return |
| name: wallet.name, | ||
| address: wallet.address, | ||
| type: wallet.type, | ||
| privateKey: privateKey, // Include in automation mode for immediate use |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outputting the private key in non-interactive mode (line 200) poses a significant security risk. Private keys should never be exposed in command outputs, logs, or console messages, even in automation scenarios. This could lead to:
- Private keys being logged to files
- Keys appearing in CI/CD logs
- Accidental exposure in shared terminals or screenshots
Consider removing the privateKey field from the success output. If the private key must be available for automation, use a separate secure mechanism (e.g., write to a secure file with restricted permissions) rather than stdout.
| privateKey: privateKey, // Include in automation mode for immediate use |
| } else { | ||
| // Interactive mode - prompt user to select | ||
| const currentActive = walletStorage.getActiveWallet() | ||
|
|
||
| const selected = await p.select({ | ||
| message: 'Select wallet to use:', | ||
| options: wallets.map((wallet) => ({ | ||
| value: wallet.id, | ||
| label: `${wallet.name} (${shortenAddress(wallet.address)})${ | ||
| currentActive?.id === wallet.id ? ' [current]' : '' | ||
| }`, | ||
| })), | ||
| }) | ||
|
|
||
| if (p.isCancel(selected)) { | ||
| p.cancel('Operation cancelled') | ||
| return | ||
| } |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In non-interactive mode (JSON or quiet mode), if neither --address nor --name is provided, the code will attempt to show an interactive select prompt (line 50). This will likely fail or hang in non-interactive environments (CI/CD, scripts, etc.).
Consider adding validation to require either --address or --name when in non-interactive mode, and show an error if neither is provided.
| let privateKey = options.privateKey | ||
| if (!privateKey) { | ||
| const privateKeyInput = await p.password({ | ||
| message: 'Enter your private key:', | ||
| validate: (value) => validator.validatePrivateKey(value), | ||
| }) | ||
|
|
||
| if (!checkCancelled(privateKey)) return | ||
| if (!checkCancelled(privateKeyInput)) return | ||
| privateKey = privateKeyInput as string |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In non-interactive mode (JSON or quiet mode), if --private-key is not provided, the code will attempt to show an interactive password prompt (line 56). This will likely fail or hang in non-interactive environments.
Consider adding validation to require --private-key when in non-interactive mode, and show an error if it's not provided.
|
|
||
| if (!wallet) { | ||
| const criteria = options.address ? `address: ${options.address}` : `name: ${options.name}` | ||
| outputError(`Wallet not found with ${criteria}`, ExitCode.WALLET_ERROR) |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wallet variable is accessed before it's guaranteed to be assigned. If wallet is not found (line 39-42), outputError is called which exits the process, but the code at line 44 will still try to access wallet.id when wallet is undefined.
Although outputError has a return type of never and exits the process, TypeScript may not infer this correctly without an explicit return or assertion.
| outputError(`Wallet not found with ${criteria}`, ExitCode.WALLET_ERROR) | |
| outputError(`Wallet not found with ${criteria}`, ExitCode.WALLET_ERROR) | |
| return |
| // Validate provided name | ||
| const error = validator.validateRequired(name, 'Wallet name') | ||
| if (error) { | ||
| outputError(error, ExitCode.INVALID_ARGS) |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar validation pattern issue as in import.ts. On lines 150-152, if the name validation returns an error, outputError is called, which exits the process. However, the code structure makes it appear that execution could continue, which is confusing for both readers and potentially for TypeScript's control flow analysis.
Consider restructuring for clearer control flow.
| outputError(error, ExitCode.INVALID_ARGS) | |
| outputError(error, ExitCode.INVALID_ARGS) | |
| return |
Summary
This PR implements Phase 2 of the non-interactive CLI enhancement, adding full argument support to all wallet commands. This enables automation and CI/CD scenarios.
Changes
wallet import
--private-key <key>option to import without prompting--name <name>option to set wallet name--jsonflagwallet create
--name <name>option to set wallet name--skip-backup-warningflag to bypass security confirmationwallet use
--address <address>option to switch wallet by address--name <name>option to switch wallet by name--jsonflagwallet remove
--address <address>option to remove wallet by address--name <name>option to remove wallet by name--forceflag to skip confirmation prompts--jsonflagAll commands maintain backward compatibility with interactive mode when no arguments are provided.
Examples
Test plan
Related
Part of the phased plan to add argument support to all CLI commands. This completes Phase 2 (wallet commands). Next phases will cover account and transaction commands.
🤖 Generated with Claude Code