
Embed Ai Tool
- 122 installs
- 798 repo stars
- Updated July 12, 2026
- leokemp223/embed-ai-tool
embed-ai-tool is an agent skill that routes embedded build, flash, and debug requests to the correct toolchain skill among 24 options—usable whenever a solo builder issues a vague MCU command before committin
About
embed-ai-tool is the umbrella skill for an embedded firmware toolkit aimed at solo builders bringing up MCUs with Keil, IAR, CMake, Makefile, PlatformIO, or ESP-IDF. Instead of guessing which flash or debug skill applies, the agent presents categorized options—build, burn, debug, serial, fieldbus, lab instruments—and waits for an explicit pick. Installation is equally deliberate: users choose full install of all 24 skills or a named subset, which prevents bloating agent context with unused probe drivers. Once routed, specialized skills handle OpenOCD GDB, J-Link RTT, FreeRTOS-aware threads, Modbus/CAN/VISA benches, and memory map analysis. The skill is Chinese-forward in user prompts but the architecture is universal for agentic embedded workflows. Treat it as the front door before any single-tool skill runs.
- Meta controller for embed-ai-tool repo: install引导 and instruction disambiguation
- 24 skills across build, flash, debug, comms, and analysis (CMake, Keil, IAR, IDF, OpenOCD, J-Link, GDB, Modbus, CAN, VIS
- Install flow requires user choice: all 24 skills vs on-demand names—no silent full install
- Disambiguation lists category candidates when user says 烧录/编译/调试 without naming a toolchain
- Marked internal metadata skill as repo entry point
Embed Ai Tool by the numbers
- 122 all-time installs (skills.sh)
- +18 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #709 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/leokemp223/embed-ai-tool --skill embed-ai-toolAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 122 |
|---|---|
| repo stars | ★ 798 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 12, 2026 |
| Repository | leokemp223/embed-ai-tool ↗ |
What it does
Route vague embedded commands (flash, compile, debug) to the right skill among 24 MCU toolchain skills and guide install-all vs pick-list setup.
Who is it for?
Best when you use agent-assisted firmware workflows across multiple vendors and debug probes.
Skip if: Pure mobile or web SaaS builds with no on-target toolchain or physical board connected.
When should I use this skill?
User requests installing embed-ai-tool repo skills, or issues vague embedded 烧录/编译/调试 instructions without a specific toolchain.
What you get
You get a categorized candidate list and a guided install path, then hand off to the specific build-, flash-, or debug-* skill that matches your hardware.
- User-selected install set or full 24-skill install commands
- Disambiguation table leading to one concrete child skill invocation
By the numbers
- 24 skills in the embed-ai-tool catalog
- 5 workflow categories: 构建, 烧录, 调试, 通信, 分析
Files
embed-ai-tool 总控
本技能负责两类交互:安装引导 和 指令消歧。
---
一、安装引导
当用户请求安装本仓库的 skill 时,按以下流程引导。不要跳过询问直接安装,也不要替用户决定安装内容——先分析工程,再让用户选。
流程
1. 分析工程 — 探测当前工作区的工程特征(构建系统、工具链、探针、协议线索等),详见"工程分析项" 2. 呈现分析结果 — 把探测到的事实客观列给用户,未识别出的也要明确说明 3. 提供选项 — 基于分析结果给出可选安装方案(推荐集 / 全部 / 按分类 / 自定义),让用户选择,详见"提供的安装选项" 4. 执行安装 — 根据用户选择执行对应命令;用户可在推荐集基础上增删,不必重走流程
工程分析项
按工作区文件特征推断工程画像,规则优先级从高到低:
| 类别 | 文件特征 | 推断结论 | 关联 skill |
|---|---|---|---|
| 构建系统 | *.uvprojx / *.uvproj | Keil MDK | build-keil flash-keil |
| 构建系统 | *.ewp / *.eww | IAR EWARM | build-iar |
| 构建系统 | platformio.ini | PlatformIO | build-platformio flash-platformio debug-platformio |
| 构建系统 | sdkconfig + components/ | ESP-IDF | build-idf flash-idf |
| 构建系统 | CMakeLists.txt + *.cmake | CMake | build-cmake |
| 构建系统 | Makefile(无 CMakeLists.txt) | Makefile | build-makefile |
| 工具链 | PATH 含 arm-none-eabi-gcc | ARM Cortex-M 目标 | — |
| 工具链 | PATH 含 xtensa-esp32-elf-gcc | ESP32 (Xtensa) 目标 | — |
| 工具链 | PATH 含 riscv64-unknown-elf-gcc | RISC-V 目标 | — |
| 调试器 | .vscode/launch.json 含 openocd | OpenOCD | flash-openocd debug-gdb-openocd |
| 调试器 | *.jlink 文件或 JLinkExe 在 PATH | J-Link | flash-jlink debug-jlink |
| 调试器 | openocd.cfg / openocd.cfg.in 存在 | OpenOCD 配置 | flash-openocd debug-gdb-openocd |
| 协议 | 源码含 #include "modbus.h" 或 mb.h | 使用 Modbus | modbus-debug |
| 协议 | 源码含 CAN HAL 驱动引用 | 使用 CAN 总线 | can-debug |
| 协议 | main.c 引用 stdio / UART 重定向 | 使用串口日志 | serial-monitor |
| 协议 | 源码含 viOpen / viWrite | 使用 SCPI 仪器 | visa-debug |
| RTOS | 源码含 FreeRTOS.h / rtthread.h / zephyr.h | 使用 RTOS | rtos-debug |
| 内存 | 工程能产出 .map / .elf | 可做内存分析 | memory-analysis |
探测失败时:如果工作区特征不明显(纯裸 C 文件、混合工程、空目录),直接告知用户"未识别出明确工程类型",建议选"全部安装"或"自定义"——不要硬推推荐集。
推荐集映射
按识别到的工程画像给出基础推荐集(用户可在其上增删):
| 工程类型 | 基础推荐集 |
|---|---|
| Keil MDK | build-keil flash-keil serial-monitor debug-gdb-openocd workflow |
| IAR EWARM | build-iar flash-openocd serial-monitor workflow |
| PlatformIO | build-platformio flash-platformio debug-platformio serial-monitor workflow |
| ESP-IDF | build-idf flash-idf serial-monitor debug-gdb-openocd workflow |
| CMake | build-cmake flash-openocd debug-gdb-openocd serial-monitor workflow |
| Makefile | build-makefile flash-openocd serial-monitor workflow |
基础集是起点,根据"工程分析项"探测到的协议线索继续追加:
- 检测到 RTOS → 追加
rtos-debug - 检测到 Modbus → 追加
modbus-debug - 检测到 CAN → 追加
can-debug - 检测到 VISA / SCPI → 追加
visa-debug - 工程能产
.map/.elf→ 追加memory-analysis
提供的安装选项
分析完成后,向用户呈现 4 个并列选项:
| 选项 | 说明 |
|---|---|
| A. 推荐集 | 基于工程分析得出的最小集(通常 5-8 个),可在此基础上增删 |
| B. 全部安装 | 安装全部 22 个 skill,适合全局工具人 / 教学场景 |
| C. 按分类逐一勾选 | 按 6 个分类顺序逐一询问,每类下用户可全选、跳过或勾选部分(详见"分类逐一勾选流程") |
| D. 自定义 | 用户直接输入 skill 名或编号,自由组合 |
用户选 A 后仍可微调(如"再加个 modbus-debug"),不要重走一遍流程。
分类逐一勾选流程
用户选 C 后,按 6 个分类顺序逐一询问。每个分类独立交互,用户可全选、跳过或勾选部分。基于工程分析预先勾选推荐项,降低决策成本。
分类顺序与内容:
| 序号 | 分类 | 包含 skill(数量) | 默认勾选规则(基于工程分析) |
|---|---|---|---|
| 1 | 构建 | build-cmake / build-keil / build-iar / build-platformio / build-idf / build-makefile(6) | 勾选识别到的构建系统对应项,其余不勾 |
| 2 | 烧录 | flash-keil / flash-openocd / flash-platformio / flash-idf / flash-jlink(5) | 勾选识别到的探针 / 工具链对应项 |
| 3 | 调试 | debug-gdb-openocd / debug-jlink / debug-platformio / rtos-debug(4) | 勾选识别到的调试器对应项;检测到 RTOS 时勾 rtos-debug |
| 4 | 通信 | serial-monitor / modbus-debug / can-debug / visa-debug(4) | 默认勾 serial-monitor;按协议线索勾 modbus / can / visa |
| 5 | 分析 | memory-analysis / static-analysis(2) | 工程能产 .map / .elf 时勾 memory-analysis |
| 6 | 编排 | workflow(1) | 默认勾选 |
单次分类询问格式:
[2/6] 烧录(5 个 skill):
✅ 1. flash-openocd — OpenOCD 烧录(识别到 .vscode/launch.json)
☐ 2. flash-keil — Keil MDK 烧录
☐ 3. flash-jlink — SEGGER J-Link 烧录
☐ 4. flash-platformio — PlatformIO 上传
☐ 5. flash-idf — ESP-IDF 串口烧录
操作:回车确认默认 / 输入编号增删(如 +3 -1)/ "全选" / "跳过"
👤 +3
🤖 [3/6] 调试(4 个 skill):
...完成所有分类后:汇总最终选定的 skill 列表,让用户确认或微调,然后执行安装。
如果用户对某个分类无任何勾选且工程分析也没识别到,应主动确认"跳过 X 分类?"避免遗漏关键能力(比如构建和烧录通常必须)。
安装示例
👤 帮我安装 embed-ai-tool 的 skill
🤖 正在分析当前工程...
检测到工程特征:
• 构建系统:CMake(CMakeLists.txt)
• 目标架构:ARM Cortex-M(PATH 含 arm-none-eabi-gcc)
• 调试器配置:OpenOCD + ST-Link(.vscode/launch.json)
• 协议线索:UART stdio 重定向(main.c)
• RTOS:未检测到
可选安装方案:
A. 推荐集(6 个)— build-cmake / flash-openocd / debug-gdb-openocd / serial-monitor / memory-analysis / workflow
B. 全部安装(22 个)
C. 按分类逐一勾选(6 个分类顺序询问)
D. 自定义(输入 skill 名或编号)
请选择(A / B / C / D):
👤 A,再加个 rtos-debug
🤖 安装:build-cmake / flash-openocd / debug-gdb-openocd / serial-monitor / memory-analysis / workflow / rtos-debug
执行中...可用技能列表
完整技能清单,用于"按分类选(C)"和"自定义(D)"时参考。推荐集(A)的子集由"工程分析项"和"推荐集映射"自动推导。
| 分类 | 技能 | 说明 |
|---|---|---|
| 构建 | build-cmake | 配置并构建基于 CMake 的 MCU 固件工程 |
| 构建 | build-keil | 配置并构建基于 Keil MDK 的固件工程 |
| 构建 | build-iar | 配置并构建基于 IAR EWARM 的固件工程 |
| 构建 | build-platformio | 配置并构建基于 PlatformIO 的固件工程 |
| 构建 | build-idf | 配置目标芯片并构建 ESP-IDF 固件工程 |
| 构建 | build-makefile | 配置并构建基于 Makefile 的固件工程 |
| 烧录 | flash-keil | 通过 Keil MDK 内置调试器烧录固件 |
| 烧录 | flash-openocd | 通过 OpenOCD 烧录 ELF/HEX/BIN 产物 |
| 烧录 | flash-platformio | 通过 PlatformIO 上传机制烧录固件 |
| 烧录 | flash-idf | 通过 ESP-IDF 工具链烧录固件并支持 JTAG 调试 |
| 烧录 | flash-jlink | 通过 SEGGER J-Link 烧录固件,支持 RTT 日志捕获 |
| 调试 | debug-gdb-openocd | 通过 OpenOCD 附着 GDB 调试 |
| 调试 | debug-jlink | 通过 J-Link GDB Server 在线调试和崩溃分析 |
| 调试 | debug-platformio | 通过 PlatformIO 内置 GDB 调试 |
| 调试 | rtos-debug | FreeRTOS/RT-Thread/Zephyr 线程感知调试 |
| 通信 | serial-monitor | 串口选择与运行日志抓取 |
| 通信 | modbus-debug | Modbus RTU/TCP 寄存器读写与从站扫描 |
| 通信 | can-debug | CAN 总线帧监听、发送和节点扫描 |
| 通信 | visa-debug | VISA 仪器 SCPI 通信、波形捕获和截图 |
| 分析 | memory-analysis | .map/ELF 内存使用报告与符号排名 |
| 分析 | static-analysis | cppcheck/clang-tidy 静态分析,MISRA-C 合规 |
| 编排 | workflow | 串联编译+烧录+监控/调试的流水线 |
安装命令
优先使用 npx skills,若用户无 Node.js 环境改用 Python 脚本。
# npx 全部安装
npx skills add LeoKemp223/embed-ai-tool -g -y
# npx 按需安装
npx skills add LeoKemp223/embed-ai-tool --skill build-cmake --skill flash-openocd -g -y
# Python 全部安装
python3 /tmp/embed-ai-tool/scripts/install.py /path/to/project
# Python 按需安装
python3 /tmp/embed-ai-tool/scripts/install.py /path/to/project --skills build-cmake flash-openocd---
二、指令消歧
当用户发出模糊指令(如"烧录"、"编译"、"调试")时,先尝试自动探测工程类型;若无法明确判断,必须列出候选 skill 让用户选择,不要自行假设。
消歧流程
用户输入模糊指令
│
▼
自动探测工程类型
│
├─ 唯一匹配 → 直接调用对应 skill
│
└─ 匹配多个或无法判断 → 列出候选 skill 供用户选择自动探测规则
按工作区文件特征判断工程类型,规则优先级从高到低:
| 文件特征 | 工程类型 | 对应 skill |
|---|---|---|
*.uvprojx / *.uvproj | Keil MDK | build-keil flash-keil |
platformio.ini | PlatformIO | build-platformio flash-platformio debug-platformio |
sdkconfig + components/ | ESP-IDF | build-idf flash-idf |
CMakeLists.txt + *.cmake | CMake | build-cmake |
Makefile / makefile(无 CMakeLists.txt) | Makefile | build-makefile |
.jlink 文件或 JLinkExe 在 PATH | J-Link | flash-jlink debug-jlink |
.vscode/launch.json 含 openocd | OpenOCD | flash-openocd debug-gdb-openocd |
| 以上均无 | 未知 | 必须询问用户 |
分类候选表
当自动探测无法唯一确定时,按用户指令所属分类展示候选 skill:
编译 / 构建:
| 技能 | 适用场景 |
|---|---|
build-keil | Keil MDK 工程(.uvprojx) |
build-cmake | CMake 工程(CMakeLists.txt) |
build-iar | IAR EWARM 工程(.ewp) |
build-platformio | PlatformIO 工程(platformio.ini) |
build-idf | ESP-IDF 工程(sdkconfig) |
build-makefile | 裸 Makefile 工程 |
烧录 / 下载:
| 技能 | 适用场景 |
|---|---|
flash-keil | Keil 工程 + 内置调试器(ST-Link / J-Link / CMSIS-DAP) |
flash-openocd | OpenOCD 兼容探针(ST-Link / CMSIS-DAP / DAPLink) |
flash-jlink | SEGGER J-Link 探针 |
flash-platformio | PlatformIO 上传(串口 / JTAG / DFU) |
flash-idf | ESP-IDF 工具链(ESP32 系列串口烧录) |
调试:
| 技能 | 适用场景 |
|---|---|
debug-gdb-openocd | OpenOCD + GDB 调试 |
debug-jlink | J-Link GDB Server 调试 |
debug-platformio | PlatformIO 内置 GDB |
rtos-debug | RTOS 线程感知调试(FreeRTOS / RT-Thread / Zephyr) |
通信 / 监控:
| 技能 | 适用场景 |
|---|---|
serial-monitor | 串口日志抓取 |
modbus-debug | Modbus RTU/TCP 通信 |
can-debug | CAN / CAN-FD 总线 |
visa-debug | SCPI 仪器通信 |
示例交互
👤 烧录
🤖 当前工作区未检测到明确的烧录工具配置,请选择:
1. flash-keil — Keil MDK 内置调试器烧录
2. flash-openocd — OpenOCD 烧录(ST-Link / CMSIS-DAP)
3. flash-jlink — SEGGER J-Link 烧录
4. flash-platformio — PlatformIO 上传
5. flash-idf — ESP-IDF 串口烧录
请输入编号或 skill 名称:
👤 2
🤖 使用 flash-openocd,正在探测探针和固件产物...---
安装后提示
安装完成后,告知用户:
- 已安装的 skill 列表
- 使用
/skill-name调用具体 skill,例如/build-cmake、/serial-monitor - 用自然语言描述需求即可触发对应 skill,例如"编译烧录"、"看串口"
- 管理命令:
npx skills ls -g(查看)、npx skills update -g(更新)、npx skills remove -g(移除)
{
"permissions": {
"allow": [
"Bash(python *)",
"Bash(git *)",
"Bash(cd *)",
"Bash(ls *)",
"Bash(cat *)",
"Bash(head *)",
"Bash(mkdir *)",
"Bash(echo *)"
]
}
}
__pycache__/
.pytest_cache/
*.pyc
*.pyo
.DS_Store
Thumbs.db
.em_skill.json
.em_skill_meta.json
贡献指南
范围
这个仓库存放面向嵌入式固件开发的 Codex skill 及共享参考资料。新的提交应当改善以下至少一项:
- 可复用的嵌入式工作流
- 共享约定或决策规则
- 被多个 skill 复用的参考文档
- 用于保证仓库一致性的校验工具
技能命名
- 仅使用小写字母、数字和连字符。
- 优先选择动作导向的名字,例如
flash-openocd或fault-triage。 - 目录名必须与 frontmatter 中的 skill
name保持一致。
技能结构
每个 skill 必须包含一个 SKILL.md,并带有以下章节:
## 适用场景## 必要输入## 自动探测## 执行步骤## 失败分流## 平台说明## 输出约定## 交接关系
不要从空白文档开始写,直接复用 templates/skill-template/SKILL.md。 在认定一个 skill 已经完成前,请用 templates/skill-template/SCENARIOS.md 定义最小覆盖范围。
内容准则
- 共享策略应写入
shared/,不要只写在某个单独 skill 中。 - 示例保持简短且具体。
- 当一个 skill 可能在多个配置或工具之间做选择时,必须写清优先级规则。
- 只有在默认值足够安全时才允许定义默认行为,否则应阻塞并要求澄清。
- Windows、macOS、Linux 的路径差异应作为共享参考资料维护,而不是散落在各 skill 内。
校验
提交前请先执行:
python3 scripts/validate_repo.py如果你新增了必需章节或文件,也要在同一提交中同步更新校验器。
 
Fully Automated AI-Driven Hardware Product Development, Debugging, and Testing
A skill set for AI coding assistants, providing LLMs with full-lifecycle MCU firmware development capabilities. Covers multi-toolchain builds (Keil / IAR / CMake / PlatformIO), flashing, GDB debugging, serial monitoring, Modbus / CAN / VISA protocol debugging, and pipeline orchestration — supporting Linux, macOS, and Windows.
<img width="1922" height="1091" alt="image" src="https://github.com/user-attachments/assets/6b23bfb1-8755-4f28-b510-abb7cc80d18f" />
⚠ AI Assistant Install Protocol (Required Reading)
When a user requests "install embed-ai-tool skills", AI assistants (Claude Code / Cursor / Codex, etc.) MUST follow this flow. Never blindly copy all skills.
Three Prohibitions
1. Do NOT run install.py for full install without explicit user confirmation — new default is analyze-only; requires --skills X Y or --yes to write files 2. Do NOT use cp -r / xcopy / Copy-Item / file explorer to bulk-copy the skills/ directory into the target project — this pollutes .claude/skills/ 3. Do NOT bypass install.py with other tools (PowerShell, FileExplorer, Robocopy, etc.) to achieve equivalent bulk copy
Required Flow
1. Analyze project — In the target project, run python3 scripts/install.py /path/to/project (no install args). The script prints project-type detection + recommended set, writes nothing 2. Present to user — Show analysis result + 4 options:
- A. Recommended set (based on project analysis, typically 5-7 skills)
- B. Install all (22 skills,
--yes) - C. Category-by-category (6 categories asked in sequence)
- D. Custom (enter skill names directly)
3. Wait for user choice — Only after explicit selection, run --skills X Y or --yes 4. Report — List installed skills when done
Why
The 22 skills cover multiple toolchains (Keil / IAR / ESP-IDF / PlatformIO / CMake / Makefile). A single project typically uses only 4-7 of them. Bulk install pollutes .claude/skills/, adds noise to the Claude skill list, and affects project collaborators.
---
One-Click Install
In any LLM chat that supports skills, enter:
Install skills from https://github.com/LeoKemp223/embed-ai-tool.gitThe AI assistant will first analyze your project type (build system, debugger, protocol hints), then let you choose from 4 options:
- A. Recommended set — derived from project analysis, typically 5-7 skills
- B. Install all — 22 skills, suitable for global tooling
- C. Category-by-category — 6 categories asked in sequence
- D. Custom — enter skill names directly
Installation only runs after your choice, avoiding project directory pollution. See "AI Assistant Install Protocol" above.
npx Install (Recommended)
Requires Node.js 14+. Uses the skills CLI for one-command management, supporting Claude Code, Cursor, Codex, and 50+ AI coding assistants.
Install All Skills
npx skills add LeoKemp223/embed-ai-tool -g -yInstall Specific Skills
npx skills add LeoKemp223/embed-ai-tool --skill build-cmake --skill flash-openocd -g -yManage
npx skills ls -g # List installed
npx skills update -g # Update
npx skills remove -g # Remove-g installs globally (~/.claude/skills/). Omit it to install to the current project (.claude/skills/).
Script Installation
Prerequisites
- Python 3.8+ (no third-party dependencies required)
- Git
Step 1: Analyze Project Type (default behavior)
git clone https://github.com/LeoKemp223/embed-ai-tool.git
python3 embed-ai-tool/scripts/install.py /path/to/your-projectThe script prints project characteristics (build system, debugger, protocols) and a recommended skill set. Writes nothing to disk.
Step 2: Install Recommended Set
python3 embed-ai-tool/scripts/install.py /path/to/your-project --skills build-cmake flash-openocd debug-gdb-openocd serial-monitor workflowOr Install All (when you're sure you want all 22)
python3 embed-ai-tool/scripts/install.py /path/to/your-project --yes⚠--yescopies all 22 skills into the target project's.claude/skills/. Only recommended for global installs or tooling scenarios.
Install Specific Skills
python3 embed-ai-tool/scripts/install.py /path/to/your-project --skills build-cmake flash-openocd serial-monitorUpdate Installed Skills
cd embed-ai-tool && git pull
python3 scripts/install.py /path/to/your-project --forceAuto-Detect Tool Paths
Append --detect during installation to automatically scan PATH for embedded tools and write them to the workspace config:
python3 embed-ai-tool/scripts/install.py /path/to/your-project --detectCheck Installation Status
python3 embed-ai-tool/scripts/install.py /path/to/your-project --statusUninstall
python3 embed-ai-tool/scripts/install.py /path/to/your-project --uninstallList Available Skills
python3 embed-ai-tool/scripts/install.py --listManual Tool Path Configuration
Some skills depend on external tools (OpenOCD, Keil, arm-none-eabi-gcc, etc.). In addition to --detect, you can manually configure them:
# Set tool path (workspace level)
python3 scripts/em_config.py set openocd /usr/bin/openocd
# Set global tool path
python3 scripts/em_config.py set uv4 "C:\Keil_v5\UV4\UV4.exe" --global
# View configured tools
python3 scripts/em_config.py list
# View config file location
python3 scripts/em_config.py pathSkill List
| Skill | Description |
|---|---|
build-cmake | Configure and build CMake-based MCU firmware projects |
build-keil | Configure and build Keil MDK firmware projects |
build-iar | Configure and build IAR EWARM firmware projects |
build-platformio | Configure and build PlatformIO firmware projects |
build-makefile | Configure and build bare Makefile embedded projects |
flash-keil | Flash firmware via Keil MDK built-in debugger |
flash-openocd | Flash ELF/HEX/BIN artifacts via OpenOCD |
flash-platformio | Flash firmware via PlatformIO upload mechanism |
flash-idf | Flash firmware via ESP-IDF toolchain with JTAG debug support |
flash-jlink | Flash firmware via SEGGER J-Link with RTT log capture |
debug-gdb-openocd | Attach GDB via OpenOCD — supports post-flash debug, attach-only, and crash triage |
debug-jlink | On-chip debugging and crash analysis via J-Link GDB Server |
debug-platformio | Debug via PlatformIO built-in GDB |
serial-monitor | Select serial port and capture runtime logs |
modbus-debug | Modbus RTU/TCP register read/write, slave scanning, and continuous monitoring |
can-debug | CAN bus frame monitoring, sending, and node scanning |
visa-debug | VISA instrument SCPI communication, waveform capture, and screenshots |
workflow | Pipeline orchestration chaining multiple skills (build + flash + monitor/debug) |
build-idf | Configure target chip and build ESP-IDF firmware projects |
memory-analysis | Parse .map files or ELF to generate memory usage reports and symbol size rankings |
rtos-debug | FreeRTOS/RT-Thread/Zephyr thread-aware debugging, stack watermark, and deadlock detection |
static-analysis | cppcheck/clang-tidy/GCC analyzer static analysis with MISRA-C compliance |
LLM Usage Examples
After installing skills, trigger them with natural language or direct skill commands in your chat.
Natural Language
👤 Build and flash
🤖 Detected Keil project Project.uvprojx, target chip STM32F103C8.
✅ Build succeeded, artifact: build/Project.axf (48.2 KB)
Detected ST-Link V2, flashing...
✅ Flash succeeded, verified and reset.
👤 Check serial output
🤖 Detected COM3 (115200), capturing logs:
[00:00.000] System Start
[00:00.012] UART initialized
👤 Debug UART
🤖 Breakpoint set at HAL_UART_Transmit entry.
Breakpoint hit: huart->Init.BaudRate = 9600, expected 115200.
Suggest checking the baud rate setting in UART initialization code.
Skill Commands
# Keil project: build + flash + serial
/build-keil
/flash-keil
/serial-monitor
# CMake project: build + flash + debug
/build-cmake
/flash-openocd
/debug-gdb-openocd
# ESP-IDF project: build + flash
/build-idf
/flash-idf
# One-click pipeline (build → flash → monitor)
/workflowRepository Structure
.
├── skills/ # Skill modules
│ ├── build-cmake/ # CMake build
│ ├── build-keil/ # Keil build
│ ├── build-iar/ # IAR build
│ ├── build-platformio/ # PlatformIO build
│ ├── flash-keil/ # Keil flash
│ ├── flash-openocd/ # OpenOCD flash
│ ├── flash-platformio/ # PlatformIO flash
│ ├── debug-gdb-openocd/ # GDB debug
│ ├── debug-platformio/ # PlatformIO debug
│ ├── serial-monitor/ # Serial monitor
│ ├── modbus-debug/ # Modbus debug
│ ├── can-debug/ # CAN bus debug
│ ├── visa-debug/ # VISA instrument debug
│ ├── workflow/ # Pipeline orchestration
│ ├── build-idf/ # ESP-IDF build
│ ├── flash-idf/ # ESP-IDF flash
│ ├── flash-jlink/ # J-Link flash
│ ├── debug-jlink/ # J-Link GDB debug
│ ├── memory-analysis/ # Firmware memory analysis
│ ├── rtos-debug/ # RTOS debug
│ └── static-analysis/ # Static analysis
├── shared/ # Shared conventions
│ ├── contracts.md # Context handoff contracts
│ ├── failure-taxonomy.md # Failure taxonomy
│ ├── platform-compatibility.md
│ ├── project_detect.py # Unified project detection module
│ └── references/
├── templates/ # Skill templates
│ └── skill-template/
└── scripts/
├── install.py # Install / uninstall / status check
├── validate_repo.py # Structure validation
└── em_config.py # Tool path config CLI<img width="2955" height="1955" alt="PixPin_2026-04-26_22-31-41" src="https://github.com/user-attachments/assets/e62e3118-929e-494c-8d24-c9dcebec22c3" />
Shared Conventions
All skills share a common set of core context for input and output:
- Project Profile — Standardized metadata for workspace, target, build system, debug probe, and artifacts
- Skill Handoff Contract — Context that downstream skills can directly inherit
- Command Outcome Schema — Unified format for success, failure, or blocked results
- Failure Taxonomy — Standard failure classification with recommended follow-up actions
See shared/contracts.md and shared/failure-taxonomy.md.
Validation
After modifications, run structure validation:
python3 scripts/validate_repo.pyThe validator checks that all skills have the required files, frontmatter, and section headings.
Contributing
See CONTRIBUTING.md. Use the templates/skill-template/ template when creating new skills.
Future Extensions
The repository structure is designed for future expansion — for example, flash-pyocd, vendor-tools, fault-triage, trace-analysis — without changes to core conventions.
Thanks to the LinuxDo community for their support! 
 
打通AI开发硬件产品的研发、调试、测试全自动化流程。
面向 AI 编程助手的嵌入式开发技能集,为大模型提供 MCU 固件开发全流程能力。涵盖多工具链构建(Keil / IAR / CMake / PlatformIO)、烧录、GDB 调试、串口监视、Modbus / CAN / VISA 协议调试及流水线编排,支持 Linux、macOS、Windows 三平台。
<img width="1922" height="1091" alt="image" src="https://github.com/user-attachments/assets/6b23bfb1-8755-4f28-b510-abb7cc80d18f" />
⚠ 给 AI 助手的安装协议(必读)
当用户请求"安装 embed-ai-tool 的 skill"时,AI 助手(Claude Code / Cursor / Codex 等)必须遵循以下流程,严禁直接全量复制。
三条禁令
1. 不要未经用户确认直接全量运行 install.py — 新版默认只分析不安装,需 --skills X Y 或 --yes 才会写入文件 2. 不要用 cp -r / xcopy / Copy-Item / 文件管理器全量复制 skills/ 目录到目标工程 — 这会污染 .claude/skills/ 3. 不要绕过 install.py 用其他工具(PowerShell、FileExplorer、Robocopy 等)做等效全量复制
必须执行的流程
1. 分析工程 — 在目标工程下运行 python3 scripts/install.py /path/to/project(不带安装参数),脚本输出工程类型探测结果 + 推荐集,不会写入任何文件 2. 呈现给用户 — 把分析结果 + 4 个选项展示给用户:
- A. 推荐集(基于工程分析,通常 5-7 个)
- B. 全部安装(22 个,
--yes) - C. 按分类逐一勾选(6 个分类顺序询问)
- D. 自定义(直接输入 skill 名)
3. 等待用户选择 — 用户明确选择后,再用 --skills X Y 或 --yes 执行实际安装 4. 汇报结果 — 安装完成后列出已装的 skill 清单
为什么
22 个 skill 涵盖 Keil / IAR / ESP-IDF / PlatformIO / CMake / Makefile 等多种工具链,单个工程通常只用其中 4-7 个。全量安装会污染 .claude/skills/ 目录、增加 Claude skill 列表的噪音、影响工程协作者。
---
一键安装
在任意支持 skill 的大模型对话中输入:
帮我安装 https://github.com/LeoKemp223/embed-ai-tool.git 的 skillAI 助手会先分析你的工程类型(构建系统、调试器、协议线索),然后让你从 4 个方案里选:
- A. 推荐集 — 基于工程分析得出,通常 5-7 个
- B. 全部安装 — 22 个,适合全局工具人
- C. 按分类逐一勾选 — 6 个分类顺序询问
- D. 自定义 — 直接输入 skill 名
你选完后才会执行安装,避免污染工程目录。详见上方的"给 AI 助手的安装协议"。
npx 安装(推荐)
需要 Node.js 14+。使用 skills CLI 一键管理,支持 Claude Code、Cursor、Codex 等 50+ AI 编码助手。
安装全部 skill
npx skills add LeoKemp223/embed-ai-tool -g -y安装指定 skill
npx skills add LeoKemp223/embed-ai-tool --skill build-cmake --skill flash-openocd -g -y管理
npx skills ls -g # 查看已安装
npx skills update -g # 更新
npx skills remove -g # 移除-g 表示全局安装(~/.claude/skills/),去掉则安装到当前项目(.claude/skills/)。
脚本安装
前置条件
- Python 3.8+(无需第三方依赖)
- Git
第一步:分析工程类型(默认行为)
git clone https://github.com/LeoKemp223/embed-ai-tool.git
python3 embed-ai-tool/scripts/install.py /path/to/your-project脚本会输出工程特征(构建系统、调试器、协议)和推荐安装的 skill 集,不会写入任何文件。
第二步:按推荐集安装
python3 embed-ai-tool/scripts/install.py /path/to/your-project --skills build-cmake flash-openocd debug-gdb-openocd serial-monitor workflow或全量安装(确认要装全部 22 个时)
python3 embed-ai-tool/scripts/install.py /path/to/your-project --yes⚠--yes会复制全部 22 个 skill 到目标工程的.claude/skills/,仅推荐用于全局安装或工具人场景。
安装指定 skill
python3 embed-ai-tool/scripts/install.py /path/to/your-project --skills build-cmake flash-openocd serial-monitor更新已安装的 skill
cd embed-ai-tool && git pull
python3 scripts/install.py /path/to/your-project --force自动探测工具路径
安装时附加 --detect,自动扫描 PATH 中的嵌入式工具并写入工作区配置:
python3 embed-ai-tool/scripts/install.py /path/to/your-project --detect查看安装状态
python3 embed-ai-tool/scripts/install.py /path/to/your-project --status卸载
python3 embed-ai-tool/scripts/install.py /path/to/your-project --uninstall列出可用 skill
python3 embed-ai-tool/scripts/install.py --list手动工具路径配置
部分 skill 依赖外部工具(OpenOCD、Keil、arm-none-eabi-gcc 等),除 --detect 外也可手动配置:
# 设置工具路径(工作区级别)
python3 scripts/em_config.py set openocd /usr/bin/openocd
# 设置全局工具路径
python3 scripts/em_config.py set uv4 "C:\Keil_v5\UV4\UV4.exe" --global
# 查看已配置的工具
python3 scripts/em_config.py list
# 查看配置文件位置
python3 scripts/em_config.py path技能列表
| 技能 | 说明 |
|---|---|
build-cmake | 配置并构建基于 CMake 的 MCU 固件工程 |
build-keil | 配置并构建基于 Keil MDK 的固件工程 |
build-iar | 配置并构建基于 IAR EWARM 的固件工程 |
build-platformio | 配置并构建基于 PlatformIO 的固件工程 |
flash-keil | 通过 Keil MDK 内置调试器烧录固件 |
flash-openocd | 通过 OpenOCD 烧录 ELF/HEX/BIN 产物 |
flash-platformio | 通过 PlatformIO 上传机制烧录固件 |
debug-gdb-openocd | 通过 OpenOCD 附着 GDB,支持下载后调试、仅附着和崩溃现场排查 |
debug-platformio | 通过 PlatformIO 内置 GDB 调试 |
serial-monitor | 选择串口并抓取运行日志 |
modbus-debug | Modbus RTU/TCP 寄存器读写、从站扫描和持续监控 |
can-debug | CAN 总线帧监听、发送和节点扫描 |
visa-debug | VISA 仪器 SCPI 通信、波形捕获和截图 |
workflow | 串联多个 skill 的流水线编排(编译+烧录+监控/调试) |
build-idf | 配置目标芯片并构建 ESP-IDF 固件工程 |
flash-idf | 通过 ESP-IDF 工具链烧录固件并支持 JTAG 调试 |
flash-jlink | 通过 SEGGER J-Link 烧录固件,支持 RTT 日志捕获 |
debug-jlink | 通过 J-Link GDB Server 进行固件在线调试和崩溃分析 |
memory-analysis | 解析 .map 文件或 ELF,生成内存使用报告和符号大小排名 |
rtos-debug | FreeRTOS/RT-Thread/Zephyr 线程感知调试,栈水位和死锁检测 |
static-analysis | cppcheck/clang-tidy/GCC analyzer 静态分析,MISRA-C 合规 |
LLM 使用示例
安装 skill 后,在对话中用自然语言即可触发,也可以直接调用 skill 命令。
自然语言
👤 编译烧录
🤖 检测到 Keil 工程 Project.uvprojx,目标芯片 STM32F103C8。
✅ 编译成功,产物: build/Project.axf (48.2 KB)
检测到 ST-Link V2,烧录中...
✅ 烧录成功,已校验并复位。
👤 看串口
🤖 检测到 COM3 (115200),抓取日志:
[00:00.000] System Start
[00:00.012] UART initialized
👤 调试下 UART
🤖 已在 HAL_UART_Transmit 入口设置断点。
断点命中: huart->Init.BaudRate = 9600,与预期 115200 不符。
建议检查 UART 初始化代码中的波特率设置。
Skill 命令
# Keil 工程:编译 + 烧录 + 串口
/build-keil
/flash-keil
/serial-monitor
# CMake 工程:编译 + 烧录 + 调试
/build-cmake
/flash-openocd
/debug-gdb-openocd
# ESP-IDF 工程:编译 + 烧录
/build-idf
/flash-idf
# 一键流水线(编译 → 烧录 → 监控)
/workflow仓库结构
.
├── skills/ # 技能模块
│ ├── build-cmake/ # CMake 构建
│ ├── build-keil/ # Keil 构建
│ ├── build-iar/ # IAR 构建
│ ├── build-platformio/ # PlatformIO 构建
│ ├── flash-keil/ # Keil 烧录
│ ├── flash-openocd/ # OpenOCD 烧录
│ ├── flash-platformio/ # PlatformIO 烧录
│ ├── debug-gdb-openocd/ # GDB 调试
│ ├── debug-platformio/ # PlatformIO 调试
│ ├── serial-monitor/ # 串口监视
│ ├── modbus-debug/ # Modbus 调试
│ ├── can-debug/ # CAN 总线调试
│ ├── visa-debug/ # VISA 仪器调试
│ ├── workflow/ # 流水线编排
│ ├── build-idf/ # ESP-IDF 构建
│ ├── flash-idf/ # ESP-IDF 烧录
│ ├── flash-jlink/ # J-Link 烧录
│ ├── debug-jlink/ # J-Link GDB 调试
│ ├── memory-analysis/ # 固件内存分析
│ ├── rtos-debug/ # RTOS 调试
│ └── static-analysis/ # 静态分析
├── shared/ # 共享约定
│ ├── contracts.md # 上下文交接合约
│ ├── failure-taxonomy.md # 失败分类
│ ├── platform-compatibility.md
│ ├── project_detect.py # 统一项目探测模块
│ └── references/
├── templates/ # Skill 模板
│ └── skill-template/
└── scripts/
├── install.py # 安装 / 卸载 / 状态检查
├── validate_repo.py # 结构校验
└── em_config.py # 工具路径配置 CLI<img width="2955" height="1955" alt="PixPin_2026-04-26_22-31-41" src="https://github.com/user-attachments/assets/e62e3118-929e-494c-8d24-c9dcebec22c3" />
共享约定
所有 skill 围绕同一套核心上下文进行输入与输出:
- Project Profile — 工作区、目标、构建系统、探针和产物的标准化元数据
- Skill Handoff Contract — 下游 skill 可直接继承的上下文
- Command Outcome Schema — 成功、失败或阻塞结果的统一格式
- Failure Taxonomy — 标准失败分类及推荐后续动作
详见 shared/contracts.md 和 shared/failure-taxonomy.md。
校验
修改后执行结构校验:
python3 scripts/validate_repo.py校验器会检查所有 skill 必需文件、frontmatter 和章节标题是否齐全。
贡献
参见 CONTRIBUTING.md。新 skill 请基于 templates/skill-template/ 模板创建。
后续扩展
仓库结构已为后续扩展预留空间,例如 flash-pyocd、vendor-tools、fault-triage、trace-analysis,无需改动核心约定。
感谢 LinuxDo 社区的支持! 
#!/usr/bin/env python3
"""em_skill 工具路径配置管理 CLI。
子命令:
set <tool> <path> [--global] 保存工具路径
get <tool> 查询工具路径
list 列出所有已配置的工具
remove <tool> [--global] 删除配置项
path 显示配置文件路径
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(_REPO_ROOT / "shared"))
from tool_config import (
get_tool_path,
list_tools,
remove_tool_path,
set_tool_path,
user_config_path,
workspace_config_path,
)
def cmd_set(args: argparse.Namespace) -> int:
tool_path = str(Path(args.path).resolve())
cfg_path = set_tool_path(args.tool, tool_path, global_=args.global_flag)
level = "全局" if args.global_flag else "工作区"
print(f"✅ 已保存 {args.tool} = {tool_path} ({level}: {cfg_path})")
return 0
def cmd_get(args: argparse.Namespace) -> int:
path = get_tool_path(args.tool)
if path:
print(path)
return 0
print(f"❌ 未找到 {args.tool} 的配置")
return 1
def cmd_list(_args: argparse.Namespace) -> int:
tools = list_tools()
if not tools:
print("ℹ️ 暂无已配置的工具")
return 0
print("📋 已配置的工具:")
for name, info in sorted(tools.items()):
print(f" {name} = {info['path']} [{info['source']}]")
return 0
def cmd_remove(args: argparse.Namespace) -> int:
removed = remove_tool_path(args.tool, global_=args.global_flag)
if removed:
level = "全局" if args.global_flag else "工作区"
print(f"✅ 已删除 {args.tool} ({level})")
return 0
print(f"❌ 未找到 {args.tool} 的配置")
return 1
def cmd_path(_args: argparse.Namespace) -> int:
print(f"全局配置: {user_config_path()}")
print(f"工作区配置: {workspace_config_path()}")
return 0
def main() -> int:
parser = argparse.ArgumentParser(
description="em_skill 工具路径配置管理",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s set openocd /usr/bin/openocd
%(prog)s set uv4 "C:\\Keil_v5\\UV4\\UV4.exe" --global
%(prog)s get openocd
%(prog)s list
%(prog)s remove openocd
%(prog)s path
""",
)
sub = parser.add_subparsers(dest="command")
# set
p_set = sub.add_parser("set", help="保存工具路径")
p_set.add_argument("tool", help="工具名称")
p_set.add_argument("path", help="工具路径")
p_set.add_argument("--global", dest="global_flag", action="store_true", help="保存到全局配置")
# get
p_get = sub.add_parser("get", help="查询工具路径")
p_get.add_argument("tool", help="工具名称")
# list
sub.add_parser("list", help="列出所有已配置的工具")
# remove
p_rm = sub.add_parser("remove", help="删除配置项")
p_rm.add_argument("tool", help="工具名称")
p_rm.add_argument("--global", dest="global_flag", action="store_true", help="从全局配置删除")
# path
sub.add_parser("path", help="显示配置文件路径")
args = parser.parse_args()
if not args.command:
parser.print_help()
return 1
dispatch = {
"set": cmd_set,
"get": cmd_get,
"list": cmd_list,
"remove": cmd_remove,
"path": cmd_path,
}
return dispatch[args.command](args)
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""embed-ai-tool 安装脚本 — 零依赖,仅标准库。
用法:
python3 scripts/install.py /path/to/project # 默认:分析工程,输出推荐集(不安装)
python3 scripts/install.py /path/to/project --skills build-cmake flash-openocd
python3 scripts/install.py /path/to/project --yes # 确认全量安装
python3 scripts/install.py /path/to/project --force # 强制覆盖
python3 scripts/install.py /path/to/project --detect # 安装后探测工具路径
python3 scripts/install.py /path/to/project --uninstall # 卸载
python3 scripts/install.py /path/to/project --status # 查看安装状态
python3 scripts/install.py /path/to/project --analyze # 显式只分析不安装
python3 scripts/install.py --list # 列出可用 skill
"""
from __future__ import annotations
import argparse
import json
import re
import shutil
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
# Windows 中文控制台默认 GBK 编码,强制 stdout/stderr 用 UTF-8 避免 emoji 崩溃
if sys.stdout.encoding and sys.stdout.encoding.lower() != "utf-8":
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, OSError):
pass
REPO_ROOT = Path(__file__).resolve().parent.parent
SKILLS_SRC = REPO_ROOT / "skills"
SHARED_SRC = REPO_ROOT / "shared"
META_FILENAME = ".em_skill_meta.json"
SKIP_PATTERNS = {"__pycache__", ".pyc", ".pyo", ".DS_Store", "Thumbs.db"}
DETECT_TOOLS = [
"cmake",
"ninja",
"make",
"openocd",
"arm-none-eabi-gcc",
"arm-none-eabi-gdb",
"gdb-multiarch",
"platformio",
"pio",
"idf.py",
"JLinkExe",
"JLinkGDBServerCLExe",
"cppcheck",
"clang-tidy",
]
def _should_skip(path: Path) -> bool:
for part in path.parts:
if part in SKIP_PATTERNS or part.endswith((".pyc", ".pyo")):
return True
return False
def _git_short_hash() -> str:
try:
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
cwd=REPO_ROOT,
capture_output=True,
text=True,
)
if result.returncode == 0:
return result.stdout.strip()
except FileNotFoundError:
pass
return "unknown"
def _copy_tree(src: Path, dst: Path, force: bool = False) -> tuple[int, int]:
"""递归拷贝目录,返回 (copied, skipped) 计数。"""
copied = 0
skipped = 0
for item in sorted(src.rglob("*")):
if not item.is_file():
continue
rel = item.relative_to(src)
if _should_skip(rel):
continue
target = dst / rel
if target.exists() and not force:
skipped += 1
continue
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(item, target)
copied += 1
return copied, skipped
def _available_skills() -> list[str]:
if not SKILLS_SRC.is_dir():
return []
return sorted(
d.name
for d in SKILLS_SRC.iterdir()
if d.is_dir() and (d / "SKILL.md").exists()
)
def _read_skill_description(skill_name: str) -> str:
skill_md = SKILLS_SRC / skill_name / "SKILL.md"
if not skill_md.is_file():
return ""
text = skill_md.read_text(encoding="utf-8")
m = re.search(r"^---\s*\n(.*?)\n---", text, re.DOTALL)
if not m:
return ""
for line in m.group(1).splitlines():
line = line.strip()
if line.startswith("description:"):
desc = line[len("description:"):].strip()
return desc.strip("\"'")
return ""
def _skills_dir(project: Path) -> Path:
return project / ".claude" / "skills"
def _meta_path(project: Path) -> Path:
return _skills_dir(project) / META_FILENAME
def _load_meta(project: Path) -> dict:
mp = _meta_path(project)
if mp.is_file():
try:
return json.loads(mp.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
pass
return {}
def _save_meta(project: Path, meta: dict) -> None:
mp = _meta_path(project)
mp.parent.mkdir(parents=True, exist_ok=True)
mp.write_text(
json.dumps(meta, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
# ── Commands ─────────────────────────────────────────────────────────
# 工程分析规则:文件特征 → (标签, 关联 skill 列表)
# 顺序即优先级,构建系统一旦命中就不再检查低优先级规则
ANALYZE_RULES_BUILD = [
(("*.uvprojx", "*.uvproj"), "Keil MDK", ["build-keil", "flash-keil"]),
(("*.ewp", "*.eww"), "IAR EWARM", ["build-iar"]),
(("platformio.ini",), "PlatformIO", ["build-platformio", "flash-platformio", "debug-platformio"]),
(("sdkconfig",), "ESP-IDF", ["build-idf", "flash-idf"]),
(("CMakeLists.txt",), "CMake", ["build-cmake"]),
(("Makefile", "makefile", "GNUmakefile"), "Makefile", ["build-makefile"]),
]
ANALYZE_TOOLS_CHAIN = [
("arm-none-eabi-gcc", "ARM Cortex-M"),
("xtensa-esp32-elf-gcc", "ESP32 (Xtensa)"),
("riscv64-unknown-elf-gcc", "RISC-V"),
]
ANALYZE_DEBUGGERS = [
("JLinkExe", "J-Link", ["flash-jlink", "debug-jlink"]),
]
def _glob_any(project: Path, patterns: tuple[str, ...]) -> bool:
for pat in patterns:
if any(project.glob(pat)):
return True
return False
def cmd_analyze(project: Path) -> None:
"""分析工程类型,输出推荐安装的 skill 集,不执行任何写入。"""
print(f"工程分析:{project}\n")
detected: list[tuple[str, str, list[str]]] = []
# 构建系统(命中即停,避免混合工程误判)
build_hit = False
for patterns, label, skills in ANALYZE_RULES_BUILD:
# ESP-IDF 需要额外检查 components/
if label == "ESP-IDF" and not (project / "components").is_dir():
continue
# Makefile 仅在没有 CMakeLists.txt 时才算
if label == "Makefile" and (project / "CMakeLists.txt").is_file():
continue
if _glob_any(project, patterns):
detected.append(("构建系统", label, skills))
build_hit = True
break
# 调试器
launch_json = project / ".vscode" / "launch.json"
if launch_json.is_file():
try:
text = launch_json.read_text(encoding="utf-8", errors="ignore")
if "openocd" in text.lower():
detected.append(("调试器", "OpenOCD(launch.json)", ["flash-openocd", "debug-gdb-openocd"]))
except OSError:
pass
if (project / "openocd.cfg").is_file() or (project / "openocd.cfg.in").is_file():
detected.append(("调试器", "OpenOCD 配置存在", ["flash-openocd", "debug-gdb-openocd"]))
if _glob_any(project, ("*.jlink",)):
detected.append(("调试器", "J-Link(.jlink 文件)", ["flash-jlink", "debug-jlink"]))
else:
for tool, label in [("JLinkExe", "J-Link")]:
if shutil.which(tool):
detected.append(("调试器", f"{label}(PATH 可用)", ["flash-jlink", "debug-jlink"]))
break
# 工具链
for tool, label in ANALYZE_TOOLS_CHAIN:
if shutil.which(tool):
detected.append(("工具链", label, []))
# 推荐集:默认含 serial-monitor + workflow,叠加探测到的 skill
available = set(_available_skills())
recommended = {"serial-monitor", "workflow"}
for _, _, skills in detected:
recommended.update(skills)
# 协议线索(粗扫 main.c,不递归)
main_candidates = ["main.c", "Src/main.c", "Core/Src/main.c", "src/main.c", "main/main.c"]
for rel in main_candidates:
main_c = project / rel
if main_c.is_file():
try:
text = main_c.read_text(encoding="utf-8", errors="ignore")
if "FreeRTOS.h" in text or "rtthread.h" in text or "zephyr.h" in text:
recommended.add("rtos-debug")
if "modbus" in text.lower() or 'mb.h' in text:
recommended.add("modbus-debug")
if "CAN" in text and ("HAL_CAN" in text or "can_" in text.lower()):
recommended.add("can-debug")
if "viOpen" in text or "viWrite" in text:
recommended.add("visa-debug")
except OSError:
pass
break
# 内存分析(几乎都能产 .map)
recommended.add("memory-analysis")
# 输出
if not detected:
print(" [!] 未识别出明确工程类型(构建系统 / 调试器 / 工具链 均未命中)\n")
else:
print(" 检测到工程特征:")
for category, label, _ in detected:
print(f" • {category}:{label}")
print()
# 过滤掉仓库里不存在的 skill
rec_sorted = sorted(s for s in recommended if s in available)
if not rec_sorted:
print(" 无可推荐的 skill(仓库可能未正确克隆)。")
return
print(f" 推荐安装集({len(rec_sorted)} 个):")
print(f" {' '.join(rec_sorted)}\n")
print(" 下一步操作:")
print(f" 按推荐集安装:python3 scripts/install.py {project} --skills {' '.join(rec_sorted)}")
print(f" 全量安装({len(available)} 个):python3 scripts/install.py {project} --yes")
print(f" 查看全部 skill:python3 scripts/install.py --list")
print(f" 只分析不安装:python3 scripts/install.py {project} --analyze")
def cmd_list() -> None:
skills = _available_skills()
if not skills:
print("未找到可用 skill。")
return
print(f"可用 skill(共 {len(skills)} 个):\n")
max_name = max(len(s) for s in skills)
for s in skills:
desc = _read_skill_description(s)
print(f" {s:<{max_name}} {desc}")
def cmd_install(project: Path, skill_names: list[str] | None, force: bool) -> None:
available = _available_skills()
if not available:
print("错误:未在仓库中找到任何 skill。", file=sys.stderr)
sys.exit(1)
if skill_names:
invalid = [s for s in skill_names if s not in available]
if invalid:
print(f"错误:以下 skill 不存在:{', '.join(invalid)}", file=sys.stderr)
print(f"可用 skill:{', '.join(available)}", file=sys.stderr)
sys.exit(1)
to_install = skill_names
else:
to_install = available
dest = _skills_dir(project)
dest.mkdir(parents=True, exist_ok=True)
total_copied = 0
total_skipped = 0
# 拷贝 skill 目录
for skill in to_install:
src = SKILLS_SRC / skill
dst = dest / skill
c, s = _copy_tree(src, dst, force)
total_copied += c
total_skipped += s
status = "✓" if c > 0 else ("跳过" if s > 0 else "空")
print(f" {status} {skill} ({c} 文件)")
# 拷贝 shared 目录
if SHARED_SRC.is_dir():
c, s = _copy_tree(SHARED_SRC, dest / "shared", force)
total_copied += c
total_skipped += s
print(f" {'✓' if c > 0 else '跳过'} shared ({c} 文件)")
# 写入 meta
meta = _load_meta(project)
existing_skills = set(meta.get("skills", []))
existing_skills.update(to_install)
meta.update(
{
"source": "embed-ai-tool",
"version": _git_short_hash(),
"installed_at": datetime.now(timezone.utc).isoformat(),
"skills": sorted(existing_skills),
}
)
_save_meta(project, meta)
print(f"\n安装完成:{total_copied} 文件已拷贝,{total_skipped} 文件已跳过。")
print(f"目标目录:{dest}")
if total_skipped > 0 and not force:
print("提示:使用 --force 可覆盖已有文件。")
def cmd_uninstall(project: Path) -> None:
dest = _skills_dir(project)
meta = _load_meta(project)
if not meta:
# 没有 meta 文件,尝试列出疑似目录
if dest.is_dir():
dirs = [d.name for d in dest.iterdir() if d.is_dir()]
if dirs:
print("未找到安装记录(.em_skill_meta.json),但发现以下目录:")
for d in sorted(dirs):
print(f" - {d}")
print("请手动确认并删除。")
return
print("未找到安装记录,也没有发现已安装的 skill。")
return
skills = meta.get("skills", [])
removed = 0
for skill in skills:
skill_dir = dest / skill
if skill_dir.is_dir():
shutil.rmtree(skill_dir)
print(f" ✓ 已删除 {skill}")
removed += 1
# 删除 shared
shared_dir = dest / "shared"
if shared_dir.is_dir():
shutil.rmtree(shared_dir)
print(" ✓ 已删除 shared")
# 删除 meta 文件
mp = _meta_path(project)
if mp.is_file():
mp.unlink()
# 如果 skills 目录为空,也删除
if dest.is_dir() and not any(dest.iterdir()):
dest.rmdir()
print(f"\n卸载完成:已删除 {removed} 个 skill。")
def cmd_status(project: Path) -> None:
meta = _load_meta(project)
if not meta:
print("未找到安装记录。该项目可能尚未安装 embed-ai-tool skill。")
return
print("embed-ai-tool 安装状态:\n")
print(f" 版本: {meta.get('version', '未知')}")
print(f" 安装时间: {meta.get('installed_at', '未知')}")
print(f" 来源: {meta.get('source', '未知')}")
skills = meta.get("skills", [])
dest = _skills_dir(project)
print(f"\n 已安装 skill({len(skills)} 个):")
for s in skills:
exists = (dest / s).is_dir()
marker = "✓" if exists else "✗ (目录缺失)"
print(f" {marker} {s}")
# 显示工具路径配置
config_path = project / ".em_skill.json"
if config_path.is_file():
try:
cfg = json.loads(config_path.read_text(encoding="utf-8"))
tools = cfg.get("tools", {})
if tools:
print(f"\n 工具路径配置({len(tools)} 个):")
for name, path in sorted(tools.items()):
print(f" {name}: {path}")
except (json.JSONDecodeError, OSError):
pass
def cmd_detect(project: Path) -> None:
print("探测工具路径...\n")
# 复用 shared/tool_config.py 的逻辑
sys.path.insert(0, str(SHARED_SRC))
try:
from tool_config import set_tool_path
except ImportError:
# 回退:直接写 .em_skill.json
set_tool_path = None
found = {}
for tool in DETECT_TOOLS:
path = shutil.which(tool)
if path:
found[tool] = path
print(f" ✓ {tool}: {path}")
else:
print(f" ✗ {tool}: 未找到")
if not found:
print("\n未找到任何工具,请确认工具已安装并在 PATH 中。")
return
# 写入配置
if set_tool_path:
for tool, path in found.items():
set_tool_path(tool, path, workspace=project)
else:
config_path = project / ".em_skill.json"
cfg = {}
if config_path.is_file():
try:
cfg = json.loads(config_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
pass
tools = cfg.setdefault("tools", {})
tools.update(found)
config_path.write_text(
json.dumps(cfg, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
print(f"\n已将 {len(found)} 个工具路径写入 {project / '.em_skill.json'}")
# ── CLI ──────────────────────────────────────────────────────────────
def main() -> None:
parser = argparse.ArgumentParser(
description="embed-ai-tool 安装脚本",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"project",
nargs="?",
help="目标工程路径",
)
parser.add_argument(
"--skills",
nargs="+",
metavar="SKILL",
help="只安装指定 skill(默认行为已改为只分析不安装)",
)
parser.add_argument(
"--yes",
action="store_true",
dest="yes_all",
help="确认全量安装全部 skill(默认行为已改为只分析不安装)",
)
parser.add_argument(
"--force",
action="store_true",
help="强制覆盖已有文件",
)
parser.add_argument(
"--uninstall",
action="store_true",
help="卸载已安装的 skill",
)
parser.add_argument(
"--list",
action="store_true",
dest="list_skills",
help="列出仓库中所有可用 skill",
)
parser.add_argument(
"--status",
action="store_true",
help="显示当前安装状态",
)
parser.add_argument(
"--analyze",
action="store_true",
help="只分析工程类型并输出推荐集,不执行安装",
)
parser.add_argument(
"--detect",
action="store_true",
help="安装后自动探测工具路径",
)
args = parser.parse_args()
# --list 不需要 project 参数
if args.list_skills:
cmd_list()
return
if not args.project:
parser.error("请指定目标工程路径(或使用 --list 查看可用 skill)。")
project = Path(args.project).resolve()
if not project.is_dir():
print(f"错误:目录不存在:{project}", file=sys.stderr)
sys.exit(1)
if args.uninstall:
cmd_uninstall(project)
return
if args.status:
cmd_status(project)
return
if args.analyze:
cmd_analyze(project)
return
# 默认行为:未指定 --skills 且未指定 --yes 时,只分析不安装
if not args.skills and not args.yes_all:
print("[!] 未指定 --skills 或 --yes,默认只执行工程分析,不写入任何文件。\n")
cmd_analyze(project)
return
# 执行安装
cmd_install(project, args.skills, args.force)
if args.detect:
print()
cmd_detect(project)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
from __future__ import annotations
import re
import sys
from pathlib import Path
REQUIRED_FILES = [
".gitignore",
"README.md",
"CONTRIBUTING.md",
"shared/contracts.md",
"shared/failure-taxonomy.md",
"shared/platform-compatibility.md",
"shared/references/tool-detection.md",
"shared/references/acceptance-scenarios.md",
"templates/skill-template/SKILL.md",
"templates/skill-template/CHECKLIST.md",
"templates/skill-template/SCENARIOS.md",
]
REQUIRED_SKILLS = [
"build-cmake",
"flash-openocd",
"serial-monitor",
"debug-gdb-openocd",
"flash-jlink",
"debug-jlink",
"memory-analysis",
"rtos-debug",
"static-analysis",
]
REQUIRED_SECTIONS = [
"## 适用场景",
"## 必要输入",
"## 自动探测",
"## 执行步骤",
"## 失败分流",
"## 平台说明",
"## 输出约定",
"## 交接关系",
]
def fail(message: str, failures: list[str]) -> None:
failures.append(message)
def validate_required_files(root: Path, failures: list[str]) -> None:
for relative_path in REQUIRED_FILES:
if not (root / relative_path).is_file():
fail(f"missing file: {relative_path}", failures)
def parse_frontmatter(text: str) -> dict[str, str]:
match = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
if not match:
return {}
fields: dict[str, str] = {}
for line in match.group(1).splitlines():
if ":" not in line:
continue
key, value = line.split(":", 1)
fields[key.strip()] = value.strip().strip('"')
return fields
def validate_skill(skill_dir: Path, failures: list[str]) -> None:
skill_md = skill_dir / "SKILL.md"
if not skill_md.is_file():
fail(f"missing file: {skill_md.relative_to(skill_dir.parent.parent)}", failures)
return
text = skill_md.read_text(encoding="utf-8")
frontmatter = parse_frontmatter(text)
expected_name = skill_dir.name
if frontmatter.get("name") != expected_name:
fail(
f"{skill_md.relative_to(skill_dir.parent.parent)} frontmatter name should be '{expected_name}'",
failures,
)
if not frontmatter.get("description"):
fail(f"{skill_md.relative_to(skill_dir.parent.parent)} missing frontmatter description", failures)
for heading in REQUIRED_SECTIONS:
if heading not in text:
fail(f"{skill_md.relative_to(skill_dir.parent.parent)} missing section: {heading}", failures)
def main() -> int:
root = Path(__file__).resolve().parent.parent
failures: list[str] = []
validate_required_files(root, failures)
for skill_name in REQUIRED_SKILLS:
validate_skill(root / "skills" / skill_name, failures)
if failures:
print("Repository validation failed:")
for failure in failures:
print(f"- {failure}")
return 1
print("Repository validation passed.")
print(f"Validated {len(REQUIRED_SKILLS)} skills and {len(REQUIRED_FILES)} shared files.")
return 0
if __name__ == "__main__":
sys.exit(main())
共享约定
本文定义了这个仓库中所有 skill 的最小共享接口。
工程画像(Project Profile)
每个 skill 都应当读取或写入标准化后的 Project Profile。输出可以使用 Markdown 或 YAML,但字段名必须保持稳定。
| 字段 | 是否必需 | 含义 |
|---|---|---|
workspace_root | 是 | 固件工作区的绝对路径。 |
workspace_os | 是 | 宿主操作系统:linux、macos 或 windows。 |
build_system | 是 | 主构建系统,例如 cmake。 |
toolchain | 否 | 工具链家族,例如 gnu-arm、clang 或厂商 SDK。 |
target_mcu | 否 | MCU 家族或更精确的芯片型号。 |
board | 否 | 如果工程绑定某块开发板,则记录板卡名称。 |
probe | 否 | 调试探针家族,例如 stlink、jlink、cmsis-dap。 |
artifact_path | 否 | 默认用于烧录或调试的固件产物路径。 |
artifact_kind | 否 | elf、hex 或 bin。 |
openocd_config | 否 | 按顺序排列的 OpenOCD 配置文件或配置片段列表。 |
gdb_executable | 否 | 首选 GDB 可执行文件。 |
serial_port | 否 | 首选串口设备路径或 COM 口。 |
baud_rate | 否 | 首选串口波特率。 |
notes | 否 | 不值得单独增加结构化字段的简短人工备注。 |
idf_path | 否 | ESP-IDF 安装路径。 |
idf_version | 否 | ESP-IDF 版本号,例如 v5.3.2。 |
idf_target | 否 | ESP-IDF 目标芯片,例如 esp32、esp32s3。 |
jlink_device | 否 | J-Link 设备名称,例如 STM32F407VG。 |
jlink_interface | 否 | J-Link 接口类型:SWD 或 JTAG。 |
rtos | 否 | RTOS 类型:freertos、rt-thread 或 zephyr。 |
动作词
以下动作词在所有 skill 中保持统一语义:
| 动作词 | 含义 |
|---|---|
detect | 检查工作区或宿主环境,并填充工程画像 |
build | 配置并编译固件产物 |
flash | 将固件烧录到目标设备 |
attach | 在不默认执行加载步骤的前提下连接调试器 |
monitor | 观察串口或运行期输出 |
reset | 通过当前工具链路复位目标设备 |
verify | 确认产物、探针或烧录状态 |
决策规则
- 显式用户输入永远优先于自动探测结果。
- 若已有
Project Profile,优先复用,而不是每次都从头探测。 - 在下游工具和用户没有明确要求其他格式时,始终优先
ELF,其次HEX,最后BIN。 - 不要猜测
BIN的烧录基地址;地址未知时必须阻塞并询问。 - 如果探测后仍存在多个同样合理的板卡、探针或串口候选,应返回阻塞结果并列出候选项。
技能交接约定
当一个 skill 将结果交给下一个 skill 时,应尽量保留这些内容:
- 标准化后的
Project Profile - 已执行过的命令
- 重要输出,例如产物路径和探测到的配置
- 若流程中断,对应的失败分类
- 推荐的下一步 skill
命令结果结构(Command Outcome Schema)
每个 skill 的结果都应当归入以下状态之一:
success:请求动作已完成。partial_success:有部分有效进展,但主目标尚未完全达成。blocked:由于仍存在高风险未知项,skill 主动停止。failure:在信息已足够的前提下,动作执行失败。
除状态外,还应至少配套这些字段:
summary:一句话说明发生了什么evidence:最关键的日志、文件或探测证据next_action:推荐的下一条命令或下一个 skillfailure_category:当状态不是success时,使用 failure-taxonomy.md 中的分类
最小示例
status: success
summary: 已使用 CMake 构建 Debug 固件,并生成 ELF 产物。
project_profile:
workspace_root: /repo/fw
workspace_os: linux
build_system: cmake
toolchain: gnu-arm
target_mcu: stm32f429zi
probe: stlink
artifact_path: /repo/fw/build/debug/app.elf
artifact_kind: elf
evidence:
- cmake preset: debug
- artifact: /repo/fw/build/debug/app.elf
next_action: flash-openocd失败分类
以下分类需要在所有 skill 中保持一致使用。
environment-missing
当必需的宿主工具或运行时不可用时使用。
- 例子:缺少
cmake、openocd、arm-none-eabi-gdb或pyserial - 响应要求:说明缺失依赖、它是如何被探测出来的,以及完成流程所需的最小安装或路径修复动作
project-config-error
当仓库布局或配置本身阻止了有效工作流时使用。
- 例子:损坏的 CMake preset、缺失的工具链文件、无效的 OpenOCD 配置、冲突的产物命名
- 响应要求:指出出错配置文件或缺失设置,不要继续猜测
connection-failure
当宿主无法连接板卡或探针时使用。
- 例子:探针未连接、USB claim 失败、OpenOCD 找不到 adapter、串口消失
- 响应要求:带上尝试过的探针或串口,以及最可能的物理连接或权限原因
artifact-missing
当请求或必须存在的固件产物不存在,或无法被安全解析时使用。
- 例子:构建后没有 ELF、存在多个 HEX 候选、发现了
BIN但没有基地址 - 响应要求:说明搜索范围,并给出候选列表或缺失路径
target-response-abnormal
当目标设备可达,但其行为异常时使用。
- 例子:烧录后校验不一致、无法停核、重复复位循环、GDB 已附着但符号不匹配
- 响应要求:说明异常发生的具体阶段,并推荐下一步诊断动作
permission-problem
当宿主权限阻止访问设备或文件时使用。
- 例子:Linux 下串口设备不可写、USB 访问被拒绝、构建目录不可写
- 响应要求:明确指出被拒绝的资源,以及需要的最小权限调整
ambiguous-context
当仍存在多个同样合理的目标候选,而任意选择一个都可能浪费时间或破坏流程时使用。
- 例子:同时接了多块板卡、存在多个 OpenOCD 配置、存在多个串口、存在多个同样合理的构建 preset
- 响应要求:列出候选项,并指出只需补充哪一个关键信息即可解除阻塞
"""ESP-IDF 环境自动探测与激活。
统一提供 IDF 环境信息,替代各脚本中重复的 _find_idf_py()。
支持 v5.x (export.sh) 和 v6.0+ (EIM activate_idf_v*.sh) 两种安装方式。
用法::
from idf_env import get_idf_env
env = get_idf_env()
if env is None:
sys.exit("ESP-IDF 环境不可用")
subprocess.run(env.idf_py_cmd + ["build"], env=env.env, cwd=project_dir)
"""
from __future__ import annotations
import os
import platform
import re
import shutil
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any
# 同目录下的 tool_config
from tool_config import get_tool_path
@dataclass
class IdfEnv:
"""已激活的 ESP-IDF 环境快照。"""
idf_py_cmd: list[str] # e.g. ["python", "/path/to/idf.py"]
env: dict[str, str] # 完整环境变量,可直接传给 subprocess.run(env=)
version: str | None # e.g. "6.0"
source: str # "already-active" | "activate-script" | "export-sh" | "path"
# ---------------------------------------------------------------------------
# 模块级缓存
# ---------------------------------------------------------------------------
_cached_env: IdfEnv | None = None
_cached_resolved: bool = False
def get_idf_env() -> IdfEnv | None:
"""探测并返回可用的 IDF 环境,同一进程内缓存结果。"""
global _cached_env, _cached_resolved
if _cached_resolved:
return _cached_env
_cached_env = _resolve_idf_env()
_cached_resolved = True
return _cached_env
# ---------------------------------------------------------------------------
# 内部实现
# ---------------------------------------------------------------------------
def _resolve_idf_env() -> IdfEnv | None:
"""按优先级依次尝试各种方式获取 IDF 环境。"""
# 1. 已激活:IDF_PATH 已设置且 idf.py 可达
result = _try_already_active()
if result:
return result
# 2. 通过激活脚本获取环境
result = _try_activate_script()
if result:
return result
# 3. idf.py 在 PATH 中(旧版安装或用户手动配置)
result = _try_path_lookup()
if result:
return result
return None
def _try_already_active() -> IdfEnv | None:
"""检查当前进程环境是否已激活 IDF。"""
idf_path = os.environ.get("IDF_PATH")
if not idf_path:
return None
idf_py = Path(idf_path) / "tools" / "idf.py"
if not idf_py.exists():
return None
cmd = [sys.executable, str(idf_py)]
env = dict(os.environ)
version = _probe_version(cmd, env)
return IdfEnv(idf_py_cmd=cmd, env=env, version=version, source="already-active")
def _try_activate_script() -> IdfEnv | None:
"""查找并 source 激活脚本,捕获激活后的环境变量。"""
# 优先使用用户配置的路径
configured = get_tool_path("idf-activate")
if configured:
script = Path(configured).expanduser()
if script.exists():
result = _source_and_capture(script)
if result:
return result
is_win = platform.system() == "Windows"
home = Path.home()
# v6.0+ EIM 安装:~/.espressif/tools/activate_idf_v*.sh
eim_dir = home / ".espressif" / "tools"
if eim_dir.is_dir():
if is_win:
pattern = "activate_idf_v*.ps1"
else:
pattern = "activate_idf_v*.sh"
scripts = sorted(eim_dir.glob(pattern), reverse=True)
for script in scripts:
result = _source_and_capture(script)
if result:
result.source = "activate-script"
return result
# v5.x:常见路径下的 export.sh
export_candidates: list[Path] = []
idf_path_env = os.environ.get("IDF_PATH")
if idf_path_env:
export_candidates.append(Path(idf_path_env) / ("export.ps1" if is_win else "export.sh"))
for candidate_dir in [home / "esp" / "esp-idf", home / "esp-idf", Path("/opt/esp-idf")]:
export_candidates.append(candidate_dir / ("export.ps1" if is_win else "export.sh"))
for script in export_candidates:
if script.exists():
result = _source_and_capture(script)
if result:
result.source = "export-sh"
return result
return None
def _try_path_lookup() -> IdfEnv | None:
"""尝试从 PATH 或 tool_config 配置中找到 idf.py。"""
env = dict(os.environ)
# tool_config 配置
configured = get_tool_path("idf-py")
if configured and shutil.which(configured):
cmd = configured.split()
version = _probe_version(cmd, env)
return IdfEnv(idf_py_cmd=cmd, env=env, version=version, source="path")
# PATH 中直接可用
if shutil.which("idf.py"):
cmd = ["idf.py"]
version = _probe_version(cmd, env)
return IdfEnv(idf_py_cmd=cmd, env=env, version=version, source="path")
return None
def _source_and_capture(script: Path) -> IdfEnv | None:
"""Source 一个 shell 脚本并捕获激活后的完整环境变量。"""
system = platform.system()
if system == "Windows":
return _source_windows(script)
else:
return _source_posix(script)
def _source_posix(script: Path) -> IdfEnv | None:
"""在 bash 子进程中 source 脚本,用 env -0 捕获环境。"""
try:
result = subprocess.run(
["bash", "-c", f'source "{script}" > /dev/null 2>&1 && env -0'],
capture_output=True, timeout=30,
)
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
return None
if result.returncode != 0:
return None
env = _parse_env0(result.stdout)
if not env:
return None
return _build_env_from_captured(env)
def _source_windows(script: Path) -> IdfEnv | None:
"""在 PowerShell / cmd 子进程中执行激活脚本并捕获环境。"""
suffix = script.suffix.lower()
try:
if suffix == ".ps1":
cmd_line = (
f'. "{script}" | Out-Null; '
'Get-ChildItem Env: | ForEach-Object { "$($_.Name)=$($_.Value)" }'
)
result = subprocess.run(
["powershell", "-NoProfile", "-Command", cmd_line],
capture_output=True, text=True, timeout=30,
)
elif suffix == ".bat":
cmd_line = f'call "{script}" > nul 2>&1 && set'
result = subprocess.run(
["cmd", "/c", cmd_line],
capture_output=True, text=True, timeout=30,
)
else:
return None
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
return None
if result.returncode != 0:
return None
env = _parse_env_text(result.stdout)
if not env:
return None
return _build_env_from_captured(env)
def _build_env_from_captured(env: dict[str, str]) -> IdfEnv | None:
"""从捕获的环境变量中构造 IdfEnv。"""
idf_path = env.get("IDF_PATH")
if not idf_path:
return None
idf_py = Path(idf_path) / "tools" / "idf.py"
if not idf_py.exists():
return None
# 使用捕获环境中的 python,回退到当前解释器
python = env.get("IDF_PYTHON_ENV_PATH")
if python:
python_bin = Path(python) / "bin" / "python"
if not python_bin.exists():
python_bin = Path(python) / "Scripts" / "python.exe"
if python_bin.exists():
cmd = [str(python_bin), str(idf_py)]
else:
cmd = [sys.executable, str(idf_py)]
else:
cmd = [sys.executable, str(idf_py)]
version = _probe_version(cmd, env)
return IdfEnv(idf_py_cmd=cmd, env=env, version=version, source="activate-script")
def _probe_version(cmd: list[str], env: dict[str, str]) -> str | None:
"""运行 idf.py --version 获取版本号。"""
try:
result = subprocess.run(
cmd + ["--version"],
capture_output=True, text=True, timeout=10, env=env,
)
if result.returncode == 0:
text = result.stdout.strip()
# 提取类似 "v5.3" 或 "ESP-IDF v5.3-dev-..."
m = re.search(r"v?(\d+\.\d+)", text)
return m.group(1) if m else text
except Exception:
pass
return env.get("ESP_IDF_VERSION")
def _parse_env0(raw: bytes) -> dict[str, str]:
"""解析 env -0 的 NUL 分隔输出。"""
env: dict[str, str] = {}
for entry in raw.split(b"\x00"):
if not entry:
continue
try:
decoded = entry.decode("utf-8", errors="replace")
except Exception:
continue
eq = decoded.find("=")
if eq > 0:
env[decoded[:eq]] = decoded[eq + 1 :]
return env
def _parse_env_text(text: str) -> dict[str, str]:
"""解析 KEY=VALUE 格式的文本环境变量输出(Windows set / PowerShell)。"""
env: dict[str, str] = {}
for line in text.splitlines():
eq = line.find("=")
if eq > 0:
env[line[:eq]] = line[eq + 1 :]
return env
平台兼容性
这个仓库面向 Linux、macOS 和 Windows 宿主机。平台相关行为统一维护在这里,避免单个 skill 膨胀。
路径与命令行规则
- 输出中尽可能使用绝对路径。
- 路径中包含空格时要正确加引号。
- 在 Windows 上,默认认为可执行文件可能带
.exe后缀,路径也可能使用反斜杠,即使工具本身接受正斜杠。 - 尽量优先使用工具原生命令行,而不是依赖某种 shell 包装方式,这样同一 skill 才能跨平台描述同一流程。
宿主识别
- 将宿主系统规范化为
linux、macos或windows。 - 在做任何平台相关决策前,先把宿主识别结果写入
Project Profile。 - 优先直接检查工具是否存在,而不是仅根据操作系统做推断。
串口命名
- Linux:常见为
/dev/ttyACM*、/dev/ttyUSB*或厂商特定的/dev/serial/*链接。 - macOS:活跃连接优先关注
/dev/cu.*,/dev/tty.*作为次级候选。 - Windows:应识别为
COM端口,并在输出中保留完整的COMx名称。
权限说明
- Linux 往往需要正确的用户组成员资格或 udev 规则才能访问 USB 探针和串口。
- macOS 上探针访问问题常表现为“找不到设备”,而不是直接报权限错误。
- Windows 上更常见的是驱动缺失或 COM 端口占用,而不是 Unix 风格的权限拒绝。
工具优先级规则
- 通过直接查找可执行文件的方式优先探测
cmake、ninja、openocd和arm-none-eabi-gdb。 - 对串口监视优先使用
python -m serial.tools.miniterm,因为安装pyserial后可跨三种宿主平台工作。 - 只有当首选跨平台工具不可用,且某个系统专用回退工具已经安装时,才使用 OS 专属回退路径。
"""统一项目探测模块。
供新 skill 调用,自动识别构建系统、目标芯片、RTOS 和调试探针。
现有 skill 不做改动,仅新 skill 引用本模块。
"""
from __future__ import annotations
import json
import os
import re
import shutil
import subprocess
from pathlib import Path
from typing import Any
def detect_build_system(workspace: Path) -> str | None:
markers = [
("CMakeLists.txt", "cmake"),
("platformio.ini", "platformio"),
("sdkconfig", "idf"),
]
for filename, system in markers:
if (workspace / filename).exists():
return system
for f in workspace.iterdir():
if f.is_file():
ext = f.suffix.lower()
if ext == ".uvprojx":
return "keil"
if ext in (".eww", ".ewp"):
return "iar"
# Makefile 检测 — 最低优先级,仅在其他系统均未匹配时使用
for mf_name in ("Makefile", "makefile", "GNUmakefile"):
if (workspace / mf_name).is_file():
return "makefile"
return None
def detect_target_mcu(workspace: Path, build_system: str | None) -> str | None:
if build_system == "keil":
for f in workspace.glob("*.uvprojx"):
try:
text = f.read_text(encoding="utf-8", errors="ignore")
m = re.search(r"<Device>(.*?)</Device>", text)
if m:
return m.group(1)
except OSError:
pass
if build_system == "iar":
for f in workspace.glob("*.ewp"):
try:
text = f.read_text(encoding="utf-8", errors="ignore")
m = re.search(r"<OGChipSelectEditMenu>(.*?)</OGChipSelectEditMenu>", text)
if m:
return m.group(1).split("\t")[0] if "\t" in m.group(1) else m.group(1)
except OSError:
pass
if build_system == "platformio":
ini = workspace / "platformio.ini"
if ini.is_file():
try:
text = ini.read_text(encoding="utf-8", errors="ignore")
m = re.search(r"board\s*=\s*(\S+)", text)
if m:
return m.group(1)
except OSError:
pass
if build_system == "idf":
sdkconfig = workspace / "sdkconfig"
if sdkconfig.is_file():
try:
text = sdkconfig.read_text(encoding="utf-8", errors="ignore")
m = re.search(r'CONFIG_IDF_TARGET="(\S+)"', text)
if m:
return m.group(1)
except OSError:
pass
if build_system == "makefile":
for mf_name in ("Makefile", "makefile", "GNUmakefile"):
mf = workspace / mf_name
if mf.is_file():
try:
text = mf.read_text(encoding="utf-8", errors="ignore")
m = re.search(r"(?:^|\n)MCU\s*[:?]?=\s*(\S+)", text)
if m:
return m.group(1).strip()
m = re.search(r"-mcpu=([a-z0-9]+)", text)
if m:
return m.group(1)
except OSError:
pass
break
return None
def detect_rtos(workspace: Path) -> str | None:
rtos_headers = {
"FreeRTOS.h": "freertos",
"rtthread.h": "rt-thread",
"zephyr/kernel.h": "zephyr",
}
rtos_symbols = {
"vTaskStartScheduler": "freertos",
"rt_thread_init": "rt-thread",
"k_thread_create": "zephyr",
}
for root, _dirs, files in os.walk(workspace):
depth = str(root).replace(str(workspace), "").count(os.sep)
if depth > 4:
continue
for fname in files:
if not fname.endswith((".c", ".h", ".cpp")):
continue
fpath = Path(root) / fname
try:
text = fpath.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
for header, rtos in rtos_headers.items():
if f'#include "{header}"' in text or f"#include <{header}>" in text:
return rtos
for symbol, rtos in rtos_symbols.items():
if symbol in text:
return rtos
return None
def detect_probes() -> list[str]:
probes: list[str] = []
if shutil.which("JLinkExe") or shutil.which("JLink.exe"):
probes.append("jlink")
if shutil.which("openocd"):
probes.append("openocd")
if shutil.which("pyocd"):
probes.append("pyocd")
return probes
def _find_artifacts(workspace: Path) -> list[dict[str, str]]:
artifacts: list[dict[str, str]] = []
build_dirs = ["build", "Build", "output", "Output", "Debug", "Release", ".pio/build"]
ext_map = {".elf": "elf", ".hex": "hex", ".bin": "bin", ".axf": "elf"}
for bd_name in build_dirs:
bd = workspace / bd_name
if not bd.is_dir():
continue
for root, _dirs, files in os.walk(bd):
for fname in files:
ext = Path(fname).suffix.lower()
kind = ext_map.get(ext)
if kind:
artifacts.append({
"path": str(Path(root) / fname),
"kind": kind,
})
return artifacts
def detect_project(workspace: Path) -> dict[str, Any]:
build_system = detect_build_system(workspace)
target_mcu = detect_target_mcu(workspace, build_system)
rtos = detect_rtos(workspace)
probes = detect_probes()
artifacts = _find_artifacts(workspace)
profile: dict[str, Any] = {
"workspace_root": str(workspace),
"workspace_os": _detect_os(),
}
if build_system:
profile["build_system"] = build_system
if target_mcu:
profile["target_mcu"] = target_mcu
if rtos:
profile["rtos"] = rtos
if probes:
profile["probes"] = probes
if artifacts:
elf_arts = [a for a in artifacts if a["kind"] == "elf"]
best = elf_arts[0] if elf_arts else artifacts[0]
profile["artifact_path"] = best["path"]
profile["artifact_kind"] = best["kind"]
profile["all_artifacts"] = artifacts
return profile
def _detect_os() -> str:
import platform as _platform
system = _platform.system().lower()
if system == "darwin":
return "macos"
if system == "windows":
return "windows"
return "linux"
验收场景
在评审新增或更新的 skill 时,优先使用以下场景类型。
每个技能的最小覆盖
每个 skill 至少应定义以下场景:
happy-path:预期中的嵌入式流程能够在目标明确的前提下成功执行missing-dependency:缺少必需宿主工具或运行时ambiguous-context:工作区或宿主环境同时呈现多个合理目标
核心嵌入式链路覆盖
这个仓库应持续覆盖以下串联流程:
1. build-cmake -> flash-openocd -> serial-monitor 2. build-cmake -> debug-gdb-openocd
需要保留的失败用例
- 构建报告成功,但没有发现任何固件产物
- OpenOCD 配置文件存在,但与实际目标不匹配
- 探针不存在,或已被其他进程占用
- 串口存在,但无法访问
- 只有
BIN,且没有已知烧录基地址 - 用户请求调试,但找不到匹配的
ELF
评审问题
- skill 是否输出了共享失败分类,而不是自定义标签?
- 当硬件选择存在歧义时,skill 是否选择阻塞而不是继续猜测?
- skill 是否保留了足够证据,以便下一个 skill 继续执行?
- 除非确实改变执行流,平台相关逻辑是否都沉淀在共享参考中?
工具探测参考
当某个 skill 需要定位宿主工具时,优先复用本文逻辑,避免在多个地方重复写同一套规则。
解析优先级
所有工具路径按以下顺序解析,先命中者优先:
1. CLI 参数(--uv4、--iar-root、--gdb 等) 2. 配置文件(工作区 .em_skill.json 覆盖全局 ~/.config/em_skill/config.json) 3. 环境变量(KEIL_ROOT、IAR_ROOT 等) 4. 硬编码常见安装路径 5. PATH 搜索(shutil.which)
配置文件通过 shared/tool_config.py 读写,CLI 管理工具为 scripts/em_config.py。
构建工具
面向 CMake 工作流的探测顺序:
1. cmake 2. 如果宿主 shell 无法解析 cmake,则在 Windows 上尝试 cmake.exe
生成器优先级:
1. 若存在 ninja,优先使用 Ninja 2. 若宿主已安装 make、gmake 或平台等价工具,则退回原生 Makefile 3. 若两者都不可用,则阻塞并报告缺少生成器支持
烧录与调试工具
OpenOCD 查找顺序:
1. 用户或工程画像中显式给出的路径 2. openocd 3. Windows 下的 openocd.exe
ARM MCU 目标的 GDB 查找顺序:
1. 用户或工程画像中显式给出的路径 2. arm-none-eabi-gdb 3. gdb-multiarch 4. 若仍未找到,则阻塞并报告缺失调试器,不要回退到宿主专用 gdb
串口工具
首选探测顺序:
1. python -m serial.tools.miniterm 2. Unix-like 宿主上已安装的终端工具,例如 picocom 或 screen 3. 若都不存在,则阻塞并报告缺失串口监视依赖
探针与配置线索
按以下顺序寻找 OpenOCD 配置线索:
1. 用户显式提供的配置列表 2. 已存在的 Project Profile 3. 仓库中命名为 openocd*.cfg 的文件 4. IDE 启动配置,例如 .vscode/launch.json 5. 工作区内的厂商文档
如果检查后仍有多个合理配置路径,应返回 ambiguous-context。
J-Link 工具
JLinkExe(J-Link Commander)查找顺序:
1. 配置文件(get_tool_path("jlink")) 2. JLinkExe(Linux/macOS PATH) 3. JLink.exe(Windows PATH) 4. /opt/SEGGER/JLink/JLinkExe(Linux/macOS) 5. C:\Program Files\SEGGER\JLink\JLink.exe / C:\Program Files (x86)\SEGGER\JLink\JLink.exe(Windows)
JLinkGDBServer 查找顺序:
1. 配置文件(get_tool_path("jlink-gdbserver")) 2. JLinkGDBServerCLExe(Linux/macOS PATH) 3. JLinkGDBServerCL.exe(Windows PATH) 4. /opt/SEGGER/JLink/JLinkGDBServerCLExe(Linux/macOS) 5. C:\Program Files\SEGGER\JLink\JLinkGDBServerCL.exe(Windows)
--device 参数为必需项,J-Link 无法安全推断设备名。
静态分析工具
cppcheck 查找顺序:
1. 配置文件(get_tool_path("cppcheck")) 2. cppcheck(PATH 搜索) 3. MISRA addon 需要 cppcheck 安装目录下的 addons/misra.py
clang-tidy 查找顺序:
1. 配置文件(get_tool_path("clang-tidy")) 2. clang-tidy(PATH 搜索) 3. 准确分析需要 compile_commands.json
GCC analyzer:
1. arm-none-eabi-gcc(PATH 搜索) 2. gcc(宿主 GCC) 3. 需要 GCC 12+ 才有 -fanalyzer 支持
"""轻量工具路径持久化配置层。
提供 JSON 配置文件的读写,支持工作区级(.em_skill.json)和全局级
(~/.config/em_skill/config.json)两层配置,工作区覆盖全局。
解析优先级(由各脚本自行实现):
CLI 参数 → 配置文件 → 环境变量 → 硬编码路径 → PATH
"""
from __future__ import annotations
import json
import os
import platform
from pathlib import Path
from typing import Any
CONFIG_FILENAME = ".em_skill.json"
def user_config_path() -> Path:
"""返回全局配置文件路径(XDG / APPDATA)。"""
if platform.system() == "Windows":
base = os.environ.get("APPDATA")
if base:
return Path(base) / "em_skill" / "config.json"
# XDG_CONFIG_HOME 或 ~/.config
base = os.environ.get("XDG_CONFIG_HOME", "")
if not base:
base = str(Path.home() / ".config")
return Path(base) / "em_skill" / "config.json"
def workspace_config_path(workspace: str | Path | None = None) -> Path:
"""返回工作区级配置文件路径。"""
ws = Path(workspace) if workspace else Path.cwd()
return ws / CONFIG_FILENAME
def load_config(path: Path) -> dict[str, Any]:
"""读取 JSON 配置文件,文件不存在或格式错误时返回空字典。"""
if not path.is_file():
return {}
try:
return json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return {}
def save_config(path: Path, data: dict[str, Any]) -> None:
"""将配置写入 JSON 文件,自动创建父目录。"""
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
def get_tool_path(
tool_name: str,
workspace: str | Path | None = None,
) -> str | None:
"""合并读取工具路径(工作区优先于全局)。"""
# 工作区级
ws_cfg = load_config(workspace_config_path(workspace))
ws_path = ws_cfg.get("tools", {}).get(tool_name)
if ws_path:
return ws_path
# 全局级
global_cfg = load_config(user_config_path())
return global_cfg.get("tools", {}).get(tool_name)
def set_tool_path(
tool_name: str,
tool_path: str,
workspace: str | Path | None = None,
global_: bool = False,
) -> Path:
"""写入工具路径到指定级别的配置文件,返回写入的文件路径。"""
cfg_path = user_config_path() if global_ else workspace_config_path(workspace)
data = load_config(cfg_path)
tools = data.setdefault("tools", {})
tools[tool_name] = tool_path
save_config(cfg_path, data)
return cfg_path
def remove_tool_path(
tool_name: str,
workspace: str | Path | None = None,
global_: bool = False,
) -> bool:
"""从配置中删除工具路径,返回是否实际删除了条目。"""
cfg_path = user_config_path() if global_ else workspace_config_path(workspace)
data = load_config(cfg_path)
tools = data.get("tools", {})
if tool_name not in tools:
return False
del tools[tool_name]
save_config(cfg_path, data)
return True
def list_tools(
workspace: str | Path | None = None,
) -> dict[str, dict[str, str]]:
"""列出所有已配置的工具,返回 {tool_name: {"path": ..., "source": ...}}。"""
result: dict[str, dict[str, str]] = {}
global_cfg = load_config(user_config_path())
for name, path in global_cfg.get("tools", {}).items():
result[name] = {"path": path, "source": "global"}
ws_cfg = load_config(workspace_config_path(workspace))
for name, path in ws_cfg.get("tools", {}).items():
result[name] = {"path": path, "source": "workspace"}
return result
def find_sdk_bundled_openocd_scripts() -> str | None:
"""探测嵌入式 SDK 自带的 OpenOCD scripts 目录。
部分 SDK(如 Raspberry Pi Pico SDK、PlatformIO 等)会安装
自己的 OpenOCD,包含芯片特定目标配置文件(系统 OpenOCD 可能没有)。
返回 scripts 目录路径,找不到返回 None。
跨平台:Windows 用 openocd.exe,macOS/Linux 用 bin/openocd。
"""
import sys as _sys
sdk_dir = Path.home() / ".pico-sdk" / "openocd"
if not sdk_dir.exists():
return None
for version_dir in sorted(sdk_dir.iterdir(), reverse=True):
if not version_dir.is_dir():
continue
# Binaries can be in root (Windows) or bin/ (Unix)
ocd = version_dir / "openocd.exe" if _sys.platform == "win32" else version_dir / "bin" / "openocd"
if not ocd.exists() and _sys.platform != "win32":
ocd = version_dir / "openocd"
if not ocd.exists():
continue
scripts = version_dir / "scripts"
if scripts.is_dir():
return str(scripts)
return None
interface:
display_name: "CMake 构建"
short_description: "配置、编译 CMake 嵌入式工程并定位固件产物。"
default_prompt: "使用 build-cmake skill,自动探测工程配置,选择合适的生成器和构建类型,执行编译,定位固件产物,并输出下一步建议。"
CMake 构建 Skill 用法
这个 skill 自带了一个可执行脚本 scripts/cmake_builder.py,适合在需要自动探测工程配置、执行 CMake 构建、定位固件产物时直接调用。
能力概览
- 自动探测 cmake、ninja/make 等构建工具是否可用
- 扫描 CMakePresets.json 并列出可用预设
- 扫描 CMakeLists.txt 提取工程名和工具链文件线索
- 自动选择生成器(优先 Ninja)
- 执行 cmake configure + build 全流程
- 在构建目录中搜索 ELF、HEX、BIN 产物并按优先级排序
- 输出结构化的构建结果和分析报告
基础用法
# 探测构建环境
python3 skills/build-cmake/scripts/cmake_builder.py --detect
# 列出可用预设
python3 skills/build-cmake/scripts/cmake_builder.py --list-presets --source /path/to/project
# 使用预设构建
python3 skills/build-cmake/scripts/cmake_builder.py --source /path/to/project --preset debug
# 手动指定构建目录和类型
python3 skills/build-cmake/scripts/cmake_builder.py \
--source /path/to/project \
--build-dir /path/to/project/build \
--build-type Debug常见模式
1. 环境探测
python3 skills/build-cmake/scripts/cmake_builder.py --detect输出 cmake 版本、可用生成器、工具链编译器等信息,适合在构建前确认环境就绪。
2. 使用预设构建
python3 skills/build-cmake/scripts/cmake_builder.py \
--source /repo/fw \
--preset debug自动读取 CMakePresets.json,使用指定预设完成配置和构建。
3. 手动配置构建
python3 skills/build-cmake/scripts/cmake_builder.py \
--source /repo/fw \
--build-dir /repo/fw/build/debug \
--generator Ninja \
--build-type Debug \
--toolchain /repo/fw/cmake/arm-none-eabi.cmake4. 指定构建目标
python3 skills/build-cmake/scripts/cmake_builder.py \
--source /repo/fw \
--preset debug \
--target app5. 仅搜索已有产物
python3 skills/build-cmake/scripts/cmake_builder.py \
--scan-artifacts /repo/fw/build/debug不执行构建,仅在指定目录中搜索 ELF/HEX/BIN 产物。
6. 清理后重新构建
python3 skills/build-cmake/scripts/cmake_builder.py \
--source /repo/fw \
--preset debug \
--clean参数说明
| 参数 | 说明 |
|---|---|
--detect | 探测构建环境(cmake、生成器、编译器) |
--source | CMake 源码目录(包含 CMakeLists.txt) |
--build-dir | 构建输出目录 |
--preset | 使用 CMakePresets.json 中的预设名 |
--list-presets | 列出可用的 CMake 预设 |
--generator | CMake 生成器,例如 Ninja、Unix Makefiles |
--build-type | 构建类型:Debug、Release、RelWithDebInfo、MinSizeRel |
--toolchain | 工具链文件路径 |
--target | 构建目标名称 |
--clean | 构建前清理构建目录 |
--scan-artifacts | 仅扫描指定目录中的固件产物 |
--extra-args | 传递给 cmake configure 的额外参数,可重复 |
-v, --verbose | 输出详细构建日志 |
-j, --jobs | 并行构建任务数 |
返回码
0:构建成功并找到产物,或探测/列表操作成功1:参数非法、依赖缺失、配置失败、构建失败、或未找到产物
与 Skill 的配合方式
在 build-cmake skill 中,推荐工作流是:
1. 先根据用户输入或 Project Profile 决定源码目录、构建类型和预设 2. 若不确定环境是否就绪,先用 --detect 确认 3. 选择合适的构建模式(预设 vs 手动配置) 4. 将脚本输出的产物路径和构建信息整理成简洁摘要 5. 用产物路径更新 Project Profile,交给 flash-openocd 或 debug-gdb-openocd
#!/usr/bin/env python
"""通用嵌入式 CMake 构建工具。
这个脚本为 `build-cmake` skill 提供可重复调用的执行入口,支持:
- 探测构建环境(cmake、生成器、编译器)
- 扫描 CMakePresets.json 并列出可用预设
- 执行 cmake configure + build 全流程
- 在构建目录中搜索 ELF、HEX、BIN 产物并按优先级排序
- 输出结构化的构建结果和分析报告
"""
from __future__ import annotations
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
try:
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
_SCRIPT_DIR = Path(__file__).resolve().parent
_SKILLS_DIR = _SCRIPT_DIR.parent.parent
for _candidate in [_SKILLS_DIR / "shared", _SKILLS_DIR.parent / "shared"]:
if (_candidate / "tool_config.py").exists():
sys.path.insert(0, str(_candidate))
break
from tool_config import get_tool_path, set_tool_path
ARTIFACT_PRIORITY = {"elf": 1, "hex": 2, "bin": 3}
ARTIFACT_EXTENSIONS = {".elf": "elf", ".hex": "hex", ".bin": "bin", ".axf": "elf"}
GENERATOR_PRIORITY = ["Ninja", "Unix Makefiles", "MinGW Makefiles", "NMake Makefiles"]
@dataclass
class ToolInfo:
name: str
path: str | None
version: str | None
@dataclass
class Preset:
name: str
display_name: str
description: str
generator: str | None
build_type: str | None
toolchain: str | None
@dataclass
class Artifact:
path: Path
kind: str
size: int
@dataclass
class BuildResult:
status: str # success, failure, blocked
summary: str
configure_cmd: str | None = None
build_cmd: str | None = None
build_dir: str | None = None
generator: str | None = None
artifacts: list[Artifact] = field(default_factory=list)
primary_artifact: Artifact | None = None
failure_category: str | None = None
evidence: list[str] = field(default_factory=list)
# ---------------------------------------------------------------------------
# 工具探测
# ---------------------------------------------------------------------------
def find_tool(name: str, alt_names: list[str] | None = None) -> ToolInfo:
# 配置文件
configured = get_tool_path(name)
if configured:
configured_path = shutil.which(configured) or configured
if Path(configured_path).exists():
version = _get_version(configured_path)
return ToolInfo(name=name, path=configured_path, version=version)
candidates = [name] + (alt_names or [])
for candidate in candidates:
path = shutil.which(candidate)
if path:
version = _get_version(path)
return ToolInfo(name=candidate, path=path, version=version)
return ToolInfo(name=name, path=None, version=None)
def _get_version(executable: str) -> str | None:
try:
result = subprocess.run(
[executable, "--version"],
capture_output=True, text=True, timeout=5,
)
first_line = (result.stdout or result.stderr).strip().split("\n")[0]
return first_line if first_line else None
except Exception:
return None
def detect_generator() -> str | None:
for gen_name in GENERATOR_PRIORITY:
if gen_name == "Ninja" and shutil.which("ninja"):
return "Ninja"
if gen_name == "Unix Makefiles" and (shutil.which("make") or shutil.which("gmake")):
return "Unix Makefiles"
if gen_name == "MinGW Makefiles" and shutil.which("mingw32-make"):
return "MinGW Makefiles"
if gen_name == "NMake Makefiles" and shutil.which("nmake"):
return "NMake Makefiles"
return None
def detect_environment() -> dict[str, Any]:
cmake = find_tool("cmake", ["cmake.exe"])
ninja = find_tool("ninja", ["ninja.exe"])
make = find_tool("make", ["gmake", "mingw32-make"])
arm_gcc = find_tool("arm-none-eabi-gcc")
generator = detect_generator()
env = {
"cmake": {"available": cmake.path is not None, "path": cmake.path, "version": cmake.version},
"ninja": {"available": ninja.path is not None, "path": ninja.path, "version": ninja.version},
"make": {"available": make.path is not None, "path": make.path, "version": make.version},
"arm_gcc": {"available": arm_gcc.path is not None, "path": arm_gcc.path, "version": arm_gcc.version},
"preferred_generator": generator,
}
return env
# ---------------------------------------------------------------------------
# CMakePresets.json 解析
# ---------------------------------------------------------------------------
def load_presets(source_dir: Path) -> list[Preset]:
presets_file = source_dir / "CMakePresets.json"
if not presets_file.exists():
return []
try:
data = json.loads(presets_file.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError) as exc:
print(f"⚠️ 无法解析 CMakePresets.json: {exc}")
return []
configure_presets = data.get("configurePresets", [])
results: list[Preset] = []
for p in configure_presets:
if p.get("hidden", False):
continue
cache_vars = p.get("cacheVariables", {})
results.append(Preset(
name=p.get("name", ""),
display_name=p.get("displayName", p.get("name", "")),
description=p.get("description", ""),
generator=p.get("generator"),
build_type=cache_vars.get("CMAKE_BUILD_TYPE"),
toolchain=p.get("toolchainFile") or cache_vars.get("CMAKE_TOOLCHAIN_FILE"),
))
return results
def list_presets_display(source_dir: Path) -> list[Preset]:
presets = load_presets(source_dir)
if not presets:
print("❌ 未找到可用的 CMake 预设")
presets_file = source_dir / "CMakePresets.json"
if not presets_file.exists():
print(f" {presets_file} 不存在")
return []
print("📋 可用 CMake 预设:")
for i, p in enumerate(presets, 1):
gen_info = f" [{p.generator}]" if p.generator else ""
bt_info = f" ({p.build_type})" if p.build_type else ""
desc = f" - {p.description}" if p.description else ""
print(f" {i}. {p.name}{gen_info}{bt_info}{desc}")
return presets
# ---------------------------------------------------------------------------
# CMakeLists.txt 扫描
# ---------------------------------------------------------------------------
def scan_cmakelists(source_dir: Path) -> dict[str, str | None]:
cmakelists = source_dir / "CMakeLists.txt"
info: dict[str, str | None] = {"project_name": None, "toolchain_hint": None}
if not cmakelists.exists():
return info
try:
content = cmakelists.read_text(encoding="utf-8", errors="ignore")
except OSError:
return info
project_match = re.search(r"project\s*\(\s*(\w+)", content, re.IGNORECASE)
if project_match:
info["project_name"] = project_match.group(1)
tc_match = re.search(r"CMAKE_TOOLCHAIN_FILE\s+[\"']?([^\s\"')]+)", content)
if tc_match:
info["toolchain_hint"] = tc_match.group(1)
return info
# ---------------------------------------------------------------------------
# 产物扫描
# ---------------------------------------------------------------------------
def scan_artifacts(build_dir: Path) -> list[Artifact]:
if not build_dir.exists():
return []
artifacts: list[Artifact] = []
seen: set[str] = set()
for root, _dirs, files in os.walk(build_dir):
for fname in files:
ext = Path(fname).suffix.lower()
kind = ARTIFACT_EXTENSIONS.get(ext)
if not kind:
continue
fpath = Path(root) / fname
real = str(fpath.resolve())
if real in seen:
continue
seen.add(real)
try:
size = fpath.stat().st_size
except OSError:
size = 0
if size < 256:
continue
artifacts.append(Artifact(path=fpath, kind=kind, size=size))
artifacts.sort(key=lambda a: (ARTIFACT_PRIORITY.get(a.kind, 9), -a.size))
return artifacts
def pick_primary_artifact(artifacts: list[Artifact]) -> Artifact | None:
if not artifacts:
return None
return artifacts[0]
# ---------------------------------------------------------------------------
# 构建执行
# ---------------------------------------------------------------------------
def run_cmake_configure(
source_dir: Path,
build_dir: Path,
preset: str | None,
generator: str | None,
build_type: str | None,
toolchain: str | None,
extra_args: list[str],
) -> tuple[bool, str, list[str]]:
cmd: list[str] = ["cmake"]
if preset:
cmd.extend(["--preset", preset])
if source_dir:
cmd.extend(["-S", str(source_dir)])
else:
cmd.extend(["-S", str(source_dir), "-B", str(build_dir)])
if generator:
cmd.extend(["-G", generator])
if build_type:
cmd.append(f"-DCMAKE_BUILD_TYPE={build_type}")
if toolchain:
cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={toolchain}")
cmd.extend(extra_args)
cmd_str = " ".join(cmd)
print(f"🔧 配置命令: {cmd_str}")
try:
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=120,
)
except subprocess.TimeoutExpired:
return False, cmd_str, ["❌ CMake 配置超时(120 秒)"]
except FileNotFoundError:
return False, cmd_str, ["❌ 未找到 cmake 命令"]
evidence = []
output = (result.stdout + "\n" + result.stderr).strip()
if result.returncode != 0:
last_lines = output.split("\n")[-20:]
evidence.append("配置失败输出(末尾):")
evidence.extend(last_lines)
return False, cmd_str, evidence
print("✅ CMake 配置成功")
return True, cmd_str, evidence
def run_cmake_build(
build_dir: Path,
preset: str | None,
target: str | None,
jobs: int | None,
verbose: bool,
source_dir: Path | None = None,
) -> tuple[bool, str, list[str]]:
cmd: list[str] = ["cmake", "--build"]
if preset:
cmd.extend(["--preset", preset])
else:
cmd.append(str(build_dir))
if target:
cmd.extend(["--target", target])
if jobs:
cmd.extend(["-j", str(jobs)])
if verbose:
cmd.append("--verbose")
cmd_str = " ".join(cmd)
print(f"🔨 构建命令: {cmd_str}")
cwd = str(source_dir) if source_dir and preset else None
start = time.time()
try:
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=600, cwd=cwd,
)
except subprocess.TimeoutExpired:
return False, cmd_str, ["❌ 构建超时(600 秒)"]
except FileNotFoundError:
return False, cmd_str, ["❌ 未找到 cmake 命令"]
elapsed = time.time() - start
evidence: list[str] = []
output = (result.stdout + "\n" + result.stderr).strip()
if result.returncode != 0:
last_lines = output.split("\n")[-30:]
evidence.append("构建失败输出(末尾):")
evidence.extend(last_lines)
return False, cmd_str, evidence
print(f"✅ 构建成功(耗时 {elapsed:.1f} 秒)")
evidence.append(f"构建耗时: {elapsed:.1f} 秒")
return True, cmd_str, evidence
def clean_build_dir(build_dir: Path) -> None:
if build_dir.exists():
print(f"🗑️ 清理构建目录: {build_dir}")
shutil.rmtree(build_dir, ignore_errors=True)
def resolve_build_dir(source_dir: Path, build_dir: str | None, preset: str | None) -> Path:
if build_dir:
return Path(build_dir).resolve()
if preset:
presets = load_presets(source_dir)
for p in presets:
if p.name == preset:
candidate = source_dir / "build" / p.name
return candidate.resolve()
return (source_dir / "build").resolve()
# ---------------------------------------------------------------------------
# 报告输出
# ---------------------------------------------------------------------------
def print_detect_report(env: dict[str, Any]) -> None:
print("\n📊 构建环境探测结果:")
for tool_name in ["cmake", "ninja", "make", "arm_gcc"]:
info = env[tool_name]
status = "✅" if info["available"] else "❌"
ver = f" ({info['version']})" if info.get("version") else ""
path = f" @ {info['path']}" if info.get("path") else ""
print(f" {status} {tool_name}{ver}{path}")
gen = env.get("preferred_generator")
if gen:
print(f"\n 首选生成器: {gen}")
else:
print("\n ⚠️ 未找到可用的生成器(需要 ninja 或 make)")
def print_build_report(result: BuildResult) -> None:
status_icon = {"success": "✅", "failure": "❌", "blocked": "⚠️"}.get(result.status, "❓")
print(f"\n📊 构建结果: {status_icon} {result.summary}")
if result.configure_cmd:
print(f"\n 配置命令: {result.configure_cmd}")
if result.build_cmd:
print(f" 构建命令: {result.build_cmd}")
if result.build_dir:
print(f" 构建目录: {result.build_dir}")
if result.generator:
print(f" 生成器: {result.generator}")
if result.artifacts:
print(f"\n📦 找到 {len(result.artifacts)} 个固件产物:")
for i, a in enumerate(result.artifacts):
size_kb = a.size / 1024
primary = " ⭐ 首选" if a == result.primary_artifact else ""
print(f" {i + 1}. [{a.kind.upper()}] {a.path} ({size_kb:.1f} KB){primary}")
elif result.status == "success":
print("\n ⚠️ 构建成功但未找到固件产物")
if result.evidence:
print("\n📝 证据:")
for line in result.evidence[:15]:
print(f" {line}")
if result.failure_category:
print(f"\n 失败分类: {result.failure_category}")
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="嵌入式 CMake 构建工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s --detect
%(prog)s --list-presets --source /repo/fw
%(prog)s --source /repo/fw --preset debug
%(prog)s --source /repo/fw --build-dir build --build-type Debug
%(prog)s --scan-artifacts /repo/fw/build/debug
""",
)
parser.add_argument("--detect", action="store_true", help="探测构建环境")
parser.add_argument("--source", help="CMake 源码目录")
parser.add_argument("--build-dir", help="构建输出目录")
parser.add_argument("--preset", help="CMake 预设名称")
parser.add_argument("--list-presets", action="store_true", help="列出可用预设")
parser.add_argument("--generator", help="CMake 生成器")
parser.add_argument("--build-type", help="构建类型: Debug, Release, RelWithDebInfo, MinSizeRel")
parser.add_argument("--toolchain", help="工具链文件路径")
parser.add_argument("--target", help="构建目标名称")
parser.add_argument("--clean", action="store_true", help="构建前清理")
parser.add_argument("--scan-artifacts", help="仅扫描指定目录中的产物")
parser.add_argument("--extra-args", action="append", default=[], help="传递给 cmake 的额外参数")
parser.add_argument("--save-config", action="store_true", help="探测成功后保存工具路径到配置")
parser.add_argument("-v", "--verbose", action="store_true", help="详细输出")
parser.add_argument("-j", "--jobs", type=int, help="并行构建任务数")
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
# 环境探测模式
if args.detect:
env = detect_environment()
print_detect_report(env)
if args.save_config:
for tool_key in ["cmake", "ninja", "make", "arm_gcc"]:
info = env[tool_key]
if info["available"]:
cfg_path = set_tool_path(tool_key.replace("_", "-"), info["path"])
print(f" 💾 {tool_key} 已保存到 {cfg_path}")
return 0 if env["cmake"]["available"] else 1
# 仅扫描产物模式
if args.scan_artifacts:
scan_dir = Path(args.scan_artifacts).resolve()
artifacts = scan_artifacts(scan_dir)
if not artifacts:
print(f"❌ 在 {scan_dir} 中未找到固件产物")
return 1
primary = pick_primary_artifact(artifacts)
result = BuildResult(
status="success",
summary=f"找到 {len(artifacts)} 个产物",
build_dir=str(scan_dir),
artifacts=artifacts,
primary_artifact=primary,
)
print_build_report(result)
return 0
# 列出预设模式
if args.list_presets:
source_dir = Path(args.source or ".").resolve()
presets = list_presets_display(source_dir)
return 0 if presets else 1
# 构建模式 - 需要源码目录
if not args.source and not args.preset:
print("❌ 请提供 --source(源码目录)或 --preset(预设名称)。")
return 1
source_dir = Path(args.source or ".").resolve()
if not (source_dir / "CMakeLists.txt").exists() and not args.preset:
print(f"❌ 在 {source_dir} 中未找到 CMakeLists.txt")
return 1
# 检查 cmake 是否可用
cmake_info = find_tool("cmake", ["cmake.exe"])
if not cmake_info.path:
print("❌ 未找到 cmake,请先安装。")
return 1
# 解析构建目录
build_dir = resolve_build_dir(source_dir, args.build_dir, args.preset)
# 选择生成器
generator = args.generator
if not generator and not args.preset:
generator = detect_generator()
if generator:
print(f"ℹ️ 自动选择生成器: {generator}")
else:
print("⚠️ 未找到 Ninja 或 Make,将使用 CMake 默认生成器")
# 选择构建类型
build_type = args.build_type
if not build_type and not args.preset:
build_type = "Debug"
print(f"ℹ️ 未指定构建类型,默认使用: {build_type}")
# 清理
if args.clean:
clean_build_dir(build_dir)
# 配置
ok, conf_cmd, conf_evidence = run_cmake_configure(
source_dir=source_dir,
build_dir=build_dir,
preset=args.preset,
generator=generator,
build_type=build_type,
toolchain=args.toolchain,
extra_args=args.extra_args,
)
if not ok:
result = BuildResult(
status="failure",
summary="CMake 配置失败",
configure_cmd=conf_cmd,
build_dir=str(build_dir),
generator=generator,
failure_category="project-config-error",
evidence=conf_evidence,
)
print_build_report(result)
return 1
# 构建
ok, bld_cmd, bld_evidence = run_cmake_build(
build_dir=build_dir,
preset=args.preset,
target=args.target,
jobs=args.jobs,
verbose=args.verbose,
source_dir=source_dir,
)
all_evidence = conf_evidence + bld_evidence
if not ok:
result = BuildResult(
status="failure",
summary="CMake 构建失败",
configure_cmd=conf_cmd,
build_cmd=bld_cmd,
build_dir=str(build_dir),
generator=generator,
failure_category="project-config-error",
evidence=all_evidence,
)
print_build_report(result)
return 1
# 扫描产物
artifacts = scan_artifacts(build_dir)
primary = pick_primary_artifact(artifacts)
if not artifacts:
result = BuildResult(
status="success",
summary="构建成功但未找到固件产物",
configure_cmd=conf_cmd,
build_cmd=bld_cmd,
build_dir=str(build_dir),
generator=generator,
artifacts=[],
failure_category="artifact-missing",
evidence=all_evidence,
)
print_build_report(result)
return 1
result = BuildResult(
status="success",
summary=f"构建成功,找到 {len(artifacts)} 个产物",
configure_cmd=conf_cmd,
build_cmd=bld_cmd,
build_dir=str(build_dir),
generator=generator,
artifacts=artifacts,
primary_artifact=primary,
evidence=all_evidence,
)
print_build_report(result)
return 0
if __name__ == "__main__":
sys.exit(main())
interface:
display_name: "IAR 构建"
short_description: "通过 IAR Embedded Workbench 命令行编译嵌入式工程并定位固件产物。"
default_prompt: "使用 build-iar skill,自动探测 IAR 安装和工程文件,选择合适的配置,执行编译,定位固件产物,并输出下一步建议。"
IAR Embedded Workbench 构建 Skill 用法
这个 skill 自带了一个可执行脚本 scripts/iar_builder.py,适合在需要探测 IAR 安装、解析 .ewp 工程文件、执行命令行编译并定位固件产物时直接调用。
能力概览
- 探测 IAR Embedded Workbench 安装路径和 iarbuild.exe 命令行工具
- 扫描工作区中的
.ewp/.eww工程文件 - 解析工程文件中的 configuration 列表、工具链、芯片和输出目录
- 通过 iarbuild.exe 命令行执行编译(make / build / clean)
- 在输出目录中搜索 .out(ELF)、HEX、BIN 产物并按优先级排序
- 输出结构化的构建结果报告
基础用法
# 探测 IAR 环境
python3 skills/build-iar/scripts/iar_builder.py --detect
# 扫描工作区工程文件
python3 skills/build-iar/scripts/iar_builder.py --scan /path/to/project
# 列出工程中的配置
python3 skills/build-iar/scripts/iar_builder.py --list-configs --project path/to/app.ewp
# 编译默认配置
python3 skills/build-iar/scripts/iar_builder.py --project path/to/app.ewp
# 编译指定配置
python3 skills/build-iar/scripts/iar_builder.py --project path/to/app.ewp --config Debug
# 重新编译(clean + build)
python3 skills/build-iar/scripts/iar_builder.py --project path/to/app.ewp --rebuild
# 清理
python3 skills/build-iar/scripts/iar_builder.py --project path/to/app.ewp --config Debug --clean常见模式
1. 环境探测
python3 skills/build-iar/scripts/iar_builder.py --detect输出 IAR 安装路径、iarbuild.exe 位置。
2. 扫描工作区工程文件
python3 skills/build-iar/scripts/iar_builder.py --scan /path/to/project在工作区中递归搜索 .ewp 和 .eww 文件。
3. 列出工程配置
python3 skills/build-iar/scripts/iar_builder.py \
--list-configs \
--project path/to/app.ewp4. 编译指定配置
python3 skills/build-iar/scripts/iar_builder.py \
--project path/to/app.ewp \
--config "Release"5. 重新编译
python3 skills/build-iar/scripts/iar_builder.py \
--project path/to/app.ewp \
--config "Debug" \
--rebuild6. 仅扫描已有产物
python3 skills/build-iar/scripts/iar_builder.py \
--scan-artifacts path/to/Debug/Exe参数说明
| 参数 | 说明 |
|---|---|
--detect | 探测 IAR 环境 |
--project | .ewp 工程文件路径 |
--config | 构建配置名称(对应工程中的 configuration) |
--list-configs | 列出工程中的所有配置 |
--rebuild | 重新编译(等价于 clean + build) |
--clean | 清理指定配置 |
--scan | 扫描指定目录中的 IAR 工程文件 |
--scan-artifacts | 仅扫描指定目录中的固件产物 |
--iar-root | 显式指定 IAR 安装根目录 |
--parallel | 并行编译任务数 |
-v, --verbose | 输出详细编译日志(-log all) |
返回码
0:编译成功并找到产物,或探测/列表操作成功1:参数非法、IAR 未安装、工程文件无效、编译失败、或未找到产物
平台说明
IAR Embedded Workbench 仅在 Windows 上原生运行。脚本在非 Windows 平台上仍可执行 --scan、--list-configs、--scan-artifacts 等不依赖 iarbuild.exe 的操作,但实际编译需要 Windows 环境。
与 Skill 的配合方式
在 build-iar skill 中,推荐工作流是:
1. 先根据用户输入或 Project Profile 确定工程文件和配置 2. 若不确定环境是否就绪,先用 --detect 确认 3. 若不确定工程文件位置,用 --scan 搜索工作区 4. 用 --list-configs 确认可用配置,再执行编译 5. 将脚本输出的产物路径和构建信息整理成简洁摘要 6. 用产物路径更新 Project Profile,交给 flash-openocd 或 debug-gdb-openocd
interface:
display_name: "ESP-IDF 编译"
short_description: "配置目标芯片并构建 ESP-IDF 固件工程。"
default_prompt: "使用 build-idf skill,探测 ESP-IDF 环境,设置目标芯片,执行构建并定位固件产物,输出下一步建议。"
ESP-IDF 编译 Skill 用法
这个 skill 自带了一个可执行脚本 scripts/idf_builder.py,适合在需要探测环境、设置目标芯片、执行构建或扫描产物时直接调用。
能力概览
- 检测 ESP-IDF 环境和
idf.py可用性 - 设置目标芯片(set-target)
- 执行完整构建流程
- 扫描构建产物(.bin、.elf)
- 执行清理操作
- 输出结构化的构建结果报告
基础用法
# 探测 ESP-IDF 构建环境
python3 skills/build-idf/scripts/idf_builder.py --detect
# 设置目标芯片
python3 skills/build-idf/scripts/idf_builder.py --set-target esp32s3 --project /path/to/project
# 构建工程
python3 skills/build-idf/scripts/idf_builder.py --build --project /path/to/project
# 仅扫描构建产物
python3 skills/build-idf/scripts/idf_builder.py --scan-artifacts /path/to/project/build
# 清理构建目录
python3 skills/build-idf/scripts/idf_builder.py --clean --project /path/to/project常见模式
1. 环境探测
python3 skills/build-idf/scripts/idf_builder.py --detect输出 idf.py 路径、IDF 版本、支持的目标芯片列表。
2. 首次构建
# 设置目标芯片
python3 skills/build-idf/scripts/idf_builder.py --set-target esp32 --project /repo/fw
# 构建
python3 skills/build-idf/scripts/idf_builder.py --build --project /repo/fw3. 重新构建
python3 skills/build-idf/scripts/idf_builder.py --build --project /repo/fw4. 清理后重建
python3 skills/build-idf/scripts/idf_builder.py --clean --project /repo/fw
python3 skills/build-idf/scripts/idf_builder.py --build --project /repo/fw参数说明
| 参数 | 说明 |
|---|---|
--detect | 探测 ESP-IDF 构建环境 |
--build | 执行构建 |
--project | ESP-IDF 工程目录路径 |
--set-target | 设置目标芯片(esp32、esp32s2、esp32s3、esp32c2、esp32c3、esp32c5、esp32c6、esp32c61、esp32h2、esp32p4) |
--clean | 执行 fullclean |
--scan-artifacts | 仅扫描指定目录中的构建产物 |
-v, --verbose | 详细输出 |
返回码
0:操作成功1:参数非法、环境缺失、构建失败或产物缺失
与 Skill 的配合方式
在 build-idf skill 中,推荐工作流是:
1. 先用 --detect 确认 ESP-IDF 环境就绪 2. 若环境未就绪,提示用户手动安装 ESP-IDF 3. 首次使用时,向用户确认目标芯片,即使 sdkconfig 中已有值也需确认 4. 用 --set-target 设置目标芯片 5. 执行 --build 构建 6. 将构建结果整理成简洁摘要 7. 更新 Project Profile,交给 flash-idf
interface:
display_name: "CAN 总线调试"
short_description: "通过 USB-CAN 适配器调试 CAN 总线通信。"
default_prompt: "使用 can-debug skill,探测 CAN 环境,执行总线监听、帧发送或节点扫描,并输出调试结果。"
Related skills
How it compares
Meta router and installer for the embed-ai-tool skill bundle, not a single-flash script like flash-openocd alone.
FAQ
Who is embed-ai-tool for?
embedded developers using Claude Code-style agents who need disambiguation across Keil, PlatformIO, ESP-IDF, OpenOCD, and J-Link workflows.
When should I use embed-ai-tool?
At first install of the repo; whenever a command is ambiguous (compile/flash/debug); during Build integrations daily; in Ship testing when attaching GDB; in Operate when capturing serial or RTT logs from production firmware.
Is embed-ai-tool safe to install?
Review the Security Audits panel on this page; downstream skills may invoke shell, hardware debuggers, and serial devices—install only the skills you need.