@raolin2025/claude-code-node 2.2.7 → 2.2.9
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 +126 -17
- package/src/permission/permission.js +13 -4
package/package.json
CHANGED
package/src/core/cli.js
CHANGED
|
@@ -116,14 +116,102 @@ function startSocketServer(engine, session, sessionManager, channelManager, verb
|
|
|
116
116
|
// Banner & Help
|
|
117
117
|
// ============================================================
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
// ============================================================
|
|
120
|
+
// Banner & Help
|
|
121
|
+
// ============================================================
|
|
122
|
+
|
|
123
|
+
// 版本号(从 package.json 读取或手动更新)
|
|
124
|
+
let CC_NODE_VERSION = '2.2.7'
|
|
125
|
+
try {
|
|
126
|
+
const pkgPath = new URL('../../package.json', import.meta.url)
|
|
127
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
128
|
+
CC_NODE_VERSION = pkg.version || CC_NODE_VERSION
|
|
129
|
+
} catch {}
|
|
130
|
+
|
|
131
|
+
// 生成 Claude Code 风格的三栏 Banner
|
|
132
|
+
function buildBanner({ model, permissionMode, session, maxTokens }) {
|
|
133
|
+
const width = 112
|
|
134
|
+
const inner = width - 2
|
|
135
|
+
const leftLabel = ` CC-Node v${CC_NODE_VERSION} `
|
|
136
|
+
const top = `╭${leftLabel}${'─'.repeat(inner - leftLabel.length)}╮`
|
|
137
|
+
const bottom = `╰${'─'.repeat(inner)}╯`
|
|
138
|
+
|
|
139
|
+
const col1 = 30 // 机器人列
|
|
140
|
+
const col2 = 42 // 标题列
|
|
141
|
+
const col3 = inner - col1 - col2 - 2 // 信息列
|
|
142
|
+
|
|
143
|
+
// ANSI 颜色
|
|
144
|
+
const BLUE = '\x1b[34m'
|
|
145
|
+
const CYAN = '\x1b[36m'
|
|
146
|
+
const RESET = '\x1b[0m'
|
|
147
|
+
|
|
148
|
+
// 原始机器人(宽21)
|
|
149
|
+
const robotRaw = [
|
|
150
|
+
' ╭───────╮ ',
|
|
151
|
+
'┌───────────────────┐',
|
|
152
|
+
'│ ██ ██ │',
|
|
153
|
+
'│ │',
|
|
154
|
+
'│ ██████ │',
|
|
155
|
+
'└───────────────────┘'
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
// 颜色化:边框蓝,眼睛/嘴巴青
|
|
159
|
+
const colorize = (line) =>
|
|
160
|
+
line
|
|
161
|
+
.replace(/[┌└─╭╮]/g, BLUE + '$&' + RESET)
|
|
162
|
+
.replace(/[█]/g, CYAN + '$&' + RESET)
|
|
163
|
+
|
|
164
|
+
const robotColored = robotRaw.map(colorize)
|
|
165
|
+
|
|
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
|
|
175
|
+
if (align === 'center') {
|
|
176
|
+
const l = Math.floor(sp / 2)
|
|
177
|
+
return ' '.repeat(l) + s + ' '.repeat(sp - l)
|
|
178
|
+
}
|
|
179
|
+
return s + ' '.repeat(sp)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const titleLines = [
|
|
183
|
+
pad('AI Code Agent', col2, 'center'),
|
|
184
|
+
pad('Node.js Edition', col2, 'center'),
|
|
185
|
+
pad('', col2, 'center'),
|
|
186
|
+
pad('─'.repeat(col2 - 2), col2, 'center'),
|
|
187
|
+
pad('/help — commands · /exit — quit', col2, 'center'),
|
|
188
|
+
pad('', col2, 'center')
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
// 信息列(右对齐)
|
|
192
|
+
const sessionId = session?.id || '??????'
|
|
193
|
+
const infoLines = [
|
|
194
|
+
pad(`Turns: ${session?.state?.turnCount ?? 0} • Tools: 0`, col3, 'right'),
|
|
195
|
+
pad(`Model: ${model}`, col3, 'right'),
|
|
196
|
+
pad(`Permission: ${permissionMode}`, col3, 'right'),
|
|
197
|
+
pad(`Budget: 0 / ${maxTokens ?? 200000}`, col3, 'right'),
|
|
198
|
+
pad(`Session: ${sessionId?.toString().slice(-6)}`, col3, 'right'),
|
|
199
|
+
pad('', col3, 'right')
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
// 构建每行:│ col1 │ col2 │ col3 │
|
|
203
|
+
const lines = [top]
|
|
204
|
+
const empty = `│${' '.repeat(inner)}│`
|
|
205
|
+
lines.push(empty)
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < robotLines.length; i++) {
|
|
208
|
+
lines.push(`│${robotLines[i]}│${titleLines[i]}│${infoLines[i]}│`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
lines.push(empty)
|
|
212
|
+
lines.push(bottom)
|
|
213
|
+
return lines.join('\n')
|
|
214
|
+
}
|
|
127
215
|
|
|
128
216
|
const HELP_TEXT = `
|
|
129
217
|
Commands:
|
|
@@ -140,7 +228,10 @@ Commands:
|
|
|
140
228
|
/cost — Show API cost report
|
|
141
229
|
/compact — Manually compact conversation context
|
|
142
230
|
/cd PATH — Change working directory
|
|
143
|
-
/allow [tool]
|
|
231
|
+
/allow [tool|all|reset] — Allow tools for this session (default: all)
|
|
232
|
+
/allow all — automatically allow all subsequent tools
|
|
233
|
+
/allow reset — reset to ask mode
|
|
234
|
+
/allow <tool> — allow specific tool (e.g. Bash)
|
|
144
235
|
/exit — Exit (also Ctrl+C)
|
|
145
236
|
/quit — Same as /exit
|
|
146
237
|
|
|
@@ -174,7 +265,7 @@ const DETAILED_HELP = {
|
|
|
174
265
|
|
|
175
266
|
cd: "/cd <path>\n Change the working directory of cc-node.\n Affects all subsequent tool executions (Bash, Read, Write, etc.).\n\n Without path: show the current working directory.\n\n Example: /cd /home/raolin/projects\n Example: /cd ..",
|
|
176
267
|
|
|
177
|
-
allow: "/allow [
|
|
268
|
+
allow: "/allow [tool|all|reset]\n Manage tool permissions for this session.\n\n Options:\n <tool> — allow a specific tool (e.g. Bash, Write, Read)\n all — automatically allow ALL remaining tools for this session\n reset — reset to ask mode (ask for each tool)\n (no arg) — same as /allow all\n\n When asked to confirm a tool, you can also type:\n y — allow this once\n a — allow all for the rest of the session\n\n Example: /allow Bash\n Example: /allow all\n Example: /allow reset",
|
|
178
269
|
|
|
179
270
|
exit: "/exit\n Exit cc-node. Same as Ctrl+C or /quit.",
|
|
180
271
|
quit: "/quit\n Exit cc-node. Same as Ctrl+C or /exit.",
|
|
@@ -354,17 +445,26 @@ export async function main() {
|
|
|
354
445
|
// 将 readline 注入引擎配置,用于 ask 模式确认和 AskUserQuestion 工具
|
|
355
446
|
if (permissionMode === 'ask') {
|
|
356
447
|
engine.config.onConfirmTool = async (toolName, input) => {
|
|
448
|
+
// 如果已经启用会话全局自动允许,直接通过
|
|
449
|
+
if (engine.permissionChecker.sessionAllowAll) return true
|
|
450
|
+
|
|
451
|
+
const snippet = JSON.stringify(input).slice(0, 120) || '(no params)'
|
|
357
452
|
return new Promise((resolve) => {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
453
|
+
rl.question(`\n⚠️ Allow tool "${toolName}"?\n Input: ${snippet}\n (y/N/a) a=all session `, (answer) => {
|
|
454
|
+
const a = answer.toLowerCase()
|
|
455
|
+
if (a === 'a') {
|
|
456
|
+
engine.permissionChecker.allowAllForSession()
|
|
457
|
+
resolve(true)
|
|
458
|
+
} else {
|
|
459
|
+
resolve(a === 'y')
|
|
460
|
+
}
|
|
361
461
|
})
|
|
362
462
|
})
|
|
363
463
|
}
|
|
364
464
|
}
|
|
365
465
|
engine.config.readline = rl
|
|
366
466
|
|
|
367
|
-
console.log(
|
|
467
|
+
console.log(buildBanner({ model, permissionMode, session, maxTokens: tokenBudget.maxTokens }))
|
|
368
468
|
console.log(`Model: ${model} | Permission: ${permissionMode} | Tools: ${registry.getNames().join(', ')}`)
|
|
369
469
|
console.log(`Socket: ${SOCK_PATH} (cc-notify can connect)`)
|
|
370
470
|
if (channelManager.list().length > 0) {
|
|
@@ -495,9 +595,18 @@ export async function main() {
|
|
|
495
595
|
break
|
|
496
596
|
}
|
|
497
597
|
case 'allow': {
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
598
|
+
const arg = rest.join(' ').toLowerCase()
|
|
599
|
+
if (arg === 'all') {
|
|
600
|
+
engine.permissionChecker.allowAllForSession()
|
|
601
|
+
console.log('✅ All tools allowed for the rest of this session')
|
|
602
|
+
} else if (arg === 'reset') {
|
|
603
|
+
engine.permissionChecker.resetSessionAllow()
|
|
604
|
+
console.log('✅ Session permission reset to normal (ask mode)')
|
|
605
|
+
} else {
|
|
606
|
+
const tool = arg || '*'
|
|
607
|
+
engine.permissionChecker.allow(tool)
|
|
608
|
+
console.log(`✅ Tool "${tool}" allowed for this session`)
|
|
609
|
+
}
|
|
501
610
|
break
|
|
502
611
|
}
|
|
503
612
|
case 'cost':
|
|
@@ -9,21 +9,20 @@ export class PermissionChecker {
|
|
|
9
9
|
this.alwaysAllow = new Set()
|
|
10
10
|
this.alwaysDeny = new Set()
|
|
11
11
|
this.askRules = new Set()
|
|
12
|
+
this.sessionAllowAll = false // 整个会话自动允许
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* 检查工具调用是否被允许
|
|
16
17
|
*/
|
|
17
18
|
async check(toolName, input = {}) {
|
|
18
|
-
if (this.mode === 'always-allow') return true
|
|
19
|
+
if (this.mode === 'always-allow' || this.sessionAllowAll) return true
|
|
19
20
|
if (this.mode === 'deny') return false
|
|
20
21
|
|
|
21
22
|
if (this.alwaysAllow.has(toolName)) return true
|
|
22
23
|
if (this.alwaysDeny.has(toolName)) return false
|
|
23
24
|
|
|
24
|
-
// 'ask'
|
|
25
|
-
// 完整版应该在终端显示确认对话框
|
|
26
|
-
return true
|
|
25
|
+
return true // 'ask' 模式下由 onConfirmTool 决定
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
allow(toolName) {
|
|
@@ -33,5 +32,15 @@ export class PermissionChecker {
|
|
|
33
32
|
deny(toolName) {
|
|
34
33
|
this.alwaysDeny.add(toolName)
|
|
35
34
|
}
|
|
35
|
+
|
|
36
|
+
// 会话剩余时间全部工具自动允许
|
|
37
|
+
allowAllForSession() {
|
|
38
|
+
this.sessionAllowAll = true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 重置会话允许状态
|
|
42
|
+
resetSessionAllow() {
|
|
43
|
+
this.sessionAllowAll = false
|
|
44
|
+
}
|
|
36
45
|
}
|
|
37
46
|
|