mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Remove cli directory (#41)
This commit is contained in:
110
cli/README.md
110
cli/README.md
@@ -1,110 +0,0 @@
|
|||||||
# Cline CLI
|
|
||||||
|
|
||||||
A command-line interface for Cline, powered by Deno.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Make sure you have [Deno](https://deno.land/) installed
|
|
||||||
2. Install the CLI globally:
|
|
||||||
```bash
|
|
||||||
cd cli
|
|
||||||
deno task install
|
|
||||||
```
|
|
||||||
|
|
||||||
If you get a PATH warning during installation, add Deno's bin directory to your PATH:
|
|
||||||
```bash
|
|
||||||
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc
|
|
||||||
source ~/.bashrc # or ~/.zshrc
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cline <task> [options]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security Model
|
|
||||||
|
|
||||||
The CLI implements several security measures:
|
|
||||||
|
|
||||||
1. File Operations:
|
|
||||||
- Read/write access limited to working directory (--allow-read=., --allow-write=.)
|
|
||||||
- Prevents access to files outside the project
|
|
||||||
|
|
||||||
2. Command Execution:
|
|
||||||
- Strict allowlist of safe commands:
|
|
||||||
* npm (install, run, test, build)
|
|
||||||
* git (status, add, commit, push, pull, clone, checkout, branch)
|
|
||||||
* deno (run, test, fmt, lint, check, compile, bundle)
|
|
||||||
* ls (-l, -a, -la, -lh)
|
|
||||||
* cat, echo
|
|
||||||
- Interactive prompts for non-allowlisted commands:
|
|
||||||
* y - Run once
|
|
||||||
* n - Cancel execution
|
|
||||||
* always - Remember for session
|
|
||||||
- Clear warnings and command details shown
|
|
||||||
- Session-based memory for approved commands
|
|
||||||
|
|
||||||
3. Required Permissions:
|
|
||||||
- --allow-read=. - Read files in working directory
|
|
||||||
- --allow-write=. - Write files in working directory
|
|
||||||
- --allow-run - Execute allowlisted commands
|
|
||||||
- --allow-net - Make API calls
|
|
||||||
- --allow-env - Access environment variables
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
- `-m, --model <model>` - LLM model to use (default: "anthropic/claude-3.5-sonnet")
|
|
||||||
- `-k, --key <key>` - OpenRouter API key (required, or set OPENROUTER_API_KEY env var)
|
|
||||||
- `-h, --help` - Display help for command
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
Analyze code:
|
|
||||||
```bash
|
|
||||||
export OPENROUTER_API_KEY=sk-or-v1-...
|
|
||||||
cline "Analyze this codebase"
|
|
||||||
```
|
|
||||||
|
|
||||||
Create files:
|
|
||||||
```bash
|
|
||||||
cline "Create a React component"
|
|
||||||
```
|
|
||||||
|
|
||||||
Run allowed command:
|
|
||||||
```bash
|
|
||||||
cline "Run npm install"
|
|
||||||
```
|
|
||||||
|
|
||||||
Run non-allowlisted command (will prompt for decision):
|
|
||||||
```bash
|
|
||||||
cline "Run yarn install"
|
|
||||||
# Responds with:
|
|
||||||
# Warning: Command not in allowlist
|
|
||||||
# Command: yarn install
|
|
||||||
# Do you want to run this command? (y/n/always)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
The CLI is built with Deno. Available tasks:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run in development mode
|
|
||||||
deno task dev "your task here"
|
|
||||||
|
|
||||||
# Install globally
|
|
||||||
deno task install
|
|
||||||
|
|
||||||
# Type check the code
|
|
||||||
deno task check
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security Features
|
|
||||||
|
|
||||||
- File operations restricted to working directory
|
|
||||||
- Command execution controlled by allowlist
|
|
||||||
- Interactive prompts for unknown commands
|
|
||||||
- Session-based command approval
|
|
||||||
- Clear warnings and command details
|
|
||||||
- Permission validation at runtime
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import type { ApiConfiguration, ApiHandler } from "../types.d.ts";
|
|
||||||
import { OpenRouterHandler } from "./providers/openrouter.ts";
|
|
||||||
|
|
||||||
// Re-export the ApiHandler interface
|
|
||||||
export type { ApiHandler };
|
|
||||||
|
|
||||||
export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
|
|
||||||
const { apiKey, model } = configuration;
|
|
||||||
return new OpenRouterHandler({ apiKey, model });
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import type { ApiStream, ModelInfo, Message, TextBlock } from "../../types.d.ts";
|
|
||||||
|
|
||||||
interface OpenRouterOptions {
|
|
||||||
model: string;
|
|
||||||
apiKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OpenRouterHandler {
|
|
||||||
private apiKey: string;
|
|
||||||
private model: string;
|
|
||||||
|
|
||||||
constructor(options: OpenRouterOptions) {
|
|
||||||
this.apiKey = options.apiKey;
|
|
||||||
this.model = options.model;
|
|
||||||
}
|
|
||||||
|
|
||||||
async *createMessage(systemPrompt: string, messages: Message[]): ApiStream {
|
|
||||||
try {
|
|
||||||
// Convert our messages to OpenRouter format
|
|
||||||
const openRouterMessages = [
|
|
||||||
{ role: "system", content: systemPrompt },
|
|
||||||
...messages.map(msg => ({
|
|
||||||
role: msg.role,
|
|
||||||
content: Array.isArray(msg.content)
|
|
||||||
? msg.content.map(c => c.text).join("\n")
|
|
||||||
: msg.content
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Authorization": `Bearer ${this.apiKey}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline",
|
|
||||||
"X-Title": "Roo Cline"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
model: this.model,
|
|
||||||
messages: openRouterMessages,
|
|
||||||
stream: true,
|
|
||||||
temperature: 0.7,
|
|
||||||
max_tokens: 4096
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => null);
|
|
||||||
throw new Error(`OpenRouter API error: ${response.statusText}${errorData ? ` - ${JSON.stringify(errorData)}` : ""}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.body) {
|
|
||||||
throw new Error("No response body received");
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
let buffer = "";
|
|
||||||
let content = "";
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
|
|
||||||
// Add new chunk to buffer and split into lines
|
|
||||||
buffer += decoder.decode(value, { stream: true });
|
|
||||||
const lines = buffer.split("\n");
|
|
||||||
|
|
||||||
// Process all complete lines
|
|
||||||
buffer = lines.pop() || ""; // Keep the last incomplete line in buffer
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.trim() === "") continue;
|
|
||||||
if (line === "data: [DONE]") continue;
|
|
||||||
|
|
||||||
if (line.startsWith("data: ")) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(line.slice(6));
|
|
||||||
if (data.choices?.[0]?.delta?.content) {
|
|
||||||
const text = data.choices[0].delta.content;
|
|
||||||
content += text;
|
|
||||||
yield { type: "text", text };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore parse errors for incomplete chunks
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process any remaining content in buffer
|
|
||||||
if (buffer.trim() && buffer.startsWith("data: ")) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(buffer.slice(6));
|
|
||||||
if (data.choices?.[0]?.delta?.content) {
|
|
||||||
const text = data.choices[0].delta.content;
|
|
||||||
content += text;
|
|
||||||
yield { type: "text", text };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore parse errors for final incomplete chunk
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Estimate token usage (4 chars per token is a rough estimate)
|
|
||||||
const inputText = systemPrompt + messages.reduce((acc, msg) =>
|
|
||||||
acc + (typeof msg.content === "string" ?
|
|
||||||
msg.content :
|
|
||||||
msg.content.reduce((a, b) => a + b.text, "")), "");
|
|
||||||
|
|
||||||
const inputTokens = Math.ceil(inputText.length / 4);
|
|
||||||
const outputTokens = Math.ceil(content.length / 4);
|
|
||||||
|
|
||||||
yield {
|
|
||||||
type: "usage",
|
|
||||||
inputTokens,
|
|
||||||
outputTokens,
|
|
||||||
totalCost: this.calculateCost(inputTokens, outputTokens)
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error in OpenRouter API call:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getModel(): { id: string; info: ModelInfo } {
|
|
||||||
return {
|
|
||||||
id: this.model,
|
|
||||||
info: {
|
|
||||||
contextWindow: 128000, // This varies by model
|
|
||||||
supportsComputerUse: true,
|
|
||||||
inputPricePerToken: 0.000002, // Approximate, varies by model
|
|
||||||
outputPricePerToken: 0.000002
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateCost(inputTokens: number, outputTokens: number): number {
|
|
||||||
const { inputPricePerToken, outputPricePerToken } = this.getModel().info;
|
|
||||||
return (
|
|
||||||
(inputTokens * (inputPricePerToken || 0)) +
|
|
||||||
(outputTokens * (outputPricePerToken || 0))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
import { blue, red, yellow } from "../deps.ts";
|
|
||||||
import { ApiHandler } from "../api/mod.ts";
|
|
||||||
import { executeCommand, readFile, writeFile, searchFiles, listFiles, listCodeDefinitions } from "../tools/mod.ts";
|
|
||||||
import type { Message, TextBlock, ToolResult } from "../types.d.ts";
|
|
||||||
|
|
||||||
interface AgentConfig {
|
|
||||||
api: ApiHandler;
|
|
||||||
systemPrompt: string;
|
|
||||||
workingDir: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StandaloneAgent {
|
|
||||||
private api: ApiHandler;
|
|
||||||
private systemPrompt: string;
|
|
||||||
private workingDir: string;
|
|
||||||
private conversationHistory: Message[] = [];
|
|
||||||
|
|
||||||
constructor(config: AgentConfig) {
|
|
||||||
this.api = config.api;
|
|
||||||
this.systemPrompt = config.systemPrompt;
|
|
||||||
this.workingDir = config.workingDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
async runTask(task: string): Promise<void> {
|
|
||||||
this.conversationHistory.push({
|
|
||||||
role: "user",
|
|
||||||
content: [{ type: "text", text: `<task>\n${task}\n</task>` }]
|
|
||||||
});
|
|
||||||
|
|
||||||
let isTaskComplete = false;
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
|
|
||||||
while (!isTaskComplete) {
|
|
||||||
const stream = this.api.createMessage(this.systemPrompt, this.conversationHistory);
|
|
||||||
let assistantMessage = "";
|
|
||||||
|
|
||||||
console.log(blue("Thinking..."));
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
if (chunk.type === "text") {
|
|
||||||
assistantMessage += chunk.text;
|
|
||||||
await Deno.stdout.write(encoder.encode(chunk.text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conversationHistory.push({
|
|
||||||
role: "assistant",
|
|
||||||
content: [{ type: "text", text: assistantMessage }]
|
|
||||||
});
|
|
||||||
|
|
||||||
const toolResults = await this.executeTools(assistantMessage);
|
|
||||||
|
|
||||||
if (toolResults.length > 0) {
|
|
||||||
this.conversationHistory.push({
|
|
||||||
role: "user",
|
|
||||||
content: toolResults.map(result => ({
|
|
||||||
type: "text",
|
|
||||||
text: `[${result.tool}] Result:${result.output}`
|
|
||||||
})) as TextBlock[]
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (assistantMessage.includes("<attempt_completion>")) {
|
|
||||||
isTaskComplete = true;
|
|
||||||
} else {
|
|
||||||
this.conversationHistory.push({
|
|
||||||
role: "user",
|
|
||||||
content: [{
|
|
||||||
type: "text",
|
|
||||||
text: "You must either use available tools to accomplish the task or call attempt_completion when the task is complete."
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeTools(message: string): Promise<ToolResult[]> {
|
|
||||||
const results: ToolResult[] = [];
|
|
||||||
const toolRegex = /<(\w+)>\s*([\s\S]*?)\s*<\/\1>/g;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = toolRegex.exec(message)) !== null) {
|
|
||||||
const [_, toolName, paramsXml] = match;
|
|
||||||
const params: Record<string, string> = {};
|
|
||||||
const paramRegex = /<(\w+)>\s*([\s\S]*?)\s*<\/\1>/g;
|
|
||||||
let paramMatch;
|
|
||||||
|
|
||||||
while ((paramMatch = paramRegex.exec(paramsXml)) !== null) {
|
|
||||||
const [__, paramName, paramValue] = paramMatch;
|
|
||||||
params[paramName] = paramValue.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
let output: string;
|
|
||||||
try {
|
|
||||||
console.log(yellow(`\nExecuting: ${this.getToolDescription(toolName, params)}`));
|
|
||||||
|
|
||||||
switch (toolName) {
|
|
||||||
case "execute_command":
|
|
||||||
output = await executeCommand(params.command);
|
|
||||||
break;
|
|
||||||
case "read_file":
|
|
||||||
output = await readFile(this.workingDir, params.path);
|
|
||||||
break;
|
|
||||||
case "write_to_file":
|
|
||||||
output = await writeFile(this.workingDir, params.path, params.content);
|
|
||||||
break;
|
|
||||||
case "search_files":
|
|
||||||
output = await searchFiles(this.workingDir, params.path, params.regex, params.file_pattern);
|
|
||||||
break;
|
|
||||||
case "list_files":
|
|
||||||
output = await listFiles(this.workingDir, params.path, params.recursive === "true");
|
|
||||||
break;
|
|
||||||
case "list_code_definition_names":
|
|
||||||
output = await listCodeDefinitions(this.workingDir, params.path);
|
|
||||||
break;
|
|
||||||
case "attempt_completion":
|
|
||||||
return results;
|
|
||||||
default:
|
|
||||||
console.warn(red(`Unknown tool: ${toolName}`));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
tool: toolName,
|
|
||||||
params,
|
|
||||||
output: output || "(No output)"
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = `Error executing ${toolName}: ${error instanceof Error ? error.message : String(error)}`;
|
|
||||||
console.error(red(errorMessage));
|
|
||||||
results.push({
|
|
||||||
tool: toolName,
|
|
||||||
params,
|
|
||||||
output: errorMessage
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getToolDescription(toolName: string, params: Record<string, string>): string {
|
|
||||||
switch (toolName) {
|
|
||||||
case "execute_command":
|
|
||||||
return `Running command: ${params.command}`;
|
|
||||||
case "read_file":
|
|
||||||
return `Reading file: ${params.path}`;
|
|
||||||
case "write_to_file":
|
|
||||||
return `Writing to file: ${params.path}`;
|
|
||||||
case "search_files":
|
|
||||||
return `Searching for "${params.regex}" in ${params.path}`;
|
|
||||||
case "list_files":
|
|
||||||
return `Listing files in ${params.path}`;
|
|
||||||
case "list_code_definition_names":
|
|
||||||
return `Analyzing code in ${params.path}`;
|
|
||||||
case "attempt_completion":
|
|
||||||
return "Completing task";
|
|
||||||
default:
|
|
||||||
return toolName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
import { join } from "https://deno.land/std@0.220.1/path/mod.ts";
|
|
||||||
|
|
||||||
export const SYSTEM_PROMPT = async (cwd: string): Promise<string> => {
|
|
||||||
let rulesContent = "";
|
|
||||||
|
|
||||||
// Load and combine rules from configuration files
|
|
||||||
const ruleFiles = ['.clinerules', '.cursorrules'];
|
|
||||||
for (const file of ruleFiles) {
|
|
||||||
const rulePath = join(cwd, file);
|
|
||||||
try {
|
|
||||||
const stat = await Deno.stat(rulePath);
|
|
||||||
if (stat.isFile) {
|
|
||||||
const content = await Deno.readTextFile(rulePath);
|
|
||||||
if (content.trim()) {
|
|
||||||
rulesContent += `\n# Rules from ${file}:\n${content.trim()}\n\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// Only ignore ENOENT (file not found) errors
|
|
||||||
if (!(err instanceof Deno.errors.NotFound)) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
|
||||||
|
|
||||||
====
|
|
||||||
|
|
||||||
TOOL USE
|
|
||||||
|
|
||||||
You have access to tools that are executed upon approval. Use one tool per message and wait for the result before proceeding. Each tool must be used with proper XML-style formatting:
|
|
||||||
|
|
||||||
<tool_name>
|
|
||||||
<parameter1_name>value1</parameter1_name>
|
|
||||||
<parameter2_name>value2</parameter2_name>
|
|
||||||
</tool_name>
|
|
||||||
|
|
||||||
# Available Tools
|
|
||||||
|
|
||||||
## execute_command
|
|
||||||
Description: Execute a CLI command on the system. Commands run in the current working directory: ${cwd}
|
|
||||||
Parameters:
|
|
||||||
- command: (required) The command to execute. Must be valid for the current OS.
|
|
||||||
Usage:
|
|
||||||
<execute_command>
|
|
||||||
<command>command to run</command>
|
|
||||||
</execute_command>
|
|
||||||
|
|
||||||
## read_file
|
|
||||||
Description: Read contents of a file. Supports text files and automatically extracts content from PDFs/DOCXs.
|
|
||||||
Parameters:
|
|
||||||
- path: (required) Path to file (relative to ${cwd})
|
|
||||||
Usage:
|
|
||||||
<read_file>
|
|
||||||
<path>path to file</path>
|
|
||||||
</read_file>
|
|
||||||
|
|
||||||
## write_to_file
|
|
||||||
Description: Write content to a file. Creates directories as needed. Will overwrite existing files.
|
|
||||||
Parameters:
|
|
||||||
- path: (required) Path to write to (relative to ${cwd})
|
|
||||||
- content: (required) Complete file content. Must include ALL parts, even unchanged sections.
|
|
||||||
Usage:
|
|
||||||
<write_to_file>
|
|
||||||
<path>path to file</path>
|
|
||||||
<content>complete file content</content>
|
|
||||||
</write_to_file>
|
|
||||||
|
|
||||||
## search_files
|
|
||||||
Description: Search files using regex patterns. Shows matches with surrounding context.
|
|
||||||
Parameters:
|
|
||||||
- path: (required) Directory to search (relative to ${cwd})
|
|
||||||
- regex: (required) Rust regex pattern to search for
|
|
||||||
- file_pattern: (optional) Glob pattern to filter files (e.g. "*.ts")
|
|
||||||
Usage:
|
|
||||||
<search_files>
|
|
||||||
<path>directory to search</path>
|
|
||||||
<regex>pattern to search</regex>
|
|
||||||
<file_pattern>optional file pattern</file_pattern>
|
|
||||||
</search_files>
|
|
||||||
|
|
||||||
## list_code_definition_names
|
|
||||||
Description: List code definitions (classes, functions, etc.) in source files.
|
|
||||||
Parameters:
|
|
||||||
- path: (required) Directory to analyze (relative to ${cwd})
|
|
||||||
Usage:
|
|
||||||
<list_code_definition_names>
|
|
||||||
<path>directory to analyze</path>
|
|
||||||
</list_code_definition_names>
|
|
||||||
|
|
||||||
## attempt_completion
|
|
||||||
Description: Signal task completion and present results.
|
|
||||||
Parameters:
|
|
||||||
- result: (required) Description of completed work
|
|
||||||
- command: (optional) Command to demonstrate result
|
|
||||||
Usage:
|
|
||||||
<attempt_completion>
|
|
||||||
<result>description of completed work</result>
|
|
||||||
<command>optional demo command</command>
|
|
||||||
</attempt_completion>
|
|
||||||
|
|
||||||
# Guidelines
|
|
||||||
|
|
||||||
1. Use one tool at a time and wait for results
|
|
||||||
2. Provide complete file content when using write_to_file
|
|
||||||
3. Be direct and technical in responses
|
|
||||||
4. Present final results using attempt_completion
|
|
||||||
5. Do not make assumptions about command success
|
|
||||||
6. Do not make up commands that don't exist
|
|
||||||
|
|
||||||
# Rules
|
|
||||||
|
|
||||||
- Current working directory is: ${cwd}
|
|
||||||
- Cannot cd to different directories
|
|
||||||
- Must wait for confirmation after each tool use
|
|
||||||
- Must provide complete file content when writing files
|
|
||||||
- Be direct and technical, not conversational
|
|
||||||
- Do not end messages with questions${rulesContent}`;
|
|
||||||
};
|
|
||||||
40
cli/deno.d.ts
vendored
40
cli/deno.d.ts
vendored
@@ -1,40 +0,0 @@
|
|||||||
declare namespace Deno {
|
|
||||||
export const args: string[];
|
|
||||||
export function exit(code?: number): never;
|
|
||||||
export const env: {
|
|
||||||
get(key: string): string | undefined;
|
|
||||||
};
|
|
||||||
export function cwd(): string;
|
|
||||||
export function readTextFile(path: string): Promise<string>;
|
|
||||||
export function writeTextFile(path: string, data: string): Promise<void>;
|
|
||||||
export function mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
||||||
export function readDir(path: string): AsyncIterable<{
|
|
||||||
name: string;
|
|
||||||
isFile: boolean;
|
|
||||||
isDirectory: boolean;
|
|
||||||
}>;
|
|
||||||
export function stat(path: string): Promise<{
|
|
||||||
isFile: boolean;
|
|
||||||
isDirectory: boolean;
|
|
||||||
}>;
|
|
||||||
export class Command {
|
|
||||||
constructor(cmd: string, options?: {
|
|
||||||
args?: string[];
|
|
||||||
stdout?: "piped";
|
|
||||||
stderr?: "piped";
|
|
||||||
});
|
|
||||||
output(): Promise<{
|
|
||||||
stdout: Uint8Array;
|
|
||||||
stderr: Uint8Array;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
export const permissions: {
|
|
||||||
query(desc: { name: string; path?: string }): Promise<{ state: "granted" | "denied" }>;
|
|
||||||
};
|
|
||||||
export const errors: {
|
|
||||||
PermissionDenied: typeof Error;
|
|
||||||
};
|
|
||||||
export const stdout: {
|
|
||||||
write(data: Uint8Array): Promise<number>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"strict": true,
|
|
||||||
"lib": ["deno.ns", "dom"]
|
|
||||||
},
|
|
||||||
"tasks": {
|
|
||||||
"start": "deno run --allow-read=. mod.ts",
|
|
||||||
"dev": "deno run --allow-read=. mod.ts",
|
|
||||||
"install": "deno install --allow-read --allow-write --allow-net --allow-env --allow-run --global --name cline mod.ts",
|
|
||||||
"check": "deno check mod.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
87
cli/deno.lock
generated
87
cli/deno.lock
generated
@@ -1,87 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "4",
|
|
||||||
"remote": {
|
|
||||||
"https://deno.land/std@0.220.1/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5",
|
|
||||||
"https://deno.land/std@0.220.1/assert/assert_exists.ts": "24a7bf965e634f909242cd09fbaf38bde6b791128ece08e33ab08586a7cc55c9",
|
|
||||||
"https://deno.land/std@0.220.1/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8",
|
|
||||||
"https://deno.land/std@0.220.1/flags/mod.ts": "9f13f3a49c54618277ac49195af934f1c7d235731bcf80fd33b8b234e6839ce9",
|
|
||||||
"https://deno.land/std@0.220.1/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a",
|
|
||||||
"https://deno.land/std@0.220.1/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883",
|
|
||||||
"https://deno.land/std@0.220.1/path/_interface.ts": "a1419fcf45c0ceb8acdccc94394e3e94f99e18cfd32d509aab514c8841799600",
|
|
||||||
"https://deno.land/std@0.220.1/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
|
|
||||||
"https://deno.land/std@0.220.1/path/basename.ts": "5d341aadb7ada266e2280561692c165771d071c98746fcb66da928870cd47668",
|
|
||||||
"https://deno.land/std@0.220.1/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643",
|
|
||||||
"https://deno.land/std@0.220.1/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36",
|
|
||||||
"https://deno.land/std@0.220.1/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c",
|
|
||||||
"https://deno.land/std@0.220.1/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441",
|
|
||||||
"https://deno.land/std@0.220.1/path/format.ts": "42a2f3201343df77061207e6aaf78c95bafce7f711dcb7fe1e5840311c505778",
|
|
||||||
"https://deno.land/std@0.220.1/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069",
|
|
||||||
"https://deno.land/std@0.220.1/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972",
|
|
||||||
"https://deno.land/std@0.220.1/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7",
|
|
||||||
"https://deno.land/std@0.220.1/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141",
|
|
||||||
"https://deno.land/std@0.220.1/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a",
|
|
||||||
"https://deno.land/std@0.220.1/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0",
|
|
||||||
"https://deno.land/std@0.220.1/path/mod.ts": "2821a1bb3a4148a0ffe79c92aa41aa9319fef73c6d6f5178f52b2c720d3eb02d",
|
|
||||||
"https://deno.land/std@0.220.1/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352",
|
|
||||||
"https://deno.land/std@0.220.1/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f",
|
|
||||||
"https://deno.land/std@0.220.1/path/parse.ts": "65e8e285f1a63b714e19ef24b68f56e76934c3df0b6e65fd440d3991f4f8aefb",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/parse.ts": "0b1fc4cb890dbb699ec1d2c232d274843b4a7142e1ad976b69fe51c954eb6080",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf",
|
|
||||||
"https://deno.land/std@0.220.1/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0",
|
|
||||||
"https://deno.land/std@0.220.1/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add",
|
|
||||||
"https://deno.land/std@0.220.1/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d",
|
|
||||||
"https://deno.land/std@0.220.1/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b",
|
|
||||||
"https://deno.land/std@0.220.1/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/basename.ts": "e2dbf31d1d6385bfab1ce38c333aa290b6d7ae9e0ecb8234a654e583cf22f8fe",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/parse.ts": "dbdfe2bc6db482d755b5f63f7207cd019240fcac02ad2efa582adf67ff10553a",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e",
|
|
||||||
"https://deno.land/std@0.220.1/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
cli/deps.ts
21
cli/deps.ts
@@ -1,21 +0,0 @@
|
|||||||
// Re-export standard library dependencies
|
|
||||||
export { parse } from "https://deno.land/std@0.220.1/flags/mod.ts";
|
|
||||||
export {
|
|
||||||
blue,
|
|
||||||
red,
|
|
||||||
gray,
|
|
||||||
yellow,
|
|
||||||
bold,
|
|
||||||
} from "https://deno.land/std@0.220.1/fmt/colors.ts";
|
|
||||||
export {
|
|
||||||
join,
|
|
||||||
dirname,
|
|
||||||
} from "https://deno.land/std@0.220.1/path/mod.ts";
|
|
||||||
|
|
||||||
// Export types
|
|
||||||
export type {
|
|
||||||
ApiHandler,
|
|
||||||
AgentConfig,
|
|
||||||
OperationMode,
|
|
||||||
ToolResponse,
|
|
||||||
} from "./types.d.ts";
|
|
||||||
123
cli/mod.ts
123
cli/mod.ts
@@ -1,123 +0,0 @@
|
|||||||
#!/usr/bin/env -S deno run --allow-read=. --allow-write=. --allow-run --allow-net --allow-env
|
|
||||||
|
|
||||||
import { parse } from "./deps.ts";
|
|
||||||
import { blue, red, gray, yellow, bold } from "./deps.ts";
|
|
||||||
import { buildApiHandler } from "./api/mod.ts";
|
|
||||||
import { StandaloneAgent } from "./core/StandaloneAgent.ts";
|
|
||||||
import { SYSTEM_PROMPT } from "./core/prompts.ts";
|
|
||||||
import type { ApiHandler, AgentConfig } from "./types.d.ts";
|
|
||||||
|
|
||||||
// Parse command line arguments
|
|
||||||
const args = parse(Deno.args, {
|
|
||||||
string: ["model", "key"],
|
|
||||||
boolean: ["help"],
|
|
||||||
alias: {
|
|
||||||
m: "model",
|
|
||||||
k: "key",
|
|
||||||
h: "help"
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
model: "anthropic/claude-3.5-sonnet"
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (args.help || Deno.args.length === 0) {
|
|
||||||
console.log(blue("\nCline - AI Coding Assistant\n"));
|
|
||||||
|
|
||||||
console.log("Usage:");
|
|
||||||
console.log(" cline <task> [options]\n");
|
|
||||||
|
|
||||||
console.log("Required Permissions:");
|
|
||||||
console.log(" --allow-read=. Read files in working directory");
|
|
||||||
console.log(" --allow-write=. Write files in working directory");
|
|
||||||
console.log(" --allow-run Execute commands (with interactive prompts)\n");
|
|
||||||
console.log(" --allow-net Make API calls");
|
|
||||||
console.log(" --allow-env Access environment variables\n");
|
|
||||||
|
|
||||||
console.log("Pre-approved Commands:");
|
|
||||||
console.log(" npm - Package management (install, run, test, build)");
|
|
||||||
console.log(" git - Version control (status, add, commit, push, pull, clone)");
|
|
||||||
console.log(" deno - Deno runtime (run, test, fmt, lint, check)");
|
|
||||||
console.log(" ls - List directory contents");
|
|
||||||
console.log(" cat - Show file contents");
|
|
||||||
console.log(" echo - Print text");
|
|
||||||
console.log(" find - Search for files");
|
|
||||||
console.log("\nOther commands will prompt for confirmation before execution.\n");
|
|
||||||
|
|
||||||
console.log("Options:");
|
|
||||||
console.log(" -m, --model <model> LLM model to use (default: \"anthropic/claude-3.5-sonnet\")");
|
|
||||||
console.log(" -k, --key <key> OpenRouter API key (or set OPENROUTER_API_KEY env var)");
|
|
||||||
console.log(" -h, --help Display help for command\n");
|
|
||||||
|
|
||||||
console.log("Examples:");
|
|
||||||
console.log(gray(" # Run pre-approved command"));
|
|
||||||
console.log(" cline \"Run npm install\"\n");
|
|
||||||
|
|
||||||
console.log(gray(" # Run command that requires confirmation"));
|
|
||||||
console.log(" cline \"Run yarn install\"\n");
|
|
||||||
|
|
||||||
Deno.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify required permissions
|
|
||||||
const requiredPermissions = [
|
|
||||||
{ name: "read", path: "." },
|
|
||||||
{ name: "write", path: "." },
|
|
||||||
{ name: "run" },
|
|
||||||
{ name: "net" },
|
|
||||||
{ name: "env" }
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
for (const permission of requiredPermissions) {
|
|
||||||
const status = await Deno.permissions.query(permission);
|
|
||||||
if (status.state !== "granted") {
|
|
||||||
console.error(red(`Error: Missing required permission`));
|
|
||||||
console.error(yellow(`Hint: Run with the following permissions:`));
|
|
||||||
console.error(yellow(` deno run ${requiredPermissions.map(p =>
|
|
||||||
"path" in p ? `--allow-${p.name}=${p.path}` : `--allow-${p.name}`
|
|
||||||
).join(" ")} cli/mod.ts ...\n`));
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = args._[0] as string;
|
|
||||||
const apiKey = args.key || Deno.env.get("OPENROUTER_API_KEY");
|
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
console.error(red("Error: OpenRouter API key is required. Set it with --key or OPENROUTER_API_KEY env var"));
|
|
||||||
console.error(yellow("Get your API key from: https://openrouter.ai/keys"));
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const workingDir = Deno.cwd();
|
|
||||||
|
|
||||||
// Initialize API handler
|
|
||||||
const apiHandler = buildApiHandler({
|
|
||||||
model: args.model,
|
|
||||||
apiKey
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create agent instance
|
|
||||||
const agent = new StandaloneAgent({
|
|
||||||
api: apiHandler,
|
|
||||||
systemPrompt: await SYSTEM_PROMPT(workingDir),
|
|
||||||
workingDir
|
|
||||||
});
|
|
||||||
|
|
||||||
// Run the task
|
|
||||||
console.log(blue(`\nStarting task: ${bold(task)}`));
|
|
||||||
console.log(gray(`Working directory: ${workingDir}`));
|
|
||||||
console.log(gray(`Model: ${args.model}`));
|
|
||||||
console.log(gray("---\n"));
|
|
||||||
|
|
||||||
await agent.runTask(task);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.error(red(`\nError: ${error.message}`));
|
|
||||||
} else {
|
|
||||||
console.error(red("\nAn unknown error occurred"));
|
|
||||||
}
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
225
cli/tools/mod.ts
225
cli/tools/mod.ts
@@ -1,225 +0,0 @@
|
|||||||
/// <reference lib="deno.ns" />
|
|
||||||
import { join, dirname } from "https://deno.land/std@0.220.1/path/mod.ts";
|
|
||||||
import { red, yellow, green } from "https://deno.land/std@0.220.1/fmt/colors.ts";
|
|
||||||
import type { ToolResponse } from "../types.d.ts";
|
|
||||||
|
|
||||||
interface CommandConfig {
|
|
||||||
desc: string;
|
|
||||||
args: readonly string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define allowed commands and their descriptions
|
|
||||||
const ALLOWED_COMMANDS: Record<string, CommandConfig> = {
|
|
||||||
'npm': {
|
|
||||||
desc: "Node package manager",
|
|
||||||
args: ["install", "run", "test", "build"]
|
|
||||||
},
|
|
||||||
'git': {
|
|
||||||
desc: "Version control",
|
|
||||||
args: ["status", "add", "commit", "push", "pull", "clone", "checkout", "branch"]
|
|
||||||
},
|
|
||||||
'deno': {
|
|
||||||
desc: "Deno runtime",
|
|
||||||
args: ["run", "test", "fmt", "lint", "check", "compile", "bundle"]
|
|
||||||
},
|
|
||||||
'ls': {
|
|
||||||
desc: "List directory contents",
|
|
||||||
args: ["-l", "-a", "-la", "-lh"]
|
|
||||||
},
|
|
||||||
'cat': {
|
|
||||||
desc: "Show file contents",
|
|
||||||
args: []
|
|
||||||
},
|
|
||||||
'echo': {
|
|
||||||
desc: "Print text",
|
|
||||||
args: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Track commands that have been allowed for this session
|
|
||||||
const alwaysAllowedCommands = new Set<string>();
|
|
||||||
|
|
||||||
function isCommandAllowed(command: string): boolean {
|
|
||||||
// Split command into parts
|
|
||||||
const parts = command.trim().split(/\s+/);
|
|
||||||
if (parts.length === 0) return false;
|
|
||||||
|
|
||||||
// Get base command
|
|
||||||
const baseCmd = parts[0];
|
|
||||||
if (!(baseCmd in ALLOWED_COMMANDS)) return false;
|
|
||||||
|
|
||||||
// If command has arguments, check if they're allowed
|
|
||||||
if (parts.length > 1 && ALLOWED_COMMANDS[baseCmd].args.length > 0) {
|
|
||||||
const arg = parts[1];
|
|
||||||
return ALLOWED_COMMANDS[baseCmd].args.includes(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function promptForCommand(command: string): Promise<boolean> {
|
|
||||||
// Check if command has been previously allowed
|
|
||||||
if (alwaysAllowedCommands.has(command)) {
|
|
||||||
console.log(yellow("\nWarning: Running previously allowed command:"), red(command));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(yellow("\nWarning: Command not in allowlist"));
|
|
||||||
console.log("Command:", red(command));
|
|
||||||
console.log("\nAllowed commands:");
|
|
||||||
Object.entries(ALLOWED_COMMANDS).forEach(([cmd, { desc, args }]) => {
|
|
||||||
console.log(` ${green(cmd)}: ${desc}`);
|
|
||||||
if (args.length) {
|
|
||||||
console.log(` Arguments: ${args.join(", ")}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const answer = prompt("\nDo you want to run this command? (y/n/always) ");
|
|
||||||
if (answer?.toLowerCase() === 'always') {
|
|
||||||
alwaysAllowedCommands.add(command);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return answer?.toLowerCase() === 'y';
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function executeCommand(command: string): Promise<ToolResponse> {
|
|
||||||
try {
|
|
||||||
// Check if command is allowed
|
|
||||||
if (!isCommandAllowed(command)) {
|
|
||||||
// Prompt user for confirmation
|
|
||||||
const shouldRun = await promptForCommand(command);
|
|
||||||
if (!shouldRun) {
|
|
||||||
return "Command execution cancelled by user";
|
|
||||||
}
|
|
||||||
console.log(yellow("\nProceeding with command execution..."));
|
|
||||||
}
|
|
||||||
|
|
||||||
const process = new Deno.Command("sh", {
|
|
||||||
args: ["-c", command],
|
|
||||||
stdout: "piped",
|
|
||||||
stderr: "piped",
|
|
||||||
});
|
|
||||||
const { stdout, stderr } = await process.output();
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
return decoder.decode(stdout) + (stderr.length ? `\nStderr:\n${decoder.decode(stderr)}` : "");
|
|
||||||
} catch (error) {
|
|
||||||
return `Error executing command: ${error instanceof Error ? error.message : String(error)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readFile(workingDir: string, relativePath: string): Promise<ToolResponse> {
|
|
||||||
try {
|
|
||||||
const fullPath = join(workingDir, relativePath);
|
|
||||||
const content = await Deno.readTextFile(fullPath);
|
|
||||||
return content;
|
|
||||||
} catch (error) {
|
|
||||||
return `Error reading file: ${error instanceof Error ? error.message : String(error)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function writeFile(workingDir: string, relativePath: string, content: string): Promise<ToolResponse> {
|
|
||||||
try {
|
|
||||||
const fullPath = join(workingDir, relativePath);
|
|
||||||
await Deno.mkdir(dirname(fullPath), { recursive: true });
|
|
||||||
await Deno.writeTextFile(fullPath, content);
|
|
||||||
return `Successfully wrote to ${relativePath}`;
|
|
||||||
} catch (error) {
|
|
||||||
return `Error writing file: ${error instanceof Error ? error.message : String(error)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function searchFiles(
|
|
||||||
workingDir: string,
|
|
||||||
searchPath: string,
|
|
||||||
regex: string,
|
|
||||||
filePattern?: string
|
|
||||||
): Promise<ToolResponse> {
|
|
||||||
try {
|
|
||||||
const fullPath = join(workingDir, searchPath);
|
|
||||||
const results: string[] = [];
|
|
||||||
|
|
||||||
const regexObj = new RegExp(regex, "g");
|
|
||||||
const patternObj = filePattern ? new RegExp(filePattern) : null;
|
|
||||||
|
|
||||||
for await (const entry of Deno.readDir(fullPath)) {
|
|
||||||
if (entry.isFile && (!patternObj || patternObj.test(entry.name))) {
|
|
||||||
const filePath = join(fullPath, entry.name);
|
|
||||||
const content = await Deno.readTextFile(filePath);
|
|
||||||
const matches = content.match(regexObj);
|
|
||||||
if (matches) {
|
|
||||||
results.push(`File: ${entry.name}\nMatches:\n${matches.join("\n")}\n`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results.join("\n") || "No matches found";
|
|
||||||
} catch (error) {
|
|
||||||
return `Error searching files: ${error instanceof Error ? error.message : String(error)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listFiles(workingDir: string, relativePath: string, recursive: boolean): Promise<ToolResponse> {
|
|
||||||
try {
|
|
||||||
const fullPath = join(workingDir, relativePath);
|
|
||||||
const files: string[] = [];
|
|
||||||
|
|
||||||
async function* walkDir(dir: string): AsyncGenerator<string> {
|
|
||||||
for await (const entry of Deno.readDir(dir)) {
|
|
||||||
const entryPath = join(dir, entry.name);
|
|
||||||
if (entry.isFile) {
|
|
||||||
yield entryPath.replace(fullPath + "/", "");
|
|
||||||
} else if (recursive && entry.isDirectory) {
|
|
||||||
yield* walkDir(entryPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const file of walkDir(fullPath)) {
|
|
||||||
files.push(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
return files.join("\n") || "No files found";
|
|
||||||
} catch (error) {
|
|
||||||
return `Error listing files: ${error instanceof Error ? error.message : String(error)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listCodeDefinitions(workingDir: string, relativePath: string): Promise<ToolResponse> {
|
|
||||||
try {
|
|
||||||
const fullPath = join(workingDir, relativePath);
|
|
||||||
const content = await Deno.readTextFile(fullPath);
|
|
||||||
|
|
||||||
// Basic regex patterns for common code definitions
|
|
||||||
const patterns = {
|
|
||||||
function: /(?:function|const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:=\s*(?:function|\([^)]*\)\s*=>)|[({])/g,
|
|
||||||
class: /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
|
|
||||||
method: /(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\([^)]*\)\s*{/g,
|
|
||||||
};
|
|
||||||
|
|
||||||
const definitions: Record<string, string[]> = {
|
|
||||||
functions: [],
|
|
||||||
classes: [],
|
|
||||||
methods: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = patterns.function.exec(content)) !== null) {
|
|
||||||
definitions.functions.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((match = patterns.class.exec(content)) !== null) {
|
|
||||||
definitions.classes.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((match = patterns.method.exec(content)) !== null) {
|
|
||||||
definitions.methods.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.entries(definitions)
|
|
||||||
.map(([type, names]) => `${type}:\n${names.join("\n")}`)
|
|
||||||
.join("\n\n");
|
|
||||||
} catch (error) {
|
|
||||||
return `Error listing code definitions: ${error instanceof Error ? error.message : String(error)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
43
cli/types.d.ts
vendored
43
cli/types.d.ts
vendored
@@ -1,43 +0,0 @@
|
|||||||
export interface ApiHandler {
|
|
||||||
sendMessage(message: string): Promise<string>;
|
|
||||||
createMessage(systemPrompt: string, history: Message[]): AsyncIterable<MessageChunk>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentConfig {
|
|
||||||
api: ApiHandler;
|
|
||||||
systemPrompt: string;
|
|
||||||
workingDir: string;
|
|
||||||
debug?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ToolResponse = string;
|
|
||||||
|
|
||||||
export interface Message {
|
|
||||||
role: "user" | "assistant";
|
|
||||||
content: TextBlock[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TextBlock {
|
|
||||||
type: "text";
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolResult {
|
|
||||||
tool: string;
|
|
||||||
params: Record<string, string>;
|
|
||||||
output: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageChunk {
|
|
||||||
type: "text";
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UsageBlock {
|
|
||||||
type: "usage";
|
|
||||||
usage: {
|
|
||||||
prompt_tokens: number;
|
|
||||||
completion_tokens: number;
|
|
||||||
total_tokens: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user