ai-cc-router 0.2.7 → 0.3.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/README.md +22 -25
- package/dist/cli/cmd-logs.js +85 -0
- package/dist/cli/cmd-setup.js +3 -182
- package/dist/cli/cmd-start.js +243 -23
- package/dist/cli/cmd-stop.js +65 -93
- package/dist/cli/index.js +8 -8
- package/dist/config/manager.js +10 -2
- package/dist/config/paths.js +4 -0
- package/dist/daemon/launcher.js +163 -0
- package/dist/daemon/pid.js +98 -0
- package/dist/daemon/service.js +260 -0
- package/dist/proxy/server.js +8 -0
- package/dist/utils/network.js +16 -0
- package/dist/utils/self-update.js +26 -6
- package/dist/utils/telemetry.js +8 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
**Round-robin proxy for multiple Claude Max accounts.**
|
|
4
4
|
Distribute Claude Code requests across N subscriptions to multiply your throughput.
|
|
5
5
|
|
|
6
|
-
[](https://github.com/VictorMinemu/CC-Router/actions/workflows/ci.yml)
|
|
7
6
|
[](https://www.npmjs.com/package/ai-cc-router)
|
|
8
7
|
[](LICENSE)
|
|
9
8
|
|
|
9
|
+

|
|
10
|
+
|
|
10
11
|
### Features
|
|
11
12
|
|
|
12
13
|
- **Round-robin token rotation** — distribute requests across 2-20 Claude Max accounts automatically
|
|
@@ -19,7 +20,7 @@ Distribute Claude Code requests across N subscriptions to multiply your throughp
|
|
|
19
20
|
- **Live dashboard** — real-time terminal UI showing account health, request counts, token usage, recent activity
|
|
20
21
|
- **Proxy authentication** — optional Bearer / x-api-key secret for internet-exposed deployments
|
|
21
22
|
- **Auto-update** — patch/minor releases install automatically (opt-out available)
|
|
22
|
-
- **Multiple deployment modes** —
|
|
23
|
+
- **Multiple deployment modes** — background daemon, native OS auto-start (launchd/systemd), foreground, Docker Compose
|
|
23
24
|
- **Cross-platform** — macOS, Linux, Windows; Node.js 20+
|
|
24
25
|
|
|
25
26
|
---
|
|
@@ -114,17 +115,10 @@ Run cc-router on a machine everyone on the team can reach — a home server, a V
|
|
|
114
115
|
```bash
|
|
115
116
|
npm install -g ai-cc-router
|
|
116
117
|
cc-router setup # configure the 3 shared accounts
|
|
117
|
-
cc-router
|
|
118
|
+
cc-router start # first run asks: background/boot/server mode — choose "server mode"
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
# Listen on all interfaces (team LAN or VPS)
|
|
124
|
-
HOST=0.0.0.0 cc-router start
|
|
125
|
-
|
|
126
|
-
# Or configure it permanently in the service
|
|
127
|
-
```
|
|
121
|
+
When you enable server mode during `cc-router start`, the proxy automatically binds to all interfaces (`0.0.0.0`) and prints instructions for connecting clients.
|
|
128
122
|
|
|
129
123
|
#### On each developer's machine
|
|
130
124
|
|
|
@@ -204,9 +198,9 @@ claude
|
|
|
204
198
|
|
|
205
199
|
That's it. Claude Code will route through the proxy without any further changes.
|
|
206
200
|
|
|
207
|
-
|
|
201
|
+
On first run, `cc-router start` asks how you want to run (background/foreground, auto-start on boot, server mode) and remembers your choice. Next time, it just starts. To change preferences later:
|
|
208
202
|
```bash
|
|
209
|
-
cc-router
|
|
203
|
+
cc-router start --reconfigure
|
|
210
204
|
```
|
|
211
205
|
|
|
212
206
|
---
|
|
@@ -274,26 +268,27 @@ cc-router setup
|
|
|
274
268
|
cc-router setup Interactive wizard: extract tokens + configure Claude Code
|
|
275
269
|
cc-router setup --add Add another account to an existing configuration
|
|
276
270
|
|
|
277
|
-
cc-router start Start proxy on
|
|
278
|
-
cc-router start --
|
|
271
|
+
cc-router start Start proxy (asks preferences on first run, then remembers)
|
|
272
|
+
cc-router start --foreground Run in the foreground (stays in terminal)
|
|
273
|
+
cc-router start --reconfigure Re-ask run preferences (background/service/server mode)
|
|
279
274
|
cc-router start --litellm Start with LiteLLM in Docker (advanced mode)
|
|
280
275
|
|
|
281
|
-
cc-router stop Stop proxy
|
|
276
|
+
cc-router stop Stop proxy (offers to remove auto-start / config)
|
|
282
277
|
cc-router stop --keep-config Stop proxy only (keep settings.json)
|
|
283
|
-
cc-router revert
|
|
278
|
+
cc-router stop --full Stop + remove auto-start + revert Claude Code (no prompts)
|
|
279
|
+
cc-router revert Same as stop --full
|
|
284
280
|
|
|
285
281
|
cc-router status Live dashboard (updates every 2s, press q to quit)
|
|
286
282
|
cc-router status --json Print current stats as JSON and exit
|
|
287
283
|
|
|
284
|
+
cc-router logs View proxy logs (background mode)
|
|
285
|
+
cc-router logs -f Follow log output in real time
|
|
286
|
+
cc-router logs --lines 100 Show last 100 lines
|
|
287
|
+
|
|
288
288
|
cc-router accounts list List configured accounts (live stats if proxy is running)
|
|
289
289
|
cc-router accounts add Add an account interactively
|
|
290
290
|
cc-router accounts remove <id> Remove an account
|
|
291
291
|
|
|
292
|
-
cc-router service install Register cc-router to start on system boot (PM2)
|
|
293
|
-
cc-router service uninstall Remove from system startup
|
|
294
|
-
cc-router service status Show PM2 service status
|
|
295
|
-
cc-router service logs Tail proxy logs from PM2
|
|
296
|
-
|
|
297
292
|
cc-router configure (Re)write ~/.claude/settings.json
|
|
298
293
|
cc-router configure --show Show current Claude Code proxy settings
|
|
299
294
|
cc-router configure --remove Remove cc-router settings (same as revert without stopping)
|
|
@@ -323,7 +318,7 @@ cc-router docker restart [service] Restart a service
|
|
|
323
318
|
Claude Code → cc-router:3456 → api.anthropic.com
|
|
324
319
|
```
|
|
325
320
|
|
|
326
|
-
Best for personal use. No Docker required.
|
|
321
|
+
Best for personal use. No Docker required. Runs in the background by default, auto-starts on boot if you choose.
|
|
327
322
|
|
|
328
323
|
```bash
|
|
329
324
|
cc-router start
|
|
@@ -477,7 +472,9 @@ To stop using cc-router and go back to normal Claude Code authentication:
|
|
|
477
472
|
cc-router revert
|
|
478
473
|
```
|
|
479
474
|
|
|
480
|
-
This stops the proxy process and removes cc-router's settings from `~/.claude/settings.json`. Claude Code will use its own authentication on the next launch.
|
|
475
|
+
This stops the proxy process, removes the auto-start service (if installed), and removes cc-router's settings from `~/.claude/settings.json`. Claude Code will use its own authentication on the next launch.
|
|
476
|
+
|
|
477
|
+
For a gentler approach, `cc-router stop` interactively asks what you want to clean up.
|
|
481
478
|
|
|
482
479
|
---
|
|
483
480
|
|
|
@@ -530,7 +527,7 @@ CC-Router sends a handful of anonymous lifecycle events to [Aptabase](https://ap
|
|
|
530
527
|
| `app_started` | First proxy start after install | `first_run: true` |
|
|
531
528
|
| `setup_completed` | Setup wizard finishes successfully | `account_count` |
|
|
532
529
|
| `proxy_started` | Each `cc-router start` | `account_count`, `mode` |
|
|
533
|
-
| `proxy_heartbeat` | Every
|
|
530
|
+
| `proxy_heartbeat` | Every hour while the proxy is running | `uptime_minutes`, `account_count` |
|
|
534
531
|
| `telemetry_disabled` | When you run `cc-router telemetry off` | — |
|
|
535
532
|
|
|
536
533
|
Plus anonymous system props with every event: `appVersion`, `osName` (macOS/Linux/Windows), `osVersion`, `locale`, `engineVersion` (Node), and an anonymous `installId` (random UUID generated on first run, stored in `~/.cc-router/telemetry.json`).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, openSync, readSync, closeSync, statSync, watch } from "fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { LOG_PATH } from "../config/paths.js";
|
|
4
|
+
export function registerLogs(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("logs")
|
|
7
|
+
.description("View proxy logs")
|
|
8
|
+
.option("--lines <n>", "Number of lines to show (default: 50)", "50")
|
|
9
|
+
.option("-f, --follow", "Follow log output in real time")
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
if (!existsSync(LOG_PATH)) {
|
|
12
|
+
console.log(chalk.yellow("No log file found."));
|
|
13
|
+
console.log(chalk.gray(` Expected: ${LOG_PATH}`));
|
|
14
|
+
console.log(chalk.gray(" Logs are created when running in background mode."));
|
|
15
|
+
console.log(chalk.gray(" Foreground mode prints directly to this terminal.\n"));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const numLines = parseInt(opts.lines, 10) || 50;
|
|
19
|
+
if (opts.follow) {
|
|
20
|
+
await tailFollow(numLines);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
tailStatic(numLines);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Print last N lines of the log file using byte-based seeking. */
|
|
28
|
+
function tailStatic(n) {
|
|
29
|
+
if (!existsSync(LOG_PATH)) {
|
|
30
|
+
console.log(chalk.gray("No log file found. Is the daemon running?"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const stats = statSync(LOG_PATH);
|
|
34
|
+
if (stats.size === 0) {
|
|
35
|
+
console.log(chalk.gray("Log file is empty."));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Read last chunk (up to 64KB should be enough for most tail operations)
|
|
39
|
+
const CHUNK = Math.min(stats.size, 64 * 1024);
|
|
40
|
+
const buf = Buffer.alloc(CHUNK);
|
|
41
|
+
const fd = openSync(LOG_PATH, "r");
|
|
42
|
+
readSync(fd, buf, 0, CHUNK, stats.size - CHUNK);
|
|
43
|
+
closeSync(fd);
|
|
44
|
+
const text = buf.toString("utf-8");
|
|
45
|
+
const lines = text.split("\n");
|
|
46
|
+
// Remove first potentially partial line if we didn't read from start
|
|
47
|
+
if (CHUNK < stats.size)
|
|
48
|
+
lines.shift();
|
|
49
|
+
const tail = lines.slice(-n).join("\n");
|
|
50
|
+
if (tail)
|
|
51
|
+
process.stdout.write(tail + "\n");
|
|
52
|
+
}
|
|
53
|
+
/** Stream log file and print new lines as they appear. */
|
|
54
|
+
async function tailFollow(initialLines) {
|
|
55
|
+
// Show initial tail
|
|
56
|
+
tailStatic(initialLines);
|
|
57
|
+
console.log(chalk.gray("\n── Following log output (Ctrl+C to stop) ──\n"));
|
|
58
|
+
if (!existsSync(LOG_PATH)) {
|
|
59
|
+
console.log(chalk.gray("Waiting for log file..."));
|
|
60
|
+
}
|
|
61
|
+
let bytePosition = existsSync(LOG_PATH) ? statSync(LOG_PATH).size : 0;
|
|
62
|
+
const watcher = watch(LOG_PATH, () => {
|
|
63
|
+
try {
|
|
64
|
+
const currentSize = statSync(LOG_PATH).size;
|
|
65
|
+
if (currentSize > bytePosition) {
|
|
66
|
+
const buf = Buffer.alloc(currentSize - bytePosition);
|
|
67
|
+
const fd = openSync(LOG_PATH, "r");
|
|
68
|
+
readSync(fd, buf, 0, buf.length, bytePosition);
|
|
69
|
+
closeSync(fd);
|
|
70
|
+
process.stdout.write(buf.toString("utf-8"));
|
|
71
|
+
bytePosition = currentSize;
|
|
72
|
+
}
|
|
73
|
+
else if (currentSize < bytePosition) {
|
|
74
|
+
// File was truncated/rotated
|
|
75
|
+
bytePosition = 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { /* file may have been removed */ }
|
|
79
|
+
});
|
|
80
|
+
process.on("SIGINT", () => {
|
|
81
|
+
watcher.close();
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
84
|
+
await new Promise(() => { }); // never resolves
|
|
85
|
+
}
|
package/dist/cli/cmd-setup.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { select, input, confirm, password } from "@inquirer/prompts";
|
|
2
|
-
import { execFile, spawn } from "child_process";
|
|
3
|
-
import { promisify } from "util";
|
|
4
2
|
import chalk from "chalk";
|
|
5
3
|
import { detectPlatform, isMacos } from "../utils/platform.js";
|
|
6
4
|
import { extractFromKeychain, extractFromCredentialsFile, formatExpiry, redactToken, } from "../utils/token-extractor.js";
|
|
@@ -14,7 +12,6 @@ import { existsSync } from "fs";
|
|
|
14
12
|
import { checkMitmproxyInstalled, isCaCertInstalled, generateCaCert, installCaCert, writeAddonScript, getNetworkExtensionStatus, openNetworkExtensionSettings, } from "../interceptor/mitmproxy-manager.js";
|
|
15
13
|
import { printDesktopSupportExplainer, printNetworkExtensionInstructions } from "./cmd-client.js";
|
|
16
14
|
import { trackEvent } from "../utils/telemetry.js";
|
|
17
|
-
const execFileAsync = promisify(execFile);
|
|
18
15
|
// ─── Public registration ──────────────────────────────────────────────────────
|
|
19
16
|
export function registerSetup(program) {
|
|
20
17
|
program
|
|
@@ -110,7 +107,7 @@ export async function setupSingleAccount(index) {
|
|
|
110
107
|
};
|
|
111
108
|
}
|
|
112
109
|
// ─── Full wizard ──────────────────────────────────────────────────────────────
|
|
113
|
-
async function runSetupWizard({ addMode }) {
|
|
110
|
+
export async function runSetupWizard({ addMode }) {
|
|
114
111
|
const platform = detectPlatform();
|
|
115
112
|
const hasExisting = accountsFileExists();
|
|
116
113
|
const existingClient = readConfig().client;
|
|
@@ -305,190 +302,14 @@ async function runPostSetupFlow(accountCount) {
|
|
|
305
302
|
console.log(chalk.gray(` ANTHROPIC_BASE_URL = ${proxyHost}`));
|
|
306
303
|
console.log(chalk.gray(` ANTHROPIC_AUTH_TOKEN = proxy-managed`));
|
|
307
304
|
}
|
|
308
|
-
// ── Auto-update preference ──────────────────────────────────────────────
|
|
309
|
-
const existingCfg = readConfig();
|
|
310
|
-
if (existingCfg.autoUpdate === undefined) {
|
|
311
|
-
const enableAutoUpdate = await confirm({
|
|
312
|
-
message: "Enable auto-updates? (proxy will install patch/minor releases automatically)",
|
|
313
|
-
default: true,
|
|
314
|
-
});
|
|
315
|
-
writeConfig({ ...existingCfg, autoUpdate: enableAutoUpdate });
|
|
316
|
-
console.log(chalk.gray(` Auto-update: ${enableAutoUpdate ? chalk.green("enabled") : chalk.gray("disabled")}`));
|
|
317
|
-
console.log(chalk.gray(" Change later with: cc-router configure --enable-auto-update / --disable-auto-update"));
|
|
318
|
-
}
|
|
319
|
-
// 2. Only ask about starting the proxy if it's local
|
|
320
|
-
console.log(chalk.bold(`\n${"━".repeat(40)}\n Start the proxy\n${"━".repeat(40)}\n`));
|
|
321
|
-
// Check if it's already running
|
|
322
|
-
const alreadyRunning = await isProxyRunning();
|
|
323
|
-
if (alreadyRunning) {
|
|
324
|
-
console.log(chalk.green(` ✓ Proxy is already running on http://localhost:${PROXY_PORT}`));
|
|
325
|
-
printDone(accountCount);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
const startChoice = await select({
|
|
329
|
-
message: "How do you want to run the proxy?",
|
|
330
|
-
choices: [
|
|
331
|
-
{ name: "Install as system service (auto-start on boot — recommended)", value: "service" },
|
|
332
|
-
{ name: "Start in background now (current session only, via PM2)", value: "daemon" },
|
|
333
|
-
{ name: "Start in foreground now (this terminal, Ctrl+C to stop)", value: "foreground" },
|
|
334
|
-
{ name: "I'll start it manually later", value: "skip" },
|
|
335
|
-
],
|
|
336
|
-
});
|
|
337
|
-
if (startChoice === "service") {
|
|
338
|
-
await installService();
|
|
339
|
-
}
|
|
340
|
-
else if (startChoice === "daemon") {
|
|
341
|
-
await startDaemon();
|
|
342
|
-
}
|
|
343
|
-
else if (startChoice === "foreground") {
|
|
344
|
-
printDone(accountCount);
|
|
345
|
-
console.log(chalk.cyan("\nStarting proxy in foreground...\n"));
|
|
346
|
-
// Launch start as child — it blocks until Ctrl+C
|
|
347
|
-
await startForeground();
|
|
348
|
-
return; // startForeground never returns normally
|
|
349
|
-
}
|
|
350
305
|
printDone(accountCount);
|
|
351
306
|
}
|
|
352
|
-
// ─── Proxy launch helpers ─────────────────────────────────────────────────────
|
|
353
|
-
async function isProxyRunning() {
|
|
354
|
-
try {
|
|
355
|
-
const res = await fetch(`http://localhost:${PROXY_PORT}/cc-router/health`, {
|
|
356
|
-
signal: AbortSignal.timeout(800),
|
|
357
|
-
});
|
|
358
|
-
return res.ok;
|
|
359
|
-
}
|
|
360
|
-
catch {
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
async function installService() {
|
|
365
|
-
console.log(chalk.cyan("\n Installing as system service via PM2..."));
|
|
366
|
-
try {
|
|
367
|
-
// Ensure PM2 is installed
|
|
368
|
-
await execFileAsync("pm2", ["--version"]).catch(async () => {
|
|
369
|
-
console.log(chalk.gray(" Installing PM2..."));
|
|
370
|
-
await execFileAsync("npm", ["install", "-g", "pm2"]);
|
|
371
|
-
});
|
|
372
|
-
const { fileURLToPath } = await import("url");
|
|
373
|
-
const { dirname, join } = await import("path");
|
|
374
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
375
|
-
const cliEntry = join(__dirname, "index.js");
|
|
376
|
-
// Start in PM2
|
|
377
|
-
await execFileAsync("pm2", [
|
|
378
|
-
"start", cliEntry,
|
|
379
|
-
"--name", "cc-router",
|
|
380
|
-
"--interpreter", process.execPath,
|
|
381
|
-
"--max-memory-restart", "500M",
|
|
382
|
-
"--", "start",
|
|
383
|
-
]).catch(async (err) => {
|
|
384
|
-
// Already registered — restart instead
|
|
385
|
-
if (err.message?.includes("already")) {
|
|
386
|
-
await execFileAsync("pm2", ["restart", "cc-router"]);
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
throw err;
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
await execFileAsync("pm2", ["save"]);
|
|
393
|
-
console.log(chalk.green(" ✓ cc-router registered in PM2 and saved"));
|
|
394
|
-
// Generate startup hook
|
|
395
|
-
try {
|
|
396
|
-
const { stdout, stderr } = await execFileAsync("pm2", ["startup"]);
|
|
397
|
-
const combined = stdout + stderr;
|
|
398
|
-
const sudoMatch = combined.match(/sudo\s+\S.+/);
|
|
399
|
-
if (sudoMatch) {
|
|
400
|
-
console.log(chalk.yellow("\n Run this command to complete auto-start setup:"));
|
|
401
|
-
console.log(chalk.white(` ${sudoMatch[0]}`));
|
|
402
|
-
console.log(chalk.gray(" Then run: pm2 save"));
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
console.log(chalk.green(" ✓ Auto-start on boot configured"));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
catch (err) {
|
|
409
|
-
const combined = (err.stdout ?? "") +
|
|
410
|
-
(err.stderr ?? "");
|
|
411
|
-
const sudoMatch = combined.match(/sudo\s+\S.+/);
|
|
412
|
-
if (sudoMatch) {
|
|
413
|
-
console.log(chalk.yellow("\n Run this command to complete auto-start setup:"));
|
|
414
|
-
console.log(chalk.white(` ${sudoMatch[0]}`));
|
|
415
|
-
console.log(chalk.gray(" Then run: pm2 save"));
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
// Wait a moment and confirm it started
|
|
419
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
420
|
-
const running = await isProxyRunning();
|
|
421
|
-
if (running) {
|
|
422
|
-
console.log(chalk.green(` ✓ Proxy is running on http://localhost:${PROXY_PORT}`));
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
console.log(chalk.yellow(" ⚠ Service registered but proxy not yet responding — it may still be starting."));
|
|
426
|
-
console.log(chalk.gray(" Check: cc-router service status"));
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
catch (err) {
|
|
430
|
-
console.log(chalk.red(` ✗ Service install failed: ${err.message}`));
|
|
431
|
-
console.log(chalk.gray(" Try manually: cc-router service install"));
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
async function startDaemon() {
|
|
435
|
-
console.log(chalk.cyan("\n Starting in background via PM2..."));
|
|
436
|
-
try {
|
|
437
|
-
const { fileURLToPath } = await import("url");
|
|
438
|
-
const { dirname, join } = await import("path");
|
|
439
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
440
|
-
const cliEntry = join(__dirname, "index.js");
|
|
441
|
-
await execFileAsync("pm2", [
|
|
442
|
-
"start", cliEntry,
|
|
443
|
-
"--name", "cc-router",
|
|
444
|
-
"--interpreter", process.execPath,
|
|
445
|
-
"--max-memory-restart", "500M",
|
|
446
|
-
"--", "start",
|
|
447
|
-
]).catch(async (err) => {
|
|
448
|
-
if (err.message?.includes("already")) {
|
|
449
|
-
await execFileAsync("pm2", ["restart", "cc-router"]);
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
throw err;
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
456
|
-
const running = await isProxyRunning();
|
|
457
|
-
if (running) {
|
|
458
|
-
console.log(chalk.green(` ✓ Proxy running in background on http://localhost:${PROXY_PORT}`));
|
|
459
|
-
console.log(chalk.gray(" Logs: pm2 logs cc-router | Stop: cc-router stop"));
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
console.log(chalk.yellow(" ⚠ PM2 registered but proxy not yet responding."));
|
|
463
|
-
console.log(chalk.gray(" Check: pm2 logs cc-router"));
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
catch (err) {
|
|
467
|
-
console.log(chalk.red(` ✗ Failed to start via PM2: ${err.message}`));
|
|
468
|
-
console.log(chalk.gray(" PM2 not installed? Run: npm install -g pm2"));
|
|
469
|
-
console.log(chalk.gray(" Or start manually: cc-router start"));
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
async function startForeground() {
|
|
473
|
-
const { fileURLToPath } = await import("url");
|
|
474
|
-
const { dirname, join } = await import("path");
|
|
475
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
476
|
-
const cliEntry = join(__dirname, "index.js");
|
|
477
|
-
const child = spawn(process.execPath, [cliEntry, "start"], { stdio: "inherit" });
|
|
478
|
-
await new Promise((resolve) => {
|
|
479
|
-
child.on("close", resolve);
|
|
480
|
-
child.on("error", (err) => {
|
|
481
|
-
console.error(chalk.red(` ✗ ${err.message}`));
|
|
482
|
-
resolve();
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
307
|
// ─── Done banner ──────────────────────────────────────────────────────────────
|
|
487
308
|
function printDone(accountCount) {
|
|
488
309
|
console.log(chalk.bold(`\n${"━".repeat(40)}\n All done — ${accountCount} account(s) ready\n${"━".repeat(40)}\n`));
|
|
489
|
-
console.log(`
|
|
310
|
+
console.log(` Start the proxy: ${chalk.cyan("cc-router start")}`);
|
|
490
311
|
console.log(` Add more accounts: ${chalk.cyan("cc-router setup --add")}`);
|
|
491
|
-
console.log(`
|
|
312
|
+
console.log(` Dashboard: ${chalk.cyan("cc-router status")}\n`);
|
|
492
313
|
}
|
|
493
314
|
// ─── Manual token input ───────────────────────────────────────────────────────
|
|
494
315
|
async function promptManualTokens() {
|