@avcodes/mi
Advanced tools
+16
-18
@@ -8,3 +8,3 @@ #!/usr/bin/env node | ||
| */ | ||
| import { createInterface } from 'readline'; import { spawn } from 'child_process'; import { readFileSync, writeFileSync, existsSync } from 'fs'; import { homedir } from 'os'; if (!process.env.OPENAI_API_KEY && !process.argv.includes('-h')) { console.error('OPENAI_API_KEY required'); process.exit(1); } | ||
| import { createInterface } from 'readline'; import { spawn } from 'child_process'; import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs'; import { homedir } from 'os'; const DIR = new URL('.', import.meta.url).pathname; if (!process.env.OPENAI_API_KEY && !process.argv.includes('-h')) { console.error('OPENAI_API_KEY required'); process.exit(1); } | ||
@@ -15,3 +15,3 @@ /* Tools the agent can invoke. */ | ||
| /* Run a command in a detached bash shell; resolve with combined output. */ | ||
| bash: ({command}) => new Promise(resolve => { const child = spawn('bash', ['-c', command], { stdio: ['ignore', 'pipe', 'pipe'], detached: true }); | ||
| bash: ({command,timeout,bg}) => { if (bg) { const log=`/tmp/mi-${Date.now()}.log`; const c=spawn('bash',['-c',`${command} >${log} 2>&1`],{stdio:'ignore',detached:true}); c.unref(); return `pid:${c.pid} log:${log}`; } return new Promise(resolve => { const child = spawn('bash', ['-c', command], { stdio: ['ignore', 'pipe', 'pipe'], detached: true }); | ||
@@ -22,6 +22,6 @@ /* Collect stdout and stderr into a single string. */ | ||
| /* Kill the process group on SIGINT; remove the listener on exit. */ | ||
| const cleanup = () => { try { process.kill(-child.pid) } catch (err) {} }; process.on('SIGINT', cleanup); | ||
| const cleanup = () => { try { process.kill(-child.pid) } catch (err) {} }; process.on('SIGINT', cleanup); const timer = timeout ? setTimeout(() => { cleanup(); resolve(output+'\n[timeout]') }, +timeout) : null; | ||
| /* Resolve with collected output once the child process exits. */ | ||
| child.on('exit', () => { process.off('SIGINT', cleanup); resolve(output); }); }), | ||
| child.on('exit', () => { process.off('SIGINT', cleanup); if (timer) clearTimeout(timer); resolve(output); }); }); }, | ||
@@ -34,9 +34,10 @@ /* Read a file as UTF-8. */ | ||
| /* Load a skill's SKILL.md from ~/.agents/skills/<name>/. */ | ||
| skill: ({name}) => readFileSync(`${process.env.HOME || homedir()}/.agents/skills/${name}/SKILL.md`, 'utf8') | ||
| /* Load a skill's SKILL.md by name, or list available skills as `- name: description` bullets parsed from YAML frontmatter. */ | ||
| skill: ({name}) => name ? loadSkill(name) : listSkills().join('\n'), | ||
| }; const makeParams = (...keys) => ({ type: 'object', properties: Object.fromEntries(keys.map(key => [key, { type: 'string' }])), required: keys }); | ||
| }; const meta = s => ({ name: s.match(/^name:\s*(.+)$/m)?.[1], description: s.match(/^description:\s*(.+)$/m)?.[1] || '' }), skillDirs = () => [`${DIR}skills/`, `${process.env.HOME || homedir()}/.agents/skills/`]; | ||
| const listSkills = () => skillDirs().flatMap(dir => existsSync(dir) ? readdirSync(dir).filter(d => existsSync(dir+d+'/SKILL.md')).map(d => { const {name,description} = meta(readFileSync(dir+d+'/SKILL.md','utf8')); return `- ${name||d}: ${description}`; }) : []), loadSkill = n => { for (const d of skillDirs()) if (existsSync(d+n+'/SKILL.md')) return readFileSync(d+n+'/SKILL.md','utf8'); }, makeParams = (...keys) => ({ type: 'object', properties: Object.fromEntries(keys.map(k => [k.replace('?',''), { type: 'string' }])), required: keys.filter(k => !k.startsWith('?')) }); | ||
| /* Tool definitions formatted for the OpenAI API. */ | ||
| const toolsDef = [{ name: 'bash', description: 'run bash cmd', parameters: makeParams('command') }, { name: 'read', description: 'read a file', parameters: makeParams('path') }, { name: 'write', description: 'write a file', parameters: makeParams('path', 'content') }, { name: 'skill', description: 'load skill', parameters: makeParams('name') }].map(func => ({ type: 'function', function: func })); | ||
| const toolsDef = [{ name: 'bash', description: 'run bash cmd; timeout=ms kills after delay, bg=truthy runs detached returning pid+log', parameters: makeParams('command', '?timeout', '?bg') }, { name: 'read', description: 'read a file', parameters: makeParams('path') }, { name: 'write', description: 'write a file', parameters: makeParams('path', 'content') }, { name: 'skill', description: 'load a skill\'s SKILL.md body by name', parameters: makeParams('?name') }].map(func => ({ type: 'function', function: func })); | ||
@@ -50,10 +51,7 @@ /* | ||
| /* POST to the completions endpoint; parse the JSON response. */ | ||
| const response = await fetch(`${(process.env.OPENAI_BASE_URL || 'https://api.openai.com').replace(/\/+$/, '')}/v1/chat/completions`, { method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: JSON.stringify({ model: process.env.MODEL || 'gpt-5.4', messages, tools: toolsDef }) }).then(res => res.json()); | ||
| const response = await fetch(`${(process.env.OPENAI_BASE_URL || 'https://api.openai.com').replace(/\/+$/, '')}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: JSON.stringify({ model: process.env.MODEL || 'gpt-5.4', messages, tools: toolsDef }) }).then(res => res.json()); | ||
| /* Throw on API error; return text content once no tool calls remain. */ | ||
| if (response.error) throw new Error(response.error.message || JSON.stringify(response.error)); const message = response.choices?.[0]?.message; if (!message) throw new Error(JSON.stringify(response)); | ||
| /* Throw on API error; push the message, return content once no tool calls remain. */ | ||
| if (response.error) throw new Error(response.error.message || JSON.stringify(response.error)); const message = response.choices?.[0]?.message; if (!message) throw new Error(JSON.stringify(response)); messages.push(message); if (!message.tool_calls) return message.content; | ||
| messages.push(message); if (!message.tool_calls) return message.content; | ||
| for (const toolCall of message.tool_calls) { | ||
@@ -69,3 +67,3 @@ const {name} = toolCall.function, args = JSON.parse(toolCall.function.arguments), formatDim = str => `\x1b[90m${str}\x1b[0m`; | ||
| /* System prompt: built-in instructions plus current directory and date. */ | ||
| const SYSTEM = (process.env.SYSTEM_PROMPT || 'You are an autonomous agent. Prefer action over speculation—use tools to answer questions and complete tasks.\nbash runs any shell command: curl/wget for HTTP, git, package managers, compilers, anything available on the system.\nread/write operate on local files. Always read before editing; write complete files.\nApproach: explore, plan, act one step at a time, verify. Be concise.') + `\nCWD: ${process.cwd()}\nDate: ${new Date().toISOString()}`; | ||
| const SYSTEM = (process.env.SYSTEM_PROMPT || 'You are an autonomous agent. Prefer action over speculation—use tools to answer questions and complete tasks.\nA skill is a SKILL.md file containing a procedure for a particular kind of task, paired with a one-line description of when it applies. The skill tool loads a skill\'s body by name.\nWhen handling a request, compare it against each skill description listed below. If a description covers what the user is asking for, call skill(name) to load that skill and follow its body as your plan.\nbash runs any shell command: curl/wget for HTTP, git, package managers, compilers, anything available on the system.\nread/write operate on local files. Always read before editing; write complete files.\nApproach: explore, plan, act one step at a time, verify. Be concise.') + `\nCWD: ${process.cwd()}\nDate: ${new Date().toISOString()}`; | ||
@@ -75,6 +73,6 @@ /* History seeded with the system prompt; getArg reads a named CLI flag. */ | ||
| if (process.argv.includes('-h')) { console.log('usage: mi [-p prompt] [-f file] [-h]\n pipe: echo "..." | mi repl: /reset clears history\nenv: OPENAI_API_KEY, MODEL, OPENAI_BASE_URL, SYSTEM_PROMPT'); process.exit(0); } | ||
| if (process.argv.includes('-h')) { console.log('usage: mi [-p prompt] [-f file] [-h]\n pipe: echo "..." | mi repl: /reset clears history\nenv: OPENAI_API_KEY, MODEL, OPENAI_BASE_URL, SYSTEM_PROMPT\nbash tool args: timeout=<ms> kills after delay · bg=truthy detaches and returns pid+log'); process.exit(0); } | ||
| /* Prepend -f file and AGENTS.md (if present) to the system message. */ | ||
| const fileArg = getArg('-f'); if (fileArg) history[0].content += `\n\nFile (${fileArg}):\n` + readFileSync(fileArg, 'utf8'); if (existsSync('AGENTS.md')) history[0].content += '\n' + readFileSync('AGENTS.md', 'utf8'); | ||
| /* Prepend -f file, AGENTS.md, and the skills index (if present) to the system message. */ | ||
| const fileArg = getArg('-f'); if (fileArg) history[0].content += `\n\nFile (${fileArg}):\n` + readFileSync(fileArg, 'utf8'); if (existsSync('AGENTS.md')) history[0].content += '\n' + readFileSync('AGENTS.md', 'utf8'); const sl = listSkills(); if (sl.length) history[0].content += '\n\nSkill descriptions:\n' + sl.join('\n'); | ||
@@ -81,0 +79,0 @@ if (getArg('-p')) { history.push({ role: 'user', content: getArg('-p') }); console.log(await run(history)); process.exit(0); } |
+2
-2
| { | ||
| "name": "@avcodes/mi", | ||
| "version": "1.0.7", | ||
| "description": "agentic coding in 30 loc. a loop, three tools, and an llm.", | ||
| "version": "1.0.9", | ||
| "description": "agentic coding in 30 loc. a loop, four tools, and an llm.", | ||
| "type": "module", | ||
@@ -6,0 +6,0 @@ "bin": { |
+4
-4
@@ -9,4 +9,4 @@  | ||
| - `bash`, `read`, `write`, and `skill` tools | ||
| - `skill` tool loads agent skills from `~/.agents/skills/` | ||
| - `bash` (optional `timeout=<ms>` kills after delay, `bg=truthy` detaches and returns pid+log), `read`, `write`, and `skill` tools | ||
| - `skill` tool loads `SKILL.md` playbooks from `~/.agents/skills/` (descriptions auto-advertised in system prompt so the model matches tasks to skills) | ||
| - accepts stdin via pipes (e.g. `echo "do this" | mi`) | ||
@@ -68,3 +68,3 @@ - file context ingestion via `-f <file>` argument | ||
| const tools = { | ||
| bash: ({ command }) => execShell(command), // run any shell command | ||
| bash: ({ command, timeout, bg }) => execShell(command, timeout, bg), // run any shell command | ||
| read: ({ path }) => readFileSync(path, 'utf8'), // read a file | ||
@@ -76,3 +76,3 @@ write: ({ path, content }) => (writeFileSync(path, content), 'ok'), // write a file | ||
| `bash` gives the agent access to the entire system: git, curl, compilers, package managers. `read` and `write` handle files. `skill` gives the agent specialized workflows. every tool returns a string because that's what goes back into the conversation. | ||
| `bash` gives the agent access to the entire system: git, curl, compilers, package managers. optional `timeout=<ms>` kills the process after the given delay and resolves with `[timeout]`. optional `bg=truthy` runs the command detached and returns `pid:X log:/tmp/mi-*.log` immediately. `read` and `write` handle files. `skill` gives the agent specialized workflows. every tool returns a string because that's what goes back into the conversation. | ||
@@ -79,0 +79,0 @@ ### tool definitions |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
15455
16.53%64
10.34%