@kikkimo/claude-launcher 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/CHANGELOG.md +57 -0
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/claude-launcher +679 -0
- package/docs/README-zh.md +177 -0
- package/package.json +56 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-07-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release of Claude Launcher
|
|
12
|
+
- Interactive menu with Claude-style orange/amber interface
|
|
13
|
+
- Arrow key navigation with fallback to number selection
|
|
14
|
+
- **AES-256-CBC Encryption**: Industry-standard encryption for API keys
|
|
15
|
+
- **Machine-specific Encryption Keys**: Keys derived from machine-specific data
|
|
16
|
+
- **Interactive API Key Setup**: Guided setup with copy/paste support and validation
|
|
17
|
+
- **Retry Logic**: Allow users to re-enter invalid API keys without restarting
|
|
18
|
+
- **Clean Process Handoff**: Claude runs in current terminal with clean environment
|
|
19
|
+
- **Enhanced Error Handling**: Improved error messages and recovery mechanisms
|
|
20
|
+
- **Security Explanations**: Clear explanations of encryption and local storage
|
|
21
|
+
- Multiple Claude Code launch options:
|
|
22
|
+
- Standard launch
|
|
23
|
+
- Skip permissions mode
|
|
24
|
+
- Kimi K2 API integration
|
|
25
|
+
- Combined Kimi API with skip permissions
|
|
26
|
+
- Smart configuration file detection across multiple locations
|
|
27
|
+
- Cross-platform support (Windows, macOS, Linux)
|
|
28
|
+
- Global npm installation support
|
|
29
|
+
- Automatic config file creation with sensible defaults
|
|
30
|
+
- Configuration template file (`claude-launcher-template.env`) for easy setup
|
|
31
|
+
- Enhanced configuration workflow with template-based initialization
|
|
32
|
+
- Multilingual documentation support (English and Chinese)
|
|
33
|
+
- Standardized configuration file naming (`.claude-launcher.env`)
|
|
34
|
+
- Beautiful Claude-style terminal interface
|
|
35
|
+
- Encrypted credential storage
|
|
36
|
+
- Multi-platform TTY/non-TTY environment support
|
|
37
|
+
- Comprehensive error handling and user feedback
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- **Simplified Configuration**: Automatic config file creation on first run
|
|
41
|
+
- **Improved Input Handling**: Fixed paste support and character duplication issues
|
|
42
|
+
- **Updated Node.js Requirement**: Minimum Node.js version updated to 20.0.0
|
|
43
|
+
- **Modernized Documentation**: Complete rewrite with Quick Start guide and better structure
|
|
44
|
+
- **Enhanced User Experience**: Clearer prompts and instructions throughout the interface
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- **Input System Overhaul**: Resolved memory leaks and EventEmitter issues
|
|
48
|
+
- **API Key Validation**: Fixed hanging input and validation problems
|
|
49
|
+
- **Process Management**: Resolved issues with Claude not starting in current terminal
|
|
50
|
+
- **Cross-platform Compatibility**: Fixed Windows-specific launching issues
|
|
51
|
+
- **Terminal State Management**: Proper cleanup of terminal settings before Claude launch
|
|
52
|
+
|
|
53
|
+
### Security
|
|
54
|
+
- **Encrypted Storage**: API keys encrypted with AES-256-CBC instead of legacy methods
|
|
55
|
+
- **Local-only Decryption**: Encrypted keys cannot be decrypted on other machines
|
|
56
|
+
- **Secure Input**: Plaintext input with proper validation and error handling
|
|
57
|
+
- **Machine Binding**: Encryption keys tied to specific machine characteristics
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 FangYi
|
|
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,177 @@
|
|
|
1
|
+
# Claude Launcher
|
|
2
|
+
|
|
3
|
+
An elegant interactive launcher for Claude Code with a beautiful Claude-style interface. Launch Claude Code with various configurations through an intuitive command-line menu.
|
|
4
|
+
|
|
5
|
+
## 📖 Documentation
|
|
6
|
+
|
|
7
|
+
- [English](README.md) (Current)
|
|
8
|
+
- [中文文档](docs/README-zh.md)
|
|
9
|
+
|
|
10
|
+
## ✨ Features
|
|
11
|
+
|
|
12
|
+
- 🎨 **Claude-style interface** with authentic orange/amber color scheme
|
|
13
|
+
- ⌨️ **Arrow key navigation** with fallback to number selection
|
|
14
|
+
- 🔐 **Encrypted API key storage** using AES-256-CBC encryption
|
|
15
|
+
- 🚀 **Multiple launch options** including permission skipping and Kimi K2 API
|
|
16
|
+
- 🌍 **Global installation** - use `claude-launcher` from anywhere
|
|
17
|
+
- 🔧 **Smart configuration** - automatically finds/creates config files
|
|
18
|
+
- 💻 **Cross-platform** - Windows, macOS, and Linux support
|
|
19
|
+
|
|
20
|
+
## 🚀 Quick Start
|
|
21
|
+
|
|
22
|
+
1. **Install globally:**
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @kikkimo/claude-launcher
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. **Run the launcher:**
|
|
28
|
+
```bash
|
|
29
|
+
claude-launcher
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. **For Kimi API users:** When prompted for the first time, enter your Kimi API key starting with `sk-`
|
|
33
|
+
|
|
34
|
+
That's it! The launcher will guide you through the setup process.
|
|
35
|
+
|
|
36
|
+
## 📦 Installation
|
|
37
|
+
|
|
38
|
+
### Global Installation (Recommended)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g @kikkimo/claude-launcher
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
After installation, you can run `claude-launcher` from any directory.
|
|
45
|
+
|
|
46
|
+
### Local Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/kikkimo/claude-launcher.git
|
|
50
|
+
cd claude-launcher
|
|
51
|
+
npm install
|
|
52
|
+
node claude-launcher
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 🎮 Usage
|
|
56
|
+
|
|
57
|
+
### Available Launch Options
|
|
58
|
+
|
|
59
|
+
1. **Launch Claude Code** - Standard Claude Code launch
|
|
60
|
+
2. **Launch Claude Code (Skip Permissions)** - Launch with `--dangerously-skip-permissions`
|
|
61
|
+
3. **Launch Claude Code with Kimi K2 API** - Use Kimi API with encrypted storage
|
|
62
|
+
4. **Launch Claude Code with Kimi K2 API (Skip Permissions)** - Combine Kimi API with permission skipping
|
|
63
|
+
5. **Exit** - Close the launcher
|
|
64
|
+
|
|
65
|
+
### Interactive Navigation
|
|
66
|
+
|
|
67
|
+
- **Arrow Keys**: Use ↑↓ to navigate, Enter to select (in TTY environments)
|
|
68
|
+
- **Number Selection**: Type 1-5 and press Enter (in non-TTY environments)
|
|
69
|
+
- **Quick Exit**: Press Esc or Q to quit anytime
|
|
70
|
+
|
|
71
|
+
### Example Session
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
$ claude-launcher
|
|
75
|
+
|
|
76
|
+
┌────────────────────────────────────────┐
|
|
77
|
+
│ Claude Code Launcher │
|
|
78
|
+
└────────────────────────────────────────┘
|
|
79
|
+
|
|
80
|
+
Use ↑↓ arrow keys to navigate, Enter to select
|
|
81
|
+
|
|
82
|
+
→ Launch Claude Code
|
|
83
|
+
Launch Claude Code (Skip Permissions)
|
|
84
|
+
Launch Claude Code with Kimi K2 API
|
|
85
|
+
Launch Claude Code with Kimi K2 API (Skip Permissions)
|
|
86
|
+
Exit
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## ⚙️ Configuration
|
|
90
|
+
|
|
91
|
+
### Automatic Configuration
|
|
92
|
+
|
|
93
|
+
On first run, if you select a Kimi API option and no configuration exists, the launcher will:
|
|
94
|
+
|
|
95
|
+
1. Automatically create a configuration file at `~/.claude-launcher.env`
|
|
96
|
+
2. Guide you through entering your Kimi API key
|
|
97
|
+
3. Encrypt and store your API key securely using machine-specific encryption
|
|
98
|
+
|
|
99
|
+
### Manual Configuration
|
|
100
|
+
|
|
101
|
+
If you prefer to set up manually, the config file locations are searched in this order:
|
|
102
|
+
|
|
103
|
+
1. `.claude-launcher.env` in current directory
|
|
104
|
+
2. `.claude-launcher.env` in your home directory
|
|
105
|
+
3. `.claude-launcher.env` in the installation directory
|
|
106
|
+
|
|
107
|
+
### Configuration File Format
|
|
108
|
+
|
|
109
|
+
```env
|
|
110
|
+
KIMI_API_KEY=your_encrypted_api_key_here
|
|
111
|
+
KIMI_BASE_URL=https://api.moonshot.cn/anthropic/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Note**: The `KIMI_API_KEY` is automatically encrypted when entered through the launcher. Do not manually edit encrypted values.
|
|
115
|
+
|
|
116
|
+
### Security Features
|
|
117
|
+
|
|
118
|
+
- **AES-256-CBC Encryption**: API keys are encrypted using industry-standard encryption
|
|
119
|
+
- **Machine-specific Keys**: Encryption keys are derived from machine-specific data
|
|
120
|
+
- **Local Storage Only**: Encrypted keys cannot be decrypted on other machines
|
|
121
|
+
- **Secure Input**: API key input supports copy/paste and validation
|
|
122
|
+
|
|
123
|
+
## 📋 Requirements
|
|
124
|
+
|
|
125
|
+
- **Node.js**: 20.0.0 or higher
|
|
126
|
+
- **Claude Code**: Installed and accessible via `claude` command
|
|
127
|
+
- **Terminal**: Any modern terminal with Node.js support
|
|
128
|
+
|
|
129
|
+
## 🔧 Development
|
|
130
|
+
|
|
131
|
+
### Building from Source
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git clone https://github.com/kikkimo/claude-launcher.git
|
|
135
|
+
cd claude-launcher
|
|
136
|
+
npm install
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Testing Locally
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm start
|
|
143
|
+
# or
|
|
144
|
+
node claude-launcher
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 🤝 Contributing
|
|
148
|
+
|
|
149
|
+
We welcome contributions! Please follow these steps:
|
|
150
|
+
|
|
151
|
+
1. Fork the repository
|
|
152
|
+
2. Create your feature branch: `git checkout -b feature/amazing-feature`
|
|
153
|
+
3. Commit your changes: `git commit -m 'Add amazing feature'`
|
|
154
|
+
4. Push to the branch: `git push origin feature/amazing-feature`
|
|
155
|
+
5. Open a Pull Request
|
|
156
|
+
|
|
157
|
+
## 📄 License
|
|
158
|
+
|
|
159
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
160
|
+
|
|
161
|
+
## 🙏 Acknowledgments
|
|
162
|
+
|
|
163
|
+
- Inspired by the beautiful design of Claude Code
|
|
164
|
+
- Built with ❤️ for the Claude Code community
|
|
165
|
+
- Thanks to all contributors and users
|
|
166
|
+
|
|
167
|
+
## 🐛 Issues & Support
|
|
168
|
+
|
|
169
|
+
If you encounter any issues or have questions:
|
|
170
|
+
|
|
171
|
+
1. Check existing [Issues](https://github.com/kikkimo/claude-launcher/issues)
|
|
172
|
+
2. Create a new issue with detailed information
|
|
173
|
+
3. Include your operating system, Node.js version, and error messages
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
**Note**: This launcher is designed to work with Claude Code and Kimi K2 API. Make sure you have Claude Code installed before using this tool.
|
package/claude-launcher
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
// Get config file location - check multiple locations for global installation
|
|
10
|
+
function getConfigPath() {
|
|
11
|
+
const locations = [
|
|
12
|
+
// Current working directory (highest priority)
|
|
13
|
+
path.join(process.cwd(), '.claude-launcher.env'),
|
|
14
|
+
// User home directory
|
|
15
|
+
path.join(require('os').homedir(), '.claude-launcher.env'),
|
|
16
|
+
// Script directory (for local installation)
|
|
17
|
+
path.join(__dirname, '.claude-launcher.env')
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
// Return the first existing config file, or the home directory path as default
|
|
21
|
+
for (const location of locations) {
|
|
22
|
+
if (fs.existsSync(location)) {
|
|
23
|
+
return location;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Default to home directory if no config exists
|
|
28
|
+
return path.join(require('os').homedir(), '.claude-launcher.env');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CONFIG_FILE = getConfigPath();
|
|
32
|
+
|
|
33
|
+
// ANSI color codes for Claude-style theming
|
|
34
|
+
const colors = {
|
|
35
|
+
reset: '\x1b[0m',
|
|
36
|
+
bright: '\x1b[1m',
|
|
37
|
+
orange: '\x1b[38;5;208m', // Claude brand orange
|
|
38
|
+
amber: '\x1b[38;5;214m', // Amber/yellow-orange
|
|
39
|
+
white: '\x1b[37m',
|
|
40
|
+
gray: '\x1b[90m',
|
|
41
|
+
green: '\x1b[32m',
|
|
42
|
+
red: '\x1b[31m',
|
|
43
|
+
yellow: '\x1b[33m',
|
|
44
|
+
black: '\x1b[30m',
|
|
45
|
+
bgOrange: '\x1b[48;5;208m', // Background orange
|
|
46
|
+
bgAmber: '\x1b[48;5;214m' // Background amber
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Generate encryption key from machine-specific data
|
|
50
|
+
function getEncryptionKey() {
|
|
51
|
+
const os = require('os');
|
|
52
|
+
// Use a combination of hostname and user info to create a machine-specific key
|
|
53
|
+
const machineId = os.hostname() + os.userInfo().username + os.platform();
|
|
54
|
+
// Derive a 32-byte key using PBKDF2
|
|
55
|
+
return crypto.pbkdf2Sync(machineId, 'claude-launcher-salt', 10000, 32, 'sha256');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Encrypt API key using AES-256-CBC
|
|
59
|
+
function encryptApiKey(plaintext) {
|
|
60
|
+
try {
|
|
61
|
+
const key = getEncryptionKey();
|
|
62
|
+
const iv = crypto.randomBytes(16); // Generate random IV
|
|
63
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
|
64
|
+
|
|
65
|
+
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
|
|
66
|
+
encrypted += cipher.final('hex');
|
|
67
|
+
|
|
68
|
+
// Combine IV and encrypted data
|
|
69
|
+
const result = iv.toString('hex') + ':' + encrypted;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
value: result,
|
|
74
|
+
error: null
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
value: null,
|
|
80
|
+
error: error.message
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Decrypt API key using AES-256-CBC
|
|
86
|
+
function decryptApiKey(encryptedData) {
|
|
87
|
+
try {
|
|
88
|
+
const key = getEncryptionKey();
|
|
89
|
+
|
|
90
|
+
// Split IV and encrypted data
|
|
91
|
+
const parts = encryptedData.split(':');
|
|
92
|
+
if (parts.length !== 2) {
|
|
93
|
+
throw new Error('Invalid encrypted data format');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
97
|
+
const encrypted = parts[1];
|
|
98
|
+
|
|
99
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
100
|
+
|
|
101
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
102
|
+
decrypted += decipher.final('utf8');
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
value: decrypted,
|
|
107
|
+
error: null
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
value: null,
|
|
113
|
+
error: error.message
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Validate API key format
|
|
119
|
+
function validateApiKey(apiKey) {
|
|
120
|
+
if (!apiKey || apiKey.trim() === '') {
|
|
121
|
+
return {
|
|
122
|
+
valid: false,
|
|
123
|
+
error: 'API key is empty or missing'
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!apiKey.startsWith('sk-')) {
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
error: 'API key must start with "sk-"'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (apiKey.length < 20) {
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
error: 'API key appears to be too short'
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
valid: true,
|
|
143
|
+
error: null,
|
|
144
|
+
value: apiKey
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Load configuration from .env file
|
|
149
|
+
function loadConfig() {
|
|
150
|
+
const config = {};
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
154
|
+
const envContent = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
155
|
+
const lines = envContent.split('\n');
|
|
156
|
+
|
|
157
|
+
lines.forEach(line => {
|
|
158
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
159
|
+
if (match) {
|
|
160
|
+
const key = match[1];
|
|
161
|
+
let value = match[2];
|
|
162
|
+
|
|
163
|
+
// Store raw value for KIMI_API_KEY (validation will be done later)
|
|
164
|
+
// Don't decrypt here to allow proper error handling
|
|
165
|
+
|
|
166
|
+
config[key] = value;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
console.log(colors.yellow + 'Warning: .claude-launcher.env file not found!' + colors.reset);
|
|
171
|
+
console.log(colors.gray + `Searched locations:` + colors.reset);
|
|
172
|
+
console.log(colors.gray + ` - ${path.join(process.cwd(), '.claude-launcher.env')} (current directory)` + colors.reset);
|
|
173
|
+
console.log(colors.gray + ` - ${path.join(require('os').homedir(), '.claude-launcher.env')} (home directory)` + colors.reset);
|
|
174
|
+
console.log(colors.gray + `Creating default config at: ${CONFIG_FILE}` + colors.reset);
|
|
175
|
+
|
|
176
|
+
// Try to read template file, fallback to hardcoded defaults
|
|
177
|
+
let defaultConfig = `KIMI_API_KEY=Your_Double_Base64_Encoded_Api_Key
|
|
178
|
+
KIMI_BASE_URL=https://api.moonshot.cn/anthropic/`;
|
|
179
|
+
|
|
180
|
+
const templatePath = path.join(__dirname, 'claude-launcher-template.env');
|
|
181
|
+
try {
|
|
182
|
+
if (fs.existsSync(templatePath)) {
|
|
183
|
+
defaultConfig = fs.readFileSync(templatePath, 'utf8');
|
|
184
|
+
console.log(colors.gray + `Using template from: ${templatePath}` + colors.reset);
|
|
185
|
+
}
|
|
186
|
+
} catch (templateError) {
|
|
187
|
+
console.log(colors.yellow + 'Template file not found, using defaults' + colors.reset);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
fs.writeFileSync(CONFIG_FILE, defaultConfig);
|
|
192
|
+
console.log(colors.green + 'Default configuration file created successfully!' + colors.reset);
|
|
193
|
+
console.log(colors.gray + 'Please edit the file to add your actual API credentials.' + colors.reset);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.log(colors.red + 'Failed to create config file: ' + error.message + colors.reset);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
config.KIMI_API_KEY = 'Your_Double_Base64_Encoded_Api_Key';
|
|
199
|
+
config.KIMI_BASE_URL = 'https://api.moonshot.cn/anthropic/';
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.log(colors.red + 'Error loading configuration: ' + error.message + colors.reset);
|
|
203
|
+
config.KIMI_API_KEY = 'Your_Double_Base64_Encoded_Api_Key';
|
|
204
|
+
config.KIMI_BASE_URL = 'https://api.moonshot.cn/anthropic/';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return config;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Launch Claude Code function with clean environment handoff
|
|
211
|
+
function launchClaude(command, envVars = {}, disableAuthTokens = false) {
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(colors.yellow + 'Starting Claude Code...' + colors.reset);
|
|
214
|
+
console.log(colors.gray + `Command: ${command}` + colors.reset);
|
|
215
|
+
|
|
216
|
+
if (Object.keys(envVars).length > 0) {
|
|
217
|
+
console.log(colors.gray + 'Environment variables:' + colors.reset);
|
|
218
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
219
|
+
if (key === 'ANTHROPIC_API_KEY') {
|
|
220
|
+
console.log(colors.gray + ` ${key}=***` + colors.reset);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(colors.gray + ` ${key}=${value}` + colors.reset);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log('');
|
|
228
|
+
console.log(colors.green + '[+] Claude will run in current terminal.' + colors.reset);
|
|
229
|
+
console.log(colors.gray + ' Launcher will exit to transfer control to Claude.' + colors.reset);
|
|
230
|
+
console.log('');
|
|
231
|
+
|
|
232
|
+
// Prepare clean environment
|
|
233
|
+
const env = { ...process.env, ...envVars };
|
|
234
|
+
|
|
235
|
+
// Disable conflicting auth tokens when using Kimi API
|
|
236
|
+
if (disableAuthTokens) {
|
|
237
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
238
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
239
|
+
console.log(colors.gray + ' Disabled: ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN' + colors.reset);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Parse command and arguments
|
|
243
|
+
const args = command.split(' ');
|
|
244
|
+
const cmd = args.shift();
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
// Clean up terminal state before launching Claude
|
|
248
|
+
if (process.stdin.isTTY) {
|
|
249
|
+
process.stdin.setRawMode(false);
|
|
250
|
+
process.stdin.pause();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Remove all event listeners to avoid conflicts
|
|
254
|
+
process.stdin.removeAllListeners('data');
|
|
255
|
+
process.stdin.removeAllListeners('keypress');
|
|
256
|
+
process.removeAllListeners('SIGINT');
|
|
257
|
+
process.removeAllListeners('SIGTERM');
|
|
258
|
+
|
|
259
|
+
// Launch Claude in current terminal, let it inherit everything
|
|
260
|
+
const child = spawn(cmd, args, {
|
|
261
|
+
stdio: 'inherit', // Claude takes over current terminal I/O
|
|
262
|
+
env: env,
|
|
263
|
+
cwd: process.cwd(),
|
|
264
|
+
shell: true // Use shell to find the command
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Don't exit immediately, wait for Claude to exit then exit launcher
|
|
268
|
+
child.on('close', (code) => {
|
|
269
|
+
process.exit(code || 0);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
child.on('error', (error) => {
|
|
273
|
+
console.log(colors.red + 'Error running Claude: ' + error.message + colors.reset);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.log(colors.red + 'Error launching Claude Code: ' + error.message + colors.reset);
|
|
279
|
+
console.log(colors.gray + 'Press any key to return to menu...' + colors.reset);
|
|
280
|
+
process.stdin.setRawMode(true);
|
|
281
|
+
process.stdin.resume();
|
|
282
|
+
process.stdin.once('data', () => {
|
|
283
|
+
process.stdin.setRawMode(false);
|
|
284
|
+
showMenu();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Simple input using readline (supports paste naturally)
|
|
290
|
+
function simpleInput(prompt) {
|
|
291
|
+
return new Promise((resolve) => {
|
|
292
|
+
const rl = readline.createInterface({
|
|
293
|
+
input: process.stdin,
|
|
294
|
+
output: process.stdout
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
rl.question(prompt, (answer) => {
|
|
298
|
+
rl.close();
|
|
299
|
+
resolve(answer.trim());
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Prompt user to input API key with simple readline
|
|
305
|
+
async function promptForApiKey() {
|
|
306
|
+
try {
|
|
307
|
+
console.clear();
|
|
308
|
+
console.log('');
|
|
309
|
+
console.log(colors.bright + colors.orange + '[*] Kimi API Key Setup' + colors.reset);
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(colors.yellow + '[!] This message appears because you have not set up a Kimi API key,' + colors.reset);
|
|
312
|
+
console.log(colors.yellow + ' or the API key decrypted from your local config file is invalid.' + colors.reset);
|
|
313
|
+
console.log('');
|
|
314
|
+
console.log(colors.yellow + '[?] Why do you need to enter your API key?' + colors.reset);
|
|
315
|
+
console.log(colors.gray + ' • Kimi K2 API provides Claude-compatible interface' + colors.reset);
|
|
316
|
+
console.log(colors.gray + ' • Your API key enables access to Kimi\'s AI services' + colors.reset);
|
|
317
|
+
console.log(colors.bright + colors.green + ' • The key will be encrypted and stored locally' + colors.reset);
|
|
318
|
+
console.log(colors.bright + colors.green + ' • Only accessible on this machine' + colors.reset);
|
|
319
|
+
console.log('');
|
|
320
|
+
console.log(colors.yellow + '[!] Security:' + colors.reset);
|
|
321
|
+
console.log(colors.gray + ' • API key is encrypted using AES-256-CBC' + colors.reset);
|
|
322
|
+
console.log(colors.gray + ' • Encryption key derived from machine-specific data' + colors.reset);
|
|
323
|
+
console.log(colors.gray + ' • Key cannot be decrypted on other machines' + colors.reset);
|
|
324
|
+
console.log('');
|
|
325
|
+
|
|
326
|
+
// Show input box (fixed spacing)
|
|
327
|
+
console.log(colors.orange + '┌─────────────────────────────────────────────────────────┐' + colors.reset);
|
|
328
|
+
console.log(colors.orange + '│' + colors.reset + ' ' + colors.bright + 'Enter your Kimi API key' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
329
|
+
console.log(colors.orange + '├─────────────────────────────────────────────────────────┤' + colors.reset);
|
|
330
|
+
console.log(colors.orange + '│' + colors.reset + ' ' + colors.gray + 'Format: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
331
|
+
console.log(colors.orange + '│' + colors.reset + ' ' + colors.gray + 'You can copy and paste your API key here' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
332
|
+
console.log(colors.orange + '│' + colors.reset + ' ' + colors.bright + colors.yellow + 'After entering, press ENTER to continue' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
333
|
+
console.log(colors.orange + '│' + colors.reset + ' ' + colors.gray + 'Type "exit" or "quit" to return to menu' + ' ' + colors.reset + colors.orange + '│' + colors.reset);
|
|
334
|
+
console.log(colors.orange + '└─────────────────────────────────────────────────────────┘' + colors.reset);
|
|
335
|
+
console.log('');
|
|
336
|
+
|
|
337
|
+
// Clean up any existing listeners once before the input loop
|
|
338
|
+
process.stdin.removeAllListeners('data');
|
|
339
|
+
process.stdin.removeAllListeners('keypress');
|
|
340
|
+
|
|
341
|
+
// Input loop - allow retry on invalid input
|
|
342
|
+
while (true) {
|
|
343
|
+
const apiKey = await simpleInput(colors.green + '[>] API Key (press ENTER after input): ' + colors.reset);
|
|
344
|
+
|
|
345
|
+
// Check for exit commands
|
|
346
|
+
if (apiKey.toLowerCase() === 'exit' || apiKey.toLowerCase() === 'quit') {
|
|
347
|
+
console.log('');
|
|
348
|
+
console.log(colors.yellow + '[!] Setup cancelled by user.' + colors.reset);
|
|
349
|
+
throw new Error('User cancelled setup');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Validate the entered API key
|
|
353
|
+
const validation = validateApiKey(apiKey);
|
|
354
|
+
|
|
355
|
+
if (!validation.valid) {
|
|
356
|
+
console.log(colors.red + '[X] Invalid API key: ' + validation.error + colors.reset);
|
|
357
|
+
console.log(colors.yellow + '[!] Please try again with a valid API key.' + colors.reset);
|
|
358
|
+
console.log('');
|
|
359
|
+
continue; // Ask again
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Encrypt and save the API key
|
|
363
|
+
const encrypted = encryptApiKey(apiKey);
|
|
364
|
+
|
|
365
|
+
if (!encrypted.success) {
|
|
366
|
+
console.log(colors.red + '[X] Failed to encrypt API key: ' + encrypted.error + colors.reset);
|
|
367
|
+
console.log(colors.yellow + '[!] Please try again.' + colors.reset);
|
|
368
|
+
console.log('');
|
|
369
|
+
continue; // Ask again
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Update configuration file
|
|
373
|
+
updateConfigFile('KIMI_API_KEY', encrypted.value);
|
|
374
|
+
|
|
375
|
+
console.log('');
|
|
376
|
+
console.log(colors.green + '[+] API key encrypted and saved successfully!' + colors.reset);
|
|
377
|
+
console.log(colors.gray + ' Configuration saved to: ' + CONFIG_FILE + colors.reset);
|
|
378
|
+
console.log('');
|
|
379
|
+
|
|
380
|
+
return apiKey;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
} catch (error) {
|
|
384
|
+
console.log(colors.red + '[X] Setup failed: ' + error.message + colors.reset);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Update configuration file with new value
|
|
390
|
+
function updateConfigFile(key, value) {
|
|
391
|
+
try {
|
|
392
|
+
let configContent = '';
|
|
393
|
+
let keyExists = false;
|
|
394
|
+
|
|
395
|
+
// Read existing config if it exists
|
|
396
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
397
|
+
configContent = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
398
|
+
const lines = configContent.split('\n');
|
|
399
|
+
|
|
400
|
+
// Update existing key or add new one
|
|
401
|
+
for (let i = 0; i < lines.length; i++) {
|
|
402
|
+
const match = lines[i].match(/^([^=]+)=(.*)$/);
|
|
403
|
+
if (match && match[1] === key) {
|
|
404
|
+
lines[i] = `${key}=${value}`;
|
|
405
|
+
keyExists = true;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (keyExists) {
|
|
411
|
+
configContent = lines.join('\n');
|
|
412
|
+
} else {
|
|
413
|
+
// Add new key at the end
|
|
414
|
+
configContent += `\n${key}=${value}`;
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
// Create new config file
|
|
418
|
+
configContent = `# Claude Launcher Configuration\n# Generated automatically\n\n${key}=${value}\nKIMI_BASE_URL=https://api.moonshot.cn/anthropic/\n`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
fs.writeFileSync(CONFIG_FILE, configContent);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.log(colors.red + 'Error updating config file: ' + error.message + colors.reset);
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Get Kimi configuration with validation and setup
|
|
429
|
+
async function getKimiConfig() {
|
|
430
|
+
try {
|
|
431
|
+
const config = loadConfig();
|
|
432
|
+
const rawApiKey = config.KIMI_API_KEY;
|
|
433
|
+
const baseUrl = config.KIMI_BASE_URL || 'https://api.moonshot.cn/anthropic/';
|
|
434
|
+
|
|
435
|
+
console.log('');
|
|
436
|
+
console.log(colors.bright + colors.orange + '[*] Validating Kimi API Configuration...' + colors.reset);
|
|
437
|
+
console.log('');
|
|
438
|
+
|
|
439
|
+
// Check if API key is configured and valid
|
|
440
|
+
if (!rawApiKey || rawApiKey === 'Your_Double_Base64_Encoded_Api_Key' || rawApiKey === 'your_kimi_api_key_here') {
|
|
441
|
+
console.log(colors.yellow + '[!] No API key configured. Starting first-time setup...' + colors.reset);
|
|
442
|
+
console.log('');
|
|
443
|
+
|
|
444
|
+
const apiKey = await promptForApiKey();
|
|
445
|
+
return {
|
|
446
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
447
|
+
ANTHROPIC_API_KEY: apiKey
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Try to decrypt existing API key
|
|
452
|
+
const decrypted = decryptApiKey(rawApiKey);
|
|
453
|
+
|
|
454
|
+
if (!decrypted.success) {
|
|
455
|
+
console.log(colors.red + '[X] Failed to decrypt stored API key: ' + decrypted.error + colors.reset);
|
|
456
|
+
console.log(colors.yellow + '[!] This might happen if the key was encrypted on a different machine.' + colors.reset);
|
|
457
|
+
console.log(colors.yellow + ' Please re-enter your API key...' + colors.reset);
|
|
458
|
+
console.log('');
|
|
459
|
+
|
|
460
|
+
const apiKey = await promptForApiKey();
|
|
461
|
+
return {
|
|
462
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
463
|
+
ANTHROPIC_API_KEY: apiKey
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Validate decrypted API key
|
|
468
|
+
const validation = validateApiKey(decrypted.value);
|
|
469
|
+
|
|
470
|
+
if (!validation.valid) {
|
|
471
|
+
console.log(colors.red + '[X] Stored API key is invalid: ' + validation.error + colors.reset);
|
|
472
|
+
console.log('');
|
|
473
|
+
|
|
474
|
+
const apiKey = await promptForApiKey();
|
|
475
|
+
return {
|
|
476
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
477
|
+
ANTHROPIC_API_KEY: apiKey
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Success case
|
|
482
|
+
console.log(colors.green + '[+] API Key validation successful!' + colors.reset);
|
|
483
|
+
console.log(colors.gray + ` Decrypted key starts with: ${validation.value.substring(0, 8)}...` + colors.reset);
|
|
484
|
+
console.log(colors.gray + ` Base URL: ${baseUrl}` + colors.reset);
|
|
485
|
+
console.log('');
|
|
486
|
+
|
|
487
|
+
return {
|
|
488
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
489
|
+
ANTHROPIC_API_KEY: validation.value
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.log(colors.red + '[X] Configuration error: ' + error.message + colors.reset);
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Display Claude Code style header
|
|
499
|
+
function displayHeader() {
|
|
500
|
+
console.clear();
|
|
501
|
+
console.log('');
|
|
502
|
+
|
|
503
|
+
// Claude-style orange/amber border with Unicode box drawing characters
|
|
504
|
+
const border = colors.orange;
|
|
505
|
+
const title = colors.white + colors.bright;
|
|
506
|
+
|
|
507
|
+
console.log(border + ' ┌────────────────────────────────────────┐' + colors.reset);
|
|
508
|
+
console.log(border + ' │' + title + ' Claude Code Launcher ' + border + '│' + colors.reset);
|
|
509
|
+
console.log(border + ' └────────────────────────────────────────┘' + colors.reset);
|
|
510
|
+
console.log('');
|
|
511
|
+
console.log(colors.gray + ' Use ↑↓ arrow keys to navigate, Enter to select' + colors.reset);
|
|
512
|
+
console.log('');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Menu options
|
|
516
|
+
const menuOptions = [
|
|
517
|
+
'Launch Claude Code',
|
|
518
|
+
'Launch Claude Code (Skip Permissions)',
|
|
519
|
+
'Launch Claude Code with Kimi K2 API',
|
|
520
|
+
'Launch Claude Code with Kimi K2 API (Skip Permissions)',
|
|
521
|
+
'Exit'
|
|
522
|
+
];
|
|
523
|
+
|
|
524
|
+
let selectedIndex = 0;
|
|
525
|
+
|
|
526
|
+
// Display menu with current selection
|
|
527
|
+
function displayMenu() {
|
|
528
|
+
displayHeader();
|
|
529
|
+
|
|
530
|
+
menuOptions.forEach((option, index) => {
|
|
531
|
+
if (index === selectedIndex) {
|
|
532
|
+
// Selected item with Claude-style highlighting
|
|
533
|
+
console.log(colors.orange + ' → ' + colors.black + colors.bgAmber + option + colors.reset);
|
|
534
|
+
} else {
|
|
535
|
+
// Normal item
|
|
536
|
+
console.log(colors.gray + ' ' + option + colors.reset);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
console.log('');
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Handle key press
|
|
544
|
+
function handleKeyPress(key) {
|
|
545
|
+
switch (key) {
|
|
546
|
+
case '\u001b[A': // Up arrow
|
|
547
|
+
selectedIndex = (selectedIndex - 1 + menuOptions.length) % menuOptions.length;
|
|
548
|
+
displayMenu();
|
|
549
|
+
break;
|
|
550
|
+
|
|
551
|
+
case '\u001b[B': // Down arrow
|
|
552
|
+
selectedIndex = (selectedIndex + 1) % menuOptions.length;
|
|
553
|
+
displayMenu();
|
|
554
|
+
break;
|
|
555
|
+
|
|
556
|
+
case '\r': // Enter
|
|
557
|
+
executeSelection();
|
|
558
|
+
break;
|
|
559
|
+
|
|
560
|
+
case '\u001b': // Escape
|
|
561
|
+
case 'q':
|
|
562
|
+
case 'Q':
|
|
563
|
+
console.log('');
|
|
564
|
+
console.log(colors.green + 'Goodbye!' + colors.reset);
|
|
565
|
+
process.exit(0);
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Handle Kimi API launches with async configuration
|
|
571
|
+
async function handleKimiLaunch(command) {
|
|
572
|
+
try {
|
|
573
|
+
// Clean up existing listeners before API key input
|
|
574
|
+
if (process.stdin.isTTY) {
|
|
575
|
+
process.stdin.setRawMode(false);
|
|
576
|
+
process.stdin.removeAllListeners('data');
|
|
577
|
+
process.stdin.pause();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const kimiConfig = await getKimiConfig();
|
|
581
|
+
if (kimiConfig) {
|
|
582
|
+
launchClaude(command, kimiConfig, true); // true to disable auth tokens
|
|
583
|
+
}
|
|
584
|
+
} catch (error) {
|
|
585
|
+
console.log('');
|
|
586
|
+
console.log(colors.red + '[X] Failed to configure Kimi API: ' + error.message + colors.reset);
|
|
587
|
+
console.log(colors.gray + 'Returning to menu...' + colors.reset);
|
|
588
|
+
console.log('');
|
|
589
|
+
|
|
590
|
+
// Simple delay before returning to menu
|
|
591
|
+
setTimeout(() => {
|
|
592
|
+
showMenu();
|
|
593
|
+
}, 2000);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Execute selected menu item
|
|
598
|
+
function executeSelection() {
|
|
599
|
+
switch (selectedIndex) {
|
|
600
|
+
case 0: // Launch Claude Code
|
|
601
|
+
launchClaude('claude');
|
|
602
|
+
break;
|
|
603
|
+
|
|
604
|
+
case 1: // Launch Claude Code (Skip Permissions)
|
|
605
|
+
launchClaude('claude --dangerously-skip-permissions');
|
|
606
|
+
break;
|
|
607
|
+
|
|
608
|
+
case 2: // Launch Claude Code with Kimi K2 API
|
|
609
|
+
handleKimiLaunch('claude');
|
|
610
|
+
break;
|
|
611
|
+
|
|
612
|
+
case 3: // Launch Claude Code with Kimi K2 API (Skip Permissions)
|
|
613
|
+
handleKimiLaunch('claude --dangerously-skip-permissions');
|
|
614
|
+
break;
|
|
615
|
+
|
|
616
|
+
case 4: // Exit
|
|
617
|
+
console.log('');
|
|
618
|
+
console.log(colors.green + 'Goodbye!' + colors.reset);
|
|
619
|
+
process.exit(0);
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Initialize menu
|
|
625
|
+
function showMenu() {
|
|
626
|
+
displayMenu();
|
|
627
|
+
|
|
628
|
+
// Check if we're in a TTY environment
|
|
629
|
+
if (process.stdin.isTTY) {
|
|
630
|
+
// Set up raw mode for capturing arrow keys
|
|
631
|
+
process.stdin.setRawMode(true);
|
|
632
|
+
process.stdin.resume();
|
|
633
|
+
process.stdin.setEncoding('utf8');
|
|
634
|
+
|
|
635
|
+
process.stdin.on('data', (key) => {
|
|
636
|
+
handleKeyPress(key);
|
|
637
|
+
});
|
|
638
|
+
} else {
|
|
639
|
+
// Fallback for non-TTY environments - use readline
|
|
640
|
+
const rl = readline.createInterface({
|
|
641
|
+
input: process.stdin,
|
|
642
|
+
output: process.stdout
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
console.log(colors.yellow + ' Arrow keys not available. Enter selection number (1-5): ' + colors.reset);
|
|
646
|
+
|
|
647
|
+
rl.on('line', (input) => {
|
|
648
|
+
const choice = parseInt(input.trim());
|
|
649
|
+
if (choice >= 1 && choice <= menuOptions.length) {
|
|
650
|
+
selectedIndex = choice - 1;
|
|
651
|
+
rl.close();
|
|
652
|
+
executeSelection();
|
|
653
|
+
} else if (input.toLowerCase() === 'q' || input.toLowerCase() === 'exit') {
|
|
654
|
+
rl.close();
|
|
655
|
+
console.log('');
|
|
656
|
+
console.log(colors.green + 'Goodbye!' + colors.reset);
|
|
657
|
+
process.exit(0);
|
|
658
|
+
} else {
|
|
659
|
+
console.log(colors.red + ' Invalid selection. Please enter 1-5.' + colors.reset);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Handle process termination
|
|
666
|
+
process.on('SIGINT', () => {
|
|
667
|
+
console.log('');
|
|
668
|
+
console.log(colors.green + 'Goodbye!' + colors.reset);
|
|
669
|
+
process.exit(0);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
process.on('SIGTERM', () => {
|
|
673
|
+
console.log('');
|
|
674
|
+
console.log(colors.green + 'Goodbye!' + colors.reset);
|
|
675
|
+
process.exit(0);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Start the application
|
|
679
|
+
showMenu();
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Claude Launcher
|
|
2
|
+
|
|
3
|
+
一个优雅的 Claude Code 交互式启动器,具有美观的 Claude 风格界面。通过直观的命令行菜单使用各种配置启动 Claude Code。
|
|
4
|
+
|
|
5
|
+
## 📖 文档
|
|
6
|
+
|
|
7
|
+
- [English](../README.md)
|
|
8
|
+
- [中文文档](README-zh.md) (当前)
|
|
9
|
+
|
|
10
|
+
## ✨ 特性
|
|
11
|
+
|
|
12
|
+
- 🎨 **Claude 风格界面** - 采用正宗的橙色/琥珀色配色方案
|
|
13
|
+
- ⌨️ **方向键导航** - 支持方向键导航,非 TTY 环境下支持数字选择
|
|
14
|
+
- 🔐 **加密 API 密钥存储** - 使用 AES-256-CBC 加密
|
|
15
|
+
- 🚀 **多种启动选项** - 包括跳过权限检查和 Kimi K2 API
|
|
16
|
+
- 🌍 **全局安装** - 在任何地方都可以使用 `claude-launcher`
|
|
17
|
+
- 🔧 **智能配置** - 自动查找/创建配置文件
|
|
18
|
+
- 💻 **跨平台支持** - Windows、macOS 和 Linux
|
|
19
|
+
|
|
20
|
+
## 🚀 快速开始
|
|
21
|
+
|
|
22
|
+
1. **全局安装:**
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @kikkimo/claude-launcher
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. **运行启动器:**
|
|
28
|
+
```bash
|
|
29
|
+
claude-launcher
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. **Kimi API 用户:** 首次使用时,输入以 `sk-` 开头的 Kimi API 密钥
|
|
33
|
+
|
|
34
|
+
就这么简单!启动器会引导您完成设置过程。
|
|
35
|
+
|
|
36
|
+
## 📦 安装
|
|
37
|
+
|
|
38
|
+
### 全局安装(推荐)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g @kikkimo/claude-launcher
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
安装后,您可以在任何目录运行 `claude-launcher`。
|
|
45
|
+
|
|
46
|
+
### 本地安装
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/kikkimo/claude-launcher.git
|
|
50
|
+
cd claude-launcher
|
|
51
|
+
npm install
|
|
52
|
+
node claude-launcher
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 🎮 使用方法
|
|
56
|
+
|
|
57
|
+
### 可用的启动选项
|
|
58
|
+
|
|
59
|
+
1. **启动 Claude Code** - 标准 Claude Code 启动
|
|
60
|
+
2. **启动 Claude Code(跳过权限)** - 使用 `--dangerously-skip-permissions` 启动
|
|
61
|
+
3. **使用 Kimi K2 API 启动 Claude Code** - 使用加密存储的 Kimi API
|
|
62
|
+
4. **使用 Kimi K2 API 启动 Claude Code(跳过权限)** - 结合 Kimi API 和跳过权限
|
|
63
|
+
5. **退出** - 关闭启动器
|
|
64
|
+
|
|
65
|
+
### 交互式导航
|
|
66
|
+
|
|
67
|
+
- **方向键**:使用 ↑↓ 导航,Enter 选择(在 TTY 环境中)
|
|
68
|
+
- **数字选择**:输入 1-5 并按 Enter(在非 TTY 环境中)
|
|
69
|
+
- **快速退出**:随时按 Esc 或 Q 退出
|
|
70
|
+
|
|
71
|
+
### 示例会话
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
$ claude-launcher
|
|
75
|
+
|
|
76
|
+
┌────────────────────────────────────────┐
|
|
77
|
+
│ Claude Code Launcher │
|
|
78
|
+
└────────────────────────────────────────┘
|
|
79
|
+
|
|
80
|
+
Use ↑↓ arrow keys to navigate, Enter to select
|
|
81
|
+
|
|
82
|
+
→ Launch Claude Code
|
|
83
|
+
Launch Claude Code (Skip Permissions)
|
|
84
|
+
Launch Claude Code with Kimi K2 API
|
|
85
|
+
Launch Claude Code with Kimi K2 API (Skip Permissions)
|
|
86
|
+
Exit
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## ⚙️ 配置
|
|
90
|
+
|
|
91
|
+
### 自动配置
|
|
92
|
+
|
|
93
|
+
首次运行时,如果您选择 Kimi API 选项且没有现有配置,启动器将:
|
|
94
|
+
|
|
95
|
+
1. 自动在 `~/.claude-launcher.env` 创建配置文件
|
|
96
|
+
2. 引导您输入 Kimi API 密钥
|
|
97
|
+
3. 使用机器特定的加密安全存储您的 API 密钥
|
|
98
|
+
|
|
99
|
+
### 手动配置
|
|
100
|
+
|
|
101
|
+
如果您更喜欢手动设置,配置文件搜索顺序如下:
|
|
102
|
+
|
|
103
|
+
1. 当前目录中的 `.claude-launcher.env`
|
|
104
|
+
2. 用户主目录中的 `.claude-launcher.env`
|
|
105
|
+
3. 安装目录中的 `.claude-launcher.env`
|
|
106
|
+
|
|
107
|
+
### 配置文件格式
|
|
108
|
+
|
|
109
|
+
```env
|
|
110
|
+
KIMI_API_KEY=your_encrypted_api_key_here
|
|
111
|
+
KIMI_BASE_URL=https://api.moonshot.cn/anthropic/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**注意**: `KIMI_API_KEY` 通过启动器输入时会自动加密。请勿手动编辑加密值。
|
|
115
|
+
|
|
116
|
+
### 安全功能
|
|
117
|
+
|
|
118
|
+
- **AES-256-CBC 加密**: 使用行业标准加密算法加密 API 密钥
|
|
119
|
+
- **机器特定密钥**: 加密密钥从机器特定数据派生
|
|
120
|
+
- **仅本地存储**: 加密密钥无法在其他机器上解密
|
|
121
|
+
- **安全输入**: API 密钥输入支持复制/粘贴和验证
|
|
122
|
+
|
|
123
|
+
## 📋 系统要求
|
|
124
|
+
|
|
125
|
+
- **Node.js**: 20.0.0 或更高版本
|
|
126
|
+
- **Claude Code**: 已安装并可通过 `claude` 命令访问
|
|
127
|
+
- **终端**: 任何支持 Node.js 的现代终端
|
|
128
|
+
|
|
129
|
+
## 🔧 开发
|
|
130
|
+
|
|
131
|
+
### 从源码构建
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git clone https://github.com/kikkimo/claude-launcher.git
|
|
135
|
+
cd claude-launcher
|
|
136
|
+
npm install
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 本地测试
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm start
|
|
143
|
+
# 或者
|
|
144
|
+
node claude-launcher
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 🤝 贡献
|
|
148
|
+
|
|
149
|
+
我们欢迎贡献!请遵循以下步骤:
|
|
150
|
+
|
|
151
|
+
1. Fork 这个仓库
|
|
152
|
+
2. 创建您的功能分支:`git checkout -b feature/amazing-feature`
|
|
153
|
+
3. 提交您的更改:`git commit -m 'Add amazing feature'`
|
|
154
|
+
4. 推送到分支:`git push origin feature/amazing-feature`
|
|
155
|
+
5. 打开一个 Pull Request
|
|
156
|
+
|
|
157
|
+
## 📄 许可证
|
|
158
|
+
|
|
159
|
+
此项目基于 MIT 许可证 - 查看 [LICENSE](../LICENSE) 文件了解详情。
|
|
160
|
+
|
|
161
|
+
## 🙏 致谢
|
|
162
|
+
|
|
163
|
+
- 灵感来源于 Claude Code 的精美设计
|
|
164
|
+
- 用 ❤️ 为 Claude Code 社区构建
|
|
165
|
+
- 感谢所有贡献者和用户
|
|
166
|
+
|
|
167
|
+
## 🐛 问题与支持
|
|
168
|
+
|
|
169
|
+
如果您遇到任何问题或有疑问:
|
|
170
|
+
|
|
171
|
+
1. 查看现有的 [Issues](https://github.com/kikkimo/claude-launcher/issues)
|
|
172
|
+
2. 创建新问题并提供详细信息
|
|
173
|
+
3. 包含您的操作系统、Node.js 版本和错误消息
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
**注意**: 此启动器设计用于 Claude Code 和 Kimi K2 API。使用此工具前请确保已安装 Claude Code。
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kikkimo/claude-launcher",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive launcher for Claude Code with beautiful Claude-style interface",
|
|
5
|
+
"main": "claude-launcher",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-launcher": "./claude-launcher"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node claude-launcher",
|
|
11
|
+
"test": "echo \"No tests specified\" && exit 0",
|
|
12
|
+
"prepublishOnly": "echo \"Publishing claude-launcher...\"",
|
|
13
|
+
"postinstall": "echo \"Claude Launcher installed successfully! Run 'claude-launcher' to start.\"",
|
|
14
|
+
"publish:public": "npm publish --access public"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"claude-launcher",
|
|
21
|
+
"claude-launcher-template.env",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"docs/"
|
|
26
|
+
],
|
|
27
|
+
"keywords": [
|
|
28
|
+
"claude",
|
|
29
|
+
"claude-code",
|
|
30
|
+
"launcher",
|
|
31
|
+
"cli",
|
|
32
|
+
"interactive",
|
|
33
|
+
"menu",
|
|
34
|
+
"anthropic",
|
|
35
|
+
"kimi"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/kikkimo/claude-launcher.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/kikkimo/claude-launcher/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/kikkimo/claude-launcher#readme",
|
|
45
|
+
"author": "kikkimo <fywhu@qq.com>",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20.0.0"
|
|
49
|
+
},
|
|
50
|
+
"preferGlobal": true,
|
|
51
|
+
"os": [
|
|
52
|
+
"darwin",
|
|
53
|
+
"linux",
|
|
54
|
+
"win32"
|
|
55
|
+
]
|
|
56
|
+
}
|