qwen-api-proxy 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * qwen-api-proxy - CLI entry point for global installation
5
+ *
6
+ * This script:
7
+ * 1. Sets up the working directory structure in the current location
8
+ * 2. Creates necessary directories (session, logs, uploads, temp)
9
+ * 3. Creates .env.example if not exists
10
+ * 4. Passes control to the main index.js
11
+ */
12
+
13
+ import { fileURLToPath } from 'url';
14
+ import path from 'path';
15
+ import fs from 'fs';
16
+ import { execSync } from 'child_process';
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
20
+ const WORKING_DIR = process.cwd();
21
+
22
+ // Parse command-line arguments early to get custom directory
23
+ const args = process.argv.slice(2);
24
+ const dirFlag = args.find(arg => arg.startsWith('--dir='));
25
+ const customDir = dirFlag ? dirFlag.split('=')[1] : null;
26
+
27
+ // Use custom directory if specified
28
+ const EFFECTIVE_DIR = customDir ? path.resolve(customDir) : WORKING_DIR;
29
+ if (customDir) {
30
+ console.log(`šŸ“ Using custom directory: ${EFFECTIVE_DIR}`);
31
+ if (!fs.existsSync(EFFECTIVE_DIR)) {
32
+ fs.mkdirSync(EFFECTIVE_DIR, { recursive: true });
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Creates required directories in the working directory
38
+ */
39
+ function setupWorkingDirectory(targetDir = EFFECTIVE_DIR) {
40
+ const dirs = [
41
+ 'session',
42
+ 'session/accounts',
43
+ 'session/history',
44
+ 'logs',
45
+ 'uploads',
46
+ 'temp'
47
+ ];
48
+
49
+ console.log('šŸ“ Setting up working directory...');
50
+
51
+ dirs.forEach(dir => {
52
+ const dirPath = path.join(targetDir, dir);
53
+ if (!fs.existsSync(dirPath)) {
54
+ fs.mkdirSync(dirPath, { recursive: true });
55
+ console.log(` āœ“ Created ${dir}/`);
56
+ } else {
57
+ console.log(` āœ“ ${dir}/ already exists`);
58
+ }
59
+ });
60
+
61
+ // Create .gitkeep for history
62
+ const gitkeepPath = path.join(targetDir, 'session', 'history', '.gitkeep');
63
+ if (!fs.existsSync(gitkeepPath)) {
64
+ fs.writeFileSync(gitkeepPath, '');
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Creates .env file from example if it doesn't exist
70
+ */
71
+ function setupEnvFile(targetDir = EFFECTIVE_DIR) {
72
+ const envPath = path.join(targetDir, '.env');
73
+ const envExamplePath = path.join(targetDir, '.env.example');
74
+
75
+ if (!fs.existsSync(envPath)) {
76
+ // Copy .env.example from package if it exists in working dir, otherwise create basic
77
+ const packageEnvExample = path.join(PACKAGE_ROOT, '.env.example');
78
+
79
+ if (fs.existsSync(packageEnvExample)) {
80
+ fs.copyFileSync(packageEnvExample, envExamplePath);
81
+ console.log(' āœ“ Created .env.example');
82
+ }
83
+
84
+ // Create empty .env if not exists
85
+ if (!fs.existsSync(envPath)) {
86
+ fs.writeFileSync(envPath, '# Qwen API Proxy Configuration\n# See .env.example for available options\n\n');
87
+ console.log(' āœ“ Created .env (empty)');
88
+ }
89
+ } else {
90
+ console.log(' āœ“ .env already exists');
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Creates or validates root .gitignore file
96
+ */
97
+ function setupGitignore(targetDir = EFFECTIVE_DIR) {
98
+ const gitignorePath = path.join(targetDir, '.gitignore');
99
+ const requiredEntries = [
100
+ 'session',
101
+ 'logs',
102
+ 'uploads',
103
+ 'temp',
104
+ 'session_backup',
105
+ 'session_backup_*'
106
+ ];
107
+ const missingEntries = [];
108
+
109
+ // Check which entries are missing from .gitignore
110
+ if (fs.existsSync(gitignorePath)) {
111
+ const content = fs.readFileSync(gitignorePath, 'utf8');
112
+ const lines = content.split('\n').map(line => line.trim());
113
+ requiredEntries.forEach(entry => {
114
+ if (!lines.includes(entry)) {
115
+ missingEntries.push(entry);
116
+ }
117
+ });
118
+ } else {
119
+ // .gitignore doesn't exist - create it
120
+ const gitignoreContent = `# Qwen API Proxy - Auto-generated
121
+ # Sensitive data and runtime files
122
+ session
123
+ logs
124
+ uploads
125
+ temp
126
+ session_backup
127
+
128
+ # Backup archives
129
+ session_backup_*
130
+ *.zip
131
+
132
+ # Environment files
133
+ .env
134
+ .env.local
135
+ .env.*.local
136
+
137
+ # Logs
138
+ *.log
139
+ npm-debug.log*
140
+
141
+ # Runtime files
142
+ .restart_flag
143
+ .pending_archive
144
+ .last_telegram_update
145
+ `;
146
+ fs.writeFileSync(gitignorePath, gitignoreContent);
147
+ console.log(' āœ“ Created .gitignore');
148
+ }
149
+
150
+ return missingEntries;
151
+ }
152
+
153
+ /**
154
+ * Check if zip is available
155
+ */
156
+ function checkDependencies() {
157
+ try {
158
+ execSync('zip --version', { stdio: 'ignore' });
159
+ console.log(' āœ“ zip command available');
160
+ } catch (error) {
161
+ console.warn(' āš ļø zip command not found (required for archive command)');
162
+ console.warn(' Install with: sudo apt install zip (Ubuntu) or brew install zip (macOS)');
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Main setup function
168
+ */
169
+ function setup() {
170
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
171
+ console.log('ā•‘ Qwen API Proxy - Working Directory Setup ā•‘');
172
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
173
+
174
+ console.log(`šŸ“ Working directory: ${EFFECTIVE_DIR}\n`);
175
+
176
+ setupWorkingDirectory();
177
+ console.log();
178
+ setupEnvFile();
179
+ console.log();
180
+
181
+ const missingGitignoreDirs = setupGitignore();
182
+ console.log();
183
+
184
+ checkDependencies();
185
+
186
+ console.log('\nāœ… Working directory setup complete!\n');
187
+
188
+ // Show warnings for missing .gitignore entries
189
+ if (missingGitignoreDirs.length > 0) {
190
+ console.warn('āš ļø WARNING: The following entries are not in .gitignore:');
191
+ missingGitignoreDirs.forEach(entry => {
192
+ console.warn(` - ${entry}`);
193
+ });
194
+ console.warn('\n Add them to .gitignore to prevent committing sensitive data!\n');
195
+ }
196
+ }
197
+
198
+ // Run setup only on first run or when --setup flag is used
199
+ const needsSetup = args.includes('--setup') || !fs.existsSync(path.join(EFFECTIVE_DIR, 'session'));
200
+
201
+ // Parse command from arguments (already defined args above)
202
+ const command = args.find(arg => !arg.startsWith('-'));
203
+
204
+ // Handle 'init' command - manual setup only
205
+ if (command === 'init') {
206
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
207
+ console.log('ā•‘ Qwen API Proxy - Initialize Working Directory ā•‘');
208
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
209
+
210
+ console.log(`šŸ“ Working directory: ${EFFECTIVE_DIR}\n`);
211
+
212
+ setupWorkingDirectory(EFFECTIVE_DIR);
213
+ console.log();
214
+ setupEnvFile(EFFECTIVE_DIR);
215
+ console.log();
216
+
217
+ const missingGitignoreDirs = setupGitignore(EFFECTIVE_DIR);
218
+ console.log();
219
+
220
+ checkDependencies();
221
+
222
+ console.log('\nāœ… Working directory initialized successfully!\n');
223
+ console.log('šŸ“ Next steps:');
224
+ console.log(' 1. Edit .env file with your configuration');
225
+ console.log(' 2. Run: qwen-api-proxy');
226
+ console.log('\n');
227
+
228
+ // Show warnings for missing .gitignore entries
229
+ if (missingGitignoreDirs.length > 0) {
230
+ console.warn('āš ļø WARNING: The following entries are not in .gitignore:');
231
+ missingGitignoreDirs.forEach(entry => {
232
+ console.warn(` - ${entry}`);
233
+ });
234
+ console.warn('\n Add them to .gitignore to prevent committing sensitive data!\n');
235
+ }
236
+
237
+ process.exit(0);
238
+ }
239
+
240
+ // Handle 'doctor' command - system health check
241
+ if (command === 'doctor') {
242
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
243
+ console.log('ā•‘ Qwen API Proxy - System Health Check ā•‘');
244
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n');
245
+
246
+ const issues = [];
247
+ const warnings = [];
248
+ const ok = [];
249
+
250
+ // Check Node.js version
251
+ const nodeVersion = process.version;
252
+ const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
253
+ if (majorVersion >= 18) {
254
+ ok.push(`Node.js ${nodeVersion} (āœ“ version >= 18)`);
255
+ } else {
256
+ issues.push(`Node.js ${nodeVersion} (āœ— requires >= 18)`);
257
+ }
258
+
259
+ // Check zip command
260
+ try {
261
+ execSync('zip --version', { stdio: 'ignore' });
262
+ ok.push('zip command available');
263
+ } catch (error) {
264
+ warnings.push('zip command not found (required for archive command)');
265
+ }
266
+
267
+ // Check working directory structure
268
+ const requiredDirs = ['session', 'session/accounts', 'session/history', 'logs', 'uploads', 'temp'];
269
+ requiredDirs.forEach(dir => {
270
+ const dirPath = path.join(EFFECTIVE_DIR, dir);
271
+ if (fs.existsSync(dirPath)) {
272
+ ok.push(`${dir}/ directory exists`);
273
+ } else {
274
+ warnings.push(`${dir}/ directory missing`);
275
+ }
276
+ });
277
+
278
+ // Check .env file
279
+ const envPath = path.join(EFFECTIVE_DIR, '.env');
280
+ if (fs.existsSync(envPath)) {
281
+ ok.push('.env file exists');
282
+
283
+ // Check for common configurations
284
+ const envContent = fs.readFileSync(envPath, 'utf8');
285
+ if (envContent.includes('TELEGRAM_BOT_TOKEN=') && !envContent.includes('TELEGRAM_BOT_TOKEN=your_bot_token_here')) {
286
+ ok.push('Telegram bot token configured');
287
+ } else {
288
+ warnings.push('Telegram bot token not configured (optional)');
289
+ }
290
+ } else {
291
+ warnings.push('.env file not found (run "qwen-api-proxy init" to create)');
292
+ }
293
+
294
+ // Check .gitignore file
295
+ const gitignorePath = path.join(EFFECTIVE_DIR, '.gitignore');
296
+ const requiredGitignoreEntries = [
297
+ 'session',
298
+ 'logs',
299
+ 'uploads',
300
+ 'temp',
301
+ 'session_backup',
302
+ 'session_backup_*'
303
+ ];
304
+ const missingGitignoreEntries = [];
305
+
306
+ if (fs.existsSync(gitignorePath)) {
307
+ const content = fs.readFileSync(gitignorePath, 'utf8');
308
+ const lines = content.split('\n').map(line => line.trim());
309
+ requiredGitignoreEntries.forEach(entry => {
310
+ if (!lines.includes(entry)) {
311
+ missingGitignoreEntries.push(entry);
312
+ }
313
+ });
314
+
315
+ if (missingGitignoreEntries.length === 0) {
316
+ ok.push('.gitignore properly configured');
317
+ } else {
318
+ warnings.push(`.gitignore missing: ${missingGitignoreEntries.join(', ')}`);
319
+ }
320
+ } else {
321
+ warnings.push('.gitignore not found (run "qwen-api-proxy init" to create)');
322
+ }
323
+
324
+ // Check permissions
325
+ const testDirs = ['session', 'logs', 'uploads'];
326
+ let permissionsOk = true;
327
+ testDirs.forEach(dir => {
328
+ const dirPath = path.join(EFFECTIVE_DIR, dir);
329
+ if (fs.existsSync(dirPath)) {
330
+ try {
331
+ const testFile = path.join(dirPath, '.write_test');
332
+ fs.writeFileSync(testFile, 'test');
333
+ fs.unlinkSync(testFile);
334
+ } catch (error) {
335
+ issues.push(`${dir}/ - no write permission`);
336
+ permissionsOk = false;
337
+ }
338
+ }
339
+ });
340
+ if (permissionsOk) {
341
+ ok.push('Directory permissions OK');
342
+ }
343
+
344
+ // Check session data
345
+ const tokensPath = path.join(EFFECTIVE_DIR, 'session', 'tokens.json');
346
+ if (fs.existsSync(tokensPath)) {
347
+ try {
348
+ const tokens = JSON.parse(fs.readFileSync(tokensPath, 'utf8'));
349
+ if (Array.isArray(tokens) && tokens.length > 0) {
350
+ ok.push(`${tokens.length} account(s) configured`);
351
+ } else {
352
+ warnings.push('No accounts configured (run server and add account)');
353
+ }
354
+ } catch (error) {
355
+ issues.push('tokens.json is corrupted');
356
+ }
357
+ } else {
358
+ warnings.push('No accounts configured yet');
359
+ }
360
+
361
+ // Check disk space
362
+ try {
363
+ const dfOutput = execSync('df -k .', { encoding: 'utf8' });
364
+ const lines = dfOutput.split('\n');
365
+ if (lines.length >= 2) {
366
+ const parts = lines[1].split(/\s+/);
367
+ const availableKB = parseInt(parts[3]);
368
+ const availableGB = (availableKB / 1024 / 1024).toFixed(2);
369
+ if (availableGB > 1) {
370
+ ok.push(`Disk space: ${availableGB} GB available`);
371
+ } else {
372
+ warnings.push(`Low disk space: ${availableGB} GB available`);
373
+ }
374
+ }
375
+ } catch (error) {
376
+ // df not available, skip
377
+ }
378
+
379
+ // Print results
380
+ console.log('āœ… OK:');
381
+ ok.forEach(msg => console.log(` āœ“ ${msg}`));
382
+
383
+ if (warnings.length > 0) {
384
+ console.log('\nāš ļø Warnings:');
385
+ warnings.forEach(msg => console.log(` ⚠ ${msg}`));
386
+ }
387
+
388
+ if (issues.length > 0) {
389
+ console.log('\nāŒ Issues:');
390
+ issues.forEach(msg => console.log(` āœ— ${msg}`));
391
+ }
392
+
393
+ console.log('\n' + '='.repeat(60));
394
+ if (issues.length === 0 && warnings.length === 0) {
395
+ console.log('šŸŽ‰ All checks passed! System is healthy.');
396
+ } else if (issues.length === 0) {
397
+ console.log(`āœ“ System operational (${warnings.length} warning(s))`);
398
+ } else {
399
+ console.log(`āœ— ${issues.length} issue(s) found - please fix before running`);
400
+ }
401
+ console.log('='.repeat(60) + '\n');
402
+
403
+ process.exit(issues.length > 0 ? 1 : 0);
404
+ }
405
+
406
+ if (needsSetup) {
407
+ setup();
408
+ }
409
+
410
+ // Set environment variable to indicate we're running from global install
411
+ process.env.QWEN_API_PROXY_GLOBAL = 'true';
412
+
413
+ // Import and run the main application
414
+ await import(path.join(PACKAGE_ROOT, 'index.js'));