neuro-cli 4.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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ToolExecutor } from '../tools/registry.js';
|
|
2
|
+
export declare const todoTool: ToolExecutor;
|
|
3
|
+
export declare const askUserTool: ToolExecutor;
|
|
4
|
+
export declare const monitorTool: ToolExecutor;
|
|
5
|
+
export declare const extendedTools: ToolExecutor[];
|
|
6
|
+
//# sourceMappingURL=extended.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Extended Tools: Todo, AskUser, Monitor, Sandbox
|
|
3
|
+
// ============================================================
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
// ---- Todo List Tool ----
|
|
9
|
+
const todoDef = {
|
|
10
|
+
name: 'todowrite',
|
|
11
|
+
description: 'Create and manage a todo list for the current session. Track tasks, priorities, and completion status.',
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
action: { type: 'string', description: 'Action: "create", "update", "list", "delete"', enum: ['create', 'update', 'list', 'delete'] },
|
|
16
|
+
id: { type: 'string', description: 'Todo item ID (for update/delete)' },
|
|
17
|
+
content: { type: 'string', description: 'Todo item content/description' },
|
|
18
|
+
status: { type: 'string', description: 'Status: "pending", "in_progress", "completed"', enum: ['pending', 'in_progress', 'completed'] },
|
|
19
|
+
priority: { type: 'string', description: 'Priority: "high", "medium", "low"', enum: ['high', 'medium', 'low'] },
|
|
20
|
+
},
|
|
21
|
+
required: ['action'],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const TODO_FILE = join(homedir(), '.neuro', 'todos.json');
|
|
25
|
+
function loadTodos() {
|
|
26
|
+
if (!existsSync(TODO_FILE))
|
|
27
|
+
return [];
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(readFileSync(TODO_FILE, 'utf-8'));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function saveTodos(todos) {
|
|
36
|
+
mkdirSync(join(homedir(), '.neuro'), { recursive: true });
|
|
37
|
+
writeFileSync(TODO_FILE, JSON.stringify(todos, null, 2), 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
export const todoTool = {
|
|
40
|
+
name: 'todowrite',
|
|
41
|
+
definition: todoDef,
|
|
42
|
+
risk: 'low',
|
|
43
|
+
async execute(args) {
|
|
44
|
+
const todos = loadTodos();
|
|
45
|
+
const action = args.action;
|
|
46
|
+
switch (action) {
|
|
47
|
+
case 'create': {
|
|
48
|
+
const item = {
|
|
49
|
+
id: `todo_${Date.now()}`,
|
|
50
|
+
content: args.content || 'Untitled task',
|
|
51
|
+
status: 'pending',
|
|
52
|
+
priority: args.priority || 'medium',
|
|
53
|
+
createdAt: Date.now(),
|
|
54
|
+
};
|
|
55
|
+
todos.push(item);
|
|
56
|
+
saveTodos(todos);
|
|
57
|
+
return `Created todo: [${item.priority}] ${item.content} (${item.id})`;
|
|
58
|
+
}
|
|
59
|
+
case 'update': {
|
|
60
|
+
const id = args.id;
|
|
61
|
+
const item = todos.find(t => t.id === id);
|
|
62
|
+
if (!item)
|
|
63
|
+
return `Todo not found: ${id}`;
|
|
64
|
+
if (args.content)
|
|
65
|
+
item.content = args.content;
|
|
66
|
+
if (args.status)
|
|
67
|
+
item.status = args.status;
|
|
68
|
+
if (args.priority)
|
|
69
|
+
item.priority = args.priority;
|
|
70
|
+
saveTodos(todos);
|
|
71
|
+
return `Updated todo ${id}: [${item.priority}] ${item.content} - ${item.status}`;
|
|
72
|
+
}
|
|
73
|
+
case 'list': {
|
|
74
|
+
if (todos.length === 0)
|
|
75
|
+
return 'No todos. Use action="create" to add one.';
|
|
76
|
+
return todos.map(t => `${t.status === 'completed' ? '✅' : t.status === 'in_progress' ? '🔄' : '⬜'} [${t.priority}] ${t.content} (${t.id})`).join('\n');
|
|
77
|
+
}
|
|
78
|
+
case 'delete': {
|
|
79
|
+
const id = args.id;
|
|
80
|
+
const idx = todos.findIndex(t => t.id === id);
|
|
81
|
+
if (idx === -1)
|
|
82
|
+
return `Todo not found: ${id}`;
|
|
83
|
+
const removed = todos.splice(idx, 1)[0];
|
|
84
|
+
saveTodos(todos);
|
|
85
|
+
return `Deleted todo: ${removed.content}`;
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
return `Unknown action: ${action}. Use create, update, list, or delete.`;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
// ---- AskUserQuestion Tool ----
|
|
93
|
+
const askUserDef = {
|
|
94
|
+
name: 'ask_user',
|
|
95
|
+
description: 'Ask the user a question with multiple-choice options. Use when you need clarification or decisions.',
|
|
96
|
+
parameters: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
question: { type: 'string', description: 'The question to ask the user' },
|
|
100
|
+
options: { type: 'string', description: 'Comma-separated list of options' },
|
|
101
|
+
default: { type: 'string', description: 'Default option if user does not respond' },
|
|
102
|
+
},
|
|
103
|
+
required: ['question'],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
export const askUserTool = {
|
|
107
|
+
name: 'ask_user',
|
|
108
|
+
definition: askUserDef,
|
|
109
|
+
risk: 'low',
|
|
110
|
+
async execute(args) {
|
|
111
|
+
const question = args.question;
|
|
112
|
+
const options = args.options?.split(',').map(o => o.trim()) || [];
|
|
113
|
+
const defaultOption = args.default;
|
|
114
|
+
// In non-interactive mode, use default
|
|
115
|
+
if (defaultOption) {
|
|
116
|
+
return `User selected (default): ${defaultOption}`;
|
|
117
|
+
}
|
|
118
|
+
// In interactive mode, this would prompt the user
|
|
119
|
+
// For now, return the question with options
|
|
120
|
+
let prompt = `❓ ${question}`;
|
|
121
|
+
if (options.length > 0) {
|
|
122
|
+
prompt += `\nOptions: ${options.map((o, i) => `${i + 1}. ${o}`).join(', ')}`;
|
|
123
|
+
}
|
|
124
|
+
prompt += '\n\n[Waiting for user input - using auto-approve in non-interactive mode]';
|
|
125
|
+
return prompt;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
// ---- Monitor Tool ----
|
|
129
|
+
const monitorDef = {
|
|
130
|
+
name: 'monitor',
|
|
131
|
+
description: 'Run a command in the background and monitor its output. Returns each output line as it becomes available.',
|
|
132
|
+
parameters: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
command: { type: 'string', description: 'Command to run in background' },
|
|
136
|
+
duration: { type: 'number', description: 'Maximum monitoring duration in seconds (default: 60)' },
|
|
137
|
+
},
|
|
138
|
+
required: ['command'],
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const activeMonitors = new Map();
|
|
142
|
+
export const monitorTool = {
|
|
143
|
+
name: 'monitor',
|
|
144
|
+
definition: monitorDef,
|
|
145
|
+
risk: 'medium',
|
|
146
|
+
async execute(args, context) {
|
|
147
|
+
const command = args.command;
|
|
148
|
+
const duration = (args.duration || 60) * 1000;
|
|
149
|
+
try {
|
|
150
|
+
const child = spawn('sh', ['-c', command], {
|
|
151
|
+
cwd: context.workingDirectory,
|
|
152
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
153
|
+
});
|
|
154
|
+
const monitorId = `mon_${Date.now()}`;
|
|
155
|
+
activeMonitors.set(monitorId, child);
|
|
156
|
+
const outputLines = [];
|
|
157
|
+
let timedOut = false;
|
|
158
|
+
const timeout = setTimeout(() => {
|
|
159
|
+
timedOut = true;
|
|
160
|
+
child.kill();
|
|
161
|
+
}, duration);
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
child.stdout?.on('data', (data) => {
|
|
164
|
+
const lines = data.toString().split('\n').filter(l => l.trim());
|
|
165
|
+
outputLines.push(...lines.slice(0, 50)); // Limit lines
|
|
166
|
+
});
|
|
167
|
+
child.stderr?.on('data', (data) => {
|
|
168
|
+
const lines = data.toString().split('\n').filter(l => l.trim());
|
|
169
|
+
outputLines.push(...lines.slice(0, 20));
|
|
170
|
+
});
|
|
171
|
+
child.on('close', (code) => {
|
|
172
|
+
clearTimeout(timeout);
|
|
173
|
+
activeMonitors.delete(monitorId);
|
|
174
|
+
const status = timedOut ? 'TIMEOUT' : `exit code ${code}`;
|
|
175
|
+
const output = outputLines.join('\n').slice(0, 10000);
|
|
176
|
+
resolve(`Monitor [${monitorId}] ${status}\nOutput:\n${output || '(no output)'}`);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return `Monitor error: ${error}`;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
// ---- All Extended Tools ----
|
|
186
|
+
export const extendedTools = [
|
|
187
|
+
todoTool,
|
|
188
|
+
askUserTool,
|
|
189
|
+
monitorTool,
|
|
190
|
+
];
|
|
191
|
+
//# sourceMappingURL=extended.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ToolExecutor } from './registry.js';
|
|
2
|
+
export declare const readFileTool: ToolExecutor;
|
|
3
|
+
export declare const writeFileTool: ToolExecutor;
|
|
4
|
+
export declare const editFileTool: ToolExecutor;
|
|
5
|
+
export declare const deleteFileTool: ToolExecutor;
|
|
6
|
+
export declare const listDirectoryTool: ToolExecutor;
|
|
7
|
+
export declare const searchFilesTool: ToolExecutor;
|
|
8
|
+
export declare const applyDiffTool: ToolExecutor;
|
|
9
|
+
export declare const fileTools: ToolExecutor[];
|
|
10
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - File Operation Tools
|
|
3
|
+
// read, write, edit, delete, list, search
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, statSync, readdirSync } from 'fs';
|
|
6
|
+
import { join, relative, extname } from 'path';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
const MAX_FILE_SIZE = 1024 * 1024; // 1MB
|
|
9
|
+
const MAX_OUTPUT_LENGTH = 50000;
|
|
10
|
+
function truncateOutput(output, maxLength = MAX_OUTPUT_LENGTH) {
|
|
11
|
+
if (output.length <= maxLength)
|
|
12
|
+
return output;
|
|
13
|
+
const half = Math.floor(maxLength / 2);
|
|
14
|
+
return output.slice(0, half) + '\n\n... [truncated] ...\n\n' + output.slice(-half);
|
|
15
|
+
}
|
|
16
|
+
// ---- Read File ----
|
|
17
|
+
const readFileDef = {
|
|
18
|
+
name: 'read_file',
|
|
19
|
+
description: 'Read the contents of a file. Returns the file content with line numbers. Supports reading specific line ranges.',
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
path: { type: 'string', description: 'Path to the file (relative to working directory or absolute)' },
|
|
24
|
+
start_line: { type: 'number', description: 'Starting line number (1-based, optional)' },
|
|
25
|
+
end_line: { type: 'number', description: 'Ending line number (inclusive, optional)' },
|
|
26
|
+
},
|
|
27
|
+
required: ['path'],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export const readFileTool = {
|
|
31
|
+
name: 'read_file',
|
|
32
|
+
definition: readFileDef,
|
|
33
|
+
risk: 'low',
|
|
34
|
+
async execute(args, context) {
|
|
35
|
+
const filePath = join(context.workingDirectory, args.path);
|
|
36
|
+
if (!existsSync(filePath))
|
|
37
|
+
return `Error: File not found: ${args.path}`;
|
|
38
|
+
const stat = statSync(filePath);
|
|
39
|
+
if (stat.isDirectory())
|
|
40
|
+
return `Error: Path is a directory, not a file: ${args.path}`;
|
|
41
|
+
if (stat.size > MAX_FILE_SIZE)
|
|
42
|
+
return `Error: File too large (${(stat.size / 1024).toFixed(1)}KB). Use search_files for large files.`;
|
|
43
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
44
|
+
const lines = content.split('\n');
|
|
45
|
+
const start = args.start_line || 1;
|
|
46
|
+
const end = args.end_line || lines.length;
|
|
47
|
+
const selected = lines.slice(start - 1, end);
|
|
48
|
+
const numbered = selected.map((line, i) => {
|
|
49
|
+
const lineNum = (start + i).toString().padStart(5, ' ');
|
|
50
|
+
return `${lineNum}│ ${line}`;
|
|
51
|
+
}).join('\n');
|
|
52
|
+
return truncateOutput(`File: ${args.path} (${lines.length} lines)\n${numbered}`);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
// ---- Write File ----
|
|
56
|
+
const writeFileDef = {
|
|
57
|
+
name: 'write_file',
|
|
58
|
+
description: 'Create or overwrite a file with the given content. Creates parent directories if needed.',
|
|
59
|
+
parameters: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
path: { type: 'string', description: 'Path to the file (relative to working directory or absolute)' },
|
|
63
|
+
content: { type: 'string', description: 'Content to write to the file' },
|
|
64
|
+
},
|
|
65
|
+
required: ['path', 'content'],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export const writeFileTool = {
|
|
69
|
+
name: 'write_file',
|
|
70
|
+
definition: writeFileDef,
|
|
71
|
+
risk: 'high',
|
|
72
|
+
getApprovalRequest(args) {
|
|
73
|
+
return {
|
|
74
|
+
toolName: 'write_file',
|
|
75
|
+
args,
|
|
76
|
+
risk: 'high',
|
|
77
|
+
description: `Write file: ${args.path} (${(String(args.content).length / 1024).toFixed(1)}KB)`,
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
async execute(args, context) {
|
|
81
|
+
const filePath = join(context.workingDirectory, args.path);
|
|
82
|
+
const dir = join(filePath, '..');
|
|
83
|
+
if (!existsSync(dir)) {
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
writeFileSync(filePath, args.content, 'utf-8');
|
|
87
|
+
return `Successfully wrote ${String(args.content).length} bytes to ${args.path}`;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
// ---- Edit File ----
|
|
91
|
+
const editFileDef = {
|
|
92
|
+
name: 'edit_file',
|
|
93
|
+
description: 'Make targeted edits to an existing file by replacing specific text. Supports multiple edits in one call.',
|
|
94
|
+
parameters: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {
|
|
97
|
+
path: { type: 'string', description: 'Path to the file' },
|
|
98
|
+
old_text: { type: 'string', description: 'Text to find in the file' },
|
|
99
|
+
new_text: { type: 'string', description: 'Text to replace it with' },
|
|
100
|
+
},
|
|
101
|
+
required: ['path', 'old_text', 'new_text'],
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
export const editFileTool = {
|
|
105
|
+
name: 'edit_file',
|
|
106
|
+
definition: editFileDef,
|
|
107
|
+
risk: 'medium',
|
|
108
|
+
getApprovalRequest(args) {
|
|
109
|
+
return {
|
|
110
|
+
toolName: 'edit_file',
|
|
111
|
+
args,
|
|
112
|
+
risk: 'medium',
|
|
113
|
+
description: `Edit file: ${args.path}`,
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
async execute(args, context) {
|
|
117
|
+
const filePath = join(context.workingDirectory, args.path);
|
|
118
|
+
if (!existsSync(filePath))
|
|
119
|
+
return `Error: File not found: ${args.path}`;
|
|
120
|
+
let content = readFileSync(filePath, 'utf-8');
|
|
121
|
+
const old_text = args.old_text;
|
|
122
|
+
const new_text = args.new_text;
|
|
123
|
+
if (!content.includes(old_text)) {
|
|
124
|
+
return `Error: Could not find the specified text in ${args.path}. The text to replace was not found exactly.`;
|
|
125
|
+
}
|
|
126
|
+
const occurrences = content.split(old_text).length - 1;
|
|
127
|
+
if (occurrences > 1) {
|
|
128
|
+
return `Error: Found ${occurrences} occurrences of the text in ${args.path}. Please provide more context to make the match unique.`;
|
|
129
|
+
}
|
|
130
|
+
content = content.replace(old_text, new_text);
|
|
131
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
132
|
+
return `Successfully applied edit to ${args.path}`;
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
// ---- Delete File ----
|
|
136
|
+
const deleteFileDef = {
|
|
137
|
+
name: 'delete_file',
|
|
138
|
+
description: 'Delete a file. Use with extreme caution.',
|
|
139
|
+
parameters: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
path: { type: 'string', description: 'Path to the file to delete' },
|
|
143
|
+
},
|
|
144
|
+
required: ['path'],
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
export const deleteFileTool = {
|
|
148
|
+
name: 'delete_file',
|
|
149
|
+
definition: deleteFileDef,
|
|
150
|
+
risk: 'high',
|
|
151
|
+
getApprovalRequest(args) {
|
|
152
|
+
return {
|
|
153
|
+
toolName: 'delete_file',
|
|
154
|
+
args,
|
|
155
|
+
risk: 'high',
|
|
156
|
+
description: `DELETE file: ${args.path}`,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
async execute(args, context) {
|
|
160
|
+
const filePath = join(context.workingDirectory, args.path);
|
|
161
|
+
if (!existsSync(filePath))
|
|
162
|
+
return `Error: File not found: ${args.path}`;
|
|
163
|
+
unlinkSync(filePath);
|
|
164
|
+
return `Successfully deleted ${args.path}`;
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
// ---- List Directory ----
|
|
168
|
+
const listDirDef = {
|
|
169
|
+
name: 'list_directory',
|
|
170
|
+
description: 'List files and directories in a path. Shows file sizes and types.',
|
|
171
|
+
parameters: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
path: { type: 'string', description: 'Directory path (default: current working directory)' },
|
|
175
|
+
recursive: { type: 'boolean', description: 'Whether to list recursively (default: false)' },
|
|
176
|
+
max_depth: { type: 'number', description: 'Maximum depth for recursive listing (default: 3)' },
|
|
177
|
+
},
|
|
178
|
+
required: [],
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
export const listDirectoryTool = {
|
|
182
|
+
name: 'list_directory',
|
|
183
|
+
definition: listDirDef,
|
|
184
|
+
risk: 'low',
|
|
185
|
+
async execute(args, context) {
|
|
186
|
+
const dirPath = join(context.workingDirectory, args.path || '.');
|
|
187
|
+
if (!existsSync(dirPath))
|
|
188
|
+
return `Error: Directory not found: ${args.path}`;
|
|
189
|
+
const stat = statSync(dirPath);
|
|
190
|
+
if (!stat.isDirectory())
|
|
191
|
+
return `Error: Path is not a directory: ${args.path}`;
|
|
192
|
+
const recursive = args.recursive || false;
|
|
193
|
+
const maxDepth = args.max_depth || 3;
|
|
194
|
+
function listDir(path, prefix, depth) {
|
|
195
|
+
if (depth > maxDepth)
|
|
196
|
+
return `${prefix}... (max depth reached)`;
|
|
197
|
+
const entries = readdirSync(path, { withFileTypes: true });
|
|
198
|
+
// Sort: directories first, then files
|
|
199
|
+
const sorted = entries.sort((a, b) => {
|
|
200
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
201
|
+
return -1;
|
|
202
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
203
|
+
return 1;
|
|
204
|
+
return a.name.localeCompare(b.name);
|
|
205
|
+
});
|
|
206
|
+
// Skip common noise directories
|
|
207
|
+
const skipDirs = new Set(['node_modules', '.git', '__pycache__', '.next', 'dist', '.turbo', '.cache']);
|
|
208
|
+
const lines = [];
|
|
209
|
+
for (const entry of sorted) {
|
|
210
|
+
if (skipDirs.has(entry.name) && entry.isDirectory()) {
|
|
211
|
+
lines.push(`${prefix}📁 ${entry.name}/ (skipped)`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const fullPath = join(path, entry.name);
|
|
215
|
+
if (entry.isDirectory()) {
|
|
216
|
+
lines.push(`${prefix}📁 ${entry.name}/`);
|
|
217
|
+
if (recursive) {
|
|
218
|
+
lines.push(listDir(fullPath, `${prefix} `, depth + 1));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
try {
|
|
223
|
+
const size = statSync(fullPath).size;
|
|
224
|
+
const sizeStr = size < 1024 ? `${size}B` : size < 1024 * 1024 ? `${(size / 1024).toFixed(1)}KB` : `${(size / (1024 * 1024)).toFixed(1)}MB`;
|
|
225
|
+
const ext = extname(entry.name);
|
|
226
|
+
const icon = getFileIcon(ext);
|
|
227
|
+
lines.push(`${prefix}${icon} ${entry.name} (${sizeStr})`);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
lines.push(`${prefix}📄 ${entry.name}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return lines.join('\n');
|
|
235
|
+
}
|
|
236
|
+
const result = listDir(dirPath, '', 0);
|
|
237
|
+
return truncateOutput(`Directory: ${args.path || '.'}\n${result}`);
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
// ---- Search Files (ripgrep) ----
|
|
241
|
+
const searchFilesDef = {
|
|
242
|
+
name: 'search_files',
|
|
243
|
+
description: 'Search for patterns in files using ripgrep. Supports regex patterns and file type filtering.',
|
|
244
|
+
parameters: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
pattern: { type: 'string', description: 'Search pattern (supports regex)' },
|
|
248
|
+
path: { type: 'string', description: 'Directory or file to search in (default: current directory)' },
|
|
249
|
+
file_type: { type: 'string', description: 'File type filter (e.g., "ts", "py", "js")' },
|
|
250
|
+
max_results: { type: 'number', description: 'Maximum number of results (default: 50)' },
|
|
251
|
+
case_insensitive: { type: 'boolean', description: 'Case insensitive search (default: false)' },
|
|
252
|
+
},
|
|
253
|
+
required: ['pattern'],
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
export const searchFilesTool = {
|
|
257
|
+
name: 'search_files',
|
|
258
|
+
definition: searchFilesDef,
|
|
259
|
+
risk: 'low',
|
|
260
|
+
async execute(args, context) {
|
|
261
|
+
const pattern = args.pattern;
|
|
262
|
+
const searchPath = join(context.workingDirectory, args.path || '.');
|
|
263
|
+
const fileType = args.file_type;
|
|
264
|
+
const maxResults = args.max_results || 50;
|
|
265
|
+
const caseInsensitive = args.case_insensitive || false;
|
|
266
|
+
let cmd = 'rg';
|
|
267
|
+
cmd += ` --line-number`;
|
|
268
|
+
cmd += ` --column`;
|
|
269
|
+
cmd += ` --max-count ${maxResults}`;
|
|
270
|
+
cmd += caseInsensitive ? ' -i' : '';
|
|
271
|
+
if (fileType)
|
|
272
|
+
cmd += ` --type ${fileType}`;
|
|
273
|
+
cmd += ` --color never`;
|
|
274
|
+
cmd += ` --no-heading`;
|
|
275
|
+
cmd += ` '${pattern.replace(/'/g, "'\\''")}'`;
|
|
276
|
+
cmd += ` "${searchPath}"`;
|
|
277
|
+
try {
|
|
278
|
+
const result = execSync(cmd, {
|
|
279
|
+
encoding: 'utf-8',
|
|
280
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
281
|
+
cwd: context.workingDirectory,
|
|
282
|
+
timeout: 30000,
|
|
283
|
+
}).trim();
|
|
284
|
+
if (!result)
|
|
285
|
+
return `No matches found for pattern: "${pattern}"`;
|
|
286
|
+
const lines = result.split('\n').slice(0, maxResults);
|
|
287
|
+
const formatted = lines.map(line => {
|
|
288
|
+
const match = line.match(/^(.+?):(\d+):(\d+):(.*)$/);
|
|
289
|
+
if (match) {
|
|
290
|
+
const [, file, lineNum, col, text] = match;
|
|
291
|
+
const relPath = relative(context.workingDirectory, file);
|
|
292
|
+
return `${relPath}:${lineNum}:${col}: ${text.trim()}`;
|
|
293
|
+
}
|
|
294
|
+
return line;
|
|
295
|
+
}).join('\n');
|
|
296
|
+
const totalLines = result.split('\n').length;
|
|
297
|
+
const suffix = totalLines > maxResults ? `\n... and ${totalLines - maxResults} more results` : '';
|
|
298
|
+
return truncateOutput(`Search results for "${pattern}":\n${formatted}${suffix}`);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (error.status === 1)
|
|
302
|
+
return `No matches found for pattern: "${pattern}"`;
|
|
303
|
+
if (error.status === 2)
|
|
304
|
+
return `Search error: ${error.message}`;
|
|
305
|
+
return `Search error: ${error.message}`;
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
// ---- Apply Diff ----
|
|
310
|
+
const applyDiffDef = {
|
|
311
|
+
name: 'apply_diff',
|
|
312
|
+
description: 'Apply a unified diff to a file. Use for precise code modifications.',
|
|
313
|
+
parameters: {
|
|
314
|
+
type: 'object',
|
|
315
|
+
properties: {
|
|
316
|
+
path: { type: 'string', description: 'Path to the file to modify' },
|
|
317
|
+
diff: { type: 'string', description: 'Unified diff to apply' },
|
|
318
|
+
},
|
|
319
|
+
required: ['path', 'diff'],
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
export const applyDiffTool = {
|
|
323
|
+
name: 'apply_diff',
|
|
324
|
+
definition: applyDiffDef,
|
|
325
|
+
risk: 'medium',
|
|
326
|
+
getApprovalRequest(args) {
|
|
327
|
+
return {
|
|
328
|
+
toolName: 'apply_diff',
|
|
329
|
+
args,
|
|
330
|
+
risk: 'medium',
|
|
331
|
+
description: `Apply diff to: ${args.path}`,
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
async execute(args, context) {
|
|
335
|
+
const filePath = join(context.workingDirectory, args.path);
|
|
336
|
+
if (!existsSync(filePath))
|
|
337
|
+
return `Error: File not found: ${args.path}`;
|
|
338
|
+
try {
|
|
339
|
+
// Write diff to temp file and apply with patch
|
|
340
|
+
const diffContent = args.diff;
|
|
341
|
+
const tmpDiffPath = join(context.workingDirectory, `.neuro-diff-${Date.now()}.patch`);
|
|
342
|
+
writeFileSync(tmpDiffPath, diffContent, 'utf-8');
|
|
343
|
+
const result = execSync(`patch --no-backup-if-mismatch -p0 < "${tmpDiffPath}"`, {
|
|
344
|
+
encoding: 'utf-8',
|
|
345
|
+
cwd: context.workingDirectory,
|
|
346
|
+
timeout: 10000,
|
|
347
|
+
});
|
|
348
|
+
// Clean up temp file
|
|
349
|
+
try {
|
|
350
|
+
unlinkSync(tmpDiffPath);
|
|
351
|
+
}
|
|
352
|
+
catch { }
|
|
353
|
+
return `Successfully applied diff to ${args.path}\n${result}`;
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
return `Error applying diff: ${error.message}`;
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
// ---- Helper Functions ----
|
|
361
|
+
function getFileIcon(ext) {
|
|
362
|
+
const icons = {
|
|
363
|
+
'.ts': '🔷', '.tsx': '⚛️', '.js': '🟨', '.jsx': '⚛️',
|
|
364
|
+
'.py': '🐍', '.rs': '🦀', '.go': '🐹', '.java': '☕',
|
|
365
|
+
'.css': '🎨', '.scss': '🎨', '.html': '🌐', '.json': '📋',
|
|
366
|
+
'.md': '📝', '.yml': '⚙️', '.yaml': '⚙️', '.toml': '⚙️',
|
|
367
|
+
'.sql': '🗃️', '.sh': '🖥️', '.bash': '🖥️', '.zsh': '🖥️',
|
|
368
|
+
'.env': '🔒', '.lock': '🔒', '.gitignore': '🚫',
|
|
369
|
+
};
|
|
370
|
+
return icons[ext] || '📄';
|
|
371
|
+
}
|
|
372
|
+
// Export all file tools
|
|
373
|
+
export const fileTools = [
|
|
374
|
+
readFileTool,
|
|
375
|
+
writeFileTool,
|
|
376
|
+
editFileTool,
|
|
377
|
+
deleteFileTool,
|
|
378
|
+
listDirectoryTool,
|
|
379
|
+
searchFilesTool,
|
|
380
|
+
applyDiffTool,
|
|
381
|
+
];
|
|
382
|
+
//# sourceMappingURL=file.js.map
|