cc-viewer 1.2.3 → 1.2.7

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/README.md CHANGED
@@ -1,24 +1,43 @@
1
1
  # CC-Viewer
2
2
 
3
- A request monitoring system for Claude Code that captures and visualizes all API requests and responses in real time. Helps developers monitor their Context for reviewing and debugging during Vibe Coding.
3
+ Claude Code request monitoring system that captures and visualizes all API requests and responses from Claude Code in real time (raw text, untruncated). Helps developers monitor their Context for reviewing and troubleshooting during Vibe Coding.
4
4
 
5
- [简体中文](./docs/README.zh.md) | [繁體中文](./docs/README.zh-TW.md) | [한국어](./docs/README.ko.md) | [日本語](./docs/README.ja.md) | [Deutsch](./docs/README.de.md) | [Español](./docs/README.es.md) | [Français](./docs/README.fr.md) | [Italiano](./docs/README.it.md) | [Dansk](./docs/README.da.md) | [Polski](./docs/README.pl.md) | [Русский](./docs/README.ru.md) | [العربية](./docs/README.ar.md) | [Norsk](./docs/README.no.md) | [Português (Brasil)](./docs/README.pt-BR.md) | [ไทย](./docs/README.th.md) | [Türkçe](./docs/README.tr.md) | [Українська](./docs/README.uk.md)
5
+ English | [简体中文](./docs/README.zh.md) | [繁體中文](./docs/README.zh-TW.md) | [한국어](./docs/README.ko.md) | [日本語](./docs/README.ja.md) | [Deutsch](./docs/README.de.md) | [Español](./docs/README.es.md) | [Français](./docs/README.fr.md) | [Italiano](./docs/README.it.md) | [Dansk](./docs/README.da.md) | [Polski](./docs/README.pl.md) | [Русский](./docs/README.ru.md) | [العربية](./docs/README.ar.md) | [Norsk](./docs/README.no.md) | [Português (Brasil)](./docs/README.pt-BR.md) | [ไทย](./docs/README.th.md) | [Türkçe](./docs/README.tr.md) | [Українська](./docs/README.uk.md)
6
6
 
7
7
  ## Usage
8
8
 
9
+ ### Installation
10
+
9
11
  ```bash
10
12
  npm install -g cc-viewer
11
13
  ```
12
14
 
13
- After installation, run:
15
+ ### Run & Auto-Configuration
14
16
 
15
17
  ```bash
16
18
  ccv
17
19
  ```
18
20
 
19
- Then use Claude Code as usual and open `http://localhost:7008` in your browser to view the monitoring interface.
21
+ This command automatically detects your local Claude Code installation method (NPM or Native Install) and adapts accordingly.
22
+
23
+ - **NPM Install**: Automatically injects an interceptor script into Claude Code's `cli.js`.
24
+ - **Native Install**: Automatically detects the `claude` binary, configures a local transparent proxy, and sets up a Zsh Shell Hook to automatically forward traffic.
25
+
26
+ ### Configuration Override
27
+
28
+ If you need to use a custom API endpoint (e.g., corporate proxy), simply configure it in `~/.claude/settings.json` or set the `ANTHROPIC_BASE_URL` environment variable. `ccv` will automatically detect it and forward requests correctly.
29
+
30
+ ### Silent Mode
31
+
32
+ By default, `ccv` runs in silent mode when wrapping `claude`, ensuring your terminal output stays clean and consistent with the native experience. All logs are captured in the background and can be viewed at `http://localhost:7008`.
33
+
34
+ Once configured, just use the `claude` command as usual. Visit `http://localhost:7008` to view the monitoring interface.
35
+
36
+ ### Troubleshooting
20
37
 
21
- After Claude Code updates, no manual action is needed the next time you run `claude`, it will automatically detect and reconfigure.
38
+ - **Mixed Output**: If you see `[CC-Viewer]` debug logs mixed with Claude's output, please update to the latest version (`npm install -g cc-viewer`).
39
+ - **Connection Refused**: Make sure the `ccv` background process is running. Running `ccv` or `claude` (after hook installation) should start it automatically.
40
+ - **Empty Body**: If you see "No Body" in the Viewer, it may be due to non-standard SSE formats. The Viewer now supports capturing raw content as a fallback.
22
41
 
23
42
  ### Uninstall
24
43
 
