@seeed-studio/solution-cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,327 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import https from 'node:https';
5
+ import http from 'node:http';
6
+ import { createCatalogDto } from './catalog-dto.js';
7
+ import { assertCompatibility, COMPATIBILITY_STATUS } from './compatibility.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const DATA_FILE = path.join(__dirname, '..', 'catalog', 'knowledge-core.json');
12
+
13
+ let cachedKnowledgeCore = null;
14
+
15
+ /**
16
+ * Loads the bundled offline knowledge core file.
17
+ *
18
+ * @returns {Object} Bundled knowledge core data
19
+ */
20
+ export function loadKnowledgeCore() {
21
+ if (cachedKnowledgeCore) return cachedKnowledgeCore;
22
+ if (!fs.existsSync(DATA_FILE)) {
23
+ throw new Error(`Knowledge core manifest not found at ${DATA_FILE}. Run "npm run build:knowledge" to generate.`);
24
+ }
25
+ const raw = fs.readFileSync(DATA_FILE, 'utf-8');
26
+ cachedKnowledgeCore = JSON.parse(raw);
27
+ return cachedKnowledgeCore;
28
+ }
29
+
30
+ /**
31
+ * Performs an HTTP GET request with a timeout.
32
+ *
33
+ * @param {string} urlStr
34
+ * @param {Object} headers
35
+ * @param {number} timeoutMs
36
+ * @returns {Promise<Object>}
37
+ */
38
+ function fetchJson(urlStr, headers = {}, timeoutMs = 2500) {
39
+ return new Promise((resolve, reject) => {
40
+ const url = new URL(urlStr);
41
+ const client = url.protocol === 'https:' ? https : http;
42
+ const req = client.get(url, { headers, timeout: timeoutMs }, (res) => {
43
+ let body = '';
44
+ res.on('data', (chunk) => { body += chunk; });
45
+ res.on('end', () => {
46
+ if (res.statusCode >= 200 && res.statusCode < 300) {
47
+ try {
48
+ resolve(JSON.parse(body));
49
+ } catch (e) {
50
+ reject(new Error(`Invalid JSON from ${urlStr}: ${e.message}`));
51
+ }
52
+ } else {
53
+ reject(new Error(`HTTP ${res.statusCode} from ${urlStr}`));
54
+ }
55
+ });
56
+ });
57
+ req.on('timeout', () => {
58
+ req.destroy();
59
+ reject(new Error(`Timeout after ${timeoutMs}ms from ${urlStr}`));
60
+ });
61
+ req.on('error', reject);
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Searches Seeed Solutions Hub scenarios and reference designs.
67
+ *
68
+ * @param {Object} options
69
+ * @param {string} options.query - Free text search query
70
+ * @param {'zh' | 'en'} [options.locale='zh'] - Language preference
71
+ * @param {number} [options.limit=5] - Maximum number of results
72
+ * @returns {Promise<Object>} Standardized Catalog DTO
73
+ */
74
+ export async function searchHub({ query = '', locale = 'zh', limit = 5 } = {}) {
75
+ const qLower = (query || '').toLowerCase().trim();
76
+ const searchKey = process.env.SENSECRAFT_TYPESENSE_PURCHASE_SEARCH_KEY || process.env.TYPESENSE_SEARCH_KEY;
77
+ const host = process.env.TYPESENSE_HOST || 'https://rel-search.seeedstudio.com';
78
+
79
+ // Attempt online Typesense search if key is provided and not explicitly running offline
80
+ if (searchKey && !process.env.SENSECRAFT_OFFLINE_ONLY) {
81
+ try {
82
+ const searchUrl = `${host}/collections/Solution/documents/search?q=${encodeURIComponent(query)}&query_by=title,description,tags&per_page=${limit}`;
83
+ const data = await fetchJson(searchUrl, { 'X-TYPESENSE-API-KEY': searchKey }, 2500);
84
+ if (data && Array.isArray(data.hits)) {
85
+ const records = data.hits.map((hit) => {
86
+ const doc = hit.document || {};
87
+ return {
88
+ type: doc.doc_type || 'scenario',
89
+ id: doc.id || doc.slug,
90
+ title: doc.title,
91
+ summary: doc.summary || doc.description,
92
+ tags: doc.tags || [],
93
+ family_ids: doc.family_ids || [],
94
+ source: 'typesense',
95
+ };
96
+ });
97
+ return createCatalogDto({
98
+ source: 'typesense',
99
+ freshness: 'FRESH',
100
+ records: records.slice(0, limit),
101
+ });
102
+ }
103
+ } catch (err) {
104
+ // Graceful fallback to offline core with notice
105
+ }
106
+ }
107
+
108
+ // Offline fallback search over knowledge-core
109
+ const core = loadKnowledgeCore();
110
+ const matchedScenarios = [];
111
+ const matchedRefDesigns = [];
112
+
113
+ const tokens = qLower.split(/\s+/).filter(Boolean);
114
+
115
+ for (const s of core.scenarios || []) {
116
+ const haystack = [
117
+ s.id,
118
+ s.title?.zh,
119
+ s.title?.en,
120
+ s.subtitle?.zh,
121
+ s.subtitle?.en,
122
+ s.summary,
123
+ ...(s.tags || []),
124
+ ].join(' ').toLowerCase();
125
+
126
+ const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);
127
+ if (tokens.length === 0 || score > 0) {
128
+ matchedScenarios.push({
129
+ type: 'scenario',
130
+ id: s.id,
131
+ title: s.title?.[locale] || s.title?.zh || s.title?.en || s.id,
132
+ summary: s.subtitle?.[locale] || s.subtitle?.zh || s.summary || '',
133
+ tags: s.tags || [],
134
+ family_ids: s.family_ids || [],
135
+ reference_designs: s.reference_designs || [],
136
+ score,
137
+ });
138
+ }
139
+ }
140
+
141
+ for (const rd of core.reference_designs || []) {
142
+ const haystack = [
143
+ rd.id,
144
+ rd.title?.zh,
145
+ rd.title?.en,
146
+ rd.summary?.zh,
147
+ rd.summary?.en,
148
+ ...(rd.presets || []),
149
+ ].join(' ').toLowerCase();
150
+
151
+ const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);
152
+ if (tokens.length === 0 || score > 0) {
153
+ matchedRefDesigns.push({
154
+ type: 'reference_design',
155
+ id: rd.id,
156
+ title: rd.title?.[locale] || rd.title?.zh || rd.title?.en || rd.id,
157
+ summary: rd.summary?.[locale] || rd.summary?.zh || rd.summary?.en || '',
158
+ presets: rd.presets || [],
159
+ wiki_url: rd.wiki_url || '',
160
+ score,
161
+ });
162
+ }
163
+ }
164
+
165
+ // Sort by score descending and take top results
166
+ matchedScenarios.sort((a, b) => b.score - a.score);
167
+ matchedRefDesigns.sort((a, b) => b.score - a.score);
168
+
169
+ const combined = [...matchedScenarios, ...matchedRefDesigns].slice(0, limit);
170
+
171
+ return createCatalogDto({
172
+ source: 'bundled_manifest',
173
+ freshness: 'FRESH',
174
+ records: combined,
175
+ warnings: searchKey
176
+ ? ['Online Typesense request timed out; fell back to local bundled manifest.']
177
+ : ['No Typesense API key configured. Displaying verified local catalog.'],
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Queries product family information, SKUs, and official purchase links.
183
+ *
184
+ * @param {string} familyId
185
+ * @param {Object} [options]
186
+ * @param {string} [options.locale='zh']
187
+ * @returns {Object} Catalog DTO with product details
188
+ */
189
+ export function queryProductFamily(familyId, { locale = 'zh' } = {}) {
190
+ const core = loadKnowledgeCore();
191
+ const family = core.product_families?.[familyId];
192
+
193
+ if (!family) {
194
+ return createCatalogDto({
195
+ source: 'bundled_manifest',
196
+ freshness: 'FRESH',
197
+ records: [],
198
+ warnings: [`Product family "${familyId}" not found in local catalog.`],
199
+ });
200
+ }
201
+
202
+ return createCatalogDto({
203
+ source: 'bundled_manifest',
204
+ freshness: 'FRESH',
205
+ records: [{
206
+ family_id: family.id,
207
+ title: family.title?.[locale] || family.title?.zh || family.title?.en || family.id,
208
+ default_sku: family.default_sku,
209
+ regions: family.regions || [],
210
+ skus_count: (family.skus || []).length,
211
+ skus: (family.skus || []).slice(0, 10),
212
+ physical_spec: family.physical_spec || null,
213
+ required_accessories_count: (family.required_accessories || []).length,
214
+ optional_accessories_count: (family.optional_accessories || []).length,
215
+ }],
216
+ });
217
+ }
218
+
219
+ /**
220
+ * Queries and verifies accessories for a product family, running physical compatibility checks.
221
+ *
222
+ * @param {string} familyId
223
+ * @param {Object} [options]
224
+ * @param {string} [options.sku] - Specific host SKU
225
+ * @param {string} [options.locale='zh']
226
+ * @returns {Object} Catalog DTO with structured, verified accessories
227
+ */
228
+ export function queryAccessories(familyId, { sku = null, locale = 'zh' } = {}) {
229
+ const core = loadKnowledgeCore();
230
+ const family = core.product_families?.[familyId];
231
+
232
+ if (!family) {
233
+ return createCatalogDto({
234
+ source: 'bundled_manifest',
235
+ freshness: 'FRESH',
236
+ records: [],
237
+ warnings: [`Product family "${familyId}" not found in local catalog.`],
238
+ });
239
+ }
240
+
241
+ const hostPhysicalSpec = family.physical_spec || null;
242
+
243
+ // Process required accessories
244
+ const required = (family.required_accessories || []).map((acc) => {
245
+ const comp = assertCompatibility(hostPhysicalSpec, acc);
246
+ return {
247
+ category: 'REQUIRED',
248
+ id: acc.id,
249
+ name: acc.name,
250
+ sku: acc.sku,
251
+ url: acc.url,
252
+ qty: acc.qty || 1,
253
+ reason: acc.reason || 'Required for standard operation',
254
+ applies_when: acc.applies_when || null,
255
+ compatibility: {
256
+ is_compatible: comp.isCompatible,
257
+ status: comp.status,
258
+ details: comp.dimensions,
259
+ },
260
+ };
261
+ });
262
+
263
+ // Process optional accessories
264
+ const optional = (family.optional_accessories || []).map((acc) => {
265
+ const comp = assertCompatibility(hostPhysicalSpec, acc);
266
+ return {
267
+ category: 'OPTIONAL',
268
+ id: acc.id,
269
+ name: acc.name,
270
+ sku: acc.sku,
271
+ url: acc.url,
272
+ type: acc.type || 'peripheral',
273
+ compatibility: {
274
+ is_compatible: comp.isCompatible,
275
+ status: comp.status,
276
+ details: comp.dimensions,
277
+ },
278
+ };
279
+ });
280
+
281
+ return createCatalogDto({
282
+ source: 'bundled_manifest',
283
+ freshness: 'FRESH',
284
+ records: [{
285
+ family_id: family.id,
286
+ host_title: family.title?.[locale] || family.title?.zh || family.title?.en,
287
+ host_sku: sku || family.default_sku,
288
+ physical_spec: hostPhysicalSpec,
289
+ accessories: {
290
+ required,
291
+ optional,
292
+ },
293
+ }],
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Generates an immutable DeploymentLockfile to isolate deployment apply from external network.
299
+ *
300
+ * @param {Object} options
301
+ * @param {string} options.solutionId
302
+ * @param {string} options.solutionVersion
303
+ * @param {Array<string>} options.selectedHardware
304
+ * @param {Array<Object>} [options.verifiedCompatibility=[]]
305
+ * @returns {Object} DeploymentLockfile
306
+ */
307
+ export function createDeploymentLockfile({
308
+ solutionId,
309
+ solutionVersion = '1.0.0',
310
+ selectedHardware = [],
311
+ verifiedCompatibility = [],
312
+ }) {
313
+ return {
314
+ $schema: 'https://spec.seeedstudio.com/schemas/v2/deployment-lock.json',
315
+ solution_id: solutionId,
316
+ solution_version: solutionVersion,
317
+ locked_at: new Date().toISOString(),
318
+ dataset_version: '2026-10-13.1',
319
+ selected_hardware: selectedHardware,
320
+ compatibility_assertions: verifiedCompatibility,
321
+ unresolved_requirements: [],
322
+ network_policy: {
323
+ commercial_queries_allowed: false,
324
+ apply_isolated: true,
325
+ },
326
+ };
327
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seeed-studio/solution-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Zero-friction installer and CLI runner for SenseCraft Engine and AI Agent Skills (Work Buddy, Claude Code, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "./lib/installer.js",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "bin",
19
19
  "lib",
20
+ "catalog",
20
21
  "skills",
21
22
  "README.md"
22
23
  ],
@@ -1,16 +1,23 @@
1
1
  ---
2
2
  name: sensecraft
3
- description: Deploy, manage, and troubleshoot SenseCraft / Seeed Studio edge IoT solutions (智慧农业, 摔倒检测, 边缘安全, 智慧仓库, 语音助手, people counting, face recognition 等) on hardware devices (NVIDIA Jetson, Raspberry Pi 树莓派, reComputer, reCamera, SenseCAP Watcher, Grove Vision AI, ESP32) using the `sensecraft-engine` command-line engine (no desktop app needed). Use whenever the user asks to deploy (部署), install (安装方案), test, inspect, or troubleshoot a SenseCraft edge IoT solution or configure devices and sensors. Changing a solution's configuration needs the desktop app (references/app-only.md).
3
+ description: Consult, select, deploy, manage, and troubleshoot SenseCraft / Seeed Studio edge IoT solutions (智慧农业, 摔倒检测, 边缘安全, 智慧仓库, 语音助手, people counting, face recognition 等) and hardware products (reComputer Jetson, reCamera, SenseCAP, Grove Vision AI, ESP32) using the `sensecraft-engine` and `solution-cli` toolset. Use whenever the user asks for edge IoT solutions, hardware recommendations, accessory compatibility, deployment, or troubleshooting.
4
4
  ---
5
5
 
6
6
  # SenseCraft Solution
7
7
 
8
- This skill drives the `sensecraft-engine` command-line engine. It works in any
9
- agent that can read files and run shell commands. Every step is a
10
- `sensecraft-engine` command; the engine starts its own background instance when
11
- a command needs one. Do not call the instance's HTTP port with `curl`: use the
12
- CLI, and `sensecraft-engine api <METHOD> <path>` for anything without its own
13
- command.
8
+ This skill drives the `sensecraft-engine` execution engine and `solution-cli` toolset. It operates across the complete lifecycle: **Solution Consultation ➔ Hardware & Accessory BOM Selection ➔ Local Deployment ➔ Self-Healing Diagnostics**.
9
+
10
+ ## Workflow Routing & Progressive Disclosure
11
+
12
+ Do NOT read all reference files at once. Determine the user's workflow state and intent, then load **at most ONE** reference file on demand:
13
+
14
+ | User State & Intent | Reference to Load | Primary CLI Tool to Call | Guardrails & Forbidden Actions |
15
+ |---|---|---|---|
16
+ | **DISCOVERY / CONSULT**<br>“我想做防跌倒监控”、“有智慧农场方案吗” | `references/consult-and-select.md` | `solution-cli hub search "<keyword>" --json` | **DO NOT run deploy**; user is exploring scenarios and has not confirmed hardware. |
17
+ | **PROCUREMENT / VERIFY_BOM**<br>“买什么硬件?”、“J4012 需要配什么配件?” | `references/hardware-bom.md` | `solution-cli product accessories <id> --json` | **DO NOT fabricate** third-party SKUs or prices. Always distinguish REQUIRED vs OPTIONAL accessories. |
18
+ | **ONBOARDING / PRECHECK**<br>“设备到了,插上了网线/USB”、“环境自检” | `references/diagnose.md` | `solution-cli doctor` / `sensecraft-engine device scan` | Freeze hardware requirements into DeploymentLockfile before proceeding to apply. |
19
+ | **DEPLOYING / APPLY**<br>“开始安装”、“部署 fall_detection” | `references/deploy.md` | `sensecraft-engine deploy --solution <id>` | **DEPLOYMENT CRITICAL SECTION**: Zero external commercial queries allowed during apply. |
20
+ | **TROUBLESHOOTING**<br>“部署报错”、“黑屏”、“无视频流” | `references/error-catalog.md` | `sensecraft-engine logs` / `solution-cli product verify-compatibility` | Max 3 fix-and-retry rounds. Follow local auto-patching rule. |
14
21
 
15
22
  Capabilities that need the **SenseCraft Solution desktop app** (editing a
16
23
  solution's configuration, the app's `/api/ai/context`, diagnosing through the
@@ -0,0 +1,56 @@
1
+ # 方案咨询与场景匹配手册 (Consult & Select Guide)
2
+
3
+ > **定位**:供 AI Agent 在用户处于 **场景探索(DISCOVERY)** 或 **方案咨询(CONSULT)** 时查阅。当用户询问“我想做某某事情”、“有什么物联网方案”、“如何解决 XX 问题”时,使用本手册定位场景、推荐参考设计并对齐硬件族。
4
+
5
+ ---
6
+
7
+ ## 1. 意图识别与场景路由规则
8
+
9
+ 当用户的自然语言输入包含以下关键词时,直接调用 CLI 工具查询相关场景:
10
+
11
+ | 行业领域 | 触发关键词(中英文) | 对应场景 Slug | 核心参考设计 |
12
+ |---|---|---|---|
13
+ | **智慧养老 / 监护** | 养老、跌倒检测、防摔、老人、呼叫、fall detection, elderly | `elderly-care-monitoring` | `fall_detection` |
14
+ | **智慧农业 / 环境** | 农田、大棚、温湿度、气象、土壤、agriculture, soil, greenhouse | `environment-monitoring` | `agri_env_monitor` |
15
+ | **工业质检 / 缺陷** | 缺陷检测、工业视觉、表面划痕、流水线装配、visual inspection, defect | `industrial-visual-inspection` | `edge_inspection_surface`, `edge_inspection_assembly` |
16
+ | **智慧交通 / 园区** | 车牌识别、停车场、车辆计数、停车位、anpr, license plate, parking | `parking-traffic-vision` | `edge-anpr-plate-recognition` |
17
+ | **边缘语音 / 助手** | 离线语音、大模型音箱、呼救感知、voice ai, audio assistant, respeaker | `conversational-voice-ai` | `home-assistant-local-voice-hub`, `conversational_voice_ai` |
18
+ | **建筑能耗 / 暖通** | 楼宇能耗、电表、水表、空调节能、HVAC, energy retrofit | `building-energy-retrofit` | `building_energy_management` |
19
+ | **室内外定位 / 资产** | 蓝牙信标、资产定位、人员卡片、防丢、GNSS, tracker, beacon, T1000 | `indoor-outdoor-positioning` | `indoor_positioning_ble_lorawan` |
20
+
21
+ ---
22
+
23
+ ## 2. 工具调用标准动作
24
+
25
+ ### 动作一:搜索场景与参考设计
26
+ 使用命令:
27
+ ```bash
28
+ solution-cli hub search "<用户关键词>" --limit 5 --json
29
+ ```
30
+ - **输出结果解释**:
31
+ - 提取匹配的 `Scenario`(阐述业务场景、痛点、成效);
32
+ - 提取推荐的 `Reference Design`(阐述技术路线、预设模式 presets、开发 wiki);
33
+ - 提取 `family_ids`(推荐的主板/相机/网关产品族)。
34
+
35
+ ### 动作二:向用户呈现专业方案推荐模板
36
+ 在向用户回答时,遵循以下结构化呈现,**严禁一上来就输出 deploy 命令**:
37
+
38
+ ```markdown
39
+ ### 💡 方案匹配建议
40
+
41
+ 1. **业务场景**:[场景名称](如:养老看护)
42
+ - **痛点与价值**:[简述核心业务价值,如:端侧跌倒检测,1.4s 本地快速判定,视频不出设备彻底保护隐私]
43
+ 2. **推荐参考设计**:[参考设计名称](ID: `fall_detection`)
44
+ - **技术方案**:[骨骼姿态估计 / MQTT 告警联动]
45
+ - **预设模式**:[如 recamera, jetson, rk, hailo]
46
+ - **开发 Wiki**:[官方 Wiki 链接]
47
+ 3. **推荐主硬件**:[硬件族列表,如 reComputer J4012, reCamera Pro]
48
+ - 采购与配件明细可进一步通过硬件选型指引查询。
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 3. 防幻觉与守则
54
+
55
+ 1. **禁止编造方案**:如果 `solution-cli hub search` 返回为空,诚实告知用户官方方案库暂无现成参考设计,并基于通用 Jetson / 树莓派 Docker 能力提供定制建议。
56
+ 2. **严禁在咨询期直接执行部署**:用户此时通常未采购或未连接硬件。应引导用户进入下一步【硬件选型与配件推荐】(查阅 `references/hardware-bom.md`)。
@@ -0,0 +1,83 @@
1
+ # 硬件选型与配件推荐准则 (Hardware & BOM Guide)
2
+
3
+ > **定位**:供 AI Agent 在用户处于 **采购选型(PROCUREMENT)** 或 **BOM 验证(VERIFY_BOM)** 阶段查阅。当用户询问“需要买什么硬件”、“XX 主板需要配什么配件”、“电源/天线怎么选”时使用本手册。
4
+
5
+ ---
6
+
7
+ ## 1. 核心流程:主板 ➔ 必选配件 ➔ 选配传感器/增强件
8
+
9
+ 为用户提供硬件采购清单时,必须严格区分:
10
+ 1. **主运算单元(Host Platform)**:如 `recomputer_j40`、`recamera_pro`、`sensecap_m2_gateway`。
11
+ 2. **必选配件(REQUIRED Accessories)**:**若缺少则系统物理上无法工作**(如:Super 款未配电源线、网关未配天线、工控机无输入电源端子)。
12
+ 3. **选配配件(OPTIONAL Accessories)**:根据具体功能选配(如:存储 SSD、4G 蜂窝网卡、降噪麦克风阵列、云台支架)。
13
+
14
+ ---
15
+
16
+ ## 2. 工具调用标准命令
17
+
18
+ ### 动作一:查询主设备规格与默认 SKU
19
+ ```bash
20
+ solution-cli product query <family_id> --json
21
+ ```
22
+ - 获取产品族名称、官方默认主 SKU、物理电气规格(电压、电流接口、M.2 槽位等)。
23
+
24
+ ### 动作二:查询经过验证的配件清单
25
+ ```bash
26
+ solution-cli product accessories <family_id> --json
27
+ ```
28
+ - 输出严格由确定性代码计算物理兼容性的配件清单:
29
+ - `REQUIRED` 列表:带必买原因(`reason`)、购买 SKU 与直达链接;
30
+ - `OPTIONAL` 列表:带周边传感器、通信模块、固态硬盘与购买链接。
31
+
32
+ ### 动作三:验证物理配件兼容性
33
+ 当用户提到已有某个第三方配件或自制电源/天线时:
34
+ ```bash
35
+ solution-cli product verify-compatibility --host <family_id> --accessory "<用户配件描述>" --json
36
+ ```
37
+
38
+ ---
39
+
40
+ ## 3. 六大物理兼容性硬红线(严禁大模型自行脑补)
41
+
42
+ Agent 严禁凭“常识”告知配件通用,必须遵循 CLI 评估结果:
43
+
44
+ 1. **供电 (Power Supply)**:
45
+ - `recomputer_j40` / `j30` 必须满足 **19V 4.74A (90W)**,DC Jack 规格为 **5.5x2.5mm**(与普通 5.5x2.1mm 路由器插头不兼容);
46
+ - USB-C 供电的设备(如 `recamera_2002`、`sensecap_watcher`),必须核对 PD 协议档位与最低电流。
47
+ 2. **射频与天线 (RF Antenna)**:
48
+ - 必须严格区分 **N 型(N-Type)** 与 **SMA**;
49
+ - 室外网关多为 **N-Type Female(母头)**,天线必须选 **N-Type Male(公头,带针)**,若为 SMA 天线必须提醒用户加购 **N转SMA转接线(Pigtail Cable)**;
50
+ - 频段必须对齐:欧洲选 `EU868`,北美选 `US915`,亚洲选 `AS923`。
51
+ 3. **M.2 扩展卡 (M.2 Key)**:
52
+ - **Key-M**:仅用于 NVMe SSD(尺寸通常为 2280);
53
+ - **Key-E**:仅用于 WiFi / 蓝牙 / AI 加速卡(尺寸通常为 2230);
54
+ - 严禁建议用户把 Key-M SSD 插到 Key-E 槽位。
55
+ 4. **以太网供电 (PoE)**:
56
+ - 区分 `802.3af`(15.4W)与 `802.3at`(30W PoE+)。`reCamera Pro` 需保证交换机端口支持 802.3at。
57
+
58
+ ---
59
+
60
+ ## 4. 推荐呈现模板
61
+
62
+ 向用户输出采购清单时,必须使用如下清晰结构:
63
+
64
+ ```markdown
65
+ ### 🛒 硬件采购清单建议(BOM)
66
+
67
+ #### 1. 主计算单元
68
+ - **产品型号**:reComputer J4012(NVIDIA Jetson Orin NX 16GB)
69
+ - **推荐 SKU**:`110110145`
70
+ - **购买链接**:[Seeed 官方商城直达](https://www.seeedstudio.com/...)
71
+
72
+ #### 2. 必需配件 (REQUIRED - 缺少无法开机)
73
+ - ⚠️ **19V 4.74A 电源适配器** (SKU: `313080684`)
74
+ - 原因:本主机出厂不配电源适配器,必须单独采购 5.5x2.5mm 19V 适配器。
75
+ - 购买链接:[购买电源](https://www.seeedstudio.com/...)
76
+ - ⚠️ **国标/美标 AC 三脚电源线** (SKU: `106990470`)
77
+
78
+ #### 3. 推荐选配 (OPTIONAL - 按需选购)
79
+ - **M.2 NVMe SSD 256GB** (SKU: `112990246`):用于部署 Docker 容器及缓存模型权重。
80
+ - **reSpeaker 4 麦克风阵列** (SKU: `107990193`):若需额外实现语音呼救感知功能。
81
+
82
+ > 收到设备并连接网线/电源后,只需对我说“设备已连接,开始部署”,我将直接为您自动化部署落地!
83
+ ```