ticketpro-auto-setup 1.1.8 → 1.1.10

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 CHANGED
@@ -5,6 +5,7 @@ import chalk from 'chalk';
5
5
  import { showWelcomePage } from './pages/welcome.js';
6
6
  import { performCleanup } from './pages/cleanup.js';
7
7
  import { apiConfigPage } from './pages/api-config.js';
8
+ import { runPreflightChecks } from './pages/preflight.js';
8
9
  import { claudeSetupPage } from './pages/claude-setup.js';
9
10
  import { codexSetupPage } from './pages/codex-setup.js';
10
11
  import { generateCompleteBanner } from './utils/banner.js';
@@ -24,6 +25,11 @@ export async function runCli() {
24
25
  }
25
26
  // Step 1: 清理环境
26
27
  await performCleanup();
28
+ // Step 1.5: 安装前依赖预检
29
+ const preflightOk = await runPreflightChecks();
30
+ if (!preflightOk) {
31
+ process.exit(1);
32
+ }
27
33
  // Step 2: 工具选择 + API 配置 + 安装
28
34
  await apiConfigPage(state);
29
35
  // Step 3: Claude Code 扩展(若选了)
@@ -51,7 +51,7 @@ export function configureClaudeCode(state) {
51
51
  settings.env = {
52
52
  ...(settings.env ?? {}),
53
53
  ANTHROPIC_BASE_URL: state.claudeBaseUrl,
54
- ANTHROPIC_API_KEY: state.apiKey,
54
+ ANTHROPIC_API_KEY: state.claudeApiKey || state.apiKey,
55
55
  CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: '78',
56
56
  };
57
57
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
@@ -137,7 +137,7 @@ function spawnCodeAbyss(command, args) {
137
137
  */
138
138
  export function repairCclineStatusLine() {
139
139
  try {
140
- const { homeDir } = getPlatform();
140
+ const { homeDir, isWindows } = getPlatform();
141
141
  const claudeDir = getClaudeDir();
142
142
  const settingsPath = path.join(claudeDir, 'settings.json');
143
143
  if (!fs.existsSync(settingsPath)) {
@@ -151,15 +151,15 @@ export function repairCclineStatusLine() {
151
151
  return { name: 'ccline statusLine', success: false, error: 'settings.json parse failed' };
152
152
  }
153
153
  const cclineDir = path.join(claudeDir, 'ccline');
154
- const localCcline = path.join(cclineDir, 'ccline');
155
- // 动态解析全局 ccline 路径(兼容 nvm: ~/.nvm/.../bin/ccline)
156
- let resolvedGlobal = '';
157
- const whichResult = execCommand('command -v ccline', { cwd: homeDir, timeout: 5000 });
158
- if (whichResult.success) {
159
- resolvedGlobal = whichResult.stdout.split('\n').pop()?.trim() ?? '';
160
- }
161
- // 如果本地路径不存在,但全局 ccline 存在,创建软链接到本地稳定路径
162
- if (!fs.existsSync(localCcline) && resolvedGlobal && fs.existsSync(resolvedGlobal)) {
154
+ const localCcline = path.join(cclineDir, isWindows ? 'ccline.cmd' : 'ccline');
155
+ // 动态探测全局 ccline(跨平台)
156
+ const detectCmd = isWindows ? 'where ccline.cmd' : 'command -v ccline';
157
+ const whichResult = execCommand(detectCmd, { cwd: homeDir, timeout: 5000 });
158
+ const resolvedGlobal = whichResult.success
159
+ ? whichResult.stdout.split(/\r?\n/).find(Boolean)?.trim() ?? ''
160
+ : '';
161
+ // Unix 下若本地路径不存在但全局 ccline 存在,创建软链接到稳定路径
162
+ if (!isWindows && !fs.existsSync(localCcline) && resolvedGlobal && fs.existsSync(resolvedGlobal)) {
163
163
  fs.mkdirSync(cclineDir, { recursive: true });
164
164
  try {
165
165
  fs.symlinkSync(resolvedGlobal, localCcline);
@@ -173,17 +173,16 @@ export function repairCclineStatusLine() {
173
173
  const currentCommand = current && typeof current === 'object' && typeof current.command === 'string'
174
174
  ? current.command
175
175
  : '';
176
- // 使用绝对路径,避免某些环境下 "~" 不展开导致 statusLine 命令不可执行
177
- const localCmd = localCcline;
178
- const fallbackCmd = resolvedGlobal && fs.existsSync(resolvedGlobal) ? resolvedGlobal : localCmd;
176
+ const preferredCommand = isWindows ? 'ccline.cmd' : 'ccline';
177
+ const fallbackCommand = fs.existsSync(localCcline) ? localCcline : preferredCommand;
179
178
  const needPatch = !currentCommand ||
180
- !currentCommand.includes('ccline') ||
181
- (currentCommand.includes('~/.claude/ccline/ccline') && !fs.existsSync(localCcline)) ||
182
- currentCommand.includes('~/.claude/ccline/ccline');
179
+ !currentCommand.toLowerCase().includes('ccline') ||
180
+ (isWindows && currentCommand.trim() === 'ccline') ||
181
+ (!isWindows && currentCommand.includes('~/.claude/ccline/ccline'));
183
182
  if (needPatch) {
184
183
  settings.statusLine = {
185
184
  type: 'command',
186
- command: fs.existsSync(localCcline) ? localCmd : fallbackCmd,
185
+ command: resolvedGlobal ? preferredCommand : fallbackCommand,
187
186
  padding: 0,
188
187
  };
189
188
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
@@ -48,7 +48,7 @@ env_key = "OPENAI_API_KEY"
48
48
  fs.writeFileSync(configPath, toml, 'utf-8');
49
49
  log.success(`Codex config written to ${configPath}`);
50
50
  // 设置环境变量
51
- setOpenAIApiKey(state.apiKey);
51
+ setOpenAIApiKey(state.codexApiKey || state.apiKey);
52
52
  return { name: 'Codex CLI Config', success: true };
53
53
  }
54
54
  catch (err) {
@@ -5,7 +5,7 @@ import fs from 'node:fs';
5
5
  import path from 'node:path';
6
6
  import ora from 'ora';
7
7
  import { execAsync, commandExists } from '../utils/shell.js';
8
- import { getClaudeDir } from '../utils/platform.js';
8
+ import { getClaudeDir, getPlatform } from '../utils/platform.js';
9
9
  import { appendToCodexConfig } from './codex-cli.js';
10
10
  import { log } from '../utils/logger.js';
11
11
  /**
@@ -36,7 +36,10 @@ async function ensureUvx() {
36
36
  const pipCmd = hasPip3 ? 'pip3' : 'pip';
37
37
  log.info(`正在通过 ${pipCmd} 安装 uv...`);
38
38
  const spinner = ora(`${pipCmd} install uv...`).start();
39
- const result = await execAsync(`${pipCmd} install uv --break-system-packages 2>/dev/null || ${pipCmd} install uv`, { timeout: 120_000 });
39
+ const pipInstallCmd = getPlatform().isWindows
40
+ ? `${pipCmd} install uv`
41
+ : `${pipCmd} install uv --break-system-packages 2>/dev/null || ${pipCmd} install uv`;
42
+ const result = await execAsync(pipInstallCmd, { timeout: 120_000 });
40
43
  if (!result.success) {
41
44
  spinner.fail('uv 安装失败');
42
45
  log.error('请手动安装 uv: pip install uv');
@@ -17,18 +17,19 @@ const JSHOOK_REPO = 'https://github.com/wuji66dde/jshook-skill.git';
17
17
  export async function installJshookSkill() {
18
18
  const spinner = ora('Installing jshook-skill (JS逆向工程技能)...').start();
19
19
  const claudeDir = getClaudeDir();
20
- const skillDir = path.join(claudeDir, 'skills', 'jshook-skill');
20
+ const skillsRoot = path.join(claudeDir, 'skills');
21
+ const skillDir = path.join(skillsRoot, 'jshook-skill');
21
22
  const { gitCmd, npmCmd } = getPlatform();
22
23
  try {
23
24
  // 确保 skills 目录存在
24
- fs.mkdirSync(path.join(claudeDir, 'skills'), { recursive: true });
25
+ fs.mkdirSync(skillsRoot, { recursive: true });
25
26
  // 如果已存在,先删除
26
27
  if (fs.existsSync(skillDir)) {
27
28
  fs.rmSync(skillDir, { recursive: true, force: true });
28
29
  }
29
- // git clone
30
+ // git clone(在 skillsRoot 下克隆相对目录,避免 Windows 引号/路径转义问题)
30
31
  spinner.text = 'Cloning jshook-skill repository...';
31
- const cloneResult = await execAsync(`${gitCmd} clone ${JSHOOK_REPO} "${skillDir}"`, { cwd: getPlatform().homeDir, timeout: 120_000 });
32
+ const cloneResult = await execAsync(`${gitCmd} clone ${JSHOOK_REPO} jshook-skill`, { cwd: skillsRoot, timeout: 120_000 });
32
33
  if (!cloneResult.success) {
33
34
  spinner.fail('jshook-skill: git clone failed');
34
35
  log.dim(`请手动执行: git clone ${JSHOOK_REPO} ~/.claude/skills/jshook-skill`);
@@ -22,17 +22,34 @@ export async function apiConfigPage(state) {
22
22
  state.toolChoice = toolChoice;
23
23
  state.installClaude = toolChoice === 'both' || toolChoice === 'claude-only';
24
24
  state.installCodex = toolChoice === 'both' || toolChoice === 'codex-only';
25
- // 2. 输入 API Key
25
+ // 2. 输入 API Key(Claude 和 Codex 分离)
26
26
  console.log();
27
- const apiKey = await password({
28
- message: '输入中转站 API Key:',
29
- mask: '*',
30
- });
31
- if (!apiKey || apiKey.trim().length === 0) {
32
- log.error('API Key 不能为空!');
33
- process.exit(1);
27
+ // Claude Key
28
+ if (state.installClaude) {
29
+ const claudeApiKey = await password({
30
+ message: '输入 Claude Code API Key(ANTHROPIC_API_KEY):',
31
+ mask: '*',
32
+ });
33
+ if (!claudeApiKey || claudeApiKey.trim().length === 0) {
34
+ log.error('Claude Code API Key 不能为空!');
35
+ process.exit(1);
36
+ }
37
+ state.claudeApiKey = claudeApiKey.trim();
38
+ }
39
+ // Codex Key
40
+ if (state.installCodex) {
41
+ const codexApiKey = await password({
42
+ message: '输入 Codex CLI API Key(OPENAI_API_KEY):',
43
+ mask: '*',
44
+ });
45
+ if (!codexApiKey || codexApiKey.trim().length === 0) {
46
+ log.error('Codex CLI API Key 不能为空!');
47
+ process.exit(1);
48
+ }
49
+ state.codexApiKey = codexApiKey.trim();
34
50
  }
35
- state.apiKey = apiKey.trim();
51
+ // 兼容旧逻辑:保留 state.apiKey,但优先使用分离字段
52
+ state.apiKey = state.claudeApiKey || state.codexApiKey || '';
36
53
  // 3. 显示中转站配置
37
54
  console.log();
38
55
  log.info('中转站配置(自动填写):');
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 安装前依赖预检:缺少关键依赖则中断流程
3
+ */
4
+ export declare function runPreflightChecks(): Promise<boolean>;
5
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1,145 @@
1
+ /**
2
+ * 预检页 — 运行环境与依赖检查
3
+ */
4
+ import path from 'node:path';
5
+ import { getPlatform } from '../utils/platform.js';
6
+ import { commandExists, execCommand, checkDependencies } from '../utils/shell.js';
7
+ import { log } from '../utils/logger.js';
8
+ /**
9
+ * 安装前依赖预检:缺少关键依赖则中断流程
10
+ */
11
+ export async function runPreflightChecks() {
12
+ log.section('🩺 环境预检');
13
+ const platform = getPlatform();
14
+ const required = checkDependencies([
15
+ { name: 'Node.js', command: 'node' },
16
+ { name: 'npm', command: platform.npmCmd },
17
+ { name: 'git', command: platform.gitCmd },
18
+ ]);
19
+ for (const item of required) {
20
+ if (item.installed) {
21
+ log.success(`${item.name} 检测通过 (${item.command})`);
22
+ }
23
+ else {
24
+ log.error(`${item.name} 缺失 (${item.command})`);
25
+ }
26
+ }
27
+ const failedRequired = required.filter((x) => x.required && !x.installed);
28
+ // 进一步验证命令可执行(不仅是存在)
29
+ const runtimeChecks = [
30
+ checkRuntime('Node.js', 'node --version'),
31
+ checkRuntime('npm', `${platform.npmCmd} --version`),
32
+ checkRuntime('git', `${platform.gitCmd} --version`),
33
+ ];
34
+ for (const r of runtimeChecks) {
35
+ if (r.ok)
36
+ log.success(`${r.name} 可执行 (${r.detail})`);
37
+ else
38
+ log.error(`${r.name} 不可执行 (${r.detail})`);
39
+ }
40
+ const runtimeFailed = runtimeChecks.filter((x) => !x.ok);
41
+ let effectiveRuntimeFailed = runtimeFailed;
42
+ // 自动修复:Windows 下 npm 可执行失败时,尝试切换到 npm.cmd
43
+ const autoFixApplied = autoFixCommandPaths(runtimeChecks);
44
+ if (autoFixApplied.length > 0) {
45
+ console.log();
46
+ for (const msg of autoFixApplied) {
47
+ log.warn(msg);
48
+ }
49
+ // 修复后重跑关键运行时检测
50
+ const rerunChecks = [
51
+ checkRuntime('npm', `${platform.npmCmd} --version`),
52
+ ];
53
+ for (const r of rerunChecks) {
54
+ if (r.ok)
55
+ log.success(`${r.name} 修复后可执行 (${r.detail})`);
56
+ else
57
+ log.error(`${r.name} 修复后仍不可执行 (${r.detail})`);
58
+ }
59
+ const fixedRuntime = runtimeFailed.filter(x => x.name !== 'npm').concat(rerunChecks.filter(x => !x.ok));
60
+ effectiveRuntimeFailed = fixedRuntime;
61
+ if (failedRequired.length > 0 || fixedRuntime.length > 0) {
62
+ console.log();
63
+ log.error('环境预检失败,已中止安装。');
64
+ printFixHints();
65
+ return false;
66
+ }
67
+ }
68
+ // 非阻断:GrokSearch 相关依赖提示
69
+ const hasPip = commandExists('pip') || commandExists('pip3');
70
+ const hasUvx = commandExists('uvx');
71
+ if (!hasPip) {
72
+ log.warn('未检测到 pip/pip3:若安装 GrokSearch MCP,后续将失败');
73
+ }
74
+ else if (!hasUvx) {
75
+ log.warn('未检测到 uvx:若安装 GrokSearch MCP,安装器会尝试自动安装 uv');
76
+ }
77
+ else {
78
+ log.success('GrokSearch 运行时依赖已就绪 (uvx)');
79
+ }
80
+ if (failedRequired.length > 0 || effectiveRuntimeFailed.length > 0) {
81
+ console.log();
82
+ log.error('环境预检失败,已中止安装。');
83
+ printFixHints();
84
+ return false;
85
+ }
86
+ console.log();
87
+ log.success('环境预检通过');
88
+ console.log();
89
+ return true;
90
+ }
91
+ function checkRuntime(name, command) {
92
+ const result = execCommand(command, { timeout: 15_000 });
93
+ return {
94
+ name,
95
+ ok: result.success,
96
+ detail: result.success ? (result.stdout.split(/\r?\n/)[0] ?? 'ok') : (result.stderr || 'failed'),
97
+ };
98
+ }
99
+ function autoFixCommandPaths(runtimeChecks) {
100
+ const { isWindows } = getPlatform();
101
+ const applied = [];
102
+ if (!isWindows)
103
+ return applied;
104
+ const npmFailed = runtimeChecks.find((x) => x.name === 'npm' && !x.ok);
105
+ if (!npmFailed)
106
+ return applied;
107
+ // 若 npm.cmd 可用但 npm 不可用,切到 cmd shell 并把 npm 所在目录前置到 PATH
108
+ const whereNpmCmd = execCommand('where npm.cmd', { timeout: 5000 });
109
+ const npmCmdPath = whereNpmCmd.success
110
+ ? whereNpmCmd.stdout.split(/\r?\n/).find(Boolean)?.trim() ?? ''
111
+ : '';
112
+ if (npmCmdPath) {
113
+ try {
114
+ process.env.npm_config_script_shell = 'cmd.exe';
115
+ const npmBinDir = path.dirname(npmCmdPath);
116
+ const currentPath = process.env.PATH ?? '';
117
+ const pathParts = currentPath.split(';').map((x) => x.trim()).filter(Boolean);
118
+ const hasNpmBin = pathParts.some((p) => p.toLowerCase() === npmBinDir.toLowerCase());
119
+ if (!hasNpmBin) {
120
+ process.env.PATH = `${npmBinDir};${currentPath}`;
121
+ }
122
+ applied.push(`检测到 npm 运行异常,已切换 shell=cmd.exe 并将 npm 目录前置 PATH: ${npmBinDir}`);
123
+ }
124
+ catch {
125
+ // ignore
126
+ }
127
+ }
128
+ return applied;
129
+ }
130
+ function printFixHints() {
131
+ const { isWindows } = getPlatform();
132
+ log.info('建议修复命令:');
133
+ if (isWindows) {
134
+ log.dim(' 1) 安装 Node.js LTS(含 npm): https://nodejs.org/');
135
+ log.dim(' 2) 安装 Git for Windows: https://git-scm.com/download/win');
136
+ log.dim(' 3) 重开终端后验证: node --version && npm --version && git --version');
137
+ log.dim(' 4) 若 npm 路径异常,检查 PATH 中 Node 安装目录是否存在');
138
+ }
139
+ else {
140
+ log.dim(' Ubuntu/Debian: sudo apt update && sudo apt install -y nodejs npm git');
141
+ log.dim(' macOS(Homebrew): brew install node git');
142
+ log.dim(' 验证: node --version && npm --version && git --version');
143
+ }
144
+ }
145
+ //# sourceMappingURL=preflight.js.map
@@ -23,8 +23,15 @@ export interface GrokConfig {
23
23
  export interface SetupState {
24
24
  /** 选择的工具 */
25
25
  toolChoice: ToolChoice;
26
- /** 中转站 API Key */
26
+ /**
27
+ * 兼容旧版本的单密钥字段(deprecated)
28
+ * 新版本请使用 claudeApiKey / codexApiKey
29
+ */
27
30
  apiKey: string;
31
+ /** Claude Code API Key (ANTHROPIC_API_KEY) */
32
+ claudeApiKey: string;
33
+ /** Codex CLI API Key (OPENAI_API_KEY) */
34
+ codexApiKey: string;
28
35
  /** Claude Code 基础 URL */
29
36
  claudeBaseUrl: string;
30
37
  /** Codex 基础 URL */
@@ -6,6 +6,8 @@ export function createDefaultState() {
6
6
  return {
7
7
  toolChoice: 'both',
8
8
  apiKey: '',
9
+ claudeApiKey: '',
10
+ codexApiKey: '',
9
11
  claudeBaseUrl: 'https://api.ticketpro.cc',
10
12
  codexBaseUrl: 'https://api.ticketpro.cc/v1',
11
13
  installClaude: true,
@@ -4,6 +4,12 @@ export interface ExecResult {
4
4
  stderr: string;
5
5
  code: number | null;
6
6
  }
7
+ /** 依赖检查项 */
8
+ export interface DependencyCheckItem {
9
+ name: string;
10
+ command: string;
11
+ required?: boolean;
12
+ }
7
13
  /**
8
14
  * 同步执行命令,返回结构化结果
9
15
  */
@@ -17,6 +23,15 @@ export declare function execCommand(command: string, options?: {
17
23
  * 检查命令是否可用
18
24
  */
19
25
  export declare function commandExists(cmd: string): boolean;
26
+ /**
27
+ * 检查命令依赖,返回逐项结果
28
+ */
29
+ export declare function checkDependencies(items: DependencyCheckItem[]): Array<{
30
+ name: string;
31
+ command: string;
32
+ required: boolean;
33
+ installed: boolean;
34
+ }>;
20
35
  /**
21
36
  * 异步执行命令(不阻塞事件循环,spinner 可正常旋转)
22
37
  */
@@ -40,6 +40,20 @@ export function commandExists(cmd) {
40
40
  const checkCmd = isWindows ? `where ${cmd}` : `command -v ${cmd}`;
41
41
  return execCommand(checkCmd).success;
42
42
  }
43
+ /**
44
+ * 检查命令依赖,返回逐项结果
45
+ */
46
+ export function checkDependencies(items) {
47
+ return items.map((item) => {
48
+ const installed = commandExists(item.command);
49
+ return {
50
+ name: item.name,
51
+ command: item.command,
52
+ required: item.required !== false,
53
+ installed,
54
+ };
55
+ });
56
+ }
43
57
  /**
44
58
  * 异步执行命令(不阻塞事件循环,spinner 可正常旋转)
45
59
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticketpro-auto-setup",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "TicketPro Auto Setup Wizard — 一键配置 Claude Code CLI / Codex CLI",
5
5
  "type": "module",
6
6
  "bin": {