cc-viewer 1.2.7 → 1.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CC-Viewer
2
2
 
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.
3
+ A Claude Code request monitoring system that captures and visualizes all API requests and responses from Claude Code in real time (raw text, unredacted). Helps developers monitor their context for review and troubleshooting during Vibe Coding sessions.
4
4
 
5
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
 
@@ -12,32 +12,40 @@ English | [简体中文](./docs/README.zh.md) | [繁體中文](./docs/README.zh-
12
12
  npm install -g cc-viewer
13
13
  ```
14
14
 
15
- ### Run & Auto-Configuration
15
+ ### Running and Auto-Configuration
16
16
 
17
17
  ```bash
18
18
  ccv
19
19
  ```
20
20
 
21
- This command automatically detects your local Claude Code installation method (NPM or Native Install) and adapts accordingly.
21
+ This command automatically detects how Claude Code is installed locally (NPM or Native Install) and adapts accordingly.
22
22
 
23
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.
24
+ - **Native Install**: Automatically detects the `claude` binary, configures a local transparent proxy, and sets up a Zsh Shell Hook to forward traffic automatically.
25
25
 
26
26
  ### Configuration Override
27
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.
28
+ If you need to use a custom API endpoint (e.g., a corporate proxy), simply configure it in `~/.claude/settings.json` or set the `ANTHROPIC_BASE_URL` environment variable. `ccv` will automatically detect and correctly forward requests.
29
29
 
30
30
  ### Silent Mode
31
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`.
32
+ By default, `ccv` runs in silent mode when wrapping `claude`, keeping your terminal output clean and consistent with the native experience. All logs are captured in the background and can be viewed at `http://localhost:7008`.
33
33
 
34
- Once configured, just use the `claude` command as usual. Visit `http://localhost:7008` to view the monitoring interface.
34
+ Once configured, use the `claude` command as normal. Visit `http://localhost:7008` to access the monitoring interface.
35
35
 
36
36
  ### Troubleshooting
37
37
 
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.
38
+ If you encounter issues starting cc-viewer, here is the ultimate troubleshooting approach:
39
+
40
+ Step 1: Open Claude Code in any directory.
41
+
42
+ Step 2: Give Claude Code the following instruction:
43
+ ```
44
+ I have installed the cc-viewer npm package but cannot get it to start. Please check cc-viewer's cli.js and findcc.js, and adapt them to the local Claude Code deployment based on the specifics. Keep the scope of changes as constrained as possible within findcc.js.
45
+ ```
46
+ Letting Claude Code diagnose the issue itself is more effective than asking anyone or reading any documentation!
47
+
48
+ After the above instruction is completed, `findcc.js` will be updated. If your project frequently requires local deployment, or if forked code often needs to resolve installation issues, keeping this file lets you simply copy it next time. At this stage, many projects and companies using Claude Code are not deploying on Mac but rather on server-side hosted environments, so the author has separated `findcc.js` to make it easier to track cc-viewer source code updates going forward.
41
49
 
42
50
  ### Uninstall
43
51
 
@@ -55,44 +63,44 @@ ccv --version
55
63
 
56
64
  ### Request Monitoring (Raw Mode)
57
65
  <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)
60
- - MainAgent requests support Body Diff JSON, showing a collapsible diff with the previous MainAgent request (only changed/added fields)
66
+ - Captures all API requests made by Claude Code in real time, ensuring raw content rather than truncated logs (this is important!!!)
67
+ - Automatically identifies and labels Main Agent and Sub Agent requests (subtypes: Bash, Task, Plan, General)
68
+ - MainAgent requests support Body Diff JSON, showing a collapsed diff of changes from the previous MainAgent request (only changed/added fields)
61
69
  - Inline token usage stats per request (input/output tokens, cache creation/read, hit rate)
62
- - Compatible with Claude Code Router (CCR) and other proxy setupsrequests are matched by API path pattern as a fallback
70
+ - Compatible with Claude Code Router (CCR) and other proxy scenariosfalls back to API path pattern matching
63
71
 
64
- ### Chat Mode
72
+ ### Conversation Mode
65
73
 
66
- Click the "Chat Mode" button in the top right to parse Main Agent's full conversation history into a chat interface:
74
+ Click the "Conversation Mode" button in the top-right corner to parse the Main Agent's full conversation history into a chat interface:
67
75
  <img width="1500" height="730" alt="image" src="https://github.com/user-attachments/assets/c973f142-748b-403f-b2b7-31a5d81e33e6" />
68
76
 
69
77
 
70
78
  - Agent Team display is not yet supported
71
79
  - 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
80
+ - `thinking` blocks are collapsed by default, rendered in Markdown, and can be expanded to view the reasoning process; one-click translation is supported (feature is still unstable)
81
+ - User selection messages (AskUserQuestion) are displayed in a Q&A format
82
+ - Bidirectional mode sync: switching to Conversation Mode automatically navigates to the conversation corresponding to the selected request; switching back to Raw Mode automatically navigates to the selected request
83
+ - Settings panel: toggle the default collapsed state of tool results and thinking blocks
76
84
 
77
85
 
78
86
  ### Statistics Tool
79
87
 
80
- "Data Statistics" hover panel in the header area:
88
+ The "Data Statistics" floating panel in the header area:
81
89
  <img width="1500" height="729" alt="image" src="https://github.com/user-attachments/assets/b23f9a81-fc3d-4937-9700-e70d84e4e5ce" />
82
90
 
83
91
  - 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
87
- - Concept help (?) icons: click to view built-in documentation for MainAgent, CacheRebuild, and each tool
92
+ - Cache rebuild statistics: grouped by reason (TTL, system/tools/model changes, message truncation/modification, key changes) showing counts and cache_creation tokens
93
+ - Tool usage statistics: displays call frequency for each tool sorted by number of calls
94
+ - Skill usage statistics: displays call frequency for each skill sorted by number of calls
95
+ - Concept help (?) icon: click to view built-in documentation for MainAgent, CacheRebuild, and each tool
88
96
 
89
97
  ### Log Management
90
98
 
91
- Via the CC-Viewer dropdown menu in the top left:
99
+ Via the CC-Viewer dropdown menu in the top-left corner:
92
100
  <img width="1200" height="672" alt="image" src="https://github.com/user-attachments/assets/8cf24f5b-9450-4790-b781-0cd074cd3b39" />
93
101
 
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)
102
+ - Import local logs: browse historical log files grouped by project, open in a new window
103
+ - Load local JSONL file: directly select a local `.jsonl` file to load and view (supports up to 500MB)
96
104
  - Save current log as: download the current monitoring JSONL log file
97
105
  - Merge logs: combine multiple JSONL log files into a single session for unified analysis
98
106
  - 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
