@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
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
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import https from "https"
|
|
2
|
-
import http from "http"
|
|
3
|
-
import type { Tool, ToolSpec, ToolResult, ToolContext } from "../protocol"
|
|
4
|
-
|
|
5
|
-
const MAX_RESPONSE_SIZE = 500_000
|
|
6
|
-
const TIMEOUT_MS = 30_000
|
|
7
|
-
|
|
8
|
-
export const webFetchTool: Tool = {
|
|
9
|
-
spec(): ToolSpec {
|
|
10
|
-
return {
|
|
11
|
-
name: "web-fetch",
|
|
12
|
-
description:
|
|
13
|
-
"Fetch content from a URL. Returns the response body as text. Supports HTTP and HTTPS. Use for reading web pages, API responses, or documentation.",
|
|
14
|
-
inputSchema: {
|
|
15
|
-
type: "object",
|
|
16
|
-
properties: {
|
|
17
|
-
url: {
|
|
18
|
-
type: "string",
|
|
19
|
-
description: "The URL to fetch (http:// or https://)",
|
|
20
|
-
},
|
|
21
|
-
method: {
|
|
22
|
-
type: "string",
|
|
23
|
-
description: "HTTP method (default: GET)",
|
|
24
|
-
},
|
|
25
|
-
headers: {
|
|
26
|
-
type: "object",
|
|
27
|
-
description: "Optional request headers as key-value pairs",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
required: ["url"],
|
|
31
|
-
},
|
|
32
|
-
isReadOnly: true,
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
run(input: Record<string, unknown>, _ctx: ToolContext): Promise<ToolResult> {
|
|
37
|
-
const url = String(input.url ?? "")
|
|
38
|
-
const method = String(input.method ?? "GET").toUpperCase()
|
|
39
|
-
const headers = (input.headers as Record<string, string>) ?? {}
|
|
40
|
-
|
|
41
|
-
if (!url) {
|
|
42
|
-
return Promise.resolve({
|
|
43
|
-
name: "web-fetch",
|
|
44
|
-
output: { error: "url is required" },
|
|
45
|
-
isError: true,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let parsedUrl: URL
|
|
50
|
-
try {
|
|
51
|
-
parsedUrl = new URL(url)
|
|
52
|
-
} catch {
|
|
53
|
-
return Promise.resolve({
|
|
54
|
-
name: "web-fetch",
|
|
55
|
-
output: { error: `Invalid URL: ${url}` },
|
|
56
|
-
isError: true,
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
61
|
-
return Promise.resolve({
|
|
62
|
-
name: "web-fetch",
|
|
63
|
-
output: { error: `Unsupported protocol: ${parsedUrl.protocol}. Use http:// or https://` },
|
|
64
|
-
isError: true,
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
const lib = parsedUrl.protocol === "https:" ? https : http
|
|
70
|
-
|
|
71
|
-
const req = lib.request(
|
|
72
|
-
url,
|
|
73
|
-
{
|
|
74
|
-
method,
|
|
75
|
-
headers: {
|
|
76
|
-
"User-Agent": "LLMTune-CLI/0.1.0",
|
|
77
|
-
Accept: "text/html,application/json,text/plain,*/*",
|
|
78
|
-
...headers,
|
|
79
|
-
},
|
|
80
|
-
timeout: TIMEOUT_MS,
|
|
81
|
-
},
|
|
82
|
-
(res) => {
|
|
83
|
-
// Follow redirects (up to 5)
|
|
84
|
-
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
85
|
-
const redirectUrl = new URL(res.headers.location, url).toString()
|
|
86
|
-
return Promise.resolve(
|
|
87
|
-
webFetchTool.run({ ...input, url: redirectUrl }, _ctx),
|
|
88
|
-
).then(resolve)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const chunks: Buffer[] = []
|
|
92
|
-
let size = 0
|
|
93
|
-
|
|
94
|
-
res.on("data", (chunk: Buffer) => {
|
|
95
|
-
size += chunk.length
|
|
96
|
-
if (size > MAX_RESPONSE_SIZE) {
|
|
97
|
-
req.destroy()
|
|
98
|
-
resolve({
|
|
99
|
-
name: "web-fetch",
|
|
100
|
-
output: {
|
|
101
|
-
error: `Response too large (${(size / 1024).toFixed(0)}KB). Maximum is ${MAX_RESPONSE_SIZE / 1024}KB.`,
|
|
102
|
-
},
|
|
103
|
-
isError: true,
|
|
104
|
-
})
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
chunks.push(chunk)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
res.on("end", () => {
|
|
111
|
-
const body = Buffer.concat(chunks).toString("utf-8")
|
|
112
|
-
const truncated = body.length > 50_000
|
|
113
|
-
const content = truncated ? body.slice(0, 50_000) + "\n... (truncated)" : body
|
|
114
|
-
|
|
115
|
-
resolve({
|
|
116
|
-
name: "web-fetch",
|
|
117
|
-
output: {
|
|
118
|
-
url,
|
|
119
|
-
status: res.statusCode ?? 0,
|
|
120
|
-
contentType: res.headers["content-type"] ?? "unknown",
|
|
121
|
-
content,
|
|
122
|
-
truncated,
|
|
123
|
-
},
|
|
124
|
-
isError: false,
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
res.on("error", (err) => {
|
|
129
|
-
resolve({
|
|
130
|
-
name: "web-fetch",
|
|
131
|
-
output: { error: `Response error: ${err.message}` },
|
|
132
|
-
isError: true,
|
|
133
|
-
})
|
|
134
|
-
})
|
|
135
|
-
},
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
req.on("error", (err) => {
|
|
139
|
-
resolve({
|
|
140
|
-
name: "web-fetch",
|
|
141
|
-
output: { error: `Request failed: ${err.message}` },
|
|
142
|
-
isError: true,
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
req.on("timeout", () => {
|
|
147
|
-
req.destroy()
|
|
148
|
-
resolve({
|
|
149
|
-
name: "web-fetch",
|
|
150
|
-
output: { error: `Request timed out after ${TIMEOUT_MS / 1000}s` },
|
|
151
|
-
isError: true,
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
req.end()
|
|
156
|
-
})
|
|
157
|
-
},
|
|
158
|
-
}
|
package/src/tools/tools/write.ts
DELETED
|
@@ -1,52 +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 writeTool: Tool = {
|
|
6
|
-
spec(): ToolSpec {
|
|
7
|
-
return {
|
|
8
|
-
name: "write",
|
|
9
|
-
description:
|
|
10
|
-
"Create or overwrite a file with the given content. Path is relative to workspace root.",
|
|
11
|
-
inputSchema: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
file_path: { type: "string", description: "Path to write (relative to workspace)" },
|
|
15
|
-
content: { type: "string", description: "Content to write" },
|
|
16
|
-
create_dirs: { type: "boolean", description: "Create parent dirs (default: true)" },
|
|
17
|
-
},
|
|
18
|
-
required: ["file_path", "content"],
|
|
19
|
-
},
|
|
20
|
-
isDestructive: true,
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
run(input: Record<string, unknown>, ctx: ToolContext): ToolResult {
|
|
25
|
-
const filePath = String(input.file_path ?? "")
|
|
26
|
-
const content = String(input.content ?? "")
|
|
27
|
-
const createDirs = input.create_dirs !== false
|
|
28
|
-
const absPath = path.resolve(ctx.cwd, filePath)
|
|
29
|
-
|
|
30
|
-
if (!absPath.startsWith(path.resolve(ctx.cwd))) {
|
|
31
|
-
return { name: "write", output: { error: "Cannot write outside workspace" }, isError: true }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
if (createDirs) {
|
|
36
|
-
fs.mkdirSync(path.dirname(absPath), { recursive: true })
|
|
37
|
-
}
|
|
38
|
-
fs.writeFileSync(absPath, content, "utf-8")
|
|
39
|
-
return {
|
|
40
|
-
name: "write",
|
|
41
|
-
output: { type: "file_written", filePath, bytesWritten: Buffer.byteLength(content) },
|
|
42
|
-
isError: false,
|
|
43
|
-
}
|
|
44
|
-
} catch (err: unknown) {
|
|
45
|
-
return {
|
|
46
|
-
name: "write",
|
|
47
|
-
output: { error: `Write failed: ${(err as Error).message}` },
|
|
48
|
-
isError: true,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
}
|
package/src/tools/validation.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSON Schema validation for tool inputs.
|
|
3
|
-
* Ported from Clawd-Code's schema_validation.py.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface ValidationIssue {
|
|
7
|
-
path: string
|
|
8
|
-
message: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function typeName(value: unknown): string {
|
|
12
|
-
if (value === null) return "null"
|
|
13
|
-
if (typeof value === "boolean") return "boolean"
|
|
14
|
-
if (typeof value === "number") {
|
|
15
|
-
if (Number.isInteger(value)) return "integer"
|
|
16
|
-
return "number"
|
|
17
|
-
}
|
|
18
|
-
if (typeof value === "string") return "string"
|
|
19
|
-
if (Array.isArray(value)) return "array"
|
|
20
|
-
if (typeof value === "object") return "object"
|
|
21
|
-
return typeof value
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class ToolInputError extends Error {
|
|
25
|
-
constructor(message: string) {
|
|
26
|
-
super(message)
|
|
27
|
-
this.name = "ToolInputError"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function validateJsonSchema(
|
|
32
|
-
value: unknown,
|
|
33
|
-
schema: Record<string, unknown>,
|
|
34
|
-
rootName = "input"
|
|
35
|
-
): void {
|
|
36
|
-
const issues: ValidationIssue[] = []
|
|
37
|
-
validateNode(value, schema, rootName, issues)
|
|
38
|
-
if (issues.length > 0) {
|
|
39
|
-
const rendered = issues
|
|
40
|
-
.slice(0, 5)
|
|
41
|
-
.map((i) => `${i.path}: ${i.message}`)
|
|
42
|
-
.join("; ")
|
|
43
|
-
const suffix = issues.length > 5 ? `; (+${issues.length - 5} more)` : ""
|
|
44
|
-
throw new ToolInputError(rendered + suffix)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function validateNode(
|
|
49
|
-
value: unknown,
|
|
50
|
-
schema: Record<string, unknown>,
|
|
51
|
-
path: string,
|
|
52
|
-
issues: ValidationIssue[]
|
|
53
|
-
): void {
|
|
54
|
-
if ("oneOf" in schema) {
|
|
55
|
-
const options = (schema.oneOf as Record<string, unknown>[]) || []
|
|
56
|
-
if (options.some((opt) => isValid(value, opt))) return
|
|
57
|
-
issues.push({ path, message: "does not match any allowed schema (oneOf)" })
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if ("anyOf" in schema) {
|
|
62
|
-
const options = (schema.anyOf as Record<string, unknown>[]) || []
|
|
63
|
-
if (options.some((opt) => isValid(value, opt))) return
|
|
64
|
-
issues.push({ path, message: "does not match any allowed schema (anyOf)" })
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const expectedType = schema.type as string | undefined
|
|
69
|
-
|
|
70
|
-
if (expectedType === "object") {
|
|
71
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
72
|
-
issues.push({ path, message: `expected object, got ${typeName(value)}` })
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
validateObject(value as Record<string, unknown>, schema, path, issues)
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (expectedType === "array") {
|
|
80
|
-
if (!Array.isArray(value)) {
|
|
81
|
-
issues.push({ path, message: `expected array, got ${typeName(value)}` })
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
const itemSchema = schema.items as Record<string, unknown> | undefined
|
|
85
|
-
if (itemSchema) {
|
|
86
|
-
value.forEach((item, idx) => {
|
|
87
|
-
validateNode(item, itemSchema, `${path}[${idx}]`, issues)
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (expectedType === "string") {
|
|
94
|
-
if (typeof value !== "string") {
|
|
95
|
-
issues.push({ path, message: `expected string, got ${typeName(value)}` })
|
|
96
|
-
}
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (expectedType === "boolean") {
|
|
101
|
-
if (typeof value !== "boolean") {
|
|
102
|
-
issues.push({ path, message: `expected boolean, got ${typeName(value)}` })
|
|
103
|
-
}
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (expectedType === "number") {
|
|
108
|
-
if (typeof value !== "number") {
|
|
109
|
-
issues.push({ path, message: `expected number, got ${typeName(value)}` })
|
|
110
|
-
}
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (expectedType === "integer") {
|
|
115
|
-
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
116
|
-
issues.push({ path, message: `expected integer, got ${typeName(value)}` })
|
|
117
|
-
}
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if ("enum" in schema) {
|
|
122
|
-
const allowed = (schema.enum as unknown[]) || []
|
|
123
|
-
if (!allowed.includes(value)) {
|
|
124
|
-
issues.push({
|
|
125
|
-
path,
|
|
126
|
-
message: `expected one of ${JSON.stringify(allowed)}, got ${JSON.stringify(value)}`,
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function validateObject(
|
|
133
|
-
value: Record<string, unknown>,
|
|
134
|
-
schema: Record<string, unknown>,
|
|
135
|
-
path: string,
|
|
136
|
-
issues: ValidationIssue[]
|
|
137
|
-
): void {
|
|
138
|
-
const required = new Set((schema.required as string[]) || [])
|
|
139
|
-
const properties = (schema.properties as Record<string, Record<string, unknown>>) || {}
|
|
140
|
-
const additional = schema.additionalProperties !== false
|
|
141
|
-
|
|
142
|
-
for (const req of required) {
|
|
143
|
-
if (!(req in value)) {
|
|
144
|
-
issues.push({ path, message: `missing required field '${req}'` })
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
for (const [key, val] of Object.entries(value)) {
|
|
149
|
-
const propSchema = properties[key]
|
|
150
|
-
if (!propSchema) {
|
|
151
|
-
if (!additional) {
|
|
152
|
-
issues.push({ path: `${path}.${key}`, message: "unexpected field" })
|
|
153
|
-
}
|
|
154
|
-
continue
|
|
155
|
-
}
|
|
156
|
-
validateNode(val, propSchema, `${path}.${key}`, issues)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function isValid(value: unknown, schema: Record<string, unknown>): boolean {
|
|
161
|
-
const issues: ValidationIssue[] = []
|
|
162
|
-
validateNode(value, schema, "$", issues)
|
|
163
|
-
return issues.length === 0
|
|
164
|
-
}
|
package/src/utils/markdown.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Terminal markdown renderer.
|
|
3
|
-
* Converts markdown text to chalk-formatted terminal output.
|
|
4
|
-
*/
|
|
5
|
-
import chalk from "chalk"
|
|
6
|
-
|
|
7
|
-
interface RenderOptions {
|
|
8
|
-
maxWidth?: number
|
|
9
|
-
indent?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function renderMarkdown(text: string, options?: RenderOptions): string {
|
|
13
|
-
const lines = text.split("\n")
|
|
14
|
-
const result: string[] = []
|
|
15
|
-
|
|
16
|
-
for (const line of lines) {
|
|
17
|
-
result.push(renderLine(line, options))
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return result.join("\n")
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function renderLine(line: string, options?: RenderOptions): string {
|
|
24
|
-
const trimmed = line.trimStart()
|
|
25
|
-
|
|
26
|
-
// Headings
|
|
27
|
-
if (trimmed.startsWith("###### ")) return chalk.bold.cyan(trimmed.slice(7))
|
|
28
|
-
if (trimmed.startsWith("##### ")) return chalk.bold.cyan(trimmed.slice(6))
|
|
29
|
-
if (trimmed.startsWith("#### ")) return chalk.bold.cyan(trimmed.slice(5))
|
|
30
|
-
if (trimmed.startsWith("### ")) return chalk.bold.cyan(trimmed.slice(4))
|
|
31
|
-
if (trimmed.startsWith("## ")) return chalk.bold.blue(trimmed.slice(3))
|
|
32
|
-
if (trimmed.startsWith("# ")) return chalk.bold.white(trimmed.slice(2))
|
|
33
|
-
|
|
34
|
-
// Code blocks
|
|
35
|
-
if (trimmed.startsWith("```")) {
|
|
36
|
-
return chalk.dim(trimmed)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Blockquotes
|
|
40
|
-
if (trimmed.startsWith("> ")) {
|
|
41
|
-
return chalk.dim("│ " + renderInline(trimmed.slice(2)))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Lists
|
|
45
|
-
if (/^[-*+]\s/.test(trimmed)) {
|
|
46
|
-
return chalk.cyan("•") + " " + renderInline(trimmed.slice(2))
|
|
47
|
-
}
|
|
48
|
-
if (/^\d+\.\s/.test(trimmed)) {
|
|
49
|
-
const match = trimmed.match(/^(\d+\.)\s(.*)$/)
|
|
50
|
-
if (match) return chalk.cyan(match[1]) + " " + renderInline(match[2])
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Horizontal rule
|
|
54
|
-
if (/^[-*_]{3,}\s*$/.test(trimmed)) {
|
|
55
|
-
return chalk.dim("─".repeat(options?.maxWidth ?? 60))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return renderInline(line)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function renderInline(text: string): string {
|
|
62
|
-
// Bold
|
|
63
|
-
text = text.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk.bold(content))
|
|
64
|
-
// Italic
|
|
65
|
-
text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, (_, content) => chalk.italic(content))
|
|
66
|
-
// Inline code
|
|
67
|
-
text = text.replace(/`([^`]+)`/g, (_, content) => chalk.bgBlackBright(content))
|
|
68
|
-
// Links
|
|
69
|
-
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) => chalk.blue.underline(label) + chalk.dim(` (${url})`))
|
|
70
|
-
|
|
71
|
-
return text
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function renderCodeBlock(code: string, language?: string): string {
|
|
75
|
-
const header = language ? chalk.dim(`─ ${language} ─`) : ""
|
|
76
|
-
const lines = code.split("\n").map((line, i) => {
|
|
77
|
-
const num = String(i + 1).padStart(4)
|
|
78
|
-
return chalk.dim(`${num} │`) + ` ${line}`
|
|
79
|
-
})
|
|
80
|
-
return header + (header ? "\n" : "") + lines.join("\n")
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function renderTable(headers: string[], rows: string[][]): string {
|
|
84
|
-
const colWidths = headers.map((h, i) => {
|
|
85
|
-
const maxDataLen = Math.max(...rows.map((r) => (r[i] ?? "").length))
|
|
86
|
-
return Math.max(h.length, maxDataLen) + 2
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
const headerLine = headers.map((h, i) => chalk.bold(h.padEnd(colWidths[i]))).join("")
|
|
90
|
-
const separator = colWidths.map((w) => chalk.dim("─".repeat(w))).join(chalk.dim("─"))
|
|
91
|
-
const dataLines = rows.map((row) =>
|
|
92
|
-
row.map((cell, i) => (cell ?? "").padEnd(colWidths[i])).join(""),
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
return [headerLine, separator, ...dataLines].join("\n")
|
|
96
|
-
}
|