mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 01:03:49 +00:00
feat(coding-agent): add read-only exploration tools (grep, find, ls) and --tools flag
Add grep, find, and ls tools for safe code exploration without modification risk. These tools are available via the new --tools CLI flag. - grep: Uses ripgrep (auto-downloaded) for fast regex searching. Respects .gitignore, supports glob filtering, context lines, and hidden files. - find: Uses fd (auto-downloaded) for fast file finding. Respects .gitignore, supports glob patterns, and hidden files. - ls: Lists directory contents with proper sorting and directory indicators. - --tools flag: Specify available tools (e.g., --tools read,grep,find,ls for read-only mode) - Dynamic system prompt adapts to selected tools with relevant guidelines Closes #74
This commit is contained in:
parent
a61eca5dee
commit
186169a820
10 changed files with 928 additions and 25 deletions
|
|
@ -11,7 +11,7 @@ import { findModel, getApiKeyForModel, getAvailableModels } from "./model-config
|
|||
import { SessionManager } from "./session-manager.js";
|
||||
import { SettingsManager } from "./settings-manager.js";
|
||||
import { initTheme } from "./theme/theme.js";
|
||||
import { codingTools } from "./tools/index.js";
|
||||
import { allTools, codingTools, type ToolName } from "./tools/index.js";
|
||||
import { ensureTool } from "./tools-manager.js";
|
||||
import { SessionSelectorComponent } from "./tui/session-selector.js";
|
||||
import { TuiRenderer } from "./tui/tui-renderer.js";
|
||||
|
|
@ -48,6 +48,7 @@ interface Args {
|
|||
noSession?: boolean;
|
||||
session?: string;
|
||||
models?: string[];
|
||||
tools?: ToolName[];
|
||||
print?: boolean;
|
||||
messages: string[];
|
||||
fileArgs: string[];
|
||||
|
|
@ -87,6 +88,19 @@ function parseArgs(args: string[]): Args {
|
|||
result.session = args[++i];
|
||||
} else if (arg === "--models" && i + 1 < args.length) {
|
||||
result.models = args[++i].split(",").map((s) => s.trim());
|
||||
} else if (arg === "--tools" && i + 1 < args.length) {
|
||||
const toolNames = args[++i].split(",").map((s) => s.trim());
|
||||
const validTools: ToolName[] = [];
|
||||
for (const name of toolNames) {
|
||||
if (name in allTools) {
|
||||
validTools.push(name as ToolName);
|
||||
} else {
|
||||
console.error(
|
||||
chalk.yellow(`Warning: Unknown tool "${name}". Valid tools: ${Object.keys(allTools).join(", ")}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
result.tools = validTools;
|
||||
} else if (arg === "--thinking" && i + 1 < args.length) {
|
||||
const level = args[++i];
|
||||
if (level === "off" || level === "minimal" || level === "low" || level === "medium" || level === "high") {
|
||||
|
|
@ -220,6 +234,8 @@ ${chalk.bold("Options:")}
|
|||
--session <path> Use specific session file
|
||||
--no-session Don't save session (ephemeral)
|
||||
--models <patterns> Comma-separated model patterns for quick cycling with Ctrl+P
|
||||
--tools <tools> Comma-separated list of tools to enable (default: read,bash,edit,write)
|
||||
Available: read, bash, edit, write, grep, find, ls
|
||||
--thinking <level> Set thinking level: off, minimal, low, medium, high
|
||||
--help, -h Show this help
|
||||
|
||||
|
|
@ -254,6 +270,9 @@ ${chalk.bold("Examples:")}
|
|||
# Start with a specific thinking level
|
||||
pi --thinking high "Solve this complex problem"
|
||||
|
||||
# Read-only mode (no file modifications possible)
|
||||
pi --tools read,grep,find,ls -p "Review the code in src/"
|
||||
|
||||
${chalk.bold("Environment Variables:")}
|
||||
ANTHROPIC_API_KEY - Anthropic Claude API key
|
||||
ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth token (alternative to API key)
|
||||
|
|
@ -266,15 +285,29 @@ ${chalk.bold("Environment Variables:")}
|
|||
ZAI_API_KEY - ZAI API key
|
||||
PI_CODING_AGENT_DIR - Session storage directory (default: ~/.pi/agent)
|
||||
|
||||
${chalk.bold("Available Tools:")}
|
||||
${chalk.bold("Available Tools (default: read, bash, edit, write):")}
|
||||
read - Read file contents
|
||||
bash - Execute bash commands
|
||||
edit - Edit files with find/replace
|
||||
write - Write files (creates/overwrites)
|
||||
grep - Search file contents (read-only, off by default)
|
||||
find - Find files by glob pattern (read-only, off by default)
|
||||
ls - List directory contents (read-only, off by default)
|
||||
`);
|
||||
}
|
||||
|
||||
function buildSystemPrompt(customPrompt?: string): string {
|
||||
// Tool descriptions for system prompt
|
||||
const toolDescriptions: Record<ToolName, string> = {
|
||||
read: "Read file contents",
|
||||
bash: "Execute bash commands (ls, grep, find, etc.)",
|
||||
edit: "Make surgical edits to files (find exact text and replace)",
|
||||
write: "Create or overwrite files",
|
||||
grep: "Search file contents for patterns (respects .gitignore)",
|
||||
find: "Find files by glob pattern (respects .gitignore)",
|
||||
ls: "List directory contents",
|
||||
};
|
||||
|
||||
function buildSystemPrompt(customPrompt?: string, selectedTools?: ToolName[]): string {
|
||||
// Check if customPrompt is a file path that exists
|
||||
if (customPrompt && existsSync(customPrompt)) {
|
||||
try {
|
||||
|
|
@ -333,22 +366,75 @@ function buildSystemPrompt(customPrompt?: string): string {
|
|||
// Get absolute path to README.md
|
||||
const readmePath = resolve(join(__dirname, "../README.md"));
|
||||
|
||||
// Build tools list based on selected tools
|
||||
const tools = selectedTools || (["read", "bash", "edit", "write"] as ToolName[]);
|
||||
const toolsList = tools.map((t) => `- ${t}: ${toolDescriptions[t]}`).join("\n");
|
||||
|
||||
// Build guidelines based on which tools are actually available
|
||||
const guidelinesList: string[] = [];
|
||||
|
||||
const hasBash = tools.includes("bash");
|
||||
const hasEdit = tools.includes("edit");
|
||||
const hasWrite = tools.includes("write");
|
||||
const hasGrep = tools.includes("grep");
|
||||
const hasFind = tools.includes("find");
|
||||
const hasLs = tools.includes("ls");
|
||||
const hasRead = tools.includes("read");
|
||||
|
||||
// Read-only mode notice (no bash, edit, or write)
|
||||
if (!hasBash && !hasEdit && !hasWrite) {
|
||||
guidelinesList.push("You are in READ-ONLY mode - you cannot modify files or execute arbitrary commands");
|
||||
}
|
||||
|
||||
// Bash without edit/write = read-only bash mode
|
||||
if (hasBash && !hasEdit && !hasWrite) {
|
||||
guidelinesList.push(
|
||||
"Use bash ONLY for read-only operations (git log, gh issue view, curl, etc.) - do NOT modify any files",
|
||||
);
|
||||
}
|
||||
|
||||
// File exploration guidelines
|
||||
if (hasBash && !hasGrep && !hasFind && !hasLs) {
|
||||
guidelinesList.push("Use bash for file operations like ls, grep, find");
|
||||
} else if (hasBash && (hasGrep || hasFind || hasLs)) {
|
||||
guidelinesList.push("Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)");
|
||||
}
|
||||
|
||||
// Read before edit guideline
|
||||
if (hasRead && hasEdit) {
|
||||
guidelinesList.push("Use read to examine files before editing");
|
||||
}
|
||||
|
||||
// Edit guideline
|
||||
if (hasEdit) {
|
||||
guidelinesList.push("Use edit for precise changes (old text must match exactly)");
|
||||
}
|
||||
|
||||
// Write guideline
|
||||
if (hasWrite) {
|
||||
guidelinesList.push("Use write only for new files or complete rewrites");
|
||||
}
|
||||
|
||||
// Output guideline (only when actually writing/executing)
|
||||
if (hasEdit || hasWrite) {
|
||||
guidelinesList.push(
|
||||
"When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did",
|
||||
);
|
||||
}
|
||||
|
||||
// Always include these
|
||||
guidelinesList.push("Be concise in your responses");
|
||||
guidelinesList.push("Show file paths clearly when working with files");
|
||||
|
||||
const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
|
||||
|
||||
let prompt = `You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.
|
||||
|
||||
Available tools:
|
||||
- read: Read file contents
|
||||
- bash: Execute bash commands (ls, grep, find, etc.)
|
||||
- edit: Make surgical edits to files (find exact text and replace)
|
||||
- write: Create or overwrite files
|
||||
${toolsList}
|
||||
|
||||
Guidelines:
|
||||
- Always use bash tool for file operations like ls, grep, find
|
||||
- Use read to examine files before editing
|
||||
- Use edit for precise changes (old text must match exactly)
|
||||
- Use write only for new files or complete rewrites
|
||||
- Be concise in your responses
|
||||
- Show file paths clearly when working with files
|
||||
- When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did
|
||||
${guidelines}
|
||||
|
||||
Documentation:
|
||||
- Your own documentation (including custom model setup and theme creation) is at: ${readmePath}
|
||||
|
|
@ -913,7 +999,7 @@ export async function main(args: string[]) {
|
|||
}
|
||||
}
|
||||
|
||||
const systemPrompt = buildSystemPrompt(parsed.systemPrompt);
|
||||
const systemPrompt = buildSystemPrompt(parsed.systemPrompt, parsed.tools);
|
||||
|
||||
// Load previous messages if continuing or resuming
|
||||
// This may update initialModel if restoring from session
|
||||
|
|
@ -996,13 +1082,16 @@ export async function main(args: string[]) {
|
|||
initialThinking = parsed.thinking;
|
||||
}
|
||||
|
||||
// Determine which tools to use
|
||||
const selectedTools = parsed.tools ? parsed.tools.map((name) => allTools[name]) : codingTools;
|
||||
|
||||
// Create agent (initialModel can be null in interactive mode)
|
||||
const agent = new Agent({
|
||||
initialState: {
|
||||
systemPrompt,
|
||||
model: initialModel as any, // Can be null
|
||||
thinkingLevel: initialThinking,
|
||||
tools: codingTools,
|
||||
tools: selectedTools,
|
||||
},
|
||||
queueMode: settingsManager.getQueueMode(),
|
||||
transport: new ProviderTransport({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue