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.
Files changed (35) hide show
  1. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/PKG-INFO +11 -8
  2. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/README.md +10 -7
  3. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/PKG-INFO +11 -8
  4. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/SOURCES.txt +5 -1
  5. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/pyproject.toml +1 -1
  6. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/__init__.py +1 -1
  7. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/acceptance-scenario.ps1 +11 -8
  8. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/common/fw.psm1 +9 -2
  9. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/doctor.ps1 +10 -3
  10. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/fw_mcp_server.py +192 -31
  11. firmwareloop-0.1.0/tools/lib/ndtdbg.py +222 -0
  12. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/logic_capture.ps1 +17 -8
  13. firmwareloop-0.1.0/tools/ndtdbg_bridge.ps1 +232 -0
  14. firmwareloop-0.1.0/tools/ndtdbg_cli.py +193 -0
  15. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/setup-agent-mcp.ps1 +277 -235
  16. firmwareloop-0.1.0/tools/sigrok_csv_to_fwloop.py +41 -0
  17. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/test-backends.ps1 +21 -8
  18. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/test.ps1 +4 -3
  19. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/LICENSE +0 -0
  20. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/dependency_links.txt +0 -0
  21. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/entry_points.txt +0 -0
  22. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/requires.txt +0 -0
  23. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/firmwareloop.egg-info/top_level.txt +0 -0
  24. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/setup.cfg +0 -0
  25. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/build.ps1 +0 -0
  26. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/can.ps1 +0 -0
  27. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/check-qoder-mcp.ps1 +0 -0
  28. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/common/build-backends.psm1 +0 -0
  29. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/common/uart_probe.py +0 -0
  30. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/flash.ps1 +0 -0
  31. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/instrument_cli.py +0 -0
  32. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/lib/__init__.py +0 -0
  33. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/lib/instruments.py +0 -0
  34. {firmwareloop-0.0.12 → firmwareloop-0.1.0}/tools/logic_decode.ps1 +0 -0
  35. {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.12
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 IDE**:
139
- ```bash
140
- qoder.cmd mcp add --global fwloop -- fwloop
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.12)
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.12)
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 IDE**:
111
- ```bash
112
- qoder.cmd mcp add --global fwloop -- fwloop
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.12)
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.12)
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.12
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 IDE**:
139
- ```bash
140
- qoder.cmd mcp add --global fwloop -- fwloop
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.12)
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.12)
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "firmwareloop"
7
- version = "0.0.12"
7
+ version = "0.1.0"
8
8
  description = "AI Agent Firmware Development and Lab Automation Platform (Dual-Tier MCP Server)"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -2,4 +2,4 @@
2
2
  FirmwareLoop - AI Agent Firmware Development and Lab Automation Platform.
3
3
  """
4
4
 
5
- __version__ = "0.0.12"
5
+ __version__ = "0.1.0"
@@ -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.ok) { $null } else { $build.diagnostics }
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 -and (Test-Path -LiteralPath (Join-Path $repoRoot '.venv\Scripts\agentic-hil.exe'))) {
118
- $ahilTool = Join-Path $repoRoot '.venv\Scripts\agentic-hil.exe'
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 relative to
80
- repo root, then PATH. Never hardcoded absolute paths (Spec §25).
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 '.venv[\\/]Scripts[\\/]python.exe$') { Split-Path $python } else { $null }
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 = if ($venvBin) { Join-Path $venvBin ($Name + '.exe') } else { $null }
28
- $cmd = if ($candidate -and (Test-Path -LiteralPath $candidate)) {
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.12).
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(["-DurationMs", str(duration_ms)])
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(["-OutputFile", output_file])
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.12"
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.12)")
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 / uv-managed installation detected.")
1377
+ print("[*] Global installation detected.")
1295
1378
  uv_path = shutil.which("uv")
1296
1379
  if uv_path:
1297
- print("[*] Refreshing uv cache for firmwareloop...")
1380
+ print("[*] Upgrading firmwareloop via `uv tool upgrade firmwareloop`...")
1298
1381
  try:
1299
- subprocess.run([uv_path, "cache", "clean", "firmwareloop"], capture_output=True)
1300
- print("[+] uv cache refreshed! The next run will automatically fetch the latest release from GitHub.")
1301
- return 0
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 refreshing uv cache: {e}")
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.12)")
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 IDE
1376
- print("\n[3] Qoder IDE:")
1377
- qoder_cmd = shutil.which("qoder.cmd") or shutil.which("qoder")
1378
- if qoder_cmd:
1379
- try:
1380
- r1 = subprocess.run([qoder_cmd, "--add-mcp", json.dumps({"name": "firmwareloop", "command": "fwloop"})], shell=True, capture_output=True, text=True)
1381
- r2 = subprocess.run([qoder_cmd, "--add-mcp", json.dumps({"name": "agentic-hil", "command": "agentic-hil", "args": ["mcp-stdio"]})], shell=True, capture_output=True, text=True)
1382
- if r1.returncode == 0 and r2.returncode == 0:
1383
- print(" - User Profile MCP: [OK] Automatically registered to Qoder User Profile")
1384
- configured_agents.append("Qoder IDE")
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 Profile MCP: Qoder CLI not in PATH (Workspace .mcp.json supported)")
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.12)")
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.12)
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.12")
1638
+ print("FirmwareLoop v0.1.0")
1478
1639
  sys.exit(0)
1479
1640
  elif cmd in ["help", "-h", "--help"]:
1480
1641
  print_cli_help()