foliko 1.1.57 → 1.1.60

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.
@@ -217,7 +217,11 @@
217
217
  "Bash(cd cli/src && node -e \"console.log\\(require.resolve\\('../../src'\\)\\)\")",
218
218
  "Bash(node -e \"console.log\\(require.resolve\\('./src'\\)\\)\")",
219
219
  "Bash(cd /d/Code/foliko/cli/src && node -e \"console.log\\(require.resolve\\('../../src'\\)\\)\")",
220
- "Bash(node -c cli/src/daemon.js 2>&1)"
220
+ "Bash(node -c cli/src/daemon.js 2>&1)",
221
+ "WebFetch(domain:github.com)",
222
+ "Bash(node -e \"const { codeToANSI } = require\\('@shikijs/cli'\\); console.log\\(typeof codeToANSI\\)\")",
223
+ "Bash(node -e ' *)",
224
+ "Bash(node -e \"const hl = require\\('cli-highlight'\\); console.log\\(hl.highlight\\('const a = 1', { language: 'javascript', theme: 'nord' }\\)\\)\")"
221
225
  ]
222
226
  }
223
227
  }
package/cli/src/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  * Foliko CLI 主逻辑
3
3
  */
4
4
 
5
+ // 启用 cli-highlight 颜色输出
6
+ process.env.FORCE_COLOR = '1';
7
+
5
8
  const { chatCommand } = require('./commands/chat');
6
9
  const { listCommand } = require('./commands/list');
7
10
  const { pluginCommand } = require('./commands/plugin');
@@ -10,6 +10,7 @@ const { cleanResponse } = require('../../../src/utils');
10
10
  const { renderLine } = require('../utils/markdown');
11
11
  const { MessageBubble } = require('./message-bubble');
12
12
  const Queue=require('js-queue');
13
+ const hl = require('cli-highlight');
13
14
  const queue=new Queue();
14
15
  // Foliko 主色(蓝绿)
15
16
  const folikoPrimary = chalk.hex('#2A9D8F');
@@ -53,8 +54,11 @@ const markdownTheme = {
53
54
  italic: (text) => chalk.italic(text),
54
55
  strikethrough: (text) => chalk.strikethrough(text),
55
56
  underline: (text) => chalk.underline(text),
56
- highlightCode: (code, lang) => [chalk.green(code)],
57
- codeBlockIndent:2
57
+ highlightCode: (code, lang) => {
58
+ const highlighted = hl.highlight(code, { language: lang || 'text', theme: 'nord' });
59
+ return highlighted.split('\n');
60
+ },
61
+ codeBlockIndent: "",
58
62
  };
59
63
 
60
64
  class ChatUI {
@@ -114,9 +118,9 @@ class ChatUI {
114
118
  }
115
119
  )
116
120
  // Spacer 占位,用于隐藏时保持布局
117
- const toolerSpacer = new Spacer(1);
118
- const loaderSpacer = new Spacer(1);
119
- const notifierSpacer = new Spacer(1);
121
+ const toolerSpacer = new Spacer(0);
122
+ const loaderSpacer = new Spacer(0);
123
+ const notifierSpacer = new Spacer(0);
120
124
 
121
125
  this.statusContainer.addChild(toolerSpacer); // tooler 位置
122
126
  this.statusContainer.addChild(loaderSpacer); // loader 位置
@@ -449,7 +453,7 @@ class ChatUI {
449
453
  }
450
454
  setTimeout(() => {
451
455
  this.tooler.hide();
452
- this.create_message(`${colored('[提示]', CYAN)} 上下文已压缩\n`,colored('● ', RED))
456
+ this.create_message(`${colored('[提示]', CYAN)} 上下文已压缩`,chalk.dim('● '))
453
457
  this.tui.requestRender();
454
458
  }, 2000);
455
459
  }).catch(err => {
@@ -67,7 +67,7 @@ class MessageBubble {
67
67
  return ' '.repeat(this.paddingX) + this.icon + truncated + ' '.repeat(this.paddingX);
68
68
  }
69
69
  // 后续行缩进到图标后面(3个字符),并保持左右 padding
70
- return ' '.repeat(this.paddingX) + ' ' + truncated + ' '.repeat(this.paddingX);
70
+ return ' '.repeat(this.paddingX) + ' ' + truncated + ' '.repeat(this.paddingX);
71
71
  });
