1+ #!/usr/bin/env node
2+ import * as path from 'path' ;
3+ import * as dotenv from 'dotenv' ;
4+ import { spawn } from 'child_process' ;
5+ import {
6+ ApiConfiguration ,
7+ createApiClient ,
8+ BaseToolExecutor ,
9+ CliContextProvider ,
10+ MessageParser ,
11+ AVAILABLE_TOOLS ,
12+ ToolResponse
13+ } from './lib' ;
14+
15+ dotenv . config ( ) ;
16+
17+ class CliToolExecutor extends BaseToolExecutor {
18+ async executeCommand ( command : string ) : Promise < [ boolean , ToolResponse ] > {
19+ return new Promise ( ( resolve ) => {
20+ const process = spawn ( command , [ ] , {
21+ shell : true ,
22+ cwd : this . getCurrentWorkingDirectory ( )
23+ } ) ;
24+
25+ let output = '' ;
26+ let error = '' ;
27+
28+ process . stdout . on ( 'data' , ( data ) => {
29+ output += data . toString ( ) ;
30+ console . log ( data . toString ( ) ) ;
31+ } ) ;
32+
33+ process . stderr . on ( 'data' , ( data ) => {
34+ error += data . toString ( ) ;
35+ console . error ( data . toString ( ) ) ;
36+ } ) ;
37+
38+ process . on ( 'close' , ( code ) => {
39+ if ( code !== 0 ) {
40+ resolve ( [ true , 'Command failed with code ' + code + '\n' + error ] ) ;
41+ } else {
42+ resolve ( [ false , output ] ) ;
43+ }
44+ } ) ;
45+ } ) ;
46+ }
47+
48+ getCurrentWorkingDirectory ( ) : string {
49+ return process . cwd ( ) ;
50+ }
51+ }
52+
53+ async function main ( ) {
54+ const args = process . argv . slice ( 2 ) ;
55+ const task = args [ 0 ] ;
56+
57+ if ( ! task ) {
58+ console . error ( 'Usage: cline "My Task" [--tools]' ) ;
59+ process . exit ( 1 ) ;
60+ }
61+
62+ const apiKey = process . env . OPENROUTER_API_KEY ;
63+ if ( ! apiKey ) {
64+ console . error ( 'Error: OPENROUTER_API_KEY environment variable is required' ) ;
65+ process . exit ( 1 ) ;
66+ }
67+
68+ const apiConfiguration : ApiConfiguration = {
69+ apiKey,
70+ apiModelId : 'anthropic/claude-3-sonnet-20240229' ,
71+ apiProvider : 'openrouter' ,
72+ } ;
73+
74+ const cwd = process . cwd ( ) ;
75+ const toolExecutor = new CliToolExecutor ( cwd ) ;
76+ const contextProvider = new CliContextProvider ( cwd ) ;
77+ const messageParser = new MessageParser ( AVAILABLE_TOOLS ) ;
78+ const apiClient = createApiClient ( apiConfiguration ) ;
79+
80+ try {
81+ const envDetails = await contextProvider . getEnvironmentDetails ( true ) ;
82+ const toolDocs = AVAILABLE_TOOLS . map ( tool => {
83+ const params = Object . entries ( tool . parameters )
84+ . map ( ( [ name , param ] ) => '- ' + name + ': (' + ( param . required ? 'required' : 'optional' ) + ') ' + param . description )
85+ . join ( '\n' ) ;
86+ return '## ' + tool . name + '\nDescription: ' + tool . description + '\nParameters:\n' + params ;
87+ } ) . join ( '\n\n' ) ;
88+
89+ const systemPromptParts = [
90+ 'You are Cline, a highly skilled software engineer.' ,
91+ '' ,
92+ 'TOOLS' ,
93+ '' ,
94+ 'You have access to the following tools that must be used with XML tags:' ,
95+ '' ,
96+ toolDocs ,
97+ '' ,
98+ 'RULES' ,
99+ '' ,
100+ '1. Use one tool at a time' ,
101+ '2. Wait for tool execution results before proceeding' ,
102+ '3. Handle errors appropriately' ,
103+ '4. Document your changes' ,
104+ '' ,
105+ 'TASK' ,
106+ '' ,
107+ task
108+ ] ;
109+
110+ const systemPrompt = systemPromptParts . join ( '\n' ) ;
111+ const history : Anthropic . MessageParam [ ] = [
112+ { role : 'user' , content : `<task>${ task } </task><environment_details>${ envDetails } </environment_details>` }
113+ ] ;
114+
115+ console . log ( 'Sending request to API...' ) ;
116+ for await ( const chunk of apiClient . createMessage ( systemPrompt , history ) ) {
117+ if ( chunk . type === 'text' && chunk . text ) {
118+ console . log ( 'Received text:' , chunk . text ) ;
119+ const toolUse = messageParser . parseToolUse ( chunk . text ) ;
120+ if ( toolUse ) {
121+ console . log ( 'Parsed tool use:' , toolUse ) ;
122+ }
123+ if ( toolUse ) {
124+ let error = false ;
125+ let result = '' ;
126+
127+ switch ( toolUse . name ) {
128+ case 'write_to_file' :
129+ [ error , result ] = await toolExecutor . writeFile (
130+ toolUse . params . path ,
131+ toolUse . params . content ,
132+ parseInt ( toolUse . params . line_count )
133+ ) ;
134+ break ;
135+ case 'read_file' :
136+ [ error , result ] = await toolExecutor . readFile ( toolUse . params . path ) ;
137+ break ;
138+ case 'list_files' :
139+ [ error , result ] = await toolExecutor . listFiles (
140+ toolUse . params . path ,
141+ toolUse . params . recursive === 'true'
142+ ) ;
143+ break ;
144+ case 'search_files' :
145+ [ error , result ] = await toolExecutor . searchFiles (
146+ toolUse . params . path ,
147+ toolUse . params . regex ,
148+ toolUse . params . file_pattern
149+ ) ;
150+ break ;
151+ case 'execute_command' :
152+ [ error , result ] = await toolExecutor . executeCommand ( toolUse . params . command ) ;
153+ break ;
154+ default :
155+ error = true ;
156+ result = `Unknown tool: ${ toolUse . name } ` ;
157+ }
158+ history . push (
159+ { role : 'assistant' , content : chunk . text } ,
160+ { role : 'user' , content : `[${ toolUse . name } ] Result: ${ result } ` }
161+ ) ;
162+ } else {
163+ console . log ( chunk . text ) ;
164+ }
165+ } else if ( chunk . type === 'usage' ) {
166+ // Log usage metrics if needed
167+ // console.log('Usage:', chunk);
168+ }
169+ }
170+ } catch ( error ) {
171+ console . error ( 'Error:' , error . message ) ;
172+ process . exit ( 1 ) ;
173+ }
174+ }
175+
176+ main ( ) . catch ( error => {
177+ console . error ( 'Fatal error:' , error ) ;
178+ process . exit ( 1 ) ;
179+ } ) ;
0 commit comments