@ww_nero/mini-cli 1.0.86 → 1.0.89
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/package.json +1 -1
- package/src/chat.js +18 -28
- package/src/prompt/tool.js +1 -6
- package/src/tools/index.js +1 -2
- package/src/utils/output.js +49 -7
- package/src/utils/renderer.js +0 -3
- package/src/tools/todos.js +0 -90
package/package.json
CHANGED
package/src/chat.js
CHANGED
|
@@ -3,6 +3,7 @@ const os = require('os');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const readline = require('readline');
|
|
5
5
|
const chalk = require('chalk');
|
|
6
|
+
const { diffLines } = require('diff');
|
|
6
7
|
const { encode } = require('gpt-tokenizer');
|
|
7
8
|
|
|
8
9
|
const { ConfigError, loadEndpointConfig, getDefaultConfigPath, ensureConfigFiles, loadSettings, DEFAULT_COMPACT_TOKEN_THRESHOLD } = require('./config');
|
|
@@ -101,42 +102,36 @@ const appendSkillsInstructions = (baseContent) => {
|
|
|
101
102
|
};
|
|
102
103
|
};
|
|
103
104
|
|
|
105
|
+
const splitDisplayLines = (text = '') => {
|
|
106
|
+
const normalized = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
107
|
+
const lines = normalized.split('\n');
|
|
108
|
+
if (lines[lines.length - 1] === '') {
|
|
109
|
+
lines.pop();
|
|
110
|
+
}
|
|
111
|
+
return lines;
|
|
112
|
+
};
|
|
113
|
+
|
|
104
114
|
const formatWriteOutput = (result) => {
|
|
105
115
|
if (!result || typeof result !== 'object' || !result.success) {
|
|
106
116
|
return null;
|
|
107
117
|
}
|
|
108
|
-
|
|
109
|
-
const lines = content.split('\n');
|
|
110
|
-
if (lines.length <= 20) {
|
|
111
|
-
return content;
|
|
112
|
-
}
|
|
113
|
-
return lines.slice(0, 20).join('\n') + '\n...(更多内容)';
|
|
118
|
+
return typeof result.content === 'string' ? result.content : String(result.content || '');
|
|
114
119
|
};
|
|
115
120
|
|
|
116
121
|
const formatEditOutput = (result) => {
|
|
117
122
|
if (!result || typeof result !== 'object' || !result.success) {
|
|
118
123
|
return null;
|
|
119
124
|
}
|
|
120
|
-
const searchContent = result.search || '';
|
|
121
|
-
const replaceContent = result.replace || '';
|
|
122
|
-
|
|
123
125
|
const lines = [];
|
|
126
|
+
const parts = diffLines(result.search || '', result.replace || '');
|
|
124
127
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (let i = 0; i < limit; i += 1) {
|
|
131
|
-
lines.push(colorizer(`${prefix}${list[i]}`));
|
|
128
|
+
for (const part of parts) {
|
|
129
|
+
const colorizer = part.added ? chalk.green : part.removed ? chalk.red : chalk.white;
|
|
130
|
+
const prefix = part.added ? '+ ' : part.removed ? '- ' : '';
|
|
131
|
+
for (const line of splitDisplayLines(part.value)) {
|
|
132
|
+
lines.push(colorizer(`${prefix}${line}`));
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
lines.push(colorizer(`${prefix} ...(更多代码)`));
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
appendLimitedLines(searchLines, '-', chalk.red);
|
|
139
|
-
appendLimitedLines(replaceLines, '+', chalk.green);
|
|
134
|
+
}
|
|
140
135
|
|
|
141
136
|
return lines.join('\n');
|
|
142
137
|
};
|
|
@@ -1002,11 +997,6 @@ const startChatSession = async ({
|
|
|
1002
997
|
if (editOutput) {
|
|
1003
998
|
console.log(editOutput);
|
|
1004
999
|
}
|
|
1005
|
-
} else if (functionName === 'todos') {
|
|
1006
|
-
// For todos, display full formatted output without truncation
|
|
1007
|
-
if (toolContent) {
|
|
1008
|
-
console.log(chalk.gray(` ${toolContent}`));
|
|
1009
|
-
}
|
|
1010
1000
|
} else if (functionName === 'skills') {
|
|
1011
1001
|
const preview = truncateForDisplay(toolContent, 200);
|
|
1012
1002
|
if (preview) {
|
package/src/prompt/tool.js
CHANGED
|
@@ -6,12 +6,7 @@ const getCurrentDate = () => {
|
|
|
6
6
|
return `${year}-${month}-${day}`;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
const toolSystemPrompt = `<current_date>${getCurrentDate()}</current_date
|
|
10
|
-
|
|
11
|
-
<basic_rules>
|
|
12
|
-
* 需要调用\`todos\`工具,来创建和更新待办事项的进度。
|
|
13
|
-
* 除代码内容外,应使用简体中文作为默认语言。
|
|
14
|
-
</basic_rules>`;
|
|
9
|
+
const toolSystemPrompt = `<current_date>${getCurrentDate()}</current_date>`;
|
|
15
10
|
|
|
16
11
|
module.exports = {
|
|
17
12
|
toolSystemPrompt
|
package/src/tools/index.js
CHANGED
|
@@ -2,12 +2,11 @@ const bash = require('./bash');
|
|
|
2
2
|
const read = require('./read');
|
|
3
3
|
const write = require('./write');
|
|
4
4
|
const edit = require('./edit');
|
|
5
|
-
const todos = require('./todos');
|
|
6
5
|
const skills = require('./skills');
|
|
7
6
|
const { createMcpManager } = require('./mcp');
|
|
8
7
|
const { loadSettings } = require('../config');
|
|
9
8
|
|
|
10
|
-
const TOOL_MODULES = [bash, read, write, edit,
|
|
9
|
+
const TOOL_MODULES = [bash, read, write, edit, skills];
|
|
11
10
|
|
|
12
11
|
const createToolRuntime = async (workspaceRoot, options = {}) => {
|
|
13
12
|
const defaultToolNames = TOOL_MODULES.map((tool) => tool.name);
|
package/src/utils/output.js
CHANGED
|
@@ -1,21 +1,64 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const {
|
|
2
|
+
const { Marked } = require('marked');
|
|
3
3
|
const { markedTerminal } = require('marked-terminal');
|
|
4
4
|
const { splitThinkContent, summarizeReasoning } = require('./think');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const ANSI_PATTERN = /\u001B\[[0-9;]*m/g;
|
|
7
|
+
|
|
8
|
+
const createUnderlineFormatter = (char) => (text = '') => {
|
|
9
|
+
const plainText = text.replace(ANSI_PATTERN, '').trim();
|
|
10
|
+
if (!plainText) {
|
|
11
|
+
return text;
|
|
12
|
+
}
|
|
13
|
+
return `${text}\n${char.repeat(Math.max(plainText.length, 3))}`;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const prefixLines = (prefix, text = '') => text
|
|
17
|
+
.split('\n')
|
|
18
|
+
.map((line) => (line ? `${prefix}${line}` : line))
|
|
19
|
+
.join('\n');
|
|
20
|
+
|
|
21
|
+
const createMarkdownParser = (options) => {
|
|
22
|
+
const parser = new Marked();
|
|
23
|
+
parser.use(markedTerminal(options));
|
|
24
|
+
return parser;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const coloredMarkdownParser = createMarkdownParser({
|
|
28
|
+
showSectionPrefix: false,
|
|
8
29
|
heading: chalk.cyan.bold,
|
|
9
|
-
firstHeading: chalk.cyan.bold,
|
|
30
|
+
firstHeading: chalk.cyan.bold.underline,
|
|
10
31
|
strong: chalk.bold,
|
|
11
32
|
em: chalk.italic,
|
|
12
33
|
blockquote: chalk.gray,
|
|
13
34
|
code: chalk.yellow,
|
|
14
35
|
codespan: chalk.yellow,
|
|
15
36
|
table: chalk.gray,
|
|
37
|
+
link: chalk.blue,
|
|
16
38
|
href: chalk.blue.underline
|
|
17
39
|
});
|
|
18
|
-
|
|
40
|
+
|
|
41
|
+
const plainMarkdownParser = createMarkdownParser({
|
|
42
|
+
showSectionPrefix: false,
|
|
43
|
+
firstHeading: createUnderlineFormatter('='),
|
|
44
|
+
heading: createUnderlineFormatter('-'),
|
|
45
|
+
blockquote: (text) => prefixLines('> ', text.replace(/^ {4}/gm, '')),
|
|
46
|
+
codespan: (text) => `\`${text}\``,
|
|
47
|
+
link: (text) => text,
|
|
48
|
+
href: (text) => text
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const shouldUseColoredMarkdown = () => Boolean(
|
|
52
|
+
process.stdout &&
|
|
53
|
+
process.stdout.isTTY &&
|
|
54
|
+
chalk.supportsColor &&
|
|
55
|
+
chalk.supportsColor.level > 0
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const renderMarkdownToTerminal = (text) => {
|
|
59
|
+
const parser = shouldUseColoredMarkdown() ? coloredMarkdownParser : plainMarkdownParser;
|
|
60
|
+
return parser.parse(text);
|
|
61
|
+
};
|
|
19
62
|
|
|
20
63
|
const mergeReasoningContent = (...segments) => {
|
|
21
64
|
const normalized = segments
|
|
@@ -38,8 +81,7 @@ const printAssistantContent = (text, ensureNewline) => {
|
|
|
38
81
|
return;
|
|
39
82
|
}
|
|
40
83
|
ensureNewline();
|
|
41
|
-
|
|
42
|
-
const output = marked(text);
|
|
84
|
+
const output = renderMarkdownToTerminal(text);
|
|
43
85
|
process.stdout.write(output);
|
|
44
86
|
if (!text.endsWith('\n')) {
|
|
45
87
|
process.stdout.write('\n');
|
package/src/utils/renderer.js
CHANGED
|
@@ -55,9 +55,6 @@ const formatHeader = (name, args, options = {}) => {
|
|
|
55
55
|
}
|
|
56
56
|
} else if (name === 'read') {
|
|
57
57
|
if (args.filePath) metaParts.push(args.filePath);
|
|
58
|
-
} else if (name === 'todos') {
|
|
59
|
-
const count = Array.isArray(args.todos) ? args.todos.length : 0;
|
|
60
|
-
metaParts.push(`${count} items`);
|
|
61
58
|
} else if (name === 'convert') {
|
|
62
59
|
if (typeof args.input === 'string') {
|
|
63
60
|
metaParts.push(`input=${truncateText(args.input, 160)}`);
|
package/src/tools/todos.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
|
|
3
|
-
const STATUS_EMOJI = {
|
|
4
|
-
pending: '⬜',
|
|
5
|
-
in_progress: '🕒',
|
|
6
|
-
completed: '✅'
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const formatTodosList = (todos = []) => {
|
|
10
|
-
if (!Array.isArray(todos) || todos.length === 0) {
|
|
11
|
-
return '暂无待办事项';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const lines = todos.map(item => {
|
|
15
|
-
const emoji = STATUS_EMOJI[item.status] || STATUS_EMOJI.pending;
|
|
16
|
-
const content = String(item.content || '').replace(/\s+/g, ' ').trim();
|
|
17
|
-
return `${emoji} ${content}`;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
return `${lines.join('\n')}`;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const validateTodos = (todos) => {
|
|
24
|
-
if (!Array.isArray(todos)) {
|
|
25
|
-
return 'todos 必须是数组';
|
|
26
|
-
}
|
|
27
|
-
for (const item of todos) {
|
|
28
|
-
if (!item || typeof item !== 'object') {
|
|
29
|
-
return 'todos 中的每一项必须是对象';
|
|
30
|
-
}
|
|
31
|
-
if (!item.id || !item.content || !item.status) {
|
|
32
|
-
return '每个待办事项必须包含 id/content/status';
|
|
33
|
-
}
|
|
34
|
-
if (!['pending', 'in_progress', 'completed'].includes(item.status)) {
|
|
35
|
-
return 'status 仅支持 pending/in_progress/completed';
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
let lastTodos = [];
|
|
42
|
-
|
|
43
|
-
const writeTodos = async ({ todos } = {}) => {
|
|
44
|
-
const error = validateTodos(todos);
|
|
45
|
-
if (error) {
|
|
46
|
-
return error;
|
|
47
|
-
}
|
|
48
|
-
lastTodos = todos;
|
|
49
|
-
const formatted = formatTodosList(todos);
|
|
50
|
-
return formatted;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const schema = {
|
|
54
|
-
type: 'function',
|
|
55
|
-
function: {
|
|
56
|
-
name: 'todos',
|
|
57
|
-
description: '更新任务列表(创建、修改、完成)',
|
|
58
|
-
parameters: {
|
|
59
|
-
type: 'object',
|
|
60
|
-
properties: {
|
|
61
|
-
todos: {
|
|
62
|
-
type: 'array',
|
|
63
|
-
items: {
|
|
64
|
-
type: 'object',
|
|
65
|
-
properties: {
|
|
66
|
-
id: { type: 'string' },
|
|
67
|
-
content: { type: 'string' },
|
|
68
|
-
status: {
|
|
69
|
-
type: 'string',
|
|
70
|
-
enum: ['pending', 'in_progress', 'completed']
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
required: ['id', 'content', 'status']
|
|
74
|
-
},
|
|
75
|
-
description: '完整的待办事项数组'
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
required: ['todos']
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const getLastTodos = () => lastTodos;
|
|
84
|
-
|
|
85
|
-
module.exports = {
|
|
86
|
-
name: 'todos',
|
|
87
|
-
schema,
|
|
88
|
-
handler: writeTodos,
|
|
89
|
-
getLastTodos
|
|
90
|
-
};
|