claude360 0.1.0 → 0.1.1
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 +4 -1
- package/bin/claude360.js +3 -1
- package/package.json +1 -1
- package/src/diagnostics.js +2 -2
- package/src/index.js +43 -5
- package/src/onboarding.js +83 -0
- package/src/tool-installer.js +12 -2
package/README.md
CHANGED
|
@@ -98,6 +98,9 @@ claude360
|
|
|
98
98
|
|
|
99
99
|
关键约束:
|
|
100
100
|
|
|
101
|
+
- 首次运行(无 `cliToken`)会先做环境检查(Node≥18 / npm / 全局 npm 权限),缺失或版本过低则给出修复指引并中止;不自动安装系统运行时。
|
|
102
|
+
- 环境检查通过后进入工具安装引导(Claude Code / Codex / 两者 / 跳过),已安装的工具会标注,安装失败不阻断后续授权。
|
|
103
|
+
- 授权服务不可用(接口返回 404 或网络不可达)时给出友好提示并退出,已安装工具不受影响,可稍后运行 `claude360 setup` 重新完成授权与 Key 配置。
|
|
101
104
|
- 授权码短期有效;CLI 仅在浏览器无法自动打开时把 `verification_url` 与 `user_code` 输出到终端。
|
|
102
105
|
- 多个 Key 时展示:名称 / 脱敏 Key / 分组 / 状态 / 剩余额度(或无限额度)/ 过期时间。
|
|
103
106
|
- 无 Key 时,CLI 通过 `/api/cli/groups` 拉取分组并按 `display_name(倍率 1.0x)` 风格展示,由用户选择后调用 `POST /api/cli/tokens` 创建。
|
|
@@ -296,7 +299,7 @@ claude360-cli/
|
|
|
296
299
|
│ ├── config-store.js # 本地配置读写(0600)
|
|
297
300
|
│ ├── diagnostics.js # 诊断报告
|
|
298
301
|
│ ├── group-manager.js # 分组与倍率展示(无本地映射)
|
|
299
|
-
│ ├── index.js #
|
|
302
|
+
│ ├── index.js # 顶层编排:环境检查 → 工具引导 → 授权 → Key → 主菜单
|
|
300
303
|
│ ├── menu.js # 主菜单
|
|
301
304
|
│ ├── platform.js # 平台路径与能力探测
|
|
302
305
|
│ ├── token-manager.js # Key 选择 / 创建 / reveal
|
package/bin/claude360.js
CHANGED
package/package.json
CHANGED
package/src/diagnostics.js
CHANGED
|
@@ -121,7 +121,7 @@ function detectTerminalQr(platform) {
|
|
|
121
121
|
return { ok: true, detail: `TTY ${term || "unknown"}` };
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
async function commandVersion(execCommand, command, args) {
|
|
124
|
+
export async function commandVersion(execCommand, command, args) {
|
|
125
125
|
const result = await execCommand(command, args);
|
|
126
126
|
if (!result.ok) {
|
|
127
127
|
return { ok: false, detail: result.stderr || result.error || "not found" };
|
|
@@ -196,7 +196,7 @@ export function defaultExecCommand(command, args, { timeoutMs = 5000 } = {}) {
|
|
|
196
196
|
});
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
async function defaultCheckPathWritable(path) {
|
|
199
|
+
export async function defaultCheckPathWritable(path) {
|
|
200
200
|
try {
|
|
201
201
|
await access(path || os.homedir(), constants.W_OK);
|
|
202
202
|
return true;
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { authenticateWithBrowser } from "./auth.js";
|
|
|
7
7
|
import { createConfigStore } from "./config-store.js";
|
|
8
8
|
import { formatDiagnosticsSummary, runDiagnostics as collectDiagnostics } from "./diagnostics.js";
|
|
9
9
|
import { runMainMenu } from "./menu.js";
|
|
10
|
+
import { ensureEnvironment, guideToolInstall as runToolInstallGuide } from "./onboarding.js";
|
|
10
11
|
import { installOrUpdateTools as installTools } from "./tool-installer.js";
|
|
11
12
|
import { launchClaudeCode as startClaudeCode, launchCodex as startCodex } from "./tool-launcher.js";
|
|
12
13
|
import { runWechatTopUp } from "./topup.js";
|
|
@@ -26,16 +27,34 @@ export async function runCli({
|
|
|
26
27
|
launchCodex = startCodex,
|
|
27
28
|
sleep,
|
|
28
29
|
writeLine = console.log,
|
|
30
|
+
forceSetup = false,
|
|
31
|
+
checkEnvironment = ensureEnvironment,
|
|
32
|
+
guideToolInstall = runToolInstallGuide,
|
|
29
33
|
} = {}) {
|
|
30
34
|
let config = await configStore.load();
|
|
31
35
|
const baseUrl = config.baseUrl || "https://claude360.xyz";
|
|
32
36
|
let api = createApiClient({ baseUrl, cliToken: config.cliToken || "" });
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await
|
|
38
|
-
|
|
38
|
+
const needsOnboarding = forceSetup || !config.cliToken;
|
|
39
|
+
if (needsOnboarding) {
|
|
40
|
+
await checkEnvironment({ writeLine });
|
|
41
|
+
await guideToolInstall({ promptSelect, confirm, installOrUpdateTools, writeLine });
|
|
42
|
+
|
|
43
|
+
if (!config.cliToken) {
|
|
44
|
+
let cliToken;
|
|
45
|
+
try {
|
|
46
|
+
cliToken = await authWithBrowser({ api, openBrowser, sleep, writeLine });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (isAuthServiceUnavailable(error)) {
|
|
49
|
+
writeLine(formatAuthUnavailable());
|
|
50
|
+
return "auth_unavailable";
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
config = { ...config, baseUrl, cliToken };
|
|
55
|
+
await configStore.save(config);
|
|
56
|
+
api = createApiClient({ baseUrl, cliToken });
|
|
57
|
+
}
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
if (!config.apiKey) {
|
|
@@ -158,3 +177,22 @@ function defaultOpenBrowser(url) {
|
|
|
158
177
|
child.on("close", () => resolve());
|
|
159
178
|
});
|
|
160
179
|
}
|
|
180
|
+
|
|
181
|
+
function isAuthServiceUnavailable(error) {
|
|
182
|
+
if (!error) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const status = error.status;
|
|
186
|
+
if (status === 404 || (typeof status === "number" && status >= 500)) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
return /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|getaddrinfo|network/i.test(error.message || "");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function formatAuthUnavailable() {
|
|
193
|
+
return [
|
|
194
|
+
"站点 CLI 授权服务暂未就绪(授权接口返回 404 或网络不可达)。",
|
|
195
|
+
"已安装的工具不受影响;待授权服务可用后,运行 `claude360 setup` 重新完成授权与 API Key 配置。",
|
|
196
|
+
"可稍后重试,或先检查网络连接与站点状态。",
|
|
197
|
+
].join("\n");
|
|
198
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { commandVersion, defaultCheckPathWritable, defaultExecCommand } from "./diagnostics.js";
|
|
2
|
+
import { installOrUpdateTools as defaultInstallOrUpdateTools } from "./tool-installer.js";
|
|
3
|
+
|
|
4
|
+
const MIN_NODE_MAJOR = 18;
|
|
5
|
+
|
|
6
|
+
export async function ensureEnvironment({
|
|
7
|
+
execCommand = defaultExecCommand,
|
|
8
|
+
checkPathWritable = defaultCheckPathWritable,
|
|
9
|
+
writeLine = console.log,
|
|
10
|
+
} = {}) {
|
|
11
|
+
const node = await commandVersion(execCommand, "node", ["--version"]);
|
|
12
|
+
if (!node.ok) {
|
|
13
|
+
throw new Error("未检测到 Node.js。Claude360 CLI 需要 Node.js 18+,请先安装 Node.js LTS 后重新运行。");
|
|
14
|
+
}
|
|
15
|
+
const nodeMajor = parseMajorVersion(node.version);
|
|
16
|
+
if (nodeMajor !== null && nodeMajor < MIN_NODE_MAJOR) {
|
|
17
|
+
throw new Error(`当前 Node.js 版本为 ${node.version},低于要求的 18。请升级到 Node.js LTS 后重新运行。`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const npm = await commandVersion(execCommand, "npm", ["--version"]);
|
|
21
|
+
if (!npm.ok) {
|
|
22
|
+
throw new Error("未检测到 npm。请安装 npm 或修复 Node.js 安装后重新运行。");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const prefix = await execCommand("npm", ["prefix", "-g"]);
|
|
26
|
+
const globalDir = prefix.ok ? prefix.stdout.trim() : "";
|
|
27
|
+
const globalNpmWritable = globalDir ? await checkPathWritable(globalDir) : false;
|
|
28
|
+
if (!globalNpmWritable) {
|
|
29
|
+
writeLine("警告:npm 全局目录可能不可写,安装工具时可能需要修复目录权限或切换到用户级 npm prefix。");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
writeLine(`环境检查通过:Node ${node.version},npm ${npm.version}。`);
|
|
33
|
+
return { node: node.version, npm: npm.version, globalNpmWritable };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function guideToolInstall({
|
|
37
|
+
execCommand = defaultExecCommand,
|
|
38
|
+
promptSelect,
|
|
39
|
+
confirm,
|
|
40
|
+
installOrUpdateTools = defaultInstallOrUpdateTools,
|
|
41
|
+
writeLine = console.log,
|
|
42
|
+
} = {}) {
|
|
43
|
+
if (typeof promptSelect !== "function") {
|
|
44
|
+
throw new Error("缺少工具选择输入");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const claudeInstalled = (await commandVersion(execCommand, "claude", ["--version"])).ok;
|
|
48
|
+
const codexInstalled = (await commandVersion(execCommand, "codex", ["--version"])).ok;
|
|
49
|
+
|
|
50
|
+
const selected = await promptSelect("选择要安装的工具(可跳过,稍后也能在主菜单安装)", [
|
|
51
|
+
{ label: withInstalledTag("仅 Claude Code", claudeInstalled), value: "claude" },
|
|
52
|
+
{ label: withInstalledTag("仅 Codex", codexInstalled), value: "codex" },
|
|
53
|
+
{ label: "Claude Code 和 Codex", value: "all" },
|
|
54
|
+
{ label: "跳过", value: "skip" },
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
if (selected === "skip") {
|
|
58
|
+
writeLine("已跳过工具安装,可稍后在主菜单选择“安装或更新工具”。");
|
|
59
|
+
return { skipped: true, targets: [] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const targets = selected === "all" ? ["claude", "codex"] : [selected];
|
|
63
|
+
const results = await installOrUpdateTools({ targets, confirm });
|
|
64
|
+
for (const result of results || []) {
|
|
65
|
+
if (result?.skipped) {
|
|
66
|
+
writeLine(`已跳过 ${result.target} 安装。`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (result?.ok === false) {
|
|
70
|
+
writeLine(`安装 ${result.target} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { skipped: false, targets, results };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function withInstalledTag(label, installed) {
|
|
77
|
+
return installed ? `${label}(已安装,可更新)` : label;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseMajorVersion(version) {
|
|
81
|
+
const match = String(version || "").match(/(\d+)/);
|
|
82
|
+
return match ? Number(match[1]) : null;
|
|
83
|
+
}
|
package/src/tool-installer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
const TOOL_COMMANDS = {
|
|
4
4
|
claude360: {
|
|
@@ -34,7 +34,7 @@ export function buildInstallCommand(target) {
|
|
|
34
34
|
export async function installOrUpdateTools({
|
|
35
35
|
targets,
|
|
36
36
|
confirm,
|
|
37
|
-
execCommand =
|
|
37
|
+
execCommand = defaultInstallExec,
|
|
38
38
|
} = {}) {
|
|
39
39
|
if (!Array.isArray(targets) || targets.length === 0) {
|
|
40
40
|
throw new Error("缺少安装或更新目标");
|
|
@@ -69,3 +69,13 @@ export async function installOrUpdateTools({
|
|
|
69
69
|
}
|
|
70
70
|
return results;
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
// 全局包安装可能耗时较久,使用 inherit-stdio 实时显示 npm 进度,且不设超时,
|
|
74
|
+
// 避免被诊断用的短超时执行器(defaultExecCommand)中途 SIGTERM 杀掉。
|
|
75
|
+
function defaultInstallExec(command, args) {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
const child = spawn(command, args, { stdio: "inherit" });
|
|
78
|
+
child.on("error", (error) => resolve({ ok: false, error: error.message }));
|
|
79
|
+
child.on("close", (code) => resolve({ ok: code === 0 }));
|
|
80
|
+
});
|
|
81
|
+
}
|