Skip to content

Commit ec2d1f7

Browse files
katspaughclaude
andcommitted
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 <[email protected]>
1 parent b28b5da commit ec2d1f7

File tree

3 files changed

+510
-0
lines changed

3 files changed

+510
-0
lines changed

src/cli.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { pullTransactions } from './commands/tx/pull.js'
2929
import { syncTransactions } from './commands/tx/sync.js'
3030
import { handleError } from './utils/errors.js'
3131
import { setGlobalOptions, type GlobalOptions } from './types/global-options.js'
32+
import { getAISuggestionService } from './services/ai-suggestion-service.js'
3233

3334
const program = new Command()
3435

@@ -53,8 +54,73 @@ program
5354
})
5455
})
5556

57+
/**
58+
* Recursively extracts all commands from a Commander program/command.
59+
* Returns commands in format like "config init", "wallet list", etc.
60+
*/
61+
function getAvailableCommands(cmd: Command, prefix: string = ''): string[] {
62+
const commands: string[] = []
63+
64+
for (const subCmd of cmd.commands) {
65+
const cmdName = subCmd.name()
66+
const fullName = prefix ? `${prefix} ${cmdName}` : cmdName
67+
const usage = subCmd.usage()
68+
69+
// Add the command with its usage (arguments)
70+
if (usage) {
71+
commands.push(`${fullName} ${usage}`)
72+
} else {
73+
commands.push(fullName)
74+
}
75+
76+
// Recursively get subcommands
77+
commands.push(...getAvailableCommands(subCmd, fullName))
78+
}
79+
80+
return commands
81+
}
82+
83+
/**
84+
* Adds an unknown command handler with AI suggestions to a command.
85+
* @param cmd The command to add the handler to
86+
* @param cmdPath The path to this command (e.g., "tx" or "config chains")
87+
*/
88+
function addUnknownCommandHandler(cmd: Command, cmdPath: string = ''): void {
89+
cmd.on('command:*', async (operands: string[]) => {
90+
const unknownCommand = operands[0]
91+
const args = operands.slice(1)
92+
const fullUnknown = cmdPath ? `${cmdPath} ${unknownCommand}` : unknownCommand
93+
94+
console.error(`error: unknown command '${fullUnknown}'`)
95+
96+
// Try to get AI suggestion
97+
const aiService = getAISuggestionService()
98+
console.error('\nAsking AI for suggestions...')
99+
100+
try {
101+
const availableCommands = getAvailableCommands(program)
102+
const suggestion = await aiService.getSuggestion(fullUnknown, args, availableCommands)
103+
if (suggestion) {
104+
console.error('\nAI suggestion:')
105+
console.error(suggestion)
106+
} else {
107+
console.error('\nNo AI tools available for suggestions.')
108+
console.error(`Run 'safe --help' to see available commands.`)
109+
}
110+
} catch {
111+
console.error(`\nRun 'safe --help' to see available commands.`)
112+
}
113+
114+
process.exit(1)
115+
})
116+
}
117+
118+
// Handle unknown commands at root level
119+
addUnknownCommandHandler(program)
120+
56121
// Config commands
57122
const config = program.command('config').description('Manage CLI configuration')
123+
addUnknownCommandHandler(config, 'config')
58124

59125
config
60126
.command('init')
@@ -80,6 +146,7 @@ config
80146

81147
// Config chains commands
82148
const chains = config.command('chains').description('Manage chain configurations')
149+
addUnknownCommandHandler(chains, 'config chains')
83150

84151
chains
85152
.command('list')
@@ -127,6 +194,7 @@ chains
127194

128195
// Wallet commands
129196
const wallet = program.command('wallet').description('Manage wallets and signers')
197+
addUnknownCommandHandler(wallet, 'wallet')
130198

131199
wallet
132200
.command('import')
@@ -207,6 +275,7 @@ wallet
207275

208276
// Account commands
209277
const account = program.command('account').description('Manage Safe accounts')
278+
addUnknownCommandHandler(account, 'account')
210279

211280
account
212281
.command('create')
@@ -316,6 +385,7 @@ account
316385

317386
// Transaction commands
318387
const tx = program.command('tx').description('Manage Safe transactions')
388+
addUnknownCommandHandler(tx, 'tx')
319389

320390
tx.command('create')
321391
.description('Create a new transaction')

0 commit comments

Comments
 (0)