diff --git a/package-lock.json b/package-lock.json index 9163725..fc1cf6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "chalk": "^5.4.1", "commander": "^13.1.0", "ink": "^7.0.0", - "react": "^19.2.5" + "react": "^19.2.5", + "strip-ansi": "^7.2.0" }, "bin": { "codeburn": "dist/cli.js" diff --git a/package.json b/package.json index fec34af..718d763 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "chalk": "^5.4.1", "commander": "^13.1.0", "ink": "^7.0.0", - "react": "^19.2.5" + "react": "^19.2.5", + "strip-ansi": "^7.2.0" }, "devDependencies": { "@types/node": "^22.19.17", diff --git a/src/bash-utils.ts b/src/bash-utils.ts index c578972..2e5fe0d 100644 --- a/src/bash-utils.ts +++ b/src/bash-utils.ts @@ -1,12 +1,14 @@ import { basename } from 'path' +import stripAnsi from 'strip-ansi' function stripQuotedStrings(command: string): string { return command.replace(/"[^"]*"|'[^']*'/g, match => ' '.repeat(match.length)) } -export function extractBashCommands(command: string): string[] { - if (!command || !command.trim()) return [] +export function extractBashCommands(rawCommand: string): string[] { + if (!rawCommand || !rawCommand.trim()) return [] + const command = stripAnsi(rawCommand) const stripped = stripQuotedStrings(command) const separatorRegex = /\s*(?:&&|;|\|)\s*/g diff --git a/src/providers/gemini.ts b/src/providers/gemini.ts index 48b3107..d00f0dc 100644 --- a/src/providers/gemini.ts +++ b/src/providers/gemini.ts @@ -3,6 +3,7 @@ import { join } from 'path' import { homedir } from 'os' import { calculateCost } from '../models.js' +import { extractBashCommands } from '../bash-utils.js' import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js' const toolNameMap: Record = { @@ -93,8 +94,7 @@ function parseSession(data: GeminiSession, seenKeys: Set): ParsedProvide const mapped = toolNameMap[tc.displayName ?? ''] ?? toolNameMap[tc.name] ?? tc.displayName ?? tc.name allTools.push(mapped) if (mapped === 'Bash' && tc.args && typeof tc.args.command === 'string') { - const cmd = tc.args.command.split(/\s+/)[0] ?? '' - if (cmd) bashCommands.push(cmd) + bashCommands.push(...extractBashCommands(tc.args.command)) } } } diff --git a/src/providers/goose.ts b/src/providers/goose.ts index b46fa13..27f0c03 100644 --- a/src/providers/goose.ts +++ b/src/providers/goose.ts @@ -2,6 +2,7 @@ import { join } from 'path' import { homedir, platform } from 'os' import { calculateCost, getShortModelName } from '../models.js' +import { extractBashCommands } from '../bash-utils.js' import { isSqliteAvailable, getSqliteLoadError, openDatabase, type SqliteDatabase } from '../sqlite.js' import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js' @@ -109,8 +110,9 @@ function extractToolsFromMessages(db: SqliteDatabase, sessionId: string): { tool if (mapped === 'Bash') { const cmd = item.toolCall?.value?.arguments?.command if (typeof cmd === 'string') { - const first = cmd.split(/\s+/)[0] ?? '' - if (first && !bashCommands.includes(first)) bashCommands.push(first) + for (const c of extractBashCommands(cmd)) { + if (!bashCommands.includes(c)) bashCommands.push(c) + } } } } diff --git a/src/providers/openclaw.ts b/src/providers/openclaw.ts index 14575df..bc6da53 100644 --- a/src/providers/openclaw.ts +++ b/src/providers/openclaw.ts @@ -4,6 +4,7 @@ import { homedir } from 'os' import { readSessionFile } from '../fs-utils.js' import { calculateCost } from '../models.js' +import { extractBashCommands } from '../bash-utils.js' import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js' const toolNameMap: Record = { @@ -78,8 +79,7 @@ function extractTools(content: Array<{ type?: string; name?: string; arguments?: const mapped = toolNameMap[block.name] ?? block.name tools.push(mapped) if (mapped === 'Bash' && block.arguments && typeof block.arguments.command === 'string') { - const cmd = block.arguments.command.split(/\s+/)[0] ?? '' - if (cmd) bashCommands.push(cmd) + bashCommands.push(...extractBashCommands(block.arguments.command)) } } } diff --git a/src/providers/qwen.ts b/src/providers/qwen.ts index 3b61ce4..427b5fd 100644 --- a/src/providers/qwen.ts +++ b/src/providers/qwen.ts @@ -4,6 +4,7 @@ import { homedir } from 'os' import { readSessionFile } from '../fs-utils.js' import { calculateCost } from '../models.js' +import { extractBashCommands } from '../bash-utils.js' import type { Provider, SessionSource, SessionParser, ParsedProviderCall } from './types.js' const toolNameMap: Record = { @@ -66,8 +67,7 @@ function extractTools(parts: QwenPart[]): { tools: string[]; bashCommands: strin const mapped = toolNameMap[part.functionCall.name] ?? part.functionCall.name tools.push(mapped) if (mapped === 'Bash' && part.functionCall.args && typeof part.functionCall.args['command'] === 'string') { - const cmd = (part.functionCall.args['command'] as string).split(/\s+/)[0] ?? '' - if (cmd) bashCommands.push(cmd) + bashCommands.push(...extractBashCommands(part.functionCall.args['command'] as string)) } } }