Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@avcodes/mi

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@avcodes/mi - npm Package Compare versions

Comparing version
1.8.0
to
1.9.0
+16
-17
index.mjs

@@ -6,3 +6,3 @@ #!/usr/bin/env node

// Node builtins only — no npm deps. These four cover REPL, filesystem, subprocesses, and home directory.
import { createInterface } from 'readline'; import { readFileSync, existsSync, readdirSync } from 'fs'; import { spawn, execFileSync } from 'child_process'; import { homedir } from 'os';
import { createInterface } from 'readline'; import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs'; import { spawn, execFileSync } from 'child_process'; import { homedir } from 'os';
// Globals: tools run in a separate module scope but need fs/spawn — expose via global rather than re-importing.

@@ -12,3 +12,3 @@ // DIR = package root (for tool/skill discovery); MI_DIR/MI_PATH = env vars so tools can locate project assets.

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.MI_SANDBOXED && (process.argv.includes('--sandbox') || process.env.MI_SANDBOX)) { const img = process.env.MI_IMAGE || 'ghcr.io/av/mi:latest', args = process.argv.slice(2).filter(a => a !== '--sandbox'), da = ['run', '--rm', process.stdin.isTTY ? '-it' : '-i', '--network=host', '-e', `OPENAI_API_KEY=${process.env.OPENAI_API_KEY || ''}`, '-e', 'MI_SANDBOXED=1', '-v', `${process.cwd()}:/work`, '-w', '/work']; if (existsSync(rc)) da.push('-v', `${rc}:/home/mi/.mi/config.json:ro`); da.push(img, ...args); try { execFileSync('docker', da, { stdio: 'inherit' }); } catch (e) { process.exit(e.status || 1); } process.exit(0); } if (!process.env.OPENAI_API_KEY && !['-h','--help','-v','--version'].some(f => process.argv.includes(f))) { console.error('OPENAI_API_KEY required'); process.exit(1); }
if (!process.env.MI_SANDBOXED && (process.argv.includes('--sandbox') || process.env.MI_SANDBOX)) { const img = process.env.MI_IMAGE || 'ghcr.io/av/mi:latest', args = process.argv.slice(2).filter(a => a !== '--sandbox'), da = ['run', '--rm', process.stdin.isTTY ? '-it' : '-i', '--network=host', '-e', `OPENAI_API_KEY=${process.env.OPENAI_API_KEY || ''}`, '-e', 'MI_SANDBOXED=1', '-v', `${process.cwd()}:/work`, '-w', '/work']; if (existsSync(rc)) da.push('-v', `${rc}:/home/mi/.mi/config.json:ro`); da.push(img, ...args); try { execFileSync('docker', da, { stdio: 'inherit' }); } catch (e) { process.exit(e.status || 1); } process.exit(0); } if (!process.env.OPENAI_API_KEY && !['-h','--help','-v','--version'].some(f => process.argv.includes(f))) { console.error('OPENAI_API_KEY required'); process.exit(1); } if (process.env.MI_API_PARAMS) { try { JSON.parse(process.env.MI_API_PARAMS); } catch { console.error('MI_API_PARAMS: invalid JSON'); process.exit(1); } }

@@ -18,6 +18,7 @@ // ── Tool discovery ───────────────────────────────────────────────────

