firmwareloop 0.0.12__tar.gz → 0.1.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.0.12 → firmwareloop-0.1.0}/PKG-INFO +11 -8
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/README.md +10 -7
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/PKG-INFO +11 -8
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/SOURCES.txt +5 -1
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/pyproject.toml +1 -1
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/__init__.py +1 -1
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/acceptance-scenario.ps1 +11 -8
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/common/fw.psm1 +9 -2
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/doctor.ps1 +10 -3
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/fw_mcp_server.py +192 -31
- firmwareloop-0.1.0/tools/lib/ndtdbg.py +222 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/logic_capture.ps1 +17 -8
- firmwareloop-0.1.0/tools/ndtdbg_bridge.ps1 +232 -0
- firmwareloop-0.1.0/tools/ndtdbg_cli.py +193 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/setup-agent-mcp.ps1 +277 -235
- firmwareloop-0.1.0/tools/sigrok_csv_to_fwloop.py +41 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/test-backends.ps1 +21 -8
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/test.ps1 +4 -3
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/LICENSE +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/dependency_links.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/entry_points.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/requires.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/top_level.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/setup.cfg +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/build.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/can.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/check-qoder-mcp.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/common/build-backends.psm1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/common/uart_probe.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/flash.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/instrument_cli.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/lib/__init__.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/lib/instruments.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/logic_decode.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/reset.ps1 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.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
|
|
@@ -135,10 +135,12 @@ claude mcp add --scope user fwloop -- fwloop
|
|
|
135
135
|
claude mcp add --scope user agentic-hil -- agentic-hil mcp-stdio
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
3. **Qoder
|
|
139
|
-
|
|
140
|
-
qoder.
|
|
141
|
-
|
|
138
|
+
3. **Qoder / Qoder CN**:
|
|
139
|
+
`fwloop setup` 会直接写入 IDE 设置页读取的用户级文件:
|
|
140
|
+
- 国际版:`~/.qoder/mcp.json`
|
|
141
|
+
- 国内版:`~/.qoder-cn/mcp.json`
|
|
142
|
+
|
|
143
|
+
不要依赖 `qoder.cmd --add-mcp`:它写到 `%APPDATA%\Qoder\User\mcp.json`,设置页看不到。
|
|
142
144
|
|
|
143
145
|
4. **Cursor / 其他 IDE**(在工程根目录的 `.mcp.json` 中并联配置):
|
|
144
146
|
```json
|
|
@@ -180,7 +182,7 @@ fwloop init
|
|
|
180
182
|
|
|
181
183
|
| 命令 | 适用级别 | 核心作用 | 使用时机 |
|
|
182
184
|
|---|---|---|---|
|
|
183
|
-
| **`fwloop setup`** | **系统全局级** | 给各大 Agent(Antigravity / Claude Code)配置全局 MCP 与同步技能包 | **全电脑只需跑 1 次** |
|
|
185
|
+
| **`fwloop setup`** | **系统全局级** | 给各大 Agent(Antigravity / Claude Code / Qoder / Qoder CN)配置全局 MCP 与同步技能包 | **全电脑只需跑 1 次** |
|
|
184
186
|
| **`fwloop init`** | **工程项目级** | 在当前单片机代码目录下生成 `AGENTS.md`、`GEMINI.md`、`CLAUDE.md` 与台架配置 | **每个新单片机项目跑 1 次** |
|
|
185
187
|
| **`fwloop doctor`** | **环境诊断** | 检查编译器(Keil/GCC)、Python 环境、串口与探针连接健康度 | 随时排查环境时使用 |
|
|
186
188
|
| **`fwloop update`** | **自动更新** | 一键自动拉取最新代码并热重载依赖 | 升级工具版本时使用 |
|
|
@@ -336,7 +338,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
336
338
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
337
339
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
338
340
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
339
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0
|
|
341
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
|
|
340
342
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
341
343
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
342
344
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -346,7 +348,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
346
348
|
|
|
347
349
|
---
|
|
348
350
|
|
|
349
|
-
## 能力验证状态(v0.0
|
|
351
|
+
## 能力验证状态(v0.1.0)
|
|
350
352
|
|
|
351
353
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
352
354
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -366,6 +368,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
366
368
|
| VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
|
|
367
369
|
| Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
|
|
368
370
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
371
|
+
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
369
372
|
|
|
370
373
|
---
|
|
371
374
|
|
|
@@ -107,10 +107,12 @@ claude mcp add --scope user fwloop -- fwloop
|
|
|
107
107
|
claude mcp add --scope user agentic-hil -- agentic-hil mcp-stdio
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
3. **Qoder
|
|
111
|
-
|
|
112
|
-
qoder.
|
|
113
|
-
|
|
110
|
+
3. **Qoder / Qoder CN**:
|
|
111
|
+
`fwloop setup` 会直接写入 IDE 设置页读取的用户级文件:
|
|
112
|
+
- 国际版:`~/.qoder/mcp.json`
|
|
113
|
+
- 国内版:`~/.qoder-cn/mcp.json`
|
|
114
|
+
|
|
115
|
+
不要依赖 `qoder.cmd --add-mcp`:它写到 `%APPDATA%\Qoder\User\mcp.json`,设置页看不到。
|
|
114
116
|
|
|
115
117
|
4. **Cursor / 其他 IDE**(在工程根目录的 `.mcp.json` 中并联配置):
|
|
116
118
|
```json
|
|
@@ -152,7 +154,7 @@ fwloop init
|
|
|
152
154
|
|
|
153
155
|
| 命令 | 适用级别 | 核心作用 | 使用时机 |
|
|
154
156
|
|---|---|---|---|
|
|
155
|
-
| **`fwloop setup`** | **系统全局级** | 给各大 Agent(Antigravity / Claude Code)配置全局 MCP 与同步技能包 | **全电脑只需跑 1 次** |
|
|
157
|
+
| **`fwloop setup`** | **系统全局级** | 给各大 Agent(Antigravity / Claude Code / Qoder / Qoder CN)配置全局 MCP 与同步技能包 | **全电脑只需跑 1 次** |
|
|
156
158
|
| **`fwloop init`** | **工程项目级** | 在当前单片机代码目录下生成 `AGENTS.md`、`GEMINI.md`、`CLAUDE.md` 与台架配置 | **每个新单片机项目跑 1 次** |
|
|
157
159
|
| **`fwloop doctor`** | **环境诊断** | 检查编译器(Keil/GCC)、Python 环境、串口与探针连接健康度 | 随时排查环境时使用 |
|
|
158
160
|
| **`fwloop update`** | **自动更新** | 一键自动拉取最新代码并热重载依赖 | 升级工具版本时使用 |
|
|
@@ -308,7 +310,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
308
310
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
309
311
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
310
312
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
311
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0
|
|
313
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
|
|
312
314
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
313
315
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
314
316
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -318,7 +320,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
318
320
|
|
|
319
321
|
---
|
|
320
322
|
|
|
321
|
-
## 能力验证状态(v0.0
|
|
323
|
+
## 能力验证状态(v0.1.0)
|
|
322
324
|
|
|
323
325
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
324
326
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -338,6 +340,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
338
340
|
| VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
|
|
339
341
|
| Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
|
|
340
342
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
343
|
+
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
341
344
|
|
|
342
345
|
---
|
|
343
346
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.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
|
|
@@ -135,10 +135,12 @@ claude mcp add --scope user fwloop -- fwloop
|
|
|
135
135
|
claude mcp add --scope user agentic-hil -- agentic-hil mcp-stdio
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
3. **Qoder
|
|
139
|
-
|
|
140
|
-
qoder.
|
|
141
|
-
|
|
138
|
+
3. **Qoder / Qoder CN**:
|
|
139
|
+
`fwloop setup` 会直接写入 IDE 设置页读取的用户级文件:
|
|
140
|
+
- 国际版:`~/.qoder/mcp.json`
|
|
141
|
+
- 国内版:`~/.qoder-cn/mcp.json`
|
|
142
|
+
|
|
143
|
+
不要依赖 `qoder.cmd --add-mcp`:它写到 `%APPDATA%\Qoder\User\mcp.json`,设置页看不到。
|
|
142
144
|
|
|
143
145
|
4. **Cursor / 其他 IDE**(在工程根目录的 `.mcp.json` 中并联配置):
|
|
144
146
|
```json
|
|
@@ -180,7 +182,7 @@ fwloop init
|
|
|
180
182
|
|
|
181
183
|
| 命令 | 适用级别 | 核心作用 | 使用时机 |
|
|
182
184
|
|---|---|---|---|
|
|
183
|
-
| **`fwloop setup`** | **系统全局级** | 给各大 Agent(Antigravity / Claude Code)配置全局 MCP 与同步技能包 | **全电脑只需跑 1 次** |
|
|
185
|
+
| **`fwloop setup`** | **系统全局级** | 给各大 Agent(Antigravity / Claude Code / Qoder / Qoder CN)配置全局 MCP 与同步技能包 | **全电脑只需跑 1 次** |
|
|
184
186
|
| **`fwloop init`** | **工程项目级** | 在当前单片机代码目录下生成 `AGENTS.md`、`GEMINI.md`、`CLAUDE.md` 与台架配置 | **每个新单片机项目跑 1 次** |
|
|
185
187
|
| **`fwloop doctor`** | **环境诊断** | 检查编译器(Keil/GCC)、Python 环境、串口与探针连接健康度 | 随时排查环境时使用 |
|
|
186
188
|
| **`fwloop update`** | **自动更新** | 一键自动拉取最新代码并热重载依赖 | 升级工具版本时使用 |
|
|
@@ -336,7 +338,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
336
338
|
├── demo-firmware/ 示例固件(CMake;宿主编译模拟 MCU,闭环验证载体)
|
|
337
339
|
├── demo-make/ 示例固件(Make;构建测试载体)
|
|
338
340
|
├── docs/ DEPENDENCY_MATRIX / REUSE_PLAN / V0.0.2_GAP_VERIFICATION
|
|
339
|
-
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.0
|
|
341
|
+
├── pyproject.toml 标准 Python 包配置与 CLI 入口声明 (v0.1.0)
|
|
340
342
|
├── .mcp.example.json 双层 MCP 配置模板(firmwareloop + agentic-hil)
|
|
341
343
|
├── AGENTS.md 通用智能体规范(Antigravity / Qoder / Cursor 等)
|
|
342
344
|
├── CLAUDE.md Claude Code CLI 指南
|
|
@@ -346,7 +348,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
346
348
|
|
|
347
349
|
---
|
|
348
350
|
|
|
349
|
-
## 能力验证状态(v0.0
|
|
351
|
+
## 能力验证状态(v0.1.0)
|
|
350
352
|
|
|
351
353
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
352
354
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -366,6 +368,7 @@ claude mcp remove --scope user agentic-hil
|
|
|
366
368
|
| VISA 仪器(simulator) | Simulator Validated -> Real Hardware Validated 待仪器 |
|
|
367
369
|
| Safety Policy(limits.yaml) | Implemented(simulator 校验)-> 外置权威配置 |
|
|
368
370
|
| 自动修复闭环(M6 载体) | Simulator Validated -> Real Hardware Validated 待 DUT |
|
|
371
|
+
| NdtBox 数据采集/调试(fw_ndtdbg_status / fw_ndtdbg_measure, ChipSea Fxx IIC) | Implemented(无硬件 Fail-Closed 已验证)-> Real Hardware Validated 待 NdtBox |
|
|
369
372
|
|
|
370
373
|
---
|
|
371
374
|
|
|
@@ -18,12 +18,16 @@ tools/fw_mcp_server.py
|
|
|
18
18
|
tools/instrument_cli.py
|
|
19
19
|
tools/logic_capture.ps1
|
|
20
20
|
tools/logic_decode.ps1
|
|
21
|
+
tools/ndtdbg_bridge.ps1
|
|
22
|
+
tools/ndtdbg_cli.py
|
|
21
23
|
tools/reset.ps1
|
|
22
24
|
tools/setup-agent-mcp.ps1
|
|
25
|
+
tools/sigrok_csv_to_fwloop.py
|
|
23
26
|
tools/test-backends.ps1
|
|
24
27
|
tools/test.ps1
|
|
25
28
|
tools/common/build-backends.psm1
|
|
26
29
|
tools/common/fw.psm1
|
|
27
30
|
tools/common/uart_probe.py
|
|
28
31
|
tools/lib/__init__.py
|
|
29
|
-
tools/lib/instruments.py
|
|
32
|
+
tools/lib/instruments.py
|
|
33
|
+
tools/lib/ndtdbg.py
|
|
@@ -94,12 +94,12 @@ $report = [ordered]@{
|
|
|
94
94
|
# ---- 1. build --------------------------------------------------------------------
|
|
95
95
|
$build = Invoke-Tool 'build.ps1' @('-Configuration', 'Debug', '-Json')
|
|
96
96
|
$report.steps.build = [ordered]@{
|
|
97
|
-
ok = $build.ok
|
|
98
|
-
errors = $build.errors
|
|
99
|
-
warnings = $build.warnings
|
|
100
|
-
artifact = $build.artifact
|
|
101
|
-
sha256 = $build.artifact_sha256
|
|
102
|
-
detail = if ($build.
|
|
97
|
+
ok = [bool]($build.PSObject.Properties['ok'] -and $build.ok)
|
|
98
|
+
errors = if ($build.PSObject.Properties['errors']) { $build.errors } else { 1 }
|
|
99
|
+
warnings = if ($build.PSObject.Properties['warnings']) { $build.warnings } else { 0 }
|
|
100
|
+
artifact = if ($build.PSObject.Properties['artifact']) { $build.artifact } else { $null }
|
|
101
|
+
sha256 = if ($build.PSObject.Properties['artifact_sha256']) { $build.artifact_sha256 } else { $null }
|
|
102
|
+
detail = if ($build.PSObject.Properties['diagnostics']) { $build.diagnostics } elseif ($build.PSObject.Properties['error']) { $build.error } else { $null }
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
# --- evidence: build.json + hardware.json (Spec §25; identity probing in real
|
|
@@ -114,8 +114,11 @@ if ($Mode -eq 'real') {
|
|
|
114
114
|
$ahilTool = $null
|
|
115
115
|
$g2 = Get-Command agentic-hil -ErrorAction SilentlyContinue
|
|
116
116
|
if ($g2) { $ahilTool = $g2.Source }
|
|
117
|
-
if (-not $ahilTool
|
|
118
|
-
$
|
|
117
|
+
if (-not $ahilTool) {
|
|
118
|
+
$c1 = Join-Path $repoRoot '.venv\Scripts\agentic-hil.exe'
|
|
119
|
+
$c2 = Join-Path $repoRoot '.venv/bin/agentic-hil'
|
|
120
|
+
if (Test-Path -LiteralPath $c1) { $ahilTool = $c1 }
|
|
121
|
+
elseif (Test-Path -LiteralPath $c2) { $ahilTool = $c2 }
|
|
119
122
|
}
|
|
120
123
|
if ($ahilTool) {
|
|
121
124
|
$probes = Invoke-FwProcess -FilePath $ahilTool -Arguments @('debugger-probes') -WorkingDirectory $repoRoot -TimeoutMs 60000
|
|
@@ -76,18 +76,25 @@ function New-FwError {
|
|
|
76
76
|
function Resolve-FwPython {
|
|
77
77
|
<#
|
|
78
78
|
.SYNOPSIS
|
|
79
|
-
Locate a project Python: prefer .venv\Scripts\python.exe
|
|
80
|
-
repo root, then PATH.
|
|
79
|
+
Locate a project Python: prefer .venv\Scripts\python.exe (Windows) or
|
|
80
|
+
.venv/bin/python (Linux/macOS) relative to repo root, then PATH.
|
|
81
|
+
Never hardcoded absolute paths (Spec §25).
|
|
81
82
|
#>
|
|
82
83
|
param([string]$RepoRoot)
|
|
83
84
|
$candidates = @()
|
|
84
85
|
if ($RepoRoot) {
|
|
85
86
|
$candidates += (Join-Path $RepoRoot '.venv\Scripts\python.exe')
|
|
87
|
+
$candidates += (Join-Path $RepoRoot '.venv\bin\python')
|
|
88
|
+
$candidates += (Join-Path $RepoRoot '.venv/bin/python')
|
|
86
89
|
$candidates += (Join-Path $RepoRoot 'venv\Scripts\python.exe')
|
|
90
|
+
$candidates += (Join-Path $RepoRoot 'venv\bin\python')
|
|
91
|
+
$candidates += (Join-Path $RepoRoot 'venv/bin/python')
|
|
87
92
|
}
|
|
88
93
|
foreach ($path in $candidates) {
|
|
89
94
|
if (Test-Path -LiteralPath $path) { return (Resolve-Path -LiteralPath $path).Path }
|
|
90
95
|
}
|
|
96
|
+
$fromPath = Get-Command python3 -ErrorAction SilentlyContinue
|
|
97
|
+
if ($fromPath) { return $fromPath.Source }
|
|
91
98
|
$fromPath = Get-Command python -ErrorAction SilentlyContinue
|
|
92
99
|
if ($fromPath) { return $fromPath.Source }
|
|
93
100
|
return $null
|
|
@@ -19,13 +19,20 @@ Import-Module (Join-Path $PSScriptRoot 'common\fw.psm1') -Force
|
|
|
19
19
|
|
|
20
20
|
$repoRoot = Get-FwRepoRoot
|
|
21
21
|
$python = Resolve-FwPython -RepoRoot $repoRoot
|
|
22
|
-
$venvBin = if ($python -and $python -match '
|
|
22
|
+
$venvBin = if ($python -and ($python -match '[\\/]\.venv[\\/](Scripts|bin)[\\/]python(\.exe)?$')) { Split-Path $python } else { $null }
|
|
23
23
|
|
|
24
24
|
function Test-Tool {
|
|
25
25
|
param([string]$Name)
|
|
26
26
|
# prefer the project venv bin dir (tools installed without polluting PATH)
|
|
27
|
-
$candidate =
|
|
28
|
-
|
|
27
|
+
$candidate = $null
|
|
28
|
+
if ($venvBin) {
|
|
29
|
+
if (Test-Path -LiteralPath (Join-Path $venvBin ($Name + '.exe'))) {
|
|
30
|
+
$candidate = Join-Path $venvBin ($Name + '.exe')
|
|
31
|
+
} elseif (Test-Path -LiteralPath (Join-Path $venvBin $Name)) {
|
|
32
|
+
$candidate = Join-Path $venvBin $Name
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
$cmd = if ($candidate) {
|
|
29
36
|
Get-Item -LiteralPath $candidate
|
|
30
37
|
} else {
|
|
31
38
|
Get-Command $Name -ErrorAction SilentlyContinue
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.0
|
|
3
|
+
fw_mcp_server.py - FirmwareLoop High-Level Workflow & Device MCP Server (v0.1.0).
|
|
4
4
|
|
|
5
5
|
Exposes high-level firmware engineering and hardware management tools to AI Coding
|
|
6
6
|
Agents (Antigravity CLI, Claude Code CLI, Qoder IDE, Cursor) via the Model Context
|
|
@@ -14,6 +14,8 @@ Tools provided:
|
|
|
14
14
|
- fw_run_hil_test: Hardware-in-the-loop pytest suite (test.ps1)
|
|
15
15
|
- fw_acceptance_scenario: End-to-end acceptance scenario (acceptance-scenario.ps1)
|
|
16
16
|
- fw_measure: Controlled PyVISA/SCPI instrument measurement (instrument_cli.py)
|
|
17
|
+
- fw_ndtdbg_status: NdtBox (USB->IIC) connectivity & firmware version probe (ndtdbg_cli.py)
|
|
18
|
+
- fw_ndtdbg_measure: NdtBox pressure-sensor data stream polling over IIC (ndtdbg_cli.py)
|
|
17
19
|
- fw_logic_capture: Logic analyzer protocol capture (logic_capture.ps1)
|
|
18
20
|
- fw_logic_decode: Logic analyzer protocol decoding & assertion (logic_decode.ps1)
|
|
19
21
|
- fw_get_evidence: Read structured audit run evidence (artifacts/runs/)
|
|
@@ -240,6 +242,36 @@ def handle_fw_measure(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
240
242
|
return res
|
|
241
243
|
|
|
242
244
|
|
|
245
|
+
def handle_fw_ndtdbg_status(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
246
|
+
cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
|
|
247
|
+
cmd = [PYTHON_EXE, cli_script, "status"]
|
|
248
|
+
if args.get("dll_dir"):
|
|
249
|
+
cmd.extend(["--dll-dir", args["dll_dir"]])
|
|
250
|
+
if args.get("slave_addr"):
|
|
251
|
+
cmd.extend(["--slave-addr", str(args["slave_addr"])])
|
|
252
|
+
res = run_process(cmd, timeout=60)
|
|
253
|
+
if res.get("data"):
|
|
254
|
+
return res["data"]
|
|
255
|
+
return res
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def handle_fw_ndtdbg_measure(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
259
|
+
cli_script = os.path.join(REPO_ROOT, "tools", "ndtdbg_cli.py")
|
|
260
|
+
duration = int(args.get("duration_seconds", 2))
|
|
261
|
+
cmd = [PYTHON_EXE, cli_script, "poll", "--duration-ms", str(duration * 1000),
|
|
262
|
+
"--interval-ms", str(args.get("interval_ms", 10))]
|
|
263
|
+
if args.get("full_output"):
|
|
264
|
+
cmd.append("--full")
|
|
265
|
+
if args.get("dll_dir"):
|
|
266
|
+
cmd.extend(["--dll-dir", args["dll_dir"]])
|
|
267
|
+
if args.get("slave_addr"):
|
|
268
|
+
cmd.extend(["--slave-addr", str(args["slave_addr"])])
|
|
269
|
+
res = run_process(cmd, timeout=60 + duration)
|
|
270
|
+
if res.get("data"):
|
|
271
|
+
return res["data"]
|
|
272
|
+
return res
|
|
273
|
+
|
|
274
|
+
|
|
243
275
|
def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
244
276
|
script = os.path.join(REPO_ROOT, "tools", "logic_capture.ps1")
|
|
245
277
|
cmd = [PWSH_EXE, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, "-Json"]
|
|
@@ -253,11 +285,11 @@ def handle_fw_logic_capture(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
253
285
|
|
|
254
286
|
duration_ms = args.get("duration_ms")
|
|
255
287
|
if duration_ms:
|
|
256
|
-
cmd.extend(["-
|
|
288
|
+
cmd.extend(["-DurationSec", str(float(duration_ms) / 1000.0)])
|
|
257
289
|
|
|
258
290
|
output_file = args.get("output_file")
|
|
259
291
|
if output_file:
|
|
260
|
-
cmd.extend(["-
|
|
292
|
+
cmd.extend(["-OutDir", output_file])
|
|
261
293
|
|
|
262
294
|
res = run_process(cmd, timeout=30)
|
|
263
295
|
if res.get("data"):
|
|
@@ -1023,6 +1055,57 @@ TOOLS_REGISTRY = {
|
|
|
1023
1055
|
},
|
|
1024
1056
|
"handler": handle_fw_measure,
|
|
1025
1057
|
},
|
|
1058
|
+
"fw_ndtdbg_status": {
|
|
1059
|
+
"description": "Probe NdtBox (USB->IIC debug box, ChipSea Fxx) status: device open, firmware version, module connectivity. Fail-closed (PROBE_NOT_FOUND / TARGET_MISMATCH) when hardware absent or no IIC response.",
|
|
1060
|
+
"inputSchema": {
|
|
1061
|
+
"type": "object",
|
|
1062
|
+
"properties": {
|
|
1063
|
+
"dll_dir": {
|
|
1064
|
+
"type": "string",
|
|
1065
|
+
"description": "NdtDebugTool lib directory containing USBIOX.DLL; default from lab.yaml ndtdbg.dll_dir",
|
|
1066
|
+
},
|
|
1067
|
+
"slave_addr": {
|
|
1068
|
+
"type": "string",
|
|
1069
|
+
"description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
|
|
1070
|
+
},
|
|
1071
|
+
},
|
|
1072
|
+
"additionalProperties": False,
|
|
1073
|
+
},
|
|
1074
|
+
"handler": handle_fw_ndtdbg_status,
|
|
1075
|
+
},
|
|
1076
|
+
"fw_ndtdbg_measure": {
|
|
1077
|
+
"description": "Poll NdtBox IIC debug data stream (ChipSea Fxx) for N seconds: frame count, checksum errors, channel samples. Read-only; firmware upgrade is a separate future capability.",
|
|
1078
|
+
"inputSchema": {
|
|
1079
|
+
"type": "object",
|
|
1080
|
+
"properties": {
|
|
1081
|
+
"duration_seconds": {
|
|
1082
|
+
"type": "integer",
|
|
1083
|
+
"description": "Poll duration in seconds",
|
|
1084
|
+
"default": 2,
|
|
1085
|
+
},
|
|
1086
|
+
"interval_ms": {
|
|
1087
|
+
"type": "integer",
|
|
1088
|
+
"description": "Sampling interval in milliseconds",
|
|
1089
|
+
"default": 10,
|
|
1090
|
+
},
|
|
1091
|
+
"full_output": {
|
|
1092
|
+
"type": "boolean",
|
|
1093
|
+
"description": "Return every frame (may be large); default keeps a frame sample plus totals",
|
|
1094
|
+
"default": False,
|
|
1095
|
+
},
|
|
1096
|
+
"dll_dir": {
|
|
1097
|
+
"type": "string",
|
|
1098
|
+
"description": "NdtDebugTool lib directory containing USBIOX.DLL; default from lab.yaml ndtdbg.dll_dir",
|
|
1099
|
+
},
|
|
1100
|
+
"slave_addr": {
|
|
1101
|
+
"type": "string",
|
|
1102
|
+
"description": "IIC slave address (e.g. 0xA0); default from lab.yaml ndtdbg.slave_addr",
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
"additionalProperties": False,
|
|
1106
|
+
},
|
|
1107
|
+
"handler": handle_fw_ndtdbg_measure,
|
|
1108
|
+
},
|
|
1026
1109
|
"fw_logic_capture": {
|
|
1027
1110
|
"description": "Capture digital protocol waveforms (I2C, SPI, UART) via Saleae / sigrok or deterministic simulator.",
|
|
1028
1111
|
"inputSchema": {
|
|
@@ -1160,7 +1243,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
1160
1243
|
},
|
|
1161
1244
|
"serverInfo": {
|
|
1162
1245
|
"name": "firmwareloop",
|
|
1163
|
-
"version": "0.0
|
|
1246
|
+
"version": "0.1.0"
|
|
1164
1247
|
}
|
|
1165
1248
|
}
|
|
1166
1249
|
}
|
|
@@ -1252,7 +1335,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
1252
1335
|
def handle_cli_update() -> int:
|
|
1253
1336
|
"""Handle `firmwareloop update` command."""
|
|
1254
1337
|
print("============================================================")
|
|
1255
|
-
print(" FirmwareLoop Auto-Updater (v0.0
|
|
1338
|
+
print(" FirmwareLoop Auto-Updater (v0.1.0)")
|
|
1256
1339
|
print("============================================================")
|
|
1257
1340
|
|
|
1258
1341
|
is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
|
|
@@ -1291,20 +1374,28 @@ def handle_cli_update() -> int:
|
|
|
1291
1374
|
print("============================================================")
|
|
1292
1375
|
return 0
|
|
1293
1376
|
else:
|
|
1294
|
-
print("[*] Global
|
|
1377
|
+
print("[*] Global installation detected.")
|
|
1295
1378
|
uv_path = shutil.which("uv")
|
|
1296
1379
|
if uv_path:
|
|
1297
|
-
print("[*]
|
|
1380
|
+
print("[*] Upgrading firmwareloop via `uv tool upgrade firmwareloop`...")
|
|
1298
1381
|
try:
|
|
1299
|
-
subprocess.run([uv_path, "
|
|
1300
|
-
|
|
1301
|
-
|
|
1382
|
+
res = subprocess.run([uv_path, "tool", "upgrade", "firmwareloop"])
|
|
1383
|
+
if res.returncode == 0:
|
|
1384
|
+
print("\n============================================================")
|
|
1385
|
+
print("[+] FirmwareLoop has been upgraded to the latest version!")
|
|
1386
|
+
print("============================================================")
|
|
1387
|
+
return 0
|
|
1388
|
+
else:
|
|
1389
|
+
print(f"[-] uv tool upgrade returned code {res.returncode}")
|
|
1390
|
+
return res.returncode
|
|
1302
1391
|
except Exception as e:
|
|
1303
|
-
print(f"[-] Error
|
|
1392
|
+
print(f"[-] Error upgrading via uv tool: {e}")
|
|
1304
1393
|
return 1
|
|
1305
1394
|
else:
|
|
1306
|
-
print("[*] Upgrading via pip...")
|
|
1395
|
+
print("[*] Upgrading via pip (`pip install --upgrade firmwareloop`)...")
|
|
1307
1396
|
res = subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", "firmwareloop"])
|
|
1397
|
+
if res.returncode == 0:
|
|
1398
|
+
print("[+] FirmwareLoop has been upgraded to the latest version!")
|
|
1308
1399
|
return res.returncode
|
|
1309
1400
|
|
|
1310
1401
|
|
|
@@ -1316,10 +1407,78 @@ def handle_cli_doctor() -> int:
|
|
|
1316
1407
|
return res.returncode
|
|
1317
1408
|
|
|
1318
1409
|
|
|
1410
|
+
QODER_USER_MCP_SERVERS = {
|
|
1411
|
+
"firmwareloop": {"command": "fwloop", "type": "stdio"},
|
|
1412
|
+
"agentic-hil": {"command": "agentic-hil", "args": ["mcp-stdio"], "type": "stdio"},
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
QODER_USER_MCP_EDITIONS = (
|
|
1416
|
+
("Qoder", ".qoder"),
|
|
1417
|
+
("Qoder CN", ".qoder-cn"),
|
|
1418
|
+
)
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
def upsert_qoder_mcp_json(mcp_path: str, servers: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
1422
|
+
"""Merge MCP servers into a Qoder user-level ~/.qoder[-cn]/mcp.json without dropping existing entries."""
|
|
1423
|
+
payload = dict(servers or QODER_USER_MCP_SERVERS)
|
|
1424
|
+
data: Dict[str, Any] = {}
|
|
1425
|
+
if os.path.exists(mcp_path):
|
|
1426
|
+
try:
|
|
1427
|
+
with open(mcp_path, "r", encoding="utf-8-sig") as f:
|
|
1428
|
+
loaded = json.load(f)
|
|
1429
|
+
if isinstance(loaded, dict):
|
|
1430
|
+
data = loaded
|
|
1431
|
+
except Exception:
|
|
1432
|
+
data = {}
|
|
1433
|
+
mcp_servers = data.get("mcpServers")
|
|
1434
|
+
if not isinstance(mcp_servers, dict):
|
|
1435
|
+
mcp_servers = {}
|
|
1436
|
+
mcp_servers.update(payload)
|
|
1437
|
+
data["mcpServers"] = mcp_servers
|
|
1438
|
+
parent = os.path.dirname(mcp_path)
|
|
1439
|
+
if parent:
|
|
1440
|
+
os.makedirs(parent, exist_ok=True)
|
|
1441
|
+
with open(mcp_path, "w", encoding="utf-8") as f:
|
|
1442
|
+
json.dump(data, f, indent=2)
|
|
1443
|
+
f.write("\n")
|
|
1444
|
+
return data
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
def configure_qoder_user_mcp(user_home: str) -> List[Dict[str, Any]]:
|
|
1448
|
+
"""Write firmwareloop MCP entries into installed Qoder / Qoder CN user mcp.json files."""
|
|
1449
|
+
results: List[Dict[str, Any]] = []
|
|
1450
|
+
for label, folder in QODER_USER_MCP_EDITIONS:
|
|
1451
|
+
edition_dir = os.path.join(user_home, folder)
|
|
1452
|
+
mcp_file = os.path.join(edition_dir, "mcp.json")
|
|
1453
|
+
if not os.path.isdir(edition_dir):
|
|
1454
|
+
results.append({
|
|
1455
|
+
"edition": label,
|
|
1456
|
+
"ok": False,
|
|
1457
|
+
"path": mcp_file,
|
|
1458
|
+
"reason": "not_installed",
|
|
1459
|
+
})
|
|
1460
|
+
continue
|
|
1461
|
+
try:
|
|
1462
|
+
upsert_qoder_mcp_json(mcp_file)
|
|
1463
|
+
results.append({
|
|
1464
|
+
"edition": label,
|
|
1465
|
+
"ok": True,
|
|
1466
|
+
"path": mcp_file,
|
|
1467
|
+
})
|
|
1468
|
+
except Exception as exc:
|
|
1469
|
+
results.append({
|
|
1470
|
+
"edition": label,
|
|
1471
|
+
"ok": False,
|
|
1472
|
+
"path": mcp_file,
|
|
1473
|
+
"reason": str(exc),
|
|
1474
|
+
})
|
|
1475
|
+
return results
|
|
1476
|
+
|
|
1477
|
+
|
|
1319
1478
|
def handle_cli_setup() -> int:
|
|
1320
1479
|
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, and Qoder IDE."""
|
|
1321
1480
|
print("============================================================")
|
|
1322
|
-
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.0
|
|
1481
|
+
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.1.0)")
|
|
1323
1482
|
print("============================================================")
|
|
1324
1483
|
print("")
|
|
1325
1484
|
|
|
@@ -1372,22 +1531,24 @@ def handle_cli_setup() -> int:
|
|
|
1372
1531
|
except Exception as e:
|
|
1373
1532
|
print(f" - Global MCP Config: [Warning] Could not update .claude.json: {e}")
|
|
1374
1533
|
|
|
1375
|
-
# 3. Qoder
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
else:
|
|
1386
|
-
print(f" - User Profile MCP: [Notice] Qoder CLI output: {r1.stdout.strip()}")
|
|
1387
|
-
except Exception as e:
|
|
1388
|
-
print(f" - User Profile MCP: [Warning] Could not invoke qoder CLI: {e}")
|
|
1534
|
+
# 3. Qoder / Qoder CN user-level MCP
|
|
1535
|
+
# IDE settings read ~/.qoder/mcp.json and ~/.qoder-cn/mcp.json.
|
|
1536
|
+
# qoder.cmd --add-mcp writes the VS Code-style %APPDATA%\Qoder\User\mcp.json, which the settings page does not show.
|
|
1537
|
+
print("\n[3] Qoder IDE / Qoder CN:")
|
|
1538
|
+
qoder_results = configure_qoder_user_mcp(user_home)
|
|
1539
|
+
qoder_ok = [item for item in qoder_results if item.get("ok")]
|
|
1540
|
+
if qoder_ok:
|
|
1541
|
+
for item in qoder_ok:
|
|
1542
|
+
print(f" - {item['edition']}: [OK] Written to {item['path']}")
|
|
1543
|
+
configured_agents.append("Qoder")
|
|
1389
1544
|
else:
|
|
1390
|
-
print(" - User
|
|
1545
|
+
print(" - User MCP: neither ~/.qoder nor ~/.qoder-cn found (Workspace .mcp.json still supported)")
|
|
1546
|
+
for item in qoder_results:
|
|
1547
|
+
if item.get("ok"):
|
|
1548
|
+
continue
|
|
1549
|
+
if item.get("reason") == "not_installed":
|
|
1550
|
+
continue
|
|
1551
|
+
print(f" - {item['edition']}: [Warning] Could not write {item['path']}: {item.get('reason')}")
|
|
1391
1552
|
|
|
1392
1553
|
# 4. Global Skills Sync for Antigravity and Claude Code
|
|
1393
1554
|
ag_skills_dir = os.path.join(user_home, ".gemini", "antigravity-cli", "skills")
|
|
@@ -1422,7 +1583,7 @@ def handle_cli_setup() -> int:
|
|
|
1422
1583
|
def handle_cli_init() -> int:
|
|
1423
1584
|
"""Handle `firmwareloop init` command."""
|
|
1424
1585
|
print("============================================================")
|
|
1425
|
-
print(" FirmwareLoop Multi-Agent Project Initializer (v0.0
|
|
1586
|
+
print(" FirmwareLoop Multi-Agent Project Initializer (v0.1.0)")
|
|
1426
1587
|
print("============================================================")
|
|
1427
1588
|
cwd = os.getcwd()
|
|
1428
1589
|
print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
|
|
@@ -1443,7 +1604,7 @@ def handle_cli_init() -> int:
|
|
|
1443
1604
|
|
|
1444
1605
|
|
|
1445
1606
|
def print_cli_help() -> None:
|
|
1446
|
-
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.0
|
|
1607
|
+
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.1.0)
|
|
1447
1608
|
|
|
1448
1609
|
Usage:
|
|
1449
1610
|
fwloop [command] (or: firmwareloop [command])
|
|
@@ -1474,7 +1635,7 @@ def main() -> None:
|
|
|
1474
1635
|
elif cmd in ["setup", "register"]:
|
|
1475
1636
|
sys.exit(handle_cli_setup())
|
|
1476
1637
|
elif cmd in ["version", "-v", "--version"]:
|
|
1477
|
-
print("FirmwareLoop v0.0
|
|
1638
|
+
print("FirmwareLoop v0.1.0")
|
|
1478
1639
|
sys.exit(0)
|
|
1479
1640
|
elif cmd in ["help", "-h", "--help"]:
|
|
1480
1641
|
print_cli_help()
|