@@ -26,80 +45,58 @@ After Claude Code updates, no manual action is needed — the next time you run
26
45
  ccv --uninstall
27
46
  ```
28
47
 
48
+ ### Check Version
49
+
50
+ ```bash
51
+ ccv --version
52
+ ```
53
+
29
54
  ## Features
30
55
 
31
56
  ### Request Monitoring (Raw Mode)
32
-
33
- - Real-time capture of all API requests from Claude Code, including streaming responses
34
- - Left panel shows request method, URL, duration, and status code
35
- - Automatically identifies and labels Main Agent and Sub Agent requests (with sub-types: Bash, Task, Plan, General)
36
- - Request list auto-scrolls to the selected item (centered on mode switch, nearest on manual click)
37
- - Right panel supports Request / Response tab switching
38
- - Request Body expands `messages`, `system`, `tools` one level by default
39
- - Response Body fully expanded by default
40
- - Toggle between JSON view and plain text view
41
- - One-click JSON content copy
57
+ <img width="1500" height="720" alt="image" src="https://github.com/user-attachments/assets/519dd496-68bd-4e76-84d7-2a3d14ae3f61" />
58
+ - Real-time capture of all API requests from Claude Code, ensuring raw text rather than truncated logs (this is important!!!)
59
+ - Automatically identifies and labels Main Agent and Sub Agent requests (sub-types: Bash, Task, Plan, General)
42
60
  - MainAgent requests support Body Diff JSON, showing a collapsible diff with the previous MainAgent request (only changed/added fields)
43
- - Diff section supports JSON/Text view switching and one-click copy
44
- - "Expand Diff" setting: when enabled, MainAgent requests auto-expand the diff section
45
- - Body Diff JSON tooltip is dismissible; once closed, the preference is persisted server-side and never shown again
46
- - Sensitive headers (`x-api-key`, `authorization`) are automatically masked in JSONL log files to prevent credential leakage
47
61
  - Inline token usage stats per request (input/output tokens, cache creation/read, hit rate)
48
62
  - Compatible with Claude Code Router (CCR) and other proxy setups — requests are matched by API path pattern as a fallback
49
63
 
50
64
  ### Chat Mode
51
65
 
52
- Click the "Chat mode" button in the top right to parse Main Agent's full conversation history into a chat interface:
53
-
54
- - User messages right-aligned (blue bubbles), Main Agent replies left-aligned (dark bubbles) with Markdown rendering
55
- - `/compact` messages auto-detected and displayed collapsed, click to expand full summary
56
- - Tool call results displayed inline within the corresponding Assistant message
57
- - `thinking` blocks collapsed by default, rendered as Markdown, click to expand; supports one-click translation
58
- - `tool_use` shown as compact tool call cards (Bash, Read, Edit, Write, Glob, Grep, Task each have dedicated displays)
59
- - Task (SubAgent) tool results rendered as Markdown
60
- - User selection messages (AskUserQuestion) shown in Q&A format
61
- - System tags (`<system-reminder>`, `<project-reminder>`, etc.) auto-collapsed
62
- - Skill loaded messages auto-detected and collapsed, showing skill name; click to expand full documentation with Markdown rendering
63
- - Skills reminder auto-detected and collapsed
64
- - System text auto-filtered, showing only real user input
65
- - Multi-session segmented display (auto-segmented after `/compact`, `/clear`, etc.)
66
- - Each message shows a timestamp accurate to the second, derived from API request timing
67
- - Each message has a "View Request" link to jump back to raw mode at the corresponding API request
68
- - Bidirectional mode sync: switching to chat mode scrolls to the conversation matching the selected request; switching back scrolls to the selected request
69
- - Settings panel: toggle default collapse state for tool results and thinking blocks
70
- - Global settings: toggle filtering of irrelevant requests (count_tokens, heartbeat) from the request list
66
+ Click the "Chat Mode" button in the top right to parse Main Agent's full conversation history into a chat interface:
67
+ <img width="1500" height="730" alt="image" src="https://github.com/user-attachments/assets/c973f142-748b-403f-b2b7-31a5d81e33e6" />
71
68
 
72
- ### Translation
73
69
 
74
- - Thinking blocks and assistant messages support one-click translation
75
- - Powered by Claude Haiku API, uses `x-api-key` authentication only (OAuth session tokens are excluded to prevent context pollution)
76
- - Automatically captures haiku model name from mainAgent requests; defaults to `claude-haiku-4-5-20251001`
77
- - Translation results are cached; click again to toggle back to the original text
78
- - Loading spinner animation shown during translation
79
- - (?) help icon on `authorization` header links to concept doc explaining context pollution
70
+ - Agent Team display is not yet supported
71
+ - User messages are right-aligned (blue bubbles), Main Agent replies are left-aligned (dark bubbles)
72
+ - `thinking` blocks are collapsed by default, rendered in Markdown, click to expand and view the thinking process; one-click translation supported (feature is still unstable)
73
+ - User selection messages (AskUserQuestion) are displayed in Q&A format
74
+ - Bidirectional mode sync: switching to Chat Mode auto-scrolls to the conversation corresponding to the selected request; switching back to Raw Mode auto-scrolls to the selected request
75
+ - Settings panel: toggle default collapse state for tool results and thinking blocks
76
+
80
77
 
81
- ### Token Stats
78
+ ### Statistics Tool
82
79
 
83
- Hover panel in the header area:
80
+ "Data Statistics" hover panel in the header area:
81
+ <img width="1500" height="729" alt="image" src="https://github.com/user-attachments/assets/b23f9a81-fc3d-4937-9700-e70d84e4e5ce" />
84
82
 
85
- - Token counts grouped by model (input/output)
86
- - Cache creation/read counts and cache hit rate
87
- - Cache rebuild statistics: grouped by reason (TTL, system/tools/model change, message truncation/modification, key change) with count and cache_creation tokens
88
- - Tool usage statistics: call counts per tool, sorted by frequency
89
- - Skill usage statistics: call counts per skill, sorted by frequency
83
+ - Displays cache creation/read counts and cache hit rate
84
+ - Cache rebuild statistics: grouped by reason (TTL, system/tools/model change, message truncation/modification, key change) showing count and cache_creation tokens
85
+ - Tool usage statistics: tools displayed by call frequency, sorted by count
86
+ - Skill usage statistics: skills displayed by call frequency, sorted by count
90
87
  - Concept help (?) icons: click to view built-in documentation for MainAgent, CacheRebuild, and each tool
91
- - Main Agent cache expiration countdown
92
88
 
93
89
  ### Log Management
94
90
 
95
91
  Via the CC-Viewer dropdown menu in the top left:
92
+ <img width="1200" height="672" alt="image" src="https://github.com/user-attachments/assets/8cf24f5b-9450-4790-b781-0cd074cd3b39" />
96
93
 
97
- - Import local logs: browse historical log files, grouped by project, opens in new window
98
- - Load local JSONL file: directly select and load a local `.jsonl` file (up to 500MB)
99
- - Download current log: download the current monitoring JSONL log file
94
+ - Import local logs: browse historical log files, grouped by project, opens in a new window
95
+ - Load local JSONL file: directly select and load a local `.jsonl` file (supports up to 500MB)
96
+ - Save current log as: download the current monitoring JSONL log file
100
97
  - Merge logs: combine multiple JSONL log files into a single session for unified analysis
101
- - Export user prompts: extract and display all user inputs with three view modes — Original (raw content), Context (with system tags collapsible), and Text (plain text only); slash commands (`/model`, `/context`, etc.) shown as standalone entries; command-related tags auto-hidden from prompt content
102
- - Export prompts to TXT: export user prompts (text only, excluding system tags) to a local `.txt` file
98
+ - View user Prompts: extract and display all user inputs, supporting three view modes — Raw mode (original content), Context mode (system tags collapsible), Text mode (plain text); slash commands (`/model`, `/context`, etc.) shown as standalone entries; command-related tags are auto-hidden from Prompt content
99
+ - Export Prompts to TXT: export user Prompts (plain text, excluding system tags) to a local `.txt` file
103
100
 
104
101
  ### Multi-language Support
105
102
 
package/cli.js CHANGED
@@ -3,8 +3,8 @@
3
3
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
4
4
  import { resolve, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
- import { execSync } from 'node:child_process';
7
6
  import { homedir } from 'node:os';
7
+ import { spawn, execSync } from 'node:child_process';
8
8
  import { t } from './i18n.js';
9
9
 
10
10
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
@@ -65,7 +65,21 @@ function getShellConfigPath() {
65
65
  return resolve(homedir(), '.zshrc');
66
66
  }
67
67
 
68
- function buildShellHook() {
68
+ function buildShellHook(isNative) {
69
+ if (isNative) {
70
+ return `${SHELL_HOOK_START}
71
+ claude() {
72
+ # Avoid recursion if ccv invokes claude
73
+ if [ "$1" = "--ccv-internal" ]; then
74
+ shift
75
+ command claude "$@"
76
+ return
77
+ fi
78
+ ccv run -- claude --ccv-internal "$@"
79
+ }
80
+ ${SHELL_HOOK_END}`;
81
+ }
82
+
69
83
  return `${SHELL_HOOK_START}
