@pikecode/api-key-manager 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/LICENSE +21 -0
- package/README.md +99 -0
- package/bin/akm.js +101 -0
- package/bin/cc.js +101 -0
- package/package.json +71 -0
- package/src/CommandRegistry.js +74 -0
- package/src/commands/BaseCommand.js +212 -0
- package/src/commands/add.js +514 -0
- package/src/commands/current.js +94 -0
- package/src/commands/edit.js +208 -0
- package/src/commands/list.js +122 -0
- package/src/commands/remove.js +150 -0
- package/src/commands/switch.js +1479 -0
- package/src/config.js +250 -0
- package/src/index.js +19 -0
- package/src/navigation/EscNavigationManager.js +213 -0
- package/src/utils/claude-settings.js +150 -0
- package/src/utils/config-opener.js +44 -0
- package/src/utils/env-launcher.js +80 -0
- package/src/utils/error-handler.js +53 -0
- package/src/utils/inquirer-setup.js +89 -0
- package/src/utils/logger.js +41 -0
- package/src/utils/provider-status-checker.js +210 -0
- package/src/utils/storage.js +55 -0
- package/src/utils/terminal-format.js +41 -0
- package/src/utils/ui-helper.js +227 -0
- package/src/utils/update-checker.js +121 -0
- package/src/utils/validator.js +157 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 API Key Manager Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# API Key Manager
|
|
2
|
+
|
|
3
|
+
一个简洁而强大的 CLI 工具,用于管理和快速切换多个 API 提供商配置。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- ✨ **快速切换** - 一键切换不同的 API 提供商配置
|
|
8
|
+
- 🔐 **安全存储** - 本地安全存储 API 密钥
|
|
9
|
+
- 🌍 **多提供商支持** - 支持多个 API 提供商(Anthropic、OpenAI 等)
|
|
10
|
+
- 🎯 **灵活配置** - 支持多种认证模式(API Key、Auth Token、OAuth)
|
|
11
|
+
- 🚀 **开箱即用** - 无需复杂配置
|
|
12
|
+
- 💾 **环境变量管理** - 自动设置和管理环境变量
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @pikecode/api-key-manager
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 列出所有命令和选项
|
|
24
|
+
akm --help
|
|
25
|
+
|
|
26
|
+
# 添加新的 API 提供商配置
|
|
27
|
+
akm add
|
|
28
|
+
|
|
29
|
+
# 切换 API 提供商
|
|
30
|
+
akm
|
|
31
|
+
|
|
32
|
+
# 查看当前配置
|
|
33
|
+
akm current
|
|
34
|
+
|
|
35
|
+
# 列出所有配置
|
|
36
|
+
akm list
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 命令
|
|
40
|
+
|
|
41
|
+
| 命令 | 说明 |
|
|
42
|
+
|------|------|
|
|
43
|
+
| `akm` | 交互式选择和切换 API 提供商 |
|
|
44
|
+
| `akm add` | 添加新的 API 提供商配置 |
|
|
45
|
+
| `akm list` | 列出所有已保存的配置 |
|
|
46
|
+
| `akm current` | 显示当前激活的配置 |
|
|
47
|
+
| `akm edit <name>` | 编辑指定提供商的配置 |
|
|
48
|
+
| `akm remove <name>` | 删除指定的提供商配置 |
|
|
49
|
+
|
|
50
|
+
## 配置文件
|
|
51
|
+
|
|
52
|
+
配置文件位置:`~/.akm-config.json`
|
|
53
|
+
|
|
54
|
+
示例配置结构:
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"version": "2.0.0",
|
|
58
|
+
"currentProvider": "provider-name",
|
|
59
|
+
"providers": {
|
|
60
|
+
"provider-name": {
|
|
61
|
+
"name": "provider-name",
|
|
62
|
+
"displayName": "Provider Name",
|
|
63
|
+
"authMode": "api_key",
|
|
64
|
+
"authToken": "your-token",
|
|
65
|
+
"tokenType": "api_key",
|
|
66
|
+
"models": {
|
|
67
|
+
"primary": "claude-sonnet-4",
|
|
68
|
+
"smallFast": "claude-haiku-4"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 支持的认证模式
|
|
76
|
+
|
|
77
|
+
- **api_key** - 标准 API 密钥模式
|
|
78
|
+
- **auth_token** - 认证令牌模式
|
|
79
|
+
- **oauth_token** - OAuth 令牌模式
|
|
80
|
+
|
|
81
|
+
## 快捷键
|
|
82
|
+
|
|
83
|
+
- **ESC** - 返回上级菜单
|
|
84
|
+
- **方向键** - 导航菜单
|
|
85
|
+
- **Ctrl+C** - 退出程序
|
|
86
|
+
|
|
87
|
+
## 系统要求
|
|
88
|
+
|
|
89
|
+
- Node.js >= 14.0.0
|
|
90
|
+
- macOS / Linux / Windows
|
|
91
|
+
|
|
92
|
+
## 许可证
|
|
93
|
+
|
|
94
|
+
MIT
|
|
95
|
+
|
|
96
|
+
## 更多信息
|
|
97
|
+
|
|
98
|
+
- GitHub: https://github.com/pikecode/api-key-manager
|
|
99
|
+
- NPM: https://www.npmjs.com/package/@pikecode/api-key-manager
|
package/bin/akm.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { main } = require('../src/index');
|
|
6
|
+
const { registry } = require('../src/CommandRegistry');
|
|
7
|
+
const pkg = require('../package.json');
|
|
8
|
+
const { checkForUpdates } = require('../src/utils/update-checker');
|
|
9
|
+
|
|
10
|
+
// Set up CLI
|
|
11
|
+
program
|
|
12
|
+
.name('akm')
|
|
13
|
+
.description('API密钥管理工具 - Manage and switch multiple API provider configurations')
|
|
14
|
+
.version(pkg.version, '-v, -V, --version', '显示版本号');
|
|
15
|
+
|
|
16
|
+
// Check for updates before any command runs
|
|
17
|
+
program.hook('preAction', async () => {
|
|
18
|
+
await checkForUpdates({ packageName: pkg.name, currentVersion: pkg.version });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Default command - show provider selection
|
|
22
|
+
program
|
|
23
|
+
.argument('[provider]', '直接切换到指定供应商')
|
|
24
|
+
.action(async (provider) => {
|
|
25
|
+
try {
|
|
26
|
+
await main(provider);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(chalk.red('❌ 执行失败:'), error.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Add command
|
|
34
|
+
program
|
|
35
|
+
.command('add')
|
|
36
|
+
.description('添加新的API密钥配置')
|
|
37
|
+
.action(async () => {
|
|
38
|
+
try {
|
|
39
|
+
await registry.executeCommand('add');
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(chalk.red('❌ 添加失败:'), error.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Remove command
|
|
47
|
+
program
|
|
48
|
+
.command('remove')
|
|
49
|
+
.argument('[provider]', '要删除的供应商名称')
|
|
50
|
+
.description('删除API密钥配置')
|
|
51
|
+
.action(async (provider) => {
|
|
52
|
+
try {
|
|
53
|
+
await registry.executeCommand('remove', provider);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(chalk.red('❌ 删除失败:'), error.message);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// List command
|
|
61
|
+
program
|
|
62
|
+
.command('list')
|
|
63
|
+
.description('列出所有API密钥配置')
|
|
64
|
+
.action(async () => {
|
|
65
|
+
try {
|
|
66
|
+
await registry.executeCommand('list');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(chalk.red('❌ 列表失败:'), error.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Current command
|
|
74
|
+
program
|
|
75
|
+
.command('current')
|
|
76
|
+
.description('显示当前活跃的配置')
|
|
77
|
+
.action(async () => {
|
|
78
|
+
try {
|
|
79
|
+
await registry.executeCommand('current');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(chalk.red('❌ 获取当前配置失败:'), error.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Edit command
|
|
87
|
+
program
|
|
88
|
+
.command('edit')
|
|
89
|
+
.argument('[provider]', '要编辑的供应商名称')
|
|
90
|
+
.description('编辑API密钥配置')
|
|
91
|
+
.action(async (provider) => {
|
|
92
|
+
try {
|
|
93
|
+
await registry.executeCommand('edit', provider);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(chalk.red('❌ 编辑失败:'), error.message);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Parse arguments
|
|
101
|
+
program.parse();
|
package/bin/cc.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { main } = require('../src/index');
|
|
6
|
+
const { registry } = require('../src/CommandRegistry');
|
|
7
|
+
const pkg = require('../package.json');
|
|
8
|
+
const { checkForUpdates } = require('../src/utils/update-checker');
|
|
9
|
+
|
|
10
|
+
// Set up CLI
|
|
11
|
+
program
|
|
12
|
+
.name('akm')
|
|
13
|
+
.description('API密钥管理工具 - Manage and switch multiple API provider configurations')
|
|
14
|
+
.version(pkg.version, '-v, -V, --version', '显示版本号');
|
|
15
|
+
|
|
16
|
+
// Check for updates before any command runs
|
|
17
|
+
program.hook('preAction', async () => {
|
|
18
|
+
await checkForUpdates({ packageName: pkg.name, currentVersion: pkg.version });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Default command - show provider selection
|
|
22
|
+
program
|
|
23
|
+
.argument('[provider]', '直接切换到指定供应商')
|
|
24
|
+
.action(async (provider) => {
|
|
25
|
+
try {
|
|
26
|
+
await main(provider);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(chalk.red('❌ 执行失败:'), error.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Add command
|
|
34
|
+
program
|
|
35
|
+
.command('add')
|
|
36
|
+
.description('添加新供应商配置')
|
|
37
|
+
.action(async () => {
|
|
38
|
+
try {
|
|
39
|
+
await registry.executeCommand('add');
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(chalk.red('❌ 添加失败:'), error.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Remove command
|
|
47
|
+
program
|
|
48
|
+
.command('remove')
|
|
49
|
+
.argument('[provider]', '要删除的供应商名称')
|
|
50
|
+
.description('删除供应商配置')
|
|
51
|
+
.action(async (provider) => {
|
|
52
|
+
try {
|
|
53
|
+
await registry.executeCommand('remove', provider);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(chalk.red('❌ 删除失败:'), error.message);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// List command
|
|
61
|
+
program
|
|
62
|
+
.command('list')
|
|
63
|
+
.description('列出所有供应商')
|
|
64
|
+
.action(async () => {
|
|
65
|
+
try {
|
|
66
|
+
await registry.executeCommand('list');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(chalk.red('❌ 列表失败:'), error.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Current command
|
|
74
|
+
program
|
|
75
|
+
.command('current')
|
|
76
|
+
.description('显示当前配置')
|
|
77
|
+
.action(async () => {
|
|
78
|
+
try {
|
|
79
|
+
await registry.executeCommand('current');
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error(chalk.red('❌ 获取当前配置失败:'), error.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Edit command
|
|
87
|
+
program
|
|
88
|
+
.command('edit')
|
|
89
|
+
.argument('[provider]', '要编辑的供应商名称')
|
|
90
|
+
.description('编辑供应商配置')
|
|
91
|
+
.action(async (provider) => {
|
|
92
|
+
try {
|
|
93
|
+
await registry.executeCommand('edit', provider);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(chalk.red('❌ 编辑失败:'), error.message);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Parse arguments
|
|
101
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pikecode/api-key-manager",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A CLI tool for managing and switching multiple API provider configurations",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"akm": "bin/akm.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node bin/akm.js",
|
|
17
|
+
"dev": "nodemon bin/akm.js",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"build": "echo 'No build needed'",
|
|
20
|
+
"prepare": "echo 'Preparing package'",
|
|
21
|
+
"prepublishOnly": "npm test",
|
|
22
|
+
"release": "npm version patch && npm publish --access public"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"api",
|
|
26
|
+
"api-key",
|
|
27
|
+
"key-manager",
|
|
28
|
+
"authentication",
|
|
29
|
+
"anthropic",
|
|
30
|
+
"anthropic-api-key",
|
|
31
|
+
"anthropic-auth-token",
|
|
32
|
+
"environment",
|
|
33
|
+
"config",
|
|
34
|
+
"provider",
|
|
35
|
+
"switcher",
|
|
36
|
+
"cli",
|
|
37
|
+
"command-line",
|
|
38
|
+
"tool"
|
|
39
|
+
],
|
|
40
|
+
"author": "pikecode",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/pikecode/api-key-manager.git"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/pikecode/api-key-manager/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/pikecode/api-key-manager#readme",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@anthropic-ai/sdk": "^0.67.0",
|
|
52
|
+
"chalk": "^4.1.2",
|
|
53
|
+
"commander": "^9.4.1",
|
|
54
|
+
"cross-spawn": "^7.0.3",
|
|
55
|
+
"fs-extra": "^11.1.1",
|
|
56
|
+
"inquirer": "^8.2.6",
|
|
57
|
+
"supports-color": "^9.4.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"jest": "^29.7.0",
|
|
61
|
+
"nodemon": "^3.0.1"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=14.0.0"
|
|
65
|
+
},
|
|
66
|
+
"os": [
|
|
67
|
+
"win32",
|
|
68
|
+
"darwin",
|
|
69
|
+
"linux"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class CommandRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.commands = new Map();
|
|
4
|
+
this.lazyCommands = new Map();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// 注册懒加载命令
|
|
8
|
+
registerLazy(name, loader) {
|
|
9
|
+
this.lazyCommands.set(name, loader);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 获取命令(懒加载)
|
|
13
|
+
async getCommand(name) {
|
|
14
|
+
if (this.commands.has(name)) {
|
|
15
|
+
return this.commands.get(name);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (this.lazyCommands.has(name)) {
|
|
19
|
+
const loader = this.lazyCommands.get(name);
|
|
20
|
+
const command = await loader();
|
|
21
|
+
this.commands.set(name, command);
|
|
22
|
+
return command;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error(`命令 '${name}' 未注册`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 执行命令
|
|
29
|
+
async executeCommand(name, ...args) {
|
|
30
|
+
const command = await this.getCommand(name);
|
|
31
|
+
return await command(...args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 清理所有缓存的命令
|
|
35
|
+
clear() {
|
|
36
|
+
this.commands.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 单例实例
|
|
41
|
+
const registry = new CommandRegistry();
|
|
42
|
+
|
|
43
|
+
// 注册所有懒加载命令
|
|
44
|
+
registry.registerLazy('switch', async () => {
|
|
45
|
+
const { switchCommand } = require('./commands/switch');
|
|
46
|
+
return switchCommand;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
registry.registerLazy('add', async () => {
|
|
50
|
+
const { addCommand } = require('./commands/add');
|
|
51
|
+
return addCommand;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
registry.registerLazy('remove', async () => {
|
|
55
|
+
const { removeCommand } = require('./commands/remove');
|
|
56
|
+
return removeCommand;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
registry.registerLazy('list', async () => {
|
|
60
|
+
const { listCommand } = require('./commands/list');
|
|
61
|
+
return listCommand;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
registry.registerLazy('current', async () => {
|
|
65
|
+
const { currentCommand } = require('./commands/current');
|
|
66
|
+
return currentCommand;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
registry.registerLazy('edit', async () => {
|
|
70
|
+
const { editCommand } = require('./commands/edit');
|
|
71
|
+
return editCommand;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
module.exports = { CommandRegistry, registry };
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const { EscNavigationManager } = require('../navigation/EscNavigationManager');
|
|
4
|
+
const { Logger } = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
// 允许在 allowEmpty 启用时提交空字符串,而不是回退到默认值
|
|
7
|
+
const resolveInputPrompt = () => {
|
|
8
|
+
const promptFromModule = inquirer.prompt && inquirer.prompt.prompts && inquirer.prompt.prompts.input;
|
|
9
|
+
if (promptFromModule) {
|
|
10
|
+
return promptFromModule;
|
|
11
|
+
}
|
|
12
|
+
const promptFromRoot = inquirer.prompts && inquirer.prompts.input;
|
|
13
|
+
if (promptFromRoot) {
|
|
14
|
+
return promptFromRoot;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return require('inquirer/lib/prompts/input');
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const InputPrompt = resolveInputPrompt();
|
|
24
|
+
if (InputPrompt && !InputPrompt.prototype.__allowEmptyPatched) {
|
|
25
|
+
const originalFilterInput = InputPrompt.prototype.filterInput;
|
|
26
|
+
const originalRun = InputPrompt.prototype._run;
|
|
27
|
+
|
|
28
|
+
InputPrompt.prototype.filterInput = function patchedFilterInput(input) {
|
|
29
|
+
if (this.opt && this.opt.allowEmpty && this.status === 'touched' && input === '') {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
return originalFilterInput.call(this, input);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
InputPrompt.prototype._run = function patchedRun(cb) {
|
|
36
|
+
const result = originalRun.call(this, cb);
|
|
37
|
+
|
|
38
|
+
if (!this.__defaultPrefilled && this.opt && this.opt.prefillDefault) {
|
|
39
|
+
const defaultValue = this.opt.default;
|
|
40
|
+
if (defaultValue !== undefined && defaultValue !== null) {
|
|
41
|
+
const text = String(defaultValue);
|
|
42
|
+
if (text.length > 0) {
|
|
43
|
+
this.__defaultPrefilled = true;
|
|
44
|
+
this.status = 'touched';
|
|
45
|
+
this.rl.write(text);
|
|
46
|
+
this.rl.cursor = text.length;
|
|
47
|
+
this.render();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
InputPrompt.prototype.__allowEmptyPatched = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ESC_CANCELLED_ERROR_CODE = 'ESC_CANCELLED';
|
|
59
|
+
|
|
60
|
+
class BaseCommand {
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
const input = options.input || process.stdin;
|
|
63
|
+
this.escManager = new EscNavigationManager(input);
|
|
64
|
+
this.activePrompt = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
isEscCancelled(error) {
|
|
68
|
+
return Boolean(error && error.code === ESC_CANCELLED_ERROR_CODE);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async prompt(questions) {
|
|
72
|
+
const promptPromise = inquirer.prompt(questions);
|
|
73
|
+
let settled = false;
|
|
74
|
+
|
|
75
|
+
return await new Promise((resolve, reject) => {
|
|
76
|
+
const cleanup = () => {
|
|
77
|
+
if (this.activePrompt && this.activePrompt.promise === promptPromise) {
|
|
78
|
+
this.activePrompt = null;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const cancel = () => {
|
|
83
|
+
if (settled) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
settled = true;
|
|
87
|
+
if (promptPromise.ui && typeof promptPromise.ui.close === 'function') {
|
|
88
|
+
promptPromise.ui.close();
|
|
89
|
+
}
|
|
90
|
+
cleanup();
|
|
91
|
+
const error = new Error('操作已通过 ESC 取消');
|
|
92
|
+
error.code = ESC_CANCELLED_ERROR_CODE;
|
|
93
|
+
reject(error);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
this.activePrompt = {
|
|
97
|
+
promise: promptPromise,
|
|
98
|
+
cancel
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
promptPromise
|
|
102
|
+
.then((answers) => {
|
|
103
|
+
if (settled) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
settled = true;
|
|
107
|
+
cleanup();
|
|
108
|
+
resolve(answers);
|
|
109
|
+
})
|
|
110
|
+
.catch((error) => {
|
|
111
|
+
if (settled) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
settled = true;
|
|
115
|
+
cleanup();
|
|
116
|
+
reject(error);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cancelActivePrompt() {
|
|
122
|
+
if (this.activePrompt && typeof this.activePrompt.cancel === 'function') {
|
|
123
|
+
this.activePrompt.cancel();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
createESCListener(callback, returnMessage = '返回上级菜单', options = {}) {
|
|
128
|
+
if (!this.escManager || !this.escManager.isSupported()) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const handler = this.escManager.register({
|
|
133
|
+
once: options.once !== false,
|
|
134
|
+
postDelay: typeof options.postDelay === 'number' ? options.postDelay : undefined,
|
|
135
|
+
onTrigger: () => {
|
|
136
|
+
this.cancelActivePrompt();
|
|
137
|
+
this.clearScreen();
|
|
138
|
+
if (returnMessage) {
|
|
139
|
+
console.log(chalk.yellow(`🔙 ESC键 - ${returnMessage}`));
|
|
140
|
+
console.log();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (typeof callback === 'function') {
|
|
144
|
+
const delay = typeof options.callbackDelay === 'number' ? options.callbackDelay : 50;
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
try {
|
|
147
|
+
const result = callback();
|
|
148
|
+
if (result && typeof result.catch === 'function') {
|
|
149
|
+
result.catch((error) => {
|
|
150
|
+
if (!this.isEscCancelled(error)) {
|
|
151
|
+
Logger.error(`ESC回退回调执行失败: ${error.message}`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!this.isEscCancelled(error)) {
|
|
157
|
+
Logger.error(`ESC回退回调执行失败: ${error.message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}, delay);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return handler;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
clearScreen() {
|
|
169
|
+
const clearSequence = process.platform === 'win32'
|
|
170
|
+
? '\x1b[3J\x1b[2J\x1b[0f'
|
|
171
|
+
: '\x1b[3J\x1b[2J\x1b[H';
|
|
172
|
+
process.stdout.write(clearSequence);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
removeESCListener(listener) {
|
|
176
|
+
if (!listener || !this.escManager) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.escManager.unregister(listener);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
cleanupAllListeners() {
|
|
184
|
+
if (this.escManager) {
|
|
185
|
+
this.escManager.reset();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async handleError(error, context) {
|
|
190
|
+
if (this.isEscCancelled(error)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
Logger.error(`${context}失败: ${error.message}`);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async safeExecute(operation, context = '操作') {
|
|
198
|
+
try {
|
|
199
|
+
return await operation();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
await this.handleError(error, context);
|
|
202
|
+
} finally {
|
|
203
|
+
this.cleanupAllListeners();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
destroy() {
|
|
208
|
+
this.cleanupAllListeners();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = { BaseCommand, ESC_CANCELLED_ERROR_CODE };
|