@singbox-iac/cli 0.1.4 → 0.1.6

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 (64) hide show
  1. package/README-en.md +62 -11
  2. package/README.md +62 -11
  3. package/assets/rule-set/geoip-cn.srs +0 -0
  4. package/assets/rule-set/geosite-anthropic.srs +0 -0
  5. package/assets/rule-set/geosite-cn.srs +0 -0
  6. package/assets/rule-set/geosite-cursor.srs +0 -0
  7. package/assets/rule-set/geosite-figma.srs +0 -0
  8. package/assets/rule-set/geosite-github-copilot.srs +0 -0
  9. package/assets/rule-set/geosite-github.srs +0 -0
  10. package/assets/rule-set/geosite-google-deepmind.srs +0 -0
  11. package/assets/rule-set/geosite-google-gemini.srs +0 -0
  12. package/assets/rule-set/geosite-google.srs +0 -0
  13. package/dist/cli/command-helpers.js +1 -1
  14. package/dist/cli/command-helpers.js.map +1 -1
  15. package/dist/cli/commands/author.d.ts +28 -0
  16. package/dist/cli/commands/author.js +119 -121
  17. package/dist/cli/commands/author.js.map +1 -1
  18. package/dist/cli/commands/build.js +8 -35
  19. package/dist/cli/commands/build.js.map +1 -1
  20. package/dist/cli/commands/go.d.ts +2 -0
  21. package/dist/cli/commands/go.js +38 -0
  22. package/dist/cli/commands/go.js.map +1 -0
  23. package/dist/cli/commands/proxifier.d.ts +2 -0
  24. package/dist/cli/commands/proxifier.js +61 -0
  25. package/dist/cli/commands/proxifier.js.map +1 -0
  26. package/dist/cli/commands/quickstart.d.ts +2 -0
  27. package/dist/cli/commands/quickstart.js +54 -0
  28. package/dist/cli/commands/quickstart.js.map +1 -0
  29. package/dist/cli/commands/setup.d.ts +30 -0
  30. package/dist/cli/commands/setup.js +277 -233
  31. package/dist/cli/commands/setup.js.map +1 -1
  32. package/dist/cli/commands/update.js +16 -2
  33. package/dist/cli/commands/update.js.map +1 -1
  34. package/dist/cli/commands/use.d.ts +2 -0
  35. package/dist/cli/commands/use.js +45 -0
  36. package/dist/cli/commands/use.js.map +1 -0
  37. package/dist/cli/index.js +43 -2
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/modules/compiler/index.js +15 -10
  40. package/dist/modules/compiler/index.js.map +1 -1
  41. package/dist/modules/fetcher/index.d.ts +1 -0
  42. package/dist/modules/fetcher/index.js +66 -19
  43. package/dist/modules/fetcher/index.js.map +1 -1
  44. package/dist/modules/manager/index.d.ts +1 -0
  45. package/dist/modules/manager/index.js +15 -0
  46. package/dist/modules/manager/index.js.map +1 -1
  47. package/dist/modules/natural-language/index.js +3 -2
  48. package/dist/modules/natural-language/index.js.map +1 -1
  49. package/dist/modules/proxifier/index.d.ts +27 -0
  50. package/dist/modules/proxifier/index.js +184 -0
  51. package/dist/modules/proxifier/index.js.map +1 -0
  52. package/dist/modules/rule-set-sync/index.d.ts +10 -0
  53. package/dist/modules/rule-set-sync/index.js +72 -12
  54. package/dist/modules/rule-set-sync/index.js.map +1 -1
  55. package/dist/modules/update/index.d.ts +1 -0
  56. package/dist/modules/update/index.js +4 -1
  57. package/dist/modules/update/index.js.map +1 -1
  58. package/dist/modules/verification/index.d.ts +15 -0
  59. package/dist/modules/verification/index.js +89 -44
  60. package/dist/modules/verification/index.js.map +1 -1
  61. package/docs/proxifier-onboarding.md +65 -0
  62. package/docs/runtime-on-macos.md +24 -7
  63. package/examples/builder.config.yaml +8 -27
  64. package/package.json +11 -2
package/README-en.md CHANGED
@@ -62,7 +62,7 @@ The system has three layers:
62
62
 
