Compare commits

...

3 Commits

6 changed files with 122 additions and 21 deletions

3
.devcontainer.json Normal file
View File

@@ -0,0 +1,3 @@
{
"image": "mcr.microsoft.com/devcontainers/javascript-node"
}

16
cli.js
View File

@@ -71,13 +71,23 @@ while(true){
let counter = 0;
// Block for user input before kicking off the LLM loop
const userText = await askUserForInput();
await streamOnce(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), userText || '');
await streamOnce(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), userText );
async function streamOnce(openai, userText) {
const toolsByFile = await loadTools();
let previousResponseId;
let input = [
{"role": "developer", "content": [ {"type": "input_text","text": `You are an interactive CLI AI assistant. Follow the user's instructions.` }] },
{"role": "developer", "content": [ {
"type": "input_text","text": `You are an interactive CLI AI assistant. Follow the user's instructions.
If a tool is available and relevant, plan to use it.
Tools:
list_files - (no/empty path means root)
patch_files - (zum anlegen, ändern und löschen von Dateien)
read_file - (nach zeilen)
ripgrep - suchmusater und dateimuster
websearch - eine Google Suche machen mit Schlüsselwörtern`
}] },
{"role": "user", "content": [ {"type": "input_text","text": userText } ]},
]
@@ -142,7 +152,7 @@ while(true){
stream.on('response.completed', async (event) => {
printIndented(10,renderUsage(event.response.usage));
if (event.response.output.filter(i => i.type === 'message').length > 0) printIndented(10, "Textresult:",event.response.output.filter(i => i.type === 'message').map(i => i.content[0].text));
if (event.response.output.filter(i => i.type === 'message').length > 0) printIndented(10,event.response.output.filter(i => i.type === 'message').map(i => i.content[0].text).join('\n'));
});
await Array.fromAsync(stream);

74
package-lock.json generated
View File

@@ -24,6 +24,7 @@
"es-set-tostringtag": "^2.1.0",
"esbuild": "^0.25.8",
"event-target-shim": "^5.0.1",
"exa-js": "^1.8.27",
"form-data": "^4.0.4",
"form-data-encoder": "^1.7.2",
"formdata-node": "^4.4.1",
@@ -723,6 +724,15 @@
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/cross-fetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.7.0"
}
},
"node_modules/cwise-compiler": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz",
@@ -899,6 +909,52 @@
"node": ">=6"
}
},
"node_modules/exa-js": {
"version": "1.8.27",
"resolved": "https://registry.npmjs.org/exa-js/-/exa-js-1.8.27.tgz",
"integrity": "sha512-C0vsC3r5B1vhibXAxtJ9OlCwXTp+AaWFPk3ozLoUBV2VH+Xd+WhkKp8/30i9mkBVJopK/8+lDMv8MyRjkOWm5g==",
"license": "MIT",
"dependencies": {
"cross-fetch": "~4.1.0",
"dotenv": "~16.4.7",
"openai": "^5.0.1",
"zod": "^3.22.0",
"zod-to-json-schema": "^3.20.0"
}
},
"node_modules/exa-js/node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/exa-js/node_modules/openai": {
"version": "5.12.2",
"resolved": "https://registry.npmjs.org/openai/-/openai-5.12.2.tgz",
"integrity": "sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ==",
"license": "Apache-2.0",
"bin": {
"openai": "bin/cli"
},
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
@@ -1780,6 +1836,24 @@
"resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
"integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
"license": "MIT"
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-to-json-schema": {
"version": "3.24.6",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
"license": "ISC",
"peerDependencies": {
"zod": "^3.24.1"
}
}
}
}

View File

@@ -19,6 +19,7 @@
"es-set-tostringtag": "^2.1.0",
"esbuild": "^0.25.8",
"event-target-shim": "^5.0.1",
"exa-js": "^1.8.27",
"form-data": "^4.0.4",
"form-data-encoder": "^1.7.2",
"formdata-node": "^4.4.1",

View File

@@ -1,16 +0,0 @@
You are an interactive CLI AI assistant. Follow the user's instructions.
If a tool is available and relevant, plan to use it.
Be explicit when information is undefined.
Do not silently fall back: surface errors.
Prefer concise answers.
Developer rules:
- Null tells the truth. If data is missing/undefined, say so; do not invent values.
- In development, never hide errors; include warnings if using fallbacks.
Behavior:
- Answer succinctly.
- Ask for clarification when the user input is ambiguous.
- Output plain text suitable for a terminal.

29
tools/websearch.js Normal file
View File

@@ -0,0 +1,29 @@
import Exa from "exa-js";
const exa = new Exa("1513ba88-5280-402b-9da3-e060d38f96d8");
export default {
type: 'function',
name: 'websearch',
description: 'Perform a google web search.',
strict: true,
parameters: {
type: 'object',
required: ['query'],
additionalProperties: false,
properties: {
query: { type: 'string', description: 'The search query.' }
}
}
};
export async function run(args) {
try
{
console.log('Google search: ', args.query);
const result = await exa.search( args.query,{ type: "auto", userLocation: "DE", numResults: 20} );
console.log('Google search result: ', result.results[0]);
return result;
} catch (error) {
return `websearch error: ${error?.message || String(error)}`;
}
}