accf
Version:
Claude-Code Flow - One-click configuration tool for Claude Code
1,714 lines (1,694 loc) • 192 kB
JavaScript
import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, rmSync, rmdirSync, readdirSync, statSync, unlinkSync, renameSync } from 'node:fs';
import process from 'node:process';
import ansis from 'ansis';
import inquirer from 'inquirer';
import { exec as exec$1 } from 'node:child_process';
import { homedir, platform } from 'node:os';
import { promisify } from 'node:util';
import dayjs from 'dayjs';
import { dirname, join } from 'pathe';
import { fileURLToPath } from 'node:url';
import ora from 'ora';
import semver from 'semver';
import { parse, stringify } from 'smol-toml';
import { exec, x } from 'tinyexec';
import { rm, mkdir, copyFile as copyFile$1 } from 'node:fs/promises';
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
const version = "4.0.0";
const homepage = "https://github.com/Simooonn/accf";
const i18n = i18next.createInstance();
const NAMESPACES = [
"common",
"api",
"ccr",
"cli",
"cometix",
"configuration",
"errors",
"installation",
"language",
"mcp",
"menu",
"tools",
"uninstall",
"updater",
"workflow",
"codex"
];
function ensureI18nInitialized() {
if (!i18n.isInitialized) {
throw new Error(
"i18n is not initialized. Please call initI18n() in CLI command before using utility functions."
);
}
}
async function initI18n(language = "zh-CN") {
if (i18n.isInitialized) {
if (i18n.language !== language) {
await i18n.changeLanguage(language);
}
return;
}
await i18n.use(Backend).init({
lng: language,
fallbackLng: "en",
// Load all translations as a single flat structure
ns: NAMESPACES,
defaultNS: "common",
preload: [language],
// Preload the selected language
// Backend configuration for loading JSON files
backend: {
loadPath: (() => {
const currentDir = dirname(fileURLToPath(import.meta.url));
const packageRoot = (() => {
let dir = currentDir;
while (dir !== dirname(dir)) {
if (existsSync(join(dir, "package.json"))) {
return dir;
}
dir = dirname(dir);
}
return currentDir;
})();
const possibleBasePaths = [
join(currentDir, "locales"),
// Development: src/i18n/locales
join(packageRoot, "dist/i18n/locales"),
// NPM package: /node_modules/zcf/dist/i18n/locales
join(process.cwd(), "dist/i18n/locales"),
// Production build: ./dist/i18n/locales
join(currentDir, "../../../dist/i18n/locales"),
// Fallback for deep chunk paths
join(currentDir, "../../i18n/locales")
// Alternative chunk structure
];
for (const basePath of possibleBasePaths) {
const testFile = join(basePath, "zh-CN/common.json");
if (existsSync(testFile)) {
return join(basePath, "{{lng}}/{{ns}}.json");
}
}
return join(process.cwd(), "dist/i18n/locales/{{lng}}/{{ns}}.json");
})()
},
// Interpolation settings
interpolation: {
escapeValue: false
// Not needed for server-side usage
},
// Disable key separator for flat keys, enable namespace separator
keySeparator: false,
nsSeparator: ":",
// Debugging (disable for clean output)
debug: false
});
for (const ns of NAMESPACES) {
if (ns !== "common") {
await i18n.loadNamespaces(ns);
}
}
}
function format(template, values) {
if (!values)
return template;
return Object.keys(values).reduce((result, key) => {
return result.replace(new RegExp(`{${key}}`, "g"), values[key]);
}, template);
}
async function changeLanguage(lng) {
await i18n.changeLanguage(lng);
}
const index = {
__proto__: null,
changeLanguage: changeLanguage,
ensureI18nInitialized: ensureI18nInitialized,
format: format,
i18n: i18n,
initI18n: initI18n
};
const MCP_SERVICE_CONFIGS = [
{
id: "context7",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "@upstash/context7-mcp@latest"],
env: {}
}
},
{
id: "open-websearch",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "open-websearch@latest"],
env: {
MODE: "stdio",
DEFAULT_SEARCH_ENGINE: "duckduckgo",
ALLOWED_SEARCH_ENGINES: "duckduckgo,bing,brave"
}
}
},
{
id: "spec-workflow",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "@pimzino/spec-workflow-mcp@latest"],
env: {}
}
},
{
id: "mcp-deepwiki",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "mcp-deepwiki@latest"],
env: {}
}
},
{
id: "Playwright",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "@playwright/mcp@latest"],
env: {}
}
},
{
id: "exa",
requiresApiKey: true,
apiKeyEnvVar: "EXA_API_KEY",
config: {
type: "stdio",
command: "npx",
args: ["-y", "exa-mcp-server@latest"],
env: {
EXA_API_KEY: "YOUR_EXA_API_KEY"
}
}
},
{
id: "memory",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "memory-mcp@latest"],
env: {}
}
},
{
id: "sequential-thinking",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
env: {}
}
},
{
id: "browsermcp",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "@browsermcp/mcp@latest"],
env: {}
}
},
{
id: "shrimp-task-manager",
requiresApiKey: false,
config: {
type: "stdio",
command: "npx",
args: ["-y", "mcp-shrimp-task-manager@latest"],
env: {}
}
},
{
id: "mcp-feedback-enhanced",
requiresApiKey: false,
config: {
type: "stdio",
command: "uvx",
args: ["mcp-feedback-enhanced@latest"],
env: {
MCP_DEBUG: "false",
MCP_WEB_HOST: "127.0.0.1",
MCP_WEB_PORT: "8765",
MCP_LANGUAGE: "zh-CN"
}
}
},
{
id: "figma-developer",
requiresApiKey: true,
apiKeyEnvVar: "FIGMA_API_KEY",
config: {
type: "stdio",
command: "npx",
args: ["-y", "figma-developer-mcp", "--figma-api-key=YOUR_FIGMA_API_KEY"],
env: {
FIGMA_API_KEY: "YOUR_FIGMA_API_KEY"
}
}
}
];
async function getMcpServices() {
ensureI18nInitialized();
const mcpServiceList = [
{
id: "context7",
name: i18n.t("mcp:services.context7.name"),
description: i18n.t("mcp:services.context7.description")
},
{
id: "open-websearch",
name: i18n.t("mcp:services.open-websearch.name"),
description: i18n.t("mcp:services.open-websearch.description")
},
{
id: "spec-workflow",
name: i18n.t("mcp:services.spec-workflow.name"),
description: i18n.t("mcp:services.spec-workflow.description")
},
{
id: "mcp-deepwiki",
name: i18n.t("mcp:services.mcp-deepwiki.name"),
description: i18n.t("mcp:services.mcp-deepwiki.description")
},
{
id: "Playwright",
name: i18n.t("mcp:services.playwright.name"),
description: i18n.t("mcp:services.playwright.description")
},
{
id: "exa",
name: i18n.t("mcp:services.exa.name"),
description: i18n.t("mcp:services.exa.description"),
apiKeyPrompt: i18n.t("mcp:services.exa.apiKeyPrompt")
},
{
id: "memory",
name: i18n.t("mcp:services.memory.name"),
description: i18n.t("mcp:services.memory.description")
},
{
id: "sequential-thinking",
name: i18n.t("mcp:services.sequential-thinking.name"),
description: i18n.t("mcp:services.sequential-thinking.description")
},
{
id: "browsermcp",
name: i18n.t("mcp:services.browsermcp.name"),
description: i18n.t("mcp:services.browsermcp.description")
},
{
id: "shrimp-task-manager",
name: i18n.t("mcp:services.shrimp-task-manager.name"),
description: i18n.t("mcp:services.shrimp-task-manager.description")
},
{
id: "mcp-feedback-enhanced",
name: i18n.t("mcp:services.mcp-feedback-enhanced.name"),
description: i18n.t("mcp:services.mcp-feedback-enhanced.description")
},
{
id: "figma-developer",
name: i18n.t("mcp:services.figma-developer.name"),
description: i18n.t("mcp:services.figma-developer.description"),
apiKeyPrompt: i18n.t("mcp:services.figma-developer.apiKeyPrompt")
}
];
return MCP_SERVICE_CONFIGS.map((config) => {
const serviceInfo = mcpServiceList.find((s) => s.id === config.id);
const service = {
id: config.id,
name: serviceInfo?.name || config.id,
description: serviceInfo?.description || "",
requiresApiKey: config.requiresApiKey,
config: config.config
};
if (config.requiresApiKey && serviceInfo?.apiKeyPrompt) {
if (serviceInfo.apiKeyPrompt !== `mcp.services.${config.id}.apiKeyPrompt`) {
service.apiKeyPrompt = serviceInfo.apiKeyPrompt;
}
}
if (config.apiKeyEnvVar) {
service.apiKeyEnvVar = config.apiKeyEnvVar;
}
return service;
});
}
const WORKFLOW_CONFIG_BASE = [
{
id: "commonTools",
defaultSelected: true,
order: 1,
commands: ["init-project.md"],
agents: [
{ id: "init-architect", filename: "init-architect.md", required: true },
{ id: "get-current-datetime", filename: "get-current-datetime.md", required: true }
],
autoInstallAgents: true,
category: "common",
outputDir: "common"
},
{
id: "sixStepsWorkflow",
defaultSelected: true,
order: 2,
commands: ["workflow.md"],
agents: [],
autoInstallAgents: false,
category: "sixStep",
outputDir: "workflow"
},
{
id: "featPlanUx",
defaultSelected: true,
order: 3,
commands: ["feat.md"],
agents: [
{ id: "planner", filename: "planner.md", required: true },
{ id: "ui-ux-designer", filename: "ui-ux-designer.md", required: true }
],
autoInstallAgents: true,
category: "plan",
outputDir: "feat"
},
{
id: "gitWorkflow",
defaultSelected: true,
order: 4,
commands: ["git-commit.md", "git-rollback.md", "git-cleanBranches.md", "git-worktree.md"],
agents: [],
autoInstallAgents: false,
category: "git",
outputDir: "git"
},
{
id: "bmadWorkflow",
defaultSelected: true,
order: 5,
commands: ["bmad-init.md"],
agents: [],
autoInstallAgents: false,
category: "bmad",
outputDir: "bmad"
}
];
function getWorkflowConfigs() {
ensureI18nInitialized();
const workflowTranslations = [
{
id: "commonTools",
name: i18n.t("workflow:workflowOption.commonTools"),
description: i18n.t("workflow:workflowDescription.commonTools")
},
{
id: "sixStepsWorkflow",
name: i18n.t("workflow:workflowOption.sixStepsWorkflow"),
description: i18n.t("workflow:workflowDescription.sixStepsWorkflow")
},
{
id: "featPlanUx",
name: i18n.t("workflow:workflowOption.featPlanUx"),
description: i18n.t("workflow:workflowDescription.featPlanUx")
},
{
id: "gitWorkflow",
name: i18n.t("workflow:workflowOption.gitWorkflow"),
description: i18n.t("workflow:workflowDescription.gitWorkflow")
},
{
id: "bmadWorkflow",
name: i18n.t("workflow:workflowOption.bmadWorkflow"),
description: i18n.t("workflow:workflowDescription.bmadWorkflow")
}
];
return WORKFLOW_CONFIG_BASE.map((baseConfig) => {
const translation = workflowTranslations.find((t) => t.id === baseConfig.id);
return {
...baseConfig,
name: translation?.name || baseConfig.id,
description: translation?.description
};
});
}
function getWorkflowConfig(workflowId) {
return getWorkflowConfigs().find((config) => config.id === workflowId);
}
function getOrderedWorkflows() {
return getWorkflowConfigs().sort((a, b) => a.order - b.order);
}
const CLAUDE_DIR = join(homedir(), ".claude");
const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
const CLAUDE_MD_FILE = join(CLAUDE_DIR, "CLAUDE.md");
const ClAUDE_CONFIG_FILE = join(homedir(), ".claude.json");
const CLAUDE_VSC_CONFIG_FILE = join(CLAUDE_DIR, "config.json");
const ZCF_CONFIG_DIR = join(homedir(), ".ufomiao", "zcf");
const ZCF_CONFIG_FILE = join(ZCF_CONFIG_DIR, "config.toml");
const LEGACY_ZCF_CONFIG_FILES = [
join(CLAUDE_DIR, ".zcf-config.json"),
join(homedir(), ".zcf.json")
];
const CODE_TOOL_TYPES = ["claude-code", "codex"];
const DEFAULT_CODE_TOOL_TYPE = "claude-code";
const CODE_TOOL_BANNERS = {
"claude-code": "for Claude Code",
"codex": "for Codex"
};
function isCodeToolType(value) {
return CODE_TOOL_TYPES.includes(value);
}
const SUPPORTED_LANGS = ["zh-CN", "en"];
const LANG_LABELS = {
"zh-CN": "\u7B80\u4F53\u4E2D\u6587",
"en": "English"
};
const AI_OUTPUT_LANGUAGES = {
"zh-CN": { directive: "Always respond in Chinese-simplified" },
"en": { directive: "Always respond in English" },
"custom": { directive: "" }
};
function getAiOutputLanguageLabel(lang) {
if (lang in LANG_LABELS) {
return LANG_LABELS[lang];
}
if (lang === "custom" && i18n?.isInitialized) {
try {
return i18n.t("language:labels.custom");
} catch {
}
}
return lang;
}
const constants = {
__proto__: null,
AI_OUTPUT_LANGUAGES: AI_OUTPUT_LANGUAGES,
CLAUDE_DIR: CLAUDE_DIR,
CLAUDE_MD_FILE: CLAUDE_MD_FILE,
CLAUDE_VSC_CONFIG_FILE: CLAUDE_VSC_CONFIG_FILE,
CODE_TOOL_BANNERS: CODE_TOOL_BANNERS,
CODE_TOOL_TYPES: CODE_TOOL_TYPES,
ClAUDE_CONFIG_FILE: ClAUDE_CONFIG_FILE,
DEFAULT_CODE_TOOL_TYPE: DEFAULT_CODE_TOOL_TYPE,
LANG_LABELS: LANG_LABELS,
LEGACY_ZCF_CONFIG_FILES: LEGACY_ZCF_CONFIG_FILES,
SETTINGS_FILE: SETTINGS_FILE,
SUPPORTED_LANGS: SUPPORTED_LANGS,
ZCF_CONFIG_DIR: ZCF_CONFIG_DIR,
ZCF_CONFIG_FILE: ZCF_CONFIG_FILE,
getAiOutputLanguageLabel: getAiOutputLanguageLabel,
isCodeToolType: isCodeToolType
};
function getDisplayWidth(str) {
let width = 0;
for (const char of str) {
if (char.match(/[\u4E00-\u9FFF\uFF01-\uFF60\u3000-\u303F]/)) {
width += 2;
} else {
width += 1;
}
}
return width;
}
function padToDisplayWidth(str, targetWidth) {
const currentWidth = getDisplayWidth(str);
const paddingNeeded = Math.max(0, targetWidth - currentWidth);
return str + " ".repeat(paddingNeeded);
}
function displayBanner(subtitle) {
ensureI18nInitialized();
const defaultSubtitle = i18n.t("cli:banner.subtitle");
const subtitleText = subtitle || defaultSubtitle;
const paddedSubtitle = padToDisplayWidth(subtitleText, 30);
const paddedTitle = padToDisplayWidth("Zero-Config Code Flow", 60);
console.log(
ansis.cyan.bold(`
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
\u2551 \u2551
\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2551
\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2551
\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2551
\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2551
\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
\u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D ${ansis.gray(paddedSubtitle)} \u2551
\u2551 \u2551
\u2551 ${ansis.white.bold(paddedTitle)} \u2551
\u2551 \u2551
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
`)
);
}
function displayBannerWithInfo(subtitle) {
displayBanner(subtitle);
console.log(ansis.gray(` Version: ${ansis.cyan(version)} | ${ansis.cyan(homepage)}
`));
}
function getPlatform() {
const p = platform();
if (p === "win32")
return "windows";
if (p === "darwin")
return "macos";
return "linux";
}
function isTermux() {
return !!(process.env.PREFIX && process.env.PREFIX.includes("com.termux")) || !!process.env.TERMUX_VERSION || existsSync("/data/data/com.termux/files/usr");
}
function getTermuxPrefix() {
return process.env.PREFIX || "/data/data/com.termux/files/usr";
}
function isWindows() {
return getPlatform() === "windows";
}
function isWSL() {
if (process.env.WSL_DISTRO_NAME) {
return true;
}
if (existsSync("/proc/version")) {
try {
const version = readFileSync("/proc/version", "utf8");
if (version.includes("Microsoft") || version.includes("WSL")) {
return true;
}
} catch {
}
}
if (existsSync("/mnt/c")) {
return true;
}
return false;
}
function getWSLDistro() {
if (process.env.WSL_DISTRO_NAME) {
return process.env.WSL_DISTRO_NAME;
}
if (existsSync("/etc/os-release")) {
try {
const osRelease = readFileSync("/etc/os-release", "utf8");
const nameMatch = osRelease.match(/^PRETTY_NAME="(.+)"$/m);
if (nameMatch) {
return nameMatch[1];
}
} catch {
}
}
return null;
}
function getWSLInfo() {
if (!isWSL()) {
return null;
}
let version = null;
if (existsSync("/proc/version")) {
try {
version = readFileSync("/proc/version", "utf8").trim();
} catch {
}
}
return {
isWSL: true,
distro: getWSLDistro(),
version
};
}
function getMcpCommand() {
if (isWindows()) {
return ["cmd", "/c", "npx"];
}
return ["npx"];
}
function getSystemRoot() {
if (!isWindows())
return null;
const env = process.env;
let systemRoot = "C:\\Windows";
if (Object.prototype.hasOwnProperty.call(env, "SYSTEMROOT") && env.SYSTEMROOT)
systemRoot = env.SYSTEMROOT;
else if (Object.prototype.hasOwnProperty.call(env, "SystemRoot") && env.SystemRoot)
systemRoot = env.SystemRoot;
return systemRoot.replace(/\\+/g, "/").replace(/\/+/g, "/");
}
async function commandExists(command) {
try {
const cmd = getPlatform() === "windows" ? "where" : "which";
const res = await exec(cmd, [command]);
if (res.exitCode === 0) {
return true;
}
} catch {
}
if (isTermux()) {
const termuxPrefix = getTermuxPrefix();
const possiblePaths = [
`${termuxPrefix}/bin/${command}`,
`${termuxPrefix}/usr/bin/${command}`,
`/data/data/com.termux/files/usr/bin/${command}`
];
for (const path of possiblePaths) {
if (existsSync(path)) {
return true;
}
}
}
if (getPlatform() !== "windows") {
const commonPaths = [
`/usr/local/bin/${command}`,
`/usr/bin/${command}`,
`/bin/${command}`,
`${process.env.HOME}/.local/bin/${command}`
];
for (const path of commonPaths) {
if (existsSync(path)) {
return true;
}
}
}
return false;
}
class FileSystemError extends Error {
constructor(message, path, cause) {
super(message);
this.path = path;
this.cause = cause;
this.name = "FileSystemError";
}
}
function exists(path) {
return existsSync(path);
}
function ensureDir(path) {
if (!existsSync(path)) {
mkdirSync(path, { recursive: true });
}
}
function ensureFileDir(filePath) {
const dir = dirname(filePath);
ensureDir(dir);
}
function readFile(path, encoding = "utf-8") {
try {
return readFileSync(path, encoding);
} catch (error) {
throw new FileSystemError(
`Failed to read file: ${path}`,
path,
error
);
}
}
function writeFile(path, content, encoding = "utf-8") {
try {
ensureFileDir(path);
writeFileSync(path, content, encoding);
} catch (error) {
throw new FileSystemError(
`Failed to write file: ${path}`,
path,
error
);
}
}
function copyFile(src, dest) {
try {
ensureFileDir(dest);
copyFileSync(src, dest);
} catch (error) {
throw new FileSystemError(
`Failed to copy file from ${src} to ${dest}`,
src,
error
);
}
}
function readDir(path) {
try {
return readdirSync(path);
} catch (error) {
throw new FileSystemError(
`Failed to read directory: ${path}`,
path,
error
);
}
}
function getStats(path) {
try {
return statSync(path);
} catch (error) {
throw new FileSystemError(
`Failed to get stats for: ${path}`,
path,
error
);
}
}
function removeFile(path) {
try {
if (exists(path)) {
unlinkSync(path);
}
} catch (error) {
throw new FileSystemError(
`Failed to remove file: ${path}`,
path,
error
);
}
}
function copyDir(src, dest, options = {}) {
const { filter, overwrite = true } = options;
if (!exists(src)) {
throw new FileSystemError(`Source directory does not exist: ${src}`, src);
}
ensureDir(dest);
const entries = readDir(src);
for (const entry of entries) {
const srcPath = `${src}/${entry}`;
const destPath = `${dest}/${entry}`;
const stats = getStats(srcPath);
if (filter && !filter(srcPath, stats)) {
continue;
}
if (stats.isDirectory()) {
copyDir(srcPath, destPath, options);
} else {
if (!overwrite && exists(destPath)) {
continue;
}
copyFile(srcPath, destPath);
}
}
}
async function isExecutable(path) {
try {
if (!exists(path)) {
return false;
}
const stats = getStats(path);
if (!stats.isFile()) {
return false;
}
if (!isWindows()) {
const mode = stats.mode;
const executePermission = 73;
return (mode & executePermission) !== 0;
}
const isWinExecutable = path.endsWith(".exe") || path.endsWith(".cmd") || path.endsWith(".bat");
return isWinExecutable || !path.includes(".");
} catch {
return false;
}
}
async function remove(path) {
try {
if (!exists(path)) {
return;
}
const stats = getStats(path);
if (stats.isDirectory()) {
const entries = readDir(path);
for (const entry of entries) {
await remove(`${path}/${entry}`);
}
try {
if (rmSync) {
rmSync(path, { recursive: true, force: true });
} else if (rmdirSync) {
rmdirSync(path);
}
} catch (error) {
throw new FileSystemError(
`Failed to remove directory: ${path}`,
path,
error
);
}
} else {
removeFile(path);
}
} catch (error) {
throw new FileSystemError(
`Failed to remove: ${path}`,
path,
error
);
}
}
const fsOperations = {
__proto__: null,
FileSystemError: FileSystemError,
copyDir: copyDir,
copyFile: copyFile,
ensureDir: ensureDir,
ensureFileDir: ensureFileDir,
exists: exists,
getStats: getStats,
isExecutable: isExecutable,
readDir: readDir,
readFile: readFile,
remove: remove,
removeFile: removeFile,
writeFile: writeFile
};
function readJsonConfig(path, options = {}) {
const { defaultValue = null, validate, sanitize } = options;
if (!exists(path)) {
return defaultValue;
}
try {
const content = readFile(path);
const data = JSON.parse(content);
if (validate && !validate(data)) {
console.log(`Invalid configuration: ${path}`);
return defaultValue;
}
if (sanitize) {
return sanitize(data);
}
return data;
} catch (error) {
console.error(`Failed to parse JSON: ${path}`, error);
return defaultValue;
}
}
function writeJsonConfig(path, data, options = {}) {
const { pretty = true, backup = false, backupDir } = options;
if (backup && exists(path)) {
backupJsonConfig(path, backupDir);
}
const content = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
writeFile(path, content);
}
function backupJsonConfig(path, backupDir) {
if (!exists(path)) {
return null;
}
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
const fileName = path.split("/").pop() || "config.json";
const baseDir = backupDir || join(path, "..", "backup");
const backupPath = join(baseDir, `${fileName}.backup_${timestamp}`);
try {
ensureDir(baseDir);
copyFile(path, backupPath);
return backupPath;
} catch (error) {
console.error("Failed to backup config", error);
return null;
}
}
function mergeArraysUnique(arr1, arr2) {
const combined = [...arr1 || [], ...arr2 || []];
return [...new Set(combined)];
}
function isPlainObject(value) {
return value !== null && typeof value === "object" && value.constructor === Object && Object.prototype.toString.call(value) === "[object Object]";
}
function deepMerge(target, source, options = {}) {
const { mergeArrays = false, arrayMergeStrategy = "replace" } = options;
const result = { ...target };
for (const key in source) {
const sourceValue = source[key];
const targetValue = result[key];
if (sourceValue === void 0) {
continue;
}
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
result[key] = deepMerge(targetValue, sourceValue, options);
} else if (Array.isArray(sourceValue)) {
if (!mergeArrays || !Array.isArray(targetValue)) {
result[key] = sourceValue;
} else {
switch (arrayMergeStrategy) {
case "concat":
result[key] = [...targetValue, ...sourceValue];
break;
case "unique":
result[key] = mergeArraysUnique(targetValue, sourceValue);
break;
case "replace":
default:
result[key] = sourceValue;
break;
}
}
} else {
result[key] = sourceValue;
}
}
return result;
}
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
if (isPlainObject(obj)) {
const cloned = {};
for (const key in obj) {
cloned[key] = deepClone(obj[key]);
}
return cloned;
}
return obj;
}
function getMcpConfigPath() {
return ClAUDE_CONFIG_FILE;
}
function readMcpConfig() {
return readJsonConfig(ClAUDE_CONFIG_FILE);
}
function writeMcpConfig(config) {
writeJsonConfig(ClAUDE_CONFIG_FILE, config);
}
function backupMcpConfig() {
const backupBaseDir = join(CLAUDE_DIR, "backup");
return backupJsonConfig(ClAUDE_CONFIG_FILE, backupBaseDir);
}
function mergeMcpServers(existing, newServers) {
const config = existing || { mcpServers: {} };
if (!config.mcpServers) {
config.mcpServers = {};
}
Object.assign(config.mcpServers, newServers);
return config;
}
function applyPlatformCommand(config) {
if (config.command === "npx" && isWindows()) {
const mcpCmd = getMcpCommand();
config.command = mcpCmd[0];
config.args = [...mcpCmd.slice(1), ...config.args || []];
}
}
function buildMcpServerConfig(baseConfig, apiKey, placeholder = "YOUR_EXA_API_KEY", envVarName) {
const config = deepClone(baseConfig);
applyPlatformCommand(config);
if (!apiKey) {
return config;
}
if (envVarName && config.env) {
config.env[envVarName] = apiKey;
return config;
}
if (config.args) {
config.args = config.args.map((arg) => arg.replace(placeholder, apiKey));
}
if (config.url) {
config.url = config.url.replace(placeholder, apiKey);
}
return config;
}
function fixWindowsMcpConfig(config) {
if (!isWindows() || !config.mcpServers) {
return config;
}
const fixed = { ...config };
for (const [, serverConfig] of Object.entries(fixed.mcpServers)) {
if (serverConfig && typeof serverConfig === "object" && "command" in serverConfig) {
applyPlatformCommand(serverConfig);
}
}
return fixed;
}
function addCompletedOnboarding() {
try {
let config = readMcpConfig();
if (!config) {
config = { mcpServers: {} };
}
if (config.hasCompletedOnboarding === true) {
return;
}
config.hasCompletedOnboarding = true;
writeMcpConfig(config);
} catch (error) {
console.error("Failed to add onboarding flag", error);
throw error;
}
}
function ensureApiKeyApproved(config, apiKey) {
if (!apiKey || typeof apiKey !== "string" || apiKey.trim() === "") {
return config;
}
const truncatedApiKey = apiKey.substring(0, 20);
const updatedConfig = { ...config };
if (!updatedConfig.customApiKeyResponses) {
updatedConfig.customApiKeyResponses = {
approved: [],
rejected: []
};
}
if (!Array.isArray(updatedConfig.customApiKeyResponses.approved)) {
updatedConfig.customApiKeyResponses.approved = [];
}
if (!Array.isArray(updatedConfig.customApiKeyResponses.rejected)) {
updatedConfig.customApiKeyResponses.rejected = [];
}
const rejectedIndex = updatedConfig.customApiKeyResponses.rejected.indexOf(truncatedApiKey);
if (rejectedIndex > -1) {
updatedConfig.customApiKeyResponses.rejected.splice(rejectedIndex, 1);
}
if (!updatedConfig.customApiKeyResponses.approved.includes(truncatedApiKey)) {
updatedConfig.customApiKeyResponses.approved.push(truncatedApiKey);
}
return updatedConfig;
}
function removeApiKeyFromRejected(config, apiKey) {
if (!config.customApiKeyResponses || !Array.isArray(config.customApiKeyResponses.rejected)) {
return config;
}
const truncatedApiKey = apiKey.substring(0, 20);
const updatedConfig = { ...config };
if (updatedConfig.customApiKeyResponses) {
const rejectedIndex = updatedConfig.customApiKeyResponses.rejected.indexOf(truncatedApiKey);
if (rejectedIndex > -1) {
updatedConfig.customApiKeyResponses.rejected.splice(rejectedIndex, 1);
}
}
return updatedConfig;
}
function manageApiKeyApproval(apiKey) {
try {
let config = readMcpConfig();
if (!config) {
config = { mcpServers: {} };
}
const updatedConfig = ensureApiKeyApproved(config, apiKey);
writeMcpConfig(updatedConfig);
} catch (error) {
ensureI18nInitialized();
console.error(i18n.t("mcp:apiKeyApprovalFailed"), error);
}
}
function setPrimaryApiKey() {
try {
let config = readJsonConfig(CLAUDE_VSC_CONFIG_FILE);
if (!config) {
config = {};
}
config.primaryApiKey = "zcf";
writeJsonConfig(CLAUDE_VSC_CONFIG_FILE, config);
} catch (error) {
ensureI18nInitialized();
console.error(i18n.t("mcp:primaryApiKeySetFailed"), error);
}
}
const claudeConfig = {
__proto__: null,
addCompletedOnboarding: addCompletedOnboarding,
backupMcpConfig: backupMcpConfig,
buildMcpServerConfig: buildMcpServerConfig,
ensureApiKeyApproved: ensureApiKeyApproved,
fixWindowsMcpConfig: fixWindowsMcpConfig,
getMcpConfigPath: getMcpConfigPath,
manageApiKeyApproval: manageApiKeyApproval,
mergeMcpServers: mergeMcpServers,
readMcpConfig: readMcpConfig,
removeApiKeyFromRejected: removeApiKeyFromRejected,
setPrimaryApiKey: setPrimaryApiKey,
writeMcpConfig: writeMcpConfig
};
function cleanupPermissions(templatePermissions, userPermissions) {
const templateSet = new Set(templatePermissions);
const cleanedPermissions = userPermissions.filter((permission) => {
if (["mcp__.*", "mcp__*", "mcp__(*)"].includes(permission)) {
return false;
}
for (const templatePerm of templatePermissions) {
if (permission === templatePerm) {
continue;
}
if (permission.startsWith(templatePerm)) {
return false;
}
}
return true;
});
const merged = [...templateSet];
for (const permission of cleanedPermissions) {
if (!templateSet.has(permission)) {
merged.push(permission);
}
}
return merged;
}
function mergeAndCleanPermissions(templatePermissions, userPermissions) {
const template = templatePermissions || [];
const user = userPermissions || [];
return cleanupPermissions(template, user);
}
function ensureClaudeDir() {
ensureDir(CLAUDE_DIR);
}
function backupExistingConfig() {
if (!exists(CLAUDE_DIR)) {
return null;
}
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
const backupBaseDir = join(CLAUDE_DIR, "backup");
const backupDir = join(backupBaseDir, `backup_${timestamp}`);
ensureDir(backupDir);
const filter = (path) => {
return !path.includes("/backup");
};
copyDir(CLAUDE_DIR, backupDir, { filter });
return backupDir;
}
function copyConfigFiles(onlyMd = false) {
const currentFilePath = fileURLToPath(import.meta.url);
const distDir = dirname(dirname(currentFilePath));
const rootDir = dirname(distDir);
const baseTemplateDir = join(rootDir, "templates", "claude-code");
if (!onlyMd) {
const baseSettingsPath = join(baseTemplateDir, "common", "settings.json");
const destSettingsPath = join(CLAUDE_DIR, "settings.json");
if (exists(baseSettingsPath)) {
mergeSettingsFile(baseSettingsPath, destSettingsPath);
}
}
}
function getDefaultSettings() {
try {
const currentFilePath = fileURLToPath(import.meta.url);
const distDir = dirname(dirname(currentFilePath));
const rootDir = dirname(distDir);
const templateSettingsPath = join(rootDir, "templates", "claude-code", "common", "settings.json");
return readJsonConfig(templateSettingsPath) || {};
} catch (error) {
console.error("Failed to read template settings", error);
return {};
}
}
function configureApi(apiConfig) {
if (!apiConfig)
return null;
let settings = getDefaultSettings();
const existingSettings = readJsonConfig(SETTINGS_FILE);
if (existingSettings) {
settings = deepMerge(settings, existingSettings);
}
if (!settings.env) {
settings.env = {};
}
if (apiConfig.authType === "api_key") {
settings.env.ANTHROPIC_API_KEY = apiConfig.key;
delete settings.env.ANTHROPIC_AUTH_TOKEN;
} else if (apiConfig.authType === "auth_token") {
settings.env.ANTHROPIC_AUTH_TOKEN = apiConfig.key;
delete settings.env.ANTHROPIC_API_KEY;
}
if (apiConfig.url) {
settings.env.ANTHROPIC_BASE_URL = apiConfig.url;
}
writeJsonConfig(SETTINGS_FILE, settings);
if (apiConfig.authType) {
try {
setPrimaryApiKey();
} catch (error) {
ensureI18nInitialized();
console.error(i18n.t("mcp:primaryApiKeySetFailed"), error);
}
}
try {
addCompletedOnboarding();
} catch (error) {
console.error("Failed to set onboarding flag", error);
}
return apiConfig;
}
function mergeConfigs(sourceFile, targetFile) {
if (!exists(sourceFile))
return;
const target = readJsonConfig(targetFile) || {};
const source = readJsonConfig(sourceFile) || {};
const merged = deepMerge(target, source);
writeJsonConfig(targetFile, merged);
}
function updateCustomModel(primaryModel, fastModel) {
if (!primaryModel?.trim() && !fastModel?.trim()) {
return;
}
let settings = getDefaultSettings();
const existingSettings = readJsonConfig(SETTINGS_FILE);
if (existingSettings) {
settings = existingSettings;
}
delete settings.model;
settings.env = settings.env || {};
if (primaryModel?.trim()) {
settings.env.ANTHROPIC_MODEL = primaryModel.trim();
}
if (fastModel?.trim()) {
settings.env.ANTHROPIC_SMALL_FAST_MODEL = fastModel.trim();
}
writeJsonConfig(SETTINGS_FILE, settings);
}
function updateDefaultModel(model) {
let settings = getDefaultSettings();
const existingSettings = readJsonConfig(SETTINGS_FILE);
if (existingSettings) {
settings = existingSettings;
}
if (!settings.env) {
settings.env = {};
}
if (model !== "custom" && settings.env) {
delete settings.env.ANTHROPIC_MODEL;
delete settings.env.ANTHROPIC_SMALL_FAST_MODEL;
}
if (model === "default") {
delete settings.model;
} else if (model === "custom") {
delete settings.model;
} else {
settings.model = model;
}
writeJsonConfig(SETTINGS_FILE, settings);
}
function mergeSettingsFile(templatePath, targetPath) {
try {
const templateSettings = readJsonConfig(templatePath);
if (!templateSettings) {
console.error("Failed to read template settings");
return;
}
if (!exists(targetPath)) {
writeJsonConfig(targetPath, templateSettings);
return;
}
const existingSettings = readJsonConfig(targetPath) || {};
const mergedEnv = {
...templateSettings.env || {},
// Template env vars first
...existingSettings.env || {}
// User's env vars override (preserving API keys, etc.)
};
const mergedSettings = deepMerge(templateSettings, existingSettings, {
mergeArrays: true,
arrayMergeStrategy: "unique"
});
mergedSettings.env = mergedEnv;
if (mergedSettings.permissions && mergedSettings.permissions.allow) {
mergedSettings.permissions.allow = mergeAndCleanPermissions(
templateSettings.permissions?.allow,
existingSettings.permissions?.allow
);
}
writeJsonConfig(targetPath, mergedSettings);
} catch (error) {
console.error("Failed to merge settings", error);
if (exists(targetPath)) {
console.log("Preserving existing settings");
} else {
copyFile(templatePath, targetPath);
}
}
}
function getExistingModelConfig() {
const settings = readJsonConfig(SETTINGS_FILE);
if (!settings) {
return null;
}
if (settings.env && (settings.env.ANTHROPIC_MODEL || settings.env.ANTHROPIC_SMALL_FAST_MODEL)) {
return "custom";
}
if (!settings.model) {
return "default";
}
return settings.model;
}
function getExistingApiConfig() {
const settings = readJsonConfig(SETTINGS_FILE);
if (!settings || !settings.env) {
return null;
}
const { ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL } = settings.env;
if (!ANTHROPIC_BASE_URL && !ANTHROPIC_API_KEY && !ANTHROPIC_AUTH_TOKEN) {
return null;
}
let authType;
let key;
if (ANTHROPIC_AUTH_TOKEN) {
authType = "auth_token";
key = ANTHROPIC_AUTH_TOKEN;
} else if (ANTHROPIC_API_KEY) {
authType = "api_key";
key = ANTHROPIC_API_KEY;
}
return {
url: ANTHROPIC_BASE_URL || "",
key: key || "",
authType
};
}
function applyAiLanguageDirective(aiOutputLang) {
const claudeFile = join(CLAUDE_DIR, "CLAUDE.md");
let directive = "";
if (aiOutputLang === "custom") {
return;
} else if (AI_OUTPUT_LANGUAGES[aiOutputLang]) {
directive = AI_OUTPUT_LANGUAGES[aiOutputLang].directive;
} else {
directive = `Always respond in ${aiOutputLang}`;
}
writeFile(claudeFile, directive);
}
function switchToOfficialLogin$1() {
try {
ensureI18nInitialized();
const settings = readJsonConfig(SETTINGS_FILE) || {};
if (settings.env) {
delete settings.env.ANTHROPIC_BASE_URL;
delete settings.env.ANTHROPIC_AUTH_TOKEN;
delete settings.env.ANTHROPIC_API_KEY;
}
writeJsonConfig(SETTINGS_FILE, settings);
const vscConfig = readJsonConfig(CLAUDE_VSC_CONFIG_FILE);
if (vscConfig) {
delete vscConfig.primaryApiKey;
writeJsonConfig(CLAUDE_VSC_CONFIG_FILE, vscConfig);
}
const mcpConfig = readMcpConfig();
if (mcpConfig) {
delete mcpConfig.hasCompletedOnboarding;
writeMcpConfig(mcpConfig);
}
console.log(i18n.t("api:officialLoginConfigured"));
return true;
} catch (error) {
ensureI18nInitialized();
console.error(i18n.t("api:officialLoginFailed"), error);
return false;
}
}
async function promptApiConfigurationAction() {
ensureI18nInitialized();
const existingConfig = getExistingApiConfig();
if (!existingConfig) {
return null;
}
console.log(`
${ansis.blue(`\u2139 ${i18n.t("api:existingApiConfig")}`)}`);
console.log(ansis.gray(` ${i18n.t("api:apiConfigUrl")}: ${existingConfig.url || "N/A"}`));
console.log(ansis.gray(` ${i18n.t("api:apiConfigKey")}: ${existingConfig.key ? `***${existingConfig.key.slice(-4)}` : "N/A"}`));
console.log(ansis.gray(` ${i18n.t("api:apiConfigAuthType")}: ${existingConfig.authType || "N/A"}
`));
const { choice } = await inquirer.prompt({
type: "list",
name: "choice",
message: i18n.t("api:selectCustomConfigAction"),
choices: [
{
name: i18n.t("api:modifyPartialConfig"),
value: "modify-partial"
},
{
name: i18n.t("api:modifyAllConfig"),
value: "modify-all"
},
{
name: i18n.t("api:keepExistingConfig"),
value: "keep-existing"
}
]
});
return choice || null;
}
const PROVIDER_PRESETS_URL = "https://pub-0dc3e1677e894f07bbea11b17a29e032.r2.dev/providers.json";
async function fetchProviderPresets() {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5e3);
const response = await fetch(PROVIDER_PRESETS_URL, {
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const presets = [];
if (Array.isArray(data)) {
for (const provider of data) {
if (provider && typeof provider === "object") {
presets.push({
name: provider.name || "",
provider: provider.name || "",
baseURL: provider.api_base_url || provider.baseURL || provider.url,
requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
models: provider.models || [],
description: provider.description || provider.name || "",
transformer: provider.transformer
});
}
}
} else if (data && typeof data === "object") {
for (const [key, value] of Object.entries(data)) {
if (typeof value === "object" && value !== null) {
const provider = value;
presets.push({
name: provider.name || key,
provider: key,
baseURL: provider.api_base_url || provider.baseURL || provider.url,
requiresApiKey: provider.api_key === "" || provider.requiresApiKey !== false,
models: provider.models || [],
description: provider.description || "",
transformer: provider.transformer
});
}
}
}
return presets;
} catch {
return getFallbackPresets();
}
}
function getFallbackPresets() {
return [
{
name: "dashscope",
provider: "dashscope",
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
requiresApiKey: true,
models: ["qwen3-coder-plus"],
description: "Alibaba DashScope",
transformer: {
"use": [["maxtoken", { max_tokens: 65536 }]],
"qwen3-coder-plus": {
use: ["enhancetool"]
}
}
},
{
name: "deepseek",
provider: "deepseek",
baseURL: "https://api.deepseek.com/chat/completions",
requiresApiKey: true,
models: ["deepseek-chat", "deepseek-reasoner"],
description: "DeepSeek AI models",
transformer: {
"use": ["deepseek"],
"deepseek-chat": {
use: ["tooluse"]
}
}
},
{
name: "gemini",
provider: "gemini",
baseURL: "https://generativelanguage.googleapis.com/v1beta/models/",
requiresApiKey: true,
models: ["gemini-2.5-flash", "gemini-2.5-pro"],
description: "Google Gemini models",
transformer: {
use: ["gemini"]
}
},
{
name: "modelscope",
provider: "modelscope",
baseURL: "https://api-inference.modelscope.cn/v1/chat/completions",
requiresApiKey: true,
models: ["Qwen/Qwen3-Coder-480B-A35B-Instruct", "Qwen/Qwen3-235B-A22B-Thinking-2507", "ZhipuAI/GLM-4.5"],
description: "ModelScope AI models",
transformer: {
"use": [["maxtoken", { max_tokens: 65536 }]],
"Qwen/Qwen3-Coder-480B-A35B-Instruct": {
use: ["enhancetool"]
},
"Qwen/Qwen3-235B-A22B-Thinking-2507": {
use: ["reasoning"]
}
}
},
{
name: "openrouter",
provider: "openrouter",
baseURL: "https://openrouter.ai/api/v1/chat/completions",
requiresApiKey: true,
models: [
"google/gemini-2.5-pro-preview",
"anthropic/claude-sonnet-4",
"anthropic/claude-3.5-sonnet",
"anthropic/claude-3.7-sonnet:thinking"
],
description: "OpenRouter API",
transformer: {
use: ["openrouter"]
}
},
{
name: "siliconflow",
provider: "siliconflow",
baseURL: "https://api.siliconflow.cn/v1/chat/completions",
requiresApiKey: true,
models: ["moonshotai/Kimi-K2-Instruct"],
description: "SiliconFlow AI",
transformer: {
use: [["maxtoken", { max_tokens: 16384 }]]
}
},
{
name: "volcengine",
provider: "volcengine",
baseURL: "https://ark.cn-beijing.volces.com/api/v3/chat/completions",
requiresApiKey: true,
models: ["deepseek-v3-250324", "deepseek-r1-250528"],
description: "Volcengine AI",
transformer: {
use: ["deepseek"]
}
}
];
}
const execAsync$4 = promisify(exec$1);
const CCR_CONFIG_DIR = join(homedir(), ".claude-code-router");
const CCR_CONFIG_FILE = join(CCR_CONFIG_DIR, "config.json");
const CCR_BACKUP_DIR = CCR_CONFIG_DIR;
function ensureCcrConfigDir() {
if (!existsSync(CCR_CONFIG_DIR)) {
mkdirSync(CCR_CONFIG_DIR, { recursive: true });
}
}
async function backupCcrConfig() {
ensureI18nInitialized();
try {
if (!existsSync(CCR_CONFIG_FILE)) {
return null;
}
const timestamp = `${dayjs().format("YYYY-MM-DDTHH-mm-ss-SSS")}Z`;
const backupFileName = `config.json.${timestamp}.bak`;
const backupPath = join(CCR_BACKUP_DIR, backupFileName);
console.log(ansis.cyan(`${i18n.t("ccr:backupCcrConfig")}`));
copyFileSync(CCR_CONFIG_FILE, backupPath);
console.log(ansis.green(`\u2714 ${i18n.t("ccr:ccrBackupSuccess").replace("{path}", backupPath)}`));
return backupPath;
} catch (error) {
console.error(ansis.red(`${i18n.t("ccr:ccrBackupFailed")}:`), error.message);
return null;
}
}
function readCcrConfig() {
if (!existsSync(CCR_CONFIG_FILE)) {
return null;
}
return readJsonConfig(CCR_CONFIG_FILE);
}
function writeCcrConfig(config) {
ensureCcrConfigDir();
writeJsonConfig(CCR_CONFIG_FILE, config);
}
async function configureCcrProxy(ccrConfig) {
const settings = readJsonConfig(SETTINGS_FILE) || {};
const host = ccrConfig.HOST || "127.0.0.1";
const port = ccrConfig.PORT || 3456;
const apiKey = ccrConfig.APIKEY || "sk-zcf-x-ccr";
if (!settings.env) {
settings.env = {};
}
settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
settings.env.ANTHROPIC_API_KEY = apiKey;
writeJsonConfig(SETTINGS_FILE, settings);
try {
setPrimaryApiKey();
} catch (error) {
ensureI18nInitialized();
console.error(i18n.t("mcp:primaryApiKeySetFailed"), error);
}
}
async function selectCcrPreset() {
ensureI18nInitialized();
console.log(ansis.cyan(`${i18n.t("ccr:fetchingPresets")}`));
const presets = await fetchProviderPresets();
if (!presets || presets.length === 0) {
console.log(ansis.yellow(`${i18n.t("ccr:noPresetsAvailable")}`));
return null;
}
try {
const choices = [
{
name: `1. ${i18n.t("ccr:skipOption")}`,
value: "skip"
},
...presets.map((p, index) => ({
name: `${index + 2}. ${p.name}`,
value: p
}))
];
const { preset } = await inquirer.prompt({
type: "list",
name: "preset",
message: i18n.t("ccr:selectCcrPreset"),
choices
});
return preset;
} catch (error) {
if (error.name === "ExitPromptError") {
console.log(ansis.yellow(i18n.t("common:cancelled")));
return null;
}
throw error;
}
}
async function configureCcrWithPreset(preset) {
ensureI18nInitialized();
const provider = {
name: preset.name,
// Use the original name from JSON
api_base_url: preset.baseURL || "",
api_key: "",
models: preset.models
};
if (preset.transformer) {
provider.transformer = preset.transformer;
}
if (preset.requiresApiKey) {
try {
const { apiKey } = await inquirer.prompt({
type: "password",
name: "apiKey",
message: i18n.t("ccr:enterApiKeyForProvider").replace("{provider}", preset.name) + i18n.t("common:inputHidden"),
validate: async (value) => !!value || i18n.t("api:keyRequired")
});
provider.api_key = apiKey;
} catch (error) {
if (error.name === "ExitPromptError") {