claude-code-inspector 0.1.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.
@@ -0,0 +1,141 @@
1
+ import Database from 'better-sqlite3';
2
+ import { CREATE_TABLES } from './schema';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+
6
+ export interface RequestLog {
7
+ id: string;
8
+ session_id?: string | null;
9
+ endpoint: string;
10
+ method: string;
11
+ request_headers?: string | null;
12
+ request_body?: string | null;
13
+ response_status?: number | null;
14
+ response_headers?: string | null;
15
+ response_body?: string | null;
16
+ streaming_events?: string | null;
17
+ input_tokens: number;
18
+ output_tokens: number;
19
+ cache_read_tokens: number;
20
+ cache_creation_tokens: number;
21
+ latency_ms?: number | null;
22
+ first_token_ms?: number | null;
23
+ status_code?: number | null;
24
+ error_message?: string | null;
25
+ model?: string | null;
26
+ cost_usd?: number | null;
27
+ created_at: string;
28
+ }
29
+
30
+ export class RequestStore {
31
+ private db: ReturnType<typeof Database>;
32
+
33
+ constructor(dbPath: string) {
34
+ const fullPath = path.resolve(dbPath);
35
+ // 确保数据库目录存在
36
+ const dir = path.dirname(fullPath);
37
+ if (!fs.existsSync(dir)) {
38
+ fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+ this.db = new Database(fullPath);
41
+ this.db.pragma('journal_mode = WAL');
42
+ this.initialize();
43
+ }
44
+
45
+ private initialize() {
46
+ this.db.exec(CREATE_TABLES);
47
+ }
48
+
49
+ public insert(log: RequestLog): void {
50
+ const stmt = this.db.prepare(`
51
+ INSERT INTO request_logs (
52
+ id, session_id, endpoint, method,
53
+ request_headers, request_body,
54
+ response_status, response_headers, response_body,
55
+ streaming_events,
56
+ input_tokens, output_tokens,
57
+ cache_read_tokens, cache_creation_tokens,
58
+ latency_ms, first_token_ms, status_code, error_message,
59
+ model, cost_usd,
60
+ created_at
61
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
62
+ `);
63
+
64
+ stmt.run(
65
+ log.id,
66
+ log.session_id || null,
67
+ log.endpoint,
68
+ log.method,
69
+ log.request_headers ? JSON.stringify(JSON.parse(log.request_headers)) : null,
70
+ log.request_body ? JSON.stringify(JSON.parse(log.request_body)) : null,
71
+ log.response_status || null,
72
+ log.response_headers ? JSON.stringify(JSON.parse(log.response_headers)) : null,
73
+ log.response_body ? JSON.stringify(JSON.parse(log.response_body)) : null,
74
+ log.streaming_events ? JSON.stringify(JSON.parse(log.streaming_events)) : null,
75
+ log.input_tokens,
76
+ log.output_tokens,
77
+ log.cache_read_tokens,
78
+ log.cache_creation_tokens,
79
+ log.latency_ms || null,
80
+ log.first_token_ms || null,
81
+ log.status_code || null,
82
+ log.error_message || null,
83
+ log.model || null,
84
+ log.cost_usd || 0,
85
+ log.created_at
86
+ );
87
+ }
88
+
89
+ public findById(id: string): RequestLog | undefined {
90
+ const stmt = this.db.prepare('SELECT * FROM request_logs WHERE id = ?');
91
+ return stmt.get(id) as RequestLog | undefined;
92
+ }
93
+
94
+ public findAll(limit: number = 100): RequestLog[] {
95
+ const stmt = this.db.prepare(`
96
+ SELECT * FROM request_logs
97
+ ORDER BY created_at DESC
98
+ LIMIT ?
99
+ `);
100
+ return stmt.all(limit) as RequestLog[];
101
+ }
102
+
103
+ public findBySessionId(sessionId: string): RequestLog[] {
104
+ const stmt = this.db.prepare(`
105
+ SELECT * FROM request_logs
106
+ WHERE session_id = ?
107
+ ORDER BY created_at DESC
108
+ `);
109
+ return stmt.all(sessionId) as RequestLog[];
110
+ }
111
+
112
+ public getRecentRequests(limit: number = 50): RequestLog[] {
113
+ const stmt = this.db.prepare(`
114
+ SELECT * FROM request_logs
115
+ ORDER BY created_at DESC
116
+ LIMIT ?
117
+ `);
118
+ return stmt.all(limit) as RequestLog[];
119
+ }
120
+
121
+ public getTotalStats() {
122
+ const stmt = this.db.prepare(`
123
+ SELECT
124
+ COUNT(*) as total_requests,
125
+ COALESCE(SUM(input_tokens), 0) as total_input_tokens,
126
+ COALESCE(SUM(output_tokens), 0) as total_output_tokens,
127
+ COALESCE(AVG(latency_ms), 0) as avg_latency_ms
128
+ FROM request_logs
129
+ `);
130
+ return stmt.get() as {
131
+ total_requests: number;
132
+ total_input_tokens: number;
133
+ total_output_tokens: number;
134
+ avg_latency_ms: number;
135
+ };
136
+ }
137
+
138
+ public close() {
139
+ this.db.close();
140
+ }
141
+ }
package/next.config.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ // 禁用 HMR 的自动刷新,防止 WebSocket 错误时页面重新加载
5
+ reactStrictMode: false,
6
+ // 开发服务器配置 - 防止页面被 unloaded
7
+ onDemandEntries: {
8
+ // 页面在不活动后保持活跃的毫秒数(设置为最大值)
9
+ maxInactiveAge: 24 * 60 * 60 * 1000, // 24 小时
10
+ // 保持多少个页面活跃
11
+ pagesBufferLength: 10,
12
+ },
13
+ // 完全禁用 Fast Refresh
14
+ compiler: {
15
+ reactRemoveProperties: true,
16
+ },
17
+ // 空的 turbopack 配置,避免警告
18
+ turbopack: {},
19
+ // webpack 配置
20
+ webpack: (config, { dev, isServer }) => {
21
+ if (dev) {
22
+ // 禁用 HMR 插件
23
+ config.plugins = config.plugins.filter(
24
+ (plugin: any) => plugin?.constructor?.name !== 'HotModuleReplacementPlugin'
25
+ );
26
+
27
+ // 禁用所有文件监听
28
+ config.watchOptions = {
29
+ // 超长聚合延迟,等效于禁用
30
+ aggregateTimeout: 300000, // 5 分钟
31
+ // 忽略所有非源代码文件
32
+ ignored: [
33
+ '**/db/**',
34
+ '**/*.sqlite',
35
+ '**/*.sqlite-wal',
36
+ '**/*.sqlite-shm',
37
+ '**/node_modules/**',
38
+ '**/.git/**',
39
+ '**/*.log',
40
+ '**/dist/**',
41
+ '**/build/**',
42
+ '**/*.json', // 忽略 JSON 文件
43
+ ],
44
+ // 禁用轮询
45
+ poll: false,
46
+ // 完全禁用跟随符号链接
47
+ followSymlinks: false,
48
+ };
49
+
50
+ // 禁用性能监听
51
+ config.performance = {
52
+ hints: false,
53
+ };
54
+ }
55
+ return config;
56
+ },
57
+ };
58
+
59
+ export default nextConfig;
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "claude-code-inspector",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org"
7
+ },
8
+ "scripts": {
9
+ "dev": "tsx server.ts",
10
+ "build": "next build",
11
+ "start": "tsx server.ts",
12
+ "start:next": "next start",
13
+ "lint": "eslint",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest"
16
+ },
17
+ "dependencies": {
18
+ "axios": "^1.13.6",
19
+ "better-sqlite3": "^12.8.0",
20
+ "next": "16.1.6",
21
+ "react": "19.2.3",
22
+ "react-dom": "19.2.3",
23
+ "uuid": "^13.0.0",
24
+ "ws": "^8.19.0"
25
+ },
26
+ "devDependencies": {
27
+ "@tailwindcss/postcss": "^4",
28
+ "@types/better-sqlite3": "^7.6.13",
29
+ "@types/node": "^20",
30
+ "@types/react": "^19",
31
+ "@types/react-dom": "^19",
32
+ "@types/uuid": "^10.0.0",
33
+ "@types/ws": "^8.18.1",
34
+ "eslint": "^9",
35
+ "eslint-config-next": "16.1.6",
36
+ "tailwindcss": "^4",
37
+ "ts-node": "^10.9.2",
38
+ "tsx": "^4.21.0",
39
+ "typescript": "^5",
40
+ "vitest": "^4.0.18"
41
+ }
42
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
package/server.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { createServer } from 'http';
2
+ import next from 'next';
3
+ import { WebSocketServer } from 'ws';
4
+ import { parse } from 'url';
5
+ import { setWssInstance } from './lib/proxy/ws-server';
6
+ import { initEnv } from './lib/env';
7
+
8
+ const port = parseInt(process.env.PORT || '3000', 10);
9
+ const dev = process.env.NODE_ENV !== 'production';
10
+
11
+ // 初始化环境变量
12
+ initEnv();
13
+
14
+ // 禁用 Turbopack,使用 Webpack
15
+ if (dev) {
16
+ // 正确禁用 Turbopack:删除环境变量或使用空字符串
17
+ // 注意:'0' 在 JavaScript 中是 truthy,会导致 if (process.env.TURBOPACK) 判断为 true
18
+ delete process.env.TURBOPACK;
19
+ }
20
+
21
+ const app = next({ dev });
22
+ const handle = app.getRequestHandler();
23
+
24
+ app.prepare().then(() => {
25
+ const upgradeHandler = app.getUpgradeHandler();
26
+ const server = createServer((req, res) => {
27
+ const parsedUrl = parse(req.url!, true);
28
+ handle(req, res, parsedUrl);
29
+ });
30
+
31
+ // 注册 WebSocket upgrade 处理器,用于处理 Next.js HMR 连接
32
+ server.on('upgrade', async (req, socket, head) => {
33
+ console.log('[SERVER] Upgrade request received for URL:', req.url);
34
+ try {
35
+ await upgradeHandler(req, socket, head);
36
+ console.log('[SERVER] Upgrade handler completed successfully');
37
+ } catch (error) {
38
+ console.error('[SERVER] Upgrade handler error:', error);
39
+ }
40
+ });
41
+
42
+ // WebSocket 服务器
43
+ const wss = new WebSocketServer({ server, path: '/api/ws' });
44
+
45
+ // 注册 WebSocket 服务器实例
46
+ setWssInstance(wss);
47
+
48
+ wss.on('connection', (ws) => {
49
+ console.log('Client connected. Total clients:', wss.clients.size);
50
+
51
+ ws.on('close', () => {
52
+ console.log('Client disconnected. Total clients:', wss.clients.size);
53
+ });
54
+
55
+ ws.on('error', (error) => {
56
+ console.error('WebSocket error:', error);
57
+ });
58
+ });
59
+
60
+ server.listen(port, () => {
61
+ console.log(`> Ready on http://localhost:${port}`);
62
+ console.log(`> WebSocket available on ws://localhost:${port}/api/ws`);
63
+ });
64
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "isolatedModules": false,
7
+ "noEmit": false,
8
+ "outDir": ".next/server"
9
+ },
10
+ "include": ["server.ts"]
11
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import path from 'path';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ environment: 'node',
8
+ },
9
+ resolve: {
10
+ alias: {
11
+ '@': path.resolve(__dirname, './'),
12
+ },
13
+ },
14
+ });