@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.
- package/package.json +1 -1
- package/src/core/cli.js +76 -29
package/package.json
CHANGED
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 =
|
|
140
|
-
const col2 =
|
|
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
|
-
//
|
|
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(/[
|
|
165
|
+
.replace(/[┌└─╭╮┐┘│]/g, BLUE + '$&' + RESET)
|
|
162
166
|
.replace(/[█]/g, CYAN + '$&' + RESET)
|
|
163
167
|
|
|
164
168
|
const robotColored = robotRaw.map(colorize)
|
|
165
169
|
|
|
166
|
-
//
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
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(
|
|
177
|
-
|
|
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
|
-
|
|
204
|
+
// 加回前导 ANSI
|
|
205
|
+
const leadingAnsi = s.match(/^(\x1b\[[0-9;]*m)*/)?.[0] || ''
|
|
206
|
+
return leadingAnsi + result
|
|
180
207
|
}
|
|
181
208
|
|
|
182
|
-
|
|
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
|
|
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]
|