@raolin2025/claude-code-node 2.2.6 → 2.2.8

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 +141 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.2.6",
3
+ "version": "2.2.8",
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
@@ -116,14 +116,102 @@ function startSocketServer(engine, session, sessionManager, channelManager, verb
116
116
  // Banner & Help
117
117
  // ============================================================
118
118
 
119
- const BANNER = `
120
- ╔═══════════════════════════════════════════════╗
121
- ║ AI Code Agent — Node.js Edition ║
122
- ║ OpenAI-Compatible · DeepSeek Default ║
123
- ║ Type '/help' for commands ║
124
- ║ Type '/exit' or Ctrl+C to quit ║
125
- ╚═══════════════════════════════════════════════╝
126
- `.trim()
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:
@@ -143,8 +231,43 @@ Commands:
143
231
  /allow [tool] — Allow a tool for the current session (default: all)
144
232
  /exit — Exit (also Ctrl+C)
145
233
  /quit — Same as /exit
234
+
235
+ Use "/help <cmd>" for detailed help on a specific command.
146
236
  `
147
237
 
238
+ const DETAILED_HELP = {
239
+ help: "/help [command]\n Show help. Without argument: list all commands.\n With a command name: show detailed help for that command.\n\n Example: /help model",
240
+
241
+ model: "/model <model_name>\n Switch the LLM model in real-time.\n The change takes effect immediately for the next message.\n You can use any model name supported by your current API provider.\n\n Example: /model deepseek-chat\n Example: /model gpt-4o",
242
+
243
+ models: "/models\n Fetch and display all available models from the current API provider.\n Shows a numbered list, then prompts you to select by number or name.\n Requires a configured API key (the one you used to start cc-node).\n Uses the endpoint: <apiBase>/models",
244
+
245
+ tools: "/tools\n List all available tools that cc-node can use.\n Shows tool names with their short descriptions.\n\n Tools include: Bash, Read, Edit, Write, Glob, Grep,\n WebFetch, WebSearch, AskUserQuestion, GitTool",
246
+
247
+ session: "/session\n Show current session information:\n - Session ID\n - Session title\n - Number of messages\n - Number of tool call turns",
248
+
249
+ sessions:"/sessions\n List all saved sessions.\n Shows session ID, title, message count, and last update time.",
250
+
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.",
252
+
253
+ 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
+
255
+ budget: "/budget\n Show token budget usage for the current session.\n Displays how many tokens have been used vs the limit.",
256
+
257
+ channel: "/channel <list|send|test>\n Manage notification channels.\n\n Subcommands:\n list — List all configured notification channels\n send <msg> — Send a message via all channels\n test — Send a test message to verify channels\n\n Requires channel environment variables to be set at startup.",
258
+
259
+ cost: "/cost\n Show API cost report.\n Displays total tokens used and estimated cost in USD.\n Supports pricing for: DeepSeek, OpenAI, Qwen, GLM, Kimi.",
260
+
261
+ compact: "/compact\n Manually trigger context compression.\n Compresses the conversation history to fit within the token budget.\n Keeps recent turns intact, compresses older ones.\n\n Typically triggered automatically at 80% budget usage.",
262
+
263
+ 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
+
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",
266
+
267
+ exit: "/exit\n Exit cc-node. Same as Ctrl+C or /quit.",
268
+ quit: "/quit\n Exit cc-node. Same as Ctrl+C or /exit.",
269
+ }
270
+
148
271
  // ============================================================
149
272
  // 参数解析
150
273
  // ============================================================
@@ -329,7 +452,7 @@ export async function main() {
329
452
  }
330
453
  engine.config.readline = rl
331
454
 
332
- console.log(BANNER)
455
+ console.log(buildBanner({ model, permissionMode, session, maxTokens: tokenBudget.maxTokens }))
333
456
  console.log(`Model: ${model} | Permission: ${permissionMode} | Tools: ${registry.getNames().join(', ')}`)
334
457
  console.log(`Socket: ${SOCK_PATH} (cc-notify can connect)`)
335
458
  if (channelManager.list().length > 0) {
@@ -352,7 +475,15 @@ export async function main() {
352
475
  if (input.startsWith('/')) {
353
476
  const [cmd, ...rest] = input.slice(1).split(' ')
354
477
  switch (cmd) {
355
- case 'help': console.log(HELP_TEXT); break
478
+ case 'help':
479
+ if (rest[0]) {
480
+ const detail = DETAILED_HELP[rest[0].toLowerCase()]
481
+ if (detail) console.log(detail)
482
+ else console.log(`No detailed help for /${rest[0]}. Type /help for all commands.`)
483
+ } else {
484
+ console.log(HELP_TEXT)
485
+ }
486
+ break
356
487
  case 'model':
357
488
  if (rest[0]) { engine.config.model = rest.join(' '); console.log(`Model → ${engine.config.model}`) }
358
489
  else console.log(`Model: ${engine.config.model}`)