abelworkflow 0.9.1 → 1.0.0-rc.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.
Files changed (128) hide show
  1. package/README.md +55 -165
  2. package/bin/abelworkflow.mjs +5 -3
  3. package/extensions/{pi-gpt-responses-compat.ts → pi-gpt-responses-compat/index.ts} +17 -0
  4. package/extensions/pi-gpt-responses-compat/tls-fetch.mjs +188 -0
  5. package/lib/cli/args.mjs +76 -0
  6. package/lib/cli/main.mjs +387 -0
  7. package/lib/cli/prompts.mjs +119 -0
  8. package/lib/config/dotenv.mjs +102 -0
  9. package/lib/config/jsonc.mjs +80 -0
  10. package/lib/config/store.mjs +310 -0
  11. package/lib/config/toml.mjs +638 -0
  12. package/lib/installer/assets.mjs +326 -0
  13. package/lib/installer/install.mjs +167 -0
  14. package/lib/installer/links.mjs +427 -0
  15. package/lib/installer/render.mjs +46 -0
  16. package/lib/installer/state.mjs +182 -0
  17. package/lib/paths.mjs +99 -0
  18. package/lib/providers/claude.mjs +388 -0
  19. package/lib/providers/codex.mjs +433 -0
  20. package/lib/providers/pi.mjs +362 -0
  21. package/lib/providers/skills.mjs +164 -0
  22. package/lib/templates/codex/agents/default.toml +7 -0
  23. package/lib/templates/codex/agents/explorer.toml +7 -0
  24. package/lib/templates/codex/agents/planner.toml +7 -0
  25. package/lib/templates/codex/agents/reviewer.toml +7 -0
  26. package/lib/templates/codex/agents/worker.toml +7 -0
  27. package/lib/templates/codex/config-base.toml +11 -3
  28. package/{AGENTS.md → lib/templates/workflow/AGENTS.md} +8 -3
  29. package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
  30. package/{commands → lib/templates/workflow/commands}/abel-implement.md +3 -8
  31. package/{commands → lib/templates/workflow/commands}/abel-init.md +1 -1
  32. package/{commands → lib/templates/workflow/commands}/abel-plan.md +1 -7
  33. package/{commands → lib/templates/workflow/commands}/abel-research.md +1 -1
  34. package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
  35. package/lib/tools/cli-installer.mjs +277 -0
  36. package/package.json +44 -10
  37. package/skills/context7-auto-research/SKILL.md +5 -5
  38. package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
  39. package/skills/dev-browser/SKILL.md +53 -33
  40. package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
  41. package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
  42. package/skills/dev-browser/dist/scripts/start.js +118 -0
  43. package/skills/dev-browser/dist/scripts/start.js.map +1 -0
  44. package/skills/dev-browser/dist/src/client.d.ts +93 -0
  45. package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
  46. package/skills/dev-browser/dist/src/client.js +310 -0
  47. package/skills/dev-browser/dist/src/client.js.map +1 -0
  48. package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
  49. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
  50. package/skills/dev-browser/dist/src/entrypoint.js +106 -0
  51. package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
  52. package/skills/dev-browser/dist/src/index.d.ts +4 -0
  53. package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
  54. package/skills/dev-browser/dist/src/index.js +2 -0
  55. package/skills/dev-browser/dist/src/index.js.map +1 -0
  56. package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
  57. package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
  58. package/skills/dev-browser/dist/src/page-api.js +104 -0
  59. package/skills/dev-browser/dist/src/page-api.js.map +1 -0
  60. package/skills/dev-browser/dist/src/relay.d.ts +27 -0
  61. package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
  62. package/skills/dev-browser/dist/src/relay.js +521 -0
  63. package/skills/dev-browser/dist/src/relay.js.map +1 -0
  64. package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
  65. package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
  66. package/skills/dev-browser/dist/src/runtime.js +54 -0
  67. package/skills/dev-browser/dist/src/runtime.js.map +1 -0
  68. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
  69. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
  70. package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
  71. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
  72. package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
  73. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
  74. package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
  75. package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
  76. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
  77. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
  78. package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
  79. package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
  80. package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
  81. package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
  82. package/skills/dev-browser/dist/src/standalone.js +174 -0
  83. package/skills/dev-browser/dist/src/standalone.js.map +1 -0
  84. package/skills/dev-browser/dist/src/startup.d.ts +55 -0
  85. package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
  86. package/skills/dev-browser/dist/src/startup.js +81 -0
  87. package/skills/dev-browser/dist/src/startup.js.map +1 -0
  88. package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
  89. package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
  90. package/skills/dev-browser/dist/src/target-registry.js +135 -0
  91. package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
  92. package/skills/dev-browser/dist/src/types.d.ts +27 -0
  93. package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
  94. package/skills/dev-browser/dist/src/types.js +2 -0
  95. package/skills/dev-browser/dist/src/types.js.map +1 -0
  96. package/skills/dev-browser/package-lock.json +67 -1510
  97. package/skills/dev-browser/package.json +18 -14
  98. package/skills/dev-browser/references/scraping.md +94 -105
  99. package/skills/grok-search/defaults.json +3 -0
  100. package/skills/grok-search/gitignore.template +4 -0
  101. package/skills/grok-search/scripts/_dotenv.py +5 -1
  102. package/skills/grok-search/scripts/groksearch_cli.py +15 -33
  103. package/skills/prompt-enhancer/TEMPLATE.md +0 -4
  104. package/skills/prompt-enhancer/requirements.txt +0 -1
  105. package/skills/prompt-enhancer/scripts/_dotenv.py +5 -1
  106. package/skills/prompt-enhancer/scripts/enhance.py +4 -51
  107. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +1 -16
  108. package/.skill-lock.json +0 -29
  109. package/lib/cli/logic.mjs +0 -213
  110. package/lib/cli.mjs +0 -2825
  111. package/skills/confidence-check/SKILL.md +0 -34
  112. package/skills/confidence-check/confidence.ts +0 -276
  113. package/skills/dev-browser/bun.lock +0 -460
  114. package/skills/dev-browser/scripts/start.ts +0 -280
  115. package/skills/dev-browser/src/client.ts +0 -474
  116. package/skills/dev-browser/src/entrypoint.ts +0 -157
  117. package/skills/dev-browser/src/index.ts +0 -289
  118. package/skills/dev-browser/src/relay.ts +0 -731
  119. package/skills/dev-browser/src/runtime.test.ts +0 -60
  120. package/skills/dev-browser/src/runtime.ts +0 -171
  121. package/skills/dev-browser/src/startup.test.ts +0 -95
  122. package/skills/dev-browser/src/startup.ts +0 -153
  123. package/skills/dev-browser/src/types.ts +0 -35
  124. package/skills/dev-browser/tsconfig.json +0 -36
  125. package/skills/dev-browser/vitest.config.ts +0 -12
  126. package/skills/sequential-think/SKILL.md +0 -198
  127. package/skills/sequential-think/scripts/.env.example +0 -5
  128. package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
