claude360 0.2.8 → 0.2.9
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 +107 -126
- package/package.json +1 -1
- package/src/account-status.js +8 -5
- package/src/auth.js +9 -2
- package/src/banner.js +6 -6
- package/src/cc-switch.js +11 -5
- package/src/colors.js +31 -0
- package/src/diagnostics.js +36 -17
- package/src/index.js +104 -80
- package/src/init-config.js +39 -25
- package/src/init-flow.js +13 -6
- package/src/mcp-skill.js +32 -21
- package/src/menu.js +8 -7
- package/src/messages.js +78 -0
- package/src/onboarding.js +12 -5
- package/src/prompts.js +7 -6
- package/src/tool-launcher.js +109 -46
- package/src/topup.js +6 -2
- package/src/ui.js +9 -8
- package/src/workflows.js +10 -7
package/src/diagnostics.js
CHANGED
|
@@ -3,8 +3,9 @@ import { access, readFile as fsReadFile } from "node:fs/promises";
|
|
|
3
3
|
import { constants } from "node:fs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
|
|
6
|
-
import { resolveCodexConfigPath } from "./tool-launcher.js";
|
|
6
|
+
import { resolveCodexConfigPath, resolveCodexProfileConfigPath } from "./tool-launcher.js";
|
|
7
7
|
import { sanitizeError, sanitizeText } from "./sanitize.js";
|
|
8
|
+
import { formatMessage } from "./messages.js";
|
|
8
9
|
import { renderHeader, renderSectionTitle, renderTable } from "./ui.js";
|
|
9
10
|
|
|
10
11
|
export async function runDiagnostics({
|
|
@@ -14,6 +15,7 @@ export async function runDiagnostics({
|
|
|
14
15
|
checkPathWritable = defaultCheckPathWritable,
|
|
15
16
|
readFile = fsReadFile,
|
|
16
17
|
codexConfigPath = resolveCodexConfigPath(),
|
|
18
|
+
codexProfileConfigPath = resolveCodexProfileConfigPath(),
|
|
17
19
|
api,
|
|
18
20
|
} = {}) {
|
|
19
21
|
const node = await commandVersion(execCommand, "node", ["--version"]);
|
|
@@ -65,7 +67,7 @@ export async function runDiagnostics({
|
|
|
65
67
|
token: buildTokenStatus({ config, tokens }),
|
|
66
68
|
topUp: buildTopUpStatus({ topUpOptions }),
|
|
67
69
|
claudeCodeConfig: buildClaudeCodeConfigStatus({ config, claudeCode }),
|
|
68
|
-
codexConfig: await buildCodexConfigStatus({ readFile, codexConfigPath, codex }),
|
|
70
|
+
codexConfig: await buildCodexConfigStatus({ readFile, codexConfigPath, codexProfileConfigPath, codex }),
|
|
69
71
|
codexCompat: buildCodexCompatStatus({ codexCompat }),
|
|
70
72
|
};
|
|
71
73
|
}
|
|
@@ -108,22 +110,39 @@ function buildClaudeCodeConfigStatus({ config, claudeCode }) {
|
|
|
108
110
|
return { ok: true, detail: "启动时注入 ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN" };
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
async function buildCodexConfigStatus({ readFile, codexConfigPath, codex }) {
|
|
113
|
+
async function buildCodexConfigStatus({ readFile, codexConfigPath, codexProfileConfigPath, codex }) {
|
|
112
114
|
if (!codex.ok) {
|
|
113
115
|
return { ok: false, detail: "Codex 未安装" };
|
|
114
116
|
}
|
|
117
|
+
let baseContent;
|
|
115
118
|
try {
|
|
116
|
-
|
|
117
|
-
if (content.includes("[model_providers.claude360]") && content.includes("[profiles.claude360]")) {
|
|
118
|
-
return { ok: true, detail: "profile claude360 正常" };
|
|
119
|
-
}
|
|
120
|
-
return { ok: false, detail: "未写入 claude360 provider/profile" };
|
|
119
|
+
baseContent = await readFile(codexConfigPath, "utf8");
|
|
121
120
|
} catch (error) {
|
|
122
121
|
if (error?.code === "ENOENT") {
|
|
123
122
|
return { ok: false, detail: "未找到 ~/.codex/config.toml" };
|
|
124
123
|
}
|
|
125
124
|
return { ok: false, detail: sanitizeError(error) || "读取 config.toml 失败" };
|
|
126
125
|
}
|
|
126
|
+
if (!baseContent.includes("[model_providers.claude360]")) {
|
|
127
|
+
return { ok: false, detail: "未写入 claude360 provider" };
|
|
128
|
+
}
|
|
129
|
+
// legacy [profiles.claude360] 与 --profile 共存会导致新版 Codex 启动失败,需重新写入迁移
|
|
130
|
+
if (baseContent.includes("[profiles.claude360]")) {
|
|
131
|
+
return { ok: false, detail: "检测到 legacy [profiles.claude360],请在「Codex 配置」中重新写入以迁移" };
|
|
132
|
+
}
|
|
133
|
+
let profileContent;
|
|
134
|
+
try {
|
|
135
|
+
profileContent = await readFile(codexProfileConfigPath, "utf8");
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error?.code === "ENOENT") {
|
|
138
|
+
return { ok: false, detail: "缺少 ~/.codex/claude360.config.toml(profile 覆盖层)" };
|
|
139
|
+
}
|
|
140
|
+
return { ok: false, detail: sanitizeError(error) || "读取 claude360.config.toml 失败" };
|
|
141
|
+
}
|
|
142
|
+
if (!/model_provider\s*=\s*"claude360"/.test(profileContent)) {
|
|
143
|
+
return { ok: false, detail: "claude360.config.toml 缺少 model_provider" };
|
|
144
|
+
}
|
|
145
|
+
return { ok: true, detail: "profile claude360 正常" };
|
|
127
146
|
}
|
|
128
147
|
|
|
129
148
|
function buildCodexCompatStatus({ codexCompat }) {
|
|
@@ -139,7 +158,7 @@ function buildCodexCompatStatus({ codexCompat }) {
|
|
|
139
158
|
// 诊断报告(补充需求第 5 节):按模块分组的小表格展示,
|
|
140
159
|
// 状态用文字标签(通过/警告/失败)明确区分,失败项附修复建议,
|
|
141
160
|
// 输出适合直接复制给客服或开发排查(出口统一脱敏)。
|
|
142
|
-
export function formatDiagnosticsSummary(report, { width = 0 } = {}) {
|
|
161
|
+
export function formatDiagnosticsSummary(report, { width = 0, color = false } = {}) {
|
|
143
162
|
const groups = [
|
|
144
163
|
{
|
|
145
164
|
title: "系统环境",
|
|
@@ -179,31 +198,31 @@ export function formatDiagnosticsSummary(report, { width = 0 } = {}) {
|
|
|
179
198
|
},
|
|
180
199
|
];
|
|
181
200
|
|
|
182
|
-
const lines = [renderHeader("诊断报告")];
|
|
201
|
+
const lines = [renderHeader("诊断报告", { color })];
|
|
183
202
|
for (const group of groups) {
|
|
184
|
-
lines.push("", renderSectionTitle(group.title));
|
|
203
|
+
lines.push("", renderSectionTitle(group.title, { color }));
|
|
185
204
|
lines.push(renderTable({
|
|
186
205
|
head: ["检查项", "结果", "状态"],
|
|
187
206
|
rows: group.items.map(({ label, item, valueKey = "detail" }) => [
|
|
188
207
|
label,
|
|
189
208
|
String(item[valueKey] || item.detail || "-"),
|
|
190
|
-
statusLabel(item),
|
|
209
|
+
statusLabel(item, color),
|
|
191
210
|
]),
|
|
192
|
-
}, { width }));
|
|
211
|
+
}, { width, color }));
|
|
193
212
|
const fixes = group.items.filter(({ item, fix }) => !item.ok && fix);
|
|
194
213
|
for (const { label, item, fix } of fixes) {
|
|
195
|
-
lines.push(
|
|
214
|
+
lines.push(formatMessage(item.warn ? "warn" : "error", `${label} 建议:${fix}`, { color }));
|
|
196
215
|
}
|
|
197
216
|
}
|
|
198
217
|
// 出口兜底:report 由调用方注入,逐项 detail 可能未经脱敏
|
|
199
218
|
return sanitizeText(lines.join("\n"));
|
|
200
219
|
}
|
|
201
220
|
|
|
202
|
-
function statusLabel(item) {
|
|
221
|
+
function statusLabel(item, color = false) {
|
|
203
222
|
if (item.ok) {
|
|
204
|
-
return "
|
|
223
|
+
return formatMessage("success", "通过", { color });
|
|
205
224
|
}
|
|
206
|
-
return item.warn ? "
|
|
225
|
+
return item.warn ? formatMessage("warn", "警告", { color }) : formatMessage("error", "失败", { color });
|
|
207
226
|
}
|
|
208
227
|
|
|
209
228
|
function defaultPlatform() {
|
package/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { colorLevel, formatCheckLine, playBanner, renderBanner } from "./banner.
|
|
|
10
10
|
import { runCcSwitchGenerator } from "./cc-switch.js";
|
|
11
11
|
import { createConfigStore } from "./config-store.js";
|
|
12
12
|
import { createPrompts, isInteractive } from "./prompts.js";
|
|
13
|
+
import { createMessenger, formatMessage } from "./messages.js";
|
|
13
14
|
import { renderHeader } from "./ui.js";
|
|
14
15
|
import {
|
|
15
16
|
commandVersion,
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
launchClaudeCode as startClaudeCode,
|
|
50
51
|
launchCodex as startCodex,
|
|
51
52
|
resolveCodexConfigPath,
|
|
53
|
+
resolveCodexProfileConfigPath,
|
|
52
54
|
} from "./tool-launcher.js";
|
|
53
55
|
import { installClaudeWorkflows, installCodexWorkflows } from "./workflows.js";
|
|
54
56
|
import { showOpenSourceNotice } from "./zcf-notice.js";
|
|
@@ -114,6 +116,10 @@ export async function runCli({
|
|
|
114
116
|
let api = createApiClient({ baseUrl, cliToken: config.cliToken || "" });
|
|
115
117
|
// 仅在真实终端(writeLine 未被测试替换)启用彩色与动效
|
|
116
118
|
const fancyOutput = writeLine === console.log ? colorLevel() : 0;
|
|
119
|
+
// 语义消息器:把行内交互文字按角色着色(成功/失败/警告/步骤/键值回显/路径/
|
|
120
|
+
// 次要说明等),统一层级,见 messages.js。color 跟随 fancyOutput,
|
|
121
|
+
// 测试/管道(writeLine 被替换)下为 0,输出退化为纯文本前缀。
|
|
122
|
+
const msg = createMessenger({ writeLine, color: fancyOutput });
|
|
117
123
|
// 方向键交互:真实 TTY 且交互未被测试替换时启用(优化需求第五节),
|
|
118
124
|
// 非交互环境降级为编号输入,由 prompts.js 统一处理
|
|
119
125
|
const interactiveUi = writeLine === console.log && isInteractive();
|
|
@@ -192,8 +198,8 @@ export async function runCli({
|
|
|
192
198
|
}
|
|
193
199
|
break;
|
|
194
200
|
}
|
|
195
|
-
|
|
196
|
-
|
|
201
|
+
msg.info("请在浏览器中完成 Claude360 授权。");
|
|
202
|
+
msg.info("如果浏览器没有自动打开,请根据终端提示访问授权页面并输入授权码。");
|
|
197
203
|
let cliToken;
|
|
198
204
|
try {
|
|
199
205
|
cliToken = await authWithBrowser({ api, openBrowser, sleep, writeLine });
|
|
@@ -206,7 +212,7 @@ export async function runCli({
|
|
|
206
212
|
}
|
|
207
213
|
await saveConfig({ cliToken });
|
|
208
214
|
api = createApiClient({ baseUrl, cliToken });
|
|
209
|
-
|
|
215
|
+
msg.success("授权成功,登录状态已保存。");
|
|
210
216
|
return true;
|
|
211
217
|
}
|
|
212
218
|
|
|
@@ -241,11 +247,14 @@ export async function runCli({
|
|
|
241
247
|
async function showBalanceAndUsage() {
|
|
242
248
|
const me = await fetchMe();
|
|
243
249
|
if (!me) {
|
|
244
|
-
|
|
250
|
+
msg.error("暂时无法获取余额与今日用量。");
|
|
245
251
|
return null;
|
|
246
252
|
}
|
|
247
|
-
|
|
248
|
-
|
|
253
|
+
msg.result("余额", me.balance_display ?? me.quota);
|
|
254
|
+
if (me.low_balance) {
|
|
255
|
+
msg.warn("余额较低");
|
|
256
|
+
}
|
|
257
|
+
msg.result("今日用量", me.today_usage_display || "-");
|
|
249
258
|
return me;
|
|
250
259
|
}
|
|
251
260
|
|
|
@@ -266,40 +275,40 @@ export async function runCli({
|
|
|
266
275
|
|
|
267
276
|
// 检查 / 安装 / 更新工具(PRD 6.1 / 8.1):展示当前与最新版本,更新需确认
|
|
268
277
|
async function checkInstallOrUpdateTool(target, toolName, command) {
|
|
269
|
-
|
|
278
|
+
msg.step(`正在检查 ${toolName}...`);
|
|
270
279
|
const check = await commandVersion(execCommand, command, ["--version"]);
|
|
271
280
|
if (!check.ok) {
|
|
272
281
|
return ensureToolInstalled(target, toolName, command);
|
|
273
282
|
}
|
|
274
|
-
|
|
283
|
+
msg.result("当前版本", check.version);
|
|
275
284
|
const packageName = buildInstallCommand(target).packageName;
|
|
276
285
|
const latest = await execCommand("npm", ["view", packageName, "version"]);
|
|
277
286
|
if (latest.ok) {
|
|
278
|
-
|
|
287
|
+
msg.result("最新版本", `v${latest.stdout.trim()}`);
|
|
279
288
|
} else {
|
|
280
|
-
|
|
289
|
+
msg.warn("最新版本获取失败(npm 网络异常时可使用国内镜像)");
|
|
281
290
|
}
|
|
282
|
-
|
|
291
|
+
msg.info(`检测到 ${toolName} 将通过 npm 更新。`);
|
|
283
292
|
const action = await promptSelect(`是否更新 ${toolName}?`, [
|
|
284
293
|
{ label: "更新到最新版", value: "update" },
|
|
285
294
|
{ label: "跳过", value: "skip" },
|
|
286
295
|
]);
|
|
287
296
|
if (action !== "update") {
|
|
288
|
-
|
|
297
|
+
msg.info(`已跳过 ${toolName} 更新。`);
|
|
289
298
|
return true;
|
|
290
299
|
}
|
|
291
300
|
const results = await installOrUpdateTools({ targets: [target], confirm });
|
|
292
301
|
const result = (results || []).find((item) => item.target === target);
|
|
293
302
|
if (result?.skipped) {
|
|
294
|
-
|
|
303
|
+
msg.info(`已跳过 ${toolName} 更新。`);
|
|
295
304
|
return true;
|
|
296
305
|
}
|
|
297
306
|
if (result?.ok === false) {
|
|
298
|
-
|
|
299
|
-
|
|
307
|
+
msg.error(`更新 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
308
|
+
msg.hint("国内网络失败时可尝试:npm install -g --registry=https://registry.npmmirror.com");
|
|
300
309
|
return false;
|
|
301
310
|
}
|
|
302
|
-
|
|
311
|
+
msg.success(`${toolName} 已更新到最新版。`);
|
|
303
312
|
return true;
|
|
304
313
|
}
|
|
305
314
|
|
|
@@ -325,14 +334,14 @@ export async function runCli({
|
|
|
325
334
|
tokenName: token.tokenName,
|
|
326
335
|
group: token.group,
|
|
327
336
|
});
|
|
328
|
-
|
|
337
|
+
msg.success(`当前 Key 已切换为 ${token.tokenName}`);
|
|
329
338
|
} else {
|
|
330
339
|
await ensureApiKey();
|
|
331
340
|
}
|
|
332
341
|
await markConfigured("claudeCode");
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
342
|
+
msg.success("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
|
|
343
|
+
msg.path(` ANTHROPIC_BASE_URL=${baseUrl}`);
|
|
344
|
+
msg.path(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
|
|
336
345
|
return true;
|
|
337
346
|
}
|
|
338
347
|
}
|
|
@@ -393,8 +402,9 @@ export async function runCli({
|
|
|
393
402
|
async function runCodexProviderMenu() {
|
|
394
403
|
while (true) {
|
|
395
404
|
writeLine(renderHeader("Codex Provider 配置", { color: fancyOutput }));
|
|
396
|
-
|
|
397
|
-
|
|
405
|
+
msg.result("当前 Provider", "claude360");
|
|
406
|
+
msg.result("当前 Profile", "claude360");
|
|
407
|
+
writeLine("");
|
|
398
408
|
const action = await promptSelect("请选择:", [
|
|
399
409
|
{ label: "写入 / 修复 Claude360 Provider", value: "write" },
|
|
400
410
|
{ label: "查看当前 Codex 配置", value: "view" },
|
|
@@ -409,13 +419,27 @@ export async function runCli({
|
|
|
409
419
|
}
|
|
410
420
|
try {
|
|
411
421
|
const content = await readFileImpl(resolveCodexConfigPath(), "utf8");
|
|
412
|
-
|
|
422
|
+
msg.info("当前 ~/.codex/config.toml 内容:");
|
|
423
|
+
writeLine("");
|
|
413
424
|
writeLine(content);
|
|
414
425
|
} catch (error) {
|
|
415
426
|
if (error?.code === "ENOENT") {
|
|
416
|
-
|
|
427
|
+
msg.info("未找到 ~/.codex/config.toml,可先执行「写入 / 修复 Claude360 Provider」。");
|
|
428
|
+
} else {
|
|
429
|
+
msg.error(`读取 Codex 配置失败:${safeErrorMessage(error)}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const profileContent = await readFileImpl(resolveCodexProfileConfigPath(), "utf8");
|
|
434
|
+
writeLine("");
|
|
435
|
+
msg.info("当前 ~/.codex/claude360.config.toml(profile 覆盖层)内容:");
|
|
436
|
+
writeLine("");
|
|
437
|
+
writeLine(profileContent);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (error?.code === "ENOENT") {
|
|
440
|
+
msg.info("未找到 ~/.codex/claude360.config.toml(profile 覆盖层),可执行「写入 / 修复 Claude360 Provider」生成。");
|
|
417
441
|
} else {
|
|
418
|
-
|
|
442
|
+
msg.error(`读取 Codex profile 配置失败:${safeErrorMessage(error)}`);
|
|
419
443
|
}
|
|
420
444
|
}
|
|
421
445
|
}
|
|
@@ -423,14 +447,14 @@ export async function runCli({
|
|
|
423
447
|
|
|
424
448
|
// Codex 协议兼容性检测(PRD 8.6)
|
|
425
449
|
async function checkCodexCompatStep() {
|
|
426
|
-
|
|
450
|
+
msg.step("正在检测 Claude360 是否支持 Codex 所需协议...");
|
|
427
451
|
const compat = await codexCompat(api);
|
|
428
452
|
if (compat.supported) {
|
|
429
|
-
|
|
453
|
+
msg.success("Claude360 已支持 Codex 所需协议");
|
|
430
454
|
return true;
|
|
431
455
|
}
|
|
432
|
-
|
|
433
|
-
|
|
456
|
+
msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议。");
|
|
457
|
+
msg.hint("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
|
|
434
458
|
const next = await promptSelect("请选择:", [
|
|
435
459
|
{ label: "返回", value: "back" },
|
|
436
460
|
{ label: "打开 Claude360 控制台", value: "console" },
|
|
@@ -443,7 +467,7 @@ export async function runCli({
|
|
|
443
467
|
|
|
444
468
|
async function testConnectionStep() {
|
|
445
469
|
const me = await fetchMe();
|
|
446
|
-
writeLine(me ? "
|
|
470
|
+
writeLine(me ? formatMessage("success", "Claude360 连接测试通过", { color: fancyOutput }) : formatMessage("warn", "Claude360 连接测试失败,可稍后在诊断菜单排查", { color: fancyOutput }));
|
|
447
471
|
}
|
|
448
472
|
|
|
449
473
|
// ──────────────────────────────────────────────
|
|
@@ -458,15 +482,15 @@ export async function runCli({
|
|
|
458
482
|
showBalance: showBalanceAndUsage,
|
|
459
483
|
ensureApiKey: async () => {
|
|
460
484
|
await ensureApiKey();
|
|
461
|
-
|
|
485
|
+
msg.result("当前 Key", config.tokenName || "-");
|
|
462
486
|
},
|
|
463
487
|
installTool: () => checkInstallOrUpdateTool("claude", "Claude Code", "claude"),
|
|
464
488
|
configureApi: async () => {
|
|
465
489
|
await ensureApiKey();
|
|
466
490
|
await markConfigured("claudeCode");
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
491
|
+
msg.success("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
|
|
492
|
+
msg.path(` ANTHROPIC_BASE_URL=${baseUrl}`);
|
|
493
|
+
msg.path(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
|
|
470
494
|
},
|
|
471
495
|
configureLanguage: () => configureLanguage({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
|
|
472
496
|
configureStyle: () => configureStyle({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
|
|
@@ -491,7 +515,7 @@ export async function runCli({
|
|
|
491
515
|
showBalance: showBalanceAndUsage,
|
|
492
516
|
ensureApiKey: async () => {
|
|
493
517
|
await ensureApiKey();
|
|
494
|
-
|
|
518
|
+
msg.result("当前 Key", config.tokenName || "-");
|
|
495
519
|
},
|
|
496
520
|
installTool: () => checkInstallOrUpdateTool("codex", "Codex", "codex"),
|
|
497
521
|
configureLanguage: () => configureLanguage({ baseDir: codexDir, filePath: agentsPath, promptSelect, writeLine }),
|
|
@@ -502,7 +526,7 @@ export async function runCli({
|
|
|
502
526
|
await ensureApiKey();
|
|
503
527
|
await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
504
528
|
await markConfigured("codex");
|
|
505
|
-
|
|
529
|
+
msg.success("已写入 Codex claude360 provider(~/.codex/config.toml)与 profile 覆盖层(~/.codex/claude360.config.toml)");
|
|
506
530
|
},
|
|
507
531
|
checkCompat: checkCodexCompatStep,
|
|
508
532
|
installMcps: () => installCodexMcpServers({ api, codexDir, multiSelect, confirm, writeLine }),
|
|
@@ -568,19 +592,19 @@ export async function runCli({
|
|
|
568
592
|
async function ensureToolInstalled(target, toolName, command) {
|
|
569
593
|
const check = await commandVersion(execCommand, command, ["--version"]);
|
|
570
594
|
if (check.ok) {
|
|
571
|
-
|
|
595
|
+
msg.success(`${toolName} 已安装(${check.version})`);
|
|
572
596
|
return true;
|
|
573
597
|
}
|
|
574
|
-
|
|
598
|
+
msg.info(`未检测到 ${toolName}。`);
|
|
575
599
|
const results = await installOrUpdateTools({ targets: [target], confirm });
|
|
576
600
|
const result = (results || []).find((item) => item.target === target);
|
|
577
601
|
if (result?.skipped) {
|
|
578
|
-
|
|
602
|
+
msg.info(`已跳过 ${toolName} 安装。`);
|
|
579
603
|
return false;
|
|
580
604
|
}
|
|
581
605
|
if (result?.ok === false) {
|
|
582
|
-
|
|
583
|
-
|
|
606
|
+
msg.error(`安装 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
607
|
+
msg.hint("国内网络失败时可尝试:npx --registry=https://registry.npmmirror.com claude360");
|
|
584
608
|
return false;
|
|
585
609
|
}
|
|
586
610
|
return true;
|
|
@@ -590,17 +614,17 @@ export async function runCli({
|
|
|
590
614
|
async function preflightLaunch(toolName) {
|
|
591
615
|
const me = await fetchMe();
|
|
592
616
|
if (!config.apiKey) {
|
|
593
|
-
|
|
617
|
+
msg.warn("当前没有可用 API Key,先完成 Key 配置。");
|
|
594
618
|
await ensureApiKey();
|
|
595
619
|
}
|
|
596
|
-
|
|
597
|
-
|
|
620
|
+
msg.heading(`${toolName} 启动前检查`);
|
|
621
|
+
msg.result("当前 Key", config.tokenName || "-");
|
|
598
622
|
if (me) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
623
|
+
msg.success("Claude360 服务可访问");
|
|
624
|
+
msg.result("余额", me.balance_display ?? me.quota);
|
|
625
|
+
msg.result("今日用量", `${me.today_usage_display || "-"}(已刷新)`);
|
|
602
626
|
} else {
|
|
603
|
-
|
|
627
|
+
msg.warn("Claude360 服务状态获取失败,仍可尝试启动");
|
|
604
628
|
}
|
|
605
629
|
if (me?.low_balance) {
|
|
606
630
|
const action = await promptSelect(
|
|
@@ -625,7 +649,7 @@ export async function runCli({
|
|
|
625
649
|
return false;
|
|
626
650
|
}
|
|
627
651
|
await markConfigured("claudeCode");
|
|
628
|
-
|
|
652
|
+
msg.step("正在启动 Claude Code...");
|
|
629
653
|
await launchClaudeCode({ config });
|
|
630
654
|
return true;
|
|
631
655
|
}
|
|
@@ -633,16 +657,16 @@ export async function runCli({
|
|
|
633
657
|
async function doLaunchCodex() {
|
|
634
658
|
const compat = await codexCompat(api);
|
|
635
659
|
if (!compat.supported) {
|
|
636
|
-
|
|
637
|
-
|
|
660
|
+
msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议。");
|
|
661
|
+
msg.hint("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
|
|
638
662
|
return false;
|
|
639
663
|
}
|
|
640
|
-
|
|
664
|
+
msg.success("Claude360 已支持 Codex 所需协议");
|
|
641
665
|
if (!(await preflightLaunch("Codex"))) {
|
|
642
666
|
return false;
|
|
643
667
|
}
|
|
644
668
|
await markConfigured("codex");
|
|
645
|
-
|
|
669
|
+
msg.step("正在启动 Codex...");
|
|
646
670
|
await launchCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
647
671
|
return true;
|
|
648
672
|
}
|
|
@@ -650,27 +674,27 @@ export async function runCli({
|
|
|
650
674
|
async function doConfigureCodex() {
|
|
651
675
|
const compat = await codexCompat(api);
|
|
652
676
|
if (!compat.supported) {
|
|
653
|
-
|
|
677
|
+
msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议,已停止写入配置。");
|
|
654
678
|
return false;
|
|
655
679
|
}
|
|
656
680
|
await ensureApiKey();
|
|
657
681
|
await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
658
682
|
await markConfigured("codex");
|
|
659
|
-
|
|
683
|
+
msg.success("已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
|
|
660
684
|
return true;
|
|
661
685
|
}
|
|
662
686
|
|
|
663
687
|
async function doWechatTopUp() {
|
|
664
688
|
try {
|
|
665
689
|
const result = await runWechatTopUp({ api, promptSelect, promptInput, writeLine, sleep });
|
|
666
|
-
|
|
690
|
+
msg.success("支付成功");
|
|
667
691
|
if (result?.balance) {
|
|
668
|
-
|
|
669
|
-
|
|
692
|
+
msg.result("当前余额", result.balance.balance_display ?? result.balance.quota);
|
|
693
|
+
msg.result("今日用量", result.balance.today_usage_display || "-");
|
|
670
694
|
}
|
|
671
695
|
return result;
|
|
672
696
|
} catch (error) {
|
|
673
|
-
|
|
697
|
+
msg.error(`充值未完成:${safeErrorMessage(error)}`);
|
|
674
698
|
return null;
|
|
675
699
|
}
|
|
676
700
|
}
|
|
@@ -691,30 +715,30 @@ export async function runCli({
|
|
|
691
715
|
const results = await installOrUpdateTools({ targets, confirm });
|
|
692
716
|
for (const result of results || []) {
|
|
693
717
|
if (result?.skipped) {
|
|
694
|
-
|
|
718
|
+
msg.info(`已跳过 ${result.target}。`);
|
|
695
719
|
continue;
|
|
696
720
|
}
|
|
697
721
|
if (result?.ok === false) {
|
|
698
|
-
|
|
722
|
+
msg.error(`安装或更新 ${result.target} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
699
723
|
continue;
|
|
700
724
|
}
|
|
701
|
-
|
|
725
|
+
msg.success(`${result.target} 安装或更新完成${result.usedMirror ? "(使用国内镜像)" : ""}`);
|
|
702
726
|
if (result.target === "claude360") {
|
|
703
|
-
|
|
727
|
+
msg.info("claude360 CLI 已更新,重新运行 npx claude360 后生效。");
|
|
704
728
|
}
|
|
705
729
|
}
|
|
706
730
|
}
|
|
707
731
|
|
|
708
732
|
async function openPage(path, label) {
|
|
709
733
|
const url = `${baseUrl}${path}`;
|
|
710
|
-
|
|
734
|
+
msg.step(`正在打开${label}:${url}`);
|
|
711
735
|
await openBrowser(url);
|
|
712
736
|
}
|
|
713
737
|
|
|
714
738
|
// 退出登录:仅清除本地凭证,不触碰网站侧账号与 Key(PRD 25.2 logout)
|
|
715
739
|
async function doLogout() {
|
|
716
740
|
if (!config.cliToken && !config.apiKey) {
|
|
717
|
-
|
|
741
|
+
msg.info("当前未登录。");
|
|
718
742
|
return true;
|
|
719
743
|
}
|
|
720
744
|
const approved = await confirm(
|
|
@@ -722,13 +746,13 @@ export async function runCli({
|
|
|
722
746
|
{ danger: true },
|
|
723
747
|
);
|
|
724
748
|
if (!approved) {
|
|
725
|
-
|
|
749
|
+
msg.info("已取消。");
|
|
726
750
|
return false;
|
|
727
751
|
}
|
|
728
752
|
config = { baseUrl };
|
|
729
753
|
await configStore.save(config);
|
|
730
754
|
api = createApiClient({ baseUrl, cliToken: "" });
|
|
731
|
-
|
|
755
|
+
msg.info("已退出登录。");
|
|
732
756
|
return true;
|
|
733
757
|
}
|
|
734
758
|
|
|
@@ -748,13 +772,13 @@ export async function runCli({
|
|
|
748
772
|
{ danger: true },
|
|
749
773
|
);
|
|
750
774
|
if (!approved) {
|
|
751
|
-
|
|
775
|
+
msg.info("已取消。");
|
|
752
776
|
return false;
|
|
753
777
|
}
|
|
754
778
|
config = { baseUrl };
|
|
755
779
|
await configStore.save(config);
|
|
756
780
|
api = createApiClient({ baseUrl, cliToken: "" });
|
|
757
|
-
|
|
781
|
+
msg.info("本地配置已清除。");
|
|
758
782
|
return ensureLogin();
|
|
759
783
|
}
|
|
760
784
|
|
|
@@ -767,7 +791,7 @@ export async function runCli({
|
|
|
767
791
|
writeLine(renderHeader("余额与充值", { color: fancyOutput }));
|
|
768
792
|
const me = await showBalanceAndUsage();
|
|
769
793
|
if (me?.low_balance) {
|
|
770
|
-
|
|
794
|
+
msg.warn("余额偏低,建议充值");
|
|
771
795
|
}
|
|
772
796
|
const action = await promptSelect("请选择:", [
|
|
773
797
|
{ label: "微信扫码充值", value: "wechat" },
|
|
@@ -796,7 +820,7 @@ export async function runCli({
|
|
|
796
820
|
tokenName: token.tokenName,
|
|
797
821
|
group: token.group,
|
|
798
822
|
});
|
|
799
|
-
|
|
823
|
+
msg.success(`当前 Key 已切换为 ${token.tokenName}`);
|
|
800
824
|
}
|
|
801
825
|
return token;
|
|
802
826
|
}
|
|
@@ -967,14 +991,14 @@ export async function runCli({
|
|
|
967
991
|
switch (action) {
|
|
968
992
|
case "diagnose": {
|
|
969
993
|
const report = await runDiagnostics({ config, api, execCommand });
|
|
970
|
-
writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
|
|
994
|
+
writeLine(formatDiagnosticsSummary(report, { width: outputWidth(), color: fancyOutput }));
|
|
971
995
|
break;
|
|
972
996
|
}
|
|
973
997
|
case "fix_claude":
|
|
974
998
|
await ensureApiKey();
|
|
975
999
|
await markConfigured("claudeCode");
|
|
976
1000
|
await showBalanceAndUsage();
|
|
977
|
-
|
|
1001
|
+
msg.success("Claude Code 配置修复完成(启动时注入环境变量)。");
|
|
978
1002
|
break;
|
|
979
1003
|
case "fix_codex":
|
|
980
1004
|
await doConfigureCodex();
|
|
@@ -987,7 +1011,7 @@ export async function runCli({
|
|
|
987
1011
|
tokenName: token.tokenName,
|
|
988
1012
|
group: token.group,
|
|
989
1013
|
});
|
|
990
|
-
|
|
1014
|
+
msg.success(`当前 Key 已切换为 ${token.tokenName}`);
|
|
991
1015
|
break;
|
|
992
1016
|
}
|
|
993
1017
|
case "reset":
|
|
@@ -1021,7 +1045,7 @@ export async function runCli({
|
|
|
1021
1045
|
return false;
|
|
1022
1046
|
}
|
|
1023
1047
|
await markConfigured("claudeCode");
|
|
1024
|
-
|
|
1048
|
+
msg.success("Claude Code 将在启动时注入 Claude360 配置。");
|
|
1025
1049
|
}
|
|
1026
1050
|
if (targets.includes("codex")) {
|
|
1027
1051
|
if (!(await ensureToolInstalled("codex", "Codex", "codex"))) {
|
|
@@ -1057,7 +1081,7 @@ export async function runCli({
|
|
|
1057
1081
|
|
|
1058
1082
|
// 测试连接
|
|
1059
1083
|
const me = await fetchMe();
|
|
1060
|
-
writeLine(me ? "
|
|
1084
|
+
writeLine(me ? formatMessage("success", "Claude360 连接测试通过", { color: fancyOutput }) : formatMessage("warn", "Claude360 连接测试失败,可稍后在诊断菜单排查", { color: fancyOutput }));
|
|
1061
1085
|
|
|
1062
1086
|
// 启动
|
|
1063
1087
|
if (targets.length === 1) {
|
|
@@ -1087,7 +1111,7 @@ export async function runCli({
|
|
|
1087
1111
|
try {
|
|
1088
1112
|
await checkEnvironment({ writeLine, execCommand });
|
|
1089
1113
|
} catch (error) {
|
|
1090
|
-
|
|
1114
|
+
msg.error(safeErrorMessage(error));
|
|
1091
1115
|
return "environment_failed";
|
|
1092
1116
|
}
|
|
1093
1117
|
}
|
|
@@ -1165,7 +1189,7 @@ export async function runCli({
|
|
|
1165
1189
|
tokenName: token.tokenName,
|
|
1166
1190
|
group: token.group,
|
|
1167
1191
|
});
|
|
1168
|
-
|
|
1192
|
+
msg.success(`当前 Key 已切换为 ${token.tokenName}`);
|
|
1169
1193
|
break;
|
|
1170
1194
|
}
|
|
1171
1195
|
case "claude_model":
|
|
@@ -1186,7 +1210,7 @@ export async function runCli({
|
|
|
1186
1210
|
writeLine(renderHeader("主菜单", { color: fancyOutput }));
|
|
1187
1211
|
}
|
|
1188
1212
|
const status = await loadAccountStatus({ api, config });
|
|
1189
|
-
writeLine(formatAccountStatus({ ...status, width: outputWidth() }));
|
|
1213
|
+
writeLine(formatAccountStatus({ ...status, width: outputWidth(), color: fancyOutput }));
|
|
1190
1214
|
writeLine("");
|
|
1191
1215
|
const action = await promptMenu({
|
|
1192
1216
|
menu: buildDailyMenu(),
|
|
@@ -1280,7 +1304,7 @@ export async function runCli({
|
|
|
1280
1304
|
return "cc_switch";
|
|
1281
1305
|
case "doctor": {
|
|
1282
1306
|
const report = await runDiagnostics({ config, api, execCommand });
|
|
1283
|
-
writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
|
|
1307
|
+
writeLine(formatDiagnosticsSummary(report, { width: outputWidth(), color: fancyOutput }));
|
|
1284
1308
|
return "doctor";
|
|
1285
1309
|
}
|
|
1286
1310
|
case "login":
|