@llmtune/cli 0.1.0 → 0.1.2
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 +39 -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 +3 -2
- 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 +375 -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 +143 -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/dist/version.d.ts +2 -0
- package/dist/version.js +9 -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,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
|
-
}
|
package/src/tools/tools/grep.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { exec } from "child_process"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import type { Tool, ToolSpec, ToolResult, ToolContext } from "../protocol"
|
|
4
|
-
|
|
5
|
-
export const grepTool: Tool = {
|
|
6
|
-
spec(): ToolSpec {
|
|
7
|
-
return {
|
|
8
|
-
name: "grep",
|
|
9
|
-
description:
|
|
10
|
-
'Search for a pattern in files. Returns matching file paths and lines. Supports regex patterns.',
|
|
11
|
-
inputSchema: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
pattern: {
|
|
15
|
-
type: "string",
|
|
16
|
-
description: "The pattern to search for (regex supported)",
|
|
17
|
-
},
|
|
18
|
-
path: {
|
|
19
|
-
type: "string",
|
|
20
|
-
description: "Directory to search in (default: workspace root)",
|
|
21
|
-
},
|
|
22
|
-
include: {
|
|
23
|
-
type: "string",
|
|
24
|
-
description: 'File glob to include (e.g. "*.ts", "*.{js,ts}")',
|
|
25
|
-
},
|
|
26
|
-
max_results: {
|
|
27
|
-
type: "number",
|
|
28
|
-
description: "Maximum number of results (default: 50)",
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
required: ["pattern"],
|
|
32
|
-
},
|
|
33
|
-
isReadOnly: true,
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
run(input: Record<string, unknown>, ctx: ToolContext): ToolResult {
|
|
38
|
-
const pattern = String(input.pattern ?? "")
|
|
39
|
-
if (!pattern) {
|
|
40
|
-
return { name: "grep", output: { error: "pattern is required" }, isError: true }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const searchPath = input.path
|
|
44
|
-
? path.resolve(ctx.cwd, String(input.path))
|
|
45
|
-
: ctx.cwd
|
|
46
|
-
|
|
47
|
-
const maxResults = typeof input.max_results === "number" ? input.max_results : 50
|
|
48
|
-
const include = input.include ? String(input.include) : undefined
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const isWin = process.platform === "win32"
|
|
52
|
-
let cmd: string
|
|
53
|
-
if (isWin) {
|
|
54
|
-
const includeOpt = include ? ` /include:${include.replace(/"/g, "")}` : ""
|
|
55
|
-
cmd = `findstr /s /n /r ${includeOpt} "${pattern.replace(/"/g, "")}" "${searchPath}\\*"`
|
|
56
|
-
} else {
|
|
57
|
-
const includeOpt = include ? ` --include="${include}"` : ""
|
|
58
|
-
cmd = `grep -r -n -E${includeOpt} "${pattern.replace(/"/g, "")}" "${searchPath}" 2>/dev/null | head -${maxResults}`
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const result = execSync(cmd, { maxBuffer: 1024 * 1024, timeout: 30000 })
|
|
62
|
-
const output = result.toString().trim()
|
|
63
|
-
const lines = output.split("\n").filter(Boolean)
|
|
64
|
-
|
|
65
|
-
const matches = lines.slice(0, maxResults).map((line) => {
|
|
66
|
-
const colonIdx = line.indexOf(":")
|
|
67
|
-
if (colonIdx === -1) return { file: "", line: 0, text: line }
|
|
68
|
-
const fileAndLine = line.slice(0, colonIdx)
|
|
69
|
-
const lastColon = fileAndLine.lastIndexOf(":")
|
|
70
|
-
if (lastColon === -1) {
|
|
71
|
-
return { file: fileAndLine, line: 0, text: line.slice(colonIdx + 1) }
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
file: fileAndLine.slice(0, lastColon),
|
|
75
|
-
line: parseInt(fileAndLine.slice(lastColon + 1), 10) || 0,
|
|
76
|
-
text: line.slice(colonIdx + 1),
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const files = [...new Set(matches.map((m) => m.file))]
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
name: "grep",
|
|
84
|
-
output: {
|
|
85
|
-
pattern,
|
|
86
|
-
path: searchPath,
|
|
87
|
-
matches: matches,
|
|
88
|
-
numFiles: files.length,
|
|
89
|
-
numMatches: matches.length,
|
|
90
|
-
truncated: matches.length >= maxResults,
|
|
91
|
-
},
|
|
92
|
-
isError: false,
|
|
93
|
-
}
|
|
94
|
-
} catch (err: any) {
|
|
95
|
-
if (err.status === 1 || err.status === 2) {
|
|
96
|
-
return {
|
|
97
|
-
name: "grep",
|
|
98
|
-
output: {
|
|
99
|
-
pattern,
|
|
100
|
-
path: searchPath,
|
|
101
|
-
matches: [],
|
|
102
|
-
numFiles: 0,
|
|
103
|
-
numMatches: 0,
|
|
104
|
-
truncated: false,
|
|
105
|
-
},
|
|
106
|
-
isError: false,
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
name: "grep",
|
|
111
|
-
output: { error: `grep failed: ${err.message}` },
|
|
112
|
-
isError: true,
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function execSync(cmd: string, opts: { maxBuffer: number; timeout: number }): Buffer {
|
|
119
|
-
const { execSync: _execSync } = require("child_process")
|
|
120
|
-
return _execSync(cmd, opts)
|
|
121
|
-
}
|
package/src/tools/tools/read.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import type { Tool, ToolSpec, ToolResult, ToolContext } from "../protocol"
|
|
4
|
-
|
|
5
|
-
export const readTool: Tool = {
|
|
6
|
-
spec(): ToolSpec {
|
|
7
|
-
return {
|
|
8
|
-
name: "read",
|
|
9
|
-
description:
|
|
10
|
-
"Read the contents of a file. Returns the file content with line numbers. Supports offset and limit for partial reads.",
|
|
11
|
-
inputSchema: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
file_path: { type: "string", description: "Path to the file to read" },
|
|
15
|
-
offset: { type: "number", description: "Line number to start reading from (1-based)" },
|
|
16
|
-
limit: { type: "number", description: "Maximum number of lines to read" },
|
|
17
|
-
},
|
|
18
|
-
required: ["file_path"],
|
|
19
|
-
},
|
|
20
|
-
isReadOnly: true,
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
run(input: Record<string, unknown>, ctx: ToolContext): ToolResult {
|
|
25
|
-
const filePath = path.resolve(ctx.cwd, String(input.file_path ?? ""))
|
|
26
|
-
|
|
27
|
-
if (!filePath.startsWith(ctx.cwd)) {
|
|
28
|
-
return { name: "read", output: { error: "Path is outside the workspace" }, isError: true }
|
|
29
|
-
}
|
|
30
|
-
if (!fs.existsSync(filePath)) {
|
|
31
|
-
return { name: "read", output: { error: `File not found: ${input.file_path}` }, isError: true }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const stat = fs.statSync(filePath)
|
|
35
|
-
if (stat.isDirectory()) {
|
|
36
|
-
return { name: "read", output: { error: `Path is a directory: ${input.file_path}` }, isError: true }
|
|
37
|
-
}
|
|
38
|
-
if (stat.size > 1_000_000) {
|
|
39
|
-
return { name: "read", output: { error: `File too large (${(stat.size / 1024).toFixed(0)}KB). Use offset/limit.` }, isError: true }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const raw = fs.readFileSync(filePath, "utf-8")
|
|
43
|
-
const lines = raw.split("\n")
|
|
44
|
-
const totalLines = lines.length
|
|
45
|
-
const offset = typeof input.offset === "number" ? Math.max(1, input.offset) : 1
|
|
46
|
-
const limit = typeof input.limit === "number" ? input.limit : Math.min(totalLines - offset + 1, 2000)
|
|
47
|
-
|
|
48
|
-
const sliced = lines.slice(offset - 1, offset - 1 + limit)
|
|
49
|
-
const numbered = sliced.map((line, i) => `${String(offset + i).padStart(6)}|${line}`).join("\n")
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
name: "read",
|
|
53
|
-
output: { type: "text", file: { filePath: String(input.file_path), startLine: offset, numLines: sliced.length, totalLines }, content: numbered },
|
|
54
|
-
isError: false,
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
}
|