@ww_nero/mini-cli 1.0.87 → 1.0.90

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ww_nero/mini-cli",
3
- "version": "1.0.87",
3
+ "version": "1.0.90",
4
4
  "description": "极简的 AI 命令行助手",
5
5
  "bin": {
6
6
  "mini": "bin/mini.js"
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
- const content = result.content || '';
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 searchLines = searchContent.split('\n');
126
- const replaceLines = replaceContent.split('\n');
127
- const maxLinesPerSide = 20;
128
- const appendLimitedLines = (list, prefix, colorizer) => {
129
- const limit = Math.min(list.length, maxLinesPerSide);
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
- if (list.length > maxLinesPerSide) {
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) {
@@ -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/edit.js CHANGED
@@ -1,35 +1,13 @@
1
- const { diffLines } = require('diff');
2
1
  const {
3
2
  resolveWorkspacePath,
4
3
  readTextFile,
5
4
  writeTextFile,
6
5
  normalizeLineBreaks,
7
6
  isCodeIdentical,
8
- processContent
7
+ processContent,
8
+ computeDiffStats
9
9
  } = require('../utils/helpers');
10
10
 
11
- const countLines = (text = '') => {
12
- if (!text) return 0;
13
- const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14
- const lines = normalized.split('\n');
15
- if (lines[lines.length - 1] === '') {
16
- lines.pop();
17
- }
18
- return lines.length;
19
- };
20
-
21
- const computeDiffStats = (before = '', after = '') => {
22
- const parts = diffLines(before, after);
23
- return parts.reduce((acc, part) => {
24
- if (part.added) {
25
- acc.added += countLines(part.value);
26
- } else if (part.removed) {
27
- acc.removed += countLines(part.value);
28
- }
29
- return acc;
30
- }, { added: 0, removed: 0 });
31
- };
32
-
33
11
  const editFile = async ({ filePath, search, replace } = {}, context = {}) => {
34
12
  if (!filePath || typeof filePath !== 'string') {
35
13
  return 'filePath 参数不能为空';
@@ -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, todos, skills];
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);
@@ -1,3 +1,4 @@
1
+ const { diffLines } = require('diff');
1
2
  const fsPromises = require('fs/promises');
2
3
  const path = require('path');
3
4
 
@@ -7,6 +8,28 @@ const normalizeLineBreaks = (text = '') => {
7
8
  return /^\n*$/.test(normalized) ? '' : normalized;
8
9
  };
9
10
 
11
+ const countLines = (text = '') => {
12
+ if (!text) return 0;
13
+ const normalized = String(text).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
14
+ const lines = normalized.split('\n');
15
+ if (lines[lines.length - 1] === '') {
16
+ lines.pop();
17
+ }
18
+ return lines.length;
19
+ };
20
+
21
+ const computeDiffStats = (before = '', after = '') => {
22
+ const parts = diffLines(String(before || ''), String(after || ''));
23
+ return parts.reduce((acc, part) => {
24
+ if (part.added) {
25
+ acc.added += countLines(part.value);
26
+ } else if (part.removed) {
27
+ acc.removed += countLines(part.value);
28
+ }
29
+ return acc;
30
+ }, { added: 0, removed: 0 });
31
+ };
32
+
10
33
  const resolveWorkspacePath = (workspaceRoot, targetPath = '.', options = {}) => {
11
34
  if (!workspaceRoot) {
12
35
  throw new Error('未找到工作区目录');
@@ -83,6 +106,8 @@ const safeJsonParse = (text) => {
83
106
  };
84
107
 
85
108
  module.exports = {
109
+ countLines,
110
+ computeDiffStats,
86
111
  normalizeLineBreaks,
87
112
  resolveWorkspacePath,
88
113
  readTextFile,
@@ -1,4 +1,5 @@
1
1
  const chalk = require('chalk');
2
+ const { countLines, computeDiffStats } = require('./helpers');
2
3
 
3
4
  const truncateText = (text, maxLength = 200) => {
4
5
  if (!text || typeof text !== 'string') {
@@ -10,15 +11,6 @@ const truncateText = (text, maxLength = 200) => {
10
11
  return text.substring(0, maxLength) + '...';
11
12
  };
12
13
 
13
- const countLines = (text = '') => {
14
- if (!text) return 0;
15
- const normalized = text.replace(/\r/g, '');
16
- const newlineMatches = normalized.match(/\n/g);
17
- const newlineCount = Array.isArray(newlineMatches) ? newlineMatches.length : 0;
18
- const endsWithNewline = normalized.endsWith('\n');
19
- return endsWithNewline ? newlineCount : newlineCount + 1;
20
- };
21
-
22
14
  const formatWriteHeader = (args = {}) => {
23
15
  const filePath = args.filePath || '';
24
16
  const lineCount = countLines(args.content);
@@ -27,9 +19,8 @@ const formatWriteHeader = (args = {}) => {
27
19
 
28
20
  const formatEditHeader = (args = {}) => {
29
21
  const filePath = args.filePath || '';
30
- const searchLines = countLines(args.search);
31
- const replaceLines = countLines(args.replace);
32
- return ` (${filePath}, ${chalk.green(`+${replaceLines}`)} ${chalk.red(`-${searchLines}`)})`;
22
+ const { added, removed } = computeDiffStats(args.search, args.replace);
23
+ return ` (${filePath}, ${chalk.green(`+${added}`)} ${chalk.red(`-${removed}`)})`;
33
24
  };
34
25
 
35
26
  const formatHeader = (name, args, options = {}) => {
@@ -55,9 +46,6 @@ const formatHeader = (name, args, options = {}) => {
55
46
  }
56
47
  } else if (name === 'read') {
57
48
  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
49
  } else if (name === 'convert') {
62
50
  if (typeof args.input === 'string') {
63
51
  metaParts.push(`input=${truncateText(args.input, 160)}`);
@@ -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
- };