Skip to content

Commit 414e0a7

Browse files
committed
fix(telemetry): show disclosure on first proxy start for existing users
Existing users upgrading from pre-telemetry versions were getting events sent without seeing the disclosure. Now showTelemetryDisclosureIfNeeded() is called from every entry point: foreground start, daemon start, service install, and setup wizard — idempotent, only prints once.
1 parent 4fb635d commit 414e0a7

6 files changed

Lines changed: 47 additions & 32 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ai-cc-router",
3-
"version": "0.2.5",
3+
"version": "0.2.6",
44
"description": "Round-robin proxy for Claude Max OAuth tokens — use multiple Claude Max accounts with Claude Code",
55
"type": "module",
66
"bin": {

src/cli/cmd-service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { fileURLToPath } from "url";
66
import { join, dirname } from "path";
77
import chalk from "chalk";
88
import { detectPlatform } from "../utils/platform.js";
9+
import { showTelemetryDisclosureIfNeeded } from "../utils/telemetry.js";
910

1011
const execFileAsync = promisify(execFile);
1112

@@ -25,6 +26,10 @@ export function registerService(program: Command): void {
2526
.action(async () => {
2627
console.log(chalk.cyan("\nInstalling cc-router as a system service...\n"));
2728

29+
// Show telemetry disclosure once before the service takes over — after
30+
// this point output goes to PM2 logs, not the user's terminal.
31+
showTelemetryDisclosureIfNeeded();
32+
2833
// 1. Verify PM2 is installed
2934
const pm2Version = await getPm2Version();
3035
if (!pm2Version) {

src/cli/cmd-setup.ts

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ import {
2828
openNetworkExtensionSettings,
2929
} from "../interceptor/mitmproxy-manager.js";
3030
import { printDesktopSupportExplainer, printNetworkExtensionInstructions } from "./cmd-client.js";
31-
import { loadTelemetryState, writeTelemetryState } from "../config/telemetry.js";
32-
import { trackEvent } from "../utils/telemetry.js";
31+
import { trackEvent, showTelemetryDisclosureIfNeeded } from "../utils/telemetry.js";
3332

3433
const execFileAsync = promisify(execFile);
3534

@@ -261,32 +260,6 @@ async function runSetupWizard({ addMode }: { addMode: boolean }): Promise<void>
261260
await runPostSetupFlow(merged.length);
262261
}
263262

264-
// Anonymous telemetry disclosure, shown exactly once after a successful setup.
265-
// Controlled by telemetry.disclosureShown in ~/.cc-router/telemetry.json.
266-
function showTelemetryDisclosureIfNeeded(): void {
267-
try {
268-
const state = loadTelemetryState();
269-
if (state.disclosureShown) return;
270-
console.log();
271-
console.log(chalk.dim("─".repeat(60)));
272-
console.log(chalk.bold(" Anonymous usage analytics"));
273-
console.log();
274-
console.log(" CC-Router sends anonymous lifecycle events (version, OS,");
275-
console.log(" startup, heartbeat) to help us understand usage and prioritize");
276-
console.log(" improvements. No IPs, no tokens, no prompts, no request content.");
277-
console.log();
278-
console.log(` Disable: ${chalk.cyan("cc-router telemetry off")}`);
279-
console.log(` Or set: ${chalk.cyan("DO_NOT_TRACK=1")} | ${chalk.cyan("CC_ROUTER_TELEMETRY=0")}`);
280-
console.log(` Source: ${chalk.dim("src/utils/telemetry.ts")}`);
281-
console.log(chalk.dim("─".repeat(60)));
282-
console.log();
283-
state.disclosureShown = true;
284-
writeTelemetryState(state);
285-
} catch {
286-
// never block setup on telemetry errors
287-
}
288-
}
289-
290263
// ─── Post-setup interactive flow ─────────────────────────────────────────────
291264

292265
async function runPostSetupFlow(accountCount: number): Promise<void> {

src/cli/cmd-start.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { execFile } from "child_process";
33
import { promisify } from "util";
44
import chalk from "chalk";
55
import { PROXY_PORT, LITELLM_PORT, ACCOUNTS_PATH } from "../config/paths.js";
6+
import { showTelemetryDisclosureIfNeeded } from "../utils/telemetry.js";
67

78
const execFileAsync = promisify(execFile);
89

@@ -16,6 +17,9 @@ export function registerStart(program: Command): void {
1617
.option("--accounts <path>", "Path to accounts.json", ACCOUNTS_PATH)
1718
.action(async (opts: { port: string; daemon?: boolean; litellm?: string | boolean; accounts: string }) => {
1819
if (opts.daemon) {
20+
// Show telemetry disclosure in the user's terminal before handing off
21+
// to PM2 — once the daemon starts, its stdout goes to PM2 logs.
22+
showTelemetryDisclosureIfNeeded();
1923
await startDaemon();
2024
return;
2125
}

src/proxy/server.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { TokenPool } from "./token-pool.js";
99
import { needsRefresh, refreshAccountToken, saveAccounts, startRefreshLoop } from "./token-refresher.js";
1010
import { loadAccounts, accountsFileExists, readAccountsFromPath, readConfig } from "../config/manager.js";
1111
import { checkForUpdate, performUpdate, restartSelf } from "../utils/self-update.js";
12-
import { trackEvent, startHeartbeat } from "../utils/telemetry.js";
12+
import { trackEvent, startHeartbeat, showTelemetryDisclosureIfNeeded } from "../utils/telemetry.js";
1313
import { loadTelemetryState } from "../config/telemetry.js";
1414
import { logRoute, logError, logStartup } from "./logger.js";
1515
import { stats } from "./stats.js";
@@ -447,8 +447,11 @@ export async function startServer(opts: ServerOptions = {}): Promise<void> {
447447
logStartup(port, host, mode, target, accounts.length);
448448
if (autoUpdate) console.log(chalk.gray(" Auto-update: enabled (patch/minor)"));
449449

450-
// Anonymous telemetry — fire-and-forget, never blocks proxy startup
450+
// Anonymous telemetry — fire-and-forget, never blocks proxy startup.
451+
// Show the disclosure on the very first start (covers existing users
452+
// upgrading from versions before telemetry existed) before sending anything.
451453
try {
454+
showTelemetryDisclosureIfNeeded();
452455
const telemetryState = loadTelemetryState();
453456
// First-run detection: if the install is brand new, emit app_started too
454457
const firstRunAge = Date.now() - new Date(telemetryState.firstRunAt).getTime();

src/utils/telemetry.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os from "os";
2-
import { isTelemetryEnabled, loadTelemetryState } from "../config/telemetry.js";
2+
import chalk from "chalk";
3+
import { isTelemetryEnabled, loadTelemetryState, writeTelemetryState } from "../config/telemetry.js";
34
import { detectPlatform } from "./platform.js";
45
import { getCurrentVersion } from "./self-update.js";
56

@@ -107,3 +108,32 @@ export function startHeartbeat(accountCount: number): void {
107108
}, 6 * 60 * 60 * 1000);
108109
timer.unref();
109110
}
111+
112+
// One-time disclosure shown the very first time CC-Router runs after install
113+
// or upgrade. Idempotent — gated by telemetry.disclosureShown so it's safe to
114+
// call from multiple entry points (setup wizard, foreground start, daemon
115+
// start, service install). Returns true if the disclosure was just shown.
116+
export function showTelemetryDisclosureIfNeeded(): boolean {
117+
try {
118+
const state = loadTelemetryState();
119+
if (state.disclosureShown) return false;
120+
console.log();
121+
console.log(chalk.dim("─".repeat(60)));
122+
console.log(chalk.bold(" Anonymous usage analytics"));
123+
console.log();
124+
console.log(" CC-Router sends anonymous lifecycle events (version, OS,");
125+
console.log(" startup, heartbeat) to help us understand usage and prioritize");
126+
console.log(" improvements. No IPs, no tokens, no prompts, no request content.");
127+
console.log();
128+
console.log(` Disable: ${chalk.cyan("cc-router telemetry off")}`);
129+
console.log(` Or set: ${chalk.cyan("DO_NOT_TRACK=1")} | ${chalk.cyan("CC_ROUTER_TELEMETRY=0")}`);
130+
console.log(` Source: ${chalk.dim("src/utils/telemetry.ts")}`);
131+
console.log(chalk.dim("─".repeat(60)));
132+
console.log();
133+
state.disclosureShown = true;
134+
writeTelemetryState(state);
135+
return true;
136+
} catch {
137+
return false;
138+
}
139+
}

0 commit comments

Comments
 (0)