miii-cli 0.1.0
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.
- package/.claude/settings.local.json +18 -0
- package/Makefile +13 -0
- package/README.md +182 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +24 -0
- package/dist/config.js.map +1 -0
- package/dist/files/ops.d.ts +11 -0
- package/dist/files/ops.js +66 -0
- package/dist/files/ops.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +32 -0
- package/dist/init.js.map +1 -0
- package/dist/llm/ollama.d.ts +9 -0
- package/dist/llm/ollama.js +51 -0
- package/dist/llm/ollama.js.map +1 -0
- package/dist/llm/stream.d.ts +12 -0
- package/dist/llm/stream.js +129 -0
- package/dist/llm/stream.js.map +1 -0
- package/dist/parser/stream-parser.d.ts +17 -0
- package/dist/parser/stream-parser.js +54 -0
- package/dist/parser/stream-parser.js.map +1 -0
- package/dist/sessions.d.ts +9 -0
- package/dist/sessions.js +48 -0
- package/dist/sessions.js.map +1 -0
- package/dist/skills/loader.d.ts +23 -0
- package/dist/skills/loader.js +91 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +79 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tui/App.d.ts +9 -0
- package/dist/tui/App.js +259 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/InputBar.d.ts +10 -0
- package/dist/tui/InputBar.js +289 -0
- package/dist/tui/InputBar.js.map +1 -0
- package/dist/tui/components/AtPicker.d.ts +8 -0
- package/dist/tui/components/AtPicker.js +19 -0
- package/dist/tui/components/AtPicker.js.map +1 -0
- package/dist/tui/components/CommandPalette.d.ts +8 -0
- package/dist/tui/components/CommandPalette.js +25 -0
- package/dist/tui/components/CommandPalette.js.map +1 -0
- package/dist/tui/components/InputArea.d.ts +11 -0
- package/dist/tui/components/InputArea.js +268 -0
- package/dist/tui/components/InputArea.js.map +1 -0
- package/dist/tui/components/MessageList.d.ts +10 -0
- package/dist/tui/components/MessageList.js +98 -0
- package/dist/tui/components/MessageList.js.map +1 -0
- package/dist/tui/components/ModelPicker.d.ts +18 -0
- package/dist/tui/components/ModelPicker.js +74 -0
- package/dist/tui/components/ModelPicker.js.map +1 -0
- package/dist/tui/components/StatusBar.d.ts +12 -0
- package/dist/tui/components/StatusBar.js +15 -0
- package/dist/tui/components/StatusBar.js.map +1 -0
- package/dist/tui/printer.d.ts +7 -0
- package/dist/tui/printer.js +106 -0
- package/dist/tui/printer.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/workers/context.worker.d.ts +1 -0
- package/dist/workers/context.worker.js +69 -0
- package/dist/workers/context.worker.js.map +1 -0
- package/dist/workers/diff.worker.d.ts +1 -0
- package/dist/workers/diff.worker.js +12 -0
- package/dist/workers/diff.worker.js.map +1 -0
- package/dist/workers/spawn.d.ts +1 -0
- package/dist/workers/spawn.js +18 -0
- package/dist/workers/spawn.js.map +1 -0
- package/install.sh +6 -0
- package/package.json +29 -0
- package/src/config.ts +25 -0
- package/src/files/ops.ts +71 -0
- package/src/index.ts +11 -0
- package/src/init.ts +39 -0
- package/src/llm/ollama.ts +58 -0
- package/src/llm/stream.ts +118 -0
- package/src/parser/stream-parser.ts +54 -0
- package/src/sessions.ts +46 -0
- package/src/skills/loader.ts +109 -0
- package/src/tools/index.ts +83 -0
- package/src/tui/App.tsx +308 -0
- package/src/tui/InputBar.tsx +347 -0
- package/src/tui/components/AtPicker.tsx +49 -0
- package/src/tui/components/CommandPalette.tsx +50 -0
- package/src/tui/components/InputArea.tsx +285 -0
- package/src/tui/components/MessageList.tsx +192 -0
- package/src/tui/components/ModelPicker.tsx +134 -0
- package/src/tui/components/StatusBar.tsx +36 -0
- package/src/tui/printer.ts +121 -0
- package/src/types.ts +25 -0
- package/src/workers/context.worker.ts +62 -0
- package/src/workers/diff.worker.ts +20 -0
- package/src/workers/spawn.ts +19 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// ANSI-formatted stdout output — goes into terminal scrollback
|
|
2
|
+
const R = '\x1b[0m';
|
|
3
|
+
const BOLD = '\x1b[1m';
|
|
4
|
+
const DIM = '\x1b[2m';
|
|
5
|
+
function bold(s) { return `${BOLD}${s}${R}`; }
|
|
6
|
+
function dim(s) { return `${DIM}${s}${R}`; }
|
|
7
|
+
function col(code, s) { return `\x1b[${code}m${s}${R}`; }
|
|
8
|
+
const blue = (s) => col(94, s);
|
|
9
|
+
const green = (s) => col(92, s);
|
|
10
|
+
const cyan = (s) => col(96, s);
|
|
11
|
+
const gray = (s) => col(90, s);
|
|
12
|
+
const yellow = (s) => col(93, s);
|
|
13
|
+
function indent(text, pad = ' ') {
|
|
14
|
+
return text.split('\n').map(l => pad + l).join('\n');
|
|
15
|
+
}
|
|
16
|
+
function formatContent(text) {
|
|
17
|
+
const lines = text.split('\n');
|
|
18
|
+
let inCode = false;
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
if (line.startsWith('<tool_call>') || line.startsWith('</tool_call>'))
|
|
22
|
+
continue;
|
|
23
|
+
if (line.startsWith('```')) {
|
|
24
|
+
inCode = !inCode;
|
|
25
|
+
out.push(' ' + dim(gray(line)));
|
|
26
|
+
}
|
|
27
|
+
else if (inCode) {
|
|
28
|
+
out.push(' ' + yellow(line || ' '));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
out.push(' ' + (line || ''));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out.join('\n');
|
|
35
|
+
}
|
|
36
|
+
export function welcome(provider, model, cwd) {
|
|
37
|
+
const cols = Math.min(process.stdout.columns ?? 80, 100);
|
|
38
|
+
const innerW = cols - 2;
|
|
39
|
+
const leftW = Math.floor(innerW * 0.44);
|
|
40
|
+
const rightW = innerW - leftW - 1;
|
|
41
|
+
function vis(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
42
|
+
function cell(s, w) {
|
|
43
|
+
const v = vis(s);
|
|
44
|
+
if (v.length < w)
|
|
45
|
+
return s + ' '.repeat(w - v.length);
|
|
46
|
+
if (v.length === w)
|
|
47
|
+
return s;
|
|
48
|
+
return v.slice(0, w - 1) + '…';
|
|
49
|
+
}
|
|
50
|
+
function row(l, r) {
|
|
51
|
+
return gray('│') + cell(l, leftW) + gray('│') + cell(r, rightW) + gray('│');
|
|
52
|
+
}
|
|
53
|
+
function blank() {
|
|
54
|
+
return gray('│') + ' '.repeat(leftW) + gray('│') + ' '.repeat(rightW) + gray('│');
|
|
55
|
+
}
|
|
56
|
+
function rcmd(key, desc, keyW = 10) {
|
|
57
|
+
return ' ' + cyan(key) + ' '.repeat(Math.max(1, keyW - key.length)) + gray(desc);
|
|
58
|
+
}
|
|
59
|
+
const titleStr = '─ MIII - CLI ';
|
|
60
|
+
const dashCount = Math.max(0, cols - 2 - titleStr.length);
|
|
61
|
+
const top = gray('╭') + gray('─') + bold(cyan(' MIII - CLI ')) + gray('─'.repeat(dashCount) + '╮');
|
|
62
|
+
const bottom = gray('╰' + '─'.repeat(innerW) + '╯');
|
|
63
|
+
const shortCwd = cwd.replace(process.env.HOME ?? '', '~');
|
|
64
|
+
const lines = [
|
|
65
|
+
top,
|
|
66
|
+
blank(),
|
|
67
|
+
row(` ${bold(cyan('MIII - CLI'))}`, ` ${bold(yellow('Getting started'))}`),
|
|
68
|
+
row(` ${gray('Claude Code-level terminal')}`, rcmd('@filename', 'inject file into context')),
|
|
69
|
+
row(` ${gray('workflows, local models.')}`, rcmd('/skill', 'run a skill or command')),
|
|
70
|
+
row('', rcmd('/models', 'switch or pull models')),
|
|
71
|
+
row('', rcmd('/list', 'list all skills')),
|
|
72
|
+
row('', rcmd('/session', 'manage sessions')),
|
|
73
|
+
blank(),
|
|
74
|
+
row(` ${gray(provider + '/' + model)}`, ` ${bold(yellow('Tips'))}`),
|
|
75
|
+
row(` ${gray(shortCwd)}`, rcmd('ctrl+c', 'stop streaming')),
|
|
76
|
+
row('', rcmd('ctrl+c x2', 'exit')),
|
|
77
|
+
blank(),
|
|
78
|
+
bottom,
|
|
79
|
+
];
|
|
80
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
81
|
+
}
|
|
82
|
+
export function userMsg(text) {
|
|
83
|
+
const atHighlighted = text.replace(/(@[\w./\-]+)/g, (m) => cyan(m));
|
|
84
|
+
console.log(`\n${bold(blue('You'))}\n${indent(atHighlighted)}`);
|
|
85
|
+
}
|
|
86
|
+
export function assistantMsg(text) {
|
|
87
|
+
console.log(`\n${bold(green('miii'))}\n${formatContent(text)}`);
|
|
88
|
+
}
|
|
89
|
+
export function toolMsg(name, result) {
|
|
90
|
+
const preview = result.length > 250 ? result.slice(0, 250) + '…' : result;
|
|
91
|
+
const body = preview.trim()
|
|
92
|
+
? preview.split('\n').map(l => gray(' ' + l)).join('\n')
|
|
93
|
+
: '';
|
|
94
|
+
console.log(` ${green('✓')} ${cyan(name)}${body ? '\n' + body : ''}`);
|
|
95
|
+
}
|
|
96
|
+
export function systemMsg(text) {
|
|
97
|
+
console.log(gray(`─ ${text}`));
|
|
98
|
+
}
|
|
99
|
+
export function errorMsg(text) {
|
|
100
|
+
console.log(gray(`error: ${text}`));
|
|
101
|
+
}
|
|
102
|
+
export function divider() {
|
|
103
|
+
const cols = process.stdout.columns ?? 80;
|
|
104
|
+
process.stdout.write(`${gray('─'.repeat(cols))}\n`);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=printer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"printer.js","sourceRoot":"","sources":["../../src/tui/printer.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type Role = 'user' | 'assistant' | 'system' | 'tool';
|
|
2
|
+
export type Status = 'idle' | 'streaming' | 'tool';
|
|
3
|
+
export interface Message {
|
|
4
|
+
id: string;
|
|
5
|
+
role: Role;
|
|
6
|
+
content: string;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Config {
|
|
10
|
+
model: string;
|
|
11
|
+
provider: 'ollama' | 'openai-compat';
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
systemPrompt?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ChatMessage {
|
|
16
|
+
role: 'system' | 'user' | 'assistant';
|
|
17
|
+
content: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function generateId(): string;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAChD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { workerData, parentPort } from 'worker_threads';
|
|
2
|
+
import { readFileSync, statSync, readdirSync, existsSync } from 'fs';
|
|
3
|
+
import { join, relative, extname } from 'path';
|
|
4
|
+
const SKIP_DIRS = new Set([
|
|
5
|
+
'node_modules', 'dist', 'build', '.git', '.next', '.nuxt', '.svelte-kit',
|
|
6
|
+
'out', '__pycache__', '.cache', 'coverage', '.nyc_output', 'vendor',
|
|
7
|
+
'target', '.turbo', '.vercel', 'generated', '.gradle', '.expo',
|
|
8
|
+
'bin', 'obj', 'tmp', 'temp', 'logs',
|
|
9
|
+
]);
|
|
10
|
+
const SKIP_EXTS = new Set(['.map', '.lock', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.mp4', '.mp3', '.pdf', '.zip', '.tar', '.gz', '.exe', '.dll', '.so', '.dylib', '.wasm', '.class', '.pyc', '.ttf', '.woff', '.woff2']);
|
|
11
|
+
function safe(p) {
|
|
12
|
+
try {
|
|
13
|
+
const s = statSync(p);
|
|
14
|
+
if (s.size > 512 * 1024)
|
|
15
|
+
return null;
|
|
16
|
+
return readFileSync(p, 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function walk(dir, out, cwd, depth = 0) {
|
|
23
|
+
if (depth > 4)
|
|
24
|
+
return;
|
|
25
|
+
for (const name of readdirSync(dir)) {
|
|
26
|
+
if (name.startsWith('.'))
|
|
27
|
+
continue;
|
|
28
|
+
if (SKIP_DIRS.has(name))
|
|
29
|
+
continue;
|
|
30
|
+
if (SKIP_EXTS.has(extname(name)))
|
|
31
|
+
continue;
|
|
32
|
+
if (name.endsWith('.d.ts') || name.endsWith('.js.map'))
|
|
33
|
+
continue;
|
|
34
|
+
const full = join(dir, name);
|
|
35
|
+
try {
|
|
36
|
+
const s = statSync(full);
|
|
37
|
+
if (s.isDirectory())
|
|
38
|
+
walk(full, out, cwd, depth + 1);
|
|
39
|
+
else
|
|
40
|
+
out.push(full);
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function build(input) {
|
|
46
|
+
const parts = [];
|
|
47
|
+
for (const p of input.paths) {
|
|
48
|
+
if (!existsSync(p))
|
|
49
|
+
continue;
|
|
50
|
+
const s = statSync(p);
|
|
51
|
+
if (s.isFile()) {
|
|
52
|
+
const content = safe(p);
|
|
53
|
+
if (content !== null)
|
|
54
|
+
parts.push(`<file path="${relative(input.cwd, p)}">\n${content}\n</file>`);
|
|
55
|
+
}
|
|
56
|
+
else if (s.isDirectory()) {
|
|
57
|
+
const files = [];
|
|
58
|
+
walk(p, files, input.cwd);
|
|
59
|
+
for (const f of files.slice(0, 100)) {
|
|
60
|
+
const content = safe(f);
|
|
61
|
+
if (content !== null)
|
|
62
|
+
parts.push(`<file path="${relative(input.cwd, f)}">\n${content}\n</file>`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return parts.join('\n\n');
|
|
67
|
+
}
|
|
68
|
+
parentPort?.postMessage({ context: build(workerData) });
|
|
69
|
+
//# sourceMappingURL=context.worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.worker.js","sourceRoot":"","sources":["../../src/workers/context.worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { workerData, parentPort } from 'worker_threads';
|
|
2
|
+
import { createPatch, applyPatch } from 'diff';
|
|
3
|
+
const inp = workerData;
|
|
4
|
+
if (inp.action === 'diff') {
|
|
5
|
+
const patch = createPatch(inp.filename ?? 'file', inp.oldContent ?? '', inp.newContent ?? '');
|
|
6
|
+
parentPort?.postMessage({ patch });
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
const result = applyPatch(inp.oldContent ?? '', inp.patch ?? '');
|
|
10
|
+
parentPort?.postMessage({ result: result === false ? null : result });
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=diff.worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.worker.js","sourceRoot":"","sources":["../../src/workers/diff.worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,MAAM,CAAA;AAU9C,MAAM,GAAG,GAAG,UAAmB,CAAA;AAE/B,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,IAAI,MAAM,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IAC7F,UAAU,EAAE,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;AACpC,CAAC;KAAM,CAAC;IACN,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;IAChE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;AACvE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function spawnWorker<T>(name: string, data: unknown): Promise<T>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Worker } from 'worker_threads';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const isDev = process.argv.some(a => a.includes('tsx')) || import.meta.url.endsWith('.ts');
|
|
6
|
+
const ext = isDev ? '.ts' : '.js';
|
|
7
|
+
export function spawnWorker(name, data) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const path = join(__dir, `${name}.worker${ext}`);
|
|
10
|
+
const w = new Worker(path, {
|
|
11
|
+
workerData: data,
|
|
12
|
+
execArgv: isDev ? ['--import', 'tsx/esm'] : [],
|
|
13
|
+
});
|
|
14
|
+
w.once('message', (r) => { w.terminate(); resolve(r); });
|
|
15
|
+
w.once('error', (e) => { w.terminate(); reject(e); });
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=spawn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/workers/spawn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEpC,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACrD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC1F,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAA;AAEjC,MAAM,UAAU,WAAW,CAAI,IAAY,EAAE,IAAa;IACxD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,UAAU,GAAG,EAAE,CAAC,CAAA;QAChD,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE;YACzB,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE;SAC/C,CAAC,CAAA;QACF,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAI,EAAE,EAAE,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAC1D,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/install.sh
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "miii-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"miii": "dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "tsx src/index.ts",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"link": "npm run build && npm link"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"ink": "^5.2.0",
|
|
16
|
+
"react": "^18.3.1",
|
|
17
|
+
"diff": "^7.0.0",
|
|
18
|
+
"glob": "^11.0.1",
|
|
19
|
+
"minimist": "^1.2.8"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.10.0",
|
|
23
|
+
"@types/react": "^18.3.1",
|
|
24
|
+
"@types/diff": "^7.0.0",
|
|
25
|
+
"@types/minimist": "^1.2.5",
|
|
26
|
+
"tsx": "^4.19.1",
|
|
27
|
+
"typescript": "^5.7.3"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs'
|
|
2
|
+
import { homedir } from 'os'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import type { Config } from './types.js'
|
|
5
|
+
|
|
6
|
+
const defaults: Config = {
|
|
7
|
+
model: 'llama3.2',
|
|
8
|
+
provider: 'ollama',
|
|
9
|
+
baseUrl: 'http://localhost:11434',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function loadConfig(): Config {
|
|
13
|
+
const candidates = [
|
|
14
|
+
join(process.cwd(), '.miii.json'),
|
|
15
|
+
join(homedir(), '.config', 'miii', 'config.json'),
|
|
16
|
+
]
|
|
17
|
+
for (const p of candidates) {
|
|
18
|
+
if (existsSync(p)) {
|
|
19
|
+
try {
|
|
20
|
+
return { ...defaults, ...JSON.parse(readFileSync(p, 'utf-8')) }
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ...defaults }
|
|
25
|
+
}
|
package/src/files/ops.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readFileSync, writeFileSync, unlinkSync,
|
|
3
|
+
mkdirSync, readdirSync, statSync, existsSync,
|
|
4
|
+
} from 'fs'
|
|
5
|
+
import { join, dirname, relative, extname } from 'path'
|
|
6
|
+
|
|
7
|
+
const SKIP_DIRS = new Set([
|
|
8
|
+
'node_modules', 'dist', 'build', '.git', '.next', '.nuxt', '.svelte-kit',
|
|
9
|
+
'out', '__pycache__', '.cache', 'coverage', '.nyc_output', 'vendor',
|
|
10
|
+
'target', '.turbo', '.vercel', 'generated', '.gradle', '.expo',
|
|
11
|
+
'bin', 'obj', '.idea', '.vscode', 'tmp', 'temp', 'logs',
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
const SKIP_EXTS = new Set([
|
|
15
|
+
'.map', '.lock',
|
|
16
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.bmp', '.tiff',
|
|
17
|
+
'.mp4', '.mp3', '.wav', '.ogg', '.pdf',
|
|
18
|
+
'.zip', '.tar', '.gz', '.rar', '.7z',
|
|
19
|
+
'.exe', '.dll', '.so', '.dylib', '.wasm', '.class', '.pyc',
|
|
20
|
+
'.ttf', '.woff', '.woff2', '.eot',
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
const SKIP_NAMES = new Set([
|
|
24
|
+
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'Cargo.lock',
|
|
25
|
+
'poetry.lock', 'Gemfile.lock', 'composer.lock',
|
|
26
|
+
'.DS_Store', 'Thumbs.db', '.env.local', 'LICENSE', 'LICENSE.md',
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
export function readFile(p: string): string {
|
|
30
|
+
if (!existsSync(p)) return ''
|
|
31
|
+
return readFileSync(p, 'utf-8')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function writeFile(p: string, content: string): void {
|
|
35
|
+
mkdirSync(dirname(p), { recursive: true })
|
|
36
|
+
writeFileSync(p, content, 'utf-8')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function deleteFile(p: string): void {
|
|
40
|
+
unlinkSync(p)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface FileEntry {
|
|
44
|
+
name: string
|
|
45
|
+
path: string
|
|
46
|
+
rel: string
|
|
47
|
+
type: 'file' | 'dir'
|
|
48
|
+
size?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function listFiles(dir: string, recursive = false, cwd = process.cwd()): FileEntry[] {
|
|
52
|
+
if (!existsSync(dir)) return []
|
|
53
|
+
const entries: FileEntry[] = []
|
|
54
|
+
for (const name of readdirSync(dir)) {
|
|
55
|
+
if (name.startsWith('.')) continue
|
|
56
|
+
if (SKIP_NAMES.has(name)) continue
|
|
57
|
+
if (SKIP_DIRS.has(name)) continue
|
|
58
|
+
const ext = extname(name)
|
|
59
|
+
if (SKIP_EXTS.has(ext)) continue
|
|
60
|
+
if (name.endsWith('.d.ts') || name.endsWith('.js.map')) continue
|
|
61
|
+
let stat
|
|
62
|
+
try { stat = statSync(join(dir, name)) } catch { continue }
|
|
63
|
+
const full = join(dir, name)
|
|
64
|
+
const type = stat.isDirectory() ? 'dir' : 'file'
|
|
65
|
+
entries.push({ name, path: full, rel: relative(cwd, full), type, size: stat.isFile() ? stat.size : undefined })
|
|
66
|
+
if (recursive && type === 'dir') {
|
|
67
|
+
entries.push(...listFiles(full, true, cwd))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return entries
|
|
71
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Minimal entry — heavy imports lazy-loaded
|
|
3
|
+
async function main() {
|
|
4
|
+
const { lazyInit } = await import('./init.js')
|
|
5
|
+
await lazyInit()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
main().catch(err => {
|
|
9
|
+
process.stderr.write(`fatal: ${err.message}\n`)
|
|
10
|
+
process.exit(1)
|
|
11
|
+
})
|
package/src/init.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { render } from 'ink'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import minimist from 'minimist'
|
|
4
|
+
import { loadConfig } from './config.js'
|
|
5
|
+
import { SkillLoader } from './skills/loader.js'
|
|
6
|
+
import { InputBar } from './tui/InputBar.js'
|
|
7
|
+
import { welcome } from './tui/printer.js'
|
|
8
|
+
|
|
9
|
+
export async function lazyInit(): Promise<void> {
|
|
10
|
+
const argv = minimist(process.argv.slice(2), {
|
|
11
|
+
string: ['model', 'url', 'provider', 'session'],
|
|
12
|
+
alias: { m: 'model', u: 'url', p: 'provider', s: 'session' },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const config = loadConfig()
|
|
16
|
+
if (argv.model) config.model = argv.model
|
|
17
|
+
if (argv.url) config.baseUrl = argv.url
|
|
18
|
+
if (argv.provider) config.provider = argv.provider as typeof config.provider
|
|
19
|
+
|
|
20
|
+
const skills = new SkillLoader()
|
|
21
|
+
await skills.loadAll()
|
|
22
|
+
|
|
23
|
+
// Print welcome banner to scrollback BEFORE Ink starts
|
|
24
|
+
welcome(config.provider, config.model, process.cwd())
|
|
25
|
+
|
|
26
|
+
// Move cursor to last terminal row so Ink renders at absolute bottom
|
|
27
|
+
process.stdout.write(`\x1b[${process.stdout.rows ?? 24};1H`)
|
|
28
|
+
|
|
29
|
+
// Ink renders ONLY the input bar (small footprint at bottom)
|
|
30
|
+
// patchConsole: true (default) ensures console.log output appears above Ink
|
|
31
|
+
const sessionName = (argv.session as string) || 'default'
|
|
32
|
+
|
|
33
|
+
const { waitUntilExit } = render(
|
|
34
|
+
React.createElement(InputBar, { config, skills, cwd: process.cwd(), session: sessionName }),
|
|
35
|
+
{ exitOnCtrlC: false }
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
await waitUntilExit()
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface OllamaModel {
|
|
2
|
+
name: string
|
|
3
|
+
size: number
|
|
4
|
+
modified_at: string
|
|
5
|
+
digest?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function listModels(baseUrl: string): Promise<OllamaModel[]> {
|
|
9
|
+
const res = await fetch(`${baseUrl}/api/tags`)
|
|
10
|
+
if (!res.ok) throw new Error(`Ollama ${res.status}: ${await res.text()}`)
|
|
11
|
+
const data = (await res.json()) as { models?: OllamaModel[] }
|
|
12
|
+
return data.models ?? []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function pullModel(
|
|
16
|
+
baseUrl: string,
|
|
17
|
+
name: string,
|
|
18
|
+
onProgress: (status: string, pct: number | undefined) => void,
|
|
19
|
+
signal?: AbortSignal,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const res = await fetch(`${baseUrl}/api/pull`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ name, stream: true }),
|
|
25
|
+
signal,
|
|
26
|
+
})
|
|
27
|
+
if (!res.ok) throw new Error(`pull failed: ${res.status} ${await res.text()}`)
|
|
28
|
+
|
|
29
|
+
const reader = res.body!.getReader()
|
|
30
|
+
const dec = new TextDecoder()
|
|
31
|
+
let buf = ''
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read()
|
|
36
|
+
if (done) break
|
|
37
|
+
buf += dec.decode(value, { stream: true })
|
|
38
|
+
const lines = buf.split('\n')
|
|
39
|
+
buf = lines.pop() ?? ''
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
if (!line.trim()) continue
|
|
42
|
+
try {
|
|
43
|
+
const obj = JSON.parse(line) as { status?: string; completed?: number; total?: number }
|
|
44
|
+
const pct = obj.total ? Math.round(((obj.completed ?? 0) / obj.total) * 100) : undefined
|
|
45
|
+
onProgress(obj.status ?? '', pct)
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
reader.releaseLock()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function fmtSize(bytes: number): string {
|
|
55
|
+
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)}GB`
|
|
56
|
+
if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(0)}MB`
|
|
57
|
+
return `${(bytes / 1e3).toFixed(0)}KB`
|
|
58
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { ChatMessage } from '../types.js'
|
|
2
|
+
|
|
3
|
+
export interface StreamConfig {
|
|
4
|
+
provider: 'ollama' | 'openai-compat'
|
|
5
|
+
model: string
|
|
6
|
+
baseUrl: string
|
|
7
|
+
messages: ChatMessage[]
|
|
8
|
+
signal?: AbortSignal
|
|
9
|
+
onToken: (token: string) => void
|
|
10
|
+
onDone: (fullText: string) => void | Promise<void>
|
|
11
|
+
onError: (err: Error) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function stream(cfg: StreamConfig): Promise<void> {
|
|
15
|
+
if (cfg.provider === 'openai-compat') {
|
|
16
|
+
return streamOpenAI(cfg)
|
|
17
|
+
}
|
|
18
|
+
return streamOllama(cfg)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function streamOllama(cfg: StreamConfig): Promise<void> {
|
|
22
|
+
const { model, messages, baseUrl, signal, onToken, onDone, onError } = cfg
|
|
23
|
+
let res: Response
|
|
24
|
+
try {
|
|
25
|
+
res = await fetch(`${baseUrl}/api/chat`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json' },
|
|
28
|
+
body: JSON.stringify({ model, messages, stream: true }),
|
|
29
|
+
signal,
|
|
30
|
+
})
|
|
31
|
+
} catch (err) {
|
|
32
|
+
onError(toError(err))
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
onError(new Error(`Ollama ${res.status}: ${await res.text()}`))
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
const reader = res.body!.getReader()
|
|
40
|
+
const dec = new TextDecoder()
|
|
41
|
+
let full = ''
|
|
42
|
+
let buf = ''
|
|
43
|
+
try {
|
|
44
|
+
while (true) {
|
|
45
|
+
const { done, value } = await reader.read()
|
|
46
|
+
if (done) break
|
|
47
|
+
buf += dec.decode(value, { stream: true })
|
|
48
|
+
const lines = buf.split('\n')
|
|
49
|
+
buf = lines.pop() ?? ''
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
if (!line.trim()) continue
|
|
52
|
+
try {
|
|
53
|
+
const obj = JSON.parse(line)
|
|
54
|
+
const tok: string = obj?.message?.content ?? ''
|
|
55
|
+
if (tok) { onToken(tok); full += tok }
|
|
56
|
+
if (obj?.done) { await onDone(full); return }
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await onDone(full)
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if ((err as Error)?.name !== 'AbortError') onError(toError(err))
|
|
63
|
+
} finally {
|
|
64
|
+
reader.releaseLock()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function streamOpenAI(cfg: StreamConfig): Promise<void> {
|
|
69
|
+
const { model, messages, baseUrl, signal, onToken, onDone, onError } = cfg
|
|
70
|
+
let res: Response
|
|
71
|
+
try {
|
|
72
|
+
res = await fetch(`${baseUrl}/v1/chat/completions`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer local' },
|
|
75
|
+
body: JSON.stringify({ model, messages, stream: true }),
|
|
76
|
+
signal,
|
|
77
|
+
})
|
|
78
|
+
} catch (err) {
|
|
79
|
+
onError(toError(err))
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
onError(new Error(`LLM ${res.status}: ${await res.text()}`))
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
const reader = res.body!.getReader()
|
|
87
|
+
const dec = new TextDecoder()
|
|
88
|
+
let full = ''
|
|
89
|
+
let buf = ''
|
|
90
|
+
try {
|
|
91
|
+
while (true) {
|
|
92
|
+
const { done, value } = await reader.read()
|
|
93
|
+
if (done) break
|
|
94
|
+
buf += dec.decode(value, { stream: true })
|
|
95
|
+
const lines = buf.split('\n')
|
|
96
|
+
buf = lines.pop() ?? ''
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
const trimmed = line.replace(/^data:\s*/, '').trim()
|
|
99
|
+
if (!trimmed || trimmed === '[DONE]') continue
|
|
100
|
+
try {
|
|
101
|
+
const obj = JSON.parse(trimmed)
|
|
102
|
+
const tok: string = obj?.choices?.[0]?.delta?.content ?? ''
|
|
103
|
+
if (tok) { onToken(tok); full += tok }
|
|
104
|
+
if (obj?.choices?.[0]?.finish_reason) { await onDone(full); return }
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
await onDone(full)
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if ((err as Error)?.name !== 'AbortError') onError(toError(err))
|
|
111
|
+
} finally {
|
|
112
|
+
reader.releaseLock()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function toError(e: unknown): Error {
|
|
117
|
+
return e instanceof Error ? e : new Error(String(e))
|
|
118
|
+
}
|