firmwareloop 0.0.12__tar.gz → 0.0.13__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.0.13}/PKG-INFO +10 -8
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/README.md +9 -7
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/firmwareloop.egg-info/PKG-INFO +10 -8
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/pyproject.toml +1 -1
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/__init__.py +1 -1
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/acceptance-scenario.ps1 +11 -8
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/common/fw.psm1 +9 -2
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/doctor.ps1 +10 -3
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/fw_mcp_server.py +107 -29
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/setup-agent-mcp.ps1 +68 -26
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/test-backends.ps1 +21 -8
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/test.ps1 +4 -3
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/LICENSE +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/firmwareloop.egg-info/SOURCES.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/firmwareloop.egg-info/dependency_links.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/firmwareloop.egg-info/entry_points.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/firmwareloop.egg-info/requires.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/firmwareloop.egg-info/top_level.txt +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/setup.cfg +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/build.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/can.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/check-qoder-mcp.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/common/build-backends.psm1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/common/uart_probe.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/flash.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/instrument_cli.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/lib/__init__.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/lib/instruments.py +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/logic_capture.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/tools/logic_decode.ps1 +0 -0
- {firmwareloop-0.0.12 → firmwareloop-0.0.13}/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.0.13
|
|
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.0.13)
|
|
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.0.13)
|
|
350
352
|
|
|
351
353
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
352
354
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -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.0.13)
|
|
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.0.13)
|
|
322
324
|
|
|
323
325
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
324
326
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firmwareloop
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.13
|
|
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.0.13)
|
|
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.0.13)
|
|
350
352
|
|
|
351
353
|
> 状态定义:`Implemented`(已实现)/ `Simulator Validated`(模拟验证)/
|
|
352
354
|
> `Real Hardware Validated`(真机验证)/ `Experimental` / `Not Implemented`
|
|
@@ -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.0.13).
|
|
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
|
|
@@ -1160,7 +1160,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
1160
1160
|
},
|
|
1161
1161
|
"serverInfo": {
|
|
1162
1162
|
"name": "firmwareloop",
|
|
1163
|
-
"version": "0.0.
|
|
1163
|
+
"version": "0.0.13"
|
|
1164
1164
|
}
|
|
1165
1165
|
}
|
|
1166
1166
|
}
|
|
@@ -1252,7 +1252,7 @@ def process_request(req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
1252
1252
|
def handle_cli_update() -> int:
|
|
1253
1253
|
"""Handle `firmwareloop update` command."""
|
|
1254
1254
|
print("============================================================")
|
|
1255
|
-
print(" FirmwareLoop Auto-Updater (v0.0.
|
|
1255
|
+
print(" FirmwareLoop Auto-Updater (v0.0.13)")
|
|
1256
1256
|
print("============================================================")
|
|
1257
1257
|
|
|
1258
1258
|
is_git_repo = os.path.exists(os.path.join(REPO_ROOT, ".git"))
|
|
@@ -1291,20 +1291,28 @@ def handle_cli_update() -> int:
|
|
|
1291
1291
|
print("============================================================")
|
|
1292
1292
|
return 0
|
|
1293
1293
|
else:
|
|
1294
|
-
print("[*] Global
|
|
1294
|
+
print("[*] Global installation detected.")
|
|
1295
1295
|
uv_path = shutil.which("uv")
|
|
1296
1296
|
if uv_path:
|
|
1297
|
-
print("[*]
|
|
1297
|
+
print("[*] Upgrading firmwareloop via `uv tool upgrade firmwareloop`...")
|
|
1298
1298
|
try:
|
|
1299
|
-
subprocess.run([uv_path, "
|
|
1300
|
-
|
|
1301
|
-
|
|
1299
|
+
res = subprocess.run([uv_path, "tool", "upgrade", "firmwareloop"])
|
|
1300
|
+
if res.returncode == 0:
|
|
1301
|
+
print("\n============================================================")
|
|
1302
|
+
print("[+] FirmwareLoop has been upgraded to the latest version!")
|
|
1303
|
+
print("============================================================")
|
|
1304
|
+
return 0
|
|
1305
|
+
else:
|
|
1306
|
+
print(f"[-] uv tool upgrade returned code {res.returncode}")
|
|
1307
|
+
return res.returncode
|
|
1302
1308
|
except Exception as e:
|
|
1303
|
-
print(f"[-] Error
|
|
1309
|
+
print(f"[-] Error upgrading via uv tool: {e}")
|
|
1304
1310
|
return 1
|
|
1305
1311
|
else:
|
|
1306
|
-
print("[*] Upgrading via pip...")
|
|
1312
|
+
print("[*] Upgrading via pip (`pip install --upgrade firmwareloop`)...")
|
|
1307
1313
|
res = subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", "firmwareloop"])
|
|
1314
|
+
if res.returncode == 0:
|
|
1315
|
+
print("[+] FirmwareLoop has been upgraded to the latest version!")
|
|
1308
1316
|
return res.returncode
|
|
1309
1317
|
|
|
1310
1318
|
|
|
@@ -1316,10 +1324,78 @@ def handle_cli_doctor() -> int:
|
|
|
1316
1324
|
return res.returncode
|
|
1317
1325
|
|
|
1318
1326
|
|
|
1327
|
+
QODER_USER_MCP_SERVERS = {
|
|
1328
|
+
"firmwareloop": {"command": "fwloop", "type": "stdio"},
|
|
1329
|
+
"agentic-hil": {"command": "agentic-hil", "args": ["mcp-stdio"], "type": "stdio"},
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
QODER_USER_MCP_EDITIONS = (
|
|
1333
|
+
("Qoder", ".qoder"),
|
|
1334
|
+
("Qoder CN", ".qoder-cn"),
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
|
|
1338
|
+
def upsert_qoder_mcp_json(mcp_path: str, servers: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
1339
|
+
"""Merge MCP servers into a Qoder user-level ~/.qoder[-cn]/mcp.json without dropping existing entries."""
|
|
1340
|
+
payload = dict(servers or QODER_USER_MCP_SERVERS)
|
|
1341
|
+
data: Dict[str, Any] = {}
|
|
1342
|
+
if os.path.exists(mcp_path):
|
|
1343
|
+
try:
|
|
1344
|
+
with open(mcp_path, "r", encoding="utf-8-sig") as f:
|
|
1345
|
+
loaded = json.load(f)
|
|
1346
|
+
if isinstance(loaded, dict):
|
|
1347
|
+
data = loaded
|
|
1348
|
+
except Exception:
|
|
1349
|
+
data = {}
|
|
1350
|
+
mcp_servers = data.get("mcpServers")
|
|
1351
|
+
if not isinstance(mcp_servers, dict):
|
|
1352
|
+
mcp_servers = {}
|
|
1353
|
+
mcp_servers.update(payload)
|
|
1354
|
+
data["mcpServers"] = mcp_servers
|
|
1355
|
+
parent = os.path.dirname(mcp_path)
|
|
1356
|
+
if parent:
|
|
1357
|
+
os.makedirs(parent, exist_ok=True)
|
|
1358
|
+
with open(mcp_path, "w", encoding="utf-8") as f:
|
|
1359
|
+
json.dump(data, f, indent=2)
|
|
1360
|
+
f.write("\n")
|
|
1361
|
+
return data
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def configure_qoder_user_mcp(user_home: str) -> List[Dict[str, Any]]:
|
|
1365
|
+
"""Write firmwareloop MCP entries into installed Qoder / Qoder CN user mcp.json files."""
|
|
1366
|
+
results: List[Dict[str, Any]] = []
|
|
1367
|
+
for label, folder in QODER_USER_MCP_EDITIONS:
|
|
1368
|
+
edition_dir = os.path.join(user_home, folder)
|
|
1369
|
+
mcp_file = os.path.join(edition_dir, "mcp.json")
|
|
1370
|
+
if not os.path.isdir(edition_dir):
|
|
1371
|
+
results.append({
|
|
1372
|
+
"edition": label,
|
|
1373
|
+
"ok": False,
|
|
1374
|
+
"path": mcp_file,
|
|
1375
|
+
"reason": "not_installed",
|
|
1376
|
+
})
|
|
1377
|
+
continue
|
|
1378
|
+
try:
|
|
1379
|
+
upsert_qoder_mcp_json(mcp_file)
|
|
1380
|
+
results.append({
|
|
1381
|
+
"edition": label,
|
|
1382
|
+
"ok": True,
|
|
1383
|
+
"path": mcp_file,
|
|
1384
|
+
})
|
|
1385
|
+
except Exception as exc:
|
|
1386
|
+
results.append({
|
|
1387
|
+
"edition": label,
|
|
1388
|
+
"ok": False,
|
|
1389
|
+
"path": mcp_file,
|
|
1390
|
+
"reason": str(exc),
|
|
1391
|
+
})
|
|
1392
|
+
return results
|
|
1393
|
+
|
|
1394
|
+
|
|
1319
1395
|
def handle_cli_setup() -> int:
|
|
1320
1396
|
"""Handle `firmwareloop setup` command: simultaneously configure Antigravity CLI, Claude Code CLI, and Qoder IDE."""
|
|
1321
1397
|
print("============================================================")
|
|
1322
|
-
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.0.
|
|
1398
|
+
print(" FirmwareLoop Multi-Agent Global Setup Helper (v0.0.13)")
|
|
1323
1399
|
print("============================================================")
|
|
1324
1400
|
print("")
|
|
1325
1401
|
|
|
@@ -1372,22 +1448,24 @@ def handle_cli_setup() -> int:
|
|
|
1372
1448
|
except Exception as e:
|
|
1373
1449
|
print(f" - Global MCP Config: [Warning] Could not update .claude.json: {e}")
|
|
1374
1450
|
|
|
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}")
|
|
1451
|
+
# 3. Qoder / Qoder CN user-level MCP
|
|
1452
|
+
# IDE settings read ~/.qoder/mcp.json and ~/.qoder-cn/mcp.json.
|
|
1453
|
+
# qoder.cmd --add-mcp writes the VS Code-style %APPDATA%\Qoder\User\mcp.json, which the settings page does not show.
|
|
1454
|
+
print("\n[3] Qoder IDE / Qoder CN:")
|
|
1455
|
+
qoder_results = configure_qoder_user_mcp(user_home)
|
|
1456
|
+
qoder_ok = [item for item in qoder_results if item.get("ok")]
|
|
1457
|
+
if qoder_ok:
|
|
1458
|
+
for item in qoder_ok:
|
|
1459
|
+
print(f" - {item['edition']}: [OK] Written to {item['path']}")
|
|
1460
|
+
configured_agents.append("Qoder")
|
|
1389
1461
|
else:
|
|
1390
|
-
print(" - User
|
|
1462
|
+
print(" - User MCP: neither ~/.qoder nor ~/.qoder-cn found (Workspace .mcp.json still supported)")
|
|
1463
|
+
for item in qoder_results:
|
|
1464
|
+
if item.get("ok"):
|
|
1465
|
+
continue
|
|
1466
|
+
if item.get("reason") == "not_installed":
|
|
1467
|
+
continue
|
|
1468
|
+
print(f" - {item['edition']}: [Warning] Could not write {item['path']}: {item.get('reason')}")
|
|
1391
1469
|
|
|
1392
1470
|
# 4. Global Skills Sync for Antigravity and Claude Code
|
|
1393
1471
|
ag_skills_dir = os.path.join(user_home, ".gemini", "antigravity-cli", "skills")
|
|
@@ -1422,7 +1500,7 @@ def handle_cli_setup() -> int:
|
|
|
1422
1500
|
def handle_cli_init() -> int:
|
|
1423
1501
|
"""Handle `firmwareloop init` command."""
|
|
1424
1502
|
print("============================================================")
|
|
1425
|
-
print(" FirmwareLoop Multi-Agent Project Initializer (v0.0.
|
|
1503
|
+
print(" FirmwareLoop Multi-Agent Project Initializer (v0.0.13)")
|
|
1426
1504
|
print("============================================================")
|
|
1427
1505
|
cwd = os.getcwd()
|
|
1428
1506
|
print(f"[*] Initializing multi-agent guidelines & bench config in:\n {cwd}\n")
|
|
@@ -1443,7 +1521,7 @@ def handle_cli_init() -> int:
|
|
|
1443
1521
|
|
|
1444
1522
|
|
|
1445
1523
|
def print_cli_help() -> None:
|
|
1446
|
-
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.0.
|
|
1524
|
+
print("""FirmwareLoop (fwloop) — AI Agent Firmware Engineering & Lab Automation Platform (v0.0.13)
|
|
1447
1525
|
|
|
1448
1526
|
Usage:
|
|
1449
1527
|
fwloop [command] (or: firmwareloop [command])
|
|
@@ -1474,7 +1552,7 @@ def main() -> None:
|
|
|
1474
1552
|
elif cmd in ["setup", "register"]:
|
|
1475
1553
|
sys.exit(handle_cli_setup())
|
|
1476
1554
|
elif cmd in ["version", "-v", "--version"]:
|
|
1477
|
-
print("FirmwareLoop v0.0.
|
|
1555
|
+
print("FirmwareLoop v0.0.13")
|
|
1478
1556
|
sys.exit(0)
|
|
1479
1557
|
elif cmd in ["help", "-h", "--help"]:
|
|
1480
1558
|
print_cli_help()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Dual-Tier MCP setup and registration helper (v0.0.
|
|
3
|
+
Dual-Tier MCP setup and registration helper (v0.0.13).
|
|
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
|
|
|
@@ -87,31 +87,69 @@ $report.agents.claude_code = [ordered]@{
|
|
|
87
87
|
)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
# 2. Qoder
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
# 2. Qoder / Qoder CN user-level MCP (~/.qoder/mcp.json, ~/.qoder-cn/mcp.json)
|
|
91
|
+
# The IDE settings page reads these files. qoder.cmd --add-mcp writes
|
|
92
|
+
# %APPDATA%\Qoder\User\mcp.json, which the settings UI does not show.
|
|
93
|
+
function Merge-FwQoderUserMcp {
|
|
94
|
+
param([string]$McpPath)
|
|
95
|
+
$data = [ordered]@{ mcpServers = [ordered]@{} }
|
|
96
|
+
if (Test-Path -LiteralPath $McpPath) {
|
|
97
|
+
try {
|
|
98
|
+
$existing = Get-Content -LiteralPath $McpPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
99
|
+
if ($existing.PSObject.Properties['mcpServers'] -and $existing.mcpServers) {
|
|
100
|
+
$data.mcpServers = [ordered]@{}
|
|
101
|
+
$existing.mcpServers.PSObject.Properties | ForEach-Object {
|
|
102
|
+
$data.mcpServers[$_.Name] = $_.Value
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
$data = [ordered]@{ mcpServers = [ordered]@{} }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
$data.mcpServers['firmwareloop'] = [pscustomobject]@{ command = 'fwloop'; type = 'stdio' }
|
|
110
|
+
$data.mcpServers['agentic-hil'] = [pscustomobject]@{ command = 'agentic-hil'; args = @('mcp-stdio'); type = 'stdio' }
|
|
111
|
+
$parent = Split-Path -Parent $McpPath
|
|
112
|
+
if (-not (Test-Path -LiteralPath $parent)) {
|
|
113
|
+
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
|
114
|
+
}
|
|
115
|
+
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
|
116
|
+
[System.IO.File]::WriteAllText($McpPath, ((ConvertTo-Json -InputObject $data -Depth 10) + [Environment]::NewLine), $utf8NoBom)
|
|
95
117
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
|
|
119
|
+
$qoderEditions = @(
|
|
120
|
+
[ordered]@{ name = 'Qoder'; dirName = '.qoder' },
|
|
121
|
+
[ordered]@{ name = 'Qoder CN'; dirName = '.qoder-cn' }
|
|
122
|
+
)
|
|
123
|
+
$qoderWritten = @()
|
|
124
|
+
$qoderResults = @()
|
|
125
|
+
foreach ($edition in $qoderEditions) {
|
|
126
|
+
$editionDir = Join-Path $env:USERPROFILE $edition.dirName
|
|
127
|
+
$mcpFile = Join-Path $editionDir 'mcp.json'
|
|
128
|
+
$entry = [ordered]@{
|
|
129
|
+
name = $edition.name
|
|
130
|
+
path = $mcpFile
|
|
131
|
+
detected = (Test-Path -LiteralPath $editionDir)
|
|
132
|
+
configured = $false
|
|
133
|
+
}
|
|
134
|
+
if ($entry.detected) {
|
|
135
|
+
try {
|
|
136
|
+
Merge-FwQoderUserMcp -McpPath $mcpFile
|
|
137
|
+
$entry.configured = $true
|
|
138
|
+
$qoderWritten += $mcpFile
|
|
139
|
+
} catch {
|
|
140
|
+
$entry.error = $_.Exception.Message
|
|
141
|
+
}
|
|
106
142
|
}
|
|
143
|
+
$qoderResults += [pscustomobject]$entry
|
|
107
144
|
}
|
|
145
|
+
$qoderConfigured = ($qoderWritten.Count -gt 0)
|
|
108
146
|
$report.agents.qoder = [ordered]@{
|
|
109
|
-
detected = ($
|
|
110
|
-
path = if ($qoderCmd) { $qoderCmd.Source } else { $null }
|
|
147
|
+
detected = ($qoderResults | Where-Object { $_.detected } | Measure-Object).Count -gt 0
|
|
111
148
|
user_profile_configured = $qoderConfigured
|
|
149
|
+
written_files = $qoderWritten
|
|
150
|
+
editions = $qoderResults
|
|
112
151
|
registration_commands = @(
|
|
113
|
-
"qoder.
|
|
114
|
-
"qoder.cmd --add-mcp '{\`"name\`":\`"agentic-hil\`",\`"command\`":\`"agentic-hil\`",\`"args\`":[\`"mcp-stdio\`"]}'"
|
|
152
|
+
"fwloop setup # writes ~/.qoder/mcp.json and ~/.qoder-cn/mcp.json"
|
|
115
153
|
)
|
|
116
154
|
}
|
|
117
155
|
|
|
@@ -204,7 +242,7 @@ if ($Json) {
|
|
|
204
242
|
Write-FwJson ([pscustomobject]$report)
|
|
205
243
|
} else {
|
|
206
244
|
Write-Host "============================================================" -ForegroundColor Cyan
|
|
207
|
-
Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.0.
|
|
245
|
+
Write-Host " FirmwareLoop Dual-Tier MCP & Agent Setup Helper (v0.0.13)" -ForegroundColor Green
|
|
208
246
|
Write-Host "============================================================" -ForegroundColor Cyan
|
|
209
247
|
Write-Host ""
|
|
210
248
|
Write-Host "[1] Google Antigravity / Gemini CLI:" -ForegroundColor Yellow
|
|
@@ -221,11 +259,15 @@ if ($Json) {
|
|
|
221
259
|
Write-Host " $($report.agents.claude_code.registration_commands[0])"
|
|
222
260
|
Write-Host " $($report.agents.claude_code.registration_commands[1])"
|
|
223
261
|
Write-Host ""
|
|
224
|
-
Write-Host "[3] Qoder IDE:" -ForegroundColor Yellow
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
262
|
+
Write-Host "[3] Qoder IDE / Qoder CN:" -ForegroundColor Yellow
|
|
263
|
+
foreach ($edition in $qoderResults) {
|
|
264
|
+
if ($edition.configured) {
|
|
265
|
+
Write-Host " - $($edition.name): [OK] Written to $($edition.path)" -ForegroundColor Green
|
|
266
|
+
} elseif ($edition.detected) {
|
|
267
|
+
Write-Host " - $($edition.name): [Warning] Could not write $($edition.path)"
|
|
268
|
+
} else {
|
|
269
|
+
Write-Host " - $($edition.name): not installed ($($edition.path))"
|
|
270
|
+
}
|
|
229
271
|
}
|
|
230
272
|
Write-Host " - Workspace: Project root '.mcp.json' is automatically detected"
|
|
231
273
|
Write-Host ""
|
|
@@ -64,11 +64,20 @@ foreach ($c in $cases) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
# ---- 2. fake executable execution (keil / iar / west / esp-idf) ----------------
|
|
67
|
+
$runningOnWindows = $IsWindows -or ($null -ne $env:OS -and $env:OS -like "*Windows*")
|
|
68
|
+
$ext = if ($runningOnWindows) { '.cmd' } else { '.sh' }
|
|
69
|
+
|
|
70
|
+
if (-not $runningOnWindows) {
|
|
71
|
+
Get-ChildItem -LiteralPath $fake -Filter *.sh | ForEach-Object {
|
|
72
|
+
try { & chmod +x $_.FullName } catch { }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
$execCases = @(
|
|
68
|
-
@{ name = 'keil'; args = @('-Backend', 'keil', '-SourceDir', 'tests/backend/fixtures/keil-proj', '-BackendTool', (Join-Path $fake
|
|
69
|
-
@{ name = 'iar'; args = @('-Backend', 'iar', '-SourceDir', 'tests/backend/fixtures/iar-proj', '-BackendTool', (Join-Path $fake
|
|
70
|
-
@{ name = 'zephyr'; args = @('-Backend', 'zephyr', '-BackendTool', (Join-Path $fake
|
|
71
|
-
@{ name = 'esp-idf'; args = @('-Backend', 'esp-idf', '-BackendTool', (Join-Path $fake
|
|
77
|
+
@{ name = 'keil'; args = @('-Backend', 'keil', '-SourceDir', 'tests/backend/fixtures/keil-proj', '-BackendTool', (Join-Path $fake "UV4$ext"), '-Json') },
|
|
78
|
+
@{ name = 'iar'; args = @('-Backend', 'iar', '-SourceDir', 'tests/backend/fixtures/iar-proj', '-BackendTool', (Join-Path $fake "IarBuild$ext"), '-Json') },
|
|
79
|
+
@{ name = 'zephyr'; args = @('-Backend', 'zephyr', '-BackendTool', (Join-Path $fake "west$ext"), '-Json') },
|
|
80
|
+
@{ name = 'esp-idf'; args = @('-Backend', 'esp-idf', '-BackendTool', (Join-Path $fake "idf.py$ext"), '-Json') }
|
|
72
81
|
)
|
|
73
82
|
foreach ($c in $execCases) {
|
|
74
83
|
$r = Invoke-Build -ToolArgs $c.args
|
|
@@ -80,11 +89,15 @@ foreach ($c in $execCases) {
|
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
# ---- 3. cmake real build still green -------------------------------------------
|
|
83
|
-
$r = Invoke-Build -ToolArgs @('-Configuration', 'Debug', '-Clean', '-Json')
|
|
92
|
+
$r = Invoke-Build -ToolArgs @('-Backend', 'cmake', '-Configuration', 'Debug', '-Clean', '-Json')
|
|
84
93
|
$jsonLine = ($r.stdout -split "`r?`n" | Where-Object { $_.TrimStart().StartsWith('{') } | Select-Object -Last 1)
|
|
85
|
-
$j = $jsonLine | ConvertFrom-Json
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
$j = if ($jsonLine) { try { $jsonLine | ConvertFrom-Json } catch { $null } } else { $null }
|
|
95
|
+
$hasArtifact = $false
|
|
96
|
+
if ($j -and $j.PSObject.Properties['ok'] -and $j.ok -and $j.PSObject.Properties['artifacts'] -and $j.artifacts.PSObject.Properties['primary'] -and $j.artifacts.primary.PSObject.Properties['path']) {
|
|
97
|
+
$hasArtifact = $j.artifacts.primary.path -like '*firmware.elf*'
|
|
98
|
+
}
|
|
99
|
+
Assert -Name 'real.cmake' -Ok ($r.exit_code -eq 0 -and $hasArtifact) `
|
|
100
|
+
-Detail "exit=$($r.exit_code) output=$jsonLine"
|
|
88
101
|
|
|
89
102
|
# ---- summary -------------------------------------------------------------------
|
|
90
103
|
$failed = @($results | Where-Object { -not $_.ok }).Count
|
|
@@ -45,8 +45,9 @@ $env:FW_RUN_DIR = $runsDir
|
|
|
45
45
|
$doctor = Invoke-FwProcess -FilePath 'pwsh' -Arguments @('-NoProfile', '-NonInteractive', '-File', (Join-Path $PSScriptRoot 'doctor.ps1'), '-Json') -WorkingDirectory $repoRoot -TimeoutMs 120000
|
|
46
46
|
if ($doctor.exit_code -in @(0, 1)) {
|
|
47
47
|
try {
|
|
48
|
-
$
|
|
49
|
-
if ($
|
|
48
|
+
$jsonLine = ($doctor.stdout -split "`r?`n" | Where-Object { $_.TrimStart().StartsWith('{') } | Select-Object -Last 1)
|
|
49
|
+
$d = if ($jsonLine) { try { $jsonLine | ConvertFrom-Json } catch { $null } } else { $null }
|
|
50
|
+
if ($d -and $d.PSObject.Properties['checks'] -and $d.checks) {
|
|
50
51
|
$deps = [ordered]@{}
|
|
51
52
|
foreach ($p in $d.checks.PSObject.Properties) {
|
|
52
53
|
$v = $p.Value
|
|
@@ -55,7 +56,7 @@ if ($doctor.exit_code -in @(0, 1)) {
|
|
|
55
56
|
}
|
|
56
57
|
$deps | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $runsDir 'dependencies.json') -Encoding utf8
|
|
57
58
|
}
|
|
58
|
-
if ($d.host) {
|
|
59
|
+
if ($d -and $d.PSObject.Properties['host'] -and $d.host) {
|
|
59
60
|
$d.host | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $runsDir 'environment.json') -Encoding utf8
|
|
60
61
|
}
|
|
61
62
|
} catch {
|
|
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
|