70
84
  claude() {
71
85
  local cli_js=""
@@ -83,14 +97,23 @@ claude() {
83
97
  ${SHELL_HOOK_END}`;
84
98
  }
85
99
 
86
- function installShellHook() {
100
+ function installShellHook(isNative) {
87
101
  const configPath = getShellConfigPath();
88
102
  try {
89
- const content = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
103
+ let content = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
104
+
90
105
  if (content.includes(SHELL_HOOK_START)) {
91
- return { path: configPath, status: 'exists' };
106
+ // Check if existing hook matches desired mode
107
+ const isNativeHook = content.includes('ccv run -- claude');
108
+ if (!!isNative === !!isNativeHook) {
109
+ return { path: configPath, status: 'exists' };
110
+ }
111
+ // Mismatch: remove old hook first
112
+ removeShellHook();
113
+ content = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
92
114
  }
93
- const hook = buildShellHook();
115
+
116
+ const hook = buildShellHook(isNative);
94
117
  const newContent = content.endsWith('\n') ? content + '\n' + hook + '\n' : content + '\n\n' + hook + '\n';
95
118
  writeFileSync(configPath, newContent);
96
119
  return { path: configPath, status: 'installed' };
@@ -138,25 +161,166 @@ function removeCliJsInjection() {
138
161
  }
139
162
  }
140
163
 
164
+ function getNativeInstallPath() {
165
+ // 1. 尝试 which/command -v(继承当前 process.env PATH)
166
+ for (const cmd of ['which claude', 'command -v claude']) {
167
+ try {
168
+ const result = execSync(cmd, { encoding: 'utf-8', shell: true, env: process.env }).trim();
169
+ // 排除 shell function 的输出(多行说明不是路径)
170
+ if (result && !result.includes('\n') && existsSync(result)) {
171
+ return result;
172
+ }
173
+ } catch {
174
+ // ignore
175
+ }
176
+ }
177
+
178
+ // 2. 检查常见 native 安装路径
179
+ const home = homedir();
180
+ const candidates = [
181
+ join(home, '.claude', 'local', 'claude'),
182
+ '/usr/local/bin/claude',
183
+ join(home, '.local', 'bin', 'claude'),
184
+ '/opt/homebrew/bin/claude',
185
+ ];
186
+ for (const p of candidates) {
187
+ if (existsSync(p)) {
188
+ return p;
189
+ }
190
+ }
191
+
192
+ return null;
193
+ }
194
+
195
+ async function runProxyCommand(args) {
196
+ try {
197
+ // Dynamic import to avoid side effects when just installing
198
+ const { startProxy } = await import('./proxy.js');
199
+ const proxyPort = await startProxy();
200
+
201
+ // args = ['run', '--', 'command', 'claude', ...] or ['run', 'claude', ...]
202
+ // Our hook uses: ccv run -- claude --ccv-internal "$@"
203
+ // args[0] is 'run'.
204
+ // If args[1] is '--', then command starts at args[2].
205
+
206
+ let cmdStartIndex = 1;
207
+ if (args[1] === '--') {
208
+ cmdStartIndex = 2;
209
+ }
210
+
211
+ let cmd = args[cmdStartIndex];
212
+ if (!cmd) {
213
+ console.error('No command provided to run.');
214
+ process.exit(1);
215
+ }
216
+ let cmdArgs = args.slice(cmdStartIndex + 1);
217
+
218
+ // If cmd is 'claude' and next arg is '--ccv-internal', remove it
219
+ // and we must use 'command claude' to avoid infinite recursion of the shell function?
220
+ // Node spawn doesn't use shell functions, so 'claude' should resolve to the binary in PATH.
221
+ // BUT, if 'claude' is a function in the current shell, spawn won't see it unless we use shell:true.
222
+ // We are using shell:false (default).
223
+ // So spawn('claude') should find /usr/local/bin/claude (the binary).
224
+ // The issue might be that ccv itself is running in a way that PATH is weird?
225
+
226
+ // Wait, the shell hook adds '--ccv-internal'. We should strip it before spawning.
227
+ if (cmdArgs[0] === '--ccv-internal') {
228
+ cmdArgs.shift();
229
+ }
230
+
231
+ const env = { ...process.env };
232
+ // [Debug] Verify hook execution
233
+ // console.error(`[CC-Viewer] Hook triggered for: ${cmd} ${cmdArgs.join(' ')}`);
234
+
235
+ // Determine the path to the native 'claude' executable
236
+ if (cmd === 'claude') {
237
+ const nativePath = getNativeInstallPath();
238
+ if (nativePath) {
239
+ cmd = nativePath;
240
+ }
241
+ }
242
+ env.ANTHROPIC_BASE_URL = `http://127.0.0.1:${proxyPort}`;
243
+
244
+ // [Debug] Force ANTHROPIC_BASE_URL via process.env is not enough for some reason?
245
+ // Let's also check if we can pass it via command line args if supported, but claude cli doesn't seem to have a --base-url arg documented.
246
+ // However, maybe the issue is that 'env' in spawn options isn't overriding what claude internal config has?
247
+ // Claude Code likely reads from ~/.claude/settings.json which might take precedence over env vars?
248
+ // No, usually env vars take precedence.
249
+
250
+ // [Fix] Check if user has ANTHROPIC_BASE_URL in settings.json and use it in proxy.js
251
+ // We already do that in proxy.js: getOriginalBaseUrl().
252
+
253
+ // [Crucial Fix]
254
+ // Use --settings JSON argument to force ANTHROPIC_BASE_URL configuration
255
+ // This is safer and more reliable than env vars which might be ignored.
256
+
257
+ // console.error(`[CC-Viewer] Setting ANTHROPIC_BASE_URL to ${env.ANTHROPIC_BASE_URL}`);
258
+
259
+ // Construct settings JSON string
260
+ // Note: We need to be careful with quoting for the shell/spawn.
261
+ // Since we use spawn without shell, we can pass the JSON string directly as an argument.
262
+ const settingsJson = JSON.stringify({
263
+ env: {
264
+ ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL
265
+ }
266
+ });
267
+
268
+ // Inject --settings argument
269
+ // We put it at the beginning of args to ensure it's picked up
270
+ cmdArgs.unshift(settingsJson);
271
+ cmdArgs.unshift('--settings');
272
+
273
+ // Force non-interactive if needed? No, we want interactive.
274
+
275
+ const child = spawn(cmd, cmdArgs, { stdio: 'inherit', env });
276
+
277
+ child.on('exit', (code) => {
278
+ process.exit(code);
279
+ });
280
+
281
+ child.on('error', (err) => {
282
+ console.error('Failed to start command:', err);
283
+ process.exit(1);
284
+ });
285
+ } catch (err) {
286
+ console.error('Proxy error:', err);
287
+ process.exit(1);
288
+ }
289
+ }
290
+
141
291
  // === 主逻辑 ===
