Skip to content

Commit bec3ab3

Browse files
Implement history management feature with database integration and CLI commands
1 parent c9babff commit bec3ab3

8 files changed

Lines changed: 340 additions & 31 deletions

File tree

deno.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
"tasks": {
33
"dev": "deno run --watch src/main.ts",
44
"build": "deno compile --output build/ --allow-env --allow-run --allow-net --allow-read --allow-write src/main.ts",
5-
"buildLocal": "deno compile --output build/ComputerUseAgent -A src/main.ts"
5+
"buildLocal": "deno compile --output build/ComputerUseAgent -A --unstable-ffi src/main.ts"
66
},
77
"imports": {
8-
"@std/assert": "jsr:@std/assert@1"
8+
"@std/assert": "jsr:@std/assert@1",
9+
"@db/sqlite": "jsr:@db/sqlite",
10+
"table": "jsr:@sauber/table"
911
}
1012
}

deno.lock

Lines changed: 64 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commands/history.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {parseArgs} from "jsr:@std/cli/parse-args"
2+
import {PromptDatabase, type PromptEntry} from "../modules/db/database.ts"
3+
import {HistoryViewer} from "../modules/cli/history_viewer.ts"
4+
import {log} from "../config/logging.ts"
5+
6+
export async function handleHistory(args: string[]) {
7+
8+
const db = new PromptDatabase()
9+
const flags = parseArgs(args, {
10+
string: ["view", "limit"],
11+
default: {limit: "10"},
12+
})
13+
14+
try {
15+
if (flags.view) {
16+
const id = parseInt(flags.view)
17+
if (isNaN(id)) {
18+
console.error("Invalid ID provided")
19+
return
20+
}
21+
const entry = db.getPromptById(id)
22+
23+
if (entry) {
24+
HistoryViewer.displayEntry(entry)
25+
} else {
26+
console.error(`No entry found with ID ${id}`)
27+
}
28+
} else {
29+
const limit = parseInt(flags.limit || "10")
30+
31+
const entries: PromptEntry[] = db.listPrompts(isNaN(limit) ? 10 : limit)
32+
HistoryViewer.displayHistory(entries)
33+
}
34+
} catch (error) {
35+
log.error(`Error displaying history: ${error}`)
36+
}
37+
}

src/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {EditorSession} from "./modules/editor/editor_session.ts"
1111
import {BashSession} from "./modules/bash/bash_session.ts"
1212
import {HybridSession} from "./modules/hybrid/hybrid_session.ts"
1313
import {determineIntent} from "./utils/intent.ts"
14+
import {handleHistory} from "./commands/history.ts"
1415

