mistagent 0.1.18 → 0.1.20
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/dist/src/components/App.d.ts +2 -0
- package/dist/src/components/App.js +23 -5
- package/dist/src/components/Composer.js +2 -1
- package/dist/src/components/Header.js +74 -166
- package/dist/src/components/MainContent.js +3 -14
- package/dist/src/components/shared/MarkdownRenderer.js +15 -26
- package/dist/src/components/shared/TextInput.js +62 -3
- package/dist/src/contexts/ChatContext.d.ts +2 -0
- package/dist/src/contexts/ChatContext.js +1 -1
- package/dist/src/hooks/useChat.js +71 -10
- package/dist/src/main.js +62 -4
- package/dist/src/types/api.d.ts +4 -0
- package/dist/src/utils/config.d.ts +2 -0
- package/dist/src/utils/config.js +23 -0
- package/dist/src/utils/constants.d.ts +1 -1
- package/dist/src/utils/constants.js +1 -1
- package/dist/src/utils/fileTunnel.d.ts +7 -2
- package/dist/src/utils/fileTunnel.js +376 -5
- package/dist/src/utils/markdown.d.ts +10 -0
- package/dist/src/utils/markdown.js +223 -0
- package/dist/src/utils/updateChecker.js +10 -4
- package/package.json +3 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/src/api/auth.d.ts.map +0 -1
- package/dist/src/api/auth.js.map +0 -1
- package/dist/src/api/chat.d.ts.map +0 -1
- package/dist/src/api/chat.js.map +0 -1
- package/dist/src/api/client.d.ts.map +0 -1
- package/dist/src/api/client.js.map +0 -1
- package/dist/src/api/models.d.ts.map +0 -1
- package/dist/src/api/models.js.map +0 -1
- package/dist/src/api/sessions.d.ts.map +0 -1
- package/dist/src/api/sessions.js.map +0 -1
- package/dist/src/api/skills.d.ts.map +0 -1
- package/dist/src/api/skills.js.map +0 -1
- package/dist/src/api/tools.d.ts.map +0 -1
- package/dist/src/api/tools.js.map +0 -1
- package/dist/src/api/tunnel.d.ts.map +0 -1
- package/dist/src/api/tunnel.js.map +0 -1
- package/dist/src/components/App.d.ts.map +0 -1
- package/dist/src/components/App.js.map +0 -1
- package/dist/src/components/AppLayout.d.ts.map +0 -1
- package/dist/src/components/AppLayout.js.map +0 -1
- package/dist/src/components/Composer.d.ts.map +0 -1
- package/dist/src/components/Composer.js.map +0 -1
- package/dist/src/components/Footer.d.ts.map +0 -1
- package/dist/src/components/Footer.js.map +0 -1
- package/dist/src/components/Header.d.ts.map +0 -1
- package/dist/src/components/Header.js.map +0 -1
- package/dist/src/components/HistoryItemDisplay.d.ts.map +0 -1
- package/dist/src/components/HistoryItemDisplay.js.map +0 -1
- package/dist/src/components/InputPrompt.d.ts.map +0 -1
- package/dist/src/components/InputPrompt.js.map +0 -1
- package/dist/src/components/LoadingIndicator.d.ts.map +0 -1
- package/dist/src/components/LoadingIndicator.js.map +0 -1
- package/dist/src/components/LoginPrompt.d.ts.map +0 -1
- package/dist/src/components/LoginPrompt.js.map +0 -1
- package/dist/src/components/MainContent.d.ts.map +0 -1
- package/dist/src/components/MainContent.js.map +0 -1
- package/dist/src/components/ModelPicker.d.ts.map +0 -1
- package/dist/src/components/ModelPicker.js.map +0 -1
- package/dist/src/components/SessionPicker.d.ts.map +0 -1
- package/dist/src/components/SessionPicker.js.map +0 -1
- package/dist/src/components/SuggestionsDisplay.d.ts.map +0 -1
- package/dist/src/components/SuggestionsDisplay.js.map +0 -1
- package/dist/src/components/ThemePicker.d.ts.map +0 -1
- package/dist/src/components/ThemePicker.js.map +0 -1
- package/dist/src/components/messages/AssistantMessage.d.ts.map +0 -1
- package/dist/src/components/messages/AssistantMessage.js.map +0 -1
- package/dist/src/components/messages/CommandResult.d.ts.map +0 -1
- package/dist/src/components/messages/CommandResult.js.map +0 -1
- package/dist/src/components/messages/ErrorMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ErrorMessage.js.map +0 -1
- package/dist/src/components/messages/InfoMessage.d.ts.map +0 -1
- package/dist/src/components/messages/InfoMessage.js.map +0 -1
- package/dist/src/components/messages/ModelMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ModelMessage.js.map +0 -1
- package/dist/src/components/messages/SessionMessage.d.ts.map +0 -1
- package/dist/src/components/messages/SessionMessage.js.map +0 -1
- package/dist/src/components/messages/ToolCallMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ToolCallMessage.js.map +0 -1
- package/dist/src/components/messages/UserMessage.d.ts.map +0 -1
- package/dist/src/components/messages/UserMessage.js.map +0 -1
- package/dist/src/components/shared/HorizontalLine.d.ts.map +0 -1
- package/dist/src/components/shared/HorizontalLine.js.map +0 -1
- package/dist/src/components/shared/MarkdownRenderer.d.ts.map +0 -1
- package/dist/src/components/shared/MarkdownRenderer.js.map +0 -1
- package/dist/src/components/shared/Spinner.d.ts.map +0 -1
- package/dist/src/components/shared/Spinner.js.map +0 -1
- package/dist/src/components/shared/TextInput.d.ts.map +0 -1
- package/dist/src/components/shared/TextInput.js.map +0 -1
- package/dist/src/contexts/AppContext.d.ts.map +0 -1
- package/dist/src/contexts/AppContext.js.map +0 -1
- package/dist/src/contexts/ChatContext.d.ts.map +0 -1
- package/dist/src/contexts/ChatContext.js.map +0 -1
- package/dist/src/contexts/KeypressContext.d.ts.map +0 -1
- package/dist/src/contexts/KeypressContext.js.map +0 -1
- package/dist/src/contexts/ModelContext.d.ts.map +0 -1
- package/dist/src/contexts/ModelContext.js.map +0 -1
- package/dist/src/contexts/SessionContext.d.ts.map +0 -1
- package/dist/src/contexts/SessionContext.js.map +0 -1
- package/dist/src/contexts/UIContext.d.ts.map +0 -1
- package/dist/src/contexts/UIContext.js.map +0 -1
- package/dist/src/hooks/useChat.d.ts.map +0 -1
- package/dist/src/hooks/useChat.js.map +0 -1
- package/dist/src/hooks/useFileCompletion.d.ts.map +0 -1
- package/dist/src/hooks/useFileCompletion.js.map +0 -1
- package/dist/src/hooks/useInputHistory.d.ts.map +0 -1
- package/dist/src/hooks/useInputHistory.js.map +0 -1
- package/dist/src/hooks/useKeypress.d.ts.map +0 -1
- package/dist/src/hooks/useKeypress.js.map +0 -1
- package/dist/src/hooks/useLoadingIndicator.d.ts.map +0 -1
- package/dist/src/hooks/useLoadingIndicator.js.map +0 -1
- package/dist/src/hooks/usePasteBuffer.d.ts.map +0 -1
- package/dist/src/hooks/usePasteBuffer.js.map +0 -1
- package/dist/src/hooks/useSlashCommand.d.ts.map +0 -1
- package/dist/src/hooks/useSlashCommand.js.map +0 -1
- package/dist/src/hooks/useStdinInterceptor.d.ts.map +0 -1
- package/dist/src/hooks/useStdinInterceptor.js.map +0 -1
- package/dist/src/hooks/useSymbolCompletion.d.ts.map +0 -1
- package/dist/src/hooks/useSymbolCompletion.js.map +0 -1
- package/dist/src/hooks/useTextBuffer.d.ts.map +0 -1
- package/dist/src/hooks/useTextBuffer.js.map +0 -1
- package/dist/src/main.d.ts.map +0 -1
- package/dist/src/main.js.map +0 -1
- package/dist/src/tools/code-analyzer/config/ignore-service.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/config/ignore-service.js.map +0 -1
- package/dist/src/tools/code-analyzer/config/supported-languages.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/config/supported-languages.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/graph.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/graph.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/types.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/types.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/call-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/call-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/community-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/community-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/import-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/import-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/pipeline.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/pipeline.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/process-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/process-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/utils.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/utils.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.js.map +0 -1
- package/dist/src/tools/code-analyzer/index.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/index.js.map +0 -1
- package/dist/src/tools/code-analyzer/lib/utils.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/lib/utils.js.map +0 -1
- package/dist/src/tools/code-analyzer/types/pipeline.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/types/pipeline.js.map +0 -1
- package/dist/src/types/api.d.ts.map +0 -1
- package/dist/src/types/api.js.map +0 -1
- package/dist/src/types/history.d.ts.map +0 -1
- package/dist/src/types/history.js.map +0 -1
- package/dist/src/utils/colors.d.ts.map +0 -1
- package/dist/src/utils/colors.js.map +0 -1
- package/dist/src/utils/config.d.ts.map +0 -1
- package/dist/src/utils/config.js.map +0 -1
- package/dist/src/utils/constants.d.ts.map +0 -1
- package/dist/src/utils/constants.js.map +0 -1
- package/dist/src/utils/fileRef.d.ts.map +0 -1
- package/dist/src/utils/fileRef.js.map +0 -1
- package/dist/src/utils/fileTunnel.d.ts.map +0 -1
- package/dist/src/utils/fileTunnel.js.map +0 -1
- package/dist/src/utils/formatters.d.ts.map +0 -1
- package/dist/src/utils/formatters.js.map +0 -1
- package/dist/src/utils/pasteUtils.d.ts.map +0 -1
- package/dist/src/utils/pasteUtils.js.map +0 -1
- package/dist/src/utils/skillScanner.d.ts.map +0 -1
- package/dist/src/utils/skillScanner.js.map +0 -1
- package/dist/src/utils/textUtils.d.ts.map +0 -1
- package/dist/src/utils/textUtils.js.map +0 -1
- package/dist/src/utils/updateChecker.d.ts.map +0 -1
- package/dist/src/utils/updateChecker.js.map +0 -1
|
@@ -110,6 +110,95 @@ export async function executeFileOperation(req) {
|
|
|
110
110
|
});
|
|
111
111
|
break;
|
|
112
112
|
}
|
|
113
|
+
// ─── code_search 工具(Codebase Investigator 子图) ───
|
|
114
|
+
case 'grep_search': {
|
|
115
|
+
const pattern = req.args.pattern;
|
|
116
|
+
const searchPath = req.args.path || '.';
|
|
117
|
+
const include = req.args.include || '';
|
|
118
|
+
const contextLines = parseInt(req.args.context_lines || '2', 10);
|
|
119
|
+
if (!pattern) {
|
|
120
|
+
return fail(req, 'Missing required argument: pattern');
|
|
121
|
+
}
|
|
122
|
+
const { execSync } = await import('node:child_process');
|
|
123
|
+
const cmd = ['grep', '-rn', '--color=never'];
|
|
124
|
+
if (contextLines > 0)
|
|
125
|
+
cmd.push('-C', String(Math.min(contextLines, 5)));
|
|
126
|
+
if (include)
|
|
127
|
+
cmd.push('--include', include);
|
|
128
|
+
IGNORE_DIRS.forEach((d) => cmd.push('--exclude-dir', d));
|
|
129
|
+
cmd.push(pattern, searchPath);
|
|
130
|
+
try {
|
|
131
|
+
const stdout = execSync(cmd.join(' '), {
|
|
132
|
+
encoding: 'utf-8',
|
|
133
|
+
timeout: 30_000,
|
|
134
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
135
|
+
});
|
|
136
|
+
const lines = stdout.split('\n');
|
|
137
|
+
result = lines.length > 200
|
|
138
|
+
? lines.slice(0, 200).join('\n') + `\n\n... (结果已截断,共 ${lines.length} 行,显示前 200 行)`
|
|
139
|
+
: stdout;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const exitCode = err.status;
|
|
143
|
+
if (exitCode === 1) {
|
|
144
|
+
result = `未找到匹配 '${pattern}' 的结果`;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'find_files': {
|
|
153
|
+
const pattern = req.args.pattern;
|
|
154
|
+
const searchPath = req.args.path || '.';
|
|
155
|
+
if (!pattern) {
|
|
156
|
+
return fail(req, 'Missing required argument: pattern');
|
|
157
|
+
}
|
|
158
|
+
const root = path.resolve(searchPath);
|
|
159
|
+
if (!fs.existsSync(root)) {
|
|
160
|
+
return fail(req, `路径不存在: ${searchPath}`);
|
|
161
|
+
}
|
|
162
|
+
const found = globFiles(root, pattern, 100);
|
|
163
|
+
result = found.length > 0 ? found.join('\n') : `未找到匹配 '${pattern}' 的文件`;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'directory_tree': {
|
|
167
|
+
const treePath = req.args.path || '.';
|
|
168
|
+
const maxDepth = Math.min(parseInt(req.args.max_depth || '3', 10), 4);
|
|
169
|
+
const root = path.resolve(treePath);
|
|
170
|
+
if (!fs.existsSync(root)) {
|
|
171
|
+
return fail(req, `路径不存在: ${treePath}`);
|
|
172
|
+
}
|
|
173
|
+
result = buildTree(root, maxDepth);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'replace_in_file': {
|
|
177
|
+
const filePath = req.args.file_path;
|
|
178
|
+
const oldStr = req.args.old_string ?? '';
|
|
179
|
+
const newStr = req.args.new_string ?? '';
|
|
180
|
+
const allowMultiple = req.args.allow_multiple === 'true';
|
|
181
|
+
if (!filePath) {
|
|
182
|
+
return fail(req, 'Missing required argument: file_path');
|
|
183
|
+
}
|
|
184
|
+
if (!fs.existsSync(filePath)) {
|
|
185
|
+
return fail(req, `File not found: ${filePath}`);
|
|
186
|
+
}
|
|
187
|
+
if (oldStr === newStr) {
|
|
188
|
+
return fail(req, 'old_string and new_string are identical');
|
|
189
|
+
}
|
|
190
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
191
|
+
const replaceResult = calculateReplacement(content, oldStr, newStr, allowMultiple);
|
|
192
|
+
if (replaceResult.occurrences === 0) {
|
|
193
|
+
return fail(req, 'No match found for old_string in the file.');
|
|
194
|
+
}
|
|
195
|
+
if (replaceResult.strategy.includes('error')) {
|
|
196
|
+
return fail(req, `Found ${replaceResult.occurrences} occurrences but allow_multiple=false.`);
|
|
197
|
+
}
|
|
198
|
+
fs.writeFileSync(filePath, replaceResult.newContent, 'utf-8');
|
|
199
|
+
result = `Replaced ${replaceResult.occurrences} occurrence(s) in ${filePath} (strategy: ${replaceResult.strategy})`;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
113
202
|
default:
|
|
114
203
|
return fail(req, `Unknown tool: ${req.tool_name}`);
|
|
115
204
|
}
|
|
@@ -125,17 +214,216 @@ export async function executeFileOperation(req) {
|
|
|
125
214
|
return fail(req, errorMsg);
|
|
126
215
|
}
|
|
127
216
|
}
|
|
217
|
+
function exactReplace(content, oldStr, newStr, allowMultiple) {
|
|
218
|
+
const norm = content.replace(/\r\n/g, '\n');
|
|
219
|
+
const oldNorm = oldStr.replace(/\r\n/g, '\n');
|
|
220
|
+
const count = norm.split(oldNorm).length - 1;
|
|
221
|
+
if (count === 0)
|
|
222
|
+
return null;
|
|
223
|
+
if (count > 1 && !allowMultiple) {
|
|
224
|
+
return { newContent: content, occurrences: count, strategy: 'exact_multiple_error' };
|
|
225
|
+
}
|
|
226
|
+
const newNorm = newStr.replace(/\r\n/g, '\n');
|
|
227
|
+
const result = allowMultiple
|
|
228
|
+
? norm.split(oldNorm).join(newNorm)
|
|
229
|
+
: norm.replace(oldNorm, newNorm);
|
|
230
|
+
return { newContent: result, occurrences: allowMultiple ? count : 1, strategy: 'exact' };
|
|
231
|
+
}
|
|
232
|
+
function flexibleReplace(content, oldStr, newStr, allowMultiple) {
|
|
233
|
+
const contentLines = content.replace(/\r\n/g, '\n').split('\n');
|
|
234
|
+
const searchLines = oldStr.replace(/\r\n/g, '\n').split('\n');
|
|
235
|
+
const replaceLines = newStr.replace(/\r\n/g, '\n').split('\n');
|
|
236
|
+
const strippedSearch = searchLines.map((l) => l.trim());
|
|
237
|
+
if (!strippedSearch[0])
|
|
238
|
+
return null;
|
|
239
|
+
const matches = [];
|
|
240
|
+
let i = 0;
|
|
241
|
+
while (i <= contentLines.length - searchLines.length) {
|
|
242
|
+
let matched = true;
|
|
243
|
+
for (let j = 0; j < strippedSearch.length; j++) {
|
|
244
|
+
if (contentLines[i + j].trim() !== strippedSearch[j]) {
|
|
245
|
+
matched = false;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (matched) {
|
|
250
|
+
matches.push(i);
|
|
251
|
+
i += searchLines.length;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
i++;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (matches.length === 0)
|
|
258
|
+
return null;
|
|
259
|
+
if (matches.length > 1 && !allowMultiple) {
|
|
260
|
+
return { newContent: content, occurrences: matches.length, strategy: 'flexible_multiple_error' };
|
|
261
|
+
}
|
|
262
|
+
const targets = allowMultiple ? matches : [matches[0]];
|
|
263
|
+
const resultLines = [...contentLines];
|
|
264
|
+
for (const start of [...targets].reverse()) {
|
|
265
|
+
const firstLine = contentLines[start];
|
|
266
|
+
const indent = firstLine.slice(0, firstLine.length - firstLine.trimStart().length);
|
|
267
|
+
const indented = replaceLines.map((r, k) => k === 0 ? indent + r.trimStart() : (r.trim() ? indent + r : r));
|
|
268
|
+
resultLines.splice(start, searchLines.length, ...indented);
|
|
269
|
+
}
|
|
270
|
+
return { newContent: resultLines.join('\n'), occurrences: targets.length, strategy: 'flexible' };
|
|
271
|
+
}
|
|
272
|
+
function regexReplace(content, oldStr, newStr, allowMultiple) {
|
|
273
|
+
const norm = content.replace(/\r\n/g, '\n');
|
|
274
|
+
const oldNorm = oldStr.replace(/\r\n/g, '\n').trim();
|
|
275
|
+
if (!oldNorm)
|
|
276
|
+
return null;
|
|
277
|
+
const tokens = oldNorm.split(/([():\[\]{}<>=,;])/).filter(Boolean).map((t) => t.trim()).filter(Boolean);
|
|
278
|
+
const parts = tokens.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
279
|
+
if (parts.length === 0)
|
|
280
|
+
return null;
|
|
281
|
+
const pattern = '([ \\t]*)' + parts.join('\\s*');
|
|
282
|
+
let globalRegex;
|
|
283
|
+
try {
|
|
284
|
+
globalRegex = new RegExp(pattern, 'gm');
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const matches = [];
|
|
290
|
+
let m;
|
|
291
|
+
let lastIndex = 0;
|
|
292
|
+
while ((m = globalRegex.exec(norm)) !== null) {
|
|
293
|
+
matches.push(m);
|
|
294
|
+
if (!allowMultiple)
|
|
295
|
+
break;
|
|
296
|
+
if (globalRegex.lastIndex === lastIndex)
|
|
297
|
+
globalRegex.lastIndex++;
|
|
298
|
+
lastIndex = globalRegex.lastIndex;
|
|
299
|
+
}
|
|
300
|
+
if (matches.length === 0)
|
|
301
|
+
return null;
|
|
302
|
+
if (matches.length > 1 && !allowMultiple) {
|
|
303
|
+
return { newContent: content, occurrences: matches.length, strategy: 'regex_multiple_error' };
|
|
304
|
+
}
|
|
305
|
+
const newNorm = newStr.replace(/\r\n/g, '\n');
|
|
306
|
+
let result = norm;
|
|
307
|
+
for (const match of [...matches].reverse()) {
|
|
308
|
+
const indent = match[1] || '';
|
|
309
|
+
const indented = newNorm.split('\n').map((line, idx) => idx > 0 && line.trim() ? indent + line : line).join('\n');
|
|
310
|
+
result = result.slice(0, match.index) + indent + indented.trimStart() + result.slice(match.index + match[0].length);
|
|
311
|
+
}
|
|
312
|
+
return { newContent: result, occurrences: matches.length, strategy: 'regex' };
|
|
313
|
+
}
|
|
314
|
+
function levenshteinDistance(s1, s2) {
|
|
315
|
+
if (s1.length < s2.length)
|
|
316
|
+
return levenshteinDistance(s2, s1);
|
|
317
|
+
if (s2.length === 0)
|
|
318
|
+
return s1.length;
|
|
319
|
+
let prevRow = Array.from({ length: s2.length + 1 }, (_, i) => i);
|
|
320
|
+
for (let i = 0; i < s1.length; i++) {
|
|
321
|
+
const currRow = [i + 1];
|
|
322
|
+
for (let j = 0; j < s2.length; j++) {
|
|
323
|
+
currRow.push(Math.min(prevRow[j + 1] + 1, currRow[j] + 1, prevRow[j] + (s1[i] !== s2[j] ? 1 : 0)));
|
|
324
|
+
}
|
|
325
|
+
prevRow = currRow;
|
|
326
|
+
}
|
|
327
|
+
return prevRow[s2.length];
|
|
328
|
+
}
|
|
329
|
+
function fuzzyReplace(content, oldStr, newStr, allowMultiple) {
|
|
330
|
+
if (oldStr.trim().length < 10)
|
|
331
|
+
return null;
|
|
332
|
+
const norm = content.replace(/\r\n/g, '\n');
|
|
333
|
+
const oldNorm = oldStr.replace(/\r\n/g, '\n');
|
|
334
|
+
const sourceLines = norm.split('\n');
|
|
335
|
+
const searchLines = oldNorm.split('\n');
|
|
336
|
+
const nSearch = searchLines.length;
|
|
337
|
+
if (nSearch === 0 || sourceLines.length * oldNorm.length ** 2 > 4e8)
|
|
338
|
+
return null;
|
|
339
|
+
const searchBlock = oldNorm.replace(/\s/g, '');
|
|
340
|
+
if (searchBlock.length === 0)
|
|
341
|
+
return null;
|
|
342
|
+
const candidates = [];
|
|
343
|
+
for (let i = 0; i <= sourceLines.length - nSearch; i++) {
|
|
344
|
+
const window = sourceLines.slice(i, i + nSearch).join('\n');
|
|
345
|
+
const windowStripped = window.replace(/\s/g, '');
|
|
346
|
+
if (Math.abs(windowStripped.length - searchBlock.length) > searchBlock.length * 0.2)
|
|
347
|
+
continue;
|
|
348
|
+
const dNorm = levenshteinDistance(windowStripped, searchBlock);
|
|
349
|
+
const dRaw = levenshteinDistance(window, oldNorm);
|
|
350
|
+
const weighted = dNorm + (dRaw - dNorm) * 0.1;
|
|
351
|
+
const score = weighted / searchBlock.length;
|
|
352
|
+
if (score <= 0.1)
|
|
353
|
+
candidates.push({ score, start: i });
|
|
354
|
+
}
|
|
355
|
+
if (candidates.length === 0)
|
|
356
|
+
return null;
|
|
357
|
+
candidates.sort((a, b) => a.score - b.score);
|
|
358
|
+
const selected = [];
|
|
359
|
+
const usedLines = new Set();
|
|
360
|
+
for (const { start } of candidates) {
|
|
361
|
+
let overlap = false;
|
|
362
|
+
for (let l = start; l < start + nSearch; l++) {
|
|
363
|
+
if (usedLines.has(l)) {
|
|
364
|
+
overlap = true;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (overlap)
|
|
369
|
+
continue;
|
|
370
|
+
selected.push(start);
|
|
371
|
+
for (let l = start; l < start + nSearch; l++)
|
|
372
|
+
usedLines.add(l);
|
|
373
|
+
if (!allowMultiple)
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
if (selected.length === 0)
|
|
377
|
+
return null;
|
|
378
|
+
const newNorm = newStr.replace(/\r\n/g, '\n');
|
|
379
|
+
const resultLines = [...sourceLines];
|
|
380
|
+
for (const start of [...selected].sort((a, b) => b - a)) {
|
|
381
|
+
const firstLine = sourceLines[start];
|
|
382
|
+
const indent = firstLine.slice(0, firstLine.length - firstLine.trimStart().length);
|
|
383
|
+
const replLines = newNorm.split('\n').map((r, k) => k === 0 ? indent + r.trimStart() : (r.trim() ? indent + r : r));
|
|
384
|
+
resultLines.splice(start, nSearch, ...replLines);
|
|
385
|
+
}
|
|
386
|
+
return { newContent: resultLines.join('\n'), occurrences: selected.length, strategy: 'fuzzy' };
|
|
387
|
+
}
|
|
388
|
+
function calculateReplacement(content, oldStr, newStr, allowMultiple) {
|
|
389
|
+
if (!oldStr)
|
|
390
|
+
return { newContent: content, occurrences: 0, strategy: 'none' };
|
|
391
|
+
for (const fn of [exactReplace, flexibleReplace, regexReplace, fuzzyReplace]) {
|
|
392
|
+
const r = fn(content, oldStr, newStr, allowMultiple);
|
|
393
|
+
if (r && (r.occurrences > 0 || r.strategy.includes('error')))
|
|
394
|
+
return r;
|
|
395
|
+
}
|
|
396
|
+
return { newContent: content, occurrences: 0, strategy: 'none' };
|
|
397
|
+
}
|
|
128
398
|
// ─── 敏感操作判断 ───
|
|
129
|
-
|
|
399
|
+
/** 始终需要确认的写入/修改工具 */
|
|
400
|
+
const WRITE_TOOLS = new Set([
|
|
130
401
|
'write_file',
|
|
402
|
+
'replace_in_file',
|
|
131
403
|
'file_delete',
|
|
132
404
|
'move_file',
|
|
133
405
|
'copy_file',
|
|
134
|
-
'bash_shell_run',
|
|
135
406
|
]);
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
|
|
407
|
+
/**
|
|
408
|
+
* 只读 bash 命令模式 — 匹配 grep, find, cat, head, tail, ls, wc, git log/diff/show 等
|
|
409
|
+
* 这些命令在 plan 研究阶段可自动放行
|
|
410
|
+
*/
|
|
411
|
+
const READONLY_CMD_RE = /^\s*(grep|rg|find|cat|head|tail|less|ls|tree|wc|file|stat|which|echo|pwd|env|git\s+(log|diff|show|status|branch|remote|tag|blame|rev-parse))\b/;
|
|
412
|
+
/**
|
|
413
|
+
* 是否为需要用户确认的敏感操作
|
|
414
|
+
* - 写入/修改工具:始终 sensitive
|
|
415
|
+
* - bash_shell_run:只读命令不 sensitive,其他命令 sensitive
|
|
416
|
+
* - read_file / list_directory 等只读工具:不 sensitive
|
|
417
|
+
*/
|
|
418
|
+
export function isSensitiveOperation(toolName, args) {
|
|
419
|
+
if (WRITE_TOOLS.has(toolName))
|
|
420
|
+
return true;
|
|
421
|
+
if (toolName === 'bash_shell_run') {
|
|
422
|
+
const cmd = args?.command || '';
|
|
423
|
+
// 只读命令自动放行
|
|
424
|
+
return !READONLY_CMD_RE.test(cmd);
|
|
425
|
+
}
|
|
426
|
+
return false;
|
|
139
427
|
}
|
|
140
428
|
/**
|
|
141
429
|
* 是否为无论 session 设置都必须确认的高危操作
|
|
@@ -152,6 +440,89 @@ export function isAlwaysConfirmRequired(req) {
|
|
|
152
440
|
}
|
|
153
441
|
return false;
|
|
154
442
|
}
|
|
443
|
+
// ─── code_search 辅助 ───
|
|
444
|
+
const IGNORE_DIRS = new Set([
|
|
445
|
+
'node_modules', '.git', '__pycache__', '.venv', 'venv',
|
|
446
|
+
'.tox', '.mypy_cache', '.pytest_cache', 'dist', 'build',
|
|
447
|
+
'.next', '.nuxt', 'target', 'vendor',
|
|
448
|
+
]);
|
|
449
|
+
/** 递归 glob 匹配文件,返回相对路径列表 */
|
|
450
|
+
function globFiles(root, pattern, maxResults) {
|
|
451
|
+
const results = [];
|
|
452
|
+
function walk(dir) {
|
|
453
|
+
if (results.length >= maxResults)
|
|
454
|
+
return;
|
|
455
|
+
let entries;
|
|
456
|
+
try {
|
|
457
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
for (const entry of entries) {
|
|
463
|
+
if (results.length >= maxResults)
|
|
464
|
+
break;
|
|
465
|
+
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith('.'))
|
|
466
|
+
continue;
|
|
467
|
+
const full = path.join(dir, entry.name);
|
|
468
|
+
if (entry.isDirectory()) {
|
|
469
|
+
walk(full);
|
|
470
|
+
}
|
|
471
|
+
else if (entry.isFile() && minimatch(path.relative(root, full), pattern)) {
|
|
472
|
+
results.push(path.relative(root, full));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
walk(root);
|
|
477
|
+
return results;
|
|
478
|
+
}
|
|
479
|
+
/** 简易 glob 匹配(不依赖外部库,支持 *, **, ? 模式) */
|
|
480
|
+
function minimatch(filePath, pattern) {
|
|
481
|
+
// 将 glob 转为正则
|
|
482
|
+
let re = pattern
|
|
483
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
|
|
484
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}') // 占位 **
|
|
485
|
+
.replace(/\*/g, '[^/]*') // * → 匹配非 / 字符
|
|
486
|
+
.replace(/\?/g, '[^/]') // ? → 匹配单个非 / 字符
|
|
487
|
+
.replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** → 匹配任意路径
|
|
488
|
+
re = '^' + re + '$';
|
|
489
|
+
return new RegExp(re).test(filePath);
|
|
490
|
+
}
|
|
491
|
+
/** 构建缩进的目录树文本 */
|
|
492
|
+
function buildTree(root, maxDepth) {
|
|
493
|
+
const lines = [path.basename(root) + '/'];
|
|
494
|
+
function walk(dir, prefix, depth) {
|
|
495
|
+
if (depth >= maxDepth)
|
|
496
|
+
return;
|
|
497
|
+
let entries;
|
|
498
|
+
try {
|
|
499
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
entries = entries
|
|
505
|
+
.filter((e) => !IGNORE_DIRS.has(e.name) && !e.name.startsWith('.'))
|
|
506
|
+
.sort((a, b) => {
|
|
507
|
+
if (a.isDirectory() !== b.isDirectory())
|
|
508
|
+
return a.isDirectory() ? -1 : 1;
|
|
509
|
+
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
510
|
+
});
|
|
511
|
+
for (let i = 0; i < entries.length; i++) {
|
|
512
|
+
const entry = entries[i];
|
|
513
|
+
const isLast = i === entries.length - 1;
|
|
514
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
515
|
+
const suffix = entry.isDirectory() ? '/' : '';
|
|
516
|
+
lines.push(`${prefix}${connector}${entry.name}${suffix}`);
|
|
517
|
+
if (entry.isDirectory()) {
|
|
518
|
+
const extension = isLast ? ' ' : '│ ';
|
|
519
|
+
walk(path.join(dir, entry.name), prefix + extension, depth + 1);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
walk(root, '', 0);
|
|
524
|
+
return lines.join('\n');
|
|
525
|
+
}
|
|
155
526
|
function fail(req, error) {
|
|
156
527
|
return {
|
|
157
528
|
request_id: req.request_id,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom markdown-to-ANSI renderer.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `marked-terminal` with a hand-rolled `formatToken()` pipeline
|
|
5
|
+
* (modelled after Claude Code's `utils/markdown.ts`). Gives us full control
|
|
6
|
+
* over styling, performance (token caching + plain-text fast-path), and
|
|
7
|
+
* terminal-width responsiveness.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyMarkdown(content: string): string;
|
|
10
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom markdown-to-ANSI renderer.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `marked-terminal` with a hand-rolled `formatToken()` pipeline
|
|
5
|
+
* (modelled after Claude Code's `utils/markdown.ts`). Gives us full control
|
|
6
|
+
* over styling, performance (token caching + plain-text fast-path), and
|
|
7
|
+
* terminal-width responsiveness.
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { marked } from 'marked';
|
|
11
|
+
import { highlight as cliHighlight, supportsLanguage } from 'cli-highlight';
|
|
12
|
+
import stripAnsi from 'strip-ansi';
|
|
13
|
+
import stringWidth from 'string-width';
|
|
14
|
+
import { palette } from './colors.js';
|
|
15
|
+
const EOL = '\n';
|
|
16
|
+
// ── marked configuration (run once) ────────────────────────────
|
|
17
|
+
let configured = false;
|
|
18
|
+
function configureMarked() {
|
|
19
|
+
if (configured)
|
|
20
|
+
return;
|
|
21
|
+
configured = true;
|
|
22
|
+
// Disable strikethrough — models use ~ for "approximately"
|
|
23
|
+
marked.use({ tokenizer: { del() { return undefined; } } });
|
|
24
|
+
}
|
|
25
|
+
// ── LRU token cache ────────────────────────────────────────────
|
|
26
|
+
const TOKEN_CACHE_MAX = 256;
|
|
27
|
+
const tokenCache = new Map();
|
|
28
|
+
// Quick regex test: does the string contain any markdown syntax?
|
|
29
|
+
const MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
|
|
30
|
+
function cachedLexer(content) {
|
|
31
|
+
// Fast path: plain text → synthetic paragraph (skip full GFM parse)
|
|
32
|
+
const sample = content.length > 500 ? content.slice(0, 500) : content;
|
|
33
|
+
if (!MD_SYNTAX_RE.test(sample)) {
|
|
34
|
+
return [{
|
|
35
|
+
type: 'paragraph', raw: content, text: content,
|
|
36
|
+
tokens: [{ type: 'text', raw: content, text: content }],
|
|
37
|
+
}];
|
|
38
|
+
}
|
|
39
|
+
const key = content;
|
|
40
|
+
const hit = tokenCache.get(key);
|
|
41
|
+
if (hit) {
|
|
42
|
+
// promote to MRU
|
|
43
|
+
tokenCache.delete(key);
|
|
44
|
+
tokenCache.set(key, hit);
|
|
45
|
+
return hit;
|
|
46
|
+
}
|
|
47
|
+
const tokens = marked.lexer(content);
|
|
48
|
+
if (tokenCache.size >= TOKEN_CACHE_MAX) {
|
|
49
|
+
const first = tokenCache.keys().next().value;
|
|
50
|
+
if (first !== undefined)
|
|
51
|
+
tokenCache.delete(first);
|
|
52
|
+
}
|
|
53
|
+
tokenCache.set(key, tokens);
|
|
54
|
+
return tokens;
|
|
55
|
+
}
|
|
56
|
+
// ── Public API ─────────────────────────────────────────────────
|
|
57
|
+
export function applyMarkdown(content) {
|
|
58
|
+
configureMarked();
|
|
59
|
+
// Strip internal heading markers
|
|
60
|
+
const cleaned = content.replace(/\{mist_session_heading:[^}]*\}\s*/g, '');
|
|
61
|
+
return cachedLexer(cleaned)
|
|
62
|
+
.map(t => formatToken(t))
|
|
63
|
+
.join('')
|
|
64
|
+
.trim();
|
|
65
|
+
}
|
|
66
|
+
// ── Token formatter ────────────────────────────────────────────
|
|
67
|
+
function formatToken(token, listDepth = 0, orderedListNumber = null, parent = null) {
|
|
68
|
+
switch (token.type) {
|
|
69
|
+
// ── Headings ──────────────────────────────────────────────
|
|
70
|
+
case 'heading': {
|
|
71
|
+
const inner = children(token, 0, null, null);
|
|
72
|
+
switch (token.depth) {
|
|
73
|
+
case 1:
|
|
74
|
+
return chalk.bold.italic.underline(inner) + EOL + EOL;
|
|
75
|
+
default:
|
|
76
|
+
return chalk.bold(inner) + EOL + EOL;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// ── Emphasis ──────────────────────────────────────────────
|
|
80
|
+
case 'strong':
|
|
81
|
+
return chalk.bold(children(token, 0, null, parent));
|
|
82
|
+
case 'em':
|
|
83
|
+
return chalk.italic(children(token, 0, null, parent));
|
|
84
|
+
// ── Code ─────────────────────────────────────────────────
|
|
85
|
+
case 'code': {
|
|
86
|
+
const codeToken = token;
|
|
87
|
+
let highlighted;
|
|
88
|
+
if (codeToken.lang && supportsLanguage(codeToken.lang)) {
|
|
89
|
+
try {
|
|
90
|
+
highlighted = cliHighlight(codeToken.text, { language: codeToken.lang });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
highlighted = codeToken.text;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
highlighted = codeToken.text;
|
|
98
|
+
}
|
|
99
|
+
return highlighted + EOL;
|
|
100
|
+
}
|
|
101
|
+
case 'codespan':
|
|
102
|
+
return chalk.hex(palette.info)(token.text);
|
|
103
|
+
// ── Blockquote ───────────────────────────────────────────
|
|
104
|
+
case 'blockquote': {
|
|
105
|
+
const inner = children(token, 0, null, null);
|
|
106
|
+
const bar = chalk.dim('│');
|
|
107
|
+
return inner
|
|
108
|
+
.split(EOL)
|
|
109
|
+
.map(line => stripAnsi(line).trim() ? `${bar} ${chalk.italic(line)}` : line)
|
|
110
|
+
.join(EOL);
|
|
111
|
+
}
|
|
112
|
+
// ── Lists ────────────────────────────────────────────────
|
|
113
|
+
case 'list':
|
|
114
|
+
return token.items
|
|
115
|
+
.map((item, i) => formatToken(item, listDepth, token.ordered ? (token.start ?? 1) + i : null, token))
|
|
116
|
+
.join('');
|
|
117
|
+
case 'list_item':
|
|
118
|
+
return (token.tokens ?? [])
|
|
119
|
+
.map(t => {
|
|
120
|
+
const indent = ' '.repeat(listDepth);
|
|
121
|
+
return `${indent}${formatToken(t, listDepth + 1, orderedListNumber, token)}`;
|
|
122
|
+
})
|
|
123
|
+
.join('');
|
|
124
|
+
// ── Paragraph / text ─────────────────────────────────────
|
|
125
|
+
case 'paragraph':
|
|
126
|
+
return children(token, 0, null, null) + EOL;
|
|
127
|
+
case 'text': {
|
|
128
|
+
if (parent?.type === 'list_item') {
|
|
129
|
+
const bullet = orderedListNumber === null
|
|
130
|
+
? '-'
|
|
131
|
+
: `${orderedListNumber}.`;
|
|
132
|
+
const inner = token.tokens
|
|
133
|
+
? token.tokens.map(t => formatToken(t, listDepth, orderedListNumber, token)).join('')
|
|
134
|
+
: token.text;
|
|
135
|
+
return `${bullet} ${inner}${EOL}`;
|
|
136
|
+
}
|
|
137
|
+
// If the text token has sub-tokens (e.g. bold inside text), render them
|
|
138
|
+
if (token.tokens) {
|
|
139
|
+
return token.tokens.map(t => formatToken(t, listDepth, orderedListNumber, token)).join('');
|
|
140
|
+
}
|
|
141
|
+
return token.text;
|
|
142
|
+
}
|
|
143
|
+
// ── Links ────────────────────────────────────────────────
|
|
144
|
+
case 'link': {
|
|
145
|
+
const linkToken = token;
|
|
146
|
+
if (linkToken.href.startsWith('mailto:')) {
|
|
147
|
+
return linkToken.href.replace(/^mailto:/, '');
|
|
148
|
+
}
|
|
149
|
+
const linkText = children(token, 0, null, token);
|
|
150
|
+
const plain = stripAnsi(linkText);
|
|
151
|
+
// If display text differs from URL, show both
|
|
152
|
+
if (plain && plain !== linkToken.href) {
|
|
153
|
+
return `${chalk.blue(plain)} ${chalk.dim(`(${linkToken.href})`)}`;
|
|
154
|
+
}
|
|
155
|
+
return chalk.blue.underline(linkToken.href);
|
|
156
|
+
}
|
|
157
|
+
case 'image':
|
|
158
|
+
return token.href;
|
|
159
|
+
// ── Table ────────────────────────────────────────────────
|
|
160
|
+
case 'table': {
|
|
161
|
+
const tableToken = token;
|
|
162
|
+
const getDisplay = (tokens) => stripAnsi(tokens?.map(t => formatToken(t)).join('') ?? '');
|
|
163
|
+
// Column widths
|
|
164
|
+
const colWidths = tableToken.header.map((h, i) => {
|
|
165
|
+
let max = stringWidth(getDisplay(h.tokens));
|
|
166
|
+
for (const row of tableToken.rows) {
|
|
167
|
+
max = Math.max(max, stringWidth(getDisplay(row[i]?.tokens)));
|
|
168
|
+
}
|
|
169
|
+
return Math.max(max, 3);
|
|
170
|
+
});
|
|
171
|
+
// Header
|
|
172
|
+
let out = '| ';
|
|
173
|
+
tableToken.header.forEach((h, i) => {
|
|
174
|
+
const content = h.tokens?.map(t => formatToken(t)).join('') ?? '';
|
|
175
|
+
const display = getDisplay(h.tokens);
|
|
176
|
+
out += padCell(content, stringWidth(display), colWidths[i]) + ' | ';
|
|
177
|
+
});
|
|
178
|
+
out = out.trimEnd() + EOL;
|
|
179
|
+
// Separator
|
|
180
|
+
out += '|';
|
|
181
|
+
colWidths.forEach(w => { out += '-'.repeat(w + 2) + '|'; });
|
|
182
|
+
out += EOL;
|
|
183
|
+
// Rows
|
|
184
|
+
tableToken.rows.forEach(row => {
|
|
185
|
+
out += '| ';
|
|
186
|
+
row.forEach((cell, i) => {
|
|
187
|
+
const content = cell.tokens?.map(t => formatToken(t)).join('') ?? '';
|
|
188
|
+
const display = getDisplay(cell.tokens);
|
|
189
|
+
out += padCell(content, stringWidth(display), colWidths[i]) + ' | ';
|
|
190
|
+
});
|
|
191
|
+
out = out.trimEnd() + EOL;
|
|
192
|
+
});
|
|
193
|
+
return out + EOL;
|
|
194
|
+
}
|
|
195
|
+
// ── Whitespace / misc ────────────────────────────────────
|
|
196
|
+
case 'space':
|
|
197
|
+
return EOL;
|
|
198
|
+
case 'br':
|
|
199
|
+
return EOL;
|
|
200
|
+
case 'hr':
|
|
201
|
+
return '---' + EOL;
|
|
202
|
+
case 'escape':
|
|
203
|
+
return token.text;
|
|
204
|
+
case 'html':
|
|
205
|
+
case 'def':
|
|
206
|
+
case 'del':
|
|
207
|
+
return '';
|
|
208
|
+
}
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
// ── Helpers ────────────────────────────────────────────────────
|
|
212
|
+
/** Recursively render child tokens. */
|
|
213
|
+
function children(token, listDepth, orderedListNumber, parent) {
|
|
214
|
+
return (token.tokens ?? [])
|
|
215
|
+
.map((t) => formatToken(t, listDepth, orderedListNumber, parent))
|
|
216
|
+
.join('');
|
|
217
|
+
}
|
|
218
|
+
/** Pad cell content to target width, preserving ANSI codes. */
|
|
219
|
+
function padCell(content, displayWidth, targetWidth) {
|
|
220
|
+
const pad = targetWidth - displayWidth;
|
|
221
|
+
return pad > 0 ? content + ' '.repeat(pad) : content;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -39,9 +39,15 @@ export async function checkForUpdate() {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
export function formatUpdateMessage(info) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const line1 = ` Update available: ${info.current} → ${info.latest} `;
|
|
43
|
+
const line2 = ` Run npm install -g ${PACKAGE_NAME} to update `;
|
|
44
|
+
const width = Math.max(line1.length, line2.length);
|
|
45
|
+
const top = '─'.repeat(width);
|
|
46
|
+
const pad1 = ' '.repeat(Math.max(0, width - line1.length));
|
|
47
|
+
const pad2 = ' '.repeat(Math.max(0, width - line2.length));
|
|
48
|
+
return (`\x1b[33m╭${top}╮\x1b[0m\n` +
|
|
49
|
+
`\x1b[33m│\x1b[0m Update available: \x1b[90m${info.current}\x1b[0m → \x1b[32m${info.latest}\x1b[0m ${pad1}\x1b[33m│\x1b[0m\n` +
|
|
50
|
+
`\x1b[33m│\x1b[0m Run \x1b[36mnpm install -g ${PACKAGE_NAME}\x1b[0m to update ${pad2}\x1b[33m│\x1b[0m\n` +
|
|
51
|
+
`\x1b[33m╰${top}╯\x1b[0m`);
|
|
46
52
|
}
|
|
47
53
|
//# sourceMappingURL=updateChecker.js.map
|