@raolin2025/claude-code-node 2.2.8 → 2.3.0
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 +77 -10
- package/src/permission/permission.js +13 -4
package/package.json
CHANGED
package/src/core/cli.js
CHANGED
|
@@ -228,7 +228,11 @@ Commands:
|
|
|
228
228
|
/cost — Show API cost report
|
|
229
229
|
/compact — Manually compact conversation context
|
|
230
230
|
/cd PATH — Change working directory
|
|
231
|
-
/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)
|
|
235
|
+
/resume <session-id> — Resume a saved conversation
|
|
232
236
|
/exit — Exit (also Ctrl+C)
|
|
233
237
|
/quit — Same as /exit
|
|
234
238
|
|
|
@@ -246,9 +250,11 @@ const DETAILED_HELP = {
|
|
|
246
250
|
|
|
247
251
|
session: "/session\n Show current session information:\n - Session ID\n - Session title\n - Number of messages\n - Number of tool call turns",
|
|
248
252
|
|
|
249
|
-
sessions:"/sessions\n List all saved sessions.\n Shows session ID, title, message count, and last update time.",
|
|
253
|
+
sessions:"/sessions\n List all saved sessions.\n Shows session ID, title, message count, and last update time.\n\n Use /resume <session-id> to restore a saved conversation.",
|
|
250
254
|
|
|
251
|
-
|
|
255
|
+
resume: "/resume <session-id>\n Restore a saved conversation by session ID.\n Session ID can be the full ID or the numeric index from /sessions list.\n\n Example: /resume session-1779605332906-52da0706986efa67\n Example: /resume 1 (resume the first session in the list)",
|
|
256
|
+
|
|
257
|
+
clear: "/clear\n Clear the current conversation context.\n Starts a fresh session. Previous messages are not sent to the API anymore.\n\n Note: Does not delete saved sessions.",
|
|
252
258
|
|
|
253
259
|
config: "/config [key]\n Without key: show the entire config as JSON.\n With a key path: show the value for that specific path.\n\n Example: /config\n Example: /config model",
|
|
254
260
|
|
|
@@ -262,7 +268,7 @@ const DETAILED_HELP = {
|
|
|
262
268
|
|
|
263
269
|
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 ..",
|
|
264
270
|
|
|
265
|
-
allow: "/allow [
|
|
271
|
+
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",
|
|
266
272
|
|
|
267
273
|
exit: "/exit\n Exit cc-node. Same as Ctrl+C or /quit.",
|
|
268
274
|
quit: "/quit\n Exit cc-node. Same as Ctrl+C or /exit.",
|
|
@@ -442,10 +448,19 @@ export async function main() {
|
|
|
442
448
|
// 将 readline 注入引擎配置,用于 ask 模式确认和 AskUserQuestion 工具
|
|
443
449
|
if (permissionMode === 'ask') {
|
|
444
450
|
engine.config.onConfirmTool = async (toolName, input) => {
|
|
451
|
+
// 如果已经启用会话全局自动允许,直接通过
|
|
452
|
+
if (engine.permissionChecker.sessionAllowAll) return true
|
|
453
|
+
|
|
454
|
+
const snippet = JSON.stringify(input).slice(0, 120) || '(no params)'
|
|
445
455
|
return new Promise((resolve) => {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
456
|
+
rl.question(`\n⚠️ Allow tool "${toolName}"?\n Input: ${snippet}\n (y/N/a) a=all session `, (answer) => {
|
|
457
|
+
const a = answer.toLowerCase()
|
|
458
|
+
if (a === 'a') {
|
|
459
|
+
engine.permissionChecker.allowAllForSession()
|
|
460
|
+
resolve(true)
|
|
461
|
+
} else {
|
|
462
|
+
resolve(a === 'y')
|
|
463
|
+
}
|
|
449
464
|
})
|
|
450
465
|
})
|
|
451
466
|
}
|
|
@@ -536,6 +551,49 @@ export async function main() {
|
|
|
536
551
|
console.log(`Messages: ${session.messages?.length || 0}`)
|
|
537
552
|
console.log(`Turns: ${engine.state.turnCount}`)
|
|
538
553
|
break
|
|
554
|
+
case 'resume': {
|
|
555
|
+
if (rest.length === 0) {
|
|
556
|
+
console.log('Usage: /resume <session-id> (from /sessions list)')
|
|
557
|
+
break
|
|
558
|
+
}
|
|
559
|
+
const target = rest.join(' ')
|
|
560
|
+
let sessionId = target
|
|
561
|
+
// 支持数字序号选择(从 /sessions 列表中)
|
|
562
|
+
if (/^\d+$/.test(target)) {
|
|
563
|
+
const idx = parseInt(target, 10) - 1
|
|
564
|
+
const list = await sessionManager.list()
|
|
565
|
+
if (idx < 0 || idx >= list.length) {
|
|
566
|
+
console.log(`❌ Session index out of range (1-${list.length})`)
|
|
567
|
+
break
|
|
568
|
+
}
|
|
569
|
+
sessionId = list[idx].id
|
|
570
|
+
}
|
|
571
|
+
const loaded = await sessionManager.load(sessionId)
|
|
572
|
+
if (!loaded) {
|
|
573
|
+
console.log(`❌ Session not found: ${sessionId}`)
|
|
574
|
+
break
|
|
575
|
+
}
|
|
576
|
+
// 恢复会话内容
|
|
577
|
+
session.id = loaded.id
|
|
578
|
+
session.title = loaded.title
|
|
579
|
+
session.messages = loaded.messages
|
|
580
|
+
session.state = loaded.state || {}
|
|
581
|
+
// 恢复引擎消息历史
|
|
582
|
+
engine.state.messages = loaded.messages.map(m => ({
|
|
583
|
+
role: m.role,
|
|
584
|
+
content: m.content,
|
|
585
|
+
toolCalls: m.toolCalls,
|
|
586
|
+
toolCallId: m.toolCallId,
|
|
587
|
+
}))
|
|
588
|
+
// 恢复计数和预算
|
|
589
|
+
engine.state.turnCount = loaded.state?.turnCount || 0
|
|
590
|
+
if (engine.tokenBudget && loaded.state?.budgetUsed != null) {
|
|
591
|
+
engine.tokenBudget.used = loaded.state.budgetUsed
|
|
592
|
+
}
|
|
593
|
+
console.log(`✅ Resumed session: ${loaded.title} (${loaded.id})`)
|
|
594
|
+
console.log(`\n💡 Tip: Use /sessions to list, /resume <id> to switch.`)
|
|
595
|
+
break
|
|
596
|
+
}
|
|
539
597
|
case 'sessions': {
|
|
540
598
|
const sessions = await sessionManager.list()
|
|
541
599
|
if (sessions.length === 0) console.log('No sessions found')
|
|
@@ -583,9 +641,18 @@ export async function main() {
|
|
|
583
641
|
break
|
|
584
642
|
}
|
|
585
643
|
case 'allow': {
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
644
|
+
const arg = rest.join(' ').toLowerCase()
|
|
645
|
+
if (arg === 'all') {
|
|
646
|
+
engine.permissionChecker.allowAllForSession()
|
|
647
|
+
console.log('✅ All tools allowed for the rest of this session')
|
|
648
|
+
} else if (arg === 'reset') {
|
|
649
|
+
engine.permissionChecker.resetSessionAllow()
|
|
650
|
+
console.log('✅ Session permission reset to normal (ask mode)')
|
|
651
|
+
} else {
|
|
652
|
+
const tool = arg || '*'
|
|
653
|
+
engine.permissionChecker.allow(tool)
|
|
654
|
+
console.log(`✅ Tool "${tool}" allowed for this session`)
|
|
655
|
+
}
|
|
589
656
|
break
|
|
590
657
|
}
|
|
591
658
|
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
|
|