@stacksjs/ai 0.70.257 → 0.70.259

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.
@@ -1,197 +1,25 @@
1
- let promptCount = 0;
2
- function formatToolUsage(toolName, input) {
3
- switch (toolName) {
4
- case "Read": {
5
- const filePath = input.file_path || "";
6
- return `
7
- \uD83D\uDCD6 Reading: ${filePath.split("/").pop() || filePath}
8
- `;
9
- }
10
- case "Glob":
11
- return `
12
- \uD83D\uDD0D Searching files: ${input.pattern || ""}
13
- `;
14
- case "Grep":
15
- return `
16
- \uD83D\uDD0E Searching for: "${input.pattern || ""}"
17
- `;
18
- case "Edit": {
19
- const filePath = input.file_path || "";
20
- return `
21
- \u270F\uFE0F Editing: ${filePath.split("/").pop() || filePath}
22
- `;
23
- }
24
- case "Write": {
25
- const filePath = input.file_path || "";
26
- return `
27
- \uD83D\uDCDD Writing: ${filePath.split("/").pop() || filePath}
28
- `;
29
- }
30
- case "Bash": {
31
- const cmd = (input.command || "").substring(0, 50);
32
- return `
33
- \uD83D\uDCBB Running: ${cmd}${cmd.length >= 50 ? "..." : ""}
34
- `;
35
- }
36
- case "Task":
37
- return `
38
- \uD83D\uDE80 Launching agent: ${input.description || "task"}
39
- `;
40
- default:
41
- return `
1
+ let promptCount=0;function formatToolUsage(toolName,input){switch(toolName){case"Read":{const filePath=input.file_path||"";return`
2
+ \uD83D\uDCD6 Reading: ${filePath.split("/").pop()||filePath}
3
+ `}case"Glob":return`
4
+ \uD83D\uDD0D Searching files: ${input.pattern||""}
5
+ `;case"Grep":return`
6
+ \uD83D\uDD0E Searching for: "${input.pattern||""}"
7
+ `;case"Edit":{const filePath=input.file_path||"";return`
8
+ \u270F\uFE0F Editing: ${filePath.split("/").pop()||filePath}
9
+ `}case"Write":{const filePath=input.file_path||"";return`
10
+ \uD83D\uDCDD Writing: ${filePath.split("/").pop()||filePath}
11
+ `}case"Bash":{const cmd=(input.command||"").substring(0,50);return`
12
+ \uD83D\uDCBB Running: ${cmd}${cmd.length>=50?"...":""}
13
+ `}case"Task":return`
14
+ \uD83D\uDE80 Launching agent: ${input.description||"task"}
15
+ `;default:return`
42
16
  \uD83D\uDD27 Using: ${toolName}
43
- `;
44
- }
45
- }
46
- export function createClaudeLocalAgent(config = {}) {
47
- return {
48
- name: "Claude CLI (Local)",
49
- async process(command, context, _history) {
50
- const { $: _$ } = await import("bun");
51
- if ((await _$`which claude`.quiet().nothrow()).exitCode !== 0)
52
- throw Error("Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code");
53
- const fullPrompt = context ? `Context:
17
+ `}}export function createClaudeLocalAgent(config={}){return{name:"Claude CLI (Local)",async process(command,context,_history){const{$:_$}=await import("bun");if((await _$`which claude`.quiet().nothrow()).exitCode!==0)throw Error("Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code");const fullPrompt=context?`Context:
54
18
  ${context}
55
19
 
56
- User request: ${command}` : command, cwd = config.cwd || process.cwd();
57
- try {
58
- return (await _$`cd ${cwd} && claude --print --dangerously-skip-permissions ${fullPrompt}`.quiet()).text().trim();
59
- } catch {
60
- try {
61
- return (await _$`cd ${cwd} && claude --print --allowedTools "Write,Edit,Bash" ${fullPrompt}`.quiet()).text().trim();
62
- } catch (innerError) {
63
- throw Error(`Claude CLI error: ${innerError.message}`);
64
- }
65
- }
66
- }
67
- };
68
- }
69
- export function createClaudeEC2Agent(config) {
70
- const {
71
- ec2Host = process.env.BUDDY_EC2_HOST,
72
- ec2User = process.env.BUDDY_EC2_USER || "ubuntu",
73
- ec2Key = process.env.BUDDY_EC2_KEY
74
- } = config;
75
- return {
76
- name: "Claude CLI (EC2)",
77
- async process(command, context, _history) {
78
- const { $: _$ } = await import("bun");
79
- if (!ec2Host)
80
- throw Error("BUDDY_EC2_HOST environment variable not set. Set it to your EC2 instance IP/hostname.");
81
- const escapedPrompt = (context ? `Context:
20
+ User request: ${command}`:command,cwd=config.cwd||process.cwd();try{return(await _$`cd ${cwd} && claude --print --dangerously-skip-permissions ${fullPrompt}`.quiet()).text().trim()}catch{try{return(await _$`cd ${cwd} && claude --print --allowedTools "Write,Edit,Bash" ${fullPrompt}`.quiet()).text().trim()}catch(innerError){throw Error(`Claude CLI error: ${innerError.message}`)}}}}}export function createClaudeEC2Agent(config){const{ec2Host=process.env.BUDDY_EC2_HOST,ec2User=process.env.BUDDY_EC2_USER||"ubuntu",ec2Key=process.env.BUDDY_EC2_KEY}=config;return{name:"Claude CLI (EC2)",async process(command,context,_history){const{$:_$}=await import("bun");if(!ec2Host)throw Error("BUDDY_EC2_HOST environment variable not set. Set it to your EC2 instance IP/hostname.");const escapedPrompt=(context?`Context:
82
21
  ${context}
83
22
 
84
- User request: ${command}` : command).replace(/'/g, "'\\''"), sshArgs = ec2Key ? `-i ${ec2Key}` : "", sshTarget = `${ec2User}@${ec2Host}`;
85
- try {
86
- return (await _$`ssh ${sshArgs} ${sshTarget} "claude --print '${escapedPrompt}'"`.quiet()).text().trim();
87
- } catch (error) {
88
- throw Error(`EC2 Claude CLI error: ${error.message}. Make sure SSH is configured and claude CLI is installed on EC2.`);
89
- }
90
- }
91
- };
92
- }
93
- export async function processCommandStreaming(command, cwd) {
94
- const { spawn } = await import("bun"), proc = spawn([
95
- "claude",
96
- "--print",
97
- "--verbose",
98
- "--output-format",
99
- "stream-json",
100
- "--dangerously-skip-permissions",
101
- command
102
- ], {
103
- cwd,
104
- stdout: "pipe",
105
- stderr: "pipe"
106
- });
107
- let fullResponse = "";
108
- const decoder = new TextDecoder, encoder = new TextEncoder, stream = new ReadableStream({
109
- async start(controller) {
110
- try {
111
- const reader = proc.stdout.getReader();
112
- let buffer = "", lastSentText = "", result = await reader.read();
113
- while (!result.done) {
114
- buffer += decoder.decode(result.value, { stream: !0 });
115
- const lines = buffer.split(`
116
- `);
117
- buffer = lines.pop() || "";
118
- for (const line of lines) {
119
- if (!line.trim())
120
- continue;
121
- try {
122
- const event = JSON.parse(line);
123
- let textContent = "";
124
- if (event.type === "system" && event.subtype === "init")
125
- promptCount++;
126
- else if (event.type === "content_block_start" && event.content_block?.text)
127
- textContent = event.content_block.text;
128
- else if (event.type === "content_block_delta" && event.delta?.text)
129
- textContent = event.delta.text;
130
- else if (event.type === "assistant" && event.message?.content) {
131
- for (const block of event.message.content)
132
- if (block.type === "text" && block.text)
133
- textContent += block.text;
134
- else if (block.type === "tool_use" && block.name)
135
- textContent += formatToolUsage(block.name, block.input || {});
136
- } else if (event.type === "user" && event.message?.content) {
137
- for (const block of event.message.content)
138
- if (block.type === "tool_result")
139
- textContent += ` \u2713 done
140
- `;
141
- } else if (event.type === "result" && event.result) {
142
- fullResponse = event.result;
143
- const resultPrefix = event.result.substring(0, Math.min(50, event.result.length));
144
- if (!lastSentText.includes(resultPrefix))
145
- textContent = event.result;
146
- }
147
- if (textContent) {
148
- if (event.type === "content_block_delta") {
149
- lastSentText += textContent;
150
- controller.enqueue(encoder.encode(textContent));
151
- } else if (!lastSentText.endsWith(textContent)) {
152
- lastSentText += textContent;
153
- controller.enqueue(encoder.encode(textContent));
154
- }
155
- }
156
- } catch {
157
- if (!lastSentText.includes(line)) {
158
- lastSentText += line;
159
- fullResponse += line;
160
- controller.enqueue(encoder.encode(line));
161
- }
162
- }
163
- }
164
- result = await reader.read();
165
- }
166
- if (buffer.trim())
167
- try {
168
- const event = JSON.parse(buffer);
169
- if (event.result && !fullResponse)
170
- fullResponse = event.result;
171
- } catch {
172
- if (!lastSentText.includes(buffer)) {
173
- fullResponse += buffer;
174
- controller.enqueue(encoder.encode(buffer));
175
- }
176
- }
177
- await proc.exited;
178
- controller.close();
179
- } catch (error) {
180
- controller.error(error);
181
- }
182
- }
183
- }), fullResponsePromise = (async () => {
184
- await proc.exited;
185
- return fullResponse.trim();
186
- })();
187
- return { stream, fullResponse: fullResponsePromise };
188
- }
189
- export function resetPromptCount() {
190
- promptCount = 0;
191
- }
192
- export const claudeAgent = {
193
- createLocal: createClaudeLocalAgent,
194
- createEC2: createClaudeEC2Agent,
195
- processStreaming: processCommandStreaming,
196
- resetPromptCount
197
- };
23
+ User request: ${command}`:command).replace(/'/g,"'\\''"),sshArgs=ec2Key?`-i ${ec2Key}`:"",sshTarget=`${ec2User}@${ec2Host}`;try{return(await _$`ssh ${sshArgs} ${sshTarget} "claude --print '${escapedPrompt}'"`.quiet()).text().trim()}catch(error){throw Error(`EC2 Claude CLI error: ${error.message}. Make sure SSH is configured and claude CLI is installed on EC2.`)}}}}export async function processCommandStreaming(command,cwd){const{spawn}=await import("bun"),proc=spawn(["claude","--print","--verbose","--output-format","stream-json","--dangerously-skip-permissions",command],{cwd,stdout:"pipe",stderr:"pipe"});let fullResponse="";const decoder=new TextDecoder,encoder=new TextEncoder,stream=new ReadableStream({async start(controller){try{const reader=proc.stdout.getReader();let buffer="",lastSentText="",result=await reader.read();while(!result.done){buffer+=decoder.decode(result.value,{stream:!0});const lines=buffer.split(`
24
+ `);buffer=lines.pop()||"";for(const line of lines){if(!line.trim())continue;try{const event=JSON.parse(line);let textContent="";if(event.type==="system"&&event.subtype==="init")promptCount++;else if(event.type==="content_block_start"&&event.content_block?.text)textContent=event.content_block.text;else if(event.type==="content_block_delta"&&event.delta?.text)textContent=event.delta.text;else if(event.type==="assistant"&&event.message?.content){for(const block of event.message.content)if(block.type==="text"&&block.text)textContent+=block.text;else if(block.type==="tool_use"&&block.name)textContent+=formatToolUsage(block.name,block.input||{})}else if(event.type==="user"&&event.message?.content){for(const block of event.message.content)if(block.type==="tool_result")textContent+=` \u2713 done
25
+ `}else if(event.type==="result"&&event.result){fullResponse=event.result;const resultPrefix=event.result.substring(0,Math.min(50,event.result.length));if(!lastSentText.includes(resultPrefix))textContent=event.result}if(textContent){if(event.type==="content_block_delta"){lastSentText+=textContent;controller.enqueue(encoder.encode(textContent))}else if(!lastSentText.endsWith(textContent)){lastSentText+=textContent;controller.enqueue(encoder.encode(textContent))}}}catch{if(!lastSentText.includes(line)){lastSentText+=line;fullResponse+=line;controller.enqueue(encoder.encode(line))}}}result=await reader.read()}if(buffer.trim())try{const event=JSON.parse(buffer);if(event.result&&!fullResponse)fullResponse=event.result}catch{if(!lastSentText.includes(buffer)){fullResponse+=buffer;controller.enqueue(encoder.encode(buffer))}}await proc.exited;controller.close()}catch(error){controller.error(error)}}}),fullResponsePromise=(async()=>{await proc.exited;return fullResponse.trim()})();return{stream,fullResponse:fullResponsePromise}}export function resetPromptCount(){promptCount=0}export const claudeAgent={createLocal:createClaudeLocalAgent,createEC2:createClaudeEC2Agent,processStreaming:processCommandStreaming,resetPromptCount};
@@ -1 +1 @@
1
- export * from "./claude";
1
+ export*from"./claude";
package/dist/buddy.js CHANGED
@@ -1,53 +1,10 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { dirname, join } from "node:path";
4
- import { claudeAgent } from "./agents";
5
- import { createAnthropicDriver, createClaudeAgentSDKDriver, createOllamaDriver, createOpenAIDriver } from "./drivers";
6
- import { buildProjectContext } from "./context";
7
- export const CONFIG = {
8
- workDir: join(homedir(), "Code", ".buddy-repos"),
9
- commitMessage: "chore: wip",
10
- ollamaHost: process.env.OLLAMA_HOST || "http://localhost:11434",
11
- ollamaModel: process.env.OLLAMA_MODEL || "llama3.2"
12
- }, apiKeys = {
13
- anthropic: process.env.ANTHROPIC_API_KEY,
14
- openai: process.env.OPENAI_API_KEY,
15
- claudeCliHost: process.env.BUDDY_EC2_HOST
16
- };
17
- const state = {
18
- repo: null,
19
- conversationHistory: [],
20
- currentDriver: "claude-cli-local",
21
- github: null
22
- };
23
- if (!existsSync(CONFIG.workDir))
24
- mkdirSync(CONFIG.workDir, { recursive: !0 });
25
- export const buddyState = {
26
- getState: () => state,
27
- setRepo: (repo) => {
28
- state.repo = repo;
29
- },
30
- setCurrentDriver: (driver) => {
31
- state.currentDriver = driver;
32
- },
33
- setGitHub: (github) => {
34
- state.github = github;
35
- },
36
- addToHistory: (message) => {
37
- state.conversationHistory.push(message);
38
- },
39
- clearHistory: () => {
40
- state.conversationHistory = [];
41
- }
42
- };
43
- export function buildSystemPrompt(context) {
44
- return `You are Buddy, an AI code assistant that helps users modify codebases through voice commands.
1
+ import{existsSync,mkdirSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";import{claudeAgent}from"./agents";import{createAnthropicDriver,createClaudeAgentSDKDriver,createOllamaDriver,createOpenAIDriver}from"./drivers";import{buildProjectContext}from"./context";export const CONFIG={workDir:join(homedir(),"Code",".buddy-repos"),commitMessage:"chore: wip",ollamaHost:process.env.OLLAMA_HOST||"http://localhost:11434",ollamaModel:process.env.OLLAMA_MODEL||"llama3.2"},apiKeys={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,claudeCliHost:process.env.BUDDY_EC2_HOST};const state={repo:null,conversationHistory:[],currentDriver:"claude-cli-local",github:null};if(!existsSync(CONFIG.workDir))mkdirSync(CONFIG.workDir,{recursive:!0});export const buddyState={getState:()=>state,setRepo:(repo)=>{state.repo=repo},setCurrentDriver:(driver)=>{state.currentDriver=driver},setGitHub:(github)=>{state.github=github},addToHistory:(message)=>{state.conversationHistory.push(message)},clearHistory:()=>{state.conversationHistory=[]}};export function buildSystemPrompt(context){return`You are Buddy, an AI code assistant that helps users modify codebases through voice commands.
45
2
 
46
- ${state.repo ? `You are working on the repository: ${state.repo.name}
3
+ ${state.repo?`You are working on the repository: ${state.repo.name}
47
4
  Branch: ${state.repo.branch}
48
5
  Path: ${state.repo.path}
49
6
 
50
- ${context}` : "No repository is currently open."}
7
+ ${context}`:"No repository is currently open."}
51
8
 
52
9
  When the user gives you a command:
53
10
  1. Analyze what they want to do
@@ -64,16 +21,7 @@ FILE: path/to/file.ts
64
21
  // Full file content
65
22
  \`\`\`
66
23
 
67
- Be concise but thorough. The user will review and commit your changes.`;
68
- }
69
- function createMockDriver() {
70
- return {
71
- name: "Mock",
72
- async process(command) {
73
- await new Promise((resolve) => setTimeout(resolve, 1000));
74
- const lowerCommand = command.toLowerCase();
75
- if (lowerCommand.includes("readme") || lowerCommand.includes("documentation"))
76
- return `I'll update the README.md file for you.
24
+ Be concise but thorough. The user will review and commit your changes.`}function createMockDriver(){return{name:"Mock",async process(command){await new Promise((resolve)=>setTimeout(resolve,1000));const lowerCommand=command.toLowerCase();if(lowerCommand.includes("readme")||lowerCommand.includes("documentation"))return`I'll update the README.md file for you.
77
25
 
78
26
  Analyzing the repository structure...
79
27
 
@@ -97,9 +45,7 @@ npm run start
97
45
  \`\`\`
98
46
 
99
47
  File modified: README.md
100
- Lines added: 12`;
101
- if (lowerCommand.includes("fix") || lowerCommand.includes("bug"))
102
- return `I'll analyze and fix the issue.
48
+ Lines added: 12`;if(lowerCommand.includes("fix")||lowerCommand.includes("bug"))return`I'll analyze and fix the issue.
103
49
 
104
50
  Scanning for potential bugs...
105
51
 
@@ -111,8 +57,7 @@ export function getData(data: { value?: string }) {
111
57
  \`\`\`
112
58
 
113
59
  Files modified: src/utils.ts
114
- Lines changed: 4`;
115
- return `I understand you want to: "${command}"
60
+ Lines changed: 4`;return`I understand you want to: "${command}"
116
61
 
117
62
  I'll analyze the repository and implement this change.
118
63
 
@@ -124,252 +69,15 @@ export function main() {
124
69
  }
125
70
  \`\`\`
126
71
 
127
- Files modified: 1`;
128
- }
129
- };
130
- }
131
- export function getDriver(driverName) {
132
- const currentState = buddyState.getState();
133
- switch (driverName) {
134
- case "claude-cli-local":
135
- return claudeAgent.createLocal({ cwd: currentState.repo?.path });
136
- case "claude-cli-ec2":
137
- return claudeAgent.createEC2({
138
- cwd: currentState.repo?.path,
139
- ec2Host: apiKeys.claudeCliHost
140
- });
141
- case "claude":
142
- case "anthropic":
143
- if (!apiKeys.anthropic)
144
- throw Error("Anthropic API key not set. Configure your API key in settings.");
145
- return createAnthropicDriver({ apiKey: apiKeys.anthropic });
146
- case "openai":
147
- if (!apiKeys.openai)
148
- throw Error("OpenAI API key not set. Configure your API key in settings.");
149
- return createOpenAIDriver({ apiKey: apiKeys.openai });
150
- case "ollama":
151
- return createOllamaDriver({
152
- host: CONFIG.ollamaHost,
153
- model: CONFIG.ollamaModel
154
- });
155
- case "claude-sdk":
156
- case "claude-agent-sdk":
157
- return createClaudeAgentSDKDriver({
158
- cwd: currentState.repo?.path,
159
- maxTurns: 25,
160
- permissionMode: "bypassPermissions"
161
- });
162
- case "mock":
163
- return createMockDriver();
164
- default:
165
- throw Error(`Unknown driver: ${driverName}. Available: claude-cli-local, claude-cli-ec2, claude, claude-sdk, openai, ollama, mock`);
166
- }
167
- }
168
- export function getAvailableDrivers() {
169
- return ["claude-cli-local", "claude-cli-ec2", "claude", "claude-sdk", "openai", "ollama", "mock"];
170
- }
171
- export async function getRepoContext(repoPath) {
172
- return buildProjectContext(repoPath).text;
173
- }
174
- export async function openRepository(input) {
175
- const { $: _$ } = await import("bun");
176
- let repoPath, repoName;
177
- if (input.includes("github.com") || input.startsWith("git@")) {
178
- repoName = input.split("/").pop()?.replace(".git", "") || "repo";
179
- repoPath = join(CONFIG.workDir, repoName);
180
- if (existsSync(repoPath))
181
- await _$`cd ${repoPath} && git pull --rebase`.quiet();
182
- else
183
- await _$`git clone ${input} ${repoPath}`.quiet();
184
- } else {
185
- repoPath = input.startsWith("~") ? input.replace("~", homedir()) : input;
186
- if (!existsSync(repoPath))
187
- throw Error(`Local path does not exist: ${repoPath}`);
188
- if (!existsSync(join(repoPath, ".git")))
189
- throw Error(`Not a git repository: ${repoPath}`);
190
- repoName = repoPath.split("/").pop() || "repo";
191
- }
192
- const branch = (await _$`cd ${repoPath} && git branch --show-current`.quiet()).text().trim(), hasChanges = (await _$`cd ${repoPath} && git status --porcelain`.quiet()).text().trim().length > 0, lastCommit = (await _$`cd ${repoPath} && git log -1 --format="%h %s"`.quiet()).text().trim(), repoState = {
193
- path: repoPath,
194
- name: repoName,
195
- branch,
196
- hasChanges,
197
- lastCommit
198
- };
199
- buddyState.setRepo(repoState);
200
- buddyState.clearHistory();
201
- return repoState;
202
- }
203
- export async function applyChanges(aiResponse) {
204
- const currentState = buddyState.getState();
205
- if (!currentState.repo)
206
- throw Error("No repository opened");
207
- const modifiedFiles = [], filePattern = /FILE:\s*([^\n]+)\n```\w*\n([\s\S]*?)```/g;
208
- for (const match of aiResponse.matchAll(filePattern)) {
209
- const filePath = match[1].trim(), content = match[2], fullPath = join(currentState.repo.path, filePath), dir = dirname(fullPath);
210
- if (!existsSync(dir))
211
- mkdirSync(dir, { recursive: !0 });
212
- writeFileSync(fullPath, content);
213
- modifiedFiles.push(filePath);
214
- }
215
- if (modifiedFiles.length > 0 && currentState.repo)
216
- currentState.repo.hasChanges = !0;
217
- return modifiedFiles;
218
- }
219
- export async function configureGitUser() {
220
- const currentState = buddyState.getState();
221
- if (!currentState.repo || !currentState.github)
222
- return;
223
- const { $: _$ } = await import("bun"), { name, email } = currentState.github;
224
- await _$`cd ${currentState.repo.path} && git config user.name ${name}`.quiet();
225
- await _$`cd ${currentState.repo.path} && git config user.email ${email}`.quiet();
226
- }
227
- export async function commitChanges() {
228
- const currentState = buddyState.getState();
229
- if (!currentState.repo)
230
- throw Error("No repository opened");
231
- const { $: _$ } = await import("bun");
232
- if (currentState.github)
233
- await configureGitUser();
234
- await _$`cd ${currentState.repo.path} && git add -A`.quiet();
235
- await _$`cd ${currentState.repo.path} && git commit -m ${CONFIG.commitMessage}`.quiet();
236
- const commitHash = (await _$`cd ${currentState.repo.path} && git rev-parse --short HEAD`.quiet()).text().trim();
237
- currentState.repo.hasChanges = !1;
238
- currentState.repo.lastCommit = commitHash;
239
- return commitHash;
240
- }
241
- export async function pushChanges() {
242
- const currentState = buddyState.getState();
243
- if (!currentState.repo)
244
- throw Error("No repository opened");
245
- const { $: _$ } = await import("bun");
246
- await _$`cd ${currentState.repo.path} && git push`.quiet();
247
- }
248
- export async function processCommand(command, driverName) {
249
- const currentState = buddyState.getState();
250
- if (!currentState.repo)
251
- throw Error("No repository opened");
252
- const normalizedDriver = driverName || currentState.currentDriver, driver = getDriver(normalizedDriver);
253
- if (driverName)
254
- buddyState.setCurrentDriver(driverName);
255
- const context = await getRepoContext(currentState.repo.path), systemPrompt = buildSystemPrompt(context), response = await driver.process(command, systemPrompt, currentState.conversationHistory);
256
- buddyState.addToHistory({ role: "user", content: command });
257
- buddyState.addToHistory({ role: "assistant", content: response });
258
- return response;
259
- }
260
- export async function buddyProcessStreaming(command, driverName, history) {
261
- const currentState = buddyState.getState();
262
- if (!currentState.repo)
263
- throw Error("No repository opened");
264
- const normalizedDriver = driverName || currentState.currentDriver, streamingDrivers = ["claude-cli-local", "claude-sdk"];
265
- if (!streamingDrivers.includes(normalizedDriver))
266
- throw Error(`Streaming only supported for ${streamingDrivers.join(", ")} drivers. Current: ${normalizedDriver}`);
267
- if (driverName)
268
- buddyState.setCurrentDriver(driverName);
269
- let contextualCommand = command;
270
- if (history && history.length > 0) {
271
- let conversationContext = `## Previous Conversation
72
+ Files modified: 1`}}}export function getDriver(driverName){const currentState=buddyState.getState();switch(driverName){case"claude-cli-local":return claudeAgent.createLocal({cwd:currentState.repo?.path});case"claude-cli-ec2":return claudeAgent.createEC2({cwd:currentState.repo?.path,ec2Host:apiKeys.claudeCliHost});case"claude":case"anthropic":if(!apiKeys.anthropic)throw Error("Anthropic API key not set. Configure your API key in settings.");return createAnthropicDriver({apiKey:apiKeys.anthropic});case"openai":if(!apiKeys.openai)throw Error("OpenAI API key not set. Configure your API key in settings.");return createOpenAIDriver({apiKey:apiKeys.openai});case"ollama":return createOllamaDriver({host:CONFIG.ollamaHost,model:CONFIG.ollamaModel});case"claude-sdk":case"claude-agent-sdk":return createClaudeAgentSDKDriver({cwd:currentState.repo?.path,maxTurns:25,permissionMode:"bypassPermissions"});case"mock":return createMockDriver();default:throw Error(`Unknown driver: ${driverName}. Available: claude-cli-local, claude-cli-ec2, claude, claude-sdk, openai, ollama, mock`)}}export function getAvailableDrivers(){return["claude-cli-local","claude-cli-ec2","claude","claude-sdk","openai","ollama","mock"]}export async function getRepoContext(repoPath){return buildProjectContext(repoPath).text}export async function openRepository(input){const{$:_$}=await import("bun");let repoPath,repoName;if(input.includes("github.com")||input.startsWith("git@")){repoName=input.split("/").pop()?.replace(".git","")||"repo";repoPath=join(CONFIG.workDir,repoName);if(existsSync(repoPath))await _$`cd ${repoPath} && git pull --rebase`.quiet();else await _$`git clone ${input} ${repoPath}`.quiet()}else{repoPath=input.startsWith("~")?input.replace("~",homedir()):input;if(!existsSync(repoPath))throw Error(`Local path does not exist: ${repoPath}`);if(!existsSync(join(repoPath,".git")))throw Error(`Not a git repository: ${repoPath}`);repoName=repoPath.split("/").pop()||"repo"}const branch=(await _$`cd ${repoPath} && git branch --show-current`.quiet()).text().trim(),hasChanges=(await _$`cd ${repoPath} && git status --porcelain`.quiet()).text().trim().length>0,lastCommit=(await _$`cd ${repoPath} && git log -1 --format="%h %s"`.quiet()).text().trim(),repoState={path:repoPath,name:repoName,branch,hasChanges,lastCommit};buddyState.setRepo(repoState);buddyState.clearHistory();return repoState}export async function applyChanges(aiResponse){const currentState=buddyState.getState();if(!currentState.repo)throw Error("No repository opened");const modifiedFiles=[],filePattern=/FILE:\s*([^\n]+)\n```\w*\n([\s\S]*?)```/g;for(const match of aiResponse.matchAll(filePattern)){const filePath=match[1].trim(),content=match[2],fullPath=join(currentState.repo.path,filePath),dir=dirname(fullPath);if(!existsSync(dir))mkdirSync(dir,{recursive:!0});writeFileSync(fullPath,content);modifiedFiles.push(filePath)}if(modifiedFiles.length>0&&currentState.repo)currentState.repo.hasChanges=!0;return modifiedFiles}export async function configureGitUser(){const currentState=buddyState.getState();if(!currentState.repo||!currentState.github)return;const{$:_$}=await import("bun"),{name,email}=currentState.github;await _$`cd ${currentState.repo.path} && git config user.name ${name}`.quiet();await _$`cd ${currentState.repo.path} && git config user.email ${email}`.quiet()}export async function commitChanges(){const currentState=buddyState.getState();if(!currentState.repo)throw Error("No repository opened");const{$:_$}=await import("bun");if(currentState.github)await configureGitUser();await _$`cd ${currentState.repo.path} && git add -A`.quiet();await _$`cd ${currentState.repo.path} && git commit -m ${CONFIG.commitMessage}`.quiet();const commitHash=(await _$`cd ${currentState.repo.path} && git rev-parse --short HEAD`.quiet()).text().trim();currentState.repo.hasChanges=!1;currentState.repo.lastCommit=commitHash;return commitHash}export async function pushChanges(){const currentState=buddyState.getState();if(!currentState.repo)throw Error("No repository opened");const{$:_$}=await import("bun");await _$`cd ${currentState.repo.path} && git push`.quiet()}export async function processCommand(command,driverName){const currentState=buddyState.getState();if(!currentState.repo)throw Error("No repository opened");const normalizedDriver=driverName||currentState.currentDriver,driver=getDriver(normalizedDriver);if(driverName)buddyState.setCurrentDriver(driverName);const context=await getRepoContext(currentState.repo.path),systemPrompt=buildSystemPrompt(context),response=await driver.process(command,systemPrompt,currentState.conversationHistory);buddyState.addToHistory({role:"user",content:command});buddyState.addToHistory({role:"assistant",content:response});return response}export async function buddyProcessStreaming(command,driverName,history){const currentState=buddyState.getState();if(!currentState.repo)throw Error("No repository opened");const normalizedDriver=driverName||currentState.currentDriver,streamingDrivers=["claude-cli-local","claude-sdk"];if(!streamingDrivers.includes(normalizedDriver))throw Error(`Streaming only supported for ${streamingDrivers.join(", ")} drivers. Current: ${normalizedDriver}`);if(driverName)buddyState.setCurrentDriver(driverName);let contextualCommand=command;if(history&&history.length>0){let conversationContext=`## Previous Conversation
272
73
  Here is our conversation so far:
273
74
 
274
- `;
275
- for (const msg of history) {
276
- const role = msg.role === "user" ? "User" : "Assistant";
277
- conversationContext += `**${role}:** ${msg.content}
75
+ `;for(const msg of history){const role=msg.role==="user"?"User":"Assistant";conversationContext+=`**${role}:** ${msg.content}
278
76
 
279
- `;
280
- }
281
- conversationContext += `---
77
+ `}conversationContext+=`---
282
78
 
283
79
  ## Current Request
284
- `;
285
- contextualCommand = conversationContext + command;
286
- }
287
- const result = await claudeAgent.processStreaming(contextualCommand, currentState.repo.path);
288
- result.fullResponse.then((response) => {
289
- buddyState.addToHistory({ role: "user", content: command });
290
- buddyState.addToHistory({ role: "assistant", content: response });
291
- });
292
- return result;
293
- }
294
- export async function buddyStreamSimple(command, history) {
295
- if (!apiKeys.anthropic)
296
- throw Error("Anthropic API key not set. Configure your API key in settings.");
297
- const messages = [];
298
- if (history && history.length > 0)
299
- for (const msg of history)
300
- messages.push({
301
- role: msg.role,
302
- content: msg.content
303
- });
304
- messages.push({ role: "user", content: command });
305
- const systemPrompt = `You are a helpful AI assistant. Answer questions naturally and conversationally.
80
+ `;contextualCommand=conversationContext+command}const result=await claudeAgent.processStreaming(contextualCommand,currentState.repo.path);result.fullResponse.then((response)=>{buddyState.addToHistory({role:"user",content:command});buddyState.addToHistory({role:"assistant",content:response})});return result}export async function buddyStreamSimple(command,history){if(!apiKeys.anthropic)throw Error("Anthropic API key not set. Configure your API key in settings.");const messages=[];if(history&&history.length>0)for(const msg of history)messages.push({role:msg.role,content:msg.content});messages.push({role:"user",content:command});const systemPrompt=`You are a helpful AI assistant. Answer questions naturally and conversationally.
306
81
  You can discuss any topic - technology, science, philosophy, everyday questions, or anything else the user asks about.
307
- Be concise but thorough. If the user asks about coding or their project specifically, help with that too.`, encoder = new TextEncoder;
308
- let fullResponse = "", resolveFullResponse;
309
- const fullResponsePromise = new Promise((resolve) => {
310
- resolveFullResponse = resolve;
311
- });
312
- return {
313
- stream: new ReadableStream({
314
- async start(controller) {
315
- try {
316
- const response = await fetch("https://api.anthropic.com/v1/messages", {
317
- method: "POST",
318
- headers: {
319
- "Content-Type": "application/json",
320
- "x-api-key": apiKeys.anthropic,
321
- "anthropic-version": "2023-06-01"
322
- },
323
- body: JSON.stringify({
324
- model: "claude-sonnet-4-20250514",
325
- max_tokens: 4096,
326
- system: systemPrompt,
327
- stream: !0,
328
- messages
329
- })
330
- });
331
- if (!response.ok) {
332
- const error = await response.text();
333
- throw Error(`Claude API error: ${error}`);
334
- }
335
- const reader = response.body?.getReader();
336
- if (!reader)
337
- throw Error("No response body");
338
- const decoder = new TextDecoder;
339
- let buffer = "";
340
- while (!0) {
341
- const { done, value } = await reader.read();
342
- if (done)
343
- break;
344
- buffer += decoder.decode(value, { stream: !0 });
345
- const lines = buffer.split(`
346
- `);
347
- buffer = lines.pop() || "";
348
- for (const line of lines)
349
- if (line.startsWith("data: ")) {
350
- const data = line.slice(6);
351
- if (data === "[DONE]")
352
- continue;
353
- try {
354
- const event = JSON.parse(data);
355
- if (event.type === "content_block_delta" && event.delta?.text) {
356
- const text = event.delta.text;
357
- fullResponse += text;
358
- controller.enqueue(encoder.encode(text));
359
- }
360
- } catch {}
361
- }
362
- }
363
- buddyState.addToHistory({ role: "user", content: command });
364
- buddyState.addToHistory({ role: "assistant", content: fullResponse });
365
- resolveFullResponse(fullResponse);
366
- controller.close();
367
- } catch (error) {
368
- resolveFullResponse(fullResponse);
369
- controller.error(error);
370
- }
371
- }
372
- }),
373
- fullResponse: fullResponsePromise
374
- };
375
- }
82
+ Be concise but thorough. If the user asks about coding or their project specifically, help with that too.`,encoder=new TextEncoder;let fullResponse="",resolveFullResponse;const fullResponsePromise=new Promise((resolve)=>{resolveFullResponse=resolve});return{stream:new ReadableStream({async start(controller){try{const response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKeys.anthropic,"anthropic-version":"2023-06-01"},body:JSON.stringify({model:"claude-sonnet-4-20250514",max_tokens:4096,system:systemPrompt,stream:!0,messages})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const reader=response.body?.getReader();if(!reader)throw Error("No response body");const decoder=new TextDecoder;let buffer="";while(!0){const{done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:!0});const lines=buffer.split(`
83
+ `);buffer=lines.pop()||"";for(const line of lines)if(line.startsWith("data: ")){const data=line.slice(6);if(data==="[DONE]")continue;try{const event=JSON.parse(data);if(event.type==="content_block_delta"&&event.delta?.text){const text=event.delta.text;fullResponse+=text;controller.enqueue(encoder.encode(text))}}catch{}}}buddyState.addToHistory({role:"user",content:command});buddyState.addToHistory({role:"assistant",content:fullResponse});resolveFullResponse(fullResponse);controller.close()}catch(error){resolveFullResponse(fullResponse);controller.error(error)}}}),fullResponse:fullResponsePromise}}