package/README.md CHANGED
@@ -1,207 +1,97 @@
1
1
  # AbelWorkflow
2
2
 
3
- Codex、OpenCode、Claude CodePi Skills、Commands/Prompts 和扩展配置仓库。
3
+ AbelWorkflow 为 Codex、Claude CodePi 部署统一的 Skills、工作流命令、Agent 配置与扩展。
4
4
 
5
- ## 目录结构
5
+ ## 环境要求
6
6
 
7
- ```
8
- .agents/
9
- ├── skills/ # 技能目录
10
- │ ├── time/ # 时间与时区工具
11
- │ ├── grok-search/ # 增强型网页搜索
12
- │ ├── sequential-think/ # 多步推理引擎
13
- │ ├── dev-browser/ # 浏览器自动化
14
- │ ├── context7-auto-research/ # 自动文档检索
15
- │ ├── confidence-check/ # 实施前信心评估
16
- │ ├── git-commit/ # Git 提交助手
17
- │ └── prompt-enhancer/ # 提示词优化器
18
- ├── commands/ # 命令目录
19
- │ └── oc/ # 工作流命令
20
- ├── extensions/ # Pi 扩展
21
- ├── AGENTS.md # Agent 全局系统 prompts
22
- └── README.md
23
- ```
24
-
25
- ## 技能概览
7
+ - Node.js 22 或更高版本
8
+ - npm 10 或兼容版本
9
+ - Linux、macOS 或原生 Windows
26
10
 