72
72
 
73
73
  // 添加上下 padding
@@ -94,29 +94,44 @@ function renderInline(text) {
94
94
  return result;
95
95
  }
96
96
 
97
- function renderThink(line, state = { inThink: false },thinking) {
97
+ function renderThink(line, state = { inThink: false }, thinking) {
98
+ // 处理只有开始标签的情况(line = "<think>" 这样单独一行)
99
+ // 这种情况在流式输出时很常见
100
+ if (line === '<think>' || line === '<think>\n') {
101
+ state.inThink = true;
102
+ return STYLES.think.prefix + line;
103
+ }
104
+
98
105
  let result = line;
99
106
 
100
107
  // 匹配 <think>...</think> 以及可能的跨行情况
101
108
  result = result.replace(/<think>([\s\S]*?)<\/think>/g, (match, content) => {
102
- const prefix=result.indexOf('<think>')>0?'\n':""
103
- return prefix+STYLES.think.prefix + match + STYLES.think.suffix;
109
+ // 去掉 content 中的空行(保留最多一个换行)
110
+ const compressed = content.replace(/\n+/g, '\n').replace(/^\n|\n$/g, '');
111
+ const prefix = result.indexOf('<think>') > 0 ? '\n' : "";
112
+ return prefix + STYLES.think.prefix + '<think>' + compressed + '</think>' + STYLES.think.suffix;
104
113
  });
105
114
 
106
115
  // 处理未闭合的 think 标签 - 更新状态
107
- if (result.includes('<think>')) {
116
+ if (result.includes('<think>') && !result.includes('</think>')) {
108
117
  state.inThink = true;
109
118
  }
110
119
  if (result.includes('</think>')) {
111
120
  state.inThink = false;
121
+ // 如果只有 </think> 标签(没有开始标签)
122
+ if (!result.includes('<think>')) {
123
+ return STYLES.think.prefix + line + STYLES.think.suffix;
124
+ }
112
125
  }
113
126
 
114
127
  // 处理未闭合的 think 标签(开始但没结束)
115
128
  if (state.inThink && !result.includes('<think>')) {
116
- result = STYLES.think.prefix + result;
129
+ // 去掉空行
130
+ const compressed = result.replace(/\n+/g, '\n').replace(/^\n|\n$/g, '');
131
+ result = STYLES.think.prefix + compressed + STYLES.think.suffix;
117
132
  }
118
133
 
119
- return thinking?result:renderInline(result);
134
+ return thinking ? result : renderInline(result);
120
135
  }
121
136
 
122
137
  /**
@@ -157,6 +172,11 @@ function renderLine(line, state = { inThink: false, inCodeBlock: false },thinkin
157
172
  return result;
158
173
  }
159
174
 
175
+ // 如果是 continuation 行(在思考块中但没有思考标签)
176
+ if (state.inThink && !line.includes('<think>') && !line.includes('</think>')) {
177
+ return STYLES.think.prefix + leadingNL + renderThink(line, state, thinking) + trailingNL + STYLES.think.suffix;
178
+ }
179
+
160
180
  // 标题
161
181
  if (line.startsWith('### ')) {
162
182
  return `${STYLES.h3.prefix}${renderInline(line.substring(4))}${STYLES.h3.suffix}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.57",
3
+ "version": "1.1.60",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -63,6 +63,7 @@
63
63
  "@modelcontextprotocol/sdk": "^1.27.1",
64
64
  "ai": "^6.0.146",
65
65
  "chalk": "^5.6.2",
66
+ "cli-highlight": "^2.1.11",
66
67
  "crypto": "^1.0.1",
67
68
  "dotenv": "^17.3.1",
68
69
  "figlet": "^1.11.0",
@@ -203,7 +203,7 @@ class ThinkPlugin extends Plugin {
203
203
 
204
204
  // 发送通知
205
205
  const level = mode === 'brainstorm' ? 'success' : 'info'
206
- this._emitNotification(level, '🧠 思考完成', thought.result.slice(0, 200))
206
+ this._emitNotification(level, '🧠 思考完成', thought.result)
207
207
  }
208
208
 
209
209
  return {