@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.
package/bin/cli.js CHANGED
@@ -4,6 +4,8 @@ import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { runInstall, runStatus, runUninstall } from '../lib/installer.js';
6
6
  import { runDoctor } from '../lib/doctor.js';
7
+ import { searchHub, queryProductFamily, queryAccessories, createDeploymentLockfile } from '../lib/knowledge.js';
8
+ import { assertCompatibility } from '../lib/compatibility.js';
7
9
 
8
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
11
  const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8'));
@@ -25,6 +27,14 @@ Commands:
25
27
  status Display current engine, solution content, and agent skills status
26
28
  doctor Diagnose environment (platform, permissions, docker, PATH, ledger)
27
29
  uninstall Cleanly remove SenseCraft skills from specified agent(s)
30
+ hub Search scenarios and reference designs from Seeed Solution Hub
31
+ Usage: solution-cli hub search <keyword> [--limit 5] [--locale zh|en] [--json]
32
+ product Query product families, SKUs, and verified accessories
33
+ Usage: solution-cli product query <family_id> [--locale zh|en] [--json]
34
+ solution-cli product accessories <family_id> [--sku <sku>] [--json]
35
+ solution-cli product verify-compatibility --host <family_id> [--json]
36
+ lockfile Create an immutable DeploymentLockfile to isolate deployment execution
37
+ Usage: solution-cli lockfile create --solution <id> [--version <v>] [--hardware <fams>] [--json]
28
38
  help Show this help message
29
39
 
30
40
  Options:
@@ -33,13 +43,16 @@ Options:
33
43
  --dry-run Simulate operations without making changes to filesystem
34
44
  --skip-content Skip pulling IoT solution packages from CDN
35
45
  --force Force re-download or re-link even if already installed
36
- --json Output machine-readable JSON (supported by status and doctor)
46
+ --json Output machine-readable JSON (supported by all query/diagnostic commands)
47
+ --locale <zh|en> Language for hub and product results (default: zh)
48
+ --limit <num> Maximum results for search (default: 5)
37
49
  --engine-path <path> Use custom engine executable path
38
50
 
39
51
  Examples:
40
52
  npx @seeed-studio/solution-cli install
41
- npx @seeed-studio/solution-cli install --agent workbuddy
42
- npx @seeed-studio/solution-cli install --agent workbuddy,claude -y
53
+ solution-cli hub search "跌倒"
54
+ solution-cli product accessories recomputer_j40
55
+ solution-cli product query sensecap_watcher --json
43
56
  solution-cli status
44
57
  solution-cli doctor