1516
async function main() {
1617
await setupLogging()
@@ -29,6 +30,11 @@ async function main() {
2930
const mode = flags.mode
3031
log.debug(`Mode selected: ${mode}`)
3132

33+
if (flags._[0] === "history") {
34+
await handleHistory(flags._.map(arg => String(arg)))
35+
return
36+
}
37+
3238
if (mode === "editor") {
3339
const session = new EditorSession(sessionId)
3440
await session.processEdit(prompt)

src/modules/cli/history_viewer.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {blue, bold, green, magenta, yellow} from "jsr:@std/fmt/colors"
2+
import {Table} from "table"
3+
4+
export class HistoryViewer {
5+
static displayHistory(entries: Array<{
6+
id: number
7+
timestamp: string
8+
mode: string
9+
prompt: string
10+
result: string
11+
tokens_used: number
12+
cost: number
13+
}>) {
14+
console.clear()
15+
16+
const table = new Table()
17+
table.theme = Table.roundTheme
18+
table.headers = [
19+
bold("ID"),
20+
bold("Time"),
21+
bold("Mode"),
22+
bold("Prompt"),
23+
bold("Cost ($)")
24+
]
25+
26+
table.rows = entries.map(entry => [
27+
blue(entry.id.toString()),
28+
green(entry.timestamp),
29+
yellow(entry.mode),
30+
entry.prompt.slice(0, 50) + (entry.prompt.length > 50 ? "..." : ""),
31+
magenta(entry.cost.toFixed(6))
32+
])
33+
34+
console.log(table.toString())
35+
console.log("\nUse 'view <id>' to see full details of a specific entry")
36+
}
37+
38+
static displayEntry(entry: {
39+
id: number
40+
timestamp: string
41+
mode: string
42+
prompt: string
43+
result: string
44+
tokens_used: number
45+
cost: number
46+
}) {
47+
console.clear()
48+
console.log(bold("\nPrompt Details:"))
49+
console.log("═".repeat(50))
50+
console.log(blue(`ID: ${entry.id}`))
51+
console.log(green(`Time: ${entry.timestamp}`))
52+
console.log(yellow(`Mode: ${entry.mode}`))
53+
console.log(magenta(`Tokens: ${entry.tokens_used}`))
54+
console.log(magenta(`Cost: $${entry.cost.toFixed(6)}`))
55+
console.log("\n" + bold("Prompt:"))
56+
console.log("─".repeat(50))
57+
console.log(entry.prompt)
58+
console.log("\n" + bold("Result:"))
59+
console.log("─".repeat(50))
60+
console.log(entry.result)
61+
console.log("\n")
62+
}
63+
}

src/modules/db/database.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {Database} from "@db/sqlite"
2+
import {join} from "jsr:@std/path"
3+
import {ensureDir} from "jsr:@std/fs"
4+
import {homedir} from "node:os"
5+
import {format} from "jsr:@std/datetime"
6+
import {log} from "../../config/logging.ts"
7+
8+
export type PromptEntry = {
9+
id: number
10+
timestamp: string
11+
mode: string
12+
prompt: string
13+
result: string
14+
tokens_used: number
15+
cost: number
16+
}
17+
18+
export class PromptDatabase {
19+
private db: Database
20+
21+
constructor() {
22+
try {
23+
const dbPath = join(homedir(), ".ComputerUseAgent", "data")
24+
// Ensure directory exists with recursive creation
25+
ensureDir(dbPath)
26+
27+
const dbFile = join(dbPath, "history.db")
28+
this.db = new Database(dbFile)
29+
this.initialize()
30+
} catch (error) {
31+
log.error(`Failed to initialize database: ${error}`)
32+
throw new Error(`Database initialization failed: ${error}`)
33+
}
34+
}
35+
36+
private initialize() {
37+
this.db.exec(`
38+
CREATE TABLE IF NOT EXISTS prompts (
39+
id INTEGER PRIMARY KEY AUTOINCREMENT,
40+
timestamp TEXT NOT NULL,
41+
mode TEXT NOT NULL,
42+
prompt TEXT NOT NULL,
43+
result TEXT,
44+
tokens_used INTEGER,
45+
cost REAL
46+
)
47+
`)
48+
}
49+
50+
async savePrompt(data: {
51+
mode: string
52+
prompt: string
53+
result: string
54+
tokens_used: number
55+
cost: number
56+
}) {
57+
58+
const timestamp = format(new Date(), "yyyy-MM-dd HH:mm:ss")
59+
const number = await this.db.exec(
60+
`INSERT INTO prompts (timestamp, mode, prompt, result, tokens_used, cost)
61+
VALUES (:timestamp, :mode, :prompt, :result, :tokens, :cost)`,
62+
{
63+
timestamp,
64+
mode: data.mode,
65+
prompt: data.prompt,
66+
result: data.result,
67+
tokens: data.tokens_used,
68+
cost: data.cost
69+
}
70+
)
71+
log.debug({number})
72+
return number
73+
}
74+
75+
listPrompts(limit = 10) {
76+
const stmt = this.db.prepare(
77+
`SELECT * FROM prompts ORDER BY timestamp DESC LIMIT ?`
78+
)
79+
const results = stmt.all(limit)
80+
return results.map((row: any) => ({
81+
id: row.id,
82+
timestamp: row.timestamp,
83+
mode: row.mode,
84+
prompt: row.prompt,
85+
result: row.result,
86+
tokens_used: row.tokens_used,
87+
cost: row.cost
88+
}))
89+
}
90+
91+
getPromptById(id: number): PromptEntry | null {
92+
const stmt = this.db.prepare(
93+
`SELECT * FROM prompts WHERE id = ?`
94+
)
95+
const results = stmt.all(id)
96+
return results.length === 0 ? null : {
97+
id: results[0].id,
98+
timestamp: results[0].timestamp,
99+
mode: results[0].mode,
100+
prompt: results[0].prompt,
101+
result: results[0].result,
102+
tokens_used: results[0].tokens_used,
103+
cost: results[0].cost
104+
}
105+
}
106+
}

src/modules/hybrid/hybrid_session.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,13 @@ export class HybridSession extends BaseSession {
7777
}
7878

7979
this.logger.logTotalCost()
80+
await this.logInteraction('hybrid', prompt, "passed")
8081
} catch (error) {
8182
log.error(`Error in hybrid process: ${error instanceof Error ? error.message : String(error)}`)
8283
if (error instanceof Error && error.stack) {
8384
log.error(error.stack)
8485
}
86+
await this.logInteraction('hybrid', prompt, `${error}`)
8587
throw error
8688
}
8789
}

0 commit comments

Comments
 (0)