proxyctl 0.3.0__tar.gz → 0.3.2__tar.gz

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.
Files changed (24) hide show
  1. {proxyctl-0.3.0 → proxyctl-0.3.2}/PKG-INFO +1 -1
  2. {proxyctl-0.3.0 → proxyctl-0.3.2}/pyproject.toml +1 -1
  3. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/__init__.py +1 -1
  4. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/audit.py +1 -1
  5. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/check.py +1 -1
  6. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/cli.py +13 -3
  7. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/core/plugin.py +18 -1
  8. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/trace.py +4 -4
  9. {proxyctl-0.3.0 → proxyctl-0.3.2}/.gitignore +0 -0
  10. {proxyctl-0.3.0 → proxyctl-0.3.2}/LICENSE +0 -0
  11. {proxyctl-0.3.0 → proxyctl-0.3.2}/README.md +0 -0
  12. {proxyctl-0.3.0 → proxyctl-0.3.2}/man/proxyctl.1 +0 -0
  13. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/_io.py +0 -0
  14. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/builtin_plugins/__init__.py +0 -0
  15. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/builtin_plugins/connectivity_basic.py +0 -0
  16. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/builtin_plugins/corp_network.py +0 -0
  17. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/completion.py +0 -0
  18. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/core/__init__.py +0 -0
  19. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/engine/__init__.py +0 -0
  20. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/engine/base.py +0 -0
  21. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/engine/mihomo.py +0 -0
  22. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/engine/singbox.py +0 -0
  23. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/explain.py +0 -0
  24. {proxyctl-0.3.0 → proxyctl-0.3.2}/src/proxyctl/status.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyctl
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Proxy configuration lifecycle management for macOS and Linux
5
5
  Project-URL: Homepage, https://github.com/crhan/proxyctl
6
6
  Project-URL: Issues, https://github.com/crhan/proxyctl/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "proxyctl"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "Proxy configuration lifecycle management for macOS and Linux"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """proxyctl — Proxy configuration lifecycle management."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.3.2"
@@ -439,7 +439,7 @@ def cmd_audit(audit_days: int, api_base: str, api_secret: str, do_apply: bool):
439
439
  if candidates and not do_apply:
440
440
  print(f"\n执行 {BOLD}proxyctl audit apply{NC} 自动写入双 config")
441
441
 
442
- _audit_emit(as_json, _sys, _real_stdout, collector)
442
+ _audit_emit(as_json, as_plain, _sys, _real_stdout, collector)
443
443
 
444
444
 
