@llmtune/cli 0.1.0 → 0.1.1
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 +1 -1
- package/dist/agent/conversation.d.ts +42 -0
- package/dist/agent/conversation.js +105 -0
- package/dist/agent/loop.d.ts +19 -0
- package/dist/agent/loop.js +185 -0
- package/dist/agent/planner.d.ts +8 -0
- package/dist/agent/planner.js +43 -0
- package/dist/auth/client.d.ts +4 -0
- package/dist/auth/client.js +24 -0
- package/dist/auth/config.d.ts +21 -0
- package/dist/auth/config.js +83 -0
- package/dist/commands/chat.d.ts +5 -0
- package/dist/commands/chat.js +27 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +37 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +93 -0
- package/dist/commands/marketplace.d.ts +6 -0
- package/dist/commands/marketplace.js +213 -0
- package/dist/commands/models.d.ts +2 -0
- package/dist/commands/models.js +53 -0
- package/dist/compact/history-store.d.ts +29 -0
- package/dist/compact/history-store.js +110 -0
- package/dist/compact/microcompact.d.ts +10 -0
- package/dist/compact/microcompact.js +43 -0
- package/dist/compact/service.d.ts +13 -0
- package/dist/compact/service.js +156 -0
- package/dist/context/analyzer.d.ts +26 -0
- package/dist/context/analyzer.js +99 -0
- package/dist/context/builder.d.ts +13 -0
- package/dist/context/builder.js +144 -0
- package/dist/context/cache.d.ts +6 -0
- package/dist/context/cache.js +8 -0
- package/dist/context/git-context.d.ts +9 -0
- package/dist/context/git-context.js +49 -0
- package/dist/context/llmtune-md.d.ts +6 -0
- package/dist/context/llmtune-md.js +73 -0
- package/dist/context/workspace.d.ts +11 -0
- package/dist/context/workspace.js +115 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/marketplace/client.d.ts +52 -0
- package/dist/marketplace/client.js +86 -0
- package/dist/memory/files.d.ts +14 -0
- package/dist/memory/files.js +116 -0
- package/dist/memory/service.d.ts +22 -0
- package/dist/memory/service.js +146 -0
- package/dist/repl/repl.d.ts +8 -0
- package/dist/repl/repl.js +374 -0
- package/dist/skills/args.d.ts +10 -0
- package/dist/skills/args.js +37 -0
- package/dist/skills/frontmatter.d.ts +6 -0
- package/dist/skills/frontmatter.js +44 -0
- package/dist/skills/loader.d.ts +5 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/registry.d.ts +27 -0
- package/dist/skills/registry.js +162 -0
- package/dist/skills/signing/signer.d.ts +19 -0
- package/dist/skills/signing/signer.js +110 -0
- package/dist/skills/trust.d.ts +11 -0
- package/dist/skills/trust.js +42 -0
- package/dist/telemetry/logger.d.ts +51 -0
- package/dist/telemetry/logger.js +135 -0
- package/dist/tools/permissions.d.ts +20 -0
- package/dist/tools/permissions.js +58 -0
- package/dist/tools/protocol.d.ts +22 -0
- package/dist/tools/protocol.js +3 -0
- package/dist/tools/registry.d.ts +20 -0
- package/dist/tools/registry.js +77 -0
- package/dist/tools/sandbox/docker.d.ts +16 -0
- package/dist/tools/sandbox/docker.js +240 -0
- package/dist/tools/sandbox/index.d.ts +18 -0
- package/dist/tools/sandbox/index.js +80 -0
- package/dist/tools/tools/ask-user.d.ts +3 -0
- package/dist/tools/tools/ask-user.js +56 -0
- package/dist/tools/tools/bash.d.ts +3 -0
- package/dist/tools/tools/bash.js +85 -0
- package/dist/tools/tools/edit.d.ts +3 -0
- package/dist/tools/tools/edit.js +138 -0
- package/dist/tools/tools/glob.d.ts +3 -0
- package/dist/tools/tools/glob.js +63 -0
- package/dist/tools/tools/grep.d.ts +3 -0
- package/dist/tools/tools/grep.js +148 -0
- package/dist/tools/tools/read.d.ts +3 -0
- package/dist/tools/tools/read.js +85 -0
- package/dist/tools/tools/web-fetch.d.ts +3 -0
- package/dist/tools/tools/web-fetch.js +142 -0
- package/dist/tools/tools/write.d.ts +3 -0
- package/dist/tools/tools/write.js +84 -0
- package/dist/tools/validation.d.ts +13 -0
- package/dist/tools/validation.js +142 -0
- package/dist/utils/markdown.d.ts +9 -0
- package/dist/utils/markdown.js +89 -0
- package/dist/utils/streaming.d.ts +10 -0
- package/dist/utils/streaming.js +63 -0
- package/dist/utils/tokens.d.ts +12 -0
- package/dist/utils/tokens.js +44 -0
- package/package.json +2 -2
- package/dist/agent/conversation.d.ts.map +0 -1
- package/dist/agent/loop.d.ts.map +0 -1
- package/dist/agent/planner.d.ts.map +0 -1
- package/dist/auth/client.d.ts.map +0 -1
- package/dist/auth/config.d.ts.map +0 -1
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/marketplace.d.ts.map +0 -1
- package/dist/commands/models.d.ts.map +0 -1
- package/dist/compact/history-store.d.ts.map +0 -1
- package/dist/compact/microcompact.d.ts.map +0 -1
- package/dist/compact/service.d.ts.map +0 -1
- package/dist/context/analyzer.d.ts.map +0 -1
- package/dist/context/builder.d.ts.map +0 -1
- package/dist/context/cache.d.ts.map +0 -1
- package/dist/context/git-context.d.ts.map +0 -1
- package/dist/context/llmtune-md.d.ts.map +0 -1
- package/dist/context/workspace.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/marketplace/client.d.ts.map +0 -1
- package/dist/memory/files.d.ts.map +0 -1
- package/dist/memory/service.d.ts.map +0 -1
- package/dist/repl/repl.d.ts.map +0 -1
- package/dist/skills/args.d.ts.map +0 -1
- package/dist/skills/frontmatter.d.ts.map +0 -1
- package/dist/skills/loader.d.ts.map +0 -1
- package/dist/skills/registry.d.ts.map +0 -1
- package/dist/skills/signing/signer.d.ts.map +0 -1
- package/dist/skills/trust.d.ts.map +0 -1
- package/dist/telemetry/logger.d.ts.map +0 -1
- package/dist/tools/permissions.d.ts.map +0 -1
- package/dist/tools/protocol.d.ts.map +0 -1
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/sandbox/docker.d.ts.map +0 -1
- package/dist/tools/sandbox/index.d.ts.map +0 -1
- package/dist/tools/tools/ask-user.d.ts.map +0 -1
- package/dist/tools/tools/bash.d.ts.map +0 -1
- package/dist/tools/tools/edit.d.ts.map +0 -1
- package/dist/tools/tools/glob.d.ts.map +0 -1
- package/dist/tools/tools/grep.d.ts.map +0 -1
- package/dist/tools/tools/read.d.ts.map +0 -1
- package/dist/tools/tools/web-fetch.d.ts.map +0 -1
- package/dist/tools/tools/write.d.ts.map +0 -1
- package/dist/tools/validation.d.ts.map +0 -1
- package/dist/utils/markdown.d.ts.map +0 -1
- package/dist/utils/streaming.d.ts.map +0 -1
- package/dist/utils/tokens.d.ts.map +0 -1
- package/src/agent/conversation.ts +0 -140
- package/src/agent/loop.ts +0 -215
- package/src/agent/planner.ts +0 -55
- package/src/auth/client.ts +0 -19
- package/src/auth/config.ts +0 -89
- package/src/commands/chat.ts +0 -28
- package/src/commands/config.ts +0 -36
- package/src/commands/login.ts +0 -63
- package/src/commands/marketplace.ts +0 -190
- package/src/commands/models.ts +0 -74
- package/src/compact/history-store.ts +0 -101
- package/src/compact/microcompact.ts +0 -49
- package/src/compact/service.ts +0 -154
- package/src/context/analyzer.ts +0 -127
- package/src/context/builder.ts +0 -123
- package/src/context/cache.ts +0 -11
- package/src/context/git-context.ts +0 -58
- package/src/context/llmtune-md.ts +0 -48
- package/src/context/workspace.ts +0 -139
- package/src/index.ts +0 -100
- package/src/marketplace/client.ts +0 -118
- package/src/memory/files.ts +0 -81
- package/src/memory/service.ts +0 -124
- package/src/repl/repl.ts +0 -400
- package/src/skills/args.ts +0 -35
- package/src/skills/builtin/explain-code/SKILL.md +0 -30
- package/src/skills/frontmatter.ts +0 -47
- package/src/skills/loader.ts +0 -25
- package/src/skills/registry.ts +0 -155
- package/src/skills/signing/signer.ts +0 -101
- package/src/skills/trust.ts +0 -50
- package/src/telemetry/logger.ts +0 -108
- package/src/tools/permissions.ts +0 -83
- package/src/tools/protocol.ts +0 -24
- package/src/tools/registry.ts +0 -93
- package/src/tools/sandbox/docker.ts +0 -225
- package/src/tools/sandbox/index.ts +0 -91
- package/src/tools/tools/ask-user.ts +0 -60
- package/src/tools/tools/bash.ts +0 -97
- package/src/tools/tools/edit.ts +0 -111
- package/src/tools/tools/glob.ts +0 -68
- package/src/tools/tools/grep.ts +0 -121
- package/src/tools/tools/read.ts +0 -57
- package/src/tools/tools/web-fetch.ts +0 -158
- package/src/tools/tools/write.ts +0 -52
- package/src/tools/validation.ts +0 -164
- package/src/utils/markdown.ts +0 -96
- package/src/utils/streaming.ts +0 -63
- package/src/utils/tokens.ts +0 -41
- package/tsconfig.json +0 -20
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Docker-based tool sandbox for enterprise deployments.
|
|
3
|
-
* Isolates bash and file write operations inside a Docker container
|
|
4
|
-
* to prevent accidental or malicious damage to the host system.
|
|
5
|
-
*/
|
|
6
|
-
import { execFile } from "child_process"
|
|
7
|
-
import { promisify } from "util"
|
|
8
|
-
import * as path from "path"
|
|
9
|
-
import * as fs from "fs"
|
|
10
|
-
import * as os from "os"
|
|
11
|
-
import type { ToolResult } from "../protocol"
|
|
12
|
-
|
|
13
|
-
const execFileAsync = promisify(execFile)
|
|
14
|
-
|
|
15
|
-
const SANDBOX_IMAGE = "llmtune-sandbox:latest"
|
|
16
|
-
const SANDBOX_WORKDIR = "/workspace"
|
|
17
|
-
const EXECUTION_TIMEOUT_MS = 60_000
|
|
18
|
-
const MAX_OUTPUT_BYTES = 100_000
|
|
19
|
-
|
|
20
|
-
export interface SandboxConfig {
|
|
21
|
-
enabled: boolean
|
|
22
|
-
image: string
|
|
23
|
-
timeoutMs: number
|
|
24
|
-
readOnlyPaths: string[]
|
|
25
|
-
maxMemoryMB: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const DEFAULT_SANDBOX_CONFIG: SandboxConfig = {
|
|
29
|
-
enabled: false,
|
|
30
|
-
image: SANDBOX_IMAGE,
|
|
31
|
-
timeoutMs: EXECUTION_TIMEOUT_MS,
|
|
32
|
-
readOnlyPaths: [],
|
|
33
|
-
maxMemoryMB: 512,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let sandboxConfig: SandboxConfig = { ...DEFAULT_SANDBOX_CONFIG }
|
|
37
|
-
|
|
38
|
-
export function configureSandbox(config: Partial<SandboxConfig>): void {
|
|
39
|
-
sandboxConfig = { ...sandboxConfig, ...config }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function isSandboxEnabled(): boolean {
|
|
43
|
-
return sandboxConfig.enabled
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function isDockerAvailable(): Promise<boolean> {
|
|
47
|
-
try {
|
|
48
|
-
const { stdout } = await execFileAsync("docker", ["--version"], {
|
|
49
|
-
timeout: 5000,
|
|
50
|
-
})
|
|
51
|
-
return /docker/i.test(stdout)
|
|
52
|
-
} catch {
|
|
53
|
-
return false
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function ensureSandboxImage(): Promise<boolean> {
|
|
58
|
-
try {
|
|
59
|
-
const { stdout } = await execFileAsync("docker", ["images", "-q", sandboxConfig.image], {
|
|
60
|
-
timeout: 10000,
|
|
61
|
-
})
|
|
62
|
-
if (stdout.trim()) return true
|
|
63
|
-
|
|
64
|
-
// Pull the image if not found
|
|
65
|
-
console.log(`Pulling sandbox image: ${sandboxConfig.image}...`)
|
|
66
|
-
await execFileAsync("docker", ["pull", sandboxConfig.image], {
|
|
67
|
-
timeout: 120_000,
|
|
68
|
-
})
|
|
69
|
-
return true
|
|
70
|
-
} catch {
|
|
71
|
-
return false
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function runInSandbox(command: string, cwd: string): Promise<ToolResult> {
|
|
76
|
-
if (!sandboxConfig.enabled) {
|
|
77
|
-
return {
|
|
78
|
-
name: "sandbox",
|
|
79
|
-
output: { error: "Sandbox is not enabled" },
|
|
80
|
-
isError: true,
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const dockerAvailable = await isDockerAvailable()
|
|
85
|
-
if (!dockerAvailable) {
|
|
86
|
-
return {
|
|
87
|
-
name: "sandbox",
|
|
88
|
-
output: { error: "Docker is not available. Install Docker to use sandbox mode." },
|
|
89
|
-
isError: true,
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const workspaceMount = `${path.resolve(cwd)}:${SANDBOX_WORKDIR}`
|
|
94
|
-
const memoryLimit = `${sandboxConfig.maxMemoryMB}m`
|
|
95
|
-
const timeout = Math.min(sandboxConfig.timeoutMs, 120_000)
|
|
96
|
-
|
|
97
|
-
const args = [
|
|
98
|
-
"run",
|
|
99
|
-
"--rm",
|
|
100
|
-
"--network", "none",
|
|
101
|
-
"--memory", memoryLimit,
|
|
102
|
-
"--cpus", "1",
|
|
103
|
-
"--timeout", String(Math.ceil(timeout / 1000)),
|
|
104
|
-
"-v", `${workspaceMount}:ro`,
|
|
105
|
-
"--workdir", SANDBOX_WORKDIR,
|
|
106
|
-
sandboxConfig.image,
|
|
107
|
-
"sh", "-c", command,
|
|
108
|
-
]
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const { stdout, stderr } = await execFileAsync("docker", args, {
|
|
112
|
-
timeout: timeout + 10_000,
|
|
113
|
-
maxBuffer: MAX_OUTPUT_BYTES,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
let output = ""
|
|
117
|
-
if (stdout) output += truncateOutput(stdout)
|
|
118
|
-
if (stderr) {
|
|
119
|
-
output += "\n--- stderr ---\n"
|
|
120
|
-
output += truncateOutput(stderr)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
name: "sandbox",
|
|
125
|
-
output: {
|
|
126
|
-
stdout: output.trim(),
|
|
127
|
-
exit_code: 0,
|
|
128
|
-
sandboxed: true,
|
|
129
|
-
},
|
|
130
|
-
isError: false,
|
|
131
|
-
}
|
|
132
|
-
} catch (err: unknown) {
|
|
133
|
-
const execErr = err as { stdout?: string; stderr?: string; killed?: boolean; code?: number }
|
|
134
|
-
let output = ""
|
|
135
|
-
if (execErr.stdout) output += truncateOutput(execErr.stdout)
|
|
136
|
-
if (execErr.stderr) {
|
|
137
|
-
output += "\n--- stderr ---\n"
|
|
138
|
-
output += truncateOutput(execErr.stderr)
|
|
139
|
-
}
|
|
140
|
-
if (execErr.killed) {
|
|
141
|
-
output += `\n--- Process killed (timeout or memory limit) ---`
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
name: "sandbox",
|
|
146
|
-
output: {
|
|
147
|
-
stdout: output.trim(),
|
|
148
|
-
exit_code: typeof execErr.code === "number" ? execErr.code : 1,
|
|
149
|
-
sandboxed: true,
|
|
150
|
-
timed_out: execErr.killed ?? false,
|
|
151
|
-
},
|
|
152
|
-
isError: true,
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export async function writeFileInSandbox(
|
|
158
|
-
filePath: string,
|
|
159
|
-
content: string,
|
|
160
|
-
cwd: string,
|
|
161
|
-
): Promise<ToolResult> {
|
|
162
|
-
if (!sandboxConfig.enabled) {
|
|
163
|
-
return {
|
|
164
|
-
name: "sandbox",
|
|
165
|
-
output: { error: "Sandbox is not enabled" },
|
|
166
|
-
isError: true,
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Write to a temp file on host, then copy into container
|
|
171
|
-
const tmpDir = path.join(os.tmpdir(), "llmtune-sandbox")
|
|
172
|
-
fs.mkdirSync(tmpDir, { recursive: true })
|
|
173
|
-
const tmpFile = path.join(tmpDir, `write-${Date.now()}.tmp`)
|
|
174
|
-
fs.writeFileSync(tmpFile, content, "utf-8")
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const absTarget = path.resolve(cwd, filePath)
|
|
178
|
-
const containerPath = `${SANDBOX_WORKDIR}/${path.relative(cwd, absTarget).replace(/\\/g, "/")}`
|
|
179
|
-
const workspaceMount = `${path.resolve(cwd)}:${SANDBOX_WORKDIR}`
|
|
180
|
-
|
|
181
|
-
const dockerArgs = [
|
|
182
|
-
"run",
|
|
183
|
-
"--rm",
|
|
184
|
-
"--network", "none",
|
|
185
|
-
"--memory", `${sandboxConfig.maxMemoryMB}m`,
|
|
186
|
-
"-v", `${workspaceMount}`,
|
|
187
|
-
"-v", `${tmpFile}:/tmp/write-content.tmp:ro`,
|
|
188
|
-
"--workdir", SANDBOX_WORKDIR,
|
|
189
|
-
sandboxConfig.image,
|
|
190
|
-
"sh", "-c", `mkdir -p "$(dirname '${containerPath}')" && cp /tmp/write-content.tmp '${containerPath}'`,
|
|
191
|
-
]
|
|
192
|
-
|
|
193
|
-
await execFileAsync("docker", dockerArgs, { timeout: 30_000 })
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
name: "sandbox",
|
|
197
|
-
output: {
|
|
198
|
-
type: "text",
|
|
199
|
-
filePath,
|
|
200
|
-
bytesWritten: Buffer.byteLength(content, "utf-8"),
|
|
201
|
-
sandboxed: true,
|
|
202
|
-
},
|
|
203
|
-
isError: false,
|
|
204
|
-
}
|
|
205
|
-
} catch (err: unknown) {
|
|
206
|
-
return {
|
|
207
|
-
name: "sandbox",
|
|
208
|
-
output: { error: `Sandbox write failed: ${(err as Error).message}` },
|
|
209
|
-
isError: true,
|
|
210
|
-
}
|
|
211
|
-
} finally {
|
|
212
|
-
try {
|
|
213
|
-
fs.unlinkSync(tmpFile)
|
|
214
|
-
} catch {
|
|
215
|
-
// cleanup failure is non-critical
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function truncateOutput(output: string): string {
|
|
221
|
-
if (output.length > 50_000) {
|
|
222
|
-
return output.slice(0, 50_000) + "\n... (truncated)"
|
|
223
|
-
}
|
|
224
|
-
return output
|
|
225
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sandbox manager: decides when to use Docker isolation vs local execution.
|
|
3
|
-
*
|
|
4
|
-
* Modes:
|
|
5
|
-
* - "off" — all tools run locally (default)
|
|
6
|
-
* - "available" — use Docker when available, fall back to local
|
|
7
|
-
* - "required" — require Docker for destructive tools (enterprise mode)
|
|
8
|
-
*/
|
|
9
|
-
import type { ToolResult } from "../protocol"
|
|
10
|
-
import { runInSandbox } from "./docker"
|
|
11
|
-
|
|
12
|
-
export type SandboxMode = "off" | "available" | "required"
|
|
13
|
-
|
|
14
|
-
let sandboxMode: SandboxMode = "off"
|
|
15
|
-
let dockerAvailable: boolean | null = null
|
|
16
|
-
|
|
17
|
-
export function getSandboxMode(): SandboxMode {
|
|
18
|
-
return sandboxMode
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function setSandboxMode(mode: SandboxMode): void {
|
|
22
|
-
sandboxMode = mode
|
|
23
|
-
dockerAvailable = null // re-check on next use
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function isDockerAvailable(): boolean {
|
|
27
|
-
if (dockerAvailable !== null) return dockerAvailable
|
|
28
|
-
// Quick check: can we run `docker info`?
|
|
29
|
-
try {
|
|
30
|
-
const { execSync } = require("child_process") as typeof import("child_process")
|
|
31
|
-
execSync("docker info", { timeout: 3000, stdio: "pipe" })
|
|
32
|
-
dockerAvailable = true
|
|
33
|
-
} catch {
|
|
34
|
-
dockerAvailable = false
|
|
35
|
-
}
|
|
36
|
-
return dockerAvailable
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Execute a bash command, optionally in a Docker sandbox.
|
|
41
|
-
*/
|
|
42
|
-
export async function sandboxedExec(
|
|
43
|
-
command: string,
|
|
44
|
-
cwd: string,
|
|
45
|
-
timeout: number,
|
|
46
|
-
): Promise<ToolResult> {
|
|
47
|
-
if (sandboxMode === "off") {
|
|
48
|
-
return localExec(command, cwd, timeout)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (sandboxMode === "available" && !isDockerAvailable()) {
|
|
52
|
-
return localExec(command, cwd, timeout)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (sandboxMode === "required" && !isDockerAvailable()) {
|
|
56
|
-
return {
|
|
57
|
-
name: "bash",
|
|
58
|
-
output: {
|
|
59
|
-
error: "Enterprise mode requires Docker but Docker is not available. Install Docker or disable enterprise mode.",
|
|
60
|
-
},
|
|
61
|
-
isError: true,
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return runInSandbox(command, cwd)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function localExec(command: string, cwd: string, timeout: number): ToolResult {
|
|
69
|
-
const { execSync } = require("child_process") as typeof import("child_process")
|
|
70
|
-
try {
|
|
71
|
-
const stdout = execSync(command, {
|
|
72
|
-
cwd,
|
|
73
|
-
timeout,
|
|
74
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
75
|
-
encoding: "utf-8",
|
|
76
|
-
shell: process.platform === "win32" ? "cmd.exe" : "/bin/bash",
|
|
77
|
-
})
|
|
78
|
-
return { name: "bash", output: { stdout: stdout || "(no output)", exitCode: 0 }, isError: false }
|
|
79
|
-
} catch (err: unknown) {
|
|
80
|
-
const e = err as { stdout?: string; stderr?: string; status?: number; killed?: boolean }
|
|
81
|
-
let output = ""
|
|
82
|
-
if (e.stdout) output += String(e.stdout).slice(0, 50_000)
|
|
83
|
-
if (e.stderr) output += "\n--- stderr ---\n" + String(e.stderr).slice(0, 50_000)
|
|
84
|
-
if (e.killed) output += `\n--- Process timed out after ${timeout}ms ---`
|
|
85
|
-
return {
|
|
86
|
-
name: "bash",
|
|
87
|
-
output: { stdout: output.trim(), exitCode: e.status ?? 1 },
|
|
88
|
-
isError: true,
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "readline"
|
|
2
|
-
import type { Tool, ToolSpec, ToolResult, ToolContext } from "../protocol"
|
|
3
|
-
|
|
4
|
-
export const askUserTool: Tool = {
|
|
5
|
-
spec(): ToolSpec {
|
|
6
|
-
return {
|
|
7
|
-
name: "ask_user",
|
|
8
|
-
description:
|
|
9
|
-
"Ask the user a question and wait for their response. Use when you need clarification, confirmation, or additional information before proceeding.",
|
|
10
|
-
inputSchema: {
|
|
11
|
-
type: "object",
|
|
12
|
-
properties: {
|
|
13
|
-
question: {
|
|
14
|
-
type: "string",
|
|
15
|
-
description: "The question to ask the user",
|
|
16
|
-
},
|
|
17
|
-
options: {
|
|
18
|
-
type: "array",
|
|
19
|
-
items: { type: "string" },
|
|
20
|
-
description: "Optional list of choices for the user to pick from",
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
required: ["question"],
|
|
24
|
-
},
|
|
25
|
-
isReadOnly: true,
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
async run(input: Record<string, unknown>, _ctx: ToolContext): Promise<ToolResult> {
|
|
30
|
-
const question = String(input.question ?? "")
|
|
31
|
-
if (!question) {
|
|
32
|
-
return { name: "ask_user", output: { error: "question is required" }, isError: true }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const options = Array.isArray(input.options) ? input.options.map(String) : []
|
|
36
|
-
|
|
37
|
-
if (options.length > 0) {
|
|
38
|
-
console.log(`\n ${question}`)
|
|
39
|
-
options.forEach((opt, i) => console.log(` ${i + 1}. ${opt}`))
|
|
40
|
-
console.log()
|
|
41
|
-
} else {
|
|
42
|
-
console.log(`\n ${question}\n`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
46
|
-
|
|
47
|
-
const answer = await new Promise<string>((resolve) => {
|
|
48
|
-
rl.question(" > ", (ans) => {
|
|
49
|
-
rl.close()
|
|
50
|
-
resolve(ans.trim())
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
name: "ask_user",
|
|
56
|
-
output: { question, answer, selectedOption: options.length > 0 ? parseInt(answer) || 0 : undefined },
|
|
57
|
-
isError: false,
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
}
|
package/src/tools/tools/bash.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { exec } from "child_process"
|
|
2
|
-
import { promisify } from "util"
|
|
3
|
-
import type { Tool, ToolSpec, ToolResult, ToolContext } from "../protocol"
|
|
4
|
-
|
|
5
|
-
const execAsync = promisify(exec)
|
|
6
|
-
|
|
7
|
-
const MAX_OUTPUT = 50_000
|
|
8
|
-
const TIMEOUT_MS = 120_000
|
|
9
|
-
|
|
10
|
-
export const bashTool: Tool = {
|
|
11
|
-
spec(): ToolSpec {
|
|
12
|
-
return {
|
|
13
|
-
name: "bash",
|
|
14
|
-
description:
|
|
15
|
-
"Execute a shell command. Returns stdout, stderr, and exit code. Use for running builds, tests, git commands, and other shell operations.",
|
|
16
|
-
inputSchema: {
|
|
17
|
-
type: "object",
|
|
18
|
-
properties: {
|
|
19
|
-
command: {
|
|
20
|
-
type: "string",
|
|
21
|
-
description: "The command to execute",
|
|
22
|
-
},
|
|
23
|
-
timeout: {
|
|
24
|
-
type: "number",
|
|
25
|
-
description: "Optional timeout in ms (default 120000, max 300000)",
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
required: ["command"],
|
|
29
|
-
},
|
|
30
|
-
isReadOnly: false,
|
|
31
|
-
isDestructive: true,
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
async run(
|
|
36
|
-
input: Record<string, unknown>,
|
|
37
|
-
_ctx: ToolContext
|
|
38
|
-
): Promise<ToolResult> {
|
|
39
|
-
const command = String(input.command ?? "")
|
|
40
|
-
const timeout = Math.min(
|
|
41
|
-
typeof input.timeout === "number" ? input.timeout : TIMEOUT_MS,
|
|
42
|
-
300_000
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if (!command.trim()) {
|
|
46
|
-
return { name: "bash", output: { error: "command is required" }, isError: true }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const { stdout, stderr } = await execAsync(command, {
|
|
51
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
52
|
-
timeout,
|
|
53
|
-
shell: process.platform === "win32" ? "cmd" : "/bin/bash",
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
let output = ""
|
|
57
|
-
if (stdout) {
|
|
58
|
-
output += stdout.length > MAX_OUTPUT
|
|
59
|
-
? stdout.slice(0, MAX_OUTPUT) + "\n... (truncated)"
|
|
60
|
-
: stdout
|
|
61
|
-
}
|
|
62
|
-
if (stderr) {
|
|
63
|
-
output += "\n--- stderr ---\n"
|
|
64
|
-
output += stderr.length > MAX_OUTPUT
|
|
65
|
-
? stderr.slice(0, MAX_OUTPUT) + "\n... (truncated)"
|
|
66
|
-
: stderr
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
name: "bash",
|
|
71
|
-
output: { stdout: output.trim(), exit_code: 0 },
|
|
72
|
-
isError: false,
|
|
73
|
-
}
|
|
74
|
-
} catch (err: unknown) {
|
|
75
|
-
const e = err as { stdout?: string; stderr?: string; code?: number | string; killed?: boolean }
|
|
76
|
-
const stdout = e.stdout || ""
|
|
77
|
-
const stderr = e.stderr || ""
|
|
78
|
-
const exitCode = typeof e.code === "number" ? e.code : 1
|
|
79
|
-
|
|
80
|
-
let output = ""
|
|
81
|
-
if (stdout) output += stdout.slice(0, MAX_OUTPUT)
|
|
82
|
-
if (stderr) {
|
|
83
|
-
output += "\n--- stderr ---\n"
|
|
84
|
-
output += stderr.slice(0, MAX_OUTPUT)
|
|
85
|
-
}
|
|
86
|
-
if (e.killed) {
|
|
87
|
-
output += `\n--- Process timed out after ${timeout}ms ---`
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
name: "bash",
|
|
92
|
-
output: { stdout: output.trim(), exit_code: exitCode },
|
|
93
|
-
isError: true,
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
}
|
package/src/tools/tools/edit.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import type { Tool, ToolResult, ToolContext } from "../protocol"
|
|
4
|
-
|
|
5
|
-
export const editTool: Tool = {
|
|
6
|
-
spec() {
|
|
7
|
-
return {
|
|
8
|
-
name: "edit",
|
|
9
|
-
description:
|
|
10
|
-
"Edit a file by replacing old text with new text. Supports replacing all occurrences.",
|
|
11
|
-
inputSchema: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
file_path: {
|
|
15
|
-
type: "string",
|
|
16
|
-
description: "Path to the file to edit",
|
|
17
|
-
},
|
|
18
|
-
old_string: {
|
|
19
|
-
type: "string",
|
|
20
|
-
description: "The text to find and replace",
|
|
21
|
-
},
|
|
22
|
-
new_string: {
|
|
23
|
-
type: "string",
|
|
24
|
-
description: "The replacement text",
|
|
25
|
-
},
|
|
26
|
-
replace_all: {
|
|
27
|
-
type: "boolean",
|
|
28
|
-
description: "Replace all occurrences (default: false)",
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
required: ["file_path", "old_string", "new_string"],
|
|
32
|
-
},
|
|
33
|
-
isReadOnly: false,
|
|
34
|
-
isDestructive: true,
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
async run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {
|
|
39
|
-
const filePath = String(input.file_path ?? "")
|
|
40
|
-
const oldString = String(input.old_string ?? "")
|
|
41
|
-
const newString = String(input.new_string ?? "")
|
|
42
|
-
const replaceAll = Boolean(input.replace_all)
|
|
43
|
-
|
|
44
|
-
if (!filePath) {
|
|
45
|
-
return { name: "edit", output: { error: "file_path is required" }, isError: true }
|
|
46
|
-
}
|
|
47
|
-
if (!oldString) {
|
|
48
|
-
return { name: "edit", output: { error: "old_string is required" }, isError: true }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const resolved = path.resolve(ctx.cwd, filePath)
|
|
52
|
-
|
|
53
|
-
let content: string
|
|
54
|
-
try {
|
|
55
|
-
content = await fs.readFile(resolved, "utf-8")
|
|
56
|
-
} catch (err: unknown) {
|
|
57
|
-
return {
|
|
58
|
-
name: "edit",
|
|
59
|
-
output: { error: `Failed to read file: ${(err as Error).message}` },
|
|
60
|
-
isError: true,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!content.includes(oldString)) {
|
|
65
|
-
return {
|
|
66
|
-
name: "edit",
|
|
67
|
-
output: {
|
|
68
|
-
error: `old_string not found in ${filePath}`,
|
|
69
|
-
hint: "Make sure the old_string matches exactly, including whitespace.",
|
|
70
|
-
},
|
|
71
|
-
isError: true,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const occurrences = content.split(oldString).length - 1
|
|
76
|
-
if (occurrences > 1 && !replaceAll) {
|
|
77
|
-
return {
|
|
78
|
-
name: "edit",
|
|
79
|
-
output: {
|
|
80
|
-
error: `Found ${occurrences} occurrences. Use replace_all: true or provide more specific old_string.`,
|
|
81
|
-
occurrences,
|
|
82
|
-
},
|
|
83
|
-
isError: true,
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const newContent = replaceAll
|
|
88
|
-
? content.split(oldString).join(newString)
|
|
89
|
-
: content.replace(oldString, newString)
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
await fs.writeFile(resolved, newContent, "utf-8")
|
|
93
|
-
} catch (err: unknown) {
|
|
94
|
-
return {
|
|
95
|
-
name: "edit",
|
|
96
|
-
output: { error: `Failed to write file: ${(err as Error).message}` },
|
|
97
|
-
isError: true,
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
name: "edit",
|
|
103
|
-
output: {
|
|
104
|
-
file_path: filePath,
|
|
105
|
-
replacements: replaceAll ? occurrences : 1,
|
|
106
|
-
status: "edited",
|
|
107
|
-
},
|
|
108
|
-
isError: false,
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
}
|
package/src/tools/tools/glob.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import { glob } from "glob"
|
|
4
|
-
import type { Tool, ToolSpec, ToolResult, ToolContext } from "../protocol"
|
|
5
|
-
|
|
6
|
-
export const globTool: Tool = {
|
|
7
|
-
spec(): ToolSpec {
|
|
8
|
-
return {
|
|
9
|
-
name: "glob",
|
|
10
|
-
description:
|
|
11
|
-
"Search for files matching a glob pattern. Returns matching file paths relative to the workspace root.",
|
|
12
|
-
inputSchema: {
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: {
|
|
15
|
-
pattern: {
|
|
16
|
-
type: "string",
|
|
17
|
-
description: "Glob pattern (e.g. '**/*.ts', 'src/**/*.py')",
|
|
18
|
-
},
|
|
19
|
-
path: {
|
|
20
|
-
type: "string",
|
|
21
|
-
description: "Base directory for the search (default: workspace root)",
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
required: ["pattern"],
|
|
25
|
-
},
|
|
26
|
-
isReadOnly: true,
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
async run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {
|
|
31
|
-
const pattern = String(input.pattern ?? "")
|
|
32
|
-
const baseDir = String(input.path ?? ctx.workspaceRoot)
|
|
33
|
-
|
|
34
|
-
if (!pattern) {
|
|
35
|
-
return { name: "glob", output: { error: "pattern is required" }, isError: true }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const matches = await glob(pattern, {
|
|
40
|
-
cwd: baseDir,
|
|
41
|
-
nodir: true,
|
|
42
|
-
absolute: false,
|
|
43
|
-
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/.next/**"],
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const truncated = matches.length > 500
|
|
47
|
-
const limited = truncated ? matches.slice(0, 500) : matches
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
name: "glob",
|
|
51
|
-
output: {
|
|
52
|
-
pattern,
|
|
53
|
-
baseDir,
|
|
54
|
-
matches: limited,
|
|
55
|
-
numFiles: matches.length,
|
|
56
|
-
truncated,
|
|
57
|
-
},
|
|
58
|
-
isError: false,
|
|
59
|
-
}
|
|
60
|
-
} catch (err: unknown) {
|
|
61
|
-
return {
|
|
62
|
-
name: "glob",
|
|
63
|
-
output: { error: String((err as Error).message ?? err) },
|
|
64
|
-
isError: true,
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
}
|