63
63
  ```mermaid
64
64
  flowchart TD
65
- A["User provides subscription URL + one sentence"] --> B["setup"]
65
+ A["User provides subscription URL + one sentence"] --> B["quickstart / setup --ready"]
66
66
  B --> C["Detect local environment<br/>macOS / sing-box / Chrome / AI CLI"]
67
67
  C --> D["Generate rules from intent"]
68
68
  D --> E["Sync local rule sets"]
@@ -158,24 +158,42 @@ singbox-iac --help
158
158
 
159
159
  ## Quick Start
160
160
 
161
+ ### Most users only need 3 commands
162
+
163
+ ```bash
164
+ singbox-iac go '<subscription-url>' '<one-sentence intent>'
165
+ singbox-iac use '<new routing sentence>'
166
+ singbox-iac update
167
+ ```
168
+
169
+ - `go`: first-time onboarding in one command
170
+ - `use`: change policy later with one sentence and re-apply it
171
+ - `update`: refresh the subscription and apply the latest config
172
+
173
+ Everything else can be treated as advanced commands for debugging or fine-grained control.
174
+
161
175
  ### One-step onboarding
162
176
 
163
177
  ```bash
164
- singbox-iac setup \
178
+ singbox-iac quickstart \
165
179
  --subscription-url 'your subscription URL' \
166
180
  --prompt 'GitHub and developer sites go through Hong Kong, Antigravity process traffic goes through the US, Gemini goes through Singapore, update every 30 minutes'
167
181
  ```
168
182
 
169
- `setup` will:
183
+ `quickstart` will:
170
184
 
171
185
  - create `~/.config/singbox-iac/builder.config.yaml`
172
186
  - create `~/.config/singbox-iac/rules/custom.rules.yaml`
187
+ - create `~/.config/singbox-iac/proxifier/` helper files
173
188
  - check local environment readiness
174
- - download default local `.srs` rule sets
189
+ - use bundled default `.srs` rule sets and only sync extras when needed
175
190
  - turn one sentence into routing rules
176
191
  - build `~/.config/singbox-iac/generated/config.staging.json`
192
+ - verify critical routes
193
+ - publish the live config
194
+ - install the recurring schedule
177
195
 
178
- For a more guided first-run path:
196
+ If you want the more explicit onboarding command, you can still use:
179
197
 
180
198
  ```bash
181
199
  singbox-iac setup \
@@ -184,11 +202,10 @@ singbox-iac setup \
184
202
  --ready
185
203
  ```
186
204
 
187
- `--ready` additionally performs:
205
+ The difference is simple:
188
206
 
189
- - route verification
190
- - live config publish
191
- - recurring schedule installation
207
+ - `quickstart` is the shortest opinionated first-run path
208
+ - `setup --ready` is better if you still want to control run/browser/Proxifier flags yourself
192
209
 
193
210
  ### Manual foreground run
194
211
 
@@ -204,7 +221,7 @@ Default local listeners:
204
221
  ### Day-to-day usage
205
222
 
206
223
  ```bash
207
- singbox-iac update --reload
224
+ singbox-iac update
208
225
  ```
209
226
 
210
227
  That command performs:
@@ -213,7 +230,7 @@ That command performs:
213
230
  - build
214
231
  - verify
215
232
  - apply
216
- - optional reload
233
+ - reload automatically when a `sing-box` process is already running
217
234
 
218
235
  ### Background schedule
219
236
 
@@ -223,6 +240,12 @@ singbox-iac schedule install
223
240
 
224
241
  ## Natural-Language Authoring
225
242
 
243
+ If you only want to change the routing sentence and apply it immediately, use the shorter command:
244
+
245
+ ```bash
246
+ singbox-iac use 'GitHub and developer sites go through Hong Kong, Gemini goes through Singapore'
247
+ ```
248
+
226
249
  For common cases, you do not need to learn raw `sing-box` JSON or even the DSL.
227
250
 
228
251
  Examples:
@@ -245,6 +268,30 @@ The authoring layer supports:
245
268
  - preview before writing
246
269
  - closed-loop update after rule generation
247
270
 
271
+ ## Proxifier Onboarding
272
+
273
+ If your AI IDE, language server, or desktop app does not respect the normal system proxy path, use the generated Proxifier helper directory:
274
+
275
+ ```text
276
+ ~/.config/singbox-iac/proxifier/
277
+ ```
278
+
279
+ It contains:
280
+
281
+ - `README.md`
282
+ - `proxy-endpoint.txt`
283
+ - `custom-processes.txt`
284
+ - `bundles/antigravity.txt`
285
+ - `bundles/cursor.txt`
286
+ - `bundles/developer-ai-cli.txt`
287
+ - `all-processes.txt`
288
+
289
+ If you only want to regenerate the Proxifier helper files:
290
+
291
+ ```bash
292
+ singbox-iac proxifier scaffold --prompt 'Antigravity process traffic goes through the US and Cursor also uses a dedicated ingress'
293
+ ```
294
+
248
295
  ## How It Works With sing-box
249
296
 
250
297
  This project does not replace the `sing-box` core binary. It generates and manages `sing-box` configuration.
@@ -282,6 +329,7 @@ The project does not automatically upload subscription URLs to any remote servic
282
329
  ```bash
