miii-cli 0.2.0 → 0.2.2
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/README.md +43 -8
- package/package.json +6 -1
- package/.claude/settings.local.json +0 -28
- package/CONTRIBUTING.md +0 -55
- package/Makefile +0 -13
- package/install.sh +0 -6
- package/mii-cli.gif +0 -0
- package/src/config.ts +0 -32
- package/src/files/ops.ts +0 -89
- package/src/index.ts +0 -11
- package/src/init.ts +0 -41
- package/src/llm/ollama.ts +0 -110
- package/src/llm/stream.ts +0 -55
- package/src/parser/stream-parser.ts +0 -196
- package/src/sessions.ts +0 -54
- package/src/skills/loader.ts +0 -144
- package/src/tools/index.ts +0 -151
- package/src/tui/App.tsx +0 -355
- package/src/tui/InputBar.tsx +0 -378
- package/src/tui/components/AtPicker.tsx +0 -49
- package/src/tui/components/CommandPalette.tsx +0 -50
- package/src/tui/components/InputArea.tsx +0 -297
- package/src/tui/components/MessageList.tsx +0 -219
- package/src/tui/components/ModelPicker.tsx +0 -134
- package/src/tui/components/StatusBar.tsx +0 -36
- package/src/tui/printer.ts +0 -130
- package/src/types.ts +0 -26
- package/src/workers/context.worker.ts +0 -66
- package/src/workers/diff.worker.ts +0 -20
- package/src/workers/spawn.ts +0 -19
- package/tsconfig.json +0 -18
package/src/tui/printer.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
// ANSI-formatted stdout output — goes into terminal scrollback
|
|
2
|
-
|
|
3
|
-
const R = '\x1b[0m'
|
|
4
|
-
const BOLD = '\x1b[1m'
|
|
5
|
-
const DIM = '\x1b[2m'
|
|
6
|
-
|
|
7
|
-
function bold(s: string) { return `${BOLD}${s}${R}` }
|
|
8
|
-
function dim(s: string) { return `${DIM}${s}${R}` }
|
|
9
|
-
function col(code: number, s: string) { return `\x1b[${code}m${s}${R}` }
|
|
10
|
-
|
|
11
|
-
const blue = (s: string) => col(94, s)
|
|
12
|
-
const green = (s: string) => col(92, s)
|
|
13
|
-
const cyan = (s: string) => col(96, s)
|
|
14
|
-
const gray = (s: string) => col(90, s)
|
|
15
|
-
const yellow = (s: string) => col(93, s)
|
|
16
|
-
|
|
17
|
-
function indent(text: string, pad = ' '): string {
|
|
18
|
-
return text.split('\n').map(l => pad + l).join('\n')
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function stripMarkdown(s: string): string {
|
|
22
|
-
return s
|
|
23
|
-
.replace(/\*\*\*(.+?)\*\*\*/g, '$1')
|
|
24
|
-
.replace(/\*\*(.+?)\*\*/g, '$1')
|
|
25
|
-
.replace(/\*(.+?)\*/g, '$1')
|
|
26
|
-
.replace(/`([^`]+)`/g, '$1')
|
|
27
|
-
.replace(/^#{1,6} /gm, '')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function formatContent(text: string): string {
|
|
31
|
-
const lines = text.split('\n')
|
|
32
|
-
let inCode = false
|
|
33
|
-
const out: string[] = []
|
|
34
|
-
for (const line of lines) {
|
|
35
|
-
if (line.startsWith('<tool_call>') || line.startsWith('</tool_call>')) continue
|
|
36
|
-
if (line.startsWith('```')) {
|
|
37
|
-
inCode = !inCode
|
|
38
|
-
out.push(' ' + dim(gray(line)))
|
|
39
|
-
} else if (inCode) {
|
|
40
|
-
out.push(' ' + yellow(line || ' '))
|
|
41
|
-
} else {
|
|
42
|
-
out.push(' ' + stripMarkdown(line || ''))
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return out.join('\n')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function welcome(provider: string, model: string, cwd: string): void {
|
|
49
|
-
const cols = Math.min(process.stdout.columns ?? 80, 100)
|
|
50
|
-
const innerW = cols - 2
|
|
51
|
-
const leftW = Math.floor(innerW * 0.44)
|
|
52
|
-
const rightW = innerW - leftW - 1
|
|
53
|
-
|
|
54
|
-
function vis(s: string): string { return s.replace(/\x1b\[[0-9;]*m/g, '') }
|
|
55
|
-
|
|
56
|
-
function cell(s: string, w: number): string {
|
|
57
|
-
const v = vis(s)
|
|
58
|
-
if (v.length < w) return s + ' '.repeat(w - v.length)
|
|
59
|
-
if (v.length === w) return s
|
|
60
|
-
return v.slice(0, w - 1) + '…'
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function row(l: string, r: string): string {
|
|
64
|
-
return gray('│') + cell(l, leftW) + gray('│') + cell(r, rightW) + gray('│')
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function blank(): string {
|
|
68
|
-
return gray('│') + ' '.repeat(leftW) + gray('│') + ' '.repeat(rightW) + gray('│')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function rcmd(key: string, desc: string, keyW = 10): string {
|
|
72
|
-
return ' ' + cyan(key) + ' '.repeat(Math.max(1, keyW - key.length)) + gray(desc)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const titleStr = '─ MIII - CLI '
|
|
76
|
-
const dashCount = Math.max(0, cols - 2 - titleStr.length)
|
|
77
|
-
const top = gray('╭') + gray('─') + bold(cyan(' MIII - CLI ')) + gray('─'.repeat(dashCount) + '╮')
|
|
78
|
-
const bottom = gray('╰' + '─'.repeat(innerW) + '╯')
|
|
79
|
-
|
|
80
|
-
const shortCwd = cwd.replace(process.env.HOME ?? '', '~')
|
|
81
|
-
|
|
82
|
-
const lines = [
|
|
83
|
-
top,
|
|
84
|
-
blank(),
|
|
85
|
-
row(` ${bold(cyan('MIII - CLI'))}`, ` ${bold(yellow('Getting started'))}`),
|
|
86
|
-
row(` ${gray('Claude Code-level terminal')}`, rcmd('@filename', 'inject file into context')),
|
|
87
|
-
row(` ${gray('workflows, local models.')}`, rcmd('/skill', 'run a skill or command')),
|
|
88
|
-
row('', rcmd('/models', 'switch or pull models')),
|
|
89
|
-
row('', rcmd('/list', 'list all skills')),
|
|
90
|
-
row('', rcmd('/session', 'manage sessions')),
|
|
91
|
-
blank(),
|
|
92
|
-
row(` ${gray(provider + '/' + model)}`, ` ${bold(yellow('Tips'))}`),
|
|
93
|
-
row(` ${gray(shortCwd)}`, rcmd('ctrl+c', 'stop thinking')),
|
|
94
|
-
row('', rcmd('ctrl+c x2','exit')),
|
|
95
|
-
blank(),
|
|
96
|
-
bottom,
|
|
97
|
-
]
|
|
98
|
-
|
|
99
|
-
process.stdout.write(lines.join('\n') + '\n')
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function userMsg(text: string): void {
|
|
103
|
-
const atHighlighted = text.replace(/(@[\w./\-]+)/g, (m) => cyan(m))
|
|
104
|
-
console.log(`\n${bold(blue('You'))}\n${indent(atHighlighted)}`)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function assistantMsg(text: string): void {
|
|
108
|
-
console.log(`\n${bold(green('miii'))}\n${formatContent(text)}`)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function toolMsg(name: string, result: string): void {
|
|
112
|
-
const preview = result.length > 250 ? result.slice(0, 250) + '…' : result
|
|
113
|
-
const body = preview.trim()
|
|
114
|
-
? preview.split('\n').map(l => gray(' ' + l)).join('\n')
|
|
115
|
-
: ''
|
|
116
|
-
console.log(` ${green('✓')} ${cyan(name)}${body ? '\n' + body : ''}`)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function systemMsg(text: string): void {
|
|
120
|
-
console.log(gray(`─ ${text}`))
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function errorMsg(text: string): void {
|
|
124
|
-
console.log(gray(`error: ${text}`))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function divider(): void {
|
|
128
|
-
const cols = process.stdout.columns ?? 80
|
|
129
|
-
process.stdout.write(`${gray('─'.repeat(cols))}\n`)
|
|
130
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export type Role = 'user' | 'assistant' | 'system' | 'tool'
|
|
2
|
-
export type Status = 'idle' | 'thinking' | 'tool'
|
|
3
|
-
|
|
4
|
-
export interface Message {
|
|
5
|
-
id: string
|
|
6
|
-
role: Role
|
|
7
|
-
content: string
|
|
8
|
-
timestamp: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface Config {
|
|
12
|
-
model: string
|
|
13
|
-
provider: 'ollama' | 'openai-compat'
|
|
14
|
-
baseUrl: string
|
|
15
|
-
systemPrompt?: string
|
|
16
|
-
apiKey?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface ChatMessage {
|
|
20
|
-
role: 'system' | 'user' | 'assistant'
|
|
21
|
-
content: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function generateId(): string {
|
|
25
|
-
return Math.random().toString(36).slice(2, 10)
|
|
26
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { workerData, parentPort } from 'worker_threads'
|
|
2
|
-
import { readFileSync, statSync, readdirSync, existsSync } from 'fs'
|
|
3
|
-
import { join, relative, extname } from 'path'
|
|
4
|
-
|
|
5
|
-
const SKIP_DIRS = new Set([
|
|
6
|
-
'node_modules', 'dist', 'build', '.git', '.next', '.nuxt', '.svelte-kit',
|
|
7
|
-
'out', '__pycache__', '.cache', 'coverage', '.nyc_output', 'vendor',
|
|
8
|
-
'target', '.turbo', '.vercel', 'generated', '.gradle', '.expo',
|
|
9
|
-
'bin', 'obj', 'tmp', 'temp', 'logs',
|
|
10
|
-
])
|
|
11
|
-
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'])
|
|
12
|
-
|
|
13
|
-
interface Input {
|
|
14
|
-
paths: string[]
|
|
15
|
-
cwd: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function safe(p: string): string | null {
|
|
19
|
-
try {
|
|
20
|
-
const s = statSync(p)
|
|
21
|
-
if (s.size > 512 * 1024) return null
|
|
22
|
-
return readFileSync(p, 'utf-8')
|
|
23
|
-
} catch { return null }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function walk(dir: string, out: string[], cwd: string, depth = 0): void {
|
|
27
|
-
if (depth > 4) return
|
|
28
|
-
for (const name of readdirSync(dir)) {
|
|
29
|
-
if (name.startsWith('.')) continue
|
|
30
|
-
if (SKIP_DIRS.has(name)) continue
|
|
31
|
-
if (SKIP_EXTS.has(extname(name))) continue
|
|
32
|
-
if (name.endsWith('.d.ts') || name.endsWith('.js.map')) continue
|
|
33
|
-
const full = join(dir, name)
|
|
34
|
-
try {
|
|
35
|
-
const s = statSync(full)
|
|
36
|
-
if (s.isDirectory()) walk(full, out, cwd, depth + 1)
|
|
37
|
-
else out.push(full)
|
|
38
|
-
} catch {}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function xmlAttr(s: string): string {
|
|
43
|
-
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function build(input: Input): string {
|
|
47
|
-
const parts: string[] = []
|
|
48
|
-
for (const p of input.paths) {
|
|
49
|
-
if (!existsSync(p)) continue
|
|
50
|
-
const s = statSync(p)
|
|
51
|
-
if (s.isFile()) {
|
|
52
|
-
const content = safe(p)
|
|
53
|
-
if (content !== null) parts.push(`<file path="${xmlAttr(relative(input.cwd, p))}">\n${content}\n</file>`)
|
|
54
|
-
} else if (s.isDirectory()) {
|
|
55
|
-
const files: string[] = []
|
|
56
|
-
walk(p, files, input.cwd)
|
|
57
|
-
for (const f of files.slice(0, 100)) {
|
|
58
|
-
const content = safe(f)
|
|
59
|
-
if (content !== null) parts.push(`<file path="${xmlAttr(relative(input.cwd, f))}">\n${content}\n</file>`)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return parts.join('\n\n')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
parentPort?.postMessage({ context: build(workerData as Input) })
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { workerData, parentPort } from 'worker_threads'
|
|
2
|
-
import { createPatch, applyPatch } from 'diff'
|
|
3
|
-
|
|
4
|
-
interface Input {
|
|
5
|
-
action: 'diff' | 'apply'
|
|
6
|
-
filename?: string
|
|
7
|
-
oldContent?: string
|
|
8
|
-
newContent?: string
|
|
9
|
-
patch?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const inp = workerData as Input
|
|
13
|
-
|
|
14
|
-
if (inp.action === 'diff') {
|
|
15
|
-
const patch = createPatch(inp.filename ?? 'file', inp.oldContent ?? '', inp.newContent ?? '')
|
|
16
|
-
parentPort?.postMessage({ patch })
|
|
17
|
-
} else {
|
|
18
|
-
const result = applyPatch(inp.oldContent ?? '', inp.patch ?? '')
|
|
19
|
-
parentPort?.postMessage({ result: result === false ? null : result })
|
|
20
|
-
}
|
package/src/workers/spawn.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Worker } from 'worker_threads'
|
|
2
|
-
import { fileURLToPath } from 'url'
|
|
3
|
-
import { dirname, join } from 'path'
|
|
4
|
-
|
|
5
|
-
const __dir = dirname(fileURLToPath(import.meta.url))
|
|
6
|
-
const isDev = process.argv.some(a => a.includes('tsx')) || import.meta.url.endsWith('.ts')
|
|
7
|
-
const ext = isDev ? '.ts' : '.js'
|
|
8
|
-
|
|
9
|
-
export function spawnWorker<T>(name: string, data: unknown): Promise<T> {
|
|
10
|
-
return new Promise((resolve, reject) => {
|
|
11
|
-
const path = join(__dir, `${name}.worker${ext}`)
|
|
12
|
-
const w = new Worker(path, {
|
|
13
|
-
workerData: data,
|
|
14
|
-
execArgv: isDev ? ['--import', 'tsx/esm'] : [],
|
|
15
|
-
})
|
|
16
|
-
w.once('message', (r: T) => { w.terminate(); resolve(r) })
|
|
17
|
-
w.once('error', (e) => { w.terminate(); reject(e) })
|
|
18
|
-
})
|
|
19
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"jsx": "react-jsx",
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"sourceMap": true
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*"],
|
|
17
|
-
"exclude": ["node_modules", "dist"]
|
|
18
|
-
}
|