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.
@@ -0,0 +1,3 @@
1
+ import { ClashAPI } from '../api';
2
+ export declare function testProxy(api: ClashAPI, proxyName: string, testUrl?: string, timeout?: number): Promise<void>;
3
+ export declare function testGroup(api: ClashAPI, groupName: string, testUrl?: string, timeout?: number): Promise<void>;
@@ -0,0 +1,49 @@
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.testProxy = testProxy;
7
+ exports.testGroup = testGroup;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ async function testProxy(api, proxyName, testUrl, timeout) {
11
+ const spinner = (0, ora_1.default)(`测试 ${proxyName} 延迟...`).start();
12
+ try {
13
+ const delay = await api.testDelay(proxyName, testUrl, timeout);
14
+ const color = delay < 200 ? chalk_1.default.green : delay < 500 ? chalk_1.default.yellow : chalk_1.default.red;
15
+ spinner.succeed(`${proxyName}: ${color(delay + 'ms')}`);
16
+ }
17
+ catch (error) {
18
+ spinner.fail(`${proxyName}: ${chalk_1.default.red('超时')}`);
19
+ }
20
+ }
21
+ async function testGroup(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).sort((a, b) => {
27
+ if (a[1] <= 0)
28
+ return 1;
29
+ if (b[1] <= 0)
30
+ return -1;
31
+ return a[1] - b[1];
32
+ });
33
+ console.log(chalk_1.default.bold(`\n${groupName} 延迟测试:\n`));
34
+ for (const [name, delay] of sorted) {
35
+ if (delay <= 0) {
36
+ console.log(` ${name}: ${chalk_1.default.red('超时')}`);
37
+ }
38
+ else {
39
+ const color = delay < 200 ? chalk_1.default.green : delay < 500 ? chalk_1.default.yellow : chalk_1.default.red;
40
+ console.log(` ${name}: ${color(delay + 'ms')}`);
41
+ }
42
+ }
43
+ console.log();
44
+ }
45
+ catch (error) {
46
+ spinner.fail('测试失败');
47
+ throw error;
48
+ }
49
+ }
@@ -0,0 +1,12 @@
1
+ export interface Config {
2
+ host: string;
3
+ port: number;
4
+ secret?: string;
5
+ testUrl: string;
6
+ timeout: number;
7
+ vergeConfigDir: string;
8
+ }
9
+ export declare function ensureConfigDir(): void;
10
+ export declare function loadConfig(): Config;
11
+ export declare function saveConfig(config: Partial<Config>): void;
12
+ export declare function getConfigPath(): string;
package/dist/config.js ADDED
@@ -0,0 +1,84 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ensureConfigDir = ensureConfigDir;
37
+ exports.loadConfig = loadConfig;
38
+ exports.saveConfig = saveConfig;
39
+ exports.getConfigPath = getConfigPath;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
43
+ const CONFIG_DIR = path.join(os.homedir(), '.clash-switcher');
44
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
45
+ function getDefaultVergeConfigDir() {
46
+ const platform = os.platform();
47
+ if (platform === 'win32') {
48
+ const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
49
+ return path.join(appData, 'io.github.clash-verge-rev.clash-verge-rev');
50
+ }
51
+ else if (platform === 'darwin') {
52
+ return path.join(os.homedir(), 'Library', 'Application Support', 'io.github.clash-verge-rev.clash-verge-rev');
53
+ }
54
+ return path.join(os.homedir(), '.config', 'clash-verge');
55
+ }
56
+ const DEFAULT_CONFIG = {
57
+ host: '127.0.0.1',
58
+ port: 9097,
59
+ testUrl: 'http://www.gstatic.com/generate_204',
60
+ timeout: 5000,
61
+ vergeConfigDir: getDefaultVergeConfigDir(),
62
+ };
63
+ function ensureConfigDir() {
64
+ if (!fs.existsSync(CONFIG_DIR)) {
65
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
66
+ }
67
+ }
68
+ function loadConfig() {
69
+ ensureConfigDir();
70
+ if (fs.existsSync(CONFIG_FILE)) {
71
+ const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
72
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
73
+ }
74
+ return DEFAULT_CONFIG;
75
+ }
76
+ function saveConfig(config) {
77
+ ensureConfigDir();
78
+ const current = loadConfig();
79
+ const newConfig = { ...current, ...config };
80
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
81
+ }
82
+ function getConfigPath() {
83
+ return CONFIG_FILE;
84
+ }
package/dist/core.d.ts ADDED
@@ -0,0 +1,184 @@
1
+ import { ClashSwitcherConfig, ResolvedConfig, ProxyGroup, Profile, DelayTestResult, ClashMode, GroupsMode } from './types';
2
+ /**
3
+ * Clash Switcher 主类
4
+ * @description 用于控制 Clash Verge 代理切换的核心类
5
+ * @example
6
+ * ```typescript
7
+ * import { ClashSwitcher } from 'clash-switcher';
8
+ *
9
+ * const switcher = new ClashSwitcher({
10
+ * port: 9097,
11
+ * timeout: 3000,
12
+ * });
13
+ *
14
+ * // 获取所有代理组
15
+ * const groups = await switcher.getProxyGroups();
16
+ *
17
+ * // 切换节点
18
+ * await switcher.setNode('代理组名', '节点名');
19
+ * ```
20
+ */
21
+ export declare class ClashSwitcher {
22
+ private config;
23
+ private client;
24
+ /**
25
+ * 创建 ClashSwitcher 实例
26
+ * @param config - 配置选项
27
+ * @param config.host - API 主机地址,默认 '127.0.0.1'
28
+ * @param config.port - API 端口号,默认 9097
29
+ * @param config.secret - API 访问密钥,默认无
30
+ * @param config.testUrl - 延迟测试 URL,默认 'http://www.gstatic.com/generate_204'
31
+ * @param config.timeout - 超时时间(毫秒),默认 5000
32
+ * @param config.vergeConfigDir - Clash Verge 配置目录,默认自动检测
33
+ */
34
+ constructor(config?: ClashSwitcherConfig);
35
+ private createClient;
36
+ /**
37
+ * 从 Clash Verge 配置文件读取 secret
38
+ */
39
+ private readSecretFromConfig;
40
+ /**
41
+ * 更新配置
42
+ * @param config - 要更新的配置项,只需传入需要修改的字段
43
+ * @example
44
+ * ```typescript
45
+ * switcher.setConfig({ port: 9090 });
46
+ * switcher.setConfig({ timeout: 3000, testUrl: 'http://example.com' });
47
+ * ```
48
+ */
49
+ setConfig(config: ClashSwitcherConfig): void;
50
+ /**
51
+ * 获取当前配置
52
+ * @returns 当前完整配置对象
53
+ */
54
+ getConfig(): ResolvedConfig;
55
+ /**
56
+ * 获取当前代理模式
57
+ * @returns 当前模式:'rule'(规则) | 'global'(全局) | 'direct'(直连)
58
+ */
59
+ getMode(): Promise<ClashMode>;
60
+ /**
61
+ * 设置代理模式
62
+ * @param mode - 目标模式:'rule'(规则) | 'global'(全局) | 'direct'(直连)
63
+ */
64
+ setMode(mode: ClashMode): Promise<void>;
65
+ /**
66
+ * 获取所有代理信息
67
+ */
68
+ private getProxies;
69
+ /**
70
+ * 获取代理组列表
71
+ * @param mode - 模式筛选
72
+ * - 不传或 'active': 当前激活模式的代理组
73
+ * - 'rule': 规则模式的代理组(排除 GLOBAL)
74
+ * - 'global': 全局模式的代理组(仅 GLOBAL)
75
+ * - 'direct': 直连模式(返回空数组)
76
+ * - 'all': 所有代理组
77
+ * @param filter - 结果筛选
78
+ * - 不传: 返回所有匹配的代理组
79
+ * - number: 按索引位置返回(从 0 开始)
80
+ * - string: 按名称模糊匹配返回
81
+ * @returns 代理组列表,按配置文件顺序排序
82
+ */
83
+ getGroups(mode?: GroupsMode, filter?: number | string): Promise<ProxyGroup[]>;
84
+ /**
85
+ * 获取单个代理组
86
+ * @param mode - 模式筛选(同 getGroups)
87
+ * @param filter - 结果筛选
88
+ * - 不传: 返回第一个代理组(主代理组)
89
+ * - number: 按索引位置返回(从 0 开始)
90
+ * - string: 按名称模糊匹配返回第一个匹配的
91
+ * @returns 代理组对象,未找到返回 null
92
+ */
93
+ getGroup(mode?: GroupsMode, filter?: number | string): Promise<ProxyGroup | null>;
94
+ /**
95
+ * 从当前订阅配置文件读取代理组顺序
96
+ */
97
+ private getProxyGroupOrder;
98
+ /**
99
+ * 解析代理组(支持模糊匹配和索引)
100
+ */
101
+ private resolveProxyGroup;
102
+ /**
103
+ * 解析节点(支持模糊匹配和索引)
104
+ */
105
+ private resolveProxy;
106
+ /**
107
+ * 切换节点
108
+ * @param group - 代理组筛选:索引(number)或名称模糊匹配(string)
109
+ * @param node - 目标节点名称,支持模糊匹配
110
+ */
111
+ setNode(group: number | string, node: string): Promise<void>;
112
+ /**
113
+ * 获取代理组内的所有节点
114
+ * @param filter - 代理组筛选:不传返回主代理组,索引(number)或名称模糊匹配(string)
115
+ * @returns 节点名称列表
116
+ */
117
+ getNodes(filter?: number | string): Promise<string[]>;
118
+ /**
119
+ * 测试单个节点延迟
120
+ * @param node - 节点名称,支持模糊匹配
121
+ * @param url - 测试 URL,不传使用默认配置
122
+ * @param timeout - 超时时间(毫秒),不传使用默认配置
123
+ * @returns 延迟测试结果 { name: 完整节点名, delay: 延迟毫秒数 }
124
+ */
125
+ testNode(node: string, url?: string, timeout?: number): Promise<DelayTestResult>;
126
+ /**
127
+ * 测试多个节点延迟
128
+ * @param filter - 筛选参数
129
+ * - 不传: 测试主代理组的所有节点
130
+ * - number: 按索引指定代理组
131
+ * - string: 按名称模糊匹配代理组
132
+ * - string[]: 指定节点名称列表(可配合 getNodes 使用)
133
+ * @param url - 测试 URL,不传使用默认配置
134
+ * @param timeout - 超时时间(毫秒),不传使用默认配置
135
+ * @returns 延迟测试结果列表,delay 为 -1 表示超时
136
+ */
137
+ testNodes(filter?: number | string | string[], url?: string, timeout?: number): Promise<DelayTestResult[]>;
138
+ /**
139
+ * 自动选择最快节点
140
+ * @param filter - 代理组筛选:不传使用主代理组,索引(number)或名称模糊匹配(string)
141
+ * @param url - 测试 URL,不传使用默认配置
142
+ * @param timeout - 超时时间(毫秒),不传使用默认配置
143
+ * @returns 切换到的节点信息,无可用节点返回 null
144
+ */
145
+ autoNode(filter?: number | string, url?: string, timeout?: number): Promise<DelayTestResult | null>;
146
+ /**
147
+ * 获取所有订阅列表
148
+ * @returns 远程订阅列表(type 为 'remote' 的订阅)
149
+ */
150
+ getSubs(): Profile[];
151
+ private parseProfilesYaml;
152
+ /**
153
+ * 获取订阅
154
+ * @param filter - 筛选参数:不传返回当前激活订阅,索引(number)或名称/UID模糊匹配(string)
155
+ * @returns 订阅对象,未找到返回 null
156
+ */
157
+ getSub(filter?: number | string): Profile | null;
158
+ /**
159
+ * 切换订阅
160
+ * @param filter - 订阅筛选:索引(number)或名称/UID模糊匹配(string)
161
+ * @param restart - 是否重启 Clash Verge,默认 true
162
+ * @returns 切换到的订阅对象
163
+ */
164
+ setSub(filter: number | string, restart?: boolean): Promise<Profile>;
165
+ /**
166
+ * 等待 API 就绪
167
+ * @param timeout - 超时时间(毫秒),默认 10000
168
+ * @param interval - 检测间隔(毫秒),默认 200
169
+ * @param waitDisconnect - 是否先等待 API 断开,默认 false
170
+ */
171
+ waitReady(timeout?: number, interval?: number, waitDisconnect?: boolean): Promise<void>;
172
+ /**
173
+ * 检测进程是否存在
174
+ */
175
+ private isProcessRunning;
176
+ /**
177
+ * 等待进程退出
178
+ */
179
+ private waitForProcessExit;
180
+ /**
181
+ * 重启 Clash Verge 应用
182
+ */
183
+ restartClashVerge(): Promise<void>;
184
+ }