@chronova/wiki-agent 1.1.4
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 +248 -0
- package/dist/agent.d.ts +35 -0
- package/dist/agent.js +317 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +113 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +95 -0
- package/dist/index-middleware.d.ts +5 -0
- package/dist/index-middleware.js +135 -0
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.js +145 -0
- package/dist/tools.d.ts +24 -0
- package/dist/tools.js +471 -0
- package/dist/tui/App.d.ts +10 -0
- package/dist/tui/App.js +31 -0
- package/dist/tui/CredentialsSetup.d.ts +8 -0
- package/dist/tui/CredentialsSetup.js +74 -0
- package/dist/tui/RunView.d.ts +12 -0
- package/dist/tui/RunView.js +94 -0
- package/package.json +57 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { readFile, writeFile, readdir, mkdir } from "node:fs/promises";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
const MAX_READ_LENGTH = 50_000;
|
|
7
|
+
const MAX_TOOL_RESULT_LENGTH = 10_000;
|
|
8
|
+
function truncateResult(result) {
|
|
9
|
+
if (result.length <= MAX_TOOL_RESULT_LENGTH) {
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
return result.slice(0, MAX_TOOL_RESULT_LENGTH) + "\n... (truncated)";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a path relative to the project root and validates it stays within
|
|
16
|
+
* the `.wiki/` directory. Throws on escape attempts.
|
|
17
|
+
*/
|
|
18
|
+
function resolveWikiPath(relativePath, projectRoot) {
|
|
19
|
+
const wikiRoot = path.resolve(projectRoot, ".wiki");
|
|
20
|
+
const resolved = path.resolve(projectRoot, relativePath);
|
|
21
|
+
if (!resolved.startsWith(wikiRoot + path.sep) && resolved !== wikiRoot) {
|
|
22
|
+
throw new Error(`Path ${relativePath} resolves outside .wiki/. Only files under .wiki/ can be written.`);
|
|
23
|
+
}
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves a path relative to the project root (for read-only operations).
|
|
28
|
+
*/
|
|
29
|
+
function resolveProjectPath(relativePath, projectRoot) {
|
|
30
|
+
const resolved = path.resolve(projectRoot, relativePath);
|
|
31
|
+
if (!resolved.startsWith(projectRoot + path.sep) && resolved !== projectRoot) {
|
|
32
|
+
throw new Error(`Path ${relativePath} resolves outside the project root.`);
|
|
33
|
+
}
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
export function createTools(projectRoot) {
|
|
37
|
+
const readFileTool = {
|
|
38
|
+
definition: {
|
|
39
|
+
type: "function",
|
|
40
|
+
function: {
|
|
41
|
+
name: "read_file",
|
|
42
|
+
description: "Read the contents of a file from the project root. Use a relative path.",
|
|
43
|
+
parameters: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
path: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Relative path to the file (e.g. src/index.ts)",
|
|
49
|
+
},
|
|
50
|
+
offset: {
|
|
51
|
+
type: "number",
|
|
52
|
+
description: "Line offset to start reading from (0-indexed). Default: 0",
|
|
53
|
+
},
|
|
54
|
+
limit: {
|
|
55
|
+
type: "number",
|
|
56
|
+
description: "Maximum number of lines to read. Default: 500",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["path"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
handler: async (args) => {
|
|
64
|
+
const filePath = resolveProjectPath(args.path, projectRoot);
|
|
65
|
+
const offset = args.offset ?? 0;
|
|
66
|
+
const limit = args.limit ?? 500;
|
|
67
|
+
const content = await readFile(filePath, "utf8");
|
|
68
|
+
const lines = content.split("\n");
|
|
69
|
+
const end = Math.min(offset + limit, lines.length);
|
|
70
|
+
const result = lines.slice(offset, end).join("\n");
|
|
71
|
+
return truncateResult(result);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const writeFileTool = {
|
|
75
|
+
definition: {
|
|
76
|
+
type: "function",
|
|
77
|
+
function: {
|
|
78
|
+
name: "write_file",
|
|
79
|
+
description: "Write content to a file under .wiki/. Creates parent directories if needed. The path must be relative and start with .wiki/.",
|
|
80
|
+
parameters: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
path: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "Relative path under .wiki/ (e.g. .wiki/quickstart.md)",
|
|
86
|
+
},
|
|
87
|
+
content: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: "The full content to write",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
required: ["path", "content"],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
handler: async (args) => {
|
|
97
|
+
const filePath = resolveWikiPath(args.path, projectRoot);
|
|
98
|
+
const content = args.content;
|
|
99
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
100
|
+
await writeFile(filePath, content, "utf8");
|
|
101
|
+
return `Wrote ${args.path}`;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const editFileTool = {
|
|
105
|
+
definition: {
|
|
106
|
+
type: "function",
|
|
107
|
+
function: {
|
|
108
|
+
name: "edit_file",
|
|
109
|
+
description: "Replace text in a file under .wiki/. Finds old_string and replaces with new_string.",
|
|
110
|
+
parameters: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
path: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "Relative path under .wiki/ (e.g. .wiki/quickstart.md)",
|
|
116
|
+
},
|
|
117
|
+
old_string: {
|
|
118
|
+
type: "string",
|
|
119
|
+
description: "The text to find",
|
|
120
|
+
},
|
|
121
|
+
new_string: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "The replacement text",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ["path", "old_string", "new_string"],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
handler: async (args) => {
|
|
131
|
+
const filePath = resolveWikiPath(args.path, projectRoot);
|
|
132
|
+
const oldString = args.old_string;
|
|
133
|
+
const newString = args.new_string;
|
|
134
|
+
const content = await readFile(filePath, "utf8");
|
|
135
|
+
const newContent = content.replace(oldString, newString);
|
|
136
|
+
if (newContent === content) {
|
|
137
|
+
return `No match found for old_string in ${args.path}`;
|
|
138
|
+
}
|
|
139
|
+
await writeFile(filePath, newContent, "utf8");
|
|
140
|
+
return `Edited ${args.path}`;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
const lsTool = {
|
|
144
|
+
definition: {
|
|
145
|
+
type: "function",
|
|
146
|
+
function: {
|
|
147
|
+
name: "ls",
|
|
148
|
+
description: "List the contents of a directory. Returns file and directory names.",
|
|
149
|
+
parameters: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
path: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "Relative path to the directory (default: project root)",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
required: [],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
handler: async (args) => {
|
|
162
|
+
const dirPath = resolveProjectPath(args.path ?? ".", projectRoot);
|
|
163
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
164
|
+
const result = entries
|
|
165
|
+
.map((e) => `${e.isDirectory() ? e.name + "/" : e.name}`)
|
|
166
|
+
.sort()
|
|
167
|
+
.join("\n");
|
|
168
|
+
return truncateResult(result || "(empty directory)");
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
const grepTool = {
|
|
172
|
+
definition: {
|
|
173
|
+
type: "function",
|
|
174
|
+
function: {
|
|
175
|
+
name: "grep",
|
|
176
|
+
description: "Search for a text pattern in files. Uses the system grep. Searches from the project root.",
|
|
177
|
+
parameters: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
pattern: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "The text pattern to search for",
|
|
183
|
+
},
|
|
184
|
+
path: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: "Relative path to search in (default: project root)",
|
|
187
|
+
},
|
|
188
|
+
glob: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Optional glob pattern to filter files (e.g. *.ts)",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
required: ["pattern"],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
handler: async (args) => {
|
|
198
|
+
const pattern = args.pattern;
|
|
199
|
+
const searchPath = resolveProjectPath(args.path ?? ".", projectRoot);
|
|
200
|
+
const globPattern = args.glob ?? "";
|
|
201
|
+
const cmd = [
|
|
202
|
+
"grep",
|
|
203
|
+
"-rn",
|
|
204
|
+
"--include=" + (globPattern || "*.ts *.tsx *.js *.jsx *.py *.go *.rs *.java *.rb *.php *.md *.yml *.yaml *.json *.toml *.sh"),
|
|
205
|
+
"--",
|
|
206
|
+
pattern.replace(/'/g, "'\\''"),
|
|
207
|
+
searchPath,
|
|
208
|
+
].join(" ");
|
|
209
|
+
try {
|
|
210
|
+
const { stdout } = await execAsync(cmd, {
|
|
211
|
+
maxBuffer: 1024 * 1024,
|
|
212
|
+
});
|
|
213
|
+
return truncateResult(stdout || "(no matches)");
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return "(no matches)";
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
const globTool = {
|
|
221
|
+
definition: {
|
|
222
|
+
type: "function",
|
|
223
|
+
function: {
|
|
224
|
+
name: "glob",
|
|
225
|
+
description: "Find files matching a pattern. Uses the system find command.",
|
|
226
|
+
parameters: {
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
pattern: {
|
|
230
|
+
type: "string",
|
|
231
|
+
description: "Glob pattern (e.g. *.ts, **/*.tsx). * matches within a directory, ** matches recursively.",
|
|
232
|
+
},
|
|
233
|
+
path: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Relative path to search in (default: project root)",
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
required: ["pattern"],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
handler: async (args) => {
|
|
243
|
+
const pattern = args.pattern;
|
|
244
|
+
const searchPath = resolveProjectPath(args.path ?? ".", projectRoot);
|
|
245
|
+
const findPattern = pattern
|
|
246
|
+
.replace(/\*\*/g, "")
|
|
247
|
+
.replace(/\*/g, "");
|
|
248
|
+
const cmd = [
|
|
249
|
+
"find",
|
|
250
|
+
searchPath,
|
|
251
|
+
"-name",
|
|
252
|
+
`'${findPattern}'`,
|
|
253
|
+
"-type",
|
|
254
|
+
"f",
|
|
255
|
+
"-not",
|
|
256
|
+
"-path",
|
|
257
|
+
"'*/node_modules/*'",
|
|
258
|
+
"-not",
|
|
259
|
+
"-path",
|
|
260
|
+
"'*/.git/*'",
|
|
261
|
+
"-not",
|
|
262
|
+
"-path",
|
|
263
|
+
"'*/dist/*'",
|
|
264
|
+
].join(" ");
|
|
265
|
+
try {
|
|
266
|
+
const { stdout } = await execAsync(cmd, {
|
|
267
|
+
maxBuffer: 1024 * 1024,
|
|
268
|
+
});
|
|
269
|
+
return truncateResult(stdout || "(no files found)");
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return "(no files found)";
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
const gitTool = {
|
|
277
|
+
definition: {
|
|
278
|
+
type: "function",
|
|
279
|
+
function: {
|
|
280
|
+
name: "git",
|
|
281
|
+
description: "Run a read-only git subcommand in the project root. Use for git log, git diff, git show, git ls-files, git blame, etc. The agent has no general shell access — only git is exposed for repository history and inspection.",
|
|
282
|
+
parameters: {
|
|
283
|
+
type: "object",
|
|
284
|
+
properties: {
|
|
285
|
+
args: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "Git subcommand and arguments, without the leading 'git'. Example: 'log --oneline -30', 'diff --stat', 'ls-files', 'show HEAD:README.md'.",
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
required: ["args"],
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
handler: async (args) => {
|
|
295
|
+
const argString = args.args ?? "";
|
|
296
|
+
// Only read-only / inspection subcommands are permitted. The agent
|
|
297
|
+
// cannot mutate the repository state through this tool. Flags that
|
|
298
|
+
// would mutate state (e.g. -d, -D, --delete) are blocked by the
|
|
299
|
+
// metacharacter guard below for any subcommand that accepts them, but
|
|
300
|
+
// the list itself excludes mutating subcommands entirely.
|
|
301
|
+
const ALLOWED_GIT_SUBCOMMANDS = {
|
|
302
|
+
log: true, diff: true, show: true, "ls-files": true, blame: true,
|
|
303
|
+
status: true, remote: true, describe: true, "rev-parse": true,
|
|
304
|
+
shortlog: true, "name-rev": true, "ls-tree": true, "cat-file": true,
|
|
305
|
+
reflog: true,
|
|
306
|
+
};
|
|
307
|
+
const tokens = argString.trim().split(/\s+/);
|
|
308
|
+
const subcommand = tokens[0] ?? "";
|
|
309
|
+
if (!ALLOWED_GIT_SUBCOMMANDS[subcommand]) {
|
|
310
|
+
return `Error: git subcommand '${subcommand}' is not permitted. Only read-only inspection subcommands are allowed (log, diff, show, ls-files, blame, status, remote, describe, rev-parse, shortlog, name-rev, ls-tree, cat-file, reflog).`;
|
|
311
|
+
}
|
|
312
|
+
// Reject shell metacharacters that could chain commands or inject
|
|
313
|
+
// flags. Git flags begin with '-', which we permit, so the guard
|
|
314
|
+
// focuses on shell-control and redirection metacharacters.
|
|
315
|
+
if (/[;&|`$()<>]/.test(argString)) {
|
|
316
|
+
return "Error: shell metacharacters are not permitted in git arguments.";
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const { stdout, stderr } = await execAsync(`git ${argString}`, {
|
|
320
|
+
cwd: projectRoot,
|
|
321
|
+
maxBuffer: 1024 * 1024,
|
|
322
|
+
timeout: 30_000,
|
|
323
|
+
});
|
|
324
|
+
const result = stdout + (stderr ? `\n${stderr}` : "");
|
|
325
|
+
return truncateResult(result || "(no output)");
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
329
|
+
return truncateResult(`Error: ${message}`);
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
const astGrepTool = {
|
|
334
|
+
definition: {
|
|
335
|
+
type: "function",
|
|
336
|
+
function: {
|
|
337
|
+
name: "ast_grep",
|
|
338
|
+
description: "Search code by AST pattern using ast-grep. Matches code structure (not text), so metavariables and node shapes work. Requires an explicit language. Use for precise structural queries like finding all calls to a function, all exports, or a specific control-flow shape.",
|
|
339
|
+
parameters: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
pattern: {
|
|
343
|
+
type: "string",
|
|
344
|
+
description: "AST pattern to match. Use $NAME for a single node metavariable and $$$ARGS for zero-or-more. Example: 'console.log($$$)'",
|
|
345
|
+
},
|
|
346
|
+
lang: {
|
|
347
|
+
type: "string",
|
|
348
|
+
description: "Language of the pattern. Supported: bash, c, cpp, csharp, css, elixir, go, haskell, html, java, javascript, json, jsx, kotlin, lua, nix, php, python, ruby, rust, scala, solidity, swift, tsx, typescript, yaml.",
|
|
349
|
+
},
|
|
350
|
+
path: {
|
|
351
|
+
type: "string",
|
|
352
|
+
description: "Relative path to search in (default: project root)",
|
|
353
|
+
},
|
|
354
|
+
selector: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "Optional AST kind to extract as the actual matcher (ast-grep --selector).",
|
|
357
|
+
},
|
|
358
|
+
strictness: {
|
|
359
|
+
type: "string",
|
|
360
|
+
description: "Optional pattern strictness: cst, smart, ast, relaxed, signature, template.",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
required: ["pattern", "lang"],
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
handler: async (args) => {
|
|
368
|
+
const pattern = args.pattern;
|
|
369
|
+
const lang = args.lang;
|
|
370
|
+
const searchPath = resolveProjectPath(args.path ?? ".", projectRoot);
|
|
371
|
+
const cmd = [
|
|
372
|
+
"ast-grep",
|
|
373
|
+
"run",
|
|
374
|
+
"--json=compact",
|
|
375
|
+
"--lang",
|
|
376
|
+
`'${lang.replace(/'/g, "'\\''")}'`,
|
|
377
|
+
"--pattern",
|
|
378
|
+
`'${pattern.replace(/'/g, "'\\''")}'`,
|
|
379
|
+
];
|
|
380
|
+
const selector = args.selector;
|
|
381
|
+
if (selector) {
|
|
382
|
+
cmd.push("--selector", `'${selector.replace(/'/g, "'\\''")}'`);
|
|
383
|
+
}
|
|
384
|
+
const strictness = args.strictness;
|
|
385
|
+
if (strictness) {
|
|
386
|
+
cmd.push("--strictness", `'${strictness.replace(/'/g, "'\\''")}'`);
|
|
387
|
+
}
|
|
388
|
+
cmd.push(`'${searchPath.replace(/'/g, "'\\''")}'`);
|
|
389
|
+
try {
|
|
390
|
+
const { stdout } = await execAsync(cmd.join(" "), {
|
|
391
|
+
cwd: projectRoot,
|
|
392
|
+
maxBuffer: 1024 * 1024,
|
|
393
|
+
timeout: 30_000,
|
|
394
|
+
});
|
|
395
|
+
return truncateResult(stdout || "(no matches)");
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
+
return truncateResult(`Error: ${message}`);
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
const astSearchTool = {
|
|
404
|
+
definition: {
|
|
405
|
+
type: "function",
|
|
406
|
+
function: {
|
|
407
|
+
name: "ast_search",
|
|
408
|
+
description: "Search code using an ast-grep YAML rule (inline). More powerful than ast_grep: supports relational/inside/has constraints and multiple rules separated by '---'. Use for complex structural queries that a single pattern cannot express.",
|
|
409
|
+
parameters: {
|
|
410
|
+
type: "object",
|
|
411
|
+
properties: {
|
|
412
|
+
rule: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "Inline ast-grep YAML rule(s). Must have id, language, and rule fields. Multiple rules separated by '---'.",
|
|
415
|
+
},
|
|
416
|
+
path: {
|
|
417
|
+
type: "string",
|
|
418
|
+
description: "Relative path to search in (default: project root)",
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
required: ["rule"],
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
handler: async (args) => {
|
|
426
|
+
const rule = args.rule;
|
|
427
|
+
const searchPath = resolveProjectPath(args.path ?? ".", projectRoot);
|
|
428
|
+
try {
|
|
429
|
+
const { stdout } = await execAsync(`ast-grep scan --json=compact --inline-rules '${rule.replace(/'/g, "'\\''")}' '${searchPath.replace(/'/g, "'\\''")}'`, {
|
|
430
|
+
cwd: projectRoot,
|
|
431
|
+
maxBuffer: 1024 * 1024,
|
|
432
|
+
timeout: 30_000,
|
|
433
|
+
});
|
|
434
|
+
return truncateResult(stdout || "(no matches)");
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
438
|
+
return truncateResult(`Error: ${message}`);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
return [
|
|
443
|
+
readFileTool,
|
|
444
|
+
writeFileTool,
|
|
445
|
+
editFileTool,
|
|
446
|
+
lsTool,
|
|
447
|
+
grepTool,
|
|
448
|
+
globTool,
|
|
449
|
+
gitTool,
|
|
450
|
+
astGrepTool,
|
|
451
|
+
astSearchTool,
|
|
452
|
+
];
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Execute a tool by name with the given arguments.
|
|
456
|
+
*/
|
|
457
|
+
export async function executeTool(toolName, args, projectRoot) {
|
|
458
|
+
const tools = createTools(projectRoot);
|
|
459
|
+
const tool = tools.find((t) => t.definition.function.name === toolName);
|
|
460
|
+
if (!tool) {
|
|
461
|
+
return `Unknown tool: ${toolName}`;
|
|
462
|
+
}
|
|
463
|
+
try {
|
|
464
|
+
return await tool.handler(args, projectRoot);
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
468
|
+
return `Error: ${message}`;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
export { MAX_READ_LENGTH, MAX_TOOL_RESULT_LENGTH };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ResolvedConfig } from "../config.js";
|
|
3
|
+
interface AppProps {
|
|
4
|
+
command: "init" | "update";
|
|
5
|
+
cwd: string;
|
|
6
|
+
config: ResolvedConfig;
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function App({ command, cwd, config, verbose }: AppProps): React.ReactElement;
|
|
10
|
+
export {};
|
package/dist/tui/App.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useState, useCallback } from "react";
|
|
2
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
3
|
+
import { CredentialsSetup } from "./CredentialsSetup.js";
|
|
4
|
+
import { RunView } from "./RunView.js";
|
|
5
|
+
export function App({ command, cwd, config, verbose }) {
|
|
6
|
+
const [needsSetup, setNeedsSetup] = useState(config.mode === "cloud" && !config.apiKey);
|
|
7
|
+
const [resolvedConfig, setResolvedConfig] = useState(config);
|
|
8
|
+
const { exit } = useApp();
|
|
9
|
+
useInput((input) => {
|
|
10
|
+
if (input === "q" || input === "\u0003") {
|
|
11
|
+
exit();
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
const handleConfigSaved = useCallback((newConfig) => {
|
|
15
|
+
setResolvedConfig(newConfig);
|
|
16
|
+
setNeedsSetup(false);
|
|
17
|
+
}, []);
|
|
18
|
+
if (needsSetup) {
|
|
19
|
+
return React.createElement(CredentialsSetup, {
|
|
20
|
+
cwd,
|
|
21
|
+
onConfigSaved: handleConfigSaved,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1 }, React.createElement(Text, null, React.createElement(Text, { bold: true }, "Wiki Agent v0.1.0"), " | ", React.createElement(Text, { color: "cyan" }, `Ollama (${resolvedConfig.mode})`), " | ", React.createElement(Text, { color: "gray" }, `model: ${resolvedConfig.model}`), " | ", React.createElement(Text, { color: "gray" }, cwd))), React.createElement(RunView, {
|
|
25
|
+
command,
|
|
26
|
+
cwd,
|
|
27
|
+
config: resolvedConfig,
|
|
28
|
+
verbose,
|
|
29
|
+
onExit: exit,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ResolvedConfig } from "../config.js";
|
|
3
|
+
interface CredentialsSetupProps {
|
|
4
|
+
cwd: string;
|
|
5
|
+
onConfigSaved: (config: ResolvedConfig) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function CredentialsSetup({ onConfigSaved, }: CredentialsSetupProps): React.ReactElement;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import TextInput from "ink-text-input";
|
|
4
|
+
import { saveGlobalConfig } from "../config.js";
|
|
5
|
+
export function CredentialsSetup({ onConfigSaved, }) {
|
|
6
|
+
const [step, setStep] = useState("mode-select");
|
|
7
|
+
const [mode, setMode] = useState("local");
|
|
8
|
+
const [apiKey, setApiKey] = useState("");
|
|
9
|
+
const [model, setModel] = useState("kimi-k2.7-code");
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
async function handleSave() {
|
|
12
|
+
setError(null);
|
|
13
|
+
setStep("saving");
|
|
14
|
+
try {
|
|
15
|
+
const globalConfig = {
|
|
16
|
+
mode,
|
|
17
|
+
defaultModel: model,
|
|
18
|
+
...(mode === "cloud" ? { apiKey } : {}),
|
|
19
|
+
};
|
|
20
|
+
await saveGlobalConfig(globalConfig);
|
|
21
|
+
const resolved = {
|
|
22
|
+
mode,
|
|
23
|
+
...(mode === "cloud" ? { apiKey } : {}),
|
|
24
|
+
baseUrl: mode === "cloud" ? "https://ollama.com" : "http://localhost:11434",
|
|
25
|
+
model,
|
|
26
|
+
};
|
|
27
|
+
onConfigSaved(resolved);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
31
|
+
setStep("mode-select");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
useInput((input) => {
|
|
35
|
+
if (step === "mode-select") {
|
|
36
|
+
if (input === "1") {
|
|
37
|
+
setMode("local");
|
|
38
|
+
setStep("model");
|
|
39
|
+
}
|
|
40
|
+
else if (input === "2") {
|
|
41
|
+
setMode("cloud");
|
|
42
|
+
setStep("api-key");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
if (step === "saving") {
|
|
47
|
+
return React.createElement(Box, null, React.createElement(Text, { color: "cyan" }, "Saving configuration..."));
|
|
48
|
+
}
|
|
49
|
+
if (step === "mode-select") {
|
|
50
|
+
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { bold: true }, "Select Ollama mode:"), React.createElement(Text, null, React.createElement(Text, { color: "green" }, " 1. "), "Ollama Local (no API key required)"), React.createElement(Text, null, React.createElement(Text, { color: "green" }, " 2. "), "Ollama Cloud (API key required)"), React.createElement(Text, { color: "gray" }, "\nPress 1 or 2 to select."), error ? React.createElement(Text, { color: "red" }, `Error: ${error}`) : null);
|
|
51
|
+
}
|
|
52
|
+
if (step === "api-key") {
|
|
53
|
+
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { bold: true }, "Enter your Ollama Cloud API key:"), React.createElement(Text, { color: "gray" }, "Get your API key from https://ollama.com"), React.createElement(TextInput, {
|
|
54
|
+
value: apiKey,
|
|
55
|
+
onChange: setApiKey,
|
|
56
|
+
onSubmit: () => {
|
|
57
|
+
if (!apiKey.trim()) {
|
|
58
|
+
setError("API key is required for cloud mode.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setError(null);
|
|
62
|
+
setStep("model");
|
|
63
|
+
},
|
|
64
|
+
}), error ? React.createElement(Text, { color: "red" }, `Error: ${error}`) : null);
|
|
65
|
+
}
|
|
66
|
+
// model step
|
|
67
|
+
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { bold: true }, "Enter default model ID:"), React.createElement(Text, { color: "gray" }, "Press Enter to use the default (kimi-k2.7-code)"), React.createElement(TextInput, {
|
|
68
|
+
value: model,
|
|
69
|
+
onChange: setModel,
|
|
70
|
+
onSubmit: () => {
|
|
71
|
+
void handleSave();
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type ResolvedConfig } from "../config.js";
|
|
3
|
+
import type { WikiCommand } from "../prompt.js";
|
|
4
|
+
interface RunViewProps {
|
|
5
|
+
command: WikiCommand;
|
|
6
|
+
cwd: string;
|
|
7
|
+
config: ResolvedConfig;
|
|
8
|
+
verbose: boolean;
|
|
9
|
+
onExit: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function RunView({ command, cwd, config, verbose, onExit, }: RunViewProps): React.ReactElement;
|
|
12
|
+
export {};
|