cc-viewer 1.7.2 → 1.7.4
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/cli.js +109 -20
- package/dist/assets/App-CKvCavmF.js +2 -0
- package/dist/assets/App-Dj37fluO.css +1 -0
- package/dist/assets/{MdxEditorPanel-BUubiE6Y.js → MdxEditorPanel-DeSJp_ct.js} +1 -1
- package/dist/assets/{Mobile-CKmfRRvo.js → Mobile-CWou-jtF.js} +1 -1
- package/dist/assets/{_baseUniq-D9_E5ZDp.js → _baseUniq-BK7Nhp_x.js} +1 -1
- package/dist/assets/{arc-D4iFrlhb.js → arc-Cm4HMXz6.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-f1neo7hg.js → architectureDiagram-Q4EWVU46-Co8QCowN.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-DWi9e9tG.js → blockDiagram-DXYQGD6D-Cudecf7x.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-DAdj49A2.js → c4Diagram-AHTNJAMY-4JeFoCHX.js} +1 -1
- package/dist/assets/{channel-B3VthT0m.js → channel-K7eT7Hjr.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-BrPcAHV3.js → chunk-4BX2VUAB-BBjl6EVW.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-Bvopc1V9.js → chunk-4TB4RGXK-DBP9YnwO.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Juw-7Z3i.js → chunk-55IACEB6--A_i9gwR.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-DVYP9tpB.js → chunk-EDXVE4YY-UBsL-3qr.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-VQ7VOXl3.js → chunk-FMBD7UC4-DBO0asmI.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-F8Rj4ZUZ.js → chunk-OYMX7WX6-DOCcEaE6.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Dn7K3yi9.js → chunk-QZHKN3VN-B-j8QbQh.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-UjO-KTsn.js → chunk-YZCP3GAM-B0WO1U-U.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Ba3tOmih.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-Ba3tOmih.js +1 -0
- package/dist/assets/clone-DjURvTeN.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BDhNj8rq.js → cose-bilkent-S5V4N54A-DQgpss_a.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-BzdcDuyt.js → dagre-KV5264BT-CCJBKmKv.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-DP_SX65N.js → diagram-5BDNPKRD-DgUcnfkU.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-B-pBFw8Q.js → diagram-G4DWMVQ6-CRpVvexw.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-6XSfN1eN.js → diagram-MMDJMWI5-C3IhpsM-.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-DJvGxvpE.js → diagram-TYMM5635-BaVbsrwt.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-CpqdpGYj.js → erDiagram-SMLLAGMA-BxRoBUR8.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-C9m7wjwI.js → flowDiagram-DWJPFMVM-Du2lIzoM.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-CU0XOfY3.js → ganttDiagram-T4ZO3ILL-DR1pDqLK.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-ipyGULnP.js → gitGraphDiagram-UUTBAWPF-DHAmE20O.js} +1 -1
- package/dist/assets/{graph-BR2KmES9.js → graph-D8Yv0IU1.js} +1 -1
- package/dist/assets/{index-C-be29ey.js → index-AOmFG_I_.js} +1 -1
- package/dist/assets/{index-D5tSeMU7.js → index-B6ZWIxY0.js} +1 -1
- package/dist/assets/{index-D5YruOKD.js → index-Bq95HjYM.js} +1 -1
- package/dist/assets/{index--wgRW_OB.js → index-D0pvxpcl.js} +1 -1
- package/dist/assets/{index-DuZQ6FqR.js → index-D2c77__8.js} +1 -1
- package/dist/assets/{index-C16fiVhv.js → index-DgtUyP57.js} +1 -1
- package/dist/assets/{index-CjsQEQc2.js → index-Dpu92rw_.js} +2 -2
- package/dist/assets/{index-OWzSX2T8.js → index-cCB-lhEO.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-BQWQjfFX.js → infoDiagram-42DDH7IO-BAqRIm2G.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-Df-KH_Hz.js → ishikawaDiagram-UXIWVN3A-CzH4tezb.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-CMbB_4G9.js → journeyDiagram-VCZTEJTY-Qs4eiyuI.js} +1 -1
- package/dist/assets/{jszip.min-B6k2t9Ch.js → jszip.min-C0OccDsk.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-CImdlKN3.js → kanban-definition-6JOO6SKY-BAjl0gpV.js} +1 -1
- package/dist/assets/{layout-DQl4aw09.js → layout-Cf6cd9xE.js} +1 -1
- package/dist/assets/{linear-CLD3aPlK.js → linear-PzdCkji6.js} +1 -1
- package/dist/assets/mermaid.core-DV9745Lq.js +7 -0
- package/dist/assets/{min-_D86henJ.js → min-CHgWxbzS.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-2JVcu1nk.js → mindmap-definition-QFDTVHPH-CHUjMdpi.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-Dik-m5u7.js → pieDiagram-DEJITSTG-D_r2QC0f.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-Bm5l40Jq.js → quadrantDiagram-34T5L4WZ-DPzGygdN.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-Ck9w5rTj.js → requirementDiagram-MS252O5E-BA2ynnGb.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-BbGVCFEf.js → sankeyDiagram-XADWPNL6-XHVvXuqU.js} +1 -1
- package/dist/assets/{seqResourceLoaders-DFrHmgnK.css → seqResourceLoaders-0RXZfUKp.css} +1 -1
- package/dist/assets/seqResourceLoaders-kBEzcKLh.js +2 -0
- package/dist/assets/{sequenceDiagram-FGHM5R23-vjvEdksk.js → sequenceDiagram-FGHM5R23-C47sA8YH.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-DTqMoiB2.js → stateDiagram-FHFEXIEX-BOmer839.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-CNbAwnhu.js → stateDiagram-v2-QKLJ7IA2-D-kVvTHA.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-Cw8O-52d.js → timeline-definition-GMOUNBTQ-CTAKVZgs.js} +1 -1
- package/dist/assets/{vendor-antd-DI7JL-mE.js → vendor-antd-BuJ6oz45.js} +2 -2
- package/dist/assets/{vendor-codemirror-B9c49dtM.js → vendor-codemirror-D2KAW4ty.js} +1 -1
- package/dist/assets/{vendor-mdxeditor-B6cpBtIE.js → vendor-mdxeditor-BsFVVUcj.js} +2 -2
- package/dist/assets/{vendor-qrcode-Dn90-s-u.js → vendor-qrcode-BpxSq04x.js} +1 -1
- package/dist/assets/{vendor-virtuoso-CpdSKD_6.js → vendor-virtuoso-an4WvAvS.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-DCuqymb7.js → vennDiagram-DHZGUBPP-CuXzoroh.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-BJ20wJUU.js → wardley-RL74JXVD-CFCmxnck.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-Vz6US3qt.js → wardleyDiagram-NUSXRM2D-gfhDB9SR.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-CzkLKrmi.js → xychartDiagram-5P7HB3ND-X-pj66qI.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/server/i18n.js +120 -55
- package/server/interceptor.js +72 -2
- package/server/lib/ccswitch-import.js +251 -0
- package/server/lib/ensure-hooks.js +28 -2
- package/server/lib/log-watcher.js +8 -0
- package/server/lib/pid-alive.js +15 -0
- package/server/lib/proxy-retry.js +716 -0
- package/server/lib/session-start-bridge.js +121 -0
- package/server/lib/settings-merge.js +170 -0
- package/server/lib/v2/live-feed.js +189 -48
- package/server/lib/v2/session-owner.js +135 -0
- package/server/lib/v2/session-select.js +15 -2
- package/server/lib/v2/v2-writer.js +181 -6
- package/server/proxy.js +115 -11
- package/server/pty-manager.js +28 -4
- package/server/routes/events.js +44 -0
- package/server/routes/preferences.js +184 -1
- package/server/server.js +15 -1
- package/src/utils/contentFilter.js +17 -1
- package/dist/assets/App-B224r-8M.js +0 -2
- package/dist/assets/App-dYPa5-df.css +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-DmSozxoO.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-DmSozxoO.js +0 -1
- package/dist/assets/clone-BQtvy9Ae.js +0 -1
- package/dist/assets/mermaid.core-CN35dvKJ.js +0 -7
- package/dist/assets/seqResourceLoaders-C-T_EMiO.js +0 -2
package/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ if (process.platform === 'win32' && !process.env.UV_THREADPOOL_SIZE) {
|
|
|
5
5
|
process.env.UV_THREADPOOL_SIZE = '16';
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
import { readFileSync, writeFileSync, existsSync, realpathSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, realpathSync, unlinkSync, mkdirSync, statSync } from 'node:fs';
|
|
9
9
|
import { resolve, isAbsolute } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
@@ -15,6 +15,7 @@ import { INJECT_IMPORT, LEGACY_INJECT_IMPORTS, resolveCliPath, resolveNativePath
|
|
|
15
15
|
import { ensureHooks, removeAllManagedHooks } from './server/lib/ensure-hooks.js';
|
|
16
16
|
import { injectCliJsAt, removeCliJsInjectionAt, INJECT_START as _INJECT_START, INJECT_END as _INJECT_END, buildInjectBlock as _buildInjectBlock } from './server/lib/cli-inject.js';
|
|
17
17
|
import { normalizeBasePath } from './server/lib/base-path.js';
|
|
18
|
+
import { mergeSettingsIntoArgs } from './server/lib/settings-merge.js';
|
|
18
19
|
import { createHardenedCleanup, installWinKeypressFallback } from './server/lib/term-signals.js';
|
|
19
20
|
|
|
20
21
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
@@ -59,6 +60,14 @@ function reportClaudeNotFound(cliPathHint) {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
// Tombstone written by --uninstall; blocks the shell hook's background self-heal
|
|
64
|
+
// (`ccv -logger --self-heal`) from silently re-installing the integration.
|
|
65
|
+
// Anchored to the config dir (not LOG_DIR, which honors --log-dir/CCV_LOG_DIR
|
|
66
|
+
// overrides) so --uninstall and the hook always resolve the same path.
|
|
67
|
+
function getUninstallFlagPath() {
|
|
68
|
+
return resolve(getClaudeConfigDir(), 'cc-viewer', 'uninstalled.flag');
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
function getShellConfigPath() {
|
|
63
72
|
const shell = process.env.SHELL || '';
|
|
64
73
|
if (shell.includes('zsh')) return resolve(homedir(), '.zshrc');
|
|
@@ -93,6 +102,12 @@ function buildShellHook(isNative) {
|
|
|
93
102
|
'--help', '-h',
|
|
94
103
|
];
|
|
95
104
|
|
|
105
|
+
// Guard shared by both variants: if ccv was uninstalled, the wrapper removes
|
|
106
|
+
// itself from the live shell and falls back to the real claude binary.
|
|
107
|
+
// `hash -r` first — otherwise a stale command-hash entry in a long-lived
|
|
108
|
+
// shell lets `command -v ccv` false-pass once after the binary is deleted.
|
|
109
|
+
const ccvGoneGuard = `hash -r 2>/dev/null; command -v ccv >/dev/null 2>&1 || { unset -f claude; command claude "$@"; return; }`;
|
|
110
|
+
|
|
96
111
|
if (isNative) {
|
|
97
112
|
return `${SHELL_HOOK_START}
|
|
98
113
|
claude() {
|
|
@@ -102,6 +117,8 @@ claude() {
|
|
|
102
117
|
command claude "$@"
|
|
103
118
|
return
|
|
104
119
|
fi
|
|
120
|
+
# If ccv is gone (uninstalled), drop this stale wrapper and use claude directly
|
|
121
|
+
${ccvGoneGuard}
|
|
105
122
|
# Pass through certain commands directly without ccv interception
|
|
106
123
|
case "$1" in
|
|
107
124
|
${passthroughCommands.join('|')})
|
|
@@ -127,6 +144,8 @@ claude() {
|
|
|
127
144
|
command claude "$@"
|
|
128
145
|
return
|
|
129
146
|
fi
|
|
147
|
+
# If ccv is gone (uninstalled), drop this stale wrapper and use claude directly
|
|
148
|
+
${ccvGoneGuard}
|
|
130
149
|
# Pass through certain commands directly without ccv interception
|
|
131
150
|
case "$1" in
|
|
132
151
|
${passthroughCommands.join('|')})
|
|
@@ -148,12 +167,12 @@ claude() {
|
|
|
148
167
|
if [ -z "$cli_js" ]; then
|
|
149
168
|
# cli.js 消失 → Claude Code 已升级到 2.1.114+(native-only 分发)。
|
|
150
169
|
# 后台重写 hook(下次 shell 就是 native hook),当前调用直接走 native proxy 路径。
|
|
151
|
-
( ccv -logger >/dev/null 2>&1 & )
|
|
170
|
+
( ccv -logger --self-heal >/dev/null 2>&1 & )
|
|
152
171
|
ccv run -- claude --ccv-internal "$@"
|
|
153
172
|
return $?
|
|
154
173
|
fi
|
|
155
174
|
if ! grep -q "CC Viewer" "$cli_js" 2>/dev/null; then
|
|
156
|
-
ccv -logger 2>/dev/null
|
|
175
|
+
ccv -logger --self-heal 2>/dev/null
|
|
157
176
|
fi
|
|
158
177
|
command claude "$@"
|
|
159
178
|
}
|
|
@@ -195,7 +214,8 @@ function removeShellHook() {
|
|
|
195
214
|
for (const f of ['.zshrc', '.zprofile', '.bashrc', '.bash_profile', '.profile']) {
|
|
196
215
|
allPaths.add(resolve(home, f));
|
|
197
216
|
}
|
|
198
|
-
|
|
217
|
+
// Per-file results; empty array = no file contained the hook marker
|
|
218
|
+
const results = [];
|
|
199
219
|
for (const p of allPaths) {
|
|
200
220
|
try {
|
|
201
221
|
if (!existsSync(p)) continue;
|
|
@@ -203,13 +223,19 @@ function removeShellHook() {
|
|
|
203
223
|
if (!content.includes(SHELL_HOOK_START)) continue;
|
|
204
224
|
const regex = new RegExp(`\\n?${SHELL_HOOK_START}[\\s\\S]*?${SHELL_HOOK_END}\\n?`, 'g');
|
|
205
225
|
const newContent = content.replace(regex, '\n');
|
|
226
|
+
if (newContent === content) {
|
|
227
|
+
// START marker present but the block is malformed (e.g. END marker damaged):
|
|
228
|
+
// leave the file untouched instead of reporting a removal that didn't happen
|
|
229
|
+
results.push({ file: p, status: 'corrupt' });
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
206
232
|
writeFileSync(p, newContent);
|
|
207
|
-
|
|
233
|
+
results.push({ file: p, status: 'removed' });
|
|
208
234
|
} catch (err) {
|
|
209
|
-
|
|
235
|
+
results.push({ file: p, status: 'error', error: err.message });
|
|
210
236
|
}
|
|
211
237
|
}
|
|
212
|
-
return
|
|
238
|
+
return results;
|
|
213
239
|
}
|
|
214
240
|
|
|
215
241
|
function injectCliJs() {
|
|
@@ -272,17 +298,34 @@ async function runProxyCommand(args) {
|
|
|
272
298
|
// 剥离 cc-viewer 的内部短路开关,避免泄漏给 claude 子进程
|
|
273
299
|
delete env.CCV_SKIP_THINKING_DISPLAY;
|
|
274
300
|
|
|
275
|
-
const
|
|
301
|
+
const settingsObj = {
|
|
276
302
|
env: {
|
|
277
303
|
ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL
|
|
278
304
|
}
|
|
279
|
-
}
|
|
305
|
+
};
|
|
306
|
+
let settingsJson = JSON.stringify(settingsObj);
|
|
307
|
+
|
|
308
|
+
const isClaudeCmd = cmd === 'claude' || /[\\/]claude(\.exe)?$/.test(cmd);
|
|
309
|
+
|
|
310
|
+
// Fold a user-supplied --settings into the injected settings and emit a single flag
|
|
311
|
+
// (claude is last-wins for duplicate --settings; two parallel flags let the user's
|
|
312
|
+
// silently clobber the injected ANTHROPIC_BASE_URL and break capture). claude only:
|
|
313
|
+
// another tool's --settings belongs to that tool and must not be touched.
|
|
314
|
+
// Runs on the RAW cmdArgs BEFORE the --thinking-display append below: otherwise a
|
|
315
|
+
// trailing valueless user --settings would consume our appended token as its value.
|
|
316
|
+
if (isClaudeCmd) {
|
|
317
|
+
const settingsMerge = mergeSettingsIntoArgs(cmdArgs, settingsObj, { cwd: process.cwd() });
|
|
318
|
+
if (settingsMerge.warningDetail) {
|
|
319
|
+
console.warn(t('cli.settingsMergeFailed', settingsMerge.warningDetail));
|
|
320
|
+
}
|
|
321
|
+
settingsJson = settingsMerge.settingsJson;
|
|
322
|
+
cmdArgs = settingsMerge.args;
|
|
323
|
+
}
|
|
280
324
|
|
|
281
325
|
// 注入默认 --thinking-display summarized,仅对 claude 二进制(其他命令如 `ccv run -- sometool` 跳过)。
|
|
282
326
|
// 若 claude 不识别该 flag(老版本/fork)会 unknown option 崩溃——由 pty-manager.js::spawnClaude 的
|
|
283
327
|
// onExit reactive retry 兜底;cli.js 这条路径是一次性子进程,没有 respawn 机会,用户需手动重试。
|
|
284
328
|
// 可通过环境变量 CCV_SKIP_THINKING_DISPLAY=1 强制跳过。
|
|
285
|
-
const isClaudeCmd = cmd === 'claude' || /[\\/]claude(\.exe)?$/.test(cmd);
|
|
286
329
|
if (isClaudeCmd && process.env.CCV_SKIP_THINKING_DISPLAY !== '1') {
|
|
287
330
|
const { withDefaultThinkingDisplay } = await import('./server/pty-manager.js');
|
|
288
331
|
cmdArgs = withDefaultThinkingDisplay(cmdArgs);
|
|
@@ -445,12 +488,18 @@ async function runCliMode(extraClaudeArgs = [], cwd, noOpen = false) {
|
|
|
445
488
|
// Win 上 `start` 是 cmd.exe 内置不是 .exe,必须 shell:true;用 spawn + 数组让 Node 自己 escape。
|
|
446
489
|
// 第二个 arg '""' 是 `start` 的 window-title 占位(否则 start 会把 URL 当 title)。
|
|
447
490
|
const { spawn } = await import('node:child_process');
|
|
491
|
+
// Headless/container envs may lack xdg-open/open/start → spawn asynchronously emits an 'error' event,
|
|
492
|
+
// which try/catch cannot catch (it only catches synchronous throws); without a handler the process
|
|
493
|
+
// would crash. Swallow it here as a best-effort fallback.
|
|
494
|
+
let child;
|
|
448
495
|
if (process.platform === 'win32') {
|
|
449
|
-
spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true, windowsHide: true })
|
|
496
|
+
child = spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true, windowsHide: true });
|
|
450
497
|
} else {
|
|
451
498
|
const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
452
|
-
spawn(cmd, [url], { stdio: 'ignore', detached: true })
|
|
499
|
+
child = spawn(cmd, [url], { stdio: 'ignore', detached: true });
|
|
453
500
|
}
|
|
501
|
+
child.on('error', () => {});
|
|
502
|
+
child.unref();
|
|
454
503
|
} catch {}
|
|
455
504
|
}
|
|
456
505
|
|
|
@@ -628,12 +677,18 @@ async function runSdkMode(extraClaudeArgs = [], cwd, noOpen = false) {
|
|
|
628
677
|
// Win 上 `start` 是 cmd.exe 内置不是 .exe,必须 shell:true;用 spawn + 数组让 Node 自己 escape。
|
|
629
678
|
// 第二个 arg '""' 是 `start` 的 window-title 占位(否则 start 会把 URL 当 title)。
|
|
630
679
|
const { spawn } = await import('node:child_process');
|
|
680
|
+
// Headless/container envs may lack xdg-open/open/start → spawn asynchronously emits an 'error' event,
|
|
681
|
+
// which try/catch cannot catch (it only catches synchronous throws); without a handler the process
|
|
682
|
+
// would crash. Swallow it here as a best-effort fallback.
|
|
683
|
+
let child;
|
|
631
684
|
if (process.platform === 'win32') {
|
|
632
|
-
spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true, windowsHide: true })
|
|
685
|
+
child = spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true, windowsHide: true });
|
|
633
686
|
} else {
|
|
634
687
|
const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
635
|
-
spawn(cmd, [url], { stdio: 'ignore', detached: true })
|
|
688
|
+
child = spawn(cmd, [url], { stdio: 'ignore', detached: true });
|
|
636
689
|
}
|
|
690
|
+
child.on('error', () => {});
|
|
691
|
+
child.unref();
|
|
637
692
|
} catch {}
|
|
638
693
|
}
|
|
639
694
|
|
|
@@ -787,7 +842,7 @@ if (isVersion) {
|
|
|
787
842
|
|
|
788
843
|
if (isUninstall) {
|
|
789
844
|
const cliResult = removeCliJsInjection();
|
|
790
|
-
const
|
|
845
|
+
const shellResults = removeShellHook();
|
|
791
846
|
|
|
792
847
|
if (cliResult === 'removed' || cliResult === 'clean') {
|
|
793
848
|
console.log(t('cli.uninstall.cliCleaned'));
|
|
@@ -797,12 +852,18 @@ if (isUninstall) {
|
|
|
797
852
|
console.log(t('cli.uninstall.cliFail'));
|
|
798
853
|
}
|
|
799
854
|
|
|
800
|
-
if (
|
|
801
|
-
console.log(t('cli.uninstall.
|
|
802
|
-
} else if (shellResult.status === 'clean' || shellResult.status === 'not_found') {
|
|
803
|
-
console.log(t('cli.uninstall.hookClean', { path: shellResult.path }));
|
|
855
|
+
if (shellResults.length === 0) {
|
|
856
|
+
console.log(t('cli.uninstall.hookClean', { path: getShellConfigPath() }));
|
|
804
857
|
} else {
|
|
805
|
-
|
|
858
|
+
for (const r of shellResults) {
|
|
859
|
+
if (r.status === 'removed') {
|
|
860
|
+
console.log(t('cli.uninstall.hookRemoved', { path: r.file }));
|
|
861
|
+
} else if (r.status === 'corrupt') {
|
|
862
|
+
console.log(t('cli.uninstall.corruptBlock', { file: r.file }));
|
|
863
|
+
} else {
|
|
864
|
+
console.log(t('cli.uninstall.hookFail', { file: r.file, error: r.error }));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
806
867
|
}
|
|
807
868
|
|
|
808
869
|
// 清理 settings.json 里的 cc-viewer-managed hooks + 历史 statusLine 残留
|
|
@@ -845,12 +906,40 @@ if (isUninstall) {
|
|
|
845
906
|
}
|
|
846
907
|
} catch { }
|
|
847
908
|
|
|
909
|
+
// Tombstone: stale hooks in still-open shells background `ccv -logger --self-heal`,
|
|
910
|
+
// which would otherwise re-install everything right after this uninstall.
|
|
911
|
+
try {
|
|
912
|
+
mkdirSync(resolve(getClaudeConfigDir(), 'cc-viewer'), { recursive: true });
|
|
913
|
+
writeFileSync(getUninstallFlagPath(), new Date().toISOString() + '\n');
|
|
914
|
+
} catch { }
|
|
915
|
+
|
|
848
916
|
console.log(t('cli.uninstall.reloadShell'));
|
|
849
917
|
console.log(t('cli.uninstall.done'));
|
|
850
918
|
process.exit(0);
|
|
851
919
|
}
|
|
852
920
|
|
|
853
921
|
if (isLogger) {
|
|
922
|
+
// Tombstone check must run before mode detection: a backgrounded self-heal with
|
|
923
|
+
// claude fully uninstalled must exit 0 silently, not emit reportClaudeNotFound noise.
|
|
924
|
+
try {
|
|
925
|
+
const flagPath = getUninstallFlagPath();
|
|
926
|
+
if (existsSync(flagPath)) {
|
|
927
|
+
if (args.includes('--self-heal')) {
|
|
928
|
+
// Explicitly uninstalled — never let a stale hook resurrect the integration
|
|
929
|
+
process.exit(0);
|
|
930
|
+
}
|
|
931
|
+
const ageMs = Date.now() - statSync(flagPath).mtimeMs;
|
|
932
|
+
const GRACE_MS = 10 * 60 * 1000;
|
|
933
|
+
if (ageMs < GRACE_MS && !args.includes('--force')) {
|
|
934
|
+
// Old deployed hooks self-heal via plain `ccv -logger`; a grace period keeps
|
|
935
|
+
// them from wiping a fresh tombstone during the uninstall race window.
|
|
936
|
+
console.log(t('cli.logger.justUninstalled'));
|
|
937
|
+
process.exit(0);
|
|
938
|
+
}
|
|
939
|
+
unlinkSync(flagPath);
|
|
940
|
+
}
|
|
941
|
+
} catch { }
|
|
942
|
+
|
|
854
943
|
// 模式选择:有 cli.js 就走 npm 注入模式(pre-2.1.113),没有就走 native proxy
|
|
855
944
|
// 模式(2.1.114+)。单一判据,不再靠 realpath 的启发式。
|
|
856
945
|
const nativePath = resolveNativePath();
|