@avcodes/mi
Advanced tools
+12
-5
@@ -9,3 +9,5 @@ #!/usr/bin/env node | ||
| // DIR = package root (for tool/skill discovery); MI_DIR/MI_PATH = env vars so tools can locate project assets. | ||
| Object.assign(global, { spawn, readFileSync, existsSync, readdirSync, homedir }); const DIR = new URL('.', import.meta.url).pathname; Object.assign(process.env, { MI_DIR: DIR, MI_PATH: new URL(import.meta.url).pathname }); const rc = `${homedir()}/.mirc`; if (existsSync(rc)) Object.entries(JSON.parse(readFileSync(rc, 'utf8'))).forEach(([k, v]) => process.env[k] ||= v); if (!process.env.OPENAI_API_KEY && !process.argv.includes('-h') && !process.argv.includes('--help')) { console.error('OPENAI_API_KEY required'); process.exit(1); } | ||
| Object.assign(global, { spawn, readFileSync, existsSync, readdirSync, homedir }); const DIR = new URL('.', import.meta.url).pathname; Object.assign(process.env, { MI_DIR: DIR, MI_PATH: new URL(import.meta.url).pathname }); | ||
| const MI_HOME = process.env.MI_HOME || `${homedir()}/.mi`, rc = `${MI_HOME}/config.json`; if (existsSync(rc)) Object.entries(JSON.parse(readFileSync(rc, 'utf8'))).forEach(([k, v]) => process.env[k] ||= v); | ||
| if (!process.env.OPENAI_API_KEY && !process.argv.includes('-h') && !process.argv.includes('--help')) { console.error('OPENAI_API_KEY required'); process.exit(1); } | ||
@@ -16,3 +18,5 @@ // ── Tool discovery ─────────────────────────────────────────────────── | ||
| const gray = s => `\x1b[90m${s}\x1b[0m`, red = s => `\x1b[31m${s}\x1b[0m`, orange = s => `\x1b[38;5;208m${s}\x1b[0m`; | ||
| let tools, toolSchemas, listSkills, loadId = 0; async function loadTools() { const toolMods = await Promise.all(readdirSync(`${DIR}tools`).filter(file => file.endsWith('.mjs')).map(file => import(`${DIR}tools/${file}?v=${++loadId}`))), defs = toolMods.map(mod => mod.default); tools = Object.fromEntries(defs.map(def => [def.name, def.handler])); toolSchemas = defs.map(def => ({ type: 'function', function: { name: def.name, description: def.description, parameters: def.parameters } })); listSkills = toolMods.find(mod => mod.listSkills)?.listSkills; } await loadTools(); | ||
| let tools, toolSchemas, listSkills, loadId = 0; | ||
| async function loadTools() { const toolMods = await Promise.all(readdirSync(`${DIR}tools`).filter(file => file.endsWith('.mjs')).map(file => import(`${DIR}tools/${file}?v=${++loadId}`))), defs = toolMods.map(mod => mod.default); tools = Object.fromEntries(defs.map(def => [def.name, def.handler])); toolSchemas = defs.map(def => ({ type: 'function', function: { name: def.name, description: def.description, parameters: def.parameters } })); listSkills = toolMods.find(mod => mod.listSkills)?.listSkills; } | ||
| await loadTools(); | ||
@@ -55,3 +59,4 @@ // ── Agent loop: chat → stream → execute tools → repeat ────────────── | ||
| // Uses short-circuit: indexOf returns -1 when missing, so `i >= 0 && argv[i + 1]` is false without a flag. | ||
| const history = [{ role: 'system', content: SYSTEM }], getArg = key => { const i = process.argv.indexOf(key); return i >= 0 && process.argv[i + 1]; }; | ||
| const history = [{ role: 'system', content: SYSTEM }]; | ||
| const getArg = key => { const i = process.argv.indexOf(key); return i >= 0 && process.argv[i + 1]; }; | ||
@@ -61,6 +66,8 @@ if (process.argv.includes('-h') || process.argv.includes('--help')) { 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, REASONING_EFFORT, SYSTEM_PROMPT\nbash tool args: timeout=<ms> kills after delay · bg=truthy detaches and returns pid+log'); process.exit(0); } | ||
| // Append -f file contents, AGENTS.md (auto-ingested repo context), and skill summaries to system message. | ||
| const sysMsg = history[0], fileArg = getArg('-f'); if (fileArg) sysMsg.content += `\n\nFile (${fileArg}):\n${readFileSync(fileArg, 'utf8')}`; if (existsSync('AGENTS.md')) sysMsg.content += `\n${readFileSync('AGENTS.md', 'utf8')}`; const skills = listSkills(); if (skills.length) sysMsg.content += `\n\nSkill descriptions:\n${skills.join('\n')}`; | ||
| const sysMsg = history[0], fileArg = getArg('-f'); if (fileArg) sysMsg.content += `\n\nFile (${fileArg}):\n${readFileSync(fileArg, 'utf8')}`; | ||
| if (existsSync('AGENTS.md')) sysMsg.content += `\n${readFileSync('AGENTS.md', 'utf8')}`; const skills = listSkills(); if (skills.length) sysMsg.content += `\n\nSkill descriptions:\n${skills.join('\n')}`; | ||
| // ── One-shot modes: -p flag and stdin pipe ─────────────────────────── | ||
| const prompt = getArg('-p'); if (prompt) { history.push({ role: 'user', content: prompt }); await run(history); process.exit(0); } if (!process.stdin.isTTY) { let input = ''; for await (const chunk of process.stdin) input += chunk; /* Buffer auto-coerces to string via += */ history.push({ role: 'user', content: input.trim() }); await run(history); process.exit(0); } | ||
| const prompt = getArg('-p'); if (prompt) { history.push({ role: 'user', content: prompt }); await run(history); process.exit(0); } | ||
| if (!process.stdin.isTTY) { let input = ''; for await (const chunk of process.stdin) input += chunk; history.push({ role: 'user', content: input.trim() }); await run(history); process.exit(0); } | ||
@@ -67,0 +74,0 @@ // ── Interactive REPL ───────────────────────────────────────────────── |
+2
-2
| { | ||
| "name": "@avcodes/mi", | ||
| "version": "1.7.1", | ||
| "version": "1.7.2", | ||
| "description": "agentic coding in 30 loc. a loop, two tools, and an llm.", | ||
@@ -25,3 +25,3 @@ "type": "module", | ||
| "test": "node --test tests/test.js", | ||
| "lines": "node scripts/count-lines.mjs index.mjs tools/*.mjs" | ||
| "lines": "node scripts/count-lines.mjs index.mjs" | ||
| }, | ||
@@ -28,0 +28,0 @@ "license": "MIT", |
+4
-3
@@ -21,3 +21,3 @@ [](https://www.youtube.com/watch?v=JdMBn7FXilg) | ||
| - graceful `SIGINT` handling for bash child processes | ||
| - optional `~/.mirc` JSON config file (env vars always override) | ||
| - optional `~/.mi/config.json` config file (env vars always override) | ||
@@ -56,3 +56,3 @@ ## install | ||
| `~/.mirc` is an optional JSON config file. keys become env var defaults — your shell env always wins. | ||
| `~/.mi/config.json` is an optional JSON config file. keys become env var defaults — your shell env always wins. | ||
@@ -67,3 +67,3 @@ ```json | ||
| any env var that mi reads can be set here: `OPENAI_API_KEY`, `MODEL`, `OPENAI_BASE_URL`, `REASONING_EFFORT`, `SYSTEM_PROMPT`. | ||
| any env var that mi reads can be set here: `OPENAI_API_KEY`, `MODEL`, `OPENAI_BASE_URL`, `REASONING_EFFORT`, `SYSTEM_PROMPT`. the config directory can be overridden with `MI_HOME`. | ||
@@ -79,2 +79,3 @@ ## env | ||
| | `SYSTEM_PROMPT` | built-in agent prompt | override the system prompt entirely | | ||
| | `MI_HOME` | `~/.mi` | config directory (reads `config.json`) | | ||
@@ -81,0 +82,0 @@ ## deep dive |
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 9 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
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 8 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
63321
0.23%106
2.91%205
0.49%15
7.14%