firmwareloop 0.7.4__tar.gz → 0.8.0__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.
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/PKG-INFO +6 -3
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/README.md +5 -2
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/firmwareloop.egg-info/PKG-INFO +6 -3
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/pyproject.toml +1 -1
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/__init__.py +1 -1
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/fw_mcp_server.py +281 -13
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/setup-agent-mcp.ps1 +2 -2
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/LICENSE +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/firmwareloop.egg-info/SOURCES.txt +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/firmwareloop.egg-info/dependency_links.txt +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/firmwareloop.egg-info/entry_points.txt +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/firmwareloop.egg-info/requires.txt +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/firmwareloop.egg-info/top_level.txt +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/setup.cfg +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/acceptance-scenario.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/build.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/can.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/check-qoder-mcp.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/common/build-backends.psm1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/common/fw.psm1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/common/uart_probe.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/doctor.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/flash.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/instrument_cli.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/lib/__init__.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/lib/instruments.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/lib/ndtdbg.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/lib/serial_assistant.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/logic_capture.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/logic_decode.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/ndtdbg_bridge.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/ndtdbg_cli.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/reset.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/serial_cli.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/sigrok_csv_to_fwloop.py +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/test-backends.ps1 +0 -0
- {firmwareloop-0.7.4 → firmwareloop-0.8.0}/tools/test.ps1 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
|
|
5
5
|
Author-email: WeilinZhang <19523330249@163.com>
|
|
6
6
|
License: MIT
|
|
@@ -177,6 +177,8 @@ fwloop setup
|
|
|
177
177
|
| Claude Code CLI | `~/.claude.json` |
|
|
178
178
|
| Qoder / Qoder CN | `~/.qoder/mcp.json`、`~/.qoder-cn/mcp.json` |
|
|
179
179
|
| CodeBuddy IDE | `~/.codebuddy/mcp.json` |
|
|
180
|
+
| DeepSeek Harness (DSH) | `~/.dsh/profiles/<已派生 profile>/cordis.patch.yml` |
|
|
181
|
+
| ZCode IDE | `~/.zcode/cli/config.json` |
|
|
180
182
|
|
|
181
183
|
2. **同步技能包**(`firmwareloop`、`fwloop-adapter`)到 Antigravity 与 Claude Code 的全局 Skill 目录。
|
|
182
184
|
3. **生成机器级全局配置** `%APPDATA%\FirmwareLoop\lab.yaml`(NdtBox 配置模板,已存在则不覆盖)。
|
|
@@ -383,7 +385,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
383
385
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
384
386
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
385
387
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
386
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
388
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.8.0)
|
|
387
389
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
388
390
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
389
391
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -393,7 +395,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
393
395
|
|
|
394
396
|
---
|
|
395
397
|
|
|
396
|
-
## 能力验证状态(v0.
|
|
398
|
+
## 能力验证状态(v0.8.0)
|
|
397
399
|
|
|
398
400
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
399
401
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -401,6 +403,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
401
403
|
| 能力 | 状态 |
|
|
402
404
|
|---|---|
|
|
403
405
|
| 双层 MCP 服务(13 个工作流与硬件工具) | Implemented + Protocol Validated(31/31 测试通过) |
|
|
406
|
+
| fwloop setup 一键注册(7 Agent:Antigravity / Claude Code / Qoder(+CN) / CodeBuddy / DeepSeek Harness / ZCode) | Implemented + Multi-Agent Validated(DSH / ZCode 真机写入验证) |
|
|
404
407
|
| 串口助手工具族(fw_serial_* 七工具, 长驻会话/发送序列/帧解析/窗口采集) | Implemented + Simulator Validated(55 例单测: 含残留 serve 自动清理 / close 进程回收 / UTF-8 线路 / 失败落 JSONL)-> Real Hardware Validated 待 DUT |
|
|
405
408
|
| 一键多 Agent 脚手架(fwloop init / fw_init_project) | Implemented + Multi-Agent Validated |
|
|
406
409
|
| 自定义/私有调试工具接入 Skill(fwloop-adapter) | Implemented + Multi-Skill Validated |
|
|
@@ -149,6 +149,8 @@ fwloop setup
|
|
|
149
149
|
| Claude Code CLI | `~/.claude.json` |
|
|
150
150
|
| Qoder / Qoder CN | `~/.qoder/mcp.json`、`~/.qoder-cn/mcp.json` |
|
|
151
151
|
| CodeBuddy IDE | `~/.codebuddy/mcp.json` |
|
|
152
|
+
| DeepSeek Harness (DSH) | `~/.dsh/profiles/<已派生 profile>/cordis.patch.yml` |
|
|
153
|
+
| ZCode IDE | `~/.zcode/cli/config.json` |
|
|
152
154
|
|
|
153
155
|
2. **同步技能包**(`firmwareloop`、`fwloop-adapter`)到 Antigravity 与 Claude Code 的全局 Skill 目录。
|
|
154
156
|
3. **生成机器级全局配置** `%APPDATA%\FirmwareLoop\lab.yaml`(NdtBox 配置模板,已存在则不覆盖)。
|
|
@@ -355,7 +357,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
355
357
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
356
358
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
357
359
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
358
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
360
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.8.0)
|
|
359
361
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
360
362
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
361
363
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -365,7 +367,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
365
367
|
|
|
366
368
|
---
|
|
367
369
|
|
|
368
|
-
## 能力验证状态(v0.
|
|
370
|
+
## 能力验证状态(v0.8.0)
|
|
369
371
|
|
|
370
372
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
371
373
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -373,6 +375,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
373
375
|
| 能力 | 状态 |
|
|
374
376
|
|---|---|
|
|
375
377
|
| 双层 MCP 服务(13 个工作流与硬件工具) | Implemented + Protocol Validated(31/31 测试通过) |
|
|
378
|
+
| fwloop setup 一键注册(7 Agent:Antigravity / Claude Code / Qoder(+CN) / CodeBuddy / DeepSeek Harness / ZCode) | Implemented + Multi-Agent Validated(DSH / ZCode 真机写入验证) |
|
|
376
379
|
| 串口助手工具族(fw_serial_* 七工具, 长驻会话/发送序列/帧解析/窗口采集) | Implemented + Simulator Validated(55 例单测: 含残留 serve 自动清理 / close 进程回收 / UTF-8 线路 / 失败落 JSONL)-> Real Hardware Validated 待 DUT |
|
|
377
380
|
| 一键多 Agent 脚手架(fwloop init / fw_init_project) | Implemented + Multi-Agent Validated |
|
|
378
381
|
| 自定义/私有调试工具接入 Skill(fwloop-adapter) | Implemented + Multi-Skill Validated |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)
|
|
5
5
|
Author-email: WeilinZhang <19523330249@163.com>
|
|
6
6
|
License: MIT
|
|
@@ -177,6 +177,8 @@ fwloop setup
|
|
|
177
177
|
| Claude Code CLI | `~/.claude.json` |
|
|
178
178
|
| Qoder / Qoder CN | `~/.qoder/mcp.json`、`~/.qoder-cn/mcp.json` |
|
|
179
179
|
| CodeBuddy IDE | `~/.codebuddy/mcp.json` |
|
|
180
|
+
| DeepSeek Harness (DSH) | `~/.dsh/profiles/<已派生 profile>/cordis.patch.yml` |
|
|
181
|
+
| ZCode IDE | `~/.zcode/cli/config.json` |
|
|
180
182
|
|
|
181
183
|
2. **同步技能包**(`firmwareloop`、`fwloop-adapter`)到 Antigravity 与 Claude Code 的全局 Skill 目录。
|
|
182
184
|
3. **生成机器级全局配置** `%APPDATA%\FirmwareLoop\lab.yaml`(NdtBox 配置模板,已存在则不覆盖)。
|
|
@@ -383,7 +385,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
383
385
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
384
386
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
385
387
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
386
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.
|
|
388
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.8.0)
|
|
387
389
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
388
390
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
389
391
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -393,7 +395,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
393
395
|
|
|
394
396
|
---
|
|
395
397
|
|
|
396
|
-
## 能力验证状态(v0.
|
|
398
|
+
## 能力验证状态(v0.8.0)
|
|
397
399
|
|
|
398
400
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
399
401
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -401,6 +403,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
401
403
|
| 能力 | 状态 |
|
|
402
404
|
|---|---|
|
|
403
405
|
| 双层 MCP 服务(13 个工作流与硬件工具) | Implemented + Protocol Validated(31/31 测试通过) |
|
|
406
|
+
| fwloop setup 一键注册(7 Agent:Antigravity / Claude Code / Qoder(+CN) / CodeBuddy / DeepSeek Harness / ZCode) | Implemented + Multi-Agent Validated(DSH / ZCode 真机写入验证) |
|
|
404
407
|
| 串口助手工具族(fw_serial_* 七工具, 长驻会话/发送序列/帧解析/窗口采集) | Implemented + Simulator Validated(55 例单测: 含残留 serve 自动清理 / close 进程回收 / UTF-8 线路 / 失败落 JSONL)-> Real Hardware Validated 待 DUT |
|
|
405
408
|
| 一键多 Agent 脚手架(fwloop init / fw_init_project) | Implemented + Multi-Agent Validated |
|
|
406
409
|
| 自定义/私有调试工具接入 Skill(fwloop-adapter) | Implemented + Multi-Skill Validated |
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.
|
|
3
|
+
fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.8.0).
|
|
4
4
|
|
|
5
5
|
Exposes high-level firmware engineering and hardware management tools to AI Coding
|
|
6
|
-
Agents (Antigravity CLI, Claude Code CLI, Qoder IDE, CodeBuddy IDE,
|
|
6
|
+
Agents (Antigravity CLI, Claude Code CLI, Qoder IDE, CodeBuddy IDE, DeepSeek Harness,
|
|
7
|
+
ZCode IDE, Cursor) via the Model Context
|
|
7
8
|
Protocol (JSON-RPC 2.0 stdio).
|
|
8
9
|
|
|
9
10
|
Tools provided:
|
|
@@ -1156,6 +1157,57 @@ custom_tools:
|
|
|
1156
1157
|
向用户展示配置位置,并说明后续在对话中可以直接使用自然语言调度:
|
|
1157
1158
|
- “*使用我们公司的烧录工具把固件刷入芯片*”
|
|
1158
1159
|
- “*启动自研抓包工具采集 5 秒串口日志*”
|
|
1160
|
+
|
|
1161
|
+
---
|
|
1162
|
+
|
|
1163
|
+
## 可选手法:目标侧"只读诊断插桩"(通用模式)
|
|
1164
|
+
|
|
1165
|
+
**适用**:目标固件"挂死 / 反复重启",没有可用串口输出(或写通道不可用),但工具族能读寄存器。
|
|
1166
|
+
思路:让固件把**运行痕迹暴露成只读寄存器**,Agent 一次读回即可定位阶段与崩溃现场,
|
|
1167
|
+
不必"改一处 → 烧一次 → 猜一次"。
|
|
1168
|
+
|
|
1169
|
+
> **⚠️ 归属规则(先看这条)**:本 skill 会**随包分发**(`fwloop init` / `fwloop setup` 会把它
|
|
1170
|
+
> 复制到用户工程与各 Agent 的 skills 目录),因此**这里只放与具体项目、具体芯片无关的通用内容**。
|
|
1171
|
+
> 每个项目的**地址约定、寄存器含义、现场案例属于项目资产**,请记录到:
|
|
1172
|
+
> - 该项目仓库内的 `docs/<project>-probe-convention.md`,或该项目自建的 project skill;
|
|
1173
|
+
>
|
|
1174
|
+
> 本 skill 负责教你**怎么设计**这套插桩、以及**记录到哪里**,不承载任何单一项目的约定。
|
|
1175
|
+
|
|
1176
|
+
### 通用模式(由粗到细四件套)
|
|
1177
|
+
|
|
1178
|
+
1. **阶段编号**:最后到达的启动边界;
|
|
1179
|
+
2. **阶段追踪环**:最近 N 步的环形缓冲(看清"路径",而不只是终点);
|
|
1180
|
+
3. **计数器组**:主循环迭代数、可疑等待循环的自旋计数、崩溃现场(fault 状态字 / PC / LR / xPSR);
|
|
1181
|
+
4. **运行时可调"停靠级别"**:写后断电即可逐级二分崩溃点。
|
|
1182
|
+
|
|
1183
|
+
### 通用约束(与芯片无关,都是踩出来的)
|
|
1184
|
+
|
|
1185
|
+
1. 插桩数组放 **NOINIT** 段:复位/软复位不清零,才看得到"重启前最后执行到哪";
|
|
1186
|
+
2. 尽量**只读注册**,不要占用可写寄存器语义;
|
|
1187
|
+
3. 注册表必须按**地址升序**:乱序会导致从机自检失败 / 初始化提前 `return`
|
|
1188
|
+
⇒ 通信通道根本没起来,现象与"目标掉电"完全一样(极易误判);
|
|
1189
|
+
4. **避开厂商协议占用的地址**:例——烧录工具会往某个固定地址写擦除/完成魔数,
|
|
1190
|
+
插桩变量放在那里会污染工具的判据;
|
|
1191
|
+
5. 采集遵守工具族的协议约束(升序读、单次长度上限、关键字节双读校验——见各工具描述);
|
|
1192
|
+
6. 插桩代码用 `#if` 开关包裹:关闭时零成本、零 RAM。
|
|
1193
|
+
|
|
1194
|
+
### 固件侧两条"已知良方"(与芯片无关)
|
|
1195
|
+
|
|
1196
|
+
1. **"通道先行"**:把"能被工具读到的通道"(I2C 从机 / 串口)初始化提到所有可疑初始化**之前**
|
|
1197
|
+
⇒ 之后无论哪里崩,设备仍然可读(把它写在最后,往往会被插桩本身锁死);
|
|
1198
|
+
2. **fault handler 禁止裸自旋**(`B .`):其优先级高于一切可配置中断,死等会饿死通信中断,
|
|
1199
|
+
目标"从总线上消失"(工具侧表现为 `BUS_TIMEOUT`)⇒ 应"记录现场 + 复位"。
|
|
1200
|
+
(进阶手法"改写异常返回地址 → 恢复函数 → 重建通道 → 停靠"与芯片强相关,换目标需复测。)
|
|
1201
|
+
|
|
1202
|
+
### 最小闭环(通用)
|
|
1203
|
+
|
|
1204
|
+
```text
|
|
1205
|
+
fw_ndtdbg_power_cycle(off_ms=500) # 复位(不需 write_allowed)
|
|
1206
|
+
fw_ndtdbg_status # 分级结论 + 判据表
|
|
1207
|
+
fw_ndtdbg_read_reg(reg=..., len=...) # 读"项目约定文档"里定义的诊断寄存器
|
|
1208
|
+
fw_ndtdbg_flash(...) # 改完固件自助烧录(含烧后冷启动冒烟)
|
|
1209
|
+
fw_serial_capture(power_cycle_off_ms=500) # 抓开机日志(窗口内自动复位)
|
|
1210
|
+
```
|
|
1159
1211
|
"""
|
|
1160
1212
|
if not os.path.exists(adapter_skill_path) or overwrite:
|
|
1161
1213
|
with open(adapter_skill_path, "w", encoding="utf-8") as fh:
|
|
@@ -2335,7 +2387,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
2335
2387
|
},
|
|
2336
2388
|
"serverInfo": {
|
|
2337
2389
|
"name": "firmwareloop",
|
|
2338
|
-
"version": "0.
|
|
2390
|
+
"version": "0.8.0"
|
|
2339
2391
|
}
|
|
2340
2392
|
}
|
|
2341
2393
|
}
|
|
@@ -2470,7 +2522,7 @@ def _settle_stashed_entrypoints(stashed) -> None:
|
|
|
2470
2522
|
def handle_cli_update() -> int:
|
|
2471
2523
|
"""Handle `firmwareloop update` command."""
|
|
2472
2524
|
print("============================================================")
|
|
2473
|
-
print(" FirmwareLoop Auto-Updater (v0.
|
|
2525
|
+
print(" FirmwareLoop Auto-Updater (v0.8.0)")
|
|
2474
2526
|
print("============================================================")
|
|
2475
2527
|
|
|
2476
2528
|
is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
|
|
@@ -2813,6 +2865,192 @@ def configure_codebuddy_user_mcp(user_home: str) -> Dict[str, Any]:
|
|
|
2813
2865
|
return {"ok": False, "path": mcp_file, "reason": str(exc)}
|
|
2814
2866
|
|
|
2815
2867
|
|
|
2868
|
+
DSH_MCP_CLIENT_PLUGIN = "@deepseek-ai/dsh-mcp-client"
|
|
2869
|
+
|
|
2870
|
+
# DSH (DeepSeek Harness) profile patch entries. Tools surface as
|
|
2871
|
+
# mcp__<serverName>__<tool> (same shape as Claude Code / Codex), e.g.
|
|
2872
|
+
# mcp__fwloop__fw_build. Bare command names resolve via PATH: the DSH
|
|
2873
|
+
# stdio transport only scrubs credential-shaped / DSH_* env vars, so the
|
|
2874
|
+
# parent PATH survives (same convention as the Qoder / Claude entries).
|
|
2875
|
+
DSH_MCP_SERVER_ENTRIES = (
|
|
2876
|
+
("mcp-fwloop", "fwloop", {"command": "fwloop", "args": []}),
|
|
2877
|
+
("mcp-agentic-hil", "agentic-hil", {"command": "agentic-hil", "args": ["mcp-stdio"]}),
|
|
2878
|
+
)
|
|
2879
|
+
|
|
2880
|
+
DSH_PATCH_HEADER = (
|
|
2881
|
+
"# FirmwareLoop dual-tier MCP (added by fwloop setup).\n"
|
|
2882
|
+
"# Bridged by @deepseek-ai/dsh-mcp-client; tools surface as mcp__<serverName>__<tool>.\n"
|
|
2883
|
+
)
|
|
2884
|
+
|
|
2885
|
+
|
|
2886
|
+
def resolve_dsh_home(user_home: str) -> str:
|
|
2887
|
+
"""Resolve the DeepSeek Harness home: non-blank $DSH_HOME wins, else ~/.dsh.
|
|
2888
|
+
|
|
2889
|
+
Mirrors @deepseek-ai/dsh-home-paths precedence (an in-harness configured
|
|
2890
|
+
home override is invisible to external tools and therefore not honored).
|
|
2891
|
+
"""
|
|
2892
|
+
env_home = (os.environ.get("DSH_HOME") or "").strip()
|
|
2893
|
+
return env_home if env_home else os.path.join(user_home, ".dsh")
|
|
2894
|
+
|
|
2895
|
+
|
|
2896
|
+
def discover_dsh_profiles(dsh_home: str) -> List[str]:
|
|
2897
|
+
"""List derived DSH profile names under <home>/profiles.
|
|
2898
|
+
|
|
2899
|
+
A directory counts as a profile only when it has a profile-root cordis.yml,
|
|
2900
|
+
which excludes the shared <home>/profiles/node_modules dependency tree.
|
|
2901
|
+
Built-in profiles (web/tui/headless) appear here once the user has booted
|
|
2902
|
+
or derived them; never-derived ones do not exist on disk and are skipped.
|
|
2903
|
+
"""
|
|
2904
|
+
profiles_dir = os.path.join(dsh_home, "profiles")
|
|
2905
|
+
found: List[str] = []
|
|
2906
|
+
if os.path.isdir(profiles_dir):
|
|
2907
|
+
for name in sorted(os.listdir(profiles_dir)):
|
|
2908
|
+
cand = os.path.join(profiles_dir, name)
|
|
2909
|
+
if os.path.isdir(cand) and os.path.isfile(os.path.join(cand, "cordis.yml")):
|
|
2910
|
+
found.append(name)
|
|
2911
|
+
return found
|
|
2912
|
+
|
|
2913
|
+
|
|
2914
|
+
def _dsh_entry_yaml(entry_id: str, server_name: str, spec: Dict[str, Any]) -> str:
|
|
2915
|
+
args_repr = ", ".join(f"'{a}'" for a in spec.get("args", []))
|
|
2916
|
+
return (
|
|
2917
|
+
f" - id: {entry_id}\n"
|
|
2918
|
+
f" name: '{DSH_MCP_CLIENT_PLUGIN}'\n"
|
|
2919
|
+
f" config:\n"
|
|
2920
|
+
f" serverName: {server_name}\n"
|
|
2921
|
+
f" transport: stdio\n"
|
|
2922
|
+
f" command: {spec['command']}\n"
|
|
2923
|
+
f" args: [{args_repr}]\n"
|
|
2924
|
+
)
|
|
2925
|
+
|
|
2926
|
+
|
|
2927
|
+
def upsert_dsh_patch_file(patch_path: str) -> str:
|
|
2928
|
+
"""Idempotently add the FirmwareLoop MCP entries to a profile's cordis.patch.yml.
|
|
2929
|
+
|
|
2930
|
+
Returns "written" or "already_configured". Text-level upsert on purpose:
|
|
2931
|
+
a yaml.safe_dump round-trip would strip the user's guide comments, so an
|
|
2932
|
+
existing file is only ever appended to. Membership is decided by a plain
|
|
2933
|
+
substring check on our fixed ids (fail-safe side: a false positive merely
|
|
2934
|
+
skips the write), and entries that already exist are never rewritten so
|
|
2935
|
+
user overrides (e.g. a custom command) survive setup re-runs.
|
|
2936
|
+
"""
|
|
2937
|
+
present = ""
|
|
2938
|
+
if os.path.exists(patch_path):
|
|
2939
|
+
with open(patch_path, "r", encoding="utf-8-sig") as f:
|
|
2940
|
+
present = f.read()
|
|
2941
|
+
missing = [eid for eid, _, _ in DSH_MCP_SERVER_ENTRIES if f"id: {eid}" not in present]
|
|
2942
|
+
if present and not missing:
|
|
2943
|
+
return "already_configured"
|
|
2944
|
+
# Single "- insert:" block holding the missing entries, matching the shape
|
|
2945
|
+
# of the official dsh-mcp-client sample (one block, several entries).
|
|
2946
|
+
new_text = "- insert:\n" + "".join(
|
|
2947
|
+
_dsh_entry_yaml(eid, name, spec)
|
|
2948
|
+
for eid, name, spec in DSH_MCP_SERVER_ENTRIES
|
|
2949
|
+
if eid in missing
|
|
2950
|
+
)
|
|
2951
|
+
parent = os.path.dirname(patch_path)
|
|
2952
|
+
if parent:
|
|
2953
|
+
os.makedirs(parent, exist_ok=True)
|
|
2954
|
+
if not present:
|
|
2955
|
+
with open(patch_path, "w", encoding="utf-8", newline="\n") as f:
|
|
2956
|
+
f.write(DSH_PATCH_HEADER)
|
|
2957
|
+
f.write(new_text)
|
|
2958
|
+
return "written"
|
|
2959
|
+
stripped = present.strip()
|
|
2960
|
+
with open(patch_path, "w", encoding="utf-8", newline="\n") as f:
|
|
2961
|
+
if not stripped or stripped == "[]":
|
|
2962
|
+
# Empty document (blank or literal []): keep leading comment lines,
|
|
2963
|
+
# then start the entries array.
|
|
2964
|
+
comments = "\n".join(
|
|
2965
|
+
line for line in present.splitlines() if line.strip().startswith("#")
|
|
2966
|
+
)
|
|
2967
|
+
if comments:
|
|
2968
|
+
f.write(comments + "\n")
|
|
2969
|
+
f.write(new_text)
|
|
2970
|
+
else:
|
|
2971
|
+
f.write(present)
|
|
2972
|
+
if not present.endswith("\n"):
|
|
2973
|
+
f.write("\n")
|
|
2974
|
+
f.write("\n" + new_text)
|
|
2975
|
+
return "written"
|
|
2976
|
+
|
|
2977
|
+
|
|
2978
|
+
def configure_dsh_profiles(user_home: str) -> List[Dict[str, Any]]:
|
|
2979
|
+
"""Add FirmwareLoop MCP entries to every derived DSH profile's cordis.patch.yml."""
|
|
2980
|
+
dsh_home = resolve_dsh_home(user_home)
|
|
2981
|
+
results: List[Dict[str, Any]] = []
|
|
2982
|
+
for profile in discover_dsh_profiles(dsh_home):
|
|
2983
|
+
patch_path = os.path.join(dsh_home, "profiles", profile, "cordis.patch.yml")
|
|
2984
|
+
try:
|
|
2985
|
+
status = upsert_dsh_patch_file(patch_path)
|
|
2986
|
+
results.append({"profile": profile, "ok": True, "path": patch_path, "status": status})
|
|
2987
|
+
except Exception as exc:
|
|
2988
|
+
results.append({"profile": profile, "ok": False, "path": patch_path, "reason": str(exc)})
|
|
2989
|
+
return results
|
|
2990
|
+
|
|
2991
|
+
|
|
2992
|
+
ZCODE_MCP_SERVERS = {
|
|
2993
|
+
"fwloop": {"command": "fwloop", "type": "stdio"},
|
|
2994
|
+
"agentic-hil": {"command": "agentic-hil", "args": ["mcp-stdio"], "type": "stdio"},
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
|
|
2998
|
+
def upsert_zcode_mcp_json(mcp_path: str, servers: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
2999
|
+
"""Merge MCP servers into ZCode's ~/.zcode/cli/config.json without dropping
|
|
3000
|
+
existing entries.
|
|
3001
|
+
|
|
3002
|
+
ZCode (GLM desktop IDE) nests its MCP registry under "mcp"."servers" —
|
|
3003
|
+
the per-entry shape (type/command/args) matches Qoder's mcpServers, so the
|
|
3004
|
+
same per-entry merge (keep user extras like env) applies.
|
|
3005
|
+
"""
|
|
3006
|
+
payload = dict(servers or ZCODE_MCP_SERVERS)
|
|
3007
|
+
data: Dict[str, Any] = {}
|
|
3008
|
+
if os.path.exists(mcp_path):
|
|
3009
|
+
try:
|
|
3010
|
+
with open(mcp_path, "r", encoding="utf-8-sig") as f:
|
|
3011
|
+
loaded = json.load(f)
|
|
3012
|
+
if isinstance(loaded, dict):
|
|
3013
|
+
data = loaded
|
|
3014
|
+
except Exception:
|
|
3015
|
+
data = {}
|
|
3016
|
+
mcp_root = data.get("mcp")
|
|
3017
|
+
if not isinstance(mcp_root, dict):
|
|
3018
|
+
mcp_root = {}
|
|
3019
|
+
mcp_servers = mcp_root.get("servers")
|
|
3020
|
+
if not isinstance(mcp_servers, dict):
|
|
3021
|
+
mcp_servers = {}
|
|
3022
|
+
for name, entry in payload.items():
|
|
3023
|
+
old = mcp_servers.get(name)
|
|
3024
|
+
if isinstance(old, dict) and isinstance(entry, dict):
|
|
3025
|
+
merged = dict(old)
|
|
3026
|
+
merged.update(entry)
|
|
3027
|
+
mcp_servers[name] = merged
|
|
3028
|
+
else:
|
|
3029
|
+
mcp_servers[name] = entry
|
|
3030
|
+
mcp_root["servers"] = mcp_servers
|
|
3031
|
+
data["mcp"] = mcp_root
|
|
3032
|
+
parent = os.path.dirname(mcp_path)
|
|
3033
|
+
if parent:
|
|
3034
|
+
os.makedirs(parent, exist_ok=True)
|
|
3035
|
+
with open(mcp_path, "w", encoding="utf-8") as f:
|
|
3036
|
+
json.dump(data, f, indent=2)
|
|
3037
|
+
f.write("\n")
|
|
3038
|
+
return data
|
|
3039
|
+
|
|
3040
|
+
|
|
3041
|
+
def configure_zcode_user_mcp(user_home: str) -> Dict[str, Any]:
|
|
3042
|
+
"""Write FirmwareLoop MCP entries into ZCode's ~/.zcode/cli/config.json."""
|
|
3043
|
+
zcode_dir = os.path.join(user_home, ".zcode")
|
|
3044
|
+
mcp_file = os.path.join(zcode_dir, "cli", "config.json")
|
|
3045
|
+
if not os.path.isdir(zcode_dir):
|
|
3046
|
+
return {"ok": False, "path": mcp_file, "reason": "not_installed"}
|
|
3047
|
+
try:
|
|
3048
|
+
upsert_zcode_mcp_json(mcp_file)
|
|
3049
|
+
return {"ok": True, "path": mcp_file}
|
|
3050
|
+
except Exception as exc:
|
|
3051
|
+
return {"ok": False, "path": mcp_file, "reason": str(exc)}
|
|
3052
|
+
|
|
3053
|
+
|
|
2816
3054
|
def resolve_ndtdebugtool_lib(tool_root: str) -> str:
|
|
2817
3055
|
"""Resolve the NdtDebugTool lib directory (must contain USBIOX.DLL)."""
|
|
2818
3056
|
if not tool_root or not os.path.isdir(tool_root):
|
|
@@ -2899,9 +3137,9 @@ def set_ndtdbg_dlldir(dll_path: str) -> str:
|
|
|
2899
3137
|
|
|
2900
3138
|
|
|
2901
3139
|
def handle_cli_setup() -> int:
|
|
2902
|
-
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, and
|
|
3140
|
+
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, Qoder IDE, CodeBuddy IDE, DeepSeek Harness (DSH), and ZCode IDE."""
|
|
2903
3141
|
print("============================================================")
|
|
2904
|
-
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.
|
|
3142
|
+
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.8.0)")
|
|
2905
3143
|
print("============================================================")
|
|
2906
3144
|
print("")
|
|
2907
3145
|
|
|
@@ -2985,7 +3223,37 @@ def handle_cli_setup() -> int:
|
|
|
2985
3223
|
else:
|
|
2986
3224
|
print(f" - User MCP: [Warning] Could not write {cb_result['path']}: {cb_result.get('reason')}")
|
|
2987
3225
|
|
|
2988
|
-
# 5.
|
|
3226
|
+
# 5. DeepSeek Harness (DSH) — per-profile cordis.patch.yml upsert
|
|
3227
|
+
# Only profiles already derived under <home>/profiles (a booted or
|
|
3228
|
+
# --from-default-profile'd profile has a profile-root cordis.yml) are
|
|
3229
|
+
# configured; never-derived built-ins do not exist on disk and are skipped.
|
|
3230
|
+
print("\n[5] DeepSeek Harness (DSH):")
|
|
3231
|
+
dsh_results = configure_dsh_profiles(user_home)
|
|
3232
|
+
dsh_ok = [item for item in dsh_results if item.get("ok")]
|
|
3233
|
+
if dsh_ok:
|
|
3234
|
+
for item in dsh_ok:
|
|
3235
|
+
state = "already configured" if item["status"] == "already_configured" else "written"
|
|
3236
|
+
print(f" - profile {item['profile']}: [OK] {state}: {item['path']}")
|
|
3237
|
+
configured_agents.append("DeepSeek Harness (DSH)")
|
|
3238
|
+
else:
|
|
3239
|
+
print(" - ~/.dsh/profiles has no derived profile (DSH not installed, or no profile booted yet)")
|
|
3240
|
+
print(" - Boot/derive a profile in DSH first, then re-run: fwloop setup")
|
|
3241
|
+
for item in dsh_results:
|
|
3242
|
+
if not item.get("ok"):
|
|
3243
|
+
print(f" - profile {item['profile']}: [Warning] Could not write {item['path']}: {item.get('reason')}")
|
|
3244
|
+
|
|
3245
|
+
# 6. ZCode IDE user-level MCP (~/.zcode/cli/config.json, {"mcp": {"servers": ...}})
|
|
3246
|
+
print("\n[6] ZCode IDE:")
|
|
3247
|
+
zc_result = configure_zcode_user_mcp(user_home)
|
|
3248
|
+
if zc_result.get("ok"):
|
|
3249
|
+
print(f" - User MCP: [OK] Written to {zc_result['path']}")
|
|
3250
|
+
configured_agents.append("ZCode")
|
|
3251
|
+
elif zc_result.get("reason") == "not_installed":
|
|
3252
|
+
print(" - User MCP: ~/.zcode not found (Workspace .mcp.json still supported)")
|
|
3253
|
+
else:
|
|
3254
|
+
print(f" - User MCP: [Warning] Could not write {zc_result['path']}: {zc_result.get('reason')}")
|
|
3255
|
+
|
|
3256
|
+
# 7. Global Skills Sync for Antigravity and Claude Code
|
|
2989
3257
|
ag_skills_dir = os.path.join(user_home, ".gemini", "antigravity-cli", "skills")
|
|
2990
3258
|
claude_skills_dir = os.path.join(user_home, ".claude", "skills")
|
|
2991
3259
|
source_skills_dir = os.path.join(REPO_ROOT, "skills")
|
|
@@ -3006,14 +3274,14 @@ def handle_cli_setup() -> int:
|
|
|
3006
3274
|
except Exception:
|
|
3007
3275
|
pass
|
|
3008
3276
|
|
|
3009
|
-
print(f"\n[
|
|
3277
|
+
print(f"\n[7] Global Skills:")
|
|
3010
3278
|
print(f" - Synchronized: [OK] {skills_synced} skills (firmwareloop, fwloop-adapter) to Antigravity & Claude Code")
|
|
3011
3279
|
|
|
3012
|
-
#
|
|
3280
|
+
# 8. Machine-level global lab.yaml (ndtdbg config for the globally installed tool)
|
|
3013
3281
|
global_cfg = ensure_global_lab_yaml()
|
|
3014
3282
|
if global_cfg:
|
|
3015
3283
|
exists = " [已存在, 未覆盖]" if os.path.isfile(global_cfg) else " [已创建]"
|
|
3016
|
-
print("\n[
|
|
3284
|
+
print("\n[8] NdtBox 全局配置:")
|
|
3017
3285
|
print(f" - {global_cfg}{exists}")
|
|
3018
3286
|
print(" - 若未配置, 请运行: fwloop ndtdebugtool \"<NdtDebugTool 根目录>\"")
|
|
3019
3287
|
|
|
@@ -3026,7 +3294,7 @@ def handle_cli_setup() -> int:
|
|
|
3026
3294
|
def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
|
|
3027
3295
|
"""Handle `firmwareloop init [target_chip]` command."""
|
|
3028
3296
|
print("============================================================")
|
|
3029
|
-
print(" FirmwareLoop Multi-Agent Project Initializer (v0.
|
|
3297
|
+
print(" FirmwareLoop Multi-Agent Project Initializer (v0.8.0)")
|
|
3030
3298
|
print("============================================================")
|
|
3031
3299
|
cwd = os.getcwd()
|
|
3032
3300
|
print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
|
|
@@ -3059,7 +3327,7 @@ def handle_cli_init(chip: Optional[str] = None, upgrade: bool = False) -> int:
|
|
|
3059
3327
|
|
|
3060
3328
|
|
|
3061
3329
|
def print_cli_help() -> None:
|
|
3062
|
-
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.
|
|
3330
|
+
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.8.0)
|
|
3063
3331
|
|
|
3064
3332
|
Usage:
|
|
3065
3333
|
fwloop [command] (or: firmwareloop [command])
|
|
@@ -3105,7 +3373,7 @@ def main() -> None:
|
|
|
3105
3373
|
sys.exit(2)
|
|
3106
3374
|
sys.exit(handle_cli_ndtdebugtool(args[1]))
|
|
3107
3375
|
elif cmd in ["version", "-v", "--version"]:
|
|
3108
|
-
print("FirmwareLoop v0.
|
|
3376
|
+
print("FirmwareLoop v0.8.0")
|
|
3109
3377
|
sys.exit(0)
|
|
3110
3378
|
elif cmd in ["help", "-h", "--help"]:
|
|
3111
3379
|
print_cli_help()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Dual-Tier MCP setup and registration helper (v0.
|
|
3
|
+
Dual-Tier MCP setup and registration helper (v0.8.0).
|
|
4
4
|
Inspects installed AI coding agents (Qoder, Claude Code, Antigravity, Cursor)
|
|
5
5
|
and configures global MCP / skills or prints exact registration commands.
|
|
6
6
|
|
|
@@ -242,7 +242,7 @@ if ($Json) {
|
|
|
242
242
|
Write-FwJson ([pscustomobject]$report)
|
|
243
243
|
} else {
|
|
244
244
|
Write-Host "============================================================" -ForegroundColor Cyan
|
|
245
|
-
Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.
|
|
245
|
+
Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.8.0)" -ForegroundColor Green
|
|
246
246
|
Write-Host "============================================================" -ForegroundColor Cyan
|
|
247
247
|
Write-Host ""
|
|
248
248
|
Write-Host "[1] Google Antigravity / Gemini CLI:" -ForegroundColor Yellow
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|