@seeed-studio/solution-cli 0.1.0 → 0.3.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.
- package/README.md +101 -48
- package/bin/cli.js +201 -4
- package/catalog/client-config.json +10 -0
- package/catalog/knowledge-core.json +9308 -0
- package/docs/cli-guide.md +264 -0
- package/lib/agent.js +6 -0
- package/lib/catalog-dto.js +69 -0
- package/lib/compatibility.js +312 -0
- package/lib/knowledge.js +516 -0
- package/package.json +5 -1
- package/skills/sensecraft/SKILL.md +30 -7
- package/skills/sensecraft/references/consult-and-select.md +120 -0
- package/skills/sensecraft/references/device-management.md +94 -0
- package/skills/sensecraft/references/hardware-bom.md +83 -0
- package/skills/sensecraft/references/secondary-development.md +87 -0
package/lib/knowledge.js
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
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
|
+
* Appends UTM tracking parameters to a URL while preserving existing query parameters.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} urlStr - Target URL
|
|
19
|
+
* @param {Object} utmParams
|
|
20
|
+
* @param {string} [utmParams.source='sensecraft_agent']
|
|
21
|
+
* @param {string} [utmParams.medium='solution_consult']
|
|
22
|
+
* @param {string} [utmParams.campaign='']
|
|
23
|
+
* @param {string} [utmParams.content='']
|
|
24
|
+
* @returns {string} URL with appended UTM parameters
|
|
25
|
+
*/
|
|
26
|
+
export function appendUtm(urlStr, {
|
|
27
|
+
source = 'sensecraft_agent',
|
|
28
|
+
medium = 'solution_consult',
|
|
29
|
+
campaign = '',
|
|
30
|
+
content = '',
|
|
31
|
+
} = {}) {
|
|
32
|
+
if (!urlStr || typeof urlStr !== 'string') return urlStr || '';
|
|
33
|
+
try {
|
|
34
|
+
const url = new URL(urlStr);
|
|
35
|
+
if (!url.searchParams.has('utm_source') && source) {
|
|
36
|
+
url.searchParams.set('utm_source', source);
|
|
37
|
+
}
|
|
38
|
+
if (!url.searchParams.has('utm_medium') && medium) {
|
|
39
|
+
url.searchParams.set('utm_medium', medium);
|
|
40
|
+
}
|
|
41
|
+
if (!url.searchParams.has('utm_campaign') && campaign) {
|
|
42
|
+
url.searchParams.set('utm_campaign', campaign);
|
|
43
|
+
}
|
|
44
|
+
if (!url.searchParams.has('utm_content') && content) {
|
|
45
|
+
url.searchParams.set('utm_content', content);
|
|
46
|
+
}
|
|
47
|
+
return url.toString();
|
|
48
|
+
} catch {
|
|
49
|
+
return urlStr;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Loads the bundled offline knowledge core file.
|
|
55
|
+
*
|
|
56
|
+
* @returns {Object} Bundled knowledge core data
|
|
57
|
+
*/
|
|
58
|
+
export function loadKnowledgeCore() {
|
|
59
|
+
if (cachedKnowledgeCore) return cachedKnowledgeCore;
|
|
60
|
+
if (!fs.existsSync(DATA_FILE)) {
|
|
61
|
+
throw new Error(`Knowledge core manifest not found at ${DATA_FILE}. Run "npm run build:knowledge" to generate.`);
|
|
62
|
+
}
|
|
63
|
+
const raw = fs.readFileSync(DATA_FILE, 'utf-8');
|
|
64
|
+
cachedKnowledgeCore = JSON.parse(raw);
|
|
65
|
+
return cachedKnowledgeCore;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Performs an HTTP GET request with a timeout.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} urlStr
|
|
72
|
+
* @param {Object} headers
|
|
73
|
+
* @param {number} timeoutMs
|
|
74
|
+
* @returns {Promise<Object>}
|
|
75
|
+
*/
|
|
76
|
+
function fetchJson(urlStr, headers = {}, timeoutMs = 2500) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const url = new URL(urlStr);
|
|
79
|
+
const client = url.protocol === 'https:' ? https : http;
|
|
80
|
+
const req = client.get(url, { headers, timeout: timeoutMs }, (res) => {
|
|
81
|
+
let body = '';
|
|
82
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
83
|
+
res.on('end', () => {
|
|
84
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
85
|
+
try {
|
|
86
|
+
resolve(JSON.parse(body));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
reject(new Error(`Invalid JSON from ${urlStr}: ${e.message}`));
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
reject(new Error(`HTTP ${res.statusCode} from ${urlStr}`));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
req.on('timeout', () => {
|
|
96
|
+
req.destroy();
|
|
97
|
+
reject(new Error(`Timeout after ${timeoutMs}ms from ${urlStr}`));
|
|
98
|
+
});
|
|
99
|
+
req.on('error', reject);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const CONFIG_FILE = path.resolve(__dirname, '../catalog/client-config.json');
|
|
104
|
+
let cachedClientConfig = null;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Loads build-time baked client configuration if present.
|
|
108
|
+
*
|
|
109
|
+
* @returns {Object|null}
|
|
110
|
+
*/
|
|
111
|
+
export function loadClientConfig() {
|
|
112
|
+
if (cachedClientConfig) return cachedClientConfig;
|
|
113
|
+
try {
|
|
114
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
115
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
116
|
+
cachedClientConfig = JSON.parse(raw);
|
|
117
|
+
return cachedClientConfig;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Graceful fallback if missing or invalid
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Searches Seeed Solutions Hub scenarios and reference designs.
|
|
127
|
+
*
|
|
128
|
+
* @param {Object} options
|
|
129
|
+
* @param {string} options.query - Free text search query
|
|
130
|
+
* @param {'zh' | 'en'} [options.locale='zh'] - Language preference
|
|
131
|
+
* @param {number} [options.limit=5] - Maximum number of results
|
|
132
|
+
* @returns {Promise<Object>} Standardized Catalog DTO
|
|
133
|
+
*/
|
|
134
|
+
export async function searchHub({ query = '', locale = 'zh', limit = 5 } = {}) {
|
|
135
|
+
const qLower = (query || '').toLowerCase().trim();
|
|
136
|
+
const clientConfig = loadClientConfig();
|
|
137
|
+
const host = process.env.TYPESENSE_HOST || clientConfig?.typesense?.host || 'https://rel-search.seeedstudio.com';
|
|
138
|
+
const keySolution = process.env.TYPESENSE_SEARCH_KEY_SOLUTION || process.env.TYPESENSE_SEARCH_KEY || clientConfig?.typesense?.search_key_solution;
|
|
139
|
+
const keyRefDesign = process.env.TYPESENSE_SEARCH_KEY_REFERENCE_DESIGN || process.env.TYPESENSE_SEARCH_KEY || clientConfig?.typesense?.search_key_reference_design;
|
|
140
|
+
|
|
141
|
+
// Attempt online Typesense search if keys are provided and not explicitly running offline
|
|
142
|
+
if ((keySolution || keyRefDesign) && !process.env.SENSECRAFT_OFFLINE_ONLY) {
|
|
143
|
+
try {
|
|
144
|
+
const core = loadKnowledgeCore();
|
|
145
|
+
const onlinePromises = [];
|
|
146
|
+
|
|
147
|
+
if (keySolution) {
|
|
148
|
+
const solUrl = `${host}/collections/Solution/documents/search?q=${encodeURIComponent(query)}&query_by=title_zh_hans,title_en,excerpt_zh_hans,excerpt_en,display_tags_zh_hans&per_page=${limit}`;
|
|
149
|
+
onlinePromises.push(
|
|
150
|
+
fetchJson(solUrl, { 'X-TYPESENSE-API-KEY': keySolution }, 2500)
|
|
151
|
+
.then((data) => ({ type: 'scenario', hits: data?.hits || [] }))
|
|
152
|
+
.catch(() => ({ type: 'scenario', hits: [] }))
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (keyRefDesign) {
|
|
157
|
+
const rdUrl = `${host}/collections/reference_design/documents/search?q=${encodeURIComponent(query)}&query_by=name_zh_hans,name_en,summary_zh_hans,summary_en,tags&per_page=${limit}`;
|
|
158
|
+
onlinePromises.push(
|
|
159
|
+
fetchJson(rdUrl, { 'X-TYPESENSE-API-KEY': keyRefDesign }, 2500)
|
|
160
|
+
.then((data) => ({ type: 'reference_design', hits: data?.hits || [] }))
|
|
161
|
+
.catch(() => ({ type: 'reference_design', hits: [] }))
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const results = await Promise.all(onlinePromises);
|
|
166
|
+
const onlineRecords = [];
|
|
167
|
+
|
|
168
|
+
for (const res of results) {
|
|
169
|
+
if (res.type === 'scenario') {
|
|
170
|
+
for (const hit of res.hits) {
|
|
171
|
+
const doc = hit.document || {};
|
|
172
|
+
const docId = doc.id || doc.slug;
|
|
173
|
+
const title = locale === 'en'
|
|
174
|
+
? (doc.title_en || doc.title_zh_hans || docId)
|
|
175
|
+
: (doc.title_zh_hans || doc.title_en || docId);
|
|
176
|
+
const summary = locale === 'en'
|
|
177
|
+
? (doc.excerpt_en || doc.seo_description_en || doc.excerpt_zh_hans || '')
|
|
178
|
+
: (doc.excerpt_zh_hans || doc.seo_description_zh_hans || doc.excerpt_en || '');
|
|
179
|
+
const tags = (locale === 'en' ? doc.display_tags_en : doc.display_tags_zh_hans) || doc.display_tags || [];
|
|
180
|
+
const pb = core.diagnostic_playbooks?.[docId] || doc.diagnostic_playbook || null;
|
|
181
|
+
|
|
182
|
+
onlineRecords.push({
|
|
183
|
+
type: 'scenario',
|
|
184
|
+
id: docId,
|
|
185
|
+
title,
|
|
186
|
+
summary,
|
|
187
|
+
tags,
|
|
188
|
+
family_ids: doc.family_ids || [],
|
|
189
|
+
reference_designs: doc.reference_designs || [],
|
|
190
|
+
landing_url: appendUtm(`https://www.seeedstudio.com/solutions/${docId}`, {
|
|
191
|
+
source: 'sensecraft_agent',
|
|
192
|
+
medium: 'solution_consult',
|
|
193
|
+
campaign: docId,
|
|
194
|
+
content: 'scenario_landing',
|
|
195
|
+
}),
|
|
196
|
+
repo_url: '',
|
|
197
|
+
diagnostic_playbook: pb,
|
|
198
|
+
source: 'typesense',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} else if (res.type === 'reference_design') {
|
|
202
|
+
for (const hit of res.hits) {
|
|
203
|
+
const doc = hit.document || {};
|
|
204
|
+
const docId = doc.id;
|
|
205
|
+
const title = locale === 'en'
|
|
206
|
+
? (doc.name_en || doc.name_zh_hans || docId)
|
|
207
|
+
: (doc.name_zh_hans || doc.name_en || docId);
|
|
208
|
+
const summary = locale === 'en'
|
|
209
|
+
? (doc.summary_en || doc.tagline_en || doc.summary_zh_hans || '')
|
|
210
|
+
: (doc.summary_zh_hans || doc.tagline_zh_hans || doc.summary_en || '');
|
|
211
|
+
|
|
212
|
+
let pb = doc.diagnostic_playbook || null;
|
|
213
|
+
if (!pb) {
|
|
214
|
+
for (const sc of core.scenarios || []) {
|
|
215
|
+
if ((sc.reference_designs || []).includes(docId)) {
|
|
216
|
+
pb = sc.diagnostic_playbook || core.diagnostic_playbooks?.[sc.id] || null;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
onlineRecords.push({
|
|
223
|
+
type: 'reference_design',
|
|
224
|
+
id: docId,
|
|
225
|
+
title,
|
|
226
|
+
summary,
|
|
227
|
+
presets: doc.presets || [],
|
|
228
|
+
landing_url: appendUtm(`https://www.seeedstudio.com/solutions/reference-designs/${docId}`, {
|
|
229
|
+
source: 'sensecraft_agent',
|
|
230
|
+
medium: 'solution_consult',
|
|
231
|
+
campaign: docId,
|
|
232
|
+
content: 'solution_landing',
|
|
233
|
+
}),
|
|
234
|
+
wiki_url: doc.wiki_url ? appendUtm(doc.wiki_url, {
|
|
235
|
+
source: 'sensecraft_agent',
|
|
236
|
+
medium: 'tech_wiki',
|
|
237
|
+
campaign: docId,
|
|
238
|
+
}) : '',
|
|
239
|
+
repo_url: doc.repo_url || `https://github.com/suharvest/sensecraft-solutions/tree/main/solutions/${docId}`,
|
|
240
|
+
diagnostic_playbook: pb,
|
|
241
|
+
source: 'typesense',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (onlineRecords.length > 0) {
|
|
248
|
+
return createCatalogDto({
|
|
249
|
+
source: 'typesense',
|
|
250
|
+
freshness: 'FRESH',
|
|
251
|
+
records: onlineRecords.slice(0, limit),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
// Graceful fallback to offline core with notice
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Offline fallback search over knowledge-core
|
|
260
|
+
const core = loadKnowledgeCore();
|
|
261
|
+
const matchedScenarios = [];
|
|
262
|
+
const matchedRefDesigns = [];
|
|
263
|
+
|
|
264
|
+
const tokens = qLower.split(/\s+/).filter(Boolean);
|
|
265
|
+
|
|
266
|
+
for (const s of core.scenarios || []) {
|
|
267
|
+
const haystack = [
|
|
268
|
+
s.id,
|
|
269
|
+
s.title?.zh,
|
|
270
|
+
s.title?.en,
|
|
271
|
+
s.subtitle?.zh,
|
|
272
|
+
s.subtitle?.en,
|
|
273
|
+
s.summary,
|
|
274
|
+
...(s.tags || []),
|
|
275
|
+
].join(' ').toLowerCase();
|
|
276
|
+
|
|
277
|
+
const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);
|
|
278
|
+
if (tokens.length === 0 || score > 0) {
|
|
279
|
+
matchedScenarios.push({
|
|
280
|
+
type: 'scenario',
|
|
281
|
+
id: s.id,
|
|
282
|
+
title: s.title?.[locale] || s.title?.zh || s.title?.en || s.id,
|
|
283
|
+
summary: s.subtitle?.[locale] || s.subtitle?.zh || s.summary || '',
|
|
284
|
+
tags: s.tags || [],
|
|
285
|
+
family_ids: s.family_ids || [],
|
|
286
|
+
reference_designs: s.reference_designs || [],
|
|
287
|
+
landing_url: appendUtm(s.landing_url || `https://www.seeedstudio.com/solutions/${s.id}`, {
|
|
288
|
+
source: 'sensecraft_agent',
|
|
289
|
+
medium: 'solution_consult',
|
|
290
|
+
campaign: s.id,
|
|
291
|
+
content: 'scenario_landing',
|
|
292
|
+
}),
|
|
293
|
+
repo_url: s.repo_url || '',
|
|
294
|
+
diagnostic_playbook: s.diagnostic_playbook || null,
|
|
295
|
+
score,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for (const rd of core.reference_designs || []) {
|
|
301
|
+
const haystack = [
|
|
302
|
+
rd.id,
|
|
303
|
+
rd.title?.zh,
|
|
304
|
+
rd.title?.en,
|
|
305
|
+
rd.summary?.zh,
|
|
306
|
+
rd.summary?.en,
|
|
307
|
+
...(rd.presets || []),
|
|
308
|
+
].join(' ').toLowerCase();
|
|
309
|
+
|
|
310
|
+
const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);
|
|
311
|
+
if (tokens.length === 0 || score > 0) {
|
|
312
|
+
matchedRefDesigns.push({
|
|
313
|
+
type: 'reference_design',
|
|
314
|
+
id: rd.id,
|
|
315
|
+
title: rd.title?.[locale] || rd.title?.zh || rd.title?.en || rd.id,
|
|
316
|
+
summary: rd.summary?.[locale] || rd.summary?.zh || rd.summary?.en || '',
|
|
317
|
+
presets: rd.presets || [],
|
|
318
|
+
landing_url: appendUtm(rd.landing_url || `https://www.seeedstudio.com/solutions/reference-designs/${rd.id}`, {
|
|
319
|
+
source: 'sensecraft_agent',
|
|
320
|
+
medium: 'solution_consult',
|
|
321
|
+
campaign: rd.id,
|
|
322
|
+
content: 'solution_landing',
|
|
323
|
+
}),
|
|
324
|
+
wiki_url: rd.wiki_url ? appendUtm(rd.wiki_url, {
|
|
325
|
+
source: 'sensecraft_agent',
|
|
326
|
+
medium: 'tech_wiki',
|
|
327
|
+
campaign: rd.id,
|
|
328
|
+
}) : '',
|
|
329
|
+
repo_url: rd.repo_url || `https://github.com/suharvest/sensecraft-solutions/tree/main/solutions/${rd.id}`,
|
|
330
|
+
diagnostic_playbook: rd.diagnostic_playbook || null,
|
|
331
|
+
score,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Sort by score descending and take top results
|
|
337
|
+
matchedScenarios.sort((a, b) => b.score - a.score);
|
|
338
|
+
matchedRefDesigns.sort((a, b) => b.score - a.score);
|
|
339
|
+
|
|
340
|
+
const combined = [...matchedScenarios, ...matchedRefDesigns].slice(0, limit);
|
|
341
|
+
|
|
342
|
+
return createCatalogDto({
|
|
343
|
+
source: 'bundled_manifest',
|
|
344
|
+
freshness: 'FRESH',
|
|
345
|
+
records: combined,
|
|
346
|
+
warnings: (keySolution || keyRefDesign)
|
|
347
|
+
? ['Online Typesense request timed out or returned no hits. Displaying verified local catalog.']
|
|
348
|
+
: ['No Typesense API key configured or baked. Displaying verified local catalog.'],
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Queries product family information, SKUs, and official purchase links.
|
|
354
|
+
*
|
|
355
|
+
* @param {string} familyId
|
|
356
|
+
* @param {Object} [options]
|
|
357
|
+
* @param {string} [options.locale='zh']
|
|
358
|
+
* @returns {Object} Catalog DTO with product details
|
|
359
|
+
*/
|
|
360
|
+
export function queryProductFamily(familyId, { locale = 'zh' } = {}) {
|
|
361
|
+
const core = loadKnowledgeCore();
|
|
362
|
+
const family = core.product_families?.[familyId];
|
|
363
|
+
|
|
364
|
+
if (!family) {
|
|
365
|
+
return createCatalogDto({
|
|
366
|
+
source: 'bundled_manifest',
|
|
367
|
+
freshness: 'FRESH',
|
|
368
|
+
records: [],
|
|
369
|
+
warnings: [`Product family "${familyId}" not found in local catalog.`],
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return createCatalogDto({
|
|
374
|
+
source: 'bundled_manifest',
|
|
375
|
+
freshness: 'FRESH',
|
|
376
|
+
records: [{
|
|
377
|
+
family_id: family.id,
|
|
378
|
+
title: family.title?.[locale] || family.title?.zh || family.title?.en || family.id,
|
|
379
|
+
default_sku: family.default_sku,
|
|
380
|
+
regions: family.regions || [],
|
|
381
|
+
skus_count: (family.skus || []).length,
|
|
382
|
+
skus: (family.skus || []).slice(0, 10).map((s) => ({
|
|
383
|
+
...s,
|
|
384
|
+
url: appendUtm(s.url, {
|
|
385
|
+
source: 'sensecraft_agent',
|
|
386
|
+
medium: 'bom_recommendation',
|
|
387
|
+
campaign: family.id,
|
|
388
|
+
content: `sku_${s.sku}`,
|
|
389
|
+
}),
|
|
390
|
+
})),
|
|
391
|
+
physical_spec: family.physical_spec || null,
|
|
392
|
+
required_accessories_count: (family.required_accessories || []).length,
|
|
393
|
+
optional_accessories_count: (family.optional_accessories || []).length,
|
|
394
|
+
}],
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Queries and verifies accessories for a product family, running physical compatibility checks.
|
|
400
|
+
*
|
|
401
|
+
* @param {string} familyId
|
|
402
|
+
* @param {Object} [options]
|
|
403
|
+
* @param {string} [options.sku] - Specific host SKU
|
|
404
|
+
* @param {string} [options.locale='zh']
|
|
405
|
+
* @returns {Object} Catalog DTO with structured, verified accessories
|
|
406
|
+
*/
|
|
407
|
+
export function queryAccessories(familyId, { sku = null, locale = 'zh' } = {}) {
|
|
408
|
+
const core = loadKnowledgeCore();
|
|
409
|
+
const family = core.product_families?.[familyId];
|
|
410
|
+
|
|
411
|
+
if (!family) {
|
|
412
|
+
return createCatalogDto({
|
|
413
|
+
source: 'bundled_manifest',
|
|
414
|
+
freshness: 'FRESH',
|
|
415
|
+
records: [],
|
|
416
|
+
warnings: [`Product family "${familyId}" not found in local catalog.`],
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const hostPhysicalSpec = family.physical_spec || null;
|
|
421
|
+
|
|
422
|
+
// Process required accessories
|
|
423
|
+
const required = (family.required_accessories || []).map((acc) => {
|
|
424
|
+
const comp = assertCompatibility(hostPhysicalSpec, acc);
|
|
425
|
+
return {
|
|
426
|
+
category: 'REQUIRED',
|
|
427
|
+
id: acc.id,
|
|
428
|
+
name: acc.name,
|
|
429
|
+
sku: acc.sku,
|
|
430
|
+
url: appendUtm(acc.url, {
|
|
431
|
+
source: 'sensecraft_agent',
|
|
432
|
+
medium: 'bom_recommendation',
|
|
433
|
+
campaign: family.id,
|
|
434
|
+
content: `required_${acc.id}`,
|
|
435
|
+
}),
|
|
436
|
+
qty: acc.qty || 1,
|
|
437
|
+
reason: acc.reason || 'Required for standard operation',
|
|
438
|
+
applies_when: acc.applies_when || null,
|
|
439
|
+
compatibility: {
|
|
440
|
+
is_compatible: comp.isCompatible,
|
|
441
|
+
status: comp.status,
|
|
442
|
+
details: comp.dimensions,
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Process optional accessories
|
|
448
|
+
const optional = (family.optional_accessories || []).map((acc) => {
|
|
449
|
+
const comp = assertCompatibility(hostPhysicalSpec, acc);
|
|
450
|
+
return {
|
|
451
|
+
category: 'OPTIONAL',
|
|
452
|
+
id: acc.id,
|
|
453
|
+
name: acc.name,
|
|
454
|
+
sku: acc.sku,
|
|
455
|
+
url: appendUtm(acc.url, {
|
|
456
|
+
source: 'sensecraft_agent',
|
|
457
|
+
medium: 'bom_recommendation',
|
|
458
|
+
campaign: family.id,
|
|
459
|
+
content: `optional_${acc.id}`,
|
|
460
|
+
}),
|
|
461
|
+
type: acc.type || 'peripheral',
|
|
462
|
+
compatibility: {
|
|
463
|
+
is_compatible: comp.isCompatible,
|
|
464
|
+
status: comp.status,
|
|
465
|
+
details: comp.dimensions,
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return createCatalogDto({
|
|
471
|
+
source: 'bundled_manifest',
|
|
472
|
+
freshness: 'FRESH',
|
|
473
|
+
records: [{
|
|
474
|
+
family_id: family.id,
|
|
475
|
+
host_title: family.title?.[locale] || family.title?.zh || family.title?.en,
|
|
476
|
+
host_sku: sku || family.default_sku,
|
|
477
|
+
physical_spec: hostPhysicalSpec,
|
|
478
|
+
accessories: {
|
|
479
|
+
required,
|
|
480
|
+
optional,
|
|
481
|
+
},
|
|
482
|
+
}],
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Generates an immutable DeploymentLockfile to isolate deployment apply from external network.
|
|
488
|
+
*
|
|
489
|
+
* @param {Object} options
|
|
490
|
+
* @param {string} options.solutionId
|
|
491
|
+
* @param {string} options.solutionVersion
|
|
492
|
+
* @param {Array<string>} options.selectedHardware
|
|
493
|
+
* @param {Array<Object>} [options.verifiedCompatibility=[]]
|
|
494
|
+
* @returns {Object} DeploymentLockfile
|
|
495
|
+
*/
|
|
496
|
+
export function createDeploymentLockfile({
|
|
497
|
+
solutionId,
|
|
498
|
+
solutionVersion = '1.0.0',
|
|
499
|
+
selectedHardware = [],
|
|
500
|
+
verifiedCompatibility = [],
|
|
501
|
+
}) {
|
|
502
|
+
return {
|
|
503
|
+
$schema: 'https://spec.seeedstudio.com/schemas/v2/deployment-lock.json',
|
|
504
|
+
solution_id: solutionId,
|
|
505
|
+
solution_version: solutionVersion,
|
|
506
|
+
locked_at: new Date().toISOString(),
|
|
507
|
+
dataset_version: '2026-10-13.1',
|
|
508
|
+
selected_hardware: selectedHardware,
|
|
509
|
+
compatibility_assertions: verifiedCompatibility,
|
|
510
|
+
unresolved_requirements: [],
|
|
511
|
+
network_policy: {
|
|
512
|
+
commercial_queries_allowed: false,
|
|
513
|
+
apply_isolated: true,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seeed-studio/solution-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,10 +17,14 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"bin",
|
|
19
19
|
"lib",
|
|
20
|
+
"catalog",
|
|
20
21
|
"skills",
|
|
22
|
+
"docs",
|
|
21
23
|
"README.md"
|
|
22
24
|
],
|
|
23
25
|
"scripts": {
|
|
26
|
+
"build": "node scripts/bake-client-config.mjs",
|
|
27
|
+
"prepack": "node scripts/bake-client-config.mjs",
|
|
24
28
|
"test": "node --test tests/cli.test.mjs",
|
|
25
29
|
"install:self": "node bin/cli.js install",
|
|
26
30
|
"status": "node bin/cli.js status",
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sensecraft
|
|
3
|
-
description:
|
|
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`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
| **MANAGE / RECONFIG**<br>“改一下 MQTT 地址”、“查看已安装应用”、“重启服务”、“修改配置” | `references/device-management.md` | `curl BASE/api/docker-devices/local/config/<id>` | **Check before write**: Only alter `reconfigurable: true` fields; check current value before modifying. |
|
|
21
|
+
| **CUSTOMIZE / SECONDARY_DEV**<br>“我想改源码”、“换成自研模型”、“改前端看板”、“GitHub 仓库” | `references/secondary-development.md` | `git clone <repo_url>` / `sensecraft-engine --solutions-dir` | **Never edit cached content**: Clone to user workspace, pair-program, test locally with `--solutions-dir`. |
|
|
22
|
+
| **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
23
|
|
|
15
24
|
Capabilities that need the **SenseCraft Solution desktop app** (editing a
|
|
16
25
|
solution's configuration, the app's `/api/ai/context`, diagnosing through the
|
|
@@ -79,6 +88,20 @@ daemon is a host prerequisite, not evidence that a deployment has passed.
|
|
|
79
88
|
6. **Temp files:** keep request bodies and scratch files in the current
|
|
80
89
|
working directory under `./.sensecraft-work/`, never in `/tmp`. Remove the
|
|
81
90
|
directory when the task ends, also after a failure.
|
|
91
|
+
7. **Conversational tone & soft referral:** Agent is an IoT domain expert and
|
|
92
|
+
answers technical, business, hardware, and configuration questions directly
|
|
93
|
+
and thoroughly using local knowledge and tools. Do NOT dump raw link lists
|
|
94
|
+
or write pushy sales pitches. External links (`landing_url`, `wiki_url`,
|
|
95
|
+
product purchase URLs, `repo_url`) must be smoothly integrated as
|
|
96
|
+
supplemental reading / procurement / code exploration. All URLs generated by
|
|
97
|
+
`solution-cli` automatically carry silent UTM parameters
|
|
98
|
+
(`utm_source=sensecraft_agent`) for analytics attribution.
|
|
99
|
+
8. **Proactive diagnosis & domain-specific questioning:** During solution consultation,
|
|
100
|
+
Agent must avoid one-way monologues. Proactively inspect the `diagnostic_playbook`
|
|
101
|
+
returned by `solution-cli hub search` (or cross-reference `references/consult-and-select.md` §3).
|
|
102
|
+
Ask 2–3 targeted domain questions explaining the engineering rationale (topology,
|
|
103
|
+
asset reuse, power/cabling, inspection speed/materials) to help the user converge
|
|
104
|
+
on the optimal hardware architecture and budget.
|
|
82
105
|
|
|
83
106
|
## Step 0 — Install the engine and the solutions (every task)
|
|
84
107
|
|