@seeed-studio/solution-cli 0.2.0 → 0.3.1
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 +111 -46
- package/bin/cli.js +7 -1
- package/catalog/client-config.json +10 -0
- package/catalog/knowledge-core.json +1923 -63
- package/docs/cli-guide.md +277 -0
- package/lib/agent.js +6 -0
- package/lib/downloader.js +85 -21
- package/lib/installer.js +50 -43
- package/lib/knowledge.js +343 -30
- package/package.json +4 -1
- package/skills/sensecraft/SKILL.md +39 -10
- package/skills/sensecraft/references/consult-and-select.md +86 -22
- package/skills/sensecraft/references/device-management.md +126 -0
- package/skills/sensecraft/references/diagnose.md +1 -1
- package/skills/sensecraft/references/hardware-bom.md +16 -16
- package/skills/sensecraft/references/secondary-development.md +84 -0
package/lib/knowledge.js
CHANGED
|
@@ -12,6 +12,44 @@ const DATA_FILE = path.join(__dirname, '..', 'catalog', 'knowledge-core.json');
|
|
|
12
12
|
|
|
13
13
|
let cachedKnowledgeCore = null;
|
|
14
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
|
+
|
|
15
53
|
/**
|
|
16
54
|
* Loads the bundled offline knowledge core file.
|
|
17
55
|
*
|
|
@@ -62,6 +100,28 @@ function fetchJson(urlStr, headers = {}, timeoutMs = 2500) {
|
|
|
62
100
|
});
|
|
63
101
|
}
|
|
64
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
|
+
|
|
65
125
|
/**
|
|
66
126
|
* Searches Seeed Solutions Hub scenarios and reference designs.
|
|
67
127
|
*
|
|
@@ -73,34 +133,232 @@ function fetchJson(urlStr, headers = {}, timeoutMs = 2500) {
|
|
|
73
133
|
*/
|
|
74
134
|
export async function searchHub({ query = '', locale = 'zh', limit = 5 } = {}) {
|
|
75
135
|
const qLower = (query || '').toLowerCase().trim();
|
|
76
|
-
const
|
|
77
|
-
const host = process.env.TYPESENSE_HOST || 'https://rel-search.seeedstudio.com';
|
|
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;
|
|
78
140
|
|
|
79
|
-
// Attempt online Typesense search if
|
|
80
|
-
if (
|
|
141
|
+
// Attempt online Typesense search if keys are provided and not explicitly running offline
|
|
142
|
+
if ((keySolution || keyRefDesign) && !process.env.SENSECRAFT_OFFLINE_ONLY) {
|
|
81
143
|
try {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
144
|
+
const core = loadKnowledgeCore();
|
|
145
|
+
const onlinePromises = [];
|
|
146
|
+
|
|
147
|
+
const locKey = locale === 'zh' ? 'zh_hans' : locale;
|
|
148
|
+
const fetchLimit = Math.max(limit * 5, 30);
|
|
149
|
+
|
|
150
|
+
if (keySolution) {
|
|
151
|
+
const solFilter = 'published:!=false';
|
|
152
|
+
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&filter_by=${encodeURIComponent(solFilter)}&per_page=${fetchLimit}`;
|
|
153
|
+
onlinePromises.push(
|
|
154
|
+
fetchJson(solUrl, { 'X-TYPESENSE-API-KEY': keySolution }, 2500)
|
|
155
|
+
.then((data) => ({ type: 'scenario', hits: data?.hits || [] }))
|
|
156
|
+
.catch(() => ({ type: 'scenario', hits: [] }))
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (keyRefDesign) {
|
|
161
|
+
const rdFilter = 'published:!=false';
|
|
162
|
+
const rdUrl = `${host}/collections/reference_design/documents/search?q=${encodeURIComponent(query)}&query_by=name_zh_hans,name_en,summary_zh_hans,summary_en,tags&filter_by=${encodeURIComponent(rdFilter)}&per_page=${fetchLimit}`;
|
|
163
|
+
onlinePromises.push(
|
|
164
|
+
fetchJson(rdUrl, { 'X-TYPESENSE-API-KEY': keyRefDesign }, 2500)
|
|
165
|
+
.then((data) => ({ type: 'reference_design', hits: data?.hits || [] }))
|
|
166
|
+
.catch(() => ({ type: 'reference_design', hits: [] }))
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const results = await Promise.all(onlinePromises);
|
|
171
|
+
const onlineScenarios = [];
|
|
172
|
+
const onlineRefDesigns = [];
|
|
173
|
+
|
|
174
|
+
for (const res of results) {
|
|
175
|
+
if (res.type === 'scenario') {
|
|
176
|
+
for (const hit of res.hits) {
|
|
177
|
+
const doc = hit.document || {};
|
|
178
|
+
// Filter unreleased or staging scenarios from production recommendations
|
|
179
|
+
if (doc.published === false || doc.visibility === 'staging' || doc.visibility === 'draft') continue;
|
|
180
|
+
if (doc[`visibility_${locKey}`] === 'staging' || doc[`visibility_${locKey}`] === 'draft') continue;
|
|
181
|
+
|
|
182
|
+
const docId = doc.id || doc.slug;
|
|
183
|
+
const docType = doc.doc_type || 'scenario';
|
|
184
|
+
const title = locale === 'en'
|
|
185
|
+
? (doc.title_en || doc.title_zh_hans || docId)
|
|
186
|
+
: (doc.title_zh_hans || doc.title_en || docId);
|
|
187
|
+
const summary = locale === 'en'
|
|
188
|
+
? (doc.excerpt_en || doc.seo_description_en || doc.excerpt_zh_hans || '')
|
|
189
|
+
: (doc.excerpt_zh_hans || doc.seo_description_zh_hans || doc.excerpt_en || '');
|
|
190
|
+
const tags = (locale === 'en' ? doc.display_tags_en : doc.display_tags_zh_hans) || doc.display_tags || [];
|
|
191
|
+
const pb = core.diagnostic_playbooks?.[docId] || doc.diagnostic_playbook || null;
|
|
192
|
+
const rawLanding = doc.external_url || doc.landing_url || `https://www.seeedstudio.com/solutions/${docId}`;
|
|
193
|
+
|
|
194
|
+
onlineScenarios.push({
|
|
195
|
+
type: docType,
|
|
196
|
+
id: docId,
|
|
197
|
+
title,
|
|
198
|
+
summary,
|
|
199
|
+
tags,
|
|
200
|
+
family_ids: doc.family_ids || [],
|
|
201
|
+
reference_designs: doc.reference_designs || [],
|
|
202
|
+
landing_url: appendUtm(rawLanding, {
|
|
203
|
+
source: 'sensecraft_agent',
|
|
204
|
+
medium: 'solution_consult',
|
|
205
|
+
campaign: docId,
|
|
206
|
+
content: `${docType}_landing`,
|
|
207
|
+
}),
|
|
208
|
+
repo_url: doc.repo_url || '',
|
|
209
|
+
diagnostic_playbook: pb,
|
|
210
|
+
source: 'typesense',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
} else if (res.type === 'reference_design') {
|
|
214
|
+
for (const hit of res.hits) {
|
|
215
|
+
const doc = hit.document || {};
|
|
216
|
+
// Filter unreleased or staging reference designs from production recommendations
|
|
217
|
+
if (doc.published === false || doc.visibility === 'staging' || doc.visibility === 'draft') continue;
|
|
218
|
+
const locKey = locale === 'zh' ? 'zh_hans' : locale;
|
|
219
|
+
if (doc[`visibility_${locKey}`] === 'staging' || doc[`visibility_${locKey}`] === 'draft') continue;
|
|
220
|
+
const docId = doc.id;
|
|
221
|
+
const title = locale === 'en'
|
|
222
|
+
? (doc.name_en || doc.name_zh_hans || docId)
|
|
223
|
+
: (doc.name_zh_hans || doc.name_en || docId);
|
|
224
|
+
const summary = locale === 'en'
|
|
225
|
+
? (doc.summary_en || doc.tagline_en || doc.summary_zh_hans || '')
|
|
226
|
+
: (doc.summary_zh_hans || doc.tagline_zh_hans || doc.summary_en || '');
|
|
227
|
+
|
|
228
|
+
let pb = doc.diagnostic_playbook || core.diagnostic_playbooks?.[docId] || null;
|
|
229
|
+
if (!pb) {
|
|
230
|
+
for (const sc of core.scenarios || []) {
|
|
231
|
+
if ((sc.reference_designs || []).includes(docId)) {
|
|
232
|
+
pb = sc.diagnostic_playbook || core.diagnostic_playbooks?.[sc.id] || null;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const rawPresets = doc.presets || [];
|
|
239
|
+
const presets = rawPresets.map((p) => {
|
|
240
|
+
if (typeof p === 'string') return p;
|
|
241
|
+
if (p && typeof p === 'object') {
|
|
242
|
+
return (locale === 'en' ? (p.name_en || p.name || p.id) : (p.name_zh_hans || p.name || p.id)) || p.id || '';
|
|
243
|
+
}
|
|
244
|
+
return String(p || '');
|
|
245
|
+
}).filter(Boolean);
|
|
246
|
+
|
|
247
|
+
const bundledRd = (core.reference_designs || []).find((r) => r.id === docId);
|
|
248
|
+
const repoUrl = doc.repo_url || bundledRd?.repo_url || '';
|
|
249
|
+
|
|
250
|
+
onlineRefDesigns.push({
|
|
251
|
+
type: 'reference_design',
|
|
252
|
+
id: docId,
|
|
253
|
+
title,
|
|
254
|
+
summary,
|
|
255
|
+
presets,
|
|
256
|
+
landing_url: appendUtm(`https://www.seeedstudio.com/solutions/reference-designs/${docId}`, {
|
|
257
|
+
source: 'sensecraft_agent',
|
|
258
|
+
medium: 'solution_consult',
|
|
259
|
+
campaign: docId,
|
|
260
|
+
content: 'solution_landing',
|
|
261
|
+
}),
|
|
262
|
+
wiki_url: doc.wiki_url ? appendUtm(doc.wiki_url, {
|
|
263
|
+
source: 'sensecraft_agent',
|
|
264
|
+
medium: 'tech_wiki',
|
|
265
|
+
campaign: docId,
|
|
266
|
+
}) : '',
|
|
267
|
+
repo_url: repoUrl,
|
|
268
|
+
diagnostic_playbook: pb,
|
|
269
|
+
source: 'typesense',
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// If one collection returned results but the other was unavailable/failed/empty, supplement from bundled catalog
|
|
276
|
+
if ((onlineScenarios.length > 0 && onlineRefDesigns.length === 0) || (onlineRefDesigns.length > 0 && onlineScenarios.length === 0)) {
|
|
277
|
+
const tokens = qLower.split(/\s+/).filter(Boolean);
|
|
278
|
+
if (onlineScenarios.length === 0) {
|
|
279
|
+
for (const s of core.scenarios || []) {
|
|
280
|
+
if (s.published === false || s.visibility === 'staging' || s.visibility === 'draft') continue;
|
|
281
|
+
const locKey = locale === 'zh' ? 'zh_hans' : locale;
|
|
282
|
+
if (s[`visibility_${locKey}`] === 'staging' || s[`visibility_${locKey}`] === 'draft') continue;
|
|
283
|
+
|
|
284
|
+
const haystack = [s.id, s.title?.zh, s.title?.en, s.subtitle?.zh, s.subtitle?.en, s.summary, ...(s.tags || [])].join(' ').toLowerCase();
|
|
285
|
+
const score = tokens.reduce((acc, t) => (haystack.includes(t) ? acc + 1 : acc), 0);
|
|
286
|
+
if (tokens.length === 0 || score > 0) {
|
|
287
|
+
onlineScenarios.push({
|
|
288
|
+
type: 'scenario',
|
|
289
|
+
id: s.id,
|
|
290
|
+
title: s.title?.[locale] || s.title?.zh || s.title?.en || s.id,
|
|
291
|
+
summary: s.subtitle?.[locale] || s.subtitle?.zh || s.summary || '',
|
|
292
|
+
tags: s.tags || [],
|
|
293
|
+
family_ids: s.family_ids || [],
|
|
294
|
+
reference_designs: s.reference_designs || [],
|
|
295
|
+
landing_url: appendUtm(s.landing_url || `https://www.seeedstudio.com/solutions/${s.id}`, {
|
|
296
|
+
source: 'sensecraft_agent',
|
|
297
|
+
medium: 'solution_consult',
|
|
298
|
+
campaign: s.id,
|
|
299
|
+
content: 'scenario_landing',
|
|
300
|
+
}),
|
|
301
|
+
repo_url: s.repo_url || '',
|
|
302
|
+
diagnostic_playbook: s.diagnostic_playbook || null,
|
|
303
|
+
source: 'bundled_manifest',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (onlineRefDesigns.length === 0) {
|
|
309
|
+
for (const rd of core.reference_designs || []) {
|
|
310
|
+
if (rd.published === false || rd.visibility === 'staging' || rd.visibility === 'draft') continue;
|
|
311
|
+
const locKey = locale === 'zh' ? 'zh_hans' : locale;
|
|
312
|
+
if (rd[`visibility_${locKey}`] === 'staging' || rd[`visibility_${locKey}`] === 'draft') continue;
|
|
313
|
+
|
|
314
|
+
const haystack = [rd.id, rd.title?.zh, rd.title?.en, rd.summary?.zh, rd.summary?.en, ...(rd.presets || [])].join(' ').toLowerCase();
|
|
315
|
+
const score = tokens.reduce((acc, t) => (haystack.includes(t) ? acc + 1 : acc), 0);
|
|
316
|
+
if (tokens.length === 0 || score > 0) {
|
|
317
|
+
onlineRefDesigns.push({
|
|
318
|
+
type: 'reference_design',
|
|
319
|
+
id: rd.id,
|
|
320
|
+
title: rd.title?.[locale] || rd.title?.zh || rd.title?.en || rd.id,
|
|
321
|
+
summary: rd.summary?.[locale] || rd.summary?.zh || rd.summary?.en || '',
|
|
322
|
+
presets: rd.presets || [],
|
|
323
|
+
landing_url: appendUtm(rd.landing_url || `https://www.seeedstudio.com/solutions/reference-designs/${rd.id}`, {
|
|
324
|
+
source: 'sensecraft_agent',
|
|
325
|
+
medium: 'solution_consult',
|
|
326
|
+
campaign: rd.id,
|
|
327
|
+
content: 'solution_landing',
|
|
328
|
+
}),
|
|
329
|
+
wiki_url: rd.wiki_url ? appendUtm(rd.wiki_url, {
|
|
330
|
+
source: 'sensecraft_agent',
|
|
331
|
+
medium: 'tech_wiki',
|
|
332
|
+
campaign: rd.id,
|
|
333
|
+
}) : '',
|
|
334
|
+
repo_url: rd.repo_url || '',
|
|
335
|
+
diagnostic_playbook: rd.diagnostic_playbook || null,
|
|
336
|
+
source: 'bundled_manifest',
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const mergedRecords = [];
|
|
344
|
+
const maxOnline = Math.max(onlineScenarios.length, onlineRefDesigns.length);
|
|
345
|
+
for (let i = 0; i < maxOnline && mergedRecords.length < limit; i++) {
|
|
346
|
+
if (i < onlineScenarios.length && mergedRecords.length < limit) {
|
|
347
|
+
mergedRecords.push(onlineScenarios[i]);
|
|
348
|
+
}
|
|
349
|
+
if (i < onlineRefDesigns.length && mergedRecords.length < limit) {
|
|
350
|
+
mergedRecords.push(onlineRefDesigns[i]);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (mergedRecords.length > 0) {
|
|
97
355
|
return createCatalogDto({
|
|
98
|
-
source: 'typesense',
|
|
356
|
+
source: (onlineScenarios.some(r => r.source === 'typesense') || onlineRefDesigns.some(r => r.source === 'typesense')) ? 'typesense' : 'bundled_manifest',
|
|
99
357
|
freshness: 'FRESH',
|
|
100
|
-
records:
|
|
358
|
+
records: mergedRecords,
|
|
101
359
|
});
|
|
102
360
|
}
|
|
103
|
-
} catch
|
|
361
|
+
} catch {
|
|
104
362
|
// Graceful fallback to offline core with notice
|
|
105
363
|
}
|
|
106
364
|
}
|
|
@@ -113,6 +371,10 @@ export async function searchHub({ query = '', locale = 'zh', limit = 5 } = {}) {
|
|
|
113
371
|
const tokens = qLower.split(/\s+/).filter(Boolean);
|
|
114
372
|
|
|
115
373
|
for (const s of core.scenarios || []) {
|
|
374
|
+
if (s.published === false || s.visibility === 'staging' || s.visibility === 'draft') continue;
|
|
375
|
+
const locKey = locale === 'zh' ? 'zh_hans' : locale;
|
|
376
|
+
if (s[`visibility_${locKey}`] === 'staging' || s[`visibility_${locKey}`] === 'draft') continue;
|
|
377
|
+
|
|
116
378
|
const haystack = [
|
|
117
379
|
s.id,
|
|
118
380
|
s.title?.zh,
|
|
@@ -133,12 +395,24 @@ export async function searchHub({ query = '', locale = 'zh', limit = 5 } = {}) {
|
|
|
133
395
|
tags: s.tags || [],
|
|
134
396
|
family_ids: s.family_ids || [],
|
|
135
397
|
reference_designs: s.reference_designs || [],
|
|
398
|
+
landing_url: appendUtm(s.landing_url || `https://www.seeedstudio.com/solutions/${s.id}`, {
|
|
399
|
+
source: 'sensecraft_agent',
|
|
400
|
+
medium: 'solution_consult',
|
|
401
|
+
campaign: s.id,
|
|
402
|
+
content: 'scenario_landing',
|
|
403
|
+
}),
|
|
404
|
+
repo_url: s.repo_url || '',
|
|
405
|
+
diagnostic_playbook: s.diagnostic_playbook || null,
|
|
136
406
|
score,
|
|
137
407
|
});
|
|
138
408
|
}
|
|
139
409
|
}
|
|
140
410
|
|
|
141
411
|
for (const rd of core.reference_designs || []) {
|
|
412
|
+
if (rd.published === false || rd.visibility === 'staging' || rd.visibility === 'draft') continue;
|
|
413
|
+
const locKey = locale === 'zh' ? 'zh_hans' : locale;
|
|
414
|
+
if (rd[`visibility_${locKey}`] === 'staging' || rd[`visibility_${locKey}`] === 'draft') continue;
|
|
415
|
+
|
|
142
416
|
const haystack = [
|
|
143
417
|
rd.id,
|
|
144
418
|
rd.title?.zh,
|
|
@@ -156,7 +430,19 @@ export async function searchHub({ query = '', locale = 'zh', limit = 5 } = {}) {
|
|
|
156
430
|
title: rd.title?.[locale] || rd.title?.zh || rd.title?.en || rd.id,
|
|
157
431
|
summary: rd.summary?.[locale] || rd.summary?.zh || rd.summary?.en || '',
|
|
158
432
|
presets: rd.presets || [],
|
|
159
|
-
|
|
433
|
+
landing_url: appendUtm(rd.landing_url || `https://www.seeedstudio.com/solutions/reference-designs/${rd.id}`, {
|
|
434
|
+
source: 'sensecraft_agent',
|
|
435
|
+
medium: 'solution_consult',
|
|
436
|
+
campaign: rd.id,
|
|
437
|
+
content: 'solution_landing',
|
|
438
|
+
}),
|
|
439
|
+
wiki_url: rd.wiki_url ? appendUtm(rd.wiki_url, {
|
|
440
|
+
source: 'sensecraft_agent',
|
|
441
|
+
medium: 'tech_wiki',
|
|
442
|
+
campaign: rd.id,
|
|
443
|
+
}) : '',
|
|
444
|
+
repo_url: rd.repo_url || '',
|
|
445
|
+
diagnostic_playbook: rd.diagnostic_playbook || null,
|
|
160
446
|
score,
|
|
161
447
|
});
|
|
162
448
|
}
|
|
@@ -166,15 +452,24 @@ export async function searchHub({ query = '', locale = 'zh', limit = 5 } = {}) {
|
|
|
166
452
|
matchedScenarios.sort((a, b) => b.score - a.score);
|
|
167
453
|
matchedRefDesigns.sort((a, b) => b.score - a.score);
|
|
168
454
|
|
|
169
|
-
const combined = [
|
|
455
|
+
const combined = [];
|
|
456
|
+
const maxOffline = Math.max(matchedScenarios.length, matchedRefDesigns.length);
|
|
457
|
+
for (let i = 0; i < maxOffline && combined.length < limit; i++) {
|
|
458
|
+
if (i < matchedScenarios.length && combined.length < limit) {
|
|
459
|
+
combined.push(matchedScenarios[i]);
|
|
460
|
+
}
|
|
461
|
+
if (i < matchedRefDesigns.length && combined.length < limit) {
|
|
462
|
+
combined.push(matchedRefDesigns[i]);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
170
465
|
|
|
171
466
|
return createCatalogDto({
|
|
172
467
|
source: 'bundled_manifest',
|
|
173
468
|
freshness: 'FRESH',
|
|
174
469
|
records: combined,
|
|
175
|
-
warnings:
|
|
176
|
-
? ['Online Typesense request timed out
|
|
177
|
-
: ['No Typesense API key configured. Displaying verified local catalog.'],
|
|
470
|
+
warnings: (keySolution || keyRefDesign)
|
|
471
|
+
? ['Online Typesense request timed out or returned no hits. Displaying verified local catalog.']
|
|
472
|
+
: ['No Typesense API key configured or baked. Displaying verified local catalog.'],
|
|
178
473
|
});
|
|
179
474
|
}
|
|
180
475
|
|
|
@@ -208,7 +503,15 @@ export function queryProductFamily(familyId, { locale = 'zh' } = {}) {
|
|
|
208
503
|
default_sku: family.default_sku,
|
|
209
504
|
regions: family.regions || [],
|
|
210
505
|
skus_count: (family.skus || []).length,
|
|
211
|
-
skus: (family.skus || []).slice(0, 10)
|
|
506
|
+
skus: (family.skus || []).slice(0, 10).map((s) => ({
|
|
507
|
+
...s,
|
|
508
|
+
url: appendUtm(s.url, {
|
|
509
|
+
source: 'sensecraft_agent',
|
|
510
|
+
medium: 'bom_recommendation',
|
|
511
|
+
campaign: family.id,
|
|
512
|
+
content: `sku_${s.sku}`,
|
|
513
|
+
}),
|
|
514
|
+
})),
|
|
212
515
|
physical_spec: family.physical_spec || null,
|
|
213
516
|
required_accessories_count: (family.required_accessories || []).length,
|
|
214
517
|
optional_accessories_count: (family.optional_accessories || []).length,
|
|
@@ -248,7 +551,12 @@ export function queryAccessories(familyId, { sku = null, locale = 'zh' } = {}) {
|
|
|
248
551
|
id: acc.id,
|
|
249
552
|
name: acc.name,
|
|
250
553
|
sku: acc.sku,
|
|
251
|
-
url: acc.url,
|
|
554
|
+
url: appendUtm(acc.url, {
|
|
555
|
+
source: 'sensecraft_agent',
|
|
556
|
+
medium: 'bom_recommendation',
|
|
557
|
+
campaign: family.id,
|
|
558
|
+
content: `required_${acc.id}`,
|
|
559
|
+
}),
|
|
252
560
|
qty: acc.qty || 1,
|
|
253
561
|
reason: acc.reason || 'Required for standard operation',
|
|
254
562
|
applies_when: acc.applies_when || null,
|
|
@@ -268,7 +576,12 @@ export function queryAccessories(familyId, { sku = null, locale = 'zh' } = {}) {
|
|
|
268
576
|
id: acc.id,
|
|
269
577
|
name: acc.name,
|
|
270
578
|
sku: acc.sku,
|
|
271
|
-
url: acc.url,
|
|
579
|
+
url: appendUtm(acc.url, {
|
|
580
|
+
source: 'sensecraft_agent',
|
|
581
|
+
medium: 'bom_recommendation',
|
|
582
|
+
campaign: family.id,
|
|
583
|
+
content: `optional_${acc.id}`,
|
|
584
|
+
}),
|
|
272
585
|
type: acc.type || 'peripheral',
|
|
273
586
|
compatibility: {
|
|
274
587
|
is_compatible: comp.isCompatible,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seeed-studio/solution-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
"lib",
|
|
20
20
|
"catalog",
|
|
21
21
|
"skills",
|
|
22
|
+
"docs",
|
|
22
23
|
"README.md"
|
|
23
24
|
],
|
|
24
25
|
"scripts": {
|
|
26
|
+
"build": "node scripts/bake-client-config.mjs",
|
|
27
|
+
"prepack": "node scripts/bake-client-config.mjs",
|
|
25
28
|
"test": "node --test tests/cli.test.mjs",
|
|
26
29
|
"install:self": "node bin/cli.js install",
|
|
27
30
|
"status": "node bin/cli.js status",
|
|
@@ -16,7 +16,9 @@ Do NOT read all reference files at once. Determine the user's workflow state and
|
|
|
16
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
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
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
|
|
19
|
+
| **DEPLOYING / APPLY**<br>“开始安装”、“部署 fall_detection” | `references/deploy.md` | `sensecraft-engine deploy <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`. |
|
|
20
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. |
|
|
21
23
|
|
|
22
24
|
Capabilities that need the **SenseCraft Solution desktop app** (editing a
|
|
@@ -76,7 +78,7 @@ daemon is a host prerequisite, not evidence that a deployment has passed.
|
|
|
76
78
|
you checked, what you changed). Transient registry or network errors follow
|
|
77
79
|
the backoff rule in `references/deploy.md` §6 and do not use up a round.
|
|
78
80
|
4. **Preserving official cache & Local auto-patching**: Never edit files directly under the official content cache directory (`~/.sensecraft/content/`). When fixing a solution-level bug (timeout too short, wrong port/env var, minor syntax/transfer glitch, broken health check):
|
|
79
|
-
- In CLI / Engine mode: copy the solution from `~/.sensecraft/content/solutions/<id>` into `./.sensecraft-work/solutions/<id>`, apply the minimal fix in that copy.
|
|
81
|
+
- In CLI / Engine mode: copy the solution from `~/.sensecraft/content/solutions/<id>` into `./.sensecraft-work/solutions/<id>`, apply the minimal fix in that copy. Then re-run deployment with `sensecraft-engine deploy <id> --solutions-dir ./.sensecraft-work/solutions` so the engine picks up the patched files. Show the user the patch diff.
|
|
80
82
|
- If running with the Desktop App: use the app's edit copy mechanism (`references/app-only.md` §4).
|
|
81
83
|
5. **Reading output:** read the CLI's JSON output directly; it is small. If a
|
|
82
84
|
filter is needed, use `jq` only when it is already installed. Do not start
|
|
@@ -86,8 +88,22 @@ daemon is a host prerequisite, not evidence that a deployment has passed.
|
|
|
86
88
|
6. **Temp files:** keep request bodies and scratch files in the current
|
|
87
89
|
working directory under `./.sensecraft-work/`, never in `/tmp`. Remove the
|
|
88
90
|
directory when the task ends, also after a failure.
|
|
89
|
-
|
|
90
|
-
|
|
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.
|
|
105
|
+
|
|
106
|
+
## Step 0 — Install the engine and the solutions (when needed)
|
|
91
107
|
|
|
92
108
|
1. Check whether it is installed: `sensecraft-engine --version`. If the command
|
|
93
109
|
is not found, also try `~/.sensecraft/bin/sensecraft-engine --version`
|
|
@@ -105,14 +121,28 @@ daemon is a host prerequisite, not evidence that a deployment has passed.
|
|
|
105
121
|
command and stop; do not install packages or start another interpreter.
|
|
106
122
|
`minisign` is required to verify the signed release manifest; Unix `jq` is
|
|
107
123
|
required by the shell installer to read it.
|
|
108
|
-
3.
|
|
109
|
-
it downloads a program)
|
|
124
|
+
3. If it is not installed, install it together with the solutions (ask the user
|
|
125
|
+
first, it downloads a program). If the user explicitly asks to upgrade or
|
|
126
|
+
reinstall, use the same solution-cli command; do not reinstall on every
|
|
127
|
+
deployment:
|
|
110
128
|
|
|
111
129
|
**Recommended (Zero-friction via solution-cli, auto-detects existing app & agents)**:
|
|
112
130
|
```bash
|
|
113
|
-
npx @seeed-studio/solution-cli install -y
|
|
131
|
+
npx -y @seeed-studio/solution-cli@latest install -y
|
|
114
132
|
```
|
|
115
133
|
|
|
134
|
+
The command checks the signed stable Engine on fresh and repeat runs, reuses
|
|
135
|
+
an equal or newer local version, and upgrades an older one. It preserves
|
|
136
|
+
modified/unregistered skills and protected symlinks. Add `--agent ...` for an
|
|
137
|
+
explicit agent selection or `--engine-path <path>` for a valid custom pin.
|
|
138
|
+
`--force` can re-download equal/older versions and overwrite existing skill
|
|
139
|
+
files, including user modifications; use it only when that overwrite is
|
|
140
|
+
intended. Protected external symlinks are not followed, and it never
|
|
141
|
+
downgrades a newer discovered Engine. Automatic upgrade rules start with
|
|
142
|
+
solution-cli 0.3.1; if npm `latest` is still 0.3.0, those rules are not live.
|
|
143
|
+
At this verification, 0.3.1 is pending publication. Latest/signature failure
|
|
144
|
+
stops before writes; offline use requires a valid explicit local Engine path.
|
|
145
|
+
|
|
116
146
|
**Fallback (Standalone shell script when Node.js is not present)**:
|
|
117
147
|
```bash
|
|
118
148
|
curl --compressed -fsSL https://sensecraft-statics.seeed.cc/solution-app/engine/install.sh | sh -s -- --with-content
|
|
@@ -146,9 +176,8 @@ daemon is a host prerequisite, not evidence that a deployment has passed.
|
|
|
146
176
|
list` or `deploy-info` exits with code 3 when no usable resource/solution
|
|
147
177
|
directory is available and tells you to run `content pull`. If a usable
|
|
148
178
|
directory exists but the requested solution ID is unknown, the query exits
|
|
149
|
-
with code 2. After `content pull` / `update`,
|
|
150
|
-
|
|
151
|
-
`sensecraft-engine daemon stop` when no deployment is running.
|
|
179
|
+
with code 2. After `content pull` / `update`, newly downloaded or updated
|
|
180
|
+
solutions are available to subsequent deployment and query commands.
|
|
152
181
|
5. Device pre-checks on this computer: `sensecraft-engine doctor --json`. Each
|
|
153
182
|
check has `status` (`ok` / `warn` / `fail`) and a `fix` command. Show the
|
|
154
183
|
user any `fail` and its `fix`; running a `fix` changes this computer and
|