27
- | 技能 | 描述 |
28
- |------|------|
29
- | **time** | 时间和时区工具,获取当前时间及时区转换 |
30
- | **grok-search** | 通过 Grok API 增强网页搜索与实时内容检索 |
31
- | **sequential-think** | 多步推理引擎,支持假设检验与分支的复杂分析 |
32
- | **dev-browser** | 浏览器自动化,支持导航、表单填写、截图与数据提取 |
33
- | **context7-auto-research** | 自动从 Context7 获取最新库/框架文档 |
34
- | **confidence-check** | 实施前置信度评估(≥90%),含架构合规与根因识别 |
35
- | **git-commit** | Conventional Commits 规范提交,智能暂存与消息生成 |
36
- | **prompt-enhancer** | CoT 推理优化 AI 编码提示词,模糊请求转结构化指令 |
11
+ ## 正式工作流命令
37
12
 
38
- ## 命令概览
13
+ | 命令 | 用途 |
14
+ | --- | --- |
15
+ | `/abel-init` | 初始化 OpenSpec 环境并验证工具链 |
16
+ | `/abel-research` | 研究需求并生成约束,不实施代码 |
17
+ | `/abel-plan` | 将已确认变更细化为可执行计划 |
18
+ | `/abel-implement` | 按强制 TDD 实施已批准变更 |
19
+ | `/abel-diagnose` | 进行根因分析、回归测试和批量修复 |
39
20
 
40
- | 命令 | 描述 |
41
- |------|------|
42
- | **/oc:init** | 初始化 OpenSpec 环境并验证工具链 |
43
- | **/oc:research** | 结构化需求探索与约束集生成(不实施) |
44
- | **/oc:plan** | 将已批准变更细化为零决策可执行方案 |
45
- | **/oc:implementation** | 以 TDD 方式实施已批准的变更 |
46
- | **/oc:diagnose** | 系统化根因分析与批量修复报告 |
21
+ 工作流:
47
22
 
48
- ## 工作流
49
-
50
- ```
51
- /oc:init → /oc:research → /oc:plan → /oc:implementation(TDD)
52
- ↘ /oc:diagnose (bug fix)
23
+ ```text
24
+ /abel-init → /abel-research → /abel-plan → /abel-implement(TDD)
25
+ ↘ /abel-diagnose (bug fix)
53
26
  ```
54
27
 
55
- ## 配置说明
56
-
57
- - **目录位置**:
58
- - Linux/macOS: `~/.agents/`
59
- - Windows: `%USERPROFILE%\.agents\`(PowerShell: `$HOME\.agents`)
60
- - **AGENTS.md**: Agent 全局系统 prompts 配置
28
+ OpenSpec 命令保持独立:`/opsx:new`、`/opsx:ff`、`/opsx:archive`、`openspec view`、`openspec status`。
61
29
 
62
30
  ## 安装与更新
63
31
 
64
- ### 方法一:npx 一键安装(推荐)
32
+ ### npx
65
33
 
66
34
  ```bash
67
- # 交互式初始化菜单
68
35
  npx abelworkflow
69
-
70
- # 直接执行工作流同步/重链
71
36
  npx abelworkflow install
72
-
73
- # 更新到最新发布版本
74
37
  npx abelworkflow@latest
75
38
  ```
76
39
 
77
- > 说明:
78
- > - npm 发布包名必须使用小写,所以实际可执行命令是 `npx abelworkflow`。
79
- > - 交互式模式下,默认会打开初始化菜单,支持:
80
- > - 同步 `~/.agents` 并自动重建 `~/.claude` / `~/.codex` / `~/.pi/agent` 链接
81
- > - 交互式填写 `grok-search`、`context7-auto-research`、`prompt-enhancer` 的 `.env`
82
- > - 一键安装或更新 `Claude Code`、`Codex`、`Pi`
83
- > - 配置 `Claude Code` 的第三方 API 到 `~/.claude/settings.json`
84
- > - 配置 `Codex` 的第三方 API 到 `~/.codex/config.toml` 和 `~/.codex/auth.json`
85
- > - 配置 `Pi` 的自定义 API 到 `~/.pi/agent/models.json` 中的 `gpt` provider,并设置 `~/.pi/agent/settings.json` 默认模型,同时携带 Pi 扩展
86
- > - 非交互场景请显式使用 `npx abelworkflow install`,不再保留旧的默认自动同步逻辑。
40
+ TTY 的环境必须显式运行 `npx abelworkflow install`。安装器将工作流部署到 `~/.agents`,并维护 Claude、Codex 与 Pi 的链接。
87
41
 
88
- ### 交互式初始化能力
42
+ ### 源码安装
89
43
 
90
- `npx abelworkflow` 现在默认提供一个类似 `npx zcf` 的菜单,常见入口包括:
44
+ 源码目录和部署目录必须分离,不能相同或互相嵌套。推荐流程:
91
45
 
92
46
  ```bash