// ANSI helpers: 90 = bright black (gray), 31 = red (error), 38;5;208 = orange (brand)
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;
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();
await loadTools(); const fetchRetry = async (url, opts, n = 3) => { for (let i = 0;; i++) { try { const r = await fetch(url, opts); if ((r.status === 429 || r.status >= 500) && i < n) { await new Promise(w => setTimeout(w, 500 << i)); continue; } return r; } catch (e) { if (i >= n) throw e; await new Promise(w => setTimeout(w, 500 << i)); } } };
const isCtx = e => /^(context_length_exceeded|exceed_context_size_error)$/.test(e.code||e.error?.code||'') || /maximum context|context (length|window|size|limit)|prompt is too long|input is too long|too many (input )?tokens|exceeds the (context window|maximum.*tokens)/i.test(e.message||''), COMPACT_PROMPT = 'Summarize this conversation. Output sections: GOAL (user task), DONE (completed work), STATE (current state, files touched), NEXT (next steps), CONSTRAINTS (user preferences). Detailed but concise.';
async function compact(msgs) { const dump = `/tmp/mi-compact-${process.pid}-${Date.now()}.json`; const elided = msgs.map(m => m.role === 'tool' && m.content?.length > 2000 ? {...m, content: m.content.slice(0, 2000) + '…[elided]'} : m); writeFileSync(dump, JSON.stringify(elided)); const r = await fetchRetry(`${(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: [{role: 'system', content: COMPACT_PROMPT}, {role: 'user', content: JSON.stringify(elided)}], stream: false, chat_template_kwargs: {enable_thinking: false} }) }); if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error('compact failed: ' + (e.error?.message || `HTTP ${r.status}`)); } const summary = ((await r.json()).choices?.[0]?.message?.content || '').trim(); if (!summary) throw new Error('compact: empty summary'); console.log(gray(`⟡ compacted ${msgs.length}→2 (dump:${dump})`)); msgs.splice(1, msgs.length - 1, {role: 'user', content: '[Earlier turns compacted to free context. Recap:]\n\n' + summary + '\n\nContinue from this state.'}); }

@@ -27,7 +28,7 @@ // ── Agent loop: chat → stream → execute tools → repeat ──────────────

// model returns a plain text reply (no further tool invocations).
async function run(messages) { while (true) {
async function run(messages) { let compacted = false; while (true) { try {
// ─ Send streaming chat completion request ─
await loadTools(); 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: toolSchemas, stream: true, ...(process.env.REASONING_EFFORT && { reasoning_effort: process.env.REASONING_EFFORT }), ...(process.env.MI_API_PARAMS && JSON.parse(process.env.MI_API_PARAMS)) }) });
if (!response.ok) { const body = await response.json().catch(() => ({})); throw new Error(body.error?.message || `HTTP ${response.status}`); }
await loadTools(); const response = await fetchRetry(`${(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: toolSchemas, stream: true, ...(process.env.REASONING_EFFORT && { reasoning_effort: process.env.REASONING_EFFORT }), ...(process.env.MI_API_PARAMS && JSON.parse(process.env.MI_API_PARAMS)) }) });
if (!response.ok) { const body = await response.json().catch(() => ({})); const err = new Error(body.error?.message || `HTTP ${response.status}`); err.code = body.error?.code; throw err; }

@@ -43,3 +44,3 @@ // ─ Parse SSE stream: print content tokens, accumulate tool-call deltas by index ─

for await (const chunk of response.body) { buffer += decoder.decode(chunk, { stream: true }); let pos; while ((pos = buffer.indexOf('\n\n')) >= 0) { const event = buffer.slice(0, pos); buffer = buffer.slice(pos + 2); /* skip past \n\n delimiter */
for (const line of event.split('\n')) { if (!line.startsWith('data: ')) continue; const payload = line.slice(6); /* strip "data: " prefix */ if (payload === '[DONE]') continue; let json; try { json = JSON.parse(payload); } catch { continue; } if (json.error) throw new Error(json.error.message || JSON.stringify(json.error)); const delta = json.choices?.[0]?.delta; /* single choice; we never request n>1 */ if (!delta) continue; if (delta.content) { process.stdout.write(delta.content); message.content += delta.content; }
for (const line of event.split('\n')) { if (!line.startsWith('data: ')) continue; const payload = line.slice(6); /* strip "data: " prefix */ if (payload === '[DONE]') continue; let json; try { json = JSON.parse(payload); } catch { continue; } if (json.error) { const err = new Error(json.error.message || JSON.stringify(json.error)); err.code = json.error.code; throw err; } const delta = json.choices?.[0]?.delta; /* single choice; we never request n>1 */ if (!delta) continue; if (delta.content) { process.stdout.write(delta.content); message.content += delta.content; }
if (delta.tool_calls) { message.tool_calls ||= []; for (const tc of delta.tool_calls) { const slot = message.tool_calls[tc.index] ||= { id: '', type: 'function', function: { name: '', arguments: '' } }; if (tc.id) slot.id = tc.id; if (tc.type) slot.type = tc.type; const fn = tc.function; if (fn?.name) slot.function.name += fn.name; if (fn?.arguments) slot.function.arguments += fn.arguments; } } } } } if (message.content) process.stdout.write('\n'); messages.push(message); if (!message.tool_calls) return;

@@ -51,3 +52,3 @@

// Log truncated to 200 chars for terminal readability; the model gets the full result.
console.log(gray(result.length > 200 ? `${result.slice(0, 200)}…` : result)); messages.push({ role: 'tool', tool_call_id: toolCall.id, content: result }); } } }
console.log(gray(result.length > 200 ? `${result.slice(0, 200)}…` : result)); messages.push({ role: 'tool', tool_call_id: toolCall.id, content: result }); } } catch (e) { if (compacted || !isCtx(e)) throw e; compacted = true; await compact(messages); } } }

@@ -63,14 +64,12 @@ // ── System prompt ────────────────────────────────────────────────────