142
292
 
143
- const isUninstall = process.argv.includes('--uninstall');
144
- const isVersion = process.argv.includes('--v') || process.argv.includes('--version');
293
+ const args = process.argv.slice(2);
294
+ const isUninstall = args.includes('--uninstall');
295
+ const isHelp = args.includes('--help') || args.includes('-h') || args[0] === 'help';
296
+ const isVersion = args.includes('--v') || args.includes('--version') || args.includes('-v');
297
+
298
+ if (isHelp) {
299
+ console.log(t('cli.help'));
300
+ process.exit(0);
301
+ }
145
302
 
146
303
  if (isVersion) {
147
- const pkg = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8'));
148
- console.log(`cc-viewer v${pkg.version}`);
304
+ try {
305
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8'));
306
+ console.log(`cc-viewer v${pkg.version}`);
307
+ } catch (e) {
308
+ console.error('Failed to read version:', e.message);
309
+ }
149
310
  process.exit(0);
150
311
  }
151
312
 
152
- if (isUninstall) {
313
+ if (args[0] === 'run') {
314
+ runProxyCommand(args);
315
+ } else if (args.includes('--uninstall')) {
153
316
  const cliResult = removeCliJsInjection();
154
317
  const shellResult = removeShellHook();
155
318
 
156
319
  if (cliResult === 'removed' || cliResult === 'clean') {
157
320
  console.log(t('cli.uninstall.cliCleaned'));
158
321
  } else if (cliResult === 'not_found') {
159
- console.log(t('cli.uninstall.cliNotFound'));
322
+ // console.log(t('cli.uninstall.cliNotFound'));
323
+ // Silent is better for mixed mode uninstall
160
324
  } else {
161
325
  console.log(t('cli.uninstall.cliFail'));
162
326
  }
@@ -171,36 +335,72 @@ if (isUninstall) {
171
335
 
172
336
  console.log(t('cli.uninstall.done'));
173
337
  process.exit(0);
174
- }
175
-
176
- // 正常安装流程
177
- try {
178
- const cliResult = injectCliJs();
179
- const shellResult = installShellHook();
180
-
181
- if (cliResult === 'exists' && shellResult.status === 'exists') {
182
- console.log(t('cli.alreadyWorking'));
338
+ } else {
339
+ // Installation Logic
340
+ let mode = 'unknown';
341
+ if (existsSync(cliPath)) {
342
+ mode = 'npm';
183
343
  } else {
184
- if (cliResult === 'exists') {
185
- console.log(t('cli.inject.exists'));
186
- } else {
187
- console.log(t('cli.inject.success'));
188
- }
189
-
190
- if (shellResult.status === 'installed') {
191
- console.log('All READY!');
192
- } else if (shellResult.status !== 'exists') {
193
- console.log(t('cli.hook.fail', { error: shellResult.error }));
344
+ const nativePath = getNativeInstallPath();
345
+ if (nativePath) {
346
+ mode = 'native';
194
347
  }
195
348
  }
196
349
 
197
- console.log(t('cli.usage.hint'));
198
- } catch (err) {
199
- if (err.code === 'ENOENT') {
350
+ if (mode === 'unknown') {
200
351
  console.error(t('cli.inject.notFound', { path: cliPath }));
201
- console.error(t('cli.inject.notFoundHint'));
352
+ console.error('Also could not find native "claude" command in PATH.');
353
+ console.error('Please make sure @anthropic-ai/claude-code is installed.');
354
+ process.exit(1);
355
+ }
356
+
357
+ if (mode === 'npm') {
358
+ try {
359
+ const cliResult = injectCliJs();
360
+ const shellResult = installShellHook(false);
361
+
362
+ if (cliResult === 'exists' && shellResult.status === 'exists') {
363
+ console.log(t('cli.alreadyWorking'));
364
+ } else {
365
+ if (cliResult === 'exists') {
366
+ console.log(t('cli.inject.exists'));
367
+ } else {
368
+ console.log(t('cli.inject.success'));
369
+ }
370
+
371
+ if (shellResult.status === 'installed') {
372
+ console.log('All READY!');
373
+ } else if (shellResult.status !== 'exists') {
374
+ console.log(t('cli.hook.fail', { error: shellResult.error }));
375
+ }
376
+ }
377
+ console.log(t('cli.usage.hint'));
378
+ } catch (err) {
379
+ if (err.code === 'ENOENT') {
380
+ console.error(t('cli.inject.notFound', { path: cliPath }));
381
+ console.error(t('cli.inject.notFoundHint'));
382
+ } else {
383
+ console.error(t('cli.inject.fail', { error: err.message }));
384
+ }
385
+ process.exit(1);
386
+ }
202
387
  } else {
203
- console.error(t('cli.inject.fail', { error: err.message }));
388
+ // Native Mode
389
+ try {
390
+ console.log('Detected Claude Code Native Install.');
391
+ const shellResult = installShellHook(true);
392
+
393
+ if (shellResult.status === 'exists') {
394
+ console.log(t('cli.alreadyWorking'));
395
+ } else if (shellResult.status === 'installed') {
396
+ console.log('Native Hook Installed! All READY!');
397
+ } else {
398
+ console.log(t('cli.hook.fail', { error: shellResult.error }));
399
+ }
400
+ console.log(t('cli.usage.hint'));
401
+ } catch (err) {
402
+ console.error('Failed to install native hook:', err);
403
+ process.exit(1);
404
+ }
204
405
  }
205
- process.exit(1);
206
406
  }