pengui-api 1.0.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/dist/cli.js +170 -0
- package/package.json +31 -0
- package/src/cli.ts +195 -0
- package/tsconfig.json +16 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import { platform } from "os";
|
|
10
|
+
const BANNER = `
|
|
11
|
+
____ _ _ ____ ___
|
|
12
|
+
| _ \\ ___ _ __ __ _ _ _(_) / \\ | _ \\_ _|
|
|
13
|
+
| |_) / _ \\ '_ \\ / _\` | | | | |___ / _ \\ | |_) | |
|
|
14
|
+
| __/ __/ | | | (_| | |_| | |___/ ___ \\| __/| |
|
|
15
|
+
|_| \\___|_| |_|\\__, |\\__,_|_| /_/ \\_\\_| |___|
|
|
16
|
+
|___/
|
|
17
|
+
`;
|
|
18
|
+
function readJsonFile(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
if (!existsSync(filePath))
|
|
21
|
+
return null;
|
|
22
|
+
const content = readFileSync(filePath, "utf-8");
|
|
23
|
+
return JSON.parse(content);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function writeJsonFile(filePath, data) {
|
|
30
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
31
|
+
}
|
|
32
|
+
const isWindows = platform() === "win32";
|
|
33
|
+
function commandExists(cmd) {
|
|
34
|
+
try {
|
|
35
|
+
execSync(isWindows ? `where ${cmd}` : `which ${cmd}`, { stdio: "ignore" });
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function tryInstall(name, pkg) {
|
|
43
|
+
const spinner = ora(`正在安装 ${name}...`).start();
|
|
44
|
+
try {
|
|
45
|
+
execSync(`npm install -g ${pkg}`, { stdio: "pipe" });
|
|
46
|
+
spinner.succeed(chalk.green(`${name} 安装成功`));
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
spinner.fail(chalk.red(`${name} 自动安装失败`));
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function checkDependencies() {
|
|
55
|
+
let ok = true;
|
|
56
|
+
// Node.js 版本检查(脚本能运行说明已安装,但检查最低版本)
|
|
57
|
+
const nodeVersion = process.versions.node;
|
|
58
|
+
const major = parseInt(nodeVersion.split(".")[0], 10);
|
|
59
|
+
if (major < 18) {
|
|
60
|
+
console.log(chalk.red(` ✗ Node.js 版本过低 (v${nodeVersion}),需要 v18+`));
|
|
61
|
+
console.log(chalk.yellow(" 请前往 https://nodejs.org 下载最新版本\n"));
|
|
62
|
+
ok = false;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(chalk.green(` ✓ Node.js v${nodeVersion}`));
|
|
66
|
+
}
|
|
67
|
+
// Windows 检查 Git
|
|
68
|
+
if (isWindows) {
|
|
69
|
+
if (commandExists("git")) {
|
|
70
|
+
console.log(chalk.green(" ✓ Git 已安装"));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(chalk.red(" ✗ Git 未安装"));
|
|
74
|
+
console.log(chalk.yellow(" 请前往 https://git-scm.com/downloads 下载安装 Git"));
|
|
75
|
+
console.log(chalk.yellow(" 安装完成后重新运行本工具\n"));
|
|
76
|
+
ok = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Claude Code 检查
|
|
80
|
+
if (commandExists("claude")) {
|
|
81
|
+
console.log(chalk.green(" ✓ Claude Code 已安装"));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(chalk.yellow(" ⚠ Claude Code 未安装,尝试自动安装..."));
|
|
85
|
+
if (tryInstall("Claude Code", "@anthropic-ai/claude-code")) {
|
|
86
|
+
console.log(chalk.green(" ✓ Claude Code 已安装"));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(chalk.yellow(" 请手动执行: npm install -g @anthropic-ai/claude-code"));
|
|
90
|
+
console.log(chalk.yellow(" 安装完成后重新运行本工具\n"));
|
|
91
|
+
ok = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.log();
|
|
95
|
+
return ok;
|
|
96
|
+
}
|
|
97
|
+
async function main() {
|
|
98
|
+
console.log(chalk.cyan(BANNER));
|
|
99
|
+
console.log(chalk.bold(" 一键配置 Claude Code 连接环境\n"));
|
|
100
|
+
// 环境检测
|
|
101
|
+
console.log(chalk.bold(" 环境检测:"));
|
|
102
|
+
if (!checkDependencies()) {
|
|
103
|
+
console.log(chalk.red.bold(" 请先解决以上问题后重新运行本工具。"));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
// API Key 获取提示
|
|
107
|
+
console.log(chalk.bold(" 如何获取 API Key:"));
|
|
108
|
+
console.log(chalk.dim(" 1. 打开 https://api.penguinsaichat.dpdns.org 控制台界面"));
|
|
109
|
+
console.log(chalk.dim(" 2. 点击左边的「令牌管理」"));
|
|
110
|
+
console.log(chalk.dim(" 3. 点击「添加令牌」"));
|
|
111
|
+
console.log(chalk.dim(" 4. 输入名称,分组选择 cc5(推荐,实惠还快,也可选其他)"));
|
|
112
|
+
console.log(chalk.dim(" 5. 点击提交,复制 Key 粘贴到下方"));
|
|
113
|
+
console.log();
|
|
114
|
+
const { apiKey } = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: "password",
|
|
117
|
+
name: "apiKey",
|
|
118
|
+
message: "请输入你的 API Key:",
|
|
119
|
+
mask: "●",
|
|
120
|
+
validate: (input) => input.trim().length > 0 ? true : "API Key 不能为空",
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
const spinner = ora("正在写入配置...").start();
|
|
124
|
+
try {
|
|
125
|
+
const home = homedir();
|
|
126
|
+
const claudeDir = join(home, ".claude");
|
|
127
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
128
|
+
const claudeJsonPath = join(home, ".claude.json");
|
|
129
|
+
// 确保 ~/.claude/ 目录存在
|
|
130
|
+
if (!existsSync(claudeDir)) {
|
|
131
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
// 写入 ~/.claude/settings.json
|
|
134
|
+
const existingSettings = readJsonFile(settingsPath) ?? {};
|
|
135
|
+
const newSettings = {
|
|
136
|
+
...existingSettings,
|
|
137
|
+
env: {
|
|
138
|
+
...(existingSettings.env ?? {}),
|
|
139
|
+
ANTHROPIC_AUTH_TOKEN: apiKey.trim(),
|
|
140
|
+
ANTHROPIC_BASE_URL: "https://api.penguinsaichat.dpdns.org",
|
|
141
|
+
API_TIMEOUT_MS: "3000000",
|
|
142
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
writeJsonFile(settingsPath, newSettings);
|
|
146
|
+
// 写入 ~/.claude.json
|
|
147
|
+
const existingClaudeJson = readJsonFile(claudeJsonPath) ?? {};
|
|
148
|
+
const newClaudeJson = {
|
|
149
|
+
...existingClaudeJson,
|
|
150
|
+
hasCompletedOnboarding: true,
|
|
151
|
+
};
|
|
152
|
+
writeJsonFile(claudeJsonPath, newClaudeJson);
|
|
153
|
+
spinner.succeed(chalk.green("配置完成!"));
|
|
154
|
+
console.log();
|
|
155
|
+
console.log(chalk.dim(" 已写入:"));
|
|
156
|
+
console.log(chalk.dim(` • ${settingsPath}`));
|
|
157
|
+
console.log(chalk.dim(` • ${claudeJsonPath}`));
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(chalk.bold.green(" ✓ 现在可以直接使用 Claude Code 了"));
|
|
160
|
+
console.log();
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
spinner.fail(chalk.red("配置写入失败"));
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
main().catch((err) => {
|
|
168
|
+
console.error(chalk.red("\n发生错误:"), err);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pengui-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pengui-API 一键配置 Claude Code 环境",
|
|
5
|
+
"bin": {
|
|
6
|
+
"pengui-api": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "dist/cli.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude",
|
|
16
|
+
"claude-code",
|
|
17
|
+
"pengui",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"chalk": "^5.3.0",
|
|
23
|
+
"inquirer": "^9.2.12",
|
|
24
|
+
"ora": "^8.0.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/inquirer": "^9.0.7",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"typescript": "^5.7.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import { platform } from "os";
|
|
11
|
+
|
|
12
|
+
const BANNER = `
|
|
13
|
+
____ _ _ ____ ___
|
|
14
|
+
| _ \\ ___ _ __ __ _ _ _(_) / \\ | _ \\_ _|
|
|
15
|
+
| |_) / _ \\ '_ \\ / _\` | | | | |___ / _ \\ | |_) | |
|
|
16
|
+
| __/ __/ | | | (_| | |_| | |___/ ___ \\| __/| |
|
|
17
|
+
|_| \\___|_| |_|\\__, |\\__,_|_| /_/ \\_\\_| |___|
|
|
18
|
+
|___/
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
interface ClaudeSettings {
|
|
22
|
+
env?: Record<string, string>;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ClaudeJson {
|
|
27
|
+
hasCompletedOnboarding?: boolean;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readJsonFile<T>(filePath: string): T | null {
|
|
32
|
+
try {
|
|
33
|
+
if (!existsSync(filePath)) return null;
|
|
34
|
+
const content = readFileSync(filePath, "utf-8");
|
|
35
|
+
return JSON.parse(content) as T;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeJsonFile(filePath: string, data: unknown): void {
|
|
42
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isWindows = platform() === "win32";
|
|
46
|
+
|
|
47
|
+
function commandExists(cmd: string): boolean {
|
|
48
|
+
try {
|
|
49
|
+
execSync(isWindows ? `where ${cmd}` : `which ${cmd}`, { stdio: "ignore" });
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function tryInstall(name: string, pkg: string): boolean {
|
|
57
|
+
const spinner = ora(`正在安装 ${name}...`).start();
|
|
58
|
+
try {
|
|
59
|
+
execSync(`npm install -g ${pkg}`, { stdio: "pipe" });
|
|
60
|
+
spinner.succeed(chalk.green(`${name} 安装成功`));
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
spinner.fail(chalk.red(`${name} 自动安装失败`));
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function checkDependencies(): boolean {
|
|
69
|
+
let ok = true;
|
|
70
|
+
|
|
71
|
+
// Node.js 版本检查(脚本能运行说明已安装,但检查最低版本)
|
|
72
|
+
const nodeVersion = process.versions.node;
|
|
73
|
+
const major = parseInt(nodeVersion.split(".")[0], 10);
|
|
74
|
+
if (major < 18) {
|
|
75
|
+
console.log(chalk.red(` ✗ Node.js 版本过低 (v${nodeVersion}),需要 v18+`));
|
|
76
|
+
console.log(chalk.yellow(" 请前往 https://nodejs.org 下载最新版本\n"));
|
|
77
|
+
ok = false;
|
|
78
|
+
} else {
|
|
79
|
+
console.log(chalk.green(` ✓ Node.js v${nodeVersion}`));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Windows 检查 Git
|
|
83
|
+
if (isWindows) {
|
|
84
|
+
if (commandExists("git")) {
|
|
85
|
+
console.log(chalk.green(" ✓ Git 已安装"));
|
|
86
|
+
} else {
|
|
87
|
+
console.log(chalk.red(" ✗ Git 未安装"));
|
|
88
|
+
console.log(chalk.yellow(" 请前往 https://git-scm.com/downloads 下载安装 Git"));
|
|
89
|
+
console.log(chalk.yellow(" 安装完成后重新运行本工具\n"));
|
|
90
|
+
ok = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Claude Code 检查
|
|
95
|
+
if (commandExists("claude")) {
|
|
96
|
+
console.log(chalk.green(" ✓ Claude Code 已安装"));
|
|
97
|
+
} else {
|
|
98
|
+
console.log(chalk.yellow(" ⚠ Claude Code 未安装,尝试自动安装..."));
|
|
99
|
+
if (tryInstall("Claude Code", "@anthropic-ai/claude-code")) {
|
|
100
|
+
console.log(chalk.green(" ✓ Claude Code 已安装"));
|
|
101
|
+
} else {
|
|
102
|
+
console.log(chalk.yellow(" 请手动执行: npm install -g @anthropic-ai/claude-code"));
|
|
103
|
+
console.log(chalk.yellow(" 安装完成后重新运行本工具\n"));
|
|
104
|
+
ok = false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log();
|
|
109
|
+
return ok;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function main(): Promise<void> {
|
|
113
|
+
console.log(chalk.cyan(BANNER));
|
|
114
|
+
console.log(chalk.bold(" 一键配置 Claude Code 连接环境\n"));
|
|
115
|
+
|
|
116
|
+
// 环境检测
|
|
117
|
+
console.log(chalk.bold(" 环境检测:"));
|
|
118
|
+
if (!checkDependencies()) {
|
|
119
|
+
console.log(chalk.red.bold(" 请先解决以上问题后重新运行本工具。"));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// API Key 获取提示
|
|
124
|
+
console.log(chalk.bold(" 如何获取 API Key:"));
|
|
125
|
+
console.log(chalk.dim(" 1. 打开 https://api.penguinsaichat.dpdns.org 控制台界面"));
|
|
126
|
+
console.log(chalk.dim(" 2. 点击左边的「令牌管理」"));
|
|
127
|
+
console.log(chalk.dim(" 3. 点击「添加令牌」"));
|
|
128
|
+
console.log(chalk.dim(" 4. 输入名称,分组选择 cc5(推荐,实惠还快,也可选其他)"));
|
|
129
|
+
console.log(chalk.dim(" 5. 点击提交,复制 Key 粘贴到下方"));
|
|
130
|
+
console.log();
|
|
131
|
+
|
|
132
|
+
const { apiKey } = await inquirer.prompt<{ apiKey: string }>([
|
|
133
|
+
{
|
|
134
|
+
type: "password",
|
|
135
|
+
name: "apiKey",
|
|
136
|
+
message: "请输入你的 API Key:",
|
|
137
|
+
mask: "●",
|
|
138
|
+
validate: (input: string) =>
|
|
139
|
+
input.trim().length > 0 ? true : "API Key 不能为空",
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
const spinner = ora("正在写入配置...").start();
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const home = homedir();
|
|
147
|
+
const claudeDir = join(home, ".claude");
|
|
148
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
149
|
+
const claudeJsonPath = join(home, ".claude.json");
|
|
150
|
+
|
|
151
|
+
// 确保 ~/.claude/ 目录存在
|
|
152
|
+
if (!existsSync(claudeDir)) {
|
|
153
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 写入 ~/.claude/settings.json
|
|
157
|
+
const existingSettings = readJsonFile<ClaudeSettings>(settingsPath) ?? {};
|
|
158
|
+
const newSettings: ClaudeSettings = {
|
|
159
|
+
...existingSettings,
|
|
160
|
+
env: {
|
|
161
|
+
...(existingSettings.env ?? {}),
|
|
162
|
+
ANTHROPIC_AUTH_TOKEN: apiKey.trim(),
|
|
163
|
+
ANTHROPIC_BASE_URL: "https://api.penguinsaichat.dpdns.org",
|
|
164
|
+
API_TIMEOUT_MS: "3000000",
|
|
165
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
writeJsonFile(settingsPath, newSettings);
|
|
169
|
+
|
|
170
|
+
// 写入 ~/.claude.json
|
|
171
|
+
const existingClaudeJson = readJsonFile<ClaudeJson>(claudeJsonPath) ?? {};
|
|
172
|
+
const newClaudeJson: ClaudeJson = {
|
|
173
|
+
...existingClaudeJson,
|
|
174
|
+
hasCompletedOnboarding: true,
|
|
175
|
+
};
|
|
176
|
+
writeJsonFile(claudeJsonPath, newClaudeJson);
|
|
177
|
+
|
|
178
|
+
spinner.succeed(chalk.green("配置完成!"));
|
|
179
|
+
console.log();
|
|
180
|
+
console.log(chalk.dim(" 已写入:"));
|
|
181
|
+
console.log(chalk.dim(` • ${settingsPath}`));
|
|
182
|
+
console.log(chalk.dim(` • ${claudeJsonPath}`));
|
|
183
|
+
console.log();
|
|
184
|
+
console.log(chalk.bold.green(" ✓ 现在可以直接使用 Claude Code 了"));
|
|
185
|
+
console.log();
|
|
186
|
+
} catch (err) {
|
|
187
|
+
spinner.fail(chalk.red("配置写入失败"));
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
main().catch((err) => {
|
|
193
|
+
console.error(chalk.red("\n发生错误:"), err);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": false
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}
|