ticketpro-auto-setup 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.d.ts +5 -0
- package/dist/cli.js +119 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +11 -0
- package/dist/installers/claude-code.d.ts +14 -0
- package/dist/installers/claude-code.js +73 -0
- package/dist/installers/code-abyss.d.ts +13 -0
- package/dist/installers/code-abyss.js +79 -0
- package/dist/installers/codex-cli.d.ts +18 -0
- package/dist/installers/codex-cli.js +122 -0
- package/dist/installers/grok-search.d.ts +10 -0
- package/dist/installers/grok-search.js +112 -0
- package/dist/installers/helloagents.d.ts +6 -0
- package/dist/installers/helloagents.js +148 -0
- package/dist/installers/jshook-skill.d.ts +6 -0
- package/dist/installers/jshook-skill.js +68 -0
- package/dist/pages/api-config.d.ts +6 -0
- package/dist/pages/api-config.js +65 -0
- package/dist/pages/claude-setup.d.ts +6 -0
- package/dist/pages/claude-setup.js +75 -0
- package/dist/pages/cleanup.d.ts +5 -0
- package/dist/pages/cleanup.js +74 -0
- package/dist/pages/codex-setup.d.ts +6 -0
- package/dist/pages/codex-setup.js +93 -0
- package/dist/pages/welcome.d.ts +6 -0
- package/dist/pages/welcome.js +35 -0
- package/dist/types/index.d.ts +58 -0
- package/dist/types/index.js +19 -0
- package/dist/utils/backup.d.ts +9 -0
- package/dist/utils/backup.js +79 -0
- package/dist/utils/banner.d.ts +9 -0
- package/dist/utils/banner.js +41 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/platform.d.ts +9 -0
- package/dist/utils/platform.js +55 -0
- package/dist/utils/shell.d.ts +27 -0
- package/dist/utils/shell.js +77 -0
- package/package.json +49 -0
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 主流程编排
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { showWelcomePage } from './pages/welcome.js';
|
|
6
|
+
import { performCleanup } from './pages/cleanup.js';
|
|
7
|
+
import { apiConfigPage } from './pages/api-config.js';
|
|
8
|
+
import { claudeSetupPage } from './pages/claude-setup.js';
|
|
9
|
+
import { codexSetupPage } from './pages/codex-setup.js';
|
|
10
|
+
import { generateCompleteBanner } from './utils/banner.js';
|
|
11
|
+
import { log } from './utils/logger.js';
|
|
12
|
+
import { createDefaultState } from './types/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* 主流程入口
|
|
15
|
+
*/
|
|
16
|
+
export async function runCli() {
|
|
17
|
+
const state = createDefaultState();
|
|
18
|
+
try {
|
|
19
|
+
// Step 0: 欢迎页 + 免责声明
|
|
20
|
+
const proceed = await showWelcomePage();
|
|
21
|
+
if (!proceed) {
|
|
22
|
+
log.info('已取消。再见!');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
// Step 1: 清理环境
|
|
26
|
+
await performCleanup();
|
|
27
|
+
// Step 2: 工具选择 + API 配置 + 安装
|
|
28
|
+
await apiConfigPage(state);
|
|
29
|
+
// Step 3: Claude Code 扩展(若选了)
|
|
30
|
+
await claudeSetupPage(state);
|
|
31
|
+
// Step 4: Codex 扩展(若选了)
|
|
32
|
+
await codexSetupPage(state);
|
|
33
|
+
// Step 5: 完成摘要
|
|
34
|
+
showSummary(state);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
// 处理用户中断(Ctrl+C)
|
|
38
|
+
if (err.name === 'ExitPromptError' || err.message?.includes('User force closed')) {
|
|
39
|
+
console.log();
|
|
40
|
+
log.info('已取消。再见!');
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
log.error(`Unexpected error: ${err.message}`);
|
|
45
|
+
log.dim(err.stack ?? '');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 显示安装摘要
|
|
51
|
+
*/
|
|
52
|
+
function showSummary(state) {
|
|
53
|
+
console.log(generateCompleteBanner());
|
|
54
|
+
const lines = [];
|
|
55
|
+
// Claude Code 摘要
|
|
56
|
+
if (state.installClaude) {
|
|
57
|
+
lines.push(chalk.bold.cyan(' Claude Code CLI'));
|
|
58
|
+
const claudeCliResult = state.results.find(r => r.name === 'Claude Code CLI');
|
|
59
|
+
const claudeConfigResult = state.results.find(r => r.name === 'Claude Code Config');
|
|
60
|
+
lines.push(formatResultLine('已安装', claudeCliResult?.success ?? false, claudeCliResult?.version));
|
|
61
|
+
lines.push(formatResultLine(`中转站: ${state.claudeBaseUrl}`, claudeConfigResult?.success ?? false));
|
|
62
|
+
lines.push(formatResultLine('Auto-Compact: 155K (78%)', claudeConfigResult?.success ?? false));
|
|
63
|
+
// 扩展结果
|
|
64
|
+
for (const ext of state.claudeExtensions) {
|
|
65
|
+
const result = state.results.find(r => r.name === ext || r.name.startsWith(ext));
|
|
66
|
+
const label = ext === 'code-abyss' ? 'code-abyss 技能'
|
|
67
|
+
: ext === 'jshook-skill' ? 'jshook-skill 技能'
|
|
68
|
+
: ext === 'grok-search' ? 'GrokSearch MCP'
|
|
69
|
+
: ext;
|
|
70
|
+
lines.push(formatResultLine(`${label}已配置`, result?.success ?? false));
|
|
71
|
+
}
|
|
72
|
+
lines.push('');
|
|
73
|
+
}
|
|
74
|
+
// Codex 摘要
|
|
75
|
+
if (state.installCodex) {
|
|
76
|
+
lines.push(chalk.bold.green(' Codex CLI'));
|
|
77
|
+
const codexCliResult = state.results.find(r => r.name === 'Codex CLI');
|
|
78
|
+
const codexConfigResult = state.results.find(r => r.name === 'Codex CLI Config');
|
|
79
|
+
lines.push(formatResultLine('已安装', codexCliResult?.success ?? false, codexCliResult?.version));
|
|
80
|
+
lines.push(formatResultLine(`中转站: ${state.codexBaseUrl}`, codexConfigResult?.success ?? false));
|
|
81
|
+
// 扩展结果
|
|
82
|
+
for (const ext of state.codexExtensions) {
|
|
83
|
+
const result = state.results.find(r => r.name === ext ||
|
|
84
|
+
r.name === `${ext} (Codex)` ||
|
|
85
|
+
r.name.startsWith(`GrokSearch (Codex`));
|
|
86
|
+
const label = ext === 'code-abyss' ? 'code-abyss 技能'
|
|
87
|
+
: ext === 'grok-search' ? 'GrokSearch MCP'
|
|
88
|
+
: ext === 'helloagents' ? 'helloagents Agent'
|
|
89
|
+
: ext;
|
|
90
|
+
lines.push(formatResultLine(`${label}已配置`, result?.success ?? false));
|
|
91
|
+
}
|
|
92
|
+
lines.push('');
|
|
93
|
+
}
|
|
94
|
+
// 备份信息
|
|
95
|
+
lines.push(chalk.dim(' 备份位置: ~/.claude.bak, ~/.codex.bak'));
|
|
96
|
+
lines.push(chalk.dim(' 如需恢复: cp -r ~/.claude.bak ~/.claude'));
|
|
97
|
+
log.box(lines);
|
|
98
|
+
// 失败项目提示
|
|
99
|
+
const failures = state.results.filter(r => !r.success);
|
|
100
|
+
if (failures.length > 0) {
|
|
101
|
+
console.log();
|
|
102
|
+
log.warn(`${failures.length} 个步骤失败:`);
|
|
103
|
+
for (const f of failures) {
|
|
104
|
+
log.error(` ${f.name}: ${f.error ?? 'Unknown error'}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
console.log();
|
|
108
|
+
log.success('Setup complete! 🎉');
|
|
109
|
+
console.log();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 格式化结果行
|
|
113
|
+
*/
|
|
114
|
+
function formatResultLine(text, success, extra) {
|
|
115
|
+
const icon = success ? chalk.green('✓') : chalk.red('✗');
|
|
116
|
+
const suffix = extra ? chalk.dim(` (${extra})`) : '';
|
|
117
|
+
return ` ├─ ${icon} ${text}${suffix}`;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TicketPro Auto Setup — Entry Point
|
|
4
|
+
* 一键配置 Claude Code CLI / Codex CLI
|
|
5
|
+
*/
|
|
6
|
+
import { runCli } from './cli.js';
|
|
7
|
+
runCli().catch((err) => {
|
|
8
|
+
console.error('Fatal error:', err);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SetupState, StepResult } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 安装 Claude Code CLI
|
|
4
|
+
*/
|
|
5
|
+
export declare function installClaudeCode(): Promise<StepResult>;
|
|
6
|
+
/**
|
|
7
|
+
* 写入 Claude Code settings.json
|
|
8
|
+
*/
|
|
9
|
+
export declare function configureClaudeCode(state: SetupState): StepResult;
|
|
10
|
+
/**
|
|
11
|
+
* 卸载 Claude Code CLI
|
|
12
|
+
*/
|
|
13
|
+
export declare function uninstallClaudeCode(): boolean;
|
|
14
|
+
//# sourceMappingURL=claude-code.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code CLI 安装 + 配置
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { execCommand } from '../utils/shell.js';
|
|
8
|
+
import { getPlatform, getClaudeDir } from '../utils/platform.js';
|
|
9
|
+
import { log } from '../utils/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* 安装 Claude Code CLI
|
|
12
|
+
*/
|
|
13
|
+
export async function installClaudeCode() {
|
|
14
|
+
const spinner = ora('Installing @anthropic-ai/claude-code...').start();
|
|
15
|
+
const { npmCmd } = getPlatform();
|
|
16
|
+
const result = execCommand(`${npmCmd} install -g @anthropic-ai/claude-code`, {
|
|
17
|
+
timeout: 300_000,
|
|
18
|
+
});
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
spinner.fail('Claude Code CLI installation failed');
|
|
21
|
+
log.dim(result.stderr);
|
|
22
|
+
return { name: 'Claude Code CLI', success: false, error: result.stderr };
|
|
23
|
+
}
|
|
24
|
+
// 获取版本
|
|
25
|
+
const versionResult = execCommand('claude --version');
|
|
26
|
+
const version = versionResult.success ? versionResult.stdout.trim() : 'unknown';
|
|
27
|
+
spinner.succeed(`Claude Code CLI installed (${version})`);
|
|
28
|
+
return { name: 'Claude Code CLI', success: true, version };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 写入 Claude Code settings.json
|
|
32
|
+
*/
|
|
33
|
+
export function configureClaudeCode(state) {
|
|
34
|
+
const claudeDir = getClaudeDir();
|
|
35
|
+
try {
|
|
36
|
+
// 确保目录存在
|
|
37
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
38
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
39
|
+
// 读取现有 settings(如果有)
|
|
40
|
+
let settings = {};
|
|
41
|
+
if (fs.existsSync(settingsPath)) {
|
|
42
|
+
try {
|
|
43
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
settings = {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// 合并 env 配置
|
|
50
|
+
settings.env = {
|
|
51
|
+
...(settings.env ?? {}),
|
|
52
|
+
ANTHROPIC_BASE_URL: state.claudeBaseUrl,
|
|
53
|
+
ANTHROPIC_API_KEY: state.apiKey,
|
|
54
|
+
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: '78',
|
|
55
|
+
};
|
|
56
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
57
|
+
log.success(`Claude Code config written to ${settingsPath}`);
|
|
58
|
+
return { name: 'Claude Code Config', success: true };
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
log.error(`Failed to write Claude Code config: ${err.message}`);
|
|
62
|
+
return { name: 'Claude Code Config', success: false, error: err.message };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 卸载 Claude Code CLI
|
|
67
|
+
*/
|
|
68
|
+
export function uninstallClaudeCode() {
|
|
69
|
+
const { npmCmd } = getPlatform();
|
|
70
|
+
const result = execCommand(`${npmCmd} uninstall -g @anthropic-ai/claude-code`);
|
|
71
|
+
return result.success;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StepResult } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 安装 code-abyss(邪修道统 · 安全工程技能)
|
|
4
|
+
*
|
|
5
|
+
* code-abyss 支持 --target 参数:
|
|
6
|
+
* --target claude → 安装到 ~/.claude
|
|
7
|
+
* --target codex → 安装到 ~/.codex
|
|
8
|
+
*
|
|
9
|
+
* 注意: code-abyss 有交互式提示(如"配置自定义 provider?"),
|
|
10
|
+
* 需要通过 stdin pipe 自动应答 "N" 跳过。
|
|
11
|
+
*/
|
|
12
|
+
export declare function installCodeAbyss(target: 'claude' | 'codex' | 'both'): Promise<StepResult>;
|
|
13
|
+
//# sourceMappingURL=code-abyss.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* code-abyss 安装逻辑
|
|
3
|
+
* npm 包: code-abyss (by telagod)
|
|
4
|
+
* 支持: npx code-abyss --target claude|codex
|
|
5
|
+
*/
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { execCommand } from '../utils/shell.js';
|
|
8
|
+
import { getPlatform } from '../utils/platform.js';
|
|
9
|
+
import { log } from '../utils/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* 安装 code-abyss(邪修道统 · 安全工程技能)
|
|
12
|
+
*
|
|
13
|
+
* code-abyss 支持 --target 参数:
|
|
14
|
+
* --target claude → 安装到 ~/.claude
|
|
15
|
+
* --target codex → 安装到 ~/.codex
|
|
16
|
+
*
|
|
17
|
+
* 注意: code-abyss 有交互式提示(如"配置自定义 provider?"),
|
|
18
|
+
* 需要通过 stdin pipe 自动应答 "N" 跳过。
|
|
19
|
+
*/
|
|
20
|
+
export async function installCodeAbyss(target) {
|
|
21
|
+
const spinner = ora(`Installing code-abyss (邪修道统) → ${target}...`).start();
|
|
22
|
+
const { npxCmd, npmCmd } = getPlatform();
|
|
23
|
+
try {
|
|
24
|
+
if (target === 'both') {
|
|
25
|
+
// 分别安装到 claude 和 codex
|
|
26
|
+
const claudeResult = await runCodeAbyss(npxCmd, npmCmd, 'claude', spinner);
|
|
27
|
+
if (!claudeResult.success)
|
|
28
|
+
return claudeResult;
|
|
29
|
+
const codexResult = await runCodeAbyss(npxCmd, npmCmd, 'codex', spinner);
|
|
30
|
+
return codexResult;
|
|
31
|
+
}
|
|
32
|
+
return await runCodeAbyss(npxCmd, npmCmd, target, spinner);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
spinner.fail('code-abyss installation failed');
|
|
36
|
+
return { name: 'code-abyss', success: false, error: err.message };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 执行 code-abyss 安装(单个 target)
|
|
41
|
+
*/
|
|
42
|
+
async function runCodeAbyss(npxCmd, npmCmd, target, spinner) {
|
|
43
|
+
const name = target === 'codex' ? 'code-abyss (Codex)' : 'code-abyss';
|
|
44
|
+
// code-abyss 内部用 @inquirer/prompts 做交互,
|
|
45
|
+
// 非 TTY 下用 `yes N |` 管道自动应答"配置自定义 provider? → N"
|
|
46
|
+
// 同时设置 CI=true 让 inquirer fallback 到默认值
|
|
47
|
+
const pipeAnswer = 'yes N |';
|
|
48
|
+
const envFlags = 'CI=true FORCE_COLOR=0';
|
|
49
|
+
// 方式 1: npx -y code-abyss --target <target>
|
|
50
|
+
// 注意: yes N 会产生大量输出,必须重定向到 /dev/null 避免缓冲区溢出 (ENOBUFS)
|
|
51
|
+
spinner.text = `Installing code-abyss → ${target} (npx)...`;
|
|
52
|
+
const isWin = npxCmd.includes('.cmd');
|
|
53
|
+
const devNull = isWin ? 'NUL' : '/dev/null';
|
|
54
|
+
const result = execCommand(`${pipeAnswer} ${envFlags} ${npxCmd} -y code-abyss --target ${target} > ${devNull} 2>&1`, { timeout: 300_000 });
|
|
55
|
+
if (result.success) {
|
|
56
|
+
spinner.succeed(`code-abyss installed → ${target}`);
|
|
57
|
+
return { name, success: true };
|
|
58
|
+
}
|
|
59
|
+
// 方式 2: 全局安装后执行
|
|
60
|
+
spinner.text = `npx failed, trying global install → ${target}...`;
|
|
61
|
+
const installResult = execCommand(`${npmCmd} install -g code-abyss`, {
|
|
62
|
+
timeout: 300_000,
|
|
63
|
+
});
|
|
64
|
+
if (installResult.success) {
|
|
65
|
+
const execResult = execCommand(`${pipeAnswer} ${envFlags} code-abyss --target ${target} > ${devNull} 2>&1`, { timeout: 300_000 });
|
|
66
|
+
if (execResult.success) {
|
|
67
|
+
spinner.succeed(`code-abyss installed → ${target} (global)`);
|
|
68
|
+
return { name, success: true };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
spinner.fail(`code-abyss installation failed → ${target}`);
|
|
72
|
+
log.dim(`请手动执行: npx code-abyss --target ${target}`);
|
|
73
|
+
return {
|
|
74
|
+
name,
|
|
75
|
+
success: false,
|
|
76
|
+
error: `Install failed for target ${target}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=code-abyss.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SetupState, StepResult } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 安装 Codex CLI
|
|
4
|
+
*/
|
|
5
|
+
export declare function installCodexCli(): Promise<StepResult>;
|
|
6
|
+
/**
|
|
7
|
+
* 写入 Codex config.toml
|
|
8
|
+
*/
|
|
9
|
+
export declare function configureCodexCli(state: SetupState): StepResult;
|
|
10
|
+
/**
|
|
11
|
+
* 向 config.toml 追加 MCP 服务器配置
|
|
12
|
+
*/
|
|
13
|
+
export declare function appendToCodexConfig(content: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 卸载 Codex CLI
|
|
16
|
+
*/
|
|
17
|
+
export declare function uninstallCodexCli(): boolean;
|
|
18
|
+
//# sourceMappingURL=codex-cli.d.ts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI 安装 + 配置
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { execCommand } from '../utils/shell.js';
|
|
8
|
+
import { getPlatform, getCodexDir, getShellProfile } from '../utils/platform.js';
|
|
9
|
+
import { log } from '../utils/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* 安装 Codex CLI
|
|
12
|
+
*/
|
|
13
|
+
export async function installCodexCli() {
|
|
14
|
+
const spinner = ora('Installing @openai/codex...').start();
|
|
15
|
+
const { npmCmd } = getPlatform();
|
|
16
|
+
const result = execCommand(`${npmCmd} install -g @openai/codex`, {
|
|
17
|
+
timeout: 300_000,
|
|
18
|
+
});
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
spinner.fail('Codex CLI installation failed');
|
|
21
|
+
log.dim(result.stderr);
|
|
22
|
+
return { name: 'Codex CLI', success: false, error: result.stderr };
|
|
23
|
+
}
|
|
24
|
+
// 获取版本
|
|
25
|
+
const versionResult = execCommand('codex --version');
|
|
26
|
+
const version = versionResult.success ? versionResult.stdout.trim() : 'unknown';
|
|
27
|
+
spinner.succeed(`Codex CLI installed (${version})`);
|
|
28
|
+
return { name: 'Codex CLI', success: true, version };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 写入 Codex config.toml
|
|
32
|
+
*/
|
|
33
|
+
export function configureCodexCli(state) {
|
|
34
|
+
const codexDir = getCodexDir();
|
|
35
|
+
try {
|
|
36
|
+
// 确保目录存在
|
|
37
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
38
|
+
const configPath = path.join(codexDir, 'config.toml');
|
|
39
|
+
// 构建基础配置
|
|
40
|
+
let toml = `# TicketPro Codex CLI Configuration
|
|
41
|
+
model_provider = "openai"
|
|
42
|
+
|
|
43
|
+
[model_providers.openai]
|
|
44
|
+
name = "TicketPro Relay"
|
|
45
|
+
base_url = "${state.codexBaseUrl}"
|
|
46
|
+
env_key = "OPENAI_API_KEY"
|
|
47
|
+
`;
|
|
48
|
+
fs.writeFileSync(configPath, toml, 'utf-8');
|
|
49
|
+
log.success(`Codex config written to ${configPath}`);
|
|
50
|
+
// 设置环境变量
|
|
51
|
+
setOpenAIApiKey(state.apiKey);
|
|
52
|
+
return { name: 'Codex CLI Config', success: true };
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
log.error(`Failed to write Codex config: ${err.message}`);
|
|
56
|
+
return { name: 'Codex CLI Config', success: false, error: err.message };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 设置 OPENAI_API_KEY 环境变量
|
|
61
|
+
*/
|
|
62
|
+
function setOpenAIApiKey(apiKey) {
|
|
63
|
+
const { isWindows } = getPlatform();
|
|
64
|
+
// 立即设置到当前进程(虽然对 CLI 工具意义不大)
|
|
65
|
+
process.env.OPENAI_API_KEY = apiKey;
|
|
66
|
+
if (isWindows) {
|
|
67
|
+
// Windows: 使用 setx 写入用户环境变量
|
|
68
|
+
const result = execCommand(`setx OPENAI_API_KEY "${apiKey}"`);
|
|
69
|
+
if (result.success) {
|
|
70
|
+
log.success('OPENAI_API_KEY set via setx (需重启终端生效)');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
log.warn('Failed to set OPENAI_API_KEY via setx. 请手动设置环境变量。');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Unix: 追加到 shell profile
|
|
78
|
+
const profilePath = getShellProfile();
|
|
79
|
+
try {
|
|
80
|
+
const exportLine = `\nexport OPENAI_API_KEY="${apiKey}"\n`;
|
|
81
|
+
const content = fs.existsSync(profilePath)
|
|
82
|
+
? fs.readFileSync(profilePath, 'utf-8')
|
|
83
|
+
: '';
|
|
84
|
+
// 检查是否已存在
|
|
85
|
+
if (content.includes('OPENAI_API_KEY=')) {
|
|
86
|
+
// 替换现有
|
|
87
|
+
const updated = content.replace(/export OPENAI_API_KEY=.*/, `export OPENAI_API_KEY="${apiKey}"`);
|
|
88
|
+
fs.writeFileSync(profilePath, updated, 'utf-8');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
fs.appendFileSync(profilePath, exportLine, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
log.success(`OPENAI_API_KEY written to ${profilePath}`);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
log.warn(`Failed to write to ${profilePath}. 请手动设置 OPENAI_API_KEY。`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 向 config.toml 追加 MCP 服务器配置
|
|
102
|
+
*/
|
|
103
|
+
export function appendToCodexConfig(content) {
|
|
104
|
+
const codexDir = getCodexDir();
|
|
105
|
+
const configPath = path.join(codexDir, 'config.toml');
|
|
106
|
+
try {
|
|
107
|
+
fs.appendFileSync(configPath, '\n' + content, 'utf-8');
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 卸载 Codex CLI
|
|
116
|
+
*/
|
|
117
|
+
export function uninstallCodexCli() {
|
|
118
|
+
const { npmCmd } = getPlatform();
|
|
119
|
+
const result = execCommand(`${npmCmd} uninstall -g @openai/codex`);
|
|
120
|
+
return result.success;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=codex-cli.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GrokConfig, StepResult } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 为 Claude Code 安装 GrokSearch MCP
|
|
4
|
+
*/
|
|
5
|
+
export declare function installGrokSearchForClaude(config: GrokConfig): Promise<StepResult>;
|
|
6
|
+
/**
|
|
7
|
+
* 为 Codex 安装 GrokSearch MCP
|
|
8
|
+
*/
|
|
9
|
+
export declare function installGrokSearchForCodex(config: GrokConfig): Promise<StepResult>;
|
|
10
|
+
//# sourceMappingURL=grok-search.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GrokSearch MCP 安装逻辑
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { execCommand, commandExists } from '../utils/shell.js';
|
|
8
|
+
import { getClaudeDir } from '../utils/platform.js';
|
|
9
|
+
import { appendToCodexConfig } from './codex-cli.js';
|
|
10
|
+
import { log } from '../utils/logger.js';
|
|
11
|
+
/**
|
|
12
|
+
* 为 Claude Code 安装 GrokSearch MCP
|
|
13
|
+
*/
|
|
14
|
+
export async function installGrokSearchForClaude(config) {
|
|
15
|
+
const spinner = ora('Configuring GrokSearch MCP for Claude Code...').start();
|
|
16
|
+
try {
|
|
17
|
+
// 检查 uvx 是否可用
|
|
18
|
+
const hasUvx = commandExists('uvx');
|
|
19
|
+
if (!hasUvx) {
|
|
20
|
+
spinner.warn('uvx not found. GrokSearch requires Python uv/uvx.');
|
|
21
|
+
log.dim('Install: pip install uv or https://docs.astral.sh/uv/');
|
|
22
|
+
log.dim('Will configure anyway — please install uvx before using.');
|
|
23
|
+
}
|
|
24
|
+
// 构建 MCP 配置 JSON
|
|
25
|
+
const mcpConfig = JSON.stringify({
|
|
26
|
+
type: 'stdio',
|
|
27
|
+
command: 'uvx',
|
|
28
|
+
args: ['--from', 'git+https://github.com/zgj19810121/GrokSearch', 'grok-search'],
|
|
29
|
+
env: {
|
|
30
|
+
GROK_API_URL: config.apiUrl,
|
|
31
|
+
GROK_API_KEY: config.apiKey,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
// 使用 claude mcp add-json 命令
|
|
35
|
+
const result = execCommand(`claude mcp add-json grok-search --scope user '${mcpConfig.replace(/'/g, "'\\''")}'`, { timeout: 30_000 });
|
|
36
|
+
if (result.success) {
|
|
37
|
+
spinner.succeed('GrokSearch MCP configured for Claude Code');
|
|
38
|
+
return { name: 'GrokSearch (Claude)', success: true };
|
|
39
|
+
}
|
|
40
|
+
// fallback: 直接写入 settings.json
|
|
41
|
+
spinner.text = 'claude mcp command failed, writing config directly...';
|
|
42
|
+
const written = writeGrokSearchToClaudeSettings(config);
|
|
43
|
+
if (written) {
|
|
44
|
+
spinner.succeed('GrokSearch MCP configured for Claude Code (direct write)');
|
|
45
|
+
return { name: 'GrokSearch (Claude)', success: true };
|
|
46
|
+
}
|
|
47
|
+
spinner.fail('GrokSearch MCP configuration failed');
|
|
48
|
+
return { name: 'GrokSearch (Claude)', success: false, error: 'Both claude mcp and direct write failed' };
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
spinner.fail('GrokSearch MCP configuration failed');
|
|
52
|
+
return { name: 'GrokSearch (Claude)', success: false, error: err.message };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 为 Codex 安装 GrokSearch MCP
|
|
57
|
+
*/
|
|
58
|
+
export async function installGrokSearchForCodex(config) {
|
|
59
|
+
const spinner = ora('Configuring GrokSearch MCP for Codex CLI...').start();
|
|
60
|
+
try {
|
|
61
|
+
const toml = `
|
|
62
|
+
[[mcp_servers]]
|
|
63
|
+
name = "grok-search"
|
|
64
|
+
command = "uvx"
|
|
65
|
+
args = ["--from", "git+https://github.com/zgj19810121/GrokSearch", "grok-search"]
|
|
66
|
+
|
|
67
|
+
[mcp_servers.env]
|
|
68
|
+
GROK_API_URL = "${config.apiUrl}"
|
|
69
|
+
GROK_API_KEY = "${config.apiKey}"
|
|
70
|
+
`;
|
|
71
|
+
const success = appendToCodexConfig(toml);
|
|
72
|
+
if (success) {
|
|
73
|
+
spinner.succeed('GrokSearch MCP configured for Codex CLI');
|
|
74
|
+
return { name: 'GrokSearch (Codex)', success: true };
|
|
75
|
+
}
|
|
76
|
+
spinner.fail('GrokSearch MCP configuration for Codex failed');
|
|
77
|
+
return { name: 'GrokSearch (Codex)', success: false, error: 'Failed to write config.toml' };
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
spinner.fail('GrokSearch MCP configuration failed');
|
|
81
|
+
return { name: 'GrokSearch (Codex)', success: false, error: err.message };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 直接写入 Claude settings.json 的 mcpServers 段
|
|
86
|
+
*/
|
|
87
|
+
function writeGrokSearchToClaudeSettings(config) {
|
|
88
|
+
try {
|
|
89
|
+
const claudeDir = getClaudeDir();
|
|
90
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
91
|
+
let settings = {};
|
|
92
|
+
if (fs.existsSync(settingsPath)) {
|
|
93
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
94
|
+
}
|
|
95
|
+
settings.mcpServers = settings.mcpServers ?? {};
|
|
96
|
+
settings.mcpServers['grok-search'] = {
|
|
97
|
+
type: 'stdio',
|
|
98
|
+
command: 'uvx',
|
|
99
|
+
args: ['--from', 'git+https://github.com/zgj19810121/GrokSearch', 'grok-search'],
|
|
100
|
+
env: {
|
|
101
|
+
GROK_API_URL: config.apiUrl,
|
|
102
|
+
GROK_API_KEY: config.apiKey,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=grok-search.js.map
|