devconfig-gen-singbox 2.1.1__tar.gz → 2.1.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 (95) hide show
  1. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/PKG-INFO +14 -14
  2. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/README.md +13 -13
  3. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/pyproject.toml +2 -3
  4. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/__init__.py +1 -1
  5. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/cli.py +17 -1
  6. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen_singbox.egg-info/PKG-INFO +14 -14
  7. devconfig_gen_singbox-2.1.2/src/devconfig_gen_singbox.egg-info/entry_points.txt +2 -0
  8. devconfig_gen_singbox-2.1.2/src/singbox_ops/cli.py +207 -0
  9. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_cli_e2e.py +1 -1
  10. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_cli.py +13 -0
  11. devconfig_gen_singbox-2.1.1/src/devconfig_gen_singbox.egg-info/entry_points.txt +0 -4
  12. devconfig_gen_singbox-2.1.1/src/singbox_ops/cli.py +0 -109
  13. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/LICENSE +0 -0
  14. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/setup.cfg +0 -0
  15. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/engine.py +0 -0
  16. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/formats.py +0 -0
  17. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/interactive.py +0 -0
  18. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/models.py +0 -0
  19. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/__init__.py +0 -0
  20. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/custom.py +0 -0
  21. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/env_provider.py +0 -0
  22. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/json_provider.py +0 -0
  23. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/__init__.py +0 -0
  24. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/data/rules.json +0 -0
  25. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/links.py +0 -0
  26. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/models.py +0 -0
  27. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/plugins/__init__.py +0 -0
  28. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/plugins/anytls.py +0 -0
  29. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/plugins/base.py +0 -0
  30. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/plugins/hysteria2.py +0 -0
  31. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/plugins/tuic.py +0 -0
  32. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/provider.py +0 -0
  33. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/route.py +0 -0
  34. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/providers/singbox/schema.py +0 -0
  35. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/py.typed +0 -0
  36. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/registry.py +0 -0
  37. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/validation.py +0 -0
  38. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen/web_ui.py +0 -0
  39. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen_singbox.egg-info/SOURCES.txt +0 -0
  40. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen_singbox.egg-info/dependency_links.txt +0 -0
  41. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen_singbox.egg-info/requires.txt +0 -0
  42. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/devconfig_gen_singbox.egg-info/top_level.txt +0 -0
  43. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/__init__.py +0 -0
  44. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/__init__.py +0 -0
  45. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/acme/__init__.py +0 -0
  46. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/acme/base.py +0 -0
  47. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/acme/cloudflare_dns01.py +0 -0
  48. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/base.py +0 -0
  49. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/dns/__init__.py +0 -0
  50. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/dns/base.py +0 -0
  51. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/dns/cloudflare.py +0 -0
  52. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/export/__init__.py +0 -0
  53. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/export/local_files.py +0 -0
  54. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/runtime/__init__.py +0 -0
  55. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/runtime/nftables.py +0 -0
  56. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/runtime/packages.py +0 -0
  57. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/runtime/systemd.py +0 -0
  58. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/runtime/warp_watchdog.py +0 -0
  59. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/secrets/__init__.py +0 -0
  60. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/secrets/base.py +0 -0
  61. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/secrets/python_secrets.py +0 -0
  62. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/secrets/singbox_subprocess.py +0 -0
  63. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/secrets/static.py +0 -0
  64. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/state/__init__.py +0 -0
  65. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/adapters/state/local_json.py +0 -0
  66. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/__init__.py +0 -0
  67. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/command.py +0 -0
  68. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/context_builder.py +0 -0
  69. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/exceptions.py +0 -0
  70. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/network.py +0 -0
  71. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/orchestrator.py +0 -0
  72. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/plan.py +0 -0
  73. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/core/protocols.py +0 -0
  74. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/templates/singbox_auto_update.py.template +0 -0
  75. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/src/singbox_ops/templates/warp_watchdog.sh.template +0 -0
  76. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_api_parity.py +0 -0
  77. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_custom_provider.py +0 -0
  78. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_devconfig_core.py +0 -0
  79. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_env_provider.py +0 -0
  80. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_formats.py +0 -0
  81. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_interactive.py +0 -0
  82. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_merge.py +0 -0
  83. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_provider_metadata.py +0 -0
  84. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_acme.py +0 -0
  85. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_context_builder.py +0 -0
  86. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_dns.py +0 -0
  87. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_orchestrator.py +0 -0
  88. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_plan.py +0 -0
  89. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_runtime.py +0 -0
  90. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_secrets.py +0 -0
  91. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_ops_state_export.py +0 -0
  92. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_singbox_provider.py +0 -0
  93. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_validation.py +0 -0
  94. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_web_ui.py +0 -0
  95. {devconfig_gen_singbox-2.1.1 → devconfig_gen_singbox-2.1.2}/tests/test_web_ui_widgets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devconfig_gen_singbox
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: Provider-based developer configuration generation engine and CLI
5
5
  Author-email: henryliu443 <henryliu443@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -28,7 +28,7 @@ Dynamic: license-file
