nova-terminal-assistant 0.1.0
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.
Potentially problematic release.
This version of nova-terminal-assistant might be problematic. Click here for more details.
- package/README.md +358 -0
- package/bin/nova +38 -0
- package/bin/nova.js +12 -0
- package/package.json +67 -0
- package/src/cli/commands/SmartCompletion.ts +458 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/startup/IFlowRepl.ts +212 -0
- package/src/cli/startup/InkBasedRepl.ts +1056 -0
- package/src/cli/startup/InteractiveRepl.ts +2833 -0
- package/src/cli/startup/NovaApp.ts +1861 -0
- package/src/cli/startup/index.ts +4 -0
- package/src/cli/startup/parseArgs.ts +293 -0
- package/src/cli/test-modules.ts +27 -0
- package/src/cli/ui/IFlowDropdown.ts +425 -0
- package/src/cli/ui/ModernReplUI.ts +276 -0
- package/src/cli/ui/SimpleSelector2.ts +215 -0
- package/src/cli/ui/components/ConfirmDialog.ts +176 -0
- package/src/cli/ui/components/ErrorPanel.ts +364 -0
- package/src/cli/ui/components/InkAppRunner.tsx +67 -0
- package/src/cli/ui/components/InkComponents.tsx +613 -0
- package/src/cli/ui/components/NovaInkApp.tsx +312 -0
- package/src/cli/ui/components/ProgressBar.ts +177 -0
- package/src/cli/ui/components/ProgressIndicator.ts +298 -0
- package/src/cli/ui/components/QuickActions.ts +396 -0
- package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
- package/src/cli/ui/components/StatusBar.ts +194 -0
- package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
- package/src/cli/ui/components/index.ts +27 -0
- package/src/cli/ui/ink-prototype.tsx +347 -0
- package/src/cli/utils/CliUI.ts +336 -0
- package/src/cli/utils/CompletionHelper.ts +388 -0
- package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
- package/src/cli/utils/EnhancedCompleter.ts +513 -0
- package/src/cli/utils/ErrorEnhancer.ts +429 -0
- package/src/cli/utils/OutputFormatter.ts +193 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/core/agents/AgentOrchestrator.ts +515 -0
- package/src/core/agents/index.ts +17 -0
- package/src/core/audit/AuditLogger.ts +509 -0
- package/src/core/audit/index.ts +11 -0
- package/src/core/auth/AuthManager.d.ts.map +1 -0
- package/src/core/auth/AuthManager.ts +138 -0
- package/src/core/auth/index.d.ts.map +1 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/config/ConfigManager.d.ts.map +1 -0
- package/src/core/config/ConfigManager.test.ts +183 -0
- package/src/core/config/ConfigManager.ts +1219 -0
- package/src/core/config/index.d.ts.map +1 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/context/ContextBuilder.d.ts.map +1 -0
- package/src/core/context/ContextBuilder.ts +171 -0
- package/src/core/context/ContextCompressor.d.ts.map +1 -0
- package/src/core/context/ContextCompressor.ts +642 -0
- package/src/core/context/LayeredMemoryManager.ts +657 -0
- package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
- package/src/core/context/MemoryDiscovery.ts +175 -0
- package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
- package/src/core/context/defaultSystemPrompt.ts +35 -0
- package/src/core/context/index.d.ts.map +1 -0
- package/src/core/context/index.ts +22 -0
- package/src/core/extensions/SkillGenerator.ts +421 -0
- package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
- package/src/core/extensions/SkillInstaller.ts +257 -0
- package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
- package/src/core/extensions/SkillRegistry.ts +361 -0
- package/src/core/extensions/SkillValidator.ts +525 -0
- package/src/core/extensions/index.ts +15 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.ts +42 -0
- package/src/core/mcp/McpManager.d.ts.map +1 -0
- package/src/core/mcp/McpManager.ts +632 -0
- package/src/core/mcp/index.d.ts.map +1 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/model/ModelClient.d.ts.map +1 -0
- package/src/core/model/ModelClient.ts +217 -0
- package/src/core/model/ModelConnectionTester.ts +363 -0
- package/src/core/model/ModelValidator.ts +348 -0
- package/src/core/model/index.d.ts.map +1 -0
- package/src/core/model/index.ts +6 -0
- package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/src/core/model/providers/AnthropicProvider.ts +279 -0
- package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/src/core/model/providers/CodingPlanProvider.ts +210 -0
- package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
- package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
- package/src/core/model/providers/OllamaManager.ts +201 -0
- package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaProvider.ts +73 -0
- package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
- package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAIProvider.ts +29 -0
- package/src/core/model/providers/index.d.ts.map +1 -0
- package/src/core/model/providers/index.ts +12 -0
- package/src/core/model/types.d.ts.map +1 -0
- package/src/core/model/types.ts +77 -0
- package/src/core/security/ApprovalManager.d.ts.map +1 -0
- package/src/core/security/ApprovalManager.ts +174 -0
- package/src/core/security/FileFilter.d.ts.map +1 -0
- package/src/core/security/FileFilter.ts +141 -0
- package/src/core/security/HookExecutor.d.ts.map +1 -0
- package/src/core/security/HookExecutor.ts +178 -0
- package/src/core/security/SandboxExecutor.ts +447 -0
- package/src/core/security/index.d.ts.map +1 -0
- package/src/core/security/index.ts +8 -0
- package/src/core/session/AgentLoop.d.ts.map +1 -0
- package/src/core/session/AgentLoop.ts +501 -0
- package/src/core/session/SessionManager.d.ts.map +1 -0
- package/src/core/session/SessionManager.test.ts +183 -0
- package/src/core/session/SessionManager.ts +460 -0
- package/src/core/session/index.d.ts.map +1 -0
- package/src/core/session/index.ts +3 -0
- package/src/core/telemetry/Telemetry.d.ts.map +1 -0
- package/src/core/telemetry/Telemetry.ts +90 -0
- package/src/core/telemetry/TelemetryService.ts +531 -0
- package/src/core/telemetry/index.d.ts.map +1 -0
- package/src/core/telemetry/index.ts +12 -0
- package/src/core/testing/AutoFixer.ts +385 -0
- package/src/core/testing/ErrorAnalyzer.ts +499 -0
- package/src/core/testing/TestRunner.ts +265 -0
- package/src/core/testing/agent-cli-tests.ts +538 -0
- package/src/core/testing/index.ts +11 -0
- package/src/core/tools/ToolRegistry.d.ts.map +1 -0
- package/src/core/tools/ToolRegistry.test.ts +206 -0
- package/src/core/tools/ToolRegistry.ts +260 -0
- package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/EditFileTool.ts +97 -0
- package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
- package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/MemoryTool.ts +102 -0
- package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/ReadFileTool.ts +58 -0
- package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchContentTool.ts +94 -0
- package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchFileTool.ts +61 -0
- package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
- package/src/core/tools/impl/ShellTool.ts +118 -0
- package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
- package/src/core/tools/impl/TaskTool.ts +207 -0
- package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
- package/src/core/tools/impl/TodoTool.ts +122 -0
- package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebFetchTool.ts +103 -0
- package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebSearchTool.ts +89 -0
- package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/WriteFileTool.ts +49 -0
- package/src/core/tools/impl/index.d.ts.map +1 -0
- package/src/core/tools/impl/index.ts +16 -0
- package/src/core/tools/index.d.ts.map +1 -0
- package/src/core/tools/index.ts +7 -0
- package/src/core/tools/schemas/execution.d.ts.map +1 -0
- package/src/core/tools/schemas/execution.ts +42 -0
- package/src/core/tools/schemas/file.d.ts.map +1 -0
- package/src/core/tools/schemas/file.ts +119 -0
- package/src/core/tools/schemas/index.d.ts.map +1 -0
- package/src/core/tools/schemas/index.ts +11 -0
- package/src/core/tools/schemas/memory.d.ts.map +1 -0
- package/src/core/tools/schemas/memory.ts +52 -0
- package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
- package/src/core/tools/schemas/orchestration.ts +44 -0
- package/src/core/tools/schemas/search.d.ts.map +1 -0
- package/src/core/tools/schemas/search.ts +112 -0
- package/src/core/tools/schemas/todo.d.ts.map +1 -0
- package/src/core/tools/schemas/todo.ts +32 -0
- package/src/core/tools/schemas/web.d.ts.map +1 -0
- package/src/core/tools/schemas/web.ts +86 -0
- package/src/core/types/config.d.ts.map +1 -0
- package/src/core/types/config.ts +200 -0
- package/src/core/types/errors.d.ts.map +1 -0
- package/src/core/types/errors.ts +204 -0
- package/src/core/types/index.d.ts.map +1 -0
- package/src/core/types/index.ts +8 -0
- package/src/core/types/session.d.ts.map +1 -0
- package/src/core/types/session.ts +216 -0
- package/src/core/types/tools.d.ts.map +1 -0
- package/src/core/types/tools.ts +157 -0
- package/src/core/utils/CheckpointManager.d.ts.map +1 -0
- package/src/core/utils/CheckpointManager.ts +327 -0
- package/src/core/utils/Logger.d.ts.map +1 -0
- package/src/core/utils/Logger.ts +98 -0
- package/src/core/utils/RetryManager.ts +471 -0
- package/src/core/utils/TokenCounter.d.ts.map +1 -0
- package/src/core/utils/TokenCounter.ts +414 -0
- package/src/core/utils/VectorMemoryStore.ts +440 -0
- package/src/core/utils/helpers.d.ts.map +1 -0
- package/src/core/utils/helpers.ts +89 -0
- package/src/core/utils/index.d.ts.map +1 -0
- package/src/core/utils/index.ts +19 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
|
|
4
|
+
import { ToolError } from '../../types/errors.js';
|
|
5
|
+
|
|
6
|
+
/** Maximum entries to return before truncating with guidance */
|
|
7
|
+
const MAX_ENTRIES = 500;
|
|
8
|
+
|
|
9
|
+
export const listDirectoryHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
|
|
10
|
+
const { dirPath, recursive = false, includeHidden = false, pattern, depth, limit } = input.params as {
|
|
11
|
+
dirPath: string;
|
|
12
|
+
recursive?: boolean;
|
|
13
|
+
includeHidden?: boolean;
|
|
14
|
+
pattern?: string;
|
|
15
|
+
depth?: number;
|
|
16
|
+
limit?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const resolvedPath = path.resolve(dirPath);
|
|
20
|
+
const maxEntries = limit || MAX_ENTRIES;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const stat = await fs.stat(resolvedPath);
|
|
24
|
+
if (!stat.isDirectory()) {
|
|
25
|
+
throw new ToolError(`Path is not a directory: ${dirPath}`, 'list_directory', undefined, {
|
|
26
|
+
code: 'NOT_DIRECTORY',
|
|
27
|
+
suggestion: `Use read_file to read a file, or specify a directory path.`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const entries: Array<{ name: string; path: string; isDirectory: boolean; size: number }> = [];
|
|
32
|
+
let totalFound = 0;
|
|
33
|
+
let truncated = false;
|
|
34
|
+
|
|
35
|
+
async function walk(currentDir: string, currentDepth: number): Promise<void> {
|
|
36
|
+
if (depth !== undefined && currentDepth > depth) return;
|
|
37
|
+
|
|
38
|
+
const items = await fs.readdir(currentDir, { withFileTypes: true });
|
|
39
|
+
|
|
40
|
+
for (const item of items) {
|
|
41
|
+
if (!includeHidden && item.name.startsWith('.')) continue;
|
|
42
|
+
|
|
43
|
+
if (pattern && !matchGlob(item.name, pattern)) continue;
|
|
44
|
+
|
|
45
|
+
totalFound++;
|
|
46
|
+
|
|
47
|
+
if (entries.length >= maxEntries) {
|
|
48
|
+
truncated = true;
|
|
49
|
+
continue; // Keep counting but don't add more entries
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const fullPath = path.join(currentDir, item.name);
|
|
53
|
+
let size = 0;
|
|
54
|
+
|
|
55
|
+
if (item.isFile()) {
|
|
56
|
+
try {
|
|
57
|
+
const itemStat = await fs.stat(fullPath);
|
|
58
|
+
size = itemStat.size;
|
|
59
|
+
} catch {
|
|
60
|
+
// Skip files we can't stat
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
entries.push({
|
|
65
|
+
name: item.name,
|
|
66
|
+
path: fullPath,
|
|
67
|
+
isDirectory: item.isDirectory(),
|
|
68
|
+
size,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (recursive && item.isDirectory()) {
|
|
72
|
+
await walk(fullPath, currentDepth + 1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await walk(resolvedPath, 0);
|
|
78
|
+
|
|
79
|
+
const output = entries
|
|
80
|
+
.map((entry) => {
|
|
81
|
+
const type = entry.isDirectory ? 'DIR ' : 'FILE';
|
|
82
|
+
const sizeStr = entry.isDirectory ? ' ' : String(entry.size).padStart(8);
|
|
83
|
+
return `${type} ${sizeStr} ${entry.path}`;
|
|
84
|
+
})
|
|
85
|
+
.join('\n');
|
|
86
|
+
|
|
87
|
+
const dirCount = entries.filter((e) => e.isDirectory).length;
|
|
88
|
+
const fileCount = entries.length - dirCount;
|
|
89
|
+
|
|
90
|
+
// Build content with truncation guidance
|
|
91
|
+
let content = output;
|
|
92
|
+
|
|
93
|
+
if (truncated) {
|
|
94
|
+
content += `\n\n[TRUNCATED: Showing ${entries.length} of ${totalFound} items.]`;
|
|
95
|
+
content += `\n[To see more, use one of these approaches:]`;
|
|
96
|
+
content += `\n • Use pattern filter: list_directory with pattern="*.ts"`;
|
|
97
|
+
content += `\n • Increase limit: list_directory with limit=1000`;
|
|
98
|
+
content += `\n • Use non-recursive: list_directory with recursive=false`;
|
|
99
|
+
content += `\n • Search specific files: search_file with pattern="**/*.ts"`;
|
|
100
|
+
} else {
|
|
101
|
+
content += `\n\n${entries.length} item(s): ${fileCount} file(s), ${dirCount} director${dirCount === 1 ? 'y' : 'ies'}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
content,
|
|
106
|
+
metadata: {
|
|
107
|
+
path: resolvedPath,
|
|
108
|
+
totalEntries: entries.length,
|
|
109
|
+
totalFound: totalFound,
|
|
110
|
+
truncated,
|
|
111
|
+
files: fileCount,
|
|
112
|
+
directories: dirCount,
|
|
113
|
+
suggestion: truncated ? 'Use pattern, limit, or non-recursive mode to narrow results' : undefined,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err instanceof ToolError) throw err;
|
|
118
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
119
|
+
if (code === 'ENOENT') {
|
|
120
|
+
throw new ToolError(`Directory not found: ${dirPath}`, 'list_directory', undefined, {
|
|
121
|
+
code,
|
|
122
|
+
suggestion: `Check the path exists. Use list_directory "." to see current directory.`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (code === 'EACCES') {
|
|
126
|
+
throw new ToolError(`Permission denied: ${dirPath}`, 'list_directory', undefined, {
|
|
127
|
+
code,
|
|
128
|
+
suggestion: `Check directory permissions or try a different path.`
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
throw new ToolError(`Failed to list directory: ${(err as Error).message}`, 'list_directory');
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function matchGlob(name: string, pattern: string): boolean {
|
|
136
|
+
// Simple glob matching: * matches any chars, ? matches single char
|
|
137
|
+
const regexStr = pattern
|
|
138
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
139
|
+
.replace(/\*/g, '.*')
|
|
140
|
+
.replace(/\?/g, '.');
|
|
141
|
+
return new RegExp(`^${regexStr}$`, 'i').test(name);
|
|
142
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MemoryTool.d.ts","sourceRoot":"","sources":["MemoryTool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAmC7F,eAAO,MAAM,iBAAiB,EAAE,WAkC/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,WA2BhC,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
|
|
5
|
+
|
|
6
|
+
interface MemoryStore {
|
|
7
|
+
_data: Record<string, { value: string; expires?: number; tags?: string[]; createdAt: number }>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getMemoryPath(scope: string): string {
|
|
11
|
+
const homeDir = os.homedir();
|
|
12
|
+
switch (scope) {
|
|
13
|
+
case 'global':
|
|
14
|
+
return path.join(homeDir, '.nova', 'memory.json');
|
|
15
|
+
case 'project':
|
|
16
|
+
return path.join(process.cwd(), '.nova', 'memory.json');
|
|
17
|
+
case 'session':
|
|
18
|
+
default:
|
|
19
|
+
return path.join(os.tmpdir(), 'nova-memory', `session-${process.pid}.json`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function loadStore(scope: string): Promise<MemoryStore> {
|
|
24
|
+
const filePath = getMemoryPath(scope);
|
|
25
|
+
try {
|
|
26
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
27
|
+
return JSON.parse(data) as MemoryStore;
|
|
28
|
+
} catch {
|
|
29
|
+
return { _data: {} };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function saveStore(scope: string, store: MemoryStore): Promise<void> {
|
|
34
|
+
const filePath = getMemoryPath(scope);
|
|
35
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
36
|
+
await fs.writeFile(filePath, JSON.stringify(store, null, 2), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const memoryReadHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
|
|
40
|
+
const { key, scope = 'session' } = input.params as { key: string; scope?: string };
|
|
41
|
+
|
|
42
|
+
const store = await loadStore(scope);
|
|
43
|
+
const entry = store._data[key];
|
|
44
|
+
|
|
45
|
+
if (!entry) {
|
|
46
|
+
return {
|
|
47
|
+
content: `No memory entry found for key: "${key}" (scope: ${scope})`,
|
|
48
|
+
metadata: { key, scope, found: false },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check TTL
|
|
53
|
+
if (entry.expires && Date.now() > entry.expires) {
|
|
54
|
+
delete store._data[key];
|
|
55
|
+
await saveStore(scope, store);
|
|
56
|
+
return {
|
|
57
|
+
content: `Memory entry "${key}" has expired`,
|
|
58
|
+
metadata: { key, scope, found: false, expired: true },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
content: entry.value,
|
|
64
|
+
metadata: {
|
|
65
|
+
key,
|
|
66
|
+
scope,
|
|
67
|
+
found: true,
|
|
68
|
+
tags: entry.tags,
|
|
69
|
+
createdAt: entry.createdAt,
|
|
70
|
+
expires: entry.expires,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const memoryWriteHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
|
|
76
|
+
const { key, value, scope = 'session', ttl, tags } = input.params as {
|
|
77
|
+
key: string;
|
|
78
|
+
value: string;
|
|
79
|
+
scope?: string;
|
|
80
|
+
ttl?: number;
|
|
81
|
+
tags?: string[];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const store = await loadStore(scope);
|
|
85
|
+
const exists = !!store._data[key];
|
|
86
|
+
|
|
87
|
+
store._data[key] = {
|
|
88
|
+
value,
|
|
89
|
+
expires: ttl ? Date.now() + ttl : undefined,
|
|
90
|
+
tags,
|
|
91
|
+
createdAt: exists ? (store._data[key]?.createdAt ?? Date.now()) : Date.now(),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
await saveStore(scope, store);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
content: exists
|
|
98
|
+
? `Updated memory entry "${key}" (scope: ${scope})`
|
|
99
|
+
: `Created memory entry "${key}" (scope: ${scope})`,
|
|
100
|
+
metadata: { key, scope, updated: exists, ttl, tags },
|
|
101
|
+
};
|
|
102
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReadFileTool.d.ts","sourceRoot":"","sources":["ReadFileTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAG7F,eAAO,MAAM,eAAe,EAAE,WAoD7B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
|
|
4
|
+
import { ToolError } from '../../types/errors.js';
|
|
5
|
+
|
|
6
|
+
export const readFileHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
|
|
7
|
+
const { filePath, offset, limit, encoding = 'utf-8' } = input.params as {
|
|
8
|
+
filePath: string;
|
|
9
|
+
offset?: number;
|
|
10
|
+
limit?: number;
|
|
11
|
+
encoding?: BufferEncoding;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const resolvedPath = path.resolve(filePath);
|
|
16
|
+
const content = await fs.readFile(resolvedPath, { encoding });
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
|
|
19
|
+
let selectedLines = lines;
|
|
20
|
+
let lineInfo = '';
|
|
21
|
+
|
|
22
|
+
if (offset !== undefined || limit !== undefined) {
|
|
23
|
+
const start = offset !== undefined ? Math.max(0, offset - 1) : 0; // 1-based to 0-based
|
|
24
|
+
const end = limit !== undefined ? start + limit : lines.length;
|
|
25
|
+
selectedLines = lines.slice(start, end);
|
|
26
|
+
lineInfo = ` (lines ${start + 1}-${Math.min(end, lines.length)} of ${lines.length})`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const output = selectedLines.map((line, i) => {
|
|
30
|
+
const lineNum = offset !== undefined ? offset + i : i + 1;
|
|
31
|
+
const numStr = String(lineNum).padStart(6, ' ');
|
|
32
|
+
return `${numStr}: ${line}`;
|
|
33
|
+
}).join('\n');
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
content: output,
|
|
37
|
+
metadata: {
|
|
38
|
+
path: resolvedPath,
|
|
39
|
+
totalLines: lines.length,
|
|
40
|
+
encoding,
|
|
41
|
+
size: Buffer.byteLength(content, encoding),
|
|
42
|
+
lineRange: lineInfo || 'full file',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
47
|
+
if (code === 'ENOENT') {
|
|
48
|
+
throw new ToolError(`File not found: ${filePath}`, 'read_file', undefined, { code });
|
|
49
|
+
}
|
|
50
|
+
if (code === 'EACCES') {
|
|
51
|
+
throw new ToolError(`Permission denied: ${filePath}`, 'read_file', undefined, { code });
|
|
52
|
+
}
|
|
53
|
+
if (code === 'EISDIR') {
|
|
54
|
+
throw new ToolError(`Path is a directory, not a file: ${filePath}`, 'read_file', undefined, { code });
|
|
55
|
+
}
|
|
56
|
+
throw new ToolError(`Failed to read file: ${(err as Error).message}`, 'read_file');
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchContentTool.d.ts","sourceRoot":"","sources":["SearchContentTool.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAK7F,eAAO,MAAM,oBAAoB,EAAE,WAqFlC,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
|
|
5
|
+
import { ToolError } from '../../types/errors.js';
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
export const searchContentHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
|
|
10
|
+
const {
|
|
11
|
+
pattern,
|
|
12
|
+
directory,
|
|
13
|
+
filePattern,
|
|
14
|
+
caseSensitive = false,
|
|
15
|
+
includeHidden = false,
|
|
16
|
+
maxResults = 50,
|
|
17
|
+
contextBefore = 0,
|
|
18
|
+
contextAfter = 0,
|
|
19
|
+
multiline = false,
|
|
20
|
+
} = input.params as {
|
|
21
|
+
pattern: string;
|
|
22
|
+
directory: string;
|
|
23
|
+
filePattern?: string;
|
|
24
|
+
caseSensitive?: boolean;
|
|
25
|
+
includeHidden?: boolean;
|
|
26
|
+
maxResults?: number;
|
|
27
|
+
contextBefore?: number;
|
|
28
|
+
contextAfter?: number;
|
|
29
|
+
multiline?: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const resolvedDir = path.resolve(directory);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Validate regex
|
|
36
|
+
new RegExp(pattern);
|
|
37
|
+
|
|
38
|
+
const args: string[] = [
|
|
39
|
+
'--no-heading',
|
|
40
|
+
'--color', 'never',
|
|
41
|
+
'--max-count', String(maxResults),
|
|
42
|
+
'-C', String(Math.max(contextBefore, contextAfter)),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
if (!caseSensitive) args.push('-i');
|
|
46
|
+
if (includeHidden) args.push('--hidden');
|
|
47
|
+
if (multiline) args.push('-U', '--multiline-dotall');
|
|
48
|
+
if (filePattern) args.push('--glob', filePattern);
|
|
49
|
+
|
|
50
|
+
// Add common ignores
|
|
51
|
+
const ignoreArgs = ['--glob', '!node_modules/**', '--glob', '!.git/**', '--glob', '!dist/**', '--glob', '!build/**'];
|
|
52
|
+
args.push(...ignoreArgs);
|
|
53
|
+
|
|
54
|
+
args.push('--', pattern, resolvedDir);
|
|
55
|
+
|
|
56
|
+
const { stdout, stderr } = await execFileAsync('rg', args, {
|
|
57
|
+
timeout: 15000,
|
|
58
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!stdout.trim()) {
|
|
62
|
+
return {
|
|
63
|
+
content: `No matches found for pattern "${pattern}" in ${resolvedDir}`,
|
|
64
|
+
metadata: { pattern, directory: resolvedDir, count: 0 },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: stdout.trim(),
|
|
70
|
+
metadata: {
|
|
71
|
+
pattern,
|
|
72
|
+
directory: resolvedDir,
|
|
73
|
+
tool: 'ripgrep',
|
|
74
|
+
options: { caseSensitive, includeHidden, maxResults, contextBefore, contextAfter, multiline },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
} catch (err) {
|
|
78
|
+
const error = err as { code?: string; message: string };
|
|
79
|
+
if (error.code === 'ENOENT') {
|
|
80
|
+
throw new ToolError(
|
|
81
|
+
'ripgrep (rg) not found. Please install it: https://github.com/BurntSushi/ripgrep',
|
|
82
|
+
'search_content'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (error.code && (error.code === '1' || error.code === '2' || Number(error.code) === 1)) {
|
|
86
|
+
// ripgrep returns 1 when no matches found, 2 for errors (not an error for our purposes)
|
|
87
|
+
return {
|
|
88
|
+
content: `No matches found for pattern "${pattern}" in ${resolvedDir}`,
|
|
89
|
+
metadata: { pattern, directory: resolvedDir, count: 0 },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
throw new ToolError(`Failed to search content: ${error.message}`, 'search_content');
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchFileTool.d.ts","sourceRoot":"","sources":["SearchFileTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAG7F,eAAO,MAAM,iBAAiB,EAAE,WAuD/B,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
|
|
4
|
+
import { ToolError } from '../../types/errors.js';
|
|
5
|
+
|
|
6
|
+
export const searchFileHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
|
|
7
|
+
const { pattern, directory, recursive = true, caseSensitive = false, excludePatterns } = input.params as {
|
|
8
|
+
pattern: string;
|
|
9
|
+
directory: string;
|
|
10
|
+
recursive?: boolean;
|
|
11
|
+
caseSensitive?: boolean;
|
|
12
|
+
excludePatterns?: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const resolvedDir = path.resolve(directory);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const globPattern = recursive ? `**/${pattern}` : pattern;
|
|
19
|
+
|
|
20
|
+
const ignore = excludePatterns || [
|
|
21
|
+
'node_modules/**',
|
|
22
|
+
'.git/**',
|
|
23
|
+
'dist/**',
|
|
24
|
+
'build/**',
|
|
25
|
+
'*.log',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const files = await glob(globPattern, {
|
|
29
|
+
cwd: resolvedDir,
|
|
30
|
+
ignore,
|
|
31
|
+
absolute: true,
|
|
32
|
+
nocase: !caseSensitive,
|
|
33
|
+
nodir: true,
|
|
34
|
+
windowsPathsNoEscape: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (files.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
content: `No files matching "${pattern}" found in ${resolvedDir}`,
|
|
40
|
+
metadata: { pattern, directory: resolvedDir, count: 0 },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const output = files
|
|
45
|
+
.sort()
|
|
46
|
+
.map((f) => path.relative(resolvedDir, f))
|
|
47
|
+
.join('\n');
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
content: output,
|
|
51
|
+
metadata: {
|
|
52
|
+
pattern,
|
|
53
|
+
directory: resolvedDir,
|
|
54
|
+
count: files.length,
|
|
55
|
+
matches: files,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new ToolError(`Failed to search files: ${(err as Error).message}`, 'search_file');
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShellTool.d.ts","sourceRoot":"","sources":["ShellTool.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAG7F,eAAO,MAAM,YAAY,EAAE,WA8G1B,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
|
|
6
|
+
import { ToolError, TimeoutError } from '../../types/errors.js';
|
|
7
|
+
|
|
8
|
+
export const shellHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
|
|
9
|
+
const {
|
|
10
|
+
command,
|
|
11
|
+
workingDirectory,
|
|
12
|
+
env: extraEnv,
|
|
13
|
+
timeout = 30000,
|
|
14
|
+
shell: shellOverride,
|
|
15
|
+
input: stdinInput,
|
|
16
|
+
} = input.params as {
|
|
17
|
+
command: string;
|
|
18
|
+
workingDirectory?: string;
|
|
19
|
+
env?: Record<string, string>;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
shell?: string;
|
|
22
|
+
input?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const cwd = workingDirectory
|
|
26
|
+
? isAbsolute(workingDirectory) ? workingDirectory : resolve(workingDirectory)
|
|
27
|
+
: input.context.workingDirectory;
|
|
28
|
+
|
|
29
|
+
// Determine shell
|
|
30
|
+
const isWindows = os.platform() === 'win32';
|
|
31
|
+
const shellCmd = shellOverride || (isWindows ? 'powershell.exe' : '/bin/bash');
|
|
32
|
+
const shellArgs = isWindows && !shellOverride ? ['-NoProfile', '-Command', command] : ['-c', command];
|
|
33
|
+
|
|
34
|
+
return new Promise<ToolHandlerOutput>((resolveResult, reject) => {
|
|
35
|
+
const proc = spawn(shellCmd, shellArgs, {
|
|
36
|
+
cwd,
|
|
37
|
+
env: { ...process.env, ...input.context.environment, ...extraEnv },
|
|
38
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let stdout = '';
|
|
43
|
+
let stderr = '';
|
|
44
|
+
|
|
45
|
+
proc.stdout.on('data', (data: Buffer) => {
|
|
46
|
+
stdout += data.toString();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
proc.stderr.on('data', (data: Buffer) => {
|
|
50
|
+
stderr += data.toString();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (stdinInput) {
|
|
54
|
+
proc.stdin.write(stdinInput);
|
|
55
|
+
proc.stdin.end();
|
|
56
|
+
} else {
|
|
57
|
+
proc.stdin.end();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
proc.kill('SIGKILL');
|
|
62
|
+
reject(new TimeoutError(`Command timed out after ${timeout}ms`, timeout));
|
|
63
|
+
}, timeout);
|
|
64
|
+
|
|
65
|
+
proc.on('close', (code) => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
const exitCode = code ?? 1;
|
|
68
|
+
|
|
69
|
+
// Enhanced truncation with actionable guidance for Agent
|
|
70
|
+
const MAX = 10_000;
|
|
71
|
+
const truncated = (s: string, label: string = 'output') => {
|
|
72
|
+
if (s.length <= MAX) return s;
|
|
73
|
+
const truncatedLen = s.length - MAX;
|
|
74
|
+
return s.slice(0, MAX) +
|
|
75
|
+
`\n\n[TRUNCATED: Showing ${MAX.toLocaleString()} of ${s.length.toLocaleString()} chars. ` +
|
|
76
|
+
`To see more, redirect to a file: ${command} > output.txt, then read_file output.txt]`;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (exitCode === 0) {
|
|
80
|
+
resolveResult({
|
|
81
|
+
content: truncated(stdout) || (stderr ? `stderr: ${truncated(stderr, 'stderr')}` : 'Command completed successfully (no output)'),
|
|
82
|
+
metadata: {
|
|
83
|
+
exitCode: 0,
|
|
84
|
+
duration: timeout,
|
|
85
|
+
cwd,
|
|
86
|
+
shell: shellCmd,
|
|
87
|
+
outputLength: stdout.length,
|
|
88
|
+
truncated: stdout.length > MAX,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
resolveResult({
|
|
93
|
+
content: truncated(stderr, 'stderr') || truncated(stdout, 'stdout') || `Command exited with code ${exitCode}`,
|
|
94
|
+
isError: true,
|
|
95
|
+
metadata: {
|
|
96
|
+
exitCode,
|
|
97
|
+
duration: timeout,
|
|
98
|
+
cwd,
|
|
99
|
+
shell: shellCmd,
|
|
100
|
+
outputLength: stderr.length || stdout.length,
|
|
101
|
+
truncated: stderr.length > MAX || stdout.length > MAX,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
proc.on('error', (err) => {
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
reject(new ToolError(`Failed to execute command: ${err.message}`, 'execute_command'));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Handle abort signal
|
|
113
|
+
input.abortSignal?.addEventListener('abort', () => {
|
|
114
|
+
proc.kill('SIGKILL');
|
|
115
|
+
reject(new ToolError('Command cancelled', 'execute_command'));
|
|
116
|
+
}, { once: true });
|
|
117
|
+
});
|
|
118
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskTool.d.ts","sourceRoot":"","sources":["TaskTool.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAmB7F;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,WAsIzB,CAAC;AAEF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCtB,CAAC"}
|