zcf 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -20
- package/dist/cli.mjs +436 -10
- package/dist/index.d.mts +211 -10
- package/dist/index.d.ts +211 -10
- package/dist/index.mjs +2 -1
- package/dist/shared/zcf.DGNSM22u.mjs +1661 -0
- package/package.json +2 -2
- package/templates/CLAUDE.md +7 -0
- package/templates/en/mcp.md +6 -0
- package/templates/en/personality.md +1 -0
- package/templates/en/{CLAUDE.md → rules.md} +1 -10
- package/templates/zh-CN/mcp.md +6 -0
- package/templates/zh-CN/personality.md +1 -0
- package/templates/zh-CN/{CLAUDE.md → rules.md} +6 -15
- package/dist/shared/zcf.D3MMT8L8.mjs +0 -988
|
@@ -1,988 +0,0 @@
|
|
|
1
|
-
import prompts from '@posva/prompts';
|
|
2
|
-
import ansis from 'ansis';
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { homedir, platform } from 'node:os';
|
|
5
|
-
import { join, dirname } from 'pathe';
|
|
6
|
-
import dayjs from 'dayjs';
|
|
7
|
-
import { exec } from 'tinyexec';
|
|
8
|
-
|
|
9
|
-
const version = "1.2.0";
|
|
10
|
-
|
|
11
|
-
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
12
|
-
const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
|
|
13
|
-
const CLAUDE_MD_FILE = join(CLAUDE_DIR, "CLAUDE.md");
|
|
14
|
-
const ClAUDE_CONFIG_FILE = join(homedir(), ".claude.json");
|
|
15
|
-
const ZCF_CONFIG_FILE = join(homedir(), ".zcf.json");
|
|
16
|
-
const SUPPORTED_LANGS = ["zh-CN", "en"];
|
|
17
|
-
const LANG_LABELS = {
|
|
18
|
-
"zh-CN": "\u7B80\u4F53\u4E2D\u6587",
|
|
19
|
-
en: "English"
|
|
20
|
-
};
|
|
21
|
-
const AI_OUTPUT_LANGUAGES = {
|
|
22
|
-
"zh-CN": { label: "\u7B80\u4F53\u4E2D\u6587", directive: "Always respond in Chinese-simplified" },
|
|
23
|
-
en: { label: "English", directive: "Always respond in English" },
|
|
24
|
-
custom: { label: "Custom", directive: "" }
|
|
25
|
-
};
|
|
26
|
-
const I18N = {
|
|
27
|
-
"zh-CN": {
|
|
28
|
-
selectScriptLang: "\u9009\u62E9\u811A\u672C\u8BED\u8A00",
|
|
29
|
-
selectConfigLang: "\u9009\u62E9 Claude Code \u914D\u7F6E\u8BED\u8A00",
|
|
30
|
-
selectAiOutputLang: "\u9009\u62E9 AI \u8F93\u51FA\u8BED\u8A00",
|
|
31
|
-
aiOutputLangHint: "AI \u5C06\u4F7F\u7528\u6B64\u8BED\u8A00\u56DE\u590D\u4F60\u7684\u95EE\u9898",
|
|
32
|
-
enterCustomLanguage: "\u8BF7\u8F93\u5165\u81EA\u5B9A\u4E49\u8BED\u8A00\uFF08\u4F8B\u5982\uFF1AJapanese, French \u7B49\uFF09",
|
|
33
|
-
configLangHint: {
|
|
34
|
-
"zh-CN": "\u4E2D\u6587\u7248\uFF08\u4FBF\u4E8E\u4E2D\u6587\u7528\u6237\u81EA\u5B9A\u4E49\uFF09",
|
|
35
|
-
en: "\u82F1\u6587\u7248\uFF08\u63A8\u8350\uFF0Ctoken \u6D88\u8017\u66F4\u4F4E\uFF09"
|
|
36
|
-
},
|
|
37
|
-
installPrompt: "\u68C0\u6D4B\u5230 Claude Code \u672A\u5B89\u88C5\uFF0C\u662F\u5426\u81EA\u52A8\u5B89\u88C5\uFF1F",
|
|
38
|
-
installing: "\u6B63\u5728\u5B89\u88C5 Claude Code...",
|
|
39
|
-
installSuccess: "Claude Code \u5B89\u88C5\u6210\u529F",
|
|
40
|
-
installFailed: "Claude Code \u5B89\u88C5\u5931\u8D25",
|
|
41
|
-
npmNotFound: "npm \u672A\u5B89\u88C5\u3002\u8BF7\u5148\u5B89\u88C5 Node.js \u548C npm\u3002",
|
|
42
|
-
configureApi: "\u9009\u62E9 API \u8BA4\u8BC1\u65B9\u5F0F",
|
|
43
|
-
useAuthToken: "\u4F7F\u7528 Auth Token (OAuth \u8BA4\u8BC1)",
|
|
44
|
-
authTokenDesc: "\u9002\u7528\u4E8E\u901A\u8FC7 OAuth \u6216\u6D4F\u89C8\u5668\u767B\u5F55\u83B7\u53D6\u7684\u4EE4\u724C",
|
|
45
|
-
useApiKey: "\u4F7F\u7528 API Key (\u5BC6\u94A5\u8BA4\u8BC1)",
|
|
46
|
-
apiKeyDesc: "\u9002\u7528\u4E8E\u4ECE Anthropic Console \u83B7\u53D6\u7684 API \u5BC6\u94A5",
|
|
47
|
-
skipApi: "\u8DF3\u8FC7\uFF08\u7A0D\u540E\u624B\u52A8\u914D\u7F6E\uFF09",
|
|
48
|
-
enterApiUrl: "\u8BF7\u8F93\u5165 API URL",
|
|
49
|
-
enterAuthToken: "\u8BF7\u8F93\u5165 Auth Token",
|
|
50
|
-
enterApiKey: "\u8BF7\u8F93\u5165 API Key",
|
|
51
|
-
existingConfig: "\u68C0\u6D4B\u5230\u5DF2\u6709\u914D\u7F6E\u6587\u4EF6\uFF0C\u5982\u4F55\u5904\u7406\uFF1F",
|
|
52
|
-
backupAndOverwrite: "\u5907\u4EFD\u5E76\u8986\u76D6\u5168\u90E8",
|
|
53
|
-
updateDocsOnly: "\u4EC5\u66F4\u65B0\u5DE5\u4F5C\u6D41\u76F8\u5173md\u5E76\u5907\u4EFD\u65E7\u914D\u7F6E",
|
|
54
|
-
mergeConfig: "\u5408\u5E76\u914D\u7F6E",
|
|
55
|
-
skip: "\u8DF3\u8FC7",
|
|
56
|
-
backupSuccess: "\u5DF2\u5907\u4EFD\u6240\u6709\u914D\u7F6E\u6587\u4EF6\u5230",
|
|
57
|
-
copying: "\u6B63\u5728\u590D\u5236\u914D\u7F6E\u6587\u4EF6...",
|
|
58
|
-
configSuccess: "\u914D\u7F6E\u6587\u4EF6\u5DF2\u590D\u5236\u5230",
|
|
59
|
-
apiConfigSuccess: "API \u914D\u7F6E\u5B8C\u6210",
|
|
60
|
-
mcpConfigSuccess: "MCP \u670D\u52A1\u5DF2\u914D\u7F6E",
|
|
61
|
-
selectMcpServices: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 MCP \u670D\u52A1\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09",
|
|
62
|
-
allServices: "\u5168\u90E8\u5B89\u88C5",
|
|
63
|
-
mcpServiceInstalled: "\u5DF2\u9009\u62E9\u7684 MCP \u670D\u52A1",
|
|
64
|
-
enterExaApiKey: "\u8BF7\u8F93\u5165 Exa API Key\uFF08\u53EF\u4ECE https://dashboard.exa.ai/api-keys \u83B7\u53D6\uFF09",
|
|
65
|
-
skipMcp: "\u8DF3\u8FC7 MCP \u914D\u7F6E",
|
|
66
|
-
configureMcp: "\u662F\u5426\u914D\u7F6E MCP \u670D\u52A1\uFF1F",
|
|
67
|
-
mcpBackupSuccess: "\u5DF2\u5907\u4EFD\u539F\u6709 MCP \u914D\u7F6E",
|
|
68
|
-
complete: "\u{1F389} \u914D\u7F6E\u5B8C\u6210\uFF01\u4F7F\u7528 'claude' \u547D\u4EE4\u5F00\u59CB\u4F53\u9A8C\u3002",
|
|
69
|
-
error: "\u9519\u8BEF",
|
|
70
|
-
yes: "\u662F",
|
|
71
|
-
no: "\u5426",
|
|
72
|
-
cancelled: "\u64CD\u4F5C\u5DF2\u53D6\u6D88",
|
|
73
|
-
apiKeyValidation: {
|
|
74
|
-
empty: "API Key \u4E0D\u80FD\u4E3A\u7A7A",
|
|
75
|
-
invalid: "API Key \u683C\u5F0F\u65E0\u6548\u3002\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\uFF08_\uFF09\u548C\u8FDE\u5B57\u7B26\uFF08-\uFF09",
|
|
76
|
-
tooShort: "API Key \u957F\u5EA6\u592A\u77ED\uFF08\u81F3\u5C11\u9700\u8981 1 \u4E2A\u5B57\u7B26\uFF09",
|
|
77
|
-
tooLong: "API Key \u957F\u5EA6\u592A\u957F\uFF08\u6700\u591A 256 \u4E2A\u5B57\u7B26\uFF09",
|
|
78
|
-
example: "\u793A\u4F8B\u683C\u5F0F: sk-abcdef123456_789xyz"
|
|
79
|
-
},
|
|
80
|
-
noExistingConfig: "\u672A\u627E\u5230\u73B0\u6709\u914D\u7F6E\u3002\u8BF7\u5148\u8FD0\u884C `zcf`\u3002",
|
|
81
|
-
updatingPrompts: "\u6B63\u5728\u66F4\u65B0 Claude Code Prompt \u6587\u6863...",
|
|
82
|
-
updateConfigLangPrompt: "\u9009\u62E9\u914D\u7F6E\u8BED\u8A00",
|
|
83
|
-
updateConfigLangChoice: {
|
|
84
|
-
"zh-CN": "\u4E2D\u6587\u7248\u914D\u7F6E",
|
|
85
|
-
en: "\u82F1\u6587\u7248\u914D\u7F6E"
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
en: {
|
|
89
|
-
selectScriptLang: "Select script language",
|
|
90
|
-
selectConfigLang: "Select Claude Code configuration language",
|
|
91
|
-
selectAiOutputLang: "Select AI output language",
|
|
92
|
-
aiOutputLangHint: "AI will respond to you in this language",
|
|
93
|
-
enterCustomLanguage: "Enter custom language (e.g., Japanese, French, etc.)",
|
|
94
|
-
configLangHint: {
|
|
95
|
-
"zh-CN": "Chinese (easier for Chinese users to customize)",
|
|
96
|
-
en: "English (recommended, lower token consumption)"
|
|
97
|
-
},
|
|
98
|
-
installPrompt: "Claude Code not found. Install automatically?",
|
|
99
|
-
installing: "Installing Claude Code...",
|
|
100
|
-
installSuccess: "Claude Code installed successfully",
|
|
101
|
-
installFailed: "Failed to install Claude Code",
|
|
102
|
-
npmNotFound: "npm is not installed. Please install Node.js and npm first.",
|
|
103
|
-
configureApi: "Select API authentication method",
|
|
104
|
-
useAuthToken: "Use Auth Token (OAuth authentication)",
|
|
105
|
-
authTokenDesc: "For tokens obtained via OAuth or browser login",
|
|
106
|
-
useApiKey: "Use API Key (Key authentication)",
|
|
107
|
-
apiKeyDesc: "For API keys from Anthropic Console",
|
|
108
|
-
skipApi: "Skip (configure manually later)",
|
|
109
|
-
enterApiUrl: "Enter API URL",
|
|
110
|
-
enterAuthToken: "Enter Auth Token",
|
|
111
|
-
enterApiKey: "Enter API Key",
|
|
112
|
-
existingConfig: "Existing config detected. How to proceed?",
|
|
113
|
-
backupAndOverwrite: "Backup and overwrite all",
|
|
114
|
-
updateDocsOnly: "Update workflow-related md files only with backup",
|
|
115
|
-
mergeConfig: "Merge config",
|
|
116
|
-
skip: "Skip",
|
|
117
|
-
backupSuccess: "All config files backed up to",
|
|
118
|
-
copying: "Copying configuration files...",
|
|
119
|
-
configSuccess: "Config files copied to",
|
|
120
|
-
apiConfigSuccess: "API configured",
|
|
121
|
-
mcpConfigSuccess: "MCP services configured",
|
|
122
|
-
selectMcpServices: "Select MCP services to install (space to select, enter to confirm)",
|
|
123
|
-
allServices: "Install all",
|
|
124
|
-
mcpServiceInstalled: "Selected MCP services",
|
|
125
|
-
enterExaApiKey: "Enter Exa API Key (get from https://dashboard.exa.ai/api-keys)",
|
|
126
|
-
skipMcp: "Skip MCP configuration",
|
|
127
|
-
configureMcp: "Configure MCP services?",
|
|
128
|
-
mcpBackupSuccess: "Original MCP config backed up",
|
|
129
|
-
complete: "\u{1F389} Setup complete! Use 'claude' command to start.",
|
|
130
|
-
error: "Error",
|
|
131
|
-
yes: "Yes",
|
|
132
|
-
no: "No",
|
|
133
|
-
cancelled: "Operation cancelled",
|
|
134
|
-
apiKeyValidation: {
|
|
135
|
-
empty: "API Key cannot be empty",
|
|
136
|
-
invalid: "Invalid API Key format. Only letters, numbers, underscores (_) and hyphens (-) are allowed",
|
|
137
|
-
tooShort: "API Key is too short (minimum 1 characters required)",
|
|
138
|
-
tooLong: "API Key is too long (maximum 256 characters allowed)",
|
|
139
|
-
example: "Example format: sk-abcdef123456_789xyz"
|
|
140
|
-
},
|
|
141
|
-
noExistingConfig: "No existing configuration found. Please run `zcf` first.",
|
|
142
|
-
updatingPrompts: "Updating Claude Code prompt documents...",
|
|
143
|
-
updateConfigLangPrompt: "Select configuration language",
|
|
144
|
-
updateConfigLangChoice: {
|
|
145
|
-
"zh-CN": "Chinese configuration",
|
|
146
|
-
en: "English configuration"
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
const MCP_SERVICES = [
|
|
151
|
-
{
|
|
152
|
-
id: "context7",
|
|
153
|
-
name: { "zh-CN": "Context7 \u6587\u6863\u67E5\u8BE2", en: "Context7 Docs" },
|
|
154
|
-
description: {
|
|
155
|
-
"zh-CN": "\u67E5\u8BE2\u6700\u65B0\u7684\u5E93\u6587\u6863\u548C\u4EE3\u7801\u793A\u4F8B",
|
|
156
|
-
en: "Query latest library documentation and code examples"
|
|
157
|
-
},
|
|
158
|
-
requiresApiKey: false,
|
|
159
|
-
config: {
|
|
160
|
-
type: "stdio",
|
|
161
|
-
command: "npx",
|
|
162
|
-
args: ["-y", "@upstash/context7-mcp"],
|
|
163
|
-
env: {}
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
id: "mcp-deepwiki",
|
|
168
|
-
name: { "zh-CN": "DeepWiki", en: "DeepWiki" },
|
|
169
|
-
description: {
|
|
170
|
-
"zh-CN": "\u67E5\u8BE2 GitHub \u4ED3\u5E93\u6587\u6863\u548C\u793A\u4F8B",
|
|
171
|
-
en: "Query GitHub repository documentation and examples"
|
|
172
|
-
},
|
|
173
|
-
requiresApiKey: false,
|
|
174
|
-
config: {
|
|
175
|
-
type: "stdio",
|
|
176
|
-
command: "npx",
|
|
177
|
-
args: ["-y", "mcp-deepwiki@latest"],
|
|
178
|
-
env: {}
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
id: "Playwright",
|
|
183
|
-
name: { "zh-CN": "Playwright \u6D4F\u89C8\u5668\u63A7\u5236", en: "Playwright Browser Control" },
|
|
184
|
-
description: {
|
|
185
|
-
"zh-CN": "\u76F4\u63A5\u63A7\u5236\u6D4F\u89C8\u5668\u8FDB\u884C\u81EA\u52A8\u5316\u64CD\u4F5C",
|
|
186
|
-
en: "Direct browser control for automation"
|
|
187
|
-
},
|
|
188
|
-
requiresApiKey: false,
|
|
189
|
-
config: {
|
|
190
|
-
type: "stdio",
|
|
191
|
-
command: "npx",
|
|
192
|
-
args: ["-y", "@playwright/mcp@latest"],
|
|
193
|
-
env: {}
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
id: "exa",
|
|
198
|
-
name: { "zh-CN": "Exa AI \u641C\u7D22", en: "Exa AI Search" },
|
|
199
|
-
description: {
|
|
200
|
-
"zh-CN": "\u4F7F\u7528 Exa AI \u8FDB\u884C\u7F51\u9875\u641C\u7D22",
|
|
201
|
-
en: "Web search using Exa AI"
|
|
202
|
-
},
|
|
203
|
-
requiresApiKey: true,
|
|
204
|
-
apiKeyPrompt: {
|
|
205
|
-
"zh-CN": "\u8BF7\u8F93\u5165 Exa API Key",
|
|
206
|
-
en: "Enter Exa API Key"
|
|
207
|
-
},
|
|
208
|
-
apiKeyPlaceholder: "YOUR_EXA_API_KEY",
|
|
209
|
-
config: {
|
|
210
|
-
type: "stdio",
|
|
211
|
-
command: "npx",
|
|
212
|
-
args: ["-y", "mcp-remote", "https://mcp.exa.ai/mcp?exaApiKey=YOUR_EXA_API_KEY"],
|
|
213
|
-
env: {}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
];
|
|
217
|
-
|
|
218
|
-
function displayBanner(subtitle) {
|
|
219
|
-
const defaultSubtitle = "One-click configuration tool for Claude Code";
|
|
220
|
-
const subtitleText = subtitle || defaultSubtitle;
|
|
221
|
-
const paddedSubtitle = subtitleText.padEnd(60, " ");
|
|
222
|
-
console.log(
|
|
223
|
-
ansis.cyan.bold(`
|
|
224
|
-
\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
|
|
225
|
-
\u2551 \u2551
|
|
226
|
-
\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
|
|
227
|
-
\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2551
|
|
228
|
-
\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2551
|
|
229
|
-
\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2551
|
|
230
|
-
\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
|
|
231
|
-
\u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u2551
|
|
232
|
-
\u2551 \u2551
|
|
233
|
-
\u2551 ${ansis.white.bold("Zero-Config Claude-Code Flow")} \u2551
|
|
234
|
-
\u2551 ${ansis.gray(paddedSubtitle)} \u2551
|
|
235
|
-
\u2551 \u2551
|
|
236
|
-
\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
|
|
237
|
-
`)
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function ensureClaudeDir() {
|
|
242
|
-
if (!existsSync(CLAUDE_DIR)) {
|
|
243
|
-
mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function backupExistingConfig() {
|
|
247
|
-
if (!existsSync(CLAUDE_DIR)) {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
|
|
251
|
-
const backupBaseDir = join(CLAUDE_DIR, "backup");
|
|
252
|
-
const backupDir = join(backupBaseDir, `backup_${timestamp}`);
|
|
253
|
-
mkdirSync(backupDir, { recursive: true });
|
|
254
|
-
const entries = readdirSync(CLAUDE_DIR);
|
|
255
|
-
for (const entry of entries) {
|
|
256
|
-
if (entry === "backup") continue;
|
|
257
|
-
const srcPath = join(CLAUDE_DIR, entry);
|
|
258
|
-
const destPath = join(backupDir, entry);
|
|
259
|
-
const stat = statSync(srcPath);
|
|
260
|
-
if (stat.isDirectory()) {
|
|
261
|
-
copyDirectory(srcPath, destPath);
|
|
262
|
-
} else {
|
|
263
|
-
copyFileSync(srcPath, destPath);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return backupDir;
|
|
267
|
-
}
|
|
268
|
-
function copyConfigFiles(lang, onlyMd = false) {
|
|
269
|
-
const currentFileUrl = new URL(import.meta.url);
|
|
270
|
-
const currentFilePath = currentFileUrl.pathname;
|
|
271
|
-
const distDir = dirname(dirname(currentFilePath));
|
|
272
|
-
const rootDir = dirname(distDir);
|
|
273
|
-
const sourceDir = join(rootDir, "templates", lang);
|
|
274
|
-
const baseTemplateDir = join(rootDir, "templates");
|
|
275
|
-
if (!existsSync(sourceDir)) {
|
|
276
|
-
throw new Error(`Template directory not found: ${sourceDir}`);
|
|
277
|
-
}
|
|
278
|
-
if (onlyMd) {
|
|
279
|
-
copyMdFiles(sourceDir, CLAUDE_DIR);
|
|
280
|
-
} else {
|
|
281
|
-
copyDirectory(sourceDir, CLAUDE_DIR);
|
|
282
|
-
const baseSettingsPath = join(baseTemplateDir, "settings.json");
|
|
283
|
-
const destSettingsPath = join(CLAUDE_DIR, "settings.json");
|
|
284
|
-
if (existsSync(baseSettingsPath)) {
|
|
285
|
-
copyFileSync(baseSettingsPath, destSettingsPath);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
function copyMdFiles(src, dest) {
|
|
290
|
-
if (!existsSync(dest)) {
|
|
291
|
-
mkdirSync(dest, { recursive: true });
|
|
292
|
-
}
|
|
293
|
-
const entries = readdirSync(src);
|
|
294
|
-
for (const entry of entries) {
|
|
295
|
-
const srcPath = join(src, entry);
|
|
296
|
-
const destPath = join(dest, entry);
|
|
297
|
-
const stat = statSync(srcPath);
|
|
298
|
-
if (stat.isDirectory()) {
|
|
299
|
-
copyMdFiles(srcPath, destPath);
|
|
300
|
-
} else if (entry.endsWith(".md")) {
|
|
301
|
-
copyFileSync(srcPath, destPath);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function copyDirectory(src, dest) {
|
|
306
|
-
if (!existsSync(dest)) {
|
|
307
|
-
mkdirSync(dest, { recursive: true });
|
|
308
|
-
}
|
|
309
|
-
const entries = readdirSync(src);
|
|
310
|
-
for (const entry of entries) {
|
|
311
|
-
if (entry === "settings.json") {
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
const srcPath = join(src, entry);
|
|
315
|
-
const destPath = join(dest, entry);
|
|
316
|
-
const stat = statSync(srcPath);
|
|
317
|
-
if (stat.isDirectory()) {
|
|
318
|
-
copyDirectory(srcPath, destPath);
|
|
319
|
-
} else {
|
|
320
|
-
copyFileSync(srcPath, destPath);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
function getDefaultSettings() {
|
|
325
|
-
try {
|
|
326
|
-
const currentFileUrl = new URL(import.meta.url);
|
|
327
|
-
const currentFilePath = currentFileUrl.pathname;
|
|
328
|
-
const distDir = dirname(dirname(currentFilePath));
|
|
329
|
-
const rootDir = dirname(distDir);
|
|
330
|
-
const templateSettingsPath = join(rootDir, "templates", "settings.json");
|
|
331
|
-
if (existsSync(templateSettingsPath)) {
|
|
332
|
-
const content = readFileSync(templateSettingsPath, "utf-8");
|
|
333
|
-
return JSON.parse(content);
|
|
334
|
-
}
|
|
335
|
-
} catch (error) {
|
|
336
|
-
console.error("Failed to read template settings.json:", error);
|
|
337
|
-
return {};
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function configureApi(apiConfig) {
|
|
341
|
-
if (!apiConfig) return null;
|
|
342
|
-
let settings = getDefaultSettings();
|
|
343
|
-
if (existsSync(SETTINGS_FILE)) {
|
|
344
|
-
const content = readFileSync(SETTINGS_FILE, "utf-8");
|
|
345
|
-
try {
|
|
346
|
-
const existingSettings = JSON.parse(content);
|
|
347
|
-
settings = deepMerge(settings, existingSettings);
|
|
348
|
-
} catch (error) {
|
|
349
|
-
console.error("Failed to parse existing settings.json, using defaults:", error);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (apiConfig.authType === "api_key") {
|
|
353
|
-
settings.env.ANTHROPIC_API_KEY = apiConfig.key;
|
|
354
|
-
} else {
|
|
355
|
-
settings.env.ANTHROPIC_AUTH_TOKEN = apiConfig.key;
|
|
356
|
-
}
|
|
357
|
-
settings.env.ANTHROPIC_BASE_URL = apiConfig.url;
|
|
358
|
-
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
|
359
|
-
return apiConfig;
|
|
360
|
-
}
|
|
361
|
-
function mergeConfigs(sourceFile, targetFile) {
|
|
362
|
-
if (!existsSync(sourceFile)) return;
|
|
363
|
-
let target = {};
|
|
364
|
-
if (existsSync(targetFile)) {
|
|
365
|
-
const content = readFileSync(targetFile, "utf-8");
|
|
366
|
-
try {
|
|
367
|
-
target = JSON.parse(content);
|
|
368
|
-
} catch {
|
|
369
|
-
target = {};
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const source = JSON.parse(readFileSync(sourceFile, "utf-8"));
|
|
373
|
-
const merged = deepMerge(target, source);
|
|
374
|
-
writeFileSync(targetFile, JSON.stringify(merged, null, 2));
|
|
375
|
-
}
|
|
376
|
-
function deepMerge(target, source) {
|
|
377
|
-
const result = { ...target };
|
|
378
|
-
for (const key in source) {
|
|
379
|
-
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
380
|
-
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
381
|
-
} else {
|
|
382
|
-
result[key] = source[key];
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return result;
|
|
386
|
-
}
|
|
387
|
-
function applyAiLanguageDirective(aiOutputLang) {
|
|
388
|
-
if (!existsSync(CLAUDE_MD_FILE)) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
let content = readFileSync(CLAUDE_MD_FILE, "utf-8");
|
|
392
|
-
const lines = content.split("\n");
|
|
393
|
-
if (lines[0] && lines[0].startsWith("Always respond in")) {
|
|
394
|
-
lines.shift();
|
|
395
|
-
if (lines[0] === "") {
|
|
396
|
-
lines.shift();
|
|
397
|
-
}
|
|
398
|
-
content = lines.join("\n");
|
|
399
|
-
}
|
|
400
|
-
let directive = "";
|
|
401
|
-
if (aiOutputLang === "custom") {
|
|
402
|
-
return;
|
|
403
|
-
} else if (AI_OUTPUT_LANGUAGES[aiOutputLang]) {
|
|
404
|
-
directive = AI_OUTPUT_LANGUAGES[aiOutputLang].directive;
|
|
405
|
-
} else {
|
|
406
|
-
directive = `Always respond in ${aiOutputLang}`;
|
|
407
|
-
}
|
|
408
|
-
const newContent = directive + "\n\n" + content;
|
|
409
|
-
writeFileSync(CLAUDE_MD_FILE, newContent, "utf-8");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function getPlatform() {
|
|
413
|
-
const p = platform();
|
|
414
|
-
if (p === "win32") return "windows";
|
|
415
|
-
if (p === "darwin") return "macos";
|
|
416
|
-
return "linux";
|
|
417
|
-
}
|
|
418
|
-
function isWindows() {
|
|
419
|
-
return getPlatform() === "windows";
|
|
420
|
-
}
|
|
421
|
-
function getMcpCommand() {
|
|
422
|
-
if (isWindows()) {
|
|
423
|
-
return ["cmd", "/c", "npx"];
|
|
424
|
-
}
|
|
425
|
-
return ["npx"];
|
|
426
|
-
}
|
|
427
|
-
async function commandExists(command) {
|
|
428
|
-
try {
|
|
429
|
-
const cmd = getPlatform() === "windows" ? "where" : "which";
|
|
430
|
-
const res = await exec(cmd, [command]);
|
|
431
|
-
return res.exitCode === 0;
|
|
432
|
-
} catch {
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async function isClaudeCodeInstalled() {
|
|
438
|
-
return await commandExists("claude");
|
|
439
|
-
}
|
|
440
|
-
async function installClaudeCode(lang) {
|
|
441
|
-
const i18n = I18N[lang];
|
|
442
|
-
console.log(i18n.installing);
|
|
443
|
-
try {
|
|
444
|
-
if (!await commandExists("npm")) {
|
|
445
|
-
throw new Error(i18n.npmNotFound);
|
|
446
|
-
}
|
|
447
|
-
await exec("npm", ["install", "-g", "@anthropic-ai/claude-code"]);
|
|
448
|
-
console.log(`\u2714 ${i18n.installSuccess}`);
|
|
449
|
-
} catch (error) {
|
|
450
|
-
console.error(`\u2716 ${i18n.installFailed}`);
|
|
451
|
-
throw error;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function getMcpConfigPath() {
|
|
456
|
-
return ClAUDE_CONFIG_FILE;
|
|
457
|
-
}
|
|
458
|
-
function readMcpConfig() {
|
|
459
|
-
if (!existsSync(ClAUDE_CONFIG_FILE)) {
|
|
460
|
-
return null;
|
|
461
|
-
}
|
|
462
|
-
try {
|
|
463
|
-
const content = readFileSync(ClAUDE_CONFIG_FILE, "utf-8");
|
|
464
|
-
return JSON.parse(content);
|
|
465
|
-
} catch (error) {
|
|
466
|
-
console.error("Failed to parse MCP config:", error);
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
function writeMcpConfig(config) {
|
|
471
|
-
const dir = dirname(ClAUDE_CONFIG_FILE);
|
|
472
|
-
if (!existsSync(dir)) {
|
|
473
|
-
mkdirSync(dir, { recursive: true });
|
|
474
|
-
}
|
|
475
|
-
writeFileSync(ClAUDE_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
476
|
-
}
|
|
477
|
-
function backupMcpConfig() {
|
|
478
|
-
if (!existsSync(ClAUDE_CONFIG_FILE)) {
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
|
|
482
|
-
const backupBaseDir = join(CLAUDE_DIR, "backup");
|
|
483
|
-
const backupPath = join(backupBaseDir, `.claude.json.backup_${timestamp}.json`);
|
|
484
|
-
try {
|
|
485
|
-
if (!existsSync(backupBaseDir)) {
|
|
486
|
-
mkdirSync(backupBaseDir, { recursive: true });
|
|
487
|
-
}
|
|
488
|
-
const content = readFileSync(ClAUDE_CONFIG_FILE, "utf-8");
|
|
489
|
-
writeFileSync(backupPath, content);
|
|
490
|
-
return backupPath;
|
|
491
|
-
} catch (error) {
|
|
492
|
-
console.error("Failed to backup MCP config:", error);
|
|
493
|
-
return null;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
function mergeMcpServers(existing, newServers) {
|
|
497
|
-
const config = existing || { mcpServers: {} };
|
|
498
|
-
if (!config.mcpServers) {
|
|
499
|
-
config.mcpServers = {};
|
|
500
|
-
}
|
|
501
|
-
Object.assign(config.mcpServers, newServers);
|
|
502
|
-
return config;
|
|
503
|
-
}
|
|
504
|
-
function applyPlatformCommand(config) {
|
|
505
|
-
if (config.command === "npx" && isWindows()) {
|
|
506
|
-
const mcpCmd = getMcpCommand();
|
|
507
|
-
config.command = mcpCmd[0];
|
|
508
|
-
config.args = [...mcpCmd.slice(1), ...config.args || []];
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
function buildMcpServerConfig(baseConfig, apiKey, placeholder = "YOUR_EXA_API_KEY") {
|
|
512
|
-
const config = JSON.parse(JSON.stringify(baseConfig));
|
|
513
|
-
applyPlatformCommand(config);
|
|
514
|
-
if (!apiKey) {
|
|
515
|
-
return config;
|
|
516
|
-
}
|
|
517
|
-
if (config.args) {
|
|
518
|
-
config.args = config.args.map((arg) => arg.replace(placeholder, apiKey));
|
|
519
|
-
}
|
|
520
|
-
if (config.url) {
|
|
521
|
-
config.url = config.url.replace(placeholder, apiKey);
|
|
522
|
-
}
|
|
523
|
-
return config;
|
|
524
|
-
}
|
|
525
|
-
function fixWindowsMcpConfig(config) {
|
|
526
|
-
if (!isWindows() || !config.mcpServers) {
|
|
527
|
-
return config;
|
|
528
|
-
}
|
|
529
|
-
const fixed = { ...config };
|
|
530
|
-
for (const [, serverConfig] of Object.entries(fixed.mcpServers)) {
|
|
531
|
-
if (serverConfig && typeof serverConfig === "object" && "command" in serverConfig) {
|
|
532
|
-
applyPlatformCommand(serverConfig);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
return fixed;
|
|
536
|
-
}
|
|
537
|
-
function addCompletedOnboarding() {
|
|
538
|
-
try {
|
|
539
|
-
let config = readMcpConfig();
|
|
540
|
-
if (!config) {
|
|
541
|
-
config = { mcpServers: {} };
|
|
542
|
-
}
|
|
543
|
-
config.hasCompletedOnboarding = true;
|
|
544
|
-
config = fixWindowsMcpConfig(config);
|
|
545
|
-
writeMcpConfig(config);
|
|
546
|
-
} catch (error) {
|
|
547
|
-
console.error("Failed to add hasCompletedOnboarding flag:", error);
|
|
548
|
-
throw error;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function readZcfConfig() {
|
|
553
|
-
try {
|
|
554
|
-
if (!existsSync(ZCF_CONFIG_FILE)) {
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
const content = readFileSync(ZCF_CONFIG_FILE, "utf-8");
|
|
558
|
-
return JSON.parse(content);
|
|
559
|
-
} catch (error) {
|
|
560
|
-
console.error("Failed to read zcf config:", error);
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
function writeZcfConfig(config) {
|
|
565
|
-
try {
|
|
566
|
-
writeFileSync(ZCF_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
567
|
-
} catch (error) {
|
|
568
|
-
console.error("Failed to write zcf config:", error);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
function updateZcfConfig(updates) {
|
|
572
|
-
const existingConfig = readZcfConfig();
|
|
573
|
-
const newConfig = {
|
|
574
|
-
version: updates.version || existingConfig?.version || "1.0.0",
|
|
575
|
-
preferredLang: updates.preferredLang || existingConfig?.preferredLang || "en",
|
|
576
|
-
aiOutputLang: updates.aiOutputLang || existingConfig?.aiOutputLang,
|
|
577
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
578
|
-
};
|
|
579
|
-
writeZcfConfig(newConfig);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
async function selectAiOutputLanguage(scriptLang, defaultLang) {
|
|
583
|
-
const i18n = I18N[scriptLang];
|
|
584
|
-
console.log(ansis.dim(`
|
|
585
|
-
${i18n.aiOutputLangHint}
|
|
586
|
-
`));
|
|
587
|
-
const aiLangChoices = Object.entries(AI_OUTPUT_LANGUAGES).map(([key, value]) => ({
|
|
588
|
-
title: value.label,
|
|
589
|
-
value: key
|
|
590
|
-
}));
|
|
591
|
-
const defaultChoice = defaultLang || (scriptLang === "zh-CN" ? "zh-CN" : "en");
|
|
592
|
-
const aiLangResponse = await prompts({
|
|
593
|
-
type: "select",
|
|
594
|
-
name: "lang",
|
|
595
|
-
message: i18n.selectAiOutputLang,
|
|
596
|
-
choices: aiLangChoices,
|
|
597
|
-
initial: aiLangChoices.findIndex((c) => c.value === defaultChoice)
|
|
598
|
-
});
|
|
599
|
-
if (!aiLangResponse.lang) {
|
|
600
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
601
|
-
process.exit(0);
|
|
602
|
-
}
|
|
603
|
-
let aiOutputLang = aiLangResponse.lang;
|
|
604
|
-
if (aiOutputLang === "custom") {
|
|
605
|
-
const customLangResponse = await prompts({
|
|
606
|
-
type: "text",
|
|
607
|
-
name: "customLang",
|
|
608
|
-
message: i18n.enterCustomLanguage,
|
|
609
|
-
validate: (value) => !!value || "Language is required"
|
|
610
|
-
});
|
|
611
|
-
if (!customLangResponse.customLang) {
|
|
612
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
613
|
-
process.exit(0);
|
|
614
|
-
}
|
|
615
|
-
return customLangResponse.customLang;
|
|
616
|
-
}
|
|
617
|
-
return aiOutputLang;
|
|
618
|
-
}
|
|
619
|
-
async function selectScriptLanguage(currentLang) {
|
|
620
|
-
const zcfConfig = readZcfConfig();
|
|
621
|
-
if (zcfConfig?.preferredLang) {
|
|
622
|
-
return zcfConfig.preferredLang;
|
|
623
|
-
}
|
|
624
|
-
if (currentLang) {
|
|
625
|
-
return currentLang;
|
|
626
|
-
}
|
|
627
|
-
const response = await prompts({
|
|
628
|
-
type: "select",
|
|
629
|
-
name: "lang",
|
|
630
|
-
message: "Select script language / \u9009\u62E9\u811A\u672C\u8BED\u8A00",
|
|
631
|
-
choices: SUPPORTED_LANGS.map((l) => ({
|
|
632
|
-
title: LANG_LABELS[l],
|
|
633
|
-
value: l
|
|
634
|
-
}))
|
|
635
|
-
});
|
|
636
|
-
if (!response.lang) {
|
|
637
|
-
console.log(ansis.yellow("Operation cancelled / \u64CD\u4F5C\u5DF2\u53D6\u6D88"));
|
|
638
|
-
process.exit(0);
|
|
639
|
-
}
|
|
640
|
-
const scriptLang = response.lang;
|
|
641
|
-
updateZcfConfig({
|
|
642
|
-
version,
|
|
643
|
-
preferredLang: scriptLang
|
|
644
|
-
});
|
|
645
|
-
return scriptLang;
|
|
646
|
-
}
|
|
647
|
-
async function resolveAiOutputLanguage(scriptLang, commandLineOption, savedConfig) {
|
|
648
|
-
const i18n = I18N[scriptLang];
|
|
649
|
-
if (commandLineOption) {
|
|
650
|
-
return commandLineOption;
|
|
651
|
-
}
|
|
652
|
-
if (savedConfig?.aiOutputLang) {
|
|
653
|
-
console.log(ansis.gray(`\u2714 ${i18n.aiOutputLangHint}: ${savedConfig.aiOutputLang}`));
|
|
654
|
-
return savedConfig.aiOutputLang;
|
|
655
|
-
}
|
|
656
|
-
return await selectAiOutputLanguage(scriptLang, scriptLang);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
function validateApiKey(apiKey, lang = "zh-CN") {
|
|
660
|
-
const i18n = I18N[lang];
|
|
661
|
-
if (!apiKey || apiKey.trim() === "") {
|
|
662
|
-
return {
|
|
663
|
-
isValid: false,
|
|
664
|
-
error: i18n.apiKeyValidation.empty
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
const apiKeyPattern = /^[A-Za-z0-9_-]+$/;
|
|
668
|
-
if (!apiKeyPattern.test(apiKey)) {
|
|
669
|
-
return {
|
|
670
|
-
isValid: false,
|
|
671
|
-
error: i18n.apiKeyValidation.invalid
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
if (apiKey.length < 1) {
|
|
675
|
-
return {
|
|
676
|
-
isValid: false,
|
|
677
|
-
error: i18n.apiKeyValidation.tooShort
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
if (apiKey.length > 256) {
|
|
681
|
-
return {
|
|
682
|
-
isValid: false,
|
|
683
|
-
error: i18n.apiKeyValidation.tooLong
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
return { isValid: true };
|
|
687
|
-
}
|
|
688
|
-
function formatApiKeyDisplay(apiKey) {
|
|
689
|
-
if (!apiKey || apiKey.length < 12) {
|
|
690
|
-
return apiKey;
|
|
691
|
-
}
|
|
692
|
-
return `${apiKey.substring(0, 8)}...${apiKey.substring(apiKey.length - 4)}`;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
async function updatePromptOnly(configLang, scriptLang, aiOutputLang) {
|
|
696
|
-
const i18n = I18N[scriptLang];
|
|
697
|
-
const backupDir = backupExistingConfig();
|
|
698
|
-
if (backupDir) {
|
|
699
|
-
console.log(ansis.gray(`\u2714 ${i18n.backupSuccess}: ${backupDir}`));
|
|
700
|
-
}
|
|
701
|
-
copyConfigFiles(configLang, true);
|
|
702
|
-
if (aiOutputLang) {
|
|
703
|
-
applyAiLanguageDirective(aiOutputLang);
|
|
704
|
-
}
|
|
705
|
-
console.log(ansis.green(`\u2714 ${i18n.configSuccess} ${CLAUDE_DIR}`));
|
|
706
|
-
console.log("\n" + ansis.cyan(i18n.complete));
|
|
707
|
-
}
|
|
708
|
-
async function init(options = {}) {
|
|
709
|
-
try {
|
|
710
|
-
displayBanner();
|
|
711
|
-
console.log(ansis.gray(` Version: ${ansis.cyan(version)} | ${ansis.cyan("https://github.com/UfoMiao/zcf")}
|
|
712
|
-
`));
|
|
713
|
-
const scriptLang = await selectScriptLanguage(options.lang);
|
|
714
|
-
const i18n = I18N[scriptLang];
|
|
715
|
-
let configLang = options.configLang;
|
|
716
|
-
if (!configLang) {
|
|
717
|
-
const response = await prompts({
|
|
718
|
-
type: "select",
|
|
719
|
-
name: "lang",
|
|
720
|
-
message: i18n.selectConfigLang,
|
|
721
|
-
choices: SUPPORTED_LANGS.map((l) => ({
|
|
722
|
-
title: `${LANG_LABELS[l]} - ${i18n.configLangHint[l]}`,
|
|
723
|
-
value: l
|
|
724
|
-
}))
|
|
725
|
-
});
|
|
726
|
-
if (!response.lang) {
|
|
727
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
728
|
-
process.exit(0);
|
|
729
|
-
}
|
|
730
|
-
configLang = response.lang;
|
|
731
|
-
}
|
|
732
|
-
const zcfConfig = readZcfConfig();
|
|
733
|
-
const aiOutputLang = await resolveAiOutputLanguage(scriptLang, options.aiOutputLang, zcfConfig);
|
|
734
|
-
const installed = await isClaudeCodeInstalled();
|
|
735
|
-
if (!installed) {
|
|
736
|
-
const response = await prompts({
|
|
737
|
-
type: "confirm",
|
|
738
|
-
name: "shouldInstall",
|
|
739
|
-
message: i18n.installPrompt,
|
|
740
|
-
initial: true
|
|
741
|
-
});
|
|
742
|
-
if (response.shouldInstall === void 0) {
|
|
743
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
744
|
-
process.exit(0);
|
|
745
|
-
}
|
|
746
|
-
if (response.shouldInstall) {
|
|
747
|
-
await installClaudeCode(scriptLang);
|
|
748
|
-
} else {
|
|
749
|
-
console.log(ansis.yellow(i18n.skip));
|
|
750
|
-
}
|
|
751
|
-
} else {
|
|
752
|
-
console.log(ansis.green(`\u2714 ${i18n.installSuccess}`));
|
|
753
|
-
}
|
|
754
|
-
ensureClaudeDir();
|
|
755
|
-
let onlyUpdateDocs = false;
|
|
756
|
-
let action = "new";
|
|
757
|
-
if (existsSync(SETTINGS_FILE) && !options.force) {
|
|
758
|
-
const actionResponse = await prompts({
|
|
759
|
-
type: "select",
|
|
760
|
-
name: "action",
|
|
761
|
-
message: i18n.existingConfig,
|
|
762
|
-
choices: [
|
|
763
|
-
{ title: i18n.backupAndOverwrite, value: "backup" },
|
|
764
|
-
{ title: i18n.updateDocsOnly, value: "docs-only" },
|
|
765
|
-
{ title: i18n.mergeConfig, value: "merge" },
|
|
766
|
-
{ title: i18n.skip, value: "skip" }
|
|
767
|
-
]
|
|
768
|
-
});
|
|
769
|
-
if (!actionResponse.action) {
|
|
770
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
771
|
-
process.exit(0);
|
|
772
|
-
}
|
|
773
|
-
action = actionResponse.action;
|
|
774
|
-
if (action === "skip") {
|
|
775
|
-
console.log(ansis.yellow(i18n.skip));
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
if (action === "docs-only") {
|
|
779
|
-
onlyUpdateDocs = true;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
let apiConfig = null;
|
|
783
|
-
const isNewInstall = !existsSync(SETTINGS_FILE);
|
|
784
|
-
if (!onlyUpdateDocs && (isNewInstall || action === "backup" || action === "merge")) {
|
|
785
|
-
const apiResponse = await prompts({
|
|
786
|
-
type: "select",
|
|
787
|
-
name: "apiChoice",
|
|
788
|
-
message: i18n.configureApi,
|
|
789
|
-
choices: [
|
|
790
|
-
{
|
|
791
|
-
title: i18n.useAuthToken,
|
|
792
|
-
value: "auth_token",
|
|
793
|
-
description: ansis.gray(i18n.authTokenDesc)
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
title: i18n.useApiKey,
|
|
797
|
-
value: "api_key",
|
|
798
|
-
description: ansis.gray(i18n.apiKeyDesc)
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
title: i18n.skipApi,
|
|
802
|
-
value: "skip"
|
|
803
|
-
}
|
|
804
|
-
]
|
|
805
|
-
});
|
|
806
|
-
if (!apiResponse.apiChoice) {
|
|
807
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
808
|
-
process.exit(0);
|
|
809
|
-
}
|
|
810
|
-
const apiChoice = apiResponse.apiChoice;
|
|
811
|
-
if (apiChoice === "auth_token" || apiChoice === "api_key") {
|
|
812
|
-
const urlResponse = await prompts({
|
|
813
|
-
type: "text",
|
|
814
|
-
name: "url",
|
|
815
|
-
message: i18n.enterApiUrl,
|
|
816
|
-
validate: (value) => {
|
|
817
|
-
if (!value) return "URL is required";
|
|
818
|
-
try {
|
|
819
|
-
new URL(value);
|
|
820
|
-
return true;
|
|
821
|
-
} catch {
|
|
822
|
-
return "Invalid URL";
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
});
|
|
826
|
-
if (urlResponse.url === void 0) {
|
|
827
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
828
|
-
process.exit(0);
|
|
829
|
-
}
|
|
830
|
-
const url = urlResponse.url;
|
|
831
|
-
const keyMessage = apiChoice === "auth_token" ? i18n.enterAuthToken : i18n.enterApiKey;
|
|
832
|
-
const keyResponse = await prompts({
|
|
833
|
-
type: "text",
|
|
834
|
-
name: "key",
|
|
835
|
-
message: keyMessage,
|
|
836
|
-
validate: (value) => {
|
|
837
|
-
if (!value) {
|
|
838
|
-
return `${apiChoice === "auth_token" ? "Auth Token" : "API Key"} is required`;
|
|
839
|
-
}
|
|
840
|
-
const validation = validateApiKey(value, scriptLang);
|
|
841
|
-
if (!validation.isValid) {
|
|
842
|
-
return validation.error || "Invalid API Key format";
|
|
843
|
-
}
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
if (keyResponse.key === void 0) {
|
|
848
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
849
|
-
process.exit(0);
|
|
850
|
-
}
|
|
851
|
-
const key = keyResponse.key;
|
|
852
|
-
console.log(ansis.gray(` API Key: ${formatApiKeyDisplay(key)}`));
|
|
853
|
-
apiConfig = { url, key, authType: apiChoice };
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
if (action === "backup") {
|
|
857
|
-
const backupDir = backupExistingConfig();
|
|
858
|
-
if (backupDir) {
|
|
859
|
-
console.log(ansis.gray(`\u2714 ${i18n.backupSuccess}: ${backupDir}`));
|
|
860
|
-
}
|
|
861
|
-
copyConfigFiles(configLang, false);
|
|
862
|
-
} else if (action === "docs-only") {
|
|
863
|
-
const backupDir = backupExistingConfig();
|
|
864
|
-
if (backupDir) {
|
|
865
|
-
console.log(ansis.gray(`\u2714 ${i18n.backupSuccess}: ${backupDir}`));
|
|
866
|
-
}
|
|
867
|
-
copyConfigFiles(configLang, true);
|
|
868
|
-
} else if (action === "merge") {
|
|
869
|
-
const backupDir = backupExistingConfig();
|
|
870
|
-
if (backupDir) {
|
|
871
|
-
console.log(ansis.gray(`\u2714 ${i18n.backupSuccess}: ${backupDir}`));
|
|
872
|
-
}
|
|
873
|
-
copyConfigFiles(configLang, false);
|
|
874
|
-
} else if (action === "new") {
|
|
875
|
-
copyConfigFiles(configLang, false);
|
|
876
|
-
}
|
|
877
|
-
applyAiLanguageDirective(aiOutputLang);
|
|
878
|
-
if (apiConfig && !onlyUpdateDocs) {
|
|
879
|
-
const configuredApi = configureApi(apiConfig);
|
|
880
|
-
if (configuredApi) {
|
|
881
|
-
console.log(ansis.green(`\u2714 ${i18n.apiConfigSuccess}`));
|
|
882
|
-
console.log(ansis.gray(` URL: ${configuredApi.url}`));
|
|
883
|
-
console.log(ansis.gray(` Key: ${formatApiKeyDisplay(configuredApi.key)}`));
|
|
884
|
-
try {
|
|
885
|
-
addCompletedOnboarding();
|
|
886
|
-
} catch (error) {
|
|
887
|
-
console.error(ansis.red("Failed to set onboarding completion flag:"), error);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
if (!onlyUpdateDocs) {
|
|
892
|
-
const mcpResponse = await prompts({
|
|
893
|
-
type: "confirm",
|
|
894
|
-
name: "shouldConfigureMcp",
|
|
895
|
-
message: i18n.configureMcp,
|
|
896
|
-
initial: true
|
|
897
|
-
});
|
|
898
|
-
if (mcpResponse.shouldConfigureMcp === void 0) {
|
|
899
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
900
|
-
process.exit(0);
|
|
901
|
-
}
|
|
902
|
-
if (mcpResponse.shouldConfigureMcp) {
|
|
903
|
-
if (isWindows()) {
|
|
904
|
-
console.log(ansis.blue(`\u2139 ${scriptLang === "zh-CN" ? "\u68C0\u6D4B\u5230 Windows \u7CFB\u7EDF\uFF0C\u5C06\u81EA\u52A8\u914D\u7F6E\u517C\u5BB9\u683C\u5F0F" : "Windows detected, will configure compatible format"}`));
|
|
905
|
-
}
|
|
906
|
-
const choices = [
|
|
907
|
-
{
|
|
908
|
-
title: ansis.bold(i18n.allServices),
|
|
909
|
-
value: "ALL",
|
|
910
|
-
selected: false
|
|
911
|
-
},
|
|
912
|
-
...MCP_SERVICES.map((service) => ({
|
|
913
|
-
title: `${service.name[scriptLang]} - ${ansis.gray(service.description[scriptLang])}`,
|
|
914
|
-
value: service.id,
|
|
915
|
-
selected: false
|
|
916
|
-
}))
|
|
917
|
-
];
|
|
918
|
-
const selectedResponse = await prompts({
|
|
919
|
-
type: "multiselect",
|
|
920
|
-
name: "services",
|
|
921
|
-
message: i18n.selectMcpServices,
|
|
922
|
-
choices,
|
|
923
|
-
instructions: false,
|
|
924
|
-
hint: "- Space to select. Return to submit"
|
|
925
|
-
});
|
|
926
|
-
if (selectedResponse.services === void 0) {
|
|
927
|
-
console.log(ansis.yellow(i18n.cancelled));
|
|
928
|
-
process.exit(0);
|
|
929
|
-
}
|
|
930
|
-
let selectedServices = selectedResponse.services || [];
|
|
931
|
-
if (selectedServices.includes("ALL")) {
|
|
932
|
-
selectedServices = MCP_SERVICES.map((s) => s.id);
|
|
933
|
-
}
|
|
934
|
-
if (selectedServices.length > 0) {
|
|
935
|
-
const mcpBackupPath = backupMcpConfig();
|
|
936
|
-
if (mcpBackupPath) {
|
|
937
|
-
console.log(ansis.gray(`\u2714 ${i18n.mcpBackupSuccess}: ${mcpBackupPath}`));
|
|
938
|
-
}
|
|
939
|
-
const newServers = {};
|
|
940
|
-
for (const serviceId of selectedServices) {
|
|
941
|
-
const service = MCP_SERVICES.find((s) => s.id === serviceId);
|
|
942
|
-
if (!service) continue;
|
|
943
|
-
let config = service.config;
|
|
944
|
-
if (service.requiresApiKey) {
|
|
945
|
-
const apiKeyResponse = await prompts({
|
|
946
|
-
type: "text",
|
|
947
|
-
name: "apiKey",
|
|
948
|
-
message: service.apiKeyPrompt[scriptLang],
|
|
949
|
-
validate: (value) => !!value || "API Key is required"
|
|
950
|
-
});
|
|
951
|
-
if (apiKeyResponse.apiKey === void 0) {
|
|
952
|
-
console.log(ansis.yellow(`${i18n.skip}: ${service.name[scriptLang]}`));
|
|
953
|
-
continue;
|
|
954
|
-
}
|
|
955
|
-
if (apiKeyResponse.apiKey) {
|
|
956
|
-
config = buildMcpServerConfig(service.config, apiKeyResponse.apiKey, service.apiKeyPlaceholder);
|
|
957
|
-
} else {
|
|
958
|
-
continue;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
newServers[service.id] = config;
|
|
962
|
-
}
|
|
963
|
-
const existingConfig = readMcpConfig();
|
|
964
|
-
let mergedConfig = mergeMcpServers(existingConfig, newServers);
|
|
965
|
-
mergedConfig = fixWindowsMcpConfig(mergedConfig);
|
|
966
|
-
try {
|
|
967
|
-
writeMcpConfig(mergedConfig);
|
|
968
|
-
console.log(ansis.green(`\u2714 ${i18n.mcpConfigSuccess}`));
|
|
969
|
-
} catch (error) {
|
|
970
|
-
console.error(ansis.red(`Failed to write MCP config: ${error}`));
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
updateZcfConfig({
|
|
976
|
-
version,
|
|
977
|
-
preferredLang: scriptLang,
|
|
978
|
-
aiOutputLang
|
|
979
|
-
});
|
|
980
|
-
console.log(ansis.green(`\u2714 ${i18n.configSuccess} ${CLAUDE_DIR}`));
|
|
981
|
-
console.log("\n" + ansis.cyan(i18n.complete));
|
|
982
|
-
} catch (error) {
|
|
983
|
-
console.error(ansis.red(`${I18N[options.lang || "en"].error}:`), error);
|
|
984
|
-
process.exit(1);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
export { AI_OUTPUT_LANGUAGES as A, updatePromptOnly as B, CLAUDE_DIR as C, updateZcfConfig as D, version as E, I18N as I, LANG_LABELS as L, MCP_SERVICES as M, SETTINGS_FILE as S, ZCF_CONFIG_FILE as Z, CLAUDE_MD_FILE as a, ClAUDE_CONFIG_FILE as b, commandExists as c, SUPPORTED_LANGS as d, isClaudeCodeInstalled as e, installClaudeCode as f, getPlatform as g, ensureClaudeDir as h, init as i, backupExistingConfig as j, copyConfigFiles as k, configureApi as l, mergeConfigs as m, applyAiLanguageDirective as n, getMcpConfigPath as o, backupMcpConfig as p, mergeMcpServers as q, readMcpConfig as r, buildMcpServerConfig as s, fixWindowsMcpConfig as t, addCompletedOnboarding as u, displayBanner as v, writeMcpConfig as w, selectScriptLanguage as x, readZcfConfig as y, resolveAiOutputLanguage as z };
|