283
330
  singbox-iac init
284
331
  singbox-iac setup
332
+ singbox-iac quickstart
285
333
  singbox-iac author
286
334
  singbox-iac build
287
335
  singbox-iac check
@@ -290,6 +338,8 @@ singbox-iac run
290
338
  singbox-iac verify
291
339
  singbox-iac update
292
340
  singbox-iac doctor
341
+ singbox-iac proxifier bundles
342
+ singbox-iac proxifier scaffold
293
343
  singbox-iac schedule install
294
344
  singbox-iac schedule remove
295
345
  singbox-iac templates list
@@ -300,6 +350,7 @@ singbox-iac templates list
300
350
  - [rules-dsl.md](./docs/rules-dsl.md)
301
351
  - [rule-templates.md](./docs/rule-templates.md)
302
352
  - [natural-language-authoring.md](./docs/natural-language-authoring.md)
353
+ - [proxifier-onboarding.md](./docs/proxifier-onboarding.md)
303
354
  - [runtime-on-macos.md](./docs/runtime-on-macos.md)
304
355
  - [openspec/project.md](./openspec/project.md)
305
356
 
package/README.md CHANGED
@@ -62,7 +62,7 @@ flowchart LR
62
62
 
63
63
  ```mermaid
64
64
  flowchart TD
65
- A["用户提供订阅地址 + 一句话需求"] --> B["setup"]
65
+ A["用户提供订阅地址 + 一句话需求"] --> B["quickstart / setup --ready"]
66
66
  B --> C["检测本地环境<br/>macOS / sing-box / Chrome / AI CLI"]
67
67
  C --> D["根据意图生成规则"]
68
68
  D --> E["同步本地 rule set"]
@@ -158,24 +158,42 @@ singbox-iac --help
158
158
 
159
159
  ## 快速开始
160
160
 
161
+ ### 大多数用户只需要 3 个命令
162
+
163
+ ```bash
164
+ singbox-iac go '<订阅地址>' '<一句话需求>'
165
+ singbox-iac use '<新的需求描述>'
166
+ singbox-iac update
167
+ ```
168
+
169
+ - `go`:第一次安装时使用,一条命令完成初始化、验证、发布和定时任务准备
170
+ - `use`:以后需求变化时使用,一句话改策略并重新应用
171
+ - `update`:日常更新订阅并自动在运行中的 `sing-box` 上生效
172
+
173
+ 其余命令都可以理解为高级命令,主要用于调试、排障或更细粒度控制。
174
+
161
175
  ### 一步完成初始化
162
176
 
163
177
  ```bash
164
- singbox-iac setup \
178
+ singbox-iac quickstart \
165
179
  --subscription-url '你的机场订阅地址' \
166
180
  --prompt 'GitHub 这类开发类走香港,Antigravity 进程级走美国,Gemini 走新加坡,每30分钟自动更新'
167
181
  ```
168
182
 
169
- `setup` 会自动:
183
+ `quickstart` 会自动:
170
184
 
171
185
  - 生成 `~/.config/singbox-iac/builder.config.yaml`
172
186
  - 生成 `~/.config/singbox-iac/rules/custom.rules.yaml`
187
+ - 生成 `~/.config/singbox-iac/proxifier/` 辅助文件
173
188
  - 检查本地环境
174
- - 下载默认本地 `.srs` 规则集
189
+ - 使用包内内置的默认 `.srs` 规则集,并在需要时补充同步
175
190
  - 把一句自然语言转成路由规则
176
191
  - 构建 `~/.config/singbox-iac/generated/config.staging.json`
192
+ - 验证关键路由
193
+ - 发布 live config
194
+ - 安装定时更新
177
195
 
178
- 如果你希望首次安装时尽可能自动化,可以这样:
196
+ 如果你希望保留更细的步骤控制,也可以继续用:
179
197
 
180
198
  ```bash
181
199
  singbox-iac setup \
@@ -184,11 +202,10 @@ singbox-iac setup \
184
202
  --ready
