@ww_nero/mini-cli 1.0.56
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/bin/mini.js +3 -0
- package/package.json +38 -0
- package/src/chat.js +1008 -0
- package/src/config.js +371 -0
- package/src/index.js +38 -0
- package/src/llm.js +147 -0
- package/src/prompt/tool.js +18 -0
- package/src/request.js +328 -0
- package/src/tools/bash.js +241 -0
- package/src/tools/convert.js +297 -0
- package/src/tools/index.js +66 -0
- package/src/tools/mcp.js +478 -0
- package/src/tools/python/html_to_png.py +100 -0
- package/src/tools/python/html_to_pptx.py +163 -0
- package/src/tools/python/pdf_to_png.py +58 -0
- package/src/tools/python/pptx_to_pdf.py +107 -0
- package/src/tools/read.js +44 -0
- package/src/tools/replace.js +135 -0
- package/src/tools/todos.js +90 -0
- package/src/tools/write.js +52 -0
- package/src/utils/cliOptions.js +8 -0
- package/src/utils/commands.js +89 -0
- package/src/utils/git.js +89 -0
- package/src/utils/helpers.js +93 -0
- package/src/utils/history.js +181 -0
- package/src/utils/model.js +127 -0
- package/src/utils/output.js +76 -0
- package/src/utils/renderer.js +92 -0
- package/src/utils/settings.js +90 -0
- package/src/utils/think.js +211 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const ORANGE = typeof chalk.hex === 'function'
|
|
4
|
+
? chalk.hex('#FFA500')
|
|
5
|
+
: chalk.keyword('orange');
|
|
6
|
+
|
|
7
|
+
const truncateText = (text, maxLength = 200) => {
|
|
8
|
+
if (!text || typeof text !== 'string') {
|
|
9
|
+
return text;
|
|
10
|
+
}
|
|
11
|
+
if (text.length <= maxLength) {
|
|
12
|
+
return text;
|
|
13
|
+
}
|
|
14
|
+
return text.substring(0, maxLength) + '...';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const formatWriteLineCountTag = (args = {}) => {
|
|
18
|
+
if (!args || typeof args.content !== 'string') {
|
|
19
|
+
return ORANGE('(0)');
|
|
20
|
+
}
|
|
21
|
+
const normalized = args.content.replace(/\r/g, '');
|
|
22
|
+
if (!normalized) {
|
|
23
|
+
return ORANGE('(0)');
|
|
24
|
+
}
|
|
25
|
+
const newlineMatches = normalized.match(/\n/g);
|
|
26
|
+
const newlineCount = Array.isArray(newlineMatches) ? newlineMatches.length : 0;
|
|
27
|
+
const endsWithNewline = normalized.endsWith('\n');
|
|
28
|
+
const lineCount = endsWithNewline ? newlineCount : newlineCount + 1;
|
|
29
|
+
return ORANGE(`(${lineCount})`);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const formatHeader = (name, args, options = {}) => {
|
|
33
|
+
const statusTag = options.statusTag || '';
|
|
34
|
+
const extraMeta = Array.isArray(options.extraMeta) ? options.extraMeta : [];
|
|
35
|
+
const labelBase = `${chalk.yellow('🔧')} ${chalk.yellow(name)}`;
|
|
36
|
+
const writeTag = name === 'write_file' ? formatWriteLineCountTag(args) : '';
|
|
37
|
+
const labelWithWriteInfo = `${labelBase}${writeTag}`;
|
|
38
|
+
const label = statusTag ? `${labelWithWriteInfo}${statusTag}` : labelWithWriteInfo;
|
|
39
|
+
const metaParts = [...extraMeta];
|
|
40
|
+
|
|
41
|
+
if (name === 'execute_bash') {
|
|
42
|
+
if (args.command) metaParts.push(`command="${truncateText(args.command)}"`);
|
|
43
|
+
if (args.workingDirectory && args.workingDirectory !== '.') {
|
|
44
|
+
metaParts.push(`dir=${args.workingDirectory}`);
|
|
45
|
+
}
|
|
46
|
+
} else if (['read_file', 'write_file', 'search_and_replace'].includes(name)) {
|
|
47
|
+
if (args.filePath) metaParts.push(args.filePath);
|
|
48
|
+
} else if (name === 'write_todos') {
|
|
49
|
+
const count = Array.isArray(args.todos) ? args.todos.length : 0;
|
|
50
|
+
metaParts.push(`${count} items`);
|
|
51
|
+
} else if (name === 'convert') {
|
|
52
|
+
if (typeof args.input === 'string') {
|
|
53
|
+
metaParts.push(`input=${truncateText(args.input, 160)}`);
|
|
54
|
+
} else if (Array.isArray(args.input)) {
|
|
55
|
+
metaParts.push(`input=${truncateText(args.input.join(', '), 160)}`);
|
|
56
|
+
}
|
|
57
|
+
if (args.output) {
|
|
58
|
+
metaParts.push(`output=${truncateText(String(args.output), 160)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const meta = metaParts.length > 0 ? ` (${metaParts.join(', ')})` : '';
|
|
63
|
+
return `${label}${meta}`;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const safeStringify = (value) => {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(value);
|
|
69
|
+
} catch (_) {
|
|
70
|
+
return String(value);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const compactText = (text) => String(text || '').replace(/\s+/g, ' ').trim();
|
|
75
|
+
|
|
76
|
+
const renderToolCall = ({ functionName, args = {} }, mcpToolNames = new Set()) => {
|
|
77
|
+
const isMcpTool = mcpToolNames.has(functionName);
|
|
78
|
+
|
|
79
|
+
const extraMeta = [];
|
|
80
|
+
if (isMcpTool) {
|
|
81
|
+
const argsPreview = truncateText(compactText(safeStringify(args) || ''), 200);
|
|
82
|
+
if (argsPreview) {
|
|
83
|
+
extraMeta.push(argsPreview);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(formatHeader(functionName, args, { extraMeta }));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
renderToolCall
|
|
92
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const MINI_DIR_NAME = '.mini';
|
|
6
|
+
const SETTINGS_FILE_NAME = 'settings.json';
|
|
7
|
+
const DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT = 32768;
|
|
8
|
+
const DEFAULT_ALLOWED_COMMANDS = [
|
|
9
|
+
'rm', 'rmdir', 'touch', 'mkdir', 'cd', 'cp', 'mv', 'node', 'npm', 'ls',
|
|
10
|
+
'grep', 'cat', 'echo', 'sed', 'head', 'tail', 'find', 'true', 'false',
|
|
11
|
+
'pkill', 'kill', 'curl', 'ps', 'lsof', 'git', 'pip', 'python', 'pandoc'
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const ensureArrayOfStrings = (value, fallback = []) => {
|
|
15
|
+
if (!Array.isArray(value)) {
|
|
16
|
+
return [...fallback];
|
|
17
|
+
}
|
|
18
|
+
return value.map(String).filter(Boolean);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const loadSettings = ({ defaultTools = [] } = {}) => {
|
|
22
|
+
const settingsPath = path.join(os.homedir(), MINI_DIR_NAME, SETTINGS_FILE_NAME);
|
|
23
|
+
const dir = path.dirname(settingsPath);
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(dir)) {
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultSettings = {
|
|
30
|
+
mcps: [],
|
|
31
|
+
tools: [...defaultTools],
|
|
32
|
+
toolOutputTokenLimit: DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT,
|
|
33
|
+
allowedCommands: [...DEFAULT_ALLOWED_COMMANDS]
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let parsed = null;
|
|
37
|
+
let created = false;
|
|
38
|
+
let needsUpdate = false;
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(settingsPath)) {
|
|
41
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(defaultSettings, null, 2)}\n`, 'utf8');
|
|
42
|
+
parsed = defaultSettings;
|
|
43
|
+
created = true;
|
|
44
|
+
} else {
|
|
45
|
+
try {
|
|
46
|
+
const raw = fs.readFileSync(settingsPath, 'utf8') || '{}';
|
|
47
|
+
parsed = JSON.parse(raw);
|
|
48
|
+
|
|
49
|
+
if (typeof parsed.toolOutputTokenLimit !== 'number') {
|
|
50
|
+
parsed.toolOutputTokenLimit = DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT;
|
|
51
|
+
needsUpdate = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!Array.isArray(parsed.allowedCommands)) {
|
|
55
|
+
parsed.allowedCommands = [...DEFAULT_ALLOWED_COMMANDS];
|
|
56
|
+
needsUpdate = true;
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
parsed = defaultSettings;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const settings = {
|
|
64
|
+
mcps: ensureArrayOfStrings(parsed.mcps, defaultSettings.mcps),
|
|
65
|
+
tools: ensureArrayOfStrings(parsed.tools, defaultSettings.tools),
|
|
66
|
+
toolOutputTokenLimit: typeof parsed.toolOutputTokenLimit === 'number' && parsed.toolOutputTokenLimit > 0
|
|
67
|
+
? parsed.toolOutputTokenLimit
|
|
68
|
+
: DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT,
|
|
69
|
+
allowedCommands: ensureArrayOfStrings(parsed.allowedCommands, DEFAULT_ALLOWED_COMMANDS)
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (needsUpdate) {
|
|
73
|
+
try {
|
|
74
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
settingsPath,
|
|
81
|
+
settings,
|
|
82
|
+
created
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
loadSettings,
|
|
88
|
+
DEFAULT_TOOL_OUTPUT_TOKEN_LIMIT,
|
|
89
|
+
DEFAULT_ALLOWED_COMMANDS
|
|
90
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const THINK_TAGS = [
|
|
2
|
+
{ name: 'think', open: '<think>', close: '</think>' },
|
|
3
|
+
{ name: 'tihinking', open: '<tihinking>', close: '</tihinking>' },
|
|
4
|
+
{ name: 'thought', open: '<thought>', close: '</thought>' }
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
const THINK_TAG_MAP = new Map(
|
|
8
|
+
THINK_TAGS.map(tag => [tag.name.toLowerCase(), { ...tag, closeLower: tag.close.toLowerCase() }])
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const findTagEnd = (text, startIndex) => {
|
|
12
|
+
let inSingleQuote = false;
|
|
13
|
+
let inDoubleQuote = false;
|
|
14
|
+
|
|
15
|
+
for (let i = startIndex; i < text.length; i++) {
|
|
16
|
+
const char = text[i];
|
|
17
|
+
|
|
18
|
+
if (char === '\'' && !inDoubleQuote) {
|
|
19
|
+
inSingleQuote = !inSingleQuote;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (char === '"' && !inSingleQuote) {
|
|
24
|
+
inDoubleQuote = !inDoubleQuote;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (char === '>' && !inSingleQuote && !inDoubleQuote) {
|
|
29
|
+
return i;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return -1;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const parseTagInfo = (tagText) => {
|
|
37
|
+
if (!tagText.startsWith('<') || !tagText.endsWith('>')) return null;
|
|
38
|
+
|
|
39
|
+
let inner = tagText.slice(1, -1).trim();
|
|
40
|
+
if (!inner) return null;
|
|
41
|
+
|
|
42
|
+
let type = 'start';
|
|
43
|
+
if (inner.startsWith('/')) {
|
|
44
|
+
type = 'end';
|
|
45
|
+
inner = inner.slice(1).trim();
|
|
46
|
+
} else if (inner.endsWith('/')) {
|
|
47
|
+
type = 'self';
|
|
48
|
+
inner = inner.slice(0, -1).trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const nameMatch = inner.match(/^([a-zA-Z0-9_-]+)/);
|
|
52
|
+
if (!nameMatch) return null;
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
type,
|
|
56
|
+
name: nameMatch[1].toLowerCase()
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const createThinkParserState = () => ({
|
|
61
|
+
buffer: '',
|
|
62
|
+
currentThink: null
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const processThinkChunk = (state, chunk = '') => {
|
|
66
|
+
if (!state) {
|
|
67
|
+
throw new Error('processThinkChunk requires a state object.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (chunk == null) {
|
|
71
|
+
chunk = '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof chunk !== 'string') {
|
|
75
|
+
chunk = String(chunk);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!state.buffer) {
|
|
79
|
+
state.buffer = '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
state.buffer += chunk;
|
|
83
|
+
|
|
84
|
+
let contentChunk = '';
|
|
85
|
+
let reasonChunk = '';
|
|
86
|
+
|
|
87
|
+
const consumePrefix = (length) => {
|
|
88
|
+
const prefix = state.buffer.slice(0, length);
|
|
89
|
+
state.buffer = state.buffer.slice(length);
|
|
90
|
+
return prefix;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
while (state.buffer.length > 0) {
|
|
94
|
+
if (state.currentThink) {
|
|
95
|
+
const thinkInfo = THINK_TAG_MAP.get(state.currentThink);
|
|
96
|
+
if (!thinkInfo) {
|
|
97
|
+
state.currentThink = null;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const bufferLower = state.buffer.toLowerCase();
|
|
102
|
+
const closeIndex = bufferLower.indexOf(thinkInfo.closeLower);
|
|
103
|
+
|
|
104
|
+
if (closeIndex === -1) {
|
|
105
|
+
reasonChunk += consumePrefix(state.buffer.length);
|
|
106
|
+
return { contentChunk, reasonChunk };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
reasonChunk += consumePrefix(closeIndex);
|
|
110
|
+
consumePrefix(thinkInfo.close.length);
|
|
111
|
+
state.currentThink = null;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const nextLt = state.buffer.indexOf('<');
|
|
116
|
+
|
|
117
|
+
if (nextLt === -1) {
|
|
118
|
+
contentChunk += consumePrefix(state.buffer.length);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (nextLt > 0) {
|
|
123
|
+
contentChunk += consumePrefix(nextLt);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const tagEnd = findTagEnd(state.buffer, 1);
|
|
128
|
+
if (tagEnd === -1) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const rawTag = consumePrefix(tagEnd + 1);
|
|
133
|
+
const tagInfo = parseTagInfo(rawTag);
|
|
134
|
+
|
|
135
|
+
if (!tagInfo) {
|
|
136
|
+
contentChunk += rawTag;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (tagInfo.type === 'start') {
|
|
141
|
+
if (THINK_TAG_MAP.has(tagInfo.name)) {
|
|
142
|
+
state.currentThink = tagInfo.name;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
contentChunk += rawTag;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (tagInfo.type === 'end') {
|
|
151
|
+
contentChunk += rawTag;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
contentChunk += rawTag;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { contentChunk, reasonChunk };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const flushThinkState = (state) => {
|
|
162
|
+
if (!state || !state.buffer) {
|
|
163
|
+
return { contentChunk: '', reasonChunk: '' };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const remaining = state.buffer;
|
|
167
|
+
state.buffer = '';
|
|
168
|
+
|
|
169
|
+
if (state.currentThink) {
|
|
170
|
+
state.currentThink = null;
|
|
171
|
+
return { contentChunk: '', reasonChunk: remaining };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { contentChunk: remaining, reasonChunk: '' };
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const splitThinkContent = (text = '') => {
|
|
178
|
+
if (!text) {
|
|
179
|
+
return { content: '', reasoning: '' };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const state = createThinkParserState();
|
|
183
|
+
let content = '';
|
|
184
|
+
let reasoning = '';
|
|
185
|
+
|
|
186
|
+
const result = processThinkChunk(state, text);
|
|
187
|
+
content += result.contentChunk;
|
|
188
|
+
reasoning += result.reasonChunk;
|
|
189
|
+
|
|
190
|
+
const tail = flushThinkState(state);
|
|
191
|
+
content += tail.contentChunk;
|
|
192
|
+
reasoning += tail.reasonChunk;
|
|
193
|
+
|
|
194
|
+
return { content, reasoning };
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const summarizeReasoning = (text = '') => {
|
|
198
|
+
if (!text) return '';
|
|
199
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
200
|
+
if (!normalized) return '';
|
|
201
|
+
return normalized;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
THINK_TAGS,
|
|
206
|
+
createThinkParserState,
|
|
207
|
+
processThinkChunk,
|
|
208
|
+
flushThinkState,
|
|
209
|
+
splitThinkContent,
|
|
210
|
+
summarizeReasoning
|
|
211
|
+
};
|