novacode 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -27
- package/dist/app-C_2My7n6.mjs +28 -0
- package/dist/app-C_2My7n6.mjs.map +1 -0
- package/dist/main.mjs +86 -36
- package/dist/main.mjs.map +1 -1
- package/package.json +1 -2
- package/dist/app-bQ9a_p_K.mjs +0 -22
- package/dist/app-bQ9a_p_K.mjs.map +0 -1
- package/src/agent/agent.ts +0 -87
- package/src/agent/loop.ts +0 -237
- package/src/agent/prompt.ts +0 -50
- package/src/commands/compact.ts +0 -28
- package/src/commands/index.ts +0 -128
- package/src/commands/models.ts +0 -85
- package/src/commands/providers.ts +0 -213
- package/src/commands/session.ts +0 -52
- package/src/config/providers.ts +0 -207
- package/src/config/store.ts +0 -66
- package/src/main.ts +0 -205
- package/src/onboarding/wizard.ts +0 -54
- package/src/provider/gemini.ts +0 -269
- package/src/provider/openai.ts +0 -239
- package/src/provider/stream.ts +0 -138
- package/src/session/compact.ts +0 -159
- package/src/session/store.ts +0 -209
- package/src/tools/fs.ts +0 -189
- package/src/tools/git.ts +0 -99
- package/src/tools/index.ts +0 -33
- package/src/tools/search.ts +0 -274
- package/src/tools/shell.ts +0 -90
- package/src/tools/web.ts +0 -239
- package/src/tui/app.tsx +0 -454
- package/src/tui/components/liveArea.tsx +0 -70
- package/src/tui/components/message.tsx +0 -117
- package/src/tui/components/statusBar.tsx +0 -64
- package/src/tui/constants.ts +0 -25
- package/src/tui/markdown.ts +0 -62
- package/src/tui/prompts.tsx +0 -205
- package/src/types.ts +0 -262
- package/src/update.ts +0 -89
- package/src/util.ts +0 -80
package/src/session/store.ts
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises"
|
|
2
|
-
import { join } from "node:path"
|
|
3
|
-
import type { Compaction, Msg, Session } from "../types.ts"
|
|
4
|
-
|
|
5
|
-
function generateId(): string {
|
|
6
|
-
return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class SessionStore {
|
|
10
|
-
#sessionsDir: string
|
|
11
|
-
|
|
12
|
-
constructor(sessionsDir: string) {
|
|
13
|
-
this.#sessionsDir = sessionsDir
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
#sessionDir(id: string): string {
|
|
17
|
-
return join(this.#sessionsDir, id)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#metadataPath(id: string): string {
|
|
21
|
-
return join(this.#sessionDir(id), "metadata.json")
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
#messagesPath(id: string): string {
|
|
25
|
-
return join(this.#sessionDir(id), "messages.jsonl")
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#compactionPath(id: string): string {
|
|
29
|
-
return join(this.#sessionDir(id), "compaction.json")
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async create(cwd: string, model: string, provider: string): Promise<Session> {
|
|
33
|
-
const id = generateId()
|
|
34
|
-
const now = Date.now()
|
|
35
|
-
const session: Session = {
|
|
36
|
-
id,
|
|
37
|
-
cwd,
|
|
38
|
-
model,
|
|
39
|
-
provider,
|
|
40
|
-
title: null,
|
|
41
|
-
created: now,
|
|
42
|
-
updated: now,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
await mkdir(this.#sessionDir(id), { recursive: true })
|
|
46
|
-
await writeFile(this.#metadataPath(id), JSON.stringify(session, null, 2))
|
|
47
|
-
return session
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async get(id: string): Promise<Session | null> {
|
|
51
|
-
try {
|
|
52
|
-
const data = await readFile(this.#metadataPath(id), "utf-8")
|
|
53
|
-
return JSON.parse(data) as Session
|
|
54
|
-
} catch {
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async list(limit = 10): Promise<Session[]> {
|
|
60
|
-
try {
|
|
61
|
-
const entries = await readdir(this.#sessionsDir, { withFileTypes: true })
|
|
62
|
-
const dirNames = entries
|
|
63
|
-
.filter((e) => e.isDirectory())
|
|
64
|
-
.map((e) => e.name)
|
|
65
|
-
.sort((a, b) => b.localeCompare(a))
|
|
66
|
-
|
|
67
|
-
const candidates = dirNames.slice(0, Math.max(limit * 2, 50))
|
|
68
|
-
const sessions: Session[] = []
|
|
69
|
-
for (const name of candidates) {
|
|
70
|
-
const s = await this.get(name)
|
|
71
|
-
if (s) sessions.push(s)
|
|
72
|
-
}
|
|
73
|
-
sessions.sort((a, b) => b.updated - a.updated)
|
|
74
|
-
return sessions.slice(0, limit)
|
|
75
|
-
} catch {
|
|
76
|
-
return []
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async latest(): Promise<Session | null> {
|
|
81
|
-
const sessions = await this.list(1)
|
|
82
|
-
return sessions[0] ?? null
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async delete(id: string): Promise<boolean> {
|
|
86
|
-
try {
|
|
87
|
-
await rm(this.#sessionDir(id), { recursive: true, force: true })
|
|
88
|
-
return true
|
|
89
|
-
} catch {
|
|
90
|
-
return false
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async deleteAll(): Promise<void> {
|
|
95
|
-
try {
|
|
96
|
-
await rm(this.#sessionsDir, { recursive: true, force: true })
|
|
97
|
-
await mkdir(this.#sessionsDir, { recursive: true })
|
|
98
|
-
} catch {
|
|
99
|
-
// ignore
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async append(sessionId: string, msg: Msg): Promise<void> {
|
|
104
|
-
const session = await this.get(sessionId)
|
|
105
|
-
if (!session) return
|
|
106
|
-
|
|
107
|
-
session.updated = Date.now()
|
|
108
|
-
await writeFile(this.#metadataPath(sessionId), JSON.stringify(session, null, 2))
|
|
109
|
-
|
|
110
|
-
const line = `${JSON.stringify(msg)}\n`
|
|
111
|
-
await appendFile(this.#messagesPath(sessionId), line)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async messages(sessionId: string): Promise<Msg[]> {
|
|
115
|
-
try {
|
|
116
|
-
const data = await readFile(this.#messagesPath(sessionId), "utf-8")
|
|
117
|
-
const lines = data.split("\n").filter((l) => l.trim().length > 0)
|
|
118
|
-
return lines.map((l) => JSON.parse(l) as Msg)
|
|
119
|
-
} catch {
|
|
120
|
-
return []
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async messageCount(sessionId: string): Promise<number> {
|
|
125
|
-
try {
|
|
126
|
-
const data = await readFile(this.#messagesPath(sessionId), "utf-8")
|
|
127
|
-
const lines = data.split("\n").filter((l) => l.trim().length > 0)
|
|
128
|
-
return lines.length
|
|
129
|
-
} catch {
|
|
130
|
-
return 0
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async setTitle(sessionId: string, title: string): Promise<void> {
|
|
135
|
-
const session = await this.get(sessionId)
|
|
136
|
-
if (!session) return
|
|
137
|
-
session.title = title
|
|
138
|
-
session.updated = Date.now()
|
|
139
|
-
await writeFile(this.#metadataPath(sessionId), JSON.stringify(session, null, 2))
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async saveCompaction(
|
|
143
|
-
sessionId: string,
|
|
144
|
-
summary: string,
|
|
145
|
-
filesRead: string[],
|
|
146
|
-
filesWrote: string[],
|
|
147
|
-
seqBefore: number,
|
|
148
|
-
): Promise<void> {
|
|
149
|
-
const compaction: Compaction = {
|
|
150
|
-
summary,
|
|
151
|
-
seqBefore,
|
|
152
|
-
filesRead,
|
|
153
|
-
filesWrote,
|
|
154
|
-
ts: Date.now(),
|
|
155
|
-
}
|
|
156
|
-
await writeFile(this.#compactionPath(sessionId), JSON.stringify(compaction, null, 2))
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async getLatestCompaction(sessionId: string): Promise<Compaction | null> {
|
|
160
|
-
try {
|
|
161
|
-
const data = await readFile(this.#compactionPath(sessionId), "utf-8")
|
|
162
|
-
return JSON.parse(data) as Compaction
|
|
163
|
-
} catch {
|
|
164
|
-
return null
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async truncateBeforeSeq(sessionId: string, seq: number): Promise<void> {
|
|
169
|
-
const msgs = await this.messages(sessionId)
|
|
170
|
-
const remaining = msgs.slice(seq)
|
|
171
|
-
const data =
|
|
172
|
-
remaining.map((m) => JSON.stringify(m)).join("\n") + (remaining.length > 0 ? "\n" : "")
|
|
173
|
-
await writeFile(this.#messagesPath(sessionId), data)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async prune(limit = 10): Promise<void> {
|
|
177
|
-
try {
|
|
178
|
-
const entries = await readdir(this.#sessionsDir, { withFileTypes: true })
|
|
179
|
-
const dirNames = entries
|
|
180
|
-
.filter((e) => e.isDirectory())
|
|
181
|
-
.map((e) => e.name)
|
|
182
|
-
.sort((a, b) => b.localeCompare(a))
|
|
183
|
-
|
|
184
|
-
const targets = limit > 0 ? dirNames.slice(0, limit) : dirNames
|
|
185
|
-
for (const name of targets) {
|
|
186
|
-
const count = await this.messageCount(name)
|
|
187
|
-
if (count === 0) {
|
|
188
|
-
await this.delete(name)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} catch {
|
|
192
|
-
// ignore
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
close(): void {
|
|
197
|
-
// no-op
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
let _store: SessionStore | null = null
|
|
202
|
-
|
|
203
|
-
export async function getSessionStore(dir?: string): Promise<SessionStore> {
|
|
204
|
-
if (_store) return _store
|
|
205
|
-
const sessionsPath = join(dir ?? join(process.env.HOME ?? "~", ".novacode"), "sessions")
|
|
206
|
-
await mkdir(sessionsPath, { recursive: true })
|
|
207
|
-
_store = new SessionStore(sessionsPath)
|
|
208
|
-
return _store
|
|
209
|
-
}
|
package/src/tools/fs.ts
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Filesystem tools for reading, writing, and editing files.
|
|
3
|
-
* Includes safety checks to prevent path traversal.
|
|
4
|
-
*/
|
|
5
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
|
6
|
-
import { dirname, extname, resolve } from "node:path"
|
|
7
|
-
import type { Tool, ToolResult } from "../types.ts"
|
|
8
|
-
import { getRelativeIfInside, textPart } from "../util.ts"
|
|
9
|
-
|
|
10
|
-
// Extensions we return as base64 images instead of text
|
|
11
|
-
const IMAGES = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp"])
|
|
12
|
-
|
|
13
|
-
function safePath(cwd: string, p: string): string {
|
|
14
|
-
const abs = resolve(cwd, p)
|
|
15
|
-
if (abs !== cwd && !abs.startsWith(`${cwd}/`)) {
|
|
16
|
-
throw new Error(`Path outside project: ${p}`)
|
|
17
|
-
}
|
|
18
|
-
return abs
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function readTool(cwd: string): Tool {
|
|
22
|
-
return {
|
|
23
|
-
def: {
|
|
24
|
-
name: "read",
|
|
25
|
-
description:
|
|
26
|
-
"Read file contents. Supports text and images (jpg, png, gif, webp). Text output is truncated to 2000 lines.",
|
|
27
|
-
parameters: {
|
|
28
|
-
type: "object",
|
|
29
|
-
properties: {
|
|
30
|
-
path: { type: "string", description: "Path to file (relative or absolute)" },
|
|
31
|
-
offset: { type: "number", description: "Start line (1-based, default 1)" },
|
|
32
|
-
limit: { type: "number", description: "Max lines to read (default 2000)" },
|
|
33
|
-
},
|
|
34
|
-
required: ["path"],
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
async execute(args): Promise<ToolResult> {
|
|
38
|
-
try {
|
|
39
|
-
const filePath = safePath(cwd, args.path as string)
|
|
40
|
-
// Return images as base64 so the LLM can process them visually
|
|
41
|
-
const ext = extname(filePath).toLowerCase()
|
|
42
|
-
if (IMAGES.has(ext)) {
|
|
43
|
-
const buf = await readFile(filePath)
|
|
44
|
-
const b64 = buf.toString("base64")
|
|
45
|
-
const mime = ext === ".jpg" ? "image/jpeg" : `image/${ext.slice(1)}`
|
|
46
|
-
return { content: [{ type: "image", data: b64, mime }], isError: false }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const content = await readFile(filePath, "utf-8")
|
|
50
|
-
const lines = content.split("\n")
|
|
51
|
-
const offset = Math.max(0, (Number(args.offset ?? 1) || 1) - 1)
|
|
52
|
-
const limit = Number(args.limit ?? 2000) || 2000
|
|
53
|
-
const slice = lines.slice(offset, offset + limit)
|
|
54
|
-
const truncated = offset + limit < lines.length
|
|
55
|
-
|
|
56
|
-
const out = slice.join("\n")
|
|
57
|
-
const suffix = truncated ? `\n…${lines.length - offset - limit} more lines` : ""
|
|
58
|
-
|
|
59
|
-
return { content: [textPart(out + suffix)], isError: false }
|
|
60
|
-
} catch (e) {
|
|
61
|
-
return {
|
|
62
|
-
content: [textPart(`Error reading file: ${(e as Error).message}`)],
|
|
63
|
-
isError: true,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function writeTool(cwd: string): Tool {
|
|
71
|
-
return {
|
|
72
|
-
def: {
|
|
73
|
-
name: "write",
|
|
74
|
-
description: "Write content to a file. Creates the file and parent directories if needed.",
|
|
75
|
-
parameters: {
|
|
76
|
-
type: "object",
|
|
77
|
-
properties: {
|
|
78
|
-
path: { type: "string", description: "Path to file" },
|
|
79
|
-
content: { type: "string", description: "Content to write" },
|
|
80
|
-
},
|
|
81
|
-
required: ["path", "content"],
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
async execute(args): Promise<ToolResult> {
|
|
85
|
-
try {
|
|
86
|
-
const filePath = safePath(cwd, args.path as string)
|
|
87
|
-
const content = args.content as string
|
|
88
|
-
await mkdir(dirname(filePath), { recursive: true })
|
|
89
|
-
await writeFile(filePath, content)
|
|
90
|
-
const relPath = getRelativeIfInside(cwd, filePath)
|
|
91
|
-
return {
|
|
92
|
-
content: [textPart(`Wrote ${content.length} bytes → ${relPath}`)],
|
|
93
|
-
isError: false,
|
|
94
|
-
}
|
|
95
|
-
} catch (e) {
|
|
96
|
-
return {
|
|
97
|
-
content: [textPart(`Error writing file: ${(e as Error).message}`)],
|
|
98
|
-
isError: true,
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Requires oldText to be unique to avoid ambiguous replacements.
|
|
106
|
-
export function editTool(cwd: string): Tool {
|
|
107
|
-
return {
|
|
108
|
-
def: {
|
|
109
|
-
name: "edit",
|
|
110
|
-
description:
|
|
111
|
-
"Edit a file using exact text replacement. Each edit's oldText must be unique in the file.",
|
|
112
|
-
parameters: {
|
|
113
|
-
type: "object",
|
|
114
|
-
properties: {
|
|
115
|
-
path: { type: "string", description: "Path to file" },
|
|
116
|
-
edits: {
|
|
117
|
-
type: "array",
|
|
118
|
-
description:
|
|
119
|
-
"Array of {oldText, newText} replacements. oldText must be unique. Non-overlapping.",
|
|
120
|
-
items: {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
oldText: { type: "string", description: "Exact text to find (must be unique)" },
|
|
124
|
-
newText: { type: "string", description: "Replacement text" },
|
|
125
|
-
},
|
|
126
|
-
required: ["oldText", "newText"],
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
required: ["path", "edits"],
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
async execute(args): Promise<ToolResult> {
|
|
134
|
-
try {
|
|
135
|
-
const filePath = safePath(cwd, args.path as string)
|
|
136
|
-
let content: string
|
|
137
|
-
try {
|
|
138
|
-
content = await readFile(filePath, "utf-8")
|
|
139
|
-
} catch {
|
|
140
|
-
return { content: [textPart(`File not found: ${args.path}`)], isError: true }
|
|
141
|
-
}
|
|
142
|
-
const edits = args.edits as Array<{ oldText: string; newText: string }>
|
|
143
|
-
|
|
144
|
-
// Validate all edits before applying any — avoids partial writes on bad input
|
|
145
|
-
for (const edit of edits) {
|
|
146
|
-
const count = content.split(edit.oldText).length - 1
|
|
147
|
-
if (count === 0) {
|
|
148
|
-
return {
|
|
149
|
-
content: [textPart(`oldText not found: "${edit.oldText.slice(0, 80)}…"`)],
|
|
150
|
-
isError: true,
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// Ambiguous match would replace the wrong occurrence
|
|
154
|
-
if (count > 1) {
|
|
155
|
-
return {
|
|
156
|
-
content: [
|
|
157
|
-
textPart(
|
|
158
|
-
`oldText found ${count} times — add surrounding context to make it unique: "${edit.oldText.slice(0, 60)}…"`,
|
|
159
|
-
),
|
|
160
|
-
],
|
|
161
|
-
isError: true,
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Apply edits sequentially
|
|
167
|
-
for (const edit of edits) {
|
|
168
|
-
content = content.replace(edit.oldText, edit.newText)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
await writeFile(filePath, content)
|
|
172
|
-
const relPath = getRelativeIfInside(cwd, filePath)
|
|
173
|
-
return {
|
|
174
|
-
content: [
|
|
175
|
-
textPart(
|
|
176
|
-
`Edited ${relPath} (${edits.length} replacement${edits.length > 1 ? "s" : ""})`,
|
|
177
|
-
),
|
|
178
|
-
],
|
|
179
|
-
isError: false,
|
|
180
|
-
}
|
|
181
|
-
} catch (e) {
|
|
182
|
-
return {
|
|
183
|
-
content: [textPart(`Error editing file: ${(e as Error).message}`)],
|
|
184
|
-
isError: true,
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
}
|
|
189
|
-
}
|
package/src/tools/git.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Git tools for executing safe repository operations programmatically.
|
|
3
|
-
*/
|
|
4
|
-
import { spawn } from "node:child_process"
|
|
5
|
-
import type { Tool, ToolResult } from "../types.ts"
|
|
6
|
-
import { textPart } from "../util.ts"
|
|
7
|
-
|
|
8
|
-
export function gitTool(cwd: string): Tool {
|
|
9
|
-
return {
|
|
10
|
-
def: {
|
|
11
|
-
name: "git",
|
|
12
|
-
description:
|
|
13
|
-
"Execute safe, non-interactive git commands (status, diff, log, add, commit) in the repository.",
|
|
14
|
-
parameters: {
|
|
15
|
-
type: "object",
|
|
16
|
-
properties: {
|
|
17
|
-
action: {
|
|
18
|
-
type: "string",
|
|
19
|
-
enum: ["status", "diff", "log", "add", "commit"],
|
|
20
|
-
description: "The git action to execute",
|
|
21
|
-
},
|
|
22
|
-
args: {
|
|
23
|
-
type: "array",
|
|
24
|
-
description: "Optional additional arguments or file paths for the git action",
|
|
25
|
-
items: { type: "string" },
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
required: ["action"],
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
async execute(args, signal): Promise<ToolResult> {
|
|
32
|
-
const action = args.action as string
|
|
33
|
-
const extraArgs = (args.args as string[]) || []
|
|
34
|
-
|
|
35
|
-
const allowed = new Set(["status", "diff", "log", "add", "commit"])
|
|
36
|
-
if (!allowed.has(action)) {
|
|
37
|
-
return {
|
|
38
|
-
content: [textPart(`Error: Git action '${action}' is not supported.`)],
|
|
39
|
-
isError: true,
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const cmd = ["git", action, ...extraArgs]
|
|
45
|
-
const proc = spawn(cmd[0]!, cmd.slice(1), {
|
|
46
|
-
cwd,
|
|
47
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
48
|
-
env: { ...process.env, PAGER: "cat" },
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
let stdout = ""
|
|
52
|
-
let stderr = ""
|
|
53
|
-
proc.stdout.on("data", (chunk: Buffer) => {
|
|
54
|
-
stdout += chunk.toString()
|
|
55
|
-
})
|
|
56
|
-
proc.stderr.on("data", (chunk: Buffer) => {
|
|
57
|
-
stderr += chunk.toString()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const onAbort = () => {
|
|
61
|
-
proc.kill("SIGKILL")
|
|
62
|
-
proc.stdout.destroy()
|
|
63
|
-
proc.stderr.destroy()
|
|
64
|
-
}
|
|
65
|
-
signal?.addEventListener("abort", onAbort, { once: true })
|
|
66
|
-
|
|
67
|
-
let exitCode: number
|
|
68
|
-
try {
|
|
69
|
-
exitCode = await new Promise<number>((resolve, reject) => {
|
|
70
|
-
proc.on("error", reject)
|
|
71
|
-
proc.on("close", (code) => resolve(code ?? -1))
|
|
72
|
-
})
|
|
73
|
-
} finally {
|
|
74
|
-
signal?.removeEventListener("abort", onAbort)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Prevent context window blowout by truncating very large outputs
|
|
78
|
-
const MAX = 50_000
|
|
79
|
-
let out = ""
|
|
80
|
-
if (stdout) out += stdout.slice(0, MAX)
|
|
81
|
-
if (stderr) {
|
|
82
|
-
if (out) out += "\n"
|
|
83
|
-
out += stderr.slice(0, MAX - out.length)
|
|
84
|
-
}
|
|
85
|
-
if (out.length >= MAX) out += "\n…truncated"
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
content: [textPart(out || "(no output)")],
|
|
89
|
-
isError: exitCode !== 0,
|
|
90
|
-
}
|
|
91
|
-
} catch (e) {
|
|
92
|
-
return {
|
|
93
|
-
content: [textPart(`Error running git: ${(e as Error).message}`)],
|
|
94
|
-
isError: true,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
}
|
|
99
|
-
}
|
package/src/tools/index.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { Tool } from "../types.ts"
|
|
2
|
-
import { editTool, readTool, writeTool } from "./fs.ts"
|
|
3
|
-
import { gitTool } from "./git.ts"
|
|
4
|
-
import { globTool, grepTool, lsTool, treeTool } from "./search.ts"
|
|
5
|
-
import { bashTool } from "./shell.ts"
|
|
6
|
-
import { webFetchTool, webSearchTool } from "./web.ts"
|
|
7
|
-
|
|
8
|
-
export function getAllTools(cwd: string): Tool[] {
|
|
9
|
-
return [
|
|
10
|
-
readTool(cwd),
|
|
11
|
-
writeTool(cwd),
|
|
12
|
-
editTool(cwd),
|
|
13
|
-
bashTool(cwd),
|
|
14
|
-
globTool(cwd),
|
|
15
|
-
grepTool(cwd),
|
|
16
|
-
lsTool(cwd),
|
|
17
|
-
treeTool(cwd),
|
|
18
|
-
gitTool(cwd),
|
|
19
|
-
webSearchTool(),
|
|
20
|
-
webFetchTool(),
|
|
21
|
-
]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function getDefaultTools(cwd: string): Tool[] {
|
|
25
|
-
return [
|
|
26
|
-
readTool(cwd),
|
|
27
|
-
writeTool(cwd),
|
|
28
|
-
editTool(cwd),
|
|
29
|
-
bashTool(cwd),
|
|
30
|
-
webSearchTool(),
|
|
31
|
-
webFetchTool(),
|
|
32
|
-
]
|
|
33
|
-
}
|