185
203
  ```
186
204
 
187
- `--ready` 会额外执行:
205
+ `setup --ready` 和 `quickstart` 的主要区别是:
188
206
 
189
- - 路由验证
190
- - 发布 live config
191
- - 安装定时更新
207
+ - `quickstart` 更偏默认的一键上手
208
+ - `setup --ready` 更适合你还想自己决定是否运行、是否开浏览器、是否写 Proxifier 辅助目录
192
209
 
193
210
  ### 前台运行测试
194
211
 
@@ -204,7 +221,7 @@ singbox-iac run
204
221
  ### 日常使用
205
222
 
206
223
  ```bash
207
- singbox-iac update --reload
224
+ singbox-iac update
208
225
  ```
209
226
 
210
227
  这条命令会执行:
@@ -213,7 +230,7 @@ singbox-iac update --reload
213
230
  - build
214
231
  - verify
215
232
  - apply
216
- - optional reload
233
+ - 如果检测到 `sing-box` 正在运行,则自动 reload
217
234
 
218
235
  ### 定时更新
219
236
 
@@ -223,6 +240,12 @@ singbox-iac schedule install
223
240
 
224
241
  ## 自然语言规则编写
225
242
 
243
+ 如果你只想“改一句话需求并立即生效”,推荐直接用更短的命令:
244
+
245
+ ```bash
246
+ singbox-iac use 'GitHub 这类开发类都走香港,Gemini 走新加坡'
247
+ ```
248
+
226
249
  对于大多数场景,不需要手写 `sing-box` JSON,也不需要理解 DSL。
227
250
 
228
251
  示例:
@@ -245,6 +268,30 @@ singbox-iac author \
245
268
  - 写入前先 preview
246
269
  - 生成规则后直接闭环 update
247
270
 
271
+ ## Proxifier 上手
272
+
273
+ 如果你的 AI IDE、language server 或桌面应用不走系统代理,可以直接使用自动生成的 Proxifier 辅助目录:
274
+
275
+ ```text
276
+ ~/.config/singbox-iac/proxifier/
277
+ ```
278
+
279
+ 其中会包含:
280
+
281
+ - `README.md`
282
+ - `proxy-endpoint.txt`
283
+ - `custom-processes.txt`
284
+ - `bundles/antigravity.txt`
285
+ - `bundles/cursor.txt`
286
+ - `bundles/developer-ai-cli.txt`
287
+ - `all-processes.txt`
288
+
289
+ 如果你只想重新生成这部分,不需要重跑全部 onboarding:
290
+
291
+ ```bash
292
+ singbox-iac proxifier scaffold --prompt 'Antigravity 进程级走美国,Cursor 也走独立入口'
293
+ ```
294
+
248
295
  ## 它和 sing-box 是怎么配合的
249
296
 
250
297
  它不会替代 `sing-box` 内核本身,而是负责生成和维护 `sing-box` 配置。
@@ -282,6 +329,7 @@ singbox-iac author \
282
329
  ```bash
283
330
  singbox-iac init
284
331
  singbox-iac setup
332
+ singbox-iac quickstart
285
333
  singbox-iac author
286
334
  singbox-iac build
287
335
  singbox-iac check
@@ -290,6 +338,8 @@ singbox-iac run
290
338
  singbox-iac verify
291
339
  singbox-iac update
292
340
  singbox-iac doctor
341
+ singbox-iac proxifier bundles
342
+ singbox-iac proxifier scaffold
293
343
  singbox-iac schedule install
294
344
  singbox-iac schedule remove
295
345
  singbox-iac templates list
@@ -300,6 +350,7 @@ singbox-iac templates list
300
350
  - [rules-dsl.md](./docs/rules-dsl.md)
301
351
  - [rule-templates.md](./docs/rule-templates.md)
302
352
  - [natural-language-authoring.md](./docs/natural-language-authoring.md)
353
+ - [proxifier-onboarding.md](./docs/proxifier-onboarding.md)
303
354
  - [runtime-on-macos.md](./docs/runtime-on-macos.md)
304
355
  - [openspec/project.md](./openspec/project.md)
305
356
 
Binary file
Binary file
@@ -42,9 +42,9 @@ async function fileExists(filePath) {
42
42
  }
43
43
  function getDefaultConfigCandidates() {
44
44
  return [
45
+ getDefaultConfigPath(),
45
46
  path.resolve(process.cwd(), "builder.config.local.yaml"),
46
47
  path.resolve(process.cwd(), "builder.config.yaml"),
47
- getDefaultConfigPath(),
48
48
  ];
49
49
  }
