@raolin2025/claude-code-node 2.3.1 → 2.3.3

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 +76 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
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 = 30 // 机器人列
140
- const col2 = 42 // 标题列
139
+ const col1 = 24 // 左侧机器人列(19字符+边距)
140
+ const col2 = 50 // 标题列
141
141
  const col3 = inner - col1 - col2 - 2 // 信息列
142
142
 
143
143
  // ANSI 颜色
@@ -145,59 +145,106 @@ function buildBanner({ model, permissionMode, session, maxTokens }) {
145
145
  const CYAN = '\x1b[36m'
146
146
  const RESET = '\x1b[0m'
147
147
 
148
- // 原始机器人(宽21)
148
+ // 像素风格 CC 机器人(19字符宽,带颜色边框)
149
149
  const robotRaw = [
150
- ' ╭───────╮ ',
151
- '┌───────────────────┐',
152
- '│ ██ ██ │',
153
- '│ │',
154
- '│ ██████ │',
155
- '└───────────────────┘'
150
+ ' ┌───────────────┐ ',
151
+ ' │ ██ ██ │ ',
152
+ ' ██████ ',
153
+ ' ██ ██ ',
154
+ ' ',
155
+ ' │ ███████ │ ',
156
+ ' │ ██ ██ │ ',
157
+ ' │ ██████ │ ',
158
+ ' │ ██ ██ ██ │ ',
159
+ ' └───────────────┘ ',
156
160
  ]
157
161
 
158
162
  // 颜色化:边框蓝,眼睛/嘴巴青
159
163
  const colorize = (line) =>
160
164
  line
161
- .replace(/[┌└─╭╮]/g, BLUE + '$&' + RESET)
165
+ .replace(/[┌└─╭╮┐┘│]/g, BLUE + '$&' + RESET)
162
166
  .replace(/[█]/g, CYAN + '$&' + RESET)
163
167
 
164
168
  const robotColored = robotRaw.map(colorize)
165
169
 
166
- // col1 内居中(考虑 ESC 序列不看长度,按可见长度21 计算)
167
- const leftPad = 4 // (30 - 21) / 2 = 4.5 => 4 left, 5 right
168
- const rightPad = 5
169
- const robotLines = robotColored.map(line => ' '.repeat(leftPad) + line + ' '.repeat(rightPad))
170
-
171
- // 标题列(居中)
172
- const pad = (s, w, align = 'center') => {
173
- if (s.length >= w) return s
174
- const sp = w - s.length
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 内居中(按可见长度 19 计算)
175
+ const visibleWidth = 19
176
+ const totalCol1 = 24
177
+ const leftPad = Math.floor((totalCol1 - visibleWidth) / 2)
178
+ const rightPad = totalCol1 - visibleWidth - leftPad
179
+ const robotLines = robotPlain.map(line => ' '.repeat(leftPad) + line + ' '.repeat(rightPad))
180
+
181
+ // 辅助函数:字符串填充(忽略 ANSI 颜色码计算真实长度,正确处理嵌入在字符串中的 ANSI)
182
+ const realLen = (s) => stripAnsi(s).length
183
+
184
+ const pad = (s, w, align = 'left') => {
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
175
196
  if (align === 'center') {
176
- const l = Math.floor(sp / 2)
177
- return ' '.repeat(l) + s + ' '.repeat(sp - l)
197
+ const l = Math.floor(spaces / 2)
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)
178
203
  }
179
- return s + ' '.repeat(sp)
204
+ // 加回前导 ANSI
205
+ const leadingAnsi = s.match(/^(\x1b\[[0-9;]*m)*/)?.[0] || ''
206
+ return leadingAnsi + result
180
207
  }
181
208
 
182
- const titleLines = [
209
+ // 标题列(居中,行数匹配 robotLines,内容放在中间行)
210
+ const baseTitleLines = [
183
211
  pad('AI Code Agent', col2, 'center'),
184
212
  pad('Node.js Edition', col2, 'center'),
185
213
  pad('', col2, 'center'),
186
214
  pad('─'.repeat(col2 - 2), col2, 'center'),
187
- pad('/help — commands · /exit — quit', col2, 'center'),
188
- pad('', col2, 'center')
215
+ pad('/help — commands · /exit — quit', col2, 'center')
189
216
  ]
217
+ // 内容放在中间位置(21行中,从第8行开始放5行内容)
218
+ const titleStart = Math.floor((robotLines.length - baseTitleLines.length) / 2)
219
+ const titleLines = []
220
+ for (let i = 0; i < robotLines.length; i++) {
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
+ }
227
+ }
190
228
 
191
- // 信息列(右对齐)
229
+ // 信息列(右对齐,内容放在中间行)
192
230
  const sessionId = session?.id || '??????'
193
- const infoLines = [
231
+ const baseInfoLines = [
194
232
  pad(`Turns: ${session?.state?.turnCount ?? 0} • Tools: 0`, col3, 'right'),
195
233
  pad(`Model: ${model}`, col3, 'right'),
196
234
  pad(`Permission: ${permissionMode}`, col3, 'right'),
197
235
  pad(`Budget: 0 / ${maxTokens ?? 200000}`, col3, 'right'),
198
- pad(`Session: ${sessionId?.toString().slice(-6)}`, col3, 'right'),
199
- pad('', col3, 'right')
236
+ pad(`Session: ${sessionId?.toString().slice(-6)}`, col3, 'right')
200
237
  ]
238
+ const infoStart = Math.floor((robotLines.length - baseInfoLines.length) / 2)
239
+ const infoLines = []
240
+ for (let i = 0; i < robotLines.length; i++) {
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
+ }
247
+ }
201
248
 
202
249
  // 构建每行:│ col1 │ col2 │ col3 │
203
250
  const lines = [top]