claude360 0.1.0 → 0.2.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 +7 -4
- package/bin/claude360.js +5 -2
- package/install/install.ps1 +61 -15
- package/install/install.sh +75 -13
- package/install/verification-matrix.md +2 -2
- package/package.json +3 -2
- package/src/account-status.js +97 -0
- package/src/api-client.js +6 -3
- package/src/auth.js +5 -3
- package/src/banner.js +42 -0
- package/src/cc-switch.js +206 -0
- package/src/diagnostics.js +115 -38
- package/src/group-manager.js +6 -3
- package/src/index.js +824 -59
- package/src/mcp-skill.js +319 -0
- package/src/menu.js +108 -61
- package/src/onboarding.js +83 -0
- package/src/sanitize.js +70 -0
- package/src/token-manager.js +46 -22
- package/src/tool-installer.js +34 -5
- package/src/tool-launcher.js +82 -21
- package/src/topup.js +8 -1
package/src/index.js
CHANGED
|
@@ -1,22 +1,60 @@
|
|
|
1
1
|
import readline from "node:readline/promises";
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
|
|
5
6
|
import { ApiClient } from "./api-client.js";
|
|
6
7
|
import { authenticateWithBrowser } from "./auth.js";
|
|
8
|
+
import { formatAccountStatus, loadAccountStatus } from "./account-status.js";
|
|
9
|
+
import { renderBanner, formatCheckLine } from "./banner.js";
|
|
10
|
+
import { runCcSwitchGenerator } from "./cc-switch.js";
|
|
7
11
|
import { createConfigStore } from "./config-store.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
12
|
+
import {
|
|
13
|
+
commandVersion,
|
|
14
|
+
defaultExecCommand,
|
|
15
|
+
formatDiagnosticsSummary,
|
|
16
|
+
runDiagnostics as collectDiagnostics,
|
|
17
|
+
} from "./diagnostics.js";
|
|
18
|
+
import {
|
|
19
|
+
buildDailyMenu,
|
|
20
|
+
buildFirstRunMenu,
|
|
21
|
+
promptMenu,
|
|
22
|
+
} from "./menu.js";
|
|
23
|
+
import {
|
|
24
|
+
installCodexAgents,
|
|
25
|
+
installRecommendedMcps,
|
|
26
|
+
installRecommendedSkills,
|
|
27
|
+
listInstalledEnhancements,
|
|
28
|
+
} from "./mcp-skill.js";
|
|
29
|
+
import { ensureEnvironment } from "./onboarding.js";
|
|
30
|
+
import { safeErrorMessage } from "./sanitize.js";
|
|
10
31
|
import { installOrUpdateTools as installTools } from "./tool-installer.js";
|
|
11
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
checkCodexCompat,
|
|
34
|
+
configureCodexProvider,
|
|
35
|
+
launchClaudeCode as startClaudeCode,
|
|
36
|
+
launchCodex as startCodex,
|
|
37
|
+
} from "./tool-launcher.js";
|
|
12
38
|
import { runWechatTopUp } from "./topup.js";
|
|
13
|
-
import { chooseOrCreateToken,
|
|
39
|
+
import { chooseOrCreateToken, createNewToken } from "./token-manager.js";
|
|
40
|
+
|
|
41
|
+
const DEFAULT_BASE_URL = "https://claude360.xyz";
|
|
42
|
+
|
|
43
|
+
function readCliVersion() {
|
|
44
|
+
try {
|
|
45
|
+
const require = createRequire(import.meta.url);
|
|
46
|
+
return require("../package.json").version || "";
|
|
47
|
+
} catch {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
14
51
|
|
|
15
52
|
export async function runCli({
|
|
16
53
|
configStore = createConfigStore(),
|
|
17
54
|
createApiClient = (config) => new ApiClient(config),
|
|
18
55
|
authenticateWithBrowser: authWithBrowser = authenticateWithBrowser,
|
|
19
56
|
chooseOrCreateToken: chooseToken = chooseOrCreateToken,
|
|
57
|
+
createToken = createNewToken,
|
|
20
58
|
promptSelect = defaultPromptSelect,
|
|
21
59
|
promptInput = defaultPromptInput,
|
|
22
60
|
confirm = defaultConfirm,
|
|
@@ -24,62 +62,788 @@ export async function runCli({
|
|
|
24
62
|
installOrUpdateTools = installTools,
|
|
25
63
|
launchClaudeCode = startClaudeCode,
|
|
26
64
|
launchCodex = startCodex,
|
|
65
|
+
codexCompat = checkCodexCompat,
|
|
66
|
+
configureCodex = configureCodexProvider,
|
|
67
|
+
installMcps = installRecommendedMcps,
|
|
68
|
+
installSkills = installRecommendedSkills,
|
|
69
|
+
installAgents = installCodexAgents,
|
|
70
|
+
listEnhancements = listInstalledEnhancements,
|
|
71
|
+
ccSwitchGenerator = runCcSwitchGenerator,
|
|
72
|
+
runDiagnostics = collectDiagnostics,
|
|
73
|
+
execCommand = defaultExecCommand,
|
|
27
74
|
sleep,
|
|
28
75
|
writeLine = console.log,
|
|
76
|
+
forceSetup = false,
|
|
77
|
+
command = "",
|
|
78
|
+
checkEnvironment = ensureEnvironment,
|
|
79
|
+
showBanner = true,
|
|
80
|
+
version = readCliVersion(),
|
|
29
81
|
} = {}) {
|
|
30
82
|
let config = await configStore.load();
|
|
31
|
-
const baseUrl = config.baseUrl ||
|
|
83
|
+
const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
32
84
|
let api = createApiClient({ baseUrl, cliToken: config.cliToken || "" });
|
|
33
85
|
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
86
|
+
if (showBanner) {
|
|
87
|
+
writeLine(renderBanner({ version, baseUrl }));
|
|
88
|
+
await showEnvironmentChecks();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ──────────────────────────────────────────────
|
|
92
|
+
// 公共动作
|
|
93
|
+
// ──────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
async function saveConfig(patch) {
|
|
96
|
+
config = { ...config, baseUrl, ...patch };
|
|
37
97
|
await configStore.save(config);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function showEnvironmentChecks() {
|
|
101
|
+
const checks = [];
|
|
102
|
+
const node = await commandVersion(execCommand, "node", ["--version"]);
|
|
103
|
+
checks.push({ status: node.ok ? "ok" : "fail", text: node.ok ? `Node.js ${node.version}` : "未检测到 Node.js 18+" });
|
|
104
|
+
const npm = await commandVersion(execCommand, "npm", ["--version"]);
|
|
105
|
+
checks.push({ status: npm.ok ? "ok" : "fail", text: npm.ok ? "npm 可用" : "npm 不可用" });
|
|
106
|
+
try {
|
|
107
|
+
await api.get("/api/status");
|
|
108
|
+
checks.push({ status: "ok", text: "Claude360 服务连接成功" });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
checks.push({ status: "fail", text: `Claude360 服务连接失败:${safeErrorMessage(error)}` });
|
|
111
|
+
}
|
|
112
|
+
checks.push({ status: "ok", text: "本地配置目录正常" });
|
|
113
|
+
checks.push(config.cliToken
|
|
114
|
+
? { status: "ok", text: "已登录 Claude360" }
|
|
115
|
+
: { status: "warn", text: "尚未登录 Claude360" });
|
|
116
|
+
writeLine(checks.map((check) => formatCheckLine(check.status, check.text)).join("\n"));
|
|
117
|
+
writeLine("");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function ensureLogin() {
|
|
121
|
+
if (config.cliToken) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
// PRD 第 14 章注册入口:CLI 不做注册表单,只提供注册页引导
|
|
125
|
+
while (true) {
|
|
126
|
+
const guide = await promptSelect("还没有 Claude360 账号?", [
|
|
127
|
+
{ label: "打开注册页面", value: "register" },
|
|
128
|
+
{ label: "我已注册,继续授权", value: "continue" },
|
|
129
|
+
{ label: "返回", value: "back" },
|
|
130
|
+
]);
|
|
131
|
+
if (guide === "register") {
|
|
132
|
+
await openPage("/register", "Claude360 注册页面");
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (guide === "back") {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
writeLine("请在浏览器中完成 Claude360 授权。");
|
|
141
|
+
writeLine("如果浏览器没有自动打开,请根据终端提示访问授权页面并输入授权码。");
|
|
142
|
+
let cliToken;
|
|
143
|
+
try {
|
|
144
|
+
cliToken = await authWithBrowser({ api, openBrowser, sleep, writeLine });
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (isAuthServiceUnavailable(error)) {
|
|
147
|
+
writeLine(formatAuthUnavailable());
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
await saveConfig({ cliToken });
|
|
38
153
|
api = createApiClient({ baseUrl, cliToken });
|
|
154
|
+
writeLine("✓ 授权成功,登录状态已保存。");
|
|
155
|
+
return true;
|
|
39
156
|
}
|
|
40
157
|
|
|
41
|
-
|
|
158
|
+
async function ensureApiKey() {
|
|
159
|
+
if (config.apiKey) {
|
|
160
|
+
return { apiKey: config.apiKey, tokenName: config.tokenName };
|
|
161
|
+
}
|
|
42
162
|
const token = await chooseToken({ api, promptSelect, promptInput });
|
|
43
|
-
|
|
44
|
-
...config,
|
|
163
|
+
await saveConfig({
|
|
45
164
|
apiKey: token.apiKey,
|
|
46
165
|
selectedTokenId: token.tokenId,
|
|
47
166
|
tokenName: token.tokenName,
|
|
48
167
|
group: token.group,
|
|
49
|
-
};
|
|
168
|
+
});
|
|
169
|
+
return token;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function fetchMe() {
|
|
173
|
+
try {
|
|
174
|
+
const me = await api.get("/api/cli/me");
|
|
175
|
+
// 缓存账号标识,me 获取失败时账户状态区仍可降级展示(脱敏由展示层处理)
|
|
176
|
+
const account = me?.email || me?.display_name || me?.username || "";
|
|
177
|
+
if (account && account !== config.account) {
|
|
178
|
+
await saveConfig({ account });
|
|
179
|
+
}
|
|
180
|
+
return me;
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function showBalanceAndUsage() {
|
|
187
|
+
const me = await fetchMe();
|
|
188
|
+
if (!me) {
|
|
189
|
+
writeLine("暂时无法获取余额与今日用量。");
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
writeLine(`余额:${me.balance_display ?? me.quota}${me.low_balance ? " ! 余额较低" : ""}`);
|
|
193
|
+
writeLine(`今日用量:${me.today_usage_display || "-"}`);
|
|
194
|
+
return me;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function markConfigured(tool) {
|
|
198
|
+
await saveConfig({
|
|
199
|
+
configuredTools: { ...(config.configuredTools || {}), [tool]: true },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function ensureToolInstalled(target, toolName, command) {
|
|
204
|
+
const check = await commandVersion(execCommand, command, ["--version"]);
|
|
205
|
+
if (check.ok) {
|
|
206
|
+
writeLine(`✓ ${toolName} 已安装(${check.version})`);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
writeLine(`未检测到 ${toolName}。`);
|
|
210
|
+
const results = await installOrUpdateTools({ targets: [target], confirm });
|
|
211
|
+
const result = (results || []).find((item) => item.target === target);
|
|
212
|
+
if (result?.skipped) {
|
|
213
|
+
writeLine(`已跳过 ${toolName} 安装。`);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (result?.ok === false) {
|
|
217
|
+
writeLine(`安装 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
218
|
+
writeLine("国内网络失败时可尝试:npx --registry=https://registry.npmmirror.com claude360");
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 启动前检查 + 低余额提醒(PRD 16 / 17.5 / 18.5)
|
|
225
|
+
async function preflightLaunch(toolName) {
|
|
226
|
+
const me = await fetchMe();
|
|
227
|
+
if (!config.apiKey) {
|
|
228
|
+
writeLine("当前没有可用 API Key,先完成 Key 配置。");
|
|
229
|
+
await ensureApiKey();
|
|
230
|
+
}
|
|
231
|
+
writeLine(`✓ ${toolName} 启动前检查`);
|
|
232
|
+
writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
|
|
233
|
+
if (me) {
|
|
234
|
+
writeLine(`✓ Claude360 服务可访问`);
|
|
235
|
+
writeLine(`✓ 余额:${me.balance_display ?? me.quota}`);
|
|
236
|
+
writeLine(`✓ 今日用量:${me.today_usage_display || "-"}(已刷新)`);
|
|
237
|
+
} else {
|
|
238
|
+
writeLine("! Claude360 服务状态获取失败,仍可尝试启动");
|
|
239
|
+
}
|
|
240
|
+
if (me?.low_balance) {
|
|
241
|
+
const action = await promptSelect(
|
|
242
|
+
`当前余额较低(${me.balance_display},低于 ${me.low_balance_threshold_display}),可能影响模型调用。`,
|
|
243
|
+
[
|
|
244
|
+
{ label: "微信扫码充值", value: "topup" },
|
|
245
|
+
{ label: "仍然继续启动", value: "continue" },
|
|
246
|
+
{ label: "返回主菜单", value: "back" },
|
|
247
|
+
],
|
|
248
|
+
);
|
|
249
|
+
if (action === "topup") {
|
|
250
|
+
await doWechatTopUp();
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
return action === "continue";
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function doLaunchClaude() {
|
|
259
|
+
if (!(await preflightLaunch("Claude Code"))) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
await markConfigured("claudeCode");
|
|
263
|
+
writeLine("正在启动 Claude Code...");
|
|
264
|
+
await launchClaudeCode({ config });
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function doLaunchCodex() {
|
|
269
|
+
const compat = await codexCompat(api);
|
|
270
|
+
if (!compat.supported) {
|
|
271
|
+
writeLine("当前 Claude360 网关暂不支持 Codex 所需协议。");
|
|
272
|
+
writeLine("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
writeLine("✓ Claude360 已支持 Codex 所需协议");
|
|
276
|
+
if (!(await preflightLaunch("Codex"))) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
await markConfigured("codex");
|
|
280
|
+
writeLine("正在启动 Codex...");
|
|
281
|
+
await launchCodex({ config, confirmConflict: confirm, writeLine });
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function doConfigureCodex() {
|
|
286
|
+
const compat = await codexCompat(api);
|
|
287
|
+
if (!compat.supported) {
|
|
288
|
+
writeLine("当前 Claude360 网关暂不支持 Codex 所需协议,已停止写入配置。");
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
await ensureApiKey();
|
|
292
|
+
await configureCodex({ config, confirmConflict: confirm, writeLine });
|
|
293
|
+
await markConfigured("codex");
|
|
294
|
+
writeLine("✓ 已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function doWechatTopUp() {
|
|
299
|
+
try {
|
|
300
|
+
const result = await runWechatTopUp({ api, promptSelect, promptInput, writeLine, sleep });
|
|
301
|
+
writeLine("✓ 支付成功");
|
|
302
|
+
if (result?.balance) {
|
|
303
|
+
writeLine(`当前余额:${result.balance.balance_display ?? result.balance.quota}`);
|
|
304
|
+
writeLine(`今日用量:${result.balance.today_usage_display || "-"}`);
|
|
305
|
+
}
|
|
306
|
+
return result;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
writeLine(`充值未完成:${safeErrorMessage(error)}`);
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 安装或更新工具(含 claude360 CLI 自身),全局 npm 操作由 installOrUpdateTools 二次确认
|
|
314
|
+
async function runInstallToolsMenu() {
|
|
315
|
+
const selected = await promptSelect("选择要安装或更新的工具:", [
|
|
316
|
+
{ label: "Claude Code", value: "claude" },
|
|
317
|
+
{ label: "Codex", value: "codex" },
|
|
318
|
+
{ label: "claude360 CLI(自身更新)", value: "claude360" },
|
|
319
|
+
{ label: "Claude Code + Codex", value: "all" },
|
|
320
|
+
{ label: "返回", value: "back" },
|
|
321
|
+
]);
|
|
322
|
+
if (selected === "back") {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const targets = selected === "all" ? ["claude", "codex"] : [selected];
|
|
326
|
+
const results = await installOrUpdateTools({ targets, confirm });
|
|
327
|
+
for (const result of results || []) {
|
|
328
|
+
if (result?.skipped) {
|
|
329
|
+
writeLine(`已跳过 ${result.target}。`);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (result?.ok === false) {
|
|
333
|
+
writeLine(`安装或更新 ${result.target} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
writeLine(`✓ ${result.target} 安装或更新完成${result.usedMirror ? "(使用国内镜像)" : ""}`);
|
|
337
|
+
if (result.target === "claude360") {
|
|
338
|
+
writeLine("claude360 CLI 已更新,重新运行 npx claude360 后生效。");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function openPage(path, label) {
|
|
344
|
+
const url = `${baseUrl}${path}`;
|
|
345
|
+
writeLine(`正在打开${label}:${url}`);
|
|
346
|
+
await openBrowser(url);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 退出登录:仅清除本地凭证,不触碰网站侧账号与 Key(PRD 25.2 logout)
|
|
350
|
+
async function doLogout() {
|
|
351
|
+
if (!config.cliToken && !config.apiKey) {
|
|
352
|
+
writeLine("当前未登录。");
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
const approved = await confirm(
|
|
356
|
+
"将退出登录并清除本地保存的 CLI Token 与 API Key。\n不会删除 Claude360 网站上的账号或 API Key。\n是否继续?",
|
|
357
|
+
);
|
|
358
|
+
if (!approved) {
|
|
359
|
+
writeLine("已取消。");
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
config = { baseUrl };
|
|
363
|
+
await configStore.save(config);
|
|
364
|
+
api = createApiClient({ baseUrl, cliToken: "" });
|
|
365
|
+
writeLine("已退出登录。");
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function doRelogin() {
|
|
370
|
+
const approved = await confirm("将重新进行浏览器授权并覆盖本地登录状态。是否继续?");
|
|
371
|
+
if (!approved) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
await saveConfig({ cliToken: "" });
|
|
375
|
+
api = createApiClient({ baseUrl, cliToken: "" });
|
|
376
|
+
return ensureLogin();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function doClearConfig() {
|
|
380
|
+
const approved = await confirm(
|
|
381
|
+
"该操作会清除本地 Claude360 CLI 登录状态和已保存 Key。\n不会删除 Claude360 网站上的账号或 API Key。\n是否继续?",
|
|
382
|
+
);
|
|
383
|
+
if (!approved) {
|
|
384
|
+
writeLine("已取消。");
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
config = { baseUrl };
|
|
50
388
|
await configStore.save(config);
|
|
389
|
+
api = createApiClient({ baseUrl, cliToken: "" });
|
|
390
|
+
writeLine("本地配置已清除。");
|
|
391
|
+
return ensureLogin();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ──────────────────────────────────────────────
|
|
395
|
+
// 子菜单
|
|
396
|
+
// ──────────────────────────────────────────────
|
|
397
|
+
|
|
398
|
+
async function runBalanceTopUpMenu() {
|
|
399
|
+
while (true) {
|
|
400
|
+
writeLine("余额与充值\n");
|
|
401
|
+
const me = await showBalanceAndUsage();
|
|
402
|
+
if (me?.low_balance) {
|
|
403
|
+
writeLine("状态:余额偏低,建议充值");
|
|
404
|
+
}
|
|
405
|
+
const action = await promptSelect("请选择:", [
|
|
406
|
+
{ label: "微信扫码充值", value: "wechat" },
|
|
407
|
+
{ label: "打开网页充值", value: "web" },
|
|
408
|
+
{ label: "返回", value: "back" },
|
|
409
|
+
]);
|
|
410
|
+
if (action === "wechat") {
|
|
411
|
+
await doWechatTopUp();
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (action === "web") {
|
|
415
|
+
await openPage("/topup", "网页充值");
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function doCreateKey() {
|
|
423
|
+
const token = await createToken({ api, promptSelect, promptInput });
|
|
424
|
+
const useNow = await confirm(`已创建 API Key:${token.tokenName}。是否将其设为当前使用的 Key?`);
|
|
425
|
+
if (useNow) {
|
|
426
|
+
await saveConfig({
|
|
427
|
+
apiKey: token.apiKey,
|
|
428
|
+
selectedTokenId: token.tokenId,
|
|
429
|
+
tokenName: token.tokenName,
|
|
430
|
+
group: token.group,
|
|
431
|
+
});
|
|
432
|
+
writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
|
|
433
|
+
}
|
|
434
|
+
return token;
|
|
51
435
|
}
|
|
52
436
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
437
|
+
async function runClaudeCodeMenu() {
|
|
438
|
+
while (true) {
|
|
439
|
+
const action = await promptSelect("Claude Code", [
|
|
440
|
+
{ label: "安装 Claude Code", value: "install" },
|
|
441
|
+
{ label: "更新 Claude Code", value: "update" },
|
|
442
|
+
{ label: "写入 / 修复 Claude360 配置", value: "repair" },
|
|
443
|
+
{ label: "安装推荐 MCP", value: "mcp" },
|
|
444
|
+
{ label: "安装推荐工作流 / Skill", value: "skill" },
|
|
445
|
+
{ label: "启动 Claude Code", value: "launch" },
|
|
446
|
+
{ label: "返回", value: "back" },
|
|
447
|
+
]);
|
|
448
|
+
switch (action) {
|
|
449
|
+
case "install":
|
|
450
|
+
case "update":
|
|
451
|
+
await installOrUpdateTools({ targets: ["claude"], confirm });
|
|
452
|
+
break;
|
|
453
|
+
case "repair":
|
|
454
|
+
await ensureApiKey();
|
|
455
|
+
await markConfigured("claudeCode");
|
|
456
|
+
writeLine("✓ Claude Code 通过启动时环境变量注入 Claude360 配置(不修改 shell profile)。");
|
|
457
|
+
writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
|
|
458
|
+
writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
|
|
459
|
+
await showBalanceAndUsage();
|
|
460
|
+
break;
|
|
461
|
+
case "mcp":
|
|
462
|
+
await installMcps({ promptSelect, confirm, writeLine });
|
|
463
|
+
break;
|
|
464
|
+
case "skill":
|
|
465
|
+
await installSkills({ promptSelect, confirm, writeLine });
|
|
466
|
+
break;
|
|
467
|
+
case "launch":
|
|
468
|
+
if (await doLaunchClaude()) {
|
|
469
|
+
return "launched";
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
472
|
+
default:
|
|
473
|
+
return "back";
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async function runCodexMenu() {
|
|
479
|
+
while (true) {
|
|
480
|
+
const action = await promptSelect("Codex", [
|
|
481
|
+
{ label: "安装 Codex", value: "install" },
|
|
482
|
+
{ label: "更新 Codex", value: "update" },
|
|
483
|
+
{ label: "写入 / 修复 Claude360 Provider", value: "repair" },
|
|
484
|
+
{ label: "安装推荐 AGENTS / Skill", value: "agents" },
|
|
485
|
+
{ label: "启动 Codex", value: "launch" },
|
|
486
|
+
{ label: "返回", value: "back" },
|
|
487
|
+
]);
|
|
488
|
+
switch (action) {
|
|
489
|
+
case "install":
|
|
490
|
+
case "update":
|
|
491
|
+
await installOrUpdateTools({ targets: ["codex"], confirm });
|
|
492
|
+
break;
|
|
493
|
+
case "repair":
|
|
494
|
+
await doConfigureCodex();
|
|
495
|
+
break;
|
|
496
|
+
case "agents":
|
|
497
|
+
await installAgents({ confirm, writeLine });
|
|
498
|
+
break;
|
|
499
|
+
case "launch":
|
|
500
|
+
if (await doLaunchCodex()) {
|
|
501
|
+
return "launched";
|
|
502
|
+
}
|
|
503
|
+
break;
|
|
504
|
+
default:
|
|
505
|
+
return "back";
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function runMcpSkillMenu() {
|
|
511
|
+
while (true) {
|
|
512
|
+
const action = await promptSelect("推荐 MCP / Skill", [
|
|
513
|
+
{ label: "为 Claude Code 安装推荐 MCP", value: "mcp" },
|
|
514
|
+
{ label: "为 Claude Code 安装推荐工作流 / Skill", value: "skill" },
|
|
515
|
+
{ label: "为 Codex 安装推荐 AGENTS / Skill", value: "agents" },
|
|
516
|
+
{ label: "查看已安装增强配置", value: "list" },
|
|
517
|
+
{ label: "返回", value: "back" },
|
|
518
|
+
]);
|
|
519
|
+
switch (action) {
|
|
520
|
+
case "mcp":
|
|
521
|
+
await installMcps({ promptSelect, confirm, writeLine });
|
|
522
|
+
break;
|
|
523
|
+
case "skill":
|
|
524
|
+
await installSkills({ promptSelect, confirm, writeLine });
|
|
525
|
+
break;
|
|
526
|
+
case "agents":
|
|
527
|
+
await installAgents({ confirm, writeLine });
|
|
528
|
+
break;
|
|
529
|
+
case "list":
|
|
530
|
+
writeLine(await listEnhancements());
|
|
531
|
+
break;
|
|
532
|
+
default:
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function runDiagnosticsMenu() {
|
|
539
|
+
while (true) {
|
|
540
|
+
const action = await promptSelect("诊断与修复", [
|
|
541
|
+
{ label: "一键诊断", value: "diagnose" },
|
|
542
|
+
{ label: "修复 Claude Code 配置", value: "fix_claude" },
|
|
543
|
+
{ label: "修复 Codex 配置", value: "fix_codex" },
|
|
544
|
+
{ label: "重新创建 / 选择 API Key", value: "rekey" },
|
|
545
|
+
{ label: "清除本地配置并重新登录", value: "reset" },
|
|
546
|
+
{ label: "返回", value: "back" },
|
|
547
|
+
]);
|
|
548
|
+
switch (action) {
|
|
549
|
+
case "diagnose": {
|
|
550
|
+
const report = await runDiagnostics({ config, api, execCommand });
|
|
551
|
+
writeLine(formatDiagnosticsSummary(report));
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case "fix_claude":
|
|
555
|
+
await ensureApiKey();
|
|
556
|
+
await markConfigured("claudeCode");
|
|
557
|
+
await showBalanceAndUsage();
|
|
558
|
+
writeLine("✓ Claude Code 配置修复完成(启动时注入环境变量)。");
|
|
559
|
+
break;
|
|
560
|
+
case "fix_codex":
|
|
561
|
+
await doConfigureCodex();
|
|
562
|
+
break;
|
|
563
|
+
case "rekey": {
|
|
564
|
+
const token = await chooseToken({ api, promptSelect, promptInput });
|
|
565
|
+
await saveConfig({
|
|
566
|
+
apiKey: token.apiKey,
|
|
567
|
+
selectedTokenId: token.tokenId,
|
|
568
|
+
tokenName: token.tokenName,
|
|
569
|
+
group: token.group,
|
|
570
|
+
});
|
|
571
|
+
writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
case "reset":
|
|
575
|
+
if (await doClearConfig()) {
|
|
576
|
+
return "reset";
|
|
577
|
+
}
|
|
578
|
+
break;
|
|
579
|
+
default:
|
|
580
|
+
return "back";
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ──────────────────────────────────────────────
|
|
586
|
+
// 首次一键配置流程(PRD 第 12 章)
|
|
587
|
+
// ──────────────────────────────────────────────
|
|
588
|
+
|
|
589
|
+
async function runFirstSetup(targets) {
|
|
590
|
+
if (!(await ensureLogin())) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
await showBalanceAndUsage();
|
|
594
|
+
await ensureApiKey();
|
|
595
|
+
|
|
596
|
+
if (targets.includes("claude")) {
|
|
597
|
+
if (!(await ensureToolInstalled("claude", "Claude Code", "claude"))) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
await markConfigured("claudeCode");
|
|
601
|
+
writeLine("✓ Claude Code 将在启动时注入 Claude360 配置。");
|
|
602
|
+
}
|
|
603
|
+
if (targets.includes("codex")) {
|
|
604
|
+
if (!(await ensureToolInstalled("codex", "Codex", "codex"))) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
if (!(await doConfigureCodex())) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// 可选增强配置,不阻塞主流程
|
|
613
|
+
if (targets.includes("claude")) {
|
|
614
|
+
const wantMcp = await promptSelect("是否安装推荐 MCP / Skill?(可跳过)", [
|
|
615
|
+
{ label: "安装推荐 MCP", value: "mcp" },
|
|
616
|
+
{ label: "安装推荐工作流 / Skill", value: "skill" },
|
|
617
|
+
{ label: "跳过", value: "skip" },
|
|
618
|
+
]);
|
|
619
|
+
if (wantMcp === "mcp") {
|
|
620
|
+
await installMcps({ promptSelect, confirm, writeLine });
|
|
621
|
+
} else if (wantMcp === "skill") {
|
|
622
|
+
await installSkills({ promptSelect, confirm, writeLine });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (targets.includes("codex")) {
|
|
626
|
+
const wantAgents = await promptSelect("是否安装 Codex 推荐 AGENTS?(可跳过)", [
|
|
627
|
+
{ label: "安装", value: "agents" },
|
|
628
|
+
{ label: "跳过", value: "skip" },
|
|
629
|
+
]);
|
|
630
|
+
if (wantAgents === "agents") {
|
|
631
|
+
await installAgents({ confirm, writeLine });
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// 测试连接
|
|
636
|
+
const me = await fetchMe();
|
|
637
|
+
writeLine(me ? "✓ Claude360 连接测试通过" : "! Claude360 连接测试失败,可稍后在诊断菜单排查");
|
|
638
|
+
|
|
639
|
+
// 启动
|
|
640
|
+
if (targets.length === 1) {
|
|
641
|
+
const tool = targets[0];
|
|
642
|
+
const label = tool === "claude" ? "Claude Code" : "Codex";
|
|
643
|
+
if (await confirm(`配置完成。是否立即启动 ${label}?`)) {
|
|
644
|
+
return tool === "claude" ? doLaunchClaude() : doLaunchCodex();
|
|
645
|
+
}
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
const launch = await promptSelect("配置完成。选择要启动的工具:", [
|
|
649
|
+
{ label: "启动 Claude Code", value: "claude" },
|
|
650
|
+
{ label: "启动 Codex", value: "codex" },
|
|
651
|
+
{ label: "暂不启动", value: "skip" },
|
|
652
|
+
]);
|
|
653
|
+
if (launch === "claude") {
|
|
654
|
+
return doLaunchClaude();
|
|
655
|
+
}
|
|
656
|
+
if (launch === "codex") {
|
|
657
|
+
return doLaunchCodex();
|
|
658
|
+
}
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async function runFirstRunFlow() {
|
|
663
|
+
if (forceSetup) {
|
|
664
|
+
try {
|
|
665
|
+
await checkEnvironment({ writeLine, execCommand });
|
|
666
|
+
} catch (error) {
|
|
667
|
+
writeLine(safeErrorMessage(error));
|
|
668
|
+
return "environment_failed";
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
while (true) {
|
|
672
|
+
const action = await promptMenu({ menu: buildFirstRunMenu(), promptInput, writeLine });
|
|
673
|
+
switch (action) {
|
|
674
|
+
case "setup_claude":
|
|
675
|
+
if (await runFirstSetup(["claude"])) {
|
|
676
|
+
return "setup_claude";
|
|
677
|
+
}
|
|
678
|
+
break;
|
|
679
|
+
case "setup_codex":
|
|
680
|
+
if (await runFirstSetup(["codex"])) {
|
|
681
|
+
return "setup_codex";
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
case "setup_both":
|
|
685
|
+
if (await runFirstSetup(["claude", "codex"])) {
|
|
686
|
+
return "setup_both";
|
|
687
|
+
}
|
|
688
|
+
break;
|
|
689
|
+
case "login_only":
|
|
690
|
+
if (await ensureLogin()) {
|
|
691
|
+
await showBalanceAndUsage();
|
|
692
|
+
await ensureApiKey();
|
|
693
|
+
return runDailyFlow();
|
|
694
|
+
}
|
|
695
|
+
break;
|
|
696
|
+
case "cc_switch":
|
|
697
|
+
if (await ensureLogin()) {
|
|
698
|
+
await ccSwitchGenerator({
|
|
699
|
+
config,
|
|
700
|
+
promptSelect,
|
|
701
|
+
writeLine,
|
|
702
|
+
ensureApiKey,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
break;
|
|
706
|
+
case "open_register":
|
|
707
|
+
await openPage("/register", "Claude360 注册 / 登录页面");
|
|
708
|
+
break;
|
|
709
|
+
default:
|
|
710
|
+
return "exit";
|
|
711
|
+
}
|
|
712
|
+
if (config.cliToken && action !== "cc_switch" && action !== "open_register") {
|
|
713
|
+
return runDailyFlow();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ──────────────────────────────────────────────
|
|
719
|
+
// 日常菜单循环(PRD 第 11 章)
|
|
720
|
+
// ──────────────────────────────────────────────
|
|
721
|
+
|
|
722
|
+
async function runDailyFlow() {
|
|
723
|
+
while (true) {
|
|
724
|
+
const status = await loadAccountStatus({ api, config });
|
|
725
|
+
writeLine(formatAccountStatus(status));
|
|
726
|
+
writeLine("");
|
|
727
|
+
const action = await promptMenu({ menu: buildDailyMenu(), promptInput, writeLine });
|
|
728
|
+
switch (action) {
|
|
729
|
+
case "launch_claude":
|
|
730
|
+
if (await doLaunchClaude()) {
|
|
731
|
+
return action;
|
|
732
|
+
}
|
|
733
|
+
break;
|
|
734
|
+
case "launch_codex":
|
|
735
|
+
if (await doLaunchCodex()) {
|
|
736
|
+
return action;
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
739
|
+
case "balance_topup":
|
|
740
|
+
await runBalanceTopUpMenu();
|
|
741
|
+
break;
|
|
742
|
+
case "create_key":
|
|
743
|
+
await doCreateKey();
|
|
744
|
+
break;
|
|
745
|
+
case "open_console":
|
|
746
|
+
await openPage("/console", "Claude360 控制台");
|
|
747
|
+
break;
|
|
748
|
+
case "claude_code_menu":
|
|
749
|
+
if ((await runClaudeCodeMenu()) === "launched") {
|
|
750
|
+
return action;
|
|
751
|
+
}
|
|
752
|
+
break;
|
|
753
|
+
case "codex_menu":
|
|
754
|
+
if ((await runCodexMenu()) === "launched") {
|
|
755
|
+
return action;
|
|
756
|
+
}
|
|
757
|
+
break;
|
|
758
|
+
case "mcp_skill":
|
|
759
|
+
await runMcpSkillMenu();
|
|
760
|
+
break;
|
|
761
|
+
case "cc_switch":
|
|
762
|
+
await ccSwitchGenerator({
|
|
763
|
+
config,
|
|
764
|
+
promptSelect,
|
|
765
|
+
writeLine,
|
|
766
|
+
ensureApiKey,
|
|
767
|
+
});
|
|
768
|
+
break;
|
|
769
|
+
case "diagnostics_menu":
|
|
770
|
+
await runDiagnosticsMenu();
|
|
771
|
+
break;
|
|
772
|
+
case "install_tools":
|
|
773
|
+
await runInstallToolsMenu();
|
|
774
|
+
break;
|
|
775
|
+
case "relogin":
|
|
776
|
+
await doRelogin();
|
|
777
|
+
break;
|
|
778
|
+
default:
|
|
779
|
+
return "exit";
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// ──────────────────────────────────────────────
|
|
785
|
+
// 快捷命令直达(PRD 25.2,可选增强)
|
|
786
|
+
// ──────────────────────────────────────────────
|
|
787
|
+
|
|
788
|
+
async function runShortcut(name) {
|
|
789
|
+
switch (name) {
|
|
790
|
+
case "claude":
|
|
791
|
+
if (!(await ensureLogin())) {
|
|
792
|
+
return "login_required";
|
|
793
|
+
}
|
|
794
|
+
return (await doLaunchClaude()) ? "launch_claude" : "launch_cancelled";
|
|
795
|
+
case "codex":
|
|
796
|
+
if (!(await ensureLogin())) {
|
|
797
|
+
return "login_required";
|
|
798
|
+
}
|
|
799
|
+
return (await doLaunchCodex()) ? "launch_codex" : "launch_cancelled";
|
|
800
|
+
case "cc-switch":
|
|
801
|
+
if (!(await ensureLogin())) {
|
|
802
|
+
return "login_required";
|
|
803
|
+
}
|
|
804
|
+
await ccSwitchGenerator({ config, promptSelect, writeLine, ensureApiKey });
|
|
805
|
+
return "cc_switch";
|
|
806
|
+
case "doctor": {
|
|
807
|
+
const report = await runDiagnostics({ config, api, execCommand });
|
|
79
808
|
writeLine(formatDiagnosticsSummary(report));
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
809
|
+
return "doctor";
|
|
810
|
+
}
|
|
811
|
+
case "login":
|
|
812
|
+
if (config.cliToken) {
|
|
813
|
+
return (await doRelogin()) ? "login" : "login_cancelled";
|
|
814
|
+
}
|
|
815
|
+
return (await ensureLogin()) ? "login" : "login_cancelled";
|
|
816
|
+
case "logout":
|
|
817
|
+
return (await doLogout()) ? "logout" : "logout_cancelled";
|
|
818
|
+
default:
|
|
819
|
+
writeLine(formatCliUsage());
|
|
820
|
+
return "unknown_command";
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (command && command !== "setup") {
|
|
825
|
+
return runShortcut(command);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (forceSetup || !config.cliToken) {
|
|
829
|
+
return runFirstRunFlow();
|
|
830
|
+
}
|
|
831
|
+
return runDailyFlow();
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function formatCliUsage() {
|
|
835
|
+
return [
|
|
836
|
+
"用法:npx claude360 [命令]",
|
|
837
|
+
"",
|
|
838
|
+
"(无命令) 进入交互式菜单",
|
|
839
|
+
"setup 重新运行首次配置向导",
|
|
840
|
+
"claude 启动 Claude Code",
|
|
841
|
+
"codex 启动 Codex",
|
|
842
|
+
"cc-switch 生成 cc-switch 配置",
|
|
843
|
+
"doctor 一键诊断",
|
|
844
|
+
"login 浏览器授权登录",
|
|
845
|
+
"logout 退出登录并清除本地凭证",
|
|
846
|
+
].join("\n");
|
|
83
847
|
}
|
|
84
848
|
|
|
85
849
|
async function defaultPromptSelect(message, choices) {
|
|
@@ -124,24 +888,6 @@ function writeChoices(message, choices) {
|
|
|
124
888
|
});
|
|
125
889
|
}
|
|
126
890
|
|
|
127
|
-
async function selectToolTargets(promptSelect) {
|
|
128
|
-
const selected = await promptSelect("选择安装或更新目标", [
|
|
129
|
-
{ label: "仅 Claude Code", value: "claude" },
|
|
130
|
-
{ label: "仅 Codex", value: "codex" },
|
|
131
|
-
{ label: "Claude Code 和 Codex", value: "all" },
|
|
132
|
-
]);
|
|
133
|
-
if (selected === "claude") {
|
|
134
|
-
return ["claude360", "claude"];
|
|
135
|
-
}
|
|
136
|
-
if (selected === "codex") {
|
|
137
|
-
return ["claude360", "codex"];
|
|
138
|
-
}
|
|
139
|
-
if (selected === "all") {
|
|
140
|
-
return ["claude360", "claude", "codex"];
|
|
141
|
-
}
|
|
142
|
-
throw new Error("安装或更新目标无效");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
891
|
function defaultOpenBrowser(url) {
|
|
146
892
|
const commands = {
|
|
147
893
|
win32: ["cmd", ["/c", "start", "", url]],
|
|
@@ -158,3 +904,22 @@ function defaultOpenBrowser(url) {
|
|
|
158
904
|
child.on("close", () => resolve());
|
|
159
905
|
});
|
|
160
906
|
}
|
|
907
|
+
|
|
908
|
+
function isAuthServiceUnavailable(error) {
|
|
909
|
+
if (!error) {
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
const status = error.status;
|
|
913
|
+
if (status === 404 || (typeof status === "number" && status >= 500)) {
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
return /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|getaddrinfo|network/i.test(error.message || "");
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function formatAuthUnavailable() {
|
|
920
|
+
return [
|
|
921
|
+
"站点 CLI 授权服务暂未就绪(授权接口返回 404 或网络不可达)。",
|
|
922
|
+
"已安装的工具不受影响;待授权服务可用后,运行 `claude360 setup` 重新完成授权与 API Key 配置。",
|
|
923
|
+
"可稍后重试,或先检查网络连接与站点状态。",
|
|
924
|
+
].join("\n");
|
|
925
|
+
}
|