claude360 0.2.8 → 0.3.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 +107 -126
- package/bin/claude360.js +7 -3
- package/package.json +1 -1
- package/src/account-status.js +8 -5
- package/src/auth.js +9 -2
- package/src/banner.js +47 -6
- package/src/cc-switch.js +34 -6
- package/src/colors.js +31 -0
- package/src/diagnostics.js +68 -25
- package/src/glyphs.js +33 -0
- package/src/index.js +248 -105
- package/src/init-config.js +61 -27
- package/src/init-flow.js +25 -6
- package/src/mcp-skill.js +48 -21
- package/src/menu.js +20 -13
- package/src/messages.js +78 -0
- package/src/notices.js +33 -0
- package/src/onboarding.js +12 -5
- package/src/prompts.js +7 -6
- package/src/token-manager.js +90 -20
- package/src/tool-installer.js +25 -9
- package/src/tool-launcher.js +150 -46
- package/src/topup.js +55 -5
- package/src/ui.js +264 -8
- package/src/workflows.js +10 -7
- package/src/zcf-notice.js +15 -1
package/src/cc-switch.js
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { colorLevel } from "./colors.js";
|
|
8
|
+
import { createMessenger } from "./messages.js";
|
|
9
|
+
import { summary, warningBlock } from "./ui.js";
|
|
10
|
+
|
|
11
|
+
// 语义消息器工厂:真实终端着色,测试/管道(writeLine 被替换)下无色。
|
|
12
|
+
const mk = (writeLine) => createMessenger({ writeLine, color: writeLine === console.log ? colorLevel() : 0 });
|
|
7
13
|
|
|
8
14
|
function normalizeBaseUrl(baseUrl = "https://claude360.xyz") {
|
|
9
15
|
return String(baseUrl).replace(/\/+$/, "");
|
|
@@ -89,6 +95,22 @@ export function maskCcSwitchConfig(config, apiKey) {
|
|
|
89
95
|
return JSON.parse(json.split(JSON.stringify(apiKey)).join(JSON.stringify(masked)));
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
function formatCcSwitchSummary(target, config, apiKey) {
|
|
99
|
+
const maskedKey = maskKeyForPreview(apiKey);
|
|
100
|
+
const rows = [
|
|
101
|
+
["目标", target],
|
|
102
|
+
["名称", config.name || "Claude360"],
|
|
103
|
+
];
|
|
104
|
+
if (Array.isArray(config.items)) {
|
|
105
|
+
rows.push(["配置项", config.items.map((item) => item.type || item.name).join(", ")]);
|
|
106
|
+
} else {
|
|
107
|
+
rows.push(["类型", config.type || "-"]);
|
|
108
|
+
rows.push(["Base URL", config.base_url || "-"]);
|
|
109
|
+
}
|
|
110
|
+
rows.push(["API Key", maskedKey || "-"]);
|
|
111
|
+
return ["cc-switch 配置摘要", "", summary(rows)].join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
export function resolveCcSwitchSavePath({
|
|
93
115
|
platform = process.platform,
|
|
94
116
|
env = process.env,
|
|
@@ -134,9 +156,10 @@ export async function runCcSwitchGenerator({
|
|
|
134
156
|
throw new Error("缺少菜单选择输入");
|
|
135
157
|
}
|
|
136
158
|
|
|
159
|
+
const msg = mk(writeLine);
|
|
137
160
|
let apiKey = config.apiKey;
|
|
138
161
|
if (!apiKey) {
|
|
139
|
-
|
|
162
|
+
msg.warn("当前没有可用 API Key,需要先创建 Claude360 API Key。");
|
|
140
163
|
if (typeof ensureApiKey !== "function") {
|
|
141
164
|
return { done: false, reason: "no_key" };
|
|
142
165
|
}
|
|
@@ -150,7 +173,7 @@ export async function runCcSwitchGenerator({
|
|
|
150
173
|
const token = await ensureApiKey();
|
|
151
174
|
apiKey = token?.apiKey;
|
|
152
175
|
if (!apiKey) {
|
|
153
|
-
|
|
176
|
+
msg.info("未获取到可用 API Key,已取消生成。");
|
|
154
177
|
return { done: false, reason: "no_key" };
|
|
155
178
|
}
|
|
156
179
|
}
|
|
@@ -184,13 +207,18 @@ export async function runCcSwitchGenerator({
|
|
|
184
207
|
|
|
185
208
|
if (reveal === "masked") {
|
|
186
209
|
const maskedConfig = maskCcSwitchConfig(fullConfig, apiKey);
|
|
187
|
-
writeLine(
|
|
188
|
-
|
|
210
|
+
writeLine(formatCcSwitchSummary(target, maskedConfig, apiKey));
|
|
211
|
+
msg.hint("提示:脱敏配置仅用于预览,无法直接使用,也不会保存到文件。");
|
|
189
212
|
return { done: true, target, masked: true, saved: false };
|
|
190
213
|
}
|
|
191
214
|
|
|
215
|
+
writeLine(warningBlock("即将显示完整 API Key", {
|
|
216
|
+
action: "在终端输出完整 cc-switch 配置",
|
|
217
|
+
impact: "当前终端可见范围内的人可能看到 API Key",
|
|
218
|
+
}));
|
|
219
|
+
writeLine("");
|
|
192
220
|
writeLine(JSON.stringify(fullConfig, null, 2));
|
|
193
|
-
|
|
221
|
+
msg.info("请将以上配置复制到 cc-switch 中使用。");
|
|
194
222
|
|
|
195
223
|
const saveChoice = await promptSelect("是否保存到本地文件?", [
|
|
196
224
|
{ label: "保存到 ~/.claude360/cc-switch-claude360.json", value: "save" },
|
|
@@ -201,6 +229,6 @@ export async function runCcSwitchGenerator({
|
|
|
201
229
|
}
|
|
202
230
|
|
|
203
231
|
const savedPath = await save(fullConfig);
|
|
204
|
-
|
|
232
|
+
msg.success(`已保存到:${savedPath}(文件权限 0600)`);
|
|
205
233
|
return { done: true, target, masked: false, saved: true, savedPath };
|
|
206
234
|
}
|
package/src/colors.js
CHANGED
|
@@ -91,3 +91,34 @@ export function fg(r, g, b, level = 2) {
|
|
|
91
91
|
}
|
|
92
92
|
return "";
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
// ──────────────────────────────────────────────
|
|
96
|
+
// 语义调色板(单一事实源)
|
|
97
|
+
// ──────────────────────────────────────────────
|
|
98
|
+
// 全 CLI 的角色配色统一在此定义,banner / menu / prompts / ui / messages
|
|
99
|
+
// 都从这里取色,杜绝各文件重复硬编码同一组 RGB(色值沿用 Tailwind 体系)。
|
|
100
|
+
// 设计目标:状态色(绿/红/黄)醒目、信息色(天蓝)中性、进行色(紫)区别于
|
|
101
|
+
// 信息、标题白做强调、两级灰(path/hint)用于回显与次要说明 —— 形成层级。
|
|
102
|
+
export const PALETTE = {
|
|
103
|
+
title: [240, 246, 255], // 强调白:页面/品牌标题、回显值
|
|
104
|
+
heading: [34, 211, 238], // 亮青:章节标题、品牌高亮
|
|
105
|
+
info: [125, 211, 252], // 天蓝:中性信息、表头、选择键
|
|
106
|
+
success: [74, 222, 128], // 绿:操作成功
|
|
107
|
+
warn: [250, 204, 21], // 黄:警告 / 降级
|
|
108
|
+
error: [248, 113, 113], // 红:失败 / 危险操作
|
|
109
|
+
step: [167, 139, 250], // 紫:进行中 / 步骤(与 info 拉开区分)
|
|
110
|
+
path: [148, 163, 184], // 灰蓝:路径 / 命令 / URL 回显
|
|
111
|
+
hint: [100, 116, 139], // 暗灰:次要说明 / 帮助 / 未选中项
|
|
112
|
+
border: [71, 85, 105], // 青灰:边框 / 分隔线
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// 按色彩深度 level 现算各语义角色的前景 SGR(真彩色保留 RGB,256 色量化)。
|
|
116
|
+
// level=0 时全部为空串,调用方据此走无色路径。
|
|
117
|
+
export function theme(level) {
|
|
118
|
+
const t = {};
|
|
119
|
+
for (const name of Object.keys(PALETTE)) {
|
|
120
|
+
const [r, g, b] = PALETTE[name];
|
|
121
|
+
t[name] = fg(r, g, b, level);
|
|
122
|
+
}
|
|
123
|
+
return t;
|
|
124
|
+
}
|
package/src/diagnostics.js
CHANGED
|
@@ -3,9 +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 {
|
|
8
|
+
import { renderChoiceTable, renderDivider, renderSectionTitle, renderTaskEnd, renderTaskStart } from "./ui.js";
|
|
9
9
|
|
|
10
10
|
export async function runDiagnostics({
|
|
11
11
|
config = {},
|
|
@@ -14,6 +14,7 @@ export async function runDiagnostics({
|
|
|
14
14
|
checkPathWritable = defaultCheckPathWritable,
|
|
15
15
|
readFile = fsReadFile,
|
|
16
16
|
codexConfigPath = resolveCodexConfigPath(),
|
|
17
|
+
codexProfileConfigPath = resolveCodexProfileConfigPath(),
|
|
17
18
|
api,
|
|
18
19
|
} = {}) {
|
|
19
20
|
const node = await commandVersion(execCommand, "node", ["--version"]);
|
|
@@ -65,7 +66,7 @@ export async function runDiagnostics({
|
|
|
65
66
|
token: buildTokenStatus({ config, tokens }),
|
|
66
67
|
topUp: buildTopUpStatus({ topUpOptions }),
|
|
67
68
|
claudeCodeConfig: buildClaudeCodeConfigStatus({ config, claudeCode }),
|
|
68
|
-
codexConfig: await buildCodexConfigStatus({ readFile, codexConfigPath, codex }),
|
|
69
|
+
codexConfig: await buildCodexConfigStatus({ readFile, codexConfigPath, codexProfileConfigPath, codex }),
|
|
69
70
|
codexCompat: buildCodexCompatStatus({ codexCompat }),
|
|
70
71
|
};
|
|
71
72
|
}
|
|
@@ -108,22 +109,39 @@ function buildClaudeCodeConfigStatus({ config, claudeCode }) {
|
|
|
108
109
|
return { ok: true, detail: "启动时注入 ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN" };
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
async function buildCodexConfigStatus({ readFile, codexConfigPath, codex }) {
|
|
112
|
+
async function buildCodexConfigStatus({ readFile, codexConfigPath, codexProfileConfigPath, codex }) {
|
|
112
113
|
if (!codex.ok) {
|
|
113
114
|
return { ok: false, detail: "Codex 未安装" };
|
|
114
115
|
}
|
|
116
|
+
let baseContent;
|
|
115
117
|
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" };
|
|
118
|
+
baseContent = await readFile(codexConfigPath, "utf8");
|
|
121
119
|
} catch (error) {
|
|
122
120
|
if (error?.code === "ENOENT") {
|
|
123
121
|
return { ok: false, detail: "未找到 ~/.codex/config.toml" };
|
|
124
122
|
}
|
|
125
123
|
return { ok: false, detail: sanitizeError(error) || "读取 config.toml 失败" };
|
|
126
124
|
}
|
|
125
|
+
if (!baseContent.includes("[model_providers.claude360]")) {
|
|
126
|
+
return { ok: false, detail: "未写入 claude360 provider" };
|
|
127
|
+
}
|
|
128
|
+
// legacy [profiles.claude360] 与 --profile 共存会导致新版 Codex 启动失败,需重新写入迁移
|
|
129
|
+
if (baseContent.includes("[profiles.claude360]")) {
|
|
130
|
+
return { ok: false, detail: "检测到 legacy [profiles.claude360],请在「Codex 配置」中重新写入以迁移" };
|
|
131
|
+
}
|
|
132
|
+
let profileContent;
|
|
133
|
+
try {
|
|
134
|
+
profileContent = await readFile(codexProfileConfigPath, "utf8");
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error?.code === "ENOENT") {
|
|
137
|
+
return { ok: false, detail: "缺少 ~/.codex/claude360.config.toml(profile 覆盖层)" };
|
|
138
|
+
}
|
|
139
|
+
return { ok: false, detail: sanitizeError(error) || "读取 claude360.config.toml 失败" };
|
|
140
|
+
}
|
|
141
|
+
if (!/model_provider\s*=\s*"claude360"/.test(profileContent)) {
|
|
142
|
+
return { ok: false, detail: "claude360.config.toml 缺少 model_provider" };
|
|
143
|
+
}
|
|
144
|
+
return { ok: true, detail: "profile claude360 正常" };
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
function buildCodexCompatStatus({ codexCompat }) {
|
|
@@ -139,7 +157,7 @@ function buildCodexCompatStatus({ codexCompat }) {
|
|
|
139
157
|
// 诊断报告(补充需求第 5 节):按模块分组的小表格展示,
|
|
140
158
|
// 状态用文字标签(通过/警告/失败)明确区分,失败项附修复建议,
|
|
141
159
|
// 输出适合直接复制给客服或开发排查(出口统一脱敏)。
|
|
142
|
-
export function formatDiagnosticsSummary(report, { width = 0 } = {}) {
|
|
160
|
+
export function formatDiagnosticsSummary(report, { width = 0, color = false, verbose = false } = {}) {
|
|
143
161
|
const groups = [
|
|
144
162
|
{
|
|
145
163
|
title: "系统环境",
|
|
@@ -179,31 +197,56 @@ export function formatDiagnosticsSummary(report, { width = 0 } = {}) {
|
|
|
179
197
|
},
|
|
180
198
|
];
|
|
181
199
|
|
|
182
|
-
const lines = [
|
|
200
|
+
const lines = [renderTaskStart("运行诊断", {
|
|
201
|
+
intro: ["检查系统环境", "检查 Claude360 账号与余额", "检查 Claude Code / Codex 配置", "汇总问题与建议"],
|
|
202
|
+
})];
|
|
203
|
+
let rowNumber = 1;
|
|
204
|
+
const allFixes = [];
|
|
183
205
|
for (const group of groups) {
|
|
184
|
-
lines.push("", renderSectionTitle(group.title));
|
|
185
|
-
lines.push(
|
|
186
|
-
|
|
187
|
-
rows: group.items.map(({ label, item, valueKey = "detail" }) =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
lines.push("", renderSectionTitle(group.title, { color }));
|
|
207
|
+
lines.push(renderChoiceTable({
|
|
208
|
+
columns: [["no", "序号"], ["check", "检查项"], ["status", "状态"], ["result", "结果"]],
|
|
209
|
+
rows: group.items.map(({ label, item, valueKey = "detail" }) => {
|
|
210
|
+
const row = {
|
|
211
|
+
no: `[${String(rowNumber).padStart(2, "0")}]`,
|
|
212
|
+
check: label,
|
|
213
|
+
status: statusLabel(item),
|
|
214
|
+
result: String(item[valueKey] || item.detail || "-"),
|
|
215
|
+
};
|
|
216
|
+
rowNumber += 1;
|
|
217
|
+
return row;
|
|
218
|
+
}),
|
|
219
|
+
}, { width, color, titleKey: "check", cardBreakpoint: 64 }));
|
|
220
|
+
allFixes.push(...group.items
|
|
221
|
+
.filter(({ item, fix }) => !item.ok && fix)
|
|
222
|
+
.map(({ label, item, fix }) => ({ label, item, fix })));
|
|
223
|
+
}
|
|
224
|
+
lines.push("", `问题数量:${allFixes.length}`);
|
|
225
|
+
if (allFixes.length > 0) {
|
|
226
|
+
lines.push("", "建议:");
|
|
227
|
+
allFixes.forEach(({ label, item, fix }, index) => {
|
|
228
|
+
lines.push(`${index + 1}. ${statusLabel(item)} ${label}:${fix}`);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// 详细模式(claude360 doctor --verbose):附关键环境变量,最终 sanitizeText 兜底脱敏
|
|
232
|
+
if (verbose) {
|
|
233
|
+
lines.push("", renderDivider("light"), "环境变量(关键项):");
|
|
234
|
+
for (const key of ["PATH", "NODE_OPTIONS", "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "npm_config_registry"]) {
|
|
235
|
+
lines.push(`[${key}] ${process.env[key] || "-"}`);
|
|
196
236
|
}
|
|
197
237
|
}
|
|
238
|
+
lines.push("", renderTaskEnd("诊断完成", {
|
|
239
|
+
summary: [["问题数量", String(allFixes.length)]],
|
|
240
|
+
}));
|
|
198
241
|
// 出口兜底:report 由调用方注入,逐项 detail 可能未经脱敏
|
|
199
242
|
return sanitizeText(lines.join("\n"));
|
|
200
243
|
}
|
|
201
244
|
|
|
202
245
|
function statusLabel(item) {
|
|
203
246
|
if (item.ok) {
|
|
204
|
-
return "
|
|
247
|
+
return "✅ 正常";
|
|
205
248
|
}
|
|
206
|
-
return item.warn ? "
|
|
249
|
+
return item.warn ? "⚠️ 注意" : "❌ 异常";
|
|
207
250
|
}
|
|
208
251
|
|
|
209
252
|
function defaultPlatform() {
|
package/src/glyphs.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// emoji 语义符号单一事实源(优化需求 V2.0 §4):执行流程中的任务 / 步骤 /
|
|
2
|
+
// 状态 / 资源类别统一从这里取符号,避免散落硬编码。emoji 作为语义符号「始终
|
|
3
|
+
// 输出」(不依赖颜色表达唯一信息),无色档与管道同样保留,保证信息层级可辨。
|
|
4
|
+
// 注意:部分符号带变体选择符 U+FE0F(⚠️ ℹ️),在多数现代终端按 2 列宽渲染,
|
|
5
|
+
// 宽度计算见 banner.displayWidth;新增符号若落在新的 Unicode 区间,需同步其
|
|
6
|
+
// emoji 宽度判定,避免表格 / 边框错位。
|
|
7
|
+
|
|
8
|
+
export const GLYPH = {
|
|
9
|
+
task: "🚀", // 任务开始
|
|
10
|
+
check: "🔎", // 检查环境 / 版本 / 配置
|
|
11
|
+
install: "📦", // 安装 / 更新工具
|
|
12
|
+
config: "⚙️", // 写入配置 / 修改设置
|
|
13
|
+
auth: "🔐", // 登录授权 / Token 检查
|
|
14
|
+
net: "🌐", // API 连通性 / 远程请求
|
|
15
|
+
pay: "💳", // 充值 / 订单 / 支付
|
|
16
|
+
wait: "⏳", // 轮询 / 等待用户操作
|
|
17
|
+
step: "▶", // 当前正在执行的步骤
|
|
18
|
+
success: "✅", // 成功完成
|
|
19
|
+
warn: "⚠️", // 可继续但需注意
|
|
20
|
+
error: "❌", // 失败 / 不可继续
|
|
21
|
+
info: "ℹ️", // 普通提示
|
|
22
|
+
file: "📄", // 配置文件 / 备份文件
|
|
23
|
+
dir: "📁", // 目录路径
|
|
24
|
+
done: "🎉", // 整个任务完成
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// 待执行 / 已完成步骤项的清单符号(§二 推荐结构:✔ 已完成 / ▶ 正在进行 / ○ 待执行)。
|
|
28
|
+
// 与 GLYPH.step 区分:这些用于「步骤总览清单」,GLYPH.step 用于当前步骤标题行。
|
|
29
|
+
export const STEP_MARK = {
|
|
30
|
+
done: "✔", // 已完成
|
|
31
|
+
active: "▶", // 正在进行
|
|
32
|
+
pending: "○", // 待执行
|
|
33
|
+
};
|