@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
1,282 lines (1,277 loc) • 94.3 kB
JavaScript
/**
* Proxy CLI Commands for NeuroLink
*
* Implements commands for managing the Claude multi-account proxy:
* - neurolink proxy start — Start the proxy server
* - neurolink proxy status — Show proxy status (accounts, sessions, routing)
*
* The proxy creates a NeuroLink instance and builds a Hono app that registers
* Claude-compatible proxy routes. All requests flow through ctx.neurolink
* (generate/stream), with an optional ModelRouter for model remapping.
*/
import { spawn } from "node:child_process";
import { homedir } from "node:os";
import { dirname, join, resolve } from "node:path";
import chalk from "chalk";
import ora from "ora";
import { buildProxyHealthResponse, createProxyReadinessState, markProxyReady, waitForProxyReadiness, } from "../../lib/proxy/proxyHealth.js";
import { logger } from "../../lib/utils/logger.js";
import { formatUptime, isProcessRunning, StateFileManager, } from "../utils/serverUtils.js";
import { loadProxyEnvFile, resolveProxyEnvFile, } from "../../lib/proxy/proxyEnv.js";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
const _require = createRequire(import.meta.url);
const { version: PROXY_VERSION } = _require("../../../package.json");
const PROXY_TELEMETRY_SCRIPT_PATH = fileURLToPath(new URL("../../../scripts/observability/manage-local-openobserve.sh", import.meta.url));
// =============================================================================
// STATE MANAGEMENT
// =============================================================================
let proxyStateManager = new StateFileManager("proxy-state.json");
/**
* Reinitialise the state manager with a custom base directory.
* Called when --dev redirects writable paths to .neurolink-dev/.
*/
function setProxyStateDir(baseDir) {
proxyStateManager = new StateFileManager("proxy-state.json", baseDir);
}
function saveProxyState(state) {
proxyStateManager.save(state);
}
function loadProxyState() {
return proxyStateManager.load();
}
function clearProxyState() {
proxyStateManager.clear();
}
const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
const PLIST_LABEL = "com.neurolink.proxy";
const PLIST_DIR = join(homedir(), "Library", "LaunchAgents");
const PLIST_PATH = join(PLIST_DIR, `${PLIST_LABEL}.plist`);
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function getProcessStatus(pid) {
try {
process.kill(pid, 0);
return "running";
}
catch (error) {
const code = error.code;
if (code === "ESRCH") {
return "not_running";
}
if (code === "EPERM") {
return "unknown";
}
return "not_running";
}
}
/** Resolve the primary-account info shown in /status. Reads the operator's
* configured email from proxy config and cross-checks it against the token
* store; falls back to the first enabled anthropic account when not set or
* when the configured account isn't currently usable. */
async function resolveStatusPrimaryAccount(proxyConfig) {
const configured = proxyConfig?.routing?.primaryAccount?.trim() || null;
let enabledAnthropicKeys = [];
try {
const { tokenStore } = await import("../../lib/auth/tokenStore.js");
const all = await tokenStore.listByPrefix("anthropic:");
const filtered = [];
for (const key of all) {
const disabled = await tokenStore.isDisabled(key);
if (!disabled) {
filtered.push(key);
}
}
enabledAnthropicKeys = filtered;
}
catch (err) {
logger.debug(`[proxy] /status: failed to enumerate anthropic accounts: ${err instanceof Error ? err.message : String(err)}`);
}
if (configured) {
const configuredKey = `anthropic:${configured}`;
if (enabledAnthropicKeys.includes(configuredKey)) {
return {
configured,
key: configuredKey,
label: configured,
source: "configured",
};
}
}
const fallbackKey = enabledAnthropicKeys[0] ?? null;
const fallbackLabel = fallbackKey
? (fallbackKey.split(":")[1] ?? null)
: null;
return {
configured,
key: fallbackKey,
label: fallbackLabel,
source: "fallback",
};
}
/**
* Check if the launchd service is loaded and actively managing the proxy.
* Returns true if launchctl reports the service as running.
*/
async function isLaunchdManaging() {
if (process.platform !== "darwin") {
return false;
}
try {
const { execFileSync } = await import("node:child_process");
const uid = process.getuid?.() ?? 501;
const output = execFileSync("launchctl", ["print", `gui/${uid}/${PLIST_LABEL}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
return /state\s*=\s*running/.test(output);
}
catch {
return false;
}
}
/**
* Attempt to restart the proxy via launchd kickstart.
* Returns true if the proxy comes back healthy within timeoutMs.
*/
async function tryLaunchdRestart(host, port, timeoutMs = 15_000) {
if (process.platform !== "darwin") {
return false;
}
try {
const { existsSync } = await import("fs");
if (!existsSync(PLIST_PATH)) {
return false;
}
}
catch {
return false;
}
try {
const { execFileSync } = await import("node:child_process");
const uid = process.getuid?.() ?? 501;
execFileSync("launchctl", ["kickstart", "-k", `gui/${uid}/${PLIST_LABEL}`], { stdio: "ignore", timeout: 5_000 });
}
catch {
return false;
}
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
await sleep(1_000);
if (await isProxyHealthy(host, port, 2_000)) {
return true;
}
}
return false;
}
/** Keys we manage in Claude Code's settings.env */
const PROXY_MANAGED_KEYS = ["ANTHROPIC_BASE_URL", "ENABLE_TOOL_SEARCH"];
async function setClaudeProxySettings(baseUrl) {
const fs = await import("fs");
let settings = {};
try {
settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf8"));
}
catch {
// file missing/invalid — create fresh settings object
}
const env = (settings.env ?? {});
// Preserve original values so clearClaudeProxySettings can restore them.
// Only snapshot once — subsequent calls should not overwrite the snapshot.
const originals = (settings
.__proxy_original_env ?? {});
for (const key of PROXY_MANAGED_KEYS) {
if (!(key in originals)) {
originals[key] = key in env ? env[key] : null;
}
}
settings.__proxy_original_env = originals;
env.ANTHROPIC_BASE_URL = baseUrl;
env.ENABLE_TOOL_SEARCH = "true";
settings.env = env;
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
}
async function clearClaudeProxySettings(expectedBaseUrl) {
const fs = await import("fs");
let settings;
try {
settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf8"));
}
catch {
return false;
}
const env = settings.env;
if (!env) {
return false;
}
if (expectedBaseUrl &&
typeof env.ANTHROPIC_BASE_URL === "string" &&
env.ANTHROPIC_BASE_URL !== expectedBaseUrl) {
// User switched to a different proxy URL; do not clobber.
return false;
}
const hadBaseUrl = typeof env.ANTHROPIC_BASE_URL === "string";
const hadToolSearch = env.ENABLE_TOOL_SEARCH === "true";
// Restore original values if they were saved, otherwise delete the keys
const originals = (settings
.__proxy_original_env ?? {});
for (const key of PROXY_MANAGED_KEYS) {
const original = originals[key];
if (original !== undefined && original !== null) {
// Restore the value that existed before the proxy was started
env[key] = original;
}
else {
// Key did not exist before — remove it
delete env[key];
}
}
delete settings.__proxy_original_env;
if (Object.keys(env).length === 0) {
delete settings.env;
}
else {
settings.env = env;
}
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
return hadBaseUrl || hadToolSearch;
}
async function isProxyHealthy(host, port, timeoutMs) {
try {
const response = await fetch(`http://${host}:${port}/health`, {
signal: AbortSignal.timeout(timeoutMs),
});
return response.ok;
}
catch {
return false;
}
}
// ---------------------------------------------------------------------------
// Stable entrypoint for launchd
// ---------------------------------------------------------------------------
/**
* Path to a small trampoline script that the plist invokes.
* The trampoline re-resolves `neurolink` via PATH on every launch,
* so launchd never gets pinned to a version-specific store path.
*/
const TRAMPOLINE_DIR = join(homedir(), ".neurolink", "bin");
const TRAMPOLINE_PATH = join(TRAMPOLINE_DIR, "neurolink-proxy");
/**
* Verify a candidate bin path actually runs by invoking `--version` on it.
* Returns the version string on success, or undefined on any failure.
*/
function probeBinVersion(binPath) {
try {
const { execFileSync } = _require("node:child_process");
const out = execFileSync(binPath, ["--version"], {
encoding: "utf8",
timeout: 5_000,
stdio: ["ignore", "pipe", "ignore"],
}).trim();
return out || undefined;
}
catch {
return undefined;
}
}
/**
* Write (or overwrite) the trampoline shell script.
*
* Defensive design: the trampoline tries multiple candidates in order and
* only `exec`s one whose `--version` check succeeds. If every PATH-based
* candidate is broken (stale shims, missing packages), it falls back to the
* baked-in `node + script` path that was verified to work at install time.
*/
function writeTrampoline() {
const { writeFileSync, mkdirSync, existsSync, chmodSync } = _require("fs");
if (!existsSync(TRAMPOLINE_DIR)) {
mkdirSync(TRAMPOLINE_DIR, { recursive: true });
}
// Baked-in fallback: the specific node + JS script currently running
// (guaranteed to work, since we ARE running). Used only if all PATH-based
// candidates fail their --version probe.
const bakedNode = process.execPath;
const bakedScript = process.argv[1] ?? join(__dirname, "..", "index.js");
// Shell-escape the baked paths (they shouldn't contain quotes in practice,
// but be safe for paths with spaces).
const shEscape = (s) => `'${s.replace(/'/g, "'\\''")}'`;
const script = `#!/bin/sh
# Auto-generated by \`neurolink proxy install\` — do not edit.
# Resolves a working neurolink binary on every launchd invocation so the
# plist never gets pinned to a broken/stale shim.
# Probe a candidate: must be executable and respond to --version cleanly.
_try() {
[ -n "$1" ] && [ -x "$1" ] || return 1
"$1" --version >/dev/null 2>&1 || return 1
return 0
}
# 1. Explicit user override (escape hatch for broken environments).
if [ -n "\${NEUROLINK_BIN:-}" ]; then
if _try "$NEUROLINK_BIN"; then
exec "$NEUROLINK_BIN" "$@"
fi
echo "[neurolink-proxy] WARN: NEUROLINK_BIN=$NEUROLINK_BIN is not runnable, trying defaults" >&2
fi
# 2. PATH-based and common install locations. First working one wins.
for cand in \\
"$(command -v neurolink 2>/dev/null || true)" \\
"\${PNPM_HOME:-}/neurolink" \\
"$HOME/.local/share/pnpm/neurolink" \\
"$HOME/Library/pnpm/neurolink" \\
"/usr/local/bin/neurolink" \\
"/opt/homebrew/bin/neurolink"; do
if _try "$cand"; then
exec "$cand" "$@"
fi
done
# 3. Baked-in fallback: the exact node + script that worked at install time.
# Always valid at install time; may become stale after package updates
# (but at that point the PATH candidates above should work).
BAKED_NODE=${shEscape(bakedNode)}
BAKED_SCRIPT=${shEscape(bakedScript)}
if [ -x "$BAKED_NODE" ] && [ -f "$BAKED_SCRIPT" ]; then
exec "$BAKED_NODE" "$BAKED_SCRIPT" "$@"
fi
echo "[neurolink-proxy] FATAL: no working neurolink binary found." >&2
echo "[neurolink-proxy] Tried: PATH, \\$PNPM_HOME, \\$HOME/.local/share/pnpm, \\$HOME/Library/pnpm, /usr/local/bin, /opt/homebrew/bin, baked-in install path." >&2
echo "[neurolink-proxy] Fix: reinstall with 'pnpm add -g @juspay/neurolink' or set NEUROLINK_BIN=/path/to/working/neurolink." >&2
exit 127
`;
writeFileSync(TRAMPOLINE_PATH, script, { mode: 0o755 });
chmodSync(TRAMPOLINE_PATH, 0o755);
}
/**
* Check whether a pnpm binary can install into the global store.
*
* Multiple pnpm major versions can coexist (e.g., standalone v8 + nvm v10).
* They use different store layouts (`store/v10` vs `store/v10/v3`), so a
* pnpm that passes `--version` may still fail `pnpm add -g` with
* ERR_PNPM_UNEXPECTED_STORE. We detect this by running `pnpm root -g` and
* checking whether the resolved global root directory actually exists on disk.
*/
function canInstallGlobally(pnpmPath) {
try {
const { execFileSync } = _require("node:child_process");
const { existsSync } = _require("fs");
const globalRoot = execFileSync(pnpmPath, ["root", "-g"], {
encoding: "utf8",
timeout: 10_000,
stdio: ["ignore", "pipe", "ignore"],
}).trim();
// If the global root exists, this pnpm version is compatible with
// the current store layout and can install packages there.
return !!globalRoot && existsSync(globalRoot);
}
catch {
return false;
}
}
/**
* Resolve the `pnpm` binary defensively.
*
* Tries multiple candidates in order of preference. Each candidate must:
* 1. Respond to `pnpm --version` (binary works)
* 2. Have a compatible global store (`pnpm root -g` points to an existing dir)
*
* This defends against environments with multiple pnpm major versions
* (e.g., standalone v8 + nvm v10) where the wrong one would fail with
* ERR_PNPM_UNEXPECTED_STORE on `pnpm add -g`.
*
* Honors `NEUROLINK_PNPM_PATH` as an escape hatch.
*/
function resolveFullPnpmPath() {
const candidates = [];
// 1. User override
if (process.env.NEUROLINK_PNPM_PATH) {
candidates.push(process.env.NEUROLINK_PNPM_PATH);
}
// 2. PNPM_HOME (pnpm's own env variable)
if (process.env.PNPM_HOME) {
candidates.push(join(process.env.PNPM_HOME, "pnpm"));
}
// 3. `which pnpm` — whatever is on PATH
try {
const { execFileSync } = _require("node:child_process");
const whichOut = execFileSync("which", ["pnpm"], {
encoding: "utf8",
timeout: 5_000,
stdio: ["ignore", "pipe", "ignore"],
}).trim();
if (whichOut) {
candidates.push(whichOut);
}
}
catch {
// ignore
}
// 4. Common standalone installer locations
candidates.push(join(homedir(), ".local", "share", "pnpm", "pnpm"));
candidates.push(join(homedir(), "Library", "pnpm", "pnpm"));
// Dedupe while preserving order
const seen = new Set();
const unique = candidates.filter((p) => {
if (!p || seen.has(p)) {
return false;
}
seen.add(p);
return true;
});
// Probe each candidate: must pass --version AND have a compatible global store
const tried = unique.map((path) => {
const version = probeBinVersion(path);
const working = version !== undefined;
const globalStoreOk = working ? canInstallGlobally(path) : false;
return { path, version, working, globalStoreOk };
});
// Prefer a candidate that can actually install globally
const fullyWorking = tried.find((r) => r.working && r.globalStoreOk);
if (fullyWorking) {
return {
bin: fullyWorking.path,
resolved: true,
version: fullyWorking.version,
tried,
};
}
// Fall back to any candidate that at least responds to --version
// (better than nothing — the install may still fail, but will be
// caught and suppressed by the caller)
const anyWorking = tried.find((r) => r.working);
if (anyWorking) {
return {
bin: anyWorking.path,
resolved: true,
version: anyWorking.version,
tried,
};
}
return { bin: "pnpm", resolved: false, tried };
}
function spawnFailOpenGuard(host, port, parentPid) {
// The guard runs the same version as this process, so process.argv[1]
// (the currently-running script) is correct here — no stale-path risk.
const entryScript = process.argv[1];
if (!entryScript) {
return undefined;
}
const args = [
entryScript,
"proxy",
"guard",
"--host",
host,
"--port",
String(port),
"--parent-pid",
String(parentPid),
"--quiet",
];
// Write guard stdout/stderr to a log file instead of discarding them.
const { openSync, closeSync, mkdirSync, existsSync } = _require("fs");
const guardLogDir = join(homedir(), ".neurolink", "logs");
if (!existsSync(guardLogDir)) {
mkdirSync(guardLogDir, { recursive: true });
}
const guardLogPath = join(guardLogDir, "proxy-guard.log");
const logFd = openSync(guardLogPath, "a");
try {
const child = spawn(process.execPath, args, {
detached: true,
stdio: ["ignore", logFd, logFd],
});
child.unref();
return child.pid;
}
catch (error) {
logger.debug(`[proxy] failed to start fail-open guard: ${error instanceof Error ? error.message : String(error)}`);
return undefined;
}
finally {
closeSync(logFd); // parent closes its copy; child keeps the fd
}
}
async function runProxyTelemetryManager(command) {
const { existsSync } = await import("fs");
if (!existsSync(PROXY_TELEMETRY_SCRIPT_PATH)) {
throw new Error("Proxy telemetry helper files were not found in this installation. Reinstall NeuroLink with observability assets included.");
}
await new Promise((resolve, reject) => {
const child = spawn("bash", [PROXY_TELEMETRY_SCRIPT_PATH, command], {
stdio: "inherit",
env: process.env,
});
child.on("error", (error) => {
reject(error);
});
child.on("exit", (code, signal) => {
if (signal) {
reject(new Error(`proxy telemetry ${command} terminated by signal ${signal}`));
return;
}
if (code !== 0) {
reject(new Error(`proxy telemetry ${command} exited with code ${code ?? 1}`));
return;
}
resolve();
});
});
}
// =============================================================================
// STARTUP BANNER
// =============================================================================
function printProxyBanner(url, strategy) {
logger.always("");
logger.always(chalk.bold.cyan("NeuroLink Claude Proxy"));
logger.always(chalk.gray("=".repeat(50)));
logger.always("");
logger.always(` ${chalk.bold("URL:")} ${chalk.cyan(url)}`);
logger.always(` ${chalk.bold("Strategy:")} ${chalk.cyan(strategy)}`);
logger.always(` ${chalk.bold("PID:")} ${chalk.cyan(process.pid)}`);
logger.always("");
logger.always(chalk.bold("Endpoints:"));
logger.always(` ${chalk.blue("POST")} /v1/messages — Proxy to Anthropic`);
logger.always(` ${chalk.green("GET")} /health — Health check`);
logger.always(` ${chalk.green("GET")} /status — Detailed status`);
logger.always("");
logger.always(chalk.bold("Set in Claude Code:"));
logger.always(` ${chalk.cyan(`ANTHROPIC_BASE_URL=${url}`)}`);
logger.always("");
logger.always(chalk.gray("Press Ctrl+C to stop the proxy"));
logger.always("");
}
export function mapClaudeErrorTypeToStatus(errorType) {
switch (errorType) {
case "invalid_request_error":
return 400;
case "authentication_error":
return 401;
case "permission_error":
return 403;
case "not_found_error":
return 404;
case "request_too_large":
return 413;
case "rate_limit_error":
return 429;
case "overloaded_error":
return 529;
case "api_error":
default:
return 502;
}
}
async function ensureProxyStartAllowed(spinner) {
const ignoreLaunchd = process.env.NEUROLINK_PROXY_IGNORE_LAUNCHD === "1" ||
process.env.NEUROLINK_PROXY_IGNORE_LAUNCHD === "true";
const existingState = loadProxyState();
if (existingState) {
if (isProcessRunning(existingState.pid)) {
// Test / dev escape hatch: when NEUROLINK_PROXY_IGNORE_LAUNCHD is set,
// allow starting a second proxy on the test's requested port even if
// a launchd-managed instance is using a different port (its state
// file is what we hit here). The shared port-conflict surface remains
// — node will fail to bind if the requested port is actually busy.
if (!ignoreLaunchd) {
if (spinner) {
spinner.fail(chalk.red(`Proxy already running on port ${existingState.port} (PID: ${existingState.pid})`));
}
logger.always(chalk.yellow("Stop it first or use 'neurolink proxy status' to inspect"));
process.exit(process.ppid === 1 ? 0 : 1);
}
}
else {
clearProxyState();
}
}
if (process.ppid === 1 || !(await isLaunchdManaging())) {
return;
}
// Test / dev escape hatch: when starting on an explicit non-default port,
// the launchd-managed proxy (typically on its own port) cannot conflict.
// Setting `NEUROLINK_PROXY_IGNORE_LAUNCHD=1` lets the test suite start a
// standalone proxy alongside the launchd one without removing the daemon.
if (process.env.NEUROLINK_PROXY_IGNORE_LAUNCHD === "1" ||
process.env.NEUROLINK_PROXY_IGNORE_LAUNCHD === "true") {
return;
}
if (spinner) {
spinner.fail(chalk.red("Proxy is managed by launchd. Manual start would cause port conflicts."));
}
logger.always(chalk.yellow("Use 'neurolink proxy uninstall' to remove the service first, " +
"or 'launchctl kickstart gui/$(id -u)/com.neurolink.proxy' to restart."));
process.exit(1);
}
async function loadProxyStartEnv(argv, spinner) {
try {
const envResult = await loadProxyEnvFile({
explicitEnvFile: argv.envFile,
});
if (spinner && envResult.path) {
spinner.text = `Loaded proxy env from ${envResult.path}`;
}
return envResult.path;
}
catch (error) {
if (spinner) {
spinner.fail(chalk.red(error instanceof Error ? error.message : String(error)));
}
process.exit(1);
}
}
async function createProxyNeurolinkRuntime(logsDir) {
process.env.NEUROLINK_SKIP_MCP = "true";
const { NeuroLink } = await import("../../lib/neurolink.js");
const neurolink = new NeuroLink();
const { initRequestLogger, cleanupLogs } = await import("../../lib/proxy/requestLogger.js");
initRequestLogger(true, logsDir);
cleanupLogs(7, 500);
return { neurolink, cleanupLogs };
}
async function loadProxyStartConfiguration(argv, spinner) {
const configPath = argv.config ?? join(homedir(), ".neurolink", "proxy-config.yaml");
let proxyConfig = null;
try {
const { loadProxyConfig } = await import("../../lib/proxy/proxyConfig.js");
proxyConfig = (await loadProxyConfig(configPath));
if (spinner) {
spinner.text = `Loaded proxy config from ${configPath}`;
}
}
catch (configError) {
if (argv.config) {
if (spinner) {
spinner.fail(chalk.red(`Failed to load proxy config: ${configPath}`));
}
process.exit(1);
}
const isNotFound = configError instanceof Error &&
"code" in configError &&
configError.code === "ENOENT";
if (!isNotFound) {
logger.warn(`[proxy] Ignoring default config ${configPath}: ${configError instanceof Error ? configError.message : String(configError)}`);
}
}
const strategy = (argv.strategy ??
proxyConfig?.routing?.strategy ??
"fill-first");
let modelRouter;
if (proxyConfig?.routing) {
const { ModelRouter } = await import("../../lib/proxy/modelRouter.js");
modelRouter = new ModelRouter({
strategy,
modelMappings: proxyConfig.routing.modelMappings ?? [],
fallbackChain: proxyConfig.routing.fallbackChain ?? [],
passthroughModels: proxyConfig.routing.passthroughModels,
});
}
const primaryAccountKey = await resolveBootPrimaryAccountKey(proxyConfig?.routing?.primaryAccount);
return {
configPath,
proxyConfig,
strategy,
modelRouter,
passthrough: argv.passthrough ?? false,
primaryAccountKey,
};
}
/** Resolve the operator's configured primary email to a stable token-store
* key (anthropic:<email>). Cross-checks the token store and emits a one-time
* startup warning if the configured account isn't authenticated — but still
* returns the key so it activates automatically once the user runs
* `auth login --add`. */
async function resolveBootPrimaryAccountKey(primaryEmail) {
const trimmed = primaryEmail?.trim();
if (!trimmed) {
return undefined;
}
const key = `anthropic:${trimmed}`;
try {
const { tokenStore } = await import("../../lib/auth/tokenStore.js");
const known = await tokenStore.listByPrefix("anthropic:");
if (!known.includes(key)) {
logger.warn(`[proxy] WARN: configured routing.primaryAccount=${trimmed} not ` +
`found in token store; falling back to first enabled account. ` +
`Run \`neurolink auth login --add\` to authenticate it, or ` +
`\`neurolink auth clear-primary\` to remove the setting.`);
}
}
catch (err) {
logger.debug(`[proxy] could not validate primary account against token store: ${err instanceof Error ? err.message : String(err)}`);
}
return key;
}
async function createProxyStartApp(params) {
const { createClaudeProxyRoutes } = await import("../../lib/server/routes/claudeProxyRoutes.js");
const { Hono } = await import("hono");
const app = new Hono();
const readiness = createProxyReadinessState();
app.onError((err, c) => {
const errMsg = err instanceof Error ? err.message : String(err);
logger.always(`[proxy] unhandled error: ${errMsg}`);
if (err instanceof Error && err.stack) {
logger.debug(`[proxy] stack: ${err.stack}`);
}
return c.json({
type: "error",
error: {
type: "api_error",
message: `Proxy internal error: ${errMsg}`,
},
}, 502);
});
const routeGroup = createClaudeProxyRoutes(params.modelRouter, "", params.strategy, params.passthrough, params.primaryAccountKey);
for (const route of routeGroup.routes) {
const method = route.method.toLowerCase();
app[method](route.path, async (c) => {
const emptyBody = {};
let body;
let rawBody;
if (method === "post") {
rawBody = await c.req.text().catch(() => undefined);
try {
body = rawBody ? JSON.parse(rawBody) : emptyBody;
}
catch {
return c.json({
type: "error",
error: {
type: "invalid_request_error",
message: "Request body must be valid JSON",
},
}, 400);
}
}
const model = body?.model ?? "-";
const stream = body?.stream
? "stream"
: "non-stream";
const bodyRec = body;
const toolCount = Array.isArray(bodyRec?.tools)
? bodyRec.tools.length
: 0;
logger.always(`[proxy] ${c.req.method} ${c.req.path} → model=${model} ${stream} tools=${toolCount}`);
const ctx = {
requestId: crypto.randomUUID(),
method: c.req.method,
path: c.req.path,
headers: Object.fromEntries(c.req.raw.headers.entries()),
query: Object.fromEntries(new URL(c.req.url).searchParams.entries()),
params: c.req.param(),
body,
rawBody,
neurolink: params.neurolink,
toolRegistry: params.neurolink.getToolRegistry(),
timestamp: Date.now(),
metadata: {},
};
const result = await route.handler(ctx);
if (result instanceof Response) {
return result;
}
if (result &&
typeof result === "object" &&
Symbol.asyncIterator in Object(result)) {
const iterator = result[Symbol.asyncIterator]();
let cancelled = false;
const responseStream = new ReadableStream({
async start(controller) {
try {
while (!cancelled) {
const { value, done } = await iterator.next();
if (done) {
break;
}
controller.enqueue(new TextEncoder().encode(value));
}
controller.close();
}
catch (streamErr) {
if (cancelled) {
controller.close();
return;
}
const errMsg = streamErr instanceof Error
? streamErr.message
: String(streamErr);
const errorEvent = `event: error\ndata: ${JSON.stringify({ type: "error", error: { type: "api_error", message: `Stream interrupted: ${errMsg}` } })}\n\n`;
try {
controller.enqueue(new TextEncoder().encode(errorEvent));
}
catch {
// Controller already errored — ignore
}
controller.close();
}
},
async cancel() {
cancelled = true;
await iterator.return?.();
},
});
return new Response(responseStream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
if (result &&
typeof result === "object" &&
"httpStatus" in result) {
const httpResult = result;
const status = httpResult.httpStatus ?? 200;
delete httpResult.httpStatus;
return c.json(result, status);
}
if (result &&
typeof result === "object" &&
"type" in result &&
result.type === "error") {
const errorResult = result;
const status = mapClaudeErrorTypeToStatus(errorResult.error?.type);
return c.json(result, status);
}
return c.json(result ?? {});
});
}
app.get("/health", (c) => c.json(buildProxyHealthResponse(readiness, {
strategy: params.strategy,
passthrough: params.passthrough,
version: PROXY_VERSION,
})));
app.get("/status", async (c) => {
const { getStats } = await import("../../lib/proxy/usageStats.js");
const stats = getStats();
const health = buildProxyHealthResponse(readiness, {
strategy: params.strategy,
passthrough: params.passthrough,
version: PROXY_VERSION,
});
const primaryAccount = await resolveStatusPrimaryAccount(params.proxyConfig);
return c.json({
status: "running",
ready: health.ready,
acceptingConnections: health.acceptingConnections,
readyAt: health.readyAt,
pid: process.pid,
port: params.port,
host: params.host,
strategy: params.strategy,
uptime: process.uptime(),
version: PROXY_VERSION,
health,
stats: {
totalAttempts: stats.totalAttempts,
totalRequests: stats.totalRequests,
totalSuccess: stats.totalSuccess,
totalErrors: stats.totalErrors,
totalRateLimits: stats.totalRateLimits,
accounts: Object.values(stats.accounts).map((account) => ({
label: account.label,
type: account.type,
attempts: account.attemptCount,
requests: account.attemptCount,
success: account.successCount,
errors: account.errorCount,
rateLimits: account.rateLimitCount,
cooling: false, // No persistent cooldown — always active
})),
primaryAccount,
},
config: params.proxyConfig
? { hasRouting: !!params.proxyConfig.routing }
: null,
});
});
return { app, readiness };
}
async function initializeProxyOpenTelemetry() {
try {
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
if (!process.env.OTEL_SERVICE_NAME) {
process.env.OTEL_SERVICE_NAME = "neurolink-proxy";
}
process.env.OTEL_RESOURCE_ATTRIBUTES = [
"service.name=neurolink-proxy",
`service.version=${PROXY_VERSION}`,
"deployment.environment=local",
process.env.OTEL_RESOURCE_ATTRIBUTES,
]
.filter(Boolean)
.join(",");
const { initializeOpenTelemetry, isOpenTelemetryInitialized } = await import("../../lib/services/server/ai/observability/instrumentation.js");
const { buildObservabilityConfigFromEnv } = await import("../../lib/utils/observabilityHelpers.js");
if (isOpenTelemetryInitialized()) {
return;
}
const observabilityConfig = buildObservabilityConfigFromEnv();
const langfuseConfig = observabilityConfig?.langfuse;
const langfuseEnabled = langfuseConfig?.enabled === true;
await initializeOpenTelemetry({
enabled: langfuseEnabled,
publicKey: langfuseConfig?.publicKey || "",
secretKey: langfuseConfig?.secretKey || "",
baseUrl: langfuseConfig?.baseUrl,
environment: "proxy",
release: PROXY_VERSION,
userId: "neurolink-proxy",
autoDetectOperationName: true,
});
if (langfuseEnabled) {
logger.always(`[proxy] Langfuse enabled — exporting to ${langfuseConfig.baseUrl || "https://cloud.langfuse.com"} (environment=proxy)`);
}
if (otlpEndpoint) {
logger.always(`[proxy] OTLP exporter enabled — exporting to ${otlpEndpoint} (service.name=neurolink-proxy)`);
}
if (!langfuseEnabled && !otlpEndpoint) {
logger.always("[proxy] OpenTelemetry exporters disabled — set OTEL_EXPORTER_OTLP_ENDPOINT or Langfuse credentials to enable proxy observability");
}
}
catch (error) {
logger.debug(`[proxy] OpenTelemetry init failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);
}
}
async function refreshProxyTokensInBackground() {
const { needsRefresh, refreshToken, persistTokens } = await import("../../lib/proxy/tokenRefresh.js");
const { tokenStore } = await import("../../lib/auth/tokenStore.js");
try {
const allKeys = await tokenStore.listProviders();
const anthropicKeys = allKeys.filter((key) => key.startsWith("anthropic:"));
for (const key of anthropicKeys) {
try {
const tokens = await tokenStore.loadTokens(key);
if (!tokens) {
continue;
}
const account = {
label: key,
token: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresAt: tokens.expiresAt,
};
if (needsRefresh(account)) {
const result = await refreshToken(account);
if (result.success) {
await persistTokens({ providerKey: key }, account);
logger.debug(`[proxy] background token refresh succeeded for ${key}`);
}
}
}
catch {
// non-fatal per-account
}
}
}
catch {
// non-fatal
}
try {
const credPath = join(homedir(), ".neurolink", "anthropic-credentials.json");
const { readFileSync } = await import("fs");
const creds = JSON.parse(readFileSync(credPath, "utf8"));
if (!creds.oauth) {
return;
}
const account = {
label: "background",
token: creds.oauth.accessToken,
refreshToken: creds.oauth.refreshToken,
expiresAt: creds.oauth.expiresAt,
};
if (needsRefresh(account)) {
const result = await refreshToken(account);
if (result.success) {
await persistTokens(credPath, account);
logger.debug("[proxy] background token refresh succeeded");
}
}
}
catch {
// non-fatal
}
}
function startProxyBackgroundMaintenance(cleanupLogs) {
const refreshInterval = setInterval(() => {
void refreshProxyTokensInBackground();
}, 30_000);
const logCleanupInterval = setInterval(() => {
cleanupLogs(7, 500);
}, 60 * 60 * 1000);
return { refreshInterval, logCleanupInterval };
}
function registerProxyShutdownHandlers(params) {
const shutdown = async (signal) => {
clearInterval(params.refreshInterval);
clearInterval(params.logCleanupInterval);
logger.always(`\nShutting down proxy (${signal})...`);
try {
const { flushOpenTelemetry, shutdownOpenTelemetry } = await import("../../lib/services/server/ai/observability/instrumentation.js");
await flushOpenTelemetry();
await shutdownOpenTelemetry();
}
catch {
// non-fatal — proxy shutdown must not block on OTel
}
if (signal === "SIGINT" && !params.isDev) {
try {
const shutdownHost = params.host === "0.0.0.0" ? "localhost" : params.host;
await clearClaudeProxySettings(`http://${shutdownHost}:${params.port}`);
}
catch {
// non-fatal
}
}
try {
params.server.close?.();
}
catch {
// Best-effort close
}
clearProxyState();
process.exit(signal === "SIGINT" ? 0 : 1);
};
process.on("SIGTERM", () => {
void shutdown("SIGTERM");
});
process.on("SIGINT", () => {
void shutdown("SIGINT");
});
}
async function startProxyRuntime(params) {
const { serve } = await import("@hono/node-server");
const server = serve({
fetch: params.app.fetch,
port: params.port,
hostname: params.host,
});
// Skip the fail-open guard in dev mode — it monitors the proxy and clears
// global Claude settings on exit, which is exactly what we want to avoid.
const guardPid = params.argv.dev
? undefined
: spawnFailOpenGuard(params.host, params.port, process.pid);
const readinessHost = params.host === "0.0.0.0" ? "127.0.0.1" : params.host;
await waitForProxyReadiness({
host: readinessHost,
port: params.port,
});
markProxyReady(params.readiness);
const fallbackChain = params.proxyConfig?.routing?.fallbackChain?.map((entry) => ({
provider: entry.provider,
model: entry.model,
}));
saveProxyState({
pid: process.pid,
port: params.port,
host: params.host,
strategy: params.strategy,
startTime: new Date().toISOString(),
ready: true,
readyAt: params.readiness.readyAtMs
? new Date(params.readiness.readyAtMs).toISOString()
: undefined,
healthPath: "/health",
statusPath: "/status",
envFile: params.loadedEnvFile,
fallbackChain,
guardPid,
managedBy: process.platform === "darwin" && process.ppid === 1
? "launchd"
: "manual",
passthrough: params.passthrough,
});
if (params.spinner) {
params.spinner.succeed(chalk.green("Claude proxy started successfully"));
}
const isDev = params.argv.dev ?? false;
const normalizedHost = params.host === "0.0.0.0" ? "localhost" : params.host;
const url = `http://${normalizedHost}:${params.port}`;
printProxyBanner(url, params.strategy);
if (isDev) {
logger.always(` ${chalk.bold("Mode:")} ${chalk.magenta("dev (isolated — state in .neurolink-dev/)")}`);
}
else {
logger.always(` ${chalk.bold("Mode:")} ${chalk.cyan(params.passthrough ? "passthrough" : "full")}`);
}
if (params.passthrough) {
logger.always(chalk.yellow(" ! Passthrough mode forwards client auth directly to Anthropic"));
logger.always(chalk.dim(" Stored proxy OAuth/API credentials are ignored; clients need their own valid Anthropic auth."));
}
if (params.loadedEnvFile) {
logger.always(` ${chalk.bold("Env File:")} ${chalk.cyan(params.loadedEnvFile)}`);
}
if (!isDev) {
try {
await setClaudeProxySettings(url);
logger.always(chalk.green(" ✓ Auto-configured Claude Code settings"));
logger.always(chalk.dim(" Restart Claude Code to connect through proxy"));
}
catch (error) {
logger.debug("[proxy] Failed to auto-configure Claude Code: " +
(error instanceof Error ? error.message : String(error)));
}
}
else {
logger.always(chalk.dim(" ⊘ Dev mode: skipping client auto-configuration"));
}
const maintenance = startProxyBackgroundMaintenance(params.cleanupLogs);
registerProxyShutdownHandlers({
server,
host: params.host,
port: params.port,
isDev,
...maintenance,
});
}
async function startProxyCommandHandler(argv) {
const spinner = argv.quiet ? null : ora("Starting Claude proxy...").start();
const isDev = argv.dev ?? false;
try {
// In dev mode: redirect writable state to .neurolink-dev/ and skip singleton check
let devPaths;
if (isDev) {
const { resolveProxyPaths } = await import("../../lib/proxy/proxyPaths.js");
devPaths = resolveProxyPaths(true);
setProxyStateDir(devPaths.stateDir);
const { initAccountQuota } = await import("../../lib/proxy/accountQuota.js");
initAccountQuota(devPaths.quotaFile);
// Ensure the dev state directory exists
const { mkdirSync, existsSync } = await import("fs");
if (!existsSync(devPaths.stateDir)) {
mkdirSync(devPaths.stateDir, { recursive: true, mode: 0o700 });
}
}
if (!isDev) {
await ensureProxyStartAllowed(spinner);
}
const loadedEnvFile = await loadProxyStartEnv(argv, spinner);
const { neurolink, cleanupLogs } = await createProxyNeurolinkRuntime(devPaths?.logsDir);
const { proxyConfig, strategy, modelRouter, passthrough, primaryAccountKey, } = await loadProxyStartConfiguration(argv, spinner);
if (spinner) {
spinner.text = "Configuring server...";
}
const port = argv.port ?? 55669;
const host = argv.host ?? "127.0.0.1";
const { app, readiness } = await createProxyStartApp({
neurolink,
modelRouter,
strategy,
passthrough,
port,
host,
proxyConfig,
primaryAccountKey,
});
await initializeProxyOpenTelemetry();
if (spinner) {
spinner.text = `Starting proxy on ${host}:${port}...`;
}
await startProxyRuntime({
argv,
spinner,
app,
readiness,
host,
port,
strategy,
proxyConfig,
loadedEnvFile,
passthrough,
cleanupLogs,
});
}
catch (error) {
if (spinner) {
spinner.fail(chalk.red("Failed to start proxy"));
}
logger.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
if (argv.debug && error instanceof Error && error.stack) {
logger.error(chalk.gray(error.stack));
}
process.exit(1);
}
}
// =============================================================================
// PROXY START COMMAND
// =============================================================================
export const proxyStartCommand = {
command: "start",
describe: "Start the Claude multi-account proxy server",
builder: (yargs) => {
return yargs
.option("port", {
type: "number",
alias: "p",
default: 55669,
description: "Port to listen on",
})
.option("host", {
type: "string",
alias: "H",
default: "127.0.0.1",
description: "Host to bind to",
})
.option("strategy", {
type: "string",
alias: "s",
choices: ["fill-first", "round-robin"],
description: "Account selection strategy for routing requests (default: fill-first)",
})
.option("health-interval", {
type: "number",
alias: "healthInterval",
default: 30,
description: "Health check interval in seconds",
})
.option("quiet", {
type: "boolean",
alias: "q",
default: false,
description: "Suppress non-essential output",
})
.option("debug", {
type: "boolean",
alias: "d",
default: false,
description: "Enable debug output",
})
.option("config", {
type: "string",
alias: "c",
description: "Path to proxy config file (YAML/JSON)",
defaultDescription: "~/.neurolink/proxy-config.yaml",
})
.option("env-file", {
type: "string",
alias: "envFile",
description: "Path to proxy provider env file (overrides cwd .env for the proxy process)",
})
.option("passthrough", {
type: "boolean",
default: false,
description: "Run in transparent passthrough mode (no retry, no rotation, no polyfill)",
})
.option("dev", {
type: "boolean",
default: false,
description: "Run in isolated dev mode — state files scoped to .neurolink-dev/ in cwd, no client auto-configuration, no singleton check",
})
.example("neurolink proxy start", "Start proxy on default port 55669 with fill-first strategy")
.example("neurolink proxy start -p 8080 -s fill-first", "Start proxy on port 8080 with fill-first")
.example("neurolink proxy start --health-interval 60", "Start proxy with 60-second health checks");
},
handler: async (argv) => {
await startProxyCommandHandler(argv);
},
};
// =============================================================================
// STATUS DISPLAY HELPERS
// =============================================================================
function printStatusStats(stats) {
console.info(`\n Stats:`);
if (stats.totalAttempts !== undefined) {
console.info(` Attempts: ${stats.totalAttempts}`);
}
console.info(` Completed: ${stats.totalRequests} total, ${stats.totalSuccess} success, ${stats.totalErrors} errors`);
console.info(` Rate limits: ${stats.totalRateLimits}`);
if (stats.accounts?.length) {
console.info(`\n Acc