whistle.easy-proxy 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.
@@ -0,0 +1,352 @@
1
+ const api = require('../rule-api');
2
+ const cfg = require('../../config');
3
+ const { parse, buildEditContent, findUnknownOperators } = require('../rule-editor');
4
+ const {
5
+ warnWhistleNotRunning,
6
+ editInEditor,
7
+ promptConfirm,
8
+ info,
9
+ ok,
10
+ warn,
11
+ err,
12
+ title,
13
+ sep,
14
+ c,
15
+ } = require('../helpers');
16
+
17
+ function registerRule(program) {
18
+ const ruleCmd = program
19
+ .command('rule')
20
+ .description('Whistle 规则管理(离线可用,启动后自动同步)');
21
+
22
+ ruleCmd
23
+ .command('list')
24
+ .description('列出所有规则集')
25
+ .action(async () => {
26
+ const running = warnWhistleNotRunning();
27
+ try {
28
+ let data, source;
29
+ if (running) {
30
+ data = await api.listRulesets();
31
+ source = 'whistle(已连接)';
32
+ } else {
33
+ const rules = api.loadOfflineRulesets();
34
+ data = {
35
+ defaultRules: api.getOfflineDefaultRules(),
36
+ list: rules,
37
+ enabledCount: rules.filter((r) => r.enabled).length,
38
+ };
39
+ source = '本地配置';
40
+ }
41
+ title('Whistle 规则集');
42
+ sep();
43
+ const defaultOn = !data.defaultRulesIsDisabled;
44
+ info(
45
+ (defaultOn ? `${c.green}✓` : `${c.red}✗`) +
46
+ c.reset +
47
+ ' 默认规则' +
48
+ c.gray +
49
+ '(内置)' +
50
+ c.reset
51
+ );
52
+ if (data.defaultRules) {
53
+ const lines = data.defaultRules.split('\n').filter((l) => l.trim());
54
+ lines.slice(0, 3).forEach((l) => console.log(`${c.gray} ${l}${c.reset}`));
55
+ if (lines.length > 3)
56
+ console.log(`${c.gray} ...(还有 ${lines.length - 3} 行)${c.reset}`);
57
+ }
58
+ const list = data.list || [];
59
+ const groups = {};
60
+ const ungrouped = [];
61
+ let currentGroup = null;
62
+ list.forEach((item) => {
63
+ if (item.name && item.name.charCodeAt(0) === 13) {
64
+ currentGroup = item.name;
65
+ groups[currentGroup] = [];
66
+ } else if (currentGroup) {
67
+ groups[currentGroup].push(item);
68
+ } else {
69
+ ungrouped.push(item);
70
+ }
71
+ });
72
+ if (groups[api.SYSTEM_GROUP]) {
73
+ console.log(`${c.blue}⚙ ${api.SYSTEM_GROUP.slice(1)}${c.reset}`);
74
+ groups[api.SYSTEM_GROUP].forEach((item) => {
75
+ const on = item.selected;
76
+ console.log(` ${on ? `${c.green}✓` : `${c.red}✗`}${c.reset} ${item.name}`);
77
+ });
78
+ }
79
+ if (groups[api.USER_GROUP]) {
80
+ console.log(`${c.blue}✎ ${api.USER_GROUP.slice(1)}${c.reset}`);
81
+ groups[api.USER_GROUP].forEach((item) => {
82
+ const on = item.selected;
83
+ console.log(` ${on ? `${c.green}✓` : `${c.red}✗`}${c.reset} ${item.name}`);
84
+ const data = item.data || item.content;
85
+ if (data) {
86
+ const lines = data.split('\n').filter((l) => l.trim());
87
+ lines.slice(0, 3).forEach((l) => console.log(`${c.gray} ${l}${c.reset}`));
88
+ if (lines.length > 3)
89
+ console.log(`${c.gray} ...(还有 ${lines.length - 3} 行)${c.reset}`);
90
+ }
91
+ });
92
+ }
93
+ if (ungrouped.length > 0) {
94
+ console.log(`${c.blue}📂 其他规则${c.reset}`);
95
+ ungrouped.forEach((item) => {
96
+ const on = item.selected;
97
+ console.log(` ${on ? `${c.green}✓` : `${c.red}✗`}${c.reset} ${item.name}`);
98
+ });
99
+ }
100
+ sep();
101
+ info(`共 ${list.length + 1} 个规则集(已启用 ${data.enabledCount} 个)`);
102
+ info(`来源: ${source}`);
103
+ if (running) info('Web 界面: http://local.whistlejs.com/#rules');
104
+ } catch (e) {
105
+ err(e.message);
106
+ process.exit(1);
107
+ }
108
+ });
109
+
110
+ ruleCmd
111
+ .command('add <name> [pattern] [target]')
112
+ .description('新增规则集(打开 $EDITOR,或直接指定 pattern+target)')
113
+ .action(async (name, pattern, target) => {
114
+ if (name === 'Default') {
115
+ err('不能添加名为 "Default" 的规则集 — 请使用: easy-proxy rule edit Default');
116
+ return;
117
+ }
118
+ const running = warnWhistleNotRunning();
119
+ const api = require('../rule-api');
120
+ try {
121
+ let content;
122
+ if (pattern && target) {
123
+ content = `${pattern} ${target}`;
124
+ // 校验直接传入的内容
125
+ try {
126
+ parse(content);
127
+ } catch (e) {
128
+ err(e.message);
129
+ return;
130
+ }
131
+ } else {
132
+ // 打开编辑器,带语法提示和校验循环
133
+ let editText = buildEditContent('');
134
+ while (true) {
135
+ try {
136
+ editText = editInEditor(editText, name);
137
+ } catch (_e) {
138
+ info('已取消');
139
+ return;
140
+ }
141
+ try {
142
+ parse(editText);
143
+ break;
144
+ } catch (e) {
145
+ err(e.message);
146
+ warn('解析失败,请修正后重新保存...');
147
+ }
148
+ }
149
+ // 从编辑内容中提取有效规则(去掉注释头部)
150
+ content = editText;
151
+ }
152
+ if (!content.trim()) {
153
+ info('内容为空,已取消');
154
+ return;
155
+ }
156
+
157
+ if (running) {
158
+ const data = await api.listRulesets();
159
+ const exists = (data.list || []).some((r) => r.name === name);
160
+ if (exists) {
161
+ err(`规则集 "${name}" 已存在 — 请使用: easy-proxy rule edit ${name}`);
162
+ return;
163
+ }
164
+ await api.createGroup(api.USER_GROUP);
165
+ await api.addRuleset(name, content, api.USER_GROUP);
166
+ }
167
+
168
+ if (api.loadOfflineRulesets().some((r) => r.name === name)) {
169
+ err(`规则集 "${name}" 已存在`);
170
+ return;
171
+ }
172
+ cfg.saveRuleset(name, content, false, api.USER_GROUP);
173
+ ok(`规则集 "${name}" 已添加: ${content}`);
174
+ } catch (e) {
175
+ err(e.message);
176
+ process.exit(1);
177
+ }
178
+ });
179
+
180
+ ruleCmd
181
+ .command('edit <name>')
182
+ .description('编辑规则集内容(打开 $EDITOR,带语法提示和校验)')
183
+ .action(async (name) => {
184
+ const running = warnWhistleNotRunning();
185
+ try {
186
+ let current = '';
187
+ if (running) {
188
+ const data = await api.listRulesets();
189
+ if (name === 'Default') current = data.defaultRules || '';
190
+ else {
191
+ const item = (data.list || []).find((r) => r.name === name);
192
+ if (!item) {
193
+ err(`规则集 "${name}" 不存在`);
194
+ return;
195
+ }
196
+ current = item.data || '';
197
+ }
198
+ } else {
199
+ if (name === 'Default') current = api.getOfflineDefaultRules() || '';
200
+ else {
201
+ const item = api.loadOfflineRulesets().find((r) => r.name === name);
202
+ if (!item) {
203
+ err(`规则集 "${name}" 不存在`);
204
+ return;
205
+ }
206
+ current = item.content || '';
207
+ }
208
+ }
209
+
210
+ // 打开编辑器,带注释头部和语法校验循环
211
+ let editText = buildEditContent(current);
212
+ let parsedRules;
213
+ while (true) {
214
+ try {
215
+ editText = editInEditor(editText, name);
216
+ } catch (_e) {
217
+ info('已取消');
218
+ return;
219
+ }
220
+ try {
221
+ parsedRules = parse(editText);
222
+ break;
223
+ } catch (e) {
224
+ err(e.message);
225
+ warn('解析失败,请修正后重新保存...');
226
+ }
227
+ }
228
+
229
+ // 重新格式化回纯规则文本(去掉注释头部)
230
+ const { format } = require('../rule-editor');
231
+ const content = format(parsedRules);
232
+
233
+ if (content === current.trim()) {
234
+ info('无改动');
235
+ return;
236
+ }
237
+
238
+ // 未知 operator 警告(不阻塞)
239
+ const unknown = findUnknownOperators(parsedRules);
240
+ if (unknown.length > 0) {
241
+ warn(
242
+ `注意:${unknown.length} 个操作符未识别:${unknown.map((u) => u.operator).join(', ')}`
243
+ );
244
+ info('保存继续,如需取消请按 Ctrl+C');
245
+ }
246
+
247
+ if (running) await api.addRuleset(name, content);
248
+ if (name === 'Default') cfg.saveRuleset('default', content, true, '');
249
+ else {
250
+ const r = cfg.getRuleset(name);
251
+ if (r) cfg.saveRuleset(name, content, r.enabled, r.group);
252
+ }
253
+ ok(`规则集 "${name}" 已更新(${parsedRules.length} 条规则)`);
254
+ } catch (e) {
255
+ err(e.message);
256
+ process.exit(1);
257
+ }
258
+ });
259
+
260
+ ruleCmd
261
+ .command('remove <name>')
262
+ .description('移除规则集')
263
+ .action(async (name) => {
264
+ if (name === 'Default') {
265
+ err('不能移除默认规则集');
266
+ return;
267
+ }
268
+ const running = warnWhistleNotRunning();
269
+ try {
270
+ if (running) {
271
+ const data = await api.listRulesets();
272
+ if (!(data.list || []).some((r) => r.name === name)) {
273
+ err(`规则集 "${name}" 不存在`);
274
+ return;
275
+ }
276
+ const confirmed = await promptConfirm(`删除规则集 "${name}"?(y/N) `);
277
+ if (!confirmed) {
278
+ info('已取消');
279
+ return;
280
+ }
281
+ await api.removeRuleset(name);
282
+ }
283
+ if (!cfg.getRuleset(name)) {
284
+ err(`规则集 "${name}" 不存在`);
285
+ return;
286
+ }
287
+ cfg.deleteRuleset(name);
288
+ ok(`规则集 "${name}" 已移除`);
289
+ } catch (e) {
290
+ err(e.message);
291
+ process.exit(1);
292
+ }
293
+ });
294
+
295
+ ruleCmd
296
+ .command('enable <name>')
297
+ .description('启用规则集')
298
+ .action(async (name) => {
299
+ const running = warnWhistleNotRunning();
300
+ try {
301
+ if (running) {
302
+ if (name === 'Default') await api.enableDefault();
303
+ else {
304
+ const data = await api.listRulesets();
305
+ const item = (data.list || []).find((r) => r.name === name);
306
+ if (!item) {
307
+ err(`规则集 "${name}" 不存在`);
308
+ return;
309
+ }
310
+ await api.selectRuleset(name, item.data || '');
311
+ }
312
+ }
313
+ const r = cfg.getRuleset(name);
314
+ if (r) cfg.saveRuleset(name, r.content, true, r.group);
315
+ ok(`规则集 "${name}" 已启用`);
316
+ } catch (e) {
317
+ err(e.message);
318
+ process.exit(1);
319
+ }
320
+ });
321
+
322
+ ruleCmd
323
+ .command('disable <name>')
324
+ .description('关闭规则集')
325
+ .action(async (name) => {
326
+ const running = warnWhistleNotRunning();
327
+ try {
328
+ if (running) {
329
+ if (name === 'Default') await api.disableDefault();
330
+ else {
331
+ const data = await api.listRulesets();
332
+ const item = (data.list || []).find((r) => r.name === name);
333
+ if (!item) {
334
+ err(`规则集 "${name}" 不存在`);
335
+ return;
336
+ }
337
+ await api.unselectRuleset(name, item.data || '');
338
+ }
339
+ }
340
+ const r = cfg.getRuleset(name);
341
+ if (r) cfg.saveRuleset(name, r.content, false, r.group);
342
+ ok(`规则集 "${name}" 已关闭`);
343
+ } catch (e) {
344
+ err(e.message);
345
+ process.exit(1);
346
+ }
347
+ });
348
+ }
349
+
350
+ module.exports = {
351
+ registerRule,
352
+ };
@@ -0,0 +1,98 @@
1
+ const { localW2Path, ok, err, info, debug } = require('../helpers');
2
+ const { enableSystemProxy, disableSystemProxy } = require('./proxy');
3
+
4
+ function registerStart(program) {
5
+ program
6
+ .command('start')
7
+ .description('启动 whistle(自动设置代理和安装证书)')
8
+ .option('--init', '初始化(安装证书 + 设置代理)')
9
+ .action(async (opts) => {
10
+ const { execSync } = require('node:child_process');
11
+ try {
12
+ const args = opts.init ? 'start --init' : 'start';
13
+ execSync(`"${localW2Path}" ${args}`, { stdio: 'inherit' });
14
+ ok('whistle 已启动');
15
+
16
+ try {
17
+ enableSystemProxy();
18
+ } catch (e) {
19
+ err(`开启系统代理失败: ${e.message}`);
20
+ }
21
+
22
+ const upstream = require('../upstream-proxy');
23
+ const proxies = upstream.loadProxies();
24
+ if (proxies.length > 0) {
25
+ info('同步上游代理规则...');
26
+ try {
27
+ await upstream.syncToWhistle(require('../rule-api'), proxies);
28
+ ok(`已同步 ${proxies.length} 个上游代理规则`);
29
+ } catch (e) {
30
+ err(`上游代理规则同步失败: ${e.message}`);
31
+ }
32
+ }
33
+
34
+ const api = require('../rule-api');
35
+ const rules = api.loadOfflineRulesets();
36
+ if (rules.length > 0) {
37
+ info('同步自定义规则...');
38
+ let count = 0;
39
+ for (const r of rules) {
40
+ try {
41
+ await api.addRuleset(r.name, r.content, r.group);
42
+ if (r.enabled) await api.selectRuleset(r.name, r.content);
43
+ count++;
44
+ } catch (e) {
45
+ debug('Rule sync skipped (duplicate):', r.name, e.message);
46
+ }
47
+ }
48
+ if (count > 0) ok(`已同步 ${count} 个自定义规则`);
49
+ }
50
+ } catch (e) {
51
+ err(`启动 whistle 失败: ${e.message}`);
52
+ process.exit(1);
53
+ }
54
+ });
55
+ }
56
+
57
+ function registerStop(program) {
58
+ program
59
+ .command('stop')
60
+ .description('停止 whistle')
61
+ .action(() => {
62
+ const { execSync } = require('node:child_process');
63
+ try {
64
+ try {
65
+ disableSystemProxy();
66
+ } catch (e) {
67
+ err(`关闭系统代理失败: ${e.message}`);
68
+ }
69
+ execSync(`"${localW2Path}" stop`, { stdio: 'inherit' });
70
+ ok('whistle 已停止');
71
+ } catch (e) {
72
+ err(`停止 whistle 失败: ${e.message}`);
73
+ process.exit(1);
74
+ }
75
+ });
76
+ }
77
+
78
+ function registerW2(program) {
79
+ program
80
+ .command('w2')
81
+ .description('直接执行 w2 命令')
82
+ .arguments('[args...]')
83
+ .action((args) => {
84
+ const { execSync } = require('node:child_process');
85
+ try {
86
+ execSync(`"${localW2Path}" ${args.join(' ')}`, { stdio: 'inherit' });
87
+ } catch (e) {
88
+ err(`w2 命令执行失败: ${e.message}`);
89
+ process.exit(1);
90
+ }
91
+ });
92
+ }
93
+
94
+ module.exports = {
95
+ registerStart,
96
+ registerStop,
97
+ registerW2,
98
+ };
@@ -0,0 +1,50 @@
1
+ const { CONFIG_PATH } = require('../../config');
2
+ const { readConfig, getWhistleStatus, info, ok, title, sep, c } = require('../helpers');
3
+
4
+ function registerStatus(program) {
5
+ program
6
+ .command('status')
7
+ .description('查看 EasyProxy 状态')
8
+ .action(() => {
9
+ const cfg = readConfig();
10
+ const ws = getWhistleStatus();
11
+ const interceptOn = cfg.interceptEnabled !== false;
12
+ const ruleCount = cfg.interceptRules ? Object.keys(cfg.interceptRules).length : 0;
13
+ title('EasyProxy 状态');
14
+ sep();
15
+ info(
16
+ 'Whistle: ' +
17
+ (ws.running ? `${c.green}✓ 运行中${c.reset}` : `${c.red}✗ 未运行${c.reset}`) +
18
+ (ws.port ? `(端口: ${ws.port})` : '')
19
+ );
20
+ info(
21
+ `加速功能: ${cfg.enabled ? `${c.green}✓ 已启用` : `${c.red}✗ 已关闭`}${c.reset}`
22
+ );
23
+ info(
24
+ '拦截规则: ' +
25
+ (interceptOn ? `${c.green}✓ 已启用` : `${c.red}✗ 已关闭`) +
26
+ c.reset +
27
+ '(' +
28
+ ruleCount +
29
+ ' 个域名)'
30
+ );
31
+ info(
32
+ 'SNI 改写: ' +
33
+ (cfg.sniRewrite?.enabled ? `${c.green}✓ 已启用` : `${c.red}✗ 已关闭`) +
34
+ c.reset
35
+ );
36
+ info(`DoH 服务器: ${cfg.doh ? cfg.doh.host : 'dns.google'}`);
37
+ info(`Hosts 覆盖: ${cfg.hosts?.length || 0} 条`);
38
+ info(`配置文件: ${CONFIG_PATH}`);
39
+ sep();
40
+ info('Web 控制台: http://local.whistlejs.com/plugin.easy-proxy/');
41
+ info('常用命令: easy-proxy start --init (启动 whistle + 代理 + 证书)');
42
+ info(' easy-proxy intercept list (查看拦截规则)');
43
+ info(' easy-proxy proxy on / easy-proxy proxy off (切换系统代理)');
44
+ info(' easy-proxy cert install (安装证书)');
45
+ });
46
+ }
47
+
48
+ module.exports = {
49
+ registerStatus,
50
+ };
@@ -0,0 +1,137 @@
1
+ const { loadConfig, saveConfig } = require('../config');
2
+ const { debug } = require('../debug');
3
+
4
+ const localW2Path = require.resolve('whistle/bin/whistle.js');
5
+
6
+ // ---- Config helpers ----
7
+ function readConfig() {
8
+ return loadConfig(null);
9
+ }
10
+ function writeConfig(config) {
11
+ saveConfig(null, config);
12
+ }
13
+
14
+ function ensureSni(config) {
15
+ if (!config.sniRewrite) config.sniRewrite = { enabled: false, defaultSni: null, sniMap: {} };
16
+ if (!config.sniRewrite.sniMap) config.sniRewrite.sniMap = {};
17
+ }
18
+
19
+ // ---- Output helpers ----
20
+ const c = {
21
+ reset: '\x1b[0m',
22
+ bold: '\x1b[1m',
23
+ green: '\x1b[32m',
24
+ red: '\x1b[31m',
25
+ blue: '\x1b[34m',
26
+ yellow: '\x1b[33m',
27
+ gray: '\x1b[90m',
28
+ };
29
+ const info = (m) => console.log(`${c.blue}ℹ${c.reset}`, m);
30
+ const ok = (m) => console.log(`${c.green}✔${c.reset}`, m);
31
+ const warn = (m) => console.log(`${c.yellow}⚠${c.reset}`, m);
32
+ const err = (m) => console.error(`${c.red}✖${c.reset}`, m);
33
+ const title = (m) => console.log(c.bold + c.blue + m + c.reset);
34
+ const sep = () => console.log(c.gray + '─'.repeat(40) + c.reset);
35
+
36
+ // ---- whistle status helper with cache ----
37
+ let _statusCache = null;
38
+ let _statusTs = 0;
39
+
40
+ function getWhistleStatus() {
41
+ // 1秒缓存,避免频繁调用 execSync
42
+ if (Date.now() - _statusTs < 1000 && _statusCache) {
43
+ return _statusCache;
44
+ }
45
+
46
+ const { execSync } = require('node:child_process');
47
+ try {
48
+ const out = execSync(`"${localW2Path}" status 2>&1`, { encoding: 'utf-8' });
49
+ if (/is running/i.test(out) && !/No running/i.test(out)) {
50
+ let port = null;
51
+ const m1 = out.match(/PORT\((\d+)\)/i);
52
+ if (m1) port = parseInt(m1[1], 10);
53
+ if (!port) {
54
+ const m2 = out.match(/127\.0\.0\.1:(\d+)/);
55
+ if (m2) port = parseInt(m2[1], 10);
56
+ }
57
+ _statusCache = { running: true, port: port };
58
+ _statusTs = Date.now();
59
+ return _statusCache;
60
+ }
61
+ } catch (_e) {
62
+ /* w2 不存在或返回非 0 */
63
+ }
64
+ _statusCache = { running: false, port: null };
65
+ _statusTs = Date.now();
66
+ return _statusCache;
67
+ }
68
+
69
+ function checkWhistleRunning() {
70
+ const ws = getWhistleStatus();
71
+ if (!ws.running) {
72
+ err('whistle 未运行,请先执行 easy-proxy start --init');
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ function warnWhistleNotRunning() {
78
+ const ws = getWhistleStatus();
79
+ if (!ws.running) {
80
+ warn('whistle 未运行,规则仅本地保存,下次启动自动同步');
81
+ }
82
+ return ws.running;
83
+ }
84
+
85
+ // ---- $EDITOR helper ----
86
+ function editInEditor(initialContent, name) {
87
+ const fs = require('node:fs');
88
+ const os = require('node:os');
89
+ const { execSync } = require('node:child_process');
90
+ const path = require('node:path');
91
+ const tmpFile = path.join(os.tmpdir(), `easy-proxy-rule-${name}-${Date.now()}.txt`);
92
+ fs.writeFileSync(tmpFile, initialContent || '', 'utf-8');
93
+ const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
94
+ try {
95
+ execSync(`${editor} "${tmpFile}"`, { stdio: 'inherit' });
96
+ return fs.readFileSync(tmpFile, 'utf-8');
97
+ } finally {
98
+ try {
99
+ fs.unlinkSync(tmpFile);
100
+ } catch (e) {
101
+ debug('Failed to cleanup temp file:', tmpFile, e.message);
102
+ }
103
+ }
104
+ }
105
+
106
+ // ---- confirm helper ----
107
+ function promptConfirm(question) {
108
+ return new Promise((resolve) => {
109
+ process.stdout.write(question);
110
+ process.stdin.setEncoding('utf-8');
111
+ process.stdin.resume();
112
+ process.stdin.once('data', (data) => {
113
+ process.stdin.pause();
114
+ resolve(data.trim().toLowerCase() === 'y');
115
+ });
116
+ });
117
+ }
118
+
119
+ module.exports = {
120
+ localW2Path,
121
+ readConfig,
122
+ writeConfig,
123
+ ensureSni,
124
+ c,
125
+ info,
126
+ ok,
127
+ warn,
128
+ err,
129
+ title,
130
+ sep,
131
+ getWhistleStatus,
132
+ checkWhistleRunning,
133
+ warnWhistleNotRunning,
134
+ editInEditor,
135
+ promptConfirm,
136
+ debug,
137
+ };
@@ -0,0 +1,41 @@
1
+ const { loadConfig, saveConfig } = require('../config');
2
+ const { DEFAULT_BYPASS_DOMAINS } = require('./bypass-domains');
3
+
4
+ function loadProxyConfig() {
5
+ try {
6
+ const config = loadConfig(null);
7
+ return {
8
+ bypassDomains: Array.isArray(config.proxyBypassDomains) ? config.proxyBypassDomains : null,
9
+ };
10
+ } catch (e) {
11
+ console.warn('读取代理配置失败:', e.message);
12
+ return { bypassDomains: null };
13
+ }
14
+ }
15
+
16
+ function saveProxyConfig(proxyConfig) {
17
+ try {
18
+ const config = loadConfig(null);
19
+ config.proxyBypassDomains = proxyConfig.bypassDomains;
20
+ saveConfig(null, config);
21
+ } catch (e) {
22
+ console.warn('保存代理配置失败:', e.message);
23
+ }
24
+ }
25
+
26
+ function getEffectiveBypass() {
27
+ const { bypassDomains } = loadProxyConfig();
28
+ return bypassDomains?.length > 0 ? bypassDomains : DEFAULT_BYPASS_DOMAINS;
29
+ }
30
+
31
+ function getDefaultBypass() {
32
+ return DEFAULT_BYPASS_DOMAINS;
33
+ }
34
+
35
+ module.exports = {
36
+ DEFAULT_BYPASS_DOMAINS,
37
+ loadConfig: loadProxyConfig,
38
+ saveConfig: saveProxyConfig,
39
+ getEffectiveBypass,
40
+ getDefaultBypass,
41
+ };