@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.
Files changed (198) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/conversation.d.ts +42 -0
  3. package/dist/agent/conversation.js +105 -0
  4. package/dist/agent/loop.d.ts +19 -0
  5. package/dist/agent/loop.js +185 -0
  6. package/dist/agent/planner.d.ts +8 -0
  7. package/dist/agent/planner.js +43 -0
  8. package/dist/auth/client.d.ts +4 -0
  9. package/dist/auth/client.js +24 -0
  10. package/dist/auth/config.d.ts +21 -0
  11. package/dist/auth/config.js +83 -0
  12. package/dist/commands/chat.d.ts +5 -0
  13. package/dist/commands/chat.js +27 -0
  14. package/dist/commands/config.d.ts +2 -0
  15. package/dist/commands/config.js +37 -0
  16. package/dist/commands/login.d.ts +2 -0
  17. package/dist/commands/login.js +93 -0
  18. package/dist/commands/marketplace.d.ts +6 -0
  19. package/dist/commands/marketplace.js +213 -0
  20. package/dist/commands/models.d.ts +2 -0
  21. package/dist/commands/models.js +53 -0
  22. package/dist/compact/history-store.d.ts +29 -0
  23. package/dist/compact/history-store.js +110 -0
  24. package/dist/compact/microcompact.d.ts +10 -0
  25. package/dist/compact/microcompact.js +43 -0
  26. package/dist/compact/service.d.ts +13 -0
  27. package/dist/compact/service.js +156 -0
  28. package/dist/context/analyzer.d.ts +26 -0
  29. package/dist/context/analyzer.js +99 -0
  30. package/dist/context/builder.d.ts +13 -0
  31. package/dist/context/builder.js +144 -0
  32. package/dist/context/cache.d.ts +6 -0
  33. package/dist/context/cache.js +8 -0
  34. package/dist/context/git-context.d.ts +9 -0
  35. package/dist/context/git-context.js +39 -0
  36. package/dist/context/llmtune-md.d.ts +6 -0
  37. package/dist/context/llmtune-md.js +73 -0
  38. package/dist/context/workspace.d.ts +11 -0
  39. package/dist/context/workspace.js +115 -0
  40. package/dist/index.d.ts +3 -0
  41. package/dist/index.js +3 -2
  42. package/dist/marketplace/client.d.ts +52 -0
  43. package/dist/marketplace/client.js +86 -0
  44. package/dist/memory/files.d.ts +14 -0
  45. package/dist/memory/files.js +116 -0
  46. package/dist/memory/service.d.ts +22 -0
  47. package/dist/memory/service.js +146 -0
  48. package/dist/repl/repl.d.ts +8 -0
  49. package/dist/repl/repl.js +375 -0
  50. package/dist/skills/args.d.ts +10 -0
  51. package/dist/skills/args.js +37 -0
  52. package/dist/skills/frontmatter.d.ts +6 -0
  53. package/dist/skills/frontmatter.js +44 -0
  54. package/dist/skills/loader.d.ts +5 -0
  55. package/dist/skills/loader.js +59 -0
  56. package/dist/skills/registry.d.ts +27 -0
  57. package/dist/skills/registry.js +162 -0
  58. package/dist/skills/signing/signer.d.ts +19 -0
  59. package/dist/skills/signing/signer.js +110 -0
  60. package/dist/skills/trust.d.ts +11 -0
  61. package/dist/skills/trust.js +42 -0
  62. package/dist/telemetry/logger.d.ts +51 -0
  63. package/dist/telemetry/logger.js +135 -0
  64. package/dist/tools/permissions.d.ts +20 -0
  65. package/dist/tools/permissions.js +58 -0
  66. package/dist/tools/protocol.d.ts +22 -0
  67. package/dist/tools/protocol.js +3 -0
  68. package/dist/tools/registry.d.ts +20 -0
  69. package/dist/tools/registry.js +77 -0
  70. package/dist/tools/sandbox/docker.d.ts +16 -0
  71. package/dist/tools/sandbox/docker.js +240 -0
  72. package/dist/tools/sandbox/index.d.ts +18 -0
  73. package/dist/tools/sandbox/index.js +80 -0
  74. package/dist/tools/tools/ask-user.d.ts +3 -0
  75. package/dist/tools/tools/ask-user.js +56 -0
  76. package/dist/tools/tools/bash.d.ts +3 -0
  77. package/dist/tools/tools/bash.js +85 -0
  78. package/dist/tools/tools/edit.d.ts +3 -0
  79. package/dist/tools/tools/edit.js +138 -0
  80. package/dist/tools/tools/glob.d.ts +3 -0
  81. package/dist/tools/tools/glob.js +63 -0
  82. package/dist/tools/tools/grep.d.ts +3 -0
  83. package/dist/tools/tools/grep.js +148 -0
  84. package/dist/tools/tools/read.d.ts +3 -0
  85. package/dist/tools/tools/read.js +85 -0
  86. package/dist/tools/tools/web-fetch.d.ts +3 -0
  87. package/dist/tools/tools/web-fetch.js +143 -0
  88. package/dist/tools/tools/write.d.ts +3 -0
  89. package/dist/tools/tools/write.js +84 -0
  90. package/dist/tools/validation.d.ts +13 -0
  91. package/dist/tools/validation.js +142 -0
  92. package/dist/utils/markdown.d.ts +9 -0
  93. package/dist/utils/markdown.js +89 -0
  94. package/dist/utils/streaming.d.ts +10 -0
  95. package/dist/utils/streaming.js +63 -0
  96. package/dist/utils/tokens.d.ts +12 -0
  97. package/dist/utils/tokens.js +44 -0
  98. package/dist/version.d.ts +2 -0
  99. package/dist/version.js +9 -0
  100. package/package.json +2 -2
  101. package/dist/agent/conversation.d.ts.map +0 -1
  102. package/dist/agent/loop.d.ts.map +0 -1
  103. package/dist/agent/planner.d.ts.map +0 -1
  104. package/dist/auth/client.d.ts.map +0 -1
  105. package/dist/auth/config.d.ts.map +0 -1
  106. package/dist/commands/chat.d.ts.map +0 -1
  107. package/dist/commands/config.d.ts.map +0 -1
  108. package/dist/commands/login.d.ts.map +0 -1
  109. package/dist/commands/marketplace.d.ts.map +0 -1
  110. package/dist/commands/models.d.ts.map +0 -1
  111. package/dist/compact/history-store.d.ts.map +0 -1
  112. package/dist/compact/microcompact.d.ts.map +0 -1
  113. package/dist/compact/service.d.ts.map +0 -1
  114. package/dist/context/analyzer.d.ts.map +0 -1
  115. package/dist/context/builder.d.ts.map +0 -1
  116. package/dist/context/cache.d.ts.map +0 -1
  117. package/dist/context/git-context.d.ts.map +0 -1
  118. package/dist/context/llmtune-md.d.ts.map +0 -1
  119. package/dist/context/workspace.d.ts.map +0 -1
  120. package/dist/index.d.ts.map +0 -1
  121. package/dist/marketplace/client.d.ts.map +0 -1
  122. package/dist/memory/files.d.ts.map +0 -1
  123. package/dist/memory/service.d.ts.map +0 -1
  124. package/dist/repl/repl.d.ts.map +0 -1
  125. package/dist/skills/args.d.ts.map +0 -1
  126. package/dist/skills/frontmatter.d.ts.map +0 -1
  127. package/dist/skills/loader.d.ts.map +0 -1
  128. package/dist/skills/registry.d.ts.map +0 -1
  129. package/dist/skills/signing/signer.d.ts.map +0 -1
  130. package/dist/skills/trust.d.ts.map +0 -1
  131. package/dist/telemetry/logger.d.ts.map +0 -1
  132. package/dist/tools/permissions.d.ts.map +0 -1
  133. package/dist/tools/protocol.d.ts.map +0 -1
  134. package/dist/tools/registry.d.ts.map +0 -1
  135. package/dist/tools/sandbox/docker.d.ts.map +0 -1
  136. package/dist/tools/sandbox/index.d.ts.map +0 -1
  137. package/dist/tools/tools/ask-user.d.ts.map +0 -1
  138. package/dist/tools/tools/bash.d.ts.map +0 -1
  139. package/dist/tools/tools/edit.d.ts.map +0 -1
  140. package/dist/tools/tools/glob.d.ts.map +0 -1
  141. package/dist/tools/tools/grep.d.ts.map +0 -1
  142. package/dist/tools/tools/read.d.ts.map +0 -1
  143. package/dist/tools/tools/web-fetch.d.ts.map +0 -1
  144. package/dist/tools/tools/write.d.ts.map +0 -1
  145. package/dist/tools/validation.d.ts.map +0 -1
  146. package/dist/utils/markdown.d.ts.map +0 -1
  147. package/dist/utils/streaming.d.ts.map +0 -1
  148. package/dist/utils/tokens.d.ts.map +0 -1
  149. package/src/agent/conversation.ts +0 -140
  150. package/src/agent/loop.ts +0 -215
  151. package/src/agent/planner.ts +0 -55
  152. package/src/auth/client.ts +0 -19
  153. package/src/auth/config.ts +0 -89
  154. package/src/commands/chat.ts +0 -28
  155. package/src/commands/config.ts +0 -36
  156. package/src/commands/login.ts +0 -63
  157. package/src/commands/marketplace.ts +0 -190
  158. package/src/commands/models.ts +0 -74
  159. package/src/compact/history-store.ts +0 -101
  160. package/src/compact/microcompact.ts +0 -49
  161. package/src/compact/service.ts +0 -154
  162. package/src/context/analyzer.ts +0 -127
  163. package/src/context/builder.ts +0 -123
  164. package/src/context/cache.ts +0 -11
  165. package/src/context/git-context.ts +0 -58
  166. package/src/context/llmtune-md.ts +0 -48
  167. package/src/context/workspace.ts +0 -139
  168. package/src/index.ts +0 -100
  169. package/src/marketplace/client.ts +0 -118
  170. package/src/memory/files.ts +0 -81
  171. package/src/memory/service.ts +0 -124
  172. package/src/repl/repl.ts +0 -400
  173. package/src/skills/args.ts +0 -35
  174. package/src/skills/builtin/explain-code/SKILL.md +0 -30
  175. package/src/skills/frontmatter.ts +0 -47
  176. package/src/skills/loader.ts +0 -25
  177. package/src/skills/registry.ts +0 -155
  178. package/src/skills/signing/signer.ts +0 -101
  179. package/src/skills/trust.ts +0 -50
  180. package/src/telemetry/logger.ts +0 -108
  181. package/src/tools/permissions.ts +0 -83
  182. package/src/tools/protocol.ts +0 -24
  183. package/src/tools/registry.ts +0 -93
  184. package/src/tools/sandbox/docker.ts +0 -225
  185. package/src/tools/sandbox/index.ts +0 -91
  186. package/src/tools/tools/ask-user.ts +0 -60
  187. package/src/tools/tools/bash.ts +0 -97
  188. package/src/tools/tools/edit.ts +0 -111
  189. package/src/tools/tools/glob.ts +0 -68
  190. package/src/tools/tools/grep.ts +0 -121
  191. package/src/tools/tools/read.ts +0 -57
  192. package/src/tools/tools/web-fetch.ts +0 -158
  193. package/src/tools/tools/write.ts +0 -52
  194. package/src/tools/validation.ts +0 -164
  195. package/src/utils/markdown.ts +0 -96
  196. package/src/utils/streaming.ts +0 -63
  197. package/src/utils/tokens.ts +0 -41
  198. 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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }