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/assets/{index-DmLf0HHi.css → index-DaYrU_Go.css} +1 -1
- package/dist/assets/{index-DkC5DsL2.js → index-hs4mCtCl.js} +145 -289
- package/dist/index.html +2 -2
- package/i18n.js +0 -120
- package/package.json +1 -1
- package/server.js +48 -18
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-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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
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 {
|
|
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
|
-
|
|
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 =
|
|
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
|
|
58
|
-
if (
|
|
59
|
-
avatarBase64 = `data:image/jpeg;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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1321
|
+
execAsync(`${cmd} ${url}`, { timeout: 5000 }).catch(() => {});
|
|
1292
1322
|
}
|
|
1293
1323
|
} catch { }
|
|
1294
1324
|
// 工作区模式下延迟到选择工作区后再启动监听
|