445
445
  def _audit_emit(as_json: bool, as_plain: bool, _sys, _real_stdout,
@@ -944,7 +944,7 @@ def cmd_check(engine, api: str, api_secret: str,
944
944
  "detail": "skipped_in_structured_modes"},
945
945
  {"stage": "connectivity",
946
946
  "ok": all(c.get("ok") for c in conn) if conn else False,
947
- "detail": ";".join(f"{c.get('target')}={c.get('http_code') or 'X'}"
947
+ "detail": ";".join(f"{c.get('name')}={'ok' if c.get('ok') else 'X'}"
948
948
  for c in conn)},
949
949
  {"stage": "outbound_ip",
950
950
  "ok": bool(out_ip),
@@ -1230,6 +1230,7 @@ def cmd_dns_unlock(config: dict):
1230
1230
  print(f"{YELLOW}dns-unlock 仅支持 macOS{NC}")
1231
1231
  return
1232
1232
  dns_lock_label = config.get("dns_lock_label", DEFAULTS["dns_lock_label"])
1233
+ dns_lock_plist = f"/Library/LaunchDaemons/{dns_lock_label}.plist"
1233
1234
 
1234
1235
  r = run(["launchctl", "bootout", f"system/{dns_lock_label}"], sudo=True, capture=True)
1235
1236
  if r.returncode == 0:
@@ -1361,7 +1362,16 @@ def _read_log_lines(path: str, tail_n: int | None) -> list:
1361
1362
 
1362
1363
  # ── 帮助 ──────────────────────────────────────────────────────────────────────
1363
1364
 
1364
- VERSION = "0.3.0"
1365
+ def _read_version() -> str:
1366
+ """单一事实来源:pyproject.toml 的 [project] version。"""
1367
+ try:
1368
+ from importlib.metadata import version
1369
+ return version("proxyctl")
1370
+ except Exception:
1371
+ return "unknown"
1372
+
1373
+
1374
+ VERSION = _read_version()
1365
1375
 
1366
1376
 
1367
1377
  # Help 输出按 group 分块的顺序(避免依赖 dict 插入顺序变化)
@@ -1714,7 +1724,7 @@ def _plan_mode(backend, target: str) -> list[dict]:
1714
1724
  "reversible": True,
1715
1725
  "side_effects": ["config-write"]},
1716
1726
  {"action": "subprocess",
1717
- "target": f"launchctl kickstart -k system/{backend.label}",
1727
+ "target": f"launchctl kickstart -k {backend.label}",
1718
1728
  "summary": f"重启 launchd 服务以读取新 mode",
1719
1729
  "reversible": True, "requires_sudo": True,
1720
1730
  "side_effects": ["process"]},
@@ -1731,7 +1741,7 @@ def _plan_engine(backend, target: str) -> list[dict]:
1731
1741
  new_plist = f"/Library/LaunchDaemons/<{target}>.plist"
1732
1742
  return [
1733
1743
  {"action": "subprocess",
1734
- "target": f"launchctl bootout system/{backend.label}",
1744
+ "target": f"launchctl bootout {backend.label}",
1735
1745
  "summary": f"停止当前引擎 {backend.name}",
1736
1746
  "reversible": True, "requires_sudo": True,
1737
1747
  "side_effects": ["process"]},
@@ -148,6 +148,20 @@ class Plugin:
148
148
  return []
149
149
 
150
150
 
151
+ # ── 内部工具 ─────────────────────────────────────────────────────────────────
152
+
153
+ def _apply_color_policy(modname: str) -> None:
154
+ """插件模块加载后立刻调用:若当前是关色态,把模块自定义的 RED/GREEN/...
155
+ 常量抹空。解决用户插件(如 sb_private.py)在 cli.main() 的 set_no_color
156
+ 之后加载,导致硬编码 ANSI 字面量泄漏到管道输出的问题。"""
157
+ try:
158
+ from proxyctl import _io
159
+ _io.maybe_disable_module_colors(modname)
160
+ except Exception:
161
+ # 插件加载链路不应被色彩策略阻断
162
+ pass
163
+
164
+
151
165
  # ── Registry ─────────────────────────────────────────────────────────────────
152
166
 
153
167
  class PluginRegistry:
@@ -171,7 +185,9 @@ class PluginRegistry:
171
185
  if modname.startswith("_"):
172
186
  continue
173
187
  try:
174
- mod = importlib.import_module(f"proxyctl.builtin_plugins.{modname}")
188
+ full_name = f"proxyctl.builtin_plugins.{modname}"
189
+ mod = importlib.import_module(full_name)
190
+ _apply_color_policy(full_name)
175
191
  self._discover_in_module(mod, config, source=f"builtin/{modname}")
176
192
  except Exception as e:
177
193
  self.errors.append((f"builtin/{modname}", _fmt_err(e)))
@@ -192,6 +208,7 @@ class PluginRegistry:
192
208
  mod = importlib.util.module_from_spec(spec)
193
209
  sys.modules[mod_name] = mod
194
210
  spec.loader.exec_module(mod)
211
+ _apply_color_policy(mod_name)
195
212
  self._discover_in_module(mod, config, source=f"user/{fname}")
196
213
  except Exception as e:
197
214
  self.errors.append((f"user/{fname}", _fmt_err(e)))
@@ -575,11 +575,12 @@ def cmd_trace(raw_input: str, api: str, secret: str, config: dict = None):
575
575
  }
576
576
 
577
577
  t.join()
578
- lines, conn_ok = connectivity_result[0]
578
+ lines, remote_ip = connectivity_result[0]
579
579
  for line in lines:
580
580
  print(line)
581
581
  collector["stages"]["connectivity"] = {
582
- "ok": bool(conn_ok),
582
+ "ok": bool(remote_ip),
583
+ "remote_ip": remote_ip,
583
584
  "lines": [_strip_ansi(line) for line in lines],
584
585
  }
585
586
 
@@ -588,8 +589,7 @@ def cmd_trace(raw_input: str, api: str, secret: str, config: dict = None):
588
589
  if as_json:
589
590
  _sys.stdout = _real_stdout
590
591
  from proxyctl._io import emit_json, envelope, OK
591
- emit_json(envelope("trace", data=collector,
592
- ok=bool(conn_ok), code=OK))
592
+ emit_json(envelope("trace", data=collector, ok=True, code=OK))
593
593
  _sys.exit(0)
594
594
 
595
595
 
File without changes
File without changes
File without changes
File without changes
File without changes