50
50
  export function resolvePackageRoot(moduleUrl) {
@@ -1 +1 @@
1
- {"version":3,"file":"command-helpers.js","sourceRoot":"","sources":["../../src/cli/command-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAOtD,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,qBAAqB,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAA6B;IAE7B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,qBAAqB,EAAE,CAAC;IACjD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,UAAU,CAAC,UAAU,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,KAAK,MAAM,QAAQ,IAAI,0BAA0B,EAAE,EAAE,CAAC;QACpD,IAAI,MAAM,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B;IACjC,OAAO;QACL,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,2BAA2B,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,qBAAqB,CAAC;QAClD,oBAAoB,EAAE;KACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,iBAAiB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,QAAQ,SAAS,EAAE,CAAC,CAAC;AAClF,CAAC"}
1
+ {"version":3,"file":"command-helpers.js","sourceRoot":"","sources":["../../src/cli/command-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAOtD,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,qBAAqB,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAA6B;IAE7B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,qBAAqB,EAAE,CAAC;IACjD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,UAAU,CAAC,UAAU,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,KAAK,MAAM,QAAQ,IAAI,0BAA0B,EAAE,EAAE,CAAC;QACpD,IAAI,MAAM,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B;IACjC,OAAO;QACL,oBAAoB,EAAE;QACtB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,2BAA2B,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,qBAAqB,CAAC;KACnD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,iBAAiB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,QAAQ,SAAS,EAAE,CAAC,CAAC;AAClF,CAAC"}
@@ -1,2 +1,30 @@
1
1
  import type { Command } from "commander";
2
2
  export declare function registerAuthorCommand(program: Command): void;
3
+ export declare function runAuthorFlow(options: AuthorCommandOptions): Promise<void>;
4
+ interface AuthorCommandOptions {
5
+ readonly prompt: string;
6
+ readonly config?: string;
7
+ readonly provider?: "deterministic" | "auto" | "claude" | "exec";
8
+ readonly authorTimeoutMs?: string;
9
+ readonly execCommand?: string;
10
+ readonly execArg: string[];
11
+ readonly rulesOut?: string;
12
+ readonly subscriptionUrl?: string;
13
+ readonly subscriptionFile?: string;
14
+ readonly preview?: boolean;
15
+ readonly skipBuild?: boolean;
16
+ readonly update?: boolean;
17
+ readonly skipVerify?: boolean;
18
+ readonly reload?: boolean;
19
+ readonly livePath?: string;
20
+ readonly backupPath?: string;
21
+ readonly singBoxBin?: string;
22
+ readonly chromeBin?: string;
23
+ readonly installSchedule?: boolean;
24
+ readonly label: string;
25
+ readonly launchAgentsDir?: string;
26
+ readonly logsDir?: string;
27
+ readonly forceSchedule?: boolean;
28
+ readonly load?: boolean;
29
+ }
30
+ export {};
@@ -9,7 +9,7 @@ import { findDefaultConfigPath, resolveBuilderConfig, resolveCliEntrypoint, } fr
9
9
  export function registerAuthorCommand(program) {
10
10
  program
11
11
  .command("author")
12
- .description("Generate custom rules from a natural-language prompt, then optionally build and schedule.")
12
+ .description("Advanced rule authoring from a natural-language prompt.")
13
13
  .requiredOption("-p, --prompt <text>", "natural-language routing prompt")
14
14
  .option("-c, --config <path>", "path to builder config YAML")
15
15
  .option("--provider <provider>", "authoring provider: deterministic, auto, claude, exec")
@@ -35,89 +35,45 @@ export function registerAuthorCommand(program) {
35
35
  .option("-f, --force-schedule", "replace an existing LaunchAgent file when installing")
36
36
  .option("--no-load", "write the LaunchAgent without calling launchctl bootstrap")
37
37
  .action(async (options) => {
38
- const [builderConfig, configPath] = await Promise.all([
39
- resolveBuilderConfig(options),
40
- options.config ? Promise.resolve(resolvePath(options.config)) : findDefaultConfigPath(),
41
- ]);
42
- if (!builderConfig || !configPath) {
43
- throw new Error("author requires a builder config. Pass --config or run init first.");
44
- }
45
- if (options.update && options.skipBuild) {
46
- throw new Error("author cannot use --update together with --skip-build.");
47
- }
48
- const planResult = await generateAuthoringPlan({
49
- prompt: options.prompt,
38
+ await runAuthorFlow(options);
39
+ });
40
+ }
41
+ export async function runAuthorFlow(options) {
42
+ const [builderConfig, configPath] = await Promise.all([
43
+ resolveBuilderConfig(options),
44
+ options.config ? Promise.resolve(resolvePath(options.config)) : findDefaultConfigPath(),
45
+ ]);
46
+ if (!builderConfig || !configPath) {
47
+ throw new Error("author requires a builder config. Pass --config or run init first.");
48
+ }
49
+ if (options.update && options.skipBuild) {
50
+ throw new Error("author cannot use --update together with --skip-build.");
51
+ }
52
+ const planResult = await generateAuthoringPlan({
53
+ prompt: options.prompt,
54
+ config: builderConfig,
55
+ ...(options.provider ? { provider: options.provider } : {}),
56
+ ...(options.authorTimeoutMs ? { timeoutMs: Number.parseInt(options.authorTimeoutMs, 10) } : {}),
57
+ ...(options.execCommand ? { execCommand: options.execCommand } : {}),
58
+ ...(options.execArg.length > 0 ? { execArgs: options.execArg } : {}),
59
+ });
60
+ const plan = planResult.plan;
61
+ const rulesPath = options.rulesOut
62
+ ? resolvePath(options.rulesOut)
63
+ : builderConfig.rules.userRulesFile;
64
+ const effectiveConfig = applyPlanToBuilderConfig(builderConfig, {
65
+ rulesPath,
66
+ plan,
67
+ });
68
+ if (options.preview) {
69
+ const preview = await generateAuthoringPreview({
70
+ configPath,
50
71
  config: builderConfig,
51
- ...(options.provider ? { provider: options.provider } : {}),
52
- ...(options.authorTimeoutMs
53
- ? { timeoutMs: Number.parseInt(options.authorTimeoutMs, 10) }
54
- : {}),
55
- ...(options.execCommand ? { execCommand: options.execCommand } : {}),
56
- ...(options.execArg.length > 0 ? { execArgs: options.execArg } : {}),
57
- });
58
- const plan = planResult.plan;
59
- const rulesPath = options.rulesOut
60
- ? resolvePath(options.rulesOut)
61
- : builderConfig.rules.userRulesFile;
62
- const effectiveConfig = applyPlanToBuilderConfig(builderConfig, {
63
- rulesPath,
64
72
  plan,
65
- });
66
- if (options.preview) {
67
- const preview = await generateAuthoringPreview({
68
- configPath,
69
- config: builderConfig,
70
- plan,
71
- rulesPath,
72
- ...(options.subscriptionFile ? { subscriptionFile: options.subscriptionFile } : {}),
73
- ...(options.subscriptionUrl ? { subscriptionUrl: options.subscriptionUrl } : {}),
74
- buildStaging: !options.skipBuild,
75
- });
76
- const lines = [
77
- `Config: ${configPath}`,
78
- `Rules: ${rulesPath}`,
79
- `Provider requested: ${planResult.providerRequested}`,
80
- `Provider used: ${planResult.providerUsed}`,
81
- `Templates: ${plan.templateIds.length > 0 ? plan.templateIds.join(", ") : "(none)"}`,
82
- `Generated rules: ${plan.beforeBuiltins.length + plan.afterBuiltins.length}`,
83
- "Preview mode: no files were written.",
84
- ];
85
- if (plan.notes.length > 0) {
86
- lines.push(`Notes: ${plan.notes.length}`);
87
- lines.push(...plan.notes.map((note) => `- ${note}`));
88
- }
89
- if (options.installSchedule) {
90
- lines.push("Schedule install: requested, but skipped in preview mode.");
91
- }
92
- if (options.update) {
93
- lines.push("Live update: requested, but skipped in preview mode.");
94
- }
95
- lines.push("");
96
- lines.push("Rules diff:");
97
- lines.push(preview.rulesDiff.diff);
98
- lines.push("");
99
- lines.push("Builder config diff:");
100
- lines.push(preview.configDiff.diff);
101
- if (preview.stagingDiff) {
102
- lines.push("");
103
- lines.push("Staging config diff:");
104
- lines.push(preview.stagingDiff.diff);
105
- }
106
- process.stdout.write(`${lines.join("\n")}\n`);
107
- return;
108
- }
109
- await writeGeneratedRules({
110
- filePath: rulesPath,
111
- plan,
112
- });
113
- await updateBuilderAuthoring({
114
- configPath,
115
73
  rulesPath,
116
- ...(plan.scheduleIntervalMinutes ? { intervalMinutes: plan.scheduleIntervalMinutes } : {}),
117
- ...(plan.groupDefaults ? { groupDefaults: plan.groupDefaults } : {}),
118
- ...(plan.verificationOverrides
119
- ? { verificationOverrides: plan.verificationOverrides }
120
- : {}),
74
+ ...(options.subscriptionFile ? { subscriptionFile: options.subscriptionFile } : {}),
75
+ ...(options.subscriptionUrl ? { subscriptionUrl: options.subscriptionUrl } : {}),
76
+ buildStaging: !options.skipBuild,
121
77
  });
122
78
  const lines = [
123
79
  `Config: ${configPath}`,
@@ -126,58 +82,100 @@ export function registerAuthorCommand(program) {
126
82
  `Provider used: ${planResult.providerUsed}`,
127
83
  `Templates: ${plan.templateIds.length > 0 ? plan.templateIds.join(", ") : "(none)"}`,
128
84
  `Generated rules: ${plan.beforeBuiltins.length + plan.afterBuiltins.length}`,
85
+ "Preview mode: no files were written.",
129
86
  ];
130
87
  if (plan.notes.length > 0) {
131
88
  lines.push(`Notes: ${plan.notes.length}`);
132
89
  lines.push(...plan.notes.map((note) => `- ${note}`));
133
90
  }
134
91
  if (options.installSchedule) {
135
- const intervalMinutes = plan.scheduleIntervalMinutes ?? effectiveConfig.schedule.intervalMinutes;
136
- const schedule = await installLaunchdSchedule({
137
- configPath,
138
- intervalMinutes,
139
- cliEntrypoint: resolveCliEntrypoint(import.meta.url),
140
- label: options.label,
141
- ...(options.launchAgentsDir
142
- ? { launchAgentsDir: resolvePath(options.launchAgentsDir) }
143
- : {}),
144
- ...(options.logsDir ? { logsDir: resolvePath(options.logsDir) } : {}),
145
- force: options.forceSchedule === true,
146
- load: options.load !== false,
147
- });
148
- lines.push(`LaunchAgent: ${schedule.plistPath}`);
92
+ lines.push("Schedule install: requested, but skipped in preview mode.");
149
93
  }
150
94
  if (options.update) {
151
- const updateResult = await runUpdate({
152
- config: effectiveConfig,
153
- ...(options.subscriptionFile ? { subscriptionFile: options.subscriptionFile } : {}),
154
- ...(options.subscriptionUrl ? { subscriptionUrl: options.subscriptionUrl } : {}),
155
- ...(options.livePath ? { livePath: resolvePath(options.livePath) } : {}),
156
- ...(options.backupPath ? { backupPath: resolvePath(options.backupPath) } : {}),
157
- ...(options.singBoxBin ? { singBoxBinary: resolvePath(options.singBoxBin) } : {}),
158
- ...(options.chromeBin ? { chromeBinary: resolvePath(options.chromeBin) } : {}),
159
- verify: !options.skipVerify,
160
- ...(options.reload ? { reload: true } : {}),
161
- });
162
- lines.push(`Staging: ${updateResult.build.outputPath}`);
163
- lines.push(updateResult.verification
164
- ? `Verified scenarios: ${updateResult.verification.scenarios.filter((scenario) => scenario.passed).length}/${updateResult.verification.scenarios.length}`
165
- : "Verified scenarios: skipped");
166
- lines.push(`Live: ${updateResult.livePath}`);
167
- if (updateResult.backupPath) {
168
- lines.push(`Backup: ${updateResult.backupPath}`);
169
- }
95
+ lines.push("Live update: requested, but skipped in preview mode.");
170
96
  }
171
- else if (!options.skipBuild) {
172
- const build = await buildConfigArtifact({
173
- config: effectiveConfig,
174
- ...(options.subscriptionFile ? { subscriptionFile: options.subscriptionFile } : {}),
175
- ...(options.subscriptionUrl ? { subscriptionUrl: options.subscriptionUrl } : {}),
176
- });
177
- lines.push(`Staging: ${build.outputPath}`);
97
+ lines.push("");
98
+ lines.push("Rules diff:");
99
+ lines.push(preview.rulesDiff.diff);
100
+ lines.push("");
101
+ lines.push("Builder config diff:");
102
+ lines.push(preview.configDiff.diff);
103
+ if (preview.stagingDiff) {
104
+ lines.push("");
105
+ lines.push("Staging config diff:");
106
+ lines.push(preview.stagingDiff.diff);
178
107
  }
179
108
  process.stdout.write(`${lines.join("\n")}\n`);
109
+ return;
110
+ }
111
+ await writeGeneratedRules({
112
+ filePath: rulesPath,
113
+ plan,
114
+ });
115
+ await updateBuilderAuthoring({
116
+ configPath,
117
+ rulesPath,
118
+ ...(plan.scheduleIntervalMinutes ? { intervalMinutes: plan.scheduleIntervalMinutes } : {}),
119
+ ...(plan.groupDefaults ? { groupDefaults: plan.groupDefaults } : {}),
120
+ ...(plan.verificationOverrides ? { verificationOverrides: plan.verificationOverrides } : {}),
180
121
  });
122
+ const lines = [
123
+ `Config: ${configPath}`,
124
+ `Rules: ${rulesPath}`,
125
+ `Provider requested: ${planResult.providerRequested}`,
126
+ `Provider used: ${planResult.providerUsed}`,
127
+ `Templates: ${plan.templateIds.length > 0 ? plan.templateIds.join(", ") : "(none)"}`,
128
+ `Generated rules: ${plan.beforeBuiltins.length + plan.afterBuiltins.length}`,
129
+ ];
130
+ if (plan.notes.length > 0) {
131
+ lines.push(`Notes: ${plan.notes.length}`);
132
+ lines.push(...plan.notes.map((note) => `- ${note}`));
133
+ }
134
+ if (options.installSchedule) {
135
+ const intervalMinutes = plan.scheduleIntervalMinutes ?? effectiveConfig.schedule.intervalMinutes;
136
+ const schedule = await installLaunchdSchedule({
137
+ configPath,
138
+ intervalMinutes,
139
+ cliEntrypoint: resolveCliEntrypoint(import.meta.url),
140
+ label: options.label,
141
+ ...(options.launchAgentsDir ? { launchAgentsDir: resolvePath(options.launchAgentsDir) } : {}),
142
+ ...(options.logsDir ? { logsDir: resolvePath(options.logsDir) } : {}),
143
+ force: options.forceSchedule === true,
144
+ load: options.load !== false,
145
+ });
146
+ lines.push(`LaunchAgent: ${schedule.plistPath}`);
147
+ }
148
+ if (options.update) {
149
+ const updateResult = await runUpdate({
150
+ config: effectiveConfig,
151
+ ...(options.subscriptionFile ? { subscriptionFile: options.subscriptionFile } : {}),
152
+ ...(options.subscriptionUrl ? { subscriptionUrl: options.subscriptionUrl } : {}),
153
+ ...(options.livePath ? { livePath: resolvePath(options.livePath) } : {}),
154
+ ...(options.backupPath ? { backupPath: resolvePath(options.backupPath) } : {}),
155
+ ...(options.singBoxBin ? { singBoxBinary: resolvePath(options.singBoxBin) } : {}),
156
+ ...(options.chromeBin ? { chromeBinary: resolvePath(options.chromeBin) } : {}),
157
+ verify: !options.skipVerify,
158
+ ...(options.reload ? { reload: true } : {}),
159
+ });
160
+ lines.push(`Staging: ${updateResult.build.outputPath}`);
161
+ lines.push(updateResult.verification
162
+ ? `Verified scenarios: ${updateResult.verification.scenarios.filter((scenario) => scenario.passed).length}/${updateResult.verification.scenarios.length}`
163
+ : "Verified scenarios: skipped");
164
+ lines.push(`Live: ${updateResult.livePath}`);
165
+ if (updateResult.backupPath) {
166
+ lines.push(`Backup: ${updateResult.backupPath}`);
167
+ }
168
+ lines.push(`Reload: ${updateResult.reloaded ? "triggered" : "skipped"}`);
169
+ }
170
+ else if (!options.skipBuild) {
171
+ const build = await buildConfigArtifact({
172
+ config: effectiveConfig,
173
+ ...(options.subscriptionFile ? { subscriptionFile: options.subscriptionFile } : {}),
174
+ ...(options.subscriptionUrl ? { subscriptionUrl: options.subscriptionUrl } : {}),
175
+ });
176
+ lines.push(`Staging: ${build.outputPath}`);
177
+ }
178
+ process.stdout.write(`${lines.join("\n")}\n`);
181
179
  }
182
180
  function resolvePath(filePath) {
183
181
  return filePath.startsWith("/") ? filePath : path.resolve(process.cwd(), filePath);