@ohos-cpf/3rdloop 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -87
- package/lib/cli.js +25 -0
- package/lib/config-cmd.js +1 -1
- package/lib/serve.js +268 -0
- package/lib/update.js +46 -6
- package/lib/web-ext.js +454 -0
- package/lib/web.js +664 -0
- package/package.json +2 -1
- package/vendor/Server/Agent/SkillSelector/README.md +11 -11
- package/vendor/Server/Agent/SkillSelector/llm/llmClient.js +3 -3
- package/vendor/Server/Skills/arkts-code-use/SKILL.md +270 -0
- package/vendor/Server/Skills/arkts-code-use/assets/TEMPLATES.md +367 -0
- package/vendor/Server/Skills/arkts-code-use/references/API_VERIFICATION.md +144 -0
- package/vendor/Server/Skills/arkts-code-use/references/ARKTS_RULES.md +240 -0
- package/vendor/Server/Skills/arkts-code-use/references/CODE_PATTERNS.md +431 -0
- package/vendor/Server/Skills/arkts-code-use/references/SYNTAX_CHECK_GUIDE.md +164 -0
- package/vendor/Server/Skills/arkts-code-use/scripts/verify-arkts.cjs +428 -0
- package/vendor/Server/Skills/gitcode-repo-fork/SKILL.md +310 -0
- package/vendor/Server/Skills/gitcode-repo-fork/assets/FORK_REPORT_TEMPLATE.md +113 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/FORK_DECISION_GUIDE.md +124 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/GITCODE_FORK_API.md +95 -0
- package/vendor/Server/Skills/gitcode-repo-fork/scripts/gitcode-fork.cjs +285 -0
- package/vendor/VERSION +3 -3
- package/web/css/arktslibrarycheck.css +322 -0
- package/web/css/codecheck.css +464 -0
- package/web/css/flutterlibrarycheck.css +322 -0
- package/web/css/knowledge.css +332 -0
- package/web/css/loop.css +578 -0
- package/web/css/md-reader.css +240 -0
- package/web/css/rnlibrarycheck.css +322 -0
- package/web/css/theme.css +702 -0
- package/web/index.html +713 -0
- package/web/js/arktslibrarycheck.js +1413 -0
- package/web/js/codecheck.js +1039 -0
- package/web/js/flutterlibrarycheck.js +1364 -0
- package/web/js/health.js +69 -0
- package/web/js/knowledge.js +358 -0
- package/web/js/loop.js +1102 -0
- package/web/js/md-reader.js +435 -0
- package/web/js/navigation.js +238 -0
- package/web/js/rnlibrarycheck.js +1378 -0
- package/web/js/stats.js +110 -0
- package/web/js/theme.js +46 -0
- package/web/js/utils.js +228 -0
- package/web/knowledge.html +146 -0
- package/web/loop.html +219 -0
package/README.md
CHANGED
|
@@ -34,73 +34,145 @@ npm i -g @ohos-cpf/3rdloop
|
|
|
34
34
|
|
|
35
35
|
## 使用
|
|
36
36
|
|
|
37
|
+
按使用场景组织:装好之后,先做**环境自检**并完成**必要配置**,然后**快速开启一个任务**;进阶场景(PR 检视/自动提 PR、Web 界面、自更新)按需查阅。
|
|
37
38
|
|
|
39
|
+
### 场景一:初次使用 —— 环境自检
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
安装后第一步,先跑环境自检:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
3rdloop doctor
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`doctor` 会逐项检查 Node / opencode / `.env` 凭据 / SKILL 目录 / 数据目录,缺什么会明确提示。除此之外还需要了解:
|
|
40
48
|
|
|
41
49
|
- **Node.js** >= 22
|
|
42
|
-
- **opencode CLI**:`3rdloop` 会自动拉起 `opencode serve
|
|
43
|
-
- **LLM
|
|
44
|
-
- 项目级:仓库 `Server/.env`
|
|
45
|
-
- 用户级:`~/.3lib/.env`
|
|
46
|
-
- 系统环境变量:`setx DASHSCOPE_API_KEY "sk-xxx"`
|
|
50
|
+
- **opencode CLI**:`3rdloop` 会自动拉起 `opencode serve`(在项目上一级目录运行),退出时清理进程树;已存在则复用,一般无需手动管理
|
|
51
|
+
- **LLM 凭据**:`doctor` 提示凭据缺失时,见下面"配置"一节
|
|
47
52
|
|
|
53
|
+
### 场景二:配置 —— 需要注意哪些配置项
|
|
48
54
|
|
|
55
|
+
绝大多数配置项都有内置默认值,**开箱即用**。只有 LLM 凭据是必配项:
|
|
49
56
|
|
|
50
|
-
|
|
57
|
+
```bash
|
|
58
|
+
# 必配:百炼 API Key(从 https://bailian.console.aliyun.com/ 获取,get/list 脱敏显示)
|
|
59
|
+
3rdloop config set DASHSCOPE_API_KEY <sk-xxxxxxxx>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
按需可选(不设置即关闭/自动):
|
|
51
63
|
|
|
52
64
|
```bash
|
|
53
|
-
#
|
|
54
|
-
3rdloop
|
|
65
|
+
# GitCode Token(仅访问私有仓库时需要,get/list 脱敏显示)
|
|
66
|
+
3rdloop config set GIT_TOKEN <your-gitcode-token>
|
|
67
|
+
|
|
68
|
+
# SKILL 目录覆盖(等效 --skill-dir,不设置则自动解析)
|
|
69
|
+
3rdloop config set 3LIB_SKILL_DIR </path/to/Skills>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
有默认值、一般无需改(调整时参考):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# LLM 端点(默认百炼 OpenAI 兼容端点,仅切换其他 LLM 服务时修改)
|
|
76
|
+
3rdloop config set LLM_BASE_URL https://dashscope.aliyuncs.com/compatible-mode/v1
|
|
77
|
+
|
|
78
|
+
# LLM 模型 id
|
|
79
|
+
3rdloop config set LLM_MODEL glm-5.2
|
|
80
|
+
|
|
81
|
+
# 默认 CLI 类型(opencode | deveco-code,HarmonyOS 场景切 deveco-code)
|
|
82
|
+
3rdloop config set CLI_TYPE deveco-code
|
|
83
|
+
|
|
84
|
+
# 数据存储目录(默认 ~/.3lib/3rdloop/db,如团队共享目录)
|
|
85
|
+
3rdloop config set 3LIB_DATA_DIR "D:\code\team\shared-db"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> 其余配置项(`LLM_MAX_TOKENS` / `LLM_ENABLE_THINKING` / `SELECTOR_*` / `MAX_CONCURRENT_SESSIONS` / `OPENCODE_*` / `DEVECO_*`)均有内置默认值,完整清单执行 `3rdloop config list` 查看。`3LIB_ROOT` / `3LIB_HOME` 仅支持系统环境变量(`export` / `setx`),`config set` 会拒绝写入。
|
|
55
89
|
|
|
56
|
-
|
|
90
|
+
配置完成后统一验证:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
3rdloop config list # 查看全部配置项及当前生效值
|
|
94
|
+
3rdloop doctor # 环境自检
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
凭据也可以不通过 `config` 配置,任选一层即可:系统环境变量(`setx DASHSCOPE_API_KEY "sk-xxx"`)、用户级 `~/.3lib/.env`、项目根 `Server/.env`。
|
|
98
|
+
|
|
99
|
+
### 场景三:快速开启一个任务
|
|
100
|
+
|
|
101
|
+
在目标三方库仓库目录下,一句话描述任务,提交并阻塞到准出终态(**最常用**):
|
|
102
|
+
|
|
103
|
+
```bash
|
|
57
104
|
3rdloop run "为 libcurl 生成鸿蒙 demo 场景并跑通编译"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`run` 会自动完成"任务拆解 → 编排执行 → 准出审核"的完整大循环,多轮次自动重规划直到通过;退出码 `0` 即通过。
|
|
58
108
|
|
|
59
|
-
|
|
109
|
+
任务比较耗时、不想干等?提交后立即返回 taskId(进程内继续跑,需保持进程存活),之后可用 `3rdloop progress/wait/result <taskId>` 跟进:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
60
112
|
3rdloop submit "分析 sqlite3 的鸿蒙化可行性"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 场景四:跑预置工作流(PR 检视 / 自动提 PR 等)
|
|
116
|
+
|
|
117
|
+
常用固定流程已封装为一键工作流,先看有哪些:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
3rdloop workflows # 列出已注册的固定编排工作流
|
|
121
|
+
3rdloop run <工作流名> --help # 查看某个工作流的参数说明
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**三方库自动提 PR**(fork 工作区修改 → 源仓库 Issue/PR + 触发 CI):
|
|
61
125
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
3rdloop
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
3rdloop
|
|
126
|
+
```bash
|
|
127
|
+
# 最常用:在 fork 仓库克隆目录下直接执行(workspace-dir 缺省取当前目录)
|
|
128
|
+
3rdloop run pr-push
|
|
129
|
+
|
|
130
|
+
# 显式指定工作区与可选参数
|
|
131
|
+
3rdloop run pr-push --workspace-dir D:\work\fork-repo --upstream-url https://gitcode.com/CPF-RN/lib \
|
|
132
|
+
--changelog auto --dry-run false
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
> 提 PR 是非幂等发布操作,失败不会自动重试;执行成功后报告 `ohos-lib-pr-push-report.md` 会自动复制到执行目录。先用 `--print-taskdef` dry-run 可以核对将执行的编排定义。
|
|
136
|
+
|
|
137
|
+
### 场景五:用 Web 界面操作(serve / web)
|
|
138
|
+
|
|
139
|
+
除了命令行,也可以在浏览器里使用 Web 工作台(提交任务 / 查看循环进度 / PR 检视等),由两个命令提供支撑:
|
|
140
|
+
|
|
141
|
+
**`3rdloop serve` —— 启动 Server HTTP 后端**(供前端 / 脚本反向代理调用):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# 只启动后端(默认 http://127.0.0.1:3000,可用 --port / 3LIB_SERVER_PORT 覆盖)
|
|
145
|
+
3rdloop serve
|
|
146
|
+
|
|
147
|
+
# 后端 + Web 前端一起拉起(前端默认 8080,可用 --web-port / 3LIB_WEB_PORT 覆盖)
|
|
148
|
+
3rdloop serve --web
|
|
68
149
|
```
|
|
69
150
|
|
|
151
|
+
> CLI 的 `run`/`submit`/`wait` 等命令保持嵌入式模式,不经此后端;两者可独立使用。
|
|
152
|
+
|
|
153
|
+
**`3rdloop web` —— 启动 Web 前端服务**(静态托管 + `/api/*` 反代到后端 + 扩展挂载,默认端口 8080):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# 启动并自动打开浏览器(--backend 默认指向 http://127.0.0.1:3000,配合 serve 使用)
|
|
157
|
+
3rdloop web --open
|
|
158
|
+
|
|
159
|
+
# 自定义端口 / 后端地址
|
|
160
|
+
3rdloop web --port 8081 --backend http://127.0.0.1:3000
|
|
161
|
+
|
|
162
|
+
# 禁用全部扩展(纯核心工作台模式)
|
|
163
|
+
3rdloop web --no-ext
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
扩展机制(tag / issue / prcheck 等二级目录功能以扩展形式按需安装,不随 npm 包分发):
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
3rdloop web install-ext <扩展目录> # 安装扩展到 ~/.3lib/3rdloop/web-ext/
|
|
170
|
+
3rdloop web list-ext # 列出可用扩展(含来源与挂载点)
|
|
171
|
+
3rdloop web remove-ext <扩展名> # 移除已安装扩展
|
|
172
|
+
```
|
|
70
173
|
|
|
174
|
+
更多进阶用法(任务管理、`step` 单步执行、`orch` 自定义编排、工作流注册等),见仓库 `cli/README_DEVELOP.md` 或 `3rdloop <子命令> --help`。
|
|
71
175
|
|
|
72
|
-
### 命令一览
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
| 命令 | 说明 |
|
|
76
|
-
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
77
|
-
| `3rdloop run <desc>` | 提交任务并阻塞到准出终态(默认行为) |
|
|
78
|
-
| `3rdloop submit <desc>` | 提交任务,打印 taskId 后进程内继续执行 |
|
|
79
|
-
| `3rdloop wait <taskId> [--timeout 30m]` | 等待任务到终态 |
|
|
80
|
-
| `3rdloop progress <taskId> [--watch]` | 查询进度;`--watch` 持续轮询到终态 |
|
|
81
|
-
| `3rdloop abort <taskId>` | 中止任务 |
|
|
82
|
-
| `3rdloop list [--status st] [--limit N] [--older-than 7d]` | 列出任务 |
|
|
83
|
-
| `3rdloop result <taskId> [--raw]` | 读取最终结果 |
|
|
84
|
-
| `3rdloop step run <desc> [--task-id id] [--step-id N] [--input-report p] [--max-retries N] [--loop N] [--task-dir p] [--specified-skill <SKILL name|目录名>]` | 单步执行(FlexRunner 直连,不经拆解/编排/审核) |
|
|
85
|
-
| `3rdloop step status <taskId> [--step-id N]` | 查询单步状态/产物/会话 |
|
|
86
|
-
| `3rdloop step result <taskId> [--step-id N]` | 读取该步 result.md 内容 |
|
|
87
|
-
| `3rdloop orch run <task-def.json>` | 加载任务定义 + 启动编排,阻塞到终态 |
|
|
88
|
-
| `3rdloop orch load <task-def.json>` | 仅加载任务定义(落盘 orchestration.json,进程退出后仍可 start) |
|
|
89
|
-
| `3rdloop orch start <taskId>` | 从磁盘恢复 LOADED 状态并启动编排,阻塞到终态 |
|
|
90
|
-
| `3rdloop orch progress <taskId> [--loop N]` | 查询编排进度 |
|
|
91
|
-
| `3rdloop orch abort|pause|resume <taskId>` | 终止/暂停/恢复编排 |
|
|
92
|
-
| `3rdloop orch tasks` | 列出编排任务 |
|
|
93
|
-
| `3rdloop workflows` | 列出已注册的固定编排工作流 |
|
|
94
|
-
| `3rdloop run <工作流名> [--flag <值> ...]` | 以固定编排执行工作流(阻塞到终态) |
|
|
95
|
-
| `3rdloop run pr-check --pr-url <URL> [--library-type <arkts|flutter|rn|tpccplus>]` | PR 代码检视 |
|
|
96
|
-
| `3rdloop run pr-push [--workspace-dir <p>] [--upstream-url <URL>] [--base-branch <b>] [--changelog <auto|true|false>] [--dry-run <true|false>]` | 三方库自动提 PR(fork 工作区修改 → 源仓库 Issue/PR + 触发 CI) |
|
|
97
|
-
| `3rdloop config list|get|set|unset` | 命令配置 `.env` 配置项(默认读写 `~/.3lib/.env`) |
|
|
98
|
-
| `3rdloop update [--check] [--yes] [--force] [--registry <url>]` | 自更新到 npm 最新版(尊重用户镜像源) |
|
|
99
|
-
| `3rdloop doctor` | 环境自检(Node/opencode/.env/SKILL/数据目录) |
|
|
100
|
-
| `3rdloop version` | 版本信息 |
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
更多进阶用法(`step` 参数细节、`orch` 任务定义格式、工作流注册),见仓库 `cli/README_DEVELOP.md` 或 `3rdloop <子命令> --help`。
|
|
104
176
|
|
|
105
177
|
### 退出码
|
|
106
178
|
|
|
@@ -119,7 +191,7 @@ npm i -g @ohos-cpf/3rdloop
|
|
|
119
191
|
|
|
120
192
|
## 配置
|
|
121
193
|
|
|
122
|
-
|
|
194
|
+
> 快速上手需要的配置(LLM 凭据等)见上文 [场景二:配置](#场景二配置--需要注意哪些配置项)。本节补充完整的配置参考:数据目录与环境变量清单。
|
|
123
195
|
|
|
124
196
|
### 数据目录(中间产物)
|
|
125
197
|
|
|
@@ -136,7 +208,6 @@ npm i -g @ohos-cpf/3rdloop
|
|
|
136
208
|
|
|
137
209
|
CLI 自身配置(`3LIB_*` 前缀):
|
|
138
210
|
|
|
139
|
-
|
|
140
211
|
| 变量 | 作用 | 默认值 |
|
|
141
212
|
| ------------------ | --------------------------------- | -------------------- |
|
|
142
213
|
| `3LIB_DATA_DIR` | 数据目录覆盖(等效 `--data-dir`) | `~/.3lib/3rdloop/db` |
|
|
@@ -145,41 +216,7 @@ CLI 自身配置(`3LIB_*` 前缀):
|
|
|
145
216
|
| `3LIB_SKILL_DIR` | SKILL 目录覆盖(等效 `--skill-dir`) | 默认按发布/开发状态自动解析 |
|
|
146
217
|
| `3LIB_TASK_PREFIX` | taskId 前缀(审计用) | 空 |
|
|
147
218
|
|
|
148
|
-
|
|
149
|
-
LLM 凭据变量(`DASHSCOPE_API_KEY` / `LLM_MODEL` 等):按上面的 `.env` 加载链任选一层配置即可。
|
|
150
|
-
|
|
151
|
-
### 命令行配置(3rdloop config)
|
|
152
|
-
|
|
153
|
-
`config` 子命令对 `.env` 配置项做命令式管理,默认读写用户级 `~/.3lib/.env`(跨项目全局生效),无需手改 `.env` 文件:
|
|
154
|
-
|
|
155
|
-
```bash
|
|
156
|
-
# 查看当前生效的数据目录
|
|
157
|
-
3rdloop config get 3LIB_DATA_DIR
|
|
158
|
-
|
|
159
|
-
# 将任务产物统一存到团队共享目录(全局生效,无需每次带 --data-dir)
|
|
160
|
-
3rdloop config set 3LIB_DATA_DIR "D:\code\team\shared-db"
|
|
161
|
-
|
|
162
|
-
# 查看全部配置项及当前生效值(密钥类脱敏)
|
|
163
|
-
3rdloop config list
|
|
164
|
-
|
|
165
|
-
# 恢复默认
|
|
166
|
-
3rdloop config unset 3LIB_DATA_DIR
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
- 读取优先级:**系统环境变量 >** `~/.3lib/.env`**(用户级) > 项目根** `Server/.env` **> 内置默认值**
|
|
170
|
-
- `config` 内置白名单与值校验,未知键 `set` 会报错;`DASHSCOPE_API_KEY` / `GIT_TOKEN` 等密钥在 `get`/`list` 中脱敏显示
|
|
171
|
-
- `3LIB_ROOT` / `3LIB_HOME` 为系统环境变量专用,`config set` 会拒绝,请用 `setx` / `export`
|
|
172
|
-
- `--data-dir` / `--skill-dir` 命令行 flag 仍是最高的单次覆盖
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
### 环境加载细节
|
|
177
|
-
|
|
178
|
-
- `.env` **加载链**:系统环境变量 > `~/.3lib/.env` > 项目根 `Server/.env`
|
|
179
|
-
- **opencode**:自动拉起 `opencode serve`(项目上一级目录),退出时清理进程树;已存在则复用
|
|
180
|
-
- **任务数据隔离**:CLI 任务数据默认与 Web 前端(读取仓库根 `db/`)分离,CLI 提交的任务不会出现在前端任务列表
|
|
181
|
-
|
|
182
|
-
|
|
219
|
+
LLM 凭据变量(`DASHSCOPE_API_KEY` / `LLM_MODEL` 等):按上文场景二所述,系统环境变量 / `~/.3lib/.env` / 项目根 `Server/.env` 任选一层配置即可。
|
|
183
220
|
|
|
184
221
|
## API
|
|
185
222
|
|
|
@@ -238,7 +275,7 @@ LLM 凭据变量(`DASHSCOPE_API_KEY` / `LLM_MODEL` 等):按上面的 `.env
|
|
|
238
275
|
|
|
239
276
|
## 维护者
|
|
240
277
|
|
|
241
|
-
[@junxiaoliu927](https://gitcode.com/Sincerelyurs
|
|
278
|
+
[@lalhan](https://gitcode.com/lalhanorz) [@junxiaoliu927](https://gitcode.com/Sincerelyurs)
|
|
242
279
|
|
|
243
280
|
## 许可证
|
|
244
281
|
|
package/lib/cli.js
CHANGED
|
@@ -144,9 +144,15 @@ export async function runCli(argv) {
|
|
|
144
144
|
case 'config':
|
|
145
145
|
return cmdConfig({ flags, rest, projectRoot, user3libHome, jsonMode, out });
|
|
146
146
|
|
|
147
|
+
case 'serve':
|
|
148
|
+
return cmdServe({ flags, rest, jsonMode });
|
|
149
|
+
|
|
147
150
|
case 'update':
|
|
148
151
|
return cmdUpdate({ flags, rest, jsonMode, out });
|
|
149
152
|
|
|
153
|
+
case 'web':
|
|
154
|
+
return cmdWeb({ flags, rest, user3libHome, dataDir, jsonMode, out });
|
|
155
|
+
|
|
150
156
|
default:
|
|
151
157
|
process.stderr.write(`未知命令: ${cmd}\n`);
|
|
152
158
|
printHelp();
|
|
@@ -218,6 +224,13 @@ async function cmdUpdate({ flags, rest, jsonMode, out }) {
|
|
|
218
224
|
return result.ok ? EXIT.OK : EXIT.ERROR;
|
|
219
225
|
}
|
|
220
226
|
|
|
227
|
+
// ─── serve(启动 Server HTTP 后端,供前端展示)─────────────────────────
|
|
228
|
+
|
|
229
|
+
async function cmdServe({ flags, rest, jsonMode }) {
|
|
230
|
+
const { cmdServe: impl } = await import('./serve.js');
|
|
231
|
+
return impl({ rest: flags.rest, jsonMode });
|
|
232
|
+
}
|
|
233
|
+
|
|
221
234
|
// ─── run / submit ──────────────────────────────────────────────────
|
|
222
235
|
|
|
223
236
|
async function cmdRun({ flags, rest, projectRoot, dataDir, skillDir, jsonMode, quiet, verbose, out, u }) {
|
|
@@ -610,6 +623,14 @@ async function cmdOrch({ flags, rest, projectRoot, dataDir, skillDir, jsonMode,
|
|
|
610
623
|
return impl({ flags, rest, projectRoot, dataDir, skillDir, jsonMode, quiet, verbose, out, u });
|
|
611
624
|
}
|
|
612
625
|
|
|
626
|
+
/**
|
|
627
|
+
* 3rdloop web 命令二段分发(转发到 lib/web.js:前端服务 + 扩展挂载)。
|
|
628
|
+
*/
|
|
629
|
+
async function cmdWeb({ flags, rest, user3libHome, dataDir, jsonMode, out }) {
|
|
630
|
+
const { cmdWeb: impl } = await import('./web.js');
|
|
631
|
+
return impl({ flags, rest, user3libHome, dataDir, jsonMode, out });
|
|
632
|
+
}
|
|
633
|
+
|
|
613
634
|
// ─── 帮助 ─────────────────────────────────────────────────────────
|
|
614
635
|
|
|
615
636
|
function printHelp() {
|
|
@@ -638,7 +659,11 @@ function printHelp() {
|
|
|
638
659
|
3rdloop workflows 列出已注册的固定编排工作流
|
|
639
660
|
3rdloop run <工作流名> [--flag <值> ...] 以固定编排执行工作流(阻塞到终态)
|
|
640
661
|
3rdloop config list|get|set|unset ... 配置 .env 配置项(默认读写 ~/.3lib/.env)
|
|
662
|
+
3rdloop serve [--port N] [-w|--web] [--web-port N] 启动 Server HTTP 后端(供前端展示)
|
|
641
663
|
3rdloop update [--check] [--yes] [--force] [--registry <url>] 自更新到 npm 最新版
|
|
664
|
+
3rdloop web [--port N] [--backend <url>] 启动 Web 前端服务
|
|
665
|
+
3rdloop web install-ext <dir> 安装扩展(tag/issue/prcheck 等二级功能)
|
|
666
|
+
3rdloop web list-ext | remove-ext <name> 扩展管理
|
|
642
667
|
3rdloop doctor 环境自检
|
|
643
668
|
3rdloop version 版本信息
|
|
644
669
|
|
package/lib/config-cmd.js
CHANGED
|
@@ -73,7 +73,7 @@ export const ENV_KEYS = [
|
|
|
73
73
|
// ── LLM(百炼 / DashScope)───────────────────────────────────────
|
|
74
74
|
{ key: 'DASHSCOPE_API_KEY', label: '百炼 API Key', description: '从 https://bailian.console.aliyun.com/ 获取', default: '', validate: validators.nonEmpty, secret: true },
|
|
75
75
|
{ key: 'LLM_BASE_URL', label: 'LLM 端点', description: '百炼 OpenAI 兼容端点(不要随意修改)', default: 'https://dashscope.aliyuncs.com/compatible-mode/v1', validate: validators.urlOrEmpty },
|
|
76
|
-
{ key: 'LLM_MODEL', label: '模型名', description: '经百炼调用的模型名(如 glm-5
|
|
76
|
+
{ key: 'LLM_MODEL', label: '模型名', description: '经百炼调用的模型名(如 glm-5.2)', default: 'glm-5.2', validate: validators.nonEmpty },
|
|
77
77
|
{ key: 'LLM_TEMPERATURE', label: 'LLM 采样温度', description: '采样温度(可选)', default: '0.1', validate: validators.temperature },
|
|
78
78
|
{ key: 'LLM_MAX_TOKENS', label: 'LLM 最大输出 tokens', description: '单次生成上限(可选)', default: '2048', validate: validators.intPositive },
|
|
79
79
|
{ key: 'LLM_ENABLE_THINKING', label: 'GLM 思考模式', description: 'true=开启思考(慢,质量高)false=关闭(快,推荐)', default: 'false', validate: validators.bool },
|
package/lib/serve.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* serve.js —— 3rdloop serve 启动 Server HTTP 后端
|
|
3
|
+
*
|
|
4
|
+
* 定位:为前端展示启动现成 Server 后端(复用 Server/Routes/server.js 的
|
|
5
|
+
* createServer,不新增任何 daemon/调度逻辑)。CLI 其余命令仍为嵌入式,不受影响。
|
|
6
|
+
*
|
|
7
|
+
* 行为:
|
|
8
|
+
* - 默认仅启动 HTTP 后端(--port / 3LIB_SERVER_PORT / 3000)
|
|
9
|
+
* - -w/--web 可选:再拉起 Web/server.js 静态前端(反向代理 /api/* 到后端)
|
|
10
|
+
* - 复用 CLI 的环境装配(cli.js 已统一执行 .env 链 + stdout 保护),
|
|
11
|
+
* 与 run/submit 等命令保持一致;json 模式下本模块自行纯化 stdout
|
|
12
|
+
* - 端口冲突 / 启动失败给出明确错误,不静默
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn, execSync } from 'node:child_process';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import process from 'node:process';
|
|
20
|
+
|
|
21
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
export const CLI_ROOT = path.resolve(moduleDir, '..');
|
|
23
|
+
|
|
24
|
+
// ─── vendor/Server 路径解析(与 runner.js 保持一致)────────────────
|
|
25
|
+
let _serverModuleRoot = null;
|
|
26
|
+
function resolveVendorServer(cliRoot) {
|
|
27
|
+
if (_serverModuleRoot) return _serverModuleRoot;
|
|
28
|
+
|
|
29
|
+
const vendorServer = path.join(cliRoot, 'vendor', 'Server');
|
|
30
|
+
if (fs.existsSync(vendorServer)) {
|
|
31
|
+
_serverModuleRoot = vendorServer;
|
|
32
|
+
return vendorServer;
|
|
33
|
+
}
|
|
34
|
+
const repoServer = path.resolve(cliRoot, '..', 'Server');
|
|
35
|
+
if (fs.existsSync(repoServer)) {
|
|
36
|
+
_serverModuleRoot = repoServer;
|
|
37
|
+
return repoServer;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(
|
|
40
|
+
`无法定位核心引擎目录:期望 cli/vendor/Server 或仓库 Server/。` +
|
|
41
|
+
`请先运行 npm run prepack(或 clone 仓库到 cli/ 的上一级)`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 动态导入 Server/Routes/server.js(一手段:vendor 优先、开发期回退仓库源码)。 */
|
|
46
|
+
async function _importServerModule() {
|
|
47
|
+
const serverRoot = resolveVendorServer(CLI_ROOT);
|
|
48
|
+
const url = new URL(`file://${path.join(serverRoot, 'Routes', 'server.js').replace(/\\/g, '/')}`);
|
|
49
|
+
return import(url.href);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Web 静态前端目录(开发期仓库 Web/;发布期 vendor/Web 预留)。 */
|
|
53
|
+
function resolveWebDir() {
|
|
54
|
+
const candidates = [
|
|
55
|
+
path.resolve(CLI_ROOT, '..', 'Web'),
|
|
56
|
+
path.join(CLI_ROOT, 'vendor', 'Web'),
|
|
57
|
+
];
|
|
58
|
+
for (const dir of candidates) {
|
|
59
|
+
if (fs.existsSync(path.join(dir, 'server.js')) && fs.existsSync(path.join(dir, 'public'))) {
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 解析 flags.rest 中 serve 专用参数(不依赖 cli.js 的 parseGlobalFlags,
|
|
68
|
+
* 因为 -w/--web 会被它归入 rest)。
|
|
69
|
+
* @returns {{ web: boolean, port?: number, webPort?: number }}
|
|
70
|
+
*/
|
|
71
|
+
function _parseServeArgs(rest = []) {
|
|
72
|
+
const args = { web: false };
|
|
73
|
+
for (let i = 0; i < rest.length; i++) {
|
|
74
|
+
const arg = rest[i];
|
|
75
|
+
if (arg === '-w' || arg === '--web') args.web = true;
|
|
76
|
+
else if (arg === '--port' || arg === '--web-port') {
|
|
77
|
+
const key = arg === '--port' ? 'port' : 'webPort';
|
|
78
|
+
const n = parseInt(rest[i + 1], 10);
|
|
79
|
+
if (Number.isFinite(n) && n > 0) args[key] = n;
|
|
80
|
+
i++; // 跳过值
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return args;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 3rdloop serve 命令实现。
|
|
88
|
+
* @param {object} opts
|
|
89
|
+
* @param {string[]} opts.rest - flags.rest(含 serve 专用参数)
|
|
90
|
+
* @param {boolean} opts.jsonMode
|
|
91
|
+
* @returns {Promise<number>} 退出码(serve 为长驻命令,仅 --help/启动失败正常返回)
|
|
92
|
+
*/
|
|
93
|
+
export async function cmdServe({ rest = [], jsonMode = false }) {
|
|
94
|
+
// --help / -h:打印用法
|
|
95
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
96
|
+
_printServeHelp();
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const args = _parseServeArgs(rest);
|
|
101
|
+
const serverPort = args.port ?? _envInt('3LIB_SERVER_PORT', 3000);
|
|
102
|
+
const webPort = args.webPort ?? _envInt('3LIB_WEB_PORT', 8080);
|
|
103
|
+
|
|
104
|
+
if (args.web && serverPort === webPort) {
|
|
105
|
+
process.stderr.write(`3rdloop serve: 后端端口与 Web 端口相同 (${serverPort}),请用 --web-port 指定不同端口\n`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const serverModule = await _importServerModule();
|
|
110
|
+
|
|
111
|
+
// json 模式下纯化 stdout:Server 内部 console.log/info 重定向到 stderr
|
|
112
|
+
let restoreStdout = null;
|
|
113
|
+
if (jsonMode) {
|
|
114
|
+
restoreStdout = _redirectStdoutLogs();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let webChild = null;
|
|
118
|
+
|
|
119
|
+
// 优雅退出:清理 Web 子进程(Windows taskkill 树)后退出
|
|
120
|
+
const cleanup = () => {
|
|
121
|
+
if (webChild && webChild.pid != null) {
|
|
122
|
+
try {
|
|
123
|
+
if (process.platform === 'win32') {
|
|
124
|
+
execSync(`taskkill /pid ${webChild.pid} /T /F`, { stdio: 'ignore' });
|
|
125
|
+
} else {
|
|
126
|
+
webChild.kill('SIGTERM');
|
|
127
|
+
}
|
|
128
|
+
} catch { /* 进程可能已退出 */ }
|
|
129
|
+
webChild = null;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
let shuttingDown = false;
|
|
133
|
+
const onSignal = (sig) => {
|
|
134
|
+
if (shuttingDown) return;
|
|
135
|
+
shuttingDown = true;
|
|
136
|
+
cleanup();
|
|
137
|
+
process.exit(sig === 'SIGINT' ? 130 : 143);
|
|
138
|
+
};
|
|
139
|
+
process.once('SIGINT', () => onSignal('SIGINT'));
|
|
140
|
+
process.once('SIGTERM', () => onSignal('SIGTERM'));
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// cliType 由环境决定(shell > .env > 默认 opencode),与 CLI 嵌入式一致;
|
|
144
|
+
// dataDir/skillDir 沿用 Server 默认(workspace 根 db/),本期不与 CLI 打通
|
|
145
|
+
const server = await serverModule.createServer({ port: serverPort });
|
|
146
|
+
|
|
147
|
+
if (jsonMode) {
|
|
148
|
+
process.stdout.write(JSON.stringify({
|
|
149
|
+
ok: true,
|
|
150
|
+
type: 'server',
|
|
151
|
+
pid: process.pid,
|
|
152
|
+
port: serverPort,
|
|
153
|
+
url: `http://127.0.0.1:${serverPort}`,
|
|
154
|
+
web: args.web ? { port: webPort, url: `http://localhost:${webPort}` } : null,
|
|
155
|
+
}, null, 2) + '\n');
|
|
156
|
+
} else {
|
|
157
|
+
process.stderr.write(`[3rdloop serve] Server HTTP 后端已启动: http://127.0.0.1:${serverPort}\n`);
|
|
158
|
+
process.stderr.write(`[3rdloop serve] 通过 Ctrl+C 停止\n`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── 可选启动 Web 前端(默认不拉起,保持聚焦后端) ──
|
|
162
|
+
if (args.web) {
|
|
163
|
+
await _startWebFrontend(webPort, serverPort, jsonMode, (child) => { webChild = child; });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 长驻:保持进程存活直到收到信号
|
|
167
|
+
await new Promise(() => {});
|
|
168
|
+
return 0;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
process.stderr.write(`3rdloop serve 启动失败: ${err.message}\n`);
|
|
171
|
+
return 1;
|
|
172
|
+
} finally {
|
|
173
|
+
restoreStdout?.();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** 启动 Web 静态前端(反向代理 /api/* → 后端)。 */
|
|
178
|
+
async function _startWebFrontend(webPort, serverPort, jsonMode, onChild) {
|
|
179
|
+
try {
|
|
180
|
+
if (await _isPortOpen(webPort)) {
|
|
181
|
+
process.stderr.write(`[3rdloop serve] Web 端口 ${webPort} 已被占用,跳过拉起前端(可直接访问该地址)\n`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
} catch { /* 探测失败不阻断 */ }
|
|
185
|
+
|
|
186
|
+
const webDir = resolveWebDir();
|
|
187
|
+
if (!webDir) {
|
|
188
|
+
process.stderr.write('[3rdloop serve] 未找到 Web/ 前端目录,跳过拉起前端\n');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const child = spawn(process.execPath, [
|
|
193
|
+
path.join(webDir, 'server.js'),
|
|
194
|
+
'--port', String(webPort),
|
|
195
|
+
'--backend', `http://127.0.0.1:${serverPort}`,
|
|
196
|
+
], {
|
|
197
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
198
|
+
shell: false,
|
|
199
|
+
detached: false,
|
|
200
|
+
});
|
|
201
|
+
child.stdout?.on('data', (d) => process.stderr.write(`[web] ${d}`));
|
|
202
|
+
child.stderr?.on('data', (d) => process.stderr.write(`[web] ${d}`));
|
|
203
|
+
child.on('error', (err) => {
|
|
204
|
+
process.stderr.write(`[3rdloop serve] 拉起 Web 前端失败: ${err.message}\n`);
|
|
205
|
+
});
|
|
206
|
+
onChild(child);
|
|
207
|
+
if (!jsonMode) {
|
|
208
|
+
process.stderr.write(`[3rdloop serve] Web 前端已启动: http://localhost:${webPort}\n`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** 读取正整数环境变量(非法/缺席返回默认值)。 */
|
|
213
|
+
function _envInt(name, def) {
|
|
214
|
+
const n = parseInt(process.env[name], 10);
|
|
215
|
+
return Number.isFinite(n) && n > 0 ? n : def;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 探测端口是否有响应(尽力而为,不阻断)。 */
|
|
219
|
+
function _isPortOpen(port, timeoutMs = 1500) {
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
const controller = new AbortController();
|
|
222
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
223
|
+
fetch(`http://127.0.0.1:${port}/`, { signal: controller.signal })
|
|
224
|
+
.then(() => resolve(true))
|
|
225
|
+
.catch(() => resolve(false))
|
|
226
|
+
.finally(() => clearTimeout(timer));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** json 模式下把 Server 内部 console.log/info 重定向到 stderr,返回恢复函数。 */
|
|
231
|
+
function _redirectStdoutLogs() {
|
|
232
|
+
const safeStringify = (a) => {
|
|
233
|
+
try { return JSON.stringify(a); } catch { return String(a); }
|
|
234
|
+
};
|
|
235
|
+
const toStderr = (args) => {
|
|
236
|
+
process.stderr.write(args.map((a) => (typeof a === 'string' ? a : safeStringify(a))).join(' ') + '\n');
|
|
237
|
+
};
|
|
238
|
+
const origLog = console.log;
|
|
239
|
+
const origInfo = console.info;
|
|
240
|
+
console.log = (...args) => toStderr(args);
|
|
241
|
+
console.info = (...args) => toStderr(args);
|
|
242
|
+
return () => {
|
|
243
|
+
console.log = origLog;
|
|
244
|
+
console.info = origInfo;
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _printServeHelp() {
|
|
249
|
+
process.stdout.write(`3rdloop serve —— 启动 Server HTTP 后端(供前端展示)
|
|
250
|
+
|
|
251
|
+
用法:
|
|
252
|
+
3rdloop serve [--port N] [-w|--web] [--web-port N]
|
|
253
|
+
|
|
254
|
+
选项:
|
|
255
|
+
--port N 后端端口(默认 3000,可用 3LIB_SERVER_PORT 覆盖)
|
|
256
|
+
-w, --web 同时拉起 Web 前端(默认 8080,可用 --web-port / 3LIB_WEB_PORT 覆盖)
|
|
257
|
+
--web-port N Web 前端端口
|
|
258
|
+
--json 输出结构化启动信息到 stdout(进度/日志走 stderr)
|
|
259
|
+
-h, --help 本帮助
|
|
260
|
+
|
|
261
|
+
说明:
|
|
262
|
+
默认只启动 Server HTTP 后端(http://127.0.0.1:3000),供 Web 前端 / 脚本反向代理调用。
|
|
263
|
+
CLI 的 run/submit/wait 等命令保持嵌入式模式,不经此后端。
|
|
264
|
+
退出: Ctrl+C / SIGTERM 优雅停止(Windows 下会清理 Web 子进程树)。
|
|
265
|
+
`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export default cmdServe;
|