package/cli.js CHANGED
@@ -1,58 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
4
- import { resolve, join } from 'node:path';
4
+ import { resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { homedir } from 'node:os';
7
- import { spawn, execSync } from 'node:child_process';
7
+ import { spawn } from 'node:child_process';
8
8
  import { t } from './i18n.js';
9
+ import { INJECT_IMPORT, resolveCliPath, resolveNativePath, buildShellCandidates } from './findcc.js';
9
10
 
10
11
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
11
12
 
12
13
  const INJECT_START = '// >>> Start CC Viewer Web Service >>>';
13
14
  const INJECT_END = '// <<< Start CC Viewer Web Service <<<';
14
- const INJECT_IMPORT = "import '../../cc-viewer/interceptor.js';";
15
15
  const INJECT_BLOCK = `${INJECT_START}\n${INJECT_IMPORT}\n${INJECT_END}`;
16
16
 
17
17
 
18
18
  const SHELL_HOOK_START = '# >>> CC-Viewer Auto-Inject >>>';
19
19
  const SHELL_HOOK_END = '# <<< CC-Viewer Auto-Inject <<<';
20
20
 
21
- // Claude Code cli.js 包名候选列表,按优先级排列
22
- const CLAUDE_CODE_PACKAGES = [
23
- '@anthropic-ai/claude-code',
24
- '@ali/claude-code',
25
- ];
26
-
27
- function getGlobalNodeModulesDir() {
28
- try {
29
- return execSync('npm root -g', { encoding: 'utf-8' }).trim();
30
- } catch {
31
- return null;
32
- }
33
- }
34
-
35
- function resolveClaudeCodeCliPath() {
36
- // 候选基础目录:__dirname 的上级(适用于常规 npm 安装)+ 全局 node_modules(适用于符号链接安装)
37
- const baseDirs = [resolve(__dirname, '..')];
38
- const globalRoot = getGlobalNodeModulesDir();
39
- if (globalRoot && globalRoot !== resolve(__dirname, '..')) {
40
- baseDirs.push(globalRoot);
41
- }
42
-
43
- for (const baseDir of baseDirs) {
44
- for (const packageName of CLAUDE_CODE_PACKAGES) {
45
- const candidate = join(baseDir, packageName, 'cli.js');
46
- if (existsSync(candidate)) {
47
- return candidate;
48
- }
49
- }
50
- }
51
- // 兜底:返回全局目录下的默认路径,便于错误提示
52
- return join(globalRoot || resolve(__dirname, '..'), CLAUDE_CODE_PACKAGES[0], 'cli.js');
53
- }
54
-
55
- const cliPath = resolveClaudeCodeCliPath();
21
+ const cliPath = resolveCliPath();
56
22
 
57
23
  function getShellConfigPath() {
58
24
  const shell = process.env.SHELL || '';
@@ -80,10 +46,11 @@ claude() {
80
46
  ${SHELL_HOOK_END}`;
81
47
  }
82
48
 
49
+ const candidates = buildShellCandidates();
83
50
  return `${SHELL_HOOK_START}
84
51
  claude() {
85
52
  local cli_js=""
86
- for candidate in "$HOME/.npm-global/lib/node_modules/@anthropic-ai/claude-code/cli.js" "$HOME/.npm-global/lib/node_modules/@ali/claude-code/cli.js"; do
53
+ for candidate in ${candidates}; do
87
54
  if [ -f "$candidate" ]; then
88
55
  cli_js="$candidate"
89
56
  break
@@ -161,37 +128,6 @@ function removeCliJsInjection() {
161
128
  }
162
129
  }
163
130
 
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
131
  async function runProxyCommand(args) {
196
132
  try {
197
133
  // Dynamic import to avoid side effects when just installing
@@ -229,49 +165,24 @@ async function runProxyCommand(args) {
229
165
  }
230
166
 
231
167
  const env = { ...process.env };
232
- // [Debug] Verify hook execution
233
- // console.error(`[CC-Viewer] Hook triggered for: ${cmd} ${cmdArgs.join(' ')}`);
234
-
235
168
  // Determine the path to the native 'claude' executable
236
169
  if (cmd === 'claude') {
237
- const nativePath = getNativeInstallPath();
170
+ const nativePath = resolveNativePath();
238
171
  if (nativePath) {
239
172
  cmd = nativePath;
240
173
  }
241
174
  }
242
175
  env.ANTHROPIC_BASE_URL = `http://127.0.0.1:${proxyPort}`;
243
176
 
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
177
  const settingsJson = JSON.stringify({
263
178
  env: {
264
179
  ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL
265
180
  }
266
181
  });
267
182
 
268
- // Inject --settings argument
269
- // We put it at the beginning of args to ensure it's picked up
270
183
  cmdArgs.unshift(settingsJson);
271
184
  cmdArgs.unshift('--settings');
272
185
 
273
- // Force non-interactive if needed? No, we want interactive.
274
-
275
186
  const child = spawn(cmd, cmdArgs, { stdio: 'inherit', env });
276
187
 
277
188
  child.on('exit', (code) => {
@@ -341,7 +252,7 @@ if (args[0] === 'run') {
341
252
  if (existsSync(cliPath)) {
342
253
  mode = 'npm';
343
254
  } else {
344
- const nativePath = getNativeInstallPath();
255
+ const nativePath = resolveNativePath();
345
256
  if (nativePath) {
346
257
  mode = 'native';
347
258
  }
package/findcc.js ADDED
@@ -0,0 +1,107 @@
1
+ import { resolve, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { existsSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { execSync } from 'node:child_process';
6
+
7
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
8
+
9
+ // ============ 配置区(第三方适配只需修改此处)============
10
+
11
+ // 日志存储根目录(所有项目日志、偏好设置均存放于此)
12
+ export const LOG_DIR = join(homedir(), '.claude', 'cc-viewer');
13
+
14
+ // npm 包名候选列表(按优先级排列)
15
+ export const PACKAGES = ['@anthropic-ai/claude-code', '@ali/claude-code'];
16
+
17
+ // npm 包内的入口文件(相对于包根目录)
18
+ export const CLI_ENTRY = 'cli.js';
19
+
20
+ // native 二进制候选路径(~ 会在运行时展开为 homedir())
21
+ const NATIVE_CANDIDATES = [
22
+ '~/.claude/local/claude',
23
+ '/usr/local/bin/claude',
24
+ '~/.local/bin/claude',
25
+ '/opt/homebrew/bin/claude',
26
+ ];
27
+
28
+ // 用于 which/command -v 查找的命令名
29
+ export const BINARY_NAME = 'claude';
30
+
31
+ // 注入到 cli.js 的 import 语句(相对路径,基于 cli.js 所在位置)
32
+ export const INJECT_IMPORT = "import '../../cc-viewer/interceptor.js';";
33
+
34
+ // ============ 导出函数 ============
35
+
36
+ export function getGlobalNodeModulesDir() {
37
+ try {
38
+ return execSync('npm root -g', { encoding: 'utf-8' }).trim();
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ export function resolveCliPath() {
45
+ // 候选基础目录:__dirname 的上级(适用于常规 npm 安装)+ 全局 node_modules(适用于符号链接安装)
46
+ const baseDirs = [resolve(__dirname, '..')];
47
+ const globalRoot = getGlobalNodeModulesDir();
48
+ if (globalRoot && globalRoot !== resolve(__dirname, '..')) {
49
+ baseDirs.push(globalRoot);
50
+ }
51
+
52
+ for (const baseDir of baseDirs) {
53
+ for (const packageName of PACKAGES) {
54
+ const candidate = join(baseDir, packageName, CLI_ENTRY);
55
+ if (existsSync(candidate)) {
56
+ return candidate;
57
+ }
58
+ }
59
+ }
60
+ // 兜底:返回全局目录下的默认路径,便于错误提示
61
+ return join(globalRoot || resolve(__dirname, '..'), PACKAGES[0], CLI_ENTRY);
62
+ }
63
+
64
+ export function resolveNativePath() {
65
+ // 1. 尝试 which/command -v(继承当前 process.env PATH)
66
+ for (const cmd of [`which ${BINARY_NAME}`, `command -v ${BINARY_NAME}`]) {
67
+ try {
68
+ const result = execSync(cmd, { encoding: 'utf-8', shell: true, env: process.env }).trim();
69
+ // 排除 shell function 的输出(多行说明不是路径)
70
+ if (result && !result.includes('\n') && existsSync(result)) {
71
+ return result;
72
+ }
73
+ } catch {
74
+ // ignore
75
+ }
76
+ }
77
+
78
+ // 2. 检查常见 native 安装路径
79
+ const home = homedir();
80
+ const candidates = NATIVE_CANDIDATES.map(p =>
81
+ p.startsWith('~') ? join(home, p.slice(2)) : p
82
+ );
83
+ for (const p of candidates) {
84
+ if (existsSync(p)) {
85
+ return p;
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ export function buildShellCandidates() {
93
+ const globalRoot = getGlobalNodeModulesDir();
94
+ // 使用 $HOME 而非硬编码绝对路径,保证 shell 可移植性
95
+ const dirs = [];
96
+ if (globalRoot) {
97
+ // 将绝对路径中的 homedir 替换为 $HOME
98
+ const home = homedir();
99
+ const shellRoot = globalRoot.startsWith(home)
100
+ ? '$HOME' + globalRoot.slice(home.length)
101
+ : globalRoot;
102
+ for (const pkg of PACKAGES) {
103
+ dirs.push(`"${shellRoot}/${pkg}/${CLI_ENTRY}"`);
104
+ }
105
+ }
106
+ return dirs.join(' ');
107
+ }