claude360 0.2.2 → 0.2.4
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 +16 -1
- package/package.json +1 -1
- package/src/account-status.js +60 -43
- package/src/backup.js +54 -0
- package/src/diagnostics.js +63 -35
- package/src/index.js +444 -45
- package/src/init-config.js +383 -0
- package/src/init-flow.js +70 -0
- package/src/mcp-skill.js +257 -41
- package/src/menu.js +67 -5
- package/src/token-manager.js +49 -1
- package/src/tool-launcher.js +15 -8
- package/src/ui.js +137 -0
- package/src/workflows.js +331 -0
- package/src/zcf-notice.js +40 -0
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- [项目结构](#项目结构)
|
|
22
22
|
- [安全模型](#安全模型)
|
|
23
23
|
- [已知风险与路线图](#已知风险与路线图)
|
|
24
|
+
- [致谢](#致谢)
|
|
24
25
|
- [许可证](#许可证)
|
|
25
26
|
|
|
26
27
|
## 功能特性
|
|
@@ -35,7 +36,12 @@
|
|
|
35
36
|
| 启动 Claude Code | 自动注入 `ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN`,以子进程启动 |
|
|
36
37
|
| 启动 Codex | 写入隔离的 `[model_providers.claude360]` / `[profiles.claude360]` 配置,并以 `codex --profile claude360` 启动 |
|
|
37
38
|
| 工具更新 | 菜单内更新 `claude360` 自身、Claude Code、Codex,全部需要二次确认 |
|
|
38
|
-
|
|
|
39
|
+
| 一键完整初始化 | Claude Code / Codex 完整初始化向导:登录、Key、安装/更新、API 注入、输出语言、提示词风格、工作流、MCP、模型、权限配置、连接测试、启动 |
|
|
40
|
+
| 推荐工作流 / Skill | 六步工作流、Git 指令、功能规划 UX、BMad 安装器等,写入 `~/.claude/commands/claude360/` 与 `~/.codex/prompts/`,写前备份、冲突二次确认 |
|
|
41
|
+
| 推荐 MCP | Context7 / Open Web Search / Spec 工作流 / DeepWiki / Playwright / Exa / Serena,多选安装、重依赖单独确认、同名去重 |
|
|
42
|
+
| AI 输出语言 / 提示词风格 | 写入 `~/.claude/CLAUDE.md` 或 `~/.codex/AGENTS.md` 的 Claude360 标记区块,不覆盖用户内容 |
|
|
43
|
+
| 配置备份 | 所有写入用户工具配置的操作先备份到 `~/.claude/backup/` 或 `~/.codex/backup/` 时间戳目录 |
|
|
44
|
+
| 诊断报告 | OS/Node/npm/全局 npm 权限/工具版本/连通性/授权状态/余额/Key/支付端点一站式检查,按模块分组表格展示 |
|
|
39
45
|
|
|
40
46
|
## 系统要求
|
|
41
47
|
|
|
@@ -353,6 +359,15 @@ web/src/pages/CliAuthorize/ # 浏览器授权页
|
|
|
353
359
|
|
|
354
360
|
完整需求边界见 `prd/claude360-cli-prd.md`。
|
|
355
361
|
|
|
362
|
+
## 致谢
|
|
363
|
+
|
|
364
|
+
本 CLI 的交互式初始化流程、工作流导入、MCP 推荐配置等设计,借鉴并参考了开源项目
|
|
365
|
+
[NPX ZCF](https://github.com/UfoMiao/zcf) 的部分功能体验,感谢作者 UfoMiao 的开源贡献。
|
|
366
|
+
|
|
367
|
+
- 相关功能入口(完整初始化、推荐工作流 / MCP / Skill / AGENTS)会在终端展示开源参考声明。
|
|
368
|
+
- 随 CLI 安装的推荐工作流文件均为 Claude360 自研改编内容,文件头保留对 NPX ZCF 的 attribution 注释。
|
|
369
|
+
- 如后续直接引入 ZCF 的源码、配置文件或文档片段,需先核查其开源许可证并在此处补充对应声明。
|
|
370
|
+
|
|
356
371
|
## 许可证
|
|
357
372
|
|
|
358
373
|
随仓库根目录 `LICENSE` 发布,未单独声明。
|
package/package.json
CHANGED
package/src/account-status.js
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// 账户状态区(补充需求第 1 节):日常菜单顶部用横向表格展示
|
|
2
|
+
// 账号 / 余额 / 已用额度 / 当前 Key / 分组 / 倍率 / 状态,
|
|
3
|
+
// 今日用量与工具配置状态以紧凑状态行展示。业务口径(余额、已用额度、
|
|
4
|
+
// 低余额阈值、今日用量格式、分组倍率)全部以后端返回为准,CLI 仅做兜底格式化;
|
|
5
|
+
// 终端过窄时自动降级为双列紧凑表。
|
|
4
6
|
|
|
7
|
+
import { loadGroups } from "./group-manager.js";
|
|
5
8
|
import { sanitizeError } from "./sanitize.js";
|
|
9
|
+
import { renderStatusTable } from "./ui.js";
|
|
6
10
|
|
|
7
11
|
export async function loadAccountStatus({ api, config = {} } = {}) {
|
|
8
12
|
let me = null;
|
|
9
13
|
let error = null;
|
|
14
|
+
let groups = [];
|
|
10
15
|
if (api && config.cliToken) {
|
|
11
16
|
try {
|
|
12
17
|
me = await api.get("/api/cli/me");
|
|
13
18
|
} catch (err) {
|
|
14
19
|
error = err;
|
|
15
20
|
}
|
|
21
|
+
try {
|
|
22
|
+
groups = await loadGroups(api);
|
|
23
|
+
} catch {
|
|
24
|
+
groups = []; // 倍率获取失败不影响状态区其余信息
|
|
25
|
+
}
|
|
16
26
|
}
|
|
17
|
-
return { me, error, config };
|
|
27
|
+
return { me, error, config, groups };
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
// 兜底格式化:今日 token 用量,million 口径,如 128.6k/m、1.25m/m
|
|
@@ -50,48 +60,55 @@ export function maskAccount(account) {
|
|
|
50
60
|
return `${text.slice(0, 2)}***${text.slice(-2)}`;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
// 分组倍率:以后端 /api/cli/groups 返回为准;auto 等 ratio 为 null 的分组不展示倍率
|
|
64
|
+
export function formatGroupRatio(groups, groupName) {
|
|
65
|
+
if (!groupName) {
|
|
66
|
+
return "-";
|
|
67
|
+
}
|
|
68
|
+
const group = (groups || []).find((item) => item?.name === groupName);
|
|
69
|
+
const ratio = group?.ratio == null ? NaN : Number(group.ratio);
|
|
70
|
+
if (!Number.isFinite(ratio)) {
|
|
71
|
+
return "-";
|
|
72
|
+
}
|
|
73
|
+
return `${Number.isInteger(ratio) ? ratio.toFixed(1) : String(ratio)}x`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const STATUS_HEAD = ["账号", "余额", "已用额度", "当前 Key", "分组", "倍率", "状态"];
|
|
77
|
+
|
|
78
|
+
export function formatAccountStatus({ me, error, config = {}, groups = [], width = 0 } = {}) {
|
|
55
79
|
const configured = config.configuredTools || {};
|
|
56
|
-
const
|
|
57
|
-
const
|
|
80
|
+
const toolLine = `Claude Code:${configured.claudeCode ? "已配置" : "未配置"} Codex:${configured.codex ? "已配置" : "未配置"}`;
|
|
81
|
+
const tokenName = config.tokenName || "-";
|
|
82
|
+
const group = config.group || me?.group || "-";
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"账号:未登录",
|
|
62
|
-
"余额:-",
|
|
63
|
-
"今日用量:-",
|
|
64
|
-
"当前 Key:-",
|
|
65
|
-
`Claude Code:${claudeCodeState}`,
|
|
66
|
-
`Codex:${codexState}`,
|
|
67
|
-
);
|
|
68
|
-
return lines.join("\n");
|
|
69
|
-
}
|
|
84
|
+
let row;
|
|
85
|
+
let extraLines = [toolLine];
|
|
70
86
|
|
|
71
|
-
if (!
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
|
|
87
|
+
if (!config.cliToken) {
|
|
88
|
+
row = ["未登录", "-", "-", tokenName, "-", "-", "未登录"];
|
|
89
|
+
} else if (!me) {
|
|
90
|
+
const reason = sanitizeError(error);
|
|
91
|
+
row = [maskAccount(config.account), "-", "-", tokenName, group, formatGroupRatio(groups, group), "获取失败"];
|
|
92
|
+
extraLines = [`! 状态获取失败${reason ? `:${reason}` : ""}`, toolLine];
|
|
93
|
+
} else {
|
|
94
|
+
const account = me.email || me.display_name || me.username;
|
|
95
|
+
const balance = me.balance_display ?? String(me.quota ?? "-");
|
|
96
|
+
const usage = me.today_usage_display || formatTokensPerMillion(me.today_tokens);
|
|
97
|
+
row = [
|
|
98
|
+
maskAccount(account),
|
|
99
|
+
balance,
|
|
100
|
+
me.used_display ?? "-",
|
|
101
|
+
tokenName,
|
|
102
|
+
group,
|
|
103
|
+
formatGroupRatio(groups, group),
|
|
104
|
+
me.low_balance ? "余额较低" : "正常",
|
|
105
|
+
];
|
|
106
|
+
extraLines = [`今日用量:${usage}${me.low_balance ? " ! 余额较低,建议充值" : ""}`, toolLine];
|
|
81
107
|
}
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
lines.push(
|
|
89
|
-
`账号:${maskAccount(account)}`,
|
|
90
|
-
`余额:${balance}${lowBalanceTag}`,
|
|
91
|
-
`今日用量:${usage}`,
|
|
92
|
-
`当前 Key:${config.tokenName || "-"}`,
|
|
93
|
-
`Claude Code:${claudeCodeState}`,
|
|
94
|
-
`Codex:${codexState}`,
|
|
95
|
-
);
|
|
96
|
-
return lines.join("\n");
|
|
109
|
+
return [
|
|
110
|
+
"Claude360 账户状态",
|
|
111
|
+
renderStatusTable({ head: STATUS_HEAD, row }, { width }),
|
|
112
|
+
...extraLines,
|
|
113
|
+
].join("\n");
|
|
97
114
|
}
|
package/src/backup.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// 配置备份(PRD 第 11 章):所有写入用户工具配置的操作先备份到
|
|
2
|
+
// <工具目录>/backup/backup_YYYY-MM-DD_HH-mm-ss/,支持文件与目录(递归)。
|
|
3
|
+
// 不存在的路径自动跳过,全部不存在时不创建备份目录。
|
|
4
|
+
|
|
5
|
+
import { cp, mkdir, stat } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
export function formatBackupStamp(date = new Date()) {
|
|
9
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
10
|
+
return [
|
|
11
|
+
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`,
|
|
12
|
+
`${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`,
|
|
13
|
+
].join("_");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveBackupDir(baseDir, date = new Date()) {
|
|
17
|
+
return path.join(baseDir, "backup", `backup_${formatBackupStamp(date)}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 备份 paths(绝对路径,文件或目录)到 baseDir/backup/backup_<时间戳>/,
|
|
21
|
+
// 返回 { backupDir, copied };copied 为成功备份的源路径列表。
|
|
22
|
+
export async function createBackup({
|
|
23
|
+
baseDir,
|
|
24
|
+
paths = [],
|
|
25
|
+
now = () => new Date(),
|
|
26
|
+
fs = { cp, mkdir, stat },
|
|
27
|
+
} = {}) {
|
|
28
|
+
if (!baseDir) {
|
|
29
|
+
throw new Error("缺少备份基础目录");
|
|
30
|
+
}
|
|
31
|
+
const existing = [];
|
|
32
|
+
for (const source of paths) {
|
|
33
|
+
try {
|
|
34
|
+
const info = await fs.stat(source);
|
|
35
|
+
existing.push({ source, isDirectory: info.isDirectory() });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error?.code !== "ENOENT") {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (existing.length === 0) {
|
|
43
|
+
return { backupDir: null, copied: [] };
|
|
44
|
+
}
|
|
45
|
+
const backupDir = resolveBackupDir(baseDir, now());
|
|
46
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
47
|
+
const copied = [];
|
|
48
|
+
for (const { source, isDirectory } of existing) {
|
|
49
|
+
const target = path.join(backupDir, path.basename(source));
|
|
50
|
+
await fs.cp(source, target, { recursive: isDirectory });
|
|
51
|
+
copied.push(source);
|
|
52
|
+
}
|
|
53
|
+
return { backupDir, copied };
|
|
54
|
+
}
|
package/src/diagnostics.js
CHANGED
|
@@ -5,6 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
|
|
6
6
|
import { resolveCodexConfigPath } from "./tool-launcher.js";
|
|
7
7
|
import { sanitizeError, sanitizeText } from "./sanitize.js";
|
|
8
|
+
import { renderSectionTitle, renderTable } from "./ui.js";
|
|
8
9
|
|
|
9
10
|
export async function runDiagnostics({
|
|
10
11
|
config = {},
|
|
@@ -135,47 +136,74 @@ function buildCodexCompatStatus({ codexCompat }) {
|
|
|
135
136
|
return { ok: true, detail: `已支持(wire_api=${codexCompat.data?.wire_api || "responses"})` };
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
139
|
+
// 诊断报告(补充需求第 5 节):按模块分组的小表格展示,
|
|
140
|
+
// 状态用文字标签(通过/警告/失败)明确区分,失败项附修复建议,
|
|
141
|
+
// 输出适合直接复制给客服或开发排查(出口统一脱敏)。
|
|
142
|
+
export function formatDiagnosticsSummary(report, { width = 0 } = {}) {
|
|
143
|
+
const groups = [
|
|
144
|
+
{
|
|
145
|
+
title: "系统环境",
|
|
146
|
+
items: [
|
|
147
|
+
{ label: "OS", item: { ok: true, detail: `${report.platform.os} ${report.platform.arch}` } },
|
|
148
|
+
{ label: "Node.js", item: report.runtime.node, fix: "安装 Node.js 18+ 后重新运行 claude360", valueKey: "version" },
|
|
149
|
+
{ label: "npm", item: report.runtime.npm, fix: "安装 npm 或修复 Node.js 安装", valueKey: "version" },
|
|
150
|
+
{ label: "全局 npm 权限", item: report.runtime.globalNpmPermission, fix: "修复 npm 全局目录权限,或切换到用户级 npm prefix" },
|
|
151
|
+
{ label: "终端二维码", item: report.platform.terminalQr, fix: "终端不支持时可复制支付链接完成支付" },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
title: "Claude360",
|
|
156
|
+
items: [
|
|
157
|
+
{ label: "服务连接", item: report.api.connectivity, fix: "检查网络连接和 Claude360 站点可用性" },
|
|
158
|
+
{ label: "登录状态", item: report.auth, fix: "在菜单中选择“重新登录”完成浏览器授权" },
|
|
159
|
+
{ label: "当前余额", item: report.balance, fix: "通过“余额与充值”菜单充值" },
|
|
160
|
+
{ label: "今日用量", item: report.usage, fix: "登录后可查看今日 Token 用量" },
|
|
161
|
+
{ label: "当前 Key", item: report.token, fix: "在菜单中切换或创建 API Key" },
|
|
162
|
+
{ label: "微信充值", item: report.topUp, fix: "检查后端微信支付配置或使用网页充值" },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
title: "Claude Code",
|
|
167
|
+
items: [
|
|
168
|
+
{ label: "安装状态", item: report.tools.claudeCode, fix: "通过菜单安装或更新 Claude Code", valueKey: "version" },
|
|
169
|
+
{ label: "Claude360 配置", item: report.claudeCodeConfig, fix: "在“Claude Code 配置”菜单中修复" },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
title: "Codex",
|
|
174
|
+
items: [
|
|
175
|
+
{ label: "安装状态", item: report.tools.codex, fix: "通过菜单安装或更新 Codex", valueKey: "version" },
|
|
176
|
+
{ label: "claude360 provider", item: report.codexConfig, fix: "在“Codex 配置”菜单中写入/修复" },
|
|
177
|
+
{ label: "协议兼容性", item: report.codexCompat, fix: "Codex 暂不可用时可先使用 Claude Code" },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const lines = ["诊断报告"];
|
|
183
|
+
for (const group of groups) {
|
|
184
|
+
lines.push("", renderSectionTitle(group.title));
|
|
185
|
+
lines.push(renderTable({
|
|
186
|
+
head: ["检查项", "结果", "状态"],
|
|
187
|
+
rows: group.items.map(({ label, item, valueKey = "detail" }) => [
|
|
188
|
+
label,
|
|
189
|
+
String(item[valueKey] || item.detail || "-"),
|
|
190
|
+
statusLabel(item),
|
|
191
|
+
]),
|
|
192
|
+
}, { width }));
|
|
193
|
+
const fixes = group.items.filter(({ item, fix }) => !item.ok && fix);
|
|
194
|
+
for (const { label, item, fix } of fixes) {
|
|
195
|
+
lines.push(`${item.warn ? "!" : "×"} ${label} 建议:${fix}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
163
198
|
// 出口兜底:report 由调用方注入,逐项 detail 可能未经脱敏
|
|
164
199
|
return sanitizeText(lines.join("\n"));
|
|
165
200
|
}
|
|
166
201
|
|
|
167
|
-
function
|
|
168
|
-
const symbol = item.ok ? "✓" : item.warn ? "!" : "×";
|
|
169
|
-
const value = item[valueKey] || item.detail || "";
|
|
170
|
-
return `${symbol} ${label}${value ? `:${value}` : ""}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function checkFix(item, label, fix, valueKey = "detail") {
|
|
174
|
-
const line = check(item, label, valueKey);
|
|
202
|
+
function statusLabel(item) {
|
|
175
203
|
if (item.ok) {
|
|
176
|
-
return
|
|
204
|
+
return "✓ 通过";
|
|
177
205
|
}
|
|
178
|
-
return
|
|
206
|
+
return item.warn ? "! 警告" : "× 失败";
|
|
179
207
|
}
|
|
180
208
|
|
|
181
209
|
function defaultPlatform() {
|