@feng3d/ctc 0.0.6

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,680 @@
1
+ /**
2
+ * @module admin-server
3
+ * @description 客户端管理页面 HTTP 服务器模块。
4
+ * 提供一个本地 HTTP 服务,用于查看客户端状态和管理代理映射。
5
+ */
6
+ import { createServer } from 'http';
7
+ /**
8
+ * 管理页面服务器类
9
+ *
10
+ * 在本地启动一个 HTTP 服务器,提供状态查询和代理管理的 API 接口,
11
+ * 以及一个可视化的 Web 管理界面。
12
+ */
13
+ export class AdminServer {
14
+ /**
15
+ * 创建管理服务器实例
16
+ *
17
+ * @param config - 服务器配置
18
+ * @param getStatus - 获取状态的回调函数
19
+ * @param addProxy - 添加代理的回调函数
20
+ * @param removeProxy - 删除代理的回调函数
21
+ */
22
+ constructor(config, getStatus, addProxy, removeProxy) {
23
+ this.port = config.port;
24
+ this.host = config.host;
25
+ this.startedAt = Date.now();
26
+ this.getStatusCallback = getStatus;
27
+ this.addProxyCallback = addProxy;
28
+ this.removeProxyCallback = removeProxy;
29
+ this.server = createServer((req, res) => this.handleRequest(req, res));
30
+ }
31
+ /**
32
+ * 启动服务器
33
+ *
34
+ * @returns 启动完成的 Promise
35
+ */
36
+ async start() {
37
+ return new Promise((resolve, reject) => {
38
+ this.server.listen(this.port, this.host, () => {
39
+ console.log(`管理页面已启动: http://${this.host}:${this.port}`);
40
+ resolve();
41
+ });
42
+ this.server.on('error', (error) => {
43
+ console.error('管理服务器错误:', error);
44
+ reject(error);
45
+ });
46
+ });
47
+ }
48
+ /**
49
+ * 处理 HTTP 请求
50
+ *
51
+ * 提供以下端点:
52
+ * - `GET /` - 管理页面
53
+ * - `GET /_ctc/status` - 获取状态
54
+ * - `POST /_ctc/proxies` - 添加代理
55
+ * - `DELETE /_ctc/proxies/:port` - 删除代理
56
+ */
57
+ handleRequest(req, res) {
58
+ const url = req.url ?? '/';
59
+ // 管理页面
60
+ if (url === '/' && req.method === 'GET') {
61
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
62
+ res.end(AdminServer.STATUS_HTML);
63
+ return;
64
+ }
65
+ // 状态 API
66
+ if (url === '/_ctc/status' && req.method === 'GET') {
67
+ const status = this.getStatusCallback();
68
+ res.writeHead(200, { 'Content-Type': 'application/json' });
69
+ res.end(JSON.stringify(status));
70
+ return;
71
+ }
72
+ // 添加代理 API
73
+ if (url === '/_ctc/proxies' && req.method === 'POST') {
74
+ let body = '';
75
+ req.on('data', (chunk) => { body += chunk; });
76
+ req.on('end', async () => {
77
+ try {
78
+ const proxy = JSON.parse(body);
79
+ await this.addProxyCallback(proxy);
80
+ res.writeHead(200, { 'Content-Type': 'application/json' });
81
+ res.end(JSON.stringify({ success: true }));
82
+ }
83
+ catch (error) {
84
+ const errorMessage = error instanceof Error ? error.message : String(error);
85
+ res.writeHead(400, { 'Content-Type': 'application/json' });
86
+ res.end(JSON.stringify({ error: errorMessage }));
87
+ }
88
+ });
89
+ return;
90
+ }
91
+ // 删除代理 API
92
+ if (url.startsWith('/_ctc/proxies/') && req.method === 'DELETE') {
93
+ const port = parseInt(url.split('/').pop(), 10);
94
+ if (isNaN(port)) {
95
+ res.writeHead(400, { 'Content-Type': 'application/json' });
96
+ res.end(JSON.stringify({ error: '无效的端口号' }));
97
+ return;
98
+ }
99
+ this.removeProxyCallback(port)
100
+ .then(() => {
101
+ res.writeHead(200, { 'Content-Type': 'application/json' });
102
+ res.end(JSON.stringify({ success: true }));
103
+ })
104
+ .catch((error) => {
105
+ const errorMessage = error instanceof Error ? error.message : String(error);
106
+ res.writeHead(400, { 'Content-Type': 'application/json' });
107
+ res.end(JSON.stringify({ error: errorMessage }));
108
+ });
109
+ return;
110
+ }
111
+ // 404
112
+ res.writeHead(404);
113
+ res.end('Not Found');
114
+ }
115
+ /**
116
+ * 停止服务器
117
+ */
118
+ async stop() {
119
+ return new Promise((resolve) => {
120
+ this.server.close(() => {
121
+ console.log('管理服务器已停止');
122
+ resolve();
123
+ });
124
+ });
125
+ }
126
+ }
127
+ /**
128
+ * 状态页面 HTML 模板
129
+ */
130
+ AdminServer.STATUS_HTML = `<!DOCTYPE html>
131
+ <html lang="zh-CN">
132
+ <head>
133
+ <meta charset="UTF-8">
134
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
135
+ <title>穿透客户端管理</title>
136
+ <style>
137
+ * { margin: 0; padding: 0; box-sizing: border-box; }
138
+ body {
139
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
140
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
141
+ min-height: 100vh;
142
+ color: #e0e0e0;
143
+ padding: 20px;
144
+ }
145
+ .container {
146
+ max-width: 900px;
147
+ margin: 0 auto;
148
+ }
149
+ .header {
150
+ text-align: center;
151
+ margin-bottom: 30px;
152
+ padding: 30px 20px;
153
+ background: rgba(255,255,255,0.05);
154
+ border-radius: 16px;
155
+ backdrop-filter: blur(10px);
156
+ border: 1px solid rgba(255,255,255,0.1);
157
+ }
158
+ .header h1 {
159
+ font-size: 28px;
160
+ margin-bottom: 8px;
161
+ background: linear-gradient(90deg, #00d9ff, #00ff88);
162
+ -webkit-background-clip: text;
163
+ -webkit-text-fill-color: transparent;
164
+ }
165
+ .status {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ gap: 8px;
169
+ padding: 6px 16px;
170
+ border-radius: 20px;
171
+ font-size: 14px;
172
+ font-weight: 500;
173
+ }
174
+ .status.running {
175
+ background: rgba(0, 255, 136, 0.15);
176
+ color: #00ff88;
177
+ }
178
+ .status.running::before {
179
+ content: "";
180
+ width: 8px;
181
+ height: 8px;
182
+ border-radius: 50%;
183
+ background: #00ff88;
184
+ animation: pulse 1.5s infinite;
185
+ }
186
+ .status.stopped {
187
+ background: rgba(255, 77, 77, 0.15);
188
+ color: #ff4d4d;
189
+ }
190
+ @keyframes pulse {
191
+ 0%, 100% { opacity: 1; }
192
+ 50% { opacity: 0.4; }
193
+ }
194
+ .grid {
195
+ display: grid;
196
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
197
+ gap: 16px;
198
+ margin-bottom: 20px;
199
+ }
200
+ .card {
201
+ background: rgba(255,255,255,0.05);
202
+ border-radius: 12px;
203
+ padding: 20px;
204
+ border: 1px solid rgba(255,255,255,0.1);
205
+ backdrop-filter: blur(10px);
206
+ }
207
+ .card-label {
208
+ font-size: 12px;
209
+ color: #888;
210
+ margin-bottom: 6px;
211
+ text-transform: uppercase;
212
+ letter-spacing: 0.5px;
213
+ }
214
+ .card-value {
215
+ font-size: 20px;
216
+ font-weight: 600;
217
+ color: #fff;
218
+ }
219
+ .card-value .unit {
220
+ font-size: 14px;
221
+ color: #888;
222
+ font-weight: 400;
223
+ }
224
+ .proxies-section {
225
+ background: rgba(255,255,255,0.05);
226
+ border-radius: 12px;
227
+ padding: 20px;
228
+ border: 1px solid rgba(255,255,255,0.1);
229
+ margin-top: 20px;
230
+ }
231
+ .section-header {
232
+ display: flex;
233
+ justify-content: space-between;
234
+ align-items: center;
235
+ margin-bottom: 16px;
236
+ }
237
+ .section-title {
238
+ font-size: 14px;
239
+ color: #888;
240
+ text-transform: uppercase;
241
+ letter-spacing: 0.5px;
242
+ }
243
+ .btn {
244
+ padding: 8px 16px;
245
+ border-radius: 8px;
246
+ border: none;
247
+ font-size: 13px;
248
+ cursor: pointer;
249
+ transition: all 0.2s;
250
+ }
251
+ .btn-primary {
252
+ background: linear-gradient(135deg, #00d9ff, #00ff88);
253
+ color: #000;
254
+ font-weight: 500;
255
+ }
256
+ .btn-primary:hover {
257
+ opacity: 0.9;
258
+ transform: translateY(-1px);
259
+ }
260
+ .btn-danger {
261
+ background: rgba(255, 77, 77, 0.2);
262
+ color: #ff4d4d;
263
+ padding: 4px 10px;
264
+ font-size: 12px;
265
+ }
266
+ .btn-danger:hover {
267
+ background: rgba(255, 77, 77, 0.3);
268
+ }
269
+ .proxy-item {
270
+ display: flex;
271
+ justify-content: space-between;
272
+ align-items: center;
273
+ padding: 12px 16px;
274
+ background: rgba(0,0,0,0.2);
275
+ border-radius: 8px;
276
+ margin-bottom: 8px;
277
+ font-size: 14px;
278
+ }
279
+ .proxy-item:last-child {
280
+ margin-bottom: 0;
281
+ }
282
+ .proxy-info {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 12px;
286
+ }
287
+ .proxy-protocol {
288
+ padding: 2px 8px;
289
+ border-radius: 4px;
290
+ font-size: 11px;
291
+ font-weight: 500;
292
+ text-transform: uppercase;
293
+ }
294
+ .proxy-protocol.http {
295
+ background: rgba(0, 217, 255, 0.2);
296
+ color: #00d9ff;
297
+ }
298
+ .proxy-protocol.websocket {
299
+ background: rgba(255, 165, 0, 0.2);
300
+ color: #ffa500;
301
+ }
302
+ .proxy-remote {
303
+ color: #00d9ff;
304
+ font-family: monospace;
305
+ }
306
+ .proxy-arrow {
307
+ color: #666;
308
+ }
309
+ .proxy-local {
310
+ color: #888;
311
+ font-family: monospace;
312
+ }
313
+ .empty-state {
314
+ text-align: center;
315
+ padding: 30px;
316
+ color: #666;
317
+ font-size: 14px;
318
+ }
319
+ .add-form {
320
+ display: none;
321
+ background: rgba(0,0,0,0.3);
322
+ border-radius: 12px;
323
+ padding: 20px;
324
+ margin-bottom: 16px;
325
+ }
326
+ .add-form.show {
327
+ display: block;
328
+ }
329
+ .form-row {
330
+ display: grid;
331
+ grid-template-columns: repeat(4, 1fr) auto;
332
+ gap: 12px;
333
+ align-items: end;
334
+ }
335
+ .form-group {
336
+ display: flex;
337
+ flex-direction: column;
338
+ gap: 6px;
339
+ }
340
+ .form-group label {
341
+ font-size: 11px;
342
+ color: #888;
343
+ text-transform: uppercase;
344
+ }
345
+ .form-group input, .form-group select {
346
+ padding: 10px 14px;
347
+ background: rgba(255,255,255,0.05);
348
+ border: 1px solid rgba(255,255,255,0.1);
349
+ border-radius: 8px;
350
+ color: #fff;
351
+ font-size: 14px;
352
+ }
353
+ .form-group input:focus, .form-group select:focus {
354
+ outline: none;
355
+ border-color: #00d9ff;
356
+ }
357
+ .form-actions {
358
+ display: flex;
359
+ gap: 8px;
360
+ }
361
+ .modal {
362
+ display: none;
363
+ position: fixed;
364
+ top: 0;
365
+ left: 0;
366
+ right: 0;
367
+ bottom: 0;
368
+ background: rgba(0,0,0,0.7);
369
+ align-items: center;
370
+ justify-content: center;
371
+ z-index: 1000;
372
+ }
373
+ .modal.show {
374
+ display: flex;
375
+ }
376
+ .modal-content {
377
+ background: #1a1a2e;
378
+ border-radius: 16px;
379
+ padding: 30px;
380
+ max-width: 400px;
381
+ width: 90%;
382
+ border: 1px solid rgba(255,255,255,0.1);
383
+ }
384
+ .modal-title {
385
+ font-size: 18px;
386
+ margin-bottom: 20px;
387
+ text-align: center;
388
+ }
389
+ .modal-actions {
390
+ display: flex;
391
+ gap: 12px;
392
+ margin-top: 20px;
393
+ }
394
+ .modal-actions .btn {
395
+ flex: 1;
396
+ }
397
+ .btn-secondary {
398
+ background: rgba(255,255,255,0.1);
399
+ color: #fff;
400
+ }
401
+ .btn-secondary:hover {
402
+ background: rgba(255,255,255,0.15);
403
+ }
404
+ .last-update {
405
+ text-align: center;
406
+ color: #666;
407
+ font-size: 12px;
408
+ margin-top: 20px;
409
+ }
410
+ .footer {
411
+ text-align: center;
412
+ margin-top: 30px;
413
+ padding: 20px;
414
+ color: #666;
415
+ font-size: 12px;
416
+ }
417
+ .footer a {
418
+ color: #00d9ff;
419
+ text-decoration: none;
420
+ }
421
+ .toast {
422
+ position: fixed;
423
+ bottom: 20px;
424
+ right: 20px;
425
+ padding: 12px 20px;
426
+ border-radius: 8px;
427
+ font-size: 14px;
428
+ transform: translateY(100px);
429
+ opacity: 0;
430
+ transition: all 0.3s;
431
+ }
432
+ .toast.show {
433
+ transform: translateY(0);
434
+ opacity: 1;
435
+ }
436
+ .toast.success {
437
+ background: rgba(0, 255, 136, 0.2);
438
+ color: #00ff88;
439
+ border: 1px solid rgba(0, 255, 136, 0.3);
440
+ }
441
+ .toast.error {
442
+ background: rgba(255, 77, 77, 0.2);
443
+ color: #ff4d4d;
444
+ border: 1px solid rgba(255, 77, 77, 0.3);
445
+ }
446
+ </style>
447
+ </head>
448
+ <body>
449
+ <div class="container">
450
+ <div class="header">
451
+ <h1>🔌 feng3d-ctc 穿透客户端</h1>
452
+ <div class="status running" id="status">运行中</div>
453
+ </div>
454
+
455
+ <div class="grid">
456
+ <div class="card">
457
+ <div class="card-label">服务器</div>
458
+ <div class="card-value" id="server">-</div>
459
+ </div>
460
+ <div class="card">
461
+ <div class="card-label">连接状态</div>
462
+ <div class="card-value" id="connection">-</div>
463
+ </div>
464
+ <div class="card">
465
+ <div class="card-label">运行时长</div>
466
+ <div class="card-value"><span id="uptime">-</span></div>
467
+ </div>
468
+ <div class="card">
469
+ <div class="card-label">代理数量</div>
470
+ <div class="card-value"><span id="proxyCount">0</span><span class="unit"> 个</span></div>
471
+ </div>
472
+ <div class="card">
473
+ <div class="card-label">重连次数</div>
474
+ <div class="card-value"><span id="reconnectCount">0</span><span class="unit"> 次</span></div>
475
+ </div>
476
+ </div>
477
+
478
+ <div class="proxies-section">
479
+ <div class="section-header">
480
+ <div class="section-title">代理映射</div>
481
+ <button class="btn btn-primary" id="showAddForm">+ 添加代理</button>
482
+ </div>
483
+
484
+ <div class="add-form" id="addForm">
485
+ <div class="form-row">
486
+ <div class="form-group">
487
+ <label>远程端口</label>
488
+ <input type="number" id="newRemotePort" placeholder="8080" min="1" max="65535">
489
+ </div>
490
+ <div class="form-group">
491
+ <label>协议</label>
492
+ <select id="newProtocol">
493
+ <option value="http">HTTP</option>
494
+ <option value="websocket">WebSocket</option>
495
+ </select>
496
+ </div>
497
+ <div class="form-group">
498
+ <label>本地端口</label>
499
+ <input type="number" id="newLocalPort" placeholder="3000" min="1" max="65535">
500
+ </div>
501
+ <div class="form-group">
502
+ <label>本地地址</label>
503
+ <input type="text" id="newLocalHost" placeholder="localhost">
504
+ </div>
505
+ <div class="form-actions">
506
+ <button class="btn btn-primary" id="addProxy">添加</button>
507
+ <button class="btn btn-secondary" id="cancelAdd">取消</button>
508
+ </div>
509
+ </div>
510
+ </div>
511
+
512
+ <div id="proxiesList"></div>
513
+ </div>
514
+
515
+ <div class="last-update">最后更新: <span id="lastUpdate">-</span></div>
516
+
517
+ <div class="footer">
518
+ <a href="https://github.com/feng3d/chuantou" target="_blank">feng3d-ctc</a>
519
+ — 内网穿透客户端
520
+ </div>
521
+ </div>
522
+
523
+ <div class="modal" id="deleteModal">
524
+ <div class="modal-content">
525
+ <div class="modal-title">确认删除代理</div>
526
+ <p style="color: #888; text-align: center;">确定要删除此代理映射吗?</p>
527
+ <div class="modal-actions">
528
+ <button class="btn btn-secondary" id="cancelDelete">取消</button>
529
+ <button class="btn btn-danger" id="confirmDelete">删除</button>
530
+ </div>
531
+ </div>
532
+ </div>
533
+
534
+ <div class="toast" id="toast"></div>
535
+
536
+ <script>
537
+ let deletePort = null;
538
+
539
+ function formatUptime(ms) {
540
+ const seconds = Math.floor(ms / 1000);
541
+ const minutes = Math.floor(seconds / 60);
542
+ const hours = Math.floor(minutes / 60);
543
+ const days = Math.floor(hours / 24);
544
+ if (days > 0) return \`\${days}天 \${hours % 24}小时\`;
545
+ if (hours > 0) return \`\${hours}小时 \${minutes % 60}分钟\`;
546
+ if (minutes > 0) return \`\${minutes}分钟 \${seconds % 60}秒\`;
547
+ return \`\${seconds}秒\`;
548
+ }
549
+
550
+ function showToast(message, type = 'success') {
551
+ const toast = document.getElementById('toast');
552
+ toast.textContent = message;
553
+ toast.className = \`toast \${type} show\`;
554
+ setTimeout(() => toast.classList.remove('show'), 3000);
555
+ }
556
+
557
+ async function updateStatus() {
558
+ try {
559
+ const res = await fetch('/_ctc/status');
560
+ const data = await res.json();
561
+
562
+ const statusEl = document.getElementById('status');
563
+ if (data.running) {
564
+ statusEl.textContent = data.authenticated ? '已连接' : (data.connected ? '认证中...' : '连接中...');
565
+ statusEl.className = 'status running';
566
+ } else {
567
+ statusEl.textContent = '已停止';
568
+ statusEl.className = 'status stopped';
569
+ }
570
+
571
+ document.getElementById('server').textContent = data.serverUrl.replace('ws://', '').replace('wss://', '');
572
+ document.getElementById('connection').textContent = data.authenticated ? '已认证' : (data.connected ? '已连接' : '未连接');
573
+ document.getElementById('uptime').textContent = formatUptime(data.uptime);
574
+ document.getElementById('proxyCount').textContent = data.proxies.length;
575
+ document.getElementById('reconnectCount').textContent = data.reconnectAttempts;
576
+ document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString('zh-CN');
577
+
578
+ // 更新代理列表
579
+ const listEl = document.getElementById('proxiesList');
580
+ if (data.proxies.length === 0) {
581
+ listEl.innerHTML = '<div class="empty-state">暂无代理映射,点击上方按钮添加</div>';
582
+ } else {
583
+ listEl.innerHTML = data.proxies.map(p => {
584
+ const protocol = p.protocol === 'websocket' ? 'websocket' : 'http';
585
+ return \`
586
+ <div class="proxy-item">
587
+ <div class="proxy-info">
588
+ <span class="proxy-protocol \${protocol}">\${protocol === 'websocket' ? 'WS' : 'HTTP'}</span>
589
+ <span class="proxy-remote">:\${p.remotePort}</span>
590
+ <span class="proxy-arrow">→</span>
591
+ <span class="proxy-local">\${p.localHost || 'localhost'}:\${p.localPort}</span>
592
+ </div>
593
+ <button class="btn btn-danger" onclick="showDeleteModal(\${p.remotePort})">删除</button>
594
+ </div>
595
+ \`;
596
+ }).join('');
597
+ }
598
+ } catch (e) {
599
+ console.error('获取状态失败:', e);
600
+ }
601
+ }
602
+
603
+ function showDeleteModal(port) {
604
+ deletePort = port;
605
+ document.getElementById('deleteModal').classList.add('show');
606
+ }
607
+
608
+ document.getElementById('cancelDelete').addEventListener('click', () => {
609
+ document.getElementById('deleteModal').classList.remove('show');
610
+ deletePort = null;
611
+ });
612
+
613
+ document.getElementById('confirmDelete').addEventListener('click', async () => {
614
+ if (deletePort) {
615
+ try {
616
+ const res = await fetch(\`/_ctc/proxies/\${deletePort}\`, { method: 'DELETE' });
617
+ if (res.ok) {
618
+ showToast('代理已删除');
619
+ updateStatus();
620
+ } else {
621
+ const data = await res.json();
622
+ showToast(\`删除失败: \${data.error}\`, 'error');
623
+ }
624
+ } catch (e) {
625
+ showToast('删除失败: 网络错误', 'error');
626
+ }
627
+ }
628
+ document.getElementById('deleteModal').classList.remove('show');
629
+ deletePort = null;
630
+ });
631
+
632
+ document.getElementById('showAddForm').addEventListener('click', () => {
633
+ document.getElementById('addForm').classList.add('show');
634
+ });
635
+
636
+ document.getElementById('cancelAdd').addEventListener('click', () => {
637
+ document.getElementById('addForm').classList.remove('show');
638
+ });
639
+
640
+ document.getElementById('addProxy').addEventListener('click', async () => {
641
+ const remotePort = parseInt(document.getElementById('newRemotePort').value);
642
+ const protocol = document.getElementById('newProtocol').value;
643
+ const localPort = parseInt(document.getElementById('newLocalPort').value);
644
+ const localHost = document.getElementById('newLocalHost').value || 'localhost';
645
+
646
+ if (!remotePort || !localPort) {
647
+ showToast('请填写完整信息', 'error');
648
+ return;
649
+ }
650
+
651
+ try {
652
+ const res = await fetch('/_ctc/proxies', {
653
+ method: 'POST',
654
+ headers: { 'Content-Type': 'application/json' },
655
+ body: JSON.stringify({ remotePort, protocol, localPort, localHost })
656
+ });
657
+
658
+ if (res.ok) {
659
+ showToast('代理已添加');
660
+ document.getElementById('addForm').classList.remove('show');
661
+ document.getElementById('newRemotePort').value = '';
662
+ document.getElementById('newLocalPort').value = '';
663
+ document.getElementById('newLocalHost').value = 'localhost';
664
+ updateStatus();
665
+ } else {
666
+ const data = await res.json();
667
+ showToast(\`添加失败: \${data.error}\`, 'error');
668
+ }
669
+ } catch (e) {
670
+ showToast('添加失败: 网络错误', 'error');
671
+ }
672
+ });
673
+
674
+ updateStatus();
675
+ setInterval(updateStatus, 3000);
676
+ </script>
677
+ </body>
678
+ </html>
679
+ `;
680
+ //# sourceMappingURL=admin-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-server.js","sourceRoot":"","sources":["../src/admin-server.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @module cli
4
+ * @description 穿透客户端命令行工具模块。
5
+ * 提供 `feng3d-ctc` CLI 命令,支持启动、停止和查询客户端状态。
6
+ * 单实例模式:只允许一个客户端实例运行,多次 start 会向已运行进程添加端口映射。
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;GAKG"}