@raolin2025/claude-code-node 2.3.2 → 2.3.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/core/cli.js +61 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Node.js AI Code Agent CLI - Zero dependencies, pure JavaScript, security hardened, multi-channel notifications",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/core/cli.js CHANGED
@@ -136,8 +136,8 @@ function buildBanner({ model, permissionMode, session, maxTokens }) {
136
136
  const top = `╭${leftLabel}${'─'.repeat(inner - leftLabel.length)}╮`
137
137
  const bottom = `╰${'─'.repeat(inner)}╯`
138
138
 
139
- const col1 = 28 // 左侧机器人列宽度
140
- const col2 = 44 // 标题列
139
+ const col1 = 24 // 左侧机器人列(19字符+边距)
140
+ const col2 = 50 // 标题列
141
141
  const col3 = inner - col1 - col2 - 2 // 信息列
142
142
 
143
143
  // ANSI 颜色
@@ -145,47 +145,68 @@ function buildBanner({ model, permissionMode, session, maxTokens }) {
145
145
  const CYAN = '\x1b[36m'
146
146
  const RESET = '\x1b[0m'
147
147
 
148
- // 像素风格 CC 机器人(19x13 字符,带颜色)
148
+ // 像素风格 CC 机器人(19字符宽,带颜色边框)
149
149
  const robotRaw = [
150
- ' ┌─────────────────┐ ',
151
- ' │ ██ ██ │ ',
152
- ' │ ██████ ',
153
- ' │ ██ ██ │ ',
154
- ' ',
155
- ' ███████ ',
156
- ' │ ██ ██ │ ',
157
- ' │ ██████ ',
158
- ' │ ██ ██ ██ │ ',
159
- ' └─────────────────┘ ',
150
+ ' ┌───────────────┐ ',
151
+ ' │ ██ ██ │ ',
152
+ ' │ ██████ ',
153
+ ' │ ██ ██ │ ',
154
+ ' ',
155
+ ' ███████ ',
156
+ ' │ ██ ██ │ ',
157
+ ' │ ██████ ',
158
+ ' │ ██ ██ ██ │ ',
159
+ ' └───────────────┘ ',
160
160
  ]
161
161
 
162
162
  // 颜色化:边框蓝,眼睛/嘴巴青
163
163
  const colorize = (line) =>
164
164
  line
165
- .replace(/[┌└─╭╮]/g, BLUE + '$&' + RESET)
165
+ .replace(/[┌└─╭╮┐┘│]/g, BLUE + '$&' + RESET)
166
166
  .replace(/[█]/g, CYAN + '$&' + RESET)
167
167
 
168
168
  const robotColored = robotRaw.map(colorize)
169
169
 
170
- // col1 内居中(按可见长度约 19 计算)
171
- const visibleWidth = 19
172
- const totalCol1 = 28
170
+ // 机器人列:先去掉 ANSI(保证 pad 长度准确),再填充
171
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '')
172
+ const robotPlain = robotColored.map(line => stripAnsi(line))
173
+
174
+ // 在 col1 内居中(按最大可见长度 21 计算,短的自动靠右)
175
+ const visibleWidth = 21
176
+ const totalCol1 = 24
173
177
  const leftPad = Math.floor((totalCol1 - visibleWidth) / 2)
174
178
  const rightPad = totalCol1 - visibleWidth - leftPad
175
- const robotLines = robotColored.map(line => ' '.repeat(leftPad) + line + ' '.repeat(rightPad))
179
+ const robotLines = robotPlain.map(line => ' '.repeat(leftPad) + line + ' '.repeat(rightPad))
180
+
181
+ // 辅助函数:字符串填充(忽略 ANSI 颜色码计算真实长度,正确处理嵌入在字符串中的 ANSI)
182
+ const realLen = (s) => stripAnsi(s).length
176
183
 
177
- // 辅助函数:字符串填充
178
184
  const pad = (s, w, align = 'left') => {
179
- if (s.length > w) return s.substring(0, w)
180
- const spaces = w - s.length
185
+ const rlen = realLen(s)
186
+ if (rlen >= w) {
187
+ // 需要截断,但保留 ANSI 序列
188
+ const result = stripAnsi(s).substring(0, w)
189
+ // 把截断后的字符对应的 ANSI 序列加回去(简化处理:只保留前导 ANSI)
190
+ const leadingAnsi = s.match(/^(\x1b\[[0-9;]*m)*/)?.[0] || ''
191
+ return leadingAnsi + result
192
+ }
193
+ const spaces = w - rlen
194
+ const afterAnsi = stripAnsi(s) // 纯文本
195
+ let result
181
196
  if (align === 'center') {
182
197
  const l = Math.floor(spaces / 2)
183
- return ' '.repeat(l) + s + ' '.repeat(spaces - l)
198
+ result = ' '.repeat(l) + afterAnsi + ' '.repeat(spaces - l)
199
+ } else if (align === 'right') {
200
+ result = ' '.repeat(spaces) + afterAnsi
201
+ } else {
202
+ result = afterAnsi + ' '.repeat(spaces)
184
203
  }
185
- return s + ' '.repeat(spaces)
204
+ // 加回前导 ANSI
205
+ const leadingAnsi = s.match(/^(\x1b\[[0-9;]*m)*/)?.[0] || ''
206
+ return leadingAnsi + result
186
207
  }
187
208
 
188
- // 标题列(居中,行数匹配 robotLines
209
+ // 标题列(居中,行数匹配 robotLines,内容放在中间行)
189
210
  const baseTitleLines = [
190
211
  pad('AI Code Agent', col2, 'center'),
191
212
  pad('Node.js Edition', col2, 'center'),
@@ -193,12 +214,19 @@ function buildBanner({ model, permissionMode, session, maxTokens }) {
193
214
  pad('─'.repeat(col2 - 2), col2, 'center'),
194
215
  pad('/help — commands · /exit — quit', col2, 'center')
195
216
  ]
217
+ // 内容从第2行开始(让分隔线与机器人中间横线对齐)
218
+ const titleStart = 1
196
219
  const titleLines = []
197
220
  for (let i = 0; i < robotLines.length; i++) {
198
- titleLines.push(i < baseTitleLines.length ? baseTitleLines[i] : pad('', col2, 'center'))
221
+ const ti = i - titleStart
222
+ if (ti >= 0 && ti < baseTitleLines.length) {
223
+ titleLines.push(baseTitleLines[ti])
224
+ } else {
225
+ titleLines.push(pad('', col2, 'center'))
226
+ }
199
227
  }
200
228
 
201
- // 信息列(右对齐,填充到 robotLines 长度)
229
+ // 信息列(右对齐,内容放在中间行)
202
230
  const sessionId = session?.id || '??????'
203
231
  const baseInfoLines = [
204
232
  pad(`Turns: ${session?.state?.turnCount ?? 0} • Tools: 0`, col3, 'right'),
@@ -207,9 +235,15 @@ function buildBanner({ model, permissionMode, session, maxTokens }) {
207
235
  pad(`Budget: 0 / ${maxTokens ?? 200000}`, col3, 'right'),
208
236
  pad(`Session: ${sessionId?.toString().slice(-6)}`, col3, 'right')
209
237
  ]
238
+ const infoStart = 1
210
239
  const infoLines = []
211
240
  for (let i = 0; i < robotLines.length; i++) {
212
- infoLines.push(i < baseInfoLines.length ? baseInfoLines[i] : pad('', col3, 'right'))
241
+ const ti = i - infoStart
242
+ if (ti >= 0 && ti < baseInfoLines.length) {
243
+ infoLines.push(baseInfoLines[ti])
244
+ } else {
245
+ infoLines.push(pad('', col3, 'right'))
246
+ }
213
247
  }
214
248
 
215
249
  // 构建每行:│ col1 │ col2 │ col3 │