pac-proxy-cli 1.2.0 → 1.2.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/lib/config-cli.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  getPacRules,
11
11
  setPacRules,
12
12
  getProxyConfig,
13
+ getSslocalConfig,
13
14
  } from './local-store.js';
14
15
  import { getSslocalCiphers } from './sslocal-manager.js';
15
16
 
@@ -135,6 +136,102 @@ export async function tryPushProxyToRunningServe() {
135
136
  return false;
136
137
  }
137
138
 
139
+ /**
140
+ * 若本机已有 `pac-proxy serve` 在跑,对其发起 PUT /api/local/sslocal,与 Web「Shadowsocks」页保存一致(写盘、启停 sslocal、applyProxyService)。
141
+ * @returns {Promise<boolean>} 是否成功命中并同步
142
+ */
143
+ export async function tryPushSslocalToRunningServe() {
144
+ const sslocal = getSslocalConfig();
145
+ const body = JSON.stringify(sslocal);
146
+ for (const port of getConsoleHttpPortsToProbe()) {
147
+ const ctrl = new AbortController();
148
+ const tid = setTimeout(() => ctrl.abort(), 800);
149
+ try {
150
+ const r = await fetch(`http://127.0.0.1:${port}/api/local/sslocal`, {
151
+ method: 'PUT',
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body,
154
+ signal: ctrl.signal,
155
+ });
156
+ if (r.ok) {
157
+ console.log('已向运行中的控制台同步 Shadowsocks (sslocal) 设置(与 Web 保存一致)。');
158
+ return true;
159
+ }
160
+ } catch {
161
+ /* 下一端口 */
162
+ } finally {
163
+ clearTimeout(tid);
164
+ }
165
+ }
166
+ return false;
167
+ }
168
+
169
+ /**
170
+ * 与 Web「Shadowsocks / sslocal」页字段一致的交互问答。
171
+ * @param {Record<string, unknown>} currentSslocal
172
+ */
173
+ async function promptSslocalFields(currentSslocal) {
174
+ const sslocal = { ...currentSslocal };
175
+ sslocal.enabled = await confirm({
176
+ message: '是否启用内置代理客户端 (sslocal)',
177
+ default: !!sslocal.enabled,
178
+ });
179
+ if (!sslocal.enabled) {
180
+ return { ...currentSslocal, enabled: false };
181
+ }
182
+
183
+ const server = await input({
184
+ message: '服务器地址',
185
+ default: sslocal.server || '',
186
+ });
187
+ if ((server || '').trim()) sslocal.server = server.trim();
188
+
189
+ const sp = await input({
190
+ message: '服务器端口',
191
+ default: String(sslocal.serverPort ?? 8388),
192
+ validate: validatePortNum,
193
+ });
194
+ sslocal.serverPort = Number(sp);
195
+
196
+ const lp = await input({
197
+ message: '本地 SOCKS5 端口',
198
+ default: String(sslocal.localPort ?? 1080),
199
+ validate: validatePortNum,
200
+ });
201
+ sslocal.localPort = Number(lp);
202
+
203
+ const changePw = await confirm({
204
+ message: '是否修改密码?(否则保留已保存密码)',
205
+ default: false,
206
+ });
207
+ if (changePw) {
208
+ const pw = await password({ message: '密码', mask: '*' });
209
+ sslocal.password = pw || '';
210
+ }
211
+
212
+ const methods = getSslocalCiphers();
213
+ const methodChoices = methods.map((m) => ({ name: m, value: m }));
214
+ const currentMethod = methods.includes(sslocal.method) ? sslocal.method : methods[0];
215
+ sslocal.method = await select({
216
+ message: '加密方法',
217
+ choices: methodChoices,
218
+ default: currentMethod,
219
+ });
220
+
221
+ const to = await input({
222
+ message: '超时(秒)',
223
+ default: String(sslocal.timeout ?? 300),
224
+ validate: (v) => {
225
+ const n = Number((v || '').trim());
226
+ if (!Number.isInteger(n) || n < 1) return '请输入正整数';
227
+ return true;
228
+ },
229
+ });
230
+ sslocal.timeout = Number((to || '').trim());
231
+ sslocal.enabled = true;
232
+ return sslocal;
233
+ }
234
+
138
235
  export async function runConfigProxyModeInteractive() {
139
236
  const proxy = getProxyConfig();
140
237
  /** @type {'direct'|'global'|'pac'|'mitm'} */
@@ -524,66 +621,12 @@ export async function runConfigInteractive() {
524
621
  default: proxy.applySystemProxy !== false,
525
622
  });
526
623
 
