@geoffai/coder 1.0.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/README.md +281 -0
- package/dist/cli.cjs +205 -0
- package/dist/cli.js +205 -0
- package/dist/index.cjs +182 -0
- package/dist/index.d.cts +1019 -0
- package/dist/index.d.ts +1019 -0
- package/dist/index.js +182 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# π Geoff Coder
|
|
2
|
+
|
|
3
|
+
A terminal-based AI coding agent built with **Bun.js** featuring an agentic loop with tool call support. This agent can read files, write code, execute commands, and iterate on tasks autonomously.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Agentic Loop**: Autonomous task execution with tool calling
|
|
8
|
+
- **System Reminders**: Context-aware reminders injected at key points (inspired by Claude Code)
|
|
9
|
+
- **OpenAI-Compatible API**: Works with any OpenAI-compatible inference API
|
|
10
|
+
- **Containerized**: Docker support for isolated execution
|
|
11
|
+
- **Tool Suite**: File operations, shell commands, code search, diff application
|
|
12
|
+
- **Streaming Support**: Real-time response streaming for verbose mode
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
The system is built around patterns observed in production coding agents like Claude Code, Cline, and OpenCode:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
20
|
+
β CLI Interface β
|
|
21
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
|
22
|
+
β Agent Loop β
|
|
23
|
+
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
|
|
24
|
+
β β Message βββββΆβ LLM Call βββββΆβ Tool β β
|
|
25
|
+
β β History β β (Stream) β β Execution β β
|
|
26
|
+
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
|
|
27
|
+
β β² β β
|
|
28
|
+
β β βββββββββββββββ β β
|
|
29
|
+
β βββββββββββ System βββββββββββββββ β
|
|
30
|
+
β β Reminders β β
|
|
31
|
+
β βββββββββββββββ β
|
|
32
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
|
|
33
|
+
β Tool Registry β
|
|
34
|
+
β read_file β write_file β execute_command β search_files β
|
|
35
|
+
β apply_diff β list_files β task_complete β
|
|
36
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Key Components
|
|
40
|
+
|
|
41
|
+
1. **Agent Loop** (`src/agent/loop.ts`): The core execution engine that:
|
|
42
|
+
- Manages conversation history
|
|
43
|
+
- Calls the LLM with tool definitions
|
|
44
|
+
- Handles tool call responses
|
|
45
|
+
- Injects system reminders at appropriate times
|
|
46
|
+
- Iterates until task completion
|
|
47
|
+
|
|
48
|
+
2. **System Reminders** (`src/prompts/system.ts`): Context-aware prompts injected at:
|
|
49
|
+
- Pre-user message (task focus)
|
|
50
|
+
- Post-tool result (verification reminders)
|
|
51
|
+
- Context refresh (iteration warnings)
|
|
52
|
+
- Task boundaries (completion prompts)
|
|
53
|
+
|
|
54
|
+
3. **Tool Registry** (`src/tools/registry.ts`): Extensible tool system with:
|
|
55
|
+
- File read/write operations
|
|
56
|
+
- Shell command execution
|
|
57
|
+
- Code search (grep)
|
|
58
|
+
- Diff application for surgical edits
|
|
59
|
+
- Task completion signaling
|
|
60
|
+
|
|
61
|
+
4. **API Client** (`src/agent/api-client.ts`): OpenAI-compatible client with:
|
|
62
|
+
- Streaming support
|
|
63
|
+
- Tool call accumulation
|
|
64
|
+
- Error handling and retries
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
### Prerequisites
|
|
69
|
+
|
|
70
|
+
- [Bun](https://bun.sh) v1.0+
|
|
71
|
+
- An OpenAI-compatible API key
|
|
72
|
+
|
|
73
|
+
### Local Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Clone or download the project
|
|
77
|
+
cd geoff-coder
|
|
78
|
+
|
|
79
|
+
# Install dependencies
|
|
80
|
+
bun install
|
|
81
|
+
|
|
82
|
+
# Set your API key
|
|
83
|
+
export OPENAI_API_KEY="your-api-key"
|
|
84
|
+
|
|
85
|
+
# Run the agent
|
|
86
|
+
bun run src/index.ts "Create a hello world Express server"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Docker Installation
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Build the image
|
|
93
|
+
docker build -t geoff-coder .
|
|
94
|
+
|
|
95
|
+
# Run interactively
|
|
96
|
+
docker run -it --rm \
|
|
97
|
+
-e OPENAI_API_KEY="your-api-key" \
|
|
98
|
+
-v $(pwd)/workspace:/workspace \
|
|
99
|
+
geoff-coder
|
|
100
|
+
|
|
101
|
+
# Or use docker-compose
|
|
102
|
+
export OPENAI_API_KEY="your-api-key"
|
|
103
|
+
docker compose up
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Usage
|
|
107
|
+
|
|
108
|
+
### Single Task Mode
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Execute a single task
|
|
112
|
+
bun run src/index.ts "Create a REST API with Express"
|
|
113
|
+
|
|
114
|
+
# With verbose output
|
|
115
|
+
bun run src/index.ts -v "Fix the bug in auth.ts"
|
|
116
|
+
|
|
117
|
+
# With custom model
|
|
118
|
+
bun run src/index.ts -m "gpt-4-turbo" "Write tests for utils"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Interactive Mode
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Start interactive REPL
|
|
125
|
+
bun run src/index.ts -i
|
|
126
|
+
|
|
127
|
+
# In the REPL
|
|
128
|
+
>>> Create a new React component for user profiles
|
|
129
|
+
>>> Add validation to the form
|
|
130
|
+
>>> exit
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### With Local LLM (Ollama)
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Using Ollama
|
|
137
|
+
bun run src/index.ts \
|
|
138
|
+
-u "http://localhost:11434/v1" \
|
|
139
|
+
-m "llama3" \
|
|
140
|
+
-k "not-needed" \
|
|
141
|
+
"Explain this codebase"
|
|
142
|
+
|
|
143
|
+
# Or with docker-compose
|
|
144
|
+
docker compose --profile local up
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### CLI Options
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
Usage: bun run src/index.ts [options] [prompt]
|
|
151
|
+
|
|
152
|
+
Options:
|
|
153
|
+
-h, --help Show help message
|
|
154
|
+
-v, --verbose Enable verbose output
|
|
155
|
+
-i, --interactive Run in interactive mode (REPL)
|
|
156
|
+
-m, --model <model> Model to use (default: gpt-4o)
|
|
157
|
+
-u, --base-url <url> API base URL
|
|
158
|
+
-k, --api-key <key> API key
|
|
159
|
+
-d, --dir <path> Working directory
|
|
160
|
+
--max-iterations <n> Maximum loop iterations (default: 20)
|
|
161
|
+
--max-tokens <n> Maximum tokens per response (default: 4096)
|
|
162
|
+
-t, --temperature <n> Temperature (default: 0.7)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Environment Variables
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
OPENAI_API_KEY # Required: Your API key
|
|
169
|
+
OPENAI_BASE_URL # Optional: API endpoint (default: https://api.openai.com/v1)
|
|
170
|
+
OPENAI_MODEL # Optional: Model name (default: gpt-4o)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Customization
|
|
174
|
+
|
|
175
|
+
### Adding Custom Tools
|
|
176
|
+
|
|
177
|
+
Extend the tool registry in `src/tools/registry.ts`:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Add to toolDefinitions array
|
|
181
|
+
{
|
|
182
|
+
type: "function",
|
|
183
|
+
function: {
|
|
184
|
+
name: "my_custom_tool",
|
|
185
|
+
description: "Description of what this tool does",
|
|
186
|
+
parameters: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: {
|
|
189
|
+
param1: { type: "string", description: "Parameter description" }
|
|
190
|
+
},
|
|
191
|
+
required: ["param1"]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Add handler in createToolRegistry function
|
|
197
|
+
handlers.set("my_custom_tool", async (args): Promise<ToolResult> => {
|
|
198
|
+
// Implementation
|
|
199
|
+
return { success: true, output: "Result" }
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Adding System Reminders
|
|
204
|
+
|
|
205
|
+
Extend the reminders in `src/prompts/system.ts`:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
systemReminders.push({
|
|
209
|
+
id: "my_reminder",
|
|
210
|
+
content: "<system-reminder>Your reminder text</system-reminder>",
|
|
211
|
+
injectionPoint: "post_tool_result",
|
|
212
|
+
priority: 5,
|
|
213
|
+
condition: (state) => state.iteration > 3
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Project-Specific Instructions
|
|
218
|
+
|
|
219
|
+
Create an `AGENTS.md` file in your project root with custom instructions that will be loaded by the agent.
|
|
220
|
+
|
|
221
|
+
## How It Works
|
|
222
|
+
|
|
223
|
+
### The Agentic Loop
|
|
224
|
+
|
|
225
|
+
1. **User Input**: User provides a task description
|
|
226
|
+
2. **System Prompt**: Base instructions + system reminders are assembled
|
|
227
|
+
3. **LLM Call**: Request sent to the model with tool definitions
|
|
228
|
+
4. **Response Processing**:
|
|
229
|
+
- If tool calls: Execute tools, add results to history, continue loop
|
|
230
|
+
- If text only: Check for completion indicators
|
|
231
|
+
5. **Iteration**: Loop continues until:
|
|
232
|
+
- `task_complete` tool is called
|
|
233
|
+
- Max iterations reached
|
|
234
|
+
- No more tool calls and response looks complete
|
|
235
|
+
|
|
236
|
+
### System Reminders (Key Innovation)
|
|
237
|
+
|
|
238
|
+
Inspired by Claude Code's `<system-reminder>` pattern, reminders are injected:
|
|
239
|
+
|
|
240
|
+
- **Pre-user message**: Focus on task, avoid unnecessary changes
|
|
241
|
+
- **Post-tool result**: Verify changes after file operations
|
|
242
|
+
- **Context refresh**: Warning after many iterations
|
|
243
|
+
- **Task boundary**: Prompt to use `task_complete`
|
|
244
|
+
|
|
245
|
+
This helps maintain agent focus and prevents drift in long-running tasks.
|
|
246
|
+
|
|
247
|
+
## Comparison with Similar Tools
|
|
248
|
+
|
|
249
|
+
| Feature | This Agent | Claude Code | Cline | OpenCode |
|
|
250
|
+
|---------|-----------|-------------|-------|----------|
|
|
251
|
+
| Open Source | β
| β | β
| β
|
|
|
252
|
+
| Provider Agnostic | β
| β | β
| β
|
|
|
253
|
+
| System Reminders | β
| β
| β | β |
|
|
254
|
+
| Containerized | β
| β | β | β |
|
|
255
|
+
| Runtime | Bun | Node | Node | Go/Bun |
|
|
256
|
+
| IDE Integration | β | β
| β
| β |
|
|
257
|
+
|
|
258
|
+
## Development
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Run in development mode (with auto-reload)
|
|
262
|
+
bun --watch run src/index.ts -i
|
|
263
|
+
|
|
264
|
+
# Type check
|
|
265
|
+
bun run tsc --noEmit
|
|
266
|
+
|
|
267
|
+
# Build for distribution
|
|
268
|
+
bun build src/index.ts --outdir dist --target bun
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT
|
|
274
|
+
|
|
275
|
+
## Acknowledgments
|
|
276
|
+
|
|
277
|
+
Architecture patterns inspired by:
|
|
278
|
+
- [Claude Code](https://claude.ai) - System reminder injection pattern
|
|
279
|
+
- [Cline](https://github.com/cline/cline) - Tool definitions and agentic loop
|
|
280
|
+
- [OpenCode](https://github.com/sst/opencode) - CLI design and provider abstraction
|
|
281
|
+
- [OpenAI Codex CLI](https://github.com/openai/codex) - Agent loop implementation
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';var C=require('fs/promises'),_=require('path'),child_process=require('child_process'),fs=require('fs'),os=require('os');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var C__namespace=/*#__PURE__*/_interopNamespace(C);var ___namespace=/*#__PURE__*/_interopNamespace(_);var _e={maxRetries:3,baseDelayMs:1e3,maxDelayMs:3e4,retryableStatuses:[429,500,502,503,504]},S=class{apiKey;baseUrl;model;retryConfig;constructor(e){this.apiKey=e.apiKey,this.baseUrl=e.baseUrl.replace(/\/$/,""),this.model=e.model,this.retryConfig={..._e,...e.retryConfig};}async sleep(e){return new Promise(s=>setTimeout(s,e))}calculateBackoff(e,s){if(s)return Math.min(s*1e3,this.retryConfig.maxDelayMs);let t=this.retryConfig.baseDelayMs*Math.pow(2,e),o=Math.random()*.3*t;return Math.min(t+o,this.retryConfig.maxDelayMs)}shouldRetry(e,s){return s<this.retryConfig.maxRetries&&this.retryConfig.retryableStatuses.includes(e)}async createCompletion(e,s,t={}){let o={model:this.model,messages:e,tools:s?.length?s:void 0,tool_choice:s?.length?"auto":void 0,stream:false,max_tokens:t.maxTokens??4096,temperature:t.temperature??.7},n=null;for(let i=0;i<=this.retryConfig.maxRetries;i++)try{let a=await fetch(`${this.baseUrl}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(o)});if(!a.ok){let l=await a.text();if(this.shouldRetry(a.status,i)){let u=a.headers.get("Retry-After"),p=this.calculateBackoff(i,u?parseInt(u):void 0);console.warn(`\x1B[33m\u26A0 API request failed (${a.status}), retrying in ${Math.round(p/1e3)}s (attempt ${i+1}/${this.retryConfig.maxRetries})\x1B[0m`),await this.sleep(p);continue}throw new Error(`API request failed: ${a.status} - ${l}`)}return a.json()}catch(a){if(n=a,i<this.retryConfig.maxRetries&&a.message.includes("fetch")){let l=this.calculateBackoff(i);console.warn(`\x1B[33m\u26A0 Network error, retrying in ${Math.round(l/1e3)}s (attempt ${i+1}/${this.retryConfig.maxRetries})\x1B[0m`),await this.sleep(l);continue}throw a}throw n??new Error("Max retries exceeded")}async*createStreamingCompletion(e,s,t={}){let o={model:this.model,messages:e,tools:s?.length?s:void 0,tool_choice:s?.length?"auto":void 0,stream:true,max_tokens:t.maxTokens??4096,temperature:t.temperature??.7},n=null;for(let i=0;i<=this.retryConfig.maxRetries;i++)try{let a=await fetch(`${this.baseUrl}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(o)});if(!a.ok){let m=await a.text();if(this.shouldRetry(a.status,i)){let d=a.headers.get("Retry-After"),g=this.calculateBackoff(i,d?parseInt(d):void 0);console.warn(`\x1B[33m\u26A0 API request failed (${a.status}), retrying in ${Math.round(g/1e3)}s (attempt ${i+1}/${this.retryConfig.maxRetries})\x1B[0m`),await this.sleep(g);continue}throw new Error(`API request failed: ${a.status} - ${m}`)}let l=a.body?.getReader();if(!l)throw new Error("No response body");let u=new TextDecoder,p="";try{for(;;){let{done:m,value:d}=await l.read();if(m)break;p+=u.decode(d,{stream:!0});let g=p.split(`
|
|
3
|
+
`);p=g.pop()??"";for(let f of g){let y=f.trim();if(!(!y||y==="data: [DONE]")&&y.startsWith("data: "))try{yield JSON.parse(y.slice(6));}catch{}}}}finally{l.releaseLock();}return}catch(a){if(n=a,i<this.retryConfig.maxRetries&&(a.message.includes("fetch")||a.message.includes("network"))){let l=this.calculateBackoff(i);console.warn(`\x1B[33m\u26A0 Network error, retrying in ${Math.round(l/1e3)}s (attempt ${i+1}/${this.retryConfig.maxRetries})\x1B[0m`),await this.sleep(l);continue}throw a}throw n??new Error("Max retries exceeded")}async accumulateStream(e,s,t={}){let o="",n=[],i=new Map;for await(let l of this.createStreamingCompletion(e,s,t)){let u=l.choices[0]?.delta;if(u){if(u.content){let p=typeof u.content=="string"?u.content:"";o+=p,t.onToken?.(p);}if(u.tool_calls)for(let p of u.tool_calls){let m=p.index??0;i.has(m)||i.set(m,{id:p.id??"",name:p.function?.name??"",args:""});let d=i.get(m);p.id&&(d.id=p.id),p.function?.name&&(d.name=p.function.name),p.function?.arguments&&(d.args+=p.function.arguments);}}}for(let[l,u]of i)u.id&&u.name&&n.push({id:u.id,type:"function",function:{name:u.name,arguments:u.args}});let a={role:"assistant",content:o||""};return n.length>0&&(a.tool_calls=n),a}};function $(r,e){let s=_.resolve(r),t=_.resolve(r,e);return !t.startsWith(s+"/")&&t!==s?{valid:false,resolvedPath:t,error:`Path "${e}" resolves outside the working directory`}:{valid:true,resolvedPath:t}}var Pe=[/rm\s+(-rf?|--recursive)?\s*[\/~]/i,/rm\s+-rf?\s*\*/i,/>\s*\/dev\/sd/i,/mkfs/i,/dd\s+.*of=\/dev/i,/:(){ :|:& };:/i,/chmod\s+(-R\s+)?777\s+\//i,/chown\s+(-R\s+)?.*\s+\//i,/curl.*\|\s*(ba)?sh/i,/wget.*\|\s*(ba)?sh/i,/eval\s*\(/i,/>\s*\/etc\//i,/rm\s+.*\/\s*$/i],Se=[/git\s+(reset|clean)\s+--hard/i,/git\s+push\s+.*--force/i,/drop\s+database/i,/truncate\s+table/i,/delete\s+from\s+\w+\s*;?\s*$/i,/npm\s+publish/i,/pip\s+install\s+--user/i];function G(r){for(let e of Pe)if(e.test(r))return {allowed:false,blocked:`Command contains dangerous pattern: ${e.source}`};for(let e of Se)if(e.test(r))return {allowed:true,warning:`Command may be destructive: ${e.source}`};return {allowed:true}}var Re=[/(?:api[_-]?key|apikey|secret|password|passwd|token|auth)[=:]\s*['"]?[\w\-\.]+['"]?/gi,/Bearer\s+[\w\-\.]+/gi,/ghp_[a-zA-Z0-9]{36}/g,/sk-[a-zA-Z0-9]{48}/g,/-----BEGIN.*PRIVATE KEY-----/gi];function R(r){let e=r;for(let s of Re)e=e.replace(s,"[REDACTED]");return e}var Me=".geoff",Ae="todos";function Ee(r){let e=process.env.HOME||process.env.USERPROFILE||process.cwd(),s=___namespace.join(e,Me,Ae),t=`${"default"}.json`;return ___namespace.join(s,t)}async function Ie(r,e){let s=Ee(),t=___namespace.dirname(s);await C__namespace.mkdir(t,{recursive:true});let o={todos:r,sessionId:"default",updatedAt:new Date().toISOString()};await C__namespace.writeFile(s,JSON.stringify(o,null,2),"utf-8");}function H(r){return async e=>{try{let s=e,{todos:t}=s;if(!Array.isArray(t))return {success:!1,output:"",error:'Parameter "todos" must be an array.'};for(let p of t){if(!p.id||typeof p.id!="string"||p.id.trim()==="")return {success:!1,output:"",error:'Each todo must have a non-empty "id" string.'};if(!p.content||typeof p.content!="string"||p.content.trim()==="")return {success:!1,output:"",error:'Each todo must have a non-empty "content" string.'};if(!["pending","in_progress","completed"].includes(p.status))return {success:!1,output:"",error:'Each todo must have a valid "status" (pending, in_progress, completed).'}}let o=t.map(p=>p.id);if(new Set(o).size!==o.length)return {success:!1,output:"",error:"Todo IDs must be unique."};await Ie(t,r);let n=t.filter(p=>p.status==="pending").length,i=t.filter(p=>p.status==="in_progress").length,a=t.filter(p=>p.status==="completed").length,l;t.length===0?l="Todo list cleared.":l=`Todo list updated: ${a} completed, ${i} in progress, ${n} pending.`;let u=JSON.stringify(t);return l+=`
|
|
4
|
+
|
|
5
|
+
<system-reminder>
|
|
6
|
+
Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:
|
|
7
|
+
|
|
8
|
+
${u}. Continue on with the tasks at hand if applicable.
|
|
9
|
+
</system-reminder>`,{success:!0,output:l}}catch(s){return {success:false,output:"",error:`Failed to write todos: ${s instanceof Error?s.message:String(s)}`}}}}var W={type:"function",function:{name:"todo_write",description:"Creates and manages a structured task list for your current coding session. This helps track progress, organize complex tasks, and demonstrate thoroughness. Use for complex multi-step tasks (3+ steps), when user provides multiple tasks, or when starting/completing work. Do NOT use for single trivial tasks.",parameters:{type:"object",properties:{todos:{type:"array",description:"The updated todo list"}},required:["todos"]}}};var j=new Map;function J(r="default"){return j.has(r)||j.set(r,{enabled:false,awaitingApproval:false,approved:false}),j.get(r)}function De(r="default"){let e=J(r);e.enabled=true,e.approved=false,e.awaitingApproval=false;}function K(r,e){return async s=>{try{let t=s,{plan:o}=t;if(!o||typeof o!="string"||o.trim()==="")return {success:!1,output:"",error:'Parameter "plan" must be a non-empty string.'};let n=J(r||"default");if(n.plan=o,n.awaitingApproval=!0,e);return n.awaitingApproval=!1,n.approved=!0,n.enabled=!1,{success:!0,output:`Plan presented for approval:
|
|
10
|
+
|
|
11
|
+
${o}
|
|
12
|
+
|
|
13
|
+
User has approved your plan. You can now start coding. Start with updating your todo list if applicable.`}}catch(t){return {success:false,output:"",error:`Failed to exit plan mode: ${t instanceof Error?t.message:String(t)}`}}}}var X={type:"function",function:{name:"exit_plan_mode",description:"Use this tool when you are in plan mode and have finished presenting your plan and are ready to code. This will prompt the user to approve the plan. IMPORTANT: Only use this tool when planning implementation steps for coding tasks. For research tasks (searching files, reading code, understanding codebase) - do NOT use this tool.",parameters:{type:"object",properties:{plan:{type:"string",description:"The plan you came up with, that you want the user to approve. Supports markdown. Should be concise but complete."}},required:["plan"]}}};function V(r,e){return async s=>{try{if(De(r||"default"),e);return {success:!0,output:"Entered plan mode. Please create a plan before starting implementation."}}catch(t){return {success:false,output:"",error:`Failed to enter plan mode: ${t instanceof Error?t.message:String(t)}`}}}}var Y={type:"function",function:{name:"enter_plan_mode",description:"Enter plan mode to create a plan before implementing code changes. Use this when starting a complex task that requires planning. In plan mode, create a clear plan and then use exit_plan_mode to get approval.",parameters:{type:"object",properties:{},required:[]}}};var U=class{processes=new Map;counter=0;start(e,s,t){let o=`bg_${++this.counter}_${Date.now()}`,n=child_process.spawn("bash",["-c",e],{cwd:s,env:{...process.env,FORCE_COLOR:"0"},detached:false}),i={id:o,pid:n.pid||0,command:e,startTime:Date.now(),output:[],isComplete:false,exitCode:null,process:n};return n.stdout?.on("data",a=>{let l=a.toString();if(i.output.push(l),i.output.length>1e3&&(i.output=i.output.slice(-500)),t){let u={type:"thinking",timestamp:Date.now(),data:{processId:o,type:"stdout",output:l,isBackground:true}};t(u);}}),n.stderr?.on("data",a=>{let l=a.toString();if(i.output.push(`[stderr] ${l}`),t){let u={type:"thinking",timestamp:Date.now(),data:{processId:o,type:"stderr",output:l,isBackground:true}};t(u);}}),n.on("close",a=>{if(i.isComplete=true,i.exitCode=a,t){let l={type:"thinking",timestamp:Date.now(),data:{processId:o,type:"exit",exitCode:a,isBackground:true,message:`Background process ${o} exited with code ${a}`}};t(l);}}),n.on("error",a=>{i.isComplete=true,i.output.push(`[error] ${a.message}`);}),this.processes.set(o,i),i}get(e){return this.processes.get(e)}getAll(){return Array.from(this.processes.values())}kill(e){let s=this.processes.get(e);if(!s)return false;try{return s.process.kill("SIGTERM"),setTimeout(()=>{try{s.process.kill("SIGKILL");}catch{}},1e3),!0}catch{return false}}getOutput(e,s){let t=this.processes.get(e);if(!t)return "";let o=t.output;return s&&s<o.length?o.slice(-s).join(""):o.join("")}cleanup(){for(let[e,s]of this.processes)s.isComplete&&this.processes.delete(e);}},k=new U;function Z(r,e){return async s=>{try{let t=s.command;if(!t||typeof t!="string")return {success:!1,output:"",error:'Parameter "command" must be a non-empty string.'};let o=k.start(t,r,e);await new Promise(i=>setTimeout(i,500));let n=k.getOutput(o.id,20);return {success:!0,output:`Started background process:
|
|
14
|
+
ID: ${o.id}
|
|
15
|
+
PID: ${o.pid}
|
|
16
|
+
Command: ${t}
|
|
17
|
+
|
|
18
|
+
Initial output:
|
|
19
|
+
${n||"(no output yet)"}
|
|
20
|
+
|
|
21
|
+
Use get_process_output to check status or kill_process to stop.`}}catch(t){return {success:false,output:"",error:`Failed to start background process: ${t instanceof Error?t.message:String(t)}`}}}}function Q(){return async r=>{try{let e=r.process_id,s=r.tail;if(!e||typeof e!="string")return {success:!1,output:"",error:'Parameter "process_id" must be a non-empty string.'};let t=k.get(e);if(!t){let a=k.getAll();if(a.length===0)return {success:!1,output:"",error:`No background processes found. Process ID ${e} is invalid.`};let l=a.map(u=>` ${u.id}: ${u.command.slice(0,50)} (${u.isComplete?"completed":"running"})`).join(`
|
|
22
|
+
`);return {success:!1,output:"",error:`Process ${e} not found. Available processes:
|
|
23
|
+
${l}`}}let o=k.getOutput(e,s||50),n=Math.round((Date.now()-t.startTime)/1e3),i;return t.isComplete?i=`Completed (exit code: ${t.exitCode})`:i="Running",{success:!0,output:`Process: ${e}
|
|
24
|
+
Status: ${i}
|
|
25
|
+
Duration: ${n}s
|
|
26
|
+
Command: ${t.command}
|
|
27
|
+
|
|
28
|
+
Output:
|
|
29
|
+
${o||"(no output)"}`}}catch(e){return {success:false,output:"",error:`Failed to get process output: ${e instanceof Error?e.message:String(e)}`}}}}function ee(){return async r=>{try{let e=r.process_id;return !e||typeof e!="string"?{success:!1,output:"",error:'Parameter "process_id" must be a non-empty string.'}:k.kill(e)?{success:!0,output:`Process ${e} has been killed.`}:{success:!1,output:"",error:`Failed to kill process ${e}. It may not exist or already be terminated.`}}catch(e){return {success:false,output:"",error:`Failed to kill process: ${e instanceof Error?e.message:String(e)}`}}}}function te(){return async r=>{try{let e=k.getAll();return e.length===0?{success:!0,output:"No background processes running."}:{success:!0,output:`Background processes:
|
|
30
|
+
|
|
31
|
+
${e.map(t=>{let o=Math.round((Date.now()-t.startTime)/1e3),n=t.isComplete?`completed (exit: ${t.exitCode})`:"running";return ` ${t.id}:
|
|
32
|
+
Command: ${t.command.slice(0,60)}${t.command.length>60?"...":""}
|
|
33
|
+
Status: ${n}
|
|
34
|
+
Duration: ${o}s`}).join(`
|
|
35
|
+
|
|
36
|
+
`)}`}}catch(e){return {success:false,output:"",error:`Failed to list processes: ${e instanceof Error?e.message:String(e)}`}}}}var se={type:"function",function:{name:"run_background",description:"Start a command running in the background. Useful for dev servers, watch processes, or long-running tasks. Returns immediately with a process ID. Use get_process_output to check status.",parameters:{type:"object",properties:{command:{type:"string",description:"The shell command to run in the background"}},required:["command"]}}},oe={type:"function",function:{name:"get_process_output",description:"Get the output from a background process. Returns recent output and status.",parameters:{type:"object",properties:{process_id:{type:"string",description:"The ID of the background process"},tail:{type:"string",description:"Number of recent lines to return (default: 50)"}},required:["process_id"]}}},re={type:"function",function:{name:"kill_process",description:"Kill a background process by ID.",parameters:{type:"object",properties:{process_id:{type:"string",description:"The ID of the background process to kill"}},required:["process_id"]}}},ne={type:"function",function:{name:"list_processes",description:"List all background processes and their status.",parameters:{type:"object",properties:{},required:[]}}};var Fe=[{type:"function",function:{name:"read_file",description:"Read the contents of a file at the specified path. Use this to examine existing code, configuration files, or any text file.",parameters:{type:"object",properties:{path:{type:"string",description:"The path to the file to read (relative to working directory or absolute)"}},required:["path"]}}},{type:"function",function:{name:"write_file",description:"Write content to a file, creating it if it doesn't exist or overwriting if it does. Creates parent directories as needed.",parameters:{type:"object",properties:{path:{type:"string",description:"The path where the file should be written"},content:{type:"string",description:"The content to write to the file"}},required:["path","content"]}}},{type:"function",function:{name:"list_files",description:"List files and directories at the specified path. Returns a tree-like structure showing the contents.",parameters:{type:"object",properties:{path:{type:"string",description:"The directory path to list (defaults to current working directory)"},recursive:{type:"string",description:"Whether to list recursively (true/false, defaults to false)",enum:["true","false"]},max_depth:{type:"string",description:"Maximum depth for recursive listing (default: 3)"}},required:["path"]}}},{type:"function",function:{name:"execute_command",description:"Execute a shell command in the working directory. Use for running scripts, installing packages, git operations, tests, etc. Commands run in bash.",parameters:{type:"object",properties:{command:{type:"string",description:"The shell command to execute"},timeout:{type:"string",description:"Timeout in milliseconds (default: 30000)"}},required:["command"]}}},{type:"function",function:{name:"search_files",description:"Search for files matching a pattern or containing specific text using grep. Returns matching files and lines.",parameters:{type:"object",properties:{pattern:{type:"string",description:"The text pattern or regex to search for"},path:{type:"string",description:"Directory to search in (defaults to working directory)"},file_pattern:{type:"string",description:"Glob pattern to filter files (e.g., '*.ts', '*.py')"}},required:["pattern"]}}},{type:"function",function:{name:"apply_diff",description:"Apply a diff/patch to modify specific parts of a file. Use for precise edits instead of rewriting entire files.",parameters:{type:"object",properties:{path:{type:"string",description:"The path to the file to modify"},original:{type:"string",description:"The exact original text to find and replace"},replacement:{type:"string",description:"The new text to replace the original with"},replace_all:{type:"string",description:"Whether to replace all occurrences (true/false, defaults to false)",enum:["true","false"]}},required:["path","original","replacement"]}}},{type:"function",function:{name:"task_complete",description:"Signal that the task has been completed. Use this when you have finished the user's request and want to present the final result.",parameters:{type:"object",properties:{summary:{type:"string",description:"A brief summary of what was accomplished"},files_modified:{type:"string",description:"Comma-separated list of files that were created or modified"}},required:["summary"]}}},W,Y,X,se,oe,re,ne],ue=["node_modules",".git","__pycache__",".venv","venv","dist","build",".next",".nuxt","coverage",".cache",".parcel-cache"];function pe(r){let e=typeof r=="string"?r:r.workingDir,s=typeof r=="string"?ue:r.ignorePatterns??ue,t=new Map;return t.set("read_file",async o=>{try{let n=$(e,o.path);if(!n.valid)return {success:!1,output:"",error:n.error};if((await C.stat(n.resolvedPath)).isDirectory()){let l=await C.readdir(n.resolvedPath);return {success:!1,output:"",error:`Path is a directory, not a file. Contents: ${l.slice(0,20).join(", ")}${l.length>20?` ... and ${l.length-20} more`:""}`}}let a=await C.readFile(n.resolvedPath,"utf-8");return {success:!0,output:R(a)}}catch(n){return {success:false,output:"",error:`Failed to read file: ${n.message}`}}}),t.set("write_file",async o=>{try{let n=$(e,o.path);return n.valid?(await C.mkdir(_.dirname(n.resolvedPath),{recursive:!0}),await C.writeFile(n.resolvedPath,o.content,"utf-8"),{success:!0,output:`Successfully wrote to ${o.path}`}):{success:!1,output:"",error:n.error}}catch(n){return {success:false,output:"",error:`Failed to write file: ${n.message}`}}}),t.set("list_files",async o=>{try{let n=$(e,o.path||".");if(!n.valid)return {success:!1,output:"",error:n.error};let i=n.resolvedPath,a=o.recursive==="true",l=parseInt(o.max_depth)||3;async function u(m,d,g){if(d>l)return [];let f=await C.readdir(m,{withFileTypes:!0}),y=[];for(let b of f){if(s.some(w=>w.includes("*")?new RegExp("^"+w.replace(/\*/g,".*")+"$").test(b.name):b.name===w))continue;let O=b.isDirectory();if(y.push(`${g}${O?"\u{1F4C1} ":"\u{1F4C4} "}${b.name}`),O&&a&&d<l){let w=await u(_.join(m,b.name),d+1,g+" ");y.push(...w);}}return y}return {success:!0,output:(await u(i,0,"")).join(`
|
|
37
|
+
`)||"(empty directory)"}}catch(n){return {success:false,output:"",error:`Failed to list files: ${n.message}`}}}),t.set("execute_command",async o=>{let n=o.command,i=parseInt(o.timeout)||3e4,a=G(n);return a.allowed?(a.warning&&console.warn(`\x1B[33m\u26A0 Warning: ${a.warning}\x1B[0m`),new Promise(l=>{let u=child_process.spawn("bash",["-c",n],{cwd:e,timeout:i,env:{...process.env,FORCE_COLOR:"0"}}),p="",m="";u.stdout.on("data",d=>{p+=d.toString();}),u.stderr.on("data",d=>{m+=d.toString();}),u.on("close",d=>{let g=R([p,m].filter(Boolean).join(`
|
|
38
|
+
`));l(d===0?{success:true,output:g||"(no output)"}:{success:false,output:g,error:`Command exited with code ${d}`});}),u.on("error",d=>{l({success:false,output:"",error:`Failed to execute command: ${d.message}`});});})):{success:false,output:"",error:`Command blocked: ${a.blocked}`}}),t.set("search_files",async o=>{let n=o.pattern,i=$(e,o.path||".");if(!i.valid)return {success:false,output:"",error:i.error};let a=i.resolvedPath,l=o.file_pattern,u=n.replace(/['"\\]/g,"\\$&"),p=(l||"*").replace(/['"\\]/g,"\\$&"),m=`rg -n --glob '${p}' '${u}' '${a}' 2>/dev/null | head -50`,d=`grep -rn --include='${p}' '${u}' '${a}' 2>/dev/null | head -50`;return new Promise(g=>{let f=child_process.spawn("bash",["-c",`command -v rg >/dev/null && ${m} || ${d}`],{cwd:e}),y="";f.stdout.on("data",b=>{y+=b.toString();}),f.on("close",()=>{if(y){let b=R(y).split(`
|
|
39
|
+
`).map(v=>v.startsWith(e)?v.replace(e+"/",""):v).join(`
|
|
40
|
+
`);g({success:true,output:b});}else g({success:true,output:"No matches found"});});})}),t.set("apply_diff",async o=>{try{let n=$(e,o.path);if(!n.valid)return {success:!1,output:"",error:n.error};let i=n.resolvedPath,a=o.original,l=o.replacement,u=o.replace_all==="true"||o.replace_all===!0,p=await C.readFile(i,"utf-8");if(!p.includes(a))return {success:!1,output:"",error:"Original text not found in file. Please verify the exact text to replace."};let m=p.split(a).length-1,d=u||m===1?p.replaceAll(a,l):p.replace(a,l);return await C.writeFile(i,d,"utf-8"),{success:!0,output:m>1&&!u?`Successfully applied diff to ${o.path} (1 of ${m} occurrences). Use replace_all: true to replace all.`:`Successfully applied diff to ${o.path}${m>1?` (${m} occurrences)`:""}`}}catch(n){return {success:false,output:"",error:`Failed to apply diff: ${n.message}`}}}),t.set("task_complete",async o=>({success:true,output:`\u2705 Task Complete
|
|
41
|
+
|
|
42
|
+
${o.summary}${o.files_modified?`
|
|
43
|
+
|
|
44
|
+
Modified files: ${o.files_modified}`:""}`})),t.set("todo_write",H()),t.set("enter_plan_mode",V()),t.set("exit_plan_mode",K()),t.set("run_background",Z(e)),t.set("get_process_output",Q()),t.set("kill_process",ee()),t.set("list_processes",te()),{definitions:Fe,handlers:t}}function Be(r){let e=[_.join(r,"AGENTS.md"),_.join(r,".agents.md"),_.join(r,"CLAUDE.md"),_.join(r,".claude.md")];for(let s of e)if(fs.existsSync(s))try{return fs.readFileSync(s,"utf-8").trim()}catch{}return null}function de(r){let e=Be(r.workingDirectory),s=`You are an AI coding agent operating in a terminal environment. You help users with software engineering tasks by reading, writing, and executing code.
|
|
45
|
+
|
|
46
|
+
## Your Capabilities
|
|
47
|
+
- Read and analyze files in the codebase
|
|
48
|
+
- Write and modify files with precise edits
|
|
49
|
+
- Execute shell commands (bash) for builds, tests, git, etc.
|
|
50
|
+
- Search through code to find relevant files and patterns
|
|
51
|
+
- Apply targeted diffs for surgical code changes
|
|
52
|
+
|
|
53
|
+
## Working Directory
|
|
54
|
+
${r.workingDirectory}
|
|
55
|
+
|
|
56
|
+
## Operating System
|
|
57
|
+
${r.osInfo}
|
|
58
|
+
|
|
59
|
+
## Guidelines
|
|
60
|
+
|
|
61
|
+
### Task Execution
|
|
62
|
+
- Break complex tasks into steps and execute them methodically
|
|
63
|
+
- Read files before modifying them to understand the context
|
|
64
|
+
- Use apply_diff for small changes; write_file for new files or large rewrites
|
|
65
|
+
- Verify your changes by reading the file after modification when appropriate
|
|
66
|
+
- Run relevant tests or builds after making changes
|
|
67
|
+
|
|
68
|
+
### Code Quality
|
|
69
|
+
- Follow existing code style and patterns in the project
|
|
70
|
+
- Add appropriate error handling
|
|
71
|
+
- Write clean, maintainable code
|
|
72
|
+
- Include comments for complex logic
|
|
73
|
+
|
|
74
|
+
### Tool Usage
|
|
75
|
+
- Use the most appropriate tool for each task
|
|
76
|
+
- Prefer apply_diff over write_file for existing files when making small changes
|
|
77
|
+
- Use search_files to find relevant code before making changes
|
|
78
|
+
- Execute commands to verify your work (run tests, check syntax, etc.)
|
|
79
|
+
|
|
80
|
+
### Communication
|
|
81
|
+
- Explain what you're doing and why
|
|
82
|
+
- Report errors clearly and suggest fixes
|
|
83
|
+
- Summarize your changes when completing a task
|
|
84
|
+
- Ask clarifying questions if the request is ambiguous
|
|
85
|
+
|
|
86
|
+
### Safety
|
|
87
|
+
- Never execute destructive commands without explicit confirmation
|
|
88
|
+
- Be careful with rm, git reset --hard, and similar operations
|
|
89
|
+
- Don't expose secrets or credentials
|
|
90
|
+
- Respect file permissions and ownership
|
|
91
|
+
|
|
92
|
+
## Important Rules
|
|
93
|
+
1. Always verify file paths exist before writing
|
|
94
|
+
2. Never guess at file contents - read them first
|
|
95
|
+
3. Keep iterating until the task is fully complete
|
|
96
|
+
4. Use task_complete when you've finished the user's request
|
|
97
|
+
5. If stuck, explain the issue and ask for guidance`;return e?`${s}
|
|
98
|
+
|
|
99
|
+
## Project-Specific Instructions
|
|
100
|
+
The following instructions are specific to this project and should be followed:
|
|
101
|
+
|
|
102
|
+
${e}`:s}var Ge=[{id:"task_focus",content:`<system-reminder>
|
|
103
|
+
Stay focused on the current task. Do what was asked - nothing more, nothing less.
|
|
104
|
+
Do not create unnecessary files or make changes beyond the scope of the request.
|
|
105
|
+
</system-reminder>`,injectionPoint:"pre_user_message",priority:10},{id:"verify_changes",content:`<system-reminder>
|
|
106
|
+
After making file changes, verify they were applied correctly if the task is complex.
|
|
107
|
+
Consider running relevant tests or build commands to catch errors early.
|
|
108
|
+
</system-reminder>`,injectionPoint:"post_tool_result",priority:5,condition:r=>{let e=r.lastToolCalls[0];return e?.function?.name==="write_file"||e?.function?.name==="apply_diff"}},{id:"iteration_warning",content:`<system-reminder>
|
|
109
|
+
You have been running for several iterations. Make sure you are making progress.
|
|
110
|
+
If stuck, consider:
|
|
111
|
+
- Re-reading the original request
|
|
112
|
+
- Checking for errors in your approach
|
|
113
|
+
- Asking the user for clarification
|
|
114
|
+
</system-reminder>`,injectionPoint:"context_refresh",priority:8,condition:r=>r.iteration>5},{id:"complete_task",content:`<system-reminder>
|
|
115
|
+
If you have finished the user's request, use the task_complete tool to signal completion.
|
|
116
|
+
Provide a clear summary of what was accomplished.
|
|
117
|
+
</system-reminder>`,injectionPoint:"task_boundary",priority:7,condition:r=>r.iteration>2},{id:"file_operation_reminder",content:`<system-reminder>
|
|
118
|
+
When modifying existing files:
|
|
119
|
+
- ALWAYS read the file first to understand its current state
|
|
120
|
+
- ALWAYS prefer apply_diff for small, targeted changes
|
|
121
|
+
- Use write_file only for new files or complete rewrites
|
|
122
|
+
</system-reminder>`,injectionPoint:"pre_user_message",priority:6}];function A(r,e){return Ge.filter(s=>s.injectionPoint===e).filter(s=>!s.condition||s.condition(r)).sort((s,t)=>t.priority-s.priority).map(s=>s.content)}function me(r,e,s){let t=A(e,"pre_user_message"),o=[];return t.length>0&&o.push(t.join(`
|
|
123
|
+
`)),o.push(r),o.join(`
|
|
124
|
+
|
|
125
|
+
`)}function ge(r,e,s){let t=A(e,"post_tool_result"),o=[r];return t.length>0&&o.push(t.join(`
|
|
126
|
+
`)),o.join(`
|
|
127
|
+
|
|
128
|
+
`)}function fe(r){return [...A(r,"context_refresh"),...A(r,"task_boundary")].join(`
|
|
129
|
+
|
|
130
|
+
`)}var c={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",gray:"\x1B[90m",clearLine:"\x1B[2K"},x={h:"\u2500",v:"\u2502",tl:"\u256D",tr:"\u256E",bl:"\u2570",br:"\u256F",hDouble:"\u2550",hHeavy:"\u2501"},h={success:"\u2713",error:"\u2717",warn:"\u26A0",info:"\u2139",tool:"\u{1F527}",user:"\u{1F464}",bot:"\u{1F916}",stats:"\u{1F4CA}",tokens:"\u{1F524}",files:"\u{1F4C1}",code:"\u{1F4DD}",exec:"\u2699\uFE0F",diamond:"\u25C8"},T=class{verbose;currentMetricsLine=null;onLogEvent;constructor(e=false,s){this.verbose=e,this.onLogEvent=s;}setLogEventCallback(e){this.onLogEvent=e;}emit(e,s){this.onLogEvent&&this.onLogEvent({type:e,timestamp:Date.now(),data:s});}timestamp(){return new Date().toISOString().split("T")[1].slice(0,8)}num(e){return e.toLocaleString()}truncate(e,s){return e.length>s?e.slice(0,s)+"...":e}colorize(e,s){return `${s}${e}${c.reset}`}statusIcon(e){return e?this.colorize(h.success,c.green):this.colorize(h.error,c.red)}line(e=x.h,s=60){return this.colorize(e.repeat(s),c.dim)}label(e,s,t=c.cyan){return ` ${e.padEnd(13)}${this.colorize(s,t)}`}section(e,s){this.print(`${c.bright}${s} ${e}${c.reset}`);}clearStatusLine(){this.currentMetricsLine!==null&&process.stdout.write(`\r${c.clearLine}`);}restoreStatusLine(){this.currentMetricsLine!==null&&process.stdout.write(this.currentMetricsLine);}print(e){this.clearStatusLine(),console.log(e),this.restoreStatusLine();}info(e){this.emit("info",{message:e}),this.print(`${this.colorize(h.info,c.blue)} ${e}`);}debug(e){this.verbose&&this.print(this.colorize(`${this.timestamp()} ${e}`,c.gray));}warn(e){this.emit("warn",{message:e}),this.print(`${this.colorize(h.warn,c.yellow)} ${e}`);}error(e){this.emit("error",{message:e}),this.print(`${this.colorize(h.error,c.red)} ${e}`);}success(e){this.emit("success",{message:e}),this.print(`${this.colorize(h.success,c.green)} ${e}`);}tool(e,s){if(this.emit("tool_call",{toolName:e,toolArgs:s}),e==="task_complete")try{let t=JSON.parse(s);this.emit("task_complete",{message:t.summary,toolName:e}),this.clearStatusLine(),console.log(`
|
|
131
|
+
${this.colorize(`${h.success} Task Complete`,c.green)}`),t.summary&&console.log(this.colorize(t.summary,c.dim)),console.log(),this.restoreStatusLine();return}catch{}this.print(`${this.colorize(`${h.tool} ${e}`,c.cyan)} ${this.colorize(this.truncate(s,200),c.dim)}`);}toolResult(e,s){if(this.emit("tool_result",{toolName:e,success:s.success,output:s.output,error:s.error}),e==="task_complete")return;let t=this.statusIcon(s.success);if(this.verbose){let o=this.truncate(s.output,500);this.print(`${t} ${this.colorize(`${e}:`,c.dim)}`),o&&this.print(this.colorize(o,c.gray)),s.error&&this.print(this.colorize(s.error,c.red));}else {let o=s.output.split(`
|
|
132
|
+
`)[0].slice(0,80)||"(no output)";this.print(`${t} ${this.colorize(o,c.dim)}`);}}assistant(e){this.emit("assistant_message",{message:e}),this.clearStatusLine(),console.log(`
|
|
133
|
+
${this.colorize(`${h.bot} Geoff:`,c.magenta)}`),console.log(e),console.log(),this.restoreStatusLine();}user(e){this.emit("user_message",{message:e}),this.clearStatusLine(),console.log(`
|
|
134
|
+
${this.colorize(`${h.user} You:`,c.green)} ${e}
|
|
135
|
+
`),this.restoreStatusLine();}separator(){this.print(this.line());}banner(){let s=(u,p,m="")=>`${c.cyan}${u}${m.padEnd(61)}${p}${c.reset}`,t=u=>s(x.v,x.v,u),o=t(""),n=["\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2591\u2588\u2588\u2588\u2588\u2588\u2557\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2591\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D","\u2588\u2588\u2551\u2591\u2591\u2588\u2588\u2557\u2591\u2588\u2588\u2588\u2588\u2588\u2557\u2591\u2591\u2588\u2588\u2551\u2591\u2591\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557\u2591\u2591\u2588\u2588\u2588\u2588\u2588\u2557\u2591\u2591","\u2588\u2588\u2551\u2591\u2591\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D\u2591\u2591\u2588\u2588\u2551\u2591\u2591\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D\u2591\u2591\u2588\u2588\u2554\u2550\u2550\u255D\u2591\u2591","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2551\u2591\u2591\u2591\u2591\u2591","\u2591\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u2591\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u2591\u255A\u2550\u2550\u2550\u2550\u255D\u2591\u255A\u2550\u255D\u2591\u2591\u2591\u2591\u2591\u255A\u2550\u255D\u2591\u2591\u2591\u2591\u2591"],i=`${c.dim}${x.hHeavy.repeat(41)}${c.reset}${c.cyan}`,a=`${c.bright}C O D E R V E R S I O N 1 : 1${c.reset}${c.cyan}`,l=[h.diamond,"Reasoning",h.diamond,"Planning",h.diamond,"Execution",h.diamond,"Learning"].map((u,p)=>p%2===0?`${c.yellow}${u}${c.reset}${c.cyan}`:u).join(" ");console.log(),console.log(s(x.tl,x.tr,x.h.repeat(61))),console.log(o),n.forEach(u=>console.log(t(` ${c.bright}${u}${c.reset}${c.cyan} `))),console.log(o),console.log(t(` ${i} `)),console.log(t(` ${a} `)),console.log(t(` ${i} `)),console.log(o),console.log(t(` ${l} `)),console.log(o),console.log(s(x.bl,x.br,x.h.repeat(61))),console.log();}config(e){this.print(this.colorize("Configuration:",c.dim));for(let[s,t]of Object.entries(e)){let o=s==="apiKey"?"***":String(t);this.print(` ${s}: ${this.colorize(o,s==="apiKey"?c.dim:c.cyan)}`);}this.print("");}metricsUpdate(e,s){this.emit("metrics_update",{iteration:s,metrics:e});let t=((Date.now()-e.startTime)/1e3).toFixed(1),o=[`[Iter ${s}]`,`${c.cyan}ctx:${this.num(e.contextSize)}${c.dim}`,`${c.green}+${e.linesAdded}${c.dim}/${c.red}-${e.linesRemoved}${c.dim} lines`,`${c.yellow}${e.filesCreated} new${c.dim}/${c.blue}${e.filesModified} mod${c.dim} files`,`${c.magenta}${this.num(e.tokensUsed)} tok${c.dim}`,`${t}s`];this.currentMetricsLine=`\r${c.dim}${o.join(" | ")}${c.reset}`,process.stdout.write(`\r${c.clearLine}${this.currentMetricsLine}`);}clearMetricsLine(){this.currentMetricsLine=null,process.stdout.write(`\r${c.clearLine}`);}metricsSummary(e,s){this.clearMetricsLine();let t=((Date.now()-e.startTime)/1e3).toFixed(1),o=e.linesAdded-e.linesRemoved,n=o>=0?c.green:c.red,i=o>=0?"+":"";console.log(`
|
|
136
|
+
${c.bright}${c.cyan}${x.hDouble.repeat(55)}${c.reset}`),console.log(`${c.bright} ${h.stats} Session Summary${c.reset}`),console.log(`${c.cyan}${x.hDouble.repeat(55)}${c.reset}
|
|
137
|
+
`),this.section("Tokens",h.tokens),console.log(this.label("Total:",this.num(e.tokensUsed),c.magenta)),console.log(this.label("Prompt:",this.num(e.promptTokens),c.dim)),console.log(this.label("Completion:",this.num(e.completionTokens),c.dim)),console.log(this.label("Context:",`${this.num(e.contextSize)} (final)`,c.cyan)),console.log(),this.section("Files",h.files),console.log(this.label("Created:",e.filesCreated,c.green));for(let a of e.createdFiles)console.log(this.colorize(` + ${a}`,c.dim));console.log(this.label("Modified:",e.filesModified,c.yellow));for(let a of e.modifiedFiles)console.log(this.colorize(` ~ ${a}`,c.dim));console.log(),this.section("Code Changes",h.code),console.log(this.label("Lines added:",`+${this.num(e.linesAdded)}`,c.green)),console.log(this.label("Lines removed:",`-${this.num(e.linesRemoved)}`,c.red)),console.log(this.label("Net change:",`${i}${this.num(o)}`,n)),console.log(),this.section("Execution",h.exec),console.log(this.label("Iterations:",s,c.blue)),console.log(this.label("API calls:",e.apiCalls,c.dim)),console.log(this.label("Tool calls:",e.toolCallsTotal,c.dim)),console.log(this.label("Commands run:",e.commandsExecuted,c.dim)),console.log(this.label("Duration:",`${t}s`,c.dim)),console.log(`
|
|
138
|
+
${c.cyan}${x.hDouble.repeat(55)}${c.reset}
|
|
139
|
+
`);}iterationHeader(e,s){this.clearMetricsLine(),console.log(`
|
|
140
|
+
${this.colorize(`${x.h.repeat(3)} Iteration ${e}/${s} ${x.h.repeat(40)}`,c.dim)}`);}};var E={"gpt-4o":128e3,"gpt-4o-mini":128e3,"gpt-4-turbo":128e3,"gpt-4":8192,"gpt-4-32k":32768,"gpt-3.5-turbo":16385,"gpt-3.5-turbo-16k":16385,"claude-3-opus":2e5,"claude-3-sonnet":2e5,"claude-3-haiku":2e5,"claude-3-5-sonnet":2e5,llama3:8192,"llama3:70b":8192,codellama:16384,mistral:32768,mixtral:32768,"qwen3-coder":131072,"qwen3-coder:30b":131072,"qwen3:30b":131072,"qwen3:8b":131072,qwen3:131072,"qwen2.5-coder":131072,default:32768};function P(r){if(E[r])return E[r];for(let[e,s]of Object.entries(E))if(r.startsWith(e))return s;return E.default}function he(r,e){let s=4;return e.includes("claude")?s=3.5:(e.includes("llama")||e.includes("mistral"))&&(s=3.8),Math.ceil(r.length/s)}function I(r,e){let s="";if(typeof r.content=="string")s=r.content;else if(Array.isArray(r.content))for(let n of r.content)n.type==="text"&&n.text&&(s+=n.text);let o=he(s,e)+4;if(r.tool_calls)for(let n of r.tool_calls)o+=he(n.function.name+n.function.arguments,e);return o}function D(r,e){return r.reduce((s,t)=>s+I(t,e),0)}function ye(r,e,s,t=4096){let n=(P(e))-t;if(D(r,e)<=n)return {messages:r,truncated:false,removedCount:0,summarized:false};let a=r[0]?.role==="system"?r[0]:null,l=a?r.slice(1):r,u=-1;for(let f=l.length-1;f>=0;f--)if(l[f].role==="user"){u=f;break}let p=[],m=a?I(a,e):0;if(u>=0){let f=l[u];p.unshift(f),m+=I(f,e);}for(let f=l.length-1;f>=0;f--){if(f===u)continue;let y=l[f],b=I(y,e);if(m+b<=n){let v=p.findIndex((O,w)=>l.indexOf(p[w])>f);v===-1?p.push(y):p.splice(v,0,y),m+=b;}}p.sort((f,y)=>{let b=l.indexOf(f),v=l.indexOf(y);return b-v});let d=l.length-p.length,g=[];return a&&g.push(a),d>0&&g.push({role:"user",content:`<system-notice>
|
|
141
|
+
Previous conversation context (${d} messages) was truncated to fit within context limits.
|
|
142
|
+
The conversation continues from where relevant context remains.
|
|
143
|
+
</system-notice>`}),g.push(...p),{messages:g,truncated:d>0,removedCount:d,summarized:d>0}}function be(r,e,s=.8){let t=P(e);return D(r,e)/t>=s}function He(){return `call_${Date.now()}_${Math.random().toString(36).substring(2,11)}`}function We(r){let e=[],s=/<function=([^>]+)>([\s\S]*?)<\/function>/g,t=/<parameter=([^>]+)>\n?([\s\S]*?)\n?<\/parameter>/g,o;for(;(o=s.exec(r))!==null;){let n=o[1].trim(),i=o[2],a={},l;for(;(l=t.exec(i))!==null;){let u=l[1].trim(),p=l[2].trim();try{a[u]=JSON.parse(p);}catch{a[u]=p;}}t.lastIndex=0,e.push({name:n,arguments:a});}return e}function Je(r){let e=[],s=/<tool_call>([\s\S]*?)<\/tool_call>/g,t;for(;(t=s.exec(r))!==null;){let o=t[1],n=/<name>([\s\S]*?)<\/name>/i.exec(o);if(!n)continue;let i=n[1].trim(),a=/<arguments>([\s\S]*?)<\/arguments>/i.exec(o);if(!a)continue;let l={},u=a[1].trim();try{l=JSON.parse(u);}catch{let p=/<([^>]+)>([\s\S]*?)<\/\1>/g,m;for(;(m=p.exec(u))!==null;){let d=m[2].trim();try{l[m[1]]=JSON.parse(d);}catch{l[m[1]]=d;}}}e.push({name:i,arguments:l});}return e}function Ke(r){let e=[],s=/<invoke\s+name=["']([^"']+)["']>([\s\S]*?)<\/invoke>/g,t=/<parameter\s+name=["']([^"']+)["']>([\s\S]*?)<\/parameter>/g,o;for(;(o=s.exec(r))!==null;){let n=o[1].trim(),i=o[2],a={},l;for(;(l=t.exec(i))!==null;){let u=l[1].trim(),p=l[2].trim();try{a[u]=JSON.parse(p);}catch{a[u]=p;}}t.lastIndex=0,e.push({name:n,arguments:a});}return e}function Xe(r){let e=[],s=/\{[\s\n]*"name"[\s\n]*:[\s\n]*"([^"]+)"[\s\n]*,[\s\n]*"arguments"[\s\n]*:[\s\n]*(\{[^}]*\}|\{\})\}/g,t;for(;(t=s.exec(r))!==null;)try{let o=t[1],n=t[2];if(o){let i=JSON.parse(n);e.push({name:o,arguments:i});}}catch{}if(e.length===0){let o=/\{[\s\S]{0,500}?"name"[\s\S]{0,50}?:[\s\S]{0,50}?"([^"]+)"[\s\S]{0,50}?,[\s\S]{0,50}?"arguments"[\s\S]{0,50}?:[\s\S]{0,500}?\}/g;for(;(t=o.exec(r))!==null;)try{let n=t[0],i=JSON.parse(n);i.name&&e.push({name:i.name,arguments:i.arguments||{}});}catch{}}return e}function L(r){return [/<function=[^>]+>/i,/<tool_call>/i,/<invoke\s+name=/i].some(s=>s.test(r))}function Ve(r){return /\{\s*"name"\s*:\s*"[^"]+"\s*,\s*"arguments"\s*:/i.test(r)}function Ye(r){let e=[];e.push(...We(r)),e.push(...Je(r)),e.push(...Ke(r)),e.push(...Xe(r));let s=new Set;return e.filter(t=>{let o=`${t.name}:${JSON.stringify(t.arguments)}`;return s.has(o)?false:(s.add(o),true)})}function Ze(r){return r.map(e=>({id:He(),type:"function",function:{name:e.name,arguments:JSON.stringify(e.arguments)}}))}function F(r){let e=r;return e=e.replace(/<function=[^>]+>[\s\S]*?<\/function>/g,""),e=e.replace(/<tool_call>[\s\S]*?<\/tool_call>/g,""),e=e.replace(/<invoke\s+name=["'][^"']+["']>[\s\S]*?<\/invoke>/g,""),e=e.replace(/\n{3,}/g,`
|
|
144
|
+
|
|
145
|
+
`).trim(),e}function xe(r){let e=L(r),s=Ve(r);if(!e&&!s)return null;let t=Ye(r);return t.length===0?null:Ze(t)}var N=class{client;config;state;tools;logger;aborted=false;constructor(e){this.config=e,this.client=new S({apiKey:e.apiKey,baseUrl:e.baseUrl,model:e.model}),this.tools=pe(e.workingDirectory),this.logger=new T(e.verbose),this.state=this.createInitialState();}emit(e,s={}){if(this.config.onEvent){let t={type:e,timestamp:Date.now(),data:s};this.config.onEvent(t);}}createInitialMetrics(){return {tokensUsed:0,promptTokens:0,completionTokens:0,contextSize:0,filesCreated:0,filesModified:0,linesAdded:0,linesRemoved:0,commandsExecuted:0,toolCallsTotal:0,apiCalls:0,startTime:Date.now(),modifiedFiles:new Set,createdFiles:new Set}}createInitialState(){return {messages:[],iteration:0,isRunning:false,lastToolCalls:[],tokensUsed:0,metrics:this.createInitialMetrics()}}getSystemMessage(){return {role:"system",content:de({workingDirectory:this.config.workingDirectory,osInfo:`${process.platform} ${process.arch}`})}}estimateContextSize(){return D(this.state.messages,this.config.model)}manageContextWindow(){let e=ye(this.state.messages,this.config.model,void 0,this.config.maxTokens);e.truncated&&(this.logger.warn(`Context truncated: removed ${e.removedCount} messages to fit within ${P(this.config.model)} token limit`),this.state.messages=e.messages);}checkContextLimits(){if(be(this.state.messages,this.config.model,.75)){let e=this.estimateContextSize(),s=P(this.config.model);this.logger.warn(`Context usage: ${e}/${s} tokens (${Math.round(e/s*100)}%)`);}}async run(e){this.state=this.createInitialState(),this.state.isRunning=true,this.aborted=false,this.state.messages=[this.getSystemMessage()];let s=me(e,this.state);this.state.messages.push({role:"user",content:s}),this.logger.info(`Starting agent loop with task: ${e.slice(0,100)}...`),this.emit("thinking",{message:`Starting task: ${e.slice(0,100)}...`,level:"info"});let t="";try{for(;this.state.iteration<this.config.maxIterations&&!this.aborted;){if(this.state.iteration++,this.state.metrics.contextSize=this.estimateContextSize(),this.emit("iteration_start",{iteration:this.state.iteration,maxIterations:this.config.maxIterations,contextSize:this.state.metrics.contextSize}),this.emit("thinking",{message:`Iteration ${this.state.iteration}/${this.config.maxIterations}`,level:"info",icon:"\u2500"}),this.logger.metricsUpdate(this.state.metrics,this.state.iteration),this.manageContextWindow(),this.checkContextLimits(),this.state.iteration>3&&this.state.iteration%3===0){let a=fe(this.state);a&&this.state.messages.push({role:"user",content:a});}this.logger.clearMetricsLine(),this.emit("llm_call",{model:this.config.model,messageCount:this.state.messages.length});let o=await this.callLLM();if(!o){this.logger.error("No response from LLM"),this.emit("error",{message:"No response from LLM"});break}let n=typeof o.content=="string"?o.content:o.content?.map(a=>a.type==="text"?a.text:"").join("")||"";this.emit("llm_response",{content:n,hasToolCalls:!!o.tool_calls?.length,toolCallCount:o.tool_calls?.length||0,contentLength:n.length});let i=await this.processXmlToolCalls(o);if(this.state.messages.push(i),i.tool_calls&&i.tool_calls.length>0){this.state.lastToolCalls=i.tool_calls,this.state.metrics.toolCallsTotal+=i.tool_calls.length;let a=await this.handleToolCalls(i.tool_calls);for(let l of i.tool_calls)if(l.function.name==="task_complete")return t=JSON.parse(l.function.arguments).summary,this.state.isRunning=!1,this.emit("task_complete",{summary:t,metrics:{tokensUsed:this.state.metrics.tokensUsed,filesCreated:this.state.metrics.filesCreated,filesModified:this.state.metrics.filesModified,linesAdded:this.state.metrics.linesAdded,linesRemoved:this.state.metrics.linesRemoved,iterations:this.state.iteration,createdFiles:Array.from(this.state.metrics.createdFiles),modifiedFiles:Array.from(this.state.metrics.modifiedFiles)}}),this.emit("thinking",{message:"Task Complete",level:"success",icon:"\u2713",summary:(t||"").slice(0,200)}),t;if(!a)break}else if(i.content){let a=typeof i.content=="string"?i.content:i.content.map(u=>u.type==="text"?u.text:"").join("");if(this.logger.assistant(a),t=a,this.state.lastToolCalls=[],this.shouldStopLoop()||this.looksLikeCompletion(a)){this.emit("task_complete",{summary:a,metrics:{tokensUsed:this.state.metrics.tokensUsed,filesCreated:this.state.metrics.filesCreated,filesModified:this.state.metrics.filesModified,iterations:this.state.iteration,createdFiles:Array.from(this.state.metrics.createdFiles),modifiedFiles:Array.from(this.state.metrics.modifiedFiles)}}),this.emit("thinking",{message:"Task Complete",level:"success",icon:"\u2713",summary:a.slice(0,200)});break}}this.logger.metricsUpdate(this.state.metrics,this.state.iteration);}this.state.iteration>=this.config.maxIterations&&(this.logger.warn(`Reached maximum iterations (${this.config.maxIterations})`),this.emit("thinking",{message:`Reached maximum iterations (${this.config.maxIterations})`,level:"warning",icon:"\u26A0"}));}catch(o){throw this.logger.error(`Agent loop error: ${o.message}`),this.emit("error",{message:o.message,stack:o.stack}),o}finally{this.state.isRunning=false;}return t}async callLLM(){try{if(this.logger.debug("Thinking..."),this.state.metrics.apiCalls++,this.config.verbose){let e=await this.client.accumulateStream(this.state.messages,this.tools.definitions,{maxTokens:this.config.maxTokens,temperature:this.config.temperature,onToken:o=>process.stdout.write(o)});console.log();let s=typeof e.content=="string"?e.content.length:0,t=Math.ceil(s/4);return this.state.metrics.completionTokens+=t,this.state.metrics.tokensUsed+=t,e}else {let e=await this.client.createCompletion(this.state.messages,this.tools.definitions,{maxTokens:this.config.maxTokens,temperature:this.config.temperature});return e.usage&&(this.state.metrics.promptTokens+=e.usage.prompt_tokens,this.state.metrics.completionTokens+=e.usage.completion_tokens,this.state.metrics.tokensUsed+=e.usage.total_tokens,this.state.tokensUsed=this.state.metrics.tokensUsed),e.choices[0]?.message??null}}catch(e){return this.logger.error(`LLM call failed: ${e.message}`),null}}async processXmlToolCalls(e){if(e.tool_calls&&e.tool_calls.length>0)return e;let s=typeof e.content=="string"?e.content:Array.isArray(e.content)?e.content.map(o=>o.type==="text"?o.text:"").join(""):"";if(!s||!L(s))return e;this.logger.debug("Detected XML-style tool calls in response, parsing...");let t=xe(s);if(t&&t.length>0){this.logger.info(`Parsed ${t.length} XML tool call(s) to JSON format`);let o=F(s);return {...e,content:o,tool_calls:t}}if(L(s)){this.logger.debug("Regex parsing failed, attempting AI-assisted conversion...");try{let o=await this.aiAssistedToolParsing(s);if(o&&o.length>0){this.logger.info(`AI parsed ${o.length} tool call(s) from XML`);let n=F(s);return {...e,content:n,tool_calls:o}}}catch(o){this.logger.warn(`AI-assisted parsing failed: ${o.message}`);}}return e}async aiAssistedToolParsing(e){let s=`Convert the following XML-style tool calls to JSON format.
|
|
146
|
+
|
|
147
|
+
INPUT:
|
|
148
|
+
${e}
|
|
149
|
+
|
|
150
|
+
OUTPUT FORMAT:
|
|
151
|
+
Return ONLY a JSON array of tool calls in this exact format:
|
|
152
|
+
[
|
|
153
|
+
{
|
|
154
|
+
"name": "tool_name",
|
|
155
|
+
"arguments": { "param1": "value1", "param2": "value2" }
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
If there are no valid tool calls, return: []
|
|
160
|
+
|
|
161
|
+
JSON OUTPUT:`;try{let o=(await this.client.createCompletion([{role:"system",content:"You are a tool call parser. Extract tool calls from XML and output them as JSON."},{role:"user",content:s}],void 0,{maxTokens:2048,temperature:0})).choices[0]?.message?.content;if(!o||typeof o!="string")return null;let n=o.match(/\[[\s\S]*\]/);if(!n)return null;let i=JSON.parse(n[0]);return !Array.isArray(i)||i.length===0?null:i.map(a=>({id:`call_${Math.random().toString(36).substring(2,11)}`,type:"function",function:{name:a.name,arguments:JSON.stringify(a.arguments)}}))}catch(t){return this.logger.debug(`AI parsing error: ${t.message}`),null}}async handleToolCalls(e){for(let s of e){let{name:t,arguments:o}=s.function;this.logger.tool(t,o);let n;try{n=JSON.parse(o);}catch{let l={success:false,output:"",error:`Invalid JSON in tool arguments: ${o}`};this.addToolResultMessage(s.id,t,l),this.emit("tool_result",{toolCallId:s.id,toolName:t,success:false,error:l.error});continue}this.emit("tool_call",{toolCallId:s.id,toolName:t,arguments:n});let i=o.length>100?o.slice(0,100)+"...":o;this.emit("thinking",{message:`${t} ${i}`,level:"tool",icon:"\u{1F527}",toolName:t});let a=this.tools.handlers.get(t);if(!a){let l={success:false,output:"",error:`Unknown tool: ${t}`};this.addToolResultMessage(s.id,t,l),this.emit("tool_result",{toolCallId:s.id,toolName:t,success:false,error:l.error});continue}try{let l=await a(n);this.logger.toolResult(t,l),this.trackToolMetrics(t,n,l),this.emit("tool_result",{toolCallId:s.id,toolName:t,success:l.success,output:l.output.slice(0,500),error:l.error});let u=l.success?"\u2713":"\u2717",p=l.success?"success":"error",m=l.success?l.output.split(`
|
|
162
|
+
`)[0].slice(0,80)||"(no output)":l.error||"Unknown error";this.emit("thinking",{message:m,level:p,icon:u,toolName:t,success:l.success});let d=n.path;l.success&&d&&(t==="write_file"?this.emit("file_written",{path:d,sandboxId:this.config.sandboxId,operation:"create"}):t==="apply_diff"&&this.emit("file_modified",{path:d,sandboxId:this.config.sandboxId,operation:"modify"}));let g=ge(l.success?l.output:`Error: ${l.error}
|
|
163
|
+
${l.output}`,this.state,t);this.state.messages.push({role:"tool",tool_call_id:s.id,content:g});}catch(l){let u={success:false,output:"",error:`Tool execution failed: ${l.message}`};this.addToolResultMessage(s.id,t,u),this.emit("tool_result",{toolCallId:s.id,toolName:t,success:false,error:u.error}),this.emit("thinking",{message:u.error,level:"error",icon:"\u2717",toolName:t});}}return true}trackToolMetrics(e,s,t){let o=this.state.metrics,n=s.path;if(t.success)switch(e){case "write_file":if(n){let a=s.content.split(`
|
|
164
|
+
`).length;!o.createdFiles.has(n)&&!o.modifiedFiles.has(n)&&(o.filesCreated++,o.createdFiles.add(n)),o.linesAdded+=a;}break;case "apply_diff":if(n){let i=s.original,a=s.replacement;!o.modifiedFiles.has(n)&&!o.createdFiles.has(n)&&(o.filesModified++,o.modifiedFiles.add(n));let l=i.split(`
|
|
165
|
+
`).length,u=a.split(`
|
|
166
|
+
`).length;o.linesRemoved+=l,o.linesAdded+=u;}break;case "execute_command":o.commandsExecuted++;break}}addToolResultMessage(e,s,t){let o=t.success?t.output:`Error: ${t.error}
|
|
167
|
+
${t.output}`;this.state.messages.push({role:"tool",tool_call_id:e,content:o});}looksLikeCompletion(e){let s=["let me know if","is there anything else","feel free to ask","hope this helps","task complete","successfully completed","all done","i've created","i've built","i have created","i have built","here's what i","here is what i","i've successfully","i have successfully","the website is ready","the app is ready","the code is ready","implementation is complete","development is complete","ready to use","you can now","you should now","everything is set up","## complete","## summary","## features","## what i've"],t=e.toLowerCase();return s.some(o=>t.includes(o))}shouldStopLoop(){return (this.state.metrics.filesCreated>0||this.state.metrics.filesModified>0)&&this.state.lastToolCalls.length===0?(this.logger.info("Files created/modified and no pending tool calls - stopping loop"),true):false}abort(){this.aborted=true,this.state.isRunning=false,this.logger.warn("Agent loop aborted");}getState(){return {...this.state}}getMetrics(){return {...this.state.metrics}}getTokensUsed(){return this.state.metrics.tokensUsed}};var et=["node_modules",".git","__pycache__",".venv","venv","dist","build",".next",".nuxt","coverage",".cache",".parcel-cache","*.log",".DS_Store"];function tt(){return {model:"qwen3-coder:30b",baseUrl:"https://geoffnet.magma-rpc.com/v1",maxIterations:20,maxTokens:4096,temperature:.7,verbose:false,ignorePatterns:et,blockedCommands:[],retry:{maxRetries:3,baseDelayMs:1e3,maxDelayMs:3e4}}}function Te(r){let s={...tt()},t=_.join(os.homedir(),".geoff.json");if(fs.existsSync(t))try{let n=JSON.parse(fs.readFileSync(t,"utf-8"));s=ke(s,n);}catch(n){console.warn(`Warning: Failed to parse ${t}: ${n.message}`);}let o=[_.join(r,".geoff.json"),_.join(r,"geoff.config.json")];for(let n of o)if(fs.existsSync(n))try{let i=JSON.parse(fs.readFileSync(n,"utf-8"));s=ke(s,i);break}catch(i){console.warn(`Warning: Failed to parse ${n}: ${i.message}`);}return s}function ke(r,e){return {...r,...e,retry:{...r.retry,...e.retry},ignorePatterns:e.ignorePatterns??r.ignorePatterns,blockedCommands:e.blockedCommands??r.blockedCommands}}function $e(r){let e=[];return r.maxIterations!==void 0&&(r.maxIterations<1||r.maxIterations>100)&&e.push("maxIterations must be between 1 and 100"),r.maxTokens!==void 0&&(r.maxTokens<100||r.maxTokens>32e3)&&e.push("maxTokens must be between 100 and 32000"),r.temperature!==void 0&&(r.temperature<0||r.temperature>2)&&e.push("temperature must be between 0 and 2"),r.retry?.maxRetries!==void 0&&(r.retry.maxRetries<0||r.retry.maxRetries>10)&&e.push("retry.maxRetries must be between 0 and 10"),{valid:e.length===0,errors:e}}var q=null,Ce=false;function rt(r){let e=async s=>{Ce&&(r.warn("Force shutdown requested"),process.exit(1)),Ce=true,r.info(`
|
|
168
|
+
Received ${s}, shutting down gracefully...`),q&&(q.abort(),await new Promise(t=>setTimeout(t,1e3))),r.info("Shutdown complete"),process.exit(0);};process.on("SIGINT",()=>e("SIGINT")),process.on("SIGTERM",()=>e("SIGTERM")),process.on("uncaughtException",s=>{r.error(`Uncaught exception: ${s.message}`),console.error(s.stack),process.exit(1);}),process.on("unhandledRejection",s=>{r.error(`Unhandled rejection: ${s}`),process.exit(1);});}function nt(){let r=process.argv.slice(2),e={workingDir:process.cwd(),maxIterations:0,maxTokens:0,temperature:-1,verbose:false,interactive:false,help:false};for(let s=0;s<r.length;s++){let t=r[s];switch(t){case "-h":case "--help":e.help=true;break;case "-v":case "--verbose":e.verbose=true;break;case "-i":case "--interactive":e.interactive=true;break;case "-d":case "--dir":e.workingDir=_.resolve(r[++s]);break;case "--max-iterations":{let o=parseInt(r[++s]);(isNaN(o)||o<1||o>100)&&(console.error("Error: --max-iterations must be a number between 1 and 100"),process.exit(1)),e.maxIterations=o;break}case "--max-tokens":{let o=parseInt(r[++s]);(isNaN(o)||o<100||o>32e3)&&(console.error("Error: --max-tokens must be a number between 100 and 32000"),process.exit(1)),e.maxTokens=o;break}case "-t":case "--temperature":{let o=parseFloat(r[++s]);(isNaN(o)||o<0||o>2)&&(console.error("Error: --temperature must be a number between 0 and 2"),process.exit(1)),e.temperature=o;break}default:!t.startsWith("-")&&!e.prompt&&(e.prompt=t);}}return e}function it(){console.log(`
|
|
169
|
+
Usage: geoff [options] [prompt]
|
|
170
|
+
|
|
171
|
+
Options:
|
|
172
|
+
-h, --help Show this help message
|
|
173
|
+
-v, --verbose Enable verbose output
|
|
174
|
+
-i, --interactive Run in interactive mode (REPL)
|
|
175
|
+
-d, --dir <path> Working directory (default: current)
|
|
176
|
+
--max-iterations <n> Maximum loop iterations (default: 20)
|
|
177
|
+
--max-tokens <n> Maximum tokens per response (default: 4096)
|
|
178
|
+
-t, --temperature <n> Temperature (default: 0.7)
|
|
179
|
+
|
|
180
|
+
Configuration Files:
|
|
181
|
+
~/.geoff.json User-level configuration
|
|
182
|
+
.geoff.json Project-level configuration (in working directory)
|
|
183
|
+
AGENTS.md Project-specific instructions for the agent
|
|
184
|
+
|
|
185
|
+
Model configuration (model, baseUrl, apiKey) is set exclusively in .geoff.json:
|
|
186
|
+
{
|
|
187
|
+
"model": "qwen3-coder:30b",
|
|
188
|
+
"baseUrl": "https://geoffnet.magma-rpc.com/v1",
|
|
189
|
+
"apiKey": "your-api-key"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
Configuration priority (highest to lowest):
|
|
193
|
+
CLI arguments > Project config (.geoff.json) > User config (~/.geoff.json) > Defaults
|
|
194
|
+
|
|
195
|
+
Examples:
|
|
196
|
+
# Single task
|
|
197
|
+
geoff "Create a hello world Express server"
|
|
198
|
+
|
|
199
|
+
# Interactive mode
|
|
200
|
+
geoff -i
|
|
201
|
+
|
|
202
|
+
# Verbose mode with custom directory
|
|
203
|
+
geoff -v -d ./my-project "Add tests"
|
|
204
|
+
`);}async function at(r,e){r.banner(),console.log(`Type your tasks, or 'exit' to quit.
|
|
205
|
+
`);let t=(await import('readline')).createInterface({input:process.stdin,output:process.stdout}),o=()=>{t.question(">>> ",async n=>{let i=n.trim();if(!i){o();return}(i.toLowerCase()==="exit"||i.toLowerCase()==="quit")&&(console.log("Goodbye!"),t.close(),process.exit(0)),r.user(i);try{let l=await e().run(i);}catch(a){r.error(a.message);}o();});};o(),await new Promise(()=>{});}async function lt(r,e,s){e.banner(),e.user(r);try{let t=await s.run(r);e.separator(),e.success("Task completed");}catch(t){e.error(t.message),process.exit(1);}}async function ct(){let r=nt();r.help&&(it(),process.exit(0)),fs.existsSync(r.workingDir)||(console.error(`Error: Working directory does not exist: ${r.workingDir}`),process.exit(1));let e=Te(r.workingDir),s=$e(e);if(!s.valid){console.error("Configuration errors:");for(let i of s.errors)console.error(` - ${i}`);process.exit(1);}let t={model:e.model,baseUrl:e.baseUrl,apiKey:e.apiKey||"",workingDir:r.workingDir,maxIterations:r.maxIterations||e.maxIterations||20,maxTokens:r.maxTokens||e.maxTokens||4096,temperature:r.temperature>=0?r.temperature:e.temperature??.7,verbose:r.verbose||e.verbose||false,interactive:r.interactive,prompt:r.prompt};t.apiKey||(console.error("Error: API key required. Add 'apiKey' to ~/.geoff.json or .geoff.json"),process.exit(1));let o=new T(t.verbose);rt(o),t.verbose&&o.config({model:t.model,baseUrl:t.baseUrl,workingDir:t.workingDir,maxIterations:t.maxIterations,temperature:t.temperature});let n=()=>{let i=new N({apiKey:t.apiKey,baseUrl:t.baseUrl,model:t.model,maxIterations:t.maxIterations,maxTokens:t.maxTokens,temperature:t.temperature,workingDirectory:t.workingDir,autoApprove:true,verbose:t.verbose});return q=i,i};t.interactive||!t.prompt?await at(o,n):await lt(t.prompt,o,n());}ct().catch(r=>{console.error("Fatal error:",r),process.exit(1);});
|