mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-20 17:47:19 +00:00
- Adds support for Pi (pi.ai) as a new session provider. - Pi sessions are stored as JSONL files under `~/.pi/agent/sessions/<project-dir>/` and use OpenAI-compatible model IDs (gpt-5, gpt-5.4, gpt-4o, etc.). - `src/providers/pi.ts` (new): Pi provider - discovers JSONL session files, parses assistant turns, extracts token counts, tool calls, and bash commands, deduplicates via response ID with line-index fallback - `src/providers/types.ts`: added bashCommands field to `ParsedProviderCall` so all providers carry extracted bash command lists - `src/providers/index.ts`: registered Pi as a core provider alongside Claude and Codex - `src/providers/codex.ts`, `cursor.ts`: added `bashCommands: []` to satisfy the new required field on `ParsedProviderCall` - `src/parser.ts`: fixed bug where `providerCallToTurn` always emitted an empty bashCommands array instead of passing through the parsed commands - `src/classifier.ts`: added lowercase tool name variants (bash, edit, read, write) to match Pi's tool naming convention in JSONL output - `src/bash-utils.ts`: exclude `true`, `false`, and shell variable assignments from extracted commands; scan past leading `NAME=val` tokens so `FOO=bar ls` correctly records `ls` rather than being dropped - `package.json`: added pi to keywords - `tests/providers/pi.test.ts` (new): 16 unit tests covering session discovery, multi-turn parsing, tool/bash extraction, deduplication, zero-token filtering, and display name mapping - `tests/provider-registry.test.ts`: updated core provider list to include pi - [X] Unit tests pass (`npx vitest run`, 56 tests across 6 files); - [X] Manually verified via `npx tsx src/cli.ts` report and showing Pi sessions alongside Claude and Codex in the dashboard.
44 lines
1.3 KiB
TypeScript
44 lines
1.3 KiB
TypeScript
import { basename } from 'path'
|
|
|
|
function stripQuotedStrings(command: string): string {
|
|
return command.replace(/"[^"]*"|'[^']*'/g, match => ' '.repeat(match.length))
|
|
}
|
|
|
|
export function extractBashCommands(command: string): string[] {
|
|
if (!command || !command.trim()) return []
|
|
|
|
const stripped = stripQuotedStrings(command)
|
|
|
|
const separatorRegex = /\s*(?:&&|;|\|)\s*/g
|
|
const separators: Array<{ start: number; end: number }> = []
|
|
let match: RegExpExecArray | null
|
|
|
|
while ((match = separatorRegex.exec(stripped)) !== null) {
|
|
separators.push({ start: match.index, end: match.index + match[0].length })
|
|
}
|
|
|
|
const ranges: Array<[number, number]> = []
|
|
let cursor = 0
|
|
for (const sep of separators) {
|
|
ranges.push([cursor, sep.start])
|
|
cursor = sep.end
|
|
}
|
|
ranges.push([cursor, command.length])
|
|
|
|
const commands: string[] = []
|
|
for (const [start, end] of ranges) {
|
|
const segment = command.slice(start, end).trim()
|
|
if (!segment) continue
|
|
|
|
const tokens = segment.split(/\s+/)
|
|
let i = 0
|
|
while (i < tokens.length && /^\w+=/.test(tokens[i]!)) i++
|
|
const base = i < tokens.length ? basename(tokens[i]!) : ''
|
|
|
|
if (base && base !== 'cd' && base !== 'true' && base !== 'false') {
|
|
commands.push(base)
|
|
}
|
|
}
|
|
|
|
return commands
|
|
}
|