93
- npx abelworkflow
94
- npx abelworkflow init
95
- npx abelworkflow install
96
- npx abelworkflow --help
47
+ git clone https://github.com/abelxiaoxing/AbelWorkflow ~/src/AbelWorkflow
48
+ cd ~/src/AbelWorkflow
49
+ npm ci
50
+ npm ci --prefix skills/dev-browser
51
+ npm run build
52
+ node bin/abelworkflow.mjs install --agents-dir ~/.agents
97
53
  ```
98
54
 
99
- 其中完整初始化会按需引导你完成:
100
-
101
- 1. 安装 AbelWorkflow 到 `~/.agents`
102
- 2. 自动链接到 `~/.claude/`、`~/.codex/` 和 `~/.pi/agent/`
103
- 3. 可选安装 `Claude Code` CLI
104
- 4. 可选配置 `Claude Code` 第三方 API
105
- 5. 可选安装 `Codex` CLI
106
- 6. 可选配置 `Codex` 第三方 API
107
- 7. 可选安装 `Pi` CLI
108
- 8. 可选配置 Pi `gpt` provider 自定义 API,并链接 Pi 扩展
109
- 9. 可选填写三个技能的环境变量
110
-
111
- ### 技能环境写入位置
112
-
113
- 交互式配置会把技能密钥写到 `~/.agents` 下对应 skill 目录的 `.env` 中:
114
-
115
- | 技能 | 写入位置 | 主要字段 |
116
- |---|---|---|
117
- | `grok-search` | `~/.agents/skills/grok-search/.env` | `GROK_API_URL` `GROK_API_KEY` `GROK_MODEL` |
118
- | `context7-auto-research` | `~/.agents/skills/context7-auto-research/.env` | `CONTEXT7_API_KEY` |
119
- | `prompt-enhancer` | `~/.agents/skills/prompt-enhancer/.env` | `PE_API_URL` `PE_API_KEY` `PE_MODEL` |
120
-
121
- ### 方法二:源码克隆安装
122
-
123
- 如需自定义修改或贡献代码,可手动克隆后直接运行本地 CLI:
124
-
125
- #### 1. 克隆仓库
126
-
127
- **Linux/macOS(bash/zsh)**
128
-
129
- ```bash
130
- # 首次安装
131
- git clone https://github.com/abelxiaoxing/AbelWorkflow ~/.agents
132
-
133
- # 更新
134
- git -C ~/.agents pull
135
- ```
136
-
137
- **Windows(PowerShell)**
138
-
139
- ```powershell
140
- # 首次安装
141
- git clone https://github.com/abelxiaoxing/AbelWorkflow "$HOME\.agents"
142
-
143
- # 更新
144
- git -C "$HOME\.agents" pull
145
- ```
146
-
147
- #### 2. 执行本地初始化
148
-
149
- **Linux/macOS(bash/zsh)**
150
-
151
- ```bash
152
- cd ~/.agents
153
- node bin/abelworkflow.mjs
154
- ```
155
-
156
- **Windows(PowerShell)**
55
+ Windows PowerShell 可将源码克隆到 `$HOME\src\AbelWorkflow`,然后运行相同的 npm 命令,并使用:
157
56
 
158
57
  ```powershell
