cc-viewer 1.4.32 → 1.5.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/dist/index.html CHANGED
@@ -6,8 +6,8 @@
6
6
  <title>Claude Code Viewer</title>
7
7
  <link rel="icon" href="/favicon.ico?v=1">
8
8
  <link rel="shortcut icon" href="/favicon.ico?v=1">
9
- <script type="module" crossorigin src="/assets/index-DkC5DsL2.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-DmLf0HHi.css">
9
+ <script type="module" crossorigin src="/assets/index-hs4mCtCl.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-DaYrU_Go.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
package/i18n.js CHANGED
@@ -121,26 +121,6 @@ const i18nData = {
121
121
  "tr": " @anthropic-ai/claude-code'un kurulu olduğundan emin olun",
122
122
  "uk": " Переконайтеся, що @anthropic-ai/claude-code встановлено"
123
123
  },
124
- "cli.hook.exists": {
125
- "zh": "自动修复 hook 已存在于 {path}",
126
- "en": "Auto-repair hook already exists in {path}",
127
- "zh-TW": "自動修復 hook 已存在於 {path}",
128
- "ko": "자동 복구 hook이 이미 {path}에 존재함",
129
- "ja": "自動修復 hook は既に {path} に存在します",
130
- "de": "Auto-Reparatur-Hook existiert bereits in {path}",
131
- "es": "Hook de auto-reparación ya existe en {path}",
132
- "fr": "Hook de réparation auto déjà présent dans {path}",
133
- "it": "Hook di auto-riparazione già presente in {path}",
134
- "da": "Auto-reparations-hook findes allerede i {path}",
135
- "pl": "Hook auto-naprawy już istnieje w {path}",
136
- "ru": "Hook автовосстановления уже существует в {path}",
137
- "ar": "hook الإصلاح التلقائي موجود بالفعل في {path}",
138
- "no": "Auto-reparasjons-hook finnes allerede i {path}",
139
- "pt-BR": "Hook de auto-reparo já existe em {path}",
140
- "th": "hook ซ่อมอัตโนมัติมีอยู่แล้วใน {path}",
141
- "tr": "Otomatik onarım hook'u zaten {path} dosyasında mevcut",
142
- "uk": "Hook автовідновлення вже існує в {path}"
143
- },
144
124
  "cli.hook.fail": {
145
125
  "zh": "⚠️ shell hook 写入失败: {error}",
146
126
  "en": "⚠️ Failed to write shell hook: {error}",
@@ -226,26 +206,6 @@ const i18nData = {
226
206
  "tr": "CC Viewer CLI modu başlatılıyor...",
227
207
  "uk": "Запуск режиму CLI CC Viewer..."
228
208
  },
229
- "cli.usage.uninstallHint": {
230
- "zh": "",
231
- "en": "",
232
- "zh-TW": "",
233
- "ko": "",
234
- "ja": "",
235
- "de": "",
236
- "es": "",
237
- "fr": "",
238
- "it": "",
239
- "da": "",
240
- "pl": "",
241
- "ru": "",
242
- "ar": "",
243
- "no": "",
244
- "pt-BR": "",
245
- "th": "",
246
- "tr": "",
247
- "uk": ""
248
- },
249
209
  "cli.uninstall.cliCleaned": {
250
210
  "zh": "✅ cli.js 已清理",
251
211
  "en": "✅ cli.js cleaned",
@@ -426,26 +386,6 @@ const i18nData = {
426
386
  "tr": "\nCC Viewer başlatıldı: http://{host}:{port}\n",
427
387
  "uk": "\nCC Viewer запущено: http://{host}:{port}\n"
428
388
  },
429
- "server.reuse": {
430
- "zh": "\nCC Viewer 已在运行: http://{host}:{port}\n",
431
- "en": "\nCC Viewer already running: http://{host}:{port}\n",
432
- "zh-TW": "\nCC Viewer 已在運行: http://{host}:{port}\n",
433
- "ko": "\nCC Viewer 이미 실행 중: http://{host}:{port}\n",
434
- "ja": "\nCC Viewer は既に実行中: http://{host}:{port}\n",
435
- "de": "\nCC Viewer läuft bereits: http://{host}:{port}\n",
436
- "es": "\nCC Viewer ya en ejecución: http://{host}:{port}\n",
437
- "fr": "\nCC Viewer déjà en cours d'exécution : http://{host}:{port}\n",
438
- "it": "\nCC Viewer già in esecuzione: http://{host}:{port}\n",
439
- "da": "\nCC Viewer kører allerede: http://{host}:{port}\n",
440
- "pl": "\nCC Viewer już działa: http://{host}:{port}\n",
441
- "ru": "\nCC Viewer уже запущен: http://{host}:{port}\n",
442
- "ar": "\nCC Viewer يعمل بالفعل: http://{host}:{port}\n",
443
- "no": "\nCC Viewer kjører allerede: http://{host}:{port}\n",
444
- "pt-BR": "\nCC Viewer já em execução: http://{host}:{port}\n",
445
- "th": "\nCC Viewer กำลังทำงานอยู่แล้ว: http://{host}:{port}\n",
446
- "tr": "\nCC Viewer zaten çalışıyor: http://{host}:{port}\n",
447
- "uk": "\nCC Viewer вже працює: http://{host}:{port}\n"
448
- },
449
389
  "update.updating": {
450
390
  "zh": "正在更新到 v{version}...",
451
391
  "en": "Updating to v{version}...",
@@ -1458,66 +1398,6 @@ const i18nData = {
1458
1398
  "tr": "Uzun tam metinleri okumaktan kaçınmak için yalnızca MainAgent'ın artımlı kısımları gösterilir — genellikle yalnızca yeni mesajlar",
1459
1399
  "uk": "Щоб не читати довгий повний текст, відображаються лише інкрементальні частини MainAgent — зазвичай лише нові повідомлення"
1460
1400
  },
1461
- "cli.resume.prompt": {
1462
- "zh": "检测到1小时内的日志: {file}",
1463
- "en": "Recent log found (within 1 hour): {file}",
1464
- "zh-TW": "偵測到1小時內的日誌: {file}",
1465
- "ko": "1시간 이내 로그 감지: {file}",
1466
- "ja": "1時間以内のログを検出: {file}",
1467
- "de": "Kürzliches Log gefunden (innerhalb 1 Stunde): {file}",
1468
- "es": "Log reciente encontrado (dentro de 1 hora): {file}",
1469
- "fr": "Log récent trouvé (moins d'1 heure): {file}",
1470
- "it": "Log recente trovato (entro 1 ora): {file}",
1471
- "da": "Nylig log fundet (inden for 1 time): {file}",
1472
- "pl": "Znaleziono ostatni log (w ciągu 1 godziny): {file}",
1473
- "ru": "Обнаружен недавний лог (менее 1 часа): {file}",
1474
- "ar": "تم العثور على سجل حديث (خلال ساعة واحدة): {file}",
1475
- "no": "Nylig logg funnet (innen 1 time): {file}",
1476
- "pt-BR": "Log recente encontrado (dentro de 1 hora): {file}",
1477
- "th": "พบล็อกล่าสุด (ภายใน 1 ชั่วโมง): {file}",
1478
- "tr": "Son günlük bulundu (1 saat içinde): {file}",
1479
- "uk": "Знайдено нещодавній лог (менше 1 години): {file}"
1480
- },
1481
- "cli.resume.continue": {
1482
- "zh": "继续",
1483
- "en": "Continue",
1484
- "zh-TW": "繼續",
1485
- "ko": "계속",
1486
- "ja": "続行",
1487
- "de": "Fortsetzen",
1488
- "es": "Continuar",
1489
- "fr": "Continuer",
1490
- "it": "Continua",
1491
- "da": "Fortsæt",
1492
- "pl": "Kontynuuj",
1493
- "ru": "Продолжить",
1494
- "ar": "متابعة",
1495
- "no": "Fortsett",
1496
- "pt-BR": "Continuar",
1497
- "th": "ดำเนินการต่อ",
1498
- "tr": "Devam",
1499
- "uk": "Продовжити"
1500
- },
1501
- "cli.resume.new": {
1502
- "zh": "新开",
1503
- "en": "New",
1504
- "zh-TW": "新開",
1505
- "ko": "새로 시작",
1506
- "ja": "新規",
1507
- "de": "Neu",
1508
- "es": "Nuevo",
1509
- "fr": "Nouveau",
1510
- "it": "Nuovo",
1511
- "da": "Ny",
1512
- "pl": "Nowy",
1513
- "ru": "Новый",
1514
- "ar": "جديد",
1515
- "no": "Ny",
1516
- "pt-BR": "Novo",
1517
- "th": "ใหม่",
1518
- "tr": "Yeni",
1519
- "uk": "Новий"
1520
- },
1521
1401
  "ui.resume.title": {
1522
1402
  "zh": "检测到近期日志",
1523
1403
  "en": "Recent Log Detected",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-viewer",
3
- "version": "1.4.32",
3
+ "version": "1.5.0",
4
4
  "description": "Claude Code Logger visualization management tool",
5
5
  "license": "MIT",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -5,8 +5,33 @@ import { readFileSync, writeFileSync, existsSync, watchFile, unwatchFile, statSy
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { dirname, join, extname } from 'node:path';
7
7
  import { homedir, userInfo, platform, networkInterfaces } from 'node:os';
8
- import { execSync } from 'node:child_process';
8
+ import { execFile, exec, spawn } from 'node:child_process';
9
+ import { promisify } from 'node:util';
9
10
  import { Worker } from 'node:worker_threads';
11
+
12
+ const execFileAsync = promisify(execFile);
13
+ const execAsync = promisify(exec);
14
+
15
+ // execFile with stdin input support (for git check-ignore --stdin)
16
+ function execWithStdin(cmd, args, input, options) {
17
+ return new Promise((resolve, reject) => {
18
+ const child = spawn(cmd, args, { ...options, stdio: ['pipe', 'pipe', 'pipe'] });
19
+ let stdout = '';
20
+ let stderr = '';
21
+ child.stdout.on('data', d => { stdout += d; });
22
+ child.stderr.on('data', d => { stderr += d; });
23
+ child.on('error', reject);
24
+ child.on('close', code => {
25
+ // git check-ignore exits 1 when no files are ignored — treat as success
26
+ resolve(stdout);
27
+ });
28
+ if (options?.timeout) {
29
+ setTimeout(() => { try { child.kill(); } catch {} reject(new Error('timeout')); }, options.timeout);
30
+ }
31
+ child.stdin.write(input);
32
+ child.stdin.end();
33
+ });
34
+ }
10
35
  import { LOG_FILE, _initPromise, _resumeState, resolveResumeChoice, _projectName, _logDir, _cachedApiKey, _cachedAuthHeader, _cachedHaikuModel, initForWorkspace, resetWorkspace } from './interceptor.js';
11
36
  import { LOG_DIR } from './findcc.js';
12
37
  import { t, detectLanguage } from './i18n.js';
@@ -39,8 +64,16 @@ export function setWorkspaceClaudePath(path, isNpm) {
39
64
 
40
65
  // macOS user profile (avatar + display name), cached once
41
66
  let _userProfile = null;
42
- function getUserProfile() {
67
+ let _userProfilePromise = null;
68
+ async function getUserProfile() {
43
69
  if (_userProfile) return _userProfile;
70
+ if (_userProfilePromise) return _userProfilePromise;
71
+ _userProfilePromise = _getUserProfileImpl();
72
+ _userProfile = await _userProfilePromise;
73
+ _userProfilePromise = null;
74
+ return _userProfile;
75
+ }
76
+ async function _getUserProfileImpl() {
44
77
  const info = userInfo();
45
78
  const name = info.username || 'User';
46
79
  let displayName = name;
@@ -48,21 +81,20 @@ function getUserProfile() {
48
81
 
49
82
  if (platform() === 'darwin') {
50
83
  try {
51
- const rn = execSync(`dscl . -read /Users/${name} RealName`, { encoding: 'utf-8', timeout: 3000 });
84
+ const { stdout: rn } = await execFileAsync('dscl', ['.', '-read', `/Users/${name}`, 'RealName'], { encoding: 'utf-8', timeout: 3000 });
52
85
  const match = rn.match(/RealName:\n?\s*(.+)/);
53
86
  if (match && match[1].trim()) displayName = match[1].trim();
54
87
  } catch { }
55
88
 
56
89
  try {
57
- const buf = execSync(`dscl . -read /Users/${name} JPEGPhoto | tail -1 | xxd -r -p`, { timeout: 5000, maxBuffer: 1024 * 1024 });
58
- if (buf && buf.length > 100) {
59
- avatarBase64 = `data:image/jpeg;base64,${buf.toString('base64')}`;
90
+ const { stdout } = await execAsync(`dscl . -read /Users/${name} JPEGPhoto | tail -1 | xxd -r -p`, { timeout: 5000, maxBuffer: 1024 * 1024, encoding: 'buffer' });
91
+ if (stdout && stdout.length > 100) {
92
+ avatarBase64 = `data:image/jpeg;base64,${stdout.toString('base64')}`;
60
93
  }
61
94
  } catch { }
62
95
  }
63
96
 
64
- _userProfile = { name: displayName, avatar: avatarBase64 };
65
- return _userProfile;
97
+ return { name: displayName, avatar: avatarBase64 };
66
98
  }
67
99
 
68
100
  const __filename = fileURLToPath(import.meta.url);
@@ -720,7 +752,7 @@ async function handleRequest(req, res) {
720
752
 
721
753
  // macOS 用户头像和显示名
722
754
  if (url === '/api/user-profile' && method === 'GET') {
723
- const profile = getUserProfile();
755
+ const profile = await getUserProfile();
724
756
  res.writeHead(200, { 'Content-Type': 'application/json' });
725
757
  res.end(JSON.stringify(profile));
726
758
  return;
@@ -754,12 +786,9 @@ async function handleRequest(req, res) {
754
786
  return i.type === 'directory' ? `${rel}/` : rel;
755
787
  });
756
788
  if (names.length > 0) {
757
- const result = execSync(`git check-ignore --stdin`, {
789
+ const result = await execWithStdin('git', ['check-ignore', '--stdin'], names.join('\n'), {
758
790
  cwd,
759
- input: names.join('\n'),
760
- encoding: 'utf-8',
761
791
  timeout: 3000,
762
- stdio: ['pipe', 'pipe', 'pipe'],
763
792
  });
764
793
  result.split('\n').filter(Boolean).forEach(line => {
765
794
  const name = line.endsWith('/') ? line.slice(0, -1) : line;
@@ -827,7 +856,7 @@ async function handleRequest(req, res) {
827
856
  if (url === '/api/git-status' && method === 'GET') {
828
857
  try {
829
858
  const cwd = process.env.CCV_PROJECT_DIR || process.cwd();
830
- const output = execSync('git status --porcelain', { cwd, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
859
+ const { stdout: output } = await execFileAsync('git', ['status', '--porcelain'], { cwd, encoding: 'utf-8', timeout: 5000 });
831
860
  const lines = output.split('\n').filter(line => line.trim());
832
861
  const changes = lines.map(line => {
833
862
  const status = line.substring(0, 2).trim();
@@ -863,7 +892,7 @@ async function handleRequest(req, res) {
863
892
  if (file.includes('..') || file.startsWith('/')) continue;
864
893
 
865
894
  try {
866
- const statusOutput = execSync(`git status --porcelain -- "${file}"`, { cwd, encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
895
+ const { stdout: statusOutput } = await execFileAsync('git', ['status', '--porcelain', '--', file], { cwd, encoding: 'utf-8', timeout: 3000 });
867
896
  if (!statusOutput.trim()) continue;
868
897
 
869
898
  const status = statusOutput.substring(0, 2).trim();
@@ -874,7 +903,7 @@ async function handleRequest(req, res) {
874
903
  let is_binary = false;
875
904
  if (!is_deleted) {
876
905
  try {
877
- const diffCheck = execSync(`git diff --numstat HEAD -- "${file}"`, { cwd, encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
906
+ const { stdout: diffCheck } = await execFileAsync('git', ['diff', '--numstat', 'HEAD', '--', file], { cwd, encoding: 'utf-8', timeout: 3000 });
878
907
  if (diffCheck.includes('-\t-\t')) {
879
908
  is_binary = true;
880
909
  }
@@ -888,7 +917,8 @@ async function handleRequest(req, res) {
888
917
  // 获取旧内容(HEAD 版本)
889
918
  if (!is_new) {
890
919
  try {
891
- old_content = execSync(`git show HEAD:"${file}"`, { cwd, encoding: 'utf-8', timeout: 5000, maxBuffer: 5 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
920
+ const { stdout } = await execFileAsync('git', ['show', `HEAD:${file}`], { cwd, encoding: 'utf-8', timeout: 5000, maxBuffer: 5 * 1024 * 1024 });
921
+ old_content = stdout;
892
922
  } catch {
893
923
  old_content = '';
894
924
  }
@@ -1288,7 +1318,7 @@ export async function startViewer() {
1288
1318
  const [maj, min, pat] = ccVer.split('.').map(Number);
1289
1319
  if (maj < 2 || (maj === 2 && min === 0 && pat < 69)) {
1290
1320
  const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open';
1291
- execSync(`${cmd} ${url}`, { stdio: 'ignore', timeout: 5000 });
1321
+ execAsync(`${cmd} ${url}`, { timeout: 5000 }).catch(() => {});
1292
1322
  }
1293
1323
  } catch { }
1294
1324
  // 工作区模式下延迟到选择工作区后再启动监听