@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.2.8",
3
+ "version": "2.3.0",
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
@@ -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] — Allow a tool for the current session (default: all)
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
- 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.",
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 [tool_name]\n Allow a tool to execute without confirmation for this session.\n Without tool name: allows ALL tools.\n\n Example: /allow\n Example: /allow Bash",
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
- const snippet = JSON.stringify(input).slice(0, 120) || '(no params)'
447
- rl.question(`\n⚠️ Allow tool "${toolName}"?\n Input: ${snippet}\n (y/N) `, (answer) => {
448
- resolve(answer.toLowerCase().startsWith('y'))
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 allowTool = rest.join(' ') || '*'
587
- engine.permissionChecker.allowForSession(allowTool, '*')
588
- console.log(`✅ Tool "${allowTool}" allowed for this session`)
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