527
- sslocal.enabled = await confirm({
528
- message: '是否启用内置代理客户端 (sslocal)',
529
- default: !!sslocal.enabled,
530
- });
531
-
532
- if (sslocal.enabled) {
533
- const server = await input({
534
- message: '服务器地址',
535
- default: sslocal.server || '',
536
- });
537
- if ((server || '').trim()) sslocal.server = server.trim();
538
-
539
- const sp = await input({
540
- message: '服务器端口',
541
- default: String(sslocal.serverPort ?? 8388),
542
- validate: validatePortNum,
543
- });
544
- sslocal.serverPort = Number(sp);
545
-
546
- const lp = await input({
547
- message: '本地 SOCKS5 端口',
548
- default: String(sslocal.localPort ?? 1080),
549
- validate: validatePortNum,
550
- });
551
- sslocal.localPort = Number(lp);
552
-
553
- const changePw = await confirm({
554
- message: '是否修改密码?(否则保留已保存密码)',
555
- default: false,
556
- });
557
- if (changePw) {
558
- const pw = await password({ message: '密码', mask: '*' });
559
- sslocal.password = pw || '';
560
- }
561
-
562
- const methods = getSslocalCiphers();
563
- const methodChoices = methods.map((m) => ({ name: m, value: m }));
564
- const currentMethod = methods.includes(sslocal.method) ? sslocal.method : methods[0];
565
- sslocal.method = await select({
566
- message: '加密方法',
567
- choices: methodChoices,
568
- default: currentMethod,
569
- });
570
-
571
- const to = await input({
572
- message: '超时(秒)',
573
- default: String(sslocal.timeout ?? 300),
574
- validate: (v) => {
575
- const n = Number((v || '').trim());
576
- if (!Number.isInteger(n) || n < 1) return '请输入正整数';
577
- return true;
578
- },
579
- });
580
- sslocal.timeout = Number((to || '').trim());
581
- }
624
+ const nextSslocal = await promptSslocalFields(sslocal);
582
625
 
583
626
  cfg = {
584
627
  ...cfg,
585
628
  proxy: { ...cfg.proxy, ...proxy },
586
- sslocal: { ...cfg.sslocal, ...sslocal, enabled: !!sslocal.enabled },
629
+ sslocal: nextSslocal,
587
630
  };
588
631
  saveConfig(cfg);
589
632
 
@@ -597,6 +640,22 @@ export async function runConfigInteractive() {
597
640
  printPathsHint(loadConfig());
598
641
  }
599
642
 
643
+ /** 仅编辑 Shadowsocks (sslocal),与 Web「Shadowsocks」页共用 config.json 字段与保存副作用。 */
644
+ export async function runConfigShadowsocksInteractive() {
645
+ const cfg = loadConfig();
646
+ console.log('\nShadowsocks (sslocal) — 与 Web 控制台「代理客户端」页共用配置。\n');
647
+
648
+ const nextSslocal = await promptSslocalFields({ ...cfg.sslocal });
649
+ saveConfig({ ...cfg, sslocal: nextSslocal });
650
+
651
+ console.log('\n配置已保存。');
652
+ const pushed = await tryPushSslocalToRunningServe();
653
+ if (!pushed) {
654
+ console.log('未检测到运行中的控制台,sslocal 进程与上游代理未自动应用;可启动 pac-proxy serve 后重试本命令或在 Web 中保存。');
655
+ }
656
+ printPathsHint(loadConfig());
657
+ }
658
+
600
659
  export async function runConfigInit() {
601
660
  console.log('\n首次配置向导(可随时用 pac-proxy config 再次编辑)。\n');
602
661
  printPathsHint(loadConfig());
package/lib/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  runConfigPrint,
8
8
  runConfigInteractive,
9
9
  runConfigInit,
10
+ runConfigShadowsocksInteractive,
10
11
  runPacImportFile,
11
12
  runPacRulesInteractive,
12
13
  runConfigProxyModeInteractive,
@@ -84,6 +85,19 @@ configCmd
84
85
  await runConfigProxyModeInteractive();
85
86
  });
86
87
 
88
+ configCmd
89
+ .command('shadowsocks')
90
+ .alias('sslocal')
91
+ .description('交互式编辑 Shadowsocks (sslocal),与 Web「代理客户端」页一致')
92
+ .action(async () => {
93
+ if (!process.stdin.isTTY) {
94
+ console.error('非交互终端无法使用。请使用: pac-proxy config --print');
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+ await runConfigShadowsocksInteractive();
99
+ });
100
+
87
101
  configCmd
88
102
  .command('import')
89
103
  .description('从 JSON 文件合并导入 PAC 规则(与 Web 导入逻辑一致)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pac-proxy-cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "node pac proxy and web control panel",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",