// 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 }];
const getArg = key => { const i = process.argv.indexOf(key); return i >= 0 && process.argv[i + 1]; };
const history = [{ role: 'system', content: SYSTEM }], getArg = key => { const i = process.argv.indexOf(key); return i >= 0 && process.argv[i + 1]; };
const ver = JSON.parse(readFileSync(`${DIR}package.json`, 'utf8')).version; if (['-v','--version'].some(f => process.argv.includes(f))) { console.log(ver); process.exit(0); } if (['-h','--help'].some(f => process.argv.includes(f))) { console.log(`mi ${ver}\n\nusage: mi [-p prompt] [-g goal [-c check]] [-f file] [--sandbox] [-v]\n\nmodes:\n mi -p "prompt" one-shot\n mi -g "goal" goal loop (iterate until judge ACKs)\n echo "..." | mi pipe\n mi repl (/reset, /new, /clear all reset history)\n\nflags:\n -p <prompt> run prompt and exit\n -g <goal> iterate subagents toward goal until judge ACKs\n -c <check> judge prompt (default: goal text). judge is a mi\n subagent that inspects files/runs commands, then\n responds ACK or NACK\n -f <file> attach file to context\n --sandbox run in docker\n -v, --version print version\n -h, --help show this help\n\nenv: OPENAI_API_KEY MODEL OPENAI_BASE_URL REASONING_EFFORT SYSTEM_PROMPT MI_HOME MI_IMAGE MI_API_PARAMS`); process.exit(0); }
const ver = JSON.parse(readFileSync(`${DIR}package.json`, 'utf8')).version; if (['-v','--version'].some(f => process.argv.includes(f))) { console.log(ver); process.exit(0); } if (['-h','--help'].some(f => process.argv.includes(f))) { console.log(`mi ${ver}\n\nusage: mi [-p prompt] [-g goal [-c check]] [-f file] [--sandbox] [-v]\n\nmodes:\n mi -p "prompt" one-shot\n mi -g "goal" goal loop (iterate until judge ACKs)\n echo "..." | mi pipe\n mi repl (/reset, /new, /clear all reset history)\n\nflags:\n -p <prompt> run prompt and exit\n -g <goal> iterate subagents toward goal until judge ACKs\n -c <check> judge prompt (default: goal text). judge is a mi\n subagent that inspects files/runs commands, then\n responds ACK or NACK\n -f <file> attach file(s) to context (repeatable, supports images)\n --sandbox run in docker\n -v, --version print version\n -h, --help show this help\n\nenv: OPENAI_API_KEY MODEL OPENAI_BASE_URL REASONING_EFFORT SYSTEM_PROMPT MI_HOME MI_IMAGE MI_API_PARAMS`); 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')}`;
const sysMsg = history[0], imgRe = /\.(png|jpg|jpeg|gif|webp)$/i, files = process.argv.reduce((a, v, i) => { const nxt = process.argv[i + 1]; return v === '-f' && nxt && !nxt.startsWith('-') ? [...a, nxt] : a; }, []), imgs = []; files.forEach(f => { if (!existsSync(f)) { console.error(`error: file not found: ${f}`); process.exit(1); } imgRe.test(f) ? imgs.push({type: 'image_url', image_url: {url: `data:image/${f.split('.').pop().toLowerCase().replace('jpg', 'jpeg')};base64,${readFileSync(f).toString('base64')}`}}) : sysMsg.content += `\n\nFile (${f}):\n${readFileSync(f, 'utf8')}`; }); if (imgs.length) history.push({role: 'user', content: [{type: 'text', text: 'attached images'}, ...imgs]});
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; 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); }
const goalArg = getArg('-g'); if (goalArg) { await (await import(`${DIR}tools/goal.mjs?v=${++loadId}`)).default.handler({ goal: goalArg, check: getArg('-c') || goalArg }); process.exit(0); }

@@ -85,2 +84,2 @@

// Error recovery: pop the failed user message so the model never sees it
readLine.on('close', () => process.exit(0)); while (true) { const input = await promptUser('\n> '); if (['/reset','/new','/clear'].includes(input)) { history.splice(1); /* keep system prompt at [0] */ console.log(gray('✓ reset')); continue; } if (input.trim()) { history.push({ role: 'user', content: input }); process.stdout.write(`${gray('─────')}\n`); try { await run(history); } catch (error) { console.error(red(`✗ ${error.message}`)); history.pop(); } } }
readLine.on('close', () => process.exit(0)); while (true) { const input = await promptUser('\n> '); if (['/reset','/new','/clear'].includes(input)) { history.splice(1); console.log(gray('✓ reset')); continue; } if (input === '/help') { console.log(gray('commands: /reset /new /clear /help\nenv: MODEL OPENAI_BASE_URL REASONING_EFFORT SYSTEM_PROMPT MI_HOME MI_API_PARAMS')); continue; } if (input.trim()) { history.push({ role: 'user', content: input }); process.stdout.write(`${gray('─────')}\n`); try { await run(history); } catch (error) { console.error(red(`✗ ${error.message}`)); history.pop(); } } }
{
"name": "@avcodes/mi",
"version": "1.8.0",
"version": "1.9.0",
"description": "agentic coding in 30 loc. a loop, two tools, and an llm.",

@@ -5,0 +5,0 @@ "type": "module",