45
58
  `);
@@ -53,7 +66,15 @@ function parseFlags(rawArgs) {
53
66
  skipContent: false,
54
67
  force: false,
55
68
  json: false,
56
- enginePath: null
69
+ enginePath: null,
70
+ limit: 5,
71
+ locale: 'zh',
72
+ sku: null,
73
+ host: null,
74
+ accessory: null,
75
+ solution: null,
76
+ version: '1.0.0',
77
+ hardware: null,
57
78
  };
58
79
 
59
80
  for (let i = 0; i < rawArgs.length; i++) {
@@ -74,6 +95,22 @@ function parseFlags(rawArgs) {
74
95
  flags.json = true;
75
96
  } else if (a === '--engine-path' && rawArgs[i + 1]) {
76
97
  flags.enginePath = rawArgs[++i];
98
+ } else if (a === '--limit' && rawArgs[i + 1]) {
99
+ flags.limit = parseInt(rawArgs[++i], 10) || 5;
100
+ } else if (a === '--locale' && rawArgs[i + 1]) {
101
+ flags.locale = rawArgs[++i];
102
+ } else if (a === '--sku' && rawArgs[i + 1]) {
103
+ flags.sku = rawArgs[++i];
104
+ } else if (a === '--host' && rawArgs[i + 1]) {
105
+ flags.host = rawArgs[++i];
106
+ } else if (a === '--accessory' && rawArgs[i + 1]) {
107
+ flags.accessory = rawArgs[++i];
108
+ } else if (a === '--solution' && rawArgs[i + 1]) {
109
+ flags.solution = rawArgs[++i];
110
+ } else if (a === '--version' && rawArgs[i + 1]) {
111
+ flags.version = rawArgs[++i];
112
+ } else if (a === '--hardware' && rawArgs[i + 1]) {
113
+ flags.hardware = rawArgs[++i];
77
114
  }
78
115
  }
79
116
 
@@ -123,6 +160,160 @@ async function main() {
123
160
  runUninstall(flags);
124
161
  break;
125
162
 
163
+ case 'hub': {
164
+ const sub = args[1] || 'search';
165
+ if (sub !== 'search') {
166
+ console.error(`Unknown hub action "${sub}". Supported: "solution-cli hub search <keyword>"`);
167
+ process.exit(1);
168
+ }
169
+ const query = args.slice(2).filter((a) => !a.startsWith('-')).join(' ');
170
+ const dto = await searchHub({ query, locale: flags.locale, limit: flags.limit });
171
+ if (flags.json) {
172
+ console.log(JSON.stringify(dto, null, 2));
173
+ } else {
174
+ console.log(`\n🔍 Solutions Hub Search Results for "${query}" (${dto.source}):\n`);
175
+ if (dto.records.length === 0) {
176
+ console.log(' No matching scenarios or reference designs found.');
177
+ } else {
178
+ dto.records.forEach((r, idx) => {
179
+ const tag = r.type === 'scenario' ? '\x1b[36m[Scenario]\x1b[0m' : '\x1b[35m[Reference Design]\x1b[0m';
180
+ console.log(` ${idx + 1}. ${tag} \x1b[1m${r.title}\x1b[0m (${r.id})`);
181
+ if (r.summary) console.log(` ${r.summary}`);
182
+ if (r.reference_designs?.length) console.log(` 对应参考设计: ${r.reference_designs.join(', ')}`);
183
+ if (r.family_ids?.length) console.log(` 推荐硬件族: ${r.family_ids.join(', ')}`);
184
+ if (r.presets?.length) console.log(` 预设模式: ${r.presets.join(', ')}`);
185
+ if (r.wiki_url) console.log(` 开发 Wiki: ${r.wiki_url}`);
186
+ console.log('');
187
+ });
188
+ }
189
+ if (dto.warnings.length) {
190
+ dto.warnings.forEach((w) => console.log(`\x1b[33mNote:\x1b[0m ${w}`));
191
+ }
192
+ }
193
+ break;
194
+ }
195
+
196
+ case 'product': {
197
+ const sub = args[1] || 'query';
198
+ const familyId = args.slice(2).find((a) => !a.startsWith('-')) || flags.host;
199
+
200
+ if (sub === 'query') {
201
+ if (!familyId) {
202
+ console.error('Please specify a product family ID (e.g. "solution-cli product query recomputer_j40")');
203
+ process.exit(1);
204
+ }
205
+ const dto = queryProductFamily(familyId, { locale: flags.locale });
206
+ if (flags.json) {
207
+ console.log(JSON.stringify(dto, null, 2));
208
+ } else {
209
+ const fam = dto.records[0];
210
+ if (!fam) {
211
+ console.log(`Product family "${familyId}" not found.`);
212
+ } else {
213
+ console.log(`\n📦 Product Family: \x1b[1m${fam.title}\x1b[0m (${fam.family_id})`);
214
+ console.log(` Default SKU: ${fam.default_sku || 'N/A'}`);
215
+ console.log(` Regions: ${(fam.regions || []).join(', ') || 'Global'}`);
216
+ console.log(` SKUs Available: ${fam.skus_count}`);
217
+ if (fam.physical_spec?.power_input) {
218
+ const p = fam.physical_spec.power_input;
219
+ console.log(` Power Spec: ${p.voltage_v || ''}V ${p.min_current_a || ''}A (${p.connector}) ${p.polarity || ''}`);
220
+ }
221
+ console.log(` Required Accessories: ${fam.required_accessories_count}`);
222
+ console.log(` Optional Accessories: ${fam.optional_accessories_count}`);
223
+ }
224
+ }
225
+ } else if (sub === 'accessories') {
226
+ if (!familyId) {
227
+ console.error('Please specify a product family ID (e.g. "solution-cli product accessories recomputer_j40")');
228
+ process.exit(1);
229
+ }
230
+ const dto = queryAccessories(familyId, { sku: flags.sku, locale: flags.locale });
231
+ if (flags.json) {
232
+ console.log(JSON.stringify(dto, null, 2));
233
+ } else {
234
+ const record = dto.records[0];
235
+ if (!record) {
236
+ console.log(`Product family "${familyId}" not found.`);
237
+ } else {
238
+ console.log(`\n📦 Verified Accessories for \x1b[1m${record.host_title}\x1b[0m (${record.family_id}):`);
239
+ console.log(` Host Default SKU: ${record.host_sku}`);
240
+ if (record.physical_spec?.power_input) {
241
+ const p = record.physical_spec.power_input;
242
+ console.log(` Host Power Spec: ${p.voltage_v || ''}V ${p.min_current_a || ''}A (${p.connector || ''})`);
243
+ }
244
+
245
+ console.log('\n \x1b[33m[REQUIRED (必需配件)]\x1b[0m');
246
+ if (record.accessories.required.length === 0) {
247
+ console.log(' (无强制单独采购的配件)');
248
+ } else {
249
+ record.accessories.required.forEach((acc) => {
250
+ const stat = acc.compatibility.is_compatible ? '\x1b[32m✔\x1b[0m' : '\x1b[31m✖\x1b[0m';
251
+ console.log(` ${stat} \x1b[1m${acc.name}\x1b[0m (SKU: ${acc.sku || 'N/A'}, Qty: ${acc.qty})`);
252
+ if (acc.reason) console.log(` 原因: ${acc.reason}`);
253
+ if (acc.url) console.log(` 购买链接: ${acc.url}`);
254
+ });
255
+ }
256
+
257
+ console.log('\n \x1b[36m[OPTIONAL (推荐选配)]\x1b[0m');
258
+ if (record.accessories.optional.length === 0) {
259
+ console.log(' (无选配项)');
260
+ } else {
261
+ record.accessories.optional.slice(0, 10).forEach((acc) => {
262
+ const stat = acc.compatibility.is_compatible ? '\x1b[32m✔\x1b[0m' : '\x1b[31m✖\x1b[0m';
263
+ console.log(` ${stat} ${acc.name} (SKU: ${acc.sku || 'N/A'})`);
264
+ if (acc.url) console.log(` 购买链接: ${acc.url}`);
265
+ });
266
+ }
267
+ console.log('');
268
+ }
269
+ }
270
+ } else if (sub === 'verify-compatibility') {
271
+ const comp = assertCompatibility(null, {
272
+ id: flags.accessory || 'custom_accessory',
273
+ name: flags.accessory || 'Custom Accessory',
274
+ });
275
+ if (flags.json) {
276
+ console.log(JSON.stringify(comp, null, 2));
277
+ } else {
278
+ const sym = comp.isCompatible ? '\x1b[32m✔\x1b[0m' : '\x1b[31m✖\x1b[0m';
279
+ console.log(`\n${sym} Physical Compatibility Status: ${comp.status}\n`);
280
+ comp.dimensions.forEach((d) => {
281
+ console.log(` - [${d.dimension}] ${d.status}: ${d.reason}`);
282
+ });
283
+ console.log('');
284
+ }
285
+ } else {
286
+ console.error(`Unknown product action "${sub}". Supported: query, accessories, verify-compatibility`);
287
+ process.exit(1);
288
+ }
289
+ break;
290
+ }
291
+
292
+ case 'lockfile': {
293
+ const sub = args[1] || 'create';
294
+ if (sub !== 'create') {
295
+ console.error(`Unknown lockfile action "${sub}". Supported: "solution-cli lockfile create"`);
296
+ process.exit(1);
297
+ }
298
+ const solutionId = flags.solution || 'unspecified_solution';
299
+ const hardware = (flags.hardware || '').split(',').map((h) => h.trim()).filter(Boolean);
300
+ const lockfile = createDeploymentLockfile({
301
+ solutionId,
302
+ solutionVersion: flags.version,
303
+ selectedHardware: hardware,
304
+ });
305
+ if (flags.json) {
306
+ console.log(JSON.stringify(lockfile, null, 2));
307
+ } else {
308
+ console.log('\n🔒 Generated DeploymentLockfile:');
309
+ console.log(` Solution: ${lockfile.solution_id}@${lockfile.solution_version}`);
310
+ console.log(` Dataset: ${lockfile.dataset_version}`);
311
+ console.log(` Network Isolation: Commercial queries DENIED during apply`);
312
+ console.log(` Selected Hardware: ${lockfile.selected_hardware.join(', ') || 'Local Host'}\n`);
313
+ }
314
+ break;
315
+ }
316
+
126
317
  default:
127
318
  console.error(`Unknown command: "${command}". Run "solution-cli help" for usage.`);
128
319
  process.exit(1);