@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 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.
@@ -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
+ }