ai-cc-router 0.3.1 → 0.4.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/cli/cmd-client.js +44 -2
- package/dist/cli/cmd-start.js +1 -1
- package/dist/interceptor/mitmproxy-manager.js +261 -14
- package/package.json +1 -1
package/dist/cli/cmd-client.js
CHANGED
|
@@ -4,7 +4,7 @@ import { input, confirm } from "@inquirer/prompts";
|
|
|
4
4
|
import { readConfig, writeConfig } from "../config/manager.js";
|
|
5
5
|
import { writeClaudeSettings, removeClaudeSettings, readClaudeProxySettings } from "../utils/claude-config.js";
|
|
6
6
|
import { isMacos, isWindows } from "../utils/platform.js";
|
|
7
|
-
import { checkMitmproxyInstalled, isCaCertInstalled, generateCaCert, installCaCert, writeAddonScript, startInterceptor, stopInterceptor, isInterceptorRunning, getProcessName, getNetworkExtensionStatus, openNetworkExtensionSettings, } from "../interceptor/mitmproxy-manager.js";
|
|
7
|
+
import { checkMitmproxyInstalled, isCaCertInstalled, generateCaCert, installCaCert, writeAddonScript, startInterceptor, stopInterceptor, isInterceptorRunning, getProcessName, getNetworkExtensionStatus, openNetworkExtensionSettings, installInterceptorService, uninstallInterceptorService, isInterceptorServiceInstalled, } from "../interceptor/mitmproxy-manager.js";
|
|
8
8
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
9
9
|
function isClaudeDesktopInstalled() {
|
|
10
10
|
if (isMacos()) {
|
|
@@ -141,6 +141,10 @@ export function registerClient(program) {
|
|
|
141
141
|
.action(async () => {
|
|
142
142
|
const cfg = readConfig();
|
|
143
143
|
if (cfg.client?.desktopEnabled) {
|
|
144
|
+
if (isInterceptorServiceInstalled()) {
|
|
145
|
+
console.log(chalk.yellow("Removing Claude Desktop interceptor service..."));
|
|
146
|
+
await uninstallInterceptorService();
|
|
147
|
+
}
|
|
144
148
|
console.log(chalk.yellow("Stopping Claude Desktop interceptor..."));
|
|
145
149
|
await stopInterceptor();
|
|
146
150
|
}
|
|
@@ -220,6 +224,7 @@ export function registerClient(program) {
|
|
|
220
224
|
console.log(chalk.bold("\n DESKTOP INTERCEPTOR (Cowork / Agent mode)"));
|
|
221
225
|
if (cfg.client.desktopEnabled) {
|
|
222
226
|
const running = await isInterceptorRunning();
|
|
227
|
+
const serviceInstalled = isInterceptorServiceInstalled();
|
|
223
228
|
if (running) {
|
|
224
229
|
console.log(` ${chalk.green("● running")}`);
|
|
225
230
|
}
|
|
@@ -227,6 +232,12 @@ export function registerClient(program) {
|
|
|
227
232
|
console.log(` ${chalk.yellow("○ configured but stopped")}`);
|
|
228
233
|
console.log(chalk.gray(" Start with: cc-router client start-desktop"));
|
|
229
234
|
}
|
|
235
|
+
if (serviceInstalled) {
|
|
236
|
+
console.log(` ${chalk.green("✓")} ${chalk.gray("Auto-start on boot: enabled")}`);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
console.log(` ${chalk.gray("○ Auto-start on boot: disabled")}`);
|
|
240
|
+
}
|
|
230
241
|
// Check Network Extension on macOS
|
|
231
242
|
if (isMacos()) {
|
|
232
243
|
const extStatus = await getNetworkExtensionStatus();
|
|
@@ -307,6 +318,27 @@ export function registerClient(program) {
|
|
|
307
318
|
process.exit(1);
|
|
308
319
|
}
|
|
309
320
|
console.log(chalk.green("\n✓ Claude Desktop interceptor running"));
|
|
321
|
+
// ── Auto-start on boot ─────────────────────────────────────────────
|
|
322
|
+
if (!cfg.client.desktopAutoStart && !isInterceptorServiceInstalled()) {
|
|
323
|
+
const autoStart = await confirm({
|
|
324
|
+
message: "Start interceptor automatically when your computer boots? (recommended)",
|
|
325
|
+
default: true,
|
|
326
|
+
});
|
|
327
|
+
if (autoStart) {
|
|
328
|
+
const ok = await installInterceptorService(target);
|
|
329
|
+
if (ok) {
|
|
330
|
+
cfg.client.desktopAutoStart = true;
|
|
331
|
+
writeConfig(cfg);
|
|
332
|
+
console.log(chalk.green("✓ Auto-start on boot configured"));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
console.log(chalk.yellow("⚠ Could not configure auto-start. You can retry later."));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (cfg.client.desktopAutoStart) {
|
|
340
|
+
console.log(chalk.gray(" Auto-start on boot: enabled"));
|
|
341
|
+
}
|
|
310
342
|
console.log();
|
|
311
343
|
console.log(chalk.bold.yellow(" Next steps:"));
|
|
312
344
|
console.log(" " + chalk.cyan("1.") + " Quit Claude Desktop completely (⌘Q)");
|
|
@@ -321,7 +353,17 @@ export function registerClient(program) {
|
|
|
321
353
|
client
|
|
322
354
|
.command("stop-desktop")
|
|
323
355
|
.description("Stop the Claude Desktop mitmproxy interceptor")
|
|
324
|
-
.
|
|
356
|
+
.option("--keep-autostart", "Stop the interceptor but keep auto-start on boot")
|
|
357
|
+
.action(async (opts) => {
|
|
358
|
+
if (isInterceptorServiceInstalled() && !opts.keepAutostart) {
|
|
359
|
+
await uninstallInterceptorService();
|
|
360
|
+
console.log(chalk.green("✓ Auto-start on boot removed"));
|
|
361
|
+
const cfg = readConfig();
|
|
362
|
+
if (cfg.client) {
|
|
363
|
+
cfg.client.desktopAutoStart = false;
|
|
364
|
+
writeConfig(cfg);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
325
367
|
await stopInterceptor();
|
|
326
368
|
console.log(chalk.green("\n✓ Claude Desktop interceptor stopped\n"));
|
|
327
369
|
});
|
package/dist/cli/cmd-start.js
CHANGED
|
@@ -183,7 +183,7 @@ async function ensureClaudeCodeConfigured(prefs, cfg) {
|
|
|
183
183
|
async function maybeUpdate() {
|
|
184
184
|
let check;
|
|
185
185
|
try {
|
|
186
|
-
check = await checkForUpdate();
|
|
186
|
+
check = await checkForUpdate(true); // force fresh check, skip disk cache
|
|
187
187
|
if (!check.updateAvailable)
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
@@ -10,19 +10,27 @@
|
|
|
10
10
|
* Windows → WinDivert (WFP kernel driver)
|
|
11
11
|
* Linux → eBPF (requires kernel ≥ 6.8)
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
14
|
-
import { join } from "path";
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
14
|
+
import { dirname, join } from "path";
|
|
15
15
|
import os from "os";
|
|
16
|
-
import { execFile, spawn } from "child_process";
|
|
16
|
+
import { execFile, execFileSync, spawn } from "child_process";
|
|
17
17
|
import { promisify } from "util";
|
|
18
|
-
import { isMacos, isWindows } from "../utils/platform.js";
|
|
18
|
+
import { isMacos, isWindows, detectPlatform } from "../utils/platform.js";
|
|
19
19
|
import { CONFIG_DIR } from "../config/paths.js";
|
|
20
20
|
const execFileP = promisify(execFile);
|
|
21
21
|
// ─── Paths ────────────────────────────────────────────────────────────────────
|
|
22
22
|
const ADDON_DIR = join(CONFIG_DIR, "interceptor");
|
|
23
23
|
const ADDON_PATH = join(ADDON_DIR, "addon.py");
|
|
24
24
|
const PID_PATH = join(ADDON_DIR, "mitmdump.pid");
|
|
25
|
+
const LOG_PATH = join(ADDON_DIR, "mitmdump.log");
|
|
25
26
|
const CA_PATH = join(os.homedir(), ".mitmproxy", "mitmproxy-ca-cert.pem");
|
|
27
|
+
// ─── Service paths ────────────────────────────────────────────────────────────
|
|
28
|
+
const LAUNCHD_LABEL = "com.cc-router.interceptor";
|
|
29
|
+
const LAUNCHD_PLIST = join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
30
|
+
const SYSTEMD_DIR = join(os.homedir(), ".config", "systemd", "user");
|
|
31
|
+
const SYSTEMD_SERVICE = join(SYSTEMD_DIR, "cc-router-interceptor.service");
|
|
32
|
+
const WINDOWS_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
33
|
+
const WINDOWS_REG_NAME = "CC-Router-Interceptor";
|
|
26
34
|
// Bundled addon template lives next to this module in src/interceptor/addon.py;
|
|
27
35
|
// at runtime (dist/) it's NOT guaranteed to exist because the .py file is only
|
|
28
36
|
// included if package.json "files" lists it. We write a copy to ~/.cc-router/
|
|
@@ -187,21 +195,28 @@ export async function installCaCert() {
|
|
|
187
195
|
export function writeAddonScript(target) {
|
|
188
196
|
if (!existsSync(ADDON_DIR))
|
|
189
197
|
mkdirSync(ADDON_DIR, { recursive: true });
|
|
190
|
-
// Try to
|
|
198
|
+
// Try to use the bundled addon as template; fall back to a minimal inline version.
|
|
199
|
+
// In BOTH cases we inject the actual target URL so the addon is self-contained
|
|
200
|
+
// and doesn't depend on the CC_ROUTER_TARGET env var being present at runtime.
|
|
191
201
|
const bundled = addonSourcePath();
|
|
202
|
+
let src;
|
|
192
203
|
if (existsSync(bundled)) {
|
|
193
|
-
|
|
194
|
-
writeFileSync(ADDON_PATH, src, "utf-8");
|
|
204
|
+
src = readFileSync(bundled, "utf-8");
|
|
195
205
|
}
|
|
196
206
|
else {
|
|
197
207
|
// Inline fallback — minimal addon (only redirects /v1/messages and /v1/models)
|
|
198
|
-
|
|
208
|
+
src = `
|
|
199
209
|
import os
|
|
200
210
|
from mitmproxy import http
|
|
201
211
|
from urllib.parse import urlparse
|
|
202
212
|
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
_target_raw = os.environ.get("CC_ROUTER_TARGET", "http://localhost:3456")
|
|
214
|
+
_target = _target_raw.rstrip("/")
|
|
215
|
+
_target_parsed = urlparse(_target)
|
|
216
|
+
|
|
217
|
+
if not _target_parsed.scheme or not _target_parsed.netloc:
|
|
218
|
+
raise RuntimeError(f"CC_ROUTER_TARGET is not a valid URL: {_target_raw!r}")
|
|
219
|
+
|
|
205
220
|
_REDIRECT_PREFIXES = ("/v1/messages", "/v1/models")
|
|
206
221
|
|
|
207
222
|
def request(flow: http.HTTPFlow) -> None:
|
|
@@ -209,13 +224,16 @@ def request(flow: http.HTTPFlow) -> None:
|
|
|
209
224
|
return
|
|
210
225
|
if not flow.request.path.startswith(_REDIRECT_PREFIXES):
|
|
211
226
|
return
|
|
212
|
-
flow.request.scheme =
|
|
213
|
-
flow.request.host =
|
|
214
|
-
flow.request.port =
|
|
227
|
+
flow.request.scheme = _target_parsed.scheme
|
|
228
|
+
flow.request.host = _target_parsed.hostname or "localhost"
|
|
229
|
+
flow.request.port = _target_parsed.port or (443 if _target_parsed.scheme == "https" else 80)
|
|
215
230
|
flow.request.headers["host"] = flow.request.host + (f":{flow.request.port}" if flow.request.port not in (80, 443) else "")
|
|
216
231
|
`.trimStart();
|
|
217
|
-
writeFileSync(ADDON_PATH, script, "utf-8");
|
|
218
232
|
}
|
|
233
|
+
// Inject the actual target URL into the default so the addon works even
|
|
234
|
+
// without the CC_ROUTER_TARGET env var (e.g. manual mitmdump restarts).
|
|
235
|
+
src = src.replace('"http://localhost:3456"', JSON.stringify(target));
|
|
236
|
+
writeFileSync(ADDON_PATH, src, "utf-8");
|
|
219
237
|
}
|
|
220
238
|
// ─── Interceptor lifecycle ────────────────────────────────────────────────────
|
|
221
239
|
/**
|
|
@@ -307,3 +325,232 @@ function readPid() {
|
|
|
307
325
|
const pid = parseInt(raw, 10);
|
|
308
326
|
return Number.isNaN(pid) ? null : pid;
|
|
309
327
|
}
|
|
328
|
+
// ─── Interceptor OS service (auto-start on boot) ────────────────────────────
|
|
329
|
+
/** Resolve the absolute path to mitmdump so launchd/systemd can find it. */
|
|
330
|
+
async function resolveMitmdumpPath() {
|
|
331
|
+
try {
|
|
332
|
+
const cmd = isWindows() ? "where" : "which";
|
|
333
|
+
const { stdout } = await execFileP(cmd, ["mitmdump"]);
|
|
334
|
+
return stdout.trim().split("\n")[0];
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return "mitmdump"; // fallback — hope it's on PATH at boot time
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function buildInterceptorPlist(mitmdumpPath, target) {
|
|
341
|
+
const processName = getProcessName();
|
|
342
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
343
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
344
|
+
<plist version="1.0">
|
|
345
|
+
<dict>
|
|
346
|
+
<key>Label</key>
|
|
347
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
348
|
+
<key>ProgramArguments</key>
|
|
349
|
+
<array>
|
|
350
|
+
<string>${mitmdumpPath}</string>
|
|
351
|
+
<string>--mode</string>
|
|
352
|
+
<string>local:${processName}</string>
|
|
353
|
+
<string>-s</string>
|
|
354
|
+
<string>${ADDON_PATH}</string>
|
|
355
|
+
<string>--set</string>
|
|
356
|
+
<string>connection_strategy=lazy</string>
|
|
357
|
+
<string>--quiet</string>
|
|
358
|
+
</array>
|
|
359
|
+
<key>RunAtLoad</key>
|
|
360
|
+
<true/>
|
|
361
|
+
<key>KeepAlive</key>
|
|
362
|
+
<dict>
|
|
363
|
+
<key>SuccessfulExit</key>
|
|
364
|
+
<false/>
|
|
365
|
+
</dict>
|
|
366
|
+
<key>StandardOutPath</key>
|
|
367
|
+
<string>${LOG_PATH}</string>
|
|
368
|
+
<key>StandardErrorPath</key>
|
|
369
|
+
<string>${LOG_PATH}</string>
|
|
370
|
+
<key>WorkingDirectory</key>
|
|
371
|
+
<string>${os.homedir()}</string>
|
|
372
|
+
<key>EnvironmentVariables</key>
|
|
373
|
+
<dict>
|
|
374
|
+
<key>PATH</key>
|
|
375
|
+
<string>${process.env["PATH"] ?? "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"}</string>
|
|
376
|
+
<key>CC_ROUTER_TARGET</key>
|
|
377
|
+
<string>${target}</string>
|
|
378
|
+
</dict>
|
|
379
|
+
</dict>
|
|
380
|
+
</plist>
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
383
|
+
function buildInterceptorSystemdUnit(mitmdumpPath, target) {
|
|
384
|
+
const processName = getProcessName();
|
|
385
|
+
return `[Unit]
|
|
386
|
+
Description=CC-Router Interceptor — mitmproxy for Claude Desktop
|
|
387
|
+
After=network-online.target
|
|
388
|
+
Wants=network-online.target
|
|
389
|
+
|
|
390
|
+
[Service]
|
|
391
|
+
Type=simple
|
|
392
|
+
ExecStart=${mitmdumpPath} --mode local:${processName} -s ${ADDON_PATH} --set connection_strategy=lazy --quiet
|
|
393
|
+
Restart=on-failure
|
|
394
|
+
RestartSec=5
|
|
395
|
+
StartLimitIntervalSec=60
|
|
396
|
+
StartLimitBurst=5
|
|
397
|
+
Environment=PATH=${process.env["PATH"] ?? "/usr/local/bin:/usr/bin:/bin"}
|
|
398
|
+
Environment=CC_ROUTER_TARGET=${target}
|
|
399
|
+
|
|
400
|
+
[Install]
|
|
401
|
+
WantedBy=default.target
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Install the mitmproxy interceptor as an OS service so it starts on boot.
|
|
406
|
+
* Stops any existing detached mitmdump process first — the OS service takes over.
|
|
407
|
+
*/
|
|
408
|
+
export async function installInterceptorService(target) {
|
|
409
|
+
// Ensure the addon script is up-to-date with the target URL
|
|
410
|
+
writeAddonScript(target);
|
|
411
|
+
// Stop any manually-spawned mitmdump — OS service will manage it now
|
|
412
|
+
await stopInterceptor();
|
|
413
|
+
const mitmdumpPath = await resolveMitmdumpPath();
|
|
414
|
+
const platform = detectPlatform();
|
|
415
|
+
switch (platform) {
|
|
416
|
+
case "macos": return installInterceptorMacOS(mitmdumpPath, target);
|
|
417
|
+
case "linux": return installInterceptorLinux(mitmdumpPath, target);
|
|
418
|
+
case "windows": return installInterceptorWindows(mitmdumpPath, target);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
export async function uninstallInterceptorService() {
|
|
422
|
+
const platform = detectPlatform();
|
|
423
|
+
switch (platform) {
|
|
424
|
+
case "macos": return uninstallInterceptorMacOS();
|
|
425
|
+
case "linux": return uninstallInterceptorLinux();
|
|
426
|
+
case "windows": return uninstallInterceptorWindows();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
export function isInterceptorServiceInstalled() {
|
|
430
|
+
const platform = detectPlatform();
|
|
431
|
+
switch (platform) {
|
|
432
|
+
case "macos": return existsSync(LAUNCHD_PLIST);
|
|
433
|
+
case "linux": return existsSync(SYSTEMD_SERVICE);
|
|
434
|
+
case "windows": return isInterceptorWindowsServiceInstalled();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// ─── macOS LaunchAgent ──────────────────────────────────────────────────────
|
|
438
|
+
async function installInterceptorMacOS(mitmdumpPath, target) {
|
|
439
|
+
const launchAgentsDir = dirname(LAUNCHD_PLIST);
|
|
440
|
+
if (!existsSync(launchAgentsDir))
|
|
441
|
+
mkdirSync(launchAgentsDir, { recursive: true });
|
|
442
|
+
// Unload existing if present
|
|
443
|
+
if (existsSync(LAUNCHD_PLIST)) {
|
|
444
|
+
await interceptorLaunchctlUnload();
|
|
445
|
+
}
|
|
446
|
+
writeFileSync(LAUNCHD_PLIST, buildInterceptorPlist(mitmdumpPath, target), "utf-8");
|
|
447
|
+
// Load — try modern `bootstrap` first, fallback to legacy `load`
|
|
448
|
+
const uid = String(process.getuid?.() ?? 501);
|
|
449
|
+
try {
|
|
450
|
+
await execFileP("launchctl", ["bootstrap", `gui/${uid}`, LAUNCHD_PLIST]);
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
try {
|
|
454
|
+
await execFileP("launchctl", ["load", LAUNCHD_PLIST]);
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
console.log(`⚠ Could not auto-load the interceptor LaunchAgent: ${err.message}`);
|
|
458
|
+
console.log(` Load manually: launchctl load ${LAUNCHD_PLIST}`);
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
async function uninstallInterceptorMacOS() {
|
|
465
|
+
if (!existsSync(LAUNCHD_PLIST))
|
|
466
|
+
return;
|
|
467
|
+
await interceptorLaunchctlUnload();
|
|
468
|
+
try {
|
|
469
|
+
unlinkSync(LAUNCHD_PLIST);
|
|
470
|
+
}
|
|
471
|
+
catch { /* ok */ }
|
|
472
|
+
}
|
|
473
|
+
async function interceptorLaunchctlUnload() {
|
|
474
|
+
const uid = String(process.getuid?.() ?? 501);
|
|
475
|
+
try {
|
|
476
|
+
await execFileP("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`]);
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
try {
|
|
480
|
+
await execFileP("launchctl", ["unload", LAUNCHD_PLIST]);
|
|
481
|
+
}
|
|
482
|
+
catch { /* already unloaded */ }
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// ─── Linux systemd user service ─────────────────────────────────────────────
|
|
486
|
+
async function installInterceptorLinux(mitmdumpPath, target) {
|
|
487
|
+
if (!existsSync(SYSTEMD_DIR))
|
|
488
|
+
mkdirSync(SYSTEMD_DIR, { recursive: true });
|
|
489
|
+
writeFileSync(SYSTEMD_SERVICE, buildInterceptorSystemdUnit(mitmdumpPath, target), "utf-8");
|
|
490
|
+
try {
|
|
491
|
+
await execFileP("systemctl", ["--user", "daemon-reload"]);
|
|
492
|
+
await execFileP("systemctl", ["--user", "enable", "cc-router-interceptor"]);
|
|
493
|
+
await execFileP("systemctl", ["--user", "start", "cc-router-interceptor"]);
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
console.log(`⚠ systemd setup issue: ${err.message}`);
|
|
498
|
+
console.log(" Enable manually: systemctl --user enable --now cc-router-interceptor");
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function uninstallInterceptorLinux() {
|
|
503
|
+
if (!existsSync(SYSTEMD_SERVICE))
|
|
504
|
+
return;
|
|
505
|
+
try {
|
|
506
|
+
await execFileP("systemctl", ["--user", "stop", "cc-router-interceptor"]);
|
|
507
|
+
await execFileP("systemctl", ["--user", "disable", "cc-router-interceptor"]);
|
|
508
|
+
}
|
|
509
|
+
catch { /* may already be stopped */ }
|
|
510
|
+
try {
|
|
511
|
+
unlinkSync(SYSTEMD_SERVICE);
|
|
512
|
+
}
|
|
513
|
+
catch { /* ok */ }
|
|
514
|
+
try {
|
|
515
|
+
await execFileP("systemctl", ["--user", "daemon-reload"]);
|
|
516
|
+
}
|
|
517
|
+
catch { /* ok */ }
|
|
518
|
+
}
|
|
519
|
+
// ─── Windows Registry ───────────────────────────────────────────────────────
|
|
520
|
+
async function installInterceptorWindows(mitmdumpPath, target) {
|
|
521
|
+
const processName = getProcessName();
|
|
522
|
+
const cmd = `cmd /c "set CC_ROUTER_TARGET=${target} && "${mitmdumpPath}" --mode local:${processName} -s "${ADDON_PATH}" --set connection_strategy=lazy --quiet"`;
|
|
523
|
+
try {
|
|
524
|
+
await execFileP("reg", [
|
|
525
|
+
"add", WINDOWS_REG_KEY,
|
|
526
|
+
"/v", WINDOWS_REG_NAME,
|
|
527
|
+
"/t", "REG_SZ",
|
|
528
|
+
"/d", cmd,
|
|
529
|
+
"/f",
|
|
530
|
+
]);
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
console.log(`⚠ Registry write failed: ${err.message}`);
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async function uninstallInterceptorWindows() {
|
|
539
|
+
try {
|
|
540
|
+
await execFileP("reg", [
|
|
541
|
+
"delete", WINDOWS_REG_KEY,
|
|
542
|
+
"/v", WINDOWS_REG_NAME,
|
|
543
|
+
"/f",
|
|
544
|
+
]);
|
|
545
|
+
}
|
|
546
|
+
catch { /* not installed */ }
|
|
547
|
+
}
|
|
548
|
+
function isInterceptorWindowsServiceInstalled() {
|
|
549
|
+
try {
|
|
550
|
+
execFileSync("reg", ["query", WINDOWS_REG_KEY, "/v", WINDOWS_REG_NAME]);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|