159
- Set-Location "$HOME\.agents"
160
- node .\bin\abelworkflow.mjs
58
+ node .\bin\abelworkflow.mjs install --agents-dir "$HOME\.agents"
161
59
  ```
162
60
 
163
- 如果只想重建链接,不进入菜单:
164
-
165
- **Linux/macOS(bash/zsh)**
61
+ 不要把源码仓库直接克隆到 `~/.agents`。已有这种布局时,先按 [1.0 迁移指南](https://github.com/abelxiaoxing/AbelWorkflow/blob/master/docs/migration-1.0.md) 人工建立独立源码目录;安装器不会猜测性移动或删除用户数据。
166
62
 
167
- ```bash
168
- cd ~/.agents
169
- node bin/abelworkflow.mjs install
170
- ```
63
+ ## 配置行为
171
64
 
172
- **Windows(PowerShell)**
65
+ - Claude 默认使用 `standard` 权限档,不预授权 Bash、Write 或 Edit;只有用户明确选择 `trusted` 才增加宽权限。已有 permissions、deny、hooks 和 timeout 保持不变。
66
+ - Codex 只更新 AbelWorkflow 管理的认证字段,保留未知字段、其他 Provider 和用户 token。
67
+ - Pi 0.80.0 及以上版本将 GPT API Key 保存在 `~/.pi/agent/auth.json`;`models.json` 只保留模型定义。旧 models-only key 会按先写 auth、后删除旧 key 的顺序迁移。
68
+ - Grok 默认模型统一为 `grok-4.20-auto`。
69
+ - Context7 使用显式 CommonJS 入口 `context7-api.cjs`。
70
+ - dev-browser 发布运行时使用 Node ESM 编译产物,入口为 `node dist/scripts/start.js`,不依赖 Bun 或运行时 `npx tsx`。
71
+ - skill 密钥写入 `~/.agents/skills/<skill>/.env`;这些文件属于私密配置。
173
72
 
174
- ```powershell
175
- Set-Location "$HOME\.agents"
176
- node .\bin\abelworkflow.mjs install
177
- ```
73
+ `.skill-lock.json` 完全属于用户:安装器不读取、不修改、不备份、不删除,也不会把它放入发布包。
178
74
 
179
- ### 映射关系(本仓库 → 配置目录)
75
+ ## 部署映射
180
76
 
181
- | 本仓库 | Claude Code | Codex | Pi | 说明 |
182
- |---|---|---|---|---|
183
- | `AGENTS.md` | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.md` | `~/.pi/agent/AGENTS.md` | 全局系统提示词/规则 |
184
- | `skills/<skill>/` | `~/.claude/skills/<skill>/` | `~/.codex/skills/<skill>/` | `~/.pi/agent/skills/<skill>/` | Skills(每个目录一个技能) |
185
- | `commands/abel-*.md` | `~/.claude/commands/abel-*.md` | `~/.codex/prompts/abel-*.md` | `~/.pi/agent/prompts/abel-*.md` | 扁平化部署,避免命名冲突 |
186
- | `extensions/*.ts` | - | - | `~/.pi/agent/extensions/*.ts` | Pi 扩展 |
77
+ | 托管内容 | Claude Code | Codex | Pi |
78
+ | --- | --- | --- | --- |
79
+ | 工作流 AGENTS 模板 | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.md` | `~/.pi/agent/AGENTS.md` |
80
+ | `skills/<skill>/` | `~/.claude/skills/<skill>/` | `~/.codex/skills/<skill>/` | `~/.pi/agent/skills/<skill>/` |
81
+ | `abel-*.md` 命令 | `~/.claude/commands/` | `~/.codex/prompts/` | `~/.pi/agent/prompts/` |
82
+ | `extensions/*` | | | `~/.pi/agent/extensions/` |
187
83
 
188
- ### 验证(可选)
84
+ ## 自签名 HTTPS 中转
189
85
 
190
- **Linux/macOS(bash/zsh)**
86
+ 优先取得中转的 CA PEM,并在启动客户端前配置:
191
87
 
192
88
  ```bash
193
- ls -la "$HOME/.claude/CLAUDE.md" "$HOME/.claude/commands/"abel-*.md
194
- ls -la "$HOME/.codex/AGENTS.md" "$HOME/.codex/prompts/"abel-*.md
195
- cat "$HOME/.claude/settings.json" | head
196
- cat "$HOME/.codex/config.toml" | head
89
+ export NODE_EXTRA_CA_CERTS=/absolute/path/relay-ca.pem
90
+ export CODEX_CA_CERTIFICATE=/absolute/path/relay-ca.pem
197
91
  ```
198
92
 
199
- **Windows(PowerShell)**
93
+ 不要全局设置 `NODE_TLS_REJECT_UNAUTHORIZED=0`。证书过期或主机名不匹配时应重新签发证书。
200
94
 
201
- ```powershell
202
- Get-Item "$HOME\.claude\CLAUDE.md"; Get-ChildItem "$HOME\.claude\commands\abel-*.md"
203
- Get-Item "$HOME\.codex\AGENTS.md"
204
- Get-ChildItem "$HOME\.codex\prompts\abel-*.md"
205
- Get-Content "$HOME\.claude\settings.json" -TotalCount 10
206
- Get-Content "$HOME\.codex\config.toml" -TotalCount 10
207
- ```
95
+ ## 升级
96
+
97
+ 0.x 升级前请阅读 [AbelWorkflow 1.0 迁移指南](https://github.com/abelxiaoxing/AbelWorkflow/blob/master/docs/migration-1.0.md)。指南涵盖源码/部署目录拆分、metadata v2、Provider 配置、用户状态、dev-browser 和回滚。
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { main } from "../lib/cli.mjs";
2
+ import { main } from "../lib/cli/main.mjs";
3
3
 
4
- main().catch((error) => {
4
+ main(process.argv.slice(2)).then((code) => {
5
+ process.exitCode = code;
6
+ }).catch((error) => {
5
7
  console.error(error instanceof Error ? error.message : String(error));
6
- process.exit(1);
8
+ process.exitCode = 1;
7
9
  });
@@ -1,6 +1,23 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { createNodeInsecureDispatcher, installProviderTlsFetch } from "./tls-fetch.mjs";
3
+
4
+ let nodeInsecureDispatcher: unknown;
5
+
6
+ function getNodeInsecureDispatcher() {
7
+ if (nodeInsecureDispatcher) return nodeInsecureDispatcher;
8
+ nodeInsecureDispatcher = createNodeInsecureDispatcher();
9
+ return nodeInsecureDispatcher;
10
+ }
2
11
 
3
12
  export default function (pi: ExtensionAPI) {
13
+ if (typeof globalThis.fetch === "function") {
14
+ const runtime = typeof (globalThis as any).Bun === "undefined" ? "node" : "bun";
15
+ installProviderTlsFetch({
16
+ runtime,
17
+ insecureDispatcher: runtime === "node" ? getNodeInsecureDispatcher : undefined
18
+ });
19
+ }
20
+
4
21
  pi.on("before_provider_request", (event, ctx) => {
5
22
  const payload = event.payload as any;
6
23
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return;
@@ -0,0 +1,188 @@
1
+ export const piInsecureTlsHeader = "x-abelworkflow-insecure-tls";
2
+
3
+ const installedFetchMarker = Symbol.for("abelworkflow.pi-provider-tls-fetch");
4
+ const undiciGlobalDispatchers = [
5
+ Symbol.for("undici.globalDispatcher.2"),
6
+ Symbol.for("undici.globalDispatcher.1")
7
+ ];
8
+ const redirectStatuses = new Set([301, 302, 303, 307, 308]);
9
+ const maxRedirects = 5;
10
+
11
+ function getMarkedHeaders(input, init) {
12
+ const source = init?.headers !== undefined
13
+ ? init.headers
14
+ : typeof Request !== "undefined" && input instanceof Request
15
+ ? input.headers
16
+ : undefined;
17
+ if (source === undefined) return null;
18
+
19
+ const headers = new Headers(source);
20
+ if (!headers.has(piInsecureTlsHeader)) return null;
21
+ const allowedOrigin = headers.get(piInsecureTlsHeader);
22
+ headers.delete(piInsecureTlsHeader);
23
+ return { allowedOrigin, headers };
24
+ }
25
+
26
+ function getRequestUrl(input) {
27
+ return typeof Request !== "undefined" && input instanceof Request
28
+ ? input.url
29
+ : input instanceof URL
30
+ ? input.href
31
+ : input;
32
+ }
33
+
34
+ function getRequestOrigin(input) {
35
+ try {
36
+ return new URL(getRequestUrl(input)).origin;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function redirectRequestInit(input, init, status) {
43
+ const method = String(init.method ?? (
44
+ typeof Request !== "undefined" && input instanceof Request ? input.method : "GET"
45
+ )).toUpperCase();
46
+ const switchToGet = status === 303 && method !== "HEAD"
47
+ || (status === 301 || status === 302) && method === "POST";
48
+ if (!switchToGet) {
49
+ if (typeof Request !== "undefined" && input instanceof Request && method !== "GET" && method !== "HEAD") {
50
+ throw new Error("Pi insecure TLS cannot safely replay a redirected Request body");
51
+ }
52
+ return init;
53
+ }
54
+
55
+ const headers = new Headers(init.headers);
56
+ for (const name of ["content-encoding", "content-language", "content-length", "content-location", "content-type"]) {
57
+ headers.delete(name);
58
+ }
59
+ const nextInit = { ...init, method: "GET", headers };
60
+ delete nextInit.body;
61
+ return nextInit;
62
+ }
63
+
64
+ async function fetchWithSameOriginRedirects({ fetchOnce, input, init, allowedOrigin }) {
65
+ let nextInput = input;
66
+ let nextInit = init;
67
+
68
+ for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount += 1) {
69
+ const response = await fetchOnce(nextInput, nextInit);
70
+ const location = redirectStatuses.has(response?.status) ? response.headers?.get?.("location") : null;
71
+ if (!location) return response;
72
+ if (redirectCount === maxRedirects) {
73
+ await response.body?.cancel?.();
74
+ throw new Error(`Pi insecure TLS exceeded ${maxRedirects} same-origin redirects`);
75
+ }
76
+
77
+ const target = new URL(location, getRequestUrl(nextInput));
78
+ if (target.origin !== allowedOrigin) {
79
+ await response.body?.cancel?.();
80
+ throw new Error(`Pi insecure TLS blocked cross-origin redirect to ${target.origin}`);
81
+ }
82
+ await response.body?.cancel?.();
83
+ nextInit = redirectRequestInit(nextInput, nextInit, response.status);
84
+ nextInput = target.href;
85
+ }
86
+ }
87
+
88
+ function applyNodeDispatcher(fetchImpl, input, init, dispatcherSource) {
89
+ let dispatcher;
90
+ try {
91
+ dispatcher = typeof dispatcherSource === "function" ? dispatcherSource() : dispatcherSource;
92
+ } catch (error) {
93
+ return Promise.reject(error);
94
+ }
95
+
96
+ if (dispatcher && typeof dispatcher.then === "function") {
97
+ return dispatcher.then((resolved) => applyNodeDispatcher(fetchImpl, input, init, resolved));
98
+ }
99
+ if (!dispatcher || typeof dispatcher.dispatch !== "function") {
100
+ return Promise.reject(new Error("Pi insecure TLS requires an injected Undici dispatcher"));
101
+ }
102
+ return fetchImpl(input, { ...init, dispatcher });
103
+ }
104
+
105
+ function getUndiciDispatcherConstructor(target) {
106
+ return undiciGlobalDispatchers
107
+ .map((symbol) => target[symbol]?.constructor)
108
+ .find((constructor) => typeof constructor === "function");
109
+ }
110
+
111
+ function initializeUndiciGlobalDispatcher(target) {
112
+ if (typeof target.fetch !== "function") return;
113
+ try {
114
+ void Promise.resolve(target.fetch("data:,")).catch(() => {});
115
+ } catch {
116
+ }
117
+ }
118
+
119
+ export function createNodeInsecureDispatcher(target = globalThis) {
120
+ let Dispatcher = getUndiciDispatcherConstructor(target);
121
+ if (typeof Dispatcher !== "function") {
122
+ initializeUndiciGlobalDispatcher(target);
123
+ Dispatcher = getUndiciDispatcherConstructor(target);
124
+ }
125
+ if (typeof Dispatcher !== "function") {
126
+ throw new Error("Pi insecure TLS requires the active Undici dispatcher");
127
+ }
128
+ return new Dispatcher({
129
+ allowH2: false,
130
+ connect: { rejectUnauthorized: false },
131
+ requestTls: { rejectUnauthorized: false }
132
+ });
133
+ }
134
+
135
+ export function createProviderTlsFetch({
136
+ fetchImpl = globalThis.fetch,
137
+ runtime = typeof globalThis.Bun === "undefined" ? "node" : "bun",
138
+ insecureDispatcher
139
+ } = {}) {
140
+ if (typeof fetchImpl !== "function") {
141
+ throw new TypeError("A fetch implementation is required");
142
+ }
143
+
144
+ return function providerTlsFetch(input, init) {
145
+ const marked = getMarkedHeaders(input, init);
146
+ if (!marked) return fetchImpl(input, init);
147
+
148
+ const nextInit = { ...(init ?? {}), headers: marked.headers };
149
+ if (!marked.allowedOrigin || getRequestOrigin(input) !== marked.allowedOrigin) {
150
+ return fetchImpl(input, nextInit);
151
+ }
152
+
153
+ nextInit.redirect = "manual";
154
+ const fetchOnce = runtime === "bun"
155
+ ? (nextInput, redirectInit) => {
156
+ const tls = redirectInit.tls && typeof redirectInit.tls === "object" ? redirectInit.tls : {};
157
+ return fetchImpl(nextInput, {
158
+ ...redirectInit,
159
+ tls: { ...tls, rejectUnauthorized: false }
160
+ });
161
+ }
162
+ : (nextInput, redirectInit) => applyNodeDispatcher(
163
+ fetchImpl,
164
+ nextInput,
165
+ redirectInit,
166
+ insecureDispatcher
167
+ );
168
+
169
+ return fetchWithSameOriginRedirects({
170
+ fetchOnce,
171
+ input,
172
+ init: nextInit,
173
+ allowedOrigin: marked.allowedOrigin
174
+ });
175
+ };
176
+ }
177
+
178
+ export function installProviderTlsFetch({ target = globalThis, ...options } = {}) {
179
+ if (target.fetch?.[installedFetchMarker]) return target.fetch;
180
+
181
+ const wrapped = createProviderTlsFetch({
182
+ ...options,
183
+ fetchImpl: target.fetch.bind(target)
184
+ });
185
+ Object.defineProperty(wrapped, installedFetchMarker, { value: true });
186
+ target.fetch = wrapped;
187
+ return wrapped;
188
+ }
@@ -0,0 +1,76 @@
1
+ function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
2
+ const options = {
3
+ agentsDir: defaultAgentsDir,
4
+ force: false,
5
+ relinkOnly: false,
6
+ nonInteractive: false,
7
+ command: "menu"
8
+ };
9
+ const positional = [];
10
+ let helpRequested = false;
11
+
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ const arg = argv[i];
14
+ if (arg === "--force" || arg === "-f") {
15
+ options.force = true;
16
+ continue;
17
+ }
18
+ if (arg === "--link-only") {
19
+ options.relinkOnly = true;
20
+ continue;
21
+ }
22
+ if (arg === "--agents-dir") {
23
+ const value = argv[i + 1];
24
+ if (!value) {
25
+ throw new Error("--agents-dir requires a path");
26
+ }
27
+ options.agentsDir = resolvePath(value);
28
+ i += 1;
29
+ continue;
30
+ }
31
+ if (arg === "--non-interactive") {
32
+ options.nonInteractive = true;
33
+ continue;
34
+ }
35
+ if (arg === "--help" || arg === "-h" || arg === "help") {
36
+ helpRequested = true;
37
+ options.command = "help";
38
+ continue;
39
+ }
40
+ if (arg.startsWith("-")) {
41
+ throw new Error(`Unknown argument: ${arg}`);
42
+ }
43
+ positional.push(arg);
44
+ }
45
+
46
+ if (positional.length > 1) {
47
+ throw new Error(`Unknown argument: ${positional.slice(1).join(" ")}`);
48
+ }
49
+
50
+ if (positional[0]) {
51
+ if (["menu", "init"].includes(positional[0])) {
52
+ if (!helpRequested) options.command = "menu";
53
+ } else if (["install", "sync"].includes(positional[0])) {
54
+ if (!helpRequested) options.command = "install";
55
+ } else {
56
+ throw new Error(`Unknown command: ${positional[0]}`);
57
+ }
58
+ }
59
+
60
+ if (options.command !== "install" && (options.force || options.relinkOnly || options.agentsDir !== defaultAgentsDir)) {
61
+ throw new Error("`--force`、`--link-only`、`--agents-dir` 仅能与 `install` 命令一起使用");
62
+ }
63
+ if (!options.nonInteractive && process.env.CI) options.nonInteractive = true;
64
+ return options;
65
+ }
66
+
67
+ function assertInteractiveMenuSupported({ command, inputIsTTY, outputIsTTY, nonInteractive }) {
68
+ if (command === "menu" && nonInteractive) {
69
+ throw new Error("非交互模式已启用;请显式使用 `npx abelworkflow install` 进行安装");
70
+ }
71
+ if (command === "menu" && (!inputIsTTY || !outputIsTTY)) {
72
+ throw new Error("交互式菜单需要 TTY 终端;非交互场景请显式使用 `npx abelworkflow install`");
73
+ }
74
+ }
75
+
76
+ export { assertInteractiveMenuSupported, parseArgs };