@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.
Files changed (196) 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 +49 -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 +1 -1
  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 +374 -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 +142 -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/package.json +2 -2
  99. package/dist/agent/conversation.d.ts.map +0 -1
  100. package/dist/agent/loop.d.ts.map +0 -1
  101. package/dist/agent/planner.d.ts.map +0 -1
  102. package/dist/auth/client.d.ts.map +0 -1
  103. package/dist/auth/config.d.ts.map +0 -1
  104. package/dist/commands/chat.d.ts.map +0 -1
  105. package/dist/commands/config.d.ts.map +0 -1
  106. package/dist/commands/login.d.ts.map +0 -1
  107. package/dist/commands/marketplace.d.ts.map +0 -1
  108. package/dist/commands/models.d.ts.map +0 -1
  109. package/dist/compact/history-store.d.ts.map +0 -1
  110. package/dist/compact/microcompact.d.ts.map +0 -1
  111. package/dist/compact/service.d.ts.map +0 -1
  112. package/dist/context/analyzer.d.ts.map +0 -1
  113. package/dist/context/builder.d.ts.map +0 -1
  114. package/dist/context/cache.d.ts.map +0 -1
  115. package/dist/context/git-context.d.ts.map +0 -1
  116. package/dist/context/llmtune-md.d.ts.map +0 -1
  117. package/dist/context/workspace.d.ts.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/marketplace/client.d.ts.map +0 -1
  120. package/dist/memory/files.d.ts.map +0 -1
  121. package/dist/memory/service.d.ts.map +0 -1
  122. package/dist/repl/repl.d.ts.map +0 -1
  123. package/dist/skills/args.d.ts.map +0 -1
  124. package/dist/skills/frontmatter.d.ts.map +0 -1
  125. package/dist/skills/loader.d.ts.map +0 -1
  126. package/dist/skills/registry.d.ts.map +0 -1
  127. package/dist/skills/signing/signer.d.ts.map +0 -1
  128. package/dist/skills/trust.d.ts.map +0 -1
  129. package/dist/telemetry/logger.d.ts.map +0 -1
  130. package/dist/tools/permissions.d.ts.map +0 -1
  131. package/dist/tools/protocol.d.ts.map +0 -1
  132. package/dist/tools/registry.d.ts.map +0 -1
  133. package/dist/tools/sandbox/docker.d.ts.map +0 -1
  134. package/dist/tools/sandbox/index.d.ts.map +0 -1
  135. package/dist/tools/tools/ask-user.d.ts.map +0 -1
  136. package/dist/tools/tools/bash.d.ts.map +0 -1
  137. package/dist/tools/tools/edit.d.ts.map +0 -1
  138. package/dist/tools/tools/glob.d.ts.map +0 -1
  139. package/dist/tools/tools/grep.d.ts.map +0 -1
  140. package/dist/tools/tools/read.d.ts.map +0 -1
  141. package/dist/tools/tools/web-fetch.d.ts.map +0 -1
  142. package/dist/tools/tools/write.d.ts.map +0 -1
  143. package/dist/tools/validation.d.ts.map +0 -1
  144. package/dist/utils/markdown.d.ts.map +0 -1
  145. package/dist/utils/streaming.d.ts.map +0 -1
  146. package/dist/utils/tokens.d.ts.map +0 -1
  147. package/src/agent/conversation.ts +0 -140
  148. package/src/agent/loop.ts +0 -215
  149. package/src/agent/planner.ts +0 -55
  150. package/src/auth/client.ts +0 -19
  151. package/src/auth/config.ts +0 -89
  152. package/src/commands/chat.ts +0 -28
  153. package/src/commands/config.ts +0 -36
  154. package/src/commands/login.ts +0 -63
  155. package/src/commands/marketplace.ts +0 -190
  156. package/src/commands/models.ts +0 -74
  157. package/src/compact/history-store.ts +0 -101
  158. package/src/compact/microcompact.ts +0 -49
  159. package/src/compact/service.ts +0 -154
  160. package/src/context/analyzer.ts +0 -127
  161. package/src/context/builder.ts +0 -123
  162. package/src/context/cache.ts +0 -11
  163. package/src/context/git-context.ts +0 -58
  164. package/src/context/llmtune-md.ts +0 -48
  165. package/src/context/workspace.ts +0 -139
  166. package/src/index.ts +0 -100
  167. package/src/marketplace/client.ts +0 -118
  168. package/src/memory/files.ts +0 -81
  169. package/src/memory/service.ts +0 -124
  170. package/src/repl/repl.ts +0 -400
  171. package/src/skills/args.ts +0 -35
  172. package/src/skills/builtin/explain-code/SKILL.md +0 -30
  173. package/src/skills/frontmatter.ts +0 -47
  174. package/src/skills/loader.ts +0 -25
  175. package/src/skills/registry.ts +0 -155
  176. package/src/skills/signing/signer.ts +0 -101
  177. package/src/skills/trust.ts +0 -50
  178. package/src/telemetry/logger.ts +0 -108
  179. package/src/tools/permissions.ts +0 -83
  180. package/src/tools/protocol.ts +0 -24
  181. package/src/tools/registry.ts +0 -93
  182. package/src/tools/sandbox/docker.ts +0 -225
  183. package/src/tools/sandbox/index.ts +0 -91
  184. package/src/tools/tools/ask-user.ts +0 -60
  185. package/src/tools/tools/bash.ts +0 -97
  186. package/src/tools/tools/edit.ts +0 -111
  187. package/src/tools/tools/glob.ts +0 -68
  188. package/src/tools/tools/grep.ts +0 -121
  189. package/src/tools/tools/read.ts +0 -57
  190. package/src/tools/tools/web-fetch.ts +0 -158
  191. package/src/tools/tools/write.ts +0 -52
  192. package/src/tools/validation.ts +0 -164
  193. package/src/utils/markdown.ts +0 -96
  194. package/src/utils/streaming.ts +0 -63
  195. package/src/utils/tokens.ts +0 -41
  196. package/tsconfig.json +0 -20
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }