clash-switcher 0.0.1

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/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # clash-switcher
2
+
3
+ Clash Verge 代理切换工具,提供 API 库和命令行工具。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install clash-switcher
9
+ ```
10
+
11
+ ## API 使用
12
+
13
+ ### 初始化
14
+
15
+ ```typescript
16
+ import { ClashSwitcher } from 'clash-switcher';
17
+
18
+ const switcher = new ClashSwitcher({
19
+ host: '127.0.0.1', // 可选,默认 127.0.0.1
20
+ port: 9097, // 可选,默认 9097
21
+ secret: '', // 可选,API 密钥
22
+ testUrl: 'http://www.gstatic.com/generate_204', // 可选
23
+ timeout: 5000, // 可选,超时时间(ms)
24
+ });
25
+ ```
26
+
27
+ ### 配置管理
28
+
29
+ ```typescript
30
+ // 获取当前配置
31
+ const config = switcher.getConfig();
32
+
33
+ // 更新配置
34
+ switcher.setConfig({ port: 9090, timeout: 3000 });
35
+ ```
36
+
37
+ ### 模式管理
38
+
39
+ ```typescript
40
+ // 获取当前模式
41
+ const mode = await switcher.getMode(); // 'rule' | 'global' | 'direct'
42
+
43
+ // 设置模式
44
+ await switcher.setMode('rule');
45
+ ```
46
+
47
+ ### 代理组
48
+
49
+ ```typescript
50
+ // 获取所有代理组
51
+ const groups = await switcher.getGroups();
52
+
53
+ // 获取单个代理组
54
+ const group = await switcher.getGroup(); // 主代理组
55
+ const group = await switcher.getGroup(undefined, 0); // 按索引
56
+ const group = await switcher.getGroup(undefined, '选择'); // 模糊匹配
57
+ ```
58
+
59
+ ### 节点操作
60
+
61
+ ```typescript
62
+ // 获取节点列表
63
+ const nodes = await switcher.getNodes(); // 主代理组的节点
64
+ const nodes = await switcher.getNodes(0); // 按索引
65
+ const nodes = await switcher.getNodes('选择'); // 模糊匹配
66
+
67
+ // 切换节点
68
+ await switcher.setNode(0, '香港'); // 按索引指定组
69
+ await switcher.setNode('选择', '香港'); // 模糊匹配组名
70
+ ```
71
+
72
+ ### 延迟测试
73
+
74
+ ```typescript
75
+ // 测试单个节点
76
+ const result = await switcher.testNode('香港');
77
+ // { name: '🇭🇰 香港 01', delay: 120 }
78
+
79
+ // 测试代理组所有节点
80
+ const results = await switcher.testNodes(); // 主代理组
81
+ const results = await switcher.testNodes(0); // 按索引
82
+ const results = await switcher.testNodes('选择'); // 模糊匹配
83
+
84
+ // 测试指定节点列表
85
+ const nodes = await switcher.getNodes();
86
+ const results = await switcher.testNodes(nodes.filter(n => n !== 'DIRECT'));
87
+
88
+ // 自动选择最快节点
89
+ const best = await switcher.autoNode(); // 主代理组
90
+ const best = await switcher.autoNode(0); // 按索引
91
+ const best = await switcher.autoNode('选择'); // 模糊匹配
92
+ ```
93
+
94
+ ### 订阅管理
95
+
96
+ ```typescript
97
+ // 获取所有订阅
98
+ const subs = switcher.getSubs();
99
+
100
+ // 获取当前订阅
101
+ const current = switcher.getSub();
102
+
103
+ // 按索引或名称获取
104
+ const sub = switcher.getSub(0);
105
+ const sub = switcher.getSub('机场名');
106
+
107
+ // 切换订阅(默认重启)
108
+ await switcher.setSub(0);
109
+ await switcher.setSub('机场名');
110
+
111
+ // 切换但不重启
112
+ await switcher.setSub('机场名', false);
113
+ ```
114
+
115
+ ### 工具方法
116
+
117
+ ```typescript
118
+ // 等待 API 就绪
119
+ await switcher.waitReady();
120
+
121
+ // 重启 Clash Verge
122
+ await switcher.restartClashVerge();
123
+ ```
124
+
125
+ ## 命令行
126
+
127
+ 全局安装后可使用 `clash-switcher` 命令。
128
+
129
+ ### 模式
130
+
131
+ ```bash
132
+ # 查看当前模式
133
+ clash-switcher mode
134
+
135
+ # 设置模式
136
+ clash-switcher mode rule
137
+ clash-switcher m global
138
+ ```
139
+
140
+ ### 代理组
141
+
142
+ ```bash
143
+ # 列出所有代理组
144
+ clash-switcher groups
145
+ clash-switcher g
146
+ ```
147
+
148
+ ### 节点
149
+
150
+ ```bash
151
+ # 列出节点(主代理组)
152
+ clash-switcher nodes
153
+ clash-switcher n
154
+
155
+ # 按索引或名称
156
+ clash-switcher n 0
157
+ clash-switcher n 选择
158
+ ```
159
+
160
+ ### 切换节点
161
+
162
+ ```bash
163
+ # 切换节点(组 + 节点)
164
+ clash-switcher set 0 香港
165
+ clash-switcher s 选择 日本
166
+ ```
167
+
168
+ ### 测试延迟
169
+
170
+ ```bash
171
+ # 测试主代理组
172
+ clash-switcher test
173
+ clash-switcher t
174
+
175
+ # 按索引或名称
176
+ clash-switcher t 0
177
+ clash-switcher t 选择
178
+
179
+ # 自定义测试参数
180
+ clash-switcher t -u http://example.com -t 3000
181
+ ```
182
+
183
+ ### 自动选择
184
+
185
+ ```bash
186
+ # 自动选择最快节点
187
+ clash-switcher auto
188
+ clash-switcher a
189
+
190
+ # 指定代理组
191
+ clash-switcher a 0
192
+ clash-switcher a 选择
193
+ ```
194
+
195
+ ### 订阅
196
+
197
+ ```bash
198
+ # 列出订阅
199
+ clash-switcher subs
200
+
201
+ # 切换订阅
202
+ clash-switcher sub 0
203
+ clash-switcher sub 机场名
204
+
205
+ # 切换但不重启
206
+ clash-switcher sub 机场名 --no-restart
207
+ ```
208
+
209
+ ### 配置
210
+
211
+ ```bash
212
+ # 查看配置
213
+ clash-switcher config
214
+ clash-switcher c
215
+
216
+ # 设置配置
217
+ clash-switcher c host 192.168.1.100
218
+ clash-switcher c port 9090
219
+ clash-switcher c timeout 3000
220
+ ```
package/dist/api.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ export interface ProxyGroup {
2
+ name: string;
3
+ type: string;
4
+ now: string;
5
+ all: string[];
6
+ }
7
+ export interface ProxyInfo {
8
+ name: string;
9
+ type: string;
10
+ history: {
11
+ delay: number;
12
+ }[];
13
+ }
14
+ export interface ClashConfig {
15
+ port: number;
16
+ 'socks-port': number;
17
+ 'mixed-port': number;
18
+ mode: string;
19
+ }
20
+ export declare class ClashAPI {
21
+ private client;
22
+ private baseURL;
23
+ constructor(host?: string, port?: number, secret?: string);
24
+ getConfig(): Promise<ClashConfig>;
25
+ getProxies(): Promise<Record<string, ProxyInfo | ProxyGroup>>;
26
+ getProxyGroups(): Promise<ProxyGroup[]>;
27
+ switchProxy(group: string, proxy: string): Promise<void>;
28
+ testDelay(proxy: string, url?: string, timeout?: number): Promise<number>;
29
+ testGroupDelays(group: string, url?: string, timeout?: number): Promise<Record<string, number>>;
30
+ }
package/dist/api.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ClashAPI = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ class ClashAPI {
9
+ constructor(host = '127.0.0.1', port = 9097, secret) {
10
+ this.baseURL = `http://${host}:${port}`;
11
+ this.client = axios_1.default.create({
12
+ baseURL: this.baseURL,
13
+ timeout: 10000,
14
+ headers: secret ? { Authorization: `Bearer ${secret}` } : {},
15
+ });
16
+ }
17
+ async getConfig() {
18
+ const { data } = await this.client.get('/configs');
19
+ return data;
20
+ }
21
+ async getProxies() {
22
+ const { data } = await this.client.get('/proxies');
23
+ return data.proxies;
24
+ }
25
+ async getProxyGroups() {
26
+ const proxies = await this.getProxies();
27
+ return Object.values(proxies).filter((p) => 'all' in p && Array.isArray(p.all));
28
+ }
29
+ async switchProxy(group, proxy) {
30
+ await this.client.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy });
31
+ }
32
+ async testDelay(proxy, url, timeout) {
33
+ const testUrl = url || 'http://www.gstatic.com/generate_204';
34
+ const { data } = await this.client.get(`/proxies/${encodeURIComponent(proxy)}/delay`, { params: { url: testUrl, timeout: timeout || 5000 } });
35
+ return data.delay;
36
+ }
37
+ async testGroupDelays(group, url, timeout) {
38
+ const testUrl = url || 'http://www.gstatic.com/generate_204';
39
+ const { data } = await this.client.get(`/group/${encodeURIComponent(group)}/delay`, { params: { url: testUrl, timeout: timeout || 5000 } });
40
+ return data;
41
+ }
42
+ }
43
+ exports.ClashAPI = ClashAPI;
@@ -0,0 +1,2 @@
1
+ export declare function showConfig(): void;
2
+ export declare function setConfig(key: string, value: string): void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.showConfig = showConfig;
7
+ exports.setConfig = setConfig;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const config_1 = require("../config");
10
+ function showConfig() {
11
+ const config = (0, config_1.loadConfig)();
12
+ console.log(chalk_1.default.bold('\n当前配置:\n'));
13
+ console.log(` 配置文件: ${chalk_1.default.gray((0, config_1.getConfigPath)())}`);
14
+ console.log(` 主机 (host): ${chalk_1.default.cyan(config.host)}`);
15
+ console.log(` 端口 (port): ${chalk_1.default.cyan(config.port)}`);
16
+ console.log(` 密钥 (secret): ${chalk_1.default.cyan(config.secret || '(未设置)')}`);
17
+ console.log(` 测试URL (testUrl): ${chalk_1.default.cyan(config.testUrl)}`);
18
+ console.log(` 超时 (timeout): ${chalk_1.default.cyan(config.timeout + 'ms')}`);
19
+ console.log(` Verge配置目录 (vergeConfigDir): ${chalk_1.default.cyan(config.vergeConfigDir)}`);
20
+ console.log();
21
+ }
22
+ function setConfig(key, value) {
23
+ const numericKeys = ['port', 'timeout'];
24
+ const parsedValue = numericKeys.includes(key) ? parseInt(value, 10) : value;
25
+ (0, config_1.saveConfig)({ [key]: parsedValue });
26
+ console.log(chalk_1.default.green(`已设置 ${key} = ${value}`));
27
+ }
@@ -0,0 +1,3 @@
1
+ import { ClashAPI } from '../api';
2
+ export declare function listGroups(api: ClashAPI): Promise<void>;
3
+ export declare function listProxies(api: ClashAPI, groupName: string): Promise<void>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listGroups = listGroups;
7
+ exports.listProxies = listProxies;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ async function listGroups(api) {
11
+ const spinner = (0, ora_1.default)('获取代理组...').start();
12
+ try {
13
+ const groups = await api.getProxyGroups();
14
+ spinner.stop();
15
+ console.log(chalk_1.default.bold('\n代理组列表:\n'));
16
+ for (const group of groups) {
17
+ console.log(chalk_1.default.cyan(` ${group.name}`));
18
+ console.log(chalk_1.default.gray(` 类型: ${group.type}`));
19
+ console.log(chalk_1.default.green(` 当前: ${group.now}`));
20
+ console.log(chalk_1.default.gray(` 节点数: ${group.all.length}`));
21
+ console.log();
22
+ }
23
+ }
24
+ catch (error) {
25
+ spinner.fail('获取代理组失败');
26
+ throw error;
27
+ }
28
+ }
29
+ async function listProxies(api, groupName) {
30
+ const spinner = (0, ora_1.default)('获取节点列表...').start();
31
+ try {
32
+ const groups = await api.getProxyGroups();
33
+ const group = groups.find((g) => g.name === groupName);
34
+ if (!group) {
35
+ spinner.fail(`未找到代理组: ${groupName}`);
36
+ return;
37
+ }
38
+ spinner.stop();
39
+ console.log(chalk_1.default.bold(`\n${groupName} 节点列表:\n`));
40
+ for (const proxy of group.all) {
41
+ const isCurrent = proxy === group.now;
42
+ const prefix = isCurrent ? chalk_1.default.green('* ') : ' ';
43
+ console.log(`${prefix}${isCurrent ? chalk_1.default.green(proxy) : proxy}`);
44
+ }
45
+ console.log();
46
+ }
47
+ catch (error) {
48
+ spinner.fail('获取节点列表失败');
49
+ throw error;
50
+ }
51
+ }
@@ -0,0 +1,16 @@
1
+ interface Profile {
2
+ uid: string;
3
+ type: 'remote' | 'local' | 'merge' | 'script';
4
+ name?: string;
5
+ desc?: string;
6
+ url?: string;
7
+ updated?: number;
8
+ }
9
+ interface ProfilesConfig {
10
+ current?: string[];
11
+ items?: Profile[];
12
+ }
13
+ export declare function loadProfiles(): ProfilesConfig;
14
+ export declare function listProfiles(): Promise<void>;
15
+ export declare function switchProfile(nameOrUid: string, restart?: boolean): Promise<void>;
16
+ export {};
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.loadProfiles = loadProfiles;
40
+ exports.listProfiles = listProfiles;
41
+ exports.switchProfile = switchProfile;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const os = __importStar(require("os"));
45
+ const child_process_1 = require("child_process");
46
+ const util_1 = require("util");
47
+ const chalk_1 = __importDefault(require("chalk"));
48
+ const ora_1 = __importDefault(require("ora"));
49
+ const config_1 = require("../config");
50
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
51
+ function getProfilesPath() {
52
+ const config = (0, config_1.loadConfig)();
53
+ return path.join(config.vergeConfigDir, 'profiles.yaml');
54
+ }
55
+ function parseYamlSimple(content) {
56
+ const result = { items: [] };
57
+ const lines = content.split('\n');
58
+ let inItems = false;
59
+ let currentItem = null;
60
+ for (const line of lines) {
61
+ // 解析 current 字段(单个值或数组)
62
+ if (line.startsWith('current:')) {
63
+ const value = line.slice(8).trim();
64
+ if (value) {
65
+ result.current = [value];
66
+ }
67
+ else {
68
+ result.current = [];
69
+ }
70
+ continue;
71
+ }
72
+ // current 数组项
73
+ if (result.current && line.startsWith('- ') && !inItems) {
74
+ result.current.push(line.slice(2).trim());
75
+ continue;
76
+ }
77
+ // items 开始
78
+ if (line.trim() === 'items:') {
79
+ inItems = true;
80
+ continue;
81
+ }
82
+ if (inItems) {
83
+ // 新的 item 开始
84
+ if (line.startsWith('- ')) {
85
+ if (currentItem && currentItem.uid) {
86
+ result.items?.push(currentItem);
87
+ }
88
+ currentItem = {};
89
+ const match = line.slice(2).match(/^(\w+):\s*(.*)$/);
90
+ if (match) {
91
+ const value = match[2].trim();
92
+ if (value && value !== 'null') {
93
+ currentItem[match[1]] = value;
94
+ }
95
+ }
96
+ }
97
+ // item 的属性(顶级属性,2空格缩进)
98
+ else if (line.startsWith(' ') && !line.startsWith(' ') && currentItem) {
99
+ const match = line.trim().match(/^(\w+):\s*(.*)$/);
100
+ if (match) {
101
+ const key = match[1];
102
+ let value = match[2].trim();
103
+ if (value === 'null' || value === '') {
104
+ value = undefined;
105
+ }
106
+ else if (value === 'true') {
107
+ value = true;
108
+ }
109
+ else if (value === 'false') {
110
+ value = false;
111
+ }
112
+ else if (/^\d+$/.test(value)) {
113
+ value = parseInt(value);
114
+ }
115
+ if (value !== undefined) {
116
+ currentItem[key] = value;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ // 添加最后一个 item
123
+ if (currentItem && currentItem.uid) {
124
+ result.items?.push(currentItem);
125
+ }
126
+ return result;
127
+ }
128
+ function loadProfiles() {
129
+ const profilesPath = getProfilesPath();
130
+ if (!fs.existsSync(profilesPath)) {
131
+ throw new Error(`未找到 Clash Verge 配置文件: ${profilesPath}`);
132
+ }
133
+ const content = fs.readFileSync(profilesPath, 'utf-8');
134
+ return parseYamlSimple(content);
135
+ }
136
+ async function listProfiles() {
137
+ const spinner = (0, ora_1.default)('读取订阅列表...').start();
138
+ try {
139
+ const config = loadProfiles();
140
+ spinner.stop();
141
+ if (!config.items || config.items.length === 0) {
142
+ console.log(chalk_1.default.yellow('\n没有找到任何订阅配置'));
143
+ return;
144
+ }
145
+ console.log(chalk_1.default.bold('\n订阅列表:\n'));
146
+ // 只显示 remote 类型的订阅
147
+ const remoteProfiles = config.items.filter(item => item.type === 'remote');
148
+ if (remoteProfiles.length === 0) {
149
+ console.log(chalk_1.default.yellow('没有找到远程订阅'));
150
+ return;
151
+ }
152
+ for (const item of remoteProfiles) {
153
+ const isCurrent = config.current?.includes(item.uid);
154
+ const prefix = isCurrent ? chalk_1.default.green('* ') : ' ';
155
+ const name = item.name || item.uid;
156
+ console.log(`${prefix}${isCurrent ? chalk_1.default.green(name) : name}`);
157
+ console.log(chalk_1.default.gray(` UID: ${item.uid}`));
158
+ console.log(chalk_1.default.gray(` 类型: ${item.type}`));
159
+ if (item.desc) {
160
+ console.log(chalk_1.default.gray(` 描述: ${item.desc}`));
161
+ }
162
+ if (item.url) {
163
+ console.log(chalk_1.default.gray(` URL: ${item.url.slice(0, 50)}...`));
164
+ }
165
+ console.log();
166
+ }
167
+ }
168
+ catch (error) {
169
+ spinner.fail('读取订阅失败');
170
+ throw error;
171
+ }
172
+ }
173
+ async function restartClashVerge() {
174
+ const platform = os.platform();
175
+ if (platform === 'win32') {
176
+ // Windows: 先获取进程路径,再关闭并重启
177
+ const { stdout } = await execAsync('powershell -Command "Get-Process clash-verge -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path"');
178
+ const appPath = stdout.trim();
179
+ if (!appPath) {
180
+ throw new Error('未找到运行中的 Clash Verge 进程');
181
+ }
182
+ await execAsync('taskkill /IM "clash-verge.exe" /F').catch(() => { });
183
+ await new Promise(resolve => setTimeout(resolve, 1500));
184
+ (0, child_process_1.exec)(`start "" "${appPath}"`);
185
+ }
186
+ else if (platform === 'darwin') {
187
+ // macOS
188
+ await execAsync('pkill -x "Clash Verge"').catch(() => { });
189
+ await new Promise(resolve => setTimeout(resolve, 1000));
190
+ (0, child_process_1.exec)('open -a "Clash Verge"');
191
+ }
192
+ else {
193
+ // Linux
194
+ await execAsync('pkill -x clash-verge').catch(() => { });
195
+ await new Promise(resolve => setTimeout(resolve, 1000));
196
+ (0, child_process_1.exec)('clash-verge &');
197
+ }
198
+ }
199
+ async function switchProfile(nameOrUid, restart = false) {
200
+ const spinner = (0, ora_1.default)('切换订阅...').start();
201
+ try {
202
+ const config = loadProfiles();
203
+ const profilesPath = getProfilesPath();
204
+ // 只在 remote 类型中查找,支持模糊匹配
205
+ const remoteProfiles = config.items?.filter(p => p.type === 'remote') || [];
206
+ const keyword = nameOrUid.toLowerCase();
207
+ const profile = remoteProfiles.find(p => p.uid === nameOrUid ||
208
+ p.name?.toLowerCase().includes(keyword));
209
+ if (!profile) {
210
+ spinner.fail(`未找到订阅: ${nameOrUid}`);
211
+ console.log(chalk_1.default.gray('\n可用的订阅:'));
212
+ remoteProfiles.forEach(p => {
213
+ console.log(chalk_1.default.gray(` - ${p.name || p.uid} (${p.uid})`));
214
+ });
215
+ return;
216
+ }
217
+ // 读取原始文件内容并替换 current 字段
218
+ let content = fs.readFileSync(profilesPath, 'utf-8');
219
+ content = content.replace(/^current:.*$/m, `current: ${profile.uid}`);
220
+ fs.writeFileSync(profilesPath, content, 'utf-8');
221
+ spinner.succeed(chalk_1.default.green(`已切换到: ${profile.name || profile.uid}`));
222
+ if (restart) {
223
+ const restartSpinner = (0, ora_1.default)('重启 Clash Verge...').start();
224
+ try {
225
+ await restartClashVerge();
226
+ restartSpinner.succeed(chalk_1.default.green('Clash Verge 已重启'));
227
+ }
228
+ catch {
229
+ restartSpinner.fail('重启失败,请手动重启 Clash Verge');
230
+ }
231
+ }
232
+ else {
233
+ console.log(chalk_1.default.yellow('\n提示: 使用 -r 参数可自动重启 Clash Verge'));
234
+ }
235
+ }
236
+ catch (error) {
237
+ spinner.fail('切换订阅失败');
238
+ throw error;
239
+ }
240
+ }
@@ -0,0 +1,3 @@
1
+ import { ClashAPI } from '../api';
2
+ export declare function switchProxy(api: ClashAPI, groupName: string, proxyName: string): Promise<void>;
3
+ export declare function testAndSwitch(api: ClashAPI, groupName: string, testUrl?: string, timeout?: number): Promise<void>;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.switchProxy = switchProxy;
7
+ exports.testAndSwitch = testAndSwitch;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ async function switchProxy(api, groupName, proxyName) {
11
+ const spinner = (0, ora_1.default)(`切换到 ${proxyName}...`).start();
12
+ try {
13
+ await api.switchProxy(groupName, proxyName);
14
+ spinner.succeed(chalk_1.default.green(`已切换到: ${proxyName}`));
15
+ }
16
+ catch (error) {
17
+ spinner.fail('切换失败');
18
+ throw error;
19
+ }
20
+ }
21
+ async function testAndSwitch(api, groupName, testUrl, timeout) {
22
+ const spinner = (0, ora_1.default)('测试所有节点延迟...').start();
23
+ try {
24
+ const delays = await api.testGroupDelays(groupName, testUrl, timeout);
25
+ spinner.stop();
26
+ const sorted = Object.entries(delays)
27
+ .filter(([_, delay]) => delay > 0)
28
+ .sort((a, b) => a[1] - b[1]);
29
+ if (sorted.length === 0) {
30
+ console.log(chalk_1.default.red('没有可用的节点'));
31
+ return;
32
+ }
33
+ console.log(chalk_1.default.bold('\n延迟测试结果:\n'));
34
+ sorted.slice(0, 10).forEach(([name, delay], index) => {
35
+ const color = delay < 200 ? chalk_1.default.green : delay < 500 ? chalk_1.default.yellow : chalk_1.default.red;
36
+ console.log(` ${index + 1}. ${name}: ${color(delay + 'ms')}`);
37
+ });
38
+ const [bestProxy, bestDelay] = sorted[0];
39
+ console.log(chalk_1.default.bold(`\n最快节点: ${chalk_1.default.green(bestProxy)} (${bestDelay}ms)`));
40
+ const switchSpinner = (0, ora_1.default)(`切换到 ${bestProxy}...`).start();
41
+ await api.switchProxy(groupName, bestProxy);
42
+ switchSpinner.succeed(chalk_1.default.green(`已切换到最快节点: ${bestProxy}`));
43
+ }
44
+ catch (error) {
45
+ spinner.fail('测试失败');
46
+ throw error;
47
+ }
48
+ }