28
28
  > **A sing-box domain implementation built on top of DevConfig-Gen.**
29
29
  > **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
30
30
 
31
- [![Version](https://img.shields.io/badge/version-2.1.1-blue.svg)](CHANGELOG.md)
31
+ [![Version](https://img.shields.io/badge/version-2.1.2-blue.svg)](CHANGELOG.md)
32
32
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
33
33
  ![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)
34
34
  ![Tests](https://img.shields.io/badge/tests-136%20passing-brightgreen.svg)
@@ -62,7 +62,7 @@ devconfig_gen_singbox
62
62
  git clone https://github.com/henryliu443/devconfig_gen_singbox.git
63
63
  cd devconfig_gen_singbox
64
64
  pip install -e ".[yaml]"
65
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist
65
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist
66
66
  ```
67
67
 
68
68
  未安装时亦可直接通过源码运行:
@@ -226,9 +226,9 @@ options:
226
226
  ## 输入示例 / Input Example
227
227
 
228
228
  ```bash
229
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist
230
- devconfig-gen validate --provider singbox --input examples/singbox.yaml
231
- devconfig-gen schema --provider singbox
229
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist
230
+ devconfig_gen_singbox validate --provider singbox --input examples/singbox.yaml
231
+ devconfig_gen_singbox schema --provider singbox
232
232
  ```
233
233
 
234
234
  完整 context 见 [`examples/singbox.yaml`](examples/singbox.yaml)。
@@ -281,14 +281,14 @@ singbox-ops destroy --plan examples/singbox-deploy.yaml
281
281
  - 结构化校验与诊断(`Diagnostic` / `ValidationError`)
282
282
  - JSON/YAML 序列化(标准库 + 可选 PyYAML,内置子集解析器兜底)
283
283
  - 多源合并与 dotted-path 覆盖(`deep_merge`、`--set`)
284
- - CLI、**终端向导**(`devconfig-gen init`)、**本地 Web 工作台**
285
- (`devconfig-gen ui`)与 Python API
284
+ - CLI、**终端向导**(`devconfig_gen_singbox init`)、**本地 Web 工作台**
285
+ (`devconfig_gen_singbox ui`)与 Python API
286
286
 
287
287
  同时也继承中立的 Provider `custom` / `json` / `env`。本仓库在其之上新增领域
288
288
  Provider `singbox`:
289
289
 
290
290
  ```bash
291
- devconfig-gen providers
291
+ devconfig_gen_singbox providers
292
292
  # custom / env / json / singbox
293
293
  ```
294
294
 
@@ -299,15 +299,15 @@ devconfig-gen providers
299
299
 
300
300
  ```bash
301
301
  # 生成 / 校验 / 查看 schema
302
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist --format yaml
303
- devconfig-gen validate --provider singbox --input examples/singbox.yaml --json
304
- devconfig-gen schema --provider singbox
302
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist --format yaml
303
+ devconfig_gen_singbox validate --provider singbox --input examples/singbox.yaml --json
304
+ devconfig_gen_singbox schema --provider singbox
305
305
 
306
306
  # 交互式终端向导(所有 provider 通用,含 singbox)
307
- devconfig-gen init --provider singbox --output-dir dist
307
+ devconfig_gen_singbox init --provider singbox --output-dir dist
308
308
 
309
309
  # 本地 Web 工作台(零构建,标准库 http.server)
310
- devconfig-gen ui --port 8848
310
+ devconfig_gen_singbox ui --port 8848
311
311
  ```
312
312
 
313
313
  ```python
@@ -3,7 +3,7 @@
3
3
  > **A sing-box domain implementation built on top of DevConfig-Gen.**
4
4
  > **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
5
5
 
6
- [![Version](https://img.shields.io/badge/version-2.1.1-blue.svg)](CHANGELOG.md)
6
+ [![Version](https://img.shields.io/badge/version-2.1.2-blue.svg)](CHANGELOG.md)
7
7
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
8
  ![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)
9
9
  ![Tests](https://img.shields.io/badge/tests-136%20passing-brightgreen.svg)
@@ -37,7 +37,7 @@ devconfig_gen_singbox
37
37
  git clone https://github.com/henryliu443/devconfig_gen_singbox.git
38
38
  cd devconfig_gen_singbox
39
39
  pip install -e ".[yaml]"
40
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist
40
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist
41
41
  ```
42
42
 
43
43
  未安装时亦可直接通过源码运行:
@@ -201,9 +201,9 @@ options:
201
201
  ## 输入示例 / Input Example
202
202
 
203
203
  ```bash
204
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist
205
- devconfig-gen validate --provider singbox --input examples/singbox.yaml
206
- devconfig-gen schema --provider singbox
204
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist
205
+ devconfig_gen_singbox validate --provider singbox --input examples/singbox.yaml
206
+ devconfig_gen_singbox schema --provider singbox
207
207
  ```
208
208
 
209
209
  完整 context 见 [`examples/singbox.yaml`](examples/singbox.yaml)。
@@ -256,14 +256,14 @@ singbox-ops destroy --plan examples/singbox-deploy.yaml
256
256
  - 结构化校验与诊断(`Diagnostic` / `ValidationError`)
257
257
  - JSON/YAML 序列化(标准库 + 可选 PyYAML,内置子集解析器兜底)
258
258
  - 多源合并与 dotted-path 覆盖(`deep_merge`、`--set`)
259
- - CLI、**终端向导**(`devconfig-gen init`)、**本地 Web 工作台**
260
- (`devconfig-gen ui`)与 Python API
259
+ - CLI、**终端向导**(`devconfig_gen_singbox init`)、**本地 Web 工作台**
260
+ (`devconfig_gen_singbox ui`)与 Python API
261
261
 
262
262
  同时也继承中立的 Provider `custom` / `json` / `env`。本仓库在其之上新增领域
263
263
  Provider `singbox`:
264
264
 
265
265
  ```bash
266
- devconfig-gen providers
266
+ devconfig_gen_singbox providers
267
267
  # custom / env / json / singbox
268
268
  ```
269
269
 
@@ -274,15 +274,15 @@ devconfig-gen providers
274
274
 
275
275
  ```bash
276
276
  # 生成 / 校验 / 查看 schema
277
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist --format yaml
278
- devconfig-gen validate --provider singbox --input examples/singbox.yaml --json
279
- devconfig-gen schema --provider singbox
277
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist --format yaml
278
+ devconfig_gen_singbox validate --provider singbox --input examples/singbox.yaml --json
279
+ devconfig_gen_singbox schema --provider singbox
280
280
 
281
281
  # 交互式终端向导(所有 provider 通用,含 singbox)
282
- devconfig-gen init --provider singbox --output-dir dist
282
+ devconfig_gen_singbox init --provider singbox --output-dir dist
283
283
 
284
284
  # 本地 Web 工作台(零构建,标准库 http.server)
285
- devconfig-gen ui --port 8848
285
+ devconfig_gen_singbox ui --port 8848
286
286
  ```
287
287
 
288
288
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devconfig_gen_singbox"
7
- version = "2.1.1"
7
+ version = "2.1.2"
8
8
  authors = [
9
9
  { name = "henryliu443", email = "henryliu443@gmail.com" },
10
10
  ]
@@ -26,9 +26,8 @@ dev = ["build", "wheel", "PyYAML>=5.1", "cryptography>=3.4"]
26
26
  docs = ["mkdocs-material>=9.5,<10"]
27
27
 
28
28
  [project.scripts]
29
- devconfig-gen = "devconfig_gen.cli:main"
29
+ # One command, named exactly after the PyPI distribution.
30
30
  devconfig_gen_singbox = "devconfig_gen.cli:main"
31
- singbox-ops = "singbox_ops.cli:main"
32
31
 
33
32
  [tool.setuptools.packages.find]
34
33
  where = ["src"]
@@ -66,4 +66,4 @@ __all__ = [
66
66
  "loads",
67
67
  "validate_request",
68
68
  ]
69
- __version__ = "2.1.1"
69
+ __version__ = "2.1.2"
@@ -14,7 +14,7 @@ from .registry import default_registry
14
14
 
15
15
  def build_parser() -> argparse.ArgumentParser:
16
16
  parser = argparse.ArgumentParser(
17
- prog="devconfig-gen",
17
+ prog="devconfig_gen_singbox",
18
18
  description="Generate and validate structured JSON/YAML configuration.",
19
19
  )
20
20
  parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
@@ -100,9 +100,25 @@ def build_parser() -> argparse.ArgumentParser:
100
100
  )
101
101
  ui_cmd.set_defaults(handler=_ui)
102
102
 
103
+ _register_ops_subparsers(sub)
104
+
103
105
  return parser
104
106
 
105
107
 
108
+ def _register_ops_subparsers(sub) -> None:
109
+ """Graft the optional ``singbox_ops`` subcommands onto this single CLI.
110
+
111
+ ``singbox_ops`` is imported lazily, so importing ``devconfig_gen`` stays
112
+ side-effect free and the pure engine works even without the ops package.
113
+ """
114
+
115
+ try:
116
+ from singbox_ops.cli import add_subparsers
117
+ except Exception: # pragma: no cover - ops layer is optional
118
+ return
119
+ add_subparsers(sub)
120
+
121
+
106
122
  def _list_providers(args) -> int:
107
123
  for name in default_registry.names():
108
124
  print(name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devconfig_gen_singbox
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: Provider-based developer configuration generation engine and CLI
5
5
  Author-email: henryliu443 <henryliu443@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -28,7 +28,7 @@ Dynamic: license-file
28
28
  > **A sing-box domain implementation built on top of DevConfig-Gen.**
29
29
  > **构建在 `DevConfig-Gen` 之上的 sing-box 领域实现(child / fork)。**
30
30
 
31
- [![Version](https://img.shields.io/badge/version-2.1.1-blue.svg)](CHANGELOG.md)
31
+ [![Version](https://img.shields.io/badge/version-2.1.2-blue.svg)](CHANGELOG.md)
32
32
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
33
33
  ![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)
34
34
  ![Tests](https://img.shields.io/badge/tests-136%20passing-brightgreen.svg)
@@ -62,7 +62,7 @@ devconfig_gen_singbox
62
62
  git clone https://github.com/henryliu443/devconfig_gen_singbox.git
63
63
  cd devconfig_gen_singbox
64
64
  pip install -e ".[yaml]"
65
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist
65
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist
66
66
  ```
67
67
 
68
68
  未安装时亦可直接通过源码运行:
@@ -226,9 +226,9 @@ options:
226
226
  ## 输入示例 / Input Example
227
227
 
228
228
  ```bash
229
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist
230
- devconfig-gen validate --provider singbox --input examples/singbox.yaml
231
- devconfig-gen schema --provider singbox
229
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist
230
+ devconfig_gen_singbox validate --provider singbox --input examples/singbox.yaml
231
+ devconfig_gen_singbox schema --provider singbox
232
232
  ```
233
233
 
234
234
  完整 context 见 [`examples/singbox.yaml`](examples/singbox.yaml)。
@@ -281,14 +281,14 @@ singbox-ops destroy --plan examples/singbox-deploy.yaml
281
281
  - 结构化校验与诊断(`Diagnostic` / `ValidationError`)
282
282
  - JSON/YAML 序列化(标准库 + 可选 PyYAML,内置子集解析器兜底)
283
283
  - 多源合并与 dotted-path 覆盖(`deep_merge`、`--set`)
284
- - CLI、**终端向导**(`devconfig-gen init`)、**本地 Web 工作台**
285
- (`devconfig-gen ui`)与 Python API
284
+ - CLI、**终端向导**(`devconfig_gen_singbox init`)、**本地 Web 工作台**
285
+ (`devconfig_gen_singbox ui`)与 Python API
286
286
 
287
287
  同时也继承中立的 Provider `custom` / `json` / `env`。本仓库在其之上新增领域
288
288
  Provider `singbox`:
289
289
 
290
290
  ```bash
291
- devconfig-gen providers
291
+ devconfig_gen_singbox providers
292
292
  # custom / env / json / singbox
293
293
  ```
294
294
 
@@ -299,15 +299,15 @@ devconfig-gen providers
299
299
 
300
300
  ```bash
301
301
  # 生成 / 校验 / 查看 schema
302
- devconfig-gen generate --provider singbox --input examples/singbox.yaml --output-dir dist --format yaml
303
- devconfig-gen validate --provider singbox --input examples/singbox.yaml --json
304
- devconfig-gen schema --provider singbox
302
+ devconfig_gen_singbox generate --provider singbox --input examples/singbox.yaml --output-dir dist --format yaml
303
+ devconfig_gen_singbox validate --provider singbox --input examples/singbox.yaml --json
304
+ devconfig_gen_singbox schema --provider singbox
305
305
 
306
306
  # 交互式终端向导(所有 provider 通用,含 singbox)
307
- devconfig-gen init --provider singbox --output-dir dist
307
+ devconfig_gen_singbox init --provider singbox --output-dir dist
308
308
 
309
309
  # 本地 Web 工作台(零构建,标准库 http.server)
310
- devconfig-gen ui --port 8848
310
+ devconfig_gen_singbox ui --port 8848
311
311
  ```
312
312
 
313
313
  ```python
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ devconfig_gen_singbox = devconfig_gen.cli:main
@@ -0,0 +1,207 @@
1
+ """Operations subcommands, registered into the single ``devconfig_gen_singbox`` CLI.
2
+
3
+ There is exactly **one** command name in this project: the PyPI name
4
+ ``devconfig_gen_singbox``. ``add_subparsers()`` is what ``devconfig_gen.cli``
5
+ calls to graft these subcommands onto that single command, so the ops layer
6
+ never introduces a second CLI name.
7
+
8
+ A-repo style: running ``deploy`` without ``--plan`` starts an interactive
9
+ wizard, so one command takes a user from prompts to a deployed server.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import sys
17
+ from typing import Optional
18
+
19
+ from devconfig_gen import formats
20
+
21
+ from .core.context_builder import build_context
22
+ from .core.exceptions import OpsError
23
+ from .core.plan import DeployPlan, load_plan
24
+ from .core.orchestrator import DRY_RUN_IP, build_suite, deploy, destroy, make_runner
25
+ from .core.protocols import PROTOCOLS
26
+
27
+ PROG = "devconfig_gen_singbox"
28
+ __version__ = "0.1.0"
29
+
30
+ SUBCOMMANDS = (
31
+ ("context", "Assemble and print the sing-box context (no side effects)"),
32
+ ("plan", "Interactive wizard: write a plan.yaml, do not deploy"),
33
+ ("deploy", "Deploy sing-box (interactive wizard when --plan is omitted)"),
34
+ ("redeploy", "Regenerate credentials and redeploy"),
35
+ ("destroy", "Tear down what a previous deploy created"),
36
+ )
37
+
38
+
39
+ def add_subparsers(sub) -> None:
40
+ """Graft the operations subcommands onto the shared root parser."""
41
+
42
+ for name, help_text in SUBCOMMANDS:
43
+ command = sub.add_parser(name, help=help_text)
44
+ command.add_argument("--plan", help="Path to a JSON/YAML plan (omit for the wizard)")
45
+ command.add_argument("--domain", help="Domain root (skips that prompt)")
46
+ command.add_argument("--protocols", help="Comma-separated protocols (skips that prompt)")
47
+ if name == "plan":
48
+ command.add_argument("--output", default="plan.yaml", help="Where to write the plan")
49
+ if name == "context":
50
+ command.add_argument("--format", choices=("json", "yaml"), help="Output format")
51
+ if name in ("deploy", "redeploy", "destroy"):
52
+ command.add_argument(
53
+ "--dry-run",
54
+ action="store_true",
55
+ help="Print intended actions without touching the system",
56
+ )
57
+ command.set_defaults(handler=_run)
58
+
59
+
60
+ # --------------------------------------------------------------------------
61
+ # interactive wizard (mirrors the legacy A-repo prompt flow)
62
+ # --------------------------------------------------------------------------
63
+ def _ask(prompt, default=None, reader=None, writer=None):
64
+ reader = reader or sys.stdin
65
+ writer = writer or sys.stdout
66
+ suffix = f" [{default}]" if default else ""
67
+ writer.write(f"? {prompt}{suffix}: ")
68
+ writer.flush()
69
+ line = reader.readline()
70
+ if line == "":
71
+ return default
72
+ line = line.strip()
73
+ return line if line else default
74
+
75
+
76
+ def _ask_yes_no(prompt, default=True, reader=None, writer=None):
77
+ hint = "[Y/n]" if default else "[y/N]"
78
+ value = _ask(f"{prompt} {hint}", None, reader, writer)
79
+ if value is None:
80
+ return default
81
+ return str(value).strip().lower() in ("y", "yes", "true", "1")
82
+
83
+
84
+ def interactive_plan(domain=None, protocols=None, reader=None, writer=None) -> dict:
85
+ writer = writer or sys.stdout
86
+ writer.write("=" * 56 + "\n")
87
+ writer.write(f" {PROG} 交互式部署向导\n")
88
+ writer.write("=" * 56 + "\n")
89
+
90
+ while not domain:
91
+ domain = _ask("主域名 (例: example.com)", None, reader, writer)
92
+ if not domain:
93
+ writer.write(" [!] 域名必填\n")
94
+
95
+ protocols = protocols or _ask("启用协议 (逗号分隔)", ",".join(PROTOCOLS), reader, writer)
96
+ mode = _ask("出站模式 none/proxy/tun", "proxy", reader, writer)
97
+ server_ip = _ask("服务器公网 IP (留空=自动探测)", None, reader, writer)
98
+ use_dns = _ask_yes_no("用 Cloudflare 管理 DNS A 记录?", True, reader, writer)
99
+ use_acme = _ask_yes_no("签发 TLS 证书 (acme.sh DNS-01)?", True, reader, writer)
100
+ use_runtime = _ask_yes_no(
101
+ "启用运行时适配器 (packages/systemd/firewall/watchdog)?", True, reader, writer
102
+ )
103
+
104
+ server_out = _ask("服务端配置输出路径", "/etc/sing-box/config.json", reader, writer)
105
+ client_out = _ask("客户端配置输出路径", "/root/singbox-client.json", reader, writer)
106
+ links_out = _ask("分享链接输出路径", "/root/singbox-links.txt", reader, writer)
107
+
108
+ return {
109
+ "domain_root": domain,
110
+ "protocols": [item.strip() for item in str(protocols).split(",") if item.strip()],
111
+ "tunnel_mode": mode,
112
+ "server_ip": server_ip or "auto",
113
+ "adapters": {
114
+ "secrets": "singbox-subprocess",
115
+ "dns": "cloudflare" if use_dns else None,
116
+ "acme": "cloudflare-dns01" if use_acme else None,
117
+ "state": "local-json" if use_runtime else None,
118
+ "runtime": {
119
+ "packages": "debian" if use_runtime else None,
120
+ "systemd": "systemd" if use_runtime else None,
121
+ "firewall": "nftables-basic" if use_runtime else None,
122
+ "watchdog": "warp" if use_runtime else None,
123
+ },
124
+ },
125
+ "outputs": {
126
+ "server_config": server_out,
127
+ "client_config": client_out,
128
+ "links": links_out,
129
+ },
130
+ }
131
+
132
+
133
+ def _detect_or_placeholder(plan) -> str:
134
+ try:
135
+ from .core.network import detect_public_ipv4
136
+
137
+ return detect_public_ipv4()
138
+ except OpsError:
139
+ return DRY_RUN_IP
140
+
141
+
142
+ def _run(args) -> int:
143
+ try:
144
+ if getattr(args, "plan", None):
145
+ plan = load_plan(args.plan)
146
+ elif args.command == "destroy":
147
+ raise OpsError("destroy requires --plan")
148
+ else:
149
+ data = interactive_plan(
150
+ domain=getattr(args, "domain", None),
151
+ protocols=getattr(args, "protocols", None),
152
+ )
153
+ if args.command == "plan":
154
+ output = getattr(args, "output", "plan.yaml") or "plan.yaml"
155
+ with open(output, "w", encoding="utf-8") as handle:
156
+ handle.write(formats.dumps(data, "yaml"))
157
+ print(f"wrote {output}")
158
+ return 0
159
+ plan = DeployPlan.from_mapping(data)
160
+
161
+ dry_run = True if args.command == "context" else bool(getattr(args, "dry_run", False))
162
+ runner = make_runner(dry_run)
163
+ suite = build_suite(plan, runner)
164
+
165
+ if args.command == "context":
166
+ generated = suite.secrets.generate(plan, dry_run=True)
167
+ server_ip = plan.server_ip or _detect_or_placeholder(plan)
168
+ context = build_context(
169
+ plan,
170
+ generated.credentials,
171
+ server_ip,
172
+ subdomain_prefixes=generated.subdomain_prefixes,
173
+ )
174
+ print(formats.dumps(context, getattr(args, "format", None) or plan.output_format))
175
+ return 0
176
+
177
+ if args.command in ("deploy", "redeploy"):
178
+ report = deploy(plan, suite, dry_run=dry_run)
179
+ else:
180
+ report = destroy(plan, suite, dry_run=dry_run)
181
+ except OpsError as exc:
182
+ print(f"error: {exc}", file=sys.stderr)
183
+ return 1
184
+ except (OSError, formats.FormatError, ValueError) as exc:
185
+ print(f"error: {exc}", file=sys.stderr)
186
+ return 2
187
+
188
+ print(json.dumps(report.as_dict(), indent=2, ensure_ascii=False))
189
+ return 0
190
+
191
+
192
+ def build_parser() -> argparse.ArgumentParser:
193
+ """Standalone parser (used by ``python -m singbox_ops.cli``)."""
194
+
195
+ parser = argparse.ArgumentParser(prog=PROG, description="sing-box operations.")
196
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
197
+ add_subparsers(parser.add_subparsers(dest="command", required=True))
198
+ return parser
199
+
200
+
201
+ def main(argv: Optional[list] = None) -> int:
202
+ args = build_parser().parse_args(argv)
203
+ return args.handler(args)
204
+
205
+
206
+ if __name__ == "__main__":
207
+ raise SystemExit(main())
@@ -15,7 +15,7 @@ class TestCliEndToEnd(unittest.TestCase):
15
15
  def test_version_matches_package_version(self):
16
16
  result = run_cli("--version")
17
17
  self.assertEqual(result.returncode, 0, result.stderr)
18
- self.assertEqual(result.stdout.strip(), f"devconfig-gen {__version__}")
18
+ self.assertEqual(result.stdout.strip(), f"devconfig_gen_singbox {__version__}")
19
19
 
20
20
  def test_providers_lists_builtins(self):
21
21
  result = run_cli("providers")
@@ -2,10 +2,23 @@ import json
2
2
  import unittest
3
3
 
4
4
  from tests._ops_support import EXAMPLES, run_ops_cli
5
+ from tests._support import run_cli
5
6
 
6
7
  PLAN = str(EXAMPLES / "singbox-deploy.yaml")
7
8
 
8
9
 
10
+ class SingleCliNameTests(unittest.TestCase):
11
+ """There is exactly one command, named after the PyPI distribution."""
12
+
13
+ def test_ops_subcommands_are_grafted_onto_the_single_cli(self):
14
+ result = run_cli("--help")
15
+ self.assertEqual(result.returncode, 0, result.stderr)
16
+ self.assertIn("devconfig_gen_singbox", result.stdout)
17
+ for name in ("generate", "init", "ui", "context", "plan", "deploy", "redeploy", "destroy"):
18
+ self.assertIn(name, result.stdout)
19
+ self.assertNotIn("singbox-ops", result.stdout)
20
+
21
+
9
22
  class OpsCliTests(unittest.TestCase):
10
23
  def test_context_command_prints_valid_provider_input(self):
11
24
  result = run_ops_cli("context", "--plan", PLAN, "--format", "json")
@@ -1,4 +0,0 @@
1
- [console_scripts]
2
- devconfig-gen = devconfig_gen.cli:main
3
- devconfig_gen_singbox = devconfig_gen.cli:main
4
- singbox-ops = singbox_ops.cli:main
@@ -1,109 +0,0 @@
1
- """Command-line interface for the operations layer.
2
-
3
- singbox-ops context --plan plan.yaml
4
- singbox-ops deploy --plan plan.yaml [--dry-run]
5
- singbox-ops redeploy --plan plan.yaml
6
- singbox-ops destroy --plan plan.yaml [--dry-run]
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import argparse
12
- import json
13
- import sys
14
- from typing import Optional
15
-
16
- from devconfig_gen import formats
17
-
18
- from .core.context_builder import build_context
19
- from .core.exceptions import OpsError
20
- from .core.plan import load_plan
21
- from .core.orchestrator import (
22
- DRY_RUN_IP,
23
- build_suite,
24
- deploy,
25
- destroy,
26
- make_runner,
27
- )
28
-
29
- __version__ = "0.1.0"
30
-
31
-
32
- def build_parser() -> argparse.ArgumentParser:
33
- parser = argparse.ArgumentParser(
34
- prog="singbox-ops",
35
- description="Deploy and manage sing-box using the DevConfig-Gen engine.",
36
- )
37
- parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
38
- sub = parser.add_subparsers(dest="command", required=True)
39
-
40
- for name, help_text in (
41
- ("context", "Assemble and print the sing-box context (no side effects)"),
42
- ("deploy", "Deploy sing-box using the plan"),
43
- ("redeploy", "Regenerate credentials and redeploy"),
44
- ("destroy", "Tear down what a previous deploy created"),
45
- ):
46
- command = sub.add_parser(name, help=help_text)
47
- command.add_argument("--plan", required=True, help="Path to a JSON/YAML plan")
48
- if name != "context":
49
- command.add_argument(
50
- "--dry-run",
51
- action="store_true",
52
- help="Print intended actions without touching the system",
53
- )
54
- if name == "context":
55
- command.add_argument("--format", choices=("json", "yaml"), help="Output format")
56
- command.set_defaults(handler=_run)
57
- return parser
58
-
59
-
60
- def _detect_or_placeholder(plan) -> str:
61
- try:
62
- from .core.network import detect_public_ipv4
63
-
64
- return detect_public_ipv4()
65
- except OpsError:
66
- return DRY_RUN_IP
67
-
68
-
69
- def _run(args) -> int:
70
- try:
71
- plan = load_plan(args.plan)
72
- dry_run = True if args.command == "context" else bool(getattr(args, "dry_run", False))
73
- runner = make_runner(dry_run)
74
- suite = build_suite(plan, runner)
75
-
76
- if args.command == "context":
77
- generated = suite.secrets.generate(plan, dry_run=True)
78
- server_ip = plan.server_ip or _detect_or_placeholder(plan)
79
- context = build_context(
80
- plan,
81
- generated.credentials,
82
- server_ip,
83
- subdomain_prefixes=generated.subdomain_prefixes,
84
- )
85
- print(formats.dumps(context, getattr(args, "format", None) or plan.output_format))
86
- return 0
87
-
88
- if args.command in ("deploy", "redeploy"):
89
- report = deploy(plan, suite, dry_run=dry_run)
90
- else:
91
- report = destroy(plan, suite, dry_run=dry_run)
92
- except OpsError as exc:
93
- print(f"error: {exc}", file=sys.stderr)
94
- return 1
95
- except (OSError, formats.FormatError, ValueError) as exc:
96
- print(f"error: {exc}", file=sys.stderr)
97
- return 2
98
-
99
- print(json.dumps(report.as_dict(), indent=2, ensure_ascii=False))
100
- return 0
101
-
102
-
103
- def main(argv: Optional[list] = None) -> int:
104
- args = build_parser().parse_args(argv)
105
- return args.handler(args)
106
-
107
-
108
- if __name__ == "__main__":
109
- raise SystemExit(main())