claude360 0.2.5 → 0.2.6
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 +25 -13
- package/package.json +1 -1
- package/src/index.js +6 -3
- package/src/init-config.js +2 -1
- package/src/mcp-skill.js +21 -11
- package/src/prompts.js +30 -10
- package/src/ui.js +5 -4
package/README.md
CHANGED
|
@@ -115,25 +115,37 @@ claude360
|
|
|
115
115
|
|
|
116
116
|
## 日常使用
|
|
117
117
|
|
|
118
|
+
主菜单按分组展示,TTY 环境下用 **方向键 + Enter** 选择(Esc 返回);非 TTY / CI 环境自动降级为编号输入,编号以实际输出为准:
|
|
119
|
+
|
|
118
120
|
```
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
请选择功能:
|
|
122
|
+
─── 常用操作 ───
|
|
123
|
+
❯ 启动 Claude Code 使用 Claude360 接入配置直接启动
|
|
124
|
+
启动 Codex 使用 Claude360 接入配置直接启动
|
|
125
|
+
─── 账户与充值 ───
|
|
126
|
+
余额与充值 查看余额用量,支持微信扫码充值
|
|
127
|
+
切换 Key / 模型 切换当前 API Key 或默认模型
|
|
128
|
+
创建新的 API Key 在当前账号下新建一个 API Key
|
|
129
|
+
打开 Claude360 控制台 在浏览器中打开网页控制台
|
|
130
|
+
─── Key / 模型 / MCP 配置 ───
|
|
131
|
+
Claude Code 配置 完整初始化、工作流、MCP、模型与记忆配置
|
|
132
|
+
Codex 配置 完整初始化、Provider、工作流与 MCP 配置
|
|
133
|
+
一键完整初始化 Claude Code / Codex 完整初始化向导
|
|
134
|
+
推荐 MCP / Skill 安装推荐的 MCP、工作流与 Skill 增强
|
|
135
|
+
生成 cc-switch 配置 生成可导入 cc-switch 的供应商配置
|
|
136
|
+
─── 维护与设置 ───
|
|
137
|
+
安装或更新工具 安装或升级 Claude Code / Codex / 本 CLI
|
|
138
|
+
诊断与修复 一键诊断环境与配置问题并尝试修复
|
|
139
|
+
重新登录 清除本地登录态后重新浏览器授权
|
|
140
|
+
退出 退出 claude360 CLI
|
|
128
141
|
```
|
|
129
142
|
|
|
130
|
-
-
|
|
131
|
-
- **微信扫码充值**:先取 `/api/cli/topup/options`,按后端校验通过的金额或最低额提交,再用 `qrcode-terminal` 在终端渲染 `code_url`,渲染失败时降级为纯文本 URL。轮询 `/api/cli/topup/order?order_id=`,支付完成自动刷新余额。
|
|
143
|
+
- **余额与充值**:调用 `GET /api/cli/me`,展示账号、余额、已用、当前 Key 名称与分组;充值先取 `/api/cli/topup/options`,按后端校验通过的金额或最低额提交,再用 `qrcode-terminal` 在终端渲染 `code_url`,渲染失败时降级为纯文本 URL。轮询 `/api/cli/topup/order?order_id=`,支付完成自动刷新余额。
|
|
132
144
|
- **启动 Claude Code**:调用 `claude` 子进程,并通过 `ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN` 注入。
|
|
133
145
|
- **启动 Codex**:写入 `~/.codex/config.toml` 中 `[model_providers.claude360]` 与 `[profiles.claude360]`,发现既有冲突字段会先要求用户确认再覆盖,然后 `codex --profile claude360`,并通过 `CLAUDE360_API_KEY` 注入。
|
|
134
146
|
- **安装或更新工具**:三选一菜单(仅 Claude Code / 仅 Codex / 两者),每步都需要确认。
|
|
135
|
-
- **切换
|
|
136
|
-
-
|
|
147
|
+
- **切换 Key / 模型**:重新进入 Token 向导,或从 `/api/cli/models` 拉取真实模型列表选择默认模型。
|
|
148
|
+
- **诊断与修复**:见下方“诊断与故障排查”。
|
|
137
149
|
|
|
138
150
|
## 本地配置
|
|
139
151
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -127,6 +127,9 @@ export async function runCli({
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
multiSelectInput = multiSelectInput || promptMultiSelect;
|
|
130
|
+
// 覆盖 ~/.codex/config.toml 冲突字段属于危险操作:默认 NO,
|
|
131
|
+
// 交互模式下 NO 使用危险色高亮(审查 P2-3)
|
|
132
|
+
const confirmDanger = (message) => confirm(message, { danger: true });
|
|
130
133
|
|
|
131
134
|
if (showBanner) {
|
|
132
135
|
if (fancyOutput) {
|
|
@@ -497,7 +500,7 @@ export async function runCli({
|
|
|
497
500
|
installWorkflows: () => installCodexWfs({ codexDir, multiSelect, confirm, writeLine }),
|
|
498
501
|
configureProvider: async () => {
|
|
499
502
|
await ensureApiKey();
|
|
500
|
-
await configureCodex({ config, confirmConflict:
|
|
503
|
+
await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
501
504
|
await markConfigured("codex");
|
|
502
505
|
writeLine("✓ 已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
|
|
503
506
|
},
|
|
@@ -640,7 +643,7 @@ export async function runCli({
|
|
|
640
643
|
}
|
|
641
644
|
await markConfigured("codex");
|
|
642
645
|
writeLine("正在启动 Codex...");
|
|
643
|
-
await launchCodex({ config, confirmConflict:
|
|
646
|
+
await launchCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
644
647
|
return true;
|
|
645
648
|
}
|
|
646
649
|
|
|
@@ -651,7 +654,7 @@ export async function runCli({
|
|
|
651
654
|
return false;
|
|
652
655
|
}
|
|
653
656
|
await ensureApiKey();
|
|
654
|
-
await configureCodex({ config, confirmConflict:
|
|
657
|
+
await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
655
658
|
await markConfigured("codex");
|
|
656
659
|
writeLine("✓ 已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
|
|
657
660
|
return true;
|
package/src/init-config.js
CHANGED
|
@@ -205,7 +205,8 @@ export const FALLBACK_CLAUDE_ALIASES = [
|
|
|
205
205
|
];
|
|
206
206
|
|
|
207
207
|
// 后端接口 GET /api/cli/models?tool=<claude_code|codex>:
|
|
208
|
-
// { models: [{ id, display_name, description, tags, recommended
|
|
208
|
+
// { models: [{ id, display_name, description, tags, recommended }] }
|
|
209
|
+
// (后端暂无上下文长度数据源,契约中不含 context_length)
|
|
209
210
|
export async function loadAvailableModels(api, tool = "") {
|
|
210
211
|
if (api && typeof api.get === "function") {
|
|
211
212
|
try {
|
package/src/mcp-skill.js
CHANGED
|
@@ -78,16 +78,24 @@ export function resolveClaudeJsonPath({ homedir = os.homedir } = {}) {
|
|
|
78
78
|
return path.join(homedir(), ".claude.json");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
// 安全边界:远程接口只允许下发展示层元数据(label / desc / recommended /
|
|
82
|
+
// platforms),安装命令(claudeArgs / codex)必须来自 CLI 内置受信清单;
|
|
83
|
+
// 未知 id 直接丢弃,防止后端被攻破或响应被篡改时执行任意命令。
|
|
84
|
+
export function normalizeRemoteMcp(mcp) {
|
|
85
|
+
if (!mcp || typeof mcp.id !== "string") {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const builtin = RECOMMENDED_MCPS.find((item) => item.id === mcp.id);
|
|
89
|
+
if (!builtin) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...builtin,
|
|
94
|
+
label: typeof mcp.label === "string" && mcp.label !== "" ? mcp.label : builtin.label,
|
|
95
|
+
desc: typeof mcp.desc === "string" ? mcp.desc : builtin.desc,
|
|
96
|
+
recommended: Boolean(mcp.recommended ?? builtin.recommended),
|
|
97
|
+
platforms: typeof mcp.platforms === "string" ? mcp.platforms : builtin.platforms,
|
|
98
|
+
};
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
// MCP 列表远程优先(优化需求第 4 节):GET /api/cli/mcps 返回
|
|
@@ -97,7 +105,9 @@ export async function loadRecommendedMcps(api) {
|
|
|
97
105
|
if (api && typeof api.get === "function") {
|
|
98
106
|
try {
|
|
99
107
|
const data = await api.get("/api/cli/mcps");
|
|
100
|
-
const mcps = (Array.isArray(data?.mcps) ? data.mcps : [])
|
|
108
|
+
const mcps = (Array.isArray(data?.mcps) ? data.mcps : [])
|
|
109
|
+
.map(normalizeRemoteMcp)
|
|
110
|
+
.filter(Boolean);
|
|
101
111
|
if (mcps.length > 0) {
|
|
102
112
|
return { mcps, source: "remote" };
|
|
103
113
|
}
|
package/src/prompts.js
CHANGED
|
@@ -35,6 +35,10 @@ export function isInteractive(stream = process.stdout) {
|
|
|
35
35
|
if (process.env.CI) {
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
|
+
// TERM=dumb 的终端可能是 TTY,但不支持方向键 UI 与 ANSI 光标控制
|
|
39
|
+
if (process.env.TERM === "dumb") {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
38
42
|
return Boolean(stream && stream.isTTY && process.stdin.isTTY);
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -62,12 +66,28 @@ export function renderConfirmLine(yesActive, { color = true, danger = false } =
|
|
|
62
66
|
|
|
63
67
|
const PAGE_SIZE = 14;
|
|
64
68
|
|
|
69
|
+
// 防御:调用方传入空列表或全部为分隔行时,方向键取模运算会得到 NaN 或
|
|
70
|
+
// 进入同步死循环,回车会访问 undefined.value。统一在渲染前抛出可读错误。
|
|
71
|
+
function getSelectableIndexes(choices = []) {
|
|
72
|
+
return choices
|
|
73
|
+
.map((choice, index) => (choice && !choice.separator ? index : -1))
|
|
74
|
+
.filter((index) => index >= 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function assertSelectableChoices(message, choices) {
|
|
78
|
+
const indexes = getSelectableIndexes(choices);
|
|
79
|
+
if (indexes.length === 0) {
|
|
80
|
+
throw new Error(`${message || "选择列表"}缺少可选择项`);
|
|
81
|
+
}
|
|
82
|
+
return indexes;
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
// 单选:方向键移动(跳过分隔行)、回车确认、Esc 返回 escValue
|
|
66
86
|
export const interactiveSelect = createPrompt((config, done) => {
|
|
67
|
-
const { message, choices, escValue, color = true } = config;
|
|
87
|
+
const { message, choices = [], escValue, color = true } = config;
|
|
88
|
+
const selectable = assertSelectableChoices(message, choices);
|
|
68
89
|
const [status, setStatus] = useState("idle");
|
|
69
|
-
const
|
|
70
|
-
const [active, setActive] = useState(Math.max(firstIndex, 0));
|
|
90
|
+
const [active, setActive] = useState(selectable[0]);
|
|
71
91
|
|
|
72
92
|
useKeypress((key) => {
|
|
73
93
|
if (status === "done") {
|
|
@@ -79,12 +99,9 @@ export const interactiveSelect = createPrompt((config, done) => {
|
|
|
79
99
|
return;
|
|
80
100
|
}
|
|
81
101
|
if (isUpKey(key) || isDownKey(key)) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
next = (next + direction + choices.length) % choices.length;
|
|
86
|
-
} while (choices[next].separator);
|
|
87
|
-
setActive(next);
|
|
102
|
+
const delta = isUpKey(key) ? -1 : 1;
|
|
103
|
+
const pos = selectable.indexOf(active);
|
|
104
|
+
setActive(selectable[(pos + delta + selectable.length) % selectable.length]);
|
|
88
105
|
return;
|
|
89
106
|
}
|
|
90
107
|
if (key.name === "escape" && escValue !== undefined) {
|
|
@@ -119,7 +136,10 @@ export const interactiveSelect = createPrompt((config, done) => {
|
|
|
119
136
|
|
|
120
137
|
// 多选:空格选择/取消、a 全选、i 反选、回车确认、Esc 取消(需求第 4 节)
|
|
121
138
|
export const interactiveMultiSelect = createPrompt((config, done) => {
|
|
122
|
-
const { message, choices, preselected = [], color = true } = config;
|
|
139
|
+
const { message, choices = [], preselected = [], color = true } = config;
|
|
140
|
+
if (choices.length === 0) {
|
|
141
|
+
throw new Error(`${message || "多选列表"}缺少可选择项`);
|
|
142
|
+
}
|
|
123
143
|
const [status, setStatus] = useState("idle");
|
|
124
144
|
const [active, setActive] = useState(0);
|
|
125
145
|
const [selected, setSelected] = useState(
|
package/src/ui.js
CHANGED
|
@@ -192,15 +192,16 @@ export function renderBox(message, { kind = "info", color = false, width = 0 } =
|
|
|
192
192
|
return lines.join("\n");
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
// 模型表(优化需求第 3
|
|
195
|
+
// 模型表(优化需求第 3 节):展示后端返回的真实模型信息。
|
|
196
|
+
// 字段契约与 /api/cli/models 对齐:id / display_name / tags / description;
|
|
197
|
+
// 后端暂无上下文长度数据源,不渲染“上下文”列,名称列回退模型 ID。
|
|
196
198
|
export function renderModelTable(models = [], { color = false, width = 0 } = {}) {
|
|
197
199
|
return renderTable({
|
|
198
|
-
head: ["模型 ID", "名称", "标签", "
|
|
200
|
+
head: ["模型 ID", "名称", "标签", "说明"],
|
|
199
201
|
rows: models.map((model) => [
|
|
200
202
|
model.id ?? "-",
|
|
201
|
-
model.display_name || "-",
|
|
203
|
+
model.display_name || model.id || "-",
|
|
202
204
|
Array.isArray(model.tags) && model.tags.length > 0 ? model.tags.join(",") : "-",
|
|
203
|
-
model.context_length ? String(model.context_length) : "-",
|
|
204
205
|
model.description || "-",
|
|
205
206
|
]),
|
|
206
207
|
}, { color, width });
|