opencode-ai-cli 1.17.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.
Files changed (40) hide show
  1. package/GEMINI.md +11 -0
  2. package/cli.ts +17 -0
  3. package/commands/agent.ts +102 -0
  4. package/commands/chat.ts +518 -0
  5. package/commands/models.ts +10 -0
  6. package/commands/providers/index.ts +10 -0
  7. package/commands/providers/login.ts +30 -0
  8. package/commands/providers/logout.ts +13 -0
  9. package/commands/providers/setProvider.ts +9 -0
  10. package/package.json +22 -0
  11. package/src/core/auth-storage.ts +136 -0
  12. package/src/core/model-registry.ts +23 -0
  13. package/src/engine/agentLoop.ts +389 -0
  14. package/src/engine/messages.ts +110 -0
  15. package/src/engine/systemPrompt.ts +58 -0
  16. package/src/engine/type.ts +133 -0
  17. package/src/providers/gemini.ts +122 -0
  18. package/src/providers/openai.ts +60 -0
  19. package/src/subagent/README.md +177 -0
  20. package/src/subagent/agents/planner.md +37 -0
  21. package/src/subagent/agents/reviewer.md +35 -0
  22. package/src/subagent/agents/scout.md +49 -0
  23. package/src/subagent/agents/worker.md +29 -0
  24. package/src/subagent/agents.ts +89 -0
  25. package/src/subagent/index.ts +224 -0
  26. package/src/subagent/prompts/implement-and-review.md +10 -0
  27. package/src/subagent/prompts/implement.md +10 -0
  28. package/src/subagent/prompts/scout-and-plan.md +9 -0
  29. package/src/tools/bash-tool.ts +44 -0
  30. package/src/tools/edit-tool.ts +85 -0
  31. package/src/tools/find-tool.ts +81 -0
  32. package/src/tools/grep-tool.ts +100 -0
  33. package/src/tools/index.ts +37 -0
  34. package/src/tools/ls-tool.ts +93 -0
  35. package/src/tools/plan-tool.ts +35 -0
  36. package/src/tools/read-tool.ts +89 -0
  37. package/src/tools/truncate.ts +21 -0
  38. package/src/tools/weather-tool.ts +55 -0
  39. package/src/tools/write-tool.ts +53 -0
  40. package/src/types.ts +28 -0
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: reviewer
3
+ description: Code review specialist for quality and security analysis
4
+ tools: read, grep, find, ls, bash
5
+ model: claude-sonnet-4-5
6
+ ---
7
+
8
+ You are a senior code reviewer. Analyze code for quality, security, and maintainability.
9
+
10
+ Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
11
+ Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
12
+
13
+ Strategy:
14
+ 1. Run `git diff` to see recent changes (if applicable)
15
+ 2. Read the modified files
16
+ 3. Check for bugs, security issues, code smells
17
+
18
+ Output format:
19
+
20
+ ## Files Reviewed
21
+ - `path/to/file.ts` (lines X-Y)
22
+
23
+ ## Critical (must fix)
24
+ - `file.ts:42` - Issue description
25
+
26
+ ## Warnings (should fix)
27
+ - `file.ts:100` - Issue description
28
+
29
+ ## Suggestions (consider)
30
+ - `file.ts:150` - Improvement idea
31
+
32
+ ## Summary
33
+ Overall assessment in 2-3 sentences.
34
+
35
+ Be specific with file paths and line numbers.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: scout
3
+ description: Fast codebase recon that returns compressed context for handoff to other agents
4
+ tools: read_file, grep, find, ls
5
+ openai_model: gpt-4o-mini
6
+ gemini_model: gemini-2.5-flash
7
+ ---
8
+
9
+ You are a scout. Investigate the codebase and return structured findings that another agent can use without re-reading everything.
10
+
11
+ Your output will be passed to an agent who has NOT seen the files you explored.
12
+
13
+ Default thoroughness is repository-aware medium: map the project shape first, then inspect the files relevant to the user's request. Do not stop after listing files.
14
+
15
+ Strategy:
16
+ 1. EXTREME SPEED: Do NOT attempt to read every file in the project. You must be fast.
17
+ 2. Use `ls` and `find` to locate the 1 or 2 files most relevant to the user's request.
18
+ 3. Read ONLY those 1 or 2 critical files using `read_file`.
19
+ 4. DO NOT follow deep import chains. If you see an import, note it, but do not read the imported file unless absolutely necessary.
20
+ 5. SURGICAL READS: Request only the line ranges you need. Do NOT dump entire files.
21
+ 6. MAXIMUM 3 READS: You are strictly limited to reading a maximum of 3 files per scout mission.
22
+ 7. Once you have the gist of the architecture and the relevant file, immediately output your report.
23
+
24
+ Output format:
25
+
26
+ ## Files Retrieved
27
+ List with exact line ranges:
28
+ 1. `path/to/file.ts` (lines 10-50) - Description of what's here
29
+ 2. `path/to/other.ts` (lines 100-150) - Description
30
+ 3. ...
31
+
32
+ ## Key Snippets
33
+ ONLY include critical types, interfaces, or logic (MAX 20 lines per snippet):
34
+
35
+ ```typescript
36
+ // path/to/file.ts lines 10-25
37
+ interface Example {
38
+ ...
39
+ }
40
+ ```
41
+
42
+ ## Architecture
43
+ Brief explanation (2-3 sentences) of how the pieces connect.
44
+
45
+ ## Start Here
46
+ Which file to look at first and why.
47
+
48
+ ## Gaps
49
+ Anything relevant you could not verify.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: worker
3
+ description: General-purpose subagent with full capabilities, isolated context
4
+ model: claude-sonnet-4-5
5
+ ---
6
+
7
+ You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation.
8
+
9
+ Work autonomously to complete the assigned task. Use all available tools as needed.
10
+
11
+ - MANDATORY: When using mutating tools (`write_file`, `edit_file`, `bash`), you MUST explicitly describe your action.
12
+ - For `edit_file`, clearly state what is being removed and what is being added, and identify the affected lines.
13
+ - For `write_file`, state that you are creating/overwriting a file at a specific path.
14
+ - For `bash`, state exactly what command you are running and its purpose.
15
+
16
+ Output format when finished:
17
+
18
+ ## Completed
19
+ What was done.
20
+
21
+ ## Files Changed
22
+ - `path/to/file.ts` - what changed
23
+
24
+ ## Notes (if any)
25
+ Anything the main agent should know.
26
+
27
+ If handing off to another agent (e.g. reviewer), include:
28
+ - Exact file paths changed
29
+ - Key functions/types touched (short list)
@@ -0,0 +1,89 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export type AgentScope = "user" | "project" | "both";
5
+
6
+ export interface AgentConfig {
7
+ name: string;
8
+ description: string;
9
+ tools?: string[];
10
+ model?: string;
11
+ openai_model?: string;
12
+ gemini_model?: string;
13
+ systemPrompt: string;
14
+ source: "user" | "project";
15
+ filePath: string;
16
+ }
17
+
18
+ export interface AgentDiscoveryResult {
19
+ agents: AgentConfig[];
20
+ projectAgentsDir: string | null;
21
+ }
22
+
23
+ // Simple built-in frontmatter parser
24
+ function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
25
+ const parts = content.split("---");
26
+ if (parts.length < 3) return { frontmatter: {}, body: content };
27
+
28
+ const fmStr = parts[1];
29
+ const body = parts.slice(2).join("---").trim();
30
+ const frontmatter: Record<string, string> = {};
31
+
32
+ fmStr.split("\n").forEach((line) => {
33
+ const [key, ...rest] = line.split(":");
34
+ if (key && rest.length) frontmatter[key.trim()] = rest.join(":").trim();
35
+ });
36
+
37
+ return { frontmatter, body };
38
+ }
39
+
40
+ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
41
+ const agents: AgentConfig[] = [];
42
+ if (!fs.existsSync(dir)) return agents;
43
+
44
+ let entries: fs.Dirent[];
45
+ try {
46
+ entries = fs.readdirSync(dir, { withFileTypes: true });
47
+ } catch {
48
+ return agents;
49
+ }
50
+
51
+ for (const entry of entries) {
52
+ if (!entry.name.endsWith(".md")) continue;
53
+ const filePath = path.join(dir, entry.name);
54
+ let content: string;
55
+ try {
56
+ content = fs.readFileSync(filePath, "utf-8");
57
+ } catch {
58
+ continue;
59
+ }
60
+ const { frontmatter, body } = parseFrontmatter(content);
61
+
62
+ if (!frontmatter.name || !frontmatter.description) continue;
63
+
64
+ const tools = frontmatter.tools
65
+ ?.split(",")
66
+ .map((t: string) => t.trim())
67
+ .filter(Boolean);
68
+
69
+ agents.push({
70
+ name: frontmatter.name,
71
+ description: frontmatter.description,
72
+ tools: tools && tools.length > 0 ? tools : undefined,
73
+ model: frontmatter.model,
74
+ openai_model: frontmatter.openai_model,
75
+ gemini_model: frontmatter.gemini_model,
76
+ systemPrompt: body,
77
+ source,
78
+ filePath,
79
+ });
80
+ }
81
+ return agents;
82
+ }
83
+
84
+ export function discoverAgents(cwd: string): AgentDiscoveryResult {
85
+ const localAgentsDir = path.join(cwd, "src", "subagent", "agents");
86
+ const agents = loadAgentsFromDir(localAgentsDir, "project");
87
+
88
+ return { agents, projectAgentsDir: localAgentsDir };
89
+ }
@@ -0,0 +1,224 @@
1
+ import { runAgentLoop } from "../engine/agentLoop";
2
+ import { AuthStorage } from "../core/auth-storage";
3
+ import { getAllTools } from "../tools/index";
4
+ import { discoverAgents, AgentConfig } from "./agents";
5
+ import chalk from "chalk";
6
+ import { AgentMessage } from "../engine/type";
7
+ import { AgentTool, AgentToolResult } from "../types";
8
+
9
+ async function runSingleAgent(
10
+ agent: AgentConfig,
11
+ task: string,
12
+ cwd: string,
13
+ ): Promise<{ output: string; usage: { promptTokens: number; completionTokens: number; totalTokens: number } }> {
14
+ const activeProvider = AuthStorage.getAuthenticatedProviders()[0] ?? "openai";
15
+ const activeKey = AuthStorage.getApiKey(activeProvider);
16
+
17
+ if (!activeKey) throw new Error("No API key available for subagent.");
18
+
19
+ // DYNAMIC MODEL SELECTION
20
+ let selectedModel = agent.model; // global fallback
21
+ if (activeProvider === "openai" && agent.openai_model) {
22
+ selectedModel = agent.openai_model;
23
+ } else if (activeProvider === "gemini" && agent.gemini_model) {
24
+ selectedModel = agent.gemini_model;
25
+ }
26
+
27
+ const allAvailableTools = getAllTools();
28
+ const toolAliases: Record<string, string> = {
29
+ read: "read_file",
30
+ };
31
+ const requestedToolNames = agent.tools?.map(
32
+ (toolName) => toolAliases[toolName] ?? toolName,
33
+ );
34
+ const agentTools =
35
+ requestedToolNames && requestedToolNames.length > 0
36
+ ? allAvailableTools.filter((t) => requestedToolNames.includes(t.name))
37
+ : allAvailableTools;
38
+
39
+ const initialMessages: AgentMessage[] = [
40
+ { role: "system", content: agent.systemPrompt },
41
+ { role: "user", content: task },
42
+ ];
43
+
44
+ const results = await runAgentLoop(
45
+ initialMessages,
46
+ {
47
+ cwd: cwd,
48
+ messages: [],
49
+ activeToolNames: agentTools.map((t) => t.name),
50
+ tools: agentTools,
51
+ },
52
+ {
53
+ provider: activeProvider,
54
+ apiKey: activeKey,
55
+ model: selectedModel,
56
+ maxTurns: 15,
57
+ },
58
+ (event) => {
59
+ if (event.type === "tool_execution_start") {
60
+ console.log(
61
+ chalk.dim(` [${agent.name}] running ${event.toolName}...`),
62
+ );
63
+ }
64
+ },
65
+ );
66
+
67
+ const usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
68
+ results.forEach((msg) => {
69
+ if (msg.role === "assistant" && msg.usage) {
70
+ usage.promptTokens += msg.usage.promptTokens;
71
+ usage.completionTokens += msg.usage.completionTokens;
72
+ usage.totalTokens += msg.usage.totalTokens;
73
+ }
74
+ });
75
+
76
+ const lastMessage = results[results.length - 1];
77
+ let output = "(no output)";
78
+ if (lastMessage && lastMessage.role === "assistant") {
79
+ output = lastMessage.content
80
+ .filter((p: any) => p.type === "text")
81
+ .map((p: any) => p.text)
82
+ .join("");
83
+ }
84
+ return { output, usage };
85
+ }
86
+
87
+ export const nativeSubagentTool: AgentTool = {
88
+ name: "subagent",
89
+ label: "Subagent",
90
+ description:
91
+ "Delegate tasks to specialized subagents (scout, planner, worker). Supports single {agent, task}, chain [{agent, task...}], or parallel tasks [{agent, task}].",
92
+ schema: {
93
+ type: "function",
94
+ function: {
95
+ name: "subagent",
96
+ description:
97
+ "Delegate to subagents. Provide exactly one mode: agent/task (single), chain (array), or tasks (parallel).",
98
+ parameters: {
99
+ type: "object",
100
+ properties: {
101
+ agent: { type: "string", description: "Agent name (Single mode)" },
102
+ task: { type: "string", description: "Task (Single mode)" },
103
+ chain: {
104
+ type: "array",
105
+ description: "Sequential chain of agents",
106
+ items: {
107
+ type: "object",
108
+ properties: {
109
+ agent: { type: "string" },
110
+ task: {
111
+ type: "string",
112
+ description: "Use {previous} to inject previous step output.",
113
+ },
114
+ },
115
+ required: ["agent", "task"],
116
+ },
117
+ },
118
+ tasks: {
119
+ type: "array",
120
+ description: "Parallel tasks",
121
+ items: {
122
+ type: "object",
123
+ properties: {
124
+ agent: { type: "string" },
125
+ task: { type: "string" },
126
+ },
127
+ required: ["agent", "task"],
128
+ },
129
+ },
130
+ },
131
+ },
132
+ },
133
+ },
134
+ execute: async (args: any): Promise<AgentToolResult> => {
135
+ const cwd = process.cwd();
136
+ const { agents } = discoverAgents(cwd);
137
+
138
+ // CHAIN MODE
139
+ if (args.chain && args.chain.length > 0) {
140
+ console.log(
141
+ chalk.bold.blue(
142
+ `\n[Sub-Agent] Starting Chain workflow (${args.chain.length} steps)...`,
143
+ ),
144
+ );
145
+ let previousOutput = "";
146
+ let finalReport = "";
147
+ const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
148
+
149
+ for (let i = 0; i < args.chain.length; i++) {
150
+ const step = args.chain[i];
151
+ const agent = agents.find((a) => a.name === step.agent);
152
+ if (!agent) throw new Error(`Agent ${step.agent} not found.`);
153
+
154
+ const taskWithContext = step.task.replace(
155
+ /\{previous\}/g,
156
+ previousOutput,
157
+ );
158
+ console.log(chalk.blue(`-> Step ${i + 1}: ${step.agent}`));
159
+
160
+ const { output, usage } = await runSingleAgent(agent, taskWithContext, cwd);
161
+ previousOutput = output;
162
+ totalUsage.promptTokens += usage.promptTokens;
163
+ totalUsage.completionTokens += usage.completionTokens;
164
+ totalUsage.totalTokens += usage.totalTokens;
165
+ finalReport += `### Step ${i + 1}: ${step.agent}\n${previousOutput}\n\n`;
166
+ }
167
+ return {
168
+ content: [{ type: "text", text: finalReport }],
169
+ terminate: false,
170
+ usage: totalUsage,
171
+ };
172
+ }
173
+
174
+ // PARALLEL MODE
175
+ if (args.tasks && args.tasks.length > 0) {
176
+ console.log(
177
+ chalk.bold.blue(
178
+ `\n[Sub-Agent] Starting Parallel workflow (${args.tasks.length} tasks)...`,
179
+ ),
180
+ );
181
+ const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
182
+ const promises = args.tasks.map(async (t: any) => {
183
+ const agent = agents.find((a) => a.name === t.agent);
184
+ if (!agent) return `### ${t.agent} (NOT FOUND)`;
185
+ try {
186
+ const { output, usage } = await runSingleAgent(agent, t.task, cwd);
187
+ totalUsage.promptTokens += usage.promptTokens;
188
+ totalUsage.completionTokens += usage.completionTokens;
189
+ totalUsage.totalTokens += usage.totalTokens;
190
+ return `### ${t.agent}\n${output}`;
191
+ } catch (error: any) {
192
+ console.log(chalk.red(` [${t.agent}] failed: ${error.message}`));
193
+ return `### ${t.agent} (FAILED)\nError: ${error.message}`;
194
+ }
195
+ });
196
+
197
+ const results = await Promise.all(promises);
198
+ return {
199
+ content: [{ type: "text", text: results.join("\n\n") }],
200
+ terminate: false,
201
+ usage: totalUsage,
202
+ };
203
+ }
204
+
205
+ // SINGLE MODE
206
+ if (args.agent && args.task) {
207
+ console.log(
208
+ chalk.bold.blue(`\n[Sub-Agent] Starting Single task: ${args.agent}...`),
209
+ );
210
+ const agent = agents.find((a) => a.name === args.agent);
211
+ if (!agent)
212
+ return {
213
+ content: [{ type: "text", text: `Agent ${args.agent} not found.` }],
214
+ };
215
+
216
+ const { output, usage } = await runSingleAgent(agent, args.task, cwd);
217
+ return { content: [{ type: "text", text: output }], terminate: false, usage };
218
+ }
219
+
220
+ return {
221
+ content: [{ type: "text", text: "Invalid subagent parameters." }],
222
+ };
223
+ },
224
+ };
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Worker implements, reviewer reviews, worker applies feedback
3
+ ---
4
+ Use the subagent tool with the chain parameter to execute this workflow:
5
+
6
+ 1. First, use the "worker" agent to implement: $@
7
+ 2. Then, use the "reviewer" agent to review the implementation from the previous step (use {previous} placeholder)
8
+ 3. Finally, use the "worker" agent to apply the feedback from the review (use {previous} placeholder)
9
+
10
+ Execute this as a chain, passing output between steps via {previous}.
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Full implementation workflow - scout gathers context, planner creates plan, worker implements
3
+ ---
4
+ Use the subagent tool with the chain parameter to execute this workflow:
5
+
6
+ 1. First, use the "scout" agent to find all code relevant to: $@
7
+ 2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder)
8
+ 3. Finally, use the "worker" agent to implement the plan from the previous step (use {previous} placeholder)
9
+
10
+ Execute this as a chain, passing output between steps via {previous}.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Scout gathers context, planner creates implementation plan (no implementation)
3
+ ---
4
+ Use the subagent tool with the chain parameter to execute this workflow:
5
+
6
+ 1. First, use the "scout" agent to find all code relevant to: $@
7
+ 2. Then, use the "planner" agent to create an implementation plan for "$@" using the context from the previous step (use {previous} placeholder)
8
+
9
+ Execute this as a chain, passing output between steps via {previous}. Do NOT implement - just return the plan.
@@ -0,0 +1,44 @@
1
+
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ import { AgentTool, AgentToolResult } from "../types";
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ export const bashTool: AgentTool = {
9
+ name: "bash",
10
+ label: "Bash Command",
11
+ description: "Execute a bash command in the terminal. Use this for installing packages, running tests, or git operations.",
12
+ schema: {
13
+ type: "function",
14
+ function: {
15
+ name: "bash",
16
+ description: "Execute a shell command.",
17
+ parameters: {
18
+ type: "object",
19
+ properties: {
20
+ command: {
21
+ type: "string",
22
+ description: "The bash command to execute.",
23
+ },
24
+ },
25
+ required: ["command"],
26
+ },
27
+ },
28
+ },
29
+ execute: async (args: any): Promise<AgentToolResult> => {
30
+ try {
31
+ const { stdout, stderr } = await execAsync(args.command, { cwd: process.cwd() });
32
+ const output = stdout + (stderr ? `\nErrors:\n${stderr}` : "");
33
+ return {
34
+ content: [{ type: "text", text: output.trim() || "Command executed successfully with no output." }],
35
+ terminate: false,
36
+ };
37
+ } catch (error: any) {
38
+ return {
39
+ content: [{ type: "text", text: `Command failed: ${error.message}\n${error.stdout || ""}\n${error.stderr || ""}` }],
40
+ terminate: false,
41
+ };
42
+ }
43
+ },
44
+ };
@@ -0,0 +1,85 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { AgentTool, AgentToolResult } from "../types";
4
+
5
+ export const editTool: AgentTool = {
6
+ name: "edit_file",
7
+ label: "Edit File",
8
+ description:
9
+ "Surgically replace text in an existing file. Provide the EXACT old string you want to replace, and the EXACT new string.",
10
+ schema: {
11
+ type: "function",
12
+ function: {
13
+ name: "edit_file",
14
+ description: "Replace a specific string of text within a file.",
15
+ parameters: {
16
+ type: "object",
17
+ properties: {
18
+ file_path: {
19
+ type: "string",
20
+ description: "The path to the file to edit.",
21
+ },
22
+ old_string: {
23
+ type: "string",
24
+ description:
25
+ "The EXACT literal text currently in the file that you want to replace.",
26
+ },
27
+ new_string: {
28
+ type: "string",
29
+ description: "The literal text to replace it with.",
30
+ },
31
+ },
32
+ required: ["file_path", "old_string", "new_string"],
33
+ },
34
+ },
35
+ },
36
+ execute: async (args: any): Promise<AgentToolResult> => {
37
+ try {
38
+ const fullPath = path.resolve(process.cwd(), args.file_path);
39
+ const content = await fs.readFile(fullPath, "utf-8");
40
+
41
+ if (!content.includes(args.old_string)) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: `Edit failed: Could not find the exact 'old_string' in ${args.file_path}. Make sure whitespace and indentation match perfectly.`,
47
+ },
48
+ ],
49
+ terminate: false,
50
+ };
51
+ }
52
+
53
+ // Check for multiple occurrences to warn the agent
54
+ const occurrences = content.split(args.old_string).length - 1;
55
+ if (occurrences > 1) {
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: `Edit failed: 'old_string' was found ${occurrences} times. Your old_string must be unique to avoid accidental replacements.`,
61
+ },
62
+ ],
63
+ terminate: false,
64
+ };
65
+ }
66
+
67
+ const newContent = content.replace(args.old_string, args.new_string);
68
+ await fs.writeFile(fullPath, newContent, "utf-8");
69
+
70
+ return {
71
+ content: [
72
+ { type: "text", text: `Successfully edited ${args.file_path}` },
73
+ ],
74
+ terminate: false,
75
+ };
76
+ } catch (error: any) {
77
+ return {
78
+ content: [
79
+ { type: "text", text: `Failed to edit file: ${error.message}` },
80
+ ],
81
+ terminate: false,
82
+ };
83
+ }
84
+ },
85
+ };
@@ -0,0 +1,81 @@
1
+ import { glob } from "glob";
2
+ import { AgentTool } from "../types";
3
+
4
+ export const findTool: AgentTool = {
5
+ name: "find",
6
+ label: "find files",
7
+ description: "The BEST tool for recursively finding files by extension (e.g. '**/*.ts') or searching deep directory trees. Always use this instead of 'ls' when looking for specific files.",
8
+ schema: {
9
+ type: "function",
10
+ function: {
11
+ name: "find",
12
+ description:
13
+ "The BEST tool for recursively finding files by extension (e.g. '**/*.ts') or searching deep directory trees. Always use this instead of 'ls' when looking for specific files.",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ pattern: {
18
+ type: "string",
19
+ description:
20
+ "Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.ts'",
21
+ },
22
+ dirPath: {
23
+ type: "string",
24
+ description:
25
+ "Directory to search in (default: current directory '.')",
26
+ },
27
+ },
28
+ required: ["pattern"],
29
+ },
30
+ },
31
+ },
32
+
33
+ execute: async (args: any) => {
34
+ try {
35
+ const pattern = args.pattern;
36
+ const targetPath = args.dirPath || ".";
37
+
38
+ console.log(
39
+ `\n system runing 'find for pattern ${pattern} in ${targetPath}`,
40
+ );
41
+
42
+ const files = await glob(pattern, {
43
+ cwd: targetPath,
44
+ ignore: ["**/node_modules/**", "**/.git/**"],
45
+ nodir: true, // Only return files not the folders
46
+ });
47
+
48
+ if (files.length === 0) {
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: `No files found by matching this pattern ${pattern}`,
54
+ },
55
+ ],
56
+ details: {},
57
+ };
58
+ }
59
+
60
+ const limit = 100;
61
+
62
+ let output = files.slice(0, limit).join("\n");
63
+
64
+ if (files.length > limit) {
65
+ output += `\n\n[Warning: Output truncated. ${files.length} total results found, showing first ${limit}.]`;
66
+ }
67
+ return {
68
+ content: [{ type: "text", text: output }],
69
+ details: {},
70
+ };
71
+ } catch (error: any) {
72
+ console.error(`find tool error ${error.message}`);
73
+ return {
74
+ content: [
75
+ { type: "text", text: `Failed to excuate find: ${error.message}` },
76
+ ],
77
+ details: {},
78
+ };
79
+ }
80
+ },
81
+ };